跳到主要内容

Slave 属性与方法

通过 master[n] 或 master.slave(n) 访问(1-based 索引)。​

提示
  • PDO 输入输出(结构体映射、字节数组、索引访问等)请参见 PDO 输入输出 页面。
  • 从站事件请参见 事件 页面。
  • 从站诊断请参见 从站诊断 页面。

属性​

类别属性类型读写说明
基本标识slave_numint只读从站编号(1-based)
indexint只读从站索引(slave_num 别名)
namestr只读从站组名(group_name,ESI GroupType 名)
drive_namestr只读设备名称(SDO 0x1008 Device Name)
设备信息vendor_idint只读制造商 ID(从 SII EEPROM 读取)
vendor_namestr只读制造商名称(从 ESI 文件读取,读取失败返回空字符串)
product_idint只读产品 ID
rev_idint只读修订版本号
serial_numberint只读序列号(从 SII EEPROM 读取)
has_mdpbool只读是否支持模块化设备配置文件(ETG.5001)
block_lrwbool只读LRW 逻辑读写操作阻止标志
地址config_addrint只读物理配置地址
alias_addressint只读别名地址
状态stateEcState只读从站当前状态
error_codeEcALState只读错误码
is_lostbool只读从站是否丢失(断开连接)
拓扑topologyint只读拓扑类型 (0=无链接, 1=端点, 2=中间, 3=分支, 4=交叉)
parent_indexint只读父从站索引 (1-based),0=父节点是主站
parent_stationint只读父从站索引 (1-based,命名为历史遗留,实际为 index 非 station address;0=父节点是主站)
parent_portint只读父端口号
entry_portint只读入口端口号
active_portsint只读激活端口位掩码
physical_typeint只读物理端口类型
child_countint只读子模块数量
PDO 数据ibits / obitsint只读输入/输出数据位数
ibytes / obytesint只读输入/输出数据字节数
ioffset / ooffsetint只读输入/输出在过程映像中的偏移
istartbit / ostartbitint只读输入/输出起始位
ESI/配置has_esibool只读是否已加载 ESI 文件
esi_versionstr只读ESI 版本号
EEPROMeep_8byte_addressingbool只读EEPROM 寻址模式(True=8字节, False=4字节)
eep_pdiint只读物理设备接口(PDI)类型
ebus_currentint只读E-bus 电流消耗(mA)
邮箱mbx_protoint只读支持的邮箱协议位掩码(ECT_MBXPROT_*:AoE=0x01、EoE=0x02、CoE=0x04、FoE=0x08、SoE=0x10、VoE=0x20)
mbx_lengthint只读邮箱发送缓冲区大小
mbx_read_lengthint只读邮箱接收缓冲区大小
mbx_read_offset / mbx_write_offsetint只读邮箱读/写偏移
mbx_countint只读邮箱协议计数器
协议详情coe_detailsint只读CoE 协议功能标志(EcCoEDetails 位掩码)
eoe_detailsint只读EoE 协议功能标志(EcEoEDetails 位掩码)
foe_detailsint只读FoE 协议详情(来自 SII EEPROM)
soe_detailsint只读SoE 协议详情(来自 SII EEPROM)
FMMUfmmu0_functionint只读FMMU0 功能类型(bit 0=输出, bit 1=输入)
fmmu1_functionint只读FMMU1 功能类型
fmmu2_functionint只读FMMU2 功能类型
fmmu3_functionint只读FMMU3 功能类型
DChas_dcbool只读是否支持 DC,详见 DC 同步
dc_activeint只读DC AssignActivate 值(16 位,0=禁用,非 0=已激活),详见 DC 同步
dc_cycle0int只读SYNC0 周期(纳秒),详见 DC 同步
dc_cycle1int只读SYNC1 周期(纳秒),详见 DC 同步
dc_shiftint只读相位偏移(纳秒),详见 DC 同步
dc_nextint只读DC 链中下一个从站的索引
dc_previousint只读DC 链中上一个从站的索引
dc_parent_portint只读DC 父端口号
dc_receive_time_aint只读端口 A 接收时间(纳秒)
dc_receive_time_bint只读端口 B 接收时间(纳秒)
dc_receive_time_cint只读端口 C 接收时间(纳秒)
dc_receive_time_dint只读端口 D 接收时间(纳秒)
propagation_delayint只读帧从主站到达此从站的传播延迟(纳秒),详见 DC 同步
pdelayint只读传播延迟(纳秒,propagation_delay 别名)
拓扑扩展childrenList[Slave]只读子从站列表(配合 child_count 使用,导航拓扑树)
supports_frame_repeatbool读写是否支持帧重复功能(ETG.1500 5.4.3)
冗余redundancy_activatedbool只读冗余是否已激活
primary_link_brokenbool只读主线路是否断路
secondary_link_brokenbool只读冗余线路是否断路
运行诊断emcy_countint只读EMCY 紧急消息数量
emcy_historyList[dict]只读EMCY 历史记录(dict 键 error_code / error_register / data / slave_index / timestamp_ms,键访问不是属性访问)
link_qualityint只读链路质量(0-100%)
sync_window_statusdict | None只读同步窗口状态(键 diff_ns / max_diff_ns / min_diff_ns / in_sync / out_of_sync_count),读取失败返回 None
配置groupint读写从站分组号(0-7,0=默认组,必须在 SAFE_OP 前设置),详见 从站分组
is_optionalbool读写可选从站标记,缺席时不影响 WKC 检查、不触发组离线告警(必须在 OP 前设置)
EcTopologyType / EcPortType 枚举值
class EcTopologyType(IntEnum):
NO_LINK = 0 # 无链接
END_POINT = 1 # 端点
LINE = 2 # 中间节点(线性拓扑)
FORK = 3 # 分支点
CROSS = 4 # 交叉点

class EcPortType(IntEnum):
NOT_USED = 0 # 未使用
MII = 1 # MII
EBUS = 2 # EBUS
EBUS_ENHANCED = 3 # EBUS 增强型
MailboxType 常量

邮箱报文头里的类型码(不是 mbx_proto 位掩码;mbx_proto 走 ECT_MBXPROT_* 位)。

class MailboxType(IntEnum):
ERROR_MAILBOX = 0x00 # 错误邮箱
ADS_OVER_ETHERCAT = 0x01 # AoE
ETHERNET_OVER_ETHERCAT = 0x02 # EoE
CANOPEN_OVER_ETHERCAT = 0x03 # CoE
FILE_OVER_ETHERCAT = 0x04 # FoE
SERVO_OVER_ETHERCAT = 0x05 # SoE
VENDOR_OVER_ETHERCAT = 0x0F # VoE
EcCoEDetails 常量
class EcCoEDetails(IntFlag):
NONE = 0x00
SDO = 0x01 # 支持 SDO
SDO_INFO = 0x02 # 支持 SDO Info
PDO_ASSIGN = 0x04 # 支持 PDO Assign
PDO_CONFIG = 0x08 # 支持 PDO Config
STARTUP = 0x10 # 支持 Startup
COMPLETE_ACCESS = 0x20 # 支持 Complete Access
EcEoEDetails 常量
class EcEoEDetails(IntFlag):
NONE = 0x00
SEND_FRAME = 0x01 # 支持发送帧
RECEIVE_FRAME = 0x02 # 支持接收帧
SET_IP_PARAM = 0x04 # 支持设置 IP 参数
GET_IP_PARAM = 0x08 # 支持获取 IP 参数
Python 特有语法糖

身份四元组 (vendor / product / revision / serial) 主 SDK 返回 dict, 不能 hash / 解包。 用 SlaveIdentityKey frozen dataclass 包装可作 dict key / set 元素 + 元组解包: vendor, product, rev, serial = SlaveIdentityKey.from_slave(slave). 详见 dataclass.

子对象​

类别属性类型读写说明
数据与事件pdoSlavePdo只读PDO 数据访问器,详见 PDO 输入输出
eventsSlaveEvents只读从站级事件,详见 从站事件
diagnosticsSlaveDiagnostics只读诊断信息访问器,详见 从站诊断
startupStartupParameterList只读启动参数列表
协议实例coeCoE只读CANopen over EtherCAT(延迟创建;是否支持用 coe.is_supported 判断)
soeSoE只读Servo over EtherCAT(延迟创建;soe.is_supported 判断支持)
foeFoE只读File over EtherCAT(延迟创建;foe.is_supported 判断支持)
eoeEoE只读Ethernet over EtherCAT(延迟创建;eoe.is_supported 判断支持)
aoeAoE只读ADS over EtherCAT(延迟创建;aoe.is_supported 判断支持)
voeVoE只读Vendor over EtherCAT(延迟创建;voe.is_supported 判断支持)
fsoeFSoE只读Functional Safety over EtherCAT(延迟创建;fsoe.is_supported 判断支持)
cia402CiA402只读CiA 402 驱动(延迟创建)
cia401CiA401只读CiA 401 I/O 模块(延迟创建)
mdpMdpDiscovery | None只读MDP 模块化设备,从站不支持 MDP 时为 None
esiEsiLoader只读ESI 文件加载器实例,提供 ESI 文件加载和解析功能
siiSiiAccessor只读SII EEPROM 完整访问(Category 遍历 / General / Strings);单字写内部自动取得主站持有并在写后还给 PDI,免手动 Acquire/Release;显式 sii.acquire() / sii.release() 用于批量写整段持有,避免 PDI 中途抢回
dc_configDcConfig只读DC 同步配置与诊断,详见 DC 同步

枚举描述​

枚举描述的取法按枚举而异:

  • EcState 用 .name 取符号名(如 "OP"),.format() 取可读串(如 "SafeOp+Error")。
  • EcALState / SDOError / EcPortType / EcTopologyType 等用 .describe() 取中文描述。
  • ModeCiA402 / StateCiA402 / RedundancyState 用 .description 取中文描述。
state = slave.state
desc = state.name # "OP"

# CiA402 通过 slave.cia402 访问 (延迟创建, 总是返回实例)
cia = master[1].cia402
state = cia.state # 驱动器状态

硬件信息查询​

当前未提供

以下批量查询方法不存在(原生层无批量导出),调用会 AttributeError:

  • get_fmmus() — 获取从站 FMMU 配置数组
  • get_sync_managers() — 获取从站 SyncManager 配置数组
  • get_sync_manager_types() — 获取从站 SyncManager 类型数组

单个 FMMU 功能用 fmmu0_function ~ fmmu3_function 属性;SM 明细经底层 master._dll.GetSlaveSMLength(mi, si, sm_idx)(原生导出)读取。

诊断​

提示

从站诊断信息(通信异常率、冗余状态、DC 同步)通过 slave.diagnostics 访问,详见 从站诊断。

实时诊断镜像 (薄读零帧)​

每从站的实时诊断属性挂在 slave.diagnostics 上(Python 惯例:用属性,不用 get_xxx() 方法),都是内核 per-slave 诊断缓存的薄读:每次访问直接读 native,不收发帧(零帧),不二次缓存,读到即此刻总线现实,无需手动刷新。通讯异常时诚实返回 UNKNOWN / raw=0,不臆造。

  • diagnostics.wc_contributed (WcContribution) — 该从站本周期对工作计数器(WKC)的贡献状态,详见 WKC 贡献语义
  • diagnostics.al_status_mirror (SlaveAlStatusMirror) — AL 状态镜像(含 al_state / has_error / al_status_code;al_status_code 为镜像高字节,完整 16 位码值走 SDO 0x0134)
  • diagnostics.mailbox_health (EcMailboxHealth) — 该从站邮箱健康度(UNKNOWN/HEALTHY/DEGRADED)
  • diagnostics.is_free_run_demoted (bool) — 该从站是否被迫从 DC 降级到 FreeRun
  • diagnostics.health_degraded_count (int | None) — 该从站邮箱半失效连续累计计数(≈秒数),读取失败返回 None
  • diagnostics.recover_mailbox_health() (bool) — 手动触发邮箱半失效修复(内核通常自动修复)
WcContribution 枚举值(WKC 贡献状态)
class WcContribution(IntEnum):
NOT_CONTRIBUTED = 0x00 # 该从站此刻没在贡献 WKC(掉站 / 无响应)
CONTRIBUTED = 0x01 # 该从站正常贡献 WKC(在总线上响应)
UNKNOWN = 0xFF # 内核未填充 / 未映射 — 诚实未知,不当作没贡献
EcMailboxHealth 枚举值(邮箱健康度)
class EcMailboxHealth(IntEnum):
UNKNOWN = 0 # 无邮箱 / 不在 OP — 邮箱健康度此时无意义
HEALTHY = 1 # 在 OP 且邮箱可用
DEGRADED = 2 # 在 OP 但邮箱半失效(CoE/SDO 可能阻塞)

WKC 贡献语义​

slave.diagnostics.wc_contributed 反映此刻该从站是否真的在响应:

  • CONTRIBUTED — 该从站正常贡献工作计数器(WKC),即聚合 WKC 完整(actual == expected,亦即缺口 WcDeficit == 0)时各从站逐字节比对都"满"。
  • NOT_CONTRIBUTED — 该从站此刻没在贡献 WKC(疑似掉站 / 热插拔恢复中)。这不是 master 故障,从站修复后内核镜像自然回到 CONTRIBUTED。
  • UNKNOWN — 内核未填充 / 未映射,诚实暴露,不臆造为"已贡献"。
R1 可靠性铁律

本属性仅"如实反映总线现实", 不参与任何 WKC 篡改 / 迁就逻辑。逐从站贡献位是聚合 WKC 缺口(expected - actual)的逐站分解:缺口大于 0 表示有从站此刻未贡献,应报警 + 诊断,不下调 expected、不停 OP。

示例:

from darra_ethercat.data.types import WcContribution, EcMailboxHealth

for slave in master:
if slave.diagnostics.wc_contributed == WcContribution.NOT_CONTRIBUTED:
al = slave.diagnostics.al_status_mirror
print(f"从站 {slave.slave_num} 此刻未贡献 WKC(疑似掉站),AL Code=0x{al.al_status_code:04X}")
if slave.diagnostics.mailbox_health == EcMailboxHealth.DEGRADED:
print(f"从站 {slave.slave_num} 在 OP 但邮箱半失效, 已持续 {slave.diagnostics.health_degraded_count}s")

EMCY 紧急消息​

从站级 EMCY 记录直接挂在 Slave 对象上(不是 slave.coe;CoEEmcyRecorder 是独立记录器,详见 CoE — EMCY 紧急消息):

  • emcy_count (int) — 紧急消息数量
  • emcy_history (List[dict]) — 紧急消息历史。每条记录是 dict(不是 EmergencyMessage 对象),用键访问:rec['error_code'] / rec['error_register'] / rec['data'] / rec['slave_index'] / rec['timestamp_ms']
  • clear_emcy() — 清除紧急消息历史
for rec in slave.emcy_history:
print(f"从站 {rec['slave_index']} EMCY 0x{rec['error_code']:04X} "
f"reg=0x{rec['error_register']:02X} t={rec['timestamp_ms']}ms")
slave.clear_emcy()

链路质量 / 端口错误计数器 / 同步窗口​

  • link_quality (int) — 链路质量(0-100%)
  • read_port_error_counters() (dict | None) — 端口错误计数器,返回 {'rx_error': [4], 'invalid_frame': [4], 'lost_link': [4]}(每端口一项),失败返回 None
  • reset_port_error_counters() (bool) — 清零端口错误计数器
  • sync_window_status (dict | None) — 同步窗口状态(键 diff_ns / max_diff_ns / min_diff_ns / in_sync / out_of_sync_count),失败返回 None
  • reset_sync_stats() — 重置同步窗口统计

ESI 方法​

set_esi_file(esi_file_name)​

def set_esi_file(esi_file_name: str) -> bool

设置从站使用的 ESI 文件。

返回值:

  • bool — 是否成功
config_by_esi / configure_from_esi 未实现

slave.config_by_esi() 与 slave.configure_from_esi() 总是抛 NotImplementedError(旧实现返回假成功,已改为显式抛出)。ESI 解析结果用 slave.esi(EsiLoader)读取;SM 物理参数自动配置走 master.auto_configure_sm(slave_index)。

过程数据看门狗​

set_watchdog(timeout_ms)​

def set_watchdog(timeout_ms: int) -> bool

设置从站过程数据看门狗超时。从站在超时时间内未收到过程数据帧时触发看门狗错误(ALStatusCode 0x001B,SYNC_MANAGER_WATCHDOG)。

参数:

  • timeout_ms (int) — 超时时间(毫秒),0 = 禁用,最大 6553ms

返回值:

  • bool — 是否成功
备注

应在 SafeOp 或 OP 状态下调用。

set_pdi_watchdog(timeout_ms)​

def set_pdi_watchdog(timeout_ms: int) -> bool

设置从站 PDI 看门狗超时。PDI 看门狗监控从站本地应用(微控制器固件)是否正常运行。

参数:

  • timeout_ms (int) — 超时时间(毫秒),0 = 禁用

返回值:

  • bool — 是否成功

示例:

slave.set_watchdog(100)  # 100ms 超时
slave.set_watchdog(0) # 禁用看门狗

watchdog_config​

@property
def watchdog_config(self) -> Optional[dict]

读取从站看门狗当前配置。返回 None 表示读取失败。

返回值:

  • Optional[dict] — 看门狗配置字典,包含以下键:
    • divider (int) — 看门狗分频器
    • pdi_timeout (int) — PDI 看门狗超时值(分频器单位)
    • pd_timeout (int) — 过程数据看门狗超时值(分频器单位)

示例:

config = slave.watchdog_config
if config is not None:
print(f"PD超时: {config['pd_timeout']} × {config['divider'] * 0.04}µs")

watchdog_status​

@property
def watchdog_status(self) -> Optional[dict]

读取从站看门狗运行状态。返回 None 表示读取失败。

返回值:

  • Optional[dict] — 看门狗状态字典,包含以下键:
    • status (int) — 看门狗状态值
    • counter (int) — 看门狗计数器(过期累计次数)
    • divider (int) — 当前分频器值
    • pd_timeout (int) — 当前过程数据超时值

示例:

status = slave.watchdog_status
if status is not None and status['status'] != 0:
print(f"看门狗触发! 计数: {status['counter']}")

状态切换​

set_state(state, timeout_ms=5000)​

def set_state(state: EcState, timeout_ms: int = 5000) -> bool

设置单个从站 EtherCAT 状态(带超时)。用于手动恢复或将单个从站切换到指定状态,整网级状态机请使用 master.set_state()。

参数:

  • state (EcState) — 目标状态
  • timeout_ms (int) — 超时时间(毫秒),默认 5000

返回值:

  • bool — 是否成功
备注

状态切换遵循 EtherCAT 标准状态机流程,协议层自动处理中间状态。例如从 INIT 切换到 OP 会自动经过 PreOp → SafeOp → OP。

示例:

# 手动恢复单个从站到 OP
if slave.state != EcState.OP:
slave.set_state(EcState.OP)

# 将从站切换到 Init(重置)
slave.set_state(EcState.INIT, timeout_ms=5000)

实时状态查询 (2.5.x 新增)​

slave.state / slave.error_code 返回的是周期缓存值。如需绕开缓存直读总线当前的 AL 状态 (例如故障注入测试、热插拔验证), 可通过 DLL 层的实时查询接口:

DLL 接口返回说明
GetSlaveStateLive(mi, si)u8实时 AL State (bit0-3=状态, bit4=Error 标志)
GetSlaveALStatusCodeLive(mi, si)u16实时 AL Status Code

这两个接口直接发 FPRD 读 ESC 0x0130 / 0x0134, 不经周期状态缓存。

示例:

from darra_ethercat import EcState

mi, si = master.master_index, slave.slave_num

raw = master._dll.GetSlaveStateLive(mi, si)
state = EcState(raw & 0x0F)
has_error = (raw & 0x10) != 0

code = master._dll.GetSlaveALStatusCodeLive(mi, si)
print(f"实时状态: {state.name}, Error={has_error}, AL Code=0x{code:04X}")
何时用 Live 查询
  • 常规监控 — 用 slave.state / slave.diagnostics, 走周期缓存, 开销最低
  • Live 查询 — 仅在需要确认"此刻总线上从站真实状态"的诊断 / 测试场景, 每次都发实报文, 不要在高频路径调用

ESC 寄存器访问 (高级)​

直接读写从站 ESC (EtherCAT Slave Controller) 寄存器,用于故障诊断、自定义 ESC 操作、底层调试。协议层走 FPRD/FPWR 数据报,自动 primary → secondary → APWR 三级回退。

高级 API

正常使用 SDK 时无需调用 — 状态切换 / PDO / 邮箱等流程 SDK 已自动配置寄存器。此 API 用于深度诊断和特殊场景(例如读取错误计数器、强制端口策略、调试 ESI 烧写不生效等)。

寄存器定义见 ETG.1000.4 §6 / ETG.1000.6 §5(公开标准),例如:

寄存器说明
0x0000Type / Revision / Build(设备类型)
0x0030AL Control(主站发起状态请求)
0x0130AL Status(从站当前状态)
0x0134AL Status Code(错误码)
0x0300-0x030F端口 0-3 错误计数器
0x0400-0x043F看门狗配置 / 计数

read_register(addr, length)​

def read_register(self, addr: int, length: int) -> Optional[bytes]

读取从站 ESC 寄存器(FPRD)。

参数:

  • addr (int) — 寄存器地址(例如 0x0130 = AL Status)
  • length (int) — 读取字节数(1 / 2 / 4 等)

返回值:

  • Optional[bytes] — 读取成功返回字节数据;失败(从站离线 / 超时)返回 None

write_register(addr, data)​

def write_register(self, addr: int, data: bytes) -> bool

写入从站 ESC 寄存器(FPWR)。

参数:

  • addr (int) — 寄存器地址
  • data (bytes) — 写入数据

返回值:

  • bool — 成功返回 True

示例:

slave = master[1]                 # 1-based 索引, 第 1 个从站

# 读取 AL Status (0x0130, 2 字节)
data = slave.read_register(0x0130, 2)
if data:
state = int.from_bytes(data, 'little')
print(f"AL Status = 0x{state:04X} (state={state & 0x0F}, err={(state & 0x10) != 0})")

# 读取 AL Status Code (0x0134, 错误码)
code_bytes = slave.read_register(0x0134, 2)
if code_bytes:
code = int.from_bytes(code_bytes, 'little')
print(f"AL Status Code = 0x{code:04X}")

# 写 AL Control = 0x04 (请求 SafeOp)
slave.write_register(0x0030, bytes([0x04, 0x00]))

EEPROM (SII) 访问​

读写从站 SII EEPROM (Slave Information Interface, ETG.1000.6 §6)。EEPROM 存储 vendor_id / product_code / revision_no / serial_no / SyncManager / FMMU / PDO 映射 / Strings 等设备身份与配置信息。SDK 在 config_init 阶段已自动读取,应用一般无需直接访问。

危险操作

EEPROM 写入慎用 — 写错可能导致从站身份信息错乱,严重时永久 brick 从站, 需厂家工具恢复。仅在以下场景使用:

  • 烧写 alias 地址(Hot-Connect 别名)
  • 修复出厂数据被误覆盖
  • 厂商授权的固件 / 参数烧录

EEPROM 写需要从站处于 Init / PreOp 状态,OP 状态下写入会被拒绝。

EEPROM 大小通常 1 KB - 16 KB(按 word 寻址,1 word = 2 byte)。起始 8 word 为厂商基本信息,之后是 Category 链表(Strings / General / FMMU / SyncM / TxPdo / RxPdo / DC / End=0xFFFF)。

read_eeprom(byte_offset, byte_length)​

def read_eeprom(self, byte_offset: int, byte_length: int) -> bytes

读取从站 SII EEPROM 字节区域 (按 word 循环, 内部走 SIIReadWord)。SDK 自动处理 BUSY 轮询、字对齐, 推荐应用层优先使用此高层 API。

参数:

  • byte_offset (int) — 起始字节偏移 (奇数偏移自动向下对齐到 word 后取对应字节)
  • byte_length (int) — 读取字节数 (建议偶数, 奇数末尾自动取低字节)

返回值:

  • bytes — byte_length 字节数据; 失败或参数非法返回空 b""

write_eeprom(byte_offset, data)​

def write_eeprom(self, byte_offset: int, data: bytes) -> bool

写入从站 SII EEPROM 字节区域 (按 word 循环, 内部走 SIIWriteWord)。byte_offset 与 len(data) 都必须是偶数。单字写内部自动取得主站持有并在写后还给 PDI,免手动 slave.sii.acquire() / slave.sii.release();显式 slave.sii.acquire() / slave.sii.release() 用于批量写整段持有,避免 PDI 中途抢回。

参数:

  • byte_offset (int) — 起始字节偏移 (必须偶数)
  • data (bytes) — 写入字节 (长度必须偶数)

返回值:

  • bool — 全部 word 成功写入返回 True

示例:

slave = master[1]                 # 1-based 索引, 第 1 个从站

# 读 vendor_id (EEPROM 字节偏移 0x10, 长度 4)
data = slave.read_eeprom(0x10, 4)
if len(data) == 4:
vendor_id = int.from_bytes(data, 'little')
print(f"VendorID = 0x{vendor_id:08X}")

# 读取头部 16 字节 (含 PDIControl / StationAlias 等)
header = slave.read_eeprom(0, 16)
print(f"EEPROM 头部: {header.hex()}")

# 写 alias 段 (EEPROM 字节偏移 0x08 起的连续 word)
# 必须从站处于 Init/PreOp 状态;此处 Acquire/Release 为批量写整段持有示例(单次写免手动)
if slave.state == EcState.INIT and slave.sii.acquire():
ok = all([
slave.write_eeprom(0x08, bytes([0x01, 0x00])), # alias = 1 (word 0x04)
slave.write_eeprom(0x0A, bytes([0x00, 0x00])), # 保留字段 (word 0x05)
])
slave.sii.release()
高层 API vs 低层 SII API
  • 首选: read_eeprom / write_eeprom (按字节, 自动字对齐)
  • 次选: slave.vendor_id / product_id / serial_number 等已封装属性
  • 高级用法 (按需): 通过底层 SII 接口读写原始 EEPROM 字节,用于枚举 Category / 读 PDO 映射原始字节 / 烧写 alias 等场景

DL Port 端口控制​

直接读写 ESC 的 DL Port Control 寄存器(0x0101),用于端口故障注入测试和冗余 / 环拓扑的手动诊断。

高级 API

正常运行时无需调用。大部分用户应该通过订阅 slave_port_link_changed 事件和读取 端口错误计数器 来诊断端口状态。仅在需要主动模拟端口故障(测试冗余切换)或排查特定端口问题时使用。

ESC 有 4 个物理端口 P0 / P1 / P2 / P3,DL Port Control 寄存器的位定义如下:

DLPORT 值行为
0x00Auto — 所有端口由 ESC 自动管理(默认)
0x03关闭 P0
0x0C关闭 P1
0x30关闭 P2
0xC0关闭 P3

SDK 的 write_register / read_register 自动采用 primary → secondary → APWR 三级回退写入路径,即使 P0 关闭后仍能通过副网口 / 广播恢复。

两种访问路径
  • slave.write_dl_port(value) / slave.read_dl_port() 专用方法(内部走 WriteSlaveDLPORT / ReadSlaveDLPORT 导出;正式版原生库未导出时返回 False / None)。
  • write_register(addr, data) / read_register(addr, length) 通用寄存器读写直接打 0x0101,不依赖专用导出。

写入 DL Port 控制寄存器(0x0101):

# value 见上表 (0-255)
ok = slave.write_dl_port(value) # 专用方法
ok = slave.write_register(0x0101, bytes([value])) # 通用寄存器写, 等效

读取 DL Port 控制寄存器当前值:

dlport = slave.read_dl_port()                # 专用方法, 失败/未导出返回 None
data = slave.read_register(0x0101, 1) # 通用寄存器读, 等效
dlport = data[0] if data else 0

示例:

# 模拟 P1 端口故障 (测试冗余切换)
ok = slave.write_register(0x0101, bytes([0x0C]))
print(f"关闭 P1: {'成功' if ok else '失败'}")

# 读回确认
data = slave.read_register(0x0101, 1)
dlport = data[0] if data else 0
print(f"当前 DLPORT = 0x{dlport:02X}")

# 故障恢复后还原
slave.write_register(0x0101, bytes([0x00])) # 恢复 Auto
配合冗余诊断

关闭一个端口后,观察 master.events.add_slave_port_link_changed() 和 master.diagnostics.break_point 验证冗余切换是否生效。

Startup 配置​

Startup 配置的“是否写入 PDO Assignment / PDO Configuration”和“是否支持 Complete Access”是 ENI/DENI 配置文件里的字段(解析层 utils/xml.py 的 SlaveXMLConfiguration),不作为从站对象属性暴露。从站对象上的 Startup 接口只有启动参数列表。

StartupAutoConfig.set_config_source_eni() / set_config_source_deni()​

def set_config_source_eni(self) -> None
def set_config_source_deni(self) -> None

标记从站配置来源为 ENI 或 DENI 文件。调用后 has_executed 返回 True,config_source 返回对应来源字符串。

这两个方法属于 StartupAutoConfig,不是 Slave 方法

set_config_source_eni / set_config_source_deni 与 has_executed / config_source 属性均定义在 darra_ethercat.slave.startup.StartupAutoConfig 类上, 不能直接 slave.set_config_source_eni() 调用。

StartupParameterList 方法(通过 slave.startup 访问):

  • add(index, sub_index, data, transition=StartupTransition.IP, priority=0, write_timing=None, complete_access=False, description="") — 用显式参数创建并添加启动参数, 返回创建的 StartupParameter
  • add_parameter(param) — 添加一个已创建的 StartupParameter 对象
  • remove(param) — 移除指定的 StartupParameter 对象, 返回 bool
  • clear() — 清除所有启动参数, 返回 native rc (int)
  • apply_all(slave) — 应用全部启动参数到从站, 返回成功应用的数量 (int)
注意 add 与 add_parameter 的区别

add() 接受显式字段并在内部构造 StartupParameter; add_parameter() 接受一个已构造好的 StartupParameter 对象。

示例:

from darra_ethercat import StartupParameter
from darra_ethercat.data.types import StartupTransition

# 方式一: add() 用显式参数
slave.startup.add(
index=0x1C12,
sub_index=0,
data=bytes([0]),
transition=StartupTransition.PS,
priority=10,
)

# 方式二: 先构造 StartupParameter 对象, 再 add_parameter()
param = StartupParameter(
index=0x1C13,
sub_index=0,
data=bytes([0]),
transition=StartupTransition.PS,
priority=10,
)
slave.startup.add_parameter(param)

相关结构:

  • IP — Init → PreOp,默认 AfterTransition
  • PS — PreOp → SafeOp,默认 BeforeTransition
  • SO — SafeOp → Op,默认 BeforeTransition
  • OS — Op → SafeOp,默认 AfterTransition
  • SP — SafeOp → PreOp,默认 AfterTransition
  • PI — PreOp → Init,默认 BeforeTransition

Slave 子结构 (Sub-Structures)​

Phase 2-D 把 EcSlave 的平面字段重新组织为多个领域子结构, 既可作为参数传递, 也可在 from_buffer_copy() 后做整体快照。所有子结构均通过 ctypes.Structure 实现, 自然对齐 (无 _pack_)。

Python 特有语法糖

对外暴露时除 ctypes.Structure 还可用 frozen dataclass (例: SlaveIdentityKey) 表达不可变身份, 自动获得 __eq__/__hash__/__repr__ + 元组解包。 详见 dataclass.

from darra_ethercat.data.structures import (
SlaveIdentity, SlaveMetadata, SlaveCapabilities, SlaveRuntime,
SlaveSmFmmu, SlaveProtoMbx, SlaveTopology, SlaveDc, SlaveMailbox,
)

SlaveIdentity​

class SlaveIdentity(Structure):
vendor_id: c_uint32 # 厂商 ID (eep_man)
product_id: c_uint32 # 产品 ID (eep_id)
revision: c_uint32 # 修订版本 (eep_rev)
serial: c_uint32 # 序列号 (eep_ser)
itype: c_uint16 # 接口类型 (Itype)
dtype: c_uint16 # 设备类型 (Dtype)

身份四元组 + 接口 / 设备类型 (20 字节)。slave.verify_identity(expected, check_revision=True, check_serial=False) 接受本结构进行身份核对。

SlaveMetadata​

class SlaveMetadata(Structure):
identity: SlaveIdentity # 身份子结构
group_name: c_ubyte * 41 # 组名称 (EC_MAXNAME + 1)
device_name: c_ubyte * 41 # 设备名称 (EC_MAXNAME + 1)
sm_count: c_uint16 # SyncManager 数量

聚合身份 + 组 / 设备名 + SM 数量 (≈104 字节)。

SlaveCapabilities​

class SlaveCapabilities(Structure):
is_optional: c_uint8 # 0=必须, 1=可选 (缺席不影响 WKC)
supports_frame_repeat: c_uint8 # 0=不支持, 1=支持 ETG.1500 帧重复
mailbox_side: c_uint8 # 0=auto/dual, 1=primary, 2=secondary
coe_details: c_uint8 # CoE 协议位 (CoEdetails)
foe_details: c_uint8 # FoE 协议位 (FoEdetails)
eoe_details: c_uint8 # EoE 协议位 (EoEdetails)
soe_details: c_uint8 # SoE 协议位 (SoEdetails)

Darra 扩展能力 + 各 mailbox 协议明细 (7 字节)。

SlaveRuntime​

class SlaveRuntime(Structure):
ebus_current: c_int16 # E-bus 电流 (Ebuscurrent)
block_lrw: c_uint8 # >0 时禁止使用 LRW (blockLRW)
group: c_uint8 # 分组编号
is_lost: c_uint8 # 从站是否丢失 (islost)

运行时易变状态 (≈6 字节)。slave.group / slave.is_lost 等属性即派生自此。

SlaveSmFmmu​

class SlaveSmFmmu(Structure):
sm_type: c_uint8 * 8 # SM 类型 (SMtype)
sm_app_length: c_uint16 * 8 # SM 应用层长度
fmmu_func: c_uint8 * 4 # FMMU 0-3 功能
fmmu_unused: c_uint8 # 第一个未使用的 FMMU 索引

SyncManager 类型 + FMMU 功能映射 (29 字节)。

SlaveProtoMbx​

class SlaveProtoMbx(Structure):
in_ptr: c_void_p # 邮箱输入缓冲区指针 (原生字段名 in)
in_full: ctypes.c_bool # 邮箱输入是否满
overrun: c_int32 # 邮箱溢出计数

每个邮箱协议 (CoE/SoE/FoE/EoE/VoE/AoE) 对应 1 个 SlaveProtoMbx (16 字节)。

ctypes 字段名 in_ptr

原生字段名为 in, Python 关键字冲突, 改名为 in_ptr。ABI 偏移与原生一致, 字段名不影响 layout。

SlaveTopology​

class SlaveTopology(Structure):
has_dc: c_uint8 # 是否支持 DC
phy_type: c_uint8 # 物理端口类型 (ptype)
link_count: c_uint8 # 拓扑类型 (topology)
active_ports: c_uint8 # 活动端口位图
consumed_ports: c_uint8 # 已使用端口位图
parent: c_uint16 # 父从站 (历史字段名 parent, 实际存从站 index, 见 parent_station)
parent_port: c_uint8 # 父从站端口号
entry_port: c_uint8 # 入口端口号

物理拓扑信息 (10 字节)。slave.parent_index / slave.parent_station / slave.active_ports 等属性派生自此。

SlaveDc​

class SlaveDc(Structure):
recvtime: c_int32 * 4 # 端口 A/B/C/D DC 接收时间 (ns)
propagation_delay: c_int32 # 传播延迟 (ns)
next: c_uint16 # DC 链下一个从站
prev: c_uint16 # DC 链上一个从站
cycle0: c_int32 # SYNC0 周期 (ns)
cycle1: c_int32 # SYNC1 周期 (ns)
shift: c_int32 # 相位偏移 (ns)
active: c_uint16 # AssignActivate 值, 0=禁用

DC 时钟相关字段 (≈40 字节)。slave.propagation_delay / slave.dc_active 等属性派生自此。

SlaveMailbox​

class SlaveMailbox(Structure):
length: c_uint16 # 写邮箱长度 (mbx_l)
write_offset: c_uint16 # 写邮箱偏移 (mbx_wo)
read_length: c_uint16 # 读邮箱长度 (mbx_rl)
read_offset: c_uint16 # 读邮箱偏移 (mbx_ro)
supported_proto: c_uint16 # 支持的邮箱协议位 ECT_MBXPROT_*
cnt: c_uint8 # 邮箱链路层协议计数器

主邮箱布局信息 (12 字节)。supported_proto 与 slave.mbx_proto 同源(ECT_MBXPROT_* 位),各协议 is_supported(如 coe.is_supported)即按这些位判断。

这些子结构不需要应用层主动构造

SDK 内部从 native 内存做 from_buffer_copy() 快照后通过 slave.identity 等属性返回字典视图, 应用层一般用字典即可; 子结构仅在需要 ABI 对齐传递 (verify_identity) 或自定义快照时显式使用。SlaveMetadata 这类聚合结构没有对应的单一 slave.metadata 访问器, 应用层经 slave.identity / slave.name 等独立属性读取等效字段。