OpenLogi

0x8081 · perKeyLighting

Per-key (per-zone) RGB lighting control — stage colours across up to 256 individually addressed zones and commit them with frame timing and optional EEPROM persistence.

Per-key (per-zone) RGB lighting control. Zone colours are staged with one of six setter functions and then committed to the display via frame_end, which also sets frame-animation timing (current_frame / frames_till_next_change) and whether the result is stored in EEPROM. get_rgb_zone_presence queries which zone IDs exist on the device, 112 zones at a time across three pages.

The six setters trade capacity for addressing flexibility: set_individual_rgb_zones handles up to 4 arbitrary zones; set_consecutive_rgb_zones sets 5 sequential zones in full 24-bit colour; the delta variants compress 8 zones (5-bit signed deltas) or 10 zones (4-bit signed deltas) into a single request; set_range_rgb_zones fills up to 3 inclusive ranges with one colour each; set_rgb_zones_single_value paints up to 13 arbitrary zones with one colour. Zone IDs 0x00 and 0xFF are reserved end-of-list sentinels.

  • Rgb — 8-bit-per-channel colour (red, green, blue).
  • RgbZonezone_id + color; sentinel IDs are rejected at call time.
  • RgbZoneRangefirst_zone_id, last_zone_id, color (inclusive).
  • ZonePresencePageZones0To111, Zones112To223, Zones224To255.
  • FramePersistenceVolatile (RAM only) or VolatileAndNonVolatile (RAM + EEPROM).

Spec: Logitech HID++ 2.0 — perKeyLighting. Used by: Typed wrapper in openlogi-hidpp.

Function reference

The PerKeyLightingFeature wrapper (0x8081) exposes:

Methods

FunctionHID++ fnSignatureReturns
get_rgb_zone_presence0(page: ZonePresencePage)[u8; 14]
set_individual_rgb_zones1(zones: &[RgbZone])()
set_consecutive_rgb_zones2(first_zone_id: u8, colors: [Rgb; 5])()
set_consecutive_rgb_zones_delta_5bit3(first_zone_id: u8, packed: [u8; 15])()
set_consecutive_rgb_zones_delta_4bit4(first_zone_id: u8, packed: [u8; 15])()
set_range_rgb_zones5(ranges: &[RgbZoneRange])()
set_rgb_zones_single_value6(color: Rgb, zone_ids: &[u8])()
frame_end7(persistence: FramePersistence, current_frame: u16, frames_till_next_change: u16)()

All methods are async and return Result<…, Hidpp20Error>.

Types

Rgb

An 8-bit-per-channel RGB color.

FieldTypeDescription
redu8Red channel.
greenu8Green channel.
blueu8Blue channel.

RgbZone

A single zone and the color to apply to it.

FieldTypeDescription
zone_idu8Zone identifier (0 and 255 are reserved end-of-list sentinels).
colorRgbColor to apply.

RgbZoneRange

A contiguous range of zones to fill with one color.

FieldTypeDescription
first_zone_idu8First zone identifier in the range (inclusive).
last_zone_idu8Last zone identifier in the range (inclusive).
colorRgbColor to apply across the range.

ZonePresencePage

Which page of zone IDs a presence query covers.

VariantValueDescription
Zones0To1110Zone IDs 0..=111.
Zones112To2231Zone IDs 112..=223.
Zones224To2552Zone IDs 224..=255.

FramePersistence

Storage persistence for frame_end.

VariantValueDescription
Volatile0Volatile: applied to RAM only.
VolatileAndNonVolatile1Applied to RAM and stored in EEPROM.

Wire format

Getter functions use a short (3-byte) request payload and return a long (16-byte) response payload read via extend_payload(). All setter functions (fn 1–7) use a long (16-byte) request payload and return no meaningful response bytes.

get_rgb_zone_presence (fn 0)

Request: [ 0x00, page, 0x00 ] — byte 0 = typeOfInfo (always 0x00), byte 1 = ZonePresencePage discriminant, byte 2 = padding.

Response (byte → field):

BytesFieldNotes
2–15zone presence bitfield14 bytes; bit i (LSB-first within each byte) = zone page_base + i exists

set_individual_rgb_zones (fn 1)

Request: 16-byte long payload, up to 4 slots of 4 bytes each. Unused slots are zeroed.

BytesFieldNotes
slot*4 + 0zone_idZone identifier for slot (0–3)
slot*4 + 1redRed channel
slot*4 + 2greenGreen channel
slot*4 + 3blueBlue channel

Response: none (ack only).

set_consecutive_rgb_zones (fn 2)

Request: 16-byte long payload for exactly 5 sequential zones.

BytesFieldNotes
0first_zone_idStarting zone ID
1 + i*3red of zone ii = 0..4
2 + i*3green of zone i
3 + i*3blue of zone i

Response: none (ack only).

set_consecutive_rgb_zones_delta_5bit (fn 3)

Request: 16-byte long payload — 8 consecutive zones encoded as 5-bit signed per-channel deltas.

BytesFieldNotes
0first_zone_idStarting zone ID
1–15packed15-byte verbatim delta payload (8×3 5-bit deltas, MSB-first, zone-then-channel)

Response: none (ack only).

set_consecutive_rgb_zones_delta_4bit (fn 4)

Request: 16-byte long payload — 10 consecutive zones encoded as 4-bit signed per-channel deltas.

BytesFieldNotes
0first_zone_idStarting zone ID
1–15packed15-byte verbatim delta payload (10×3 4-bit signed deltas, two per byte, high nibble first)

Response: none (ack only).

set_range_rgb_zones (fn 5)

Request: 16-byte long payload, up to 3 slots of 5 bytes each. Unused slots are zeroed.

BytesFieldNotes
slot*5 + 0first_zone_idFirst zone ID in range (inclusive), slot 0–2
slot*5 + 1last_zone_idLast zone ID in range (inclusive)
slot*5 + 2redRed channel
slot*5 + 3greenGreen channel
slot*5 + 4blueBlue channel

Response: none (ack only).

set_rgb_zones_single_value (fn 6)

Request: 16-byte long payload — one color applied to up to 13 individually addressed zones.

BytesFieldNotes
0redRed channel of the single color
1greenGreen channel
2blueBlue channel
3–15zone IDsUp to 13 zone IDs; unused bytes zeroed

Response: none (ack only).

frame_end (fn 7)

Request: 16-byte long payload — commits all staged zone changes.

BytesFieldNotes
0persistenceFramePersistence discriminant (0 = volatile, 1 = volatile + non-volatile)
1current_frame high byteBig-endian u16
2current_frame low byte
3frames_till_next_change high byteBig-endian u16
4frames_till_next_change low byte
5–15Zeroed padding

Response: none (ack only).

Usage (Rust)

use hidpp::{device::Device, feature::per_key_lighting::{
    FramePersistence, PerKeyLightingFeature, Rgb, RgbZone, ZonePresencePage,
}};

// mut device: Device, already created via Device::new(channel, index)
device.enumerate_features().await?;
if let Some(feat) = device.get_feature::<PerKeyLightingFeature>() {
    // Query which zone IDs are present on this device (first 112 zones)
    let presence = feat.get_rgb_zone_presence(ZonePresencePage::Zones0To111).await?;

    // Stage two individual zones: zone 1 = red, zone 2 = blue
    feat.set_individual_rgb_zones(&[
        RgbZone { zone_id: 1, color: Rgb { red: 0xff, green: 0x00, blue: 0x00 } },
        RgbZone { zone_id: 2, color: Rgb { red: 0x00, green: 0x00, blue: 0xff } },
    ]).await?;

    // Commit the staged changes to RAM only, one-shot (no animation)
    feat.frame_end(FramePersistence::Volatile, 0, 0).await?;
}

On this page