Skip to content

Releases: scholzri/rainy75-zmk

v0.2.1

Choose a tag to compare

@scholzri scholzri released this 02 Aug 13:55
b690adc

Diagnostics and build-integrity release. No user-facing behaviour changes on a healthy board.

LED rail divergence trap

A dark-strip episode was observed once on v0.2.0: all 83 LEDs went dark while the keyboard typed normally and SMP answered, a host-mode fill was accepted with zero light, and it stayed dark for hours before self-healing at a USB resume without the board rebooting.

rail_on in the render loop is loop-local, so anything that drops the LED rail (PC2) behind the loop's back is invisible to it and never re-powered: every frame then renders into an unpowered strip, which is silent, logless and dark. That matches the symptoms exactly, but the cause has not been identified and the fault has not recurred, so this ships as instrumentation plus a defensive self-heal rather than a fix.

Every frame now records what the loop believes alongside what the pin actually reads into the USB diagnostic ring (RGB_STATE, code 32), on any change plus a 30 minute keepalive. The ring is .noinit, survives a replug on battery, is readable over SMP, and is persisted to NVS the moment a divergence is seen, before the self-heal removes the only symptom:

reverse/tools/usb_diag.py            # live ring
reverse/tools/usb_diag.py --saved    # the copy persisted at fault time

On divergence the loop re-asserts the rail, restoring light in about 20 ms instead of leaving a dark keyboard. Recovery replays the full drive configuration (GPIO mode, output enable, level), not just the level, so it covers every state the trap can prove rather than only one of the three.

Thanks to @ecliptik for the episode capture, the trap, and the NVS durability work.

Build integrity

build.sh decided a patch was already applied using git apply --reverse --check. That test is invalid for a stacked series: once a later patch rewrites the same lines, the earlier one no longer reverses cleanly. Four of the thirteen patches re-applied and failed noisily on every build.

The noise was the smaller half of the problem. It was indistinguishable from a patch that genuinely did not apply, which is how the USB bind-window grace went missing from every build unnoticed until it was regenerated by hand. Patches are now identified by commit subject, which git am records verbatim and which survives later edits, and a patch that fails to apply now stops the build. Building without one silently produces firmware that is not the tree anyone reviewed.

patches/apply-patches.sh is removed. Nothing called it, it had not been updated since the initial release, and it covered only patches/zephyr, so running it after a west update would have applied 9 of 13 patches and produced a tree silently missing every ZMK patch.

USB workqueue stack

CONFIG_USB_WORKQUEUE_STACK_SIZE=2048, up from Zephyr's 1024 default. Every USB transfer completion runs on that thread, as do the CDC and SMP transport callbacks, and an overflow there would present exactly like the CDC wedge fixed in v0.2.0. Costs 1 KB of RAM.

Upgrading

mcumgr image list reports 0.2.1. Firmware behaviour on a healthy board is unchanged from v0.2.0, so this is optional unless you want the rail trap or are building from source.

v0.2.0

Choose a tag to compare

@scholzri scholzri released this 25 Jul 12:28
8e75d16

USB remote wakeup

A keypress now wakes a sleeping host directly, over real USB resume signaling, instead of the keyboard having to re-present itself and be re-enumerated. That round trip cost roughly 0.6 s of dead HID and swallowed the first keystrokes typed into it.

The B91 can drive resume from the WAKEUPEN system register (0x801401ee), which Telink's public SDK pulses in usb_hardware_remote_wakeup(). The datasheet lists remote wakeup as a suspend-mode feature but documents no bit for driving it, so the SDK is the reference. The stock Evision firmware advertises the capability and never implements it.

Verified on hardware against a sleeping Linux host, keypress as the only input:

SETUP SET_FEATURE wValue=1                 host arms remote wakeup as it sleeps
WAKE_REQ -> RESUME PULSE DRIVEN            keypress
STATUS SUSPEND
WAKE_REQ (susp-flag, susp-level) -> RESUME PULSE DRIVEN
STATUS RESUME                              host resumed the bus
SETUP CLEAR_FEATURE wValue=1               host disarms after waking

No DETACH, no SET_CONFIGURATION, and an unchanged kernel device number: a true resume rather than a re-presentation.

Hosts have to opt in. A host only arms a device it has been told may wake it, and the kernel default is off, deliberately, since a keyboard pressed inside a bag would otherwise wake the machine. Install 99-rainy75-zmk.rules into /etc/udev/rules.d/: it arms the keyboard and the root hub that forwards the resume. Without a rule the per-device setting resets on every replug and every DFU cycle. Where a host never arms it, the previous re-present path still runs, so nothing regresses.

Only reachable when the active endpoint is USB. Over BLE no wakeup is requested.

CDC ACM wedge fixed

After USB re-attach churn the CDC console could die in both directions while HID kept typing and the host still saw a healthy device, surviving physical re-enumeration and cured only by a power cycle.

The cause is upstream Zephyr: usb_enable() re-ran k_work_init() over transfer slots whose work items could still be linked in the USB workqueue's pending list. Zeroing a linked node truncates the list and orphans everything behind it, so those transfers never complete and their endpoints stay blocked. A 20-cycle reproducer wedged the board 20/20 before the fix and 0/20 after.

Shipped alongside it: a .noinit USB diagnostic event ring readable over SMP and persisted to flash, a transfer-stall watchdog covering both directions, and a recovery ladder that defers to a typing pause so self-healing never eats keystrokes.

Wake latency

Every wake from host sleep previously cost a few seconds of dead keyboard and several lost characters: the dead-bus heartbeat treated the host's normal HID bind delay as a dead binding and cycled a healthy device. Failures inside a 5 s grace window after each enumeration no longer count, which removes a needless second re-attach measured at 2.6 s after the wake enumeration.

Upgrading

mcumgr image list now reports 0.2.0, so you can tell what a keyboard is running.

Thanks to @ecliptik for the CDC wedge root-cause analysis, the wake-latency fix, and the diagnostic tooling that made the wakeup work measurable.

v0.1.1

Choose a tag to compare

@scholzri scholzri released this 23 Jul 15:29
53449f7

What's Changed

  • feat(build): bake the release version into the MCUboot image header by @scholzri in #11
  • feat(rainy_rgb): opt-in activity-idle LED blank (CONFIG_RAINY_RGB_IDLE_BLANK) by @ecliptik in #14
  • rainy_rgb: PR #14 review follow-ups + LED VCC rail auto-cut (PC2) by @scholzri in #15
  • fix(usb): make EP SRAM allocation idempotent across re-enumerations by @ecliptik in #17
  • chore: ignore clangd .cache/ index by @ecliptik in #16

Full Changelog: v0.1.0...v0.1.1

v0.1.0

Choose a tag to compare

@scholzri scholzri released this 18 Jul 10:32
9d3512f

First tagged release — this tags the exact tree that was hardware-verified on 2026-07-18.

Verified on hardware

Board Verified
ISO DE (maintainer) Rainy 75 Pro ISO DE USB HID + CDC console, BLE pairing/HID/reconnect, all 12 RGB effects + functional overlays, rgb_mgmt host control over USB and BLE (incl. fully wireless on battery), mcumgr DFU over USB, deep sleep + keypress wakeup
ANSI (@ecliptik) Rainy 75 Pro ANSI Multi-day daily-driver use over USB + BLE, incl. the dead-bus reconnect recovery (#7), ANSI LED map, speedcolour

Builds against (pinned)

  • ZMK 27afcb11 (+ patches/zmk-src/ 0001–0002)
  • Zephyr v4.1.0 (6d7b7922f8, + patches/zephyr/)
  • MCUboot v2.2.0, hal_telink 33fcf82 (+ patches)
  • Telink BLE blob @ fc489d71 (SHA-256 354b2f97…, fetched at build time)
  • Zephyr SDK 0.17.0 (exactly — 0.17.4 does not work)

Install

Source-only release — build with ./build.sh -a --iso (or --ansi), then install_zmk.sh from stock or mcumgr DFU from an existing ZMK install. See docs/zmk-firmware.md. No prebuilt binaries: every built image links the proprietary Telink BLE blob, whose redistribution status is unclear.

What's Changed

  • fix: make the repo build from a clean clone by @jaxx2104 in #2
  • fix(led_map): ANSI LED map — the ANSI strip omits two LEDs by @ecliptik in #5
  • feat(rainy_rgb): speedcolour — colour deepens with typing speed by @ecliptik in #3
  • fix(build): make build/flash scripts portable to macOS (BSD userland) by @ecliptik in #8
  • feat(rainy_rgb): opt-in walker diagnostic effect (one LED, steps on keypress) by @ecliptik in #9
  • feat(usb): dead-bus reconnect recovery for the no-VBUS-detect Telink B91 by @ecliptik in #7
  • feat: host-controlled per-key RGB via mcumgr (rgb_mgmt, group 65) by @schroejahr2 in #6
  • feat(tools): BLE client for rgb_mgmt host control — hardware-verified by @scholzri in #10

New Contributors

Full Changelog: https://github.com/scholzri/rainy75-zmk/commits/v0.1.0