主站诊断
通过 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 秒平均抖动 (微秒) | |
| max_jitter_us() | f64 | 是 | 最近 5 秒最大抖动 (微秒) | |
| avg_cycle_time_us() | f64 | 是 | 平均周期时间 (微秒) | |
| WKC | wkc() | u16 | 不可停 | 当前 WKC |
| expected_wkc() | u16 | 不可停 | 期望 WKC | |
| primary_wkc() | u16 | 不可停 | 主网口工作计数器(冗余模式下独立跟踪) | |
| secondary_wkc() | u16 | 不可停 | 副网口工作计数器(冗余模式下独立跟踪) | |
| 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, 最大 {:.2}us", diag.avg_jitter_us(), diag.max_jitter_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());
诊断快照
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 max_jitter_us: f64, // 最大抖动 (微秒)
pub cycle_time_us: i32, // 实际周期时间 (微秒)
pub wkc_actual: u16, // 当前工作计数器
pub wkc_expected: u16, // 期望工作计数器
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 故障
pub is_link_down: bool, // 是否为断线故障
pub is_crc_fault: bool, // 是否为 CRC 故障 (线缆/连接器劣化)
}
故障类型说明:
- 断线 (
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()或状态转换失败时获取
返回值:
#[repr(u8)]
pub enum ALErrorCategory {
None = 0, // 无错误
Transient = 1, // 瞬态错误,可重试状态转换,通常自动恢复
Configuration = 2, // 配置错误,检查 PDO 映射、SM 配置、Startup 参数等
Hardware = 3, // 硬件错误,检查从站硬件、线缆、电源
Unknown = 4, // 未知错误,查阅 ETG.1000 或从站手册
}
示例:
let slave = master.slave(1);
let error_code = slave.error_code();
if error_code != 0 {
let category = classify_al_error(error_code);
println!("从站 {} 错误 0x{:04X}: {:?}", slave.slave_num(), error_code, category);
match category {
ALErrorCategory::Transient => {
println!("瞬态错误,尝试重新切换状态...");
}
ALErrorCategory::Configuration => {
println!("配置错误,请检查 PDO/SM 配置");
}
ALErrorCategory::Hardware => {
println!("硬件错误,请检查从站设备");
}
_ => {}
}
}
0x001E无效输入映射 — Configuration0x001D无效输出映射 — Configuration0x0011无效邮箱配置 — Configuration0x002D同步错误 — Transient0x0032DC 同步超时 — Transient0x0050EEPROM 错误 — Hardware
从站错误计数器
read_slave_error_counters()
pub fn read_slave_error_counters(&self, slave_index: u16) -> SlaveErrorCounters
读取指定从站的错误计数器汇总(CRC 错误、帧错误、丢帧统计)。
参数:
slave_index— 从站索引 (1-based)
返回值:
pub struct SlaveErrorCounters {
pub slave_index: u16, // 从站编号
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 ethercat::slave::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, 最大 {:.2}us",
diag.avg_jitter_us(), diag.max_jitter_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);
}
// 从站错误计数器
for i in 1..=master.slave_count() {
let c = diag.read_slave_error_counters(i);
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();
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);
}
}