跳到主要内容

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 ID
  • timeoutUs (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;
}