Skip to content

#305 - extract calypso-sim into a standalone MQTT simulator crate#326

Draft
bracyw wants to merge 10 commits into
Developfrom
305-calypso-sim
Draft

#305 - extract calypso-sim into a standalone MQTT simulator crate#326
bracyw wants to merge 10 commits into
Developfrom
305-calypso-sim

Conversation

@bracyw

@bracyw bracyw commented Jun 28, 2026

Copy link
Copy Markdown
Collaborator

Changes

Pulls the MQTT simulator out of the main calypso binary into its own standalone calypso-sim crate, and deletes the old in-tree src/bin/simulate.rs. The new crate has four input modes — autonomous heartbeat, interactive keymap, scripted replay, and a JSON-RPC stream driver — arbitrated by a per-topic ownership model so the heartbeat doesn't fight a live driver. Also drops the now-unused crossterm dep from the main crate and guards SimValue::initialize against degenerate spec entries (empty min==max range / empty discrete options) so a malformed spec can't panic at startup.

Notes

  • calypso-sim is its own workspace (detached Cargo.lock + target dir), so it does not build from a top-level cargo build --allcd calypso-sim first. See calypso-sim/README.md for all flags, the keymap format, and the stream protocol.
  • calypso-sim/src/{simulatable_message,data}.rs are intentional vendored copies of the main crate's files (the sim crate can't depend on calypso). One small documented sim-local divergence: topic_values_inject compiles its {} placeholder regex once instead of per call.
  • Heads-up for local checks: the main calypso crate doesn't compile on macOS (it uses socketcan in non-cfg-gated lib code — pre-existing, identical on Develop), so root-workspace clippy/test only pass on Linux/CI. The changed code here is the calypso-sim crate, which passes fmt / clippy -D warnings / test locally.
  • Non-blocking follow-ups for later: reconcile a keymap.rs doc-vs-code nit on unit precedence for known topics, and (upstream, then re-vendor) the remaining simulatable_message.rs micro-perf items (rand::rng() reuse).

Test Cases

All assume a broker running (mosquitto) and a viewer (mqttui). From calypso-sim/:

  • cargo run -- --list-topics — prints every simulatable topic + unit and exits 0.
  • cargo run — autonomous heartbeat; mqttui shows live-looking values updating for every topic that has a sim_freq in the spec. Topics without one are listed once at startup as Warning topics (not simulated): ....
  • cargo run -- --enable-topic '^BMS/' — heartbeat publishes only BMS/... topics; --disable-topic '^BMS/' does the inverse (the two are mutually exclusive).
  • cargo run -- --key-map manual_sim_keymap.example.json — interactive raw mode; a mapped keypress publishes its topic (random / pinned / increment / sequence per the entry), logging [k] Topic = [..] unit. Ctrl+C exits cleanly.
  • cargo run -- --key-map keys.json --script play.txt — replays the script lines (single chars fire keys, sleep N waits), then exits.
  • cargo run -- --key-map keys.json --auto — interactive plus background heartbeat; claimed keymap topics override the heartbeat and release back to it afterward.
  • cargo run -- --stream then on stdin: {"jsonrpc":"2.0","id":1,"method":"publish","params":{"topic":"Wheel/Buttons/button_id","value":5}} → one-line response {"...","result":{"ts_us":...}} and the message lands on the broker. claim / release / silence / status / list_topics / ping behave per the README table.

Checklist

  • No merge conflicts
  • All checks passing
  • Remove any non-applicable sections of this template
  • Assign the PR to yourself
  • Request reviewers & ping on Slack
  • PR is linked to the ticket (fill in the closes line below)

Closes #305

bracyw added 10 commits April 26, 2026 15:35
…ous modes

Replace `simulate` with a single `calypso-sim` binary organized as a
folder of focused modules under `src/bin/calypso-sim/`.

Modes (can run together):
- Autonomous heartbeat (`--auto`, default when no other mode is chosen)
- Interactive keymap (`--key-map`)
- Scripted keymap replay (`--script` with `--key-map`)
- JSON-RPC 2.0 over stdio (`--stream`, for an agent)

Per-topic ownership (`auto` / `stream` / `silenced`) lets stream and
keymap modes claim/release/silence topics; the autonomous loop checks
ownership before each publish.

Stream protocol: NDJSON over stdin/stdout, methods `publish`, `claim`,
`release`, `silence`, `status`, `list_topics`, `ping`. Tracing on stderr
so stdout stays clean for JSON-RPC.

Other:
- Startup warning lists CAN topics with no `sim_freq`
- README documents all four modes, keymap formats, and the stream protocol
- Keymap supports Random / Pinned / Increment / Sequence per key, with
  optional `desc` per entry
Adds calypso-sim/scripts/: vcu_mimic.py (drives the sim's stream mode to
emulate the VCU), the generated serverdata_pb2.py, and a README.
# Conflicts:
#	Cargo.toml
#	Odyssey-Definitions
#	src/bin/simulate.rs
Add `"unit": ""` to the 9 Wheel/Buttons/button_id entries so they publish
without relying on a `sim` block in Odyssey-Definitions. We dropped the
local-only OD sim-block commit and point the submodule at Develop's pointer,
where button_id is not auto-simulatable; explicit units keep these keymap
entries working. button_id is the only non-simulatable topic in this keymap
(home_mode/nero_index/not_in_reverse remain simulatable via Develop's OD).
- autonomous: validate --enable/--disable-topic regexes up front so a bad
  pattern exits non-zero instead of silently disabling the heartbeat
- script: fail-fast on malformed sleep / multi-char / unknown-key lines
  (deterministic replay shouldn't silently skip)
- stream: async stdout so a stalled consumer can't block a runtime worker
- build.rs: rerun-if-changed=Odyssey-Definitions so spec edits rebuild sim data
- simulatable_message: guard SimValue::initialize against empty range/options
  to avoid a startup panic on a degenerate spec
- drop set_topic_alias_max(600) to match the decoder and prior simulator
- fix S5 README description (REVERSE is gateless) and vcu_mimic cleanup branch
- remove Embedded-Base gitlink (not on Develop, no .gitmodules entry, unreferenced)
…st degenerate spec

- Remove `crossterm` from the root Cargo.toml (zero refs in src/ or libs/;
  only the separate calypso-sim crate uses it, via its own manifest) and drop
  its now-orphaned transitive crates from Cargo.lock — pure removal, no other
  version changes.
- Guard `SimValue::initialize` in the main crate against degenerate spec
  entries (empty min==max range, empty discrete options) so a malformed CAN
  spec can't panic at startup, bringing it to parity with the already-hardened
  calypso-sim copy.
- Remove the now-stale "Divergence from upstream" note from the vendored
  calypso-sim copy, since upstream now carries the same guards.
…compile placeholder regex once

Centralize the per-topic publish/ownership policy that was re-derived inline at
four call sites across three mode files: add TopicRegistry::auto_may_publish
(the heartbeat publishes only while a topic is still Auto-owned) and
driver_may_publish (stream/keymap drivers publish unless a topic is Silenced),
co-located with the Owner enum, and route autonomous/keymap/stream through them.
Behavior-preserving.

In the vendored simulatable_message.rs, hoist the `{}` placeholder Regex in
topic_values_inject into a compile-once LazyLock and drop the per-call
intermediate Vec - a documented sim-local divergence from the upstream copy,
which still recompiles per call.
These per-repo Claude Code command/skill files were force-added in fdab310
despite `.claude` already being in .gitignore (since 7ac8de7). Untrack them
with `git rm --cached` so the ignore rule takes effect — the files stay on
disk locally and remain in history at fdab310; this only drops them from the
branch tip going forward.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[SIM] - Unified calypso-sim binary with JSON-RPC streaming control plane

1 participant