Skip to main content

autd3_rs/
error.rs

1use std::sync::Arc;
2
3use thiserror::Error;
4
5use autd3_rs_core::error::{EncodeError, LinkError};
6use autd3_rs_core::protocol::describe_device_error;
7
8use crate::firmware_version::FirmwareVersion;
9use crate::mirror::{BankLoop, SilencerAxis};
10use crate::telemetry::Telemetry;
11use autd3_rs_core::value::{PulseWidthError, SamplingConfigError, TransitionMode};
12
13#[derive(Clone)]
14pub struct LinkCause(Arc<dyn core::error::Error + Send + Sync>);
15
16impl LinkCause {
17    #[must_use]
18    pub fn new<E: core::error::Error + Send + Sync + 'static>(source: E) -> Self {
19        Self(Arc::new(source))
20    }
21}
22
23impl core::ops::Deref for LinkCause {
24    type Target = dyn core::error::Error + Send + Sync + 'static;
25
26    fn deref(&self) -> &Self::Target {
27        &*self.0
28    }
29}
30
31impl core::fmt::Debug for LinkCause {
32    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
33        core::fmt::Debug::fmt(&*self.0, f)
34    }
35}
36
37impl core::fmt::Display for LinkCause {
38    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
39        core::fmt::Display::fmt(&*self.0, f)
40    }
41}
42
43#[derive(Debug, Error)]
44#[non_exhaustive]
45pub enum Error {
46    #[error("device {device} firmware error {code:#04x}: {}", describe_device_error(*code))]
47    DeviceError { device: usize, code: u8 },
48
49    #[error(
50        "device {device}: strict silencer {axis:?} completion {completion_steps} steps exceeds sampling divider {sampling_div}"
51    )]
52    SilencerConstraint {
53        device: usize,
54        axis: SilencerAxis,
55        completion_steps: u16,
56        sampling_div: u16,
57    },
58
59    #[error(
60        "device {device}: transition mode {transition_mode:?} is invalid for a {bank_loop:?} loop bank"
61    )]
62    TransitionConstraint {
63        device: usize,
64        transition_mode: TransitionMode,
65        bank_loop: BankLoop,
66    },
67
68    #[error(
69        "device {device} runs firmware {version}, which is outside the series supported by this SDK ({}.{}.x)",
70        FirmwareVersion::SUPPORTED_SERIES.0,
71        FirmwareVersion::SUPPORTED_SERIES.1
72    )]
73    UnsupportedFirmware {
74        device: usize,
75        version: FirmwareVersion,
76    },
77
78    #[error(
79        "device {device} rejected telemetry counter {counter:?}; its firmware does not know this counter"
80    )]
81    UnsupportedTelemetry { device: usize, counter: Telemetry },
82
83    #[error("ack timeout after {cycles} cycles")]
84    Timeout { cycles: u32 },
85
86    #[error("link error: {0}")]
87    Link(#[source] LinkCause),
88
89    #[error(transparent)]
90    DcSysTime(#[from] autd3_rs_core::value::DcSysTimeError),
91
92    #[error("invalid payload: {0}")]
93    InvalidPayload(PayloadError),
94
95    #[error(transparent)]
96    Encode(#[from] EncodeError),
97
98    #[error("client RT worker is no longer alive")]
99    RtClosed,
100
101    #[error("RT thread panicked")]
102    RtPanicked,
103}
104
105impl From<LinkError> for Error {
106    fn from(e: LinkError) -> Self {
107        Error::Link(LinkCause::new(e))
108    }
109}
110
111impl<E> From<E> for Error
112where
113    E: Into<PayloadError>,
114{
115    fn from(value: E) -> Self {
116        Error::InvalidPayload(value.into())
117    }
118}
119
120#[derive(Clone, Copy, Debug, PartialEq, Error)]
121#[non_exhaustive]
122pub enum PayloadError {
123    #[error("max_inflight must be <= {max}")]
124    MaxInflightTooLarge { max: usize },
125
126    #[error("link must expose 1..={max} devices, got {got}")]
127    DeviceCountOutOfRange { got: usize, max: usize },
128
129    #[error("geometry has {geometry} device(s) but link exposes {link}")]
130    GeometryDeviceMismatch { geometry: usize, link: usize },
131
132    #[error("expected {expected} datagram(s) (one per device), got {got}")]
133    DatagramCountMismatch { expected: usize, got: usize },
134
135    #[error("modulation size {size} out of range {min}..={max}")]
136    ModulationSizeOutOfRange { size: usize, min: usize, max: usize },
137
138    #[error("modulation data must not be empty")]
139    ModulationDataEmpty,
140
141    #[error("modulation offset {offset} must be even (word-write-only RAM)")]
142    ModulationOffsetNotEven { offset: usize },
143
144    #[error("modulation write [{offset}, {end}) exceeds buffer capacity {capacity}")]
145    ModulationWriteExceedsCapacity {
146        offset: usize,
147        end: usize,
148        capacity: usize,
149    },
150
151    #[error("foci must not be empty")]
152    FociEmpty,
153
154    #[error("foci write [{offset}, {end}) exceeds capacity {capacity}")]
155    FociWriteExceedsCapacity {
156        offset: usize,
157        end: usize,
158        capacity: usize,
159    },
160
161    #[error("silencer completion time {0:?} must be a multiple of the ultrasound period")]
162    SilencerCompletionTimeNotMultiple(core::time::Duration),
163
164    #[error("silencer completion time {0:?} is out of range (1..=65535 ultrasound periods)")]
165    SilencerCompletionTimeOutOfRange(core::time::Duration),
166
167    #[error("pattern size {size} must be >= {min}")]
168    PatternSizeTooSmall { size: usize, min: usize },
169
170    #[error("{count} patterns do not fit the {format} compression, which carries {max} per frame")]
171    PatternCountExceedsFormat {
172        count: usize,
173        format: &'static str,
174        max: usize,
175    },
176
177    #[error("a {size}-sample bank never advances its index, so it requires an infinite loop")]
178    FiniteLoopNeedsMultipleSamples { size: usize },
179
180    #[error("num_foci {num_foci} out of range 1..={max}")]
181    NumFociOutOfRange { num_foci: u8, max: u8 },
182
183    #[error("STM size {size} x num_foci {num_foci} exceeds capacity {capacity}")]
184    StmFociExceedCapacity {
185        size: usize,
186        num_foci: u8,
187        capacity: usize,
188    },
189
190    #[error("sound_speed must be >= 1")]
191    SoundSpeedZero,
192
193    #[error("STM size {size} out of range {min}..={max}")]
194    StmSizeOutOfRange { size: usize, min: usize, max: usize },
195
196    #[error("emissions has {len} entr(ies) but device {device} was requested")]
197    EmissionsDeviceOutOfRange { device: usize, len: usize },
198
199    #[error("device {device} has {got} transducer entr(ies) but {expected} are required")]
200    TransducerCountMismatch {
201        device: usize,
202        got: usize,
203        expected: usize,
204    },
205
206    #[error("device {device} pattern data ({len} byte(s)) exceeds frame capacity {capacity}")]
207    PatternWriteExceedsCapacity {
208        device: usize,
209        len: usize,
210        capacity: usize,
211    },
212
213    #[error("pattern STM index {index} out of range 0..{max}")]
214    PatternIndexOutOfRange { index: usize, max: usize },
215
216    #[error(transparent)]
217    SamplingConfig(#[from] SamplingConfigError),
218
219    #[error(transparent)]
220    PulseWidth(#[from] PulseWidthError),
221}
222
223#[cfg(test)]
224mod tests {
225    use super::*;
226
227    fn chain(e: &Error) -> Vec<String> {
228        let mut out = Vec::new();
229        let mut cur: Option<&(dyn core::error::Error + 'static)> = core::error::Error::source(e);
230        while let Some(e) = cur {
231            out.push(e.to_string());
232            cur = e.source();
233        }
234        out
235    }
236
237    #[test]
238    fn a_link_error_keeps_its_source_when_it_becomes_a_client_error() {
239        let io = std::io::Error::from(std::io::ErrorKind::PermissionDenied);
240        let e = Error::from(LinkError::with_source("failed to open the link", io));
241
242        assert_eq!(e.to_string(), "link error: failed to open the link");
243        assert_eq!(
244            chain(&e),
245            vec![
246                "failed to open the link".to_owned(),
247                std::io::Error::from(std::io::ErrorKind::PermissionDenied).to_string(),
248            ]
249        );
250
251        let link_error = core::error::Error::source(&e)
252            .expect("the cause must be reachable through source()")
253            .downcast_ref::<LinkError>()
254            .expect("the LinkError itself must survive the conversion");
255        assert_eq!(
256            core::error::Error::source(link_error)
257                .expect("the source must survive")
258                .downcast_ref::<std::io::Error>()
259                .map(std::io::Error::kind),
260            Some(std::io::ErrorKind::PermissionDenied)
261        );
262    }
263
264    #[test]
265    fn a_link_error_without_a_source_ends_the_chain() {
266        let e = Error::from(LinkError::new("the bus is gone"));
267
268        assert_eq!(e.to_string(), "link error: the bus is gone");
269        assert_eq!(chain(&e), vec!["the bus is gone".to_owned()]);
270    }
271}