chore: remove esp32 firmware tree and dev mpremote helper
Made-with: Cursor
This commit is contained in:
53
dev.py
53
dev.py
@@ -1,53 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import subprocess
|
|
||||||
import serial
|
|
||||||
import sys
|
|
||||||
|
|
||||||
print(sys.argv)
|
|
||||||
|
|
||||||
# Extract port (first arg if it's not a command)
|
|
||||||
commands = ["src", "lib", "ls", "reset", "follow", "db"]
|
|
||||||
port = None
|
|
||||||
if len(sys.argv) > 1 and sys.argv[1] not in commands:
|
|
||||||
port = sys.argv[1]
|
|
||||||
|
|
||||||
|
|
||||||
for cmd in sys.argv[1:]:
|
|
||||||
print(cmd)
|
|
||||||
match cmd:
|
|
||||||
case "src":
|
|
||||||
if port:
|
|
||||||
subprocess.call(["mpremote", "connect", port, "fs", "cp", "-r", ".", ":" ], cwd="src")
|
|
||||||
else:
|
|
||||||
print("Error: Port required for 'src' command")
|
|
||||||
case "lib":
|
|
||||||
if port:
|
|
||||||
subprocess.call(["mpremote", "connect", port, "fs", "cp", "-r", "lib", ":" ])
|
|
||||||
else:
|
|
||||||
print("Error: Port required for 'lib' command")
|
|
||||||
case "ls":
|
|
||||||
if port:
|
|
||||||
subprocess.call(["mpremote", "connect", port, "fs", "ls", ":" ])
|
|
||||||
else:
|
|
||||||
print("Error: Port required for 'ls' command")
|
|
||||||
case "reset":
|
|
||||||
if port:
|
|
||||||
with serial.Serial(port, baudrate=115200) as ser:
|
|
||||||
ser.write(b'\x03\x03\x04')
|
|
||||||
else:
|
|
||||||
print("Error: Port required for 'reset' command")
|
|
||||||
case "follow":
|
|
||||||
if port:
|
|
||||||
with serial.Serial(port, baudrate=115200) as ser:
|
|
||||||
while True:
|
|
||||||
if ser.in_waiting > 0: # Check if there is data in the buffer
|
|
||||||
data = ser.readline().decode('utf-8').strip() # Read and decode the data
|
|
||||||
print(data)
|
|
||||||
else:
|
|
||||||
print("Error: Port required for 'follow' command")
|
|
||||||
case "db":
|
|
||||||
if port:
|
|
||||||
subprocess.call(["mpremote", "connect", port, "fs", "cp", "-r", "db", ":" ])
|
|
||||||
else:
|
|
||||||
print("Error: Port required for 'db' command")
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
# Benchmark: LRU eviction vs add-then-remove-after-use on ESP32.
|
|
||||||
# Run on device: mpremote run esp32/benchmark_peers.py
|
|
||||||
# (add/del_peer are timed; send() may fail if no peer is listening - timing still valid)
|
|
||||||
import espnow
|
|
||||||
import network
|
|
||||||
import time
|
|
||||||
|
|
||||||
BROADCAST = b"\xff\xff\xff\xff\xff\xff"
|
|
||||||
MAX_PEERS = 20
|
|
||||||
ITERATIONS = 50
|
|
||||||
PAYLOAD = b"x" * 32 # small payload
|
|
||||||
|
|
||||||
network.WLAN(network.STA_IF).active(True)
|
|
||||||
esp = espnow.ESPNow()
|
|
||||||
esp.active(True)
|
|
||||||
esp.add_peer(BROADCAST)
|
|
||||||
|
|
||||||
# Build 19 dummy MACs so we have 20 peers total (broadcast + 19).
|
|
||||||
def mac(i):
|
|
||||||
return bytes([0, 0, 0, 0, 0, i])
|
|
||||||
peers_list = [mac(i) for i in range(1, 20)]
|
|
||||||
for p in peers_list:
|
|
||||||
esp.add_peer(p)
|
|
||||||
|
|
||||||
# One "new" MAC we'll add/remove.
|
|
||||||
new_mac = bytes([0, 0, 0, 0, 0, 99])
|
|
||||||
|
|
||||||
def bench_lru():
|
|
||||||
"""LRU: ensure_peer (evict oldest + add new), send, update last_used."""
|
|
||||||
last_used = {BROADCAST: time.ticks_ms()}
|
|
||||||
for p in peers_list:
|
|
||||||
last_used[p] = time.ticks_ms()
|
|
||||||
# Pre-remove one so we have 19; ensure_peer(new) will add 20th.
|
|
||||||
esp.del_peer(peers_list[-1])
|
|
||||||
last_used.pop(peers_list[-1], None)
|
|
||||||
# Now 19 peers. Each iteration: ensure_peer(new) -> add_peer(new), send, update.
|
|
||||||
# Next iter: ensure_peer(new) -> already there, just send. So we need to force
|
|
||||||
# eviction each time: use a different "new" each time so we always evict+add.
|
|
||||||
t0 = time.ticks_us()
|
|
||||||
for i in range(ITERATIONS):
|
|
||||||
addr = bytes([0, 0, 0, 0, 0, 50 + (i % 30)]) # 30 different "new" MACs
|
|
||||||
peers = esp.get_peers()
|
|
||||||
peer_macs = [p[0] for p in peers]
|
|
||||||
if addr not in peer_macs:
|
|
||||||
if len(peer_macs) >= MAX_PEERS:
|
|
||||||
oldest_mac = None
|
|
||||||
oldest_ts = time.ticks_ms()
|
|
||||||
for m in peer_macs:
|
|
||||||
if m == BROADCAST:
|
|
||||||
continue
|
|
||||||
ts = last_used.get(m, 0)
|
|
||||||
if ts <= oldest_ts:
|
|
||||||
oldest_ts = ts
|
|
||||||
oldest_mac = m
|
|
||||||
if oldest_mac is not None:
|
|
||||||
esp.del_peer(oldest_mac)
|
|
||||||
last_used.pop(oldest_mac, None)
|
|
||||||
esp.add_peer(addr)
|
|
||||||
esp.send(addr, PAYLOAD)
|
|
||||||
last_used[addr] = time.ticks_ms()
|
|
||||||
t1 = time.ticks_us()
|
|
||||||
return time.ticks_diff(t1, t0)
|
|
||||||
|
|
||||||
def bench_add_then_remove():
|
|
||||||
"""Add peer, send, del_peer (remove after use). At 20 we must del one first."""
|
|
||||||
# Start full: 20 peers. To add new we del any one, add new, send, del new.
|
|
||||||
victim = peers_list[0]
|
|
||||||
t0 = time.ticks_us()
|
|
||||||
for i in range(ITERATIONS):
|
|
||||||
esp.del_peer(victim) # make room
|
|
||||||
esp.add_peer(new_mac)
|
|
||||||
esp.send(new_mac, PAYLOAD)
|
|
||||||
esp.del_peer(new_mac)
|
|
||||||
esp.add_peer(victim) # put victim back so we're at 20 again
|
|
||||||
t1 = time.ticks_us()
|
|
||||||
return time.ticks_diff(t1, t0)
|
|
||||||
|
|
||||||
def bench_send_existing():
|
|
||||||
"""Baseline: send to existing peer only (no add/del)."""
|
|
||||||
t0 = time.ticks_us()
|
|
||||||
for _ in range(ITERATIONS):
|
|
||||||
esp.send(peers_list[0], PAYLOAD)
|
|
||||||
t1 = time.ticks_us()
|
|
||||||
return time.ticks_diff(t1, t0)
|
|
||||||
|
|
||||||
print("ESP-NOW peer benchmark ({} iterations)".format(ITERATIONS))
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Baseline: send to existing peer
|
|
||||||
try:
|
|
||||||
us = bench_send_existing()
|
|
||||||
print("Send to existing peer only: {:>8} us total {:>7.1f} us/iter".format(us, us / ITERATIONS))
|
|
||||||
except Exception as e:
|
|
||||||
print("Send existing failed:", e)
|
|
||||||
print()
|
|
||||||
|
|
||||||
# LRU: evict oldest then add new, send
|
|
||||||
try:
|
|
||||||
us = bench_lru()
|
|
||||||
print("LRU (evict oldest + add + send): {:>8} us total {:>7.1f} us/iter".format(us, us / ITERATIONS))
|
|
||||||
except Exception as e:
|
|
||||||
print("LRU failed:", e)
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Add then remove after use
|
|
||||||
try:
|
|
||||||
us = bench_add_then_remove()
|
|
||||||
print("Add then remove after use: {:>8} us total {:>7.1f} us/iter".format(us, us / ITERATIONS))
|
|
||||||
except Exception as e:
|
|
||||||
print("Add-then-remove failed:", e)
|
|
||||||
print()
|
|
||||||
print("Done.")
|
|
||||||
253
esp32/main.py
253
esp32/main.py
@@ -1,253 +0,0 @@
|
|||||||
# Serial-to-ESP-NOW bridge: JSON in both directions on UART + ESP-NOW.
|
|
||||||
#
|
|
||||||
# Pi → UART (two supported forms):
|
|
||||||
# A) Legacy: 6 bytes destination MAC + UTF-8 JSON payload (one write = one frame).
|
|
||||||
# B) Newline JSON: one object per line, UTF-8, ending with \n
|
|
||||||
# - Multicast via ESP32: {"m":"split","peers":["12hex",...],"body":{...}}
|
|
||||||
# - Unicast / broadcast: {"to":"12hex","v":"1",...} (all keys except to/dest go to peers)
|
|
||||||
#
|
|
||||||
# ESP-NOW → Pi: newline-delimited JSON, one object per packet:
|
|
||||||
# {"dir":"espnow_rx","from":"<12hex>","payload":{...}} if body was JSON
|
|
||||||
# {"dir":"espnow_rx","from":"<12hex>","payload_text":"..."} if UTF-8 not JSON
|
|
||||||
# {"dir":"espnow_rx","from":"<12hex>","payload_b64":"..."} if binary
|
|
||||||
from machine import Pin, UART
|
|
||||||
import espnow
|
|
||||||
import json
|
|
||||||
import network
|
|
||||||
import time
|
|
||||||
import ubinascii
|
|
||||||
|
|
||||||
UART_BAUD = 912000
|
|
||||||
BROADCAST = b"\xff\xff\xff\xff\xff\xff"
|
|
||||||
MAX_PEERS = 20
|
|
||||||
WIFI_CHANNEL = 6
|
|
||||||
|
|
||||||
sta = network.WLAN(network.STA_IF)
|
|
||||||
sta.active(True)
|
|
||||||
sta.config(pm=network.WLAN.PM_NONE, channel=WIFI_CHANNEL)
|
|
||||||
print("WiFi STA channel:", sta.config("channel"), "(WIFI_CHANNEL=%s)" % WIFI_CHANNEL)
|
|
||||||
|
|
||||||
esp = espnow.ESPNow()
|
|
||||||
esp.active(True)
|
|
||||||
esp.add_peer(BROADCAST)
|
|
||||||
|
|
||||||
uart = UART(1, UART_BAUD, tx=Pin(21), rx=Pin(6))
|
|
||||||
|
|
||||||
last_used = {BROADCAST: time.ticks_ms()}
|
|
||||||
uart_rx_buf = b""
|
|
||||||
|
|
||||||
ESP_ERR_ESPNOW_EXIST = -12395
|
|
||||||
|
|
||||||
|
|
||||||
def ensure_peer(addr):
|
|
||||||
peers = esp.get_peers()
|
|
||||||
peer_macs = [p[0] for p in peers]
|
|
||||||
if addr in peer_macs:
|
|
||||||
return
|
|
||||||
if len(peer_macs) >= MAX_PEERS:
|
|
||||||
oldest_mac = None
|
|
||||||
oldest_ts = time.ticks_ms()
|
|
||||||
for mac in peer_macs:
|
|
||||||
if mac == BROADCAST:
|
|
||||||
continue
|
|
||||||
ts = last_used.get(mac, 0)
|
|
||||||
if ts <= oldest_ts:
|
|
||||||
oldest_ts = ts
|
|
||||||
oldest_mac = mac
|
|
||||||
if oldest_mac is not None:
|
|
||||||
esp.del_peer(oldest_mac)
|
|
||||||
last_used.pop(oldest_mac, None)
|
|
||||||
try:
|
|
||||||
esp.add_peer(addr)
|
|
||||||
except OSError as e:
|
|
||||||
if e.args[0] != ESP_ERR_ESPNOW_EXIST:
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
def try_apply_bridge_config(obj):
|
|
||||||
"""Pi sends {"m":"bridge","ch":1..11} — set STA channel only; do not ESP-NOW forward."""
|
|
||||||
if not isinstance(obj, dict) or obj.get("m") != "bridge":
|
|
||||||
return False
|
|
||||||
ch = obj.get("ch")
|
|
||||||
if ch is None:
|
|
||||||
ch = obj.get("wifi_channel")
|
|
||||||
if ch is None:
|
|
||||||
return True
|
|
||||||
try:
|
|
||||||
n = int(ch)
|
|
||||||
if 1 <= n <= 11:
|
|
||||||
sta.config(pm=network.WLAN.PM_NONE, channel=n)
|
|
||||||
print("Bridge STA channel ->", n)
|
|
||||||
except Exception as e:
|
|
||||||
print("bridge config:", e)
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def send_split_from_obj(obj):
|
|
||||||
"""obj has m=split, peers=[12hex,...], body=dict."""
|
|
||||||
body = obj.get("body")
|
|
||||||
if body is None:
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
out = json.dumps(body).encode("utf-8")
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
return
|
|
||||||
for peer in obj.get("peers") or []:
|
|
||||||
if not isinstance(peer, str) or len(peer) != 12:
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
mac = bytes.fromhex(peer)
|
|
||||||
except ValueError:
|
|
||||||
continue
|
|
||||||
if len(mac) != 6:
|
|
||||||
continue
|
|
||||||
ensure_peer(mac)
|
|
||||||
esp.send(mac, out)
|
|
||||||
last_used[mac] = time.ticks_ms()
|
|
||||||
|
|
||||||
|
|
||||||
def process_broadcast_payload_split_or_flood(payload):
|
|
||||||
try:
|
|
||||||
text = payload.decode("utf-8")
|
|
||||||
obj = json.loads(text)
|
|
||||||
except Exception:
|
|
||||||
obj = None
|
|
||||||
if isinstance(obj, dict) and try_apply_bridge_config(obj):
|
|
||||||
return
|
|
||||||
if (
|
|
||||||
isinstance(obj, dict)
|
|
||||||
and obj.get("m") == "split"
|
|
||||||
and isinstance(obj.get("peers"), list)
|
|
||||||
):
|
|
||||||
send_split_from_obj(obj)
|
|
||||||
return
|
|
||||||
ensure_peer(BROADCAST)
|
|
||||||
esp.send(BROADCAST, payload)
|
|
||||||
last_used[BROADCAST] = time.ticks_ms()
|
|
||||||
|
|
||||||
|
|
||||||
def process_legacy_uart_frame(data):
|
|
||||||
if not data or len(data) < 6:
|
|
||||||
return
|
|
||||||
addr = data[:6]
|
|
||||||
payload = data[6:]
|
|
||||||
if addr == BROADCAST:
|
|
||||||
process_broadcast_payload_split_or_flood(payload)
|
|
||||||
return
|
|
||||||
ensure_peer(addr)
|
|
||||||
esp.send(addr, payload)
|
|
||||||
last_used[addr] = time.ticks_ms()
|
|
||||||
|
|
||||||
|
|
||||||
def handle_json_command_line(obj):
|
|
||||||
if not isinstance(obj, dict):
|
|
||||||
return
|
|
||||||
if try_apply_bridge_config(obj):
|
|
||||||
return
|
|
||||||
if obj.get("m") == "split" and isinstance(obj.get("peers"), list):
|
|
||||||
send_split_from_obj(obj)
|
|
||||||
return
|
|
||||||
to = obj.get("to") or obj.get("dest")
|
|
||||||
if isinstance(to, str) and len(to) == 12:
|
|
||||||
try:
|
|
||||||
mac = bytes.fromhex(to)
|
|
||||||
except ValueError:
|
|
||||||
return
|
|
||||||
if len(mac) != 6:
|
|
||||||
return
|
|
||||||
body = {k: v for k, v in obj.items() if k not in ("to", "dest")}
|
|
||||||
if not body:
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
out = json.dumps(body).encode("utf-8")
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
return
|
|
||||||
ensure_peer(mac)
|
|
||||||
esp.send(mac, out)
|
|
||||||
last_used[mac] = time.ticks_ms()
|
|
||||||
|
|
||||||
|
|
||||||
def drain_uart_json_lines():
|
|
||||||
"""Parse leading newline-delimited JSON objects from uart_rx_buf; leave rest."""
|
|
||||||
global uart_rx_buf
|
|
||||||
while True:
|
|
||||||
s = uart_rx_buf.lstrip()
|
|
||||||
if not s:
|
|
||||||
uart_rx_buf = b""
|
|
||||||
return
|
|
||||||
if s[0] != ord("{"):
|
|
||||||
uart_rx_buf = s
|
|
||||||
return
|
|
||||||
nl = s.find(b"\n")
|
|
||||||
if nl < 0:
|
|
||||||
uart_rx_buf = s
|
|
||||||
return
|
|
||||||
line = s[:nl].strip()
|
|
||||||
uart_rx_buf = s[nl + 1 :]
|
|
||||||
if line:
|
|
||||||
try:
|
|
||||||
text = line.decode("utf-8")
|
|
||||||
obj = json.loads(text)
|
|
||||||
handle_json_command_line(obj)
|
|
||||||
except Exception as e:
|
|
||||||
print("UART JSON line error:", e)
|
|
||||||
# continue; there may be another JSON line in buffer
|
|
||||||
|
|
||||||
|
|
||||||
def drain_uart_legacy_frame():
|
|
||||||
"""If buffer does not start with '{', treat whole buffer as one 6-byte MAC + JSON frame."""
|
|
||||||
global uart_rx_buf
|
|
||||||
s = uart_rx_buf
|
|
||||||
if not s or s[0] == ord("{"):
|
|
||||||
return
|
|
||||||
if len(s) < 6:
|
|
||||||
return
|
|
||||||
data = s
|
|
||||||
uart_rx_buf = b""
|
|
||||||
process_legacy_uart_frame(data)
|
|
||||||
|
|
||||||
|
|
||||||
def forward_espnow_to_uart(mac, msg):
|
|
||||||
peer_hex = ubinascii.hexlify(mac).decode()
|
|
||||||
try:
|
|
||||||
text = msg.decode("utf-8")
|
|
||||||
try:
|
|
||||||
payload = json.loads(text)
|
|
||||||
line_obj = {"dir": "espnow_rx", "from": peer_hex, "payload": payload}
|
|
||||||
except ValueError:
|
|
||||||
line_obj = {"dir": "espnow_rx", "from": peer_hex, "payload_text": text}
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
line_obj = {
|
|
||||||
"dir": "espnow_rx",
|
|
||||||
"from": peer_hex,
|
|
||||||
"payload_b64": ubinascii.b64encode(msg).decode(),
|
|
||||||
}
|
|
||||||
try:
|
|
||||||
line = json.dumps(line_obj) + "\n"
|
|
||||||
uart.write(line.encode("utf-8"))
|
|
||||||
except Exception as e:
|
|
||||||
print("UART TX error:", e)
|
|
||||||
|
|
||||||
|
|
||||||
print("Starting ESP32 bridge (UART JSON + legacy MAC+JSON, ESP-NOW RX → UART JSON lines)")
|
|
||||||
|
|
||||||
while True:
|
|
||||||
idle = True
|
|
||||||
if uart.any():
|
|
||||||
idle = False
|
|
||||||
uart_rx_buf += uart.read()
|
|
||||||
drain_uart_json_lines()
|
|
||||||
drain_uart_legacy_frame()
|
|
||||||
|
|
||||||
try:
|
|
||||||
peer, msg = esp.recv(0)
|
|
||||||
except OSError:
|
|
||||||
peer, msg = None, None
|
|
||||||
|
|
||||||
if peer is not None and msg is not None:
|
|
||||||
idle = False
|
|
||||||
if len(peer) == 6:
|
|
||||||
forward_espnow_to_uart(peer, msg)
|
|
||||||
|
|
||||||
if idle:
|
|
||||||
time.sleep_ms(1)
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"ch": 6,
|
|
||||||
|
|
||||||
"peers": {
|
|
||||||
"12:3456789012":{
|
|
||||||
"select": [["name1", "preset1"]]
|
|
||||||
|
|
||||||
,
|
|
||||||
"ff:ff:ff:ff:ff:ff": {
|
|
||||||
"presets": {
|
|
||||||
"preset1": {
|
|
||||||
"pattern": "on",
|
|
||||||
"colors": ["#FF0000", "#00FF00", "#0000FF"],
|
|
||||||
"delay": 100,
|
|
||||||
"brightness": 127,
|
|
||||||
"auto": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Copy esp32/main.py to the connected ESP32 as /main.py (single line, no wrap).
|
|
||||||
cd "$(dirname "$0")/.."
|
|
||||||
pipenv run mpremote fs cp esp32/main.py :/main.py
|
|
||||||
Reference in New Issue
Block a user