zencan_node/
pdo.rs

1//! Implementation of PDO configuration objects and PDO transmission
2//!
3
4use crate::object_dict::{
5    find_object_entry, ConstField, ODEntry, ObjectAccess, ProvidesSubObjects, SubObjectAccess,
6};
7use zencan_common::{
8    objects::{AccessType, DataType, ObjectCode, PdoMapping, SubInfo},
9    sdo::AbortCode,
10    AtomicCell, CanId,
11};
12
13/// Specifies the number of mapping parameters supported per PDO
14///
15/// Since we do not yet support CAN-FD, or sub-byte mapping, it's not possible to map more than 8
16/// objects to a single PDO
17const N_MAPPING_PARAMS: usize = 8;
18
19#[derive(Clone, Copy)]
20struct MappingEntry {
21    object: &'static ODEntry<'static>,
22    sub: u8,
23    length: u8,
24}
25
26/// Represents a single PDO state
27#[allow(missing_debug_implementations)]
28pub struct Pdo {
29    /// The COB-ID used to send or receive this PDO
30    cob_id: AtomicCell<CanId>,
31    /// Indicates if the PDO is enabled
32    valid: AtomicCell<bool>,
33    /// If set, this PDO cannot be requested via RTR
34    rtr_disabled: AtomicCell<bool>,
35    /// Transmission type field (subindex 0x2)
36    /// Determines when the PDO is sent/received
37    ///
38    /// 0 (unused): PDO is sent on receipt of SYNC, but only if the event has been triggered
39    /// 1 - 240: PDO is sent on receipt of every Nth SYNC message
40    /// 254: PDO is sent asynchronously on application request
41    transmission_type: AtomicCell<u8>,
42    /// Tracks the number of sync signals since this was last sent or received
43    sync_counter: AtomicCell<u8>,
44    /// The last received data value for an RPDO
45    pub buffered_value: AtomicCell<Option<[u8; 8]>>,
46    /// Indicates how many of the values in mapping_params are valid
47    ///
48    /// This represents sub0 for the mapping object
49    valid_maps: AtomicCell<u8>,
50    /// The mapping parameters
51    ///
52    /// These specify which objects are
53    mapping_params: [AtomicCell<Option<MappingEntry>>; N_MAPPING_PARAMS],
54}
55
56impl Default for Pdo {
57    fn default() -> Self {
58        Self::new()
59    }
60}
61
62impl Pdo {
63    /// Create a new PDO object
64    pub const fn new() -> Self {
65        let cob_id = AtomicCell::new(CanId::Std(0));
66        let valid = AtomicCell::new(false);
67        let rtr_disabled = AtomicCell::new(false);
68        let transmission_type = AtomicCell::new(0);
69        let sync_counter = AtomicCell::new(0);
70        let buffered_value = AtomicCell::new(None);
71        let valid_maps = AtomicCell::new(0);
72        let mapping_params = [const { AtomicCell::new(None) }; N_MAPPING_PARAMS];
73        Self {
74            cob_id,
75            valid,
76            rtr_disabled,
77            transmission_type,
78            sync_counter,
79            buffered_value,
80            valid_maps,
81            mapping_params,
82        }
83    }
84
85    /// Set the valid bit
86    pub fn set_valid(&self, value: bool) {
87        self.valid.store(value);
88    }
89
90    /// Get the valid bit value
91    pub fn valid(&self) -> bool {
92        self.valid.load()
93    }
94
95    /// Set the transmission type for this PDO
96    pub fn set_transmission_type(&self, value: u8) {
97        self.transmission_type.store(value);
98    }
99
100    /// Get the transmission type for this PDO
101    pub fn transmission_type(&self) -> u8 {
102        self.transmission_type.load()
103    }
104
105    /// Set the COB used for transmission of this PDO
106    pub fn set_cob_id(&self, value: CanId) {
107        self.cob_id.store(value)
108    }
109
110    /// Get the COB used for transmission of this PDO
111    pub fn cob_id(&self) -> CanId {
112        self.cob_id.load()
113    }
114
115    /// This function should be called when a SYNC event occurs
116    ///
117    /// It will return true if the PDO should be sent in response to the SYNC event
118    pub fn sync_update(&self) -> bool {
119        if !self.valid.load() {
120            return false;
121        }
122
123        let transmission_type = self.transmission_type.load();
124        if transmission_type == 0 {
125            // TODO: Figure out how to determine application "event" which triggers the PDO
126            // For now, send every sync
127            true
128        } else if transmission_type <= 240 {
129            let cnt = self.sync_counter.fetch_add(1) + 1;
130            cnt == transmission_type
131        } else {
132            false
133        }
134    }
135
136    /// Check mapped objects for TPDO event flag
137    pub fn read_events(&self) -> bool {
138        if !self.valid.load() {
139            return false;
140        }
141
142        for i in 0..self.mapping_params.len() {
143            let param = self.mapping_params[i].load();
144            if param.is_none() {
145                break;
146            }
147            let param = param.unwrap();
148            if param.object.data.read_event_flag(param.sub) {
149                return true;
150            }
151        }
152        false
153    }
154
155    pub(crate) fn clear_events(&self) {
156        for i in 0..self.mapping_params.len() {
157            let param = self.mapping_params[i].load();
158            if param.is_none() {
159                break;
160            }
161            let param = param.unwrap();
162            param.object.data.clear_events();
163        }
164    }
165
166    pub(crate) fn store_pdo_data(&self, data: &[u8]) {
167        let mut offset = 0;
168        let valid_maps = self.valid_maps.load() as usize;
169        for (i, param) in self.mapping_params.iter().enumerate() {
170            if i >= valid_maps {
171                break;
172            }
173            let param = param.load();
174            if param.is_none() {
175                break;
176            }
177            let param = param.unwrap();
178            let length = param.length as usize;
179            if offset + length > data.len() {
180                break;
181            }
182            let data_to_write = &data[offset..offset + length];
183            // validity of the mappings must be validated during write, so that error here is not
184            // possible
185            param.object.data.write(param.sub, data_to_write).ok();
186            offset += length;
187        }
188    }
189
190    pub(crate) fn read_pdo_data(&self, data: &mut [u8]) {
191        let mut offset = 0;
192        let valid_maps = self.valid_maps.load() as usize;
193        for (i, param) in self.mapping_params.iter().enumerate() {
194            if i >= valid_maps {
195                break;
196            }
197            let param = param.load();
198            // The first N params will be valid. Can assume if one is None, all remaining will be as
199            // well
200            if param.is_none() {
201                break;
202            }
203            let param = param.unwrap();
204            let length = param.length as usize;
205            if offset + length > data.len() {
206                break;
207            }
208            // validity of the mappings must be validated during write, so that error here is not
209            // possible
210            param
211                .object
212                .data
213                .read(param.sub, 0, &mut data[offset..offset + length])
214                .ok();
215            offset += length;
216        }
217    }
218}
219
220struct PdoCobSubObject {
221    pdo: &'static Pdo,
222}
223
224impl PdoCobSubObject {
225    pub const fn new(pdo: &'static Pdo) -> Self {
226        Self { pdo }
227    }
228}
229
230impl SubObjectAccess for PdoCobSubObject {
231    fn read(&self, offset: usize, buf: &mut [u8]) -> Result<usize, AbortCode> {
232        let cob_id = self.pdo.cob_id.load();
233        let mut value = cob_id.raw();
234        if cob_id.is_extended() {
235            value |= 1 << 29;
236        }
237        if self.pdo.rtr_disabled.load() {
238            value |= 1 << 30;
239        }
240        if !self.pdo.valid.load() {
241            value |= 1 << 31;
242        }
243
244        let bytes = value.to_le_bytes();
245        if offset < bytes.len() {
246            let read_len = buf.len().min(bytes.len() - offset);
247            buf[0..read_len].copy_from_slice(&bytes[offset..offset + read_len]);
248            Ok(read_len)
249        } else {
250            Ok(0)
251        }
252    }
253
254    fn read_size(&self) -> usize {
255        4
256    }
257
258    fn write(&self, data: &[u8]) -> Result<(), AbortCode> {
259        if data.len() < 4 {
260            Err(AbortCode::DataTypeMismatchLengthLow)
261        } else if data.len() > 4 {
262            Err(AbortCode::DataTypeMismatchLengthHigh)
263        } else {
264            let value = u32::from_le_bytes(data.try_into().unwrap());
265            let not_valid = (value & (1 << 31)) != 0;
266            let no_rtr = (value & (1 << 30)) != 0;
267            let extended_id = (value & (1 << 29)) != 0;
268
269            let can_id = if extended_id {
270                CanId::Extended(value & 0x1FFFFFFF)
271            } else {
272                CanId::Std((value & 0x7FF) as u16)
273            };
274            self.pdo.cob_id.store(can_id);
275            self.pdo.valid.store(!not_valid);
276            self.pdo.rtr_disabled.store(no_rtr);
277            Ok(())
278        }
279    }
280}
281
282struct PdoTransmissionTypeSubObject {
283    pdo: &'static Pdo,
284}
285
286impl PdoTransmissionTypeSubObject {
287    pub const fn new(pdo: &'static Pdo) -> Self {
288        Self { pdo }
289    }
290}
291
292impl SubObjectAccess for PdoTransmissionTypeSubObject {
293    fn read(&self, offset: usize, buf: &mut [u8]) -> Result<usize, AbortCode> {
294        if offset > 1 {
295            return Ok(0);
296        }
297        buf[0] = self.pdo.transmission_type();
298        Ok(1)
299    }
300
301    fn read_size(&self) -> usize {
302        1
303    }
304
305    fn write(&self, data: &[u8]) -> Result<(), AbortCode> {
306        if data.is_empty() {
307            Err(AbortCode::DataTypeMismatchLengthLow)
308        } else {
309            self.pdo.set_transmission_type(data[0]);
310            Ok(())
311        }
312    }
313}
314
315/// Implements a PDO communications config object for both RPDOs and TPDOs
316#[allow(missing_debug_implementations)]
317pub struct PdoCommObject {
318    cob: PdoCobSubObject,
319    transmission_type: PdoTransmissionTypeSubObject,
320}
321
322impl PdoCommObject {
323    /// Create a new PdoCommObject
324    pub const fn new(pdo: &'static Pdo) -> Self {
325        let cob = PdoCobSubObject::new(pdo);
326        let transmission_type = PdoTransmissionTypeSubObject::new(pdo);
327        Self {
328            cob,
329            transmission_type,
330        }
331    }
332}
333
334impl ProvidesSubObjects for PdoCommObject {
335    fn get_sub_object(&self, sub: u8) -> Option<(SubInfo, &dyn SubObjectAccess)> {
336        match sub {
337            0 => Some((
338                SubInfo::MAX_SUB_NUMBER,
339                const { &ConstField::new(2u8.to_le_bytes()) },
340            )),
341            1 => Some((SubInfo::new_u32().rw_access().persist(true), &self.cob)),
342            2 => Some((
343                SubInfo::new_u8().rw_access().persist(true),
344                &self.transmission_type,
345            )),
346            _ => None,
347        }
348    }
349
350    fn object_code(&self) -> ObjectCode {
351        ObjectCode::Record
352    }
353}
354
355/// Implements a PDO mapping config object for both TPDOs and RPDOs
356#[allow(missing_debug_implementations)]
357pub struct PdoMappingObject {
358    od: &'static [ODEntry<'static>],
359    pdo: &'static Pdo,
360}
361
362impl PdoMappingObject {
363    /// Create a new PdoMappingObject
364    pub const fn new(od: &'static [ODEntry<'static>], pdo: &'static Pdo) -> Self {
365        Self { od, pdo }
366    }
367}
368
369impl ObjectAccess for PdoMappingObject {
370    fn read(&self, sub: u8, offset: usize, buf: &mut [u8]) -> Result<usize, AbortCode> {
371        if sub == 0 {
372            if offset < 1 && !buf.is_empty() {
373                buf[0] = self.pdo.valid_maps.load();
374                Ok(1)
375            } else {
376                Ok(0)
377            }
378        } else if sub <= self.pdo.mapping_params.len() as u8 {
379            let value = if let Some(param) = self.pdo.mapping_params[(sub - 1) as usize].load() {
380                ((param.object.index as u32) << 16)
381                    + ((param.sub as u32) << 8)
382                    + param.length as u32 * 8
383            } else {
384                0u32
385            };
386            let bytes = value.to_le_bytes();
387            let read_len = buf.len().min(bytes.len() - offset);
388            buf[..read_len].copy_from_slice(&bytes[offset..offset + read_len]);
389            Ok(read_len)
390        } else {
391            Err(AbortCode::NoSuchSubIndex)
392        }
393    }
394
395    fn read_size(&self, sub: u8) -> Result<usize, AbortCode> {
396        if sub == 0 {
397            Ok(1)
398        } else if sub <= N_MAPPING_PARAMS as u8 {
399            Ok(4)
400        } else {
401            Err(AbortCode::NoSuchSubIndex)
402        }
403    }
404
405    fn write(&self, sub: u8, data: &[u8]) -> Result<(), AbortCode> {
406        if sub == 0 {
407            self.pdo.valid_maps.store(data[0]);
408            Ok(())
409        } else if sub <= self.pdo.mapping_params.len() as u8 {
410            if data.len() != 4 {
411                return Err(AbortCode::DataTypeMismatch);
412            }
413            let value = u32::from_le_bytes(data.try_into().unwrap());
414
415            let object_id = (value >> 16) as u16;
416            let mapping_sub = ((value & 0xFF00) >> 8) as u8;
417            // Rounding up to BYTES, because we do not currently support bit access
418            let length = (value & 0xFF) as usize;
419            if (length % 8) != 0 {
420                // only support byte level access for now
421                return Err(AbortCode::IncompatibleParameter);
422            }
423            let length = length / 8;
424            let entry = find_object_entry(self.od, object_id).ok_or(AbortCode::NoSuchObject)?;
425            let sub_info = entry.data.sub_info(mapping_sub)?;
426            if sub_info.size < length {
427                return Err(AbortCode::IncompatibleParameter);
428            }
429            self.pdo.mapping_params[(sub - 1) as usize].store(Some(MappingEntry {
430                object: entry,
431                sub: mapping_sub,
432                length: length as u8,
433            }));
434            Ok(())
435        } else {
436            Err(AbortCode::NoSuchSubIndex)
437        }
438    }
439
440    fn object_code(&self) -> ObjectCode {
441        ObjectCode::Record
442    }
443
444    fn sub_info(&self, sub: u8) -> Result<SubInfo, AbortCode> {
445        if sub == 0 {
446            Ok(SubInfo {
447                size: 1,
448                data_type: DataType::UInt8,
449                access_type: AccessType::Rw,
450                pdo_mapping: PdoMapping::None,
451                persist: true,
452            })
453        } else if sub <= self.pdo.mapping_params.len() as u8 {
454            Ok(SubInfo {
455                size: 4,
456                data_type: DataType::UInt32,
457                access_type: AccessType::Rw,
458                pdo_mapping: PdoMapping::None,
459                persist: true,
460            })
461        } else {
462            Err(AbortCode::NoSuchSubIndex)
463        }
464    }
465}