draw_bridge 0.1.1

A driver for AxiDraw Pen Plotters
Documentation
use std::collections::HashMap;
use std::convert::From;
use std::fmt;

use regex::Regex;

use crate::instructions::{EBBInstruction, EBBResponse};
use crate::plotter::Device;
use crate::runtime::Config;

lazy_static! {
    static ref VERSION_RE: Regex =
        Regex::new(r"^EBBv13_and_above EB Firmware Version (\d+)\.(\d+)(?:\.(\d+))?$").unwrap();
}

#[derive(Debug, PartialEq, Eq)]
pub struct Response {
    msg: String,
}

impl Response {
    pub fn new(msg: String) -> Self {
        Response { msg }
    }

    pub fn noop() -> Self {
        Response {
            msg: String::from("This command was a no-op"),
        }
    }

    pub fn fail(msg: Option<String>) -> Self {
        let default = String::from("This command failed");
        let message = msg.unwrap_or(default);
        Self::new(message)
    }
}

impl fmt::Display for Response {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.msg)
    }
}

#[derive(Debug, Serialize)]
pub enum Error {
    InvalidCommand,
    ParsePlotterResponse(String),
    PlotterExecution(String),
}


pub type Result = std::result::Result<Vec<EBBResponse>, Error>;
// Used internally for result verification
type ValidationResult = std::result::Result<(), Error>;

/// A Command is a higher-order abstraction over the notion of an Instruction.
/// While Instructions represent a concrete directive sent to an EBB and the
/// parsing of the EBBs response, a Command could be made of many Instructions,
/// and is responsible for error checking and reporting along the way.
pub trait Command: fmt::Debug {
    fn run(&self, dev: Box<dyn Device>) -> Result;

    fn validate(&self, _ctx: Config) -> ValidationResult {
        Ok(())
    }
}

pub trait FromParams {
    type Item;

    fn from_params(params: &[String]) -> Vec<Self::Item>;
}

pub trait IntoResponse {
    type CmdResponse;

    fn into_response(self, response: String) -> Self::CmdResponse;
}

// I sincerely cannot remember why I implemented a Noop command :|
// Keeping it around for just a little in case I remember.
// -- RMD 2019-07-21
// #[derive(Debug)]
// pub struct Noop {}

// impl Command for Noop {
//     fn run(&self, _d: Box<&mut dyn Device>) -> Response {
//         Response::new(String::from("NOOP"))
//     }
// }

#[derive(Debug, PartialEq)]
pub struct TogglePen {}

impl Command for TogglePen {
    fn run(&self, mut dev: Box<dyn Device>) -> Result {
        let cmd = EBBInstruction::TogglePen;
        let bytes = cmd.as_bytes();

        match dev.execute(&bytes, cmd.responses()) {
            Err(e) => Err(Error::PlotterExecution(e.to_string())),
            Ok(res) => match cmd.parse_response(res) {
                Ok(resp) => Ok(vec![resp]),
                Err(e) => Err(Error::ParsePlotterResponse(e.to_string())),
            },
        }
    }
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, FromParams, Clone, Copy)]
pub struct GoTo {
    x: i32,
    y: i32,
    duration: Option<i32>,
}

impl From<&HashMap<String, String>> for GoTo {
    fn from(hm: &HashMap<String, String>) -> GoTo {
        let x: i32 = hm.get("x_steps").unwrap().parse().expect("Failed to parse");
        let y: i32 = hm.get("y_steps").unwrap().parse().expect("Failed to parse");

        GoTo {
            x,
            y,
            duration: None,
        }
    }
}

impl fmt::Display for GoTo {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Go to: X{}, Y{}", self.x, self.y)
    }
}

impl Command for GoTo {
    fn run(&self, mut dev: Box<dyn Device>) -> Result {
        let cmd = EBBInstruction::MixedGeometryMove {
            x: self.x,
            y: self.y,
            duration: None,
        };
        let bytes = cmd.as_bytes();

        match dev.execute(&bytes, cmd.responses()) {
            Ok(resp) => match cmd.parse_response(resp) {
                Ok(ebb_resp) => Ok(vec![ebb_resp]),
                Err(e) => Err(Error::ParsePlotterResponse(e)),
            },
            Err(e) => Err(Error::PlotterExecution(e.to_string())),
        }
    }
}

/// `Home` must wait for the motion queue to clear. This means we need to query
/// the motor state until it reports its no longer executing.
#[derive(Debug, FromParams, Serialize, Deserialize)]
pub struct Home {}

// This was very WIP when I realized my data modeling was bad. Comment out for
// now, return once I finish this refactor.
//
// impl Command for Home {
//     fn run(&self, dev: impl Device, socket: &Path) -> Response {
//         let sleep_dur = Duration::from_millis(500);
//         let qm_cmd = EBBInstruction::QueryMotors;
//         let qm_bytes = qm_cmd.as_bytes();

//         while true {
//             let resp = dev
//                 .execute(qm_bytes)
//                 .expect("Failed to query motor status");
//             match qm_cmd.parse_response(resp) {
//                 Some(status) => {
//                     if status.motion_queue_empty && !status.command_executing {
//                         break;
//                     } else {
//                         std::thread::sleep(sleep_dur);
//                     }
//                 }
//                 None => return Response::fail(Some("something is bad".to_owned())),
//             }
//         }

//         let qs_bytes = instructions::query_steps().into_bytes();

//         Response::new("oh no".to_owned())
//     }
// }

#[derive(PartialEq, Debug)]
pub struct Version {}

impl Command for Version {
    fn run(&self, mut dev: Box<dyn Device>) -> Result {
        let cmd = EBBInstruction::Version;
        let bytes = cmd.as_bytes();

        match dev.execute(&bytes, cmd.responses()) {
            Err(e) => Err(Error::PlotterExecution(e.to_string())),
            Ok(res) => match cmd.parse_response(res) {
                Ok(resp) => Ok(vec![resp]),
                Err(e) => Err(Error::ParsePlotterResponse(e.to_string())),
            },
        }
    }
}