Hello all!
When I picked up the RGB Macropad, the main use I had in mind was less about having extra programmable buttons and more about making use of the LEDs as status indicators for various things. For example, I already have a key bound on my keyboard for toggling microphone mute, but having one that could turn red when the microphone was muted so I could easily see that at a glance sounded cool (and useful, but more cool).
I was pretty surprised to find out that this is apparently just… not a thing? Like, aggressively not a thing. It seems like such an obvious thing to want to do when you add 24 individually addressable RGB LEDs on to the side of your laptop but I could not find any easy way to make this happen.
(There are some paid and other products that involve using fully custom firmware, but then there’s the challenge of then integrating on with other things, and regardless I’d prefer to stay as close to stock Framework as possible.)
I just wanted a little script that would let me set LEDs on my macropad to arbitrary colours so I could build stuff. And it turns out down that road lies only utter madness.
In the end I drew on my decades of experience as a professional software engineer, time spent working on embedded devices, time spent dissecting and reimplementing various USB protocols to interface with 3rd party devices… so I could turn a light on and off. Technology is great.
At this point I’ve got what I need out of this, so I’m going to code/brain dump this here in case anyone can use it or can use it as a foundation to build something way more cool and useful.
What this is:
- Some small modifications to the Framework qmk_firmware for the RGB Macropad and ANSI RGB Keyboard (though I only have the macropad to test with) that implement a protocol for taking remote control of the RGB LEDs and changing their colours.
- A small python script that talks to that code on your macropad/keyboard that so you can play with the lights.
- Fully backward compatible with keyboard.frame.work and all existing tooling/features.
- Generally unpolished.
What this is not:
- A way to individually configure LEDs via keyboard.frame.work.
- A way to have LEDs react to the keyboard state such as keypresses, layers, etc.
- A fully fleshed out, polished guide to making your keyboard react to system events.
That out of the way, let’s get started!
Disclaimer
- This is provided as-is, without any warranty. You use at your own risk, and I take no responsibility for anything that happens with it.
- This involves flashing firmware. There are always inherent risks to something like that. You assume those risks.
- It’s unlikely I’ll have a ton of time to support this. As mentioned, this is a brain/code dump for other people to potentially build cool stuff on top of or use as a reference. I’ll do my best to pop back in and answer questions, but don’t assume there will be any support.
- I make no assertion as to owning copyright over any of the provided or referenced code. To whatever extent I may have any copyright or other rights, I license them under CC0 and intend for them to be available in the public domain.
Background
All the Framework 16 keyboards/numpads/etc use the open source QMK firmware with Via which provides things like the nice web GUI configuration tool on top of QMK.
QMK, out of the box, provides no real implementation of any device<->host communication. It has extension points to implement your own. Several people have taken advantage of this to implement this same basic idea (send colours to device, light things up) in a way that can be bolted on to existing keyboard firmware.
However, as mentioned, Framework’s firmware uses Via. Via implements extensive device<->host communication to allow for building out things like the web GUI configuration tool. All the existing implementations I could find conflicted with that. Unfortunately, Via itself doesn’t directly provide any way to set key colours.
Even if you were to disable Via, Framework’s keyboard implementation has a fallback when Via is disabled in the build to use the raw HID functionality for its additions… which then also conflicts.
This implementation tackles this all and builds out commands for controlling the lights within Via/Framework’s existing USB control protocol. It does so in a way that should remain compatible with all Via functionality as well as the limited functionality Framework has gone to build on top of that.
QMK Firmware
I’m not going to write a comprehensive guide on setting up the QMK tooling and building and installing firmware as other people here have done an excellent job of that already. Go follow that and ensure you’re able to build and flash the stock Framework firmware before you get started. (This also gives you a way to revert any changes if you run into trouble.)
Once you can build and flash code to your board, you can clone this repository that has the changes already integrated. The changes are detailed below if you want to understand what’s been done or if you want to apply them against Framework’s official repository.
rgb_remote
The actual code for handling the remote control is largely self-contained. Two files are added to the framework boards:
These handle processing incoming USB HID frames, translating them into the various supported commands, and then implementing those.
For anyone looking to do any sort of custom software to talk to this, rgb_remote.h has concrete examples of exactly what to send and what sort of reply you will receive from the controller.
Integrating with a Board
My repository already has these files integrated into the general Framework board code.
The changes boil down to:
- Modify Framework’s
factory.cto:- Include the rgb_remote header. I’ve included it any time RGB_MATRIX_ENABLE is set.
- In the
handle_hidmethod, match our command ID and call into the rgb_remote code.
- Modify the board’s rules to include the rgb_remote.c source file.
Controlling the LEDs
I’ve put together a basic Python class and CLI tool for controlling the LEDs once the firmware is installed. You can find it on GitLab.
You will need Python and the hid package for it to work. The hid package requires libhid or a libhid dev package I believe (e.g., on Debian I believe it needed libhidapi-dev… though there was enough iteration here I’m really not sure).
Setup and usage instructions are provided in the README.md for that repository, the tl;dr is pip install -r requirements.txt ; python setled.py enable ; python setled.py set 128 64 64 1.
If you want to develop something to talk directly to the board, the best reference is the comments in rgb_remote.h. Don’t punish yourself by trying to reverse engineer the python script.
Bye
Hopefully someone finds this useful. As for me, I’m off to start trying to make sense of dbus to see about properly monitoring audio state…
(Also thank you @MJ1. Several of your comments around the forum were a huge help in figuring a bunch of this out.)