跳到主要内容

邮箱网关 (Mailbox Gateway)

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

通过 master.MailboxGateway() 访问。

ETG.8200 标准

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

服务控制

getPort() / setPort(int)

public int getPort()
public void setPort(int port)

UDP 端口号,默认 0x88A4 + masterIndex(即 34980 + 主站编号)。

多实例自动偏移

运行多个主站实例时,端口会根据 MasterIndex 自动偏移,避免端口冲突:

  • 实例 0: 34980(默认)
  • 实例 1: 34981
  • 实例 N: 34980 + N
警告

端口号必须在服务启动前设置。

isRunning()

public boolean isRunning()

服务是否正在运行。

start()

public void start() throws IOException

启动邮箱网关服务,开始监听 UDP 请求。

示例:

MailboxGatewayService gateway = master.MailboxGateway();
gateway.start();
System.out.println("邮箱网关已启动,端口: " + gateway.getPort());

stop()

public void stop()

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

支持的协议

协议类型说明
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 循环)

完整示例

启动网关

try (EtherCATMaster master = EtherCATMaster.create()) {
master.SetNetwork("\\Device\\NPF_{...}");
master.Start();

// 启动邮箱网关
MailboxGatewayService gateway = master.MailboxGateway();
gateway.start();
System.out.println("邮箱网关已启动,端口: " + gateway.getPort());

// 等待...
System.in.read();

gateway.stop();
}

外部客户端示例(Java)

import java.net.*;
import java.nio.*;

/**
* ETG.8200 邮箱网关 UDP 客户端
* 用于从外部程序访问主站/从站对象字典
*/
class MailboxGatewayClient {
private final DatagramSocket socket;
private final InetAddress host;
private final int port;

public MailboxGatewayClient(String host, int port) throws Exception {
this.socket = new DatagramSocket();
this.socket.setSoTimeout(3000);
this.host = InetAddress.getByName(host);
this.port = port;
}

public MailboxGatewayClient(String host) throws Exception {
this(host, 0x88A4);
}

/** 构建 ETG.8200 帧 */
private byte[] buildFrame(short address, byte mailboxType, byte[] mailboxData) {
int mbLength = mailboxData.length;
int ecatLength = 6 + mbLength;

ByteBuffer buf = ByteBuffer.allocate(2 + 6 + mbLength);
buf.order(ByteOrder.LITTLE_ENDIAN);

// EtherCAT Header
short ecatHeader = (short) ((0x05 << 12) | (ecatLength & 0x07FF));
buf.putShort(ecatHeader);

// Mailbox Header
buf.putShort((short) mbLength); // Length
buf.putShort(address); // Address
buf.put((byte) 0x00); // Channel + Priority
buf.put((byte) (mailboxType << 4));// Type

// Data
buf.put(mailboxData);
return buf.array();
}

/** 发送请求并接收响应 */
public byte[] sendRequest(short address, byte mailboxType, byte[] mailboxData) throws Exception {
byte[] frame = buildFrame(address, mailboxType, mailboxData);
socket.send(new DatagramPacket(frame, frame.length, host, port));

byte[] recvBuf = new byte[4096];
DatagramPacket recv = new DatagramPacket(recvBuf, recvBuf.length);
socket.receive(recv);

byte[] result = new byte[recv.getLength()];
System.arraycopy(recvBuf, 0, result, 0, recv.getLength());
return result;
}

/** CoE SDO 读取 */
public byte[] coeSdoRead(short address, short index, byte subindex) throws Exception {
byte[] coData = new byte[8];
coData[1] = 0x20; // CoE Type: SDO Request
coData[2] = 0x40; // SDO Upload (Read)
coData[3] = (byte) (index & 0xFF);
coData[4] = (byte) ((index >> 8) & 0xFF);
coData[5] = subindex;
return sendRequest(address, (byte) 0x03, coData);
}

/** CoE SDO 写入 */
public byte[] coeSdoWrite(short address, short index, byte subindex, byte[] value) throws Exception {
byte[] coData = new byte[6 + value.length];
coData[1] = 0x20;
coData[2] = 0x20; // SDO Download (Write)
coData[3] = (byte) (index & 0xFF);
coData[4] = (byte) ((index >> 8) & 0xFF);
coData[5] = subindex;
System.arraycopy(value, 0, coData, 6, value.length);
return sendRequest(address, (byte) 0x03, coData);
}

/** SoE IDN 读取 */
public byte[] soeRead(short address, short idn, byte driveNo, byte elementFlags) throws Exception {
byte[] soeData = new byte[4];
soeData[0] = 0x01; // OpCode: Read
soeData[1] = (byte) ((driveNo << 3) | (elementFlags & 0x07));
soeData[2] = (byte) (idn & 0xFF);
soeData[3] = (byte) ((idn >> 8) & 0xFF);
return sendRequest(address, (byte) 0x04, soeData);
}

/** SoE IDN 写入 */
public byte[] soeWrite(short address, short idn, byte[] value,
byte driveNo, byte elementFlags) throws Exception {
byte[] soeData = new byte[4 + value.length];
soeData[0] = 0x02; // OpCode: Write
soeData[1] = (byte) ((driveNo << 3) | (elementFlags & 0x07));
soeData[2] = (byte) (idn & 0xFF);
soeData[3] = (byte) ((idn >> 8) & 0xFF);
System.arraycopy(value, 0, soeData, 4, value.length);
return sendRequest(address, (byte) 0x04, soeData);
}

/** FoE 读取(从从站下载文件,仅支持单包) */
public byte[] foeRead(short address, String filename) throws Exception {
byte[] nameBytes = (filename + "\0").getBytes(java.nio.charset.StandardCharsets.US_ASCII);
byte[] foeData = new byte[5 + nameBytes.length];
foeData[0] = 0x01; // OpCode: Read Request
System.arraycopy(nameBytes, 0, foeData, 5, nameBytes.length);
return sendRequest(address, (byte) 0x05, foeData);
}

/** VoE 发送/接收(厂商特定协议) */
public byte[] voeSendReceive(short address, int vendorId, short vendorType,
byte[] data) throws Exception {
ByteBuffer buf = ByteBuffer.allocate(6 + data.length).order(ByteOrder.LITTLE_ENDIAN);
buf.putInt(vendorId);
buf.putShort(vendorType);
buf.put(data);
return sendRequest(address, (byte) 0x0F, buf.array());
}
}

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

// CoE: 读取主站对象字典 (address=0x0000)
byte[] resp = client.coeSdoRead((short) 0x0000, (short) 0x1018, (byte) 0x01);

// CoE: 读取从站对象字典
resp = client.coeSdoRead((short) 0x03E9, (short) 0x6040, (byte) 0x00);

// CoE: 写入从站对象字典
byte[] value = { 0x06, 0x00 };
client.coeSdoWrite((short) 0x03E9, (short) 0x6040, (byte) 0x00, value);

// SoE: 读取从站 IDN 参数值
resp = client.soeRead((short) 0x03E9, (short) 32, (byte) 0, (byte) 0x05);

// SoE: 写入从站 IDN 参数值
ByteBuffer valBuf = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN);
valBuf.putInt(1000);
client.soeWrite((short) 0x03E9, (short) 32, valBuf.array(), (byte) 0, (byte) 0x05);

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

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

外部客户端示例(C#)

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

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);
}

private byte[] BuildFrame(ushort address, byte mailboxType, byte[] mailboxData)
{
ushort mbLength = (ushort)mailboxData.Length;
ushort ecatLength = (ushort)(6 + mbLength);
var frame = new byte[2 + 6 + mbLength];
ushort ecatHeader = (ushort)((0x05 << 12) | (ecatLength & 0x07FF));
BitConverter.GetBytes(ecatHeader).CopyTo(frame, 0);
BitConverter.GetBytes(mbLength).CopyTo(frame, 2);
BitConverter.GetBytes(address).CopyTo(frame, 4);
frame[6] = 0x00;
frame[7] = (byte)(mailboxType << 4);
mailboxData.CopyTo(frame, 8);
return frame;
}

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);
}

public byte[] CoE_SDO_Read(ushort address, ushort index, byte subindex)
{
var coData = new byte[8];
coData[1] = 0x20;
coData[2] = 0x40;
BitConverter.GetBytes(index).CopyTo(coData, 3);
coData[5] = subindex;
return SendRequest(address, 0x03, coData);
}

public byte[] CoE_SDO_Write(ushort address, ushort index, byte subindex, byte[] value)
{
var coData = new byte[6 + value.Length];
coData[1] = 0x20;
coData[2] = 0x20;
BitConverter.GetBytes(index).CopyTo(coData, 3);
coData[5] = subindex;
value.CopyTo(coData, 6);
return SendRequest(address, 0x03, coData);
}
}

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

// CoE: 读取主站对象字典
var resp = client.CoE_SDO_Read(0x0000, 0x1018, 0x01);

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

外部客户端示例(Python)

import socket
import struct

class MailboxGatewayClient:
"""ETG.8200 邮箱网关 UDP 客户端"""

def __init__(self, host="127.0.0.1", port=0x88A4):
self.addr = (host, port)
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock.settimeout(3.0)

def _build_frame(self, address: int, mb_type: int, mb_data: bytes) -> bytes:
mb_len = len(mb_data)
ecat_len = 6 + mb_len
ecat_header = (0x05 << 12) | (ecat_len & 0x07FF)

frame = struct.pack('<H', ecat_header)
frame += struct.pack('<H', mb_len)
frame += struct.pack('<H', address)
frame += bytes([0x00])
frame += bytes([mb_type << 4])
frame += mb_data
return frame

def send_request(self, address: int, mb_type: int, mb_data: bytes) -> bytes:
frame = self._build_frame(address, mb_type, mb_data)
self.sock.sendto(frame, self.addr)
resp, _ = self.sock.recvfrom(4096)
return resp

def coe_sdo_read(self, address: int, index: int, subindex: int) -> bytes:
"""CoE SDO Upload(读取对象字典)"""
co_data = bytearray(8)
co_data[1] = 0x20
co_data[2] = 0x40
struct.pack_into('<H', co_data, 3, index)
co_data[5] = subindex
return self.send_request(address, 0x03, bytes(co_data))

def coe_sdo_write(self, address: int, index: int, subindex: int, value: bytes) -> bytes:
"""CoE SDO Download(写入对象字典)"""
co_data = bytearray(6 + len(value))
co_data[1] = 0x20
co_data[2] = 0x20
struct.pack_into('<H', co_data, 3, index)
co_data[5] = subindex
co_data[6:6+len(value)] = value
return self.send_request(address, 0x03, bytes(co_data))

def soe_read(self, address: int, idn: int, drive_no: int = 0, element: int = 0x05) -> bytes:
"""SoE Read(读取 IDN 参数)"""
soe_data = bytearray(4)
soe_data[0] = 0x01
soe_data[1] = (drive_no << 3) | (element & 0x07)
struct.pack_into('<H', soe_data, 2, idn)
return self.send_request(address, 0x04, bytes(soe_data))

def soe_write(self, address: int, idn: int, value: bytes, drive_no: int = 0, element: int = 0x05) -> bytes:
"""SoE Write(写入 IDN 参数)"""
soe_data = bytearray(4 + len(value))
soe_data[0] = 0x02
soe_data[1] = (drive_no << 3) | (element & 0x07)
struct.pack_into('<H', soe_data, 2, idn)
soe_data[4:4+len(value)] = value
return self.send_request(address, 0x04, bytes(soe_data))

def foe_read(self, address: int, filename: str) -> bytes:
"""FoE Read(从从站下载文件,仅支持单包)"""
name_bytes = filename.encode('ascii') + b'\x00'
foe_data = bytearray(5 + len(name_bytes))
foe_data[0] = 0x01
foe_data[5:] = name_bytes
return self.send_request(address, 0x05, bytes(foe_data))

def voe_send(self, address: int, vendor_id: int, vendor_type: int, data: bytes) -> bytes:
"""VoE 发送/接收(厂商特定协议)"""
voe_data = bytearray(6 + len(data))
struct.pack_into('<I', voe_data, 0, vendor_id)
struct.pack_into('<H', voe_data, 4, vendor_type)
voe_data[6:6+len(data)] = data
return self.send_request(address, 0x0F, bytes(voe_data))

# --- 使用示例 ---
client = MailboxGatewayClient("192.168.1.100")

# CoE: 读取主站对象字典
resp = client.coe_sdo_read(0x0000, 0x1018, 0x01)
print(f"主站 VendorID: {resp.hex()}")

# CoE: 读取/写入从站对象字典
resp = client.coe_sdo_read(0x03E9, 0x6040, 0x00)
client.coe_sdo_write(0x03E9, 0x6040, 0x00, struct.pack('<H', 0x0006))

# SoE: 读取/写入从站 IDN 参数值
resp = client.soe_read(0x03E9, idn=32, drive_no=0, element=0x05)
client.soe_write(0x03E9, idn=32, value=struct.pack('<I', 1000))

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

# VoE: 厂商特定协议
resp = client.voe_send(0x03E9, vendor_id=0x00001164, vendor_type=0x0001, data=b'\x01\x02')

协议详解

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 响应
网络安全

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

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

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

限制

已实现功能

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

待实现功能

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