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