blink1rs 0.2.0

Control the blink(1) USB RGB LED. Simple sync API over hidapi, with hardware watchdog support for alerting.
Documentation

blink1rs

crates.io docs.rs CI MSRV license

Control the blink(1) USB RGB LED from Rust.

A small synchronous API over hidapi, aimed at wiring a blink(1) into alerting: red when the build breaks, green when it passes, and dark when the thing that was watching stops running.

use blink1rs::{Blink1, Color};
use std::time::Duration;

let mut b = Blink1::open()?;
b.fade(Color::RED, Duration::from_millis(500))?;

Why this crate

  • hidapi, not libusb. No driver swapping on Windows, no rusb context to thread around. Works on Linux, macOS and Windows.
  • The watchdog is first class. The device can change its own colour when your process stops talking to it. That is the thing a desktop notification cannot do.
  • Colours are sent as given. Gamma correction is off by default, so #ff8000 is the byte triple the device receives, and reading it back returns exactly that.
  • Real error types, no stringly-typed failures, and hidapi stays out of the public API.

Install

cargo add blink1rs

Library only, without the CLI and its dependencies:

[dependencies]
blink1rs = { version = "0.2", default-features = false }

The watchdog

Arm it, then tickle it from your health-check loop. If the loop dies, the device acts by itself.

use blink1rs::{Blink1, Color, Led, OnTimeout};
use std::time::Duration;

let mut b = Blink1::open()?;

// 'D' can only go dark, stay lit, or play a pattern, so "turn red" means
// parking red in pattern line 0 and pointing the watchdog at it.
b.write_pattern_line(0, Color::RED, Duration::ZERO, Led::All)?;
let fire = OnTimeout::PlayPattern { start: 0, end: 0 };
b.watchdog_enable(Duration::from_secs(30), fire)?;
b.set(Color::GREEN)?;

loop {
    // ... check whatever you are watching ...
    b.watchdog_tickle()?;
    std::thread::sleep(Duration::from_secs(10));
}

Firmware 204 tops out near 62 seconds no matter what you ask for, so tickle well inside the timeout.

Command line

The crate also installs a blink1rs binary.

blink1rs list                       # attached devices, by serial
blink1rs info                       # hardware, firmware, current colour
blink1rs set red                    # a name, #rrggbb, or r,g,b
blink1rs set '#00ff80' --fade 2s
blink1rs blink red --count 3
blink1rs off
blink1rs --device 1 set blue        # index from `list`
blink1rs --serial 20001234 set blue
blink1rs watchdog --timeout 30s --color red
blink1rs watchdog --off

Several devices

Blink1::list() sorts by serial number, the same order blink1-tool -d N uses, so indices agree between the two.

for info in blink1rs::Blink1::list()? {
    println!("{} ({})", info.serial, info.kind);
}

Hardware support

mk1 mk2 mk3 mk4
set / fade yes yes yes yes
read colour back no yes yes yes
per-LED addressing no yes yes yes
pattern lines 16 16 32 32
save_patterns needed no yes yes yes
watchdog yes yes yes yes

Generation is read from the serial number prefix, the same way blink1-lib does it. Per-LED addressing and per-LED pattern lines need firmware 204 or later; startup parameters need firmware 206 or later, or mk3 and up. This crate documents those requirements but does not enforce them, matching the C library; older firmware ignores the command rather than failing.

Linux

A blink(1) with no read permission reports no serial number and is skipped during enumeration, so it looks like no device is attached rather than like a permission error. Install the udev rule:

sudo tee /etc/udev/rules.d/51-blink1.rules <<'EOF'
ATTRS{idVendor}=="27b8", ATTRS{idProduct}=="01ed", \
  MODE:="666", GROUP="plugdev"
EOF
sudo udevadm control --reload && sudo udevadm trigger

Then replug the device.

Building needs libudev-dev and pkg-config, which hidapi's default hidraw backend links against. To use a different hidapi backend, depend on hidapi directly and configure it there; blink1rs does not re-export its backend features, because Cargo would unify them in ways that silently undo the choice.

Differences from blink1-tool

  • Gamma is off by default. blink1-tool applies a correction table, so the same hex value looks different between the two. Call set_gamma(true) to match it.
  • save_patterns() returns Ok without confirmation. The device stalls the USB transfer while it writes flash, so there is nothing to confirm with; blink1-lib does the same. Read a pattern line back if you need to be sure.
  • Durations are Duration. The wire format counts 10ms ticks, so values under 10ms jump instantly and anything over 655.35s is clamped.

Threads

Blink1 is Send but not Sync: move it into a thread, or share one behind a Mutex.

Testing

cargo test                                              # no hardware needed
cargo test --test hardware -- --ignored --test-threads=1  # with a device

The unit tests assert every report against byte vectors transcribed from blink1-lib.c, with the source function cited in each test, so they catch a wire-format regression without a device attached. The hardware tests must run single-threaded: there is one HID handle per device.

License

MIT