update
This commit is contained in:
@@ -120,6 +120,7 @@ import traceback
|
||||
import threading
|
||||
import sys
|
||||
import sqlite3
|
||||
import struct
|
||||
import RPi.GPIO as GPIO
|
||||
from threading import Thread
|
||||
from datetime import datetime
|
||||
@@ -138,8 +139,6 @@ if uptime_seconds < 120:
|
||||
|
||||
#Payload CSV to be sent to data.nebuleair.fr
|
||||
payload_csv = [None] * 30
|
||||
#Payload UPD to be sent to miotiq
|
||||
payload_udp = [None] * 30
|
||||
|
||||
#Payload JSON to be sent to uSpot
|
||||
payload_json = {
|
||||
@@ -240,7 +239,6 @@ NOISE_sensor = config.get('NOISE', False)
|
||||
|
||||
#update device id in the payload json
|
||||
payload_json["nebuleairid"] = device_id
|
||||
payload_udp[0] = device_id
|
||||
|
||||
# Skip execution if modem_config_mode is true
|
||||
if modem_config_mode:
|
||||
@@ -256,6 +254,115 @@ ser_sara = serial.Serial(
|
||||
timeout = 2
|
||||
)
|
||||
|
||||
class SensorPayload:
|
||||
"""
|
||||
Class to manage a fixed 100-byte sensor payload
|
||||
All positions are predefined, no CSV intermediary
|
||||
"""
|
||||
|
||||
def __init__(self, device_id):
|
||||
# Initialize 100-byte array with 0xFF (no data marker)
|
||||
self.payload = bytearray(100)
|
||||
for i in range(100):
|
||||
self.payload[i] = 0xFF
|
||||
|
||||
# Set device ID (bytes 0-7)
|
||||
device_id_bytes = device_id.encode('ascii')[:8].ljust(8, b'\x00')
|
||||
#device_id_bytes = bytes.fromhex(device_id)[:8].ljust(8, b'\x00')
|
||||
|
||||
self.payload[0:8] = device_id_bytes
|
||||
|
||||
# Set protocol version (byte 9)
|
||||
self.payload[9] = 0x01
|
||||
|
||||
def set_signal_quality(self, value):
|
||||
"""Set 4G signal quality (byte 8)"""
|
||||
if value is not None:
|
||||
self.payload[8] = min(value, 255)
|
||||
|
||||
def set_npm_core(self, pm1, pm25, pm10):
|
||||
"""Set NPM core values (bytes 10-15)"""
|
||||
if pm1 is not None:
|
||||
self.payload[10:12] = struct.pack('>H', int(pm1 * 10))
|
||||
if pm25 is not None:
|
||||
self.payload[12:14] = struct.pack('>H', int(pm25 * 10))
|
||||
if pm10 is not None:
|
||||
self.payload[14:16] = struct.pack('>H', int(pm10 * 10))
|
||||
|
||||
def set_bme280(self, temperature, humidity, pressure):
|
||||
"""Set BME280 values (bytes 16-21)"""
|
||||
if temperature is not None:
|
||||
self.payload[16:18] = struct.pack('>h', int(temperature * 10)) # Signed
|
||||
if humidity is not None:
|
||||
self.payload[18:20] = struct.pack('>H', int(humidity * 10))
|
||||
if pressure is not None:
|
||||
self.payload[20:22] = struct.pack('>H', int(pressure))
|
||||
|
||||
def set_noise(self, avg_noise, max_noise=None, min_noise=None):
|
||||
"""Set noise values (bytes 22-27)"""
|
||||
if avg_noise is not None:
|
||||
self.payload[22:24] = struct.pack('>H', int(avg_noise * 10))
|
||||
if max_noise is not None:
|
||||
self.payload[24:26] = struct.pack('>H', int(max_noise * 10))
|
||||
if min_noise is not None:
|
||||
self.payload[26:28] = struct.pack('>H', int(min_noise * 10))
|
||||
|
||||
def set_envea(self, no2, h2s, nh3, co, o3):
|
||||
"""Set ENVEA gas sensor values (bytes 28-37)"""
|
||||
if no2 is not None:
|
||||
self.payload[28:30] = struct.pack('>H', int(no2))
|
||||
if h2s is not None:
|
||||
self.payload[30:32] = struct.pack('>H', int(h2s))
|
||||
if nh3 is not None:
|
||||
self.payload[32:34] = struct.pack('>H', int(nh3))
|
||||
if co is not None:
|
||||
self.payload[34:36] = struct.pack('>H', int(co))
|
||||
if o3 is not None:
|
||||
self.payload[36:38] = struct.pack('>H', int(o3))
|
||||
|
||||
def set_npm_5channels(self, ch1, ch2, ch3, ch4, ch5):
|
||||
"""Set NPM 5 channel values (bytes 38-47)"""
|
||||
channels = [ch1, ch2, ch3, ch4, ch5]
|
||||
for i, value in enumerate(channels):
|
||||
if value is not None:
|
||||
self.payload[38 + i*2:40 + i*2] = struct.pack('>H', int(value))
|
||||
|
||||
def set_npm_internal(self, temperature, humidity):
|
||||
"""Set NPM internal temp/humidity (bytes 48-51)"""
|
||||
if temperature is not None:
|
||||
self.payload[48:50] = struct.pack('>h', int(temperature * 10)) # Signed
|
||||
if humidity is not None:
|
||||
self.payload[50:52] = struct.pack('>H', int(humidity * 10))
|
||||
|
||||
def set_mppt(self, battery_voltage, battery_current, solar_voltage, solar_power, charger_status):
|
||||
"""Set MPPT charger values (bytes 52-61)"""
|
||||
if battery_voltage is not None:
|
||||
self.payload[52:54] = struct.pack('>H', int(battery_voltage * 10))
|
||||
if battery_current is not None:
|
||||
self.payload[54:56] = struct.pack('>h', int(battery_current * 10)) # Signed
|
||||
if solar_voltage is not None:
|
||||
self.payload[56:58] = struct.pack('>H', int(solar_voltage * 10))
|
||||
if solar_power is not None:
|
||||
self.payload[58:60] = struct.pack('>H', int(solar_power))
|
||||
if charger_status is not None:
|
||||
self.payload[60:62] = struct.pack('>H', int(charger_status))
|
||||
|
||||
def set_wind(self, speed, direction):
|
||||
"""Set wind meter values (bytes 62-65)"""
|
||||
if speed is not None:
|
||||
self.payload[62:64] = struct.pack('>H', int(speed * 10))
|
||||
if direction is not None:
|
||||
self.payload[64:66] = struct.pack('>H', int(direction))
|
||||
|
||||
def get_bytes(self):
|
||||
"""Get the complete 100-byte payload"""
|
||||
return bytes(self.payload)
|
||||
|
||||
def get_base64(self):
|
||||
"""Get base64 encoded payload for transmission"""
|
||||
import base64
|
||||
return base64.b64encode(self.payload).decode('ascii')
|
||||
|
||||
def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2, wait_for_lines=None, debug=True):
|
||||
'''
|
||||
Fonction très importante !!!
|
||||
@@ -608,6 +715,12 @@ try:
|
||||
|
||||
'''
|
||||
print('<h3>START LOOP</h3>')
|
||||
#payload = SensorPayload(device_id)
|
||||
payload = SensorPayload("484AE134")
|
||||
print("deviceID (ASCII):")
|
||||
print(payload.get_bytes()[:8].hex())
|
||||
|
||||
|
||||
#print(f'Modem version: {modem_version}')
|
||||
|
||||
#Local timestamp
|
||||
@@ -655,12 +768,18 @@ try:
|
||||
num_columns = len(data_values[0])
|
||||
averages = [round(sum(col) / len(col),1) for col in zip(*data_values)]
|
||||
|
||||
|
||||
PM1 = averages[0]
|
||||
PM25 = averages[1]
|
||||
PM10 = averages[2]
|
||||
npm_temp = averages[3]
|
||||
npm_hum = averages[4]
|
||||
|
||||
print(f"PM1: {PM1}")
|
||||
print(f"PM2.5: {PM25}")
|
||||
print(f"PM10: {PM10}")
|
||||
|
||||
|
||||
#Add data to payload CSV
|
||||
payload_csv[0] = PM1
|
||||
payload_csv[1] = PM25
|
||||
@@ -669,9 +788,8 @@ try:
|
||||
payload_csv[19] = npm_hum
|
||||
|
||||
#add data to payload UDP
|
||||
payload_udp[2] = PM1
|
||||
payload_udp[3] = PM25
|
||||
payload_udp[4] = PM10
|
||||
payload.set_npm_core(PM1, PM25, PM10)
|
||||
payload.set_npm_internal(npm_temp, npm_hum)
|
||||
|
||||
#Add data to payload JSON
|
||||
payload_json["sensordatavalues"].append({"value_type": "NPM_P0", "value": str(PM1)})
|
||||
@@ -714,9 +832,11 @@ try:
|
||||
payload_csv[5] = BME280_pressure
|
||||
|
||||
#Add data to payload UDP
|
||||
payload_udp[5] = BME280_temperature
|
||||
payload_udp[6] = BME280_humidity
|
||||
payload_udp[7] = BME280_pressure
|
||||
payload.set_bme280(
|
||||
temperature=last_row[1],
|
||||
humidity=last_row[2],
|
||||
pressure=last_row[3]
|
||||
)
|
||||
|
||||
#Add data to payload JSON
|
||||
payload_json["sensordatavalues"].append({"value_type": "BME280_temperature", "value": str(BME280_temperature)})
|
||||
@@ -749,6 +869,15 @@ try:
|
||||
payload_csv[27] = averages[3] # envea_CO
|
||||
payload_csv[28] = averages[4] # envea_O3
|
||||
|
||||
#Add data to payload UDP
|
||||
payload.set_envea(
|
||||
no2=averages[0],
|
||||
h2s=averages[1],
|
||||
nh3=averages[2],
|
||||
co=averages[3],
|
||||
o3=averages[4]
|
||||
)
|
||||
|
||||
#Add data to payload JSON
|
||||
payload_json["sensordatavalues"].append({"value_type": "CAIRSENS_NO2", "value": str(averages[0])})
|
||||
payload_json["sensordatavalues"].append({"value_type": "CAIRSENS_H2S", "value": str(averages[1])})
|
||||
@@ -768,6 +897,12 @@ try:
|
||||
payload_csv[25] = wind_speed
|
||||
payload_csv[26] = wind_direction
|
||||
|
||||
#Add data to payload UDP
|
||||
payload.set_wind(
|
||||
speed=last_row[1],
|
||||
direction=last_row[2]
|
||||
)
|
||||
|
||||
else:
|
||||
print("No data available in the database.")
|
||||
|
||||
@@ -791,6 +926,15 @@ try:
|
||||
payload_csv[22] = solar_voltage
|
||||
payload_csv[23] = solar_power
|
||||
payload_csv[24] = charger_status
|
||||
|
||||
#Add data to payload UDP
|
||||
payload.set_mppt(
|
||||
battery_voltage=last_row[1],
|
||||
battery_current=last_row[2],
|
||||
solar_voltage=last_row[3],
|
||||
solar_power=last_row[4],
|
||||
charger_status=last_row[5]
|
||||
)
|
||||
else:
|
||||
print("No data available in the database.")
|
||||
|
||||
@@ -807,6 +951,13 @@ try:
|
||||
#Add data to payload CSV
|
||||
payload_csv[6] = DB_A_value
|
||||
|
||||
#Add data to payload UDP
|
||||
payload.set_noise(
|
||||
avg_noise=last_row[2], # DB_A_value
|
||||
max_noise=None, # Add if available
|
||||
min_noise=None # Add if available
|
||||
)
|
||||
|
||||
#print("Verify SARA connection (AT)")
|
||||
|
||||
# Getting the LTE Signal (AT+CSQ)
|
||||
@@ -879,6 +1030,8 @@ try:
|
||||
if match:
|
||||
signal_quality = int(match.group(1))
|
||||
payload_csv[12]=signal_quality
|
||||
payload.set_signal_quality(signal_quality)
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
# On vérifie si le signal n'est pas à 99 pour déconnexion
|
||||
@@ -917,9 +1070,14 @@ try:
|
||||
if send_miotiq:
|
||||
print('<p class="fw-bold">➡️SEND TO MIOTIQ</p>', end="")
|
||||
|
||||
binary_data = payload.get_bytes()
|
||||
|
||||
print(f"Binary payload: {len(binary_data)} bytes")
|
||||
|
||||
|
||||
#create UDP socket (will return socket number) -> 17 is UDP protocol and 6 is TCP protocol
|
||||
# IF ERROR -> need to create the PDP connection
|
||||
print("Create Socket:")
|
||||
print("Create Socket:", end="")
|
||||
command = f'AT+USOCR=17\r'
|
||||
ser_sara.write(command.encode('utf-8'))
|
||||
response_SARA_1 = read_complete_response(ser_sara, wait_for_lines=["OK", "+CME ERROR", "ERROR"], debug=False)
|
||||
@@ -927,7 +1085,7 @@ try:
|
||||
print(response_SARA_1)
|
||||
print("</p>", end="")
|
||||
|
||||
if "+CME ERROR" in response_SARA_1:
|
||||
if "+CME ERROR" in response_SARA_1 or "ERROR" in response_SARA_1:
|
||||
print('<span style="color: red;font-weight: bold;">⚠️ATTENTION: need to reset PDP connection⚠️</span>')
|
||||
psd_csd_resets = reset_PSD_CSD_connection()
|
||||
if psd_csd_resets:
|
||||
@@ -945,7 +1103,7 @@ try:
|
||||
print("Failed to extract socket ID")
|
||||
|
||||
#Connect to UDP server (USOCO)
|
||||
print("Connect to server:")
|
||||
print("Connect to server:", end="")
|
||||
command = f'AT+USOCO={socket_id},"192.168.0.20",4242\r'
|
||||
ser_sara.write(command.encode('utf-8'))
|
||||
response_SARA_2 = read_complete_response(ser_sara, wait_for_lines=["OK", "+CME ERROR", "ERROR"], debug=False)
|
||||
@@ -953,37 +1111,34 @@ try:
|
||||
print(response_SARA_2)
|
||||
print("</p>", end="")
|
||||
|
||||
#prepare data
|
||||
csv_udp_string = ','.join(str(value) if value is not None else '' for value in payload_udp)
|
||||
size_of_udp_string = len(csv_udp_string)
|
||||
# Write data and send
|
||||
|
||||
# 4. Write data and send (USOWR)
|
||||
print("Write data:")
|
||||
print(csv_udp_string)
|
||||
command = f'AT+USOWR={socket_id},{size_of_udp_string}\r'
|
||||
print(f"Write data: {len(binary_data)} bytes")
|
||||
command = f'AT+USOWR={socket_id},{len(binary_data)}\r'
|
||||
ser_sara.write(command.encode('utf-8'))
|
||||
response_SARA_2 = read_complete_response(ser_sara, wait_for_lines=["@","OK", "+CME ERROR", "ERROR"], debug=False)
|
||||
print('<p class="text-danger-emphasis">')
|
||||
print(response_SARA_2)
|
||||
print("</p>", end="")
|
||||
|
||||
ser_sara.write(csv_udp_string.encode())
|
||||
# Send the raw payload bytes (already prepared)
|
||||
ser_sara.write(binary_data)
|
||||
response_SARA_2 = read_complete_response(ser_sara, wait_for_lines=["@","OK", "+CME ERROR", "ERROR"], debug=False)
|
||||
print('<p class="text-danger-emphasis">')
|
||||
print(response_SARA_2)
|
||||
print("</p>", end="")
|
||||
|
||||
#Read reply from server (USORD)
|
||||
print("Read reply:")
|
||||
command = f'AT+USORD=0,100\r'
|
||||
ser_sara.write(command.encode('utf-8'))
|
||||
response_SARA_2 = read_complete_response(ser_sara, wait_for_lines=["OK", "+CME ERROR", "ERROR"], debug=False)
|
||||
print('<p class="text-danger-emphasis">')
|
||||
print(response_SARA_2)
|
||||
print("</p>", end="")
|
||||
#print("Read reply:", end="")
|
||||
#command = f'AT+USORD=0,100\r'
|
||||
#ser_sara.write(command.encode('utf-8'))
|
||||
#response_SARA_2 = read_complete_response(ser_sara, wait_for_lines=["OK", "+CME ERROR", "ERROR"], debug=False)
|
||||
#print('<p class="text-danger-emphasis">')
|
||||
#print(response_SARA_2)
|
||||
#print("</p>", end="")
|
||||
|
||||
#Close socket
|
||||
print("Close socket:")
|
||||
print("Close socket:", end="")
|
||||
command = f'AT+USOCL={socket_id}\r'
|
||||
ser_sara.write(command.encode('utf-8'))
|
||||
response_SARA_2 = read_complete_response(ser_sara, wait_for_lines=["OK", "+CME ERROR", "ERROR"], debug=False)
|
||||
|
||||
Reference in New Issue
Block a user