SoE (Servo over EtherCAT)
SoE 协议将 SERCOS 伺服通信协议封装在 EtherCAT 邮箱中,适用于 SERCOS 兼容的伺服驱动器。
通过 slave.GetSoE() 获取 SoE& 引用。
大多数 EtherCAT 伺服驱动器使用 CoE + CiA 402 协议栈。SoE 仅用于 SERCOS 兼容的驱动器(如 Bosch Rexroth IndraDrive 系列)。 两者不能混用——从站支持哪种取决于硬件。
SERCOS IDN 体系
SoE 通过 IDN(Identification Number) 寻址驱动器参数。每个 IDN 是一个 16 位编号,代表一个参数(位置、速度、控制字等)。
IDN 命名规则:
| 前缀 | 范围 | 说明 |
|---|---|---|
| S-x-xxxx | 0x0000 – 0x7FFF | SERCOS 标准参数(所有厂商通用) |
| P-x-xxxx | 0x8000 – 0xBFFF | 产品特定参数 |
| — | 0xC000 – 0xFFFF | 厂商自定义参数 |
常用标准 IDN(伺服控制相关):
| IDN | SERCOS 名 | 说明 | 数据类型 |
|---|---|---|---|
| S-0-0001 (0x0001) | Cycle Time | 通信周期时间 | uint, us |
| S-0-0011 (0x000B) | Position Feedback 1 | 实际位置 | int, inc |
| S-0-0012 (0x000C) | Position Command | 目标位置 | int, inc |
| S-0-0013 (0x000D) | Velocity Feedback | 实际速度 | int, rpm |
| S-0-0014 (0x000E) | Velocity Command | 目标速度 | int, rpm |
| S-0-0016 (0x0010) | AT Config | 输入映射配置列表 | list |
| S-0-0019 (0x0013) | Drive Status | 驱动器状态字 | ushort |
| S-0-0024 (0x0018) | MDT Config | 输出映射配置列表 | list |
| S-0-0036 (0x0024) | Max Velocity | 最大速度限制 | uint, rpm |
| S-0-0040 (0x0028) | Homing Velocity | 回零速度 | uint, rpm |
| S-0-0064 (0x0040) | Drive Control Word | 驱动器控制字 | ushort |
| S-0-0071 (0x0047) | Drive Status Word | 驱动器状态字 | ushort |
元素标志(Element Flags)— 读取 IDN 的不同"层面":
| 标志 | 含义 |
|---|---|
| 0x01 | 数据状态 |
| 0x02 | 参数名称(字符串) |
| 0x04 | 属性(数据类型/长度/权限位域) |
| 0x08 | 单位(字符串) |
| 0x10 | 最小值 |
| 0x20 | 最大值 |
| 0x40 | 数据值(默认,最常用) |
| 0x80 | 默认值 |
SoEElementFlags
C++ wrapper 用 enum class SoEElementFlags : uint8_t 表示 Element Flags 位掩码(对齐 C# SoEElementFlags)。提供 operator| / operator& 重载和 ToByte() 显式转换,可直接传给 Read(idn, flags) / Write(idn, ..., flags) 替代裸 uint8_t。
enum class SoEElementFlags : uint8_t {
None = 0x00,
DataState = 0x01,
Name = 0x02,
Attribute = 0x04,
Unit = 0x08,
MinValue = 0x10,
MaxValue = 0x20,
Value = 0x40, // 默认 — 数据值
DefaultValue = 0x80
};
inline constexpr SoEElementFlags operator|(SoEElementFlags, SoEElementFlags);
inline constexpr SoEElementFlags operator&(SoEElementFlags, SoEElementFlags);
inline constexpr uint8_t ToByte (SoEElementFlags);
示例:
using darra::ethercat::SoEElementFlags;
// 同时取 Value + Unit
auto flags = SoEElementFlags::Value | SoEElementFlags::Unit;
auto raw = soe.Read(0x000B, ToByte(flags));
SoEIdnTriple / EncodeIdn / DecodeIdn
ETG.6220 把 16 位 IDN 拆为 3 个字段:is_standard (S/P)、parameter_set (0..7)、data_block (0..0x0FFF)。SDK 提供解析 / 拼装两个工具函数 + 一个三元组结构体,便于跨厂商的 IDN 字符串(如 "S-0-0011" → 0x000B)转换。
struct SoEIdnTriple {
bool is_standard; // true = S- 标准, false = P- 产品/厂商
uint8_t parameter_set; // 0..7
uint16_t data_block; // 0..0x0FFF
};
inline uint16_t EncodeIdn(bool is_standard, uint8_t parameter_set, uint16_t data_block);
inline SoEIdnTriple DecodeIdn(uint16_t idn);
EncodeIdn(...)— 三元组 → 16 位 IDN(S-0-0011→0x000B)DecodeIdn(idn)— 16 位 IDN → 三元组(用于打印 / 校验)
示例:
using darra::ethercat::EncodeIdn;
using darra::ethercat::DecodeIdn;
// "S-0-0011" 实际位置反馈 → 0x000B
uint16_t idn = EncodeIdn(/*is_standard*/ true, /*set*/ 0, /*block*/ 11);
auto t = DecodeIdn(idn);
printf("%s-%u-%04u\n", t.is_standard ? "S" : "P", t.parameter_set, t.data_block);
// 输出: S-0-0011
基本读写
Read()
std::vector<uint8_t> Read(uint16_t idn, uint8_t elementFlags = 0x40,
int timeoutMs = 500) const;
读取 IDN 参数原始字节。失败返回空 vector。elementFlags 默认 0x40 表示数据值。
参数:
idn(uint16_t) — IDN 编号elementFlags(uint8_t) — 元素标志(默认 0x40 = 数据值)timeoutMs(int) — 超时时间(毫秒)
返回值:
std::vector<uint8_t>— 读取的数据,失败返回空 vector
示例:
// 读取通信周期时间 (S-0-0001)
auto data = soe.Read(0x0001, 0x40);
if (data.size() >= 4) {
uint32_t cycleTime;
std::memcpy(&cycleTime, data.data(), 4);
printf("周期时间: %u μs\n", cycleTime);
}
Write()
bool Write(uint16_t idn, const void* data, int dataLen,
uint8_t elementFlags = 0x40, int timeoutMs = 500) const;
写入 IDN 参数。
参数:
idn(uint16_t) — IDN 编号data(const void*) — 要写入的数据指针dataLen(int) — 数据长度(字节)elementFlags(uint8_t) — 元素标志(默认 0x40 = 数据值)timeoutMs(int) — 超时时间(毫秒)
返回值:
bool— 成功返回true
示例:
auto& soe = master.GetSlave(1).GetSoE();
// 读取实际位置 (S-0-0011)
auto pos_data = soe.Read(0x000B);
if (pos_data.size() >= 4) {
int32_t pos;
std::memcpy(&pos, pos_data.data(), 4);
printf("实际位置: %d\n", pos);
}
// 写入控制字 (S-0-0064)
uint16_t cw = 0x0006;
soe.Write(0x0040, &cw, sizeof(cw));
类型化读取
所有方法签名一致:ReadXxx(uint16_t idn, int timeoutMs = 500)
| 方法 | 返回类型 | 说明 |
|---|---|---|
| ReadInt16() | std::optional<int16_t> | 读取 16 位有符号整数 |
| ReadInt32() | std::optional<int32_t> | 读取 32 位有符号整数 |
| ReadUInt16() | std::optional<uint16_t> | 读取 16 位无符号整数 |
| ReadUInt32() | std::optional<uint32_t> | 读取 32 位无符号整数 |
| ReadFloat() | std::optional<float> | 读取单精度浮点数 |
| ReadDouble() | std::optional<double> | 读取双精度浮点数 |
| ReadString() | std::optional<std::string> | 读取字符串 |
示例:
auto& soe = master.GetSlave(1).GetSoE();
// 读取实际位置 (S-0-0011)
auto actualPos = soe.ReadInt32(0x000B);
if (actualPos) printf("实际位置: %d\n", *actualPos);
// 读取驱动器状态字 (S-0-0071)
auto status = soe.ReadUInt16(0x0047);
if (status) printf("状态字: 0x%04X\n", *status);
// 读取参数名称
auto name = soe.ReadString(0x000B);
if (name) printf("参数名: %s\n", name->c_str());
类型化写入
所有方法签名一致:WriteXxx(uint16_t idn, T value, int timeoutMs = 500),返回 bool。
| 方法 | 值类型 | 说明 |
|---|---|---|
| WriteInt16() | int16_t | 写入 16 位有符号整数 |
| WriteInt32() | int32_t | 写入 32 位有符号整数 |
| WriteUInt16() | uint16_t | 写入 16 位无符号整数 |
| WriteUInt32() | uint32_t | 写入 32 位无符号整数 |
| WriteFloat() | float | 写入单精度浮点数 |
| WriteDouble() | double | 写入双精度浮点数 |
| WriteString() | const std::string& | 写入字符串 |
示例:
auto& soe = master.GetSlave(1).GetSoE();
// 设置目标位置 (S-0-0012)
soe.WriteInt32(0x000C, 50000);
// 写控制字 (S-0-0064)
soe.WriteUInt16(0x0040, 0x0006);
元数据读取
ReadName()
std::optional<std::string> ReadName(uint16_t idn, int timeoutMs = 500) const;
读取 IDN 参数名称(内部使用 ElementFlag 0x02)。
ReadUnit()
std::optional<std::string> ReadUnit(uint16_t idn, int timeoutMs = 500) const;
读取 IDN 参数单位(内部使用 ElementFlag 0x08)。
ReadAttributes()
std::optional<SoEAttributes> ReadAttributes(uint16_t idn, int timeoutMs = 500) const;
读取 IDN 参数属性(数据类型、长度、权限位域等)。
ReadMinValue / ReadMaxValue / ReadDefaultValue
std::vector<uint8_t> ReadMinValue(uint16_t idn, int timeoutMs = 500) const;
std::vector<uint8_t> ReadMaxValue(uint16_t idn, int timeoutMs = 500) const;
std::vector<uint8_t> ReadDefaultValue(uint16_t idn, int timeoutMs = 500) const;
读取 IDN 参数的最小值(0x10)、最大值(0x20)、默认值(0x80)。
示例:
auto& soe = master.GetSlave(1).GetSoE();
auto name = soe.ReadName(0x000B);
auto unit = soe.ReadUnit(0x000B);
auto attrs = soe.ReadAttributes(0x000B);
auto minVal = soe.ReadMinValue(0x000B);
auto maxVal = soe.ReadMaxValue(0x000B);
if (name) printf("参数: %s", name->c_str());
if (unit) printf(" (%s)", unit->c_str());
printf("\n");
if (attrs) {
printf("数据类型: %d, 列表=%s, 命令=%s\n",
static_cast<int>(attrs->DataType),
attrs->IsList ? "是" : "否",
attrs->IsCommand ? "是" : "否");
}
if (minVal.size() >= 4 && maxVal.size() >= 4) {
int32_t mn, mx;
std::memcpy(&mn, minVal.data(), 4);
std::memcpy(&mx, maxVal.data(), 4);
printf("范围: %d - %d\n", mn, mx);
}
参数信息
GetAvailableIDNs()
std::vector<uint16_t> GetAvailableIDNs(int timeoutMs = 5000) const;
获取从站支持的所有 IDN 列表。
返回值:
std::vector<uint16_t>— IDN 编号列表
GetParameterInfo()
SoEParameter GetParameterInfo(uint16_t idn, int timeoutMs = 5000) const;
获取 IDN 参数的完整信息(名称、单位、属性、当前值、最小/最大值等)。
返回值:
SoEParameter— 包含完整的参数信息
相关结构:
struct SoEParameter {
uint16_t IDN; // IDN 编号
std::string Name; // 参数名称
std::string Unit; // 单位
SoEAttributes Attributes; // 属性(数据类型、长度等)
std::vector<uint8_t> Value; // 当前值
std::vector<uint8_t> DefaultValue; // 默认值
std::vector<uint8_t> MinValue; // 最小值
std::vector<uint8_t> MaxValue; // 最大值
std::string Category() const; // 类别("Standard" / "Product" / "Vendor")
std::string SercosIDN() const; // SERCOS IDN 格式(如 "S-0-0001")
bool IsReadOnly() const; // 是否只读
std::string AccessMode() const; // 访问权限("RO" / "RW" / "RW*")
std::string FormattedValue() const; // 格式化的当前值
std::string FormattedMinValue() const; // 格式化的最小值
std::string FormattedMaxValue() const; // 格式化的最大值
std::string FormattedDefaultValue() const; // 格式化的默认值
};
struct SoEAttributes {
uint16_t EvaluationFactor; // 评价因子
uint8_t Length; // 数据长度
bool IsList; // 是否为列表
bool IsCommand; // 是否为命令
SoEDataType DataType; // 数据类型
uint8_t Decimals; // 小数位数
bool WriteProtectedPreOp; // PreOp 状态写保护
bool WriteProtectedSafeOp; // SafeOp 状态写保护
bool WriteProtectedOp; // Op 状态写保护
std::string AccessMode() const; // 访问权限字符串 ("RO", "RW", "RW*")
};
enum class SoEDataType : int {
Binary = 0, // 二进制
UInt = 1, // 无符号整数
Int = 2, // 有符号整数
Hexadecimal = 3, // 十六进制
String = 4, // 字符串
IDN = 5, // IDN 引用
Float = 6, // 浮点数
Parameter = 7 // 参数
};
示例:
auto& soe = master.GetSlave(1).GetSoE();
auto param = soe.GetParameterInfo(0x000B);
printf("IDN: %s - %s\n", param.SercosIDN().c_str(), param.Name.c_str());
printf("单位: %s, 访问: %s\n", param.Unit.c_str(), param.AccessMode().c_str());
printf("当前值: %s\n", param.FormattedValue().c_str());
printf("范围: %s - %s\n", param.FormattedMinValue().c_str(), param.FormattedMaxValue().c_str());
AT/MDT 映射
SERCOS 使用 AT(Acknowledge Telegram)和 MDT(Master Data Telegram)传输周期数据:
- AT = 从站→主站的周期输入(实际位置、实际速度、状态字…)
- MDT = 主站→从站的周期输出(目标位置、目标速度、控制字…)
映射配置决定了哪些 IDN 参数被包含在周期数据帧中。
GetIDNMapping()
SoEMappingInfo GetIDNMapping(int timeoutMs = 5000) const;
读取当前驱动器的 AT/MDT 映射配置。
返回值:
SoEMappingInfo— 映射信息对象
相关结构:
struct SoEMappingEntry {
uint16_t IDN; // IDN 编号
std::string Name; // 参数名称
int BitLength; // 数据长度(位)
int ByteLength() const; // 数据长度(字节)
};
struct SoEMappingInfo {
std::vector<SoEMappingEntry> ATMapping; // AT(输入)映射列表
std::vector<SoEMappingEntry> MDTMapping; // MDT(输出)映射列表
int ATBitSize; // AT 总位数
int MDTBitSize; // MDT 总位数
int ATByteSize() const; // AT 总字节数
int MDTByteSize() const; // MDT 总字节数
};
GetAllDriveMappings()
std::map<uint8_t, SoEMappingInfo> GetAllDriveMappings(int timeoutMs = 5000) const;
获取所有驱动器的映射信息(最多扫描 8 个驱动器)。
返回值:
std::map<uint8_t, SoEMappingInfo>— 驱动器编号到映射信息的字典
参数变化通知
SoE Notification 功能允许监控 SERCOS 从站 IDN 参数的变化。通过轮询机制检测参数值改变并返回变化事件列表。
实现机制:
- 硬件通知:通过写入 S-0-0127(通知请求 IDN)尝试启用从站的原生 SoE Notification(OpCode=5)
- 轮询检测:如果从站不支持硬件通知,自动回退到轮询模式,周期性读取参数值并与基线比较
EnableNotification()
bool EnableNotification(uint16_t idn, int timeoutMs = 500);
启用指定 IDN 的参数变化通知。首先尝试通过 S-0-0127 启用硬件通知,无论是否成功都会将 IDN 加入轮询监控列表。
参数:
idn(uint16_t) — 要监控的 IDN 编号timeoutMs(int) — 超时时间(毫秒,默认 500)
返回值:
bool—true表示从站确认支持硬件通知;false表示回退到轮询模式
DisableNotification()
void DisableNotification(uint16_t idn);
禁用指定 IDN 的参数变化通知,从监控列表中移除。
DisableAllNotifications()
void DisableAllNotifications();
禁用所有已启用的通知,清空监控列表。
PollNotifications()
std::vector<SoENotificationEventArgs> PollNotifications(int timeoutMs = 200);
轮询检测所有已监控 IDN 的参数变化。逐个读取当前值并与上次记录值比较,发现变化时返回变化事件列表。适合在定时器或后台线程中周期性调用。
参数:
timeoutMs(int) — 每个 IDN 读取的超时时间(毫秒,默认 200)
返回值:
std::vector<SoENotificationEventArgs>— 本次轮询检测到的变化事件列表
GetMonitoredIDNs()
std::vector<uint16_t> GetMonitoredIDNs() const;
获取当前正在监控的 IDN 列表。
SoENotificationEventArgs
struct SoENotificationEventArgs {
uint16_t SlaveIndex; // 从站索引
uint8_t DriveNumber; // 驱动器编号
uint16_t IDN; // 变化的 IDN 编号
std::vector<uint8_t> NewValue; // 新值
std::vector<uint8_t> OldValue; // 旧值
std::string SercosIDN() const; // 格式化的 SERCOS IDN 名称 (如 "S-0-0127")
std::string ToString() const; // 格式化的事件描述
};
示例:
auto& soe = master.GetSlave(1).GetSoE();
// 启用监控实际位置 (S-0-0011)
bool hwSupported = soe.EnableNotification(0x000B);
printf("硬件通知: %s\n", hwSupported ? "支持" : "回退到轮询");
// 启用监控驱动器状态字 (S-0-0071)
soe.EnableNotification(0x0047);
// 轮询检测变化
while (running) {
auto changes = soe.PollNotifications();
for (auto& c : changes) {
printf("参数变化: %s (IDN 0x%04X)\n", c.SercosIDN().c_str(), c.IDN);
printf(" 旧值: %s\n", FormatByteArray(c.OldValue).c_str());
printf(" 新值: %s\n", FormatByteArray(c.NewValue).c_str());
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
// 查看已监控的 IDN
auto monitored = soe.GetMonitoredIDNs();
printf("正在监控 %zu 个 IDN\n", monitored.size());
// 停止监控
soe.DisableAllNotifications();
程序命令
ExecuteCommand()
bool ExecuteCommand(uint16_t idn, int timeoutMs = 5000, int pollIntervalMs = 50) const;
执行 SoE 程序命令(SERCOS Procedure Command)。通过写入 DataState 元素(0x01)的 bit0 触发命令执行,然后轮询等待命令完成。
参数:
idn(uint16_t) — 要执行的命令 IDN 编号timeoutMs(int) — 等待命令完成的超时时间(毫秒,默认 5000)pollIntervalMs(int) — 轮询间隔(毫秒,默认 50)
返回值:
bool— 命令执行成功返回true,超时或失败返回false
示例:
auto& soe = master.GetSlave(1).GetSoE();
// 执行回零命令 (S-0-0148)
bool success = soe.ExecuteCommand(0x0094);
if (success)
printf("回零完成\n");
else
printf("回零超时或失败\n");
// 自定义超时(长时间命令)
bool ok = soe.ExecuteCommand(0x0094, 30000, 100);
ReadDataState()
std::optional<uint16_t> ReadDataState(uint16_t idn, int timeoutMs = 500) const;
读取 IDN 的 Data State 元素(ElementFlag 0x01)。Data State 包含命令状态、数据有效性等信息。
参数:
idn(uint16_t) — IDN 编号timeoutMs(int) — 超时时间(毫秒,默认 500)
返回值:
std::optional<uint16_t>— Data State 值,失败返回std::nullopt
示例:
auto& soe = master.GetSlave(1).GetSoE();
// 读取 IDN 的 Data State
auto state = soe.ReadDataState(0x0094);
if (state) {
bool commandActive = (*state & 0x01) != 0;
bool commandError = (*state & 0x02) != 0;
printf("命令激活: %s, 命令错误: %s\n",
commandActive ? "是" : "否",
commandError ? "是" : "否");
}
标准 IDN 常量
struct StandardIDN {
static constexpr uint16_t IDNList = 0x0000; // IDN 列表 (S-0-0000)
static constexpr uint16_t CycleTime = 0x0001; // 通信周期时间 (S-0-0001)
static constexpr uint16_t ATConfig = 0x0010; // AT 配置 - 输入映射 (S-0-0016)
static constexpr uint16_t MDTConfig = 0x0018; // MDT 配置 - 输出映射 (S-0-0024)
static constexpr uint16_t NotificationRequest = 0x007F; // 通知请求 (S-0-0127)
};
通过 SoE::StandardIDN::CycleTime 等访问。
完整示例
参数扫描
#include "ethercat.hpp"
using namespace darra;
auto& soe = master.GetSlave(1).GetSoE();
// 获取所有支持的 IDN
auto idns = soe.GetAvailableIDNs();
printf("从站支持 %zu 个 IDN 参数\n\n", idns.size());
int limit = std::min<int>(static_cast<int>(idns.size()), 10);
for (int i = 0; i < limit; ++i) {
auto info = soe.GetParameterInfo(idns[i]);
printf("%s: %s = %s %s [%s]\n",
info.SercosIDN().c_str(),
info.Name.c_str(),
info.FormattedValue().c_str(),
info.Unit.c_str(),
info.AccessMode().c_str());
}
伺服驱动器控制
#include "ethercat.hpp"
using namespace darra;
int main() {
EtherCATMaster master(dll);
master.SetNetwork("\\Device\\NPF_{...}")
.SetENI("config.deni")
.Build();
master.SetState(EcState::OP);
master.Start();
auto& soe = master.GetSlave(1).GetSoE();
// 1. 读取驱动器状态
auto statusWord = soe.ReadUInt16(0x0047);
if (statusWord)
printf("状态字: 0x%04X\n", *statusWord);
// 2. 查看 AT/MDT 映射(了解周期数据布局)
auto mapping = soe.GetIDNMapping();
printf("\nAT 映射(输入 %d 字节):\n", mapping.ATByteSize());
for (auto& entry : mapping.ATMapping)
printf(" IDN 0x%04X: %s (%dB)\n", entry.IDN, entry.Name.c_str(), entry.ByteLength());
printf("\nMDT 映射(输出 %d 字节):\n", mapping.MDTByteSize());
for (auto& entry : mapping.MDTMapping)
printf(" IDN 0x%04X: %s (%dB)\n", entry.IDN, entry.Name.c_str(), entry.ByteLength());
// 3. 写控制字使能驱动器 (S-0-0064)
soe.WriteUInt16(0x0040, 0x0006);
// 4. 设置目标位置 (S-0-0012)
soe.WriteInt32(0x000C, 50000);
// 5. 读取实际位置 (S-0-0011)
auto actualPos = soe.ReadInt32(0x000B);
if (actualPos)
printf("\n实际位置: %d inc\n", *actualPos);
getchar();
return 0;
}
参数变化 / 紧急消息回调
除 PollNotifications() 轮询方式外,SDK 还提供基于 std::function 的回调注册机制。参数变化(IDN Notification)和紧急消息(Emergency)事件由 SDK 后台线程自动检测,触发时回调在后台线程上下文执行——回调函数内如需访问 UI 或共享状态,需自行加锁或投递到主线程。
SoENotificationEvent
struct SoENotificationEvent {
int slave_index; // 从站索引 (1 基)
uint8_t drive_number; // 驱动器编号
uint8_t element_flags; // 元素标志 (0x40 = 数据值)
uint16_t idn; // 变化的 IDN
std::vector<uint8_t> new_value; // 新值原始字节
std::chrono::system_clock::time_point timestamp; // 事件时间戳
};
SoEEmergencyEvent
struct SoEEmergencyEvent {
int slave_index; // 从站索引
uint8_t drive_number; // 驱动器编号
uint16_t error_code; // SERCOS 错误码
std::chrono::system_clock::time_point timestamp; // 事件时间戳
};
AddNotificationCallback()
class SoE {
public:
using NotificationCallback = std::function<void(const SoENotificationEvent&)>;
void AddNotificationCallback(NotificationCallback cb);
};
注册参数变化回调。可多次调用注册多个回调,所有回调按注册顺序依次触发。回调在 SDK 后台通知线程执行。
AddEmergencyCallback()
class SoE {
public:
using EmergencyCallback = std::function<void(const SoEEmergencyEvent&)>;
void AddEmergencyCallback(EmergencyCallback cb);
};
注册紧急消息回调。SERCOS 驱动器触发硬件紧急事件(如过流、过压、通信超时)时,从站通过邮箱向主站上报 Emergency 帧,SDK 解析后触发回调。
示例 (C++17 lambda):
auto& soe = master.GetSlave(1).GetSoE();
// 参数变化回调
soe.AddNotificationCallback([](const SoENotificationEvent& e) {
std::cout << "[" << std::chrono::duration_cast<std::chrono::milliseconds>(
e.timestamp.time_since_epoch()).count()
<< "ms] IDN 0x" << std::hex << e.idn
<< " 变化 (从站 " << std::dec << e.slave_index
<< " 驱动 " << (int)e.drive_number << ")\n";
});
// 紧急消息回调
soe.AddEmergencyCallback([](const SoEEmergencyEvent& e) {
std::cerr << "[EMCY] 从站 " << e.slave_index
<< " 错误码 0x" << std::hex << e.error_code << "\n";
});
// 启用监控目标 IDN(触发上方 Notification 回调)
soe.EnableNotification(0x0047); // 驱动器状态字
soe.EnableNotification(0x000B); // 实际位置
- 回调列表为追加式,SDK 不提供删除单个回调的 API;如需动态启停,可在回调内用原子标志过滤
- 回调在后台线程执行,操作 UI 或非线程安全容器时需自行
std::mutex/PostMessage/Invoke - 回调内避免阻塞(如文件 IO、同步 IO),以免拖慢通知线程
ETG.6220 §7 SoE Notification; SERCOS II 标准 §2.10 Diagnostic data。