1use core::{convert::Infallible, sync::atomic::Ordering};
5
6use zencan_common::{
7 constants::object_ids,
8 lss::LssIdentity,
9 messages::{
10 CanId, CanMessage, Heartbeat, NmtCommandSpecifier, SyncObject, ZencanMessage, LSS_RESP_ID,
11 },
12 nmt::NmtState,
13 NodeId,
14};
15
16use crate::sdo_server::SdoServer;
17use crate::{
18 lss_slave::{LssConfig, LssSlave},
19 node_mbox::NodeMbox,
20 node_state::NmtStateAccess as _,
21 object_dict::{find_object, ODEntry},
22 NodeState,
23};
24
25use defmt_or_log::{debug, info};
26
27pub type StoreNodeConfigFn<'a> = dyn FnMut(NodeId) + 'a;
28pub type StoreObjectsFn<'a> = dyn Fn(&mut dyn embedded_io::Read<Error = Infallible>, usize) + 'a;
29pub type StateChangeFn<'a> = dyn FnMut(&'a [ODEntry<'a>]) + 'a;
30pub type SyncReceiveFn<'a> = dyn FnMut(SyncObject) + 'a;
31
32#[allow(missing_debug_implementations)]
36#[derive(Default)]
37pub struct Callbacks<'a> {
38 pub store_node_config: Option<&'a mut StoreNodeConfigFn<'a>>,
44
45 pub store_objects: Option<&'a mut StoreObjectsFn<'a>>,
50
51 pub reset_app: Option<&'a mut StateChangeFn<'a>>,
57
58 pub reset_comms: Option<&'a mut StateChangeFn<'a>>,
68
69 pub enter_operational: Option<&'a mut StateChangeFn<'a>>,
71
72 pub enter_stopped: Option<&'a mut StateChangeFn<'a>>,
74
75 pub enter_preoperational: Option<&'a mut StateChangeFn<'a>>,
77
78 pub sync_received: Option<&'a mut SyncReceiveFn<'a>>,
80}
81
82impl<'a> Callbacks<'a> {
83 pub const fn new() -> Self {
85 Self {
86 store_node_config: None,
87 store_objects: None,
88 reset_app: None,
89 reset_comms: None,
90 enter_operational: None,
91 enter_stopped: None,
92 enter_preoperational: None,
93 sync_received: None,
94 }
95 }
96}
97
98fn read_identity(od: &[ODEntry]) -> Option<LssIdentity> {
99 let obj = find_object(od, object_ids::IDENTITY)?;
100 let vendor_id = obj.read_u32(1).ok()?;
101 let product_code = obj.read_u32(2).ok()?;
102 let revision = obj.read_u32(3).ok()?;
103 let serial = obj.read_u32(4).ok()?;
104 Some(LssIdentity {
105 vendor_id,
106 product_code,
107 revision,
108 serial,
109 })
110}
111
112fn read_heartbeat_period(od: &[ODEntry]) -> Option<u16> {
113 let obj = find_object(od, object_ids::HEARTBEAT_PRODUCER_TIME)?;
114 obj.read_u16(0).ok()
115}
116
117fn read_autostart(od: &[ODEntry]) -> Option<bool> {
118 let obj = find_object(od, object_ids::AUTO_START)?;
119 Some(obj.read_u8(0).unwrap() != 0)
120}
121
122#[allow(missing_debug_implementations)]
133pub struct Node<'a> {
134 node_id: NodeId,
135 sdo_server: SdoServer<'a>,
136 lss_slave: LssSlave,
137 message_count: u32,
138 od: &'static [ODEntry<'static>],
139 mbox: &'static NodeMbox,
140 state: &'static NodeState<'static>,
141 reassigned_node_id: Option<NodeId>,
142 next_heartbeat_time_us: u64,
143 heartbeat_period_ms: u16,
144 auto_start: bool,
145 last_process_time_us: u64,
146 callbacks: Callbacks<'a>,
147 transmit_flag: bool,
148}
149
150impl<'a> Node<'a> {
151 pub fn new(
160 node_id: NodeId,
161 callbacks: Callbacks<'a>,
162 mbox: &'static NodeMbox,
163 state: &'static NodeState<'static>,
164 od: &'static [ODEntry<'static>],
165 ) -> Self {
166 let message_count = 0;
167 let sdo_server = SdoServer::new();
168 let lss_slave = LssSlave::new(LssConfig {
169 identity: read_identity(od).unwrap_or_default(),
170 node_id,
171 store_supported: false,
172 });
173 let reassigned_node_id = None;
174
175 if callbacks.store_objects.is_some() {
177 state
178 .storage_context()
179 .store_supported
180 .store(true, Ordering::Relaxed);
181 }
182
183 let heartbeat_period_ms = read_heartbeat_period(od).unwrap_or(0);
184 let next_heartbeat_time_us = 0;
185 let auto_start = read_autostart(od).unwrap_or(false);
186 let last_process_time_us = 0;
187 let transmit_flag = false;
188
189 let mut node = Self {
190 node_id,
191 callbacks,
192 sdo_server,
193 lss_slave,
194 message_count,
195 od,
196 mbox,
197 state,
198 reassigned_node_id,
199 next_heartbeat_time_us,
200 heartbeat_period_ms,
201 auto_start,
202 last_process_time_us,
203 transmit_flag,
204 };
205
206 node.reset_app();
207 node
208 }
209
210 pub fn set_node_id(&mut self, node_id: NodeId) {
214 self.reassigned_node_id = Some(node_id);
215 }
216
217 pub fn process(&mut self, now_us: u64) -> bool {
235 let elapsed = (now_us - self.last_process_time_us) as u32;
236 self.last_process_time_us = now_us;
237
238 self.transmit_flag = false;
239
240 let mut update_flag = false;
241 if let Some(new_node_id) = self.reassigned_node_id.take() {
242 self.node_id = new_node_id;
243 self.state.set_nmt_state(NmtState::Bootup);
244 }
245
246 if self.nmt_state() == NmtState::Bootup {
247 self.enter_preoperational();
249 self.boot_up();
250 }
251
252 if self.auto_start && self.node_id.is_configured() {
255 self.auto_start = false;
257 self.enter_operational();
258 }
259
260 let (message_sent, updated_index) =
262 self.sdo_server
263 .process(self.mbox.sdo_comms(), elapsed, self.od);
264
265 self.transmit_flag |= message_sent;
266 if updated_index.is_some() {
267 update_flag = true;
268 }
269
270 if self
272 .state
273 .storage_context()
274 .store_flag
275 .swap(false, Ordering::Relaxed)
276 {
277 if let Some(cb) = &mut self.callbacks.store_objects {
279 crate::persist::serialize(self.od, *cb);
280 }
281 }
282
283 if let Some(msg) = self.mbox.read_nmt_mbox() {
285 if let Ok(ZencanMessage::NmtCommand(cmd)) = msg.try_into() {
286 self.message_count += 1;
287 if let NodeId::Configured(node_id) = self.node_id {
290 if cmd.node == 0 || cmd.node == node_id.raw() {
291 debug!("Received NMT command: {:?}", cmd.cs);
292 self.handle_nmt_command(cmd.cs);
293 }
294 }
295 }
296 }
297
298 if let Ok(Some(resp)) = self.lss_slave.process(self.mbox.lss_receiver()) {
299 self.send_message(resp.to_can_message(LSS_RESP_ID));
300
301 if let Some(event) = self.lss_slave.pending_event() {
302 info!("LSS Slave Event: {:?}", event);
303 match event {
304 crate::lss_slave::LssEvent::StoreConfiguration => {
305 if let Some(cb) = &mut self.callbacks.store_node_config {
306 (cb)(self.node_id)
307 }
308 }
309 crate::lss_slave::LssEvent::ActivateBitTiming {
310 table: _,
311 index: _,
312 delay: _,
313 } => (),
314 crate::lss_slave::LssEvent::ConfigureNodeId { node_id } => {
315 self.set_node_id(node_id)
316 }
317 }
318 }
319 }
320
321 if self.heartbeat_period_ms != 0 && now_us >= self.next_heartbeat_time_us {
322 self.send_heartbeat();
323 if self.next_heartbeat_time_us < now_us {
326 self.next_heartbeat_time_us = now_us;
327 }
328 }
329
330 let sync = self.mbox.read_sync_flag();
332
333 if self.nmt_state() == NmtState::Operational {
334 let global_trigger = self.state.object_flag_sync().toggle();
341
342 for pdo in self.state.tpdos() {
343 if !(pdo.valid()) {
344 continue;
345 }
346 let transmission_type = pdo.transmission_type();
347 if transmission_type >= 254 {
348 if global_trigger && pdo.read_events() {
349 pdo.send_pdo();
350 self.transmit_flag = true;
351 }
352 } else if sync.is_some() && pdo.sync_update() {
353 pdo.send_pdo();
354 self.transmit_flag = true;
355 }
356 }
357
358 for pdo in self.state.tpdos() {
359 pdo.clear_events();
360 }
361
362 for rpdo in self.state.rpdos() {
363 if !rpdo.valid() {
364 continue;
365 }
366 if let Some(new_data) = rpdo.buffered_value.take() {
367 rpdo.store_pdo_data(&new_data);
368 update_flag = true;
369 }
370 }
371 }
372
373 if matches!(
377 self.nmt_state(),
378 NmtState::Operational | NmtState::PreOperational
379 ) {
380 if let Some(cb) = &mut self.callbacks.sync_received {
381 if let Some(obj) = sync {
382 (*cb)(obj);
383 }
384 }
385 }
386
387 if self.transmit_flag {
388 self.mbox.transmit_notify();
389 }
390
391 update_flag
392 }
393
394 fn handle_nmt_command(&mut self, cmd: NmtCommandSpecifier) {
395 let prev_state = self.nmt_state();
396
397 match cmd {
398 NmtCommandSpecifier::Start => self.enter_operational(),
399 NmtCommandSpecifier::Stop => self.enter_stopped(),
400 NmtCommandSpecifier::EnterPreOp => self.enter_preoperational(),
401 NmtCommandSpecifier::ResetApp => self.reset_app(),
402 NmtCommandSpecifier::ResetComm => self.reset_comm(),
403 }
404
405 debug!(
406 "NMT state changed from {:?} to {:?}",
407 prev_state,
408 self.nmt_state()
409 );
410 }
411
412 pub fn node_id(&self) -> u8 {
414 self.node_id.into()
415 }
416
417 pub fn nmt_state(&self) -> NmtState {
419 self.state.nmt_state()
420 }
421
422 pub fn rx_message_count(&self) -> u32 {
424 self.message_count
425 }
426
427 fn sdo_tx_cob_id(&self) -> CanId {
428 let node_id: u8 = self.node_id.into();
429 CanId::Std(0x580 + node_id as u16)
430 }
431
432 fn sdo_rx_cob_id(&self) -> CanId {
433 let node_id: u8 = self.node_id.into();
434 CanId::Std(0x600 + node_id as u16)
435 }
436
437 fn send_message(&mut self, msg: CanMessage) {
438 self.transmit_flag = true;
439 self.mbox.queue_transmit_message(msg).ok();
441 }
442
443 fn enter_operational(&mut self) {
444 self.state.set_nmt_state(NmtState::Operational);
445 if let Some(cb) = &mut self.callbacks.enter_operational {
446 (*cb)(self.od);
447 }
448 }
449
450 fn enter_stopped(&mut self) {
451 self.state.set_nmt_state(NmtState::Stopped);
452 if let Some(cb) = &mut self.callbacks.enter_stopped {
453 (*cb)(self.od);
454 }
455 }
456
457 fn enter_preoperational(&mut self) {
458 self.state.set_nmt_state(NmtState::PreOperational);
459 if let Some(cb) = &mut self.callbacks.enter_preoperational {
460 (*cb)(self.od);
461 }
462 }
463
464 fn reset_app(&mut self) {
465 for pdo in self.state.rpdos().iter().chain(self.state.tpdos()) {
467 pdo.init_defaults(self.node_id);
468 }
469
470 if let Some(reset_app_cb) = &mut self.callbacks.reset_app {
471 (*reset_app_cb)(self.od);
472 }
473 self.state.set_nmt_state(NmtState::Bootup);
474 }
475
476 fn reset_comm(&mut self) {
477 for pdo in self.state.rpdos().iter().chain(self.state.tpdos()) {
478 pdo.init_defaults(self.node_id);
479 }
480 if let Some(reset_comms_cb) = &mut self.callbacks.reset_comms {
481 (*reset_comms_cb)(self.od);
482 }
483 self.state.set_nmt_state(NmtState::Bootup);
484 }
485
486 fn boot_up(&mut self) {
487 self.lss_slave.update_config(LssConfig {
489 identity: read_identity(self.od).unwrap_or_default(),
490 node_id: self.node_id,
491 store_supported: self.callbacks.store_node_config.is_some(),
492 });
493
494 if let NodeId::Configured(node_id) = self.node_id {
495 info!("Booting node with ID {}", node_id.raw());
496 self.mbox.set_sdo_rx_cob_id(Some(self.sdo_rx_cob_id()));
497 self.mbox.set_sdo_tx_cob_id(Some(self.sdo_tx_cob_id()));
498 self.send_heartbeat();
499 }
500 }
501
502 fn send_heartbeat(&mut self) {
503 if let NodeId::Configured(node_id) = self.node_id {
504 let heartbeat = Heartbeat {
505 node: node_id.raw(),
506 toggle: false,
507 state: self.nmt_state(),
508 };
509 self.send_message(heartbeat.into());
510 self.next_heartbeat_time_us += (self.heartbeat_period_ms as u64) * 1000;
511 }
512 }
513}
514
515#[cfg(test)]
516mod tests {
517 use zencan_common::{
518 nmt::NmtState,
519 objects::{ObjectCode, SubInfo},
520 CanMessage, NodeId,
521 };
522
523 use crate::{
524 object_dict::{ODEntry, ProvidesSubObjects, ScalarField, SubObjectAccess},
525 priority_queue::PriorityQueue,
526 Callbacks, Node, NodeMbox, NodeState,
527 };
528
529 struct AutoStartObject {
530 value: ScalarField<u8>,
531 }
532
533 impl AutoStartObject {
534 pub fn new(value: u8) -> Self {
535 Self {
536 value: ScalarField::<u8>::new(value),
537 }
538 }
539 }
540 impl ProvidesSubObjects for AutoStartObject {
541 fn get_sub_object(&self, sub: u8) -> Option<(SubInfo, &dyn SubObjectAccess)> {
542 match sub {
543 0 => Some((SubInfo::new_u8(), &self.value)),
544 _ => None,
545 }
546 }
547
548 fn object_code(&self) -> ObjectCode {
549 ObjectCode::Var
550 }
551 }
552
553 #[test]
554 fn test_node_autostart_enabled() {
555 let object5000 = Box::leak(Box::new(AutoStartObject::new(1)));
556 let od_table = Box::leak(Box::new([ODEntry {
557 index: 0x5000,
558 data: object5000,
559 }]));
560
561 let tx_queue = Box::leak(Box::new(PriorityQueue::<4, CanMessage>::new()));
562 let sdo_buffer = Box::leak(Box::new([0u8; 100]));
563 let mbox = Box::leak(Box::new(NodeMbox::new(&[], &[], tx_queue, sdo_buffer)));
564 let state = Box::leak(Box::new(NodeState::new(&[], &[])));
565
566 let mut node = Node::new(
567 NodeId::new(1).unwrap(),
568 Callbacks::default(),
569 mbox,
570 state,
571 od_table,
572 );
573
574 node.process(0);
575 assert_eq!(NmtState::Operational, node.nmt_state());
576 }
577
578 #[test]
579 fn test_node_autostart_disabled() {
580 let object5000 = Box::leak(Box::new(AutoStartObject::new(0)));
581 let od_table = Box::leak(Box::new([ODEntry {
582 index: 0x5000,
583 data: object5000,
584 }]));
585
586 let tx_queue = Box::leak(Box::new(PriorityQueue::<4, CanMessage>::new()));
587 let sdo_buffer = Box::leak(Box::new([0u8; 100]));
588 let mbox = Box::leak(Box::new(NodeMbox::new(&[], &[], tx_queue, sdo_buffer)));
589 let state = Box::leak(Box::new(NodeState::new(&[], &[])));
590
591 let mut node = Node::new(
592 NodeId::new(1).unwrap(),
593 Callbacks::default(),
594 mbox,
595 state,
596 od_table,
597 );
598
599 node.process(0);
600 assert_eq!(NmtState::PreOperational, node.nmt_state());
601 }
602}