darra-ethercat-master 2.6.0

Commercial EtherCAT master protocol stack, real-time kernel driver integration, Windows and Linux support, multi-language SDKs, complex topology and hot-plug support.
Documentation

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};

#[derive(Debug, Clone)]
pub struct StartupParameter {

    pub index: u16,

    pub sub_index: u8,

    pub data: Vec<u8>,

    pub transition: StartupTransition,

    pub write_timing: StartupWriteTiming,

    pub priority: i32,

    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(),
        }
    }

    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);
    }

    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);
    }

    pub fn sync_to_dll(&self) -> Result<i32> {

        unsafe { ffi::ClearStartupParameters(self.master_index, self.slave_index) };

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

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

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

    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,
        StartupTransition::PS => StartupWriteTiming::BeforeTransition,
        StartupTransition::SO => StartupWriteTiming::BeforeTransition,
        StartupTransition::OS => StartupWriteTiming::AfterTransition,
        StartupTransition::SP => StartupWriteTiming::AfterTransition,
        StartupTransition::PI => StartupWriteTiming::BeforeTransition,
    }
}

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

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,
    }
}

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)",
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StartupAutoCapabilities {

    pub default_pdo_enumeration: bool,

    pub esi_auto_startup: bool,

    pub mdp_auto_startup: bool,

    pub sm_auto_configuration: bool,
}

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(),
        }
    }

    pub fn capabilities(&self) -> StartupAutoCapabilities {
        StartupAutoCapabilities {
            default_pdo_enumeration: true,
            esi_auto_startup: false,
            mdp_auto_startup: false,
            sm_auto_configuration: false,
        }
    }

    pub fn supports_full_auto_startup(&self) -> bool {
        let caps = self.capabilities();
        caps.esi_auto_startup || caps.mdp_auto_startup || caps.sm_auto_configuration
    }

    pub fn apply_full_auto_startup(&mut self) -> Result<i32> {
        Err(DarraError::Other(
            "Rust SDK 尚未公开完整 ESI/MDP/SM 自动启动执行能力; 请显式构造启动参数或调用默认 PDO 枚举".into(),
        ))
    }

    pub fn enumerate_default_pdo(&self, direction: i32, max_count: usize) -> Vec<u16> {
        self.enumerate_default_pdo_result(direction, max_count)
            .unwrap_or_default()
    }

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

    pub fn set_config_source_eni(&mut self) {
        self.config_source = "ENI".to_string();
    }

    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 }
}