1use crate::error::{Error, Result};
7use crate::types::{DacType, LaserFrame};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
16pub enum WriteResult {
17 Written,
19 DeviceBusy,
21}
22
23mod private {
24 pub trait Sealed {}
25}
26
27pub trait DacBackend: private::Sealed + Send + 'static {
33 fn dac_type(&self) -> DacType;
35
36 fn connect(&mut self) -> Result<()>;
38
39 fn disconnect(&mut self) -> Result<()>;
41
42 fn is_connected(&self) -> bool;
44
45 fn write_frame(&mut self, frame: &LaserFrame) -> Result<WriteResult>;
50
51 fn stop(&mut self) -> Result<()>;
53
54 fn set_shutter(&mut self, open: bool) -> Result<()>;
56}
57
58#[cfg(feature = "helios")]
63mod helios_backend {
64 use super::*;
65 use crate::protocols::helios::{
66 DeviceStatus, Frame, HeliosDac, HeliosDacController, Point as HeliosPoint,
67 };
68
69 pub struct HeliosBackend {
71 dac: Option<HeliosDac>,
72 device_index: usize,
73 }
74
75 impl super::private::Sealed for HeliosBackend {}
76
77 impl HeliosBackend {
78 pub fn new(device_index: usize) -> Self {
80 Self {
81 dac: None,
82 device_index,
83 }
84 }
85
86 pub fn from_dac(dac: HeliosDac) -> Self {
88 Self {
89 dac: Some(dac),
90 device_index: 0,
91 }
92 }
93
94 pub fn discover() -> Result<Vec<HeliosDac>> {
96 let controller = HeliosDacController::new()
97 .map_err(|e| Error::context("Failed to create controller", e))?;
98 controller
99 .list_devices()
100 .map_err(|e| Error::context("Failed to list devices", e))
101 }
102 }
103
104 impl DacBackend for HeliosBackend {
105 fn dac_type(&self) -> DacType {
106 DacType::Helios
107 }
108
109 fn connect(&mut self) -> Result<()> {
110 if self.dac.is_some() {
111 if let Some(dac) = self.dac.take() {
113 let dac = dac
114 .open()
115 .map_err(|e| Error::context("Failed to open device", e))?;
116 self.dac = Some(dac);
117 }
118 return Ok(());
119 }
120
121 let controller = HeliosDacController::new()
123 .map_err(|e| Error::context("Failed to create controller", e))?;
124 let mut dacs = controller
125 .list_devices()
126 .map_err(|e| Error::context("Failed to list devices", e))?;
127
128 if self.device_index >= dacs.len() {
129 return Err(Error::msg(format!(
130 "Device index {} out of range (found {} devices)",
131 self.device_index,
132 dacs.len()
133 )));
134 }
135
136 let dac = dacs.remove(self.device_index);
137 let dac = dac
138 .open()
139 .map_err(|e| Error::context("Failed to open device", e))?;
140 self.dac = Some(dac);
141 Ok(())
142 }
143
144 fn disconnect(&mut self) -> Result<()> {
145 self.dac = None;
147 Ok(())
148 }
149
150 fn is_connected(&self) -> bool {
151 matches!(self.dac, Some(HeliosDac::Open { .. }))
152 }
153
154 fn write_frame(&mut self, frame: &LaserFrame) -> Result<WriteResult> {
155 let dac = self
156 .dac
157 .as_mut()
158 .ok_or_else(|| Error::msg("Not connected"))?;
159
160 match dac.status() {
162 Ok(DeviceStatus::Ready) => {}
163 Ok(DeviceStatus::NotReady) => return Ok(WriteResult::DeviceBusy),
164 Err(e) => return Err(Error::context("Failed to get status", e)),
165 }
166
167 let helios_points: Vec<HeliosPoint> = frame.points.iter().map(|p| p.into()).collect();
169
170 let helios_frame = Frame::new(frame.pps, helios_points);
171
172 dac.write_frame(helios_frame)
173 .map_err(|e| Error::context("Failed to write frame", e))?;
174
175 Ok(WriteResult::Written)
176 }
177
178 fn stop(&mut self) -> Result<()> {
179 if let Some(ref dac) = self.dac {
180 dac.stop()
181 .map_err(|e| Error::context("Failed to stop", e))?;
182 }
183 Ok(())
184 }
185
186 fn set_shutter(&mut self, _open: bool) -> Result<()> {
187 Ok(())
190 }
191 }
192}
193
194#[cfg(feature = "helios")]
195pub use helios_backend::HeliosBackend;
196
197#[cfg(feature = "ether-dream")]
198mod ether_dream_backend {
199 use super::*;
200 use crate::protocols::ether_dream::dac::{stream, LightEngine, Playback, PlaybackFlags};
201 use crate::protocols::ether_dream::protocol::{DacBroadcast, DacPoint};
202 use std::net::IpAddr;
203 use std::time::Duration;
204
205 pub struct EtherDreamBackend {
207 broadcast: DacBroadcast,
208 ip_addr: IpAddr,
209 stream: Option<stream::Stream>,
210 }
211
212 impl super::private::Sealed for EtherDreamBackend {}
213
214 impl EtherDreamBackend {
215 pub fn new(broadcast: DacBroadcast, ip_addr: IpAddr) -> Self {
216 Self {
217 broadcast,
218 ip_addr,
219 stream: None,
220 }
221 }
222 }
223
224 impl DacBackend for EtherDreamBackend {
225 fn dac_type(&self) -> DacType {
226 DacType::EtherDream
227 }
228
229 fn connect(&mut self) -> Result<()> {
230 let stream =
231 stream::connect_timeout(&self.broadcast, self.ip_addr, Duration::from_secs(5))
232 .map_err(|e| Error::context("Failed to connect", e))?;
233
234 self.stream = Some(stream);
235 Ok(())
236 }
237
238 fn disconnect(&mut self) -> Result<()> {
239 if let Some(ref mut stream) = self.stream {
240 let _ = stream.queue_commands().stop().submit();
241 }
242 self.stream = None;
243 Ok(())
244 }
245
246 fn is_connected(&self) -> bool {
247 self.stream.is_some()
248 }
249
250 fn write_frame(&mut self, frame: &LaserFrame) -> Result<WriteResult> {
251 let stream = self
252 .stream
253 .as_mut()
254 .ok_or_else(|| Error::msg("Not connected"))?;
255
256 let points: Vec<DacPoint> = frame.points.iter().map(|p| p.into()).collect();
257 if points.is_empty() {
258 return Ok(WriteResult::DeviceBusy);
259 }
260
261 let light_engine = stream.dac().status.light_engine;
263
264 match light_engine {
265 LightEngine::EmergencyStop => {
266 stream
267 .queue_commands()
268 .clear_emergency_stop()
269 .submit()
270 .map_err(|e| Error::context("Failed to clear emergency stop", e))?;
271
272 stream
274 .queue_commands()
275 .ping()
276 .submit()
277 .map_err(|e| Error::context("Failed to ping after clearing e-stop", e))?;
278
279 if stream.dac().status.light_engine == LightEngine::EmergencyStop {
281 return Err(Error::msg(
282 "DAC stuck in emergency stop - check hardware interlock",
283 ));
284 }
285 }
287 LightEngine::Warmup | LightEngine::Cooldown => {
288 return Ok(WriteResult::DeviceBusy);
289 }
290 LightEngine::Ready => {
291 }
293 }
294
295 let buffer_capacity = stream.dac().buffer_capacity;
297 let buffer_fullness = stream.dac().status.buffer_fullness;
298 let available = buffer_capacity as usize - buffer_fullness as usize - 1;
299
300 if available == 0 {
301 return Ok(WriteResult::DeviceBusy);
302 }
303
304 let point_rate = if frame.pps > 0 {
305 frame.pps
306 } else {
307 stream.dac().max_point_rate / 16
308 };
309
310 const MIN_POINTS_BEFORE_BEGIN: u16 = 500;
312 let target_buffer_points =
313 (point_rate / 20).max(MIN_POINTS_BEFORE_BEGIN as u32) as usize;
314 let target_len = target_buffer_points
315 .min(available)
316 .max(points.len().min(available));
317
318 let mut points_to_send = points;
319 if points_to_send.len() > available {
320 points_to_send.truncate(available);
321 } else if points_to_send.len() < target_len {
322 let seed = points_to_send.clone();
323 let mut idx = 0;
324 while points_to_send.len() < target_len {
325 points_to_send.push(seed[idx % seed.len()]);
326 idx += 1;
327 }
328 }
329
330 let playback_flags = stream.dac().status.playback_flags;
331 let playback = stream.dac().status.playback;
332 let current_point_rate = stream.dac().status.point_rate;
333
334 let mut force_begin = false;
335 if playback_flags.contains(PlaybackFlags::UNDERFLOWED) {
336 stream
337 .queue_commands()
338 .prepare_stream()
339 .submit()
340 .map_err(|e| Error::context("Failed to recover stream", e))?;
341 force_begin = true;
342 }
343
344 let result =
345 if force_begin {
346 stream
348 .queue_commands()
349 .data(points_to_send.clone())
350 .submit()
351 .map_err(|e| Error::context("Failed to send data", e))?;
352
353 let buffer_fullness = stream.dac().status.buffer_fullness;
355
356 if buffer_fullness >= MIN_POINTS_BEFORE_BEGIN {
357 stream.queue_commands().begin(0, point_rate).submit()
358 } else {
359 Ok(())
360 }
361 } else {
362 match playback {
363 Playback::Idle => {
364 stream
365 .queue_commands()
366 .prepare_stream()
367 .submit()
368 .map_err(|e| Error::context("Failed to prepare stream", e))?;
369
370 stream
372 .queue_commands()
373 .data(points_to_send.clone())
374 .submit()
375 .map_err(|e| Error::context("Failed to send data", e))?;
376
377 let buffer_fullness = stream.dac().status.buffer_fullness;
379
380 if buffer_fullness >= MIN_POINTS_BEFORE_BEGIN {
381 stream.queue_commands().begin(0, point_rate).submit()
382 } else {
383 Ok(())
384 }
385 }
386 Playback::Prepared => {
387 stream
389 .queue_commands()
390 .data(points_to_send.clone())
391 .submit()
392 .map_err(|e| Error::context("Failed to send data", e))?;
393
394 let buffer_fullness = stream.dac().status.buffer_fullness;
396
397 if buffer_fullness >= MIN_POINTS_BEFORE_BEGIN {
398 stream.queue_commands().begin(0, point_rate).submit()
399 } else {
400 Ok(())
401 }
402 }
403 Playback::Playing => {
404 let send_result = if current_point_rate != point_rate {
405 stream
406 .queue_commands()
407 .update(0, point_rate)
408 .data(points_to_send.clone())
409 .submit()
410 } else {
411 stream
412 .queue_commands()
413 .data(points_to_send.clone())
414 .submit()
415 };
416
417 if send_result.is_err() {
420 let current_playback = stream.dac().status.playback;
421
422 if current_playback == Playback::Idle {
423 stream.queue_commands().prepare_stream().submit().map_err(
425 |e| Error::context("Failed to recover from underflow", e),
426 )?;
427
428 stream
429 .queue_commands()
430 .data(points_to_send.clone())
431 .submit()
432 .map_err(|e| {
433 Error::context("Failed to send data after recovery", e)
434 })?;
435
436 let buffer_fullness = stream.dac().status.buffer_fullness;
437 if buffer_fullness >= MIN_POINTS_BEFORE_BEGIN {
438 stream.queue_commands().begin(0, point_rate).submit()
439 } else {
440 Ok(())
441 }
442 } else {
443 send_result
445 }
446 } else {
447 send_result
448 }
449 }
450 }
451 };
452
453 result.map_err(|e| Error::context("Failed to write frame", e))?;
454 Ok(WriteResult::Written)
455 }
456
457 fn stop(&mut self) -> Result<()> {
458 if let Some(ref mut stream) = self.stream {
459 stream
460 .queue_commands()
461 .stop()
462 .submit()
463 .map_err(|e| Error::context("Failed to stop", e))?;
464 }
465 Ok(())
466 }
467
468 fn set_shutter(&mut self, _open: bool) -> Result<()> {
469 Ok(())
471 }
472 }
473}
474
475#[cfg(feature = "ether-dream")]
476pub use ether_dream_backend::EtherDreamBackend;
477
478#[cfg(feature = "idn")]
479mod idn_backend {
480 use super::*;
481 use crate::protocols::idn::dac::{stream, ServerInfo, ServiceInfo};
482 use crate::protocols::idn::protocol::PointXyrgbi;
483
484 pub struct IdnBackend {
486 server: ServerInfo,
487 service: ServiceInfo,
488 stream: Option<stream::Stream>,
489 }
490
491 impl super::private::Sealed for IdnBackend {}
492
493 impl IdnBackend {
494 pub fn new(server: ServerInfo, service: ServiceInfo) -> Self {
495 Self {
496 server,
497 service,
498 stream: None,
499 }
500 }
501 }
502
503 impl DacBackend for IdnBackend {
504 fn dac_type(&self) -> DacType {
505 DacType::Idn
506 }
507
508 fn connect(&mut self) -> Result<()> {
509 let stream = stream::connect(&self.server, self.service.service_id)
510 .map_err(|e| Error::context("Failed to connect", e))?;
511
512 self.stream = Some(stream);
513 Ok(())
514 }
515
516 fn disconnect(&mut self) -> Result<()> {
517 if let Some(ref mut stream) = self.stream {
518 let _ = stream.close();
519 }
520 self.stream = None;
521 Ok(())
522 }
523
524 fn is_connected(&self) -> bool {
525 self.stream.is_some()
526 }
527
528 fn write_frame(&mut self, frame: &LaserFrame) -> Result<WriteResult> {
529 let stream = self
530 .stream
531 .as_mut()
532 .ok_or_else(|| Error::msg("Not connected"))?;
533
534 stream.set_scan_speed(frame.pps);
535 let points: Vec<PointXyrgbi> = frame.points.iter().map(|p| p.into()).collect();
536
537 stream
538 .write_frame(&points)
539 .map_err(|e| Error::context("Failed to write frame", e))?;
540
541 Ok(WriteResult::Written)
542 }
543
544 fn stop(&mut self) -> Result<()> {
545 if let Some(ref mut stream) = self.stream {
546 let blank_point = PointXyrgbi::new(0, 0, 0, 0, 0, 0);
547 let blank_frame = vec![blank_point; 10];
548 let _ = stream.write_frame(&blank_frame);
549 }
550 Ok(())
551 }
552
553 fn set_shutter(&mut self, _open: bool) -> Result<()> {
554 Ok(())
556 }
557 }
558}
559
560#[cfg(feature = "idn")]
561pub use idn_backend::IdnBackend;
562
563#[cfg(feature = "lasercube-wifi")]
564mod lasercube_wifi_backend {
565 use super::*;
566 use crate::protocols::lasercube_wifi::dac::{stream, Addressed};
567 use crate::protocols::lasercube_wifi::protocol::{DeviceInfo, Point as LasercubePoint};
568 use std::net::SocketAddr;
569
570 pub struct LasercubeWifiBackend {
572 addressed: Addressed,
573 stream: Option<stream::Stream>,
574 }
575
576 impl super::private::Sealed for LasercubeWifiBackend {}
577
578 impl LasercubeWifiBackend {
579 pub fn new(addressed: Addressed) -> Self {
580 Self {
581 addressed,
582 stream: None,
583 }
584 }
585
586 pub fn from_discovery(info: &DeviceInfo, source_addr: SocketAddr) -> Self {
587 Self::new(Addressed::from_discovery(info, source_addr))
588 }
589 }
590
591 impl DacBackend for LasercubeWifiBackend {
592 fn dac_type(&self) -> DacType {
593 DacType::LasercubeWifi
594 }
595
596 fn connect(&mut self) -> Result<()> {
597 let stream = stream::connect(&self.addressed)
598 .map_err(|e| Error::context("Failed to connect", e))?;
599
600 self.stream = Some(stream);
601 Ok(())
602 }
603
604 fn disconnect(&mut self) -> Result<()> {
605 if let Some(ref mut stream) = self.stream {
606 let _ = stream.stop();
607 }
608 self.stream = None;
609 Ok(())
610 }
611
612 fn is_connected(&self) -> bool {
613 self.stream.is_some()
614 }
615
616 fn write_frame(&mut self, frame: &LaserFrame) -> Result<WriteResult> {
617 let stream = self
618 .stream
619 .as_mut()
620 .ok_or_else(|| Error::msg("Not connected"))?;
621
622 let lc_points: Vec<LasercubePoint> = frame.points.iter().map(|p| p.into()).collect();
623
624 stream
625 .write_frame(&lc_points, frame.pps)
626 .map_err(|e| Error::context("Failed to write frame", e))?;
627
628 Ok(WriteResult::Written)
629 }
630
631 fn stop(&mut self) -> Result<()> {
632 if let Some(ref mut stream) = self.stream {
633 stream
634 .stop()
635 .map_err(|e| Error::context("Failed to stop", e))?;
636 }
637 Ok(())
638 }
639
640 fn set_shutter(&mut self, open: bool) -> Result<()> {
641 if let Some(ref mut stream) = self.stream {
642 stream
643 .set_output(open)
644 .map_err(|e| Error::context("Failed to set shutter", e))?;
645 }
646 Ok(())
647 }
648 }
649}
650
651#[cfg(feature = "lasercube-wifi")]
652pub use lasercube_wifi_backend::LasercubeWifiBackend;
653
654#[cfg(feature = "lasercube-usb")]
655mod lasercube_usb_backend {
656 use super::*;
657 use crate::protocols::lasercube_usb::dac::Stream;
658 use crate::protocols::lasercube_usb::protocol::Sample as LasercubeUsbSample;
659 use crate::protocols::lasercube_usb::{discover_dacs, rusb};
660
661 pub struct LasercubeUsbBackend {
663 device: Option<rusb::Device<rusb::Context>>,
664 stream: Option<Stream<rusb::Context>>,
665 }
666
667 impl super::private::Sealed for LasercubeUsbBackend {}
668
669 impl LasercubeUsbBackend {
670 pub fn new(device: rusb::Device<rusb::Context>) -> Self {
671 Self {
672 device: Some(device),
673 stream: None,
674 }
675 }
676
677 pub fn from_stream(stream: Stream<rusb::Context>) -> Self {
678 Self {
679 device: None,
680 stream: Some(stream),
681 }
682 }
683
684 pub fn discover_devices() -> Result<Vec<rusb::Device<rusb::Context>>> {
685 discover_dacs().map_err(|e| Error::context("Failed to discover devices", e))
686 }
687 }
688
689 impl DacBackend for LasercubeUsbBackend {
690 fn dac_type(&self) -> DacType {
691 DacType::LasercubeUsb
692 }
693
694 fn connect(&mut self) -> Result<()> {
695 if self.stream.is_some() {
696 return Ok(());
697 }
698
699 let device = self
700 .device
701 .take()
702 .ok_or_else(|| Error::msg("No device available"))?;
703
704 let mut stream =
705 Stream::open(device).map_err(|e| Error::context("Failed to open device", e))?;
706
707 stream
708 .enable_output()
709 .map_err(|e| Error::context("Failed to enable output", e))?;
710
711 self.stream = Some(stream);
712 Ok(())
713 }
714
715 fn disconnect(&mut self) -> Result<()> {
716 if let Some(ref mut stream) = self.stream {
717 let _ = stream.stop();
718 }
719 self.stream = None;
720 Ok(())
721 }
722
723 fn is_connected(&self) -> bool {
724 self.stream.is_some()
725 }
726
727 fn write_frame(&mut self, frame: &LaserFrame) -> Result<WriteResult> {
728 let stream = self
729 .stream
730 .as_mut()
731 .ok_or_else(|| Error::msg("Not connected"))?;
732
733 let samples: Vec<LasercubeUsbSample> = frame.points.iter().map(|p| p.into()).collect();
734
735 stream
736 .write_frame(&samples, frame.pps)
737 .map_err(|e| Error::context("Failed to write frame", e))?;
738
739 Ok(WriteResult::Written)
740 }
741
742 fn stop(&mut self) -> Result<()> {
743 if let Some(ref mut stream) = self.stream {
744 stream
745 .stop()
746 .map_err(|e| Error::context("Failed to stop", e))?;
747 }
748 Ok(())
749 }
750
751 fn set_shutter(&mut self, open: bool) -> Result<()> {
752 if let Some(ref mut stream) = self.stream {
753 if open {
754 stream
755 .enable_output()
756 .map_err(|e| Error::context("Failed to enable output", e))?;
757 } else {
758 stream
759 .disable_output()
760 .map_err(|e| Error::context("Failed to disable output", e))?;
761 }
762 }
763 Ok(())
764 }
765 }
766}
767
768#[cfg(feature = "lasercube-usb")]
769pub use lasercube_usb_backend::LasercubeUsbBackend;