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}