Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
6d2302c
feat(rmk-types): add Rynk protocol wire types and command tables
HaoboGu Jul 16, 2026
d90bf13
feat(rmk-config): rework keyboard.toml layout into [layout].map and […
HaoboGu Jul 16, 2026
7178a13
refactor(rmk): unify Vial and Rynk host handling behind a shared GATT…
HaoboGu Jul 16, 2026
1b06773
feat(rmk): add Rynk USB and BLE transports with split glue
HaoboGu Jul 16, 2026
f109a06
feat(rmk): add Rynk command handlers, dispatch, and bulk streaming
HaoboGu Jul 16, 2026
65f55e0
test(rmk): add Rynk loopback and HID-loopback integration tests
HaoboGu Jul 16, 2026
e86e888
feat(rynk): add host client core (api, driver, device, layout)
HaoboGu Jul 16, 2026
fde7f14
feat(rynk): add serial and BLE host transports
HaoboGu Jul 16, 2026
c597d79
feat(rynk): add wasm bridge and WebHID transport
HaoboGu Jul 16, 2026
2612448
feat(rynk): add KLE layout conversion
HaoboGu Jul 16, 2026
9aed01c
feat(examples): migrate configs to the new layout schema and add Rynk…
HaoboGu Jul 16, 2026
4e3c594
docs: add Rynk protocol reference and v0.9 migration guide
HaoboGu Jul 16, 2026
5d269f3
ci: build and test the Rynk host workspace
HaoboGu Jul 16, 2026
b8c8da7
perf(keymap): index tap-hold profiles instead of inlining MorseProfile
HaoboGu Jul 15, 2026
6ac7e8a
Full-duplex rynk communication (#969)
HaoboGu Jul 19, 2026
9e9a728
refactor: improve the rynk firmware design
HaoboGu Jul 19, 2026
684a35c
chore: restore changes carried by the dropped 'chore' merge commit
HaoboGu Jul 19, 2026
c860967
fix: make tx loop have higher priority
HaoboGu Jul 20, 2026
cbbef3f
docs: correct Rynk bulk/layout errors and fill changelog/migration gaps
HaoboGu Jul 20, 2026
37abf73
fix(rynk): terminate a packet-aligned USB CDC frame with a ZLP
HaoboGu Jul 20, 2026
6a35ac1
fix: send topics after handshaked
HaoboGu Jul 22, 2026
749e82d
test(rynk): complete the handshake before expecting topic pushes
HaoboGu Jul 22, 2026
17c867c
fix: fix windows serial connection error, clean a little bit
HaoboGu Jul 23, 2026
cfab724
feat(rynk): use cobs encoding in rynk protocol (#994)
HaoboGu Jul 28, 2026
d2c97ab
refactor: clean code
HaoboGu Jul 30, 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
  •  
  •  
  •  
20 changes: 13 additions & 7 deletions .github/ci/_lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,27 +23,33 @@ export CARGO_TERM_PROGRESS_WHEN=never
export CARGO_NET_GIT_FETCH_WITH_CLI=true
export TERM="${TERM:-dumb}"

# Shared target dir for scripts that run cargo. Callers that need it should
# `mkdir -p "$target_root"` before use; we don't create it here so scripts
# that don't run cargo (e.g. discover.sh) don't leave an empty directory.
# Shared parent for CI target directories. Cargo creates each target directory
# itself so it also writes the CACHEDIR.TAG required by `cargo clean`.
target_root="$repo_root/target/ci"

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
2 changes: 0 additions & 2 deletions .github/ci/check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ set -euo pipefail
# shellcheck source=_lib.sh
source "$(dirname "${BASH_SOURCE[0]}")/_lib.sh"

mkdir -p "$target_root"

# Emit "--- <cmd> ..." tuples for rmk (every feature set) plus the other
# workspace crates. Tokens are literal or feature lists with no whitespace,
# so relying on word-splitting at the call site is safe.
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
40 changes: 40 additions & 0 deletions .github/ci/host.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/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}"

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

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

log_section "No-alloc smoke check"
# The dongle build: every other job runs `rynk` with default features (std ⇒
# alloc), so without this its `#[cfg(not(feature = "alloc"))]` half is never
# compiled.
cargo +stable clippy -p rynk --lib --no-default-features -- -D warnings

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
6 changes: 2 additions & 4 deletions .github/ci/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ source "$(dirname "${BASH_SOURCE[0]}")/_lib.sh"

export CARGO_NET_OFFLINE=false
export CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-$target_root/test}"
mkdir -p "$CARGO_TARGET_DIR"

# Each crate is its own cargo workspace in this repo, so nextest's default
# `<workspace>/.config/nextest.toml` lookup would miss our shared config at
Expand All @@ -16,10 +15,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:
Comment thread
HaoboGu marked this conversation as resolved.
Dismissed
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. 488 bytes = 2*BLE maximum packet size
# rynk_buffer_size = 488

# 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