canopen_rs/sync.rs
1//! Synchronisation object (SYNC) — the network heartbeat for synchronous
2//! communication (CiA 301 §7.2.5).
3//!
4//! SYNC is a high-priority, broadcast frame produced periodically by a single
5//! node (the SYNC producer). It carries no addressed data: consumers act on
6//! its *arrival* — sampling inputs, applying synchronous RPDOs, transmitting
7//! synchronous TPDOs. The frame is empty by default, or carries a single
8//! **counter** byte when the producer's synchronous-counter-overflow value
9//! (object `0x1019`) is set to `2..=240`.
10//!
11//! The default COB-ID is `0x080` ([`SYNC_COB_ID`]), configurable via object
12//! `0x1005`.
13
14use crate::{Error, Result};
15
16/// The default SYNC COB-ID (`0x080`), configurable via object `0x1005`.
17pub const SYNC_COB_ID: u16 = 0x080;
18
19/// Whether a synchronous-counter-overflow value (object `0x1019`) is valid:
20/// `0` disables the counter (empty SYNC), and `2..=240` enables a counting
21/// SYNC. The value `1` is reserved.
22pub const fn is_valid_counter_overflow(overflow: u8) -> bool {
23 overflow == 0 || (2 <= overflow && overflow <= 240)
24}
25
26/// Decode a received SYNC frame's data field.
27///
28/// Returns `Ok(None)` for an empty (counter-less) SYNC, or `Ok(Some(count))`
29/// for a counting SYNC. Returns [`Error::BadLength`] if the frame carries more
30/// than one byte.
31pub fn decode(data: &[u8]) -> Result<Option<u8>> {
32 match data.len() {
33 0 => Ok(None),
34 1 => Ok(Some(data[0])),
35 _ => Err(Error::BadLength),
36 }
37}
38
39/// The data field of a counting SYNC carrying `count`.
40///
41/// The empty (counter-less) SYNC has no data field, so there is nothing to
42/// encode for it — transmit a zero-length frame.
43pub fn encode_counter(count: u8) -> [u8; 1] {
44 [count]
45}
46
47/// The counter for a SYNC producer (CiA 301 §7.2.5.2.1).
48///
49/// When enabled, the counter cycles `1..=overflow` and wraps back to `1`.
50/// Constructed from the object `0x1019` overflow value: `0` (or the reserved
51/// `1`) means the producer emits empty SYNC frames, so [`SyncCounter::advance`]
52/// yields `None`.
53#[derive(Debug, Clone, Copy)]
54pub struct SyncCounter {
55 overflow: u8,
56 value: u8,
57}
58
59impl SyncCounter {
60 /// Create a SYNC counter from an overflow value.
61 ///
62 /// Returns [`Error::InvalidSyncCounter`] unless `overflow` is `0` or in
63 /// `2..=240`.
64 pub fn new(overflow: u8) -> Result<Self> {
65 if !is_valid_counter_overflow(overflow) {
66 return Err(Error::InvalidSyncCounter);
67 }
68 // `value` holds the last emitted count; the first `advance` yields 1.
69 Ok(Self { overflow, value: 0 })
70 }
71
72 /// Whether this producer emits a counter (overflow in `2..=240`).
73 pub const fn is_counting(&self) -> bool {
74 self.overflow >= 2
75 }
76
77 /// Advance to the next SYNC counter value.
78 ///
79 /// Returns `None` for a counter-less producer (emit an empty SYNC), or
80 /// `Some(count)` cycling `1..=overflow`.
81 pub fn advance(&mut self) -> Option<u8> {
82 if !self.is_counting() {
83 return None;
84 }
85 self.value = if self.value >= self.overflow {
86 1
87 } else {
88 self.value + 1
89 };
90 Some(self.value)
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
99 fn cob_id_is_default() {
100 assert_eq!(SYNC_COB_ID, 0x080);
101 }
102
103 #[test]
104 fn counter_overflow_validation() {
105 assert!(is_valid_counter_overflow(0));
106 assert!(!is_valid_counter_overflow(1)); // reserved
107 assert!(is_valid_counter_overflow(2));
108 assert!(is_valid_counter_overflow(240));
109 assert!(!is_valid_counter_overflow(241));
110 }
111
112 #[test]
113 fn decode_empty_and_counting_frames() {
114 assert_eq!(decode(&[]).unwrap(), None);
115 assert_eq!(decode(&[7]).unwrap(), Some(7));
116 assert_eq!(decode(&[1, 2]), Err(Error::BadLength));
117 }
118
119 #[test]
120 fn encode_counter_frame() {
121 assert_eq!(encode_counter(5), [5]);
122 }
123
124 #[test]
125 fn counter_disabled_yields_none() {
126 let mut c = SyncCounter::new(0).unwrap();
127 assert!(!c.is_counting());
128 assert_eq!(c.advance(), None);
129 }
130
131 #[test]
132 fn counter_cycles_and_wraps() {
133 let mut c = SyncCounter::new(3).unwrap();
134 assert_eq!(c.advance(), Some(1));
135 assert_eq!(c.advance(), Some(2));
136 assert_eq!(c.advance(), Some(3));
137 assert_eq!(c.advance(), Some(1)); // wraps back to 1
138 }
139
140 #[test]
141 fn new_rejects_reserved_overflow() {
142 assert_eq!(SyncCounter::new(1).err(), Some(Error::InvalidSyncCounter));
143 assert_eq!(SyncCounter::new(241).err(), Some(Error::InvalidSyncCounter));
144 }
145}