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, or COMMAND_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:

  1. Verification: Confirms the route matches a Litra device using litra_model_for_route
  2. Encoding: Calls encode_command to generate the raw 20-byte buffer
  3. Locking: Acquires a per-device async lock (device_lock) to prevent concurrent writes
  4. Transmission: Opens a raw HID writer via open_route_writer and writes the report with a 2-second timeout
  5. 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 0xff43 and usage 0x0202.
  • Device identification relies on static descriptors in openlogi-device-registry/src/litra.rs and the litra_model_for_route helper.
  • Commands are encoded into 20-byte reports with a fixed [0xff, 0x04] prefix by the encode_command function.
  • The apply function uses per-device async locking (device_lock) and a 2-second timeout to prevent concurrent write conflicts.
  • Invalid values return WriteError::InvalidLightValue before 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →