ESI 文件管理 (EsiManager)
darra::EsiManager 类提供 EtherCAT Slave Information (ESI) 文件的加载、绑定和版本匹配功能。ESI 文件包含从站的完整配置信息, 包括设备标识、对象字典、PDO 映射、DC 配置等。
通过 EsiManager 类访问,构造时传入 dll_t*。
当前 C++ EsiManager 类的真实成员仅有:
DefaultPath()(静态)、AddFile()、LoadPath()、MatchRevision()(静态)、BindToSlave()、
ApplyAllSlaves()、AutoMatchAll()、GetLoadedCount()、GetFiles()、Clear()。
本页下文出现的 LoadPathIncremental() / PreloadDefault() / GetDeviceInfo() /
GetMatchingDevice() / FindEsiFile() / IsFileLoaded() / GetAllFiles(),以及带
progressCallback 参数的 LoadPath() 重载,目前只是 C# SDK 的能力,C++ EsiManager 尚未提供。
EepromCrc 工具 CalculateEepromCrc() / ValidateEepromCrc() 在 C++ 中是
darra 命名空间下的自由函数,不是 EsiManager 的静态成员。
加载和管理
LoadPath()
int LoadPath(const std::string& dirPath);
加载指定目录下的所有 ESI 文件(.xml / .ESI)。当前 C++ 版本不带进度回调参数。
参数:
dirPath(std::string) — ESI 文件目录路径
返回值:
int— DLL 报告的设备数(FFI 不可用时返回 0)
示例:
EsiManager mgr(&dll);
mgr.LoadPath("C:/EtherCAT/ESI");
LoadPathIncremental()
C++ EsiManager 没有 LoadPathIncremental()。C++ 中重复 LoadPath() 同一目录即可 (内部 std::set 去重)。
int LoadPathIncremental(const std::string& dirPath,
std::function<void(int, int, const std::string&)> progressCallback = {});
增量加载 ESI 文件 (仅加载未加载的文件)。
参数:
dirPath(std::string) — ESI 文件目录路径progressCallback(std::function) — 进度回调(current, total, fileName), 可省略
返回值:
int— 本次新加载的文件数
示例:
mgr.LoadPath("C:/ESI/Standard");
mgr.LoadPathIncremental("C:/ESI/Custom");
PreloadDefault()
C++ EsiManager 没有 PreloadDefault()。C++ 中用 mgr.LoadPath(EsiManager::DefaultPath()) 等效。
int PreloadDefault(std::function<void(int, int, const std::string&)> progressCallback = {});
预加载默认 ESI 文件 (从默认路径)。
返回值:
int— 加载的文件数
示例:
mgr.PreloadDefault([](int current, int total, const std::string& fileName) {
int percent = current * 100 / total;
printf("\r加载 ESI: %d%% - %s", percent, fileName.c_str());
});
printf("\nESI 文件加载完成\n");
AddFile()
int AddFile(const std::string& filePath);
添加单个 ESI 文件到仓库。
参数:
filePath(std::string) — ESI 文件完整路径
返回值:
int— 文件中包含的设备数
示例:
mgr.AddFile("C:/CustomDevices/MyDevice.xml");
Clear()
void Clear();
清除所有已加载的 ESI 文件。
示例:
mgr.Clear();
mgr.LoadPath("C:/NewESI");
查询和检索
GetDeviceInfo() / GetMatchingDevice() / FindEsiFile() 当前不是 C++ EsiManager 的成员。
C++ 侧若需 ESI 设备信息, 用 darra 命名空间下的 ESI 数据模型结构体 (EsiDeviceInfo 等, 见
utils/esi.hpp) 自行解析, 或经 BindToSlave() / ApplyAllSlaves() 让 SDK 内部消费 ESI。
GetDeviceInfo()
std::optional<EsiDeviceInfo> GetDeviceInfo(uint32_t vendorId, uint32_t productId, uint32_t revisionId) const;
根据设备标识获取 ESI 设备信息。
参数:
vendorId(uint32_t) — 厂商 IDproductId(uint32_t) — 产品代码revisionId(uint32_t) — 修订号
返回值:
std::optional<EsiDeviceInfo>— 设备信息, 未找到返回std::nullopt
相关结构:
struct EsiDeviceInfo {
std::string product_name; // 产品名称
std::string device_class; // 设备类型
std::string group_type; // 分组类型
uint32_t vendor_id; // 厂商 ID
uint32_t product_id; // 产品代码
uint32_t revision_id; // 修订号
std::string physics; // 物理层类型 (如 "YKY", "KK")
};
示例:
auto info = mgr.GetDeviceInfo(0x00000002, 0x03F03052, 0x00120000);
if (info) {
printf("设备名称: %s\n", info->product_name.c_str());
printf("类型: %s\n", info->device_class.c_str());
printf("分组: %s\n", info->group_type.c_str());
}
GetMatchingDevice()
std::shared_ptr<EsiDevice> GetMatchingDevice(uint32_t vendorId, uint32_t productCode, uint32_t revisionId) const;
获取匹配的设备完整 ESI 数据。
返回值:
std::shared_ptr<EsiDevice>— ESI 设备节点, 未找到返回空指针
示例:
auto device = mgr.GetMatchingDevice(0x00000002, 0x03F03052, 0x00120000);
if (device) {
printf("设备: %s\n", device->name().c_str());
printf("RxPDO 数: %zu\n", device->rx_pdo().size());
}
FindEsiFile()
std::string FindEsiFile(const std::string& fileName) const;
根据文件名查找 ESI 文件完整路径。
参数:
fileName(std::string) — ESI 文件名
返回值:
std::string— 完整路径, 未找到返回空字符串
示例:
std::string path = mgr.FindEsiFile("Beckhoff EL1008.xml");
if (!path.empty()) {
printf("找到文件: %s\n", path.c_str());
}
状态查询
GetLoadedCount()
int GetLoadedCount() const;
获取已加载的 ESI 文件 / 设备数量(C++ 方法名为 GetLoadedCount(),不是 GetLoadedFileCount())。
示例:
int count = mgr.GetLoadedCount();
printf("已加载 %d 个 ESI 文件\n", count);
IsFileLoaded()
C++ EsiManager 没有 IsFileLoaded()。C++ 中遍历 GetFiles() 返回的列表自行判断。
bool IsFileLoaded(const std::string& fileName) const;
检查指定 ESI 文件是否已加载。
示例:
if (mgr.IsFileLoaded("MyDevice.xml")) {
printf("自定义设备 ESI 已加载\n");
}
GetFiles()
std::vector<std::string> GetFiles() const;
获取所有已加载文件的路径列表。
返回值:
std::vector<std::string>— 已加载 ESI 文件路径
示例:
auto files = mgr.GetFiles();
printf("已加载的 ESI 文件:\n");
for (const auto& file : files) {
printf(" - %s\n", file.c_str());
}
GetAllFiles()
C++ EsiManager 没有 GetAllFiles(), 也没有 EsiDevice 类型。C++ 中用 GetFiles()
取已加载文件路径列表 (std::vector<std::string>)。
std::map<std::string, std::shared_ptr<EsiDevice>> GetAllFiles() const;
获取所有已加载文件的排序映射。
示例:
auto files = mgr.GetAllFiles();
for (const auto& [name, dev] : files) {
printf("%s\n", name.c_str());
}
默认路径
DefaultPath()
static std::string DefaultPath();
获取默认 ESI 文件搜索路径 (Windows: .\\ESI, Linux: ./ESI)。
返回值:
std::string— 默认 ESI 目录路径
示例:
std::string path = EsiManager::DefaultPath();
printf("默认 ESI 路径: %s\n", path.c_str());
ESI 绑定到从站
加载到 ESI 缓存后, 还需要将 ESI 设备节点绑定到具体从站, 才能让 SDK 在拓扑/PDO/DC/邮箱协议解析时使用 ESI 信息。
BindToSlave()
int BindToSlave(uint16_t masterIndex, uint16_t slaveIndex, const std::string& esiFilePath);
为单个从站绑定指定 ESI 文件。常用于已知拓扑、需要为某站强制使用某个版本 ESI 的场景。
参数:
masterIndex(uint16_t) — 主站编号slaveIndex(uint16_t) — 从站索引 (1-based, 与从站编号一致)esiFilePath(std::string) — ESI XML 文件完整路径
返回值:
int— 绑定成功返回> 0; 从站不存在 / 文件无效 / 索引越界 / FFI 不可用返回0
示例:
int ok = mgr.BindToSlave(master.MasterNumber(), 2, "D:/esi/Vendor_Drive_v3.xml");
if (ok == 0) printf("绑定失败, 检查从站索引和文件路径\n");
ApplyAllSlaves()
int ApplyAllSlaves(uint16_t masterIndex);
遍历主站下所有从站, 按 VendorId + ProductId 在已加载缓存中查找匹配的 ESI 设备并自动绑定。调用前应先 LoadPath() / PreloadDefault() 把 ESI 文件加载进缓存。
参数:
masterIndex(uint16_t) — 主站编号
返回值:
int— 成功匹配并绑定的从站数
示例:
mgr.LoadPath("D:/esi");
int matched = mgr.ApplyAllSlaves(master.MasterNumber());
printf("已为 %d / %u 个从站匹配 ESI\n", matched, master.SlaveCount());
ApplyAllSlaves 应在 Build() 完成、从站列表就绪之后调用。SDK 在 PDO 配置 / DC 启用 / 邮箱协议建立阶段会自动消费已绑定的 ESI 信息。
ESI Device 字段直读
绑定 ESI 后(BindToSlave / ApplyAllSlaves / AutoMatchAll),可按 (masterIndex, slaveIndex) 直接读取该从站已绑定 ESI Device 节点声明的 PDO / SyncManager / 邮箱超时 / 厂商标识等字段。这些是 darra::ethercat 命名空间下的自由函数(utils/esi.hpp),底层走 native esi_parser,不在 SDK 自身解析 XML,故无需 link pugixml 即可使用。
必须先用 BindToSlave() / AutoMatchAll() / ApplyAllSlaves() 把 ESI Device 挂到该从站;未绑定时下列函数返回空 vector / std::nullopt / 负值。
EcEsiGetDevicePdoIndices()
std::vector<uint16_t> EcEsiGetDevicePdoIndices(uint16_t masterIndex, uint16_t slaveIndex,
int direction, int maxCount = 64);
列出已绑定从站 ESI Device 声明的 PDO 索引(0x1600 / 0x1A00 等)。
参数:
masterIndex(uint16_t) — 主站编号slaveIndex(uint16_t) — 从站索引(1-based)direction(int) —0= RxPDO(Output, Master→Slave),1= TxPDO(Input, Slave→Master)maxCount(int) — 最大读取数量(默认 64)
返回值:
std::vector<uint16_t>— PDO 索引列表; 未绑定 / 失败返回空 vector
EcEsiGetDevicePdoSizeBits()
int EcEsiGetDevicePdoSizeBits(uint16_t masterIndex, uint16_t slaveIndex, uint16_t pdoIndex);
获取单个 PDO 在 ESI Device 中定义的总位长(含 Padding)。
返回值:
int—>= 0该 PDO 总 bit 数;< 0表示未绑定 / PDO 找不到 / 参数错误
EcEsiGetDeviceSmInfo()
std::optional<EcEsiSmInfo> EcEsiGetDeviceSmInfo(uint16_t masterIndex, uint16_t slaveIndex,
uint8_t smIdx);
获取 ESI Device <Sm> 节点信息(StartAddress / Length / ControlByte)。
参数:
smIdx(uint8_t) —0..7, ESI 中<Sm>出现顺序对应 SM0..SM7
返回值:
std::optional<EcEsiSmInfo>— 命中返回信息; 未绑定 / 索引越界返回std::nullopt
struct EcEsiSmInfo {
uint16_t StartAddress; // ESI <Sm StartAddress="...">
uint16_t Length; // ESI <Sm DefaultSize="..."> (兜底 MaxSize)
uint8_t ControlByte; // ESI <Sm ControlByte="...">
};
EcEsiGetDeviceMailboxTimeout()
std::optional<EcEsiMailboxTimeout> EcEsiGetDeviceMailboxTimeout(uint16_t masterIndex, uint16_t slaveIndex);
获取 ESI Device 邮箱 Request / Response 超时(毫秒)。字段缺失时对应值为 0 但仍视为成功(调用方应取默认值)。
返回值:
std::optional<EcEsiMailboxTimeout>— 已读取返回超时; 未绑定返回std::nullopt
struct EcEsiMailboxTimeout {
uint32_t RequestMs; // RequestTimeout (ms)
uint32_t ResponseMs; // ResponseTimeout (ms)
};
EcEsiGetDeviceIdentity()
std::optional<EcEsiIdentity> EcEsiGetDeviceIdentity(uint16_t masterIndex, uint16_t slaveIndex,
int nameBufSize = 256);
获取 ESI Device 厂商 / 产品 / Revision / 名称。
参数:
nameBufSize(int) — 名称缓冲容量(含结尾'\0'); native 不足时截断并保证'\0'
返回值:
std::optional<EcEsiIdentity>— 命中返回标识; 未绑定返回std::nullopt
struct EcEsiIdentity {
uint32_t VendorId; // Vendor Id
uint32_t ProductCode; // Product Code
uint32_t RevisionNo; // Revision No
std::string Name; // 设备名 (UTF-8)
};
示例:
using namespace darra::ethercat;
EsiManager mgr(&dll);
mgr.LoadPath("D:/esi");
mgr.ApplyAllSlaves(master.MasterNumber()); // 先把 ESI Device 绑到从站
uint16_t mi = master.MasterNumber(), si = 2;
// 厂商/产品/名称
if (auto id = EcEsiGetDeviceIdentity(mi, si)) {
printf("%s VID=0x%08X PID=0x%08X Rev=0x%08X\n",
id->Name.c_str(), id->VendorId, id->ProductCode, id->RevisionNo);
}
// TxPDO 索引 + 每个 PDO 的总位长
for (uint16_t pdo : EcEsiGetDevicePdoIndices(mi, si, /*TxPDO*/ 1)) {
int bits = EcEsiGetDevicePdoSizeBits(mi, si, pdo);
printf("TxPDO 0x%04X: %d bit\n", pdo, bits);
}
// SM2 节点信息
if (auto sm = EcEsiGetDeviceSmInfo(mi, si, 2))
printf("SM2: start=0x%04X len=%u ctrl=0x%02X\n",
sm->StartAddress, sm->Length, sm->ControlByte);
ESI 版本匹配
MatchRevision()
static bool MatchRevision(uint32_t actual, uint32_t expected,
EsiRevisionCheckStrategy strategy = EsiRevisionCheckStrategy::EQ_OR_G);
根据指定策略判断从站实际版本号与 ESI 期望版本号是否匹配。
参数:
actual(uint32_t) — 从站实际上报的版本号expected(uint32_t) — ESI 文件中定义的期望版本号strategy(EsiRevisionCheckStrategy) — 匹配策略
相关结构:
enum class EsiRevisionCheckStrategy : uint8_t {
None = 0, // 不检查版本号
EQ = 1, // 完全匹配
EQ_OR_G = 2, // 等于或大于 (默认推荐)
LW_EQ = 3, // 低 16 位匹配
HW_EQ = 4, // 高 16 位匹配
LW_EQ_OR_G = 5, // 低 16 位等于或大于
HW_EQ_OR_G = 6 // 高 16 位等于或大于
};
返回值:
bool— 匹配成功返回true
示例:
uint32_t slaveRev = 0x00130000;
uint32_t esiRev = 0x00120000;
bool exact = EsiManager::MatchRevision(slaveRev, esiRev, EsiRevisionCheckStrategy::EQ);
bool compat = EsiManager::MatchRevision(slaveRev, esiRev, EsiRevisionCheckStrategy::EQ_OR_G);
bool hwMatch = EsiManager::MatchRevision(slaveRev, esiRev, EsiRevisionCheckStrategy::HW_EQ);
EEPROM CRC 工具
CalculateEepromCrc() / ValidateEepromCrc() 是 darra 命名空间下的自由函数(utils/esi.hpp),
不是 EsiManager 的静态成员。调用时直接 darra::CalculateEepromCrc(...),不要写 EsiManager::。
CalculateEepromCrc()
namespace darra {
uint8_t CalculateEepromCrc(const uint8_t* data, int length);
}
计算 EEPROM 数据的 CRC-8 校验值 (ETG.2010 CRC-8/ITU 算法)。
参数:
data(const uint8_t*) — EEPROM 数据length(int) — 参与计算的数据长度
返回值:
uint8_t— CRC-8 校验值
示例:
auto eeprom = slave.ReadEeprom(0, 16);
uint8_t crc = darra::CalculateEepromCrc(eeprom.data(), 14);
printf("CRC: 0x%02X\n", crc);
ValidateEepromCrc()
namespace darra {
bool ValidateEepromCrc(const uint8_t* eepromData, int length);
}
校验 EEPROM 数据的 CRC 是否正确。内部对前 14 字节算 CRC-8 并与第 15 字节 (eepromData[14]) 比较。
参数:
eepromData(const uint8_t*) — 完整 EEPROM 数据length(int) — 数据长度
返回值:
bool— CRC 校验通过返回true
示例:
auto eeprom = slave.ReadEeprom(0, 128);
if (darra::ValidateEepromCrc(eeprom.data(), static_cast<int>(eeprom.size()))) {
printf("EEPROM CRC 校验通过\n");
} else {
printf("EEPROM CRC 校验失败, 数据可能已损坏\n");
}
完整示例
EsiManager 真实成员仅使用 DefaultPath() / AddFile() / LoadPath() / MatchRevision() / BindToSlave() /
ApplyAllSlaves() / AutoMatchAll() / GetLoadedCount() / GetFiles() / Clear()。
不要参考上文 C# 专有 API(PreloadDefault / LoadPathIncremental / GetDeviceInfo 等)写代码。
应用启动时预加载 ESI
int main() {
EsiManager mgr(&dll);
printf("正在加载 ESI 文件...\n");
// C++ 用 LoadPath 加载默认目录 (无进度回调重载)
int deviceCount = mgr.LoadPath(EsiManager::DefaultPath());
printf("成功加载 %d 个设备, 共 %d 个 ESI 文件\n",
deviceCount, mgr.GetLoadedCount());
return 0;
}
加载后绑定到从站
void BindEsiToTopology(EsiManager& mgr, EtherCATMaster& master) {
// 1. 把 ESI 目录加载进缓存
mgr.LoadPath("D:/esi");
// 2. 按 VendorId + ProductId 自动给所有从站匹配并绑定
int matched = mgr.ApplyAllSlaves(master.MasterNumber());
printf("已为 %d / %u 个从站匹配 ESI\n", matched, master.SlaveCount());
// 3. 对个别需要强制指定版本的从站, 单独绑定
mgr.BindToSlave(master.MasterNumber(), 2, "D:/esi/Vendor_Drive_v3.xml");
}
ESI 管理器封装
class ESIManagerWrapper {
public:
explicit ESIManagerWrapper(dll_t* dll) : mgr_(dll) {}
void Initialize(const std::string& customPath = "") {
if (initialized_) return;
printf("初始化 ESI 管理器...\n");
mgr_.LoadPath(EsiManager::DefaultPath());
if (!customPath.empty()) {
printf("加载自定义 ESI: %s\n", customPath.c_str());
mgr_.LoadPath(customPath);
}
initialized_ = true;
printf("ESI 初始化完成, 共加载 %d 个文件\n", mgr_.GetLoadedCount());
}
void Reload() {
mgr_.Clear();
initialized_ = false;
Initialize();
}
private:
EsiManager mgr_;
bool initialized_ = false;
};
注意事项
ESI 文件加载可能需要几秒钟。建议在应用启动时一次性 LoadPath() 预加载, 避免运行时延迟。
确保 ESI 文件路径正确且文件有效。无效的 ESI 文件会被自动跳过, 不会影响其他文件的加载。