跳到主要内容

邮箱网关 (Mailbox Gateway)

邮箱网关实现 ETG.8200 标准, 允许外部诊断工具通过 UDP/IP 访问 EtherCAT 主站和从站的对象字典。

通过 master.MailboxGateway() 访问。

ETG.8200 标准

邮箱网关服务符合 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):

字段类型说明
RxPacketsuint64_t收到的 UDP 数据报总数
TxPacketsuint64_t发送的 UDP 响应总数
RxBytesuint64_t入站字节累计
TxBytesuint64_t出站字节累计
MasterOdRequestsuint64_t路由到主站对象字典 (ETG.1510, Address = 0x0000) 的请求数
SlaveRequestsuint64_t路由到从站邮箱 (CoE / SoE / FoE / VoE) 的请求数
DropMalformeduint64_t帧格式错误丢弃数
DropTimeoutuint64_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);

支持的协议

协议类型说明
CoE0x03SDO 读写, 支持主站 (ETG.1510) 和从站对象字典
SoE0x04IDN 参数读写 (ETG.1020 标准)
FoE0x05文件传输 (当前仅支持单包)
VoE0x0F厂商特定协议透传
地址路由
  • 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 接口
  • 自动处理请求/响应
  • 支持任意长度数据