EoE (Ethernet over EtherCAT)
EoE 协议通过 EtherCAT 总线实现以太网通信, 支持配置 IP/MAC/DNS 地址和以太网帧收发, 符合 ETG.1000.6 和 ETG.1020 标准。
通过 slave.GetEoE() 获取 EoE& 引用。使用 IsSupported() 检查从站是否支持 EoE。
属性
读写访问器实时操作: getter 从设备读取当前值, setter 校验格式后立即写入设备。
| 方法 | 类型 | 访问 | 说明 |
|---|---|---|---|
| CanSendFrame | bool | 只读 | 是否支持发送以太网帧 |
| CanReceiveFrame | bool | 只读 | 是否支持接收以太网帧 |
| CanSetIP | bool | 只读 | 是否支持设置 IP 参数 |
| CanGetIP | bool | 只读 | 是否支持获取 IP 参数 |
| GetIPConfig / SetIPConfig | std::optional<std::tuple<uint32_t,uint32_t,uint32_t>> | 读写 | IP / 子网 / 网关 (网络字节序 uint32_t) |
| GetMAC / SetMAC | std::optional<std::array<uint8_t,6>> | 读写 | MAC 地址 (6 字节) |
| GetDNS / SetDNS | std::optional<std::tuple<uint32_t,std::string>> | 读写 | DNS 服务器 + 域名 |
| GetFullParam / SetFullParam | std::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 主循环线程触发, 内部不能阻塞 (避免拖慢实时收发).
返回值:
bool—true注册成功,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();
ReceiveFrame()同步轮询, 没有 hook 时 DLL 把帧丢进内部环形队列, 应用必须周期性消费, 否则队列满就丢帧SetReceiveHook()注册后 DLL 不再入队, 直接同步触发回调, 回调内必须在 1ms 内返回- 同一 master 二者只能选其一, 注册 hook 后再调
ReceiveFrame()不会拿到任何帧