Meshtastic on FrogNet.
Summary
Implement Meshtastic's defined API as a new, transport-agnostic application on FrogNet, built on Ribbit shared RAM and held-read scheduling. The API is the compatibility contract; everything behind it is greenfield and designed around FrogNet's model, not Meshtastic's internal architecture.
Compatibility is verified with Meshtastic's existing tests, with stock Meshtastic resolving gaps and ambiguities. Both implementations then run under the same simulated link conditions across machines, and are compared on functionality, traffic, latency, capacity, source size and complexity.
Once compatible, the system extends to websites, databases, sensors, AI, voice and video, each sized to the transport that carries it. The goal is a working system the Meshtastic community runs: the functionality they already use, on FrogNet, with measured differences and room for far broader services. No physical radios are needed to prove it; the boards are how it ships.
What this proves
Testing establishes that it works; comparison establishes what it saves. Three claims, all provable in simulation.
- Compatibility. Unmodified Meshtastic clients and tests work against the FrogNet implementation.
- Measured difference. Under identical imposed link conditions, the two are compared side by side on traffic, latency, capacity, source size and complexity.
- Reach. The same system carries services Meshtastic cannot, each through its own interface, scaled to whatever the link allows. Existing clients keep the Meshtastic API unchanged.
The radio is a byte pipe: it carries Meshtastic packets or FNWP SAME/DIFF without knowing which. It drops out of the comparison only if both implementations face the same shared channel: the same airtime, contention, loss and connectivity. Independent rate-limited connections would not be equivalent to a shared radio channel. Running on boards people own is how the working system ships, after the proof.
The contract
The primary contract is the client API, defined in Meshtastic's protobufs repository. The over-the-air wire is the second edge, needed for mixed meshes.
| Contract | What it is | Who uses it | Role here |
|---|---|---|---|
| Client API | ToRadio / FromRadio protobuf stream over BLE, serial and TCP 4403 | Phone apps, Python CLI, Home Assistant and other integrations | Primary contract; must be exact |
| Over-the-air wire | MeshPacket format, channel PSK encryption, PKI direct messages | Unflashed stock nodes in range | Edge to stock neighbours in a mixed mesh |
| MQTT gateway | Meshtastic's MQTT topics | Internet bridging | Optional edge, off by default: one participant holds reads on the cells and publishes Meshtastic's MQTT topics for outside integrations. No MQTT or other pub/sub inside the system |
The defined API, as published in the protobufs, is the contract. The Phase 0 inventory maps which parts of it existing tests cover and where the gaps are; stock meshtasticd resolves the gaps.
Architecture
The contract edges are kept; everything between them is replaced. Kept: the client API with byte-exact framing, the config surface, the incumbent's tests, and the sensor, GPS, display and power drivers. Between those edges none of Meshtastic's internal machinery survives: no packet path between nodes, no node database, no module messaging. Each node writes its own truths into its own cells, services act on owned cells, and the client edge builds every response a client expects from a write or a read.
Each node is the only writer of its own cells: identity with its public key, position, telemetry and status as latest-value cells; channel and direct messages in a ring per sender; config as permanent cells. Every reader acts by a held read on the families it serves. Each node runs three participants: the client edge (the client API, and every ACK, NAK and reply), the own-facts writer (GPS, sensors, owner changes) and the admin applier (remote admin intents applied to its own cells). MQTT and the over-the-air link are optional edges. A request from a client for position, telemetry, node info or a remote admin get is answered by that client's own edge reading the owner's cell; no request travels to another node and no reply comes back. Liveness comes from session-bound families: when a node's session ends its cells go, and every reader learns it is gone. A want_ack is answered by the sender's edge when its write lands, under the condition stated in Acknowledgment below. Phase 1 reached this in mechanical steps, with stock's packet path and modules still running over the memory to learn the incumbent; that scaffolding is not the system.
No node keeps a copy or aggregation of another node's data. Meshtastic's node database is removed: each node writes its own user record with its public key, position, telemetry and status once, in its own cells, and every read the services made of the node database is a read of the owner's cell. A PKI key is one read; the node list in the config dump is a read of the user family when the dump runs; last heard is the cell's updated time.
Ingress and egress
Only two boundaries need Meshtastic fidelity; everything between them is Ribbit. The initial implementation and comparison need only the client API edge; the over-the-air edge is an additional deliverable.
- Client API edge. In from phones and tools, out to them. A Ribbit handler speaks ToRadio / FromRadio; each client request is the RAM call, made in the handler and done.
- Over-the-air edge. In from stock nodes, out to them. A FrogNet node that speaks Meshtastic on the air is also a gateway between the stock mesh and the rest of FrogNet.
Ingress volume is handled by the memory contract, not by a queue designed in advance. NodeInfo, position and telemetry are owner-written cells, so any number of updates from one node is one cell overwritten, unique message IDs or not: stored state stays bounded. That bounds state, not the work of receiving updates and delivering changes to readers. On LoRa the radio should not outrun the RAM: LongFast carries roughly a kilobit a second, a few packets a second across the whole channel (estimated from airtime), while the RAM host has been measured at thousands of writes a second across a 24 ms path. Faster transports carry no such margin by default. Whether any link needs a rate limit is established by the Phase 2 load surface; where one is needed, a tuple sets it. A node flooding the channel can only rewrite its own cells. Admission follows the incumbent: a previously unknown peer is accepted exactly when stock Meshtastic would accept it.
Identity crosses the client API edge. Node identity, PKI keys and channel PSKs arrive through the client API's admin messages and must survive a reflash; otherwise stock peers see a reflashed node as a new one.
Verification
Meshtastic has no single conformance suite like Redis's, so stock Meshtastic arbitrates more here than in earlier conversions. Oracles, in order of authority:
| Oracle | What it covers | Status |
|---|---|---|
| Stock meshtasticd (native Linux build) | Ground truth for any behaviour the tests leave open | 2.8.2.332b5b0 built and run, pio run -e native |
| Firmware hardware test suite (pytest harness in meshtastic-mcp) | Device-level behaviour | Hardware tiers find devices by USB VID or hub slot only; its sim e2e passes against stock |
| Python CLI tests | Client library; testSimulator connects to a node on localhost; smokevirt (40 tests, one sim node) and smokemesh (7 tests, 3-node chain) run against meshtasticd |
40/40 and 7/7 pass against stock |
| RepeaterTastic golden vectors | 25 vectors captured from meshtasticd 2.7.26 and 2.8.0; PKI DMs with ACKs over UDP multicast | 12/12 recaptured from stock 2.8.2 verify; one shape change from 2.8.0 |
| Firmware native C++ suites (bin/run-tests.sh) | 88 in-process unit suites under ASan | 88/88 pass against stock |
No AI-written tests serve as the arbiter.
Comparison rig and metrics
Both systems ride one link model, so the only variable is the stack above it. The comparison is meshtasticd against the FrogNet node: same link underneath, same clients on top.
- Link model. Meshtasticator's PHY and pathloss code (airtime per modem preset, selectable pathloss models), extracted into a shim both sides use. The shim is one shared channel: a transmission occupies airtime for every node in range, with the same contention, collisions, loss and connectivity for both implementations. Meshtasticator's interactive mode already runs real meshtasticd instances with the LoRa chip emulated over TCP; meshtasticd also has a UDP multicast sim-radio path.
- Where it runs. FrogNet's own simulator in /opt/frognet_semantic, tier 3 for complex topologies, multi-machine with traffic crossing. No new harness.
- Test control. Participants advertise in shared RAM; the controller owns the instruction tuple; clients pick it up by family trigger; results return the same way.
- Load surface. Fix the link rate and raise node count until delivery degrades; then fix node count and lower the rate.
| Metric | Measured as |
|---|---|
| Functionality | Defined API behaviour matching stock, per message type |
| Traffic | Airtime, bytes and transmissions on the shared channel per delivered message, including RAM-host access and recovery |
| Latency | Send to delivery, per message |
| Capacity | Nodes and message rate before delivery degrades; lost messages not counted as throughput |
| Source size | Lines of node code, Meshtastic firmware vs FrogNet node |
| Complexity | Components and protocol paths in each stack |
Capabilities added and problems targeted
Capabilities added
- Any transport. The same apps over LoRa, Wi-Fi, wired and Internet tunnels; a user is reachable beyond RF range through FrogNet itself.
- Shared state. Node list, positions and telemetry are owner-written cells any participant reads, instead of each node rebuilding its own picture from broadcasts.
- Broader services. Websites, databases, sensors, AI, voice and video, sized to the link.
- Internet RAM. Applications anywhere read and write the mesh's state under the same model.
Problems targeted
- Flood congestion. Meshtastic broadcasts use managed flooding; with 2.6/2.7's SNR suppression and next-hop DMs, cost still scales O(n) per message (MeshRoute). Wellington's mesh passed 150 active nodes by Q3 2024 and hit significant congestion (Meshtastic blog). Ribbit is designed to deliver changes only to the readers that need them; the comparison tests whether that cuts airtime, counting every transmission, including RAM-host access and recovery.
- Liveness chatter. Repeaters announce about every 3 hours; telemetry and GPS broadcasts consume transmit time. With SAME/DIFF, unchanged state should cost little on the wire; measured, not assumed.
- High-hop infrastructure. A high-placed router relaying high-hop packets can congest a whole region (firmware #3833). The comparison includes a high-placed relay topology to test whether this failure mode arises.
Each of these is a hypothesis. Savings are reported only as measured outcomes.
Extensions by transport capacity
After compatibility, services are added in the order the links can carry them. The link a message crosses decides which services reach it. Existing clients keep the Meshtastic API; each added service gets its own interface, outside Meshtastic compatibility.
| Link class | Services |
|---|---|
| LoRa (kbps range, preset dependent) | Meshtastic contract; sensors; small database reads and writes; AI queries with short answers |
| Mid-rate radio (900 MHz, HaLow) | Adds websites and voice |
| Wi-Fi, wired, Internet tunnels | Adds video. The Communicator has already run 160x120 at 24 fps with synchronised audio on about 121 Kbps |
Slow structured state stays in the RAM; perishable streams (voice, video) use the high-speed connection where the link supports it.
Adoption path
People join from what they already have, starting with the phone in their pocket. Radio reach is added to that, not required for it.
- Android phones. Install FrogNet on the phone and the phone is a participant: it joins the memory over the connections it already has, Wi-Fi, cellular and Internet tunnels, with no radio bought. The Meshtastic app on the phone keeps working against the node's client API, while FrogNet itself reads and writes the memory directly. The Communicator adds chat, voice and video on the same membership.
- Radio owners. Radio is another transport added to that. A board left on stock firmware stays a stock peer, reached through a FrogNet node's over-the-air edge; a board can be reflashed with the same web flasher or esptool flow people already use; a Linux node running meshtasticd adds the software and restarts. Their apps keep working against the client API, and unflashed neighbours keep working over the air.
- Mixed meshes. Nodes migrate one at a time. Each FrogNet node is also a gateway between the stock mesh and the rest of FrogNet.
- App and integration developers. They write against the protobufs, the CLI, MQTT and Home Assistant, and change nothing.
- Firmware module authors. They write C++ modules compiled into Meshtastic's image, so they port, unless the FrogNet firmware offers a compatible module surface.
Hardware and firmware (later phase)
An ESP32 runs only what is flashed, so the FrogNet node is its own firmware image, written on ESP-IDF (FreeRTOS, lwIP, C++), with the Ribbit C++ code compiled in and the SX1262 driven through RadioLib.
| Board tier | Examples | Memory | Role |
|---|---|---|---|
| ESP32-P4 | P4 modules with ESP32-C6 for Wi-Fi 6 / BLE 5.3 | 768 KB SRAM, 32 MB in-package PSRAM; H.264 encoder | Heavy node: RAM host, AI, video |
| ESP32-S3 with PSRAM | T-Beam Supreme, Station G2, Heltec V4 TFT, T-Watch-S3 | 8 MB PSRAM | FrogNet node on boards people already own |
| No PSRAM | Heltec V1–V3, T-Beam classic; RAK4631 (nRF52840) | About 60–90 KB free heap under stock Meshtastic | Stays stock, or acts as a modem behind a FrogNet node |
Development and test for this phase:
- Logic and scale. A native host build of the node code in FrogNet's simulator, the same way meshtasticd is Meshtastic's firmware built for Linux.
- Chip fidelity. Espressif's esp-emulator (S3, P4, C6 and others; Wi-Fi soft AP, BLE, Ethernet, configurable PSRAM). Wokwi covers S3 and P4 (beta), with PSRAM capped at 8 MB.
- Radio. No emulator covers the SX1262; the radio is replaced by the same link shim used in the comparison.
Build requirements and release gates
These are requirements of the working system, enforced at release, not preconditions for starting.
Build requirements
- Message retention. Chat messages are events, not latest-value state. Each has an identity and an owner and lives in its sender's family for as long as the family's declared retention holds it. A reader gets what the family holds while it is retained; a reader slower than the retention misses what aged out. Neither exactly-once nor at-least-once is promised. Each family's retention is set to hold at least what the incumbent holds for a client that reconnects.
- Acknowledgment. In a memory-based system an ack is nonsensical. A message is a write to the sender's own cells and a read by the destination; nothing travels, so there is nothing to acknowledge. A per-message ack would be a property of the message object, a one-time flag on every message that can never be cleared. The client API still expects an ACK packet, so the participant serving the client hands it one when the write lands, with the incumbent's fields (from = destination). It is a stub for the contract and asserts nothing about delivery. No acknowledgment passes between participants. A destination with no identity cell is a node this one does not know, and the client gets stock's answer for an unknown node at once.
- Full transport path. Everything FrogNet puts on the link rides the modeled channel: IP carriage, packetization, session setup, acknowledgments, retries, RAM access and recovery. The simulator's TCP supplies no reliability or bandwidth the radio lacks; verified in the shim.
- Interfaces. Existing clients keep the Meshtastic API unchanged; added services get their own interfaces.
Release gates for published measurements
- Source size reported in three parts on both sides: application implementation, FrogNet/Ribbit or Meshtastic core, and board support.
- Degradation thresholds fixed before any comparison run.
- Test-control traffic either crosses the measured channel for both systems or stays outside both.
Phases and deliverables
Phases 0 to 2 are the initial deliverable: the Linux implementation and the comparison. Phases 3 to 5 are additional deliverables and do not gate them. Each phase ends at a gate.
- Phase 0: Baseline (done). Build stock meshtasticd native; run the CLI tests, the firmware test harness and the RepeaterTastic vectors against it; map test coverage of the defined API and its gaps. Deliverable: the coverage map and stock baseline numbers. Gate: every oracle runs against stock.
- Phase 1: Mechanical conversion (done). Scaffolding to learn the incumbent, not the product: stock firmware with its plumbing moved onto the memory one step at a time, the incumbent's suites run after each. Deliverable: the steps green, a one-machine side by side with stock under good and bad data, and the register of decisions. Gate: every incumbent suite green at every step.
- Phase 2: The library and the side-by-side. The library built between the contract edges from the truth table, one truth or participant at a time; then the comparison with the shared link shim from Meshtasticator, multi-machine on FrogNet's simulator. Deliverable: a FrogNet node implementing the defined API, and the metrics table filled for both plus the load surface. Gate: all oracles pass with gaps resolved against stock, and the runs reproduce from the published package.
- Phase 3: Over-the-air edge. Meshtastic on the air to stock neighbours; identity and keys carried across reflash. Deliverable: mixed-mesh runs with stock and FrogNet nodes. Gate: stock nodes see FrogNet nodes as Meshtastic peers.
- Phase 4: Extensions. Services by link class. Deliverable: each service working in the system.
- Phase 5: Boards. ESP-IDF firmware for S3 and P4. Deliverable: flashable images for the community's own boards.
Open decisions
- What subset of FrogNet defines a standalone node on a board (sizes Phase 5 against 8 MB vs 32 MB PSRAM).
- Whether the incumbent's RF-topology assertions (traceroute route through a relay, hopLimit=0 reach) are kept by deriving hops from adjacency cells the link model writes, or declared outside the contract.
- Whether Meshtasticator (last pushed February 2025) still drives firmware 2.8.
- Whether the FrogNet firmware offers a compatible surface for existing Meshtastic modules.
- How identity moves from stock firmware in Phase 5: a full-flash erase wipes NVS, so keys and channel PSKs either come across through the client API's admin messages after the reflash, or an import reads the stock NVS layout before it is erased.
Sources
- Meshtasticator and Meshtastic docs: Meshtasticator
- Meshtastic docs: interactive simulator
- Meshtastic docs: discrete-event simulator
- Meshtastic docs: Linux native
- meshtastic.test (Python CLI)
- meshtastic-mcp
- RepeaterTastic releases
- Meshtastic firmware PR #10316
- Meshtastic firmware discussion #3833
- Meshtastic blog: switch from LongFast
- MeshRoute summary
- Espressif esp-emulator releases
- Wokwi ESP32 guide
- Waveshare ESP32-P4 module
- LILYGO T-Beam Supreme