diff --git a/Cargo.lock b/Cargo.lock index b114eb2..64c50d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,18 +2,58 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "autocfg" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "crc32fast" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "flate2" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "log" version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + [[package]] name = "num" version = "0.4.3" @@ -120,6 +160,7 @@ dependencies = [ name = "servicepoint2" version = "0.1.0" dependencies = [ + "flate2", "log", "num", "num-derive", diff --git a/Cargo.toml b/Cargo.toml index 4216dd6..cc26ab8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,4 +7,5 @@ edition = "2021" num = "0.4" num-derive = "0.4" num-traits = "0.2" -log = "0.4.21" +log = "0.4" +flate2 = "1.0" diff --git a/examples/Cargo.lock b/examples/Cargo.lock index 26f6d56..2f2338a 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "aho-corasick" version = "1.1.3" @@ -128,6 +134,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +[[package]] +name = "crc32fast" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +dependencies = [ + "cfg-if", +] + [[package]] name = "env_filter" version = "0.1.0" @@ -151,6 +166,16 @@ dependencies = [ "log", ] +[[package]] +name = "flate2" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "game_of_life" version = "0.1.0" @@ -209,6 +234,15 @@ version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + [[package]] name = "moving_line" version = "0.1.0" @@ -401,6 +435,7 @@ checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" name = "servicepoint2" version = "0.1.0" dependencies = [ + "flate2", "log", "num", "num-derive", diff --git a/examples/wiping_clear/src/main.rs b/examples/wiping_clear/src/main.rs index 6cf38c4..1310c5f 100644 --- a/examples/wiping_clear/src/main.rs +++ b/examples/wiping_clear/src/main.rs @@ -3,7 +3,7 @@ use std::time::Duration; use clap::Parser; -use servicepoint2::{BitVec, Connection, PIXEL_HEIGHT, PIXEL_WIDTH, PixelGrid}; +use servicepoint2::{BitVec, CompressionCode, Connection, PIXEL_HEIGHT, PIXEL_WIDTH, PixelGrid}; use servicepoint2::Command::BitmapLinearAnd; #[derive(Parser, Debug)] @@ -16,7 +16,7 @@ struct Cli { fn main() { env_logger::builder() - .filter_level(log::LevelFilter::Info) + .filter_level(log::LevelFilter::Debug) .init(); let cli = Cli::parse(); @@ -35,7 +35,7 @@ fn main() { let pixel_data: Vec = enabled_pixels.clone().into(); let bit_vec = BitVec::load(&*pixel_data); - connection.send(BitmapLinearAnd(0, bit_vec)).unwrap(); + connection.send(BitmapLinearAnd(0, bit_vec, CompressionCode::Gz)).unwrap(); thread::sleep(sleep_duration); } } diff --git a/src/command.rs b/src/command.rs index b9f1c5b..83d4501 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,5 +1,9 @@ +use std::io::Read; +use flate2::bufread::GzEncoder; +use flate2::Compression; +use flate2::read::GzDecoder; use crate::{BitVec, ByteGrid, Header, Packet, PixelGrid, TILE_SIZE}; -use crate::command_codes::CommandCode; +use crate::command_codes::{CommandCode, CompressionCode}; /// An origin marks the top left position of the /// data sent to the display. @@ -29,28 +33,14 @@ pub enum Command { Brightness(Brightness), #[deprecated] BitmapLegacy, - BitmapLinear(Offset, BitVec), - BitmapLinearAnd(Offset, BitVec), - BitmapLinearOr(Offset, BitVec), - BitmapLinearXor(Offset, BitVec), + BitmapLinear(Offset, BitVec, CompressionCode), + BitmapLinearAnd(Offset, BitVec, CompressionCode), + BitmapLinearOr(Offset, BitVec, CompressionCode), + BitmapLinearXor(Offset, BitVec, CompressionCode), Cp437Data(Origin, ByteGrid), BitmapLinearWin(Origin, PixelGrid), } -fn offset_and_payload(command: CommandCode, offset: Offset, payload: Vec) -> Packet { - Packet(Header(command.to_primitive(), offset, payload.len() as u16, 0, 0), payload) -} - -fn origin_size_payload(command: CommandCode, origin: Origin, size: Size, payload: Vec) -> Packet { - let Origin(x, y) = origin; - let Size(w, h) = size; - Packet(Header(command.to_primitive(), x, y, w, h), payload.into()) -} - -fn command_code_only(code: CommandCode) -> Packet { - Packet(Header(code.to_primitive(), 0x0000, 0x0000, 0x0000, 0x0000), vec!()) -} - impl Into for Command { fn into(self) -> Packet { match self { @@ -59,7 +49,6 @@ impl Into for Command { Command::HardReset => command_code_only(CommandCode::HardReset), #[allow(deprecated)] Command::BitmapLegacy => command_code_only(CommandCode::BitmapLegacy), - Command::CharBrightness(origin, grid) => { origin_size_payload(CommandCode::CharBrightness, origin, @@ -69,10 +58,6 @@ impl Into for Command { Command::Brightness(brightness) => { Packet(Header(CommandCode::Brightness.to_primitive(), 0x00000, 0x0000, 0x0000, 0x0000), vec!(brightness)) } - - Command::BitmapLinear(offset, bits) => { - offset_and_payload(CommandCode::BitmapLinear, offset, bits.into()) - } Command::BitmapLinearWin(Origin(pixel_x, pixel_y), pixels) => { debug_assert_eq!(pixel_x % 8, 0); debug_assert_eq!(pixels.width % 8, 0); @@ -84,14 +69,17 @@ impl Into for Command { pixels.height as u16), pixels.into()) } - Command::BitmapLinearAnd(offset, bits) => { - offset_and_payload(CommandCode::BitmapLinearAnd, offset, bits.into()) + Command::BitmapLinear(offset, bits, compression) => { + bitmap_linear_into_packet(CommandCode::BitmapLinear, offset, compression, bits.into()) } - Command::BitmapLinearOr(offset, bits) => { - offset_and_payload(CommandCode::BitmapLinearOr, offset, bits.into()) + Command::BitmapLinearAnd(offset, bits, compression) => { + bitmap_linear_into_packet(CommandCode::BitmapLinearAnd, offset, compression, bits.into()) } - Command::BitmapLinearXor(offset, bits) => { - offset_and_payload(CommandCode::BitmapLinearXor, offset, bits.into()) + Command::BitmapLinearOr(offset, bits, compression) => { + bitmap_linear_into_packet(CommandCode::BitmapLinearOr, offset, compression, bits.into()) + } + Command::BitmapLinearXor(offset, bits, compression) => { + bitmap_linear_into_packet(CommandCode::BitmapLinearXor, offset, compression, bits.into()) } Command::Cp437Data(origin, grid) => { origin_size_payload(CommandCode::Cp437Data, @@ -108,86 +96,62 @@ pub enum TryFromPacketError { InvalidCommand(u16), UnexpectedPayloadSize(usize, usize), ExtraneousHeaderValues, - UnsupportedSubcommand(u16), -} - -fn check_empty_header(packet: &Packet) -> Option { - let Packet(Header(_, a, b, c, d), _) = &packet; - if *a != 0 || *b != 0 || *c != 0 || *d != 0 { - Some(TryFromPacketError::ExtraneousHeaderValues) - } else { - None - } -} - -fn check_command_only(packet: &Packet) -> Option { - let Packet(_, payload) = packet; - if payload.len() != 0 { - Some(TryFromPacketError::UnexpectedPayloadSize(0, payload.len())) - } else { - check_empty_header(packet) - } -} - -fn check_linear_bitmap(packet: &Packet) -> Option { - let Packet(Header(_, _, length, sub, reserved), payload) = packet; - if *reserved != 0 { - return Some(TryFromPacketError::ExtraneousHeaderValues); - } - if *sub != 0 { - return Some(TryFromPacketError::UnsupportedSubcommand(*sub)); - } - if payload.len() != *length as usize { - return Some(TryFromPacketError::UnexpectedPayloadSize(*length as usize, payload.len())); - } - None + InvalidCompressionCode(u16), + DecompressionFailed(std::io::Error), } impl TryFrom for Command { type Error = TryFromPacketError; fn try_from(value: Packet) -> Result { - let Packet(Header(command_u16, a, b, c, d), payload) = &value; - let command_code = match CommandCode::from_primitive(*command_u16) { - None => return Err(TryFromPacketError::InvalidCommand(*command_u16)), + let Packet(Header(command_u16, a, b, c, d), _) = value; + let command_code = match CommandCode::from_primitive(command_u16) { + None => return Err(TryFromPacketError::InvalidCommand(command_u16)), Some(value) => value }; match command_code { CommandCode::Clear => { - if let Some(err) = check_command_only(&value) { - return Err(err); + match check_command_only(value) { + Some(err) => Err(err), + None => Ok(Command::Clear), } - Ok(Command::Clear) } CommandCode::Brightness => { - if let Some(err) = check_empty_header(&value) { - return Err(err); + let Packet(header, payload) = value; + if payload.len() != 1 { + return Err(TryFromPacketError::UnexpectedPayloadSize(1, payload.len())); + } + + match check_empty_header(header) { + Some(err) => Err(err), + None => Ok(Command::Brightness(payload[0])), } - Ok(Command::Brightness(payload[0])) } CommandCode::HardReset => { - if let Some(err) = check_command_only(&value) { - return Err(err); + match check_command_only(value) { + Some(err) => Err(err), + None => Ok(Command::HardReset), } - Ok(Command::HardReset) } CommandCode::FadeOut => { - if let Some(err) = check_command_only(&value) { - return Err(err); + match check_command_only(value) { + Some(err) => Err(err), + None => Ok(Command::FadeOut), } - Ok(Command::FadeOut) } CommandCode::Cp437Data => { + let Packet(_, payload) = value; Ok(Command::Cp437Data( - Origin(*a, *b), - ByteGrid::load(*c as usize, *d as usize, payload), + Origin(a, b), + ByteGrid::load(c as usize, d as usize, &payload), )) } CommandCode::CharBrightness => { + let Packet(_, payload) = value; Ok(Command::CharBrightness( - Origin(*a, *b), - ByteGrid::load(*c as usize, *d as usize, payload), + Origin(a, b), + ByteGrid::load(c as usize, d as usize, &payload), )) } #[allow(deprecated)] @@ -195,35 +159,109 @@ impl TryFrom for Command { Ok(Command::BitmapLegacy) } CommandCode::BitmapLinearWin => { + let Packet(_, payload) = value; Ok(Command::BitmapLinearWin( - Origin(*a * TILE_SIZE, *b), - PixelGrid::load(*c as usize * TILE_SIZE as usize, *d as usize, payload), + Origin(a * TILE_SIZE, b), + PixelGrid::load(c as usize * TILE_SIZE as usize, d as usize, &payload), )) } CommandCode::BitmapLinear => { - if let Some(err) = check_linear_bitmap(&value) { - return Err(err); - } - Ok(Command::BitmapLinear(*a, BitVec::load(payload))) + let (vec, compression) = packet_into_linear_bitmap(value)?; + Ok(Command::BitmapLinear(a, vec, compression)) } CommandCode::BitmapLinearAnd => { - if let Some(err) = check_linear_bitmap(&value) { - return Err(err); - } - Ok(Command::BitmapLinearAnd(*a, BitVec::load(payload))) + let (vec, compression) = packet_into_linear_bitmap(value)?; + Ok(Command::BitmapLinearAnd(a, vec, compression)) } CommandCode::BitmapLinearOr => { - if let Some(err) = check_linear_bitmap(&value) { - return Err(err); - } - Ok(Command::BitmapLinearOr(*a, BitVec::load(payload))) + let (vec, compression) = packet_into_linear_bitmap(value)?; + Ok(Command::BitmapLinearOr(a, vec, compression)) } CommandCode::BitmapLinearXor => { - if let Some(err) = check_linear_bitmap(&value) { - return Err(err); - } - Ok(Command::BitmapLinearXor(*a, BitVec::load(payload))) + let (vec, compression) = packet_into_linear_bitmap(value)?; + Ok(Command::BitmapLinearXor(a, vec, compression)) } } } } + +fn bitmap_linear_into_packet(command: CommandCode, offset: Offset, compression: CompressionCode, payload: Vec) -> Packet { + let payload = match compression { + CompressionCode::None => payload, + CompressionCode::Gz => { + let mut encoder = GzEncoder::new(&*payload, Compression::best()); + let mut compressed = vec!(); + match encoder.read_to_end(&mut compressed) { + Err(err) => panic!("could not compress payload: {}", err), + Ok(_) => compressed, + } + } + CompressionCode::Bz => todo!(), + CompressionCode::Lz => todo!(), + CompressionCode::Zs => todo!(), + }; + + let compression = CompressionCode::to_primitive(&compression); + Packet(Header(command.to_primitive(), offset, payload.len() as u16, compression, 0), payload) +} + +fn origin_size_payload(command: CommandCode, origin: Origin, size: Size, payload: Vec) -> Packet { + let Origin(x, y) = origin; + let Size(w, h) = size; + Packet(Header(command.to_primitive(), x, y, w, h), payload.into()) +} + +fn command_code_only(code: CommandCode) -> Packet { + Packet(Header(code.to_primitive(), 0x0000, 0x0000, 0x0000, 0x0000), vec!()) +} + +fn check_empty_header(header: Header) -> Option { + let Header(_, a, b, c, d) = header; + if a != 0 || b != 0 || c != 0 || d != 0 { + Some(TryFromPacketError::ExtraneousHeaderValues) + } else { + None + } +} + +fn check_command_only(packet: Packet) -> Option { + let Packet(Header(_, a, b, c, d), payload) = packet; + if payload.len() != 0 { + Some(TryFromPacketError::UnexpectedPayloadSize(0, payload.len())) + } else if a != 0 || b != 0 || c != 0 || d != 0 { + Some(TryFromPacketError::ExtraneousHeaderValues) + } else { + None + } +} + +fn packet_into_linear_bitmap(packet: Packet) -> Result<(BitVec, CompressionCode), TryFromPacketError> { + let Packet(Header(_, _, length, sub, reserved), payload) = packet; + if reserved != 0 { + return Err(TryFromPacketError::ExtraneousHeaderValues); + } + if payload.len() != length as usize { + return Err(TryFromPacketError::UnexpectedPayloadSize(length as usize, payload.len())); + } + let sub = match CompressionCode::from_primitive(sub) { + None => return Err(TryFromPacketError::InvalidCompressionCode(sub)), + Some(value) => value + }; + let payload = match sub { + CompressionCode::None => payload, + CompressionCode::Gz => { + let mut decoder = GzDecoder::new(&*payload); + let mut decompressed = vec!(); + match decoder.read_to_end(&mut decompressed) { + Err(err) => return Err(TryFromPacketError::DecompressionFailed(err)), + Ok(_) => {} + } + decompressed + } + CompressionCode::Bz => todo!(), + CompressionCode::Lz => todo!(), + CompressionCode::Zs => todo!(), + }; + + Ok((BitVec::load(&payload), sub)) +} diff --git a/src/command_codes.rs b/src/command_codes.rs index c8c16c5..4978c99 100644 --- a/src/command_codes.rs +++ b/src/command_codes.rs @@ -27,4 +27,24 @@ impl CommandCode { pub fn to_primitive(&self) -> u16 { ToPrimitive::to_u16(self).unwrap() } -} \ No newline at end of file +} + +#[repr(u16)] +#[derive(Debug, FromPrimitive, ToPrimitive)] +pub enum CompressionCode { + None = 0x0, + Gz = 0x677a, + Bz = 0x627a, + Lz = 0x6c7a, + Zs = 0x7a73, +} + +impl CompressionCode { + pub fn from_primitive(value: u16) -> Option { + FromPrimitive::from_u16(value) + } + + pub fn to_primitive(&self) -> u16 { + ToPrimitive::to_u16(self).unwrap() + } +} diff --git a/src/connection.rs b/src/connection.rs index bb4bd77..ef9bbc5 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -17,9 +17,9 @@ impl Connection { } /// Send a command to the display - pub fn send(&self, packet: impl Into + Debug) -> std::io::Result<()> { + pub fn send(&self, packet: impl Into + Debug) -> Result<(), std::io::Error> { debug!("sending {packet:?}"); - let packet = packet.into(); + let packet: Packet = packet.into(); let data: Vec = packet.into(); self.socket.send(&*data)?; Ok(()) diff --git a/src/lib.rs b/src/lib.rs index 8db1903..6a49669 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,7 +11,7 @@ pub use crate::pixel_grid::PixelGrid; pub use crate::bit_vec::BitVec; pub use crate::packet::{Packet, Header, Payload}; pub use crate::command::{Command, Size, Origin}; -pub use crate::command_codes::CommandCode; +pub use crate::command_codes::{CommandCode, CompressionCode}; pub use crate::byte_grid::ByteGrid; pub const TILE_SIZE: u16 = 8;