1use heapless::Vec;
31
32use crate::datatypes::Value;
33use crate::emcy::{self, EmergencyMessage, ErrorRegister};
34use crate::lss::{self, LssAddress, LssSlave};
35use crate::nmt::{self, NmtState, NmtStateMachine};
36use crate::object_dictionary::{Address, ObjectDictionary};
37use crate::pdo::{self, PdoMapping, TransmissionType};
38use crate::sdo::{self, SdoServer};
39use crate::sync::{self, SyncCounter};
40use crate::types::NodeId;
41use crate::{Error, Result};
42
43pub const MAX_PDOS: usize = 4;
46
47pub const MAX_PDO_MAPPING: usize = 8;
50
51pub const MAX_ERROR_HISTORY: usize = 8;
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub struct TxFrame {
58 pub cob_id: u16,
60 data: [u8; 8],
61 len: u8,
62}
63
64impl TxFrame {
65 fn new(cob_id: u16, bytes: &[u8]) -> Self {
66 let len = bytes.len().min(8);
67 let mut data = [0u8; 8];
68 data[..len].copy_from_slice(&bytes[..len]);
69 Self {
70 cob_id,
71 data,
72 len: len as u8,
73 }
74 }
75
76 pub fn data(&self) -> &[u8] {
78 &self.data[..self.len as usize]
79 }
80}
81
82#[derive(Debug)]
84struct RpdoSlot {
85 cob_id: u16,
86 mapping: PdoMapping<MAX_PDO_MAPPING>,
87}
88
89#[derive(Debug)]
91struct TpdoSlot {
92 cob_id: u16,
93 mapping: PdoMapping<MAX_PDO_MAPPING>,
94 transmission: TransmissionType,
95}
96
97#[derive(Debug)]
100pub struct Node<const N: usize> {
101 node_id: NodeId,
102 od: ObjectDictionary<N>,
103 sdo: SdoServer,
104 nmt: NmtStateMachine,
105 rpdos: Vec<RpdoSlot, MAX_PDOS>,
106 tpdos: Vec<TpdoSlot, MAX_PDOS>,
107 lss: Option<LssSlave>,
108 guard_toggle: bool,
109 error_register: u8,
110 error_history: Vec<u32, MAX_ERROR_HISTORY>,
111 sync_producer: Option<SyncCounter>,
112}
113
114impl<const N: usize> Node<N> {
115 pub fn new(node_id: NodeId, od: ObjectDictionary<N>) -> Self {
118 Self {
119 node_id,
120 od,
121 sdo: SdoServer::new(node_id),
122 nmt: NmtStateMachine::new(),
123 rpdos: Vec::new(),
124 tpdos: Vec::new(),
125 lss: None,
126 guard_toggle: false,
127 error_register: 0,
128 error_history: Vec::new(),
129 sync_producer: None,
130 }
131 }
132
133 pub fn enable_lss(&mut self, address: LssAddress) {
141 self.lss = Some(LssSlave::new(address, self.node_id.raw()));
142 }
143
144 pub fn enable_lss_unconfigured(&mut self, address: LssAddress) {
154 self.lss = Some(LssSlave::new(address, lss::UNCONFIGURED_NODE_ID));
155 }
156
157 fn lss_unconfigured(&self) -> bool {
160 matches!(&self.lss, Some(l) if l.node_id() == lss::UNCONFIGURED_NODE_ID)
161 }
162
163 pub fn set_node_id(&mut self, node_id: NodeId) {
166 self.node_id = node_id;
167 self.sdo = SdoServer::new(node_id);
168 }
169
170 pub fn apply_lss_node_id(&mut self) -> Option<NodeId> {
174 let pending = self.lss.as_ref()?.pending_node_id();
175 let node_id = NodeId::new(pending).ok()?;
176 self.set_node_id(node_id);
177 if let Some(lss) = &mut self.lss {
178 lss.adopt_pending();
179 }
180 Some(node_id)
181 }
182
183 pub fn lss(&self) -> Option<&LssSlave> {
185 self.lss.as_ref()
186 }
187
188 pub fn add_rpdo(&mut self, cob_id: u16, mapping: PdoMapping<MAX_PDO_MAPPING>) -> Result<()> {
193 self.rpdos
194 .push(RpdoSlot { cob_id, mapping })
195 .map_err(|_| Error::MappingFull)
196 }
197
198 pub fn add_tpdo(
203 &mut self,
204 cob_id: u16,
205 mapping: PdoMapping<MAX_PDO_MAPPING>,
206 transmission: TransmissionType,
207 ) -> Result<()> {
208 self.tpdos
209 .push(TpdoSlot {
210 cob_id,
211 mapping,
212 transmission,
213 })
214 .map_err(|_| Error::MappingFull)
215 }
216
217 pub fn configure_pdos_from_od(&mut self) {
227 self.rpdos.clear();
228 self.tpdos.clear();
229 for n in 0..MAX_PDOS as u16 {
230 if let Some(slot) = build_rpdo(&self.od, n) {
231 let _ = self.rpdos.push(slot);
232 }
233 if let Some(slot) = build_tpdo(&self.od, n) {
234 let _ = self.tpdos.push(slot);
235 }
236 }
237 }
238
239 pub fn take_written_object(&mut self) -> Option<crate::object_dictionary::Address> {
256 self.sdo.take_write()
257 }
258
259 pub fn node_id(&self) -> NodeId {
261 self.node_id
262 }
263
264 pub fn state(&self) -> NmtState {
266 self.nmt.state()
267 }
268
269 pub fn od(&self) -> &ObjectDictionary<N> {
271 &self.od
272 }
273
274 pub fn od_mut(&mut self) -> &mut ObjectDictionary<N> {
276 &mut self.od
277 }
278
279 pub fn boot(&mut self) -> TxFrame {
282 self.nmt.boot();
283 self.guard_toggle = false;
284 TxFrame::new(nmt::heartbeat_cob_id(self.node_id), &nmt::BOOTUP_FRAME)
285 }
286
287 pub fn node_guard_response(&mut self) -> TxFrame {
292 let byte = nmt::encode_node_guard(self.nmt.state(), self.guard_toggle);
293 self.guard_toggle = !self.guard_toggle;
294 TxFrame::new(nmt::heartbeat_cob_id(self.node_id), &[byte])
295 }
296
297 pub fn heartbeat(&self) -> TxFrame {
300 TxFrame::new(
301 nmt::heartbeat_cob_id(self.node_id),
302 &nmt::encode_heartbeat(self.nmt.state()),
303 )
304 }
305
306 pub fn error_register(&self) -> ErrorRegister {
309 ErrorRegister(self.error_register)
310 }
311
312 pub fn error_history(&self) -> &[u32] {
316 &self.error_history
317 }
318
319 pub fn raise_emergency(
327 &mut self,
328 code: u16,
329 register: ErrorRegister,
330 info: [u8; 5],
331 ) -> TxFrame {
332 self.error_register |= register.0 | ErrorRegister::GENERIC;
334
335 if self.error_history.is_full() {
337 self.error_history.pop();
338 }
339 let len = self.error_history.len();
340 let _ = self.error_history.push(0); for i in (0..len).rev() {
342 self.error_history[i + 1] = self.error_history[i];
343 }
344 self.error_history[0] = u32::from(code);
345
346 self.mirror_error_objects();
347 let msg = EmergencyMessage::new(code, ErrorRegister(self.error_register), info);
348 TxFrame::new(emcy::emcy_cob_id(self.node_id), &msg.encode())
349 }
350
351 pub fn clear_errors(&mut self) -> TxFrame {
355 self.error_register = 0;
356 self.error_history.clear();
357 self.mirror_error_objects();
358 TxFrame::new(
359 emcy::emcy_cob_id(self.node_id),
360 &EmergencyMessage::error_reset().encode(),
361 )
362 }
363
364 fn mirror_error_objects(&mut self) {
369 let _ = self.od.set(
370 Address::new(0x1001, 0),
371 Value::Unsigned8(self.error_register),
372 );
373 let count = self.error_history.len() as u8;
374 let _ = self
375 .od
376 .set(Address::new(0x1003, 0), Value::Unsigned8(count));
377 for (i, &entry) in self.error_history.iter().enumerate() {
378 let sub = (i + 1) as u8;
379 let _ = self
380 .od
381 .set(Address::new(0x1003, sub), Value::Unsigned32(entry));
382 }
383 }
384
385 pub fn enable_sync_producer(&mut self, counter_overflow: u8) -> Result<()> {
392 self.sync_producer = Some(SyncCounter::new(counter_overflow)?);
393 Ok(())
394 }
395
396 pub fn produce_sync(&mut self) -> Option<TxFrame> {
405 let counter = self.sync_producer.as_mut()?;
406 Some(match counter.advance() {
407 Some(count) => TxFrame::new(sync::SYNC_COB_ID, &sync::encode_counter(count)),
408 None => TxFrame::new(sync::SYNC_COB_ID, &[]),
409 })
410 }
411
412 pub fn on_frame(&mut self, cob_id: u16, data: &[u8]) -> Option<TxFrame> {
424 if cob_id == lss::LSS_MASTER_COB_ID {
425 return self.on_lss(data);
426 }
427 if self.lss_unconfigured() {
428 return None; }
430 if cob_id == nmt::NMT_COMMAND_COB_ID {
431 self.on_nmt(data);
432 None
433 } else if cob_id == self.sdo.request_cob_id() {
434 self.on_sdo(data)
435 } else {
436 self.on_rpdo(cob_id, data);
437 None
438 }
439 }
440
441 fn on_lss(&mut self, data: &[u8]) -> Option<TxFrame> {
442 let lss = self.lss.as_mut()?;
443 if data.len() > 8 {
444 return None;
445 }
446 let mut frame: lss::LssFrame = [0u8; 8];
447 frame[..data.len()].copy_from_slice(data);
448 lss.handle(&frame)
449 .map(|resp| TxFrame::new(lss::LSS_SLAVE_COB_ID, &resp))
450 }
451
452 pub fn sync_tpdos(&self) -> Vec<TxFrame, MAX_PDOS> {
458 let mut frames = Vec::new();
459 if self.nmt.state() != NmtState::Operational {
460 return frames;
461 }
462 for slot in &self.tpdos {
463 if is_synchronous(slot.transmission) {
464 if let Some(frame) = self.build_tpdo(slot) {
465 let _ = frames.push(frame);
467 }
468 }
469 }
470 frames
471 }
472
473 pub fn tpdo(&self, index: usize) -> Option<TxFrame> {
476 if self.nmt.state() != NmtState::Operational {
477 return None;
478 }
479 self.build_tpdo(self.tpdos.get(index)?)
480 }
481
482 fn build_tpdo(&self, slot: &TpdoSlot) -> Option<TxFrame> {
483 if slot.mapping.is_empty() {
484 return None;
485 }
486 let mut buf = [0u8; 8];
487 let len = pdo::pack(&slot.mapping, &self.od, &mut buf).ok()?;
488 Some(TxFrame::new(slot.cob_id, &buf[..len]))
489 }
490
491 fn on_rpdo(&mut self, cob_id: u16, data: &[u8]) {
492 if self.nmt.state() != NmtState::Operational {
494 return;
495 }
496 if let Some(i) = self.rpdos.iter().position(|r| r.cob_id == cob_id) {
497 let _ = pdo::unpack(&self.rpdos[i].mapping, &mut self.od, data);
499 }
500 }
501
502 fn on_nmt(&mut self, data: &[u8]) {
503 if data.len() < 2 {
505 return;
506 }
507 if let Ok((command, target)) = nmt::decode_command(&[data[0], data[1]]) {
508 if target == NodeId::BROADCAST || target == self.node_id {
509 self.nmt.apply(command);
510 }
511 }
512 }
513
514 fn on_sdo(&mut self, data: &[u8]) -> Option<TxFrame> {
515 if !matches!(
517 self.nmt.state(),
518 NmtState::PreOperational | NmtState::Operational
519 ) {
520 return None;
521 }
522 let mut payload: sdo::SdoPayload = [0u8; 8];
523 if data.len() > payload.len() {
524 return None;
525 }
526 payload[..data.len()].copy_from_slice(data);
527 let response = self.sdo.handle(&mut self.od, &payload)?;
528 Some(TxFrame::new(self.sdo.response_cob_id(), &response))
529 }
530}
531
532fn is_synchronous(transmission: TransmissionType) -> bool {
534 matches!(
535 transmission,
536 TransmissionType::SynchronousAcyclic | TransmissionType::SynchronousCyclic(_)
537 )
538}
539
540fn od_u32<const N: usize>(od: &ObjectDictionary<N>, index: u16, sub: u8) -> Option<u32> {
543 match od
544 .read(crate::object_dictionary::Address::new(index, sub))
545 .ok()?
546 {
547 crate::datatypes::Value::Unsigned32(v) => Some(v),
548 _ => None,
549 }
550}
551
552fn od_u8<const N: usize>(od: &ObjectDictionary<N>, index: u16, sub: u8) -> Option<u8> {
553 match od
554 .read(crate::object_dictionary::Address::new(index, sub))
555 .ok()?
556 {
557 crate::datatypes::Value::Unsigned8(v) => Some(v),
558 _ => None,
559 }
560}
561
562fn read_pdo_mapping<const N: usize>(
565 od: &ObjectDictionary<N>,
566 map_index: u16,
567) -> Option<PdoMapping<MAX_PDO_MAPPING>> {
568 let count = od_u8(od, map_index, 0)?;
569 let mut mapping = PdoMapping::new();
570 for sub in 1..=count {
571 mapping
572 .push(pdo::MappingEntry::from_u32(od_u32(od, map_index, sub)?))
573 .ok()?;
574 }
575 Some(mapping)
576}
577
578fn build_rpdo<const N: usize>(od: &ObjectDictionary<N>, n: u16) -> Option<RpdoSlot> {
579 let cob_id = od_u32(od, 0x1400 + n, 1)?;
580 if !pdo::pdo_is_valid(cob_id) {
581 return None;
582 }
583 Some(RpdoSlot {
584 cob_id: pdo::pdo_can_id(cob_id),
585 mapping: read_pdo_mapping(od, 0x1600 + n)?,
586 })
587}
588
589fn build_tpdo<const N: usize>(od: &ObjectDictionary<N>, n: u16) -> Option<TpdoSlot> {
590 let cob_id = od_u32(od, 0x1800 + n, 1)?;
591 if !pdo::pdo_is_valid(cob_id) {
592 return None;
593 }
594 let transmission = od_u8(od, 0x1800 + n, 2)
596 .and_then(|b| TransmissionType::from_byte(b).ok())
597 .unwrap_or(TransmissionType::EventDrivenProfile);
598 Some(TpdoSlot {
599 cob_id: pdo::pdo_can_id(cob_id),
600 mapping: read_pdo_mapping(od, 0x1A00 + n)?,
601 transmission,
602 })
603}
604
605#[cfg(test)]
606mod tests {
607 use super::*;
608 use crate::object_dictionary::{Address, Entry};
609 use crate::pdo::MappingEntry;
610 use crate::sdo::{encode_download_expedited, encode_upload_request};
611 use crate::{DataType, NmtCommand, Value};
612
613 fn start<const N: usize>(n: &mut Node<N>) {
614 n.on_frame(
615 nmt::NMT_COMMAND_COB_ID,
616 &[NmtCommand::StartRemoteNode as u8, 0x10],
617 );
618 }
619
620 fn od() -> ObjectDictionary<8> {
621 let mut od = ObjectDictionary::new();
622 od.insert(
623 Address::new(0x1000, 0),
624 Entry::constant(Value::Unsigned32(0x192)),
625 )
626 .unwrap();
627 od.insert(Address::new(0x1017, 0), Entry::rw(Value::Unsigned16(1000)))
628 .unwrap();
629 od
630 }
631
632 fn node() -> Node<8> {
633 Node::new(NodeId::new(0x10).unwrap(), od())
634 }
635
636 #[test]
637 fn boots_from_init_to_preop_and_announces() {
638 let mut n = node();
639 assert_eq!(n.state(), NmtState::Initialising);
640 let boot = n.boot();
641 assert_eq!(n.state(), NmtState::PreOperational);
642 assert_eq!(boot.cob_id, 0x710); assert_eq!(boot.data(), &[0x00]);
644 }
645
646 #[test]
647 fn heartbeat_reflects_state() {
648 let mut n = node();
649 n.boot();
650 assert_eq!(n.heartbeat().data(), &[0x7F]); n.on_frame(
652 nmt::NMT_COMMAND_COB_ID,
653 &[NmtCommand::StartRemoteNode as u8, 0x10],
654 );
655 assert_eq!(n.state(), NmtState::Operational);
656 assert_eq!(n.heartbeat().data(), &[0x05]); }
658
659 #[test]
660 fn node_guard_response_toggles() {
661 let mut n = node();
662 n.boot();
663 start(&mut n); let first = n.node_guard_response();
665 assert_eq!(first.cob_id, 0x710); assert_eq!(first.data(), &[0x05]); assert_eq!(n.node_guard_response().data(), &[0x85]); assert_eq!(n.node_guard_response().data(), &[0x05]); }
670
671 #[test]
672 fn serves_sdo_read_when_preoperational() {
673 let mut n = node();
674 n.boot();
675 let req = encode_upload_request(Address::new(0x1000, 0));
676 let resp = n.on_frame(0x610, &req).expect("SDO response");
677 assert_eq!(resp.cob_id, 0x590); let (_, value) = crate::sdo::decode_upload_expedited_response(
679 resp.data().try_into().unwrap(),
680 DataType::Unsigned32,
681 )
682 .unwrap();
683 assert_eq!(value, Value::Unsigned32(0x192));
684 }
685
686 #[test]
687 fn ignores_sdo_before_boot() {
688 let mut n = node(); let req = encode_upload_request(Address::new(0x1000, 0));
690 assert!(n.on_frame(0x610, &req).is_none());
691 }
692
693 #[test]
694 fn ignores_sdo_when_stopped() {
695 let mut n = node();
696 n.boot();
697 n.on_frame(
698 nmt::NMT_COMMAND_COB_ID,
699 &[NmtCommand::StopRemoteNode as u8, 0x10],
700 );
701 assert_eq!(n.state(), NmtState::Stopped);
702 let req = encode_upload_request(Address::new(0x1000, 0));
703 assert!(n.on_frame(0x610, &req).is_none());
704 }
705
706 #[test]
707 fn nmt_command_for_other_node_is_ignored() {
708 let mut n = node();
709 n.boot();
710 n.on_frame(
712 nmt::NMT_COMMAND_COB_ID,
713 &[NmtCommand::StartRemoteNode as u8, 0x20],
714 );
715 assert_eq!(n.state(), NmtState::PreOperational); }
717
718 #[test]
719 fn broadcast_nmt_applies() {
720 let mut n = node();
721 n.boot();
722 n.on_frame(
723 nmt::NMT_COMMAND_COB_ID,
724 &[NmtCommand::StartRemoteNode as u8, 0x00],
725 );
726 assert_eq!(n.state(), NmtState::Operational);
727 }
728
729 #[test]
730 fn serves_sdo_write_and_updates_od() {
731 let mut n = node();
732 n.boot();
733 let req =
734 encode_download_expedited(Address::new(0x1017, 0), &Value::Unsigned16(1234)).unwrap();
735 assert!(n.on_frame(0x610, &req).is_some());
736 assert_eq!(
737 n.od().read(Address::new(0x1017, 0)).unwrap(),
738 Value::Unsigned16(1234)
739 );
740 }
741
742 #[test]
743 fn ignores_unrelated_cob_id() {
744 let mut n = node();
745 n.boot();
746 assert!(n.on_frame(0x123, &[0; 8]).is_none());
747 }
748
749 fn pdo_od() -> ObjectDictionary<8> {
751 let mut od = ObjectDictionary::new();
752 od.insert(
754 Address::new(0x6000, 1),
755 Entry::rw(Value::Unsigned16(0xBEEF)),
756 )
757 .unwrap();
758 od.insert(Address::new(0x6000, 2), Entry::rw(Value::Unsigned8(0x42)))
759 .unwrap();
760 od.insert(Address::new(0x6200, 1), Entry::rw(Value::Unsigned16(0)))
761 .unwrap();
762 od
763 }
764
765 fn mapping(entries: &[(u16, u8, u8)]) -> PdoMapping<MAX_PDO_MAPPING> {
766 let mut m = PdoMapping::new();
767 for &(index, sub, bits) in entries {
768 m.push(MappingEntry::new(index, sub, bits)).unwrap();
769 }
770 m
771 }
772
773 #[test]
774 fn tpdo_transmits_only_when_operational() {
775 let mut n = Node::new(NodeId::new(0x10).unwrap(), pdo_od());
776 n.add_tpdo(
777 0x18A,
778 mapping(&[(0x6000, 1, 16), (0x6000, 2, 8)]),
779 TransmissionType::SynchronousAcyclic,
780 )
781 .unwrap();
782 n.boot();
783
784 assert!(n.sync_tpdos().is_empty());
786
787 start(&mut n);
788 let frames = n.sync_tpdos();
789 assert_eq!(frames.len(), 1);
790 assert_eq!(frames[0].cob_id, 0x18A);
791 assert_eq!(frames[0].data(), &[0xEF, 0xBE, 0x42]);
793 }
794
795 #[test]
796 fn event_tpdo_by_index() {
797 let mut n = Node::new(NodeId::new(0x10).unwrap(), pdo_od());
798 n.add_tpdo(
800 0x18A,
801 mapping(&[(0x6000, 2, 8)]),
802 TransmissionType::EventDrivenProfile,
803 )
804 .unwrap();
805 n.boot();
806 start(&mut n);
807 assert!(n.sync_tpdos().is_empty());
808 assert_eq!(n.tpdo(0).unwrap().data(), &[0x42]);
809 assert!(n.tpdo(1).is_none());
810 }
811
812 #[test]
813 fn rpdo_applies_only_when_operational() {
814 let mut n = Node::new(NodeId::new(0x10).unwrap(), pdo_od());
815 n.add_rpdo(0x20A, mapping(&[(0x6200, 1, 16)])).unwrap();
816 n.boot();
817
818 assert!(n.on_frame(0x20A, &[0x34, 0x12]).is_none());
820 assert_eq!(
821 n.od().read(Address::new(0x6200, 1)).unwrap(),
822 Value::Unsigned16(0)
823 );
824
825 start(&mut n);
827 n.on_frame(0x20A, &[0x34, 0x12]);
828 assert_eq!(
829 n.od().read(Address::new(0x6200, 1)).unwrap(),
830 Value::Unsigned16(0x1234)
831 );
832 }
833
834 #[test]
835 fn pdo_capacity_is_enforced() {
836 let mut n = Node::new(NodeId::new(0x10).unwrap(), pdo_od());
837 for _ in 0..MAX_PDOS {
838 n.add_tpdo(
839 0x18A,
840 mapping(&[(0x6000, 2, 8)]),
841 TransmissionType::SynchronousAcyclic,
842 )
843 .unwrap();
844 }
845 assert_eq!(
846 n.add_tpdo(
847 0x18A,
848 mapping(&[(0x6000, 2, 8)]),
849 TransmissionType::SynchronousAcyclic
850 ),
851 Err(Error::MappingFull)
852 );
853 }
854
855 use crate::lss::{self, encode_configure_node_id, encode_switch_global, LssAddress, LssState};
857
858 fn lss_address() -> LssAddress {
859 LssAddress {
860 vendor_id: 0x1F,
861 product_code: 0x2A,
862 revision_number: 1,
863 serial_number: 0x99,
864 }
865 }
866
867 #[test]
868 fn routes_lss_frames_when_enabled() {
869 let mut n = node();
870 n.enable_lss(lss_address());
871 assert!(n
873 .on_frame(lss::LSS_MASTER_COB_ID, &encode_switch_global(true))
874 .is_none());
875 assert_eq!(n.lss().unwrap().state(), LssState::Configuration);
876 }
877
878 #[test]
879 fn lss_frames_ignored_when_disabled() {
880 let mut n = node(); assert!(n
882 .on_frame(lss::LSS_MASTER_COB_ID, &encode_switch_global(true))
883 .is_none());
884 assert!(n.lss().is_none());
885 }
886
887 #[test]
888 fn lss_assigns_node_id_and_moves_sdo_cob_id() {
889 let mut n = Node::new(NodeId::new(1).unwrap(), od());
892 n.enable_lss(lss_address());
893 assert_eq!(n.node_id(), NodeId::new(1).unwrap());
894
895 n.on_frame(lss::LSS_MASTER_COB_ID, &encode_switch_global(true));
897 let resp = n
898 .on_frame(lss::LSS_MASTER_COB_ID, &encode_configure_node_id(0x20))
899 .expect("configure response");
900 assert_eq!(resp.cob_id, lss::LSS_SLAVE_COB_ID);
901 assert_eq!(&resp.data()[..2], &[0x11, 0x00]); assert_eq!(n.apply_lss_node_id(), Some(NodeId::new(0x20).unwrap()));
905 assert_eq!(n.node_id(), NodeId::new(0x20).unwrap());
906
907 n.boot();
908 let req = encode_upload_request(Address::new(0x1000, 0));
909 assert!(n.on_frame(0x601, &req).is_none()); assert!(n.on_frame(0x620, &req).is_some()); }
912
913 #[test]
914 fn unconfigured_node_serves_only_lss() {
915 let mut n = Node::new(NodeId::new(1).unwrap(), od());
916 n.enable_lss_unconfigured(lss_address());
917 n.boot(); let req = encode_upload_request(Address::new(0x1000, 0));
921 assert!(n.on_frame(0x601, &req).is_none());
922
923 n.on_frame(lss::LSS_MASTER_COB_ID, &encode_switch_global(true));
925 assert_eq!(n.lss().unwrap().state(), LssState::Configuration);
926 }
927
928 #[test]
929 fn fastscan_discovers_then_configures_node_via_on_frame() {
930 use crate::lss::FastscanMaster;
931 let addr = lss_address();
932 let mut n = Node::new(NodeId::new(1).unwrap(), od());
933 n.enable_lss_unconfigured(addr);
934
935 let mut master = FastscanMaster::new();
937 let mut steps = 0;
938 while let Some(req) = master.next_request() {
939 let answered = n.on_frame(lss::LSS_MASTER_COB_ID, &req).is_some();
940 master.on_response(answered);
941 steps += 1;
942 assert!(steps < 200, "fastscan did not converge");
943 }
944 assert!(master.found());
945 assert_eq!(master.address(), addr);
946 assert_eq!(n.lss().unwrap().state(), LssState::Configuration);
947
948 n.on_frame(lss::LSS_MASTER_COB_ID, &encode_configure_node_id(0x33));
950 assert_eq!(n.apply_lss_node_id(), Some(NodeId::new(0x33).unwrap()));
951 n.boot();
952 let req = encode_upload_request(Address::new(0x1000, 0));
953 assert!(n.on_frame(0x600 + 0x33, &req).is_some());
954 }
955
956 #[test]
958 fn configures_pdos_from_od() {
959 let mut od = ObjectDictionary::<16>::new();
960 od.insert(Address::new(0x1400, 1), Entry::rw(Value::Unsigned32(0x210)))
962 .unwrap();
963 od.insert(Address::new(0x1600, 0), Entry::rw(Value::Unsigned8(1)))
964 .unwrap();
965 od.insert(
966 Address::new(0x1600, 1),
967 Entry::rw(Value::Unsigned32(MappingEntry::new(0x6200, 1, 16).to_u32())),
968 )
969 .unwrap();
970 od.insert(Address::new(0x1800, 1), Entry::rw(Value::Unsigned32(0x190)))
972 .unwrap();
973 od.insert(Address::new(0x1800, 2), Entry::rw(Value::Unsigned8(1)))
974 .unwrap(); od.insert(Address::new(0x1A00, 0), Entry::rw(Value::Unsigned8(1)))
976 .unwrap();
977 od.insert(
978 Address::new(0x1A00, 1),
979 Entry::rw(Value::Unsigned32(MappingEntry::new(0x6000, 1, 16).to_u32())),
980 )
981 .unwrap();
982 od.insert(Address::new(0x6200, 1), Entry::rw(Value::Unsigned16(0)))
984 .unwrap();
985 od.insert(
986 Address::new(0x6000, 1),
987 Entry::rw(Value::Unsigned16(0xBEEF)),
988 )
989 .unwrap();
990
991 let mut n = Node::new(NodeId::new(0x10).unwrap(), od);
992 n.configure_pdos_from_od();
993 n.boot();
994 start(&mut n);
995
996 n.on_frame(0x210, &[0x34, 0x12]);
998 assert_eq!(
999 n.od().read(Address::new(0x6200, 1)).unwrap(),
1000 Value::Unsigned16(0x1234)
1001 );
1002 let tpdos = n.sync_tpdos();
1004 assert_eq!(tpdos.len(), 1);
1005 assert_eq!(tpdos[0].cob_id, 0x190);
1006 assert_eq!(tpdos[0].data(), &[0xEF, 0xBE]);
1007 }
1008
1009 #[test]
1010 fn skips_pdo_with_invalid_cob_id() {
1011 let mut od = ObjectDictionary::<8>::new();
1012 od.insert(
1014 Address::new(0x1800, 1),
1015 Entry::rw(Value::Unsigned32(0x8000_0190)),
1016 )
1017 .unwrap();
1018 od.insert(Address::new(0x1800, 2), Entry::rw(Value::Unsigned8(1)))
1019 .unwrap();
1020 od.insert(Address::new(0x1A00, 0), Entry::rw(Value::Unsigned8(1)))
1021 .unwrap();
1022 od.insert(
1023 Address::new(0x1A00, 1),
1024 Entry::rw(Value::Unsigned32(MappingEntry::new(0x6000, 1, 16).to_u32())),
1025 )
1026 .unwrap();
1027 od.insert(
1028 Address::new(0x6000, 1),
1029 Entry::rw(Value::Unsigned16(0xBEEF)),
1030 )
1031 .unwrap();
1032
1033 let mut n = Node::new(NodeId::new(0x10).unwrap(), od);
1034 n.configure_pdos_from_od();
1035 n.boot();
1036 start(&mut n);
1037 assert!(n.sync_tpdos().is_empty()); }
1039
1040 #[test]
1041 fn reacts_to_a_pdo_parameter_write() {
1042 let mut od = ObjectDictionary::<16>::new();
1044 od.insert(
1045 Address::new(0x1800, 1),
1046 Entry::rw(Value::Unsigned32(0x8000_0190)),
1047 )
1048 .unwrap();
1049 od.insert(Address::new(0x1800, 2), Entry::rw(Value::Unsigned8(1)))
1050 .unwrap();
1051 od.insert(Address::new(0x1A00, 0), Entry::rw(Value::Unsigned8(1)))
1052 .unwrap();
1053 od.insert(
1054 Address::new(0x1A00, 1),
1055 Entry::rw(Value::Unsigned32(MappingEntry::new(0x6000, 1, 16).to_u32())),
1056 )
1057 .unwrap();
1058 od.insert(
1059 Address::new(0x6000, 1),
1060 Entry::rw(Value::Unsigned16(0xBEEF)),
1061 )
1062 .unwrap();
1063
1064 let mut n = Node::new(NodeId::new(0x10).unwrap(), od);
1065 n.configure_pdos_from_od();
1066 n.boot();
1067 start(&mut n);
1068 assert!(n.sync_tpdos().is_empty());
1069
1070 let req =
1072 encode_download_expedited(Address::new(0x1800, 1), &Value::Unsigned32(0x190)).unwrap();
1073 n.on_frame(0x610, &req);
1074
1075 let written = n.take_written_object().expect("a write was recorded");
1077 assert_eq!(written, Address::new(0x1800, 1));
1078 assert_eq!(n.take_written_object(), None); if (0x1400..=0x1BFF).contains(&written.index) {
1080 n.configure_pdos_from_od();
1081 }
1082
1083 let tpdos = n.sync_tpdos();
1085 assert_eq!(tpdos.len(), 1);
1086 assert_eq!(tpdos[0].cob_id, 0x190);
1087 }
1088
1089 fn od_with_error_objects() -> ObjectDictionary<8> {
1091 let mut od = ObjectDictionary::new();
1092 od.insert(Address::new(0x1001, 0), Entry::ro(Value::Unsigned8(0)))
1093 .unwrap();
1094 od.insert(Address::new(0x1003, 0), Entry::ro(Value::Unsigned8(0)))
1095 .unwrap();
1096 od.insert(Address::new(0x1003, 1), Entry::ro(Value::Unsigned32(0)))
1097 .unwrap();
1098 od.insert(Address::new(0x1003, 2), Entry::ro(Value::Unsigned32(0)))
1099 .unwrap();
1100 od
1101 }
1102
1103 #[test]
1104 fn emergency_sets_register_and_emits_frame() {
1105 let mut n = node();
1106 let tx = n.raise_emergency(
1107 emcy::error_code::VOLTAGE,
1108 ErrorRegister(ErrorRegister::VOLTAGE),
1109 [0xAA, 0xBB, 0xCC, 0xDD, 0xEE],
1110 );
1111 assert_eq!(
1113 n.error_register(),
1114 ErrorRegister(ErrorRegister::GENERIC | ErrorRegister::VOLTAGE)
1115 );
1116 assert_eq!(tx.cob_id, 0x090); let msg = EmergencyMessage::decode(tx.data()).unwrap();
1118 assert_eq!(msg.error_code, emcy::error_code::VOLTAGE);
1119 assert_eq!(
1120 msg.error_register.0,
1121 ErrorRegister::GENERIC | ErrorRegister::VOLTAGE
1122 );
1123 assert_eq!(msg.vendor_specific, [0xAA, 0xBB, 0xCC, 0xDD, 0xEE]);
1124 assert_eq!(n.error_history(), &[u32::from(emcy::error_code::VOLTAGE)]);
1125 }
1126
1127 #[test]
1128 fn error_history_is_most_recent_first_and_bounded() {
1129 let mut n = node();
1130 let total = MAX_ERROR_HISTORY as u16 + 2;
1131 for i in 0..total {
1132 n.raise_emergency(0x1000 + i, ErrorRegister::NONE, [0; 5]);
1133 }
1134 assert_eq!(n.error_history().len(), MAX_ERROR_HISTORY);
1135 assert_eq!(n.error_history()[0], u32::from(0x1000 + total - 1)); let oldest_kept = 0x1000 + total - MAX_ERROR_HISTORY as u16;
1137 assert_eq!(*n.error_history().last().unwrap(), u32::from(oldest_kept));
1138 }
1139
1140 #[test]
1141 fn emergency_mirrors_into_object_dictionary() {
1142 let mut n = Node::new(NodeId::new(0x10).unwrap(), od_with_error_objects());
1143 n.raise_emergency(0x5530, ErrorRegister(ErrorRegister::DEVICE_PROFILE), [0; 5]);
1144
1145 assert_eq!(
1146 n.od().read(Address::new(0x1001, 0)).unwrap(),
1147 Value::Unsigned8(ErrorRegister::GENERIC | ErrorRegister::DEVICE_PROFILE)
1148 );
1149 assert_eq!(
1150 n.od().read(Address::new(0x1003, 0)).unwrap(),
1151 Value::Unsigned8(1)
1152 );
1153 assert_eq!(
1154 n.od().read(Address::new(0x1003, 1)).unwrap(),
1155 Value::Unsigned32(0x5530)
1156 );
1157 }
1158
1159 #[test]
1160 fn clear_errors_resets_register_history_and_od() {
1161 let mut n = Node::new(NodeId::new(0x10).unwrap(), od_with_error_objects());
1162 n.raise_emergency(
1163 0x3210,
1164 ErrorRegister(ErrorRegister::VOLTAGE),
1165 [1, 2, 3, 4, 5],
1166 );
1167
1168 let tx = n.clear_errors();
1169 assert_eq!(n.error_register(), ErrorRegister::NONE);
1170 assert!(n.error_history().is_empty());
1171 let msg = EmergencyMessage::decode(tx.data()).unwrap();
1172 assert!(msg.is_error_reset());
1173 assert_eq!(tx.cob_id, 0x090);
1174 assert_eq!(
1175 n.od().read(Address::new(0x1001, 0)).unwrap(),
1176 Value::Unsigned8(0)
1177 );
1178 assert_eq!(
1179 n.od().read(Address::new(0x1003, 0)).unwrap(),
1180 Value::Unsigned8(0)
1181 );
1182 }
1183
1184 #[test]
1185 fn emergency_without_error_objects_still_works() {
1186 let mut n = node();
1189 let tx = n.raise_emergency(0x8130, ErrorRegister(ErrorRegister::COMMUNICATION), [0; 5]);
1190 assert_eq!(tx.cob_id, 0x090);
1191 assert_eq!(
1192 n.error_register(),
1193 ErrorRegister(ErrorRegister::GENERIC | ErrorRegister::COMMUNICATION)
1194 );
1195 }
1196
1197 #[test]
1199 fn sync_production_is_off_by_default() {
1200 let mut n = node();
1201 assert!(n.produce_sync().is_none());
1202 }
1203
1204 #[test]
1205 fn counterless_sync_producer_emits_empty_frames() {
1206 let mut n = node();
1207 n.enable_sync_producer(0).unwrap();
1208 let tx = n.produce_sync().expect("a SYNC frame");
1209 assert_eq!(tx.cob_id, sync::SYNC_COB_ID); assert_eq!(tx.data(), &[] as &[u8]); }
1212
1213 #[test]
1214 fn counting_sync_producer_cycles_and_wraps() {
1215 let mut n = node();
1216 n.enable_sync_producer(3).unwrap();
1217 let counts: [&[u8]; 4] = [&[1], &[2], &[3], &[1]];
1218 for expected in counts {
1219 let tx = n.produce_sync().unwrap();
1220 assert_eq!(tx.cob_id, sync::SYNC_COB_ID);
1221 assert_eq!(tx.data(), expected);
1222 }
1223 }
1224
1225 #[test]
1226 fn enable_sync_producer_rejects_reserved_overflow() {
1227 let mut n = node();
1228 assert_eq!(n.enable_sync_producer(1), Err(Error::InvalidSyncCounter));
1229 assert!(n.produce_sync().is_none()); }
1231}