openrr_teleop/
robot_command_executor.rs

1use std::{path::PathBuf, sync::Arc};
2
3use arci::{
4    gamepad::{Button, GamepadEvent},
5    Speaker,
6};
7use async_trait::async_trait;
8use clap::Parser;
9use openrr_client::{resolve_relative_path, ArcRobotClient};
10use openrr_command::{load_command_file_and_filter, RobotCommand};
11use parking_lot::Mutex;
12use schemars::JsonSchema;
13use serde::{Deserialize, Serialize};
14use tracing::{error, info, warn};
15
16use crate::ControlMode;
17
18const MODE: &str = "command";
19
20struct RobotCommandExecutorInner {
21    commands: Vec<RobotCommandConfig>,
22    submode: String,
23    command_index: usize,
24    is_trigger_holding: bool,
25    is_sending: bool,
26}
27
28impl RobotCommandExecutorInner {
29    fn new(commands: Vec<RobotCommandConfig>) -> Self {
30        let submode = commands[0].name.clone();
31        Self {
32            commands,
33            submode,
34            command_index: 0,
35            is_trigger_holding: false,
36            is_sending: false,
37        }
38    }
39
40    fn handle_event(&mut self, event: arci::gamepad::GamepadEvent) -> Option<&str> {
41        match event {
42            GamepadEvent::ButtonPressed(Button::East) => {
43                self.command_index = (self.command_index + 1) % self.commands.len();
44                let command = &self.commands[self.command_index];
45                self.submode = command.name.clone();
46                return Some(&self.submode);
47            }
48            GamepadEvent::ButtonPressed(Button::RightTrigger2) => {
49                self.is_trigger_holding = true;
50            }
51            GamepadEvent::ButtonReleased(Button::RightTrigger2) => {
52                self.is_trigger_holding = false;
53                self.is_sending = false;
54            }
55            GamepadEvent::ButtonPressed(Button::West) => {
56                self.is_sending = true;
57            }
58            GamepadEvent::ButtonReleased(Button::West) => {
59                self.is_sending = false;
60            }
61            GamepadEvent::Disconnected => {
62                self.is_trigger_holding = false;
63                self.is_sending = false;
64            }
65            _ => {}
66        }
67        None
68    }
69
70    fn get_command(&self) -> &RobotCommandConfig {
71        &self.commands[self.command_index]
72    }
73}
74
75pub struct RobotCommandExecutor<S>
76where
77    S: Speaker,
78{
79    base_path: PathBuf,
80    robot_client: Arc<ArcRobotClient>,
81    speaker: S,
82    inner: Mutex<RobotCommandExecutorInner>,
83}
84
85impl<S> RobotCommandExecutor<S>
86where
87    S: Speaker,
88{
89    pub fn new(
90        base_path: PathBuf,
91        commands: Vec<RobotCommandConfig>,
92        robot_client: Arc<ArcRobotClient>,
93        speaker: S,
94    ) -> Option<Self> {
95        if commands.is_empty() {
96            None
97        } else {
98            Some(Self {
99                base_path,
100                robot_client,
101                speaker,
102                inner: Mutex::new(RobotCommandExecutorInner::new(commands)),
103            })
104        }
105    }
106}
107
108#[async_trait]
109impl<S> ControlMode for RobotCommandExecutor<S>
110where
111    S: Speaker,
112{
113    fn handle_event(&self, event: arci::gamepad::GamepadEvent) {
114        if let Some(submode) = self.inner.lock().handle_event(event) {
115            // do not wait
116            drop(self.speaker.speak(&format!("{MODE} {submode}")).unwrap());
117        }
118    }
119
120    async fn proc(&self) {
121        let command = {
122            let inner = self.inner.lock();
123            if inner.is_trigger_holding && inner.is_sending {
124                inner.get_command().clone()
125            } else {
126                return;
127            }
128        };
129        let executor = openrr_command::RobotCommandExecutor {};
130        match resolve_relative_path(&self.base_path, command.file_path.clone()) {
131            Ok(path) => {
132                match load_command_file_and_filter(path) {
133                    Ok(commands) => {
134                        let commands_len = commands.len() as f64;
135                        for (i, command) in commands.iter().enumerate() {
136                            if !self.inner.lock().is_trigger_holding {
137                                warn!("Remaining commands are canceled.");
138                                return;
139                            }
140                            let command_parsed_iter = command.split_whitespace();
141                            // Parse the command
142                            let read_opt = RobotCommand::parse_from(command_parsed_iter);
143                            // Execute the parsed command
144                            info!("Executing {command} {i}/{commands_len}");
145
146                            match executor.execute(&self.robot_client, &read_opt).await {
147                                Ok(_) => {
148                                    info!("finished command {command}");
149                                }
150                                Err(e) => {
151                                    error!("failed command {command} {e:?}");
152                                    break;
153                                }
154                            }
155                        }
156                    }
157                    Err(e) => {
158                        error!("failed to load file {e:?}");
159                    }
160                }
161            }
162            Err(e) => {
163                error!("failed to resolve_relative_path {e:?}");
164            }
165        }
166    }
167
168    fn mode(&self) -> &str {
169        MODE
170    }
171
172    fn submode(&self) -> String {
173        self.inner.lock().submode.to_owned()
174    }
175}
176
177#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)]
178#[serde(deny_unknown_fields)]
179pub struct RobotCommandConfig {
180    pub name: String,
181    pub file_path: String,
182}
183
184#[cfg(test)]
185mod test {
186    use std::collections::HashMap;
187
188    use arci::{
189        DummyJointTrajectoryClient, DummyLocalization, DummyMoveBase, DummyNavigation,
190        DummySpeaker, JointTrajectoryClient, Localization, MoveBase, Navigation,
191    };
192    use openrr_client::RobotClient;
193
194    use super::*;
195
196    #[test]
197    fn test_robot_command_executor_inner() {
198        let commands = vec![
199            RobotCommandConfig {
200                name: String::from("name0"),
201                file_path: String::from("path0"),
202            },
203            RobotCommandConfig {
204                name: String::from("name1"),
205                file_path: String::from("path1"),
206            },
207        ];
208        let mut inner = RobotCommandExecutorInner::new(commands);
209
210        assert_eq!(inner.get_command().name, "name0");
211        assert_eq!(inner.get_command().file_path, "path0");
212
213        // Changed submode
214        inner.handle_event(GamepadEvent::ButtonPressed(Button::East));
215        assert_eq!(inner.submode, "name1");
216        assert_eq!(inner.command_index, 1);
217
218        // Case that enable switch is on
219        inner.handle_event(GamepadEvent::ButtonPressed(Button::RightTrigger2));
220        assert!(inner.is_trigger_holding);
221        assert!(!inner.is_sending);
222
223        // Case that enable switch becomes off
224        inner.handle_event(GamepadEvent::ButtonReleased(Button::RightTrigger2));
225        assert!(!inner.is_trigger_holding);
226        assert!(!inner.is_sending);
227
228        // Send command
229        inner.handle_event(GamepadEvent::ButtonPressed(Button::West));
230        assert!(inner.is_sending);
231
232        // Stop send command
233        inner.handle_event(GamepadEvent::ButtonReleased(Button::West));
234        assert!(!inner.is_sending);
235
236        // Disconnected
237        inner.handle_event(GamepadEvent::Disconnected);
238        assert!(!inner.is_trigger_holding);
239        assert!(!inner.is_sending);
240    }
241
242    #[test]
243    fn test_robot_command_executor_new() {
244        let robot_client = Arc::new(
245            RobotClient::new(
246                toml::from_str("urdf_path = \"path\"").unwrap(),
247                {
248                    HashMap::from([(
249                        String::from("arm"),
250                        Arc::new(DummyJointTrajectoryClient::new(vec![String::from(
251                            "test_joint1",
252                        )])) as Arc<dyn JointTrajectoryClient>,
253                    )])
254                },
255                {
256                    HashMap::from([(
257                        String::from("speaker"),
258                        Arc::new(DummySpeaker::new()) as Arc<dyn Speaker>,
259                    )])
260                },
261                Some(Arc::new(DummyLocalization::new()) as Arc<dyn Localization>),
262                Some(Arc::new(DummyMoveBase::new()) as Arc<dyn MoveBase>),
263                Some(Arc::new(DummyNavigation::new()) as Arc<dyn Navigation>),
264            )
265            .unwrap(),
266        );
267
268        let robot_command_executor = RobotCommandExecutor::new(
269            PathBuf::from("path"),
270            vec![RobotCommandConfig {
271                name: String::from("name"),
272                file_path: String::from("file_path"),
273            }],
274            robot_client.clone(),
275            DummySpeaker::new(),
276        )
277        .unwrap();
278
279        assert_eq!(robot_command_executor.base_path, PathBuf::from("path"));
280        assert_eq!(
281            robot_command_executor.inner.lock().commands[0].name,
282            String::from("name")
283        );
284        assert_eq!(
285            robot_command_executor.inner.lock().commands[0].file_path,
286            String::from("file_path")
287        );
288
289        assert!(RobotCommandExecutor::new(
290            PathBuf::from("path"),
291            vec![],
292            robot_client,
293            DummySpeaker::new()
294        )
295        .is_none());
296    }
297}