use crate::utils::ffi;
use std::collections::HashSet;
use std::sync::{Arc, Mutex, Once};
type PdoCyclicList = Arc<Mutex<Vec<Box<dyn Fn(u16) + Send + 'static>>>>;
type SlaveStateChangeList = Arc<Mutex<Vec<Box<dyn Fn(u16, u16, i32, i32) + Send + 'static>>>>;
type EmergencyList = Arc<Mutex<Vec<Box<dyn Fn(u16, u16, u16, u16, u8, u16, u16) + Send + 'static>>>>;
type SlaveDiscoveryList = Arc<Mutex<Vec<Box<dyn Fn(u16, u16, bool) + Send + 'static>>>>;
type PdoFrameLossList = Arc<Mutex<Vec<Box<dyn Fn(u16, u8, u32, u32) + Send + 'static>>>>;
type DcSyncLostList = Arc<Mutex<Vec<Box<dyn Fn(u16, u16, i32) + Send + 'static>>>>;
type RedundancyChangedList = Arc<Mutex<Vec<Box<dyn Fn(u16, i32, i32) + Send + 'static>>>>;
type InputChangedList = Arc<Mutex<Vec<Box<dyn Fn(u16, *const u8, u16) + Send + 'static>>>>;
type PreOpReconfigList = Arc<Mutex<Vec<Box<dyn Fn(u16, u16) + Send + 'static>>>>;
type SlaveIdentityMismatchList = Arc<Mutex<Vec<Box<dyn Fn(SlaveIdentityMismatch) + Send + 'static>>>>;
type SlavePortLinkChangedList = Arc<Mutex<Vec<Box<dyn Fn(u16, u16, u8, bool) + Send + 'static>>>>;
#[derive(Debug, Clone, Copy)]
pub struct SlaveIdentityMismatch {
pub master_index: u16,
pub slave_index: u16,
pub expected_vendor: u32,
pub expected_product: u32,
pub expected_revision: u32,
pub actual_vendor: u32,
pub actual_product: u32,
pub actual_revision: u32,
}
fn pdo_cyclic_sync() -> &'static PdoCyclicList {
use std::sync::OnceLock;
static INSTANCE: OnceLock<PdoCyclicList> = OnceLock::new();
INSTANCE.get_or_init(|| Arc::new(Mutex::new(Vec::new())))
}
fn pdo_cyclic_async() -> &'static PdoCyclicList {
use std::sync::OnceLock;
static INSTANCE: OnceLock<PdoCyclicList> = OnceLock::new();
INSTANCE.get_or_init(|| Arc::new(Mutex::new(Vec::new())))
}
fn slave_state_change_list() -> &'static SlaveStateChangeList {
use std::sync::OnceLock;
static INSTANCE: OnceLock<SlaveStateChangeList> = OnceLock::new();
INSTANCE.get_or_init(|| Arc::new(Mutex::new(Vec::new())))
}
fn emergency_list() -> &'static EmergencyList {
use std::sync::OnceLock;
static INSTANCE: OnceLock<EmergencyList> = OnceLock::new();
INSTANCE.get_or_init(|| Arc::new(Mutex::new(Vec::new())))
}
fn slave_discovery_list() -> &'static SlaveDiscoveryList {
use std::sync::OnceLock;
static INSTANCE: OnceLock<SlaveDiscoveryList> = OnceLock::new();
INSTANCE.get_or_init(|| Arc::new(Mutex::new(Vec::new())))
}
fn pdo_frame_loss_list() -> &'static PdoFrameLossList {
use std::sync::OnceLock;
static INSTANCE: OnceLock<PdoFrameLossList> = OnceLock::new();
INSTANCE.get_or_init(|| Arc::new(Mutex::new(Vec::new())))
}
fn dc_sync_lost_list() -> &'static DcSyncLostList {
use std::sync::OnceLock;
static INSTANCE: OnceLock<DcSyncLostList> = OnceLock::new();
INSTANCE.get_or_init(|| Arc::new(Mutex::new(Vec::new())))
}
fn redundancy_changed_list() -> &'static RedundancyChangedList {
use std::sync::OnceLock;
static INSTANCE: OnceLock<RedundancyChangedList> = OnceLock::new();
INSTANCE.get_or_init(|| Arc::new(Mutex::new(Vec::new())))
}
fn input_changed_list() -> &'static InputChangedList {
use std::sync::OnceLock;
static INSTANCE: OnceLock<InputChangedList> = OnceLock::new();
INSTANCE.get_or_init(|| Arc::new(Mutex::new(Vec::new())))
}
fn preop_reconfig_list() -> &'static PreOpReconfigList {
use std::sync::OnceLock;
static INSTANCE: OnceLock<PreOpReconfigList> = OnceLock::new();
INSTANCE.get_or_init(|| Arc::new(Mutex::new(Vec::new())))
}
fn slave_identity_mismatch_list() -> &'static SlaveIdentityMismatchList {
use std::sync::OnceLock;
static INSTANCE: OnceLock<SlaveIdentityMismatchList> = OnceLock::new();
INSTANCE.get_or_init(|| Arc::new(Mutex::new(Vec::new())))
}
fn slave_port_link_changed_list() -> &'static SlavePortLinkChangedList {
use std::sync::OnceLock;
static INSTANCE: OnceLock<SlavePortLinkChangedList> = OnceLock::new();
INSTANCE.get_or_init(|| Arc::new(Mutex::new(Vec::new())))
}
fn slave_state_change_async_list() -> &'static SlaveStateChangeList {
use std::sync::OnceLock;
static INSTANCE: OnceLock<SlaveStateChangeList> = OnceLock::new();
INSTANCE.get_or_init(|| Arc::new(Mutex::new(Vec::new())))
}
fn slave_discovery_async_list() -> &'static SlaveDiscoveryList {
use std::sync::OnceLock;
static INSTANCE: OnceLock<SlaveDiscoveryList> = OnceLock::new();
INSTANCE.get_or_init(|| Arc::new(Mutex::new(Vec::new())))
}
fn offline_slaves() -> &'static Mutex<HashSet<u16>> {
use std::sync::OnceLock;
static INSTANCE: OnceLock<Mutex<HashSet<u16>>> = OnceLock::new();
INSTANCE.get_or_init(|| Mutex::new(HashSet::new()))
}
static CALLBACKS_INIT: Once = Once::new();
pub fn initialize_callbacks() {
CALLBACKS_INIT.call_once(|| {
unsafe {
ffi::RegisterProcessDataCyclicCallbackSync(on_pdo_cyclic_sync);
ffi::RegisterProcessDataCyclicCallbackAsync(on_pdo_cyclic_async);
ffi::RegisterSlaveStateChangeCallbackSync(on_slave_state_change);
ffi::RegisterSlaveStateChangeCallbackAsync(on_slave_state_change_async);
ffi::RegisterEmergencyEventCallback(on_emergency);
ffi::RegisterSlaveDiscoveryCallbackSync(on_slave_discovery);
ffi::RegisterSlaveDiscoveryCallbackAsync(on_slave_discovery_async);
ffi::RegisterPDOFrameLossCallback(on_pdo_frame_loss);
ffi::SetDCSyncLostCallback(on_dc_sync_lost);
ffi::RegisterRedundancyModeChangedCallback(on_redundancy_changed);
ffi::RegisterInputDataChangedCallback(on_input_changed);
ffi::RegisterSlavePreOpReconfigCallback(on_preop_reconfig);
ffi::RegisterSlaveIdentityMismatchCallback(on_slave_identity_mismatch);
ffi::RegisterSlavePortLinkChangedCallback(on_slave_port_link_changed);
}
});
}
extern "C" fn on_pdo_cyclic_sync(master_index: u16) {
if let Ok(list) = pdo_cyclic_sync().lock() {
for cb in list.iter() {
cb(master_index);
}
}
}
extern "C" fn on_pdo_cyclic_async(master_index: u16) {
if let Ok(list) = pdo_cyclic_async().lock() {
for cb in list.iter() {
cb(master_index);
}
}
}
extern "C" fn on_slave_state_change(master_index: u16, slave_index: u16, old_state: i32, new_state: i32) {
if let Ok(list) = slave_state_change_list().lock() {
for cb in list.iter() {
cb(master_index, slave_index, old_state, new_state);
}
}
}
extern "C" fn on_slave_state_change_async(master_index: u16, slave_index: u16, old_state: i32, new_state: i32) {
if let Ok(list) = slave_state_change_async_list().lock() {
for cb in list.iter() {
cb(master_index, slave_index, old_state, new_state);
}
}
}
extern "C" fn on_emergency(
master_index: u16, slave_index: u16,
error_code: u16, error_reg: u16, b1: u8, w1: u16, w2: u16,
) {
if let Ok(list) = emergency_list().lock() {
for cb in list.iter() {
cb(master_index, slave_index, error_code, error_reg, b1, w1, w2);
}
}
}
extern "C" fn on_slave_discovery(master_index: u16, slave_index: u16, is_found: i32) {
let found = is_found != 0;
if let Ok(mut set) = offline_slaves().lock() {
if found {
set.remove(&slave_index);
} else {
set.insert(slave_index);
}
}
if let Ok(list) = slave_discovery_list().lock() {
for cb in list.iter() {
cb(master_index, slave_index, found);
}
}
}
extern "C" fn on_slave_discovery_async(master_index: u16, slave_index: u16, is_found: i32) {
let found = is_found != 0;
if let Ok(list) = slave_discovery_async_list().lock() {
for cb in list.iter() {
cb(master_index, slave_index, found);
}
}
}
extern "C" fn on_pdo_frame_loss(master_index: u16, group: u8, consecutive: u32, total: u32) {
if let Ok(list) = pdo_frame_loss_list().lock() {
for cb in list.iter() {
cb(master_index, group, consecutive, total);
}
}
}
extern "C" fn on_dc_sync_lost(master_index: u16, slave_index: u16, diff_ns: i32) {
if let Ok(list) = dc_sync_lost_list().lock() {
for cb in list.iter() {
cb(master_index, slave_index, diff_ns);
}
}
}
extern "C" fn on_redundancy_changed(master_index: u16, old_mode: i32, new_mode: i32) {
if let Ok(list) = redundancy_changed_list().lock() {
for cb in list.iter() {
cb(master_index, old_mode, new_mode);
}
}
}
extern "C" fn on_input_changed(master_index: u16, changed_slave_bits: *const u8, changed_count: u16) {
if let Ok(list) = input_changed_list().lock() {
for cb in list.iter() {
cb(master_index, changed_slave_bits, changed_count);
}
}
}
extern "C" fn on_preop_reconfig(master_index: u16, slave_index: u16) {
if let Ok(list) = preop_reconfig_list().lock() {
for cb in list.iter() {
cb(master_index, slave_index);
}
}
}
extern "C" fn on_slave_identity_mismatch(
master_index: u16,
slave_index: u16,
expected_vendor: u32,
expected_product: u32,
expected_revision: u32,
actual_vendor: u32,
actual_product: u32,
actual_revision: u32,
) {
let args = SlaveIdentityMismatch {
master_index,
slave_index,
expected_vendor,
expected_product,
expected_revision,
actual_vendor,
actual_product,
actual_revision,
};
if let Ok(list) = slave_identity_mismatch_list().lock() {
for cb in list.iter() {
cb(args);
}
}
}
extern "C" fn on_slave_port_link_changed(master_index: u16, slave_index: u16, port: u8, is_up: i32) {
let up = is_up != 0;
if let Ok(list) = slave_port_link_changed_list().lock() {
for cb in list.iter() {
cb(master_index, slave_index, port, up);
}
}
}
pub struct MasterEvents {
_master_index: u16,
}
impl MasterEvents {
pub(crate) fn new(master_index: u16) -> Self {
initialize_callbacks();
Self { _master_index: master_index }
}
pub fn on_pdo_cyclic_sync<F>(&self, callback: F)
where
F: Fn(u16) + Send + 'static,
{
if let Ok(mut list) = pdo_cyclic_sync().lock() {
list.push(Box::new(callback));
}
}
pub fn on_pdo_cyclic_async<F>(&self, callback: F)
where
F: Fn(u16) + Send + 'static,
{
if let Ok(mut list) = pdo_cyclic_async().lock() {
list.push(Box::new(callback));
}
}
pub fn on_slave_state_changed<F>(&self, callback: F)
where
F: Fn(u16, u16, i32, i32) + Send + 'static,
{
if let Ok(mut list) = slave_state_change_list().lock() {
list.push(Box::new(callback));
}
}
pub fn on_slave_state_changed_async<F>(&self, callback: F)
where
F: Fn(u16, u16, i32, i32) + Send + 'static,
{
if let Ok(mut list) = slave_state_change_async_list().lock() {
list.push(Box::new(callback));
}
}
pub fn on_emergency<F>(&self, callback: F)
where
F: Fn(u16, u16, u16, u16, u8, u16, u16) + Send + 'static,
{
if let Ok(mut list) = emergency_list().lock() {
list.push(Box::new(callback));
}
}
pub fn on_slave_offline<F>(&self, callback: F)
where
F: Fn(u16) + Send + 'static,
{
if let Ok(mut list) = slave_discovery_list().lock() {
let cb = Box::new(move |_master: u16, slave: u16, found: bool| {
if !found {
callback(slave);
}
});
list.push(cb);
}
}
pub fn on_slave_online<F>(&self, callback: F)
where
F: Fn(u16) + Send + 'static,
{
if let Ok(mut list) = slave_discovery_list().lock() {
let cb = Box::new(move |_master: u16, slave: u16, found: bool| {
if found {
callback(slave);
}
});
list.push(cb);
}
}
pub fn on_slave_discovery<F>(&self, callback: F)
where
F: Fn(u16, u16, bool) + Send + 'static,
{
if let Ok(mut list) = slave_discovery_list().lock() {
list.push(Box::new(callback));
}
}
pub fn on_slave_discovery_async<F>(&self, callback: F)
where
F: Fn(u16, u16, bool) + Send + 'static,
{
if let Ok(mut list) = slave_discovery_async_list().lock() {
list.push(Box::new(callback));
}
}
pub fn on_pdo_frame_loss<F>(&self, callback: F)
where
F: Fn(u16, u8, u32, u32) + Send + 'static,
{
if let Ok(mut list) = pdo_frame_loss_list().lock() {
list.push(Box::new(callback));
}
}
pub fn on_dc_sync_lost<F>(&self, callback: F)
where
F: Fn(u16, u16, i32) + Send + 'static,
{
if let Ok(mut list) = dc_sync_lost_list().lock() {
list.push(Box::new(callback));
}
}
pub fn on_redundancy_mode_changed<F>(&self, callback: F)
where
F: Fn(u16, i32, i32) + Send + 'static,
{
if let Ok(mut list) = redundancy_changed_list().lock() {
list.push(Box::new(callback));
}
}
pub fn on_input_data_changed_raw<F>(&self, callback: F)
where
F: Fn(u16, *const u8, u16) + Send + 'static,
{
if let Ok(mut list) = input_changed_list().lock() {
list.push(Box::new(callback));
}
}
pub fn on_input_data_changed<F>(&self, callback: F)
where
F: Fn(u16, Vec<u16>) + Send + 'static,
{
if let Ok(mut list) = input_changed_list().lock() {
let cb = Box::new(move |master: u16, bits_ptr: *const u8, count: u16| {
if bits_ptr.is_null() || count == 0 {
return;
}
let mut slaves = Vec::with_capacity(count as usize);
let mut found: u16 = 0;
let max_bytes = ((count as usize) + 7) / 8;
let mut byte_idx: usize = 0;
while found < count && byte_idx < max_bytes {
let b = unsafe { *bits_ptr.add(byte_idx) };
if b != 0 {
for bit_idx in 0..8u16 {
if found >= count {
break;
}
if (b & (1 << bit_idx)) != 0 {
slaves.push(byte_idx as u16 * 8 + bit_idx);
found += 1;
}
}
}
byte_idx += 1;
}
callback(master, slaves);
});
list.push(cb);
}
}
pub fn on_preop_reconfig<F>(&self, callback: F)
where
F: Fn(u16, u16) + Send + 'static,
{
if let Ok(mut list) = preop_reconfig_list().lock() {
list.push(Box::new(callback));
}
}
pub fn on_slave_identity_mismatch<F>(&self, callback: F)
where
F: Fn(SlaveIdentityMismatch) + Send + 'static,
{
if let Ok(mut list) = slave_identity_mismatch_list().lock() {
list.push(Box::new(callback));
}
}
pub fn on_slave_port_link_changed<F>(&self, callback: F)
where
F: Fn(u16, u16, u8, bool) + Send + 'static,
{
if let Ok(mut list) = slave_port_link_changed_list().lock() {
list.push(Box::new(callback));
}
}
pub fn clear_all(&self) {
if let Ok(mut list) = pdo_cyclic_sync().lock() { list.clear(); }
if let Ok(mut list) = pdo_cyclic_async().lock() { list.clear(); }
if let Ok(mut list) = slave_state_change_list().lock() { list.clear(); }
if let Ok(mut list) = slave_state_change_async_list().lock() { list.clear(); }
if let Ok(mut list) = emergency_list().lock() { list.clear(); }
if let Ok(mut list) = slave_discovery_list().lock() { list.clear(); }
if let Ok(mut list) = slave_discovery_async_list().lock() { list.clear(); }
if let Ok(mut list) = pdo_frame_loss_list().lock() { list.clear(); }
if let Ok(mut list) = dc_sync_lost_list().lock() { list.clear(); }
if let Ok(mut list) = redundancy_changed_list().lock() { list.clear(); }
if let Ok(mut list) = input_changed_list().lock() { list.clear(); }
if let Ok(mut list) = preop_reconfig_list().lock() { list.clear(); }
if let Ok(mut list) = slave_identity_mismatch_list().lock() { list.clear(); }
if let Ok(mut list) = slave_port_link_changed_list().lock() { list.clear(); }
if let Ok(mut set) = offline_slaves().lock() { set.clear(); }
}
pub fn is_slave_offline(&self, slave_index: u16) -> bool {
if let Ok(set) = offline_slaves().lock() {
set.contains(&slave_index)
} else {
false
}
}
pub fn offline_slaves(&self) -> Vec<u16> {
if let Ok(set) = offline_slaves().lock() {
set.iter().copied().collect()
} else {
Vec::new()
}
}
pub fn offline_slave_count(&self) -> usize {
if let Ok(set) = offline_slaves().lock() {
set.len()
} else {
0
}
}
}
pub struct SlaveEvents {
_master_index: u16,
slave_index: u16,
}
impl SlaveEvents {
pub(crate) fn new(master_index: u16, slave_index: u16) -> Self {
initialize_callbacks();
Self { _master_index: master_index, slave_index }
}
pub fn on_state_changed<F>(&self, callback: F)
where
F: Fn(i32, i32) + Send + 'static,
{
let target_slave = self.slave_index;
if let Ok(mut list) = slave_state_change_list().lock() {
list.push(Box::new(move |_master, slave, old_state, new_state| {
if slave == target_slave {
callback(old_state, new_state);
}
}));
}
}
pub fn on_emergency<F>(&self, callback: F)
where
F: Fn(u16, u16, u8, u16, u16) + Send + 'static,
{
let target_slave = self.slave_index;
if let Ok(mut list) = emergency_list().lock() {
list.push(Box::new(move |_master, slave, ec, er, b1, w1, w2| {
if slave == target_slave {
callback(ec, er, b1, w1, w2);
}
}));
}
}
pub fn on_offline<F>(&self, callback: F)
where
F: Fn() + Send + 'static,
{
let target_slave = self.slave_index;
if let Ok(mut list) = slave_discovery_list().lock() {
list.push(Box::new(move |_master, slave, found| {
if slave == target_slave && !found {
callback();
}
}));
}
}
pub fn on_online<F>(&self, callback: F)
where
F: Fn() + Send + 'static,
{
let target_slave = self.slave_index;
if let Ok(mut list) = slave_discovery_list().lock() {
list.push(Box::new(move |_master, slave, found| {
if slave == target_slave && found {
callback();
}
}));
}
}
pub fn on_dc_sync_lost<F>(&self, callback: F)
where
F: Fn(i32) + Send + 'static,
{
let target_slave = self.slave_index;
if let Ok(mut list) = dc_sync_lost_list().lock() {
list.push(Box::new(move |_master, slave, diff_ns| {
if slave == target_slave {
callback(diff_ns);
}
}));
}
}
pub fn clear_all(&self) {
if let Ok(mut list) = slave_state_change_list().lock() { list.clear(); }
if let Ok(mut list) = emergency_list().lock() { list.clear(); }
if let Ok(mut list) = slave_discovery_list().lock() { list.clear(); }
if let Ok(mut list) = dc_sync_lost_list().lock() { list.clear(); }
if let Ok(mut list) = input_changed_list().lock() { list.clear(); }
}
pub fn on_input_changed<F>(&self, callback: F)
where
F: Fn() + Send + 'static,
{
let target_slave = self.slave_index;
if let Ok(mut list) = input_changed_list().lock() {
list.push(Box::new(move |_master, bits_ptr, count| {
if bits_ptr.is_null() || count == 0 {
return;
}
let byte_idx = (target_slave / 8) as usize;
let max_bytes = ((count as usize) + 7) / 8;
if byte_idx >= max_bytes {
return;
}
let bit_idx = target_slave % 8;
let b = unsafe { *bits_ptr.add(byte_idx) };
if (b & (1 << bit_idx)) != 0 {
callback();
}
}));
}
}
}