跳到主要内容

异常处理

C++ SDK 使用异常机制报告错误,SDK 操作失败时抛出 DarraException。

DarraException​

class DarraException : public std::runtime_error {
public:
DarraException(int code, const std::string& msg);
DarraException(const std::string& msg);

/** 获取错误码 */
int code() const noexcept;
};

DarraException 继承自 std::runtime_error,包含错误码和错误消息。位于 darra::ethercat 命名空间。

构造方式:

  • DarraException(int code, const std::string& msg) — 带错误码
  • DarraException(const std::string& msg) — 不带错误码(默认 -1)

成员方法:

  • what() (const char*) — 错误消息(继承自 std::runtime_error)
  • code() (int) — 错误码

异常触发场景​

SDK 在以下场景抛出 DarraException:

场景说明
JSON 初始化失败EtherCATMaster_FromJson() / EtherCATMaster_FromJsonFile() 配置无效

CoEAccessDeniedException​

CoE SDO 访问权限相关错误使用独立的异常类型 CoEAccessDeniedException, 位于 darra 命名空间(darra::CoEAccessDeniedException), 继承自 std::runtime_error。

class CoEAccessDeniedException : public std::runtime_error {
public:
CoEAccessDeniedException(uint16_t index, uint8_t sub, const std::string& name,
bool isRead, uint16_t objAccess);

uint16_t Index() const;
uint8_t SubIndex() const;
bool IsReadOperation() const;
uint16_t ObjAccess() const;

static CoEAccessDeniedException CreateWithCustomMessage(
uint16_t index, uint8_t sub, const std::string& msg,
bool isRead, uint16_t objAccess);
};

成员方法:

类别方法类型读写说明
权限错误对象Index()uint16_t只读SDO 对象索引
SubIndex()uint8_t只读子索引
IsReadOperation()bool只读触发操作是否为读(false=写)
ObjAccess()uint16_t只读对象访问位图 (ObjAccess)
what()const char*只读包含中文权限描述的可读消息

触发场景: 对只读对象写入、对只写对象读取、在当前 EtherCAT 状态下不允许的访问(如 OP 写 PreOP-only 对象)等。

示例:

try {
coe.SDOWriteU16(0x6041, 0, 0x0006); // StatusWord 是只读
} catch (const darra::CoEAccessDeniedException& e) {
printf("访问拒绝: idx=0x%04X sub=%u 读=%d (%s)\n",
e.Index(), e.SubIndex(), e.IsReadOperation(), e.what());
}
与 SDOError 的区别

SDO Abort Code(SDOError 枚举)是底层从站返回的中止码, 通过 coe.last_sdo_error() 查询, 不抛异常; CoEAccessDeniedException 是 SDK 发帧前的访问拦截(ECM-SDDOC1):OD 表只在条目明确只读(ObjAccess 有效且不含写位)时才拦,残缺 OD 缓存放行、由从站 SDO abort 当真相源;SDO 写门槛为 PreOp 及以上,Init 拒。

错误处理模式​

异常捕获​

#include "ethercat.hpp"
using namespace darra;

try {
EtherCATMaster master(dll);
master.SetNetwork("\\Device\\NPF_{...}")
.SetENI("config.deni")
.Build();
master.SetState(EcState::OP);
master.Start();
} catch (const ethercat::DarraException& e) {
printf("SDK 错误: %s (code=%d)\n", e.what(), e.code());
} catch (const std::exception& e) {
printf("标准错误: %s\n", e.what());
}

std::optional 返回值​

大部分 Slave 和协议类方法使用 std::optional 表示可能失败的操作,不会抛出异常:

auto& slave = master.GetSlave(1);

// CoE 类型化读取(可能失败)
auto& coe = slave.GetCoE();
auto sw = coe.SDOReadU16(0x6041, 0); // std::optional<uint16_t>
if (sw) {
printf("StatusWord: 0x%04X\n", *sw);
} else {
printf("SDO 读取失败\n");
}

空 vector 返回值​

数据读取方法返回空 std::vector<uint8_t> 通常表示失败,但空 vector 不等于失败:空字符串对象读成功时也是空 vector,须用 coe.last_sdo_error()(SDOError::NoError 即成功)区分;要诚实成败可改用 SDOReadEx / SDOReadExAsync(ok / abort_code / wkc 如实):

auto& coe = slave.GetCoE();

// SDO 读取
auto data = coe.SDORead(0x6041, 0);
if (data.empty()) {
printf("SDO 读取失败\n");
return;
}

// FoE 文件读取
auto& foe = slave.GetFoE();
auto file = foe.Download("config.xml");
if (file.empty()) {
printf("文件读取失败\n");
}

bool 返回值​

写入操作返回 bool 表示成功/失败:

auto& coe = slave.GetCoE();

bool ok = coe.SDOWriteU16(0x6040, 0, 0x0006);
if (!ok) {
printf("SDO 写入失败\n");
}

Build() 返回值​

EtherCATMaster::Build() 返回 bool 而非抛出异常:

EtherCATMaster master(dll);
master.SetNetwork("\\Device\\NPF_{...}");
if (!master.Build()) {
printf("主站初始化失败\n");
return -1;
}

AL 错误分类​

ALErrorClassifier 对从站 AL Status Code 进行分类,帮助快速判断错误性质和处理策略。

auto category = ALErrorClassifier::Classify(slave.ErrorCode());
switch (category) {
case ALErrorCategory::None: /* 无错误 */ break;
case ALErrorCategory::Transient: /* 瞬态错误,可重试 */ break;
case ALErrorCategory::Configuration: /* 配置错误,检查 PDO/SM */ break;
case ALErrorCategory::Hardware: /* 硬件错误,检查设备 */ break;
case ALErrorCategory::Unknown: /* 未知,查阅 ETG.1000 */ break;
}

详见 主站诊断 - AL 错误分类。

错误码 5023:数据闸关闭(授权未通过)​

原生层把 Windows ERROR_INVALID_STATE(5023)复用为数据闸关闭判据:授权未通过(未授权 / 授权过期 / 在线裁决 DENY / 完整性校验失败)时数据闸保持关闭,数据面调用直接被拒。

调用失败表现
网卡绑定SetNetwork(...) 是 Builder 方法(返回 EtherCATMaster&),原生绑定在 Build() 内执行;失败时 Build() 返回 false
SetState() 进 SafeOp / Operational返回 false;master_state::SetState(...) 返回的 StateResult.message 带原因

5023 与网卡无关。 见到 5023 不要去换网线、重装驱动或重新绑定网卡 —— 先查授权状态。

判读顺序:

  1. Build() 返回 false / SetState() 返回 false 时,先看日志里的原生错误码,不要凭"失败"就归因网卡。
  2. 5023 = 数据闸 / 授权问题;1168 = ERROR_NOT_FOUND(NDIS 协议未绑定网卡);21 = ERROR_NOT_READY(驱动通道未就绪,瞬态可重试)。
  3. 其它值按 Windows 系统错误码解释。
备注

C++ 包装层不对外暴露网卡绑定的原生 Darra_GetLastError()。要精确区分 5023 与 1168 / 21,可直连原生 SetNetwork() 后立即取 Darra_GetLastError()(utils/dll.h,包装 Windows GetLastError()),或读日志里的 SetNetwork: 内部服务异常 / SetState: 内部服务异常 (0x..)。

错误处理最佳实践​

#include "ethercat.hpp"
using namespace darra;

int main() {
try {
// Build 返回 bool
EtherCATMaster master(dll);
master.SetNetwork("\\Device\\NPF_{...}")
.SetENI("config.deni");
if (!master.Build()) {
printf("初始化失败\n");
return -1;
}

auto& slave = master.GetSlave(1);

// 协议操作使用 optional/bool 检查
auto& coe = slave.GetCoE();
if (auto sw = coe.SDOReadU16(0x6041, 0)) {
printf("StatusWord: 0x%04X\n", *sw);
}

if (!coe.SDOWriteU16(0x6040, 0, 0x0006)) {
printf("ControlWord 写入失败\n");
}

// Start/Stop 返回 bool
master.SetState(EcState::OP);
if (!master.Start()) {
printf("启动失败\n");
}

getchar();

} catch (const ethercat::DarraException& e) {
fprintf(stderr, "DarraEtherCAT 错误: %s (code=%d)\n",
e.what(), e.code());
return 1;
}
return 0;
}