Simplified Uart comunications
This commit is contained in:
+19
-82
@@ -5,98 +5,42 @@ import ujson
|
|||||||
|
|
||||||
class SafeUART:
|
class SafeUART:
|
||||||
def __init__(self, uart_id, tx_pin, rx_pin, baudrate=115200):
|
def __init__(self, uart_id, tx_pin, rx_pin, baudrate=115200):
|
||||||
# Initialize the hardware UART channel
|
# Setting timeout allows readline() to be non-blocking
|
||||||
self.uart = UART(uart_id, baudrate=baudrate, tx=tx_pin, rx=rx_pin, timeout=10, rxbuf=1024)
|
self.uart = UART(uart_id, baudrate=baudrate, tx=tx_pin, rx=rx_pin, timeout=10, rxbuf=1024)
|
||||||
|
|
||||||
# Core thread-safety assets
|
|
||||||
self.lock = _thread.allocate_lock()
|
self.lock = _thread.allocate_lock()
|
||||||
self.rx_queue = []
|
self.rx_queue = []
|
||||||
self.buffer = b""
|
|
||||||
|
|
||||||
# Start the background data worker thread
|
_thread.stack_size(4096)
|
||||||
_thread.stack_size(4096) # Cap the stack size for the UART listener
|
|
||||||
_thread.start_new_thread(self._listener_worker, ())
|
_thread.start_new_thread(self._listener_worker, ())
|
||||||
_thread.stack_size(0)
|
_thread.stack_size(0)
|
||||||
|
|
||||||
print(f"[UART] Thread initialized on UART{uart_id} (TX:{tx_pin}, RX:{rx_pin})")
|
|
||||||
|
|
||||||
def _listener_worker(self):
|
def _listener_worker(self):
|
||||||
"""Worker loop that handles nested JSON structures by tracking brace depth."""
|
"""Simple worker that relies on newline framing instead of manual JSON parsing."""
|
||||||
while True:
|
while True:
|
||||||
messages_found = []
|
if self.uart.any():
|
||||||
|
with self.lock:
|
||||||
|
line = self.uart.readline()
|
||||||
|
|
||||||
with self.lock:
|
if line:
|
||||||
if self.uart.any():
|
try:
|
||||||
chunk = self.uart.read()
|
decoded = line.decode('utf-8').strip()
|
||||||
if chunk is not None and isinstance(chunk, bytes):
|
if decoded: # Ignore empty lines
|
||||||
self.buffer += chunk
|
with self.lock:
|
||||||
|
self.rx_queue.append(decoded)
|
||||||
# Extract complete JSON objects while accounting for nested braces
|
except UnicodeError:
|
||||||
while True:
|
pass # Drop corrupted bytes cleanly
|
||||||
start_idx = self.buffer.find(b'{')
|
|
||||||
if start_idx == -1:
|
|
||||||
# No starting brace; clear any garbage bytes currently in buffer
|
|
||||||
self.buffer = b""
|
|
||||||
break
|
|
||||||
|
|
||||||
# Trim any leading noise before the first '{'
|
|
||||||
if start_idx > 0:
|
|
||||||
self.buffer = self.buffer[start_idx:]
|
|
||||||
|
|
||||||
# Track depth to find the matching OUTER '}'
|
|
||||||
depth = 0
|
|
||||||
in_string = False
|
|
||||||
escape = False
|
|
||||||
end_idx = -1
|
|
||||||
|
|
||||||
for i in range(len(self.buffer)):
|
|
||||||
b = self.buffer[i]
|
|
||||||
|
|
||||||
# Ignore braces inside string literals ("...")
|
|
||||||
if b == 34 and not escape: # 34 is ASCII for '"'
|
|
||||||
in_string = not in_string
|
|
||||||
elif b == 92 and in_string: # 92 is ASCII for '\'
|
|
||||||
escape = not escape
|
|
||||||
continue
|
|
||||||
elif not in_string:
|
|
||||||
if b == 123: # '{'
|
|
||||||
depth += 1
|
|
||||||
elif b == 125: # '}'
|
|
||||||
depth -= 1
|
|
||||||
if depth == 0:
|
|
||||||
end_idx = i
|
|
||||||
break
|
|
||||||
escape = False
|
|
||||||
|
|
||||||
if end_idx != -1:
|
|
||||||
# Full nested JSON object extracted safely
|
|
||||||
json_bytes = self.buffer[:end_idx + 1]
|
|
||||||
self.buffer = self.buffer[end_idx + 1:]
|
|
||||||
messages_found.append(json_bytes)
|
|
||||||
else:
|
|
||||||
# The complete outer JSON hasn't fully arrived yet; wait for next UART chunk
|
|
||||||
break
|
|
||||||
|
|
||||||
# Process valid complete frames outside the lock
|
|
||||||
for json_bytes in messages_found:
|
|
||||||
try:
|
|
||||||
decoded_str = json_bytes.decode('utf-8')
|
|
||||||
with self.lock:
|
|
||||||
self.rx_queue.append(decoded_str)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[UART Parse Error]: {e}")
|
|
||||||
|
|
||||||
time.sleep_ms(10)
|
time.sleep_ms(10)
|
||||||
|
|
||||||
def send(self, message):
|
def send(self, message):
|
||||||
"""Safely pushes strings across the serial wire from any thread context."""
|
|
||||||
if not message.endswith('\n'):
|
if not message.endswith('\n'):
|
||||||
message += '\n'
|
message += '\n'
|
||||||
|
|
||||||
data = message.encode('utf-8')
|
|
||||||
|
|
||||||
with self.lock:
|
with self.lock:
|
||||||
self.uart.write(data)
|
self.uart.write(message.encode('utf-8'))
|
||||||
|
|
||||||
|
def read(self):
|
||||||
|
with self.lock:
|
||||||
|
return self.rx_queue.pop(0) if self.rx_queue else None
|
||||||
|
|
||||||
def send_as_command(self, command: 'UARTCommand'):
|
def send_as_command(self, command: 'UARTCommand'):
|
||||||
"""Safely sends a structured command over UART."""
|
"""Safely sends a structured command over UART."""
|
||||||
@@ -108,13 +52,6 @@ class SafeUART:
|
|||||||
with self.lock:
|
with self.lock:
|
||||||
return len(self.rx_queue) > 0
|
return len(self.rx_queue) > 0
|
||||||
|
|
||||||
def read(self):
|
|
||||||
"""Pulls the oldest unread string from the queue. Returns None if empty."""
|
|
||||||
with self.lock:
|
|
||||||
if self.rx_queue:
|
|
||||||
return self.rx_queue.pop(0)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def read_as_command(self) -> 'UARTCommand | None':
|
def read_as_command(self) -> 'UARTCommand | None':
|
||||||
"""Attempts to read the oldest unread string and parse it as a UARTCommand. Returns None if empty or invalid."""
|
"""Attempts to read the oldest unread string and parse it as a UARTCommand. Returns None if empty or invalid."""
|
||||||
raw_message = self.read()
|
raw_message = self.read()
|
||||||
|
|||||||
Reference in New Issue
Block a user