跳到主要内容

CoE (CANopen over EtherCAT)

通过 slave.GetCoE() 获取 CoE& 引用。从站不支持 CoE 时相关方法返回空值或 false。​

快速开始​

auto& coe = master.GetSlave(1).GetCoE();

// 读取 SDO (原始字节) — 失败 / WKC=0 返回空 vector,必须先判空再 memcpy
auto data = coe.SDORead(0x6041, 0);
if (data.empty()) {
auto err = coe.last_sdo_error();
printf("SDO 失败: 0x%08X\n", static_cast<uint32_t>(err)); // GeneralError ≠ 对象不存在
return;
}
uint16_t status_word;
std::memcpy(&status_word, data.data(), 2);
printf("StatusWord: 0x%04X\n", status_word);

// 写入 SDO (原始字节)
uint16_t control_word = 0x0006;
coe.SDOWrite(0x6040, 0,
reinterpret_cast<const uint8_t*>(&control_word),
sizeof(control_word));
SDORead 失败 / WKC=0 返回空 vector

不要对空 vector 直接 memcpy / 当 uint32_t 解析。last_sdo_error() == SDOError::GeneralError 是邮箱没回来(WKC=0 / 超时),不是「对象不存在」。确定性 abort(不支持访问、对象/子索引不存在:ObjectDoesNotExist 0x06020000 / SubindexDoesNotExist 0x06090011 / 0x06090012)才进 NotPresent 缓存。

SDO 读写​

SDORead()​

std::vector<uint8_t> SDORead(uint16_t index, uint8_t subindex = 0,
bool completeAccess = false) const;

读取 SDO 原始字节数据。失败 / WKC=0 返回空 vector。空成功(空 VISIBLE_STRING)也是空 vector,用 last_sdo_error() 区分:NoError = 空字符串成功,GeneralError = WKC=0 / 超时。

单次读取长度上限 64 KB:SDO 读取与 OD 枚举的应答数据超过 64 KB 时按读失败返回(空 std::vector,last_sdo_error() 如实记录),超大对象请拆分读取。

必须判空
  • 不要对返回值直接 memcpy 成 uint32_t
  • SDOError::GeneralError (0x08000000) 不是「对象不存在」,不写入 NotPresent
  • 确定性 abort 才进 NotPresent(不支持访问、对象/子索引不存在):ObjectDoesNotExist / SubindexDoesNotExist / 0x06090012

SDOReadEx()​

SDOReadExResult SDOReadEx(uint16_t index, uint8_t subindex = 0,
bool completeAccess = false) const;

返回 ok / data / abort_code / wkc 四个字段(wkc 为底层真值,失败路径为 0;仅缓存短路与底层库不可用回落两条路径按 ok 合成 1/0)。dx_sdo_read_ex 不可用时回落 SDORead。热插拔后调 ResetNotPresentCache();ClearSdoCaches() 与其等价(C++ 层无邮箱值缓存)。应答数据超过 64 KB 时按失败返回(ok=false,data 为空),超大对象请拆分读取。

SDOWrite()​

bool SDOWrite(uint16_t index, uint8_t subindex,
const uint8_t* data, int dataLen,
bool completeAccess = false) const;

bool SDOWrite(uint16_t index, uint8_t subindex,
const std::vector<uint8_t>& data,
bool completeAccess = false) const;

写入 SDO 数据。支持原始指针和 std::vector 两种方式。

类型化 SDO 读取​

所有类型化读取方法返回 std::optional,失败时为 std::nullopt:

auto& coe = master.GetSlave(1).GetCoE();

// 读取各类型
auto sw = coe.SDOReadU16(0x6041, 0); // std::optional<uint16_t>
auto pos = coe.SDOReadI32(0x6064, 0); // std::optional<int32_t>
auto vid = coe.SDOReadU32(0x1018, 1); // std::optional<uint32_t>
auto temp = coe.SDOReadFloat(0x6078, 0); // std::optional<float>
auto name = coe.SDOReadString(0x1008, 0); // std::optional<std::string>

if (sw) printf("StatusWord: 0x%04X\n", *sw);
if (pos) printf("位置: %d\n", *pos);
类别方法类型读写说明
类型化 SDO 读取SDOReadU8(index, sub)std::optional<uint8_t>只读无符号 8 位
SDOReadU16(index, sub)std::optional<uint16_t>只读无符号 16 位
SDOReadU32(index, sub)std::optional<uint32_t>只读无符号 32 位
SDOReadI16(index, sub)std::optional<int16_t>只读有符号 16 位
SDOReadI32(index, sub)std::optional<int32_t>只读有符号 32 位
SDOReadFloat(index, sub)std::optional<float>只读32 位浮点
SDOReadString(index, sub)std::optional<std::string>只读字符串

类型化 SDO 写入​

auto& coe = master.GetSlave(1).GetCoE();

// 类型化写入
coe.SDOWriteU16(0x6040, 0, 0x0006); // ControlWord
coe.SDOWriteU32(0x607A, 0, 100000); // TargetPosition (uint32)
coe.SDOWriteI32(0x607A, 0, 100000); // TargetPosition (int32)
coe.SDOWriteU8(0x6060, 0, 8); // 操作模式 CSP
类别方法类型读写说明
类型化 SDO 写入SDOWriteU8(index, sub, val)uint8_t只写无符号 8 位
SDOWriteU16(index, sub, val)uint16_t只写无符号 16 位
SDOWriteU32(index, sub, val)uint32_t只写无符号 32 位
SDOWriteI16(index, sub, val)int16_t只写有符号 16 位
SDOWriteI32(index, sub, val)int32_t只写有符号 32 位

类型化 SDO 补充​

以下类型化方法同样可用:

类别方法类型读写说明
类型化 SDO 补充SDOReadU64(index, sub)std::optional<uint64_t>只读无符号 64 位
SDOReadDouble(index, sub)std::optional<double>只读64 位浮点
SDOWriteU64(index, sub, val)uint64_t只写无符号 64 位
SDOWriteFloat(index, sub, val)float只写32 位浮点
SDOWriteDouble(index, sub, val)double只写64 位浮点

属性​

类别属性类型读写说明
诊断last_sdo_error()SDOError只读最后一次 SDO 操作的错误码

last_sdo_error()​

SDOError last_sdo_error() const;
命名: C++ 用 snake_case last_sdo_error()

C++ SDK 的方法名是 last_sdo_error()(snake_case),返回 SDOError 枚举(注意 SDO 三字母全大写)。

最后一次 SDO 操作的错误码。当 SDORead() / SDOWrite() 失败时,可通过此方法获取 SDO 中止码 (Abort Code)。成功操作后自动重置为 SDOError::NoError。

GeneralError 不是「对象不存在」
  • SDOError::GeneralError (0x08000000) — WKC=0 / 邮箱超时 / 通信未回。abort 可能是上一次残留,不当对象不存在,也 不写入 NotPresent。
  • 确定性 abort 才进 NotPresent(不支持访问、对象/子索引不存在):ObjectDoesNotExist (0x06020000)、SubindexDoesNotExist (0x06090011)、0x06090012。

缓存语义:

CoE 实例内部维护 last_sdo_error_ 成员(声明为 mutable,对调用方仍是 const 接口):

  • SDORead() / SDOWrite() / SDOReadXxx() / SDOWriteXxx() 等所有 SDO 路径在返回前都会更新该缓存
  • 成功 → 缓存被清为 SDOError::NoError
  • 失败 → 缓存被设为底层映射的 SDO Abort Code(没有 Abort Code 时退化为 SDOError::GeneralError)
  • 缓存按 每个 CoE 实例 隔离(即每个从站独立),不会被其他从站的 SDO 失败覆盖
  • 缓存被下次同实例 SDO 调用覆盖, 不支持多线程并发读取 (调用方需在 SDO 调用与 last_sdo_error() 读取之间保持单线程顺序)

示例:

auto data = coe.SDORead(0x6041, 0);
if (data.empty()) {
auto error = coe.last_sdo_error();
printf("SDO 读取失败: 0x%08X\n", static_cast<uint32_t>(error));
}

SDOError 枚举​

SDO 操作错误代码(CANopen 标准 / ETG.1020)。

名称值说明
NoError0x00000000无错误
ToggleBitNotChanged0x05030000Toggle 位未改变
SDOProtocolTimeout0x05040000SDO 协议超时
InvalidCommandSpecifier0x05040001无效的命令标识符
OutOfMemory0x05040005内存不足
UnsupportedAccess0x06010000不支持的访问
ReadWriteOnlyError0x06010001尝试读取只写对象
WriteReadOnlyError0x06010002尝试写入只读对象
SubindexWriteError0x06010003子索引不允许写入
CompleteAccessNotSupported0x06010004不支持完全访问
ObjectLengthExceeded0x06010005对象长度超限
ObjectMappedToRxPDO0x06010006对象已映射到 RxPDO
ObjectDoesNotExist0x06020000对象不存在
CannotBeMappedToPDO0x06040041不可映射到 PDO
ExceedsPDOLength0x06040042超出 PDO 长度
ParameterIncompatibility0x06040043参数不兼容
InternalIncompatibility0x06040047内部不兼容
HardwareAccessError0x06060000硬件访问错误
DataTypeMismatch0x06070010数据类型不匹配
DataTypeTooHigh0x06070012数据类型长度过大
DataTypeTooLow0x06070013数据类型长度过小
SubindexDoesNotExist0x06090011子索引不存在
ValueRangeExceeded0x06090030值超出范围
ValueTooHigh0x06090031值过大
ValueTooLow0x06090032值过小
ModuleIdentListMismatch0x06090033模块列表不匹配
MaxLessThanMin0x06090036最大值小于最小值
GeneralError0x08000000一般错误
DataTransferError0x08000020数据传输错误
LocalControlError0x08000021本地控制错误
DeviceStateError0x08000022设备状态错误
DictionaryError0x08000023字典错误
UnknownError0xFFFFFFFF未知错误

对象字典​

auto& coe = master.GetSlave(1).GetCoE();

// 加载对象字典列表(真发 SDO Information)
// 空字典不是成功:指针空或 Count()==0 都是失败(WKC=0 不当已加载)
EcODList* odList = coe.LoadODList();
if (!odList || odList->Count() == 0) {
printf("对象字典加载失败: 0x%08X\n", static_cast<uint32_t>(coe.last_sdo_error()));
return;
}
printf("对象数量: %d\n", odList->Count());

// 异步加载。OP / SafeOp 只发 SDO Information Basic 0x01(GetSlaveSDOListBasic),
// 不全量读对象描述 0x03。PreOp 才完整加载子索引。
auto future = coe.LoadODListAsync();
EcODList* result = future.get();
if (!result || result->Count() == 0) {
printf("异步加载失败\n");
return;
}

EcODList 字段与方法:

类别方法类型读写说明
EcODListCount()int只读对象数量
Get(uint16_t idx)const ObjectDictionary*只读按索引获取
Get(const std::string& key)const ObjectDictionary*只读按名称获取
GetByPosition(int pos)const ObjectDictionary*只读按位置获取
ContainsKey(uint16_t idx)bool只读检查索引是否存在
ContainsKey(const std::string& key)bool只读检查名称是否存在
Copy()EcODList只读创建副本

支持 range-based for 循环遍历:

auto* odList = coe.LoadODList();
if (!odList || odList->Count() == 0) {
printf("对象字典加载失败: 0x%08X\n", static_cast<uint32_t>(coe.last_sdo_error()));
return;
}
for (auto& od : *odList) {
printf("0x%04X %s (%d 条目)\n", od.index, od.name.c_str(), od.Count());
}

ObjectDictionary 字段与方法:

类别字段/方法类型读写说明
ObjectDictionaryindexuint16_t只读对象索引
namestd::string只读对象名称
datatypeuint16_t只读数据类型
object_codeuint8_t只读对象代码
max_subuint8_t只读最大子索引
entriesstd::vector<ObjectEntry>只读子索引条目
Count()int只读子索引数量
IsVar()bool只读是否为 VAR 类型
IsArray()bool只读是否为 ARRAY 类型
IsRecord()bool只读是否为 RECORD 类型
Get(uint8_t sub)const ObjectEntry*只读按子索引获取条目
ContainsKey(uint8_t sub)bool只读检查子索引是否存在

ObjectEntry 字段与方法:

类别字段/方法类型读写说明
ObjectEntryod_indexuint16_t只读父对象索引
sub_indexuint8_t只读子索引
namestd::string只读条目名称
data_typeuint16_t只读数据类型
bit_lengthuint16_t只读位长度
obj_accessuint16_t只读访问权限
ByteLength()int只读字节长度
CanRead()bool只读是否可读
CanWrite()bool只读是否可写
IsReadOnly()bool只读是否只读
AccessDescription()std::string只读访问权限中文描述

批量 SDO 读取​

ReadMultiple()​

std::vector<SDOReadResult> ReadMultiple(const std::vector<SDOReadEntry>& entries) const;

批量 SDO 读取 — 一次调用读取多个对象。同步版本,依次读取所有条目。

参数:

  • entries — 要读取的 (index, subindex) 列表

返回值:

struct SDOReadEntry {
uint16_t index; // 对象索引
uint8_t subindex; // 子索引
};

struct SDOReadResult {
uint16_t index; // 对象索引
uint8_t subindex; // 子索引
bool success; // 是否成功
std::vector<uint8_t> data; // 读取的数据 (失败时为空)
};

示例:

auto& coe = master.GetSlave(1).GetCoE();

// 批量读取多个对象
std::vector<CoE::SDOReadEntry> entries = {
{0x1018, 1}, // VendorID
{0x1018, 2}, // ProductCode
{0x6041, 0}, // StatusWord
{0x6064, 0}, // ActualPosition
};

auto results = coe.ReadMultiple(entries);
for (auto& r : results) {
if (r.success)
printf("0x%04X:%02X 读取成功 (%zu 字节)\n", r.index, r.subindex, r.data.size());
else
printf("0x%04X:%02X 读取失败\n", r.index, r.subindex);
}

ReadMultipleAsync()​

std::future<std::vector<SDOReadResult>> ReadMultipleAsync(
const std::vector<SDOReadEntry>& entries) const;

批量 SDO 读取的异步版本,在后台线程中执行,不阻塞调用线程。

示例:

auto future = coe.ReadMultipleAsync(entries);
// ... 执行其他操作 ...
auto results = future.get(); // 等待完成

诊断消息 (ETG.1510)​

auto messages = coe.ReadDiagnosticMessages();
for (auto& msg : messages) {
printf("诊断码: 0x%08X, 标志: 0x%04X\n", msg.diag_code, msg.flags);
}
C++ 特有语法糖

跨线程上报诊断时, 可先 auto snaps = darra::sugar::slaves_snapshot(master); 拿 POD 快照再发到 worker 线程, 避免持有 dll_t&. 详见 数据快照.

设备协议检测​

uint16_t profile = coe.GetDeviceProfile();  // 读取 0x1000 低 16 位
bool is402 = coe.IsCiA402(); // 是否为 CiA 402 伺服
bool is401 = coe.IsCiA401(); // 是否为 CiA 401 I/O

紧急消息历史​

C++ wrapper 提供两套接口读取 EMCY:

  • CoE::GetEmergencyHistory() / ClearEmergencyHistory() — 通过 SDO 读写 0x1003 (CANopen Pre-defined Error Field), 按子索引逐条取得从站本地缓存的历史错误帧。
  • darra::ethercat::slave_emcy 命名空间下的自由函数 — 读取运行时实时捕获的 EMCY 事件缓存, 不触发 SDO.

GetEmergencyHistory()​

std::vector<std::vector<uint8_t>> GetEmergencyHistory() const;

SDO 读取 0x1003. 返回的每一项为一帧原始 EMCY 字节 (按 ETG/CiA DS301 §7.2.8.1 定义, 至少包含 2 字节 Error Code + 1 字节 Error Register + 5 字节 Manufacturer-specific).

ClearEmergencyHistory()​

bool ClearEmergencyHistory() const;

写入 0x1003:00 = 0 清空从站历史错误区。 成功返回 true.

slave_emcy 自由函数​

namespace darra::ethercat::slave_emcy {
int emcy_count(dll_t& dll, uint16_t mi, uint16_t si);
std::vector<ec_emcy_record_t>
emcy_history(dll_t& dll, uint16_t mi, uint16_t si);
void clear_emcy(dll_t& dll, uint16_t mi, uint16_t si);
}

读取运行时实时 EMCY 环形缓冲, 返回 ec_emcy_record_t 数组。 不会触发额外 SDO 流量。 每条记录字段如下:

  • timestamp_ms (uint32_t) — 捕获时间戳 (毫秒)
  • slave_index (uint16_t) — 从站索引
  • error_code (uint16_t) — EMCY 错误码
  • error_register (uint8_t) — 错误寄存器
  • data (uint8_t[5]) — 厂商自定义字节

示例 (SDO 0x1003 方式):

auto& coe = master.GetSlave(1).GetCoE();
auto history = coe.GetEmergencyHistory();
printf("0x1003 历史条目: %zu\n", history.size());
for (auto& frame : history) {
if (frame.size() >= 3) {
uint16_t err_code;
std::memcpy(&err_code, frame.data(), 2);
uint8_t err_reg = frame[2];
printf("EMCY 错误码=0x%04X 寄存器=0x%02X\n", err_code, err_reg);
}
}
coe.ClearEmergencyHistory();

示例 (实时 EMCY 缓存方式):

using namespace darra::ethercat;
int n = slave_emcy::emcy_count(dll, mi, si);
auto records = slave_emcy::emcy_history(dll, mi, si);
for (auto& r : records) {
printf("EMCY t=%u ms slave=%u code=0x%04X reg=0x%02X\n",
r.timestamp_ms, r.slave_index, r.error_code, r.error_register);
}
slave_emcy::clear_emcy(dll, mi, si);

EcDataType 枚举​

CoE 对象字典中使用的 EtherCAT 数据类型标识符。

名称值C++ 类型说明
BOOLEAN0x0001bool布尔
INTEGER80x0002int8_t有符号 8 位
INTEGER160x0003int16_t有符号 16 位
INTEGER320x0004int32_t有符号 32 位
UNSIGNED80x0005uint8_t无符号 8 位
UNSIGNED160x0006uint16_t无符号 16 位
UNSIGNED320x0007uint32_t无符号 32 位
REAL320x0008float单精度浮点
VISIBLE_STRING0x0009std::string可见字符串
OCTET_STRING0x000Astd::vector<uint8_t>字节数组
UNICODE_STRING0x000Bstd::stringUnicode 字符串
TIME_OF_DAY0x000Cstd::vector<uint8_t>时间(原始字节)
TIME_DIFF0x000Dstd::vector<uint8_t>时间差(原始字节)
DOMAIN0x000Fstd::vector<uint8_t>原始数据
REAL640x0011double双精度浮点
INTEGER640x0015int64_t有符号 64 位
UNSIGNED640x001Buint64_t无符号 64 位

对象字典遍历​

auto* odList = coe.LoadODList();
if (!odList || odList->Count() == 0) {
printf("对象字典加载失败: 0x%08X\n", static_cast<uint32_t>(coe.last_sdo_error()));
return;
}
for (auto& od : *odList) {
printf("0x%04X %s (子索引数=%d)\n", od.index, od.name.c_str(), od.Count());
for (auto& entry : od.entries) {
printf(" [%d] %s (类型=0x%04X, %d位, %s)\n",
entry.sub_index, entry.name.c_str(),
entry.data_type, entry.bit_length,
entry.AccessDescription().c_str());
}
}

完整示例​

#include "ethercat.hpp"
using namespace darra;

int main() {
EtherCATMaster master(dll);
master.SetNetwork("\\Device\\NPF_{...}");
master.Build();
master.SetState(EcState::OP);
master.Start();

auto& coe = master.GetSlave(1).GetCoE();

// 读取 Identity Object
auto vid = coe.SDOReadU32(0x1018, 1);
auto pid = coe.SDOReadU32(0x1018, 2);
if (vid && pid)
printf("VID=0x%08X PID=0x%08X\n", *vid, *pid);

// 读取 StatusWord
auto sw = coe.SDOReadU16(0x6041, 0);
if (sw) printf("StatusWord: 0x%04X\n", *sw);

// 写入 ControlWord
coe.SDOWriteU16(0x6040, 0, 0x0006);

// 设置操作模式为 CSP
coe.SDOWriteU8(0x6060, 0, 8);

// 完全访问模式读取 PDO 映射
auto pdo_map = coe.SDORead(0x1C12, 0, true);
if (!pdo_map.empty())
printf("PDO 映射数据: %zu 字节\n", pdo_map.size());

// 读取设备名称
auto name = coe.SDOReadString(0x1008, 0);
if (name) printf("设备名称: %s\n", name->c_str());

return 0;
}

CoE 诊断历史 (0x10F3, ETG.1020 §16)​

从站在 0x10F3 对象下维护一个诊断消息环形缓冲区, 记录硬件故障、通信警告、参数超限等事件。 C++ wrapper 通过一次调用 ReadDiagnosticMessages() 拉取当前缓冲区内最多 20 条消息 (SDO 逐子索引读取)。如需细粒度的"是否有新消息"轻量轮询或逐条确认接口, 请直接使用 SDORead(0x10F3, ...) 按 ETG.1020 自行封装。

ReadDiagnosticMessages()​

std::vector<DiagnosticMessage> ReadDiagnosticMessages() const;

读取 0x10F3 诊断历史, 返回 DiagnosticMessage 列表 (最多 20 条). 从站不支持或尚无消息时返回空 vector.

相关结构:

struct DiagnosticMessage {
uint8_t sub_index; // 子索引
uint32_t diag_code; // 诊断码 (ETG.1020 Table 65)
uint16_t flags; // 标志位
uint16_t text_index; // 文本索引
std::vector<uint8_t> raw_data; // 原始字节 (含上述头 + 参数)
};

示例:

auto& coe = master.GetSlave(1).GetCoE();
auto messages = coe.ReadDiagnosticMessages();
for (auto& msg : messages) {
printf("[Sub=%u] 诊断码: 0x%08X, Flags: 0x%04X, TextIdx: 0x%04X\n",
msg.sub_index, msg.diag_code, msg.flags, msg.text_index);
}
标准依据

ETG.1020 §16 诊断历史对象; CANopen CiA 301 DS301 §7.5.3.

0x10F3 诊断历史 (轻量轮询 + 逐条确认)​

ReadDiagnosticMessages() 一次性把 0x10F3 子索引拉一遍, 适合"导出全部诊断"的离线场景。 实时监控如果想避免反复读取, 应使用 ETG.1020 §16 的轻量轮询接口: 先 PollHasNewDiagnostic() 看有没有新消息, 有再读 ReadDiagnosticMeta() 拿元数据 (newest 编号 / overrun 标志), 按编号 ReadDiagnosticMessage() 取一条, 处理完写 AcknowledgeDiagnostic() 通知从站删除。

四个 API 由 SDK 在首次调用时按需绑定底层运行时入口并缓存。 当前运行时不支持该轻量轮询接口时, 所有调用安静返回 false / nullopt / -1, 不抛异常。 失败原因可读 LastDiagAbortCode() 拿 SDO Abort Code.

DiagMeta 结构​

struct DiagMeta {
uint8_t maxMessages; // 0x10F3:01 缓冲区最大消息数
uint8_t newestMessage; // 0x10F3:02 最新消息编号 (环形写入下一个位置)
uint8_t newestAcknowledged; // 0x10F3:03 最后一个已确认编号
uint16_t flags; // 0x10F3:05 Bit4=RingBuffer, Bit5=Overrun

bool isRingBuffer() const; // Bit4
bool hasOverrun() const; // Bit5 (从消息超过缓冲容量, 旧消息已被覆盖)
};

PollHasNewDiagnostic()​

int PollHasNewDiagnostic();

轻量轮询 — 不消耗邮箱, 只查询 newestMessage > newestAcknowledged. 适合周期性检查。

返回值:

  • >0 — 有新诊断消息
  • 0 — 无新消息
  • <0 — 当前运行时不支持该接口或通信失败

ReadDiagnosticMeta()​

std::optional<DiagMeta> ReadDiagnosticMeta();

读取 0x10F3:01..05 元数据。 失败返回 std::nullopt.

ReadDiagnosticMessage()​

std::vector<uint8_t> ReadDiagnosticMessage(uint8_t msgIdx, size_t bufSize = 1024);

读取指定编号的诊断消息字节 (msgIdx 通常 6..255). bufSize 默认 1024, ETG.1020 推荐。 失败返回空 vector, 具体错误见 LastDiagAbortCode().

AcknowledgeDiagnostic()​

bool AcknowledgeDiagnostic(uint8_t msgIdx);

把 msgIdx 写到 0x10F3:03, 通知从站可以从环形缓冲清除该条及之前的消息。

LastDiagAbortCode()​

uint32_t LastDiagAbortCode() const;

最近一次 4 个诊断 API 的 SDO Abort Code, 0 表示无错误。

端到端示例​

auto& coe = master.GetSlave(1).GetCoE();

while (running) {
int n = coe.PollHasNewDiagnostic();
if (n <= 0) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
continue;
}

auto meta = coe.ReadDiagnosticMeta();
if (!meta) {
printf("读元数据失败, AbortCode=0x%08X\n", coe.LastDiagAbortCode());
continue;
}

if (meta->hasOverrun()) {
printf("[警告] 诊断历史溢出, 部分旧消息丢失\n");
}

// 从最后已确认的下一条开始, 读到最新
for (uint8_t i = static_cast<uint8_t>(meta->newestAcknowledged + 1);
i != static_cast<uint8_t>(meta->newestMessage + 1); ++i) {
auto bytes = coe.ReadDiagnosticMessage(i);
if (bytes.size() < 8) continue;

uint32_t diagCode;
uint16_t flags, textIdx;
std::memcpy(&diagCode, bytes.data(), 4);
std::memcpy(&flags, bytes.data() + 4, 2);
std::memcpy(&textIdx, bytes.data() + 6, 2);

printf("Diag[%u] code=0x%08X flags=0x%04X textIdx=0x%04X\n",
i, diagCode, flags, textIdx);

coe.AcknowledgeDiagnostic(i);
}
}
与 ReadDiagnosticMessages() 的取舍
  • ReadDiagnosticMessages() — 一次拉满最多 20 条, 不区分新旧, 适合离线导出
  • 4 个轻量 API — 增量轮询 + 逐条确认, 适合 7×24 实时监控, 避免重复读取与缓冲溢出
  • 不要混用。 一旦走轻量 API, 由应用维护 newestAcknowledged 进度, 不要再调 ReadDiagnosticMessages() 干扰从站环形缓冲
参考

ETG.1020 §16 Table 65; ETG.1510 §6.2 Diagnosis history.