跳到主要内容

主站诊断

通过 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平均周期时间 (微秒)
WKCwkc()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) / 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, 最大 {:.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!("硬件错误,请检查从站设备");
}
_ => {}
}
}
常见 AL Status Code
  • 0x001E 无效输入映射 — Configuration
  • 0x001D 无效输出映射 — Configuration
  • 0x0011 无效邮箱配置 — Configuration
  • 0x002D 同步错误 — Transient
  • 0x0032 DC 同步超时 — Transient
  • 0x0050 EEPROM 错误 — 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);
}
}