主站诊断
通过 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 秒结算 | |
| WKC | wkc() | 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) / TX | 5 秒滑窗, pipeline 在途不算丢 |
| late_frame_rate | LateDrop / TX | idx 出 8 帧窗 stale, 不计入丢包 |
需 set_enabled(true)。
PDOFrameLossStats 结构
| 字段 | 类型 | 说明 |
|---|---|---|
| total_lost | u32 | 累计丢帧数 |
| consecutive_lost | u32 | 当前连续丢帧数 |
| max_consecutive_lost | u32 | 历史最大连续丢帧数 |
示例:
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);
}
}
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(); // 序列号判断镜像是否更新过(无需轮询帧)
单个从站对 WKC 的贡献状态请用 slave.wc_contributed()(WcContribution 枚举)。wc_deficit() > 0 时遍历从站即可定位掉站点。
热插拔重建
hot_swap_rebuild()
pub fn hot_swap_rebuild(&self) -> i32
运行中一次性热插拔重建拓扑:任意状态下调用一次,在线重扫总线 + 重建拓扑图 + 重配 PDO + 恢复到运行态(OP)。适用于现场拔掉 / 换上一个模块后,不停总线地把网络重新带回运行态。它不是 build() 那种"释放全网从头扫"的重建——是面向热插拔的在线增量恢复。该方法在 diagnostics_info() 上。
返回值(错误码语义):
| 返回码 | 名称 | 含义与处理 |
|---|---|---|
0 | OK | 成功:从站全部回到 OP |
-20 | BUSY | 另一操作正在进行——稍后重试 |
-21 | RESCAN_0 | 重扫到 0 个从站——已停在 PreOp 安全态,检查物理链路 / 供电 |
-22 | SDO_ABORT | 重配被从站 SDO abort 拒绝——检查 PDO 映射 / Startup 参数 |
-23 | NO_OP | 部分从站未达 OP——已回滚停安全态,查各从站 AL Status Code |
-24 | TIMEOUT | 重建超时 |
-25 | IDX_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!("硬件错误,请检查从站设备");
}
_ => {}
}
}
0x001E无效输入映射 — Configuration0x001D无效输出映射 — Configuration0x0011无效邮箱配置 — Configuration0x002D同步错误 — Transient0x0032DC 同步超时 — Transient0x0050EEPROM 错误 — Hardware
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);
}
}
}