Skip to main content

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}