跳到主要内容

事件

通过 master.Events() 订阅所有主站级事件。

自动初始化

所有 DLL 回调在创建第一个主站实例时自动注册,无需手动调用。

从站级事件

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

自动日志

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

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

功能概览

类别注册方法说明自动日志
PDO 周期回调addProcessDataCyclicAsyncListenerPDO 周期回调(异步,推荐)--
addProcessDataCyclicSyncListenerPDO 周期回调(同步,慎用)--
状态事件addStateChangedListener主站 EtherCAT 状态变化O
addSlaveStateChangedListener从站 EtherCAT 状态变化O
热插拔事件addSlaveOfflineListener从站离线(断开)O
addSlaveOnlineListener从站上线(恢复)O
addSlaveIdentityMismatchListener从站身份不符(换成错误型号/低版本)O
异常事件addEmergencyEventListenerCoE Emergency 紧急消息O
addPDOFrameLossListenerPDO 连续丢帧(按组独立跟踪)O
addDCSyncLostListenerDC 同步丢失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);
}
});
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)
unsigned 32-bit

Java 没有原生的 unsigned int 类型,SDK 使用 long 承载 C 层的 uint32 以避免负值。打印时用 %08XLong.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));
}
});
与 ProcessDataCyclic 的关系

InputDataChangedProcessDataCyclicSync/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 / P3
  • isUp (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 数据
});