Compare commits

...
Author SHA1 Message Date
vinzenz
e509e067dd Merge pull request 'make everything a trait, dont panic' (#3) from next into main
All checks were successful
Rust / build (push) Successful in 1m54s
Reviewed-on: #3
2025-05-03 11:15:52 +02:00
Vinzenz Schroeter
663adde30a update dependencies
All checks were successful
Rust / build (pull_request) Successful in 1m52s
2025-05-03 11:08:17 +02:00
Vinzenz Schroeter
4984197d95 rename BitVecU8Msb0 to DisplayBitVec 2025-05-03 11:08:17 +02:00
Vinzenz Schroeter
8e47d3c966 update README 2025-05-03 11:08:17 +02:00
Vinzenz Schroeter
b08701c342 remove SendCommandExt 2025-05-03 10:54:19 +02:00
Vinzenz Schroeter
473bbbc3f9 improve compression error logging 2025-05-03 09:35:48 +02:00
Vinzenz Schroeter
8ddbaeaaa6 v0.14.0
All checks were successful
Rust / build (pull_request) Successful in 2m6s
2025-05-01 19:55:39 +02:00
Vinzenz Schroeter
de8a1a2fe8 remove connections
All checks were successful
Rust / build (pull_request) Successful in 2m6s
2025-05-01 19:43:32 +02:00
Vinzenz Schroeter
114385868a make CommandCode pub
All checks were successful
Rust / build (pull_request) Successful in 2m19s
2025-04-12 21:27:15 +02:00
Vinzenz Schroeter
cecccb3f28 make BinaryOperation repr(u8)
All checks were successful
Rust / build (pull_request) Successful in 2m20s
2025-04-12 18:17:32 +02:00
Vinzenz Schroeter
4e3d1ce208 copy packet to slice 2025-04-12 18:17:32 +02:00
Vinzenz Schroeter
e985140417 make packet repr(C)
Some checks failed
Rust / build (pull_request) Failing after 1m0s
2025-04-12 11:20:44 +02:00
Vinzenz Schroeter
ff56215c06 rename bitvec to fix cbindgen duplicate name
Some checks failed
Rust / build (pull_request) Failing after 1m1s
2025-04-12 10:18:20 +02:00
Vinzenz Schroeter
75d24f6587 see post for details
All checks were successful
Rust / build (pull_request) Successful in 2m23s
2025-04-10 20:14:50 +02:00
Vinzenz Schroeter
62d1666ec2 update flake
All checks were successful
Rust / build (pull_request) Successful in 2m17s
2025-04-06 11:43:15 +02:00
Vinzenz Schroeter
b6f5f74fa4 fix warnings, add instructions to get smallest possible binary 2025-04-06 11:40:15 +02:00
Vinzenz Schroeter
fe67160974 fix test
All checks were successful
Rust / build (pull_request) Successful in 2m17s
2025-03-27 18:49:42 +01:00
Vinzenz Schroeter
739675a9f5 rename BrightnessCommand to GlobalBrightnessCommand, name fields in error struct
Some checks failed
Rust / build (pull_request) Failing after 1m17s
2025-03-27 18:43:09 +01:00
Vinzenz Schroeter
b3bf57301a add unit tests for into command 2025-03-27 18:13:20 +01:00
Vinzenz Schroeter
1cf37413e6 implement From<X> for XCommand
All checks were successful
Rust / build (pull_request) Successful in 2m15s
2025-03-25 22:20:44 +01:00
Vinzenz Schroeter
cbab86bd93 fix broken example
All checks were successful
Rust / build (pull_request) Successful in 2m15s
2025-03-25 21:50:50 +01:00
Vinzenz Schroeter
b69e7df635 fix warning when no compression is used
Some checks failed
Rust / build (pull_request) Failing after 2m13s
2025-03-25 21:46:18 +01:00
Vinzenz Schroeter
fe1aa3ebd1 do not use () as Err, clean up error handling
Some checks failed
Rust / build (pull_request) Failing after 1m15s
2025-03-25 21:42:17 +01:00
Vinzenz Schroeter
373d0efe55 remove TryFrom<&Origin<Pixels>> for Origin<Tiles> 2025-03-25 21:38:36 +01:00
Vinzenz Schroeter
eba1a6a6be this one is different depending on the rust version
Some checks failed
Rust / build (pull_request) Failing after 1m15s
2025-03-25 20:35:41 +01:00
Vinzenz Schroeter
617c37c713 do not run clippy on examples and tests in CI
Some checks failed
Rust / build (pull_request) Failing after 59s
2025-03-25 20:24:01 +01:00
Vinzenz Schroeter
ff886ca27d more clippy fixes and/or whitelists
Some checks failed
Rust / build (pull_request) Failing after 1m5s
2025-03-25 20:17:01 +01:00
Vinzenz Schroeter
61f83a7042 more clippy fixes and/or whitelists 2025-03-25 20:01:04 +01:00
Vinzenz Schroeter
9f239ec71d more clippy fixes and/or whitelists
Some checks failed
Rust / build (pull_request) Failing after 1m6s
2025-03-25 19:55:15 +01:00
Vinzenz Schroeter
5ba01ec4cc cargo clippy --fix 2025-03-25 19:20:55 +01:00
Vinzenz Schroeter
3384cc4ee9 even stricter lints, first fixes 2025-03-25 19:18:21 +01:00
Vinzenz Schroeter
2c3d31f649 remove redundant CP437 ZST 2025-03-25 19:11:39 +01:00
Vinzenz Schroeter
fbd42d7c47 add script to generate coverage report 2025-03-25 18:55:17 +01:00
Vinzenz Schroeter
05ab631eb6 make Origin::ZERO the Default::default()
Some checks failed
Rust / build (pull_request) Failing after 1m7s
2025-03-25 18:48:23 +01:00
Vinzenz Schroeter
bf2b320c81 add missing docs 2025-03-25 18:47:53 +01:00
Vinzenz Schroeter
2d72ee05a7 add more must_use annotations 2025-03-25 18:42:38 +01:00
Vinzenz Schroeter
5e38ced392 reorder fields by importance 2025-03-25 18:11:31 +01:00
Vinzenz Schroeter
44fe6961e7 into packet can fail 2025-03-21 14:56:31 +01:00
Vinzenz Schroeter
08ed6a6fee add a bunch of lints and change more panics to result/option
Some checks failed
Rust / build (pull_request) Failing after 1m7s
2025-03-12 22:45:29 +01:00
Vinzenz Schroeter
4ccbd57ba8 add missing docs, clippy
Some checks failed
Rust / build (pull_request) Failing after 1m5s
2025-03-08 18:23:05 +01:00
Vinzenz Schroeter
28f0bd5903 add tests, fix bug
Some checks failed
Rust / build (pull_request) Failing after 1m4s
2025-03-08 18:13:51 +01:00
Vinzenz Schroeter
18db901fb5 do not panic in ValueGrid 2025-03-08 17:50:22 +01:00
Vinzenz Schroeter
b178b48834 add tests 2025-03-08 17:37:13 +01:00
Vinzenz Schroeter
7cd26cd50e move tests to the module they test 2025-03-08 17:09:26 +01:00
Vinzenz Schroeter
c8a38870d1 brightness to command not packet, move docs, clippy 2025-03-08 14:26:14 +01:00
Vinzenz Schroeter
d6229ece87 adjust mod structure
Some checks failed
Rust / build (pull_request) Failing after 1m4s
2025-03-08 12:09:07 +01:00
Vinzenz Schroeter
03f84c337f adjust tests for higher coverage 2025-03-08 11:56:49 +01:00
Vinzenz Schroeter
2ff49aaf7a move TypedCommand to own mod 2025-03-08 11:47:25 +01:00
Vinzenz Schroeter
159abd36d9 fix docs
Some checks failed
Rust / build (pull_request) Failing after 1m3s
2025-03-08 11:41:56 +01:00
Vinzenz Schroeter
8022b65991 move containers into own mod 2025-03-08 11:32:12 +01:00
Vinzenz Schroeter
e3fc56c200 rename commands, add suffix and export on top level 2025-03-08 11:25:29 +01:00
Vinzenz Schroeter
9bff9bd346 do not panic if bitmap has invalid parameters 2025-03-08 10:06:25 +01:00
Vinzenz Schroeter
427dd93088 merge BitmapLinear-Commands
Some checks failed
Rust / build (pull_request) Failing after 1m5s
2025-03-08 00:39:08 +01:00
Vinzenz Schroeter
d195f6100a rename modules 2025-03-08 00:38:10 +01:00
Vinzenz Schroeter
c66e6db498 Command is now a trait
Some checks failed
Rust / build (pull_request) Failing after 1m4s
2025-03-07 22:51:32 +01:00
Vinzenz Schroeter
b691ef33f8 Connection is now a trait 2025-03-06 23:50:08 +01:00
Vinzenz Schroeter
111f35b242 fix panic message 2025-03-02 13:28:23 +01:00
51 changed files with 3012 additions and 2106 deletions

View file

@ -28,7 +28,7 @@ jobs:
run: sudo apt-get update && sudo apt-get install -y liblzma-dev
- name: Run Clippy
run: cargo clippy --all-targets --all-features
run: cargo clippy --all-features
- name: no features -- test (without doctest)
run: cargo test --lib --no-default-features

3
.gitignore vendored
View file

@ -4,4 +4,5 @@ out
.direnv
.envrc
result
mutants.*
mutants.*
tarpaulin-report.html

288
Cargo.lock generated
View file

@ -58,6 +58,12 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "bitflags"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
[[package]]
name = "bitvec"
version = "1.0.1"
@ -70,52 +76,30 @@ dependencies = [
"wyz",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9"
[[package]]
name = "bzip2"
version = "0.5.1"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75b89e7c29231c673a61a46e722602bcd138298f6b9e81e71119693534585f5c"
checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47"
dependencies = [
"bzip2-sys",
]
[[package]]
name = "bzip2-sys"
version = "0.1.12+1.0.8"
version = "0.1.13+1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72ebc2f1a417f01e1da30ef264ee86ae31d2dcd2d603ea283d3c244a883ca2a9"
checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14"
dependencies = [
"cc",
"libc",
"pkg-config",
]
[[package]]
name = "cc"
version = "1.2.14"
version = "1.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c3d1b2e905a3a7b00a6141adb0e4c0bb941d11caf55349d863942a1cc44e3c9"
checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0"
dependencies = [
"jobserver",
"libc",
@ -130,9 +114,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.5.29"
version = "4.5.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8acebd8ad879283633b343856142139f2da2317c96b05b4dd6181c61e2480184"
checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071"
dependencies = [
"clap_builder",
"clap_derive",
@ -140,9 +124,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.29"
version = "4.5.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ba32cbda51c7e1dfd49acc1457ba1a7dec5b64fe360e828acb13ca8dc9c2f9"
checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2"
dependencies = [
"anstream",
"anstyle",
@ -152,9 +136,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.5.28"
version = "4.5.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed"
checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7"
dependencies = [
"heck",
"proc-macro2",
@ -174,15 +158,6 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]]
name = "crc32fast"
version = "1.4.2"
@ -192,48 +167,16 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "data-encoding"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010"
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "flate2"
version = "1.0.35"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "funty"
version = "2.0.0"
@ -241,24 +184,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]]
name = "generic-array"
version = "0.14.7"
name = "getrandom"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
dependencies = [
"typenum",
"version_check",
"cfg-if",
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
]
[[package]]
name = "getrandom"
version = "0.2.15"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0"
dependencies = [
"cfg-if",
"libc",
"wasi",
"r-efi",
"wasi 0.14.2+wasi-0.2.4",
]
[[package]]
@ -267,104 +212,88 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "http"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]]
name = "httparse"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itoa"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
[[package]]
name = "jobserver"
version = "0.1.32"
version = "0.1.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
dependencies = [
"getrandom 0.3.2",
"libc",
]
[[package]]
name = "libc"
version = "0.2.169"
version = "0.2.172"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
[[package]]
name = "log"
version = "0.4.25"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "miniz_oxide"
version = "0.8.4"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b"
checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a"
dependencies = [
"adler2",
]
[[package]]
name = "once_cell"
version = "1.20.3"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "pkg-config"
version = "0.3.31"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "ppv-lite86"
version = "0.2.20"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy",
]
[[package]]
name = "proc-macro2"
version = "1.0.93"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.38"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
[[package]]
name = "radium"
version = "0.7.0"
@ -398,7 +327,7 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
"getrandom 0.2.16",
]
[[package]]
@ -413,7 +342,7 @@ dependencies = [
[[package]]
name = "servicepoint"
version = "0.13.2"
version = "0.14.0"
dependencies = [
"bitvec",
"bzip2",
@ -424,21 +353,9 @@ dependencies = [
"rand",
"rust-lzma",
"thiserror",
"tungstenite",
"zstd",
]
[[package]]
name = "sha1"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "shlex"
version = "1.3.0"
@ -453,9 +370,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.98"
version = "2.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
dependencies = [
"proc-macro2",
"quote",
@ -470,59 +387,29 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "thiserror"
version = "2.0.11"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.11"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tungstenite"
version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413083a99c579593656008130e29255e54dcaae495be556cc26888f211648c24"
dependencies = [
"byteorder",
"bytes",
"data-encoding",
"http",
"httparse",
"log",
"rand",
"sha1",
"thiserror",
"utf-8",
]
[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "unicode-ident"
version = "1.0.16"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "utf8parse"
@ -536,18 +423,21 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasi"
version = "0.14.2+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
dependencies = [
"wit-bindgen-rt",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
@ -621,6 +511,15 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "wit-bindgen-rt"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
"bitflags",
]
[[package]]
name = "wyz"
version = "0.5.1"
@ -632,19 +531,18 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.7.35"
version = "0.8.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
dependencies = [
"byteorder",
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
version = "0.8.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
dependencies = [
"proc-macro2",
"quote",
@ -653,27 +551,27 @@ dependencies = [
[[package]]
name = "zstd"
version = "0.13.2"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9"
checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
dependencies = [
"zstd-safe",
]
[[package]]
name = "zstd-safe"
version = "7.2.1"
version = "7.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059"
checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
dependencies = [
"zstd-sys",
]
[[package]]
name = "zstd-sys"
version = "2.0.13+zstd.1.5.6"
version = "2.0.15+zstd.1.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa"
checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237"
dependencies = [
"cc",
"pkg-config",

View file

@ -1,6 +1,6 @@
[package]
name = "servicepoint"
version = "0.13.2"
version = "0.14.0"
publish = true
edition = "2021"
license = "GPL-3.0-or-later"
@ -9,6 +9,7 @@ homepage = "https://docs.rs/crate/servicepoint"
repository = "https://git.berlin.ccc.de/servicepoint/servicepoint"
readme = "README.md"
keywords = ["cccb", "cccb-servicepoint"]
rust-version = "1.70.0"
[lib]
crate-type = ["rlib"]
@ -21,20 +22,17 @@ 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 = "2.0"
[features]
default = ["compression_lzma", "protocol_udp", "cp437"]
default = ["compression_lzma", "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]]
@ -45,16 +43,55 @@ required-features = ["rand"]
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.rust]
missing-docs = "warn"
deprecated-safe = "warn"
future-incompatible = "warn"
keyword-idents = "warn"
let-underscore = "warn"
nonstandard-style = "warn"
refining_impl_trait_reachable = "warn"
rust-2024-compatibility = "warn"
[lints.clippy]
## Categories
complexity = {level = "warn", priority = -1 }
perf = {level = "warn", priority = -1 }
style = {level = "warn", priority = -1 }
pedantic = {level = "warn", priority = -1 }
## Blacklist
unwrap_used = "warn"
expect_used = "warn"
panic = "warn"
incompatible_msrv = "forbid"
allow_attributes_without_reason = "warn"
## Whitelist
# Too many false positives as often a module only contains one struct that is re-exported at top-level
module_name_repetitions = "allow"
# The pretty detailed exception types should be enough for now
missing_errors_doc = "allow"
# The few places where a panic is triggered in code are inspected and should never panic
missing_panics_doc = "allow"
# Does not work for all types, but should probably be fixed at some point
iter_without_into_iter = "allow"
[lints.rustdoc]
private_doc_tests = "warn"
unescaped_backticks = "warn"
[package.metadata.docs.rs]
all-features = true
[profile.size-optimized]
inherits = "release"
opt-level = 'z' # Optimize for size
lto = true # Enable link-time optimization
codegen-units = 1 # Reduce number of codegen units to increase optimizations
panic = 'abort' # Abort on panic
strip = true # Strip symbols from binary

View file

@ -1,9 +1,11 @@
# servicepoint
[![Release](https://git.berlin.ccc.de/servicepoint/servicepoint/badges/release.svg)](https://git.berlin.ccc.de/servicepoint/servicepoint/releases)
[![crates.io](https://img.shields.io/crates/v/servicepoint.svg)](https://crates.io/crates/servicepoint)
[![Crates.io Total Downloads](https://img.shields.io/crates/d/servicepoint)](https://crates.io/crates/servicepoint)
[![docs.rs](https://img.shields.io/docsrs/servicepoint)](https://docs.rs/servicepoint/latest/servicepoint/)
[![GPLv3 licensed](https://img.shields.io/crates/l/servicepoint)](./LICENSE)
[![CI](https://git.berlin.ccc.de/servicepoint/servicepoint/badges/workflows/rust.yml/badge.svg)](https://git.berlin.ccc.de/servicepoint/servicepoint)
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".
@ -12,24 +14,22 @@ This crate contains a library for parsing, encoding and sending packets to this
The library itself is written in Rust, but can be used from multiple languages
via [language bindings](#supported-language-bindings).
This project moved
to [git.berlin.ccc.de/servicepoint/servicepoint](https://git.berlin.ccc.de/servicepoint/servicepoint).
The [GitHub repository](https://github.com/cccb/servicepoint) remains available as a mirror.
## Examples
```rust no_run
use std::net::UdpSocket;
// everything you need is in the top-level
use servicepoint::*;
fn main() {
// establish connection
let connection = Connection::open("172.23.42.29:2342")
.expect("connection failed");
// this should be the IP of the real display @CCCB
let destination = "172.23.42.29:2342";
// clear screen content
connection.send(Command::Clear)
.expect("send failed");
// establish connection
let connection = UdpSocket::bind(destination).expect("connection failed");
// clear screen content using the UdpSocketExt
connection.send_command(ClearCommand).expect("send failed");
}
```
@ -46,7 +46,7 @@ or
```toml
[dependencies]
servicepoint = "0.13.2"
servicepoint = "0.14.0"
```
## Note on stability
@ -61,21 +61,32 @@ There should be no breaking changes in patch releases, but there may also be fea
All of this means for you: please specify the full version including patch in your Cargo.toml until 1.0 is released.
Release notes are published [here](https://git.berlin.ccc.de/servicepoint/servicepoint/releases), please check them before updating.
Currently, this crate requires Rust [v1.70](https://releases.rs/docs/1.70.0/) from June 2023.
## Features
This library has multiple optional dependencies.
You can choose to (not) include them by toggling the related features.
| Name | Default | Description | Dependencies |
|--------------------|---------|----------------------------------------------|-----------------------------------------------------|
| protocol_udp | true | `Connection::Udp` | |
| cp437 | true | Conversion to and from CP-437 | [once_cell](https://crates.io/crates/once_cell) |
| compression_lzma | true | Enable additional compression algo | [rust-lzma](https://crates.io/crates/rust-lzma) |
| compression_zlib | false | Enable additional compression algo | [flate2](https://crates.io/crates/flate2) |
| compression_bzip2 | false | Enable additional compression algo | [bzip2](https://crates.io/crates/bzip2) |
| compression_zstd | false | Enable additional compression algo | [zstd](https://crates.io/crates/zstd) |
| protocol_websocket | false | `Connection::WebSocket` | [tungstenite](https://crates.io/crates/tungstenite) |
| rand | false | `impl Distribution<Brightness> for Standard` | [rand](https://crates.io/crates/rand) |
| Name | Default | Description | Dependencies |
|-------------------|---------|----------------------------------------------|-------------------------------------------------|
| cp437 | true | Conversion to and from CP-437 | [once_cell](https://crates.io/crates/once_cell) |
| compression_lzma | true | Enable additional compression algorithm | [rust-lzma](https://crates.io/crates/rust-lzma) |
| compression_zlib | false | Enable additional compression algorithm | [flate2](https://crates.io/crates/flate2) |
| compression_bzip2 | false | Enable additional compression algorithm | [bzip2](https://crates.io/crates/bzip2) |
| compression_zstd | false | Enable additional compression algorithm | [zstd](https://crates.io/crates/zstd) |
| rand | false | `impl Distribution<Brightness> for Standard` | [rand](https://crates.io/crates/rand) |
Es an example, if you only want zlib compression:
```toml
[dependencies]
servicepoint = { version = "0.14.0", default-features = false, features = ["compression_zlib"] }
```
If you are looking at features to minimize binary size: take a look at the `tiny_announce`-example!
## Supported language bindings
@ -91,17 +102,14 @@ You can choose to (not) include them by toggling the related features.
## Projects using the library
- screen simulator (rust): [servicepoint-simulator](https://git.berlin.ccc.de/servicepoint/servicepoint-simulator)
- 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, can also share your screen: [servicepoint-cli](https://git.berlin.ccc.de/servicepoint/servicepoint-cli)
- [servicepoint-simulator](https://git.berlin.ccc.de/servicepoint/servicepoint-simulator): a screen simulator written in rust
- [servicepoint-tanks](https://git.berlin.ccc.de/vinzenz/servicepoint-tanks): a multiplayer game written in C# with a second screen in the browser written in React/Typescript
- [servicepoint-life](https://git.berlin.ccc.de/vinzenz/servicepoint-life): a cellular automata slideshow written in rust
- [servicepoint-cli](https://git.berlin.ccc.de/servicepoint/servicepoint-cli): a CLI that can:
- share (stream) your screen
- send image files with dithering
- clear the display
- ...
To add yourself to the list, open a pull request.
@ -110,10 +118,28 @@ bigger collection of projects, including some not related to this library.
If you have access, there is even more software linked in [the wiki](https://wiki.berlin.ccc.de/LED-Riesendisplay).
Some more related projects:
- [cccb-servicepoint-browser](https://github.com/SamuelScheit/cccb-servicepoint-browser): a partial typescript implementation inspired by this library and browser stream
- [arfst23/ServicePoint](https://github.com/arfst23/ServicePoint): a bunch of projects in C that [used to](https://zerforschen.plus/posts/tiny-binaries-rust/) use the C bindings
- 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
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md).
You are welcome to contribute, see [CONTRIBUTING.md](CONTRIBUTING.md).
## What happened to servicepoint2?
## History
After `servicepoint2` has been merged into `servicepoint`, `servicepoint2` will not continue to get any updates.
### Move to Forgejo
This project moved
to [git.berlin.ccc.de/servicepoint/servicepoint](https://git.berlin.ccc.de/servicepoint/servicepoint).
The [GitHub repository](https://github.com/cccb/servicepoint) remains available as a mirror.
### What happened to servicepoint2?
`servicepoint2` was a fork of `servicepoint`. Since `servicepoint2` has been merged into `servicepoint`, `servicepoint2` did not get any updates.

View file

@ -1,7 +1,10 @@
//! An example for how to send text to the display.
use clap::Parser;
use servicepoint::*;
use servicepoint::{
CharGrid, CharGridCommand, ClearCommand, UdpSocketExt, TILE_WIDTH,
};
use std::net::UdpSocket;
#[derive(Parser, Debug)]
struct Cli {
@ -31,18 +34,18 @@ fn main() {
cli.text.push("Hello, CCCB!".to_string());
}
let connection = Connection::open(&cli.destination)
let connection = UdpSocket::bind(&cli.destination)
.expect("could not connect to display");
if cli.clear {
connection
.send(Command::Clear)
.send_command(ClearCommand)
.expect("sending clear failed");
}
let text = cli.text.join("\n");
let grid = CharGrid::wrap_str(TILE_WIDTH, &text);
let command: CharGridCommand = CharGrid::wrap_str(TILE_WIDTH, &text).into();
connection
.send(Command::Utf8Data(Origin::ZERO, grid))
.send_command(command)
.expect("sending text failed");
}

View file

@ -1,7 +1,11 @@
//! Show a brightness level test pattern on screen
use clap::Parser;
use servicepoint::*;
use servicepoint::{
Bitmap, BitmapCommand, Brightness, BrightnessGrid, BrightnessGridCommand,
DataRef, Grid, UdpSocketExt, TILE_HEIGHT, TILE_WIDTH,
};
use std::net::UdpSocket;
#[derive(Parser, Debug)]
struct Cli {
@ -11,27 +15,23 @@ struct Cli {
fn main() {
let cli = Cli::parse();
let connection = Connection::open(cli.destination)
.expect("could not connect to display");
let connection =
UdpSocket::bind(cli.destination).expect("could not connect to display");
let mut pixels = Bitmap::max_sized();
pixels.fill(true);
let mut bitmap = Bitmap::max_sized();
bitmap.fill(true);
let command = Command::BitmapLinearWin(
Origin::ZERO,
pixels,
CompressionCode::default(),
);
connection.send(command).expect("send failed");
connection
.send_command(BitmapCommand::from(bitmap))
.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;
let level = (index % u8::MAX as usize) as u8 % max_brightness;
*byte = Brightness::try_from(level).unwrap();
}
connection
.send(Command::CharBrightness(Origin::ZERO, brightnesses))
.expect("send failed");
let command: BrightnessGridCommand = brightnesses.into();
connection.send_command(command).expect("send failed");
}

View file

@ -2,8 +2,8 @@
use clap::Parser;
use rand::{distributions, Rng};
use servicepoint::*;
use std::thread;
use servicepoint::{Bitmap, BitmapCommand, Grid, UdpSocketExt, FRAME_PACING};
use std::{net::UdpSocket, thread};
#[derive(Parser, Debug)]
struct Cli {
@ -16,17 +16,13 @@ struct Cli {
fn main() {
let cli = Cli::parse();
let connection = Connection::open(&cli.destination)
let connection = UdpSocket::bind(&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::default(),
);
connection.send(command).expect("could not send");
let command = BitmapCommand::from(field.clone());
connection.send_command(command).expect("could not send");
thread::sleep(FRAME_PACING);
field = iteration(field);
}
@ -39,10 +35,8 @@ fn iteration(field: Bitmap) -> Bitmap {
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)
);
let new_state =
matches!((old_state, neighbors), (true, 2 | 3) | (false, 3));
next.set(x, y, new_state);
}
}

View file

@ -1,8 +1,11 @@
//! A simple example for how to send pixel data to the display.
use clap::Parser;
use servicepoint::*;
use std::thread;
use servicepoint::{
Bitmap, BitmapCommand, Grid, UdpSocketExt, FRAME_PACING, PIXEL_HEIGHT,
PIXEL_WIDTH,
};
use std::{net::UdpSocket, thread};
#[derive(Parser, Debug)]
struct Cli {
@ -11,23 +14,19 @@ struct Cli {
}
fn main() {
let connection = Connection::open(Cli::parse().destination)
let connection = UdpSocket::bind(Cli::parse().destination)
.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 {
pixels.fill(false);
bitmap.fill(false);
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(
Origin::ZERO,
pixels.clone(),
CompressionCode::default(),
);
connection.send(command).expect("send failed");
let command = BitmapCommand::from(bitmap.clone());
connection.send_command(command).expect("send failed");
thread::sleep(FRAME_PACING);
}
}

View file

@ -3,8 +3,12 @@
use clap::Parser;
use rand::Rng;
use servicepoint::*;
use std::time::Duration;
use servicepoint::{
Bitmap, BitmapCommand, Brightness, BrightnessGrid, BrightnessGridCommand,
GlobalBrightnessCommand, Grid, Origin, UdpSocketExt,
TILE_HEIGHT, TILE_WIDTH,
};
use std::{net::UdpSocket, time::Duration};
#[derive(Parser, Debug)]
struct Cli {
@ -19,7 +23,7 @@ struct Cli {
fn main() {
let cli = Cli::parse();
let connection = Connection::open(cli.destination)
let connection = UdpSocket::bind_connect(cli.destination)
.expect("could not connect to display");
let wait_duration = Duration::from_millis(cli.wait_ms);
@ -28,17 +32,14 @@ fn main() {
let mut filled_grid = Bitmap::max_sized();
filled_grid.fill(true);
let command = Command::BitmapLinearWin(
Origin::ZERO,
filled_grid,
CompressionCode::default(),
);
connection.send(command).expect("send failed");
let command = BitmapCommand::from(filled_grid);
connection.send_command(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();
let command: GlobalBrightnessCommand = rng.r#gen::<Brightness>().into();
connection.send_command(command).unwrap();
// continuously update random windows to new random brightness
loop {
@ -54,12 +55,12 @@ fn main() {
for y in 0..h {
for x in 0..w {
luma.set(x, y, rng.gen());
luma.set(x, y, rng.r#gen());
}
}
connection
.send(Command::CharBrightness(origin, luma))
.send_command(BrightnessGridCommand { origin, grid: luma })
.unwrap();
std::thread::sleep(wait_duration);
}

47
examples/tiny_announce.rs Normal file
View file

@ -0,0 +1,47 @@
//! An example for how to send text to the display - but optimized for minimal binary size.
//!
//! See [zerforschen.plus/posts/tiny-binaries-rust](https://zerforschen.plus/posts/tiny-binaries-rust/)
//! for details.
//!
//! The bulk of optimizations are compiler options, though there are some code changes that together
//! make a huge difference.
//!
//! To build this example inside this repository for the smallest possible size, you can run:
//! ```sh
//! RUSTFLAGS="-Zlocation-detail=none -Zfmt-debug=none" \
//! cargo build \
//! --example=tiny_announce \
//! --profile=size-optimized \
//! --no-default-features --features=protocol_udp \
//! -Zbuild-std="core,std,alloc,proc_macro,panic_abort" \
//! -Zbuild-std-features="panic_immediate_abort"
//!```
//!
//! This requires unstable rust.
#![no_main]
use servicepoint::{CharGrid, CharGridCommand, ClearCommand, UdpSocketExt};
use std::net::{SocketAddr, UdpSocket};
/// This is the entry point of the example.
/// `#![no_main]` is used to remove the default rust main
/// Because we use `#![no_main]`, this is a C-style main function.
#[unsafe(no_mangle)]
pub extern "C" fn main(_argc: isize, _argv: *const *const u8) -> isize {
let addr = SocketAddr::from(([172, 23, 42, 29], 2342));
let connection = UdpSocket::bind(addr).unwrap();
connection.send_command(ClearCommand).unwrap();
let grid = CharGrid::from_vec(
5,
vec!['H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd'],
)
.unwrap();
connection
.send_command(CharGridCommand::from(grid))
.unwrap();
0
}

View file

@ -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::default(),
))
.unwrap();
}

View file

@ -1,8 +1,11 @@
//! 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;
use servicepoint::{
Bitmap, BitmapCommand, Grid, UdpSocketExt, FRAME_PACING, PIXEL_HEIGHT,
PIXEL_WIDTH,
};
use std::{net::UdpSocket, thread, time::Duration};
#[derive(Parser, Debug)]
struct Cli {
@ -20,8 +23,8 @@ fn main() {
Duration::from_millis(cli.time / PIXEL_WIDTH as u64),
);
let connection = Connection::open(cli.destination)
.expect("could not connect to display");
let connection =
UdpSocket::bind(cli.destination).expect("could not connect to display");
let mut enabled_pixels = Bitmap::max_sized();
enabled_pixels.fill(true);
@ -31,12 +34,9 @@ fn main() {
enabled_pixels.set(x_offset % PIXEL_WIDTH, y, false);
}
let command = BitmapCommand::from(enabled_pixels.clone());
connection
.send(Command::BitmapLinearWin(
Origin::ZERO,
enabled_pixels.clone(),
CompressionCode::default(),
))
.send_command(command)
.expect("could not send command to display");
thread::sleep(sleep_duration);
}

12
flake.lock generated
View file

@ -7,11 +7,11 @@
]
},
"locked": {
"lastModified": 1736429655,
"narHash": "sha256-BwMekRuVlSB9C0QgwKMICiJ5EVbLGjfe4qyueyNQyGI=",
"lastModified": 1745925850,
"narHash": "sha256-cyAAMal0aPrlb1NgzMxZqeN1mAJ2pJseDhm2m6Um8T0=",
"owner": "nix-community",
"repo": "naersk",
"rev": "0621e47bd95542b8e1ce2ee2d65d6a1f887a13ce",
"rev": "38bc60bbc157ae266d4a0c96671c6c742ee17a5f",
"type": "github"
},
"original": {
@ -22,11 +22,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1739357830,
"narHash": "sha256-9xim3nJJUFbVbJCz48UP4fGRStVW5nv4VdbimbKxJ3I=",
"lastModified": 1746183838,
"narHash": "sha256-kwaaguGkAqTZ1oK0yXeQ3ayYjs8u/W7eEfrFpFfIDFA=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "0ff09db9d034a04acd4e8908820ba0b410d7a33a",
"rev": "bf3287dac860542719fe7554e21e686108716879",
"type": "github"
},
"original": {

View file

@ -137,6 +137,9 @@
clippy
cargo-expand
cargo-tarpaulin
cargo-semver-checks
cargo-show-asm
cargo-flamegraph
];
})
];

2
generate-coverage Executable file
View file

@ -0,0 +1,2 @@
#/usr/bin/env bash
cargo tarpaulin --out Html --all-features

View file

@ -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::*;
}

View file

@ -9,15 +9,16 @@ use rand::{
/// # Examples
///
/// ```
/// # use servicepoint::{Brightness, Command, Connection};
/// # use servicepoint::*;
/// let b = Brightness::MAX;
/// let val: u8 = b.into();
///
/// let b = Brightness::try_from(7).unwrap();
/// # let connection = Connection::Fake;
/// let result = connection.send(Command::Brightness(b));
/// # let connection = FakeConnection;
/// let result = connection.send_command(GlobalBrightnessCommand::from(b));
/// ```
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)]
#[repr(transparent)]
pub struct Brightness(u8);
impl From<Brightness> for u8 {
@ -50,9 +51,10 @@ impl Brightness {
/// lowest possible brightness value, 0
pub const MIN: Brightness = Brightness(0);
/// Create a brightness value without returning an error for brightnesses above [Brightness::MAX].
/// Create a brightness value without returning an error for brightnesses above [`Brightness::MAX`].
///
/// returns: the specified value as a [Brightness], or [Brightness::MAX].
/// returns: the specified value as a [Brightness], or [`Brightness::MAX`].
#[must_use]
pub fn saturating_from(value: u8) -> Brightness {
if value > Brightness::MAX.into() {
Brightness::MAX
@ -90,7 +92,7 @@ mod tests {
fn rand_brightness() {
let mut rng = rand::thread_rng();
for _ in 0..100 {
let _: Brightness = rng.gen();
let _: Brightness = rng.r#gen();
}
}
@ -104,6 +106,10 @@ mod tests {
#[cfg(feature = "rand")]
fn test() {
let mut rng = rand::thread_rng();
assert_ne!(rng.gen::<Brightness>(), rng.gen());
// two so test failure is less likely
assert_ne!(
[rng.r#gen::<Brightness>(), rng.r#gen()],
[rng.r#gen(), rng.r#gen()]
);
}
}

View file

@ -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::{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::{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::{Command, Connection, Origin, CharGrid};
/// # 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};
/// # 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::{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};
/// # 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::{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::{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::{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::{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))
);
}
}

View file

@ -1,7 +1,8 @@
/// The u16 command codes used for the [Command]s.
#[repr(u16)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) enum CommandCode {
#[allow(missing_docs)]
pub enum CommandCode {
Clear = 0x0002,
Cp437Data = 0x0003,
CharBrightness = 0x0005,
@ -33,8 +34,12 @@ impl From<CommandCode> for u16 {
}
}
#[derive(Debug, thiserror::Error, Eq, PartialEq)]
#[error("The command code {0} is not known.")]
pub struct InvalidCommandCodeError(pub u16);
impl TryFrom<u16> for CommandCode {
type Error = ();
type Error = InvalidCommandCodeError;
/// Returns the enum value for the specified `u16` or `Error` if the code is unknown.
fn try_from(value: u16) -> Result<Self, Self::Error> {
@ -97,7 +102,7 @@ impl TryFrom<u16> for CommandCode {
value if value == CommandCode::Utf8Data as u16 => {
Ok(CommandCode::Utf8Data)
}
_ => Err(()),
_ => Err(InvalidCommandCodeError(value)),
}
}
}

Some files were not shown because too many files have changed in this diff Show more