ui: split out safety ui into its own dedicated set of layers
This commit is contained in:
@@ -25,6 +25,7 @@ wokwi = ["max-usb-power"]
|
|||||||
headless = []
|
headless = []
|
||||||
mpu = ["dep:mpu6050-dmp"]
|
mpu = ["dep:mpu6050-dmp"]
|
||||||
gps = ["dep:nmea"]
|
gps = ["dep:nmea"]
|
||||||
|
oled = []
|
||||||
demo = []
|
demo = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
@@ -137,3 +138,4 @@ overflow-checks = false
|
|||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
image = "0.25.8"
|
image = "0.25.8"
|
||||||
|
rmp = "0.8"
|
||||||
8
build.rs
8
build.rs
@@ -3,6 +3,7 @@ use std::io::Write;
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use image::GenericImageView;
|
use image::GenericImageView;
|
||||||
|
use rmp;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
linker_be_nice();
|
linker_be_nice();
|
||||||
@@ -54,6 +55,13 @@ fn main() {
|
|||||||
println!("cargo::rerun-if-changed={fname_str}");
|
println!("cargo::rerun-if-changed={fname_str}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*let test_data_path = Path::new("test-data");
|
||||||
|
let gps_data = File::open(test_data_path.join("LocationGps.csv")).unwrap();
|
||||||
|
let accel_data = File::open(test_data_path.join("AccelerometerUncalibrated.csv")).unwrap();
|
||||||
|
let gyro_data = File::open(test_data_path.join("GyroscopeUncalibrated.csv")).unwrap();
|
||||||
|
let mut test_data_output = File::create(Path::new("target/test_data.rs")).unwrap();*/
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn linker_be_nice() {
|
fn linker_be_nice() {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use core::ptr::addr_of_mut;
|
|||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_time::{Instant, Timer};
|
use embassy_time::{Instant, Timer};
|
||||||
|
|
||||||
|
use esp_hal::{gpio::{Output, OutputConfig}, peripherals, time::Rate};
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use esp_hal::{
|
use esp_hal::{
|
||||||
clock::CpuClock, interrupt::software::SoftwareInterruptControl, system::{AppCoreGuard, CpuControl, Stack}, timer::{systimer::SystemTimer, timg::{TimerGroup, Wdt}},
|
clock::CpuClock, interrupt::software::SoftwareInterruptControl, system::{AppCoreGuard, CpuControl, Stack}, timer::{systimer::SystemTimer, timg::{TimerGroup, Wdt}},
|
||||||
@@ -19,7 +20,7 @@ use esp_hal::{
|
|||||||
|
|
||||||
use esp_hal_embassy::{Executor, InterruptExecutor};
|
use esp_hal_embassy::{Executor, InterruptExecutor};
|
||||||
use log::*;
|
use log::*;
|
||||||
use renderbug_embassy::{logging::RenderbugLogger, tasks::ui::UiSurfacePool};
|
use renderbug_embassy::{logging::RenderbugLogger, tasks::{safetyui::{safety_ui_main, SafetyUi}, ui::UiSurfacePool}};
|
||||||
use renderbug_embassy::events::BusGarage;
|
use renderbug_embassy::events::BusGarage;
|
||||||
use static_cell::StaticCell;
|
use static_cell::StaticCell;
|
||||||
use esp_backtrace as _;
|
use esp_backtrace as _;
|
||||||
@@ -45,7 +46,6 @@ static CORE_HANDLE: StaticCell<AppCoreGuard> = StaticCell::new();
|
|||||||
async fn main(spawner: Spawner) {
|
async fn main(spawner: Spawner) {
|
||||||
critical_section::with(|_| {
|
critical_section::with(|_| {
|
||||||
RenderbugLogger::init_logger();
|
RenderbugLogger::init_logger();
|
||||||
//esp_println::logger::init_logger_from_env();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
|
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
|
||||||
@@ -62,13 +62,15 @@ async fn main(spawner: Spawner) {
|
|||||||
let timer1 = TimerGroup::new(peripherals.TIMG1);
|
let timer1 = TimerGroup::new(peripherals.TIMG1);
|
||||||
let mut ui_wdt = timer1.wdt;
|
let mut ui_wdt = timer1.wdt;
|
||||||
ui_wdt.set_timeout(esp_hal::timer::timg::MwdtStage::Stage0, esp_hal::time::Duration::from_secs(10));
|
ui_wdt.set_timeout(esp_hal::timer::timg::MwdtStage::Stage0, esp_hal::time::Duration::from_secs(10));
|
||||||
//ui_wdt.enable(); //FIXME: Re-enable UI watchdog once we have a brain task running
|
ui_wdt.enable(); //FIXME: Re-enable UI watchdog once we have a brain task running
|
||||||
|
|
||||||
let garage = BUS_GARAGE.init(Default::default());
|
let garage = BUS_GARAGE.init(Default::default());
|
||||||
|
|
||||||
info!("Setting up rendering pipeline");
|
info!("Setting up rendering pipeline");
|
||||||
let mut surfaces = UiSurfacePool::default();
|
let mut surfaces = UiSurfacePool::default();
|
||||||
let ui = Ui::new(&mut surfaces, garage.display.clone());
|
let ui = Ui::new(&mut surfaces);
|
||||||
|
let mut safety_surfaces = UiSurfacePool::default();
|
||||||
|
let safety_ui = SafetyUi::new(&mut safety_surfaces, garage.display.clone());
|
||||||
|
|
||||||
let mut wdt = timer0.wdt;
|
let mut wdt = timer0.wdt;
|
||||||
wdt.set_timeout(esp_hal::timer::timg::MwdtStage::Stage0, esp_hal::time::Duration::from_secs(3));
|
wdt.set_timeout(esp_hal::timer::timg::MwdtStage::Stage0, esp_hal::time::Duration::from_secs(3));
|
||||||
@@ -80,7 +82,7 @@ async fn main(spawner: Spawner) {
|
|||||||
#[cfg(not(feature="headless"))]
|
#[cfg(not(feature="headless"))]
|
||||||
{
|
{
|
||||||
wdt.enable();
|
wdt.enable();
|
||||||
hi_spawn.must_spawn(renderbug_embassy::tasks::render::render(peripherals.RMT, peripherals.GPIO5.degrade(), surfaces, garage.display.clone(), wdt));
|
hi_spawn.must_spawn(renderbug_embassy::tasks::render::render(peripherals.RMT, peripherals.GPIO5.degrade(), surfaces, safety_surfaces, garage.display.clone(), wdt));
|
||||||
}
|
}
|
||||||
#[cfg(feature="headless")]
|
#[cfg(feature="headless")]
|
||||||
garage.display.notify_render_is_running(true);
|
garage.display.notify_render_is_running(true);
|
||||||
@@ -104,6 +106,26 @@ async fn main(spawner: Spawner) {
|
|||||||
spawner.must_spawn(renderbug_embassy::tasks::gps::gps_task(garage.motion.dyn_sender(), I2cDevice::new(i2c_bus)));
|
spawner.must_spawn(renderbug_embassy::tasks::gps::gps_task(garage.motion.dyn_sender(), I2cDevice::new(i2c_bus)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature="oled")]
|
||||||
|
{
|
||||||
|
use esp_hal::i2c::master::{Config, I2c};
|
||||||
|
|
||||||
|
let mut rst = Output::new(peripherals.GPIO21, esp_hal::gpio::Level::Low, OutputConfig::default());
|
||||||
|
Timer::after_millis(10).await;
|
||||||
|
rst.set_high();
|
||||||
|
Timer::after_millis(10).await;
|
||||||
|
rst.set_low();
|
||||||
|
Timer::after_millis(10).await;
|
||||||
|
rst.set_high();
|
||||||
|
|
||||||
|
let i2c = I2c::new(
|
||||||
|
peripherals.I2C0,
|
||||||
|
Config::default().with_frequency(Rate::from_khz(400))
|
||||||
|
).unwrap().with_scl(peripherals.GPIO18).with_sda(peripherals.GPIO17).into_async();
|
||||||
|
|
||||||
|
spawner.must_spawn(renderbug_embassy::tasks::oled::oled_task(i2c, garage.telemetry.dyn_subscriber().unwrap()));
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature="simulation")]
|
#[cfg(feature="simulation")]
|
||||||
{
|
{
|
||||||
spawner.must_spawn(renderbug_embassy::tasks::simulation::motion_simulation_task(garage.motion.dyn_sender()));
|
spawner.must_spawn(renderbug_embassy::tasks::simulation::motion_simulation_task(garage.motion.dyn_sender()));
|
||||||
@@ -111,7 +133,7 @@ async fn main(spawner: Spawner) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
info!("Launching motion engine");
|
info!("Launching motion engine");
|
||||||
spawner.must_spawn(motion_task(garage.motion.dyn_receiver(), garage.notify.dyn_sender(), garage.predict.dyn_sender()));
|
spawner.must_spawn(motion_task(garage.motion.dyn_receiver(), garage.notify.dyn_publisher().unwrap(), garage.predict.dyn_sender()));
|
||||||
|
|
||||||
info!("Starting core 2");
|
info!("Starting core 2");
|
||||||
let mut cpu_control = CpuControl::new(peripherals.CPU_CTRL);
|
let mut cpu_control = CpuControl::new(peripherals.CPU_CTRL);
|
||||||
@@ -122,19 +144,21 @@ async fn main(spawner: Spawner) {
|
|||||||
#[cfg(feature="radio")]
|
#[cfg(feature="radio")]
|
||||||
{
|
{
|
||||||
info!("Launching wifi");
|
info!("Launching wifi");
|
||||||
spawner.must_spawn(renderbug_embassy::tasks::wifi::wireless_task(garage.notify.dyn_receiver(), timer0.timer0.into(), peripherals.RNG, peripherals.WIFI, peripherals.BT));
|
//spawner.must_spawn(renderbug_embassy::tasks::wifi::wireless_task(garage.notify.dyn_receiver().unwrap(), timer0.timer0.into(), peripherals.RNG, peripherals.WIFI, peripherals.BT));
|
||||||
}
|
}
|
||||||
|
info!("Launching Safety UI");
|
||||||
|
spawner.must_spawn(safety_ui_main(garage.notify.dyn_subscriber().unwrap(), safety_ui));
|
||||||
info!("Launching UI");
|
info!("Launching UI");
|
||||||
spawner.must_spawn(ui_main(garage.notify.dyn_receiver(), ui));
|
spawner.must_spawn(ui_main(garage.notify.dyn_subscriber().unwrap(), garage.telemetry.dyn_publisher().unwrap(), ui));
|
||||||
#[cfg(feature="demo")]
|
#[cfg(feature="demo")]
|
||||||
{
|
{
|
||||||
warn!("Launching with demo sequencer");
|
warn!("Launching with demo sequencer");
|
||||||
spawner.must_spawn(renderbug_embassy::tasks::demo::demo_task(garage.notify.dyn_sender()));
|
spawner.must_spawn(renderbug_embassy::tasks::demo::demo_task(garage.notify.dyn_publisher().unwrap()));
|
||||||
}
|
}
|
||||||
#[cfg(not(feature="demo"))]
|
#[cfg(not(feature="demo"))]
|
||||||
{
|
{
|
||||||
info!("Launching prediction engine");
|
info!("Launching prediction engine");
|
||||||
spawner.must_spawn(renderbug_embassy::tasks::predict::prediction_task(garage.predict.dyn_receiver(), garage.notify.dyn_sender()));
|
spawner.must_spawn(renderbug_embassy::tasks::predict::prediction_task(garage.predict.dyn_receiver(), garage.notify.dyn_publisher().unwrap(), garage.telemetry.dyn_publisher().unwrap()));
|
||||||
}
|
}
|
||||||
info!("Launching core 2 watchdog");
|
info!("Launching core 2 watchdog");
|
||||||
spawner.must_spawn(wdt_task(ui_wdt));
|
spawner.must_spawn(wdt_task(ui_wdt));
|
||||||
|
|||||||
@@ -10,4 +10,6 @@ pub mod wifi;
|
|||||||
#[cfg(feature="simulation")]
|
#[cfg(feature="simulation")]
|
||||||
pub mod simulation;
|
pub mod simulation;
|
||||||
pub mod predict;
|
pub mod predict;
|
||||||
pub mod demo;
|
pub mod demo;
|
||||||
|
pub mod safetyui;
|
||||||
|
pub mod oled;
|
||||||
136
src/tasks/safetyui.rs
Normal file
136
src/tasks/safetyui.rs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
155
src/tasks/ui.rs
155
src/tasks/ui.rs
@@ -1,5 +1,5 @@
|
|||||||
use embassy_sync::channel::DynamicReceiver;
|
use embassy_sync::{channel::{DynamicReceiver, DynamicSender}, pubsub::{DynPublisher, DynSubscriber}};
|
||||||
use embassy_time::Duration;
|
use embassy_time::{Duration, Timer};
|
||||||
use figments::prelude::*;
|
use figments::prelude::*;
|
||||||
use rgb::{Rgb, Rgba};
|
use rgb::{Rgb, Rgba};
|
||||||
use log::*;
|
use log::*;
|
||||||
@@ -7,7 +7,7 @@ use alloc::sync::Arc;
|
|||||||
use core::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
use futures::join;
|
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> {
|
pub struct Ui<S: Surface> {
|
||||||
// Background layer provides an always-running background for everything to draw on
|
// 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 layer sits on top of the content, and is used for transient event notifications (gps lost, wifi found, etc)
|
||||||
notification: AnimatedSurface<S>,
|
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> {
|
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 {
|
Self {
|
||||||
background: SurfaceBuilder::build(surfaces)
|
background: SurfaceBuilder::build(surfaces)
|
||||||
.rect(Rectangle::everything())
|
.rect(Rectangle::everything())
|
||||||
@@ -58,24 +50,7 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
|
|||||||
.rect(Rectangle::everything())
|
.rect(Rectangle::everything())
|
||||||
.shader(Background::default())
|
.shader(Background::default())
|
||||||
.visible(false)
|
.visible(false)
|
||||||
.finish().unwrap().into(),
|
.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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,71 +77,18 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
|
|||||||
self.notification.set_visible(false);
|
self.notification.set_visible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn sleep(&mut self) {
|
pub fn as_slice(&mut self) -> [&mut S; 4] {
|
||||||
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
|
|
||||||
[
|
[
|
||||||
&mut *self.tail,
|
&mut *self.tail,
|
||||||
&mut *self.panels,
|
&mut *self.panels,
|
||||||
&mut *self.background,
|
&mut *self.background,
|
||||||
&mut *self.motion
|
&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) {
|
pub async fn show(&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)
|
|
||||||
);
|
|
||||||
|
|
||||||
info!("Flipping on surfaces");
|
info!("Flipping on surfaces");
|
||||||
// Flip on all the layers
|
self.as_slice().set_visible(true);
|
||||||
[
|
|
||||||
&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!");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Brakelight should only be toggled when actually braking or stationary
|
// 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
|
// Scene change
|
||||||
Notification::SceneChange(scene) => self.apply_scene(scene).await,
|
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 => {
|
Notification::SensorsOffline => {
|
||||||
self.flash_notification_color(Rgb::new(255, 0, 0)).await;
|
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, 255, 0)).await;
|
||||||
self.flash_notification_color(Rgb::new(0, 0, 255)).await;
|
self.flash_notification_color(Rgb::new(0, 0, 255)).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Notification::WakeUp => self.show().await,
|
||||||
_ => ()
|
_ => ()
|
||||||
|
|
||||||
// Other event ideas:
|
// 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")]
|
#[cfg(feature="headless")]
|
||||||
pub type UiSurfacePool = NullBufferPool<NullSurface<Uniforms, SegmentSpace, Rgba<u8>>>;
|
pub type UiSurfacePool = NullBufferPool<NullSurface<Uniforms, SegmentSpace, Rgba<u8>>>;
|
||||||
#[cfg(not(feature="headless"))]
|
#[cfg(not(feature="headless"))]
|
||||||
pub type UiSurfacePool = BufferedSurfacePool<Uniforms, SegmentSpace, Rgba<u8>>;
|
pub type UiSurfacePool = BufferedSurfacePool<Uniforms, SegmentSpace, Rgba<u8>>;
|
||||||
|
|
||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
pub async fn ui_main(events: DynamicReceiver<'static, Notification>, mut ui: Ui<<UiSurfacePool as Surfaces<SegmentSpace>>::Surface>) {
|
pub async fn ui_main(mut events: DynSubscriber<'static, Notification>, telemetery: DynPublisher<'static, Telemetry>, mut ui: Ui<<UiSurfacePool as Surfaces<SegmentSpace>>::Surface>) {
|
||||||
// Wait for the renderer to start running
|
// 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
|
||||||
//ui.display.render_is_running.wait().await;
|
Timer::after_secs(3).await;
|
||||||
|
ui.show().await;
|
||||||
|
|
||||||
// Run the wake sequence, and turn on the lights
|
// Enter the startup scene
|
||||||
ui.wakeup().await;
|
ui.apply_scene(Scene::Ready).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 event loop
|
// Enter the event loop
|
||||||
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