# zmk-rgb-effects Modular RGB effects for ZMK keyboards. Provides per-key per-layer RGB indicators and a framework for adding custom effects without forking ZMK. Requires: [ZMK fork](https://github.com/afiqzudinhadi/zmk) branch `rgb-modular-v1.1` (modular effect registration API). ## Setup Add to your `west.yml`: ```yaml manifest: remotes: - name: afiqzudinhadi url-base: https://github.com/afiqzudinhadi projects: - name: zmk remote: afiqzudinhadi revision: rgb-modular-v1.1 import: app/west.yml - name: zmk-rgb-effects remote: afiqzudinhadi revision: v1.1.1 ``` Enable in your `.conf`: ``` CONFIG_ZMK_RGB_UNDERGLOW=y CONFIG_ZMK_RGB_EFFECT_LAYER=y ``` ## Per-key layer effect Define per-key colors in your keymap/overlay using devicetree: ```dts #include / { underglow-layer { compatible = "zmk,underglow-layer"; pixel-lookup = < 5 4 3 2 1 0 6 7 8 9 10 11 17 16 15 14 13 12 >; layer0 { layer-id = <0>; bindings = < &ugc RGB_RED &ugc RGB_RED &ugc RGB_OFF ... >; }; layer1 { layer-id = <1>; fade-delay = <15>; bindings = < &ugc RGB_BLUE &ugc RGB_BLUE &ugc RGB_OFF ... >; }; }; }; ``` ### Properties - `pixel-lookup` — maps LED chain index to key position - `layer-id` — which keymap layer this color map applies to - `fade-delay` — seconds before reverting to animated effect (-1 = never) - `bindings` — color per key using behaviors below ### Behaviors | Behavior | Description | Params | |----------|-------------|--------| | `&ugc` | Static color | `param1` = RGB hex color | | `&ubi` | Battery indicator | `param1` = low color, `param2` = ok color, `threshold` DT prop | | `&ugi` | HID indicator (caps/num/scroll lock) | `param1` = off color, `param2` = on color, `indicator` DT prop | ## Adding custom effects Create a `.c` file with `ZMK_RGB_EFFECT_DEFINE`: ```c #include static void my_render(struct zmk_rgb_effect_ctx *ctx) { for (int i = 0; i < ctx->num_pixels; i++) { struct zmk_led_hsb hsb = ctx->base_color; hsb.h = (*ctx->animation_step + i * 30) % 360; ctx->pixels[i] = zmk_rgb_hsb_to_rgb(zmk_rgb_hsb_scale_min_max(hsb)); } *ctx->animation_step += ctx->animation_speed; } ZMK_RGB_EFFECT_DEFINE(my_effect, "My Effect", my_render, 0, NULL, NULL, NULL, NULL); ``` Add to `CMakeLists.txt`: ```cmake target_sources(app PRIVATE src/effects/my_effect.c) ``` The effect auto-registers via linker section. Accessible by cycling with `RGB_EFF`. ### As a separate module repo You can also create effects in their own repo without modifying this module: ``` my-zmk-rgb-rainbow/ ├── zephyr/ │ └── module.yml ├── CMakeLists.txt ├── Kconfig └── src/ └── rainbow.c ``` `zephyr/module.yml`: ```yaml build: cmake: . kconfig: Kconfig ``` `Kconfig`: ``` config ZMK_RGB_EFFECT_RAINBOW bool "Rainbow RGB effect" depends on ZMK_RGB_UNDERGLOW default y ``` `CMakeLists.txt`: ```cmake if(CONFIG_ZMK_RGB_EFFECT_RAINBOW) target_sources(app PRIVATE src/rainbow.c) endif() ``` `src/rainbow.c` uses `ZMK_RGB_EFFECT_DEFINE` exactly as above. Add to your `west.yml`: ```yaml - name: my-zmk-rgb-rainbow remote: your-github-remote revision: main ``` Requires the ZMK fork with the modular effect API (`rgb-modular-v1.1` or later). No dependency on this module — the linker section collects effect registrations across all modules. ### Effect flags | Flag | Description | |------|-------------| | `0` | Animated — core runs render at 25ms tick | | `ZMK_RGB_EFFECT_STATIC` | No periodic tick — render called on demand via `zmk_rgb_request_refresh()` | | `ZMK_RGB_EFFECT_PERSISTENT` | Survives RGB toggle and idle sleep | ### Effect callbacks | Callback | When called | |----------|-------------| | `render` | Each tick (animated) or on refresh (static) | | `on_select` | Effect becomes active | | `on_deselect` | User cycles to different effect | | `on_idle(bool awake)` | Keyboard sleeps/wakes (persistent effects only) | | `is_active()` | Queried for ext power gating — return `true` to keep LEDs powered | ### Render context | Field | Type | Description | |-------|------|-------------| | `pixels` | `struct led_rgb *` | LED buffer to write | | `num_pixels` | `uint16_t` | Strip length | | `base_color` | `struct zmk_led_hsb` | User-configured HSB color | | `animation_step` | `uint16_t *` | Read/write animation counter | | `animation_speed` | `uint8_t` | User-configured speed (1-5) | ### Utility functions - `zmk_rgb_hsb_to_rgb(hsb)` — HSB to RGB conversion - `zmk_rgb_hsb_scale_min_max(hsb)` — scale brightness to configured min/max - `zmk_rgb_hsb_scale_zero_max(hsb)` — scale brightness from 0 to max - `zmk_rgb_request_refresh()` — trigger re-render for static effects - `zmk_rgb_request_refresh_wakeup(bool)` — refresh with conditional wake from sleep - `zmk_rgb_set_tick_delay(int seconds)` — delayed tick restart (for fade effects) - `zmk_rgb_is_on()` — query RGB on/off state ## Versioning `vX.Y.Z` where: - **X** — major version (breaking API changes) - **Y** — ZMK core dependency version (matches `rgb-modular-vX.Y`) - **Z** — custom effects (0 = defaults only, 1+ = custom effects included)