AoE (ADS over EtherCAT)
AoE 协议实现了 ADS (Automation Device Specification) over EtherCAT 通信,支持与实现 ADS 协议的设备进行通信。
通过 slave.AoE() 访问。子对象延迟创建,始终返回实例(不会为 null);从站是否支持 AoE 用 slave.AoE().isSupported() 判断。
AoE 各方法的不带 timeoutUs 参数重载使用内置默认超时 500000 微秒(0.5 秒)。该默认值为 SDK 内部常量,不作为公开属性暴露;需要自定义超时请使用带 timeoutUs 参数的重载。
数据读写
Read(int indexGroup, int indexOffset, int length, int timeoutUs)
public byte[] Read(int indexGroup, int indexOffset, int length, int timeoutUs)
public byte[] Read(int indexGroup, int indexOffset, int length) // 默认超时
读取 ADS 数据。
参数:
indexGroup(int) — 索引组indexOffset(int) — 索引偏移length(int) — 读取长度timeoutUs(int) — 超时时间(微秒)
返回值:
byte[]— 读取的数据,失败返回null
示例:
byte[] status = slave.AoE().Read(0x4020, 0, 4);
if (status != null) {
int value = (status[0] & 0xFF) | ((status[1] & 0xFF) << 8)
| ((status[2] & 0xFF) << 16) | ((status[3] & 0xFF) << 24);
System.out.printf("状态值: 0x%08X%n", value);
}
异步重载:
public CompletableFuture<byte[]> readAsync(int indexGroup, int indexOffset, int length, int timeoutUs)
public CompletableFuture<byte[]> readAsync(int indexGroup, int indexOffset, int length)
异步读取 ADS 数据。底层失败时 future 以 AoE.AoEProtocolException 异常完成。
slave.AoE().readAsync(0x4020, 0, 4)
.thenAccept(data -> System.out.printf("状态长度: %d%n", data.length))
.exceptionally(ex -> { System.err.println("失败: " + ex.getMessage()); return null; });
Write(int indexGroup, int indexOffset, byte[] data, int timeoutUs)
public boolean Write(int indexGroup, int indexOffset, byte[] data, int timeoutUs)
public boolean Write(int indexGroup, int indexOffset, byte[] data) // 默认超时
写入 ADS 数据。
参数:
indexGroup(int) — 索引组indexOffset(int) — 索引偏移data(byte[]) — 写入数据timeoutUs(int) — 超时时间(微秒)
返回值:
boolean— 成功返回true
示例:
byte[] data = { 0x06, 0x00, 0x00, 0x00 };
slave.AoE().Write(0x4020, 0, data);
异步重载:
public CompletableFuture<Boolean> writeAsync(int indexGroup, int indexOffset, byte[] data, int timeoutUs)
public CompletableFuture<Boolean> writeAsync(int indexGroup, int indexOffset, byte[] data)
ReadWrite(int indexGroup, int indexOffset, int readLength, byte[] writeData, int timeoutUs)
public byte[] ReadWrite(int indexGroup, int indexOffset, int readLength, byte[] writeData, int timeoutUs)
同时读写数据(ADS ReadWrite 命令)。
参数:
indexGroup(int) — 索引组indexOffset(int) — 索引偏移readLength(int) — 读取长度writeData(byte[]) — 写入数据(可为null)timeoutUs(int) — 超时时间(微秒)
返回值:
byte[]— 读取的数据,失败返回null
异步重载:
public CompletableFuture<byte[]> readWriteAsync(int indexGroup, int indexOffset, int readLength,
byte[] writeData, int timeoutUs)
设备信息
ReadDeviceInfo(int timeoutUs)
public DeviceInfo ReadDeviceInfo(int timeoutUs)
public DeviceInfo ReadDeviceInfo()
读取 ADS 设备信息。
返回值:
DeviceInfo— 设备信息对象
相关结构:
public static class DeviceInfo {
public boolean Success; // 是否成功
public byte MajorVer; // 主版本号
public byte MinorVer; // 次版本号
public short Build; // 编译号
public String DeviceName; // 设备名称
}
示例:
AoE.DeviceInfo info = slave.AoE().ReadDeviceInfo();
if (info.Success)
System.out.printf("设备: %s v%d.%d.%d%n",
info.DeviceName, info.MajorVer, info.MinorVer, info.Build);
ReadState(int timeoutUs)
public AdsState ReadState(int timeoutUs)
public AdsState ReadState()
读取 ADS 状态。
返回值:
AdsState— ADS 状态对象
相关结构:
public static class AdsState {
public boolean Success; // 是否成功
public short AdsState; // ADS 状态
public short DeviceState; // 设备状态
}
ADS 状态码含义:
0—Invalid无效状态1—Idle空闲2—Reset复位3—Init初始化4—Start启动5—Run运行6—Stop停止7—SaveConfig保存配置8—LoadConfig加载配置9—PowerFailure电源故障10—PowerGood电源正常11—Error错误15—Config配置
示例:
AoE.AdsState state = slave.AoE().ReadState();
if (state.Success)
System.out.println("ADS 状态: " + AoE.GetAdsStateDescription(state.AdsState));
WriteControl(short adsState, short deviceState, byte[] data, int timeoutUs)
public boolean WriteControl(short adsState, short deviceState, byte[] data, int timeoutUs)
public boolean WriteControl(short adsState, short deviceState)
写入控制命令(切换设备状态)。
参数:
adsState(short) — 目标 ADS 状态deviceState(short) — 目标设备状态data(byte[]) — 附加数据(可选)timeoutUs(int) — 超时时间(微秒)
返回值:
boolean— 成功返回true
示例:
slave.AoE().WriteControl((short) 5, (short) 0); // 切换到 Run 状态
GetAdsStateDescription(int adsState)
public static String GetAdsStateDescription(int adsState)
获取 ADS 状态码的中文描述。
数据订阅
Subscribe(int indexGroup, int indexOffset, int dataLength, NotificationCallback callback, int cycleTimeMs)
public int Subscribe(int indexGroup, int indexOffset, int dataLength,
NotificationCallback callback, int cycleTimeMs)
订阅数据变化通知。
参数:
indexGroup(int) — 索引组indexOffset(int) — 索引偏移dataLength(int) — 数据长度callback(NotificationCallback) — 数据变化回调cycleTimeMs(int) — 检查周期(毫秒)
返回值:
int— 通知句柄,失败返回-1
相关结构:
@FunctionalInterface
public interface NotificationCallback {
void OnNotification(short slave, int handle, byte[] data, long timestamp);
}
示例:
int handle = slave.AoE().Subscribe(0x4020, 0, 4, (slaveNo, h, data, ts) -> {
int value = (data[0] & 0xFF) | ((data[1] & 0xFF) << 8)
| ((data[2] & 0xFF) << 16) | ((data[3] & 0xFF) << 24);
System.out.printf("数据变化: 0x%08X%n", value);
}, 100);
Unsubscribe(int handle)
public boolean Unsubscribe(int handle)
取消指定订阅。
UnsubscribeAll()
public void UnsubscribeAll()
取消所有订阅。
GetActiveSubscriptions()
public int[] GetActiveSubscriptions()
获取所有活跃订阅的句柄列表。
SendCommand(short targetPort, short commandId, byte[] commandData, int timeoutUs)
public byte[] SendCommand(short targetPort, short commandId, byte[] commandData, int timeoutUs)
发送原始 ADS 命令(高级 API,常规场景请使用 Read/Write/Subscribe)。
参数:
targetPort(short) — 目标端口commandId(short) — 命令 IDcommandData(byte[]) — 命令数据timeoutUs(int) — 超时时间(微秒)
返回值:
byte[]— 响应数据,失败返回null
AoE 配置
SetConfig(byte[] targetNetId, short targetPort, byte[] sourceNetId, short sourcePort)
public boolean SetConfig(byte[] targetNetId, short targetPort, byte[] sourceNetId, short sourcePort)
设置 AoE 路由配置(AMS NetID 和端口)。
参数:
targetNetId(byte[]) — 目标 AMS NetID(6 字节)targetPort(short) — 目标 AMS 端口sourceNetId(byte[]) — 源 AMS NetID(6 字节)sourcePort(short) — 源 AMS 端口
返回值:
boolean— 成功返回true
示例:
byte[] targetNetId = { 5, 80, (byte)187, (byte)177, 1, 1 };
byte[] sourceNetId = { (byte)192, (byte)168, 1, 100, 1, 1 };
slave.AoE().SetConfig(targetNetId, (short) 851, sourceNetId, (short) 32768);
InitializeSlaveNetId(byte[] netId, int timeoutUs)
public boolean InitializeSlaveNetId(byte[] netId, int timeoutUs)
public boolean InitializeSlaveNetId(byte[] netId)
初始化从站 AoE Net ID(ETG.1020 §9.4)。在 INIT→PreOp 状态切换期间调用,将 Net ID 写入从站 ADS 路由表。
参数:
netId(byte[]) — 6 字节的 AMS Net IDtimeoutUs(int) — 超时时间(微秒)
返回值:
boolean— 成功返回true
示例:
byte[] netId = { 5, 80, (byte)187, (byte)177, 1, 1 };
slave.AoE().InitializeSlaveNetId(netId);
GetConfig()
public AoEConfig GetConfig()
获取当前 AoE 路由配置。
返回值:
AoEConfig— 配置信息对象
相关结构:
public static class AoEConfig {
public boolean Success; // 是否成功
public byte[] TargetNetId; // 目标 AMS NetID (6 字节)
public short TargetPort; // 目标 AMS 端口
public byte[] SourceNetId; // 源 AMS NetID (6 字节)
public short SourcePort; // 源 AMS 端口
}
跨协议网关
通过 AoE 路由访问其他邮箱协议(ETG.1020),支持 CoE 和 SoE 协议的透明转发。
ReadCoEViaAoE(int index, int subindex, int readLength, int timeoutUs)
public byte[] ReadCoEViaAoE(int index, int subindex, int readLength, int timeoutUs)
public byte[] ReadCoEViaAoE(int index, int subindex, int readLength)
通过 AoE 路由读取 CoE 对象(IndexGroup=0xF302)。
参数:
index(int) — CoE 对象索引subindex(int) — CoE 子索引readLength(int) — 期望读取长度timeoutUs(int) — 超时时间(微秒)
返回值:
byte[]— 读取的数据,失败返回null
示例:
// 通过 AoE 网关读取 CoE 对象 0x6041:0(状态字)
byte[] data = slave.AoE().ReadCoEViaAoE(0x6041, 0, 2);
if (data != null) {
int statusWord = (data[0] & 0xFF) | ((data[1] & 0xFF) << 8);
System.out.printf("状态字: 0x%04X%n", statusWord);
}
WriteCoEViaAoE(int index, int subindex, byte[] data, int timeoutUs)
public boolean WriteCoEViaAoE(int index, int subindex, byte[] data, int timeoutUs)
public boolean WriteCoEViaAoE(int index, int subindex, byte[] data)
通过 AoE 路由写入 CoE 对象(IndexGroup=0xF302)。
参数:
index(int) — CoE 对象索引subindex(int) — CoE 子索引data(byte[]) — 写入数据timeoutUs(int) — 超时时间(微秒)
返回值:
boolean— 成功返回true
示例:
// 通过 AoE 网关写入 CoE 对象 0x6040:0(控制字)
byte[] ctrlWord = { 0x0F, 0x00 };
slave.AoE().WriteCoEViaAoE(0x6040, 0, ctrlWord);
ReadSoEViaAoE(int idn, int readLength, int timeoutUs)
public byte[] ReadSoEViaAoE(int idn, int readLength, int timeoutUs)
public byte[] ReadSoEViaAoE(int idn, int readLength)
通过 AoE 路由读取 SoE IDN(IndexGroup=0xF420)。
参数:
idn(int) — SoE IDN 编号readLength(int) — 期望读取长度timeoutUs(int) — 超时时间(微秒)
返回值:
byte[]— 读取的数据,失败返回null
WriteSoEViaAoE(int idn, byte[] data, int timeoutUs)
public boolean WriteSoEViaAoE(int idn, byte[] data, int timeoutUs)
public boolean WriteSoEViaAoE(int idn, byte[] data)
通过 AoE 路由写入 SoE IDN(IndexGroup=0xF420)。
参数:
idn(int) — SoE IDN 编号data(byte[]) — 写入数据timeoutUs(int) — 超时时间(微秒)
返回值:
boolean— 成功返回true
路由 IndexGroup 常量
public static final int COE_INDEX_GROUP = 0xF302; // CoE over AoE 路由
public static final int SOE_INDEX_GROUP = 0xF420; // SoE over AoE 路由
ReadCoEViaAoE / WriteCoEViaAoE 内部使用 COE_INDEX_GROUP,IndexOffset 编码为 (index << 16) | subindex;ReadSoEViaAoE / WriteSoEViaAoE 使用 SOE_INDEX_GROUP,IndexOffset = IDN 编号。
错误码与异常
AoEResultCode(枚举)
ETG.1020 Table 16 定义的 AoE/ADS 结果码,数值与 C#/C++/Python/Rust 一致:高字节 = 错误类(0x0700 = ADS 设备错误,0x0740 = 客户端错误),低字节 = 具体错误码。
public enum AoEResultCode {
NO_ERROR(0x0000),
INTERNAL_ERROR(0x0001),
NO_REALTIME(0x0002),
INSERT_MAILBOX_ERROR(0x0004),
TARGET_PORT_NOT_FOUND(0x0006),
TARGET_MACHINE_NOT_FOUND(0x0007),
UNKNOWN_COMMAND_ID(0x0008),
INVALID_AMS_LENGTH(0x000E),
INVALID_AMS_NET_ID(0x000F),
PORT_DISABLED(0x0012),
AMS_SYNC_TIMEOUT(0x0015),
DEVICE_INVALID_GROUP(0x0700),
DEVICE_INVALID_OFFSET(0x0701),
DEVICE_NOT_READY(0x0705),
DEVICE_TIMEOUT(0x0712),
DEVICE_ACCESS_DENIED(0x071C),
CLIENT_ERROR(0x0740),
CLIENT_INVOKE_TIMEOUT(0x0744),
CLIENT_QUEUE_FULL(0x0750);
// 完整列表(共约 60 个常量)见 SDK 源码 AoE.AoEResultCode (ETG.1020 Table 16)
public int getValue(); // 数值(与 C# AoEResultCode 一致)
public static AoEResultCode fromValue(int code); // 数值反查,未匹配返回 null
}
| 类别 | 方法 | 类型 | 读写 | 说明 |
|---|---|---|---|---|
| 数值 | getValue() | int | 只读 | 枚举常量对应的 ADS 结果码 |
| 反查 | fromValue(int code) | AoEResultCode | 只读(静态) | 由数值匹配枚举常量,未匹配返回 null |
AoEProtocolException
AoE 异步方法(readAsync / writeAsync / readWriteAsync)底层失败时,返回的 CompletableFuture 以该异常完成。
public static class AoEProtocolException extends RuntimeException {
public AoEProtocolException(String message);
public AoEProtocolException(String message, Throwable cause);
}
示例:
slave.AoE().readAsync(0x4020, 0, 4)
.thenAccept(data -> System.out.printf("长度: %d%n", data.length))
.exceptionally(ex -> {
if (ex.getCause() instanceof AoE.AoEProtocolException)
System.err.println("AoE 读取失败: " + ex.getCause().getMessage());
return null;
});
邮箱统计
AoE 实现统一的 IMailboxProtocol 接口。2.5.x 起,getStatistics() / resetStatistics() 经真实导出的 native EcxMbxGetStats / EcxMbxResetStats 拉取(此前为占位空快照)。
| 类别 | 方法 | 类型 | 读写 | 说明 |
|---|---|---|---|---|
| 协议标识 | getProtocolType() | byte | 只读 | 协议类型常量(AoE=0x01) |
| getProtocolName() | String | 只读 | 协议名称("AoE") | |
| isSupported() | boolean | 只读 | 从站是否支持本协议 | |
| 事务状态 | getLastStatus() | MailboxStatus | 只读 | 最近一次邮箱事务的统一状态 |
| getLastErrorCode() | long | 只读 | 最近一次协议层错误码(0=无错误) | |
| 统计 | getStatistics() | MailboxStatistics | 只读 | 邮箱统计快照 |
| resetStatistics() | void | — | 重置邮箱统计计数器 |
MailboxStatistics 结构字段与 MailboxStatus 枚举详见 CoE — 邮箱统计。
完整示例
数据读写
AoE aoe = slave.AoE();
// 读取数据
byte[] status = aoe.Read(0x4020, 0, 4);
if (status != null) {
int value = (status[0] & 0xFF) | ((status[1] & 0xFF) << 8)
| ((status[2] & 0xFF) << 16) | ((status[3] & 0xFF) << 24);
System.out.printf("状态: 0x%08X%n", value);
}
// 写入数据
byte[] writeData = { 0x06, 0x00, 0x00, 0x00 };
aoe.Write(0x4020, 0, writeData);
// 设备信息
AoE.DeviceInfo info = aoe.ReadDeviceInfo();
if (info.Success)
System.out.printf("设备: %s v%d.%d.%d%n",
info.DeviceName, info.MajorVer, info.MinorVer, info.Build);
数据订阅
AoE aoe = slave.AoE();
int handle = aoe.Subscribe(0x4020, 0, 4, (slaveNo, h, data, ts) -> {
System.out.println("数据变化");
}, 100);
Thread.sleep(10000);
aoe.UnsubscribeAll();
跨协议网关
AoE aoe = slave.AoE();
// 通过 AoE 读取 CoE 对象
byte[] statusWord = aoe.ReadCoEViaAoE(0x6041, 0, 2);
if (statusWord != null) {
int value = (statusWord[0] & 0xFF) | ((statusWord[1] & 0xFF) << 8);
System.out.printf("状态字: 0x%04X%n", value);
}
// 通过 AoE 写入 CoE 对象
byte[] ctrlWord = { 0x0F, 0x00 };
aoe.WriteCoEViaAoE(0x6040, 0, ctrlWord);
// 通过 AoE 读取 SoE IDN
byte[] idnData = aoe.ReadSoEViaAoE(32, 4);
if (idnData != null) {
int value = (idnData[0] & 0xFF) | ((idnData[1] & 0xFF) << 8)
| ((idnData[2] & 0xFF) << 16) | ((idnData[3] & 0xFF) << 24);
System.out.printf("IDN 32 值: %d%n", value);
}