Skip to main content

autd3_rs/legacy/
error.rs

1use core::time::Duration;
2
3use autd3_rs_core::error::{EncodeError, LinkError};
4use autd3_rs_core::value::{PulseWidthError, SamplingConfigError};
5use thiserror::Error;
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8#[non_exhaustive]
9pub enum TimeoutPhase {
10    Handshake,
11    Command { tag: u8 },
12}
13
14impl core::fmt::Display for TimeoutPhase {
15    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
16        match self {
17            TimeoutPhase::Handshake => write!(f, "handshake"),
18            TimeoutPhase::Command { tag } => write!(f, "command {tag:#04x}"),
19        }
20    }
21}
22
23pub(crate) const NO_ERROR: u8 = 0x00;
24pub const NOT_SUPPORTED_TAG: u8 = 0x01;
25pub const INVALID_MSG_ID: u8 = 0x02;
26pub const INVALID_INFO_TYPE: u8 = 0x03;
27pub const INVALID_GAIN_STM_MODE: u8 = 0x04;
28pub const INVALID_SEGMENT_TRANSITION: u8 = 0x05;
29pub const MISS_TRANSITION_TIME: u8 = 0x06;
30pub const INVALID_SILENCER_SETTINGS: u8 = 0x07;
31pub const INVALID_TRANSITION_MODE: u8 = 0x08;
32
33#[must_use]
34pub const fn describe_device_error(code: u8) -> &'static str {
35    match code {
36        NO_ERROR => "no error",
37        NOT_SUPPORTED_TAG => "the device does not support the sent tag",
38        INVALID_MSG_ID => "the message id is out of range",
39        INVALID_INFO_TYPE => "the firmware info type is out of range",
40        INVALID_GAIN_STM_MODE => "the GainSTM mode is out of range",
41        INVALID_SEGMENT_TRANSITION => "the requested segment cannot be activated",
42        MISS_TRANSITION_TIME => "the requested transition time has already passed",
43        INVALID_SILENCER_SETTINGS => "the silencer completion time exceeds the sampling period",
44        INVALID_TRANSITION_MODE => "the transition mode is invalid for the target segment",
45        _ => "unknown firmware error",
46    }
47}
48
49#[derive(Debug, Error)]
50#[non_exhaustive]
51pub enum LegacyError {
52    #[error("device {device} firmware error {code:#04x}: {}", describe_device_error(*code))]
53    Device { device: usize, code: u8 },
54
55    #[error(
56        "{phase} timed out after {cycles} cycles waiting for msg_id {expected:#04x} \
57         (device acks: {acks}; {stale_cycles} of those cycles were stale)"
58    )]
59    Timeout {
60        phase: TimeoutPhase,
61        cycles: u32,
62        expected: u8,
63        acks: String,
64        stale_cycles: u32,
65    },
66
67    #[error(
68        "{phase}: the bus never completed a cycle in {cycles} tries — every cycle was stale, \
69         so the devices are not all in OP (check the EtherCAT state and DC configuration; \
70         a device whose firmware is wedged stops answering and drops out of OP)"
71    )]
72    BusNotOperational { phase: TimeoutPhase, cycles: u32 },
73
74    #[error("link error: {0}")]
75    Link(String),
76
77    #[error(
78        "device {device} reports CPU firmware {version}, but this client only supports legacy-v12.1.0"
79    )]
80    UnsupportedFirmware { device: usize, version: String },
81
82    #[error(
83        "device {device} returned fpga state {state:#04x} with the valid bit clear; \
84         the fpga state reads were turned off (a Clear or ReadsFpgaState(false) command) \
85         and re-enabling them did not take effect"
86    )]
87    FpgaStateInvalid { device: usize, state: u8 },
88
89    #[error("geometry has {geometry} device(s) but the link exposes {link}")]
90    DeviceCountMismatch { geometry: usize, link: usize },
91
92    #[error("the link must expose at least one device")]
93    NoDevices,
94
95    #[error(transparent)]
96    Encode(#[from] EncodeError),
97
98    #[error(transparent)]
99    SamplingConfig(#[from] SamplingConfigError),
100
101    #[error(transparent)]
102    PulseWidth(#[from] PulseWidthError),
103
104    #[error(transparent)]
105    DcSysTime(#[from] autd3_rs_core::value::DcSysTimeError),
106
107    #[error("invalid payload: {0}")]
108    InvalidPayload(#[from] PayloadError),
109
110    #[error("legacy RT worker is no longer alive")]
111    RtClosed,
112
113    #[error("the legacy client is closing or already closed")]
114    Closed,
115}
116
117impl From<LinkError> for LegacyError {
118    fn from(e: LinkError) -> Self {
119        LegacyError::Link(e.message().to_owned())
120    }
121}
122
123#[derive(Clone, Copy, Debug, PartialEq, Eq, Error)]
124#[non_exhaustive]
125pub enum PayloadError {
126    #[error("the link must expose 1..={max} devices, got {got}")]
127    DeviceCountOutOfRange { got: usize, max: usize },
128
129    #[error("expected a frame for {expected} device(s), got one for {got}")]
130    FrameDeviceCountMismatch { expected: usize, got: usize },
131
132    #[error("modulation size {size} out of range {min}..={max}")]
133    ModulationSizeOutOfRange { size: usize, min: usize, max: usize },
134
135    #[error("FociSTM num_foci {num_foci} out of range {min}..={max}")]
136    NumFociOutOfRange {
137        num_foci: usize,
138        min: usize,
139        max: usize,
140    },
141
142    #[error("FociSTM total foci {total} out of range {min}..={max}")]
143    FociStmTotalSizeOutOfRange {
144        total: usize,
145        min: usize,
146        max: usize,
147    },
148
149    #[error("GainSTM size {size} out of range {min}..={max}")]
150    GainStmSizeOutOfRange { size: usize, min: usize, max: usize },
151
152    #[error("emission buffer has {got} slot(s) but the geometry has {expected} device(s)")]
153    EmissionDeviceCountMismatch { expected: usize, got: usize },
154
155    #[error("device {device} has {expected} transducer(s) but the buffer holds {got}")]
156    EmissionTransducerCountMismatch {
157        device: usize,
158        expected: usize,
159        got: usize,
160    },
161
162    #[error("silencer completion time {0:?} must be a multiple of the ultrasound period")]
163    SilencerCompletionTimeNotMultiple(Duration),
164
165    #[error("silencer completion time {0:?} is out of range (1..=65535 ultrasound periods)")]
166    SilencerCompletionTimeOutOfRange(Duration),
167}
168
169pub(crate) fn check_device_error(device: usize, code: u8) -> Result<(), LegacyError> {
170    if code == NO_ERROR {
171        Ok(())
172    } else {
173        Err(LegacyError::Device { device, code })
174    }
175}
176
177#[cfg(test)]
178mod tests {
179    use super::*;
180
181    #[test]
182    fn error_codes_match_legacy_firmware() {
183        assert_eq!(NOT_SUPPORTED_TAG, 0x01);
184        assert_eq!(INVALID_MSG_ID, 0x02);
185        assert_eq!(INVALID_INFO_TYPE, 0x03);
186        assert_eq!(INVALID_GAIN_STM_MODE, 0x04);
187        assert_eq!(INVALID_SEGMENT_TRANSITION, 0x05);
188        assert_eq!(MISS_TRANSITION_TIME, 0x06);
189        assert_eq!(INVALID_SILENCER_SETTINGS, 0x07);
190        assert_eq!(INVALID_TRANSITION_MODE, 0x08);
191    }
192
193    #[test]
194    fn unknown_codes_get_a_generic_description() {
195        assert_eq!(describe_device_error(0x0F), "unknown firmware error");
196    }
197
198    #[test]
199    fn check_device_error_passes_only_zero() {
200        assert!(check_device_error(0, NO_ERROR).is_ok());
201        let e = check_device_error(3, INVALID_MSG_ID).unwrap_err();
202        assert!(matches!(
203            e,
204            LegacyError::Device {
205                device: 3,
206                code: INVALID_MSG_ID
207            }
208        ));
209        assert_eq!(
210            e.to_string(),
211            "device 3 firmware error 0x02: the message id is out of range"
212        );
213    }
214}