use crate::sync_manager_channel::{self, Direction, OperationMode};
use ethercrab_wire::{EtherCrabWireRead, EtherCrabWireSized};
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, ethercrab_wire::EtherCrabWireReadWrite)]
#[repr(u8)]
pub enum SiiOwner {
#[default]
Master = 0x00,
Pdi = 0x01,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, ethercrab_wire::EtherCrabWireReadWrite)]
#[wire(bytes = 2)]
pub struct SiiControl {
#[wire(bits = 1)]
pub access: SiiAccess,
#[wire(pre_skip = 4, bits = 1)]
pub emulate_sii: bool,
#[wire(bits = 1)]
pub read_size: SiiReadSize,
#[wire(bits = 1)]
pub address_type: SiiAddressSize,
#[wire(bits = 1)]
pub read: bool,
#[wire(bits = 1)]
pub write: bool,
#[wire(bits = 1)]
pub reload: bool,
#[wire(bits = 1)]
pub checksum_error: bool,
#[wire(bits = 1)]
pub device_info_error: bool,
#[wire(bits = 1)]
pub command_error: bool,
#[wire(bits = 1)]
pub write_error: bool,
#[wire(bits = 1)]
pub busy: bool,
}
impl SiiControl {
pub fn has_error(&self) -> bool {
self.checksum_error || self.device_info_error || self.write_error
}
pub fn error_reset(self) -> Self {
Self {
checksum_error: false,
device_info_error: false,
command_error: false,
write_error: false,
..self
}
}
fn read() -> Self {
Self {
read: true,
..Default::default()
}
}
fn write() -> Self {
Self {
access: SiiAccess::ReadWrite,
write: true,
..Default::default()
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, ethercrab_wire::EtherCrabWireReadWrite)]
#[repr(u8)]
pub enum SiiAccess {
#[default]
ReadOnly = 0x00,
ReadWrite = 0x01,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, ethercrab_wire::EtherCrabWireReadWrite)]
#[repr(u8)]
pub enum SiiReadSize {
#[default]
Octets4 = 0x00,
Octets8 = 0x01,
}
impl SiiReadSize {
pub fn chunk_len(&self) -> u16 {
match self {
SiiReadSize::Octets4 => 4,
SiiReadSize::Octets8 => 8,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, ethercrab_wire::EtherCrabWireReadWrite)]
#[repr(u8)]
pub enum SiiAddressSize {
#[default]
U8 = 0x00,
U16 = 0x01,
}
#[derive(PartialEq, ethercrab_wire::EtherCrabWireReadWrite)]
#[wire(bytes = 6)]
pub struct SiiRequest {
#[wire(bytes = 2)]
control: SiiControl,
#[wire(bytes = 2, post_skip = 16)]
address: u16,
}
impl core::fmt::Debug for SiiRequest {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("SiiRequest")
.field("control", &self.control)
.field("address", &format_args!("{:#06x}", self.address))
.finish()
}
}
impl SiiRequest {
pub fn read(address: u16) -> Self {
Self {
control: SiiControl::read(),
address,
}
}
pub fn write(address: u16) -> Self {
Self {
control: SiiControl::write(),
address,
}
}
}
#[derive(Debug, Copy, Clone, ethercrab_wire::EtherCrabWireRead)]
#[repr(u16)]
pub enum SiiCoding {
PdiControl = 0x0000,
PdiConfiguration = 0x0001,
SyncImpulseLen = 0x0002,
PdiConfiguration2 = 0x0003,
ConfiguredStationAlias = 0x0004,
Checksum = 0x0007,
VendorId = 0x0008,
ProductCode = 0x000A,
RevisionNumber = 0x000C,
SerialNumber = 0x000E,
Reserved = 0x0010,
BootstrapReceiveMailboxOffset = 0x0014,
BootstrapReceiveMailboxSize = 0x0015,
BootstrapSendMailboxOffset = 0x0016,
BootstrapSendMailboxSize = 0x0017,
StandardReceiveMailboxOffset = 0x0018,
StandardReceiveMailboxSize = 0x0019,
StandardSendMailboxOffset = 0x001A,
StandardSendMailboxSize = 0x001B,
MailboxProtocol = 0x001C,
Size = 0x003E,
Version = 0x003F,
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, ethercrab_wire::EtherCrabWireRead)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u16)]
pub enum CategoryType {
#[default]
Nop = 0,
#[wire(alternatives = [2,3,4,5,6,7,8,9])]
DeviceSpecific = 1,
Strings = 10,
DataTypes = 20,
General = 30,
Fmmu = 40,
SyncManager = 41,
FmmuExtended = 42,
SyncUnit = 43,
TxPdo = 50,
RxPdo = 51,
DistributedClock = 60,
End = 0xffff,
}
#[derive(Debug, Copy, Clone)]
pub enum PdoType {
Tx = 50,
Rx = 51,
}
impl From<PdoType> for CategoryType {
fn from(value: PdoType) -> Self {
match value {
PdoType::Tx => Self::TxPdo,
PdoType::Rx => Self::RxPdo,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, ethercrab_wire::EtherCrabWireRead)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum FmmuUsage {
#[wire(alternatives = [0xff])]
Unused = 0x00,
Outputs = 0x01,
Inputs = 0x02,
SyncManagerStatus = 0x03,
}
#[derive(Debug, Copy, Clone, PartialEq, ethercrab_wire::EtherCrabWireRead)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[wire(bytes = 3)]
pub struct FmmuEx {
#[wire(pre_skip_bytes = 1, bytes = 1, post_skip_bytes = 1)]
pub sync_manager: u8,
}
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PortStatuses(pub [PortStatus; 4]);
impl EtherCrabWireSized for PortStatuses {
const PACKED_LEN: usize = 2;
type Buffer = [u8; Self::PACKED_LEN];
fn buffer() -> Self::Buffer {
[0u8; Self::PACKED_LEN]
}
}
impl EtherCrabWireRead for PortStatuses {
fn unpack_from_slice(buf: &[u8]) -> Result<Self, ethercrab_wire::WireError> {
let Some(&[lo, hi]) = buf.get(0..Self::PACKED_LEN) else {
return Err(ethercrab_wire::WireError::ReadBufferTooShort);
};
let p1 = lo & 0x0f;
let p2 = (lo >> 4) & 0x0f;
let p3 = hi & 0x0f;
let p4 = (hi >> 4) & 0x0f;
Ok(Self([
PortStatus::from(p1),
PortStatus::from(p2),
PortStatus::from(p3),
PortStatus::from(p4),
]))
}
}
#[derive(Debug, Default, PartialEq, Eq, ethercrab_wire::EtherCrabWireRead)]
#[wire(bytes = 18)]
pub struct SiiGeneral {
#[wire(bytes = 1)]
pub(crate) group_string_idx: u8,
#[wire(bytes = 1)]
pub(crate) image_string_idx: u8,
#[wire(bytes = 1)]
pub(crate) order_string_idx: u8,
#[wire(bytes = 1, post_skip_bytes = 1)]
pub name_string_idx: u8,
#[wire(bytes = 1)]
pub coe_details: CoeDetails,
#[wire(bytes = 1)]
pub(crate) foe_enabled: bool,
#[wire(bytes = 1, post_skip_bytes = 3)]
pub(crate) eoe_enabled: bool,
#[wire(bytes = 1)]
pub(crate) flags: Flags,
#[wire(bytes = 2)]
pub(crate) ebus_current: i16,
#[wire(bytes = 2)]
pub(crate) ports: PortStatuses,
#[wire(bytes = 2)]
pub(crate) physical_memory_addr: u16,
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, ethercrab_wire::EtherCrabWireRead)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum PortStatus {
#[default]
Unused = 0x00,
Mii = 0x01,
Reserved = 0x02,
Ebus = 0x03,
FastHotConnect = 0x04,
}
bitflags::bitflags! {
#[derive(Debug, Default, PartialEq, Eq)]
pub struct Flags: u8 {
const ENABLE_SAFE_OP = 0x01;
const ENABLE_NOT_LRW = 0x02;
const MAILBOX_DLL = 0x04;
const IDENT_AL_STATUS = 0x08;
const IDENT_PHY_M = 0x10;
}
}
impl EtherCrabWireSized for Flags {
const PACKED_LEN: usize = 1;
type Buffer = [u8; Self::PACKED_LEN];
fn buffer() -> Self::Buffer {
[0u8; Self::PACKED_LEN]
}
}
impl EtherCrabWireRead for Flags {
fn unpack_from_slice(buf: &[u8]) -> Result<Self, ethercrab_wire::WireError> {
u8::unpack_from_slice(buf)
.and_then(|value| Self::from_bits(value).ok_or(ethercrab_wire::WireError::InvalidValue))
}
}
bitflags::bitflags! {
#[derive(Debug, Default, PartialEq, Eq)]
pub struct CoeDetails: u8 {
const ENABLE_SDO = 0x01;
const ENABLE_SDO_INFO = 0x02;
const ENABLE_PDO_ASSIGN = 0x04;
const ENABLE_PDO_CONFIG = 0x08;
const ENABLE_STARTUP_UPLOAD = 0x10;
const ENABLE_COMPLETE_ACCESS = 0x20;
}
}
impl EtherCrabWireSized for CoeDetails {
const PACKED_LEN: usize = 1;
type Buffer = [u8; Self::PACKED_LEN];
fn buffer() -> Self::Buffer {
[0u8; Self::PACKED_LEN]
}
}
impl EtherCrabWireRead for CoeDetails {
fn unpack_from_slice(buf: &[u8]) -> Result<Self, ethercrab_wire::WireError> {
u8::unpack_from_slice(buf)
.and_then(|value| Self::from_bits(value).ok_or(ethercrab_wire::WireError::InvalidValue))
}
}
#[derive(Copy, Clone, PartialEq, Eq, ethercrab_wire::EtherCrabWireRead)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[wire(bytes = 8)]
pub struct SyncManager {
#[wire(bytes = 2)]
pub(crate) start_addr: u16,
#[wire(bytes = 2)]
pub(crate) length: u16,
#[wire(bytes = 1, post_skip_bytes = 1)]
pub(crate) control: sync_manager_channel::Control,
#[wire(bytes = 1)]
pub(crate) enable: SyncManagerEnable,
#[wire(bytes = 1)]
pub(crate) usage_type: SyncManagerType,
}
impl SyncManager {
pub(crate) fn usage_type(&self) -> SyncManagerType {
if self.usage_type != SyncManagerType::Unknown {
self.usage_type
} else {
match (self.control.operation_mode, self.control.direction) {
(OperationMode::Normal, Direction::MasterRead) => SyncManagerType::ProcessDataRead,
(OperationMode::Normal, Direction::MasterWrite) => {
SyncManagerType::ProcessDataWrite
}
(OperationMode::Mailbox, Direction::MasterRead) => SyncManagerType::MailboxRead,
(OperationMode::Mailbox, Direction::MasterWrite) => SyncManagerType::MailboxWrite,
}
}
}
}
impl core::fmt::Debug for SyncManager {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("SyncManager")
.field("start_addr", &format_args!("{:#06x}", self.start_addr))
.field("length", &format_args!("{:#06x}", self.length))
.field("control", &self.control)
.field("enable", &self.enable)
.field("usage_type", &self.usage_type)
.finish()
}
}
bitflags::bitflags! {
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct SyncManagerEnable: u8 {
const ENABLE = 0x01;
const IS_FIXED = 0x02;
const IS_VIRTUAL = 0x04;
const OP_ONLY = 0x08;
}
}
impl EtherCrabWireSized for SyncManagerEnable {
const PACKED_LEN: usize = 1;
type Buffer = [u8; Self::PACKED_LEN];
fn buffer() -> Self::Buffer {
[0u8; Self::PACKED_LEN]
}
}
impl EtherCrabWireRead for SyncManagerEnable {
fn unpack_from_slice(buf: &[u8]) -> Result<Self, ethercrab_wire::WireError> {
u8::unpack_from_slice(buf)
.and_then(|value| Self::from_bits(value).ok_or(ethercrab_wire::WireError::InvalidValue))
}
}
#[cfg(feature = "defmt")]
impl defmt::Format for SyncManagerEnable {
fn format(&self, f: defmt::Formatter) {
defmt::write!(f, "{=u8:b}", self.bits())
}
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, ethercrab_wire::EtherCrabWireRead)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum SyncManagerType {
#[default]
Unknown = 0x00,
MailboxWrite = 0x01,
MailboxRead = 0x02,
ProcessDataWrite = 0x03,
ProcessDataRead = 0x04,
}
#[derive(Debug, Copy, Clone, PartialEq, ethercrab_wire::EtherCrabWireRead)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[wire(bytes = 8)]
pub struct Pdo {
#[wire(bytes = 2)]
pub(crate) index: u16,
#[wire(bytes = 1)]
pub(crate) num_entries: u8,
#[wire(bytes = 1, post_skip_bytes = 4)]
pub(crate) sync_manager: u8,
#[wire(skip)]
pub(crate) bit_len: u16,
}
#[derive(Debug, Clone, PartialEq, ethercrab_wire::EtherCrabWireRead)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[wire(bytes = 8)]
pub struct PdoEntry {
#[wire(bytes = 1, pre_skip_bytes = 5, post_skip_bytes = 2)]
pub(crate) data_length_bits: u8,
}
bitflags::bitflags! {
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct PdoFlags: u16 {
const PDO_MANDATORY = 0x0001;
const PDO_DEFAULT = 0x0002;
const PDO_OVERSAMPLE = 0x0004;
const PDO_FIXED_CONTENT = 0x0010;
const PDO_VIRTUAL_CONTENT = 0x0020;
const PDO_DOWNLOAD_ANYWAY = 0x0040;
const PDO_FROM_MODULE = 0x0080;
const PDO_MODULE_ALIGN = 0x0100;
const PDO_DEPEND_ON_SLOT = 0x0200;
const PDO_DEPEND_ON_SLOT_GROUP = 0x0400;
const PDO_OVERWRITTEN_BY_MODULE = 0x0800;
const PDO_CONFIGURABLE = 0x1000;
const PDO_AUTO_PDO_NAME = 0x2000;
const PDO_DIS_AUTO_EXCLUDE = 0x4000;
const PDO_WRITABLE = 0x8000;
}
}
impl EtherCrabWireSized for PdoFlags {
const PACKED_LEN: usize = 2;
type Buffer = [u8; Self::PACKED_LEN];
fn buffer() -> Self::Buffer {
[0u8; Self::PACKED_LEN]
}
}
impl EtherCrabWireRead for PdoFlags {
fn unpack_from_slice(buf: &[u8]) -> Result<Self, ethercrab_wire::WireError> {
u16::unpack_from_slice(buf)
.and_then(|value| Self::from_bits(value).ok_or(ethercrab_wire::WireError::InvalidValue))
}
}
#[cfg(feature = "defmt")]
impl defmt::Format for PdoFlags {
fn format(&self, f: defmt::Formatter) {
defmt::write!(f, "{=u16:b}", self.bits())
}
}
bitflags::bitflags! {
#[derive(Copy, Clone, Default, Debug, PartialEq)]
pub struct MailboxProtocols: u8 {
const AOE = 0x01;
const EOE = 0x02;
const COE = 0x04;
const FOE = 0x08;
const SOE = 0x10;
const VOE = 0x20;
}
}
impl EtherCrabWireSized for MailboxProtocols {
const PACKED_LEN: usize = 2;
type Buffer = [u8; Self::PACKED_LEN];
fn buffer() -> Self::Buffer {
[0u8; Self::PACKED_LEN]
}
}
impl EtherCrabWireRead for MailboxProtocols {
fn unpack_from_slice(buf: &[u8]) -> Result<Self, ethercrab_wire::WireError> {
buf.first()
.ok_or(ethercrab_wire::WireError::ReadBufferTooShort)
.and_then(|res| Self::from_bits(*res).ok_or(ethercrab_wire::WireError::InvalidValue))
}
}
#[cfg(feature = "defmt")]
impl defmt::Format for MailboxProtocols {
fn format(&self, f: defmt::Formatter) {
defmt::write!(f, "{=u8:b}", self.bits())
}
}
#[derive(Copy, Clone, Default, PartialEq, ethercrab_wire::EtherCrabWireRead)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[wire(bytes = 10)]
pub struct DefaultMailbox {
#[wire(bytes = 2)]
pub subdevice_receive_offset: u16,
#[wire(bytes = 2)]
pub subdevice_receive_size: u16,
#[wire(bytes = 2)]
pub subdevice_send_offset: u16,
#[wire(bytes = 2)]
pub subdevice_send_size: u16,
#[wire(bytes = 2)]
pub supported_protocols: MailboxProtocols,
}
impl DefaultMailbox {
pub fn has_mailbox(&self) -> bool {
!self.supported_protocols.is_empty() && self.subdevice_receive_size > 0
|| self.subdevice_send_size > 0
}
}
impl core::fmt::Debug for DefaultMailbox {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("MailboxConfig")
.field(
"subdevice_receive_offset",
&format_args!("{:#06x}", self.subdevice_receive_offset),
)
.field(
"subdevice_receive_size",
&format_args!("{:#06x}", self.subdevice_receive_size),
)
.field(
"subdevice_send_offset",
&format_args!("{:#06x}", self.subdevice_send_offset),
)
.field(
"subdevice_send_size",
&format_args!("{:#06x}", self.subdevice_send_size),
)
.field("supported_protocols", &self.supported_protocols)
.finish()
}
}
#[cfg(test)]
mod tests {
use crate::sync_manager_channel::Control;
use super::*;
use ethercrab_wire::EtherCrabWireWriteSized;
#[test]
fn sii_control_pack() {
let ctl = SiiControl {
access: SiiAccess::ReadWrite,
emulate_sii: false,
read_size: SiiReadSize::Octets8,
address_type: SiiAddressSize::U8,
read: false,
write: false,
reload: false,
checksum_error: false,
device_info_error: false,
command_error: false,
write_error: false,
busy: true,
};
assert_eq!(ctl.pack(), [0b0100_0001, 0b1000_0000],);
}
#[test]
fn sii_request_read_pack() {
let packed = SiiRequest::read(0x1234).pack();
assert_eq!(packed, [0x00, 0x01, 0x34, 0x12, 0x00, 0x00]);
}
#[test]
fn sii_control_unpack() {
let ctl = SiiControl {
access: SiiAccess::ReadWrite,
emulate_sii: false,
read_size: SiiReadSize::Octets8,
address_type: SiiAddressSize::U8,
read: false,
write: false,
reload: false,
checksum_error: false,
device_info_error: false,
command_error: false,
write_error: false,
busy: true,
};
assert_eq!(
SiiControl::unpack_from_slice(&[0b0100_0001, 0b1000_0000]),
Ok(ctl)
);
}
#[test]
fn sii_request_read_unpack() {
let packed = SiiRequest::read(0x1234);
let buf = [0x00, 0x01, 0x34, 0x12, 0x00, 0x00];
assert_eq!(SiiRequest::unpack_from_slice(&buf), Ok(packed));
}
#[test]
fn sii_general_ek1100() {
let expected = SiiGeneral {
group_string_idx: 2,
image_string_idx: 0,
order_string_idx: 1,
name_string_idx: 4,
coe_details: CoeDetails::empty(),
foe_enabled: false,
eoe_enabled: false,
flags: Flags::empty(),
ebus_current: -2000,
ports: PortStatuses([
PortStatus::Ebus,
PortStatus::Unused,
PortStatus::Unused,
PortStatus::Unused,
]),
physical_memory_addr: 0,
};
let raw = [2u8, 0, 1, 4, 2, 0, 0, 0, 0, 0, 0, 0, 48, 248, 3, 0, 0, 0];
assert_eq!(SiiGeneral::unpack_from_slice(&raw), Ok(expected))
}
#[test]
fn fmmu_ex() {
let data = [0xaa, 0xbb, 0xcc];
assert_eq!(
FmmuEx::unpack_from_slice(&data),
Ok(FmmuEx { sync_manager: 0xbb })
);
}
#[test]
fn recover_unknown_sm_types() {
assert_eq!(
SyncManager {
start_addr: 0x1000,
length: 0x0080,
control: Control {
operation_mode: OperationMode::Mailbox,
direction: Direction::MasterWrite,
ecat_event_enable: true,
dls_user_event_enable: true,
watchdog_enable: false,
},
enable: SyncManagerEnable::ENABLE,
usage_type: SyncManagerType::Unknown,
}
.usage_type(),
SyncManagerType::MailboxWrite
);
assert_eq!(
SyncManager {
start_addr: 0x10c0,
length: 0x0080,
control: Control {
operation_mode: OperationMode::Mailbox,
direction: Direction::MasterRead,
ecat_event_enable: true,
dls_user_event_enable: true,
watchdog_enable: false,
},
enable: SyncManagerEnable::ENABLE,
usage_type: SyncManagerType::Unknown,
}
.usage_type(),
SyncManagerType::MailboxRead
);
assert_eq!(
SyncManager {
start_addr: 0x1180,
length: 0x0006,
control: Control {
operation_mode: OperationMode::Normal,
direction: Direction::MasterWrite,
ecat_event_enable: false,
dls_user_event_enable: true,
watchdog_enable: false,
},
enable: SyncManagerEnable::ENABLE,
usage_type: SyncManagerType::Unknown,
}
.usage_type(),
SyncManagerType::ProcessDataWrite
);
assert_eq!(
SyncManager {
start_addr: 0x1480,
length: 0x0006,
control: Control {
operation_mode: OperationMode::Normal,
direction: Direction::MasterRead,
ecat_event_enable: false,
dls_user_event_enable: false,
watchdog_enable: false,
},
enable: SyncManagerEnable::ENABLE,
usage_type: SyncManagerType::Unknown,
}
.usage_type(),
SyncManagerType::ProcessDataRead
);
}
}