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