From d5b2e9c6c38cfd60e1fa37df5f7a8ab0fb2bc5c8 Mon Sep 17 00:00:00 2001 From: paul_vua Date: Sat, 14 Mar 2026 22:53:59 +0100 Subject: [PATCH] =?UTF-8?q?v1.4.2=20=E2=80=94=20Fix=20bug=20AT+USOWR=20lea?= =?UTF-8?q?k=20dans=20payload=20UDP=20Miotiq?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Corrige une desynchronisation serie qui causait l'envoi de la commande AT+USOWR comme donnees UDP au lieu du payload capteurs. Ajout de flush buffer serie, verification du prompt @, et abort propre a chaque etape. Co-Authored-By: Claude Opus 4.6 --- VERSION | 2 +- changelog.json | 17 ++++ loop/AUDIT_SARA_send_data_v2.md | 107 +++++++++++++++++++++++++ loop/SARA_send_data_v2.py | 138 ++++++++++++++++++-------------- 4 files changed, 203 insertions(+), 61 deletions(-) create mode 100644 loop/AUDIT_SARA_send_data_v2.md diff --git a/VERSION b/VERSION index 347f583..9df886c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.4.1 +1.4.2 diff --git a/changelog.json b/changelog.json index edbf3e8..be401f5 100644 --- a/changelog.json +++ b/changelog.json @@ -1,5 +1,22 @@ { "versions": [ + { + "version": "1.4.2", + "date": "2026-03-14", + "changes": { + "features": [], + "improvements": [], + "fixes": [ + "Fix envoi UDP Miotiq: desynchronisation serie causant l'envoi de la commande AT+USOWR comme payload au lieu des donnees capteurs", + "Ajout flush buffer serie (reset_input_buffer) avant chaque etape UDP critique", + "Verification du prompt @ du modem avant envoi des donnees binaires", + "Abort propre de l'envoi UDP si creation socket, connexion ou prompt @ echoue", + "Retry creation socket apres reset PDP reussi" + ], + "compatibility": [] + }, + "notes": "Corrige un bug ou le modem SARA envoyait la commande AT+USOWR comme donnees UDP, causant des erreurs UNKNOWN_DEVICE sur le parser Miotiq." + }, { "version": "1.4.1", "date": "2026-03-12", diff --git a/loop/AUDIT_SARA_send_data_v2.md b/loop/AUDIT_SARA_send_data_v2.md new file mode 100644 index 0000000..0341ac8 --- /dev/null +++ b/loop/AUDIT_SARA_send_data_v2.md @@ -0,0 +1,107 @@ +# Audit SARA_send_data_v2.py + +Date: 2026-03-14 + +## Correction deja appliquee + +**Bug AT+USOWR leak dans payload UDP Miotiq** — Le device_id recu par Miotiq etait `41542b55534f5752` = `AT+USOWR` (la commande AT elle-meme). + +Cause: desynchronisation serie entre le script et le modem. Le code envoyait les donnees binaires sans verifier que le modem avait bien envoye le prompt `@`. + +Corrections appliquees dans la section UDP (send_miotiq): +- `ser_sara.reset_input_buffer()` avant chaque commande AT critique +- Verification que `"@" in response` avant d'envoyer les donnees binaires +- Abort propre a chaque etape via `socket_id = None` si creation socket, connexion, ou prompt `@` echoue +- Retry `AT+USOCR=17` apres un PDP reset reussi + +--- + +## Bugs critiques restants + +### 1. Double `\r` sur plusieurs commandes AT + +Certaines commandes AT ont `\r` dans le f-string ET un `+ '\r'` lors du write, ce qui envoie `\r\r` au modem. + +**Lignes concernees:** +- **Ligne 988**: `command = f'AT+CSQ\r'` puis `ser_sara.write((command + '\r')...)` +- **Lignes 588, 628, 646, 656, 666, 674, 682, 690, 698**: fonctions `reset_server_hostname` et `reset_server_hostname_https` +- **Lignes 1403, 1541, 1570, 1694, 1741**: sections AirCarto et uSpot + +Le modem tolere souvent le double `\r`, mais ca peut generer des reponses parasites dans le buffer serie et contribuer a des bugs de desynchronisation. + +**Correction**: retirer le `\r` du f-string OU retirer le `+ '\r'` dans le write. Choisir une convention unique. + +### 2. Double guillemet dans AT+URDFILE (ligne 1402) + +```python +command = f'AT+URDFILE="aircarto_server_response.txt""\r' +# ^^ double " +``` + +Le `"` en trop peut causer une erreur AT ou une reponse inattendue. + +**Correction**: `command = f'AT+URDFILE="aircarto_server_response.txt"\r'` + +### 3. Crash si table SQLite vide (lignes 781-786, 820-826, 868-880) + +```python +rows = cursor.fetchall() +data_values = [row[2:] for row in rows] +averages = [round(sum(col) / len(col),1) for col in zip(*data_values)] +``` + +Si `data_NPM`, `data_NPM_5channels` ou `data_envea` est vide, `data_values` sera `[]` et le calcul d'average crashera (IndexError / division par zero). + +**Correction**: ajouter un check `if rows:` avant le calcul, comme c'est deja fait pour BME280 (ligne 840), wind (ligne 912) et MPPT (ligne 936). + +### 4. Overflow struct.pack sur valeurs negatives (class SensorPayload) + +```python +def set_npm_core(self, pm1, pm25, pm10): + self.payload[10:12] = struct.pack('>H', int(pm1 * 10)) # H = unsigned 16-bit +``` + +Si un capteur retourne une valeur negative (erreur capteur, -1, etc.), `struct.pack('>H', -10)` leve `struct.error`. Concerne: `set_npm_core`, `set_noise`, `set_envea`, `set_npm_5channels`, `set_wind`, `set_mppt` (sauf battery_current et temperatures qui utilisent `'>h'` signe). + +**Correction**: clamper les valeurs avant pack: `max(0, int(value * 10))` pour les champs unsigned, ou verifier `value >= 0` avant le pack. + +--- + +## Problemes importants + +### 5. Port serie et SQLite jamais fermes + +`ser_sara` (ligne 248) et `conn` (ligne 155) sont ouverts mais jamais fermes, meme dans le bloc `except` final (ligne 1755). Si le script crash, le port serie peut rester verrouille pour le prochain cycle. + +**Correction**: ajouter un bloc `finally` apres le `except` (ligne 1757): +```python +finally: + ser_sara.close() + conn.close() +``` + +### 6. Code mort dans reset_server_hostname_https (lignes 717-722) + +```python +if profile_id == 1: # ligne 613 + ... +elif profile_id == 1: # ligne 718 — jamais atteint car meme condition + pass +``` + +Copie-colle de `reset_server_hostname`. Le elif est mort. + +**Correction**: supprimer le bloc elif (lignes 717-722). + +--- + +## Resume + +| # | Type | Description | Lignes | +|----|----------|------------------------------------------|----------------| +| 1 | Bug | Double \r sur commandes AT | 988, 588+, ... | +| 2 | Bug | Double guillemet AT+URDFILE | 1402 | +| 3 | Crash | Table SQLite vide -> IndexError | 781, 820, 868 | +| 4 | Crash | struct.pack overflow valeur negative | SensorPayload | +| 5 | Cleanup | Serial/SQLite jamais fermes (finally) | 248, 155 | +| 6 | Cleanup | Code mort elif profile_id==1 | 717-722 | diff --git a/loop/SARA_send_data_v2.py b/loop/SARA_send_data_v2.py index 4c786dc..4ddb38d 100755 --- a/loop/SARA_send_data_v2.py +++ b/loop/SARA_send_data_v2.py @@ -1091,10 +1091,13 @@ try: print('

➡️SEND TO MIOTIQ

', end="") binary_data = payload.get_bytes() - + print(f"Binary payload: {len(binary_data)} bytes") #print(f"Binary payload: {binary_data}") + # Flush serial buffer to avoid stale data from previous operations + ser_sara.reset_input_buffer() + #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:", end="") @@ -1110,88 +1113,103 @@ try: psd_csd_resets = reset_PSD_CSD_connection() if psd_csd_resets: print("✅PSD CSD connection reset successfully") + # Retry socket creation after PDP reset + ser_sara.reset_input_buffer() + 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) + print("Retry create socket:", end="") + print(response_SARA_1) else: print("⛔There were issues with the modem CSD PSD reinitialize process") # Clignotement LED rouge en cas d'erreur led_thread = Thread(target=blink_led, args=(24, 5, 0.5)) led_thread.start() - #Retreive Socket ID + socket_id = None match = re.search(r'\+USOCR:\s*(\d+)', response_SARA_1) if match: socket_id = match.group(1) print(f"Socket ID: {socket_id}", end="") else: - print("Failed to extract socket ID") - + print('⚠️Failed to extract socket ID - skip UDP send⚠️') + #Connect to UDP server (USOCO) - 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) - print('

', end="") - print(response_SARA_2) - print("

", end="") + if socket_id is not None: + print("Connect to server:", end="") + ser_sara.reset_input_buffer() + 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) + print('

', end="") + print(response_SARA_2) + print("

", end="") - # Write data and send + if "+CME ERROR" in response_SARA_2 or "ERROR" in response_SARA_2: + print('⚠️ATTENTION: Error connecting socket - skip UDP send⚠️') + ser_sara.write(f'AT+USOCL={socket_id}\r'.encode('utf-8')) + read_complete_response(ser_sara, wait_for_lines=["OK", "+CME ERROR", "ERROR"], timeout=1, end_of_response_timeout=1, debug=False) + socket_id = None - print(f"Write data: {len(binary_data)} bytes", end="") - 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('

', end="") - print(response_SARA_2) - print("

", end="") + # Write data and send + if socket_id is not None: + print(f"Write data: {len(binary_data)} bytes", end="") + ser_sara.reset_input_buffer() + 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('

', end="") + print(response_SARA_2) + print("

", end="") - # 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('

', end="") - print(response_SARA_2) - print("

", end="") + # Verify modem sent @ prompt (ready for binary data) + if "@" not in response_SARA_2: + print('⚠️Modem did not send @ prompt - skip data send to avoid AT+USOWR leak⚠️') + ser_sara.reset_input_buffer() + ser_sara.write(f'AT+USOCL={socket_id}\r'.encode('utf-8')) + read_complete_response(ser_sara, wait_for_lines=["OK", "+CME ERROR", "ERROR"], timeout=1, end_of_response_timeout=1, debug=False) + socket_id = None - #parfois ici on peut avoir une erreur ERROR - if "+CME ERROR" in response_SARA_2 or "ERROR" in response_SARA_2: - print('⚠️ATTENTION: Error while sending data⚠️') - print('🛑STOP LOOP🛑') - print("
") + if socket_id is not None: + # 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('

', end="") + print(response_SARA_2) + print("

", end="") - #Send notification (WIFI) - send_error_notification(device_id, "UDP sending issue") - #Hardware Reboot - hardware_reboot_success = modem_hardware_reboot() - if hardware_reboot_success: - print("✅Modem successfully rebooted and reinitialized") - else: - print("⛔There were issues with the modem reboot/reinitialize process") - - #end loop - sys.exit() + #parfois ici on peut avoir une erreur ERROR + if "+CME ERROR" in response_SARA_2 or "ERROR" in response_SARA_2: + print('⚠️ATTENTION: Error while sending data⚠️') + print('🛑STOP LOOP🛑') + print("
") + #Send notification (WIFI) + send_error_notification(device_id, "UDP sending issue") + #Hardware Reboot + hardware_reboot_success = modem_hardware_reboot() + if hardware_reboot_success: + print("✅Modem successfully rebooted and reinitialized") + else: + print("⛔There were issues with the modem reboot/reinitialize process") - #Read reply from server (USORD) - #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="") + #end loop + sys.exit() - #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) + #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) - #blink green LEDs - led_thread = Thread(target=blink_led, args=(23, 5, 0.5)) - led_thread.start() + #blink green LEDs + led_thread = Thread(target=blink_led, args=(23, 5, 0.5)) + led_thread.start() - print('

', end="") - print(response_SARA_2) - print("

", end="") + print('

', end="") + print(response_SARA_2) + print("

", end="")