ethercat_controller/
ethercat_controller.rs

1use std::{
2    collections::HashMap,
3    fs::File,
4    io::{self, Read},
5    ops::Range,
6    sync::{Arc, Condvar, Mutex, RwLock},
7    thread,
8    time::Duration,
9};
10
11use ethercat::{
12    AlState, DataType, DomainIdx, Master, MasterAccess, Offset, PdoCfg, PdoEntryIdx, PdoEntryInfo,
13    PdoEntryPos, PdoIdx, PdoPos, SdoData, SdoIdx, SdoPos, SlaveAddr, SlaveId, SlavePos, SmCfg,
14    SmIdx, SmInfo, SubIdx,
15};
16
17use crossbeam_channel::{bounded, Receiver, Sender};
18
19use crate::{watchdog, MailboxPdoEntries, PdoOffsets, SlaveNames, SlaveOffsets, SlaveSetup};
20
21// function not available in the ethercat-rs crate
22use crate::ethercat_patch::master_configure_sync;
23
24#[cfg(feature = "verify_mailbox_pdos")]
25use crate::mailboxes::{init_mailbox_pdo_verification, verify_mailbox_pdos};
26#[cfg(feature = "enable_watchdog")]
27use crate::watchdog::{init_watchdog_settings, verify_watchdog};
28
29use crate::mailboxes::mailbox_sdo_read;
30
31/// The EtherCAT controller is the main struct to interact with the EtherCAT network
32/// encapsulating the master and exchanging data with the slaves
33#[derive(Debug)]
34pub struct EtherCatController {
35    offsets: SlaveOffsets,
36    slave_names: SlaveNames,
37
38    data_lock: Arc<RwLock<Option<Vec<u8>>>>,
39    ready_condvar: Arc<(Mutex<bool>, Condvar)>,
40    cycle_condvar: Arc<(Mutex<bool>, Condvar)>,
41    slave_states_condvar: Arc<(Mutex<Vec<u8>>, Condvar)>,
42
43    cmd_buff: Sender<(Range<usize>, Vec<u8>)>,
44
45    // is poulpe setup
46    setup_condvar: Arc<(Mutex<SlaveSetup>, Condvar)>,
47
48    pub command_drop_time_us: u32,
49}
50
51impl EtherCatController {
52    /// This function creates a new EtherCAT controller
53    /// - It configures the master and the slaves in the EtherCAT network
54    /// - Instantiates a new master thread that continuously reads and writes data to the slaves
55    ///
56    /// # Arguments
57    ///
58    /// * `master_id` - The id of the master in the EtherCAT network
59    /// * `cycle_period` - The time between each cycle
60    /// * `command_drop_time_us` - The time to wait for the command to be dropped
61    /// * `watchdog_timeout_ms` - The time to wait for the watchdog to be updated
62    /// * `mailbox_wait_time_ms` - The time to wait for the mailbox to be updated
63    ///
64    /// # Returns
65    ///
66    /// * `Result<Self, io::Error>` - The result of the operation
67    pub fn open(
68        master_id: u32,
69        cycle_period: Duration,
70        command_drop_time_us: u32,
71        watchdog_timeout_ms: u32,
72        mailbox_wait_time_ms: u32,
73    ) -> Result<Self, io::Error> {
74        let (mut master, domain_idx, offsets, slave_names, mailbox_pdo_entries) =
75            init_master(master_id)?;
76
77        // read the slave info using SDOs
78        // IMPORTANT !!!!!!!
79        // must be done before master.activate()
80        for slave_id in 0..slave_names.len() {
81            let mut data = vec![0u8; 1];
82            match mailbox_sdo_read(&master, slave_id as u16, 0x201, 0x1, &mut data) {
83                Ok(_) => {
84                    log::info!("Slave {}, DXL_ID: {:?}", slave_id, data[0]);
85                }
86                Err(_) => {
87                    log::warn!("Slave {}, DXL_ID unknown!", slave_id);
88                }
89            }
90
91            let mut data = vec![0u8; 40];
92            match mailbox_sdo_read(&master, slave_id as u16, 0x200, 0x1, &mut data) {
93                Ok(_) => {
94                    log::info!(
95                        "Slave {} firmware version: {:?}",
96                        slave_id,
97                        String::from_utf8(data).unwrap()
98                    );
99                }
100                Err(_) => {
101                    log::warn!("Slave {}, firmware version unknown!", slave_id);
102                }
103            }
104        }
105
106        master.activate()?;
107
108        // log the pdo offsets (debug)
109        log_pdo_offsets(&offsets);
110
111        // create the synhronization variables
112        // EtherCAT data mutex
113        let data_lock = Arc::new(RwLock::new(None));
114        let write_data_lock = Arc::clone(&data_lock);
115
116        // ethercat master ready mutex
117        let ready_condvar = Arc::new((Mutex::new(false), Condvar::new()));
118        let write_ready_condvar = Arc::clone(&ready_condvar);
119
120        // ethercat master cycle mutex
121        let cycle_condvar = Arc::new((Mutex::new(false), Condvar::new()));
122        let write_cycle_condvar = Arc::clone(&cycle_condvar);
123
124        // ethercat slave states mutex
125        let slave_states_condvar = Arc::new((Mutex::new(vec![0]), Condvar::new()));
126        let sstate_condvar = Arc::clone(&slave_states_condvar);
127
128        // ethercat slave is setup mutex
129        let mut is_poulpe_setup: SlaveSetup = HashMap::new();
130        for i in 0..slave_names.len() {
131            is_poulpe_setup.insert(SlavePos::from(i as u16), false);
132        }
133        let slave_setup_condvar = Arc::new((Mutex::new(is_poulpe_setup), Condvar::new()));
134        let setup_condvar = Arc::clone(&slave_setup_condvar);
135
136        // get the slave number
137        let slave_number = slave_names.len() as u32;
138        // create a function to map slave id to slave name
139        let slave_name_from_id = create_slave_name_mapper(slave_names.clone());
140
141        // create a sync channel to send data to the master
142        // crossbeam_channel is more efficient than std::sync::mcsp::SyncChannel
143        let buffer_size = (slave_number * 20) as usize;
144        let (tx, rx): (
145            crossbeam_channel::Sender<(Range<usize>, Vec<u8>)>,
146            Receiver<(Range<usize>, Vec<u8>)>,
147        ) = bounded(buffer_size);
148
149        #[cfg(feature = "verify_mailbox_pdos")]
150        // initialize the mailbox verification
151        let (
152            mut slave_mailbox_pdo_offsets,
153            mut slave_mailbox_pdo_timestamps,
154            mut slave_is_mailbox_pdo_responding,
155            mut slave_mailbox_pdo_data_buffer,
156        ) = init_mailbox_pdo_verification(
157            slave_number,
158            &mailbox_pdo_entries,
159            &offsets,
160            &get_reg_addr_ranges,
161        );
162
163        #[cfg(feature = "enable_watchdog")]
164        // initialize the watchdog settings
165        let (
166            slave_watchdog_control_offsets,
167            slave_watchdog_status_offsets,
168            mut slave_watchdog_timestamps,
169            mut slave_is_watchdog_responding,
170            mut slave_previous_watchdog_counter,
171        ) = init_watchdog_settings(slave_number, &offsets, &get_reg_addr_ranges);
172
173        let mut watchdog_counter = 0;
174
175        thread::spawn(move || {
176            // is master operational flag
177            let mut master_operational = false;
178            // timestamp to say from when the master is not operational
179            let mut display_not_operational_timestamp = std::time::Instant::now();
180            // timestap used to establish the loop period
181            let mut loop_period_timestamp = std::time::Instant::now();
182            let mut debug_loop_timestamp = std::time::Instant::now();
183            let mut debug_loop_counter = 0;
184            // spawn a thread to handle the master
185            loop {
186                // check the loop period
187                // make it approximately equal to the cycle period
188                // make sure that the subtraction does not return a negative value
189                let dt_sleep =
190                    cycle_period.as_secs_f32() - loop_period_timestamp.elapsed().as_secs_f32();
191                if dt_sleep > 0.0 {
192                    thread::sleep(Duration::from_secs_f32(dt_sleep));
193                }
194                // set the loop period timestamp
195                loop_period_timestamp = std::time::Instant::now();
196
197                // debugging output
198                debug_loop_counter += 1;
199                if debug_loop_timestamp.elapsed().as_secs_f32() > 10.0 {
200                    log::info!(
201                        "EtherCAT loop: {:.02} Hz",
202                        debug_loop_counter as f32 / debug_loop_timestamp.elapsed().as_secs_f32()
203                    );
204                    debug_loop_timestamp = std::time::Instant::now();
205                    debug_loop_counter = 0;
206                }
207
208                // get the master data
209                master.receive().unwrap();
210                master.domain(domain_idx).process().unwrap();
211                master.domain(domain_idx).queue().unwrap();
212
213                // get the domain data
214                let mut data = master.domain_data(domain_idx).unwrap();
215
216                // verify that the poulpes are still writing
217                // for each slave check if the mailbox mailbox pdo entries are updated
218                // the mailbox data is being written by slaves at arounf 10Hz
219                // if the mailbox data is not updated for more than 1s
220                // the slave is considered not as responding
221                //
222                // if at least one slave is not responding function will return false
223                //
224                // if the slaves are responding it will update the data buffer
225                // with the mailbox data (which might have been read some time ago (but less than 1s ago))
226                #[cfg(feature = "verify_mailbox_pdos")]
227                let all_slaves_responding = verify_mailbox_pdos(
228                    slave_number,
229                    &mut data,
230                    &mut slave_mailbox_pdo_offsets,
231                    &mut slave_mailbox_pdo_timestamps,
232                    &mut slave_is_mailbox_pdo_responding,
233                    &mut slave_mailbox_pdo_data_buffer,
234                    mailbox_wait_time_ms,
235                );
236
237                // write the data to the data lock
238                if let Ok(mut write_guard) = write_data_lock.write() {
239                    *write_guard = Some(data.to_vec());
240                }
241
242                // notify the next cycle
243                notify_next_cycle(&write_cycle_condvar);
244
245                // check if the master is operational
246                // and only if operational update the data buffer with the new data to send to the slaves
247                if master_operational {
248                    // check if the RX buffer is getting full!!!
249                    // if rx.len() > 40 {log::warn!("RX buffer almost full: {}/{}", rx.len(), buffer_size)}
250                    // update the data buffer with the new data to send
251                    while let Ok((reg_addr_range, value)) = rx.try_recv() {
252                        data[reg_addr_range].copy_from_slice(&value);
253                    }
254                }
255
256                #[cfg(feature = "enable_watchdog")]
257                // verify the watchdog
258                let all_slaves_have_watchdog = verify_watchdog(
259                    slave_number,
260                    &mut data,
261                    watchdog_timeout_ms,
262                    watchdog_counter,
263                    &slave_watchdog_control_offsets,
264                    &slave_watchdog_status_offsets,
265                    &mut slave_watchdog_timestamps,
266                    &mut slave_is_watchdog_responding,
267                    &mut slave_previous_watchdog_counter,
268                    &slave_name_from_id,
269                );
270                // update the watchdog counter
271                watchdog_counter = (watchdog_counter + 1) % 8;
272
273                // send the data to the slaves
274                master.send().unwrap();
275
276                // get the master state
277                let m_state = master.state().unwrap();
278                #[cfg(not(feature = "verify_mailbox_pdos"))]
279                // get the slave states without mailbox verification
280                let all_slaves_responding = m_state.slaves_responding == slave_number;
281                #[cfg(not(feature = "enable_watchdog"))]
282                let all_slaves_have_watchdog = m_state.slaves_responding == slave_number;
283
284                // master opration state machine
285                // if the master is not operational
286                //  - check if all slaves are responding
287                //  - check if the link is up
288                //  - check if the master is in operational state
289                //  - check if the number of slaves responding is equal to the number of slaves connected (no disconnected or newly connected slaves)
290                //  -> if all the conditions are met notify the operational state to the master and the slaves
291                //
292                // if the master is operational
293                //  - check if the master is still operational
294                //  - check if all slaves are connected
295                //  - check if all slaves are responding
296                //  - check if the master is in operational state
297                //  - check if the number of slaves responding is equal to the number of slaves connected (no disconnected or newly connected slaves)
298                //  -> if any of the conditions are not met notify the operational state to the slaves and set the ready flag to false
299                if !master_operational {
300                    // master is not operational
301
302                    // To go to the operational state
303                    // - if all slaves are responding
304                    // - if all slaves have watchdog
305                    // - if the link is up
306                    // - if the master is in operational state
307                    // - if the number of slaves responding is equal to the number of slaves connected (no disconnected or newly connected slaves)
308                    if all_slaves_responding
309                        && all_slaves_have_watchdog
310                        && m_state.link_up
311                        && m_state.al_states == AlState::Op as u8 // OP = 8 is operational
312                        && m_state.slaves_responding == slave_number
313                    {
314                        // notify the operational state to the master
315                        set_ready_flag(&write_ready_condvar, true);
316                        master_operational = true;
317                        // notify the operational state to the slaves
318                        notify_slave_state(
319                            &sstate_condvar,
320                            vec![AlState::Op as u8; slave_number as usize],
321                        );
322                        log::info!("Master and all slaves operational!");
323                    } else {
324                        // check each second
325                        if display_not_operational_timestamp.elapsed().as_secs() > 1 {
326                            display_not_operational_timestamp = std::time::Instant::now();
327                            log::warn!("Master cannot go to operational!");
328                            // display the master state
329                            // if the master is not operational
330                            log_master_state(
331                                &master,
332                                slave_number,
333                                #[cfg(feature = "verify_mailbox_pdos")]
334                                mailbox_wait_time_ms,
335                                #[cfg(feature = "enable_watchdog")]
336                                watchdog_timeout_ms,
337                                &slave_name_from_id,
338                                #[cfg(feature = "verify_mailbox_pdos")]
339                                &slave_is_mailbox_pdo_responding,
340                                #[cfg(feature = "enable_watchdog")]
341                                &slave_is_watchdog_responding,
342                            );
343
344                            // kill the master if error recovery not supported
345                            #[cfg(feature = "stop_opeation_on_error")]
346                            std::process::exit(10);
347                        }
348                    }
349                } else {
350                    // check if the master is still operational
351
352                    // check if all slaves are connected
353                    // thsis will fail if a slave is disconnected
354                    // or if a new slave is connected
355                    if m_state.slaves_responding != slave_number {
356                        match m_state.slaves_responding {
357                            0 => log::error!("No slaves are connected!"),
358                            _ if m_state.slaves_responding < slave_number => log::error!(
359                                "Not all slaves are connected! Expected: {}, Responding: {}",
360                                slave_number,
361                                m_state.slaves_responding
362                            ),
363                            _ if m_state.slaves_responding > slave_number => log::error!(
364                                "New slaves are connected! Inintially: {}, Now: {}",
365                                slave_number,
366                                m_state.slaves_responding
367                            ),
368                            _ => {}
369                        }
370
371                        // update the slave states
372                        let slave_current_state = (0..slave_number)
373                            .map(|i| {
374                                get_slave_current_state(
375                                    &master,
376                                    SlavePos::from(i as u16),
377                                    &slave_name_from_id,
378                                    #[cfg(feature = "verify_mailbox_pdos")]
379                                    slave_is_mailbox_pdo_responding[i as usize],
380                                    #[cfg(feature = "enable_watchdog")]
381                                    slave_is_watchdog_responding[i as usize],
382                                )
383                            })
384                            .collect::<Vec<_>>();
385
386                        // notify the operational state for the slaves
387                        notify_slave_state(&sstate_condvar, slave_current_state);
388
389                        set_ready_flag(&write_ready_condvar, false);
390                        master_operational = false;
391                    }
392
393                    // if master state has changed or not all slaves are responding
394                    if m_state.al_states != AlState::Op as u8
395                        || !all_slaves_responding
396                        || !all_slaves_have_watchdog
397                    {
398                        // master state has changed
399                        if m_state.al_states != AlState::Op as u8 {
400                            log::error!(
401                                "Master is not operational! State: {:?}",
402                                m_state.al_states
403                            );
404                        }
405                        if !all_slaves_responding {
406                            // not all slaves are responding
407                            log::error!("Not all slaves are responding!");
408                        }
409                        if !all_slaves_have_watchdog {
410                            // not all slaves have watchdog
411                            log::error!("Not all slaves have watchdog!");
412                        }
413
414                        // update the slave states
415                        // with mailbox verification
416                        // and watchdog verification
417                        // if enabled
418                        let slave_current_state = (0..slave_number)
419                            .map(|i| {
420                                get_slave_current_state(
421                                    &master,
422                                    SlavePos::from(i as u16),
423                                    &slave_name_from_id,
424                                    #[cfg(feature = "verify_mailbox_pdos")]
425                                    slave_is_mailbox_pdo_responding[i as usize],
426                                    #[cfg(feature = "enable_watchdog")]
427                                    slave_is_watchdog_responding[i as usize],
428                                )
429                            })
430                            .collect::<Vec<_>>();
431
432                        // notify the operational state for the slaves
433                        notify_slave_state(&sstate_condvar, slave_current_state);
434
435                        // set the ready flag to false
436                        set_ready_flag(&write_ready_condvar, false);
437                        // master is not operational
438                        master_operational = false;
439                    }
440                }
441            }
442        });
443
444        Ok(EtherCatController {
445            offsets,
446            slave_names,
447            data_lock,
448            ready_condvar,
449            cycle_condvar,
450            slave_states_condvar,
451            setup_condvar,
452            cmd_buff: tx,
453            command_drop_time_us,
454        })
455    }
456
457    /// Get all available slave ids
458    pub fn get_slave_ids(&self) -> Vec<u16> {
459        let mut ids: Vec<u16> = self
460            .offsets
461            .keys()
462            .map(|slave_pos| u16::from(*slave_pos))
463            .collect();
464        ids.sort();
465        ids
466    }
467
468    /// get PDO register entry contents
469    ///
470    /// # Arguments
471    ///
472    /// * `slave_id` - The id of the slave
473    /// * `register` - The name of the register
474    /// * `index` - The index of the register
475    ///
476    /// # Returns
477    ///
478    /// * `Option<Vec<u8>>` - The contents of the register
479    pub fn get_pdo_register(
480        &self,
481        slave_id: u16,
482        register: &String,
483        index: usize,
484    ) -> Option<Vec<u8>> {
485        let reg_addr_range = self.get_reg_addr_range(slave_id, register, index);
486
487        (*self.data_lock.read().unwrap())
488            .as_ref()
489            .map(|data| data[reg_addr_range].to_vec())
490    }
491
492    /// set PDO register entry contents
493    ///
494    /// # Arguments
495    ///
496    /// * `slave_id` - The id of the slave
497    /// * `register` - The name of the register
498    /// * `index` - The index of the register
499    /// * `value` - The value to set
500    pub fn set_pdo_register(&self, slave_id: u16, register: &String, index: usize, value: Vec<u8>) {
501        let reg_addr_range = self.get_reg_addr_range(slave_id, register, index);
502
503        self.cmd_buff.send((reg_addr_range, value)).unwrap();
504    }
505
506    /// get multiple PDO entries with the same register name
507    ///
508    /// # Arguments
509    ///
510    /// * `slave_id` - The id of the slave
511    /// * `register` - The name of the register
512    ///
513    /// # Returns
514    ///
515    /// * `Option<Vec<Vec<u8>>>` - The contents of the registers
516    pub fn get_pdo_registers(&self, slave_id: u16, register: &String) -> Option<Vec<Vec<u8>>> {
517        let reg_addr_ranges = self.get_reg_addr_ranges(slave_id, register);
518
519        let vals = reg_addr_ranges
520            .iter()
521            .map(|reg_addr_range| {
522                (*self.data_lock.read().unwrap())
523                    .as_ref()
524                    .map(|data| data[reg_addr_range.clone()].to_vec())
525            })
526            .collect::<Option<Vec<Vec<u8>>>>()?;
527        Some(vals)
528    }
529
530    /// set multiple PDO entries with the same register name
531    ///
532    /// # Arguments
533    ///
534    /// * `slave_id` - The id of the slave
535    /// * `register` - The name of the register
536    /// * `values` - The values to set
537    pub fn set_pdo_registers(&self, slave_id: u16, register: &String, values: Vec<Vec<u8>>) {
538        let reg_addr_ranges = self.get_reg_addr_ranges(slave_id, register);
539
540        if values.len() != reg_addr_ranges.len() {
541            // log::error!("values: {:?}", values);
542            log::warn!(
543                "Values length does not match register count, using first {} elements!",
544                reg_addr_ranges.len()
545            );
546        }
547
548        for (reg_addr_range, v) in reg_addr_ranges.iter().zip(values) {
549            self.cmd_buff.send((reg_addr_range.clone(), v)).unwrap();
550        }
551    }
552
553    /// Block until the next cycle
554    pub fn wait_for_next_cycle(&self) {
555        let (lock, cvar) = &*self.cycle_condvar;
556        let mut next_cycle = lock.lock().unwrap();
557
558        *next_cycle = false;
559        while !*next_cycle {
560            next_cycle = cvar.wait(next_cycle).unwrap();
561        }
562    }
563
564    /// check if the master is in the operational state
565    pub fn master_operational(self) -> bool {
566        {
567            let (lock, _cvar) = &*self.ready_condvar;
568            let ready = lock.lock().unwrap();
569            return *ready;
570        }
571    }
572
573    /// Check if the slave is in the operational state
574    pub fn is_slave_ready(&self, slave_id: u16) -> bool {
575        let states = self.get_slave_states();
576        match states.get(slave_id as usize).map(|s| *s) {
577            Some(state) => state == (AlState::Op as u8),
578            None => return false,
579        }
580    }
581
582    /// get all the slave states connected/configured to the master
583    pub fn get_slave_states(&self) -> Vec<u8> {
584        {
585            let (lock, _cvar) = &*self.slave_states_condvar;
586            let states = lock.lock().unwrap();
587            states.clone()
588        }
589    }
590
591    /// Blocking wait for the master to be ready
592    pub fn wait_for_ready(self) -> Self {
593        {
594            let (lock, cvar) = &*self.ready_condvar;
595            let mut ready = lock.lock().unwrap();
596
597            *ready = false;
598            while !*ready {
599                ready = cvar.wait(ready).unwrap();
600            }
601        }
602        self
603    }
604
605    /// Check if slave is setup
606    pub fn get_slave_setup(&self, slave_id: u16) -> bool {
607        {
608            let (lock, _cvar) = &*self.setup_condvar;
609            let setup = lock.lock().unwrap();
610            *setup.get(&SlavePos::from(slave_id)).unwrap_or(&false)
611        }
612    }
613    /// Set slave setup state
614    pub fn set_slave_setup(&self, slave_id: u16, setup: bool) {
615        {
616            let (lock, cvar) = &*self.setup_condvar;
617            let mut setup_lock = lock.lock().unwrap();
618            *setup_lock.get_mut(&SlavePos::from(slave_id)).unwrap() = setup;
619        }
620    }
621
622    /// Get the addes range of the PDO register in the data buffer
623    fn get_reg_addr_range(&self, slave_id: u16, register: &String, index: usize) -> Range<usize> {
624        get_reg_addr_range(&self.offsets, slave_id, register, index)
625    }
626
627    /// Get the addes ranges of the PDO registers with the same name in the data buffer
628    fn get_reg_addr_ranges(&self, slave_id: u16, register: &String) -> Vec<Range<usize>> {
629        get_reg_addr_ranges(&self.offsets, slave_id, register)
630    }
631
632    /// Get the name of the slave with id
633    pub fn get_slave_name(&self, slave_id: u16) -> Option<String> {
634        self.slave_names
635            .iter()
636            .find(|(_, id)| u16::from(**id) == slave_id)
637            .map(|(name, _)| name.clone())
638    }
639
640    /// Get the id of the slave with the name
641    pub fn get_slave_id(&self, slave_name: &String) -> Option<u16> {
642        self.slave_names.get(slave_name).map(|id| u16::from(*id))
643    }
644
645    /// Get tuples of slave ids and names
646    pub fn get_slave_ids_and_names(&self) -> Vec<(u16, String)> {
647        self.slave_names
648            .iter()
649            .map(|(name, id)| (u16::from(*id), name.clone()))
650            .collect()
651    }
652}
653
654/// helping function finding the address range of the PDO register in the data buffer
655pub fn get_reg_addr_range(
656    offsets: &SlaveOffsets,
657    slave_id: u16,
658    register: &String,
659    index: usize,
660) -> Range<usize> {
661    let slave_pos = SlavePos::from(slave_id);
662
663    let (_pdo_entry_idx, bit_len, offset) = offsets[&slave_pos][register][index];
664    let addr = offset.byte;
665    let bytes_len = (bit_len / 8) as usize;
666
667    addr..addr + bytes_len
668}
669
670/// helping function finding the address ranges of the PDO registers with the same name in the buffer
671fn get_reg_addr_ranges(
672    offsets: &SlaveOffsets,
673    slave_id: u16,
674    register: &String,
675) -> Vec<Range<usize>> {
676    let slave_pos = SlavePos::from(slave_id);
677
678    // Fetch data once to minimize locking time
679    let register_data = &offsets[&slave_pos][register];
680
681    let mut ranges = Vec::with_capacity(register_data.len());
682    for i in 0..register_data.len() {
683        ranges.push(get_reg_addr_range(offsets, slave_id, register, i));
684    }
685    ranges
686}
687
688/// Initialises the master for File Over EtherCAT (FOE) communication
689///
690/// This function does
691/// - Connect to the master
692/// - Finds the slaves connected to the master
693/// - Configures them for FOE communication
694/// - Returns the master object
695pub fn init_master_for_foe(idx: u32) -> Result<Master, io::Error> {
696    // try to open the master
697    // if it fails return error
698    let mut master = match Master::open(idx, MasterAccess::ReadWrite) {
699        Ok(master) => master,
700        Err(_) => {
701            log::error!("Failed to connecitng to master! Is ethercat master started?");
702            return Err(io::Error::new(
703                io::ErrorKind::Other,
704                "Failed to connect to master",
705            ));
706        }
707    };
708    log::debug!("Reserve master");
709    master.reserve()?;
710    log::debug!("Create domain");
711    let domain_idx = master.create_domain()?;
712
713    let slave_num = master.get_info().unwrap().slave_count;
714    log::info!("Found {:?} slaves", slave_num);
715
716    // if there are no slaves connected return error
717    if slave_num == 0 {
718        log::error!("No slaves found, check slave connections!");
719        return Err(io::Error::new(io::ErrorKind::Other, "No slaves found"));
720    }
721
722    for i in 0..slave_num {
723        let slave_info = master.get_slave_info(SlavePos::from(i as u16)).unwrap();
724        log::info!("Slave {:?} at position {:?}", slave_info.name, i);
725        log::debug!("Found device {:?}", slave_info);
726        log::debug!(
727            "Vendor ID: {:X}, Product Code: {:X}, SM count {:?}",
728            slave_info.id.vendor_id,
729            slave_info.id.product_code,
730            slave_info.sync_count
731        );
732        let slave_addr = SlaveAddr::ByPos(i as u16);
733        let slave_id = SlaveId {
734            vendor_id: slave_info.id.vendor_id,
735            product_code: slave_info.id.product_code,
736        };
737
738        for j in 0..slave_info.sync_count {
739            let sm_idx = SmIdx::new(j);
740            let sm_info = master.get_sync(SlavePos::from(i as u16), sm_idx).unwrap();
741
742            // sanity check
743            if sm_info.pdo_count == 0 {
744                log::debug!("No PDOs found for SM {:?}", sm_idx);
745            }
746
747            // check if second bit is set
748            // if it is its in mailbox mode
749            if sm_info.control_register & 0b10 != 0 {
750                log::debug!("SM is in mailbox mode!");
751            } else {
752                log::debug!("SM is in buffered mode!");
753                continue;
754            }
755
756            if sm_info.control_register & 0b100 != 0 {
757                log::debug!("Input SM!");
758            } else {
759                log::debug!("Output SM!");
760            }
761
762            master_configure_sync(&mut master, SlavePos::from(i as u16), sm_info);
763        }
764
765        let mut config = master.configure_slave(slave_addr, slave_id)?;
766
767        let cfg_index = config.index();
768
769        let cfg_info = master.get_config_info(cfg_index)?;
770        if cfg_info.slave_position.is_none() {
771            return Err(io::Error::new(
772                io::ErrorKind::Other,
773                "Unable to configure slave",
774            ));
775        }
776    }
777
778    Ok(master)
779}
780
781/// Initializing the master and automatically determining the slaves in the network, and their configurations
782///
783/// This function does
784/// - Connect to the master
785/// - Create a domain
786/// - Finds the slaves connected to the master
787/// - For each slave:
788///     - Finds the sync managers
789///     - Finds the available  PDO groups
790///     - Finds the PDO entries
791///     - Configures the masater for the slave
792/// - Returns the master object, domain index, slave offsets, slave names, mailbox pdo entries    
793///
794/// # Arguments
795///
796/// * `idx` - The index of the master to connect to
797///
798/// # Returns
799///
800/// * `Master` - The master object to interact with the EtherCAT network
801/// * `DomainIdx` - The domain index
802/// * `SlaveOffsets` - The slave offsets
803/// * `SlaveNames` - The slave names
804/// * `MailboxPdoEntries` - The mailbox pdo entries
805///
806pub fn init_master(
807    idx: u32,
808) -> Result<
809    (
810        Master,
811        DomainIdx,
812        SlaveOffsets,
813        SlaveNames,
814        MailboxPdoEntries,
815    ),
816    io::Error,
817> {
818    // try to open the master
819    // if it fails return error
820    let mut master = match Master::open(idx, MasterAccess::ReadWrite) {
821        Ok(master) => master,
822        Err(_) => {
823            log::error!("Failed to connecitng to master! Is ethercat master started?");
824            return Err(io::Error::new(
825                io::ErrorKind::Other,
826                "Failed to connect to master",
827            ));
828        }
829    };
830    log::debug!("Reserve master");
831    master.reserve()?;
832    log::debug!("Create domain");
833    let domain_idx = master.create_domain()?;
834    let mut offsets: SlaveOffsets = HashMap::new();
835    let mut slave_names: SlaveNames = HashMap::new();
836
837    let mut mailbox_pdos: MailboxPdoEntries = HashMap::new();
838
839    let slave_num = master.get_info().unwrap().slave_count;
840    log::info!("Found {:?} slaves", slave_num);
841
842    // if there are no slaves connected return error
843    if slave_num == 0 {
844        log::error!("No slaves found, check slave connections!");
845        return Err(io::Error::new(io::ErrorKind::Other, "No slaves found"));
846    }
847
848    for i in 0..slave_num {
849        let slave_info = master.get_slave_info(SlavePos::from(i as u16)).unwrap();
850        log::info!("Slave {:?} at position {:?}", slave_info.name, i);
851        slave_names.insert(slave_info.name.clone(), SlavePos::from(i as u16));
852        log::debug!("Found device {:?}", slave_info);
853        log::debug!(
854            "Vendor ID: {:X}, Product Code: {:X}, SM count {:?}",
855            slave_info.id.vendor_id,
856            slave_info.id.product_code,
857            slave_info.sync_count
858        );
859        let slave_addr = SlaveAddr::ByPos(i as u16);
860        let slave_id = SlaveId {
861            vendor_id: slave_info.id.vendor_id,
862            product_code: slave_info.id.product_code,
863        };
864
865        let mut pdos: Vec<Vec<PdoCfg>> = vec![];
866        let mut sms = vec![];
867        let mut mailbox = vec![];
868        let mut direction = vec![];
869        let mut mailbox_entires = vec![];
870        for j in 0..slave_info.sync_count {
871            let sm_idx = SmIdx::new(j);
872            let sm_info = master.get_sync(SlavePos::from(i as u16), sm_idx).unwrap();
873            log::debug!("Found sm {:?}, pdo_count {:?}", sm_info, sm_info.pdo_count);
874
875            // sanity check
876            if sm_info.pdo_count == 0 {
877                log::debug!("No pdo found in sync manager, skipping!");
878                continue;
879            }
880
881            // check if second bit is set
882            // if it is its in mailbox mode
883            if sm_info.control_register & 0b10 != 0 {
884                log::debug!("SM is in mailbox mode!");
885                mailbox.push(true);
886            } else {
887                log::debug!("SM is in buffered mode!");
888                mailbox.push(false);
889            }
890
891            if sm_info.control_register & 0b100 != 0 {
892                log::debug!("Input pdos!");
893                direction.push(1);
894            } else {
895                log::debug!("Output pdos!");
896                direction.push(-1);
897            }
898
899            let mut pdo_cfgs = vec![];
900            for pdo_ind in 0..sm_info.pdo_count {
901                let pdo_cfg: PdoCfg = {
902                    let pdo_info = master
903                        .get_pdo(SlavePos::from(i as u16), sm_idx, PdoPos::new(pdo_ind))
904                        .unwrap();
905                    log::debug!(
906                        "Found pdo {:?}, entry_count {:?}",
907                        pdo_info,
908                        pdo_info.entry_count
909                    );
910
911                    let pdo_entries = (0..pdo_info.entry_count)
912                        .map(|e| {
913                            let entry_info = master
914                                .get_pdo_entry(
915                                    SlavePos::from(i as u16),
916                                    sm_idx,
917                                    PdoPos::new(pdo_ind),
918                                    PdoEntryPos::new(e),
919                                )
920                                .unwrap();
921                            log::debug!(
922                                "Found entry {:?}, bit_len {:?}",
923                                entry_info,
924                                entry_info.bit_len
925                            );
926                            PdoEntryInfo {
927                                entry_idx: entry_info.entry_idx,
928                                bit_len: entry_info.bit_len as u8,
929                                name: entry_info.name.clone(),
930                                pos: PdoEntryPos::from(e as u8),
931                            }
932                        })
933                        .collect();
934                    PdoCfg {
935                        idx: PdoIdx::new(pdo_info.idx.into()),
936                        entries: pdo_entries,
937                    }
938                };
939                pdo_cfgs.push(pdo_cfg.clone());
940            }
941            pdos.push(pdo_cfgs.clone());
942            sms.push(sm_info);
943        }
944
945        let mut config = master.configure_slave(slave_addr, slave_id)?;
946        let mut entry_offsets: PdoOffsets = HashMap::new();
947
948        for i in 0..sms.len() {
949            let pds = pdos[i].clone();
950            let sm = sms[i].clone();
951
952            // check if second bit is set
953            // if it is its in input mode
954            if direction[i] > 0 {
955                config.config_sm_pdos(SmCfg::output(sm.idx), &pds)?;
956                // Positions of TX PDO
957                for pdo in &pds {
958                    log::debug!("Positions of TX PDO 0x{:X}:", u16::from(pdo.idx));
959                }
960            } else {
961                config.config_sm_pdos(SmCfg::input(sm.idx), &pds)?;
962                // Positions of RX PDO
963                for pdo in &pds {
964                    log::debug!("Positions of RX PDO 0x{:X}:", u16::from(pdo.idx));
965                }
966            }
967            for pdo in pds {
968                for entry in &pdo.entries {
969                    let offset = config.register_pdo_entry(entry.entry_idx, domain_idx)?;
970                    let name = entry.name.clone();
971                    if entry_offsets.contains_key(&name) {
972                        entry_offsets.get_mut(&name).unwrap().push((
973                            entry.entry_idx,
974                            entry.bit_len,
975                            offset,
976                        ));
977                    } else {
978                        entry_offsets.insert(name, vec![(entry.entry_idx, entry.bit_len, offset)]);
979                    }
980                    if mailbox[i] && direction[i] < 0 {
981                        // add the input mailbox to the list
982                        mailbox_entires.push(entry.name.clone());
983                    }
984                }
985            }
986        }
987
988        let cfg_index = config.index();
989
990        let cfg_info = master.get_config_info(cfg_index)?;
991        log::debug!("Config info: {:#?}", cfg_info);
992        if cfg_info.slave_position.is_none() {
993            return Err(io::Error::new(
994                io::ErrorKind::Other,
995                "Unable to configure slave",
996            ));
997        }
998        offsets.insert(SlavePos::new(i as u16), entry_offsets);
999        mailbox_pdos.insert(SlavePos::new(i as u16), mailbox_entires);
1000    }
1001
1002    Ok((master, domain_idx, offsets, slave_names, mailbox_pdos))
1003}
1004
1005/// log the pdo offsets
1006fn log_pdo_offsets(offsets: &SlaveOffsets) {
1007    for (s, o) in offsets {
1008        log::debug!("PDO offsets of Slave {}:", u16::from(*s));
1009        for (name, pdos) in o {
1010            for (pdo, bit_len, offset) in pdos {
1011                log::debug!(
1012                    " - \"{}\" : {:X}:{:X} - {:?}, bit length: {}",
1013                    name,
1014                    u16::from(pdo.idx),
1015                    u8::from(pdo.sub_idx),
1016                    offset,
1017                    bit_len
1018                );
1019            }
1020        }
1021    }
1022}
1023
1024/// create a function to map slave id to slave name
1025fn create_slave_name_mapper(slave_names: SlaveNames) -> impl Fn(u16) -> String {
1026    move |id: u16| -> String {
1027        slave_names
1028            .iter()
1029            .find(|(_, sid)| u16::from(**sid) == id)
1030            .unwrap()
1031            .0
1032            .clone()
1033    }
1034}
1035
1036/// set the ready flag with mutex
1037fn set_ready_flag(condvar: &Arc<(Mutex<bool>, Condvar)>, flag: bool) {
1038    let (lock, cvar) = &**condvar;
1039    let mut ready = lock.lock().unwrap();
1040    *ready = flag;
1041    cvar.notify_one();
1042}
1043
1044/// notify the slave state with mutex
1045fn notify_slave_state(condvar: &Arc<(Mutex<Vec<u8>>, Condvar)>, state: Vec<u8>) {
1046    let (lock, cvar) = &**condvar;
1047    let mut sstate = lock.lock().unwrap();
1048    *sstate = state;
1049    cvar.notify_one();
1050}
1051
1052fn notify_next_cycle(condvar: &Arc<(Mutex<bool>, Condvar)>) {
1053    let (lock, cvar) = &**condvar;
1054    let mut next_cycle = lock.lock().unwrap();
1055    *next_cycle = true;
1056    cvar.notify_one();
1057}
1058
1059/// Function to get the current state of a slave
1060fn get_slave_current_state(
1061    master: &Master,
1062    slave_pos: SlavePos,
1063    slave_name_from_id: &impl Fn(u16) -> String,
1064    #[cfg(feature = "verify_mailbox_pdos")] slave_is_mailbox_pdo_responding: bool,
1065    #[cfg(feature = "enable_watchdog")] slave_is_watchdog_responding: bool,
1066) -> u8 {
1067    #[cfg(feature = "verify_mailbox_pdos")]
1068    if !slave_is_mailbox_pdo_responding {
1069        log::error!(
1070            "Slave {:?} (pos: {:?}) is not responding (mailbox check failed)!",
1071            slave_name_from_id(slave_pos.into()),
1072            slave_pos
1073        );
1074        return 0;
1075    }
1076    #[cfg(feature = "enable_watchdog")]
1077    if !slave_is_watchdog_responding {
1078        log::error!(
1079            "Slave {:?} (pos: {:?}) is not responding (watchdog check failed)!",
1080            slave_name_from_id(slave_pos.into()),
1081            slave_pos
1082        );
1083        return 0;
1084    }
1085
1086    match master.get_slave_info(slave_pos) {
1087        Ok(info) => {
1088            if info.al_state != AlState::Op {
1089                log::error!(
1090                    "Slave {:?} is not operational! State: {:?}",
1091                    info.name,
1092                    info.al_state
1093                );
1094                0
1095            } else {
1096                AlState::Op as u8
1097            }
1098        }
1099        Err(_) => {
1100            log::error!(
1101                "Failed to get slave info for slave {:?}, name: {:?}",
1102                slave_pos,
1103                slave_name_from_id(slave_pos.into())
1104            );
1105            255
1106        }
1107    }
1108}
1109
1110/// Function that logs the current state of the master
1111fn log_master_state(
1112    master: &Master,
1113    slave_number: u32,
1114    #[cfg(feature = "verify_mailbox_pdos")] maibox_timeout_ms: u32,
1115    #[cfg(feature = "enable_watchdog")] watchdog_timeout_ms: u32,
1116    slave_name_from_id: &impl Fn(u16) -> String,
1117    #[cfg(feature = "verify_mailbox_pdos")] slave_is_mailbox_pdo_responding: &Vec<bool>,
1118    #[cfg(feature = "enable_watchdog")] slave_is_watchdog_responding: &Vec<bool>,
1119) {
1120    let m_state = master.state().unwrap();
1121    log::debug!(
1122        "Master State: {:?}, Link up: {}, Slaves connected: {} out of {}",
1123        m_state.al_states,
1124        m_state.link_up,
1125        m_state.slaves_responding,
1126        slave_number
1127    );
1128    if m_state.al_states != AlState::Op as u8 {
1129        log::error!("Master is not operational! State: {:?}", m_state.al_states);
1130    }
1131    if !m_state.link_up {
1132        log::error!("Link is not up!");
1133    }
1134    if m_state.slaves_responding < slave_number {
1135        log::error!(
1136            "Not all slaves are connected! Expected: {}, Responding: {}",
1137            slave_number,
1138            m_state.slaves_responding
1139        );
1140    }
1141    if m_state.slaves_responding > slave_number {
1142        log::error!(
1143            "New slaves are connected! Inintially: {}, Now: {}",
1144            slave_number,
1145            m_state.slaves_responding
1146        );
1147    }
1148
1149    // print the state of each slave
1150    log::info!("Connected slaves:");
1151    for i in 0..slave_number {
1152        match master.get_slave_info(SlavePos::from(i as u16)) {
1153            Ok(info) => {
1154                if info.al_state == AlState::Op {
1155                    log::info!(
1156                        "Slave {:?} (id: {}) is connected and operational! State: {:?}",
1157                        info.name,
1158                        i,
1159                        info.al_state
1160                    );
1161                } else {
1162                    log::warn!(
1163                        "Slave {:?} (id: {}) is connected but not operational! State: {:?}",
1164                        info.name,
1165                        i,
1166                        info.al_state
1167                    );
1168                }
1169            }
1170            Err(_) => {
1171                log::error!("Slave {:?} not connected!", i);
1172            }
1173        }
1174    }
1175
1176    // notify the operational state to the master
1177    #[cfg(feature = "verify_mailbox_pdos")]
1178    if !slave_is_mailbox_pdo_responding.iter().all(|&r| r) {
1179        log::error!("Not all slaves are responding!");
1180        for i in 0..slave_number {
1181            if !slave_is_mailbox_pdo_responding[i as usize] {
1182                log::error!(
1183                    "Poulpe {:?} (pos: {:?}) not responding for more than {}ms",
1184                    slave_name_from_id(i as u16),
1185                    i,
1186                    maibox_timeout_ms
1187                );
1188            }
1189        }
1190    }
1191
1192    #[cfg(feature = "enable_watchdog")]
1193    if !slave_is_watchdog_responding.iter().all(|&r| r) {
1194        log::error!("Not all slaves have watchdog!");
1195        for i in 0..slave_number {
1196            if !slave_is_watchdog_responding[i as usize] {
1197                log::error!(
1198                    "Poulpe {:?} (pos: {:?}) watchdog not responding for more than {}ms",
1199                    slave_name_from_id(i as u16),
1200                    i,
1201                    watchdog_timeout_ms
1202                );
1203            }
1204        }
1205    }
1206}