# mesh-control Operator quickstart for the LoRa modulation jam test framework. Protocol/spec lives in [reference/concept.md](reference/concept.md). ## Components - `modjam-station` — runs on each Test Station (Raspberry Pi or simulator container) talking to a MeshCore node over USB serial. - `modjam-control` — interactive REPL on the macbook (or simulator container) that sends START/STOP commands. - Simulator — `docker compose` with three test stations (A, B, C) and one control container, swapping the radio for UDP. ## Install (host: Pi or Mac) ```sh python3.11 -m venv .venv && source .venv/bin/activate pip install -e . ``` Drop a config at `~/modjam-config.json`. Use [sim/station-a.json](sim/station-a.json) as a template; add `"port": "/dev/ttyUSB0"` (Pi) or `"/dev/tty.usbmodem1301"` (Mac) for the attached MeshCore node. Run: ```sh modjam-station # on each Pi modjam-control # on the Mac ``` ## Simulator Three stations + control, no hardware: ```sh docker compose build docker compose up -d station-a station-b station-c docker compose run --rm control ``` Inside the control REPL: ``` > start name=sim1 stations=A,B,C bw=500 sf=8 cr=5 pow=22 duration=20 padding=10 spacing=2 size=40 at=1 > stop > quit ``` TSV logs appear in `./sim/logs/` (one per station per run, format matches [reference/A.tsv](reference/A.tsv) / [reference/B.tsv](reference/B.tsv)). `SIMULATOR=true` is what flips the radio backend from MeshCore-USB to UDP. The UDP backend drops cross-config datagrams, so receivers only "hear" senders whose freq/bw/sf/cr/channel match — same behavior as real radios. The simulator also drops a fraction of received **test packets** at random to mimic real-world packet loss. Default is 15%; override with `SIM_PACKET_LOSS=0.0` (no loss) up to `1.0`. Protocol traffic (START/STOP, heartbeats, next/done) is never dropped — only payloads matching the test-packet pattern. Each station gets a deterministic per-station RNG seed so reruns are repeatable; set `SIM_SEED=...` to vary. ```sh SIM_PACKET_LOSS=0.3 docker compose up -d station-a station-b station-c ``` ## REPL commands | Command | Effect | |---|---| | `start [k=v ...]` | encode and broadcast a START command (see [reference/concept.md](reference/concept.md) for keys: `name`, `f`, `bw`, `sf`, `cr`, `pow`, `size`, `stations`, `duration`, `padding`, `spacing`, `at`) | | `stop` | multicast STOP — running stations return to IDLE | | `help` | show command list | | `quit` | exit | ## Layout ``` modjam/ ├── cli.py # entrypoints ├── config.py # ~/modjam-config.json loader ├── protocol.py # `[:]|k:v,…` codec + heartbeat/next/done formatters ├── cuesheet.py # build cuesheet from START params ├── station.py # IDLE/RUNNING state machine ├── control.py # REPL + rx tail ├── log.py # TSV logger └── radio/ ├── base.py # Radio ABC ├── meshcore.py # MeshCore over USB serial ├── udp.py # simulator radio └── factory.py # picks backend from SIMULATOR env ``` ## Tests ```sh pip install -e '.[test]' pytest ``` CI runs the same suite via [.forgejo/workflows/test.yml](.forgejo/workflows/test.yml) on push to `main` and on PRs. ## Scope v1: IDLE + RUNNING only. RESULTS / DOWNLINKING is not implemented yet (see [reference/concept.md](reference/concept.md) and [notes.md](notes.md) for the protocol).