1use hidpp_transport::channel::report;
4
5pub const SOFTWARE_ID: u8 = 0x01;
8
9pub const DEVICE_INDEX_DIRECT: u8 = 0xFF;
11
12pub const DEVICE_INDEX_RECEIVER_MIN: u8 = 0x01;
14pub const DEVICE_INDEX_RECEIVER_MAX: u8 = 0x06;
15
16pub mod feature_id {
18 pub const IROOT: u16 = 0x0000;
20 pub const IFEATURE_SET: u16 = 0x0001;
22 pub const FIRMWARE_INFO: u16 = 0x0003;
24 pub const DEVICE_NAME: u16 = 0x0005;
26 pub const UNIFIED_BATTERY: u16 = 0x1000;
28 pub const BATTERY_STATUS: u16 = 0x1001;
30 pub const REPROG_CONTROLS: u16 = 0x1B04;
32 pub const SMART_SHIFT: u16 = 0x2110;
34 pub const HIRES_SCROLLING: u16 = 0x2121;
36 pub const LOWRES_SCROLLING: u16 = 0x2130;
38 pub const THUMB_WHEEL: u16 = 0x2150;
40 pub const ADJUSTABLE_DPI: u16 = 0x2201;
42 pub const ONBOARD_PROFILES: u16 = 0x8100;
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum ProtocolVersion {
49 V1_0,
51 V2_0,
53 Unknown(u8, u8),
55}
56
57impl ProtocolVersion {
58 #[must_use]
60 pub fn new(major: u8, minor: u8) -> Self {
61 match (major, minor) {
62 (1, 0) => Self::V1_0,
63 (2 | 4, _) => Self::V2_0, _ => Self::Unknown(major, minor),
65 }
66 }
67
68 #[must_use]
70 pub fn major(&self) -> u8 {
71 match self {
72 Self::V1_0 => 1,
73 Self::V2_0 => 2,
74 Self::Unknown(major, _) => *major,
75 }
76 }
77
78 #[must_use]
80 pub fn minor(&self) -> u8 {
81 match self {
82 Self::V1_0 | Self::V2_0 => 0,
83 Self::Unknown(_, minor) => *minor,
84 }
85 }
86
87 #[must_use]
89 pub fn supports_hidpp2(&self) -> bool {
90 matches!(self, Self::V2_0 | Self::Unknown(2..=4, _))
91 }
92}
93
94impl std::fmt::Display for ProtocolVersion {
95 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96 match self {
97 Self::V1_0 => write!(f, "HID++ 1.0"),
98 Self::V2_0 => write!(f, "HID++ 2.0"),
99 Self::Unknown(major, minor) => write!(f, "HID++ {major}.{minor}"),
100 }
101 }
102}
103
104#[must_use]
112pub fn build_short_request(
113 device_index: u8,
114 feature_index: u8,
115 function_id: u8,
116 params: &[u8],
117) -> [u8; report::SHORT_REPORT_LEN] {
118 let mut buf = [0u8; report::SHORT_REPORT_LEN];
119 buf[0] = report::SHORT_REPORT_ID;
120 buf[1] = device_index;
121 buf[2] = feature_index;
122 buf[3] = (function_id << 4) | (SOFTWARE_ID & 0x0F);
123
124 let param_len = params.len().min(3);
125 buf[4..4 + param_len].copy_from_slice(¶ms[..param_len]);
126
127 buf
128}
129
130#[must_use]
138pub fn build_long_request(
139 device_index: u8,
140 feature_index: u8,
141 function_id: u8,
142 params: &[u8],
143) -> [u8; report::LONG_REPORT_LEN] {
144 let mut buf = [0u8; report::LONG_REPORT_LEN];
145 buf[0] = report::LONG_REPORT_ID;
146 buf[1] = device_index;
147 buf[2] = feature_index;
148 buf[3] = (function_id << 4) | (SOFTWARE_ID & 0x0F);
149
150 let param_len = params.len().min(16);
151 buf[4..4 + param_len].copy_from_slice(¶ms[..param_len]);
152
153 buf
154}
155
156#[must_use]
158pub fn parse_software_id(response: &[u8]) -> Option<u8> {
159 response.get(3).map(|b| b & 0x0F)
160}
161
162#[must_use]
164pub fn parse_function_id(response: &[u8]) -> Option<u8> {
165 response.get(3).map(|b| b >> 4)
166}
167
168#[must_use]
170pub fn is_error_response(response: &[u8]) -> bool {
171 response.first() == Some(&0x8F)
172}
173
174#[must_use]
176pub fn get_error_code(response: &[u8]) -> Option<u8> {
177 if is_error_response(response) && response.len() >= 5 {
178 Some(response[4])
179 } else {
180 None
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 #[test]
189 fn test_build_short_request() {
190 let request = build_short_request(0xFF, 0x00, 0x01, &[0x00, 0x00]);
191 assert_eq!(request[0], report::SHORT_REPORT_ID);
192 assert_eq!(request[1], 0xFF);
193 assert_eq!(request[2], 0x00);
194 assert_eq!(request[3], 0x11); }
196
197 #[test]
198 fn test_protocol_version() {
199 assert!(ProtocolVersion::V2_0.supports_hidpp2());
200 assert!(!ProtocolVersion::V1_0.supports_hidpp2());
201
202 let v45 = ProtocolVersion::new(4, 5);
203 assert!(v45.supports_hidpp2());
204 }
205}