darra-ethercat-master 2.1.0

商业 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.
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
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
//! 协议常量解码 (转发到 DLL ec_*.h 模块)
//!
//! 本模块为 11 组 DLL 协议解码 API 提供 Rust 友好封装:
//! - AL_StatusCode  : ETG.1000.6 AL 状态码 (Severity / 描述 / Recovery Hint / VendorSpecific)
//! - SDOAbort       : CiA 301 SDO Abort (类别 / 描述 / 是否可重试 / 处理建议)
//! - EmcyCode       : CiA 301 Emergency 错误码 (大类 / 描述 / 错误寄存器位)
//! - EcState        : ESM 状态合法性 (转换检查 / 路径 / Bootstrap)
//! - EcPdoCodec     : PDO BitField 编解码 + 端口拓扑
//! - CiA402Modes    : CiA 402 操作模式 + 回零方法
//! - EcMailbox      : 邮箱协议类型/错误码
//! - EcSoE          : SoE 错误码
//! - EcSii          : SII (EEPROM) 解析
//! - EcCouplerId    : 耦合器/厂商识别
//! - EcDiagStrings  : 诊断信息文本
//!
//! 所有字符串结果在 C 端是 UTF-8 静态常量, 安全 borrow 后 to_string_lossy.
//! 失败 (NULL 指针) 时返回兜底字符串 (如 "未知" / "未知错误"), 不会 panic.

use std::ffi::CStr;
use std::os::raw::c_char;

use crate::utils::ffi;

/// 内部辅助: 将 C 端 const char* 转 String, NULL 时返回 fallback.
fn cstr_to_string(p: *const c_char, fallback: &str) -> String {
    if p.is_null() {
        return fallback.to_string();
    }
    unsafe { CStr::from_ptr(p).to_string_lossy().into_owned() }
}

/// AL Status Code 严重程度 (对齐 ec_status_codes.h al_status_severity_t)
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AlSeverity {
    None = 0,
    Info = 1,
    Warning = 2,
    Error = 3,
    Fatal = 4,
}

impl AlSeverity {
    pub fn from_i32(v: i32) -> Self {
        match v {
            0 => Self::None,
            1 => Self::Info,
            2 => Self::Warning,
            3 => Self::Error,
            _ => Self::Fatal,
        }
    }
}

/// SDO Abort 类别 (对齐 sdo_abort_category_t)
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SdoAbortCategory {
    Transfer = 0,
    Access = 1,
    Value = 2,
    Hardware = 3,
    General = 4,
}

impl SdoAbortCategory {
    pub fn from_i32(v: i32) -> Self {
        match v {
            0 => Self::Transfer,
            1 => Self::Access,
            2 => Self::Value,
            3 => Self::Hardware,
            _ => Self::General,
        }
    }
}

/// EMCY 大类 (对齐 emcy_class_t)
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EmcyClass {
    NoError = 0x0000,
    Generic = 0x1000,
    Current = 0x2000,
    Voltage = 0x3000,
    Temperature = 0x4000,
    Hardware = 0x5000,
    Software = 0x6000,
    AdditionalModules = 0x7000,
    Monitoring = 0x8000,
    External = 0x9000,
    AdditionalFunc = 0xF000,
    DeviceSpecific = 0xFF00,
}

impl EmcyClass {
    pub fn from_i32(v: i32) -> Self {
        match v {
            0x0000 => Self::NoError,
            0x1000 => Self::Generic,
            0x2000 => Self::Current,
            0x3000 => Self::Voltage,
            0x4000 => Self::Temperature,
            0x5000 => Self::Hardware,
            0x6000 => Self::Software,
            0x7000 => Self::AdditionalModules,
            0x8000 => Self::Monitoring,
            0x9000 => Self::External,
            0xF000 => Self::AdditionalFunc,
            _ => Self::DeviceSpecific,
        }
    }
}

/// ESM 状态转换类型 (对齐 ec_transition_type_t)
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EcTransitionType {
    Invalid = 0,
    Up = 1,
    Down = 2,
    Same = 3,
    Bootstrap = 4,
    JumpUp = 5,
    JumpDown = 6,
}

impl EcTransitionType {
    pub fn from_i32(v: i32) -> Self {
        match v {
            1 => Self::Up,
            2 => Self::Down,
            3 => Self::Same,
            4 => Self::Bootstrap,
            5 => Self::JumpUp,
            6 => Self::JumpDown,
            _ => Self::Invalid,
        }
    }
}

/// PDO 端口拓扑 (对齐 ec_topology_t)
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EcTopology {
    Unknown = 0,
    Line = 1,
    Pass = 2,
    Branch = 3,
    Cross = 4,
}

impl EcTopology {
    pub fn from_i32(v: i32) -> Self {
        match v {
            1 => Self::Line,
            2 => Self::Pass,
            3 => Self::Branch,
            4 => Self::Cross,
            _ => Self::Unknown,
        }
    }
}

/// 耦合器识别得到的设备类型 (对齐 ec_device_type_t)
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EcDeviceType {
    Unknown = 0,
    Coupler = 1,
    Terminal = 2,
    Drive = 3,
    Sensor = 4,
    Gateway = 5,
    Safety = 6,
    Controller = 7,
}

impl EcDeviceType {
    pub fn from_i32(v: i32) -> Self {
        match v {
            1 => Self::Coupler,
            2 => Self::Terminal,
            3 => Self::Drive,
            4 => Self::Sensor,
            5 => Self::Gateway,
            6 => Self::Safety,
            7 => Self::Controller,
            _ => Self::Unknown,
        }
    }
}

/// Homing 触发源类型 (对齐 homing_trigger_t)
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HomingTrigger {
    NegativeLimit = 0,
    PositiveLimit = 1,
    HomeSwitch = 2,
    IndexPulse = 3,
    HardStop = 4,
    CurrentPos = 5,
    Vendor = 6,
}

impl HomingTrigger {
    pub fn from_i32(v: i32) -> Self {
        match v {
            0 => Self::NegativeLimit,
            1 => Self::PositiveLimit,
            2 => Self::HomeSwitch,
            3 => Self::IndexPulse,
            4 => Self::HardStop,
            5 => Self::CurrentPos,
            _ => Self::Vendor,
        }
    }
}

/// 协议解码统一入口 (零状态结构, 仅起命名空间作用).
pub struct ProtocolCodes;

impl ProtocolCodes {
    // ===================== AL Status Code =====================

    /// AL_STATUS_CODE 中文描述 (ETG.1000.6 Table 11)
    pub fn al_status_description(code: u16) -> String {
        unsafe { cstr_to_string(ffi::AL_StatusCode_GetDescription(code), "未知错误") }
    }

    /// AL_STATUS_CODE 严重程度
    pub fn al_status_severity(code: u16) -> AlSeverity {
        unsafe { AlSeverity::from_i32(ffi::AL_StatusCode_GetSeverity(code)) }
    }

    /// AL_STATUS_CODE 推荐处理动作描述
    pub fn al_status_recovery_hint(code: u16) -> String {
        unsafe { cstr_to_string(ffi::AL_StatusCode_GetRecoveryHint(code), "请联系厂商") }
    }

    /// 是否为厂商自定义 AL Status Code (0x8000-0xFFFF)
    pub fn al_status_is_vendor_specific(code: u16) -> bool {
        unsafe { ffi::AL_StatusCode_IsVendorSpecific(code) != 0 }
    }

    // ===================== SDO Abort =====================

    /// SDO Abort Code 中英文描述
    pub fn sdo_abort_description(abort_code: u32) -> String {
        unsafe { cstr_to_string(ffi::SDOAbort_GetDescription(abort_code), "未知中止码") }
    }

    /// SDO Abort 类别
    pub fn sdo_abort_category(abort_code: u32) -> SdoAbortCategory {
        unsafe { SdoAbortCategory::from_i32(ffi::SDOAbort_GetCategory(abort_code)) }
    }

    /// SDO Abort 是否可重试
    pub fn sdo_abort_is_retryable(abort_code: u32) -> bool {
        unsafe { ffi::SDOAbort_IsRetryable(abort_code) != 0 }
    }

    /// SDO Abort 处理建议
    pub fn sdo_abort_hint(abort_code: u32) -> String {
        unsafe { cstr_to_string(ffi::SDOAbort_GetHint(abort_code), "请检查从站配置") }
    }

    // ===================== EMCY Emergency =====================

    /// EMCY 错误码描述 (CiA 301 Table 24)
    pub fn emcy_description(error_code: u16) -> String {
        unsafe { cstr_to_string(ffi::EmcyCode_GetDescription(error_code), "未知紧急错误") }
    }

    /// EMCY 大类
    pub fn emcy_class(error_code: u16) -> EmcyClass {
        unsafe { EmcyClass::from_i32(ffi::EmcyCode_GetClass(error_code)) }
    }

    /// EMCY 大类名称
    pub fn emcy_class_name(class_code: EmcyClass) -> String {
        unsafe { cstr_to_string(ffi::EmcyCode_GetClassName(class_code as i32), "未知大类") }
    }

    /// 解析 Error Register 位掩码为可读字符串 (例 0x16 -> "通用|电流|温度|通讯")
    pub fn emcy_format_error_register(error_register: u8) -> String {
        let mut buf = vec![0u8; 128];
        unsafe {
            ffi::EmcyCode_FormatErrorRegister(error_register, buf.as_mut_ptr(), buf.len() as i32);
        }
        // 找到 NUL 终止符
        let len = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
        String::from_utf8_lossy(&buf[..len]).into_owned()
    }

    /// EMCY 是否为错误恢复消息 (error_code == 0x0000)
    pub fn emcy_is_recovery(error_code: u16) -> bool {
        unsafe { ffi::EmcyCode_IsRecovery(error_code) != 0 }
    }

    // ===================== ESM 状态合法性 =====================

    /// 检查从站状态转换是否合法 (传 ETG.1000.6 §6.4.1 表 14 数值)
    pub fn ec_state_is_valid_transition(from: u16, to: u16) -> bool {
        unsafe { ffi::EcState_IsValidTransition(from, to) != 0 }
    }

    /// 获取状态转换类型 (合法性 + 方向)
    pub fn ec_state_transition_type(from: u16, to: u16) -> EcTransitionType {
        unsafe { EcTransitionType::from_i32(ffi::EcState_GetTransitionType(from, to)) }
    }

    /// 获取从 from 到 to 必经的中间状态序列, 非法转换返回空 Vec
    pub fn ec_state_transition_path(from: u16, to: u16) -> Vec<u16> {
        let mut buf = [0u16; 8];
        let n =
            unsafe { ffi::EcState_GetTransitionPath(from, to, buf.as_mut_ptr(), buf.len() as i32) };
        if n <= 0 {
            return Vec::new();
        }
        buf[..(n as usize).min(buf.len())].to_vec()
    }

    /// 是否需要 Bootstrap (用于固件升级)
    pub fn ec_state_is_bootstrap_required(state: u16) -> bool {
        unsafe { ffi::EcState_IsBootstrapRequired(state) != 0 }
    }

    /// 状态名 (中文)
    pub fn ec_state_name(state: u16) -> String {
        unsafe { cstr_to_string(ffi::EcState_GetName(state), "未知状态") }
    }

    /// 状态名 (英文)
    pub fn ec_state_name_en(state: u16) -> String {
        unsafe { cstr_to_string(ffi::EcState_GetNameEn(state), "Unknown") }
    }

    /// 状态是否带 ERROR ACK 位 (0x10)
    pub fn ec_state_has_error_ack(state: u16) -> bool {
        unsafe { ffi::EcState_HasErrorAck(state) != 0 }
    }

    /// 去除 ERROR ACK 位, 返回基础状态
    pub fn ec_state_strip_error_ack(state: u16) -> u16 {
        unsafe { ffi::EcState_StripErrorAck(state) }
    }

    // ===================== PDO Codec / 端口拓扑 =====================

    /// 数据类型对应的 bit 长度. 0=未知, -1=变长字符串
    pub fn pdo_data_type_bit_size(dt: u16) -> i32 {
        unsafe { ffi::EcPdoCodec_DataTypeBitSize(dt as i32) }
    }

    /// 数据类型可读名 (英文)
    pub fn pdo_data_type_name(dt: u16) -> String {
        unsafe { cstr_to_string(ffi::EcPdoCodec_DataTypeName(dt as i32), "Unknown") }
    }

    /// BitField 抽取 u64. 失败返回 None.
    pub fn pdo_extract_u64(src: &[u8], bit_offset: i32, bit_length: i32) -> Option<u64> {
        let mut out: u64 = 0;
        let r = unsafe {
            ffi::EcPdoCodec_ExtractU64(
                src.as_ptr(),
                src.len() as i32,
                bit_offset,
                bit_length,
                &mut out,
            )
        };
        if r == 0 {
            Some(out)
        } else {
            None
        }
    }

    /// BitField 抽取 i64 (按 bit_length 做符号扩展)
    pub fn pdo_extract_i64(src: &[u8], bit_offset: i32, bit_length: i32) -> Option<i64> {
        let mut out: i64 = 0;
        let r = unsafe {
            ffi::EcPdoCodec_ExtractI64(
                src.as_ptr(),
                src.len() as i32,
                bit_offset,
                bit_length,
                &mut out,
            )
        };
        if r == 0 {
            Some(out)
        } else {
            None
        }
    }

    /// BitField 写入 u64. 返回 true 表示成功.
    pub fn pdo_insert_u64(dst: &mut [u8], bit_offset: i32, bit_length: i32, value: u64) -> bool {
        let n = dst.len() as i32;
        unsafe { ffi::EcPdoCodec_InsertU64(dst.as_mut_ptr(), n, bit_offset, bit_length, value) == 0 }
    }

    /// BitField 写入 i64
    pub fn pdo_insert_i64(dst: &mut [u8], bit_offset: i32, bit_length: i32, value: i64) -> bool {
        let n = dst.len() as i32;
        unsafe { ffi::EcPdoCodec_InsertI64(dst.as_mut_ptr(), n, bit_offset, bit_length, value) == 0 }
    }

    /// 抽取 REAL32 (bit_offset 必须 8 对齐)
    pub fn pdo_extract_real32(src: &[u8], bit_offset: i32) -> Option<f32> {
        let mut out: f32 = 0.0;
        let r = unsafe {
            ffi::EcPdoCodec_ExtractReal32(src.as_ptr(), src.len() as i32, bit_offset, &mut out)
        };
        if r == 0 {
            Some(out)
        } else {
            None
        }
    }

    /// 抽取 REAL64 (bit_offset 必须 8 对齐)
    pub fn pdo_extract_real64(src: &[u8], bit_offset: i32) -> Option<f64> {
        let mut out: f64 = 0.0;
        let r = unsafe {
            ffi::EcPdoCodec_ExtractReal64(src.as_ptr(), src.len() as i32, bit_offset, &mut out)
        };
        if r == 0 {
            Some(out)
        } else {
            None
        }
    }

    /// 写入 REAL32
    pub fn pdo_insert_real32(dst: &mut [u8], bit_offset: i32, value: f32) -> bool {
        let n = dst.len() as i32;
        unsafe { ffi::EcPdoCodec_InsertReal32(dst.as_mut_ptr(), n, bit_offset, value) == 0 }
    }

    /// 写入 REAL64
    pub fn pdo_insert_real64(dst: &mut [u8], bit_offset: i32, value: f64) -> bool {
        let n = dst.len() as i32;
        unsafe { ffi::EcPdoCodec_InsertReal64(dst.as_mut_ptr(), n, bit_offset, value) == 0 }
    }

    /// ActivePorts 字节中活跃端口数 (0..4)
    pub fn pdo_count_active_ports(active_ports: u8) -> i32 {
        unsafe { ffi::EcPdoCodec_CountActivePorts(active_ports) }
    }

    /// ActivePorts 解码为拓扑类型
    pub fn pdo_get_topology(active_ports: u8) -> EcTopology {
        unsafe { EcTopology::from_i32(ffi::EcPdoCodec_GetTopology(active_ports)) }
    }

    /// 拓扑类型中文名
    pub fn pdo_topology_name(topo: EcTopology) -> String {
        unsafe { cstr_to_string(ffi::EcPdoCodec_GetTopologyName(topo as i32), "未知") }
    }

    /// 拓扑类型英文名
    pub fn pdo_topology_name_en(topo: EcTopology) -> String {
        unsafe { cstr_to_string(ffi::EcPdoCodec_GetTopologyNameEn(topo as i32), "Unknown") }
    }

    /// 检查指定端口是否活跃 (port: 0..3)
    pub fn pdo_is_port_active(active_ports: u8, port: i32) -> bool {
        unsafe { ffi::EcPdoCodec_IsPortActive(active_ports, port) != 0 }
    }

    // ===================== CiA402 模式 / 回零方法 =====================

    /// 模式对应 0x6502 SupportedDriveModes 的 bit 索引. -1=无对应位
    pub fn cia402_mode_to_supported_bit(mode: i8) -> i32 {
        unsafe { ffi::CiA402Modes_ModeToSupportedBit(mode) }
    }

    /// 检查 0x6502 掩码是否支持指定模式
    pub fn cia402_is_mode_supported(mask: u32, mode: i8) -> bool {
        unsafe { ffi::CiA402Modes_IsModeSupportedInMask(mask, mode) != 0 }
    }

    /// 展开 0x6502 掩码为模式列表
    pub fn cia402_expand_supported_mask(mask: u32) -> Vec<i8> {
        let mut buf = [0i8; 16];
        let n =
            unsafe { ffi::CiA402Modes_ExpandSupportedMask(mask, buf.as_mut_ptr(), buf.len() as i32) };
        if n <= 0 {
            return Vec::new();
        }
        buf[..(n as usize).min(buf.len())].to_vec()
    }

    /// 模式名 (中文 + 英文缩写)
    pub fn cia402_mode_name(mode: i8) -> String {
        unsafe { cstr_to_string(ffi::CiA402Modes_GetModeName(mode), "未知模式") }
    }

    /// 模式名 (英文全称)
    pub fn cia402_mode_name_en(mode: i8) -> String {
        unsafe { cstr_to_string(ffi::CiA402Modes_GetModeNameEn(mode), "Unknown") }
    }

    /// 模式描述 (用途说明)
    pub fn cia402_mode_description(mode: i8) -> String {
        unsafe { cstr_to_string(ffi::CiA402Modes_GetModeDescription(mode), "无描述") }
    }

    /// 是否周期同步模式 (CSP/CSV/CST/CSTCA)
    pub fn cia402_is_cyclic_sync_mode(mode: i8) -> bool {
        unsafe { ffi::CiA402Modes_IsCyclicSyncMode(mode) != 0 }
    }

    /// 模式是否需要 DC 同步
    pub fn cia402_requires_dc(mode: i8) -> bool {
        unsafe { ffi::CiA402Modes_RequiresDC(mode) != 0 }
    }

    /// 是否标准 ETG.6010 回零方法 (1..35 或 37)
    pub fn cia402_is_standard_homing_method(method: i8) -> bool {
        unsafe { ffi::CiA402Modes_IsStandardHomingMethod(method) != 0 }
    }

    /// 回零方法描述 (中文)
    pub fn cia402_homing_method_name(method: i8) -> String {
        unsafe { cstr_to_string(ffi::CiA402Modes_GetHomingMethodName(method), "厂商自定义") }
    }

    /// 回零方法触发源
    pub fn cia402_homing_trigger(method: i8) -> HomingTrigger {
        unsafe { HomingTrigger::from_i32(ffi::CiA402Modes_GetHomingTrigger(method)) }
    }

    /// 回零方法移动方向: -1=负, 0=不移动, +1=正
    pub fn cia402_homing_direction(method: i8) -> i32 {
        unsafe { ffi::CiA402Modes_GetHomingDirection(method) }
    }

    /// 列出所有标准回零方法编号 (0,1..35,37)
    pub fn cia402_list_standard_homing_methods() -> Vec<i8> {
        let mut buf = [0i8; 64];
        let n = unsafe {
            ffi::CiA402Modes_ListStandardHomingMethods(buf.as_mut_ptr(), buf.len() as i32)
        };
        if n <= 0 {
            return Vec::new();
        }
        buf[..(n as usize).min(buf.len())].to_vec()
    }

    // ===================== Mailbox =====================

    /// 邮箱类型中文名
    pub fn mailbox_type_name(mbx_type: u16) -> String {
        unsafe { cstr_to_string(ffi::EcMailbox_GetTypeName(mbx_type), "未知") }
    }

    /// 邮箱类型英文名
    pub fn mailbox_type_name_en(mbx_type: u16) -> String {
        unsafe { cstr_to_string(ffi::EcMailbox_GetTypeNameEn(mbx_type), "Unknown") }
    }

    /// 邮箱错误码描述
    pub fn mailbox_error_description(mbx_error_code: u16) -> String {
        unsafe { cstr_to_string(ffi::EcMailbox_GetErrorDescription(mbx_error_code), "未知错误") }
    }

    /// Mailbox Counter 1..7 循环
    pub fn mailbox_next_counter(current: u8) -> u8 {
        unsafe { ffi::EcMailbox_NextCounter(current) }
    }

    // ===================== SoE =====================

    /// SoE 错误码描述 (中文)
    pub fn soe_error_description(soe_error: u16) -> String {
        unsafe { cstr_to_string(ffi::EcSoE_GetErrorDescription(soe_error), "未知 SoE 错误") }
    }

    // ===================== SII (EEPROM) =====================

    /// 在 SII 缓冲中查找指定 Category, 返回 (起始字节偏移, 字节长度); 未找到返回 None.
    pub fn sii_find_category(sii_data: &[u8], cat_type: u16) -> Option<(i32, i32)> {
        let mut out_size: i32 = 0;
        let off = unsafe {
            ffi::EcSii_FindCategory(sii_data.as_ptr(), sii_data.len() as i32, cat_type, &mut out_size)
        };
        if off < 0 {
            None
        } else {
            Some((off, out_size))
        }
    }

    /// 枚举 SII 中所有 Category 类型
    pub fn sii_enumerate_categories(sii_data: &[u8]) -> Vec<u16> {
        let mut buf = vec![0u16; 32];
        let n = unsafe {
            ffi::EcSii_EnumerateCategories(
                sii_data.as_ptr(),
                sii_data.len() as i32,
                buf.as_mut_ptr(),
                buf.len() as i32,
            )
        };
        if n <= 0 {
            return Vec::new();
        }
        buf.truncate((n as usize).min(buf.len()));
        buf
    }

    /// 从 Strings Category 取第 idx (1-based) 个字符串
    pub fn sii_get_string_by_index(cat_data: &[u8], idx: i32) -> Option<String> {
        let mut buf = vec![0u8; 256];
        let n = unsafe {
            ffi::EcSii_GetStringByIndex(
                cat_data.as_ptr(),
                cat_data.len() as i32,
                idx,
                buf.as_mut_ptr(),
                buf.len() as i32,
            )
        };
        if n < 0 {
            return None;
        }
        let n = (n as usize).min(buf.len());
        Some(String::from_utf8_lossy(&buf[..n]).into_owned())
    }

    /// 字符串总数
    pub fn sii_get_string_count(cat_data: &[u8]) -> i32 {
        unsafe { ffi::EcSii_GetStringCount(cat_data.as_ptr(), cat_data.len() as i32) }
    }

    /// SII 中的 Vendor ID (Word 8-9)
    pub fn sii_get_vendor_id(sii_data: &[u8]) -> u32 {
        unsafe { ffi::EcSii_GetVendorId(sii_data.as_ptr(), sii_data.len() as i32) }
    }

    /// SII 中的 Product Code (Word 10-11)
    pub fn sii_get_product_code(sii_data: &[u8]) -> u32 {
        unsafe { ffi::EcSii_GetProductCode(sii_data.as_ptr(), sii_data.len() as i32) }
    }

    /// SII 中的 Revision (Word 12-13)
    pub fn sii_get_revision(sii_data: &[u8]) -> u32 {
        unsafe { ffi::EcSii_GetRevision(sii_data.as_ptr(), sii_data.len() as i32) }
    }

    /// SII 中的 Serial Number (Word 14-15)
    pub fn sii_get_serial_number(sii_data: &[u8]) -> u32 {
        unsafe { ffi::EcSii_GetSerialNumber(sii_data.as_ptr(), sii_data.len() as i32) }
    }

    /// SII 中的 Configured Station Alias (Word 4)
    pub fn sii_get_configured_alias(sii_data: &[u8]) -> u16 {
        unsafe { ffi::EcSii_GetConfiguredAlias(sii_data.as_ptr(), sii_data.len() as i32) }
    }

    /// CoE 启用 (Bit 0)
    pub fn sii_coe_enabled(coe_details: u8) -> bool {
        unsafe { ffi::EcSii_CoeEnabled(coe_details) != 0 }
    }

    /// CoE SDO Info (Bit 1)
    pub fn sii_coe_sdo_info(coe_details: u8) -> bool {
        unsafe { ffi::EcSii_CoeSdoInfo(coe_details) != 0 }
    }

    /// CoE PDO Assign (Bit 2)
    pub fn sii_coe_pdo_assign(coe_details: u8) -> bool {
        unsafe { ffi::EcSii_CoePdoAssign(coe_details) != 0 }
    }

    /// CoE PDO Config (Bit 3)
    pub fn sii_coe_pdo_config(coe_details: u8) -> bool {
        unsafe { ffi::EcSii_CoePdoConfig(coe_details) != 0 }
    }

    /// CoE Upload at Startup (Bit 4)
    pub fn sii_coe_upload_at_startup(coe_details: u8) -> bool {
        unsafe { ffi::EcSii_CoeUploadAtStartup(coe_details) != 0 }
    }

    /// CoE Complete Access (Bit 5)
    pub fn sii_coe_complete_access(coe_details: u8) -> bool {
        unsafe { ffi::EcSii_CoeCompleteAccess(coe_details) != 0 }
    }

    /// FoE 启用 (Bit 0)
    pub fn sii_foe_enabled(foe_details: u8) -> bool {
        unsafe { ffi::EcSii_FoeEnabled(foe_details) != 0 }
    }

    /// EoE 启用 (Bit 0)
    pub fn sii_eoe_enabled(eoe_details: u8) -> bool {
        unsafe { ffi::EcSii_EoeEnabled(eoe_details) != 0 }
    }

    // ===================== 耦合器识别 =====================

    /// 根据 VendorID + ProductCode 识别设备类型
    pub fn coupler_detect_device_type(vendor_id: u32, product_code: u32) -> EcDeviceType {
        unsafe {
            EcDeviceType::from_i32(ffi::EcCouplerId_DetectDeviceType(vendor_id, product_code))
        }
    }

    /// 是否为耦合器 (头模块)
    pub fn coupler_is_coupler(vendor_id: u32, product_code: u32) -> bool {
        unsafe { ffi::EcCouplerId_IsCoupler(vendor_id, product_code) != 0 }
    }

    /// 是否为终端 I/O 模块
    pub fn coupler_is_terminal(vendor_id: u32, product_code: u32) -> bool {
        unsafe { ffi::EcCouplerId_IsTerminal(vendor_id, product_code) != 0 }
    }

    /// 厂商名称 (中文)
    pub fn coupler_vendor_name(vendor_id: u32) -> String {
        unsafe { cstr_to_string(ffi::EcCouplerId_GetVendorName(vendor_id), "未知厂商") }
    }

    /// 厂商名称 (英文)
    pub fn coupler_vendor_name_en(vendor_id: u32) -> String {
        unsafe { cstr_to_string(ffi::EcCouplerId_GetVendorNameEn(vendor_id), "Unknown") }
    }

    /// 设备类型名称 (中文)
    pub fn coupler_device_type_name(dtype: EcDeviceType) -> String {
        unsafe { cstr_to_string(ffi::EcCouplerId_GetDeviceTypeName(dtype as i32), "未知") }
    }

    // ===================== 诊断翻译 =====================

    /// 拓扑描述 (0=线性, 1=环形, 2=环+分支)
    pub fn diag_topology_description(topo: u8) -> String {
        unsafe { cstr_to_string(ffi::EcDiagStrings_TopologyDescription(topo), "未知") }
    }

    /// 定时模式描述
    pub fn diag_timing_mode(mode: u32) -> String {
        unsafe { cstr_to_string(ffi::EcDiagStrings_TimingMode(mode), "未知") }
    }

    /// 断点故障类型描述
    pub fn diag_breakpoint_type(bp: u8) -> String {
        unsafe { cstr_to_string(ffi::EcDiagStrings_BreakpointType(bp), "未知故障") }
    }

    /// 综合断点信息格式化 (例: "从站5 P2 断线")
    pub fn diag_format_breakpoint(slave_idx: u16, port: u8, bp: u8) -> String {
        let mut buf = vec![0u8; 128];
        unsafe {
            ffi::EcDiagStrings_FormatBreakpoint(
                slave_idx,
                port,
                bp,
                buf.as_mut_ptr(),
                buf.len() as i32,
            );
        }
        let len = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
        String::from_utf8_lossy(&buf[..len]).into_owned()
    }
}