spectro_rs/device.rs
1//! High-level spectrometer device abstraction.
2//!
3//! This module defines the [`Spectrometer`] trait, which provides a unified
4//! interface for all supported spectrometer devices, regardless of their
5//! underlying hardware or communication protocol.
6
7use crate::spectrum::SpectralData;
8use crate::{MeasurementMode, Result};
9
10/// Information about a spectrometer device.
11#[derive(Debug, Clone)]
12pub struct DeviceInfo {
13 /// Human-readable device model name (e.g., "ColorMunki", "i1Display Pro").
14 pub model: String,
15 /// Device serial number.
16 pub serial: String,
17 /// Firmware version string.
18 pub firmware: String,
19}
20
21/// The current status of a spectrometer device.
22#[derive(Debug, Clone)]
23pub struct DeviceStatus {
24 /// The current physical position/mode of the device dial.
25 pub position: DevicePosition,
26 /// Whether a button is currently pressed.
27 pub button_pressed: bool,
28 /// Whether the device is calibrated and ready for measurement.
29 pub is_calibrated: bool,
30}
31
32/// Physical position/mode selector on the device.
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum DevicePosition {
35 /// Projector/display measurement position.
36 Projector,
37 /// Surface/reflective measurement position.
38 Surface,
39 /// Calibration tile position.
40 Calibration,
41 /// Ambient light measurement position (with diffuser).
42 Ambient,
43 /// Unknown or unsupported position.
44 Unknown(u8),
45}
46
47impl DevicePosition {
48 /// Returns a human-readable name for this position.
49 pub fn name(&self) -> &'static str {
50 match self {
51 DevicePosition::Projector => "Projector",
52 DevicePosition::Surface => "Surface",
53 DevicePosition::Calibration => "Calibration",
54 DevicePosition::Ambient => "Ambient",
55 DevicePosition::Unknown(_) => "Unknown",
56 }
57 }
58}
59
60/// A unified interface for spectrometer devices.
61///
62/// This trait abstracts the differences between various spectrometer models
63/// (ColorMunki, i1Display Pro, Spyder, etc.), allowing application code to
64/// work with any supported device through a common API.
65///
66/// # Example
67///
68/// ```ignore
69/// use spectro_rs::{discover, MeasurementMode, Spectrometer};
70///
71/// let mut device = discover()?;
72/// println!("Found: {}", device.info()?.model);
73///
74/// device.calibrate()?;
75/// let spectrum = device.measure(MeasurementMode::Emissive)?;
76/// println!("Luminance: {:.2} cd/m²", spectrum.to_xyz().y);
77/// ```
78pub trait Spectrometer {
79 /// Returns information about the connected device.
80 fn info(&self) -> Result<DeviceInfo>;
81
82 /// Returns the current status of the device.
83 fn status(&self) -> Result<DeviceStatus>;
84
85 /// Performs device calibration.
86 ///
87 /// For reflective measurements, this typically involves measuring a white
88 /// reference tile. For emissive/ambient modes, a dark calibration may be
89 /// performed.
90 ///
91 /// # Errors
92 ///
93 /// Returns an error if the device is not in the correct physical position
94 /// for calibration, or if the calibration measurement fails.
95 fn calibrate(&mut self) -> Result<()>;
96
97 /// Performs a single-point measurement in the specified mode.
98 ///
99 /// # Arguments
100 ///
101 /// * `mode` - The type of measurement to perform.
102 ///
103 /// # Returns
104 ///
105 /// The measured spectral data, which can be converted to various color
106 /// spaces (XYZ, Lab, etc.) using the methods on [`SpectralData`].
107 ///
108 /// # Errors
109 ///
110 /// Returns an error if the device is not calibrated (for modes that require
111 /// calibration), or if the measurement fails.
112 fn measure(&mut self, mode: MeasurementMode) -> Result<SpectralData>;
113
114 /// Returns the supported measurement modes for this device.
115 fn supported_modes(&self) -> Vec<MeasurementMode>;
116
117 /// Returns whether the device is currently calibrated for the given mode.
118 fn is_calibrated(&self, mode: MeasurementMode) -> bool;
119}
120
121/// A boxed spectrometer for dynamic dispatch.
122///
123/// This type alias makes it convenient to store different spectrometer
124/// implementations in the same collection or return them from factory functions.
125pub type BoxedSpectrometer = Box<dyn Spectrometer + Send>;