ESI 文件管理 (Esi)
EtherCAT Slave Information (ESI) 文件包含从站的完整配置信息,包括设备标识、对象字典、PDO 映射、DC 配置和启动参数 (InitCmd)。Rust SDK 提供两套互补的 ESI 接口:
EsiLoader—— 每个从站一个的加载器实例,通过slave.esi()获取,把 ESI 文件解析后自动把启动参数注入该从站。EsiManager—— 进程级 ESI 仓库(位于darra_ethercat::utils::esi),静态方法管理 ESI 文件目录、批量绑定从站。- 一组模块级自由函数 ——
load_esi_file()/load_and_apply_startup()/match_revision()/calculate_eeprom_crc()/validate_eeprom_crc()/load_eni()/save_eni()。
EsiLoader —— 从站级 ESI 加载器
EsiLoader 通过 slave.esi() 获取(直接返回实例,不是 Option)。它持有对应从站句柄,load() 时解析 ESI 文件并把其中的 InitCmd 启动参数自动添加到该从站。
| 方法 | 类别 | 读写 | 说明 |
|---|---|---|---|
| load(file_path) | ESI 加载 | 操作 | 解析 ESI 文件并注入从站启动参数 |
| is_loaded() | 状态 | 只读 | ESI 文件是否已加载 |
| file_path() | 状态 | 只读 | 已加载的 ESI 文件路径 |
| device_info() | 设备信息 | 只读 | 解析后的设备信息 |
load()
pub fn load(&mut self, file_path: &str) -> bool
加载 ESI XML 文件:解析设备信息并提取 InitCmd 启动参数 (SDO init),自动添加到从站的启动参数列表。成功返回 true,文件不存在或解析失败返回 false。
is_loaded() / file_path() / device_info()
pub fn is_loaded(&self) -> bool
pub fn file_path(&self) -> Option<&str>
pub fn device_info(&self) -> Option<&EsiDeviceInfo>
示例:
let slave = master.slave(1);
let mut esi = slave.esi(); // 直接返回 EsiLoader (非 Option)
if esi.load(r"C:\EtherCAT\ESI\Beckhoff EL1008.xml") {
if let Some(info) = esi.device_info() {
println!("设备: {} ({})", info.product_name, info.device_class);
}
println!("ESI 路径: {:?}", esi.file_path());
}
EsiLoader 也实现 Display,可直接 println!("{}", esi)。
EsiManager —— 进程级 ESI 仓库
EsiManager(darra_ethercat::utils::esi::EsiManager)是一组静态方法,管理整个进程的 ESI 文件缓存,并可批量绑定从站。
| 方法 | 类别 | 读写 | 说明 |
|---|---|---|---|
| default_path() | 路径 | 只读 | 默认 ESI 目录 (<cwd>/ESI) |
| add_file(file_path) | 加载 | 操作 | 加载单个 ESI 文件,返回 Device 数 |
| load_path(dir_path) | 加载 | 操作 | 加载目录下全部 .xml/.esi 文件 |
| bind_to_slave(mi, si, file_path) | 绑定 | 操作 | 给指定从站绑定 ESI 文件 |
| apply_all_slaves(mi) | 绑定 | 操作 | 自动给主站下所有从站匹配并应用 ESI |
| get_loaded_count() | 状态 | 只读 | 已加载 ESI 文件数 |
| get_files() | 状态 | 只读 | 已加载文件路径快照 |
| clear() | 维护 | 操作 | 清空 managed + DLL 缓存 |
| match_revision(actual, expected, strategy) | 版本 | 只读 | 版本号匹配判断 |
default_path()
pub fn default_path() -> String
返回默认 ESI 目录,即 <进程当前目录>/ESI(不存在时会自动创建)。
add_file() / load_path()
pub fn add_file(file_path: &str) -> i32
pub fn load_path(dir_path: &str) -> i32
add_file() 加载单个 ESI 文件,load_path() 加载目录下所有 .xml / .esi 文件。两者均返回 DLL 报告的 Device 数(FFI 不可用时返回 0)。
示例:
use darra_ethercat::utils::esi::EsiManager;
// 加载默认目录
let n = EsiManager::load_path(&EsiManager::default_path());
println!("从默认目录加载了 {} 个设备", n);
// 加载指定目录 / 单个文件
EsiManager::load_path(r"C:\EtherCAT\ESI");
EsiManager::add_file(r"C:\CustomDevices\MyDevice.xml");
bind_to_slave() / apply_all_slaves()
pub fn bind_to_slave(master_index: u16, slave_index: u16, file_path: &str) -> i32
pub fn apply_all_slaves(master_index: u16) -> i32
bind_to_slave() 给单个从站绑定指定 ESI 文件;apply_all_slaves() 自动给主站下所有从站匹配并应用 ESI。
get_loaded_count() / get_files() / clear()
pub fn get_loaded_count() -> i32
pub fn get_files() -> Vec<String>
pub fn clear()
get_loaded_count() 返回已加载 ESI 文件数;get_files() 返回已加载文件路径快照 (Vec<String>);clear() 清空 managed 与 DLL 两侧缓存。
示例:
use darra_ethercat::utils::esi::EsiManager;
EsiManager::load_path(r"C:\EtherCAT\ESI");
println!("已加载 {} 个 ESI 文件", EsiManager::get_loaded_count());
for path in EsiManager::get_files() {
println!(" - {}", path);
}
// 重新加载
EsiManager::clear();
EsiManager::load_path(r"C:\EtherCAT\ESI");
EsiDeviceInfo
EsiLoader::device_info() 与 load_esi_file() 返回的设备信息结构(节选主要字段):
pub struct EsiDeviceInfo {
pub product_name: String, // 产品名称
pub product_text: String, // 产品描述文本
pub product_url: String, // 产品 URL
pub device_class: String, // 设备类型
pub group_type: String, // 分组类型
pub file_name: String, // 来源 ESI 文件名
pub product_id: u32, // 产品代码
pub revision_id: u32, // 修订号
pub vendor_id: u32, // 厂商 ID
pub vendor_name: String, // 厂商名称
pub vendor_comment: String, // 厂商备注
pub physics: String, // 物理层类型 (如 "YY", "KK")
pub profile_number: String, // CoE Profile 号
pub revision_check_strategy: EsiRevisionCheckStrategy, // 版本检查策略
pub supports_coe: bool, // 支持 CoE
pub supports_eoe: bool, // 支持 EoE
pub supports_foe: bool, // 支持 FoE
pub supports_soe: bool, // 支持 SoE
pub supports_voe: bool, // 支持 VoE
pub supports_frame_repeat: bool, // 支持帧重复
// 以下为可选嵌套结构 (Option<...>):
pub coe_details: Option<EsiCoEDetails>,
pub dc_configuration: Option<EsiDcConfiguration>,
pub eeprom_configuration: Option<EsiEepromConfiguration>,
pub electrical: Option<EsiElectricalInfo>,
pub identification: Option<EsiIdentification>,
pub mailbox_timeout: Option<EsiMailboxTimeout>,
pub physics_info: Option<EsiPhysicsInfo>,
pub ports: Vec<EsiPortInfo>,
}
EsiDeviceInfo 同时携带 product_id / revision_id 与 vendor_id / vendor_name / vendor_comment(厂商信息由 ESI 文件的 Vendor 节点解析填入)。dc_assign_activate() / supports_dc() / protocol_summary() 是其便捷方法。
模块级自由函数
load_esi_file()
pub fn load_esi_file(path: &str) -> Result<EsiDeviceInfo, String>
直接解析单个 ESI XML 文件为 EsiDeviceInfo,不绑定任何从站。失败返回 Err(String)。
use darra_ethercat::load_esi_file;
let info = load_esi_file(r"C:\EtherCAT\ESI\Beckhoff EL1008.xml")?;
println!("产品: {} (PC=0x{:08X}, Rev=0x{:08X})",
info.product_name, info.product_id, info.revision_id);
load_and_apply_startup()
pub fn load_and_apply_startup(mi: u16, si: u16, path: &str) -> Result<i32, String>
解析 ESI 文件,提取 InitCmd 中的 SDO 启动参数,通过 FFI 添加到指定从站。成功返回添加的参数数量。
use darra_ethercat::load_and_apply_startup;
let count = load_and_apply_startup(master.index(), 1,
r"C:\EtherCAT\ESI\MyDrive.xml")?;
println!("已应用 {} 条启动参数", count);
extract_startup_parameters()
pub fn extract_startup_parameters(xml: &str) -> Vec<EsiStartupParam>
从 ESI XML 文本中提取所有 InitCmd 启动参数。
pub struct EsiStartupParam {
pub index: u16, // 对象索引
pub subindex: u8, // 子索引
pub data: Vec<u8>, // 数据
pub transition: u8, // 状态转换类型
pub timing: u8, // 写入时序
pub complete_access: bool, // 完整访问标志
}
ESI 版本匹配
match_revision()
pub fn match_revision(actual: u32, expected: u32,
strategy: EsiRevisionCheckStrategy) -> bool
根据指定策略判断从站实际版本号与 ESI 期望版本号是否匹配。match_revision 是 crate 根重新导出的自由函数,EsiManager::match_revision() 是其同名转调。
#[derive(Debug, Clone, Copy)]
pub enum EsiRevisionCheckStrategy {
None, // 不检查版本号
Eq, // 完全匹配
EqOrG, // 等于或大于
LwEq, // 低 16 位匹配
HwEq, // 高 16 位匹配
LwEqOrG, // 低 16 位等于或大于
HwEqOrG, // 高 16 位等于或大于
}
示例:
use darra_ethercat::{match_revision, EsiRevisionCheckStrategy};
let slave_rev: u32 = 0x00130000; // actual: 从站实际版本
let esi_rev: u32 = 0x00120000; // expected: ESI 期望版本
// 严格匹配 — 不通过
let exact = match_revision(slave_rev, esi_rev, EsiRevisionCheckStrategy::Eq);
// 允许更高版本 — 通过 (actual >= expected)
let compat = match_revision(slave_rev, esi_rev, EsiRevisionCheckStrategy::EqOrG);
ENI 配置文件
ENI (EtherCAT Network Information, ETG.2100) 是网络级配置文件,描述完整的从站列表 / PDO 映射 / DC 设置 / 启动 SDO。load_eni() / save_eni() 是 darra_ethercat::utils::esi 下的自由函数。
load_eni()
pub fn load_eni(path: &str) -> Result<MasterXmlConfiguration, String>
从 XML 文件解析 ENI/DENI 网络配置(内部走 utils::xml::load_xml_configuration,同时识别 <EtherCATConfig> 根 (ENI) 与旧版 DENI 根)。失败返回 Err(String)。
use darra_ethercat::utils::esi::load_eni;
let cfg = load_eni(r"C:\projects\machine_a.eni")?;
// MasterXmlConfiguration 描述主站 + 从站配置
save_eni()
pub fn save_eni(path: &str, config: &MasterXmlConfiguration) -> Result<(), String>
把内存中的 MasterXmlConfiguration 序列化回 ENI XML 写入文件。失败返回 Err(String)。
日常推荐用 Darra 配置工具导出的 DENI 文件配合 EtherCATMaster::builder().set_eni(...) 加载;load_eni / save_eni 适合在 SDK 端做 ENI XML 的检查与互通。
EEPROM CRC 工具
calculate_eeprom_crc()
pub fn calculate_eeprom_crc(data: &[u8], length: usize) -> u8
计算 EEPROM 数据的 CRC-8 校验值(ETG.2010 定义的 CRC-8/ITU 算法)。
validate_eeprom_crc()
pub fn validate_eeprom_crc(eeprom_data: &[u8]) -> bool
校验 EEPROM 数据的 CRC 是否正确,自动读取数据中的 CRC 字段并与计算值比较。
示例:
use darra_ethercat::validate_eeprom_crc;
let eeprom = slave.read_eeprom(0, 128)?; // read_eeprom 返回 Result<Vec<u8>>
if validate_eeprom_crc(&eeprom) {
println!("EEPROM CRC 校验通过");
} else {
println!("EEPROM CRC 校验失败,数据可能已损坏");
}
完整示例
应用启动时加载 ESI 仓库
use darra_ethercat::utils::esi::EsiManager;
println!("加载 ESI 文件...");
let n = EsiManager::load_path(&EsiManager::default_path());
println!("从默认目录加载了 {} 个设备", n);
println!("ESI 仓库共 {} 个文件", EsiManager::get_loaded_count());
给从站绑定 ESI 并应用启动参数
let slave = master.slave(1);
let mut esi = slave.esi();
if esi.load(r"C:\EtherCAT\ESI\MyDrive.xml") {
// load() 已自动把 InitCmd 启动参数注入从站
if let Some(info) = esi.device_info() {
println!("已绑定 ESI: {} (CoE={})", info.product_name, info.supports_coe);
}
} else {
println!("ESI 加载失败");
}
批量给所有从站匹配 ESI
use darra_ethercat::utils::esi::EsiManager;
EsiManager::load_path(r"C:\EtherCAT\ESI");
let applied = EsiManager::apply_all_slaves(master.index());
println!("已为 {} 个从站匹配并应用 ESI", applied);
注意事项
ESI 文件解析可能耗时较长。建议在应用启动阶段一次性 EsiManager::load_path() 预加载整个目录。
确保 ESI 文件路径正确且文件有效。无效的 ESI 文件会被跳过,不影响其他文件加载。