Projects Experimental

Mycelium — ESP-NOW Sensor Mesh

A self-healing mesh of 12 battery-powered ESP32-C3 nodes that gossip sensor readings toward a gateway over ESP-NOW — no router, no pairing, no infrastructure.

Prototype since Feb 2026 #esp32#esp-now#mesh-networking#low-power
12
Nodes deployed
4
Max hop count
210 m
Node-to-node range
94 days
Battery life

Mycelium exists because I wanted sensor coverage across a space far bigger than one Wi-Fi router can reach, without paying the pairing-and-provisioning tax of Zigbee or BLE mesh. ESP-NOW gives you raw connectionless frames between ESP32s with no access point involved — so the question became: can 12 battery-powered ESP32-C3 nodes route around each other with nothing but broadcast frames and a little discipline?

How the gossip works

There is no routing table and no coordinator. Every node broadcasts its sensor reading in a small packet: source ID, a 16-bit rolling packet ID, a TTL starting at 5, and the payload. Any node that hears a packet it hasn’t seen before decrements the TTL, waits a random 5–40 ms backoff, and rebroadcasts. Gateways are just nodes that also log to SQLite instead of relaying. Nodes never know where the gateway is — packets diffuse outward and any gateway in range of the flood catches them. Adding a node is literally flashing it and turning it on.

The broadcast storm problem

Version one had no deduplication. With 12 nodes, one reading triggered every neighbor to rebroadcast, and their neighbors rebroadcast the rebroadcasts. Airtime saturated within seconds and packet delivery collapsed to about 30%. The fix was a per-source dedup cache — each node remembers the last 8 packet IDs it has seen from every source and drops repeats before they touch the radio:

bool seen_before(uint8_t src, uint16_t pkt_id) {
    dedup_ring_t *ring = &cache[src];
    for (int i = 0; i < DEDUP_DEPTH; i++)
        if (ring->ids[i] == pkt_id) return true;
    ring->ids[ring->head++ % DEDUP_DEPTH] = pkt_id;
    return false;
}

Rebroadcasts dropped by roughly 85% and end-to-end delivery came back up to 97% over four hops. The rolling ID wraps at 65535, which is fine at one packet per minute — a stale collision would need 45 days of perfectly aligned silence.

Range and battery, measured

Line-of-sight node-to-node range came out at 210 m at the C3’s default TX power; through two interior walls it fell to about 40 m, which set the node spacing indoors. On a 3000 mAh 18650 with 60-second wake cycles, measured average draw works out to 94 days per charge — the wake window dominates, not the transmit. Relay-heavy nodes near the gateway drain faster, which is the honest cost of a flooding protocol and the first thing the roadmap addresses.

Development timeline

  1. 2026-02

    Two-node link

    First ESP-NOW packets exchanged, measured raw range with a walk test.

  2. 2026-03

    Gossip protocol v1

    Flooding with TTL worked; also produced my first broadcast storm.

  3. 2026-04

    Dedup cache

    Rolling packet IDs cut rebroadcasts by roughly 85% at 12 nodes.

  4. 2026-06

    Battery deployment

    All 12 nodes on 18650 cells reporting through up to 4 hops.