跳到主要内容

主站诊断

通过 master.diagnostics_info 访问高级诊断功能(帧计数、抖动、丢包率等 MasterDiagnosticsInfo 对象)。​

通过 master.diagnostics 访问 ETG.1510 基础诊断数据(返回 dict,包含 cyclic_lost_frames、acyclic_lost_frames、cyclic_fps、acyclic_fps、state;失败返回 None)。​

配合事件使用

建议通过 事件 驱动异常处理,而非自行轮询。 直接读取诊断属性适用于 UI 显示等场景。

从站诊断

单个从站的状态诊断、链路质量请参考 从站诊断。

功能概览​

功能说明
通信与性能统计帧计数、丢包、抖动、PDO 丢帧、网口状态、拓扑
DC 同步同步窗口阈值、dc_sync_lost 事件
冗余状态冗余激活、故障点检测
诊断控制启停数据采集、重置统计

通信与性能统计​

类别属性类型读写需启用说明
性能统计rt_cntint只读是每秒帧数(Hz),5秒平均
error_cntint只读是每秒错误数,5秒平均
packet_loss_ratefloat只读是丢包率(0.0~1.0)— TX vs RX 5 秒滑窗, pipeline 在途不算丢
late_frame_ratefloat只读是过慢帧率(0.0~1.0)— idx 出 8 帧窗 stale, 不计入丢包
周期与抖动cycle_time_spanint只读是实际周期时间(微秒),实时值。对照 DENI/LoopCycle 设定,不是写死 1ms。诊断块 native 为 0 时回退 round(1e6/rt_cnt)。
avg_jitter_usfloat只读是最近5秒平均抖动(微秒)。隔离未生效时抖动变大;周期仍应钉在设定值(帧频 = 1e9/LoopCycleNs)。相对设定 ±20%,或 max jitter > 周期的 25%,连续偏离必须记日志并报警(Events.PDOFrameLoss / Events.DCSyncLost)。最短周期 125µs,不要按 1000Hz 写死。
mailbox_latency_usfloat只读—邮箱收发延迟 - 最大(微秒),见下方说明
mailbox_latency_avg_usfloat只读—邮箱收发延迟 - 平均(微秒),见下方说明
丢帧统计pdo.total_lostint只读—累计丢帧数(所有组合计)
pdo.consecutive_lostint只读—当前连续丢帧数
pdo.get_frame_loss_stats(group)PDOFrameLossStats只读—指定组的丢帧统计(group=0-7;省略或传 255 返回所有组汇总)
从站健康worst_slave_indexint只读—最差链路质量的从站索引
worst_link_qualityint只读—最差从站的通信健康度(%)
WKC 镜像wkc_actual_mirrorint只读—总线实测工作计数器镜像(actual WKC)— 反映此刻哪些从站真的在响应。R1:如实值永不篡改。薄读零帧,实时
wkc_expected_mirrorint只读—期望工作计数器镜像(expected WKC)— 配置期/进 OP 确定的固定真值。R1:拓扑固定它就固定,永不动态下调迁就劣化总线。薄读零帧,实时
wc_deficitint只读—WKC 缺额 = expected − actual(>0 表示有映射从站此刻没贡献 WKC,疑似掉站/热插拔)。从站恢复后自动归 0。薄读零帧,实时
mapped_slave_countint只读—已映射(参与 WKC 计算)的从站数。薄读零帧,实时
wc_state_seqint只读—内核 WcState 缓存序列号(每次刷新自增,64bit)。判断 per-slave 镜像是否在两次读取间更新过。薄读零帧,实时
冗余端口primary_port_okbool只读—主端口是否正常
secondary_port_okbool只读—副端口是否正常
primary_port_errorsint只读—主端口最近5秒错误数
secondary_port_errorsint只读—副端口最近5秒错误数
拓扑与定时topology_descriptionstr只读—拓扑模式描述
timing_modestr只读—定时模式:"WDK 实时驱动" / "Npcap PDO 线程" / "未启动" / "未知"

PDOFrameLossStats:

@dataclass
class PDOFrameLossStats:
total_lost: int # 累计丢帧数
consecutive_lost: int # 当前连续丢帧数
max_consecutive_lost: int # 历史最大连续丢帧数
邮箱收发延迟(mailbox_latency_us / mailbox_latency_avg_us,2026-05-21)

旧 max_jitter_us("应用抖动")测的是异步通知线程被 PDO 每周期唤醒的间隔抖动。PDO 过程数据已是纯内核 RT 收发 + 内核共享内存指针零拷贝(金路径 inputs_mapping / outputs_mapping 映射一次钉页;CopySlaveInputPdo 为整帧拷贝快照,WriteSlaveOutputPdo 为整帧替换写入),不存在"用户态每周期通知"环节,该指标失去意义,已移除。

同一底层字段(layout 不变)改为承载邮箱事务收发往返时延——邮箱(CoE/SoE/FoE/EoE/AoE/VoE 等非周期、请求-响应)从请求发出到响应返回的耗时(最近 1 秒结算)。mailbox_latency_us 为窗口最大值,mailbox_latency_avg_us 为平均值。

实时性抖动监控请改用 bus_max_jitter_us / bus_avg_jitter_us(RT 内核线程帧发送时序偏差),即旧 max_jitter_us 在 WDK 模式下的实际返回值。

WKC 镜像(掉站可观测)​

wkc_actual_mirror / wkc_expected_mirror / wc_deficit 是内核 per-slave wc_state 诊断缓存的主站级聚合镜像,均为 master.diagnostics_info 上的属性,全部薄读零帧(不缓存、不需刷新——内核每周期、WKC 异常时立即维护,DLL 保证读到即此刻真实总线现实)。配合每个从站的 slave.diagnostics.wc_contributed 可定位到具体掉了哪个从站。

R1 可观测性约定(不可违反):

  • wkc_expected_mirror = 配置期 / 进 OP 时确定的固定真值,拓扑固定它就固定,概念上不可变,永不动态下调迁就劣化总线。
  • wkc_actual_mirror = 总线实测值,如实反映此刻哪些从站真的在响应,永不篡改。
  • wc_deficit = wkc_expected_mirror − wkc_actual_mirror。wc_deficit > 0 不是 master 故障,而是有映射从站此刻没贡献 WKC(疑似掉站 / 热插拔恢复中);从站恢复后 wc_deficit 自动归 0,无需任何重置调用。

示例:

diag = master.diagnostics_info

# 主站级 WKC 镜像(薄读零帧,实时)
print(f"WKC: {diag.wkc_actual_mirror} / {diag.wkc_expected_mirror} "
f"(映射从站 {diag.mapped_slave_count} 个)")
if diag.wc_deficit > 0:
print(f"⚠️ WKC 缺额 {diag.wc_deficit} — 有从站没在响应,逐个从站定位:")
from darra_ethercat.data.types import WcContribution
for slave in master.slaves:
if slave.diagnostics.wc_contributed == WcContribution.NOT_CONTRIBUTED:
print(f" 从站 {slave.slave_num} 未贡献 WKC")

# 序列号判断镜像是否更新过(无需轮询帧)
seq = diag.wc_state_seq
per-slave wc_state

单个从站对 WKC 的贡献状态请用 slave.diagnostics.wc_contributed(WcContribution 枚举)。wc_deficit > 0 时遍历从站即可定位掉站点。

诊断快照​

get_snapshot()​

def get_snapshot(self) -> DiagnosticsSnapshot

获取诊断数据的一致快照。返回当前时刻所有诊断指标的冻结快照(不可变 dataclass),适用于日志记录、UI 刷新、跨线程传递等场景。

DiagnosticsSnapshot 数据类:

@dataclass(frozen=True)
class DiagnosticsSnapshot:
frequency: int # 每秒帧数 (Hz)
error_count: int # 每秒错误数
packet_loss_rate: float # 丢包率 (0.0-1.0) — TX vs RX 5s 滑窗
late_frame_rate: float # 过慢帧率 (0.0-1.0) — idx 出 8 帧窗 stale, 不计丢
avg_jitter_us: float # 平均抖动 (µs)
mailbox_latency_us: float # 邮箱收发延迟 - 最大 (µs)
mailbox_latency_avg_us: float # 邮箱收发延迟 - 平均 (µs)
cycle_time_us: int # 实际周期时间 (µs)
wkc_actual: int # 当前 WKC
wkc_expected: int # 期望 WKC
bus_cycle_hz: int # 总线频率 (Hz)
bus_max_jitter_us: float # 总线最大抖动 (µs)
bus_avg_jitter_us: float # 总线平均抖动 (µs)
bus_roundtrip_us: float # 总线往返延迟 (µs)
bus_load_percent: float # 通讯负载 (%) — RTT/周期×100
smi_count: int # SMI 次数
smi_peak_us: float # SMI 峰值 (µs)
primary_port_ok: bool # 主端口正常
secondary_port_ok: bool # 副端口正常
redundancy_active: bool # 冗余激活

示例:

diag = master.diagnostics_info
diag.enabled = True

snap = diag.get_snapshot()
print(f"频率: {snap.frequency} Hz, 丢包率: {snap.packet_loss_rate:.2%}")
print(f"WKC: {snap.wkc_actual}/{snap.wkc_expected}")
print(f"总线抖动: 平均 {snap.bus_avg_jitter_us:.2f} us, 最大 {snap.bus_max_jitter_us:.2f} us")
print(f"邮箱延迟: 平均 {snap.mailbox_latency_avg_us:.2f} us, 最大 {snap.mailbox_latency_us:.2f} us")

print(f"冗余: {'激活' if snap.redundancy_active else '未激活'}")

计算公式:

  • rt_cnt = 采样周期帧数 / 窗口秒数 — 滑动窗口平均帧频
  • error_cnt = 采样周期错误数 / 窗口秒数 — 滑动窗口平均错误率
  • packet_loss_rate = (TX - RX - pipeline) / TX — 5 秒滑窗, pipeline 在途不算丢
  • late_frame_rate = LateDrop / TX — idx 出 8 帧窗 stale, 不计入丢包

需 enabled = True。

示例:

diag = master.diagnostics_info
diag.enabled = True # 启用诊断数据采集

print(f"帧频: {diag.rt_cnt} Hz")
print(f"丢包率: {diag.packet_loss_rate:.2%}")
print(f"错误数: {diag.error_cnt}")
print(f"周期时间: {diag.cycle_time_span} us")
print(f"总线抖动: 平均 {diag.bus_avg_jitter_us:.2f} us, 最大 {diag.bus_max_jitter_us:.2f} us")
print(f"邮箱延迟: 平均 {diag.mailbox_latency_avg_us:.2f} us, 最大 {diag.mailbox_latency_us:.2f} us")

# PDO 丢帧
pdo = diag.pdo
print(f"PDO 丢帧: 累计={pdo.total_lost}, 连续={pdo.consecutive_lost}")

# 按组查询
stats0 = pdo.get_frame_loss_stats(0)
stats1 = pdo.get_frame_loss_stats(1)
print(f"组0丢帧: {stats0.total_lost}, 组1丢帧: {stats1.total_lost}")

# 从站异常
print(f"最差从站: #{diag.worst_slave_index} ({diag.worst_link_quality}%)")

# 网口状态
print(f"主端口: {'正常' if diag.primary_port_ok else '异常'}")
print(f"副端口: {'正常' if diag.secondary_port_ok else '未连接'}")

# 拓扑信息
print(f"拓扑: {diag.topology_description}")
print(f"定时: {diag.timing_mode}")
从站通信诊断

每个从站的 ESC 端口错误通过 slave.diagnostics.read_port_errors() 获取。详见 从站诊断 - 通信诊断。

热插拔重建​

hot_swap_rebuild()​

def hot_swap_rebuild(self) -> int

运行中一次性热插拔重建拓扑:任意状态下调用一次,在线重扫总线 + 重建拓扑图 + 重配 PDO + 恢复到运行态(OP)。适用于现场拔掉 / 换上一个模块后,不停总线地把网络重新带回运行态。它不是 build() 那种"释放全网从头扫"的重建——是面向热插拔的在线增量恢复。

返回值(错误码语义):

  • 0 (OK) — 成功:从站全部回到 OP
  • -20 (BUSY) — 另一操作正在进行——稍后重试
  • -21 (RESCAN_0) — 重扫到 0 个从站——已停在 PreOp 安全态,检查物理链路 / 供电
  • -22 (SDO_ABORT) — 重配被从站 SDO abort 拒绝——检查 PDO 映射 / Startup 参数
  • -23 (NO_OP) — 部分从站未达 OP——已回滚停安全态,查各从站 AL Status Code
  • -24 (TIMEOUT) — 重建超时
  • -25 (IDX_FULL) — 帧索引池饱和
  • -1(通用失败)— 调用层异常,不抛异常直接返通用失败码

行为 / 约束(R1 如实可观测):

  • 在 OP 状态调用会短暂中断 PDO(内部降 PreOp 重配再升回 OP),调用方需容忍这一周期的过程数据空窗。
  • 失败时返错误码不掩盖——绝不靠篡改内部计数让结果"看起来对"。
  • 即使最终 0 个从站在 OP,PDO 循环也照常运行(周期不掉、帧不停),expected_wkc 不动态下调迁就劣化总线。

示例:

rc = master.diagnostics_info.hot_swap_rebuild()
if rc == 0:
print("热插拔重建成功,从站已回到 OP")
elif rc == -20:
print("有操作进行中,稍后重试")
elif rc == -21:
print("重扫到 0 站,已停 PreOp 安全态——检查链路/供电")
elif rc == -23:
print("部分从站未达 OP,已回滚安全态——逐个查 AL Status Code:")
for slave in master.slaves:
if slave.state != EcState.OP:
print(f" 从站 {slave.slave_num}: AL=0x{slave.error_code.value:04X}")
else:
print(f"热插拔重建失败: {rc}")

DC 同步​

自动监控(ETG.1500 5.13.3),每秒检查各从站时间偏差。超出 sync_window_threshold 阈值时触发 dc_sync_lost 事件。

单个从站

单个从站的同步状态请使用 slave.diagnostics.dc.is_in_sync 和 slave.diagnostics.dc.sync_time_difference。

sync_window_threshold​

@property
def sync_window_threshold(self) -> int

@sync_window_threshold.setter
def sync_window_threshold(self, value: int) -> None

同步窗口阈值(纳秒),默认 1000ns。超出阈值触发 dc_sync_lost 事件。

冗余状态​

类别属性类型读写说明
冗余状态redundancy_activebool只读冗余是否激活
break_pointOptional[dict]只读当前故障点(count, slave_index, port, type)

RingMode 枚举:

class RingMode(IntEnum):
INACTIVE = 0 # 未激活
DUAL = 1 # 双向冗余
DEGRADED = 2 # 降级模式

break_point 返回字典,包含以下字段:

{
'count': int, # 故障点数量
'slave_index': int, # 故障从站索引 (1-based)
'port': int, # 故障端口号 (0-3)
'type': int, # 故障类型:0=断线,1=CRC 故障
}

break_point 统一检测两类物理故障:

类型fault_type检测方式典型场景
断线0DL Status 端口物理链路丢失拔线、线缆断裂
CRC 故障1端口级 RxError + InvalidFrame 持续增长接触不良、线缆老化、连接器氧化

故障线缆段定位: 当相邻从站的对向端口(如从站 N 的 P1 和从站 N+1 的 P0)同时报故障,说明连接线缆有问题。仅单侧报故障则定位到该端口连接器。

从站冗余诊断

单个从站的冗余状态请参考 从站诊断 - 冗余诊断。

示例:

diag = master.diagnostics_info

print(f"冗余模式: {master.ring_mode.name}")
if master.ring_mode == RingMode.DEGRADED:
print("警告: secondary链路不可用")

if diag.redundancy_active:
print("冗余已激活")

bp = diag.break_point
if bp is not None:
print(f"故障: 从站{bp['slave_index']} P{bp['port']} "
f"{'断线' if bp['type'] == 0 else 'CRC故障'}")
if bp['type'] == 1:
print("建议检查线缆/连接器")

诊断控制​

Reset (诊断计数器)​

@property
def enabled(self) -> bool # 诊断数据采集开关 (默认关闭)
def reset(self) -> None # 一次性重置所有诊断统计

enabled 控制诊断数据采集开关(默认关闭)。启用后周期性采样, 记录标记为"需启用"的统计数据; 其他功能(PDO 丢帧、从站异常、网口状态、拓扑等)始终活跃。reset() 一次性清零所有诊断计数器。

AL 错误分类​

对 AL Status Code 进行分类,帮助快速判断错误性质和处理策略。

classify_al_error()​

def classify_al_error(al_status_code: int) -> ALErrorCategory

对 AL Status Code(从站返回的错误码)进行分类,帮助快速判断错误性质和处理策略。

参数:

  • al_status_code (int) — AL Status Code,从 slave.error_code(EcALState 枚举,IntEnum 可直接传入)或状态转换失败时获取

返回值:

  • ALErrorCategory — 错误分类枚举

ALErrorCategory 枚举:

class ALErrorCategory(IntEnum):
NONE = 0 # 无错误
TRANSIENT = 1 # 瞬态错误,可重试状态转换,通常自动恢复
CONFIGURATION = 2 # 配置错误,检查 PDO 映射、SM 配置、Startup 参数等
HARDWARE = 3 # 硬件错误,检查从站硬件、线缆、电源
UNKNOWN = 4 # 未知错误,查阅 ETG.1000 或从站手册

示例:

from darra_ethercat import classify_al_error, ALErrorCategory, EcALState

slave = master[1]
if slave.error_code != EcALState.NO_ERROR:
category = classify_al_error(slave.error_code)
print(f"从站 {slave.slave_num} 错误 0x{slave.error_code.value:04X}: {category.name}")

if category == ALErrorCategory.TRANSIENT:
print("瞬态错误,尝试重新切换状态...")
elif category == ALErrorCategory.CONFIGURATION:
print("配置错误,请检查 PDO/SM 配置")
elif category == ALErrorCategory.HARDWARE:
print("硬件错误,请检查从站设备")
常见 AL Status Code(括号内为 classify_al_error 实际返回)
  • 0x0011 无效的状态变更(EcALState.INVALID_STATE_CHANGE)— TRANSIENT
  • 0x0015 无效的邮箱配置(EcALState.INVALID_MAILBOX_CONFIG)— CONFIGURATION
  • 0x001E 无效的输入配置(EcALState.INVALID_INPUT_CONFIG)— CONFIGURATION
  • 0x0034 DC 同步超时(EcALState.DC_SYNC_TIMEOUT)— TRANSIENT
  • 0x0050 EEPROM 无访问权限(EcALState.EEPROM_NO_ACCESS)— HARDWARE
  • 0x0051 EEPROM 错误(EcALState.EEPROM_ERROR)— HARDWARE

未列入内置码表的值返回 UNKNOWN(如 0x0024 无效的输入映射);0x0040-0x005F 范围一律归 HARDWARE。

从站错误计数器​

read_slave_error_counters()​

def read_slave_error_counters(self, slave_index: int) -> Optional[dict]

读取指定从站的错误计数器(ESC 寄存器 0x0300-0x0307)。

参数:

  • slave_index (int) — 从站索引(1-based)

返回值:

  • Optional[dict] — 错误计数器字典;读取失败返回 None(不返回全 0 假冒"无错误")。字典包含以下字段:
{
'slave_index': int, # 从站编号
'rx_error': List[int], # 各端口 RX 错误计数 [Port0-3]
'invalid_frame': List[int], # 各端口无效帧计数 [Port0-3]
'lost_link': List[int], # 各端口链路丢失计数 [Port0-3]
}

示例:

counters = master.diagnostics_info.read_slave_error_counters(1)
if counters and any(v > 0 for v in counters['rx_error']):
print(f"从站 1 错误计数:")
print(f" RX 错误: {counters['rx_error']}")
print(f" 无效帧: {counters['invalid_frame']}")
print(f" 链路丢失: {counters['lost_link']}")

诊断消息​

read_diagnostic_messages()​

from darra_ethercat.slave.coe import read_diagnostic_messages

def read_diagnostic_messages(coe: CoE) -> List[DiagnosticMessage]

通过 CoE 读取从站对象 0x10F3(诊断历史对象,ETG.1020)中的诊断消息。返回从站记录的诊断事件列表,每条含 sub_index / diag_code(诊断码)/ flags / text_index / raw_data。

参数:

  • coe (CoE) — 从站的 CoE 接口(slave.coe)

返回值:

  • List[DiagnosticMessage] — 诊断消息列表,无消息时返回空列表(最多 20 条)

示例:

from darra_ethercat.slave.coe import read_diagnostic_messages

for slave in master.slaves:
if slave.coe is None:
continue
messages = read_diagnostic_messages(slave.coe)
for msg in messages:
print(f"[从站 {slave.slave_num}] 代码=0x{msg.diag_code:08X}, {msg}")
备注

并非所有从站都支持 0x10F3 诊断历史对象。不支持的从站调用时返回空列表。此方法通过 SDO 读取,不建议在实时路径中高频调用。

按问题分块的诊断字段指南​

本章按现场症状分块, 帮你定位问题在哪。注意数据来源差异:get_slave_diagnostics() 在 2.11.0 只填充 link_quality_percent,SlaveDiagnosticsData 其余字段保持默认 0(详见下方各小节标注);实时统计请走 MasterDiagnosticsInfo、事件或端口错误计数 API。

现场问题 → 字段映射​

字段所属对象: D = SlaveDiagnosticsData (经 get_slave_diagnostics()); M = master.diagnostics_info (MasterDiagnosticsInfo)。

  • 是不是丢包? — packet_loss_rate (M);丢帧计数看 pdo.total_lost (M) 与 Events.PDOFrameLoss
  • 是偶发还是持续? — pdo.consecutive_lost / pdo.get_frame_loss_stats().max_consecutive_lost (M)
  • 哪个从站链路质量差? — link_quality_percent[i] (D) / worst_slave_index (M)
  • 网线接触不稳? — read_slave_error_counters(i)['lost_link'] 或 slave.diagnostics.read_port_errors().lost_link
  • 哪根网线断了? — break_point (M)
  • 主副口错误? — primary_port_errors / secondary_port_errors (M)
  • 冗余真在工作吗? — redundancy_active (M)
  • 总线负载高吗? — bus_load_percent (M)

帧错误字段(SlaveDiagnosticsData 上存在,2.11.0 未填充)​

SlaveDiagnosticsData 声明了下列字段,但 get_slave_diagnostics() 不填充(恒为默认 0)。实时帧错误统计请用 MasterDiagnosticsInfo 的 error_cnt / pdo.* 与事件(Events.PDOFrameLoss 等):

  • lost_frames — 帧根本没回来
  • timeout_frames — PDO 周期未收到响应
  • working_counter_errors — WKC 不匹配
  • consecutive_wkc_errors — 连续 WKC 错误
  • frame_errors — 派生总和
  • checksum_errors — 物理层 CRC

端口错误计数​

  • primary_port_errors / secondary_port_errors — 主/副网卡 5 秒滑动窗口错误。MasterDiagnosticsInfo 上是实时值;SlaveDiagnosticsData 上同名字段 2.11.0 不填充。
  • 逐从站 ESC 端口错误走 master.diagnostics_info.read_slave_error_counters(i) 或 slave.diagnostics.read_port_errors()。

per-slave 物理层 (ESC 寄存器)​

SlaveDiagnosticsData 由模块级函数 get_slave_diagnostics(dll, master_index, slave_count) 获取 (位于 darra_ethercat.master.other), EtherCATMaster 上没有 get_diagnostics() 方法。该数据类提供下列逐从站列表字段 (索引 = 从站编号 1-based):

  • link_quality_percent[i] — 从站 i 链路质量百分比 (0-100),由 get_slave_diagnostics() 实际填充
  • port_error_count[i] — 从站 i 端口错误合计(2.11.0 不填充,恒 0;用 read_slave_error_counters(i))
  • lost_link_count[i] — 从站 i 链路丢失计数(2.11.0 不填充,恒 0;用 read_slave_error_counters(i)['lost_link'])

SlaveDiagnosticsData 全局统计字段​

SlaveDiagnosticsData 的标量字段(5 秒滑动窗口口径设计;2.11.0 经 get_slave_diagnostics() 取到的均为默认 0):

  • frame_errors — 全局帧错误合计
  • lost_frames — 全局丢帧合计
  • checksum_errors — 物理层 CRC 错误
  • timeout_frames — 超时帧数
  • working_counter_errors — WKC 错误合计
  • consecutive_wkc_errors — 连续 WKC 错误
  • primary_port_errors / secondary_port_errors — 主/副网口 5 秒窗口错误

RT 性能 + 内核稳定性​

下列字段是 master.diagnostics_info (MasterDiagnosticsInfo) 的属性, 不在 SlaveDiagnosticsData 上:

  • bus_cycle_hz — RT 内核帧频率
  • bus_max_jitter_us / bus_avg_jitter_us — 总线抖动
  • bus_roundtrip_us — RTT
  • bus_load_percent — 通讯负载(>70% 降频)

完整诊断流程示例​

from darra_ethercat.master.other import get_slave_diagnostics

# MasterDiagnosticsInfo 对象 (帧率/抖动/丢包/故障点/总线负载)
diag = master.diagnostics_info
diag.enabled = True

# SlaveDiagnosticsData 快照 — 2.11.0 实际提供逐从站链路质量
slave_diag = get_slave_diagnostics(master._dll, master.master_index,
master.slave_count)

# 1. 持续 vs 偶发 (PDO 连续丢帧)
if diag.pdo.consecutive_lost >= 5:
print(f"⚠️ 持续 PDO 丢帧 (连续 {diag.pdo.consecutive_lost})")
elif diag.pdo.consecutive_lost > 0:
print("偶发 PDO 丢帧")

# 2. 丢包率 (MasterDiagnosticsInfo)
if diag.packet_loss_rate > 0.01:
print(f"⚠️ 丢包率 {diag.packet_loss_rate:.2%} 严重")

# 3. 故障定位 (break_point 返回 dict)
bp = diag.break_point
if bp is not None:
print(f"故障点: 从站 {bp['slave_index']} P{bp['port']}")

# 4. 链路质量最差从站 (link_quality_percent 列表, 索引 1-based)
if slave_diag and slave_diag.link_quality_percent:
lq = slave_diag.link_quality_percent
worst = min(range(1, len(lq)), key=lambda i: lq[i], default=None)
if worst is not None and lq[worst] < 100:
print(f"链路质量最差从站: #{worst} ({lq[worst]}%)")

# 5. 网线接触诊断 (逐从站 ESC 链路丢失计数)
for slave in master.slaves:
counters = diag.read_slave_error_counters(slave.slave_num)
if counters and any(n > 0 for n in counters['lost_link']):
print(f"从站 {slave.slave_num} 链路丢失: {counters['lost_link']}")

# 6. 总线负载 (MasterDiagnosticsInfo)
if diag.bus_load_percent > 70:
print(f"⚠️ 总线负载 {diag.bus_load_percent:.1f}%, 必须降频")