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 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.")
@@ -805,7 +949,14 @@ try:
DB_A_value = last_row[2]
#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)")
@@ -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)