laser_dac/lib.rs
1//! Unified DAC backend abstraction for laser projectors.
2//!
3//! This crate provides a common interface for communicating with various
4//! laser DAC (Digital-to-Analog Converter) hardware using a streaming API
5//! that provides uniform pacing and backpressure across all device types.
6//!
7//! # Getting Started
8//!
9//! The streaming API uses a zero-allocation callback approach where the library
10//! manages timing and you fill buffers with points:
11//!
12//! ## Callback Mode
13//!
14//! Use `run()` with a producer closure for simpler code:
15//!
16//! ```no_run
17//! use laser_dac::{list_devices, open_device, StreamConfig, LaserPoint, ChunkRequest, ChunkResult};
18//!
19//! let device = open_device("my-device").unwrap();
20//! let config = StreamConfig::new(30_000);
21//! let (stream, _info) = device.start_stream(config).unwrap();
22//!
23//! stream.control().arm().unwrap();
24//!
25//! let exit = stream.run(
26//! |req: &ChunkRequest, buffer: &mut [LaserPoint]| {
27//! let n = req.target_points;
28//! for i in 0..n {
29//! buffer[i] = LaserPoint::blanked(0.0, 0.0);
30//! }
31//! ChunkResult::Filled(n)
32//! },
33//! |err| eprintln!("Stream error: {}", err),
34//! );
35//! ```
36//!
37//! # Supported DACs
38//!
39//! - **Helios** - USB laser DAC (feature: `helios`)
40//! - **Ether Dream** - Network laser DAC (feature: `ether-dream`)
41//! - **IDN** - ILDA Digital Network protocol (feature: `idn`)
42//! - **LaserCube WiFi** - WiFi-connected laser DAC (feature: `lasercube-wifi`)
43//! - **LaserCube USB** - USB laser DAC / LaserDock (feature: `lasercube-usb`)
44//! - **AVB Audio Devices** - AVB audio output via CoreAudio/ASIO (feature: `avb`, macOS/Windows)
45//!
46//! # Features
47//!
48//! - `all-dacs` (default): Enable all DAC protocols
49//! - `usb-dacs`: Enable USB DACs (Helios, LaserCube USB)
50//! - `network-dacs`: Enable network DACs (Ether Dream, IDN, LaserCube WiFi)
51//! - `audio-dacs`: Enable audio DACs (AVB)
52//!
53//! # Coordinate System
54//!
55//! All backends use normalized coordinates:
56//! - X: -1.0 (left) to 1.0 (right)
57//! - Y: -1.0 (bottom) to 1.0 (top)
58//! - Colors: 0-65535 for R, G, B, and intensity
59//!
60//! Each backend handles conversion to its native format internally.
61
62pub mod backend;
63pub mod discovery;
64mod error;
65mod frame_adapter;
66#[cfg(any(feature = "idn", feature = "lasercube-wifi"))]
67mod net_utils;
68pub mod protocols;
69pub mod session;
70pub mod stream;
71pub mod types;
72
73// Crate-level error types
74pub use error::{Error, Result};
75
76// Backend trait and types
77pub use backend::{StreamBackend, WriteOutcome};
78
79// Discovery types
80pub use discovery::{
81 DacDiscovery, DiscoveredDevice, DiscoveredDeviceInfo, ExternalDevice, ExternalDiscoverer,
82};
83
84// Core types
85pub use types::{
86 // DAC types
87 caps_for_dac_type,
88 ChunkRequest,
89 ChunkResult,
90 // Streaming types
91 DacCapabilities,
92 DacConnectionState,
93 DacDevice,
94 DacInfo,
95 DacType,
96 EnabledDacTypes,
97 LaserPoint,
98 OutputModel,
99 RunExit,
100 StreamConfig,
101 StreamInstant,
102 StreamStats,
103 StreamStatus,
104 UnderrunPolicy,
105};
106
107// Stream and Dac types
108pub use session::{ReconnectingSession, SessionControl};
109pub use stream::{Dac, Stream, StreamControl};
110
111// Frame adapters (converts point buffers to continuous streams)
112pub use frame_adapter::{Frame, FrameAdapter, SharedFrameAdapter};
113
114// Conditional exports based on features
115
116// Helios
117#[cfg(feature = "helios")]
118pub use backend::HeliosBackend;
119#[cfg(feature = "helios")]
120pub use protocols::helios;
121
122// Ether Dream
123#[cfg(feature = "ether-dream")]
124pub use backend::EtherDreamBackend;
125#[cfg(feature = "ether-dream")]
126pub use protocols::ether_dream;
127
128// IDN
129#[cfg(feature = "idn")]
130pub use backend::IdnBackend;
131#[cfg(feature = "idn")]
132pub use protocols::idn;
133
134// LaserCube WiFi
135#[cfg(feature = "lasercube-wifi")]
136pub use backend::LasercubeWifiBackend;
137#[cfg(feature = "lasercube-wifi")]
138pub use protocols::lasercube_wifi;
139
140// LaserCube USB
141#[cfg(feature = "lasercube-usb")]
142pub use backend::LasercubeUsbBackend;
143#[cfg(feature = "lasercube-usb")]
144pub use protocols::lasercube_usb;
145
146// AVB
147#[cfg(feature = "avb")]
148pub use backend::AvbBackend;
149#[cfg(feature = "avb")]
150pub use protocols::avb;
151
152// Re-export rusb for consumers that need the Context type (for LaserCube USB)
153#[cfg(feature = "lasercube-usb")]
154pub use protocols::lasercube_usb::rusb;
155
156// =============================================================================
157// Device Discovery Functions
158// =============================================================================
159
160use backend::Result as BackendResult;
161
162/// List all available DACs.
163///
164/// Returns DAC info for each discovered DAC, including capabilities.
165pub fn list_devices() -> BackendResult<Vec<DacInfo>> {
166 list_devices_filtered(&EnabledDacTypes::all())
167}
168
169/// List available DACs filtered by DAC type.
170pub fn list_devices_filtered(enabled_types: &EnabledDacTypes) -> BackendResult<Vec<DacInfo>> {
171 let mut discovery = DacDiscovery::new(enabled_types.clone());
172 let devices = discovery
173 .scan()
174 .into_iter()
175 .map(|device| {
176 let info = device.info();
177 DacInfo {
178 id: info.stable_id(),
179 name: info.name(),
180 kind: device.dac_type(),
181 caps: caps_for_dac_type(&device.dac_type()),
182 }
183 })
184 .collect();
185
186 Ok(devices)
187}
188
189/// Open a DAC by ID.
190///
191/// The ID should match the `id` field returned by [`list_devices`].
192/// IDs are namespaced by protocol (e.g., `etherdream:aa:bb:cc:dd:ee:ff`,
193/// `idn:hostname.local`, `helios:serial`, `avb:device-slug:n`).
194pub fn open_device(id: &str) -> BackendResult<Dac> {
195 let mut discovery = DacDiscovery::new(EnabledDacTypes::all());
196 discovery.open_by_id(id)
197}