1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
//! SOEM low level context

use ethercat_soem_sys as sys;
use std::{
    ffi::{CStr, CString},
    mem::{self, zeroed},
    os::raw::c_void,
    time::Duration,
};

mod error;
mod group;
mod od_list;
mod oe_list;
mod slave;
mod sm;

pub use crate::{error::*, group::*, od_list::*, oe_list::*, slave::*};

const EC_MAX_GROUP: usize = 2;
const EC_MAX_SLAVE: usize = 200;

/// Size of EEPROM bitmap cache
const EC_MAX_EEP_BITMAP: usize = 128;

/// Size of EEPROM cache buffer
const EC_MAX_EEP_BUF: usize = EC_MAX_EEP_BITMAP << 5;

/// SOEM `ecx_context` wrapper
pub struct Ctx {
    #[allow(dead_code)]
    port: Box<sys::ecx_portt>,
    slave_list: Box<[Slave; EC_MAX_SLAVE]>,
    slave_count: Box<i32>,
    group_list: Box<[Group; EC_MAX_GROUP]>,
    #[allow(dead_code)]
    esi_buf: Box<[u8; EC_MAX_EEP_BUF]>,
    #[allow(dead_code)]
    esi_map: Box<[u32; EC_MAX_EEP_BITMAP]>,
    #[allow(dead_code)]
    e_list: Box<[u32; 456]>,
    #[allow(dead_code)]
    idx_stack: Box<[u64; 27]>,
    #[allow(dead_code)]
    ecat_error: Box<u8>,
    dc_time: Box<i64>,
    #[allow(dead_code)]
    sm_comm_type: Box<[u8; 10]>,
    #[allow(dead_code)]
    pdo_assign: Box<[u8; 514]>,
    #[allow(dead_code)]
    pdo_desc: Box<[u8; 1026]>,
    #[allow(dead_code)]
    eep_sm: Box<[u16; 6]>,
    #[allow(dead_code)]
    eep_fmmu: Box<[u16; 4]>,

    /// The original context
    ecx_ctx: sys::ecx_context,

    /// I/O map
    pub io_map: [u8; 4096],
}

impl Default for Ctx {
    fn default() -> Self {
        let mut port = Box::new(sys::ecx_portt {
            _bindgen_opaque_blob: [0; 6502],
        });
        let mut slave_list: Box<[Slave; EC_MAX_SLAVE]> = Box::new(unsafe { zeroed() });
        let mut slave_count = Box::new(0);
        let mut group_list: Box<[Group; EC_MAX_GROUP]> = Box::new(unsafe { zeroed() });
        let mut esi_buf: Box<[u8; EC_MAX_EEP_BUF]> = Box::new([0; EC_MAX_EEP_BUF]);
        let mut esi_map: Box<[u32; EC_MAX_EEP_BITMAP]> = Box::new([0; EC_MAX_EEP_BITMAP]);
        let mut e_list: Box<[u32; 456]> = Box::new([0; 456]);
        let mut idx_stack: Box<[u64; 27]> = Box::new([0; 27]);
        let mut ecat_error: Box<u8> = Box::new(0);
        let mut dc_time = Box::new(0);
        let mut sm_comm_type: Box<[u8; 10]> = Box::new([0; 10]);
        let mut pdo_assign: Box<[u8; 514]> = Box::new([0; 514]);
        let mut pdo_desc: Box<[u8; 1026]> = Box::new([0; 1026]);
        let mut eep_sm: Box<[u16; 6]> = Box::new([0; 6]);
        let mut eep_fmmu: Box<[u16; 4]> = Box::new([0; 4]);
        let io_map = [0; 4096];

        // The original context
        let ecx_ctx = sys::ecx_context {
            port: &mut *port,
            slavelist: slave_list.as_mut_ptr() as *mut sys::ec_slave,
            slavecount: &mut *slave_count,
            maxslave: EC_MAX_SLAVE as i32,
            grouplist: group_list.as_mut_ptr() as *mut sys::ec_group,
            maxgroup: EC_MAX_GROUP as i32,
            esibuf: esi_buf.as_mut_ptr(),
            esimap: esi_map.as_mut_ptr(),
            esislave: 0,
            elist: &mut *e_list,
            idxstack: &mut *idx_stack,
            ecaterror: &mut *ecat_error,
            DCtime: &mut *dc_time,
            SMcommtype: &mut *sm_comm_type,
            PDOassign: &mut *pdo_assign,
            PDOdesc: &mut *pdo_desc,
            eepSM: &mut *eep_sm,
            eepFMMU: &mut *eep_fmmu,
            FOEhook: None,
            EOEhook: None,
            manualstatechange: 0,
        };

        Self {
            port,
            slave_list,
            slave_count,
            group_list,
            esi_buf,
            esi_map,
            e_list,
            idx_stack,
            ecat_error,
            dc_time,
            sm_comm_type,
            pdo_assign,
            pdo_desc,
            eep_sm,
            eep_fmmu,
            ecx_ctx,
            io_map,
        }
    }
}

impl Ctx {
    /// Initialise lib in single NIC mode.
    ///
    /// Return > 0 if OK.
    pub fn init(&mut self, iface: CString) -> i32 {
        unsafe { sys::ecx_init(&mut self.ecx_ctx, iface.as_ptr()) }
    }
    pub fn config_init(&mut self, use_table: bool) -> i32 {
        unsafe { sys::ecx_config_init(&mut self.ecx_ctx, if use_table { 1 } else { 0 }) }
    }
    pub fn config_map_group(&mut self, group: u8) -> i32 {
        unsafe {
            sys::ecx_config_map_group(
                &mut self.ecx_ctx,
                self.io_map.as_mut_ptr() as *mut std::ffi::c_void,
                group,
            )
        }
    }
    pub fn config_dc(&mut self) -> u8 {
        unsafe { sys::ecx_configdc(&mut self.ecx_ctx) }
    }
    pub const fn slave_count(&self) -> usize {
        *self.slave_count as usize
    }
    pub fn slaves(&self) -> &[Slave; EC_MAX_SLAVE] {
        &self.slave_list
    }
    pub fn slaves_mut(&mut self) -> &mut [Slave; EC_MAX_SLAVE] {
        &mut self.slave_list
    }
    pub const fn groups(&self) -> &[Group; EC_MAX_GROUP] {
        &self.group_list
    }
    /// Write slave state, if slave = 0 then write to all slaves.
    ///
    /// The function does not check if the actual state is changed.
    /// It returns Workcounter or `EC_NOFRAME` (= `-1`),
    pub fn write_state(&mut self, slave: u16) -> i32 {
        unsafe { sys::ecx_writestate(&mut self.ecx_ctx, slave) }
    }
    /// Check actual slave state.
    ///
    /// This is a blocking function.
    /// To refresh the state of all slaves `read_state()` should be called.
    ///
    /// Parameter `slave` = Slave number, 0 = all slaves (only the "slavelist[0].state" is refreshed).
    ///
    /// It returns requested state, or found state after timeout.
    pub fn state_check(&mut self, slave: u16, state: u16, timeout: Duration) -> u16 {
        unsafe { sys::ecx_statecheck(&mut self.ecx_ctx, slave, state, timeout.as_micros() as i32) }
    }
    /// Read all slave states in ec_slave.
    ///
    /// It returns the lowest state found,
    pub fn read_state(&mut self) -> i32 {
        unsafe { sys::ecx_readstate(&mut self.ecx_ctx) }
    }
    pub fn send_processdata(&mut self) -> i32 {
        unsafe { sys::ecx_send_processdata(&mut self.ecx_ctx) }
    }
    pub fn receive_processdata(&mut self, timeout: Duration) -> i32 {
        unsafe { sys::ecx_receive_processdata(&mut self.ecx_ctx, timeout.as_micros() as i32) }
    }
    pub fn read_od_list(&mut self, slave: u16, od_list: &mut OdList) -> i32 {
        unsafe { sys::ecx_readODlist(&mut self.ecx_ctx, slave, &mut od_list.0) }
    }
    pub fn read_od_description(&mut self, item: u16, od_list: &mut OdList) -> i32 {
        unsafe { sys::ecx_readODdescription(&mut self.ecx_ctx, item, &mut od_list.0) }
    }
    pub fn read_oe(&mut self, item: u16, od_list: &mut OdList, oe_list: &mut OeList) -> i32 {
        unsafe { sys::ecx_readOE(&mut self.ecx_ctx, item, &mut od_list.0, &mut oe_list.0) }
    }
    pub fn sdo_read<'t>(
        &mut self,
        slave: u16,
        idx: u16,
        sub_idx: u8,
        access_complete: bool,
        target: &'t mut [u8],
        timeout: Duration,
    ) -> (i32, &'t mut [u8]) {
        let mut size = mem::size_of_val(target) as i32;
        let timeout = timeout.as_micros() as i32; //TODO: check overflow
        let wkc = unsafe {
            sys::ecx_SDOread(
                &mut self.ecx_ctx,
                slave,
                idx,
                sub_idx,
                if access_complete { 1 } else { 0 },
                &mut size,
                target.as_mut_ptr() as *mut c_void,
                timeout,
            )
        };
        if wkc <= 0 {
            (wkc, target)
        } else {
            (wkc, &mut target[..size as usize])
        }
    }
    pub fn sdo_write(
        &mut self,
        slave: u16,
        idx: u16,
        sub_idx: u8,
        access_complete: bool,
        data: &[u8],
        timeout: Duration,
    ) -> i32 {
        let size = mem::size_of_val(data) as i32;
        let timeout = timeout.as_micros() as i32; //TODO: check overflow
        unsafe {
            sys::ecx_SDOwrite(
                &mut self.ecx_ctx,
                slave,
                idx,
                sub_idx,
                if access_complete { 1 } else { 0 },
                size,
                data.as_ptr() as *mut c_void,
                timeout,
            )
        }
    }
    pub const fn max_group(&self) -> i32 {
        self.ecx_ctx.maxgroup
    }
    pub fn is_err(&mut self) -> bool {
        unsafe { sys::ecx_iserror(&mut self.ecx_ctx) != 0 }
    }
    pub fn pop_error(&mut self) -> Option<Error> {
        let mut ec: sys::ec_errort = unsafe { zeroed() };
        if unsafe { sys::ecx_poperror(&mut self.ecx_ctx, &mut ec) } != 0 {
            Some(Error::from(ec))
        } else {
            None
        }
    }
    pub const fn dc_time(&self) -> i64 {
        *self.dc_time
    }
}

fn c_array_to_string(data: *const i8) -> String {
    unsafe { CStr::from_ptr(data).to_string_lossy().into_owned() }
}

#[cfg(test)]
mod tests {

    use super::*;

    #[test]
    fn context_wrapper() {
        let mut wrapper = Ctx::default();
        wrapper.port._bindgen_opaque_blob[3] = 5;
        assert_eq!(
            unsafe { (*wrapper.ecx_ctx.port)._bindgen_opaque_blob[3] },
            5
        );
        assert_eq!(wrapper.slave_list.len(), 200);
        assert_eq!(wrapper.slave_list[7].0.ALstatuscode, 0);
        wrapper.slave_list[7].0.ALstatuscode = 33;
        assert_eq!(
            unsafe {
                std::slice::from_raw_parts_mut(wrapper.ecx_ctx.slavelist, 200)[7].ALstatuscode
            },
            33
        );
    }
}