ui: split out safety ui into its own dedicated set of layers

This commit is contained in:
2025-10-17 14:40:07 +02:00
parent 818f8af49a
commit a97a28bf9f
6 changed files with 235 additions and 114 deletions

136
src/tasks/safetyui.rs Normal file
View File

@@ -0,0 +1,136 @@
use embassy_sync::{channel::DynamicReceiver, pubsub::DynSubscriber};
use embassy_time::{Duration, Timer};
use figments::prelude::*;
use figments_render::output::Brightness;
use rgb::{Rgb, Rgba};
use log::*;
use alloc::sync::Arc;
use core::fmt::Debug;
use futures::join;
use crate::{animation::{AnimDisplay, AnimatedSurface, Animation}, display::{DisplayControls, SegmentSpace, Uniforms}, events::{Notification, Scene, SensorSource}, shaders::*, tasks::ui::UiSurfacePool};
pub struct SafetyUi<S: Surface> {
// Headlight and brakelight layers can only be overpainted by the bootsplash overlay layer
headlight: AnimatedSurface<S>,
brakelight: AnimatedSurface<S>,
// The overlay covers everything and is used to implement a power-on and power-off animation.
overlay: AnimatedSurface<S>,
display: DisplayControls
}
impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pixel = Rgba<u8>>> SafetyUi<S> {
pub fn new<SS: Surfaces<SegmentSpace, Surface = S>>(surfaces: &mut SS, display: DisplayControls) -> Self where SS::Error: Debug {
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
}
}
pub async fn sleep(&mut self) {
info!("Running sleep sequence");
let fade_out = Animation::default().duration(Duration::from_secs(1)).from(255).to(0);
let mut disp_anim = AnimDisplay(&mut self.display);
fade_out.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);
info!("Fading in brightness with 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);
join!(
fade_in.apply(&mut self.headlight),
fade_in.apply(&mut self.brakelight)
);
let fade_out = Animation::default().duration(Duration::from_secs(1)).from(255).to(0);
info!("Fade out overlay");
fade_out.apply(&mut self.overlay).await;
self.overlay.set_visible(false);
Timer::after_secs(3).await;
warn!("Turning off safety lights");
join!(
fade_out.apply(&mut self.headlight),
fade_out.apply(&mut self.brakelight)
);
info!("Wakeup complete!");
}
pub async fn on_event(&mut self, event: Notification) {
match event {
Notification::SceneChange(_) => (), // We already log this inside apply_scene()
evt => info!("SafetyUI event: {evt:?}")
}
match event {
// Toggling head and brake lights
// FIXME: These should be a Off/Low/High enum, so the stopping brake looks different from the dayrunning brake.
Notification::SetBrakelight(is_on) => self.brakelight.set_on(is_on).await,
Notification::SetHeadlight(is_on) => self.headlight.set_on(is_on).await,
Notification::Sleep => self.sleep().await,
Notification::WakeUp => self.wake().await,
_ => ()
}
}
}
#[embassy_executor::task]
pub async fn safety_ui_main(mut events: DynSubscriber<'static, Notification>, mut ui: SafetyUi<<UiSurfacePool as Surfaces<SegmentSpace>>::Surface>) {
// Wait for the renderer to start running
//ui.display.render_is_running.wait().await;
ui.display.wait_until_render_is_running().await;
// 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;
}
}