use clap::{App, AppSettings, Arg, SubCommand};
use serialport::{open_with_settings, SerialPortSettings};
arg_enum!{
#[derive(Debug, PartialEq)]
pub enum Message {
Reboot,
SetPosition,
SetSpeed,
EnableTorque,
DisableTorque,
SetId,
ClearErrors,
ForceSetId,
GetPosition,
GetId,
GetTorque
}
}
pub fn parse_args<'a, 'b>() -> App<'a, 'b> {
let app = app_from_crate!()
.subcommand(SubCommand::with_name("gui").about("Start the application in graphical mode."))
.subcommand(SubCommand::with_name("completions")
.about("Generates completion scripts for your shell")
.arg(Arg::with_name("SHELL")
.required(true)
.possible_values(&["bash", "fish", "zsh"])
.help("The shell to generate the script for")))
.arg(
Arg::with_name("id")
.short("i")
.long("id")
.takes_value(true)
.help("The ID of the servomotor you are communicating with.")
.long_help(
"This must be a valid integer between 0 and 255. You have to write it \
in a decimal form.",
)
.use_delimiter(false)
.validator(|s| match s.parse::<u8>() {
Ok(_) => Ok(()),
Err(e) => Err(format!(
"The provided ID is not a valid integer between 0 and 254 : {}.",
e
)),
}),
)
.subcommand(
SubCommand::with_name("message")
.about("Send the message <command> through the serial connection <connection> to the servomotors of id <id>.")
.arg(Arg::with_name("command")
.possible_values(&Message::variants())
.case_insensitive(true)
.takes_value(false)
.use_delimiter(false)
.empty_values(true)
.help("The command that will be sent to the servomotor. Case insensitive.")
.long_help(
"The command can be one of :\n
- <setspeed> : set the servomotor into continuous rotation mode, with a speed between 0 and 1023. \n
- <setposition> : ask the servomotor to go to the absolute position, with a value between 0 and 1023. \n
- <reboot> : Send a reboot message to the servomotor. This will force the servomotor to reload EEP register into RAM taking into account ID changes (which are written to EEP). \n
- <enabletorque> : Ask the servomotor to enable torque. If you don't enable the torque, position commands and speed commands won't work. \n
- <disabletorque> : Ask the servomotor to disable torque. If the servomotor is running (either a position command or a speed command) it will stop it's movement. \n
- <setid> : Write the new ID of the servomotor inside it's permanent memory (EEP). You need to reboot the servomotor to force it to take the change into account. \n\
- <forcesetid> : Force the servomotor to change it's ID by sending a message to all the ID (so 253 messages). \n
- <getposition> : Request the servomotor to send it's absolute position. \n
- <gettorque> : Request the servomotor to send it's raw PWM torque. \n
- <getid> : Request the servomotor to send back it's ID. \n
")
.required(true)
)
.arg(Arg::with_name("desired_speed")
.takes_value(true)
.short("s")
.long("speed")
.help("The desired speed for the servomotor.")
.long_help("The desired speed for the servomotor. The speed must be in the range 0~1023.")
.use_delimiter(false)
.validator(|s| match s.parse::<u16>() {
Ok(v) if v < 1024 => Ok(()),
Err(e) => Err(format!("Could not parse speed : {}.",e)),
Ok(_) => Err(format!("Invalid speed. It should be in the range 0~1023."))
})
.empty_values(false)
.required_if("command","setspeed")
)
.arg(Arg::with_name("desired_position")
.long("position")
.short("p")
.takes_value(true)
.help("The desired absolute position for the servomotor.")
.long_help("The desired absolute position for the servomotor.\
The position must be in the range 0~1023. \
Please note that if the position is outside of the range 21~1002 the servomotor will be in error")
.use_delimiter(false)
.validator(|s| match s.parse::<u16>() {
Ok(v) if v < 1024 => Ok(()),
Err(e) => Err(format!("Could not parse position: {}.",e)),
Ok(_) => Err(format!("Invalid position. It should be in the range 0~1023."))
})
.empty_values(false)
.required_if("command","setposition")
)
.arg(Arg::with_name("set_id")
.short("d")
.long("setid")
.takes_value(true)
.help("Change the ID of the servomotor.")
.long_help("Write the new ID of the servomotor inside it's permanent memory (EEP). \
You need to reboot the servomotor to force it to take the change into account.")
.use_delimiter(false)
.validator(|s| match s.parse::<u8>() {
Ok(v) if v < 254 => Ok(()),
Err(e) => Err(format!("Could not parse ID : {}.",e)),
Ok(_) => Err(format!("Invalid ID. It should be in the range 0~253."))
})
.empty_values(false)
.required_ifs(&[("command", "setid"), ("command", "forcesetid")])
)
.arg(
Arg::with_name("id")
.short("i")
.long("id")
.takes_value(true)
.help("The ID of the servomotor you are communicating with.")
.long_help(
"This must be a valid integer between 0 and 255. You have to write it \
in a decimal form.",
)
.use_delimiter(false)
.validator(|s| match s.parse::<u8>() {
Ok(v) if v <= 254 => Ok(()),
Err(e) => Err(format!(
"The provided ID is not a valid integer between 0 and 254: {}",
e
)),
Ok(_) => Err(format!("The provided ID is invalid. It should in the range 0~254."))
})
.required(true))
.arg(
Arg::with_name("connection")
.short("o")
.long("output")
.takes_value(true)
.help(
"The serial connection to use when communicating with the servomotors.",
)
.long_help("Points to a file that represent a valid serial connection to the servomotor. \
Usually in the form /dev/ttyUSB0 or /dev/ttyACM0. \
The number may vary depending on the number of devices connected to your computer.")
.use_delimiter(false)
.validator(|s| {
let mut settings = SerialPortSettings::default();
settings.baud_rate = 115_200;
let connection = open_with_settings(&s, &settings);
match connection {
Ok(_) => Ok(()),
Err(e) => Err(format!("Failed to initialize connection: {}.", e)),
}
})
.required(true)
.takes_value(true))
).setting(AppSettings::ArgRequiredElseHelp);
app
}