ethercat_soem_ctx/
lib.rs

1//! SOEM low level context
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_sys as sys;
17use std::{
18    ffi::{CStr, CString},
19    mem::{self, zeroed},
20    os::raw::c_void,
21    time::Duration,
22};
23
24mod error;
25mod group;
26mod od_list;
27mod oe_list;
28mod slave;
29mod sm;
30
31pub use crate::{error::*, group::*, od_list::*, oe_list::*, slave::*};
32
33const EC_MAX_GROUP: usize = 2;
34const EC_MAX_SLAVE: usize = 200;
35
36/// Size of EEPROM bitmap cache
37const EC_MAX_EEP_BITMAP: usize = 128;
38
39/// Size of EEPROM cache buffer
40const EC_MAX_EEP_BUF: usize = EC_MAX_EEP_BITMAP << 5;
41
42/// SOEM `ecx_context` wrapper
43#[allow(missing_debug_implementations)]
44pub struct Ctx {
45    #[allow(dead_code)]
46    port: Box<sys::ecx_portt>,
47    slave_list: Box<[Slave; EC_MAX_SLAVE]>,
48    slave_count: Box<i32>,
49    group_list: Box<[Group; EC_MAX_GROUP]>,
50    #[allow(dead_code)]
51    esi_buf: Box<[u8; EC_MAX_EEP_BUF]>,
52    #[allow(dead_code)]
53    esi_map: Box<[u32; EC_MAX_EEP_BITMAP]>,
54    #[allow(dead_code)]
55    e_list: Box<[u32; 456]>,
56    #[allow(dead_code)]
57    idx_stack: Box<[u64; 27]>,
58    #[allow(dead_code)]
59    ecat_error: Box<u8>,
60    dc_time: Box<i64>,
61    #[allow(dead_code)]
62    sm_comm_type: Box<[u8; 10]>,
63    #[allow(dead_code)]
64    pdo_assign: Box<[u8; 514]>,
65    #[allow(dead_code)]
66    pdo_desc: Box<[u8; 1026]>,
67    #[allow(dead_code)]
68    eep_sm: Box<[u16; 6]>,
69    #[allow(dead_code)]
70    eep_fmmu: Box<[u16; 4]>,
71
72    /// The original context
73    ecx_ctx: sys::ecx_context,
74
75    /// I/O map
76    pub io_map: [u8; 4096],
77}
78
79impl Default for Ctx {
80    fn default() -> Self {
81        let mut port = Box::new(sys::ecx_portt {
82            _bindgen_opaque_blob: [0; 6502],
83        });
84        let mut slave_list: Box<[Slave; EC_MAX_SLAVE]> = Box::new(unsafe { zeroed() });
85        let mut slave_count = Box::new(0);
86        let mut group_list: Box<[Group; EC_MAX_GROUP]> = Box::new(unsafe { zeroed() });
87        let mut esi_buf: Box<[u8; EC_MAX_EEP_BUF]> = Box::new([0; EC_MAX_EEP_BUF]);
88        let mut esi_map: Box<[u32; EC_MAX_EEP_BITMAP]> = Box::new([0; EC_MAX_EEP_BITMAP]);
89        let mut e_list: Box<[u32; 456]> = Box::new([0; 456]);
90        let mut idx_stack: Box<[u64; 27]> = Box::new([0; 27]);
91        let mut ecat_error: Box<u8> = Box::new(0);
92        let mut dc_time = Box::new(0);
93        let mut sm_comm_type: Box<[u8; 10]> = Box::new([0; 10]);
94        let mut pdo_assign: Box<[u8; 514]> = Box::new([0; 514]);
95        let mut pdo_desc: Box<[u8; 1026]> = Box::new([0; 1026]);
96        let mut eep_sm: Box<[u16; 6]> = Box::new([0; 6]);
97        let mut eep_fmmu: Box<[u16; 4]> = Box::new([0; 4]);
98        let io_map = [0; 4096];
99
100        // The original context
101        let ecx_ctx = sys::ecx_context {
102            port: &mut *port,
103            slavelist: slave_list.as_mut_ptr() as *mut sys::ec_slave,
104            slavecount: &mut *slave_count,
105            maxslave: EC_MAX_SLAVE as i32,
106            grouplist: group_list.as_mut_ptr() as *mut sys::ec_group,
107            maxgroup: EC_MAX_GROUP as i32,
108            esibuf: esi_buf.as_mut_ptr(),
109            esimap: esi_map.as_mut_ptr(),
110            esislave: 0,
111            elist: &mut *e_list,
112            idxstack: &mut *idx_stack,
113            ecaterror: &mut *ecat_error,
114            DCtime: &mut *dc_time,
115            SMcommtype: &mut *sm_comm_type,
116            PDOassign: &mut *pdo_assign,
117            PDOdesc: &mut *pdo_desc,
118            eepSM: &mut *eep_sm,
119            eepFMMU: &mut *eep_fmmu,
120            FOEhook: None,
121            EOEhook: None,
122            manualstatechange: 0,
123            userdata: std::ptr::null_mut(),
124        };
125
126        Self {
127            port,
128            slave_list,
129            slave_count,
130            group_list,
131            esi_buf,
132            esi_map,
133            e_list,
134            idx_stack,
135            ecat_error,
136            dc_time,
137            sm_comm_type,
138            pdo_assign,
139            pdo_desc,
140            eep_sm,
141            eep_fmmu,
142            ecx_ctx,
143            io_map,
144        }
145    }
146}
147
148impl Ctx {
149    /// Initialise lib in single NIC mode.
150    ///
151    /// Return > 0 if OK.
152    pub fn init(&mut self, iface: CString) -> i32 {
153        unsafe { sys::ecx_init(&mut self.ecx_ctx, iface.as_ptr()) }
154    }
155    pub fn config_init(&mut self, use_table: bool) -> i32 {
156        unsafe { sys::ecx_config_init(&mut self.ecx_ctx, if use_table { 1 } else { 0 }) }
157    }
158    pub fn config_map_group(&mut self, group: u8) -> i32 {
159        unsafe {
160            sys::ecx_config_map_group(
161                &mut self.ecx_ctx,
162                self.io_map.as_mut_ptr() as *mut std::ffi::c_void,
163                group,
164            )
165        }
166    }
167    pub fn config_dc(&mut self) -> u8 {
168        unsafe { sys::ecx_configdc(&mut self.ecx_ctx) }
169    }
170    pub const fn slave_count(&self) -> usize {
171        *self.slave_count as usize
172    }
173    pub fn slaves(&self) -> &[Slave; EC_MAX_SLAVE] {
174        &self.slave_list
175    }
176    pub fn slaves_mut(&mut self) -> &mut [Slave; EC_MAX_SLAVE] {
177        &mut self.slave_list
178    }
179    pub const fn groups(&self) -> &[Group; EC_MAX_GROUP] {
180        &self.group_list
181    }
182    /// Write slave state, if slave = 0 then write to all slaves.
183    ///
184    /// The function does not check if the actual state is changed.
185    /// It returns Workcounter or `EC_NOFRAME` (= `-1`),
186    pub fn write_state(&mut self, slave: u16) -> i32 {
187        unsafe { sys::ecx_writestate(&mut self.ecx_ctx, slave) }
188    }
189    /// Check actual slave state.
190    ///
191    /// This is a blocking function.
192    /// To refresh the state of all slaves `read_state()` should be called.
193    ///
194    /// Parameter `slave` = Slave number, 0 = all slaves (only the "slavelist[0].state" is refreshed).
195    ///
196    /// It returns requested state, or found state after timeout.
197    pub fn state_check(&mut self, slave: u16, state: u16, timeout: Duration) -> u16 {
198        unsafe { sys::ecx_statecheck(&mut self.ecx_ctx, slave, state, timeout.as_micros() as i32) }
199    }
200    /// Read all slave states in ec_slave.
201    ///
202    /// It returns the lowest state found,
203    pub fn read_state(&mut self) -> i32 {
204        unsafe { sys::ecx_readstate(&mut self.ecx_ctx) }
205    }
206    pub fn send_processdata(&mut self) -> i32 {
207        unsafe { sys::ecx_send_processdata(&mut self.ecx_ctx) }
208    }
209    pub fn receive_processdata(&mut self, timeout: Duration) -> i32 {
210        unsafe { sys::ecx_receive_processdata(&mut self.ecx_ctx, timeout.as_micros() as i32) }
211    }
212    pub fn read_od_list(&mut self, slave: u16, od_list: &mut OdList) -> i32 {
213        unsafe { sys::ecx_readODlist(&mut self.ecx_ctx, slave, &mut od_list.0) }
214    }
215    pub fn read_od_description(&mut self, item: u16, od_list: &mut OdList) -> i32 {
216        unsafe { sys::ecx_readODdescription(&mut self.ecx_ctx, item, &mut od_list.0) }
217    }
218    pub fn read_oe(&mut self, item: u16, od_list: &mut OdList, oe_list: &mut OeList) -> i32 {
219        unsafe { sys::ecx_readOE(&mut self.ecx_ctx, item, &mut od_list.0, &mut oe_list.0) }
220    }
221    pub fn sdo_read<'t>(
222        &mut self,
223        slave: u16,
224        idx: u16,
225        sub_idx: u8,
226        access_complete: bool,
227        target: &'t mut [u8],
228        timeout: Duration,
229    ) -> (i32, &'t mut [u8]) {
230        let mut size = mem::size_of_val(target) as i32;
231        let timeout = timeout.as_micros() as i32; //TODO: check overflow
232        let wkc = unsafe {
233            sys::ecx_SDOread(
234                &mut self.ecx_ctx,
235                slave,
236                idx,
237                sub_idx,
238                if access_complete { 1 } else { 0 },
239                &mut size,
240                target.as_mut_ptr() as *mut c_void,
241                timeout,
242            )
243        };
244        if wkc <= 0 {
245            (wkc, target)
246        } else {
247            (wkc, &mut target[..size as usize])
248        }
249    }
250    pub fn sdo_write(
251        &mut self,
252        slave: u16,
253        idx: u16,
254        sub_idx: u8,
255        access_complete: bool,
256        data: &[u8],
257        timeout: Duration,
258    ) -> i32 {
259        let size = mem::size_of_val(data) as i32;
260        let timeout = timeout.as_micros() as i32; //TODO: check overflow
261        unsafe {
262            sys::ecx_SDOwrite(
263                &mut self.ecx_ctx,
264                slave,
265                idx,
266                sub_idx,
267                if access_complete { 1 } else { 0 },
268                size,
269                data.as_ptr() as *mut c_void,
270                timeout,
271            )
272        }
273    }
274    pub const fn max_group(&self) -> i32 {
275        self.ecx_ctx.maxgroup
276    }
277    pub fn is_err(&mut self) -> bool {
278        unsafe { sys::ecx_iserror(&mut self.ecx_ctx) != 0 }
279    }
280    pub fn pop_error(&mut self) -> Option<Error> {
281        let mut ec: sys::ec_errort = unsafe { zeroed() };
282        if unsafe { sys::ecx_poperror(&mut self.ecx_ctx, &mut ec) } != 0 {
283            Some(Error::from(ec))
284        } else {
285            None
286        }
287    }
288    pub const fn dc_time(&self) -> i64 {
289        *self.dc_time
290    }
291}
292
293fn c_array_to_string(data: *const i8) -> String {
294    unsafe { CStr::from_ptr(data).to_string_lossy().into_owned() }
295}
296
297#[cfg(test)]
298mod tests {
299
300    use super::*;
301
302    #[test]
303    fn context_wrapper() {
304        let mut wrapper = Ctx::default();
305        wrapper.port._bindgen_opaque_blob[3] = 5;
306        assert_eq!(
307            unsafe { (*wrapper.ecx_ctx.port)._bindgen_opaque_blob[3] },
308            5
309        );
310        assert_eq!(wrapper.slave_list.len(), 200);
311        assert_eq!(wrapper.slave_list[7].0.ALstatuscode, 0);
312        wrapper.slave_list[7].0.ALstatuscode = 33;
313        assert_eq!(
314            unsafe {
315                std::slice::from_raw_parts_mut(wrapper.ecx_ctx.slavelist, 200)[7].ALstatuscode
316            },
317            33
318        );
319    }
320}