1#![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#[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 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 pub fn io_map(&mut self) -> &mut [u8] {
83 &mut self.ctx.io_map
84 }
85
86 #[doc(hidden)]
87 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 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 pub fn leak_ptr(self) {
115 let Self { ctx, .. } = self;
116 Box::leak(ctx);
117 }
118
119 #[doc(hidden)]
120 #[must_use]
122 pub fn sdo_info_cache(&self) -> &[SdoInfo] {
123 &self.sdos
124 }
125
126 #[doc(hidden)]
127 #[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 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; 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 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 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; } else {
903 s.outputs_mut()[offset.byte] &= !mask; }
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; const RD_S: u16 = 0b_0000_0010; const RD_O: u16 = 0b_0000_0100; const WR_P: u16 = 0b_0000_1000; const WR_S: u16 = 0b_0001_0000; const WR_O: u16 = 0b_0010_0000; 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}