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
//! 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) }
    }
}