use std::io;
use embedded_can::Frame as _;
use socketcan::tokio::CanSocket;
use socketcan::CanFrame;
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;
use crate::transport::{open_error, Received, SdoError};
#[derive(Debug)]
pub struct AsyncSocketCan {
socket: CanSocket,
}
impl AsyncSocketCan {
pub fn open(interface: &str) -> io::Result<Self> {
CanSocket::open(interface)
.map(|socket| Self { socket })
.map_err(|e| open_error(interface, e))
}
pub async 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).await
}
pub async fn recv(&self) -> io::Result<Received> {
loop {
let frame = self.socket.read_frame().await?;
if !frame.is_data_frame() {
continue;
}
let Some(cob) = cob_id(&frame) else { continue };
return Ok(Received::new(cob, frame.data()));
}
}
async fn recv_on(&self, cob_id: u16) -> io::Result<Received> {
loop {
let frame = self.recv().await?;
if frame.cob_id == cob_id {
return Ok(frame);
}
}
}
pub async fn send_nmt(&self, command: NmtCommand, target: NodeId) -> io::Result<()> {
self.send(NMT_COMMAND_COB_ID, &encode_command(command, target))
.await
}
pub async 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).await?;
let reply = self.recv_on(client.response_cob_id()).await?;
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 async 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).await?;
let reply = self.recv_on(client.response_cob_id()).await?;
match client.on_response(reply.payload()) {
SdoEvent::Send(next) => request = next,
SdoEvent::Complete(_) => return Ok(()),
SdoEvent::Aborted(code) => return Err(SdoError::Aborted(code)),
}
}
}
}