| Crates.io | laser-dac |
| lib.rs | laser-dac |
| version | 0.7.0 |
| created_at | 2025-12-15 22:57:48.431574+00 |
| updated_at | 2026-01-20 22:25:27.696757+00 |
| description | Unified laser DAC abstraction supporting multiple protocols |
| homepage | https://github.com/ModulaserApp/laser-dac-rs |
| repository | https://github.com/ModulaserApp/laser-dac-rs |
| max_upload_size | |
| id | 1986936 |
| size | 631,329 |
Unified DAC backend abstraction for laser projectors.
This crate provides a complete solution for communicating with various laser DAC hardware:
This crate does not apply any additional processing on points (like blanking), except to make it compatible with the target DAC.
⚠️ Warning: use at your own risk! Laser projectors can be dangerous.
| DAC | Connection | Verified | Notes |
|---|---|---|---|
| Helios | USB | ✅ | |
| Ether Dream | Network | ✅ | |
| IDN (ILDA Digital Network) | Network | ✅ | IDN is a standardized protocol. We tested with HeliosPRO |
| LaserCube WiFi | Network | ✅ | Recommend to not use through WiFi mode; use LAN only |
| LaserCube USB / Laserdock | USB | ✅ |
All DACs have been manually verified to work.
Connect your laser DAC and run an example. For full API details, see the documentation.
cargo run --example stream -- circle
# or:
cargo run --example stream -- triangle
# callback mode (DAC-driven timing):
cargo run --example callback -- circle
# frame mode (using FrameAdapter):
cargo run --example frame_adapter -- circle
# reconnecting session (auto-reconnect on disconnect):
cargo run --example reconnect -- circle
# audio-reactive (requires microphone):
cargo run --example audio
The examples run continuously until you press Ctrl+C.
There are two ways to stream points to a DAC:
You control timing by calling next_request() which blocks until the DAC needs more data:
use laser_dac::{list_devices, open_device, StreamConfig};
let devices = list_devices()?;
let device = open_device(&devices[0].id)?;
let config = StreamConfig::new(30_000); // 30k points per second
let (mut stream, info) = device.start_stream(config)?;
stream.control().arm()?; // Enable laser output
loop {
let req = stream.next_request()?; // Blocks until DAC ready
let points = generate_points(req.n_points);
stream.write(&req, &points)?;
}
The DAC drives timing by invoking your callback whenever it's ready for more data:
use laser_dac::{list_devices, open_device, ChunkRequest, StreamConfig};
let devices = list_devices()?;
let device = open_device(&devices[0].id)?;
let config = StreamConfig::new(30_000);
let (stream, info) = device.start_stream(config)?;
stream.control().arm()?;
let exit = stream.run(
// Producer callback - invoked when device needs more data
|req: ChunkRequest| {
let points = generate_points(req.n_points);
Some(points) // Return None to stop
},
// Error callback
|err| eprintln!("Stream error: {}", err),
)?;
Return Some(points) to continue streaming, or None to signal completion.
If you want automatic reconnection by device ID, use ReconnectingSession:
use laser_dac::{ReconnectingSession, StreamConfig};
use std::time::Duration;
let mut session = ReconnectingSession::new("my-device", StreamConfig::new(30_000))
.with_max_retries(5)
.with_backoff(Duration::from_secs(1))
.on_disconnect(|err| eprintln!("Lost connection: {}", err))
.on_reconnect(|info| println!("Reconnected to {}", info.name));
// Arm output as usual (this persists across reconnects)
session.control().arm()?;
let exit = session.run(
|req| Some(generate_points(req.n_points)),
|err| eprintln!("Stream error: {}", err),
)?;
Note: ReconnectingSession uses open_device() internally, so it won't include
external discoverers registered on a custom DacDiscovery.
All backends use normalized coordinates:
Each backend handles conversion to its native format internally.
| Type | Description |
|---|---|
DacInfo |
DAC metadata (name, type, capabilities) |
Dac |
Opened DAC ready for streaming |
Stream |
Active streaming session |
ReconnectingSession |
Stream wrapper with automatic reconnect |
StreamConfig |
Stream settings (PPS, chunk size) |
ChunkRequest |
Request for points from the DAC |
LaserPoint |
Single point with position (f32) and color (u16) |
DacType |
Enum of supported DAC hardware |
By default, all DAC protocols are enabled via the all-dacs feature.
| Feature | Description |
|---|---|
all-dacs |
Enable all DAC protocols (default) |
usb-dacs |
Enable USB DACs: helios, lasercube-usb |
network-dacs |
Enable network DACs: ether-dream, idn, lasercube-wifi |
helios |
Helios USB DAC |
lasercube-usb |
LaserCube USB (LaserDock) DAC |
ether-dream |
Ether Dream network DAC |
idn |
ILDA Digital Network DAC |
lasercube-wifi |
LaserCube WiFi DAC |
For example, to enable only network DACs:
[dependencies]
laser-dac = { version = "*", default-features = false, features = ["network-dacs"] }
| Feature | Description |
|---|---|
serde |
Enable serde serialization for DacType and EnabledDacTypes |
USB DACs (helios, lasercube-usb) use rusb which requires CMake to build.
The repository includes a debug simulator (in tools/idn-simulator/) that acts as a virtual IDN laser DAC. This is useful for testing and development without physical hardware.
# Build and run the simulator
cargo run -p idn-simulator
# With custom options
cargo run -p idn-simulator -- --hostname "Test-DAC" --service-name "Simulator" --port 7255
Features:
Usage:
When the simulator is running, launch your work that scans for IDN devices. You can use this crate, or any other tool that supports IDN!
For a simple test, you can run one of our examples: cargo run --example stream -- circle
CLI Options:
| Option | Description | Default |
|---|---|---|
-n, --hostname |
Hostname in scan responses | IDN-Simulator |
-s, --service-name |
Service name in service map | Simulator Laser |
-p, --port |
UDP port to listen on | 7255 |
MIT