diff --git a/shared/lora_device.py b/shared/lora_device.py index 0198abd..97f0291 100644 --- a/shared/lora_device.py +++ b/shared/lora_device.py @@ -1,6 +1,7 @@ import sys import time import random +from shared.logging import log IS_MICROPYTHON = sys.implementation.name == 'micropython' @@ -32,9 +33,10 @@ class BaseLoraDevice: """Sends an immediate acknowledgement packet back to the sender.""" print(f"[ReliableLoRa] -> Triggering ACK send for msg_id: {ack_id}") if IS_MICROPYTHON: - time.sleep_ms(10) + time.sleep_ms(100) else: - time.sleep(0.01) + # Give LA66 chip time to finish logging RSSI and reset RF frontend + time.sleep(0.25) ack_payload = {"_type": "_ack", "_ack_id": ack_id} self.send(ack_payload) @@ -72,8 +74,8 @@ class BaseLoraDevice: return packet - def send_reliable(self, payload, max_retries=4, ack_timeout=2.5): - """Sends a payload and retries until an ACK is received or max retries are reached.""" + def send_reliable(self, payload, max_retries=4, ack_timeout=3.0): + """Sends a payload and listens in a single continuous RX window for the ACK.""" lock = getattr(self, 'lock', None) if isinstance(payload, dict): @@ -89,37 +91,31 @@ class BaseLoraDevice: for attempt in range(max_retries): print(f"[ReliableLoRa] Attempt {attempt + 1}/{max_retries} transmitting msg_id {msg_id}") self.send(payload) - start_time = time.time() - while (time.time() - start_time) < ack_timeout: + # 1. Open a single continuous RX window for the full timeout duration + # (Defaulted to 3.0s to account for LA66 UART + transmission time) + timeout_ms = int(ack_timeout * 1000) + packet = self.receive_packet(timeout_ms=timeout_ms) + + # 2. Process incoming packet if received + if packet: if lock: lock.acquire() try: - if msg_id in self.received_acks: - self.received_acks.remove(msg_id) - print(f"[ReliableLoRa] === ACK received for msg_id {msg_id} on attempt {attempt + 1} ===") - return True + filtered_packet = self._process_incoming_packet(packet) + if filtered_packet: + self.pending_rx_queue.append(filtered_packet) finally: if lock: lock.release() - packet = self.receive_packet(timeout_ms=500) - if packet: - print(f"[ReliableLoRa] Received raw packet while waiting for ACK: {packet}") - if lock: lock.acquire() - try: - filtered_packet = self._process_incoming_packet(packet) - if filtered_packet: - self.pending_rx_queue.append(filtered_packet) - finally: - if lock: lock.release() - - if lock: lock.acquire() - try: - if msg_id in self.received_acks: - self.received_acks.remove(msg_id) - print(f"[ReliableLoRa] === ACK received for msg_id {msg_id} after poll ===") - return True - finally: - if lock: lock.release() + # 3. Check if matching ACK was received + if lock: lock.acquire() + try: + if msg_id in self.received_acks: + self.received_acks.remove(msg_id) + print(f"[ReliableLoRa] === ACK received for msg_id {msg_id} on attempt {attempt + 1} ===") + return True + finally: + if lock: lock.release() print(f"[ReliableLoRa] Attempt {attempt + 1} timed out waiting for ACK for msg_id {msg_id}") @@ -246,37 +242,31 @@ if IS_MICROPYTHON: except Exception as e: print(f"[LoRa SPI] Recv error caught: {e}") return None + if state == 0 and data is not None and len(data) > 0: - if data[0] in (0x7B, 0x5B): # Starts with '{' or '[' - group = self.default_group - payload_brute = data.strip(b'\x00 \r\n\t') - elif len(data) > 1: - group = data[0] - payload_brute = data[1:].strip(b'\x00 \r\n\t') - else: + # Decode to string, ignoring unprintable characters + try: + raw_text = data.decode('utf-8', 'ignore').strip() + except Exception: return None + + # Find where the actual JSON payload starts ({ or [) + idx_brace = raw_text.find('{') + idx_bracket = raw_text.find('[') - try: - text = payload_brute.decode('utf-8').strip('\x00 \r\n\t') - except UnicodeError: - return None - - if text.startswith('{') or text.startswith('['): - decoded_text = text - elif text.lower().startswith('7b') or text.lower().startswith('5b'): + valid_indices = [i for i in (idx_brace, idx_bracket) if i != -1] + + if valid_indices: + # Slice off all leading group bytes/control characters (\x02) + json_str = raw_text[min(valid_indices):] try: - decoded_text = ubinascii.unhexlify(text).decode('utf-8').strip('\x00 \r\n\t') + parsed_json = json.loads(json_str) + return {"group": self.default_group, "data": parsed_json, "raw": False} except Exception: - decoded_text = text - else: - decoded_text = text + pass - try: - parsed_json = json.loads(decoded_text) - return {"group": group, "data": parsed_json, "raw": False} - except ValueError: - return {"group": group, "data": decoded_text, "raw": True} + return {"group": self.default_group, "data": raw_text, "raw": True} return None @@ -301,10 +291,15 @@ else: # Initial configuration self.configure(freq=868.1, sf=7, bw=125) - def _send_at_cmd(self, cmd, wait_time=0.15): - """Helper to send AT command and purge response buffer.""" + def _send_at_cmd(self, cmd, wait_time=0.3): + """Sends AT command, draining unread serial noise first.""" + # Drain any lingering lines (like 'Rssi= -4' or incoming data) + if self.ser.in_waiting > 0: + self.ser.read_all() + self.ser.write(f"{cmd}\r\n".encode('utf-8')) time.sleep(wait_time) + resp = "" while self.ser.in_waiting > 0: resp += self.ser.readline().decode('utf-8', errors='ignore') @@ -326,7 +321,7 @@ else: self.ser.reset_input_buffer() def send(self, payload, group=None): - """Encodes payload into HEX AT command and re-enables continuous RX.""" + """Encodes payload into a HEX string and transmits via 4-parameter AT+SEND.""" with self.lock: if group is None: group = self.default_group @@ -337,17 +332,15 @@ else: if isinstance(payload, str): payload = payload.encode('utf-8') - paquet_physique = bytes([group]) + payload - hex_payload = paquet_physique.hex() - self.ser.reset_input_buffer() + hex_payload = payload.hex() + + print(f"[RPi LoRa Serial] Transmitting HEX string: {hex_payload}") - print(f"[RPi LoRa Serial] Transmitting HEX payload: {hex_payload}") - cmd = f"AT+PSEND={hex_payload}" - resp = self._send_at_cmd(cmd, wait_time=0.25) # Wait for RF TX to finish - print(f"[RPi LoRa Serial] AT+PSEND response: {resp}") + # Format: AT+SEND=,,, + cmd = f"AT+SEND={group},{hex_payload},0,3" - # Re-enable continuous receive mode after transmission completes - self._send_at_cmd("AT+PRECV=65535", wait_time=0.05) + resp = self._send_at_cmd(cmd, wait_time=0.3) + print(f"[RPi LoRa Serial] AT+SEND response: {resp}") def receive_packet(self, timeout_ms=500): """Reads incoming serial lines from LA66 stick with robust format parsing."""