LED‑Coached Typing: Turn Per‑Key RGB Into a Real‑Time Tutor (QMK/ZMK Recipes You Can Ship)

LED‑Coached Typing: Turn Per‑Key RGB Into a Real‑Time Tutor (QMK/ZMK Recipes You Can Ship)

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)

Implementation paths at a glance

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

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

Practical tips

---

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.

LED‑Coached Typing: Turn Per‑Key RGB Into a Real‑Time Tutor (QMK/ZMK Recipes You Can Ship) - article illustration

Ready to improve your typing speed?

Start a Free Typing Test