use std::fmt;
use std::io;
use std::time::Duration;
use embedded_can::Frame as _;
use socketcan::{CanFrame, CanSocket, Socket};
use canopen_rs::datatypes::{DataType, Value};
use canopen_rs::nmt::{encode_command, NMT_COMMAND_COB_ID};
use canopen_rs::object_dictionary::Address;
use canopen_rs::sdo::{SdoClient, SdoEvent};
use canopen_rs::transport::{cob_id, frame_from};
use canopen_rs::types::NodeId;
use canopen_rs::NmtCommand;
#[derive(Debug, Clone)]
pub struct Received {
pub cob_id: u16,
data: [u8; 8],
len: usize,
}
impl Received {
pub(crate) fn new(cob_id: u16, bytes: &[u8]) -> Self {
let len = bytes.len().min(8);
let mut data = [0u8; 8];
data[..len].copy_from_slice(&bytes[..len]);
Self { cob_id, data, len }
}
pub fn data(&self) -> &[u8] {
&self.data[..self.len]
}
pub fn payload(&self) -> &[u8; 8] {
&self.data
}
}
#[derive(Debug)]
pub enum SdoError {
Io(io::Error),
Aborted(u32),
NoValue,
}
impl fmt::Display for SdoError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SdoError::Io(e) => write!(f, "SDO transport I/O error: {e}"),
SdoError::Aborted(code) => write!(f, "SDO transfer aborted (code {code:#010x})"),
SdoError::NoValue => f.write_str("SDO transfer completed without a value"),
}
}
}
impl std::error::Error for SdoError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
SdoError::Io(e) => Some(e),
_ => None,
}
}
}
impl From<io::Error> for SdoError {
fn from(e: io::Error) -> Self {
SdoError::Io(e)
}
}
pub(crate) fn open_error(interface: &str, e: io::Error) -> io::Error {
let hint = if e.raw_os_error() == Some(19) {
format!(" — interface not found; is it up? (e.g. `sudo ip link set up {interface}`)")
} else {
String::new()
};
io::Error::new(
e.kind(),
format!("opening CAN interface '{interface}': {e}{hint}"),
)
}
#[derive(Debug)]
pub struct SocketCan {
socket: CanSocket,
}
impl SocketCan {
pub fn open(interface: &str) -> io::Result<Self> {
CanSocket::open(interface)
.map(|socket| Self { socket })
.map_err(|e| open_error(interface, e))
}
pub fn set_read_timeout(&self, timeout: Duration) -> io::Result<()> {
self.socket.set_read_timeout(timeout)
}
pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
self.socket.set_nonblocking(nonblocking)
}
pub fn send(&self, cob_id: u16, data: &[u8]) -> io::Result<()> {
let frame: CanFrame = frame_from(cob_id, data).ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
"invalid COB-ID or over-long data",
)
})?;
self.socket.write_frame(&frame)
}
pub fn recv(&self) -> io::Result<Received> {
loop {
let frame = self.socket.read_frame()?;
if !frame.is_data_frame() {
continue;
}
let Some(cob) = cob_id(&frame) else { continue };
return Ok(Received::new(cob, frame.data()));
}
}
pub fn send_nmt(&self, command: NmtCommand, target: NodeId) -> io::Result<()> {
self.send(NMT_COMMAND_COB_ID, &encode_command(command, target))
}
fn recv_on(&self, cob_id: u16) -> io::Result<Received> {
loop {
let frame = self.recv()?;
if frame.cob_id == cob_id {
return Ok(frame);
}
}
}
pub fn sdo_read(
&self,
node: NodeId,
addr: Address,
data_type: DataType,
) -> Result<Value, SdoError> {
let mut client = SdoClient::new(node);
let mut request = client.read(addr, data_type);
loop {
self.send(client.request_cob_id(), &request)?;
let reply = self.recv_on(client.response_cob_id())?;
match client.on_response(reply.payload()) {
SdoEvent::Send(next) => request = next,
SdoEvent::Complete(value) => return value.ok_or(SdoError::NoValue),
SdoEvent::Aborted(code) => return Err(SdoError::Aborted(code)),
}
}
}
pub fn sdo_write(&self, node: NodeId, addr: Address, value: Value) -> Result<(), SdoError> {
let mut client = SdoClient::new(node);
let mut request = client.write(addr, value);
loop {
self.send(client.request_cob_id(), &request)?;
let reply = self.recv_on(client.response_cob_id())?;
match client.on_response(reply.payload()) {
SdoEvent::Send(next) => request = next,
SdoEvent::Complete(_) => return Ok(()),
SdoEvent::Aborted(code) => return Err(SdoError::Aborted(code)),
}
}
}
}