import asyncio import json # Default: broadcast (6 bytes). Pi always sends 6-byte address + payload to ESP32. BROADCAST_MAC = bytes.fromhex("ffffffffffff") def _encode_payload(data): if isinstance(data, str): return data.encode() if isinstance(data, dict): return json.dumps(data).encode() return data def _parse_mac(addr): """Convert 12-char hex string or 6-byte bytes to 6-byte MAC.""" if addr is None or addr == b"": return BROADCAST_MAC if isinstance(addr, bytes) and len(addr) == 6: return addr if isinstance(addr, str) and len(addr) == 12: return bytes.fromhex(addr) return BROADCAST_MAC async def _to_thread(func, *args): to_thread = getattr(asyncio, "to_thread", None) if to_thread: return await to_thread(func, *args) loop = asyncio.get_event_loop() return await loop.run_in_executor(None, func, *args) class NullSender: """Used when no ESP-NOW UART bridge is configured or the port cannot be opened.""" async def send(self, data, addr=None): return True class SerialSender: def __init__(self, port, baudrate, default_addr=None): import serial self._serial = serial.Serial(port, baudrate=baudrate, timeout=1) self._default_addr = _parse_mac(default_addr) self._write_lock = asyncio.Lock() async def send(self, data, addr=None): mac = _parse_mac(addr) if addr is not None else self._default_addr payload = _encode_payload(data) async with self._write_lock: await _to_thread(self._serial.write, mac + payload) return True _current_sender = None def set_sender(sender): global _current_sender _current_sender = sender def get_current_sender(): return _current_sender def get_sender(settings): # Serial ESP-NOW bridge is opt-in (serial_enabled true); default off for dev / Wi-Fi-only. if not settings.get("serial_enabled"): print("[startup] serial bridge disabled (set serial_enabled true in settings.json to enable)") return NullSender() port = settings.get("serial_port", "/dev/ttyS0") raw_port = str(port).strip() if port is not None else "" if not raw_port or raw_port.lower() in ("none", "off"): print("[startup] serial bridge disabled (empty serial_port)") return NullSender() baudrate = settings.get("serial_baudrate", 912000) default_addr = settings.get("serial_destination_mac", "ffffffffffff") try: return SerialSender(raw_port, baudrate, default_addr=default_addr) except Exception as e: print( f"[startup] serial open failed ({raw_port!r}): {e}; " "continuing without ESP-NOW bridge (Wi-Fi drivers unchanged)" ) return NullSender()