跳到主要内容

事件

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

自动初始化

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

从站级事件

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

自动日志

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

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

功能概览

类别方法说明自动日志
PDO 周期回调on_pdo_cyclic_asyncPDO 周期回调(异步,推荐)
on_pdo_cyclic_syncPDO 周期回调(同步,慎用)
状态事件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_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);
}
});
ErrorCode

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);
});
单个从站 DC 诊断

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_pdo_cyclic 的关系

on_input_data_changedon_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);
});
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().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

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 数据
});