openlogi_device/inventory/
events.rs1use std::sync::atomic::{AtomicUsize, Ordering};
10use std::sync::{Arc, PoisonError, RwLock};
11
12use hidpp::channel::{HidppChannel, HidppMessage, MessageListenerGuard};
13use hidpp::feature::unified_battery::BatteryEvent;
14use hidpp::feature::wireless_device_status::WirelessDeviceStatusEvent;
15use hidpp::protocol::{v10, v20};
16use hidpp::receiver::{bolt, unifying};
17use serde::{Deserialize, Serialize};
18use tokio::sync::mpsc;
19
20use crate::ReceiverProtocol;
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub enum HidppEventSource {
26 ReceiverConnection,
28 WirelessDeviceStatus,
30 UnifiedBattery,
32}
33
34#[derive(Clone)]
40pub struct EventNotifier {
41 sender: mpsc::Sender<HidppEventSource>,
42}
43
44impl EventNotifier {
45 fn notify(&self, source: HidppEventSource) {
46 let _ = self.sender.try_send(source);
47 }
48}
49
50pub type EventReceiver = mpsc::Receiver<HidppEventSource>;
52
53#[must_use]
55pub fn event_channel() -> (EventNotifier, EventReceiver) {
56 let (sender, receiver) = mpsc::channel(1);
57 (EventNotifier { sender }, receiver)
58}
59
60#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
66pub(super) struct EventFeatureIndices {
67 pub(super) wireless_status: Option<u8>,
68 pub(super) unified_battery: Option<u8>,
69}
70
71impl EventFeatureIndices {
72 pub(super) fn from_feature_ids(ids: &[u16]) -> Self {
73 let mut indices = Self::default();
74 for (position, id) in ids.iter().copied().enumerate() {
75 let Ok(index) = u8::try_from(position + 1) else {
76 break;
77 };
78 match id {
79 0x1d4b => indices.wireless_status = Some(index),
80 0x1004 => indices.unified_battery = Some(index),
81 _ => {}
82 }
83 }
84 indices
85 }
86
87 fn recognizes(self, message: &v20::Message) -> Option<HidppEventSource> {
88 let header = message.header();
89 if header.software_id.to_lo() != 0 {
90 return None;
91 }
92 let function_id = header.function_id.to_lo();
93 let payload = message.extend_payload();
94 if self.wireless_status == Some(header.feature_index)
95 && WirelessDeviceStatusEvent::decode(function_id, &payload).is_some()
96 {
97 return Some(HidppEventSource::WirelessDeviceStatus);
98 }
99 if self.unified_battery == Some(header.feature_index)
100 && BatteryEvent::decode(function_id, &payload).is_some()
101 {
102 return Some(HidppEventSource::UnifiedBattery);
103 }
104 None
105 }
106}
107
108#[derive(Clone, Copy)]
109struct DeviceEvents {
110 device_index: u8,
111 features: EventFeatureIndices,
112}
113
114struct SubscriptionState {
115 protocol: Option<ReceiverProtocol>,
116 devices: RwLock<Vec<DeviceEvents>>,
117 receiver_snapshot_depth: AtomicUsize,
118 notifier: EventNotifier,
119}
120
121impl SubscriptionState {
122 fn decode(&self, raw: HidppMessage, matched: bool) -> Option<HidppEventSource> {
123 if matched {
124 return None;
125 }
126
127 let receiver_connection = match self.protocol {
128 Some(ReceiverProtocol::Bolt) => matches!(
129 bolt::decode_notification(&v10::Message::from(raw)),
130 Some(bolt::Event::DeviceConnection(_))
131 ),
132 Some(ReceiverProtocol::Unifying) => matches!(
133 unifying::decode_notification(&v10::Message::from(raw)),
134 Some(unifying::Event::DeviceConnection(_))
135 ),
136 None => false,
137 };
138 if receiver_connection {
139 return (self.receiver_snapshot_depth.load(Ordering::Acquire) == 0)
140 .then_some(HidppEventSource::ReceiverConnection);
141 }
142
143 let message = v20::Message::from(raw);
144 let device_index = message.header().device_index;
145 self.devices
146 .read()
147 .unwrap_or_else(PoisonError::into_inner)
148 .iter()
149 .find(|device| device.device_index == device_index)
150 .and_then(|device| device.features.recognizes(&message))
151 }
152}
153
154#[derive(Clone)]
156pub(super) struct EventSubscriptionHandle {
157 state: Arc<SubscriptionState>,
158}
159
160impl EventSubscriptionHandle {
161 pub(super) fn register_device(&self, device_index: u8, features: EventFeatureIndices) {
166 let mut devices = self
167 .state
168 .devices
169 .write()
170 .unwrap_or_else(PoisonError::into_inner);
171 if let Some(device) = devices
172 .iter_mut()
173 .find(|device| device.device_index == device_index)
174 {
175 device.features = features;
176 } else {
177 devices.push(DeviceEvents {
178 device_index,
179 features,
180 });
181 }
182 }
183
184 pub(super) fn begin_receiver_snapshot(&self) -> ReceiverSnapshotGuard {
188 self.state
189 .receiver_snapshot_depth
190 .fetch_add(1, Ordering::AcqRel);
191 ReceiverSnapshotGuard {
192 state: Arc::clone(&self.state),
193 }
194 }
195}
196
197pub(super) struct ChannelEventSubscriptions {
199 handle: EventSubscriptionHandle,
200 _listener: MessageListenerGuard,
201}
202
203impl ChannelEventSubscriptions {
204 pub(super) fn attach(
205 channel: &Arc<HidppChannel>,
206 protocol: Option<ReceiverProtocol>,
207 notifier: EventNotifier,
208 ) -> Self {
209 let state = Arc::new(SubscriptionState {
210 protocol,
211 devices: RwLock::new(Vec::new()),
212 receiver_snapshot_depth: AtomicUsize::new(0),
213 notifier,
214 });
215 let listener = channel.add_msg_listener_guarded({
216 let state = Arc::clone(&state);
217 move |raw, matched| {
218 if let Some(source) = state.decode(raw, matched) {
219 state.notifier.notify(source);
220 }
221 }
222 });
223 Self {
224 handle: EventSubscriptionHandle { state },
225 _listener: listener,
226 }
227 }
228
229 pub(super) fn handle(&self) -> EventSubscriptionHandle {
230 self.handle.clone()
231 }
232}
233
234pub(super) struct ReceiverSnapshotGuard {
236 state: Arc<SubscriptionState>,
237}
238
239impl Drop for ReceiverSnapshotGuard {
240 fn drop(&mut self) {
241 self.state
242 .receiver_snapshot_depth
243 .fetch_sub(1, Ordering::AcqRel);
244 }
245}
246
247#[cfg(test)]
248mod tests {
249 use hidpp::nibble::U4;
250 use hidpp::protocol::{v10, v20};
251
252 use super::*;
253
254 fn state(protocol: Option<ReceiverProtocol>) -> Arc<SubscriptionState> {
255 let (notifier, _receiver) = event_channel();
256 Arc::new(SubscriptionState {
257 protocol,
258 devices: RwLock::new(Vec::new()),
259 receiver_snapshot_depth: AtomicUsize::new(0),
260 notifier,
261 })
262 }
263
264 #[test]
265 fn feature_indices_are_runtime_table_positions() {
266 assert_eq!(
267 EventFeatureIndices::from_feature_ids(&[0x0001, 0x1d4b, 0x1004]),
268 EventFeatureIndices {
269 wireless_status: Some(2),
270 unified_battery: Some(3),
271 }
272 );
273 }
274
275 #[test]
276 fn receiver_snapshot_suppresses_only_triggered_connection_wakeups() {
277 let state = state(Some(ReceiverProtocol::Unifying));
278 let raw = v10::Message::Short(
279 v10::MessageHeader {
280 device_index: 2,
281 sub_id: 0x41,
282 },
283 [0, 0x02, 0x34, 0x12],
284 )
285 .into();
286
287 assert_eq!(
288 state.decode(raw, false),
289 Some(HidppEventSource::ReceiverConnection)
290 );
291 state.receiver_snapshot_depth.store(1, Ordering::Release);
292 assert_eq!(state.decode(raw, false), None);
293 }
294
295 #[test]
296 fn receiver_snapshot_does_not_suppress_device_feature_events() {
297 let state = state(Some(ReceiverProtocol::Bolt));
298 state.devices.write().unwrap().push(DeviceEvents {
299 device_index: 2,
300 features: EventFeatureIndices {
301 wireless_status: Some(5),
302 unified_battery: None,
303 },
304 });
305 state.receiver_snapshot_depth.store(1, Ordering::Release);
306 let wireless = v20::Message::Long(
307 v20::MessageHeader {
308 device_index: 2,
309 feature_index: 5,
310 function_id: U4::from_lo(0),
311 software_id: U4::from_lo(0),
312 },
313 [0; 16],
314 )
315 .into();
316
317 assert_eq!(
318 state.decode(wireless, false),
319 Some(HidppEventSource::WirelessDeviceStatus)
320 );
321 }
322
323 #[test]
324 fn registered_device_events_decode_to_typed_sources() {
325 let state = state(None);
326 state.devices.write().unwrap().push(DeviceEvents {
327 device_index: 3,
328 features: EventFeatureIndices {
329 wireless_status: Some(5),
330 unified_battery: Some(7),
331 },
332 });
333
334 let wireless = v20::Message::Long(
335 v20::MessageHeader {
336 device_index: 3,
337 feature_index: 5,
338 function_id: U4::from_lo(0),
339 software_id: U4::from_lo(0),
340 },
341 [0; 16],
342 )
343 .into();
344 assert_eq!(
345 state.decode(wireless, false),
346 Some(HidppEventSource::WirelessDeviceStatus)
347 );
348
349 let mut payload = [0; 16];
350 payload[0] = 80;
351 payload[1] = 4;
352 let battery = v20::Message::Long(
353 v20::MessageHeader {
354 device_index: 3,
355 feature_index: 7,
356 function_id: U4::from_lo(0),
357 software_id: U4::from_lo(0),
358 },
359 payload,
360 )
361 .into();
362 assert_eq!(
363 state.decode(battery, false),
364 Some(HidppEventSource::UnifiedBattery)
365 );
366 }
367
368 #[test]
369 fn event_requests_are_bounded_and_coalesced() {
370 let (notifier, mut receiver) = event_channel();
371 notifier.notify(HidppEventSource::ReceiverConnection);
372 notifier.notify(HidppEventSource::UnifiedBattery);
373
374 assert_eq!(
375 receiver.try_recv(),
376 Ok(HidppEventSource::ReceiverConnection)
377 );
378 assert_eq!(receiver.try_recv(), Err(mpsc::error::TryRecvError::Empty));
379 }
380}