从站诊断
每个从站提供独立的诊断属性和子对象,可查询端口错误、冗余、DC 同步等信息。
配合事件使用
建议通过 事件 驱动异常处理(如 on_slave_state_changed、on_slave_offline、on_dc_sync_lost),而非轮询。
功能概览
| 功能 | 访问路径 | 说明 |
|---|---|---|
| WKC 与健康镜像 | slave.wc_contributed() / al_status_mirror() / mailbox_health_raw() 等 | per-slave WcState/AL/邮箱健康内核镜像(薄读零帧,实时) |
| 通信诊断 | slave.diagnostics().read_port_errors() | ESC 端口错误计数器 |
| 冗余诊断 | slave.diagnostics().* | 冗余激活、断路检测 |
| DC 同步 | slave.diagnostics().dc().* | 同步状态、时间差 |
WKC 与健康镜像
这些方法直接在 slave 上调用(不在 slave.diagnostics() 子对象下),反映内核 per-slave 诊断缓存的薄读零帧——内核每周期(WKC 异常时立即)维护缓存,读到即此刻真实总线现实,无需任何刷新。配合主站级 master.diagnostics_info().wc_deficit() 可在掉站时定位到具体从站。
| 方法 | 类型 | 读写 | 说明 |
|---|---|---|---|
| wc_contributed() | WcContribution | 只读 | 该从站对工作计数器(WKC)的贡献状态。NotContributed ≠ master 故障,是该从站此刻没在响应(疑似掉站/热插拔恢复中);内核未填充时诚实返回 Unknown,不臆造 |
| wc_contributed_raw() | u8 | 只读 | 同上原始字节(1=贡献 / 0=没贡献 / 0xFF=未知) |
| al_status_mirror() | SlaveAlMirror | 只读 | 该从站 AL 状态镜像解析结构(含 al_state / al_status_code / raw + has_fault() / is_unknown()) |
| al_status_mirror_raw() | u16 | 只读 | 同上原始 16bit(低字节 AL State / 高字节 AL Status Code) |
| mailbox_health_raw() | u8 | 只读 | 邮箱健康原始值(0=正常/健康,2=降级:在 OP 但邮箱半失效)。如实反映不参与 WKC 篡改 |
| mailbox_healthy() | bool | 只读 | 邮箱是否健康(mailbox_health_raw() == 0) |
| free_run_demoted() | bool | 只读 | 该从站是否被降级为 FreeRun(DC/SM 同步异常后内核摘下转 FreeRun,诚实故障反映) |
| health_degraded_count() | u32 | 只读 | 邮箱半失效累计降级次数(从站修复后不清零,反映历史劣化) |
| recover_mailbox_health() | bool | 动作 | 修复邮箱半失效(显式运维动作,true=成功)。通常底层已自动修复 |
R1 可观测性约定(不可违反): wc_contributed() 返回 NotContributed 不是 master 故障,而是该从站此刻没在响应(疑似掉站 / 热插拔恢复中);修复后自动回 Contributed。底层尚无数据时诚实返回 Unknown,不臆造为已贡献。
WcContribution
#[repr(u8)]
pub enum WcContribution {
NotContributed = 0, // 该从站本周期未贡献 WKC(掉站 / 未响应)
Contributed = 1, // 该从站本周期正常贡献 WKC
Unknown = 0xFF, // 内核尚无该从站镜像(未映射 / 缓存未就绪)— 诚实不臆造
}
impl WcContribution {
pub fn from_raw(raw: u8) -> Self; // 1=>Contributed, 0=>NotContributed, 其余=>Unknown
pub fn is_contributed(self) -> bool; // 仅 Contributed 为真(Unknown 不算贡献)
}
SlaveAlMirror
pub struct SlaveAlMirror {
pub al_state: EcSlaveStatus, // AL State(含 Error 位的完整命名状态)
pub al_status_code: u16, // AL Status Code(高字节,0 = 无错误码 / Unknown)
pub raw: u16, // native getter 原始 16bit(诚实回溯用)
}
impl SlaveAlMirror {
pub fn has_fault(self) -> bool; // AL State 是否带 Error 标志位(bit4 = 0x10)
pub fn is_unknown(self) -> bool; // raw == 0(内核尚无镜像,诚实表示 Unknown)
}
示例:
use darra_ethercat::WcContribution;
for slave in master.slaves().into_iter() {
// WKC 贡献:掉站定位
if slave.wc_contributed() == WcContribution::NotContributed {
let al = slave.al_status_mirror();
println!("从站 {} 未贡献 WKC, AL=0x{:04X}", slave.index(), al.al_status_code);
}
// 邮箱半失效(在 OP 但 CoE 可能阻塞)
if !slave.mailbox_healthy() {
println!("从站 {} 邮箱半失效 {} 次{}", slave.index(),
slave.health_degraded_count(),
if slave.free_run_demoted() { " (被迫 FreeRun)" } else { "" });
if slave.health_degraded_count() >= 5 {
slave.recover_mailbox_health(); // 通常内核已自动修复,此为手动介入
}
}
}
通信诊断
read_port_errors()
pub fn read_port_errors(&self) -> Option<EscPortErrors>
读取从站 ESC 端口错误计数器。
pub struct EscPortErrors {
pub rx_error: [u8; 4], // 各端口 RX 错误计数 [Port0-3]
pub invalid_frame: [u8; 4], // 各端口无效帧计数 [Port0-3]
pub lost_link: [u8; 4], // 各端口链路丢失计数 [Port0-3]
pub fwd_rx_error: [u8; 4], // 各端口转发 RX 错误计数 [Port0-3]
}
impl EscPortErrors {
pub fn has_errors(&self) -> bool // 是否存在任何错误
}
备注
read_port_errors() 需实时读取从站,不建议高频调用(建议 >=100ms 间隔)。
冗余和 DC 属性为"始终活跃",无需启用诊断模块。
示例:
for slave in master.slaves().into_iter() {
if let Some(errors) = slave.diagnostics().read_port_errors() {
if errors.has_errors() {
println!("从站 {}: RX错误={:?}, 无效帧={:?}, 链路丢失={:?}",
slave.index(), errors.rx_error,
errors.invalid_frame, errors.lost_link);
}
}
}
冗余诊断
通过 slave.diagnostics() 访问从站的冗余状态。仅在主站启用冗余时有意义。
| 方法 | 类型 | 读写 | 说明 |
|---|---|---|---|
| redundancy_activated() | bool | 只读 | 冗余被激活。从站未丢失,但网络中存在断线,冗余机制正在工作。CRC 故障不触发此标志 |
| primary_link_broken() | bool | 只读 | 主线路断路。从主端口 (Port0) 到该从站的路径上存在断线。仅检测物理断线 |
| secondary_link_broken() | bool | 只读 | 冗余线路断路。从副端口 (Port1) 反向到该从站的路径上存在断线。仅检测物理断线 |
示例:
for slave in master.slaves().into_iter() {
if !slave.diagnostics().redundancy_activated() { continue; }
print!("从站 {}: 冗余激活", slave.index());
if slave.diagnostics().primary_link_broken() {
print!(", 主线路断路");
}
if slave.diagnostics().secondary_link_broken() {
print!(", 冗余线路断路");
}
println!();
}
DC 同步
通过 slave.diagnostics().dc() 访问。
| 方法 | 类型 | 读写 | 说明 |
|---|---|---|---|
| is_in_sync() | bool | 只读 | 是否在同步窗口内 |
| sync_time_difference() | i32 | 只读 | 当前与参考时钟的时间差(纳秒) |
示例:
for slave in master.slaves().into_iter() {
if !slave.has_dc() { continue; }
let dc = slave.diagnostics().dc();
println!("从站 {}: 时间差={}ns, 同步={}",
slave.index(), dc.sync_time_difference(), dc.is_in_sync());
}
从站错误计数器
通过 master.diagnostics_info().read_slave_error_counters() 访问。
pub fn read_slave_error_counters(&self, slave_index: i32) -> SlaveErrorCounters
pub struct SlaveErrorCounters {
pub slave_index: i32,
pub port0_crc_errors: u32,
pub port1_crc_errors: u32,
pub port2_crc_errors: u32,
pub port3_crc_errors: u32,
pub frame_errors: u32,
pub lost_frames: u32,
}
完整示例
for slave in master.slaves().into_iter() {
println!("--- 从站 {}: {} ---", slave.index(), slave.name());
// 端口错误
if let Some(errors) = slave.diagnostics().read_port_errors() {
if errors.has_errors() {
println!(" 端口错误: RX={:?}, 无效帧={:?}, 链路丢失={:?}",
errors.rx_error, errors.invalid_frame, errors.lost_link);
}
}
// 冗余
if slave.diagnostics().redundancy_activated() {
println!(" 冗余激活: 主线路断={}, 冗余线路断={}",
slave.diagnostics().primary_link_broken(),
slave.diagnostics().secondary_link_broken());
}
// DC 同步
if slave.has_dc() {
let dc = slave.diagnostics().dc();
println!(" DC: 时间差={}ns, 同步={}", dc.sync_time_difference(), dc.is_in_sync());
}
}