herkulex_communicator 0.2.7

Communicate with drs0101 and drs0201 servomotors from command line.
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!()
        // GUI
        .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")))
        // ID
        .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
                    )),
                }),
        )
        // MESSAGE
        .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")
                )
                // SET ID
                .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")])
                )

                // ID
                .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))

                // CONNECTION
                .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();
                            // Since the default baudrate of the servomotors is 115_200 bps we use this one.
                            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
}