跳到主要内容

事件与回调

通过 master.Events() 获取 MasterEvents& 引用,直接赋值 std::function 回调。

与 C API 的区别

C++ SDK 使用 MasterEvents 类持有所有事件回调,支持 Lambda、成员函数绑定、闭包捕获等现代 C++ 特性。事件通过 master.Events() 访问并直接赋值。

自动日志

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

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

功能概览

类别事件字段说明自动日志
PDO 周期回调ProcessDataCyclicSyncPDO 同步周期回调(最低延迟)--
ProcessDataCyclicAsyncPDO 异步周期回调(推荐)--
状态事件StateChanged主站 EtherCAT 状态变化yes
SlaveStateChanged从站 EtherCAT 状态变化yes
热插拔事件SlaveOffline从站离线(断开)yes
SlaveOnline从站上线(恢复)yes
热插拔事件SlaveIdentityMismatch从站身份不符(换成错误型号/低版本)yes
异常事件EmergencyEventCoE Emergency 紧急消息yes
PDOFrameLossPDO 连续丢帧yes
DCSyncLostDC 同步丢失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) — 配置期望的厂商 ID
  • expectedProduct (uint32_t) — 配置期望的产品代码
  • expectedRevision (uint32_t) — 配置期望的最低修订号
  • actualVendor (uint32_t) — 当前实际厂商 ID
  • actualProduct (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)

返回值:

  • booltrue 表示已接受并复位 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 / P3
  • isUp (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,包含:

字段类型说明
StateChangedstd::function<void(EcState, EcState)>状态变化
Emergencystd::function<void(uint16_t, uint16_t, uint8_t, uint16_t, uint16_t)>紧急消息
Offlinestd::function<void()>离线
Onlinestd::function<void()>上线
DCSyncLoststd::function<void(int)>DC 同步丢失
InputChangedstd::function<void()>输入数据变化

线程安全

回调线程

所有事件回调在非 UI 线程上触发。在回调中访问共享数据时必须使用同步机制。

线程模型:

  • ProcessDataCyclicSync -- 在 PDO 实时线程中执行,不要执行任何耗时操作
  • ProcessDataCyclicAsync -- 在独立异步线程中执行,可以执行稍慢的操作
  • 其他所有事件 -- 在事件分发线程中依次执行,不要长时间阻塞

注意事项:

  • 回调中不要调用 SetState()Stop() 等可能导致死锁的方法
  • 同步回调中不要进行 SDO 读写、文件 I/O 等阻塞操作
  • 使用 std::atomic 处理简单标志,使用 std::mutex 保护复杂数据

C++ 中建议使用 std::mutexstd::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;
}