use crate::{
mailbox::{Mailbox, MailboxType, MailboxError, MailboxHeader},
sdo::Sdo,
data::{self, PduData, Storage, Cursor},
error::{EthercatError, EthercatResult},
};
use bilge::prelude::*;
use tokio::sync::Mutex;
use std::sync::Arc;
const MAILBOX_MAX_SIZE: usize = 1024;
const EXPEDITED_MAX_SIZE: usize = 4;
const SDO_SEGMENT_MAX_SIZE: usize = MAILBOX_MAX_SIZE
- <MailboxHeader as PduData>::Packed::LEN
- <CoeHeader as PduData>::Packed::LEN
- <SdoSegmentHeader as PduData>::Packed::LEN;
pub struct Can {
mailbox: Arc<Mutex<Mailbox>>,
}
impl Can {
pub fn new(mailbox: Arc<Mutex<Mailbox>>) -> Can {
Can {mailbox}
}
pub async fn sdo_read<T: PduData>(&mut self, sdo: &Sdo<T>, priority: u2)
-> EthercatResult<T, CanError>
{
let mut data = T::Packed::uninit();
Ok(T::unpack(self.sdo_read_slice(&sdo.downcast(), priority, data.as_mut()).await?)?)
}
pub async fn sdo_read_slice<'b>(&mut self, sdo: &Sdo, priority: u2, data: &'b mut [u8])
-> EthercatResult<&'b mut [u8], CanError>
{
let mut mailbox = self.mailbox.lock().await;
let mut buffer = [0; MAILBOX_MAX_SIZE];
let mut frame = Cursor::new(buffer.as_mut_slice());
frame.pack(&CoeHeader::new(u9::new(0), CanService::SdoRequest)).unwrap();
frame.pack(&SdoHeader::new(
false, false, u2::new(0), sdo.sub.is_complete(),
u3::from(SdoCommandRequest::Upload),
sdo.index,
sdo.sub.unwrap(),
)).unwrap();
frame.write(&[0; 4]).unwrap();
mailbox.write(MailboxType::Can, priority, frame.finish()).await?;
let (header, frame) = Self::receive_sdo_response(
&mut mailbox,
&mut buffer,
SdoCommandResponse::Upload,
sdo,
).await?;
if ! header.sized()
{return Err(Error::Protocol("got SDO response without data size"))}
if header.expedited() {
let total = EXPEDITED_MAX_SIZE - u8::from(header.size()) as usize;
if total > data.len()
{return Err(Error::Master("data buffer is too small for requested SDO"))}
data[.. total].copy_from_slice(Cursor::new(frame)
.read(total)
.map_err(|_| Error::Protocol("inconsistent expedited response data size"))?
);
Ok(&mut data[.. total])
}
else {
let mut frame = Cursor::new(frame);
let total = frame.unpack::<u32>()
.map_err(|_| Error::Protocol("unable unpack sdo size from SDO response"))?
.try_into().expect("SDO is too big for master memory");
if total > data.len()
{return Err(Error::Master("read buffer is too small for requested SDO"))}
let mut received = Cursor::new(&mut data.as_mut()[.. total]);
let mut toggle = false;
received.write(frame.remain())
.map_err(|_| Error::Protocol("received more data than declared from SDO"))?;
while received.remain().len() != 0 {
{
let mut frame = Cursor::new(buffer.as_mut_slice());
frame.pack(&CoeHeader::new(u9::new(0), CanService::SdoRequest)).unwrap();
frame.pack(&SdoSegmentHeader::new(
false,
u3::new(0),
toggle,
u3::from(SdoCommandRequest::UploadSegment),
)).unwrap();
frame.write(&[0; 7]).unwrap();
mailbox.write(MailboxType::Can, priority, frame.finish()).await?;
}
{
let (header, segment) = Self::receive_sdo_segment(
&mut mailbox,
&mut buffer,
SdoCommandResponse::UploadSegment,
toggle,
).await?;
let segment = &segment[.. received.remain().len()];
received.write(segment)
.map_err(|_| Error::Protocol("received more data than declared from SDO"))?;
if ! header.more () {break}
}
toggle = ! toggle;
}
Ok(received.finish())
}
}
pub async fn sdo_write<T: PduData>(&mut self, sdo: &Sdo<T>, priority: u2, data: T)
-> EthercatResult<(), CanError>
{
let mut packed = T::Packed::uninit();
data.pack(packed.as_mut())
.expect("unable to pack data for sending");
self.sdo_write_slice(&sdo.downcast(), priority, packed.as_ref()).await
}
pub async fn sdo_write_slice(&mut self, sdo: &Sdo, priority: u2, data: &[u8])
-> EthercatResult<(), CanError>
{
let mut mailbox = self.mailbox.lock().await;
let mut buffer = [0; MAILBOX_MAX_SIZE];
if data.len() <= EXPEDITED_MAX_SIZE {
{
let mut frame = Cursor::new(buffer.as_mut_slice());
frame.pack(&CoeHeader::new(u9::new(0), CanService::SdoRequest)).unwrap();
frame.pack(&SdoHeader::new(
true,
true,
u2::new((EXPEDITED_MAX_SIZE - data.len()) as u8),
sdo.sub.is_complete(),
u3::from(SdoCommandRequest::Download),
sdo.index,
sdo.sub.unwrap(),
)).unwrap();
frame.write(data).unwrap();
frame.write(&[0; 4][data.len() ..]).unwrap();
mailbox.write(MailboxType::Can, priority, frame.finish()).await?;
}
Self::receive_sdo_response(
&mut mailbox,
&mut buffer,
SdoCommandResponse::Download,
sdo,
).await?;
}
else {
let mut data = Cursor::new(data.as_ref());
{
let mut frame = Cursor::new(buffer.as_mut_slice());
frame.pack(&CoeHeader::new(u9::new(0), CanService::SdoRequest)).unwrap();
frame.pack(&SdoHeader::new(
true,
false,
u2::new(0),
sdo.sub.is_complete(),
u3::from(SdoCommandRequest::Download),
sdo.index,
sdo.sub.unwrap(),
)).unwrap();
frame.pack(&(data.remain().len() as u32)).unwrap();
let segment = data.remain().len().min(SDO_SEGMENT_MAX_SIZE);
frame.write(data.read(segment).unwrap()).unwrap();
mailbox.write(MailboxType::Can, priority, frame.finish()).await?;
}
Self::receive_sdo_response(
&mut mailbox,
&mut buffer,
SdoCommandResponse::Download,
sdo,
).await?;
let mut toggle = false;
while data.remain().len() != 0 {
{
let segment = data.remain().len().min(SDO_SEGMENT_MAX_SIZE);
let mut frame = Cursor::new(buffer.as_mut_slice());
frame.pack(&CoeHeader::new(u9::new(0), CanService::SdoRequest)).unwrap();
frame.pack(&SdoSegmentHeader::new(
data.remain().len() != 0,
u3::new(0),
toggle,
u3::from(SdoCommandRequest::DownloadSegment),
)).unwrap();
frame.write(data.read(segment).unwrap()).unwrap();
mailbox.write(MailboxType::Can, priority, frame.finish()).await?;
}
Self::receive_sdo_segment(
&mut mailbox,
&mut buffer,
SdoCommandResponse::DownloadSegment,
toggle,
).await?;
toggle = !toggle;
}
}
Ok(())
}
async fn receive_sdo_response<'b, T: PduData>(
mailbox: &mut Mailbox,
buffer: &'b mut [u8],
expected: SdoCommandResponse,
sdo: &Sdo<T>,
) -> EthercatResult<(SdoHeader, &'b [u8]), CanError>
{
let mut frame = Cursor::new(mailbox.read(MailboxType::Can, buffer).await?);
let check_header = |header: SdoHeader| {
if header.index() != sdo.index {return Err(Error::Protocol("slave answered about wrong item"))}
if header.sub() != sdo.sub.unwrap() {return Err(Error::Protocol("slave answered about wrong subitem"))}
Ok(())
};
match frame.unpack::<CoeHeader>()
.map_err(|_| Error::Protocol("unable to unpack COE frame header"))?
.service()
{
CanService::SdoResponse => {
let header = frame.unpack::<SdoHeader>()
.map_err(|_| Error::Protocol("unable to unpack SDO response header"))?;
if SdoCommandResponse::try_from(header.command()) != Ok(expected)
{return Err(Error::Protocol("slave answered with wrong operation"))}
check_header(header)?;
Ok((header, frame.remain()))
},
CanService::SdoRequest => {
let header = frame.unpack::<SdoHeader>()
.map_err(|_| Error::Protocol("unable to unpack SDO request header"))?;
if SdoCommandRequest::try_from(header.command()) != Ok(SdoCommandRequest::Abort)
{return Err(Error::Protocol("slave answered a COE request"))}
check_header(header)?;
let error = frame.unpack::<SdoAbortCode>()
.map_err(|_| Error::Protocol("unable to unpack SDO error code"))?;
Err(Error::Slave(mailbox.slave(), CanError::Sdo(error)))
},
_ => {return Err(Error::Protocol("unexpected COE service during SDO operation"))},
}
}
async fn receive_sdo_segment<'b>(
mailbox: &mut Mailbox,
buffer: &'b mut [u8],
expected: SdoCommandResponse,
toggle: bool,
) -> EthercatResult<(SdoSegmentHeader, &'b [u8]), CanError>
{
let mut frame = Cursor::new(mailbox.read(MailboxType::Can, buffer).await?);
match frame.unpack::<CoeHeader>()
.map_err(|_| Error::Protocol("unable to unpack COE frame header"))?
.service()
{
CanService::SdoResponse => {
let header = frame.unpack::<SdoSegmentHeader>()
.map_err(|_| Error::Protocol("unable to unpack segment response header"))?;
if SdoCommandResponse::try_from(header.command()) != Ok(expected)
{return Err(Error::Protocol("slave answered with a COE request"))}
if header.toggle() != toggle
{return Err(Error::Protocol("bad toggle bit in segment received"))}
Ok((header, frame.remain()))
},
CanService::SdoRequest => {
let header = frame.unpack::<SdoHeader>()
.map_err(|_| Error::Protocol("unable to unpack request header"))?;
if SdoCommandRequest::try_from(header.command()) != Ok(SdoCommandRequest::Abort)
{return Err(Error::Protocol("slave answered a COE request"))}
let error = frame.unpack::<SdoAbortCode>()
.map_err(|_| Error::Protocol("unable to unpack SDO error code"))?;
Err(Error::Slave(mailbox.slave(), CanError::Sdo(error)))
},
_ => {return Err(Error::Protocol("unexpected COE service during SDO segment operation"))},
}
}
pub fn pdo_read() {todo!()}
pub fn pdo_write() {todo!()}
pub fn info_dictionnary() {todo!()}
pub fn info_sdo() {todo!()}
pub fn info_subitem() {todo!()}
}
#[bitsize(16)]
#[derive(TryFromBits, DebugBits, Copy, Clone)]
pub struct CoeHeader {
pub number: u9,
reserved: u3,
pub service: CanService,
}
data::bilge_pdudata!(CoeHeader, u16);
#[bitsize(4)]
#[derive(TryFromBits, Debug, Copy, Clone, Eq, PartialEq)]
pub enum CanService {
Emergency = 0x1,
SdoRequest = 0x2,
SdoResponse = 0x3,
TransmitPdo = 0x4,
ReceivePdo = 0x5,
TransmitPdoRemoteRequest = 0x6,
ReceivePdoRemoteRequest = 0x7,
SdoInformation = 0x8,
}
data::bilge_pdudata!(CanService, u4);
#[bitsize(32)]
#[derive(TryFromBits, DebugBits, Copy, Clone)]
pub struct SdoHeader {
pub sized: bool,
pub expedited: bool,
pub size: u2,
pub complete: bool,
pub command: u3,
pub index: u16,
pub sub: u8,
}
data::bilge_pdudata!(SdoHeader, u32);
#[bitsize(8)]
#[derive(TryFromBits, DebugBits, Copy, Clone)]
pub struct SdoSegmentHeader {
pub more: bool,
pub size: u3,
pub toggle: bool,
pub command: u3,
}
data::bilge_pdudata!(SdoSegmentHeader, u8);
#[bitsize(3)]
#[derive(TryFromBits, Debug, Copy, Clone, Eq, PartialEq)]
pub enum SdoCommandRequest {
Download = 0x1,
DownloadSegment = 0x0,
Upload = 0x2,
UploadSegment = 0x3,
Abort = 0x4,
}
data::bilge_pdudata!(SdoCommandRequest, u3);
#[bitsize(3)]
#[derive(TryFromBits, Debug, Copy, Clone, Eq, PartialEq)]
pub enum SdoCommandResponse {
Download = 0x3,
DownloadSegment = 0x1,
Upload = 0x2,
UploadSegment = 0x0,
Abort = 0x4,
}
data::bilge_pdudata!(SdoCommandResponse, u3);
#[bitsize(32)]
#[derive(TryFromBits, Debug, Copy, Clone, Eq, PartialEq)]
pub enum SdoAbortCode {
BadToggle = 0x05_03_00_00,
Timeout = 0x05_04_00_00,
UnsupportedCommand = 0x05_04_00_01,
OufOfMemory = 0x05_04_00_05,
UnsupportedAccess = 0x06_01_00_00,
WriteOnly = 0x06_01_00_01,
ReadOnly = 0x06_01_00_02,
WriteError = 0x06_01_00_03,
VariableLength = 0x06_01_00_04,
ObjectTooBig = 0x06_01_00_05,
LockedByPdo = 0x06_01_00_06,
InvalidIndex = 0x06_02_00_00,
CannotMap = 0x06_04_00_41,
PdoTooSmall = 0x06_04_00_42,
IncompatibleParameter = 0x06_04_00_43,
IncompatibleDevice = 0x06_04_00_47,
HardwareError = 0x06_06_00_00,
InvalidLength = 0x06_07_00_10,
ServiceTooBig = 0x06_07_00_12,
ServiceTooSmall = 0x06_07_00_13,
InvalidSubIndex = 0x06_09_00_11,
ValueOutOfRange = 0x06_09_00_30,
ValueTooHigh = 0x06_09_00_31,
ValueTooLow = 0x06_09_00_32,
InvalidRange = 0x06_09_00_36,
GeneralError = 0x08_00_00_00,
Refused = 0x08_00_00_20,
ApplicationRefused = 0x08_00_00_21,
StateRefused = 0x08_00_00_22,
DictionnaryEmpty = 0x08_00_00_23,
}
data::bilge_pdudata!(SdoAbortCode, u32);
impl SdoAbortCode {
pub fn object_related(self) -> bool {u32::from(self) >> 24 == 0x06}
pub fn subitem_related(self) -> bool {u32::from(self) >> 16 == 0x06_09}
pub fn mapping_related(self) -> bool {u32::from(self) >> 16 == 0x06_04}
pub fn device_related(self) -> bool {u32::from(self) >> 24 == 0x08}
pub fn protocol_related(self) -> bool {u32::from(self) >> 24 == 0x05}
}
type Error = EthercatError<CanError>;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum CanError {
Mailbox(MailboxError),
Sdo(SdoAbortCode),
}
impl From<MailboxError> for CanError {
fn from(src: MailboxError) -> Self {CanError::Mailbox(src)}
}
impl From<EthercatError<MailboxError>> for EthercatError<CanError> {
fn from(src: EthercatError<MailboxError>) -> Self {src.into()}
}
impl From<EthercatError<()>> for EthercatError<CanError> {
fn from(src: EthercatError<()>) -> Self {src.upgrade()}
}