From db8370e642dc4feaa7557e118d09873d3ddf3624 Mon Sep 17 00:00:00 2001 From: Vinzenz Schroeter Date: Thu, 9 May 2024 23:30:18 +0200 Subject: [PATCH] first working version --- .gitignore | 2 + Cargo.lock | 7 ++++ Cargo.toml | 6 +++ src/bit_vec.rs | 44 +++++++++++++++++++++ src/connection.rs | 22 +++++++++++ src/lib.rs | 98 +++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 27 +++++++++++++ src/pixel_grid.rs | 34 ++++++++++++++++ 8 files changed, 240 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/bit_vec.rs create mode 100644 src/connection.rs create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 src/pixel_grid.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2a0038a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +.idea \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..c55f3b7 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "servicepoint2" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2684a6e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "servicepoint2" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/src/bit_vec.rs b/src/bit_vec.rs new file mode 100644 index 0000000..977f13c --- /dev/null +++ b/src/bit_vec.rs @@ -0,0 +1,44 @@ +use crate::Packet; + +/// A vector of bits +#[derive(Debug)] +pub struct BitVec { + data: Vec, +} + +impl From for Packet { + fn from(value: BitVec) -> Self { + value.data + } +} + +impl BitVec { + pub fn new(size: usize) -> BitVec { + assert_eq!(size % 8, 0); + Self { data: vec!(0; size / 8) } + } + + pub fn set(&mut self, index: usize, value: bool) -> bool { + let byte_index = index / 8; + let bit_in_byte_index = 7 - index % 8; + let bit_mask = 1 << bit_in_byte_index; + + let byte = self.data[byte_index]; + let old_value = byte & bit_mask != 0; + + self.data[byte_index] = if value { + byte | bit_mask + } else { + byte ^ bit_mask + }; + + return old_value; + } + + pub fn get(&self, index: usize) -> bool { + let byte_index = index / 8; + let bit_in_byte_index = 7 - index % 8; + let bit_mask = 1 << bit_in_byte_index; + return self.data[byte_index] & bit_mask != 0; + } +} diff --git a/src/connection.rs b/src/connection.rs new file mode 100644 index 0000000..5f77425 --- /dev/null +++ b/src/connection.rs @@ -0,0 +1,22 @@ +use std::net::{ToSocketAddrs, UdpSocket}; +use crate::{Command, Packet}; + +pub struct Connection { + socket: UdpSocket, +} + +impl Connection { + /// Open a new UDP socket and create a display instance + pub fn open(addr: impl ToSocketAddrs) -> std::io::Result { + let socket = UdpSocket::bind("0.0.0.0:0")?; + socket.connect(addr)?; + Ok(Self { socket }) + } + + /// Send a command to the display + pub fn send(&self, command: Command) -> std::io::Result<()> { + let packet: Packet = command.into(); + self.socket.send(&packet)?; + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..3ca9bc7 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,98 @@ +mod connection; +mod pixel_grid; +mod bit_vec; + +pub use crate::connection::*; +pub use crate::pixel_grid::*; +pub use crate::bit_vec::*; + +pub const TILE_SIZE: u16 = 8; +pub const TILE_WIDTH: u16 = 56; +pub const TILE_HEIGHT: u16 = 20; +pub const PIXEL_WIDTH: u16 = TILE_WIDTH * TILE_SIZE; +pub const PIXEL_HEIGHT: u16 = TILE_HEIGHT * TILE_SIZE; + +pub const PIXEL_COUNT: usize = PIXEL_WIDTH as usize * PIXEL_HEIGHT as usize; + +/// A window +#[derive(Debug)] +pub struct Window(pub Origin, pub Size); + +/// An origin marks the top left position of the +/// data sent to the display. +/// A window +#[derive(Debug)] +pub struct Origin(pub u16, pub u16); + +/// Size defines the width and height of a window +/// A window +#[derive(Debug)] +pub struct Size(pub u16, pub u16); + +type Offset = u16; + +type Brightness = u8; + +type Packet = Vec; + +#[derive(Debug)] +pub enum Command { + Clear, + HardReset, + FadeOut, + CharBrightness(Window, Vec), + Brightness(Brightness), + BitmapLinear(Offset, BitVec), + BitmapLinearAnd(Offset, BitVec), + BitmapLinearOr(Offset, BitVec), + BitmapLinearXor(Offset, BitVec), + Cp437Data(Window, Vec), + BitmapLinearWin(Window, PixelGrid), +} + +fn offset_and_payload(command: u16, offset: Offset, payload: Vec) -> Packet { + let mut packet = vec!(0u8; 10 + payload.len()); + + packet[0..=1].copy_from_slice(&u16::to_be_bytes(command)); + packet[2..=3].copy_from_slice(&u16::to_be_bytes(offset)); + packet[4..=5].copy_from_slice(&u16::to_be_bytes(payload.len() as u16)); + packet[6..=7].copy_from_slice(&[0x00, 0x00]); // subcommand 0 => no compression + packet[8..=9].copy_from_slice(&[0x00, 0x00]); // reserved + + packet[10..].copy_from_slice(&*payload); + + packet +} + +fn window_and_payload(command: u16, window: Window, payload: Vec) -> Packet { + let Window(Origin(x, y), Size(w, h)) = window; + + let mut packet = vec!(0u8; 10 + payload.len()); + packet[0..=1].copy_from_slice(&u16::to_be_bytes(command)); + packet[2..=3].copy_from_slice(&u16::to_be_bytes(x)); + packet[4..=5].copy_from_slice(&u16::to_be_bytes(y)); + packet[6..=7].copy_from_slice(&u16::to_be_bytes(w)); + packet[8..=9].copy_from_slice(&u16::to_be_bytes(h)); + + packet[10..].copy_from_slice(&*payload); + + packet +} + +impl From for Packet { + fn from(value: Command) -> Self { + match value { + Command::Clear => vec!(0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00), + Command::CharBrightness(window, payload) => window_and_payload(0x0005, window, payload), + Command::Brightness(brightness) => vec!(0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, brightness), + Command::HardReset => vec!(0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00), + Command::FadeOut => vec!(0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00), + Command::BitmapLinear(offset, payload) => offset_and_payload(0x0012, offset, payload.into()), + Command::BitmapLinearWin(window, payload) => window_and_payload(0x0013, window, payload.into()), + Command::BitmapLinearAnd(offset, payload) => offset_and_payload(0x0014, offset, payload.into()), + Command::BitmapLinearOr(offset, payload) => offset_and_payload(0x0015, offset, payload.into()), + Command::BitmapLinearXor(offset, payload) => offset_and_payload(0x0016, offset, payload.into()), + Command::Cp437Data(window, payload) => window_and_payload(0x0003, window, payload), + } + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..6a7d5bf --- /dev/null +++ b/src/main.rs @@ -0,0 +1,27 @@ +use std::thread; +use std::time::Duration; +use servicepoint2::{Command, Connection, Origin, PIXEL_HEIGHT, PIXEL_WIDTH, PixelGrid, Size, TILE_WIDTH, Window}; + +fn main() { + // 172.23.42.29 + let connection = Connection::open("localhost:2342").unwrap(); + connection.send(Command::Clear).unwrap(); + + connection.send(Command::Cp437Data(Window(Origin(0, 0), Size(3, 2)), Vec::from("abcdef"))).unwrap(); + + loop { + for x_offset in 0..usize::MAX { + let mut pixels = PixelGrid::new(PIXEL_WIDTH as usize, PIXEL_HEIGHT as usize); + for y in 0..PIXEL_HEIGHT as usize { + for x_add in 0..=y%8 { + pixels.set((y + x_offset +x_add) % PIXEL_WIDTH as usize, y, true); + } + } + + let window = Window(Origin(0, 0), Size(TILE_WIDTH, PIXEL_HEIGHT)); + connection.send(Command::BitmapLinearWin(window, pixels)).unwrap(); + + thread::sleep(Duration::from_millis(100)); + } + } +} \ No newline at end of file diff --git a/src/pixel_grid.rs b/src/pixel_grid.rs new file mode 100644 index 0000000..2a30c5b --- /dev/null +++ b/src/pixel_grid.rs @@ -0,0 +1,34 @@ +use crate::{BitVec, Packet}; + +#[derive(Debug)] +pub struct PixelGrid { + pub width: usize, + pub height: usize, + bit_vec: BitVec, +} + +impl PixelGrid { + pub fn new(width: usize, height: usize) -> PixelGrid { + assert_eq!(width % 8, 0); + assert_eq!(height % 8, 0); + Self { + width, + height, + bit_vec: BitVec::new(width * height), + } + } + + pub fn set(&mut self, x: usize, y: usize, value: bool) -> bool { + self.bit_vec.set(x + y * self.width, value) + } + + pub fn get(&self, x: usize, y: usize) -> bool { + self.bit_vec.get(x + y * self.width) + } +} + +impl From for Packet { + fn from(value: PixelGrid) -> Self { + value.bit_vec.into() + } +}