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).RgbZone—zone_id+color; sentinel IDs are rejected at call time.RgbZoneRange—first_zone_id,last_zone_id,color(inclusive).ZonePresencePage—Zones0To111,Zones112To223,Zones224To255.FramePersistence—Volatile(RAM only) orVolatileAndNonVolatile(RAM + EEPROM).
Spec: Logitech HID++ 2.0 — perKeyLighting. Used by: Typed wrapper in
openlogi-hidpp.
Function reference
The PerKeyLightingFeature wrapper (0x8081) exposes:
Methods
| Function | HID++ fn | Signature | Returns |
|---|---|---|---|
get_rgb_zone_presence | 0 | (page: ZonePresencePage) | [u8; 14] |
set_individual_rgb_zones | 1 | (zones: &[RgbZone]) | () |
set_consecutive_rgb_zones | 2 | (first_zone_id: u8, colors: [Rgb; 5]) | () |
set_consecutive_rgb_zones_delta_5bit | 3 | (first_zone_id: u8, packed: [u8; 15]) | () |
set_consecutive_rgb_zones_delta_4bit | 4 | (first_zone_id: u8, packed: [u8; 15]) | () |
set_range_rgb_zones | 5 | (ranges: &[RgbZoneRange]) | () |
set_rgb_zones_single_value | 6 | (color: Rgb, zone_ids: &[u8]) | () |
frame_end | 7 | (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.
| Field | Type | Description |
|---|---|---|
red | u8 | Red channel. |
green | u8 | Green channel. |
blue | u8 | Blue channel. |
RgbZone
A single zone and the color to apply to it.
| Field | Type | Description |
|---|---|---|
zone_id | u8 | Zone identifier (0 and 255 are reserved end-of-list sentinels). |
color | Rgb | Color to apply. |
RgbZoneRange
A contiguous range of zones to fill with one color.
| Field | Type | Description |
|---|---|---|
first_zone_id | u8 | First zone identifier in the range (inclusive). |
last_zone_id | u8 | Last zone identifier in the range (inclusive). |
color | Rgb | Color to apply across the range. |
ZonePresencePage
Which page of zone IDs a presence query covers.
| Variant | Value | Description |
|---|---|---|
Zones0To111 | 0 | Zone IDs 0..=111. |
Zones112To223 | 1 | Zone IDs 112..=223. |
Zones224To255 | 2 | Zone IDs 224..=255. |
FramePersistence
Storage persistence for frame_end.
| Variant | Value | Description |
|---|---|---|
Volatile | 0 | Volatile: applied to RAM only. |
VolatileAndNonVolatile | 1 | Applied 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):
| Bytes | Field | Notes |
|---|---|---|
| 2–15 | zone presence bitfield | 14 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.
| Bytes | Field | Notes |
|---|---|---|
slot*4 + 0 | zone_id | Zone identifier for slot (0–3) |
slot*4 + 1 | red | Red channel |
slot*4 + 2 | green | Green channel |
slot*4 + 3 | blue | Blue channel |
Response: none (ack only).
set_consecutive_rgb_zones (fn 2)
Request: 16-byte long payload for exactly 5 sequential zones.
| Bytes | Field | Notes |
|---|---|---|
| 0 | first_zone_id | Starting zone ID |
1 + i*3 | red of zone i | i = 0..4 |
2 + i*3 | green of zone i | |
3 + i*3 | blue 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.
| Bytes | Field | Notes |
|---|---|---|
| 0 | first_zone_id | Starting zone ID |
| 1–15 | packed | 15-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.
| Bytes | Field | Notes |
|---|---|---|
| 0 | first_zone_id | Starting zone ID |
| 1–15 | packed | 15-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.
| Bytes | Field | Notes |
|---|---|---|
slot*5 + 0 | first_zone_id | First zone ID in range (inclusive), slot 0–2 |
slot*5 + 1 | last_zone_id | Last zone ID in range (inclusive) |
slot*5 + 2 | red | Red channel |
slot*5 + 3 | green | Green channel |
slot*5 + 4 | blue | Blue 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.
| Bytes | Field | Notes |
|---|---|---|
| 0 | red | Red channel of the single color |
| 1 | green | Green channel |
| 2 | blue | Blue channel |
| 3–15 | zone IDs | Up 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.
| Bytes | Field | Notes |
|---|---|---|
| 0 | persistence | FramePersistence discriminant (0 = volatile, 1 = volatile + non-volatile) |
| 1 | current_frame high byte | Big-endian u16 |
| 2 | current_frame low byte | |
| 3 | frames_till_next_change high byte | Big-endian u16 |
| 4 | frames_till_next_change low byte | |
| 5–15 | — | Zeroed 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?;
}