loomwave://mesh — status: weaving_ v0.x · hidden-node LoRa mesh · open firmware
◇ Mesh network · woven from scratch

A network you
weave by hand.

A from-scratch LoRa mesh platform built around the hidden-node regime — reliable delivery and graceful degradation from 5 to 500 nodes, with no carriers and no required infrastructure. You flash the firmware. The network grows itself.

Flash a device → Read the docs flasher.loomwave.org
// the premise

The loom and the wave.

the loom — handcraft

From scratch. Tagged. Validated.

No protocol decision rests on intuition. Every claim cites a tagged simulation result, a named hardware run, or a captured real-world trace — the project's quantitative-discipline rule. Seven client platforms shipping on one mesh stack; built by one engineer with AI-accelerated R&D in the loop.

the wave — technical

Radio packets. Scheduled access where the hub can see everyone; epoch-hash contention where it can't.

Engineered from first principles around the hidden-node regime that breaks contention MACs — turn the topology liability (one elevated node hears many mutually-hidden terminals) into the asset that makes a collision-free schedule possible.

01

No infrastructure required

No carriers, no SIM. Where an elevated node exists it becomes the time, slot, and modulation authority for a collision-free schedule. Where it doesn't, the network drops to epoch-hash contention with no carrier sense — a floor that never performs worse than the alternatives.

02

Coordinator-liveness failover

Clients detect a dead coordinator by beacon silence and either re-home onto an adjacent cell or drop into the coordinator-free fallback. Authority concentration buys schedule feasibility without becoming a single point of failure.

03

Designed to scale

From-scratch protocol, not bolted onto a legacy stack. Validated three ways — calibrated simulation, hardware bring-up across seven client platforms, and a captured real-world hidden-node hub. Target: graceful operation from 5 to 500 nodes.

// the why

Why another mesh?

Flood-based meshes are simple and need no coordination, but channel load grows as N²·ToA/T — on default long-range LoRa settings the channel saturates at roughly 5–11 nodes at one message per node per minute.

Placing a relay on a hilltop makes things worse: it gathers more mutually-hidden transmitters into one collision domain, and carrier-sensing can't help because a node can't sense a signal it can't hear. These are the failure modes Loomwave is built around.

// the stack

Three layers, chosen against an airtime budget.

MAC

Hybrid CSMA / TDMA

Rate-capped CSMA discovery plus a TDMA data plane. TDMA solves the hidden-node problem by construction — no GPS required. Degrades gracefully to flood behavior without a coordinator.

Routing

Infrastructure-aggregated distance-vector

Flooded control is O(N²) at scale. Aggregating through infrastructure nodes makes it O(N): scheduled unicast registration with link-state only among infrastructure nodes.

Security

Per-packet AEAD

Authenticated encryption on every packet with a derived nonce and truncated tag (~16 bytes overhead). Replay protection with no synchronized clocks required.

// the hardware

Node roles.

Separate optimized firmware per role — not one binary.

RoleHardwareFunction
CLIENT ESP32 / nRF52
Heltec, T-Beam, RAK4631…
User endpoint via BLE/USB. Sleeps aggressively. Relays only if configured.
REPEATER Same hardware class Always-on relay with neighbor table and routing. Optimized for solar/battery field deployment.
INFRA Linux SBC Full routing with link-state. Time and slot authority. Persistent store-and-forward. Optional MQTT/IP bridge.
// firmware

The current release.

client-fw-2026.07.09 — 911.5 MHz, SF9, BW125. The current fleet build fixes delivery confirmations on busy networks — a full peer-key table could silently block receipts, so senders saw "failed" for delivered messages; now the table is larger with smart eviction, and confirmations wait for keys instead of dropping — on top of the shared node map, cell-wide position relay, and the offline inbox. All seven platforms: Heltec T114, XiaoGator, RAK WisBlock RAK4631, Seeed T1000-E, Heltec V3, Heltec V4, and Station G2 (client + backbone relay). Flash directly from a Chrome or Edge browser at flasher.loomwave.org — no tools or drivers required.

BoardRadio / MCUFlash method
Heltec T114 nRF52840 · SX1262 · L76K GPS UF2 drag-and-drop
XiaoGator v0.9 nRF52840 · E22-900M30S (1 W) · L76K GPS UF2 drag-and-drop
RAK WisBlock RAK4631 nRF52840 · SX1262 UF2 drag-and-drop
Seeed SenseCAP T1000-E nRF52840 · LR1110 · AG3335 GPS UF2 drag-and-drop
Heltec WiFi LoRa 32 V3 ESP32-S3 · SX1262 Web installer — one click
Heltec WiFi LoRa 32 V4 ESP32-S3 · SX1262 + FEM Web installer — one click
Station G2 (client) ESP32-S3 · SX1262 Web installer — hold BOOT first
Station G2 (coordinator) ESP32-S3 · SX1262 Web installer — hold BOOT first
Browser-based web installer

Flash Heltec V3, Heltec V4, or Station G2 directly from Chrome or Edge — connect via USB, click Flash, done. For nRF52840 boards (T114, XiaoGator, RAK4631, T1000-E), double-tap reset to enter bootloader, then drag the .uf2 file to the drive that appears.

Open flasher →
// join the weave

Bring your own node.

Grab a supported radio board, open the flasher in your browser, and you're a thread in the cloth. No accounts, no gatekeepers.

Open the flasher →
01
Get a supported radio board
ESP32 + LoRa class hardware. Cheap, off-the-shelf.
02
Open the web flasher
Plug in over USB. Firmware writes in seconds.
03
Join the weave
Power on — your node finds the mesh and relays.
// the progress

Roadmap.

// news & updates

A record of milestones.

DateMilestone
2026-07-09 Android Auto, field-validated — the full hands-free loop in a real truck — on an actual head unit, the in-car screen showed the live mesh map, and a message crossed the network to the truck, was read aloud, and the spoken reply traveled back over the mesh to the sender. The complete incoming + reply loop, validated in the field with the Google Play build — no developer mode, no cables.
2026-07-09 Firmware release client-fw-2026.07.09 — road-test bug fixed the same night (#131) — the truck test flushed out a real reliability bug: on a busy cell, a node's peer-key table could fill up and silently block delivery confirmations — messages arrived fine, but senders burned their retries and saw "failed." Root-caused, fixed (larger table, least-recently-used eviction that never evicts active peers, and receipts that wait for keys instead of dropping), bench-validated end-to-end, and shipped to every board that night. Flash at flasher.loomwave.org.
2026-07-09 The app is on Google Play (internal testing) — with an automated release pipeline — build, sign, upload, and release notes in one command via the Play Developer API. The Play build is what unlocks the complete Android Auto experience on real head units, and internal testers now get updates through the store instead of sideloading. Closed testing is next.
2026-07-08 The mesh map is live — every opted-in node on a real map, from any node (#114) — nodes that turn on "share my location" now appear as pins on a street-map view in the app and on the Android Auto car screen. Positions relay cell-wide, so the map is complete no matter which node your phone connects through — including a headless Pi tower. Works offline too: with no internet the map stays pannable and pins render on a plain backdrop. Confirmed on the production tower and in the field.
2026-07-07 Any two nodes can now message each other reliably (#120) — messaging between two client devices could silently fail in one direction — permanently — with a "no key" error, while the reverse direction worked fine. Two separate causes: a power-napping device only listened during part of each cycle and kept missing the moment the coordinator shared a particular peer's identity, and a long-settled device's cached identity quietly expired at the coordinator with nothing to refresh it. Both are fixed — devices now re-share their identity at a low background rate, and the coordinator varies the order it relays them — so every device learns every other device's key regardless of timing. Bench-proven on the pairs that used to fail.
2026-07-07 Seeed T1000-E reception fixed — from ~⅓ of beacons missed to none — the LR1110-radio tracker was dropping 25–37% of the tower's beacons even at close range and bouncing between coordinators. The culprit was its own radio-stall recovery: it wasn't restoring the radio's high-sensitivity power mode after firing, and it was re-initializing the radio every healthy frame instead of only after a genuine miss. With both fixed, a bench T1000-E went from 5-of-8 beacons to 18-of-18 — zero misses. (This also retired the long-standing theory that power-napping caused the drops; the misses happened with napping fully off.)
2026-07-06 On-node inbox — messages wait for your phone instead of getting lost (#127) — a message that arrived while your phone app was disconnected used to vanish: the node handed it to a Bluetooth link nobody was listening on. Now the node keeps a small on-device inbox of undelivered messages and replays them when the phone reconnects — and it survives a node reboot, since the buffer is persisted to the node's flash. The queue is bounded (newest messages win) and replayed gently so a backlog never stalls the radio schedule. A follow-up fix ensured the on-device drive-log can never crowd the inbox out of flash — verified end to end: messages injected, node hard-reset, both replayed on boot.
2026-07-06 Companion app reliability pass — several quality fixes landed together: message threads are now scoped to the node you're connected through (and you can't message yourself); a delivered message (✓✓) can no longer flicker back to a single check when a late radio retransmit arrives; each node can be individually forgotten and display names are sanitized; and a small build-version footer makes it easy for testers to report exactly which build they're on.
2026-07-04 In-car node map and mesh status — Android Auto screens verified; Play Store next (#78) — the companion app's in-car surface grew beyond hands-free messaging: a node map and a live mesh-status screen over Android Auto, validated on Google's head-unit emulator (one crash traced to a missing map-template permission and fixed). With a published privacy policy and upload signing in place, the app is headed to Play Store closed testing — so testers get the in-car screens without developer mode. Also new: a proper Loomwave launcher icon and a clear-node-list control.
2026-07-03 Node positions, phase 1 — "you are here" with privacy by default (#114) — the phone now receives its own node's position over the wire: a compact GPS block (live fix, surveyed location, or none) shown as a "you are here" line in the app. Sharing location with the rest of the mesh is a separate, opt-in switch — default off, persisted on the node itself — decided as the privacy model before any position ever leaves the device.
2026-07-03 Heltec V4 GPS — first confirmed fix on hardware — the original bring-up was driving the GPS module's reset line as "enable," holding it in reset forever, and listening on the wrong UART pin. A one-boot diagnostic pin sweep (kept in the firmware for future boards) measured the truth: enable, reset, and UART orientation all corrected, and the V4 went from GPS-dead to a live fix in under a minute.
2026-07-03 Field usability: power-off, button wake, chirp de-spam, dead-battery guard — hold the button ~4 s for a power-off with an on-screen countdown (release to cancel); button presses now register instantly via interrupt instead of a 1 Hz poll that caught ~1 in 5 taps; join chirps now sound only on the first join after boot or on recovery from a real outage, instead of beeping on every rejoin; and a V3/V4 with a dead battery now sleeps and waits for charge instead of brownout boot-looping under radio-init load — a real field failure.
2026-07-03 Roaming loyalty and eviction patience (#59, #112, #116) — a client between two comparable coordinators no longer ping-pongs: the incumbent gets a 6 dB hysteresis edge, and a transient beacon loss no longer blacklists it — a 10-second re-acquire grace stops the client from greedily grabbing whichever coordinator speaks first, so the incumbent's next beacon wins. This churn turned out to be the real source of tracker battery drain; bench-validated at zero coordinator changes in 150 s where before it flipped roughly every 25 s. On the coordinator side, small cells now require both missed roster turns and a real silence window before evicting, so a collision burst can't knock out a live member.
2026-07-03 "Cell full" is gone — PACT-lite membership on the ESP32 coordinator and tower (#109) — membership is now a table row (up to 32 members) with a rotating per-frame telemetry roster, replacing the three standing slots that had a fleet of live clients playing musical chairs between coordinators all night. Ported byte-identical from the Rust coordinator to the ESP32 and hardware-validated with the live fleet: everyone seated, roster rotating, rows held. Beacon timing now uses the measured time-on-air instead of a stale constant (up to ~370 ms of error at worst case), and formerly silent receive drops now log with names.
2026-07-03 Mesh-clock hardening — the schedule epoch always advances (#110) — on-air debugging traced a serving-cell collapse to a frozen schedule epoch: clients hash their contention-window picks from it, so a frozen value locked them into permanent collision patterns and the member table drained to zero. The epoch now always moves forward, converges to a pure UTC-derived value in steady state on time-anchored coordinators, and caps how long any single value can be re-served (encryption nonce safety). Clients also mix a per-beacon counter into their picks so a held epoch can never freeze them again.
2026-07-03 Node identity from persisted hardware randomness (#94) — node addresses now derive from a persisted hardware TRNG seed instead of the radio's broadcast MAC, so identity survives reflashes and reboots and is no longer tied to a hardware identifier the radio announces to the world. The whole device fleet was rebuilt and published; a node updating from an older build gets a new address once, on first boot, by design.
2026-07-03 Identity pins survive eviction (#112) — a member evicted for radio silence used to lose its pinned public key, so its next join stalled awaiting a fresh identity advert and, worse, its address was briefly open to being claimed under a different key — defeating first-writer-wins exactly when the rightful owner was knocked out. Trust-on-first-use pins, session keys, and replay history now persist through eviction, and the pin table is hard-capped so an attacker can never flood out an existing pin.
2026-07-02 Live network telemetry over MQTT (#89) — infrastructure coordinators can now stream liveness, periodic metrics, and topology events (joins, evictions, backbone adjacency, routing forwards) to an MQTT broker — opt-in via a single environment variable, fully off when unset. The radio frame loop is never blocked: publishing rides a bounded queue in a dedicated thread, and the connection self-heals with backoff when the broker is down or slow. Groundwork for centralized live mesh mapping.
2026-07-02 Coordinator hardening — nonce-reuse fix and hardware watchdog (#95, #104) — coordinators now persist an encryption-epoch high-water mark across reboots, closing a window where a rebooted coordinator re-used (epoch, sequence) pairs — two packets sealed under the same keystream is a serious AEAD break. Separately, a hardware watchdog self-resets a wedged coordinator, and radio-init failure now retries then hard-resets instead of hanging until a manual power cycle. Scoped to the ESP32 coordinator; the nRF52 watchdog stays off deliberately to protect the headless-reflash DFU window.
2026-07-02 Android app — reliability and persistence (#99, #100) — message history, the learned peer directory, and the chosen connection now survive app restarts. Bluetooth auto-reconnects with exponential backoff after an unexpected drop (walking out of range, node reboot) instead of staying down until a manual tap. And a listener leak that made each reconnect show incoming messages an extra time is fixed, with incoming messages now deduplicated by ID.
2026-07-02 Wired INFRA-to-INFRA backbone — hardware-validated over a real LAN (#64/#72) — the coordinator-to-coordinator backbone is now transport-agnostic: two distinct coordinators formed a signed-identity adjacency and exchanged bidirectional AEAD-sealed cross-cell frames over TCP, with none of the RF forwarding cap. A wired trunk can also be pinned to its designated partner tower (a peer-authorization ACL), so an open listener no longer trusts any node with a valid identity.
2026-06-29 Firmware release client-fw-2026.06.29 — the reliable-delivery + automatic-retransmission build is now the published fleet release, and the platform roster grows to seven with the Seeed SenseCAP T1000-E (first LR11x0-radio client, with live GPS) and the Heltec WiFi LoRa 32 V4. Flash at flasher.loomwave.org.
2026-06-29 Frame-integrity CRC16 — ghost-message bug fixed (#83) — first field test from a real masted site surfaced a confusing bug where garbled 'ghost' messages from phantom contacts appeared on the phone. Root cause: a dropped BLE notification desynced the phone↔node frame parser, which locked onto a stray byte and rendered a frame header as a message source. A trailing CRC16-CCITT on every BLE frame now lets both ends detect a false lock and resync cleanly. The whole device fleet plus the Android app were rebuilt to matched builds and published.
2026-06-28 Hands-free in-car messaging — validated in an actual vehicle (Android Auto) — a message sent across the LoRa mesh was read aloud by Android Auto on the receiving phone, and a spoken voice-reply traveled back over the mesh to the sender. The full incoming + reply loop, no hands.
2026-06-28 Opportunistic GPS time-sync — first coordinator adoption (#79) — foundation for network-wide time sync where any node with a GPS fix (client or coordinator) can anchor the whole mesh's clock. Bench-validated: a free-running coordinator adopted a Heltec T114 client's GPS-derived UTC over the air and re-derived its epoch around the better source. The first step toward seamless cross-cell message relay.
2026-06-28 In-cell delivery reliability improvements — hardware-verified — two complementary fixes: the coordinator now TXes pending downlinks ahead of housekeeping traffic at each frame boundary (in-cell delivery jumped from 73% → 90%), and the client adds app-layer ARQ that resends an un-acked message every 45 s (up to four retries) so messages survive transient RF loss. Bench-validated end-to-end: a receiver reset mid-send still received the message at +64 s after rejoin.
2026-06-28 LR1121 receive sensitivity recovered ~38 dB — the Nebra Duo's E80-900M2213S has its RF-switch DIOs wired opposite the LR1121 eval-board convention the driver assumed. Fixing the mapping (rx=DIO6) brought the mid-tower from −89 dBm to −51 dBm on the bench; distant nodes that were buried in the noise floor came back. TX was unaffected (both DIOs are HIGH on transmit, swap-immune), which is why the issue stayed hidden until receiving a distant signal.
2026-06-24 RFC specification paper published — the full Loomwave design document is now online at loomwave.org/rfc/. Covers the complete stack: motivation, protocol, simulation results, and all hardware-verified results through Stage 6.
2026-06-24 Boundary make-before-break handoff (#59) merged to main — when a peer coordinator's LSA confirms a client has roamed to it, the previous coordinator releases the slot immediately instead of waiting the ~50 s silence timeout. Eliminates wasted slots and stale LSAs at cell boundaries. Baked into the current field-test firmware; field verification planned June 27–28.
2026-06-23 Dual-radio hub — two full cells on one node, hardware-verified — a single coordinator (Pi + Duo HAT) ran two independent cells simultaneously: SX1262 on channel A (915 MHz) and LR1121 on channel B (916 MHz). Heltec V3 joined cell A and V4 joined cell B, both streaming telemetry concurrently for 70+ frames, zero errors. Opens the capacity-layer path to N>1 cells per infrastructure node.
2026-06-23 Per-link bandwidth — hardware-verified — each roster member now runs at its own (SF, BW) pair within the same frame. Bench-verified: SF7/BW500 for a strong member alongside SF9/BW125 for a marginal one in a single transmission, 13+18 positions, zero overruns.
2026-06-23 Automatic (SF, BW) modulation selection — hardware-verified — the coordinator auto-selects the fastest bandwidth the cell can support, stepping up in hysteresis-respecting stages: SF9/BW125 → SF9/BW500 → SF7/BW500. Hardware-verified on the bench: member followed all steps (66 positions). SF7/BW500 delivers 4× the capacity of the SF7/BW125 floor that spreading-factor selection alone cannot reach.

Older milestones — the full build record from the start →

// case studies

Production data, real networks.

Captures from real, healthy LoRa mesh networks — analyzed to understand what current protocols do well, where they hit structural limits, and what that means for the Loomwave design.

Meshtastic

North Georgia metro, June 2026

19,049 packets · 77.8 h continuous · 312 nodes · 37 MQTT gateways. What a well-run Meshtastic network looks like on the air — and what no amount of tuning can fix without a protocol change.

Download PDF
MeshCore

Atlanta repeater network, June 2026

19,697 packets · up to 79.8 h · three PYMC repeater vantages. Source-routed unicast and LBT fix several Meshtastic failure modes — and reveal where the next structural wall stands.

Download PDF
// node operators wanted

Meshtastic & MeshCore operators — share what your node hears.

The Stage 1 analysis is grounded in data from a real ~842-node network in North Georgia, pulled from the Meshview and Malla public APIs. Those sources give breadth. A direct connection to a live node gives depth — and some things that public aggregators structurally cannot provide:

Packet loss

Measured, not modeled

Public loggers only record packets that decoded and uplinked — collisions, CRC errors, and dropped packets are invisible (survivorship bias). A node's own counters (num_packets_rx_bad, num_rx_dupe, channel_utilization) are the only source of real loss data.

Propagation

Clean SNR/RSSI samples

Position, antenna, role, and firmware are all known exactly, so every reception is an unambiguous propagation sample with no guesswork about the receiver's location or config — unlike a position-join from a third-party log.

Protocol behavior

Why, not just what

The node exposes the decisions it made: route type, duplicate suppression, drop reasons, per-packet airtime. Aggregated logs show what moved. The node shows why it forwarded, dropped, or deferred — which validates the protocol design directly.

Connecting to a Meshtastic node and a MeshCore node in the same area produces a controlled head-to-head comparison in one RF environment — impossible from separate public tools alone.

How to share — lowest-trust options first

We only ever subscribe to heard-packet metadata and telemetry counters. We never send messages, change configuration, or record message content — only payload byte-length and packet type. Access is read-only, on your terms, revocable anytime.

operator_capture.py

Self-contained Python script. Runs on the Pi hosting meshtasticd. Connects only to 127.0.0.1 — nothing is exposed to the network. Produces a single .zip archive to share. One short file you can read before running anything.

Download script

To get involved or ask questions: github.com/Loomwave