poulpe_ethercat_py/
lib.rs

1use std::{collections::HashMap, sync::Arc, time::Duration};
2
3use poulpe_ethercat_grpc::client;
4use pyo3::prelude::*;
5use pyo3::wrap_pyfunction;
6use tonic::transport::Uri;
7
8use poulpe_ethercat_grpc::client::{PoulpeIdClient, PoulpeRemoteClient};
9
10use poulpe_ethercat_controller::state_machine;
11
12#[pyclass]
13pub struct PyPoulpeRemoteClient {
14    client: PoulpeRemoteClient,
15}
16
17#[pymethods]
18impl PyPoulpeRemoteClient {
19    #[new]
20    pub fn new(addr: &str, ids: Vec<u16>, update_period: f32) -> Self {
21        let addr_uri = match addr.parse::<Uri>() {
22            Ok(uri) => uri,
23            Err(_) => panic!("Invalid URI format"),
24        };
25        let duration = Duration::from_secs_f32(update_period);
26
27        let client = match PoulpeRemoteClient::connect(addr_uri, ids, duration) {
28            Ok(client) => client,
29            Err(e) => panic!("Failed to connect to the server: {}", e),
30        };
31
32        PyPoulpeRemoteClient { client }
33    }
34
35    /// Get the mode of operation
36    ///
37    /// # Args:
38    /// * slave_id (int): The slave id
39    /// # Returns:
40    /// * int: The mode of operation  - 1: Profile Position Mode, 3: Profile Velocity Mode, 4: Profile Torque Mode
41    pub fn get_mode_of_operation(&mut self, slave_id: u16) -> u32 {
42        match self.client.get_mode_of_operation(slave_id) {
43            Ok(mode) => mode,
44            _ => panic!("Error in getting mode of operation"),
45        }
46    }
47
48    /// Set the mode of operation
49    ///
50    /// # Args:
51    /// * slave_id (int): The slave id
52    /// * mode (int): The mode of operation  - 1: Profile Position Mode, 3: Profile Velocity Mode, 4: Profile Torque Mode
53    pub fn set_mode_of_operation(&mut self, slave_id: u16, mode: u32) {
54        self.client.set_mode_of_operation(slave_id, mode);
55    }
56
57    /// Print the mode of operation
58    ///
59    /// # Args:
60    /// * slave_id (int): The slave id
61    ///
62    /// Outputs the mode of operation
63    pub fn print_mode_of_operation(&mut self, slave_id: u16) {
64        let mode = match self.client.get_mode_of_operation(slave_id) {
65            Ok(mode) => mode,
66            _ => panic!("Error in getting mode of operation"),
67        };
68        let mop = state_machine::CiA402ModeOfOperation::from_u8(mode as u8).unwrap();
69        println!("Mode of operation: {:?}", mop);
70    }
71
72    /// Enable the actuators
73    ///
74    /// # Args:
75    /// * slave_id (int): The slave id
76    pub fn turn_on(&mut self, slave_id: u16) {
77        self.client.turn_on(slave_id);
78    }
79
80    /// Disable the actuators
81    ///
82    /// # Args:
83    /// * slave_id (int): The slave id
84    pub fn turn_off(&mut self, slave_id: u16) {
85        self.client.turn_off(slave_id);
86    }
87
88    /// Set the target position
89    ///
90    /// # Args:
91    /// * slave_id (int): The slave id
92    /// * position (list): The target position
93    pub fn set_target_position(&mut self, slave_id: u16, position: Vec<f32>) {
94        self.client.set_target_position(slave_id, position);
95    }
96
97    /// Set the velocity limit
98    ///
99    /// # Args:
100    /// * slave_id (int): The slave id
101    /// * velocity_limit (list): Relative velocity limit from 0 to 1
102    pub fn set_velocity_limit(&mut self, slave_id: u16, velocity: Vec<f32>) {
103        self.client.set_velocity_limit(slave_id, velocity);
104    }
105
106    /// Set the torque limit
107    ///
108    /// # Args:
109    /// * slave_id (int): The slave id
110    /// * torque_limit (list): Relative torque limit from 0 to 1
111    pub fn set_torque_limit(&mut self, slave_id: u16, torque: Vec<f32>) {
112        self.client.set_torque_limit(slave_id, torque);
113    }
114
115    /// Get the actual position
116    ///
117    /// # Args:
118    /// * slave_id (int): The slave id
119    /// # Returns:
120    /// * list: The actual position
121    pub fn get_position_actual_value(&mut self, slave_id: u16) -> Vec<f32> {
122        match self.client.get_position_actual_value(slave_id) {
123            Ok(position) => position,
124            _ => panic!("Error in getting position actual value"),
125        }
126    }
127
128    /// Get the target position
129    ///
130    /// # Args:
131    /// * slave_id (int): The slave id
132    /// # Returns:
133    /// * list: The target position
134    pub fn get_target_position(&mut self, slave_id: u16) -> Vec<f32> {
135        match self.client.get_target_position(slave_id) {
136            Ok(position) => position,
137            _ => panic!("Error in getting target position"),
138        }
139    }
140
141    /// Get the target velocity
142    ///
143    /// # Args:
144    /// * slave_id (int): The slave id
145    /// # Returns:
146    /// * list: The target velocity
147    pub fn set_target_velocity(&mut self, slave_id: u16, velocity: Vec<f32>) {
148        self.client.set_target_velocity(slave_id, velocity);
149    }
150
151    /// Set the target torque
152    ///
153    /// # Args:
154    /// * slave_id (int): The slave id
155    /// * torque (list): The target torque
156    pub fn set_target_torque(&mut self, slave_id: u16, torque: Vec<f32>) {
157        self.client.set_target_torque(slave_id, torque);
158    }
159
160    /// Get the actual velocity
161    ///
162    /// # Args:
163    /// * slave_id (int): The slave id
164    /// # Returns:
165    /// * list: The actual velocity
166    pub fn get_velocity_actual_value(&mut self, slave_id: u16) -> Vec<f32> {
167        match self.client.get_velocity_actual_value(slave_id) {
168            Ok(velocity) => velocity,
169            _ => panic!("Error in getting velocity actual value"),
170        }
171    }
172
173    /// Get the torque velocity
174    ///
175    /// # Args:
176    /// * slave_id (int): The slave id
177    /// # Returns:
178    /// * list: The actual torque
179    pub fn get_torque_actual_value(&mut self, slave_id: u16) -> Vec<f32> {
180        match self.client.get_torque_actual_value(slave_id) {
181            Ok(torque) => torque,
182            _ => panic!("Error in getting torque actual value"),
183        }
184    }
185
186    /// Get the current axis sensor values
187    ///
188    /// # Args:
189    /// * slave_id (int): The slave id
190    /// # Returns:
191    /// * list: The current axis sensor values
192    pub fn get_axis_sensors(&mut self, slave_id: u16) -> Vec<f32> {
193        match self.client.get_axis_sensors(slave_id) {
194            Ok(sensors) => sensors,
195            _ => panic!("Error in getting axis sensors"),
196        }
197    }
198
199    /// Get the axis sensor zeros in firmware
200    ///
201    /// # Args:
202    /// * slave_id (int): The slave id
203    /// # Returns:
204    /// * list: The axis sensor zero values
205    pub fn get_axis_sensor_zeros(&mut self, slave_id: u16) -> Vec<f32> {
206        match self.client.get_axis_sensor_zeros(slave_id) {
207            Ok(zeros) => zeros,
208            _ => panic!("Error in getting axis sensor zeros"),
209        }
210    }
211
212    /// Check if motors are activated
213    ///
214    /// # Args:
215    /// * slave_id (int): The slave id
216    /// # Returns:
217    /// * bool: True if the motor is activated, False otherwise
218    pub fn get_torque_state(&mut self, slave_id: u16) -> bool {
219        match self.client.get_torque_state(slave_id) {
220            Ok(state) => state,
221            _ => panic!("Error in getting torque state"),
222        }
223    }
224
225    /// Get the state
226    ///
227    /// # Args:
228    /// * slave_id (int): The slave id    
229    /// # Returns:
230    /// * int: The state  (CiA402 state machine)
231    pub fn get_state(&mut self, slave_id: u16) -> u32 {
232        match self.client.get_state(slave_id) {
233            Ok(state) => state,
234            _ => panic!("Error in getting state"),
235        }
236    }
237
238    /// Print the state
239    /// Outputs the CiA402 state machine state
240    ///
241    /// # Args:
242    /// * slave_id (int): The slave id
243    pub fn print_state(&mut self, slave_id: u16) {
244        let state = match self.client.get_cia402_state(slave_id) {
245            Ok(state) => state,
246            _ => panic!("Error in getting state"),
247        };
248
249        let cia_state = state_machine::parse_state_from_status_word(state as u16);
250        println!("State: {:?}", cia_state);
251    }
252
253    /// Get the error codes
254    ///
255    /// # Args:
256    /// * slave_id (int): The slave id
257    /// # Returns:
258    /// * list: The error codes  (see poule_ethercat_controller/src/state_machine.rs)
259    pub fn get_error_codes(&mut self, slave_id: u16) -> Vec<i32> {
260        match self.client.get_error_codes(slave_id) {
261            Ok(codes) => codes,
262            _ => panic!("Error in getting error codes"),
263        }
264    }
265
266    /// Print the error codes  
267    ///
268    /// # Args:
269    /// * slave_id (int): The slave id
270    pub fn print_error_codes(&mut self, slave_id: u16) {
271        let error_codes = match self.client.get_error_codes(slave_id) {
272            Ok(codes) => codes,
273            _ => panic!("Error in getting error codes"),
274        };
275
276        // homing error flags
277        let homing_error =
278            state_machine::parse_homing_error_flags((error_codes[0] as u16).to_le_bytes());
279        if homing_error.len() > 0 {
280            println!("Homing | error flags: {:?}", homing_error);
281        } else {
282            println!("Homing | OK!");
283        }
284        // motor error flags
285        for (i, code) in error_codes[1..].iter().enumerate() {
286            let m_code = state_machine::parse_motor_error_flags((*code as u16).to_le_bytes());
287            if m_code.len() > 0 {
288                println!("Motor {} | Error flags: {:?}", i, m_code);
289            } else {
290                println!("Motor {} | OK!", i);
291            }
292        }
293    }
294
295    /// Get the connected devices
296    ///
297    /// # Returns:
298    /// * list(tuple): The connected devices (slave ids, device names)
299    pub fn get_connected_devices(&mut self) -> (Vec<u16>, Vec<String>) {
300        (self.client.ids.clone(), self.client.names.clone())
301    }
302
303    pub fn get_all_slaves_in_network(&mut self) -> (Vec<u16>, Vec<String>) {
304        match self.client.get_poulpe_ids_sync() {
305            Ok(slaves) => slaves,
306            _ => panic!("Error in getting connected devices"),
307        }
308    }
309
310    /// Get the motor temperatures
311    ///
312    /// # Args:
313    /// * slave_id (int): The slave id
314    /// # Returns:
315    /// * list: The motor temperatures
316    pub fn get_motor_temperatures(&mut self, slave_id: u16) -> Vec<f32> {
317        match self.client.get_motor_temperatures(slave_id) {
318            Ok(temps) => temps,
319            _ => panic!("Error in getting temperatures"),
320        }
321    }
322    /// Get the board temperatures
323    ///
324    /// ## Args:
325    /// * slave_id (int): The slave id
326    /// ## Returns:
327    /// * list: The board temperatures
328    pub fn get_board_temperatures(&mut self, slave_id: u16) -> Vec<f32> {
329        match self.client.get_board_temperatures(slave_id) {
330            Ok(temps) => temps,
331            _ => panic!("Error in getting temperatures"),
332        }
333    }
334
335    /// Do an emergency stop
336    ///
337    /// ## Args:
338    /// * slave_id (int): The slave id
339    pub fn emergency_stop(&mut self, slave_id: u16) {
340        self.client.emergency_stop(slave_id);
341    }
342
343    // Define other methods similarly...
344}
345
346#[pyclass]
347pub struct PyEthercatServer {
348    #[pyo3(get, set)]
349    pub addr: String,
350}
351
352#[pymethods]
353impl PyEthercatServer {
354    /// Create a new instance of the server
355    #[new]
356    pub fn new() -> Self {
357        PyEthercatServer {
358            addr: "http://127.0.0.1:50098".to_string(),
359        }
360    }
361
362    /// Launch the server
363    ///
364    /// ## Args:
365    /// * file_name (str): The path to the configuration file (default: ../config/ethercat.yaml
366    /// ## Returns:
367    /// * str: The URL address of the server
368    #[pyo3(signature = (file_name=None))]
369    pub fn launch_server(&mut self, file_name: Option<&str>) {
370        let filename = match file_name {
371            Some(name) => name.to_string(),
372            None => "../config/ethercat.yaml".to_string(),
373        };
374
375        std::thread::spawn(move || {
376            let rt = tokio::runtime::Runtime::new().unwrap();
377            rt.block_on(async {
378                if let Err(e) = poulpe_ethercat_grpc::server::launch_server(&filename).await {
379                    eprintln!("Failed to launch the server: {}", e);
380                }
381            });
382        });
383    }
384    /// Get all slaves connected to the master
385    ///
386    /// ## Returns:
387    /// * tuple: The slave ids and device names
388    pub fn get_all_slaves_in_network(&mut self) -> (Vec<u16>, Vec<String>) {
389        let addr_uri = match self.addr.parse::<Uri>() {
390            Ok(uri) => uri,
391            Err(_) => panic!("Invalid URI format"),
392        };
393
394        match PoulpeIdClient::new(addr_uri).get_slaves() {
395            Ok(slaves) => slaves,
396            _ => panic!("Error in getting connected devices"),
397        }
398    }
399}
400
401#[pymodule]
402fn poulpe_ethercat_py(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
403    // add client methods
404    m.add_class::<PyPoulpeRemoteClient>()?;
405    // add server methods
406    m.add_class::<PyEthercatServer>()?;
407    Ok(())
408}