# How to Configure Static RGB Lighting Using HID++ Features 0x8070 and 0x8071 in OpenLogi

> Learn to configure static RGB lighting in OpenLogi using HID++ features 0x8070 and 0x8071. Discover the FixedColor effect and send RGB payloads for custom lighting.

- Repository: [Xuan Zhang/OpenLogi](https://github.com/AprilNEA/OpenLogi)
- Tags: how-to-guide
- Published: 2026-09-06

---

**Static RGB lighting in OpenLogi requires discovering the FixedColor effect from your device's HID++ 2.0 feature set and sending a three-byte RGB payload with appropriate persistence settings.**

OpenLogi provides Rust-based APIs for controlling Logitech RGB devices through two distinct HID++ feature IDs. This guide walks you through the complete implementation for both the legacy `ColorLedEffects` (0x8070) and modern `RgbEffects` (0x8071) engines. All code examples reference the actual OpenLogi source implementation, ensuring production-ready integration.

## Understanding the Two HID++ RGB Features

Logitech devices implement RGB control through one of two feature engines. You must probe your device to determine which feature ID it exposes.

| Feature ID | Feature Name | Device Generation | Granularity |
|------------|--------------|-------------------|-------------|
| `0x8070` | **ColorLedEffects** | Legacy (older keyboards, mice) | Per-zone lighting |
| `0x8071` | **RgbEffects** | Modern (newer devices) | Per-cluster lighting |

Both features follow an identical five-step workflow:

1. **Discover effect list** — enumerate supported effect IDs
2. **Select FixedColor effect** — identify the static RGB engine
3. **Build parameter payload** — pack R, G, B into first three bytes
4. **Choose persistence** — volatile (temporary) or non-volatile (EEPROM)
5. **Send Set-Effect command** — `set_zone_effect` or `set_rgb_cluster_effect`

## Implementing Static Color with Feature 0x8070 (ColorLedEffects)

The legacy zone-based API lives in [`crates/openlogi-hidpp/src/feature/color_led_effects.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hidpp/src/feature/color_led_effects.rs) (lines 34-96). This implementation targets older Logitech keyboards and mice with zone-based LED grouping.

### Complete Implementation Example

```rust
use openlogi_hidpp::feature::color_led_effects::{
    ColorLedEffectsFeature, Persistence, ZONE_EFFECT_PARAM_COUNT,
};
use openlogi_hidpp::hidpp20::Hidpp20Device;

async fn set_static_color_0x8070(
    device: &Hidpp20Device,
    zone: u8,
    r: u8,
    g: u8,
    b: u8,
) -> anyhow::Result<()> {
    // Step 1: Initialize the feature object
    let color_feat = ColorLedEffectsFeature::new(device.clone());

    // Step 2: Discover the FixedColor effect index
    let mut fixed_effect_idx = None;
    for idx in 0..=0x0f {
        let info = color_feat.get_zone_effect_info(zone, idx).await?;
        if info.effect_id == openlogi_hidpp::feature::color_led_effects::types::EffectId::FixedColor as u8 {
            fixed_effect_idx = Some(idx);
            break;
        }
    }
    let effect_idx = fixed_effect_idx
        .ok_or_else(|| anyhow::anyhow!("FixedColor not supported on this device"))?;

    // Step 3: Build parameter buffer (R, G, B, remaining bytes zeroed)
    let mut params = [0u8; ZONE_EFFECT_PARAM_COUNT];
    params[0] = r;
    params[1] = g;
    params[2] = b;

    // Step 4 & 5: Apply with volatile persistence (immediate, non-persistent)
    color_feat
        .set_zone_effect(zone, effect_idx, params, Persistence::Volatile)
        .await?;

    Ok(())
}

```

**Key constants from source:**
- `ZONE_EFFECT_PARAM_COUNT` — size of the parameter buffer (typically 16 bytes)
- `EffectId::FixedColor` — the static color effect identifier

## Implementing Static Color with Feature 0x8071 (RgbEffects)

The modern cluster-based API resides in [`crates/openlogi-hidpp/src/feature/rgb_effects.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hidpp/src/feature/rgb_effects.rs) (lines 27-48). This engine supports finer-grained control with power-mode targeting for battery-powered devices.

### Complete Implementation Example

```rust
use openlogi_hidpp::feature::rgb_effects::{
    RgbEffectsFeature, RgbPersistence, PowerModeTarget, CLUSTER_EFFECT_PARAM_COUNT,
};
use openlogi_hidpp::hidpp20::Hidpp20Device;

async fn set_static_color_0x8071(
    device: &Hidpp20Device,
    cluster: u8,
    r: u8,
    g: u8,
    b: u8,
) -> anyhow::Result<()> {
    // Initialize feature
    let rgb_feat = RgbEffectsFeature::new(device.clone());

    // Discover FixedColor effect
    let mut fixed_idx = None;
    for idx in 0..=0x0f {
        let info = rgb_feat.get_effect_info(cluster, idx).await?;
        if info.effect_id == openlogi_hidpp::feature::rgb_effects::types::EffectId::FixedColor as u8 {
            fixed_idx = Some(idx);
            break;
        }
    }
    let effect_idx = fixed_idx
        .ok_or_else(|| anyhow::anyhow!("FixedColor not supported on this cluster"))?;

    // Build RGB payload
    let mut params = [0u8; CLUSTER_EFFECT_PARAM_COUNT];
    params[0] = r;
    params[1] = g;
    params[2] = b;

    // Apply with volatile persistence and active power mode
    rgb_feat
        .set_rgb_cluster_effect(
            cluster,
            effect_idx,
            params,
            RgbPersistence::Volatile,
            PowerModeTarget::Active,
        )
        .await?;

    Ok(())
}

```

**Critical difference from 0x8070:** The `set_rgb_cluster_effect` method requires a `PowerModeTarget` parameter. Use `PowerModeTarget::Active` for standard powered operation, or `PowerModeTarget::Suspend` to configure colors visible during standby.

## Persistence and EEPROM Considerations

Both features support volatile and non-volatile persistence modes. Choose based on your use case:

| Persistence Mode | 0x8070 Constant | 0x8071 Constant | Behavior |
|------------------|-----------------|-----------------|----------|
| Volatile (recommended for testing) | `Persistence::Volatile` | `RgbPersistence::Volatile` | Applies immediately, lost on reboot |
| Non-volatile (permanent storage) | `Persistence::NonVolatile` | `RgbPersistence::NonVolatile` | Written to EEPROM, survives power cycles |

**Warning:** Non-volatile writes consume limited EEPROM write cycles. OpenLogi's CLI wrapper in [`crates/openlogi-cli/src/cmd/light.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-cli/src/cmd/light.rs) defaults to volatile persistence for this reason.

## Device Detection and Feature Selection

OpenLogi's higher-level driver automatically selects the appropriate feature. Reference [`crates/openlogi-device/src/write/lighting.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-device/src/write/lighting.rs) for the detection logic:

1. Query device capabilities via `get_info` or `get_device_info`
2. Check for `RgbEffects` (0x8071) first — preferred for newer devices
3. Fall back to `ColorLedEffects` (0x8070) for legacy compatibility
4. Cache the feature handle for subsequent operations

Your application should implement similar capability detection rather than hardcoding feature IDs.

## Common Pitfalls and Error Handling

### Effect Discovery Failure

Not all devices expose `FixedColor`. The discovery loop may complete without finding a valid index. Always handle `None` results with meaningful errors:

```rust
let effect_idx = fixed_effect_idx
    .ok_or_else(|| anyhow::anyhow!("Device does not support static RGB"))?;

```

Attempting to use an unsupported effect index returns `Hidpp20Error::UnsupportedResponse` from the device firmware.

### Zone/Cluster Mismatch

Using zone indices with `RgbEffects` or cluster indices with `ColorLedEffects` produces undefined behavior. Verify your addressing matches the detected feature.

### Power Mode Confusion (0x8071 Only)

Omitting `PowerModeTarget` or using `Suspend` unintentionally may cause colors to appear only during low-power states. Explicitly specify `PowerModeTarget::Active` for standard visibility.

## Summary

- **Two feature IDs control RGB:** `0x8070` (legacy zones) and `0x8071` (modern clusters)
- **Always discover effects:** Scan indices 0x00-0x0f to locate `FixedColor`
- **RGB payload format:** Three bytes `[R, G, B]` at positions 0-2, remainder zeroed
- **Prefer volatile persistence:** Avoid EEPROM wear during development and testing
- **Source references:** [`color_led_effects.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/color_led_effects.rs) (lines 34-96), [`rgb_effects.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/rgb_effects.rs) (lines 27-48), [`light.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/light.rs) CLI wrapper

## Frequently Asked Questions

### How do I know which HID++ feature my device supports?

Query the device's feature table using `Hidpp20Device::get_feature` or examine `get_device_info` output. OpenLogi's [`crates/openlogi-device/src/write/lighting.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-device/src/write/lighting.rs) implements automatic detection — the `RgbEffects` feature (0x8071) takes precedence when present, with fallback to `ColorLedEffects` (0x8070).

### Why does my static color disappear after unplugging the device?

You likely used volatile persistence. Change `Persistence::Volatile` to `Persistence::NonVolatile` (or the `RgbPersistence` equivalent) to write the configuration to EEPROM. Limit non-volatile writes to avoid wearing out the device's flash memory.

### Can I set different colors for individual keys rather than zones or clusters?

Per-key control requires devices with high-density cluster mapping via `RgbEffects` (0x8071). The `ColorLedEffects` (0x8070) engine only supports coarse zone granularity. Check your device's technical specifications or probe cluster count through `RgbEffectsFeature::get_info`.