This commit is contained in:
parent
b691ef33f8
commit
c66e6db498
|
@ -28,7 +28,7 @@ fn main() {
|
||||||
.expect("connection failed");
|
.expect("connection failed");
|
||||||
|
|
||||||
// clear screen content
|
// clear screen content
|
||||||
connection.send(Command::Clear)
|
connection.send(command::Clear)
|
||||||
.expect("send failed");
|
.expect("send failed");
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
@ -36,13 +36,16 @@ fn main() {
|
||||||
|
|
||||||
if cli.clear {
|
if cli.clear {
|
||||||
connection
|
connection
|
||||||
.send(Command::Clear)
|
.send(command::Clear)
|
||||||
.expect("sending clear failed");
|
.expect("sending clear failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
let text = cli.text.join("\n");
|
let text = cli.text.join("\n");
|
||||||
let grid = CharGrid::wrap_str(TILE_WIDTH, &text);
|
let command = command::Utf8Data {
|
||||||
|
origin: Origin::ZERO,
|
||||||
|
grid: CharGrid::wrap_str(TILE_WIDTH, &text),
|
||||||
|
};
|
||||||
connection
|
connection
|
||||||
.send(Command::Utf8Data(Origin::ZERO, grid))
|
.send(command)
|
||||||
.expect("sending text failed");
|
.expect("sending text failed");
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,14 +14,14 @@ fn main() {
|
||||||
let connection = connection::Udp::open(cli.destination)
|
let connection = connection::Udp::open(cli.destination)
|
||||||
.expect("could not connect to display");
|
.expect("could not connect to display");
|
||||||
|
|
||||||
let mut pixels = Bitmap::max_sized();
|
let mut bitmap = Bitmap::max_sized();
|
||||||
pixels.fill(true);
|
bitmap.fill(true);
|
||||||
|
|
||||||
let command = Command::BitmapLinearWin(
|
let command = command::BitmapLinearWin {
|
||||||
Origin::ZERO,
|
origin: Origin::ZERO,
|
||||||
pixels,
|
bitmap,
|
||||||
CompressionCode::default(),
|
compression: CompressionCode::default(),
|
||||||
);
|
};
|
||||||
connection.send(command).expect("send failed");
|
connection.send(command).expect("send failed");
|
||||||
|
|
||||||
let max_brightness: u8 = Brightness::MAX.into();
|
let max_brightness: u8 = Brightness::MAX.into();
|
||||||
|
@ -31,7 +31,9 @@ fn main() {
|
||||||
*byte = Brightness::try_from(level).unwrap();
|
*byte = Brightness::try_from(level).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
connection
|
let command = command::CharBrightness {
|
||||||
.send(Command::CharBrightness(Origin::ZERO, brightnesses))
|
origin: Origin::ZERO,
|
||||||
.expect("send failed");
|
grid: brightnesses,
|
||||||
|
};
|
||||||
|
connection.send(command).expect("send failed");
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,11 +22,11 @@ fn main() {
|
||||||
let mut field = make_random_field(cli.probability);
|
let mut field = make_random_field(cli.probability);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let command = Command::BitmapLinearWin(
|
let command = command::BitmapLinearWin {
|
||||||
Origin::ZERO,
|
origin: Origin::ZERO,
|
||||||
field.clone(),
|
bitmap: field.clone(),
|
||||||
CompressionCode::default(),
|
compression: CompressionCode::default(),
|
||||||
);
|
};
|
||||||
connection.send(command).expect("could not send");
|
connection.send(command).expect("could not send");
|
||||||
thread::sleep(FRAME_PACING);
|
thread::sleep(FRAME_PACING);
|
||||||
field = iteration(field);
|
field = iteration(field);
|
||||||
|
|
|
@ -14,19 +14,19 @@ fn main() {
|
||||||
let connection = connection::Udp::open(Cli::parse().destination)
|
let connection = connection::Udp::open(Cli::parse().destination)
|
||||||
.expect("could not connect to display");
|
.expect("could not connect to display");
|
||||||
|
|
||||||
let mut pixels = Bitmap::max_sized();
|
let mut bitmap = Bitmap::max_sized();
|
||||||
for x_offset in 0..usize::MAX {
|
for x_offset in 0..usize::MAX {
|
||||||
pixels.fill(false);
|
bitmap.fill(false);
|
||||||
|
|
||||||
for y in 0..PIXEL_HEIGHT {
|
for y in 0..PIXEL_HEIGHT {
|
||||||
pixels.set((y + x_offset) % PIXEL_WIDTH, y, true);
|
bitmap.set((y + x_offset) % PIXEL_WIDTH, y, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
let command = Command::BitmapLinearWin(
|
let command = command::BitmapLinearWin {
|
||||||
Origin::ZERO,
|
bitmap: bitmap.clone(),
|
||||||
pixels.clone(),
|
compression: CompressionCode::default(),
|
||||||
CompressionCode::default(),
|
origin: Origin::ZERO,
|
||||||
);
|
};
|
||||||
connection.send(command).expect("send failed");
|
connection.send(command).expect("send failed");
|
||||||
thread::sleep(FRAME_PACING);
|
thread::sleep(FRAME_PACING);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use servicepoint::*;
|
use servicepoint::*;
|
||||||
use std::net::UdpSocket;
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
|
@ -29,17 +28,17 @@ fn main() {
|
||||||
let mut filled_grid = Bitmap::max_sized();
|
let mut filled_grid = Bitmap::max_sized();
|
||||||
filled_grid.fill(true);
|
filled_grid.fill(true);
|
||||||
|
|
||||||
let command = Command::BitmapLinearWin(
|
let command = command::BitmapLinearWin {
|
||||||
Origin::ZERO,
|
origin: Origin::ZERO,
|
||||||
filled_grid,
|
bitmap: filled_grid,
|
||||||
CompressionCode::default(),
|
compression: CompressionCode::default(),
|
||||||
);
|
};
|
||||||
connection.send(command).expect("send failed");
|
connection.send(command).expect("send failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
// set all pixels to the same random brightness
|
// set all pixels to the same random brightness
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
connection.send(Command::Brightness(rng.gen())).unwrap();
|
connection.send(rng.gen::<Brightness>()).unwrap();
|
||||||
|
|
||||||
// continuously update random windows to new random brightness
|
// continuously update random windows to new random brightness
|
||||||
loop {
|
loop {
|
||||||
|
@ -60,7 +59,7 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
connection
|
connection
|
||||||
.send(Command::CharBrightness(origin, luma))
|
.send(command::CharBrightness { origin, grid: luma })
|
||||||
.unwrap();
|
.unwrap();
|
||||||
std::thread::sleep(wait_duration);
|
std::thread::sleep(wait_duration);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,21 @@
|
||||||
//! Example for how to use the WebSocket connection
|
//! Example for how to use the WebSocket connection
|
||||||
|
|
||||||
use servicepoint::connection::Websocket;
|
use servicepoint::connection::Websocket;
|
||||||
use servicepoint::{
|
use servicepoint::*;
|
||||||
Bitmap, Command, CompressionCode, Connection, Grid, Origin,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let connection =
|
let uri = "ws://localhost:8080".parse().unwrap();
|
||||||
Websocket::open("ws://localhost:8080".parse().unwrap()).unwrap();
|
let connection = Websocket::open(uri).unwrap();
|
||||||
|
|
||||||
connection.send(Command::Clear).unwrap();
|
connection.send(command::Clear).unwrap();
|
||||||
|
|
||||||
let mut pixels = Bitmap::max_sized();
|
let mut pixels = Bitmap::max_sized();
|
||||||
pixels.fill(true);
|
pixels.fill(true);
|
||||||
|
|
||||||
connection
|
let command = command::BitmapLinearWin {
|
||||||
.send(Command::BitmapLinearWin(
|
origin: Origin::ZERO,
|
||||||
Origin::ZERO,
|
bitmap: pixels,
|
||||||
pixels,
|
compression: CompressionCode::default(),
|
||||||
CompressionCode::default(),
|
};
|
||||||
))
|
connection.send(command).unwrap();
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,12 +32,13 @@ fn main() {
|
||||||
enabled_pixels.set(x_offset % PIXEL_WIDTH, y, false);
|
enabled_pixels.set(x_offset % PIXEL_WIDTH, y, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let command = command::BitmapLinearWin {
|
||||||
|
origin: Origin::ZERO,
|
||||||
|
bitmap: enabled_pixels.clone(),
|
||||||
|
compression: CompressionCode::default(),
|
||||||
|
};
|
||||||
connection
|
connection
|
||||||
.send(Command::BitmapLinearWin(
|
.send(command)
|
||||||
Origin::ZERO,
|
|
||||||
enabled_pixels.clone(),
|
|
||||||
CompressionCode::default(),
|
|
||||||
))
|
|
||||||
.expect("could not send command to display");
|
.expect("could not send command to display");
|
||||||
thread::sleep(sleep_duration);
|
thread::sleep(sleep_duration);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ use rand::{
|
||||||
///
|
///
|
||||||
/// let b = Brightness::try_from(7).unwrap();
|
/// let b = Brightness::try_from(7).unwrap();
|
||||||
/// # let connection = connection::Fake;
|
/// # let connection = connection::Fake;
|
||||||
/// let result = connection.send(Command::Brightness(b));
|
/// let result = connection.send(b);
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)]
|
||||||
pub struct Brightness(u8);
|
pub struct Brightness(u8);
|
||||||
|
|
|
@ -8,13 +8,16 @@ use crate::ByteGrid;
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use servicepoint::{Brightness, BrightnessGrid, Command, Connection, Grid, Origin, connection};
|
/// # use servicepoint::*;
|
||||||
/// let mut grid = BrightnessGrid::new(2,2);
|
/// let mut grid = BrightnessGrid::new(2,2);
|
||||||
/// grid.set(0, 0, Brightness::MIN);
|
/// grid.set(0, 0, Brightness::MIN);
|
||||||
/// grid.set(1, 1, Brightness::MIN);
|
/// grid.set(1, 1, Brightness::MIN);
|
||||||
///
|
///
|
||||||
/// # let connection = connection::Fake;
|
/// # let connection = connection::Fake;
|
||||||
/// connection.send(Command::CharBrightness(Origin::new(3, 7), grid)).unwrap()
|
/// connection.send(command::CharBrightness {
|
||||||
|
/// origin: Origin::new(3, 7),
|
||||||
|
/// grid
|
||||||
|
/// }).unwrap()
|
||||||
/// ```
|
/// ```
|
||||||
pub type BrightnessGrid = ValueGrid<Brightness>;
|
pub type BrightnessGrid = ValueGrid<Brightness>;
|
||||||
|
|
||||||
|
|
|
@ -10,12 +10,13 @@ use std::string::FromUtf8Error;
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use servicepoint::{connection, CharGrid, Command, Connection, Origin};
|
/// # use servicepoint::*;
|
||||||
/// let grid = CharGrid::from("You can\nload multiline\nstrings directly");
|
/// let grid = CharGrid::from("You can\nload multiline\nstrings directly");
|
||||||
/// assert_eq!(grid.get_row_str(1), Some("load multiline\0\0".to_string()));
|
/// assert_eq!(grid.get_row_str(1), Some("load multiline\0\0".to_string()));
|
||||||
///
|
///
|
||||||
/// # let connection = connection::Fake;
|
/// # let connection = connection::Fake;
|
||||||
/// let command = Command::Utf8Data(Origin::ZERO, grid);
|
/// let command = command::Utf8Data { origin: Origin::ZERO, grid };
|
||||||
|
/// connection.send(command).unwrap()
|
||||||
/// ```
|
/// ```
|
||||||
pub type CharGrid = ValueGrid<char>;
|
pub type CharGrid = ValueGrid<char>;
|
||||||
|
|
||||||
|
|
968
src/command.rs
968
src/command.rs
|
@ -1,968 +0,0 @@
|
||||||
use crate::command_code::CommandCode;
|
|
||||||
use crate::compression::into_decompressed;
|
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// Type alias for documenting the meaning of the u16 in enum values
|
|
||||||
pub type Offset = usize;
|
|
||||||
|
|
||||||
/// A low-level display command.
|
|
||||||
///
|
|
||||||
/// This struct and associated functions implement the UDP protocol for the display.
|
|
||||||
///
|
|
||||||
/// To send a [Command], use a [connection][crate::Connection].
|
|
||||||
///
|
|
||||||
/// # Available commands
|
|
||||||
///
|
|
||||||
/// To send text, take a look at [Command::Cp437Data].
|
|
||||||
///
|
|
||||||
/// To draw pixels, the easiest command to use is [Command::BitmapLinearWin].
|
|
||||||
///
|
|
||||||
/// The other BitmapLinear-Commands operate on a region of pixel memory directly.
|
|
||||||
/// [Command::BitmapLinear] overwrites a region.
|
|
||||||
/// [Command::BitmapLinearOr], [Command::BitmapLinearAnd] and [Command::BitmapLinearXor] apply logical operations per pixel.
|
|
||||||
///
|
|
||||||
/// Out of bounds operations may be truncated or ignored by the display.
|
|
||||||
///
|
|
||||||
/// # Compression
|
|
||||||
///
|
|
||||||
/// Some commands can contain compressed payloads.
|
|
||||||
/// To get started, use [CompressionCode::default].
|
|
||||||
///
|
|
||||||
/// If you want to archive the best performance (e.g. latency),
|
|
||||||
/// you can try the different compression algorithms for your hardware and use case.
|
|
||||||
///
|
|
||||||
/// In memory, the payload is not compressed in the [Command].
|
|
||||||
/// Payload (de-)compression happens when converting the [Command] into a [Packet] or vice versa.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// use servicepoint::{connection, Brightness, Command, Connection, Packet};
|
|
||||||
/// #
|
|
||||||
/// // create command
|
|
||||||
/// let command = Command::Brightness(Brightness::MAX);
|
|
||||||
///
|
|
||||||
/// // turn command into Packet
|
|
||||||
/// let packet: Packet = command.clone().into();
|
|
||||||
///
|
|
||||||
/// // read command from packet
|
|
||||||
/// let round_tripped = Command::try_from(packet).unwrap();
|
|
||||||
///
|
|
||||||
/// // round tripping produces exact copy
|
|
||||||
/// assert_eq!(command, round_tripped);
|
|
||||||
///
|
|
||||||
/// // send command
|
|
||||||
/// # let connection = connection::Fake;
|
|
||||||
/// connection.send(command).unwrap();
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub enum Command {
|
|
||||||
/// Set all pixels to the off state. Does not affect brightness.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # use servicepoint::{connection, Command, Connection};
|
|
||||||
/// # let connection = connection::Fake;
|
|
||||||
/// connection.send(Command::Clear).unwrap();
|
|
||||||
/// ```
|
|
||||||
Clear,
|
|
||||||
|
|
||||||
/// Show text on the screen.
|
|
||||||
///
|
|
||||||
/// The text is sent in the form of a 2D grid of UTF-8 encoded characters (the default encoding in rust).
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # use servicepoint::*;
|
|
||||||
/// # let connection = connection::Fake;
|
|
||||||
/// let grid = CharGrid::from("Hello,\nWorld!");
|
|
||||||
/// connection.send(Command::Utf8Data(Origin::ZERO, grid)).expect("send failed");
|
|
||||||
/// ```
|
|
||||||
Utf8Data(Origin<Tiles>, CharGrid),
|
|
||||||
|
|
||||||
/// Show text on the screen.
|
|
||||||
///
|
|
||||||
/// The text is sent in the form of a 2D grid of [CP-437] encoded characters.
|
|
||||||
///
|
|
||||||
/// <div class="warning">You probably want to use [Command::Utf8Data] instead</div>
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # use servicepoint::{Command, Connection, Origin, CharGrid, Cp437Grid, connection};
|
|
||||||
/// # let connection = connection::Fake;
|
|
||||||
/// let grid = CharGrid::from("Hello,\nWorld!");
|
|
||||||
/// let grid = Cp437Grid::from(&grid);
|
|
||||||
/// connection.send(Command::Cp437Data(Origin::ZERO, grid)).expect("send failed");
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # use servicepoint::{connection, Command, Connection, Cp437Grid, Origin};
|
|
||||||
/// # let connection = connection::Fake;
|
|
||||||
/// let grid = Cp437Grid::load_ascii("Hello\nWorld", 5, false).unwrap();
|
|
||||||
/// connection.send(Command::Cp437Data(Origin::new(2, 2), grid)).unwrap();
|
|
||||||
/// ```
|
|
||||||
/// [CP-437]: https://en.wikipedia.org/wiki/Code_page_437
|
|
||||||
Cp437Data(Origin<Tiles>, Cp437Grid),
|
|
||||||
|
|
||||||
/// Overwrites a rectangular region of pixels.
|
|
||||||
///
|
|
||||||
/// Origin coordinates must be divisible by 8.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # use servicepoint::{Command, CompressionCode, Grid, Bitmap, Connection};
|
|
||||||
/// # let connection = servicepoint::connection::Fake;
|
|
||||||
/// #
|
|
||||||
/// let mut pixels = Bitmap::max_sized();
|
|
||||||
/// // draw something to the pixels here
|
|
||||||
/// # pixels.set(2, 5, true);
|
|
||||||
///
|
|
||||||
/// // create command to send pixels
|
|
||||||
/// let command = Command::BitmapLinearWin(
|
|
||||||
/// servicepoint::Origin::ZERO,
|
|
||||||
/// pixels,
|
|
||||||
/// CompressionCode::default()
|
|
||||||
/// );
|
|
||||||
///
|
|
||||||
/// connection.send(command).expect("send failed");
|
|
||||||
/// ```
|
|
||||||
BitmapLinearWin(Origin<Pixels>, Bitmap, CompressionCode),
|
|
||||||
|
|
||||||
/// Set the brightness of all tiles to the same value.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # use servicepoint::{connection, Brightness, Command, Connection};
|
|
||||||
/// # let connection = connection::Fake;
|
|
||||||
/// let command = Command::Brightness(Brightness::MAX);
|
|
||||||
/// connection.send(command).unwrap();
|
|
||||||
/// ```
|
|
||||||
Brightness(Brightness),
|
|
||||||
|
|
||||||
/// Set the brightness of individual tiles in a rectangular area of the display.
|
|
||||||
CharBrightness(Origin<Tiles>, BrightnessGrid),
|
|
||||||
|
|
||||||
/// Set pixel data starting at the pixel offset on screen.
|
|
||||||
///
|
|
||||||
/// The screen will continuously overwrite more pixel data without regarding the offset, meaning
|
|
||||||
/// once the starting row is full, overwriting will continue on column 0.
|
|
||||||
///
|
|
||||||
/// The contained [BitVec] is always uncompressed.
|
|
||||||
BitmapLinear(Offset, BitVec, CompressionCode),
|
|
||||||
|
|
||||||
/// Set pixel data according to an and-mask starting at the offset.
|
|
||||||
///
|
|
||||||
/// The screen will continuously overwrite more pixel data without regarding the offset, meaning
|
|
||||||
/// once the starting row is full, overwriting will continue on column 0.
|
|
||||||
///
|
|
||||||
/// The contained [BitVec] is always uncompressed.
|
|
||||||
BitmapLinearAnd(Offset, BitVec, CompressionCode),
|
|
||||||
|
|
||||||
/// Set pixel data according to an or-mask starting at the offset.
|
|
||||||
///
|
|
||||||
/// The screen will continuously overwrite more pixel data without regarding the offset, meaning
|
|
||||||
/// once the starting row is full, overwriting will continue on column 0.
|
|
||||||
///
|
|
||||||
/// The contained [BitVec] is always uncompressed.
|
|
||||||
BitmapLinearOr(Offset, BitVec, CompressionCode),
|
|
||||||
|
|
||||||
/// Set pixel data according to a xor-mask starting at the offset.
|
|
||||||
///
|
|
||||||
/// The screen will continuously overwrite more pixel data without regarding the offset, meaning
|
|
||||||
/// once the starting row is full, overwriting will continue on column 0.
|
|
||||||
///
|
|
||||||
/// The contained [BitVec] is always uncompressed.
|
|
||||||
BitmapLinearXor(Offset, BitVec, CompressionCode),
|
|
||||||
|
|
||||||
/// Kills the udp daemon on the display, which usually results in a restart.
|
|
||||||
///
|
|
||||||
/// Please do not send this in your normal program flow.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # use servicepoint::{connection, Command, Connection};
|
|
||||||
/// # let connection = connection::Fake;
|
|
||||||
/// connection.send(Command::HardReset).unwrap();
|
|
||||||
/// ```
|
|
||||||
HardReset,
|
|
||||||
|
|
||||||
/// <div class="warning">Untested</div>
|
|
||||||
///
|
|
||||||
/// Slowly decrease brightness until off or something like that?
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # use servicepoint::{connection, Command, Connection};
|
|
||||||
/// # let connection = connection::Fake;
|
|
||||||
/// connection.send(Command::FadeOut).unwrap();
|
|
||||||
/// ```
|
|
||||||
FadeOut,
|
|
||||||
|
|
||||||
/// Legacy command code, gets ignored by the real display.
|
|
||||||
///
|
|
||||||
/// Might be useful as a noop package.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # use servicepoint::{connection, Command, Connection};
|
|
||||||
/// # let connection = connection::Fake;
|
|
||||||
/// // this sends a packet that does nothing
|
|
||||||
/// # #[allow(deprecated)]
|
|
||||||
/// connection.send(Command::BitmapLegacy).unwrap();
|
|
||||||
/// ```
|
|
||||||
#[deprecated]
|
|
||||||
BitmapLegacy,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Err values for [Command::try_from].
|
|
||||||
#[derive(Debug, PartialEq, thiserror::Error)]
|
|
||||||
pub enum TryFromPacketError {
|
|
||||||
/// the contained command code does not correspond to a known command
|
|
||||||
#[error("The command code {0:?} does not correspond to a known command")]
|
|
||||||
InvalidCommand(u16),
|
|
||||||
/// the expected payload size was n, but size m was found
|
|
||||||
#[error("the expected payload size was {0}, but size {1} was found")]
|
|
||||||
UnexpectedPayloadSize(usize, usize),
|
|
||||||
/// Header fields not needed for the command have been used.
|
|
||||||
///
|
|
||||||
/// Note that these commands would usually still work on the actual display.
|
|
||||||
#[error("Header fields not needed for the command have been used")]
|
|
||||||
ExtraneousHeaderValues,
|
|
||||||
/// The contained compression code is not known. This could be of disabled features.
|
|
||||||
#[error("The compression code {0:?} does not correspond to a known compression algorithm.")]
|
|
||||||
InvalidCompressionCode(u16),
|
|
||||||
/// Decompression of the payload failed. This can be caused by corrupted packets.
|
|
||||||
#[error("The decompression of the payload failed")]
|
|
||||||
DecompressionFailed,
|
|
||||||
/// The given brightness value is out of bounds
|
|
||||||
#[error("The given brightness value {0} is out of bounds.")]
|
|
||||||
InvalidBrightness(u8),
|
|
||||||
#[error(transparent)]
|
|
||||||
InvalidUtf8(#[from] std::string::FromUtf8Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<Packet> for Command {
|
|
||||||
type Error = TryFromPacketError;
|
|
||||||
|
|
||||||
/// Try to interpret the [Packet] as one containing a [Command]
|
|
||||||
fn try_from(packet: Packet) -> Result<Self, Self::Error> {
|
|
||||||
let Packet {
|
|
||||||
header: Header {
|
|
||||||
command_code, a, ..
|
|
||||||
},
|
|
||||||
..
|
|
||||||
} = packet;
|
|
||||||
let command_code = match CommandCode::try_from(command_code) {
|
|
||||||
Err(()) => {
|
|
||||||
return Err(TryFromPacketError::InvalidCommand(command_code));
|
|
||||||
}
|
|
||||||
Ok(value) => value,
|
|
||||||
};
|
|
||||||
|
|
||||||
match command_code {
|
|
||||||
CommandCode::Clear => {
|
|
||||||
Self::packet_into_command_only(packet, Command::Clear)
|
|
||||||
}
|
|
||||||
CommandCode::Brightness => Self::packet_into_brightness(&packet),
|
|
||||||
CommandCode::HardReset => {
|
|
||||||
Self::packet_into_command_only(packet, Command::HardReset)
|
|
||||||
}
|
|
||||||
CommandCode::FadeOut => {
|
|
||||||
Self::packet_into_command_only(packet, Command::FadeOut)
|
|
||||||
}
|
|
||||||
CommandCode::Cp437Data => Self::packet_into_cp437(&packet),
|
|
||||||
CommandCode::CharBrightness => {
|
|
||||||
Self::packet_into_char_brightness(&packet)
|
|
||||||
}
|
|
||||||
CommandCode::Utf8Data => Self::packet_into_utf8(&packet),
|
|
||||||
#[allow(deprecated)]
|
|
||||||
CommandCode::BitmapLegacy => Ok(Command::BitmapLegacy),
|
|
||||||
CommandCode::BitmapLinear => {
|
|
||||||
let (vec, compression) =
|
|
||||||
Self::packet_into_linear_bitmap(packet)?;
|
|
||||||
Ok(Command::BitmapLinear(a as Offset, vec, compression))
|
|
||||||
}
|
|
||||||
CommandCode::BitmapLinearAnd => {
|
|
||||||
let (vec, compression) =
|
|
||||||
Self::packet_into_linear_bitmap(packet)?;
|
|
||||||
Ok(Command::BitmapLinearAnd(a as Offset, vec, compression))
|
|
||||||
}
|
|
||||||
CommandCode::BitmapLinearOr => {
|
|
||||||
let (vec, compression) =
|
|
||||||
Self::packet_into_linear_bitmap(packet)?;
|
|
||||||
Ok(Command::BitmapLinearOr(a as Offset, vec, compression))
|
|
||||||
}
|
|
||||||
CommandCode::BitmapLinearXor => {
|
|
||||||
let (vec, compression) =
|
|
||||||
Self::packet_into_linear_bitmap(packet)?;
|
|
||||||
Ok(Command::BitmapLinearXor(a as Offset, vec, compression))
|
|
||||||
}
|
|
||||||
CommandCode::BitmapLinearWinUncompressed => {
|
|
||||||
Self::packet_into_bitmap_win(
|
|
||||||
packet,
|
|
||||||
CompressionCode::Uncompressed,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
#[cfg(feature = "compression_zlib")]
|
|
||||||
CommandCode::BitmapLinearWinZlib => {
|
|
||||||
Self::packet_into_bitmap_win(packet, CompressionCode::Zlib)
|
|
||||||
}
|
|
||||||
#[cfg(feature = "compression_bzip2")]
|
|
||||||
CommandCode::BitmapLinearWinBzip2 => {
|
|
||||||
Self::packet_into_bitmap_win(packet, CompressionCode::Bzip2)
|
|
||||||
}
|
|
||||||
#[cfg(feature = "compression_lzma")]
|
|
||||||
CommandCode::BitmapLinearWinLzma => {
|
|
||||||
Self::packet_into_bitmap_win(packet, CompressionCode::Lzma)
|
|
||||||
}
|
|
||||||
#[cfg(feature = "compression_zstd")]
|
|
||||||
CommandCode::BitmapLinearWinZstd => {
|
|
||||||
Self::packet_into_bitmap_win(packet, CompressionCode::Zstd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Command {
|
|
||||||
fn packet_into_bitmap_win(
|
|
||||||
packet: Packet,
|
|
||||||
compression: CompressionCode,
|
|
||||||
) -> Result<Command, TryFromPacketError> {
|
|
||||||
let Packet {
|
|
||||||
header:
|
|
||||||
Header {
|
|
||||||
command_code: _,
|
|
||||||
a: tiles_x,
|
|
||||||
b: pixels_y,
|
|
||||||
c: tile_w,
|
|
||||||
d: pixel_h,
|
|
||||||
},
|
|
||||||
payload,
|
|
||||||
} = packet;
|
|
||||||
|
|
||||||
let payload = match into_decompressed(compression, payload) {
|
|
||||||
None => return Err(TryFromPacketError::DecompressionFailed),
|
|
||||||
Some(decompressed) => decompressed,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Command::BitmapLinearWin(
|
|
||||||
Origin::new(tiles_x as usize * TILE_SIZE, pixels_y as usize),
|
|
||||||
Bitmap::load(
|
|
||||||
tile_w as usize * TILE_SIZE,
|
|
||||||
pixel_h as usize,
|
|
||||||
&payload,
|
|
||||||
),
|
|
||||||
compression,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper method for checking that a packet is empty and only contains a command code
|
|
||||||
fn packet_into_command_only(
|
|
||||||
packet: Packet,
|
|
||||||
command: Command,
|
|
||||||
) -> Result<Command, TryFromPacketError> {
|
|
||||||
let Packet {
|
|
||||||
header:
|
|
||||||
Header {
|
|
||||||
command_code: _,
|
|
||||||
a,
|
|
||||||
b,
|
|
||||||
c,
|
|
||||||
d,
|
|
||||||
},
|
|
||||||
payload,
|
|
||||||
} = packet;
|
|
||||||
if !payload.is_empty() {
|
|
||||||
Err(TryFromPacketError::UnexpectedPayloadSize(0, payload.len()))
|
|
||||||
} else if a != 0 || b != 0 || c != 0 || d != 0 {
|
|
||||||
Err(TryFromPacketError::ExtraneousHeaderValues)
|
|
||||||
} else {
|
|
||||||
Ok(command)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper method for Packets into `BitmapLinear*`-Commands
|
|
||||||
fn packet_into_linear_bitmap(
|
|
||||||
packet: Packet,
|
|
||||||
) -> Result<(BitVec, CompressionCode), TryFromPacketError> {
|
|
||||||
let Packet {
|
|
||||||
header:
|
|
||||||
Header {
|
|
||||||
b: length,
|
|
||||||
c: sub,
|
|
||||||
d: reserved,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
payload,
|
|
||||||
} = packet;
|
|
||||||
if reserved != 0 {
|
|
||||||
return Err(TryFromPacketError::ExtraneousHeaderValues);
|
|
||||||
}
|
|
||||||
let sub = match CompressionCode::try_from(sub) {
|
|
||||||
Err(()) => {
|
|
||||||
return Err(TryFromPacketError::InvalidCompressionCode(sub));
|
|
||||||
}
|
|
||||||
Ok(value) => value,
|
|
||||||
};
|
|
||||||
let payload = match into_decompressed(sub, payload) {
|
|
||||||
None => return Err(TryFromPacketError::DecompressionFailed),
|
|
||||||
Some(value) => value,
|
|
||||||
};
|
|
||||||
if payload.len() != length as usize {
|
|
||||||
return Err(TryFromPacketError::UnexpectedPayloadSize(
|
|
||||||
length as usize,
|
|
||||||
payload.len(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
Ok((BitVec::from_vec(payload), sub))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn packet_into_char_brightness(
|
|
||||||
packet: &Packet,
|
|
||||||
) -> Result<Command, TryFromPacketError> {
|
|
||||||
let Packet {
|
|
||||||
header:
|
|
||||||
Header {
|
|
||||||
command_code: _,
|
|
||||||
a: x,
|
|
||||||
b: y,
|
|
||||||
c: width,
|
|
||||||
d: height,
|
|
||||||
},
|
|
||||||
payload,
|
|
||||||
} = packet;
|
|
||||||
|
|
||||||
let grid = ByteGrid::load(*width as usize, *height as usize, payload);
|
|
||||||
let grid = match BrightnessGrid::try_from(grid) {
|
|
||||||
Ok(grid) => grid,
|
|
||||||
Err(val) => return Err(TryFromPacketError::InvalidBrightness(val)),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Command::CharBrightness(
|
|
||||||
Origin::new(*x as usize, *y as usize),
|
|
||||||
grid,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn packet_into_brightness(
|
|
||||||
packet: &Packet,
|
|
||||||
) -> Result<Command, TryFromPacketError> {
|
|
||||||
let Packet {
|
|
||||||
header:
|
|
||||||
Header {
|
|
||||||
command_code: _,
|
|
||||||
a,
|
|
||||||
b,
|
|
||||||
c,
|
|
||||||
d,
|
|
||||||
},
|
|
||||||
payload,
|
|
||||||
} = packet;
|
|
||||||
if payload.len() != 1 {
|
|
||||||
return Err(TryFromPacketError::UnexpectedPayloadSize(
|
|
||||||
1,
|
|
||||||
payload.len(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if *a != 0 || *b != 0 || *c != 0 || *d != 0 {
|
|
||||||
return Err(TryFromPacketError::ExtraneousHeaderValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
match Brightness::try_from(payload[0]) {
|
|
||||||
Ok(b) => Ok(Command::Brightness(b)),
|
|
||||||
Err(_) => Err(TryFromPacketError::InvalidBrightness(payload[0])),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn packet_into_cp437(
|
|
||||||
packet: &Packet,
|
|
||||||
) -> Result<Command, TryFromPacketError> {
|
|
||||||
let Packet {
|
|
||||||
header:
|
|
||||||
Header {
|
|
||||||
command_code: _,
|
|
||||||
a,
|
|
||||||
b,
|
|
||||||
c,
|
|
||||||
d,
|
|
||||||
},
|
|
||||||
payload,
|
|
||||||
} = packet;
|
|
||||||
Ok(Command::Cp437Data(
|
|
||||||
Origin::new(*a as usize, *b as usize),
|
|
||||||
Cp437Grid::load(*c as usize, *d as usize, payload),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn packet_into_utf8(
|
|
||||||
packet: &Packet,
|
|
||||||
) -> Result<Command, TryFromPacketError> {
|
|
||||||
let Packet {
|
|
||||||
header:
|
|
||||||
Header {
|
|
||||||
command_code: _,
|
|
||||||
a,
|
|
||||||
b,
|
|
||||||
c,
|
|
||||||
d,
|
|
||||||
},
|
|
||||||
payload,
|
|
||||||
} = packet;
|
|
||||||
let payload: Vec<_> =
|
|
||||||
String::from_utf8(payload.clone())?.chars().collect();
|
|
||||||
Ok(Command::Utf8Data(
|
|
||||||
Origin::new(*a as usize, *b as usize),
|
|
||||||
CharGrid::load(*c as usize, *d as usize, &payload),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use crate::command::TryFromPacketError;
|
|
||||||
use crate::command_code::CommandCode;
|
|
||||||
use crate::{
|
|
||||||
BitVec, Bitmap, Brightness, BrightnessGrid, CharGrid, Command,
|
|
||||||
CompressionCode, Cp437Grid, Header, Origin, Packet, Pixels,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn round_trip(original: Command) {
|
|
||||||
let packet: Packet = original.clone().into();
|
|
||||||
let copy: Command = match Command::try_from(packet) {
|
|
||||||
Ok(command) => command,
|
|
||||||
Err(err) => panic!("could not reload {original:?}: {err:?}"),
|
|
||||||
};
|
|
||||||
assert_eq!(copy, original);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn all_compressions<'t>() -> &'t [CompressionCode] {
|
|
||||||
&[
|
|
||||||
CompressionCode::Uncompressed,
|
|
||||||
#[cfg(feature = "compression_lzma")]
|
|
||||||
CompressionCode::Lzma,
|
|
||||||
#[cfg(feature = "compression_bzip2")]
|
|
||||||
CompressionCode::Bzip2,
|
|
||||||
#[cfg(feature = "compression_zlib")]
|
|
||||||
CompressionCode::Zlib,
|
|
||||||
#[cfg(feature = "compression_zstd")]
|
|
||||||
CompressionCode::Zstd,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn round_trip_clear() {
|
|
||||||
round_trip(Command::Clear);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn round_trip_hard_reset() {
|
|
||||||
round_trip(Command::HardReset);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn round_trip_fade_out() {
|
|
||||||
round_trip(Command::FadeOut);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn round_trip_brightness() {
|
|
||||||
round_trip(Command::Brightness(Brightness::try_from(6).unwrap()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[allow(deprecated)]
|
|
||||||
fn round_trip_bitmap_legacy() {
|
|
||||||
round_trip(Command::BitmapLegacy);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn round_trip_char_brightness() {
|
|
||||||
round_trip(Command::CharBrightness(
|
|
||||||
Origin::new(5, 2),
|
|
||||||
BrightnessGrid::new(7, 5),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn round_trip_cp437_data() {
|
|
||||||
round_trip(Command::Cp437Data(Origin::new(5, 2), Cp437Grid::new(7, 5)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn round_trip_utf8_data() {
|
|
||||||
round_trip(Command::Utf8Data(Origin::new(5, 2), CharGrid::new(7, 5)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn round_trip_bitmap_linear() {
|
|
||||||
for compression in all_compressions().iter().copied() {
|
|
||||||
round_trip(Command::BitmapLinear(
|
|
||||||
23,
|
|
||||||
BitVec::repeat(false, 40),
|
|
||||||
compression,
|
|
||||||
));
|
|
||||||
round_trip(Command::BitmapLinearAnd(
|
|
||||||
23,
|
|
||||||
BitVec::repeat(false, 40),
|
|
||||||
compression,
|
|
||||||
));
|
|
||||||
round_trip(Command::BitmapLinearOr(
|
|
||||||
23,
|
|
||||||
BitVec::repeat(false, 40),
|
|
||||||
compression,
|
|
||||||
));
|
|
||||||
round_trip(Command::BitmapLinearXor(
|
|
||||||
23,
|
|
||||||
BitVec::repeat(false, 40),
|
|
||||||
compression,
|
|
||||||
));
|
|
||||||
round_trip(Command::BitmapLinearWin(
|
|
||||||
Origin::ZERO,
|
|
||||||
Bitmap::max_sized(),
|
|
||||||
compression,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn error_invalid_command() {
|
|
||||||
let p = Packet {
|
|
||||||
header: Header {
|
|
||||||
command_code: 0xFF,
|
|
||||||
a: 0x00,
|
|
||||||
b: 0x00,
|
|
||||||
c: 0x00,
|
|
||||||
d: 0x00,
|
|
||||||
},
|
|
||||||
payload: vec![],
|
|
||||||
};
|
|
||||||
let result = Command::try_from(p);
|
|
||||||
assert!(matches!(
|
|
||||||
result,
|
|
||||||
Err(TryFromPacketError::InvalidCommand(0xFF))
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn error_extraneous_header_values_clear() {
|
|
||||||
let p = Packet {
|
|
||||||
header: Header {
|
|
||||||
command_code: CommandCode::Clear.into(),
|
|
||||||
a: 0x05,
|
|
||||||
b: 0x00,
|
|
||||||
c: 0x00,
|
|
||||||
d: 0x00,
|
|
||||||
},
|
|
||||||
payload: vec![],
|
|
||||||
};
|
|
||||||
let result = Command::try_from(p);
|
|
||||||
assert!(matches!(
|
|
||||||
result,
|
|
||||||
Err(TryFromPacketError::ExtraneousHeaderValues)
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn error_extraneous_header_values_brightness() {
|
|
||||||
let p = Packet {
|
|
||||||
header: Header {
|
|
||||||
command_code: CommandCode::Brightness.into(),
|
|
||||||
a: 0x00,
|
|
||||||
b: 0x13,
|
|
||||||
c: 0x37,
|
|
||||||
d: 0x00,
|
|
||||||
},
|
|
||||||
payload: vec![5],
|
|
||||||
};
|
|
||||||
let result = Command::try_from(p);
|
|
||||||
assert!(matches!(
|
|
||||||
result,
|
|
||||||
Err(TryFromPacketError::ExtraneousHeaderValues)
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn error_extraneous_header_hard_reset() {
|
|
||||||
let p = Packet {
|
|
||||||
header: Header {
|
|
||||||
command_code: CommandCode::HardReset.into(),
|
|
||||||
a: 0x00,
|
|
||||||
b: 0x00,
|
|
||||||
c: 0x00,
|
|
||||||
d: 0x01,
|
|
||||||
},
|
|
||||||
payload: vec![],
|
|
||||||
};
|
|
||||||
let result = Command::try_from(p);
|
|
||||||
assert!(matches!(
|
|
||||||
result,
|
|
||||||
Err(TryFromPacketError::ExtraneousHeaderValues)
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn error_extraneous_header_fade_out() {
|
|
||||||
let p = Packet {
|
|
||||||
header: Header {
|
|
||||||
command_code: CommandCode::FadeOut.into(),
|
|
||||||
a: 0x10,
|
|
||||||
b: 0x00,
|
|
||||||
c: 0x00,
|
|
||||||
d: 0x01,
|
|
||||||
},
|
|
||||||
payload: vec![],
|
|
||||||
};
|
|
||||||
let result = Command::try_from(p);
|
|
||||||
assert!(matches!(
|
|
||||||
result,
|
|
||||||
Err(TryFromPacketError::ExtraneousHeaderValues)
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn error_unexpected_payload() {
|
|
||||||
let p = Packet {
|
|
||||||
header: Header {
|
|
||||||
command_code: CommandCode::FadeOut.into(),
|
|
||||||
a: 0x00,
|
|
||||||
b: 0x00,
|
|
||||||
c: 0x00,
|
|
||||||
d: 0x00,
|
|
||||||
},
|
|
||||||
payload: vec![5, 7],
|
|
||||||
};
|
|
||||||
let result = Command::try_from(p);
|
|
||||||
assert!(matches!(
|
|
||||||
result,
|
|
||||||
Err(TryFromPacketError::UnexpectedPayloadSize(0, 2))
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn error_decompression_failed_win() {
|
|
||||||
for compression in all_compressions().iter().copied() {
|
|
||||||
let p: Packet = Command::BitmapLinearWin(
|
|
||||||
Origin::new(16, 8),
|
|
||||||
Bitmap::new(8, 8),
|
|
||||||
compression,
|
|
||||||
)
|
|
||||||
.into();
|
|
||||||
|
|
||||||
let Packet {
|
|
||||||
header,
|
|
||||||
mut payload,
|
|
||||||
} = p;
|
|
||||||
|
|
||||||
// mangle it
|
|
||||||
for byte in payload.iter_mut() {
|
|
||||||
*byte -= *byte / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
let p = Packet { header, payload };
|
|
||||||
let result = Command::try_from(p);
|
|
||||||
if compression != CompressionCode::Uncompressed {
|
|
||||||
assert_eq!(result, Err(TryFromPacketError::DecompressionFailed))
|
|
||||||
} else {
|
|
||||||
assert!(result.is_ok());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn error_decompression_failed_and() {
|
|
||||||
for compression in all_compressions().iter().copied() {
|
|
||||||
let p: Packet = Command::BitmapLinearAnd(
|
|
||||||
0,
|
|
||||||
BitVec::repeat(false, 8),
|
|
||||||
compression,
|
|
||||||
)
|
|
||||||
.into();
|
|
||||||
let Packet {
|
|
||||||
header,
|
|
||||||
mut payload,
|
|
||||||
} = p;
|
|
||||||
|
|
||||||
// mangle it
|
|
||||||
for byte in payload.iter_mut() {
|
|
||||||
*byte -= *byte / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
let p = Packet { header, payload };
|
|
||||||
let result = Command::try_from(p);
|
|
||||||
if compression != CompressionCode::Uncompressed {
|
|
||||||
assert_eq!(result, Err(TryFromPacketError::DecompressionFailed))
|
|
||||||
} else {
|
|
||||||
// when not compressing, there is no way to detect corrupted data
|
|
||||||
assert!(result.is_ok());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn unexpected_payload_size_brightness() {
|
|
||||||
assert_eq!(
|
|
||||||
Command::try_from(Packet {
|
|
||||||
header: Header {
|
|
||||||
command_code: CommandCode::Brightness.into(),
|
|
||||||
a: 0,
|
|
||||||
b: 0,
|
|
||||||
c: 0,
|
|
||||||
d: 0,
|
|
||||||
},
|
|
||||||
payload: vec!()
|
|
||||||
}),
|
|
||||||
Err(TryFromPacketError::UnexpectedPayloadSize(1, 0))
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
Command::try_from(Packet {
|
|
||||||
header: Header {
|
|
||||||
command_code: CommandCode::Brightness.into(),
|
|
||||||
a: 0,
|
|
||||||
b: 0,
|
|
||||||
c: 0,
|
|
||||||
d: 0,
|
|
||||||
},
|
|
||||||
payload: vec!(0, 0)
|
|
||||||
}),
|
|
||||||
Err(TryFromPacketError::UnexpectedPayloadSize(1, 2))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn error_reserved_used() {
|
|
||||||
let Packet { header, payload } = Command::BitmapLinear(
|
|
||||||
0,
|
|
||||||
BitVec::repeat(false, 8),
|
|
||||||
CompressionCode::Uncompressed,
|
|
||||||
)
|
|
||||||
.into();
|
|
||||||
let Header {
|
|
||||||
command_code: command,
|
|
||||||
a: offset,
|
|
||||||
b: length,
|
|
||||||
c: sub,
|
|
||||||
d: _reserved,
|
|
||||||
} = header;
|
|
||||||
let p = Packet {
|
|
||||||
header: Header {
|
|
||||||
command_code: command,
|
|
||||||
a: offset,
|
|
||||||
b: length,
|
|
||||||
c: sub,
|
|
||||||
d: 69,
|
|
||||||
},
|
|
||||||
payload,
|
|
||||||
};
|
|
||||||
assert_eq!(
|
|
||||||
Command::try_from(p),
|
|
||||||
Err(TryFromPacketError::ExtraneousHeaderValues)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn error_invalid_compression() {
|
|
||||||
let Packet { header, payload } = Command::BitmapLinear(
|
|
||||||
0,
|
|
||||||
BitVec::repeat(false, 8),
|
|
||||||
CompressionCode::Uncompressed,
|
|
||||||
)
|
|
||||||
.into();
|
|
||||||
let Header {
|
|
||||||
command_code: command,
|
|
||||||
a: offset,
|
|
||||||
b: length,
|
|
||||||
c: _sub,
|
|
||||||
d: reserved,
|
|
||||||
} = header;
|
|
||||||
let p = Packet {
|
|
||||||
header: Header {
|
|
||||||
command_code: command,
|
|
||||||
a: offset,
|
|
||||||
b: length,
|
|
||||||
c: 42,
|
|
||||||
d: reserved,
|
|
||||||
},
|
|
||||||
payload,
|
|
||||||
};
|
|
||||||
assert_eq!(
|
|
||||||
Command::try_from(p),
|
|
||||||
Err(TryFromPacketError::InvalidCompressionCode(42))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn error_unexpected_size() {
|
|
||||||
let Packet { header, payload } = Command::BitmapLinear(
|
|
||||||
0,
|
|
||||||
BitVec::repeat(false, 8),
|
|
||||||
CompressionCode::Uncompressed,
|
|
||||||
)
|
|
||||||
.into();
|
|
||||||
let Header {
|
|
||||||
command_code: command,
|
|
||||||
a: offset,
|
|
||||||
b: length,
|
|
||||||
c: compression,
|
|
||||||
d: reserved,
|
|
||||||
} = header;
|
|
||||||
let p = Packet {
|
|
||||||
header: Header {
|
|
||||||
command_code: command,
|
|
||||||
a: offset,
|
|
||||||
b: 420,
|
|
||||||
c: compression,
|
|
||||||
d: reserved,
|
|
||||||
},
|
|
||||||
payload,
|
|
||||||
};
|
|
||||||
assert_eq!(
|
|
||||||
Command::try_from(p),
|
|
||||||
Err(TryFromPacketError::UnexpectedPayloadSize(
|
|
||||||
420,
|
|
||||||
length as usize,
|
|
||||||
))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn origin_add() {
|
|
||||||
assert_eq!(
|
|
||||||
Origin::<Pixels>::new(4, 2),
|
|
||||||
Origin::new(1, 0) + Origin::new(3, 2)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn packet_into_char_brightness_invalid() {
|
|
||||||
let grid = BrightnessGrid::new(2, 2);
|
|
||||||
let command = Command::CharBrightness(Origin::ZERO, grid);
|
|
||||||
let mut packet: Packet = command.into();
|
|
||||||
let slot = packet.payload.get_mut(1).unwrap();
|
|
||||||
*slot = 23;
|
|
||||||
assert_eq!(
|
|
||||||
Command::try_from(packet),
|
|
||||||
Err(TryFromPacketError::InvalidBrightness(23))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn packet_into_brightness_invalid() {
|
|
||||||
let mut packet: Packet = Command::Brightness(Brightness::MAX).into();
|
|
||||||
let slot = packet.payload.get_mut(0).unwrap();
|
|
||||||
*slot = 42;
|
|
||||||
assert_eq!(
|
|
||||||
Command::try_from(packet),
|
|
||||||
Err(TryFromPacketError::InvalidBrightness(42))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
51
src/command/bitmap_legacy.rs
Normal file
51
src/command/bitmap_legacy.rs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
use crate::{
|
||||||
|
command::check_command_code_only, command::TryFromPacketError,
|
||||||
|
command_code::CommandCode, Packet, TypedCommand,
|
||||||
|
};
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
/// Legacy command code, gets ignored by the real display.
|
||||||
|
///
|
||||||
|
/// Might be useful as a noop package.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use servicepoint::*;
|
||||||
|
/// # let connection = connection::Fake;
|
||||||
|
/// // this sends a packet that does nothing
|
||||||
|
/// # #[allow(deprecated)]
|
||||||
|
/// connection.send(command::BitmapLegacy).unwrap();
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
#[deprecated]
|
||||||
|
pub struct BitmapLegacy;
|
||||||
|
|
||||||
|
#[allow(deprecated)]
|
||||||
|
impl TryFrom<Packet> for BitmapLegacy {
|
||||||
|
type Error = TryFromPacketError;
|
||||||
|
|
||||||
|
fn try_from(value: Packet) -> Result<Self, Self::Error> {
|
||||||
|
if let Some(e) =
|
||||||
|
check_command_code_only(value, CommandCode::BitmapLegacy)
|
||||||
|
{
|
||||||
|
Err(e)
|
||||||
|
} else {
|
||||||
|
Ok(Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(deprecated)]
|
||||||
|
impl From<BitmapLegacy> for Packet {
|
||||||
|
fn from(_: BitmapLegacy) -> Self {
|
||||||
|
Packet::command_code_only(CommandCode::BitmapLegacy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(deprecated)]
|
||||||
|
impl From<BitmapLegacy> for TypedCommand {
|
||||||
|
fn from(command: BitmapLegacy) -> Self {
|
||||||
|
Self::BitmapLegacy(command)
|
||||||
|
}
|
||||||
|
}
|
91
src/command/bitmap_linear.rs
Normal file
91
src/command/bitmap_linear.rs
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
use crate::{
|
||||||
|
command::TryFromPacketError, command_code::CommandCode,
|
||||||
|
compression::into_decompressed, BitVec, CompressionCode, Header, Offset,
|
||||||
|
Packet, TypedCommand,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Set pixel data starting at the pixel offset on screen.
|
||||||
|
///
|
||||||
|
/// The screen will continuously overwrite more pixel data without regarding the offset, meaning
|
||||||
|
/// once the starting row is full, overwriting will continue on column 0.
|
||||||
|
///
|
||||||
|
/// The contained [BitVec] is always uncompressed.
|
||||||
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
|
pub struct BitmapLinear {
|
||||||
|
/// where to start overwriting pixel data
|
||||||
|
pub offset: Offset,
|
||||||
|
/// the pixels to send to the display as one long row
|
||||||
|
pub bitvec: BitVec,
|
||||||
|
/// how to compress the command when converting to packet
|
||||||
|
pub compression: CompressionCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BitmapLinear> for Packet {
|
||||||
|
fn from(bitmap: BitmapLinear) -> Self {
|
||||||
|
Packet::bitmap_linear_into_packet(
|
||||||
|
CommandCode::BitmapLinear,
|
||||||
|
bitmap.offset,
|
||||||
|
bitmap.compression,
|
||||||
|
bitmap.bitvec.into(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Packet> for BitmapLinear {
|
||||||
|
type Error = TryFromPacketError;
|
||||||
|
|
||||||
|
fn try_from(packet: Packet) -> Result<Self, Self::Error> {
|
||||||
|
let (offset, bitvec, compression) =
|
||||||
|
Self::packet_into_linear_bitmap(packet)?;
|
||||||
|
Ok(Self {
|
||||||
|
offset,
|
||||||
|
bitvec,
|
||||||
|
compression,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BitmapLinear> for TypedCommand {
|
||||||
|
fn from(command: BitmapLinear) -> Self {
|
||||||
|
Self::BitmapLinear(command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BitmapLinear {
|
||||||
|
/// Helper method for Packets into `BitmapLinear*`-Commands
|
||||||
|
pub(crate) fn packet_into_linear_bitmap(
|
||||||
|
packet: Packet,
|
||||||
|
) -> Result<(Offset, BitVec, CompressionCode), TryFromPacketError> {
|
||||||
|
let Packet {
|
||||||
|
header:
|
||||||
|
Header {
|
||||||
|
a: offset,
|
||||||
|
b: length,
|
||||||
|
c: sub,
|
||||||
|
d: reserved,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
payload,
|
||||||
|
} = packet;
|
||||||
|
if reserved != 0 {
|
||||||
|
return Err(TryFromPacketError::ExtraneousHeaderValues);
|
||||||
|
}
|
||||||
|
let sub = match CompressionCode::try_from(sub) {
|
||||||
|
Err(()) => {
|
||||||
|
return Err(TryFromPacketError::InvalidCompressionCode(sub));
|
||||||
|
}
|
||||||
|
Ok(value) => value,
|
||||||
|
};
|
||||||
|
let payload = match into_decompressed(sub, payload) {
|
||||||
|
None => return Err(TryFromPacketError::DecompressionFailed),
|
||||||
|
Some(value) => value,
|
||||||
|
};
|
||||||
|
if payload.len() != length as usize {
|
||||||
|
return Err(TryFromPacketError::UnexpectedPayloadSize(
|
||||||
|
length as usize,
|
||||||
|
payload.len(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok((offset as Offset, BitVec::from_vec(payload), sub))
|
||||||
|
}
|
||||||
|
}
|
52
src/command/bitmap_linear_and.rs
Normal file
52
src/command/bitmap_linear_and.rs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
use crate::{
|
||||||
|
command::{BitmapLinear, TryFromPacketError},
|
||||||
|
command_code::CommandCode,
|
||||||
|
BitVec, CompressionCode, Offset, Packet, TypedCommand,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Set pixel data according to an and-mask starting at the offset.
|
||||||
|
///
|
||||||
|
/// The screen will continuously overwrite more pixel data without regarding the offset, meaning
|
||||||
|
/// once the starting row is full, overwriting will continue on column 0.
|
||||||
|
///
|
||||||
|
/// The contained [BitVec] is always uncompressed.
|
||||||
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
|
pub struct BitmapLinearAnd {
|
||||||
|
/// where to start overwriting pixel data
|
||||||
|
pub offset: Offset,
|
||||||
|
/// the pixels to send to the display as one long row
|
||||||
|
pub bitvec: BitVec,
|
||||||
|
/// how to compress the command when converting to packet
|
||||||
|
pub compression: CompressionCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Packet> for BitmapLinearAnd {
|
||||||
|
type Error = TryFromPacketError;
|
||||||
|
|
||||||
|
fn try_from(packet: Packet) -> Result<Self, Self::Error> {
|
||||||
|
let (offset, bitvec, compression) =
|
||||||
|
BitmapLinear::packet_into_linear_bitmap(packet)?;
|
||||||
|
Ok(Self {
|
||||||
|
offset,
|
||||||
|
bitvec,
|
||||||
|
compression,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BitmapLinearAnd> for Packet {
|
||||||
|
fn from(bitmap: BitmapLinearAnd) -> Self {
|
||||||
|
Packet::bitmap_linear_into_packet(
|
||||||
|
CommandCode::BitmapLinearAnd,
|
||||||
|
bitmap.offset,
|
||||||
|
bitmap.compression,
|
||||||
|
bitmap.bitvec.into(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BitmapLinearAnd> for TypedCommand {
|
||||||
|
fn from(command: BitmapLinearAnd) -> Self {
|
||||||
|
Self::BitmapLinearAnd(command)
|
||||||
|
}
|
||||||
|
}
|
52
src/command/bitmap_linear_or.rs
Normal file
52
src/command/bitmap_linear_or.rs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
use crate::{
|
||||||
|
command::{BitmapLinear, TryFromPacketError},
|
||||||
|
command_code::CommandCode,
|
||||||
|
BitVec, CompressionCode, Offset, Packet, TypedCommand,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Set pixel data according to an or-mask starting at the offset.
|
||||||
|
///
|
||||||
|
/// The screen will continuously overwrite more pixel data without regarding the offset, meaning
|
||||||
|
/// once the starting row is full, overwriting will continue on column 0.
|
||||||
|
///
|
||||||
|
/// The contained [BitVec] is always uncompressed.
|
||||||
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
|
pub struct BitmapLinearOr {
|
||||||
|
/// where to start overwriting pixel data
|
||||||
|
pub offset: Offset,
|
||||||
|
/// the pixels to send to the display as one long row
|
||||||
|
pub bitvec: BitVec,
|
||||||
|
/// how to compress the command when converting to packet
|
||||||
|
pub compression: CompressionCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Packet> for BitmapLinearOr {
|
||||||
|
type Error = TryFromPacketError;
|
||||||
|
|
||||||
|
fn try_from(packet: Packet) -> Result<Self, Self::Error> {
|
||||||
|
let (offset, bitvec, compression) =
|
||||||
|
BitmapLinear::packet_into_linear_bitmap(packet)?;
|
||||||
|
Ok(Self {
|
||||||
|
offset,
|
||||||
|
bitvec,
|
||||||
|
compression,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BitmapLinearOr> for Packet {
|
||||||
|
fn from(bitmap: BitmapLinearOr) -> Self {
|
||||||
|
Packet::bitmap_linear_into_packet(
|
||||||
|
CommandCode::BitmapLinearOr,
|
||||||
|
bitmap.offset,
|
||||||
|
bitmap.compression,
|
||||||
|
bitmap.bitvec.into(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BitmapLinearOr> for TypedCommand {
|
||||||
|
fn from(command: BitmapLinearOr) -> Self {
|
||||||
|
Self::BitmapLinearOr(command)
|
||||||
|
}
|
||||||
|
}
|
157
src/command/bitmap_linear_win.rs
Normal file
157
src/command/bitmap_linear_win.rs
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
use crate::{
|
||||||
|
command::TryFromPacketError, command_code::CommandCode,
|
||||||
|
compression::into_compressed, compression::into_decompressed, Bitmap,
|
||||||
|
CompressionCode, Grid, Header, Origin, Packet, Pixels, TypedCommand,
|
||||||
|
TILE_SIZE,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Overwrites a rectangular region of pixels.
|
||||||
|
///
|
||||||
|
/// Origin coordinates must be divisible by 8.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use servicepoint::*;
|
||||||
|
/// # let connection = connection::Fake;
|
||||||
|
/// #
|
||||||
|
/// let mut bitmap = Bitmap::max_sized();
|
||||||
|
/// // draw something to the pixels here
|
||||||
|
/// # bitmap.set(2, 5, true);
|
||||||
|
///
|
||||||
|
/// // create command to send pixels
|
||||||
|
/// let command = command::BitmapLinearWin {
|
||||||
|
/// bitmap,
|
||||||
|
/// origin: Origin::ZERO,
|
||||||
|
/// compression: CompressionCode::Uncompressed
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// connection.send(command).expect("send failed");
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct BitmapLinearWin {
|
||||||
|
/// where to start drawing the pixels
|
||||||
|
pub origin: Origin<Pixels>,
|
||||||
|
/// the pixels to send
|
||||||
|
pub bitmap: Bitmap,
|
||||||
|
/// how to compress the command when converting to packet
|
||||||
|
pub compression: CompressionCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BitmapLinearWin> for Packet {
|
||||||
|
fn from(value: BitmapLinearWin) -> Self {
|
||||||
|
assert_eq!(value.origin.x % 8, 0);
|
||||||
|
assert_eq!(value.bitmap.width() % 8, 0);
|
||||||
|
|
||||||
|
let tile_x = (value.origin.x / TILE_SIZE) as u16;
|
||||||
|
let tile_w = (value.bitmap.width() / TILE_SIZE) as u16;
|
||||||
|
let pixel_h = value.bitmap.height() as u16;
|
||||||
|
let payload = into_compressed(value.compression, value.bitmap.into());
|
||||||
|
let command = match value.compression {
|
||||||
|
CompressionCode::Uncompressed => {
|
||||||
|
CommandCode::BitmapLinearWinUncompressed
|
||||||
|
}
|
||||||
|
#[cfg(feature = "compression_zlib")]
|
||||||
|
CompressionCode::Zlib => CommandCode::BitmapLinearWinZlib,
|
||||||
|
#[cfg(feature = "compression_bzip2")]
|
||||||
|
CompressionCode::Bzip2 => CommandCode::BitmapLinearWinBzip2,
|
||||||
|
#[cfg(feature = "compression_lzma")]
|
||||||
|
CompressionCode::Lzma => CommandCode::BitmapLinearWinLzma,
|
||||||
|
#[cfg(feature = "compression_zstd")]
|
||||||
|
CompressionCode::Zstd => CommandCode::BitmapLinearWinZstd,
|
||||||
|
};
|
||||||
|
|
||||||
|
Packet {
|
||||||
|
header: Header {
|
||||||
|
command_code: command.into(),
|
||||||
|
a: tile_x,
|
||||||
|
b: value.origin.y as u16,
|
||||||
|
c: tile_w,
|
||||||
|
d: pixel_h,
|
||||||
|
},
|
||||||
|
payload,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Packet> for BitmapLinearWin {
|
||||||
|
type Error = TryFromPacketError;
|
||||||
|
|
||||||
|
fn try_from(packet: Packet) -> Result<Self, Self::Error> {
|
||||||
|
let code = CommandCode::try_from(packet.header.command_code).map_err(
|
||||||
|
|_| TryFromPacketError::InvalidCommand(packet.header.command_code),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
match code {
|
||||||
|
CommandCode::BitmapLinearWinUncompressed => {
|
||||||
|
Self::packet_into_bitmap_win(
|
||||||
|
packet,
|
||||||
|
CompressionCode::Uncompressed,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "compression_zlib")]
|
||||||
|
CommandCode::BitmapLinearWinZlib => {
|
||||||
|
Self::packet_into_bitmap_win(packet, CompressionCode::Zlib)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "compression_bzip2")]
|
||||||
|
CommandCode::BitmapLinearWinBzip2 => {
|
||||||
|
Self::packet_into_bitmap_win(packet, CompressionCode::Bzip2)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "compression_lzma")]
|
||||||
|
CommandCode::BitmapLinearWinLzma => {
|
||||||
|
Self::packet_into_bitmap_win(packet, CompressionCode::Lzma)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "compression_zstd")]
|
||||||
|
CommandCode::BitmapLinearWinZstd => {
|
||||||
|
Self::packet_into_bitmap_win(packet, CompressionCode::Zstd)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => Err(TryFromPacketError::InvalidCommand(
|
||||||
|
packet.header.command_code,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BitmapLinearWin> for TypedCommand {
|
||||||
|
fn from(command: BitmapLinearWin) -> Self {
|
||||||
|
Self::BitmapLinearWin(command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BitmapLinearWin {
|
||||||
|
fn packet_into_bitmap_win(
|
||||||
|
packet: Packet,
|
||||||
|
compression: CompressionCode,
|
||||||
|
) -> Result<Self, TryFromPacketError> {
|
||||||
|
let Packet {
|
||||||
|
header:
|
||||||
|
Header {
|
||||||
|
command_code: _,
|
||||||
|
a: tiles_x,
|
||||||
|
b: pixels_y,
|
||||||
|
c: tile_w,
|
||||||
|
d: pixel_h,
|
||||||
|
},
|
||||||
|
payload,
|
||||||
|
} = packet;
|
||||||
|
|
||||||
|
let payload = match into_decompressed(compression, payload) {
|
||||||
|
None => return Err(TryFromPacketError::DecompressionFailed),
|
||||||
|
Some(decompressed) => decompressed,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
origin: Origin::new(
|
||||||
|
tiles_x as usize * TILE_SIZE,
|
||||||
|
pixels_y as usize,
|
||||||
|
),
|
||||||
|
bitmap: Bitmap::load(
|
||||||
|
tile_w as usize * TILE_SIZE,
|
||||||
|
pixel_h as usize,
|
||||||
|
&payload,
|
||||||
|
),
|
||||||
|
compression,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
52
src/command/bitmap_linear_xor.rs
Normal file
52
src/command/bitmap_linear_xor.rs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
use crate::{
|
||||||
|
command::{BitmapLinear, TryFromPacketError},
|
||||||
|
command_code::CommandCode,
|
||||||
|
BitVec, CompressionCode, Offset, Packet, TypedCommand,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Set pixel data according to a xor-mask starting at the offset.
|
||||||
|
///
|
||||||
|
/// The screen will continuously overwrite more pixel data without regarding the offset, meaning
|
||||||
|
/// once the starting row is full, overwriting will continue on column 0.
|
||||||
|
///
|
||||||
|
/// The contained [BitVec] is always uncompressed.
|
||||||
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
|
pub struct BitmapLinearXor {
|
||||||
|
/// where to start overwriting pixel data
|
||||||
|
pub offset: Offset,
|
||||||
|
/// the pixels to send to the display as one long row
|
||||||
|
pub bitvec: BitVec,
|
||||||
|
/// how to compress the command when converting to packet
|
||||||
|
pub compression: CompressionCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Packet> for BitmapLinearXor {
|
||||||
|
type Error = TryFromPacketError;
|
||||||
|
|
||||||
|
fn try_from(packet: Packet) -> Result<Self, Self::Error> {
|
||||||
|
let (offset, bitvec, compression) =
|
||||||
|
BitmapLinear::packet_into_linear_bitmap(packet)?;
|
||||||
|
Ok(Self {
|
||||||
|
offset,
|
||||||
|
bitvec,
|
||||||
|
compression,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BitmapLinearXor> for Packet {
|
||||||
|
fn from(bitmap: BitmapLinearXor) -> Self {
|
||||||
|
Packet::bitmap_linear_into_packet(
|
||||||
|
CommandCode::BitmapLinearXor,
|
||||||
|
bitmap.offset,
|
||||||
|
bitmap.compression,
|
||||||
|
bitmap.bitvec.into(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BitmapLinearXor> for TypedCommand {
|
||||||
|
fn from(command: BitmapLinearXor) -> Self {
|
||||||
|
Self::BitmapLinearXor(command)
|
||||||
|
}
|
||||||
|
}
|
58
src/command/char_brightness.rs
Normal file
58
src/command/char_brightness.rs
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
use crate::{
|
||||||
|
command::TryFromPacketError, command_code::CommandCode, BrightnessGrid,
|
||||||
|
ByteGrid, Header, Origin, Packet, Tiles, TypedCommand,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Set the brightness of individual tiles in a rectangular area of the display.
|
||||||
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
|
pub struct CharBrightness {
|
||||||
|
/// which tile the brightness rectangle should start
|
||||||
|
pub origin: Origin<Tiles>,
|
||||||
|
/// the brightness values per tile
|
||||||
|
pub grid: BrightnessGrid,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CharBrightness> for Packet {
|
||||||
|
fn from(value: CharBrightness) -> Self {
|
||||||
|
Packet::origin_grid_to_packet(
|
||||||
|
value.origin,
|
||||||
|
value.grid,
|
||||||
|
CommandCode::CharBrightness,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Packet> for CharBrightness {
|
||||||
|
type Error = TryFromPacketError;
|
||||||
|
|
||||||
|
fn try_from(packet: Packet) -> Result<Self, Self::Error> {
|
||||||
|
let Packet {
|
||||||
|
header:
|
||||||
|
Header {
|
||||||
|
command_code: _,
|
||||||
|
a: x,
|
||||||
|
b: y,
|
||||||
|
c: width,
|
||||||
|
d: height,
|
||||||
|
},
|
||||||
|
payload,
|
||||||
|
} = packet;
|
||||||
|
|
||||||
|
let grid = ByteGrid::load(width as usize, height as usize, &*payload);
|
||||||
|
let grid = match BrightnessGrid::try_from(grid) {
|
||||||
|
Ok(grid) => grid,
|
||||||
|
Err(val) => return Err(TryFromPacketError::InvalidBrightness(val)),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
grid,
|
||||||
|
origin: Origin::new(x as usize, y as usize),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CharBrightness> for TypedCommand {
|
||||||
|
fn from(command: CharBrightness) -> Self {
|
||||||
|
Self::CharBrightness(command)
|
||||||
|
}
|
||||||
|
}
|
41
src/command/clear.rs
Normal file
41
src/command/clear.rs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
use crate::{
|
||||||
|
command::check_command_code_only, command::TryFromPacketError,
|
||||||
|
command_code::CommandCode, Packet, TypedCommand,
|
||||||
|
};
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
/// Set all pixels to the off state. Does not affect brightness.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use servicepoint::{connection, Command, Connection, command};
|
||||||
|
/// # let connection = connection::Fake;
|
||||||
|
/// connection.send(command::Clear).unwrap();
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
/// ```
|
||||||
|
pub struct Clear;
|
||||||
|
|
||||||
|
impl TryFrom<Packet> for Clear {
|
||||||
|
type Error = TryFromPacketError;
|
||||||
|
|
||||||
|
fn try_from(value: Packet) -> Result<Self, Self::Error> {
|
||||||
|
if let Some(e) = check_command_code_only(value, CommandCode::Clear) {
|
||||||
|
Err(e)
|
||||||
|
} else {
|
||||||
|
Ok(Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Clear> for Packet {
|
||||||
|
fn from(_: Clear) -> Self {
|
||||||
|
Packet::command_code_only(CommandCode::Clear)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Clear> for TypedCommand {
|
||||||
|
fn from(command: Clear) -> Self {
|
||||||
|
Self::Clear(command)
|
||||||
|
}
|
||||||
|
}
|
73
src/command/cp437_data.rs
Normal file
73
src/command/cp437_data.rs
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
use crate::{
|
||||||
|
command::TryFromPacketError, command_code::CommandCode, Cp437Grid, Header,
|
||||||
|
Origin, Packet, Tiles, TypedCommand,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Show text on the screen.
|
||||||
|
///
|
||||||
|
/// The text is sent in the form of a 2D grid of [CP-437] encoded characters.
|
||||||
|
///
|
||||||
|
/// <div class="warning">You probably want to use [Command::Utf8Data] instead</div>
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use servicepoint::*;
|
||||||
|
/// # let connection = connection::Fake;
|
||||||
|
/// let grid = CharGrid::from("Hello,\nWorld!");
|
||||||
|
/// let grid = Cp437Grid::from(&grid);
|
||||||
|
/// connection.send(command::Cp437Data{ origin: Origin::ZERO, grid }).expect("send failed");
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use servicepoint::*;
|
||||||
|
/// # let connection = connection::Fake;
|
||||||
|
/// let grid = Cp437Grid::load_ascii("Hello\nWorld", 5, false).unwrap();
|
||||||
|
/// connection.send(command::Cp437Data{ origin: Origin::new(2, 2), grid }).unwrap();
|
||||||
|
/// ```
|
||||||
|
/// [CP-437]: https://en.wikipedia.org/wiki/Code_page_437
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct Cp437Data {
|
||||||
|
/// which tile the text should start
|
||||||
|
pub origin: Origin<Tiles>,
|
||||||
|
/// the text to send to the display
|
||||||
|
pub grid: Cp437Grid,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Cp437Data> for Packet {
|
||||||
|
fn from(value: Cp437Data) -> Self {
|
||||||
|
Packet::origin_grid_to_packet(
|
||||||
|
value.origin,
|
||||||
|
value.grid,
|
||||||
|
CommandCode::Cp437Data,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Packet> for Cp437Data {
|
||||||
|
type Error = TryFromPacketError;
|
||||||
|
|
||||||
|
fn try_from(packet: Packet) -> Result<Self, Self::Error> {
|
||||||
|
let Packet {
|
||||||
|
header:
|
||||||
|
Header {
|
||||||
|
command_code: _,
|
||||||
|
a,
|
||||||
|
b,
|
||||||
|
c,
|
||||||
|
d,
|
||||||
|
},
|
||||||
|
payload,
|
||||||
|
} = packet;
|
||||||
|
Ok(Self {
|
||||||
|
origin: Origin::new(a as usize, b as usize),
|
||||||
|
grid: Cp437Grid::load(c as usize, d as usize, &*payload),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Cp437Data> for TypedCommand {
|
||||||
|
fn from(command: Cp437Data) -> Self {
|
||||||
|
Self::Cp437Data(command)
|
||||||
|
}
|
||||||
|
}
|
44
src/command/fade_out.rs
Normal file
44
src/command/fade_out.rs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
use crate::{
|
||||||
|
command::check_command_code_only, command::TryFromPacketError,
|
||||||
|
command_code::CommandCode, Packet, TypedCommand,
|
||||||
|
};
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
/// <div class="warning">Untested</div>
|
||||||
|
///
|
||||||
|
/// Slowly decrease brightness until off or something like that?
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use servicepoint::*;
|
||||||
|
/// # let connection = connection::Fake;
|
||||||
|
/// connection.send(command::FadeOut).unwrap();
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
/// ```
|
||||||
|
pub struct FadeOut;
|
||||||
|
|
||||||
|
impl TryFrom<Packet> for FadeOut {
|
||||||
|
type Error = TryFromPacketError;
|
||||||
|
|
||||||
|
fn try_from(value: Packet) -> Result<Self, Self::Error> {
|
||||||
|
if let Some(e) = check_command_code_only(value, CommandCode::FadeOut) {
|
||||||
|
Err(e)
|
||||||
|
} else {
|
||||||
|
Ok(Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<FadeOut> for Packet {
|
||||||
|
fn from(_: FadeOut) -> Self {
|
||||||
|
Packet::command_code_only(CommandCode::FadeOut)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<FadeOut> for TypedCommand {
|
||||||
|
fn from(command: FadeOut) -> Self {
|
||||||
|
Self::FadeOut(command)
|
||||||
|
}
|
||||||
|
}
|
80
src/command/global_brightness.rs
Normal file
80
src/command/global_brightness.rs
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
use crate::{
|
||||||
|
command::TryFromPacketError, command_code::CommandCode, Brightness, Header,
|
||||||
|
Packet, TypedCommand,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Set the brightness of all tiles to the same value.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use servicepoint::*;
|
||||||
|
/// # let connection = connection::Fake;
|
||||||
|
/// let command = command::GlobalBrightness { brightness: Brightness::MAX };
|
||||||
|
/// connection.send(command).unwrap();
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct GlobalBrightness {
|
||||||
|
/// the brightness to set all pixels to
|
||||||
|
pub brightness: Brightness,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<GlobalBrightness> for Packet {
|
||||||
|
fn from(command: GlobalBrightness) -> Self {
|
||||||
|
Self {
|
||||||
|
header: Header {
|
||||||
|
command_code: CommandCode::Brightness.into(),
|
||||||
|
a: 0x00000,
|
||||||
|
b: 0x0000,
|
||||||
|
c: 0x0000,
|
||||||
|
d: 0x0000,
|
||||||
|
},
|
||||||
|
payload: vec![command.brightness.into()],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Packet> for GlobalBrightness {
|
||||||
|
type Error = TryFromPacketError;
|
||||||
|
|
||||||
|
fn try_from(packet: Packet) -> Result<Self, Self::Error> {
|
||||||
|
let Packet {
|
||||||
|
header:
|
||||||
|
Header {
|
||||||
|
command_code: _,
|
||||||
|
a,
|
||||||
|
b,
|
||||||
|
c,
|
||||||
|
d,
|
||||||
|
},
|
||||||
|
payload,
|
||||||
|
} = packet;
|
||||||
|
if payload.len() != 1 {
|
||||||
|
return Err(TryFromPacketError::UnexpectedPayloadSize(
|
||||||
|
1,
|
||||||
|
payload.len(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if a != 0 || b != 0 || c != 0 || d != 0 {
|
||||||
|
return Err(TryFromPacketError::ExtraneousHeaderValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
match Brightness::try_from(payload[0]) {
|
||||||
|
Ok(brightness) => Ok(Self { brightness }),
|
||||||
|
Err(_) => Err(TryFromPacketError::InvalidBrightness(payload[0])),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<GlobalBrightness> for TypedCommand {
|
||||||
|
fn from(command: GlobalBrightness) -> Self {
|
||||||
|
Self::GlobalBrightness(command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Brightness> for Packet {
|
||||||
|
fn from(brightness: Brightness) -> Self {
|
||||||
|
Packet::from(GlobalBrightness { brightness })
|
||||||
|
}
|
||||||
|
}
|
45
src/command/hard_reset.rs
Normal file
45
src/command/hard_reset.rs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
use crate::{
|
||||||
|
command::check_command_code_only, command::TryFromPacketError,
|
||||||
|
command_code::CommandCode, Packet, TypedCommand,
|
||||||
|
};
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
/// Kills the udp daemon on the display, which usually results in a restart.
|
||||||
|
///
|
||||||
|
/// Please do not send this in your normal program flow.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use servicepoint::*;
|
||||||
|
/// # let connection = connection::Fake;
|
||||||
|
/// connection.send(command::HardReset).unwrap();
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
/// ```
|
||||||
|
pub struct HardReset;
|
||||||
|
|
||||||
|
impl TryFrom<Packet> for HardReset {
|
||||||
|
type Error = TryFromPacketError;
|
||||||
|
|
||||||
|
fn try_from(value: Packet) -> Result<Self, Self::Error> {
|
||||||
|
if let Some(e) = check_command_code_only(value, CommandCode::HardReset)
|
||||||
|
{
|
||||||
|
Err(e)
|
||||||
|
} else {
|
||||||
|
Ok(Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<HardReset> for Packet {
|
||||||
|
fn from(_: HardReset) -> Self {
|
||||||
|
Packet::command_code_only(CommandCode::HardReset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<HardReset> for TypedCommand {
|
||||||
|
fn from(command: HardReset) -> Self {
|
||||||
|
Self::HardReset(command)
|
||||||
|
}
|
||||||
|
}
|
722
src/command/mod.rs
Normal file
722
src/command/mod.rs
Normal file
|
@ -0,0 +1,722 @@
|
||||||
|
//! This module contains the basic commands the display can handle, which all implement [Command].
|
||||||
|
//!
|
||||||
|
//! To send a [Command], use a [connection][crate::Connection].
|
||||||
|
//!
|
||||||
|
//! # Available commands
|
||||||
|
//!
|
||||||
|
//! To send text, take a look at [Cp437Data].
|
||||||
|
//!
|
||||||
|
//! To draw pixels, the easiest command to use is [BitmapLinearWin].
|
||||||
|
//!
|
||||||
|
//! The other BitmapLinear-Commands operate on a region of pixel memory directly.
|
||||||
|
//! [BitmapLinear] overwrites a region.
|
||||||
|
//! [BitmapLinearOr], [BitmapLinearAnd] and [BitmapLinearXor] apply logical operations per pixel.
|
||||||
|
//!
|
||||||
|
//! Out of bounds operations may be truncated or ignored by the display.
|
||||||
|
//!
|
||||||
|
//! # Compression
|
||||||
|
//!
|
||||||
|
//! Some commands can contain compressed payloads.
|
||||||
|
//! To get started, use [CompressionCode::default].
|
||||||
|
//!
|
||||||
|
//! If you want to archive the best performance (e.g. latency),
|
||||||
|
//! you can try the different compression algorithms for your hardware and use case.
|
||||||
|
//!
|
||||||
|
//! In memory, the payload is not compressed in the [Command].
|
||||||
|
//! Payload (de-)compression happens when converting the [Command] into a [Packet] or vice versa.
|
||||||
|
//!
|
||||||
|
//! # Examples
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use servicepoint::*;
|
||||||
|
//!
|
||||||
|
//! // create command
|
||||||
|
//! let command = command::GlobalBrightness{ brightness: Brightness::MAX };
|
||||||
|
//!
|
||||||
|
//! // turn command into Packet
|
||||||
|
//! let packet: Packet = command.clone().into();
|
||||||
|
//!
|
||||||
|
//! // read command from packet
|
||||||
|
//! let round_tripped = command::TypedCommand::try_from(packet).unwrap();
|
||||||
|
//!
|
||||||
|
//! // round tripping produces exact copy
|
||||||
|
//! assert_eq!(round_tripped, TypedCommand::from(command.clone()));
|
||||||
|
//!
|
||||||
|
//! // send command
|
||||||
|
//! # let connection = connection::Fake;
|
||||||
|
//! connection.send(command).unwrap();
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
mod bitmap_legacy;
|
||||||
|
mod bitmap_linear;
|
||||||
|
mod bitmap_linear_and;
|
||||||
|
mod bitmap_linear_or;
|
||||||
|
mod bitmap_linear_win;
|
||||||
|
mod bitmap_linear_xor;
|
||||||
|
mod char_brightness;
|
||||||
|
mod clear;
|
||||||
|
mod cp437_data;
|
||||||
|
mod fade_out;
|
||||||
|
mod global_brightness;
|
||||||
|
mod hard_reset;
|
||||||
|
mod utf8_data;
|
||||||
|
|
||||||
|
use crate::command_code::CommandCode;
|
||||||
|
use crate::*;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
pub use bitmap_legacy::*;
|
||||||
|
pub use bitmap_linear::*;
|
||||||
|
pub use bitmap_linear_and::*;
|
||||||
|
pub use bitmap_linear_or::*;
|
||||||
|
pub use bitmap_linear_win::*;
|
||||||
|
pub use bitmap_linear_xor::*;
|
||||||
|
pub use char_brightness::*;
|
||||||
|
pub use clear::*;
|
||||||
|
pub use cp437_data::*;
|
||||||
|
pub use fade_out::*;
|
||||||
|
pub use global_brightness::*;
|
||||||
|
pub use hard_reset::*;
|
||||||
|
pub use utf8_data::*;
|
||||||
|
|
||||||
|
/// Represents a command that can be sent to the display.
|
||||||
|
pub trait Command: Debug + Clone + PartialEq + Into<Packet> {}
|
||||||
|
|
||||||
|
impl<T: Debug + Clone + PartialEq + Into<Packet>> Command for T {}
|
||||||
|
|
||||||
|
/// This enum contains all commands provided by the library.
|
||||||
|
/// This is useful in case you want one data type for all kinds of commands without using `dyn`.
|
||||||
|
///
|
||||||
|
/// Please look at the contained structs for documentation per command.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub enum TypedCommand {
|
||||||
|
Clear(Clear),
|
||||||
|
|
||||||
|
Utf8Data(Utf8Data),
|
||||||
|
|
||||||
|
Cp437Data(Cp437Data),
|
||||||
|
|
||||||
|
BitmapLinearWin(BitmapLinearWin),
|
||||||
|
|
||||||
|
GlobalBrightness(GlobalBrightness),
|
||||||
|
|
||||||
|
CharBrightness(CharBrightness),
|
||||||
|
|
||||||
|
BitmapLinear(BitmapLinear),
|
||||||
|
|
||||||
|
BitmapLinearAnd(BitmapLinearAnd),
|
||||||
|
|
||||||
|
BitmapLinearOr(BitmapLinearOr),
|
||||||
|
|
||||||
|
BitmapLinearXor(BitmapLinearXor),
|
||||||
|
|
||||||
|
HardReset(HardReset),
|
||||||
|
|
||||||
|
FadeOut(FadeOut),
|
||||||
|
|
||||||
|
#[allow(deprecated)]
|
||||||
|
#[deprecated]
|
||||||
|
BitmapLegacy(BitmapLegacy),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Err values for [Command::try_from].
|
||||||
|
#[derive(Debug, PartialEq, thiserror::Error)]
|
||||||
|
pub enum TryFromPacketError {
|
||||||
|
/// the contained command code does not correspond to a known command
|
||||||
|
#[error("The command code {0:?} does not correspond to a known command")]
|
||||||
|
InvalidCommand(u16),
|
||||||
|
/// the expected payload size was n, but size m was found
|
||||||
|
#[error("the expected payload size was {0}, but size {1} was found")]
|
||||||
|
UnexpectedPayloadSize(usize, usize),
|
||||||
|
/// Header fields not needed for the command have been used.
|
||||||
|
///
|
||||||
|
/// Note that these commands would usually still work on the actual display.
|
||||||
|
#[error("Header fields not needed for the command have been used")]
|
||||||
|
ExtraneousHeaderValues,
|
||||||
|
/// The contained compression code is not known. This could be of disabled features.
|
||||||
|
#[error("The compression code {0:?} does not correspond to a known compression algorithm.")]
|
||||||
|
InvalidCompressionCode(u16),
|
||||||
|
/// Decompression of the payload failed. This can be caused by corrupted packets.
|
||||||
|
#[error("The decompression of the payload failed")]
|
||||||
|
DecompressionFailed,
|
||||||
|
/// The given brightness value is out of bounds
|
||||||
|
#[error("The given brightness value {0} is out of bounds.")]
|
||||||
|
InvalidBrightness(u8),
|
||||||
|
/// Some provided text was not valid UTF-8.
|
||||||
|
#[error(transparent)]
|
||||||
|
InvalidUtf8(#[from] std::string::FromUtf8Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! packet_to_command_case {
|
||||||
|
($T:tt, $packet:ident) => {
|
||||||
|
TypedCommand::$T($T::try_from($packet)?)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Packet> for TypedCommand {
|
||||||
|
type Error = TryFromPacketError;
|
||||||
|
|
||||||
|
/// Try to interpret the [Packet] as one containing a [Command]
|
||||||
|
fn try_from(packet: Packet) -> Result<Self, Self::Error> {
|
||||||
|
let Packet {
|
||||||
|
header: Header { command_code, .. },
|
||||||
|
..
|
||||||
|
} = packet;
|
||||||
|
let command_code = match CommandCode::try_from(command_code) {
|
||||||
|
Err(()) => {
|
||||||
|
return Err(TryFromPacketError::InvalidCommand(command_code));
|
||||||
|
}
|
||||||
|
Ok(value) => value,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(match command_code {
|
||||||
|
CommandCode::Clear => packet_to_command_case!(Clear, packet),
|
||||||
|
CommandCode::Brightness => {
|
||||||
|
packet_to_command_case!(GlobalBrightness, packet)
|
||||||
|
}
|
||||||
|
CommandCode::HardReset => {
|
||||||
|
packet_to_command_case!(HardReset, packet)
|
||||||
|
}
|
||||||
|
CommandCode::FadeOut => {
|
||||||
|
packet_to_command_case!(FadeOut, packet)
|
||||||
|
}
|
||||||
|
CommandCode::Cp437Data => {
|
||||||
|
packet_to_command_case!(Cp437Data, packet)
|
||||||
|
}
|
||||||
|
CommandCode::CharBrightness => {
|
||||||
|
packet_to_command_case!(CharBrightness, packet)
|
||||||
|
}
|
||||||
|
CommandCode::Utf8Data => {
|
||||||
|
packet_to_command_case!(Utf8Data, packet)
|
||||||
|
}
|
||||||
|
#[allow(deprecated)]
|
||||||
|
CommandCode::BitmapLegacy => {
|
||||||
|
packet_to_command_case!(BitmapLegacy, packet)
|
||||||
|
}
|
||||||
|
CommandCode::BitmapLinear => {
|
||||||
|
packet_to_command_case!(BitmapLinear, packet)
|
||||||
|
}
|
||||||
|
CommandCode::BitmapLinearAnd => {
|
||||||
|
packet_to_command_case!(BitmapLinearAnd, packet)
|
||||||
|
}
|
||||||
|
CommandCode::BitmapLinearOr => {
|
||||||
|
packet_to_command_case!(BitmapLinearOr, packet)
|
||||||
|
}
|
||||||
|
CommandCode::BitmapLinearXor => {
|
||||||
|
packet_to_command_case!(BitmapLinearXor, packet)
|
||||||
|
}
|
||||||
|
CommandCode::BitmapLinearWinUncompressed => {
|
||||||
|
packet_to_command_case!(BitmapLinearWin, packet)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "compression_zlib")]
|
||||||
|
CommandCode::BitmapLinearWinZlib => {
|
||||||
|
packet_to_command_case!(BitmapLinearWin, packet)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "compression_bzip2")]
|
||||||
|
CommandCode::BitmapLinearWinBzip2 => {
|
||||||
|
packet_to_command_case!(BitmapLinearWin, packet)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "compression_lzma")]
|
||||||
|
CommandCode::BitmapLinearWinLzma => {
|
||||||
|
packet_to_command_case!(BitmapLinearWin, packet)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "compression_zstd")]
|
||||||
|
CommandCode::BitmapLinearWinZstd => {
|
||||||
|
packet_to_command_case!(BitmapLinearWin, packet)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TypedCommand> for Packet {
|
||||||
|
fn from(command: TypedCommand) -> Self {
|
||||||
|
match command {
|
||||||
|
TypedCommand::Clear(c) => c.into(),
|
||||||
|
TypedCommand::Utf8Data(c) => c.into(),
|
||||||
|
TypedCommand::Cp437Data(c) => c.into(),
|
||||||
|
TypedCommand::BitmapLinearWin(c) => c.into(),
|
||||||
|
TypedCommand::GlobalBrightness(c) => c.into(),
|
||||||
|
TypedCommand::CharBrightness(c) => c.into(),
|
||||||
|
TypedCommand::BitmapLinear(c) => c.into(),
|
||||||
|
TypedCommand::BitmapLinearAnd(c) => c.into(),
|
||||||
|
TypedCommand::BitmapLinearOr(c) => c.into(),
|
||||||
|
TypedCommand::BitmapLinearXor(c) => c.into(),
|
||||||
|
TypedCommand::HardReset(c) => c.into(),
|
||||||
|
TypedCommand::FadeOut(c) => c.into(),
|
||||||
|
#[allow(deprecated)]
|
||||||
|
TypedCommand::BitmapLegacy(c) => c.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(self) fn check_command_code_only(packet: Packet, code: CommandCode) -> Option<TryFromPacketError> {
|
||||||
|
let Packet {
|
||||||
|
header:
|
||||||
|
Header {
|
||||||
|
command_code: _,
|
||||||
|
a,
|
||||||
|
b,
|
||||||
|
c,
|
||||||
|
d,
|
||||||
|
},
|
||||||
|
payload,
|
||||||
|
} = packet;
|
||||||
|
if packet.header.command_code != u16::from(code) {
|
||||||
|
Some(TryFromPacketError::InvalidCommand(packet.header.command_code))
|
||||||
|
} else if !payload.is_empty() {
|
||||||
|
Some(TryFromPacketError::UnexpectedPayloadSize(0, payload.len()))
|
||||||
|
} else if a != 0 || b != 0 || c != 0 || d != 0 {
|
||||||
|
Some(TryFromPacketError::ExtraneousHeaderValues)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::command::{BitmapLinear, BitmapLinearWin, BitmapLinearXor, CharBrightness, GlobalBrightness, TryFromPacketError};
|
||||||
|
use crate::command_code::CommandCode;
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
fn round_trip(original: TypedCommand) {
|
||||||
|
let packet: Packet = original.clone().into();
|
||||||
|
let copy: TypedCommand = match TypedCommand::try_from(packet) {
|
||||||
|
Ok(command) => command,
|
||||||
|
Err(err) => panic!("could not reload {original:?}: {err:?}"),
|
||||||
|
};
|
||||||
|
assert_eq!(copy, original);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn all_compressions<'t>() -> &'t [CompressionCode] {
|
||||||
|
&[
|
||||||
|
CompressionCode::Uncompressed,
|
||||||
|
#[cfg(feature = "compression_lzma")]
|
||||||
|
CompressionCode::Lzma,
|
||||||
|
#[cfg(feature = "compression_bzip2")]
|
||||||
|
CompressionCode::Bzip2,
|
||||||
|
#[cfg(feature = "compression_zlib")]
|
||||||
|
CompressionCode::Zlib,
|
||||||
|
#[cfg(feature = "compression_zstd")]
|
||||||
|
CompressionCode::Zstd,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn round_trip_clear() {
|
||||||
|
round_trip(TypedCommand::Clear(command::Clear));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn round_trip_hard_reset() {
|
||||||
|
round_trip(TypedCommand::HardReset(command::HardReset));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn round_trip_fade_out() {
|
||||||
|
round_trip(TypedCommand::FadeOut(command::FadeOut));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn round_trip_brightness() {
|
||||||
|
round_trip(TypedCommand::GlobalBrightness(GlobalBrightness {
|
||||||
|
brightness: Brightness::try_from(6).unwrap(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[allow(deprecated)]
|
||||||
|
fn round_trip_bitmap_legacy() {
|
||||||
|
round_trip(TypedCommand::BitmapLegacy(command::BitmapLegacy));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn round_trip_char_brightness() {
|
||||||
|
round_trip(TypedCommand::CharBrightness(CharBrightness {
|
||||||
|
origin: Origin::new(5, 2),
|
||||||
|
grid: BrightnessGrid::new(7, 5),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn round_trip_cp437_data() {
|
||||||
|
round_trip(TypedCommand::Cp437Data(command::Cp437Data {
|
||||||
|
origin: Origin::new(5, 2),
|
||||||
|
grid: Cp437Grid::new(7, 5),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn round_trip_utf8_data() {
|
||||||
|
round_trip(TypedCommand::Utf8Data(command::Utf8Data {
|
||||||
|
origin: Origin::new(5, 2),
|
||||||
|
grid: CharGrid::new(7, 5),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn round_trip_bitmap_linear() {
|
||||||
|
for compression in all_compressions().iter().copied() {
|
||||||
|
round_trip(TypedCommand::BitmapLinear(BitmapLinear {
|
||||||
|
offset: 23,
|
||||||
|
bitvec: BitVec::repeat(false, 40),
|
||||||
|
compression,
|
||||||
|
}));
|
||||||
|
round_trip(TypedCommand::BitmapLinearAnd(
|
||||||
|
command::BitmapLinearAnd {
|
||||||
|
offset: 23,
|
||||||
|
bitvec: BitVec::repeat(false, 40),
|
||||||
|
compression,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
round_trip(TypedCommand::BitmapLinearOr(command::BitmapLinearOr {
|
||||||
|
offset: 23,
|
||||||
|
bitvec: BitVec::repeat(false, 40),
|
||||||
|
compression,
|
||||||
|
}));
|
||||||
|
round_trip(TypedCommand::BitmapLinearXor(BitmapLinearXor {
|
||||||
|
offset: 23,
|
||||||
|
bitvec: BitVec::repeat(false, 40),
|
||||||
|
compression,
|
||||||
|
}));
|
||||||
|
round_trip(TypedCommand::BitmapLinearWin(BitmapLinearWin {
|
||||||
|
origin: Origin::ZERO,
|
||||||
|
bitmap: Bitmap::max_sized(),
|
||||||
|
compression,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn error_invalid_command() {
|
||||||
|
let p = Packet {
|
||||||
|
header: Header {
|
||||||
|
command_code: 0xFF,
|
||||||
|
a: 0x00,
|
||||||
|
b: 0x00,
|
||||||
|
c: 0x00,
|
||||||
|
d: 0x00,
|
||||||
|
},
|
||||||
|
payload: vec![],
|
||||||
|
};
|
||||||
|
let result = TypedCommand::try_from(p);
|
||||||
|
assert!(matches!(
|
||||||
|
result,
|
||||||
|
Err(TryFromPacketError::InvalidCommand(0xFF))
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn error_extraneous_header_values_clear() {
|
||||||
|
let p = Packet {
|
||||||
|
header: Header {
|
||||||
|
command_code: CommandCode::Clear.into(),
|
||||||
|
a: 0x05,
|
||||||
|
b: 0x00,
|
||||||
|
c: 0x00,
|
||||||
|
d: 0x00,
|
||||||
|
},
|
||||||
|
payload: vec![],
|
||||||
|
};
|
||||||
|
let result = TypedCommand::try_from(p);
|
||||||
|
assert!(matches!(
|
||||||
|
result,
|
||||||
|
Err(TryFromPacketError::ExtraneousHeaderValues)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn error_extraneous_header_values_brightness() {
|
||||||
|
let p = Packet {
|
||||||
|
header: Header {
|
||||||
|
command_code: CommandCode::Brightness.into(),
|
||||||
|
a: 0x00,
|
||||||
|
b: 0x13,
|
||||||
|
c: 0x37,
|
||||||
|
d: 0x00,
|
||||||
|
},
|
||||||
|
payload: vec![5],
|
||||||
|
};
|
||||||
|
let result = TypedCommand::try_from(p);
|
||||||
|
assert!(matches!(
|
||||||
|
result,
|
||||||
|
Err(TryFromPacketError::ExtraneousHeaderValues)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn error_extraneous_header_hard_reset() {
|
||||||
|
let p = Packet {
|
||||||
|
header: Header {
|
||||||
|
command_code: CommandCode::HardReset.into(),
|
||||||
|
a: 0x00,
|
||||||
|
b: 0x00,
|
||||||
|
c: 0x00,
|
||||||
|
d: 0x01,
|
||||||
|
},
|
||||||
|
payload: vec![],
|
||||||
|
};
|
||||||
|
let result = TypedCommand::try_from(p);
|
||||||
|
assert!(matches!(
|
||||||
|
result,
|
||||||
|
Err(TryFromPacketError::ExtraneousHeaderValues)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn error_extraneous_header_fade_out() {
|
||||||
|
let p = Packet {
|
||||||
|
header: Header {
|
||||||
|
command_code: CommandCode::FadeOut.into(),
|
||||||
|
a: 0x10,
|
||||||
|
b: 0x00,
|
||||||
|
c: 0x00,
|
||||||
|
d: 0x01,
|
||||||
|
},
|
||||||
|
payload: vec![],
|
||||||
|
};
|
||||||
|
let result = TypedCommand::try_from(p);
|
||||||
|
assert!(matches!(
|
||||||
|
result,
|
||||||
|
Err(TryFromPacketError::ExtraneousHeaderValues)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn error_unexpected_payload() {
|
||||||
|
let p = Packet {
|
||||||
|
header: Header {
|
||||||
|
command_code: CommandCode::FadeOut.into(),
|
||||||
|
a: 0x00,
|
||||||
|
b: 0x00,
|
||||||
|
c: 0x00,
|
||||||
|
d: 0x00,
|
||||||
|
},
|
||||||
|
payload: vec![5, 7],
|
||||||
|
};
|
||||||
|
let result = TypedCommand::try_from(p);
|
||||||
|
assert!(matches!(
|
||||||
|
result,
|
||||||
|
Err(TryFromPacketError::UnexpectedPayloadSize(0, 2))
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn error_decompression_failed_win() {
|
||||||
|
for compression in all_compressions().iter().copied() {
|
||||||
|
let p: Packet = command::BitmapLinearWin {
|
||||||
|
origin: Origin::new(16, 8),
|
||||||
|
bitmap: Bitmap::new(8, 8),
|
||||||
|
compression,
|
||||||
|
}
|
||||||
|
.into();
|
||||||
|
|
||||||
|
let Packet {
|
||||||
|
header,
|
||||||
|
mut payload,
|
||||||
|
} = p;
|
||||||
|
|
||||||
|
// mangle it
|
||||||
|
for byte in payload.iter_mut() {
|
||||||
|
*byte -= *byte / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
let p = Packet { header, payload };
|
||||||
|
let result = TypedCommand::try_from(p);
|
||||||
|
if compression != CompressionCode::Uncompressed {
|
||||||
|
assert_eq!(result, Err(TryFromPacketError::DecompressionFailed))
|
||||||
|
} else {
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn error_decompression_failed_and() {
|
||||||
|
for compression in all_compressions().iter().copied() {
|
||||||
|
let p: Packet = command::BitmapLinearAnd {
|
||||||
|
offset: 0,
|
||||||
|
bitvec: BitVec::repeat(false, 8),
|
||||||
|
compression,
|
||||||
|
}
|
||||||
|
.into();
|
||||||
|
let Packet {
|
||||||
|
header,
|
||||||
|
mut payload,
|
||||||
|
} = p;
|
||||||
|
|
||||||
|
// mangle it
|
||||||
|
for byte in payload.iter_mut() {
|
||||||
|
*byte -= *byte / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
let p = Packet { header, payload };
|
||||||
|
let result = TypedCommand::try_from(p);
|
||||||
|
if compression != CompressionCode::Uncompressed {
|
||||||
|
assert_eq!(result, Err(TryFromPacketError::DecompressionFailed))
|
||||||
|
} else {
|
||||||
|
// when not compressing, there is no way to detect corrupted data
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unexpected_payload_size_brightness() {
|
||||||
|
assert_eq!(
|
||||||
|
TypedCommand::try_from(Packet {
|
||||||
|
header: Header {
|
||||||
|
command_code: CommandCode::Brightness.into(),
|
||||||
|
a: 0,
|
||||||
|
b: 0,
|
||||||
|
c: 0,
|
||||||
|
d: 0,
|
||||||
|
},
|
||||||
|
payload: vec!()
|
||||||
|
}),
|
||||||
|
Err(TryFromPacketError::UnexpectedPayloadSize(1, 0))
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
TypedCommand::try_from(Packet {
|
||||||
|
header: Header {
|
||||||
|
command_code: CommandCode::Brightness.into(),
|
||||||
|
a: 0,
|
||||||
|
b: 0,
|
||||||
|
c: 0,
|
||||||
|
d: 0,
|
||||||
|
},
|
||||||
|
payload: vec!(0, 0)
|
||||||
|
}),
|
||||||
|
Err(TryFromPacketError::UnexpectedPayloadSize(1, 2))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn error_reserved_used() {
|
||||||
|
let Packet { header, payload } = command::BitmapLinear {
|
||||||
|
offset: 0,
|
||||||
|
bitvec: BitVec::repeat(false, 8),
|
||||||
|
compression: CompressionCode::Uncompressed,
|
||||||
|
}
|
||||||
|
.into();
|
||||||
|
let Header {
|
||||||
|
command_code: command,
|
||||||
|
a: offset,
|
||||||
|
b: length,
|
||||||
|
c: sub,
|
||||||
|
d: _reserved,
|
||||||
|
} = header;
|
||||||
|
let p = Packet {
|
||||||
|
header: Header {
|
||||||
|
command_code: command,
|
||||||
|
a: offset,
|
||||||
|
b: length,
|
||||||
|
c: sub,
|
||||||
|
d: 69,
|
||||||
|
},
|
||||||
|
payload,
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
TypedCommand::try_from(p),
|
||||||
|
Err(TryFromPacketError::ExtraneousHeaderValues)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn error_invalid_compression() {
|
||||||
|
let Packet { header, payload } = command::BitmapLinear {
|
||||||
|
offset: 0,
|
||||||
|
bitvec: BitVec::repeat(false, 8),
|
||||||
|
compression: CompressionCode::Uncompressed,
|
||||||
|
}
|
||||||
|
.into();
|
||||||
|
let Header {
|
||||||
|
command_code: command,
|
||||||
|
a: offset,
|
||||||
|
b: length,
|
||||||
|
c: _sub,
|
||||||
|
d: reserved,
|
||||||
|
} = header;
|
||||||
|
let p = Packet {
|
||||||
|
header: Header {
|
||||||
|
command_code: command,
|
||||||
|
a: offset,
|
||||||
|
b: length,
|
||||||
|
c: 42,
|
||||||
|
d: reserved,
|
||||||
|
},
|
||||||
|
payload,
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
TypedCommand::try_from(p),
|
||||||
|
Err(TryFromPacketError::InvalidCompressionCode(42))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn error_unexpected_size() {
|
||||||
|
let Packet { header, payload } = command::BitmapLinear {
|
||||||
|
offset: 0,
|
||||||
|
bitvec: BitVec::repeat(false, 8),
|
||||||
|
compression: CompressionCode::Uncompressed,
|
||||||
|
}
|
||||||
|
.into();
|
||||||
|
let Header {
|
||||||
|
command_code: command,
|
||||||
|
a: offset,
|
||||||
|
b: length,
|
||||||
|
c: compression,
|
||||||
|
d: reserved,
|
||||||
|
} = header;
|
||||||
|
let p = Packet {
|
||||||
|
header: Header {
|
||||||
|
command_code: command,
|
||||||
|
a: offset,
|
||||||
|
b: 420,
|
||||||
|
c: compression,
|
||||||
|
d: reserved,
|
||||||
|
},
|
||||||
|
payload,
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
TypedCommand::try_from(p),
|
||||||
|
Err(TryFromPacketError::UnexpectedPayloadSize(
|
||||||
|
420,
|
||||||
|
length as usize,
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn origin_add() {
|
||||||
|
assert_eq!(
|
||||||
|
Origin::<Pixels>::new(4, 2),
|
||||||
|
Origin::new(1, 0) + Origin::new(3, 2)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn packet_into_char_brightness_invalid() {
|
||||||
|
let grid = BrightnessGrid::new(2, 2);
|
||||||
|
let command = command::CharBrightness{origin: Origin::ZERO, grid};
|
||||||
|
let mut packet: Packet = command.into();
|
||||||
|
let slot = packet.payload.get_mut(1).unwrap();
|
||||||
|
*slot = 23;
|
||||||
|
assert_eq!(
|
||||||
|
TypedCommand::try_from(packet),
|
||||||
|
Err(TryFromPacketError::InvalidBrightness(23))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn packet_into_brightness_invalid() {
|
||||||
|
let mut packet: Packet = command::GlobalBrightness{brightness: Brightness::MAX}.into();
|
||||||
|
let slot = packet.payload.get_mut(0).unwrap();
|
||||||
|
*slot = 42;
|
||||||
|
assert_eq!(
|
||||||
|
TypedCommand::try_from(packet),
|
||||||
|
Err(TryFromPacketError::InvalidBrightness(42))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
64
src/command/utf8_data.rs
Normal file
64
src/command/utf8_data.rs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
use crate::{
|
||||||
|
command::TryFromPacketError, command_code::CommandCode, CharGrid, Header,
|
||||||
|
Origin, Packet, Tiles, TypedCommand,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Show text on the screen.
|
||||||
|
///
|
||||||
|
/// The text is sent in the form of a 2D grid of UTF-8 encoded characters (the default encoding in rust).
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use servicepoint::*;
|
||||||
|
/// # let connection = connection::Fake;
|
||||||
|
/// let grid = CharGrid::from("Hello,\nWorld!");
|
||||||
|
/// connection.send(command::Utf8Data { origin: Origin::ZERO, grid }).expect("send failed");
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct Utf8Data {
|
||||||
|
/// which tile the text should start
|
||||||
|
pub origin: Origin<Tiles>,
|
||||||
|
/// the text to send to the display
|
||||||
|
pub grid: CharGrid,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Utf8Data> for Packet {
|
||||||
|
fn from(value: Utf8Data) -> Self {
|
||||||
|
Packet::origin_grid_to_packet(
|
||||||
|
value.origin,
|
||||||
|
value.grid,
|
||||||
|
CommandCode::Utf8Data,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Packet> for Utf8Data {
|
||||||
|
type Error = TryFromPacketError;
|
||||||
|
|
||||||
|
fn try_from(packet: Packet) -> Result<Self, Self::Error> {
|
||||||
|
let Packet {
|
||||||
|
header:
|
||||||
|
Header {
|
||||||
|
command_code: _,
|
||||||
|
a,
|
||||||
|
b,
|
||||||
|
c,
|
||||||
|
d,
|
||||||
|
},
|
||||||
|
payload,
|
||||||
|
} = packet;
|
||||||
|
let payload: Vec<_> =
|
||||||
|
String::from_utf8(payload.clone())?.chars().collect();
|
||||||
|
Ok(Self {
|
||||||
|
origin: Origin::new(a as usize, b as usize),
|
||||||
|
grid: CharGrid::load(c as usize, d as usize, &payload),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Utf8Data> for TypedCommand {
|
||||||
|
fn from(command: Utf8Data) -> Self {
|
||||||
|
Self::Utf8Data(command)
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,14 +3,22 @@
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use servicepoint::{Command, CompressionCode, Origin, Bitmap};
|
/// # use servicepoint::*;
|
||||||
/// // create command without payload compression
|
/// // create command without payload compression
|
||||||
/// # let pixels = Bitmap::max_sized();
|
/// # let pixels = Bitmap::max_sized();
|
||||||
/// _ = Command::BitmapLinearWin(Origin::ZERO, pixels, CompressionCode::Uncompressed);
|
/// _ = command::BitmapLinearWin {
|
||||||
|
/// origin: Origin::ZERO,
|
||||||
|
/// bitmap: pixels,
|
||||||
|
/// compression: CompressionCode::Uncompressed
|
||||||
|
/// };
|
||||||
///
|
///
|
||||||
/// // create command with payload compressed with lzma and appropriate header flags
|
/// // create command with payload compressed with lzma and appropriate header flags
|
||||||
/// # let pixels = Bitmap::max_sized();
|
/// # let pixels = Bitmap::max_sized();
|
||||||
/// _ = Command::BitmapLinearWin(Origin::ZERO, pixels, CompressionCode::Lzma);
|
/// _ = command::BitmapLinearWin {
|
||||||
|
/// origin: Origin::ZERO,
|
||||||
|
/// bitmap: pixels,
|
||||||
|
/// compression: CompressionCode::Lzma
|
||||||
|
/// };
|
||||||
/// ```
|
/// ```
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
|
|
@ -22,10 +22,10 @@ pub use websocket::*;
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use servicepoint::Connection;
|
/// use servicepoint::{command, connection, Connection};
|
||||||
/// let connection = servicepoint::connection::Udp::open("127.0.0.1:2342")
|
/// let connection = connection::Udp::open("127.0.0.1:2342")
|
||||||
/// .expect("connection failed");
|
/// .expect("connection failed");
|
||||||
/// connection.send(servicepoint::Command::Clear)
|
/// connection.send(command::Clear)
|
||||||
/// .expect("send failed");
|
/// .expect("send failed");
|
||||||
/// ```
|
/// ```
|
||||||
pub trait Connection: Debug {
|
pub trait Connection: Debug {
|
||||||
|
@ -46,7 +46,7 @@ pub trait Connection: Debug {
|
||||||
/// # use servicepoint::connection::Connection;
|
/// # use servicepoint::connection::Connection;
|
||||||
/// let connection = servicepoint::connection::Fake;
|
/// let connection = servicepoint::connection::Fake;
|
||||||
/// // turn off all pixels on display
|
/// // turn off all pixels on display
|
||||||
/// connection.send(servicepoint::Command::Clear)
|
/// connection.send(servicepoint::command::Clear)
|
||||||
/// .expect("send failed");
|
/// .expect("send failed");
|
||||||
/// ```
|
/// ```
|
||||||
fn send(&self, packet: impl Into<Packet>) -> Result<(), Self::Error>;
|
fn send(&self, packet: impl Into<Packet>) -> Result<(), Self::Error>;
|
||||||
|
|
|
@ -52,19 +52,19 @@ pub const PIXEL_COUNT: usize = PIXEL_WIDTH * PIXEL_HEIGHT;
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use std::time::Instant;
|
/// # use std::time::Instant;
|
||||||
/// # use servicepoint::{Command, CompressionCode, FRAME_PACING, Origin, Bitmap, Connection};
|
/// # use servicepoint::*;
|
||||||
/// # let connection = servicepoint::connection::Fake;
|
/// # let connection = connection::Fake;
|
||||||
/// # let pixels = Bitmap::max_sized();
|
/// # let pixels = Bitmap::max_sized();
|
||||||
/// loop {
|
/// loop {
|
||||||
/// let start = Instant::now();
|
/// let start = Instant::now();
|
||||||
///
|
///
|
||||||
/// // Change pixels here
|
/// // Change pixels here
|
||||||
///
|
///
|
||||||
/// connection.send(Command::BitmapLinearWin(
|
/// connection.send(command::BitmapLinearWin {
|
||||||
/// Origin::new(0,0),
|
/// origin: Origin::new(0,0),
|
||||||
/// pixels,
|
/// bitmap: pixels,
|
||||||
/// CompressionCode::default()
|
/// compression: CompressionCode::default()
|
||||||
/// ))
|
/// })
|
||||||
/// .expect("send failed");
|
/// .expect("send failed");
|
||||||
///
|
///
|
||||||
/// // warning: will crash if resulting duration is negative, e.g. when resuming from standby
|
/// // warning: will crash if resulting duration is negative, e.g. when resuming from standby
|
||||||
|
|
29
src/lib.rs
29
src/lib.rs
|
@ -9,32 +9,32 @@
|
||||||
//! ### Clear display
|
//! ### Clear display
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use servicepoint::{Connection, Command, connection};
|
//! use servicepoint::*;
|
||||||
//!
|
//!
|
||||||
//! // establish a connection
|
//! // establish a connection
|
||||||
//! let connection = connection::Udp::open("127.0.0.1:2342")
|
//! let connection = connection::Udp::open("127.0.0.1:2342")
|
||||||
//! .expect("connection failed");
|
//! .expect("connection failed");
|
||||||
//!
|
//!
|
||||||
//! // turn off all pixels on display
|
//! // turn off all pixels on display
|
||||||
//! connection.send(Command::Clear)
|
//! connection.send(command::Clear)
|
||||||
//! .expect("send failed");
|
//! .expect("send failed");
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! ### Set all pixels to on
|
//! ### Set all pixels to on
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! # use servicepoint::{Command, CompressionCode, Grid, Bitmap, Connection};
|
//! # use servicepoint::*;
|
||||||
//! # let connection = servicepoint::connection::Udp::open("127.0.0.1:2342").expect("connection failed");
|
//! # let connection = connection::Udp::open("127.0.0.1:2342").expect("connection failed");
|
||||||
//! // turn on all pixels in a grid
|
//! // turn on all pixels in a grid
|
||||||
//! let mut pixels = Bitmap::max_sized();
|
//! let mut pixels = Bitmap::max_sized();
|
||||||
//! pixels.fill(true);
|
//! pixels.fill(true);
|
||||||
//!
|
//!
|
||||||
//! // create command to send pixels
|
//! // create command to send pixels
|
||||||
//! let command = Command::BitmapLinearWin(
|
//! let command = command::BitmapLinearWin {
|
||||||
//! servicepoint::Origin::ZERO,
|
//! origin: Origin::ZERO,
|
||||||
//! pixels,
|
//! bitmap: pixels,
|
||||||
//! CompressionCode::default()
|
//! compression: CompressionCode::default()
|
||||||
//! );
|
//! };
|
||||||
//!
|
//!
|
||||||
//! // send command to display
|
//! // send command to display
|
||||||
//! connection.send(command).expect("send failed");
|
//! connection.send(command).expect("send failed");
|
||||||
|
@ -50,7 +50,7 @@
|
||||||
//! // modify the grid
|
//! // modify the grid
|
||||||
//! grid.set(grid.width() - 1, 1, '!');
|
//! grid.set(grid.width() - 1, 1, '!');
|
||||||
//! // create the command to send the data
|
//! // create the command to send the data
|
||||||
//! let command = Command::Utf8Data(Origin::ZERO, grid);
|
//! let command = command::Utf8Data { origin: Origin::ZERO, grid };
|
||||||
//! // send command to display
|
//! // send command to display
|
||||||
//! connection.send(command).expect("send failed");
|
//! connection.send(command).expect("send failed");
|
||||||
//! ```
|
//! ```
|
||||||
|
@ -61,7 +61,7 @@ pub use crate::brightness::Brightness;
|
||||||
pub use crate::brightness_grid::BrightnessGrid;
|
pub use crate::brightness_grid::BrightnessGrid;
|
||||||
pub use crate::byte_grid::ByteGrid;
|
pub use crate::byte_grid::ByteGrid;
|
||||||
pub use crate::char_grid::CharGrid;
|
pub use crate::char_grid::CharGrid;
|
||||||
pub use crate::command::{Command, Offset};
|
pub use crate::command::{Command, TypedCommand};
|
||||||
pub use crate::compression_code::CompressionCode;
|
pub use crate::compression_code::CompressionCode;
|
||||||
pub use crate::connection::Connection;
|
pub use crate::connection::Connection;
|
||||||
pub use crate::constants::*;
|
pub use crate::constants::*;
|
||||||
|
@ -80,7 +80,7 @@ mod brightness;
|
||||||
mod brightness_grid;
|
mod brightness_grid;
|
||||||
mod byte_grid;
|
mod byte_grid;
|
||||||
mod char_grid;
|
mod char_grid;
|
||||||
mod command;
|
pub mod command;
|
||||||
mod command_code;
|
mod command_code;
|
||||||
mod compression;
|
mod compression;
|
||||||
mod compression_code;
|
mod compression_code;
|
||||||
|
@ -95,6 +95,8 @@ mod value_grid;
|
||||||
|
|
||||||
#[cfg(feature = "cp437")]
|
#[cfg(feature = "cp437")]
|
||||||
mod cp437;
|
mod cp437;
|
||||||
|
mod parser;
|
||||||
|
|
||||||
#[cfg(feature = "cp437")]
|
#[cfg(feature = "cp437")]
|
||||||
pub use crate::cp437::Cp437Converter;
|
pub use crate::cp437::Cp437Converter;
|
||||||
|
|
||||||
|
@ -102,3 +104,6 @@ pub use crate::cp437::Cp437Converter;
|
||||||
#[doc = include_str!("../README.md")]
|
#[doc = include_str!("../README.md")]
|
||||||
#[cfg(doctest)]
|
#[cfg(doctest)]
|
||||||
pub struct ReadmeDocTests;
|
pub struct ReadmeDocTests;
|
||||||
|
|
||||||
|
/// Type alias for documenting the meaning of the u16 in enum values
|
||||||
|
pub type Offset = usize;
|
||||||
|
|
160
src/packet.rs
160
src/packet.rs
|
@ -7,17 +7,17 @@
|
||||||
//! Converting a packet to a command and back:
|
//! Converting a packet to a command and back:
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use servicepoint::{Command, Packet};
|
//! use servicepoint::{Command, Packet, TypedCommand};
|
||||||
//! # let command = Command::Clear;
|
//! # let command = servicepoint::command::Clear;
|
||||||
//! let packet: Packet = command.into();
|
//! let packet: Packet = command.into();
|
||||||
//! let command: Command = Command::try_from(packet).expect("could not read command from packet");
|
//! let command = TypedCommand::try_from(packet).expect("could not read command from packet");
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! Converting a packet to bytes and back:
|
//! Converting a packet to bytes and back:
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use servicepoint::{Command, Packet};
|
//! use servicepoint::{Command, Packet};
|
||||||
//! # let command = Command::Clear;
|
//! # let command = servicepoint::command::Clear;
|
||||||
//! # let packet: Packet = command.into();
|
//! # let packet: Packet = command.into();
|
||||||
//! let bytes: Vec<u8> = packet.into();
|
//! let bytes: Vec<u8> = packet.into();
|
||||||
//! let packet = Packet::try_from(bytes).expect("could not read packet from bytes");
|
//! let packet = Packet::try_from(bytes).expect("could not read packet from bytes");
|
||||||
|
@ -25,10 +25,7 @@
|
||||||
|
|
||||||
use crate::command_code::CommandCode;
|
use crate::command_code::CommandCode;
|
||||||
use crate::compression::into_compressed;
|
use crate::compression::into_compressed;
|
||||||
use crate::{
|
use crate::{CompressionCode, Grid, Offset, Origin, Tiles};
|
||||||
Bitmap, Command, CompressionCode, Grid, Offset, Origin, Pixels, Tiles,
|
|
||||||
TILE_SIZE,
|
|
||||||
};
|
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
|
|
||||||
/// A raw header.
|
/// A raw header.
|
||||||
|
@ -37,7 +34,7 @@ use std::mem::size_of;
|
||||||
/// payload, where applicable.
|
/// payload, where applicable.
|
||||||
///
|
///
|
||||||
/// Because the meaning of most fields depend on the command, there are no speaking names for them.
|
/// Because the meaning of most fields depend on the command, there are no speaking names for them.
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Default)]
|
||||||
pub struct Header {
|
pub struct Header {
|
||||||
/// The first two bytes specify which command this packet represents.
|
/// The first two bytes specify which command this packet represents.
|
||||||
pub command_code: u16,
|
pub command_code: u16,
|
||||||
|
@ -138,88 +135,10 @@ impl TryFrom<Vec<u8>> for Packet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Command> for Packet {
|
|
||||||
/// Move the [Command] into a [Packet] instance for sending.
|
|
||||||
#[allow(clippy::cast_possible_truncation)]
|
|
||||||
fn from(value: Command) -> Self {
|
|
||||||
match value {
|
|
||||||
Command::Clear => Self::command_code_only(CommandCode::Clear),
|
|
||||||
Command::FadeOut => Self::command_code_only(CommandCode::FadeOut),
|
|
||||||
Command::HardReset => {
|
|
||||||
Self::command_code_only(CommandCode::HardReset)
|
|
||||||
}
|
|
||||||
#[allow(deprecated)]
|
|
||||||
Command::BitmapLegacy => {
|
|
||||||
Self::command_code_only(CommandCode::BitmapLegacy)
|
|
||||||
}
|
|
||||||
Command::CharBrightness(origin, grid) => {
|
|
||||||
Self::origin_grid_to_packet(
|
|
||||||
origin,
|
|
||||||
grid,
|
|
||||||
CommandCode::CharBrightness,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Command::Brightness(brightness) => Packet {
|
|
||||||
header: Header {
|
|
||||||
command_code: CommandCode::Brightness.into(),
|
|
||||||
a: 0x00000,
|
|
||||||
b: 0x0000,
|
|
||||||
c: 0x0000,
|
|
||||||
d: 0x0000,
|
|
||||||
},
|
|
||||||
payload: vec![brightness.into()],
|
|
||||||
},
|
|
||||||
Command::BitmapLinearWin(origin, pixels, compression) => {
|
|
||||||
Self::bitmap_win_into_packet(origin, pixels, compression)
|
|
||||||
}
|
|
||||||
Command::BitmapLinear(offset, bits, compression) => {
|
|
||||||
Self::bitmap_linear_into_packet(
|
|
||||||
CommandCode::BitmapLinear,
|
|
||||||
offset,
|
|
||||||
compression,
|
|
||||||
bits.into(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Command::BitmapLinearAnd(offset, bits, compression) => {
|
|
||||||
Self::bitmap_linear_into_packet(
|
|
||||||
CommandCode::BitmapLinearAnd,
|
|
||||||
offset,
|
|
||||||
compression,
|
|
||||||
bits.into(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Command::BitmapLinearOr(offset, bits, compression) => {
|
|
||||||
Self::bitmap_linear_into_packet(
|
|
||||||
CommandCode::BitmapLinearOr,
|
|
||||||
offset,
|
|
||||||
compression,
|
|
||||||
bits.into(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Command::BitmapLinearXor(offset, bits, compression) => {
|
|
||||||
Self::bitmap_linear_into_packet(
|
|
||||||
CommandCode::BitmapLinearXor,
|
|
||||||
offset,
|
|
||||||
compression,
|
|
||||||
bits.into(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Command::Cp437Data(origin, grid) => Self::origin_grid_to_packet(
|
|
||||||
origin,
|
|
||||||
grid,
|
|
||||||
CommandCode::Cp437Data,
|
|
||||||
),
|
|
||||||
Command::Utf8Data(origin, grid) => {
|
|
||||||
Self::origin_grid_to_packet(origin, grid, CommandCode::Utf8Data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Packet {
|
impl Packet {
|
||||||
/// Helper method for `BitmapLinear*`-Commands into [Packet]
|
/// Helper method for `BitmapLinear*`-Commands into [Packet]
|
||||||
#[allow(clippy::cast_possible_truncation)]
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
fn bitmap_linear_into_packet(
|
pub(crate) fn bitmap_linear_into_packet(
|
||||||
command: CommandCode,
|
command: CommandCode,
|
||||||
offset: Offset,
|
offset: Offset,
|
||||||
compression: CompressionCode,
|
compression: CompressionCode,
|
||||||
|
@ -239,59 +158,6 @@ impl Packet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::cast_possible_truncation)]
|
|
||||||
fn bitmap_win_into_packet(
|
|
||||||
origin: Origin<Pixels>,
|
|
||||||
pixels: Bitmap,
|
|
||||||
compression: CompressionCode,
|
|
||||||
) -> Packet {
|
|
||||||
debug_assert_eq!(origin.x % 8, 0);
|
|
||||||
debug_assert_eq!(pixels.width() % 8, 0);
|
|
||||||
|
|
||||||
let tile_x = (origin.x / TILE_SIZE) as u16;
|
|
||||||
let tile_w = (pixels.width() / TILE_SIZE) as u16;
|
|
||||||
let pixel_h = pixels.height() as u16;
|
|
||||||
let payload = into_compressed(compression, pixels.into());
|
|
||||||
let command = match compression {
|
|
||||||
CompressionCode::Uncompressed => {
|
|
||||||
CommandCode::BitmapLinearWinUncompressed
|
|
||||||
}
|
|
||||||
#[cfg(feature = "compression_zlib")]
|
|
||||||
CompressionCode::Zlib => CommandCode::BitmapLinearWinZlib,
|
|
||||||
#[cfg(feature = "compression_bzip2")]
|
|
||||||
CompressionCode::Bzip2 => CommandCode::BitmapLinearWinBzip2,
|
|
||||||
#[cfg(feature = "compression_lzma")]
|
|
||||||
CompressionCode::Lzma => CommandCode::BitmapLinearWinLzma,
|
|
||||||
#[cfg(feature = "compression_zstd")]
|
|
||||||
CompressionCode::Zstd => CommandCode::BitmapLinearWinZstd,
|
|
||||||
};
|
|
||||||
|
|
||||||
Packet {
|
|
||||||
header: Header {
|
|
||||||
command_code: command.into(),
|
|
||||||
a: tile_x,
|
|
||||||
b: origin.y as u16,
|
|
||||||
c: tile_w,
|
|
||||||
d: pixel_h,
|
|
||||||
},
|
|
||||||
payload,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper method for creating empty packets only containing the command code
|
|
||||||
fn command_code_only(code: CommandCode) -> Packet {
|
|
||||||
Packet {
|
|
||||||
header: Header {
|
|
||||||
command_code: code.into(),
|
|
||||||
a: 0x0000,
|
|
||||||
b: 0x0000,
|
|
||||||
c: 0x0000,
|
|
||||||
d: 0x0000,
|
|
||||||
},
|
|
||||||
payload: vec![],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn u16_from_be_slice(slice: &[u8]) -> u16 {
|
fn u16_from_be_slice(slice: &[u8]) -> u16 {
|
||||||
let mut bytes = [0u8; 2];
|
let mut bytes = [0u8; 2];
|
||||||
bytes[0] = slice[0];
|
bytes[0] = slice[0];
|
||||||
|
@ -299,7 +165,7 @@ impl Packet {
|
||||||
u16::from_be_bytes(bytes)
|
u16::from_be_bytes(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn origin_grid_to_packet<T>(
|
pub(crate) fn origin_grid_to_packet<T>(
|
||||||
origin: Origin<Tiles>,
|
origin: Origin<Tiles>,
|
||||||
grid: impl Grid<T> + Into<Payload>,
|
grid: impl Grid<T> + Into<Payload>,
|
||||||
command_code: CommandCode,
|
command_code: CommandCode,
|
||||||
|
@ -315,6 +181,16 @@ impl Packet {
|
||||||
payload: grid.into(),
|
payload: grid.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn command_code_only(c: CommandCode) -> Self {
|
||||||
|
Self {
|
||||||
|
header: Header {
|
||||||
|
command_code: c.into(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
payload: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
0
src/parser.rs
Normal file
0
src/parser.rs
Normal file
|
@ -212,11 +212,11 @@ impl<T: Value> ValueGrid<T> {
|
||||||
/// Use logic written for u8s and then convert to [Brightness] values for sending in a [Command].
|
/// Use logic written for u8s and then convert to [Brightness] values for sending in a [Command].
|
||||||
/// ```
|
/// ```
|
||||||
/// # fn foo(grid: &mut ByteGrid) {}
|
/// # fn foo(grid: &mut ByteGrid) {}
|
||||||
/// # use servicepoint::{Brightness, BrightnessGrid, ByteGrid, Command, Origin, TILE_HEIGHT, TILE_WIDTH};
|
/// # use servicepoint::*;
|
||||||
/// let mut grid: ByteGrid = ByteGrid::new(TILE_WIDTH, TILE_HEIGHT);
|
/// let mut grid: ByteGrid = ByteGrid::new(TILE_WIDTH, TILE_HEIGHT);
|
||||||
/// foo(&mut grid);
|
/// foo(&mut grid);
|
||||||
/// let grid: BrightnessGrid = grid.map(Brightness::saturating_from);
|
/// let grid: BrightnessGrid = grid.map(Brightness::saturating_from);
|
||||||
/// let command = Command::CharBrightness(Origin::ZERO, grid);
|
/// let command = command::CharBrightness { origin: Origin::ZERO, grid };
|
||||||
/// ```
|
/// ```
|
||||||
/// [Brightness]: [crate::Brightness]
|
/// [Brightness]: [crate::Brightness]
|
||||||
/// [Command]: [crate::Command]
|
/// [Command]: [crate::Command]
|
||||||
|
|
Loading…
Reference in a new issue