use embassy_sync::pubsub::DynSubscriber; use embassy_time::Duration; use figments::prelude::*; use figments_render::output::Brightness; use rgb::Rgba; use core::fmt::Debug; use log::*; use crate::{animation::{AnimDisplay, AnimatedSurface, Animation}, events::{Personality, Prediction}, graphics::{display::{DisplayControls, SegmentSpace, Uniforms}, shaders::*}, tasks::ui::UiSurfacePool}; #[derive(Debug)] pub struct SafetyUi { // Headlight and brakelight layers can only be overpainted by the bootsplash overlay layer headlight: AnimatedSurface, brakelight: AnimatedSurface, // The overlay covers everything and is used to implement a power-on and power-off animation. overlay: AnimatedSurface, display: DisplayControls } impl>> SafetyUi { pub fn new>(surfaces: &mut SS, display: DisplayControls) -> Self where SS::Error: Debug { let ret = Self { overlay: SurfaceBuilder::build(surfaces) .rect(Rectangle::everything()) .shader(Thinking::default()) .visible(false) .finish().unwrap().into(), headlight: SurfaceBuilder::build(surfaces) .rect(Rectangle::new_from_coordinates(0, 0, 255, 0)) .shader(Headlight::default()) .visible(false) .opacity(0) .finish().unwrap().into(), brakelight: SurfaceBuilder::build(surfaces) .rect(Brakelight::rectangle()) .shader(Brakelight::default()) .visible(false) .opacity(0) .finish().unwrap().into(), display }; trace!("shader={:?}", ret.overlay); ret } pub async fn sleep(&mut self) { info!("Running sleep sequence"); let mut disp_anim = AnimDisplay(&mut self.display); TURN_OFF.apply(&mut disp_anim).await; warn!("Resetting safety lights"); self.brakelight.set_visible(false); self.headlight.set_visible(false); warn!("Turning off display"); self.display.set_on(false); // Wait for the display hardware to actually turn off, before we return to process the next event, which could cause funky behaviors. // FIXME: also deadlocks :( //self.display.render_is_running.wait().await; } pub async fn wake(&mut self) { info!("Running startup fade sequence"); info!("Turning on display"); // Turn on the display hardware self.display.set_brightness(0); self.display.set_on(true); // Wait for the renderer to start running again // FIXME: This deadlocks :( //self.display.render_is_running.wait().await; let fade_in = Animation::default().duration(Duration::from_secs(3)).from(0).to(255); trace!("Fading in brightness with overlay={:?}", self.overlay); self.overlay.set_opacity(255); self.overlay.set_visible(true); fade_in.apply(&mut AnimDisplay(&mut self.display)).await; warn!("Turning on safety lights"); self.headlight.set_opacity(0); self.headlight.set_visible(true); self.brakelight.set_opacity(0); self.brakelight.set_visible(true); embassy_futures::join::join( fade_in.apply(&mut self.headlight), fade_in.apply(&mut self.brakelight) ).await; info!("Fade out overlay"); TURN_OFF.apply(&mut self.overlay).await; self.overlay.set_visible(false); info!("Wakeup complete!"); } pub async fn on_event(&mut self, event: Prediction) { if let Prediction::SetPersonality(personality) = event { match personality { Personality::Active => { // FIXME: These should be a Off/Low/High enum, so the stopping brake looks different from the dayrunning brake. warn!("Active personality: Turning on safety lights"); embassy_futures::join::join( TURN_ON.apply(&mut self.brakelight), TURN_ON.apply(&mut self.headlight) ).await; }, Personality::Parked => { warn!("Idle personality: Turning off safety lights"); embassy_futures::join::join( TURN_OFF.apply(&mut self.brakelight), TURN_OFF.apply(&mut self.headlight) ).await; }, Personality::Sleeping => { warn!("Sleeping personality: Safety UI is going to sleep"); self.sleep().await; }, Personality::Waking => { warn!("Waking personality: Waking up safety UI"); self.wake().await; }, } } } } const TURN_ON: Animation = Animation::new().duration(Duration::from_secs(1)).from(0).to(255); const TURN_OFF: Animation = Animation::new().duration(Duration::from_secs(1)).from(255).to(0); #[embassy_executor::task] pub async fn safety_ui_main(mut events: DynSubscriber<'static, Prediction>, mut ui: SafetyUi<>::Surface>) { // Wait for the renderer to start running //ui.display.render_is_running.wait().await; trace!("spooling until render starts ui={ui:?}"); ui.display.wait_until_render_is_running().await; trace!("spooling wait task ui={ui:?}"); // Run the wake sequence, and turn on the lights ui.wake().await; // Enter the event loop loop { ui.on_event(events.next_message_pure().await).await; } }