Require serial_enabled true in settings to open serial_port; default false in set_defaults for Wi-Fi-only and dev machines. Co-authored-by: Cursor <cursoragent@cursor.com>
91 lines
2.7 KiB
Python
91 lines
2.7 KiB
Python
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()
|