事件与回调
通过 master.Events() 获取 MasterEvents& 引用,直接赋值 std::function 回调。
C++ SDK 使用 MasterEvents 类持有所有事件回调,支持 Lambda、成员函数绑定、闭包捕获等现代 C++ 特性。事件通过 master.Events() 访问并直接赋值。
所有事件(PDO 周期回调除外)触发时系统均自动记录日志,无论是否订阅。确保关键运行状态不会被静默丢失。
PDO 周期回调(ProcessDataCyclicAsync、ProcessDataCyclicSync)为高频回调,不记录日志。
功能概览
| 类别 | 事件字段 | 说明 | 自动日志 |
|---|---|---|---|
| PDO 周期回调 | ProcessDataCyclicSync | PDO 同步周期回调(最低延迟) | -- |
| ProcessDataCyclicAsync | PDO 异步周期回调(推荐) | -- | |
| 状态事件 | StateChanged | 主站 EtherCAT 状态变化 | yes |
| SlaveStateChanged | 从站 EtherCAT 状态变化 | yes | |
| 热插拔事件 | SlaveOffline | 从站离线(断开) | yes |
| SlaveOnline | 从站上线(恢复) | yes | |
| 热插拔事件 | SlaveIdentityMismatch | 从站身份不符(换成错误型号/低版本) | yes |
| 异常事件 | EmergencyEvent | CoE Emergency 紧急消息 | yes |
| PDOFrameLoss | PDO 连续丢帧 | yes | |
| DCSyncLost | DC 同步丢失 | yes | |
| 输入数据变化 | InputDataChanged | 从站输入 PDO 数据变化 | -- |
| 冗余事件 | RedundancyModeChanged | 冗余模式变化 | yes |
| SlavePortLinkChanged | 从站端口 link 变化(P0-P3 断开/恢复) | yes |
PDO 周期回调
ProcessDataCyclicSync / ProcessDataCyclicAsync
using ProcessDataCyclicCallback = std::function<void(uint16_t masterIndex)>;
每个 PDO 周期触发一次。Sync 在 PDO 线程中直接执行,Async 在异步线程中执行。
示例:
// 同步模式(最低延迟)
master.Events().ProcessDataCyclicSync = [&](uint16_t mi) {
auto& servo = master.GetSlave(1);
void* input = servo.InputDataPointer();
void* output = servo.OutputDataPointer();
// 零拷贝读写 PDO 数据
};
// 异步模式(不阻塞实时线程)
master.Events().ProcessDataCyclicAsync = [&](uint16_t mi) {
// 可以执行较慢的操作
};
同步回调中不要执行耗时操作(如 SDO 读写、文件 I/O、锁等待),否则会阻塞实时线程导致丢帧。
状态事件
StateChanged
std::function<void(EcState oldState, EcState newState)>
主站 EtherCAT 状态变化时触发(包括主动切换和异常降级)。始终自动记录日志。
示例:
master.Events().StateChanged = [](EcState oldS, EcState newS) {
printf("主站状态: %s -> %s\n",
EcStateFormat::Format(oldS).c_str(),
EcStateFormat::Format(newS).c_str());
};
SlaveStateChanged
using SlaveStateChangeCallback = std::function<void(
uint16_t masterIndex, uint16_t slaveIndex,
EcState oldState, EcState newState)>;
从站 EtherCAT 状态变化时触发(包括主动切换和异常降级)。
示例:
master.Events().SlaveStateChanged = [](uint16_t mi, uint16_t si,
EcState oldS, EcState newS) {
printf("从站 %d 状态: %s -> %s\n", si,
EcStateFormat::Format(oldS).c_str(),
EcStateFormat::Format(newS).c_str());
};
从站上线/离线事件
SlaveOnline / SlaveOffline
using SlaveOnlineCallback = std::function<void(uint16_t slaveIndex)>;
using SlaveOfflineCallback = std::function<void(uint16_t slaveIndex)>;
从站上线或离线时触发。
示例:
master.Events().SlaveOnline = [](uint16_t si) {
printf("从站 %d 上线\n", si);
};
master.Events().SlaveOffline = [](uint16_t si) {
printf("从站 %d 离线!\n", si);
};
IsSlaveOffline()
bool IsSlaveOffline(uint16_t slaveIndex) const;
查询从站是否已被事件系统确认为离线状态。
参数:
slaveIndex(uint16_t) -- 从站索引
返回值:
bool-- 从站离线返回true
示例:
if (master.Events().IsSlaveOffline(2)) {
printf("从站 2 处于离线状态\n");
}
SlaveIdentityMismatch
using SlaveIdentityMismatchCallback = std::function<void(
uint16_t masterIndex, uint16_t slaveIndex,
uint32_t expectedVendor, uint32_t expectedProduct, uint32_t expectedRevision,
uint32_t actualVendor, uint32_t actualProduct, uint32_t actualRevision)>;
从站断电重插后身份不符时触发:识别状态机读取到的 Vendor / Product 与配置不匹配,或 Revision 低于配置(向后兼容:实际 Revision ≥ 配置值视为匹配)。始终自动记录日志。
触发后从站进入 IDENT_REJECTED 状态,不会自动重探测(防止错设备反复刷屏)。操作员检查/更换设备后需调用 acknowledge_slave_replacement 让 SDK 重新探测。
回调参数:
masterIndex(uint16_t) — 主站索引slaveIndex(uint16_t) — 从站索引(1-based)expectedVendor(uint32_t) — 配置期望的厂商 IDexpectedProduct(uint32_t) — 配置期望的产品代码expectedRevision(uint32_t) — 配置期望的最低修订号actualVendor(uint32_t) — 当前实际厂商 IDactualProduct(uint32_t) — 当前实际产品代码actualRevision(uint32_t) — 当前实际修订号
示例:
master.Events().OnSlaveIdentityMismatch(
[&](uint16_t mi, uint16_t si,
uint32_t eV, uint32_t eP, uint32_t eR,
uint32_t aV, uint32_t aP, uint32_t aR) {
printf("从站 %u 身份不符:\n", si);
printf(" 期望: V=0x%08X P=0x%08X R>=0x%08X\n", eV, eP, eR);
printf(" 实际: V=0x%08X P=0x%08X R=0x%08X\n", aV, aP, aR);
// UI 提示用户换回正确设备, 确认后调用:
// master_other::acknowledge_slave_replacement(master, si);
});
同一从站进入 IDENT_REJECTED 状态仅触发一次事件。调用 AcknowledgeSlaveReplacement 后重置探测,身份仍不匹配会再次触发。
AcknowledgeSlaveReplacement()
namespace darra { namespace ethercat { namespace master_other {
bool acknowledge_slave_replacement(EtherCATMaster& m, uint16_t slaveIndex);
} } }
操作员检查 / 更换从站完毕后调用, 让 SDK 复位识别状态机重新探测该从站。配套 SlaveIdentityMismatch 事件使用。
参数:
m(EtherCATMaster&) — 主站引用slaveIndex(uint16_t) — 从站索引 (1-based)
返回值:
bool—true表示已接受并复位 FSM;false表示从站不在IDENT_REJECTED/FAILED状态, 或参数无效
行为:
- 身份纠正 (换回正确设备 / 同型号升级 Revision) → 自动恢复, 触发
SlaveOnline - 身份仍不匹配 → 再次触发
SlaveIdentityMismatch, 回到IDENT_REJECTED
未触发 SlaveIdentityMismatch 之前调用本函数无效, 返回 false。
示例:
using namespace darra::ethercat;
master.Events().OnSlaveIdentityMismatch(
[&](uint16_t mi, uint16_t si,
uint32_t eV, uint32_t eP, uint32_t eR,
uint32_t aV, uint32_t aP, uint32_t aR) {
printf("从站 %u 身份不符\n", si);
if (ShowReplacementDialog(si)) {
bool ok = master_other::acknowledge_slave_replacement(master, si);
if (!ok) printf("确认失败: 从站不在 IDENT_REJECTED 状态\n");
}
});
紧急消息事件
EmergencyEvent
using EmergencyEventCallback = std::function<void(
uint16_t masterIndex, uint16_t slaveIndex,
uint16_t errorCode, uint16_t errorReg,
uint8_t b1, uint16_t w1, uint16_t w2)>;
CoE Emergency 紧急消息事件。数据格式遵循 CANopen Emergency 协议(CiA 301)。
示例:
master.Events().EmergencyEvent = [](uint16_t mi, uint16_t si,
uint16_t code, uint16_t reg,
uint8_t b1, uint16_t w1, uint16_t w2) {
printf("从站 %d 紧急消息: 错误码=0x%04X, 寄存器=0x%04X\n",
si, code, reg);
};
PDO 丢帧事件
PDOFrameLoss
using PDOFrameLossCallback = std::function<void(
uint16_t masterIndex, uint8_t group,
uint32_t consecutiveLost, uint32_t totalLost)>;
PDO 连续丢帧事件。
示例:
master.Events().PDOFrameLoss = [](uint16_t mi, uint8_t grp,
uint32_t consec, uint32_t total) {
printf("组 %d 丢帧: 连续=%u, 累计=%u\n", grp, consec, total);
};
DC 同步丢失事件
DCSyncLost
using DCSyncLostCallback = std::function<void(
uint16_t masterIndex, uint16_t slaveIndex, int diffNs)>;
DC 同步丢失事件。
示例:
master.Events().DCSyncLost = [](uint16_t mi, uint16_t si, int diff) {
printf("从站 %d DC 同步丢失: 偏差 %d ns\n", si, diff);
};
输入数据变化事件
InputDataChanged
using InputDataChangedCallback = std::function<void(
uint16_t masterIndex, uint16_t slaveIndex)>;
从站输入 PDO 数据变化时触发。仅在数据确实变化时触发,不会每周期都触发。
- 每个 PDO 周期自动对比输入数据,检测到变化时触发回调
- 仅检测字节级变化,位级别的变化也会被检测到
示例:
master.Events().InputDataChanged = [&](uint16_t mi, uint16_t si) {
auto& slave = master.GetSlave(si);
void* input = slave.InputDataPointer();
// 输入数据已变化,可安全读取
printf("从站 %d 输入数据变化\n", si);
};
冗余事件
RedundancyModeChanged
using RedundancyModeChangedCallback = std::function<void(
uint16_t masterIndex, int oldMode, int newMode)>;
冗余运行模式变化时触发。模式值:0=Inactive, 1=Dual, 2=Degraded。
示例:
master.Events().RedundancyModeChanged = [](uint16_t mi, int oldM, int newM) {
const char* names[] = {"Inactive", "Dual", "Degraded"};
printf("冗余模式: %s -> %s\n", names[oldM], names[newM]);
};
SlavePortLinkChanged
using SlavePortLinkChangedCallback = std::function<void(
uint16_t masterIndex, uint16_t slaveIndex, uint8_t port, bool isUp)>;
从站 ESC 端口物理链路变化时触发(P0-P3 之一断开或恢复)。每秒诊断周期检测 DL Status 寄存器的 link bit,从 1→0 触发"断开",从 0→1 触发"恢复"。始终自动记录日志。
用于精确定位故障线缆段(相邻从站的对向端口同时报断,说明中间线缆有问题)。
回调参数:
masterIndex(uint16_t) — 主站索引slaveIndex(uint16_t) — 从站索引(1-based)port(uint8_t) — 端口号 0-3,对应 P0 / P1 / P2 / P3isUp(bool) — true=link 恢复, false=link 断开
示例:
master.Events().OnSlavePortLinkChanged(
[](uint16_t mi, uint16_t si, uint8_t port, bool isUp) {
const char* state = isUp ? "恢复" : "断开";
printf("从站 %u 端口 P%u link %s\n", si, port, state);
});
master.GetDiagnostics().BreakPoint() / GetAllBreakPoints() 提供聚合后的故障点视图(断线 + CRC 故障)。SlavePortLinkChanged 是原始事件源,适合做实时告警。详见 主站诊断 - 冗余状态。
ClearAll()
void ClearAll();
清除所有事件订阅,防止内存泄漏。在销毁主站或重新初始化前调用。
示例:
// 清除主站所有事件
master.Events().ClearAll();
// 清除从站事件
master.GetSlave(1).Events().ClearAll();
ClearAll() 等价于 RemoveAll(),同时清除从站离线状态跟踪。建议在 Dispose() 前调用,确保回调中的捕获引用不会产生悬垂指针。
从站事件 (SlaveEvents)
从站也有独立的事件集合 SlaveEvents,包含:
| 字段 | 类型 | 说明 |
|---|---|---|
| StateChanged | std::function<void(EcState, EcState)> | 状态变化 |
| Emergency | std::function<void(uint16_t, uint16_t, uint8_t, uint16_t, uint16_t)> | 紧急消息 |
| Offline | std::function<void()> | 离线 |
| Online | std::function<void()> | 上线 |
| DCSyncLost | std::function<void(int)> | DC 同步丢失 |
| InputChanged | std::function<void()> | 输入数据变化 |
线程安全
所有事件回调在非 UI 线程上触发。在回调中访问共享数据时必须使用同步机制。
线程模型:
ProcessDataCyclicSync-- 在 PDO 实时线程中执行,不要执行任何耗时操作ProcessDataCyclicAsync-- 在独立异步线程中执行,可以执行稍慢的操作- 其他所有事件 -- 在事件分发线程中依次执行,不要长时间阻塞
注意事项:
- 回调中不要调用
SetState()、Stop()等可能导致死锁的方法 - 同步回调中不要进行 SDO 读写、文件 I/O 等阻塞操作
- 使用
std::atomic处理简单标志,使用std::mutex保护复杂数据
C++ 中建议使用 std::mutex 或 std::atomic 保护共享数据:
#include <mutex>
#include <atomic>
std::atomic<bool> g_slave_offline{false};
master.Events().SlaveOffline = [&](uint16_t si) {
g_slave_offline.store(true); // 原子操作
};
// 主线程中安全读取
if (g_slave_offline.load()) {
printf("有从站离线\n");
}
完整示例
#include "ethercat.hpp"
#include <cstdio>
#include <atomic>
using namespace darra;
int main() {
std::atomic<bool> running{true};
try {
EtherCATMaster master(dll);
// 注册所有回调
auto& ev = master.Events();
// ===== 状态事件 =====
ev.StateChanged = [](EcState oldS, EcState newS) {
printf("主站: %s -> %s\n",
EcStateFormat::Format(oldS).c_str(),
EcStateFormat::Format(newS).c_str());
};
ev.SlaveStateChanged = [](uint16_t mi, uint16_t si,
EcState oldS, EcState newS) {
printf("从站 %d: %s -> %s\n", si,
EcStateFormat::Format(oldS).c_str(),
EcStateFormat::Format(newS).c_str());
};
// ===== 热插拔 =====
ev.SlaveOnline = [](uint16_t si) {
printf("从站 %d 上线\n", si);
};
ev.SlaveOffline = [](uint16_t si) {
printf("从站 %d 离线\n", si);
};
// ===== 异常事件 =====
ev.EmergencyEvent = [](uint16_t mi, uint16_t si, uint16_t code,
uint16_t reg, uint8_t b1, uint16_t w1, uint16_t w2) {
printf("从站 %d 紧急: 0x%04X\n", si, code);
};
ev.PDOFrameLoss = [](uint16_t mi, uint8_t grp, uint32_t c, uint32_t t) {
printf("组 %d 丢帧: %u/%u\n", grp, c, t);
};
ev.DCSyncLost = [](uint16_t mi, uint16_t si, int diff) {
printf("从站 %d DC 偏差: %dns\n", si, diff);
};
// ===== 冗余事件 =====
ev.RedundancyModeChanged = [](uint16_t mi, int oldM, int newM) {
const char* names[] = {"Inactive", "Dual", "Degraded"};
printf("冗余模式: %s -> %s\n", names[oldM], names[newM]);
};
// ===== 输入数据变化 =====
ev.InputDataChanged = [](uint16_t mi, uint16_t si) {
printf("从站 %d 输入数据变化\n", si);
};
// ===== PDO 周期回调 =====
ev.ProcessDataCyclicSync = [&](uint16_t mi) {
if (!running.load()) return;
// PDO 数据处理
};
// 启动
master.SetNetwork("\\Device\\NPF_{...}");
master.Build();
master.SetState(EcState::OP);
master.Start();
printf("运行中... 按 Enter 停止\n");
getchar();
running.store(false);
} catch (const ethercat::DarraException& e) {
printf("错误: %s\n", e.what());
return -1;
}
return 0;
}