can_motor_control/transport/mod.rs
1//! CAN transport abstraction.
2
3use std::io;
4use std::os::fd::RawFd;
5
6use motor_codec::{BusCapabilities, CanFrame, FrameError};
7use thiserror::Error;
8
9mod gs_usb;
10mod mock;
11mod poller;
12#[cfg(target_os = "linux")]
13mod socketcan;
14
15#[cfg(target_os = "macos")]
16pub use gs_usb::GsUsbStatistics;
17#[cfg(target_os = "macos")]
18pub use gs_usb::{GsUsbBus, GsUsbConfig};
19pub use mock::{MockCanBus, MockRecordedCall};
20pub use poller::BusPoller;
21#[cfg(target_os = "linux")]
22pub use socketcan::SocketCanBus;
23
24/// The contract every CAN transport must satisfy.
25///
26/// The trait is intentionally object-safe so that `Box<dyn CanBus>` is a valid
27/// field type in [`crate::Bus`] / `Robot`. Implementations must be `Send`
28/// because they may move between threads (background IO thread, future async
29/// adapter).
30pub trait CanBus: Send {
31 /// Human-readable interface name (`"vcan0"`, `"can0"`, `"mock-0"`).
32 fn name(&self) -> &str;
33
34 /// Runtime capabilities of this bus (FD support, max payload length).
35 fn capabilities(&self) -> BusCapabilities;
36
37 /// Send a single frame. Must not call `read` or `poll` internally.
38 fn send(&mut self, frame: &CanFrame) -> Result<(), TransportError>;
39
40 /// Drain every frame currently in the receive queue and return them in
41 /// arrival order. Returns `Ok(vec![])` immediately if the queue is empty.
42 fn drain_inbound_nonblocking(&mut self) -> Result<Vec<CanFrame>, TransportError>;
43
44 /// Pollable file descriptor for `poll(2)`-based multiplexing. Returns
45 /// `None` for transports that have no single pollable fd. The robot drains
46 /// those transports from memory on every [`crate::Robot::tick`] call.
47 fn raw_fd(&self) -> Option<RawFd>;
48}
49
50/// Errors returned by transport operations.
51#[derive(Debug, Error)]
52#[non_exhaustive]
53pub enum TransportError {
54 /// The requested transport setup is contradictory or unsupported.
55 #[error("invalid transport configuration: {0}")]
56 InvalidConfiguration(String),
57
58 /// The named interface does not exist on this host.
59 #[error("interface not found: {0}")]
60 InterfaceNotFound(String),
61
62 /// The caller lacks permission to open or operate the socket
63 /// (typically: missing `CAP_NET_RAW`).
64 #[error("permission denied opening CAN socket")]
65 PermissionDenied,
66
67 /// Generic IO error from the underlying syscall.
68 #[error("transport IO error: {0}")]
69 Io(#[from] io::Error),
70
71 /// The kernel's send buffer is full and remained full after the bounded
72 /// retry budget was exhausted.
73 #[error("send buffer full after retries")]
74 SendBufferFull,
75
76 /// A frame the codec produced was rejected before any IO was issued.
77 #[error("frame error: {0}")]
78 FrameError(#[from] FrameError),
79
80 /// A CAN-FD frame was passed to a classical-only bus.
81 #[error("CAN-FD frame on non-FD bus")]
82 FdFrameOnNonFdBus,
83
84 /// The frame's payload length exceeds the bus's max payload length.
85 #[error("payload length {len} exceeds bus max {max}")]
86 PayloadExceedsBusCapacity {
87 /// Payload length the caller attempted to send.
88 len: u8,
89 /// Bus's max payload length.
90 max: u8,
91 },
92
93 /// The transport cannot send 29-bit extended IDs.
94 #[error("extended (29-bit) CAN IDs not supported by this transport")]
95 ExtendedIdNotSupported,
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 #[test]
103 fn trait_is_object_safe() {
104 // Just needs to compile.
105 fn _accept(_b: Box<dyn CanBus>) {}
106 }
107}