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:
- Discover effect list — enumerate supported effect IDs
- Select FixedColor effect — identify the static RGB engine
- Build parameter payload — pack R, G, B into first three bytes
- Choose persistence — volatile (temporary) or non-volatile (EEPROM)
- Send Set-Effect command —
set_zone_effectorset_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:
- Query device capabilities via
get_infoorget_device_info - Check for
RgbEffects(0x8071) first — preferred for newer devices - Fall back to
ColorLedEffects(0x8070) for legacy compatibility - 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) and0x8071(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.rsCLI 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →