Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 44 additions & 6 deletions examples/dc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ use ethercrab::{
DcSync, MainDevice, MainDeviceConfig, PduStorage, RegisterAddress, Timeouts,
error::Error,
std::ethercat_now,
subdevice_group::{CycleInfo, DcConfiguration, TxRxResponse},
subdevice_group::{
CycleInfo, DcConfiguration, HasDc, PreOpPdi, SafeOp, SubDeviceGroup, TxRxResponse,
},
};
use futures_lite::StreamExt;
use std::{
Expand Down Expand Up @@ -339,12 +341,48 @@ fn main() -> Result<(), Error> {
)
.await?;

let group = group
.into_safe_op(&maindevice)
.await
.expect("PRE-OP -> SAFE-OP");
// State machine to handle transition to SafeOp with process data
enum GroupState {
PreOp(SubDeviceGroup<MAX_SUBDEVICES, PDI_LEN, PreOpPdi, HasDc>),
SafeOp(SubDeviceGroup<MAX_SUBDEVICES, PDI_LEN, SafeOp, HasDc>),
}

let mut group_container = Some(GroupState::PreOp(group));
let mut tick = 0;

let group = loop {
let now = Instant::now();

match group_container.take().unwrap() {
GroupState::PreOp(group) => {
let res = group.tx_rx_dc(&maindevice).await.expect("TX/RX");

log::info!("SAFE-OP");
if tick > 300 {
let group = group.request_into_safe_op(&maindevice).await?;
group_container = Some(GroupState::SafeOp(group));
log::info!("Requested SAFE-OP");
} else {
group_container = Some(GroupState::PreOp(group));
}

smol::Timer::at(now + res.extra.next_cycle_wait).await;
}
GroupState::SafeOp(group) => {
let res = group.tx_rx_dc(&maindevice).await.expect("TX/RX");

if res.all_safe_op() {
log::info!("SAFE-OP");
break group;
} else {
group_container = Some(GroupState::SafeOp(group));
}

smol::Timer::at(now + res.extra.next_cycle_wait).await;
}
}

tick += 1;
};

#[derive(serde::Serialize)]
struct ProcessStat {
Expand Down
14 changes: 14 additions & 0 deletions src/subdevice/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1142,6 +1142,20 @@ impl<'maindevice, S> SubDeviceRef<'maindevice, S> {
self.read(register.into()).receive(self.maindevice).await
}

/// Read a register and ignore the response's working counter.
///
/// Note that while this method is marked safe, raw alterations to SubDevice config or behaviour can
/// break higher level interactions with EtherCrab.
pub async fn register_read_ignore_wkc<T>(&self, register: impl Into<u16>) -> Result<T, Error>
where
T: EtherCrabWireReadSized,
{
self.read(register.into())
.ignore_wkc()
.receive(self.maindevice)
.await
}

/// Write a register.
///
/// Note that while this method is marked safe, raw alterations to SubDevice config or behaviour can
Expand Down
94 changes: 76 additions & 18 deletions src/subdevice_group/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,31 @@ impl<const MAX_SUBDEVICES: usize, const MAX_PDI: usize, DC>
))
}

/// Mutably borrow an individual SubDevice.
#[deny(clippy::panic)]
#[doc(alias = "slave")]
pub fn subdevice_mut<'maindevice, 'group>(
&'group mut self,
maindevice: &'maindevice MainDevice<'maindevice>,
index: usize,
) -> Result<SubDeviceRef<'maindevice, &'group mut SubDevice>, Error> {
let subdevice = self
.inner
.get_mut()
.subdevices
.get_mut(index)
.ok_or(Error::NotFound {
item: Item::SubDevice,
index: Some(index),
})?;

Ok(SubDeviceRef::new(
maindevice,
subdevice.configured_address(),
subdevice,
))
}

/// Transition the group from PRE-OP -> SAFE-OP -> OP.
///
/// To transition individually from PRE-OP to SAFE-OP, then SAFE-OP to OP, see
Expand Down Expand Up @@ -399,6 +424,27 @@ where
_state: PhantomData::<PreOp>,
};

let reference_device = self_
.iter(maindevice)
.find(|x| x.configured_address() == reference)
.expect("Could not find reference device");

let reference_time: u64 = reference_device
.read(RegisterAddress::DcSystemTime)
.ignore_wkc()
.receive(maindevice)
.await?;

fmt::debug!("--> Reference time {} ns", reference_time);

let sync0_period = sync0_period.as_nanos() as u64;
let first_pulse_delay = start_delay.as_nanos() as u64;

// Round first pulse time to a whole number of cycles
let start_time = (reference_time + first_pulse_delay) / sync0_period * sync0_period;

fmt::debug!("--> Computed DC sync start time: {}", start_time);

// Only configure DC for those devices that want and support it
let dc_devices = self_.iter(maindevice).filter(|subdevice| {
subdevice.dc_support().any() && !matches!(subdevice.dc_sync(), DcSync::Disabled)
Expand All @@ -425,23 +471,6 @@ where
.send(maindevice, 0u8)
.await?;

let device_time: u64 = subdevice
.read(RegisterAddress::DcSystemTime)
.ignore_wkc()
.receive(maindevice)
.await?;

fmt::debug!("--> Device time {} ns", device_time);

let sync0_period = sync0_period.as_nanos() as u64;

let first_pulse_delay = start_delay.as_nanos() as u64;

// Round first pulse time to a whole number of cycles
let start_time = (device_time + first_pulse_delay) / sync0_period * sync0_period;

fmt::debug!("--> Computed DC sync start time: {}", start_time);

subdevice
.write(RegisterAddress::DcSyncStartTime)
.send(maindevice, start_time)
Expand Down Expand Up @@ -477,7 +506,7 @@ where
pdi_len: self_.pdi_len,
inner: self_.inner,
dc_conf: HasDc {
sync0_period: sync0_period.as_nanos() as u64,
sync0_period,
sync0_shift: sync0_shift.as_nanos() as u64,
reference,
},
Expand Down Expand Up @@ -527,6 +556,35 @@ impl<const MAX_SUBDEVICES: usize, const MAX_PDI: usize, DC>
self_.request_into_op(maindevice).await
}

/// Like [`into_safe_op`](SubDeviceGroup::into_safe_op), however does not wait for all SubDevices to enter
/// SAFE-OP state.
///
/// This allows the application process data loop to be started, so as to e.g. not time out
/// watchdogs, or provide valid data to prevent DC sync errors.
///
/// The group's state can be checked by testing the result of a `tx_rx_*` call using methods on
/// the [`TxRxResponse`] struct.
pub async fn request_into_safe_op(
mut self,
maindevice: &MainDevice<'_>,
) -> Result<SubDeviceGroup<MAX_SUBDEVICES, MAX_PDI, SafeOp, DC>, Error> {
for subdevice in self.inner.get_mut().subdevices.iter_mut() {
SubDeviceRef::new(maindevice, subdevice.configured_address(), subdevice)
.request_subdevice_state_nowait(SubDeviceState::SafeOp)
.await?;
}

Ok(SubDeviceGroup {
id: self.id,
pdi: self.pdi,
read_pdi_len: self.read_pdi_len,
pdi_len: self.pdi_len,
inner: self.inner,
dc_conf: self.dc_conf,
_state: PhantomData,
})
}

/// Transition all SubDevices in the group from PRE-OP to INIT.
pub async fn into_init(
self,
Expand Down
7 changes: 7 additions & 0 deletions src/subdevice_group/tx_rx_response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,13 @@ impl<const N: usize, T> TxRxResponse<N, T> {
.filter(|s| *s == SubDeviceState::Op)
.is_some()
}

/// A helper method to ease EtherCrab version upgrades.
pub fn all_safe_op(&self) -> bool {
self.group_in_single_state()
.filter(|s| *s == SubDeviceState::SafeOp)
.is_some()
}
}

#[cfg(test)]
Expand Down