diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f80a5fa..ac1b52bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Changed +- Display PNG images on AniMe with scale, position, angle, and brightness +- AniMe display parts split out to individual crate in preparation for publishing + on crates.io + # [3.3.0] - 2021-04-3 ### Changed - Add ledmodes for G733QS diff --git a/Cargo.lock b/Cargo.lock index b3ee9a81..db0ac285 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,11 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "aho-corasick" version = "0.7.15" @@ -34,10 +40,12 @@ dependencies = [ [[package]] name = "asusctl" -version = "3.1.5" +version = "3.3.1" dependencies = [ "daemon", + "glam", "gumdrop", + "rog_anime", "rog_dbus", "rog_types", "serde_json", @@ -197,12 +205,13 @@ dependencies = [ [[package]] name = "daemon" -version = "3.3.0" +version = "3.3.1" dependencies = [ "env_logger", "intel-pstate", "log", "logind-zbus", + "rog_anime", "rog_dbus", "rog_fan_curve", "rog_types", @@ -431,6 +440,15 @@ dependencies = [ "wasi 0.10.2+wasi-snapshot-preview1", ] +[[package]] +name = "glam" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70155b56080764b8b758e91e4c63d06da0262c0c939f2cd991cd1382087147df" +dependencies = [ + "spirv-std", +] + [[package]] name = "gumdrop" version = "0.8.0" @@ -509,6 +527,12 @@ version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba4aede83fc3617411dc6993bc8c70919750c1c257c6ca6a502aed6e0e2394ae" +[[package]] +name = "libm" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" + [[package]] name = "libudev-sys" version = "0.1.4" @@ -581,6 +605,16 @@ version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + [[package]] name = "nb-connect" version = "1.0.3" @@ -657,6 +691,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -700,6 +735,12 @@ version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" +[[package]] +name = "owo-colors" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fe43bf372b08cc9ccee5144715db59c79ab00168bbe4cf0d274dc0d5f64d7f" + [[package]] name = "parking" version = "2.0.0" @@ -718,12 +759,28 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pix" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bea9d5c668f13b4a1b97d848780e00cfabf76eb83538129c264c0c6d6a968047" + [[package]] name = "pkg-config" version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" +[[package]] +name = "png_pong" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75851150081bd473079e03e2fa00e25557bcb19706e502b095ca71ce392b70ff" +dependencies = [ + "miniz_oxide", + "pix", +] + [[package]] name = "polling" version = "2.0.2" @@ -887,9 +944,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548" [[package]] -name = "rog_dbus" -version = "3.1.0" +name = "rog_anime" +version = "1.0.0" dependencies = [ + "glam", + "owo-colors", + "pix", + "png_pong", + "serde", + "serde_derive", + "zvariant", + "zvariant_derive", +] + +[[package]] +name = "rog_dbus" +version = "3.2.0" +dependencies = [ + "rog_anime", "rog_fan_curve", "rog_types", "serde_json", @@ -1044,6 +1116,27 @@ dependencies = [ "winapi", ] +[[package]] +name = "spirv-std" +version = "0.4.0-alpha.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6de6f1b80004dfc3e9e02fdf8eb32c663f3b85e3942f39d02b6540ed0d2460dd" +dependencies = [ + "num-traits", + "spirv-std-macros", +] + +[[package]] +name = "spirv-std-macros" +version = "0.4.0-alpha.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4972082b5236fd57a46cc47fbc315ad78b5ad07b33e51077c688a2fe28d6f2d" +dependencies = [ + "proc-macro2", + "quote 1.0.9", + "syn 1.0.64", +] + [[package]] name = "strum" version = "0.8.0" diff --git a/Cargo.toml b/Cargo.toml index 9462e627..3c6d9912 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["asusctl", "asus-notify", "daemon", "rog-types", "rog-dbus"] +members = ["asusctl", "asus-notify", "daemon", "rog-types", "rog-dbus", "rog-anime"] [profile.release] lto = true diff --git a/asus-notify/Cargo.toml b/asus-notify/Cargo.toml index 24b320bf..38bf2ad1 100644 --- a/asus-notify/Cargo.toml +++ b/asus-notify/Cargo.toml @@ -14,6 +14,6 @@ rog_types = { path = "../rog-types" } daemon = { path = "../daemon" } [dependencies.notify-rust] -version = "^4.0" +version = "^4.3" default-features = false features = ["z"] \ No newline at end of file diff --git a/asus-notify/src/main.rs b/asus-notify/src/main.rs index 01dd7206..0d0c71aa 100644 --- a/asus-notify/src/main.rs +++ b/asus-notify/src/main.rs @@ -9,9 +9,6 @@ fn main() -> Result<(), Box> { println!(" daemon version {}", daemon::VERSION); println!(" rog-dbus version {}", rog_dbus::VERSION); - // let mut cfg = Config::read_new()?; - // let mut last_profile = String::new(); - let (proxies, conn) = DbusProxies::new()?; let signals = Signals::new(&proxies)?; diff --git a/asusctl/Cargo.toml b/asusctl/Cargo.toml index 293e06a7..5145ddfe 100644 --- a/asusctl/Cargo.toml +++ b/asusctl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "asusctl" -version = "3.1.5" +version = "3.3.1" authors = ["Luke D Jones "] edition = "2018" @@ -9,13 +9,14 @@ edition = "2018" [dependencies] # serialisation serde_json = "^1.0" +rog_anime = { path = "../rog-anime" } rog_dbus = { path = "../rog-dbus" } rog_types = { path = "../rog-types" } daemon = { path = "../daemon" } gumdrop = "^0.8" yansi-term = "^0.1" - [dev-dependencies] tinybmp = "^0.2.3" +glam = "*" rog_dbus = { path = "../rog-dbus" } \ No newline at end of file diff --git a/asusctl/examples/animatrix-grid.rs b/asusctl/examples/animatrix-grid.rs new file mode 100644 index 00000000..1773d088 --- /dev/null +++ b/asusctl/examples/animatrix-grid.rs @@ -0,0 +1,26 @@ +use rog_anime::{AniMeDataBuffer, AniMeGrid}; +use rog_dbus::AuraDbusClient; + +// In usable data: +// Top row start at 1, ends at 32 + +fn main() { + let (client, _) = AuraDbusClient::new().unwrap(); + let mut matrix = AniMeGrid::new(); + { + let tmp = matrix.get_mut(); + for row in tmp.iter_mut() { + row[row.len() - 33] = 0xff; + + row[row.len() - 22] = 0xff; + + row[row.len() - 11] = 0xff; + + row[row.len() - 1] = 0xff; + } + } + + let matrix = ::from(matrix); + + client.proxies().anime().write(matrix).unwrap(); +} diff --git a/asusctl/examples/animatrix-outline.rs b/asusctl/examples/animatrix-outline.rs new file mode 100644 index 00000000..5efd207a --- /dev/null +++ b/asusctl/examples/animatrix-outline.rs @@ -0,0 +1,129 @@ +use rog_anime::AniMeDataBuffer; +use rog_dbus::AuraDbusClient; + +// In usable data: +// Top row start at 1, ends at 32 + +fn main() { + let (client, _) = AuraDbusClient::new().unwrap(); + let mut matrix = AniMeDataBuffer::new(); + matrix.get_mut()[1] = 100; // start = 1 + for n in matrix.get_mut()[2..32].iter_mut() { + *n = 250; + } + matrix.get_mut()[32] = 100; // end + matrix.get_mut()[34] = 100; // start x = 0 + matrix.get_mut()[66] = 100; // end + matrix.get_mut()[69] = 100; // start x = 1 + matrix.get_mut()[101] = 100; // end + matrix.get_mut()[102] = 100; // start + matrix.get_mut()[134] = 100; // end + matrix.get_mut()[137] = 100; // start + matrix.get_mut()[169] = 100; // end + matrix.get_mut()[170] = 100; // start + matrix.get_mut()[202] = 100; // end + matrix.get_mut()[204] = 100; // start + matrix.get_mut()[236] = 100; // end + matrix.get_mut()[237] = 100; // start + matrix.get_mut()[268] = 100; // end + matrix.get_mut()[270] = 100; // start + matrix.get_mut()[301] = 100; // end + matrix.get_mut()[302] = 100; // start + matrix.get_mut()[332] = 100; // end + matrix.get_mut()[334] = 100; // start + matrix.get_mut()[364] = 100; // end + matrix.get_mut()[365] = 100; // start + matrix.get_mut()[394] = 100; // end + matrix.get_mut()[396] = 100; // start + matrix.get_mut()[425] = 100; // end + matrix.get_mut()[426] = 100; // start + matrix.get_mut()[454] = 100; // end + matrix.get_mut()[456] = 100; // start + matrix.get_mut()[484] = 100; // end + matrix.get_mut()[485] = 100; // start + matrix.get_mut()[512] = 100; // end + matrix.get_mut()[514] = 100; // start + matrix.get_mut()[541] = 100; // end + matrix.get_mut()[542] = 100; // start + matrix.get_mut()[568] = 100; // end + matrix.get_mut()[570] = 100; // start + matrix.get_mut()[596] = 100; // end + matrix.get_mut()[597] = 100; // start + matrix.get_mut()[622] = 100; // end + matrix.get_mut()[624] = 100; // start + matrix.get_mut()[649] = 100; // end + matrix.get_mut()[650] = 100; // start + matrix.get_mut()[674] = 100; // end + matrix.get_mut()[676] = 100; // start + matrix.get_mut()[700] = 100; // end + matrix.get_mut()[701] = 100; // start + matrix.get_mut()[724] = 100; // end + matrix.get_mut()[726] = 100; // start + matrix.get_mut()[749] = 100; // end + matrix.get_mut()[750] = 100; // start + matrix.get_mut()[772] = 100; // end + matrix.get_mut()[774] = 100; // start + matrix.get_mut()[796] = 100; // end + matrix.get_mut()[797] = 100; // start + matrix.get_mut()[818] = 100; // end + matrix.get_mut()[820] = 100; // start + matrix.get_mut()[841] = 100; // end + matrix.get_mut()[842] = 100; // start + matrix.get_mut()[862] = 100; // end + matrix.get_mut()[864] = 100; // start + matrix.get_mut()[884] = 100; // end + matrix.get_mut()[885] = 100; // start + matrix.get_mut()[904] = 100; // end + matrix.get_mut()[906] = 100; // start + matrix.get_mut()[925] = 100; // end + matrix.get_mut()[926] = 100; // start + matrix.get_mut()[944] = 100; // end + matrix.get_mut()[946] = 100; // start + matrix.get_mut()[964] = 100; // end + matrix.get_mut()[965] = 100; // start + matrix.get_mut()[982] = 100; // end + matrix.get_mut()[984] = 100; // start + matrix.get_mut()[1001] = 100; // end + matrix.get_mut()[1002] = 100; // start + matrix.get_mut()[1018] = 100; // end + matrix.get_mut()[1020] = 100; // start + matrix.get_mut()[1036] = 100; // end + matrix.get_mut()[1037] = 100; // start + matrix.get_mut()[1052] = 100; // end + matrix.get_mut()[1054] = 100; // start + matrix.get_mut()[1069] = 100; // end + matrix.get_mut()[1070] = 100; // start + matrix.get_mut()[1084] = 100; // end + matrix.get_mut()[1086] = 100; // start + matrix.get_mut()[1100] = 100; // end + matrix.get_mut()[1101] = 100; // start + matrix.get_mut()[1114] = 100; // end + matrix.get_mut()[1116] = 100; // start + matrix.get_mut()[1129] = 100; // end + matrix.get_mut()[1130] = 100; // start + matrix.get_mut()[1142] = 100; // end + matrix.get_mut()[1144] = 100; // start + matrix.get_mut()[1156] = 100; // end + matrix.get_mut()[1157] = 100; // start + matrix.get_mut()[1168] = 100; // end + matrix.get_mut()[1170] = 100; // start + matrix.get_mut()[1181] = 100; // end + matrix.get_mut()[1182] = 100; // start + matrix.get_mut()[1192] = 100; // end + matrix.get_mut()[1194] = 100; // start + matrix.get_mut()[1204] = 100; // end + matrix.get_mut()[1205] = 100; // start + matrix.get_mut()[1214] = 100; // end + matrix.get_mut()[1216] = 100; // start + matrix.get_mut()[1225] = 100; // end + matrix.get_mut()[1226] = 100; // start + matrix.get_mut()[1234] = 100; // end + matrix.get_mut()[1236] = 100; // start + for n in matrix.get_mut()[1237..1244].iter_mut() { + *n = 250; + } + matrix.get_mut()[1244] = 100; // end + println!("{:?}", &matrix); + + client.proxies().anime().write(matrix).unwrap(); +} diff --git a/asusctl/examples/animatrix-png.rs b/asusctl/examples/animatrix-png.rs new file mode 100644 index 00000000..dc6619a3 --- /dev/null +++ b/asusctl/examples/animatrix-png.rs @@ -0,0 +1,42 @@ +use std::{env, error::Error, path::Path, process::exit}; + +use rog_anime::{ + AniMeDataBuffer, {AnimeImage, Vec2}, +}; +use rog_dbus::AuraDbusClient; + +fn main() -> Result<(), Box> { + let (client, _) = AuraDbusClient::new().unwrap(); + + let args: Vec = env::args().into_iter().collect(); + if args.len() != 8 { + println!( + "Usage: " + ); + println!("e.g, asusctl/examples/doom_large.png 0.9 0.9 0.4 0.0 0.0, 0.8"); + println!("All args except path and fineness are floats"); + exit(-1); + } + + let matrix = AnimeImage::from_png( + Path::new(&args[1]), + Vec2::new( + args[2].parse::().unwrap(), + args[3].parse::().unwrap(), + ), + args[4].parse::().unwrap(), + Vec2::new( + args[5].parse::().unwrap(), + args[6].parse::().unwrap(), + ), + args[7].parse::().unwrap(), + )?; + + client + .proxies() + .anime() + .write(::from(&matrix)) + .unwrap(); + + Ok(()) +} diff --git a/asusctl/examples/animatrix-spinning.rs b/asusctl/examples/animatrix-spinning.rs new file mode 100644 index 00000000..73bd8f2e --- /dev/null +++ b/asusctl/examples/animatrix-spinning.rs @@ -0,0 +1,49 @@ +use std::{ + env, error::Error, f32::consts::PI, path::Path, process::exit, thread::sleep, time::Duration, +}; + +use rog_anime::{ + AniMeDataBuffer, {AnimeImage, Vec2}, +}; +use rog_dbus::AuraDbusClient; + +fn main() -> Result<(), Box> { + let (client, _) = AuraDbusClient::new().unwrap(); + + let args: Vec = env::args().into_iter().collect(); + if args.len() != 8 { + println!("Usage: "); + println!("e.g, asusctl/examples/doom_large.bmp 0.9 0.9 0.4 0.0 0.0, 0.8"); + println!("All args except path and fineness are floats"); + exit(-1); + } + + let mut matrix = AnimeImage::from_png( + Path::new(&args[1]), + Vec2::new( + args[2].parse::().unwrap(), + args[3].parse::().unwrap(), + ), + args[4].parse::().unwrap(), + Vec2::new( + args[5].parse::().unwrap(), + args[6].parse::().unwrap(), + ), + args[7].parse::().unwrap(), + )?; + + loop { + matrix.angle += 0.05; + if matrix.angle > PI * 2.0 { + matrix.angle = 0.0 + } + matrix.update(); + + client + .proxies() + .anime() + .write(::from(&matrix)) + .unwrap(); + sleep(Duration::from_micros(500)); + } +} diff --git a/asusctl/examples/animatrix.rs b/asusctl/examples/animatrix.rs deleted file mode 100644 index dcaa4106..00000000 --- a/asusctl/examples/animatrix.rs +++ /dev/null @@ -1,44 +0,0 @@ -use rog_dbus::AuraDbusClient; -use rog_types::anime_matrix::{AniMeImageBuffer, HEIGHT, WIDTH}; -use tinybmp::{Bmp, Pixel}; - -fn main() { - let (client, _) = AuraDbusClient::new().unwrap(); - - let bmp = Bmp::from_slice(include_bytes!("rust.bmp")).expect("Failed to parse BMP image"); - let pixels: Vec = bmp.into_iter().collect(); - //assert_eq!(pixels.len(), 56 * 56); - - // Try an outline, top and right - let mut matrix = AniMeImageBuffer::new(); - - // Aligned left - for (i, px) in pixels.iter().enumerate() { - if (px.x as usize / 2) < WIDTH && (px.y as usize) < HEIGHT && px.x % 2 == 0 { - let c = px.color as u32; - matrix.get_mut()[px.y as usize][px.x as usize / 2] = c as u8; - } - } - - // Throw an alignment border up - { - let tmp = matrix.get_mut(); - for x in tmp[0].iter_mut() { - *x = 0xff; - } - for (i, row) in tmp.iter_mut().enumerate() { - if i % 2 == 0 { - let l = row.len(); - row[l - 1] = 0xff; - } - } - } - - matrix.debug_print(); - - //let mut matrix: AniMePacketType = AniMePacketType::from(matrix); - // println!("{:?}", matrix[0].to_vec()); - // println!("{:?}", matrix[1].to_vec()); - - client.proxies().anime().write_image(matrix).unwrap(); -} diff --git a/asusctl/examples/doom.png b/asusctl/examples/doom.png new file mode 100644 index 00000000..a4853c5a Binary files /dev/null and b/asusctl/examples/doom.png differ diff --git a/asusctl/examples/ferris.png b/asusctl/examples/ferris.png new file mode 100644 index 00000000..e20a802f Binary files /dev/null and b/asusctl/examples/ferris.png differ diff --git a/asusctl/examples/non-skewed.bmp b/asusctl/examples/non-skewed.bmp deleted file mode 100644 index 4d43c87f..00000000 Binary files a/asusctl/examples/non-skewed.bmp and /dev/null differ diff --git a/asusctl/examples/non-skewed_r.bmp b/asusctl/examples/non-skewed_r.bmp deleted file mode 100644 index 2fb40a23..00000000 Binary files a/asusctl/examples/non-skewed_r.bmp and /dev/null differ diff --git a/asusctl/examples/nudoom.png b/asusctl/examples/nudoom.png new file mode 100644 index 00000000..7b8d3a7b Binary files /dev/null and b/asusctl/examples/nudoom.png differ diff --git a/asusctl/examples/per-key-effect-2.rs b/asusctl/examples/per-key-effect-2.rs index 5f3947ad..10f10df0 100644 --- a/asusctl/examples/per-key-effect-2.rs +++ b/asusctl/examples/per-key-effect-2.rs @@ -10,7 +10,7 @@ fn main() -> Result<(), Box> { loop { let count = 49; for _ in 0..count { - *key_colours.key(Key::ROG).unwrap().0 += 5; + *key_colours.key(Key::Rog).unwrap().0 += 5; *key_colours.key(Key::L).unwrap().0 += 5; *key_colours.key(Key::I).unwrap().0 += 5; *key_colours.key(Key::N).unwrap().0 += 5; @@ -19,7 +19,7 @@ fn main() -> Result<(), Box> { dbus.proxies().led().set_per_key(&key_colours)?; } for _ in 0..count { - *key_colours.key(Key::ROG).unwrap().0 -= 5; + *key_colours.key(Key::Rog).unwrap().0 -= 5; *key_colours.key(Key::L).unwrap().0 -= 5; *key_colours.key(Key::I).unwrap().0 -= 5; *key_colours.key(Key::N).unwrap().0 -= 5; diff --git a/asusctl/examples/rust.bmp b/asusctl/examples/rust.bmp deleted file mode 100644 index a079736b..00000000 Binary files a/asusctl/examples/rust.bmp and /dev/null differ diff --git a/asusctl/examples/rust.png b/asusctl/examples/rust.png new file mode 100644 index 00000000..e9a4c2e3 Binary files /dev/null and b/asusctl/examples/rust.png differ diff --git a/asusctl/examples/test-skinny-45deg.bmp b/asusctl/examples/test-skinny-45deg.bmp deleted file mode 100644 index 4e6cf9f3..00000000 Binary files a/asusctl/examples/test-skinny-45deg.bmp and /dev/null differ diff --git a/asusctl/examples/test.bmp b/asusctl/examples/test.bmp deleted file mode 100644 index 1d5824d6..00000000 Binary files a/asusctl/examples/test.bmp and /dev/null differ diff --git a/asusctl/examples/test2.bmp b/asusctl/examples/test2.bmp deleted file mode 100644 index 589295a7..00000000 Binary files a/asusctl/examples/test2.bmp and /dev/null differ diff --git a/asusctl/src/anime_cli.rs b/asusctl/src/anime_cli.rs new file mode 100644 index 00000000..92b0dd18 --- /dev/null +++ b/asusctl/src/anime_cli.rs @@ -0,0 +1,94 @@ +use gumdrop::Options; +use rog_types::error::AuraError; +use std::str::FromStr; + +#[derive(Copy, Clone, Debug)] +pub enum AniMeStatusValue { + On, + Off, +} +impl FromStr for AniMeStatusValue { + type Err = AuraError; + + fn from_str(s: &str) -> Result { + let s = s.to_lowercase(); + match s.as_str() { + "on" => Ok(AniMeStatusValue::On), + "off" => Ok(AniMeStatusValue::Off), + _ => { + print!("Invalid argument, must be one of: on, off"); + Err(AuraError::ParseAnime) + } + } + } +} +impl From for bool { + fn from(value: AniMeStatusValue) -> Self { + match value { + AniMeStatusValue::On => true, + AniMeStatusValue::Off => false, + } + } +} + +#[derive(Options)] +pub struct AniMeLeds { + #[options(help = "print help message")] + help: bool, + #[options( + no_long, + required, + short = "b", + meta = "", + help = "set all leds brightness value" + )] + led_brightness: u8, +} +impl AniMeLeds { + pub fn led_brightness(&self) -> u8 { + self.led_brightness + } +} + +#[derive(Options)] +pub struct AniMeCommand { + #[options(help = "print help message")] + pub help: bool, + #[options( + meta = "", + help = "turn on/off the panel (accept/reject write requests)" + )] + pub turn: Option, + #[options(meta = "", help = "turn on/off the panel at boot (with Asus effect)")] + pub boot: Option, + #[options(command)] + pub command: Option, +} + +#[derive(Options)] +pub enum AniMeActions { + #[options(help = "change all leds brightness")] + Leds(AniMeLeds), + #[options(help = "display an 8bit greyscale png")] + Image(AniMeImage), +} + +#[derive(Options)] +pub struct AniMeImage { + #[options(help = "print help message")] + pub help: bool, + #[options(meta = "", help = "full path to the png to display")] + pub path: String, + #[options(meta = "", default = "0.0", help = "x scale 0.0-1.0")] + pub x_scale: f32, + #[options(meta = "", default = "0.0", help = "y scale 0.0-1.0")] + pub y_scale: f32, + #[options(meta = "", default = "0.0", help = "x position (float)")] + pub x_pos: f32, + #[options(meta = "", default = "0.0", help = "y position (float)")] + pub y_pos: f32, + #[options(meta = "", default = "0.0", help = "the angle in radians")] + pub angle: f32, + #[options(meta = "", default = "1.0", help = "brightness 0.0-1.0")] + pub bright: f32, +} diff --git a/asusctl/src/main.rs b/asusctl/src/main.rs index 4a1ddd01..657bfbe4 100644 --- a/asusctl/src/main.rs +++ b/asusctl/src/main.rs @@ -1,25 +1,29 @@ +mod anime_cli; mod aura_cli; use crate::aura_cli::{LedBrightness, SetAuraBuiltin}; +use anime_cli::{AniMeActions, AniMeCommand}; use daemon::{ ctrl_fan_cpu::FanCpuSupportedFunctions, ctrl_leds::LedSupportedFunctions, ctrl_rog_bios::RogBiosSupportedFunctions, ctrl_supported::SupportedFunctions, }; use gumdrop::{Opt, Options}; +use rog_anime::{ + AniMeDataBuffer, ANIME_DATA_LEN, + AnimeImage, Vec2, +}; use rog_dbus::AuraDbusClient; use rog_types::{ - anime_matrix::{AniMeDataBuffer, FULL_PANE_LEN}, aura_modes::{self, AuraEffect, AuraModeNum}, - cli_options::{AniMeActions, AniMeStatusValue}, gfx_vendors::GfxVendors, profile::{FanLevel, ProfileCommand, ProfileEvent}, }; -use std::env::args; +use std::{env::args, path::Path}; use yansi_term::Colour::Green; use yansi_term::Colour::Red; #[derive(Default, Options)] -struct CLIStart { +struct CliStart { #[options(help_flag, help = "print help message")] help: bool, #[options(help = "show program version number")] @@ -82,21 +86,6 @@ struct GraphicsCommand { force: bool, } -#[derive(Options)] -struct AniMeCommand { - #[options(help = "print help message")] - help: bool, - #[options( - meta = "", - help = "turn on/off the panel (accept/reject write requests)" - )] - turn: Option, - #[options(meta = "", help = "turn on/off the panel at boot (with Asus effect)")] - boot: Option, - #[options(command)] - command: Option, -} - #[derive(Options, Debug)] struct BiosCommand { #[options(help = "print help message")] @@ -118,14 +107,14 @@ struct BiosCommand { fn main() -> Result<(), Box> { let args: Vec = args().skip(1).collect(); - let parsed: CLIStart; + let parsed: CliStart; let missing_argument_k = gumdrop::Error::missing_argument(Opt::Short('k')); - match CLIStart::parse_args_default(&args) { + match CliStart::parse_args_default(&args) { Ok(p) => { parsed = p; } Err(err) if err.to_string() == missing_argument_k.to_string() => { - parsed = CLIStart { + parsed = CliStart { kbd_bright: Some(LedBrightness::new(None)), ..Default::default() }; @@ -175,9 +164,32 @@ fn main() -> Result<(), Box> { if let Some(action) = cmd.command { match action { AniMeActions::Leds(anime_leds) => { - let mut data = AniMeDataBuffer::new(); - data.set([anime_leds.led_brightness(); FULL_PANE_LEN]); - dbus.proxies().anime().write_direct(data)?; + let data = AniMeDataBuffer::from_vec( + [anime_leds.led_brightness(); ANIME_DATA_LEN].to_vec(), + ); + dbus.proxies().anime().write(data)?; + } + AniMeActions::Image(image) => { + if image.help_requested() { + println!("Missing arg or command\n\n{}", image.self_usage()); + if let Some(lst) = image.self_command_list() { + println!("\n{}", lst); + } + std::process::exit(1); + } + + let matrix = AnimeImage::from_png( + Path::new(&image.path), + Vec2::new(image.x_scale, image.y_scale), + image.angle, + Vec2::new(image.x_pos, image.y_pos), + image.bright, + )?; + + dbus.proxies() + .anime() + .write(::from(&matrix)) + .unwrap(); } } } @@ -190,9 +202,9 @@ fn main() -> Result<(), Box> { && parsed.chg_limit.is_none()) || parsed.help { - println!("{}", CLIStart::usage()); + println!("{}", CliStart::usage()); println!(); - println!("{}", CLIStart::command_list().unwrap()); + println!("{}", CliStart::command_list().unwrap()); } } } @@ -224,7 +236,7 @@ fn main() -> Result<(), Box> { Ok(()) } -fn print_supported_help(supported: &SupportedFunctions, parsed: &CLIStart) { +fn print_supported_help(supported: &SupportedFunctions, parsed: &CliStart) { // As help option don't work with `parse_args_default` // we will call `parse_args_default_or_exit` instead let usage: Vec = parsed.self_usage().lines().map(|s| s.to_string()).collect(); @@ -311,10 +323,11 @@ fn do_gfx( } if command.pow { let res = dbus.proxies().gfx().gfx_get_pwr()?; - if res.contains("active") { - println!("Current power status: {}", Red.paint(&res)); - } else { - println!("Current power status: {}", Green.paint(&res)); + match res { + rog_types::gfx_vendors::GfxPower::Active => { + println!("Current power status: {}", Red.paint(<&str>::from(&res))) + } + _ => println!("Current power status: {}", Green.paint(<&str>::from(&res))), } } Ok(()) diff --git a/daemon/Cargo.toml b/daemon/Cargo.toml index 5fe268b9..f3a5fb7a 100644 --- a/daemon/Cargo.toml +++ b/daemon/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "daemon" -version = "3.3.0" +version = "3.3.1" license = "MPL-2.0" readme = "README.md" authors = ["Luke "] @@ -18,6 +18,7 @@ name = "asusd" path = "src/daemon.rs" [dependencies] +rog_anime = { path = "../rog-anime" } rog_types = { path = "../rog-types" } rog_dbus = { path = "../rog-dbus" } rusb = "^0.7" diff --git a/daemon/src/ctrl_anime.rs b/daemon/src/ctrl_anime.rs index 09de479f..14f29051 100644 --- a/daemon/src/ctrl_anime.rs +++ b/daemon/src/ctrl_anime.rs @@ -14,12 +14,7 @@ const APPLY: u8 = 0xc4; const ON_OFF: u8 = 0x04; use log::{error, info, warn}; -use rog_types::{ - anime_matrix::{ - AniMeDataBuffer, AniMeImageBuffer, AniMePacketType, ANIME_PANE1_PREFIX, ANIME_PANE2_PREFIX, - }, - error::AuraError, -}; +use rog_anime::{AniMeDataBuffer, AniMePacketType}; use rusb::{Device, DeviceHandle}; use std::error::Error; use std::time::Duration; @@ -45,11 +40,8 @@ pub struct CtrlAnimeDisplay { //AnimatrixWrite pub trait Dbus { - /// Write an image 34x56 pixels. Each pixel is 0-255 greyscale. - fn write_image(&self, input: AniMeImageBuffer); - /// Write a direct stream of data - fn write_direct(&self, input: AniMeDataBuffer); + fn write(&self, input: AniMeDataBuffer); fn set_on_off(&self, status: bool); @@ -70,16 +62,9 @@ impl crate::ZbusAdd for CtrlAnimeDisplay { #[dbus_interface(name = "org.asuslinux.Daemon")] impl Dbus for CtrlAnimeDisplay { - /// Writes a 34x56 image - fn write_image(&self, input: AniMeImageBuffer) { - self.write_image_buffer(input) - .map_or_else(|err| warn!("{}", err), |()| info!("Writing image to Anime")); - } - /// Writes a data stream of length - fn write_direct(&self, input: AniMeDataBuffer) { - self.write_data_buffer(input) - .map_or_else(|err| warn!("{}", err), |()| info!("Writing data to Anime")); + fn write(&self, input: AniMeDataBuffer) { + self.write_data_buffer(input); } fn set_on_off(&self, status: bool) { @@ -98,16 +83,8 @@ impl Dbus for CtrlAnimeDisplay { } fn set_boot_on_off(&self, status: bool) { - let status_str = if status { "on" } else { "off" }; - - self.do_set_boot(status).map_or_else( - |err| warn!("{}", err), - |()| info!("Turning {} the AniMe at boot/shutdown", status_str), - ); - self.do_apply().map_or_else( - |err| warn!("{}", err), - |()| info!("Turning {} the AniMe at boot/shutdown", status_str), - ); + self.do_set_boot(status); + self.do_apply(); } } @@ -132,7 +109,7 @@ impl CtrlAnimeDisplay { info!("Device has an AniMe Matrix display"); let ctrl = CtrlAnimeDisplay { handle: device }; - ctrl.do_initialization()?; + ctrl.do_initialization(); Ok(ctrl) } @@ -167,48 +144,16 @@ impl CtrlAnimeDisplay { } } #[inline] - fn write_data_buffer(&self, buffer: AniMeDataBuffer) -> Result<(), AuraError> { - let mut image = AniMePacketType::from(buffer); - image[0][..7].copy_from_slice(&ANIME_PANE1_PREFIX); - image[1][..7].copy_from_slice(&ANIME_PANE2_PREFIX); - - for row in image.iter() { + fn write_data_buffer(&self, buffer: AniMeDataBuffer) { + let data = AniMePacketType::from(buffer); + for row in data.iter() { self.write_bytes(row); } - self.do_flush()?; - Ok(()) - } - - /// Write an Animatrix image - /// - /// The expected USB input here is *two* Vectors, 640 bytes in length. The two vectors - /// are each one half of the full image write. - /// - /// After each write a flush is written, it is assumed that this tells the device to - /// go ahead and display the written bytes - /// - /// # Note: - /// The vectors are expected to contain the full sequence of bytes as follows - /// - /// - Write pane 1: 0x5e 0xc0 0x02 0x01 0x00 0x73 0x02 .. - /// - Write pane 2: 0x5e 0xc0 0x02 0x74 0x02 0x73 0x02 .. - /// - /// Where led brightness is 0..255, low to high - #[inline] - fn write_image_buffer(&self, buffer: AniMeImageBuffer) -> Result<(), AuraError> { - let mut image = AniMePacketType::from(buffer); - image[0][..7].copy_from_slice(&ANIME_PANE1_PREFIX); - image[1][..7].copy_from_slice(&ANIME_PANE2_PREFIX); - - for row in image.iter() { - self.write_bytes(row); - } - self.do_flush()?; - Ok(()) + self.do_flush(); } #[inline] - fn do_initialization(&self) -> Result<(), AuraError> { + fn do_initialization(&self) { let mut init = [0; PACKET_SIZE]; init[0] = DEV_PAGE; // This is the USB page we're using throughout for (idx, byte) in INIT_STR.as_bytes().iter().enumerate() { @@ -224,22 +169,20 @@ impl CtrlAnimeDisplay { init[1] = INIT; self.write_bytes(&init); - Ok(()) } #[inline] - fn do_flush(&self) -> Result<(), AuraError> { + fn do_flush(&self) { let mut flush = [0; PACKET_SIZE]; flush[0] = DEV_PAGE; flush[1] = WRITE; flush[2] = 0x03; self.write_bytes(&flush); - Ok(()) } #[inline] - fn do_set_boot(&self, status: bool) -> Result<(), AuraError> { + fn do_set_boot(&self, status: bool) { let mut flush = [0; PACKET_SIZE]; flush[0] = DEV_PAGE; flush[1] = SET; @@ -247,11 +190,10 @@ impl CtrlAnimeDisplay { flush[3] = if status { 0x00 } else { 0x80 }; self.write_bytes(&flush); - Ok(()) } #[inline] - fn do_apply(&self) -> Result<(), AuraError> { + fn do_apply(&self) { let mut flush = [0; PACKET_SIZE]; flush[0] = DEV_PAGE; flush[1] = APPLY; @@ -259,6 +201,5 @@ impl CtrlAnimeDisplay { flush[3] = 0x80; self.write_bytes(&flush); - Ok(()) } } diff --git a/daemon/src/ctrl_fan_cpu.rs b/daemon/src/ctrl_fan_cpu.rs index a3a67302..a33fdeb3 100644 --- a/daemon/src/ctrl_fan_cpu.rs +++ b/daemon/src/ctrl_fan_cpu.rs @@ -14,7 +14,7 @@ static FAN_TYPE_1_PATH: &str = "/sys/devices/platform/asus-nb-wmi/throttle_therm static FAN_TYPE_2_PATH: &str = "/sys/devices/platform/asus-nb-wmi/fan_boost_mode"; static AMD_BOOST_PATH: &str = "/sys/devices/system/cpu/cpufreq/boost"; -pub struct CtrlFanAndCPU { +pub struct CtrlFanAndCpu { pub path: &'static str, config: Arc>, } @@ -26,12 +26,12 @@ pub struct FanCpuSupportedFunctions { pub fan_curve_set: bool, } -impl GetSupported for CtrlFanAndCPU { +impl GetSupported for CtrlFanAndCpu { type A = FanCpuSupportedFunctions; fn get_supported() -> Self::A { FanCpuSupportedFunctions { - stock_fan_modes: CtrlFanAndCPU::get_fan_path().is_ok(), + stock_fan_modes: CtrlFanAndCpu::get_fan_path().is_ok(), min_max_freq: intel_pstate::PState::new().is_ok(), fan_curve_set: rog_fan_curve::Board::from_board_name().is_some(), } @@ -39,11 +39,11 @@ impl GetSupported for CtrlFanAndCPU { } pub struct DbusFanAndCpu { - inner: Arc>, + inner: Arc>, } impl DbusFanAndCpu { - pub fn new(inner: Arc>) -> Self { + pub fn new(inner: Arc>) -> Self { Self { inner } } } @@ -189,7 +189,7 @@ impl crate::ZbusAdd for DbusFanAndCpu { } } -impl crate::Reloadable for CtrlFanAndCPU { +impl crate::Reloadable for CtrlFanAndCpu { fn reload(&mut self) -> Result<(), RogError> { if let Ok(mut config) = self.config.clone().try_lock() { let profile = config.active_profile.clone(); @@ -203,11 +203,11 @@ impl crate::Reloadable for CtrlFanAndCPU { } } -impl CtrlFanAndCPU { +impl CtrlFanAndCpu { pub fn new(config: Arc>) -> Result { - let path = CtrlFanAndCPU::get_fan_path()?; + let path = CtrlFanAndCpu::get_fan_path()?; info!("Device has thermal throttle control"); - Ok(CtrlFanAndCPU { path, config }) + Ok(CtrlFanAndCpu { path, config }) } fn get_fan_path() -> Result<&'static str, RogError> { diff --git a/daemon/src/ctrl_gfx/error.rs b/daemon/src/ctrl_gfx/error.rs index fb039cd6..ec765653 100644 --- a/daemon/src/ctrl_gfx/error.rs +++ b/daemon/src/ctrl_gfx/error.rs @@ -6,6 +6,7 @@ use crate::error::RogError; #[derive(Debug)] pub enum GfxError { ParseVendor, + ParsePower, Bus(String, std::io::Error), DisplayManagerAction(String, ExitStatus), DisplayManagerTimeout(String), @@ -22,6 +23,7 @@ impl fmt::Display for GfxError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { GfxError::ParseVendor => write!(f, "Could not parse vendor name"), + GfxError::ParsePower => write!(f, "Could not parse dGPU power status"), GfxError::Bus(func, error) => write!(f, "Bus error: {}: {}", func, error), GfxError::DisplayManagerAction(action, status) => { write!(f, "Display-manager action {} failed: {}", action, status) diff --git a/daemon/src/ctrl_gfx/gfx.rs b/daemon/src/ctrl_gfx/gfx.rs index d61aadbe..cac9e174 100644 --- a/daemon/src/ctrl_gfx/gfx.rs +++ b/daemon/src/ctrl_gfx/gfx.rs @@ -6,11 +6,11 @@ use logind_zbus::{ types::{SessionClass, SessionInfo, SessionState, SessionType}, ManagerProxy, SessionProxy, }; -use rog_types::gfx_vendors::{GfxRequiredUserAction, GfxVendors}; -use std::sync::mpsc; +use rog_types::gfx_vendors::{GfxPower, GfxRequiredUserAction, GfxVendors}; use std::{io::Write, ops::Add, path::Path, time::Instant}; use std::{iter::FromIterator, thread::JoinHandle}; use std::{process::Command, thread::sleep, time::Duration}; +use std::{str::FromStr, sync::mpsc}; use std::{sync::Arc, sync::Mutex}; use sysfs_class::{PciDevice, SysClass}; use system::{GraphicsDevice, PciBus}; @@ -33,7 +33,7 @@ pub struct CtrlGraphics { trait Dbus { fn vendor(&self) -> zbus::fdo::Result; - fn power(&self) -> String; + fn power(&self) -> zbus::fdo::Result; fn set_vendor(&mut self, vendor: GfxVendors) -> zbus::fdo::Result; fn notify_gfx(&self, vendor: &GfxVendors) -> zbus::Result<()>; fn notify_action(&self, action: &GfxRequiredUserAction) -> zbus::Result<()>; @@ -48,8 +48,11 @@ impl Dbus for CtrlGraphics { }) } - fn power(&self) -> String { - Self::get_runtime_status().unwrap_or_else(|err| format!("Get power status failed: {}", err)) + fn power(&self) -> zbus::fdo::Result { + Self::get_runtime_status().map_err(|err| { + error!("GFX: {}", err); + zbus::fdo::Error::Failed(format!("GFX fail: {}", err)) + }) } fn set_vendor(&mut self, vendor: GfxVendors) -> zbus::fdo::Result { @@ -178,10 +181,19 @@ impl CtrlGraphics { Ok(GfxVendors::Hybrid) } - fn get_runtime_status() -> Result { - const PATH: &str = "/sys/bus/pci/devices/0000:01:00.0/power/runtime_status"; - let buf = std::fs::read_to_string(PATH).map_err(|err| RogError::Read(PATH.into(), err))?; - Ok(buf) + fn get_runtime_status() -> Result { + let path = Path::new("/sys/bus/pci/devices/0000:01:00.0/power/runtime_status"); + if path.exists() { + let buf = std::fs::read_to_string(path).map_err(|err| { + RogError::Read( + "/sys/bus/pci/devices/0000:01:00.0/power/runtime_status".to_string(), + err, + ) + })?; + Ok(GfxPower::from_str(&buf)?) + } else { + Ok(GfxPower::Off) + } } /// Some systems have a fallback service to load nouveau if nvidia fails diff --git a/daemon/src/ctrl_leds.rs b/daemon/src/ctrl_leds.rs index 00634c38..2fa93046 100644 --- a/daemon/src/ctrl_leds.rs +++ b/daemon/src/ctrl_leds.rs @@ -248,7 +248,7 @@ impl crate::CtrlTask for CtrlKbdBacklight { } return Ok(()); } - Err(RogError::ParseLED) + Err(RogError::ParseLed) } } diff --git a/daemon/src/ctrl_supported.rs b/daemon/src/ctrl_supported.rs index 64d043d6..c52a56bc 100644 --- a/daemon/src/ctrl_supported.rs +++ b/daemon/src/ctrl_supported.rs @@ -5,7 +5,7 @@ use zbus::dbus_interface; use crate::{ ctrl_anime::{AnimeSupportedFunctions, CtrlAnimeDisplay}, ctrl_charge::{ChargeSupportedFunctions, CtrlCharge}, - ctrl_fan_cpu::{CtrlFanAndCPU, FanCpuSupportedFunctions}, + ctrl_fan_cpu::{CtrlFanAndCpu, FanCpuSupportedFunctions}, ctrl_leds::{CtrlKbdBacklight, LedSupportedFunctions}, ctrl_rog_bios::{CtrlRogBios, RogBiosSupportedFunctions}, GetSupported, @@ -47,7 +47,7 @@ impl GetSupported for SupportedFunctions { keyboard_led: CtrlKbdBacklight::get_supported(), anime_ctrl: CtrlAnimeDisplay::get_supported(), charge_ctrl: CtrlCharge::get_supported(), - fan_cpu_ctrl: CtrlFanAndCPU::get_supported(), + fan_cpu_ctrl: CtrlFanAndCpu::get_supported(), rog_bios_ctrl: CtrlRogBios::get_supported(), } } diff --git a/daemon/src/daemon.rs b/daemon/src/daemon.rs index b07842fd..9f8199cf 100644 --- a/daemon/src/daemon.rs +++ b/daemon/src/daemon.rs @@ -5,7 +5,7 @@ use daemon::{ use daemon::{config_aura::AuraConfig, ctrl_charge::CtrlCharge}; use daemon::{ctrl_anime::CtrlAnimeDisplay, ctrl_gfx::gfx::CtrlGraphics}; use daemon::{ - ctrl_fan_cpu::{CtrlFanAndCPU, DbusFanAndCpu}, + ctrl_fan_cpu::{CtrlFanAndCpu, DbusFanAndCpu}, laptops::LaptopLedData, }; @@ -134,7 +134,7 @@ fn start_daemon() -> Result<(), Box> { // Collect tasks for task thread let mut tasks: Vec>> = Vec::new(); - if let Ok(mut ctrl) = CtrlFanAndCPU::new(config).map_err(|err| { + if let Ok(mut ctrl) = CtrlFanAndCpu::new(config).map_err(|err| { error!("Profile control: {}", err); }) { ctrl.reload() diff --git a/daemon/src/error.rs b/daemon/src/error.rs index 79140f57..ba3384d5 100644 --- a/daemon/src/error.rs +++ b/daemon/src/error.rs @@ -10,7 +10,7 @@ use crate::ctrl_gfx::error::GfxError; pub enum RogError { ParseFanLevel, ParseVendor, - ParseLED, + ParseLed, MissingProfile(String), Udev(String, std::io::Error), Path(String, std::io::Error), @@ -38,7 +38,7 @@ impl fmt::Display for RogError { match self { RogError::ParseFanLevel => write!(f, "Parse profile error"), RogError::ParseVendor => write!(f, "Parse gfx vendor error"), - RogError::ParseLED => write!(f, "Parse LED error"), + RogError::ParseLed => write!(f, "Parse LED error"), RogError::MissingProfile(profile) => write!(f, "Profile does not exist {}", profile), RogError::Udev(deets, error) => write!(f, "udev {}: {}", deets, error), RogError::Path(path, error) => write!(f, "Path {}: {}", path, error), @@ -80,6 +80,7 @@ impl From for RogError { fn from(err: GraphicsError) -> Self { match err { GraphicsError::ParseVendor => RogError::GfxSwitching(GfxError::ParseVendor), + GraphicsError::ParsePower => RogError::GfxSwitching(GfxError::ParsePower), } } } diff --git a/rog-anime/Cargo.toml b/rog-anime/Cargo.toml new file mode 100644 index 00000000..b22dbe35 --- /dev/null +++ b/rog-anime/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "rog_anime" +version = "1.0.0" +license = "MPL-2.0" +readme = "README.md" +authors = ["Luke "] +repository = "https://gitlab.com/asus-linux/asus-nb-ctrl" +homepage = "https://gitlab.com/asus-linux/asus-nb-ctrl" +description = "Types useful for translating images and other data for display on the ASUS AniMe Matrix display" +edition = "2018" + +[dependencies] +png_pong = "^0.8.0" +glam = "*" +pix = "0.13" +owo-colors = "2.0.0" + +serde = "^1.0" +serde_derive = "^1.0" +zvariant = "^2.5" +zvariant_derive = "^2.5" + +[features] +default = ["zbus"] +zbus = [] \ No newline at end of file diff --git a/rog-anime/LICENSE b/rog-anime/LICENSE new file mode 100644 index 00000000..a612ad98 --- /dev/null +++ b/rog-anime/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/rog-anime/README.md b/rog-anime/README.md new file mode 100644 index 00000000..31b2018a --- /dev/null +++ b/rog-anime/README.md @@ -0,0 +1,56 @@ +# rog-anime + +## Features + +`zbus` is enabled by default. + +## Example + +```rust +use std::{env, error::Error, path::Path, process::exit}; + +use rog_dbus::AuraDbusClient; +use rog_anime::{ + anime_data::AniMeDataBuffer, + anime_image::{AnimeImage, Vec2}, +}; + +fn main() -> Result<(), Box> { + let (client, _) = AuraDbusClient::new().unwrap(); + + let args: Vec = env::args().into_iter().collect(); + if args.len() != 8 { + println!( + "Usage: " + ); + println!("e.g, asusctl/examples/doom_large.png 0.9 0.9 0.4 0.0 0.0, 0.8"); + println!("All args except path and fineness are floats"); + exit(-1); + } + + let image = AnimeImage::from_png( + Path::new(&args[1]), + Vec2::new( + args[2].parse::().unwrap(), + args[3].parse::().unwrap(), + ), + args[4].parse::().unwrap(), + Vec2::new( + args[5].parse::().unwrap(), + args[6].parse::().unwrap(), + ), + args[7].parse::().unwrap(), + )?; + + /// This data can also be written direct to the USB device by transforming with + let data = AniMePacketType::from(image); + let data = ::from(&image); + client + .proxies() + .anime() + .write(data) + .unwrap(); + + Ok(()) +} +``` \ No newline at end of file diff --git a/rog-anime/src/anime_data.rs b/rog-anime/src/anime_data.rs new file mode 100644 index 00000000..df360049 --- /dev/null +++ b/rog-anime/src/anime_data.rs @@ -0,0 +1,62 @@ +use serde_derive::{Deserialize, Serialize}; +#[cfg(feature = "zbus")] +use zvariant_derive::Type; + +/// The first 7 bytes of a USB packet are accounted for by `USB_PREFIX1` and `USB_PREFIX2` +const BLOCK_START: usize = 7; +/// *Not* inclusive, the byte before this is the final for each "pane" +const BLOCK_END: usize = 634; +/// Individual usable data length of each USB packet +const PANE_LEN: usize = BLOCK_END - BLOCK_START; +/// The length of usable data +pub const ANIME_DATA_LEN: usize = PANE_LEN * 2; + +const USB_PREFIX1: [u8; 7] = [0x5e, 0xc0, 0x02, 0x01, 0x00, 0x73, 0x02]; +const USB_PREFIX2: [u8; 7] = [0x5e, 0xc0, 0x02, 0x74, 0x02, 0x73, 0x02]; + +/// The minimal serializable data that can be transferred over wire types. +/// Other data structures in `rog_anime` will convert to this. +#[cfg_attr(feature = "zbus", derive(Type))] +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct AniMeDataBuffer(Vec); + +impl Default for AniMeDataBuffer { + fn default() -> Self { + Self::new() + } +} + +impl AniMeDataBuffer { + pub fn new() -> Self { + AniMeDataBuffer(vec![0u8; ANIME_DATA_LEN]) + } + + pub fn get(&self) -> &[u8] { + &self.0 + } + + pub fn get_mut(&mut self) -> &mut [u8] { + &mut self.0 + } + + pub fn from_vec(input: Vec) -> Self { + Self(input) + } +} + +/// The two packets to be written to USB +pub type AniMePacketType = [[u8; 640]; 2]; + +impl From for AniMePacketType { + #[inline] + fn from(anime: AniMeDataBuffer) -> Self { + assert!(anime.0.len() == ANIME_DATA_LEN); + let mut buffers = [[0; 640]; 2]; + for (idx, chunk) in anime.0.as_slice().chunks(PANE_LEN).enumerate() { + buffers[idx][BLOCK_START..BLOCK_END].copy_from_slice(chunk); + } + buffers[0][..7].copy_from_slice(&USB_PREFIX1); + buffers[1][..7].copy_from_slice(&USB_PREFIX2); + buffers + } +} diff --git a/rog-anime/src/anime_grid.rs b/rog-anime/src/anime_grid.rs new file mode 100644 index 00000000..0d0d319a --- /dev/null +++ b/rog-anime/src/anime_grid.rs @@ -0,0 +1,165 @@ +use crate::anime_data::{AniMeDataBuffer, ANIME_DATA_LEN}; +use crate::anime_image::LED_IMAGE_POSITIONS; +use owo_colors::{OwoColorize, Rgb}; + +const WIDTH: usize = 33; +const HEIGHT: usize = 55; + +/// Helper structure for writing images. +/// +/// See the examples for ways to write an image to `AniMeMatrix` format. +/// Width = 33 +/// height = 55 +#[derive(Debug, Clone)] +pub struct AniMeGrid([[u8; WIDTH]; HEIGHT]); + +impl Default for AniMeGrid { + fn default() -> Self { + Self::new() + } +} + +impl AniMeGrid { + pub fn new() -> Self { + AniMeGrid([[0u8; WIDTH]; HEIGHT]) + } + + pub fn set(&mut self, x: usize, y: usize, b: u8) { + self.0[y][x] = b; + } + + pub fn get(&self) -> &[[u8; WIDTH]; HEIGHT] { + &self.0 + } + + pub fn get_mut(&mut self) -> &mut [[u8; WIDTH]; HEIGHT] { + &mut self.0 + } + + pub fn fill_with(&mut self, fill: u8) { + for row in self.0.iter_mut() { + for x in row.iter_mut() { + *x = fill; + } + } + } + + pub fn debug_print(&self) { + // this is the index from right. It is used to progressively shorten rows + let mut prog_row_len = WIDTH - 2; + + for (count, row) in self.0.iter().enumerate() { + // Switch to next block (looks like ) + if count % 2 != 0 { + // Row after 6 is only 1 less, then rows after 7 follow pattern + if count == 7 { + prog_row_len -= 1; + } else { + prog_row_len -= 2; + } + } else { + prog_row_len += 1; // if count 6, 0 + } + + let index = row.len() - prog_row_len; + + if count % 2 == 0 { + print!(" "); + } + for (i, n) in row.iter().enumerate() { + if i >= index { + print!(" {}", "XXX".color(Rgb(0, *n, 0))); + } else { + print!(" "); + } + } + println!(); + } + } +} + +impl From for AniMeDataBuffer { + /// Do conversion from the nested Vec in AniMeMatrix to the two required + /// packets suitable for sending over USB + #[inline] + fn from(anime: AniMeGrid) -> Self { + let mut buf = vec![0u8; ANIME_DATA_LEN]; + + for (idx, pos) in LED_IMAGE_POSITIONS.iter().enumerate() { + if let Some(pos) = pos { + let x = pos.x().ceil() as usize; + let y = pos.y().ceil() as usize; + buf[idx + 1] = anime.0[y][x]; + } + } + AniMeDataBuffer::from_vec(buf) + } +} + +#[cfg(test)] +mod tests { + use crate::anime_grid::*; + + #[test] + fn check_data_alignment() { + let mut matrix = AniMeGrid::new(); + { + let tmp = matrix.get_mut(); + for row in tmp.iter_mut() { + let idx = row.len() - 1; + row[idx] = 0xff; + } + } + + let matrix = ::from(matrix); + + let data_cmp = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, + 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + assert_eq!(matrix.get(), &data_cmp); + } +} diff --git a/rog-anime/src/anime_image.rs b/rog-anime/src/anime_image.rs new file mode 100644 index 00000000..8f1ad4b8 --- /dev/null +++ b/rog-anime/src/anime_image.rs @@ -0,0 +1,1626 @@ +use std::path::Path; + +pub use glam::Vec2; +use glam::{Mat3, Vec3}; + +use crate::{ + anime_data::{AniMeDataBuffer, ANIME_DATA_LEN}, + error::AnimeError, +}; + +const LED_PIXEL_LEN: usize = 1244; + +#[derive(Copy, Clone, Debug, Default)] +struct Pixel { + color: u32, + alpha: f32, +} + +/// A single LED position and brightness. The intention of this struct +/// is to be used to sample an image and set the LED brightness. +/// +/// The position of the Led in `LedPositions` determines the placement in the final +/// data packets when written to the AniMe. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Led(f32, f32, u8); + +impl Led { + const fn new(x: f32, y: f32) -> Self { + Led(x, y, 0) + } + + pub(crate) const fn x(&self) -> f32 { + self.0 + } + + pub(crate) const fn y(&self) -> f32 { + self.1 + } + + const fn bright(&self) -> u8 { + self.2 + } + + fn set_bright(&mut self, a: u8) { + self.2 = a; + } +} + +/// Container of `Led`, each of which specifies a position within the image +/// The main use of this is to position and sample colours for the final image +/// to show on AniMe +pub struct AnimeImage { + pub scale: Vec2, + /// Angle in radians + pub angle: f32, + pub translation: Vec2, + /// Brightness of final image, `0.0` = off, `1.0` = full + pub bright: f32, + /// Positions of all the LEDs + led_pos: [Option; LED_PIXEL_LEN], + /// THe image data for sampling + img_pixels: Vec, + width: u32, +} + +impl AnimeImage { + const fn new( + scale: Vec2, + angle: f32, + translation: Vec2, + bright: f32, + pixels: Vec, + width: u32, + ) -> Self { + Self { + scale, + angle, + translation, + bright, + led_pos: LED_IMAGE_POSITIONS, + img_pixels: pixels, + width, + } + } + + /// Scale ratio in CM + const fn scale_x() -> f32 { + 0.8 + } + + /// Scale ratio in CM + const fn scale_y() -> f32 { + 0.3 + } + + /// Get the starting X position for the data we actually require when writing + /// it out to LEDs + const fn first_x(y: u32) -> u32 { + if y < 5 { + return 0; + } + (y + 1) / 2 - 3 + } + + /// Width in LED count + const fn width(y: u32) -> u32 { + if y < 5 { + return 33; + } + 36 - (y + 1) / 2 + } + + fn phys_width() -> f32 { + (32.0 - -0.5 + 1.0) * Self::scale_x() + } + + /// Height in LED count + const fn height() -> u32 { + 55 + } + + fn phys_height() -> f32 { + (54.0 + 1.0) * Self::scale_y() + } + + const fn pitch(y: u32) -> u32 { + match y { + 0 | 2 | 4 => 33, + 1 | 3 => 35, + _ => 36 - y / 2, + } + } + + /// Really only used to generate the output for including as a full const in `LED_IMAGE_POSITIONS` + pub fn generate() -> Vec> { + (0..AnimeImage::height()) + .flat_map(|y| { + (0..AnimeImage::pitch(y)).map(move |l| { + if l < AnimeImage::width(y) { + let x = AnimeImage::first_x(y) + l; + Some(Led::new(x as f32 - 0.5 * (y % 2) as f32, y as f32)) + } else { + None + } + }) + }) + .collect() + } + + /// Called after setting new angle, position, or scale to refresh the image + /// samples, the result can then been transformed to the appropriate data + /// for displaying + pub fn update(&mut self) { + let width = self.width as i32; + let height = self.img_pixels.len() as i32 / width; + let led_from_px = self.put(width as f32, height as f32); + // Steps should be configurable as "sharpness" + let du = led_from_px * Vec3::new(-0.5, 0.5, 0.0); + let dv = led_from_px * Vec3::new(0.5, 0.5, 0.0); + + for led in self.led_pos.iter_mut() { + if let Some(led) = led { + let mut sum = 0.0; + let mut alpha = 0.0; + let mut count = 0; + + let pos = Vec3::new(led.x(), led.y(), 1.0); + let x0 = led_from_px.mul_vec3(pos + Vec3::new(0.0, -0.5, 0.0)); + + const GROUP: [f32; 4] = [0.0, 0.5, 1.0, 1.5]; + for u in GROUP.iter() { + for v in GROUP.iter() { + let sample = x0 + *u * du + *v * dv; + + let mut y = sample.y as i32; + if y > height - 1 { + y = height - 1 + } else if y < 0 { + y = 0; + } + + let mut x = sample.x as i32; + if x > width - 1 { + x = width - 1; + } else if x < 0 { + x = 0; + } + + let p = self.img_pixels[(x + (y * width)) as usize]; + sum += p.color as f32; + alpha += p.alpha; + count += 1; + } + } + alpha /= count as f32; + sum /= count as f32; + led.set_bright((sum * self.bright * alpha) as u8); + } + } + } + + fn put(&self, bmp_w: f32, bmp_h: f32) -> Mat3 { + // Center of image + let center = Mat3::from_translation(Vec2::new(-0.5 * bmp_w, -0.5 * bmp_h)); + // Find the scale required for cleanly showing the image + let h = AnimeImage::phys_height() / bmp_h; + let mut base_scale = AnimeImage::phys_width() / bmp_w; + if base_scale > h { + base_scale = h; + } + + let cm_from_px = Mat3::from_scale(Vec2::new(base_scale, base_scale)); + + let led_from_cm = Mat3::from_scale(Vec2::new( + 1.0 / AnimeImage::scale_x(), + 1.0 / AnimeImage::scale_y(), + )); + + let transform = + Mat3::from_scale_angle_translation(self.scale, self.angle, self.translation); + + let pos_in_leds = Mat3::from_translation(Vec2::new(20.0, 20.0)); + // Get LED-to-image coords + let led_from_px = pos_in_leds * led_from_cm * transform * cm_from_px * center; + + led_from_px.inverse() + } + + /// Generate the base image from inputs. The result can be displayed as is or + /// updated via scale, position, or angle then displayed again after `update()`. + pub fn from_png( + path: &Path, + scale: Vec2, + angle: f32, + translation: Vec2, + bright: f32, + ) -> Result { + use pix::el::Pixel; + let data = std::fs::read(path)?; + let data = std::io::Cursor::new(data); + let decoder = png_pong::Decoder::new(data)?.into_steps(); + let png_pong::Step { raster, delay: _ } = decoder + .last() + .ok_or(AnimeError::NoFrames)??; + + let width; + let pixels = match raster { + png_pong::PngRaster::Graya8(ras) => { + width = ras.width(); + ras.pixels() + .iter() + .map(|px| crate::anime_image::Pixel { + color: ::from(px.one()) as u32, + alpha: ::from(px.alpha()), + }) + .collect() + } + _ => return Err(AnimeError::Format), + }; + + let mut matrix = AnimeImage::new(scale, angle, translation, bright, pixels, width); + + matrix.update(); + Ok(matrix) + } +} + +impl From<&AnimeImage> for AniMeDataBuffer { + /// Do conversion from the nested Vec in AniMeMatrix to the two required + /// packets suitable for sending over USB + #[inline] + fn from(leds: &AnimeImage) -> Self { + let mut l: Vec = leds + .led_pos + .iter() + .map(|l| if let Some(l) = l { l.bright() } else { 0 }) + .collect(); + let mut v = Vec::with_capacity(ANIME_DATA_LEN); + v.push(0); + v.append(&mut l); + v.append(&mut vec![0u8; 9]); + AniMeDataBuffer::from_vec(v) + } +} + +/// Data starts at first index which means that when mapping this data to the final +/// USB packet it must start from index 8, not 7. +/// +/// Verbatim copy of `generate()`. `LED_IMAGE_POSITIONS` is `const` so prefer this. +pub const LED_IMAGE_POSITIONS: [Option; LED_PIXEL_LEN] = [ + Some(Led(0.0, 0.0, 0)), + Some(Led(1.0, 0.0, 0)), + Some(Led(2.0, 0.0, 0)), + Some(Led(3.0, 0.0, 0)), + Some(Led(4.0, 0.0, 0)), + Some(Led(5.0, 0.0, 0)), + Some(Led(6.0, 0.0, 0)), + Some(Led(7.0, 0.0, 0)), + Some(Led(8.0, 0.0, 0)), + Some(Led(9.0, 0.0, 0)), + Some(Led(10.0, 0.0, 0)), + Some(Led(11.0, 0.0, 0)), + Some(Led(12.0, 0.0, 0)), + Some(Led(13.0, 0.0, 0)), + Some(Led(14.0, 0.0, 0)), + Some(Led(15.0, 0.0, 0)), + Some(Led(16.0, 0.0, 0)), + Some(Led(17.0, 0.0, 0)), + Some(Led(18.0, 0.0, 0)), + Some(Led(19.0, 0.0, 0)), + Some(Led(20.0, 0.0, 0)), + Some(Led(21.0, 0.0, 0)), + Some(Led(22.0, 0.0, 0)), + Some(Led(23.0, 0.0, 0)), + Some(Led(24.0, 0.0, 0)), + Some(Led(25.0, 0.0, 0)), + Some(Led(26.0, 0.0, 0)), + Some(Led(27.0, 0.0, 0)), + Some(Led(28.0, 0.0, 0)), + Some(Led(29.0, 0.0, 0)), + Some(Led(30.0, 0.0, 0)), + Some(Led(31.0, 0.0, 0)), + Some(Led(32.0, 0.0, 0)), + Some(Led(-0.5, 1.0, 0)), + Some(Led(0.5, 1.0, 0)), + Some(Led(1.5, 1.0, 0)), + Some(Led(2.5, 1.0, 0)), + Some(Led(3.5, 1.0, 0)), + Some(Led(4.5, 1.0, 0)), + Some(Led(5.5, 1.0, 0)), + Some(Led(6.5, 1.0, 0)), + Some(Led(7.5, 1.0, 0)), + Some(Led(8.5, 1.0, 0)), + Some(Led(9.5, 1.0, 0)), + Some(Led(10.5, 1.0, 0)), + Some(Led(11.5, 1.0, 0)), + Some(Led(12.5, 1.0, 0)), + Some(Led(13.5, 1.0, 0)), + Some(Led(14.5, 1.0, 0)), + Some(Led(15.5, 1.0, 0)), + Some(Led(16.5, 1.0, 0)), + Some(Led(17.5, 1.0, 0)), + Some(Led(18.5, 1.0, 0)), + Some(Led(19.5, 1.0, 0)), + Some(Led(20.5, 1.0, 0)), + Some(Led(21.5, 1.0, 0)), + Some(Led(22.5, 1.0, 0)), + Some(Led(23.5, 1.0, 0)), + Some(Led(24.5, 1.0, 0)), + Some(Led(25.5, 1.0, 0)), + Some(Led(26.5, 1.0, 0)), + Some(Led(27.5, 1.0, 0)), + Some(Led(28.5, 1.0, 0)), + Some(Led(29.5, 1.0, 0)), + Some(Led(30.5, 1.0, 0)), + Some(Led(31.5, 1.0, 0)), + None, + None, + Some(Led(0.0, 2.0, 0)), + Some(Led(1.0, 2.0, 0)), + Some(Led(2.0, 2.0, 0)), + Some(Led(3.0, 2.0, 0)), + Some(Led(4.0, 2.0, 0)), + Some(Led(5.0, 2.0, 0)), + Some(Led(6.0, 2.0, 0)), + Some(Led(7.0, 2.0, 0)), + Some(Led(8.0, 2.0, 0)), + Some(Led(9.0, 2.0, 0)), + Some(Led(10.0, 2.0, 0)), + Some(Led(11.0, 2.0, 0)), + Some(Led(12.0, 2.0, 0)), + Some(Led(13.0, 2.0, 0)), + Some(Led(14.0, 2.0, 0)), + Some(Led(15.0, 2.0, 0)), + Some(Led(16.0, 2.0, 0)), + Some(Led(17.0, 2.0, 0)), + Some(Led(18.0, 2.0, 0)), + Some(Led(19.0, 2.0, 0)), + Some(Led(20.0, 2.0, 0)), + Some(Led(21.0, 2.0, 0)), + Some(Led(22.0, 2.0, 0)), + Some(Led(23.0, 2.0, 0)), + Some(Led(24.0, 2.0, 0)), + Some(Led(25.0, 2.0, 0)), + Some(Led(26.0, 2.0, 0)), + Some(Led(27.0, 2.0, 0)), + Some(Led(28.0, 2.0, 0)), + Some(Led(29.0, 2.0, 0)), + Some(Led(30.0, 2.0, 0)), + Some(Led(31.0, 2.0, 0)), + Some(Led(32.0, 2.0, 0)), + Some(Led(-0.5, 3.0, 0)), + Some(Led(0.5, 3.0, 0)), + Some(Led(1.5, 3.0, 0)), + Some(Led(2.5, 3.0, 0)), + Some(Led(3.5, 3.0, 0)), + Some(Led(4.5, 3.0, 0)), + Some(Led(5.5, 3.0, 0)), + Some(Led(6.5, 3.0, 0)), + Some(Led(7.5, 3.0, 0)), + Some(Led(8.5, 3.0, 0)), + Some(Led(9.5, 3.0, 0)), + Some(Led(10.5, 3.0, 0)), + Some(Led(11.5, 3.0, 0)), + Some(Led(12.5, 3.0, 0)), + Some(Led(13.5, 3.0, 0)), + Some(Led(14.5, 3.0, 0)), + Some(Led(15.5, 3.0, 0)), + Some(Led(16.5, 3.0, 0)), + Some(Led(17.5, 3.0, 0)), + Some(Led(18.5, 3.0, 0)), + Some(Led(19.5, 3.0, 0)), + Some(Led(20.5, 3.0, 0)), + Some(Led(21.5, 3.0, 0)), + Some(Led(22.5, 3.0, 0)), + Some(Led(23.5, 3.0, 0)), + Some(Led(24.5, 3.0, 0)), + Some(Led(25.5, 3.0, 0)), + Some(Led(26.5, 3.0, 0)), + Some(Led(27.5, 3.0, 0)), + Some(Led(28.5, 3.0, 0)), + Some(Led(29.5, 3.0, 0)), + Some(Led(30.5, 3.0, 0)), + Some(Led(31.5, 3.0, 0)), + None, + None, + Some(Led(0.0, 4.0, 0)), + Some(Led(1.0, 4.0, 0)), + Some(Led(2.0, 4.0, 0)), + Some(Led(3.0, 4.0, 0)), + Some(Led(4.0, 4.0, 0)), + Some(Led(5.0, 4.0, 0)), + Some(Led(6.0, 4.0, 0)), + Some(Led(7.0, 4.0, 0)), + Some(Led(8.0, 4.0, 0)), + Some(Led(9.0, 4.0, 0)), + Some(Led(10.0, 4.0, 0)), + Some(Led(11.0, 4.0, 0)), + Some(Led(12.0, 4.0, 0)), + Some(Led(13.0, 4.0, 0)), + Some(Led(14.0, 4.0, 0)), + Some(Led(15.0, 4.0, 0)), + Some(Led(16.0, 4.0, 0)), + Some(Led(17.0, 4.0, 0)), + Some(Led(18.0, 4.0, 0)), + Some(Led(19.0, 4.0, 0)), + Some(Led(20.0, 4.0, 0)), + Some(Led(21.0, 4.0, 0)), + Some(Led(22.0, 4.0, 0)), + Some(Led(23.0, 4.0, 0)), + Some(Led(24.0, 4.0, 0)), + Some(Led(25.0, 4.0, 0)), + Some(Led(26.0, 4.0, 0)), + Some(Led(27.0, 4.0, 0)), + Some(Led(28.0, 4.0, 0)), + Some(Led(29.0, 4.0, 0)), + Some(Led(30.0, 4.0, 0)), + Some(Led(31.0, 4.0, 0)), + Some(Led(32.0, 4.0, 0)), + Some(Led(-0.5, 5.0, 0)), + Some(Led(0.5, 5.0, 0)), + Some(Led(1.5, 5.0, 0)), + Some(Led(2.5, 5.0, 0)), + Some(Led(3.5, 5.0, 0)), + Some(Led(4.5, 5.0, 0)), + Some(Led(5.5, 5.0, 0)), + Some(Led(6.5, 5.0, 0)), + Some(Led(7.5, 5.0, 0)), + Some(Led(8.5, 5.0, 0)), + Some(Led(9.5, 5.0, 0)), + Some(Led(10.5, 5.0, 0)), + Some(Led(11.5, 5.0, 0)), + Some(Led(12.5, 5.0, 0)), + Some(Led(13.5, 5.0, 0)), + Some(Led(14.5, 5.0, 0)), + Some(Led(15.5, 5.0, 0)), + Some(Led(16.5, 5.0, 0)), + Some(Led(17.5, 5.0, 0)), + Some(Led(18.5, 5.0, 0)), + Some(Led(19.5, 5.0, 0)), + Some(Led(20.5, 5.0, 0)), + Some(Led(21.5, 5.0, 0)), + Some(Led(22.5, 5.0, 0)), + Some(Led(23.5, 5.0, 0)), + Some(Led(24.5, 5.0, 0)), + Some(Led(25.5, 5.0, 0)), + Some(Led(26.5, 5.0, 0)), + Some(Led(27.5, 5.0, 0)), + Some(Led(28.5, 5.0, 0)), + Some(Led(29.5, 5.0, 0)), + Some(Led(30.5, 5.0, 0)), + Some(Led(31.5, 5.0, 0)), + None, + Some(Led(0.0, 6.0, 0)), + Some(Led(1.0, 6.0, 0)), + Some(Led(2.0, 6.0, 0)), + Some(Led(3.0, 6.0, 0)), + Some(Led(4.0, 6.0, 0)), + Some(Led(5.0, 6.0, 0)), + Some(Led(6.0, 6.0, 0)), + Some(Led(7.0, 6.0, 0)), + Some(Led(8.0, 6.0, 0)), + Some(Led(9.0, 6.0, 0)), + Some(Led(10.0, 6.0, 0)), + Some(Led(11.0, 6.0, 0)), + Some(Led(12.0, 6.0, 0)), + Some(Led(13.0, 6.0, 0)), + Some(Led(14.0, 6.0, 0)), + Some(Led(15.0, 6.0, 0)), + Some(Led(16.0, 6.0, 0)), + Some(Led(17.0, 6.0, 0)), + Some(Led(18.0, 6.0, 0)), + Some(Led(19.0, 6.0, 0)), + Some(Led(20.0, 6.0, 0)), + Some(Led(21.0, 6.0, 0)), + Some(Led(22.0, 6.0, 0)), + Some(Led(23.0, 6.0, 0)), + Some(Led(24.0, 6.0, 0)), + Some(Led(25.0, 6.0, 0)), + Some(Led(26.0, 6.0, 0)), + Some(Led(27.0, 6.0, 0)), + Some(Led(28.0, 6.0, 0)), + Some(Led(29.0, 6.0, 0)), + Some(Led(30.0, 6.0, 0)), + Some(Led(31.0, 6.0, 0)), + Some(Led(32.0, 6.0, 0)), + Some(Led(0.5, 7.0, 0)), + Some(Led(1.5, 7.0, 0)), + Some(Led(2.5, 7.0, 0)), + Some(Led(3.5, 7.0, 0)), + Some(Led(4.5, 7.0, 0)), + Some(Led(5.5, 7.0, 0)), + Some(Led(6.5, 7.0, 0)), + Some(Led(7.5, 7.0, 0)), + Some(Led(8.5, 7.0, 0)), + Some(Led(9.5, 7.0, 0)), + Some(Led(10.5, 7.0, 0)), + Some(Led(11.5, 7.0, 0)), + Some(Led(12.5, 7.0, 0)), + Some(Led(13.5, 7.0, 0)), + Some(Led(14.5, 7.0, 0)), + Some(Led(15.5, 7.0, 0)), + Some(Led(16.5, 7.0, 0)), + Some(Led(17.5, 7.0, 0)), + Some(Led(18.5, 7.0, 0)), + Some(Led(19.5, 7.0, 0)), + Some(Led(20.5, 7.0, 0)), + Some(Led(21.5, 7.0, 0)), + Some(Led(22.5, 7.0, 0)), + Some(Led(23.5, 7.0, 0)), + Some(Led(24.5, 7.0, 0)), + Some(Led(25.5, 7.0, 0)), + Some(Led(26.5, 7.0, 0)), + Some(Led(27.5, 7.0, 0)), + Some(Led(28.5, 7.0, 0)), + Some(Led(29.5, 7.0, 0)), + Some(Led(30.5, 7.0, 0)), + Some(Led(31.5, 7.0, 0)), + None, + Some(Led(1.0, 8.0, 0)), + Some(Led(2.0, 8.0, 0)), + Some(Led(3.0, 8.0, 0)), + Some(Led(4.0, 8.0, 0)), + Some(Led(5.0, 8.0, 0)), + Some(Led(6.0, 8.0, 0)), + Some(Led(7.0, 8.0, 0)), + Some(Led(8.0, 8.0, 0)), + Some(Led(9.0, 8.0, 0)), + Some(Led(10.0, 8.0, 0)), + Some(Led(11.0, 8.0, 0)), + Some(Led(12.0, 8.0, 0)), + Some(Led(13.0, 8.0, 0)), + Some(Led(14.0, 8.0, 0)), + Some(Led(15.0, 8.0, 0)), + Some(Led(16.0, 8.0, 0)), + Some(Led(17.0, 8.0, 0)), + Some(Led(18.0, 8.0, 0)), + Some(Led(19.0, 8.0, 0)), + Some(Led(20.0, 8.0, 0)), + Some(Led(21.0, 8.0, 0)), + Some(Led(22.0, 8.0, 0)), + Some(Led(23.0, 8.0, 0)), + Some(Led(24.0, 8.0, 0)), + Some(Led(25.0, 8.0, 0)), + Some(Led(26.0, 8.0, 0)), + Some(Led(27.0, 8.0, 0)), + Some(Led(28.0, 8.0, 0)), + Some(Led(29.0, 8.0, 0)), + Some(Led(30.0, 8.0, 0)), + Some(Led(31.0, 8.0, 0)), + Some(Led(32.0, 8.0, 0)), + Some(Led(1.5, 9.0, 0)), + Some(Led(2.5, 9.0, 0)), + Some(Led(3.5, 9.0, 0)), + Some(Led(4.5, 9.0, 0)), + Some(Led(5.5, 9.0, 0)), + Some(Led(6.5, 9.0, 0)), + Some(Led(7.5, 9.0, 0)), + Some(Led(8.5, 9.0, 0)), + Some(Led(9.5, 9.0, 0)), + Some(Led(10.5, 9.0, 0)), + Some(Led(11.5, 9.0, 0)), + Some(Led(12.5, 9.0, 0)), + Some(Led(13.5, 9.0, 0)), + Some(Led(14.5, 9.0, 0)), + Some(Led(15.5, 9.0, 0)), + Some(Led(16.5, 9.0, 0)), + Some(Led(17.5, 9.0, 0)), + Some(Led(18.5, 9.0, 0)), + Some(Led(19.5, 9.0, 0)), + Some(Led(20.5, 9.0, 0)), + Some(Led(21.5, 9.0, 0)), + Some(Led(22.5, 9.0, 0)), + Some(Led(23.5, 9.0, 0)), + Some(Led(24.5, 9.0, 0)), + Some(Led(25.5, 9.0, 0)), + Some(Led(26.5, 9.0, 0)), + Some(Led(27.5, 9.0, 0)), + Some(Led(28.5, 9.0, 0)), + Some(Led(29.5, 9.0, 0)), + Some(Led(30.5, 9.0, 0)), + Some(Led(31.5, 9.0, 0)), + None, + Some(Led(2.0, 10.0, 0)), + Some(Led(3.0, 10.0, 0)), + Some(Led(4.0, 10.0, 0)), + Some(Led(5.0, 10.0, 0)), + Some(Led(6.0, 10.0, 0)), + Some(Led(7.0, 10.0, 0)), + Some(Led(8.0, 10.0, 0)), + Some(Led(9.0, 10.0, 0)), + Some(Led(10.0, 10.0, 0)), + Some(Led(11.0, 10.0, 0)), + Some(Led(12.0, 10.0, 0)), + Some(Led(13.0, 10.0, 0)), + Some(Led(14.0, 10.0, 0)), + Some(Led(15.0, 10.0, 0)), + Some(Led(16.0, 10.0, 0)), + Some(Led(17.0, 10.0, 0)), + Some(Led(18.0, 10.0, 0)), + Some(Led(19.0, 10.0, 0)), + Some(Led(20.0, 10.0, 0)), + Some(Led(21.0, 10.0, 0)), + Some(Led(22.0, 10.0, 0)), + Some(Led(23.0, 10.0, 0)), + Some(Led(24.0, 10.0, 0)), + Some(Led(25.0, 10.0, 0)), + Some(Led(26.0, 10.0, 0)), + Some(Led(27.0, 10.0, 0)), + Some(Led(28.0, 10.0, 0)), + Some(Led(29.0, 10.0, 0)), + Some(Led(30.0, 10.0, 0)), + Some(Led(31.0, 10.0, 0)), + Some(Led(32.0, 10.0, 0)), + Some(Led(2.5, 11.0, 0)), + Some(Led(3.5, 11.0, 0)), + Some(Led(4.5, 11.0, 0)), + Some(Led(5.5, 11.0, 0)), + Some(Led(6.5, 11.0, 0)), + Some(Led(7.5, 11.0, 0)), + Some(Led(8.5, 11.0, 0)), + Some(Led(9.5, 11.0, 0)), + Some(Led(10.5, 11.0, 0)), + Some(Led(11.5, 11.0, 0)), + Some(Led(12.5, 11.0, 0)), + Some(Led(13.5, 11.0, 0)), + Some(Led(14.5, 11.0, 0)), + Some(Led(15.5, 11.0, 0)), + Some(Led(16.5, 11.0, 0)), + Some(Led(17.5, 11.0, 0)), + Some(Led(18.5, 11.0, 0)), + Some(Led(19.5, 11.0, 0)), + Some(Led(20.5, 11.0, 0)), + Some(Led(21.5, 11.0, 0)), + Some(Led(22.5, 11.0, 0)), + Some(Led(23.5, 11.0, 0)), + Some(Led(24.5, 11.0, 0)), + Some(Led(25.5, 11.0, 0)), + Some(Led(26.5, 11.0, 0)), + Some(Led(27.5, 11.0, 0)), + Some(Led(28.5, 11.0, 0)), + Some(Led(29.5, 11.0, 0)), + Some(Led(30.5, 11.0, 0)), + Some(Led(31.5, 11.0, 0)), + None, + Some(Led(3.0, 12.0, 0)), + Some(Led(4.0, 12.0, 0)), + Some(Led(5.0, 12.0, 0)), + Some(Led(6.0, 12.0, 0)), + Some(Led(7.0, 12.0, 0)), + Some(Led(8.0, 12.0, 0)), + Some(Led(9.0, 12.0, 0)), + Some(Led(10.0, 12.0, 0)), + Some(Led(11.0, 12.0, 0)), + Some(Led(12.0, 12.0, 0)), + Some(Led(13.0, 12.0, 0)), + Some(Led(14.0, 12.0, 0)), + Some(Led(15.0, 12.0, 0)), + Some(Led(16.0, 12.0, 0)), + Some(Led(17.0, 12.0, 0)), + Some(Led(18.0, 12.0, 0)), + Some(Led(19.0, 12.0, 0)), + Some(Led(20.0, 12.0, 0)), + Some(Led(21.0, 12.0, 0)), + Some(Led(22.0, 12.0, 0)), + Some(Led(23.0, 12.0, 0)), + Some(Led(24.0, 12.0, 0)), + Some(Led(25.0, 12.0, 0)), + Some(Led(26.0, 12.0, 0)), + Some(Led(27.0, 12.0, 0)), + Some(Led(28.0, 12.0, 0)), + Some(Led(29.0, 12.0, 0)), + Some(Led(30.0, 12.0, 0)), + Some(Led(31.0, 12.0, 0)), + Some(Led(32.0, 12.0, 0)), + Some(Led(3.5, 13.0, 0)), + Some(Led(4.5, 13.0, 0)), + Some(Led(5.5, 13.0, 0)), + Some(Led(6.5, 13.0, 0)), + Some(Led(7.5, 13.0, 0)), + Some(Led(8.5, 13.0, 0)), + Some(Led(9.5, 13.0, 0)), + Some(Led(10.5, 13.0, 0)), + Some(Led(11.5, 13.0, 0)), + Some(Led(12.5, 13.0, 0)), + Some(Led(13.5, 13.0, 0)), + Some(Led(14.5, 13.0, 0)), + Some(Led(15.5, 13.0, 0)), + Some(Led(16.5, 13.0, 0)), + Some(Led(17.5, 13.0, 0)), + Some(Led(18.5, 13.0, 0)), + Some(Led(19.5, 13.0, 0)), + Some(Led(20.5, 13.0, 0)), + Some(Led(21.5, 13.0, 0)), + Some(Led(22.5, 13.0, 0)), + Some(Led(23.5, 13.0, 0)), + Some(Led(24.5, 13.0, 0)), + Some(Led(25.5, 13.0, 0)), + Some(Led(26.5, 13.0, 0)), + Some(Led(27.5, 13.0, 0)), + Some(Led(28.5, 13.0, 0)), + Some(Led(29.5, 13.0, 0)), + Some(Led(30.5, 13.0, 0)), + Some(Led(31.5, 13.0, 0)), + None, + Some(Led(4.0, 14.0, 0)), + Some(Led(5.0, 14.0, 0)), + Some(Led(6.0, 14.0, 0)), + Some(Led(7.0, 14.0, 0)), + Some(Led(8.0, 14.0, 0)), + Some(Led(9.0, 14.0, 0)), + Some(Led(10.0, 14.0, 0)), + Some(Led(11.0, 14.0, 0)), + Some(Led(12.0, 14.0, 0)), + Some(Led(13.0, 14.0, 0)), + Some(Led(14.0, 14.0, 0)), + Some(Led(15.0, 14.0, 0)), + Some(Led(16.0, 14.0, 0)), + Some(Led(17.0, 14.0, 0)), + Some(Led(18.0, 14.0, 0)), + Some(Led(19.0, 14.0, 0)), + Some(Led(20.0, 14.0, 0)), + Some(Led(21.0, 14.0, 0)), + Some(Led(22.0, 14.0, 0)), + Some(Led(23.0, 14.0, 0)), + Some(Led(24.0, 14.0, 0)), + Some(Led(25.0, 14.0, 0)), + Some(Led(26.0, 14.0, 0)), + Some(Led(27.0, 14.0, 0)), + Some(Led(28.0, 14.0, 0)), + Some(Led(29.0, 14.0, 0)), + Some(Led(30.0, 14.0, 0)), + Some(Led(31.0, 14.0, 0)), + Some(Led(32.0, 14.0, 0)), + Some(Led(4.5, 15.0, 0)), + Some(Led(5.5, 15.0, 0)), + Some(Led(6.5, 15.0, 0)), + Some(Led(7.5, 15.0, 0)), + Some(Led(8.5, 15.0, 0)), + Some(Led(9.5, 15.0, 0)), + Some(Led(10.5, 15.0, 0)), + Some(Led(11.5, 15.0, 0)), + Some(Led(12.5, 15.0, 0)), + Some(Led(13.5, 15.0, 0)), + Some(Led(14.5, 15.0, 0)), + Some(Led(15.5, 15.0, 0)), + Some(Led(16.5, 15.0, 0)), + Some(Led(17.5, 15.0, 0)), + Some(Led(18.5, 15.0, 0)), + Some(Led(19.5, 15.0, 0)), + Some(Led(20.5, 15.0, 0)), + Some(Led(21.5, 15.0, 0)), + Some(Led(22.5, 15.0, 0)), + Some(Led(23.5, 15.0, 0)), + Some(Led(24.5, 15.0, 0)), + Some(Led(25.5, 15.0, 0)), + Some(Led(26.5, 15.0, 0)), + Some(Led(27.5, 15.0, 0)), + Some(Led(28.5, 15.0, 0)), + Some(Led(29.5, 15.0, 0)), + Some(Led(30.5, 15.0, 0)), + Some(Led(31.5, 15.0, 0)), + None, + Some(Led(5.0, 16.0, 0)), + Some(Led(6.0, 16.0, 0)), + Some(Led(7.0, 16.0, 0)), + Some(Led(8.0, 16.0, 0)), + Some(Led(9.0, 16.0, 0)), + Some(Led(10.0, 16.0, 0)), + Some(Led(11.0, 16.0, 0)), + Some(Led(12.0, 16.0, 0)), + Some(Led(13.0, 16.0, 0)), + Some(Led(14.0, 16.0, 0)), + Some(Led(15.0, 16.0, 0)), + Some(Led(16.0, 16.0, 0)), + Some(Led(17.0, 16.0, 0)), + Some(Led(18.0, 16.0, 0)), + Some(Led(19.0, 16.0, 0)), + Some(Led(20.0, 16.0, 0)), + Some(Led(21.0, 16.0, 0)), + Some(Led(22.0, 16.0, 0)), + Some(Led(23.0, 16.0, 0)), + Some(Led(24.0, 16.0, 0)), + Some(Led(25.0, 16.0, 0)), + Some(Led(26.0, 16.0, 0)), + Some(Led(27.0, 16.0, 0)), + Some(Led(28.0, 16.0, 0)), + Some(Led(29.0, 16.0, 0)), + Some(Led(30.0, 16.0, 0)), + Some(Led(31.0, 16.0, 0)), + Some(Led(32.0, 16.0, 0)), + Some(Led(5.5, 17.0, 0)), + Some(Led(6.5, 17.0, 0)), + Some(Led(7.5, 17.0, 0)), + Some(Led(8.5, 17.0, 0)), + Some(Led(9.5, 17.0, 0)), + Some(Led(10.5, 17.0, 0)), + Some(Led(11.5, 17.0, 0)), + Some(Led(12.5, 17.0, 0)), + Some(Led(13.5, 17.0, 0)), + Some(Led(14.5, 17.0, 0)), + Some(Led(15.5, 17.0, 0)), + Some(Led(16.5, 17.0, 0)), + Some(Led(17.5, 17.0, 0)), + Some(Led(18.5, 17.0, 0)), + Some(Led(19.5, 17.0, 0)), + Some(Led(20.5, 17.0, 0)), + Some(Led(21.5, 17.0, 0)), + Some(Led(22.5, 17.0, 0)), + Some(Led(23.5, 17.0, 0)), + Some(Led(24.5, 17.0, 0)), + Some(Led(25.5, 17.0, 0)), + Some(Led(26.5, 17.0, 0)), + Some(Led(27.5, 17.0, 0)), + Some(Led(28.5, 17.0, 0)), + Some(Led(29.5, 17.0, 0)), + Some(Led(30.5, 17.0, 0)), + Some(Led(31.5, 17.0, 0)), + None, + Some(Led(6.0, 18.0, 0)), + Some(Led(7.0, 18.0, 0)), + Some(Led(8.0, 18.0, 0)), + Some(Led(9.0, 18.0, 0)), + Some(Led(10.0, 18.0, 0)), + Some(Led(11.0, 18.0, 0)), + Some(Led(12.0, 18.0, 0)), + Some(Led(13.0, 18.0, 0)), + Some(Led(14.0, 18.0, 0)), + Some(Led(15.0, 18.0, 0)), + Some(Led(16.0, 18.0, 0)), + Some(Led(17.0, 18.0, 0)), + Some(Led(18.0, 18.0, 0)), + Some(Led(19.0, 18.0, 0)), + Some(Led(20.0, 18.0, 0)), + Some(Led(21.0, 18.0, 0)), + Some(Led(22.0, 18.0, 0)), + Some(Led(23.0, 18.0, 0)), + Some(Led(24.0, 18.0, 0)), + Some(Led(25.0, 18.0, 0)), + Some(Led(26.0, 18.0, 0)), + Some(Led(27.0, 18.0, 0)), + Some(Led(28.0, 18.0, 0)), + Some(Led(29.0, 18.0, 0)), + Some(Led(30.0, 18.0, 0)), + Some(Led(31.0, 18.0, 0)), + Some(Led(32.0, 18.0, 0)), + Some(Led(6.5, 19.0, 0)), + Some(Led(7.5, 19.0, 0)), + Some(Led(8.5, 19.0, 0)), + Some(Led(9.5, 19.0, 0)), + Some(Led(10.5, 19.0, 0)), + Some(Led(11.5, 19.0, 0)), + Some(Led(12.5, 19.0, 0)), + Some(Led(13.5, 19.0, 0)), + Some(Led(14.5, 19.0, 0)), + Some(Led(15.5, 19.0, 0)), + Some(Led(16.5, 19.0, 0)), + Some(Led(17.5, 19.0, 0)), + Some(Led(18.5, 19.0, 0)), + Some(Led(19.5, 19.0, 0)), + Some(Led(20.5, 19.0, 0)), + Some(Led(21.5, 19.0, 0)), + Some(Led(22.5, 19.0, 0)), + Some(Led(23.5, 19.0, 0)), + Some(Led(24.5, 19.0, 0)), + Some(Led(25.5, 19.0, 0)), + Some(Led(26.5, 19.0, 0)), + Some(Led(27.5, 19.0, 0)), + Some(Led(28.5, 19.0, 0)), + Some(Led(29.5, 19.0, 0)), + Some(Led(30.5, 19.0, 0)), + Some(Led(31.5, 19.0, 0)), + None, + Some(Led(7.0, 20.0, 0)), + Some(Led(8.0, 20.0, 0)), + Some(Led(9.0, 20.0, 0)), + Some(Led(10.0, 20.0, 0)), + Some(Led(11.0, 20.0, 0)), + Some(Led(12.0, 20.0, 0)), + Some(Led(13.0, 20.0, 0)), + Some(Led(14.0, 20.0, 0)), + Some(Led(15.0, 20.0, 0)), + Some(Led(16.0, 20.0, 0)), + Some(Led(17.0, 20.0, 0)), + Some(Led(18.0, 20.0, 0)), + Some(Led(19.0, 20.0, 0)), + Some(Led(20.0, 20.0, 0)), + Some(Led(21.0, 20.0, 0)), + Some(Led(22.0, 20.0, 0)), + Some(Led(23.0, 20.0, 0)), + Some(Led(24.0, 20.0, 0)), + Some(Led(25.0, 20.0, 0)), + Some(Led(26.0, 20.0, 0)), + Some(Led(27.0, 20.0, 0)), + Some(Led(28.0, 20.0, 0)), + Some(Led(29.0, 20.0, 0)), + Some(Led(30.0, 20.0, 0)), + Some(Led(31.0, 20.0, 0)), + Some(Led(32.0, 20.0, 0)), + Some(Led(7.5, 21.0, 0)), + Some(Led(8.5, 21.0, 0)), + Some(Led(9.5, 21.0, 0)), + Some(Led(10.5, 21.0, 0)), + Some(Led(11.5, 21.0, 0)), + Some(Led(12.5, 21.0, 0)), + Some(Led(13.5, 21.0, 0)), + Some(Led(14.5, 21.0, 0)), + Some(Led(15.5, 21.0, 0)), + Some(Led(16.5, 21.0, 0)), + Some(Led(17.5, 21.0, 0)), + Some(Led(18.5, 21.0, 0)), + Some(Led(19.5, 21.0, 0)), + Some(Led(20.5, 21.0, 0)), + Some(Led(21.5, 21.0, 0)), + Some(Led(22.5, 21.0, 0)), + Some(Led(23.5, 21.0, 0)), + Some(Led(24.5, 21.0, 0)), + Some(Led(25.5, 21.0, 0)), + Some(Led(26.5, 21.0, 0)), + Some(Led(27.5, 21.0, 0)), + Some(Led(28.5, 21.0, 0)), + Some(Led(29.5, 21.0, 0)), + Some(Led(30.5, 21.0, 0)), + Some(Led(31.5, 21.0, 0)), + None, + Some(Led(8.0, 22.0, 0)), + Some(Led(9.0, 22.0, 0)), + Some(Led(10.0, 22.0, 0)), + Some(Led(11.0, 22.0, 0)), + Some(Led(12.0, 22.0, 0)), + Some(Led(13.0, 22.0, 0)), + Some(Led(14.0, 22.0, 0)), + Some(Led(15.0, 22.0, 0)), + Some(Led(16.0, 22.0, 0)), + Some(Led(17.0, 22.0, 0)), + Some(Led(18.0, 22.0, 0)), + Some(Led(19.0, 22.0, 0)), + Some(Led(20.0, 22.0, 0)), + Some(Led(21.0, 22.0, 0)), + Some(Led(22.0, 22.0, 0)), + Some(Led(23.0, 22.0, 0)), + Some(Led(24.0, 22.0, 0)), + Some(Led(25.0, 22.0, 0)), + Some(Led(26.0, 22.0, 0)), + Some(Led(27.0, 22.0, 0)), + Some(Led(28.0, 22.0, 0)), + Some(Led(29.0, 22.0, 0)), + Some(Led(30.0, 22.0, 0)), + Some(Led(31.0, 22.0, 0)), + Some(Led(32.0, 22.0, 0)), + Some(Led(8.5, 23.0, 0)), + Some(Led(9.5, 23.0, 0)), + Some(Led(10.5, 23.0, 0)), + Some(Led(11.5, 23.0, 0)), + Some(Led(12.5, 23.0, 0)), + Some(Led(13.5, 23.0, 0)), + Some(Led(14.5, 23.0, 0)), + Some(Led(15.5, 23.0, 0)), + Some(Led(16.5, 23.0, 0)), + Some(Led(17.5, 23.0, 0)), + Some(Led(18.5, 23.0, 0)), + Some(Led(19.5, 23.0, 0)), + Some(Led(20.5, 23.0, 0)), + Some(Led(21.5, 23.0, 0)), + Some(Led(22.5, 23.0, 0)), + Some(Led(23.5, 23.0, 0)), + Some(Led(24.5, 23.0, 0)), + Some(Led(25.5, 23.0, 0)), + Some(Led(26.5, 23.0, 0)), + Some(Led(27.5, 23.0, 0)), + Some(Led(28.5, 23.0, 0)), + Some(Led(29.5, 23.0, 0)), + Some(Led(30.5, 23.0, 0)), + Some(Led(31.5, 23.0, 0)), + None, + Some(Led(9.0, 24.0, 0)), + Some(Led(10.0, 24.0, 0)), + Some(Led(11.0, 24.0, 0)), + Some(Led(12.0, 24.0, 0)), + Some(Led(13.0, 24.0, 0)), + Some(Led(14.0, 24.0, 0)), + Some(Led(15.0, 24.0, 0)), + Some(Led(16.0, 24.0, 0)), + Some(Led(17.0, 24.0, 0)), + Some(Led(18.0, 24.0, 0)), + Some(Led(19.0, 24.0, 0)), + Some(Led(20.0, 24.0, 0)), + Some(Led(21.0, 24.0, 0)), + Some(Led(22.0, 24.0, 0)), + Some(Led(23.0, 24.0, 0)), + Some(Led(24.0, 24.0, 0)), + Some(Led(25.0, 24.0, 0)), + Some(Led(26.0, 24.0, 0)), + Some(Led(27.0, 24.0, 0)), + Some(Led(28.0, 24.0, 0)), + Some(Led(29.0, 24.0, 0)), + Some(Led(30.0, 24.0, 0)), + Some(Led(31.0, 24.0, 0)), + Some(Led(32.0, 24.0, 0)), + Some(Led(9.5, 25.0, 0)), + Some(Led(10.5, 25.0, 0)), + Some(Led(11.5, 25.0, 0)), + Some(Led(12.5, 25.0, 0)), + Some(Led(13.5, 25.0, 0)), + Some(Led(14.5, 25.0, 0)), + Some(Led(15.5, 25.0, 0)), + Some(Led(16.5, 25.0, 0)), + Some(Led(17.5, 25.0, 0)), + Some(Led(18.5, 25.0, 0)), + Some(Led(19.5, 25.0, 0)), + Some(Led(20.5, 25.0, 0)), + Some(Led(21.5, 25.0, 0)), + Some(Led(22.5, 25.0, 0)), + Some(Led(23.5, 25.0, 0)), + Some(Led(24.5, 25.0, 0)), + Some(Led(25.5, 25.0, 0)), + Some(Led(26.5, 25.0, 0)), + Some(Led(27.5, 25.0, 0)), + Some(Led(28.5, 25.0, 0)), + Some(Led(29.5, 25.0, 0)), + Some(Led(30.5, 25.0, 0)), + Some(Led(31.5, 25.0, 0)), + None, + Some(Led(10.0, 26.0, 0)), + Some(Led(11.0, 26.0, 0)), + Some(Led(12.0, 26.0, 0)), + Some(Led(13.0, 26.0, 0)), + Some(Led(14.0, 26.0, 0)), + Some(Led(15.0, 26.0, 0)), + Some(Led(16.0, 26.0, 0)), + Some(Led(17.0, 26.0, 0)), + Some(Led(18.0, 26.0, 0)), + Some(Led(19.0, 26.0, 0)), + Some(Led(20.0, 26.0, 0)), + Some(Led(21.0, 26.0, 0)), + Some(Led(22.0, 26.0, 0)), + Some(Led(23.0, 26.0, 0)), + Some(Led(24.0, 26.0, 0)), + Some(Led(25.0, 26.0, 0)), + Some(Led(26.0, 26.0, 0)), + Some(Led(27.0, 26.0, 0)), + Some(Led(28.0, 26.0, 0)), + Some(Led(29.0, 26.0, 0)), + Some(Led(30.0, 26.0, 0)), + Some(Led(31.0, 26.0, 0)), + Some(Led(32.0, 26.0, 0)), + Some(Led(10.5, 27.0, 0)), + Some(Led(11.5, 27.0, 0)), + Some(Led(12.5, 27.0, 0)), + Some(Led(13.5, 27.0, 0)), + Some(Led(14.5, 27.0, 0)), + Some(Led(15.5, 27.0, 0)), + Some(Led(16.5, 27.0, 0)), + Some(Led(17.5, 27.0, 0)), + Some(Led(18.5, 27.0, 0)), + Some(Led(19.5, 27.0, 0)), + Some(Led(20.5, 27.0, 0)), + Some(Led(21.5, 27.0, 0)), + Some(Led(22.5, 27.0, 0)), + Some(Led(23.5, 27.0, 0)), + Some(Led(24.5, 27.0, 0)), + Some(Led(25.5, 27.0, 0)), + Some(Led(26.5, 27.0, 0)), + Some(Led(27.5, 27.0, 0)), + Some(Led(28.5, 27.0, 0)), + Some(Led(29.5, 27.0, 0)), + Some(Led(30.5, 27.0, 0)), + Some(Led(31.5, 27.0, 0)), + None, + Some(Led(11.0, 28.0, 0)), + Some(Led(12.0, 28.0, 0)), + Some(Led(13.0, 28.0, 0)), + Some(Led(14.0, 28.0, 0)), + Some(Led(15.0, 28.0, 0)), + Some(Led(16.0, 28.0, 0)), + Some(Led(17.0, 28.0, 0)), + Some(Led(18.0, 28.0, 0)), + Some(Led(19.0, 28.0, 0)), + Some(Led(20.0, 28.0, 0)), + Some(Led(21.0, 28.0, 0)), + Some(Led(22.0, 28.0, 0)), + Some(Led(23.0, 28.0, 0)), + Some(Led(24.0, 28.0, 0)), + Some(Led(25.0, 28.0, 0)), + Some(Led(26.0, 28.0, 0)), + Some(Led(27.0, 28.0, 0)), + Some(Led(28.0, 28.0, 0)), + Some(Led(29.0, 28.0, 0)), + Some(Led(30.0, 28.0, 0)), + Some(Led(31.0, 28.0, 0)), + Some(Led(32.0, 28.0, 0)), + Some(Led(11.5, 29.0, 0)), + Some(Led(12.5, 29.0, 0)), + Some(Led(13.5, 29.0, 0)), + Some(Led(14.5, 29.0, 0)), + Some(Led(15.5, 29.0, 0)), + Some(Led(16.5, 29.0, 0)), + Some(Led(17.5, 29.0, 0)), + Some(Led(18.5, 29.0, 0)), + Some(Led(19.5, 29.0, 0)), + Some(Led(20.5, 29.0, 0)), + Some(Led(21.5, 29.0, 0)), + Some(Led(22.5, 29.0, 0)), + Some(Led(23.5, 29.0, 0)), + Some(Led(24.5, 29.0, 0)), + Some(Led(25.5, 29.0, 0)), + Some(Led(26.5, 29.0, 0)), + Some(Led(27.5, 29.0, 0)), + Some(Led(28.5, 29.0, 0)), + Some(Led(29.5, 29.0, 0)), + Some(Led(30.5, 29.0, 0)), + Some(Led(31.5, 29.0, 0)), + None, + Some(Led(12.0, 30.0, 0)), + Some(Led(13.0, 30.0, 0)), + Some(Led(14.0, 30.0, 0)), + Some(Led(15.0, 30.0, 0)), + Some(Led(16.0, 30.0, 0)), + Some(Led(17.0, 30.0, 0)), + Some(Led(18.0, 30.0, 0)), + Some(Led(19.0, 30.0, 0)), + Some(Led(20.0, 30.0, 0)), + Some(Led(21.0, 30.0, 0)), + Some(Led(22.0, 30.0, 0)), + Some(Led(23.0, 30.0, 0)), + Some(Led(24.0, 30.0, 0)), + Some(Led(25.0, 30.0, 0)), + Some(Led(26.0, 30.0, 0)), + Some(Led(27.0, 30.0, 0)), + Some(Led(28.0, 30.0, 0)), + Some(Led(29.0, 30.0, 0)), + Some(Led(30.0, 30.0, 0)), + Some(Led(31.0, 30.0, 0)), + Some(Led(32.0, 30.0, 0)), + Some(Led(12.5, 31.0, 0)), + Some(Led(13.5, 31.0, 0)), + Some(Led(14.5, 31.0, 0)), + Some(Led(15.5, 31.0, 0)), + Some(Led(16.5, 31.0, 0)), + Some(Led(17.5, 31.0, 0)), + Some(Led(18.5, 31.0, 0)), + Some(Led(19.5, 31.0, 0)), + Some(Led(20.5, 31.0, 0)), + Some(Led(21.5, 31.0, 0)), + Some(Led(22.5, 31.0, 0)), + Some(Led(23.5, 31.0, 0)), + Some(Led(24.5, 31.0, 0)), + Some(Led(25.5, 31.0, 0)), + Some(Led(26.5, 31.0, 0)), + Some(Led(27.5, 31.0, 0)), + Some(Led(28.5, 31.0, 0)), + Some(Led(29.5, 31.0, 0)), + Some(Led(30.5, 31.0, 0)), + Some(Led(31.5, 31.0, 0)), + None, + Some(Led(13.0, 32.0, 0)), + Some(Led(14.0, 32.0, 0)), + Some(Led(15.0, 32.0, 0)), + Some(Led(16.0, 32.0, 0)), + Some(Led(17.0, 32.0, 0)), + Some(Led(18.0, 32.0, 0)), + Some(Led(19.0, 32.0, 0)), + Some(Led(20.0, 32.0, 0)), + Some(Led(21.0, 32.0, 0)), + Some(Led(22.0, 32.0, 0)), + Some(Led(23.0, 32.0, 0)), + Some(Led(24.0, 32.0, 0)), + Some(Led(25.0, 32.0, 0)), + Some(Led(26.0, 32.0, 0)), + Some(Led(27.0, 32.0, 0)), + Some(Led(28.0, 32.0, 0)), + Some(Led(29.0, 32.0, 0)), + Some(Led(30.0, 32.0, 0)), + Some(Led(31.0, 32.0, 0)), + Some(Led(32.0, 32.0, 0)), + Some(Led(13.5, 33.0, 0)), + Some(Led(14.5, 33.0, 0)), + Some(Led(15.5, 33.0, 0)), + Some(Led(16.5, 33.0, 0)), + Some(Led(17.5, 33.0, 0)), + Some(Led(18.5, 33.0, 0)), + Some(Led(19.5, 33.0, 0)), + Some(Led(20.5, 33.0, 0)), + Some(Led(21.5, 33.0, 0)), + Some(Led(22.5, 33.0, 0)), + Some(Led(23.5, 33.0, 0)), + Some(Led(24.5, 33.0, 0)), + Some(Led(25.5, 33.0, 0)), + Some(Led(26.5, 33.0, 0)), + Some(Led(27.5, 33.0, 0)), + Some(Led(28.5, 33.0, 0)), + Some(Led(29.5, 33.0, 0)), + Some(Led(30.5, 33.0, 0)), + Some(Led(31.5, 33.0, 0)), + None, + Some(Led(14.0, 34.0, 0)), + Some(Led(15.0, 34.0, 0)), + Some(Led(16.0, 34.0, 0)), + Some(Led(17.0, 34.0, 0)), + Some(Led(18.0, 34.0, 0)), + Some(Led(19.0, 34.0, 0)), + Some(Led(20.0, 34.0, 0)), + Some(Led(21.0, 34.0, 0)), + Some(Led(22.0, 34.0, 0)), + Some(Led(23.0, 34.0, 0)), + Some(Led(24.0, 34.0, 0)), + Some(Led(25.0, 34.0, 0)), + Some(Led(26.0, 34.0, 0)), + Some(Led(27.0, 34.0, 0)), + Some(Led(28.0, 34.0, 0)), + Some(Led(29.0, 34.0, 0)), + Some(Led(30.0, 34.0, 0)), + Some(Led(31.0, 34.0, 0)), + Some(Led(32.0, 34.0, 0)), + Some(Led(14.5, 35.0, 0)), + Some(Led(15.5, 35.0, 0)), + Some(Led(16.5, 35.0, 0)), + Some(Led(17.5, 35.0, 0)), + Some(Led(18.5, 35.0, 0)), + Some(Led(19.5, 35.0, 0)), + Some(Led(20.5, 35.0, 0)), + Some(Led(21.5, 35.0, 0)), + Some(Led(22.5, 35.0, 0)), + Some(Led(23.5, 35.0, 0)), + Some(Led(24.5, 35.0, 0)), + Some(Led(25.5, 35.0, 0)), + Some(Led(26.5, 35.0, 0)), + Some(Led(27.5, 35.0, 0)), + Some(Led(28.5, 35.0, 0)), + Some(Led(29.5, 35.0, 0)), + Some(Led(30.5, 35.0, 0)), + Some(Led(31.5, 35.0, 0)), + None, + Some(Led(15.0, 36.0, 0)), + Some(Led(16.0, 36.0, 0)), + Some(Led(17.0, 36.0, 0)), + Some(Led(18.0, 36.0, 0)), + Some(Led(19.0, 36.0, 0)), + Some(Led(20.0, 36.0, 0)), + Some(Led(21.0, 36.0, 0)), + Some(Led(22.0, 36.0, 0)), + Some(Led(23.0, 36.0, 0)), + Some(Led(24.0, 36.0, 0)), + Some(Led(25.0, 36.0, 0)), + Some(Led(26.0, 36.0, 0)), + Some(Led(27.0, 36.0, 0)), + Some(Led(28.0, 36.0, 0)), + Some(Led(29.0, 36.0, 0)), + Some(Led(30.0, 36.0, 0)), + Some(Led(31.0, 36.0, 0)), + Some(Led(32.0, 36.0, 0)), + Some(Led(15.5, 37.0, 0)), + Some(Led(16.5, 37.0, 0)), + Some(Led(17.5, 37.0, 0)), + Some(Led(18.5, 37.0, 0)), + Some(Led(19.5, 37.0, 0)), + Some(Led(20.5, 37.0, 0)), + Some(Led(21.5, 37.0, 0)), + Some(Led(22.5, 37.0, 0)), + Some(Led(23.5, 37.0, 0)), + Some(Led(24.5, 37.0, 0)), + Some(Led(25.5, 37.0, 0)), + Some(Led(26.5, 37.0, 0)), + Some(Led(27.5, 37.0, 0)), + Some(Led(28.5, 37.0, 0)), + Some(Led(29.5, 37.0, 0)), + Some(Led(30.5, 37.0, 0)), + Some(Led(31.5, 37.0, 0)), + None, + Some(Led(16.0, 38.0, 0)), + Some(Led(17.0, 38.0, 0)), + Some(Led(18.0, 38.0, 0)), + Some(Led(19.0, 38.0, 0)), + Some(Led(20.0, 38.0, 0)), + Some(Led(21.0, 38.0, 0)), + Some(Led(22.0, 38.0, 0)), + Some(Led(23.0, 38.0, 0)), + Some(Led(24.0, 38.0, 0)), + Some(Led(25.0, 38.0, 0)), + Some(Led(26.0, 38.0, 0)), + Some(Led(27.0, 38.0, 0)), + Some(Led(28.0, 38.0, 0)), + Some(Led(29.0, 38.0, 0)), + Some(Led(30.0, 38.0, 0)), + Some(Led(31.0, 38.0, 0)), + Some(Led(32.0, 38.0, 0)), + Some(Led(16.5, 39.0, 0)), + Some(Led(17.5, 39.0, 0)), + Some(Led(18.5, 39.0, 0)), + Some(Led(19.5, 39.0, 0)), + Some(Led(20.5, 39.0, 0)), + Some(Led(21.5, 39.0, 0)), + Some(Led(22.5, 39.0, 0)), + Some(Led(23.5, 39.0, 0)), + Some(Led(24.5, 39.0, 0)), + Some(Led(25.5, 39.0, 0)), + Some(Led(26.5, 39.0, 0)), + Some(Led(27.5, 39.0, 0)), + Some(Led(28.5, 39.0, 0)), + Some(Led(29.5, 39.0, 0)), + Some(Led(30.5, 39.0, 0)), + Some(Led(31.5, 39.0, 0)), + None, + Some(Led(17.0, 40.0, 0)), + Some(Led(18.0, 40.0, 0)), + Some(Led(19.0, 40.0, 0)), + Some(Led(20.0, 40.0, 0)), + Some(Led(21.0, 40.0, 0)), + Some(Led(22.0, 40.0, 0)), + Some(Led(23.0, 40.0, 0)), + Some(Led(24.0, 40.0, 0)), + Some(Led(25.0, 40.0, 0)), + Some(Led(26.0, 40.0, 0)), + Some(Led(27.0, 40.0, 0)), + Some(Led(28.0, 40.0, 0)), + Some(Led(29.0, 40.0, 0)), + Some(Led(30.0, 40.0, 0)), + Some(Led(31.0, 40.0, 0)), + Some(Led(32.0, 40.0, 0)), + Some(Led(17.5, 41.0, 0)), + Some(Led(18.5, 41.0, 0)), + Some(Led(19.5, 41.0, 0)), + Some(Led(20.5, 41.0, 0)), + Some(Led(21.5, 41.0, 0)), + Some(Led(22.5, 41.0, 0)), + Some(Led(23.5, 41.0, 0)), + Some(Led(24.5, 41.0, 0)), + Some(Led(25.5, 41.0, 0)), + Some(Led(26.5, 41.0, 0)), + Some(Led(27.5, 41.0, 0)), + Some(Led(28.5, 41.0, 0)), + Some(Led(29.5, 41.0, 0)), + Some(Led(30.5, 41.0, 0)), + Some(Led(31.5, 41.0, 0)), + None, + Some(Led(18.0, 42.0, 0)), + Some(Led(19.0, 42.0, 0)), + Some(Led(20.0, 42.0, 0)), + Some(Led(21.0, 42.0, 0)), + Some(Led(22.0, 42.0, 0)), + Some(Led(23.0, 42.0, 0)), + Some(Led(24.0, 42.0, 0)), + Some(Led(25.0, 42.0, 0)), + Some(Led(26.0, 42.0, 0)), + Some(Led(27.0, 42.0, 0)), + Some(Led(28.0, 42.0, 0)), + Some(Led(29.0, 42.0, 0)), + Some(Led(30.0, 42.0, 0)), + Some(Led(31.0, 42.0, 0)), + Some(Led(32.0, 42.0, 0)), + Some(Led(18.5, 43.0, 0)), + Some(Led(19.5, 43.0, 0)), + Some(Led(20.5, 43.0, 0)), + Some(Led(21.5, 43.0, 0)), + Some(Led(22.5, 43.0, 0)), + Some(Led(23.5, 43.0, 0)), + Some(Led(24.5, 43.0, 0)), + Some(Led(25.5, 43.0, 0)), + Some(Led(26.5, 43.0, 0)), + Some(Led(27.5, 43.0, 0)), + Some(Led(28.5, 43.0, 0)), + Some(Led(29.5, 43.0, 0)), + Some(Led(30.5, 43.0, 0)), + Some(Led(31.5, 43.0, 0)), + None, + Some(Led(19.0, 44.0, 0)), + Some(Led(20.0, 44.0, 0)), + Some(Led(21.0, 44.0, 0)), + Some(Led(22.0, 44.0, 0)), + Some(Led(23.0, 44.0, 0)), + Some(Led(24.0, 44.0, 0)), + Some(Led(25.0, 44.0, 0)), + Some(Led(26.0, 44.0, 0)), + Some(Led(27.0, 44.0, 0)), + Some(Led(28.0, 44.0, 0)), + Some(Led(29.0, 44.0, 0)), + Some(Led(30.0, 44.0, 0)), + Some(Led(31.0, 44.0, 0)), + Some(Led(32.0, 44.0, 0)), + Some(Led(19.5, 45.0, 0)), + Some(Led(20.5, 45.0, 0)), + Some(Led(21.5, 45.0, 0)), + Some(Led(22.5, 45.0, 0)), + Some(Led(23.5, 45.0, 0)), + Some(Led(24.5, 45.0, 0)), + Some(Led(25.5, 45.0, 0)), + Some(Led(26.5, 45.0, 0)), + Some(Led(27.5, 45.0, 0)), + Some(Led(28.5, 45.0, 0)), + Some(Led(29.5, 45.0, 0)), + Some(Led(30.5, 45.0, 0)), + Some(Led(31.5, 45.0, 0)), + None, + Some(Led(20.0, 46.0, 0)), + Some(Led(21.0, 46.0, 0)), + Some(Led(22.0, 46.0, 0)), + Some(Led(23.0, 46.0, 0)), + Some(Led(24.0, 46.0, 0)), + Some(Led(25.0, 46.0, 0)), + Some(Led(26.0, 46.0, 0)), + Some(Led(27.0, 46.0, 0)), + Some(Led(28.0, 46.0, 0)), + Some(Led(29.0, 46.0, 0)), + Some(Led(30.0, 46.0, 0)), + Some(Led(31.0, 46.0, 0)), + Some(Led(32.0, 46.0, 0)), + Some(Led(20.5, 47.0, 0)), + Some(Led(21.5, 47.0, 0)), + Some(Led(22.5, 47.0, 0)), + Some(Led(23.5, 47.0, 0)), + Some(Led(24.5, 47.0, 0)), + Some(Led(25.5, 47.0, 0)), + Some(Led(26.5, 47.0, 0)), + Some(Led(27.5, 47.0, 0)), + Some(Led(28.5, 47.0, 0)), + Some(Led(29.5, 47.0, 0)), + Some(Led(30.5, 47.0, 0)), + Some(Led(31.5, 47.0, 0)), + None, + Some(Led(21.0, 48.0, 0)), + Some(Led(22.0, 48.0, 0)), + Some(Led(23.0, 48.0, 0)), + Some(Led(24.0, 48.0, 0)), + Some(Led(25.0, 48.0, 0)), + Some(Led(26.0, 48.0, 0)), + Some(Led(27.0, 48.0, 0)), + Some(Led(28.0, 48.0, 0)), + Some(Led(29.0, 48.0, 0)), + Some(Led(30.0, 48.0, 0)), + Some(Led(31.0, 48.0, 0)), + Some(Led(32.0, 48.0, 0)), + Some(Led(21.5, 49.0, 0)), + Some(Led(22.5, 49.0, 0)), + Some(Led(23.5, 49.0, 0)), + Some(Led(24.5, 49.0, 0)), + Some(Led(25.5, 49.0, 0)), + Some(Led(26.5, 49.0, 0)), + Some(Led(27.5, 49.0, 0)), + Some(Led(28.5, 49.0, 0)), + Some(Led(29.5, 49.0, 0)), + Some(Led(30.5, 49.0, 0)), + Some(Led(31.5, 49.0, 0)), + None, + Some(Led(22.0, 50.0, 0)), + Some(Led(23.0, 50.0, 0)), + Some(Led(24.0, 50.0, 0)), + Some(Led(25.0, 50.0, 0)), + Some(Led(26.0, 50.0, 0)), + Some(Led(27.0, 50.0, 0)), + Some(Led(28.0, 50.0, 0)), + Some(Led(29.0, 50.0, 0)), + Some(Led(30.0, 50.0, 0)), + Some(Led(31.0, 50.0, 0)), + Some(Led(32.0, 50.0, 0)), + Some(Led(22.5, 51.0, 0)), + Some(Led(23.5, 51.0, 0)), + Some(Led(24.5, 51.0, 0)), + Some(Led(25.5, 51.0, 0)), + Some(Led(26.5, 51.0, 0)), + Some(Led(27.5, 51.0, 0)), + Some(Led(28.5, 51.0, 0)), + Some(Led(29.5, 51.0, 0)), + Some(Led(30.5, 51.0, 0)), + Some(Led(31.5, 51.0, 0)), + None, + Some(Led(23.0, 52.0, 0)), + Some(Led(24.0, 52.0, 0)), + Some(Led(25.0, 52.0, 0)), + Some(Led(26.0, 52.0, 0)), + Some(Led(27.0, 52.0, 0)), + Some(Led(28.0, 52.0, 0)), + Some(Led(29.0, 52.0, 0)), + Some(Led(30.0, 52.0, 0)), + Some(Led(31.0, 52.0, 0)), + Some(Led(32.0, 52.0, 0)), + Some(Led(23.5, 53.0, 0)), + Some(Led(24.5, 53.0, 0)), + Some(Led(25.5, 53.0, 0)), + Some(Led(26.5, 53.0, 0)), + Some(Led(27.5, 53.0, 0)), + Some(Led(28.5, 53.0, 0)), + Some(Led(29.5, 53.0, 0)), + Some(Led(30.5, 53.0, 0)), + Some(Led(31.5, 53.0, 0)), + None, + Some(Led(24.0, 54.0, 0)), + Some(Led(25.0, 54.0, 0)), + Some(Led(26.0, 54.0, 0)), + Some(Led(27.0, 54.0, 0)), + Some(Led(28.0, 54.0, 0)), + Some(Led(29.0, 54.0, 0)), + Some(Led(30.0, 54.0, 0)), + Some(Led(31.0, 54.0, 0)), + Some(Led(32.0, 54.0, 0)), +]; + +#[cfg(test)] +mod tests { + use crate::anime_image::*; + + #[test] + fn led_positions() { + let leds = AnimeImage::generate(); + assert_eq!(leds[0], Some(Led(0.0, 0.0, 0))); + assert_eq!(leds[1], Some(Led(1.0, 0.0, 0))); + assert_eq!(leds[2], Some(Led(2.0, 0.0, 0))); + assert_eq!(leds[32], Some(Led(32.0, 0.0, 0))); + assert_eq!(leds[33], Some(Led(-0.5, 1.0, 0))); + assert_eq!(leds[65], Some(Led(31.5, 1.0, 0))); + assert_eq!(leds[66], None); + assert_eq!(leds[67], None); + assert_eq!(leds[68], Some(Led(0.0, 2.0, 0))); + assert_eq!(leds[100], Some(Led(32.0, 2.0, 0))); + assert_eq!(leds[101], Some(Led(-0.5, 3.0, 0))); + assert_eq!(leds[133], Some(Led(31.5, 3.0, 0))); + assert_eq!(leds[134], None); + assert_eq!(leds[135], None); + assert_eq!(leds[136], Some(Led(0.0, 4.0, 0))); + assert_eq!(leds[168], Some(Led(32.0, 4.0, 0))); + assert_eq!(leds[169], Some(Led(-0.5, 5.0, 0))); + assert_eq!(leds[201], Some(Led(31.5, 5.0, 0))); + assert_eq!(leds[202], None); + assert_eq!(leds[203], Some(Led(0.0, 6.0, 0))); + assert_eq!(leds[235], Some(Led(32.0, 6.0, 0))); + assert_eq!(leds[648], Some(Led(32.0, 20.0, 0))); // end + assert_eq!(leds[649], Some(Led(7.5, 21.0, 0))); // start + assert_eq!(leds[673], Some(Led(31.5, 21.0, 0))); // end + } + + #[test] + fn led_positions_const() { + let leds = AnimeImage::generate(); + assert_eq!(leds[1], LED_IMAGE_POSITIONS[1]); + assert_eq!(leds[34], LED_IMAGE_POSITIONS[34]); + assert_eq!(leds[69], LED_IMAGE_POSITIONS[69]); + assert_eq!(leds[137], LED_IMAGE_POSITIONS[137]); + assert_eq!(leds[169], LED_IMAGE_POSITIONS[169]); + assert_eq!(leds[170], LED_IMAGE_POSITIONS[170]); + assert_eq!(leds[236], LED_IMAGE_POSITIONS[236]); + assert_eq!(leds[649], LED_IMAGE_POSITIONS[649]); + assert_eq!(leds[674], LED_IMAGE_POSITIONS[674]); + } + + #[test] + fn row_starts() { + assert_eq!(AnimeImage::first_x(5), 0); + assert_eq!(AnimeImage::first_x(6), 0); + assert_eq!(AnimeImage::first_x(7), 1); + assert_eq!(AnimeImage::first_x(8), 1); + assert_eq!(AnimeImage::first_x(9), 2); + assert_eq!(AnimeImage::first_x(10), 2); + assert_eq!(AnimeImage::first_x(11), 3); + } + + #[test] + fn row_widths() { + assert_eq!(AnimeImage::width(5), 33); + assert_eq!(AnimeImage::width(6), 33); + assert_eq!(AnimeImage::width(7), 32); + assert_eq!(AnimeImage::width(8), 32); + assert_eq!(AnimeImage::width(9), 31); + assert_eq!(AnimeImage::width(10), 31); + assert_eq!(AnimeImage::width(11), 30); + assert_eq!(AnimeImage::width(12), 30); + assert_eq!(AnimeImage::width(13), 29); + assert_eq!(AnimeImage::width(14), 29); + assert_eq!(AnimeImage::width(15), 28); + assert_eq!(AnimeImage::width(16), 28); + assert_eq!(AnimeImage::width(17), 27); + assert_eq!(AnimeImage::width(18), 27); + } + + #[test] + fn row_pitch() { + assert_eq!(AnimeImage::pitch(5), 34); + assert_eq!(AnimeImage::pitch(6), 33); + assert_eq!(AnimeImage::pitch(7), 33); + assert_eq!(AnimeImage::pitch(8), 32); + assert_eq!(AnimeImage::pitch(9), 32); + assert_eq!(AnimeImage::pitch(10), 31); + assert_eq!(AnimeImage::pitch(11), 31); + assert_eq!(AnimeImage::pitch(12), 30); + assert_eq!(AnimeImage::pitch(13), 30); + assert_eq!(AnimeImage::pitch(14), 29); + } +} diff --git a/rog-anime/src/error.rs b/rog-anime/src/error.rs new file mode 100644 index 00000000..3e7c7ddc --- /dev/null +++ b/rog-anime/src/error.rs @@ -0,0 +1,37 @@ +use std::error::Error; +use std::fmt; +use png_pong::decode::Error as PngError; + +#[derive(Debug)] +pub enum AnimeError { + NoFrames, + Io(std::io::Error), + Png(PngError), + Format +} + +impl fmt::Display for AnimeError { + // This trait requires `fmt` with this exact signature. + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + AnimeError::NoFrames => write!(f, "No frames in PNG"), + AnimeError::Io(e) => write!(f, "Could not open: {}", e), + AnimeError::Png(e) => write!(f, "PNG error: {}", e), + AnimeError::Format => write!(f, "PNG file is not 8bit greyscale"), + } + } +} + +impl Error for AnimeError {} + +impl From for AnimeError { + fn from(err: std::io::Error) -> Self { + AnimeError::Io(err) + } +} + +impl From for AnimeError { + fn from(err: PngError) -> Self { + AnimeError::Png(err) + } +} \ No newline at end of file diff --git a/rog-anime/src/lib.rs b/rog-anime/src/lib.rs new file mode 100644 index 00000000..0d9a86a0 --- /dev/null +++ b/rog-anime/src/lib.rs @@ -0,0 +1,15 @@ +/// The main data conversion for transfering in shortform over dbus or other, +/// or writing directly to the USB device +mod anime_data; +pub use anime_data::*; + +/// Useful for specialised effects that required a grid of data +mod anime_grid; +pub use anime_grid::*; + +/// Transform a PNG image for displaying on AniMe matrix display +mod anime_image; +pub use anime_image::*; + +/// Base errors that are possible +pub mod error; \ No newline at end of file diff --git a/rog-dbus/Cargo.toml b/rog-dbus/Cargo.toml index fd1e3778..bab8ceb7 100644 --- a/rog-dbus/Cargo.toml +++ b/rog-dbus/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rog_dbus" -version = "3.1.0" +version = "3.2.0" license = "MPL-2.0" readme = "README.md" authors = ["Luke "] @@ -11,6 +11,7 @@ edition = "2018" [dependencies] serde_json = "^1.0" +rog_anime = { path = "../rog-anime" } rog_types = { path = "../rog-types" } rog_fan_curve = { version = "^0.1", features = ["serde"] } zbus = "^1.8" diff --git a/rog-dbus/src/zbus_anime.rs b/rog-dbus/src/zbus_anime.rs index 209e6432..ed9746b8 100644 --- a/rog-dbus/src/zbus_anime.rs +++ b/rog-dbus/src/zbus_anime.rs @@ -19,7 +19,7 @@ //! //! …consequently `zbus-xmlgen` did not generate code for the above interfaces. -use rog_types::anime_matrix::{AniMeDataBuffer, AniMeImageBuffer}; +use rog_anime::AniMeDataBuffer; use zbus::{dbus_proxy, Connection, Result}; #[dbus_proxy( @@ -34,10 +34,7 @@ trait Daemon { fn set_on_off(&self, status: bool) -> zbus::Result<()>; /// WriteDirect method - fn write_direct(&self, input: &[u8]) -> zbus::Result<()>; - - /// WriteImage method - fn write_image(&self, input: &[Vec]) -> zbus::Result<()>; + fn write(&self, input: &[u8]) -> zbus::Result<()>; } pub struct AnimeProxy<'a>(DaemonProxy<'a>); @@ -63,12 +60,7 @@ impl<'a> AnimeProxy<'a> { } #[inline] - pub fn write_direct(&self, input: AniMeDataBuffer) -> Result<()> { - self.0.write_direct(input.get()) - } - - #[inline] - pub fn write_image(&self, input: AniMeImageBuffer) -> Result<()> { - self.0.write_image(input.get()) + pub fn write(&self, input: AniMeDataBuffer) -> Result<()> { + self.0.write(input.get()) } } diff --git a/rog-dbus/src/zbus_gfx.rs b/rog-dbus/src/zbus_gfx.rs index c0f726b9..a8263b42 100644 --- a/rog-dbus/src/zbus_gfx.rs +++ b/rog-dbus/src/zbus_gfx.rs @@ -21,7 +21,7 @@ use std::sync::{Arc, Mutex}; -use rog_types::gfx_vendors::{GfxRequiredUserAction, GfxVendors}; +use rog_types::gfx_vendors::{GfxPower, GfxRequiredUserAction, GfxVendors}; use zbus::{dbus_proxy, Connection, Result}; #[dbus_proxy( @@ -30,7 +30,7 @@ use zbus::{dbus_proxy, Connection, Result}; )] trait Daemon { /// Power method - fn power(&self) -> zbus::Result; + fn power(&self) -> zbus::Result; /// SetVendor method fn set_vendor(&self, vendor: &GfxVendors) -> zbus::Result; @@ -60,7 +60,7 @@ impl<'a> GfxProxy<'a> { } #[inline] - pub fn gfx_get_pwr(&self) -> Result { + pub fn gfx_get_pwr(&self) -> Result { self.0.power() } diff --git a/rog-types/Cargo.toml b/rog-types/Cargo.toml index f5a47edc..7c2241e5 100644 --- a/rog-types/Cargo.toml +++ b/rog-types/Cargo.toml @@ -11,8 +11,8 @@ edition = "2018" [dependencies] gumdrop = "^0.8" +rog_fan_curve = { version = "^0.1", features = ["serde"] } serde = "^1.0" serde_derive = "^1.0" -rog_fan_curve = { version = "^0.1", features = ["serde"] } zvariant = "^2.5" zvariant_derive = "^2.5" \ No newline at end of file diff --git a/rog-types/src/anime_matrix.rs b/rog-types/src/anime_matrix.rs deleted file mode 100644 index fa5c6ad2..00000000 --- a/rog-types/src/anime_matrix.rs +++ /dev/null @@ -1,291 +0,0 @@ -use serde_derive::{Deserialize, Serialize}; -use zvariant_derive::Type; - -pub const WIDTH: usize = 34; // Width is definitely 34 items -pub const HEIGHT: usize = 56; -pub type AniMePacketType = [[u8; 640]; 2]; -const BLOCK_START: usize = 7; -/// *Not* inclusive, the byte before this is the final for each "pane" -const BLOCK_END: usize = 634; -pub const PANE_LEN: usize = BLOCK_END - BLOCK_START; -/// The length of usable data -pub const FULL_PANE_LEN: usize = PANE_LEN * 2; - -pub const ANIME_PANE1_PREFIX: [u8; 7] = [0x5e, 0xc0, 0x02, 0x01, 0x00, 0x73, 0x02]; -pub const ANIME_PANE2_PREFIX: [u8; 7] = [0x5e, 0xc0, 0x02, 0x74, 0x02, 0x73, 0x02]; - -#[derive(Debug, Deserialize, Serialize, Type)] -pub struct AniMeDataBuffer(Vec); - -impl Default for AniMeDataBuffer { - fn default() -> Self { - Self::new() - } -} - -impl AniMeDataBuffer { - pub fn new() -> Self { - AniMeDataBuffer(vec![0u8; FULL_PANE_LEN]) - } - - pub fn get(&self) -> &[u8] { - &self.0 - } - - pub fn set(&mut self, input: [u8; FULL_PANE_LEN]) { - self.0 = input.to_vec(); - } -} - -impl From for AniMePacketType { - #[inline] - fn from(anime: AniMeDataBuffer) -> Self { - assert!(anime.0.len() == FULL_PANE_LEN); - let mut buffers = [[0; 640]; 2]; - for (idx, chunk) in anime.0.as_slice().chunks(PANE_LEN).enumerate() { - buffers[idx][BLOCK_START..BLOCK_END].copy_from_slice(chunk); - } - buffers - } -} - -/// Helper structure for writing images. -/// -/// See the examples for ways to write an image to `AniMeMatrix` format. -#[derive(Debug, Deserialize, Serialize, Type)] -pub struct AniMeImageBuffer(Vec>); - -impl Default for AniMeImageBuffer { - fn default() -> Self { - Self::new() - } -} - -impl AniMeImageBuffer { - pub fn new() -> Self { - AniMeImageBuffer(vec![vec![0u8; WIDTH]; HEIGHT]) - } - - pub fn get(&self) -> &Vec> { - &self.0 - } - - pub fn get_mut(&mut self) -> &mut Vec> { - &mut self.0 - } - - pub fn fill_with(&mut self, fill: u8) { - for row in self.0.iter_mut() { - for x in row.iter_mut() { - *x = fill; - } - } - } - - pub fn debug_print(&self) { - // this is the index from right. It is used to progressively shorten rows - let mut prog_row_len = WIDTH - 2; - - for (count, row) in self.0.iter().enumerate() { - // Write the top block of LEDs (first 7 rows) - if count < 6 { - if count % 2 != 0 { - print!(" "); - } else { - print!(""); - } - let tmp = if count == 0 || count == 1 || count == 3 || count == 5 { - row[1..].iter() - } else { - row.iter() - }; - for _ in tmp { - print!(" XY"); - } - - println!(); - } else { - // Switch to next block (looks like ) - if count % 2 != 0 { - // Row after 6 is only 1 less, then rows after 7 follow pattern - if count == 7 { - prog_row_len -= 1; - } else { - prog_row_len -= 2; - } - } else { - prog_row_len += 1; // if count 6, 0 - } - - let index = row.len() - prog_row_len; - - if count % 2 == 0 { - print!(" "); - } - for (i, _) in row.iter().enumerate() { - if i >= index { - print!(" XY"); - } else { - print!(" "); - } - } - println!(); - } - } - } -} - -impl From for AniMePacketType { - /// Do conversion from the nested Vec in AniMeMatrix to the two required - /// packets suitable for sending over USB - #[inline] - fn from(anime: AniMeImageBuffer) -> Self { - let mut buffers = [[0; 640]; 2]; - - let mut write_index = BLOCK_START; - let mut write_block = &mut buffers[0]; - let mut block1_done = false; - - // this is the index from right. It is used to progressively shorten rows - let mut prog_row_len = WIDTH - 2; - - for (count, row) in anime.0.iter().enumerate() { - // Write the top block of LEDs (first 7 rows) - if count < 6 { - for (i, x) in row.iter().enumerate() { - // Rows 0, 1, 3, 5 are short and misaligned - if count == 0 || count == 1 || count == 3 || count == 5 { - if i > 0 { - write_block[write_index - 1] = *x; - } - } else { - write_block[write_index] = *x; - } - write_index += 1; - } - } else { - // Switch to next block (looks like ) - if count % 2 != 0 { - // Row after 6 is only 1 less, then rows after 7 follow pattern - if count == 7 { - prog_row_len -= 1; - } else { - prog_row_len -= 2; - } - } else { - prog_row_len += 1; // if count 6, 0 - } - - let index = row.len() - prog_row_len; - for n in row.iter().skip(index) { - // Require a special case to catch the correct end-of-packet which is - // 6 bytes from the end - if write_index == BLOCK_END && !block1_done { - block1_done = true; - write_block = &mut buffers[1]; - write_index = BLOCK_START; - } - - write_block[write_index] = *n; - write_index += 1; - } - } - } - buffers - } -} - -#[cfg(test)] -mod tests { - use crate::anime_matrix::*; - - use super::AniMeDataBuffer; - - #[test] - fn check_from_data_buffer() { - let mut data = AniMeDataBuffer::new(); - data.set([42u8; FULL_PANE_LEN]); - - let out: AniMePacketType = data.into(); - } - - #[test] - fn check_data_alignment() { - let mut matrix = AniMeImageBuffer::new(); - { - let tmp = matrix.get_mut(); - for row in tmp.iter_mut() { - let idx = row.len() - 1; - row[idx] = 0xff; - } - } - - let matrix: AniMePacketType = AniMePacketType::from(matrix); - - // The bytes at the right of the initial AniMeMatrix should always end up aligned in the - // same place after conversion to data packets - - // Check against manually worked out right align - assert_eq!( - matrix[0].to_vec(), - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] - .to_vec() - ); - assert_eq!( - matrix[1].to_vec(), - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, - 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, - 0, 0, 0, 0 - ] - .to_vec() - ); - } -} diff --git a/rog-types/src/aura_perkey.rs b/rog-types/src/aura_perkey.rs index 82721643..6c4da418 100644 --- a/rog-types/src/aura_perkey.rs +++ b/rog-types/src/aura_perkey.rs @@ -59,7 +59,7 @@ impl KeyColourArray { Key::VolDown => (0, 15), Key::VolUp => (0, 18), Key::MicMute => (0, 21), - Key::ROG => (0, 24), + Key::Rog => (0, 24), // Key::Esc => (1, 24), Key::F1 => (1, 30), @@ -186,7 +186,7 @@ pub enum Key { VolUp, VolDown, MicMute, - ROG, + Rog, Esc, F1, F2, @@ -287,6 +287,7 @@ pub trait KeyLayout { fn get_rows(&self) -> &Vec<[Key; 17]>; } +#[allow(clippy::upper_case_acronyms)] pub struct GX502Layout(Vec<[Key; 17]>); impl KeyLayout for GX502Layout { @@ -304,7 +305,7 @@ impl Default for GX502Layout { Key::VolDown, Key::VolUp, Key::MicMute, - Key::ROG, + Key::Rog, Key::None, Key::None, Key::None, diff --git a/rog-types/src/cli_options.rs b/rog-types/src/cli_options.rs deleted file mode 100644 index c186becc..00000000 --- a/rog-types/src/cli_options.rs +++ /dev/null @@ -1,57 +0,0 @@ -use crate::error::AuraError; -use gumdrop::Options; -use std::str::FromStr; - -#[derive(Copy, Clone, Debug)] -pub enum AniMeStatusValue { - On, - Off, -} -impl FromStr for AniMeStatusValue { - type Err = AuraError; - - fn from_str(s: &str) -> Result { - let s = s.to_lowercase(); - match s.as_str() { - "on" => Ok(AniMeStatusValue::On), - "off" => Ok(AniMeStatusValue::Off), - _ => { - print!("Invalid argument, must be one of: on, off"); - Err(AuraError::ParseAnime) - } - } - } -} -impl From for bool { - fn from(value: AniMeStatusValue) -> Self { - match value { - AniMeStatusValue::On => true, - AniMeStatusValue::Off => false, - } - } -} - -#[derive(Options)] -pub struct AniMeLeds { - #[options(help = "print help message")] - help: bool, - #[options( - no_long, - required, - short = "b", - meta = "", - help = "set all leds brightness value" - )] - led_brightness: u8, -} -impl AniMeLeds { - pub fn led_brightness(&self) -> u8 { - self.led_brightness - } -} - -#[derive(Options)] -pub enum AniMeActions { - #[options(help = "change all leds brightness")] - Leds(AniMeLeds), -} diff --git a/rog-types/src/error.rs b/rog-types/src/error.rs index 21556c22..153e1cdf 100644 --- a/rog-types/src/error.rs +++ b/rog-types/src/error.rs @@ -28,6 +28,7 @@ impl Error for AuraError {} #[derive(Debug)] pub enum GraphicsError { ParseVendor, + ParsePower, } impl fmt::Display for GraphicsError { @@ -35,8 +36,33 @@ impl fmt::Display for GraphicsError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { GraphicsError::ParseVendor => write!(f, "Could not parse vendor name"), + GraphicsError::ParsePower => write!(f, "Could not parse dGPU power status"), } } } impl Error for GraphicsError {} + +#[derive(Debug)] +pub enum AnimeError { + InvalidBitmap, + Io(std::io::Error), +} + +impl fmt::Display for AnimeError { + // This trait requires `fmt` with this exact signature. + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + AnimeError::InvalidBitmap => write!(f, "Bitmap is invalid"), + AnimeError::Io(e) => write!(f, "Could not open: {}", e), + } + } +} + +impl Error for AnimeError {} + +impl From for AnimeError { + fn from(err: std::io::Error) -> Self { + AnimeError::Io(err) + } +} diff --git a/rog-types/src/gfx_vendors.rs b/rog-types/src/gfx_vendors.rs index dad441d8..62a2b48c 100644 --- a/rog-types/src/gfx_vendors.rs +++ b/rog-types/src/gfx_vendors.rs @@ -3,6 +3,38 @@ use serde_derive::{Deserialize, Serialize}; use std::str::FromStr; use zvariant_derive::Type; +#[derive(Debug, Type, PartialEq, Copy, Clone, Deserialize, Serialize)] +pub enum GfxPower { + Active, + Suspended, + Off, + Unknown, +} + +impl FromStr for GfxPower { + type Err = GraphicsError; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().trim() { + "active" => Ok(GfxPower::Active), + "suspended" => Ok(GfxPower::Suspended), + "off" => Ok(GfxPower::Off), + _ => Ok(GfxPower::Unknown), + } + } +} + +impl From<&GfxPower> for &str { + fn from(gfx: &GfxPower) -> &'static str { + match gfx { + GfxPower::Active => "active", + GfxPower::Suspended => "suspended", + GfxPower::Off => "off", + GfxPower::Unknown => "unknown", + } + } +} + #[derive(Debug, Type, PartialEq, Copy, Clone, Deserialize, Serialize)] pub enum GfxVendors { Nvidia, diff --git a/rog-types/src/lib.rs b/rog-types/src/lib.rs index 3da40802..93dcf9a6 100644 --- a/rog-types/src/lib.rs +++ b/rog-types/src/lib.rs @@ -11,15 +11,9 @@ pub mod aura_modes; pub mod profile; -/// Contains mostly only what is required for parsing CLI options -pub mod cli_options; - /// Enables you to create fancy RGB effects pub mod aura_perkey; -/// Helper functions for the AniMe display -pub mod anime_matrix; - pub mod gfx_vendors; pub mod error; diff --git a/rog-types/src/profile.rs b/rog-types/src/profile.rs index a181c186..e6519847 100644 --- a/rog-types/src/profile.rs +++ b/rog-types/src/profile.rs @@ -13,7 +13,7 @@ pub struct Profile { } #[deprecated] -pub type CPUSettings = Profile; +pub type CpuSettings = Profile; impl Default for Profile { fn default() -> Self {