| PyWiSim (Python) | espnow-protocols (C) |
|---|---|
class Node: on_receive(msg, sender) |
void on_recv(char sender, const uint8_t *data, size_t) |
self.broadcast(msg) |
wisim_broadcast(buf, len) |
self.unicast(dest, msg) |
wisim_unicast(dest_nid, buf, len) |
self.schedule(delay, fn, *args) |
wisim_schedule(delay_ms, cb, arg) |
self.net.log(...) |
wisim_log("...") |
WirelessNetwork(tx_range=..., loss=..) |
real radio; rssi_threshold to restrict reach |
loop.run(until=...) |
firmware runs forever |
Python tuples like ('RREQ', orig, dest, seq, rid, hops) become packed C
structs with a leading type byte:
typedef struct __attribute__((packed)) {
uint8_t type;
char orig;
char dest;
uint16_t seq;
uint16_t rid;
uint8_t hops;
} rreq_t;on_recv switches on data[0] (the type byte) and casts to the right struct.
Keep all message types under 240 bytes (ESP-NOW limits frames to 250 with
some header overhead).
Python uses arbitrary strings ('A', 'P1', etc.). We use a single char
to keep code terse and let us index state arrays directly by NID. Conventions:
'A'..'F'for line / mesh topologies.'C'= coordinator,'P','Q','R'= participants in two-phase commit (renamed from PyWiSim'sP1/P2/P3).
Direct port. seen set → bool g_seen[256]. The Python version increments
the seen set on first receipt to suppress re-broadcast; same here.
Neighbor enumeration differs: PyWiSim asks the simulator for nodes in
range. In our port, "neighbors" = every NID in node_table.c that has a
non-zero MAC. To get a real multi-hop tree on a single desk, set
cfg.rssi_threshold to drop weak peers.
The seen_rreqs set is a fixed-size ring (SEEN_CAP=32). For longer runs
or larger networks, increase it. Routing table is an array indexed by
NID — wastes memory but is trivially fast and bounded.
Sequence-number rollover is not handled (would matter for a multi-hour run with many discoveries).
random → esp_random(). Stagger first broadcast by (self - 'A') * 200ms
so the serial console isn't a wall of simultaneous output.
Renamed participants P1/P2/P3 → P/Q/R (single-char NIDs). 'R' is the
NO-voter to force ABORT, matching the Python example's third participant.
- Mobility (
mobility.py) — would need physically moving boards. - Encounters / DTN (
encounter.py) — same. - Distance-based loss model — replaced by RSSI threshold (a coarser
knob; configurable per-example via
wisim_config_t.rssi_threshold). - Carrier-sense backoff — the ESP-NOW stack does its own MAC-layer arbitration; the PyWiSim CSMA simulation isn't relevant here.
ESP-NOW recv callbacks fire in a restricted context (cannot call esp_now_send
from inside). We funnel every recv and timer event through a FreeRTOS queue
drained by a dedicated dispatcher task, so example code can freely call
wisim_broadcast / wisim_unicast / wisim_schedule from inside on_recv.
This matches the PyWiSim convention where on_receive can immediately call
broadcast etc. — same mental model.