# shared/uart_comm.py import _thread from machine import UART import time class SafeUART: def __init__(self, uart_id, tx_pin, rx_pin, baudrate=115200): # Initialize the hardware UART channel self.uart = UART(uart_id, baudrate=baudrate, tx=tx_pin, rx=rx_pin, timeout=10) # Core thread-safety assets self.lock = _thread.allocate_lock() self.rx_queue = [] self.buffer = b"" # Start the background data worker thread _thread.stack_size(4096) # Cap the stack size for the UART listener _thread.start_new_thread(self._listener_worker, ()) _thread.stack_size(0) print(f"[UART] Thread initialized on UART{uart_id} (TX:{tx_pin}, RX:{rx_pin})") def _listener_worker(self): """Asynchronous internal loop parsing incoming stream lines into the queue.""" while True: try: if self.uart.any(): with self.lock: # Pull all raw bytes waiting in the hardware ring buffer chunk = self.uart.read(self.uart.any()) if chunk: self.buffer += chunk # Process complete lines terminated by a newline character while b'\n' in self.buffer: line, self.buffer = self.buffer.split(b'\n', 1) try: decoded_line = line.decode('utf-8').strip() if decoded_line: self.rx_queue.append(decoded_line) except Exception: pass # Discard corrupt data frames safely except Exception as e: print("[UART Thread Error]:", e) time.sleep_ms(20) # Give other background threads breathing room def send(self, message): """Safely pushes strings across the serial wire from any thread context.""" if not message.endswith('\n'): message += '\n' with self.lock: self.uart.write(message.encode('utf-8')) def any(self): """Checks if any complete messages are waiting to be read.""" with self.lock: 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