ui: split out safety ui into its own dedicated set of layers
This commit is contained in:
155
src/tasks/ui.rs
155
src/tasks/ui.rs
@@ -1,5 +1,5 @@
|
||||
use embassy_sync::channel::DynamicReceiver;
|
||||
use embassy_time::Duration;
|
||||
use embassy_sync::{channel::{DynamicReceiver, DynamicSender}, pubsub::{DynPublisher, DynSubscriber}};
|
||||
use embassy_time::{Duration, Timer};
|
||||
use figments::prelude::*;
|
||||
use rgb::{Rgb, Rgba};
|
||||
use log::*;
|
||||
@@ -7,7 +7,7 @@ use alloc::sync::Arc;
|
||||
use core::fmt::Debug;
|
||||
use futures::join;
|
||||
|
||||
use crate::{animation::{AnimDisplay, AnimatedSurface, Animation}, display::{SegmentSpace, Uniforms}, events::{DisplayControls, Notification, Scene, SensorSource}, shaders::*};
|
||||
use crate::{animation::{AnimDisplay, AnimatedSurface, Animation}, display::{SegmentSpace, Uniforms}, events::{Notification, Scene, SensorSource, Telemetry}, shaders::*};
|
||||
|
||||
pub struct Ui<S: Surface> {
|
||||
// Background layer provides an always-running background for everything to draw on
|
||||
@@ -21,18 +21,10 @@ pub struct Ui<S: Surface> {
|
||||
|
||||
// Notification layer sits on top of the content, and is used for transient event notifications (gps lost, wifi found, etc)
|
||||
notification: AnimatedSurface<S>,
|
||||
|
||||
// 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: Arc<DisplayControls>
|
||||
}
|
||||
|
||||
impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pixel = Rgba<u8>>> Ui<S> {
|
||||
pub fn new<SS: Surfaces<SegmentSpace, Surface = S>>(surfaces: &mut SS, display: Arc<DisplayControls>) -> Self where SS::Error: Debug {
|
||||
pub fn new<SS: Surfaces<SegmentSpace, Surface = S>>(surfaces: &mut SS) -> Self where SS::Error: Debug {
|
||||
Self {
|
||||
background: SurfaceBuilder::build(surfaces)
|
||||
.rect(Rectangle::everything())
|
||||
@@ -58,24 +50,7 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
|
||||
.rect(Rectangle::everything())
|
||||
.shader(Background::default())
|
||||
.visible(false)
|
||||
.finish().unwrap().into(),
|
||||
// FIXME: Headlight, brakelight, and the overlay should all be handled as part of the safety UI
|
||||
headlight: SurfaceBuilder::build(surfaces)
|
||||
.rect(Rectangle::new_from_coordinates(0, 0, 255, 0))
|
||||
.shader(Headlight::default())
|
||||
.visible(false)
|
||||
.finish().unwrap().into(),
|
||||
brakelight: SurfaceBuilder::build(surfaces)
|
||||
.rect(Brakelight::rectangle())
|
||||
.shader(Brakelight::default())
|
||||
.visible(false)
|
||||
.finish().unwrap().into(),
|
||||
overlay: SurfaceBuilder::build(surfaces)
|
||||
.rect(Rectangle::everything())
|
||||
.shader(Thinking::default())
|
||||
.visible(false)
|
||||
.finish().unwrap().into(),
|
||||
display
|
||||
.finish().unwrap().into()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,71 +77,18 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
|
||||
self.notification.set_visible(false);
|
||||
}
|
||||
|
||||
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(&self.display);
|
||||
fade_out.apply(&mut disp_anim).await;
|
||||
|
||||
// Reset layers to the initial state now that the display should be off
|
||||
pub fn as_slice(&mut self) -> [&mut S; 4] {
|
||||
[
|
||||
&mut *self.tail,
|
||||
&mut *self.panels,
|
||||
&mut *self.background,
|
||||
&mut *self.motion
|
||||
].set_opacity(0);
|
||||
[
|
||||
&mut *self.tail,
|
||||
&mut *self.panels,
|
||||
&mut *self.background,
|
||||
&mut *self.motion
|
||||
].set_visible(false);
|
||||
|
||||
info!("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;
|
||||
info!("Display is now sleeping.");
|
||||
]
|
||||
}
|
||||
|
||||
pub async fn wakeup(&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);
|
||||
let fade_out = Animation::default().duration(Duration::from_secs(1)).from(255).to(0);
|
||||
let mut disp_anim = AnimDisplay(&self.display);
|
||||
info!("Fade in overlay");
|
||||
self.overlay.set_visible(true);
|
||||
join!(
|
||||
fade_in.apply(&mut self.overlay),
|
||||
fade_in.apply(&mut disp_anim)
|
||||
);
|
||||
|
||||
pub async fn show(&mut self) {
|
||||
info!("Flipping on surfaces");
|
||||
// Flip on all the layers
|
||||
[
|
||||
&mut *self.panels,
|
||||
&mut *self.tail,
|
||||
&mut *self.background,
|
||||
&mut *self.motion
|
||||
].set_visible(true);
|
||||
|
||||
// Enter the startup scene
|
||||
self.apply_scene(Scene::Ready).await;
|
||||
|
||||
info!("Fade out overlay");
|
||||
fade_out.apply(&mut self.overlay).await;
|
||||
self.overlay.set_visible(false);
|
||||
info!("Wakeup complete!");
|
||||
self.as_slice().set_visible(true);
|
||||
}
|
||||
|
||||
// TODO: Brakelight should only be toggled when actually braking or stationary
|
||||
@@ -225,18 +147,13 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
|
||||
// Scene change
|
||||
Notification::SceneChange(scene) => self.apply_scene(scene).await,
|
||||
|
||||
// Toggling head and brake lights
|
||||
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.wakeup().await,
|
||||
|
||||
Notification::SensorsOffline => {
|
||||
self.flash_notification_color(Rgb::new(255, 0, 0)).await;
|
||||
self.flash_notification_color(Rgb::new(0, 255, 0)).await;
|
||||
self.flash_notification_color(Rgb::new(0, 0, 255)).await;
|
||||
}
|
||||
|
||||
Notification::WakeUp => self.show().await,
|
||||
_ => ()
|
||||
|
||||
// Other event ideas:
|
||||
@@ -252,24 +169,56 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pixel = Rgba<u8>>> Surface for Ui<S> {
|
||||
type Uniforms = S::Uniforms;
|
||||
|
||||
type CoordinateSpace = S::CoordinateSpace;
|
||||
|
||||
type Pixel = S::Pixel;
|
||||
|
||||
fn set_shader<T: Shader<Self::Uniforms, Self::CoordinateSpace, Self::Pixel> + 'static>(&mut self, shader: T) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn clear_shader(&mut self) {
|
||||
self.as_slice().clear_shader();
|
||||
}
|
||||
|
||||
fn set_rect(&mut self, rect: Rectangle<Self::CoordinateSpace>) {
|
||||
self.as_slice().set_rect(rect);
|
||||
}
|
||||
|
||||
fn set_opacity(&mut self, opacity: u8) {
|
||||
self.as_slice().set_opacity(opacity);
|
||||
}
|
||||
|
||||
fn set_visible(&mut self, visible: bool) {
|
||||
self.as_slice().set_visible(visible);
|
||||
}
|
||||
|
||||
fn set_offset(&mut self, offset: Coordinates<Self::CoordinateSpace>) {
|
||||
self.as_slice().set_offset(offset);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature="headless")]
|
||||
pub type UiSurfacePool = NullBufferPool<NullSurface<Uniforms, SegmentSpace, Rgba<u8>>>;
|
||||
#[cfg(not(feature="headless"))]
|
||||
pub type UiSurfacePool = BufferedSurfacePool<Uniforms, SegmentSpace, Rgba<u8>>;
|
||||
|
||||
#[embassy_executor::task]
|
||||
pub async fn ui_main(events: DynamicReceiver<'static, Notification>, mut ui: Ui<<UiSurfacePool as Surfaces<SegmentSpace>>::Surface>) {
|
||||
// Wait for the renderer to start running
|
||||
//ui.display.render_is_running.wait().await;
|
||||
pub async fn ui_main(mut events: DynSubscriber<'static, Notification>, telemetery: DynPublisher<'static, Telemetry>, mut ui: Ui<<UiSurfacePool as Surfaces<SegmentSpace>>::Surface>) {
|
||||
// FIXME: This should instead wait on some kind of flag set by the safety UI, or else we risk painting before we even have a display up and running
|
||||
Timer::after_secs(3).await;
|
||||
ui.show().await;
|
||||
|
||||
// Run the wake sequence, and turn on the lights
|
||||
ui.wakeup().await;
|
||||
// FIXME: Moving the safety lights into another task will let us turn them both on in parallel with the wakeup sequence
|
||||
ui.headlight.set_on(true).await;
|
||||
ui.brakelight.set_on(true).await;
|
||||
// Enter the startup scene
|
||||
ui.apply_scene(Scene::Ready).await;
|
||||
|
||||
// Enter the event loop
|
||||
loop {
|
||||
ui.on_event(events.receive().await).await;
|
||||
let evt = events.next_message_pure().await;
|
||||
ui.on_event(evt).await;
|
||||
telemetery.publish(Telemetry::Notification(evt)).await;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user