From 8689bdb6ef2821e7dfaeaa9e6fa4ce9bb6e51108 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Sat, 21 Mar 2026 19:59:52 +1300 Subject: [PATCH] Restore esp32 MicroPython sources (main, benchmark_peers) Adjust .gitignore to ignore esp32/* except *.py so firmware .bin stays untracked. Made-with: Cursor --- .gitignore | 4 +- esp32/benchmark_peers.py | 112 +++++++++++++++++++++++++++++++++++++++ esp32/main.py | 63 ++++++++++++++++++++++ 3 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 esp32/benchmark_peers.py create mode 100644 esp32/main.py diff --git a/.gitignore b/.gitignore index 582c7a4..2ee9657 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,8 @@ Thumbs.db *.db *.sqlite -esp32 +# ESP32: track MicroPython sources; ignore firmware blobs (can't ignore esp32/ then un-ignore children) +esp32/* +!esp32/*.py diff --git a/esp32/benchmark_peers.py b/esp32/benchmark_peers.py new file mode 100644 index 0000000..49f4f6e --- /dev/null +++ b/esp32/benchmark_peers.py @@ -0,0 +1,112 @@ +# 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.") diff --git a/esp32/main.py b/esp32/main.py new file mode 100644 index 0000000..26c8bb5 --- /dev/null +++ b/esp32/main.py @@ -0,0 +1,63 @@ +# Serial-to-ESP-NOW bridge: receives from Pi on UART, forwards to ESP-NOW peers. +# Wire format: first 6 bytes = destination MAC, rest = payload. Address is always 6 bytes. +from machine import Pin, UART +import espnow +import network +import time + +UART_BAUD = 912000 +BROADCAST = b"\xff\xff\xff\xff\xff\xff" +MAX_PEERS = 20 + +network.WLAN(network.STA_IF).active(True) +esp = espnow.ESPNow() +esp.active(True) +esp.add_peer(BROADCAST) + +uart = UART(1, UART_BAUD, tx=Pin(21), rx=Pin(6)) + +# Track last send time per peer for LRU eviction (remove oldest when at limit). +last_used = {BROADCAST: time.ticks_ms()} + + +# ESP_ERR_ESPNOW_EXIST: peer already registered (ignore when adding). +ESP_ERR_ESPNOW_EXIST = -12395 + + +def ensure_peer(addr): + """Ensure addr is in the peer list. When at 20 peers, remove the oldest-used (LRU).""" + peers = esp.get_peers() + peer_macs = [p[0] for p in peers] + if addr in peer_macs: + return + if len(peer_macs) >= MAX_PEERS: + # Remove the peer we used least recently (oldest). + 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 + + +while True: + if uart.any(): + data = uart.read() + if not data or len(data) < 6: + continue + addr = data[:6] + payload = data[6:] + ensure_peer(addr) + esp.send(addr, payload) + last_used[addr] = time.ticks_ms() \ No newline at end of file