Skip to main content

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}