ESI 文件管理
EsiManager 静态类提供 EtherCAT Slave Information (ESI) 文件的加载、管理和查询功能。ESI 文件包含从站的完整配置信息。
EsiManager(darra_ethercat.utils.esi) — ESI 文件加载 / 缓存的静态类EsiLoader(darra_ethercat.slave.esi) — 为单个从站绑定 ESI 文件ESIDeviceInfo(darra_ethercat.utils.esi) — ESI 解析出的设备详细信息数据类
加载和管理
load_path()
@classmethod
def load_path(cls, path: str,
progress_callback: Optional[Callable[[int, int, str], None]] = None) -> None
加载指定路径下的所有 ESI 文件。内部自动跳过已加载文件 (等效增量加载)。
参数:
path(str) — ESI 文件目录路径progress_callback(Callable, 可选) — 进度回调(current, total, file_name)
示例:
from darra_ethercat.utils.esi import EsiManager
# 加载默认路径
EsiManager.load_default()
# 加载指定路径,显示进度
EsiManager.load_path(r"C:\EtherCAT\ESI", lambda cur, total, name:
print(f"加载ESI文件: {cur}/{total} - {name}"))
preload()
@classmethod
def preload(cls, progress_callback: Optional[Callable[[int, int, str], None]] = None) -> None
预加载默认路径的 ESI 文件。
示例:
# 启动时预加载
EsiManager.preload(lambda cur, total, name:
print(f"\r加载ESI: {cur * 100 // total}% - {name}", end=""))
print("\nESI文件加载完成")
load_default()
@classmethod
def load_default(cls,
progress_callback: Optional[Callable[[int, int, str], None]] = None) -> None
从默认路径加载 ESI 文件 (与 preload() 等价, 显式语义版本)。
示例:
EsiManager.load_default()
clear()
@classmethod
def clear(cls) -> None
清除所有已加载的 ESI 文件。
示例:
# 清除并重新加载
EsiManager.clear()
EsiManager.load_path(r"C:\NewESI")
remove_from_cache()
@classmethod
def remove_from_cache(cls, file_name: str) -> None
从缓存中移除指定 ESI 文件。
示例:
EsiManager.remove_from_cache("MyVendor_EL7211.xml")
查询和检索
要拿到结构化的 ESIDeviceInfo (产品名 / 设备类 / 物理层 / CoE/DC 能力等), 用 EsiLoader 绑定某个从站的 ESI 文件后读 loader.device_info (见下文 ESI 绑定到从站)。EsiManager 静态类本身只负责文件级加载 / 缓存, 不提供按 VID/PID 检索设备信息的方法。
get_config_data_bytes()
@classmethod
def get_config_data_bytes(cls, vendor_id: int, product_code: int,
revision: int = 0) -> Optional[bytes]
获取 ESI 配置数据的原始字节。在已加载的 ESI 文件中查找匹配的设备,返回其 EEPROM 配置数据。
参数:
vendor_id(int) — 厂商 IDproduct_code(int) — 产品代码revision(int, 可选) — 修订号,0 表示不限
返回值:
Optional[bytes]— 配置数据字节,未找到返回None
示例:
config = EsiManager.get_config_data_bytes(0x00000002, 0x03F03052)
if config is not None:
print(f"配置数据: {config.hex()}")
find_esi_file()
def find_esi_file(filename: str, search_paths: Optional[List[str]] = None) -> Optional[str]
根据文件名查找 ESI 文件完整路径。
参数:
filename(str) — ESI 文件名search_paths(List[str], 可选) — 搜索路径列表,None使用默认路径
返回值:
Optional[str]— 完整路径,未找到返回None
示例:
from darra_ethercat.utils.esi import find_esi_file
path = find_esi_file("Beckhoff EL1008.xml")
if path is not None:
print(f"找到文件: {path}")
状态查询
loaded_file_count()
@classmethod
def loaded_file_count(cls) -> int
获取已加载的 ESI 文件数量。
示例:
count = EsiManager.loaded_file_count()
print(f"已加载 {count} 个ESI文件")
is_file_loaded()
@classmethod
def is_file_loaded(cls, file_name: str) -> bool
检查指定 ESI 文件是否已加载。
示例:
if EsiManager.is_file_loaded("MyDevice.xml"):
print("自定义设备ESI已加载")
is_preloaded()
@classmethod
def is_preloaded(cls) -> bool
检查是否已预加载(即是否有任何已加载文件)。
示例:
if not EsiManager.is_preloaded():
EsiManager.preload()
默认路径
default_path()
@classmethod
def default_path(cls) -> str
获取默认 ESI 文件搜索路径。
返回值:
str— 默认 ESI 目录路径
示例:
path = EsiManager.default_path()
print(f"默认ESI路径: {path}")
ESI 绑定到从站
加载到 ESI 缓存后, 还需要将 ESI 设备节点绑定到具体从站, 才能让 SDK 在拓扑/PDO/DC/邮箱协议解析时使用 ESI 信息。Python SDK 通过 EsiLoader 类完成绑定。
EsiLoader.load()
class EsiLoader:
def load(self, file_path: str) -> bool
为单个从站加载并绑定指定 ESI 文件。常用于已知拓扑、需要为某站强制使用某个版本 ESI 的场景。
参数:
file_path(str) — ESI XML 文件完整路径
返回值:
bool— 绑定成功返回True; 文件无效 / 解析失败返回False
示例:
from darra_ethercat.slave.esi import EsiLoader
# 给从站 #2 绑定特定厂商版本的 ESI
loader = EsiLoader(slave=master[2])
ok = loader.load(r"D:\esi\Vendor_Drive_v3.xml")
if not ok:
print("绑定失败, 检查从站索引和文件路径")
EsiLoader.is_loaded / file_path / device_info
@property
def is_loaded(self) -> bool
@property
def file_path(self) -> Optional[str]
@property
def device_info(self) -> EsiDeviceInfo
绑定后查询 ESI 加载状态、文件路径与解析出的设备信息。
示例:
loader = EsiLoader(slave=master[2])
loader.load(r"D:\esi\Vendor.xml")
if loader.is_loaded:
print(f"ESI 文件: {loader.file_path}")
print(f"设备名: {loader.device_info.device_name}")
print(f"启动参数数量: {len(loader.startup_params)}")
绑定应在 master.set_state(EcState.PRE_OP) 完成、从站列表就绪之后调用。SDK 在 PDO 配置 / DC 启用 / 邮箱协议建立阶段会自动消费已绑定的 ESI 信息。
ESI 版本匹配
match_revision()
from darra_ethercat.utils.esi import match_revision, ESIRevisionCheckStrategy
def match_revision(actual: int, expected: int,
strategy: ESIRevisionCheckStrategy) -> bool
根据指定策略判断从站实际版本号与 ESI 期望版本号是否匹配 (模块级函数)。
ESIRevisionCheckStrategy 枚举:
class ESIRevisionCheckStrategy(IntEnum):
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位等于或大于
EEPROM CRC 工具
calculate_eeprom_crc()
def calculate_eeprom_crc(data: bytes, length: int) -> int
计算 EEPROM 数据的 CRC-8 校验值。使用 ETG.2010 定义的 CRC-8/ITU 算法(多项式 0x07,初始值 0xFF)。
参数:
data(bytes) — EEPROM 数据length(int) — 参与计算的数据长度
返回值:
int— CRC-8 校验值
示例:
from darra_ethercat.utils.esi import calculate_eeprom_crc
eeprom_data = slave.read_eeprom(0, 16)
crc = calculate_eeprom_crc(eeprom_data, 14) # 前 14 字节参与 CRC
print(f"CRC: 0x{crc:02X}")
validate_eeprom_crc()
def validate_eeprom_crc(eeprom_data: bytes) -> bool
校验 EEPROM 数据的 CRC 是否正确。自动读取数据中的 CRC 字段并与计算值比较。
参数:
eeprom_data(bytes) — 完整 EEPROM 数据(至少 15 字节)
返回值:
bool— CRC 校验通过返回True
示例:
from darra_ethercat.utils.esi import validate_eeprom_crc
eeprom = slave.read_eeprom(0, 128)
if validate_eeprom_crc(eeprom):
print("EEPROM CRC 校验通过")
else:
print("EEPROM CRC 校验失败,数据可能已损坏")
完整示例
应用启动时预加载 ESI
from darra_ethercat.utils.esi import EsiManager
print("正在加载ESI文件...")
EsiManager.preload(lambda cur, total, name:
print(f"\r进度: {cur * 100 // total}% ({cur}/{total}) - {name}", end=""))
print(f"\n成功加载 {EsiManager.loaded_file_count()} 个ESI文件")
从站识别 (基础属性)
from darra_ethercat import EtherCATMaster, EcState
from darra_ethercat.utils.esi import EsiManager
# 预加载 ESI
EsiManager.preload()
with EtherCATMaster() as master:
master.set_eni(r"C:\config.deni")
master.set_network(r"\\Device\\NPF_{GUID}")
master.set_state(EcState.OP)
# 遍历从站,读取从站基础标识属性
for slave in master.slaves:
print(f"[{slave.slave_num}] {slave.name} "
f"VID=0x{slave.vendor_id:08X} PID=0x{slave.product_id:08X} "
f"Rev=0x{slave.rev_id:08X}")
缓存管理
from darra_ethercat.utils.esi import EsiManager
# 检查特定文件是否已加载
if EsiManager.is_file_loaded("MyVendor_EL7211.xml"):
print("自定义设备 ESI 已加载")
# 从缓存中移除特定文件(例如更新 ESI 后重新加载)
EsiManager.remove_from_cache("MyVendor_EL7211.xml")
EsiManager.load_path(r"C:\ESI\Updated")
# 完全清除并重新加载
EsiManager.clear()
EsiManager.load_path(r"C:\ESI")
print(f"重新加载完成: {EsiManager.loaded_file_count()} 个文件")
自定义设备支持
import os
from darra_ethercat.utils.esi import EsiManager
def load_custom_devices(custom_esi_path: str):
# 加载标准 ESI
EsiManager.preload()
# 增量加载自定义设备 ESI
if os.path.isdir(custom_esi_path):
EsiManager.load_path(custom_esi_path)
print("自定义设备ESI已加载")
ESI 诊断工具
from darra_ethercat.utils.esi import EsiManager
def generate_esi_report():
print("=== ESI诊断报告 ===")
print()
# 基本统计
file_count = EsiManager.loaded_file_count()
print(f"已加载文件数: {file_count}")
print(f"默认路径: {EsiManager.default_path()}")
print()
# 检查预加载状态
if EsiManager.is_preloaded():
print("ESI 已预加载")
else:
print("ESI 未预加载")
ESI 管理器封装
from darra_ethercat.utils.esi import EsiManager
import os
class ESIManagerWrapper:
_initialized = False
@classmethod
def initialize(cls, custom_path: str = None):
if cls._initialized:
return
print("初始化ESI管理器...")
# 加载默认 ESI 文件
print("加载标准ESI文件...")
EsiManager.preload(lambda cur, total, name:
print(f"\r 进度: {cur * 100 // total}% ({cur}/{total}) - {name}", end=""))
# 加载自定义 ESI 文件
if custom_path and os.path.isdir(custom_path):
print(f"\n加载自定义ESI文件: {custom_path}")
EsiManager.load_path(custom_path)
cls._initialized = True
print(f"\nESI初始化完成,共加载 {EsiManager.loaded_file_count()} 个文件")
@classmethod
def reload(cls):
print("重新加载ESI文件...")
EsiManager.clear()
cls._initialized = False
cls.initialize()
ENI 配置导出
ENI (ETG.2100 EtherCATConfig) 是 EtherCAT 主站的标准网络配置文件. SDK 提供 save_eni() 直接生成符合 ETG.2100 的 XML 骨架.
EniMasterConfig
@dataclass
class EniMasterConfig:
name: str = "" # 主站名称
cycle_time_us: int = 1000 # 周期时间 (微秒)
dc_cycle_time_us: int = 1000 # DC 周期时间 (微秒)
expected_slave_count: int = 0 # 期望从站数量
EniSlaveConfig
@dataclass
class EniSlaveConfig:
physical_address: int = 0 # PhysAddr (站地址)
auto_inc_address: int = 0 # AutoIncAddr (自增地址)
vendor_id: int = 0 # VendorID
product_code: int = 0 # ProductCode
revision_number: int = 0 # RevisionNo
name: str = "" # 设备名称
dc_assign_activate: int = 0 # DC AssignActivate
sm2_offset: int = 0 # SM2 起始地址
sm2_length: int = 0 # SM2 长度
sm3_offset: int = 0 # SM3 起始地址
sm3_length: int = 0 # SM3 长度
EniConfiguration
@dataclass
class EniConfiguration:
master: EniMasterConfig # 主站配置
slaves: List[EniSlaveConfig] # 从站列表
version: str = "3.0" # ENI XML 版本
save_eni()
def save_eni(file_path: str, config: EniConfiguration) -> bool
把 EniConfiguration 序列化为 ETG.2100 XML 文件.
参数:
file_path(str) — 输出 XML 路径config(EniConfiguration) — 主站 + 从站列表配置
返回值:
bool— 写盘成功返回True; 参数非法 / IO 失败返回False
示例:
from darra_ethercat.utils.esi import (
EniConfiguration, EniMasterConfig, EniSlaveConfig, save_eni
)
config = EniConfiguration(
master=EniMasterConfig(
name="Darra EtherCAT Master",
cycle_time_us=1000,
dc_cycle_time_us=1000,
expected_slave_count=2,
),
slaves=[
EniSlaveConfig(
physical_address=0x03E9,
auto_inc_address=0,
vendor_id=0x00000002,
product_code=0x044C2C52,
name="EK1100",
),
EniSlaveConfig(
physical_address=0x03EA,
auto_inc_address=0xFFFF,
vendor_id=0x00000002,
product_code=0x07D03052,
name="EL2008",
sm2_offset=0x0F00, sm2_length=0x10,
sm3_offset=0x1000, sm3_length=0x10,
),
],
)
ok = save_eni(r"C:\EtherCAT\my_network.xml", config)
print(f"ENI 导出: {'成功' if ok else '失败'}")
此 ENI 输出仅包含 ETG.2100 必填的 Master + Slave[] 信息 (Info / ProcessData / Dc). DC offset / cycle 真实值需要主站完整 build 后才能取得. 如需带完整 PDO Mapping / Cyclic / DC 真实运行数据的 ENI, 请使用 Darra 配置工具导出 DENI 文件.
主站 XML 配置
由 Darra 主界面 / PLC 中间层导出的 DENI/XML 配置文件可通过 SDK 加载, 得到 MasterXMLConfiguration 数据类。
SDK 不提供反向写盘 API (save_xml_configuration 已移除, DENI 导出由 Darra 主界面/PLC 中间层负责)。
MasterXMLConfiguration
主站 XML 配置数据类 (与 DENI/XML 字段一一对应)。
@dataclass
class MasterXMLConfiguration:
version: str = "2.0" # XML 版本
cycle_time_us: int = 1000 # PDO 周期 (微秒)
dc_cycle_ns: int = 1000000 # DC 周期 (纳秒)
adapter_name: str = "" # 主网卡设备名
adapter_description: str = "" # 主网卡描述
adapter_mac: str = "" # 主网卡 MAC
redundant_adapter_name: str = "" # 冗余网卡设备名
redundant_adapter_description: str = "" # 冗余网卡描述
redundant_adapter_mac: str = "" # 冗余网卡 MAC
redundancy_enabled: bool = False # 是否启用冗余
expected_slave_count: int = 0 # 期望从站数量
use_udp: bool = False # 是否使用 UDP 模式
vlan_id: int = 0 # VLAN ID (0=禁用)
vlan_priority: int = 0 # VLAN 优先级 (0-7)
enable_overlapping_groups: bool = False # PDO 重叠模式
packed_mode: bool = False # Packed PDO 映射
frame_high_priority: bool = False # 帧高优先级
cpu_affinity: int = -1 # CPU 亲和性 (-1=自动)
pdo_logging: bool = False # PDO 日志
mailbox_logging: bool = False # 邮箱日志
adaptive_timeout: bool = False # 自适应超时
frame_repeat_count: int = 1 # 帧重复次数 (1-3)
filter_threshold: int = 3 # PDO 连续丢帧阈值
mutex_protection: bool = True # 互斥锁保护
load_xml_configuration()
def load_xml_configuration(path: str) -> Tuple[Optional[MasterXMLConfiguration], str]
从 DENI/XML 文件加载主站配置。
参数:
path(str) — DENI/XML 文件完整路径
返回值:
config(MasterXMLConfiguration | None) — 解析成功返回数据类, 失败返回Nonemessage(str) — 结果信息 ("加载成功"或"加载失败: <原因>")
示例:
from darra_ethercat import load_xml_configuration
config, msg = load_xml_configuration(r"C:\config\my_network.deni")
if config is None:
print(f"加载失败: {msg}")
else:
print(f"周期 {config.cycle_time_us} us, 期望 {config.expected_slave_count} 从站")
print(f"主网卡: {config.adapter_description}")
if config.redundancy_enabled:
print(f"冗余网卡: {config.redundant_adapter_description}")
DENI 文件由 Darra 主界面或 PLC 中间层生成, SDK 端只需 load_xml_configuration() 读取并按字段调用 master.config 各项参数即可。
注意事项
ESI 文件加载可能需要几秒钟。建议在应用启动时使用 preload() 预加载,避免运行时延迟。
确保 ESI 文件路径正确且文件有效。无效的 ESI 文件会被自动跳过,不会影响其他文件的加载。
EsiManager.load_path() 自动跳过已加载文件,等同于增量加载,可以避免重复加载相同文件,提高效率。