144 lines
5.6 KiB
Rust
144 lines
5.6 KiB
Rust
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<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 {
|
|
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<<UiSurfacePool as Surfaces<SegmentSpace>>::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;
|
|
}
|
|
} |