canopen_host/
transport.rs1use std::fmt;
21use std::io;
22use std::time::Duration;
23
24use embedded_can::Frame as _;
25use socketcan::{CanFrame, CanSocket, Socket};
26
27use canopen_rs::datatypes::{DataType, Value};
28use canopen_rs::nmt::{encode_command, NMT_COMMAND_COB_ID};
29use canopen_rs::object_dictionary::Address;
30use canopen_rs::sdo::{SdoClient, SdoEvent};
31use canopen_rs::transport::{cob_id, frame_from};
32use canopen_rs::types::NodeId;
33use canopen_rs::NmtCommand;
34
35#[derive(Debug, Clone)]
37pub struct Received {
38 pub cob_id: u16,
40 data: [u8; 8],
41 len: usize,
42}
43
44impl Received {
45 pub(crate) fn new(cob_id: u16, bytes: &[u8]) -> Self {
47 let len = bytes.len().min(8);
48 let mut data = [0u8; 8];
49 data[..len].copy_from_slice(&bytes[..len]);
50 Self { cob_id, data, len }
51 }
52
53 pub fn data(&self) -> &[u8] {
55 &self.data[..self.len]
56 }
57
58 pub fn payload(&self) -> &[u8; 8] {
61 &self.data
62 }
63}
64
65#[derive(Debug)]
67pub enum SdoError {
68 Io(io::Error),
70 Aborted(u32),
72 NoValue,
74}
75
76impl fmt::Display for SdoError {
77 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78 match self {
79 SdoError::Io(e) => write!(f, "SDO transport I/O error: {e}"),
80 SdoError::Aborted(code) => write!(f, "SDO transfer aborted (code {code:#010x})"),
81 SdoError::NoValue => f.write_str("SDO transfer completed without a value"),
82 }
83 }
84}
85
86impl std::error::Error for SdoError {
87 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
88 match self {
89 SdoError::Io(e) => Some(e),
90 _ => None,
91 }
92 }
93}
94
95impl From<io::Error> for SdoError {
96 fn from(e: io::Error) -> Self {
97 SdoError::Io(e)
98 }
99}
100
101pub(crate) fn open_error(interface: &str, e: io::Error) -> io::Error {
104 let hint = if e.raw_os_error() == Some(19) {
106 format!(" — interface not found; is it up? (e.g. `sudo ip link set up {interface}`)")
107 } else {
108 String::new()
109 };
110 io::Error::new(
111 e.kind(),
112 format!("opening CAN interface '{interface}': {e}{hint}"),
113 )
114}
115
116#[derive(Debug)]
118pub struct SocketCan {
119 socket: CanSocket,
120}
121
122impl SocketCan {
123 pub fn open(interface: &str) -> io::Result<Self> {
128 CanSocket::open(interface)
129 .map(|socket| Self { socket })
130 .map_err(|e| open_error(interface, e))
131 }
132
133 pub fn set_read_timeout(&self, timeout: Duration) -> io::Result<()> {
136 self.socket.set_read_timeout(timeout)
137 }
138
139 pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
141 self.socket.set_nonblocking(nonblocking)
142 }
143
144 pub fn send(&self, cob_id: u16, data: &[u8]) -> io::Result<()> {
146 let frame: CanFrame = frame_from(cob_id, data).ok_or_else(|| {
147 io::Error::new(
148 io::ErrorKind::InvalidInput,
149 "invalid COB-ID or over-long data",
150 )
151 })?;
152 self.socket.write_frame(&frame)
153 }
154
155 pub fn recv(&self) -> io::Result<Received> {
158 loop {
159 let frame = self.socket.read_frame()?;
160 if !frame.is_data_frame() {
161 continue;
162 }
163 let Some(cob) = cob_id(&frame) else { continue };
164 return Ok(Received::new(cob, frame.data()));
165 }
166 }
167
168 pub fn send_nmt(&self, command: NmtCommand, target: NodeId) -> io::Result<()> {
173 self.send(NMT_COMMAND_COB_ID, &encode_command(command, target))
174 }
175
176 fn recv_on(&self, cob_id: u16) -> io::Result<Received> {
178 loop {
179 let frame = self.recv()?;
180 if frame.cob_id == cob_id {
181 return Ok(frame);
182 }
183 }
184 }
185
186 pub fn sdo_read(
192 &self,
193 node: NodeId,
194 addr: Address,
195 data_type: DataType,
196 ) -> Result<Value, SdoError> {
197 let mut client = SdoClient::new(node);
198 let mut request = client.read(addr, data_type);
199 loop {
200 self.send(client.request_cob_id(), &request)?;
201 let reply = self.recv_on(client.response_cob_id())?;
202 match client.on_response(reply.payload()) {
203 SdoEvent::Send(next) => request = next,
204 SdoEvent::Complete(value) => return value.ok_or(SdoError::NoValue),
205 SdoEvent::Aborted(code) => return Err(SdoError::Aborted(code)),
206 }
207 }
208 }
209
210 pub fn sdo_write(&self, node: NodeId, addr: Address, value: Value) -> Result<(), SdoError> {
215 let mut client = SdoClient::new(node);
216 let mut request = client.write(addr, value);
217 loop {
218 self.send(client.request_cob_id(), &request)?;
219 let reply = self.recv_on(client.response_cob_id())?;
220 match client.on_response(reply.payload()) {
221 SdoEvent::Send(next) => request = next,
222 SdoEvent::Complete(_) => return Ok(()),
223 SdoEvent::Aborted(code) => return Err(SdoError::Aborted(code)),
224 }
225 }
226 }
227}