Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
de08d08
feat(rmk-types): add Rynk protocol wire types and command tables
HaoboGu Jul 16, 2026
5a0f1e2
feat(rmk-config): rework keyboard.toml layout into [layout].map and […
HaoboGu Jul 16, 2026
94885c3
refactor(rmk): unify Vial and Rynk host handling behind a shared GATT…
HaoboGu Jul 16, 2026
f3761de
feat(rmk): add Rynk USB and BLE transports with split glue
HaoboGu Jul 16, 2026
20d9b89
feat(rmk): add Rynk command handlers, dispatch, and bulk streaming
HaoboGu Jul 16, 2026
af26f64
test(rmk): add Rynk loopback and HID-loopback integration tests
HaoboGu Jul 16, 2026
254b0b9
feat(rynk): add host client core (api, driver, device, layout)
HaoboGu Jul 16, 2026
3cb5cb1
feat(rynk): add serial and BLE host transports
HaoboGu Jul 16, 2026
b02f31b
feat(rynk): add wasm bridge and WebHID transport
HaoboGu Jul 16, 2026
fd7a78f
feat(rynk): add KLE layout conversion
HaoboGu Jul 16, 2026
970f1f6
feat(examples): migrate configs to the new layout schema and add Rynk…
HaoboGu Jul 16, 2026
04a0f32
docs: add Rynk protocol reference and v0.9 migration guide
HaoboGu Jul 16, 2026
1ef4c5e
ci: build and test the Rynk host workspace
HaoboGu Jul 16, 2026
a524447
perf(keymap): index tap-hold profiles instead of inlining MorseProfile
HaoboGu Jul 15, 2026
52edb59
Full-duplex rynk communication (#969)
HaoboGu Jul 19, 2026
540dfee
refactor: improve the rynk firmware design
HaoboGu Jul 19, 2026
aa3398e
chore: restore changes carried by the dropped 'chore' merge commit
HaoboGu Jul 19, 2026
e3480de
fix: make tx loop have higher priority
HaoboGu Jul 20, 2026
5210f77
docs: correct Rynk bulk/layout errors and fill changelog/migration gaps
HaoboGu Jul 20, 2026
8bfc94f
fix(rynk): terminate a packet-aligned USB CDC frame with a ZLP
HaoboGu Jul 20, 2026
6f436cf
split: add a bounded application-message side channel
colonelpanic8 Jul 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
15 changes: 11 additions & 4 deletions .github/ci/_lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,25 @@ log_section() {
printf "\n==> %s\n" "$1"
}

# Feature-set matrix for rmk check/clippy/test. An empty entry means
# `--no-default-features` with no extra features on top. Kept here so
# check.sh and test.sh stay in lockstep — a set added for check is also
# exercised by tests, and vice versa.
# Shared rmk feature-set matrix; empty means only `--no-default-features`.
RMK_FEATURESETS=(
""
"log,std"
"storage"
"async_matrix,storage"
"vial,host_lock,storage"
"vial,_ble"
"split,async_matrix"
"split,async_matrix,_ble"
"split,vial,async_matrix"
"split,vial,async_matrix,_ble"
"split,vial,storage"
"passkey_entry"
"split,vial,storage,passkey_entry"
"vial,storage,steno"
"split,vial,storage,async_matrix,_ble,steno"
"rynk,_ble,split,storage,async_matrix"
"rynk,storage"
)

# Examples auto-discovery skiplist. Reasons:
Expand Down
3 changes: 3 additions & 0 deletions .github/ci/format.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ for crate in rmk rmk-config rmk-macro rmk-types; do
cargo +nightly fmt --manifest-path "$crate/Cargo.toml" --check
done

log_section "Formatting host tooling"
cargo +nightly fmt --manifest-path rynk/Cargo.toml --all --check

log_section "Formatting examples"
while IFS= read -r manifest; do
cargo +nightly fmt --manifest-path "$manifest" --check
Expand Down
35 changes: 35 additions & 0 deletions .github/ci/host.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/bin/bash
set -euo pipefail
# shellcheck source=_lib.sh
source "$(dirname "${BASH_SOURCE[0]}")/_lib.sh"

# The host tooling is its own cargo workspace.
cd "$repo_root/rynk"

export CARGO_TERM_COLOR=always
export CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-$target_root/host}"
mkdir -p "$CARGO_TARGET_DIR"

log_section "Tests"
cargo +stable test --workspace --lib --tests

log_section "Doctests"
cargo +stable test -p rynk --doc

log_section "Wasm smoke check"
cargo +stable check -p rynk --lib --target wasm32-unknown-unknown
cargo +stable check -p rynk-wasm --target wasm32-unknown-unknown

log_section "Wasm package build"
# wasm-pack emits the JS package + generated .d.ts under rynk-wasm/pkg/ (ignored, not checked in).
# --dev keeps wasm-bindgen's type descriptors un-optimized, so a malformed one surfaces as invalid TS below.
(cd rynk-wasm && wasm-pack build --dev --target web >/dev/null)
# Typecheck the whole generated .d.ts: a broken descriptor for any exported type fails CI.
npx --yes --package typescript@5.9.3 tsc \
--noEmit --strict --target ES2022 --lib ES2022,DOM,ESNext.Disposable \
--module ES2022 --moduleResolution bundler \
rynk-wasm/pkg/rynk_wasm.d.ts

log_section "Clippy"
cargo +stable clippy --workspace --lib --tests --examples -- -D warnings
cargo +stable clippy -p rynk-wasm --target wasm32-unknown-unknown -- -D warnings
5 changes: 2 additions & 3 deletions .github/ci/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,9 @@ nx=(nextest run --config-file "$nextest_cfg" --profile ci)
log_section "Running tests"
cargo +stable "${nx[@]}" --manifest-path rmk-config/Cargo.toml
cargo +stable "${nx[@]}" --manifest-path rmk-types/Cargo.toml
# Exercise the rmk_protocol module (gated behind `rmk_protocol`) so the wire-format
# snapshot tests under rmk-types/src/protocol/rmk/snapshots/ run in CI. `host`
# enables rmk_protocol + bulk + _ble + split, covering every snapshot.
# Exercise the rynk protocol module (gated behind `rynk`).
cargo +stable "${nx[@]}" --manifest-path rmk-types/Cargo.toml --features host
cargo +stable "${nx[@]}" --manifest-path rmk-types/Cargo.toml --features steno
cargo +stable "${nx[@]}" --manifest-path rmk-macro/Cargo.toml
for feats in "${RMK_FEATURESETS[@]}"; do
if [[ -z "$feats" ]]; then
Expand Down
43 changes: 43 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,49 @@ jobs:
run: cargo install --locked cargo-expand
- run: .github/ci/test.sh

# Host tooling lives in its own workspace, so it gets its own job.
host:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
targets: wasm32-unknown-unknown
- name: Install host system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libdbus-1-dev pkg-config
# host.sh runs wasm-pack to verify JS/.d.ts package generation.
- uses: taiki-e/install-action@v2
with:
tool: wasm-pack
- uses: Swatinem/rust-cache@v2
with:
shared-key: ci-host
cache-directories: |
target/ci
- run: .github/ci/host.sh

qemu-rynk:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
with:
targets: riscv32imac-unknown-none-elf
- uses: Swatinem/rust-cache@v2
with:
shared-key: ci-qemu-rynk
- name: Install QEMU
run: |
sudo apt-get update
sudo apt-get install -y qemu-system-misc libdbus-1-dev pkg-config
qemu-system-riscv32 --version
- name: Run QEMU Rynk behavior verifier
working-directory: examples/use_rust/qemu-riscv-rynk
run: python3 run.py

# Discover buildable examples for the matrix
discover:
runs-on: ubuntu-latest
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
target
node_modules

# wasm-pack output
pkg

.DS_Store

*.bin
Expand All @@ -15,6 +18,7 @@ rmk/Cargo.lock
rmk-macro/Cargo.lock
rmk-config/Cargo.lock
rmk-types/Cargo.lock
rynk/Cargo.lock

# esp idf build files
.embuild
Expand Down
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"rust-analyzer.linkedProjects": [
"rmk-macro/Cargo.toml",
"rmk/Cargo.toml",
"rynk/Cargo.toml",
// To work on the examples, comment the line above and all of the cargo.features lines,
// then uncomment ONE line below to select the chip you want to work on.
// This makes rust-analyzer work on the example crate and all its dependencies.
Expand Down
7 changes: 5 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,18 @@ Matrix / InputDevices → Events (pub/sub channels) -> InputProcessors/Keyboard(

### `keyboard.toml` and compile-time constants

`keyboard.toml` is parsed by `rmk-config` (`KeyboardTomlConfig`) at two points: by `rmk/build.rs` at build time, and by `rmk-macro` at macro-expansion time. The path defaults to `keyboard.toml` next to `Cargo.toml` and can be overridden with `KEYBOARD_TOML_PATH` in user space's `.cargo/config.toml`.
`keyboard.toml` is parsed by `rmk-config` (`KeyboardTomlConfig`) at two points: by `rmk-types/build.rs` at build time, and by `rmk-macro` at macro-expansion time. The path defaults to `keyboard.toml` next to `Cargo.toml` and can be overridden with `KEYBOARD_TOML_PATH` in user space's `.cargo/config.toml`.

Config is loaded in three layers (later overrides earlier): `event_default.toml` → chip-specific default (from `rmk-config/src/default_config/<chip>.toml`, selected via `[keyboard].chip`) → user `keyboard.toml`.

`build.rs` reads only the `[rmk]` and `[event]` sections, then emits `constants.rs` as Rust `const` items. The full `KeyboardTomlConfig` struct in `rmk-config/src/lib.rs` is the authoritative reference for all available fields and their defaults.
`rmk-types/build.rs` reads only the `[rmk]` and `[event]` sections, then emits `constants.rs` as Rust `const` items. The full `KeyboardTomlConfig` struct in `rmk-config/src/lib.rs` is the authoritative reference for all available fields and their defaults.

`[event]` tunes per-event pub/sub channel sizes (`channel_size`, `pubs`, `subs`). All event names and their defaults live in `rmk-config/src/default_config/event_default.toml`.

## Rules

- Don't use `pub use` for convenient usage **within** the crate
- Don't add a small helper function (≤ 10 lines) that has only one call site — inline it at the call site
- When writing code comments, follow the principles in [Best practices for writing code comments](https://stackoverflow.blog/2021/12/23/best-practices-for-writing-code-comments/)
- When writing documentation and READMEs, follow the principles in [Best practices for GitHub Docs](https://docs.github.com/en/contributing/writing-for-github-docs/best-practices-for-github-docs)
- If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
## Features

- **Broad microcontroller compatibility**: Leveraging [embassy](https://github.com/embassy-rs/embassy), RMK supports a comprehensive range of microcontrollers, including stm32, nRF, rp2040(w), esp32, etc
- **Dynamic keymap customization**: RMK offers native [Vial](https://get.vial.today) support, enabling real-time keymap modifications. You can even edit keymaps over BLE connections wirelessly
- **Dynamic keymap customization**: RMK offers real-time keymap modifications through [Vial](https://get.vial.today), or its native [Rynk](https://rmk.rs/docs/features/rynk) protocol. You can even edit keymaps over BLE connections wirelessly
- **Advanced keyboard functionality**: RMK comes with lots of advanced keyboard features by default, including layer switching, media controls, system commands, mouse control, and more
- **Wireless connectivity**: BLE wireless support with automatic reconnection and multi-device capabilities for nRF52 and esp32 microcontrollers, tested on nRF52840, esp32c3, esp32s3, Pi Pico W
- **Easy configuration**: RMK simplifies keyboard development through a single `keyboard.toml` configuration file. For Rust enthusiasts, the firmware remains highly customizable using Rust code
Expand Down
2 changes: 1 addition & 1 deletion README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
## 特性

- **MCU支持丰富**:基于 [embassy](https://github.com/embassy-rs/embassy),RMK 支持非常多的MCU系列,例如 stm32/nrf/rp2040/esp32等。
- **实时键位编辑**:RMK 默认支持 Vial 进行实时键位编辑,即时生效。您甚至可以通过 BLE 使用 Vial 来编辑键位
- **实时键位编辑**:RMK 通过默认启用的 [Vial](https://get.vial.today) 或可选的原生协议 [Rynk](https://rmk.rs/docs/features/rynk) 支持实时键位编辑,即时生效。您甚至可以通过 BLE 无线编辑键位
- **高级键盘功能**:RMK 默认提供许多高级键盘功能,如层切换、媒体控制、系统控制、鼠标控制等。
- **无线支持**:RMK 支持 BLE 无线连接,包括自动重连和多设备支持,已经在 nrf52840, esp32 和 Pi Pico W 上进行了测试。
- **易于配置**:RMK提供了一个非常简单的配置键盘的方法,你只需要一个`keyboard.toml`文件,就可以构建起你的键盘固件(不需要写任何Rust代码)!当然,对于 Rust 开发者来说,你仍然可以使用代码方式来使用 RMK 从而获得更大的灵活性。
Expand Down
50 changes: 32 additions & 18 deletions docs/docs/main/docs/configuration/appendix.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,7 @@ direct_pin_low_active = true
rows = 5
# Number of cols. For a split keyboard, this is the total number of cols for all splits
cols = 4
# Number of layers. Be careful, since large layer number takes more flash and RAM
layers = 3
# keypad example: (for the key in position (2,1) the `H1` profile is activated)
# keypad example
# ┌───┬───┬───┬───┐
# │NUM│ / │ * │ - │ <-- row 0, col 0..4
# ├───┼───┼───┼───┤
Expand All @@ -63,14 +61,18 @@ layers = 3
# ├───┴───┼───┤ N │
# │ 0 │ . │ T │
# └───────┴───┴───┘
matrix_map = """
(0,0,R) (0,1,R) (0,2,R) (0,3,R)
(1,0,R) (1,1,R) (1,2,R) (1,3,R)
(2,0,R) (2,1,R:H1) (2,2,R)
(3,0,R) (3,1,R) (3,2,R) (3,3,R)
(4,0,R) (4,1,R)
map = """
(0,0,R) (0,1,R) (0,2,R) (0,3,R)
(1,0,R) (1,1,R) (1,2,R) (1,3,R)
(2,0,R) (2,1,R) (2,2,R)
(3,0,R) (3,1,R) (3,2,R) (3,3,R)
(4,0,R) (4,1,R)
"""

[keymap]
# Number of layers. Be careful, since large layer number takes more flash and RAM
layers = 3

# here are the aliases for the example layer.keys below
[aliases]
MyCut = "WM(X, LCtrl)"
Expand All @@ -79,13 +81,13 @@ MyPaste = "WM(V, LCtrl)"

# Key map definitions per layer:
# The number (and order) of entries on each layer should be
# identical with the number (and order) of entries in `matrix_map`.
# identical with the number (and order) of entries in `layout.map`.
# Empty layers will be used to fill if the number of explicitly
# defined layers is smaller than the `layout.layers` setting
# defined layers is smaller than the `keymap.layers` setting

# layer 0 (default):
# (the number comes from the order of '[[layer]] entries' in the file)
[[layer]]
# (the number comes from the order of '[[keymap.layer]] entries' in the file)
[[keymap.layer]]
name = "base_layer" #optional name for the layer
keys = """
NumLock KpSlash KpAsterisk KpMinus
Expand All @@ -96,7 +98,7 @@ Kp1 Kp2 Kp3 Enter
"""

# layer 1:
[[layer]]
[[keymap.layer]]
name = "mouse_navigation" #optional name for the layer
keys = """
TO(base_layer) @MyCut @MyCopy @MyPaste
Expand Down Expand Up @@ -131,7 +133,7 @@ one_shot_modifiers = {
enable_flow_tap = true
prior_idle_time = "120ms"
hold_on_other_press = true
unilateral_false = false
unilateral_tap = false
hold_timeout = "250ms"
gap_timeout = "250ms"

Expand Down Expand Up @@ -185,7 +187,8 @@ morses = [
]

[behavior.morse.profiles]
# matrix_map may refer these to override the defaults given in [behavior.morse] for some key positions - this example is a home row mod
# Named overrides of the [behavior.morse] defaults, referenced by name from a morse key
# (profile = "...") or a tap-hold action's optional profile arg — e.g. a home row mod is MT(key, mod, H1).
H1 = { permissive_hold = true, unilateral_tap = true, hold_timeout = "250ms", gap_timeout = "250ms" }
H2 = { enable_flow_tap = false, permissive_hold = true, unilateral_tap = true, hold_timeout = "200ms", gap_timeout = "200ms" }
MRZ = { normal_mode = true, unilateral_tap = false, hold_timeout = "200ms", gap_timeout = "200ms" }
Expand Down Expand Up @@ -295,6 +298,10 @@ split_peripherals_num = 0
ble_profiles_num = 3
# BLE Split Central sleep timeout in seconds (0 = disabled)
split_central_sleep_timeout_seconds = 0
# Maximum macro data bytes in one Rynk macro request or response
protocol_macro_chunk_size = 64
# Rynk RX/TX buffer size in bytes.
# rynk_buffer_size = 512

# Split configuration
# This section conflicts with the [matrix] section. You can only have either [matrix] or [split], but NOT BOTH
Expand Down Expand Up @@ -395,11 +402,18 @@ defmt_log = true

# Host-side tools configuration
[host]
# Whether Vial is enabled (default: true)
# Whether Vial is enabled (default: true in keyboard.toml config)
vial_enabled = true
# Whether Rynk is enabled (default: false in keyboard.toml config)
# Rynk and Vial are mutually exclusive and must match Cargo features.
rynk_enabled = false
# The unlock keys are the combo of the row 0, col 0 key and
# the row 0, col 1 key
# the row 0, col 1 key. Shared by the Vial lock and the Rynk lock gate.
unlock_keys = [[0, 0], [0, 1]]
# Start (and stay) unlocked. Renamed from `vial_insecure` (still parses).
insecure = false
# Rynk only: move config writes into the locked tier (default: false).
write_requires_unlock = false

# Chip-specific configuration
# To use the default configuration, ignore this section completely
Expand Down
Loading