diff --git a/examples/dc.rs b/examples/dc.rs index ba10b918..85439c47 100644 --- a/examples/dc.rs +++ b/examples/dc.rs @@ -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::{ @@ -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), + SafeOp(SubDeviceGroup), + } + + 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 { diff --git a/src/subdevice/mod.rs b/src/subdevice/mod.rs index ac665e2b..5ecd04bb 100644 --- a/src/subdevice/mod.rs +++ b/src/subdevice/mod.rs @@ -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(&self, register: impl Into) -> Result + 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 diff --git a/src/subdevice_group/mod.rs b/src/subdevice_group/mod.rs index ded8907c..7e01953b 100644 --- a/src/subdevice_group/mod.rs +++ b/src/subdevice_group/mod.rs @@ -276,6 +276,31 @@ impl )) } + /// 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, 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 @@ -399,6 +424,27 @@ where _state: PhantomData::, }; + 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) @@ -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) @@ -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, }, @@ -527,6 +556,35 @@ impl 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, 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, diff --git a/src/subdevice_group/tx_rx_response.rs b/src/subdevice_group/tx_rx_response.rs index bce61bf9..47423f66 100644 --- a/src/subdevice_group/tx_rx_response.rs +++ b/src/subdevice_group/tx_rx_response.rs @@ -108,6 +108,13 @@ impl TxRxResponse { .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)]