From b858786ecc33d501eaf744169986b6b0e0811638 Mon Sep 17 00:00:00 2001 From: rafael Date: Thu, 8 Aug 2024 23:21:15 +0200 Subject: [PATCH] finished light effects (#48) --- src/task/display.rs | 37 ++++++---- src/task/light_effects.rs | 150 ++++++++++++++++++++++++++++---------- 2 files changed, 134 insertions(+), 53 deletions(-) diff --git a/src/task/display.rs b/src/task/display.rs index 5341faf..b7132fd 100644 --- a/src/task/display.rs +++ b/src/task/display.rs @@ -178,19 +178,30 @@ pub async fn display_handler( drop(state_manager_guard); // get the system datetime - let dt = match rtc_ref.borrow().now() { - Ok(dt) => dt, - Err(e) => { - info!("RTC not running: {:?}", Debug2Format(&e)); - // return an empty DateTime - DateTime { - year: 0, - month: 0, - day: 0, - day_of_week: DayOfWeek::Monday, - hour: 0, - minute: 0, - second: 0, + let dt = { + let rtc = match rtc_ref.try_borrow() { + Ok(rtc) => rtc, + Err(_) => { + error!("RTC borrow failed"); + Timer::after(Duration::from_secs(1)).await; + continue; + } + }; + + match rtc.now() { + Ok(dt) => dt, + Err(e) => { + info!("RTC not running: {:?}", Debug2Format(&e)); + // Return an empty DateTime + DateTime { + year: 0, + month: 0, + day: 0, + day_of_week: DayOfWeek::Monday, + hour: 0, + minute: 0, + second: 0, + } } } }; diff --git a/src/task/light_effects.rs b/src/task/light_effects.rs index 3a71a17..2a5f1c1 100644 --- a/src/task/light_effects.rs +++ b/src/task/light_effects.rs @@ -10,6 +10,7 @@ use crate::task::task_messages::{ use defmt::*; use embassy_executor::Spawner; use embassy_rp::spi::{Config, Phase, Polarity, Spi}; +use embassy_time::Instant; use embassy_time::{Duration, Timer}; use smart_leds::{brightness, RGB8}; use ws2812_async::Ws2812; @@ -49,6 +50,20 @@ impl NeopixelManager { b: color.2, } } + + /// Function to convert a color wheel value to RGB + pub fn wheel(&self, mut wheel_pos: u8) -> RGB8 { + wheel_pos = 255 - wheel_pos; + if wheel_pos < 85 { + return (255 - wheel_pos * 3, 0, wheel_pos * 3).into(); + } + if wheel_pos < 170 { + wheel_pos -= 85; + return (0, wheel_pos * 3, 255 - wheel_pos * 3).into(); + } + wheel_pos -= 170; + (wheel_pos * 3, 255 - wheel_pos * 3, 0).into() + } } #[embassy_executor::task] @@ -72,9 +87,10 @@ pub async fn light_effects_handler(_spawner: Spawner, r: NeopixelResources) { let mut data = [RGB8::default(); NUM_LEDS]; np.write(brightness(data.iter().cloned(), 0)).await.ok(); - loop { + '_outer: loop { // wait for the signal to update the neopixel let command = LIGHTFX_SIGNAL.wait().await; + info!("LightFX signal received: {:?}", command); let (hour, minute, second) = match command { Commands::LightFXUpdate((hour, minute, second)) => (hour, minute, second), _ => (0, 0, 0), @@ -143,6 +159,7 @@ pub async fn light_effects_handler(_spawner: Spawner, r: NeopixelResources) { data[hour_index as usize] = neopixel_mgr.rgb_to_grb((255, 255, 255)); }; + // write the data to the neopixel let _ = np .write(brightness( data.iter().cloned(), @@ -160,65 +177,118 @@ pub async fn light_effects_handler(_spawner: Spawner, r: NeopixelResources) { match state_manager.alarm_state { AlarmState::Sunrise => { info!("Sunrise effect"); + // simumlate a sunrise: start with all leds off, then slowly add leds while all leds that are already used slowly change color from red to warm white - // ToDo: loop through the sunrise effect, just a simple blinking effect for now - let mut cntr = 0; - loop { - if cntr > 10 { - break; + // all off + let mut data = [RGB8::default(); NUM_LEDS]; + let _ = np.write(brightness(data.iter().cloned(), 0)).await; + + // initialize the variables + let start_color = RGB8::new(139, 0, 0); //dark red + let end_color = RGB8::new(255, 250, 244); // warm white + let end_brightness = 100.0; + let effect_duration: u64 = 60; // seconds + let start_time = Instant::now(); + + // loop for duration seconds + 'sunrise: while Instant::now() - start_time + < Duration::from_secs(effect_duration) + { + // check if the effect should be stopped + if LIGHTFX_STOP_SIGNAL.signaled() { + info!("Sunrise effect aborting"); + LIGHTFX_STOP_SIGNAL.reset(); + break 'sunrise; } - // Set all LEDs to blue - let color = neopixel_mgr.rgb_to_grb((0, 0, 255)); - let data = [color; NUM_LEDS]; - let _ = np - .write(brightness( - data.iter().cloned(), - neopixel_mgr.clock_brightness(), - )) - .await; - Timer::after(Duration::from_secs(1)).await; + // calculate the elapsed time and the remaining time + let elapsed_time = Instant::now() - start_time; + let remaining_time = + Duration::from_secs(effect_duration) - elapsed_time; + let fraction_elapsed = + elapsed_time.as_secs() as f32 / effect_duration as f32; + + // calculate the current brightness based on the elapsed time + let current_brightness = end_brightness as u8 + - (remaining_time.as_secs() as f32 / effect_duration as f32 + * end_brightness) as u8; + + // calculate the current color based on the elapsed time + let mut current_color = RGB8::new( + start_color.r + + ((end_color.r as i16 - start_color.r as i16) as f32 + / effect_duration as f32 + * elapsed_time.as_secs() as f32) + as u8, + start_color.g + + ((end_color.g as i16 - start_color.g as i16) as f32 + / effect_duration as f32 + * elapsed_time.as_secs() as f32) + as u8, + start_color.b + + ((end_color.b as i16 - start_color.b as i16) as f32 + / effect_duration as f32 + * elapsed_time.as_secs() as f32) + as u8, + ); + current_color = neopixel_mgr.rgb_to_grb(( + current_color.r, + current_color.g, + current_color.b, + )); - // all off - let data = [RGB8::default(); NUM_LEDS]; - np.write(brightness(data.iter().cloned(), 0)).await.ok(); - Timer::after(Duration::from_secs(1)).await; + // calculate the number of leds to light up based on the elapsed time fraction, min 1, max NUM_LEDS + let current_leds = (((fraction_elapsed * NUM_LEDS as f32) as usize) + + 1) + .clamp(1, NUM_LEDS); - cntr += 1; + // set the leds + for i in 0..current_leds { + data[i] = current_color; + } + + // write the date to the neopixel + let _ = np + .write(brightness(data.iter().cloned(), current_brightness)) + .await; } EVENT_CHANNEL .sender() .send(Events::SunriseEffectFinished) .await; + + // and wait a bit, so that the last of the effect is visible + Timer::after(Duration::from_millis(300)).await; } AlarmState::Noise => { info!("Noise effect"); - // ToDo: loop through the noise effect, until the alarm is stopped - loop { + + // a beautiful rainbow effect, taken from https://github.com/kalkyl/ws2812-async + let mut data = [RGB8::default(); NUM_LEDS]; + + 'noise: loop { if LIGHTFX_STOP_SIGNAL.signaled() { info!("Noise effect aborting"); LIGHTFX_STOP_SIGNAL.reset(); - break; - } + break 'noise; + }; - // Set all LEDs to red - let color = neopixel_mgr.rgb_to_grb((255, 0, 0)); - let data = [color; NUM_LEDS]; - let _ = np - .write(brightness( + for j in 0..(256 * 5) { + for i in 0..NUM_LEDS { + data[i] = neopixel_mgr.wheel( + (((i * 256) as u16 / NUM_LEDS as u16 + j as u16) & 255) + as u8, + ); + } + np.write(brightness( data.iter().cloned(), - neopixel_mgr.clock_brightness(), + neopixel_mgr.alarm_brightness, )) - .await; - Timer::after(Duration::from_secs(1)).await; - - // all off - let data = [RGB8::default(); NUM_LEDS]; - np.write(brightness(data.iter().cloned(), 0)).await.ok(); - Timer::after(Duration::from_secs(1)).await; - - Timer::after(Duration::from_secs(1)).await; + .await + .ok(); + Timer::after(Duration::from_millis(5)).await; + } } } AlarmState::None => {