zencan_node/object_dict/
mod.rs

1//! Object Dictionary
2//!
3//! # Objects Overview
4//!
5//! The object dictionary is the main mechanism of configuration and communication for a node. For
6//! example, SDO access is performed on sub objects, which are identified by the 16-bit object ID of
7//! their parent object, and an 8-bit sub index. Objects come in three varieties:
8//!
9//! - VAR: A single variable of any type (accessed at sub index 0)
10//! - ARRAY: An array of sub-objects, all with the same type. Sub-index 0 is a u8 containing the
11//!   size of the array. Sub indices 1-N contain the array values.
12//! - RECORD: A collection of sub-objects of heterogenous types. Sub-index 0 contains the highest
13//!   implemented sub index.
14//!
15//! The set of data types which are be stored are defined by the [`DataType`](crate::common::objects::DataType) enum.
16//!
17//! The object dictionary is generated at build time using the `zencan-build` crate, based on the
18//! device config TOML file. A goal of zencan is to minimize the amount of generated code, so the
19//! generated code primarily instantiates the types defined here.
20//!
21//! # Object Storage
22//!
23//! Most objects implement their own storage, and are statically allocated. However, it is possible
24//! to register object handlers at run-time, which may store data in any way they wish, and perform
25//! whatever logic is required upon access to the object. The objects must be declared as
26//! `application_callback` objects at build time, so that a [`CallbackObject`] is inserted into the
27//! object dictionary as a placeholder to store the run-time provided object.
28//!
29//! # The ObjectAccess trait
30//!
31//! Any struct which implements the [`ObjectAccess`] trait can be used to represent an object in the
32//! dictionary. For simple data objects, the object can be defined in TOML and a type implementing
33//! this trait will be created for it during code generation. Additionally, accessor methods will be
34//! defined for accessing the sub objects directly.
35//!
36//! For more complex logic, custom objects can be implemented by implementing the [`ObjectAccess`]
37//! trait. A more ergonomic way to implement this trait is to implement the [`ProvidesSubObjects`]
38//! trait, and implement the sub objects individually by implementing the [`SubObjectAccess`] trait.
39//! Any object which implements [`ProvidesSubObjects`] will also get an [`ObjectAccess`]
40//! implementation.
41//!
42//! ## SubObject implementations
43//!
44//! Most sub objects can be implemented using one of the following existing types:
45//!
46//! - [`ScalarField<T>`]
47//! - [`ByteField``]
48//! - [`NullTermByteField`]
49//! - [`ConstField`]
50//! - [`ConstByteRefField`]
51//!
52//! ## Example Custom Object Implementation
53//!
54//! ```rust
55//! use zencan_node::object_dict::{ConstField, ScalarField, ProvidesSubObjects, SubObjectAccess};
56//! use zencan_node::common::objects::{ObjectCode, SubInfo};
57//! use zencan_node::common::sdo::AbortCode;
58//! // Example external API used to access a value for a sub field
59//! struct ExternalApi {}
60//!
61//! impl ExternalApi {
62//!     pub fn get_value(&self) -> f32 {
63//!         42.0
64//!     }
65//!     pub fn set_value(&self, value: f32) {
66//!         // TODO: Do something with the value
67//!     }
68//! }
69//!
70//! struct ExternalSubObject {
71//!     external_api: &'static ExternalApi,
72//! }
73//!
74//! impl ExternalSubObject {
75//!     pub fn new(external_api: &'static ExternalApi) -> Self {
76//!         Self { external_api }
77//!     }
78//! }
79//!
80//! impl SubObjectAccess for ExternalSubObject {
81//!     fn read(&self, offset: usize, buf: &mut [u8]) -> Result<usize, AbortCode> {
82//!         let value_bytes = self.external_api.get_value().to_le_bytes();
83//!         if offset < value_bytes.len() {
84//!             let read_len = buf.len().min(value_bytes.len() - offset);
85//!             buf[..read_len].copy_from_slice(&value_bytes[offset..offset + read_len]);
86//!             Ok(read_len)
87//!         } else {
88//!             Ok(0)
89//!         }
90//!     }
91//!
92//!     fn read_size(&self) -> usize {
93//!         4
94//!     }
95//!
96//!     fn write(&self, data: &[u8]) -> Result<(), AbortCode> {
97//!         if data.len() == 4 {
98//!             let value = f32::from_le_bytes(data.try_into().unwrap());
99//!             self.external_api.set_value(value);
100//!             Ok(())
101//!         } else if data.len() < 4 {
102//!             Err(AbortCode::DataTypeMismatchLengthLow)
103//!         } else if data.len() > 4 {
104//!             Err(AbortCode::DataTypeMismatchLengthHigh)
105//!         } else {
106//!             let value = f32::from_le_bytes(data.try_into().unwrap());
107//!             self.external_api.set_value(value);
108//!             Ok(())
109//!         }
110//!     }
111//! }
112//!
113//! struct CustomObject {
114//!     stored_field: ScalarField<u32>,
115//!     external_field: ExternalSubObject,
116//! }
117//!
118//! impl CustomObject {
119//!     pub fn new(external_api: &'static ExternalApi) -> Self {
120//!         Self {
121//!             external_field: ExternalSubObject::new(external_api),
122//!             stored_field: ScalarField::<u32>::new(0),
123//!         }
124//!     }
125//! }
126//!
127//! impl ProvidesSubObjects for CustomObject {
128//!     fn get_sub_object(&self, sub: u8) -> Option<(SubInfo, &dyn SubObjectAccess)> {
129//!         match sub {
130//!             // Sub 0 returns the highest sub index on the object
131//!             0 => Some((
132//!                 SubInfo::MAX_SUB_NUMBER,
133//!                 const { &ConstField::new(3u8.to_le_bytes()) },
134//!             )),
135//!             // Sub 1 returns the u32 field stored in the object, implemented using ScalarField<u32>
136//!             1 => Some((SubInfo::new_u32().rw_access().persist(true), &self.stored_field)),
137//!             // Sub 2 returns a custom sub object which accesses the external API
138//!             2 => Some((SubInfo::new_f32().rw_access().persist(false), &self.external_field)),
139//!             _ => None,
140//!         }
141//!     }
142//!
143//!     fn object_code(&self) -> ObjectCode {
144//!         ObjectCode::Record
145//!     }
146//! }
147//! ```
148//!
149//! # Object threading support
150//!
151//! All object must be `Sync` and `Send`, to allow for access from any thread. This is implemented
152//! using the `critical_section` crate. All objects support [`ObjectAccess::read`] and
153//! [`ObjectAccess::write`], which allow for atomic access of objects. For small objects, the SDO
154//! server will access objects using a single read or write call, buffering the data for segmented
155//! or block transfers, ensuring atomic access. However, if the size of the transfer is larger than
156//! the SDO buffer (currently fixed at 889 bytes, but likely to become adjustable in the future)
157//! then the SDO server is unable to buffer all of the data. For reading data, this will result in
158//! multiple calls to `read` with no guarantees that the data will not change in between, so it is
159//! possible that a client can get a "torn read". For writing data to an object, the partial write
160//! API is used, and has similar concerns.
161//!
162//! # Object flags for TPDO event triggering
163//!
164//! Some objects support event flags, which can be set via [`ObjectAccess::set_event_flag`]. These
165//! are used to trigger TPDO transmission.
166//!
167
168mod object_flags;
169mod objects;
170mod sub_objects;
171
172// Pull up public sub module definitions. The submodules provide some code organization, but
173// shouldn't clutter the public API
174pub use object_flags::*;
175pub use objects::*;
176pub use sub_objects::*;