diff --git a/loop/SARA_send_data_v2.py b/loop/SARA_send_data_v2.py index 83696c8..0b50fc5 100755 --- a/loop/SARA_send_data_v2.py +++ b/loop/SARA_send_data_v2.py @@ -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('
➡️SEND TO MIOTIQ
', 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("", end="") - if "+CME ERROR" in response_SARA_1: + if "+CME ERROR" in response_SARA_1 or "ERROR" in response_SARA_1: print('⚠️ATTENTION: need to reset PDP connection⚠️') 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("", 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('') print(response_SARA_2) print("
", 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('') print(response_SARA_2) print("
", 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('') - print(response_SARA_2) - print("
", 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('') + #print(response_SARA_2) + #print("
", 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)