Skip to main content

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}