mod messages {
use std::collections::HashMap;
use std::convert::From;
use std::fmt;
use crate::commands as cmds;
use crate::plotter::RegisteredPlotters;
type Result = std::result::Result<Box<dyn cmds::Command>, MessageParsingError>;
pub trait Response {
fn respond(&self) -> String;
}
#[derive(Debug)]
pub enum MessageParsingError {
TypeKeyMissing,
APINotImplemented(String),
CommandNotImplemented(String),
}
impl fmt::Display for MessageParsingError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = match self {
Self::APINotImplemented(api) => format!("API named '{}' is not implemented", api),
Self::CommandNotImplemented(cmd) => format!("Command '{}' is not implemented", cmd),
Self::TypeKeyMissing => String::from("Key 'type' was missing from the command map"),
};
write!(f, "Failed to parse message: {}", s)
}
}
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
pub enum API {
#[serde(rename = "config")]
Config,
#[serde(rename = "core")]
Core,
#[serde(alias = "plot")]
Plot,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ApiMessage {
pub api: API,
pub plotter: Option<RegisteredPlotters>,
pub command: HashMap<String, String>,
}
impl ApiMessage {
pub fn render_commands(&self) -> Result {
match self.api {
API::Config => Err(MessageParsingError::APINotImplemented(String::from(
"config",
))),
API::Core => core::parse_command(&self.command),
API::Plot => Err(MessageParsingError::APINotImplemented(String::from("plot"))),
}
}
}
#[derive(Debug, Serialize)]
pub struct ApiResponse {
pub api: API,
pub plotter: Option<RegisteredPlotters>,
pub command: HashMap<String, String>,
pub result: cmds::Result,
}
pub mod config {
use std::convert::TryFrom;
use super::MessageParsingError;
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
pub enum Action {
#[serde(rename = "get_config")]
GetConfig,
#[serde(rename = "set_config")]
SetConfig,
}
impl TryFrom<&str> for Action {
type Error = MessageParsingError;
fn try_from(s: &str) -> std::result::Result<Action, Self::Error> {
match s {
"get_config" => Ok(Action::GetConfig),
"set_conig" => Ok(Action::SetConfig),
_ => Err(MessageParsingError::CommandNotImplemented(s.to_string())),
}
}
}
}
pub mod core {
use std::collections::HashMap;
use std::convert::{From, TryFrom};
use crate::commands as cmds;
use crate::commands::Command;
use super::{MessageParsingError, Result};
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
pub enum Action {
#[serde(rename = "raise_pen")]
PlotterRaisePen,
#[serde(rename = "lower_pen")]
PlotterLowerPen,
#[serde(rename = "toggle_pen")]
PlotterTogglePen,
#[serde(rename = "move_pen")]
PlotterMovePen,
#[serde(rename = "firmware_version")]
QueryFirmwareVersion,
}
impl TryFrom<&str> for Action {
type Error = MessageParsingError;
fn try_from(s: &str) -> std::result::Result<Action, Self::Error> {
match s {
"raise_pen" => Ok(Action::PlotterRaisePen),
"lower_pen" => Ok(Action::PlotterLowerPen),
"toggle_pen" => Ok(Action::PlotterTogglePen),
"move_pen" => Ok(Action::PlotterMovePen),
"firmware_version" => Ok(Action::QueryFirmwareVersion),
_ => Err(MessageParsingError::CommandNotImplemented(s.to_string())),
}
}
}
pub fn parse_command(input: &HashMap<String, String>) -> Result {
input
.get("type")
.map_or(Err(MessageParsingError::TypeKeyMissing), |t| -> Result {
Action::try_from(t.as_ref()).map(|action| -> Box<dyn Command> {
match action {
Action::PlotterLowerPen => Box::new(cmds::TogglePen {}),
Action::PlotterRaisePen => Box::new(cmds::TogglePen {}),
Action::PlotterTogglePen => Box::new(cmds::TogglePen {}),
Action::PlotterMovePen => Box::new(cmds::GoTo::from(input)),
Action::QueryFirmwareVersion => Box::new(cmds::Version {}),
}
})
})
}
}
}
use std::io::{BufRead, BufReader, Write};
use std::os::unix::net::UnixStream;
use serial_core::SerialDevice;
use serial_unix::TTYPort;
use log::{debug, info};
use crate::plotter::{Device, Plotter, ReferenceDevice};
use crate::runtime::Context;
fn load_device(ctx: Context, reference: bool) -> Option<Box<dyn Device>> {
match (ctx.socket, reference) {
(_, true) => Some(Box::new(ReferenceDevice::default())), (Some(socket), false) => {
let mut tty =
TTYPort::open(&socket.as_path()).expect("Failed to open reader on TTY to plotter");
let timeout = std::time::Duration::from_millis(50);
tty.set_timeout(timeout)
.expect("Failed to set timeout on TTY");
let p = Plotter::new(tty);
Some(Box::new(p))
}
(None, false) => None,
}
}
pub fn make_api_handler(mut stream: UnixStream, reference: bool) -> impl FnMut() -> () + Sync {
move || {
let mut buf = Vec::new();
let mut reader = BufReader::new(&stream);
let ctx = Context::new();
let bytes_read = reader.read_until(b'\r', &mut buf);
debug!("Read {} bytes", bytes_read.expect("Read no bytes"));
let msg: messages::ApiMessage =
serde_json::from_slice(&buf).expect("Failed to parse message");
info!("Received message:\n{:#?}", msg);
let cmd = msg.render_commands().expect("Failed to parse commands");
let device = load_device(ctx, reference);
match msg.api {
messages::API::Config => {}
messages::API::Core => {
match device {
Some(dev) => {
info!("Running command...");
let res = cmd.run(dev);
info!("Got result: {:#?}", res);
let response = messages::ApiResponse {
api: msg.api,
command: msg.command,
plotter: msg.plotter,
result: res,
};
serde_json::to_writer(&stream, &response)
.expect("Failed to write response to stream");
stream.flush().expect("Failed to flush intermediate buffer");
}
_ => debug!("No plotter found!"),
}
}
messages::API::Plot => (),
}
}
}
#[cfg(test)]
mod api_test {
fn build_test_json(cmd: &str, body: &str) -> String {
format!(
r#"
{{
"api_version": 1,
"action": "{}",
"params": [{}]
}}
"#,
cmd, body
)
}
}