pub const SYNC_COB_ID: u16 = 0x80;
pub const SYNC_COUNTER_MAX: u8 = 240;
#[derive(Debug, Clone)]
pub struct SyncProducer {
cob_id: u16,
counter: u8,
counter_overflow: u8,
total_syncs: u64,
enabled: bool,
}
impl SyncProducer {
pub fn new(cob_id: u16) -> Self {
Self {
cob_id,
counter: 0,
counter_overflow: 0,
total_syncs: 0,
enabled: true,
}
}
pub fn with_counter(cob_id: u16, overflow: u8) -> Self {
let overflow = overflow.clamp(1, SYNC_COUNTER_MAX);
Self {
cob_id,
counter: 1,
counter_overflow: overflow,
total_syncs: 0,
enabled: true,
}
}
pub fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
pub fn cob_id(&self) -> u16 {
self.cob_id
}
pub fn counter(&self) -> u8 {
self.counter
}
pub fn total_syncs(&self) -> u64 {
self.total_syncs
}
pub fn generate(&mut self) -> Option<[u8; 8]> {
if !self.enabled {
return None;
}
self.total_syncs += 1;
let mut frame = [0u8; 8];
if self.counter_overflow > 0 {
frame[0] = self.counter;
self.counter += 1;
if self.counter > self.counter_overflow {
self.counter = 1;
}
}
Some(frame)
}
pub fn reset_counter(&mut self) {
self.counter = if self.counter_overflow > 0 { 1 } else { 0 };
}
pub fn set_counter_overflow(&mut self, overflow: u8) {
if overflow == 0 {
self.counter_overflow = 0;
self.counter = 0;
} else {
self.counter_overflow = overflow.clamp(1, SYNC_COUNTER_MAX);
self.counter = 1;
}
}
}
impl Default for SyncProducer {
fn default() -> Self {
Self::new(SYNC_COB_ID)
}
}
#[derive(Clone, Copy)]
struct SyncCallback {
period: u8,
callback: fn(u8),
}
pub struct SyncConsumer<const CALLBACKS: usize = 8> {
cob_id: u16,
counter: u8,
counter_overflow: u8,
total_received: u64,
callbacks: [Option<SyncCallback>; CALLBACKS],
n_callbacks: usize,
last_counter: u8,
overflow_count: u32,
}
impl<const CALLBACKS: usize> SyncConsumer<CALLBACKS> {
pub fn new(cob_id: u16) -> Self {
Self {
cob_id,
counter: 0,
counter_overflow: 0,
total_received: 0,
callbacks: [None; CALLBACKS],
n_callbacks: 0,
last_counter: 0,
overflow_count: 0,
}
}
pub fn set_counter_overflow(&mut self, overflow: u8) {
self.counter_overflow = overflow;
}
pub fn register_callback(&mut self, callback: fn(u8), period: u8) -> bool {
if self.n_callbacks >= CALLBACKS {
return false;
}
self.callbacks[self.n_callbacks] = Some(SyncCallback { period, callback });
self.n_callbacks += 1;
true
}
pub fn on_sync(&mut self, frame_data: &[u8; 8]) -> bool {
self.total_received += 1;
self.counter += 1;
let recv_counter = frame_data[0];
if self.counter_overflow > 0 && recv_counter < self.last_counter {
self.overflow_count += 1;
}
self.last_counter = recv_counter;
for slot in self.callbacks[..self.n_callbacks].iter().flatten() {
let fire = if slot.period == 0 {
true
} else {
self.total_received % (slot.period as u64) == 0
};
if fire {
(slot.callback)(recv_counter);
}
}
true
}
pub fn cob_id(&self) -> u16 {
self.cob_id
}
pub fn total_received(&self) -> u64 {
self.total_received
}
pub fn overflow_count(&self) -> u32 {
self.overflow_count
}
pub fn counter(&self) -> u8 {
self.counter
}
pub fn n_callbacks(&self) -> usize {
self.n_callbacks
}
}
impl<const CALLBACKS: usize> Default for SyncConsumer<CALLBACKS> {
fn default() -> Self {
Self::new(SYNC_COB_ID)
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::sync::atomic::{AtomicU32, Ordering};
static CALLBACK_COUNT: AtomicU32 = AtomicU32::new(0);
fn test_callback(_counter: u8) {
CALLBACK_COUNT.fetch_add(1, Ordering::Relaxed);
}
#[test]
fn test_sync_producer_generate() {
let mut prod = SyncProducer::new(SYNC_COB_ID);
let frame = prod.generate().unwrap();
assert_eq!(frame, [0u8; 8]); assert_eq!(prod.total_syncs(), 1);
}
#[test]
fn test_sync_producer_counter_mode() {
let mut prod = SyncProducer::with_counter(SYNC_COB_ID, 3);
let f1 = prod.generate().unwrap();
assert_eq!(f1[0], 1);
let f2 = prod.generate().unwrap();
assert_eq!(f2[0], 2);
let f3 = prod.generate().unwrap();
assert_eq!(f3[0], 3);
let f4 = prod.generate().unwrap();
assert_eq!(f4[0], 1); }
#[test]
fn test_sync_producer_disabled() {
let mut prod = SyncProducer::new(SYNC_COB_ID);
prod.set_enabled(false);
assert!(prod.generate().is_none());
}
#[test]
fn test_sync_consumer_callback() {
CALLBACK_COUNT.store(0, Ordering::Relaxed);
let mut cons = SyncConsumer::<4>::new(SYNC_COB_ID);
cons.register_callback(test_callback, 0);
let frame = [0u8; 8];
cons.on_sync(&frame);
cons.on_sync(&frame);
assert_eq!(CALLBACK_COUNT.load(Ordering::Relaxed), 2);
}
#[test]
fn test_sync_consumer_period_callback() {
CALLBACK_COUNT.store(0, Ordering::Relaxed);
let mut cons = SyncConsumer::<4>::new(SYNC_COB_ID);
cons.register_callback(test_callback, 2);
let frame = [0u8; 8];
for _ in 0..6 {
cons.on_sync(&frame);
}
assert_eq!(CALLBACK_COUNT.load(Ordering::Relaxed), 3);
}
#[test]
fn test_sync_counter_overflow_detection() {
let mut cons = SyncConsumer::<4>::new(SYNC_COB_ID);
cons.set_counter_overflow(3);
cons.on_sync(&[1, 0, 0, 0, 0, 0, 0, 0]);
cons.on_sync(&[2, 0, 0, 0, 0, 0, 0, 0]);
cons.on_sync(&[3, 0, 0, 0, 0, 0, 0, 0]);
cons.on_sync(&[1, 0, 0, 0, 0, 0, 0, 0]); assert_eq!(cons.overflow_count(), 1);
}
#[test]
fn test_producer_consumer_roundtrip() {
let mut prod = SyncProducer::with_counter(SYNC_COB_ID, 5);
let mut cons = SyncConsumer::<4>::new(SYNC_COB_ID);
cons.set_counter_overflow(5);
for i in 1..=5 {
let frame = prod.generate().unwrap();
assert_eq!(frame[0], i);
cons.on_sync(&frame);
}
let wrap_frame = prod.generate().unwrap();
assert_eq!(wrap_frame[0], 1);
assert_eq!(prod.total_syncs(), 6);
assert_eq!(cons.total_received(), 5);
}
}