rust_patlite_beacon/
lib.rs

1use anyhow::{anyhow, Result};
2use rusb::{Device, DeviceHandle, GlobalContext};
3use std::sync::{Arc, Mutex};
4use std::time::Duration;
5use thiserror::Error;
6use tokio::time::sleep;
7
8const VENDOR_ID: u16 = 0x191A;
9const DEVICE_ID: u16 = 0x6001;
10const COMMAND_VERSION: u8 = 0x0;
11const COMMAND_ID_CONTROL: u8 = 0x0;
12const COMMAND_ID_SETTING: u8 = 0x1;
13const COMMAND_ID_GETSTATE: u8 = 0x80;
14const ENDPOINT_ADDRESS: u8 = 0x01;
15const ENDPOINT_ADDRESS_GET: u8 = 0x81;
16const SEND_TIMEOUT: Duration = Duration::from_millis(1000);
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19#[repr(u8)]
20pub enum LedColor {
21    Off = 0,
22    Red = 1,
23    Green = 2,
24    Yellow = 3,
25    Blue = 4,
26    Purple = 5,
27    LightBlue = 6,
28    White = 7,
29    Keep = 0xF,
30}
31
32impl LedColor {
33    pub fn from_str(s: &str) -> Option<Self> {
34        match s.to_lowercase().as_str() {
35            "off" => Some(LedColor::Off),
36            "red" => Some(LedColor::Red),
37            "green" => Some(LedColor::Green),
38            "yellow" => Some(LedColor::Yellow),
39            "blue" => Some(LedColor::Blue),
40            "purple" => Some(LedColor::Purple),
41            "lightblue" | "light-blue" | "skyblue" | "sky-blue" => Some(LedColor::LightBlue),
42            "white" => Some(LedColor::White),
43            _ => None,
44        }
45    }
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49#[repr(u8)]
50pub enum LedPattern {
51    Off = 0x0,
52    On = 0x1,
53    Pattern1 = 0x2,
54    Pattern2 = 0x3,
55    Pattern3 = 0x4,
56    Pattern4 = 0x5,
57    Pattern5 = 0x6,
58    Pattern6 = 0x7,
59    Keep = 0xF,
60}
61
62impl LedPattern {
63    pub fn from_str(s: &str) -> Option<Self> {
64        match s.to_lowercase().as_str() {
65            "off" => Some(LedPattern::Off),
66            "on" | "solid" => Some(LedPattern::On),
67            "pattern1" | "1" => Some(LedPattern::Pattern1),
68            "pattern2" | "2" => Some(LedPattern::Pattern2),
69            "pattern3" | "3" => Some(LedPattern::Pattern3),
70            "pattern4" | "4" => Some(LedPattern::Pattern4),
71            "pattern5" | "5" => Some(LedPattern::Pattern5),
72            "pattern6" | "6" => Some(LedPattern::Pattern6),
73            _ => None,
74        }
75    }
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79#[repr(u8)]
80pub enum BuzzerPattern {
81    Off = 0x0,
82    On = 0x1,
83    Sweep = 0x2,
84    Intermittent = 0x3,
85    WeakAttention = 0x4,
86    StrongAttention = 0x5,
87    ShiningStar = 0x6,
88    LondonBridge = 0x7,
89    Keep = 0xF,
90}
91
92impl BuzzerPattern {
93    pub fn from_str(s: &str) -> Option<Self> {
94        match s.to_lowercase().as_str() {
95            "off" => Some(BuzzerPattern::Off),
96            "on" | "continuous" => Some(BuzzerPattern::On),
97            "sweep" => Some(BuzzerPattern::Sweep),
98            "intermittent" => Some(BuzzerPattern::Intermittent),
99            "weak" | "weak-attention" => Some(BuzzerPattern::WeakAttention),
100            "strong" | "strong-attention" => Some(BuzzerPattern::StrongAttention),
101            "shining-star" => Some(BuzzerPattern::ShiningStar),
102            "london-bridge" => Some(BuzzerPattern::LondonBridge),
103            _ => None,
104        }
105    }
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
109pub struct BuzzerCount(u8);
110
111impl BuzzerCount {
112    pub const CONTINUOUS: Self = Self(0x0);
113    pub const KEEP: Self = Self(0xF);
114
115    pub fn times(count: u8) -> Option<Self> {
116        if count >= 1 && count <= 14 {
117            Some(Self(count))
118        } else {
119            None
120        }
121    }
122
123    pub fn value(&self) -> u8 {
124        self.0
125    }
126}
127
128#[derive(Debug, Clone, Copy, PartialEq, Eq)]
129pub struct BuzzerVolume(u8);
130
131impl BuzzerVolume {
132    pub const OFF: Self = Self(0x0);
133    pub const MAX: Self = Self(0xA);
134    pub const KEEP: Self = Self(0xF);
135
136    pub fn level(level: u8) -> Option<Self> {
137        if level <= 0xA {
138            Some(Self(level))
139        } else {
140            None
141        }
142    }
143
144    pub fn value(&self) -> u8 {
145        self.0
146    }
147}
148
149#[derive(Debug, Clone, Copy, PartialEq, Eq)]
150#[repr(u8)]
151pub enum Setting {
152    Off = 0x0,
153    On = 0x1,
154}
155
156#[derive(Debug, Error)]
157pub enum BeaconError {
158    #[error("USB error: {0}")]
159    Usb(#[from] rusb::Error),
160    
161    #[error("Device not found")]
162    DeviceNotFound,
163    
164    #[error("Failed to send command")]
165    SendFailed,
166    
167    #[error("Invalid parameter")]
168    InvalidParameter,
169}
170
171pub struct Beacon {
172    handle: DeviceHandle<GlobalContext>,
173    _device: Device<GlobalContext>,
174}
175
176impl Beacon {
177    pub fn open() -> Result<Self> {
178        let devices = rusb::devices()?;
179        
180        for device in devices.iter() {
181            let device_desc = device.device_descriptor()?;
182            
183            if device_desc.vendor_id() == VENDOR_ID && device_desc.product_id() == DEVICE_ID {
184                let handle = device.open()?;
185                
186                // Detach kernel driver if necessary
187                if let Ok(active) = handle.kernel_driver_active(0) {
188                    if active {
189                        handle.detach_kernel_driver(0)?;
190                    }
191                }
192                
193                // Set configuration
194                handle.set_active_configuration(1)?;
195                
196                // Claim interface
197                handle.claim_interface(0)?;
198                
199                return Ok(Beacon {
200                    handle,
201                    _device: device,
202                });
203            }
204        }
205        
206        Err(anyhow!("Device not found"))
207    }
208
209    pub fn scan() -> Result<Vec<(u8, u8, u16, u16)>> {
210        let devices = rusb::devices()?;
211        let mut found = Vec::new();
212        
213        for device in devices.iter() {
214            let device_desc = device.device_descriptor()?;
215            
216            if device_desc.vendor_id() == VENDOR_ID && device_desc.product_id() == DEVICE_ID {
217                found.push((
218                    device.bus_number(),
219                    device.address(),
220                    device_desc.vendor_id(),
221                    device_desc.product_id(),
222                ));
223            }
224        }
225        
226        Ok(found)
227    }
228
229    fn send_command(&self, data: &[u8]) -> Result<()> {
230        let bytes_written = self.handle.write_bulk(ENDPOINT_ADDRESS, data, SEND_TIMEOUT)?;
231        
232        if bytes_written != data.len() {
233            return Err(anyhow!("Failed to send complete command"));
234        }
235        
236        Ok(())
237    }
238
239    pub fn set_light(&self, color: LedColor, pattern: LedPattern) -> Result<()> {
240        let buzzer_control = (BuzzerCount::KEEP.value() << 4) | BuzzerPattern::Keep as u8;
241        let led_control = ((color as u8) << 4) | (pattern as u8);
242        
243        let data = [
244            COMMAND_VERSION,
245            COMMAND_ID_CONTROL,
246            buzzer_control,
247            BuzzerVolume::KEEP.value(),
248            led_control,
249            0, 0, 0,
250        ];
251        
252        self.send_command(&data)
253    }
254
255    pub fn set_buzzer(&self, pattern: BuzzerPattern, count: BuzzerCount) -> Result<()> {
256        let buzzer_control = (count.value() << 4) | (pattern as u8);
257        let led_control = ((LedColor::Keep as u8) << 4) | (LedPattern::Keep as u8);
258        
259        let data = [
260            COMMAND_VERSION,
261            COMMAND_ID_CONTROL,
262            buzzer_control,
263            BuzzerVolume::KEEP.value(),
264            led_control,
265            0, 0, 0,
266        ];
267        
268        self.send_command(&data)
269    }
270
271    pub fn set_volume(&self, volume: BuzzerVolume) -> Result<()> {
272        let buzzer_control = (BuzzerCount::KEEP.value() << 4) | (BuzzerPattern::Keep as u8);
273        let led_control = ((LedColor::Keep as u8) << 4) | (LedPattern::Keep as u8);
274        
275        let data = [
276            COMMAND_VERSION,
277            COMMAND_ID_CONTROL,
278            buzzer_control,
279            volume.value(),
280            led_control,
281            0, 0, 0,
282        ];
283        
284        self.send_command(&data)
285    }
286
287    pub fn set_buzzer_ex(&self, pattern: BuzzerPattern, count: BuzzerCount, volume: BuzzerVolume) -> Result<()> {
288        let buzzer_control = (count.value() << 4) | (pattern as u8);
289        let led_control = ((LedColor::Keep as u8) << 4) | (LedPattern::Keep as u8);
290        
291        let data = [
292            COMMAND_VERSION,
293            COMMAND_ID_CONTROL,
294            buzzer_control,
295            volume.value(),
296            led_control,
297            0, 0, 0,
298        ];
299        
300        self.send_command(&data)
301    }
302
303    pub fn set_setting(&self, setting: Setting) -> Result<()> {
304        let data = [
305            COMMAND_VERSION,
306            COMMAND_ID_SETTING,
307            setting as u8,
308            0, 0, 0, 0, 0,
309        ];
310        
311        self.send_command(&data)
312    }
313
314    pub fn get_touch_sensor_state(&self) -> Result<bool> {
315        let data = [
316            COMMAND_VERSION,
317            COMMAND_ID_GETSTATE,
318            0, 0, 0, 0, 0, 0,
319        ];
320        
321        self.send_command(&data)?;
322        
323        let mut response = [0u8; 2];
324        self.handle.read_bulk(ENDPOINT_ADDRESS_GET, &mut response, SEND_TIMEOUT)?;
325        
326        Ok((response[1] & 1) == 1)
327    }
328
329    pub fn reset(&self) -> Result<()> {
330        let buzzer_control = (BuzzerCount::KEEP.value() << 4) | (BuzzerPattern::Off as u8);
331        let led_control = ((LedColor::Off as u8) << 4) | (LedPattern::Off as u8);
332        
333        let data = [
334            COMMAND_VERSION,
335            COMMAND_ID_CONTROL,
336            buzzer_control,
337            BuzzerVolume::KEEP.value(),
338            led_control,
339            0, 0, 0,
340        ];
341        
342        self.send_command(&data)
343    }
344
345    pub fn wait_for_touch_sync(&self) -> Result<()> {
346        let initial_state = self.get_touch_sensor_state()?;
347        
348        if initial_state {
349            while self.get_touch_sensor_state()? {
350                std::thread::sleep(Duration::from_millis(50));
351            }
352        }
353        
354        while !self.get_touch_sensor_state()? {
355            std::thread::sleep(Duration::from_millis(50));
356        }
357        
358        Ok(())
359    }
360
361    pub fn wait_for_touch_with_callback_sync<F>(&self, mut callback: F) -> Result<()>
362    where
363        F: FnMut(bool),
364    {
365        let initial_state = self.get_touch_sensor_state()?;
366        let mut last_state = initial_state;
367        callback(last_state);
368        
369        if initial_state {
370            while self.get_touch_sensor_state()? {
371                std::thread::sleep(Duration::from_millis(50));
372            }
373            last_state = false;
374            callback(last_state);
375        }
376        
377        while !self.get_touch_sensor_state()? {
378            std::thread::sleep(Duration::from_millis(50));
379        }
380        callback(true);
381        
382        Ok(())
383    }
384
385    pub fn poll_touch_sensor_sync<F>(&self, mut callback: F, poll_interval: Duration) -> Result<()>
386    where
387        F: FnMut(bool) -> bool,
388    {
389        loop {
390            let state = self.get_touch_sensor_state()?;
391            if !callback(state) {
392                break;
393            }
394            std::thread::sleep(poll_interval);
395        }
396        Ok(())
397    }
398
399}
400
401pub struct AsyncBeacon {
402    beacon: Arc<Mutex<Beacon>>,
403}
404
405impl AsyncBeacon {
406    pub fn new(beacon: Beacon) -> Self {
407        Self {
408            beacon: Arc::new(Mutex::new(beacon)),
409        }
410    }
411
412    pub async fn wait_for_touch(&self) -> Result<()> {
413        let initial_state = {
414            let beacon = self.beacon.lock().unwrap();
415            beacon.get_touch_sensor_state()?
416        };
417        
418        if initial_state {
419            loop {
420                let state = {
421                    let beacon = self.beacon.lock().unwrap();
422                    beacon.get_touch_sensor_state()?
423                };
424                if !state {
425                    break;
426                }
427                sleep(Duration::from_millis(50)).await;
428            }
429        }
430        
431        loop {
432            let state = {
433                let beacon = self.beacon.lock().unwrap();
434                beacon.get_touch_sensor_state()?
435            };
436            if state {
437                break;
438            }
439            sleep(Duration::from_millis(50)).await;
440        }
441        
442        Ok(())
443    }
444
445    pub async fn wait_for_touch_with_callback<F>(&self, mut callback: F) -> Result<()>
446    where
447        F: FnMut(bool),
448    {
449        let initial_state = {
450            let beacon = self.beacon.lock().unwrap();
451            beacon.get_touch_sensor_state()?
452        };
453        let mut last_state = initial_state;
454        callback(last_state);
455        
456        if initial_state {
457            loop {
458                let state = {
459                    let beacon = self.beacon.lock().unwrap();
460                    beacon.get_touch_sensor_state()?
461                };
462                if !state {
463                    last_state = false;
464                    callback(last_state);
465                    break;
466                }
467                sleep(Duration::from_millis(50)).await;
468            }
469        }
470        
471        loop {
472            let state = {
473                let beacon = self.beacon.lock().unwrap();
474                beacon.get_touch_sensor_state()?
475            };
476            if state {
477                callback(true);
478                break;
479            }
480            sleep(Duration::from_millis(50)).await;
481        }
482        
483        Ok(())
484    }
485
486    pub async fn poll_touch_sensor<F>(&self, mut callback: F, poll_interval: Duration) -> Result<()>
487    where
488        F: FnMut(bool) -> bool,
489    {
490        loop {
491            let state = {
492                let beacon = self.beacon.lock().unwrap();
493                beacon.get_touch_sensor_state()?
494            };
495            if !callback(state) {
496                break;
497            }
498            sleep(poll_interval).await;
499        }
500        Ok(())
501    }
502}
503
504impl Drop for Beacon {
505    fn drop(&mut self) {
506        // Release the interface when dropping
507        let _ = self.handle.release_interface(0);
508    }
509}
510
511#[cfg(test)]
512mod tests {
513    use super::*;
514
515    #[test]
516    fn test_led_color_from_str() {
517        assert_eq!(LedColor::from_str("off"), Some(LedColor::Off));
518        assert_eq!(LedColor::from_str("red"), Some(LedColor::Red));
519        assert_eq!(LedColor::from_str("RED"), Some(LedColor::Red));
520        assert_eq!(LedColor::from_str("green"), Some(LedColor::Green));
521        assert_eq!(LedColor::from_str("yellow"), Some(LedColor::Yellow));
522        assert_eq!(LedColor::from_str("blue"), Some(LedColor::Blue));
523        assert_eq!(LedColor::from_str("purple"), Some(LedColor::Purple));
524        assert_eq!(LedColor::from_str("lightblue"), Some(LedColor::LightBlue));
525        assert_eq!(LedColor::from_str("light-blue"), Some(LedColor::LightBlue));
526        assert_eq!(LedColor::from_str("skyblue"), Some(LedColor::LightBlue));
527        assert_eq!(LedColor::from_str("sky-blue"), Some(LedColor::LightBlue));
528        assert_eq!(LedColor::from_str("white"), Some(LedColor::White));
529        assert_eq!(LedColor::from_str("invalid"), None);
530    }
531
532    #[test]
533    fn test_led_color_values() {
534        assert_eq!(LedColor::Off as u8, 0);
535        assert_eq!(LedColor::Red as u8, 1);
536        assert_eq!(LedColor::Green as u8, 2);
537        assert_eq!(LedColor::Yellow as u8, 3);
538        assert_eq!(LedColor::Blue as u8, 4);
539        assert_eq!(LedColor::Purple as u8, 5);
540        assert_eq!(LedColor::LightBlue as u8, 6);
541        assert_eq!(LedColor::White as u8, 7);
542        assert_eq!(LedColor::Keep as u8, 0xF);
543    }
544
545    #[test]
546    fn test_led_pattern_from_str() {
547        assert_eq!(LedPattern::from_str("off"), Some(LedPattern::Off));
548        assert_eq!(LedPattern::from_str("on"), Some(LedPattern::On));
549        assert_eq!(LedPattern::from_str("solid"), Some(LedPattern::On));
550        assert_eq!(LedPattern::from_str("pattern1"), Some(LedPattern::Pattern1));
551        assert_eq!(LedPattern::from_str("1"), Some(LedPattern::Pattern1));
552        assert_eq!(LedPattern::from_str("pattern2"), Some(LedPattern::Pattern2));
553        assert_eq!(LedPattern::from_str("2"), Some(LedPattern::Pattern2));
554        assert_eq!(LedPattern::from_str("pattern3"), Some(LedPattern::Pattern3));
555        assert_eq!(LedPattern::from_str("3"), Some(LedPattern::Pattern3));
556        assert_eq!(LedPattern::from_str("pattern4"), Some(LedPattern::Pattern4));
557        assert_eq!(LedPattern::from_str("4"), Some(LedPattern::Pattern4));
558        assert_eq!(LedPattern::from_str("pattern5"), Some(LedPattern::Pattern5));
559        assert_eq!(LedPattern::from_str("5"), Some(LedPattern::Pattern5));
560        assert_eq!(LedPattern::from_str("pattern6"), Some(LedPattern::Pattern6));
561        assert_eq!(LedPattern::from_str("6"), Some(LedPattern::Pattern6));
562        assert_eq!(LedPattern::from_str("PATTERN1"), Some(LedPattern::Pattern1));
563        assert_eq!(LedPattern::from_str("invalid"), None);
564    }
565
566    #[test]
567    fn test_led_pattern_values() {
568        assert_eq!(LedPattern::Off as u8, 0x0);
569        assert_eq!(LedPattern::On as u8, 0x1);
570        assert_eq!(LedPattern::Pattern1 as u8, 0x2);
571        assert_eq!(LedPattern::Pattern2 as u8, 0x3);
572        assert_eq!(LedPattern::Pattern3 as u8, 0x4);
573        assert_eq!(LedPattern::Pattern4 as u8, 0x5);
574        assert_eq!(LedPattern::Pattern5 as u8, 0x6);
575        assert_eq!(LedPattern::Pattern6 as u8, 0x7);
576        assert_eq!(LedPattern::Keep as u8, 0xF);
577    }
578
579    #[test]
580    fn test_buzzer_pattern_from_str() {
581        assert_eq!(BuzzerPattern::from_str("off"), Some(BuzzerPattern::Off));
582        assert_eq!(BuzzerPattern::from_str("on"), Some(BuzzerPattern::On));
583        assert_eq!(BuzzerPattern::from_str("continuous"), Some(BuzzerPattern::On));
584        assert_eq!(BuzzerPattern::from_str("sweep"), Some(BuzzerPattern::Sweep));
585        assert_eq!(BuzzerPattern::from_str("intermittent"), Some(BuzzerPattern::Intermittent));
586        assert_eq!(BuzzerPattern::from_str("weak"), Some(BuzzerPattern::WeakAttention));
587        assert_eq!(BuzzerPattern::from_str("weak-attention"), Some(BuzzerPattern::WeakAttention));
588        assert_eq!(BuzzerPattern::from_str("strong"), Some(BuzzerPattern::StrongAttention));
589        assert_eq!(BuzzerPattern::from_str("strong-attention"), Some(BuzzerPattern::StrongAttention));
590        assert_eq!(BuzzerPattern::from_str("shining-star"), Some(BuzzerPattern::ShiningStar));
591        assert_eq!(BuzzerPattern::from_str("london-bridge"), Some(BuzzerPattern::LondonBridge));
592        assert_eq!(BuzzerPattern::from_str("SWEEP"), Some(BuzzerPattern::Sweep));
593        assert_eq!(BuzzerPattern::from_str("invalid"), None);
594    }
595
596    #[test]
597    fn test_buzzer_pattern_values() {
598        assert_eq!(BuzzerPattern::Off as u8, 0x0);
599        assert_eq!(BuzzerPattern::On as u8, 0x1);
600        assert_eq!(BuzzerPattern::Sweep as u8, 0x2);
601        assert_eq!(BuzzerPattern::Intermittent as u8, 0x3);
602        assert_eq!(BuzzerPattern::WeakAttention as u8, 0x4);
603        assert_eq!(BuzzerPattern::StrongAttention as u8, 0x5);
604        assert_eq!(BuzzerPattern::ShiningStar as u8, 0x6);
605        assert_eq!(BuzzerPattern::LondonBridge as u8, 0x7);
606        assert_eq!(BuzzerPattern::Keep as u8, 0xF);
607    }
608
609    #[test]
610    fn test_buzzer_count_times() {
611        assert_eq!(BuzzerCount::times(0), None);
612        assert_eq!(BuzzerCount::times(1), Some(BuzzerCount(1)));
613        assert_eq!(BuzzerCount::times(7), Some(BuzzerCount(7)));
614        assert_eq!(BuzzerCount::times(14), Some(BuzzerCount(14)));
615        assert_eq!(BuzzerCount::times(15), None);
616        assert_eq!(BuzzerCount::times(100), None);
617    }
618
619    #[test]
620    fn test_buzzer_count_constants() {
621        assert_eq!(BuzzerCount::CONTINUOUS.value(), 0x0);
622        assert_eq!(BuzzerCount::KEEP.value(), 0xF);
623    }
624
625    #[test]
626    fn test_buzzer_count_value() {
627        let count = BuzzerCount::times(5).unwrap();
628        assert_eq!(count.value(), 5);
629    }
630
631    #[test]
632    fn test_buzzer_volume_level() {
633        assert_eq!(BuzzerVolume::level(0), Some(BuzzerVolume(0)));
634        assert_eq!(BuzzerVolume::level(5), Some(BuzzerVolume(5)));
635        assert_eq!(BuzzerVolume::level(10), Some(BuzzerVolume(10)));
636        assert_eq!(BuzzerVolume::level(11), None);
637        assert_eq!(BuzzerVolume::level(100), None);
638    }
639
640    #[test]
641    fn test_buzzer_volume_constants() {
642        assert_eq!(BuzzerVolume::OFF.value(), 0x0);
643        assert_eq!(BuzzerVolume::MAX.value(), 0xA);
644        assert_eq!(BuzzerVolume::KEEP.value(), 0xF);
645    }
646
647    #[test]
648    fn test_buzzer_volume_value() {
649        let volume = BuzzerVolume::level(7).unwrap();
650        assert_eq!(volume.value(), 7);
651    }
652
653    #[test]
654    fn test_setting_values() {
655        assert_eq!(Setting::Off as u8, 0x0);
656        assert_eq!(Setting::On as u8, 0x1);
657    }
658
659    #[test]
660    fn test_command_byte_generation_led() {
661        let color = LedColor::Red as u8;
662        let pattern = LedPattern::Pattern1 as u8;
663        let led_control = (color << 4) | pattern;
664        assert_eq!(led_control, 0x12);
665
666        let color = LedColor::Green as u8;
667        let pattern = LedPattern::On as u8;
668        let led_control = (color << 4) | pattern;
669        assert_eq!(led_control, 0x21);
670
671        let color = LedColor::Keep as u8;
672        let pattern = LedPattern::Keep as u8;
673        let led_control = (color << 4) | pattern;
674        assert_eq!(led_control, 0xFF);
675    }
676
677    #[test]
678    fn test_command_byte_generation_buzzer() {
679        let count = BuzzerCount::times(3).unwrap().value();
680        let pattern = BuzzerPattern::Sweep as u8;
681        let buzzer_control = (count << 4) | pattern;
682        assert_eq!(buzzer_control, 0x32);
683
684        let count = BuzzerCount::CONTINUOUS.value();
685        let pattern = BuzzerPattern::On as u8;
686        let buzzer_control = (count << 4) | pattern;
687        assert_eq!(buzzer_control, 0x01);
688
689        let count = BuzzerCount::KEEP.value();
690        let pattern = BuzzerPattern::Keep as u8;
691        let buzzer_control = (count << 4) | pattern;
692        assert_eq!(buzzer_control, 0xFF);
693    }
694
695    #[test]
696    fn test_full_command_construction() {
697        let buzzer_count = BuzzerCount::times(2).unwrap().value();
698        let buzzer_pattern = BuzzerPattern::Intermittent as u8;
699        let buzzer_control = (buzzer_count << 4) | buzzer_pattern;
700        
701        let led_color = LedColor::Blue as u8;
702        let led_pattern = LedPattern::Pattern3 as u8;
703        let led_control = (led_color << 4) | led_pattern;
704        
705        let volume = BuzzerVolume::level(5).unwrap().value();
706        
707        let data = [
708            COMMAND_VERSION,
709            COMMAND_ID_CONTROL,
710            buzzer_control,
711            volume,
712            led_control,
713            0, 0, 0,
714        ];
715        
716        assert_eq!(data[0], 0x0);
717        assert_eq!(data[1], 0x0);
718        assert_eq!(data[2], 0x23);
719        assert_eq!(data[3], 0x5);
720        assert_eq!(data[4], 0x44); 
721    }
722
723    #[test]
724    fn test_touch_sensor_command_construction() {
725        let data = [
726            COMMAND_VERSION,
727            COMMAND_ID_GETSTATE,
728            0, 0, 0, 0, 0, 0,
729        ];
730        
731        assert_eq!(data[0], 0x0);
732        assert_eq!(data[1], 0x80);
733        assert_eq!(data.len(), 8);
734    }
735
736    #[test]
737    fn test_async_beacon_creation() {
738        // This test verifies that AsyncBeacon can be created
739        // Real device testing would require hardware
740        
741        // We can't create a real Beacon without hardware, but we can
742        // verify the type system works
743        fn _type_check() {
744            // This function is never called, just type-checked
745            fn create_async_beacon(beacon: Beacon) -> AsyncBeacon {
746                AsyncBeacon::new(beacon)
747            }
748        }
749    }
750
751    #[test]
752    fn test_poll_interval_duration() {
753        use std::time::Duration;
754        
755        // Test that Duration creation works correctly for polling
756        let poll_interval = Duration::from_millis(50);
757        assert_eq!(poll_interval.as_millis(), 50);
758        
759        let poll_interval = Duration::from_millis(100);
760        assert_eq!(poll_interval.as_millis(), 100);
761    }
762}