接收器
无线 Logitech 设备配对所用的 USB 接收器,通过 HID++ 1.0 寄存器驱动,以 USB 厂商/产品 ID 识别。
接收器(receiver) 是无线 Logitech 设备配对所用的 USB 适配器。OpenLogi 在其 USB/HID 通道上检测接收器,再枚举与之配对的设备。
与设备功能(走 HID++ 2.0)不同,接收器通过 HID++ 1.0 寄存器驱动,并且始终以
设备索引 0xFF(RECEIVER_DEVICE_INDEX)寻址。识别只看 USB 厂商/产品 ID ——
openlogi-hidpp 的 receiver::detect() 将通道的 VID/PID 与已知集合匹配,返回一个
Receiver。
实现状态
vendor 进来的 hidpp crate 对接收器的支持刻意保守 —— 公开文档很稀少。Logi
Bolt 最完整(基于真实硬件开发并验证);Unifying 覆盖发现与枚举,但配对/管理
面较薄,欢迎硬件实测反馈。
检测
| 接收器 | VID:PID | 传输方式 |
|---|---|---|
| Logi Bolt | 046D:C548 | HID++ 1.0 寄存器(基于 BLE) |
| Unifying | 046D:C52B、046D:C532、046D:C537、046D:C539 | HID++ 1.0 寄存器(Unifying / DJ) |
| Lightspeed | 046D:C53F、046D:C547 | Unifying 寄存器,G 系列适配器 |
receiver::detect(chan) 对已知适配器返回 Some(Receiver),否则返回 None。
G 系列设备附带的 Lightspeed 适配器应答与 Unifying 相同的 HID++ 1.0 寄存器,
因此检测、枚举、路由、配对都走 Unifying 代码路径 —— 只有面向用户的名称不同
(Lightspeed Receiver)。C53F 是 G305 等无线鼠标的 nano 接收器,已用 G305
实测(配对设备 wpid 0x4074);C547 随 G915 键盘、G502 X LIGHTSPEED 等较新设备
出货,已用 G915 实测(wpid 0x407c)。C539 是 Lightspeed 游戏接收器,因按
Unifying 路由而与 Unifying 的 PID 列在一起。C537 是 G602 附带的 Nano 接收器,
应答相同的枚举与配对信息寄存器,同样按 Unifying 路由。
通用 API
Receiver 枚举包装具体的接收器类型,暴露共享接口:
| 项 | 说明 |
|---|---|
Receiver::Bolt(_) / Receiver::Unifying(_) | 检测到的接收器类型。 |
name() | 人类可读的名称(如 "Logi Bolt Receiver")。 |
get_unique_id() | 唯一标识该接收器的字符串(序列号或等价物)。 |
RECEIVER_DEVICE_INDEX(0xFF) | 在通道上寻址接收器所用的设备索引。 |
错误以 ReceiverError 呈现 —— UnknownReceiver,或包装后的 HID++ 1.0
Protocol 错误。
Logi Bolt
现款 Logitech 接收器。Bolt 基于 BLE,最多配对 6 台设备,配对前用
passkey 认证新设备。bolt::Receiver 提供:
| 函数 | 用途 |
|---|---|
count_pairings() | 当前已配对设备数(含离线设备)。 |
collect_paired_devices() | 枚举全部已配对设备(Vec<DeviceConnection>)。 |
get_device_pairing_information(index) | 单个槽位的配对信息(DevicePairingInformation)。 |
get_device_codename(index) | 设备的代号 / 名称。 |
get_notification_state() / set_notification_state(..) | 读取 / 启用接收器通知。 |
trigger_device_arrival() | 为全部已配对设备重发 DeviceConnection 事件(枚举用)。 |
discover_devices(timeout) / cancel_device_discovery() | 开始 / 停止发现可配对设备(≤ 60 秒)。 |
pair_device(slot, address, authentication, entropy) | 开始配对一台已发现的设备。 |
unpair_device(index) | 移除一个配对。 |
listen() | 订阅接收器 Event。 |
配对流程
discover_devices(Some(secs))—— 接收器为附近每台设备发出Event::DeviceDiscoveryDeviceDetails(携带设备address、kind、wpid、authentication)与Event::DeviceDiscoveryDeviceName。pair_device(slot, address, authentication, entropy)——entropy设定 passkey 复杂度(对鼠标而言,是用户需要输入的左/右键点击次数)。- 接收器完成配对,发出
Event::DeviceConnection。
事件(bolt::Event)
| 变体 | 触发时机 |
|---|---|
DeviceConnection(DeviceConnection) | 设备连接 / 断开(需已启用无线通知)。 |
DeviceDiscoveryStatus { discovery_enabled } | 发现模式开关变化。 |
DeviceDiscoveryDeviceDetails { counter, kind, wpid, address, authentication } | 发现一台设备(配对需要这些详情与地址)。 |
DeviceDiscoveryDeviceName { .. } | 已发现设备的名称。 |
DeviceKind 覆盖 Keyboard、Mouse、Numpad、Presenter、Remote、
Trackball、Touchpad、Tablet、Gamepad、Joystick、Headset(外加
Unknown)。
Unifying
上一代接收器。Unifying 通过私有的 Unifying / DJ 协议最多配对 6 台设备;已配对
设备一旦按槽位索引寻址,就说 HID++ 2.0。unifying::Receiver 提供:
| 函数 | 用途 |
|---|---|
count_pairings() | 已配对设备数(含离线设备)。 |
get_receiver_info() | 接收器信息,含 pairing_slots(ReceiverInfo)。 |
get_device_pairing_information(index) | 单个槽位的配对信息(DevicePairingInformation)。 |
trigger_device_arrival() | 为每个已配对槽位重播连接事件,离线槽位也在内(启动时枚举)。 |
get_unique_id() | 接收器序列号 / 唯一 id。 |
listen() | 订阅接收器 Event。 |
0x41 连接通知
两种接收器都通过同一条主动上报的 HID++ 1.0 通知(sub-id 0x41)报告设备状态。
已配对设备连接或断开时它会触发;trigger_device_arrival() 则让接收器为每个
已配对槽位重播一条 —— 设备已关机或超出范围的槽位同样会收到,只是标记为离线。
通知的设备索引就是设备的配对槽位,payload 为:
| 字节 | 内容 |
|---|---|
| 0 | 协议类型(eQUAD 变体)。 |
| 1 | device-info 字节(见下)。 |
| 2–3 | Wireless PID,小端序。 |
device-info 字节打包了设备类型与四个状态标志位:
| 位 | 掩码 | 含义 |
|---|---|---|
| 0–3 | 0x0F | 设备类型(0x1 键盘、0x2 鼠标……)。 |
| 4 | 0x10 | Software present —— 主机曾通过寄存器 0x00 的 SOFTWARE_PRESENT 通知标志声明驱动软件在场。 |
| 5 | 0x20 | Link encrypted —— eQUAD 无线链路使用配对时建立的加密密钥。 |
| 6 | 0x40 | Link not established —— 置位表示设备离线。语义取反。 |
| 7 | 0x80 | 后随 payload 数据包。 |
bit 5 和 bit 4 长得像,说的却是两个世界。bit 5 是 2.4 GHz 链路自身的属性:设备与
接收器之间的流量是否加密,在配对时就已决定。它关乎安全 —— 未加密的链路可以被空中
嗅探和注入(2016 年的 MouseJack 攻击打的正是这类链路),所以 Solaar 在配对结束后
发现该位为 0 会向用户告警。Bolt 的链路设计上恒为加密。bit 4 与无线电毫无关系:它
回显的是主机写入寄存器 0x00 的一个标志,声明"有软件在接管设备的部分行为",
设备固件据此决定(比如)手势键是发出转移报告(diverted report)还是退回固件内置
行为。
bit 4 不是加密
这两个位很容易混淆:OpenLogi vendor 的 hidpp fork 一直把 bit 4 当作加密标志读,
直到与 Solaar 的解码器对照才发现;而且 Unifying 与 Bolt 的布局完全一致 —— 并不存在
需要区别对待的按接收器差异。
一段真实抓包,短消息 payload 04 62 69 40:协议 0x04(eQUAD),device-info
0x62 = 0110 0010 —— bit 6 置位(离线)、bit 5 置位(配对时已建立链路加密)、
bit 4 为 0、类型 0x2(鼠标)—— wpid 0x4069,即 MX Master 2S。这就是离线槽位
重播的样子:身份完好,链路断开。
枚举已配对设备(Rust)
use std::sync::Arc;
use hidpp::{
channel::HidppChannel,
receiver::{self, Receiver},
};
// chan: 绑定到接收器 HID 接口的 Arc<HidppChannel>。
let Some(rx) = receiver::detect(Arc::clone(&chan)) else {
return; // 不是已知的 Logitech 接收器
};
println!("{} — {}", rx.name(), rx.get_unique_id().await?);
match rx {
Receiver::Bolt(bolt) => {
for dev in bolt.collect_paired_devices().await? {
// dev: bolt::DeviceConnection —— 一台已配对设备
let _ = dev;
}
}
Receiver::Unifying(uni) => {
let events = uni.listen();
uni.trigger_device_arrival().await?; // 重发 DeviceConnection 事件
while let Ok(event) = events.recv().await {
// 处理 unifying::Event::DeviceConnection { .. }
let _ = event;
break;
}
}
_ => {}
}无接收器
蓝牙直连与有线设备作为各自独立的清单枚举,不涉及接收器。参见 连接设备。