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
//! DC (分布式时钟) 从站级配置
//!
//! 提供从站级别的 DC 配置方法, 包括:
//! - ConfigureDC: 配置 SYNC0/SYNC1 信号
//! - DisableDC: 禁用 DC 同步
//! - 传播延迟、同步窗口状态查询
//!
//! 主站级 DC 方法已在 `master.rs` 中实现。
use crate::data::error::{SyncWindowStatus, Result};
use crate::utils::ffi;
use crate::data::types::DcSyncMode;
/// 从站 DC 配置器
///
/// 通过 `Slave::dc()` 获取实例。
pub struct SlaveDC {
master_index: u16,
slave_index: u16,
}
impl SlaveDC {
/// 创建从站 DC 配置器 (内部使用)
pub(crate) fn new(master_index: u16, slave_index: u16) -> Self {
Self { master_index, slave_index }
}
/// 配置 DC 同步 (参数单位: 纳秒) — 对齐 C# `ConfigureDC` (返回 bool)
///
/// Rust 惯例使用 `Result<(), DarraError>` 替代 bool. `Ok(())` = 派发成功,
/// `Err(DarraError::InvalidParameter)` = 参数非法, 其他错误包装为 `DarraError::Other`.
/// 注意: 底层 `SetSyncBySlaveIndex` 无返回码, 真实硬件错误状态需通过 `Master::last_error()` 查询.
///
/// - `sync0_ns`: SYNC0 周期 (纳秒)
/// - `sync1_ns`: SYNC1 周期 (纳秒), 0 表示禁用
/// - `shift_ns`: 相位偏移 (纳秒), None=自动计算, Some(0)=不设偏移
pub fn configure(&self, sync0_ns: u32, sync1_ns: u32, shift_ns: Option<i32>) -> Result<()> {
let actual_shift = match shift_ns {
Some(s) => s,
None => {
// 自动计算偏移: MaxPropagationDelay - SlavePropagationDelay
unsafe { ffi::UpdatePropagationDelays(self.master_index) };
let max_delay = unsafe { ffi::GetMaxPropagationDelay(self.master_index) };
let slave_delay = unsafe { ffi::GetSlavePropagationDelay(self.master_index, self.slave_index) };
if max_delay > 0 { max_delay - slave_delay } else { 0 }
}
};
unsafe {
ffi::SetSyncBySlaveIndex(self.master_index, self.slave_index, sync0_ns, sync1_ns, actual_shift);
}
Ok(())
}
/// 根据同步模式配置 DC (ETG.1020)
///
/// - `FreeRun`: 禁用 SYNC0/SYNC1, 设置 SM 同步类型为 0x0000
/// - `SmSynchron`: 禁用 SYNC0/SYNC1, 设置 SM 同步类型为 0x0001
/// - `DcSynchron`: 仅 SYNC0 信号, 设置 SM 同步类型为 0x0002
/// - `DcSynchron01`: SYNC0 + SYNC1 信号, 设置 SM 同步类型为 0x0003
///
/// 每种模式都会通过 CoE SDO 写入 0x1C32:01 和 0x1C33:01 配置 SM 同步类型 (ETG.1020 §23.1.2)
///
/// 对齐 C# `ConfigureDC(DcSyncMode, ...)` 返回 bool — Rust 惯例用 `Result<(), DarraError>`.
pub fn configure_mode(&self, mode: DcSyncMode, sync0_ns: u32, sync1_ns: u32, shift_ns: Option<i32>) -> Result<()> {
match mode {
DcSyncMode::FreeRun => {
unsafe {
ffi::SetSyncBySlaveIndex(self.master_index, self.slave_index, 0, 0, 0);
}
self.configure_sm_sync_type(DcSyncMode::FreeRun);
Ok(())
}
DcSyncMode::SmSynchron => {
unsafe {
ffi::SetSyncBySlaveIndex(self.master_index, self.slave_index, 0, 0, 0);
}
self.configure_sm_sync_type(DcSyncMode::SmSynchron);
Ok(())
}
DcSyncMode::DcSynchron => {
// DC-Synchron: 仅 SYNC0 信号
self.configure(sync0_ns, 0, shift_ns)?;
self.configure_sm_sync_type(DcSyncMode::DcSynchron);
Ok(())
}
DcSyncMode::DcSynchron01 => {
// DC-Synchron01: SYNC0 + SYNC1 信号
self.configure(sync0_ns, sync1_ns, shift_ns)?;
self.configure_sm_sync_type(DcSyncMode::DcSynchron01);
Ok(())
}
}
}
/// 通过 native 接口配置从站 SM 同步类型 (协议算法已下沉到 Darra.Core.dll).
///
/// SDO 0x1C32:01 / 0x1C33:01 编排在 native dc_sync_mode.c 中执行,
/// SDK 公开代码不再出现 ETG.1020 §23.1.2 协议数字.
fn configure_sm_sync_type(&self, mode: DcSyncMode) {
let sync_type: u8 = match mode {
DcSyncMode::FreeRun => 0,
DcSyncMode::SmSynchron => 1,
DcSyncMode::DcSynchron => 2,
DcSyncMode::DcSynchron01 => 3,
};
unsafe {
// 失败 (0) 仅由从站不支持 / 主站未初始化触发, 与 6 SDK 原行为一致, 静默忽略
let _ = ffi::SetDcSyncMode(self.master_index, self.slave_index, sync_type);
}
}
/// 禁用 DC 同步
pub fn disable(&self) {
unsafe {
ffi::SetSyncBySlaveIndex(self.master_index, self.slave_index, 0, 0, 0);
}
}
/// 获取从站传播延迟 (纳秒)
pub fn propagation_delay(&self) -> i32 {
unsafe { ffi::GetSlavePropagationDelay(self.master_index, self.slave_index) }
}
/// 从站是否启用 DC (ESC 硬件 bit, 对齐 C# `Slave.HasDC`).
///
/// 与 `slave/core.rs` 的 `Slave::has_dc()` 同源 (读 `EcSlaveBlittable.topo.has_dc`),
/// 此处提供 `SlaveDC` 命名空间下的便捷入口. FFI 未导出独立的
/// `GetSlaveHasDC`, 通过 `GetSlave` 读结构体实现 (同 slave::core).
pub fn has_dc(&self) -> bool {
let ptr = unsafe { ffi::GetSlave(self.master_index, self.slave_index) };
if ptr.is_null() { return false; }
let s = unsafe { std::ptr::read_unaligned(ptr as *const ffi::EcSlaveBlittable) };
s.topo.has_dc != 0
}
/// ESI 是否声明此从站使用 DC 同步 (对齐 C# `Slave.HasEsiDcSync`).
///
/// `has_dc()` 仅反映 ESC 硬件 bit, 不代表应用层使用 DC. 启用 DC
/// 前应同时检查 `has_dc() && has_esi_dc_sync()`, 用于过滤例如
/// GCAN-8200 这类 ESC 报 DC capable 但 ESI 未声明 DC OpMode 的从站,
/// 避免强写 DCCUC=0x0300 卡在 PreOP→SafeOP 过渡.
///
/// # 路由优先级 (managed 实装)
/// 1. FFI `GetSlaveHasEsiDcSync` (DLL 端 ESI 解析) — 解析到则用 DLL
/// 2. Managed: 遍历 [`crate::utils::esi::EsiManager`] 缓存的 ESI 文件,
/// 匹配 `<VendorId>` + `<ProductCode>`, 解析 `<Dc>/<OpMode>/<AssignActivate>`,
/// 任一 OpMode `AssignActivate != 0` 即视为声明 DC 同步.
/// 3. 兜底: `has_dc()` (ESC 硬件 bit, 保守)
pub fn has_esi_dc_sync(&self) -> bool {
// 1) FFI 实装
if let Some(f) = ffi::dynamic_ffi::ffi_gap().get_slave_has_esi_dc_sync {
return unsafe { f(self.master_index, self.slave_index) != 0 };
}
// 2) Managed: 解析 EsiManager 缓存的 ESI 文件, 按 VendorId/ProductCode 匹配
// Phase 2-E: vendor/product 已收敛进 metadata.identity
let want_vid = unsafe {
let p = ffi::GetSlave(self.master_index, self.slave_index);
if p.is_null() { 0u32 } else {
std::ptr::read_unaligned(p as *const ffi::EcSlaveBlittable).metadata.identity.vendor_id
}
};
let want_pid = unsafe {
let p = ffi::GetSlave(self.master_index, self.slave_index);
if p.is_null() { 0u32 } else {
std::ptr::read_unaligned(p as *const ffi::EcSlaveBlittable).metadata.identity.product_id
}
};
if want_vid != 0 || want_pid != 0 {
for file in crate::utils::esi::EsiManager::get_files() {
if let Ok(info) = crate::slave::esi::load_esi_file(&file) {
if info.vendor_id == want_vid && info.product_id == want_pid {
if let Some(dc) = info.dc_configuration.as_ref() {
// 任一 OpMode AssignActivate != 0 即视为支持 DC 同步
if dc.op_modes.iter().any(|m| m.assign_activate != 0) {
return true;
}
}
// 找到匹配但 ESI 未声明 DC OpMode → false
return false;
}
}
}
}
// 3) 兜底: ESC 硬件 bit (与 C# 注释保持保守一致)
self.has_dc()
}
/// 获取从站同步窗口状态
pub fn sync_window_status(&self) -> Option<SyncWindowStatus> {
let mut diff_ns: i32 = 0;
let mut max_diff_ns: i32 = 0;
let mut min_diff_ns: i32 = 0;
let mut in_sync: i32 = 0;
let mut out_of_sync_count: u32 = 0;
let ret = unsafe {
ffi::GetSlaveSyncWindowStatus(
self.master_index, self.slave_index,
&mut diff_ns, &mut max_diff_ns, &mut min_diff_ns,
&mut in_sync, &mut out_of_sync_count,
)
};
if ret != 0 {
Some(SyncWindowStatus {
diff_ns,
max_diff_ns,
min_diff_ns,
in_sync: in_sync != 0,
out_of_sync_count,
})
} else {
None
}
}
/// 重置同步窗口统计
pub fn reset_sync_window_stats(&self) {
unsafe { ffi::ResetSlaveSyncWindowStats(self.master_index, self.slave_index) };
}
/// 获取当前 DC 同步模式 (协议算法下沉到 Darra.Core.dll).
///
/// 通过 native `GetDcSyncMode` (内部读 0x1C32:01) 判断真实同步模式.
/// 读失败 / 不支持时返回 `FreeRun` (与 SDK 原行为一致).
pub fn current_dc_sync_mode(&self) -> DcSyncMode {
let mode = unsafe { ffi::GetDcSyncMode(self.master_index, self.slave_index) };
match mode {
1 => DcSyncMode::SmSynchron,
2 => DcSyncMode::DcSynchron,
3 => DcSyncMode::DcSynchron01,
_ => DcSyncMode::FreeRun,
}
}
}
// ===================== 主站级 DC 管理 =====================
/// 主站级 DC 管理器
///
/// 提供主站级别的 DC 功能,包括持续测量、漂移补偿、
/// 全局同步状态查询等。
/// 对应 C# DarraEtherCAT (master-level) DC 方法。
pub struct MasterDC {
master_index: u16,
}
impl MasterDC {
/// 创建主站 DC 管理器
pub fn new(master_index: u16) -> Self {
Self { master_index }
}
/// 启用/禁用持续传播延迟测量 (ETG.1500 5.13.2)
///
/// 对应 C# EnableContinuousMeasurement
///
/// # 参数
/// - `enabled`: 是否启用
/// - `interval_sec`: 测量间隔 (秒), 0 表示使用默认值
pub fn enable_continuous_measurement(&self, enabled: bool, interval_sec: u32) {
unsafe {
ffi::EnableContinuousMeasurement(
self.master_index,
if enabled { 1 } else { 0 },
interval_sec,
);
}
}
/// 启用/禁用 DC 漂移补偿
///
/// 对应 C# EnableDriftCompensation
///
/// 注意: 大多数情况下 ESC 硬件自动处理时钟同步, 无需手动补偿。
/// 仅当观察到持续漂移且 ESC 内置补偿不足时才启用。
///
/// # 参数
/// - `enabled`: 是否启用
/// - `threshold_ns`: 漂移阈值 (纳秒), 超过此值才进行补偿, 默认 1000
/// - `gain`: 比例增益 (0-1024, 表示 0.0-1.0), 默认 512 (0.5)
pub fn enable_drift_compensation(&self, enabled: bool, threshold_ns: i32, gain: i32) {
unsafe {
ffi::EnableDriftCompensation(
self.master_index,
if enabled { 1 } else { 0 },
threshold_ns,
gain,
);
}
}
/// 获取所有 DC 从站的最大时间差 (纳秒)
///
/// 对应 C# GetMaxSyncDifference
pub fn get_max_sync_difference(&self) -> i32 {
unsafe { ffi::GetMaxSyncDifference(self.master_index) }
}
/// 检查所有 DC 从站是否都在同步窗口内
///
/// 对应 C# IsAllSlavesInSync
pub fn is_all_slaves_in_sync(&self) -> bool {
unsafe { ffi::IsAllSlavesInSync(self.master_index) != 0 }
}
/// 重置所有从站的同步窗口统计
///
/// 对应 C# ResetAllSyncWindowStats
/// 传入 slave_index=0 表示重置所有从站
pub fn reset_all_sync_window_stats(&self) {
unsafe { ffi::ResetSlaveSyncWindowStats(self.master_index, 0); }
}
/// 设置同步窗口阈值 (纳秒)
pub fn set_sync_window_threshold(&self, threshold_ns: i32) {
unsafe { ffi::SetSyncWindowThreshold(self.master_index, threshold_ns); }
}
/// 获取同步窗口阈值 (纳秒)
pub fn get_sync_window_threshold(&self) -> i32 {
unsafe { ffi::GetSyncWindowThreshold(self.master_index) }
}
/// 启用/禁用 DC 自动偏移计算
pub fn set_auto_shift_enabled(&self, enabled: bool) {
unsafe { ffi::SetDCAutoShiftEnabled(self.master_index, if enabled { 1 } else { 0 }); }
}
/// 获取 DC 自动偏移是否启用
pub fn get_auto_shift_enabled(&self) -> bool {
unsafe { ffi::GetDCAutoShiftEnabled(self.master_index) != 0 }
}
/// 主站最近一次 PDO 周期从参考时钟从站 FRMW 取回的 64-bit DC 系统时间.
///
/// 单位纳秒, 纪元 2000-01-01 00:00 UTC, 对齐 C# `DLL.GetMasterDCTime` /
/// Java `getMasterDCTime` / Python `master_dc_time`.
///
/// 与 `master/core.rs` 的 `Master::master_dc_time()` 同源, 此处提供
/// `MasterDC` 命名空间下的便捷入口. 未启用 DC 时返回 0.
pub fn master_dc_time(&self) -> u64 {
let t = unsafe { ffi::GetMasterDCTime(self.master_index) };
if t < 0 { 0 } else { t as u64 }
}
/// 当前参考时钟从站索引 (1-based).
///
/// 对齐 C# `DLL.GetReferenceClockSlaveIndex` / Java
/// `getReferenceClockSlaveIndex` / Python `reference_clock_slave_index`.
/// 由 DLL 在 configdc 阶段自动选为第一个 DC-capable 从站, 用户
/// 无需配置. 返回 0 表示网络中无 DC 从站.
pub fn reference_clock_slave_index(&self) -> u16 {
unsafe { ffi::GetReferenceClockSlaveIndex(self.master_index) }
}
}