etherage/
slave.rs

1
2use crate::{
3    master::Master,
4    rawmaster::{RawMaster, SlaveAddress},
5    data::{PduData, Field},
6    sii::{Sii, SiiError},
7    mailbox::Mailbox,
8    can::Can,
9    registers::{self, AlError},
10    eeprom,
11    error::{EthercatError, EthercatResult},
12    };
13use tokio::sync::{Mutex, MutexGuard};
14use std::sync::Arc;
15use core::time::Duration;
16
17
18
19pub type CommunicationState = registers::AlState;
20use registers::AlState::*;
21
22
23
24/**
25    This struct exposes the ethercat master functions addressing one slave.
26
27    Its lifetime refers to the [Master] the slave answers to.
28
29    ## Note
30
31    At contrary to [RawMaster], this struct is protocol-safe, which mean the communication cannot break because methods as not been called in the right order or at the right moment. There is nothing the user can do that might accidentally break the communication.
32    The communication might however fail for hardware reasons, and the communication-safe functions shall report such errors.
33    
34    ## Communication state
35    
36    A slave's current [CommunicationState] determines what this slave is capable of doing. Upgrading or downgrading the communication states is done though [Slave::switch].
37    
38    ![communication state](https://raw.githubusercontent.com/jimy-byerley/etherage/master/schemes/communication-state.svg)
39
40    ## Example
41
42    The following is a typical configuration sequence of a slave
43
44    ```ignore
45    slave.switch(CommunicationState::Init).await;
46    slave.set_address(1).await;
47    slave.init_mailbox().await;
48    slave.init_coe().await;
49    slave.switch(CommunicationState::PreOperational).await;
50    group.configure(&slave).await;
51    slave.switch(CommunicationState::SafeOperational).await;
52    slave.switch(CommunicationState::Operational).await;
53    ```
54
55    In this example, `group` is a tool to manage the logical memory and mappings from [crate::mapping].
56*/
57
58pub struct Slave<'a> {
59    master: Arc<RawMaster>,
60    /// current address in use, fixed or topological
61    address: SlaveAddress,
62    /// assumed current state
63    state: CommunicationState,
64    /// safe master to report to if existing
65    safemaster: Option<&'a Master>,
66
67    // internal structures are inter-referencing, thus must be stored in Rc to ensure the references to it will not void because of deallocation or data move
68    sii: Option<Mutex<Sii>>,
69    mailbox: Option<Arc<Mutex<Mailbox>>>,
70    coe: Option<Arc<Mutex<Can>>>,
71}
72impl<'a> Slave<'a> {
73    /**
74        build a slave from a `RawMaster`. As everything constructed with a `RawMaster` this is not protocol-safe: no check is done, in particular nothing prevents to create multiple instances for the same physical slave, or for non-existing slaves.
75        However if the physical slave is used only through one `Slave` instance, all operations will be protocol-safe.
76    */
77    pub fn raw(master: Arc<RawMaster>, address: SlaveAddress) -> Self {
78        Self {
79            master,
80            safemaster: None,
81            address,
82            state: Init,
83
84            sii: None,
85            mailbox: None,
86            coe: None,
87        }
88    }
89    /**
90        build a slave from a `Master`. exclusive acces to the addressed slave is ensured by `Master`, and the use of this struct will be protocl-safe.
91    */
92    pub async fn new(master: &'a Master, address: SlaveAddress) -> EthercatResult<Slave<'a>> {
93        let fixed = SlaveAddress::Fixed(master.raw.read(address, registers::address::fixed).await.one()?);
94        let mut book = master.slaves.lock().unwrap();
95        
96        if book.contains(&address)
97        || book.contains(&fixed)
98            {Err(EthercatError::Master("slave already in use by an other instance"))}
99        else {
100            book.insert(address);
101            drop(book);
102            Ok(Self {
103                master: master.raw.clone(),
104                safemaster: Some(master),
105                address,
106                state: Init,
107
108                sii: None,
109                mailbox: None,
110                coe: None,
111            })
112        }
113    }
114
115    /// return a reference to the underlying `RawMaster` used, this method is unsafe since it allows accessing any slave concurrently to what all `Slave` and `Master` instances are doing.
116    pub unsafe fn raw_master(&self) -> &Arc<RawMaster> {&self.master}
117
118    /// retreive the slave's identification informations
119    pub fn informations(&self)  {todo!()}
120
121    /// return the current state of the slave, it does not the current expected state for this slave
122    pub async fn state(&self) -> EthercatResult<CommunicationState> {
123        self.master.read(self.address, registers::al::status).await.one()?
124            .state().try_into()
125            .map_err(|_| EthercatError::Protocol("undefined slave state"))
126    }
127    /// send a state change request to the slave, and return once the slave has switched
128    pub async fn switch(&mut self, target: CommunicationState) -> EthercatResult<(), AlError>  {
129        // send state switch request
130        self.master.write(self.address, registers::al::control, {
131                let mut config = registers::AlControlRequest::default();
132                config.set_state(target.into());
133                config.set_ack(true);
134                config.set_request_id(true);
135                config
136		}).await.one()?;
137
138        // wait for state change, or error
139        loop {
140            let status = self.master.read(self.address, registers::al::response).await.one()?;
141            if status.error() {
142//                 if status.id() {
143                    let error = self.master.read(self.address, registers::al::error).await.one()?;
144                    if error != registers::AlError::NoError
145                        {return Err(EthercatError::Slave(self.address, error))}
146//                 }
147//                 else 
148//                     {return Err(EthercatError::Protocol("error during state change, but no error code provided"))}
149            }
150            if status.state() == target.into()  {break}
151        }
152        self.state = target;
153		Ok(())
154    }
155    /**
156        set the expected state of the slave.
157
158        this actually does not perform any operation on the slave, but will change the expected behavior and thus error handling of the slave's methods
159    */
160    pub fn expect(&mut self, state: CommunicationState) {
161        self.state = state;
162    }
163    /// expected state of the slave
164    pub fn expected(&self) -> CommunicationState {
165        self.state
166    }
167
168    /// get the current address used to communicate with the slave
169    pub fn address(&self) -> SlaveAddress  {self.address}
170    /// set a fixed address for the slave, `0` is forbidden
171    pub async fn set_address(&mut self, fixed: u16) -> EthercatResult {
172        assert!(fixed != 0);
173        self.master.write(self.address, registers::address::fixed, fixed).await.one()?;
174        let new = SlaveAddress::Fixed(fixed);
175        // report existing address if a safemaster is used
176        if let Some(safe) = self.safemaster {
177            let mut book = safe.slaves.lock().unwrap();
178            book.remove(&self.address);
179            book.insert(new);
180        }
181        self.address = new;
182        Ok(())
183    }
184    /// reset fixed address, the slave instance is consumed because the original topological address may have changed since
185    pub async fn reset_address(self) -> EthercatResult {
186        self.master.write(self.address, registers::address::fixed, 0).await.one()?;
187        Ok(())
188    }
189
190    /// initialize the SII which grants access to the EEPROM
191    pub async fn init_sii(&mut self) -> EthercatResult<(), SiiError> {
192        let sii = Sii::new(self.master.clone(), self.address).await?;
193        self.sii = Some(Mutex::new(sii));
194        Ok(())
195    }
196
197    /// initialize the slave's mailbox (if supported by the slave)
198    pub async fn init_mailbox(&mut self) -> EthercatResult<(), SiiError> {
199        assert_eq!(self.state, Init);
200
201        let address = match self.address {
202            SlaveAddress::Fixed(i) => i,
203            _ => panic!("mailbox is unsafe without fixed addresses"),
204        };
205        // get recommended settings
206        if self.sii.is_none() {
207            self.init_sii().await?;
208        }
209        let mut sii = self.sii().await;
210        sii.acquire().await?;
211        let write_offset = sii.read(eeprom::mailbox::standard::write::offset).await?;
212        let write_size   = sii.read(eeprom::mailbox::standard::write::size).await?;
213        let read_offset  = sii.read(eeprom::mailbox::standard::read::offset).await?;
214        let read_size    = sii.read(eeprom::mailbox::standard::read::size).await?;
215        sii.release().await?;
216        drop(sii);
217
218        // setup the mailbox
219        let mailbox = Mailbox::new(
220            self.master.clone(),
221            address,
222            (registers::sync_manager::interface.mailbox_write(), write_offset .. write_offset + write_size),
223            (registers::sync_manager::interface.mailbox_read(), read_offset .. read_offset + read_size),
224            ).await?;
225
226        self.coe = None;
227        self.mailbox = Some(Arc::new(Mutex::new(mailbox)));
228        Ok(())
229    }
230
231    /// initialize CoE (Canopen over Ethercat) communication (if supported by the slave), this requires the mailbox to be initialized
232    pub async fn init_coe(&mut self) -> EthercatResult<(), SiiError> {
233        if self.mailbox.is_none() {
234            self.init_mailbox().await?;
235        }
236        let mailbox = self.mailbox.clone().unwrap();
237        self.coe = Some(Arc::new(Mutex::new(Can::new(mailbox))));
238        Ok(())
239    }
240
241    /**
242        enable synchronization of slaves tasks to the master
243
244        Once enabled, the synchronous data exchanges done by the master must be in synchronization with the [DistributedClock](crate::clock::DistributedClock). And should send by anticipating the master-reference delay so that the data arrives on the reference slave when the clock value modulo is wrapping.
245
246        ## Note:
247
248        at the moment, only dc sync0 is supported. dc sync1 is not, SM-sync is not, SM-DC-sync is not
249    */
250    pub async fn init_sync(&mut self, period: Duration, activation: Duration) -> EthercatResult {
251        assert_eq!(self.state, PreOperational);
252        let period = u32::try_from(period.as_nanos()).expect("synchronization period must be below 4seconds");
253        let activation = u32::try_from(activation.as_nanos()).expect("activation delay must be below 4seconds");
254
255        self.master.write(self.address, registers::isochronous::sync::enable, Default::default()).await.one()?;
256        self.master.write(self.address, Field::<u8>::simple(0x980), 0).await.one()?;
257        let start = self.master.read(self.address, registers::dc::system_time).await.one()? as u32;
258        let start = ((start + activation) / period) * period + period;
259        self.master.write(self.address, registers::isochronous::sync::start_time, start).await.one()?;
260        self.master.write(self.address, registers::isochronous::sync::sync0_cycle_time, period).await.one()?;
261        self.master.write(self.address, registers::isochronous::sync::enable, {
262			let mut enables = registers::IsochronousEnables::default();
263			enables.set_operation(true);
264			enables.set_sync0(true);
265			enables
266			}).await.one()?;
267        Ok(())
268    }
269
270    /// lock access to SII communication and return the instance of [Sii] controling it
271    pub async fn sii(&self) -> MutexGuard<'_, Sii> {
272        self.sii
273            .as_ref().expect("sii not initialized")
274            .lock().await
275    }
276
277    /// locks access to CoE communication and return the underlying instance of [Can] running CoE
278    pub async fn coe(&self) -> MutexGuard<'_, Can>    {
279        self.coe
280            .as_ref().expect("coe not initialized")
281            .lock().await
282    }
283
284//     /// locks access to EoE communication
285//     pub fn eoe(&'a self) {todo!()}
286
287    /// read a value from the slave's physical memory
288    pub async fn physical_read<T: PduData>(&self, field: Field<T>) -> EthercatResult<T>  {
289        self.master.read(self.address, field).await.one()
290    }
291}
292
293impl Drop for Slave<'_> {
294    fn drop(&mut self) {
295        // deregister from the safemaster if any
296        if let Some(safe) = self.safemaster {
297            let mut book = safe.slaves.lock().unwrap();
298            book.remove(&self.address);
299        }
300    }
301}
302
303
304impl From<EthercatError<()>> for EthercatError<AlError> {
305    fn from(src: EthercatError<()>) -> Self {src.upgrade()}
306}