AoE (ADS over EtherCAT)
AoE 协议实现了 ADS (Automation Device Specification) over EtherCAT 通信,支持 Beckhoff TwinCAT 和类似设备。
通过 slave.GetAoE() 获取 AoE& 引用。
数据读写
ReadWrite()
std::vector<uint8_t> ReadWrite(uint32_t indexGroup, uint32_t indexOffset,
uint32_t readLength,
const uint8_t* writeData = nullptr,
uint32_t writeLength = 0,
int timeoutUs = 500000) const;
AoE 读写操作。
Read()
std::vector<uint8_t> Read(uint32_t indexGroup, uint32_t indexOffset,
uint32_t length, int timeoutUs = 500000) const;
Write()
bool Write(uint32_t indexGroup, uint32_t indexOffset,
const uint8_t* data, uint32_t dataLen,
int timeoutUs = 500000) const;
示例:
auto& aoe = master.GetSlave(1).GetAoE();
// 读取数据
auto data = aoe.Read(0x4020, 0, 4);
if (data.size() >= 4) {
uint32_t val;
std::memcpy(&val, data.data(), 4);
printf("值: 0x%08X\n", val);
}
异步 API (std::future)
C++ SDK 在同步 Read / Write / ReadWrite 之外提供 3 个 std::future 异步入口(对齐 C# AoEInstance.ReadAsync / WriteAsync / ReadWriteAsync)。内部以 std::async(std::launch::async) 把同步调用甩到后台线程,不阻塞主线程。
ReadAsync()
std::future<std::vector<uint8_t>> ReadAsync(uint32_t indexGroup, uint32_t indexOffset,
uint32_t length,
int timeoutUs = 500000) const;
异步读取 ADS 数据。future.get() 返回读取结果,失败时返回空 vector。
WriteAsync()
std::future<bool> WriteAsync(uint32_t indexGroup, uint32_t indexOffset,
std::vector<uint8_t> data,
int timeoutUs = 500000) const;
异步写入 ADS 数据。data 按值捕获,调用方可立即析构源对象。
ReadWriteAsync()
std::future<std::vector<uint8_t>> ReadWriteAsync(uint32_t indexGroup, uint32_t indexOffset,
uint32_t readLength,
std::vector<uint8_t> writeData,
int timeoutUs = 500000) const;
异步执行 ADS Read+Write 复合操作。
示例 (并行读多个 ADS 变量):
auto& aoe = master.GetSlave(1).GetAoE();
auto fa = aoe.ReadAsync(0x4020, 0, 4);
auto fb = aoe.ReadAsync(0x4021, 0, 4);
auto va = fa.get();
auto vb = fb.get();
错误码
ADS 协议错误码(ETG.1020 Table 16),对齐 C# AoEResultCode / Java AoeResultCode。底层 ADS Header 的 errorCode 字段非 0 时通过 AoEResultCode 枚举返回。
enum class AoEResultCode : uint32_t {
Success = 0x0000, // 成功
InternalError = 0x0001, // 内部错误
NoRights = 0x0002, // 无权限
TimeoutElapsed = 0x0005, // 等待超时
InvalidAmsPort = 0x0006, // 无效 AMS 端口
NotFound = 0x0007, // 对象未找到
InvalidParameter = 0x0008, // 无效参数
NotInitialized = 0x0009, // 未初始化
AlreadyInitialized = 0x000A, // 已初始化
InvalidAddress = 0x000B, // 无效地址
InvalidNetId = 0x000C, // 无效 NetID
InstallationLevel = 0x000D, // 安装级别错误
NoDebugAvailable = 0x000E, // 无调试可用
PortDisabled = 0x000F, // 端口已禁用
PortAlreadyConnected = 0x0010, // 端口已连接
PortNotConnected = 0x0011, // 端口未连接
InvalidPduType = 0x0012, // 无效 PDU 类型
InvalidPduSize = 0x0013, // 无效 PDU 长度
GeneralError = 0xFFFFFFFF
};
示例:
auto data = aoe.Read(0x4020, 0, 4);
if (data.empty()) {
AoEResultCode rc = aoe.LastErrorCode();
if (rc == AoEResultCode::TimeoutElapsed) {
printf("ADS 读取超时\n");
} else {
printf("ADS 读取失败: 0x%08X\n", static_cast<uint32_t>(rc));
}
}
设备信息
ReadDeviceInfo()
std::tuple<bool, uint8_t, uint8_t, uint16_t, std::string>
ReadDeviceInfo(int timeoutUs = 500000) const;
读取 ADS 设备信息。返回 {success, major, minor, build, name} 元组。
示例:
auto& aoe = master.GetSlave(1).GetAoE();
auto [ok, major, minor, build, name] = aoe.ReadDeviceInfo();
if (ok)
printf("设备: %s v%d.%d.%d\n", name.c_str(), major, minor, build);
ADS 状态
ReadState()
std::tuple<bool, uint16_t, uint16_t> ReadState(int timeoutUs = 500000) const;
读取 ADS 状态。返回 {success, adsState, deviceState} 元组。
WriteControl()
bool WriteControl(uint16_t adsState, uint16_t deviceState,
const uint8_t* data = nullptr, int dataLen = 0,
int timeoutUs = 500000) const;
写入 ADS 控制命令。
示例:
auto& aoe = master.GetSlave(1).GetAoE();
auto [ok, ads, dev] = aoe.ReadState();
if (ok) {
printf("ADS 状态: %s, 设备状态: %d\n",
AoE::GetAdsStateName(ads).c_str(), dev);
}
// 切换到 Run 状态
aoe.WriteControl(5, 0); // ADS_STATE_RUN = 5
命令发送
SendCommand()
std::tuple<bool, std::vector<uint8_t>>
SendCommand(uint16_t targetPort, uint16_t commandId,
const uint8_t* cmdData = nullptr, uint32_t cmdSize = 0,
int timeoutUs = 500000) const;
发送 AoE 命令并接收响应。
数据订阅
Subscribe()
使用 AoESubscriptionManager 类管理订阅生命周期,析构时自动清理。
int Subscribe(uint32_t indexGroup, uint32_t indexOffset, uint32_t dataLength,
AoETransmissionMode mode, uint32_t maxDelay, uint32_t cycleTimeMs,
std::function<void(uint32_t, uint32_t, uint32_t,
const uint8_t*, uint32_t)> callback);
订阅数据变化通知。
参数:
indexGroup(uint32_t) — 索引组indexOffset(uint32_t) — 索引偏移dataLength(uint32_t) — 数据长度mode(AoETransmissionMode) — 传输模式(默认OnChange)maxDelay(uint32_t) — 最大延迟(微秒)cycleTimeMs(uint32_t) — 检查周期(毫秒)callback— 数据变化回调函数
返回值:
int— 订阅 ID,失败返回 -1
enum class AoETransmissionMode : uint32_t {
NoTransmission = 0, // 无通知
Cyclic = 1, // 循环通知 - 按指定周期发送
OnChange = 2, // 变化通知 - 数据变化时发送
CyclicInDevice = 3, // 设备端循环通知
OnChangeInDevice = 4 // 设备端变化通知
};
示例:
AoESubscriptionManager subMgr(dll, masterIndex, slaveIndex);
auto subId = subMgr.Subscribe(0x4020, 0, 4,
AoETransmissionMode::OnChange, 0, 100,
[](uint32_t handle, uint32_t ig, uint32_t io,
const uint8_t* data, uint32_t size) {
printf("数据变化: handle=%u, size=%u\n", handle, size);
});
Unsubscribe()
bool Unsubscribe(int subscriptionId);
取消指定订阅。
UnsubscribeAll()
void UnsubscribeAll();
取消所有订阅。
ActiveCount()
int ActiveCount() const;
获取活跃订阅数。
示例:
AoESubscriptionManager subMgr(dll, masterIndex, slaveIndex);
// 添加订阅
int subId = subMgr.Subscribe(0x4020, 0, 4,
AoETransmissionMode::OnChange, 0, 100,
[](uint32_t handle, uint32_t ig, uint32_t io,
const uint8_t* data, uint32_t size) {
printf("数据变化: handle=%u, size=%u\n", handle, size);
});
printf("活跃订阅: %d\n", subMgr.ActiveCount());
// 移除订阅
subMgr.Unsubscribe(subId);
// 移除所有订阅
subMgr.UnsubscribeAll();
低层 API (AddNotification / DelNotification)
std::optional<uint32_t> AddNotification(
uint32_t indexGroup, uint32_t indexOffset, uint32_t length,
uint32_t transMode, uint32_t maxDelay, uint32_t cycleTime,
int timeoutUs = 500000) const;
bool DelNotification(uint32_t handle, int timeoutUs = 500000) const;
添加/删除 ADS 数据变更通知(底层 API,推荐使用 AoESubscriptionManager)。
AoE 配置
SetConfig()
bool SetConfig(const uint8_t targetNetId[6], uint16_t targetPort,
const uint8_t sourceNetId[6], uint16_t sourcePort) const;
设置 AoE 路由配置(AMS NetID 和端口)。
参数:
targetNetId(const uint8_t[6]) — 目标 AMS NetID(6 字节)targetPort(uint16_t) — 目标 AMS 端口sourceNetId(const uint8_t[6]) — 源 AMS NetID(6 字节)sourcePort(uint16_t) — 源 AMS 端口
返回值:
bool— 成功返回true
示例:
uint8_t targetNetId[] = { 5, 80, 187, 177, 1, 1 };
uint8_t sourceNetId[] = { 192, 168, 1, 100, 1, 1 };
aoe.SetConfig(targetNetId, 851, sourceNetId, 32768);
GetConfig()
std::tuple<bool, std::array<uint8_t, 6>, uint16_t,
std::array<uint8_t, 6>, uint16_t>
GetConfig() const;
获取 AoE 路由配置。返回 {success, targetNetId, targetPort, sourceNetId, sourcePort} 元组。
InitializeSlaveNetId()
bool InitializeSlaveNetId(const uint8_t netId[6],
int timeoutUs = 500000) const;
初始化从站 AoE Net ID(ETG.1020 §9.4)。在 INIT→PreOp 状态切换期间调用,将 Net ID 写入从站 ADS 路由表。
参数:
netId(const uint8_t[6]) — 6 字节的 AMS Net IDtimeoutUs(int) — 超时时间(微秒)
返回值:
bool— 成功返回true
示例:
uint8_t netId[] = { 5, 80, 187, 177, 1, 1 };
aoe.InitializeSlaveNetId(netId);
跨协议网关
通过 AoE 路由访问其他邮箱协议(ETG.1020),支持 CoE 和 SoE 协议的透明转发。
ReadCoEViaAoE()
std::vector<uint8_t> ReadCoEViaAoE(uint16_t index, uint8_t subindex,
uint32_t readLength,
int timeoutUs = 500000) const;
通过 AoE 路由读取 CoE 对象(IndexGroup=0xF302,IndexOffset=(index << 16) | subindex)。
参数:
index(uint16_t) — CoE 对象索引subindex(uint8_t) — CoE 子索引readLength(uint32_t) — 期望读取长度timeoutUs(int) — 超时时间(微秒)
返回值:
std::vector<uint8_t>— 读取的数据,失败返回空
WriteCoEViaAoE()
bool WriteCoEViaAoE(uint16_t index, uint8_t subindex,
const uint8_t* data, uint32_t dataLen,
int timeoutUs = 500000) const;
通过 AoE 路由写入 CoE 对象(IndexGroup=0xF302)。
参数:
index(uint16_t) — CoE 对象索引subindex(uint8_t) — CoE 子索引data(const uint8_t*) — 写入数据dataLen(uint32_t) — 数据长度timeoutUs(int) — 超时时间(微秒)
返回值:
bool— 成功返回true
ReadSoEViaAoE()
std::vector<uint8_t> ReadSoEViaAoE(uint32_t idn, uint32_t readLength,
int timeoutUs = 500000) const;
通过 AoE 路由读取 SoE IDN(IndexGroup=0xF420,IndexOffset=IDN 编号)。
WriteSoEViaAoE()
bool WriteSoEViaAoE(uint32_t idn, const uint8_t* data, uint32_t dataLen,
int timeoutUs = 500000) const;
通过 AoE 路由写入 SoE IDN(IndexGroup=0xF420)。
完整示例
数据读写
auto& aoe = master.GetSlave(1).GetAoE();
// 读取数据
auto status = aoe.Read(0x4020, 0, 4);
if (status.size() >= 4) {
uint32_t val;
std::memcpy(&val, status.data(), 4);
printf("状态: 0x%08X\n", val);
}
// 写入数据
uint32_t writeVal = 0x0006;
aoe.Write(0x4020, 0, reinterpret_cast<const uint8_t*>(&writeVal), 4);
// 设备信息
auto [ok, major, minor, build, name] = aoe.ReadDeviceInfo();
if (ok)
printf("设备: %s v%d.%d.%d\n", name.c_str(), major, minor, build);
数据订阅
AoESubscriptionManager subMgr(dll, masterIndex, slaveIndex);
auto subId = subMgr.Subscribe(0x4020, 0, 4,
AoETransmissionMode::OnChange, 0, 100,
[](uint32_t handle, uint32_t ig, uint32_t io,
const uint8_t* data, uint32_t size) {
printf("数据变化, handle=%u\n", handle);
});
// ... 运行中 ...
subMgr.UnsubscribeAll();
跨协议网关
auto& aoe = master.GetSlave(1).GetAoE();
// 通过 AoE 读取 CoE 对象
auto statusWord = aoe.ReadCoEViaAoE(0x6041, 0, 2);
if (statusWord.size() >= 2) {
uint16_t val;
std::memcpy(&val, statusWord.data(), 2);
printf("状态字: 0x%04X\n", val);
}
// 通过 AoE 写入 CoE 对象
uint16_t ctrlWord = 0x000F;
aoe.WriteCoEViaAoE(0x6040, 0, reinterpret_cast<const uint8_t*>(&ctrlWord), 2);
// 通过 AoE 读取 SoE IDN
auto idnData = aoe.ReadSoEViaAoE(32, 4);
if (idnData.size() >= 4) {
uint32_t idnVal;
std::memcpy(&idnVal, idnData.data(), 4);
printf("IDN 32 值: %u\n", idnVal);
}
端到端流程
#include "ethercat.hpp"
using namespace darra;
int main() {
EtherCATMaster master(dll);
master.SetNetwork("\\Device\\NPF_{...}")
.SetENI("config.deni")
.Build();
master.SetState(EcState::OP);
master.Start();
auto& aoe = master.GetSlave(1).GetAoE();
// 设备信息
auto [infoOk, major, minor, build, name] = aoe.ReadDeviceInfo();
if (infoOk)
printf("设备: %s v%d.%d.%d\n", name.c_str(), major, minor, build);
// ADS 状态
auto [stateOk, ads, dev] = aoe.ReadState();
if (stateOk)
printf("ADS=%s, 设备=%d\n", AoE::GetAdsStateName(ads).c_str(), dev);
// 读取数据
auto data = aoe.Read(0x4020, 0, 4);
if (data.size() >= 4) {
uint32_t val;
std::memcpy(&val, data.data(), 4);
printf("值: 0x%08X\n", val);
}
getchar();
return 0;
}