hidpp/feature/device_information/mod.rs
1//! Implements the `DeviceInformation` feature (ID `0x0003`) that provides some
2//! general information about the device.
3
4use std::sync::Arc;
5
6use num_enum::{IntoPrimitive, TryFromPrimitive};
7
8use crate::{
9 bcd,
10 channel::HidppChannel,
11 feature::{CreatableFeature, Feature},
12 nibble::U4,
13 protocol::v20::{self, Hidpp20Error},
14};
15
16/// Implements the `DeviceInformation` / `0x0003` feature.
17#[derive(Clone)]
18pub struct DeviceInformationFeature {
19 /// The underlying HID++ channel.
20 chan: Arc<HidppChannel>,
21
22 /// The index of the device to implement the feature for.
23 device_index: u8,
24
25 /// The index of the feature in the feature table.
26 feature_index: u8,
27}
28
29impl CreatableFeature for DeviceInformationFeature {
30 const ID: u16 = 0x0003;
31 const STARTING_VERSION: u8 = 0;
32
33 fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self {
34 Self {
35 chan,
36 device_index,
37 feature_index,
38 }
39 }
40}
41
42impl Feature for DeviceInformationFeature {}
43
44impl DeviceInformationFeature {
45 /// Retrieves general information about the device and its capabilities.
46 pub async fn get_device_info(&self) -> Result<DeviceInformation, Hidpp20Error> {
47 let response = self
48 .chan
49 .send_v20(v20::Message::Short(
50 v20::MessageHeader {
51 device_index: self.device_index,
52 feature_index: self.feature_index,
53 function_id: U4::from_lo(0),
54 software_id: self.chan.get_sw_id(),
55 },
56 [0x00, 0x00, 0x00],
57 ))
58 .await?;
59
60 let payload = response.extend_payload();
61
62 Ok(DeviceInformation {
63 entity_count: payload[0],
64 unit_id: payload[1..=4].try_into().unwrap(),
65 transport: DeviceTransport::from(payload[6]),
66 model_id: [
67 u16::from_be_bytes(payload[7..=8].try_into().unwrap()),
68 u16::from_be_bytes(payload[9..=10].try_into().unwrap()),
69 u16::from_be_bytes(payload[11..=12].try_into().unwrap()),
70 ],
71 extended_model_id: payload[13],
72 capabilities: DeviceInformationCapabilities::from(payload[14]),
73 })
74 }
75
76 /// Retrieves information about the firmware of a specific entity,
77 /// identified by its index bound by the value in
78 /// [`DeviceInformation::entity_count`].
79 pub async fn get_fw_info(
80 &self,
81 entity_index: u8,
82 ) -> Result<DeviceEntityFirmwareInfo, Hidpp20Error> {
83 let response = self
84 .chan
85 .send_v20(v20::Message::Short(
86 v20::MessageHeader {
87 device_index: self.device_index,
88 feature_index: self.feature_index,
89 function_id: U4::from_lo(1),
90 software_id: self.chan.get_sw_id(),
91 },
92 [entity_index, 0x00, 0x00],
93 ))
94 .await?;
95
96 let payload = response.extend_payload();
97
98 Ok(DeviceEntityFirmwareInfo {
99 entity_type: DeviceEntityType::try_from(payload[0])
100 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
101 firmware_prefix: String::from_utf8(payload[1..=3].to_vec())
102 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
103 firmware_number: bcd::convert_packed_u8(payload[4])
104 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
105 revision: bcd::convert_packed_u8(payload[5])
106 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
107 build: bcd::convert_packed_u16(u16::from_be_bytes(payload[6..=7].try_into().unwrap()))
108 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
109 active: payload[8] & 1 != 0,
110 transport_pid: u16::from_be_bytes(payload[9..=10].try_into().unwrap()),
111 extra_version: payload[11..=15].try_into().unwrap(),
112 })
113 }
114
115 /// Retrieves the serial number of the device.
116 ///
117 /// This function was added in feature version 4 and will likely result in
118 /// an [`v20::ErrorType::InvalidFunctionId`] error for older versions,
119 /// so [`DeviceInformationCapabilities::serial_number`] should be
120 /// verified before calling.
121 pub async fn get_serial_number(&self) -> Result<String, Hidpp20Error> {
122 let response = self
123 .chan
124 .send_v20(v20::Message::Short(
125 v20::MessageHeader {
126 device_index: self.device_index,
127 feature_index: self.feature_index,
128 function_id: U4::from_lo(2),
129 software_id: self.chan.get_sw_id(),
130 },
131 [0x00, 0x00, 0x00],
132 ))
133 .await?;
134
135 let payload = response.extend_payload();
136
137 String::from_utf8(payload[..12].to_vec()).map_err(|_| Hidpp20Error::UnsupportedResponse)
138 }
139}
140
141/// Represents information about the device as reported by
142/// [`DeviceInformationFeature::get_device_info`].
143#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
144#[non_exhaustive]
145pub struct DeviceInformation {
146 /// The amount of entities in the device from which version information can
147 /// be retrieved using [`DeviceInformationFeature::get_fw_info`].
148 pub entity_count: u8,
149
150 /// A 4-byte random value serving as a unique identifier (among all devices
151 /// with the same [`Self::model_id`]) for the unit.
152 ///
153 /// This field was added in feature version 1 and will always be `0` for
154 /// older versions.
155 pub unit_id: [u8; 4],
156
157 /// A bitfield about which transport protocols the device supports.
158 ///
159 /// This field was added in feature version 1 and will always be `0` for
160 /// older versions.
161 pub transport: DeviceTransport,
162
163 /// A 6-byte array serving as the identifier for the device model.
164 ///
165 /// This array will consist of the application PIDs of the different
166 /// transport protocols supported by the device, as stated in
167 /// [`Self::transport`].
168 /// The 16-bit PID for every supported transport protocol will be appended
169 /// into this array, limiting the total amount of supported transport
170 /// protocols to three.
171 ///
172 /// This field was added in feature version 1 and will always be `0` for
173 /// older versions.
174 pub model_id: [u16; 3],
175
176 /// An 8-bit value representing an additional configurable attribute for a
177 /// given [`Self::model_id`], set on the production line. This could be the
178 /// color of the device.
179 ///
180 /// This field was added in feature version 2 and will always be `0` for
181 /// older versions.
182 pub extended_model_id: u8,
183
184 /// Additional capability flags of this feature.
185 ///
186 /// This field was added in feature version 4 together with the serial
187 /// number retrieval function. All capabilities will be flagged as
188 /// unsupported for older versions.
189 pub capabilities: DeviceInformationCapabilities,
190}
191
192/// Represents the bitfield stating which transport protocols a device supports.
193///
194/// One given device can only support up to three transport protocols at a time.
195#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
196#[cfg_attr(feature = "serde", derive(serde::Serialize))]
197#[non_exhaustive]
198pub struct DeviceTransport {
199 /// Whether the device supports USB.
200 pub usb: bool,
201
202 /// Whether the device supports eQuad, the protocol used by the Unifying
203 /// Receiver.
204 pub e_quad: bool,
205
206 /// Whether the device supports Bluetooth Low Energy as used by the Bolt
207 /// Receiver.
208 pub btle: bool,
209
210 /// Whether the device supports Bluetooth.
211 pub bluetooth: bool,
212}
213
214impl From<u8> for DeviceTransport {
215 fn from(value: u8) -> Self {
216 Self {
217 usb: value & (1 << 3) != 0,
218 e_quad: value & (1 << 2) != 0,
219 btle: value & (1 << 1) != 0,
220 bluetooth: value & 1 != 0,
221 }
222 }
223}
224
225/// Represents the bitfield stating which additional capabilities this feature
226/// supports.
227#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
228#[cfg_attr(feature = "serde", derive(serde::Serialize))]
229#[non_exhaustive]
230pub struct DeviceInformationCapabilities {
231 /// Whether serial number retrieval is supported.
232 ///
233 /// This field was added in feature version 4 and will always be `false` for
234 /// older versions.
235 pub serial_number: bool,
236}
237
238impl From<u8> for DeviceInformationCapabilities {
239 fn from(value: u8) -> Self {
240 Self {
241 serial_number: value & 1 != 0,
242 }
243 }
244}
245
246/// Represents information about the firmware of a specific device entity as
247/// obtained via [`DeviceInformationFeature::get_fw_info`].
248#[derive(Clone, PartialEq, Eq, Hash, Debug)]
249#[cfg_attr(feature = "serde", derive(serde::Serialize))]
250#[non_exhaustive]
251pub struct DeviceEntityFirmwareInfo {
252 /// The type of the described entity.
253 pub entity_type: DeviceEntityType,
254
255 /// A 3-letter prefix for the firmware name.
256 pub firmware_prefix: String,
257
258 /// The firmware number.
259 ///
260 /// This is represented in packed BCD format in the protocol itself, but
261 /// decoding is handled by this implementation automatically.
262 pub firmware_number: u8,
263
264 /// The firmware revision.
265 ///
266 /// This is represented in packed BCD format in the protocol itself, but
267 /// decoding is handled by this implementation automatically.
268 pub revision: u8,
269
270 /// The firmware build.
271 ///
272 /// This is represented in packed BCD format in the protocol itself, but
273 /// decoding is handled by this implementation automatically.
274 pub build: u16,
275
276 /// Whether the entity is the responding and active one.
277 ///
278 /// Exactly one entity will be active at any given time.
279 pub active: bool,
280
281 /// The transport protocol PID.
282 ///
283 /// If this entity is the active one (see [`Self::active`]), this will be
284 /// set to the actual PID. If it is not, this field COULD be all-zero.
285 pub transport_pid: u16,
286
287 /// Optional extra versioning information.
288 pub extra_version: [u8; 5],
289}
290
291/// Represents the type of a device entity.
292#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, IntoPrimitive, TryFromPrimitive)]
293#[cfg_attr(feature = "serde", derive(serde::Serialize))]
294#[non_exhaustive]
295#[repr(u8)]
296pub enum DeviceEntityType {
297 MainApplication = 0,
298 Bootloader = 1,
299 Hardware = 2,
300 Touchpad = 3,
301 OpticalSensor = 4,
302 Softdevice = 5,
303 RfCompanionMcu = 6,
304 FactoryApplication = 7,
305 RgbCustomEffect = 8,
306 MotorDrive = 9,
307}