ethercat_soem/
lib.rs

1//! ethercat-soem
2
3#![cfg_attr(not(debug_assertions), deny(warnings))]
4#![deny(rust_2018_idioms)]
5#![deny(rust_2021_compatibility)]
6#![deny(missing_debug_implementations)]
7#![deny(rustdoc::broken_intra_doc_links)]
8#![deny(clippy::all)]
9#![deny(clippy::explicit_deref_methods)]
10#![deny(clippy::explicit_into_iter_loop)]
11#![deny(clippy::explicit_iter_loop)]
12#![deny(clippy::must_use_candidate)]
13#![cfg_attr(not(test), deny(clippy::panic_in_result_fn))]
14#![cfg_attr(not(debug_assertions), deny(clippy::used_underscore_binding))]
15
16use ethercat_soem_ctx as ctx;
17use ethercat_types as ec;
18use num_traits::cast::FromPrimitive;
19use std::{convert::TryFrom, ffi::CString, time::Duration};
20
21mod al_status;
22mod error;
23mod util;
24
25pub use self::{al_status::*, error::Error};
26
27const DEFAULT_RECV_TIMEOUT: Duration = Duration::from_micros(2_000);
28const DEFAULT_SDO_TIMEOUT: Duration = Duration::from_millis(3_000);
29
30const MAX_SM_CNT: u8 = 8;
31
32const SDO_IDX_PDO_ASSIGN: u16 = 0x1C10;
33const SDO_IDX_SM_COMM_TYPE: ec::Idx = ec::Idx::new(0x1C00);
34
35const EC_NOFRAME: i32 = -1;
36
37type Result<T> = std::result::Result<T, Error>;
38
39type SdoInfo = Vec<(ec::SdoInfo, Vec<Option<ec::SdoEntryInfo>>)>;
40type PdoInfo = Vec<(ec::PdoInfo, Vec<PdoEntryInfo>)>;
41
42// TODO: merge into ec::PdoEntryInfo
43#[derive(Debug, Clone, PartialEq)]
44pub struct PdoEntryInfo {
45    pub idx: ec::PdoEntryIdx,
46    pub pos: ec::PdoEntryPos,
47    pub data_type: ec::DataType,
48    pub offset: ec::Offset,
49    pub bit_len: usize,
50    pub name: String,
51    pub sm: ec::SmType,
52    pub sdo: ec::SdoIdx,
53}
54
55#[allow(missing_debug_implementations)]
56pub struct Master {
57    ctx: Box<ctx::Ctx>,
58    sdos: Vec<SdoInfo>,
59    pdos: Vec<PdoInfo>,
60}
61
62impl Master {
63    pub fn try_new<S: Into<String>>(iface: S) -> Result<Self> {
64        let mut master = Self {
65            ctx: Box::new(ctx::Ctx::default()),
66            sdos: vec![],
67            pdos: vec![],
68        };
69        master.init(iface.into())?;
70        Ok(master)
71    }
72
73    #[doc(hidden)]
74    /// Don't use this!
75    pub fn ptr(&mut self) -> *mut ctx::Ctx {
76        let reference: &mut ctx::Ctx = &mut *self.ctx;
77        reference as *mut ctx::Ctx
78    }
79
80    #[doc(hidden)]
81    /// Don't use this!
82    pub fn io_map(&mut self) -> &mut [u8] {
83        &mut self.ctx.io_map
84    }
85
86    #[doc(hidden)]
87    /// Don't use this!
88    pub unsafe fn from_ptr(ctx_ptr: *mut ctx::Ctx) -> Self {
89        Master::from_ptr_with_caches(ctx_ptr, vec![], vec![])
90    }
91
92    #[doc(hidden)]
93    /// Don't use this!
94    pub unsafe fn from_ptr_with_caches(
95        ctx_ptr: *mut ctx::Ctx,
96        sdos: Vec<SdoInfo>,
97        pdos: Vec<PdoInfo>,
98    ) -> Self {
99        Master {
100            ctx: Box::from_raw(ctx_ptr),
101            sdos,
102            pdos,
103        }
104    }
105
106    #[doc(hidden)]
107    /// Drop a secondary instance safely
108    ///
109    /// Drop an instance that has been created with `from_ptr` or `from_ptr_with_caches`
110    /// from a shared context pointer that is dropped later. Otherwise dropping this
111    /// instance would result in a double-free.
112    ///
113    /// TODO: Avoid all these ugly hacks!
114    pub fn leak_ptr(self) {
115        let Self { ctx, .. } = self;
116        Box::leak(ctx);
117    }
118
119    #[doc(hidden)]
120    /// Don't use this!
121    #[must_use]
122    pub fn sdo_info_cache(&self) -> &[SdoInfo] {
123        &self.sdos
124    }
125
126    #[doc(hidden)]
127    /// Don't use this!
128    #[must_use]
129    pub fn pdo_info_cache(&self) -> &[PdoInfo] {
130        &self.pdos
131    }
132
133    fn init(&mut self, iface: String) -> Result<()> {
134        log::debug!("Initialise SOEM stack: bind socket to {}", iface);
135        let iface = CString::new(iface).map_err(|_| Error::Iface)?;
136        let res = self.ctx.init(iface);
137        if res <= 0 {
138            log::debug!("Context errors: {:?}", self.ctx_errors());
139            return Err(Error::Init);
140        }
141        Ok(())
142    }
143
144    /// Automatically configure slaves and fetch SDO & PDO information.
145    pub fn auto_config(&mut self) -> Result<()> {
146        log::debug!("Find and auto-config slaves");
147        let usetable = false;
148        let res = self.ctx.config_init(usetable);
149        if res <= 0 {
150            log::debug!("Context errors: {:?}", self.ctx_errors());
151            return Err(match res {
152                -1 => Error::NoFrame,
153                -2 => Error::OtherFrame,
154                _ => Error::NoSlaves,
155            });
156        }
157        let slave_count = self.ctx.slave_count();
158        log::debug!("{} slaves found", slave_count);
159        let res = self.ctx.config_dc();
160        if res == 0 {
161            log::debug!("Context errors: {:?}", self.ctx_errors());
162            return Err(Error::CfgDc);
163        }
164        let group = 0;
165        let io_map_size = self.ctx.config_map_group(group);
166        if io_map_size <= 0 {
167            log::debug!("Context errors: {:?}", self.ctx_errors());
168            return Err(Error::CfgMapGroup);
169        }
170        let expected_wkc = self.group_outputs_wkc(0)? * 2 + self.group_inputs_wkc(0)?;
171        log::debug!("Expected working counter = {}", expected_wkc);
172        self.scan_slave_objects()?;
173        self.pdos = self.coe_pdo_info()?;
174        Ok(())
175    }
176
177    fn scan_slave_objects(&mut self) -> Result<()> {
178        log::debug!("Fetch SDO info");
179        for i in 0..self.ctx.slave_count() {
180            let pos = ec::SlavePos::from(i as u16);
181            let sdo_info = self.read_od_list(pos)?;
182            self.sdos.push(sdo_info);
183        }
184        Ok(())
185    }
186
187    fn coe_pdo_info(&mut self) -> Result<Vec<PdoInfo>> {
188        log::debug!("Fetch PDO mapping according to CoE");
189        let mut res = vec![];
190
191        for slave in 0..self.ctx.slave_count() as u16 {
192            let slave_pos = ec::SlavePos::new(slave);
193
194            let obj_cnt = self.sm_comm_type_sdo(slave_pos, 0)?;
195            if obj_cnt <= 2 {
196                log::warn!("Slave {}: found less than two sync manager types", slave);
197                continue;
198            }
199
200            let mut sm_cnt = obj_cnt - 1; // make sm_cnt equal to number of defined SM
201
202            if sm_cnt > MAX_SM_CNT {
203                log::debug!(
204                    "Slave {}: limit to max. {} number of sync managers",
205                    slave,
206                    MAX_SM_CNT
207                );
208                sm_cnt = MAX_SM_CNT;
209            }
210
211            let mut pdo_info = vec![];
212
213            let mut sm_types = vec![];
214
215            for sm in 2..=sm_cnt {
216                let sm_type_id = self
217                    .slaves()
218                    .get(slave as usize)
219                    .and_then(|s: &ctx::Slave| s.sm_type().get(sm as usize).cloned())
220                    .ok_or(Error::InvalidSmType)?;
221
222                let sm_type = ec::SmType::try_from(sm_type_id)?;
223                if sm == 2 && sm_type == ec::SmType::MbxRd {
224                    log::warn!(
225                        "SM2 has type 2 == mailbox out, this is a bug in {:?}!",
226                        slave_pos
227                    );
228                    continue;
229                }
230                sm_types.push((sm, sm_type));
231            }
232            for (sm, sm_type) in &sm_types {
233                log::debug!(
234                    "SM {} has type {} (= {:?})",
235                    sm,
236                    u8::from(*sm_type),
237                    sm_type
238                );
239            }
240            let mut pdo_cnt_offset = 0;
241            let mut pdo_entry_cnt_offset = 0;
242
243            for t in &[ec::SmType::Outputs, ec::SmType::Inputs] {
244                for (sm, sm_type) in sm_types.iter().filter(|(_, sm_type)| sm_type == t) {
245                    log::debug!("Check PDO assignment for SM {}", sm);
246                    let pdo_assign = self.si_pdo_assign(
247                        slave_pos,
248                        *sm,
249                        *sm_type,
250                        pdo_cnt_offset,
251                        pdo_entry_cnt_offset,
252                    )?;
253                    log::debug!(
254                        "Slave {}: SM {} (type: {:?}): read the assigned PDOs",
255                        slave,
256                        sm,
257                        sm_type
258                    );
259                    pdo_cnt_offset += pdo_assign.len() as u8;
260                    pdo_entry_cnt_offset += pdo_assign
261                        .iter()
262                        .map(|(_, entries)| entries.len())
263                        .sum::<usize>() as u8;
264                    pdo_info.extend_from_slice(&pdo_assign);
265                }
266            }
267            res.push(pdo_info);
268        }
269        Ok(res)
270    }
271
272    /// Read PDO assign structure
273    fn si_pdo_assign(
274        &mut self,
275        slave: ec::SlavePos,
276        sm: u8,
277        sm_type: ec::SmType,
278        pdo_pos_offset: u8,
279        pdo_entry_pos_offset: u8,
280    ) -> Result<PdoInfo> {
281        let idx = SDO_IDX_PDO_ASSIGN + sm as u16;
282
283        let mut val = [0];
284        self.read_sdo(
285            slave,
286            ec::SdoIdx::new(idx, 0),
287            false,
288            &mut val,
289            DEFAULT_SDO_TIMEOUT,
290        )?;
291        let pdo_cnt = val[0];
292        log::debug!("Available PDO entries in 0x{:X}: {}", idx, pdo_cnt);
293
294        let mut pdo_entry_pos = pdo_entry_pos_offset;
295        let mut bit_offset = 0_usize;
296
297        let mut pdos = vec![];
298
299        // read all PDO's
300        for pdo_sub in 1..=pdo_cnt {
301            let pdo_pos = pdo_pos_offset + pdo_sub - 1;
302            log::debug!("{:?}: read PDO IDX from 0x{:X}.0x{:X}", slave, idx, pdo_sub);
303
304            let mut val = [0; 2];
305            self.read_sdo(
306                slave,
307                ec::SdoIdx::new(idx, pdo_sub),
308                false,
309                &mut val,
310                DEFAULT_SDO_TIMEOUT,
311            )?;
312            let pdo_idx = u16::from_ne_bytes(val);
313            log::debug!("PDO IDX is 0x{:X}", pdo_idx);
314
315            log::debug!("{:?}: read PDO count from 0x{:X}.0x{:X}", slave, pdo_idx, 0);
316            let mut val = [0];
317            self.read_sdo(
318                slave,
319                ec::SdoIdx::new(pdo_idx, 0),
320                false,
321                &mut val,
322                DEFAULT_SDO_TIMEOUT,
323            )?;
324            let pdo_entry_cnt = val[0];
325            log::debug!("... PDO count is {}", pdo_entry_cnt);
326
327            let mut pdo_entries = vec![];
328
329            for entry_sub in 1..=pdo_entry_cnt {
330                let pdo_data_sdo_idx = ec::SdoIdx::new(pdo_idx, entry_sub);
331                let mut val = [0; 4];
332                self.read_sdo(
333                    slave,
334                    pdo_data_sdo_idx,
335                    false,
336                    &mut val,
337                    DEFAULT_SDO_TIMEOUT,
338                )?;
339
340                let data = u32::from_ne_bytes(val);
341                let bit_len = (data & 0x_00FF) as usize;
342                let obj_idx = (data >> 16) as u16;
343                let obj_subidx = ((data >> 8) & 0x_0000_00FF) as u8;
344
345                let sdo = ec::SdoIdx::new(obj_idx, obj_subidx);
346                let sdo_entry = self.cached_sdo_entry(slave, sdo);
347
348                let (name, data_type) = match sdo_entry {
349                    Some(e) => {
350                        debug_assert_eq!(bit_len as u16, e.bit_len);
351                        (e.description.clone(), e.data_type)
352                    }
353                    None => {
354                        log::warn!("Could not find SDO ({:?}) entry description", sdo);
355                        (String::new(), ec::DataType::Raw)
356                    }
357                };
358                let idx = ec::PdoEntryIdx::new(pdo_idx, entry_sub);
359                let pos = ec::PdoEntryPos::new(pdo_entry_pos);
360
361                let offset = ec::Offset {
362                    byte: bit_offset / 8,
363                    bit: (bit_offset % 8) as u32,
364                };
365                bit_offset += bit_len;
366
367                let pdo_entry_info = PdoEntryInfo {
368                    bit_len,
369                    data_type,
370                    name,
371                    sdo,
372                    idx,
373                    sm: sm_type,
374                    pos,
375                    offset,
376                };
377
378                pdo_entries.push(pdo_entry_info);
379                pdo_entry_pos += 1;
380            }
381            if let Some(e) = pdo_entries.get(0) {
382                let sdo_info = self.cached_sdo_info(slave, e.sdo.idx);
383
384                let name = match sdo_info {
385                    Some(info) => info.name.clone(),
386                    None => {
387                        log::warn!("Could not find SDO ({:?}) name", e.sdo.idx);
388                        String::new()
389                    }
390                };
391
392                let pdo_info = ec::PdoInfo {
393                    sm: ec::SmIdx::new(sm),
394                    pos: ec::PdoPos::new(pdo_pos),
395                    idx: pdo_idx.into(),
396                    entry_count: pdo_entry_cnt,
397                    name,
398                };
399                pdos.push((pdo_info, pdo_entries));
400            }
401        }
402        Ok(pdos)
403    }
404
405    fn cached_sdo_entry(
406        &mut self,
407        slave: ec::SlavePos,
408        idx: ec::SdoIdx,
409    ) -> Option<&ec::SdoEntryInfo> {
410        self.sdos
411            .get(usize::from(slave))
412            .and_then(|sdos| sdos.iter().find(|(info, _entries)| info.idx == idx.idx))
413            .map(|(_, x)| x)
414            .and_then(|entries| entries.get(u8::from(idx.sub_idx) as usize))
415            .and_then(Option::as_ref)
416    }
417
418    fn cached_sdo_info(&mut self, slave: ec::SlavePos, idx: ec::Idx) -> Option<&ec::SdoInfo> {
419        self.sdos
420            .get(usize::from(slave))
421            .and_then(|sdos| sdos.iter().find(|(info, _entries)| info.idx == idx))
422            .map(|(x, _)| x)
423    }
424
425    fn sm_comm_type_sdo(&mut self, slave: ec::SlavePos, sub_idx: u8) -> Result<u8> {
426        let mut val = [0];
427        self.read_sdo(
428            slave,
429            ec::SdoIdx {
430                idx: SDO_IDX_SM_COMM_TYPE,
431                sub_idx: sub_idx.into(),
432            },
433            false,
434            &mut val,
435            DEFAULT_SDO_TIMEOUT,
436        )?;
437        Ok(val[0])
438    }
439
440    pub fn request_states(&mut self, state: ec::AlState) -> Result<()> {
441        log::debug!("wait for all slaves to reach {:?} state", state);
442        let s = u8::from(state) as u16;
443        for i in 0..=self.ctx.slave_count() {
444            self.ctx.slaves_mut()[i].set_state(s);
445        }
446        match self.ctx.write_state(0) {
447            EC_NOFRAME => Err(Error::NoFrame),
448            0 => {
449                if self.ctx.is_err() {
450                    log::debug!("Context errors: {:?}", self.ctx_errors());
451                }
452                log::warn!("Could not set state {:?} for slaves", state);
453                Err(Error::SetState)
454            }
455            _ => Ok(()),
456        }
457    }
458
459    pub fn check_states(&mut self, state: ec::AlState, timeout: Duration) -> Result<ec::AlState> {
460        let res = self.ctx.state_check(0, u8::from(state) as u16, timeout);
461        if res == 0 {
462            log::debug!("Context errors: {:?}", self.ctx_errors());
463            log::warn!("Could not check state {:?} for slaves", state);
464            return Err(Error::CheckState);
465        }
466        let found_state = ec::AlState::try_from(res as u8).map_err(|_| {
467            log::warn!("Could not translate u16 `{}` into AlState", res);
468            Error::CheckState
469        })?;
470        if found_state != state {
471            log::debug!(
472                "Current state {:?} does not match expected state {:?}",
473                found_state,
474                state
475            );
476        }
477        Ok(found_state)
478    }
479
480    #[must_use]
481    pub fn slaves(&self) -> &[ctx::Slave] {
482        let cnt = self.ctx.slave_count();
483        &self.ctx.slaves()[1..=cnt]
484    }
485
486    pub fn slaves_mut(&mut self) -> &mut [ctx::Slave] {
487        let cnt = self.ctx.slave_count();
488        &mut self.ctx.slaves_mut()[1..=cnt]
489    }
490
491    pub fn states(&mut self) -> Result<Vec<ec::AlState>> {
492        let lowest_state = self.ctx.read_state();
493        if lowest_state <= 0 {
494            log::debug!("Context errors: {:?}", self.ctx_errors());
495            return Err(Error::ReadStates);
496        }
497        let states = (1..=self.ctx.slave_count())
498            .into_iter()
499            .map(|i| self.slave_state(i))
500            .collect::<Result<_>>()?;
501        Ok(states)
502    }
503
504    fn slave_state(&self, slave: usize) -> Result<ec::AlState> {
505        let s = &self.ctx.slaves()[slave];
506        let state = s.state();
507        let status_code = s.al_status_code();
508        ec::AlState::try_from(state as u8).map_err(|_| Error::AlState(AlStatus::from(status_code)))
509    }
510
511    pub fn send_processdata(&mut self) -> Result<()> {
512        self.ctx.send_processdata();
513        if self.ctx.is_err() {
514            log::debug!("Context errors: {:?}", self.ctx_errors());
515            return Err(Error::SendProcessData);
516        }
517        Ok(())
518    }
519
520    pub fn recv_processdata(&mut self) -> Result<usize> {
521        let wkc = self.ctx.receive_processdata(DEFAULT_RECV_TIMEOUT);
522        if self.ctx.is_err() {
523            log::debug!("Context errors: {:?}", self.ctx_errors());
524            return Err(Error::RecvProcessData);
525        }
526        Ok(wkc as usize)
527    }
528
529    pub fn group_outputs_wkc(&mut self, i: usize) -> Result<usize> {
530        if i >= self.ctx.groups().len() || i >= self.max_group() {
531            if self.ctx.is_err() {
532                log::debug!("Context errors: {:?}", self.ctx_errors());
533            }
534            return Err(Error::GroupId);
535        }
536        Ok(self.ctx.groups()[i].outputs_wkc() as usize)
537    }
538
539    pub fn group_inputs_wkc(&mut self, i: usize) -> Result<usize> {
540        if i >= self.ctx.groups().len() || i >= self.max_group() {
541            log::debug!("Context errors: {:?}", self.ctx_errors());
542            return Err(Error::GroupId);
543        }
544        Ok(self.ctx.groups()[i].inputs_wkc() as usize)
545    }
546
547    #[must_use]
548    pub fn slave_count(&self) -> usize {
549        self.ctx.slave_count() as usize
550    }
551
552    #[must_use]
553    pub fn max_group(&self) -> usize {
554        self.ctx.max_group() as usize
555    }
556
557    #[must_use]
558    pub fn dc_time(&self) -> i64 {
559        self.ctx.dc_time()
560    }
561
562    fn read_od_desc(&mut self, item: u16, od_list: &mut ctx::OdList) -> Result<ec::SdoInfo> {
563        let res = self.ctx.read_od_description(item, od_list);
564        let pos = ec::SdoPos::from(item);
565        if res <= 0 {
566            log::debug!("Context errors: {:?}", self.ctx_errors());
567            return Err(Error::ReadOdDesc(pos));
568        }
569        let i = item as usize;
570        let idx = ec::Idx::from(od_list.indexes()[i]);
571        let object_code = Some(od_list.object_codes()[i]);
572        let name = od_list.names()[i].clone();
573        let max_sub_idx = ec::SubIdx::from(od_list.max_subs()[i]);
574        let info = ec::SdoInfo {
575            pos,
576            idx,
577            max_sub_idx,
578            object_code,
579            name,
580        };
581        Ok(info)
582    }
583
584    fn read_oe_list(&mut self, item: u16, od_list: &mut ctx::OdList) -> Result<ctx::OeList> {
585        let mut oe_list = ctx::OeList::default();
586        let res = self.ctx.read_oe(item, od_list, &mut oe_list);
587        let pos = ec::SdoPos::from(item);
588        if res <= 0 {
589            log::debug!("Context errors: {:?}", self.ctx_errors());
590            return Err(Error::ReadOeList(pos));
591        }
592        Ok(oe_list)
593    }
594
595    pub fn read_od_list(&mut self, slave: ec::SlavePos) -> Result<SdoInfo> {
596        let mut od_list = ctx::OdList::default();
597        let res = self.ctx.read_od_list(u16::from(slave) + 1, &mut od_list);
598
599        if res <= 0 {
600            log::debug!("Context errors: {:?}", self.ctx_errors());
601            return Err(Error::ReadOdList(slave));
602        }
603        log::debug!(
604            "CoE Object Description: found {} entries",
605            od_list.entries()
606        );
607        let mut sdos = vec![];
608        for i in 0..od_list.entries() {
609            let sdo_info = self.read_od_desc(i as u16, &mut od_list)?;
610            let oe_list = self.read_oe_list(i as u16, &mut od_list)?;
611            let mut entries = vec![];
612            for j in 0..=u8::from(sdo_info.max_sub_idx) as usize {
613                let dt = oe_list.data_types()[j];
614                let len = oe_list.bit_lengths()[j];
615                let bit_len = if len == 0 { None } else { Some(len) };
616                let info = ec::DataType::from_u16(dt)
617                    .zip(bit_len)
618                    .map(|(data_type, bit_len)| {
619                        let access = access_from_u16(oe_list.object_access()[j]);
620                        let description = oe_list.names()[j].clone();
621                        ec::SdoEntryInfo {
622                            data_type,
623                            bit_len,
624                            access,
625                            description,
626                        }
627                    });
628                if info.is_none() {
629                    log::warn!(
630                        "Invalid entry at {:?} index {}: Unknown data type ({}) with bit length {}",
631                        sdo_info.pos,
632                        j,
633                        dt,
634                        len
635                    );
636                }
637                entries.push(info);
638            }
639            sdos.push((sdo_info, entries));
640        }
641        Ok(sdos)
642    }
643
644    pub fn read_sdo<'t>(
645        &mut self,
646        slave: ec::SlavePos,
647        idx: ec::SdoIdx,
648        access_complete: bool,
649        target: &'t mut [u8],
650        timeout: Duration,
651    ) -> Result<&'t mut [u8]> {
652        let index = u16::from(idx.idx);
653        let subindex = u8::from(idx.sub_idx);
654        let (wkc, slice) = self.ctx.sdo_read(
655            u16::from(slave) + 1,
656            index,
657            subindex,
658            access_complete,
659            target,
660            timeout,
661        );
662        if wkc <= 0 {
663            let errs = self.ctx_errors();
664            log::debug!("Context errors: {:?}", errs);
665            for e in errs {
666                if e.err_type == ctx::ErrType::Packet && e.abort_code == 3 {
667                    log::warn!("data container too small for type");
668                }
669            }
670            return Err(Error::ReadSdo(slave, idx));
671        }
672        Ok(slice)
673    }
674
675    pub fn read_sdo_entry(
676        &mut self,
677        slave: ec::SlavePos,
678        idx: ec::SdoIdx,
679        timeout: Duration,
680    ) -> Result<ec::Value> {
681        let info = self
682            .sdos
683            .get(usize::from(slave))
684            .and_then(|info| info.iter().find(|(info, _)| info.idx == idx.idx))
685            .and_then(|(_, entries)| entries.get(u8::from(idx.sub_idx) as usize))
686            .and_then(Option::as_ref)
687            .ok_or(Error::SubIdxNotFound(slave, idx))?;
688        let dt = info.data_type;
689        let len = info.bit_len as usize;
690        let byte_count = if len % 8 == 0 { len / 8 } else { (len / 8) + 1 };
691        let mut target = vec![0; byte_count];
692        let raw_value = self.read_sdo(slave, idx, false, &mut target, timeout)?;
693        debug_assert!(!raw_value.is_empty());
694        log::debug!(
695            "SDO raw data at {:?} 0x{:X}.{:X} is {:?}",
696            slave,
697            u16::from(idx.idx),
698            u8::from(idx.sub_idx),
699            raw_value
700        );
701        util::value_from_slice(dt, raw_value, 0)
702    }
703
704    pub fn read_sdo_complete(
705        &mut self,
706        slave: ec::SlavePos,
707        idx: ec::Idx,
708        timeout: Duration,
709    ) -> Result<Vec<Option<ec::Value>>> {
710        let entries = self
711            .sdos
712            .get(usize::from(slave))
713            .and_then(|info| info.iter().find(|(info, _)| info.idx == idx))
714            .map(|(_, entries)| entries)
715            .ok_or(Error::IdxNotFound(slave, idx))?;
716        let entries: Vec<Option<(usize, ec::DataType)>> = entries
717            .iter()
718            .map(|e| {
719                e.as_ref().map(|e| {
720                    let len = e.bit_len as usize;
721                    let byte_count = if len % 8 == 0 { len / 8 } else { (len / 8) + 1 };
722                    (byte_count, e.data_type)
723                })
724            })
725            .collect();
726        let max_byte_count: usize = entries
727            .iter()
728            .filter_map(Option::as_ref)
729            .map(|(cnt, _)| *cnt)
730            .max()
731            .unwrap_or(0);
732        let buff_size = max_byte_count * entries.len();
733        let mut target = vec![0; buff_size];
734        let raw = self.read_sdo(
735            slave,
736            ec::SdoIdx {
737                idx,
738                sub_idx: ec::SubIdx::from(0),
739            },
740            true,
741            &mut target,
742            timeout,
743        )?;
744        let mut byte_pos = 0;
745        let mut values = vec![];
746        for e in entries {
747            let res = match e {
748                Some((cnt, data_type)) => {
749                    let raw_value = &raw[byte_pos..byte_pos + cnt];
750                    let val = util::value_from_slice(data_type, raw_value, 0)?;
751                    byte_pos += cnt;
752                    Some(val)
753                }
754                None => None,
755            };
756            values.push(res);
757        }
758        Ok(values)
759    }
760
761    pub fn write_sdo(
762        &mut self,
763        slave: ec::SlavePos,
764        idx: ec::SdoIdx,
765        access_complete: bool,
766        data: &[u8],
767        timeout: Duration,
768    ) -> Result<()> {
769        let index = u16::from(idx.idx);
770        let subindex = u8::from(idx.sub_idx);
771        let wkc = self.ctx.sdo_write(
772            u16::from(slave) + 1,
773            index,
774            subindex,
775            access_complete,
776            data,
777            timeout,
778        );
779
780        if wkc <= 0 {
781            let errs = self.ctx_errors();
782            log::debug!("Context errors: {:?}", errs);
783            return Err(Error::WriteSdo(slave, idx));
784        }
785        Ok(())
786    }
787
788    pub fn write_sdo_entry(
789        &mut self,
790        slave: ec::SlavePos,
791        idx: ec::SdoIdx,
792        value: ec::Value,
793        timeout: Duration,
794    ) -> Result<()> {
795        let index = u16::from(idx.idx);
796        let subindex = u8::from(idx.sub_idx);
797        let data = util::value_to_bytes(value)?;
798        log::debug!(
799            "Write SDO raw data {:?} to {:?} 0x{:X}.{:X}",
800            data,
801            slave,
802            u16::from(idx.idx),
803            u8::from(idx.sub_idx),
804        );
805        let wkc = self
806            .ctx
807            .sdo_write(u16::from(slave) + 1, index, subindex, false, &data, timeout);
808
809        if wkc <= 0 {
810            let errs = self.ctx_errors();
811            log::debug!("Context errors: {:?}", errs);
812            return Err(Error::WriteSdo(slave, idx));
813        }
814        Ok(())
815    }
816
817    #[must_use]
818    pub fn pdo_values(&self) -> Vec<Vec<(ec::Idx, Vec<ec::Value>)>> {
819        let mut all_pdos = vec![];
820        for (i, slave) in self.slaves().iter().enumerate() {
821            let mut slave_pdos = vec![];
822            if let Some(pdo_meta_data) = self.pdos.get(i) {
823                let inputs: &[u8] = slave.inputs();
824                let outputs: &[u8] = slave.outputs();
825
826                for (pdo_info, pdo_entries) in pdo_meta_data {
827                    let mut pdos = vec![];
828                    for PdoEntryInfo {
829                        bit_len,
830                        offset,
831                        data_type,
832                        sm,
833                        ..
834                    } in pdo_entries
835                    {
836                        let ec::Offset { byte, bit } = *offset;
837                        let slice = match sm {
838                            ec::SmType::Inputs => Some(inputs),
839                            ec::SmType::Outputs => Some(outputs),
840                            _ => None,
841                        };
842                        match slice {
843                            Some(d) => {
844                                let len = byte_cnt(*bit_len as usize);
845                                let raw = &d[byte..byte + len];
846                                match util::value_from_slice(*data_type, raw, bit as usize) {
847                                    Ok(val) => {
848                                        pdos.push(val);
849                                    }
850                                    Err(err) => {
851                                        log::warn!("{}", err);
852                                    }
853                                }
854                            }
855                            None => {
856                                log::warn!("Unexpected SM type: {:?}", sm);
857                            }
858                        }
859                    }
860                    slave_pdos.push((pdo_info.idx, pdos));
861                }
862            } else {
863                log::warn!("Could not find PDO meta data for Slave {}", i);
864            }
865            all_pdos.push(slave_pdos);
866        }
867
868        all_pdos
869    }
870
871    pub fn set_pdo_value(
872        &mut self,
873        slave: ec::SlavePos,
874        idx: ec::PdoEntryIdx,
875        v: ec::Value,
876    ) -> Result<()> {
877        let (data_type, offset) = {
878            let e = self
879                .pdos
880                .get(usize::from(slave))
881                .ok_or(Error::SlaveNotFound(slave))?
882                .iter()
883                .find(|(info, _)| info.idx == idx.idx)
884                .and_then(|(_, entries)| entries.iter().find(|e| e.idx == idx))
885                .ok_or(Error::PdoEntryNotFound(idx))?;
886            if e.sm != ec::SmType::Outputs {
887                return Err(Error::InvalidSmType);
888            }
889            (e.data_type, e.offset)
890        };
891        let s: &mut ctx::Slave = self
892            .slaves_mut()
893            .get_mut(usize::from(slave))
894            .ok_or(Error::SlaveNotFound(slave))?;
895        let bytes = util::value_to_bytes(v)?;
896
897        if data_type == ec::DataType::Bool {
898            debug_assert_eq!(bytes.len(), 1);
899            let mask = 1 << offset.bit;
900            if bytes[0] == 1 {
901                s.outputs_mut()[offset.byte] |= mask; // Set Bit
902            } else {
903                s.outputs_mut()[offset.byte] &= !mask; // Clear Bit
904            }
905        } else {
906            for (i, b) in bytes.into_iter().enumerate() {
907                s.outputs_mut()[offset.byte + i] = b;
908            }
909        }
910        Ok(())
911    }
912
913    fn ctx_errors(&mut self) -> Vec<ctx::Error> {
914        let mut errors = vec![];
915        while let Some(e) = self.ctx.pop_error() {
916            errors.push(e);
917        }
918        errors
919    }
920}
921
922fn byte_cnt(bits: usize) -> usize {
923    if bits % 8 == 0 {
924        bits / 8
925    } else {
926        (bits / 8) + 1
927    }
928}
929
930fn access_from_u16(x: u16) -> ec::SdoEntryAccess {
931    const RD_P: u16 = 0b_0000_0001; // Bit 0
932    const RD_S: u16 = 0b_0000_0010; // Bit 1
933    const RD_O: u16 = 0b_0000_0100; // Bit 2
934    const WR_P: u16 = 0b_0000_1000; // Bit 3
935    const WR_S: u16 = 0b_0001_0000; // Bit 4
936    const WR_O: u16 = 0b_0010_0000; // Bit 5
937                                    // Bit 6 RX PDO map
938                                    // Bit 7 TX PDO map
939
940    let p = access(x & RD_P > 0, x & WR_P > 0);
941    let s = access(x & RD_S > 0, x & WR_S > 0);
942    let o = access(x & RD_O > 0, x & WR_O > 0);
943
944    ec::SdoEntryAccess {
945        pre_op: p,
946        safe_op: s,
947        op: o,
948    }
949}
950
951fn access(read: bool, write: bool) -> ec::Access {
952    match (read, write) {
953        (true, false) => ec::Access::ReadOnly,
954        (false, true) => ec::Access::WriteOnly,
955        (true, true) => ec::Access::ReadWrite,
956        _ => ec::Access::Unknown,
957    }
958}
959
960#[cfg(test)]
961mod tests {
962    use super::*;
963
964    #[test]
965    fn get_access_type_from_u8() {
966        assert_eq!(
967            access_from_u16(0),
968            ec::SdoEntryAccess {
969                pre_op: ec::Access::Unknown,
970                safe_op: ec::Access::Unknown,
971                op: ec::Access::Unknown,
972            }
973        );
974        assert_eq!(
975            access_from_u16(0b_0000_0001),
976            ec::SdoEntryAccess {
977                pre_op: ec::Access::ReadOnly,
978                safe_op: ec::Access::Unknown,
979                op: ec::Access::Unknown,
980            }
981        );
982        assert_eq!(
983            access_from_u16(0b_0000_1001),
984            ec::SdoEntryAccess {
985                pre_op: ec::Access::ReadWrite,
986                safe_op: ec::Access::Unknown,
987                op: ec::Access::Unknown,
988            }
989        );
990        assert_eq!(
991            access_from_u16(0b_0001_1101),
992            ec::SdoEntryAccess {
993                pre_op: ec::Access::ReadWrite,
994                safe_op: ec::Access::WriteOnly,
995                op: ec::Access::ReadOnly,
996            }
997        );
998    }
999}