use std::convert::{From, TryFrom};
use std::fmt;
use regex::Regex;
lazy_static! {
static ref VERSION_RE: Regex =
Regex::new(r"^EBBv13_and_above EB Firmware Version (\d+)\.(\d+)(?:\.(\d+))?$").unwrap();
static ref MOTOR_STATUS_RE: Regex = Regex::new(r"^QM,(\d+),(\d+),(\d+),(\d+)$").unwrap();
}
type PlotterResponse = Vec<String>;
#[derive(Debug, Eq, PartialEq, Serialize)]
pub enum EBBResponse {
Unit,
Version {
major: u8,
minor: u8,
patch: u8,
},
Steps {
x_pos: i32,
y_pos: i32,
},
MotorStatus {
exec_ing: bool,
x_moving: bool,
y_moving: bool,
motion_queue_empty: bool,
},
PenPosition(PenPosition),
}
impl fmt::Display for EBBResponse {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
EBBResponse::MotorStatus {
exec_ing,
x_moving,
y_moving,
motion_queue_empty,
} => write!(
f,
"Executing: {}; X Moving: {}; Y Moving: {}; Queue Empty: {}",
exec_ing, x_moving, y_moving, motion_queue_empty
),
EBBResponse::PenPosition(pos) => {
write!(f, "Pen is currently {}", pos)
}
EBBResponse::Steps { x_pos, y_pos } => {
write!(f, "Motor has X position {}, Y Position {}", x_pos, y_pos)
}
EBBResponse::Unit => write!(f, "Ok"),
EBBResponse::Version {
major,
minor,
patch,
} => write!(f, "Firmware is at version: {}.{}.{}", major, minor, patch),
}
}
}
type Result = std::result::Result<EBBResponse, String>;
#[derive(Debug, Eq, PartialEq, Serialize)]
pub enum PenPosition {
Up,
Down,
}
impl fmt::Display for PenPosition {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
PenPosition::Up => write!(f, "{}", "Up"),
PenPosition::Down => write!(f, "{}", "Down"),
}
}
}
impl TryFrom<&str> for PenPosition {
type Error = String;
fn try_from(s: &str) -> std::result::Result<PenPosition, String> {
match s {
"0" => Ok(PenPosition::Down),
"1" => Ok(PenPosition::Up),
_ => {
let msg = format!("Invalid pen position: {}", s);
Err(msg)
}
}
}
}
pub enum EBBInstruction {
HomeMove,
MixedGeometryMove {
x: i32,
y: i32,
duration: Option<i32>,
},
QueryMotors,
QueryPen,
QuerySteps,
SetPen(PenPosition),
TogglePen,
Version,
}
impl EBBInstruction {
pub fn as_bytes(&self) -> Vec<u8> {
let cmd_str = match self {
EBBInstruction::HomeMove => home_move(),
EBBInstruction::MixedGeometryMove { x, y, duration } => move_pen(*x, *y, *duration),
EBBInstruction::QueryMotors => query_motors(),
EBBInstruction::QueryPen => query_pen(),
EBBInstruction::QuerySteps => query_steps(),
EBBInstruction::SetPen(set_to) => set_pen(set_to),
EBBInstruction::TogglePen => toggle_pen(),
EBBInstruction::Version => version(),
};
cmd_str.into_bytes()
}
pub fn parse_response(&self, resp: PlotterResponse) -> Result {
match self {
EBBInstruction::HomeMove => parse_ok(resp),
EBBInstruction::MixedGeometryMove { .. } => parse_ok(resp),
EBBInstruction::QueryMotors => parse_query_motor_response(resp),
EBBInstruction::QueryPen => parse_query_pen_response(resp),
EBBInstruction::QuerySteps => parse_query_steps_response(resp),
EBBInstruction::SetPen(_) => parse_ok(resp),
EBBInstruction::TogglePen => parse_ok(resp),
EBBInstruction::Version => parse_version_response(resp),
}
}
pub fn responses(&self) -> usize {
match self {
_ => 1,
}
}
}
fn to_err_string(not_ok: PlotterResponse) -> String {
not_ok.join(",")
}
pub fn parse_ok(maybe_ok: PlotterResponse) -> Result {
if maybe_ok.len() == 1 && maybe_ok[0] == "OK" {
Ok(EBBResponse::Unit)
} else {
Err(to_err_string(maybe_ok))
}
}
fn move_pen(x: i32, y: i32, maybe_duration: Option<i32>) -> String {
let default_duration = 500;
let duration = maybe_duration.unwrap_or(default_duration);
format!("XM,{},{},{}\r", duration, x, y)
}
fn query_motors() -> String {
String::from("QM\r")
}
pub fn version() -> String {
String::from("V\r")
}
#[derive(Debug, Eq, PartialEq)]
pub struct Version {
pub major: u8,
pub minor: u8,
pub patch: u8,
}
fn match_to_u8(m: Option<regex::Match>) -> u8 {
match m {
Some(res) => {
let parsed = res.as_str().parse();
parsed.expect("Failed to parse string")
}
None => 0,
}
}
fn match_to_i32(m: Option<regex::Match>) -> i32 {
match m {
Some(res) => {
let parsed = res.as_str().parse();
parsed.expect("Failed to parse string")
}
None => 0,
}
}
fn match_to_bool(m: Option<regex::Match>) -> bool {
match m {
Some(res) => "0" == res.as_str(),
None => false,
}
}
pub struct MotorStatus {
pub command_executing: bool,
pub x_moving: bool,
pub y_moving: bool,
pub motion_queue_empty: bool,
}
pub fn parse_query_motor_response(resp: PlotterResponse) -> Result {
if resp.len() == 1 {
MOTOR_STATUS_RE.captures(&resp[0]).map_or(
Err(format!("Couldn't parse EBB response: {}", resp.join(","))),
|cap| {
let exec_ing = match_to_bool(cap.get(1));
let x_moving = match_to_bool(cap.get(2));
let y_moving = match_to_bool(cap.get(3));
let motion_queue_empty = match_to_bool(cap.get(4));
Ok(EBBResponse::MotorStatus {
exec_ing,
x_moving,
y_moving,
motion_queue_empty,
})
},
)
} else {
Err(to_err_string(resp))
}
}
fn parse_version_response(resp: PlotterResponse) -> Result {
if resp.len() == 1 {
VERSION_RE.captures(&resp[0]).map_or(
Err(String::from("Could't parse EBB Response")),
|cap| {
let major = match_to_u8(cap.get(1));
let minor = match_to_u8(cap.get(2));
let patch = match_to_u8(cap.get(3));
Ok(EBBResponse::Version {
major,
minor,
patch,
})
},
)
} else {
Err(to_err_string(resp))
}
}
pub fn toggle_pen() -> String {
String::from("TP\r")
}
pub fn query_steps() -> String {
String::from("QS\r")
}
#[derive(Debug, PartialEq, Eq)]
pub struct Steps {
pub x_pos: i32,
pub y_pos: i32,
}
pub fn parse_query_steps_response(resp: PlotterResponse) -> Result {
match resp.len() {
2 => {
let maybe_steps: Vec<&str> = resp[0].split(',').collect();
let x_pos: i32 = maybe_steps[0].parse().expect("Failed to parse");
let y_pos: i32 = maybe_steps[1].parse().expect("Failed to parse");
Ok(EBBResponse::Steps { x_pos, y_pos })
}
_ => Err(to_err_string(resp)),
}
}
pub fn query_pen() -> String {
String::from("QP\r")
}
pub fn parse_query_pen_response(resp: PlotterResponse) -> Result {
match resp.len() {
2 => {
let maybe_pos = &resp[0];
PenPosition::try_from(maybe_pos.as_ref())
.and_then(|position| Ok(EBBResponse::PenPosition(position)))
}
_ => Err(to_err_string(resp)),
}
}
pub fn home_move() -> String {
return String::from("HM,1000\r");
}
pub fn set_pen(set_to: &PenPosition) -> String {
match set_to {
PenPosition::Up => String::from("SP,1\r"),
PenPosition::Down => String::from("SP,0\r"),
}
}
#[cfg(test)]
mod instructions_test {
use super::*;
#[test]
pub fn test_parse_query_steps_response() {
let test_response = vec![String::from("1,2"), String::from("OK")];
let expected = EBBResponse::Steps { x_pos: 1, y_pos: 2 };
let got = parse_query_steps_response(test_response);
assert_eq!(Ok(expected), got);
}
#[test]
pub fn test_version_parse_result() {
let test_str = String::from("EBBv13_and_above EB Firmware Version 2.4.2");
let expected = EBBResponse::Version {
major: 2,
minor: 4,
patch: 2,
};
let result = parse_version_response(vec![test_str]);
assert_eq!(Ok(expected), result);
}
}