openrr_apps_robot_command/
robot_command.rs1use std::path::PathBuf;
2
3use anyhow::Result;
4use clap::{CommandFactory, Parser};
5use clap_complete::Shell;
6use openrr_apps::{
7 Error, RobotConfig,
8 utils::{LogConfig, init_tracing, init_tracing_with_file_appender},
9};
10use openrr_client::BoxRobotClient;
11use openrr_command::{RobotCommand, RobotCommandExecutor};
12use tracing::info;
13
14#[derive(Parser, Debug)]
16#[clap(name = env!("CARGO_BIN_NAME"))]
17struct RobotCommandArgs {
18 #[clap(short, long, value_parser)]
20 config_path: Option<PathBuf>,
21 #[clap(long)]
24 config: Option<String>,
25 #[clap(subcommand)]
26 command: Option<RobotCommand>,
27 #[clap(long)]
29 show_default_config: bool,
30 #[clap(short, long)]
32 interactive: bool,
33 #[clap(long, value_parser)]
35 log_directory: Option<PathBuf>,
36}
37
38fn shell_completion(shell_type: openrr_command::ShellType) {
39 clap_complete::generate(
40 Shell::from(shell_type),
41 &mut RobotCommandArgs::command(),
42 env!("CARGO_BIN_NAME"),
43 &mut std::io::stdout(),
44 );
45}
46
47#[tokio::main]
48async fn main() -> Result<()> {
49 let args = RobotCommandArgs::parse();
50 if args.log_directory.is_none() {
51 init_tracing();
52 }
53 #[cfg(not(feature = "ros"))]
54 let _guard = args.log_directory.as_ref().map(|log_directory| {
55 init_tracing_with_file_appender(
56 LogConfig {
57 directory: log_directory.to_path_buf(),
58 ..Default::default()
59 },
60 env!("CARGO_BIN_NAME").to_string(),
61 )
62 });
63
64 info!("ParsedArgs {args:?}");
65
66 if args.show_default_config {
67 print!("{}", toml::to_string(&RobotConfig::default()).unwrap());
68 return Ok(());
69 }
70
71 if let Some(RobotCommand::ShellCompletion(shell_type)) = args.command.as_ref() {
73 shell_completion(*shell_type);
74 return Ok(());
75 }
76
77 let config_path = openrr_apps::utils::get_apps_robot_config(args.config_path);
78
79 let robot_config =
80 openrr_apps::utils::resolve_robot_config(config_path.as_deref(), args.config.as_deref())?;
81
82 openrr_apps::utils::init_with_anonymize(env!("CARGO_BIN_NAME"), &robot_config);
83 #[cfg(feature = "ros")]
84 let _guard = args.log_directory.map(|log_directory| {
85 init_tracing_with_file_appender(
86 LogConfig {
87 directory: log_directory,
88 ..Default::default()
89 },
90 if robot_config.has_ros_clients() {
91 arci_ros::name()
92 } else {
93 env!("CARGO_BIN_NAME").to_string()
94 },
95 )
96 });
97 let client: BoxRobotClient = robot_config.create_robot_client()?;
98 let executor = RobotCommandExecutor {};
99
100 if args.interactive {
101 Ok(executor.run_interactive_shell(&client).await?)
102 } else {
103 let command = args.command.ok_or(Error::NoCommand)?;
104 Ok(executor.execute(&client, &command).await?)
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use std::{env, path::Path};
111
112 use openrr_command::load_command_file_and_filter;
113 use tracing::log::error;
114
115 use super::*;
116
117 #[test]
118 fn parse_args() {
119 let bin = env!("CARGO_BIN_NAME");
120 assert!(RobotCommandArgs::try_parse_from([bin]).is_ok());
121 assert!(RobotCommandArgs::try_parse_from([bin, "--show-default-config"]).is_ok());
122 assert!(RobotCommandArgs::try_parse_from([bin, "--config-path", "path", "list"]).is_ok());
123 assert!(
124 RobotCommandArgs::try_parse_from([
125 bin,
126 "--show-default-config",
127 "--config-path",
128 "path"
129 ])
130 .is_ok()
131 );
132 }
133
134 #[test]
135 fn assert_app() {
136 RobotCommandArgs::command().debug_assert();
137 }
138
139 const COMMAND_PATHS: [&str; 11] = [
140 "command/sample_cmd_urdf_viz.txt",
141 "command/pr2_cmd_collision.txt",
142 "command/pr2_cmd_ik.txt",
143 "command/pr2_cmd_joints.txt",
144 "command/pr2_cmd_ros.txt",
145 "command/pr2_cmd_urdf_viz.txt",
146 "command/ur10_cmd_collision.txt",
147 "command/ur10_cmd_ik.txt",
148 "command/ur10_cmd_joints.txt",
149 "command/ur10_cmd_ros.txt",
150 "command/ur10_cmd_urdf_viz.txt",
151 ];
152
153 #[test]
154 fn assert_commands() {
155 for command_path in COMMAND_PATHS {
156 let bin = env!("CARGO_BIN_NAME");
157 let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
158
159 let config_path = manifest_dir.join("config/sample_robot_client_for_urdf_viz.toml");
160 let config_path_arg = "--config-path=".to_string() + config_path.to_str().unwrap();
161 let sample_command_path = manifest_dir.join(command_path);
162
163 let sample_command = RobotCommandArgs::try_parse_from([
164 bin,
165 config_path_arg.as_str(),
166 "load_commands",
167 sample_command_path.to_str().unwrap(),
168 ])
169 .unwrap();
170 let mut results = Vec::new();
171
172 let command = sample_command.command.ok_or(Error::NoCommand).unwrap();
173
174 check_command(&command, &mut results, command_path);
175
176 for result in results {
177 assert!(result);
178 }
179 }
180 }
181
182 fn check_command(command: &RobotCommand, log: &mut Vec<bool>, command_path: &str) {
183 match &command {
184 RobotCommand::SendJoints {
185 name: _,
186 duration,
187 use_interpolation: _,
188 joint: _,
189 max_resolution_for_interpolation: _,
190 min_number_of_points_for_interpolation: _,
191 } => {
192 log.push(*duration >= 0f64);
193 }
194 RobotCommand::SendJointsPose {
195 name: _,
196 pose_name: _,
197 duration,
198 } => {
199 log.push(*duration >= 0f64);
200 }
201 RobotCommand::MoveIk {
202 name: _,
203 x: _,
204 y: _,
205 z: _,
206 yaw: _,
207 pitch: _,
208 roll: _,
209 duration,
210 use_interpolation: _,
211 is_local: _,
212 max_resolution_for_interpolation: _,
213 min_number_of_points_for_interpolation: _,
214 } => {
215 log.push(*duration >= 0f64);
216 }
217 RobotCommand::GetState { name: _ } => log.push(true),
218 RobotCommand::LoadCommands { command_file_path } => {
219 let target_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
220 let target_command_file_path = target_dir.join(command_path);
221 log.push(*command_file_path == target_command_file_path);
222
223 for command in load_command_file_and_filter(target_command_file_path).unwrap() {
224 let command_parsed_iter = command.split_whitespace();
225 let read_opt = RobotCommand::try_parse_from(command_parsed_iter);
226 match read_opt {
227 Ok(robot_command) => {
228 check_command(&robot_command, log, command_path);
229 }
230 Err(err) => {
231 error!("Error in {command_file_path:?}: {err}");
232 log.push(false);
233 }
234 }
235 }
236 }
237 RobotCommand::List => {
238 log.push(true);
239 }
240 RobotCommand::Speak {
241 name: _,
242 message: _,
243 } => {
244 log.push(true);
245 }
246 RobotCommand::ExecuteCommand { command: _ } => {
247 log.push(true);
248 }
249 RobotCommand::GetNavigationCurrentPose => {
250 log.push(true);
251 }
252 RobotCommand::SendNavigationGoal {
253 x: _,
254 y: _,
255 yaw: _,
256 frame_id: _,
257 timeout_secs: _,
258 } => {
259 log.push(true);
260 }
261 RobotCommand::CancelNavigationGoal => {
262 log.push(true);
263 }
264 RobotCommand::SendBaseVelocity {
265 x: _,
266 y: _,
267 theta: _,
268 duration_secs,
269 } => {
270 log.push(*duration_secs >= 0f64);
271 }
272 _ => {
273 log.push(false);
274 }
275 }
276 }
277}