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},
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}