邮箱网关 (Mailbox Gateway)
邮箱网关实现 ETG.8200 标准, 允许外部诊断工具通过 UDP/IP 访问 EtherCAT 主站和从站的对象字典。
通过 master.MailboxGateway() 访问。
邮箱网关服务符合 ETG.8200 标准规范, 使用标准 UDP 端口 0x88A4 (34980), 帧结构完全符合 Table 1 要求。
服务控制
Port
uint16_t Port() const;
bool SetPort(uint16_t port);
UDP 端口号, 默认 0x88A4 (34980)。启动后会更新为实际绑定的端口。
运行多个主站实例时, 端口会根据 MasterNumber 自动偏移, 避免端口冲突:
- 实例 1:
34980(默认) - 实例 2:
34981 - 实例 N:
34980 + (N - 1)
如需手动指定端口, 在 Start() 前调用 SetPort() 即可覆盖自动值。
端口号必须在服务启动前设置。服务运行时无法更改端口。如果期望端口被占用, 会自动回退到可用端口, Port() 会返回实际值。
IsRunning
bool IsRunning() const;
服务是否正在运行。
Start()
bool Start();
启动邮箱网关服务, 开始监听 UDP 请求。如果期望端口被占用, 会自动尝试备用端口, 最终由系统分配。
返回值:
bool— 启动成功返回true
示例:
auto& gateway = master.MailboxGateway();
gateway.Start();
printf("邮箱网关已启动, 端口: %u\n", gateway.Port());
Stop()
void Stop();
停止邮箱网关服务, 释放 UDP 端口。停止后可再次调用 Start() 重新启动。
示例:
master.MailboxGateway().Stop();
统计信息
GetStatistics()
MailboxGatewayStats GetStatistics() const;
获取邮箱网关的运行统计快照。所有 5 种 SDK 语言 (C#/C++/Java/Python/Rust) 暴露同一组 8 个核心字段, 便于跨语言诊断对齐。
返回值字段 (MailboxGatewayStats):
| 字段 | 类型 | 说明 |
|---|---|---|
| RxPackets | uint64_t | 收到的 UDP 数据报总数 |
| TxPackets | uint64_t | 发送的 UDP 响应总数 |
| RxBytes | uint64_t | 入站字节累计 |
| TxBytes | uint64_t | 出站字节累计 |
| MasterOdRequests | uint64_t | 路由到主站对象字典 (ETG.1510, Address = 0x0000) 的请求数 |
| SlaveRequests | uint64_t | 路由到从站邮箱 (CoE / SoE / FoE / VoE) 的请求数 |
| DropMalformed | uint64_t | 帧格式错误丢弃数 |
| DropTimeout | uint64_t | 处理 / 转发超时丢弃数 |
这 8 个字段在 5 个 SDK 中名字与语义完全一致。诊断脚本可以无缝在 C++ 与其他语言之间互译。
示例:
auto stats = master.MailboxGateway().GetStatistics();
printf("Mailbox Gateway 运行 %llu req / %llu resp\n",
(unsigned long long)stats.RxPackets,
(unsigned long long)stats.TxPackets);
printf(" Master OD: %llu, Slave 邮箱: %llu\n",
(unsigned long long)stats.MasterOdRequests,
(unsigned long long)stats.SlaveRequests);
printf(" 丢弃: 格式错=%llu, 超时=%llu\n",
(unsigned long long)stats.DropMalformed,
(unsigned long long)stats.DropTimeout);
支持的协议
| 协议 | 类型 | 说明 |
|---|---|---|
| CoE | 0x03 | SDO 读写, 支持主站 (ETG.1510) 和从站对象字典 |
| SoE | 0x04 | IDN 参数读写 (ETG.1020 标准) |
| FoE | 0x05 | 文件传输 (当前仅支持单包) |
| VoE | 0x0F | 厂商特定协议透传 |
- Address =
0x0000-> 访问主站对象字典 (仅 CoE) - Address = 从站站地址 -> 透传到对应从站 (CoE / SoE / FoE / VoE)
帧结构
EtherCAT Header:
- Length: Mailbox Header + Data 的总长度
- Data Type: 固定为 0x05 (Mailbox communication)
Mailbox Header:
- Length: 邮箱数据部分长度
- Address: 0x0000=主站, 其他=从站站地址
- Type: 0x03=CoE, 0x04=SoE, 0x05=FoE, 0x0F=VoE
- Cnt: 邮箱计数器 (1-7 循环)
完整示例
场景 A — 集成现有配置/调试工具
让第三方配置工具 (或工程师工具) 通过 UDP 访问主站/从站对象字典, 无需修改被控设备固件。
场景 B — 自定义诊断或自动化脚本
通过脚本/服务定期采集从站参数、批量读取对象或执行远程配置。
场景 C — 远程监控与告警采集
把网关用于远程被动监控 (周期性读取或订阅), 并把关键数据汇报到集中监控系统。
自己启动案例
#include "ethercat.hpp"
using namespace darra::ethercat;
int main() {
EtherCATMaster master(dll);
master.SetEni("config.xml");
master.Build();
master.SetState(EcState::OP);
auto& gateway = master.MailboxGateway();
// gateway.SetPort(12345); // 默认 34980, 如需修改请在 Start 前设置
gateway.Start();
printf("邮箱网关已启动, 端口: %u\n", gateway.Port());
getchar();
gateway.Stop();
master.Close();
return 0;
}
外部代码案例 (C++)
#include <cstdint>
#include <cstring>
#include <vector>
#include <string>
#include <stdexcept>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#endif
class MailboxGatewayClient {
public:
MailboxGatewayClient(const std::string& host = "127.0.0.1", uint16_t port = 0x88A4)
: host_(host), port_(port) {
sock_ = socket(AF_INET, SOCK_DGRAM, 0);
addr_.sin_family = AF_INET;
addr_.sin_port = htons(port);
inet_pton(AF_INET, host.c_str(), &addr_.sin_addr);
}
~MailboxGatewayClient() { if (sock_ >= 0) close(sock_); }
std::vector<uint8_t> SendRequest(uint16_t address, uint8_t mailboxType,
const std::vector<uint8_t>& mailboxData) {
auto frame = BuildFrame(address, mailboxType, mailboxData);
sendto(sock_, reinterpret_cast<const char*>(frame.data()),
(int)frame.size(), 0,
reinterpret_cast<sockaddr*>(&addr_), sizeof(addr_));
std::vector<uint8_t> resp(4096);
sockaddr_in from{}; socklen_t flen = sizeof(from);
int n = recvfrom(sock_, reinterpret_cast<char*>(resp.data()),
(int)resp.size(), 0,
reinterpret_cast<sockaddr*>(&from), &flen);
if (n <= 0) throw std::runtime_error("recvfrom failed");
resp.resize(n);
return resp;
}
// CoE SDO Upload (读取对象字典)
std::vector<uint8_t> CoeSdoRead(uint16_t address, uint16_t index, uint8_t subindex) {
std::vector<uint8_t> coData(8, 0);
coData[1] = 0x20; // CoE Type: SDO Request
coData[2] = 0x40; // SDO Upload (Read)
std::memcpy(&coData[3], &index, 2);
coData[5] = subindex;
return SendRequest(address, 0x03, coData);
}
// CoE SDO Download (写入对象字典)
std::vector<uint8_t> CoeSdoWrite(uint16_t address, uint16_t index, uint8_t subindex,
const std::vector<uint8_t>& value) {
std::vector<uint8_t> coData(6 + value.size(), 0);
coData[1] = 0x20;
coData[2] = 0x20; // SDO Download (Write)
std::memcpy(&coData[3], &index, 2);
coData[5] = subindex;
std::memcpy(&coData[6], value.data(), value.size());
return SendRequest(address, 0x03, coData);
}
// SoE Read (读取 IDN 参数)
std::vector<uint8_t> SoeRead(uint16_t address, uint16_t idn,
uint8_t driveNo = 0, uint8_t element = 0x05) {
std::vector<uint8_t> soe(4, 0);
soe[0] = 0x01; // OpCode: Read
soe[1] = static_cast<uint8_t>((driveNo << 3) | (element & 0x07));
std::memcpy(&soe[2], &idn, 2);
return SendRequest(address, 0x04, soe);
}
// FoE Read (从从站下载文件, 仅支持单包)
std::vector<uint8_t> FoeRead(uint16_t address, const std::string& filename) {
std::string name = filename + '\0';
std::vector<uint8_t> foe(5 + name.size(), 0);
foe[0] = 0x01; // OpCode: Read Request
std::memcpy(&foe[5], name.data(), name.size());
return SendRequest(address, 0x05, foe);
}
// VoE 发送/接收 (厂商特定协议)
std::vector<uint8_t> VoeSendReceive(uint16_t address, uint32_t vendorId,
uint16_t vendorType,
const std::vector<uint8_t>& data) {
std::vector<uint8_t> voe(6 + data.size(), 0);
std::memcpy(&voe[0], &vendorId, 4);
std::memcpy(&voe[4], &vendorType, 2);
std::memcpy(&voe[6], data.data(), data.size());
return SendRequest(address, 0x0F, voe);
}
private:
std::vector<uint8_t> BuildFrame(uint16_t address, uint8_t mailboxType,
const std::vector<uint8_t>& mailboxData) {
uint16_t mbLen = static_cast<uint16_t>(mailboxData.size());
uint16_t ecatLen = 6 + mbLen;
uint16_t ecatHeader = static_cast<uint16_t>((0x05 << 12) | (ecatLen & 0x07FF));
std::vector<uint8_t> frame(2 + 6 + mbLen, 0);
std::memcpy(&frame[0], &ecatHeader, 2);
std::memcpy(&frame[2], &mbLen, 2);
std::memcpy(&frame[4], &address, 2);
frame[6] = 0x00;
frame[7] = static_cast<uint8_t>(mailboxType << 4);
std::memcpy(&frame[8], mailboxData.data(), mbLen);
return frame;
}
std::string host_;
uint16_t port_;
int sock_ = -1;
sockaddr_in addr_{};
};
// --- 使用示例 ---
// MailboxGatewayClient client("192.168.1.100");
// auto resp = client.CoeSdoRead(0x0000, 0x1018, 0x01); // 主站 OD
// resp = client.CoeSdoRead(0x03E9, 0x6040, 0x00); // 从站 OD
// resp = client.SoeRead(0x03E9, 32); // 从站 IDN
// resp = client.FoeRead(0x03E9, "firmware.bin"); // 文件
注意事项
默认网关是强透传的, 允许网络访问主站和从站的全部对象字典。建议:
- 务必在受控网络中使用, 或通过 IP 白名单 / VPN 隧道限制访问
- 配合操作系统防火墙规则, 仅放行可信来源 IP
- 生产环境中谨慎开启, 避免暴露到公网
邮箱网关运行在独立线程, 对主循环性能影响极小。但大量同步 SDO/邮箱操作仍可能影响总体延迟 — 请合理限制并发与速率。
协议详解
CoE (CAN over EtherCAT)
支持的操作:
- SDO Upload (0x40) - 读取对象字典
- SDO Download (0x20) - 写入对象字典
- SDO Abort (0x80) - 错误响应
SoE (Servo over EtherCAT)
支持的操作:
- IDN Read (0x01) - 读取 IDN 参数
- IDN Write (0x02) - 写入 IDN 参数
支持的元素:
- Value (0x05) - 参数值
- Name (0x00) - 参数名称
- Attribute (0x01) - 参数属性
- Unit (0x02) - 单位
- Min/Max (0x03/0x04) - 最小/最大值
- Default (0x06) - 默认值
- Data Type (0x07) - 数据类型
FoE (File over EtherCAT)
支持的操作:
- Read Request (0x01) - 文件下载请求
- Write Request (0x02) - 文件上传准备
- Data (0x03) - 文件数据传输
- Ack (0x04) - 确认
- Error (0x05) - 错误响应
当前限制:
- 仅支持单包文件传输 (小文件)
- 完整分段传输需要会话状态管理 (未来版本)
VoE (Vendor over EtherCAT)
帧格式:
- VendorID (4 bytes) - 厂商标识
- VendorType (2 bytes) - 厂商类型
- Data (n bytes) - 厂商特定数据
功能:
- 完整透传到从站 VoE 接口
- 自动处理请求/响应
- 支持任意长度数据