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 Avb,
79 Custom(String),
81}
82
83impl DacType {
84 pub fn all() -> &'static [DacType] {
86 &[
87 DacType::Helios,
88 DacType::EtherDream,
89 DacType::Idn,
90 DacType::LasercubeWifi,
91 DacType::LasercubeUsb,
92 DacType::Avb,
93 ]
94 }
95
96 pub fn display_name(&self) -> &str {
98 match self {
99 DacType::Helios => "Helios",
100 DacType::EtherDream => "Ether Dream",
101 DacType::Idn => "IDN",
102 DacType::LasercubeWifi => "LaserCube WiFi",
103 DacType::LasercubeUsb => "LaserCube USB (Laserdock)",
104 DacType::Avb => "AVB Audio Device",
105 DacType::Custom(name) => name,
106 }
107 }
108
109 pub fn description(&self) -> &'static str {
111 match self {
112 DacType::Helios => "USB laser DAC",
113 DacType::EtherDream => "Network laser DAC",
114 DacType::Idn => "ILDA Digital Network laser DAC",
115 DacType::LasercubeWifi => "WiFi laser DAC",
116 DacType::LasercubeUsb => "USB laser DAC",
117 DacType::Avb => "AVB audio network output",
118 DacType::Custom(_) => "Custom DAC",
119 }
120 }
121}
122
123impl fmt::Display for DacType {
124 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125 write!(f, "{}", self.display_name())
126 }
127}
128
129#[derive(Debug, Clone, PartialEq, Eq)]
131#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
132pub struct EnabledDacTypes {
133 types: HashSet<DacType>,
134}
135
136impl EnabledDacTypes {
137 pub fn all() -> Self {
139 Self {
140 types: DacType::all().iter().cloned().collect(),
141 }
142 }
143
144 #[allow(dead_code)]
146 pub fn none() -> Self {
147 Self {
148 types: HashSet::new(),
149 }
150 }
151
152 pub fn is_enabled(&self, dac_type: DacType) -> bool {
154 self.types.contains(&dac_type)
155 }
156
157 pub fn enable(&mut self, dac_type: DacType) -> &mut Self {
173 self.types.insert(dac_type);
174 self
175 }
176
177 pub fn disable(&mut self, dac_type: DacType) -> &mut Self {
193 self.types.remove(&dac_type);
194 self
195 }
196
197 #[allow(dead_code)]
199 pub fn iter(&self) -> impl Iterator<Item = DacType> + '_ {
200 self.types.iter().cloned()
201 }
202
203 #[allow(dead_code)]
205 pub fn is_empty(&self) -> bool {
206 self.types.is_empty()
207 }
208}
209
210impl Default for EnabledDacTypes {
211 fn default() -> Self {
212 Self::all()
213 }
214}
215
216impl std::iter::FromIterator<DacType> for EnabledDacTypes {
217 fn from_iter<I: IntoIterator<Item = DacType>>(iter: I) -> Self {
218 Self {
219 types: iter.into_iter().collect(),
220 }
221 }
222}
223
224impl Extend<DacType> for EnabledDacTypes {
225 fn extend<I: IntoIterator<Item = DacType>>(&mut self, iter: I) {
226 self.types.extend(iter);
227 }
228}
229
230#[derive(Debug, Clone, PartialEq, Eq, Hash)]
233#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
234pub struct DacDevice {
235 pub name: String,
236 pub dac_type: DacType,
237}
238
239impl DacDevice {
240 pub fn new(name: String, dac_type: DacType) -> Self {
241 Self { name, dac_type }
242 }
243}
244
245#[derive(Debug, Clone, PartialEq, Eq, Hash)]
247#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
248pub enum DacConnectionState {
249 Connected { name: String },
251 Stopped { name: String },
253 Lost { name: String, error: Option<String> },
255}
256
257#[derive(Clone, Debug)]
263#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
264pub struct DacCapabilities {
265 pub pps_min: u32,
271 pub pps_max: u32,
273 pub max_points_per_chunk: usize,
275 pub output_model: OutputModel,
277}
278
279impl Default for DacCapabilities {
280 fn default() -> Self {
281 Self {
282 pps_min: 1,
283 pps_max: 100_000,
284 max_points_per_chunk: 4096,
285 output_model: OutputModel::NetworkFifo,
286 }
287 }
288}
289
290pub fn caps_for_dac_type(dac_type: &DacType) -> DacCapabilities {
297 match dac_type {
298 #[cfg(feature = "helios")]
299 DacType::Helios => crate::protocols::helios::default_capabilities(),
300 #[cfg(not(feature = "helios"))]
301 DacType::Helios => DacCapabilities::default(),
302
303 #[cfg(feature = "ether-dream")]
304 DacType::EtherDream => crate::protocols::ether_dream::default_capabilities(),
305 #[cfg(not(feature = "ether-dream"))]
306 DacType::EtherDream => DacCapabilities::default(),
307
308 #[cfg(feature = "idn")]
309 DacType::Idn => crate::protocols::idn::default_capabilities(),
310 #[cfg(not(feature = "idn"))]
311 DacType::Idn => DacCapabilities::default(),
312
313 #[cfg(feature = "lasercube-wifi")]
314 DacType::LasercubeWifi => crate::protocols::lasercube_wifi::default_capabilities(),
315 #[cfg(not(feature = "lasercube-wifi"))]
316 DacType::LasercubeWifi => DacCapabilities::default(),
317
318 #[cfg(feature = "lasercube-usb")]
319 DacType::LasercubeUsb => crate::protocols::lasercube_usb::default_capabilities(),
320 #[cfg(not(feature = "lasercube-usb"))]
321 DacType::LasercubeUsb => DacCapabilities::default(),
322
323 #[cfg(feature = "avb")]
324 DacType::Avb => crate::protocols::avb::default_capabilities(),
325 #[cfg(not(feature = "avb"))]
326 DacType::Avb => DacCapabilities::default(),
327
328 DacType::Custom(_) => DacCapabilities::default(),
329 }
330}
331
332#[derive(Clone, Debug, PartialEq, Eq)]
334#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
335pub enum OutputModel {
336 UsbFrameSwap,
338 NetworkFifo,
340 UdpTimed,
342}
343
344#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
358#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
359pub struct StreamInstant(pub u64);
360
361impl StreamInstant {
362 pub fn new(points: u64) -> Self {
364 Self(points)
365 }
366
367 pub fn points(&self) -> u64 {
369 self.0
370 }
371
372 pub fn as_seconds(&self, pps: u32) -> f64 {
374 self.0 as f64 / pps as f64
375 }
376
377 #[inline]
382 pub fn as_secs_f64(&self, pps: u32) -> f64 {
383 self.as_seconds(pps)
384 }
385
386 pub fn from_seconds(seconds: f64, pps: u32) -> Self {
388 Self((seconds * pps as f64) as u64)
389 }
390
391 pub fn add_points(&self, points: u64) -> Self {
393 Self(self.0.saturating_add(points))
394 }
395
396 pub fn sub_points(&self, points: u64) -> Self {
398 Self(self.0.saturating_sub(points))
399 }
400}
401
402impl std::ops::Add<u64> for StreamInstant {
403 type Output = Self;
404 fn add(self, rhs: u64) -> Self::Output {
405 self.add_points(rhs)
406 }
407}
408
409impl std::ops::Sub<u64> for StreamInstant {
410 type Output = Self;
411 fn sub(self, rhs: u64) -> Self::Output {
412 self.sub_points(rhs)
413 }
414}
415
416impl std::ops::AddAssign<u64> for StreamInstant {
417 fn add_assign(&mut self, rhs: u64) {
418 self.0 = self.0.saturating_add(rhs);
419 }
420}
421
422impl std::ops::SubAssign<u64> for StreamInstant {
423 fn sub_assign(&mut self, rhs: u64) {
424 self.0 = self.0.saturating_sub(rhs);
425 }
426}
427
428#[derive(Clone, Debug)]
442#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
443pub struct StreamConfig {
444 pub pps: u32,
446
447 #[cfg_attr(feature = "serde", serde(with = "duration_millis"))]
452 pub target_buffer: std::time::Duration,
453
454 #[cfg_attr(feature = "serde", serde(with = "duration_millis"))]
458 pub min_buffer: std::time::Duration,
459
460 pub underrun: UnderrunPolicy,
462
463 #[cfg_attr(feature = "serde", serde(with = "duration_millis"))]
469 pub drain_timeout: std::time::Duration,
470
471 #[cfg_attr(feature = "serde", serde(with = "duration_micros"))]
483 pub color_delay: std::time::Duration,
484
485 #[cfg_attr(feature = "serde", serde(with = "duration_micros"))]
498 pub startup_blank: std::time::Duration,
499}
500
501#[cfg(feature = "serde")]
502mod duration_millis {
503 use serde::{Deserialize, Deserializer, Serialize, Serializer};
504 use std::time::Duration;
505
506 pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
507 where
508 S: Serializer,
509 {
510 let millis = duration.as_millis().min(u64::MAX as u128) as u64;
513 millis.serialize(serializer)
514 }
515
516 pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
517 where
518 D: Deserializer<'de>,
519 {
520 let millis = u64::deserialize(deserializer)?;
521 Ok(Duration::from_millis(millis))
522 }
523}
524
525#[cfg(feature = "serde")]
526mod duration_micros {
527 use serde::{Deserialize, Deserializer, Serialize, Serializer};
528 use std::time::Duration;
529
530 pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
531 where
532 S: Serializer,
533 {
534 let micros = duration.as_micros().min(u64::MAX as u128) as u64;
535 micros.serialize(serializer)
536 }
537
538 pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
539 where
540 D: Deserializer<'de>,
541 {
542 let micros = u64::deserialize(deserializer)?;
543 Ok(Duration::from_micros(micros))
544 }
545}
546
547impl Default for StreamConfig {
548 fn default() -> Self {
549 use std::time::Duration;
550 Self {
551 pps: 30_000,
552 target_buffer: Duration::from_millis(20),
553 min_buffer: Duration::from_millis(8),
554 underrun: UnderrunPolicy::default(),
555 drain_timeout: Duration::from_secs(1),
556 color_delay: Duration::ZERO,
557 startup_blank: Duration::from_millis(1),
558 }
559 }
560}
561
562impl StreamConfig {
563 pub fn new(pps: u32) -> Self {
565 Self {
566 pps,
567 ..Default::default()
568 }
569 }
570
571 pub fn with_target_buffer(mut self, duration: std::time::Duration) -> Self {
576 self.target_buffer = duration;
577 self
578 }
579
580 pub fn with_min_buffer(mut self, duration: std::time::Duration) -> Self {
584 self.min_buffer = duration;
585 self
586 }
587
588 pub fn with_underrun(mut self, policy: UnderrunPolicy) -> Self {
590 self.underrun = policy;
591 self
592 }
593
594 pub fn with_drain_timeout(mut self, timeout: std::time::Duration) -> Self {
598 self.drain_timeout = timeout;
599 self
600 }
601
602 pub fn with_color_delay(mut self, delay: std::time::Duration) -> Self {
606 self.color_delay = delay;
607 self
608 }
609
610 pub fn with_startup_blank(mut self, duration: std::time::Duration) -> Self {
614 self.startup_blank = duration;
615 self
616 }
617}
618
619#[derive(Clone, Debug, PartialEq)]
621#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
622#[derive(Default)]
623pub enum UnderrunPolicy {
624 RepeatLast,
626 #[default]
628 Blank,
629 Park { x: f32, y: f32 },
631 Stop,
633}
634
635#[derive(Clone, Debug)]
652pub struct ChunkRequest {
653 pub start: StreamInstant,
658
659 pub pps: u32,
661
662 pub min_points: usize,
667
668 pub target_points: usize,
672
673 pub buffered_points: u64,
675
676 pub buffered: std::time::Duration,
678
679 pub device_queued_points: Option<u64>,
681}
682
683#[derive(Clone, Copy, Debug, PartialEq, Eq)]
695pub enum ChunkResult {
696 Filled(usize),
702
703 Starved,
708
709 End,
715}
716
717#[derive(Clone, Debug)]
719pub struct StreamStatus {
720 pub connected: bool,
722 pub scheduled_ahead_points: u64,
724 pub device_queued_points: Option<u64>,
726 pub stats: Option<StreamStats>,
728}
729
730#[derive(Clone, Debug, Default)]
732pub struct StreamStats {
733 pub underrun_count: u64,
735 pub late_chunk_count: u64,
737 pub reconnect_count: u64,
739 pub chunks_written: u64,
741 pub points_written: u64,
743}
744
745#[derive(Clone, Debug, PartialEq, Eq)]
747pub enum RunExit {
748 Stopped,
750 ProducerEnded,
752 Disconnected,
754}
755
756#[derive(Clone, Debug)]
758#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
759pub struct DacInfo {
760 pub id: String,
762 pub name: String,
764 pub kind: DacType,
766 pub caps: DacCapabilities,
768}
769
770impl DacInfo {
771 pub fn new(
773 id: impl Into<String>,
774 name: impl Into<String>,
775 kind: DacType,
776 caps: DacCapabilities,
777 ) -> Self {
778 Self {
779 id: id.into(),
780 name: name.into(),
781 kind,
782 caps,
783 }
784 }
785}
786
787#[cfg(test)]
788mod tests {
789 use super::*;
790
791 #[test]
796 fn test_laser_point_blanked_sets_all_colors_to_zero() {
797 let point = LaserPoint::blanked(0.25, 0.75);
799 assert_eq!(point.x, 0.25);
800 assert_eq!(point.y, 0.75);
801 assert_eq!(point.r, 0);
802 assert_eq!(point.g, 0);
803 assert_eq!(point.b, 0);
804 assert_eq!(point.intensity, 0);
805 }
806
807 #[test]
812 fn test_dac_type_all_returns_all_builtin_types() {
813 let all_types = DacType::all();
814 assert_eq!(all_types.len(), 6);
815 assert!(all_types.contains(&DacType::Helios));
816 assert!(all_types.contains(&DacType::EtherDream));
817 assert!(all_types.contains(&DacType::Idn));
818 assert!(all_types.contains(&DacType::LasercubeWifi));
819 assert!(all_types.contains(&DacType::LasercubeUsb));
820 assert!(all_types.contains(&DacType::Avb));
821 }
822
823 #[test]
824 fn test_dac_type_display_uses_display_name() {
825 assert_eq!(
827 format!("{}", DacType::Helios),
828 DacType::Helios.display_name()
829 );
830 assert_eq!(
831 format!("{}", DacType::EtherDream),
832 DacType::EtherDream.display_name()
833 );
834 }
835
836 #[test]
837 fn test_dac_type_can_be_used_in_hashset() {
838 use std::collections::HashSet;
839
840 let mut set = HashSet::new();
841 set.insert(DacType::Helios);
842 set.insert(DacType::Helios); assert_eq!(set.len(), 1);
845 }
846
847 #[test]
852 fn test_enabled_dac_types_all_enables_everything() {
853 let enabled = EnabledDacTypes::all();
854 for dac_type in DacType::all() {
855 assert!(
856 enabled.is_enabled(dac_type.clone()),
857 "{:?} should be enabled",
858 dac_type
859 );
860 }
861 assert!(!enabled.is_empty());
862 }
863
864 #[test]
865 fn test_enabled_dac_types_none_disables_everything() {
866 let enabled = EnabledDacTypes::none();
867 for dac_type in DacType::all() {
868 assert!(
869 !enabled.is_enabled(dac_type.clone()),
870 "{:?} should be disabled",
871 dac_type
872 );
873 }
874 assert!(enabled.is_empty());
875 }
876
877 #[test]
878 fn test_enabled_dac_types_enable_disable_toggles_correctly() {
879 let mut enabled = EnabledDacTypes::none();
880
881 enabled.enable(DacType::Helios);
883 assert!(enabled.is_enabled(DacType::Helios));
884 assert!(!enabled.is_enabled(DacType::EtherDream));
885
886 enabled.enable(DacType::EtherDream);
888 assert!(enabled.is_enabled(DacType::Helios));
889 assert!(enabled.is_enabled(DacType::EtherDream));
890
891 enabled.disable(DacType::Helios);
893 assert!(!enabled.is_enabled(DacType::Helios));
894 assert!(enabled.is_enabled(DacType::EtherDream));
895 }
896
897 #[test]
898 fn test_enabled_dac_types_iter_only_returns_enabled() {
899 let mut enabled = EnabledDacTypes::none();
900 enabled.enable(DacType::Helios);
901 enabled.enable(DacType::Idn);
902
903 let types: Vec<DacType> = enabled.iter().collect();
904 assert_eq!(types.len(), 2);
905 assert!(types.contains(&DacType::Helios));
906 assert!(types.contains(&DacType::Idn));
907 assert!(!types.contains(&DacType::EtherDream));
908 }
909
910 #[test]
911 fn test_enabled_dac_types_default_enables_all() {
912 let enabled = EnabledDacTypes::default();
913 for dac_type in DacType::all() {
915 assert!(enabled.is_enabled(dac_type.clone()));
916 }
917 }
918
919 #[test]
920 fn test_enabled_dac_types_idempotent_operations() {
921 let mut enabled = EnabledDacTypes::none();
922
923 enabled.enable(DacType::Helios);
925 enabled.enable(DacType::Helios);
926 assert!(enabled.is_enabled(DacType::Helios));
927
928 enabled.disable(DacType::Helios);
930 enabled.disable(DacType::Helios);
931 assert!(!enabled.is_enabled(DacType::Helios));
932 }
933
934 #[test]
935 fn test_enabled_dac_types_chaining() {
936 let mut enabled = EnabledDacTypes::none();
937 enabled
938 .enable(DacType::Helios)
939 .enable(DacType::EtherDream)
940 .disable(DacType::Helios);
941
942 assert!(!enabled.is_enabled(DacType::Helios));
943 assert!(enabled.is_enabled(DacType::EtherDream));
944 }
945
946 #[test]
951 fn test_dac_connection_state_equality() {
952 let s1 = DacConnectionState::Connected {
953 name: "DAC1".to_string(),
954 };
955 let s2 = DacConnectionState::Connected {
956 name: "DAC1".to_string(),
957 };
958 let s3 = DacConnectionState::Connected {
959 name: "DAC2".to_string(),
960 };
961 let s4 = DacConnectionState::Lost {
962 name: "DAC1".to_string(),
963 error: None,
964 };
965
966 assert_eq!(s1, s2);
967 assert_ne!(s1, s3); assert_ne!(s1, s4); }
970
971 #[cfg(feature = "serde")]
976 #[test]
977 fn test_stream_config_serde_roundtrip() {
978 use std::time::Duration;
979
980 let config = StreamConfig {
981 pps: 45000,
982 target_buffer: Duration::from_millis(50),
983 min_buffer: Duration::from_millis(12),
984 underrun: UnderrunPolicy::Park { x: 0.5, y: -0.3 },
985 drain_timeout: Duration::from_secs(2),
986 color_delay: Duration::from_micros(150),
987 startup_blank: Duration::from_micros(800),
988 };
989
990 let json = serde_json::to_string(&config).expect("serialize to JSON");
992 let restored: StreamConfig = serde_json::from_str(&json).expect("deserialize from JSON");
993
994 assert_eq!(restored.pps, config.pps);
995 assert_eq!(restored.target_buffer, config.target_buffer);
996 assert_eq!(restored.min_buffer, config.min_buffer);
997 assert_eq!(restored.drain_timeout, config.drain_timeout);
998 assert_eq!(restored.color_delay, config.color_delay);
999 assert_eq!(restored.startup_blank, config.startup_blank);
1000
1001 match restored.underrun {
1003 UnderrunPolicy::Park { x, y } => {
1004 assert!((x - 0.5).abs() < f32::EPSILON);
1005 assert!((y - (-0.3)).abs() < f32::EPSILON);
1006 }
1007 _ => panic!("Expected Park policy"),
1008 }
1009 }
1010
1011 #[cfg(feature = "serde")]
1012 #[test]
1013 fn test_duration_millis_roundtrip_consistency() {
1014 use std::time::Duration;
1015
1016 let test_durations = [
1018 Duration::from_millis(0),
1019 Duration::from_millis(1),
1020 Duration::from_millis(10),
1021 Duration::from_millis(100),
1022 Duration::from_millis(1000),
1023 Duration::from_millis(u64::MAX / 1000), ];
1025
1026 for &duration in &test_durations {
1027 let config = StreamConfig {
1028 target_buffer: duration,
1029 ..StreamConfig::default()
1030 };
1031
1032 let json = serde_json::to_string(&config).expect("serialize");
1033 let restored: StreamConfig = serde_json::from_str(&json).expect("deserialize");
1034
1035 assert_eq!(
1036 restored.target_buffer, duration,
1037 "Duration {:?} did not round-trip correctly",
1038 duration
1039 );
1040 }
1041 }
1042}