# Modulation Jam Operator quickstart for the LoRa modulation jam test framework. Protocol/spec lives in [reference/concept.md](reference/concept.md). > **Authorship & copyright.** After [7424416b58dbbcdb490a099695cd46ecfe44d5fb](https://source.catbriar.dev/yaymesh/modjam/commit/7424416b58dbbcdb490a099695cd46ecfe44d5fb), this repository was produced primarily by a large language model (Claude). LLM-generated output is generally not eligible for copyright in the US and similar jurisdictions, so this code is in the public domain. To the extent any part is copyrightable, it is dedicated to the public domain via [CC0 1.0](LICENSE). ## 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).