use crate::{
al_control::AlControl,
al_status_code::AlStatusCode,
command::Command,
dc,
eeprom::types::SyncManager,
error::{Error, Item},
fmmu::Fmmu,
fmt,
pdi::PdiOffset,
pdu_loop::{PduLoop, ReceivedPdu},
register::RegisterAddress,
subdevice::SubDevice,
subdevice_group::{self, SubDeviceGroupHandle},
subdevice_state::SubDeviceState,
timer_factory::IntoTimeout,
MainDeviceConfig, SubDeviceGroup, Timeouts, BASE_SUBDEVICE_ADDRESS,
};
use core::{
cell::UnsafeCell,
mem::size_of,
sync::atomic::{AtomicU16, Ordering},
};
use ethercrab_wire::{EtherCrabWireSized, EtherCrabWireWrite};
use heapless::FnvIndexMap;
#[doc(alias = "Client")]
#[doc(alias = "Master")]
#[derive(Debug)]
pub struct MainDevice<'sto> {
pub(crate) pdu_loop: PduLoop<'sto>,
num_subdevices: AtomicU16,
dc_reference_configured_address: AtomicU16,
pub(crate) timeouts: Timeouts,
pub(crate) config: MainDeviceConfig,
}
unsafe impl<'sto> Sync for MainDevice<'sto> {}
impl<'sto> MainDevice<'sto> {
pub const fn new(
pdu_loop: PduLoop<'sto>,
timeouts: Timeouts,
config: MainDeviceConfig,
) -> Self {
Self {
pdu_loop,
num_subdevices: AtomicU16::new(0),
dc_reference_configured_address: AtomicU16::new(0),
timeouts,
config,
}
}
async fn blank_memory<const LEN: usize>(&self, start: impl Into<u16>) -> Result<(), Error> {
let start = start.into();
self.pdu_loop
.pdu_broadcast_zeros(
start,
LEN as u16,
self.timeouts.pdu,
self.config.retry_behaviour.retry_count(),
)
.await
}
async fn reset_subdevices(&self) -> Result<(), Error> {
fmt::debug!("Beginning reset");
Command::bwr(RegisterAddress::AlControl.into())
.ignore_wkc()
.send(self, AlControl::reset())
.await?;
for fmmu_idx in 0..16 {
self.blank_memory::<{ Fmmu::PACKED_LEN }>(RegisterAddress::fmmu(fmmu_idx))
.await?;
}
for sm_idx in 0..16 {
self.blank_memory::<{ SyncManager::PACKED_LEN }>(RegisterAddress::sync_manager(sm_idx))
.await?;
}
self.blank_memory::<{ size_of::<u8>() }>(RegisterAddress::DcCyclicUnitControl)
.await?;
self.blank_memory::<{ size_of::<u64>() }>(RegisterAddress::DcSystemTime)
.await?;
self.blank_memory::<{ size_of::<u64>() }>(RegisterAddress::DcSystemTimeOffset)
.await?;
self.blank_memory::<{ size_of::<u32>() }>(RegisterAddress::DcSystemTimeTransmissionDelay)
.await?;
self.blank_memory::<{ size_of::<u32>() }>(RegisterAddress::DcSystemTimeDifference)
.await?;
self.blank_memory::<{ size_of::<u8>() }>(RegisterAddress::DcSyncActive)
.await?;
self.blank_memory::<{ size_of::<u32>() }>(RegisterAddress::DcSyncStartTime)
.await?;
self.blank_memory::<{ size_of::<u32>() }>(RegisterAddress::DcSync0CycleTime)
.await?;
self.blank_memory::<{ size_of::<u32>() }>(RegisterAddress::DcSync1CycleTime)
.await?;
Command::bwr(RegisterAddress::DcControlLoopParam3.into())
.ignore_wkc()
.send(self, 0x0c00u16)
.await?;
Command::bwr(RegisterAddress::DcControlLoopParam1.into())
.ignore_wkc()
.send(self, 0x1000u16)
.await?;
fmt::debug!("--> Reset complete");
Ok(())
}
pub async fn init<const MAX_SUBDEVICES: usize, G>(
&self,
now: impl Fn() -> u64 + Copy,
mut group_filter: impl for<'g> FnMut(
&'g G,
&SubDevice,
) -> Result<&'g dyn SubDeviceGroupHandle, Error>,
) -> Result<G, Error>
where
G: Default,
{
let groups = G::default();
let num_subdevices = self.count_subdevices().await?;
fmt::debug!("Discovered {} SubDevices", num_subdevices);
if num_subdevices == 0 {
fmt::warn!("No SubDevices were discovered. Check NIC device, connections and PDU response timeouts");
return Ok(groups);
}
self.reset_subdevices().await?;
self.num_subdevices.store(num_subdevices, Ordering::Relaxed);
let mut subdevices = heapless::Deque::<SubDevice, MAX_SUBDEVICES>::new();
for subdevice_idx in 0..num_subdevices {
let configured_address = BASE_SUBDEVICE_ADDRESS.wrapping_add(subdevice_idx);
Command::apwr(
subdevice_idx,
RegisterAddress::ConfiguredStationAddress.into(),
)
.send(self, configured_address)
.await?;
let subdevice = SubDevice::new(self, subdevice_idx, configured_address).await?;
subdevices
.push_back(subdevice)
.map_err(|_| Error::Capacity(Item::SubDevice))?;
}
fmt::debug!("Configuring topology/distributed clocks");
let dc_master = dc::configure_dc(self, subdevices.as_mut_slices().0, now).await?;
if let Some(dc_master) = dc_master {
self.dc_reference_configured_address
.store(dc_master.configured_address(), Ordering::Relaxed);
dc::run_dc_static_sync(self, dc_master, self.config.dc_static_sync_iterations).await?;
}
{
let mut group_map = FnvIndexMap::<_, _, MAX_SUBDEVICES>::new();
while let Some(subdevice) = subdevices.pop_front() {
let group = group_filter(&groups, &subdevice)?;
unsafe { group.push(subdevice)? };
group_map
.insert(usize::from(group.id()), UnsafeCell::new(group))
.map_err(|_| Error::Capacity(Item::Group))?;
}
let mut offset = PdiOffset::default();
for (id, group) in group_map.into_iter() {
let group = unsafe { *group.get() };
offset = group.as_ref().into_pre_op(offset, self).await?;
fmt::debug!("After group ID {} offset: {:?}", id, offset);
}
fmt::debug!("Total PDI {} bytes", offset.start_address);
}
self.wait_for_state(SubDeviceState::PreOp).await?;
Ok(groups)
}
pub async fn init_single_group<const MAX_SUBDEVICES: usize, const MAX_PDI: usize>(
&self,
now: impl Fn() -> u64 + Copy,
) -> Result<SubDeviceGroup<MAX_SUBDEVICES, MAX_PDI, subdevice_group::PreOp>, Error> {
self.init::<MAX_SUBDEVICES, _>(now, |group, _subdevice| Ok(group))
.await
}
async fn count_subdevices(&self) -> Result<u16, Error> {
Command::brd(RegisterAddress::Type.into())
.receive_wkc::<u8>(self)
.await
}
pub fn num_subdevices(&self) -> usize {
usize::from(self.num_subdevices.load(Ordering::Relaxed))
}
pub(crate) fn dc_ref_address(&self) -> Option<u16> {
let addr = self.dc_reference_configured_address.load(Ordering::Relaxed);
if addr > 0 {
Some(addr)
} else {
None
}
}
pub async fn wait_for_state(&self, desired_state: SubDeviceState) -> Result<(), Error> {
let num_subdevices = self.num_subdevices.load(Ordering::Relaxed);
async {
loop {
let status = Command::brd(RegisterAddress::AlStatus.into())
.with_wkc(num_subdevices)
.receive::<AlControl>(self)
.await?;
fmt::trace!("Global AL status {:?}", status);
if status.error {
fmt::error!(
"Error occurred transitioning all SubDevices to {:?}",
desired_state,
);
for subdevice_addr in BASE_SUBDEVICE_ADDRESS
..(BASE_SUBDEVICE_ADDRESS + self.num_subdevices() as u16)
{
let status =
Command::fprd(subdevice_addr, RegisterAddress::AlStatusCode.into())
.ignore_wkc()
.receive::<AlStatusCode>(self)
.await
.unwrap_or(AlStatusCode::UnspecifiedError);
fmt::error!(
"--> SubDevice {:#06x} status code {}",
subdevice_addr,
status
);
}
return Err(Error::StateTransition);
}
if status.state == desired_state {
break Ok(());
}
self.timeouts.loop_tick().await;
}
}
.timeout(self.timeouts.state_transition)
.await
}
#[allow(unused)]
pub(crate) const fn max_frame_data(&self) -> usize {
self.pdu_loop.max_frame_data()
}
pub(crate) async fn single_pdu(
&'sto self,
command: Command,
data: impl EtherCrabWireWrite,
len_override: Option<u16>,
) -> Result<ReceivedPdu<'sto>, Error> {
let mut frame = self.pdu_loop.alloc_frame()?;
let handle = frame.push_pdu(command, data, len_override)?;
let frame = frame.mark_sendable(
&self.pdu_loop,
self.timeouts.pdu,
self.config.retry_behaviour.retry_count(),
);
self.pdu_loop.wake_sender();
frame.await?.first_pdu(handle)
}
pub unsafe fn release(mut self) -> PduLoop<'sto> {
self.pdu_loop.reset();
self.pdu_loop
}
pub unsafe fn release_all(mut self) -> PduLoop<'sto> {
self.pdu_loop.reset_all();
self.pdu_loop.wake_sender();
self.pdu_loop
}
}