跳到主要内容

主站诊断

通过 master.diagnostics_info() 获取 MasterDiagnosticsInfo 访问所有诊断、监控、统计功能。

配合事件使用

建议通过 事件 驱动异常处理,而非自行轮询。 直接读取诊断属性适用于 UI 显示等场景。

从站诊断

单个从站的状态诊断、链路质量请参考 从站诊断

功能概览

功能说明
通信与性能统计帧计数、丢包、抖动、PDO 丢帧、网口状态、拓扑
诊断快照一致性快照,适用于 UI 刷新、日志记录
DC 同步同步窗口阈值、DCSyncLost 事件
冗余状态冗余激活、故障点检测(断线 + CRC 故障定位)
诊断控制启停数据采集、重置统计

通信与性能统计

类别方法类型读写可停说明
帧计数rt_cnt()u32只读每秒帧数 (Hz),5 秒平均
error_cnt()u32只读每秒错误数,5 秒平均
packet_loss_rate()f32只读丢包率 (0.0~1.0) — TX vs RX 5s 滑窗, pipeline 在途不算丢
late_frame_rate()f32只读过慢帧率 (0.0~1.0) — idx 出 8 帧窗 stale, 不计入丢包
周期与抖动cycle_time_span()u32只读实际周期时间 (微秒),实时值
avg_jitter_us()f64只读最近 5 秒平均抖动 (微秒)
avg_cycle_time_us()f64只读平均周期时间 (微秒)
邮箱延迟mailbox_latency_us()f64只读邮箱收发延迟 - 最大 (微秒),最近 1 秒结算
mailbox_latency_avg_us()f64只读邮箱收发延迟 - 平均 (微秒),最近 1 秒结算
WKCwkc()u16只读不可停当前 WKC
expected_wkc()u16只读不可停期望 WKC
WKC 镜像wkc_actual_mirror()u16只读不可停总线实测工作计数器镜像(actual WKC)— 反映此刻哪些从站真的在响应。R1:如实值永不篡改。薄读零帧,实时
wkc_expected_mirror()u16只读不可停期望工作计数器镜像(expected WKC)— 配置期/进 OP 确定的固定真值。R1:永不动态下调迁就劣化总线。薄读零帧,实时
wc_deficit()u16只读不可停WKC 缺额 = expected − actual(>0 表示有映射从站此刻没贡献 WKC,疑似掉站/热插拔)。从站恢复后自动归 0。薄读零帧,实时
mapped_slave_count()u16只读不可停已映射(参与 WKC 计算)的从站数。薄读零帧,实时
wc_state_seq()u64只读不可停内核 WcState 缓存序列号(每次刷新自增)。判断 per-slave 镜像是否在两次读取间更新过。薄读零帧,实时
PDO 丢帧pdo().total_lost()u32只读不可停累计丢帧数(所有组合计)
pdo().consecutive_lost()u32只读不可停当前连续丢帧数(所有组中最大值)
pdo().frame_loss_stats(group)PDOFrameLossStats只读不可停指定组的 PDO 丢帧统计
从站异常worst_slave_index()u16只读不可停异常率最高的从站索引
worst_link_quality()i16只读不可停最差从站通信健康度 (%)
网口状态primary_port_ok()bool只读不可停主端口是否正常
secondary_port_ok()bool只读不可停副端口是否正常
primary_port_errors()u32只读不可停主端口最近 5 秒错误数
secondary_port_errors()u32只读不可停副端口最近 5 秒错误数
拓扑topology_description()&str只读不可停拓扑模式描述 ("线性" / "环形")
timing_mode()&str只读不可停定时模式描述("硬件定时器" / "RT就绪" / "降级" / "RT错误")。WDK 驱动必须就绪
计算公式
指标公式说明
rt_cnt采样周期帧数 / 窗口秒数滑动窗口平均帧频
error_cnt采样周期错误数 / 窗口秒数滑动窗口平均错误率
packet_loss_rate(TX-RX-pipeline) / TX5 秒滑窗, pipeline 在途不算丢
late_frame_rateLateDrop / TXidx 出 8 帧窗 stale, 不计入丢包

set_enabled(true)

PDOFrameLossStats 结构
字段类型说明
total_lostu32累计丢帧数
consecutive_lostu32当前连续丢帧数
max_consecutive_lostu32历史最大连续丢帧数

示例:

let diag = master.diagnostics_info();
diag.set_enabled(true); // 启用诊断数据采集

// 帧计数
println!("帧频: {} Hz", diag.rt_cnt());
println!("丢包率: {:.2}%", diag.packet_loss_rate() * 100.0);
println!("错误数: {}", diag.error_cnt());

// 周期与抖动
println!("周期时间: {} us", diag.cycle_time_span());
println!("平均抖动: {:.2}us", diag.avg_jitter_us());

// 邮箱收发延迟
println!("邮箱延迟: 平均 {:.2}us, 最大 {:.2}us",
diag.mailbox_latency_avg_us(), diag.mailbox_latency_us());

// PDO 丢帧 (所有组汇总)
let pdo = diag.pdo();
println!("PDO 丢帧: 累计={}, 连续={}", pdo.total_lost(), pdo.consecutive_lost());

// 按组查询丢帧 (0-7)
let group0_stats = pdo.frame_loss_stats(0); // 组0
let group1_stats = pdo.frame_loss_stats(1); // 组1
println!("组0丢帧: {}, 组1丢帧: {}",
group0_stats.total_lost, group1_stats.total_lost);

// 从站异常
println!("最差从站: #{} ({}%)", diag.worst_slave_index(), diag.worst_link_quality());

// 网口状态
println!("主端口: {}", if diag.primary_port_ok() { "正常" } else { "异常" });
println!("副端口: {}", if diag.secondary_port_ok() { "正常" } else { "未连接" });

// 拓扑信息
println!("拓扑: {}", diag.topology_description());
println!("定时: {}", diag.timing_mode());

实时性能统计

realtime_stats()

// EtherCATMaster 上的方法 (非 diagnostics_info())
pub fn realtime_stats(&self) -> Option<RealtimeStats>

一次性取回主站当前周期的实时性能指标 (当前周期时长 / 抖动 / 丢帧 / 破损帧 / 总线利用率)。返回 None 表示 native 调用失败 (性能监控未启动 / 主站未就绪)。

返回值:

pub struct RealtimeStats {
pub current_cycle_time_us: f64, // 当前周期时长 (微秒)
pub jitter_us: f64, // 周期抖动 (微秒)
pub lost_frames: u32, // 丢帧累计
pub corrupted_frames: u32, // 破损帧累计
pub bus_utilization: f64, // 总线利用率 (0.0 ~ 1.0)
}

示例:

if let Some(rt) = master.realtime_stats() {
println!("周期 {:.2}us, 抖动 {:.2}us, 总线利用率 {:.1}%",
rt.current_cycle_time_us, rt.jitter_us, rt.bus_utilization * 100.0);
if rt.corrupted_frames > 0 {
println!("⚠ 破损帧 {} 个", rt.corrupted_frames);
}
}
与 diagnostics_info() 的区别

realtime_stats()EtherCATMaster 直接提供的轻量一次性快照, 与 diagnostics_info() 的滑窗统计 (5 秒平均) 互补。realtime_stats() 反映"此刻"的周期值, 不需要 set_enabled(true)

WKC 镜像(掉站可观测)

wkc_actual_mirror() / wkc_expected_mirror() / wc_deficit()diagnostics_info() 上的方法,反映内核 per-slave WcState 诊断缓存的主站级聚合镜像,全部薄读零帧(不缓存、不需刷新——内核每周期、WKC 异常时立即维护,DLL 保证读到即此刻真实总线现实)。配合每个从站的 slave.wc_contributed() 可定位到具体掉了哪个从站。

R1 可观测性约定(不可违反):

  • wkc_expected_mirror() = 配置期 / 进 OP 时确定的固定真值,拓扑固定它就固定,概念上不可变,永不动态下调迁就劣化总线
  • wkc_actual_mirror() = 总线实测值,如实反映此刻哪些从站真的在响应,永不篡改
  • wc_deficit() = wkc_expected_mirror() − wkc_actual_mirror()> 0 不是 master 故障,而是有映射从站此刻没贡献 WKC(疑似掉站 / 热插拔恢复中);从站恢复后自动归 0,无需任何重置调用。

示例:

use darra_ethercat::WcContribution;

let diag = master.diagnostics_info();

// 主站级 WKC 镜像(薄读零帧,实时)
println!("WKC: {} / {} (映射从站 {} 个)",
diag.wkc_actual_mirror(), diag.wkc_expected_mirror(), diag.mapped_slave_count());

if diag.wc_deficit() > 0 {
println!("⚠ WKC 缺额 {} — 有从站没在响应,逐个从站定位:", diag.wc_deficit());
for i in 1..=master.slave_count() {
let slave = master.slave(i);
if slave.wc_contributed() == WcContribution::NotContributed {
println!(" 从站 {} 未贡献 WKC", i);
}
}
}

let seq = diag.wc_state_seq(); // 序列号判断镜像是否更新过(无需轮询帧)
per-slave WcState

单个从站对 WKC 的贡献状态请用 slave.wc_contributed()WcContribution 枚举)。wc_deficit() > 0 时遍历从站即可定位掉站点。

热插拔重建

hot_swap_rebuild()

pub fn hot_swap_rebuild(&self) -> i32

运行中一次性热插拔重建拓扑:任意状态下调用一次,在线重扫总线 + 重建拓扑图 + 重配 PDO + 恢复到运行态(OP)。适用于现场拔掉 / 换上一个模块后,不停总线地把网络重新带回运行态。它不是 build() 那种"释放全网从头扫"的重建——是面向热插拔的在线增量恢复。该方法在 diagnostics_info() 上。

返回值(错误码语义):

返回码名称含义与处理
0OK成功:从站全部回到 OP
-20BUSY另一操作正在进行——稍后重试
-21RESCAN_0重扫到 0 个从站——已停在 PreOp 安全态,检查物理链路 / 供电
-22SDO_ABORT重配被从站 SDO abort 拒绝——检查 PDO 映射 / Startup 参数
-23NO_OP部分从站未达 OP——已回滚停安全态,查各从站 AL Status Code
-24TIMEOUT重建超时
-25IDX_FULL帧索引池饱和
-1(通用失败)调用层异常(DLL 未导出等),不 panic 直接返通用失败码

行为 / 约束(R1 如实可观测):

  • OP 状态调用会短暂中断 PDO(内部降 PreOp 重配再升回 OP),调用方需容忍这一周期的过程数据空窗。
  • 失败时返错误码不掩盖——绝不靠篡改内部计数让结果"看起来对"。
  • 即使最终 0 个从站在 OP,PDO 循环也照常运行(周期不掉、帧不停),expected_wkc() 不动态下调迁就劣化总线。

示例:

let rc = master.diagnostics_info().hot_swap_rebuild();
match rc {
0 => println!("热插拔重建成功,从站已回到 OP"),
-20 => println!("有操作进行中,稍后重试"),
-21 => println!("重扫到 0 站,已停 PreOp 安全态——检查链路/供电"),
-23 => {
println!("部分从站未达 OP,已回滚安全态——逐个查 AL Status Code:");
for i in 1..=master.slave_count() {
let slave = master.slave(i);
if slave.error_code_raw() != 0 {
println!(" 从站 {}: AL=0x{:04X}", i, slave.error_code_raw());
}
}
}
other => println!("热插拔重建失败: {}", other),
}

诊断快照

get_snapshot()

pub fn get_snapshot(&self) -> DiagnosticsSnapshot

获取诊断数据的一致快照。将当前所有诊断指标复制到一个独立结构体中,避免多次读取共享内存时数据不一致。适用于 UI 刷新、日志记录等场景。

返回值:

pub struct DiagnosticsSnapshot {
pub frequency: i32, // 应用层帧频率 (Hz)
pub error_count: u32, // 累计错误数
pub packet_loss_rate: f32, // 丢包率 (0.0 ~ 1.0) — TX vs RX 5s 滑窗
pub late_frame_rate: f32, // 过慢帧率 (0.0 ~ 1.0) — idx 出 8 帧窗 stale, 不计丢
pub avg_jitter_us: f64, // 平均抖动 (微秒)
pub mailbox_latency_us: f64, // 邮箱收发延迟 - 最大 (微秒,1 秒结算)
pub mailbox_latency_avg_us: f64, // 邮箱收发延迟 - 平均 (微秒,1 秒结算)
pub cycle_time_us: i32, // 实际周期时间 (微秒)
pub wkc_actual: u16, // 当前工作计数器
pub wkc_expected: u16, // 期望工作计数器
pub bus_cycle_hz: u32, // 总线帧发送频率 (Hz, RT 内核层)
pub bus_max_jitter_us: f64, // 总线最大抖动 (微秒, RT 内核层)
pub bus_avg_jitter_us: f64, // 总线平均抖动 (微秒, RT 内核层)
pub bus_roundtrip_us: f64, // 报文往返延迟 (微秒)
pub bus_load_percent: f64, // 通讯负载 (%) — RTT/周期×100
pub smi_count: u32, // SMI 累计次数
pub smi_peak_us: f64, // SMI 峰值抖动 (微秒)
pub primary_port_ok: bool, // 主端口是否正常
pub secondary_port_ok: bool, // 副端口是否正常
pub redundancy_active: bool, // 冗余是否激活
}

示例:

let diag = master.diagnostics_info();
diag.set_enabled(true);

// 获取一致快照用于 UI 刷新
let snapshot = diag.get_snapshot();
println!("频率: {} Hz, 丢包: {:.2}%, 抖动: {:.2}us",
snapshot.frequency,
snapshot.packet_loss_rate * 100.0,
snapshot.avg_jitter_us);

if snapshot.wkc_actual != snapshot.wkc_expected {
println!("WKC 不匹配: {}/{}", snapshot.wkc_actual, snapshot.wkc_expected);
}

DC 同步

自动监控 (ETG.1500 5.13.3),每秒检查各从站时间偏差。超出 sync_window_threshold() 阈值时触发 on_dc_sync_lost 事件。

sync_window_threshold() / set_sync_window_threshold()

pub fn sync_window_threshold(&self) -> i32
pub fn set_sync_window_threshold(&self, ns: i32)

同步窗口阈值 (纳秒),默认 1000ns。超出阈值触发 DCSyncLost 事件。

单个从站

单个从站的同步状态请使用 slave.diagnostics().dc().is_in_sync()slave.diagnostics().dc().sync_time_difference()

冗余状态

redundancy_active()

pub fn redundancy_active(&self) -> bool

冗余是否激活(存在断线但网络仍正常运行)。

ring_mode()

pub fn ring_mode(&self) -> RingMode

环拓扑冗余运行模式。

相关结构:

#[repr(i32)]
pub enum RingMode {
Inactive = 0, // 未激活: 冗余监控未初始化
Dual = 1, // 双向冗余: 双端口发送, 正常工作
Degraded = 2, // 降级模式: secondary 链路不可用, 仅 primary 工作
}

break_point()

pub fn break_point(&self) -> Option<BreakPointInfo>

当前故障点(多个故障只返回第一个)。支持断线和 CRC 故障两种类型,恢复后自动清除,无故障返回 None

相关结构:

pub struct BreakPointInfo {
pub slave_index: u16, // 故障从站索引 (1-based)
pub port: u8, // 故障端口号 (0-3, 对应 P0-P3)
pub fault_type: u8, // 故障类型: 0=断线, 1=CRC 故障
}

impl BreakPointInfo {
pub fn is_link_down(&self) -> bool; // 是否为断线故障 (fault_type == 0)
pub fn is_crc_fault(&self) -> bool; // 是否为 CRC 故障 (fault_type == 1)
}

is_link_down() / is_crc_fault()方法(不是字段),需要带括号调用。

故障类型说明:

  • 断线 (fault_type=0) — DL Status 端口物理链路丢失,典型场景: 拔线、线缆断裂
  • CRC 故障 (fault_type=1) — 端口级 RxError + InvalidFrame 持续增长,典型场景: 接触不良、线缆老化

故障线缆段定位: 当相邻从站的对向端口同时报故障,说明连接线缆有问题。仅单侧报故障则定位到该端口连接器。

示例:

let diag = master.diagnostics_info();

// 环拓扑冗余模式
println!("冗余模式: {:?}", diag.ring_mode());

if diag.redundancy_active() {
println!("冗余已激活");
}

if let Some(bp) = diag.break_point() {
println!("故障: {}", bp); // "从站3 P1 断线" 或 "从站3 P1 CRC故障"
if bp.is_crc_fault() { // 方法调用, 带括号
println!("建议检查线缆/连接器");
}
}

诊断控制

标记为"是"的属性需先调用 set_enabled(true) 启动周期性采样,标记为"不可停"的功能始终活跃。

enabled() / set_enabled()

pub fn enabled(&self) -> bool
pub fn set_enabled(&self, enabled: bool)

诊断数据采集开关(默认关闭)。

reset()

pub fn reset(&self)

一次性重置所有诊断统计,包括:

  • 基础诊断统计(帧错误等)
  • PDO 丢帧统计
  • DC 同步窗口统计
  • 所有从站的端口错误计数器

AL 错误分类

对 AL Status Code 进行分类,帮助快速判断错误性质和处理策略。

classify_al_error()

pub fn classify_al_error(code: u16) -> ALErrorCategory

对 AL Status Code(从站返回的错误码)进行分类,帮助快速判断错误性质和处理策略。

参数:

  • code (u16) -- AL Status Code 原始值,用 slave.error_code_raw() 获取(slave.error_code() 返回 EcALState 枚举)

返回值:

#[repr(u8)]
pub enum ALErrorCategory {
None = 0, // 无错误
Transient = 1, // 瞬态错误,可重试状态转换,通常自动恢复
Configuration = 2, // 配置错误,检查 PDO 映射、SM 配置、Startup 参数等
Hardware = 3, // 硬件错误,检查从站硬件、线缆、电源
Unknown = 4, // 未知错误,查阅 ETG.1000 或从站手册
}

示例:

use darra_ethercat::{classify_al_error, ALErrorCategory};

let slave = master.slave(1);
let error_code = slave.error_code_raw(); // u16 原始 AL Status Code
if error_code != 0 {
let category = classify_al_error(error_code);
println!("从站 {} 错误 0x{:04X}: {:?}", slave.index(), error_code, category);

match category {
ALErrorCategory::Transient => {
println!("瞬态错误,尝试重新切换状态...");
}
ALErrorCategory::Configuration => {
println!("配置错误,请检查 PDO/SM 配置");
}
ALErrorCategory::Hardware => {
println!("硬件错误,请检查从站设备");
}
_ => {}
}
}
常见 AL Status Code
  • 0x001E 无效输入映射 — Configuration
  • 0x001D 无效输出映射 — Configuration
  • 0x0011 无效邮箱配置 — Configuration
  • 0x002D 同步错误 — Transient
  • 0x0032 DC 同步超时 — Transient
  • 0x0050 EEPROM 错误 — Hardware
Rust 特有语法糖

Rust sugar 为 EcALState 实现 Display, 可直接 format!("ALSC: {}", EcALState::SyncError) 输出友好字符串 (兜底 AL(0x00XX)), 日志/UI 不必再写匹配函数. 详见 Display 增强.

从站错误计数器

read_slave_error_counters()

pub fn read_slave_error_counters(&self, slave_index: i32) -> SlaveErrorCounters

读取指定从站的错误计数器汇总(CRC 错误、帧错误、丢帧统计)。

参数:

  • slave_index (i32) — 从站索引 (1-based)

返回值:

pub struct SlaveErrorCounters {
pub slave_index: i32, // 从站编号
pub port0_crc_errors: u32, // 端口 0 CRC 错误
pub port1_crc_errors: u32, // 端口 1 CRC 错误
pub port2_crc_errors: u32, // 端口 2 CRC 错误
pub port3_crc_errors: u32, // 端口 3 CRC 错误
pub frame_errors: u32, // 帧错误计数
pub lost_frames: u32, // 丢帧计数
}

示例:

let counters = diag.read_slave_error_counters(1);
if counters.port0_crc_errors + counters.frame_errors + counters.lost_frames > 0 {
println!("从站 1 错误计数:");
println!(" CRC 错误: P0={}, P1={}, P2={}, P3={}",
counters.port0_crc_errors, counters.port1_crc_errors,
counters.port2_crc_errors, counters.port3_crc_errors);
println!(" 帧错误: {}", counters.frame_errors);
println!(" 丢帧: {}", counters.lost_frames);
}

诊断消息

read_diagnostic_messages()

pub fn read_diagnostic_messages(master_index: u16, slave_index: u16) -> Vec<DiagnosticMessage>

通过 CoE 读取从站对象 0x10F3(诊断历史对象,ETG.1020)中的诊断消息。返回从站记录的诊断事件列表。

返回值:

pub struct DiagnosticMessage {
pub sub_index: u8, // 子索引
pub diag_code: u32, // 诊断代码
pub flags: u16, // 标志位
pub text_index: u16, // 文本索引
pub raw_data: Vec<u8>, // 原始数据
}

示例:

use darra_ethercat::slave::coe::read_diagnostic_messages;

let messages = read_diagnostic_messages(master.index(), 1);
for msg in &messages {
println!("[从站 1] Code=0x{:08X}, Flags=0x{:04X}",
msg.diag_code, msg.flags);
}
备注

并非所有从站都支持 0x10F3 诊断历史对象。不支持的从站返回空列表。

完整示例

let diag = master.diagnostics_info();
diag.set_enabled(true);

// 通信与性能
println!("帧频: {} Hz", diag.rt_cnt());
println!("丢包率: {:.2}%", diag.packet_loss_rate() * 100.0);
println!("平均抖动: {:.2}us", diag.avg_jitter_us());
println!("邮箱延迟: 平均 {:.2}us, 最大 {:.2}us",
diag.mailbox_latency_avg_us(), diag.mailbox_latency_us());

// PDO 丢帧
let pdo = diag.pdo();
println!("PDO 丢帧: 累计={}, 连续={}", pdo.total_lost(), pdo.consecutive_lost());

// 从站异常
println!("最差从站: #{} ({}%)", diag.worst_slave_index(), diag.worst_link_quality());

// 冗余状态
if diag.redundancy_active() {
println!("冗余模式: {:?}", diag.ring_mode());
}
if let Some(bp) = diag.break_point() {
println!("故障: {}", bp);
}

// 从站错误计数器
// slave_count() 返回 u16, read_slave_error_counters 形参为 i32, 需显式转换
for i in 1..=master.slave_count() {
let c = diag.read_slave_error_counters(i as i32);
if c.frame_errors > 0 || c.lost_frames > 0 {
println!("从站 {}: 帧错误={}, 丢帧={}", i, c.frame_errors, c.lost_frames);
}
}

// AL 错误分类
for i in 1..=master.slave_count() {
let slave = master.slave(i);
let error_code = slave.error_code_raw(); // u16 原始码
if error_code != 0 {
let category = classify_al_error(error_code);
println!("从站 {} AL错误 0x{:04X}: {:?}", i, error_code, category);
}
}

// 诊断消息 (ETG.1020 0x10F3)
for i in 1..=master.slave_count() {
let messages = read_diagnostic_messages(master.index(), i);
for msg in &messages {
println!("[从站 {}] Code=0x{:08X}, Flags=0x{:04X}",
i, msg.diag_code, msg.flags);
}
}

按问题分块的诊断字段指南

现场症状用哪个 API
是不是丢包?diagnostics_info().packet_loss_rate() + pdo().total_lost()
周期是否稳定?diagnostics_info().avg_jitter_us() / cycle_time_span() / bus_max_jitter_us() / bus_clean_max_jitter_us() (排除 SMI 后的干净最大抖动)
邮箱事务慢?diagnostics_info().mailbox_latency_us() / mailbox_latency_avg_us()
哪个从站不稳定?diagnostics_info().worst_slave_index() / worst_link_quality()
网线接触/CRC 问题?diagnostics_info().read_slave_error_counters(i)get_diagnostics()port_error_count[]
哪根网线断了?diagnostics_info().break_point()
主/副端口错误?diagnostics_info().primary_port_errors() / secondary_port_errors()
整网诊断快照?darra_ethercat::master::other::get_diagnostics(mi, slave_count)

整网从站诊断快照 (get_diagnostics)

get_diagnostics()darra_ethercat::master::other 下的自由函数, 一次性拉取所有从站的链路质量 / 端口错误 / 链路丢失 (见 属性与状态机 — 主站全局诊断快照)。

pub struct SlaveDiagnosticsData {
pub link_quality_percent: Vec<u32>, // 各从站链路质量 (0-100), 索引 1-based
pub port_error_count: Vec<u32>, // 各从站端口错误合计 (Rx + 无效帧)
pub lost_link_count: Vec<u32>, // 各从站链路丢失计数
pub frame_errors: u32, // 帧错误数
pub lost_frames: u32, // 丢失帧数
pub checksum_errors: u32, // 校验和错误数
pub timeout_frames: u32, // 超时帧数
pub primary_port_errors: u32, // 主端口错误计数
pub secondary_port_errors: u32, // 副端口错误计数
}

完整诊断流程示例

use darra_ethercat::master::other::get_diagnostics;

let diag = master.diagnostics_info();
diag.set_enabled(true);

// 1. 丢包率
if diag.packet_loss_rate() > 0.01 {
println!("丢包率 {:.2}% 严重", diag.packet_loss_rate() * 100.0);
}

// 2. 邮箱收发延迟
if diag.mailbox_latency_us() > 1000.0 {
println!("邮箱事务延迟偏大: {:.2}us", diag.mailbox_latency_us());
}

// 3. 故障定位
if let Some(bp) = diag.break_point() {
println!("故障点: 从站 {} P{}", bp.slave_index, bp.port);
}

// 4. 最差从站
println!("最差从站: #{} ({}%)",
diag.worst_slave_index(), diag.worst_link_quality());

// 5. 整网从站快照
if let Some(snap) = get_diagnostics(master.index(), master.slave_count()) {
for i in 1..=master.slave_count() as usize {
let q = snap.link_quality_percent[i];
let err = snap.port_error_count[i];
if q < 100 || err > 0 {
println!("从站 {}: 链路质量 {}%, 端口错误 {}", i, q, err);
}
}
}