1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
//! A library to implement a CANOpen node in Rust
//!
//! Zencan-node is a library to implement CAN communications for an embedded node, using the CANOpen
//! protocol. It is primarily intended to be run on microcontrollers, and so it is no_std compatible
//! and performs no heap allocation, instead statically allocating storage. It is also possible to
//! use it on std environments, for example on linux using socketcan. It provides the following
//! features:
//!
//! * Implements the *LSS* protocol for node discovery and configuration.
//! * Implements the *NMT* protocol for reporting and controlling the operating state of nodes.
//! * Generates an *object dictionary* to represent all of the data which can be communicated on the
//! bus. This includes a number of standard communication objects, as well as application specific
//! objects specified by the user.
//! * Implements an *SDO* server, allowing a remote client to access objects in the dictionary.
//! * Implements transmit and receive PDOs, allowing the mapping of objects to user-specified CAN
//! IDs for reading and writing those objects.
//! * Provides callback hooks to allow for persistent storage of selected object values on command.
//!
//! # Getting Started
//!
//! ## Device Configuration
//!
//! A zencan node is configured using a [DeviceConfig](common::device_config::DeviceConfig) TOML
//! file, see [common::device_config] module docs for more info.
//!
//! ## Code Generation
//!
//! The device configuration is used to generate types and static instances for each object in the
//! object dictionary, as well as some additional objects like a [NodeMbox], and [NodeState].
//!
//! ### Add zencan-build as build dependency
//!
//! This crate contains functions to generate the object dictionary code from the device config TOML
//! file.
//!
//! ```toml
//! [build-dependencies]
//! zencan-build = "0.0.1"
//! ```
//!
//! ### Add the code generation to your `build.rs` file
//!
//! ```ignore
//! fn main() {
//! if let Err(e) = zencan_build::build_node_from_device_config("ZENCAN_CONFIG", "zencan_config.toml") {
//! eprintln!("Failed to parse zencan_config.toml: {}", e.to_string());
//! std::process::exit(-1);
//! }
//! }
//! ```
//!
//! ### Include the generated code in your application
//!
//! When including the code, it is included using the name specified in build -- `ZENCAN_CONFIG` in
//! this case. This allows creating multiple object dictionaries in a single application.
//!
//! Typically, an application would add a snippet like this into `main.rs`:
//!
//! ```ignore
//! mod zencan {
//! zencan_node::include_modules!(ZENCAN_CONFIG);
//! }
//! ```
//!
//! ## Instantiating the [`Node`] object
//!
//! ### Object setup
//!
//! One of the first things you should do before instantiating a node is set the serial number on
//! the 0x1018 object. Devices are identified by an "identity" that includes the vendor, product,
//! revision, and a serial number. If you are going to put more than one of a particular device type
//! on a network, each should somehow come up with a unique serial. Most MCUs will have a unique ID
//! register which can be used for this purpose.
//!
//! ```ignore
//! // Use the UID register to set a unique serial number
//! zencan::OBJECT1018.set_serial(get_serial());
//! ```
//!
//! ### Node Creation
//!
//!
//! Instantiate the node by providing it with the node ID, a set of event callbacks, the object
//! dictionary, the mailbox, and the node state object.
//!
//! - Node ID: This is the ID boot up ID of the node. It can be stored in flash, it can be a
//! constant, it can be set by DIP switches, etc. It can also be left as `NodeId::Unconfigured`.
//! It is then possible to configure the node ID over the bus using the LSS protocol.
//! - Object dictionary: This is a table where all of the objects are stored. It is created as a
//! static variable by `zencan-build`, and is called `OD_TABLE`.
//! - Mailbox: This is a data structure for receiving incoming CAN messages. It buffers received
//! messages so that messages can be pass to it in an interrupt, and then processed in the next
//! call to `process`. It is defined by the generated code in a static variable named `NODE_MBOX`.
//! - Node state: This is a global state structure which provides some communications between the
//! Node and objects such as PDO configuration objects, or special purpose object like the Save
//! Command object. It is defined by the generated code in a static variable named `NODE_STATE`.
//!
//! There are a variety of callback functions you may provide as well, although they are not
//! required.
//!
//!
//! ```ignore
//! // Get references to the functions which save to flash
//! let store_node_config = &mut store_node_config;
//! let store_objects = &mut store_objects;
//!
//! let callbacks = Callbacks {
//! store_node_config: Some(store_node_config),
//! store_objects: Some(store_objects),
//! ..Default::default()
//! };
//!
//!
//! // Initialize node, providing references to the static objects created by `zencan-build`
//! let mut node = Node::new(
//! NodeId::Unconfigured,
//! callbacks,
//! &zencan::NODE_MBOX,
//! &zencan::NODE_STATE,
//! &zencan::OD_TABLE,
//! );
//! ```
//!
//! ## Handling CAN messages
//!
//! The application has to handle sending and receiving CAN messages.
//!
//! The NODE_MBOX struct acts as a mailbox for both incoming and outgoing mailboxes, and the
//! application must pass messages between the mailbox and the CAN controller. This can be done in
//! any thread -- a good way to do it is to have the CAN controller interrupt store messages here
//! directly.
//!
//! ```ignore
//! // Assuming we've received a message (id, and buffer) from somewhere, pass it to the mailbox
//! let msg = zencan_node::common::messages::CanMessage::new(id, &buffer[..msg.len as usize]);
//! // Ignore error -- as an Err is returned for messages that are not consumed by the node
//! // stack. You may handle those some other way, or simply drop them.
//! zencan::NODE_MBOX.store_message(msg).ok();
//! ```
//!
//! Outgoing messages can be read from the mbox using the [`NodeMbox::next_transmit_message`]
//! function. A callback can be registered (see [`NodeMbox::set_transmit_notify_callback`]) to be
//! notified when new messages are queued for transmission -- this can be used to e.g. push the
//! first message(s) to the CAN controller to initiate an IRQ driven transmit look, or to wake an
//! async task which is responsible for moving messages from the node to the CAN controller.
//!
//! ```ignore
//! #[embassy_executor::task]
//! async fn twai_tx_task(mut twai_tx: TwaiTx<'static, Async>) {
//! loop {
//! while let Some(msg) = zencan::NODE_MBOX.next_transmit_message() {
//! let frame =
//! EspTwaiFrame::new(StandardId::new(msg.id.raw() as u16).unwrap(), msg.data())
//! .unwrap();
//! if let Err(e) = twai_tx.transmit_async(&frame).await {
//! log::error!("Error sending CAN message: {e:?}");
//! }
//! }
//!
//! // Wait for wakeup signal when new CAN messages become ready for sending
//! CANOPEN_TX_SIGNAL.wait().await;
//! }
//! }
//! ```
//!
//! ## Calling periodic process method
//!
//! To execute the Node logic, the [`Node::process`] function must be called periodically. While it
//! is possible to call process only periodically, the NODE_MBOX object provides a
//! [callback](NodeMbox::set_process_notify_callback) which can be used to notify another task that
//! process should be called when a message is received and requires processing.
//!
//! Here's an example of a lilos task which executes process when either CAN_NOTIFY is signals, or
//! 10ms has passed since the last notification.
//!
//! ```ignore
//! async fn can_task(
//! mut node: Node,
//! ) -> Infallible {
//! let epoch = lilos::time::TickTime::now();
//! loop {
//! lilos::time::with_timeout(Duration::from_millis(10), CAN_NOTIFY.until_next()).await;
//! let time_us = epoch.elapsed().0 * 1000;
//! node.process(time_us);
//! }
//! }
//! ```
//!
// Re-export proc macros
pub use build_object_dict;
// Re-export types used by generated code
pub use critical_section;
pub use embedded_io;
pub use zencan_common as common;
pub use ;
pub use open_socketcan;
pub use ;
pub use NodeMbox;
pub use NodeState;
pub use ;
pub use SDO_BUFFER_SIZE;
/// Include the code generated for the object dict in the build script.