draw_bridge 0.1.1

A driver for AxiDraw Pen Plotters
Documentation
use std::io::{BufRead, BufReader, Error, ErrorKind, Read, Write};

type Result = std::io::Result<Vec<String>>;

#[derive(Debug, Deserialize, Serialize, Copy, Clone)]
pub enum RegisteredPlotters {
    AxiDrawV3,
    AxiDrawV3A3,
}

/// The `Device` trait provides us with the entry-point of interacting with some
/// kind of e.g. Plotter. We abstract out this behavior primarily because:
///
///   1. We will pass our Device as an argument into a given `Command`.
///   2. `Command` is represented as a trait object, and must therefore be
///      object safe.
///   3. We want the actual ReadWriter of the Device to be generic to enable testing.
pub trait Device {
    fn execute(&mut self, cmd: &[u8], responses: usize) -> Result;
}

#[derive(Debug)]
pub struct Plotter<P>
where
    P: Read + Write,
{
    serial: P,
}

impl<P> Plotter<P>
where
    P: Read + Write,
{
    pub fn new(serial: P) -> Self {
        Plotter { serial }
    }

    fn read_response(&mut self, responses: usize) -> Result {
        let mut result = Vec::new();
        let mut rdr = BufReader::new(&mut self.serial);

        let mut idx = 0;

        while idx < responses {
            let mut buf = String::new();

            let _ = rdr.read_line(&mut buf);
            let cleaned = buf.trim().to_owned();
            result.push(cleaned);

            idx += 1;
        }

        Ok(result)
    }
}

impl<P> Device for Plotter<P>
where
    P: Read + Write,
{
    /// `execute` runs a command provided as a byte vec against the plotter and
    /// returns the result as a Vec<String>
    fn execute(&mut self, cmd: &[u8], responses: usize) -> Result {
        match self.serial.write(&cmd) {
            Ok(bytes_written) => {
                if bytes_written != cmd.len() {
                    let e = Error::from(ErrorKind::Other);
                    return Err(e);
                }
            }
            Err(e) => return Err(e),
        };

        self.serial
            .flush()
            .expect("Failed to flush bytes to socket");

        self.read_response(responses)
    }
}

#[cfg(test)]
mod plotter_tests {

    use super::*;

    use std::io::Cursor;

    // A Mock Socket; exists to implement Read + Write to two different buffers.
    struct Mocket {
        read_buf: Vec<u8>,
        write_buf: Vec<u8>,
    }

    impl Read for Mocket {
        fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
            buf.copy_from_slice(&self.read_buf);
            return Ok(self.read_buf.len());
        }
    }

    impl Write for Mocket {
        fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
            self.write_buf.copy_from_slice(&buf);
            return Ok(self.write_buf.len());
        }

        fn flush(&mut self) -> std::io::Result<()> {
            Ok(())
        }
    }

    impl Mocket {
        fn new() -> Self {
            Mocket {
                read_buf: Vec::new(),
                write_buf: Vec::new(),
            }
        }

        fn new_with_read_write(read_buf: Vec<u8>, write_buf: Vec<u8>) -> Self {
            Mocket {
                read_buf,
                write_buf,
            }
        }

        fn new_with_read(read_buf: Vec<u8>) -> Self {
            Self::new_with_read_write(read_buf, Vec::new())
        }

        fn new_with_write(write_buf: Vec<u8>) -> Self {
            Self::new_with_read_write(Vec::new(), write_buf)
        }
    }

    fn make_test_plotter(curs: Cursor<&mut Vec<u8>>) -> Plotter<Cursor<&mut Vec<u8>>> {
        Plotter::new(curs)
    }

    #[test]
    fn test_correct_number_of_responses() {
        let first = "first";
        let second = "second";
        let third = "third";

        let first_message = format!("{}\n\r", first);
        let second_message = format!("{}\n\r{}\n\r{}\n\r", first, second, third);

        let first_responses = 1;
        let second_responses = 3;

        let mut first_data = first_message.into_bytes();
        let curs = Cursor::new(&mut first_data);

        let mut plotter = make_test_plotter(curs);

        let first_expected = vec![first.to_owned()];

        match plotter.read_response(first_responses) {
            Ok(got) => {
                assert_eq!(first_responses, got.len());
                assert_eq!(first_expected, got);
            }
            Err(e) => panic!("Expected OK, got: {}", e),
        }

        let mut second_data = second_message.into_bytes();
        let curs = Cursor::new(&mut second_data);

        let mut plotter = make_test_plotter(curs);

        let second_expected = vec![first.to_owned(), second.to_owned(), third.to_owned()];

        match plotter.read_response(second_responses) {
            Ok(got) => {
                assert_eq!(second_responses, got.len());
                assert_eq!(second_expected, got);
            }
            Err(e) => panic!("Expected OK, got: {}", e),
        }
    }

    // Bless it's weird heart, the EBB will sometimes delimit \n\r, but other
    // times it's \r\n. Cool.
    #[test]
    fn test_delimiting_and_cleaning() {
        let text_one = "hi";
        let text_two = "lois";
        let responses = 2;

        let mut data = format!("{}\r\n\r{}\n\r", text_one, text_two).into_bytes();
        let curs = Cursor::new(&mut data);

        let mut plotter = make_test_plotter(curs);

        let expected = vec![text_one.to_owned(), text_two.to_owned()];

        match plotter.read_response(responses) {
            Ok(got) => {
                assert_eq!(responses, got.len());
                assert_eq!(expected, got);
            }
            Err(e) => panic!("Expected OK, got: {}", e),
        }
    }
}

#[derive(Debug, Default)]
pub struct ReferenceDevice {
    motor1: i32,
    motor2: i32,
    pen: i8,
}

impl ReferenceDevice {
    fn increment_non_negative(&self, m: i32, v: i32) -> i32 {
        // If v is negative.
        if (m + v) <= 0 {
            0
        } else {
            m + v
        }
    }

    fn increment_motors(&mut self, im1: i32, im2: i32) {
        self.motor1 = self.increment_non_negative(self.motor1, im1);
        self.motor2 = self.increment_non_negative(self.motor2, im2);
    }

    fn toggle_pen(&mut self) {
        if self.pen == 1 {
            self.pen = 0;
        } else {
            self.pen = 1;
        }
    }

    fn respond(&mut self, cmd: &str) -> Result {
        let parts_vec: Vec<&str> = cmd.trim().split(",").collect();
        let ok = String::from("OK");

        match &parts_vec[..] {
            ["V"] => Ok(vec![String::from(
                "EBBv13_and_above EB Firmware Version 2.4.2",
            )]),
            ["XM", _duration, motor1, motor2] => match (motor1.parse(), motor2.parse()) {
                (Ok(m1), Ok(m2)) => {
                    println!("incrementing by {} and {}", m1, m2);
                    self.increment_motors(m1, m2);
                    Ok(vec![ok])
                }
                _ => Err(Error::from(ErrorKind::InvalidInput)),
            },
            ["QM"] => {
                let resp_str = format!("QM,1,{},{},1", self.motor1, self.motor2);
                let resp = vec![resp_str];
                Ok(resp)
            }
            ["TP"] => {
                self.toggle_pen();
                Ok(vec![ok])
            }
            _ => Err(Error::from(ErrorKind::InvalidData)),
        }
    }
}

impl Device for ReferenceDevice {
    fn execute(&mut self, cmd: &[u8], _responses: usize) -> Result {
        let cmd_str =
            String::from_utf8(cmd.to_vec()).expect("Passed unparseable garbage! Exploding");
        self.respond(&cmd_str)
    }
}

#[cfg(test)]
mod reference_device_internal_api_tests {

    //! ReferenceDevice has a set of private functions we should make sure
    //! actually work.

    use super::*;

    #[test]
    fn test_increment_non_negative() {
        let dev = ReferenceDevice::default();
        let motor = 5;

        let step1 = 1;
        let step2 = 5;
        let step3 = -10;

        let expected1 = 6;
        let expected2 = 10;
        let expected3 = 0;

        assert_eq!(expected1, dev.increment_non_negative(motor, step1));
        assert_eq!(expected2, dev.increment_non_negative(motor, step2));
        assert_eq!(expected3, dev.increment_non_negative(motor, step3));
    }

    #[test]
    fn test_increment_motors() {
        let mut dev = ReferenceDevice::default();

        dev.increment_motors(10, 10);

        assert_eq!(10, dev.motor1);
        assert_eq!(10, dev.motor2);
    }

    #[test]
    fn test_toggle_pen() {
        let mut dev = ReferenceDevice::default();

        assert_eq!(0, dev.pen);

        dev.toggle_pen();
        assert_eq!(1, dev.pen);
    }
}

#[cfg(test)]
mod reference_device_tests {

    use super::*;
    use crate::instructions::EBBInstruction;

    #[test]
    fn test_toggle_pen() {
        let mut dev = ReferenceDevice::default();

        let inst = EBBInstruction::TogglePen;
        match dev.execute(&inst.as_bytes(), 1) {
            Ok(resp) => assert_eq!(vec![String::from("OK")], resp),
            Err(_) => panic!(),
        }
    }

    #[test]
    fn test_mix_axis_and_query_motors() {
        let ok = vec![String::from("OK")];
        let qm = EBBInstruction::QueryMotors;

        let mut dev = ReferenceDevice::default();
        let expected_starting_pos = vec![String::from("QM,1,0,0,1")];

        let move_1 = EBBInstruction::MixedGeometryMove {
            x: 5,
            y: 0,
            duration: None,
        };
        let move_2 = EBBInstruction::MixedGeometryMove {
            x: 0,
            y: 5,
            duration: None,
        };

        let expected_after_mov_1 = vec![String::from("QM,1,5,0,1")];
        let expected_after_mov_2 = vec![String::from("QM,1,5,5,1")];

        match dev.execute(&qm.as_bytes(), 1) {
            Ok(resp) => assert_eq!(expected_starting_pos, resp),
            Err(e) => panic!(e),
        };

        match dev.execute(&move_1.as_bytes(), 1) {
            Ok(resp) => assert_eq!(ok, resp),
            _ => panic!(),
        };

        match dev.execute(&qm.as_bytes(), 1) {
            Ok(resp) => assert_eq!(expected_after_mov_1, resp),
            Err(e) => panic!(e),
        }

        match dev.execute(&move_2.as_bytes(), 1) {
            Ok(resp) => assert_eq!(ok, resp),
            _ => panic!(),
        };

        match dev.execute(&qm.as_bytes(), 1) {
            Ok(resp) => assert_eq!(expected_after_mov_2, resp),
            Err(e) => panic!(e),
        }
    }

}