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 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 let read_opt = RobotCommand::parse_from(command_parsed_iter);
143 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 inner.handle_event(GamepadEvent::ButtonPressed(Button::East));
215 assert_eq!(inner.submode, "name1");
216 assert_eq!(inner.command_index, 1);
217
218 inner.handle_event(GamepadEvent::ButtonPressed(Button::RightTrigger2));
220 assert!(inner.is_trigger_holding);
221 assert!(!inner.is_sending);
222
223 inner.handle_event(GamepadEvent::ButtonReleased(Button::RightTrigger2));
225 assert!(!inner.is_trigger_holding);
226 assert!(!inner.is_sending);
227
228 inner.handle_event(GamepadEvent::ButtonPressed(Button::West));
230 assert!(inner.is_sending);
231
232 inner.handle_event(GamepadEvent::ButtonReleased(Button::West));
234 assert!(!inner.is_sending);
235
236 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}