# Litra Light Integration in OpenLogi: Camera-Linked Auto Power Control Explained

> Learn how OpenLogi integrates Litra Light with camera-linked auto power control. Discover the raw-HID stack and auto camera flag for seamless lighting automation.

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

---

**OpenLogi integrates Logitech Litra lights through a three-layer raw-HID stack that supports camera-automated power control via the `auto_camera` flag in `LightSettings`, which automatically powers the light on when any camera becomes active.**

OpenLogi treats Logitech Litra lights as *stand-alone-light* devices accessible through a raw-HID interface. This integration enables precise hardware control alongside an optional automation feature that links light power state to camera activity. The implementation spans device detection, low-level protocol encoding, async IPC handling, and desktop UI configuration.

## How OpenLogi Detects Litra Devices

The device registry identifies Litra hardware through USB vendor/product IDs and HID usage descriptors. In [`crates/openlogi-device-registry/src/litra.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-device-registry/src/litra.rs), the `matches_litra` function validates connected devices against known Litra identifiers:

```rust
// openlogi_device_registry/src/litra.rs
pub fn matches_litra(vendor_id: u16, product_id: u16, usage_page: u16, usage_id: u16) -> bool {
    find_litra(vendor_id, product_id, usage_page, usage_id).is_some()
}

```

Supported models include **Litra Glow** (PID `0xc900`) and **Litra Beam** (PID `0xc901`). Upon detection, the registry constructs a `LitraDescriptor` recording the model variant and capability ranges for brightness and color temperature.

## Raw-HID Driver: Encoding and Sending Commands

The Litra driver in [`crates/openlogi-device/src/write/litra.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-device/src/write/litra.rs) handles protocol translation. The `encode_command` function converts semantic `LightCommand` values into the 20-byte report format required by Logitech's firmware:

```rust
// openlogi_device/src/write/litra.rs
pub fn encode_command(model: LitraModel, command: LightCommand) -> Result<[u8; REPORT_LEN], WriteError> {
    // … builds the report and validates ranges …
}

```

Report structure uses **report ID `0x11`**, prefix bytes `0xff 0x04`, and command identifiers `0x1c` (power), `0x4c` (brightness), and `0x9c` (temperature). The `apply` function executes the write with per-device locking and a 2-second timeout:

```rust
// openlogi_device/src/write/litra.rs
pub async fn apply(
    backend: &dyn HidBackend,
    route: &DeviceRoute,
    model: LitraModel,
    command: LightCommand,
) -> Result<(), WriteError> {
    // … resolve model, encode, lock, write …
}

```

## Agent-Core IPC and Async API

The agent core provides a higher-level interface in [`crates/openlogi-agent-core/src/hardware/light.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent-core/src/hardware/light.rs). `apply_light_unlocked` resolves the Litra model and forwards commands through `openlogi_hid::apply_litra`:

```rust
// openlogi-agent-core/src/hardware/light.rs
async fn apply_light_unlocked(route: &DeviceRoute, command: LightCommand) -> Result<(), WriteError> {
    let Some(model) = litra_model_for_route(route) else {
        return Err(WriteError::LightUnsupported { … });
    };
    super::timed(HidppOperation::Light, openlogi_hid::apply_litra(route, model, command)).await
}

```

A per-device async mutex (`light_write_lock`) serializes all writes via `apply_light`, preventing concurrent HID access conflicts.

## Persisting Settings and Background Re-Application

`LightSettings` in `openlogi_core::config` stores user preferences including the critical **`auto_camera` boolean** for camera-linked automation. When settings change or devices reconnect, `set_light_in_background` spawns a coalescing worker from `LIGHT_WORKERS` to re-apply commands:

```rust
// openlogi-agent-core/src/hardware/light.rs
pub fn set_light_in_background(..., light: &LightSettings, ...) {
    // send a LightApplyRequest to the per‑device worker
}

```

The worker calls `apply_light_settings`, which iterates through `commands_for_light_settings` outputs—brightness, temperature, and power commands—and dispatches each via `apply_light_unlocked`.

## Desktop UI: Camera-Automation Toggle

The stand-alone-light panel in [`crates/openlogi-desktop/src/features/lighting/standalone.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/features/lighting/standalone.rs) exposes the **camera-automation toggle** ("Auto-on with camera"). Toggling updates `LightSettings.auto_camera` and commits to persistent state:

```rust
// openlogi-desktop/src/features/lighting/standalone.rs
Toggle::new("standalone-light-camera-automation")
    .selected(current.auto_camera)
    .on_change(|auto_camera, _window, cx| {
        update_light(cx, |state| {
            let mut light = state.light();
            light.auto_camera = *auto_camera;
            state.commit_light(light);
        });
    });

```

The UI consults `state.camera_automation_active()` to determine whether manual slider adjustments should immediately power the light. With automation disabled, slider movement sets `light.enabled = true` directly. With automation enabled, the light waits for camera activation.

## How Camera Activity Triggers Auto Power-On

The camera subsystem emits `StateEvent::CameraChanged` when any Logitech camera activates. The `LightPanel` listens for this event and updates accordingly. The automation flow works as follows:

1. Camera activation fires `CameraChanged`
2. UI logic (or background listener) sets `light.enabled = true`
3. Background worker's next re-apply cycle issues `Power(true)` via `apply_light_settings`

The `auto_camera` flag itself does not directly control hardware; it gates whether the UI/background logic sets `light.enabled` based on camera state. The worker faithfully re-applies whatever `enabled` value is present in `LightSettings`.

## Command-Line and Programmatic Usage

### CLI Examples

```bash

# Discover Litra device route

openlogi list --filter litra

# Power on and configure

openlogi light on
openlogi light brightness 70

# Enable camera automation

openlogi light auto-camera on

```

### Rust API

```rust
use openlogi_hid::{LightCommand, apply_litra};
use openlogi_device_registry::litra::find_litra;
use openlogi_core::config::LightSettings;

let route = /* DeviceRoute::RawHid … */;
let model = find_litra(0x046d, 0xc900, 0xff43, 0x0202).unwrap().model;

// Direct hardware control
apply_litra(&route, model, LightCommand::Power(true)).await?;
apply_litra(&route, model, LightCommand::BrightnessPercent(70)).await?;

// Persist with automation enabled
let mut settings = LightSettings::default();
settings.enabled = true;
settings.brightness_percent = 70;
settings.auto_camera = true;
openlogi_agent_core::hardware::light::set_light_in_background(
    &device_io_gate,
    Some(route.clone()),
    &settings,
    litra_capabilities(model),
);

```

## Key Source Files

| File | Responsibility |
|------|--------------|
| [`crates/openlogi-device-registry/src/litra.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-device-registry/src/litra.rs) | USB VID/PID matching, capability description |
| [`crates/openlogi-device/src/write/litra.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-device/src/write/litra.rs) | 20-byte report encoding, raw-HID writes |
| [`crates/openlogi-hid/src/lib.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hid/src/lib.rs) | `apply_litra` IPC entry point |
| [`crates/openlogi-agent-core/src/hardware/light.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent-core/src/hardware/light.rs) | Async API, background workers, setting re-application |
| [`crates/openlogi-desktop/src/features/lighting/standalone.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/features/lighting/standalone.rs) | UI panel, camera-automation toggle |
| [`crates/openlogi-desktop/src/state/app_state.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/state/app_state.rs) | `camera_automation_active()`, `LightSettings` state |

## Summary

- **Three-layer architecture**: Device registry for detection, raw-HID driver for protocol encoding, agent/UI for high-level control and automation
- **Camera-linked automation**: Controlled by `LightSettings.auto_camera` boolean; when enabled, camera activity sets `light.enabled = true`, triggering background `Power(true)` commands
- **Concurrent safety**: Per-device async mutexes and coalescing background workers prevent write collisions
- **Protocol precision**: Fixed 20-byte reports with validated model-specific ranges for Glow and Beam variants

## Frequently Asked Questions

### What Litra models does OpenLogi support?

OpenLogi supports **Litra Glow** (USB PID `0xc900`) and **Litra Beam** (USB PID `0xc901`). The `find_litra` function in the device registry matches these identifiers along with the HID usage page `0xff43` and usage ID `0x0202` that Logitech firmware exposes.

### How does camera auto-power actually work?

The `auto_camera` flag gates whether the UI automatically sets `light.enabled = true` when `StateEvent::CameraChanged` fires. The background worker then re-applies settings and issues `LightCommand::Power(true)` to the Litra. The flag does not bypass the normal `LightSettings` → worker → HID driver pathway.

### Can I control Litra brightness and temperature without the UI?

Yes. The `openlogi` CLI provides `light brightness` and `light temperature` subcommands. Programmatically, use `apply_litra` with `LightCommand::BrightnessPercent(n)` or `LightCommand::TemperatureKelvin(k)` after resolving the `DeviceRoute` and `LitraModel`.

### What prevents conflicting commands when multiple operations target the Litra?

Two mechanisms ensure serialization: (1) `apply_light` acquires a per-device async mutex (`light_write_lock`) before calling `apply_light_unlocked`, and (2) the raw-HID driver's `apply` function obtains a `device_lock` before writing. Background workers further coalesce rapid setting changes through `LIGHT_WORKERS` to reduce HID traffic.