跳到主要内容

EoE (Ethernet over EtherCAT)

EoE 协议通过 EtherCAT 总线实现以太网通信, 支持配置 IP/MAC/DNS 地址和以太网帧收发, 符合 ETG.1000.6 和 ETG.1020 标准。

通过 slave.GetEoE() 获取 EoE& 引用。使用 IsSupported() 检查从站是否支持 EoE。

属性

读写访问器实时操作: getter 从设备读取当前值, setter 校验格式后立即写入设备。

方法类型访问说明
CanSendFramebool只读是否支持发送以太网帧
CanReceiveFramebool只读是否支持接收以太网帧
CanSetIPbool只读是否支持设置 IP 参数
CanGetIPbool只读是否支持获取 IP 参数
GetIPConfig / SetIPConfigstd::optional<std::tuple<uint32_t,uint32_t,uint32_t>>读写IP / 子网 / 网关 (网络字节序 uint32_t)
GetMAC / SetMACstd::optional<std::array<uint8_t,6>>读写MAC 地址 (6 字节)
GetDNS / SetDNSstd::optional<std::tuple<uint32_t,std::string>>读写DNS 服务器 + 域名
GetFullParam / SetFullParamstd::optional<FullParam>读写一次性读写 IP+MAC+DNS+子网+网关

相关结构:

struct FullParam {
uint32_t ip; // IP 地址
uint32_t subnet; // 子网掩码
uint32_t gateway; // 网关
std::array<uint8_t, 6> mac; // MAC 地址
uint32_t dns_ip; // DNS 服务器 IP
std::string dns_name; // DNS 名称
};

示例:

auto& eoe = master.GetSlave(1).GetEoE();
if (eoe.IsSupported()) {
auto ip_info = eoe.GetIPConfig();
if (ip_info) {
auto [ip, mask, gw] = *ip_info;
printf("IP=%08X Mask=%08X GW=%08X\n", ip, mask, gw);
}

eoe.SetIPConfig(0xC0A80164, 0xFFFFFF00, 0xC0A80101);
uint8_t mac[6] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF};
eoe.SetMAC(mac);
eoe.SetDNS(0x08080808, "dns.google");
}

地址过滤器 (ETG.1020)

GetAddressFilter()

std::optional<std::tuple<uint8_t, std::vector<uint8_t>>>
GetAddressFilter(int maxFilters = 16, int timeoutMs = 500) const;

获取 MAC 地址过滤器列表, 返回 {过滤器数量, MAC 地址数据} 元组。

SetAddressFilter()

bool SetAddressFilter(uint8_t filterCount, const uint8_t* macFilters, int timeoutMs = 500) const;

设置 MAC 地址过滤器列表。

AddAddressFilter / RemoveAddressFilter / ClearAddressFilters

bool AddAddressFilter(const uint8_t mac[6], int timeoutMs = 500) const;
bool RemoveAddressFilter(const uint8_t mac[6], int timeoutMs = 500) const;
bool ClearAddressFilters(int timeoutMs = 500) const;

添加/移除单个 MAC 地址过滤器, 或清空所有过滤器。AddAddressFilter 内部先读取已有列表再追加。

示例:

auto& eoe = master.GetSlave(1).GetEoE();
uint8_t mac[6] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF};
eoe.AddAddressFilter(mac);

auto filters = eoe.GetAddressFilter();
if (filters) {
auto [count, data] = *filters;
printf("过滤器数量: %d\n", count);
}

eoe.ClearAddressFilters();

以太网帧收发

SendFrame()

bool SendFrame(const std::vector<uint8_t>& frame, int timeoutMs = 500) const;
bool SendFrameToPort(const std::vector<uint8_t>& frame, uint8_t port, int timeoutMs = 500) const;

发送以太网帧 (可指定端口)。

ReceiveFrame()

std::vector<uint8_t> ReceiveFrame(int timeoutMs = 500) const;
std::vector<uint8_t> ReceiveFrameFromPort(uint8_t port, int timeoutMs = 500) const;

接收 EoE 帧, 失败返回空 vector。

返回值:

  • std::vector<uint8_t> — 接收到的帧数据, 失败返回空向量

示例:

auto& eoe = master.GetSlave(1).GetEoE();
std::vector<uint8_t> frame = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x08, 0x00};
eoe.SendFrame(frame);

auto recv = eoe.ReceiveFrame();
if (!recv.empty()) {
printf("收到 %zu 字节帧\n", recv.size());
}

Ping 测试

Ping()

EoEPingResult Ping(uint32_t targetIp, int timeoutMs = 3000) const;
EoEPingResult Ping(const std::string& targetIpStr, int timeoutMs = 3000) const;

通过 EoE 发送 ICMP Ping 并等待响应。支持 uint32_t IP 和字符串 IP 两种形式。

参数:

  • targetIp (uint32_t / std::string) — 目标 IP 地址
  • timeoutMs (int) — 超时时间 (毫秒)

相关结构:

struct EoEPingResult {
bool Success; // 是否成功
double RoundTripTimeMs; // 往返时间 (毫秒)
std::string TargetAddress; // 目标地址
uint8_t TTL; // 生存时间
std::string ErrorMessage; // 错误信息
};

示例:

auto& eoe = master.GetSlave(1).GetEoE();
auto result = eoe.Ping("192.168.1.1");
if (result.Success) {
printf("Ping 成功: %.1f ms, TTL=%d\n", result.RoundTripTimeMs, result.TTL);
} else {
printf("Ping 失败: %s\n", result.ErrorMessage.c_str());
}

完整示例

网络配置与 Ping

#include "ethercat.hpp"
using namespace darra::ethercat;

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

auto& eoe = master.GetSlave(1).GetEoE();
if (!eoe.IsSupported()) {
printf("从站不支持 EoE\n");
return 1;
}

// 配置 IP / 子网 / 网关 / DNS
eoe.SetIPConfig(0x0A000064, 0xFFFFFF00, 0x0A000001);
eoe.SetDNS(0x0A000001);

// 读取完整配置
auto param = eoe.GetFullParam();
if (param) {
printf("IP: %08X, 子网: %08X, 网关: %08X\n",
param->ip, param->subnet, param->gateway);
}

// Ping 测试
auto ping = eoe.Ping("10.0.0.1");
if (ping.Success) {
printf("Ping 成功: %.1f ms\n", ping.RoundTripTimeMs);
}

getchar();
return 0;
}

ARP 缓存管理

SetArpEntry()

bool SetArpEntry(uint32_t ip, const uint8_t mac[6], int timeoutMs = 500) const;

设置 ARP 缓存条目, 将指定 IP 地址与 MAC 地址绑定。

参数:

  • ip (uint32_t) — IP 地址 (网络字节序)
  • mac (const uint8_t[6]) — MAC 地址 (6 字节)

ClearArpCache()

bool ClearArpCache(int timeoutMs = 500) const;

清除从站的 ARP 缓存。

示例:

auto& eoe = master.GetSlave(1).GetEoE();
if (eoe.IsSupported()) {
uint8_t mac[6] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF};
eoe.SetArpEntry(0xC0A80101, mac);
eoe.ClearArpCache();
}

异步帧接收 Hook

SendFrame / ReceiveFrame 同步收发适合一问一答的场景, 但 EoE 隧道可能由从站主动推送 (例如远端 PLC 上行报文 / DHCP 应答 / SNMP Trap), 应用如果不持续 ReceiveFrame 就会丢帧. SetReceiveHook 在 DLL 层为本主站绑定一个接收回调, 收到任意从站的 EoE 帧时由 DLL 直接推上来, 应用线程无须轮询.

EoEFrameEvent 与 ReceiveCallback

struct EoEFrameEvent {
uint16_t slaveIndex = 0; // 来源从站 (1-based)
std::vector<uint8_t> data; // 帧字节 (含以太网头, 不含 EtherCAT 包装)
};

using ReceiveCallback = std::function<void(const EoEFrameEvent&)>;

事件结构里只有发送方从站索引和帧字节. DLL 在调用回调前已经把指针深拷贝成 std::vector, 所以分发到任意线程都安全, 不存在生命周期风险.

SetReceiveHook()

bool SetReceiveHook(ReceiveCallback cb);

注册 EoE 帧接收回调. 注册维度 = master_index, 同一 master 多次调用会覆盖前一次, 不允许多个回调并存 (与 C# / Python 单 hook 行为一致). 回调在 PDO 主循环线程触发, 内部不能阻塞 (避免拖慢实时收发).

返回值:

  • booltrue 注册成功, false 表示 DLL 句柄无效或导出未加载

ClearReceiveHook()

bool ClearReceiveHook();

清除 DLL 侧 hook 并删除本层注册表对应条目. 析构 EoE 实例不会自动清除 (因为 hook 维度是 master, 不是 slave 实例), 应用退出前必须显式调用, 否则 DLL 释放后回调仍会触发, 导致 use-after-free.

端到端示例

auto& eoe = master.GetSlave(1).GetEoE();

if (!eoe.SetReceiveHook([](const EoE::EoEFrameEvent& ev) {
printf("[EoE] 从站 %u 收到 %zu 字节\n",
ev.slaveIndex, ev.data.size());
// 推到主线程队列, 不在 PDO 线程里做长耗时处理
})) {
printf("EoE Hook 注册失败 (DLL 未导出)\n");
}

master.SetState(EcState::OP);
master.Start();

// ... 业务运行 ...

// 退出前必须清理
eoe.ClearReceiveHook();
Hook vs ReceiveFrame
  • ReceiveFrame() 同步轮询, 没有 hook 时 DLL 把帧丢进内部环形队列, 应用必须周期性消费, 否则队列满就丢帧
  • SetReceiveHook() 注册后 DLL 不再入队, 直接同步触发回调, 回调内必须在 1ms 内返回
  • 同一 master 二者只能选其一, 注册 hook 后再调 ReceiveFrame() 不会拿到任何帧