事件
通过 master.Events() 订阅所有主站级事件。
所有 DLL 回调在创建第一个主站实例时自动注册,无需手动调用。
从站 PDO 数据变化通知、FSoE 安全事件请参考 从站事件。
所有事件(PDO 周期回调除外)触发时系统均自动记录日志,无论是否订阅。确保关键运行状态不会被静默丢失。
PDO 周期回调(ProcessDataCyclicAsync、ProcessDataCyclicSync)为高频回调,不记录日志。
功能概览
| 类别 | 注册方法 | 说明 | 自动日志 |
|---|---|---|---|
| PDO 周期回调 | addProcessDataCyclicAsyncListener | PDO 周期回调(异步,推荐) | -- |
| addProcessDataCyclicSyncListener | PDO 周期回调(同步,慎用) | -- | |
| 状态事件 | addStateChangedListener | 主站 EtherCAT 状态变化 | O |
| addSlaveStateChangedListener | 从站 EtherCAT 状态变化 | O | |
| 热插拔事件 | addSlaveOfflineListener | 从站离线(断开) | O |
| addSlaveOnlineListener | 从站上线(恢复) | O | |
| addSlaveIdentityMismatchListener | 从站身份不符(换成错误型号/低版本) | O | |
| 异常事件 | addEmergencyEventListener | CoE Emergency 紧急消息 | O |
| addPDOFrameLossListener | PDO 连续丢帧(按组独立跟踪) | O | |
| addDCSyncLostListener | DC 同步丢失 | O | |
| 输入数据变化 | addInputDataChangedListener | 从站输入 PDO 数据变化 | -- |
| 冗余事件 | addRedundancyModeChangedListener | 冗余模式变化 | O |
| addSlavePortLinkChangedListener | 从站端口 link 变化(P0-P3 断开/恢复) | O | |
| 清除订阅 | ClearAll() | 清除所有事件订阅 | -- |
Java SDK 使用 add/remove 监听器模式,支持多个监听器。对应的移除方法为 remove*Listener。
PDO 周期回调
addProcessDataCyclicAsyncListener
master.Events().addProcessDataCyclicAsyncListener((masterIndex) -> {
// ...
});
每个 PDO 周期触发一次(异步模式)。回调不阻塞实时 PDO 线程,适合大多数场景。
回调参数:
masterIndex(int) -- 主站索引
示例:
master.Events().addProcessDataCyclicAsyncListener((masterIndex) -> {
Slave slave = master.getSlave(1);
Short status = slave.CoE().SDOReadInt16((short) 0x6041, (byte) 0);
});
addProcessDataCyclicSyncListener
master.Events().addProcessDataCyclicSyncListener((masterIndex) -> {
// ...
});
每个 PDO 周期触发一次(同步模式)。回调在实时线程中直接执行,延迟最低但回调必须快速返回。
同步回调中不要执行耗时操作(如 SDO 读写、文件 I/O、锁等待),否则会阻塞实时线程导致丢帧。
回调参数:
masterIndex(int) -- 主站索引
示例:
master.Events().addProcessDataCyclicSyncListener((masterIndex) -> {
Slave slave = master.getSlave(1);
ByteBuffer inBuf = slave.PDO().GetInputByteBuffer();
ByteBuffer outBuf = slave.PDO().GetOutputByteBuffer();
outBuf.putShort(0, (short) 0x000F);
});
状态事件
addStateChangedListener
master.Events().addStateChangedListener((oldState, newState) -> {
// ...
});
主站 EtherCAT 状态变化时触发(包括主动切换和异常降级)。
回调参数:
oldState(EcState) -- 变化前的状态newState(EcState) -- 变化后的状态
示例:
master.Events().addStateChangedListener((oldState, newState) -> {
System.out.println("主站状态: " + oldState + " -> " + newState);
});
addSlaveStateChangedListener
master.Events().addSlaveStateChangedListener((masterIndex, slaveIndex, oldState, newState) -> {
// ...
});
从站 EtherCAT 状态变化时触发。由 DLL 后台线程监控并回调。
回调参数:
masterIndex(int) -- 主站索引slaveIndex(int) -- 从站索引(1-based)oldState(EcState) -- 变化前的状态newState(EcState) -- 变化后的状态
示例:
master.Events().addSlaveStateChangedListener((masterIndex, slaveIndex, oldState, newState) -> {
System.out.println("从站 " + slaveIndex + ": " + oldState + " -> " + newState);
Slave slave = master.getSlave(slaveIndex);
short errorCode = slave.ErrorCode();
if (errorCode != 0) {
System.out.printf(" 错误码: 0x%04X%n", errorCode);
}
});
slave.ErrorCode() 读取 AL Status Code,返回状态切换失败的具体原因。
热插拔事件
addSlaveOfflineListener
master.Events().addSlaveOfflineListener((slaveIndex) -> {
// ...
});
从站离线事件。热插拔断开时触发,PDO 线程内置的恢复状态机会自动恢复从站。
回调参数:
slaveIndex(int) -- 从站索引
示例:
master.Events().addSlaveOfflineListener((slaveIndex) -> {
System.out.println("从站 " + slaveIndex + " 离线");
});
addSlaveOnlineListener
master.Events().addSlaveOnlineListener((slaveIndex) -> {
// ...
});
从站上线事件。热插拔恢复时触发。
回调参数:
slaveIndex(int) -- 从站索引
示例:
master.Events().addSlaveOnlineListener((slaveIndex) -> {
System.out.println("从站 " + slaveIndex + " 上线");
});
isSlaveOffline
public boolean isSlaveOffline(int slaveIndex)
查询从站是否已被事件系统确认为离线状态。
参数:
slaveIndex(int) -- 从站索引
返回值:
boolean-- 从站离线返回true
getOfflineSlaves
public Set<Integer> getOfflineSlaves()
获取所有离线从站编号集合(只读副本)。
示例:
if (master.Events().isSlaveOffline(2)) {
System.out.println("从站 2 处于离线状态");
}
Set<Integer> offlines = master.Events().getOfflineSlaves();
System.out.println("离线从站: " + offlines);
addSlaveIdentityMismatchListener
master.Events().addSlaveIdentityMismatchListener((args) -> {
// ...
});
从站断电重插后身份不符时触发:ident FSM 读取到的 Vendor/Product 与配置不匹配,或 Revision 低于配置(向后兼容:实际 Revision ≥ 配置值视为匹配)。始终自动记录日志。
触发后从站进入 IDENT_REJECTED 状态,不会自动重探测(防止错设备反复刷屏)。操作员检查/更换设备后需调用 master.AcknowledgeSlaveReplacement 让 SDK 重新探测。
回调参数 (SlaveIdentityMismatchEventArgs):
masterIndex(int) — 主站索引slaveIndex(int) — 从站索引(1-based)expectedVendor(long) — 配置期望的厂商 ID(unsigned 32-bit)expectedProduct(long) — 配置期望的产品代码(unsigned 32-bit)expectedRevision(long) — 配置期望的最低修订号(unsigned 32-bit)actualVendor(long) — 当前实际厂商 ID(unsigned 32-bit)actualProduct(long) — 当前实际产品代码(unsigned 32-bit)actualRevision(long) — 当前实际修订号(unsigned 32-bit)
Java 没有原生的 unsigned int 类型,SDK 使用 long 承载 C 层的 uint32 以避免负值。打印时用 %08X 或 Long.toHexString 格式化。
示例:
master.Events().addSlaveIdentityMismatchListener((args) -> {
System.out.printf("从站 %d 身份不符:%n", args.slaveIndex);
System.out.printf(" 期望: Vendor=0x%08X, Product=0x%08X, Rev>=0x%08X%n",
args.expectedVendor, args.expectedProduct, args.expectedRevision);
System.out.printf(" 实际: Vendor=0x%08X, Product=0x%08X, Rev=0x%08X%n",
args.actualVendor, args.actualProduct, args.actualRevision);
// UI 弹窗提示用户换回正确设备, 确认后调用 master.AcknowledgeSlaveReplacement(args.slaveIndex)
});
同一从站进入 IDENT_REJECTED 状态仅触发一次事件。调用 AcknowledgeSlaveReplacement 后重置探测,身份仍不匹配会再次触发。
异常事件
addEmergencyEventListener
master.Events().addEmergencyEventListener(
(masterIndex, slaveIndex, errorCode, errorReg, b1, w1, w2) -> {
// ...
});
CoE Emergency 紧急消息事件。从站固件检测到错误时发送,数据格式遵循 CANopen Emergency 协议(CiA 301)。
回调参数:
masterIndex(int) -- 主站索引slaveIndex(int) -- 从站索引(1-based)errorCode(int) -- 错误代码(CANopen Emergency Error Code,对象 0x603F)errorReg(int) -- 错误寄存器(对象 0x1001)b1(int) -- 制造商特定数据(字节 3)w1(int) -- 制造商特定数据(字节 4-5)w2(int) -- 制造商特定数据(字节 6-7)
常见 Emergency Error Code:
0x0000-- 错误已复位0x1000-- 通用错误0x2000-- 电流错误0x3000-- 电压错误0x4000-- 温度错误0x5000-- 设备硬件错误0x7000-- 附加模块错误0x8000-- 监控错误(通信)
示例:
master.Events().addEmergencyEventListener(
(masterIndex, slaveIndex, errorCode, errorReg, b1, w1, w2) -> {
System.out.printf("从站 %d 紧急消息: 错误码=0x%04X, 寄存器=0x%02X%n",
slaveIndex, errorCode, errorReg);
});
addPDOFrameLossListener
master.Events().addPDOFrameLossListener(
(masterIndex, group, consecutiveLostCount, totalLostCount) -> {
// ...
});
PDO 连续丢帧事件。当连续丢帧达到阈值时触发,单次丢帧仅计数不触发。每组独立跟踪。
回调参数:
masterIndex(int) -- 主站索引group(int) -- 发生丢帧的组号(0-7),对应从站分组consecutiveLostCount(int) -- 该组连续丢帧数totalLostCount(int) -- 该组累计丢帧数
示例:
master.Events().addPDOFrameLossListener(
(masterIndex, group, consecutiveLostCount, totalLostCount) -> {
System.out.printf("组 %d 丢帧: 连续=%d, 累计=%d%n",
group, consecutiveLostCount, totalLostCount);
});
PDO 丢帧的详细统计(按组查询)请参考 主站诊断 - PDO 丢帧。
addDCSyncLostListener
master.Events().addDCSyncLostListener((masterIndex, slaveIndex, diffNs) -> {
// ...
});
DC 同步丢失事件。当从站从同步->失同步(超出阈值)时触发一次,持续超出不重复触发。
回调参数:
masterIndex(int) -- 主站索引slaveIndex(int) -- 失同步的从站索引(1-based)diffNs(int) -- 当前与参考时钟的时间差(纳秒)
示例:
master.Diagnostics().setSyncWindowThreshold(500); // 500ns
master.Events().addDCSyncLostListener((masterIndex, slaveIndex, diffNs) -> {
System.out.println("从站 " + slaveIndex + " DC 同步丢失: 偏差 " + diffNs + "ns");
});
输入数据变化事件
addInputDataChangedListener
master.Events().addInputDataChangedListener((masterIndex, slaveIndex) -> {
// ...
});
从站输入 PDO 数据变化时触发。仅当从站输入数据与上一周期不同时回调。
- 无变化时: 不触发回调,零开销
- 有变化时: 仅触发变化从站的回调
- 检测精度: 逐字节比较,任何一个 bit 变化都能检测到
回调参数:
masterIndex(int) -- 主站索引slaveIndex(int) -- 输入数据发生变化的从站索引(1-based)
示例:
master.Events().addInputDataChangedListener((masterIndex, slaveIndex) -> {
Slave slave = master.getSlave(slaveIndex);
ByteBuffer inBuf = slave.PDO().GetInputByteBuffer();
if (inBuf != null) {
System.out.printf("从站 %d 输入变化: StatusWord=0x%04X%n",
slaveIndex, inBuf.getShort(0));
}
});
InputDataChanged 在 ProcessDataCyclicSync/Async 之前触发。这意味着在 Sync 回调中可以确信变化事件已经分发完毕。两者可以同时使用,互不影响。
冗余事件
addRedundancyModeChangedListener
master.Events().addRedundancyModeChangedListener((masterIndex, oldMode, newMode) -> {
// ...
});
冗余运行模式发生变化时触发。oldMode / newMode 对应 RingMode 值(0=Inactive, 1=Dual, 2=Degraded)。
回调参数:
masterIndex(int) -- 主站索引oldMode(int) -- 变化前的冗余模式newMode(int) -- 变化后的冗余模式
示例:
master.Events().addRedundancyModeChangedListener((masterIndex, oldMode, newMode) -> {
System.out.println("冗余模式: " + oldMode + " -> " + newMode);
});
addSlavePortLinkChangedListener
master.Events().addSlavePortLinkChangedListener((masterIndex, slaveIndex, port, isUp) -> {
// ...
});
从站 ESC 端口物理链路变化时触发(P0-P3 之一断开或恢复)。每秒诊断周期检测 DL Status 寄存器的 link bit,从 1->0 触发"断开",从 0->1 触发"恢复"。始终自动记录日志。
用于精确定位故障线缆段(相邻从站的对向端口同时报断,说明中间线缆有问题)。
回调参数:
masterIndex(int) -- 主站索引slaveIndex(int) -- 从站索引(1-based)port(byte) -- 端口号0-3,对应P0/P1/P2/P3isUp(boolean) --true=link 恢复,false=link 断开
示例:
master.Events().addSlavePortLinkChangedListener((masterIndex, slaveIndex, port, isUp) -> {
String state = isUp ? "恢复" : "断开";
System.out.printf("从站 %d 端口 P%d link %s%n", slaveIndex, port, state);
});
master.Diagnostics().getAllBreakPoints() 提供聚合后的故障点视图(断线 + CRC 故障)。SlavePortLinkChanged 是原始事件源,适合做实时告警。
清除所有订阅
ClearAll()
public void ClearAll()
清除所有事件订阅,防止内存泄漏。在销毁主站或重新初始化前调用。同时清除离线从站跟踪状态。
示例:
// 销毁前清理
master.Events().ClearAll();
master.close();
// 从站级事件也支持 ClearAll
slave.Events().ClearAll();
SlaveEvents 也提供 ClearAll() 方法,清除从站级别的所有事件订阅。
线程安全
事件回调在非 UI 线程上触发。如果使用 Swing/JavaFX,需在事件线程中更新 UI:
// Swing
master.Events().addSlaveOfflineListener((idx) ->
SwingUtilities.invokeLater(() ->
labelStatus.setText("从站 " + idx + " 离线")));
// JavaFX
master.Events().addSlaveOfflineListener((idx) ->
Platform.runLater(() ->
statusLabel.setText("从站 " + idx + " 离线")));
完整示例
MasterEvents events = master.Events();
// ===== 主站状态 =====
events.addStateChangedListener((oldState, newState) ->
System.out.println("主站: " + oldState + " -> " + newState));
// ===== 从站状态 =====
events.addSlaveStateChangedListener((mi, si, os, ns) -> {
System.out.println("从站 " + si + ": " + os + " -> " + ns);
Slave slave = master.getSlave(si);
short errorCode = slave.ErrorCode();
if (errorCode != 0) {
System.out.printf(" 错误码: 0x%04X%n", errorCode);
}
});
// ===== 热插拔 =====
events.addSlaveOfflineListener((si) ->
System.out.println("从站 " + si + " 离线"));
events.addSlaveOnlineListener((si) ->
System.out.println("从站 " + si + " 上线"));
// ===== 异常事件 =====
events.addEmergencyEventListener((mi, si, ec, er, b, w1, w2) ->
System.out.printf("从站 %d 紧急消息: 0x%04X%n", si, ec));
events.addPDOFrameLossListener((mi, g, c, t) ->
System.out.printf("组 %d 丢帧: 连续=%d, 累计=%d%n", g, c, t));
events.addDCSyncLostListener((mi, si, d) ->
System.out.println("从站 " + si + " DC 同步丢失: " + d + "ns"));
// ===== 冗余事件 =====
events.addRedundancyModeChangedListener((mi, om, nm) ->
System.out.println("冗余模式变化: " + om + " -> " + nm));
events.addSlavePortLinkChangedListener((mi, si, port, isUp) ->
System.out.printf("从站 %d P%d link %s%n", si, port, isUp ? "恢复" : "断开"));
// ===== 热插拔身份不符 =====
events.addSlaveIdentityMismatchListener((args) ->
System.out.printf("从站 %d 身份不符, 换设备后调用 AcknowledgeSlaveReplacement(%d)%n",
args.slaveIndex, args.slaveIndex));
// ===== 输入数据变化 =====
events.addInputDataChangedListener((mi, si) ->
System.out.println("从站 " + si + " 输入数据变化"));
// ===== PDO 周期回调 =====
events.addProcessDataCyclicAsyncListener((masterIndex) -> {
// 读写 PDO 数据
});