事件
通过 master.events() 获取 MasterEvents,订阅所有主站级事件。
所有事件回调和日志系统在主站初始化时自动注册,无需手动调用。
从站 PDO 数据变化通知、FSoE 安全事件请参考 从站事件。
所有事件(PDO 周期回调除外)触发时系统均自动记录日志,无论是否订阅。确保关键运行状态不会被静默丢失。
PDO 周期回调(on_pdo_cyclic_async、on_pdo_cyclic_sync)为高频回调,不记录日志。
功能概览
| 类别 | 方法 | 说明 | 自动日志 |
|---|---|---|---|
| PDO 周期回调 | on_pdo_cyclic_async | PDO 周期回调(异步,推荐) | — |
| on_pdo_cyclic_sync | PDO 周期回调(同步,慎用) | — | |
| 状态事件 | on_slave_state_changed | 从站 EtherCAT 状态变化 | ✓ |
| 热插拔事件 | on_slave_offline | 从站离线(断开) | ✓ |
| on_slave_online | 从站上线(恢复) | ✓ | |
| on_slave_discovery | 从站发现回调(底层离线/上线) | ✓ | |
| on_preop_reconfig | 从站 PreOp 重新配置回调 | ✓ | |
| on_slave_identity_mismatch | 从站身份不符(换成错误型号/低版本) | ✓ | |
| 异常事件 | on_emergency | CoE Emergency 紧急消息 | ✓ |
| on_pdo_frame_loss | PDO 连续丢帧(按组独立跟踪) | ✓ | |
| on_dc_sync_lost | DC 同步丢失 | ✓ | |
| 输入数据变化 | on_input_data_changed | 从站输入 PDO 数据变化(原生检测) | — |
| 冗余事件 | on_redundancy_mode_changed | 冗余模式变化(Inactive/Dual/Degraded) | ✓ |
| on_slave_port_link_changed | 从站端口 link 变化(P0-P3 断开/恢复) | ✓ | |
| 清除事件 | clear_all | 清除所有事件订阅 | — |
PDO 周期回调
on_pdo_cyclic_async()
pub fn on_pdo_cyclic_async<F>(&self, callback: F)
where F: Fn(u16) + Send + 'static
// 参数: (master_index)
每个 PDO 周期触发一次(异步模式)。回调不阻塞实时 PDO 线程,适合大多数场景。
回调参数:
master_index(u16) — 主站索引
示例:
let events = master.events();
events.on_pdo_cyclic_async(|master_idx| {
let slave = master.slave(1);
let status: u16 = slave.sdo_read_value(0x6041, 0x00).unwrap_or(0);
});
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、锁等待),否则会阻塞实时线程导致丢帧。
示例:
events.on_pdo_cyclic_sync(|master_idx| {
// 仅做快速 PDO 读写
let slave = master.slave(1);
let status: u16 = slave.read_input_u16(0);
slave.write_output_u16(0, 0x000F);
});
状态事件
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值)
示例:
master.events().on_slave_state_changed(|_master_idx, slave_idx, old, new_state| {
println!("从站 {}: {} -> {}", slave_idx, old, new_state);
let slave = master.slave(slave_idx);
let error_code = slave.error_code();
if error_code != EcALState::NoError {
println!(" 错误码: {:?}", error_code);
}
});
slave.error_code() 读取 AL Status Code,返回状态切换失败的具体原因。详见 从站属性。
热插拔事件
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);
});
slave.diagnostics().dc().is_in_sync() 和 slave.diagnostics().dc().sync_time_difference() 可查询从站当前同步状态。详见 从站诊断 - DC 同步。
输入数据变化事件
on_input_data_changed()
pub fn on_input_data_changed<F>(&self, callback: F)
where F: Fn(u16, Vec<u16>) + Send + 'static
// 参数: (master_index, changed_slave_indices)
从站输入 PDO 数据变化时触发。自动解析 DLL 位图,返回变化的从站索引列表。仅当从站输入数据与上一周期不同时回调。
- 无变化时: 不触发回调,零开销
- 有变化时: 仅触发变化从站的回调
- 检测精度: 逐字节比较,任何一个 bit 变化都能检测到
回调参数:
master_index(u16) — 主站索引changed_slave_indices(Vec<u16>) — 输入数据发生变化的从站索引列表(1-based)
示例:
master.events().on_input_data_changed(|_master_idx, slave_indices| {
for idx in slave_indices {
let slave = master.slave(idx);
println!("从站 {} 输入数据变化", idx);
}
});
on_input_data_changed_raw()
pub fn on_input_data_changed_raw<F>(&self, callback: F)
where F: Fn(u16, *const u8, u16) + Send + 'static
// 参数: (master_index, changed_slave_bits, changed_count)
原始位图版本,位图中第 N 位为 1 表示从站 N 的输入数据发生了变化。适合需要高性能批量处理的场景。
slave.events().on_input_changed()— 单从站变化通知master.events().on_input_data_changed()— 统一监控所有从站变化master.events().on_pdo_cyclic_sync()— 每周期都需要处理数据
on_input_data_changed 在 on_pdo_cyclic_sync/async 之前触发。这意味着在 Sync 回调中可以确信变化事件已经分发完毕。两者可以同时使用,互不影响。
冗余事件
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);
});
on_slave_port_link_changed()
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/P3is_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().break_point() / get_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 也提供 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);
});
// ===== 输入数据变化 =====
events.on_input_data_changed(|_, slave_indices| {
for idx in slave_indices {
println!("从站 {} 输入数据变化", idx);
}
});
// ===== PDO 周期回调 =====
events.on_pdo_cyclic_async(|_| {
// 读写 PDO 数据
});