事件
通过 master.Events() 订阅所有主站级事件。
所有 DLL 回调在创建第一个主站实例时自动注册,无需手动调用。
从站 PDO 数据变化通知、FSoE 安全事件请参考 从站事件。
所有事件(PDO 周期回调除外)触发时系统均自动记录日志,无论是否订阅。确保关键运行状态不会被静默丢失。
PDO 周期回调(ProcessDataCyclicSync)为高频回调,不记录日志。
功能概览
| 类别 | 注册方法 | 读写 | 说明 | 自动日志 |
|---|---|---|---|---|
| PDO 周期回调 | addProcessDataCyclicSyncListener | 只写 | PDO 周期回调(同步,供 FSoE 等当周期实时控制) | -- |
| 状态事件 | addStateChangedListener | 只写 | 主站 EtherCAT 状态变化 | O |
| addSlaveStateChangedListener | 只写 | 从站 EtherCAT 状态变化 | O | |
| 热插拔事件 | addSlaveOfflineListener | 只写 | 从站离线(断开) | O |
| addSlaveOnlineListener | 只写 | 从站上线(恢复) | O | |
| addSlaveIdentityMismatchListener | 只写 | 从站身份不符(换成错误型号/低版本) | O | |
| 异常事件 | addEmergencyEventListener | 只写 | CoE Emergency 紧急消息 | O |
| addPDOFrameLossListener | 只写 | PDO 连续丢帧(按组独立跟踪) | O | |
| addDCSyncLostListener | 只写 | DC 同步丢失 | O | |
| 冗余事件 | addRedundancyModeChangedListener | 只写 | 冗余模式变化 | O |
| addSlavePortLinkChangedListener | 只写 | 从站端口 link 变化(P0-P3 断开/恢复) | O | |
| 清除订阅 | ClearAll() | 只写 | 清除所有事件订阅 | -- |
Java SDK 使用 add/remove 监听器模式,支持多个监听器。对应的移除方法为 remove*Listener。
PDO 周期回调
PDO 过程数据由内核 RT 收发。金路径是进 OP 后 inputsMapping / outputsMapping 映射一次,Structure 钉在过程映像上(Java 字段是缓存:读最新要 read(),改输出要 write();见 PDO 输入输出)。copyInputPdo / writeOutputPdo 是快照备选。addProcessDataCyclicSyncListener 用于需要与周期对齐的应用逻辑(含 FSoE)。本 SDK 不提供异步周期回调 addProcessDataCyclicAsyncListener。
addProcessDataCyclicSyncListener
master.Events().addProcessDataCyclicSyncListener((masterIndex) -> {
// ...
});
每个 PDO 周期触发一次(同步模式)。回调在实时线程中直接执行,延迟最低但回调必须快速返回。
同步回调中不要执行耗时操作(如 SDO 读写、文件 I/O、锁等待),否则会阻塞实时线程导致丢帧。
回调参数:
masterIndex(short) -- 主站索引
示例(钉页 0 拷贝,映射在回调外只做一次):
PDO pdo = master.getSlave(1).PDO();
InPdo inputs = pdo.inputsMapping(InPdo.class);
OutPdo outputs = pdo.outputsMapping(OutPdo.class);
master.Events().addProcessDataCyclicSyncListener((masterIndex) -> {
inputs.read();
outputs.controlWord = 0x000F;
outputs.targetPosition = inputs.actualPosition;
outputs.write();
});
快照备选(每拍 Copy,拷出来的 byte[] 不会自己变):
master.Events().addProcessDataCyclicSyncListener((masterIndex) -> {
PDO pdo = master.getSlave(1).PDO();
byte[] in = new byte[inBytes];
int[] copied = new int[1];
int[] required = new int[1];
pdo.copyInputPdo(in, copied, required);
byte[] out = new byte[outBytes];
int[] written = new int[1];
pdo.writeOutputPdo(out, written, required);
});
状态事件
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(short) -- 主站索引slaveIndex(short) -- 从站索引(1-based)oldState(EcState) -- 变化前的状态newState(EcState) -- 变化后的状态
示例:
master.Events().addSlaveStateChangedListener((masterIndex, slaveIndex, oldState, newState) -> {
System.out.println("从站 " + slaveIndex + ": " + oldState + " -> " + newState);
Slave slave = master.getSlave(slaveIndex);
EtherCATTypes.EcALState errorCode = slave.ErrorCode();
if (errorCode != EtherCATTypes.EcALState.NO_ERROR) {
System.out.println(" 错误码: " + errorCode);
}
});
slave.ErrorCode() 读取 AL Status Code,返回状态切换失败的具体原因。
热插拔事件
addSlaveOfflineListener
master.Events().addSlaveOfflineListener((slaveIndex) -> {
// ...
});
从站离线事件。热插拔断开时触发,PDO 线程内置的恢复状态机会自动恢复从站。
回调参数:
slaveIndex(short) -- 从站索引
示例:
master.Events().addSlaveOfflineListener((slaveIndex) -> {
System.out.println("从站 " + slaveIndex + " 离线");
});
addSlaveOnlineListener
master.Events().addSlaveOnlineListener((slaveIndex) -> {
// ...
});
从站上线事件。热插拔恢复时触发。
回调参数:
slaveIndex(short) -- 从站索引
示例:
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 后重置探测,身份仍不匹配会再次触发。
异常事件
周期按 DENI/LoopCycle 设定,最短 125µs,不是写死 1ms。Diagnostics().getRTcnt() 应对齐 1e9/LoopCycleNs。连续偏离设定 ±20%,或 max jitter 超过周期的 25%,必须写日志并在 UI 报警。同时核对 PDOFrameLoss、DCSyncLost。隔离未生效只允许抖动变大,不允许周期漂走还不报。不要在 PDO 热路径里做报警 I/O。
addEmergencyEventListener
master.Events().addEmergencyEventListener(
(masterIndex, slaveIndex, errorCode, errorReg, b1, w1, w2) -> {
// ...
});
CoE Emergency 紧急消息事件。从站固件检测到错误时发送,数据格式遵循 CANopen Emergency 协议(CiA 301)。
回调参数:
masterIndex(short) -- 主站索引slaveIndex(short) -- 从站索引(1-based)errorCode(short) -- 错误代码(CANopen Emergency Error Code,对象 0x603F)errorReg(short) -- 错误寄存器(对象 0x1001)b1(byte) -- 制造商特定数据(字节 3)w1(short) -- 制造商特定数据(字节 4-5)w2(short) -- 制造商特定数据(字节 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(short) -- 主站索引group(byte) -- 发生丢帧的组号(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(short) -- 主站索引slaveIndex(short) -- 失同步的从站索引(1-based)diffNs(int) -- 当前与参考时钟的时间差(纳秒)
示例:
master.Diagnostics().setSyncWindowThreshold(500); // 500ns
master.Events().addDCSyncLostListener((masterIndex, slaveIndex, diffNs) -> {
System.out.println("从站 " + slaveIndex + " DC 同步丢失: 偏差 " + diffNs + "ns");
});
Java 可用 BlockingQueue<EmergencyEvent> 把 EMCY / 状态变化等事件回调转成线程安全队列, 把慢消费(写库 / 推 MQ)移出 PDO 线程, 避免阻塞实时周期。 详见 BlockingQueue(诊断流).
输入数据变化(已移除)
addInputDataChangedListener 输入数据变化事件已移除。该事件原靠 DLL 层每周期对输入 PDO 做 memcmp 检测变化。
如何感知输入变化: 自行 copyInputPdo(或 SlavePdo.Inputs() 快照)并与上一周期比对。
byte[] prev = null;
while (running) {
PDO pdo = master.getSlave(1).PDO();
int[] copied = new int[1];
int[] required = new int[1];
pdo.copyInputPdo(null, copied, required);
byte[] cur = new byte[required[0]];
if (pdo.copyInputPdo(cur, copied, required) == PDO.PdoIoStatus.Success
&& prev != null && !java.util.Arrays.equals(prev, cur)) {
// 输入变化
}
prev = cur;
Thread.sleep(1);
}
冗余事件
addRedundancyModeChangedListener
master.Events().addRedundancyModeChangedListener((masterIndex, oldMode, newMode) -> {
// ...
});
冗余运行模式发生变化时触发。oldMode / newMode 对应 RingMode 值(0=Inactive, 1=Dual, 2=Degraded)。
回调参数:
masterIndex(short) -- 主站索引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(short) -- 主站索引slaveIndex(short) -- 从站索引(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);
EtherCATTypes.EcALState errorCode = slave.ErrorCode();
if (errorCode != EtherCATTypes.EcALState.NO_ERROR) {
System.out.println(" 错误码: " + 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));
// ===== PDO 周期回调 (同步, 供 FSoE 等当周期实时控制) =====
events.addProcessDataCyclicSyncListener((masterIndex) -> {
// 当周期实时控制; 普通读写用已映射的结构体 (inputsMapping / outputsMapping)
});
本 SDK 不提供异步 PDO 周期回调与输入数据变化事件。应用层主路径是进 OP 后 inputsMapping / outputsMapping 映射一次,Structure 钉在过程映像上;copyInputPdo / writeOutputPdo 是整帧快照备选。需周期处理输入/输出请自行起应用线程轮询(映射好的结构体 read() / write(),或 copyInputPdo 快照)。