OpenLogi

0x1815 · hostsInfo

检查 Easy-Switch 主机槽位、名称和操作系统,并在设备允许时重命名已配对的主机。

hostsInfo 描述多主机设备保存的主机通道。此功能报告通道配对状态、连接类型、名称元数据和原始描述符页。此功能也提供分页主机名称和操作系统数据。OpenLogi v0.8.15 可读取这些数据,并可重命名已配对通道。

**规格:**没有公开的 Logitech 规格。布局来自对 Logi Options+ 2.8 的静态分析。**交叉核对:**Solaar。用于:Easy-Switch。

函数参考

HostsInfoFeature 要求设备声明功能版本 2。此封装实现函数 0~4 和 7。v0.8.15 没有函数 5、6 和 8 的封装。

方法

函数HID++ fn签名返回值
get_feature_info0()HostsInfoFeatureInfo
get_host_info1(host: HostIndex)HostInfo
get_host_descriptor2(host: HostIndex, page: u8)HostDescriptorPage
get_host_friendly_name3(host: HostIndex, offset: u8)HostNameChunk
set_host_friendly_name4(host: HostIndex, offset: u8, bytes: [u8; HOST_NAME_CHUNK_LEN])()
get_host_os_version7(host: HostIndex)HostOsVersion

所有方法都是 async,并返回 Result<…, Hidpp20Error>。

类型

HostsInfoFeatureInfo

函数 0 返回此功能的静态信息。

字段类型说明
capabilitiesHostsInfoCapabilities线格式能力掩码的高字节。
descriptor_capabilitiesHostDescriptorCapabilities线格式能力掩码的低字节。
host_countu8主机通道数量。
current_hostHostIndex当前主机选择器或 Current。

HostInfo

函数 1 返回一个主机通道记录。

字段类型说明
host_indexHostIndex设备回显的选择器。
statusHostSlotStatus空槽或已配对状态。
bus_typeHostBusType连接类型。
page_countu8描述符页数量。
name_lenu8当前主机名称的字节长度。
name_max_lenu8主机名称的最大字节长度。

HostDescriptorPage

函数 2 返回一个原始描述符页。

字段类型说明
host_indexHostIndex设备回显的选择器。
bus_typeHostBusTypeOpenLogi 对页头高半字节的解码。
page_indexu8OpenLogi 对页头低半字节的解码。
body[u8; 14]原始描述符字节。

HostNameChunk

函数 3 返回固定长度的名称块。

字段类型说明
host_indexHostIndex设备回显的选择器。
offsetu8设备回显的字节偏移量。
bytes[u8; 14]名称字节及其后的填充。

HOST_NAME_CHUNK_LEN 的值为 14。

HostOsVersion

函数 7 返回保存的操作系统和版本。

字段类型说明
host_indexHostIndex设备回显的选择器。
os_typeHostOsType保存的操作系统类型。
majoru8主版本号。
minoru16次版本号,按 BE 解码。
patchu16修订版本号,按 BE 解码。

HostIndex

变体值说明
Current0xFF由设备选择的当前主机通道。
Slot(u8)0~254显式的零起始主机通道。

HostSlotStatus

变体值说明
Empty0通道没有已配对的主机。
Paired1通道有已配对的主机。

HostBusType

变体值说明
Undefined0未定义或未知的连接。
Equad1eQuad 无线连接。
Usb2USB 连接。
Bt3Bluetooth Classic 连接。
Ble4Bluetooth Low Energy 连接。
BlePro5BLE Pro / Logi Bolt 连接。

HostOsType

变体值说明
Unknown0没有保存操作系统。
Windows1Windows。
WindowsEmbedded2Windows Embedded。
Linux3Linux。
Chrome4ChromeOS。
Android5Android。
MacOs6macOS。
Ios7iOS 或 iPadOS。
WebOs8webOS。
Tizen9Tizen。

HostsInfoCapabilities

这些具名标志位于 BE 能力掩码的高字节。

标志位/值说明
GET_NAME1 << 0 / 0x0100可以读取主机名称。Solaar 对此标志作了交叉核对。
SET_NAME1 << 1 / 0x0200可以写入主机名称。Options+ 2.8 用此标志限制重命名。
MOVE_HOST1 << 2 / 0x0400可以移动主机槽位。Options+ 2.8 用此标志限制函数 5。
DELETE_HOST1 << 3 / 0x0800可以删除主机槽位。Options+ 2.8 用此标志限制函数 6。
SET_OS_VERSION1 << 4 / 0x1000可以写入主机操作系统版本。Options+ 2.8 用此标志限制函数 8。

HostDescriptorCapabilities

OpenLogi 将下列标志分配给能力掩码的低字节。

标志位/值说明
EQUAD1 << 0 / 0x0001OpenLogi 的 eQuad 描述符系列标志。
USB1 << 1 / 0x0002OpenLogi 的 USB 描述符系列标志。
BT1 << 2 / 0x0004OpenLogi 的 Bluetooth Classic 描述符系列标志。
BLE1 << 3 / 0x0008OpenLogi 的 Bluetooth Low Energy 描述符系列标志。

公开规格尚未独立确认低字节标志的名称。

事件

OpenLogi v0.8.15 不公开类型化的 HostsInfo 事件。已审阅的证据没有确定未经请求的 0x1815 报告布局。

线格式

此功能除函数 4 外均使用 3 字节短请求。函数 4 使用 16 字节长请求。OpenLogi 使用 extend_payload() 读取响应。

get_feature_info(fn 0)

请求:[0x00, 0x00, 0x00]。

字节字段备注
0~1capability_mask一个 BE u16。OpenLogi 将字节 0 和字节 1 公开为两个独立位标志。
0capabilitiescapability_mask 的高字节。未知位会保留。
1descriptor_capabilitiescapability_mask 的低字节。未知位会保留。
2host_count主机通道数量。
3current_host0xFF 会变为 HostIndex::Current。其他值会变为 Slot(n)。

OpenLogi 字段保留完整掩码。可用 u16::from_be_bytes([capabilities.bits(), descriptor_capabilities.bits()]) 重建掩码。

get_host_info(fn 1)

请求:[host, 0x00, 0x00]。host 对 HostIndex::Current 为 0xFF,否则为零起始通道号。

字节字段备注
0host_index回显的选择器。
1status0 是 Empty。1 是 Paired。其他值会导致解码失败。
2bus_type0=Undefined,1=Equad,2=Usb,3=Bt,4=Ble,5=BlePro。其他值会导致解码失败。
3page_count描述符页数量。
4name_len当前主机名称的字节长度。
5name_max_len主机名称的最大字节长度。

get_host_descriptor(fn 2)

请求:[host, page, 0x00]。

字节字段备注
0host_index回显的选择器。
1,位 7~4bus_typeOpenLogi 将高半字节解码为 HostBusType。此含义尚未确认。
1,位 3~0page_indexOpenLogi 将低半字节解码为页索引。此含义尚未确认。
2~15body14 个原始描述符字节。

get_host_friendly_name(fn 3)

请求:[host, offset, 0x00]。offset 是字节偏移量,不是页码。

字节字段备注
0host_index回显的选择器。
1offset回显的字节偏移量。
2~15bytes最多 14 个名称字节。必要时后接填充。

封装返回全部 14 字节。设备层按 name_len 读取名称块。设备层会在拼接名称块前验证返回的选择器和偏移量。

set_host_friendly_name(fn 4)

OpenLogi 发送 16 字节长请求,并等待确认。

字节字段备注
0host显式的零起始主机通道。
1offset主机名称缓冲区内的字节偏移量。
2~15bytes正好 14 个名称字节或填充字节。

原始封装会原样写入所提供的字节。设备层用 14 字节块写入全部 name_max_len 字节。设备层用零字节填充最后一块,从而清除旧名称的尾部。

move_host(fn 5)

OpenLogi v0.8.15 不公开此函数。Options+ 2.8 用两个请求字节移动主机。

字节字段备注
0源主机源主机通道。Options+ 2.8。
1目标主机目标主机通道。Options+ 2.8。
2/未恢复其含义。

Options+ 2.8 用掩码位 0x0400 限制此操作。未恢复确认布局。

delete_host(fn 6)

OpenLogi v0.8.15 不公开此函数。Options+ 2.8 删除一个主机通道。

字节字段备注
0host要删除的主机通道。Options+ 2.8。
1~2/未恢复其含义。

Options+ 2.8 用掩码位 0x0800 限制此操作。未恢复确认布局。

get_host_os_version(fn 7)

请求:[host, 0x00, 0x00]。

字节字段备注
0host_index回显的选择器。
1os_typeHostOsType 值。未知值会导致解码失败。
2major操作系统主版本号。
3~4minorBE u16。
5~6patchBE u16。

set_host_os_version(fn 8)

OpenLogi v0.8.15 不公开此函数。Options+ 2.8 写入下列 7 个字节。

字节字段备注
0host主机通道。Options+ 2.8。
1os_type操作系统类型。Options+ 2.8。
2major操作系统主版本号。Options+ 2.8。
3~4minorBE u16。Options+ 2.8。
5~6patchBE u16。Options+ 2.8。
7…/未恢复其布局。

Options+ 2.8 用掩码位 0x1000 限制此操作。

OpenLogi 的用法

OpenLogi 只在 HostsInfo 的功能版本为 2 时创建封装。读取 Easy-Switch 数据时,OpenLogi 先读取函数 0,再为 0..host_count 内的每个索引读取函数 1。OpenLogi 只为已配对通道读取函数 3 和 7。OpenLogi 将总线类型映射为面向用户的连接类型。

设备可以拒绝函数 3 或 7。此时,OpenLogi 会将名称或操作系统保留为空。超时、传输失败,或函数 3 和函数 7 的回显不匹配,都会使完整读取失败。

重命名前,OpenLogi 会读取完整通道表。OpenLogi 要求 SET_NAME、已配对的目标通道、非空的修剪后 UTF-8 名称,以及不超过 name_max_len 的字节长度。OpenLogi 通过函数 4 写入完整的零填充名称缓冲区。随后,OpenLogi 再次读取通道表,并拒绝设备未保留的名称。

OpenLogi 不会自动写入主机名称或操作系统版本。OpenLogi 不调用函数 5、6 或 8。面向用户的 Easy-Switch 页面说明此卡片和 Automation API。

未确认的细节

Options+ 2.8 将函数 0 的字节 0~1 读取为一个 BE u16 掩码。OpenLogi 将相同的两个字节公开为两个 u8 位标志集合,因此不会丢失线格式位。低字节描述符标志的语义名称尚未确认。

OpenLogi 将函数 2 的页头解码为总线类型半字节和页索引半字节。已审阅的静态证据没有独立确认这两个半字节的含义。

Options+ 2.8 确定了函数 5、6 和 8 及其观察到的请求字段。公开规格和硬件抓包都未确认其余字节、确认响应或设备支持。OpenLogi 有意不公开这些持久操作。

Rust 用法

use hidpp::feature::hosts_info::{HostIndex, HostSlotStatus, HostsInfoFeature};

// `device` 是已初始化的可变 HID++ 设备。
device.enumerate_features().await?;
if let Some(feature) = device.get_feature::<HostsInfoFeature>() {
    let feature_info = feature.get_feature_info().await?;

    for slot in 0..feature_info.host_count {
        let host = feature.get_host_info(HostIndex::Slot(slot)).await?;
        if host.status == HostSlotStatus::Paired {
            let name = feature
                .get_host_friendly_name(HostIndex::Slot(slot), 0)
                .await?;
            let os = feature.get_host_os_version(HostIndex::Slot(slot)).await?;
            println!("slot {slot}: {:?}, {:?}", name.bytes, os.os_type);
        }
    }
}

本页目录