跳到主要内容

邮箱网关 (Mailbox Gateway)

邮箱网关是正式用户 SDK(ETG.8200),允许外部诊断工具通过 UDP/IP 访问 EtherCAT 主站和从站的对象字典。

通过 master.MailboxGateway 访问。​

ETG.8200标准

邮箱网关服务符合 ETG.8200 标准规范,使用标准 UDP 端口 0x88A4 (34980),帧结构完全符合 Table 1 要求。

服务控制​

类别成员类型读写说明
服务控制Portint读写UDP 端口号,默认 0x88A4 (34980);须在 Start() 前设置
IsRunningbool只读服务是否正在运行
Start()void写入启动服务,开始监听 UDP 请求
Stop()void写入停止服务,释放 UDP 端口
统计GetStatistics()MailboxGatewayStats只读运行统计快照
ResetStatistics()void写入清零全部统计计数(保留运行状态字段)

Port​

public int Port { get; set; }

UDP 端口号(读写),默认 0x88A4 (34980)。启动后会更新为实际绑定的端口。

多实例自动偏移

运行多个主站实例时,端口会根据 MasterNumber(主站编号,从 1 起)自动偏移,避免端口冲突:

  • 主站 1: 34980(默认)
  • 主站 N: 34980 + (N - 1)

如需手动指定端口,在 Start() 前设置 Port 属性即可覆盖自动值。

警告

端口号必须在服务启动前设置。服务运行时无法更改端口。如果期望端口被占用,会自动回退到可用端口,Port 会更新为实际值。

IsRunning​

public bool IsRunning { get; }

服务是否正在运行(只读)。

Start()​

public void Start()

启动邮箱网关服务,开始监听 UDP 请求。如果期望端口被占用,会自动尝试备用端口,最终由系统分配。

示例:

var gateway = master.MailboxGateway;
gateway.Start();
Console.WriteLine($"邮箱网关已启动,端口: {gateway.Port}");

Stop()​

public void Stop()

停止邮箱网关服务,释放 UDP 端口。停止后可再次调用 Start() 重新启动。

示例:

master.MailboxGateway.Stop();

统计信息​

public MailboxGatewayStats GetStatistics()

获取邮箱网关的运行统计快照。快照固定包含 8 个核心字段:

返回值字段(MailboxGatewayStats):

类别属性类型读写说明
统计RxPacketsulong只读收到的 UDP 数据报总数
TxPacketsulong只读发送的 UDP 响应总数
RxBytesulong只读入站字节累计
TxBytesulong只读出站字节累计
MasterOdRequestsulong只读路由到主站对象字典(ETG.1510,Address = 0x0000)的请求数
SlaveRequestsulong只读路由到从站邮箱(CoE / SoE / FoE / VoE)的请求数
DropMalformedulong只读帧格式错误丢弃数
DropTimeoutulong只读处理 / 转发超时丢弃数
扩展字段

除上述 8 个核心统计字段外,MailboxGatewayStats 还带 IsRunning / UdpPort / TcpRunning / TcpPort / ForwardedOk / ForwardedFailed 等字段。实例服务 master.MailboxGateway 的快照填 8 个核心字段与 IsRunning / UdpPort;Forwarded* / Tcp* 由静态 MailboxGateway.GetStatistics()(DLL 通道)填充。

示例:

var stats = master.MailboxGateway.GetStatistics();

Console.WriteLine(
$"Mailbox Gateway 运行 {stats.RxPackets} req / {stats.TxPackets} resp\n" +
$" ↳ Master OD: {stats.MasterOdRequests}, Slave 邮箱: {stats.SlaveRequests}\n" +
$" ↳ 丢弃: 格式错={stats.DropMalformed}, 超时={stats.DropTimeout}\n" +
$" ↳ 流量: Rx={stats.RxBytes / 1024.0:F1} KB, Tx={stats.TxBytes / 1024.0:F1} KB");

ResetStatistics()​

public void ResetStatistics()

清零邮箱网关的全部统计计数(保留运行状态字段)。常用于在新一轮诊断 / 测试用例开始前复位基线。

示例:

master.MailboxGateway.ResetStatistics();  // 清零后重新采集

支持的协议​

协议类型说明
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 — 远程监控与告警采集​

把网关用于远程被动监控(周期性读取或订阅),并把关键数据汇报到集中监控系统。

自己启动案例​

using DarraEtherCAT_Master;

var master = new DarraEtherCAT()
.SetENI("config.xml")
.Build()
.Master;

master.SetState(EcState.OP);

// 启动邮箱网关(可选:自定义端口)
var gateway = master.MailboxGateway;
// gateway.Port = 12345; // 默认 34980,如需修改请在 Start 前设置
gateway.Start();
Console.WriteLine($"邮箱网关已启动,端口: {gateway.Port}");

// PDO 自动在后台线程运行,网关在 OP 状态下提供邮箱服务
Console.ReadKey();

gateway.Stop();
master.Close();

外部代码案例 (C#)​

using System.Net;
using System.Net.Sockets;

/// <summary>
/// ETG.8200 邮箱网关 UDP 客户端
/// 用于从外部程序访问主站/从站对象字典
/// </summary>
class MailboxGatewayClient
{
private readonly UdpClient _udp = new();
private readonly IPEndPoint _gateway;

public MailboxGatewayClient(string host = "127.0.0.1", int port = 0x88A4)
{
_gateway = new IPEndPoint(IPAddress.Parse(host), port);
}

/// <summary>
/// 构建 ETG.8200 帧:EtherCAT Header (2) + Mailbox Header (6) + Data
/// </summary>
private byte[] BuildFrame(ushort address, byte mailboxType, byte[] mailboxData)
{
ushort mbLength = (ushort)mailboxData.Length;
ushort ecatLength = (ushort)(6 + mbLength); // Mailbox Header + Data

var frame = new byte[2 + 6 + mbLength];

// EtherCAT Header: Length[10:0] | DataType[15:12]=0x05
ushort ecatHeader = (ushort)((0x05 << 12) | (ecatLength & 0x07FF));
BitConverter.GetBytes(ecatHeader).CopyTo(frame, 0);

// Mailbox Header
BitConverter.GetBytes(mbLength).CopyTo(frame, 2); // Length
BitConverter.GetBytes(address).CopyTo(frame, 4); // Address
frame[6] = 0x00; // Channel + Priority
frame[7] = (byte)(mailboxType << 4); // Type[7:4]

// Mailbox Data
mailboxData.CopyTo(frame, 8);

return frame;
}

/// <summary>
/// 发送请求并接收响应
/// </summary>
public byte[] SendRequest(ushort address, byte mailboxType, byte[] mailboxData)
{
var frame = BuildFrame(address, mailboxType, mailboxData);
_udp.Send(frame, frame.Length, _gateway);

var remoteEP = new IPEndPoint(IPAddress.Any, 0);
return _udp.Receive(ref remoteEP);
}

/// <summary>
/// CoE SDO Upload(读取对象字典)
/// address: 0x0000=主站, 其他=从站站地址
/// </summary>
public byte[] CoE_SDO_Read(ushort address, ushort index, byte subindex)
{
// CoE Header (2) + SDO Request (6)
var coData = new byte[8];
coData[0] = 0x00;
coData[1] = 0x20; // CoE Type: SDO Request
coData[2] = 0x40; // SDO Upload (Read)
BitConverter.GetBytes(index).CopyTo(coData, 3); // Index
coData[5] = subindex; // SubIndex

return SendRequest(address, 0x03, coData); // 0x03 = CoE
}

/// <summary>
/// CoE SDO Download(写入对象字典)
/// </summary>
public byte[] CoE_SDO_Write(ushort address, ushort index, byte subindex, byte[] value)
{
var coData = new byte[6 + value.Length];
coData[0] = 0x00;
coData[1] = 0x20; // CoE Type: SDO Request
coData[2] = 0x20; // SDO Download (Write)
BitConverter.GetBytes(index).CopyTo(coData, 3);
coData[5] = subindex;
value.CopyTo(coData, 6);

return SendRequest(address, 0x03, coData);
}

/// <summary>
/// SoE Read(读取 IDN 参数)
/// elementFlags: 0x00=Name, 0x01=Attribute, 0x02=Unit, 0x03=Min, 0x04=Max, 0x05=Value, 0x06=Default, 0x07=DataType
/// </summary>
public byte[] SoE_Read(ushort address, ushort idn, byte driveNo = 0, byte elementFlags = 0x05)
{
var soeData = new byte[4];
soeData[0] = 0x01; // OpCode: Read
soeData[1] = (byte)((driveNo << 3) | (elementFlags & 0x07));
BitConverter.GetBytes(idn).CopyTo(soeData, 2);

return SendRequest(address, 0x04, soeData); // 0x04 = SoE
}

/// <summary>
/// SoE Write(写入 IDN 参数)
/// </summary>
public byte[] SoE_Write(ushort address, ushort idn, byte[] value, byte driveNo = 0, byte elementFlags = 0x05)
{
var soeData = new byte[4 + value.Length];
soeData[0] = 0x02; // OpCode: Write
soeData[1] = (byte)((driveNo << 3) | (elementFlags & 0x07));
BitConverter.GetBytes(idn).CopyTo(soeData, 2);
value.CopyTo(soeData, 4);

return SendRequest(address, 0x04, soeData);
}

/// <summary>
/// FoE Read(从从站下载文件,仅支持单包)
/// </summary>
public byte[] FoE_Read(ushort address, string filename)
{
var nameBytes = Encoding.ASCII.GetBytes(filename + '\0');
var foeData = new byte[5 + nameBytes.Length];
foeData[0] = 0x01; // OpCode: Read Request
nameBytes.CopyTo(foeData, 5);

return SendRequest(address, 0x05, foeData); // 0x05 = FoE
}

/// <summary>
/// VoE 发送/接收(厂商特定协议)
/// </summary>
public byte[] VoE_SendReceive(ushort address, uint vendorId, ushort vendorType, byte[] data)
{
var voeData = new byte[6 + data.Length];
BitConverter.GetBytes(vendorId).CopyTo(voeData, 0); // VendorID
BitConverter.GetBytes(vendorType).CopyTo(voeData, 4); // VendorType
data.CopyTo(voeData, 6);

return SendRequest(address, 0x0F, voeData); // 0x0F = VoE
}
}

// --- 使用示例 ---
var client = new MailboxGatewayClient("192.168.1.100");

// CoE: 读取主站对象字典 (address=0x0000)
var resp = client.CoE_SDO_Read(0x0000, 0x1018, 0x01);
Console.WriteLine($"主站 VendorID: {BitConverter.ToString(resp)}");

// CoE: 读取/写入从站对象字典
resp = client.CoE_SDO_Read(0x03E9, 0x6040, 0x00);
client.CoE_SDO_Write(0x03E9, 0x6040, 0x00, BitConverter.GetBytes((ushort)0x0006));

// SoE: 读取从站 IDN 参数值
resp = client.SoE_Read(0x03E9, 32, driveNo: 0, elementFlags: 0x05);

// SoE: 写入从站 IDN 参数值
client.SoE_Write(0x03E9, 32, BitConverter.GetBytes(1000), driveNo: 0);

// FoE: 从从站下载文件
resp = client.FoE_Read(0x03E9, "firmware.bin");

// VoE: 厂商特定协议
resp = client.VoE_SendReceive(0x03E9, 0x00001164, 0x0001, new byte[] { 0x01, 0x02 });

注意事项​

网络安全

默认网关是强透传的,允许网络访问主站和从站的全部对象字典。建议:

  • 务必在受控网络中使用,或通过 IP 白名单 / VPN 隧道 限制访问
  • 配合操作系统防火墙规则,仅放行可信来源 IP
  • 生产环境中谨慎开启,避免暴露到公网
性能影响

邮箱网关运行在独立线程,对主循环性能影响极小。但大量同步 SDO/邮箱操作仍可能影响总体延迟——请合理限制并发与速率。

从站转发无响应时(WKC=0 / 超时)不回包,计入 DropTimeout 丢弃,由客户端自身超时兜底。

协议详解​

CoE (CAN over EtherCAT)​

支持的操作:

  • SDO Upload (0x40) - 读取对象字典
  • SDO Download (0x20) - 写入对象字典
  • SDO Abort (0x80) - 错误响应

错误码:

  • 0x06020000 - 对象不存在
  • 0x06010000 - 不支持的访问
  • 0x06010002 - 写保护
  • 0x05040001 - 命令未实现
  • 0x08000000 - 一般错误

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) - 数据类型

错误码:

  • 0x8001 - 服务不可用
  • 0x1001 - 无效命令
  • 0x1009 - IDN不存在
  • 0x3002 - 无效数据大小
  • 0x7002 - 写入失败
  • 0x8000 - 一般错误

FoE (File over EtherCAT)​

支持的操作:

  • Read Request (0x01) - 文件下载请求
  • Write Request (0x02) - 文件上传准备
  • Data (0x03) - 文件数据传输
  • Ack (0x04) - 确认
  • Error (0x05) - 错误响应

当前限制:

  • 仅支持单包文件传输(小文件)
  • 完整分段传输需要会话状态管理(未来版本)

错误码:

  • 0x00008001 - 无效操作码 / 未实现
  • 0x00008002 - 文件未找到
  • 0x00008003 - 非法文件名
  • 0x00008000 - 一般错误

VoE (Vendor over EtherCAT)​

帧格式:

  • VendorID (4 bytes) - 厂商标识
  • VendorType (2 bytes) - 厂商类型
  • Data (n bytes) - 厂商特定数据

功能:

  • 完整透传到从站 VoE 接口
  • 自动处理请求/响应
  • 支持任意长度数据

错误码:

  • 0x0001 - 服务不可用
  • 0x0002 - 无效帧
  • 0x0003 - 无响应
  • 0x0000 - 一般错误

错误处理​

协议错误类型错误码说明
CoE对象不存在0x06020000请求的对象或从站不存在
CoE不支持的访问0x06010000协议类型不支持或从站无对应功能
CoE写保护0x06010002尝试写入只读对象
CoE命令未实现0x05040001SDO 命令规范不支持
CoE一般错误0x08000000其他处理错误
SoEIDN不存在0x1009请求的IDN参数不存在
SoE写入失败0x7002IDN参数写入失败
FoE文件未找到0x00008002请求的文件不存在
FoE非法文件名0x00008003文件名格式错误
VoE无响应0x0003从站无VoE响应

限制​

已实现功能​

  • CoE 完整透传
  • SoE IDN 参数访问
  • FoE 单包文件传输
  • VoE 厂商协议透传
  • 主站对象字典访问
  • 从站邮箱透传
  • 错误处理和错误码映射

待实现功能​

  • FoE 分段传输(大文件支持)
  • Address 映射表 (+0x8000 虚拟映射)
  • 多播/广播请求
  • 会话状态管理