Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/configure.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ The session-protocol version is the suffix on the prompt - `DAPPSv1>` today; `DA
- **Forward-compatible additions stay on the current version.** New optional `ihave` headers and new commands ride the existing prompt; receivers ignore unknown headers and respond `?` to unrecognised commands.
- **Bump the prompt on any incompatible wire change.** A clean version cut beats sticking a patch on `v1` and hoping every implementation interprets it the same way.
- **Newer implementations should speak older versions** for one-way compatibility.
- **The UDP / future MeshCore datagram bearer codec versions independently** - different format, different schedule.
- **The UDP / MeshCore datagram bearer codec versions independently** - different format, different schedule. (MeshCore also versions its shared compression dictionary on the wire, so a dictionary change can't silently corrupt a mixed-version fleet.)

**Pre-shipping caveat**: while DAPPS has no non-author operators on the air, breaking changes to either format are still fair game without a version bump. The cost of compatibility tape that nobody benefits from is real. The policy fully kicks in when the first independent operator picks DAPPS up.

Expand Down
4 changes: 2 additions & 2 deletions docs/connect/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ Today that interface is **AGW** - the long-standing host-side TCP protocol that
| BPQ AGW | Production | [BPQ (AGW)](bpq.md) |
| XRouter (RHPv2) | Production - RHPv2 is required for DAPPS-on-XRouter; XRouter AGW does not work as a DAPPS bearer | [XRouter (RHPv2)](xrouter.md) |
| Other AGW host| Likely works | [BPQ (AGW)](bpq.md) covers the protocol-shaped bits; only the config-file specifics differ |
| MeshCore Companion | Planned | [MeshCore](meshcore.md) |
| MeshCore Companion (USB) | Available - off by default (`MeshCoreEnabled=true`) | [MeshCore](meshcore.md) |
| MeshCore KISS | Planned | [MeshCore](meshcore.md) |
| RHPv2 (other hosts) | Will work as soon as another host ships RHPv2 | [RHPv2](rhp.md) |
| UDP datagram | Test stand-in | Not for production; used as the architectural placeholder for what MeshCore will become. |
| UDP datagram | Test stand-in | Not for production; the architectural placeholder that proved out the datagram-bearer seam MeshCore now uses. |

## Why bearer-agnostic matters

Expand Down
126 changes: 88 additions & 38 deletions docs/connect/meshcore.md
Original file line number Diff line number Diff line change
@@ -1,60 +1,110 @@
# Connect via MeshCore

**Status: planned.** MeshCore as a backhaul bearer for DAPPS is on the roadmap but not yet implemented in shipping code. This page exists so you can see the shape of the integration and plan around it.
**Status: shipped (Companion-over-USB).** MeshCore is a first-class DAPPS backhaul bearer:
a node can carry `BackhaulMessage`s over a MeshCore radio's private channel alongside (or
instead of) AGW/AX.25. It's **off by default** — set `MeshCoreEnabled=true` to turn it on.
The KISS-driven flavour (below) is still future work.

## Why MeshCore

MeshCore is a small, modern mesh radio firmware (LoRa-shaped today) with a built-in routing layer that's a much better fit for slow, lossy, mostly-unreliable RF than AX.25. As a bearer for DAPPS it gives:

- A datagram interface (no AX.25 connection setup, no T1/T2/T3 timer dance) - better for short, frequent messages.
- A working hop-by-hop mesh underneath, so DAPPS doesn't have to solve "how do I reach a node three hops away" the way it does for the broadcast bearer of AX.25.
- A real low-cost long-haul story for operators without HF - LoRa NVIS-style propagation is well-documented at this point.

The DAPPS architecture has already been refactored to support a non-stream bearer - the existing UDP-datagram backhaul (`UdpDatagramBackhaul`) is the test stand-in proving the bearer-agnostic layer works. MeshCore lands in the same shape: a small adapter that emits and ingests `BackhaulMessage` units over the radio.

## What integration will look like

Two flavours, expected as separate packages:

### MeshCore Companion

Talks to MeshCore via the **Companion API** - a USB / BLE / Wi-Fi link to a Companion-mode radio, where the radio handles the mesh and exposes a friendly API for an attached host. Best for operators who want a self-contained DAPPS-on-MeshCore stack without dealing with KISS at all.

Configuration sketch (subject to change before release):
MeshCore is a small, modern mesh radio firmware (LoRa-shaped today) with a built-in routing
layer that's a much better fit for slow, lossy, mostly-unreliable RF than AX.25. As a bearer
for DAPPS it gives:

- A datagram interface (no AX.25 connection setup, no T1/T2/T3 timer dance) — better for
short, frequent messages.
- A working hop-by-hop mesh underneath, so DAPPS doesn't have to solve "how do I reach a node
three hops away" itself.
- A real low-cost long-haul story for operators without HF.

## What's implemented (H1 — Companion over USB)

DAPPS talks to a Heltec-class radio running MeshCore **Companion (USB)** firmware (tested on
v1.16.0) over the serial Companion protocol, using the **binary channel-data** path. The bearer
lives in `dapps.meshcore` and is wired in behind the standard bearer seam; see
[`src/dapps/dapps.meshcore/README.md`](https://github.com/packet-net/dapps/blob/master/src/dapps/dapps.meshcore/README.md)
for the deep detail. Feature set:

- **Binary transport + heavy compression** — messages are encoded with the real DAPPS codec,
zstd-compressed against a **versioned shared dictionary** (~3× goodput, usually one LoRa
packet/message), fragmented to the ~165 B payload, and sent as channel-data floods.
- **Good-citizen controls** — a hard trailing-hour **airtime governor** plus **adaptive**
listen-before-talk and congestion backoff (from overheard-packet occupancy), so DAPPS rides a
shared preset politely.
- **End-to-end reliability** — the channel is a fire-and-forget flood, so DAPPS adds its own
datagram-style ACK + resend with **idempotent** (dedup-by-id) delivery. Not a session protocol.
- **Passive discovery** — a node auto-learns peers it hears over MeshCore and routes to them with
no manual neighbour config.
- **Watchdog + recovery** — detects a hung radio and hard-resets it over CP2102 DTR/RTS, then
re-applies config.
- **Device control** — region presets, TX power (region-capped), channel name + PSK, node name,
all from the dashboard or config; live status + a Reset-radio button.

## Enabling it

Configure via the dashboard **Settings** page, the `systemoptions` table, or `DAPPS_MESH_CORE_*`
environment variables (note the underscore in `MESH_CORE`). The essentials:

```
DAPPS_MESHCORE_COMPANION_ENABLED=true
DAPPS_MESHCORE_COMPANION_TRANSPORT=usb # or ble, wifi
DAPPS_MESHCORE_COMPANION_DEVICE=/dev/ttyUSB0 # for usb
DAPPS_MESH_CORE_ENABLED=true
DAPPS_MESH_CORE_PORT=/dev/ttyUSB0
DAPPS_MESH_CORE_REGION=uk-test # preset: uk-narrow | uk-test | eu-legacy | custom
DAPPS_MESH_CORE_CHANNEL_NAME=dapps
DAPPS_MESH_CORE_CHANNEL_PSK=<32-char hex, or a passphrase>
DAPPS_MESH_CORE_TX_POWER_DBM=8 # capped by the region's regulatory max
```

DAPPS would auto-register a MeshCore "address" on the radio for inbound dispatch (analogous to AGW's callsign registration today) and use the Companion API for both directions.
Other options (all optional, sensible defaults): `NODE_NAME`, `CHANNEL_INDEX`,
`AIRTIME_BUDGET_SECONDS_PER_HOUR`, `COMPRESS`, `CONGESTION_BACKOFF_FRACTION`, `LBT_GUARD_MS`,
`RELIABLE_DELIVERY`, plus the deployment-model knobs below. The full table is in the
[library README](https://github.com/packet-net/dapps/blob/master/src/dapps/dapps.meshcore/README.md).
Runtime status/control: `GET /MeshCore/status`, `POST /MeshCore/reset`.

### MeshCore KISS
## Deployment models — privacy vs containment

For operators who already have a radio in **KISS-TNC mode** and want DAPPS to drive it directly. This is the closer analogue of the BPQ AGW path - DAPPS opens a TCP/serial connection to the KISS endpoint and emits MeshCore frames itself, including doing its own retries.
A MeshCore private channel gives **privacy** (PSK) but **not containment**: channel messages
flood *unscoped* by default and any same-preset Repeater relays them network-wide *without the
PSK*. Pick a model per node (details + firmware caveats in the library README's "Containment"):

```
DAPPS_MESHCORE_KISS_ENABLED=true
DAPPS_MESHCORE_KISS_HOST=127.0.0.1
DAPPS_MESHCORE_KISS_PORT=8001
```
| Model | Config | What you get |
|---|---|---|
| **A** unscoped public preset | `REGION=uk-narrow`, no scope | free public-repeater carriage everywhere — relies on the good-citizen controls; light traffic only |
| **B** scoped public preset | `REGION=uk-narrow` + `FLOOD_SCOPE_KEY=<name>` | your floods are dropped by repeaters that don't share the scope; needs your own scoped repeaters to carry between DAPPS nodes |
| **C** dedicated preset | `REGION=custom` + `CUSTOM_PRESET=freq=868.4;bw=62.5;sf=8;cr=8;pwr=14` | total physical isolation on your own frequency/SF — least config risk |

## What stays the same

- The DAPPSv1 application layer is unchanged. An app subscribing to `dapps/in/<app>` doesn't know or care whether the underlying bearer is AGW, MeshCore Companion, or MeshCore KISS.
- The neighbour table holds MeshCore peers alongside AGW peers - same row shape, just a different bearer hint.
- The discovery / routing layer treats MeshCore links as another link-class with cost hints; the routing algorithm picks per destination.
- The dashboard, REST, MQTT, MCP - unchanged.
- The DAPPSv1 application layer is unchanged — an app doesn't know or care whether the bearer is
AGW, UDP, or MeshCore.
- Discovered/neighbour peers hold MeshCore reachability alongside other bearers (a
`MeshCoreChannel` hint on the route); the routing resolver picks per destination by cost.
- Dashboard, REST, MQTT, MCP — unchanged.

## Routing implications

MeshCore doing its own mesh routing under DAPPS is interesting. It means DAPPS's hop-count model and MeshCore's hop-count model are stacked: a DAPPS message that's "one hop" from DAPPS's perspective might traverse three MeshCore hops underneath. DAPPS uses cost hints rather than raw hop counts, so this works out, but it's worth understanding when you're tuning a multi-bearer setup.
MeshCore does its own mesh routing under DAPPS, so the two hop-count models stack: a message
that's "one hop" to DAPPS may traverse several MeshCore hops underneath. DAPPS routes by **cost
hints** rather than raw hop counts (MeshCore is a mid-cost RF class), so this works out; it's
worth understanding when tuning a multi-bearer setup. Background:
[docs/meshcore-backhaul-routing.md](https://github.com/packet-net/dapps/blob/master/docs/meshcore-backhaul-routing.md).

## How it's been validated

A separate design note covering the MeshCore-as-a-bearer trade-offs is in [docs/meshcore-backhaul-routing.md in the repo](https://github.com/packet-net/dapps/blob/master/docs/meshcore-backhaul-routing.md). Most of it is still relevant; some of the concrete API sketches will be revised as the integration lands.
- **On air**, two Heltec V3s: bidirectional exchange, watchdog reset+recovery, the airtime
governor and adaptive backoff, reliability recovering ~40 % induced loss with no duplicate
delivery, passive discovery, and both the custom preset and the flood-scope key being accepted
by real firmware.
- **In simulation** (`dapps.meshcore.sim`), the real bearer runs over an in-process multi-hop mesh
the two bench radios can't reach: multi-hop flood + dedup-across-paths, flood-scope containment,
and reliability recovering every message over four hops at 30–40 % per-edge loss (delivered
exactly once).
- **Not yet validated on hardware:** a repeater actually *dropping* an out-of-scope flood — that
needs a physical Repeater node + attenuators, which is the next hardware step.

## When?
## Future

Currently blocked on hardware availability for testing - we want a real two-radio setup in the loop before declaring it shippable rather than relying on emulation.
- **MeshCore KISS (H2)** — for operators with a radio already in KISS-TNC mode, DAPPS driving
MeshCore frames itself. Planned, not yet implemented.
- **Firmware flash from DAPPS** — managing/upgrading radio firmware from the node.

If you've got a MeshCore radio and want to be a tester, [open an issue](https://github.com/packet-net/dapps/issues).
If you've got a MeshCore radio and want to test, [open an issue](https://github.com/packet-net/dapps/issues).
4 changes: 2 additions & 2 deletions docs/glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Terms used throughout this manual.

**backhaul** - The bearer-side communication between two DAPPS nodes (as distinct from the app interface, which is local). AGW and RHPv2 today; MeshCore in flight.

**bearer** - The underlying medium DAPPS hands off to: AGW (via BPQ or another AGW host), RHPv2 (via XRouter or another RHPv2 host), MeshCore Companion / KISS (planned), UDP datagram (test stand-in). The bearer-agnostic seam means apps and routing don't care which.
**bearer** - The underlying medium DAPPS hands off to: AGW (via BPQ or another AGW host), RHPv2 (via XRouter or another RHPv2 host), MeshCore Companion (USB, shipped; KISS planned), UDP datagram (test stand-in). The bearer-agnostic seam means apps and routing don't care which.

**beacon** - A small periodic transmission on a discovery channel advertising "I am here, I am callsign X, I'm reachable on this bearer at this cost." Stateless, no session.

Expand Down Expand Up @@ -62,7 +62,7 @@ Terms used throughout this manual.

**MCP** - Model Context Protocol. The endpoint at `/mcp` exposes operator-facing tools to AI assistants.

**MeshCore** - A modern small-mesh radio firmware (LoRa-shaped today). Planned DAPPS bearer; design supports it but no ship yet.
**MeshCore** - A modern small-mesh radio firmware (LoRa-shaped today). Shipped DAPPS bearer via the Companion-over-USB path (binary channel-data, compression, good-citizen controls, reliability, discovery, deployment models A/B/C); off by default. The KISS-driven flavour is still planned.

**MQTT** - A pub-sub messaging protocol; DAPPS embeds an MQTT broker for the local app interface.

Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ DAPPS does **not** require BPQ. The default setup guide is BPQ because that's wh

- **Protocol**: DAPPSv1 specified end-to-end. App authors want [the protocol reference](app-developers/reference.md); second-source implementers want [Implement DAPPS](implement.md).
- **Implementation**: in active development. The [versioning policy](configure.md#versioning) describes where breaking changes are still fair game versus where compatibility is preserved.
- **Bearers**: AGW (BPQ today, any AGW host in principle) and RHPv2 (XRouter today; mainline BPQ when it ships RHPv2) are production-quality. [MeshCore](connect/meshcore.md) Companion + KISS are planned, not yet shipped.
- **Bearers**: AGW (BPQ today, any AGW host in principle) and RHPv2 (XRouter today; mainline BPQ when it ships RHPv2) are production-quality. [MeshCore](connect/meshcore.md) Companion-over-USB has shipped (off by default; binary bearer with compression, good-citizen controls, reliability, discovery, and A/B/C deployment models); the MeshCore KISS flavour is still planned.

If you've never heard of DAPPS before, [start here](getting-started.md).
15 changes: 11 additions & 4 deletions src/dapps/dapps.meshcore/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,19 @@ evidence this is built on.
- **Broadcast semantics** — a private channel is one shared medium, so a message is broadcast once
and the addressee self-selects (the inbox `IsLocal` gate). Identical message ids offered for
multiple neighbours are coalesced within a window so we don't re-broadcast.
- **Passive discovery** (#27) — a node auto-records peers it hears over MeshCore as routable
`DbDiscoveredPeer`s (throttled, self-skipping), so outbound to them works with no manual neighbour.
- **Watchdog + recovery** (`MeshCoreLink`, #160) — opens and configures the radio, then detects a
hung/mute companion (idle liveness probe) and recovers it by hard-resetting the ESP32 over
CP2102 DTR/RTS, re-opening, and re-applying the radio/channel config. Bounded attempts + backoff;
link state surfaced (`Healthy`/`Resetting`/`Failed`).
- **Device control** — region presets (`Regions`: `uk-narrow`, `uk-test`, `eu-legacy`), TX power
(region-capped), channel name + PSK, node name.
CP2102 DTR/RTS, re-opening, and re-applying the radio/channel config (including the flood-scope,
which is RAM-only). Bounded attempts + backoff; link state surfaced (`Healthy`/`Resetting`/`Failed`).
- **Device control** — region presets (`Regions`: `uk-narrow`, `uk-test`, `eu-legacy`, or `custom`),
TX power (region-capped), channel name + PSK, node name.
- **Deployment models A/B/C** (#24) — first-class preset + flood-scope config to trade off public-
repeater carriage vs containment vs physical isolation. See **Containment** below.

For scale-testing this bearer over multi-hop topologies without radios, see the in-process mesh in
[`../dapps.meshcore.sim`](../dapps.meshcore.sim/README.md).

## Enabling it in a dapps node

Expand Down
Loading