Why make your LEDs coach you?
Per‑key RGB isn’t just bling. With a little firmware and a tiny host script, those LEDs can become a real‑time tutor: highlighting the next‑likely key, finger zones, or rhythm cues while you type. That’s useful because motor‑learning research shows that feedback matters—especially when it’s timed and dosed well. In particular, “knowledge of results” (KR) given on every trial can boost immediate performance but may hurt retention; fading or moderate‑frequency KR tends to produce better learning over time. In other words, guidance that dims at the right moments can help skills stick. (pubmed.ncbi.nlm.nih.gov)
We’ll pair that with practical measurement: WPM using the standard 5‑character word definition, plus accepted error‑rate metrics from the text‑entry literature. For cognitive load, add the NASA‑TLX after each practice block. (yorku.ca)
What to light up (and when)
- Predictive highlights: Use your site’s language model to light the top 1–3 next‑likely keys. Dim the probability tail (e.g., 10–20%) to reduce visual noise and avoid over‑guidance. Tie intensity to probability.
- Finger‑zone scaffolding: Color each finger’s zone uniquely; brighten the exact target key just before press, then fade on press to build proprioceptive mapping without dependence.
- Rhythm cues: Pulse a subtle metronome on the spacebar or underglow to encourage steady pacing during copy typing; taper cues as consistency improves (faded KR principle). (sciencedirect.com)
Implementation paths at a glance
- QMK (wired/USB): Drive LEDs directly and receive real‑time cues from the browser via Raw HID. Use RGB Matrix for per‑key control. (docs.qmk.fm)
- ZMK (wireless/BLE or USB): Enable RGB underglow/per‑key lighting, then talk to the board via ZMK Studio’s RPC over a documented BLE GATT service (or serial). Great for split/wireless boards. (zmk.dev)
Tip: For wireless boards, be mindful of battery draw. Vendors commonly recommend disabling backlighting for multi‑month battery life; design your tutor to be mostly off and “wake” LEDs for very short hints. (appliedergonomics.com)
---
QMK recipe: per‑key highlights from your website
Requirements
- QMK with RGB Matrix and Raw HID enabled.
rules.mk
```make
RGB_MATRIX_ENABLE = yes
RGB_MATRIX_CUSTOM_USER = yes
RAW_ENABLE = yes
```
keymap.c (minimal example)
```c
#include QMK_KEYBOARD_H
#include "raw_hid.h"
// Simple protocol: host sends up to 6 LED indices to highlight and an RGB color.
// Packet layout (32 bytes): [0]=0x01 (cmd), [1]=count, [2..7]=indices, [8]=R,[9]=G,[10]=B
static uint8_t highlight_idx[6];
static uint8_t highlight_count = 0;
static uint8_t color_r=0, color_g=180, color_b=255;
void raw_hid_receive(uint8_t *data, uint8_t length) {
if (length < 11 || data[0] != 0x01) return;
highlight_count = data[1] > 6 ? 6 : data[1];
for (uint8_t i = 0; i < highlight_count; i++) highlight_idx[i] = data[2 + i];
color_r = data[8]; color_g = data[9]; color_b = data[10];
}
bool rgb_matrix_indicators_user(void) {
for (uint8_t i = 0; i < rgb_matrix_get_num_leds(); i++) {
// Dim everything first (optional):
rgb_matrix_set_color(i, 10, 10, 10);
}
for (uint8_t i = 0; i < highlight_count; i++) {
rgb_matrix_set_color(highlight_idx[i], color_r, color_g, color_b);
}
return false; // allow animations to run; we overwrite as needed
}
```
This uses two documented hooks: a Raw HID receive callback and the RGB Matrix per‑frame indicator hook with `rgb_matrix_set_color()`. Map indices to your keyboard’s LED order (QMK’s led_config) and you’re set. (docs.qmk.fm)
Browser side (WebHID; Chrome/Edge/Chromium)
```js
// Minimal prototype: request the Raw HID interface and push a highlight packet.
// Note: Firefox/Safari don’t ship WebHID; Firefox has an optional bridging add‑on.
// Align reportId and usage filters with your Raw HID descriptor.
const vendorId = 0xFEED; // example
async function sendHighlights(indices = [13, 18, 19], rgb=[0,180,255]) {
const [device] = await navigator.hid.requestDevice({ filters: [{ vendorId }] });
await device.open();
const buf = new Uint8Array(32);
buf[0] = 0x01; buf[1] = indices.length;
indices.slice(0,6).forEach((v,i)=> buf[2+i]=v);
[buf[8], buf[9], buf[10]] = rgb;
await device.sendReport(0x00, buf); // reportId per your descriptor
}
```
WebHID is documented on MDN and Chrome’s developer site; build a small adapter in your typing web app to call `sendHighlights()` each keystroke (debounced) or at word boundaries. (developer.mozilla.org)
---
ZMK recipe: BLE‑driven cues with Studio RPC
ZMK supports RGB underglow and per‑key lighting (hardware‑dependent). Enable it in your board/shield config and keymap behaviors. (zmk.dev)
prj.conf (excerpt)
```conf
CONFIG_ZMK_RGB_UNDERGLOW=y
Optional: start off, then let the tutor light momentarily
CONFIG_ZMK_RGB_UNDERGLOW_ON_START=n
```
Keymap (example behavior binding)
```dts
&keymap {
// Example: a key or combo that unlocks Studio so the host can adjust lighting
bindings = <&studio_unlock>; // then connect from your site
};
```
ZMK Studio exposes a documented RPC protocol over a dedicated BLE GATT service (UUID `00000000-0196-6107-c967-c5cfb1c2482a`) with an RPC characteristic (`00000001-0196-6107-c967-c5cfb1c2482a`). Messages are protobuf‑encoded and framed; you can implement a tiny JS client that compiles the official `.proto` definitions and writes framed requests to that characteristic. (zmk.dev)
Browser side (Web Bluetooth; skeleton)
```js
// Using the ZMK Studio GATT service. You’ll need protobuf.js and the
// zmk-studio-messages repo to encode a Request message, then wrap with 0xAB...0xAD framing.
const SERVICE = '00000000-0196-6107-c967-c5cfb1c2482a';
const CHAR = '00000001-0196-6107-c967-c5cfb1c2482a';
async function connectZMK() {
const device = await navigator.bluetooth.requestDevice({ filters: [{ services: [SERVICE] }] });
const server = await device.gatt.connect();
const service = await server.getPrimaryService(SERVICE);
const characteristic = await service.getCharacteristic(CHAR);
return characteristic; // cache and write framed protobuf messages here
}
```
ZMK’s lighting controls (HSB/effects) are configurable; you can implement an RPC that sets specific LEDs or zones for “next key” and fades them quickly to preserve battery life. Start with underglow or per‑key support documented by ZMK and extend from there. (zmk.dev)
---
How to A/B test LEDs vs on‑screen cues
Design
- Within‑subjects crossover with 3 conditions: A) on‑screen cues, B) LED cues only, C) both. Counterbalance order; use identical drills per condition.
- Metrics
- Speed: WPM using 5‑char word definition. (yorku.ca)
- Accuracy: Include an MSD‑based error rate or KSPC per standard text‑entry metrics. (yorku.ca)
- Cognitive load: Post‑block NASA‑TLX (6 subscales). (nasa.gov)
- Retention: Repeat a no‑cues test 24–72 hours later; expect that heavy, always‑on guidance may inflate practice performance but reduce retention (the guidance hypothesis). (pubmed.ncbi.nlm.nih.gov)
Practical tips
- Dose your hints: Start with frequent highlights; fade to “only on errors” or “only on tough bigrams.” That aligns with evidence favoring moderate/fading feedback for learning. (sciencedirect.com)
- Keep the palette calm: reserve saturated colors for the exact next key; use low‑saturation zone colors elsewhere.
- Respect the stack: WebHID works in Chromium; Firefox users may need an add‑on bridge. For ZMK, Web Bluetooth works on most desktop Chrome; verify GATT permissions and include a fallback on USB serial when available. (developer.mozilla.org)
- Battery first (ZMK): default LEDs to off; flash micro‑prompts under 150–250 ms.
---
Where do the speed and error numbers come from?
In text‑entry research, WPM uses a standard 5‑character word definition, and error rates are computed with methods like minimum‑string‑distance and KSPC. Using these lets you compare LED vs on‑screen conditions apples‑to‑apples with prior work. (yorku.ca)
Ship it: a minimal integration plan
1) Add the QMK/ZMK snippets above to your open‑source firmware repos (MIT‑license them for community reuse). 2) Drop a tiny JS client into your typing app to send highlight packets each keystroke/word. 3) Launch a 2‑week A/B test with NASA‑TLX after each 3–5 minute block. 4) Publish your results and iterate on palette and timing—try predictive top‑2 highlights vs rhythm‑only prompts.
With just a few dozen lines of firmware and a couple hundred lines of browser code, your site can turn per‑key RGB into a coach that measurably improves speed, reduces error rate, and does it without gluing learners to on‑screen overlays.