# How Are Litra Lights Controlled via HID++ in OpenLogi? Raw HID Protocol Explained

> Discover how OpenLogi controls Litra lights using raw HID reports, not HID++. Learn about the 20-byte data structure for power, brightness, and temperature commands.

- Repository: [Xuan Zhang/OpenLogi](https://github.com/AprilNEA/OpenLogi)
- Tags: deep-dive
- Published: 2026-09-12

---

**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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-core/src/hid/light.rs) and re-exported by the Litra driver, represents the four operations OpenLogi can send to a Litra light:

```rust
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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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:**

```rust
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:**

```rust
let cmd = LightCommand::BrightnessPercent(50);
apply(&backend, &route, LitraModel::Glow, cmd).await?;

```

**Changing colour temperature to 4600K:**

```rust
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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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.