use std::fmt;
use std::collections::HashMap;
use crate::data::error::{DarraError, Result};
use crate::utils::ffi;
use std::os::raw::c_int;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u16)]
pub enum EcDataType {
Unknown = 0x0000,
Boolean = 0x0001,
Integer8 = 0x0002,
Integer16 = 0x0003,
Integer32 = 0x0004,
Unsigned8 = 0x0005,
Unsigned16 = 0x0006,
Unsigned32 = 0x0007,
Real32 = 0x0008,
VisibleString = 0x0009,
OctetString = 0x000A,
UnicodeString = 0x000B,
TimeOfDay = 0x000C,
TimeDifference = 0x000D,
Domain = 0x000F,
Integer24 = 0x0010,
Real64 = 0x0011,
Integer40 = 0x0012,
Integer48 = 0x0013,
Integer56 = 0x0014,
Integer64 = 0x0015,
Unsigned24 = 0x0016,
Unsigned40 = 0x0018,
Unsigned48 = 0x0019,
Unsigned56 = 0x001A,
Unsigned64 = 0x001B,
Guid = 0x001D,
Byte = 0x001E,
Word = 0x001F,
DWord = 0x0020,
PdoMapping = 0x0021,
SmComType = 0x0022,
Identity = 0x0023,
Bit1 = 0x0030,
Bit2 = 0x0031,
Bit3 = 0x0032,
Bit4 = 0x0033,
Bit5 = 0x0034,
Bit6 = 0x0035,
Bit7 = 0x0036,
Bit8 = 0x0037,
}
impl EcDataType {
pub fn from_raw(val: u16) -> Self {
match val {
0x0000 => Self::Unknown,
0x0001 => Self::Boolean,
0x0002 => Self::Integer8,
0x0003 => Self::Integer16,
0x0004 => Self::Integer32,
0x0005 => Self::Unsigned8,
0x0006 => Self::Unsigned16,
0x0007 => Self::Unsigned32,
0x0008 => Self::Real32,
0x0009 => Self::VisibleString,
0x000A => Self::OctetString,
0x000B => Self::UnicodeString,
0x000C => Self::TimeOfDay,
0x000D => Self::TimeDifference,
0x000F => Self::Domain,
0x0010 => Self::Integer24,
0x0011 => Self::Real64,
0x0012 => Self::Integer40,
0x0013 => Self::Integer48,
0x0014 => Self::Integer56,
0x0015 => Self::Integer64,
0x0016 => Self::Unsigned24,
0x0018 => Self::Unsigned40,
0x0019 => Self::Unsigned48,
0x001A => Self::Unsigned56,
0x001B => Self::Unsigned64,
0x001D => Self::Guid,
0x001E => Self::Byte,
0x001F => Self::Word,
0x0020 => Self::DWord,
0x0021 => Self::PdoMapping,
0x0022 => Self::SmComType,
0x0023 => Self::Identity,
0x0030 => Self::Bit1,
0x0031 => Self::Bit2,
0x0032 => Self::Bit3,
0x0033 => Self::Bit4,
0x0034 => Self::Bit5,
0x0035 => Self::Bit6,
0x0036 => Self::Bit7,
0x0037 => Self::Bit8,
_ => Self::Unknown,
}
}
pub fn byte_size(self) -> Option<usize> {
match self {
Self::Boolean | Self::Integer8 | Self::Unsigned8 | Self::Byte => Some(1),
Self::Integer16 | Self::Unsigned16 | Self::Word => Some(2),
Self::Integer24 | Self::Unsigned24 => Some(3),
Self::Integer32 | Self::Unsigned32 | Self::Real32 | Self::DWord => Some(4),
Self::Integer40 | Self::Unsigned40 => Some(5),
Self::Integer48 | Self::Unsigned48 => Some(6),
Self::Integer56 | Self::Unsigned56 => Some(7),
Self::Integer64 | Self::Unsigned64 | Self::Real64 => Some(8),
_ => None,
}
}
}
impl fmt::Display for EcDataType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match self {
Self::Unknown => "UNKNOWN",
Self::Boolean => "BOOL",
Self::Integer8 => "INT8",
Self::Integer16 => "INT16",
Self::Integer32 => "INT32",
Self::Integer64 => "INT64",
Self::Integer24 => "INT24",
Self::Integer40 => "INT40",
Self::Integer48 => "INT48",
Self::Integer56 => "INT56",
Self::Unsigned8 => "UINT8",
Self::Unsigned16 => "UINT16",
Self::Unsigned32 => "UINT32",
Self::Unsigned24 => "UINT24",
Self::Unsigned40 => "UINT40",
Self::Unsigned48 => "UINT48",
Self::Unsigned56 => "UINT56",
Self::Unsigned64 => "UINT64",
Self::Real32 => "FLOAT",
Self::Real64 => "DOUBLE",
Self::VisibleString => "STRING",
Self::OctetString => "OCTET_STRING",
Self::UnicodeString => "UNICODE_STRING",
Self::TimeOfDay => "TIME_OF_DAY",
Self::TimeDifference => "TIME_DIFF",
Self::Domain => "DOMAIN",
Self::Guid => "GUID",
Self::Byte => "BYTE",
Self::Word => "WORD",
Self::DWord => "DWORD",
Self::PdoMapping => "PDO_MAP",
Self::SmComType => "SM_COM_TYPE",
Self::Identity => "IDENTITY",
Self::Bit1 => "BIT1",
Self::Bit2 => "BIT2",
Self::Bit3 => "BIT3",
Self::Bit4 => "BIT4",
Self::Bit5 => "BIT5",
Self::Bit6 => "BIT6",
Self::Bit7 => "BIT7",
Self::Bit8 => "BIT8",
};
write!(f, "{}", name)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ObjAccess(pub u16);
impl ObjAccess {
pub const NONE: u16 = 0x0000;
pub const READ_PREOP: u16 = 0x0001;
pub const READ_SAFEOP: u16 = 0x0002;
pub const READ_OP: u16 = 0x0004;
pub const WRITE_PREOP: u16 = 0x0008;
pub const WRITE_SAFEOP: u16 = 0x0010;
pub const WRITE_OP: u16 = 0x0020;
pub const MAP_RXPDO: u16 = 0x0040;
pub const MAP_TXPDO: u16 = 0x0080;
pub const SETTINGS: u16 = 0x0100;
pub const READ_ANY: u16 = Self::READ_PREOP | Self::READ_SAFEOP | Self::READ_OP;
pub const WRITE_ANY: u16 = Self::WRITE_PREOP | Self::WRITE_SAFEOP | Self::WRITE_OP;
pub fn can_read(self) -> bool {
(self.0 & Self::READ_ANY) != 0
}
pub fn can_write(self) -> bool {
(self.0 & Self::WRITE_ANY) != 0
}
pub fn is_read_only(self) -> bool {
self.can_read() && !self.can_write()
}
pub fn can_write_preop(self) -> bool {
(self.0 & Self::WRITE_PREOP) != 0
}
pub fn can_write_safeop(self) -> bool {
(self.0 & (Self::WRITE_PREOP | Self::WRITE_SAFEOP)) != 0
}
pub fn can_write_op(self) -> bool {
(self.0 & Self::WRITE_ANY) != 0
}
pub fn rxpdo_mappable(self) -> bool {
(self.0 & Self::MAP_RXPDO) != 0
}
pub fn txpdo_mappable(self) -> bool {
(self.0 & Self::MAP_TXPDO) != 0
}
pub fn readable(self) -> bool { self.can_read() }
pub fn writable(self) -> bool { self.can_write() }
pub fn access_description(self) -> String {
let can_read = self.can_read();
let can_write = self.can_write();
if can_read && can_write {
let mut parts = Vec::new();
if (self.0 & Self::WRITE_PREOP) != 0 { parts.push("PreOP"); }
if (self.0 & Self::WRITE_SAFEOP) != 0 { parts.push("SafeOP"); }
if (self.0 & Self::WRITE_OP) != 0 { parts.push("OP"); }
if parts.is_empty() {
"读写".to_string()
} else {
format!("读写({}可写)", parts.join(","))
}
} else if can_read {
"只读".to_string()
} else if can_write {
let mut parts = Vec::new();
if (self.0 & Self::WRITE_PREOP) != 0 { parts.push("PreOP"); }
if (self.0 & Self::WRITE_SAFEOP) != 0 { parts.push("SafeOP"); }
if (self.0 & Self::WRITE_OP) != 0 { parts.push("OP"); }
if parts.is_empty() {
"只写".to_string()
} else {
format!("只写({}可写)", parts.join(","))
}
} else {
"无权限".to_string()
}
}
}
impl fmt::Display for ObjAccess {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut parts = Vec::new();
if (self.0 & Self::READ_PREOP) != 0 { parts.push("R(PreOp)"); }
if (self.0 & Self::READ_SAFEOP) != 0 { parts.push("R(SafeOp)"); }
if (self.0 & Self::READ_OP) != 0 { parts.push("R(Op)"); }
if (self.0 & Self::WRITE_PREOP) != 0 { parts.push("W(PreOp)"); }
if (self.0 & Self::WRITE_SAFEOP) != 0 { parts.push("W(SafeOp)"); }
if (self.0 & Self::WRITE_OP) != 0 { parts.push("W(Op)"); }
if (self.0 & Self::MAP_RXPDO) != 0 { parts.push("RxPDO"); }
if (self.0 & Self::MAP_TXPDO) != 0 { parts.push("TxPDO"); }
write!(f, "[{}]", parts.join("|"))
}
}
#[derive(Debug, Clone)]
pub struct DiagnosticMessage {
pub sub_index: u8,
pub diag_code: u32,
pub flags: u16,
pub text_index: u16,
pub raw_data: Vec<u8>,
}
impl fmt::Display for DiagnosticMessage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Diag[{}]: Code=0x{:08X}, Flags=0x{:04X}",
self.sub_index, self.diag_code, self.flags)
}
}
#[derive(Debug, Clone)]
pub struct CoEAccessDenied {
pub index: u16,
pub sub_index: u8,
pub entry_name: String,
pub is_read_operation: bool,
pub obj_access: u16,
pub message: String,
}
impl CoEAccessDenied {
pub fn new(index: u16, sub_index: u8, entry_name: &str,
is_read_operation: bool, obj_access: u16) -> Self {
let operation = if is_read_operation { "读取" } else { "写入" };
let access_desc = ObjAccess(obj_access).access_description();
let message = format!(
"对象条目 0x{:04X}:{:02X} ({}) 不支持{}操作。访问权限: {}",
index, sub_index, entry_name, operation, access_desc
);
Self { index, sub_index, entry_name: entry_name.to_string(),
is_read_operation, obj_access, message }
}
pub fn create_with_custom_message(
index: u16, sub_index: u8, custom_message: &str,
is_read_operation: bool, obj_access: u16,
) -> Self {
Self { index, sub_index, entry_name: custom_message.to_string(),
is_read_operation, obj_access, message: custom_message.to_string() }
}
}
impl fmt::Display for CoEAccessDenied {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message)
}
}
impl std::error::Error for CoEAccessDenied {}
#[derive(Debug, Clone)]
pub struct ObjectEntry {
pub od_index: u16,
pub sub_index: u8,
pub name: String,
pub data_type: EcDataType,
pub bit_length: u16,
pub access: ObjAccess,
pub value_info: u8,
}
impl ObjectEntry {
pub fn byte_length(&self) -> usize {
((self.bit_length as usize) + 7) / 8
}
pub fn datatype(&self) -> EcDataType {
self.data_type
}
pub fn can_read(&self) -> bool { self.access.can_read() }
pub fn can_write(&self) -> bool { self.access.can_write() }
pub fn is_read_only(&self) -> bool { self.access.is_read_only() }
pub fn can_write_preop(&self) -> bool { self.access.can_write_preop() }
pub fn can_write_safeop(&self) -> bool { self.access.can_write_safeop() }
pub fn can_write_op(&self) -> bool { self.access.can_write_op() }
pub fn access_description(&self) -> String {
self.access.access_description()
}
pub fn read_raw(&self, master_index: u16, slave_index: u16) -> Result<Vec<u8>> {
if !self.can_read() {
return Err(DarraError::SdoReadFailed {
index: self.od_index, subindex: self.sub_index,
});
}
let mut size: c_int = 0;
let ptr = unsafe {
ffi::SDOread(master_index, slave_index, self.od_index, self.sub_index, 0, &mut size)
};
if ptr.is_null() || size <= 0 {
if !ptr.is_null() {
unsafe { ffi::FreeMemory(ptr as *mut _) };
}
return Err(DarraError::SdoReadFailed {
index: self.od_index, subindex: self.sub_index,
});
}
let data = unsafe { std::slice::from_raw_parts(ptr, size as usize).to_vec() };
unsafe { ffi::FreeMemory(ptr as *mut _) };
Ok(data)
}
pub fn bytes(&self, master_index: u16, slave_index: u16) -> Result<Vec<u8>> {
self.read_raw(master_index, slave_index)
}
pub fn read_u8(&self, master_index: u16, slave_index: u16) -> Result<u8> {
let data = self.read_raw(master_index, slave_index)?;
data.first().copied().ok_or(DarraError::SdoReadFailed {
index: self.od_index, subindex: self.sub_index,
})
}
pub fn read_u16(&self, master_index: u16, slave_index: u16) -> Result<u16> {
let data = self.read_raw(master_index, slave_index)?;
if data.len() < 2 {
return Err(DarraError::SdoReadFailed { index: self.od_index, subindex: self.sub_index });
}
Ok(u16::from_le_bytes([data[0], data[1]]))
}
pub fn read_u32(&self, master_index: u16, slave_index: u16) -> Result<u32> {
let data = self.read_raw(master_index, slave_index)?;
if data.len() < 4 {
return Err(DarraError::SdoReadFailed { index: self.od_index, subindex: self.sub_index });
}
Ok(u32::from_le_bytes([data[0], data[1], data[2], data[3]]))
}
pub fn read_u64(&self, master_index: u16, slave_index: u16) -> Result<u64> {
let data = self.read_raw(master_index, slave_index)?;
if data.len() < 8 {
return Err(DarraError::SdoReadFailed { index: self.od_index, subindex: self.sub_index });
}
Ok(u64::from_le_bytes([
data[0], data[1], data[2], data[3],
data[4], data[5], data[6], data[7],
]))
}
pub fn read_i32(&self, master_index: u16, slave_index: u16) -> Result<i32> {
Ok(self.read_u32(master_index, slave_index)? as i32)
}
pub fn read_i64(&self, master_index: u16, slave_index: u16) -> Result<i64> {
Ok(self.read_u64(master_index, slave_index)? as i64)
}
pub fn read_f32(&self, master_index: u16, slave_index: u16) -> Result<f32> {
let bits = self.read_u32(master_index, slave_index)?;
Ok(f32::from_bits(bits))
}
pub fn read_f64(&self, master_index: u16, slave_index: u16) -> Result<f64> {
let bits = self.read_u64(master_index, slave_index)?;
Ok(f64::from_bits(bits))
}
pub fn read_string(&self, master_index: u16, slave_index: u16) -> Result<String> {
let data = self.read_raw(master_index, slave_index)?;
let end = data.iter().position(|&b| b == 0).unwrap_or(data.len());
Ok(String::from_utf8_lossy(&data[..end]).to_string())
}
pub fn get_value(&self, master_index: u16, slave_index: u16) -> Result<CoEValue> {
match self.data_type {
EcDataType::Boolean => {
let v = self.read_u8(master_index, slave_index)?;
Ok(CoEValue::Bool(v != 0))
}
EcDataType::Integer8 => {
let v = self.read_u8(master_index, slave_index)?;
Ok(CoEValue::I8(v as i8))
}
EcDataType::Unsigned8 | EcDataType::Byte => {
Ok(CoEValue::U8(self.read_u8(master_index, slave_index)?))
}
EcDataType::Integer16 => {
let v = self.read_u16(master_index, slave_index)?;
Ok(CoEValue::I16(v as i16))
}
EcDataType::Unsigned16 | EcDataType::Word => {
Ok(CoEValue::U16(self.read_u16(master_index, slave_index)?))
}
EcDataType::Integer32 | EcDataType::Integer24 => {
Ok(CoEValue::I32(self.read_i32(master_index, slave_index)?))
}
EcDataType::Unsigned32 | EcDataType::Unsigned24 | EcDataType::DWord => {
Ok(CoEValue::U32(self.read_u32(master_index, slave_index)?))
}
EcDataType::Integer64 => {
Ok(CoEValue::I64(self.read_i64(master_index, slave_index)?))
}
EcDataType::Unsigned64 => {
Ok(CoEValue::U64(self.read_u64(master_index, slave_index)?))
}
EcDataType::Real32 => {
Ok(CoEValue::F32(self.read_f32(master_index, slave_index)?))
}
EcDataType::Real64 => {
Ok(CoEValue::F64(self.read_f64(master_index, slave_index)?))
}
EcDataType::VisibleString | EcDataType::UnicodeString => {
Ok(CoEValue::Str(self.read_string(master_index, slave_index)?))
}
_ => {
Ok(CoEValue::Raw(self.read_raw(master_index, slave_index)?))
}
}
}
pub fn write_raw(&self, master_index: u16, slave_index: u16, data: &[u8]) -> Result<()> {
if !self.can_write() {
return Err(DarraError::SdoWriteFailed {
index: self.od_index, subindex: self.sub_index,
});
}
let ok = unsafe {
ffi::SDOwrite_raw(master_index, slave_index, self.od_index, self.sub_index,
0, data.as_ptr(), data.len() as c_int)
};
if ok != 0 {
Ok(())
} else {
Err(DarraError::SdoWriteFailed { index: self.od_index, subindex: self.sub_index })
}
}
pub fn write_u8(&self, master_index: u16, slave_index: u16, value: u8) -> Result<()> {
self.write_raw(master_index, slave_index, &[value])
}
pub fn write_u16(&self, master_index: u16, slave_index: u16, value: u16) -> Result<()> {
self.write_raw(master_index, slave_index, &value.to_le_bytes())
}
pub fn write_u32(&self, master_index: u16, slave_index: u16, value: u32) -> Result<()> {
self.write_raw(master_index, slave_index, &value.to_le_bytes())
}
pub fn write_u64(&self, master_index: u16, slave_index: u16, value: u64) -> Result<()> {
self.write_raw(master_index, slave_index, &value.to_le_bytes())
}
pub fn write_i32(&self, master_index: u16, slave_index: u16, value: i32) -> Result<()> {
self.write_raw(master_index, slave_index, &value.to_le_bytes())
}
pub fn write_i64(&self, master_index: u16, slave_index: u16, value: i64) -> Result<()> {
self.write_raw(master_index, slave_index, &value.to_le_bytes())
}
pub fn write_f32(&self, master_index: u16, slave_index: u16, value: f32) -> Result<()> {
self.write_raw(master_index, slave_index, &value.to_bits().to_le_bytes())
}
pub fn write_f64(&self, master_index: u16, slave_index: u16, value: f64) -> Result<()> {
self.write_raw(master_index, slave_index, &value.to_bits().to_le_bytes())
}
}
impl fmt::Display for ObjectEntry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f,
" [{:02X}] {:40} {:12} {}bit {}",
self.sub_index, self.name, self.data_type, self.bit_length, self.access
)
}
}
#[derive(Debug, Clone)]
pub enum CoEValue {
Bool(bool),
I8(i8),
U8(u8),
I16(i16),
U16(u16),
I32(i32),
U32(u32),
I64(i64),
U64(u64),
F32(f32),
F64(f64),
Str(String),
Raw(Vec<u8>),
}
impl fmt::Display for CoEValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CoEValue::Bool(v) => write!(f, "{}", v),
CoEValue::I8(v) => write!(f, "{}", v),
CoEValue::U8(v) => write!(f, "{}", v),
CoEValue::I16(v) => write!(f, "{}", v),
CoEValue::U16(v) => write!(f, "{}", v),
CoEValue::I32(v) => write!(f, "{}", v),
CoEValue::U32(v) => write!(f, "{}", v),
CoEValue::I64(v) => write!(f, "{}", v),
CoEValue::U64(v) => write!(f, "{}", v),
CoEValue::F32(v) => write!(f, "{:.6}", v),
CoEValue::F64(v) => write!(f, "{:.6}", v),
CoEValue::Str(v) => write!(f, "{}", v),
CoEValue::Raw(v) => {
let hex: Vec<String> = v.iter().map(|b| format!("{:02X}", b)).collect();
write!(f, "{}", hex.join(" "))
}
}
}
}
#[derive(Debug, Clone)]
pub struct OdObject {
pub index: u16,
pub name: String,
pub object_code: u8,
pub data_type: EcDataType,
pub max_sub: u8,
pub entries: Vec<ObjectEntry>,
}
impl OdObject {
pub fn count(&self) -> usize {
self.entries.len()
}
pub fn datatype(&self) -> EcDataType {
self.data_type
}
pub fn is_var(&self) -> bool {
self.object_code == 0x07
}
pub fn is_array(&self) -> bool {
self.object_code == 0x08
}
pub fn is_record(&self) -> bool {
self.object_code == 0x09
}
pub fn entry(&self, sub_index: u8) -> Option<&ObjectEntry> {
self.entries.iter().find(|e| e.sub_index == sub_index)
}
pub fn contains_key(&self, sub_index: u8) -> bool {
self.entries.iter().any(|e| e.sub_index == sub_index)
}
pub fn entry_by_name(&self, name: &str) -> Option<&ObjectEntry> {
let name_lower = name.to_lowercase();
self.entries.iter().find(|e| e.name.to_lowercase() == name_lower)
}
pub fn get_by_position(&self, position: usize) -> Option<&ObjectEntry> {
self.entries.get(position)
}
pub fn read_all(&self, master_index: u16, slave_index: u16) -> HashMap<u8, Vec<u8>> {
let mut result = HashMap::new();
for entry in &self.entries {
if let Ok(data) = entry.read_raw(master_index, slave_index) {
if !data.is_empty() {
result.insert(entry.sub_index, data);
}
}
}
result
}
pub fn copy(&self) -> Self {
self.clone()
}
}
impl fmt::Display for OdObject {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let kind = match self.object_code {
0x07 => "VAR ",
0x08 => "ARRAY ",
0x09 => "RECORD",
_ => "OTHER ",
};
writeln!(f, "[0x{:04X}] {} {} ({}条目)", self.index, kind, self.name, self.entries.len())?;
for entry in &self.entries {
writeln!(f, "{}", entry)?;
}
Ok(())
}
}
pub struct OdList {
pub master_index: u16,
pub slave_index: u16,
pub objects: Vec<OdObject>,
}
impl OdList {
pub fn load(master_index: u16, slave_index: u16) -> Result<Self> {
let odlist_ptr = unsafe { ffi::GetSlaveSDOList(master_index, slave_index) };
if odlist_ptr.is_null() {
return Err(DarraError::SdoReadFailed { index: 0, subindex: 0 });
}
let objects = Self::parse_odlist_objects(master_index, slave_index, odlist_ptr, true)?;
unsafe { ffi::FreeMemory(odlist_ptr as *mut std::os::raw::c_void) };
Ok(Self { master_index, slave_index, objects })
}
pub fn load_basic(master_index: u16, slave_index: u16) -> Result<Self> {
let odlist_ptr = unsafe { ffi::GetSlaveSDOListBasic(master_index, slave_index) };
if odlist_ptr.is_null() {
return Err(DarraError::SdoReadFailed { index: 0, subindex: 0 });
}
let objects = Self::parse_odlist_objects(master_index, slave_index, odlist_ptr, false)?;
unsafe { ffi::FreeMemory(odlist_ptr as *mut _) };
Ok(Self { master_index, slave_index, objects })
}
pub(crate) fn from_raw_ptr(
master_index: u16, slave_index: u16, odlist_ptr: *const std::ffi::c_void,
) -> Result<Self> {
if odlist_ptr.is_null() {
return Err(DarraError::SdoReadFailed { index: 0, subindex: 0 });
}
let objects = Self::parse_odlist_objects(master_index, slave_index, odlist_ptr, true)?;
Ok(Self { master_index, slave_index, objects })
}
fn parse_odlist_objects(
master_index: u16, slave_index: u16,
odlist_ptr: *const std::ffi::c_void, load_entries: bool,
) -> Result<Vec<OdObject>> {
let base = odlist_ptr as *const u8;
let entries_count = unsafe {
let p = base.add(2) as *const u16;
std::ptr::read_unaligned(p) as usize
};
let entries_count = entries_count.min(1024);
let mut objects = Vec::with_capacity(entries_count);
for i in 0..entries_count {
let idx_val = unsafe {
let p = base.add(4 + i * 2) as *const u16;
std::ptr::read_unaligned(p)
};
let dt_raw = unsafe {
let p = base.add(2052 + i * 2) as *const u16;
std::ptr::read_unaligned(p)
};
let obj_code = unsafe { *base.add(4100 + i) };
let max_sub = unsafe { *base.add(5124 + i) };
let name = {
let name_ptr = unsafe { base.add(6148 + i * 41) };
let name_slice = unsafe { std::slice::from_raw_parts(name_ptr, 41) };
let end = name_slice.iter().position(|&b| b == 0).unwrap_or(41);
String::from_utf8_lossy(&name_slice[..end]).to_string()
};
let mut entries = Vec::new();
if load_entries {
let oe_ptr = unsafe {
ffi::GetSlavePointer_SDO_WithODList(master_index, slave_index, i as u16, odlist_ptr)
};
if !oe_ptr.is_null() {
entries = parse_oe_entries(oe_ptr, idx_val, max_sub);
unsafe { ffi::FreeMemory(oe_ptr as *mut std::os::raw::c_void) };
}
}
objects.push(OdObject {
index: idx_val,
name,
object_code: obj_code,
data_type: EcDataType::from_raw(dt_raw),
max_sub,
entries,
});
}
Ok(objects)
}
pub fn count(&self) -> usize {
self.objects.len()
}
pub fn find(&self, index: u16) -> Option<&OdObject> {
self.objects.iter().find(|o| o.index == index)
}
pub fn contains_key(&self, index: u16) -> bool {
self.objects.iter().any(|o| o.index == index)
}
pub fn find_by_name(&self, name: &str) -> Vec<&OdObject> {
let name_lower = name.to_lowercase();
self.objects.iter()
.filter(|o| o.name.to_lowercase().contains(&name_lower))
.collect()
}
pub fn read_entry_raw(
&self, index: u16, sub_index: u8,
) -> Result<Vec<u8>> {
let mut size: c_int = 0;
let ptr = unsafe {
ffi::SDOread(self.master_index, self.slave_index, index, sub_index, 0, &mut size)
};
if ptr.is_null() || size <= 0 {
if !ptr.is_null() {
unsafe { ffi::FreeMemory(ptr as *mut _) };
}
return Err(DarraError::SdoReadFailed { index, subindex: sub_index });
}
let data = unsafe { std::slice::from_raw_parts(ptr, size as usize).to_vec() };
unsafe { ffi::FreeMemory(ptr as *mut _) };
Ok(data)
}
pub fn get_by_position(&self, position: usize) -> Option<&OdObject> {
self.objects.get(position)
}
pub fn len(&self) -> usize {
self.objects.len()
}
pub fn is_empty(&self) -> bool {
self.objects.is_empty()
}
pub fn copy(&self) -> Self {
Self {
master_index: self.master_index,
slave_index: self.slave_index,
objects: self.objects.clone(),
}
}
pub fn keys(&self) -> Vec<u16> {
self.objects.iter().map(|o| o.index).collect()
}
}
impl fmt::Display for OdList {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "从站 {} 对象字典 ({} 个对象):", self.slave_index, self.objects.len())?;
writeln!(f, "{}", "=".repeat(72))?;
for obj in &self.objects {
write!(f, "{}", obj)?;
}
Ok(())
}
}
pub fn read_diagnostic_messages(master_index: u16, slave_index: u16) -> Vec<DiagnosticMessage> {
let mut messages = Vec::new();
let mut size: c_int = 0;
let ptr = unsafe {
ffi::SDOread(master_index, slave_index, 0x10F3, 0, 0, &mut size)
};
if ptr.is_null() || size < 1 {
if !ptr.is_null() {
unsafe { ffi::FreeMemory(ptr as *mut _) };
}
return messages;
}
let count = unsafe { *ptr } as usize;
unsafe { ffi::FreeMemory(ptr as *mut _) };
for i in 1..=count.min(20) {
let mut msg_size: c_int = 0;
let msg_ptr = unsafe {
ffi::SDOread(master_index, slave_index, 0x10F3, i as u8, 0, &mut msg_size)
};
if msg_ptr.is_null() || msg_size < 8 {
if !msg_ptr.is_null() {
unsafe { ffi::FreeMemory(msg_ptr as *mut _) };
}
continue;
}
let data = unsafe { std::slice::from_raw_parts(msg_ptr, msg_size as usize).to_vec() };
unsafe { ffi::FreeMemory(msg_ptr as *mut _) };
let diag_code = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
let flags = u16::from_le_bytes([data[4], data[5]]);
let text_index = u16::from_le_bytes([data[6], data[7]]);
messages.push(DiagnosticMessage {
sub_index: i as u8,
diag_code,
flags,
text_index,
raw_data: data,
});
}
messages
}
pub fn get_device_profile(master_index: u16, slave_index: u16) -> u16 {
let mut size: c_int = 0;
let ptr = unsafe {
ffi::SDOread(master_index, slave_index, 0x1000, 0, 0, &mut size)
};
if ptr.is_null() || size < 4 {
if !ptr.is_null() {
unsafe { ffi::FreeMemory(ptr as *mut _) };
}
return 0;
}
let data = unsafe { std::slice::from_raw_parts(ptr, 4) };
let device_type = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
unsafe { ffi::FreeMemory(ptr as *mut _) };
(device_type & 0xFFFF) as u16
}
fn parse_oe_entries(oe_ptr: *const std::ffi::c_void, od_index: u16, max_sub: u8) -> Vec<ObjectEntry> {
let base = oe_ptr as *const u8;
let oe_entries = unsafe {
let p = base as *const u16;
std::ptr::read_unaligned(p) as usize
};
let count = oe_entries.min(max_sub as usize + 1).min(256);
let mut entries = Vec::with_capacity(count);
for i in 0..count {
let value_info = unsafe { *base.add(2 + i) };
let dt_raw = unsafe {
let p = base.add(258 + i * 2) as *const u16;
std::ptr::read_unaligned(p)
};
let bit_length = unsafe {
let p = base.add(770 + i * 2) as *const u16;
std::ptr::read_unaligned(p)
};
let access_raw = unsafe {
let p = base.add(1282 + i * 2) as *const u16;
std::ptr::read_unaligned(p)
};
let name = {
let name_ptr = unsafe { base.add(1794 + i * 41) };
let name_slice = unsafe { std::slice::from_raw_parts(name_ptr, 41) };
let end = name_slice.iter().position(|&b| b == 0).unwrap_or(41);
String::from_utf8_lossy(&name_slice[..end]).to_string()
};
entries.push(ObjectEntry {
od_index,
sub_index: i as u8,
name,
data_type: EcDataType::from_raw(dt_raw),
bit_length,
access: ObjAccess(access_raw),
value_info,
});
}
entries
}
use crate::data::types::SDOError;
use std::cell::Cell;
pub struct CoEInstance {
master_index: u16,
slave_index: u16,
last_sdo_error: Cell<SDOError>,
}
impl CoEInstance {
pub fn new(master_index: u16, slave_index: u16) -> Self {
Self {
master_index,
slave_index,
last_sdo_error: Cell::new(SDOError::NoError),
}
}
pub fn last_sdo_error(&self) -> SDOError {
if let Some(f) = ffi::dynamic_ffi::ffi_gap().get_last_sdo_error {
let code = unsafe { f(self.master_index, self.slave_index) };
if code != 0 {
return SDOError::from_u32(code);
}
}
self.last_sdo_error.get()
}
pub fn is_supported(&self) -> bool {
let proto = unsafe { ffi::GetSlaveMailboxProto(self.master_index, self.slave_index) };
(proto & 0x04) != 0
}
pub fn sdo_read(&self, index: u16, subindex: u8, complete_access: bool) -> Result<Vec<u8>> {
let mut size: c_int = 0;
let ptr = unsafe {
ffi::SDOread(self.master_index, self.slave_index, index, subindex,
if complete_access { 1 } else { 0 }, &mut size)
};
if ptr.is_null() || size <= 0 {
if size < 0 {
let abort_code = (-size) as u32;
self.last_sdo_error.set(SDOError::from_u32(abort_code));
} else {
self.last_sdo_error.set(SDOError::GeneralError);
}
if !ptr.is_null() { unsafe { ffi::FreeMemory(ptr as *mut _) }; }
return Err(DarraError::SdoReadFailed { index, subindex });
}
let data = unsafe { std::slice::from_raw_parts(ptr, size as usize).to_vec() };
unsafe { ffi::FreeMemory(ptr as *mut _) };
self.last_sdo_error.set(SDOError::NoError);
Ok(data)
}
pub fn sdo_write(&self, index: u16, subindex: u8, complete_access: bool, data: &[u8]) -> Result<()> {
let ok = unsafe {
ffi::SDOwrite_raw(self.master_index, self.slave_index, index, subindex,
if complete_access { 1 } else { 0 }, data.as_ptr(), data.len() as c_int)
};
if ok == 0 {
self.last_sdo_error.set(SDOError::GeneralError);
let mut probe_size: c_int = 0;
let probe = unsafe {
ffi::SDOread(self.master_index, self.slave_index, index, subindex, 0, &mut probe_size)
};
if probe.is_null() && probe_size < 0 {
let abort_code = (-probe_size) as u32;
self.last_sdo_error.set(SDOError::from_u32(abort_code));
} else if !probe.is_null() {
unsafe { ffi::FreeMemory(probe as *mut _) };
}
Err(DarraError::SdoWriteFailed { index, subindex })
} else {
self.last_sdo_error.set(SDOError::NoError);
Ok(())
}
}
pub fn read_multiple(&self, entries: &[(u16, u8)]) -> HashMap<(u16, u8), Option<Vec<u8>>> {
let mut results = HashMap::new();
for &(index, subindex) in entries {
let data = self.sdo_read(index, subindex, false).ok();
results.insert((index, subindex), data);
}
results
}
pub fn master_index(&self) -> u16 { self.master_index }
pub fn slave_index(&self) -> u16 { self.slave_index }
}
#[derive(Debug, Clone)]
pub enum CoeError {
DllFailed { abort_code: u32 },
InvalidParameter(String),
InvalidResponse(String),
}
impl std::fmt::Display for CoeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::DllFailed { abort_code } => {
if *abort_code != 0 {
write!(f, "CoE DLL 失败 (Abort Code 0x{:08X})", abort_code)
} else {
write!(f, "CoE DLL 失败")
}
}
Self::InvalidParameter(msg) => write!(f, "CoE 无效参数: {}", msg),
Self::InvalidResponse(msg) => write!(f, "CoE 响应异常: {}", msg),
}
}
}
impl std::error::Error for CoeError {}
impl From<CoeError> for DarraError {
fn from(e: CoeError) -> Self {
DarraError::Other(e.to_string())
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct DiagMeta {
pub max_messages: u8,
pub newest_message: u8,
pub newest_acknowledged: u8,
pub flags: u16,
}
impl DiagMeta {
pub fn is_ring_buffer(&self) -> bool {
(self.flags & 0x0010) != 0
}
pub fn has_overrun(&self) -> bool {
(self.flags & 0x0020) != 0
}
}
impl CoEInstance {
pub fn poll_has_new_diagnostic(&self) -> i32 {
let mut abort_code: u32 = 0;
unsafe {
ffi::coe_diag_poll_new_available(self.master_index, self.slave_index, &mut abort_code)
}
}
pub fn read_diagnostic_meta(&self) -> std::result::Result<DiagMeta, CoeError> {
let mut max_msgs: u8 = 0;
let mut newest: u8 = 0;
let mut acknowledged: u8 = 0;
let mut flags: u16 = 0;
let mut abort_code: u32 = 0;
let rc = unsafe {
ffi::coe_diag_read_meta(
self.master_index,
self.slave_index,
&mut max_msgs,
&mut newest,
&mut acknowledged,
&mut flags,
&mut abort_code,
)
};
if rc == 1 {
Ok(DiagMeta {
max_messages: max_msgs,
newest_message: newest,
newest_acknowledged: acknowledged,
flags,
})
} else {
Err(CoeError::DllFailed { abort_code })
}
}
pub fn read_diagnostic_message(
&self,
msg_idx: u16,
buf_size: usize,
) -> std::result::Result<Vec<u8>, CoeError> {
if !(6..=255).contains(&msg_idx) {
return Err(CoeError::InvalidParameter(format!(
"msg_idx 必须在 6..255 之间, 实际={}",
msg_idx
)));
}
if buf_size == 0 || buf_size > 64 * 1024 {
return Err(CoeError::InvalidParameter(format!(
"buf_size 必须在 1..65536 之间, 实际={}",
buf_size
)));
}
let mut buf = vec![0u8; buf_size];
let mut out_len: c_int = 0;
let mut abort_code: u32 = 0;
let rc = unsafe {
ffi::coe_diag_read_message(
self.master_index,
self.slave_index,
msg_idx as u8,
buf.as_mut_ptr(),
buf_size as c_int,
&mut out_len,
&mut abort_code,
)
};
if rc == 1 {
if out_len < 0 || out_len as usize > buf_size {
return Err(CoeError::InvalidResponse(format!(
"DLL 返回 out_len={}, buf_size={}",
out_len, buf_size
)));
}
buf.truncate(out_len as usize);
Ok(buf)
} else {
Err(CoeError::DllFailed { abort_code })
}
}
pub fn acknowledge_diagnostic(&self, msg_idx: u16) -> std::result::Result<(), CoeError> {
if !(6..=255).contains(&msg_idx) {
return Err(CoeError::InvalidParameter(format!(
"msg_idx 必须在 6..255 之间, 实际={}",
msg_idx
)));
}
let mut abort_code: u32 = 0;
let rc = unsafe {
ffi::coe_diag_acknowledge(
self.master_index,
self.slave_index,
msg_idx as u8,
&mut abort_code,
)
};
if rc == 1 {
Ok(())
} else {
Err(CoeError::DllFailed { abort_code })
}
}
}
pub struct CaObjectEntry {
pub sub_index: u8,
pub data_type: u16,
pub bit_length: u16,
}
pub fn parse_complete_access_data(data: &[u8], entries: &[CaObjectEntry]) -> Vec<(u8, Vec<u8>)> {
let mut result = Vec::new();
if data.is_empty() || entries.is_empty() {
return result;
}
let si0_bytes = 2usize;
if data.len() >= si0_bytes {
result.push((0, data[..si0_bytes].to_vec()));
}
let mut bit_offset = si0_bytes * 8;
for i in 1..entries.len() {
let oe = &entries[i];
let dt = oe.data_type;
let is_bit_type = dt >= 0x0030 && dt <= 0x0037;
let bit_size = if is_bit_type {
(dt - 0x0030 + 1) as usize } else if oe.bit_length > 0 {
oe.bit_length as usize
} else {
8
};
if !is_bit_type && (bit_offset % 8) != 0 {
bit_offset = ((bit_offset + 7) / 8) * 8;
}
let byte_start = bit_offset / 8;
let bytes_needed = (bit_size + 7) / 8;
if byte_start + bytes_needed <= data.len() {
result.push((oe.sub_index, data[byte_start..byte_start + bytes_needed].to_vec()));
}
bit_offset += bit_size;
}
result
}
impl CoEInstance {
pub fn sdo_read_blocking(
master_index: u16,
slave_index: u16,
index: u16,
subindex: u8,
complete_access: bool,
) -> std::thread::JoinHandle<Result<Vec<u8>>> {
std::thread::spawn(move || {
let coe = CoEInstance::new(master_index, slave_index);
coe.sdo_read(index, subindex, complete_access)
})
}
pub fn sdo_write_blocking(
master_index: u16,
slave_index: u16,
index: u16,
subindex: u8,
complete_access: bool,
data: Vec<u8>,
) -> std::thread::JoinHandle<Result<()>> {
std::thread::spawn(move || {
let coe = CoEInstance::new(master_index, slave_index);
coe.sdo_write(index, subindex, complete_access, &data)
})
}
}
#[cfg(feature = "async-tokio")]
impl CoEInstance {
pub async fn sdo_read_async(
&self,
index: u16,
subindex: u8,
complete_access: bool,
) -> Result<Vec<u8>> {
let master = self.master_index;
let slave = self.slave_index;
tokio::task::spawn_blocking(move || {
let coe = CoEInstance::new(master, slave);
coe.sdo_read(index, subindex, complete_access)
})
.await
.map_err(|e| DarraError::Other(format!("tokio join error: {}", e)))?
}
pub async fn sdo_write_async(
&self,
index: u16,
subindex: u8,
complete_access: bool,
data: Vec<u8>,
) -> Result<()> {
let master = self.master_index;
let slave = self.slave_index;
tokio::task::spawn_blocking(move || {
let coe = CoEInstance::new(master, slave);
coe.sdo_write(index, subindex, complete_access, &data)
})
.await
.map_err(|e| DarraError::Other(format!("tokio join error: {}", e)))?
}
}
impl crate::abstractions::MailboxProtocol for CoEInstance {
fn protocol_type(&self) -> u8 { 0x03 }
fn protocol_name(&self) -> &'static str { "CoE" }
fn is_supported(&self) -> bool {
CoEInstance::is_supported(self)
}
fn last_error_code(&self) -> u32 {
self.last_sdo_error.get() as u32
}
fn statistics(&self) -> crate::abstractions::MailboxStatistics {
let mut stats = ffi::EcMbxStatsC::default();
let rc = unsafe {
ffi::mbx_get_stats_by_master(
self.master_index, self.slave_index, 0x03, &mut stats,
)
};
if rc == 1 {
stats.into()
} else {
crate::abstractions::MailboxStatistics::empty()
}
}
fn reset_statistics(&self) {
unsafe {
ffi::mbx_reset_stats_by_master(self.master_index, self.slave_index, 0x03);
}
}
}