跳到主要内容

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")

查询和检索

解析单个 ESI 设备节点

要拿到结构化的 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) — 厂商 ID
  • product_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) — 解析成功返回数据类, 失败返回 None
  • message (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() 自动跳过已加载文件,等同于增量加载,可以避免重复加载相同文件,提高效率。

相关功能

ESI 设备信息在主站构造从站管理和本页 ENI 配置中均有使用。