mirror of
https://github.com/cccb/servicepoint.git
synced 2025-01-18 10:00:14 +01:00
first working version
This commit is contained in:
commit
db8370e642
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
.idea
|
7
Cargo.lock
generated
Normal file
7
Cargo.lock
generated
Normal file
|
@ -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"
|
6
Cargo.toml
Normal file
6
Cargo.toml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
[package]
|
||||||
|
name = "servicepoint2"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
44
src/bit_vec.rs
Normal file
44
src/bit_vec.rs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
use crate::Packet;
|
||||||
|
|
||||||
|
/// A vector of bits
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct BitVec {
|
||||||
|
data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BitVec> 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;
|
||||||
|
}
|
||||||
|
}
|
22
src/connection.rs
Normal file
22
src/connection.rs
Normal file
|
@ -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<Self> {
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
98
src/lib.rs
Normal file
98
src/lib.rs
Normal file
|
@ -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<u8>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Command {
|
||||||
|
Clear,
|
||||||
|
HardReset,
|
||||||
|
FadeOut,
|
||||||
|
CharBrightness(Window, Vec<Brightness>),
|
||||||
|
Brightness(Brightness),
|
||||||
|
BitmapLinear(Offset, BitVec),
|
||||||
|
BitmapLinearAnd(Offset, BitVec),
|
||||||
|
BitmapLinearOr(Offset, BitVec),
|
||||||
|
BitmapLinearXor(Offset, BitVec),
|
||||||
|
Cp437Data(Window, Vec<u8>),
|
||||||
|
BitmapLinearWin(Window, PixelGrid),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn offset_and_payload(command: u16, offset: Offset, payload: Vec<u8>) -> 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<u8>) -> 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<Command> 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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
src/main.rs
Normal file
27
src/main.rs
Normal file
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
src/pixel_grid.rs
Normal file
34
src/pixel_grid.rs
Normal file
|
@ -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<PixelGrid> for Packet {
|
||||||
|
fn from(value: PixelGrid) -> Self {
|
||||||
|
value.bit_vec.into()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue