Skip to main content

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}