use std::collections::HashMap;
use crate::data::error::{DarraError, Result};
use crate::utils::ffi::{self, AOENotificationCallback};
use std::os::raw::c_void;
const DEFAULT_TIMEOUT_US: i32 = 500_000;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum AoEResultCode {
NoError = 0x0000,
InternalError = 0x0001,
NoRealTime = 0x0002,
AllocationLockedMemoryError = 0x0003,
InsertMailboxError = 0x0004,
WrongReceiveHMsg = 0x0005,
TargetPortNotFound = 0x0006,
TargetMachineNotFound = 0x0007,
UnknownCommandId = 0x0008,
BadTaskId = 0x0009,
NoIO = 0x000A,
UnknownAmsCommand = 0x000B,
Win32Error = 0x000C,
PortNotConnected = 0x000D,
InvalidAmsLength = 0x000E,
InvalidAmsNetId = 0x000F,
LowInstLevel = 0x0010,
NoDebugAvailable = 0x0011,
PortDisabled = 0x0012,
PortAlreadyConnected = 0x0013,
AmsSyncWin32Error = 0x0014,
AmsSyncTimeout = 0x0015,
AmsSyncAmsError = 0x0016,
AmsSyncNoIndexMap = 0x0017,
InvalidAmsPort = 0x0018,
NoMemory = 0x0019,
TcpSendError = 0x001A,
HostUnreachable = 0x001B,
InvalidAmsFragment = 0x001C,
WsaServiceNotStarted = 0x274D,
DeviceInvalidGroup = 0x0700,
DeviceInvalidOffset = 0x0701,
DeviceInvalidAccess = 0x0702,
DeviceInvalidSize = 0x0703,
DeviceInvalidData = 0x0704,
DeviceNotReady = 0x0705,
DeviceBusy = 0x0706,
DeviceInvalidContext = 0x0707,
DeviceNotFound = 0x0708,
DeviceAlreadyExists = 0x0709,
DeviceSymbolNotFound = 0x070A,
DeviceSymbolVersionInvalid = 0x070B,
DeviceInvalidState = 0x070C,
DeviceTimeout = 0x0712,
DeviceNoInterfaceQuery = 0x0713,
DeviceInvalidInterface = 0x0714,
DeviceInvalidClassId = 0x0715,
DeviceInvalidObjectId = 0x0716,
DevicePending = 0x0717,
DeviceAborted = 0x0718,
DeviceWarning = 0x0719,
DeviceInvalidArrayIndex = 0x071A,
DeviceSymbolNotActive = 0x071B,
DeviceAccessDenied = 0x071C,
ClientError = 0x0740,
ClientInvalidParam = 0x0741,
ClientListEmpty = 0x0742,
ClientVarAlreadyInUse = 0x0743,
ClientInvokeTimeout = 0x0744,
ClientPortNotOpen = 0x0745,
ClientNotStarted = 0x0746,
ClientQueueFull = 0x0750,
}
impl AoEResultCode {
pub fn from_u32(code: u32) -> Option<Self> {
Some(match code {
0x0000 => Self::NoError,
0x0001 => Self::InternalError,
0x0002 => Self::NoRealTime,
0x0003 => Self::AllocationLockedMemoryError,
0x0004 => Self::InsertMailboxError,
0x0005 => Self::WrongReceiveHMsg,
0x0006 => Self::TargetPortNotFound,
0x0007 => Self::TargetMachineNotFound,
0x0008 => Self::UnknownCommandId,
0x0009 => Self::BadTaskId,
0x000A => Self::NoIO,
0x000B => Self::UnknownAmsCommand,
0x000C => Self::Win32Error,
0x000D => Self::PortNotConnected,
0x000E => Self::InvalidAmsLength,
0x000F => Self::InvalidAmsNetId,
0x0010 => Self::LowInstLevel,
0x0011 => Self::NoDebugAvailable,
0x0012 => Self::PortDisabled,
0x0013 => Self::PortAlreadyConnected,
0x0014 => Self::AmsSyncWin32Error,
0x0015 => Self::AmsSyncTimeout,
0x0016 => Self::AmsSyncAmsError,
0x0017 => Self::AmsSyncNoIndexMap,
0x0018 => Self::InvalidAmsPort,
0x0019 => Self::NoMemory,
0x001A => Self::TcpSendError,
0x001B => Self::HostUnreachable,
0x001C => Self::InvalidAmsFragment,
0x274D => Self::WsaServiceNotStarted,
0x0700 => Self::DeviceInvalidGroup,
0x0701 => Self::DeviceInvalidOffset,
0x0702 => Self::DeviceInvalidAccess,
0x0703 => Self::DeviceInvalidSize,
0x0704 => Self::DeviceInvalidData,
0x0705 => Self::DeviceNotReady,
0x0706 => Self::DeviceBusy,
0x0707 => Self::DeviceInvalidContext,
0x0708 => Self::DeviceNotFound,
0x0709 => Self::DeviceAlreadyExists,
0x070A => Self::DeviceSymbolNotFound,
0x070B => Self::DeviceSymbolVersionInvalid,
0x070C => Self::DeviceInvalidState,
0x0712 => Self::DeviceTimeout,
0x0713 => Self::DeviceNoInterfaceQuery,
0x0714 => Self::DeviceInvalidInterface,
0x0715 => Self::DeviceInvalidClassId,
0x0716 => Self::DeviceInvalidObjectId,
0x0717 => Self::DevicePending,
0x0718 => Self::DeviceAborted,
0x0719 => Self::DeviceWarning,
0x071A => Self::DeviceInvalidArrayIndex,
0x071B => Self::DeviceSymbolNotActive,
0x071C => Self::DeviceAccessDenied,
0x0740 => Self::ClientError,
0x0741 => Self::ClientInvalidParam,
0x0742 => Self::ClientListEmpty,
0x0743 => Self::ClientVarAlreadyInUse,
0x0744 => Self::ClientInvokeTimeout,
0x0745 => Self::ClientPortNotOpen,
0x0746 => Self::ClientNotStarted,
0x0750 => Self::ClientQueueFull,
_ => return None,
})
}
pub fn value(self) -> u32 { self as u32 }
}
pub struct AoEInstance {
master_index: u16,
slave_index: u16,
}
impl AoEInstance {
pub(crate) fn new(master_index: u16, slave_index: u16) -> Self {
Self { master_index, slave_index }
}
pub fn master_index(&self) -> u16 { self.master_index }
pub fn slave_index(&self) -> u16 { self.slave_index }
pub fn is_supported(&self) -> bool {
let proto = unsafe { ffi::GetSlaveMailboxProto(self.master_index, self.slave_index) };
(proto & 0x01) != 0
}
pub fn read_write(
&self,
index_group: u32,
index_offset: u32,
read_length: u32,
write_data: Option<&[u8]>,
timeout_us: i32,
) -> Result<Option<Vec<u8>>> {
let write_buf = write_data.unwrap_or(&[]);
let write_len = write_buf.len() as u32;
let mut read_ptr: *mut c_void = std::ptr::null_mut();
let mut bytes_read: u32 = 0;
let ret = unsafe {
ffi::AOEReadWrite(
self.master_index, self.slave_index,
index_group, index_offset,
read_length, write_len, write_buf.as_ptr(),
&mut read_ptr, &mut bytes_read, timeout_us,
)
};
if ret != 0 {
if !read_ptr.is_null() && bytes_read > 0 {
let mut data = vec![0u8; bytes_read as usize];
unsafe {
std::ptr::copy_nonoverlapping(read_ptr as *const u8, data.as_mut_ptr(), bytes_read as usize);
ffi::FreeMemory(read_ptr);
}
Ok(Some(data))
} else {
if !read_ptr.is_null() { unsafe { ffi::FreeMemory(read_ptr) }; }
Ok(None)
}
} else {
Err(DarraError::AoeFailed)
}
}
pub fn read(&self, index_group: u32, index_offset: u32, length: u32) -> Result<Option<Vec<u8>>> {
self.read_write(index_group, index_offset, length, None, DEFAULT_TIMEOUT_US)
}
pub fn read_with_timeout(&self, index_group: u32, index_offset: u32, length: u32, timeout_us: i32) -> Result<Option<Vec<u8>>> {
self.read_write(index_group, index_offset, length, None, timeout_us)
}
pub fn write(&self, index_group: u32, index_offset: u32, data: &[u8]) -> Result<()> {
let mut read_ptr: *mut c_void = std::ptr::null_mut();
let mut bytes_read: u32 = 0;
let ret = unsafe {
ffi::AOEReadWrite(
self.master_index, self.slave_index,
index_group, index_offset,
0, data.len() as u32, data.as_ptr(),
&mut read_ptr, &mut bytes_read, DEFAULT_TIMEOUT_US,
)
};
if !read_ptr.is_null() { unsafe { ffi::FreeMemory(read_ptr) }; }
if ret != 0 { Ok(()) } else { Err(DarraError::AoeFailed) }
}
pub fn send_command(
&self,
target_port: u16,
command_id: u16,
command_data: Option<&[u8]>,
timeout_us: i32,
) -> Result<(bool, Option<Vec<u8>>)> {
let cmd_data = command_data.unwrap_or(&[]);
let cmd_size = cmd_data.len() as u32;
let mut response_ptr: *mut c_void = std::ptr::null_mut();
let mut response_size: u32 = 0;
let ret = unsafe {
ffi::AOESendCommand(
self.master_index, self.slave_index,
target_port, command_id,
cmd_data.as_ptr(), cmd_size,
&mut response_ptr, &mut response_size, timeout_us,
)
};
if ret != 0 {
if !response_ptr.is_null() && response_size > 0 {
let mut data = vec![0u8; response_size as usize];
unsafe {
std::ptr::copy_nonoverlapping(response_ptr as *const u8, data.as_mut_ptr(), response_size as usize);
ffi::FreeMemory(response_ptr);
}
Ok((true, Some(data)))
} else {
if !response_ptr.is_null() { unsafe { ffi::FreeMemory(response_ptr) }; }
Ok((true, None))
}
} else {
Ok((false, None))
}
}
pub fn read_device_info(&self) -> Result<(u8, u8, u16, String)> {
let mut major_ver: u8 = 0;
let mut minor_ver: u8 = 0;
let mut build: u16 = 0;
let mut name_buf = vec![0u8; 256];
let ret = unsafe {
ffi::AOEReadDeviceInfo(
self.master_index, self.slave_index,
&mut major_ver, &mut minor_ver, &mut build,
name_buf.as_mut_ptr(), name_buf.len() as i32,
DEFAULT_TIMEOUT_US,
)
};
if ret != 0 {
let null_pos = name_buf.iter().position(|&b| b == 0).unwrap_or(name_buf.len());
let name = crate::utils::help::decode_ethercat_string(&name_buf[..null_pos])
.trim()
.to_string();
Ok((major_ver, minor_ver, build, name))
} else {
Err(DarraError::AoeFailed)
}
}
pub fn read_state(&self) -> Result<(u16, u16)> {
let mut ads_state: u16 = 0;
let mut device_state: u16 = 0;
let ret = unsafe {
ffi::AOEReadState(
self.master_index, self.slave_index,
&mut ads_state, &mut device_state, DEFAULT_TIMEOUT_US,
)
};
if ret != 0 { Ok((ads_state, device_state)) } else { Err(DarraError::AoeFailed) }
}
pub fn write_control(&self, ads_state: u16, device_state: u16, data: Option<&[u8]>) -> Result<()> {
let write_data = data.unwrap_or(&[]);
let ret = unsafe {
ffi::AOEWriteControl(
self.master_index, self.slave_index,
ads_state, device_state,
write_data.as_ptr(), write_data.len() as i32,
DEFAULT_TIMEOUT_US,
)
};
if ret != 0 { Ok(()) } else { Err(DarraError::AoeFailed) }
}
pub fn add_notification(
&self, index_group: u32, index_offset: u32, length: u32,
trans_mode: u32, max_delay: u32, cycle_time: u32,
) -> Result<u32> {
let mut handle: u32 = 0;
let ret = unsafe {
ffi::AOEAddNotification(
self.master_index, self.slave_index,
index_group, index_offset, length,
trans_mode, max_delay, cycle_time,
&mut handle, DEFAULT_TIMEOUT_US,
)
};
if ret != 0 { Ok(handle) } else { Err(DarraError::AoeFailed) }
}
pub fn del_notification(&self, handle: u32) -> Result<()> {
let ret = unsafe {
ffi::AOEDelNotification(self.master_index, self.slave_index, handle, DEFAULT_TIMEOUT_US)
};
if ret != 0 { Ok(()) } else { Err(DarraError::AoeFailed) }
}
pub fn set_config(
&self, target_net_id: &[u8; 6], target_port: u16,
source_net_id: &[u8; 6], source_port: u16,
) -> Result<()> {
let ret = unsafe {
ffi::AOESetConfig(
self.master_index, self.slave_index,
target_net_id.as_ptr(), target_port,
source_net_id.as_ptr(), source_port,
)
};
if ret != 0 { Ok(()) } else { Err(DarraError::AoeFailed) }
}
pub fn config(&self) -> Result<([u8; 6], u16, [u8; 6], u16)> {
let mut target_net_id = [0u8; 6];
let mut target_port: u16 = 0;
let mut source_net_id = [0u8; 6];
let mut source_port: u16 = 0;
let ret = unsafe {
ffi::AOEGetConfig(
self.master_index, self.slave_index,
target_net_id.as_mut_ptr(), &mut target_port,
source_net_id.as_mut_ptr(), &mut source_port,
)
};
if ret != 0 { Ok((target_net_id, target_port, source_net_id, source_port)) }
else { Err(DarraError::AoeFailed) }
}
const COE_INDEX_GROUP: u32 = 0xF302;
const SOE_INDEX_GROUP: u32 = 0xF420;
pub fn read_coe_via_aoe(&self, index: u16, subindex: u8, read_length: u32) -> Result<Option<Vec<u8>>> {
let index_offset = ((index as u32) << 16) | (subindex as u32);
self.read_write(Self::COE_INDEX_GROUP, index_offset, read_length, None, DEFAULT_TIMEOUT_US)
}
pub fn write_coe_via_aoe(&self, index: u16, subindex: u8, data: &[u8]) -> Result<()> {
let index_offset = ((index as u32) << 16) | (subindex as u32);
let mut read_ptr: *mut c_void = std::ptr::null_mut();
let mut bytes_read: u32 = 0;
let ret = unsafe {
ffi::AOEReadWrite(
self.master_index, self.slave_index,
Self::COE_INDEX_GROUP, index_offset,
0, data.len() as u32, data.as_ptr(),
&mut read_ptr, &mut bytes_read, DEFAULT_TIMEOUT_US,
)
};
if !read_ptr.is_null() { unsafe { ffi::FreeMemory(read_ptr) }; }
if ret != 0 { Ok(()) } else { Err(DarraError::AoeFailed) }
}
pub fn read_soe_via_aoe(&self, idn: u32, read_length: u32) -> Result<Option<Vec<u8>>> {
self.read_write(Self::SOE_INDEX_GROUP, idn, read_length, None, DEFAULT_TIMEOUT_US)
}
pub fn write_soe_via_aoe(&self, idn: u32, data: &[u8]) -> Result<()> {
let mut read_ptr: *mut c_void = std::ptr::null_mut();
let mut bytes_read: u32 = 0;
let ret = unsafe {
ffi::AOEReadWrite(
self.master_index, self.slave_index,
Self::SOE_INDEX_GROUP, idn,
0, data.len() as u32, data.as_ptr(),
&mut read_ptr, &mut bytes_read, DEFAULT_TIMEOUT_US,
)
};
if !read_ptr.is_null() { unsafe { ffi::FreeMemory(read_ptr) }; }
if ret != 0 { Ok(()) } else { Err(DarraError::AoeFailed) }
}
pub fn initialize_slave_net_id(&self, net_id: &[u8; 6]) -> Result<()> {
self.write(1, 3, net_id)
}
pub fn ads_state_name(ads_state: u16) -> &'static str {
match ads_state {
0 => "Invalid", 1 => "Idle", 2 => "Reset", 3 => "Init",
4 => "Start", 5 => "Run", 6 => "Stop", 7 => "SaveCfg",
8 => "LoadCfg", 9 => "PowerFailure", 10 => "PowerGood",
11 => "Error", 12 => "Shutdown", 13 => "Suspend",
14 => "Resume", 15 => "Config", 16 => "ReConfig",
_ => "Unknown",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum AoeTransMode {
NoTransmission = 0,
Cyclic = 1,
OnChange = 2,
CyclicInDevice = 3,
OnChangeInDevice = 4,
}
#[derive(Debug, Clone)]
pub struct AoeSubscription {
pub slave_index: u16,
pub index_group: u32,
pub index_offset: u32,
pub length: u32,
pub trans_mode: AoeTransMode,
pub max_delay_us: u32,
pub cycle_time_us: u32,
pub timeout_us: i32,
}
impl AoeSubscription {
pub fn on_change(
slave_index: u16, index_group: u32, index_offset: u32,
length: u32, timeout_us: i32,
) -> Self {
Self {
slave_index, index_group, index_offset, length,
trans_mode: AoeTransMode::OnChange,
max_delay_us: 1000, cycle_time_us: 0, timeout_us,
}
}
pub fn cyclic(
slave_index: u16, index_group: u32, index_offset: u32,
length: u32, cycle_time_us: u32, timeout_us: i32,
) -> Self {
Self {
slave_index, index_group, index_offset, length,
trans_mode: AoeTransMode::Cyclic,
max_delay_us: cycle_time_us, cycle_time_us, timeout_us,
}
}
}
#[derive(Debug)]
struct ActiveSubscription {
slave_index: u16,
aoe_handle: u32,
subscription_index: i32,
timeout_us: i32,
config: AoeSubscription,
}
pub struct AoeSubscriptionManager {
master_index: u16,
subscriptions: HashMap<String, ActiveSubscription>,
next_id: u32,
}
impl AoeSubscriptionManager {
pub fn new(master_index: u16) -> Self {
Self { master_index, subscriptions: HashMap::new(), next_id: 1 }
}
pub fn subscribe(
&mut self, name: impl Into<String>, config: AoeSubscription,
callback: AOENotificationCallback, user_data: *mut std::ffi::c_void,
) -> Result<u32> {
let name = name.into();
if self.subscriptions.contains_key(&name) {
return Err(DarraError::Other(format!("订阅 '{}' 已存在", name)));
}
let mut aoe_handle: u32 = 0;
let add_ok = unsafe {
ffi::AOEAddNotification(
self.master_index, config.slave_index,
config.index_group, config.index_offset, config.length,
config.trans_mode as u32,
config.max_delay_us, config.cycle_time_us,
&mut aoe_handle, config.timeout_us,
)
};
if add_ok == 0 { return Err(DarraError::AoeFailed); }
let sub_idx = unsafe {
ffi::AOERegisterNotification(
config.slave_index, aoe_handle,
config.index_group, config.index_offset, config.length,
callback, user_data,
)
};
if sub_idx < 0 {
unsafe { ffi::AOEDelNotification(self.master_index, config.slave_index, aoe_handle, config.timeout_us); }
return Err(DarraError::AoeFailed);
}
let id = self.next_id;
self.next_id += 1;
self.subscriptions.insert(name, ActiveSubscription {
slave_index: config.slave_index, aoe_handle,
subscription_index: sub_idx, timeout_us: config.timeout_us, config,
});
Ok(id)
}
pub fn unsubscribe(&mut self, name: &str) -> Result<()> {
let sub = self.subscriptions.remove(name)
.ok_or_else(|| DarraError::Other(format!("订阅 '{}' 不存在", name)))?;
unsafe { ffi::AOEUnregisterNotification(sub.subscription_index) };
let del_ok = unsafe {
ffi::AOEDelNotification(self.master_index, sub.slave_index, sub.aoe_handle, sub.timeout_us)
};
if del_ok == 0 { Err(DarraError::AoeFailed) } else { Ok(()) }
}
pub fn unsubscribe_all(&mut self) {
let names: Vec<String> = self.subscriptions.keys().cloned().collect();
for name in names { let _ = self.unsubscribe(&name); }
}
pub fn has_subscription(&self, name: &str) -> bool { self.subscriptions.contains_key(name) }
pub fn subscription_count(&self) -> usize { self.subscriptions.len() }
pub fn subscription_names(&self) -> Vec<&str> { self.subscriptions.keys().map(|s| s.as_str()).collect() }
pub fn subscription_config(&self, name: &str) -> Option<&AoeSubscription> {
self.subscriptions.get(name).map(|s| &s.config)
}
pub fn subscription_handle(&self, name: &str) -> Option<u32> {
self.subscriptions.get(name).map(|s| s.aoe_handle)
}
}
impl Drop for AoeSubscriptionManager {
fn drop(&mut self) { self.unsubscribe_all(); }
}
impl std::fmt::Debug for AoeSubscriptionManager {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AoeSubscriptionManager")
.field("master_index", &self.master_index)
.field("active_subscriptions", &self.subscriptions.len())
.finish()
}
}
impl AoeSubscriptionManager {
pub fn active_subscriptions(&self) -> Vec<&str> {
self.subscriptions.keys().map(|k| k.as_str()).collect()
}
}
pub fn ads_state_description(ads_state: u16) -> &'static str {
match ads_state {
0 => "无效",
1 => "空闲",
2 => "复位",
3 => "初始化",
4 => "启动",
5 => "运行",
6 => "停止",
7 => "保存配置",
8 => "加载配置",
9 => "关机",
10 => "暂停",
11 => "重新配置",
15 => "错误",
_ => "未知状态",
}
}
impl AoEInstance {
pub fn read_blocking(
master_index: u16,
slave_index: u16,
index_group: u32,
index_offset: u32,
length: u32,
) -> std::thread::JoinHandle<Result<Option<Vec<u8>>>> {
std::thread::spawn(move || {
let aoe = AoEInstance::new(master_index, slave_index);
aoe.read(index_group, index_offset, length)
})
}
pub fn write_blocking(
master_index: u16,
slave_index: u16,
index_group: u32,
index_offset: u32,
data: Vec<u8>,
) -> std::thread::JoinHandle<Result<()>> {
std::thread::spawn(move || {
let aoe = AoEInstance::new(master_index, slave_index);
aoe.write(index_group, index_offset, &data)
})
}
}
#[cfg(feature = "async-tokio")]
impl AoEInstance {
pub async fn read_async(
&self,
index_group: u32,
index_offset: u32,
length: u32,
) -> Result<Option<Vec<u8>>> {
let master = self.master_index;
let slave = self.slave_index;
tokio::task::spawn_blocking(move || {
let aoe = AoEInstance::new(master, slave);
aoe.read(index_group, index_offset, length)
})
.await
.map_err(|e| DarraError::Other(format!("tokio join error: {}", e)))?
}
pub async fn write_async(
&self,
index_group: u32,
index_offset: u32,
data: Vec<u8>,
) -> Result<()> {
let master = self.master_index;
let slave = self.slave_index;
tokio::task::spawn_blocking(move || {
let aoe = AoEInstance::new(master, slave);
aoe.write(index_group, index_offset, &data)
})
.await
.map_err(|e| DarraError::Other(format!("tokio join error: {}", e)))?
}
}
impl crate::abstractions::MailboxProtocol for AoEInstance {
fn protocol_type(&self) -> u8 { 0x01 }
fn protocol_name(&self) -> &'static str { "AoE" }
fn is_supported(&self) -> bool {
AoEInstance::is_supported(self)
}
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, 0x01, &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, 0x01);
}
}
}