hidpp/protocol/mod.rs
1//! Implements the protocol-specific parts of HID++.
2
3use std::fmt::Debug;
4
5use crate::{
6 channel::{ChannelError, HidppChannel},
7 nibble::{self, U4},
8};
9
10pub mod v10;
11pub mod v20;
12
13/// Represents the protocol version a device supports.
14#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize))]
16pub enum ProtocolVersion {
17 /// The older HID++1.0 protocol. Mostly used for receivers.
18 V10,
19
20 /// All newer protocols starting from HID+2.0.
21 ///
22 /// Traditionally, the version was split into a major and a minor version,
23 /// defining the concrete protocol version. These two values were later
24 /// redefined to serve the purpose of indicating which host software to
25 /// target.
26 V20 {
27 /// The protocol number is a field that hints the host software if it
28 /// should support the device.
29 ///
30 /// `protocol_num = 2` : Intended target SW is Logitech SetPoint\
31 /// `protocol_num = 3` : Intended OEM SW described in `target_sw` field\
32 /// `protocol_num = 4` : Intended target SW described in `target_sw`
33 /// field
34 protocol_num: u8,
35
36 /// When `protocol_num >= 3` this field further hints at which software
37 /// should support the device. Otherwise the value is zero.
38 ///
39 /// See <https://drive.google.com/file/d/1ULmw9uJL8b8iwwUo5xjSS9F5Zvno-86y/view>
40 /// for more information.
41 target_sw: u8,
42 },
43}
44
45/// Tries to determine the protocol version of a specific device.
46///
47/// Returns `Ok(None)` if no device was found for the given device index.
48pub async fn determine_version(
49 chan: &HidppChannel,
50 device_index: u8,
51) -> Result<Option<ProtocolVersion>, ChannelError> {
52 // To determine the protocol version, we send a HID++2.0 ping message
53 // feature with index 0x00, function 0x01).
54 // Devices supporting protocol >=2.0 will respond with a defined response
55 // including the particular protocol version.
56 // Devices only supporting protocol 1.0 will respond with an error message
57 // indicating 0x00 is no valid sub ID. We make use of this to pin them to
58 // version 1.0.
59
60 let sw_id = chan.get_sw_id();
61 let msg = v20::Message::Short(
62 v20::MessageHeader {
63 device_index,
64 feature_index: 0x00,
65 function_id: U4::from_lo(0x1),
66 software_id: sw_id,
67 },
68 [0x00, 0x00, 0x00],
69 );
70
71 let response = chan
72 .send(msg.into(), move |resp| {
73 // If we receive a valid HID++2.0 response, we'll use that.
74 if v20::Message::from(*resp).header() == msg.header() {
75 return true;
76 }
77
78 // We only care about HID++1.0 error messages, which are always short according
79 // to the spec.
80 if let v10::Message::Short(header, payload) = v10::Message::from(*resp)
81 && header.device_index == device_index
82 && header.sub_id == v10::MessageType::Error.into()
83 // The feature index we sent would be interpreted as the sub ID by HID++1.0, which is included in the error message.
84 && payload[0] == 0x00
85 // The function & software IDs would be interpreted as the register address in HID++1.0.
86 && payload[1] == nibble::combine(msg.header().function_id, sw_id)
87 {
88 return true;
89 }
90
91 false
92 })
93 .await?;
94
95 let v20_msg = v20::Message::from(response);
96 if v20_msg.header() == msg.header() {
97 let payload = v20_msg.extend_payload();
98 return Ok(Some(ProtocolVersion::V20 {
99 protocol_num: payload[0],
100 target_sw: payload[1],
101 }));
102 }
103
104 let v10::Message::Short(_, payload) = v10::Message::from(response) else {
105 return Ok(None);
106 };
107
108 if payload[2] == v10::ErrorType::InvalidSubId.into() {
109 Ok(Some(ProtocolVersion::V10))
110 } else {
111 Ok(None)
112 }
113}