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