This commit is contained in:
parent
2f7a2dfd62
commit
a492733bd3
103 changed files with 101 additions and 14569 deletions
|
|
@ -1 +0,0 @@
|
||||||
use flake
|
|
||||||
11
.github/workflows/rust.yml
vendored
11
.github/workflows/rust.yml
vendored
|
|
@ -32,14 +32,3 @@ jobs:
|
||||||
|
|
||||||
- name: build default features
|
- name: build default features
|
||||||
run: cargo build --all --verbose
|
run: cargo build --all --verbose
|
||||||
- name: build default features -- examples
|
|
||||||
run: cargo build --examples --verbose
|
|
||||||
- name: test default features
|
|
||||||
run: cargo test --all --verbose
|
|
||||||
|
|
||||||
- name: build all features
|
|
||||||
run: cargo build --all-features --verbose
|
|
||||||
- name: build all features -- examples
|
|
||||||
run: cargo build --all-features --examples --verbose
|
|
||||||
- name: test all features
|
|
||||||
run: cargo test --all --all-features --verbose
|
|
||||||
|
|
|
||||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "servicepoint-binding-uniffi"]
|
||||||
|
path = servicepoint-binding-uniffi
|
||||||
|
url = https://git.berlin.ccc.de/servicepoint/servicepoint-binding-uniffi.git
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
# Contributing
|
|
||||||
|
|
||||||
Contributions are accepted in any form (issues, documentation, feature requests, code, review, ...).
|
|
||||||
|
|
||||||
All creatures welcome.
|
|
||||||
|
|
||||||
If you have access, please contribute on the [CCCB Forgejo](https://git.berlin.ccc.de/servicepoint/servicepoint).
|
|
||||||
Contributions on GitHub will be copied over and merged there.
|
|
||||||
|
|
||||||
## Pull requests
|
|
||||||
|
|
||||||
Feel free to create a PR, even if your change is not done yet.
|
|
||||||
|
|
||||||
Mark your PR as a draft as long as you do not want it to be merged.
|
|
||||||
|
|
||||||
The main branch is supposed to be a working version, including language bindings,
|
|
||||||
which means sometimes your PR may be merged into a temporary development branch.
|
|
||||||
|
|
||||||
Unit tests and documentation are required for the core library.
|
|
||||||
|
|
||||||
## Language bindings
|
|
||||||
|
|
||||||
Pull requests for your preferred language will be accepted.
|
|
||||||
If there is no code generator, it should call the C ABI methods provided by `servicepoint_binding_c`.
|
|
||||||
It should be able to send most of the basic commands in a way the simulator accepts, receiving is
|
|
||||||
not required for the merge.
|
|
||||||
|
|
||||||
It is okay for the feature set of a language binding to lag behind the one of the rust crate.
|
|
||||||
This also means you do not have to expose a feature to all the language bindings when adding something to the core.
|
|
||||||
|
|
||||||
If your change may break other language bindings, please note that in your PR description so someone can check them.
|
|
||||||
868
Cargo.lock
generated
868
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
14
Cargo.toml
14
Cargo.toml
|
|
@ -1,17 +1,7 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = [
|
members = [
|
||||||
"crates/servicepoint",
|
"uniffi-bindgen",
|
||||||
"crates/servicepoint_binding_c",
|
"servicepoint-binding-uniffi"
|
||||||
"crates/servicepoint_binding_c/examples/lang_c",
|
|
||||||
"crates/servicepoint_binding_uniffi"
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
|
||||||
version = "0.13.1"
|
|
||||||
|
|
||||||
[workspace.lints.rust]
|
|
||||||
missing-docs = "warn"
|
|
||||||
|
|
||||||
[workspace.dependencies]
|
|
||||||
thiserror = "2.0"
|
|
||||||
|
|
|
||||||
57
README.md
57
README.md
|
|
@ -1,49 +1,34 @@
|
||||||
# servicepoint
|
# ServicePoint
|
||||||
|
|
||||||
[](https://crates.io/crates/servicepoint)
|
|
||||||
[](https://crates.io/crates/servicepoint)
|
|
||||||
[](https://docs.rs/servicepoint/latest/servicepoint/)
|
|
||||||
[](./LICENSE)
|
|
||||||
|
|
||||||
In [CCCB](https://berlin.ccc.de/), there is a big pixel matrix hanging on the wall. It is called "Service Point
|
In [CCCB](https://berlin.ccc.de/), there is a big pixel matrix hanging on the wall. It is called "Service Point
|
||||||
Display" or "Airport Display".
|
Display" or "Airport Display".
|
||||||
This repository contains a library for parsing, encoding and sending packets to this display via UDP in multiple
|
|
||||||
programming languages.
|
|
||||||
|
|
||||||
This project moved to [git.berlin.ccc.de/servicepoint/servicepoint](https://git.berlin.ccc.de/servicepoint/servicepoint).
|
This crate contains C# bindings for the [servicepoint](https://git.berlin.ccc.de/servicepoint/servicepoint) library based on [servicepoint-binding-uniffi](https://git.berlin.ccc.de/servicepoint/servicepoint-binding-uniffi).
|
||||||
The [GitHub repository](https://github.com/cccb/servicepoint) remains available as a mirror.
|
|
||||||
|
|
||||||
Take a look at the contained crates for language specific information:
|
Also take a look at the main project [README](https://git.berlin.ccc.de/servicepoint/servicepoint/src/branch/main/README.md) for more
|
||||||
|
information.
|
||||||
|
|
||||||
| Crate | Languages | Readme |
|
## Note on stability
|
||||||
|-----------------------------|-----------------------------------|-----------------------------------------------------------------------------|
|
|
||||||
| servicepoint | Rust | [servicepoint](crates/servicepoint/README.md) |
|
|
||||||
| servicepoint_binding_c | C / C++ | [servicepoint_binding_c](crates/servicepoint_binding_c/README.md) |
|
|
||||||
| servicepoint_binding_uniffi | C# / Python / Go / Kotlin / Swift | [servicepoint_binding_uniffi](crates/servicepoint_binding_uniffi/README.md) |
|
|
||||||
|
|
||||||
## Projects using the library
|
This library is still in early development.
|
||||||
|
You can absolutely use it, and it works, but expect minor breaking changes with every version bump.
|
||||||
|
|
||||||
- screen simulator (rust): [servicepoint-simulator](https://git.berlin.ccc.de/servicepoint/servicepoint-simulator)
|
## Notes on differences to rust library
|
||||||
- A bunch of projects (C): [arfst23/ServicePoint](https://github.com/arfst23/ServicePoint), including
|
|
||||||
- a CLI tool to display image files on the display or use the display as a TTY
|
|
||||||
- a BSD games robots clone
|
|
||||||
- a split-flap-display simulator
|
|
||||||
- animations that play on the display
|
|
||||||
- tanks game (C#): [servicepoint-tanks](https://github.com/kaesaecracker/cccb-tanks-cs)
|
|
||||||
- cellular automata slideshow (rust): [servicepoint-life](https://github.com/kaesaecracker/servicepoint-life)
|
|
||||||
- partial typescript implementation inspired by this library and browser stream: [cccb-servicepoint-browser](https://github.com/SamuelScheit/cccb-servicepoint-browser)
|
|
||||||
- a CLI: [servicepoint-cli](https://git.berlin.ccc.de/servicepoint/servicepoint-cli)
|
|
||||||
|
|
||||||
To add yourself to the list, open a pull request.
|
- Performance will not be as good as the rust version:
|
||||||
|
- most objects are reference counted.
|
||||||
|
- objects with mutating methods will also have a MRSW lock
|
||||||
|
- You will not get rust backtraces in release builds of the native code
|
||||||
|
- Panic messages will work (PanicException)
|
||||||
|
|
||||||
You can also check out [awesome-servicepoint](https://github.com/stars/kaesaecracker/lists/awesome-servicepoint) for a bigger collection of projects, including some not related to this library.
|
## Installation
|
||||||
|
|
||||||
If you have access, there is even more software linked in [the wiki](https://wiki.berlin.ccc.de/LED-Riesendisplay).
|
Including this repository as a submodule and building from source is the recommended way of using the library.
|
||||||
|
|
||||||
## Contributing
|
```bash
|
||||||
|
git submodule add https://git.berlin.ccc.de/servicepoint/servicepoint.git
|
||||||
|
git commit -m "add servicepoint submodule"
|
||||||
|
```
|
||||||
|
|
||||||
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
Run `generate-binding.sh` to regenerate all bindings. This will also build `libservicepoint.so` (or equivalent on your
|
||||||
|
platform).
|
||||||
## What happened to servicepoint2?
|
|
||||||
|
|
||||||
After `servicepoint2` has been merged into `servicepoint`, `servicepoint2` will not continue to get any updates.
|
|
||||||
|
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
# About the display
|
|
||||||
|
|
||||||
- Resolution: 352x160=56,320 pixels
|
|
||||||
- Pixels are grouped into 44x20=880 tiles (8x8=64 pixels each)
|
|
||||||
- Smallest addressable unit: row of pixels inside of a tile (8 pixels = 1 byte)
|
|
||||||
- The brightness can only be set per tile
|
|
||||||
- Screen content can be changed using a simple UDP protocol
|
|
||||||
- Between each row of tiles, there is a gap of around 4 pixels size. This gap changes the aspect ratio of the display.
|
|
||||||
|
|
||||||
### Binary format
|
|
||||||
|
|
||||||
A UDP package sent to the display has a header size of 10 bytes.
|
|
||||||
Each header value has a size of two bytes (unsigned 16 bit integer).
|
|
||||||
Depending on the command, there can be a payload following the header.
|
|
||||||
|
|
||||||
To change screen contents, these commands are the most relevant:
|
|
||||||
|
|
||||||
1. Clear screen
|
|
||||||
- command: `0x0002`
|
|
||||||
- (rest does not matter)
|
|
||||||
2. Send CP437 data: render specified text into rectangular region
|
|
||||||
- command: `0x0003`
|
|
||||||
- top left tile x
|
|
||||||
- top left tile y
|
|
||||||
- width in tiles
|
|
||||||
- height in tiles
|
|
||||||
- payload: (width in tiles * height in tiles) bytes
|
|
||||||
- 1 byte = 1 character
|
|
||||||
- each character is rendered into one tile (mono-spaced)
|
|
||||||
- characters are encoded using code page 437
|
|
||||||
3. Send bitmap window: set pixel states for a rectangular region
|
|
||||||
- command: `0x0013`
|
|
||||||
- top left tile x
|
|
||||||
- top left _pixel_ y
|
|
||||||
- width in tiles
|
|
||||||
- height in _pixels_
|
|
||||||
- payload: (width in tiles * height in pixels) bytes
|
|
||||||
- network byte order
|
|
||||||
- 1 bit = 1 pixel
|
|
||||||
|
|
||||||
There are other commands implemented as well, e.g. for changing the brightness.
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "servicepoint"
|
|
||||||
version.workspace = true
|
|
||||||
publish = true
|
|
||||||
edition = "2021"
|
|
||||||
license = "GPL-3.0-or-later"
|
|
||||||
description = "A rust library for the CCCB Service Point Display."
|
|
||||||
homepage = "https://docs.rs/crate/servicepoint"
|
|
||||||
repository = "https://git.berlin.ccc.de/servicepoint/servicepoint"
|
|
||||||
readme = "README.md"
|
|
||||||
keywords = ["cccb", "cccb-servicepoint"]
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
crate-type = ["rlib"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
log = "0.4"
|
|
||||||
bitvec = "1.0"
|
|
||||||
flate2 = { version = "1.0", optional = true }
|
|
||||||
bzip2 = { version = "0.5", optional = true }
|
|
||||||
zstd = { version = "0.13", optional = true }
|
|
||||||
rust-lzma = { version = "0.6", optional = true }
|
|
||||||
rand = { version = "0.8", optional = true }
|
|
||||||
tungstenite = { version = "0.26", optional = true }
|
|
||||||
once_cell = { version = "1.20", optional = true }
|
|
||||||
thiserror.workspace = true
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["compression_lzma", "protocol_udp", "cp437"]
|
|
||||||
compression_zlib = ["dep:flate2"]
|
|
||||||
compression_bzip2 = ["dep:bzip2"]
|
|
||||||
compression_lzma = ["dep:rust-lzma"]
|
|
||||||
compression_zstd = ["dep:zstd"]
|
|
||||||
all_compressions = ["compression_zlib", "compression_bzip2", "compression_lzma", "compression_zstd"]
|
|
||||||
rand = ["dep:rand"]
|
|
||||||
protocol_udp = []
|
|
||||||
protocol_websocket = ["dep:tungstenite"]
|
|
||||||
cp437 = ["dep:once_cell"]
|
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "random_brightness"
|
|
||||||
required-features = ["rand"]
|
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "game_of_life"
|
|
||||||
required-features = ["rand"]
|
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "websocket"
|
|
||||||
required-features = ["protocol_websocket"]
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
# for examples
|
|
||||||
clap = { version = "4.5", features = ["derive"] }
|
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
|
||||||
all-features = true
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
# servicepoint
|
|
||||||
|
|
||||||
[](https://crates.io/crates/servicepoint)
|
|
||||||
[](https://crates.io/crates/servicepoint)
|
|
||||||
[](https://docs.rs/servicepoint/latest/servicepoint/)
|
|
||||||
[](../../LICENSE)
|
|
||||||
|
|
||||||
In [CCCB](https://berlin.ccc.de/), there is a big pixel matrix hanging on the wall. It is called "Service Point
|
|
||||||
Display" or "Airport Display".
|
|
||||||
This crate contains a library for parsing, encoding and sending packets to this display via UDP.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo add servicepoint
|
|
||||||
```
|
|
||||||
or
|
|
||||||
```toml
|
|
||||||
[dependencies]
|
|
||||||
servicepoint = "0.13.1"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
```rust no_run
|
|
||||||
fn main() {
|
|
||||||
// establish connection
|
|
||||||
let connection = servicepoint::Connection::open("172.23.42.29:2342")
|
|
||||||
.expect("connection failed");
|
|
||||||
|
|
||||||
// clear screen content
|
|
||||||
connection.send(servicepoint::Command::Clear)
|
|
||||||
.expect("send failed");
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
More examples are available in the crate.
|
|
||||||
Execute `cargo run --example` for a list of available examples and `cargo run --example <name>` to run one.
|
|
||||||
|
|
||||||
## Note on stability
|
|
||||||
|
|
||||||
This library can be used for creative project or just to play around with the display.
|
|
||||||
A decent coverage by unit tests prevents major problems and I also test this with my own projects, which mostly use up-to-date versions.
|
|
||||||
|
|
||||||
That being said, the API is still being worked on.
|
|
||||||
Expect minor breaking changes with every version bump.
|
|
||||||
Please specify the full version including patch in your Cargo.toml until 1.0 is released.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
This library has multiple optional dependencies.
|
|
||||||
You can choose to (not) include them by toggling the related features.
|
|
||||||
|
|
||||||
| Name | Default | Description |
|
|
||||||
|--------------------|---------|--------------------------------------------|
|
|
||||||
| compression_zlib | false | Enable additional compression algo |
|
|
||||||
| compression_bzip2 | false | Enable additional compression algo |
|
|
||||||
| compression_lzma | true | Enable additional compression algo |
|
|
||||||
| compression_zstd | false | Enable additional compression algo |
|
|
||||||
| protocol_udp | true | Connection::Udp |
|
|
||||||
| protocol_websocket | false | Connection::WebSocket |
|
|
||||||
| rand | false | impl Distribution<Brightness> for Standard |
|
|
||||||
| cp437 | true | Conversion to and from CP-437 |
|
|
||||||
|
|
||||||
## Everything else
|
|
||||||
|
|
||||||
Look at the main project [README](https://git.berlin.ccc.de/servicepoint/servicepoint/src/branch/main/README.md) for further information.
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
//! An example for how to send text to the display.
|
|
||||||
|
|
||||||
use clap::Parser;
|
|
||||||
use servicepoint::*;
|
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
|
||||||
struct Cli {
|
|
||||||
#[arg(
|
|
||||||
short,
|
|
||||||
long,
|
|
||||||
default_value = "localhost:2342",
|
|
||||||
help = "Address of the display"
|
|
||||||
)]
|
|
||||||
destination: String,
|
|
||||||
#[arg(short, long, num_args = 1.., value_delimiter = '\n',
|
|
||||||
help = "Text to send - specify multiple times for multiple lines")]
|
|
||||||
text: Vec<String>,
|
|
||||||
#[arg(
|
|
||||||
short,
|
|
||||||
long,
|
|
||||||
default_value_t = true,
|
|
||||||
help = "Clear screen before sending text"
|
|
||||||
)]
|
|
||||||
clear: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// example: `cargo run -- --text "Hallo" --text "CCCB"`
|
|
||||||
fn main() {
|
|
||||||
let mut cli = Cli::parse();
|
|
||||||
if cli.text.is_empty() {
|
|
||||||
cli.text.push("Hello, CCCB!".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
let connection = Connection::open(&cli.destination)
|
|
||||||
.expect("could not connect to display");
|
|
||||||
|
|
||||||
if cli.clear {
|
|
||||||
connection
|
|
||||||
.send(Command::Clear)
|
|
||||||
.expect("sending clear failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
let text = cli.text.join("\n");
|
|
||||||
let grid = CharGrid::wrap_str(TILE_WIDTH, &text);
|
|
||||||
connection
|
|
||||||
.send(Command::Utf8Data(Origin::ZERO, grid))
|
|
||||||
.expect("sending text failed");
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
//! Show a brightness level test pattern on screen
|
|
||||||
|
|
||||||
use clap::Parser;
|
|
||||||
use servicepoint::*;
|
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
|
||||||
struct Cli {
|
|
||||||
#[arg(short, long, default_value = "localhost:2342")]
|
|
||||||
destination: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let cli = Cli::parse();
|
|
||||||
let connection = Connection::open(cli.destination)
|
|
||||||
.expect("could not connect to display");
|
|
||||||
|
|
||||||
let mut pixels = Bitmap::max_sized();
|
|
||||||
pixels.fill(true);
|
|
||||||
|
|
||||||
let command = Command::BitmapLinearWin(
|
|
||||||
Origin::ZERO,
|
|
||||||
pixels,
|
|
||||||
CompressionCode::Uncompressed,
|
|
||||||
);
|
|
||||||
connection.send(command).expect("send failed");
|
|
||||||
|
|
||||||
let max_brightness: u8 = Brightness::MAX.into();
|
|
||||||
let mut brightnesses = BrightnessGrid::new(TILE_WIDTH, TILE_HEIGHT);
|
|
||||||
for (index, byte) in brightnesses.data_ref_mut().iter_mut().enumerate() {
|
|
||||||
let level = index as u8 % max_brightness;
|
|
||||||
*byte = Brightness::try_from(level).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
connection
|
|
||||||
.send(Command::CharBrightness(Origin::ZERO, brightnesses))
|
|
||||||
.expect("send failed");
|
|
||||||
}
|
|
||||||
|
|
@ -1,89 +0,0 @@
|
||||||
//! A simple game of life implementation to show how to render graphics to the display.
|
|
||||||
|
|
||||||
use clap::Parser;
|
|
||||||
use rand::{distributions, Rng};
|
|
||||||
use servicepoint::*;
|
|
||||||
use std::thread;
|
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
|
||||||
struct Cli {
|
|
||||||
#[arg(short, long, default_value = "localhost:2342")]
|
|
||||||
destination: String,
|
|
||||||
#[arg(short, long, default_value_t = 0.5f64)]
|
|
||||||
probability: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let cli = Cli::parse();
|
|
||||||
|
|
||||||
let connection = Connection::open(&cli.destination)
|
|
||||||
.expect("could not connect to display");
|
|
||||||
let mut field = make_random_field(cli.probability);
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let command = Command::BitmapLinearWin(
|
|
||||||
Origin::ZERO,
|
|
||||||
field.clone(),
|
|
||||||
CompressionCode::Lzma,
|
|
||||||
);
|
|
||||||
connection.send(command).expect("could not send");
|
|
||||||
thread::sleep(FRAME_PACING);
|
|
||||||
field = iteration(field);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn iteration(field: Bitmap) -> Bitmap {
|
|
||||||
let mut next = field.clone();
|
|
||||||
for x in 0..field.width() {
|
|
||||||
for y in 0..field.height() {
|
|
||||||
let old_state = field.get(x, y);
|
|
||||||
let neighbors = count_neighbors(&field, x as i32, y as i32);
|
|
||||||
|
|
||||||
let new_state = matches!(
|
|
||||||
(old_state, neighbors),
|
|
||||||
(true, 2) | (true, 3) | (false, 3)
|
|
||||||
);
|
|
||||||
next.set(x, y, new_state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
next
|
|
||||||
}
|
|
||||||
|
|
||||||
fn count_neighbors(field: &Bitmap, x: i32, y: i32) -> i32 {
|
|
||||||
let mut count = 0;
|
|
||||||
for nx in x - 1..=x + 1 {
|
|
||||||
for ny in y - 1..=y + 1 {
|
|
||||||
if nx == x && ny == y {
|
|
||||||
continue; // the cell itself does not count
|
|
||||||
}
|
|
||||||
|
|
||||||
if nx < 0
|
|
||||||
|| ny < 0
|
|
||||||
|| nx >= field.width() as i32
|
|
||||||
|| ny >= field.height() as i32
|
|
||||||
{
|
|
||||||
continue; // pixels outside the grid do not count
|
|
||||||
}
|
|
||||||
|
|
||||||
if !field.get(nx as usize, ny as usize) {
|
|
||||||
continue; // dead cells do not count
|
|
||||||
}
|
|
||||||
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
count
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_random_field(probability: f64) -> Bitmap {
|
|
||||||
let mut field = Bitmap::max_sized();
|
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
let d = distributions::Bernoulli::new(probability).unwrap();
|
|
||||||
for x in 0..field.width() {
|
|
||||||
for y in 0..field.height() {
|
|
||||||
field.set(x, y, rng.sample(d));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
field
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
//! A simple example for how to send pixel data to the display.
|
|
||||||
|
|
||||||
use clap::Parser;
|
|
||||||
use servicepoint::*;
|
|
||||||
use std::thread;
|
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
|
||||||
struct Cli {
|
|
||||||
#[arg(short, long, default_value = "localhost:2342")]
|
|
||||||
destination: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let connection = Connection::open(Cli::parse().destination)
|
|
||||||
.expect("could not connect to display");
|
|
||||||
|
|
||||||
let mut pixels = Bitmap::max_sized();
|
|
||||||
for x_offset in 0..usize::MAX {
|
|
||||||
pixels.fill(false);
|
|
||||||
|
|
||||||
for y in 0..PIXEL_HEIGHT {
|
|
||||||
pixels.set((y + x_offset) % PIXEL_WIDTH, y, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
let command = Command::BitmapLinearWin(
|
|
||||||
Origin::ZERO,
|
|
||||||
pixels.clone(),
|
|
||||||
CompressionCode::Lzma,
|
|
||||||
);
|
|
||||||
connection.send(command).expect("send failed");
|
|
||||||
thread::sleep(FRAME_PACING);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
//! A simple example for how to set brightnesses for tiles on the screen.
|
|
||||||
//! Continuously changes the tiles in a random window to random brightnesses.
|
|
||||||
|
|
||||||
use clap::Parser;
|
|
||||||
use rand::Rng;
|
|
||||||
use servicepoint::*;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
|
||||||
struct Cli {
|
|
||||||
#[arg(short, long, default_value = "localhost:2342")]
|
|
||||||
destination: String,
|
|
||||||
#[arg(short, long, default_value_t = true)]
|
|
||||||
enable_all: bool,
|
|
||||||
#[arg(short, long, default_value_t = 100, allow_negative_numbers = false)]
|
|
||||||
wait_ms: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let cli = Cli::parse();
|
|
||||||
|
|
||||||
let connection = Connection::open(cli.destination)
|
|
||||||
.expect("could not connect to display");
|
|
||||||
let wait_duration = Duration::from_millis(cli.wait_ms);
|
|
||||||
|
|
||||||
// put all pixels in on state
|
|
||||||
if cli.enable_all {
|
|
||||||
let mut filled_grid = Bitmap::max_sized();
|
|
||||||
filled_grid.fill(true);
|
|
||||||
|
|
||||||
let command = Command::BitmapLinearWin(
|
|
||||||
Origin::ZERO,
|
|
||||||
filled_grid,
|
|
||||||
CompressionCode::Lzma,
|
|
||||||
);
|
|
||||||
connection.send(command).expect("send failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
// set all pixels to the same random brightness
|
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
connection.send(Command::Brightness(rng.gen())).unwrap();
|
|
||||||
|
|
||||||
// continuously update random windows to new random brightness
|
|
||||||
loop {
|
|
||||||
let min_size = 1;
|
|
||||||
let x = rng.gen_range(0..TILE_WIDTH - min_size);
|
|
||||||
let y = rng.gen_range(0..TILE_HEIGHT - min_size);
|
|
||||||
|
|
||||||
let w = rng.gen_range(min_size..=TILE_WIDTH - x);
|
|
||||||
let h = rng.gen_range(min_size..=TILE_HEIGHT - y);
|
|
||||||
|
|
||||||
let origin = Origin::new(x, y);
|
|
||||||
let mut luma = BrightnessGrid::new(w, h);
|
|
||||||
|
|
||||||
for y in 0..h {
|
|
||||||
for x in 0..w {
|
|
||||||
luma.set(x, y, rng.gen());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
connection
|
|
||||||
.send(Command::CharBrightness(origin, luma))
|
|
||||||
.unwrap();
|
|
||||||
std::thread::sleep(wait_duration);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
//! Example for how to use the WebSocket connection
|
|
||||||
|
|
||||||
use servicepoint::{
|
|
||||||
Bitmap, Command, CompressionCode, Connection, Grid, Origin,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let connection =
|
|
||||||
Connection::open_websocket("ws://localhost:8080".parse().unwrap())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
connection.send(Command::Clear).unwrap();
|
|
||||||
|
|
||||||
let mut pixels = Bitmap::max_sized();
|
|
||||||
pixels.fill(true);
|
|
||||||
|
|
||||||
connection
|
|
||||||
.send(Command::BitmapLinearWin(
|
|
||||||
Origin::ZERO,
|
|
||||||
pixels,
|
|
||||||
CompressionCode::Lzma,
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
//! An example on how to modify the image on screen without knowing the current content.
|
|
||||||
use clap::Parser;
|
|
||||||
use servicepoint::*;
|
|
||||||
use std::thread;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
|
||||||
struct Cli {
|
|
||||||
#[arg(short, long, default_value = "localhost:2342")]
|
|
||||||
destination: String,
|
|
||||||
#[arg(short, long = "duration-ms", default_value_t = 5000)]
|
|
||||||
time: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let cli = Cli::parse();
|
|
||||||
|
|
||||||
let sleep_duration = Duration::max(
|
|
||||||
FRAME_PACING,
|
|
||||||
Duration::from_millis(cli.time / PIXEL_WIDTH as u64),
|
|
||||||
);
|
|
||||||
|
|
||||||
let connection = Connection::open(cli.destination)
|
|
||||||
.expect("could not connect to display");
|
|
||||||
|
|
||||||
let mut enabled_pixels = Bitmap::max_sized();
|
|
||||||
enabled_pixels.fill(true);
|
|
||||||
|
|
||||||
for x_offset in 0..PIXEL_WIDTH {
|
|
||||||
for y in 0..PIXEL_HEIGHT {
|
|
||||||
enabled_pixels.set(x_offset % PIXEL_WIDTH, y, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection
|
|
||||||
.send(Command::BitmapLinearWin(
|
|
||||||
Origin::ZERO,
|
|
||||||
enabled_pixels.clone(),
|
|
||||||
CompressionCode::Lzma,
|
|
||||||
))
|
|
||||||
.expect("could not send command to display");
|
|
||||||
thread::sleep(sleep_duration);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
/// A byte-packed vector of booleans.
|
|
||||||
///
|
|
||||||
/// The implementation is provided by [bitvec].
|
|
||||||
/// This is an alias for the specific type of [bitvec::BitVec] used in this crate.
|
|
||||||
pub type BitVec = bitvec::BitVec<u8, bitvec::Msb0>;
|
|
||||||
|
|
||||||
pub mod bitvec {
|
|
||||||
//! Re-export of the used library [mod@bitvec].
|
|
||||||
pub use bitvec::prelude::*;
|
|
||||||
}
|
|
||||||
|
|
@ -1,379 +0,0 @@
|
||||||
use crate::data_ref::DataRef;
|
|
||||||
use crate::BitVec;
|
|
||||||
use crate::*;
|
|
||||||
use ::bitvec::order::Msb0;
|
|
||||||
use ::bitvec::prelude::BitSlice;
|
|
||||||
use ::bitvec::slice::IterMut;
|
|
||||||
|
|
||||||
/// A fixed-size 2D grid of booleans.
|
|
||||||
///
|
|
||||||
/// The values are stored in packed bytes (8 values per byte) in the same order as used by the display for storing pixels.
|
|
||||||
/// This means that no conversion is necessary for sending the data to the display.
|
|
||||||
/// The downside is that the width has to be a multiple of 8.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// use servicepoint::Bitmap;
|
|
||||||
/// let mut bitmap = Bitmap::new(8, 2);
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub struct Bitmap {
|
|
||||||
width: usize,
|
|
||||||
height: usize,
|
|
||||||
bit_vec: BitVec,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Bitmap {
|
|
||||||
/// Creates a new [Bitmap] with the specified dimensions.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// - `width`: size in pixels in x-direction
|
|
||||||
/// - `height`: size in pixels in y-direction
|
|
||||||
///
|
|
||||||
/// returns: [Bitmap] initialized to all pixels off
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// - when the width is not dividable by 8
|
|
||||||
pub fn new(width: usize, height: usize) -> Self {
|
|
||||||
assert_eq!(
|
|
||||||
width % 8,
|
|
||||||
0,
|
|
||||||
"width must be a multiple of 8, but is {width}"
|
|
||||||
);
|
|
||||||
Self {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
bit_vec: BitVec::repeat(false, width * height),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new pixel grid with the size of the whole screen.
|
|
||||||
#[must_use]
|
|
||||||
pub fn max_sized() -> Self {
|
|
||||||
Self::new(PIXEL_WIDTH, PIXEL_HEIGHT)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Loads a [Bitmap] with the specified dimensions from the provided data.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// - `width`: size in pixels in x-direction
|
|
||||||
/// - `height`: size in pixels in y-direction
|
|
||||||
///
|
|
||||||
/// returns: [Bitmap] that contains a copy of the provided data
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// - when the dimensions and data size do not match exactly.
|
|
||||||
/// - when the width is not dividable by 8
|
|
||||||
#[must_use]
|
|
||||||
pub fn load(width: usize, height: usize, data: &[u8]) -> Self {
|
|
||||||
assert_eq!(width % 8, 0, "width must be a multiple of 8, but is {width}");
|
|
||||||
assert_eq!(data.len(), height * width / 8, "data length must match dimensions, with 8 pixels per byte.");
|
|
||||||
Self {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
bit_vec: BitVec::from_slice(data),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a [Bitmap] with the specified width from the provided [BitVec] without copying it.
|
|
||||||
///
|
|
||||||
/// returns: [Bitmap] that contains the provided data.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// - when the bitvec size is not dividable by the provided width
|
|
||||||
/// - when the width is not dividable by 8
|
|
||||||
#[must_use]
|
|
||||||
pub fn from_bitvec(width: usize, bit_vec: BitVec) -> Self {
|
|
||||||
assert_eq!(width % 8, 0, "width must be a multiple of 8, but is {width}");
|
|
||||||
let len = bit_vec.len();
|
|
||||||
let height = len / width;
|
|
||||||
assert_eq!(0, len % width, "dimension mismatch - len {len} is not dividable by {width}");
|
|
||||||
Self { width, height, bit_vec }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterate over all cells in [Bitmap].
|
|
||||||
///
|
|
||||||
/// Order is equivalent to the following loop:
|
|
||||||
/// ```
|
|
||||||
/// # use servicepoint::{Bitmap, Grid};
|
|
||||||
/// # let grid = Bitmap::new(8,2);
|
|
||||||
/// for y in 0..grid.height() {
|
|
||||||
/// for x in 0..grid.width() {
|
|
||||||
/// grid.get(x, y);
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub fn iter(&self) -> impl Iterator<Item = &bool> {
|
|
||||||
self.bit_vec.iter().by_refs()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterate over all cells in [Bitmap] mutably.
|
|
||||||
///
|
|
||||||
/// Order is equivalent to the following loop:
|
|
||||||
/// ```
|
|
||||||
/// # use servicepoint::{Bitmap, Grid};
|
|
||||||
/// # let mut grid = Bitmap::new(8,2);
|
|
||||||
/// # let value = false;
|
|
||||||
/// for y in 0..grid.height() {
|
|
||||||
/// for x in 0..grid.width() {
|
|
||||||
/// grid.set(x, y, value);
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
/// ```
|
|
||||||
/// # use servicepoint::{Bitmap, Grid};
|
|
||||||
/// # let mut grid = Bitmap::new(8,2);
|
|
||||||
/// # let value = false;
|
|
||||||
/// for (index, mut pixel) in grid.iter_mut().enumerate() {
|
|
||||||
/// pixel.set(index % 2 == 0)
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub fn iter_mut(&mut self) -> IterMut<u8, Msb0> {
|
|
||||||
self.bit_vec.iter_mut()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterate over all rows in [Bitmap] top to bottom.
|
|
||||||
pub fn iter_rows(&self) -> IterRows {
|
|
||||||
IterRows {
|
|
||||||
bitmap: self,
|
|
||||||
row: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Grid<bool> for Bitmap {
|
|
||||||
/// Sets the value of the specified position in the [Bitmap].
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// - `x` and `y`: position of the cell
|
|
||||||
/// - `value`: the value to write to the cell
|
|
||||||
///
|
|
||||||
/// returns: old value of the cell
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// When accessing `x` or `y` out of bounds.
|
|
||||||
fn set(&mut self, x: usize, y: usize, value: bool) {
|
|
||||||
self.assert_in_bounds(x, y);
|
|
||||||
self.bit_vec.set(x + y * self.width, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get(&self, x: usize, y: usize) -> bool {
|
|
||||||
self.assert_in_bounds(x, y);
|
|
||||||
self.bit_vec[x + y * self.width]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the state of all pixels in the [Bitmap].
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// - `this`: instance to write to
|
|
||||||
/// - `value`: the value to set all pixels to
|
|
||||||
fn fill(&mut self, value: bool) {
|
|
||||||
self.bit_vec.fill(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn width(&self) -> usize {
|
|
||||||
self.width
|
|
||||||
}
|
|
||||||
|
|
||||||
fn height(&self) -> usize {
|
|
||||||
self.height
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DataRef<u8> for Bitmap {
|
|
||||||
fn data_ref_mut(&mut self) -> &mut [u8] {
|
|
||||||
self.bit_vec.as_raw_mut_slice()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn data_ref(&self) -> &[u8] {
|
|
||||||
self.bit_vec.as_raw_slice()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Bitmap> for Vec<u8> {
|
|
||||||
/// Turns a [Bitmap] into the underlying [`Vec<u8>`].
|
|
||||||
fn from(value: Bitmap) -> Self {
|
|
||||||
value.bit_vec.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Bitmap> for BitVec {
|
|
||||||
/// Turns a [Bitmap] into the underlying [BitVec].
|
|
||||||
fn from(value: Bitmap) -> Self {
|
|
||||||
value.bit_vec
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&ValueGrid<bool>> for Bitmap {
|
|
||||||
/// Converts a grid of [bool]s into a [Bitmap].
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// - when the width of `value` is not dividable by 8
|
|
||||||
fn from(value: &ValueGrid<bool>) -> Self {
|
|
||||||
let mut result = Self::new(value.width(), value.height());
|
|
||||||
for (mut to, from) in result.iter_mut().zip(value.iter()) {
|
|
||||||
*to = *from;
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&Bitmap> for ValueGrid<bool> {
|
|
||||||
/// Converts a [Bitmap] into a grid of [bool]s.
|
|
||||||
fn from(value: &Bitmap) -> Self {
|
|
||||||
let mut result = Self::new(value.width(), value.height());
|
|
||||||
for (to, from) in result.iter_mut().zip(value.iter()) {
|
|
||||||
*to = *from;
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct IterRows<'t> {
|
|
||||||
bitmap: &'t Bitmap,
|
|
||||||
row: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'t> Iterator for IterRows<'t> {
|
|
||||||
type Item = &'t BitSlice<u8, Msb0>;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
if self.row >= self.bitmap.height {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let start = self.row * self.bitmap.width;
|
|
||||||
let end = start + self.bitmap.width;
|
|
||||||
self.row += 1;
|
|
||||||
Some(&self.bitmap.bit_vec[start..end])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use crate::{BitVec, Bitmap, DataRef, Grid, ValueGrid};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn fill() {
|
|
||||||
let mut grid = Bitmap::new(8, 2);
|
|
||||||
assert_eq!(grid.data_ref(), [0x00, 0x00]);
|
|
||||||
|
|
||||||
grid.fill(true);
|
|
||||||
assert_eq!(grid.data_ref(), [0xFF, 0xFF]);
|
|
||||||
|
|
||||||
grid.fill(false);
|
|
||||||
assert_eq!(grid.data_ref(), [0x00, 0x00]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn get_set() {
|
|
||||||
let mut grid = Bitmap::new(8, 2);
|
|
||||||
assert!(!grid.get(0, 0));
|
|
||||||
assert!(!grid.get(1, 1));
|
|
||||||
|
|
||||||
grid.set(5, 0, true);
|
|
||||||
grid.set(1, 1, true);
|
|
||||||
assert_eq!(grid.data_ref(), [0x04, 0x40]);
|
|
||||||
|
|
||||||
assert!(grid.get(5, 0));
|
|
||||||
assert!(grid.get(1, 1));
|
|
||||||
assert!(!grid.get(1, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn load() {
|
|
||||||
let mut grid = Bitmap::new(8, 3);
|
|
||||||
for x in 0..grid.width {
|
|
||||||
for y in 0..grid.height {
|
|
||||||
grid.set(x, y, (x + y) % 2 == 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_eq!(grid.data_ref(), [0xAA, 0x55, 0xAA]);
|
|
||||||
|
|
||||||
let data: Vec<u8> = grid.into();
|
|
||||||
|
|
||||||
let grid = Bitmap::load(8, 3, &data);
|
|
||||||
assert_eq!(grid.data_ref(), [0xAA, 0x55, 0xAA]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[should_panic]
|
|
||||||
fn out_of_bounds_x() {
|
|
||||||
let vec = Bitmap::new(8, 2);
|
|
||||||
vec.get(8, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[should_panic]
|
|
||||||
fn out_of_bounds_y() {
|
|
||||||
let mut vec = Bitmap::new(8, 2);
|
|
||||||
vec.set(1, 2, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn iter() {
|
|
||||||
let grid = Bitmap::new(8, 2);
|
|
||||||
assert_eq!(16, grid.iter().count())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn iter_rows() {
|
|
||||||
let grid = Bitmap::load(8, 2, &[0x04, 0x40]);
|
|
||||||
let mut iter = grid.iter_rows();
|
|
||||||
|
|
||||||
assert_eq!(iter.next().unwrap().count_ones(), 1);
|
|
||||||
assert_eq!(iter.next().unwrap().count_ones(), 1);
|
|
||||||
assert_eq!(None, iter.next());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn iter_mut() {
|
|
||||||
let mut grid = Bitmap::new(8, 2);
|
|
||||||
for (index, mut pixel) in grid.iter_mut().enumerate() {
|
|
||||||
pixel.set(index % 2 == 0);
|
|
||||||
}
|
|
||||||
assert_eq!(grid.data_ref(), [0xAA, 0xAA]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn data_ref_mut() {
|
|
||||||
let mut grid = Bitmap::new(8, 2);
|
|
||||||
let data = grid.data_ref_mut();
|
|
||||||
data[1] = 0x0F;
|
|
||||||
assert!(grid.get(7, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn to_bitvec() {
|
|
||||||
let mut grid = Bitmap::new(8, 2);
|
|
||||||
grid.set(0, 0, true);
|
|
||||||
let bitvec: BitVec = grid.into();
|
|
||||||
assert_eq!(bitvec.as_raw_slice(), [0x80, 0x00]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn from_bool_grid() {
|
|
||||||
let original = ValueGrid::load(
|
|
||||||
8,
|
|
||||||
1,
|
|
||||||
&[true, false, true, false, true, false, true, false],
|
|
||||||
);
|
|
||||||
let converted = Bitmap::from(&original);
|
|
||||||
let reconverted = ValueGrid::from(&converted);
|
|
||||||
assert_eq!(original, reconverted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,109 +0,0 @@
|
||||||
#[cfg(feature = "rand")]
|
|
||||||
use rand::{
|
|
||||||
distributions::{Distribution, Standard},
|
|
||||||
Rng,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A display brightness value, checked for correct value range
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// # use servicepoint::{Brightness, Command, Connection};
|
|
||||||
/// let b = Brightness::MAX;
|
|
||||||
/// let val: u8 = b.into();
|
|
||||||
///
|
|
||||||
/// let b = Brightness::try_from(7).unwrap();
|
|
||||||
/// # let connection = Connection::open("127.0.0.1:2342").unwrap();
|
|
||||||
/// let result = connection.send(Command::Brightness(b));
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)]
|
|
||||||
pub struct Brightness(u8);
|
|
||||||
|
|
||||||
impl From<Brightness> for u8 {
|
|
||||||
fn from(brightness: Brightness) -> Self {
|
|
||||||
Self::from(&brightness)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&Brightness> for u8 {
|
|
||||||
fn from(brightness: &Brightness) -> Self {
|
|
||||||
brightness.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<u8> for Brightness {
|
|
||||||
type Error = u8;
|
|
||||||
|
|
||||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
|
||||||
if value > Self::MAX.0 {
|
|
||||||
Err(value)
|
|
||||||
} else {
|
|
||||||
Ok(Brightness(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Brightness {
|
|
||||||
/// highest possible brightness value, 11
|
|
||||||
pub const MAX: Brightness = Brightness(11);
|
|
||||||
/// lowest possible brightness value, 0
|
|
||||||
pub const MIN: Brightness = Brightness(0);
|
|
||||||
|
|
||||||
/// Create a brightness value without returning an error for brightnesses above [Brightness::MAX].
|
|
||||||
///
|
|
||||||
/// returns: the specified value as a [Brightness], or [Brightness::MAX].
|
|
||||||
pub fn saturating_from(value: u8) -> Brightness {
|
|
||||||
if value > Brightness::MAX.into() {
|
|
||||||
Brightness::MAX
|
|
||||||
} else {
|
|
||||||
Brightness(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Brightness {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::MAX
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "rand")]
|
|
||||||
impl Distribution<Brightness> for Standard {
|
|
||||||
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Brightness {
|
|
||||||
Brightness(rng.gen_range(Brightness::MIN.0..=Brightness::MAX.0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn brightness_from_u8() {
|
|
||||||
assert_eq!(Err(100), Brightness::try_from(100));
|
|
||||||
assert_eq!(Ok(Brightness(1)), Brightness::try_from(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg(feature = "rand")]
|
|
||||||
fn rand_brightness() {
|
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
for _ in 0..100 {
|
|
||||||
let _: Brightness = rng.gen();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn saturating_convert() {
|
|
||||||
assert_eq!(Brightness::MAX, Brightness::saturating_from(100));
|
|
||||||
assert_eq!(Brightness(5), Brightness::saturating_from(5));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg(feature = "rand")]
|
|
||||||
fn test() {
|
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
assert_ne!(rng.gen::<Brightness>(), rng.gen());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue