mirror of
https://github.com/cccb/servicepoint.git
synced 2025-01-19 02:20:12 +01:00
refactored and added tests
This commit is contained in:
parent
8cdf0d6051
commit
6701e8769e
|
@ -1,64 +1,6 @@
|
||||||
|
use std::convert::From;
|
||||||
|
|
||||||
/// A window for luminance and text commands
|
use super::text;
|
||||||
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<u8>);
|
|
||||||
|
|
||||||
impl TextRaw {
|
|
||||||
pub fn new(x: u16, y: u16, bytes: Vec<u8>) -> 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),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Display Commands
|
/// Display Commands
|
||||||
pub enum Command {
|
pub enum Command {
|
||||||
|
@ -66,5 +8,21 @@ pub enum Command {
|
||||||
Clear,
|
Clear,
|
||||||
Reboot,
|
Reboot,
|
||||||
Fadeout,
|
Fadeout,
|
||||||
Text(Text),
|
Text(text::Text),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Directly converty a raw text into a command which
|
||||||
|
/// can be sent to the display.
|
||||||
|
impl From<text::Raw> 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<text::Buffer> for Command {
|
||||||
|
fn from(buffer: text::Buffer) -> Self {
|
||||||
|
Command::Text(text::Text::Buffer(buffer))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +1,30 @@
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use std::net::UdpSocket;
|
use std::net::UdpSocket;
|
||||||
|
|
||||||
use crate::display::commands::Command;
|
use super::commands::Command;
|
||||||
use crate::display::protocol::Data;
|
use crate::protocol::Data;
|
||||||
|
|
||||||
pub struct Display {
|
pub struct Display {
|
||||||
addr: String,
|
addr: String,
|
||||||
socket: UdpSocket,
|
socket: UdpSocket,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Display {
|
impl Display {
|
||||||
|
/// Open a new UDP socket and create a display instance
|
||||||
pub fn open(addr: String) -> Result<Self> {
|
pub fn open(addr: String) -> Result<Self> {
|
||||||
let socket = UdpSocket::bind("0.0.0.0:17382")?;
|
let socket = UdpSocket::bind("0.0.0.0:0")?;
|
||||||
Ok(Self{
|
Ok(Self{
|
||||||
addr: addr,
|
addr: addr,
|
||||||
socket: socket,
|
socket: socket,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Send a command to the display
|
||||||
pub fn send(&self, cmd: Command) -> Result<()> {
|
pub fn send(&self, cmd: Command) -> Result<()> {
|
||||||
let data: Data = cmd.into();
|
let data: Data = cmd.into();
|
||||||
for frame in data {
|
for frame in data {
|
||||||
println!("sending payload: {:?}", &frame);
|
self.socket.send_to(
|
||||||
self.socket.send_to(frame.as_slice(), self.addr.clone().as_str())?;
|
frame.as_slice(), self.addr.clone().as_str())?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
18
airportdisplay/src/geometry.rs
Normal file
18
airportdisplay/src/geometry.rs
Normal file
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
mod display;
|
mod display;
|
||||||
mod net;
|
mod protocol;
|
||||||
|
mod commands;
|
||||||
|
mod geometry;
|
||||||
|
mod text;
|
||||||
|
|
||||||
pub use display::commands::{Command, Text, TextRaw, TextBuffer, Window};
|
pub const TEXT_COLUMNS: usize = 56;
|
||||||
pub use display::protocol::Data;
|
pub const TEXT_ROWS: usize = 20;
|
||||||
pub use net::display::Display;
|
|
||||||
|
pub use commands::{Command};
|
||||||
|
pub use protocol::Data;
|
||||||
|
pub use display::Display;
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
pub mod commands;
|
|
||||||
pub mod protocol;
|
|
||||||
|
|
||||||
pub const TEXT_COLUMNS: usize = 56;
|
|
||||||
pub const TEXT_ROWS: usize = 20;
|
|
|
@ -1,13 +1,16 @@
|
||||||
use std::convert::From;
|
use std::convert::From;
|
||||||
|
|
||||||
use bytes::BufMut;
|
|
||||||
use codepage_437::{CP437_WINGDINGS, ToCp437};
|
use codepage_437::{CP437_WINGDINGS, ToCp437};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
commands::{Command, Text, TextBuffer, TextRaw, Window},
|
commands::{Command},
|
||||||
|
text,
|
||||||
|
geometry::{Window, Origin, Size},
|
||||||
TEXT_COLUMNS, TEXT_ROWS,
|
TEXT_COLUMNS, TEXT_ROWS,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const CMD_RAW_TEXT: &'static [u8] = &[0x00, 0x03];
|
||||||
|
|
||||||
/// A frame holds a single encoded display command,
|
/// A frame holds a single encoded display command,
|
||||||
/// like set text at pos x, y.
|
/// like set text at pos x, y.
|
||||||
pub type Frame = Vec<u8>;
|
pub type Frame = Vec<u8>;
|
||||||
|
@ -15,29 +18,46 @@ pub type Frame = Vec<u8>;
|
||||||
/// Data is a list of commands to be sent to the display.
|
/// Data is a list of commands to be sent to the display.
|
||||||
pub type Data = Vec<Frame>;
|
pub type Data = Vec<Frame>;
|
||||||
|
|
||||||
|
/// Encode position data as big endian
|
||||||
|
impl From<Origin> 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<Size> 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
|
/// Encode window data
|
||||||
impl From<Window> for Frame {
|
impl From<Window> for Frame {
|
||||||
fn from(Window { x, y, w, h }: Window) -> Self {
|
fn from(Window(origin, size): Window) -> Self {
|
||||||
let mut buf = vec![];
|
[Frame::from(origin), Frame::from(size)].concat()
|
||||||
buf.put_u16(x);
|
|
||||||
buf.put_u16(y);
|
|
||||||
buf.put_u16(w);
|
|
||||||
buf.put_u16(h);
|
|
||||||
buf
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<TextRaw> for Data {
|
/// Encode raw text byte command
|
||||||
fn from(TextRaw(position, bytes): TextRaw) -> Data {
|
impl From<text::Raw> for Data {
|
||||||
|
fn from(text::Raw(origin, bytes): text::Raw) -> Data {
|
||||||
let mut bytes = bytes.clone();
|
let mut bytes = bytes.clone();
|
||||||
bytes.truncate(TEXT_COLUMNS);
|
bytes.truncate(TEXT_COLUMNS);
|
||||||
let window = Window::new(position.x, position.y, bytes.len() as u16, 1);
|
let size = Size(bytes.len() as u16, 1);
|
||||||
vec![[vec![0x00, 0x03], window.into(), bytes].concat()]
|
vec![[CMD_RAW_TEXT.into(), origin.into(), size.into(), bytes].concat()]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<TextBuffer> for Data {
|
/// Encode a text buffer as a series of commands (data).
|
||||||
fn from(TextBuffer(position, text): TextBuffer) -> Data {
|
impl From<text::Buffer> for Data {
|
||||||
|
fn from(text::Buffer(Origin(x, y), text): text::Buffer) -> Data {
|
||||||
let mut lines: Vec<&str> = text.split("\n").collect();
|
let mut lines: Vec<&str> = text.split("\n").collect();
|
||||||
lines.truncate(TEXT_ROWS);
|
lines.truncate(TEXT_ROWS);
|
||||||
|
|
||||||
|
@ -45,10 +65,12 @@ impl From<TextBuffer> for Data {
|
||||||
for (i, line) in lines.iter().enumerate() {
|
for (i, line) in lines.iter().enumerate() {
|
||||||
if let Ok(bytes) = line.to_cp437(&CP437_WINGDINGS) {
|
if let Ok(bytes) = line.to_cp437(&CP437_WINGDINGS) {
|
||||||
let len = bytes.len() as u16;
|
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([
|
data.push([
|
||||||
vec![0x00, 0x03],
|
Frame::from(CMD_RAW_TEXT),
|
||||||
window.into(),
|
pos.into(),
|
||||||
|
size.into(),
|
||||||
bytes.into(),
|
bytes.into(),
|
||||||
].concat());
|
].concat());
|
||||||
}
|
}
|
||||||
|
@ -57,11 +79,11 @@ impl From<TextBuffer> for Data {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Text> for Data {
|
impl From<text::Text> for Data {
|
||||||
fn from(text: Text) -> Data {
|
fn from(text: text::Text) -> Data {
|
||||||
match text {
|
match text {
|
||||||
Text::Raw(raw) => raw.into(),
|
text::Text::Raw(raw) => raw.into(),
|
||||||
Text::Buffer(buffer) => buffer.into(),
|
text::Text::Buffer(buffer) => buffer.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,12 +103,62 @@ impl From<Command> for Data {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{Command, Data, Text, TextRaw, Window};
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn frame_data_from_text_command() {
|
fn encode_origin_big_endian() {
|
||||||
let cmd = Command::Text(Text::Raw(TextRaw(Window::position(1, 2), "text123".into())));
|
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<u8> = "text123".into();
|
||||||
|
let len: u8 = bytes.len() as u8;
|
||||||
|
let cmd: Command = text::Raw::from(bytes).into();
|
||||||
let data: Data = cmd.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!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
65
airportdisplay/src/text.rs
Normal file
65
airportdisplay/src/text.rs
Normal file
|
@ -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<u8>);
|
||||||
|
|
||||||
|
/// Convert from bytes
|
||||||
|
impl From<Vec<u8>> for Raw {
|
||||||
|
fn from(bytes: Vec<u8>) -> 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<String> for Buffer {
|
||||||
|
fn from(text: String) -> Self {
|
||||||
|
Self(Origin::default(), text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Text Commands
|
||||||
|
pub enum Text {
|
||||||
|
Raw(Raw),
|
||||||
|
Buffer(Buffer),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Raw> for Text {
|
||||||
|
fn from(raw: Raw) -> Self {
|
||||||
|
Text::Raw(raw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Buffer> 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!");
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue