darra-ethercat-master 2.0.6

商业 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
//! 从站 PDO 实例封装
//!
//! 对应 C# Slave/SlavePdo.cs
//! 将 PDO 相关功能集中到从站 PDO 实例中。

use crate::utils::ffi;

/// 从站 PDO 封装类
///
/// 对应 C# SlavePdo
/// 提供输入/输出数据的字节数组读写。
pub struct SlavePdo {
    master_index: u16,
    slave_index: u16,
}

impl SlavePdo {
    /// 创建新的 PDO 实例
    pub fn new(master_index: u16, slave_index: u16) -> Self {
        Self {
            master_index,
            slave_index,
        }
    }

    /// 读取输入 PDO 数据 (TxPDO, 从站到主站) - 新接口
    pub fn read_input_u8(&self, offset: u32) -> u8 {
        unsafe { ffi::PDOReadInputU8(self.master_index, self.slave_index, offset) }
    }

    /// 读取输入 i16
    pub fn read_input_i16(&self, offset: u32) -> i16 {
        unsafe { ffi::PDOReadInputI16(self.master_index, self.slave_index, offset) }
    }

    /// 读取输入 u16
    pub fn read_input_u16(&self, offset: u32) -> u16 {
        unsafe { ffi::PDOReadInputU16(self.master_index, self.slave_index, offset) }
    }

    /// 读取输入 i32
    pub fn read_input_i32(&self, offset: u32) -> i32 {
        unsafe { ffi::PDOReadInputI32(self.master_index, self.slave_index, offset) }
    }

    /// 读取输入 u32
    pub fn read_input_u32(&self, offset: u32) -> u32 {
        unsafe { ffi::PDOReadInputU32(self.master_index, self.slave_index, offset) }
    }

    /// 读取输入 f32
    pub fn read_input_f32(&self, offset: u32) -> f32 {
        unsafe { ffi::PDOReadInputF32(self.master_index, self.slave_index, offset) }
    }

    /// 写入输出 u8
    pub fn write_output_u8(&self, offset: u32, value: u8) -> bool {
        unsafe { ffi::PDOWriteOutputU8(self.master_index, self.slave_index, offset, value) != 0 }
    }

    /// 写入输出 i16
    pub fn write_output_i16(&self, offset: u32, value: i16) -> bool {
        unsafe { ffi::PDOWriteOutputI16(self.master_index, self.slave_index, offset, value) != 0 }
    }

    /// 写入输出 u16
    pub fn write_output_u16(&self, offset: u32, value: u16) -> bool {
        unsafe { ffi::PDOWriteOutputU16(self.master_index, self.slave_index, offset, value) != 0 }
    }

    /// 写入输出 i32
    pub fn write_output_i32(&self, offset: u32, value: i32) -> bool {
        unsafe { ffi::PDOWriteOutputI32(self.master_index, self.slave_index, offset, value) != 0 }
    }

    /// 写入输出 u32
    pub fn write_output_u32(&self, offset: u32, value: u32) -> bool {
        unsafe { ffi::PDOWriteOutputU32(self.master_index, self.slave_index, offset, value) != 0 }
    }

    /// 写入输出 f32
    pub fn write_output_f32(&self, offset: u32, value: f32) -> bool {
        unsafe { ffi::PDOWriteOutputF32(self.master_index, self.slave_index, offset, value) != 0 }
    }

    /// PDO 统计信息
    pub fn stats(&self) -> Option<*const std::os::raw::c_void> {
        unsafe {
            let ptr = ffi::GetPDOStats(self.master_index, self.slave_index);
            if ptr.is_null() { None } else { Some(ptr) }
        }
    }

    /// 重置 PDO 统计信息
    pub fn reset_stats(&self) {
        unsafe { ffi::ResetPDOStats(self.master_index, self.slave_index); }
    }

    // ===================== 零拷贝指针访问 =====================

    /// 获取输入数据的直接指针(零拷贝访问)
    ///
    /// 警告: 仅在 PDO 循环中使用, 指针在 IOmap 重新映射后会失效
    pub fn get_input_data_pointer(&self) -> *mut u8 {
        super::pdo::get_input_data_pointer(self.master_index, self.slave_index)
    }

    /// 获取输出数据的直接指针(零拷贝访问)
    ///
    /// 警告: 仅在 PDO 循环中使用, 指针在 IOmap 重新映射后会失效
    pub fn get_output_data_pointer(&self) -> *mut u8 {
        super::pdo::get_output_data_pointer(self.master_index, self.slave_index)
    }

    // ===================== 便捷拷贝访问 =====================

    /// 获取输入 PDO 数据的拷贝 (便捷方法)
    ///
    /// 对应 C# SlavePdo.Inputs / Python inputs 属性
    /// 注意: 此方法会进行 memcpy, 零拷贝请使用 input_slice()
    pub fn inputs(&self) -> Vec<u8> {
        let (ptr, size) = self.inputs_mapping_info();
        if ptr.is_null() || size == 0 {
            return Vec::new();
        }
        let mut buf = vec![0u8; size];
        unsafe { std::ptr::copy_nonoverlapping(ptr, buf.as_mut_ptr(), size); }
        buf
    }

    /// 获取输出 PDO 数据的拷贝 (便捷方法)
    ///
    /// 对应 C# SlavePdo.Outputs / Python outputs 属性
    /// 注意: 此方法会进行 memcpy, 零拷贝请使用 output_slice()
    pub fn outputs(&self) -> Vec<u8> {
        let (ptr, size) = self.outputs_mapping_info();
        if ptr.is_null() || size == 0 {
            return Vec::new();
        }
        let mut buf = vec![0u8; size];
        unsafe { std::ptr::copy_nonoverlapping(ptr, buf.as_mut_ptr(), size); }
        buf
    }

    /// 写入输出 PDO 数据 (便捷方法, 整体覆盖)
    ///
    /// 对应 C# SlavePdo.WriteOutputs(byte[])
    pub fn write_outputs(&self, data: &[u8]) {
        let (ptr, size) = self.outputs_mapping_info();
        if ptr.is_null() || size == 0 { return; }
        let len = data.len().min(size);
        unsafe { std::ptr::copy_nonoverlapping(data.as_ptr(), ptr, len); }
    }

    // ===================== 直接读写 =====================

    /// 读取输入数据(零拷贝,直接从 IOmap)
    ///
    /// 返回实际读取的字节数
    pub fn read_input_data_direct(&self, buffer: &mut [u8], offset: usize, length: usize) -> usize {
        super::pdo::read_input_data_direct(self.master_index, self.slave_index, buffer, offset, length)
    }

    /// 写入输出数据(零拷贝,直接到 IOmap)
    ///
    /// 返回实际写入的字节数
    pub fn write_output_data_direct(&self, data: &[u8], offset: usize, length: usize) -> usize {
        super::pdo::write_output_data_direct(self.master_index, self.slave_index, data, offset, length)
    }

    // ===================== 零拷贝切片访问 =====================

    /// 获取输入数据切片(零拷贝)
    ///
    /// 对应 C# InputsMapping / Python get_input_memoryview
    ///
    /// # 安全性
    /// 返回的切片直接指向 IOmap 内存
    pub unsafe fn input_slice(&self) -> &[u8] {
        super::pdo::input_slice(self.master_index, self.slave_index)
    }

    /// 获取输出数据切片(零拷贝)
    ///
    /// 对应 C# OutputsMapping / Python get_output_memoryview
    ///
    /// # 安全性
    /// 返回的切片直接指向 IOmap 内存
    pub unsafe fn output_slice(&self) -> &mut [u8] {
        super::pdo::output_slice(self.master_index, self.slave_index)
    }

    // ===================== PDO 映射信息查询 =====================

    /// 获取输入 PDO 映射信息
    ///
    /// 返回 (指针地址, 数据大小) 元组
    pub fn inputs_mapping_info(&self) -> (*mut u8, usize) {
        let mut out_size: i32 = 0;
        let mut out_ptr: *mut u8 = std::ptr::null_mut();
        let mut in_size: i32 = 0;
        let mut in_ptr: *mut u8 = std::ptr::null_mut();
        unsafe {
            ffi::GetIO(self.master_index, self.slave_index,
                       &mut out_size, &mut out_ptr,
                       &mut in_size, &mut in_ptr);
        }
        (in_ptr, in_size.max(0) as usize)
    }

    /// 获取输出 PDO 映射信息
    ///
    /// 返回 (指针地址, 数据大小) 元组
    pub fn outputs_mapping_info(&self) -> (*mut u8, usize) {
        let mut out_size: i32 = 0;
        let mut out_ptr: *mut u8 = std::ptr::null_mut();
        let mut in_size: i32 = 0;
        let mut in_ptr: *mut u8 = std::ptr::null_mut();
        unsafe {
            ffi::GetIO(self.master_index, self.slave_index,
                       &mut out_size, &mut out_ptr,
                       &mut in_size, &mut in_ptr);
        }
        (out_ptr, out_size.max(0) as usize)
    }

    /// 获取输入切片映射(偏移访问)
    ///
    /// 对应 C# InputsSliceMapping
    pub unsafe fn inputs_slice_mapping(&self, offset: usize, size: usize) -> &[u8] {
        let (ptr, total) = self.inputs_mapping_info();
        if ptr.is_null() || offset + size > total {
            return &[];
        }
        std::slice::from_raw_parts(ptr.add(offset), size)
    }

    /// 获取输出切片映射(偏移访问)
    ///
    /// 对应 C# OutputsSliceMapping
    pub unsafe fn outputs_slice_mapping(&self, offset: usize, size: usize) -> &mut [u8] {
        let (ptr, total) = self.outputs_mapping_info();
        if ptr.is_null() || offset + size > total {
            return &mut [];
        }
        std::slice::from_raw_parts_mut(ptr.add(offset), size)
    }

    // ===================== 结构体绑定 =====================

    /// 将 PDO 映射绑定到用户结构体指针 (对应 C# BindPdoStruct)
    ///
    /// 直接将 IOmap 中的 PDO 数据区视为指定的 T 类型,
    /// 实现零拷贝的结构化访问。
    ///
    /// # 安全性
    /// - T 必须是 #[repr(C)] 或 #[repr(packed)] 结构体
    /// - IOmap 重新映射后指针会失效
    /// - 调用者必须确保 T 的大小不超过 PDO 数据区
    pub unsafe fn bind_pdo_struct<T>(&self, is_input: bool) -> Option<*mut T> {
        let (ptr, size) = if is_input {
            self.inputs_mapping_info()
        } else {
            self.outputs_mapping_info()
        };
        if ptr.is_null() || size < std::mem::size_of::<T>() {
            return None;
        }
        Some(ptr as *mut T)
    }

    // ===================== PDO 监控控制 =====================

    /// 启动 PDO 监控
    ///
    /// 对应 C# PDOManager.StartMonitoring
    pub fn start_monitoring(&self) {
        unsafe { ffi::EnablePDOMonitoring(1); }
    }

    /// 停止 PDO 监控
    ///
    /// 对应 C# PDOManager.StopMonitoring
    pub fn stop_monitoring(&self) {
        unsafe { ffi::EnablePDOMonitoring(0); }
    }

    /// 查询 PDO 监控是否启用
    pub fn is_monitoring_enabled(&self) -> bool {
        unsafe { ffi::IsPDOMonitoringEnabled() != 0 }
    }

    // ===================== PDO 映射查询 =====================

    /// 查询指定 SM 索引的 PDO 映射信息
    ///
    /// 对应 C# GetPDOMapping
    ///
    /// # 参数
    /// - `sm_index`: SyncManager 索引 (通常 0x1A00 为输入, 0x1600 为输出)
    ///
    /// # 返回
    /// 映射数据的原始字节和条目数量
    pub fn get_pdo_mapping(&self, sm_index: u16) -> Option<(Vec<u8>, u32)> {
        let mut buffer = vec![0u8; 512];
        let mut mapping_count: u32 = 0;
        let ret = unsafe {
            ffi::GetPDOMapping(
                self.master_index, self.slave_index, sm_index,
                buffer.as_mut_ptr() as *mut std::os::raw::c_void,
                buffer.len() as u32, &mut mapping_count,
            )
        };
        if ret != 0 && mapping_count > 0 {
            buffer.truncate((mapping_count * 8) as usize); // 每条映射 8 字节
            Some((buffer, mapping_count))
        } else {
            None
        }
    }

    // ===================== IOmap 互斥锁 =====================

    /// 锁定 IOmap (对应 C# LockIOmap)
    ///
    /// 在多线程访问 PDO 数据时使用,防止数据竞争。
    /// 必须与 unlock_iomap 配对使用。
    pub fn lock_iomap(&self) {
        unsafe { ffi::LockIOmap(self.master_index); }
    }

    /// 解锁 IOmap (对应 C# UnlockIOmap)
    pub fn unlock_iomap(&self) {
        unsafe { ffi::UnlockIOmap(self.master_index); }
    }

    /// 设置互斥保护模式 (对应 C# SetMutexProtection)
    ///
    /// 启用后,所有 PDO 读写操作自动加锁。
    pub fn set_mutex_protection(&self, enabled: bool) {
        unsafe { ffi::SetMutexProtection(self.master_index, if enabled { 1 } else { 0 }); }
    }

    /// 获取互斥保护模式状态
    pub fn get_mutex_protection(&self) -> bool {
        unsafe { ffi::GetMutexProtection(self.master_index) != 0 }
    }

    // ===================== 直接 PDO 读写 (按索引) =====================

    /// 按 PDO 索引直接读取数据 (对应 C# PDOReadDirect)
    ///
    /// # 参数
    /// - `pdo_index`: PDO 对象索引
    /// - `size`: 要读取的字节数
    ///
    /// # 返回
    /// 读取的数据, 失败返回 None
    pub fn read_direct(&self, pdo_index: u16, size: u32) -> Option<Vec<u8>> {
        let mut buffer = vec![0u8; size as usize];
        let mut bytes_read: u32 = 0;
        let ret = unsafe {
            ffi::PDOReadDirect(
                self.master_index, self.slave_index, pdo_index,
                buffer.as_mut_ptr() as *mut std::os::raw::c_void,
                size, &mut bytes_read,
            )
        };
        if ret != 0 && bytes_read > 0 {
            buffer.truncate(bytes_read as usize);
            Some(buffer)
        } else {
            None
        }
    }

    /// 按 PDO 索引直接写入数据 (对应 C# PDOWriteDirect)
    ///
    /// # 参数
    /// - `pdo_index`: PDO 对象索引
    /// - `data`: 要写入的数据
    ///
    /// # 返回
    /// 是否成功
    pub fn write_direct(&self, pdo_index: u16, data: &[u8]) -> bool {
        let ret = unsafe {
            ffi::PDOWriteDirect(
                self.master_index, self.slave_index, pdo_index,
                data.as_ptr() as *const std::os::raw::c_void,
                data.len() as u32,
            )
        };
        ret != 0
    }

    // ===================== 字符串 PDO 读写 =====================

    /// 读取字符串型 PDO 输入
    ///
    /// 通过逐字节读取 PDO 输入数据区实现字符串读取。
    ///
    /// # 参数
    /// - `offset`: 在输入数据区的字节偏移
    /// - `max_len`: 最大字符串长度
    pub fn read_string(&self, offset: u32, max_len: u32) -> String {
        // 逐字节读取, 遇到 0 终止
        let mut buf = Vec::with_capacity(max_len as usize);
        for i in 0..max_len {
            let b = unsafe { ffi::PDOReadInputU8(self.master_index, self.slave_index, offset + i) };
            if b == 0 { break; }
            buf.push(b);
        }
        // [2026-05-08 修复乱码] PDO 输入字符串走多编码兜底 (UTF-8/ASCII/Latin-1)
        crate::utils::help::decode_ethercat_string(&buf)
    }

    /// 写入字符串型 PDO 输出
    ///
    /// 通过逐字节写入 PDO 输出数据区实现字符串写入。
    ///
    /// # 参数
    /// - `offset`: 在输出数据区的字节偏移
    /// - `value`: 要写入的字符串
    /// - `max_len`: 最大字符串长度
    pub fn write_string(&self, offset: u32, value: &str, max_len: u32) -> bool {
        let bytes = value.as_bytes();
        let write_len = bytes.len().min(max_len as usize);
        for i in 0..write_len {
            let ret = unsafe {
                ffi::PDOWriteOutputU8(self.master_index, self.slave_index, offset + i as u32, bytes[i])
            };
            if ret == 0 { return false; }
        }
        // 写入终止符
        if write_len < max_len as usize {
            unsafe {
                ffi::PDOWriteOutputU8(self.master_index, self.slave_index, offset + write_len as u32, 0);
            };
        }
        true
    }

    // ===================== I64 PDO 读写 =====================

    /// 读取输入 i64 值
    ///
    /// 通过两次 i32 读取组合为 i64。
    pub fn read_input_i64(&self, offset: u32) -> i64 {
        let low = unsafe { ffi::PDOReadInputU32(self.master_index, self.slave_index, offset) };
        let high = unsafe { ffi::PDOReadInputU32(self.master_index, self.slave_index, offset + 4) };
        ((high as i64) << 32) | (low as i64)
    }

    /// 写入输出 i64 值
    ///
    /// 通过两次 i32 写入实现 i64 写入。
    pub fn write_output_i64(&self, offset: u32, value: i64) -> bool {
        let low = value as u32;
        let high = (value >> 32) as u32;
        unsafe {
            ffi::PDOWriteOutputU32(self.master_index, self.slave_index, offset, low);
            ffi::PDOWriteOutputU32(self.master_index, self.slave_index, offset + 4, high);
        }
        true
    }

    // ===================== 组周期分频器 =====================

    /// 获取组的周期分频器 (对应 C# GroupCycleDivider)
    ///
    /// 分频器值表示该组每 N 个主周期执行一次 PDO 交换。
    pub fn group_cycle_divider(&self, group: u8) -> u8 {
        unsafe { ffi::GetGroupCycleDivider(self.master_index, group) }
    }

    /// 设置组的周期分频器 (对应 C# SetGroupCycleDivider)
    ///
    /// # 参数
    /// - `group`: 组号
    /// - `divider`: 分频值 (1=每个周期, 2=每两个周期, ...)
    pub fn set_group_cycle_divider(&self, group: u8, divider: u8) -> bool {
        unsafe { ffi::SetGroupCycleDivider(self.master_index, group, divider) != 0 }
    }

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

    /// 获取 PDO 丢帧统计 (对应 C# FrameLossStats)
    ///
    /// # 参数
    /// - `group`: 组号
    ///
    /// # 返回
    /// (总丢帧数, 连续丢帧数, 最大连续丢帧数)
    pub fn frame_loss_stats(&self, group: u8) -> (u32, u32, u32) {
        let mut total_lost: u32 = 0;
        let mut consecutive_lost: u32 = 0;
        let mut max_consecutive_lost: u32 = 0;
        unsafe {
            ffi::GetPDOFrameLossStats(
                self.master_index, group,
                &mut total_lost, &mut consecutive_lost, &mut max_consecutive_lost,
            );
        }
        (total_lost, consecutive_lost, max_consecutive_lost)
    }

    /// 重置 PDO 丢帧统计
    pub fn reset_frame_loss_stats(&self, group: u8) {
        unsafe { ffi::ResetPDOFrameLossStats(self.master_index, group); }
    }
}