Add topology-aware lighting with Rynk control - #987
Draft
colonelpanic8 wants to merge 31 commits into
Draft
Conversation
Replace the placeholder rmk protocol module with the Rynk wire format: command, endpoint, and topic tables, error and message types, layout and system payloads, plus wire snapshots. Add tsify-based TypeScript type generation for host reuse.
…[keymap.layer]] Separate the physical key map from the keymap: drop the legacy matrix_map/keymap path and its codegen, and add the macro codegen for the new schema.
… handler Route USB and BLE host access through one handler and replace the vial_lock feature with host_lock.
Includes the lock gate, device-info and layout-blob endpoints, and keymap bulk transfer across layer boundaries.
Generates the TypeScript type bindings consumed by the web demo.
… examples Add rp2040_rynk and qemu-riscv-rynk, adapt example sources, and regenerate Cargo.lock files.
Signed-off-by: Haobo Gu <haobogu@outlook.com>
Store each tap-hold's timing profile as a u8 index into a small, deduplicated `taphold_profiles` table rather than an inline 4-byte `MorseProfile`. `KeyAction::TapHold` shrinks from 12 to 7 bytes, which also drops the u32's 4-byte alignment padding across every KeyAction-sized RAM buffer (keymap array, held-key buffer, combos, forks, morse cache). Measured on the nrf52840 example at a 5x14x5 (350-key) board: .bss -2408 bytes, .text -4 bytes (no flash cost). Savings scale with board size; the table cost (CAP*4, default CAP=16 -> 64 B) is fixed. - keyboard.toml: named profiles are interned automatically by the proc-macro; users keep referencing them by name. - pure-Rust: default macros (th!/mt!/lt!/tt!) are unchanged; the custom-profile macros (thp!/mtp!/ltp!/ttp!) now take a table index and the user populates `behavior.morse.taphold_profiles`. - Table capacity is configurable via `[rmk] taphold_profile_max_num`. - Vial wire format is unchanged (profile isn't carried); the stored KeyAction encoding changes but auto-migrates via the build-hash guard. Regenerated the rmk_protocol keymap endpoint snapshots (KeyAction schema change propagates to keymap/combo/encoder/fork endpoints).
* refactor: redesign core rynk traits, achieve full-duplex communication Signed-off-by: Haobo Gu <haobogu@outlook.com> * refactor: update rynk crates to use the new contract Signed-off-by: Haobo Gu <haobogu@outlook.com> * chore(rynk): trim comments * refactor(rynk): third simplification pass - rynk-wasm: drop the label round-trip (JS already owns the picker name) and the WebDevice wrapper — JsByteLink is a local wasm-bindgen type, so it implements RynkDevice directly; device.rs deleted - qemu_behavior: drop six request_raw calls duplicating the typed calls right above them (request delegates to request_raw, same code path) - driver/tests: drop three tests subsumed by supersets (cmd mismatch, topic-during-request, cancelled-request recovery) - rynk-serial: declare the tokio feature actually used (io-util, not time) Signed-off-by: Haobo Gu <haobogu@outlook.com> --------- Signed-off-by: Haobo Gu <haobogu@outlook.com>
Signed-off-by: Haobo Gu <haobogu@outlook.com>
Rebasing onto origin/main linearized away the merge commit 9b8d64c, which carried hand-made changes not present in either parent: the nrf54lm20_ble example tweaks, the rynk_loopback fixture downsize, and the rynk/Cargo.lock removal. Restore them to match the pre-rebase tree.
Signed-off-by: Haobo Gu <haobogu@outlook.com>
- features/rynk.md: drop the non-existent `bulk` Cargo feature (the `features = ["bulk"]` snippet failed to compile); bulk is always on with `rynk`, sized by `rynk_buffer_size`. Note the tool-author example needs `embassy-futures`. - migration/v08_v09.md: fix the fabricated `bulk_transfer` -> `bulk` rename (removed, folded into `rynk`); document the `KeyAction::TapHold` u8-index break, the removed `[rmk].protocol_max_bulk_size` key, and the new unknown-key strictness. - configuration/layout.md: remove the `PDF(n)` action (unsupported in keyboard.toml — the keymap grammar has no rule for it), document the `Trns` transparent form, and split two examples that shared one toml fence. - rmk/CHANGELOG.md: add the Rynk feature entry and the BREAKING `keyboard.toml` layout-schema change.
A USB bulk-IN transfer completes on the host only once it sees a packet shorter than the max packet size. A device-to-host frame whose length is a multiple of that size (64 at Full-Speed) ends on a full packet, so the host read stays pending. Wrap the CDC Sender in a small Write adapter that appends a zero-length packet after such a frame. Scoped to the USB transport; run_session, UART, and BLE are untouched. This is an independent robustness fix, not the root cause of the nRF52840 "CDC IN never completes" report — there even the short handshake response and raw libusb hung, which a ZLP cannot explain. Signed-off-by: Haobo Gu <haobogu@outlook.com>
Add an opaque, bounded application payload that firmware can exchange between the split central and peripheral alongside the normal split traffic, without ever taking priority over key events. * `split_app` module: `SplitAppData` (a small, `MaxSize`, postcard length-prefixed payload) plus four statics — `SPLIT_APP_TX` (central -> peripheral), `SPLIT_APP_PERIPH_TX` (peripheral -> central), the symmetric `SPLIT_APP_RX` inbox, and the `SPLIT_APP_LINK` watch that reports split-link state to the application. * Producers use `try_send` only (bounded, drop-on-full) so the split read/write loops never block or get starved by application traffic; key events are always polled first. * The split driver and peripheral drain the application queues as the lowest-priority arm of their outgoing selects and forward received `SplitMessage::Application` payloads into the inbox. * `SPLIT_APP_LINK` is state-based (a `Watch`), so a late-subscribing application still observes the current link state; the `false -> true` edge is a resync trigger. Link-down edges are emitted from a drop guard so they survive async cancellation of the split session. * On the peripheral the link is raised on the FIRST inbound message from the central rather than on bare connection: over BLE, notifications to a central that has not yet subscribed are silently dropped, so the connection alone is not proof the application channel is usable. Developed for a split keyboard port.
colonelpanic8
marked this pull request as draft
July 20, 2026 22:48
Add the protocol types for runtime-configurable per-layer scenes: LightingSceneCell (layer + stable LED id + effect), LightingLayerPolicy, a revision-pinned scenes page, single-cell set/unset requests, an atomic Begin/Put/Commit/Abort replacement transaction, and SetLightingLayerPolicy. Discovery deliberately leaves LightingCapabilities and LightingState byte-identical: postcard is positional, so appending fields would break new-host/old-firmware decode. Scenes are advertised through a new LAYER_SCENES bit in the existing LightingFeatureFlags plus a dedicated GetLightingSceneStatus endpoint carrying capacity, occupancy, and policy. New LightingError variants (UnknownLayer, SceneFull) are appended, so existing encodings are unchanged. Protocol version bumps to v0.3. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Give the standard engine an owned, fixed-capacity SceneTable (new SCENE_CAP const generic, default 0) holding per-layer, per-slot effects with the same EffectiveOnly/ActiveStack composition semantics as the static LayerScenes. The table composes in the layer band immediately after the board's static scenes — a runtime cell overrides a static default for the same slot — while the TTL overlay stays highest-priority. Scene mutations are revision-checked commands; whole-table replacement stages inside the engine via bounded chunks (Begin/Put/Commit/Abort) with inactivity expiry and an idempotent commit. Staging engine-side instead of reusing the host-side overlay staging keeps kilobyte-sized scene batches off the bounded mailbox channels, whose payloads are copied by value. Scene reads and transaction reservation need non-state readback, so the engine's reply type becomes the StandardReply enum and StandardState gains scene_len/scene_policy. The Rynk service exposes the table through nine endpoints for status, revision-pinned paged reads, single-cell set/unset, layer policy, and the chunked replacement transaction. It validates effects, layer bounds (against the live keymap), and stable LED ids (against the topology) before anything reaches the engine; boards opt in by advertising a capacity via RynkLightingController::with_scene_capacity, and scene endpoints reject with Unsupported when none is wired. Scene writes honor the same unlock gate as the other lighting writes. Scene configuration is durable config like the keymap: after every successful mutation the adapter reads the table back out of the engine and persists it as one header record (len + policy) plus chunk-sized shard records in wire (stable LED id) form. The storage task compares before writing so unchanged shards cost no flash traffic; a storage reset clears them. Boards load persisted scenes at boot with Storage::read_lighting_scenes + install_lighting_scenes, which skips cells whose LED id no longer resolves. Lighting featuresets join the CI test/clippy matrix. Composed with the split renderer replicas underneath: the runtime scene table travels inside StandardReplicaState, so replica renderers draw runtime scenes exactly like the authority without ever seeing the incremental scene mutation commands. StandardReplicaState/-Slot and StandardCommand gain a trailing SCENE_CAP const generic (default 0), ExportReplica snapshots the table and ApplyReplica installs it, and the replica round-trip test now covers a runtime scene cell. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ndings Typed client methods for every scene endpoint, plus two alloc conveniences: read_all_lighting_scenes pages the whole table under one pinned revision and restarts on a concurrent-mutation conflict, and replace_all_lighting_scenes drives the begin/put/commit transaction with a best-effort abort on staging failure. The wasm client re-exports the same surface, so the generated TypeScript picks up the scene types. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
HaoboGu
force-pushed
the
feat/rynk
branch
5 times, most recently
from
July 31, 2026 14:10
c09015e to
d2c97ab
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Why
RMK currently has no general model for per-key lighting that can span irregular and split layouts while keeping firmware, Rynk, and Vial state consistent. This implementation makes one lighting service authoritative, so changes made by either host protocol are reflected by later state queries and rendered output.
Dependency
This branch is stacked on #984 because split keyboards use the bounded application-message channel to propagate lighting state. Once #984 lands, this PR can be rebased to contain only the lighting commits.
This is the complete implementation that supersedes the smaller, closed abstraction draft in #982.
Checks