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};
18#[cfg(test)]
19use tokio::sync::Notify;
20use tokio::sync::mpsc;
21
22use crate::ReceiverProtocol;
23
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
27pub enum HidppEventSource {
28 ReceiverConnection,
30 WirelessDeviceStatus,
32 UnifiedBattery,
34}
35
36#[derive(Clone)]
42pub struct EventNotifier {
43 sender: mpsc::Sender<HidppEventSource>,
44 #[cfg(test)]
45 observation: Option<Arc<EventObservation>>,
46}
47
48impl EventNotifier {
49 fn notify(&self, source: HidppEventSource) {
50 let _ = self.sender.try_send(source);
51 #[cfg(test)]
52 if let Some(observation) = &self.observation {
53 observation.count.fetch_add(1, Ordering::Release);
54 observation.changed.notify_waiters();
55 }
56 }
57}
58
59pub type EventReceiver = mpsc::Receiver<HidppEventSource>;
61
62#[must_use]
64pub fn event_channel() -> (EventNotifier, EventReceiver) {
65 let (sender, receiver) = mpsc::channel(1);
66 (
67 EventNotifier {
68 sender,
69 #[cfg(test)]
70 observation: None,
71 },
72 receiver,
73 )
74}
75
76#[cfg(test)]
77struct EventObservation {
78 count: AtomicUsize,
79 changed: Notify,
80}
81
82#[cfg(test)]
84pub(crate) struct EventObserver {
85 observation: Arc<EventObservation>,
86}
87
88#[cfg(test)]
89impl EventObserver {
90 pub(crate) async fn wait_for(&self, expected: usize) {
91 loop {
92 let changed = self.observation.changed.notified();
93 if self.observation.count.load(Ordering::Acquire) >= expected {
94 return;
95 }
96 changed.await;
97 }
98 }
99}
100
101#[cfg(test)]
103pub(crate) fn observed_event_channel() -> (EventNotifier, EventReceiver, EventObserver) {
104 let (sender, receiver) = mpsc::channel(1);
105 let observation = Arc::new(EventObservation {
106 count: AtomicUsize::new(0),
107 changed: Notify::new(),
108 });
109 (
110 EventNotifier {
111 sender,
112 observation: Some(Arc::clone(&observation)),
113 },
114 receiver,
115 EventObserver { observation },
116 )
117}
118
119#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
125pub(super) struct EventFeatureIndices {
126 pub(super) wireless_status: Option<u8>,
127 pub(super) unified_battery: Option<u8>,
128}
129
130impl EventFeatureIndices {
131 pub(super) fn from_feature_ids(ids: &[u16]) -> Self {
132 let mut indices = Self::default();
133 for (position, id) in ids.iter().copied().enumerate() {
134 let Ok(index) = u8::try_from(position + 1) else {
135 break;
136 };
137 match id {
138 0x1d4b => indices.wireless_status = Some(index),
139 0x1004 => indices.unified_battery = Some(index),
140 _ => {}
141 }
142 }
143 indices
144 }
145
146 fn recognizes(self, message: &v20::Message) -> Option<HidppEventSource> {
147 let header = message.header();
148 if header.software_id.to_lo() != 0 {
149 return None;
150 }
151 let function_id = header.function_id.to_lo();
152 let payload = message.extend_payload();
153 if self.wireless_status == Some(header.feature_index)
154 && WirelessDeviceStatusEvent::decode(function_id, &payload).is_some()
155 {
156 return Some(HidppEventSource::WirelessDeviceStatus);
157 }
158 if self.unified_battery == Some(header.feature_index)
159 && BatteryEvent::decode(function_id, &payload).is_some()
160 {
161 return Some(HidppEventSource::UnifiedBattery);
162 }
163 None
164 }
165}
166
167#[derive(Clone, Copy)]
168struct DeviceEvents {
169 device_index: u8,
170 features: EventFeatureIndices,
171}
172
173struct SubscriptionState {
174 protocol: Option<ReceiverProtocol>,
175 devices: RwLock<Vec<DeviceEvents>>,
176 receiver_snapshot_depth: AtomicUsize,
177 notifier: EventNotifier,
178}
179
180impl SubscriptionState {
181 fn decode(&self, raw: HidppMessage, matched: bool) -> Option<HidppEventSource> {
182 if matched {
183 return None;
184 }
185
186 let receiver_connection = match self.protocol {
187 Some(ReceiverProtocol::Bolt) => matches!(
188 bolt::decode_notification(&v10::Message::from(raw)),
189 Some(bolt::Event::DeviceConnection(_))
190 ),
191 Some(ReceiverProtocol::Unifying) => matches!(
192 unifying::decode_notification(&v10::Message::from(raw)),
193 Some(unifying::Event::DeviceConnection(_))
194 ),
195 None => false,
196 };
197 if receiver_connection {
198 return (self.receiver_snapshot_depth.load(Ordering::Acquire) == 0)
199 .then_some(HidppEventSource::ReceiverConnection);
200 }
201
202 let message = v20::Message::from(raw);
203 let device_index = message.header().device_index;
204 self.devices
205 .read()
206 .unwrap_or_else(PoisonError::into_inner)
207 .iter()
208 .find(|device| device.device_index == device_index)
209 .and_then(|device| device.features.recognizes(&message))
210 }
211}
212
213#[derive(Clone)]
215pub(super) struct EventSubscriptionHandle {
216 state: Arc<SubscriptionState>,
217}
218
219impl EventSubscriptionHandle {
220 pub(super) fn register_device(&self, device_index: u8, features: EventFeatureIndices) {
225 let mut devices = self
226 .state
227 .devices
228 .write()
229 .unwrap_or_else(PoisonError::into_inner);
230 if let Some(device) = devices
231 .iter_mut()
232 .find(|device| device.device_index == device_index)
233 {
234 device.features = features;
235 } else {
236 devices.push(DeviceEvents {
237 device_index,
238 features,
239 });
240 }
241 }
242
243 pub(super) fn begin_receiver_snapshot(&self) -> ReceiverSnapshotGuard {
247 self.state
248 .receiver_snapshot_depth
249 .fetch_add(1, Ordering::AcqRel);
250 ReceiverSnapshotGuard {
251 state: Arc::clone(&self.state),
252 }
253 }
254}
255
256pub(super) struct ChannelEventSubscriptions {
258 handle: EventSubscriptionHandle,
259 _listener: MessageListenerGuard,
260}
261
262impl ChannelEventSubscriptions {
263 pub(super) fn attach(
264 channel: &Arc<HidppChannel>,
265 protocol: Option<ReceiverProtocol>,
266 notifier: EventNotifier,
267 ) -> Self {
268 let state = Arc::new(SubscriptionState {
269 protocol,
270 devices: RwLock::new(Vec::new()),
271 receiver_snapshot_depth: AtomicUsize::new(0),
272 notifier,
273 });
274 let listener = channel.add_msg_listener_guarded({
275 let state = Arc::clone(&state);
276 move |raw, matched| {
277 if let Some(source) = state.decode(raw, matched) {
278 state.notifier.notify(source);
279 }
280 }
281 });
282 Self {
283 handle: EventSubscriptionHandle { state },
284 _listener: listener,
285 }
286 }
287
288 pub(super) fn handle(&self) -> EventSubscriptionHandle {
289 self.handle.clone()
290 }
291}
292
293pub(super) struct ReceiverSnapshotGuard {
295 state: Arc<SubscriptionState>,
296}
297
298impl Drop for ReceiverSnapshotGuard {
299 fn drop(&mut self) {
300 self.state
301 .receiver_snapshot_depth
302 .fetch_sub(1, Ordering::AcqRel);
303 }
304}
305
306#[cfg(test)]
307mod tests {
308 use hidpp::nibble::U4;
309 use hidpp::protocol::{v10, v20};
310
311 use super::*;
312
313 fn state(protocol: Option<ReceiverProtocol>) -> Arc<SubscriptionState> {
314 let (notifier, _receiver) = event_channel();
315 Arc::new(SubscriptionState {
316 protocol,
317 devices: RwLock::new(Vec::new()),
318 receiver_snapshot_depth: AtomicUsize::new(0),
319 notifier,
320 })
321 }
322
323 #[test]
324 fn feature_indices_are_runtime_table_positions() {
325 assert_eq!(
326 EventFeatureIndices::from_feature_ids(&[0x0001, 0x1d4b, 0x1004]),
327 EventFeatureIndices {
328 wireless_status: Some(2),
329 unified_battery: Some(3),
330 }
331 );
332 }
333
334 #[test]
335 fn receiver_snapshot_suppresses_only_triggered_connection_wakeups() {
336 let state = state(Some(ReceiverProtocol::Unifying));
337 let raw = v10::Message::Short(
338 v10::MessageHeader {
339 device_index: 2,
340 sub_id: 0x41,
341 },
342 [0, 0x02, 0x34, 0x12],
343 )
344 .into();
345
346 assert_eq!(
347 state.decode(raw, false),
348 Some(HidppEventSource::ReceiverConnection)
349 );
350 state.receiver_snapshot_depth.store(1, Ordering::Release);
351 assert_eq!(state.decode(raw, false), None);
352 }
353
354 #[test]
355 fn receiver_snapshot_does_not_suppress_device_feature_events() {
356 let state = state(Some(ReceiverProtocol::Bolt));
357 state.devices.write().unwrap().push(DeviceEvents {
358 device_index: 2,
359 features: EventFeatureIndices {
360 wireless_status: Some(5),
361 unified_battery: None,
362 },
363 });
364 state.receiver_snapshot_depth.store(1, Ordering::Release);
365 let wireless = v20::Message::Long(
366 v20::MessageHeader {
367 device_index: 2,
368 feature_index: 5,
369 function_id: U4::from_lo(0),
370 software_id: U4::from_lo(0),
371 },
372 [0; 16],
373 )
374 .into();
375
376 assert_eq!(
377 state.decode(wireless, false),
378 Some(HidppEventSource::WirelessDeviceStatus)
379 );
380 }
381
382 #[test]
383 fn registered_device_events_decode_to_typed_sources() {
384 let state = state(None);
385 state.devices.write().unwrap().push(DeviceEvents {
386 device_index: 3,
387 features: EventFeatureIndices {
388 wireless_status: Some(5),
389 unified_battery: Some(7),
390 },
391 });
392
393 let wireless = v20::Message::Long(
394 v20::MessageHeader {
395 device_index: 3,
396 feature_index: 5,
397 function_id: U4::from_lo(0),
398 software_id: U4::from_lo(0),
399 },
400 [0; 16],
401 )
402 .into();
403 assert_eq!(
404 state.decode(wireless, false),
405 Some(HidppEventSource::WirelessDeviceStatus)
406 );
407
408 let mut payload = [0; 16];
409 payload[0] = 80;
410 payload[1] = 4;
411 let battery = v20::Message::Long(
412 v20::MessageHeader {
413 device_index: 3,
414 feature_index: 7,
415 function_id: U4::from_lo(0),
416 software_id: U4::from_lo(0),
417 },
418 payload,
419 )
420 .into();
421 assert_eq!(
422 state.decode(battery, false),
423 Some(HidppEventSource::UnifiedBattery)
424 );
425 }
426
427 #[test]
428 fn event_requests_are_bounded_and_coalesced() {
429 let (notifier, mut receiver) = event_channel();
430 notifier.notify(HidppEventSource::ReceiverConnection);
431 notifier.notify(HidppEventSource::UnifiedBattery);
432
433 assert_eq!(
434 receiver.try_recv(),
435 Ok(HidppEventSource::ReceiverConnection)
436 );
437 assert_eq!(receiver.try_recv(), Err(mpsc::error::TryRecvError::Empty));
438 }
439}