use crate::{
error::{EthercatError, EthercatResult},
data::{self, PduData, Storage, Field, Cursor},
rawmaster::{RawMaster, SlaveAddress},
registers,
eeprom,
};
use std::sync::Arc;
use bilge::prelude::*;
pub const WORD: u16 = eeprom::WORD as _;
pub struct Sii {
master: Arc<RawMaster>,
slave: SlaveAddress,
mask: u16,
writable: bool,
locked: bool,
}
impl Sii {
pub async fn new(master: Arc<RawMaster>, slave: SlaveAddress) -> EthercatResult<Sii, SiiError> {
let status = master.read(slave, registers::sii::control).await.one()?;
let mask = match status.address_unit() {
registers::SiiUnit::Byte => 0xff,
registers::SiiUnit::Word => 0xffff,
};
if status.checksum_error()
{return Err(EthercatError::Slave(slave, SiiError::Checksum))};
Ok(Self {
master,
slave,
mask,
writable: status.write_access(),
locked: false,
})
}
pub fn writable(&self) -> bool {self.writable}
pub async fn acquire(&mut self) -> EthercatResult {
if ! self.locked {
self.locked = true;
self.master.write(self.slave, registers::sii::access, {
let mut config = registers::SiiAccess::default();
config.set_owner(registers::SiiOwner::EthercatDL);
config
}).await.one()?;
}
Ok(())
}
pub async fn release(&mut self) -> EthercatResult {
if self.locked {
self.locked = false;
self.master.write(self.slave, registers::sii::access, {
let mut config = registers::SiiAccess::default();
config.set_owner(registers::SiiOwner::Pdi);
config
}).await.one()?;
}
Ok(())
}
pub async fn read<T: PduData>(&mut self, field: Field<T>) -> EthercatResult<T, SiiError> {
let mut buffer = T::Packed::uninit();
self.read_slice(field.byte as _, buffer.as_mut()).await?;
Ok(T::unpack(buffer.as_ref())?)
}
pub async fn read_slice<'b>(&mut self, address: u16, value: &'b mut [u8]) -> EthercatResult<&'b [u8], SiiError> {
if address & !self.mask != 0
{return Err(EthercatError::Master("wrong EEPROM address: address range is 1 byte only"))}
let mut start = (address % WORD) as usize;
let mut cursor = Cursor::new(value.as_mut());
while cursor.remain().len() != 0 {
self.master.write(self.slave, registers::sii::control_address, registers::SiiControlAddress {
control: {
let mut control = registers::SiiControl::default();
control.set_read_operation(true);
control
},
address: (address + cursor.position() as u16) / WORD,
}).await.one()?;
let status = loop {
if let Ok(answer) = self.master.read(self.slave, registers::sii::control).await.one() {
if ! answer.busy() && ! answer.read_operation()
{break answer}
}
};
if status.command_error()
{return Err(EthercatError::Slave(self.slave, SiiError::Command))}
if status.device_info_error()
{return Err(EthercatError::Slave(self.slave, SiiError::DeviceInfo))}
let size = match status.read_size() {
registers::SiiTransaction::Bytes4 => 4,
registers::SiiTransaction::Bytes8 => 8,
}.min(start + cursor.remain().len());
let data = self.master.read(self.slave, registers::sii::data).await.one()?;
cursor.write(&data[start .. size]).unwrap();
start = 0;
}
Ok(value)
}
pub async fn write<T: PduData>(&mut self, field: Field<T>, value: &T) -> EthercatResult<(), SiiError> {
let mut buffer = T::Packed::uninit();
value.pack(buffer.as_mut()).unwrap();
self.write_slice(field.byte as _, buffer.as_ref()).await
}
pub async fn write_slice(&mut self, address: u16, value: &[u8]) -> EthercatResult<(), SiiError> {
if address % WORD != 0
{return Err(EthercatError::Master("address must be word-aligned"))}
let mut cursor = Cursor::new(value.as_ref());
while cursor.remain().len() != 0 {
self.master.write(self.slave, registers::sii::control_address_data, registers::SiiControlAddressData {
control: {
let mut control = registers::SiiControl::default();
control.set_write_operation(true);
control
},
address: (address + cursor.position() as u16) / WORD,
reserved: 0,
data: cursor.unpack().unwrap(),
}).await.one()?;
let status = loop {
if let Ok(answer) = self.master.read(self.slave, registers::sii::control).await.one() {
if ! answer.busy() && ! answer.write_operation()
{break answer}
}
};
if status.command_error()
{return Err(EthercatError::Slave(self.slave, SiiError::Command))}
if status.write_error()
{return Err(EthercatError::Slave(self.slave, SiiError::Write))}
}
Ok(())
}
pub async fn reload(&mut self) -> EthercatResult<(), SiiError> {
self.master.write(self.slave, registers::sii::control, {
let mut control = registers::SiiControl::default();
control.set_reload_operation(true);
control
}).await.one()?;
let status = loop {
if let Ok(answer) = self.master.read(self.slave, registers::sii::control).await.one() {
if ! answer.busy() && ! answer.reload_operation()
{break answer}
}
};
if status.command_error()
{return Err(EthercatError::Slave(self.slave, SiiError::Command))}
if status.checksum_error()
{return Err(EthercatError::Slave(self.slave, SiiError::Checksum))}
if status.device_info_error()
{return Err(EthercatError::Slave(self.slave, SiiError::DeviceInfo))}
Ok(())
}
pub fn categories(&mut self) -> SiiCursor<'_> {
SiiCursor::new(self, eeprom::categories)
}
pub async fn strings(&mut self) -> EthercatResult<Vec<String>, SiiError> {
let mut categories = self.categories();
loop {
let category = categories.unpack::<Category>().await?;
if category.ty() == CategoryType::Strings {
let num = categories.unpack::<u8>().await?;
let mut strings = Vec::with_capacity(num as _);
for _ in 0 .. num {
let len = categories.unpack::<u8>().await?;
let mut buffer = vec![0; len as _];
categories.read(&mut buffer).await?;
strings.push(String::from_utf8(buffer)
.map_err(|_| EthercatError::<SiiError>::Master("strings in EEPROM are not UTF8"))?
);
}
return Ok(strings)
}
else if category.ty() == CategoryType::End {
return Err(EthercatError::Master("no strings category in EEPROM"))
}
else {
categories.advance(WORD*category.size());
}
}
}
pub async fn generals(&mut self) -> EthercatResult<General, SiiError> {
let mut categories = self.categories();
loop {
let category = categories.unpack::<Category>().await?;
if category.ty() == CategoryType::General {
return categories.unpack::<General>().await
}
else if category.ty() == CategoryType::End {
return Err(EthercatError::Master("no general category in EEPROM"))
}
else {
categories.advance(WORD*category.size());
}
}
}
}
pub struct SiiCursor<'a> {
sii: &'a mut Sii,
position: u16,
end: u16,
}
impl<'a> SiiCursor<'a> {
pub fn new(sii: &'a mut Sii, position: u16) -> Self
{Self {sii, position, end: u16::MAX}}
pub fn position(&self) -> u16
{self.position}
pub fn remain(&self) -> u16
{self.end.max(self.position) - self.position}
pub fn shadow(&mut self) -> SiiCursor<'_> {
SiiCursor {
sii: self.sii,
position: self.position,
end: self.end,
}
}
pub fn sub(&mut self, size: u16) -> SiiCursor<'_> {
let position = self.position;
self.position += size;
SiiCursor {
sii: self.sii,
position,
end: self.position,
}
}
pub fn advance(&mut self, increment: u16) {
self.position += increment;
}
pub async fn read(&mut self, dst: &mut [u8]) -> EthercatResult<(), SiiError> {
self.sii.read_slice(self.position, dst).await?;
self.position += dst.len() as u16;
Ok(())
}
pub async fn unpack<T: PduData>(&mut self) -> EthercatResult<T, SiiError> {
let mut buffer = T::Packed::uninit();
self.read(buffer.as_mut()).await?;
Ok(T::unpack(buffer.as_ref())?)
}
pub async fn write(&mut self, dst: &[u8]) -> EthercatResult<(), SiiError> {
self.sii.write_slice(self.position, dst).await?;
self.position += dst.len() as u16;
Ok(())
}
pub async fn pack<T: PduData>(&mut self, value: T) -> EthercatResult<(), SiiError> {
let mut buffer = T::Packed::uninit();
value.pack(buffer.as_mut()).unwrap();
self.write(buffer.as_ref()).await
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum SiiError {
Command,
Checksum,
DeviceInfo,
Write,
}
impl From<EthercatError<()>> for EthercatError<SiiError> {
fn from(src: EthercatError<()>) -> Self {src.upgrade()}
}
#[bitsize(32)]
#[derive(TryFromBits, DebugBits, Copy, Clone)]
pub struct Category {
pub ty: CategoryType,
pub size: u16,
}
data::bilge_pdudata!(Category, u32);
#[bitsize(16)]
#[derive(FromBits, Debug, Copy, Clone, Eq, PartialEq)]
pub enum CategoryType {
Nop = 0,
Strings = 10,
DataTypes = 20,
General = 30,
Fmmu = 40,
FmmuExtension = 42,
SyncManager = 41,
SyncUnit = 43,
PdoWrite = 50,
PdoRead = 51,
Dc = 60,
#[fallback]
Unsupported = 0x0800,
End = 0xffff,
}
#[repr(packed)]
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct General {
pub group: u8,
pub image: u8,
pub order: u8,
pub name: u8,
_reserved0: u8,
pub coe: CoeDetails,
pub foe: FoeDetails,
pub eoe: EoeDetails,
_reserved1: [u8;3],
pub flags: GeneralFlags,
pub ebus_current: i16,
pub group2: u8,
_reserved2: u8,
pub ports: PhysicialPorts,
pub identification_address: u16,
}
data::packed_pdudata!(General);
#[bitsize(8)]
#[derive(FromBits, DebugBits, Copy, Clone, Eq, PartialEq)]
pub struct CoeDetails {
pub sdo: bool,
pub sdo_info: bool,
pub pdo_assign: bool,
pub pdo_config: bool,
pub startup_upload: bool,
pub sdo_complete: bool,
_reserved: u2,
}
#[bitsize(8)]
#[derive(FromBits, DebugBits, Copy, Clone, Eq, PartialEq)]
pub struct FoeDetails {
pub enable: bool,
reserved: u7,
}
#[bitsize(8)]
#[derive(FromBits, DebugBits, Copy, Clone, Eq, PartialEq)]
pub struct EoeDetails {
pub enable: bool,
reserved: u7,
}
#[bitsize(8)]
#[derive(FromBits, DebugBits, Copy, Clone, Eq, PartialEq)]
pub struct GeneralFlags {
pub enable_safeop: bool,
pub enable_notlrw: bool,
pub mbox_dll: bool,
pub ident_alsts: bool,
pub ident_phym: bool,
reserved: u3,
}
#[bitsize(16)]
#[derive(FromBits, DebugBits, Copy, Clone, Eq, PartialEq)]
pub struct PhysicialPorts {
pub ports: [PhysicalPort; 4],
}
#[bitsize(4)]
#[derive(FromBits, Debug, Copy, Clone, Eq, PartialEq)]
pub enum PhysicalPort {
#[fallback]
Disabled = 0x0,
Mii = 0x1,
Reserved = 0x2,
Ebus = 0x3,
FastHotconnect = 0x4,
}
#[bitsize(8)]
#[derive(FromBits, Debug, Copy, Clone, Eq, PartialEq)]
pub enum FmmuUsage {
#[fallback]
Disabled = 0,
Outputs = 1,
Inputs = 2,
SyncManagerStatus = 3,
}
data::bilge_pdudata!(FmmuUsage, u8);
#[bitsize(24)]
#[derive(TryFromBits, DebugBits, Copy, Clone, Eq, PartialEq)]
pub struct FmmuExtension {
pub op_only: bool,
pub sm_defined: bool,
pub su_defined: bool,
reserved: u5,
pub sm: u8,
pub su: u8,
}
data::bilge_pdudata!(FmmuExtension, u24);
#[bitsize(64)]
#[derive(TryFromBits, DebugBits, Copy, Clone, Eq, PartialEq)]
pub struct SyncManager {
pub address: u16,
pub length: u16,
pub control: u8,
pub status: u8,
pub enable: SyncManagerEnable,
pub usage: SyncManagerUsage,
}
data::bilge_pdudata!(SyncManager, u64);
#[bitsize(8)]
#[derive(FromBits, DebugBits, Copy, Clone, Eq, PartialEq)]
pub struct SyncManagerEnable {
pub enable: bool,
pub fixed_content: bool,
pub virtual_sync_manager: bool,
pub oponly: bool,
_reserved: u4,
}
#[bitsize(8)]
#[derive(TryFromBits, Debug, Copy, Clone, Eq, PartialEq)]
pub enum SyncManagerUsage {
Disabled = 0x0,
MailboxOut = 0x1,
MailboxIn = 0x2,
ProcessOut = 0x3,
ProcessIn = 0x4,
}
#[repr(packed)]
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct DistributedClock {
pub period: u32,
pub shift0: u32,
pub shift1: u32,
pub sync1_period_factor: i16,
pub assign_activate: u16,
pub sync0_period_factor: i16,
pub name: u8,
pub description: u8,
pub _reserved0: [u8;4],
}
data::packed_pdudata!(DistributedClock);
#[bitsize(8)]
#[derive(TryFromBits, DebugBits, Copy, Clone, Eq, PartialEq)]
pub struct SyncUnit {
pub separate_su: bool,
pub separate_frame: bool,
pub depend_on_input_state: bool,
pub frame_repeat_support: bool,
reserved: u4,
}
data::bilge_pdudata!(SyncUnit, u8);
#[repr(packed)]
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Pdo {
pub index: u16,
pub entries: u8,
pub synchronization: u8,
pub name: u8,
pub flags: u16,
}
data::packed_pdudata!(Pdo);
#[repr(packed)]
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct PdoEntry {
pub index: u16,
pub sub: u8,
pub name: u8,
pub dtype: u8,
pub bitlen: u8,
pub flags: u16,
}
data::packed_pdudata!(PdoEntry);