Files
meshtastic/scripts/meshtasticator_e2e
eric a4abb5d15d feat(e2e): Meshtasticator radio-level validation harness for the adapter
Adds a skip-gated end-to-end suite (plugin/tests/e2e) that drives the
adapter against live Meshtasticator simulated nodes - real MeshtasticD
instances speaking the actual TCP/protobuf API - covering the layer no
contract test can reach: TCP handshake, inbound text over the simulated
mesh, and chunked outbound sends arriving at a peer node.

scripts/meshtasticator_e2e/run_e2e.sh boots N nodes (Docker or Linux
native MeshtasticD), discovers their TCP API ports, and runs the suite.
Radio-level reconnect and CI wiring are documented as open items pending
a Linux/Docker validation run.
2026-09-07 21:06:01 -07:00
..

Meshtasticator e2e — radio-level validation for hermes-meshtastic

This directory is the radio-level counterpart to the contract suite (plugin/tests/contract). The contract suite guarantees the adapter matches the Hermes gateway API; it cannot prove the adapter actually talks to a Meshtastic node. These e2e tests boot the Meshtasticator interactive simulator — which runs the real MeshtasticD device software per node with a simulated LoRa PHY — and drive the adapter against a live simulated node.

Layer Validated by
Adapter ↔ Hermes gateway API plugin/tests/contract (no deployment)
Adapter ↔ real MeshtasticD TCP/protobuf session this suite
Inbound text across the (simulated) LoRa mesh this suite
Outbound chunked sends arriving at another node this suite
True RF (silicon LoRa, real propagation, duty cycle) physical radio only

Layout

scripts/meshtasticator_e2e/
  run_e2e.sh                  orchestrator: boots N nodes, runs pytest, tears down
  README.md                   this file
plugin/tests/e2e/
  test_meshtasticator_e2e.py  the e2e assertions (skip-gated)

Prerequisites

  • uv + Python 3.13.
  • A contract venv with the real gateway and the plugin (with deps) installed:
    scripts/contract-test.sh                 # creates plugin/.venv-contract (hermes-agent pinned)
    uv pip install --python plugin/.venv-contract/bin/python -e plugin
    
    (-e plugin installs the meshtastic client library the tests also use.)
  • A clone of Meshtasticator: git clone https://github.com/meshtastic/meshtasticator
  • Runtime for the simulator — pick one:
    • Docker (simplest, works on macOS/Windows): the simulator builds MeshtasticD from a container image on first run (slow once).
    • Linux native: build MeshtasticD's native PlatformIO target and pass the build dir with --program.

Run

# Docker mode (default)
MESHTASTICATOR_DIR=/path/to/meshtasticator \
  scripts/meshtasticator_e2e/run_e2e.sh

# Linux native mode
MESHTASTICATOR_DIR=/path/to/meshtasticator \
  scripts/meshtasticator_e2e/run_e2e.sh \
    --mode native --program /path/to/firmware/.pio/build/native

# Tune the scenario
  ... --nodes 3 --channel-index 0 --host 127.0.0.1

The orchestrator boots the simulator, waits until N node TCP API ports accept connections (Meshtasticator assigns node n → port 4404 + n), then runs pytest plugin/tests/e2e with MESHTASTICATOR_E2E=1. Simulator output lands in meshtasticator-sim.log at the repo root.

Without MESHTASTICATOR_E2E=1 the e2e module skips, so ordinary unit/CI runs stay fast and gateway-free.

What the tests do

  1. Inbound: peer node 1 broadcasts text; the adapter (connected to node 0) must receive it as a real gateway MessageEvent (host/port/channel_index come from E2E_PORTS/E2E_HOST/E2E_CHANNEL_INDEX).
  2. Outbound: the adapter sends a >180-char message; it must be chunked and the final chunk ((i/n) … word59) must arrive at peer node 1.

A _ProbeAdapter subclass records inbound events instead of dispatching to a Hermes agent handler (no agent runs in these tests; dispatch is already covered by the contract suite).

Status / open items (v1 scaffold)

This is an initial, structurally validated scaffold. A full green run still needs to happen on a Linux/Docker host with a MeshtasticD build, and these items are expected to need iteration there:

  • Node adjacency: the simulator's default random placement may put nodes out of range. For deterministic tests, seed close-together coordinates (the sim supports --from-file with an out/nodeConfig.yaml).
  • Channel semantics: tests default to channel index 0 (stock primary channel). Mirroring production (channel 1 / private channel) requires configuring matching channels on the simulated nodes.
  • Reconnect flow: killing/restarting a single simulated node needs per-node lifecycle control (native mode subprocesses; docker mode currently runs all nodes in one container), so the adapter's watchdog reconnect is not yet exercised here. See the watchdog unit coverage in the adapter for now.
  • CI wiring: an e2e job needs a docker-enabled runner and tolerates a long first-run firmware build — deliberately left out of this PR.