Files
potato-mesh/data/tools/README.md
T
TJ Paulson 1b934ee6f7 feat(ingestor): passive UDP transport (Mesh via UDP), primary-channel only (#838)
Adds an optional passive UDP transport (TRANSPORT=udp) that reads the node's
"Mesh via UDP" LAN multicast instead of holding the radio's single API/serial
slot, filters to the PRIMARY channel by channel-hash (fail-closed), decrypts
with the default PSK, and enriches payloads to match the API-path packet shape
so the collector receives identical records. Implemented as a MeshProtocol
provider; 100% unit coverage on new modules; real-capture fixtures included
(node IDs/GPS anonymized). See PR description for details.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 22:24:26 +02:00

3.3 KiB

data/tools/ — passive UDP operator & dev tools

Helpers for the passive UDP transport (TRANSPORT=udp, see the Passive UDP transport section of the README). Neither file is part of the ingestor runtime.

capture_udp_fixtures.py — capture real datagrams for testing

A receive-only diagnostic that joins the node's "Mesh via UDP" multicast group and writes each raw datagram to a JSONL file (base64 in raw_b64). Used to produce the real-traffic fixtures under ../../tests/fixtures/mesh_udp/.

# Prereq: "Mesh via UDP" enabled on the node
#   meshtastic --set network.enabled_protocols 1
# Run on a host on the node's LAN (host networking; multicast can't cross a NAT):
python data/tools/capture_udp_fixtures.py --out capture.jsonl --count 40
# Optional live decode summary of primary-channel packets:
python data/tools/capture_udp_fixtures.py --out capture.jsonl --primary-only

It never transmits and never connects to the radio API, so it is safe to run alongside a live ingestor or the phone app.

Coverage note: this is an operator-run diagnostic that needs a live LAN socket, so it is intentionally exempt from the ingestor package's 100%-unit-test gate. The runtime decode/crypto it exercises is fully covered by tests/test_meshtastic_udp_decode_unit.py against the captured fixtures.

compose.udp.pi.yml — Raspberry Pi (arm64) deployment

A Docker Compose file for running the ingestor in passive UDP mode on a Pi 5. It requires network_mode: host (multicast 224.0.0.69 cannot reach a bridged container) and reads the same .env as the standard deployment.

.env keys the UDP deployment reads

TRANSPORT=udp
PRIMARY_CHANNEL_ONLY=1
PRIMARY_CHANNEL_KEY=AQ==          # base64 primary PSK (Meshtastic default)
PRIMARY_CHANNEL_NAME=MediumFast   # REQUIRED: name of channel 0 (or the preset
                                  # name if blank on the radio); resolves the
                                  # channel hash. If unset, primary-only mode
                                  # drops ALL traffic (fail closed).
INGESTOR_NODE_ID=!xxxxxxxx        # host node id for the ingestor heartbeat
MESH_UDP_GROUP=224.0.0.69
MESH_UDP_PORT=4403
# plus the standard API_TOKEN / INSTANCE_DOMAIN

Build → ship → verify

The image is built natively on an arm64 Pi (no QEMU) and copied to the target:

# 1. On a build Pi, from the source tree:
docker build -f data/Dockerfile -t potato-mesh-ingestor:udp .
docker save potato-mesh-ingestor:udp | gzip > potato-mesh-ingestor-udp.tar.gz

# 2. Copy the image tarball to the target Pi, then:
docker load < potato-mesh-ingestor-udp.tar.gz
cp data/tools/compose.udp.pi.yml compose.yml   # first time only; add .env keys above
docker compose up -d

# 3. Verify: the startup log pins the channel, and no secondary names appear.
docker compose logs ingestor | grep "UDP primary-channel filter"   # primary_channel_hash=<n>, severity=info
docker compose logs ingestor | grep "POST request failed"          # (expect nothing)

A primary_channel_hash of null / severity=warn means PRIMARY_CHANNEL_NAME is unset and the ingestor is dropping everything (fail closed).