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