Unofficial Rust protocol and USB control library for the Lian Li HydroShift II LCD-C
No L-Connect process, DLL, service, or network connection is required.
Important
This is an independent, reverse-engineered project. It is not affiliated with,
endorsed by, or supported by Lian Li. The implementation has been verified on
the HydroShift II LCD-C 360N with USB ID 1CBE:A021.
| Surface | Capability |
|---|---|
| Round LCD | 480×480 JPEG background and transparent PNG overlay |
| LCD controls | Brightness, 0°/90°/180°/270° rotation, clock/session setup |
| Pump ring | 24 addressable RGB LEDs, static frames and uploaded animations |
| Cooling | Pump target mapping from 1600–2500 RPM and three fan-control bytes |
| Transport | Direct USB bulk endpoints 0x01 / 0x81 |
The crate exposes both a high-level [HydroShiftII] controller and a stateless
[Protocol] packet encoder. USB, image encoding and bundled TinyUZ support are
feature-gated.
[dependencies]
hydroshift2 = { git = "https://github.com/andyjusa/hydroshift2-rs" }use hydroshift2::{HydroShiftII, Rotation};
fn main() -> hydroshift2::Result<()> {
// Claims the USB interface and clears retained vendor LCD layers.
let mut device = HydroShiftII::open_and_initialize()?;
device.set_lcd_brightness(220)?;
device.set_rotation(Rotation::Degrees0)?;
device.set_pump_speed(2025, [0, 0, 0])?;
// One RGB frame uses the dependency-free Rust TinyUZ encoder.
device.set_static_rgb([0, 232, 255], 0.8)?;
// A 64-frame, 20 FPS loop is compressed once and played by the device.
device.set_rainbow()?;
Ok(())
}Opening the device does not change its state:
use hydroshift2::HydroShiftII;
let device = HydroShiftII::open()?;
// Call initialize_lcd() only when you want to clear the retained LCD layers.
# Ok::<(), hydroshift2::Error>(())use hydroshift2::HydroShiftII;
fn main() -> hydroshift2::Result<()> {
let image = image::open("dashboard.png")?;
let mut device = HydroShiftII::open_and_initialize()?;
device.send_image(&image, 90)?;
Ok(())
}The hardware display is physically round, but its transmitted canvas is a square 480×480 image. Mask or compose the visible circle in your renderer.
The pump ring does not need a host USB write for every animation frame. A batch
of frames is TinyUZ-compressed, uploaded with command 0xFC, then played by the
controller at the supplied interval. set_rainbow() uploads 64 frames with a
50 ms interval (20 FPS).
- One-frame updates use the built-in pure-Rust encoder.
- Multi-frame uploads use the pinned official TinyUZ 1.1.1 compressor on Windows because that representation is known to be accepted by the H2 firmware.
- The executable is MIT-licensed, SHA-256 checked, embedded at build time, and
extracted to
%LOCALAPPDATA%/hydroshift2-rs/tinyuz/1.1.1/when required. - Other platforms can pass
TinyUzCli::from_path(...)tosend_rgb_frames_with(...).
Each control message begins with a 500-byte plaintext header. The header is
DES-CBC encrypted with PKCS#7 padding, placed in a 512-byte envelope, and ends
with A1 1A. Image and RGB payloads follow that envelope.
| Command | Purpose | Payload detail |
|---|---|---|
0x65 |
JPEG LCD background | big-endian byte length at header 8..12 |
0x66 |
PNG LCD overlay | big-endian byte length at header 8..12 |
0x0D |
LCD rotation | one byte, 0..3 |
0x0E |
LCD brightness | one byte, 0..255 |
0x33 |
device clock | UTC date/time and display mode |
0xFB |
pump/fan control | 16-byte block with CRC-16/CCITT |
0xFC |
24-LED ring frames | TinyUZ data + frame count + interval + LED count |
Protocol::*_at variants accept an explicit timestamp for packet-capture
comparison and deterministic tests.
| Feature | Default | Description |
|---|---|---|
usb |
✓ | rusb discovery and bulk transport |
image |
✓ | JPEG/PNG encoding and clean LCD initialization |
bundled-tinyuz |
✓ | pinned Windows x64 TinyUZ for multi-frame RGB |
Protocol-only consumers can disable everything:
hydroshift2 = {
git = "https://github.com/andyjusa/hydroshift2-rs",
default-features = false
}- Close L-Connect before claiming the USB interface. Two controllers writing to the same device will race and may leave stale LCD or RGB state.
- Pump requests are clamped to the observed 1600–2500 RPM range.
open()only claims the interface;open_and_initialize()intentionally clears both retained LCD composition layers.- Treat all reverse-engineered hardware control as use-at-your-own-risk.
cargo run --example probe
cargo run --example static_rgb
cargo run --example rainbow
cargo run --example lcd_image -- path\to\dashboard.png
cargo test --all-featuresThe original implementation is exercised in SpatialLume, where the LCD and ring are driven independently of L-Connect.
The Rust library is available under the MIT License. The bundled TinyUZ binary retains its upstream MIT license; see THIRD_PARTY_NOTICES.md.