跳到主要内容

事件

通过 master.events() 获取 MasterEvents,订阅所有主站级事件。

自动初始化

所有事件回调和日志系统在主站初始化时自动注册,无需手动调用。

从站级事件

从站 PDO 数据变化通知、FSoE 安全事件请参考 从站事件

自动日志

所有事件(PDO 周期回调除外)触发时系统均自动记录日志,无论是否订阅。确保关键运行状态不会被静默丢失。

PDO 周期回调(on_pdo_cyclic_sync)为高频回调,不记录日志。

2026-05-21 架构清理

on_pdo_cyclic_async(异步 PDO 周期回调)与 on_input_data_changed / on_input_data_changed_raw(输入数据变化事件)已删除。PDO 过程数据通路改为纯内核共享内存指针轮询(零拷贝),应用层直接读写过程映像,异步周期回调 / 输入变化检测机制不再驱动。需感知输入变化请自行轮询过程映像指针并比对上一周期快照。同步回调 on_pdo_cyclic_sync 保留,供 FSoE 等需要当周期实时控制的场景。

功能概览

类别方法说明自动日志
PDO 周期回调on_pdo_cyclic_syncPDO 周期回调(同步,供 FSoE 等实时控制)
状态事件on_slave_state_changed从站 EtherCAT 状态变化
热插拔事件on_slave_offline从站离线(断开)
on_slave_online从站上线(恢复)
on_slave_discovery从站发现回调(底层离线/上线)
on_preop_reconfig从站 PreOp 重新配置回调
on_slave_identity_mismatch从站身份不符(换成错误型号/低版本)
异常事件on_emergencyCoE Emergency 紧急消息
on_pdo_frame_lossPDO 连续丢帧(按组独立跟踪)
on_dc_sync_lostDC 同步丢失
冗余事件on_redundancy_mode_changed冗余模式变化(Inactive/Dual/Degraded)
on_slave_port_link_changed从站端口 link 变化(P0-P3 断开/恢复)
清除事件clear_all清除所有事件订阅

PDO 周期回调

2026-05-21 架构清理

异步 PDO 周期回调 on_pdo_cyclic_async 已删除。PDO 过程数据通路改为纯内核共享内存指针轮询(零拷贝),应用层直接读写过程映像,异步周期回调机制不再驱动。仅保留同步回调 on_pdo_cyclic_sync,供 FSoE 等需要当周期实时控制的场景。

on_pdo_cyclic_sync()

pub fn on_pdo_cyclic_sync<F>(&self, callback: F)
where F: Fn(u16) + Send + 'static
// 参数: (master_index)

每个 PDO 周期触发一次(同步模式)。回调在实时线程中直接执行,延迟最低但回调必须快速返回。

回调参数:

  • master_index (u16) — 主站索引
注意

同步回调中不要执行耗时操作(如 SDO 读写、文件 I/O、锁等待),否则会阻塞实时线程导致丢帧。

示例:

use darra_ethercat::SlavePdo;

events.on_pdo_cyclic_sync(|master_idx| {
// 仅做快速 PDO 读写。回调要求 'static, 不能捕获 master,
// 用回调参数 master_idx 现场构造 SlavePdo (其 ::new 接受裸 u16)。
let pdo = SlavePdo::new(master_idx, 1);
let _status: u16 = pdo.read_input_u16(0);
let _ = pdo.write_output_u16(0, 0x000F); // 返回 bool, 此处忽略
});

状态事件

on_slave_state_changed()

pub fn on_slave_state_changed<F>(&self, callback: F)
where F: Fn(u16, u16, i32, i32) + Send + 'static
// 参数: (master_index, slave_index, old_state, new_state)

从站 EtherCAT 状态变化时触发(包括主动切换和异常降级)。始终自动记录日志。

回调参数:

  • master_index (u16) — 主站索引
  • slave_index (u16) — 从站索引(1-based)
  • old_state (i32) — 变化前的状态(对应 EcState 值)
  • new_state (i32) — 变化后的状态(对应 EcState 值)

示例:

// 回调要求 Fn(..) + 'static, 不能捕获 master (EtherCATMaster 非 Clone 且有 Drop)。
// old / new_state 是 i32 形式的 EcState 值, 直接用回调参数即可。
master.events().on_slave_state_changed(|_master_idx, slave_idx, old, new_state| {
println!("从站 {}: {} -> {}", slave_idx, old, new_state);
// 状态降级 (new < old) 通常意味着出现故障
if new_state < old {
println!(" 从站 {} 状态降级, 建议在主循环中读取 AL Status Code", slave_idx);
}
});
ErrorCode

回调内无法直接构造 Slave (需要 EtherCATMaster)。需要查 AL Status Code 时, 在持有 master 的主循环里调用 master.slave(idx).error_code()。详见 从站属性

Rust 特有语法糖

Rust 可用 mpsc::channel 把 callback 转 receiver, for ev in rx 直接迭代消费, 多线程不必自己 Arc<Mutex<...>>. 详见 状态变化通道.

热插拔事件

on_slave_offline()

pub fn on_slave_offline<F>(&self, callback: F)
where F: Fn(u16) + Send + 'static
// 参数: (slave_index)

从站离线事件。热插拔断开时触发,PDO 线程内置的恢复状态机会自动恢复从站(零 PDO 影响)。始终自动记录日志。

回调参数:

  • slave_index (u16) — 离线从站索引(1-based)

示例:

master.events().on_slave_offline(|slave_idx| {
println!("从站 {} 离线", slave_idx);
});

on_slave_online()

pub fn on_slave_online<F>(&self, callback: F)
where F: Fn(u16) + Send + 'static
// 参数: (slave_index)

从站上线事件。热插拔恢复时触发。始终自动记录日志。

回调参数:

  • slave_index (u16) — 上线从站索引(1-based)

示例:

master.events().on_slave_online(|slave_idx| {
println!("从站 {} 上线", slave_idx);
});

is_slave_offline()

pub fn is_slave_offline(&self, slave_index: u16) -> bool

查询从站是否已被事件系统确认为离线状态。

参数:

  • slave_index (u16) — 从站索引

返回值:

  • bool — 从站离线返回 true

示例:

if master.events().is_slave_offline(2) {
println!("从站 2 处于离线状态");
}

offline_slaves() / offline_slave_count()

pub fn offline_slaves(&self) -> Vec<u16>
pub fn offline_slave_count(&self) -> usize

获取所有离线从站索引列表或数量。

on_preop_reconfig()

pub fn on_preop_reconfig<F>(&self, callback: F)
where F: Fn(u16, u16) + Send + 'static
// 参数: (master_index, slave_index)

热插拔恢复后重新应用启动参数时触发。始终自动记录日志。

回调参数:

  • master_index (u16) — 主站索引
  • slave_index (u16) — 需要重新配置的从站索引(1-based)

on_slave_identity_mismatch()

pub fn on_slave_identity_mismatch<F>(&self, callback: F)
where F: Fn(SlaveIdentityMismatch) + Send + 'static
// 参数: SlaveIdentityMismatch 结构体

从站断电重插后身份不符时触发:EtherCAT 识别状态机读取到的 Vendor/Product 与配置不匹配,或 Revision 低于配置(向后兼容:实际 Revision ≥ 配置值视为匹配)。始终自动记录日志。

触发后从站进入 IDENT_REJECTED 状态,不会自动重探测(防止错设备反复刷屏)。操作员检查/更换设备后需调用 master.acknowledge_slave_replacement 让 SDK 重新探测。

相关结构:

pub struct SlaveIdentityMismatch {
pub master_index: u16, // 主站索引
pub slave_index: u16, // 从站索引 (1-based)
pub expected_vendor: u32, // 配置期望的厂商 ID
pub expected_product: u32, // 配置期望的产品代码
pub expected_revision: u32, // 配置期望的最低修订号
pub actual_vendor: u32, // 当前实际厂商 ID
pub actual_product: u32, // 当前实际产品代码
pub actual_revision: u32, // 当前实际修订号
}

示例:

master.events().on_slave_identity_mismatch(|args| {
println!("从站 {} 身份不符:", args.slave_index);
println!(" 期望: Vendor=0x{:08X}, Product=0x{:08X}, Rev>=0x{:08X}",
args.expected_vendor, args.expected_product, args.expected_revision);
println!(" 实际: Vendor=0x{:08X}, Product=0x{:08X}, Rev=0x{:08X}",
args.actual_vendor, args.actual_product, args.actual_revision);
// UI 弹窗提示用户换回正确设备, 确认后调用
// master.acknowledge_slave_replacement(args.slave_index)
});
去重机制

同一从站进入 IDENT_REJECTED 状态仅触发一次事件。调用 acknowledge_slave_replacement 后重置探测,身份仍不匹配会再次触发。

异常事件

on_emergency()

pub fn on_emergency<F>(&self, callback: F)
where F: Fn(u16, u16, u16, u16, u8, u16, u16) + Send + 'static
// 参数: (master_index, slave_index, error_code, error_reg, b1, w1, w2)

CoE Emergency 紧急消息事件。从站固件检测到错误时发送,数据格式遵循 CANopen Emergency 协议(CiA 301)。始终自动记录日志。

回调参数:

  • master_index (u16) — 主站索引
  • slave_index (u16) — 从站索引(1-based)
  • error_code (u16) — 错误代码(CANopen Emergency Error Code,对象 0x603F)
  • error_reg (u16) — 错误寄存器(对象 0x1001)
  • b1 (u8) — 制造商特定数据(字节 3)
  • w1 (u16) — 制造商特定数据(字节 4-5)
  • w2 (u16) — 制造商特定数据(字节 6-7)

常见 Emergency Error Code:

  • 0x0000 — 错误已复位
  • 0x1000 — 通用错误
  • 0x2000 — 电流错误
  • 0x3000 — 电压错误
  • 0x4000 — 温度错误
  • 0x5000 — 设备硬件错误
  • 0x6000 — 设备软件错误
  • 0x7000 — 附加模块错误
  • 0x8000 — 监控错误(通信)
  • 0xFF00 — 制造商特定错误

示例:

master.events().on_emergency(|_master_idx, slave_idx, error_code, error_reg, b1, w1, w2| {
println!("从站 {} 紧急消息: 错误码=0x{:04X}, 寄存器=0x{:02X}",
slave_idx, error_code, error_reg);
});

on_pdo_frame_loss()

pub fn on_pdo_frame_loss<F>(&self, callback: F)
where F: Fn(u16, u8, u32, u32) + Send + 'static
// 参数: (master_index, group, consecutive_lost, total_lost)

PDO 连续丢帧事件。当连续丢帧达到阈值时触发,单次丢帧仅计数不触发。每组独立跟踪。始终自动记录日志。

回调参数:

  • master_index (u16) — 主站索引
  • group (u8) — 发生丢帧的组号(0-7),对应从站分组
  • consecutive_lost (u32) — 该组连续丢帧数
  • total_lost (u32) — 该组累计丢帧数

示例:

master.events().on_pdo_frame_loss(|_master_idx, group, consecutive, total| {
println!("组 {} 丢帧: 连续={}, 累计={}", group, consecutive, total);
});
丢帧统计

PDO 丢帧的详细统计(按组查询)请参考 主站诊断 - PDO 丢帧

on_dc_sync_lost()

pub fn on_dc_sync_lost<F>(&self, callback: F)
where F: Fn(u16, u16, i32) + Send + 'static
// 参数: (master_index, slave_index, diff_ns)

DC 同步丢失事件。当从站从同步->失同步(超出阈值)时触发一次,持续超出不重复触发。始终自动记录日志。

回调参数:

  • master_index (u16) — 主站索引
  • slave_index (u16) — 失同步的从站索引(1-based)
  • diff_ns (i32) — 当前与参考时钟的时间差(纳秒)

示例:

master.diagnostics_info().set_sync_window_threshold(500); // 500ns

master.events().on_dc_sync_lost(|_master_idx, slave_idx, diff_ns| {
println!("从站 {} DC 同步丢失: 偏差 {}ns", slave_idx, diff_ns);
});
单个从站 DC 诊断

slave.diagnostics().dc().is_in_sync()slave.diagnostics().dc().sync_time_difference() 可查询从站当前同步状态。详见 从站诊断 - DC 同步

输入数据变化事件

2026-05-21 架构清理 — 已删除

on_input_data_changed / on_input_data_changed_raw(以及从站级 slave.events().on_input_changed())已删除。

旧机制靠 SDK 底层每周期对输入 PDO 做逐字节比对检测变化。PDO 过程数据通路改为纯内核共享内存指针轮询(零拷贝)后,应用层直接读写过程映像,"每周期变化检测 + 回调分发"环节不再驱动。

替代方式: 需感知输入变化时,自行轮询过程映像指针并比对上一周期快照:

use darra_ethercat::SlavePdo;

let pdo = SlavePdo::new(master.index(), 1);
let mut last = pdo.inputs();
loop {
std::thread::sleep(std::time::Duration::from_millis(10));
let now = pdo.inputs();
if now != last {
println!("从站 1 输入数据变化, {} 字节", now.len());
last = now;
}
}

冗余事件

on_redundancy_mode_changed()

pub fn on_redundancy_mode_changed<F>(&self, callback: F)
where F: Fn(u16, i32, i32) + Send + 'static
// 参数: (master_index, old_mode, new_mode)

冗余运行模式发生变化时触发。old_mode / new_mode 对应 RingMode 枚举值(0=Inactive, 1=Dual, 2=Degraded)。始终自动记录日志。

回调参数:

  • master_index (u16) — 主站索引
  • old_mode (i32) — 变化前的冗余模式
  • new_mode (i32) — 变化后的冗余模式

示例:

master.events().on_redundancy_mode_changed(|_master_idx, old_mode, new_mode| {
println!("冗余模式: {} -> {}", old_mode, new_mode);
});
pub fn on_slave_port_link_changed<F>(&self, callback: F)
where F: Fn(u16, u16, u8, bool) + Send + 'static
// 参数: (master_index, slave_index, port, is_up)

从站 ESC 端口物理链路变化时触发(P0-P3 之一断开或恢复)。每秒诊断周期检测 DL Status 寄存器的 link bit,从 1→0 触发"断开",从 0→1 触发"恢复"。始终自动记录日志。

用于精确定位故障线缆段(相邻从站的对向端口同时报断,说明中间线缆有问题)。

回调参数:

  • master_index (u16) — 主站索引
  • slave_index (u16) — 从站索引(1-based)
  • port (u8) — 端口号 0-3,对应 P0 / P1 / P2 / P3
  • is_up (bool) — true=link 恢复, false=link 断开

示例:

master.events().on_slave_port_link_changed(|_master_idx, slave_idx, port, is_up| {
let state = if is_up { "恢复" } else { "断开" };
println!("从站 {} 端口 P{} link {}", slave_idx, port, state);
});
配合断点定位

master.diagnostics_info().break_point() / all_break_points() 提供聚合后的故障点视图(断线 + CRC 故障)。on_slave_port_link_changed 是原始事件源,适合做实时告警。详见 主站诊断 - 冗余状态

清除事件订阅

clear_all()

pub fn clear_all(&self)

清除所有已注册的事件回调,防止内存泄漏。适用于主站重新初始化或程序退出前的清理。

示例:

let events = master.events();
events.on_slave_offline(|idx| println!("从站 {} 离线", idx));
events.on_emergency(|_, slave, ec, _, _, _, _| println!("EMCY: {} 0x{:04X}", slave, ec));

// 重新初始化前清除所有回调
events.clear_all();
SlaveEvents

SlaveEvents 也提供 clear_all() 方法。由于回调列表是全局共享的,SlaveEvents::clear_all() 会清除对应类别的所有回调(包括其他从站的)。建议统一使用 MasterEvents::clear_all() 进行清理。

线程安全

事件回调在非 UI 线程上触发。Rust 的 Send + 'static 约束确保回调在跨线程使用时的安全性。如需共享状态,使用 Arc<Mutex<T>> 或原子类型:

use std::sync::{Arc, Mutex};

let shared_state = Arc::new(Mutex::new(Vec::new()));
let state_clone = shared_state.clone();

master.events().on_slave_offline(move |slave_idx| {
let mut state = state_clone.lock().unwrap();
state.push(slave_idx);
println!("从站 {} 离线, 累计离线: {}", slave_idx, state.len());
});

完整示例

let events = master.events();

// ===== 从站状态 =====
events.on_slave_state_changed(|_, slave_idx, old, new_state| {
println!("从站 {}: {} -> {}", slave_idx, old, new_state);
});

// ===== 热插拔 =====
events.on_slave_offline(|idx| println!("从站 {} 离线", idx));
events.on_slave_online(|idx| println!("从站 {} 上线", idx));

// ===== 异常事件 =====
events.on_emergency(|_, slave_idx, error_code, _, _, _, _| {
println!("从站 {} 紧急消息: 0x{:04X}", slave_idx, error_code);
});

events.on_pdo_frame_loss(|_, group, consecutive, total| {
println!("组 {} 丢帧: 连续={}, 累计={}", group, consecutive, total);
});

events.on_dc_sync_lost(|_, slave_idx, diff_ns| {
println!("从站 {} DC 同步丢失: {}ns", slave_idx, diff_ns);
});

// ===== 冗余事件 =====
events.on_redundancy_mode_changed(|_, old_mode, new_mode| {
println!("冗余模式变化: {} -> {}", old_mode, new_mode);
});

events.on_slave_port_link_changed(|_, slave_idx, port, is_up| {
println!("从站 {} P{} link {}", slave_idx, port,
if is_up { "恢复" } else { "断开" });
});

// ===== 热插拔身份不符 =====
events.on_slave_identity_mismatch(|args| {
println!("从站 {} 身份不符, 换设备后调用 acknowledge_slave_replacement({})",
args.slave_index, args.slave_index);
});

// ===== PDO 周期回调 (同步, 供 FSoE 等实时控制) =====
events.on_pdo_cyclic_sync(|_master_idx| {
// 仅做快速 PDO 读写, 不要执行耗时操作
});

// 注: on_pdo_cyclic_async / on_input_data_changed 已于 2026-05-21 删除 —
// PDO 通路改为纯内核共享内存指针轮询, 需感知输入变化请轮询过程映像并比对快照。