跳到主要内容

冗余 (Redundancy)

EtherCAT 冗余通过双网卡 + 环形拓扑实现链路级容错: 主网口走 primary 帧, 副网口走 secondary 帧, 任一段断线时 SDK 自动从两侧 WKC 合并完成的从站状态, 整个网络仍正常运行。

通过 master.Diagnostics 访问冗余状态; 启用冗余通过初始化时指定双网口完成。​

启用方式

冗余在 初始化阶段 通过 SetNetwork(primary, redundant) 指定双网口启用, 不需要单独的开关 API。运行时无需手动控制。

相关页面

冗余状态查询 API​

冗余运行时状态通过 master.Diagnostics(类型 MasterDiagnosticsInfo)的公开属性查询,不需要自行创建冗余管理器;详细线路状态由 Diagnostics.RedundancyStatus 直接给出。常用属性:

类别属性类型读写说明
冗余RedundancyActivebool只读冗余是否激活(存在断线但网络仍正常运行)
RingModeRingMode只读环拓扑冗余运行模式(Inactive / Dual / Degraded)
RedundancyStatusRedundancyStatus?只读冗余线路详细状态(每线路 Tx/Rx 帧数、主/副链路 up、故障转移次数与时间);内核未提供或读取失败为 null
网口PrimaryPortOkbool只读主端口是否正常
SecondaryPortOkbool只读副端口是否正常(无冗余时始终 false)
PrimaryPortErrorsuint只读主端口最近 5 秒错误数
SecondaryPortErrorsuint只读副端口最近 5 秒错误数
故障BreakPointBreakPointInfo?只读断线 / CRC 故障点定位

主站实例上另有 master.IsSecondaryAlive(bool,只读)— 副口是否存活:未启用冗余 / 单网卡时恒为 true;读取失败保守返回 true(只留日志,不与降级 / 切换联动)。

RedundancyStatus 结构 / RedundancyState 枚举

Diagnostics.RedundancyStatus 返回的冗余状态对象(RedundancyStatus 类):

类别属性类型读写说明
线路状态StateRedundancyState只读冗余状态枚举
PrimaryLinkUpbool只读主链路是否 up
SecondaryLinkUpbool只读副链路是否 up
帧计数PrimaryTxFramesuint只读主线路累计发送帧数
PrimaryRxFramesuint只读主线路累计接收帧数
SecondaryTxFramesuint只读副线路累计发送帧数
SecondaryRxFramesuint只读副线路累计接收帧数
故障转移FailoverCountuint只读故障转移累计次数
LastFailoverTimeDateTime只读最近一次故障转移的本地时间;从未发生故障转移为 DateTime.MinValue

RedundancyState 枚举:

值名称说明
0None无冗余
1Primary仅使用主网络
2Secondary仅使用备用网络
3Both使用双网络(活动冗余)

完整的诊断属性表与 RingMode / BreakPointInfo 枚举/结构定义见 主站诊断 - 冗余状态。

强制故障转移 ForceFailover​

C# 公开 API 名为 ForceFailover,接到 native ForceRedundancyFailover(D_1536)。自动电缆冗余不依赖此调用。

public bool ForceFailover()

请求把发送路径切到副网口,供调试或计划性维护。

返回值含义
truenative 执行了切换
false未切换。常见原因是双向 merge 不手动切(两侧帧同时在跑,没有可切的主/备路)。对象已释放也会返回 false

不得把 false 当成成功。

bool switched = master.ForceFailover();
if (!switched)
Console.WriteLine("未切换: 双向 merge 不手动切, 或主站已释放");

冗余 WKC 读取 (合并 WKC)​

合并 WKC 是权威值, 别拿主口单口 WKC 判故障

环形冗余下, 主帧 (primary) 从 P0 入网走 ring + branch, 副帧 (secondary) 从 P1 入网仅走 ring。某段断开时, 一侧帧只能看到断点之前的从站, 另一侧从反方向补齐 —— SDK 把两侧 WKC 合并, 得到本周期真正完成 PDO 交换的从站数。

铁律: 合并 WKC == 期望 WKC, 即网络完好; 此时主口单口 WKC 可以 < 期望, 不是故障。 切勿用 PrimaryWKC 单口值去比 ExpectedWKC 判掉站 —— 在断点被冗余补偿的场景下这必然误报。

类别属性类型读写说明
合并 WKC (权威)WKCushort只读主+副两侧合并后本周期实际完成交换的从站数
期望 WKCExpectedWKCushort只读配置期/进 OP 时确定的固定真值, 永不下调迁就劣化总线
主口单口PrimaryWKCushort只读主口实测 WKC (冗余模式独立跟踪); 断点被补偿时可 < ExpectedWKC, 属正常
副口单口SecondaryWKCushort只读副口实测 WKC (冗余模式独立跟踪); 单网卡模式恒为 0
主口单口 (RT)PrimaryWkcRtushort只读主口 RT 路径直读实测 WKC (内核已滤波, 延迟更低)
副口单口 (RT)SecondaryWkcRtushort只读副口 RT 路径直读实测 WKC (内核已滤波, 延迟更低)
主口期望PrimaryExpectedWKCushort只读主发帧期望 WKC = ring + branch (主口拓扑完好真值); 单口/拓扑未识别时为 0
副口期望SecondaryExpectedWKCushort只读副发帧期望 WKC = ring (副口拓扑完好真值); 单口/拓扑未识别时为 0
冗余激活RedundancyActivebool只读冗余是否激活 (双端口均有流量, 即存在被补偿的断点)

判健康的正确写法 — 以合并 WKC 为准:

var diag = master.Diagnostics;

// ✅ 正确: 合并 WKC == 期望 WKC, 网络完好 (即使有断点被冗余补偿)
bool linkHealthy = diag.WKC == diag.ExpectedWKC;

if (linkHealthy && diag.RedundancyActive)
Console.WriteLine("有断点, 但冗余已补齐, 网络仍完整 (合并 WKC 达期望)");
else if (!linkHealthy)
Console.WriteLine($"真有从站掉了: 合并 WKC={diag.WKC} < 期望={diag.ExpectedWKC}");

// 诊断断点位置时再看主/副单口 (不要拿单口去判整体健康)
Console.WriteLine($"主口单口 WKC={diag.PrimaryWKC} (期望主路={diag.PrimaryExpectedWKC}), " +
$"副口单口 WKC={diag.SecondaryWKC} (期望副路={diag.SecondaryExpectedWKC})");

完整示例​

启用冗余 + 健康监控​

// 通过双网口启用冗余 (推荐通过 GetNetworkInfo 自动识别冗余对)
var adapters = DarraEtherCAT.GetNetworkInfo();
var pair = adapters.Where(a => a.RedundantSlaveNum > 0).Take(2).ToList();
if (pair.Count < 2) return;

var master = new DarraEtherCAT()
.SetNetwork(pair[0], pair[1])
.SetENI(@"C:\config.xml")
.Build()
.Master;
if (master == null) return;

// 订阅冗余模式变化 (回调签名: ushort masterIndex, int oldMode, int newMode)
master.Events.RedundancyModeChanged += (masterIndex, oldMode, newMode) =>
{
Console.WriteLine($"冗余模式: {oldMode} -> {newMode}");
};

master.SetState(EcState.OP);

// 周期性健康检查 — 通过诊断模块查看冗余状态
var diag = master.Diagnostics;
while (true)
{
Console.WriteLine($"冗余: ring_mode={diag.RingMode}, " +
$"active={diag.RedundancyActive}, " +
$"primary_ok={diag.PrimaryPortOk}, secondary_ok={diag.SecondaryPortOk}");
if (diag.BreakPoint != null)
Console.WriteLine($"故障点: {diag.BreakPoint}");
Thread.Sleep(1000);
}