1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
//! EtherCrab error types.
pub use crate::coe::abort_code::CoeAbortCode;
use crate::{command::Command, fmt, AlStatusCode, SubDeviceState};
use core::num::TryFromIntError;
/// An EtherCrab error.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum Error {
/// A low level error occurred when producing or consuming a PDU.
Pdu(PduError),
/// A working counter (WKC) error was encountered.
WorkingCounter {
/// The expected working counter value.
expected: u16,
/// The actual value received.
received: u16,
},
/// Something timed out.
Timeout,
/// An EEPROM error was encountered.
Eeprom(EepromError),
/// A fixed size array was not large enough to hold a given item type.
Capacity(Item),
/// A string was too long to fit in a fixed size buffer.
StringTooLong {
/// The length of the fixed size buffer.
max_length: usize,
/// The length of the input string.
string_length: usize,
},
/// A mailbox error was encountered.
Mailbox(MailboxError),
/// Failed to send a frame over the network interace.
SendFrame,
/// Failed to receive a frame properly.
ReceiveFrame,
/// A frame was only partially sent.
PartialSend {
/// Frame length in bytes.
len: usize,
/// The number of bytes sent.
sent: usize,
},
/// A value may be too large or otherwise could not be converted into a target type.
///
/// E.g. converting `99_999usize` into a `u16` will fail as the value is larger than `u16::MAX`.
IntegerTypeConversion,
/// The allotted storage for a group's PDI is too small for the calculated length read from all
/// SubDevices in the group.
PdiTooLong {
/// Maximum PDI length.
max_length: usize,
/// Actual PDI length.
desired_length: usize,
},
/// An item in a list could not be found.
NotFound {
/// Item kind.
item: Item,
/// An index into a list of items.
index: Option<usize>,
},
/// An internal error occurred. This indicates something that shouldn't happen within EtherCrab.
Internal,
/// There is a problem with the discovered EtherCAT SubDevice topology.
Topology,
/// An error was read back from one or more SubDevices when attempting to transition to a new
/// state.
StateTransition,
/// An unknown SubDevice was encountered during device discovery/initialisation.
UnknownSubDevice,
/// An invalid state was encountered.
InvalidState {
/// The desired state.
expected: SubDeviceState,
/// The actual state.
actual: SubDeviceState,
/// SubDevice address.
configured_address: u16,
},
/// An error occurred encoding or decoding an item.
Wire(ethercrab_wire::WireError),
/// A subdevice produced an error.
SubDevice(AlStatusCode),
/// A distributed clock error occurred.
DistributedClock(DistributedClockError),
}
#[cfg(feature = "std")]
impl std::error::Error for Error {}
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Error::Pdu(e) => write!(f, "pdu: {}", e),
Error::WorkingCounter { expected, received } => {
write!(f, "working counter expected {}, got {}", expected, received)
}
Error::Timeout => f.write_str("timeout"),
Error::Eeprom(e) => write!(f, "eeprom: {}", e),
Error::Capacity(item) => write!(f, "not enough capacity for {:?}", item),
Error::StringTooLong {
max_length,
string_length,
} => write!(
f,
"string of {} bytes is too long to fit in max storage of {} bytes",
string_length, max_length
),
Error::Mailbox(e) => write!(f, "mailbox: {e}"),
Error::SendFrame => f.write_str("failed to send EtherCAT frame"),
Error::ReceiveFrame => f.write_str("failed to receive an EtherCAT frame"),
Error::PartialSend { len, sent } => {
write!(f, "frame of {} bytes only had {} bytes sent", len, sent)
}
Error::IntegerTypeConversion => write!(f, "failed to convert between integer types"),
Error::PdiTooLong {
max_length,
desired_length,
} => write!(
f,
"Process Data Image is too long ({} bytes), max length is {}",
desired_length, max_length
),
Error::NotFound { item, index } => {
write!(f, "item kind {:?} not found (index: {:?})", item, index)
}
Error::Internal => f.write_str("internal error"),
Error::Topology => f.write_str("topology"),
Error::StateTransition => {
f.write_str("a SubDevice failed to transition to a new state")
}
Error::UnknownSubDevice => f.write_str("unknown SubDevice"),
Error::InvalidState {
expected,
actual,
configured_address,
} => write!(
f,
"SubDevice {:#06x} state is invalid: {}, expected {}",
configured_address, actual, expected
),
Error::Wire(e) => write!(f, "wire encode/decode error: {}", e),
Error::SubDevice(e) => write!(f, "subdevice error: {}", e),
Error::DistributedClock(e) => write!(f, "distributed clock: {}", e),
}
}
}
/// The kind of item being looked for.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum Item {
/// An EtherCAT SubDevice.
SubDevice,
/// Process Data Object.
Pdo,
/// Fieldbus Memory Management Unit.
Fmmu,
/// Sync Manager.
SyncManager,
/// A PDO entry.
PdoEntry,
/// Extended Fieldbus Memory Management Unit config.
FmmuEx,
/// A user-defined SubDevice group.
Group,
/// A SDO sub-index.
SdoSubIndex,
}
/// Low-level PDU (Process Data Unit) error.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum PduError {
/// Failed to decode raw PDU data into a given data type.
Decode,
/// Something went wrong when encoding/decoding the raw Ethernet II frame.
Ethernet,
/// PDU data is too long to fit in the given buffer.
TooLong,
/// Failed to create an Ethernet II frame.
CreateFrame,
/// A frame index was given that does not point to a frame.
InvalidIndex(u8),
/// A received frame is invalid.
Validation(PduValidationError),
/// A frame is not ready to be reused.
///
/// This may be caused by a too small [`MAX_FRAMES`](crate::pdu_loop::PduLoop) value, or sending
/// frames too quickly.
InvalidFrameState,
/// Failed to swap atomic state for a PDU frame.
///
/// This is an internal error and should not appear in user code. Please [open an
/// issue](https://github.com/ethercrab-rs/ethercrab/issues/new) if this is encountered.
SwapState,
}
impl core::fmt::Display for PduError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
PduError::Decode => f.write_str("failed to decode raw PDU data into type"),
PduError::Ethernet => f.write_str("network"),
PduError::TooLong => f.write_str("data is too long to fit in given buffer"),
PduError::CreateFrame => f.write_str("failed to create frame"),
PduError::InvalidIndex(index) => write!(f, "invalid PDU index {}", index),
PduError::Validation(e) => write!(f, "received PDU validation failed: {}", e),
PduError::InvalidFrameState => f.write_str("invalid PDU frame state"),
PduError::SwapState => f.write_str("failed to swap frame state"),
}
}
}
/// CoE mailbox error.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum DistributedClockError {
/// No DC System Time reference SubDevice was found.
NoReference,
}
impl core::fmt::Display for DistributedClockError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::NoReference => f.write_str("No DC reference SubDevice found"),
}
}
}
/// CoE mailbox error.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum MailboxError {
/// The mailbox operation was aborted.
Aborted {
/// Abort code.
code: CoeAbortCode,
/// The address used in the operation.
address: u16,
/// The subindex used in the operation.
sub_index: u8,
},
/// Mailbox data is too long to fit in the given type.
TooLong {
/// The address used in the operation.
address: u16,
/// The subindex used in the operation.
sub_index: u8,
},
/// A SubDeviceve has no mailbox but requires one for a given action.
NoMailbox,
/// The response to a mailbox action is invalid.
SdoResponseInvalid {
/// The address used in the operation.
address: u16,
/// The subindex used in the operation.
sub_index: u8,
},
/// The returned counter value does not match that which was sent.
///
/// Slowing down mailbox reads may help mitigate this error.
InvalidCount,
/// SubDevice sent an emergency message.
Emergency {
/// Error code.
error_code: u16,
/// Error register.
error_register: u8,
},
}
impl core::fmt::Display for MailboxError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
MailboxError::Aborted {
code,
address,
sub_index,
} => write!(f, "{:#06x}:{} aborted: {}", address, sub_index, code),
MailboxError::TooLong { address, sub_index } => write!(
f,
"{:#06x}:{} returned data is too long",
address, sub_index
),
MailboxError::NoMailbox => f.write_str("device has no mailbox"),
MailboxError::SdoResponseInvalid { address, sub_index } => write!(
f,
"{:#06x}:{} invalid response from device",
address, sub_index
),
MailboxError::InvalidCount => f.write_str("incorrect mailbox count value"),
MailboxError::Emergency {
error_code,
error_register,
} => write!(
f,
"emergency: code {:#06x}, register {:#04x}",
error_code, error_register
),
}
}
}
/// EEPROM (SII) error.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum EepromError {
/// Failed to decode data from EEPROM.
Decode,
/// An EEPROM section is too large to fit in the given buffer.
SectionOverrun,
/// The given category does not exist in the SubDevice's EEPROM.
NoCategory,
/// The section in the SubDevice's EEPROM is too small to fill the given buffer.
SectionUnderrun,
/// An attempt to clear errors on the device failed.
ClearErrors,
}
impl core::fmt::Display for EepromError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
EepromError::Decode => f.write_str("failed to decode data"),
EepromError::SectionOverrun => f.write_str("section too large to fit in buffer"),
EepromError::NoCategory => f.write_str("category not found"),
EepromError::SectionUnderrun => f.write_str("section too short to fill buffer"),
EepromError::ClearErrors => f.write_str("clear device errors failed"),
}
}
}
/// An EtherCat "visible string" (i.e. a human readable string) error.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum VisibleStringError {
/// The source data is too long to fit in a given storage type.
TooLong,
}
#[cfg(feature = "defmt")]
impl defmt::Format for VisibleStringError {
fn format(&self, f: defmt::Formatter) {
match self {
VisibleStringError::TooLong => defmt::write!(f, "TooLong"),
}
}
}
impl core::fmt::Display for VisibleStringError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
VisibleStringError::TooLong => f.write_str("string is too long"),
}
}
}
/// A PDU response failed to validate.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum PduValidationError {
/// The index of the received PDU does not match that of the sent one.
IndexMismatch {
/// Sent index.
sent: u8,
/// Received index.
received: u8,
},
/// The received command does not match the one sent.
CommandMismatch {
/// Sent command.
sent: Command,
/// Received command.
received: Command,
},
}
impl core::fmt::Display for PduValidationError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::IndexMismatch { sent, received } => {
write!(
f,
"PDU index mismatch: sent {}, received {}",
sent, received
)
}
Self::CommandMismatch { sent, received } => {
write!(
f,
"PDU command mismatch: sent {}, received {}",
sent, received
)
}
}
}
}
impl From<PduError> for Error {
fn from(e: PduError) -> Self {
Self::Pdu(e)
}
}
impl From<EepromError> for Error {
fn from(e: EepromError) -> Self {
Self::Eeprom(e)
}
}
impl From<DistributedClockError> for Error {
fn from(e: DistributedClockError) -> Self {
Self::DistributedClock(e)
}
}
impl From<PduValidationError> for PduError {
fn from(e: PduValidationError) -> Self {
Self::Validation(e)
}
}
impl From<TryFromIntError> for Error {
fn from(_e: TryFromIntError) -> Self {
fmt::error!("Integer conversion error");
Self::IntegerTypeConversion
}
}
impl From<ethercrab_wire::WireError> for Error {
fn from(value: ethercrab_wire::WireError) -> Self {
Self::Wire(value)
}
}
/// Turn [`EepromError::NoCategory`] errors into `None`.
pub(crate) trait IgnoreNoCategory<T> {
fn ignore_no_category(self) -> Result<Option<T>, Error>;
}
impl<T> IgnoreNoCategory<T> for Result<T, Error> {
fn ignore_no_category(self) -> Result<Option<T>, Error> {
match self {
Ok(result) => Ok(Some(result)),
Err(Error::Eeprom(EepromError::NoCategory)) => Ok(None),
Err(e) => return Err(e),
}
}
}