1#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8use std::collections::HashSet;
9use std::fmt;
10
11#[derive(Debug, Clone, Copy, PartialEq, Default)]
24#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
25pub struct LaserPoint {
26 pub x: f32,
28 pub y: f32,
30 pub r: u16,
32 pub g: u16,
34 pub b: u16,
36 pub intensity: u16,
38}
39
40impl LaserPoint {
41 pub fn new(x: f32, y: f32, r: u16, g: u16, b: u16, intensity: u16) -> Self {
43 Self {
44 x,
45 y,
46 r,
47 g,
48 b,
49 intensity,
50 }
51 }
52
53 pub fn blanked(x: f32, y: f32) -> Self {
55 Self {
56 x,
57 y,
58 ..Default::default()
59 }
60 }
61}
62
63#[derive(Debug, Clone, PartialEq, Eq, Hash)]
65#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
66pub enum DacType {
67 Helios,
69 EtherDream,
71 Idn,
73 LasercubeWifi,
75 LasercubeUsb,
77 Custom(String),
79}
80
81impl DacType {
82 pub fn all() -> &'static [DacType] {
84 &[
85 DacType::Helios,
86 DacType::EtherDream,
87 DacType::Idn,
88 DacType::LasercubeWifi,
89 DacType::LasercubeUsb,
90 ]
91 }
92
93 pub fn display_name(&self) -> &str {
95 match self {
96 DacType::Helios => "Helios",
97 DacType::EtherDream => "Ether Dream",
98 DacType::Idn => "IDN",
99 DacType::LasercubeWifi => "LaserCube WiFi",
100 DacType::LasercubeUsb => "LaserCube USB (Laserdock)",
101 DacType::Custom(name) => name,
102 }
103 }
104
105 pub fn description(&self) -> &'static str {
107 match self {
108 DacType::Helios => "USB laser DAC",
109 DacType::EtherDream => "Network laser DAC",
110 DacType::Idn => "ILDA Digital Network laser DAC",
111 DacType::LasercubeWifi => "WiFi laser DAC",
112 DacType::LasercubeUsb => "USB laser DAC",
113 DacType::Custom(_) => "Custom DAC",
114 }
115 }
116}
117
118impl fmt::Display for DacType {
119 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120 write!(f, "{}", self.display_name())
121 }
122}
123
124#[derive(Debug, Clone, PartialEq, Eq)]
126#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
127pub struct EnabledDacTypes {
128 types: HashSet<DacType>,
129}
130
131impl EnabledDacTypes {
132 pub fn all() -> Self {
134 Self {
135 types: DacType::all().iter().cloned().collect(),
136 }
137 }
138
139 #[allow(dead_code)]
141 pub fn none() -> Self {
142 Self {
143 types: HashSet::new(),
144 }
145 }
146
147 pub fn is_enabled(&self, dac_type: DacType) -> bool {
149 self.types.contains(&dac_type)
150 }
151
152 pub fn enable(&mut self, dac_type: DacType) -> &mut Self {
168 self.types.insert(dac_type);
169 self
170 }
171
172 pub fn disable(&mut self, dac_type: DacType) -> &mut Self {
188 self.types.remove(&dac_type);
189 self
190 }
191
192 #[allow(dead_code)]
194 pub fn iter(&self) -> impl Iterator<Item = DacType> + '_ {
195 self.types.iter().cloned()
196 }
197
198 #[allow(dead_code)]
200 pub fn is_empty(&self) -> bool {
201 self.types.is_empty()
202 }
203}
204
205impl Default for EnabledDacTypes {
206 fn default() -> Self {
207 Self::all()
208 }
209}
210
211impl std::iter::FromIterator<DacType> for EnabledDacTypes {
212 fn from_iter<I: IntoIterator<Item = DacType>>(iter: I) -> Self {
213 Self {
214 types: iter.into_iter().collect(),
215 }
216 }
217}
218
219impl Extend<DacType> for EnabledDacTypes {
220 fn extend<I: IntoIterator<Item = DacType>>(&mut self, iter: I) {
221 self.types.extend(iter);
222 }
223}
224
225#[derive(Debug, Clone, PartialEq, Eq, Hash)]
228#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
229pub struct DacDevice {
230 pub name: String,
231 pub dac_type: DacType,
232}
233
234impl DacDevice {
235 pub fn new(name: String, dac_type: DacType) -> Self {
236 Self { name, dac_type }
237 }
238}
239
240#[derive(Debug, Clone, PartialEq, Eq, Hash)]
242#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
243pub enum DacConnectionState {
244 Connected { name: String },
246 Stopped { name: String },
248 Lost { name: String, error: Option<String> },
250}
251
252#[derive(Clone, Debug)]
258#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
259pub struct DacCapabilities {
260 pub pps_min: u32,
266 pub pps_max: u32,
268 pub max_points_per_chunk: usize,
270 pub prefers_constant_pps: bool,
272 pub can_estimate_queue: bool,
274 pub output_model: OutputModel,
276}
277
278impl Default for DacCapabilities {
279 fn default() -> Self {
280 Self {
281 pps_min: 1,
282 pps_max: 100_000,
283 max_points_per_chunk: 4096,
284 prefers_constant_pps: false,
285 can_estimate_queue: false,
286 output_model: OutputModel::NetworkFifo,
287 }
288 }
289}
290
291pub fn caps_for_dac_type(dac_type: &DacType) -> DacCapabilities {
298 match dac_type {
299 #[cfg(feature = "helios")]
300 DacType::Helios => crate::protocols::helios::default_capabilities(),
301 #[cfg(not(feature = "helios"))]
302 DacType::Helios => DacCapabilities::default(),
303
304 #[cfg(feature = "ether-dream")]
305 DacType::EtherDream => crate::protocols::ether_dream::default_capabilities(),
306 #[cfg(not(feature = "ether-dream"))]
307 DacType::EtherDream => DacCapabilities::default(),
308
309 #[cfg(feature = "idn")]
310 DacType::Idn => crate::protocols::idn::default_capabilities(),
311 #[cfg(not(feature = "idn"))]
312 DacType::Idn => DacCapabilities::default(),
313
314 #[cfg(feature = "lasercube-wifi")]
315 DacType::LasercubeWifi => crate::protocols::lasercube_wifi::default_capabilities(),
316 #[cfg(not(feature = "lasercube-wifi"))]
317 DacType::LasercubeWifi => DacCapabilities::default(),
318
319 #[cfg(feature = "lasercube-usb")]
320 DacType::LasercubeUsb => crate::protocols::lasercube_usb::default_capabilities(),
321 #[cfg(not(feature = "lasercube-usb"))]
322 DacType::LasercubeUsb => DacCapabilities::default(),
323
324 DacType::Custom(_) => DacCapabilities::default(),
325 }
326}
327
328#[derive(Clone, Debug, PartialEq, Eq)]
330#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
331pub enum OutputModel {
332 UsbFrameSwap,
334 NetworkFifo,
336 UdpTimed,
338}
339
340#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
342#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
343pub struct StreamInstant(pub u64);
344
345impl StreamInstant {
346 pub fn new(points: u64) -> Self {
348 Self(points)
349 }
350
351 pub fn points(&self) -> u64 {
353 self.0
354 }
355
356 pub fn as_seconds(&self, pps: u32) -> f64 {
358 self.0 as f64 / pps as f64
359 }
360
361 pub fn from_seconds(seconds: f64, pps: u32) -> Self {
363 Self((seconds * pps as f64) as u64)
364 }
365
366 pub fn add_points(&self, points: u64) -> Self {
368 Self(self.0.saturating_add(points))
369 }
370
371 pub fn sub_points(&self, points: u64) -> Self {
373 Self(self.0.saturating_sub(points))
374 }
375}
376
377impl std::ops::Add<u64> for StreamInstant {
378 type Output = Self;
379 fn add(self, rhs: u64) -> Self::Output {
380 self.add_points(rhs)
381 }
382}
383
384impl std::ops::Sub<u64> for StreamInstant {
385 type Output = Self;
386 fn sub(self, rhs: u64) -> Self::Output {
387 self.sub_points(rhs)
388 }
389}
390
391impl std::ops::AddAssign<u64> for StreamInstant {
392 fn add_assign(&mut self, rhs: u64) {
393 self.0 = self.0.saturating_add(rhs);
394 }
395}
396
397impl std::ops::SubAssign<u64> for StreamInstant {
398 fn sub_assign(&mut self, rhs: u64) {
399 self.0 = self.0.saturating_sub(rhs);
400 }
401}
402
403#[derive(Clone, Debug)]
405#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
406pub struct StreamConfig {
407 pub pps: u32,
409 pub chunk_points: Option<usize>,
411 pub target_queue_points: usize,
413 pub underrun: UnderrunPolicy,
415}
416
417impl Default for StreamConfig {
418 fn default() -> Self {
419 Self {
420 pps: 30_000,
421 chunk_points: None,
422 target_queue_points: 3000,
423 underrun: UnderrunPolicy::default(),
424 }
425 }
426}
427
428impl StreamConfig {
429 pub fn new(pps: u32) -> Self {
431 Self {
432 pps,
433 ..Default::default()
434 }
435 }
436
437 pub fn with_chunk_points(mut self, chunk_points: usize) -> Self {
439 self.chunk_points = Some(chunk_points);
440 self
441 }
442
443 pub fn with_target_queue_points(mut self, points: usize) -> Self {
445 self.target_queue_points = points;
446 self
447 }
448
449 pub fn with_underrun(mut self, policy: UnderrunPolicy) -> Self {
451 self.underrun = policy;
452 self
453 }
454}
455
456#[derive(Clone, Debug, PartialEq)]
458#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
459#[derive(Default)]
460pub enum UnderrunPolicy {
461 RepeatLast,
463 #[default]
465 Blank,
466 Park { x: f32, y: f32 },
468 Stop,
470}
471
472#[derive(Clone, Debug)]
474pub struct ChunkRequest {
475 pub start: StreamInstant,
477 pub pps: u32,
479 pub n_points: usize,
481 pub scheduled_ahead_points: u64,
483 pub device_queued_points: Option<u64>,
485}
486
487#[derive(Clone, Debug)]
489pub struct StreamStatus {
490 pub connected: bool,
492 pub chunk_points: usize,
494 pub scheduled_ahead_points: u64,
496 pub device_queued_points: Option<u64>,
498 pub stats: Option<StreamStats>,
500}
501
502#[derive(Clone, Debug, Default)]
504pub struct StreamStats {
505 pub underrun_count: u64,
507 pub late_chunk_count: u64,
509 pub reconnect_count: u64,
511 pub chunks_written: u64,
513 pub points_written: u64,
515}
516
517#[derive(Clone, Debug, PartialEq, Eq)]
519pub enum RunExit {
520 Stopped,
522 ProducerEnded,
524 Disconnected,
526}
527
528#[derive(Clone, Debug)]
530#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
531pub struct DacInfo {
532 pub id: String,
534 pub name: String,
536 pub kind: DacType,
538 pub caps: DacCapabilities,
540}
541
542impl DacInfo {
543 pub fn new(
545 id: impl Into<String>,
546 name: impl Into<String>,
547 kind: DacType,
548 caps: DacCapabilities,
549 ) -> Self {
550 Self {
551 id: id.into(),
552 name: name.into(),
553 kind,
554 caps,
555 }
556 }
557}
558
559#[cfg(test)]
560mod tests {
561 use super::*;
562
563 #[test]
568 fn test_laser_point_blanked_sets_all_colors_to_zero() {
569 let point = LaserPoint::blanked(0.25, 0.75);
571 assert_eq!(point.x, 0.25);
572 assert_eq!(point.y, 0.75);
573 assert_eq!(point.r, 0);
574 assert_eq!(point.g, 0);
575 assert_eq!(point.b, 0);
576 assert_eq!(point.intensity, 0);
577 }
578
579 #[test]
584 fn test_dac_type_all_returns_all_five_types() {
585 let all_types = DacType::all();
586 assert_eq!(all_types.len(), 5);
587 assert!(all_types.contains(&DacType::Helios));
588 assert!(all_types.contains(&DacType::EtherDream));
589 assert!(all_types.contains(&DacType::Idn));
590 assert!(all_types.contains(&DacType::LasercubeWifi));
591 assert!(all_types.contains(&DacType::LasercubeUsb));
592 }
593
594 #[test]
595 fn test_dac_type_display_uses_display_name() {
596 assert_eq!(
598 format!("{}", DacType::Helios),
599 DacType::Helios.display_name()
600 );
601 assert_eq!(
602 format!("{}", DacType::EtherDream),
603 DacType::EtherDream.display_name()
604 );
605 }
606
607 #[test]
608 fn test_dac_type_can_be_used_in_hashset() {
609 use std::collections::HashSet;
610
611 let mut set = HashSet::new();
612 set.insert(DacType::Helios);
613 set.insert(DacType::Helios); assert_eq!(set.len(), 1);
616 }
617
618 #[test]
623 fn test_enabled_dac_types_all_enables_everything() {
624 let enabled = EnabledDacTypes::all();
625 for dac_type in DacType::all() {
626 assert!(
627 enabled.is_enabled(dac_type.clone()),
628 "{:?} should be enabled",
629 dac_type
630 );
631 }
632 assert!(!enabled.is_empty());
633 }
634
635 #[test]
636 fn test_enabled_dac_types_none_disables_everything() {
637 let enabled = EnabledDacTypes::none();
638 for dac_type in DacType::all() {
639 assert!(
640 !enabled.is_enabled(dac_type.clone()),
641 "{:?} should be disabled",
642 dac_type
643 );
644 }
645 assert!(enabled.is_empty());
646 }
647
648 #[test]
649 fn test_enabled_dac_types_enable_disable_toggles_correctly() {
650 let mut enabled = EnabledDacTypes::none();
651
652 enabled.enable(DacType::Helios);
654 assert!(enabled.is_enabled(DacType::Helios));
655 assert!(!enabled.is_enabled(DacType::EtherDream));
656
657 enabled.enable(DacType::EtherDream);
659 assert!(enabled.is_enabled(DacType::Helios));
660 assert!(enabled.is_enabled(DacType::EtherDream));
661
662 enabled.disable(DacType::Helios);
664 assert!(!enabled.is_enabled(DacType::Helios));
665 assert!(enabled.is_enabled(DacType::EtherDream));
666 }
667
668 #[test]
669 fn test_enabled_dac_types_iter_only_returns_enabled() {
670 let mut enabled = EnabledDacTypes::none();
671 enabled.enable(DacType::Helios);
672 enabled.enable(DacType::Idn);
673
674 let types: Vec<DacType> = enabled.iter().collect();
675 assert_eq!(types.len(), 2);
676 assert!(types.contains(&DacType::Helios));
677 assert!(types.contains(&DacType::Idn));
678 assert!(!types.contains(&DacType::EtherDream));
679 }
680
681 #[test]
682 fn test_enabled_dac_types_default_enables_all() {
683 let enabled = EnabledDacTypes::default();
684 for dac_type in DacType::all() {
686 assert!(enabled.is_enabled(dac_type.clone()));
687 }
688 }
689
690 #[test]
691 fn test_enabled_dac_types_idempotent_operations() {
692 let mut enabled = EnabledDacTypes::none();
693
694 enabled.enable(DacType::Helios);
696 enabled.enable(DacType::Helios);
697 assert!(enabled.is_enabled(DacType::Helios));
698
699 enabled.disable(DacType::Helios);
701 enabled.disable(DacType::Helios);
702 assert!(!enabled.is_enabled(DacType::Helios));
703 }
704
705 #[test]
706 fn test_enabled_dac_types_chaining() {
707 let mut enabled = EnabledDacTypes::none();
708 enabled
709 .enable(DacType::Helios)
710 .enable(DacType::EtherDream)
711 .disable(DacType::Helios);
712
713 assert!(!enabled.is_enabled(DacType::Helios));
714 assert!(enabled.is_enabled(DacType::EtherDream));
715 }
716
717 #[test]
722 fn test_dac_connection_state_equality() {
723 let s1 = DacConnectionState::Connected {
724 name: "DAC1".to_string(),
725 };
726 let s2 = DacConnectionState::Connected {
727 name: "DAC1".to_string(),
728 };
729 let s3 = DacConnectionState::Connected {
730 name: "DAC2".to_string(),
731 };
732 let s4 = DacConnectionState::Lost {
733 name: "DAC1".to_string(),
734 error: None,
735 };
736
737 assert_eq!(s1, s2);
738 assert_ne!(s1, s3); assert_ne!(s1, s4); }
741}