Feep! » Blog » Post

Using a USB RGB LED as an ambient status monitor

Since I just updated to a newer copy of the Stack Exchange data dumps, I’ve been shepherding that process through my CI pipelines to get the updated data into production. Even when everything goes well this takes a couple of days to finish (my server is not very fast), but invariably it doesn’t go well and I have to make tweaks and restart it a couple of times. Since it can fail unexpectedly at any time, I find myself constantly refreshing the CI status page, which is a distraction from other tasks. In the past, I’ve written about improving this process with script tweaks, but this time, I’m focusing on a hardware improvement: a simple ambient status monitor that I can see at a glance, without interrupting my workflow, or even touching my computer.

A bare circuit board sticking out of a USB port, with a green light showing at the far end.

Seeing the light

There are a number of devices on the market that all function similarly: a small microcontroller (like an ATmega) controlling an RGB LED, running open-source firmware, and communicating with a computer through some protocol or another. Off the top of my head, I know about the ThingM blink(1), the BlinkStick, and the fit-statUSB. (The BlinkStick uses a custom USB HID protocol, the statUSB has a simple serial protocol; I assume the blink(1) has its own unique method but I haven’t looked at it. Most also provide some kind of client software for easier communication.)

I happen to have a BlinkStick v1 lying around, from when I bought the kit as soldering practice some ten years ago. At the time, I didn’t have a desk setup, and I realized that having a bare circuit board sticking out of the side of my laptop was a recipe for a smashed-up USB port, so it quickly went into a box and got mostly forgotten about until last week.

Unfortunately, I had some problems getting it to work again. The version of the Python CLI client software I had lying around had bit-rotted—it still wanted Python 2. When I reinstalled the package, I discovered first that the new version of the program was missing chmod u+x, and then that it had Windows line endings so the shebang was broken, and then it complained that “'array.array' object has no attribute 'tostring'”, at which point I gave up on it. My impression is that while it officially supports Linux, in practice it seems like it’s maybe only maintained for Windows, if at all.

Next, I tried using the Node.js version, only to discover it hadn’t been updated in six years and relied on outdated bindings that I couldn’t get to compile for a modern version of Node. So, in the end, I hacked together my own client, copying some code from a GitHub issue where someone else had the same problems. It does exactly what I need—not any less, but also not any more.

As far as I can tell, this issue isn’t specific to BlinkStick. My impression is that these devices were something of a fad and now have fallen by the wayside a bit. For example, the fit-statUSB, which I’ve also used before, appears to be abandoned as well—the product page is a 404, though some documentation still lingers on their website.

Setting up scripts

My next step was to resurrect all the tooling I had from the last time I’d set up an indicator like this. Fortunately, I had anticipated the need to change tools, so I already had a small wrapper script to abstract this communication away from all the higher-level things I might want to integrate with an indicator light. It’s called blink, and it takes a single argument: a color to change to. It then interfaces with whatever device I currently have connected. This way, all my other scripts remain device-agnostic—they don’t need to know how to talk to a BlinkStick (or any future device I might use). For the moment, that script looks like this:

#!/bin/sh
blink() {
	node ~/src/vend/blinkstick/blinkstick.mjs "$1" # aka `blinkstick "$1"`
}

case "${1:-blue}" in
	black) blink '#000000' ;;
	red)   blink '#FF0000' ;;
	green) blink '#00FF00' ;;
	blue)  blink '#0000FF' ;;
	*)     blink '#FF7500' ;; # orange
esac

On top of this, I built a small tool called blinkstat. This script runs the given command and changes the LED color based on its exit code, green for success (exit code 0) or red for failure (any other exit code).

#!/bin/sh
blink blue
"$@" && blink green || blink red

This is useful for long-running commands where I don’t want to keep checking the terminal: I just prepend blinkstat to a command, and when it finishes, a glance at the LED shows if it succeeded or failed.

Monitoring GitLab pipelines

blinkstat is useful for local long-running commands, but I also want to monitor GitLab CI pipelines. To integrate that with my LED, I asked ChatGPT for a script that would talk to the GitLab API:

Write the following program:

gl-monitor-pipeline(1) - report the status of a GitLab pipeline

Usage: gl-monitor-pipeline <url>

Example: gl-monitor-pipeline https://gitlab.example.com/group/project/-/pipelines/12910

Waits until the pipeline is complete, then prints the final status and exits with either success or failure depending on the result.

I had to give it a couple of extra hints (it first assumed that the URL was an API it made up, so I had to tell it to look up how the pipeline API actually worked) but overall it wrote the script a lot faster than I could have, and probably with fewer mistakes. I haven’t bothered to review the code; I'm just blindly trusting that it knows what it's doing on the grounds that if it has bugs I’ll find out about them when I run into them.

Bringing it all together, I can run:

blinkstat gl-monitor-status https://gitlab.home/user/project/pipeline/1234

This turns the LED blue while the pipeline is running. I can step away, work on something else, and check back later to see whether it has finished successfully (green) or failed (red).

Conclusion

So far, this setup has been useful; I think I’ve saved myself a fair bit of refreshing the last few days. This morning, I woke up and saw a green LED—confirmation that my pipeline had finally completed successfully overnight.

However, there’s still room for improvement. Right now, this setup only works for CI pipelines when pushing to production, not for dev work. The LED is connected to my laptop, but most of my development happens over SSH on a different computer. I haven’t yet figured out how to hook up this light to commands running on that remote machine, but I’m sure I’ll come up with something eventually.