167 lines
4.2 KiB
Python
167 lines
4.2 KiB
Python
"""ESP-NOW bridge: Pi USB-serial downlink, ESP-NOW to drivers (sync loop)."""
|
|
|
|
import gc, json, struct, time
|
|
import espnow, machine, network
|
|
from machine import Pin, UART
|
|
from settings import Settings
|
|
|
|
BROADCAST = b"\xff\xff\xff\xff\xff\xff"
|
|
WIRE = 0x4C
|
|
MAX_SERIAL = 4096
|
|
MAX_ESPNOW = 250
|
|
ESPNOW_EXIST = -12395
|
|
ESPNOW_FULL = -12392
|
|
|
|
|
|
def add_peer_if_needed(esp, dest, ch):
|
|
try:
|
|
esp.add_peer(dest, channel=ch)
|
|
except TypeError:
|
|
try:
|
|
esp.add_peer(dest)
|
|
except OSError as e:
|
|
if e.args[0] != ESPNOW_EXIST:
|
|
raise
|
|
except OSError as e:
|
|
if e.args[0] != ESPNOW_EXIST:
|
|
raise
|
|
|
|
|
|
def del_peer_if_present(esp, dest):
|
|
try:
|
|
esp.del_peer(dest)
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
def send_unicast_temp_peer(esp, dest, ch, pkt):
|
|
try:
|
|
add_peer_if_needed(esp, dest, ch)
|
|
except OSError as e:
|
|
if e.args and e.args[0] == ESPNOW_FULL:
|
|
del_peer_if_present(esp, dest)
|
|
add_peer_if_needed(esp, dest, ch)
|
|
else:
|
|
raise
|
|
try:
|
|
esp.send(dest, pkt, True)
|
|
finally:
|
|
del_peer_if_present(esp, dest)
|
|
|
|
|
|
def init_radio(ch, name, password):
|
|
network.WLAN(network.STA_IF).active(False)
|
|
network.WLAN(network.AP_IF).active(False)
|
|
time.sleep_ms(100)
|
|
ap = network.WLAN(network.AP_IF)
|
|
ap.active(True)
|
|
time.sleep_ms(50)
|
|
if password:
|
|
try:
|
|
ap.config(essid=name or "bridge", password=password, channel=ch, hidden=True)
|
|
except TypeError:
|
|
ap.config(essid=name or "bridge", channel=ch)
|
|
else:
|
|
try:
|
|
ap.config(essid=name or "bridge", channel=ch, hidden=True)
|
|
except TypeError:
|
|
ap.config(essid=name or "bridge", channel=ch)
|
|
sta = network.WLAN(network.STA_IF)
|
|
sta.active(True)
|
|
sta.config(pm=network.WLAN.PM_NONE)
|
|
try:
|
|
sta.config(channel=ch)
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
def mac_bytes(addr):
|
|
h = str(addr).replace(":", "").replace("-", "").strip().lower()
|
|
return bytes.fromhex(h)
|
|
|
|
|
|
def read_serial(uart, buf):
|
|
if uart.any():
|
|
buf.extend(uart.read(min(uart.any(), 256)))
|
|
out = []
|
|
while len(buf) >= 2:
|
|
n = (buf[0] << 8) | buf[1]
|
|
if n > MAX_SERIAL:
|
|
buf[:] = buf[1:]
|
|
continue
|
|
need = 2 + n
|
|
if len(buf) < need:
|
|
break
|
|
out.append(bytes(buf[2:need]))
|
|
buf[:] = buf[need:]
|
|
return out
|
|
|
|
|
|
def downlink(esp, ch, raw):
|
|
if not raw:
|
|
return
|
|
if raw[0] == WIRE:
|
|
if len(raw) < 2:
|
|
return
|
|
esp.send(BROADCAST, raw, True)
|
|
return
|
|
if len(raw) < 8 or raw[0] != ord("{"):
|
|
return
|
|
try:
|
|
data = json.loads(raw)
|
|
except ValueError:
|
|
return
|
|
devs = data.get("dv") or data.get("devices")
|
|
if data.get("v") != "1" or not isinstance(devs, dict):
|
|
return
|
|
for mac_s, body in devs.items():
|
|
if not isinstance(body, dict):
|
|
continue
|
|
try:
|
|
msg = {"v": "1"}
|
|
msg.update(body)
|
|
pkt = json.dumps(msg, separators=(",", ":")).encode()
|
|
if len(pkt) > MAX_ESPNOW:
|
|
continue
|
|
dest = mac_bytes(mac_s)
|
|
except (ValueError, TypeError):
|
|
continue
|
|
if dest == BROADCAST:
|
|
esp.send(BROADCAST, pkt, True)
|
|
else:
|
|
send_unicast_temp_peer(esp, dest, ch, pkt)
|
|
time.sleep_ms(5)
|
|
|
|
|
|
gc.collect()
|
|
s = Settings()
|
|
ch = max(1, min(11, int(s.get("wifi_channel", 5))))
|
|
init_radio(ch, s.get("name"), s.get("ap_password") or "")
|
|
baud = int(s.get("serial_baudrate", 921600))
|
|
uart = UART(
|
|
int(s.get("serial_uart_id", 1)),
|
|
baud,
|
|
tx=Pin(int(s.get("serial_tx_pin", 2))),
|
|
rx=Pin(int(s.get("serial_rx_pin", 3))),
|
|
)
|
|
esp = espnow.ESPNow()
|
|
esp.active(True)
|
|
add_peer_if_needed(esp, BROADCAST, ch)
|
|
print("bridge ch", ch, "baud", baud, "heap", gc.mem_free())
|
|
|
|
wdt = machine.WDT(timeout=10000)
|
|
rx_buf = bytearray()
|
|
while True:
|
|
wdt.feed()
|
|
for frame in read_serial(uart, rx_buf):
|
|
try:
|
|
downlink(esp, ch, frame)
|
|
except OSError as e:
|
|
print("dl", e)
|
|
host, msg = esp.recv(0)
|
|
if host:
|
|
up = bytes([0]) + host + msg
|
|
uart.write(struct.pack(">H", len(up)) + up)
|
|
else:
|
|
time.sleep_ms(1)
|