hidpp/feature/change_host.rs
1//! Implements the `ChangeHost` feature (ID `0x1814`) that selects which host /
2//! RF channel a multi-host device is connected to.
3
4use openlogi_hidpp_derive::Feature;
5
6use crate::{feature::FeatureEndpoint, protocol::v20::Hidpp20Error};
7
8bitflags::bitflags! {
9 /// Host-switching capabilities reported by [`ChangeHostFeature::get_host_info`].
10 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
11 #[cfg_attr(feature = "serde", derive(serde::Serialize))]
12 pub struct ChangeHostCapabilities: u8 {
13 /// Enhanced host switching is enabled: on a failed connection the device
14 /// falls back to another host with a non-zero cookie before returning to
15 /// the original host.
16 const ENHANCED_HOST_SWITCH = 1 << 0;
17 }
18}
19
20/// Host configuration returned by [`ChangeHostFeature::get_host_info`].
21#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
22#[cfg_attr(feature = "serde", derive(serde::Serialize))]
23#[non_exhaustive]
24pub struct ChangeHostInfo {
25 /// Number of hosts / RF channels.
26 pub host_count: u8,
27 /// Current host index, in `0..host_count`.
28 pub current_host: u8,
29 /// Host-switching capabilities.
30 pub capabilities: ChangeHostCapabilities,
31}
32
33/// Implements the `ChangeHost` / `0x1814` feature.
34#[derive(Clone, Feature)]
35#[creatable(id = 0x1814, version = 0)]
36pub struct ChangeHostFeature {
37 /// The endpoint this feature talks to.
38 endpoint: FeatureEndpoint,
39}
40
41impl ChangeHostFeature {
42 /// Retrieves the host count, current host and host-switching flags.
43 pub async fn get_host_info(&self) -> Result<ChangeHostInfo, Hidpp20Error> {
44 let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
45 Ok(ChangeHostInfo {
46 host_count: payload[0],
47 current_host: payload[1],
48 capabilities: ChangeHostCapabilities::from_bits_retain(payload[2]),
49 })
50 }
51
52 /// Selects `host` as the current host.
53 ///
54 /// This is sent fire-and-forget: a successful switch usually resets the
55 /// device, so no response is awaited. The device drops off the current host
56 /// once it acts on the request.
57 pub async fn set_current_host(&self, host: u8) -> Result<(), Hidpp20Error> {
58 self.endpoint.notify(1, [host, 0, 0]).await
59 }
60
61 /// Retrieves the persistent per-host cookie bytes.
62 ///
63 /// `host_count` is the value from [`ChangeHostInfo::host_count`]; the device
64 /// returns one cookie byte per host and does not delimit the list.
65 pub async fn get_cookies(&self, host_count: u8) -> Result<Vec<u8>, Hidpp20Error> {
66 let count = usize::from(host_count);
67 let payload = self.endpoint.call(2, [0; 3]).await?.extend_payload();
68 if count > payload.len() {
69 return Err(Hidpp20Error::UnsupportedResponse);
70 }
71 Ok(payload[..count].to_vec())
72 }
73
74 /// Writes the persistent `cookie` byte for `host`.
75 ///
76 /// Cookies are arbitrary software-defined bytes stored in the device's
77 /// non-volatile memory; the firmware clears a host's cookie when a new host
78 /// connects to that slot.
79 pub async fn set_cookie(&self, host: u8, cookie: u8) -> Result<(), Hidpp20Error> {
80 self.endpoint.call(3, [host, cookie, 0]).await?;
81 Ok(())
82 }
83}