100 lines
3.6 KiB
Python
100 lines
3.6 KiB
Python
import _thread
|
|
from machine import UART
|
|
import time
|
|
import ujson
|
|
|
|
class SafeUART:
|
|
def __init__(self, uart_id, tx_pin, rx_pin, baudrate=115200):
|
|
# 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.lock = _thread.allocate_lock()
|
|
self.rx_queue = []
|
|
|
|
_thread.stack_size(4096)
|
|
_thread.start_new_thread(self._listener_worker, ())
|
|
_thread.stack_size(0)
|
|
|
|
def _listener_worker(self):
|
|
"""Simple worker that relies on newline framing instead of manual JSON parsing."""
|
|
while True:
|
|
if self.uart.any():
|
|
with self.lock:
|
|
line = self.uart.readline()
|
|
|
|
if line:
|
|
try:
|
|
decoded = line.decode('utf-8').strip()
|
|
if decoded: # Ignore empty lines
|
|
with self.lock:
|
|
self.rx_queue.append(decoded)
|
|
except UnicodeError:
|
|
pass # Drop corrupted bytes cleanly
|
|
|
|
time.sleep_ms(10)
|
|
|
|
def send(self, message):
|
|
if not message.endswith('\n'):
|
|
message += '\n'
|
|
with self.lock:
|
|
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'):
|
|
"""Safely sends a structured command over UART."""
|
|
json_message = command.to_json()
|
|
self.send(json_message)
|
|
|
|
def any(self):
|
|
"""Checks if any complete messages are waiting to be read."""
|
|
with self.lock:
|
|
return len(self.rx_queue) > 0
|
|
|
|
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."""
|
|
raw_message = self.read()
|
|
if raw_message is not None:
|
|
cmd = UARTCommand.from_json(raw_message)
|
|
if cmd is None:
|
|
print("[UART] Impossible de traiter le message brut :", raw_message)
|
|
return cmd
|
|
return None
|
|
|
|
|
|
class UARTCommand:
|
|
"""A simple wrapper for commands sent over UART, allowing for structured data."""
|
|
def __init__(self, command_type: str, payload):
|
|
self.command_type = command_type
|
|
self.payload = payload
|
|
|
|
def to_json(self):
|
|
"""Serializes the command to a JSON string."""
|
|
return ujson.dumps({
|
|
"command_type": self.command_type,
|
|
"payload": self.payload
|
|
})
|
|
|
|
@staticmethod
|
|
def from_json(json_string: str) -> 'UARTCommand | None':
|
|
"""Deserializes a JSON string into a UARTCommand object."""
|
|
try:
|
|
# Remplacement préventif si des guillemets simples sont reçus
|
|
clean_str = json_string.replace("'", '"') if "'" in json_string else json_string
|
|
data = ujson.loads(clean_str)
|
|
|
|
if not isinstance(data, dict):
|
|
return None
|
|
|
|
return UARTCommand(data.get("command_type"), data.get("payload"))
|
|
except Exception as err:
|
|
# Affiche l'erreur exacte rencontrée par ujson (ex: syntax error)
|
|
print(f"[UARTCommand Parsing Error]: {err} -> Contenu: {json_string}")
|
|
return None
|
|
|
|
|
|
class UARTCommandType:
|
|
"""Enumeration of known UART command types."""
|
|
COOKING_PARAMS = "COOKING_PARAMS"
|
|
COOKING_STATE_UPDATE = "COOKING_STATE_UPDATE" |