diff --git a/Cargo.lock b/Cargo.lock index 4118f22..edb3bff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -120,6 +120,12 @@ version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + [[package]] name = "async-channel" version = "1.9.0" @@ -330,6 +336,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "az" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" + [[package]] name = "backtrace" version = "0.3.69" @@ -345,6 +357,15 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "bare-metal" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" +dependencies = [ + "rustc_version 0.2.3", +] + [[package]] name = "base64" version = "0.9.3" @@ -367,6 +388,22 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bike" +version = "0.1.0" +dependencies = [ + "az", + "cortex-m", + "cortex-m-rt", + "embedded-alloc", + "embedded-hal", + "fixed", + "fugit", + "lights-core", + "panic-halt", + "rp-pico", +] + [[package]] name = "bit-set" version = "0.5.3" @@ -388,6 +425,12 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" +[[package]] +name = "bitfield" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" + [[package]] name = "bitflags" version = "1.3.2" @@ -692,6 +735,38 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +[[package]] +name = "cortex-m" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" +dependencies = [ + "bare-metal", + "bitfield", + "embedded-hal", + "volatile-register", +] + +[[package]] +name = "cortex-m-rt" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee84e813d593101b1723e13ec38b6ab6abbdbaaa4546553f5395ed274079ddb1" +dependencies = [ + "cortex-m-rt-macros", +] + +[[package]] +name = "cortex-m-rt-macros" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f6f3e36f203cfedbc78b357fb28730aa2c6dc1ab060ee5c2405e843988d3c7" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "cpufeatures" version = "0.2.11" @@ -710,6 +785,15 @@ dependencies = [ "crc-catalog", ] +[[package]] +name = "crc-any" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62ec9ff5f7965e4d7280bd5482acd20aadb50d632cf6c1d74493856b011fa73" +dependencies = [ + "debug-helper", +] + [[package]] name = "crc-catalog" version = "2.4.0" @@ -725,6 +809,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "critical-section" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" + [[package]] name = "crossbeam-deque" version = "0.8.4" @@ -826,6 +916,12 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" +[[package]] +name = "debug-helper" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f578e8e2c440e7297e008bb5486a3a8a194775224bbc23729b0dbdfaeebf162e" + [[package]] name = "deflate" version = "0.8.6" @@ -918,6 +1014,35 @@ dependencies = [ "serde 1.0.193", ] +[[package]] +name = "embedded-alloc" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddae17915accbac2cfbc64ea0ae6e3b330e6ea124ba108dada63646fd3c6f815" +dependencies = [ + "critical-section", + "linked_list_allocator", +] + +[[package]] +name = "embedded-dma" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "994f7e5b5cb23521c22304927195f236813053eb9c065dd2226a32ba64695446" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + [[package]] name = "emseries" version = "0.6.0" @@ -1056,7 +1181,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" dependencies = [ "memoffset", - "rustc_version", + "rustc_version 0.4.0", ] [[package]] @@ -1117,6 +1242,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "fixed" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02c69ce7e7c0f17aa18fdd9d0de39727adb9c6281f2ad12f57cbe54ae6e76e7d" +dependencies = [ + "az", + "bytemuck", + "half", + "typenum", +] + [[package]] name = "flate2" version = "1.0.28" @@ -1222,6 +1359,45 @@ dependencies = [ "percent-encoding 2.3.1", ] +[[package]] +name = "frunk" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11a351b59e12f97b4176ee78497dff72e4276fb1ceb13e19056aca7fa0206287" +dependencies = [ + "frunk_core", + "frunk_derives", +] + +[[package]] +name = "frunk_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af2469fab0bd07e64ccf0ad57a1438f63160c69b2e57f04a439653d68eb558d6" + +[[package]] +name = "frunk_derives" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fa992f1656e1707946bbba340ad244f0814009ef8c0118eb7b658395f19a2e" +dependencies = [ + "frunk_proc_macro_helpers", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "frunk_proc_macro_helpers" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35b54add839292b743aeda6ebedbd8b11e93404f902c56223e51b9ec18a13d2c" +dependencies = [ + "frunk_core", + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "ft-core" version = "0.1.0" @@ -1241,6 +1417,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" +[[package]] +name = "fugit" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17186ad64927d5ac8f02c1e77ccefa08ccd9eaa314d5a4772278aa204a22f7e7" +dependencies = [ + "gcd", +] + [[package]] name = "futures" version = "0.3.29" @@ -1369,6 +1554,12 @@ dependencies = [ "slab", ] +[[package]] +name = "gcd" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" + [[package]] name = "gdk-pixbuf" version = "0.18.3" @@ -2176,6 +2367,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.12.0" @@ -2303,6 +2503,20 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "lights-core" +version = "0.1.0" +dependencies = [ + "az", + "fixed", +] + +[[package]] +name = "linked_list_allocator" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286" + [[package]] name = "linux-raw-sys" version = "0.3.8" @@ -2531,6 +2745,21 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + [[package]] name = "nix" version = "0.27.1" @@ -2646,6 +2875,26 @@ dependencies = [ "libc", ] +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "object" version = "0.32.1" @@ -2768,6 +3017,12 @@ dependencies = [ "system-deps", ] +[[package]] +name = "panic-halt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de96540e0ebde571dc55c73d60ef407c653844e6f9a1e2fdbd40c07b9252d812" + [[package]] name = "parking" version = "2.2.0" @@ -2942,6 +3197,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pio" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76e09694b50f89f302ed531c1f2a7569f0be5867aee4ab4f8f729bbeec0078e3" +dependencies = [ + "arrayvec", + "num_enum", + "paste", +] + [[package]] name = "piper" version = "0.2.1" @@ -3452,6 +3718,76 @@ dependencies = [ "thiserror", ] +[[package]] +name = "rp-pico" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6341771e6f8e5d130b2b3cbc23435b7847761adf198af09f4b2a60407d43bd56" +dependencies = [ + "cortex-m-rt", + "fugit", + "rp2040-boot2", + "rp2040-hal", + "usb-device", +] + +[[package]] +name = "rp2040-boot2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c92f344f63f950ee36cf4080050e4dce850839b9175da38f9d2ffb69b4dbb21" +dependencies = [ + "crc-any", +] + +[[package]] +name = "rp2040-hal" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ff2b9ae7e6dd6720fd9f64250c9087260e50fe98b6b032ccca65be3581167ca" +dependencies = [ + "cortex-m", + "critical-section", + "embedded-dma", + "embedded-hal", + "frunk", + "fugit", + "itertools 0.10.5", + "nb 1.1.0", + "paste", + "pio", + "rand_core 0.6.4", + "rp2040-hal-macros", + "rp2040-pac", + "usb-device", + "vcell", + "void", +] + +[[package]] +name = "rp2040-hal-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86479063e497efe1ae81995ef9071f54fd1c7427e04d6c5b84cde545ff672a5e" +dependencies = [ + "cortex-m-rt", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rp2040-pac" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12d9d8375815f543f54835d01160d4e47f9e2cae75f17ff8f1ec19ce1da96e4c" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "critical-section", + "vcell", +] + [[package]] name = "rsa" version = "0.9.6" @@ -3484,13 +3820,22 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + [[package]] name = "rustc_version" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver", + "semver 1.0.20", ] [[package]] @@ -3626,12 +3971,27 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e388332cd64eb80cd595a00941baf513caffae8dce9cfd0467fc9c66397dade6" +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + [[package]] name = "semver" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + [[package]] name = "serde" version = "0.9.15" @@ -3751,6 +4111,20 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "simulator" +version = "0.1.0" +dependencies = [ + "cairo-rs", + "fixed", + "gio", + "glib", + "gtk4", + "libadwaita", + "lights-core", + "pango", +] + [[package]] name = "siphasher" version = "0.2.3" @@ -3835,7 +4209,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce81b7bd7c4493975347ef60d8c7e8b742d4694f4c49f93e0a12ea263938176c" dependencies = [ - "itertools", + "itertools 0.12.0", "nom", "unicode_categories", ] @@ -4036,6 +4410,12 @@ dependencies = [ "urlencoding", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "stringprep" version = "0.1.4" @@ -4626,6 +5006,12 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" +[[package]] +name = "usb-device" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f6cc3adc849b5292b4075fc0d5fdcf2f24866e88e336dd27a8943090a520508" + [[package]] name = "utf-8" version = "0.7.6" @@ -4673,6 +5059,12 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "126e423afe2dd9ac52142e7e9d5ce4135d7e13776c529d27fd6bc49f19e3280b" +[[package]] +name = "vcell" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" + [[package]] name = "vcpkg" version = "0.2.15" @@ -4709,6 +5101,21 @@ dependencies = [ "warp", ] +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "volatile-register" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc" +dependencies = [ + "vcell", +] + [[package]] name = "wait-timeout" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 538b62b..fe7eceb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,9 @@ resolver = "2" members = [ "authdb", + "bike-lights/bike", + "bike-lights/core", + "bike-lights/simulator", "changeset", "config", "config-derive", diff --git a/bike-lights/bike/.cargo/config b/bike-lights/bike/.cargo/config new file mode 100644 index 0000000..ef4d7f1 --- /dev/null +++ b/bike-lights/bike/.cargo/config @@ -0,0 +1,12 @@ +[build] +target = "thumbv6m-none-eabi" + +[target.thumbv6m-none-eabi] +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "inline-threshold=5", + "-C", "no-vectorize-loops", +] + +runner = "elf2uf2-rs -d" diff --git a/bike-lights/bike/Cargo.toml b/bike-lights/bike/Cargo.toml new file mode 100644 index 0000000..e62c730 --- /dev/null +++ b/bike-lights/bike/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "bike" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +az = { version = "1" } +cortex-m-rt = { version = "0.7.3" } +cortex-m = { version = "0.7.7" } +embedded-alloc = { version = "0.5.1" } +embedded-hal = { version = "0.2.7" } +fixed = { version = "1" } +fugit = { version = "0.3.7" } +lights-core = { path = "../core" } +panic-halt = { version = "0.2.0" } +rp-pico = { version = "0.8.0" } diff --git a/bike-lights/bike/src/main.rs b/bike-lights/bike/src/main.rs new file mode 100644 index 0000000..8bd6779 --- /dev/null +++ b/bike-lights/bike/src/main.rs @@ -0,0 +1,222 @@ +#![no_main] +#![no_std] + +extern crate alloc; + +use alloc::boxed::Box; +use az::*; +use core::cell::RefCell; +use cortex_m::delay::Delay; +use embedded_alloc::Heap; +use embedded_hal::{blocking::spi::Write, digital::v2::InputPin, digital::v2::OutputPin}; +use fixed::types::I16F16; +use fugit::RateExtU32; +use lights_core::{App, BodyPattern, DashboardPattern, Event, Instant, FPS, UI}; +use panic_halt as _; +use rp_pico::{ + entry, + hal::{ + clocks::init_clocks_and_plls, + gpio::{FunctionSio, Pin, PinId, PullUp, SioInput}, + pac::{CorePeripherals, Peripherals}, + spi::{Enabled, Spi, SpiDevice, ValidSpiPinout}, + watchdog::Watchdog, + Clock, Sio, + }, + Pins, +}; + +#[global_allocator] +static HEAP: Heap = Heap::empty(); + +const LIGHT_SCALE: I16F16 = I16F16::lit("256.0"); +const DASHBOARD_BRIGHTESS: u8 = 1; +const BODY_BRIGHTNESS: u8 = 8; + +struct DebouncedButton { + debounce: Instant, + pin: Pin, PullUp>, +} + +impl DebouncedButton

{ + fn new(pin: Pin, PullUp>) -> Self { + Self { + debounce: Instant((0 as u32).into()), + pin, + } + } + + fn is_low(&self, time: Instant) -> bool { + if time <= self.debounce { + return false; + } + self.pin.is_low().unwrap_or(false) + } + + fn set_debounce(&mut self, time: Instant) { + self.debounce = time + Instant((250 as u32).into()); + } +} + +struct BikeUI< + D: SpiDevice, + P: ValidSpiPinout, + LeftId: PinId, + RightId: PinId, + PreviousId: PinId, + NextId: PinId, +> { + spi: RefCell>, + left_blinker_button: DebouncedButton, + right_blinker_button: DebouncedButton, + previous_animation_button: DebouncedButton, + next_animation_button: DebouncedButton, +} + +impl< + D: SpiDevice, + P: ValidSpiPinout, + LeftId: PinId, + RightId: PinId, + PreviousId: PinId, + NextId: PinId, + > BikeUI +{ + fn new( + spi: Spi, + left_blinker_button: Pin, PullUp>, + right_blinker_button: Pin, PullUp>, + previous_animation_button: Pin, PullUp>, + next_animation_button: Pin, PullUp>, + ) -> Self { + Self { + spi: RefCell::new(spi), + left_blinker_button: DebouncedButton::new(left_blinker_button), + right_blinker_button: DebouncedButton::new(right_blinker_button), + previous_animation_button: DebouncedButton::new(previous_animation_button), + next_animation_button: DebouncedButton::new(next_animation_button), + } + } +} + +impl< + D: SpiDevice, + P: ValidSpiPinout, + LeftId: PinId, + RightId: PinId, + PreviousId: PinId, + NextId: PinId, + > UI for BikeUI +{ + fn check_event(&mut self, current_time: Instant) -> Option { + if self.left_blinker_button.is_low(current_time) { + self.left_blinker_button.set_debounce(current_time); + Some(Event::LeftBlinker) + } else if self.right_blinker_button.is_low(current_time) { + self.right_blinker_button.set_debounce(current_time); + Some(Event::RightBlinker) + } else if self.previous_animation_button.is_low(current_time) { + self.previous_animation_button.set_debounce(current_time); + Some(Event::PreviousPattern) + } else if self.next_animation_button.is_low(current_time) { + self.next_animation_button.set_debounce(current_time); + Some(Event::NextPattern) + } else { + None + } + } + + fn update_lights(&self, dashboard_lights: DashboardPattern, body_lights: BodyPattern) { + let mut lights: [u8; 260] = [0; 260]; + lights[256] = 0xff; + lights[257] = 0xff; + lights[258] = 0xff; + lights[259] = 0xff; + for (idx, rgb) in dashboard_lights.iter().enumerate() { + lights[(idx + 1) * 4 + 0] = 0xe0 + DASHBOARD_BRIGHTESS; + lights[(idx + 1) * 4 + 1] = (I16F16::from(rgb.r) * LIGHT_SCALE).saturating_as(); + lights[(idx + 1) * 4 + 2] = (I16F16::from(rgb.b) * LIGHT_SCALE).saturating_as(); + lights[(idx + 1) * 4 + 3] = (I16F16::from(rgb.g) * LIGHT_SCALE).saturating_as(); + } + for (idx, rgb) in body_lights.iter().enumerate() { + lights[(idx + 4) * 4 + 0] = 0xe0 + BODY_BRIGHTNESS; + lights[(idx + 4) * 4 + 1] = (I16F16::from(rgb.b) * LIGHT_SCALE).saturating_as(); + lights[(idx + 4) * 4 + 2] = (I16F16::from(rgb.g) * LIGHT_SCALE).saturating_as(); + lights[(idx + 4) * 4 + 3] = (I16F16::from(rgb.r) * LIGHT_SCALE).saturating_as(); + } + let mut spi = self.spi.borrow_mut(); + spi.write(lights.as_slice()); + } +} + +#[entry] +fn main() -> ! { + { + use core::mem::MaybeUninit; + const HEAP_SIZE: usize = 8096; + static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; + unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) } + } + + let mut pac = Peripherals::take().unwrap(); + let core = CorePeripherals::take().unwrap(); + let sio = Sio::new(pac.SIO); + let mut watchdog = Watchdog::new(pac.WATCHDOG); + + let pins = Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + let clocks = init_clocks_and_plls( + 12_000_000u32, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + let mut delay = Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()); + let mut spi_clk = pins.gpio10.into_function(); + let mut spi_sdo = pins.gpio11.into_function(); + let spi = Spi::<_, _, _, 8>::new(pac.SPI1, (spi_sdo, spi_clk)); + let mut spi = spi.init( + &mut pac.RESETS, + clocks.peripheral_clock.freq(), + 1_u32.MHz(), + embedded_hal::spi::MODE_1, + ); + + let left_blinker_button = pins.gpio17.into_pull_up_input(); + let right_blinker_button = pins.gpio16.into_pull_up_input(); + let previous_animation_button = pins.gpio27.into_pull_up_input(); + let next_animation_button = pins.gpio26.into_pull_up_input(); + + let ui = BikeUI::new( + spi, + left_blinker_button, + right_blinker_button, + previous_animation_button, + next_animation_button, + ); + + let mut app = App::new(Box::new(ui)); + + let mut led_pin = pins.led.into_push_pull_output(); + led_pin.set_high(); + + let mut time = Instant::default(); + let delay_ms = 1000 / (FPS as u32); + loop { + app.tick(time); + + delay.delay_ms(delay_ms); + time = time + Instant(delay_ms.into()); + } +} diff --git a/bike-lights/core/Cargo.toml b/bike-lights/core/Cargo.toml new file mode 100644 index 0000000..be3f09e --- /dev/null +++ b/bike-lights/core/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "lights-core" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +az = { version = "1" } +fixed = { version = "1" } diff --git a/bike-lights/core/src/lib.rs b/bike-lights/core/src/lib.rs new file mode 100644 index 0000000..3bc79be --- /dev/null +++ b/bike-lights/core/src/lib.rs @@ -0,0 +1,481 @@ +#![no_std] + +extern crate alloc; +use alloc::boxed::Box; +use az::*; +use core::{ + clone::Clone, + cmp::PartialEq, + default::Default, + ops::{Add, Sub}, + option::Option, +}; +use fixed::types::{I48F16, I8F8, U128F0, U16F0}; + +mod patterns; +pub use patterns::*; + +mod types; +pub use types::{BodyPattern, DashboardPattern, RGB}; + +fn calculate_frames(starting_time: U128F0, now: U128F0) -> U16F0 { + let frames_128 = (now - starting_time) / U128F0::from(FPS); + (frames_128 % U128F0::from(U16F0::MAX)).cast() +} + +fn calculate_slope(start: I8F8, end: I8F8, frames: U16F0) -> I8F8 { + let slope_i16f16 = (I48F16::from(end) - I48F16::from(start)) / I48F16::from(frames); + slope_i16f16.saturating_as() +} + +fn linear_ease(value: I8F8, frames: U16F0, slope: I8F8) -> I8F8 { + let value_i16f16 = I48F16::from(value) + I48F16::from(frames) * I48F16::from(slope); + value_i16f16.saturating_as() +} + +#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq)] +pub struct Instant(pub U128F0); + +impl Default for Instant { + fn default() -> Self { + Self(U128F0::from(0 as u8)) + } +} + +impl Add for Instant { + type Output = Self; + + fn add(self, r: Self) -> Self::Output { + Self(self.0 + r.0) + } +} + +impl Sub for Instant { + type Output = Self; + + fn sub(self, r: Self) -> Self::Output { + Self(self.0 - r.0) + } +} + +pub const FPS: u8 = 30; + +pub trait UI { + fn check_event(&mut self, current_time: Instant) -> Option; + fn update_lights(&self, dashboard_lights: DashboardPattern, body_lights: BodyPattern); +} + +pub trait Animation { + fn tick(&mut self, time: Instant) -> (DashboardPattern, BodyPattern); +} + +/* +pub struct DefaultAnimation {} + +impl Animation for DefaultAnimation { + fn tick(&mut self, _: Instant) -> (DashboardPattern, BodyPattern) { + (WATER_DASHBOARD, WATER_BODY) + } +} +*/ + +pub struct Fade { + starting_dashboard: DashboardPattern, + starting_lights: BodyPattern, + + start_time: Instant, + dashboard_slope: [RGB; 3], + body_slope: [RGB; 60], + frames: U16F0, +} + +impl Fade { + fn new( + dashboard: DashboardPattern, + lights: BodyPattern, + ending_dashboard: DashboardPattern, + ending_lights: BodyPattern, + frames: U16F0, + time: Instant, + ) -> Self { + let mut dashboard_slope = [Default::default(); 3]; + let mut body_slope = [Default::default(); 60]; + for i in 0..3 { + let slope = RGB { + r: calculate_slope(dashboard[i].r, ending_dashboard[i].r, frames), + g: calculate_slope(dashboard[i].g, ending_dashboard[i].g, frames), + b: calculate_slope(dashboard[i].b, ending_dashboard[i].b, frames), + }; + dashboard_slope[i] = slope; + } + + for i in 0..60 { + let slope = RGB { + r: calculate_slope(lights[i].r, ending_lights[i].r, frames), + g: calculate_slope(lights[i].g, ending_lights[i].g, frames), + b: calculate_slope(lights[i].b, ending_lights[i].b, frames), + }; + body_slope[i] = slope; + } + + Self { + starting_dashboard: dashboard, + starting_lights: lights, + start_time: time, + dashboard_slope, + body_slope, + frames, + } + } +} + +impl Animation for Fade { + fn tick(&mut self, time: Instant) -> (DashboardPattern, BodyPattern) { + let mut frames = calculate_frames(self.start_time.0, time.0); + if frames > self.frames { + frames = self.frames + } + let mut dashboard_pattern: DashboardPattern = OFF_DASHBOARD; + let mut body_pattern: BodyPattern = OFF_BODY; + + for i in 0..3 { + dashboard_pattern[i].r = linear_ease( + self.starting_dashboard[i].r, + frames, + self.dashboard_slope[i].r, + ); + dashboard_pattern[i].g = linear_ease( + self.starting_dashboard[i].g, + frames, + self.dashboard_slope[i].g, + ); + dashboard_pattern[i].b = linear_ease( + self.starting_dashboard[i].b, + frames, + self.dashboard_slope[i].b, + ); + } + + for i in 0..60 { + body_pattern[i].r = + linear_ease(self.starting_lights[i].r, frames, self.body_slope[i].r); + body_pattern[i].g = + linear_ease(self.starting_lights[i].g, frames, self.body_slope[i].g); + body_pattern[i].b = + linear_ease(self.starting_lights[i].b, frames, self.body_slope[i].b); + } + + (dashboard_pattern, body_pattern) + } +} + +#[derive(Debug)] +pub enum FadeDirection { + Transition, + FadeIn, + FadeOut, +} + +pub enum BlinkerDirection { + Left, + Right, +} + +pub struct Blinker { + transition: Fade, + fade_in: Fade, + fade_out: Fade, + direction: FadeDirection, + + start_time: Instant, + frames: U16F0, +} + +impl Blinker { + fn new( + starting_dashboard: DashboardPattern, + starting_body: BodyPattern, + direction: BlinkerDirection, + time: Instant, + ) -> Self { + let mut ending_dashboard = OFF_DASHBOARD.clone(); + + match direction { + BlinkerDirection::Left => { + ending_dashboard[0].r = LEFT_BLINKER_DASHBOARD[0].r; + ending_dashboard[0].g = LEFT_BLINKER_DASHBOARD[0].g; + ending_dashboard[0].b = LEFT_BLINKER_DASHBOARD[0].b; + } + BlinkerDirection::Right => { + ending_dashboard[2].r = RIGHT_BLINKER_DASHBOARD[2].r; + ending_dashboard[2].g = RIGHT_BLINKER_DASHBOARD[2].g; + ending_dashboard[2].b = RIGHT_BLINKER_DASHBOARD[2].b; + } + } + + let mut ending_body = OFF_BODY.clone(); + match direction { + BlinkerDirection::Left => { + for i in 0..30 { + ending_body[i].r = LEFT_BLINKER_BODY[i].r; + ending_body[i].g = LEFT_BLINKER_BODY[i].g; + ending_body[i].b = LEFT_BLINKER_BODY[i].b; + } + } + BlinkerDirection::Right => { + for i in 30..60 { + ending_body[i].r = RIGHT_BLINKER_BODY[i].r; + ending_body[i].g = RIGHT_BLINKER_BODY[i].g; + ending_body[i].b = RIGHT_BLINKER_BODY[i].b; + } + } + } + + Blinker { + transition: Fade::new( + starting_dashboard.clone(), + starting_body.clone(), + ending_dashboard.clone(), + ending_body.clone(), + BLINKER_FRAMES, + time, + ), + fade_in: Fade::new( + OFF_DASHBOARD.clone(), + OFF_BODY.clone(), + ending_dashboard.clone(), + ending_body.clone(), + BLINKER_FRAMES, + time, + ), + fade_out: Fade::new( + ending_dashboard.clone(), + ending_body.clone(), + OFF_DASHBOARD.clone(), + OFF_BODY.clone(), + BLINKER_FRAMES, + time, + ), + direction: FadeDirection::Transition, + start_time: time, + frames: BLINKER_FRAMES, + } + } +} + +impl Animation for Blinker { + fn tick(&mut self, time: Instant) -> (DashboardPattern, BodyPattern) { + let frames = calculate_frames(self.start_time.0, time.0); + if frames > self.frames { + match self.direction { + FadeDirection::Transition => { + self.direction = FadeDirection::FadeOut; + self.fade_out.start_time = time; + } + FadeDirection::FadeIn => { + self.direction = FadeDirection::FadeOut; + self.fade_out.start_time = time; + } + FadeDirection::FadeOut => { + self.direction = FadeDirection::FadeIn; + self.fade_in.start_time = time; + } + } + self.start_time = time; + } + + match self.direction { + FadeDirection::Transition => self.transition.tick(time), + FadeDirection::FadeIn => self.fade_in.tick(time), + FadeDirection::FadeOut => self.fade_out.tick(time), + } + } +} + +#[derive(Clone, Debug)] +pub enum Event { + Brake, + BrakeRelease, + LeftBlinker, + NextPattern, + PreviousPattern, + RightBlinker, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Pattern { + Water, + GayPride, + TransPride, +} + +impl Pattern { + fn previous(&self) -> Pattern { + match self { + Pattern::Water => Pattern::TransPride, + Pattern::GayPride => Pattern::Water, + Pattern::TransPride => Pattern::GayPride, + } + } + + fn next(&self) -> Pattern { + match self { + Pattern::Water => Pattern::GayPride, + Pattern::GayPride => Pattern::TransPride, + Pattern::TransPride => Pattern::Water, + } + } + + fn dashboard(&self) -> DashboardPattern { + match self { + Pattern::Water => WATER_DASHBOARD, + Pattern::GayPride => PRIDE_DASHBOARD, + Pattern::TransPride => TRANS_PRIDE_DASHBOARD, + } + } + + fn body(&self) -> BodyPattern { + match self { + Pattern::Water => OFF_BODY, + Pattern::GayPride => PRIDE_BODY, + Pattern::TransPride => TRANS_PRIDE_BODY, + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum State { + Pattern(Pattern), + Brake, + LeftBlinker, + RightBlinker, + BrakeLeftBlinker, + BrakeRightBlinker, +} + +pub struct App { + ui: Box, + state: State, + home_pattern: Pattern, + current_animation: Box, + dashboard_lights: DashboardPattern, + lights: BodyPattern, +} + +impl App { + pub fn new(ui: Box) -> Self { + let pattern = Pattern::Water; + Self { + ui, + state: State::Pattern(pattern), + home_pattern: pattern, + current_animation: Box::new(Fade::new( + OFF_DASHBOARD, + OFF_BODY, + pattern.dashboard(), + pattern.body(), + DEFAULT_FRAMES, + Instant((0 as u32).into()), + )), + dashboard_lights: OFF_DASHBOARD, + lights: OFF_BODY, + } + } + + fn update_animation(&mut self, time: Instant) { + match self.state { + State::Pattern(ref pattern) => { + self.current_animation = Box::new(Fade::new( + self.dashboard_lights.clone(), + self.lights.clone(), + pattern.dashboard(), + pattern.body(), + DEFAULT_FRAMES, + time, + )) + } + State::Brake => { + self.current_animation = Box::new(Fade::new( + self.dashboard_lights.clone(), + self.lights.clone(), + BRAKES_DASHBOARD, + BRAKES_BODY, + BRAKES_FRAMES, + time, + )); + } + State::LeftBlinker => { + self.current_animation = Box::new(Blinker::new( + self.dashboard_lights.clone(), + self.lights.clone(), + BlinkerDirection::Left, + time, + )); + } + State::RightBlinker => { + self.current_animation = Box::new(Blinker::new( + self.dashboard_lights.clone(), + self.lights.clone(), + BlinkerDirection::Right, + time, + )); + } + State::BrakeLeftBlinker => (), + State::BrakeRightBlinker => (), + } + } + + fn update_state(&mut self, event: Event) { + match event { + Event::Brake => { + if self.state == State::Brake { + self.state = State::Pattern(self.home_pattern); + } else { + self.state = State::Brake; + } + } + Event::BrakeRelease => self.state = State::Pattern(self.home_pattern), + Event::LeftBlinker => match self.state { + State::Brake => self.state = State::BrakeLeftBlinker, + State::BrakeLeftBlinker => self.state = State::Brake, + State::LeftBlinker => self.state = State::Pattern(self.home_pattern), + _ => self.state = State::LeftBlinker, + }, + Event::NextPattern => match self.state { + State::Pattern(ref pattern) => { + self.home_pattern = pattern.next(); + self.state = State::Pattern(self.home_pattern); + } + _ => (), + }, + Event::PreviousPattern => match self.state { + State::Pattern(ref pattern) => { + self.home_pattern = pattern.previous(); + self.state = State::Pattern(self.home_pattern); + } + _ => (), + }, + Event::RightBlinker => match self.state { + State::Brake => self.state = State::BrakeRightBlinker, + State::BrakeRightBlinker => self.state = State::Brake, + State::RightBlinker => self.state = State::Pattern(self.home_pattern), + _ => self.state = State::RightBlinker, + }, + } + } + + pub fn tick(&mut self, time: Instant) { + match self.ui.check_event(time) { + Some(event) => { + self.update_state(event); + self.update_animation(time); + } + None => {} + }; + + let (dashboard, lights) = self.current_animation.tick(time); + self.dashboard_lights = dashboard.clone(); + self.lights = lights.clone(); + self.ui.update_lights(dashboard, lights); + } +} diff --git a/bike-lights/core/src/patterns.rs b/bike-lights/core/src/patterns.rs new file mode 100644 index 0000000..12fb7ed --- /dev/null +++ b/bike-lights/core/src/patterns.rs @@ -0,0 +1,333 @@ +use crate::{BodyPattern, DashboardPattern, RGB}; +use fixed::types::{I8F8, U16F0}; + +pub const RGB_OFF: RGB = RGB { + r: I8F8::lit("0"), + g: I8F8::lit("0"), + b: I8F8::lit("0"), +}; + +pub const RGB_WHITE: RGB = RGB { + r: I8F8::lit("1"), + g: I8F8::lit("1"), + b: I8F8::lit("1"), +}; + +pub const BRAKES_RED: RGB = RGB { + r: I8F8::lit("1"), + g: I8F8::lit("0"), + b: I8F8::lit("0"), +}; + +pub const BLINKER_AMBER: RGB = RGB { + r: I8F8::lit("1"), + g: I8F8::lit("0.15"), + b: I8F8::lit("0"), +}; + +pub const PRIDE_RED: RGB = RGB { + r: I8F8::lit("0.95"), + g: I8F8::lit("0.00"), + b: I8F8::lit("0.00"), +}; + +pub const PRIDE_ORANGE: RGB = RGB { + r: I8F8::lit("1.0"), + g: I8F8::lit("0.25"), + b: I8F8::lit("0"), +}; + +pub const PRIDE_YELLOW: RGB = RGB { + r: I8F8::lit("1.0"), + g: I8F8::lit("0.85"), + b: I8F8::lit("0"), +}; + +pub const PRIDE_GREEN: RGB = RGB { + r: I8F8::lit("0"), + g: I8F8::lit("0.95"), + b: I8F8::lit("0.05"), +}; + +pub const PRIDE_INDIGO: RGB = RGB { + r: I8F8::lit("0.04"), + g: I8F8::lit("0.15"), + b: I8F8::lit("0.55"), +}; + +pub const PRIDE_VIOLET: RGB = RGB { + r: I8F8::lit("0.75"), + g: I8F8::lit("0.0"), + b: I8F8::lit("0.80"), +}; + +pub const TRANS_BLUE: RGB = RGB { + r: I8F8::lit("0.06"), + g: I8F8::lit("0.41"), + b: I8F8::lit("0.98"), +}; + +pub const TRANS_PINK: RGB = RGB { + r: I8F8::lit("0.96"), + g: I8F8::lit("0.16"), + b: I8F8::lit("0.32"), +}; + +pub const WATER_1: RGB = RGB { + r: I8F8::lit("0.0"), + g: I8F8::lit("0.0"), + b: I8F8::lit("0.75"), +}; + +pub const WATER_2: RGB = RGB { + r: I8F8::lit("0.8"), + g: I8F8::lit("0.8"), + b: I8F8::lit("0.8"), +}; + +pub const WATER_3: RGB = RGB { + r: I8F8::lit("0.00"), + g: I8F8::lit("0.75"), + b: I8F8::lit("0.75"), +}; + +pub const OFF_DASHBOARD: DashboardPattern = [RGB_OFF; 3]; +pub const OFF_BODY: BodyPattern = [RGB_OFF; 60]; + +pub const DEFAULT_FRAMES: U16F0 = U16F0::lit("30"); + +pub const WATER_DASHBOARD: DashboardPattern = [WATER_1, WATER_2, WATER_3]; + +pub const WATER_BODY: BodyPattern = [RGB_OFF; 60]; + +pub const PRIDE_DASHBOARD: DashboardPattern = [PRIDE_RED, PRIDE_GREEN, PRIDE_INDIGO]; + +pub const PRIDE_BODY: BodyPattern = [ + // Left Side + // Red + PRIDE_RED, + PRIDE_RED, + PRIDE_RED, + PRIDE_RED, + PRIDE_RED, + // Orange + PRIDE_ORANGE, + PRIDE_ORANGE, + PRIDE_ORANGE, + PRIDE_ORANGE, + PRIDE_ORANGE, + // Yellow + PRIDE_YELLOW, + PRIDE_YELLOW, + PRIDE_YELLOW, + PRIDE_YELLOW, + PRIDE_YELLOW, + // Green + PRIDE_GREEN, + PRIDE_GREEN, + PRIDE_GREEN, + PRIDE_GREEN, + PRIDE_GREEN, + // Indigo + PRIDE_INDIGO, + PRIDE_INDIGO, + PRIDE_INDIGO, + PRIDE_INDIGO, + PRIDE_INDIGO, + // Violet + PRIDE_VIOLET, + PRIDE_VIOLET, + PRIDE_VIOLET, + PRIDE_VIOLET, + PRIDE_VIOLET, + // Right Side + // Violet + PRIDE_VIOLET, + PRIDE_VIOLET, + PRIDE_VIOLET, + PRIDE_VIOLET, + PRIDE_VIOLET, + // Indigo + PRIDE_INDIGO, + PRIDE_INDIGO, + PRIDE_INDIGO, + PRIDE_INDIGO, + PRIDE_INDIGO, + // Green + PRIDE_GREEN, + PRIDE_GREEN, + PRIDE_GREEN, + PRIDE_GREEN, + PRIDE_GREEN, + // Yellow + PRIDE_YELLOW, + PRIDE_YELLOW, + PRIDE_YELLOW, + PRIDE_YELLOW, + PRIDE_YELLOW, + // Orange + PRIDE_ORANGE, + PRIDE_ORANGE, + PRIDE_ORANGE, + PRIDE_ORANGE, + PRIDE_ORANGE, + // Red + PRIDE_RED, + PRIDE_RED, + PRIDE_RED, + PRIDE_RED, + PRIDE_RED, +]; + +pub const TRANS_PRIDE_DASHBOARD: DashboardPattern = [TRANS_BLUE, RGB_WHITE, TRANS_PINK]; + +pub const TRANS_PRIDE_BODY: BodyPattern = [ + // Left Side + TRANS_BLUE, TRANS_BLUE, TRANS_BLUE, TRANS_BLUE, TRANS_BLUE, TRANS_BLUE, TRANS_PINK, TRANS_PINK, + TRANS_PINK, TRANS_PINK, TRANS_PINK, TRANS_PINK, RGB_WHITE, RGB_WHITE, RGB_WHITE, RGB_WHITE, + RGB_WHITE, RGB_WHITE, TRANS_PINK, TRANS_PINK, TRANS_PINK, TRANS_PINK, TRANS_PINK, TRANS_PINK, + TRANS_BLUE, TRANS_BLUE, TRANS_BLUE, TRANS_BLUE, TRANS_BLUE, TRANS_BLUE, + // Right side + TRANS_BLUE, TRANS_BLUE, TRANS_BLUE, TRANS_BLUE, TRANS_BLUE, TRANS_BLUE, TRANS_PINK, TRANS_PINK, + TRANS_PINK, TRANS_PINK, TRANS_PINK, TRANS_PINK, RGB_WHITE, RGB_WHITE, RGB_WHITE, RGB_WHITE, + RGB_WHITE, RGB_WHITE, TRANS_PINK, TRANS_PINK, TRANS_PINK, TRANS_PINK, TRANS_PINK, TRANS_PINK, + TRANS_BLUE, TRANS_BLUE, TRANS_BLUE, TRANS_BLUE, TRANS_BLUE, TRANS_BLUE, +]; + +pub const BRAKES_FRAMES: U16F0 = U16F0::lit("15"); + +pub const BRAKES_DASHBOARD: DashboardPattern = [BRAKES_RED; 3]; + +pub const BRAKES_BODY: BodyPattern = [BRAKES_RED; 60]; + +pub const BLINKER_FRAMES: U16F0 = U16F0::lit("10"); + +pub const LEFT_BLINKER_DASHBOARD: DashboardPattern = [BLINKER_AMBER, RGB_OFF, RGB_OFF]; + +pub const LEFT_BLINKER_BODY: BodyPattern = [ + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, +]; + +pub const RIGHT_BLINKER_DASHBOARD: DashboardPattern = [RGB_OFF, RGB_OFF, BLINKER_AMBER]; + +pub const RIGHT_BLINKER_BODY: BodyPattern = [ + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + RGB_OFF, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, + BLINKER_AMBER, +]; diff --git a/bike-lights/core/src/types.rs b/bike-lights/core/src/types.rs new file mode 100644 index 0000000..c7acf2a --- /dev/null +++ b/bike-lights/core/src/types.rs @@ -0,0 +1,17 @@ +use core::default::Default; +use fixed::types::I8F8; + +#[derive(Clone, Copy, Default, Debug)] +pub struct RGB { + pub r: T, + pub g: T, + pub b: T, +} + +const DASHBOARD_LIGHT_COUNT: usize = 3; + +pub type DashboardPattern = [RGB; DASHBOARD_LIGHT_COUNT]; + +const BODY_LIGHT_COUNT: usize = 60; + +pub type BodyPattern = [RGB; BODY_LIGHT_COUNT]; diff --git a/bike-lights/simulator/Cargo.toml b/bike-lights/simulator/Cargo.toml new file mode 100644 index 0000000..42787ae --- /dev/null +++ b/bike-lights/simulator/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "simulator" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +adw = { version = "0.5", package = "libadwaita", features = [ "v1_2" ] } +cairo-rs = { version = "0.18" } +fixed = { version = "1" } +gio = { version = "0.18" } +glib = { version = "0.18" } +gtk = { version = "0.7", package = "gtk4", features = [ "v4_8" ] } +lights-core = { path = "../core" } +pango = { version = "*" } diff --git a/bike-lights/simulator/src/main.rs b/bike-lights/simulator/src/main.rs new file mode 100644 index 0000000..e96d0ee --- /dev/null +++ b/bike-lights/simulator/src/main.rs @@ -0,0 +1,288 @@ +use adw::prelude::*; +use fixed::types::{I8F8, U128F0}; +use glib::{Object, Sender}; +use gtk::subclass::prelude::*; +use lights_core::{ + App, BodyPattern, DashboardPattern, Event, Instant, FPS, OFF_BODY, OFF_DASHBOARD, RGB, UI, +}; +use std::{ + cell::RefCell, + env, + rc::Rc, + sync::mpsc::{Receiver, TryRecvError}, +}; + +const WIDTH: i32 = 640; +const HEIGHT: i32 = 480; + +pub struct Update { + dashboard: DashboardPattern, + lights: BodyPattern, +} + +pub struct DashboardLightsPrivate { + lights: Rc>, +} + +#[glib::object_subclass] +impl ObjectSubclass for DashboardLightsPrivate { + const NAME: &'static str = "DashboardLights"; + type Type = DashboardLights; + type ParentType = gtk::DrawingArea; + + fn new() -> Self { + Self { + lights: Rc::new(RefCell::new(OFF_DASHBOARD)), + } + } +} + +impl ObjectImpl for DashboardLightsPrivate {} +impl WidgetImpl for DashboardLightsPrivate {} +impl DrawingAreaImpl for DashboardLightsPrivate {} + +glib::wrapper! { + pub struct DashboardLights(ObjectSubclass) @extends gtk::DrawingArea, gtk::Widget; +} + +impl DashboardLights { + pub fn new() -> Self { + let s: Self = Object::builder().build(); + + s.set_width_request(WIDTH); + s.set_height_request(100); + + s.set_draw_func({ + let s = s.clone(); + move |_, context, width, _| { + let start = width as f64 / 2. - 150.; + let lights = s.imp().lights.borrow(); + for i in 0..3 { + context.set_source_rgb( + lights[i].r.into(), + lights[i].g.into(), + lights[i].b.into(), + ); + context.rectangle(start + 100. * i as f64, 10., 80., 80.); + let _ = context.fill(); + } + } + }); + + s + } + + pub fn set_lights(&self, lights: DashboardPattern) { + *self.imp().lights.borrow_mut() = lights; + self.queue_draw(); + } +} + +pub struct BikeLightsPrivate { + lights: Rc>, +} + +#[glib::object_subclass] +impl ObjectSubclass for BikeLightsPrivate { + const NAME: &'static str = "BikeLights"; + type Type = BikeLights; + type ParentType = gtk::DrawingArea; + + fn new() -> Self { + Self { + lights: Rc::new(RefCell::new(OFF_BODY)), + } + } +} + +impl ObjectImpl for BikeLightsPrivate {} +impl WidgetImpl for BikeLightsPrivate {} +impl DrawingAreaImpl for BikeLightsPrivate {} + +glib::wrapper! { + pub struct BikeLights(ObjectSubclass) @extends gtk::DrawingArea, gtk::Widget; +} + +impl BikeLights { + pub fn new() -> Self { + let s: Self = Object::builder().build(); + + s.set_width_request(WIDTH); + s.set_height_request(640); + + let center = WIDTH as f64 / 2.; + + s.set_draw_func({ + let s = s.clone(); + move |_, context, _, _| { + let lights = s.imp().lights.borrow(); + for i in 0..30 { + context.set_source_rgb( + lights[i].r.into(), + lights[i].g.into(), + lights[i].b.into(), + ); + context.rectangle(center - 45., 5. + 20. * i as f64, 15., 15.); + let _ = context.fill(); + } + for i in 0..30 { + context.set_source_rgb( + lights[i + 30].r.into(), + lights[i + 30].g.into(), + lights[i + 30].b.into(), + ); + context.rectangle(center + 15., 5. + 20. * (30. - (i + 1) as f64), 15., 15.); + let _ = context.fill(); + } + } + }); + + s + } + + pub fn set_lights(&self, lights: [RGB; 60]) { + *self.imp().lights.borrow_mut() = lights; + self.queue_draw(); + } +} + +struct GTKUI { + tx: Sender, + rx: Receiver, +} + +impl UI for GTKUI { + fn check_event(&mut self, _: Instant) -> Option { + match self.rx.try_recv() { + Ok(event) => Some(event), + Err(TryRecvError::Empty) => None, + Err(TryRecvError::Disconnected) => None, + } + } + + fn update_lights(&self, dashboard_lights: DashboardPattern, lights: BodyPattern) { + self.tx + .send(Update { + dashboard: dashboard_lights, + lights, + }) + .unwrap(); + } +} + +fn main() { + let adw_app = adw::Application::builder() + .application_id("com.luminescent-dreams.bike-light-simulator") + .build(); + + adw_app.connect_activate(move |adw_app| { + let (update_tx, update_rx) = + gtk::glib::MainContext::channel::(gtk::glib::Priority::DEFAULT); + let (event_tx, event_rx) = std::sync::mpsc::channel(); + + std::thread::spawn(move || { + let mut bike_app = App::new(Box::new(GTKUI { + tx: update_tx, + rx: event_rx, + })); + loop { + bike_app.tick(Instant(U128F0::from( + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_millis(), + ))); + std::thread::sleep(std::time::Duration::from_millis(1000 / (FPS as u64))); + } + }); + + let window = adw::ApplicationWindow::builder() + .application(adw_app) + .default_width(WIDTH) + .default_height(HEIGHT) + .build(); + let layout = gtk::Box::builder() + .orientation(gtk::Orientation::Vertical) + .build(); + let controls = gtk::Box::builder() + .orientation(gtk::Orientation::Horizontal) + .build(); + + let dashboard_lights = DashboardLights::new(); + let bike_lights = BikeLights::new(); + + let left_button = gtk::Button::builder().label("L").build(); + let brake_button = gtk::Button::builder().label("Brakes").build(); + let right_button = gtk::Button::builder().label("R").build(); + + left_button.connect_clicked({ + let event_tx = event_tx.clone(); + move |_| { + let _ = event_tx.send(Event::LeftBlinker); + } + }); + + brake_button.connect_clicked({ + let event_tx = event_tx.clone(); + move |_| { + let _ = event_tx.send(Event::Brake); + } + }); + + right_button.connect_clicked({ + let event_tx = event_tx.clone(); + move |_| { + let _ = event_tx.send(Event::RightBlinker); + } + }); + + controls.append(&left_button); + controls.append(&brake_button); + controls.append(&right_button); + layout.append(&controls); + + let pattern_controls = gtk::Box::builder() + .orientation(gtk::Orientation::Horizontal) + .build(); + + let previous_pattern = gtk::Button::builder().label("Previous").build(); + let next_pattern = gtk::Button::builder().label("Next").build(); + + previous_pattern.connect_clicked({ + let event_tx = event_tx.clone(); + move |_| { + let _ = event_tx.send(Event::PreviousPattern); + } + }); + + next_pattern.connect_clicked({ + let event_tx = event_tx.clone(); + move |_| { + let _ = event_tx.send(Event::NextPattern); + } + }); + + pattern_controls.append(&previous_pattern); + pattern_controls.append(&next_pattern); + layout.append(&pattern_controls); + + layout.append(&dashboard_lights); + layout.append(&bike_lights); + + update_rx.attach(None, { + let dashboard_lights = dashboard_lights.clone(); + let bike_lights = bike_lights.clone(); + move |Update { dashboard, lights }| { + dashboard_lights.set_lights(dashboard); + bike_lights.set_lights(lights); + glib::ControlFlow::Continue + } + }); + + window.set_content(Some(&layout)); + window.present(); + }); + + let args: Vec = env::args().collect(); + ApplicationExtManual::run_with_args(&adw_app, &args); +} diff --git a/rust-toolchain b/rust-toolchain index 09aa8a4..10e3c69 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] channel = "1.77.0" -targets = [ "wasm32-unknown-unknown" ] +targets = [ "wasm32-unknown-unknown", "thumbv6m-none-eabi" ]