How Are Litra Lights Controlled via HID++ in OpenLogi? Raw HID Protocol Explained
OpenLogi does not use HID++ for Litra lights; instead, it transmits raw 20-byte HID reports directly to the device, encoding power, brightness, and temperature commands with a fixed header prefix.
The OpenLogi project provides native support for Logitech Litra Glow and Litra Beam lights through a specialized raw HID driver. While the repository handles HID++ devices elsewhere, Litra lights are treated as standalone raw HID peripherals using a proprietary 20-byte report format. This implementation bypasses the HID++ protocol stack entirely, communicating directly with the firmware via usage page 0xff43 and usage 0x0202.
Device Identification and Model Registry
OpenLogi identifies Litra hardware through static descriptors defined in the device registry crate. File openlogi-device-registry/src/litra.rs contains the constants LITRA_GLOW_PRODUCT_ID and LITRA_BEAM_PRODUCT_ID, which map to specific USB product identifiers.
The helper function litra_model_for_route validates discovered HID routes by checking the vendor ID, product ID, usage page 0xff43, and usage 0x0202. When a route matches these criteria, the function returns a LitraModel enum variant—either Glow or Beam—that subsequent code uses to determine valid operational ranges.
Each model maps to a LightCapabilities struct (defined in the same file via the litra_capabilities function) that declares supported controls—power, brightness, and temperature—along with valid value ranges for lumens and kelvin specific to that hardware revision.
Command Abstraction and Capabilities
The generic LightCommand enum, declared in openlogi-core/src/hid/light.rs and re-exported by the Litra driver, represents the four operations OpenLogi can send to a Litra light:
pub enum LightCommand {
Power(bool),
BrightnessPercent(u8),
BrightnessNative(u16),
TemperatureKelvin(u16),
}
This abstraction allows the CLI and core logic to issue high-level commands without knowledge of the underlying HID report structure. The LightCapabilities struct ensures that values remain within hardware limits before encoding occurs.
Encoding Raw HID Reports
Raw report construction happens in openlogi-device/src/write/litra.rs (lines 92–140) within the encode_command function. This function builds the exact 20-byte buffer required by Litra firmware:
- Inserts a fixed prefix
[0xff, 0x04] - Selects the command byte (
COMMAND_POWER,COMMAND_BRIGHTNESS, orCOMMAND_TEMPERATURE) - Writes the value in big-endian format
- Validates the supplied value against the model’s capability range
If validation fails, the function returns WriteError::InvalidLightValue immediately, preventing malformed reports from reaching the hardware.
Async Write Pipeline and Report Transmission
The apply function (lines 176–200 in openlogi-device/src/write/litra.rs) serves as the high-level entry point for sending commands. It executes the following sequence:
- Verification: Confirms the route matches a Litra device using
litra_model_for_route - Encoding: Calls
encode_commandto generate the raw 20-byte buffer - Locking: Acquires a per-device async lock (
device_lock) to prevent concurrent writes - Transmission: Opens a raw HID writer via
open_route_writerand writes the report with a 2-second timeout - Logging: Emits a debug log entry (
debug!(route = %route, "applied raw Litra command"))
This architecture ensures thread-safe access to the HID interface while maintaining low-latency control.
Practical Usage Examples
The following examples demonstrate how to control a Litra device using the apply function and LightCommand enum:
Detecting a Litra device and turning it on:
use openlogi_core::hid::light::LightCommand;
use openlogi_device::write::litra::{apply, LitraModel};
use openlogi_core::device::DeviceRoute;
use openlogi_core::device::backend::HidBackend;
// Assume `backend` implements `HidBackend` and `route` is a discovered DeviceRoute.
let model = LitraModel::Glow; // or LitraModel::Beam
let cmd = LightCommand::Power(true); // turn the lamp on
apply(&backend, &route, model, cmd).await?;
Setting brightness to 50% on a Litra Glow:
let cmd = LightCommand::BrightnessPercent(50);
apply(&backend, &route, LitraModel::Glow, cmd).await?;
Changing colour temperature to 4600K:
let cmd = LightCommand::TemperatureKelvin(4600);
apply(&backend, &route, LitraModel::Glow, cmd).await?;
Integration with OpenLogi Core and CLI
The Litra driver integrates into the unified device management pipeline through openlogi-core::device::Device, which treats Litra hardware as a "stand-alone raw-HID device" (see Device::apply_light). The CLI command openlogi light in openlogi-cli/src/cmd/light.rs exposes these controls to end users, utilising the same LightCommand enum to toggle power, adjust brightness, or modify colour temperature.
Because the Litra protocol operates independently of HID++, the driver bypasses the HID++ command layer entirely while remaining compatible with OpenLogi’s generic device abstraction.
Summary
- OpenLogi treats Litra lights as standalone raw HID devices, not HID++ peripherals, using usage page
0xff43and usage0x0202. - Device identification relies on static descriptors in
openlogi-device-registry/src/litra.rsand thelitra_model_for_routehelper. - Commands are encoded into 20-byte reports with a fixed
[0xff, 0x04]prefix by theencode_commandfunction. - The
applyfunction uses per-device async locking (device_lock) and a 2-second timeout to prevent concurrent write conflicts. - Invalid values return
WriteError::InvalidLightValuebefore transmission, ensuring hardware safety.
Frequently Asked Questions
Does OpenLogi use HID++ to control Litra lights?
No, OpenLogi bypasses the HID++ protocol entirely for Litra devices. The driver in openlogi-device/src/write/litra.rs implements a raw HID protocol that writes 20-byte reports directly to the device endpoint, avoiding the HID++ command layer used for other Logitech peripherals.
How does OpenLogi identify Litra Glow versus Litra Beam devices?
The litra_model_for_route function in openlogi-device-registry/src/litra.rs checks the product ID against static constants LITRA_GLOW_PRODUCT_ID and LITRA_BEAM_PRODUCT_ID. It validates the vendor ID, usage page 0xff43, and usage 0x0202 to determine the specific LitraModel variant.
What happens if I attempt to set a brightness value outside the valid range?
The encode_command function validates all values against the model’s LightCapabilities before transmission. If the value is invalid, the function returns WriteError::InvalidLightValue immediately, preventing malformed reports from reaching the hardware.
How does OpenLogi handle concurrent control commands to the same Litra device?
The apply function acquires a per-device async lock (device_lock) before opening the raw HID writer with open_route_writer. This locking mechanism ensures that only one thread can write to the device at a time, preventing race conditions during 20-byte report transmission.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →