hidpp/receiver/bolt.rs
1//! Implements the Logi Bolt receiver.
2//!
3//! Bolt can be seen as a successor to the Unifying receiver. Both of them
4//! support up to 6 paired devices, but Bolt uses BTLE technology and introduces
5//! so-called passkeys for authenticating devices before pairing them.
6//!
7//! There is little to no public documentation about what registers Bolt
8//! supports (and they seem to differ quite substantially from registers
9//! supported by Unifying and other receivers), so this implementation is based
10//! largely on information gathered by looking at other codebases (primarily
11//! Solaar) and searching registers by fuzzing them.
12
13use std::sync::Arc;
14
15use derive_builder::Builder;
16use futures::{FutureExt, pin_mut, select};
17use num_enum::{IntoPrimitive, TryFromPrimitive};
18
19use super::{RECEIVER_DEVICE_INDEX, ReceiverError};
20use crate::{
21 channel::HidppChannel,
22 event::EventEmitter,
23 protocol::v10::{self, Hidpp10Error},
24};
25
26/// All USB vendor & product ID pairs that are known to identify Bolt receivers.
27pub const VPID_PAIRS: &[(u16, u16)] = &[(0x046d, 0xc548)];
28
29/// All known registers of the Bolt receiver.
30///
31/// In most cases you should not need to access these manually, as [`Receiver`]
32/// implements many features.
33#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, IntoPrimitive, TryFromPrimitive)]
34#[cfg_attr(feature = "serde", derive(serde::Serialize))]
35#[non_exhaustive]
36#[repr(u8)]
37pub enum Register {
38 /// Allows control over what notifications the receiver sends.
39 Notifications = 0x00,
40
41 /// Provides the amount of currently paired devices.
42 ///
43 /// This is exposed by [`Receiver::count_pairings`].
44 Connections = 0x02,
45
46 /// Provides information about the receiver and paired devices.
47 ///
48 /// It uses sub-registers, as defined in [`InfoSubRegister`], to
49 /// differentiate between different kinds of information.
50 ReceiverInfo = 0xb5,
51
52 /// Provides support for discovering devices that are ready to pair.
53 ///
54 /// Use [`Receiver::discover_devices`] and
55 /// [`Receiver::cancel_device_discovery`] to control device discovery.
56 DeviceDiscovery = 0xc0,
57
58 /// Provides pairing and unpairing support.
59 ///
60 /// Use [`Receiver::pair_device`] and [`Receiver::unpair_device`] for
61 /// pairing and unpairing.
62 Pairing = 0xc1,
63
64 /// Exposes the unique ID of the receiver. This seems to differ from the
65 /// serial number.
66 ///
67 /// Use [`Receiver::get_unique_id`] to query this value.
68 UniqueId = 0xfb,
69}
70
71/// All known sub-registers of the [`Register::ReceiverInfo`] register.
72#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, IntoPrimitive, TryFromPrimitive)]
73#[cfg_attr(feature = "serde", derive(serde::Serialize))]
74#[non_exhaustive]
75#[repr(u8)]
76pub enum InfoSubRegister {
77 /// Provides information about a specific paired device. The device index (4
78 /// bits) has to be added to the register address.
79 ///
80 /// Exposed by [`Receiver::get_device_pairing_information`].
81 DevicePairingInformation = 0x50, // 0x5N with N = device index
82
83 /// Provides the name of a paired device. The device index (4
84 /// bits) has to be added to the register address.
85 ///
86 /// Exposed by [`Receiver::get_device_codename`].
87 DeviceCodename = 0x60, // 0x6N with N = device index
88}
89
90/// Implements the Bolt receiver.
91#[derive(Clone)]
92pub struct Receiver {
93 chan: Arc<HidppChannel>,
94 emitter: Arc<EventEmitter<Event>>,
95 msg_listener_hdl: u32,
96}
97
98impl Receiver {
99 /// Tries to initialize a new [`Receiver`] from a raw HID++ channel.
100 ///
101 /// If no receiver could be found, or if the vendor and product IDs don't
102 /// match the ones of any known Bolt receiver, this function will return
103 /// [`ReceiverError::UnknownReceiver`].
104 pub fn new(chan: Arc<HidppChannel>) -> Result<Self, ReceiverError> {
105 if !VPID_PAIRS.contains(&(chan.vendor_id, chan.product_id)) {
106 return Err(ReceiverError::UnknownReceiver);
107 }
108
109 let emitter = Arc::new(EventEmitter::new());
110
111 let hdl = chan.add_msg_listener({
112 let emitter = Arc::clone(&emitter);
113
114 move |raw, matched| {
115 if matched {
116 return;
117 }
118
119 let parsed = v10::Message::from(raw);
120 let header = parsed.header();
121 let payload = parsed.extend_payload();
122
123 if header.device_index != RECEIVER_DEVICE_INDEX && header.sub_id != 0x41 {
124 return;
125 }
126
127 match header.sub_id {
128 // Device connection
129 0x41 => {
130 let Ok(kind) = DeviceKind::try_from(payload[1] & 0x0f) else {
131 return;
132 };
133
134 emitter.emit(Event::DeviceConnection(DeviceConnection {
135 index: header.device_index,
136 kind,
137 encrypted: payload[1] & (1 << 5) != 0,
138 online: payload[1] & (1 << 6) == 0,
139 wpid: u16::from_le_bytes(payload[2..=3].try_into().unwrap()),
140 }));
141 }
142 // Device discovery
143 0x4f => {
144 match payload[2] {
145 // Device data
146 0 => {
147 let Ok(kind) = DeviceKind::try_from(payload[4] & 0x0f) else {
148 return;
149 };
150
151 emitter.emit(Event::DeviceDiscoveryDeviceDetails {
152 counter: payload[0] as u16 + payload[1] as u16 * 256,
153 kind,
154 wpid: u16::from_le_bytes(payload[5..=6].try_into().unwrap()),
155 address: payload[7..=12].try_into().unwrap(),
156 authentication: payload[15],
157 });
158 }
159 // Device name
160 1 => {
161 let Ok(name) =
162 str::from_utf8(&payload[4..(4 + payload[3] as usize)])
163 else {
164 return;
165 };
166
167 emitter.emit(Event::DeviceDiscoveryDeviceName {
168 counter: payload[0] as u16 + payload[1] as u16 * 256,
169 name: name.to_string(),
170 });
171 }
172 _ => (),
173 }
174 }
175 // Device discovery status
176 0x53 => {
177 emitter.emit(Event::DeviceDiscoveryStatus {
178 discovery_enabled: payload[0] == 0x00,
179 });
180 }
181 // Pairing status
182 0x54 => {
183 // payload[0] contains some kind of information about the status. I don't
184 // know how to map that though.
185
186 let error = if payload[1] == 0x00 {
187 None
188 } else {
189 let Ok(parsed) = PairingError::try_from(payload[1]) else {
190 return;
191 };
192
193 Some(parsed)
194 };
195
196 emitter.emit(Event::PairingStatus {
197 device_address: payload[2..=7].try_into().unwrap(),
198 pairing_error: error,
199 slot: if payload[8] == 0x00 {
200 None
201 } else {
202 Some(payload[8])
203 },
204 });
205 }
206 // Passkey request
207 0x4d => {
208 let Ok(passkey) = str::from_utf8(&payload[1..=6]) else {
209 return;
210 };
211
212 emitter.emit(Event::PairingPasskeyRequest {
213 device_address: payload[7..=12].try_into().unwrap(),
214 passkey: passkey.to_string(),
215 });
216 }
217 // Passkey pressed
218 0x4e => {
219 let Ok(press_type) = PairingPasskeyPressType::try_from(payload[0]) else {
220 return;
221 };
222
223 emitter.emit(Event::PairingPasskeyPressed {
224 device_address: payload[1..=6].try_into().unwrap(),
225 press_type,
226 });
227 }
228 _ => (),
229 }
230 }
231 });
232
233 Ok(Receiver {
234 chan,
235 emitter,
236 msg_listener_hdl: hdl,
237 })
238 }
239
240 /// Creates a new listener for receiving receiver events.
241 pub fn listen(&self) -> async_channel::Receiver<Event> {
242 self.emitter.create_receiver()
243 }
244
245 /// Queries the current information about what notifications are enabled.
246 pub async fn get_notification_state(&self) -> Result<NotificationState, ReceiverError> {
247 let response = self
248 .chan
249 .read_register(
250 RECEIVER_DEVICE_INDEX,
251 Register::Notifications.into(),
252 [0u8; 3],
253 )
254 .await?;
255
256 Ok(NotificationState {
257 wireless_notifications: (response[1] & 1) != 0,
258 })
259 }
260
261 /// Configures what notifications are enabled and thus reported by the
262 /// receiver.
263 pub async fn set_notification_state(
264 &self,
265 state: NotificationState,
266 ) -> Result<(), ReceiverError> {
267 self.chan
268 .write_register(
269 RECEIVER_DEVICE_INDEX,
270 Register::Notifications.into(),
271 [0, if state.wireless_notifications { 1 } else { 0 }, 0],
272 )
273 .await?;
274
275 Ok(())
276 }
277
278 /// Counts the amount of devices currently paired to this receiver. The
279 /// devices don't have to be online to be included here as pairings are
280 /// persistent.
281 pub async fn count_pairings(&self) -> Result<u8, ReceiverError> {
282 let response = self
283 .chan
284 .read_register(
285 RECEIVER_DEVICE_INDEX,
286 Register::Connections.into(),
287 [0u8; 3],
288 )
289 .await?;
290
291 Ok(response[1])
292 }
293
294 /// Triggers device arrival notifications for all devices currently
295 /// connected to the receiver. This is useful for device enumeration.
296 ///
297 /// Check [`Self::get_notification_state`] first to make sure that
298 /// [`NotificationState::wireless_notifications`] is enabled.
299 pub async fn trigger_device_arrival(&self) -> Result<(), ReceiverError> {
300 self.chan
301 .write_register(
302 RECEIVER_DEVICE_INDEX,
303 Register::Connections.into(),
304 [0x02, 0x00, 0x00],
305 )
306 .await?;
307
308 Ok(())
309 }
310
311 /// Collects information about all paired devices by calling
312 /// [`Self::trigger_device_arrival`] and collecting incoming
313 /// [`Event::DeviceConnection`] events.
314 ///
315 /// Check [`Self::get_notification_state`] first to make sure that
316 /// [`NotificationState::wireless_notifications`] is enabled.
317 pub async fn collect_paired_devices(&self) -> Result<Vec<DeviceConnection>, ReceiverError> {
318 // The idea here is that, when triggering fake device arrival notifications, the
319 // receiver will send the register write confirmation message only AFTER sending
320 // all arrival notifications.
321 // So we will trigger device arrival notifications and continue collecting those
322 // until the original future has completed.
323
324 let mut devices = vec![];
325
326 let rx = self.listen();
327 let fin = self.trigger_device_arrival().fuse();
328 pin_mut!(fin);
329
330 loop {
331 select! {
332 _ = fin => break,
333 res = rx.recv().fuse() => {
334 let Ok(Event::DeviceConnection(connection)) = res else {
335 continue;
336 };
337
338 devices.push(connection);
339 }
340 }
341 }
342
343 Ok(devices)
344 }
345
346 /// Retrieves the unique ID of the receiver. This is not the same as the
347 /// serial number.
348 pub async fn get_unique_id(&self) -> Result<String, ReceiverError> {
349 let response = self
350 .chan
351 .read_long_register(RECEIVER_DEVICE_INDEX, Register::UniqueId.into(), [0u8; 3])
352 .await?;
353
354 // When decoding the last 8 bytes of the response to their ASCII representation
355 // we seem to get a valid hex string representing 4 bytes of data.
356 // Interpreting this hex string as little endian we seem to get the same decimal
357 // value the Options+ software calls `udid` (unique device identifier?). I am
358 // not sure what this is about and it may be a (major) coincidence that these
359 // values match for my receiver, but it could be worth keeping this in mind.
360
361 // I have no clue how to retrieve the serial number of the receiver.
362
363 Ok(str::from_utf8(&response)
364 .map_err(|_| Hidpp10Error::UnsupportedResponse)?
365 .to_string())
366 }
367
368 /// Provides the pairing information of a specific paired device by its
369 /// index.
370 pub async fn get_device_pairing_information(
371 &self,
372 device_index: u8,
373 ) -> Result<DevicePairingInformation, ReceiverError> {
374 let response = self
375 .chan
376 .read_long_register(
377 RECEIVER_DEVICE_INDEX,
378 Register::ReceiverInfo.into(),
379 [
380 u8::from(InfoSubRegister::DevicePairingInformation) + (device_index & 0x0f),
381 0x00,
382 0x00,
383 ],
384 )
385 .await?;
386
387 Ok(DevicePairingInformation {
388 wpid: u16::from_le_bytes(response[2..=3].try_into().unwrap()),
389 kind: DeviceKind::try_from(response[1] & 0x0f)
390 .map_err(|_| Hidpp10Error::UnsupportedResponse)?,
391 encrypted: response[1] & (1 << 5) != 0,
392 online: response[1] & (1 << 6) == 0,
393 unit_id: response[4..=7].try_into().unwrap(),
394 })
395 }
396
397 /// Provides the codename of a specific paired device by its index.
398 pub async fn get_device_codename(&self, device_index: u8) -> Result<String, ReceiverError> {
399 // For device names longer than 13 characters this may need to be called
400 // multiple times with different parameters. I don't have a device with
401 // such a name to be able to test this.
402
403 let response = self
404 .chan
405 .read_long_register(
406 RECEIVER_DEVICE_INDEX,
407 Register::ReceiverInfo.into(),
408 [
409 u8::from(InfoSubRegister::DeviceCodename) + (device_index & 0x0f),
410 0x01,
411 0x00,
412 ],
413 )
414 .await?;
415
416 let end_idx = 3 + response[2] as usize;
417 Ok(str::from_utf8(&response[3..end_idx])
418 .map_err(|_| Hidpp10Error::UnsupportedResponse)?
419 .to_string())
420 }
421
422 /// Unpairs a device from the receiver by its index.
423 pub async fn unpair_device(&self, device_index: u8) -> Result<(), ReceiverError> {
424 let mut payload = [0u8; 16];
425 payload[0] = 0x03;
426 payload[1] = device_index;
427
428 self.chan
429 .write_long_register(RECEIVER_DEVICE_INDEX, Register::Pairing.into(), payload)
430 .await?;
431
432 Ok(())
433 }
434
435 /// Starts the pairing process for a new device.
436 ///
437 /// The required `address` and `authentication` values are usually
438 /// discovered from the [`Event::DeviceDiscoveryDeviceDetails`] event which
439 /// is emitted regularly when actively discovering available devices
440 /// ([`Self::discover_devices`]).
441 ///
442 /// `entropy` specifies how complex the authentication passkey should be.
443 /// For mice, this defines the amount of keypresses (left or right) the user
444 /// has to perform. Not all values seem to be supported.
445 pub async fn pair_device(
446 &self,
447 slot: u8,
448 address: [u8; 6],
449 authentication: u8,
450 entropy: u8,
451 ) -> Result<(), ReceiverError> {
452 let mut payload = [0u8; 16];
453 payload[0] = 0x01;
454 payload[1] = slot;
455 payload[2..=7].copy_from_slice(&address);
456 payload[8] = authentication;
457 payload[9] = entropy;
458
459 self.chan
460 .write_long_register(RECEIVER_DEVICE_INDEX, Register::Pairing.into(), payload)
461 .await?;
462
463 Ok(())
464 }
465
466 /// Starts device discovery for `timeout` seconds ([`None`] = default, seems
467 /// to be 30s). The maximum supported value is 60s.
468 ///
469 /// While device discovery is enabled,
470 /// [`Event::DeviceDiscoveryDeviceDetails`] and
471 /// [`Event::DeviceDiscoveryDeviceName`] events are emitted for every
472 /// discovered device.
473 pub async fn discover_devices(&self, timeout: Option<u8>) -> Result<(), ReceiverError> {
474 self.chan
475 .write_register(
476 RECEIVER_DEVICE_INDEX,
477 Register::DeviceDiscovery.into(),
478 [timeout.unwrap_or(0x00), 0x01, 0x00],
479 )
480 .await?;
481
482 Ok(())
483 }
484
485 /// Cancels the device discovery process.
486 pub async fn cancel_device_discovery(&self) -> Result<(), ReceiverError> {
487 self.chan
488 .write_register(
489 RECEIVER_DEVICE_INDEX,
490 Register::DeviceDiscovery.into(),
491 [0x00, 0x02, 0x00],
492 )
493 .await?;
494
495 Ok(())
496 }
497}
498
499impl Drop for Receiver {
500 fn drop(&mut self) {
501 self.chan.remove_msg_listener(self.msg_listener_hdl);
502 }
503}
504
505/// Indicates which notifications are enabled and thus sent by the receiver.
506///
507/// This information can be queried using [`Receiver::get_notification_state`].
508#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Builder)]
509#[cfg_attr(feature = "serde", derive(serde::Serialize))]
510#[non_exhaustive]
511pub struct NotificationState {
512 /// Whether the receiver sends device arrival/removal notifications.
513 pub wireless_notifications: bool,
514}
515
516/// Represents information about a paired device.
517///
518/// This information can be queried using
519/// [`Receiver::get_device_pairing_information`].
520#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
521#[cfg_attr(feature = "serde", derive(serde::Serialize))]
522#[non_exhaustive]
523pub struct DevicePairingInformation {
524 pub wpid: u16,
525 pub kind: DeviceKind,
526 pub encrypted: bool,
527 pub online: bool,
528 pub unit_id: [u8; 4],
529}
530
531/// Represents the kind of a device paired to a Bolt receiver.
532#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, IntoPrimitive, TryFromPrimitive)]
533#[cfg_attr(feature = "serde", derive(serde::Serialize))]
534#[non_exhaustive]
535#[repr(u8)]
536pub enum DeviceKind {
537 Unknown = 0x00,
538 Keyboard = 0x01,
539 Mouse = 0x02,
540 Numpad = 0x03,
541 Presenter = 0x04,
542 Remote = 0x07,
543 Trackball = 0x08,
544 Touchpad = 0x09,
545 Tablet = 0x0a,
546 Gamepad = 0x0b,
547 Joystick = 0x0c,
548 Headset = 0x0d,
549}
550
551/// Represents an event emitted by the receiver.
552///
553/// You can listen to these events using [`Receiver::listen`]. Only enabled
554/// notifications as indicated by [`Receiver::get_notification_state`] are
555/// emitted.
556#[derive(Clone, PartialEq, Eq, Hash, Debug)]
557#[cfg_attr(feature = "serde", derive(serde::Serialize))]
558#[non_exhaustive]
559pub enum Event {
560 /// Is emitted whenever a device connects to or disconnects from the
561 /// receiver, but only if [`NotificationState::wireless_notifications`] is
562 /// enabled.
563 ///
564 /// Can be triggered for all paired devices using
565 /// [`Receiver::trigger_device_arrival`] to allow easy device enumeration.
566 ///
567 /// [`Receiver::collect_paired_devices`] implements a simple mechanism to
568 /// collect all paired devices.
569 DeviceConnection(DeviceConnection),
570
571 /// Is emitted whenever the device discovery status changes.
572 DeviceDiscoveryStatus { discovery_enabled: bool },
573
574 /// Is emitted many times for every device discovered using
575 /// [`Receiver::discover_devices`].
576 ///
577 /// This event contains device details, including its address required to
578 /// start pairing. The [`Event::DeviceDiscoveryDeviceName`] event will also
579 /// be emitted and contains the device name.
580 DeviceDiscoveryDeviceDetails {
581 /// The incrementing event counter. This can be used to map
582 /// [`Event::DeviceDiscoveryDeviceDetails`] and
583 /// [`Event::DeviceDiscoveryDeviceName`] events.
584 counter: u16,
585
586 kind: DeviceKind,
587 wpid: u16,
588
589 /// The address of the device required to pair it using
590 /// [`Receiver::pair_device`].
591 ///
592 /// This can also be used as the unique device identifier when
593 /// collecting discovered devices.
594 address: [u8; 6],
595
596 /// The authentication type(s) the device supports. Unfortunately, there
597 /// is not much information about this value and whether it is a
598 /// single value or a bitfield.
599 authentication: u8,
600 },
601
602 /// Is emitted many times for every device discovered using
603 /// [`Receiver::discover_devices`].
604 ///
605 /// This event only contains the device name. Device details will be
606 /// provided using the [`Event::DeviceDiscoveryDeviceDetails`] event.
607 DeviceDiscoveryDeviceName {
608 /// The incrementing event counter. This can be used to map
609 /// [`Event::DeviceDiscoveryDeviceDetails`] and
610 /// [`Event::DeviceDiscoveryDeviceName`] events.
611 counter: u16,
612
613 name: String,
614 },
615
616 /// Is emitted whenever the status of a pairing process changes.
617 PairingStatus {
618 device_address: [u8; 6],
619 pairing_error: Option<PairingError>,
620
621 /// The receiver slot the newly paired device was paired to. This can be
622 /// used as the device index for subsequent operations.
623 slot: Option<u8>,
624 },
625
626 /// Is emitted once the receiver requests a passkey to be entered on a
627 /// device that should be paired to it.
628 PairingPasskeyRequest {
629 device_address: [u8; 6],
630
631 /// The passkey the user has to enter in order to pair the device.
632 ///
633 /// Depending on the device and authentication type, this value has
634 /// different implications.
635 ///
636 /// For mice, this value will be a valid 6-digit number. After parsing
637 /// this into an integer, the (least significant) bits represent
638 /// the sequence of mouse presses (`0` = left, `1` = right) the
639 /// user has to perform, with an additional press of both mouse
640 /// buttons simultaneously.
641 ///
642 /// The amount of bits significant to this equals to the `entropy`
643 /// passed to [`Receiver::pair_device`].
644 passkey: String,
645 },
646
647 /// Is emitted for every keypress a user performs while entering a pairing
648 /// passkey.
649 PairingPasskeyPressed {
650 device_address: [u8; 6],
651
652 /// The type of the keypress the user performed.
653 ///
654 /// Every passkey sequence starts with an event where this value is set
655 /// to [`PairingPasskeyPressType::Initialization`]. Each time the user
656 /// presses a key, an event with a press type of
657 /// [`PairingPasskeyPressType::Keypress`] is emitted. Once the user
658 /// submits their passkey, this value will be
659 /// [`PairingPasskeyPressType::Submit`].
660 press_type: PairingPasskeyPressType,
661 },
662}
663
664/// Represents a device connected to a Bolt receiver.
665///
666/// This information is emitted by the [`Event::DeviceConnection`] event and can
667/// be conveniently collected using [`Receiver::collect_paired_devices`].
668#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
669#[cfg_attr(feature = "serde", derive(serde::Serialize))]
670#[non_exhaustive]
671pub struct DeviceConnection {
672 pub index: u8,
673 pub kind: DeviceKind,
674 pub encrypted: bool,
675 pub online: bool,
676 pub wpid: u16,
677}
678
679/// Represents an error during device pairing.
680///
681/// This is reported by the [`Event::PairingStatus`] event.
682#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, TryFromPrimitive, IntoPrimitive)]
683#[cfg_attr(feature = "serde", derive(serde::Serialize))]
684#[non_exhaustive]
685#[repr(u8)]
686pub enum PairingError {
687 DeviceTimeout = 0x01,
688 Failed = 0x02,
689}
690
691/// Represents the type of a single passkey press.
692///
693/// This is reported by the [`Event::PairingPasskeyPressed`] event, which also
694/// includes some further information about the context of these values.
695#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, TryFromPrimitive, IntoPrimitive)]
696#[cfg_attr(feature = "serde", derive(serde::Serialize))]
697#[non_exhaustive]
698#[repr(u8)]
699pub enum PairingPasskeyPressType {
700 Initialization = 0x00,
701 Keypress = 0x01,
702 Submit = 0x04,
703}