EoE (Ethernet over EtherCAT)
EoE 协议通过 EtherCAT 总线实现以太网通信,支持配置 IP/MAC/DNS 地址和以太网帧收发,符合 ETG.1000.6 和 ETG.1020 标准。
通过 slave.eoe() 获取 EoEInstance 实例 —— 该方法直接返回 EoEInstance(不是 Option),是否真正支持 EoE 可用 eoe.is_supported() 判断。
IP / MAC / DNS 配置
所有 getter / setter 都是邮箱事务,因此都带 timeout_ms 参数并返回 Result。IP 类参数用 u32(网络字节序),MAC 用 [u8; 6]。
| 方法 | 类型 | 类别 | 读写 | 说明 |
|---|---|---|---|---|
| ip(timeout_ms) | Result<u32> | 网络配置 | 只读 | IP 地址 (u32, 网络字节序) |
| subnet(timeout_ms) | Result<u32> | 网络配置 | 只读 | 子网掩码 |
| gateway(timeout_ms) | Result<u32> | 网络配置 | 只读 | 默认网关 |
| set_ip(ip, subnet, gateway, timeout_ms) | Result<()> | 网络配置 | 只写 | 一次写入 IP/子网/网关 |
| mac(timeout_ms) | Result<[u8; 6]> | 网络配置 | 只读 | MAC 地址 (6 字节) |
| mac_string(timeout_ms) | Result<String> | 网络配置 | 只读 | MAC 地址 ("AA:BB:CC:DD:EE:FF" 格式) |
| set_mac(&mac, timeout_ms) | Result<()> | 网络配置 | 只写 | 写入 MAC 地址 |
| dns(timeout_ms) | Result<u32> | 网络配置 | 只读 | DNS 服务器地址 |
| set_dns(dns_ip, dns_name, timeout_ms) | Result<()> | 网络配置 | 只写 | 写入 DNS 服务器 IP + 名称 |
示例:
let eoe = slave.eoe();
if !eoe.is_supported() {
println!("从站不支持 EoE");
return Ok(());
}
// 读取 (返回 Result, 需传超时)
println!("IP: 0x{:08X}", eoe.ip(5000)?);
println!("MAC: {}", eoe.mac_string(5000)?);
// 写入: set_ip 一次写 IP/子网/网关 (u32, 网络字节序)
let ip = u32::from_be_bytes([192, 168, 1, 100]);
let subnet = u32::from_be_bytes([255, 255, 255, 0]);
let gateway = u32::from_be_bytes([192, 168, 1, 1]);
eoe.set_ip(ip, subnet, gateway, 5000)?;
eoe.set_mac(&[0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF], 5000)?;
eoe.set_dns(u32::from_be_bytes([8, 8, 8, 8]), "dns.google", 5000)?;
地址过滤器 (ETG.1020)
get_address_filters()
pub fn get_address_filters(&self, timeout_ms: i32) -> Result<Vec<[u8; 6]>>
获取从站当前配置的 MAC 地址过滤器列表(最多 16 条),每条以 [u8; 6] 表示。
set_address_filters_batch()
pub fn set_address_filters_batch(&self, filters: &[[u8; 6]], timeout_ms: i32) -> Result<()>
一次性替换从站全部 MAC 地址过滤器。
示例:
// 替换为只允许两个上位机
eoe.set_address_filters_batch(&[
[0x00, 0x1B, 0x21, 0xAA, 0xBB, 0xCC],
[0x00, 0x1B, 0x21, 0xDD, 0xEE, 0xFF],
], 5000)?;
let filters = eoe.get_address_filters(5000)?;
for f in &filters {
println!("过滤器: {:02X?}", f);
}
以太网帧收发
send_frame()
pub fn send_frame(&self, frame: &[u8], timeout_ms: i32) -> Result<()>
发送以太网帧(端口号在创建 EoEInstance 时指定)。
参数:
frame(&[u8]) — 以太网帧数据timeout_ms(i32) — 超时时间(毫秒)
返回值:
Result<()>— 成功或错误
receive_frame()
pub fn receive_frame(&self, timeout_ms: i32) -> Result<Vec<u8>>
接收以太网帧。
参数:
timeout_ms(i32) — 超时时间(毫秒)
返回值:
Result<Vec<u8>>— 接收到的帧数据
示例:
// 发送帧
let frame = vec![0xFF; 64]; // 示例帧数据
eoe.send_frame(&frame, 5000)?;
// 接收帧
let data = eoe.receive_frame(5000)?;
println!("收到帧: {} 字节", data.len());
Ping 测试
pub fn ping(&self, target_ip: u32, timeout_ms: i32) -> EoEPingResult
通过 EoE 发送 ICMP Ping 并等待响应。该方法不返回 Result,失败信息包含在 EoEPingResult 中。
参数:
target_ip(u32) — 目标 IP 地址(网络字节序,如 192.168.1.1 = 0xC0A80101)timeout_ms(i32) — 超时时间(毫秒)
pub struct EoEPingResult {
pub success: bool, // 是否成功
pub round_trip_time_ms: f64, // 往返时间(毫秒)
pub target_address: String, // 目标地址
pub ttl: u8, // 生存时间
pub error_message: String, // 错误信息
}
示例:
let target = u32::from_be_bytes([192, 168, 1, 1]);
let result = eoe.ping(target, 5000);
if result.success {
println!("Ping 成功: {:.1}ms", result.round_trip_time_ms);
} else {
println!("Ping 失败: {}", result.error_message);
}
本地 ARP 缓存
EoEInstance 内置一份本机的 ARP 缓存(从收到的帧学习 IP↔MAC),不向从站写入。DLL 未导出 EOESetArpEntry,因此 SDK 不提供向从站设置 ARP 条目的方法。
clear_arp_cache()
pub fn clear_arp_cache(&mut self)
清空本地 ARP 缓存(重新创建空缓存)。注意需要 &mut 接收者。
完整示例
网络配置与 Ping
let eoe = slave.eoe();
if !eoe.is_supported() {
return Ok(());
}
println!("当前 IP: 0x{:08X}", eoe.ip(5000)?);
println!("当前 MAC: {}", eoe.mac_string(5000)?);
let ip = u32::from_be_bytes([10, 0, 0, 100]);
let subnet = u32::from_be_bytes([255, 255, 255, 0]);
let gateway = u32::from_be_bytes([10, 0, 0, 1]);
eoe.set_ip(ip, subnet, gateway, 5000)?;
eoe.set_dns(gateway, "", 5000)?;
let ping = eoe.ping(gateway, 5000);
if ping.success {
println!("Ping 成功: {:.1}ms", ping.round_trip_time_ms);
}
以太网帧收发
let eoe = slave.eoe();
if !eoe.is_supported() {
return Ok(());
}
// send_frame / receive_frame 返回 Result
let frame = vec![0xFF; 64];
eoe.send_frame(&frame, 5000)?;
let data = eoe.receive_frame(5000)?;
println!("收到帧: {} 字节", data.len());
异步接收 Hook (frame_received)
对应 C# EoEInstance.FrameReceived 事件。SDK 把底层推送的帧异步转发到用户闭包, 多帧可并发到达, 同主站只保留最近一次注册的回调。
set_receive_hook
pub fn set_receive_hook<F>(&self, callback: F) -> Result<()>
where
F: Fn(u16, u16, &[u8]) + Send + Sync + 'static
注册接收回调, 闭包参数 (master_index, slave_index, &frame_bytes)。同一 master_index 多次调用会覆盖之前的闭包 (与 C# 单回调语义一致)。DLL 未导出 EOESetReceiveHook 时返回 DarraError::Other 而非 panic。
clear_receive_hook
pub fn clear_receive_hook(&self) -> Result<()>
注销本主站的接收回调, 同时通知 DLL EOEClearReceiveHook。
示例:
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
let frame_count = Arc::new(AtomicU64::new(0));
let counter = Arc::clone(&frame_count);
eoe.set_receive_hook(move |master, slave, frame| {
counter.fetch_add(1, Ordering::Relaxed);
println!("[Master {} Slave {}] 收到 {} 字节", master, slave, frame.len());
})?;
// ... 业务逻辑 ...
eoe.clear_receive_hook()?;
println!("总共收到 {} 帧", frame_count.load(Ordering::Relaxed));
回调在 DLL 工作线程执行, 闭包内引用的数据需通过 Arc<Mutex<_>> / AtomicXxx 等同步原语共享; 不要捕获栈上引用。
地址过滤器
| 方法 | 说明 |
|---|---|
get_address_filters(timeout_ms) | 读取当前从站配置的 MAC 过滤器列表 (最多 16 条) |
set_address_filters_batch(&filters, timeout_ms) | 一次性替换全部过滤器 |
clear_arp_cache() | 清空本地 ARP 缓存 (重新创建空缓存) |
// 替换为只允许两个上位机
eoe.set_address_filters_batch(&[
[0x00, 0x1B, 0x21, 0xAA, 0xBB, 0xCC],
[0x00, 0x1B, 0x21, 0xDD, 0xEE, 0xFF],
], 1000)?;
let filters = eoe.get_address_filters(1000)?;
println!("当前过滤器数: {}", filters.len());
带时间戳帧发送
send_frame_with_timestamp
pub fn send_frame_with_timestamp(&self, frame: &[u8], timestamp: u64, timeout_ms: i32) -> Result<()>
ETG.1000.6 §5.7 TimeAppended: 在帧末尾追加 4 字节 (Unsigned32) 时间戳, 取 timestamp 低 32 位 (DC 时间纳秒)。
let dc_ns = master.master_dc_time();
eoe.send_frame_with_timestamp(&frame, dc_ns, 1000)?;
DLL 未导出 EOESetArpEntry, Rust SDK 不提供向从站设置 ARP 条目的方法。本机 ARP 学习与解析由 EoEInstance 内置的本地 ARP 缓存完成 (见 本地 ARP 缓存), 不写入从站。