从站诊断
每个从站提供独立的诊断属性和子对象,可查询端口错误、冗余、DC 同步等信息。
从站状态(State()、ErrorCode()、IsLost())等基础属性请参考 属性与方法。
建议通过 事件 驱动异常处理(如 addSlaveStateChangedListener、addSlaveOfflineListener、addDCSyncLostListener),而非轮询。
直接读取从站诊断属性适用于 UI 显示等场景。
全局通信统计、丢包率、PDO 丢帧汇总请参考 主站诊断。
功能概览
| 功能 | 访问路径 | 说明 |
|---|---|---|
| 实时状态镜像 | slave.WcContributed() / MailboxHealth() 等 | per-slave WcState/AL/邮箱健康内核镜像(薄读零帧,实时) |
| 通信诊断 | slave.Diagnostics().ReadPortErrors() | ESC 端口错误计数器 |
| 冗余诊断 | slave.RedundancyActivated() 等 | 冗余激活、主/冗余线路断路检测 |
| DC 同步 | slave.DC().GetSyncWindowStatus() | 同步状态、时间差 |
属性
| 类别 | 方法 | 类型 | 说明 |
|---|---|---|---|
| 通信诊断 | ReadPortErrors() | EscPortErrors | 读取 ESC 端口错误计数器 |
| ResetPortErrors() | boolean | 重置端口错误计数器 | |
| LinkQuality() | short | 获取链路质量(0-100%) | |
| 冗余诊断 | RedundancyActivated() | boolean | 冗余被激活。从站未丢失但网络中存在断线,冗余机制正在工作 |
| PrimaryLinkBroken() | boolean | 主线路断路。从主端口 (Port0) 到该从站的路径上存在断线 | |
| SecondaryLinkBroken() | boolean | 冗余线路断路。从副端口 (Port1) 反向到该从站的路径上存在断线 | |
| DC 同步 | DC().GetSyncWindowStatus() | SyncWindowStatus | 同步窗口状态(时间差、是否同步等) |
EscPortErrors 结构:
| 字段 | 类型 | 说明 |
|---|---|---|
| RxError | byte[4] | 各端口 RX 错误计数 [Port0-3] |
| InvalidFrame | byte[4] | 各端口无效帧计数 [Port0-3] |
| LostLink | byte[4] | 各端口链路丢失计数 [Port0-3] |
| FwdRxError | byte[4] | 各端口转发 RX 错误计数 [Port0-3] |
| HasErrors() | boolean | 是否存在任何错误 |
冗余和 DC 属性始终活跃,无需启用 master.Diagnostics().setEnabled(true)。
ReadPortErrors() 需实时读取从站,不建议高频调用。
实时状态镜像
2.5.x 起,从站提供零帧实时状态镜像访问 — 内核每 PDO 周期(WKC 异常立即)维护 per-slave 诊断缓存,SDK 经 native getter 薄读,不发额外帧、属性本身实时。适合 UI 高频刷新和掉站检测。
| 类别 | 方法 | 类型 | 读写 | 说明 |
|---|---|---|---|---|
| WKC 贡献 | WcContributed() | EcWcContributed | 只读 | 本周期该从站是否贡献了应有 WKC(内核 per-slave 位) |
| AL 状态镜像 | AlStatusMirror() | EcAlStatusMirror | 只读 | 从站 AL 状态镜像(内核 round-robin 填的 16-bit raw,含 AL State + Error + AL Status Code) |
| 邮箱健康 | MailboxHealth() | EcMailboxHealth | 只读 | 该从站邮箱健康度(内核每秒更新的镜像薄读)。DEGRADED 表示在 OP 但邮箱半失效(CoE/SDO 可能阻塞),如实反映不参与 WKC 篡改 |
| 邮箱健康 | IsFreeRunDemoted() | boolean | 只读 | 该从站是否被迫降级到 FreeRun 同步模式(薄读零帧)。true 仅当"配置期要 DC 但运行中被迫退到 FreeRun"——半失效根源观测点 |
| 邮箱健康 | HealthDegradedCount() | long | 只读 | 邮箱半失效连续累计计数(≈秒数,unsigned uint32 已 & 0xFFFFFFFFL 还原)。0 = 健康或未评估;持续累加表示半失效未恢复,内核在 >=3 时触发自动修复 |
| 邮箱健康 | RecoverMailboxHealth() | boolean | 只读 | 手动触发邮箱半失效修复。通常无需手动调用——内核检测到半失效持续若干秒后会自动修复。通讯异常时返回 false 不抛异常 |
| 掉站标志 | IsLost() | boolean | 只读 | 从站是否丢失 |
EcWcContributed 枚举
public enum EcWcContributed {
CONTRIBUTED(1), // 本周期该从站贡献了应有 WKC(在线响应正常)
NOT_CONTRIBUTED(0), // 本周期没贡献 WKC(疑似掉站 / 异常)
UNKNOWN(0xFF); // 未知 — 契约未就绪 / 内核共享内存未就绪(诚实哨兵,不臆造在线或掉站)
}
isKnown()— 数据是否可信(非UNKNOWN)。UI 据此决定显示真值还是"未知"。
EcAlStatusMirror 结构
AlStatusMirror() 返回的解析对象,把内核 16-bit raw 镜像拆为枚举:
| 方法 | 返回类型 | 说明 |
|---|---|---|
| getRaw() | int | 原始 16-bit 镜像值(unsigned,0..65535) |
| getAlState() | EcState | AL State 基础枚举(不含 Error 标志) |
| hasError() | boolean | 是否处于错误状态(bit4 置位) |
| getAlStatusCode() | EcALState | AL Status Code 枚举(仅 Error 置位时有效,否则 NO_ERROR) |
| isKnown() | boolean | 数据是否可信(raw == 0 表示内核未提供,UI 应显"未知") |
MailboxHealth() / IsFreeRunDemoted() / HealthDegradedCount() / RecoverMailboxHealth()
让"在 OP 但邮箱半失效"(CoE/SDO 阻塞)这种隐性故障对上层可见。MailboxHealth() == DEGRADED 表示从站仍在 OP 但邮箱通道半失效。HealthDegradedCount() 持续累加表示半失效未恢复(内核 >=3 秒自动修复),也可 RecoverMailboxHealth() 手动介入。R1:全部如实反映,不参与任何 WKC 篡改。
EcMailboxHealth 枚举
public enum EcMailboxHealth {
UNKNOWN(0), // 未知 — 无邮箱 / 不在 OP / 契约未就绪(诚实哨兵)
HEALTHY(1), // 健康 — 在 OP 且邮箱可用
DEGRADED(2); // 降级 — 在 OP 但邮箱半失效(handler 丢失 / DC 降级伴 0x001F)
}
isKnown()— 数据是否可信(非UNKNOWN)。UI 据此决定显示真值还是"未知"。
示例:
for (Slave slave : master.Slaves()) {
// 邮箱半失效(在 OP 但 CoE 可能阻塞)
if (slave.MailboxHealth() == EcatSlaveDiag.EcMailboxHealth.DEGRADED) {
System.out.printf("从站 %d 邮箱半失效 %ds%s%n",
slave.SlaveNum, slave.HealthDegradedCount(),
slave.IsFreeRunDemoted() ? "(被迫 FreeRun)" : "");
if (slave.HealthDegradedCount() >= 5) {
slave.RecoverMailboxHealth(); // 通常内核已自动修复,此为手动介入
}
}
}
WcContributed()、AlStatusMirror() 与 MailboxHealth() 在内核契约未就绪 / 无共享内存时诚实返回 UNKNOWN / 未知,绝不臆造在线或掉站状态。
示例:
for (Slave slave : master.Slaves()) {
EcatSlaveDiag.EcWcContributed wc = slave.WcContributed();
if (wc == EcatSlaveDiag.EcWcContributed.NOT_CONTRIBUTED) {
System.out.printf("从站 %d 本周期未贡献 WKC,疑似掉站%n", slave.SlaveNum);
}
EcatSlaveDiag.EcAlStatusMirror al = slave.AlStatusMirror();
if (al.isKnown() && al.hasError()) {
System.out.printf("从站 %d AL 错误: %s, Code=%s%n",
slave.SlaveNum, al.getAlState(), al.getAlStatusCode());
}
}
通信诊断
ReadPortErrors()
public EscPortErrors ReadPortErrors()
读取从站 ESC 端口错误计数器,返回各端口的物理层错误统计。
返回值:
EscPortErrors-- 端口错误计数器,读取失败返回null
示例:
for (Slave slave : master.Slaves()) {
EscPortErrors errors = slave.Diagnostics().ReadPortErrors();
if (errors != null && errors.HasErrors()) {
System.out.printf("从站 %d: RX错误=%s, 无效帧=%s, 链路丢失=%s%n",
slave.SlaveNum,
Arrays.toString(errors.RxError),
Arrays.toString(errors.InvalidFrame),
Arrays.toString(errors.LostLink));
}
}
拓扑信息
通过 slave.Topology() 访问 SlaveTopology 拓扑信息。
| 方法 | 返回类型 | 说明 |
|---|---|---|
| EntryPort() | byte | 入口端口 |
| ParentPort() | byte | 父端口号 |
| ParentStation() | short | 父站地址 (注: 实际是从站索引 1-based, 不是 station address; 0 表示父节点是主站) |
| PhysicalType() | byte | 物理类型 |
| TopologyByte() | byte | 拓扑字节 |
| ActivePorts() | byte | 活动端口 |
| ConsumedPorts() | byte | 已使用端口数 |
冗余诊断
直接通过 Slave 属性访问从站的冗余状态。仅在主站启用冗余时有意义。
全局冗余状态和断线点请参考 主站诊断 - 冗余状态。
示例:
for (Slave slave : master.Slaves()) {
if (!slave.RedundancyActivated()) continue;
System.out.print("从站 " + slave.SlaveNum + ": 冗余激活");
if (slave.PrimaryLinkBroken()) System.out.print(", 主线路断路");
if (slave.SecondaryLinkBroken()) System.out.print(", 冗余线路断路");
System.out.println();
}
DC 同步
通过 slave.DC().GetSyncWindowStatus() 访问从站的 DC 同步状态。
全局阈值和事件请参考 主站诊断 - DC 同步。
SyncWindowStatus 结构:
| 字段 | 类型 | 说明 |
|---|---|---|
| DiffNs | int | 当前时间差(纳秒) |
| MaxDiffNs | int | 最大时间差(纳秒) |
| MinDiffNs | int | 最小时间差(纳秒) |
| InSync | boolean | 是否在同步窗口内 |
| OutOfSyncCount | int | 超出同步窗口次数 |
示例:
for (Slave slave : master.Slaves()) {
if (!slave.HasDC()) continue;
DC.SyncWindowStatus status = slave.DC().GetSyncWindowStatus();
if (status != null) {
System.out.printf("从站 %d: 时间差=%dns, 同步=%b%n",
slave.SlaveNum, status.DiffNs, status.InSync);
}
}
完整示例
for (Slave slave : master.Slaves()) {
System.out.println("--- 从站 " + slave.SlaveNum + ": " + slave.Name() + " ---");
// 端口错误
EscPortErrors errors = slave.Diagnostics().ReadPortErrors();
if (errors != null && errors.HasErrors()) {
System.out.printf(" 端口错误: RX=%s, 无效帧=%s, 链路丢失=%s%n",
Arrays.toString(errors.RxError),
Arrays.toString(errors.InvalidFrame),
Arrays.toString(errors.LostLink));
}
// 冗余(仅冗余模式下有意义)
if (slave.RedundancyActivated()) {
System.out.printf(" 冗余激活: 主线路断=%b, 冗余线路断=%b%n",
slave.PrimaryLinkBroken(), slave.SecondaryLinkBroken());
}
// DC 同步
if (slave.HasDC()) {
DC.SyncWindowStatus status = slave.DC().GetSyncWindowStatus();
if (status != null) {
System.out.printf(" DC: 时间差=%dns, 同步=%b%n",
status.DiffNs, status.InSync);
}
}
}