OpenLogi

0x8081 · perKeyLighting

逐键 RGB 灯光控制,通过 7 个 HID++ 函数暂存区域更新,再由帧提交选择是否写入非易失性存储。

0x8081 是面向带可寻址 LED 键盘的 0x8080 区域寻址后继 feature。 它先暂存区域颜色,再由 frame_end 提交完整帧。

**规格:**没有公开的 Logitech 规格。布局由 Logi Options+ 2.8 静态分析逆向得出。**交叉核对:**Solaar。用于:键盘灯光。

函数参考

PerKeyLightingFeature 从 feature 版本 0 开始实现 0x8081。 所有方法都是 async,并返回 Result<…, Hidpp20Error>。

方法

函数HID++ fn签名返回值
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)()

individual、range 和 single-value setter 最多分别发送 4、3 和 13 个切片条目。 它们会忽略超过这些上限的条目。

类型

Rgb

Rgb 为每个颜色通道保存一个 8-bit 值。

字段类型说明
redu8红色通道。
greenu8绿色通道。
blueu8蓝色通道。

RgbZone

RgbZone 将一个 Rgb 值分配给一个区域。

字段类型说明
zone_idu8区域 ID。0x00 和 0xff 是列表结束哨兵值,调用时会被拒绝。
colorRgb要暂存的颜色。

RgbZoneRange

RgbZoneRange 将一种颜色分配给一个闭区间范围。

字段类型说明
first_zone_idu8包含在范围内的第一个区域 ID。哨兵 ID 会被拒绝。
last_zone_idu8包含在范围内的最后一个区域 ID。哨兵 ID 会被拒绝。
colorRgb要在范围内暂存的颜色。

ZonePresencePage

ZonePresencePage 选择一个 112-bit 的区域存在位图页。 这个非穷尽枚举有下列已知变体。

变体值说明
Zones0To1110区域 ID 0..=111。
Zones112To2231区域 ID 112..=223。
Zones224To2552区域 ID 224..=255。

FramePersistence

FramePersistence 选择 frame_end 的存储目标。 这个非穷尽枚举有下列已知变体。

变体值说明
Volatile0只在 RAM 中应用帧。
VolatileAndNonVolatile1在 RAM 中应用帧,并写入 EEPROM。

事件

OpenLogi 没有定义 0x8081 专属的 feature 事件类型。 frame_end 是请求,不是设备事件。

线格式

函数 0 发送 3 字节短载荷。 函数 1 至 7 发送 16 字节长载荷。

setter 回复会重复已接受的请求参数。 OpenLogi 要求函数 1 至 6 返回长回复,并检查区域 ID 回显。

get_rgb_zone_presence(fn 0)

请求载荷为 [0x00, page, 0x00]。 字节 0 选择区域存在信息,字节 1 选择页,字节 2 是零填充。

字节字段编码备注
0–1回复前缀由设备返回OpenLogi 不建模这些字节。
2–15区域存在位图14 字节;每个字节内 LSB-first位 i 对应区域 page_base + i。Options+ 2.8

set_individual_rgb_zones(fn 1)

请求有 4 个四字节槽位。 OpenLogi 会将未使用的槽位清零。

字节字段编码备注
slot × 4zone_id一个 u8槽位为 0..=3;发送的 ID 不能为 0x00 或 0xff。
slot × 4 + 1red一个 u8/
slot × 4 + 2green一个 u8/
slot × 4 + 3blue一个 u8/

回复必须为长回复。 OpenLogi 会验证偏移 0、4、8 和 12 处每个已发送的 zone_id。

set_consecutive_rgb_zones(fn 2)

请求以完整 24-bit RGB 设置恰好 5 个连续区域。

字节字段编码备注
0first_zone_id一个 u8哨兵 ID 会被拒绝。
1 + 3 × i区域 i 的红色一个 u8i 为 0..=4。
2 + 3 × i区域 i 的绿色一个 u8/
3 + 3 × i区域 i 的蓝色一个 u8/

回复必须为长回复。 OpenLogi 会验证字节 0 处回显的 first_zone_id。

set_consecutive_rgb_zones_delta_5bit(fn 3)

请求将 8 个连续区域编码为有符号 5-bit 通道差分值。 差分值按区域、再按通道以 MSB-first 顺序打包。

字节字段编码备注
0first_zone_id一个 u8哨兵 ID 会被拒绝。
1–15packed15 字节8 组,每组包含 3 个有符号 5-bit 差分值。OpenLogi 原样发送这些字节。

回复必须为长回复。 OpenLogi 会验证字节 0 处回显的 first_zone_id。

set_consecutive_rgb_zones_delta_4bit(fn 4)

请求将 10 个连续区域编码为有符号 4-bit 通道差分值。 差分值先使用高半字节,再使用低半字节。

字节字段编码备注
0first_zone_id一个 u8哨兵 ID 会被拒绝。
1–15packed15 字节10 组,每组包含 3 个有符号 4-bit 差分值。OpenLogi 原样发送这些字节。

回复必须为长回复。 OpenLogi 会验证字节 0 处回显的 first_zone_id。

set_range_rgb_zones(fn 5)

请求有 3 个五字节范围槽位。 OpenLogi 会将未使用的槽位清零。

字节字段编码备注
slot × 5first_zone_id一个 u8槽位为 0..=2;哨兵 ID 会被拒绝。
slot × 5 + 1last_zone_id一个 u8闭区间末尾;哨兵 ID 会被拒绝。
slot × 5 + 2red一个 u8/
slot × 5 + 3green一个 u8/
slot × 5 + 4blue一个 u8/
15填充0x003 个槽位占用字节 0–14。

回复必须为长回复。 OpenLogi 只验证字节 0 处第一个已发送的 first_zone_id。

set_rgb_zones_single_value(fn 6)

请求将一种颜色应用到最多 13 个指定区域。 OpenLogi 会将未使用的区域 ID 字节清零。

字节字段编码备注
0red一个 u8/
1green一个 u8/
2blue一个 u8/
3–15zone_ids最多 13 × u8发送的 ID 不能为 0x00 或 0xff。

回复必须为长回复。 OpenLogi 会验证字节 3 处第一个已发送的 zone_id。

frame_end(fn 7)

frame_end 会提交所有暂存的区域更新。 若要单次更新,请将两个计数器都设为零。

字节字段编码备注
0persistence0 或 1FramePersistence 值。
1–2current_frameBE u16帧号。
3–4frames_till_next_changeBE u16到下次变化前的帧数。
5–15填充0x00OpenLogi 写入零。

OpenLogi 会等待函数 7 的匹配回复。 它不检查回复载荷。

OpenLogi 的用法

当 0x8070 和 0x8071 无法提供完整的纯色路径时,LightingMethod::Auto 会使用 0x8081。 LightingMethod::PerKeyV2 会直接选择此路径。

OpenLogi 会在写入前查询全部 3 个区域存在页。 它会跳过哨兵 ID 0x00、0xff,以及第 3 页中大于 0xff 的填充。

设备写入器会为每 13 个存在的区域发送一个函数 6 请求。 它会为所有存在区域写入相同颜色,然后以 Volatile、0 和 0 调用函数 7。

任一子写入失败都会阻止 frame_end。 如果 0x8081 不存在或没有可用区域,Auto 会回退到原始 0x8080。

存在 0x8071 时,OpenLogi 会在修改设备前读取软件控制状态。 它会申请 ALL_CLUSTERS、将可用 Static effect 写为底色、发送 0x8081 帧,并在新申请后重放一次。

写入失败会恢复之前的软件控制标志。 agent 写入成功后会保留租约,并在 agent 退出时交还控制权。

仅有 0x8081 而没有 0x8071 的设备会在不申请软件控制权的情况下接收帧。 用户可在键盘灯光中使用此设置。

类型化封装公开了全部 6 个区域 setter。 生产设备路径只使用 set_rgb_zones_single_value,因此它提供的是整键盘纯色,不是用户可见的逐区域映射。

未确认的细节

没有硬件捕获可验证哪些设备需要 0x8071 软件控制和 Static effect,才能显示 0x8081 颜色。 Solaar 将逐键写入视为 Static 子模式,而 OpenLogi 只在存在 0x8071 时采用该顺序。

Options+ 2.8 静态分析支持函数 1 至 6 的区域回显。 完整的多范围回复协议仍未确认,因此 OpenLogi 对函数 5 只检查第一个范围起点。

Options+ 2.8 不检查函数 7 的回复载荷。 在硬件验证前,OpenLogi 也接受任意匹配的函数 7 回复。

Rust 用法

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

async fn stage_two_zones(device: &mut Device) -> Result<(), Hidpp20Error> {
    device.enumerate_features().await?;
    let Some(feature) = device.get_feature::<PerKeyLightingFeature>() else {
        return Ok(());
    };

    let _presence = feature
        .get_rgb_zone_presence(ZonePresencePage::Zones0To111)
        .await?;

    feature
        .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?;

    feature.frame_end(FramePersistence::Volatile, 0, 0).await
}

本页目录