darra-ethercat-master 2.0.2

商业 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.
//! 启动参数管理
//!
//! 提供 StartupParameter 和 StartupParameterList 结构体,
//! 封装从站启动参数 (SDO 写入配置) 的管理。
//!
//! 对应 C# `Slave/Startup.cs`。

use crate::data::error::{DarraError, Result};
use crate::utils::ffi::{self, StartupParam, TRANS_IP, TRANS_PS, TRANS_SO, TRANS_OS, TRANS_SP, TRANS_PI, TIMING_BEFORE, TIMING_AFTER};
use crate::data::types::{StartupTransition, StartupWriteTiming};

/// 启动参数 (单个 SDO 写入配置)
#[derive(Debug, Clone)]
pub struct StartupParameter {
    /// SDO 索引
    pub index: u16,
    /// SDO 子索引
    pub sub_index: u8,
    /// 写入数据
    pub data: Vec<u8>,
    /// 状态转换阶段
    pub transition: StartupTransition,
    /// 写入时机
    pub write_timing: StartupWriteTiming,
    /// 优先级 (数值越小越优先)
    pub priority: i32,
    /// 完整访问模式 (Complete Access)
    pub complete_access: bool,
    /// 描述信息
    pub description: String,
}

impl StartupParameter {
    /// 创建新的启动参数
    pub fn new(index: u16, sub_index: u8, data: Vec<u8>) -> Self {
        Self {
            index,
            sub_index,
            data,
            transition: StartupTransition::IP,
            write_timing: StartupWriteTiming::AfterTransition,
            priority: 100,
            complete_access: false,
            description: String::new(),
        }
    }

    /// 创建带转换阶段的启动参数
    pub fn with_transition(index: u16, sub_index: u8, data: Vec<u8>, transition: StartupTransition) -> Self {
        let timing = default_write_timing(transition);
        Self {
            index,
            sub_index,
            data,
            transition,
            write_timing: timing,
            priority: 100,
            complete_access: false,
            description: String::new(),
        }
    }

    /// 转换为 DLL FFI 结构体
    fn to_ffi(&self) -> StartupParam {
        let (trans, timing) = transition_to_ffi(self.transition, self.write_timing);
        let mut param = StartupParam {
            index: self.index,
            sub_index: self.sub_index,
            data: [0u8; 256],
            data_len: std::cmp::min(self.data.len(), 256) as u16,
            transition: trans,
            timing,
            complete_access: if self.complete_access { 1 } else { 0 },
            is_register_write: 0,
            priority: self.priority as u16,
        };
        let copy_len = std::cmp::min(self.data.len(), 256);
        param.data[..copy_len].copy_from_slice(&self.data[..copy_len]);
        param
    }
}

/// 启动参数列表
pub struct StartupParameterList {
    master_index: u16,
    slave_index: u16,
    params: Vec<StartupParameter>,
}

impl StartupParameterList {
    /// 创建启动参数列表 (内部使用)
    pub(crate) fn new(master_index: u16, slave_index: u16) -> Self {
        Self {
            master_index,
            slave_index,
            params: Vec::new(),
        }
    }

    /// 添加启动参数
    pub fn add(&mut self, param: StartupParameter) {
        self.params.push(param);
    }

    /// 添加 SDO 写入参数 (简便方法)
    pub fn add_sdo_write(
        &mut self,
        index: u16,
        sub_index: u8,
        data: Vec<u8>,
        transition: StartupTransition,
    ) {
        self.params.push(StartupParameter::with_transition(index, sub_index, data, transition));
    }

    /// 移除指定索引的参数
    pub fn remove(&mut self, idx: usize) -> Option<StartupParameter> {
        if idx < self.params.len() {
            Some(self.params.remove(idx))
        } else {
            None
        }
    }

    /// 清除所有参数
    pub fn clear(&mut self) {
        self.params.clear();
        unsafe { ffi::ClearStartupParameters(self.master_index, self.slave_index) };
    }

    /// 获取参数数量
    pub fn count(&self) -> usize {
        self.params.len()
    }

    /// 获取所有参数的引用
    pub fn params(&self) -> &[StartupParameter] {
        &self.params
    }

    /// 按优先级排序
    pub fn sort_by_priority(&mut self) {
        self.params.sort_by_key(|p| p.priority);
    }

    /// 同步到 DLL (批量写入所有参数到 DLL 层)
    pub fn sync_to_dll(&self) -> Result<i32> {
        // 先清除 DLL 层旧参数
        unsafe { ffi::ClearStartupParameters(self.master_index, self.slave_index) };

        if self.params.is_empty() {
            return Ok(0);
        }

        // 将参数转换为 FFI 结构并批量添加
        let ffi_params: Vec<StartupParam> = self.params.iter().map(|p| p.to_ffi()).collect();
        let ret = unsafe {
            ffi::AddStartupParameterBatch(
                self.master_index,
                self.slave_index,
                ffi_params.as_ptr(),
                ffi_params.len() as i32,
            )
        };

        if ret >= 0 {
            Ok(ret)
        } else {
            Err(DarraError::Other(format!("同步启动参数到 DLL 失败 (返回码: {})", ret)))
        }
    }

    /// 单独添加一个参数到 DLL
    pub fn apply_single(&self, param: &StartupParameter) -> Result<()> {
        let ffi_param = param.to_ffi();
        let ret = unsafe {
            ffi::AddStartupParameter(self.master_index, self.slave_index, &ffi_param)
        };
        if ret >= 0 {
            Ok(())
        } else {
            Err(DarraError::Other(format!("添加启动参数失败 (返回码: {})", ret)))
        }
    }

    /// 执行指定转换阶段和时机的启动参数
    pub fn apply(&self, transition: u8, timing: u8) -> Result<i32> {
        let ret = unsafe {
            ffi::DarraCoreInvoke(
                self.master_index,
                ffi::CORE_OP_26,
                self.slave_index as u32,
                1u32 << transition,
                1u32 << timing)
        };
        if ret >= 0 {
            Ok(ret)
        } else {
            Err(DarraError::Other(format!("执行启动参数失败 (返回码: {})", ret)))
        }
    }

    /// 获取 DLL 层当前的启动参数数量
    pub fn dll_count(&self) -> i32 {
        unsafe { ffi::GetStartupParameterCount(self.master_index, self.slave_index) }
    }
}

// ===================== 辅助函数 =====================

/// 获取状态转换的默认写入时机
pub fn default_write_timing(transition: StartupTransition) -> StartupWriteTiming {
    match transition {
        StartupTransition::IP => StartupWriteTiming::AfterTransition,  // Init 无邮箱, 必须等 PreOp
        StartupTransition::PS => StartupWriteTiming::BeforeTransition, // PDO 配置在 SafeOp 前
        StartupTransition::SO => StartupWriteTiming::BeforeTransition,
        StartupTransition::OS => StartupWriteTiming::AfterTransition,
        StartupTransition::SP => StartupWriteTiming::AfterTransition,
        StartupTransition::PI => StartupWriteTiming::BeforeTransition,
    }
}

/// 将 StartupTransition + StartupWriteTiming 转换为 DLL 参数
fn transition_to_ffi(transition: StartupTransition, timing: StartupWriteTiming) -> (u8, u8) {
    let trans = match transition {
        StartupTransition::IP => TRANS_IP,
        StartupTransition::PS => TRANS_PS,
        StartupTransition::SO => TRANS_SO,
        StartupTransition::OS => TRANS_OS,
        StartupTransition::SP => TRANS_SP,
        StartupTransition::PI => TRANS_PI,
    };
    let time = match timing {
        StartupWriteTiming::BeforeTransition => TIMING_BEFORE,
        StartupWriteTiming::AfterTransition => TIMING_AFTER,
    };
    (trans, time)
}

/// 从字符串解析 StartupTransition
pub fn parse_transition(s: &str) -> StartupTransition {
    match s.trim().to_uppercase().as_str() {
        "IP" => StartupTransition::IP,
        "PS" => StartupTransition::PS,
        "SO" => StartupTransition::SO,
        "OS" => StartupTransition::OS,
        "SP" => StartupTransition::SP,
        "PI" | "SI" => StartupTransition::PI,
        _ => StartupTransition::IP,
    }
}

/// 将 StartupTransition 转换为字符串
pub fn transition_to_string(transition: StartupTransition) -> &'static str {
    match transition {
        StartupTransition::IP => "IP",
        StartupTransition::PS => "PS",
        StartupTransition::SO => "SO",
        StartupTransition::OS => "OS",
        StartupTransition::SP => "SP",
        StartupTransition::PI => "PI",
    }
}

/// 获取状态转换的中文描述
pub fn transition_description(transition: StartupTransition) -> &'static str {
    match transition {
        StartupTransition::IP => "I>P (Init\u{2192}PreOp)",
        StartupTransition::PS => "P>S (PreOp\u{2192}SafeOp)",
        StartupTransition::SO => "S>O (SafeOp\u{2192}Op)",
        StartupTransition::OS => "O>S (Op\u{2192}SafeOp)",
        StartupTransition::SP => "S>P (SafeOp\u{2192}PreOp)",
        StartupTransition::PI => "P>I (PreOp\u{2192}Init)",
    }
}

// ===================== 自动启动配置 =====================

/// 自动启动配置能力声明.
///
/// `true` 只表示 Rust SDK 当前公开层能直接调用该能力; `false` 表示尚未公开为可执行 API,
/// 调用方不能把按钮或配置来源标记当作已经完成自动配置.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StartupAutoCapabilities {
    /// 可通过 native 枚举默认 PDO Assignment.
    pub default_pdo_enumeration: bool,
    /// 可完整执行 ESI 自动启动参数生成/下发.
    pub esi_auto_startup: bool,
    /// 可完整执行 MDP 自动启动参数生成/下发.
    pub mdp_auto_startup: bool,
    /// 可完整执行 SM 自动配置.
    pub sm_auto_configuration: bool,
}

/// 启动参数自动配置器
///
/// Rust SDK 当前只公开默认 PDO 枚举和启动参数队列写入; ESI/MDP/SM 一键自动配置未公开为可执行 API.
pub struct StartupAutoConfig {
    master_index: u16,
    slave_index: u16,
    has_executed: bool,
    config_source: String,
}

impl StartupAutoConfig {
    /// 创建自动配置器
    pub fn new(master_index: u16, slave_index: u16) -> Self {
        Self {
            master_index,
            slave_index,
            has_executed: false,
            config_source: String::new(),
        }
    }

    /// 查询 Rust SDK 当前真实公开的自动启动能力.
    pub fn capabilities(&self) -> StartupAutoCapabilities {
        StartupAutoCapabilities {
            default_pdo_enumeration: true,
            esi_auto_startup: false,
            mdp_auto_startup: false,
            sm_auto_configuration: false,
        }
    }

    /// 是否支持完整 ESI/MDP/SM 自动配置执行.
    pub fn supports_full_auto_startup(&self) -> bool {
        let caps = self.capabilities();
        caps.esi_auto_startup || caps.mdp_auto_startup || caps.sm_auto_configuration
    }

    /// 完整自动配置尚未在 Rust SDK 公开.
    ///
    /// 调用方应使用 `enumerate_default_pdo_result` 获取真实可用的 PDO 枚举能力,
    /// 或显式构造 `StartupParameterList` 后调用 `sync_to_dll`/`apply`.
    pub fn apply_full_auto_startup(&mut self) -> Result<i32> {
        Err(DarraError::Other(
            "Rust SDK 尚未公开完整 ESI/MDP/SM 自动启动执行能力; 请显式构造启动参数或调用默认 PDO 枚举".into(),
        ))
    }

    /// 枚举从站默认 PDO Assignment (协议算法下沉到 native, 见 default_pdo.c, 2026-05-02)
    ///
    /// 从从站对象字典读取当前 PDO 索引列表 (RxPDO 或 TxPDO).
    /// SDK 公开层不再循环 SDORead PDO Assignment SubIndex; 由 native 处理协议细节.
    ///
    /// # 参数
    /// * `direction` - 0=RxPDO (master→slave 输出), 1=TxPDO (slave→master 输入)
    /// * `max_count` - 最多枚举的 PDO 数量
    ///
    /// # 返回
    /// PDO 索引列表 (e.g. `[0x1600, 0x1601, ...]`); 失败返回空 Vec
    pub fn enumerate_default_pdo(&self, direction: i32, max_count: usize) -> Vec<u16> {
        self.enumerate_default_pdo_result(direction, max_count)
            .unwrap_or_default()
    }

    /// 枚举从站默认 PDO Assignment, 失败时返回明确错误.
    pub fn enumerate_default_pdo_result(&self, direction: i32, max_count: usize) -> Result<Vec<u16>> {
        if direction != 0 && direction != 1 {
            return Err(DarraError::InvalidParameter("direction 必须是 0(RxPDO) 或 1(TxPDO)".into()));
        }
        if max_count == 0 {
            return Ok(Vec::new());
        }
        let mut buf: Vec<u16> = vec![0u16; max_count];
        let n = unsafe {
            ffi::EnumerateDefaultPdo(
                self.master_index,
                self.slave_index,
                direction as std::os::raw::c_int,
                buf.as_mut_ptr(),
                max_count as std::os::raw::c_int,
            )
        };
        if n < 0 {
            return Err(DarraError::PdoFailed(format!("默认 PDO 枚举失败 (返回码: {})", n)));
        }
        if n == 0 {
            return Ok(Vec::new());
        }
        let n = (n as usize).min(max_count);
        buf.truncate(n);
        Ok(buf)
    }

    /// 标记已从 ENI 加载配置
    pub fn set_config_source_eni(&mut self) {
        self.config_source = "ENI".to_string();
    }

    /// 标记已从 DENI 加载配置
    pub fn set_config_source_deni(&mut self) {
        self.config_source = "DENI".to_string();
    }

    /// 是否已执行过自动配置
    pub fn has_executed(&self) -> bool { self.has_executed }

    /// 配置来源
    pub fn config_source(&self) -> &str { &self.config_source }
}