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