事件
通过 master.Events 订阅所有主站级事件。
所有事件回调和日志系统在 Build() 时自动注册,无需手动调用。
从站 PDO 数据变化通知、FSoE 安全事件请参考 从站事件。
所有事件(PDO 周期回调除外)触发时系统均自动记录日志,无论是否订阅。确保关键运行状态不会被静默丢失。
PDO 周期回调(ProcessDataCyclicAsync、ProcessDataCyclicSync)为高频回调,不记录日志。
功能概览
| 类别 | 事件 | 说明 | 自动日志 |
|---|---|---|---|
| PDO 周期回调 | master.Events.ProcessDataCyclicAsync | PDO 周期回调(异步,推荐) | — |
| master.Events.ProcessDataCyclicSync | PDO 周期回调(同步,慎用) | — | |
| 状态事件 | master.Events.StateChanged | 主站 EtherCAT 状态变化(含异常降级) | ✓ |
| master.Events.SlaveStateChanged | 从站 EtherCAT 状态变化 | ✓ | |
| 热插拔事件 | master.Events.SlaveOffline | 从站离线(断开) | ✓ |
| master.Events.SlaveOnline | 从站上线(恢复) | ✓ | |
| master.Events.SlaveIdentityMismatch | 从站身份不符(换成错误型号/低版本) | ✓ | |
| 异常事件 | master.Events.EmergencyEvent | CoE Emergency 紧急消息 | ✓ |
| master.Events.PDOFrameLoss | PDO 连续丢帧(按组独立跟踪) | ✓ | |
| master.Events.DCSyncLost | DC 同步丢失 | ✓ | |
| 输入数据变化 | master.Events.InputDataChanged | 从站输入PDO数据变化(原生检测) | — |
| 冗余事件 | master.Events.RedundancyModeChanged | 冗余模式变化(Inactive/Dual/Degraded) | ✓ |
| master.Events.SlavePortLinkChanged | 从站端口 link 变化(P0-P3 断开/恢复) | ✓ | |
| 日志 | Logs.Updated | 日志数据更新通知 | — |
PDO 周期回调
ProcessDataCyclicAsync
public event ProcessDataCyclicAsyncEventHandler? ProcessDataCyclicAsync;
// delegate void ProcessDataCyclicAsyncEventHandler(ushort masterIndex);
每个 PDO 周期触发一次(异步模式)。回调不阻塞实时 PDO 线程,适合大多数场景。
示例:
master.Events.ProcessDataCyclicAsync += (masterIndex) =>
{
ushort status = slave.CoE[0x6041][0].Value;
};
ProcessDataCyclicSync
public event ProcessDataCyclicSyncEventHandler? ProcessDataCyclicSync;
// delegate void ProcessDataCyclicSyncEventHandler(ushort masterIndex);
每个 PDO 周期触发一次(同步模式)。回调在实时线程中直接执行,延迟最低但回调必须快速返回。
同步回调中不要执行耗时操作(如 SDO 读写、文件 I/O、锁等待),否则会阻塞实时线程导致丢帧。
示例:
master.Events.ProcessDataCyclicSync += (masterIndex) =>
{
ref var input = ref slave.PDO.InputsMapping<ServoInput>();
ref var output = ref slave.PDO.OutputsMapping<ServoOutput>();
output.ControlWord = 0x000F;
};
状态事件
Master.StateChanged
master.Events.StateChanged += (sender, e) => { };
// EventHandler<StateChangedEventArgs>
主站 EtherCAT 状态变化时触发(包括主动切换和异常降级)。始终自动记录日志。该事件只反映主站自身的 EcState,与单个从站状态 (slave.Events.StateChanged) 和 FSoE 安全连接状态 (slave.FSoE.StateChanged) 互相独立。
回调参数:
public class StateChangedEventArgs
{
public EcState OldState { get; } // 变化前的状态
public EcState NewState { get; } // 变化后的状态
}
示例:
master.Events.StateChanged += (sender, e) =>
{
Console.WriteLine($"主站状态: {e.OldState} → {e.NewState}");
};
SlaveStateChanged
master.Events.SlaveStateChanged += (masterIndex, slaveIndex, oldState, newState) => { };
// delegate void SlaveStateChangeEventHandler(ushort masterIndex, ushort slaveIndex, EcState oldState, EcState newState);
从站 EtherCAT 状态变化时触发。后台自动监控。始终自动记录日志。
回调参数:
masterIndex(ushort) — 主站索引slaveIndex(ushort) — 从站索引(1-based)oldState(EcState) — 变化前的状态newState(EcState) — 变化后的状态
示例:
master.Events.SlaveStateChanged += (masterIndex, slaveIndex, oldState, newState) =>
{
Console.WriteLine($"从站 {slaveIndex}: {oldState} → {newState}");
var slave = master.Slaves[slaveIndex - 1];
if (slave.ErrorCode != EcALState.NoError)
Console.WriteLine($" 错误码: {slave.ErrorCode}");
};
slave.ErrorCode 读取 AL Status Code,返回状态切换失败的具体原因。详见 属性与状态机。
热插拔事件
SlaveOffline
master.Events.SlaveOffline += (slaveIndex) => { };
// delegate void SlaveOfflineEventHandler(ushort slaveIndex);
从站离线事件。热插拔断开时触发,PDO 线程内置的恢复状态机会自动恢复从站(零 PDO 影响)。始终自动记录日志。
示例:
master.Events.SlaveOffline += (slaveIndex) =>
{
Console.WriteLine($"从站 {slaveIndex} 离线");
};
SlaveOnline
master.Events.SlaveOnline += (slaveIndex) => { };
// delegate void SlaveOnlineEventHandler(ushort slaveIndex);
从站上线事件。热插拔恢复时触发。始终自动记录日志。
示例:
master.Events.SlaveOnline += (slaveIndex) =>
{
Console.WriteLine($"从站 {slaveIndex} 上线");
};
IsSlaveOffline(ushort slaveIndex)
public bool IsSlaveOffline(ushort slaveIndex)
查询从站是否已被事件系统确认为离线状态。
参数:
slaveIndex(ushort) — 从站索引
返回值:
bool— 从站离线返回true
示例:
if (master.Events.IsSlaveOffline(2))
Console.WriteLine("从站 2 处于离线状态");
SlaveIdentityMismatch
master.Events.SlaveIdentityMismatch += (sender, args) => { };
// EventHandler<SlaveIdentityMismatchEventArgs>
从站断电重插后身份不符时触发:EtherCAT 识别状态机读取到的 Vendor/Product 与配置不匹配,或 Revision 低于配置(向后兼容:实际 Revision ≥ 配置值视为匹配)。始终自动记录日志。
触发后从站进入 IDENT_REJECTED 状态,不会自动重探测(防止错设备反复刷屏)。操作员检查/更换设备后需调用 master.AcknowledgeSlaveReplacement 让 SDK 重新探测。
回调参数 (SlaveIdentityMismatchEventArgs):
public class SlaveIdentityMismatchEventArgs
{
public ushort MasterIndex { get; } // 主站索引
public ushort SlaveIndex { get; } // 从站索引(1-based)
public uint ExpectedVendor { get; } // 配置期望的厂商 ID
public uint ExpectedProduct { get; } // 配置期望的产品代码
public uint ExpectedRevision { get; } // 配置期望的最低修订号
public uint ActualVendor { get; } // 当前实际厂商 ID
public uint ActualProduct { get; } // 当前实际产品代码
public uint ActualRevision { get; } // 当前实际修订号
}
示例:
master.Events.SlaveIdentityMismatch += (sender, args) =>
{
Console.WriteLine($"从站 {args.SlaveIndex} 身份不符:");
Console.WriteLine($" 期望: Vendor=0x{args.ExpectedVendor:X8}, Product=0x{args.ExpectedProduct:X8}, Rev>=0x{args.ExpectedRevision:X8}");
Console.WriteLine($" 实际: Vendor=0x{args.ActualVendor:X8}, Product=0x{args.ActualProduct:X8}, Rev=0x{args.ActualRevision:X8}");
// UI 弹窗提示用户换回正确设备, 确认后调用 master.AcknowledgeSlaveReplacement(args.SlaveIndex)
};
同一从站进入 IDENT_REJECTED 状态仅触发一次事件。调用 AcknowledgeSlaveReplacement 后重置探测,身份仍不匹配会再次触发。
异常事件
EmergencyEvent
master.Events.EmergencyEvent += (masterIndex, slaveIndex, errorCode, errorReg, b1, w1, w2) => { };
// delegate void EmergencyEventHandler(ushort masterIndex, ushort slaveIndex,
// ushort errorCode, ushort errorReg, byte b1, ushort w1, ushort w2);
CoE Emergency 紧急消息事件。从站固件检测到错误时发送,数据格式遵循 CANopen Emergency 协议(CiA 301)。始终自动记录日志。
回调参数:
masterIndex(ushort) — 主站索引slaveIndex(ushort) — 从站索引(1-based)errorCode(ushort) — 错误代码(CANopen Emergency Error Code,对象 0x603F)errorReg(ushort) — 错误寄存器(对象 0x1001)b1(byte) — 制造商特定数据(字节 3)w1(ushort) — 制造商特定数据(字节 4-5)w2(ushort) — 制造商特定数据(字节 6-7)
相关结构:
常见 Emergency Error Code:
0x0000— 错误已复位0x1000— 通用错误0x2000— 电流错误0x3000— 电压错误0x4000— 温度错误0x5000— 设备硬件错误0x6000— 设备软件错误0x7000— 附加模块错误0x8000— 监控错误(通信)0xFF00— 制造商特定错误
示例:
master.Events.EmergencyEvent += (masterIndex, slaveIndex, errorCode, errorReg, b1, w1, w2) =>
{
Console.WriteLine($"从站 {slaveIndex} 紧急消息: 错误码=0x{errorCode:X4}, 寄存器=0x{errorReg:X2}");
};
PDOFrameLoss
master.Events.PDOFrameLoss += (masterIndex, group, consecutiveLostCount, totalLostCount) => { };
// delegate void PDOFrameLossEventHandler(ushort masterIndex, byte group,
// uint consecutiveLostCount, uint totalLostCount);
PDO 连续丢帧事件。当连续丢帧达到阈值时触发,单次丢帧仅计数不触发。每组独立跟踪。始终自动记录日志。
回调参数:
masterIndex(ushort) — 主站索引group(byte) — 发生丢帧的组号(0-7),对应从站分组consecutiveLostCount(uint) — 该组连续丢帧数totalLostCount(uint) — 该组累计丢帧数
示例:
master.Events.PDOFrameLoss += (masterIndex, group, consecutive, total) =>
{
Console.WriteLine($"组 {group} 丢帧: 连续={consecutive}, 累计={total}");
};
PDO 丢帧的详细统计(按组查询)请参考 主站诊断 - PDO 丢帧。
DCSyncLost
master.Events.DCSyncLost += (masterIndex, slaveIndex, diffNs) => { };
// delegate void DCSyncLostEventHandler(ushort masterIndex, ushort slaveIndex, int diffNs);
DC 同步丢失事件。当从站从同步→失同步(超出阈值)时触发一次,持续超出不重复触发。始终自动记录日志。
回调参数:
masterIndex(ushort) — 主站索引slaveIndex(ushort) — 失同步的从站索引(1-based)diffNs(int) — 当前与参考时钟的时间差(纳秒)
示例:
master.Diagnostics.SyncWindowThreshold = 500; // 500ns
master.Events.DCSyncLost += (masterIndex, slaveIndex, diffNs) =>
{
Console.WriteLine($"从站 {slaveIndex} DC 同步丢失: 偏差 {diffNs}ns");
};
slave.Diagnostics.DC.IsInSync 和 slave.Diagnostics.DC.SyncTimeDifference 可查询从站当前同步状态。详见 从站诊断 - DC 同步。
输入数据变化事件
InputDataChanged
master.Events.InputDataChanged += (masterIndex, slaveIndex) => { };
// delegate void InputDataChangedEventHandler(ushort masterIndex, ushort slaveIndex);
从站输入 PDO 数据变化时触发。仅当从站输入数据与上一周期不同时回调。
- 无变化时: 不触发回调,零开销
- 有变化时: 仅触发变化从站的回调
- 检测精度: 逐字节比较,任何一个 bit 变化都能检测到
回调参数:
masterIndex(ushort) — 主站索引slaveIndex(ushort) — 输入数据发生变化的从站索引(1-based)
示例:
master.Events.InputDataChanged += (masterIndex, slaveIndex) =>
{
var slave = master.Slaves[slaveIndex - 1];
ref var input = ref slave.PDO.InputsMapping<ServoInput>();
Console.WriteLine($"从站 {slaveIndex} 输入变化: StatusWord=0x{input.StatusWord:X4}");
};
slave.Events.InputChanged— 仅需知道"变了",自行读取数据master.Events.InputDataChanged— 统一监控所有从站变化slave.PDO.InputsMapping<T>(onChanged)— 自动获取变化前后的结构体值master.Events.ProcessDataCyclicSync— 每周期都需要处理数据
详见 从站事件。
InputDataChanged 在 ProcessDataCyclicSync/Async 之前触发。这意味着在 Sync 回调中可以确信变化事件已经分发完毕。两者可以同时使用,互不影响。
冗余事件
RedundancyModeChanged
master.Events.RedundancyModeChanged += (masterIndex, oldMode, newMode) => { };
// delegate void RedundancyModeChangedEventHandler(ushort masterIndex, int oldMode, int newMode);
冗余运行模式发生变化时触发。oldMode / newMode 对应 RingMode 枚举值(0=Inactive, 1=Dual, 2=Degraded)。
示例:
master.Events.RedundancyModeChanged += (masterIndex, oldMode, newMode) =>
{
Console.WriteLine($"冗余模式: {oldMode} → {newMode}");
};
SlavePortLinkChanged
master.Events.SlavePortLinkChanged += (masterIndex, slaveIndex, port, isUp) => { };
// delegate void SlavePortLinkChangedEventHandler(ushort masterIndex, ushort slaveIndex, byte port, bool isUp);
从站 ESC 端口物理链路变化时触发(P0-P3 之一断开或恢复)。每秒诊断周期检测 DL Status 寄存器的 link bit,从 1→0 触发"断开",从 0→1 触发"恢复"。始终自动记录日志。
用于精确定位故障线缆段(相邻从站的对向端口同时报断,说明中间线缆有问题)。
回调参数:
masterIndex(ushort) — 主站索引slaveIndex(ushort) — 从站索引(1-based)port(byte) — 端口号0-3,对应P0/P1/P2/P3isUp(bool) —true=link 恢复,false=link 断开
示例:
master.Events.SlavePortLinkChanged += (masterIndex, slaveIndex, port, isUp) =>
{
string state = isUp ? "恢复" : "断开";
Console.WriteLine($"从站 {slaveIndex} 端口 P{port} link {state}");
};
master.Diagnostics.BreakPoint / GetAllBreakPoints() 提供聚合后的故障点视图(断线 + CRC 故障)。SlavePortLinkChanged 是原始事件源,适合做实时告警。详见 主站诊断 - 冗余状态。
清除事件订阅
Events.ClearAll()
public void ClearAll()
一键清除所有主站级事件订阅,防止内存泄漏。在销毁主站或重新初始化前调用。
示例:
// 销毁前清除所有事件
master.Events.ClearAll();
master.Close();
从站也有 slave.Events.ClearAll() 方法,清除该从站的所有事件订阅。
线程安全
事件回调在非 UI 线程上触发。更新 UI 时需要线程同步:
// WinForms
master.Events.SlaveOffline += (idx) =>
this.Invoke(() => labelStatus.Text = $"从站 {idx} 离线");
// WPF
master.Events.SlaveOffline += (idx) =>
Dispatcher.Invoke(() => StatusText = $"从站 {idx} 离线");
完整示例
// ===== 主站状态 =====
master.Events.StateChanged += (sender, e) =>
Console.WriteLine($"主站: {e.OldState} → {e.NewState}");
//从站状态
master.Events.SlaveStateChanged += (masterIndex, slaveIndex, oldState, newState) =>
{
var slave = master.Slaves[slaveIndex - 1];
Console.WriteLine($"从站 {slaveIndex}: {oldState} → {newState}");
if (slave.ErrorCode != EcALState.NoError)
Console.WriteLine($" 错误码: {slave.ErrorCode}");
};
// ===== 热插拔 =====
master.Events.SlaveOffline += (slaveIndex) =>
Console.WriteLine($"从站 {slaveIndex} 离线");
master.Events.SlaveOnline += (slaveIndex) =>
Console.WriteLine($"从站 {slaveIndex} 上线");
// ===== 异常事件 =====
master.Events.EmergencyEvent += (masterIndex, slaveIndex, errorCode, errorReg, b1, w1, w2) =>
Console.WriteLine($"从站 {slaveIndex} 紧急消息: 0x{errorCode:X4}");
master.Events.PDOFrameLoss += (masterIndex, group, consecutive, total) =>
Console.WriteLine($"组 {group} 丢帧: 连续={consecutive}, 累计={total}");
master.Events.DCSyncLost += (masterIndex, slaveIndex, diffNs) =>
Console.WriteLine($"从站 {slaveIndex} DC 同步丢失: {diffNs}ns");
// ===== 冗余事件 =====
master.Events.RedundancyModeChanged += (masterIndex, oldMode, newMode) =>
Console.WriteLine($"冗余模式变化: {oldMode} → {newMode}");
master.Events.SlavePortLinkChanged += (masterIndex, slaveIndex, port, isUp) =>
Console.WriteLine($"从站 {slaveIndex} P{port} link {(isUp ? "恢复" : "断开")}");
// ===== 热插拔身份不符 =====
master.Events.SlaveIdentityMismatch += (sender, args) =>
Console.WriteLine($"从站 {args.SlaveIndex} 身份不符, 换设备后调用 AcknowledgeSlaveReplacement({args.SlaveIndex})");
// ===== 输入数据变化 =====
master.Events.InputDataChanged += (masterIndex, slaveIndex) =>
Console.WriteLine($"从站 {slaveIndex} 输入数据变化");
// ===== PDO 周期回调 =====
master.Events.ProcessDataCyclicAsync += (masterIndex) =>
{
// 读写 PDO 数据
};