diff --git a/firmware/src/art.rs b/firmware/src/art.rs index 563a8fa..5a76797 100644 --- a/firmware/src/art.rs +++ b/firmware/src/art.rs @@ -37,6 +37,17 @@ impl HappyEyes { image_data: Bmp::from_slice(include_bytes!("assets/eye1.bmp")).unwrap(), } } + + pub fn draw_colored>( + &self, + target: &mut D, + color: Rgb888, + ) -> Result<(), D::Error> { + self.draw(&mut ColorMapFilter { + inner_target: target, + filter: |c| mix(c, color), + }) + } } impl Drawable for HappyEyes { @@ -122,12 +133,19 @@ impl<'a, D: Dimensions> Dimensions for RainbowFilter<'a, D> { } } -pub struct ColorMapFilter<'a, D> { +pub struct ColorMapFilter<'a, D, F> +where + F: FnMut(Rgb888) -> Rgb888, +{ pub inner_target: &'a mut D, - pub filter: fn(Rgb888) -> Rgb888, + pub filter: F, } -impl<'a, D: DrawTarget> DrawTarget for ColorMapFilter<'a, D> { +impl<'a, D, F> DrawTarget for ColorMapFilter<'a, D, F> +where + D: DrawTarget, + F: FnMut(Rgb888) -> Rgb888, +{ type Color = D::Color; type Error = D::Error; @@ -143,7 +161,10 @@ impl<'a, D: DrawTarget> DrawTarget for ColorMapFilter<'a, D> { } } -impl<'a, D: Dimensions> Dimensions for ColorMapFilter<'a, D> { +impl<'a, D: Dimensions, F> Dimensions for ColorMapFilter<'a, D, F> +where + F: FnMut(Rgb888) -> Rgb888, +{ fn bounding_box(&self) -> Rectangle { self.inner_target.bounding_box() } diff --git a/firmware/src/main.rs b/firmware/src/main.rs index 2604204..0abb61b 100644 --- a/firmware/src/main.rs +++ b/firmware/src/main.rs @@ -15,17 +15,17 @@ use embassy_rp::pio_programs::ws2812::{PioWs2812, PioWs2812Program}; use embassy_rp::pwm::{self, Pwm}; use embassy_rp::rom_data::reset_to_usb_boot; use embassy_rp::spi::{self, Blocking, Spi}; -use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_rp::spinlock_mutex::SpinlockRawMutex; use embassy_sync::signal::Signal; use embassy_time::{Delay, Duration, Instant, Ticker, Timer}; use embedded_graphics::pixelcolor::Rgb888; -use embedded_graphics::prelude::{DrawTarget, Drawable, RgbColor, Size}; +use embedded_graphics::prelude::{DrawTarget, Drawable, RgbColor, Size, WebColors}; use embedded_hal_bus::spi::ExclusiveDevice; use mipidsi::interface::SpiInterface; use mipidsi::models::ST7789; use mipidsi::options::Orientation; -use crate::art::{Hal, HappyEyes, Rainbow, RainbowFilter}; +use crate::art::{ColorMapFilter, Hal, HappyEyes, Rainbow, RainbowFilter}; use crate::led_matrix::{LedMatrix, Serpentine}; use defmt_rtt as _; @@ -164,13 +164,62 @@ impl ButtonStates { } } -enum LedConfig { +#[derive(Clone, Copy, Default)] +struct LedConfig { + pattern: LedPattern, + hal: HalConfig, + happy: HappyConfig, +} + +#[derive(Clone, Copy, PartialEq, Default)] +enum LedPattern { + #[default] Blank, Hal, Happy, } -static LED_CONFIG_SIGNAL: Signal = Signal::new(); +#[derive(Clone, Copy, PartialEq, Default)] +enum HalConfig { + #[default] + Red, + Blue, +} + +impl HalConfig { + fn next(self) -> Self { + match self { + HalConfig::Red => HalConfig::Blue, + HalConfig::Blue => HalConfig::Red, + } + } +} + +#[derive(Clone, Copy, PartialEq, Default)] +enum HappyConfig { + Green, + Blue, + #[default] + Purple, + Pink, + RainbowSlow, + RainbowFast, +} + +impl HappyConfig { + fn next(self) -> Self { + match self { + HappyConfig::Green => HappyConfig::Blue, + HappyConfig::Blue => HappyConfig::Purple, + HappyConfig::Purple => HappyConfig::Pink, + HappyConfig::Pink => HappyConfig::RainbowSlow, + HappyConfig::RainbowSlow => HappyConfig::RainbowFast, + HappyConfig::RainbowFast => HappyConfig::Green, + } + } +} + +static LED_CONFIG_SIGNAL: Signal, LedConfig> = Signal::new(); #[embassy_executor::task] async fn controller_task(buttons: Buttons) { @@ -179,6 +228,8 @@ async fn controller_task(buttons: Buttons) { let mut stable_chord = 0; let mut pending_chord = 0; + let mut led_config = LedConfig::default(); + loop { let state = buttons.read(); let new_chord = state.chord(); @@ -204,18 +255,29 @@ async fn controller_task(buttons: Buttons) { match stable_chord { 0x1 => { // blank display - LED_CONFIG_SIGNAL.signal(LedConfig::Blank); + led_config.pattern = LedPattern::Blank; + LED_CONFIG_SIGNAL.signal(led_config); } 0x2 => { // happy - LED_CONFIG_SIGNAL.signal(LedConfig::Happy); + if led_config.pattern == LedPattern::Happy { + led_config.happy = led_config.happy.next(); + } else { + led_config.pattern = LedPattern::Happy; + } + LED_CONFIG_SIGNAL.signal(led_config); } 0x4 => { // hal - LED_CONFIG_SIGNAL.signal(LedConfig::Hal); + if led_config.pattern == LedPattern::Hal { + led_config.hal = led_config.hal.next(); + } else { + led_config.pattern = LedPattern::Hal; + } + LED_CONFIG_SIGNAL.signal(led_config); } _ => { - // unknown pattern + // unknown chord } } @@ -230,9 +292,12 @@ async fn controller_task(buttons: Buttons) { async fn led_task(mut led_surface: LedMatrix<'static>) { let hal = Hal::new(); let eyes = HappyEyes::new(); - let mut rainbow = Rainbow { - // rate: Duration::from_secs(10), - // width: 300, + let mut rainbow_slow = Rainbow { + rate: Duration::from_secs(10), + width: 300, + elapsed: Duration::from_secs(0), + }; + let mut rainbow_fast = Rainbow { rate: Duration::from_secs(1), width: 80, elapsed: Duration::from_secs(0), @@ -241,29 +306,62 @@ async fn led_task(mut led_surface: LedMatrix<'static>) { let mut ticker = Ticker::every(Duration::from_hz(30)); let start = Instant::now(); - let mut led_config = LedConfig::Blank; + let mut led_config = LedConfig::default(); loop { let elapsed = start.elapsed(); - rainbow.elapsed = elapsed; + rainbow_slow.elapsed = elapsed; + rainbow_fast.elapsed = elapsed; if let Some(new_config) = LED_CONFIG_SIGNAL.try_take() { led_config = new_config; } led_surface.clear(Rgb888::BLACK).ok(); - match led_config { - LedConfig::Blank => {} - LedConfig::Hal => { - hal.draw(&mut led_surface).ok(); - } - LedConfig::Happy => { - eyes.draw(&mut RainbowFilter { - inner_target: &mut led_surface, - rainbow: &rainbow, - }) - .ok(); - } + match led_config.pattern { + LedPattern::Blank => {} + LedPattern::Hal => match led_config.hal { + HalConfig::Red => { + hal.draw(&mut led_surface).ok(); + } + HalConfig::Blue => { + hal.draw(&mut ColorMapFilter { + inner_target: &mut led_surface, + filter: |color| Rgb888::new(color.b(), color.g(), color.r() / 2), + }) + .ok(); + } + }, + LedPattern::Happy => match led_config.happy { + HappyConfig::Green => { + eyes.draw_colored(&mut led_surface, Rgb888::GREEN).ok(); + } + HappyConfig::Blue => { + eyes.draw_colored(&mut led_surface, Rgb888::BLUE).ok(); + } + HappyConfig::Purple => { + eyes.draw_colored(&mut led_surface, Rgb888::new(172, 20, 172)) + .ok(); + } + HappyConfig::Pink => { + eyes.draw_colored(&mut led_surface, Rgb888::CSS_HOT_PINK) + .ok(); + } + HappyConfig::RainbowSlow => { + eyes.draw(&mut RainbowFilter { + inner_target: &mut led_surface, + rainbow: &rainbow_slow, + }) + .ok(); + } + HappyConfig::RainbowFast => { + eyes.draw(&mut RainbowFilter { + inner_target: &mut led_surface, + rainbow: &rainbow_fast, + }) + .ok(); + } + }, } led_surface.sync().await; ticker.next().await;