No description
Find a file
Alec Perkins 33bf287c30
All checks were successful
tests / pytest (push) Successful in 1m24s
Wait for tx-complete + add per-packet correlation token
Two related changes to make sender/receiver logs analyzable:

1. MeshCoreRadio.send no longer returns until the radio firmware
   reports the packet was actually transmitted. send_chan_msg only
   waits for EventType.OK (firmware accepted bytes) — no
   tx-complete event is pushed for channel messages, so we poll
   commands.get_stats_packets() and wait for nb_sent to strictly
   increment past a pre-send baseline. Times out after 30s.

2. Each test packet now embeds a random 8-hex-char token as the
   4th comma-separated field of the prefix, distinct from any
   radio-assigned packet id. Receivers extract it from the text
   and write it on every TSV row, so a sender's queued/sent rows
   tie 1:1 to each receiver's record without depending on
   whatever packet id the radio surfaces on each side.

   TSV gains a new column (3rd, after packet_id):
     queued:   ts queued <id> <token> <freq,bw,sf,cr,pow>
     sent:     ts sent <id> <token> <duration_ms> <text>
     received: ts received <id> <token> <text>
   Missing token writes "-".

UDP simulator regex updated to accept both legacy 3-field test
payloads and the new 4-field format with token.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 22:07:32 -04:00
.forgejo/workflows CI fix + add meshcore source as submodules 2026-05-07 22:07:04 -04:00
docker Implement test/control stations, simulator, tests 2026-05-07 21:27:41 -04:00
modjam Wait for tx-complete + add per-packet correlation token 2026-05-07 22:07:32 -04:00
reference CI fix + add meshcore source as submodules 2026-05-07 22:07:04 -04:00
sim Implement test/control stations, simulator, tests 2026-05-07 21:27:41 -04:00
tests Wait for tx-complete + add per-packet correlation token 2026-05-07 22:07:32 -04:00
.gitignore Implement test/control stations, simulator, tests 2026-05-07 21:27:41 -04:00
.gitmodules CI fix + add meshcore source as submodules 2026-05-07 22:07:04 -04:00
docker-compose.yml Implement test/control stations, simulator, tests 2026-05-07 21:27:41 -04:00
LICENSE Dedicate to the public domain (CC0) with LLM-authorship notice 2026-05-07 21:32:28 -04:00
notes.md Implement test/control stations, simulator, tests 2026-05-07 21:27:41 -04:00
pyproject.toml Dedicate to the public domain (CC0) with LLM-authorship notice 2026-05-07 21:32:28 -04:00
README.md Dedicate to the public domain (CC0) with LLM-authorship notice 2026-05-07 21:32:28 -04:00
requirements.txt Reorg for mesh control-based version 2026-05-07 20:49:00 -04:00

Modulation Jam

Operator quickstart for the LoRa modulation jam test framework. Protocol/spec lives in reference/concept.md.

Authorship & copyright. After 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.

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)

python3.11 -m venv .venv && source .venv/bin/activate
pip install -e .

Drop a config at ~/modjam-config.json. Use sim/station-a.json as a template; add "port": "/dev/ttyUSB0" (Pi) or "/dev/tty.usbmodem1301" (Mac) for the attached MeshCore node.

Run:

modjam-station                # on each Pi
modjam-control                # on the Mac

Simulator

Three stations + control, no hardware:

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/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.

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 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       # `<cmd>[:<station>]|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

pip install -e '.[test]'
pytest

CI runs the same suite via .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 and notes.md for the protocol).