LoRa Fix
This commit is contained in:
+58
-65
@@ -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=<group>,<payload_string>,<confirm>,<retries>
|
||||
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."""
|
||||
|
||||
Reference in New Issue
Block a user