From 6701e8769e0185c2d8d36a49254475ffc9c9f10c Mon Sep 17 00:00:00 2001 From: Annika Hannig Date: Sat, 27 Aug 2022 11:45:46 +0000 Subject: [PATCH] refactored and added tests --- airportdisplay/src/commands.rs | 80 +++++---------------- airportdisplay/src/display.rs | 14 ++-- airportdisplay/src/geometry.rs | 18 +++++ airportdisplay/src/lib.rs | 14 ++-- airportdisplay/src/mod.rs | 5 -- airportdisplay/src/protocol.rs | 124 ++++++++++++++++++++++++++------- airportdisplay/src/text.rs | 65 +++++++++++++++++ 7 files changed, 217 insertions(+), 103 deletions(-) create mode 100644 airportdisplay/src/geometry.rs delete mode 100644 airportdisplay/src/mod.rs create mode 100644 airportdisplay/src/text.rs diff --git a/airportdisplay/src/commands.rs b/airportdisplay/src/commands.rs index 5048f41..2f93b25 100644 --- a/airportdisplay/src/commands.rs +++ b/airportdisplay/src/commands.rs @@ -1,64 +1,6 @@ +use std::convert::From; -/// A window for luminance and text commands -pub struct Window { - pub x: u16, // 0..55 - pub y: u16, // 0..19 - pub w: u16, // 1..56 - pub h: u16, // 1..20 -} - -impl Window { - pub fn new(x: u16, y: u16, w: u16, h: u16) -> Self { - Window { - x: x, - y: y, - w: w, - h: h, - } - } - - pub fn position(x: u16, y: u16) -> Self { - Window { - x: x, - y: y, - w: 0, - h: 0, - } - } -} - -/// Luminance value -pub struct Luminance(Window, u8); - -/// TextRaw holds bytes and a window -pub struct TextRaw(pub Window, pub Vec); - -impl TextRaw { - pub fn new(x: u16, y: u16, bytes: Vec) -> Self { - Self(Window::position(x,y), bytes) - } -} - -/// TextBuffer holds a window an a text buffer -/// the text will be truncated to fit into the window -pub struct TextBuffer(pub Window, pub String); - -impl TextBuffer { - pub fn at(x: u16, y: u16, text: String) -> Self { - Self(Window::position(x,y), text) - } - - pub fn from(text: String) -> Self { - Self::at(0, 0, text) - } - -} - -/// Text Commands -pub enum Text { - Raw(TextRaw), - Buffer(TextBuffer), -} +use super::text; /// Display Commands pub enum Command { @@ -66,5 +8,21 @@ pub enum Command { Clear, Reboot, Fadeout, - Text(Text), + Text(text::Text), +} + +/// Directly converty a raw text into a command which +/// can be sent to the display. +impl From for Command { + fn from(raw: text::Raw) -> Self { + Command::Text(text::Text::Raw(raw)) + } +} + +/// Shortcut to directly convert a text buffer into +/// a commmand which can be sent to the display. +impl From for Command { + fn from(buffer: text::Buffer) -> Self { + Command::Text(text::Text::Buffer(buffer)) + } } diff --git a/airportdisplay/src/display.rs b/airportdisplay/src/display.rs index c4483b2..6228ff2 100644 --- a/airportdisplay/src/display.rs +++ b/airportdisplay/src/display.rs @@ -1,30 +1,30 @@ - use anyhow::Result; use std::net::UdpSocket; -use crate::display::commands::Command; -use crate::display::protocol::Data; +use super::commands::Command; +use crate::protocol::Data; pub struct Display { addr: String, socket: UdpSocket, } - impl Display { + /// Open a new UDP socket and create a display instance pub fn open(addr: String) -> Result { - let socket = UdpSocket::bind("0.0.0.0:17382")?; + let socket = UdpSocket::bind("0.0.0.0:0")?; Ok(Self{ addr: addr, socket: socket, }) } + /// Send a command to the display pub fn send(&self, cmd: Command) -> Result<()> { let data: Data = cmd.into(); for frame in data { - println!("sending payload: {:?}", &frame); - self.socket.send_to(frame.as_slice(), self.addr.clone().as_str())?; + self.socket.send_to( + frame.as_slice(), self.addr.clone().as_str())?; } Ok(()) } diff --git a/airportdisplay/src/geometry.rs b/airportdisplay/src/geometry.rs new file mode 100644 index 0000000..e05c7fd --- /dev/null +++ b/airportdisplay/src/geometry.rs @@ -0,0 +1,18 @@ + +/// An origin marks the top left position of the +/// data sent to the display. +#[derive(Default)] +pub struct Origin(pub u16, pub u16); + +/// Size defines the width and height of a window +pub struct Size(pub u16, pub u16); + +/// A window +pub struct Window(pub Origin, pub Size); + +impl Window { + pub fn new(x: u16, y: u16, w: u16, h: u16) -> Self { + Window(Origin(x, y), Size(w, h)) + } +} + diff --git a/airportdisplay/src/lib.rs b/airportdisplay/src/lib.rs index 5fe8b78..1cbc069 100644 --- a/airportdisplay/src/lib.rs +++ b/airportdisplay/src/lib.rs @@ -1,6 +1,12 @@ mod display; -mod net; +mod protocol; +mod commands; +mod geometry; +mod text; -pub use display::commands::{Command, Text, TextRaw, TextBuffer, Window}; -pub use display::protocol::Data; -pub use net::display::Display; +pub const TEXT_COLUMNS: usize = 56; +pub const TEXT_ROWS: usize = 20; + +pub use commands::{Command}; +pub use protocol::Data; +pub use display::Display; diff --git a/airportdisplay/src/mod.rs b/airportdisplay/src/mod.rs deleted file mode 100644 index 4b15efc..0000000 --- a/airportdisplay/src/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod commands; -pub mod protocol; - -pub const TEXT_COLUMNS: usize = 56; -pub const TEXT_ROWS: usize = 20; diff --git a/airportdisplay/src/protocol.rs b/airportdisplay/src/protocol.rs index 6a1ae9f..720ce28 100644 --- a/airportdisplay/src/protocol.rs +++ b/airportdisplay/src/protocol.rs @@ -1,13 +1,16 @@ use std::convert::From; -use bytes::BufMut; use codepage_437::{CP437_WINGDINGS, ToCp437}; use super::{ - commands::{Command, Text, TextBuffer, TextRaw, Window}, + commands::{Command}, + text, + geometry::{Window, Origin, Size}, TEXT_COLUMNS, TEXT_ROWS, }; +const CMD_RAW_TEXT: &'static [u8] = &[0x00, 0x03]; + /// A frame holds a single encoded display command, /// like set text at pos x, y. pub type Frame = Vec; @@ -15,29 +18,46 @@ pub type Frame = Vec; /// Data is a list of commands to be sent to the display. pub type Data = Vec; +/// Encode position data as big endian +impl From for Frame { + fn from(Origin(x, y): Origin) -> Self { + vec![ + (x >> 8) as u8, x as u8, + (y >> 8) as u8, y as u8, + ] + } +} + +/// Encode size as big endian +impl From for Frame { + fn from(Size(w, h): Size) -> Self { + vec![ + (w >> 8) as u8, w as u8, + (h >> 8) as u8, h as u8, + ] + } +} + /// Encode window data impl From for Frame { - fn from(Window { x, y, w, h }: Window) -> Self { - let mut buf = vec![]; - buf.put_u16(x); - buf.put_u16(y); - buf.put_u16(w); - buf.put_u16(h); - buf + fn from(Window(origin, size): Window) -> Self { + [Frame::from(origin), Frame::from(size)].concat() } } -impl From for Data { - fn from(TextRaw(position, bytes): TextRaw) -> Data { +/// Encode raw text byte command +impl From for Data { + fn from(text::Raw(origin, bytes): text::Raw) -> Data { let mut bytes = bytes.clone(); bytes.truncate(TEXT_COLUMNS); - let window = Window::new(position.x, position.y, bytes.len() as u16, 1); - vec![[vec![0x00, 0x03], window.into(), bytes].concat()] + let size = Size(bytes.len() as u16, 1); + vec![[CMD_RAW_TEXT.into(), origin.into(), size.into(), bytes].concat()] } } -impl From for Data { - fn from(TextBuffer(position, text): TextBuffer) -> Data { +/// Encode a text buffer as a series of commands (data). +impl From for Data { + fn from(text::Buffer(Origin(x, y), text): text::Buffer) -> Data { let mut lines: Vec<&str> = text.split("\n").collect(); lines.truncate(TEXT_ROWS); @@ -45,10 +65,12 @@ impl From for Data { for (i, line) in lines.iter().enumerate() { if let Ok(bytes) = line.to_cp437(&CP437_WINGDINGS) { let len = bytes.len() as u16; - let window = Window::new(position.x, position.y + i as u16, len as u16, 1); + let pos = Origin(x, y + i as u16); + let size = Size(len, 1); data.push([ - vec![0x00, 0x03], - window.into(), + Frame::from(CMD_RAW_TEXT), + pos.into(), + size.into(), bytes.into(), ].concat()); } @@ -57,11 +79,11 @@ impl From for Data { } } -impl From for Data { - fn from(text: Text) -> Data { +impl From for Data { + fn from(text: text::Text) -> Data { match text { - Text::Raw(raw) => raw.into(), - Text::Buffer(buffer) => buffer.into(), + text::Text::Raw(raw) => raw.into(), + text::Text::Buffer(buffer) => buffer.into(), } } } @@ -81,12 +103,62 @@ impl From for Data { #[cfg(test)] mod tests { - use super::{Command, Data, Text, TextRaw, Window}; + use super::*; #[test] - fn frame_data_from_text_command() { - let cmd = Command::Text(Text::Raw(TextRaw(Window::position(1, 2), "text123".into()))); + fn encode_origin_big_endian() { + let frame: Frame = Origin(23, 42).into(); + assert_eq!(frame[0], 0); + assert_eq!(frame[1], 23); + assert_eq!(frame[2], 0); + assert_eq!(frame[3], 42); + + let frame: Frame = Origin(0xabcd, 0xcdef).into(); + assert_eq!(frame[0], 0xab); + assert_eq!(frame[1], 0xcd); + assert_eq!(frame[2], 0xcd); + assert_eq!(frame[3], 0xef); + } + + #[test] + fn encode_size_big_endian() { + let frame: Frame = Size(23, 42).into(); + assert_eq!(frame[0], 0); + assert_eq!(frame[1], 23); + assert_eq!(frame[2], 0); + assert_eq!(frame[3], 42); + + let frame: Frame = Size(0xabcd, 0xcdef).into(); + assert_eq!(frame[0], 0xab); + assert_eq!(frame[1], 0xcd); + assert_eq!(frame[2], 0xcd); + assert_eq!(frame[3], 0xef); + } + + #[test] + fn data_from_raw_text() { + let bytes: Vec = "text123".into(); + let len: u8 = bytes.len() as u8; + let cmd: Command = text::Raw::from(bytes).into(); let data: Data = cmd.into(); - println!("data: {:?}", data) + + println!("data: {:?}\n", data); + + assert_eq!(data[0][1], 0x03); // TEXT + assert_eq!(data[0][7], len); + } + + #[test] + fn data_from_text_buffer() { + let text: String = "hello\ndisplay!".into(); + let cmd: Command = text::Buffer::from(text).into(); + let data: Data = cmd.into(); + + println!("data: {:?}\n", data); + + assert_eq!(data.len(), 2); // 2 commands + assert_eq!(data[0][1], 0x03); // TEXT + assert_eq!(data[0][7], 5); // len(hallo) + assert_eq!(data[1][7], 8); // len(display!) } } diff --git a/airportdisplay/src/text.rs b/airportdisplay/src/text.rs new file mode 100644 index 0000000..1291576 --- /dev/null +++ b/airportdisplay/src/text.rs @@ -0,0 +1,65 @@ + +use std::convert::From; + +use super::geometry::Origin; + + +/// TextRaw holds bytes and a window +pub struct Raw(pub Origin, pub Vec); + +/// Convert from bytes +impl From> for Raw { + fn from(bytes: Vec) -> Self { + Self(Origin::default(), bytes) + } +} + + +/// TextBuffer holds a multiline block of utf8 text +/// data and a origin. +pub struct Buffer(pub Origin, pub String); + +impl Buffer { + pub fn at(x: u16, y: u16, text: String) -> Self { + Self(Origin(x, y), text) + } +} + +/// Implement convert from trait for String +impl From for Buffer { + fn from(text: String) -> Self { + Self(Origin::default(), text) + } +} + +/// Text Commands +pub enum Text { + Raw(Raw), + Buffer(Buffer), +} + +impl From for Text { + fn from(raw: Raw) -> Self { + Text::Raw(raw) + } +} + +impl From for Text { + fn from(buffer: Buffer) -> Self { + Text::Buffer(buffer) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn buffer_from_string() { + let buf: Buffer = String::from("hej there!").into(); + let Buffer(Origin(x, y), text) = buf; + assert_eq!(x, 0); + assert_eq!(y, 0); + assert_eq!(text, "hej there!"); + } +}