use std::ffi::CStr;
use std::os::raw::c_char;
use crate::utils::ffi;
fn cstr_to_string(p: *const c_char, fallback: &str) -> String {
if p.is_null() {
return fallback.to_string();
}
unsafe { CStr::from_ptr(p).to_string_lossy().into_owned() }
}
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AlSeverity {
None = 0,
Info = 1,
Warning = 2,
Error = 3,
Fatal = 4,
}
impl AlSeverity {
pub fn from_i32(v: i32) -> Self {
match v {
0 => Self::None,
1 => Self::Info,
2 => Self::Warning,
3 => Self::Error,
_ => Self::Fatal,
}
}
}
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SdoAbortCategory {
Transfer = 0,
Access = 1,
Value = 2,
Hardware = 3,
General = 4,
}
impl SdoAbortCategory {
pub fn from_i32(v: i32) -> Self {
match v {
0 => Self::Transfer,
1 => Self::Access,
2 => Self::Value,
3 => Self::Hardware,
_ => Self::General,
}
}
}
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EmcyClass {
NoError = 0x0000,
Generic = 0x1000,
Current = 0x2000,
Voltage = 0x3000,
Temperature = 0x4000,
Hardware = 0x5000,
Software = 0x6000,
AdditionalModules = 0x7000,
Monitoring = 0x8000,
External = 0x9000,
AdditionalFunc = 0xF000,
DeviceSpecific = 0xFF00,
}
impl EmcyClass {
pub fn from_i32(v: i32) -> Self {
match v {
0x0000 => Self::NoError,
0x1000 => Self::Generic,
0x2000 => Self::Current,
0x3000 => Self::Voltage,
0x4000 => Self::Temperature,
0x5000 => Self::Hardware,
0x6000 => Self::Software,
0x7000 => Self::AdditionalModules,
0x8000 => Self::Monitoring,
0x9000 => Self::External,
0xF000 => Self::AdditionalFunc,
_ => Self::DeviceSpecific,
}
}
}
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EcTransitionType {
Invalid = 0,
Up = 1,
Down = 2,
Same = 3,
Bootstrap = 4,
JumpUp = 5,
JumpDown = 6,
}
impl EcTransitionType {
pub fn from_i32(v: i32) -> Self {
match v {
1 => Self::Up,
2 => Self::Down,
3 => Self::Same,
4 => Self::Bootstrap,
5 => Self::JumpUp,
6 => Self::JumpDown,
_ => Self::Invalid,
}
}
}
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EcTopology {
Unknown = 0,
Line = 1,
Pass = 2,
Branch = 3,
Cross = 4,
}
impl EcTopology {
pub fn from_i32(v: i32) -> Self {
match v {
1 => Self::Line,
2 => Self::Pass,
3 => Self::Branch,
4 => Self::Cross,
_ => Self::Unknown,
}
}
}
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EcDeviceType {
Unknown = 0,
Coupler = 1,
Terminal = 2,
Drive = 3,
Sensor = 4,
Gateway = 5,
Safety = 6,
Controller = 7,
}
impl EcDeviceType {
pub fn from_i32(v: i32) -> Self {
match v {
1 => Self::Coupler,
2 => Self::Terminal,
3 => Self::Drive,
4 => Self::Sensor,
5 => Self::Gateway,
6 => Self::Safety,
7 => Self::Controller,
_ => Self::Unknown,
}
}
}
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HomingTrigger {
NegativeLimit = 0,
PositiveLimit = 1,
HomeSwitch = 2,
IndexPulse = 3,
HardStop = 4,
CurrentPos = 5,
Vendor = 6,
}
impl HomingTrigger {
pub fn from_i32(v: i32) -> Self {
match v {
0 => Self::NegativeLimit,
1 => Self::PositiveLimit,
2 => Self::HomeSwitch,
3 => Self::IndexPulse,
4 => Self::HardStop,
5 => Self::CurrentPos,
_ => Self::Vendor,
}
}
}
pub struct ProtocolCodes;
impl ProtocolCodes {
pub fn al_status_description(code: u16) -> String {
unsafe { cstr_to_string(ffi::AL_StatusCode_GetDescription(code), "未知错误") }
}
pub fn al_status_severity(code: u16) -> AlSeverity {
unsafe { AlSeverity::from_i32(ffi::AL_StatusCode_GetSeverity(code)) }
}
pub fn al_status_recovery_hint(code: u16) -> String {
unsafe { cstr_to_string(ffi::AL_StatusCode_GetRecoveryHint(code), "请联系厂商") }
}
pub fn al_status_is_vendor_specific(code: u16) -> bool {
unsafe { ffi::AL_StatusCode_IsVendorSpecific(code) != 0 }
}
pub fn sdo_abort_description(abort_code: u32) -> String {
unsafe { cstr_to_string(ffi::SDOAbort_GetDescription(abort_code), "未知中止码") }
}
pub fn sdo_abort_category(abort_code: u32) -> SdoAbortCategory {
unsafe { SdoAbortCategory::from_i32(ffi::SDOAbort_GetCategory(abort_code)) }
}
pub fn sdo_abort_is_retryable(abort_code: u32) -> bool {
unsafe { ffi::SDOAbort_IsRetryable(abort_code) != 0 }
}
pub fn sdo_abort_hint(abort_code: u32) -> String {
unsafe { cstr_to_string(ffi::SDOAbort_GetHint(abort_code), "请检查从站配置") }
}
pub fn emcy_description(error_code: u16) -> String {
unsafe { cstr_to_string(ffi::EmcyCode_GetDescription(error_code), "未知紧急错误") }
}
pub fn emcy_class(error_code: u16) -> EmcyClass {
unsafe { EmcyClass::from_i32(ffi::EmcyCode_GetClass(error_code)) }
}
pub fn emcy_class_name(class_code: EmcyClass) -> String {
unsafe { cstr_to_string(ffi::EmcyCode_GetClassName(class_code as i32), "未知大类") }
}
pub fn emcy_format_error_register(error_register: u8) -> String {
let mut buf = vec![0u8; 128];
unsafe {
ffi::EmcyCode_FormatErrorRegister(error_register, buf.as_mut_ptr(), buf.len() as i32);
}
let len = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
String::from_utf8_lossy(&buf[..len]).into_owned()
}
pub fn emcy_is_recovery(error_code: u16) -> bool {
unsafe { ffi::EmcyCode_IsRecovery(error_code) != 0 }
}
pub fn ec_state_is_valid_transition(from: u16, to: u16) -> bool {
unsafe { ffi::EcState_IsValidTransition(from, to) != 0 }
}
pub fn ec_state_transition_type(from: u16, to: u16) -> EcTransitionType {
unsafe { EcTransitionType::from_i32(ffi::EcState_GetTransitionType(from, to)) }
}
pub fn ec_state_transition_path(from: u16, to: u16) -> Vec<u16> {
let mut buf = [0u16; 8];
let n =
unsafe { ffi::EcState_GetTransitionPath(from, to, buf.as_mut_ptr(), buf.len() as i32) };
if n <= 0 {
return Vec::new();
}
buf[..(n as usize).min(buf.len())].to_vec()
}
pub fn ec_state_is_bootstrap_required(state: u16) -> bool {
unsafe { ffi::EcState_IsBootstrapRequired(state) != 0 }
}
pub fn ec_state_name(state: u16) -> String {
unsafe { cstr_to_string(ffi::EcState_GetName(state), "未知状态") }
}
pub fn ec_state_name_en(state: u16) -> String {
unsafe { cstr_to_string(ffi::EcState_GetNameEn(state), "Unknown") }
}
pub fn ec_state_has_error_ack(state: u16) -> bool {
unsafe { ffi::EcState_HasErrorAck(state) != 0 }
}
pub fn ec_state_strip_error_ack(state: u16) -> u16 {
unsafe { ffi::EcState_StripErrorAck(state) }
}
pub fn pdo_data_type_bit_size(dt: u16) -> i32 {
unsafe { ffi::EcPdoCodec_DataTypeBitSize(dt as i32) }
}
pub fn pdo_data_type_name(dt: u16) -> String {
unsafe { cstr_to_string(ffi::EcPdoCodec_DataTypeName(dt as i32), "Unknown") }
}
pub fn pdo_extract_u64(src: &[u8], bit_offset: i32, bit_length: i32) -> Option<u64> {
let mut out: u64 = 0;
let r = unsafe {
ffi::EcPdoCodec_ExtractU64(
src.as_ptr(),
src.len() as i32,
bit_offset,
bit_length,
&mut out,
)
};
if r == 0 {
Some(out)
} else {
None
}
}
pub fn pdo_extract_i64(src: &[u8], bit_offset: i32, bit_length: i32) -> Option<i64> {
let mut out: i64 = 0;
let r = unsafe {
ffi::EcPdoCodec_ExtractI64(
src.as_ptr(),
src.len() as i32,
bit_offset,
bit_length,
&mut out,
)
};
if r == 0 {
Some(out)
} else {
None
}
}
pub fn pdo_insert_u64(dst: &mut [u8], bit_offset: i32, bit_length: i32, value: u64) -> bool {
let n = dst.len() as i32;
unsafe { ffi::EcPdoCodec_InsertU64(dst.as_mut_ptr(), n, bit_offset, bit_length, value) == 0 }
}
pub fn pdo_insert_i64(dst: &mut [u8], bit_offset: i32, bit_length: i32, value: i64) -> bool {
let n = dst.len() as i32;
unsafe { ffi::EcPdoCodec_InsertI64(dst.as_mut_ptr(), n, bit_offset, bit_length, value) == 0 }
}
pub fn pdo_extract_real32(src: &[u8], bit_offset: i32) -> Option<f32> {
let mut out: f32 = 0.0;
let r = unsafe {
ffi::EcPdoCodec_ExtractReal32(src.as_ptr(), src.len() as i32, bit_offset, &mut out)
};
if r == 0 {
Some(out)
} else {
None
}
}
pub fn pdo_extract_real64(src: &[u8], bit_offset: i32) -> Option<f64> {
let mut out: f64 = 0.0;
let r = unsafe {
ffi::EcPdoCodec_ExtractReal64(src.as_ptr(), src.len() as i32, bit_offset, &mut out)
};
if r == 0 {
Some(out)
} else {
None
}
}
pub fn pdo_insert_real32(dst: &mut [u8], bit_offset: i32, value: f32) -> bool {
let n = dst.len() as i32;
unsafe { ffi::EcPdoCodec_InsertReal32(dst.as_mut_ptr(), n, bit_offset, value) == 0 }
}
pub fn pdo_insert_real64(dst: &mut [u8], bit_offset: i32, value: f64) -> bool {
let n = dst.len() as i32;
unsafe { ffi::EcPdoCodec_InsertReal64(dst.as_mut_ptr(), n, bit_offset, value) == 0 }
}
pub fn pdo_count_active_ports(active_ports: u8) -> i32 {
unsafe { ffi::EcPdoCodec_CountActivePorts(active_ports) }
}
pub fn pdo_get_topology(active_ports: u8) -> EcTopology {
unsafe { EcTopology::from_i32(ffi::EcPdoCodec_GetTopology(active_ports)) }
}
pub fn pdo_topology_name(topo: EcTopology) -> String {
unsafe { cstr_to_string(ffi::EcPdoCodec_GetTopologyName(topo as i32), "未知") }
}
pub fn pdo_topology_name_en(topo: EcTopology) -> String {
unsafe { cstr_to_string(ffi::EcPdoCodec_GetTopologyNameEn(topo as i32), "Unknown") }
}
pub fn pdo_is_port_active(active_ports: u8, port: i32) -> bool {
unsafe { ffi::EcPdoCodec_IsPortActive(active_ports, port) != 0 }
}
pub fn cia402_mode_to_supported_bit(mode: i8) -> i32 {
unsafe { ffi::CiA402Modes_ModeToSupportedBit(mode) }
}
pub fn cia402_is_mode_supported(mask: u32, mode: i8) -> bool {
unsafe { ffi::CiA402Modes_IsModeSupportedInMask(mask, mode) != 0 }
}
pub fn cia402_expand_supported_mask(mask: u32) -> Vec<i8> {
let mut buf = [0i8; 16];
let n =
unsafe { ffi::CiA402Modes_ExpandSupportedMask(mask, buf.as_mut_ptr(), buf.len() as i32) };
if n <= 0 {
return Vec::new();
}
buf[..(n as usize).min(buf.len())].to_vec()
}
pub fn cia402_mode_name(mode: i8) -> String {
unsafe { cstr_to_string(ffi::CiA402Modes_GetModeName(mode), "未知模式") }
}
pub fn cia402_mode_name_en(mode: i8) -> String {
unsafe { cstr_to_string(ffi::CiA402Modes_GetModeNameEn(mode), "Unknown") }
}
pub fn cia402_mode_description(mode: i8) -> String {
unsafe { cstr_to_string(ffi::CiA402Modes_GetModeDescription(mode), "无描述") }
}
pub fn cia402_is_cyclic_sync_mode(mode: i8) -> bool {
unsafe { ffi::CiA402Modes_IsCyclicSyncMode(mode) != 0 }
}
pub fn cia402_requires_dc(mode: i8) -> bool {
unsafe { ffi::CiA402Modes_RequiresDC(mode) != 0 }
}
pub fn cia402_is_standard_homing_method(method: i8) -> bool {
unsafe { ffi::CiA402Modes_IsStandardHomingMethod(method) != 0 }
}
pub fn cia402_homing_method_name(method: i8) -> String {
unsafe { cstr_to_string(ffi::CiA402Modes_GetHomingMethodName(method), "厂商自定义") }
}
pub fn cia402_homing_trigger(method: i8) -> HomingTrigger {
unsafe { HomingTrigger::from_i32(ffi::CiA402Modes_GetHomingTrigger(method)) }
}
pub fn cia402_homing_direction(method: i8) -> i32 {
unsafe { ffi::CiA402Modes_GetHomingDirection(method) }
}
pub fn cia402_list_standard_homing_methods() -> Vec<i8> {
let mut buf = [0i8; 64];
let n = unsafe {
ffi::CiA402Modes_ListStandardHomingMethods(buf.as_mut_ptr(), buf.len() as i32)
};
if n <= 0 {
return Vec::new();
}
buf[..(n as usize).min(buf.len())].to_vec()
}
pub fn mailbox_type_name(mbx_type: u16) -> String {
unsafe { cstr_to_string(ffi::EcMailbox_GetTypeName(mbx_type), "未知") }
}
pub fn mailbox_type_name_en(mbx_type: u16) -> String {
unsafe { cstr_to_string(ffi::EcMailbox_GetTypeNameEn(mbx_type), "Unknown") }
}
pub fn mailbox_error_description(mbx_error_code: u16) -> String {
unsafe { cstr_to_string(ffi::EcMailbox_GetErrorDescription(mbx_error_code), "未知错误") }
}
pub fn mailbox_next_counter(current: u8) -> u8 {
unsafe { ffi::EcMailbox_NextCounter(current) }
}
pub fn soe_error_description(soe_error: u16) -> String {
unsafe { cstr_to_string(ffi::EcSoE_GetErrorDescription(soe_error), "未知 SoE 错误") }
}
pub fn sii_find_category(sii_data: &[u8], cat_type: u16) -> Option<(i32, i32)> {
let mut out_size: i32 = 0;
let off = unsafe {
ffi::EcSii_FindCategory(sii_data.as_ptr(), sii_data.len() as i32, cat_type, &mut out_size)
};
if off < 0 {
None
} else {
Some((off, out_size))
}
}
pub fn sii_enumerate_categories(sii_data: &[u8]) -> Vec<u16> {
let mut buf = vec![0u16; 32];
let n = unsafe {
ffi::EcSii_EnumerateCategories(
sii_data.as_ptr(),
sii_data.len() as i32,
buf.as_mut_ptr(),
buf.len() as i32,
)
};
if n <= 0 {
return Vec::new();
}
buf.truncate((n as usize).min(buf.len()));
buf
}
pub fn sii_get_string_by_index(cat_data: &[u8], idx: i32) -> Option<String> {
let mut buf = vec![0u8; 256];
let n = unsafe {
ffi::EcSii_GetStringByIndex(
cat_data.as_ptr(),
cat_data.len() as i32,
idx,
buf.as_mut_ptr(),
buf.len() as i32,
)
};
if n < 0 {
return None;
}
let n = (n as usize).min(buf.len());
Some(String::from_utf8_lossy(&buf[..n]).into_owned())
}
pub fn sii_get_string_count(cat_data: &[u8]) -> i32 {
unsafe { ffi::EcSii_GetStringCount(cat_data.as_ptr(), cat_data.len() as i32) }
}
pub fn sii_get_vendor_id(sii_data: &[u8]) -> u32 {
unsafe { ffi::EcSii_GetVendorId(sii_data.as_ptr(), sii_data.len() as i32) }
}
pub fn sii_get_product_code(sii_data: &[u8]) -> u32 {
unsafe { ffi::EcSii_GetProductCode(sii_data.as_ptr(), sii_data.len() as i32) }
}
pub fn sii_get_revision(sii_data: &[u8]) -> u32 {
unsafe { ffi::EcSii_GetRevision(sii_data.as_ptr(), sii_data.len() as i32) }
}
pub fn sii_get_serial_number(sii_data: &[u8]) -> u32 {
unsafe { ffi::EcSii_GetSerialNumber(sii_data.as_ptr(), sii_data.len() as i32) }
}
pub fn sii_get_configured_alias(sii_data: &[u8]) -> u16 {
unsafe { ffi::EcSii_GetConfiguredAlias(sii_data.as_ptr(), sii_data.len() as i32) }
}
pub fn sii_coe_enabled(coe_details: u8) -> bool {
unsafe { ffi::EcSii_CoeEnabled(coe_details) != 0 }
}
pub fn sii_coe_sdo_info(coe_details: u8) -> bool {
unsafe { ffi::EcSii_CoeSdoInfo(coe_details) != 0 }
}
pub fn sii_coe_pdo_assign(coe_details: u8) -> bool {
unsafe { ffi::EcSii_CoePdoAssign(coe_details) != 0 }
}
pub fn sii_coe_pdo_config(coe_details: u8) -> bool {
unsafe { ffi::EcSii_CoePdoConfig(coe_details) != 0 }
}
pub fn sii_coe_upload_at_startup(coe_details: u8) -> bool {
unsafe { ffi::EcSii_CoeUploadAtStartup(coe_details) != 0 }
}
pub fn sii_coe_complete_access(coe_details: u8) -> bool {
unsafe { ffi::EcSii_CoeCompleteAccess(coe_details) != 0 }
}
pub fn sii_foe_enabled(foe_details: u8) -> bool {
unsafe { ffi::EcSii_FoeEnabled(foe_details) != 0 }
}
pub fn sii_eoe_enabled(eoe_details: u8) -> bool {
unsafe { ffi::EcSii_EoeEnabled(eoe_details) != 0 }
}
pub fn coupler_detect_device_type(vendor_id: u32, product_code: u32) -> EcDeviceType {
unsafe {
EcDeviceType::from_i32(ffi::EcCouplerId_DetectDeviceType(vendor_id, product_code))
}
}
pub fn coupler_is_coupler(vendor_id: u32, product_code: u32) -> bool {
unsafe { ffi::EcCouplerId_IsCoupler(vendor_id, product_code) != 0 }
}
pub fn coupler_is_terminal(vendor_id: u32, product_code: u32) -> bool {
unsafe { ffi::EcCouplerId_IsTerminal(vendor_id, product_code) != 0 }
}
pub fn coupler_vendor_name(vendor_id: u32) -> String {
unsafe { cstr_to_string(ffi::EcCouplerId_GetVendorName(vendor_id), "未知厂商") }
}
pub fn coupler_vendor_name_en(vendor_id: u32) -> String {
unsafe { cstr_to_string(ffi::EcCouplerId_GetVendorNameEn(vendor_id), "Unknown") }
}
pub fn coupler_device_type_name(dtype: EcDeviceType) -> String {
unsafe { cstr_to_string(ffi::EcCouplerId_GetDeviceTypeName(dtype as i32), "未知") }
}
pub fn diag_topology_description(topo: u8) -> String {
unsafe { cstr_to_string(ffi::EcDiagStrings_TopologyDescription(topo), "未知") }
}
pub fn diag_timing_mode(mode: u32) -> String {
unsafe { cstr_to_string(ffi::EcDiagStrings_TimingMode(mode), "未知") }
}
pub fn diag_breakpoint_type(bp: u8) -> String {
unsafe { cstr_to_string(ffi::EcDiagStrings_BreakpointType(bp), "未知故障") }
}
pub fn diag_format_breakpoint(slave_idx: u16, port: u8, bp: u8) -> String {
let mut buf = vec![0u8; 128];
unsafe {
ffi::EcDiagStrings_FormatBreakpoint(
slave_idx,
port,
bp,
buf.as_mut_ptr(),
buf.len() as i32,
);
}
let len = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
String::from_utf8_lossy(&buf[..len]).into_owned()
}
}