use crate::master::core::EtherCATMaster;
use crate::data::types::{RingMode, SlaveErrorCounters};
use crate::data::structures::InternalDiagnostics;
#[repr(C)]
struct NativeSummary {
total_errors: u32,
error_rate_per_1000: f64,
worst_link_quality: i16,
worst_slave_index: u16,
redundancy_active: i32,
primary_port_ok: i32,
secondary_port_ok: i32,
primary_port_errors_window: u32,
secondary_port_errors_window: u32,
rt_frequency: u32,
cycle_time_us: u32,
packet_loss_percent: f64,
mailbox_avg_latency_us: f64,
mailbox_max_latency_us: f64,
avg_cycle_time_us: f64,
bus_avg_jitter_us: f64,
bus_max_jitter_us: f64,
bus_clean_max_jitter_us: f64,
bus_cycle_hz: u32,
bus_roundtrip_us: f64,
smi_count: u32,
smi_peak_us: f64,
topology_mode: u8,
_reserved: u8,
break_point_count: i16,
}
#[derive(Debug, Clone, Copy)]
pub struct BreakPointInfo {
pub slave_index: u16,
pub port: u8,
pub fault_type: u8,
}
impl BreakPointInfo {
pub fn is_link_down(&self) -> bool {
self.fault_type == 0
}
pub fn is_crc_fault(&self) -> bool {
self.fault_type == 1
}
}
impl std::fmt::Display for BreakPointInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.fault_type {
0 => write!(f, "从站{} P{} 断线", self.slave_index, self.port),
1 => write!(f, "从站{} P{} CRC故障", self.slave_index, self.port),
_ => write!(f, "从站{} P{} 未知故障({})", self.slave_index, self.port, self.fault_type),
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct PDOFrameLossStats {
pub total_lost: u32,
pub consecutive_lost: u32,
pub max_consecutive_lost: u32,
}
pub struct MasterPDODiagnostics<'a> {
master: &'a EtherCATMaster,
}
impl<'a> MasterPDODiagnostics<'a> {
pub fn frame_loss_stats(&self, group: u8) -> PDOFrameLossStats {
if group <= 7 {
let (total, consecutive, max_consecutive) = self.master.pdo_frame_loss_stats(group);
PDOFrameLossStats { total_lost: total, consecutive_lost: consecutive, max_consecutive_lost: max_consecutive }
} else {
self.all_groups_stats()
}
}
pub fn all_groups_stats(&self) -> PDOFrameLossStats {
let mut total_sum = 0u32;
let mut consecutive_max = 0u32;
let mut max_consecutive_max = 0u32;
for g in 0..8u8 {
let (total, consecutive, max_consecutive) = self.master.pdo_frame_loss_stats(g);
total_sum += total;
if consecutive > consecutive_max { consecutive_max = consecutive; }
if max_consecutive > max_consecutive_max { max_consecutive_max = max_consecutive; }
}
PDOFrameLossStats {
total_lost: total_sum,
consecutive_lost: consecutive_max,
max_consecutive_lost: max_consecutive_max,
}
}
pub fn total_lost(&self) -> u32 {
self.all_groups_stats().total_lost
}
pub fn consecutive_lost(&self) -> u32 {
self.all_groups_stats().consecutive_lost
}
}
#[derive(Debug, Clone)]
pub struct DiagnosticsSnapshot {
pub frequency: i32,
pub error_count: u32,
pub packet_loss_rate: f32,
pub late_frame_rate: f32,
pub avg_jitter_us: f64,
pub mailbox_latency_us: f64,
pub mailbox_latency_avg_us: f64,
pub cycle_time_us: i32,
pub wkc_actual: u16,
pub wkc_expected: u16,
pub bus_cycle_hz: u32,
pub bus_max_jitter_us: f64,
pub bus_avg_jitter_us: f64,
pub bus_roundtrip_us: f64,
pub bus_load_percent: f64,
pub smi_count: u32,
pub smi_peak_us: f64,
pub primary_port_ok: bool,
pub secondary_port_ok: bool,
pub redundancy_active: bool,
}
pub struct MasterDiagnosticsInfo<'a> {
master: &'a EtherCATMaster,
}
impl<'a> MasterDiagnosticsInfo<'a> {
pub(crate) fn new(master: &'a EtherCATMaster) -> Self {
Self { master }
}
pub fn enabled(&self) -> bool {
self.master.diagnostics_enabled()
}
pub fn set_enabled(&self, enable: bool) {
self.master.set_diagnostics_enabled(enable);
}
fn with_summary<T, F: FnOnce(&NativeSummary) -> T>(&self, default: T, f: F) -> T {
let ptr = self.master.summary_pointer() as *const NativeSummary;
if ptr.is_null() { return default; }
unsafe { f(&*ptr) }
}
fn with_diag<T, F: FnOnce(&InternalDiagnostics) -> T>(&self, default: T, f: F) -> T {
let ptr = self.master.diagnostics_pointer() as *const InternalDiagnostics;
if ptr.is_null() { return default; }
unsafe { f(&*ptr) }
}
pub fn packet_loss_rate(&self) -> f32 {
unsafe { crate::utils::ffi::GetPacketLossRate(self.master.index()) }
}
pub fn late_frame_rate(&self) -> f32 {
unsafe { crate::utils::ffi::GetLateFrameRate(self.master.index()) }
}
pub fn wc_deficit(&self) -> u16 {
unsafe { crate::utils::ffi::GetWcDeficit(self.master.index()) }
}
pub fn wc_state_seq(&self) -> u64 {
unsafe { crate::utils::ffi::GetWcStateSeq(self.master.index()) }
}
pub fn mapped_slave_count(&self) -> u16 {
unsafe { crate::utils::ffi::GetMappedSlaveCount(self.master.index()) }
}
pub fn wkc_actual_mirror(&self) -> u16 {
unsafe { crate::utils::ffi::GetWkcActualMirror(self.master.index()) }
}
pub fn wkc_expected_mirror(&self) -> u16 {
unsafe { crate::utils::ffi::GetWkcExpectedMirror(self.master.index()) }
}
pub fn hot_swap_rebuild(&self) -> i32 {
unsafe { crate::utils::ffi::HotSwapRebuild(self.master.index()) }
}
pub fn rt_cnt(&self) -> u32 {
self.with_summary(0, |s| s.rt_frequency)
}
pub fn cycle_time_span(&self) -> u32 {
self.with_diag(0, |d| d.cycle_time_span)
}
pub fn avg_jitter_us(&self) -> f64 {
self.with_summary(0.0, |s| s.bus_avg_jitter_us)
}
pub fn mailbox_latency_us(&self) -> f64 {
self.with_summary(0.0, |s| s.mailbox_max_latency_us)
}
pub fn mailbox_latency_avg_us(&self) -> f64 {
self.with_summary(0.0, |s| s.mailbox_avg_latency_us)
}
pub fn avg_cycle_time_us(&self) -> f64 {
self.with_summary(0.0, |s| s.avg_cycle_time_us)
}
pub fn wkc(&self) -> u16 {
self.with_diag(0, |d| d.wkc)
}
pub fn expected_wkc(&self) -> u16 {
self.with_diag(0, |d| d.expected_wkc)
}
pub fn error_cnt(&self) -> u32 {
self.with_summary(0, |s| s.total_errors)
}
pub fn bus_cycle_hz(&self) -> u32 {
self.with_summary(0, |s| s.bus_cycle_hz)
}
pub fn bus_max_jitter_us(&self) -> f64 {
self.with_summary(0.0, |s| s.bus_max_jitter_us)
}
pub fn bus_avg_jitter_us(&self) -> f64 {
self.with_summary(0.0, |s| s.bus_avg_jitter_us)
}
pub fn bus_roundtrip_us(&self) -> f64 {
self.with_summary(0.0, |s| s.bus_roundtrip_us)
}
pub fn bus_load_percent(&self) -> f64 {
let rtt = self.bus_roundtrip_us();
let cycle_us = self.cycle_time_span() as f64;
if cycle_us <= 0.0 || rtt <= 0.0 {
return 0.0;
}
let pct = rtt / cycle_us * 100.0;
if pct > 100.0 { 100.0 } else { pct }
}
pub fn bus_clean_max_jitter_us(&self) -> f64 {
self.with_summary(0.0, |s| s.bus_clean_max_jitter_us)
}
pub fn smi_count(&self) -> u32 {
self.with_summary(0, |s| s.smi_count)
}
pub fn smi_peak_us(&self) -> f64 {
self.with_summary(0.0, |s| s.smi_peak_us)
}
pub fn primary_port_ok(&self) -> bool {
self.with_summary(false, |s| s.primary_port_ok != 0)
}
pub fn secondary_port_ok(&self) -> bool {
self.with_summary(false, |s| s.secondary_port_ok != 0)
}
pub fn primary_port_errors(&self) -> u32 {
self.with_summary(0, |s| s.primary_port_errors_window)
}
pub fn secondary_port_errors(&self) -> u32 {
self.with_summary(0, |s| s.secondary_port_errors_window)
}
pub fn redundancy_active(&self) -> bool {
self.with_summary(false, |s| s.redundancy_active != 0)
}
pub fn ring_mode(&self) -> RingMode {
RingMode::from_value(self.master.ring_mode())
}
pub fn worst_slave_index(&self) -> u16 {
self.with_summary(0, |s| s.worst_slave_index)
}
pub fn worst_link_quality(&self) -> i16 {
self.with_summary(100, |s| s.worst_link_quality)
}
pub fn topology_description(&self) -> &'static str {
let mode = self.with_summary(0u8, |s| s.topology_mode);
match mode {
0 => "线性",
1 => "环形",
2 => "环+分支",
_ => "未知",
}
}
pub fn break_point(&self) -> Option<BreakPointInfo> {
let results = self.master.break_points(1);
results.first().map(|&(slave, port, fault_type)| {
BreakPointInfo { slave_index: slave, port, fault_type }
})
}
pub fn all_break_points(&self) -> Vec<BreakPointInfo> {
self.master.break_points(64)
.into_iter()
.map(|(slave, port, fault_type)| BreakPointInfo { slave_index: slave, port, fault_type })
.collect()
}
pub fn timing_mode(&self) -> &'static str {
match self.master.timing_mode() {
1 => "硬件定时器",
2 => "软件定时器",
3 => "降级",
4 => "RT就绪",
_ => "未知",
}
}
pub fn sync_window_threshold(&self) -> i32 {
self.master.sync_window_threshold()
}
pub fn set_sync_window_threshold(&self, threshold_ns: i32) {
self.master.set_sync_window_threshold(threshold_ns);
}
pub fn reset(&self) {
self.master.reset_diagnostics();
for g in 0..8u8 {
self.master.reset_pdo_frame_loss_stats(g);
}
}
pub fn read_slave_error_counters(&self, slave_index: i32) -> SlaveErrorCounters {
let mut counters = SlaveErrorCounters {
slave_index,
..Default::default()
};
if let Some((rx_error, invalid_frame, lost_link)) =
self.master.read_slave_port_error_counters(slave_index as u16)
{
counters.port0_crc_errors = rx_error[0] as u32;
counters.port1_crc_errors = rx_error[1] as u32;
counters.port2_crc_errors = rx_error[2] as u32;
counters.port3_crc_errors = rx_error[3] as u32;
counters.frame_errors = invalid_frame.iter().map(|&x| x as u32).sum();
counters.lost_frames = lost_link.iter().map(|&x| x as u32).sum();
}
counters
}
pub fn pdo(&self) -> MasterPDODiagnostics<'_> {
MasterPDODiagnostics { master: self.master }
}
pub fn get_snapshot(&self) -> DiagnosticsSnapshot {
DiagnosticsSnapshot {
frequency: self.rt_cnt() as i32,
error_count: self.error_cnt(),
packet_loss_rate: self.packet_loss_rate(),
late_frame_rate: self.late_frame_rate(),
avg_jitter_us: self.avg_jitter_us(),
mailbox_latency_us: self.mailbox_latency_us(),
mailbox_latency_avg_us: self.mailbox_latency_avg_us(),
cycle_time_us: self.cycle_time_span() as i32,
wkc_actual: self.wkc(),
wkc_expected: self.expected_wkc(),
bus_cycle_hz: self.bus_cycle_hz(),
bus_max_jitter_us: self.bus_max_jitter_us(),
bus_avg_jitter_us: self.bus_avg_jitter_us(),
bus_roundtrip_us: self.bus_roundtrip_us(),
bus_load_percent: self.bus_load_percent(),
smi_count: self.smi_count(),
smi_peak_us: self.smi_peak_us(),
primary_port_ok: self.primary_port_ok(),
secondary_port_ok: self.secondary_port_ok(),
redundancy_active: self.redundancy_active(),
}
}
}
impl<'a> std::fmt::Display for MasterDiagnosticsInfo<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Lost: {:.2}%, RTcnt: {}, Jitter: {:.2}us",
self.packet_loss_rate() * 100.0, self.rt_cnt(), self.avg_jitter_us())
}
}