Skip to main content

zencan_node/
pdo.rs

1//! Implementation of PDO configuration objects and PDO transmission
2//!
3//! ## PDO Default Configuration
4//!
5//! PDO default configuration can be controlled in the device config, so that PDOs may be mapped to
6//! certain object and enabled by default in a device. This is done in the `[pdos]` section of the
7//! config, which is defined by the
8//! [`PdoDefaultConfig`](crate::common::device_config::PdoDefaultConfig) struct.
9//!
10//! The default PDO COB ID may be specified as an absolute value, or it may be offset by the node ID
11//! at runtime.
12//!
13//! Example default PDO config:
14//!
15//! ```toml
16//! [pdos]
17//! num_rpdo = 4
18//! num_tpdo = 4
19//!
20//! # Enable TPDO1 to send on 0x200 + NODE_ID
21//! [pdos.tpdo.1]
22//! enabled = true
23//! cob_id = 0x200
24//! add_node_id = true
25//! transmission_type = 254
26//! mappings = [
27//!     { index=0x2000, sub=1, size=32 },
28//! ]
29//!
30//! # Configure RPDO0 to receive on extended ID 0x5000
31//! [pdos.rpdo.0]
32//! enabled = true
33//! extended = true
34//! rtr_disabled = false
35//! cob_id = 0x5000
36//! add_node_id = false
37//! transmission_type = 254
38//! mappings = [
39//!     { index = 0x2000, sub=2, size=32 },
40//! ]
41//! ```
42
43use crate::{
44    node_state::NmtStateAccess,
45    object_dict::{
46        find_object_entry, ConstField, ODEntry, ObjectAccess, ProvidesSubObjects, SubObjectAccess,
47    },
48};
49use zencan_common::{
50    nmt::NmtState,
51    objects::{AccessType, DataType, ObjectCode, PdoMappable, SubInfo},
52    pdo::PdoMapping,
53    sdo::AbortCode,
54    AtomicCell, CanId, NodeId,
55};
56
57/// Specifies the number of mapping parameters supported per PDO
58///
59/// Since we do not yet support CAN-FD, or sub-byte mapping, it's not possible to map more than 8
60/// objects to a single PDO
61const N_MAPPING_PARAMS: usize = 8;
62
63#[derive(Clone, Copy)]
64/// Data structure for storing a PDO object mapping
65struct MappingEntry<'a> {
66    /// A reference to the object which is mapped
67    pub object: &'a ODEntry<'a>,
68    /// The index of the sub object mapped
69    pub sub: u8,
70    /// The length of the mapping in bytes
71    pub length: u8,
72}
73
74#[allow(missing_debug_implementations)]
75/// Initialization values for a PDO
76#[derive(Copy, Clone)]
77pub struct PdoDefaults<'a> {
78    cob_id: u32,
79    flags: u8,
80    transmission_type: u8,
81    mappings: &'a [u32],
82}
83
84impl Default for PdoDefaults<'_> {
85    fn default() -> Self {
86        Self::DEFAULT
87    }
88}
89
90#[allow(missing_docs)]
91impl<'a> PdoDefaults<'a> {
92    const ADD_NODE_ID_FLAG: usize = 0;
93    const VALID_FLAG: usize = 1;
94    const RTR_DISABLED_FLAG: usize = 2;
95    const IS_EXTENDED_FLAG: usize = 3;
96
97    /// The PDO defaults used when no other defaults are configured
98    pub const DEFAULT: PdoDefaults<'a> = Self {
99        cob_id: 0,
100        flags: 0,
101        transmission_type: 0,
102        mappings: &[],
103    };
104
105    /// Create a new PdoDefaults object
106    pub const fn new(
107        cob_id: u32,
108        extended: bool,
109        add_node_id: bool,
110        valid: bool,
111        rtr_disabled: bool,
112        transmission_type: u8,
113        mappings: &'static [u32],
114    ) -> Self {
115        // Store flags as a single field to save those precious few bytes
116        let mut flags = 0u8;
117        if valid {
118            flags |= 1 << Self::VALID_FLAG;
119        }
120        if rtr_disabled {
121            flags |= 1 << Self::RTR_DISABLED_FLAG;
122        }
123        if add_node_id {
124            flags |= 1 << Self::ADD_NODE_ID_FLAG;
125        }
126        if extended {
127            flags |= 1 << Self::IS_EXTENDED_FLAG;
128        }
129
130        Self {
131            cob_id,
132            flags,
133            transmission_type,
134            mappings,
135        }
136    }
137
138    pub const fn valid(&self) -> bool {
139        self.flags & (1 << Self::VALID_FLAG) != 0
140    }
141
142    pub const fn rtr_disabled(&self) -> bool {
143        self.flags & (1 << Self::RTR_DISABLED_FLAG) != 0
144    }
145
146    pub const fn add_node_id(&self) -> bool {
147        self.flags & (1 << Self::ADD_NODE_ID_FLAG) != 0
148    }
149
150    pub const fn extended(&self) -> bool {
151        self.flags & (1 << Self::IS_EXTENDED_FLAG) != 0
152    }
153
154    pub const fn can_id(&self, node_id: u8) -> CanId {
155        let id = if self.add_node_id() {
156            self.cob_id + node_id as u32
157        } else {
158            self.cob_id
159        };
160        if self.extended() {
161            CanId::Extended(id)
162        } else {
163            CanId::Std(id as u16)
164        }
165    }
166}
167
168/// Represents a single PDO state
169#[allow(missing_debug_implementations)]
170pub struct Pdo<'a> {
171    /// The object dictionary
172    ///
173    /// PDOs have to access other objects and use this to do so
174    od: &'a [ODEntry<'a>],
175    /// Accessor for the node NMT state
176    nmt_state: &'a dyn NmtStateAccess,
177    /// Configured Node ID for the system
178    node_id: AtomicCell<NodeId>,
179    /// The COB-ID used to send or receive this PDO
180    cob_id: AtomicCell<Option<CanId>>,
181    /// Indicates if the PDO is enabled
182    valid: AtomicCell<bool>,
183    /// If set, this PDO cannot be requested via RTR
184    rtr_disabled: AtomicCell<bool>,
185    /// Transmission type field (subindex 0x2)
186    /// Determines when the PDO is sent/received
187    ///
188    /// 0 (unused): PDO is sent on receipt of SYNC, but only if the event has been triggered
189    /// 1 - 240: PDO is sent on receipt of every Nth SYNC message
190    /// 254: PDO is sent asynchronously on application request
191    transmission_type: AtomicCell<u8>,
192    /// Tracks the number of sync signals since this was last sent or received
193    sync_counter: AtomicCell<u8>,
194    /// The last received data value for an RPDO, or ready to transmit data for a TPDO
195    pub buffered_value: AtomicCell<Option<[u8; 8]>>,
196    /// Indicates how many of the values in mapping_params are valid
197    ///
198    /// This represents sub0 for the mapping object
199    valid_maps: AtomicCell<u8>,
200    /// The mapping parameters
201    ///
202    /// These specify which objects are
203    mapping_params: [AtomicCell<Option<MappingEntry<'a>>>; N_MAPPING_PARAMS],
204    /// System default values for this PDO
205    defaults: Option<&'a PdoDefaults<'a>>,
206}
207
208impl<'a> Pdo<'a> {
209    /// Create a new PDO object
210    pub const fn new(od: &'a [ODEntry<'a>], nmt_state: &'a dyn NmtStateAccess) -> Self {
211        let cob_id = AtomicCell::new(None);
212        let node_id = AtomicCell::new(NodeId::Unconfigured);
213        let valid = AtomicCell::new(false);
214        let rtr_disabled = AtomicCell::new(false);
215        let transmission_type = AtomicCell::new(0);
216        let sync_counter = AtomicCell::new(0);
217        let buffered_value = AtomicCell::new(None);
218        let valid_maps = AtomicCell::new(0);
219        let mapping_params = [const { AtomicCell::new(None) }; N_MAPPING_PARAMS];
220        let defaults = None;
221        Self {
222            od,
223            nmt_state,
224            node_id,
225            cob_id,
226            valid,
227            rtr_disabled,
228            transmission_type,
229            sync_counter,
230            buffered_value,
231            valid_maps,
232            mapping_params,
233            defaults,
234        }
235    }
236
237    /// Create a new PDO object with provided defaults
238    pub const fn new_with_defaults(
239        od: &'static [ODEntry<'static>],
240        nmt_state: &'static dyn NmtStateAccess,
241        defaults: &'static PdoDefaults,
242    ) -> Self {
243        let mut pdo = Pdo::new(od, nmt_state);
244        pdo.defaults = Some(defaults);
245        pdo
246    }
247
248    /// Set the valid bit
249    pub fn set_valid(&self, value: bool) {
250        self.valid.store(value);
251    }
252
253    /// Get the valid bit value
254    pub fn valid(&self) -> bool {
255        self.valid.load()
256    }
257
258    /// Set the transmission type for this PDO
259    pub fn set_transmission_type(&self, value: u8) {
260        self.transmission_type.store(value);
261    }
262
263    /// Get the transmission type for this PDO
264    pub fn transmission_type(&self) -> u8 {
265        self.transmission_type.load()
266    }
267
268    /// Get the COB ID used for transmission of this PDO
269    pub fn cob_id(&self) -> CanId {
270        self.cob_id.load().unwrap_or(self.default_cob_id())
271    }
272
273    /// Get the default COB ID for transmission of this PDO
274    pub fn default_cob_id(&self) -> CanId {
275        if self.defaults.is_none() {
276            return CanId::std(0);
277        }
278        let defaults = self.defaults.unwrap();
279        let node_id = match self.node_id.load() {
280            NodeId::Unconfigured => 0,
281            NodeId::Configured(node_id) => node_id.raw(),
282        };
283        defaults.can_id(node_id)
284    }
285
286    /// This function should be called when a SYNC event occurs
287    ///
288    /// It will return true if the PDO should be sent in response to the SYNC event
289    pub fn sync_update(&self) -> bool {
290        if !self.valid.load() {
291            return false;
292        }
293
294        let transmission_type = self.transmission_type.load();
295        if transmission_type == 0 {
296            // TODO: Figure out how to determine application "event" which triggers the PDO
297            // For now, send every sync
298            true
299        } else if transmission_type <= 240 {
300            // Atomically update this PDO's sync counter. If it has
301            // reached the transmit threshold ("transmission_type"),
302            // then reset it to zero.
303            let r = self.sync_counter.fetch_update(|old| {
304                let new = old + 1;
305                if new >= transmission_type {
306                    Some(0)
307                } else {
308                    Some(new)
309                }
310            });
311
312            // We don't care if the update worked or not (because there's
313            // nothing we can do about it). Just use whatever the old
314            // value was.
315            let cnt = match r {
316                Ok(old) => old,
317                Err(old) => old,
318            } + 1;
319
320            cnt == transmission_type
321        } else {
322            false
323        }
324    }
325
326    /// Check mapped objects for TPDO event flag
327    pub fn read_events(&self) -> bool {
328        if !self.valid.load() {
329            return false;
330        }
331
332        for i in 0..self.mapping_params.len() {
333            let param = self.mapping_params[i].load();
334            if param.is_none() {
335                break;
336            }
337            let param = param.unwrap();
338            if param.object.data.read_event_flag(param.sub) {
339                return true;
340            }
341        }
342        false
343    }
344
345    fn nmt_state(&self) -> NmtState {
346        self.nmt_state.nmt_state()
347    }
348
349    pub(crate) fn clear_events(&self) {
350        for i in 0..self.mapping_params.len() {
351            let param = self.mapping_params[i].load();
352            if param.is_none() {
353                break;
354            }
355            let param = param.unwrap();
356            param.object.data.clear_events();
357        }
358    }
359
360    pub(crate) fn store_pdo_data(&self, data: &[u8]) {
361        let mut offset = 0;
362        let valid_maps = self.valid_maps.load() as usize;
363        for (i, param) in self.mapping_params.iter().enumerate() {
364            if i >= valid_maps {
365                break;
366            }
367            let param = param.load();
368            if param.is_none() {
369                break;
370            }
371            let param = param.unwrap();
372            let length = param.length as usize;
373            if offset + length > data.len() {
374                break;
375            }
376            let data_to_write = &data[offset..offset + length];
377            // validity of the mappings must be validated during write, so that error here is not
378            // possible
379            param.object.data.write(param.sub, data_to_write).ok();
380            offset += length;
381        }
382    }
383
384    pub(crate) fn send_pdo(&self) {
385        let mut data = [0u8; 8];
386        let mut offset = 0;
387        let valid_maps = self.valid_maps.load() as usize;
388        for (i, param) in self.mapping_params.iter().enumerate() {
389            if i >= valid_maps {
390                break;
391            }
392            let param = param.load();
393            // The first N params will be valid. Can assume if one is None, all remaining will be as
394            // well
395            if param.is_none() {
396                break;
397            }
398            let param = param.unwrap();
399            let length = param.length as usize;
400            if offset + length > data.len() {
401                break;
402            }
403            // validity of the mappings must be validated during write, so that error here is not
404            // possible
405            param
406                .object
407                .data
408                .read(param.sub, 0, &mut data[offset..offset + length])
409                .ok();
410            offset += length;
411        }
412        // If there is an old value here which has not been sent yet, replace it with the latest
413        // Data will be sent by mbox in message handling thread.
414        self.buffered_value.store(Some(data));
415    }
416
417    /// Lookup a PDO mapped object and create a MappingEntry if it is valid
418    ///
419    /// The returned MappingEntry can be stored in the Pdo mappings and includes
420    /// a reference to the mapped object for faster access when
421    /// sending/receiving PDOs.
422    ///
423    /// This function may fail if the mapped object doesn't exist, or if it is
424    /// too short.
425    fn try_create_mapping_entry(&self, mapping: PdoMapping) -> Result<MappingEntry<'a>, AbortCode> {
426        let PdoMapping {
427            index,
428            sub,
429            size: length,
430        } = mapping;
431        // length is in bits.
432        if (length % 8) != 0 {
433            // only support byte level access for now
434            return Err(AbortCode::IncompatibleParameter);
435        }
436        let entry = find_object_entry(self.od, index).ok_or(AbortCode::NoSuchObject)?;
437        let sub_info = entry.data.sub_info(sub)?;
438        if sub_info.size < length as usize / 8 {
439            return Err(AbortCode::IncompatibleParameter);
440        }
441        Ok(MappingEntry {
442            object: entry,
443            sub,
444            length: length / 8,
445        })
446    }
447
448    /// Initialize the PDO configuration with its default value
449    pub fn init_defaults(&'a self, node_id: NodeId) {
450        if self.defaults.is_none() {
451            return;
452        }
453        let defaults = self.defaults.unwrap();
454
455        self.node_id.store(node_id);
456        for (i, m) in defaults.mappings.iter().enumerate() {
457            if i >= self.mapping_params.len() {
458                return;
459            }
460            if let Ok(entry) = self.try_create_mapping_entry(PdoMapping::from_object_value(*m)) {
461                self.mapping_params[i].store(Some(entry));
462            }
463        }
464        self.valid_maps.store(defaults.mappings.len() as u8);
465
466        self.valid.store(defaults.valid());
467        // None means "use the default computed ID"
468        self.cob_id.store(None);
469        self.rtr_disabled.store(defaults.rtr_disabled());
470        self.transmission_type.store(defaults.transmission_type);
471    }
472}
473
474struct PdoCobSubObject<'a> {
475    pdo: &'a Pdo<'a>,
476}
477
478impl<'a> PdoCobSubObject<'a> {
479    pub const fn new(pdo: &'a Pdo<'a>) -> Self {
480        Self { pdo }
481    }
482
483    /// Should the COB sub object be persisted
484    ///
485    /// The object is only persisted when a non-default COB ID has been assigned.
486    pub fn should_persist(&self) -> bool {
487        self.pdo.cob_id.load().is_some()
488    }
489}
490
491impl SubObjectAccess for PdoCobSubObject<'_> {
492    fn read(&self, offset: usize, buf: &mut [u8]) -> Result<usize, AbortCode> {
493        let cob_id = self.pdo.cob_id();
494        let mut value = cob_id.raw();
495        if cob_id.is_extended() {
496            value |= 1 << 29;
497        }
498        if self.pdo.rtr_disabled.load() {
499            value |= 1 << 30;
500        }
501        if !self.pdo.valid.load() {
502            value |= 1 << 31;
503        }
504
505        let bytes = value.to_le_bytes();
506        if offset < bytes.len() {
507            let read_len = buf.len().min(bytes.len() - offset);
508            buf[0..read_len].copy_from_slice(&bytes[offset..offset + read_len]);
509            Ok(read_len)
510        } else {
511            Ok(0)
512        }
513    }
514
515    fn read_size(&self) -> usize {
516        4
517    }
518
519    fn write(&self, data: &[u8]) -> Result<(), AbortCode> {
520        // Changing PDO config is only allowed during PreOperational state, or Bootup when the
521        // defaults are loaded (Bootup is a always a short-lived state).
522        let nmt_state = self.pdo.nmt_state();
523        if nmt_state != NmtState::PreOperational && nmt_state != NmtState::Bootup {
524            return Err(AbortCode::GeneralError);
525        }
526        if data.len() < 4 {
527            Err(AbortCode::DataTypeMismatchLengthLow)
528        } else if data.len() > 4 {
529            Err(AbortCode::DataTypeMismatchLengthHigh)
530        } else {
531            let value = u32::from_le_bytes(data.try_into().unwrap());
532            let not_valid = (value & (1 << 31)) != 0;
533            let no_rtr = (value & (1 << 30)) != 0;
534            let extended_id = (value & (1 << 29)) != 0;
535
536            let can_id = if extended_id {
537                CanId::Extended(value & 0x1FFFFFFF)
538            } else {
539                CanId::Std((value & 0x7FF) as u16)
540            };
541            self.pdo.cob_id.store(Some(can_id));
542            self.pdo.valid.store(!not_valid);
543            self.pdo.rtr_disabled.store(no_rtr);
544            Ok(())
545        }
546    }
547}
548
549struct PdoTransmissionTypeSubObject<'a> {
550    pdo: &'a Pdo<'a>,
551}
552
553impl<'a> PdoTransmissionTypeSubObject<'a> {
554    pub const fn new(pdo: &'a Pdo<'a>) -> Self {
555        Self { pdo }
556    }
557}
558
559impl SubObjectAccess for PdoTransmissionTypeSubObject<'_> {
560    fn read(&self, offset: usize, buf: &mut [u8]) -> Result<usize, AbortCode> {
561        if offset > 1 {
562            return Ok(0);
563        }
564        buf[0] = self.pdo.transmission_type();
565        Ok(1)
566    }
567
568    fn read_size(&self) -> usize {
569        1
570    }
571
572    fn write(&self, data: &[u8]) -> Result<(), AbortCode> {
573        // Changing PDO config is only allowed during PreOperational state, or Bootup when the
574        // defaults are loaded (Bootup is a always a short-lived state).
575        let nmt_state = self.pdo.nmt_state();
576        if nmt_state != NmtState::PreOperational && nmt_state != NmtState::Bootup {
577            return Err(AbortCode::GeneralError);
578        }
579        if data.is_empty() {
580            Err(AbortCode::DataTypeMismatchLengthLow)
581        } else {
582            self.pdo.set_transmission_type(data[0]);
583            Ok(())
584        }
585    }
586}
587
588/// Implements a PDO communications config object for both RPDOs and TPDOs
589#[allow(missing_debug_implementations)]
590pub struct PdoCommObject<'a> {
591    cob: PdoCobSubObject<'a>,
592    transmission_type: PdoTransmissionTypeSubObject<'a>,
593}
594
595impl<'a> PdoCommObject<'a> {
596    /// Create a new PdoCommObject
597    pub const fn new(pdo: &'a Pdo<'a>) -> Self {
598        let cob = PdoCobSubObject::new(pdo);
599        let transmission_type = PdoTransmissionTypeSubObject::new(pdo);
600        Self {
601            cob,
602            transmission_type,
603        }
604    }
605}
606
607impl ProvidesSubObjects for PdoCommObject<'_> {
608    fn get_sub_object(&self, sub: u8) -> Option<(SubInfo, &dyn SubObjectAccess)> {
609        match sub {
610            0 => Some((
611                SubInfo::MAX_SUB_NUMBER,
612                const { &ConstField::new(2u8.to_le_bytes()) },
613            )),
614            1 => Some((
615                SubInfo::new_u32()
616                    .rw_access()
617                    .persist(self.cob.should_persist()),
618                &self.cob,
619            )),
620            2 => Some((
621                SubInfo::new_u8().rw_access().persist(true),
622                &self.transmission_type,
623            )),
624            _ => None,
625        }
626    }
627
628    fn object_code(&self) -> ObjectCode {
629        ObjectCode::Record
630    }
631}
632
633/// Implements a PDO mapping config object for both TPDOs and RPDOs
634#[allow(missing_debug_implementations)]
635pub struct PdoMappingObject<'a> {
636    pdo: &'a Pdo<'a>,
637}
638
639impl<'a> PdoMappingObject<'a> {
640    /// Create a new PdoMappingObject
641    pub const fn new(pdo: &'a Pdo<'a>) -> Self {
642        Self { pdo }
643    }
644}
645
646impl ObjectAccess for PdoMappingObject<'_> {
647    fn read(&self, sub: u8, offset: usize, buf: &mut [u8]) -> Result<usize, AbortCode> {
648        if sub == 0 {
649            if offset < 1 && !buf.is_empty() {
650                buf[0] = self.pdo.valid_maps.load();
651                Ok(1)
652            } else {
653                Ok(0)
654            }
655        } else if sub <= self.pdo.mapping_params.len() as u8 {
656            let value = if let Some(param) = self.pdo.mapping_params[(sub - 1) as usize].load() {
657                ((param.object.index as u32) << 16)
658                    + ((param.sub as u32) << 8)
659                    + param.length as u32 * 8
660            } else {
661                0u32
662            };
663            let bytes = value.to_le_bytes();
664            let read_len = buf.len().min(bytes.len() - offset);
665            buf[..read_len].copy_from_slice(&bytes[offset..offset + read_len]);
666            Ok(read_len)
667        } else {
668            Err(AbortCode::NoSuchSubIndex)
669        }
670    }
671
672    fn read_size(&self, sub: u8) -> Result<usize, AbortCode> {
673        if sub == 0 {
674            Ok(1)
675        } else if sub <= N_MAPPING_PARAMS as u8 {
676            Ok(4)
677        } else {
678            Err(AbortCode::NoSuchSubIndex)
679        }
680    }
681
682    fn write(&self, sub: u8, data: &[u8]) -> Result<(), AbortCode> {
683        // Changing PDO config is only allowed during PreOperational state, or Bootup when the
684        // defaults are loaded (Bootup is a always a short-lived state).
685        let nmt_state = self.pdo.nmt_state();
686        if nmt_state != NmtState::PreOperational && nmt_state != NmtState::Bootup {
687            return Err(AbortCode::GeneralError);
688        }
689        if sub == 0 {
690            self.pdo.valid_maps.store(data[0]);
691            Ok(())
692        } else if sub <= self.pdo.mapping_params.len() as u8 {
693            if data.len() != 4 {
694                return Err(AbortCode::DataTypeMismatch);
695            }
696            let value = u32::from_le_bytes(data.try_into().unwrap());
697
698            let mapping = PdoMapping::from_object_value(value);
699
700            self.pdo.mapping_params[(sub - 1) as usize]
701                .store(Some(self.pdo.try_create_mapping_entry(mapping)?));
702            Ok(())
703        } else {
704            Err(AbortCode::NoSuchSubIndex)
705        }
706    }
707
708    fn object_code(&self) -> ObjectCode {
709        ObjectCode::Record
710    }
711
712    fn sub_info(&self, sub: u8) -> Result<SubInfo, AbortCode> {
713        if sub == 0 {
714            Ok(SubInfo {
715                size: 1,
716                data_type: DataType::UInt8,
717                access_type: AccessType::Rw,
718                pdo_mapping: PdoMappable::None,
719                persist: true,
720            })
721        } else if sub <= self.pdo.mapping_params.len() as u8 {
722            Ok(SubInfo {
723                size: 4,
724                data_type: DataType::UInt32,
725                access_type: AccessType::Rw,
726                pdo_mapping: PdoMappable::None,
727                persist: true,
728            })
729        } else {
730            Err(AbortCode::NoSuchSubIndex)
731        }
732    }
733}
734
735#[cfg(test)]
736mod tests {
737    use super::*;
738    use crate::object_dict::ScalarField;
739
740    #[derive(Default)]
741    struct TestObject {
742        value: ScalarField<u32>,
743    }
744
745    impl ProvidesSubObjects for TestObject {
746        fn get_sub_object(&self, sub: u8) -> Option<(SubInfo, &dyn SubObjectAccess)> {
747            match sub {
748                0 => Some((SubInfo::new_u32(), &self.value)),
749                _ => None,
750            }
751        }
752
753        fn object_code(&self) -> ObjectCode {
754            ObjectCode::Var
755        }
756    }
757
758    #[test]
759    /// Assert that attempts to update PDO comms or mapping parameters fail when in operational mode
760    pub fn test_changes_denied_while_operational() {
761        let object1000 = TestObject::default();
762        let od = &[ODEntry {
763            index: 0x1000,
764            data: &object1000,
765        }];
766        let nmt_state = AtomicCell::new(NmtState::PreOperational);
767
768        let pdo = Pdo::new(od, &nmt_state);
769
770        let comm_obj = PdoCommObject::new(&pdo);
771        let mapping_obj = PdoMappingObject::new(&pdo);
772
773        // Setup initially
774        mapping_obj
775            .write(1, &((0x1000 << 16) | 32 as u32).to_le_bytes())
776            .unwrap();
777        mapping_obj.write(0, &[1]).unwrap();
778        comm_obj.write(1, &(1u32 << 31).to_le_bytes()).unwrap();
779
780        nmt_state.store(NmtState::Operational);
781
782        // Changing now should error
783        let result = mapping_obj.write(1, &0u32.to_le_bytes());
784        assert_eq!(Err(AbortCode::GeneralError), result);
785        let result = comm_obj.write(1, &0u32.to_le_bytes());
786        assert_eq!(Err(AbortCode::GeneralError), result);
787        let result = comm_obj.write(2, &0u32.to_le_bytes());
788        assert_eq!(Err(AbortCode::GeneralError), result);
789    }
790}