etherage/
can.rs

1//! implementation of CoE (Canopen Over Ethercat)
2
3use crate::{
4    mailbox::{Mailbox, MailboxType, MailboxError, MailboxHeader},
5    sdo::Sdo,
6    data::{self, PduData, Storage, Cursor},
7    error::{EthercatError, EthercatResult},
8    };
9use bilge::prelude::*;
10use tokio::sync::Mutex;
11use std::sync::Arc;
12
13
14
15const MAILBOX_MAX_SIZE: usize = 1024;
16/// maximum byte size of sdo data that can be expedited
17const EXPEDITED_MAX_SIZE: usize = 4;
18/// maximum byte size of an sdo data that can be put in a sdo segment
19/// it is constrained by the mailbox buffer size on the slave
20const SDO_SEGMENT_MAX_SIZE: usize = MAILBOX_MAX_SIZE
21                                        - <MailboxHeader as PduData>::Packed::LEN
22                                        - <CoeHeader as PduData>::Packed::LEN
23                                        - <SdoSegmentHeader as PduData>::Packed::LEN;
24
25/**
26    implementation of CoE (Canopen Over Ethercat)
27
28    It works exactly as in a Can bus, except each of its frame is encapsulated in an ethercat mailbox frame, and PDOs access is therefore not realtime.
29    For realtime PDOs exchange, they must be mapped to the logical memory using a SM (Sync Manager) channel.
30
31    Canopen protocol exposes 2 data structures:
32
33    - a dictionnary of simple values or single level structures, for non-realtime access
34
35        these are named SDO (Service Data Object).
36        See [crate::sdo] for more details
37
38    - several buffers gathering dictionnary objects for realtime access
39
40        these are named PDO (Process Data Object)
41
42    The following shows how the mapping of SDOs to PDOs is done, how it extends to logical memory in the case of CoE, and how the master interacts with each memory area.
43    
44    ![CoE mapping](https://raw.githubusercontent.com/jimy-byerley/etherage/master/schemes/coe-mapping.svg)
45    
46    This scheme comes in addition to the slave memory areas described in [crate::rawmaster::RawMaster], for slaves supporting CoE.
47
48    A `Can` instance is generally obtained from [Slave::coe](crate::Slave::coe)
49*/
50pub struct Can {
51    mailbox: Arc<Mutex<Mailbox>>,
52}
53impl Can {
54    pub fn new(mailbox: Arc<Mutex<Mailbox>>) -> Can {
55        Can {mailbox}
56    }
57    /// read an SDO, any size
58    pub async fn sdo_read<T: PduData>(&mut self, sdo: &Sdo<T>, priority: u2) 
59        -> EthercatResult<T, CanError> 
60    {
61        let mut data = T::Packed::uninit();
62        Ok(T::unpack(self.sdo_read_slice(&sdo.downcast(), priority, data.as_mut()).await?)?)
63    }
64        
65    pub async fn sdo_read_slice<'b>(&mut self, sdo: &Sdo, priority: u2, data: &'b mut [u8]) 
66        -> EthercatResult<&'b mut [u8], CanError>   
67    {
68        let mut mailbox = self.mailbox.lock().await;
69        let mut buffer = [0; MAILBOX_MAX_SIZE];
70
71        // generic request
72        let mut frame = Cursor::new(buffer.as_mut_slice());
73        frame.pack(&CoeHeader::new(u9::new(0), CanService::SdoRequest)).unwrap();
74        frame.pack(&SdoHeader::new(
75                false,  // uninit
76                false,  // uninit
77                u2::new(0),  // uninit
78                sdo.sub.is_complete(),
79                u3::from(SdoCommandRequest::Upload),
80                sdo.index,
81                sdo.sub.unwrap(),
82            )).unwrap();
83        frame.write(&[0; 4]).unwrap();
84        mailbox.write(MailboxType::Can, priority, frame.finish()).await?;
85        
86        // receive data
87        let (header, frame) = Self::receive_sdo_response(
88                &mut mailbox,
89                &mut buffer, 
90                SdoCommandResponse::Upload, 
91                sdo,
92                ).await?;
93        if ! header.sized()
94            {return Err(Error::Protocol("got SDO response without data size"))}
95        
96        
97        if header.expedited() {
98            // expedited transfer
99            let total = EXPEDITED_MAX_SIZE - u8::from(header.size()) as usize;
100            if total > data.len() 
101                {return Err(Error::Master("data buffer is too small for requested SDO"))}
102            data[.. total].copy_from_slice(Cursor::new(frame)
103                .read(total)
104                .map_err(|_| Error::Protocol("inconsistent expedited response data size"))?
105                );
106            Ok(&mut data[.. total])
107        }
108        else {
109            // normal transfer, eventually segmented
110            let mut frame = Cursor::new(frame);
111            let total = frame.unpack::<u32>()
112                .map_err(|_| Error::Protocol("unable unpack sdo size from SDO response"))?
113                .try_into().expect("SDO is too big for master memory");
114            if total > data.len()
115                {return Err(Error::Master("read buffer is too small for requested SDO"))}
116            
117            let mut received = Cursor::new(&mut data.as_mut()[.. total]);
118            let mut toggle = false;
119            received.write(frame.remain())
120                .map_err(|_| Error::Protocol("received more data than declared from SDO"))?;
121            
122            // receive more data from segments
123            // TODO check for possible SDO error
124            while received.remain().len() != 0 {
125                // send segment request
126                {
127                    let mut frame = Cursor::new(buffer.as_mut_slice());
128                    frame.pack(&CoeHeader::new(u9::new(0), CanService::SdoRequest)).unwrap();
129                    frame.pack(&SdoSegmentHeader::new(
130                            false,
131                            u3::new(0),
132                            toggle,
133                            u3::from(SdoCommandRequest::UploadSegment),
134                        )).unwrap();
135                    frame.write(&[0; 7]).unwrap();
136                    mailbox.write(MailboxType::Can, priority, frame.finish()).await?;
137                }
138
139                // receive segment
140                {
141                    let (header, segment) = Self::receive_sdo_segment(
142                            &mut mailbox, 
143                            &mut buffer, 
144                            SdoCommandResponse::UploadSegment, 
145                            toggle,
146                            ).await?;
147                    let segment = &segment[.. received.remain().len()];
148                    received.write(segment)
149                        .map_err(|_| Error::Protocol("received more data than declared from SDO"))?;
150                    
151                    if ! header.more () {break}
152                }
153
154                toggle = ! toggle;
155            }
156            Ok(received.finish())
157        }
158
159        // TODO send SdoCommand::Abort in case any error
160    }
161    /// write an SDO, any size
162    pub async fn sdo_write<T: PduData>(&mut self, sdo: &Sdo<T>, priority: u2, data: T) 
163        -> EthercatResult<(), CanError>  
164    {
165        let mut packed = T::Packed::uninit();
166        data.pack(packed.as_mut())
167            .expect("unable to pack data for sending");
168        self.sdo_write_slice(&sdo.downcast(), priority, packed.as_ref()).await
169    }
170    pub async fn sdo_write_slice(&mut self, sdo: &Sdo, priority: u2, data: &[u8]) 
171        -> EthercatResult<(), CanError>  
172    {
173        let mut mailbox = self.mailbox.lock().await;		
174        let mut buffer = [0; MAILBOX_MAX_SIZE];
175        if data.len() <= EXPEDITED_MAX_SIZE {
176            // expedited transfer
177            // send data in the 4 bytes instead of data size
178            {
179                let mut frame = Cursor::new(buffer.as_mut_slice());
180                frame.pack(&CoeHeader::new(u9::new(0), CanService::SdoRequest)).unwrap();
181                frame.pack(&SdoHeader::new(
182                            true,
183                            true,
184                            u2::new((EXPEDITED_MAX_SIZE - data.len()) as u8),
185                            sdo.sub.is_complete(),
186                            u3::from(SdoCommandRequest::Download),
187                            sdo.index,
188                            sdo.sub.unwrap(),
189                        )).unwrap();
190                frame.write(data).unwrap();
191                frame.write(&[0; 4][data.len() ..]).unwrap();
192                mailbox.write(MailboxType::Can, priority, frame.finish()).await?;
193            }
194
195            // receive acknowledge
196            Self::receive_sdo_response(
197                &mut mailbox,
198                &mut buffer,
199                SdoCommandResponse::Download,
200                sdo,
201                ).await?;
202        }
203        else {
204            // normal transfer, eventually segmented
205            let mut data = Cursor::new(data.as_ref());
206
207            // send one download request with the start of data
208            {
209                let mut frame = Cursor::new(buffer.as_mut_slice());
210                frame.pack(&CoeHeader::new(u9::new(0), CanService::SdoRequest)).unwrap();
211                frame.pack(&SdoHeader::new(
212                            true,
213                            false,
214                            u2::new(0),
215                            sdo.sub.is_complete(),
216                            u3::from(SdoCommandRequest::Download),
217                            sdo.index,
218                            sdo.sub.unwrap(),
219                        )).unwrap();
220                frame.pack(&(data.remain().len() as u32)).unwrap();
221                let segment = data.remain().len().min(SDO_SEGMENT_MAX_SIZE);
222                frame.write(data.read(segment).unwrap()).unwrap();
223                mailbox.write(MailboxType::Can, priority, frame.finish()).await?;
224            }
225
226            // receive acknowledge
227            Self::receive_sdo_response(
228                &mut mailbox,
229                &mut buffer, 
230                SdoCommandResponse::Download, 
231                sdo,
232                ).await?;
233            
234            // send many segments for the rest of the data, aknowledge each time
235            let mut toggle = false;
236            while data.remain().len() != 0 {
237                // send segment
238                {
239                    let segment = data.remain().len().min(SDO_SEGMENT_MAX_SIZE);
240                    let mut frame = Cursor::new(buffer.as_mut_slice());
241                    frame.pack(&CoeHeader::new(u9::new(0), CanService::SdoRequest)).unwrap();
242                    frame.pack(&SdoSegmentHeader::new(
243                            data.remain().len() != 0,
244                            u3::new(0),
245                            toggle,
246                            u3::from(SdoCommandRequest::DownloadSegment),
247                        )).unwrap();
248                    frame.write(data.read(segment).unwrap()).unwrap();
249                    mailbox.write(MailboxType::Can, priority, frame.finish()).await?;
250                }
251
252                // receive aknowledge
253                Self::receive_sdo_segment(
254                    &mut mailbox,
255                    &mut buffer, 
256                    SdoCommandResponse::DownloadSegment, 
257                    toggle,
258                    ).await?;
259                toggle = !toggle;
260            }
261        }
262        Ok(())
263        
264        // TODO send SdoCommand::Abort in case any error
265    }
266
267    /// read the mailbox, check for
268    async fn receive_sdo_response<'b, T: PduData>(
269        mailbox: &mut Mailbox,
270        buffer: &'b mut [u8], 
271        expected: SdoCommandResponse,
272        sdo: &Sdo<T>, 
273        ) -> EthercatResult<(SdoHeader, &'b [u8]), CanError> 
274    {
275        let mut frame = Cursor::new(mailbox.read(MailboxType::Can, buffer).await?);
276        
277        let check_header = |header: SdoHeader| {
278            if header.index() != sdo.index        {return Err(Error::Protocol("slave answered about wrong item"))}
279            if header.sub() != sdo.sub.unwrap()   {return Err(Error::Protocol("slave answered about wrong subitem"))}
280            Ok(())
281        };
282        
283        match frame.unpack::<CoeHeader>()
284            .map_err(|_| Error::Protocol("unable to unpack COE frame header"))?
285            .service() 
286        {
287            CanService::SdoResponse => {
288                let header = frame.unpack::<SdoHeader>()
289                    .map_err(|_| Error::Protocol("unable to unpack SDO response header"))?;
290                if SdoCommandResponse::try_from(header.command()) != Ok(expected)
291                    {return Err(Error::Protocol("slave answered with wrong operation"))}
292                check_header(header)?;
293                Ok((header, frame.remain()))
294                },
295            CanService::SdoRequest => {
296                let header = frame.unpack::<SdoHeader>()
297                    .map_err(|_| Error::Protocol("unable to unpack SDO request header"))?;
298                if SdoCommandRequest::try_from(header.command()) != Ok(SdoCommandRequest::Abort)
299                    {return Err(Error::Protocol("slave answered a COE request"))}
300                check_header(header)?;
301                let error = frame.unpack::<SdoAbortCode>()
302                        .map_err(|_| Error::Protocol("unable to unpack SDO error code"))?;
303                Err(Error::Slave(mailbox.slave(), CanError::Sdo(error)))
304                },
305            _ => {return Err(Error::Protocol("unexpected COE service during SDO operation"))},
306        }
307    }
308
309    async fn receive_sdo_segment<'b>(
310        mailbox: &mut Mailbox,
311        buffer: &'b mut [u8], 
312        expected: SdoCommandResponse,
313        toggle: bool, 
314        ) -> EthercatResult<(SdoSegmentHeader, &'b [u8]), CanError> 
315    {
316        let mut frame = Cursor::new(mailbox.read(MailboxType::Can, buffer).await?);
317        
318        match frame.unpack::<CoeHeader>()
319            .map_err(|_|  Error::Protocol("unable to unpack COE frame header"))?
320            .service() 
321        {
322            CanService::SdoResponse => {
323                let header = frame.unpack::<SdoSegmentHeader>()
324                    .map_err(|_|  Error::Protocol("unable to unpack segment response header"))?;
325                if SdoCommandResponse::try_from(header.command()) != Ok(expected)
326                    {return Err(Error::Protocol("slave answered with a COE request"))}
327                if header.toggle() != toggle   
328                    {return Err(Error::Protocol("bad toggle bit in segment received"))}
329                Ok((header, frame.remain()))
330                },
331            CanService::SdoRequest => {
332                let header = frame.unpack::<SdoHeader>()
333                    .map_err(|_|  Error::Protocol("unable to unpack request header"))?;
334                if SdoCommandRequest::try_from(header.command()) != Ok(SdoCommandRequest::Abort)
335                    {return Err(Error::Protocol("slave answered a COE request"))}
336                let error = frame.unpack::<SdoAbortCode>()
337                        .map_err(|_| Error::Protocol("unable to unpack SDO error code"))?;
338                Err(Error::Slave(mailbox.slave(), CanError::Sdo(error)))
339                },
340            _ => {return Err(Error::Protocol("unexpected COE service during SDO segment operation"))},
341        }
342    }
343
344    pub fn pdo_read() {todo!()}
345    pub fn pdo_write() {todo!()}
346
347    pub fn info_dictionnary() {todo!()}
348    pub fn info_sdo() {todo!()}
349    pub fn info_subitem() {todo!()}
350}
351
352
353
354#[bitsize(16)]
355#[derive(TryFromBits, DebugBits, Copy, Clone)]
356pub struct CoeHeader {
357    /// present in the Can protocol, but not used in CoE
358    pub number: u9,
359    reserved: u3,
360    /// Can command
361    pub service: CanService,
362}
363data::bilge_pdudata!(CoeHeader, u16);
364
365/**
366    Type of can service
367
368    receiving and transmiting is from the point of view of the slave:
369        - transmitting is slave -> master
370        - receiving is master -> slave
371*/
372#[bitsize(4)]
373#[derive(TryFromBits, Debug, Copy, Clone, Eq, PartialEq)]
374pub enum CanService {
375    Emergency = 0x1,
376    SdoRequest = 0x2,
377    SdoResponse = 0x3,
378    TransmitPdo = 0x4,
379    ReceivePdo = 0x5,
380    TransmitPdoRemoteRequest = 0x6,
381    ReceivePdoRemoteRequest = 0x7,
382    SdoInformation = 0x8,
383}
384data::bilge_pdudata!(CanService, u4);
385
386
387// use crate::data::FrameData;
388// use core::marker::PhantomData;
389//
390// struct SdoFrame<'a, T: FrameData<'a>> {
391//     header: SdoHeader,
392//     data: T,
393//     phantom: PhantomData<&'a ()>,
394// }
395//
396// impl<'a, T: FrameData<'a>>   FrameData<'a> for SdoFrame<'a, T> {
397//     fn pack(&self, dst: &mut [u8]) -> PackingResult<()> {
398//         dst[.. SdoHeader::packed_size()].copy_from_slice(&self.header.pack());
399//         self.data.pack(&mut dst[SdoHeader::packed_size() ..])?;
400//         Ok(())
401//     }
402//     fn unpack(src: &'a [u8]) -> PackingResult<Self> {
403// 		let header = SdoHeader::unpack(src)?;
404//         Ok(Self {
405//             header,
406//             data: T::unpack(&src[SdoHeader::packed_size() ..][.. header.length() as usize])?,
407//         })
408//     }
409// }
410
411
412/// Header for operations with SDOs
413///
414/// ETG.1000.6 5.6.2
415#[bitsize(32)]
416#[derive(TryFromBits, DebugBits, Copy, Clone)]
417pub struct SdoHeader {
418    /// true if field `size` is used
419    pub sized: bool,
420    /// true in case of an expedited transfer (the data size specified by `size`)
421    pub expedited: bool,
422    /// indicate the data size but not as an integer.
423    /// this value shall be `4 - data.len()`
424    pub size: u2,
425    /// true if a complete SDO is accessed
426    pub complete: bool,
427    /// operation to perform with the indexed SDO, this should be a value of [SdoCommandRequest] or [SdoCommandResponse]
428    pub command: u3,
429    /// SDO index
430    pub index: u16,
431    /**
432    - if subitem is accessed: SDO subindex
433    - if complete item is accessed:
434        + put 0 to include subindex 0 in transmission
435        + put 1 to exclude subindex 0 from transmission
436    */
437    pub sub: u8,
438}
439data::bilge_pdudata!(SdoHeader, u32);
440
441#[bitsize(8)]
442#[derive(TryFromBits, DebugBits, Copy, Clone)]
443pub struct SdoSegmentHeader {
444    pub more: bool,
445    pub size: u3,
446    pub toggle: bool,
447    pub command: u3,
448}
449data::bilge_pdudata!(SdoSegmentHeader, u8);
450
451/// request operation to perform with an SDO in CoE
452///
453/// ETG.1000.6 5.6.2.1-7
454#[bitsize(3)]
455#[derive(TryFromBits, Debug, Copy, Clone, Eq, PartialEq)]
456pub enum SdoCommandRequest {
457    Download = 0x1,
458    DownloadSegment = 0x0,
459    Upload = 0x2,
460    UploadSegment = 0x3,
461    Abort = 0x4,
462}
463data::bilge_pdudata!(SdoCommandRequest, u3);
464
465/// response operation to perform with an SDO in CoE
466///
467/// ETG.1000.6 5.6.2.1-7
468#[bitsize(3)]
469#[derive(TryFromBits, Debug, Copy, Clone, Eq, PartialEq)]
470pub enum SdoCommandResponse {
471    Download = 0x3,
472    DownloadSegment = 0x1,
473    Upload = 0x2,
474    UploadSegment = 0x0,
475    Abort = 0x4,
476}
477data::bilge_pdudata!(SdoCommandResponse, u3);
478
479#[bitsize(32)]
480#[derive(TryFromBits, Debug, Copy, Clone, Eq, PartialEq)]
481pub enum SdoAbortCode {
482    /// Toggle bit not changed
483    BadToggle = 0x05_03_00_00,
484    /// SDO protocol timeout
485    Timeout = 0x05_04_00_00,
486    /// Client/Server command specifier not valid or unknown
487    UnsupportedCommand = 0x05_04_00_01,
488    /// Out of memory
489    OufOfMemory = 0x05_04_00_05,
490    /// Unsupported access to an object, this is raised when trying to access a complete SDO when complete SDO access is not supported
491    UnsupportedAccess = 0x06_01_00_00,
492    /// Attempt to read to a write only object
493    WriteOnly = 0x06_01_00_01,
494    /// Attempt to write to a read only object
495    ReadOnly = 0x06_01_00_02,
496    /// Subindex cannot be written, SI0 must be 0 for write access
497    WriteError = 0x06_01_00_03,
498    /// SDO Complete access not supported for objects of variable length such as ENUM object types
499    VariableLength = 0x06_01_00_04,
500    /// Object length exceeds mailbox size
501    ObjectTooBig = 0x06_01_00_05,
502    /// Object mapped to RxPDO, SDO Download blocked
503    LockedByPdo = 0x06_01_00_06,
504    /// The object does not exist in the object directory
505    InvalidIndex = 0x06_02_00_00,
506    /// The object can not be mapped into the PDO
507    CannotMap = 0x06_04_00_41,
508    /// The number and length of the objects to be mapped would exceed the PDO length
509    PdoTooSmall = 0x06_04_00_42,
510    /// General parameter incompatibility reason
511    IncompatibleParameter = 0x06_04_00_43,
512    /// General internal incompatibility in the device
513    IncompatibleDevice = 0x06_04_00_47,
514    /// Access failed due to a hardware error
515    HardwareError = 0x06_06_00_00,
516    /// Data type does not match, length of service parameter does not match
517    InvalidLength = 0x06_07_00_10,
518    /// Data type does not match, length of service parameter too high
519    ServiceTooBig = 0x06_07_00_12,
520    /// Data type does not match, length of service parameter too low
521    ServiceTooSmall = 0x06_07_00_13,
522    /// Subindex does not exist
523    InvalidSubIndex = 0x06_09_00_11,
524    /// Value range of parameter exceeded (only for write access)
525    ValueOutOfRange = 0x06_09_00_30,
526    /// Value of parameter written too high
527    ValueTooHigh = 0x06_09_00_31,
528    /// Value of parameter written too low
529    ValueTooLow = 0x06_09_00_32,
530    /// Maximum value is less than minimum value
531    InvalidRange = 0x06_09_00_36,
532    /// General error
533    GeneralError = 0x08_00_00_00,
534    /**
535    Data cannot be transferred or stored to the application
536
537    NOTE: This is the general Abort Code in case no further detail on the reason can determined. It is recommended to use one of the more detailed Abort Codes (0x08000021, 0x08000022)
538    */
539    Refused = 0x08_00_00_20,
540    /**
541    Data cannot be transferred or stored to the application because of local control
542
543    NOTE: “local control” means an application specific reason. It does not mean the
544    ESM-specific control
545    */
546    ApplicationRefused = 0x08_00_00_21,
547    /**
548    Data cannot be transferred or stored to the application because of the present device state
549
550    NOTE: “device state” means the ESM state
551    */
552    StateRefused = 0x08_00_00_22,
553    /// Object dictionary dynamic generation fails or no object dictionary is present
554    DictionnaryEmpty = 0x08_00_00_23,
555}
556data::bilge_pdudata!(SdoAbortCode, u32);
557
558impl SdoAbortCode {
559    pub fn object_related(self) -> bool   {u32::from(self) >> 24 == 0x06}
560    pub fn subitem_related(self) -> bool  {u32::from(self) >> 16 == 0x06_09}
561    pub fn mapping_related(self) -> bool  {u32::from(self) >> 16 == 0x06_04}
562    pub fn device_related(self) -> bool   {u32::from(self) >> 24 == 0x08}
563    pub fn protocol_related(self) -> bool {u32::from(self) >> 24 == 0x05}
564}
565
566/// error type returned by the CoE functions
567type Error = EthercatError<CanError>;
568
569#[derive(Copy, Clone, Debug, Eq, PartialEq)]
570pub enum CanError {
571    Mailbox(MailboxError),
572    Sdo(SdoAbortCode),
573//     Pdo(PdoAbortCode),
574}
575impl From<MailboxError> for CanError {
576    fn from(src: MailboxError) -> Self {CanError::Mailbox(src)}
577}
578impl From<EthercatError<MailboxError>> for EthercatError<CanError> {
579    fn from(src: EthercatError<MailboxError>) -> Self {src.into()}
580}
581impl From<EthercatError<()>> for EthercatError<CanError> {
582    fn from(src: EthercatError<()>) -> Self {src.upgrade()}
583}
584