This commit is contained in:
Your Name
2025-09-18 16:43:49 +01:00
parent fe61b56b5b
commit 4779f426d9

View File

@@ -120,6 +120,7 @@ import traceback
import threading import threading
import sys import sys
import sqlite3 import sqlite3
import struct
import RPi.GPIO as GPIO import RPi.GPIO as GPIO
from threading import Thread from threading import Thread
from datetime import datetime from datetime import datetime
@@ -138,8 +139,6 @@ if uptime_seconds < 120:
#Payload CSV to be sent to data.nebuleair.fr #Payload CSV to be sent to data.nebuleair.fr
payload_csv = [None] * 30 payload_csv = [None] * 30
#Payload UPD to be sent to miotiq
payload_udp = [None] * 30
#Payload JSON to be sent to uSpot #Payload JSON to be sent to uSpot
payload_json = { payload_json = {
@@ -240,7 +239,6 @@ NOISE_sensor = config.get('NOISE', False)
#update device id in the payload json #update device id in the payload json
payload_json["nebuleairid"] = device_id payload_json["nebuleairid"] = device_id
payload_udp[0] = device_id
# Skip execution if modem_config_mode is true # Skip execution if modem_config_mode is true
if modem_config_mode: if modem_config_mode:
@@ -256,6 +254,115 @@ ser_sara = serial.Serial(
timeout = 2 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): def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2, wait_for_lines=None, debug=True):
''' '''
Fonction très importante !!! Fonction très importante !!!
@@ -608,6 +715,12 @@ try:
''' '''
print('<h3>START LOOP</h3>') 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}') #print(f'Modem version: {modem_version}')
#Local timestamp #Local timestamp
@@ -655,12 +768,18 @@ try:
num_columns = len(data_values[0]) num_columns = len(data_values[0])
averages = [round(sum(col) / len(col),1) for col in zip(*data_values)] averages = [round(sum(col) / len(col),1) for col in zip(*data_values)]
PM1 = averages[0] PM1 = averages[0]
PM25 = averages[1] PM25 = averages[1]
PM10 = averages[2] PM10 = averages[2]
npm_temp = averages[3] npm_temp = averages[3]
npm_hum = averages[4] npm_hum = averages[4]
print(f"PM1: {PM1}")
print(f"PM2.5: {PM25}")
print(f"PM10: {PM10}")
#Add data to payload CSV #Add data to payload CSV
payload_csv[0] = PM1 payload_csv[0] = PM1
payload_csv[1] = PM25 payload_csv[1] = PM25
@@ -669,9 +788,8 @@ try:
payload_csv[19] = npm_hum payload_csv[19] = npm_hum
#add data to payload UDP #add data to payload UDP
payload_udp[2] = PM1 payload.set_npm_core(PM1, PM25, PM10)
payload_udp[3] = PM25 payload.set_npm_internal(npm_temp, npm_hum)
payload_udp[4] = PM10
#Add data to payload JSON #Add data to payload JSON
payload_json["sensordatavalues"].append({"value_type": "NPM_P0", "value": str(PM1)}) payload_json["sensordatavalues"].append({"value_type": "NPM_P0", "value": str(PM1)})
@@ -714,9 +832,11 @@ try:
payload_csv[5] = BME280_pressure payload_csv[5] = BME280_pressure
#Add data to payload UDP #Add data to payload UDP
payload_udp[5] = BME280_temperature payload.set_bme280(
payload_udp[6] = BME280_humidity temperature=last_row[1],
payload_udp[7] = BME280_pressure humidity=last_row[2],
pressure=last_row[3]
)
#Add data to payload JSON #Add data to payload JSON
payload_json["sensordatavalues"].append({"value_type": "BME280_temperature", "value": str(BME280_temperature)}) 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[27] = averages[3] # envea_CO
payload_csv[28] = averages[4] # envea_O3 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 #Add data to payload JSON
payload_json["sensordatavalues"].append({"value_type": "CAIRSENS_NO2", "value": str(averages[0])}) payload_json["sensordatavalues"].append({"value_type": "CAIRSENS_NO2", "value": str(averages[0])})
payload_json["sensordatavalues"].append({"value_type": "CAIRSENS_H2S", "value": str(averages[1])}) payload_json["sensordatavalues"].append({"value_type": "CAIRSENS_H2S", "value": str(averages[1])})
@@ -768,6 +897,12 @@ try:
payload_csv[25] = wind_speed payload_csv[25] = wind_speed
payload_csv[26] = wind_direction payload_csv[26] = wind_direction
#Add data to payload UDP
payload.set_wind(
speed=last_row[1],
direction=last_row[2]
)
else: else:
print("No data available in the database.") print("No data available in the database.")
@@ -791,6 +926,15 @@ try:
payload_csv[22] = solar_voltage payload_csv[22] = solar_voltage
payload_csv[23] = solar_power payload_csv[23] = solar_power
payload_csv[24] = charger_status 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: else:
print("No data available in the database.") print("No data available in the database.")
@@ -807,6 +951,13 @@ try:
#Add data to payload CSV #Add data to payload CSV
payload_csv[6] = DB_A_value 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)") #print("Verify SARA connection (AT)")
# Getting the LTE Signal (AT+CSQ) # Getting the LTE Signal (AT+CSQ)
@@ -879,6 +1030,8 @@ try:
if match: if match:
signal_quality = int(match.group(1)) signal_quality = int(match.group(1))
payload_csv[12]=signal_quality payload_csv[12]=signal_quality
payload.set_signal_quality(signal_quality)
time.sleep(0.1) time.sleep(0.1)
# On vérifie si le signal n'est pas à 99 pour déconnexion # On vérifie si le signal n'est pas à 99 pour déconnexion
@@ -917,9 +1070,14 @@ try:
if send_miotiq: if send_miotiq:
print('<p class="fw-bold">➡SEND TO MIOTIQ</p>', end="") 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 #create UDP socket (will return socket number) -> 17 is UDP protocol and 6 is TCP protocol
# IF ERROR -> need to create the PDP connection # IF ERROR -> need to create the PDP connection
print("Create Socket:") print("Create Socket:", end="")
command = f'AT+USOCR=17\r' command = f'AT+USOCR=17\r'
ser_sara.write(command.encode('utf-8')) ser_sara.write(command.encode('utf-8'))
response_SARA_1 = read_complete_response(ser_sara, wait_for_lines=["OK", "+CME ERROR", "ERROR"], debug=False) 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(response_SARA_1)
print("</p>", end="") 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>') print('<span style="color: red;font-weight: bold;">⚠ATTENTION: need to reset PDP connection⚠</span>')
psd_csd_resets = reset_PSD_CSD_connection() psd_csd_resets = reset_PSD_CSD_connection()
if psd_csd_resets: if psd_csd_resets:
@@ -945,7 +1103,7 @@ try:
print("Failed to extract socket ID") print("Failed to extract socket ID")
#Connect to UDP server (USOCO) #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' command = f'AT+USOCO={socket_id},"192.168.0.20",4242\r'
ser_sara.write(command.encode('utf-8')) ser_sara.write(command.encode('utf-8'))
response_SARA_2 = read_complete_response(ser_sara, wait_for_lines=["OK", "+CME ERROR", "ERROR"], debug=False) 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(response_SARA_2)
print("</p>", end="") print("</p>", end="")
#prepare data # Write data and send
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)
# 4. Write data and send (USOWR) print(f"Write data: {len(binary_data)} bytes")
print("Write data:") command = f'AT+USOWR={socket_id},{len(binary_data)}\r'
print(csv_udp_string)
command = f'AT+USOWR={socket_id},{size_of_udp_string}\r'
ser_sara.write(command.encode('utf-8')) ser_sara.write(command.encode('utf-8'))
response_SARA_2 = read_complete_response(ser_sara, wait_for_lines=["@","OK", "+CME ERROR", "ERROR"], debug=False) response_SARA_2 = read_complete_response(ser_sara, wait_for_lines=["@","OK", "+CME ERROR", "ERROR"], debug=False)
print('<p class="text-danger-emphasis">') print('<p class="text-danger-emphasis">')
print(response_SARA_2) print(response_SARA_2)
print("</p>", end="") 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) response_SARA_2 = read_complete_response(ser_sara, wait_for_lines=["@","OK", "+CME ERROR", "ERROR"], debug=False)
print('<p class="text-danger-emphasis">') print('<p class="text-danger-emphasis">')
print(response_SARA_2) print(response_SARA_2)
print("</p>", end="") print("</p>", end="")
#Read reply from server (USORD) #Read reply from server (USORD)
print("Read reply:") #print("Read reply:", end="")
command = f'AT+USORD=0,100\r' #command = f'AT+USORD=0,100\r'
ser_sara.write(command.encode('utf-8')) #ser_sara.write(command.encode('utf-8'))
response_SARA_2 = read_complete_response(ser_sara, wait_for_lines=["OK", "+CME ERROR", "ERROR"], debug=False) #response_SARA_2 = read_complete_response(ser_sara, wait_for_lines=["OK", "+CME ERROR", "ERROR"], debug=False)
print('<p class="text-danger-emphasis">') #print('<p class="text-danger-emphasis">')
print(response_SARA_2) #print(response_SARA_2)
print("</p>", end="") #print("</p>", end="")
#Close socket #Close socket
print("Close socket:") print("Close socket:", end="")
command = f'AT+USOCL={socket_id}\r' command = f'AT+USOCL={socket_id}\r'
ser_sara.write(command.encode('utf-8')) ser_sara.write(command.encode('utf-8'))
response_SARA_2 = read_complete_response(ser_sara, wait_for_lines=["OK", "+CME ERROR", "ERROR"], debug=False) response_SARA_2 = read_complete_response(ser_sara, wait_for_lines=["OK", "+CME ERROR", "ERROR"], debug=False)