darra-ethercat-master 1.99.7

商业 EtherCAT 主站协议栈 · 实时内核驱动 · 抖动 1µs · Windows + Linux · 多编程语言 · 全协议 · 支持复杂拓扑 + 热插拔 · ethercat.darra.xyz · Commercial EtherCAT Master protocol stack · Real-time kernel driver · 1µs jitter · Multi-platform · Multi-language · Complex topology + hot-plug.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
//! 主站诊断信息 - 帧/错误/抖动统计, 5秒滑动窗口, 0.2秒刷新
//!
//! 通过 `master.diagnostics_info()` 访问。
//! 对应 C# MasterDiagnosticsInfo 类。

use crate::master::core::EtherCATMaster;
use crate::data::types::{RingMode, SlaveErrorCounters};

// ===================== 内部诊断结构体 (对应 C 端 InternalDiagnostics) =====================

/// 内部诊断数据 (C 端共享内存布局)
#[repr(C)]
#[allow(non_snake_case)]
struct InternalDiagnostics {
    cycle_count: u32,
    frame_errors: u32,
    lost_frames: u32,
    checksum_errors: u32,
    timeout_frames: u32,
    jitter_accumulator: f64,
    jitter_sample_count: u32,
    max_jitter_in_second: f64,
    CycleTimeSpan: u32,
    WKC: u16,
    ExpectedWKC: u16,
}

/// 摘要数据 (C 端 ec_diagnostics_summary_t, 零拷贝指针访问)
#[repr(C)]
struct NativeSummary {
    total_errors: u32,
    error_rate_per_1000: f64,
    worst_link_quality: i16,
    worst_slave_index: u16,
    // 网口状态
    redundancy_active: i32,
    primary_port_ok: i32,
    secondary_port_ok: i32,
    primary_port_errors_window: u32,
    secondary_port_errors_window: u32,
    // 实时性能 (应用层)
    rt_frequency: u32,
    cycle_time_us: u32,
    packet_loss_percent: f64,
    avg_jitter_us: f64,
    max_jitter_us: f64,
    avg_cycle_time_us: f64,
    // 总线性能 (RT 内核层)
    bus_avg_jitter_us: f64,
    bus_max_jitter_us: f64,
    bus_clean_max_jitter_us: f64,  // 干净最大抖动 (排除SMI)
    bus_cycle_hz: u32,
    bus_roundtrip_us: f64,  // 报文往返延迟 (µs)
    smi_count: u32,         // SMI累计次数
    smi_peak_us: f64,       // SMI峰值抖动 (µs)
    // 拓扑
    topology_mode: u8,
    _reserved: u8,
    break_point_count: i16,
}

// ===================== 故障点信息 =====================

/// 故障点信息 (断线或CRC故障)
#[derive(Debug, Clone, Copy)]
pub struct BreakPointInfo {
    /// 故障从站索引 (1-based)
    pub slave_index: u16,
    /// 故障端口号 (0-3, 对应 P0-P3)
    pub port: u8,
    /// 故障类型: 0=断线, 1=CRC故障(线缆/连接器劣化)
    pub fault_type: u8,
}

impl BreakPointInfo {
    /// 是否为断线故障
    pub fn is_link_down(&self) -> bool {
        self.fault_type == 0
    }

    /// 是否为 CRC 故障 (线缆/连接器劣化)
    pub fn is_crc_fault(&self) -> bool {
        self.fault_type == 1
    }
}

impl std::fmt::Display for BreakPointInfo {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self.fault_type {
            0 => write!(f, "从站{} P{} 断线", self.slave_index, self.port),
            1 => write!(f, "从站{} P{} CRC故障", self.slave_index, self.port),
            _ => write!(f, "从站{} P{} 未知故障({})", self.slave_index, self.port, self.fault_type),
        }
    }
}

// ===================== PDO 丢帧统计 =====================

/// PDO 丢帧统计
#[derive(Debug, Clone, Copy, Default)]
pub struct PDOFrameLossStats {
    /// 累计丢帧数
    pub total_lost: u32,
    /// 当前连续丢帧数
    pub consecutive_lost: u32,
    /// 最大连续丢帧数
    pub max_consecutive_lost: u32,
}

// ===================== PDO 诊断 =====================

/// PDO 丢帧诊断
/// 通过 `master.diagnostics_info().pdo()` 访问
pub struct MasterPDODiagnostics<'a> {
    master: &'a EtherCATMaster,
}

impl<'a> MasterPDODiagnostics<'a> {
    /// 指定组的 PDO 丢帧统计
    pub fn frame_loss_stats(&self, group: u8) -> PDOFrameLossStats {
        if group <= 7 {
            let (total, consecutive, max_consecutive) = self.master.pdo_frame_loss_stats(group);
            PDOFrameLossStats { total_lost: total, consecutive_lost: consecutive, max_consecutive_lost: max_consecutive }
        } else {
            self.all_groups_stats()
        }
    }

    /// 所有组汇总的 PDO 丢帧统计
    pub fn all_groups_stats(&self) -> PDOFrameLossStats {
        let mut total_sum = 0u32;
        let mut consecutive_max = 0u32;
        let mut max_consecutive_max = 0u32;
        for g in 0..8u8 {
            let (total, consecutive, max_consecutive) = self.master.pdo_frame_loss_stats(g);
            total_sum += total;
            if consecutive > consecutive_max { consecutive_max = consecutive; }
            if max_consecutive > max_consecutive_max { max_consecutive_max = max_consecutive; }
        }
        PDOFrameLossStats {
            total_lost: total_sum,
            consecutive_lost: consecutive_max,
            max_consecutive_lost: max_consecutive_max,
        }
    }

    /// 累计丢帧数 (所有组合计)
    pub fn total_lost(&self) -> u32 {
        self.all_groups_stats().total_lost
    }

    /// 当前连续丢帧数 (所有组中最大值)
    pub fn consecutive_lost(&self) -> u32 {
        self.all_groups_stats().consecutive_lost
    }
}

// ===================== 诊断数据快照 =====================

/// 诊断数据快照
///
/// 包含某一时刻所有诊断指标的一致副本,避免多次读取共享内存时数据不一致。
#[derive(Debug, Clone)]
pub struct DiagnosticsSnapshot {
    /// 应用层帧频率 (Hz)
    pub frequency: i32,
    /// 累计错误数
    pub error_count: u32,
    /// 丢包率 (0.0 ~ 1.0) — TX vs RX, 5 秒滑窗
    pub packet_loss_rate: f32,
    /// 过慢帧率 (0.0 ~ 1.0) — idx 出 8 帧窗 stale, 不计丢
    pub late_frame_rate: f32,
    /// 平均抖动 (微秒)
    pub avg_jitter_us: f64,
    /// 最大抖动 (微秒)
    pub max_jitter_us: f64,
    /// 实际周期时间 (微秒)
    pub cycle_time_us: i32,
    /// 当前工作计数器
    pub wkc_actual: u16,
    /// 期望工作计数器
    pub wkc_expected: u16,
    /// 总线帧发送频率 (Hz, RT 内核层)
    pub bus_cycle_hz: u32,
    /// 总线最大抖动 (微秒, RT 内核层)
    pub bus_max_jitter_us: f64,
    /// 总线平均抖动 (微秒, RT 内核层)
    pub bus_avg_jitter_us: f64,
    /// 报文往返延迟 (微秒)
    pub bus_roundtrip_us: f64,
    /// 通讯负载 (%) — RTT/周期×100, <30% 健康 / 30-70% 中等 / >70% 须降频
    pub bus_load_percent: f64,
    /// SMI 累计次数
    pub smi_count: u32,
    /// SMI 峰值抖动 (微秒)
    pub smi_peak_us: f64,
    /// 主端口是否正常
    pub primary_port_ok: bool,
    /// 副端口是否正常
    pub secondary_port_ok: bool,
    /// 冗余是否激活
    pub redundancy_active: bool,
}

// ===================== 主站诊断信息 =====================

/// 主站诊断信息
///
/// 提供帧/错误/抖动统计, 通过 DLL 共享内存零拷贝读取。
/// 对应 C# MasterDiagnosticsInfo 类。
///
/// # 使用方式
/// ```no_run
/// let diag = master.diagnostics_info();
/// diag.set_enabled(true);
/// println!("丢包率: {:.2}%", diag.lost() * 100.0);
/// println!("每秒帧数: {}", diag.rt_cnt());
/// println!("平均抖动: {:.2}us", diag.avg_jitter_us());
/// ```
pub struct MasterDiagnosticsInfo<'a> {
    master: &'a EtherCATMaster,
}

impl<'a> MasterDiagnosticsInfo<'a> {
    /// 创建诊断信息实例 (内部使用)
    pub(crate) fn new(master: &'a EtherCATMaster) -> Self {
        Self { master }
    }

    // ===================== 诊断开关 =====================

    /// 诊断数据采集是否启用
    pub fn enabled(&self) -> bool {
        self.master.diagnostics_enabled()
    }

    /// 设置诊断数据采集开关
    pub fn set_enabled(&self, enable: bool) {
        self.master.set_diagnostics_enabled(enable);
    }

    // ===================== PDO 通信状态 (从 Summary 指针读取) =====================

    /// 获取 Summary 指针, 读取字段 (unsafe 内部使用)
    fn with_summary<T, F: FnOnce(&NativeSummary) -> T>(&self, default: T, f: F) -> T {
        let ptr = self.master.summary_pointer() as *const NativeSummary;
        if ptr.is_null() { return default; }
        unsafe { f(&*ptr) }
    }

    /// 获取 Diagnostics 指针, 读取字段 (unsafe 内部使用)
    fn with_diag<T, F: FnOnce(&InternalDiagnostics) -> T>(&self, default: T, f: F) -> T {
        let ptr = self.master.diagnostics_pointer() as *const InternalDiagnostics;
        if ptr.is_null() { return default; }
        unsafe { f(&*ptr) }
    }

    /// 丢包率 (0.0 ~ 1.0) — 最近 5 秒滑窗.
    ///
    /// 用户原话 (2026-04-27): "丢包率 = 发出 vs 接收, 过慢 (pipeline) 不算".
    /// 算法: max(0, ΣTX - ΣRX - 2 帧 pipeline) / ΣTX, 内核统一计算 (单一数据源).
    /// 对齐 C# PacketLossRate.
    pub fn packet_loss_rate(&self) -> f32 {
        unsafe { crate::utils::ffi::GetPacketLossRate(self.master.index()) }
    }

    /// 过慢帧率 (0.0 ~ 1.0) — 最近 5 秒滑窗.
    /// idx 出 8 帧窗的 stale 帧 kernel drop, 不计入丢包. 对齐 C# LateFrameRate.
    pub fn late_frame_rate(&self) -> f32 {
        unsafe { crate::utils::ffi::GetLateFrameRate(self.master.index()) }
    }


    /// 每秒帧数 (Hz), 来自摘要数据
    pub fn rt_cnt(&self) -> u32 {
        self.with_summary(0, |s| s.rt_frequency)
    }

    /// 实际周期时间 (微秒), 来自诊断指针
    pub fn cycle_time_span(&self) -> u32 {
        self.with_diag(0, |d| d.CycleTimeSpan)
    }

    /// 平均抖动 (微秒), 来自摘要数据
    pub fn avg_jitter_us(&self) -> f64 {
        self.with_summary(0.0, |s| s.avg_jitter_us)
    }

    /// 最大抖动 (微秒), 来自摘要数据
    pub fn max_jitter_us(&self) -> f64 {
        self.with_summary(0.0, |s| s.max_jitter_us)
    }

    /// 平均周期时间 (微秒)
    pub fn avg_cycle_time_us(&self) -> f64 {
        self.with_summary(0.0, |s| s.avg_cycle_time_us)
    }

    /// 当前工作计数器
    pub fn wkc(&self) -> u16 {
        self.with_diag(0, |d| d.WKC)
    }

    /// 期望工作计数器
    pub fn expected_wkc(&self) -> u16 {
        self.with_diag(0, |d| d.ExpectedWKC)
    }

    /// 累计错误数 (所有类型)
    pub fn error_cnt(&self) -> u32 {
        self.with_summary(0, |s| s.total_errors)
    }

    /// 总线帧发送频率 (Hz, RT 内核层测量)
    pub fn bus_cycle_hz(&self) -> u32 {
        self.with_summary(0, |s| s.bus_cycle_hz)
    }

    /// 总线最大抖动 (微秒, RT 内核层测量)
    pub fn bus_max_jitter_us(&self) -> f64 {
        self.with_summary(0.0, |s| s.bus_max_jitter_us)
    }

    /// 总线平均抖动 (微秒, RT 内核层测量)
    pub fn bus_avg_jitter_us(&self) -> f64 {
        self.with_summary(0.0, |s| s.bus_avg_jitter_us)
    }

    /// 报文往返延迟 (微秒, 发送→接收) - 对齐 C# BusRoundtripUs
    pub fn bus_roundtrip_us(&self) -> f64 {
        self.with_summary(0.0, |s| s.bus_roundtrip_us)
    }

    /// 通讯负载 (%) — 一个 PDO 周期内, 报文发送到接收占用的时间百分比.
    ///
    /// 计算: `bus_roundtrip_us / cycle_time_span × 100`.
    ///
    /// 含义:
    /// - <30%  健康, 链路宽裕
    /// - 30-70% 中等, 升频率/加从站需谨慎
    /// - >70%  接近极限, 必须降频或减少从站
    ///
    /// 对齐 C# BusLoadPercent.
    pub fn bus_load_percent(&self) -> f64 {
        let rtt = self.bus_roundtrip_us();
        let cycle_us = self.cycle_time_span() as f64;
        if cycle_us <= 0.0 || rtt <= 0.0 {
            return 0.0;
        }
        let pct = rtt / cycle_us * 100.0;
        if pct > 100.0 { 100.0 } else { pct }
    }

    /// 总线干净最大抖动 (微秒, 排除SMI) - 对齐 C# BusCleanMaxJitterUs
    pub fn bus_clean_max_jitter_us(&self) -> f64 {
        self.with_summary(0.0, |s| s.bus_clean_max_jitter_us)
    }

    /// SMI累计次数 - 对齐 C# SmiCount
    pub fn smi_count(&self) -> u32 {
        self.with_summary(0, |s| s.smi_count)
    }

    /// SMI峰值抖动 (微秒) - 对齐 C# SmiPeakUs
    pub fn smi_peak_us(&self) -> f64 {
        self.with_summary(0.0, |s| s.smi_peak_us)
    }

    // ===================== 网口状态 =====================

    /// 主端口是否正常 (有流量且5秒内无错误)
    pub fn primary_port_ok(&self) -> bool {
        self.with_summary(false, |s| s.primary_port_ok != 0)
    }

    /// 副端口是否正常 (有流量且5秒内无错误, 无冗余时始终 false)
    pub fn secondary_port_ok(&self) -> bool {
        self.with_summary(false, |s| s.secondary_port_ok != 0)
    }

    /// 主端口最近5秒错误数
    pub fn primary_port_errors(&self) -> u32 {
        self.with_summary(0, |s| s.primary_port_errors_window)
    }

    /// 副端口最近5秒错误数
    pub fn secondary_port_errors(&self) -> u32 {
        self.with_summary(0, |s| s.secondary_port_errors_window)
    }

    /// 冗余是否激活 (双端口均有流量)
    pub fn redundancy_active(&self) -> bool {
        self.with_summary(false, |s| s.redundancy_active != 0)
    }

    /// 冗余模式
    pub fn ring_mode(&self) -> RingMode {
        RingMode::from_value(self.master.ring_mode())
    }

    // ===================== 从站链路与断线 =====================

    /// 最差链路质量的从站索引
    pub fn worst_slave_index(&self) -> u16 {
        self.with_summary(0, |s| s.worst_slave_index)
    }

    /// 最差链路质量百分比 (0-100%)
    pub fn worst_link_quality(&self) -> i16 {
        self.with_summary(100, |s| s.worst_link_quality)
    }

    /// 拓扑模式描述
    pub fn topology_description(&self) -> &'static str {
        let mode = self.with_summary(0u8, |s| s.topology_mode);
        match mode {
            0 => "线性",
            1 => "环形",
            2 => "环+分支",
            _ => "未知",
        }
    }

    /// 获取当前故障点 (第一个故障点, 无故障返回 None)
    pub fn break_point(&self) -> Option<BreakPointInfo> {
        let results = self.master.break_points(1);
        results.first().map(|&(slave, port, fault_type)| {
            BreakPointInfo { slave_index: slave, port, fault_type }
        })
    }

    /// 获取所有故障点 (断线 + CRC 故障)
    pub fn all_break_points(&self) -> Vec<BreakPointInfo> {
        self.master.break_points(64)
            .into_iter()
            .map(|(slave, port, fault_type)| BreakPointInfo { slave_index: slave, port, fault_type })
            .collect()
    }

    // ===================== 控制 =====================

    /// 定时模式描述
    pub fn timing_mode(&self) -> &'static str {
        match self.master.timing_mode() {
            1 => "硬件定时器",
            2 => "软件定时器",
            3 => "降级",
            4 => "RT就绪",
            _ => "未知",
        }
    }

    /// DC 同步窗口阈值 (纳秒)
    pub fn sync_window_threshold(&self) -> i32 {
        self.master.sync_window_threshold()
    }

    /// 设置 DC 同步窗口阈值 (纳秒)
    pub fn set_sync_window_threshold(&self, threshold_ns: i32) {
        self.master.set_sync_window_threshold(threshold_ns);
    }

    /// 重置所有诊断统计
    pub fn reset(&self) {
        self.master.reset_diagnostics();
        for g in 0..8u8 {
            self.master.reset_pdo_frame_loss_stats(g);
        }
    }

    // ===================== 从站错误计数 =====================

    /// 读取指定从站的错误计数器 (寄存器 0x0300-0x0307)
    pub fn read_slave_error_counters(&self, slave_index: i32) -> SlaveErrorCounters {
        let mut counters = SlaveErrorCounters {
            slave_index,
            ..Default::default()
        };

        if let Some((rx_error, invalid_frame, lost_link)) =
            self.master.read_slave_port_error_counters(slave_index as u16)
        {
            counters.port0_crc_errors = rx_error[0] as u32;
            counters.port1_crc_errors = rx_error[1] as u32;
            counters.port2_crc_errors = rx_error[2] as u32;
            counters.port3_crc_errors = rx_error[3] as u32;
            counters.frame_errors = invalid_frame.iter().map(|&x| x as u32).sum();
            counters.lost_frames = lost_link.iter().map(|&x| x as u32).sum();
        }

        counters
    }

    // ===================== PDO 丢帧诊断 =====================

    /// PDO 丢帧诊断
    pub fn pdo(&self) -> MasterPDODiagnostics<'_> {
        MasterPDODiagnostics { master: self.master }
    }

    // ===================== 诊断快照 =====================

    /// 获取诊断数据的一致快照
    ///
    /// 将当前所有诊断指标复制到一个独立结构体中,避免多次读取共享内存时数据不一致。
    /// 适用于 UI 刷新、日志记录等需要原子性读取的场景。
    pub fn get_snapshot(&self) -> DiagnosticsSnapshot {
        DiagnosticsSnapshot {
            frequency: self.rt_cnt() as i32,
            error_count: self.error_cnt(),
            packet_loss_rate: self.packet_loss_rate(),
            late_frame_rate: self.late_frame_rate(),
            avg_jitter_us: self.avg_jitter_us(),
            max_jitter_us: self.max_jitter_us(),
            cycle_time_us: self.cycle_time_span() as i32,
            wkc_actual: self.wkc(),
            wkc_expected: self.expected_wkc(),
            bus_cycle_hz: self.bus_cycle_hz(),
            bus_max_jitter_us: self.bus_max_jitter_us(),
            bus_avg_jitter_us: self.bus_avg_jitter_us(),
            bus_roundtrip_us: self.bus_roundtrip_us(),
            bus_load_percent: self.bus_load_percent(),
            smi_count: self.smi_count(),
            smi_peak_us: self.smi_peak_us(),
            primary_port_ok: self.primary_port_ok(),
            secondary_port_ok: self.secondary_port_ok(),
            redundancy_active: self.redundancy_active(),
        }
    }
}

impl<'a> std::fmt::Display for MasterDiagnosticsInfo<'a> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "Lost: {:.2}%, RTcnt: {}, Jitter: {:.2}us",
            self.packet_loss_rate() * 100.0, self.rt_cnt(), self.avg_jitter_us())
    }
}