1use zencan_common::{
5 constants::object_ids,
6 lss::LssIdentity,
7 messages::{
8 CanId, CanMessage, Heartbeat, NmtCommandSpecifier, NmtState, ZencanMessage, LSS_RESP_ID,
9 },
10 NodeId,
11};
12
13use crate::{
14 lss_slave::{LssConfig, LssSlave},
15 node_mbox::NodeMbox,
16 object_dict::{find_object, ODEntry},
17 storage::StoreObjectsCallback,
18};
19use crate::{node_state::NodeStateAccess, sdo_server::SdoServer};
20
21use defmt_or_log::{debug, info};
22
23type StoreNodeConfigCallback = dyn Fn(&NodeId) + Sync;
24
25#[derive(Default)]
26struct Callbacks {
27 store_node_config: Option<&'static StoreNodeConfigCallback>,
28}
29
30fn read_identity(od: &[ODEntry]) -> Option<LssIdentity> {
31 let obj = find_object(od, object_ids::IDENTITY)?;
32 let vendor_id = obj.read_u32(1).ok()?;
33 let product_code = obj.read_u32(2).ok()?;
34 let revision = obj.read_u32(3).ok()?;
35 let serial = obj.read_u32(4).ok()?;
36 Some(LssIdentity {
37 vendor_id,
38 product_code,
39 revision,
40 serial,
41 })
42}
43
44fn read_heartbeat_period(od: &[ODEntry]) -> Option<u16> {
45 let obj = find_object(od, object_ids::HEARTBEAT_PRODUCER_TIME)?;
46 obj.read_u16(0).ok()
47}
48
49fn read_autostart(od: &[ODEntry]) -> Option<bool> {
50 let obj = find_object(od, object_ids::AUTO_START)?;
51 Some(obj.read_u8(0).unwrap() != 0)
52}
53
54#[allow(missing_debug_implementations)]
65pub struct Node {
66 node_id: NodeId,
67 nmt_state: NmtState,
68 sdo_server: SdoServer,
69 lss_slave: LssSlave,
70 message_count: u32,
71 od: &'static [ODEntry<'static>],
72 mbox: &'static NodeMbox,
73 state: &'static dyn NodeStateAccess,
74 reassigned_node_id: Option<NodeId>,
75 callbacks: Callbacks,
76 next_heartbeat_time_us: u64,
77 heartbeat_period_ms: u16,
78 heartbeat_toggle: bool,
79 auto_start: bool,
80 last_process_time_us: u64,
81}
82
83impl Node {
84 pub fn new(
93 node_id: NodeId,
94 mbox: &'static NodeMbox,
95 state: &'static dyn NodeStateAccess,
96 od: &'static [ODEntry<'static>],
97 ) -> Self {
98 let message_count = 0;
99 let sdo_server = SdoServer::new();
100 let lss_slave = LssSlave::new(LssConfig {
101 identity: read_identity(od).unwrap(),
102 node_id,
103 store_supported: false,
104 });
105 let nmt_state = NmtState::Bootup;
106 let reassigned_node_id = None;
107
108 let heartbeat_period_ms = read_heartbeat_period(od).expect("Heartbeat object must exist");
109 let next_heartbeat_time_us = 0;
110 let heartbeat_toggle = false;
111 let auto_start = read_autostart(od).expect("auto start object must exist");
112 let last_process_time_us = 0;
113 Self {
114 node_id,
115 nmt_state,
116 sdo_server,
117 lss_slave,
118 message_count,
119 od,
120 mbox,
121 state,
122 reassigned_node_id,
123 next_heartbeat_time_us,
124 heartbeat_period_ms,
125 heartbeat_toggle,
126 auto_start,
127 callbacks: Callbacks::default(),
128 last_process_time_us,
129 }
130 }
131
132 pub fn set_node_id(&mut self, node_id: NodeId) {
136 self.reassigned_node_id = Some(node_id);
137 }
138
139 pub fn register_store_node_config(&mut self, cb: &'static StoreNodeConfigCallback) {
141 self.callbacks.store_node_config = Some(cb);
142 }
143
144 pub fn register_store_objects(&mut self, cb: &'static StoreObjectsCallback) {
146 self.state.storage_context().store_callback.store(Some(cb));
147 }
148
149 pub fn process(&mut self, now_us: u64, send_cb: &mut dyn FnMut(CanMessage)) -> bool {
168 let elapsed = (now_us - self.last_process_time_us) as u32;
169 self.last_process_time_us = now_us;
170
171 let mut update_flag = false;
172 if let Some(new_node_id) = self.reassigned_node_id.take() {
173 self.node_id = new_node_id;
174 self.nmt_state = NmtState::Bootup;
175 }
176
177 if self.nmt_state == NmtState::Bootup {
178 self.nmt_state = NmtState::PreOperational;
180 self.boot_up(send_cb);
181 }
182
183 if self.auto_start && self.node_id.is_configured() {
186 self.auto_start = false;
187 self.nmt_state = NmtState::Operational;
188 }
189
190 let (resp, updated_index) =
192 self.sdo_server
193 .process(self.mbox.sdo_receiver(), elapsed, self.od);
194 if let Some(resp) = resp {
195 send_cb(resp.to_can_message(self.sdo_tx_cob_id()));
196 }
197 if updated_index.is_some() {
198 update_flag = true;
199 }
200
201 if let Some(msg) = self.mbox.read_nmt_mbox() {
203 if let Ok(ZencanMessage::NmtCommand(cmd)) = msg.try_into() {
204 self.message_count += 1;
205 if let NodeId::Configured(node_id) = self.node_id {
208 if cmd.node == 0 || cmd.node == node_id.raw() {
209 debug!("Received NMT command: {:?}", cmd.cs);
210 self.handle_nmt_command(cmd.cs);
211 }
212 }
213 }
214 }
215
216 if let Ok(Some(resp)) = self.lss_slave.process(self.mbox.lss_receiver()) {
217 send_cb(resp.to_can_message(LSS_RESP_ID));
218
219 if let Some(event) = self.lss_slave.pending_event() {
220 info!("LSS Slave Event: {:?}", event);
221 match event {
222 crate::lss_slave::LssEvent::StoreConfiguration => {
223 if let Some(cb) = self.callbacks.store_node_config {
224 (cb)(&self.node_id)
225 }
226 }
227 crate::lss_slave::LssEvent::ActivateBitTiming {
228 table: _,
229 index: _,
230 delay: _,
231 } => (),
232 crate::lss_slave::LssEvent::ConfigureNodeId { node_id } => {
233 self.set_node_id(node_id)
234 }
235 }
236 }
237 }
238
239 if self.heartbeat_period_ms != 0 && now_us >= self.next_heartbeat_time_us {
240 self.send_heartbeat(send_cb);
241 if self.next_heartbeat_time_us < now_us {
244 self.next_heartbeat_time_us = now_us;
245 }
246 }
247
248 if self.nmt_state == NmtState::Operational {
249 let sync = self.mbox.read_sync_flag();
251
252 let global_trigger = self.state.get_pdo_sync().toggle();
257
258 for pdo in self.state.get_tpdos() {
259 if !(pdo.valid()) {
260 continue;
261 }
262 let transmission_type = pdo.transmission_type();
263 if transmission_type >= 254 {
264 if global_trigger && pdo.read_events() {
265 let mut data = [0u8; 8];
266 pdo.read_pdo_data(&mut data);
267 let msg = CanMessage::new(pdo.cob_id(), &data);
268 send_cb(msg);
269 }
270 } else if sync && pdo.sync_update() {
271 let mut data = [0u8; 8];
272 pdo.read_pdo_data(&mut data);
273 let msg = CanMessage::new(pdo.cob_id(), &data);
274 send_cb(msg);
275 }
276 }
277
278 for pdo in self.state.get_tpdos() {
279 pdo.clear_events();
280 }
281
282 for rpdo in self.state.get_rpdos() {
283 if !rpdo.valid() {
284 continue;
285 }
286 if let Some(new_data) = rpdo.buffered_value.take() {
287 rpdo.store_pdo_data(&new_data);
288 update_flag = true;
289 }
290 }
291 }
292
293 update_flag
294 }
295
296 fn handle_nmt_command(&mut self, cmd: NmtCommandSpecifier) {
297 let prev_state = self.nmt_state;
298
299 match cmd {
300 NmtCommandSpecifier::Start => self.nmt_state = NmtState::Operational,
301 NmtCommandSpecifier::Stop => self.nmt_state = NmtState::Stopped,
302 NmtCommandSpecifier::EnterPreOp => self.nmt_state = NmtState::PreOperational,
303 NmtCommandSpecifier::ResetApp => {
304 self.nmt_state = NmtState::Bootup;
308 }
309 NmtCommandSpecifier::ResetComm => self.nmt_state = NmtState::Bootup,
310 }
311
312 debug!(
313 "NMT state changed from {:?} to {:?}",
314 prev_state, self.nmt_state
315 );
316 }
317
318 pub fn node_id(&self) -> u8 {
320 self.node_id.into()
321 }
322
323 pub fn nmt_state(&self) -> NmtState {
325 self.nmt_state
326 }
327
328 pub fn rx_message_count(&self) -> u32 {
330 self.message_count
331 }
332
333 fn sdo_tx_cob_id(&self) -> CanId {
334 let node_id: u8 = self.node_id.into();
335 CanId::Std(0x580 + node_id as u16)
336 }
337
338 fn sdo_rx_cob_id(&self) -> CanId {
339 let node_id: u8 = self.node_id.into();
340 CanId::Std(0x600 + node_id as u16)
341 }
342
343 fn boot_up(&mut self, sender: &mut dyn FnMut(CanMessage)) {
344 self.lss_slave.update_config(LssConfig {
346 identity: read_identity(self.od).unwrap(),
347 node_id: self.node_id,
348 store_supported: self.callbacks.store_node_config.is_some(),
349 });
350
351 if let NodeId::Configured(node_id) = self.node_id {
352 info!("Booting node with ID {}", node_id.raw());
353 self.mbox.set_sdo_cob_id(Some(self.sdo_rx_cob_id()));
354 self.send_heartbeat(sender);
355 }
356 }
357
358 fn send_heartbeat(&mut self, sender: &mut dyn FnMut(CanMessage)) {
359 if let NodeId::Configured(node_id) = self.node_id {
360 let heartbeat = Heartbeat {
361 node: node_id.raw(),
362 toggle: self.heartbeat_toggle,
363 state: self.nmt_state,
364 };
365 self.heartbeat_toggle = !self.heartbeat_toggle;
366 sender(heartbeat.into());
367 self.next_heartbeat_time_us += (self.heartbeat_period_ms as u64) * 1000;
368 }
369 }
370}