hidpp/device.rs
1//! Implements peripheral devices connected to HID++ channels.
2
3use std::{any::TypeId, collections::HashMap, sync::Arc};
4
5use thiserror::Error;
6use tracing::trace;
7
8use crate::{
9 channel::{ChannelError, HidppChannel},
10 feature::{
11 self, CreatableFeature, Feature,
12 feature_set::{FeatureInformation, FeatureSetFeature},
13 root::RootFeature,
14 },
15 protocol::{self, ProtocolVersion, v20::Hidpp20Error},
16};
17
18/// Represents a single HID++ device connected to a [`HidppChannel`].
19///
20/// This is used only for peripheral devices and not receivers.
21#[derive(Clone)]
22pub struct Device {
23 /// The underlying HID++ channel.
24 chan: Arc<HidppChannel>,
25
26 /// The initialized implementation of features the device supports.
27 features: HashMap<TypeId, Arc<dyn Feature>>,
28
29 /// The index of the device on the HID++ channel.
30 pub device_index: u8,
31
32 /// The supported protocol version reported by the device.
33 pub protocol_version: ProtocolVersion,
34}
35
36impl Device {
37 /// Tries to initialize a device on a HID++ channel.
38 ///
39 /// This will automatically ping the device to determine the protocol
40 /// version it supports via [`protocol::determine_version`].
41 ///
42 /// Returns [`DeviceError::DeviceNotFound`] if there is no device with the
43 /// specified index connected to the channel.
44 ///
45 /// Returns [`DeviceError::UnsupportedProtocolVersion`] if the device only
46 /// supports [`ProtocolVersion::V10`].
47 pub async fn new(chan: Arc<HidppChannel>, device_index: u8) -> Result<Self, DeviceError> {
48 let protocol_version = protocol::determine_version(&chan, device_index).await?;
49
50 if protocol_version.is_none() {
51 return Err(DeviceError::DeviceNotFound);
52 }
53 let version = protocol_version.unwrap();
54
55 if version == ProtocolVersion::V10 {
56 return Err(DeviceError::UnsupportedProtocolVersion);
57 }
58
59 let mut device = Self {
60 chan,
61 features: HashMap::new(),
62 device_index,
63 protocol_version: version,
64 };
65
66 // Every HID++2.0 device supports the root feature.
67 // We implicitly verified that using [`protocol::determine_version`].
68 device.add_feature::<RootFeature>(0);
69
70 Ok(device)
71 }
72
73 /// A convenience wrapper around [`Self::get_feature`] to obtain the root
74 /// feature.
75 pub fn root(&self) -> Arc<RootFeature> {
76 self.get_feature::<RootFeature>().unwrap()
77 }
78
79 /// Adds a new feature implementation to the list of available features.
80 /// This will override an existing implementation of the same type.
81 /// The caller is responsible for making sure the device actually supports
82 /// the feature.
83 pub fn add_feature_instance<F: Feature>(&mut self, feature: F) -> Arc<F> {
84 let feat_rc: Arc<dyn Feature> = Arc::new(feature);
85
86 self.features
87 .insert(TypeId::of::<F>(), Arc::clone(&feat_rc));
88
89 Arc::downcast::<F>(feat_rc).unwrap()
90 }
91
92 /// Adds a new feature implementation to the list of available features.
93 /// This will override an existing implementation of the same type.
94 /// The caller is responsible for making sure the device actually supports
95 /// the feature.
96 ///
97 /// This method uses [`CreatableFeature`] to automatically create an
98 /// instance of the feature implementation and adds it using
99 /// [`Self::add_feature_instance`].
100 pub fn add_feature<F: CreatableFeature>(&mut self, feature_index: u8) -> Arc<F> {
101 self.add_feature_instance(F::new(
102 Arc::clone(&self.chan),
103 self.device_index,
104 feature_index,
105 ))
106 }
107
108 /// Checks whether a specific feature implementation is provided by the
109 /// device.
110 pub fn provides_feature<F: Feature>(&self) -> bool {
111 self.features.contains_key(&TypeId::of::<F>())
112 }
113
114 /// Tries to retrieve a feature implementation from the device.
115 ///
116 /// Returns [`None`] if the requested feature implementation is not
117 /// provided.
118 pub fn get_feature<F: Feature>(&self) -> Option<Arc<F>> {
119 self.features
120 .get(&TypeId::of::<F>())
121 .cloned()
122 .and_then(|feat| Arc::downcast::<F>(feat).ok())
123 }
124
125 /// Tries to detect all features supported by the device and add
126 /// implementations for them using [`feature::registry::lookup_version`].
127 ///
128 /// Returns a vector containing all feature IDs supported by the device.
129 ///
130 /// Returns `Ok(None)` if the [`FeatureSetFeature`] feature, which is
131 /// required for feature enumeration, is not supported by the device.
132 pub async fn enumerate_features(
133 &mut self,
134 ) -> Result<Option<Vec<FeatureInformation>>, Hidpp20Error> {
135 let Some(feature_set_info) = self.root().get_feature(FeatureSetFeature::ID).await? else {
136 return Ok(None);
137 };
138
139 let feature_set_feature = self.add_feature::<FeatureSetFeature>(feature_set_info.index);
140
141 let count = feature_set_feature.count().await?;
142 trace!(
143 index = self.device_index,
144 count, "enumerating feature table"
145 );
146 let mut features = Vec::with_capacity(count as usize);
147 for i in 1..=count {
148 let info = feature_set_feature.get_feature(i).await?;
149 trace!(
150 index = self.device_index,
151 slot = i,
152 id = format_args!("{:#06x}", info.id),
153 version = info.version,
154 "feature",
155 );
156 features.push(info);
157
158 if i == feature_set_info.index {
159 continue;
160 }
161
162 let Some(impls) = feature::registry::lookup_version(info.id, info.version) else {
163 continue;
164 };
165
166 for feat_impl in impls {
167 let (type_id, instance) =
168 (feat_impl.producer)(Arc::clone(&self.chan), self.device_index, i);
169
170 self.features.insert(type_id, instance);
171 }
172 }
173
174 Ok(Some(features))
175 }
176}
177
178/// Represents a device-specific error.
179#[derive(Debug, Error)]
180#[non_exhaustive]
181pub enum DeviceError {
182 /// Indicates that the underlying [`HidppChannel`] returned an error.
183 #[error("the HID++ channel returned an error")]
184 Channel(#[from] ChannelError),
185
186 /// Indicates that the specified device index points to no device.
187 #[error("there is no device with the specified device index")]
188 DeviceNotFound,
189
190 /// Indicates that the addressed device does only support HID++1.0.
191 #[error("the device does not support HID++2.0 or newer")]
192 UnsupportedProtocolVersion,
193}