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, NmtState, ZencanMessage, LSS_RESP_ID,
11 },
12 NodeId,
13};
14
15use crate::{
16 lss_slave::{LssConfig, LssSlave},
17 node_mbox::NodeMbox,
18 object_dict::{find_object, ODEntry},
19};
20use crate::{node_state::NodeStateAccess, sdo_server::SdoServer};
21
22use defmt_or_log::{debug, info};
23
24pub type StoreNodeConfigFn<'a> = dyn FnMut(NodeId) + 'a;
25pub type StoreObjectsFn<'a> = dyn Fn(&mut dyn embedded_io::Read<Error = Infallible>, usize) + 'a;
26pub type StateChangeFn<'a> = dyn FnMut(&'a [ODEntry<'a>]) + 'a;
27
28#[allow(missing_debug_implementations)]
32#[derive(Default)]
33pub struct Callbacks<'a> {
34 pub store_node_config: Option<&'a mut StoreNodeConfigFn<'a>>,
40
41 pub store_objects: Option<&'a mut StoreObjectsFn<'a>>,
46
47 pub reset_app: Option<&'a mut StateChangeFn<'a>>,
53
54 pub reset_comms: Option<&'a mut StateChangeFn<'a>>,
64
65 pub enter_operational: Option<&'a mut StateChangeFn<'a>>,
67
68 pub enter_stopped: Option<&'a mut StateChangeFn<'a>>,
70
71 pub enter_preoperational: Option<&'a mut StateChangeFn<'a>>,
73}
74
75impl<'a> Callbacks<'a> {
76 pub const fn new() -> Self {
78 Self {
79 store_node_config: None,
80 store_objects: None,
81 reset_app: None,
82 reset_comms: None,
83 enter_operational: None,
84 enter_stopped: None,
85 enter_preoperational: None,
86 }
87 }
88}
89
90fn read_identity(od: &[ODEntry]) -> Option<LssIdentity> {
91 let obj = find_object(od, object_ids::IDENTITY)?;
92 let vendor_id = obj.read_u32(1).ok()?;
93 let product_code = obj.read_u32(2).ok()?;
94 let revision = obj.read_u32(3).ok()?;
95 let serial = obj.read_u32(4).ok()?;
96 Some(LssIdentity {
97 vendor_id,
98 product_code,
99 revision,
100 serial,
101 })
102}
103
104fn read_heartbeat_period(od: &[ODEntry]) -> Option<u16> {
105 let obj = find_object(od, object_ids::HEARTBEAT_PRODUCER_TIME)?;
106 obj.read_u16(0).ok()
107}
108
109fn read_autostart(od: &[ODEntry]) -> Option<bool> {
110 let obj = find_object(od, object_ids::AUTO_START)?;
111 Some(obj.read_u8(0).unwrap() != 0)
112}
113
114#[allow(missing_debug_implementations)]
125pub struct Node<'a> {
126 node_id: NodeId,
127 nmt_state: NmtState,
128 sdo_server: SdoServer<'a>,
129 lss_slave: LssSlave,
130 message_count: u32,
131 od: &'a [ODEntry<'a>],
132 mbox: &'a NodeMbox,
133 state: &'a dyn NodeStateAccess,
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: &'a NodeMbox,
156 state: &'a dyn NodeStateAccess,
157 od: &'a [ODEntry<'a>],
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(),
163 node_id,
164 store_supported: false,
165 });
166 let nmt_state = NmtState::Bootup;
167 let reassigned_node_id = None;
168
169 if callbacks.store_objects.is_some() {
171 state
172 .storage_context()
173 .store_supported
174 .store(true, Ordering::Relaxed);
175 }
176
177 let heartbeat_period_ms = read_heartbeat_period(od).unwrap_or(0);
178 let next_heartbeat_time_us = 0;
179 let auto_start = read_autostart(od).unwrap_or(false);
180 let last_process_time_us = 0;
181 let transmit_flag = false;
182
183 let mut node = Self {
184 node_id,
185 callbacks,
186 nmt_state,
187 sdo_server,
188 lss_slave,
189 message_count,
190 od,
191 mbox,
192 state,
193 reassigned_node_id,
194 next_heartbeat_time_us,
195 heartbeat_period_ms,
196 auto_start,
197 last_process_time_us,
198 transmit_flag,
199 };
200
201 node.reset_app();
202 node
203 }
204
205 pub fn set_node_id(&mut self, node_id: NodeId) {
209 self.reassigned_node_id = Some(node_id);
210 }
211
212 pub fn process(&mut self, now_us: u64) -> bool {
230 let elapsed = (now_us - self.last_process_time_us) as u32;
231 self.last_process_time_us = now_us;
232
233 self.transmit_flag = false;
234
235 let mut update_flag = false;
236 if let Some(new_node_id) = self.reassigned_node_id.take() {
237 self.node_id = new_node_id;
238 self.nmt_state = NmtState::Bootup;
239 }
240
241 if self.nmt_state == NmtState::Bootup {
242 self.enter_preoperational();
244 self.boot_up();
245 }
246
247 if self.auto_start && self.node_id.is_configured() {
250 self.auto_start = false;
252 self.enter_operational();
253 }
254
255 let (message_sent, updated_index) =
257 self.sdo_server
258 .process(self.mbox.sdo_comms(), elapsed, self.od);
259
260 self.transmit_flag |= message_sent;
261 if updated_index.is_some() {
262 update_flag = true;
263 }
264
265 if self
267 .state
268 .storage_context()
269 .store_flag
270 .swap(false, Ordering::Relaxed)
271 {
272 if let Some(cb) = &mut self.callbacks.store_objects {
274 crate::persist::serialize(self.od, *cb);
275 }
276 }
277
278 if let Some(msg) = self.mbox.read_nmt_mbox() {
280 if let Ok(ZencanMessage::NmtCommand(cmd)) = msg.try_into() {
281 self.message_count += 1;
282 if let NodeId::Configured(node_id) = self.node_id {
285 if cmd.node == 0 || cmd.node == node_id.raw() {
286 debug!("Received NMT command: {:?}", cmd.cs);
287 self.handle_nmt_command(cmd.cs);
288 }
289 }
290 }
291 }
292
293 if let Ok(Some(resp)) = self.lss_slave.process(self.mbox.lss_receiver()) {
294 self.send_message(resp.to_can_message(LSS_RESP_ID));
295
296 if let Some(event) = self.lss_slave.pending_event() {
297 info!("LSS Slave Event: {:?}", event);
298 match event {
299 crate::lss_slave::LssEvent::StoreConfiguration => {
300 if let Some(cb) = &mut self.callbacks.store_node_config {
301 (cb)(self.node_id)
302 }
303 }
304 crate::lss_slave::LssEvent::ActivateBitTiming {
305 table: _,
306 index: _,
307 delay: _,
308 } => (),
309 crate::lss_slave::LssEvent::ConfigureNodeId { node_id } => {
310 self.set_node_id(node_id)
311 }
312 }
313 }
314 }
315
316 if self.heartbeat_period_ms != 0 && now_us >= self.next_heartbeat_time_us {
317 self.send_heartbeat();
318 if self.next_heartbeat_time_us < now_us {
321 self.next_heartbeat_time_us = now_us;
322 }
323 }
324
325 if self.nmt_state == NmtState::Operational {
326 let sync = self.mbox.read_sync_flag();
328
329 let global_trigger = self.state.object_flag_sync().toggle();
334
335 for pdo in self.state.get_tpdos() {
336 if !(pdo.valid()) {
337 continue;
338 }
339 let transmission_type = pdo.transmission_type();
340 if transmission_type >= 254 {
341 if global_trigger && pdo.read_events() {
342 pdo.send_pdo();
343 self.transmit_flag = true;
344 }
345 } else if sync && pdo.sync_update() {
346 pdo.send_pdo();
347 self.transmit_flag = true;
348 }
349 }
350
351 for pdo in self.state.get_tpdos() {
352 pdo.clear_events();
353 }
354
355 for rpdo in self.state.get_rpdos() {
356 if !rpdo.valid() {
357 continue;
358 }
359 if let Some(new_data) = rpdo.buffered_value.take() {
360 rpdo.store_pdo_data(&new_data);
361 update_flag = true;
362 }
363 }
364 }
365
366 if self.transmit_flag {
367 self.mbox.transmit_notify();
368 }
369
370 update_flag
371 }
372
373 fn handle_nmt_command(&mut self, cmd: NmtCommandSpecifier) {
374 let prev_state = self.nmt_state;
375
376 match cmd {
377 NmtCommandSpecifier::Start => self.enter_operational(),
378 NmtCommandSpecifier::Stop => self.enter_stopped(),
379 NmtCommandSpecifier::EnterPreOp => self.enter_preoperational(),
380 NmtCommandSpecifier::ResetApp => self.reset_app(),
381 NmtCommandSpecifier::ResetComm => self.reset_comm(),
382 }
383
384 debug!(
385 "NMT state changed from {:?} to {:?}",
386 prev_state, self.nmt_state
387 );
388 }
389
390 pub fn node_id(&self) -> u8 {
392 self.node_id.into()
393 }
394
395 pub fn nmt_state(&self) -> NmtState {
397 self.nmt_state
398 }
399
400 pub fn rx_message_count(&self) -> u32 {
402 self.message_count
403 }
404
405 fn sdo_tx_cob_id(&self) -> CanId {
406 let node_id: u8 = self.node_id.into();
407 CanId::Std(0x580 + node_id as u16)
408 }
409
410 fn sdo_rx_cob_id(&self) -> CanId {
411 let node_id: u8 = self.node_id.into();
412 CanId::Std(0x600 + node_id as u16)
413 }
414
415 fn send_message(&mut self, msg: CanMessage) {
416 self.transmit_flag = true;
417 self.mbox.queue_transmit_message(msg).ok();
419 }
420
421 fn enter_operational(&mut self) {
422 self.nmt_state = NmtState::Operational;
423 if let Some(cb) = &mut self.callbacks.enter_operational {
424 (*cb)(self.od);
425 }
426 }
427
428 fn enter_stopped(&mut self) {
429 self.nmt_state = NmtState::Stopped;
430 if let Some(cb) = &mut self.callbacks.enter_stopped {
431 (*cb)(self.od);
432 }
433 }
434
435 fn enter_preoperational(&mut self) {
436 self.nmt_state = NmtState::PreOperational;
437 if let Some(cb) = &mut self.callbacks.enter_preoperational {
438 (*cb)(self.od);
439 }
440 }
441
442 fn reset_app(&mut self) {
443 for pdo in self.state.get_rpdos().iter().chain(self.state.get_tpdos()) {
445 pdo.init_defaults(self.node_id);
446 }
447
448 if let Some(reset_app_cb) = &mut self.callbacks.reset_app {
449 (*reset_app_cb)(self.od);
450 }
451 self.nmt_state = NmtState::Bootup;
452 }
453
454 fn reset_comm(&mut self) {
455 for pdo in self.state.get_rpdos().iter().chain(self.state.get_tpdos()) {
456 pdo.init_defaults(self.node_id);
457 }
458 if let Some(reset_comms_cb) = &mut self.callbacks.reset_comms {
459 (*reset_comms_cb)(self.od);
460 }
461 self.nmt_state = NmtState::Bootup;
462 }
463
464 fn boot_up(&mut self) {
465 self.lss_slave.update_config(LssConfig {
467 identity: read_identity(self.od).unwrap(),
468 node_id: self.node_id,
469 store_supported: self.callbacks.store_node_config.is_some(),
470 });
471
472 if let NodeId::Configured(node_id) = self.node_id {
473 info!("Booting node with ID {}", node_id.raw());
474 self.mbox.set_sdo_rx_cob_id(Some(self.sdo_rx_cob_id()));
475 self.mbox.set_sdo_tx_cob_id(Some(self.sdo_tx_cob_id()));
476 self.send_heartbeat();
477 }
478 }
479
480 fn send_heartbeat(&mut self) {
481 if let NodeId::Configured(node_id) = self.node_id {
482 let heartbeat = Heartbeat {
483 node: node_id.raw(),
484 toggle: false,
485 state: self.nmt_state,
486 };
487 self.send_message(heartbeat.into());
488 self.next_heartbeat_time_us += (self.heartbeat_period_ms as u64) * 1000;
489 }
490 }
491}