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

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 (lines 34-96). This implementation targets older Logitech keyboards and mice with zone-based LED grouping.

Complete Implementation Example

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 (lines 27-48). This engine supports finer-grained control with power-mode targeting for battery-powered devices.

Complete Implementation Example

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 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 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:

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 (lines 34-96), rgb_effects.rs (lines 27-48), 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 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.

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 →