oled: rewrite the oled UI to have animations, and support a version of sleeping where the display is blank, at least
This commit is contained in:
@@ -9,7 +9,7 @@ name = "renderbug-embassy"
|
|||||||
path = "./src/bin/main.rs"
|
path = "./src/bin/main.rs"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["real-output"]
|
default = ["real-output", "oled"]
|
||||||
real-output = []
|
real-output = []
|
||||||
simulation = ["dep:rmp"]
|
simulation = ["dep:rmp"]
|
||||||
radio = [
|
radio = [
|
||||||
|
|||||||
121
assets/boot-logo.pbm
Normal file
121
assets/boot-logo.pbm
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
P1
|
||||||
|
# Created by GIMP version 3.0.4 PNM plug-in
|
||||||
|
128 64
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000001100000000000
|
||||||
|
0000000001000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0001111111111000000000000000000000000000000011110000000000000000001110
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000111111111
|
||||||
|
1110000000000000000000000000000011110000000000000000011110000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000001111001111000000000
|
||||||
|
0000000000000000000001110000000000000000011110000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000001111000111100000000000000100000
|
||||||
|
0000000101110000000000000000001110000000000000000000101110011000000000
|
||||||
|
0000000000000000000000000001111000111000000011110001111111000001111111
|
||||||
|
0000111100011011101111111100001100111100111111111100000000000000000000
|
||||||
|
0000000000000001111001111000001111110011111111100011111111001111110011
|
||||||
|
1111101111111110011111111100111111111100000000000000000000000000000000
|
||||||
|
0001111111110000001110111011111111100011100111001110111011111110111100
|
||||||
|
1110011110111001110011111000000000000000000000000000000000000111111110
|
||||||
|
0000011111111001110011100111000111011111111011110110111000011100111001
|
||||||
|
1001110011110000000000000000000000000000000000000111111110000001111111
|
||||||
|
0001110011100111000011011111110001110000111000011100111001100111001111
|
||||||
|
0000000000000000000000000000000000000111001111000001110000000111001110
|
||||||
|
0111000011011100000001110000111000011100111001100011101110000000000000
|
||||||
|
0000000000000000000000000111100111110001110000000111001110011100011101
|
||||||
|
1100000001110000111000011100111001100001111110000000000000000000000000
|
||||||
|
0000000000000111100011111001110001100111001111011110011101110001100111
|
||||||
|
0000111100011100111011100011110000000000000000000000000000000000000000
|
||||||
|
1111110001111000111111101111111111001111111100111111100111100011110011
|
||||||
|
1000111111110011111110000000000000000000000000000000000001111111100111
|
||||||
|
1100111111001111111111001111111110111111001111100001111111100011111111
|
||||||
|
0011111111100000000000000000000000000000000000111111000011110000111000
|
||||||
|
0111001111000111001100001110001111000001111111000001110110001111111110
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000011110001110000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000011100001110000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000011111111110000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000001111111100000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
1111110000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000001111101110000000110000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000001111001111000001110000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000011100
|
||||||
|
1110000011110000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000011100110000001111
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000001111110000011111000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000001111100000111111000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000111100000110111000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000111000001100111000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000011100
|
||||||
|
0011111111111100000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000011100001111111111
|
||||||
|
1100000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000001000001111111111110000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000011100000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000011100000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000011100000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000011100000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000001110000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
00
|
||||||
@@ -61,7 +61,7 @@ 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();
|
||||||
|
|
||||||
let garage = BUS_GARAGE.init(Default::default());
|
let garage = BUS_GARAGE.init(Default::default());
|
||||||
|
|
||||||
@@ -104,19 +104,12 @@ async fn main(spawner: Spawner) {
|
|||||||
use esp_hal::i2c::master::{Config, I2c};
|
use esp_hal::i2c::master::{Config, I2c};
|
||||||
|
|
||||||
let mut rst = Output::new(peripherals.GPIO21, esp_hal::gpio::Level::Low, OutputConfig::default());
|
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(
|
let i2c = I2c::new(
|
||||||
peripherals.I2C0,
|
peripherals.I2C0,
|
||||||
Config::default().with_frequency(Rate::from_khz(400))
|
Config::default().with_frequency(Rate::from_khz(400))
|
||||||
).unwrap().with_scl(peripherals.GPIO18).with_sda(peripherals.GPIO17).into_async();
|
).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()));
|
spawner.must_spawn(renderbug_embassy::tasks::oled::oled_task(i2c, rst, garage.telemetry.dyn_subscriber().unwrap()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature="simulation")]
|
#[cfg(feature="simulation")]
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
use core::cell::RefCell;
|
use core::{cell::RefCell, f32::consts::PI};
|
||||||
|
|
||||||
use alloc::format;
|
use alloc::format;
|
||||||
use embassy_sync::pubsub::DynSubscriber;
|
use embassy_sync::pubsub::DynSubscriber;
|
||||||
use embassy_time::Timer;
|
use embassy_time::Timer;
|
||||||
use embedded_graphics::{image::Image, mono_font::{ascii::{FONT_6X10, FONT_10X20, FONT_9X18_BOLD}, MonoTextStyleBuilder}, pixelcolor::BinaryColor, prelude::*, primitives::{Line, PrimitiveStyleBuilder, Rectangle, StyledDrawable}, text::{Alignment, Baseline, Text}};
|
use embedded_graphics::{image::Image, mono_font::{ascii::{FONT_6X10, FONT_10X20, FONT_9X18_BOLD}, MonoTextStyleBuilder}, pixelcolor::BinaryColor, prelude::*, primitives::{Line, PrimitiveStyleBuilder, Rectangle, StyledDrawable}, text::{Alignment, Baseline, Text}};
|
||||||
use esp_hal::{i2c::master::I2c, Async};
|
use esp_hal::{i2c::master::I2c, Async, gpio::Output};
|
||||||
|
use figments::{liber8tion::{interpolate::{ease_in_out_quad, scale8}, trig::{cos8, sin8}}, prelude::Fract8Ops};
|
||||||
use nalgebra::Vector2;
|
use nalgebra::Vector2;
|
||||||
use ssd1306::{mode::DisplayConfigAsync, prelude::DisplayRotation, size::DisplaySize128x64, I2CDisplayInterface, Ssd1306Async};
|
use ssd1306::{mode::{BufferedGraphicsModeAsync, DisplayConfigAsync}, prelude::{DisplayRotation, I2CInterface}, size::DisplaySize128x64, I2CDisplayInterface, Ssd1306Async};
|
||||||
use log::*;
|
use log::*;
|
||||||
|
use embedded_graphics::mono_font::MonoTextStyle;
|
||||||
|
use embedded_graphics::primitives::PrimitiveStyle;
|
||||||
|
use nalgebra::ComplexField;
|
||||||
|
|
||||||
use crate::{backoff::Backoff, ego::engine::MotionState, events::{Notification, Prediction, Scene, SensorSource, Telemetry}};
|
use crate::{backoff::Backoff, ego::engine::MotionState, events::{Notification, Prediction, Scene, SensorSource, Telemetry}, tasks::ui};
|
||||||
|
|
||||||
mod images {
|
mod images {
|
||||||
use embedded_graphics::{
|
use embedded_graphics::{
|
||||||
@@ -29,15 +33,194 @@ struct OledUI {
|
|||||||
imu_online: bool,
|
imu_online: bool,
|
||||||
velocity: f32,
|
velocity: f32,
|
||||||
location: Vector2<f64>,
|
location: Vector2<f64>,
|
||||||
sleep: bool
|
sleep: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct UiPainter<'a>(&'a mut Ssd1306Async<I2CInterface<I2c<'static, Async>>, DisplaySize128x64, BufferedGraphicsModeAsync<DisplaySize128x64>>);
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, Copy)]
|
||||||
|
enum Screen {
|
||||||
|
#[default]
|
||||||
|
Blank,
|
||||||
|
Bootsplash,
|
||||||
|
Home,
|
||||||
|
Sleeping,
|
||||||
|
Waking
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiPainter<'_> {
|
||||||
|
pub async fn screen_transition(&mut self, from: Screen, to: Screen, state: &OledUI) {
|
||||||
|
warn!("Transitioning screens from {from:?} to {to:?}");
|
||||||
|
for pos in 0..16 {
|
||||||
|
self.blank();
|
||||||
|
// Draw the screen, then paint the swipe across it from left to right
|
||||||
|
self.draw_screen(from, 0, state);
|
||||||
|
Rectangle::new(Point::zero(), Size { width: figments::liber8tion::interpolate::ease_in_out_quad(pos*8) as u32, height: 64}).draw_styled(&DRAW_STYLE, self.0).unwrap();
|
||||||
|
self.commit().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
for pos in 0..16 {
|
||||||
|
self.blank();
|
||||||
|
// Paint the next screen, then cover the right half with the swipe
|
||||||
|
self.draw_screen(to, 0, state);
|
||||||
|
let pos = figments::liber8tion::interpolate::ease_in_out_quad(pos*8) as u32;
|
||||||
|
Rectangle::new(Point::new(pos as i32, 0), Size { width: 128 - pos, height: 64}).draw_styled(&DRAW_STYLE, self.0).unwrap();
|
||||||
|
self.commit().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally draw the untouched screen
|
||||||
|
self.blank();
|
||||||
|
self.draw_screen(to, 0, state);
|
||||||
|
self.commit().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_screen(&mut self, screen: Screen, frame: usize, ui_state: &OledUI) {
|
||||||
|
match screen {
|
||||||
|
Screen::Blank => (),
|
||||||
|
Screen::Bootsplash => {
|
||||||
|
Image::new(&images::BOOT_LOGO, Point::zero()).draw(self.0).unwrap();
|
||||||
|
const SPARKLE_COUNT: i32 = 8;
|
||||||
|
for n in 0..SPARKLE_COUNT {
|
||||||
|
let sparkle_center = Point::new(128u8.scale8(cos8(frame.wrapping_mul(n as usize))) as i32, 64u8.scale8(sin8(frame.wrapping_mul(n as usize) as u8)) as i32);
|
||||||
|
let offset = (frame / 2 % 32) as i32 - 16;
|
||||||
|
let rotation = PI * 2.0 * (sin8(frame) as f32 / 255.0);
|
||||||
|
let normal = Point::new((rotation.cos() * offset as f32) as i32, (rotation.sin() * offset as f32) as i32);
|
||||||
|
let cross_normal = Point::new((rotation.sin() * offset as f32) as i32, (rotation.cos() * offset as f32) as i32);
|
||||||
|
// Draw horizontal
|
||||||
|
Line::new(sparkle_center - normal, sparkle_center + normal)
|
||||||
|
.draw_styled(&SPARKLE_STYLE, self.0).unwrap();
|
||||||
|
// Draw vertical
|
||||||
|
Line::new(sparkle_center - cross_normal, sparkle_center + cross_normal)
|
||||||
|
.draw_styled(&SPARKLE_STYLE, self.0).unwrap();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Screen::Home => {
|
||||||
|
// Status bar
|
||||||
|
// Sensor indicators
|
||||||
|
let gps_img = if ui_state.gps_online {
|
||||||
|
&images::GPS_ON
|
||||||
|
} else {
|
||||||
|
&images::GPS_OFF
|
||||||
|
};
|
||||||
|
let imu_img = if ui_state.imu_online {
|
||||||
|
&images::IMU_ON
|
||||||
|
} else {
|
||||||
|
&images::IMU_OFF
|
||||||
|
};
|
||||||
|
Image::new(gps_img, Point::zero()).draw(self.0).unwrap();
|
||||||
|
Image::new(imu_img, Point::new((gps_img.size().width + 2) as i32, 0)).draw(self.0).unwrap();
|
||||||
|
|
||||||
|
#[cfg(feature="demo")]
|
||||||
|
Text::with_alignment("Demo", Point::new(128, 10), TEXT_STYLE, Alignment::Right)
|
||||||
|
.draw(self.0)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
#[cfg(feature="simulation")]
|
||||||
|
Text::with_alignment("Sim", Point::new(128, 10), TEXT_STYLE, Alignment::Right)
|
||||||
|
.draw(self.0)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Separates the status bar from the UI
|
||||||
|
Line::new(Point::new(0, 18), Point::new(128, 18)).draw_styled(&INACTIVE_STYLE, self.0).unwrap();
|
||||||
|
|
||||||
|
// Speed display at the top
|
||||||
|
Text::with_alignment(&format!("{}", ui_state.velocity), Point::new(128 / 2, 12), SPEED_STYLE, Alignment::Center)
|
||||||
|
.draw(self.0)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// The main UI content
|
||||||
|
Image::new(&images::BIKE, Point::new((128 / 2 - images::BIKE.size().width / 2) as i32, 24)).draw(self.0).unwrap();
|
||||||
|
|
||||||
|
let headlight_img = if ui_state.headlight {
|
||||||
|
&images::HEADLIGHT_ON
|
||||||
|
} else {
|
||||||
|
&images::HEADLIGHT_OFF
|
||||||
|
};
|
||||||
|
let brakelight_img = if ui_state.brakelight {
|
||||||
|
&images::BRAKELIGHT_ON
|
||||||
|
} else {
|
||||||
|
&images::BRAKELIGHT_OFF
|
||||||
|
};
|
||||||
|
Image::new(headlight_img, Point::new(((128 / 2 - images::BIKE.size().width / 2) - 18) as i32, 28)).draw(self.0).unwrap();
|
||||||
|
Image::new(brakelight_img, Point::new(((128 / 2 + images::BIKE.size().width / 2) + 2) as i32, 28)).draw(self.0).unwrap();
|
||||||
|
|
||||||
|
// TODO: Replace the state texts with cute animations or smth
|
||||||
|
// Current prediction from the motion engine
|
||||||
|
Text::with_alignment(&format!("{:?}", ui_state.motion), Point::new(128 / 2, 64 - 3), TEXT_STYLE, Alignment::Center)
|
||||||
|
.draw(self.0)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Current scene in the UI
|
||||||
|
Text::with_alignment(&format!("{:?}", ui_state.scene), Point::new(128 / 2, 64 - 13), TEXT_STYLE, Alignment::Center)
|
||||||
|
.draw(self.0)
|
||||||
|
.unwrap();
|
||||||
|
},
|
||||||
|
Screen::Sleeping => {
|
||||||
|
Text::with_alignment("so eepy Zzz...", Point::new(128 / 2, 64 / 2), LOGO_STYLE, Alignment::Center)
|
||||||
|
.draw(self.0)
|
||||||
|
.unwrap();
|
||||||
|
},
|
||||||
|
Screen::Waking => {
|
||||||
|
Text::with_alignment("OwO hewwo again :D", Point::new(128 / 2, 64 / 2), LOGO_STYLE, Alignment::Center)
|
||||||
|
.draw(self.0)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn blank(&mut self) {
|
||||||
|
self.0.clear_buffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn commit(&mut self) {
|
||||||
|
self.0.flush().await.expect("Could not commit frame");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const SPARKLE_STYLE: PrimitiveStyle<BinaryColor> = PrimitiveStyleBuilder::new()
|
||||||
|
.stroke_color(BinaryColor::On)
|
||||||
|
.stroke_width(2)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
const TEXT_STYLE: MonoTextStyle<BinaryColor> = MonoTextStyleBuilder::new()
|
||||||
|
.font(&FONT_6X10)
|
||||||
|
.text_color(BinaryColor::On)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
const LOGO_STYLE: MonoTextStyle<BinaryColor> = MonoTextStyleBuilder::new()
|
||||||
|
.font(&FONT_9X18_BOLD)
|
||||||
|
.text_color(BinaryColor::On)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
const SPEED_STYLE: MonoTextStyle<BinaryColor> = MonoTextStyleBuilder::new()
|
||||||
|
.font(&FONT_10X20)
|
||||||
|
.text_color(BinaryColor::On)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
const DRAW_STYLE: PrimitiveStyle<BinaryColor> = PrimitiveStyleBuilder::new()
|
||||||
|
.fill_color(BinaryColor::On)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
const INACTIVE_STYLE: PrimitiveStyle<BinaryColor> = PrimitiveStyleBuilder::new()
|
||||||
|
.fill_color(BinaryColor::Off)
|
||||||
|
.stroke_color(BinaryColor::On)
|
||||||
|
.stroke_width(2)
|
||||||
|
.build();
|
||||||
|
|
||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
pub async fn oled_task(i2c: I2c<'static, Async>, mut events: DynSubscriber<'static, Telemetry>) {
|
pub async fn oled_task(i2c: I2c<'static, Async>, mut reset_pin: Output<'static>, mut events: DynSubscriber<'static, Telemetry>) {
|
||||||
let interface = RefCell::new(Some(I2CDisplayInterface::new(i2c)));
|
let interface = RefCell::new(Some(I2CDisplayInterface::new(i2c)));
|
||||||
|
let mut ui_state = OledUI::default();
|
||||||
|
|
||||||
// initialize the display
|
// initialize the display
|
||||||
let mut display = Backoff::from_secs(3).forever().attempt(async || {
|
let mut display = Backoff::from_secs(3).forever().attempt(async || {
|
||||||
info!("Setting up OLED display");
|
info!("Setting up OLED display");
|
||||||
|
Timer::after_millis(10).await;
|
||||||
|
reset_pin.set_high();
|
||||||
|
Timer::after_millis(10).await;
|
||||||
|
reset_pin.set_low();
|
||||||
|
Timer::after_millis(10).await;
|
||||||
|
reset_pin.set_high();
|
||||||
let mut display = Ssd1306Async::new(interface.replace(None).unwrap(), DisplaySize128x64, DisplayRotation::Rotate0)
|
let mut display = Ssd1306Async::new(interface.replace(None).unwrap(), DisplaySize128x64, DisplayRotation::Rotate0)
|
||||||
.into_buffered_graphics_mode();
|
.into_buffered_graphics_mode();
|
||||||
if let Err(e) = display.init().await {
|
if let Err(e) = display.init().await {
|
||||||
@@ -52,119 +235,49 @@ pub async fn oled_task(i2c: I2c<'static, Async>, mut events: DynSubscriber<'sta
|
|||||||
display.clear(BinaryColor::Off).unwrap();
|
display.clear(BinaryColor::Off).unwrap();
|
||||||
display.flush().await.unwrap();
|
display.flush().await.unwrap();
|
||||||
|
|
||||||
let text_style = MonoTextStyleBuilder::new()
|
|
||||||
.font(&FONT_6X10)
|
|
||||||
.text_color(BinaryColor::On)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let logo_style = MonoTextStyleBuilder::new()
|
|
||||||
.font(&FONT_9X18_BOLD)
|
|
||||||
.text_color(BinaryColor::On)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let speed_style = MonoTextStyleBuilder::new()
|
|
||||||
.font(&FONT_10X20)
|
|
||||||
.text_color(BinaryColor::On)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let draw_style = PrimitiveStyleBuilder::new()
|
|
||||||
.fill_color(BinaryColor::On)
|
|
||||||
.build();
|
|
||||||
let erase_style = PrimitiveStyleBuilder::new()
|
|
||||||
.fill_color(BinaryColor::Off)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let inactive_style = PrimitiveStyleBuilder::new()
|
|
||||||
.fill_color(BinaryColor::Off)
|
|
||||||
.stroke_color(BinaryColor::On)
|
|
||||||
.stroke_width(2)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let mut ui_state = OledUI::default();
|
|
||||||
|
|
||||||
info!("Running boot splash animation");
|
info!("Running boot splash animation");
|
||||||
for pos in 0..16 {
|
let mut painter = UiPainter(&mut display);
|
||||||
Rectangle::new(Point::zero(), Size { width: figments::liber8tion::interpolate::ease_in_out_quad(pos*8) as u32, height: 64}).draw_styled(&draw_style, &mut display).unwrap();
|
painter.screen_transition(Screen::Blank, Screen::Bootsplash, &ui_state).await;
|
||||||
display.flush().await.unwrap();
|
for i in 0..255 {
|
||||||
|
painter.blank();
|
||||||
|
painter.draw_screen(Screen::Bootsplash, i, &ui_state);
|
||||||
|
painter.commit().await;
|
||||||
}
|
}
|
||||||
|
painter.screen_transition(Screen::Bootsplash, Screen::Home, &ui_state).await;
|
||||||
for pos in 0..16 {
|
|
||||||
Rectangle::new(Point::zero(), Size { width: figments::liber8tion::interpolate::ease_in_out_quad(pos*8) as u32, height: 64}).draw_styled(&erase_style, &mut display).unwrap();
|
|
||||||
Text::with_baseline("Renderbug v4", Point::new(0, 16), logo_style, Baseline::Top)
|
|
||||||
.draw(&mut display)
|
|
||||||
.unwrap();
|
|
||||||
display.flush().await.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer::after_secs(2).await;
|
|
||||||
|
|
||||||
info!("OLED display is ready!");
|
info!("OLED display is ready!");
|
||||||
loop {
|
loop {
|
||||||
// FIXME: Need to implement sleep handling to electrically turn the display on/off and do a sleep animation
|
// FIXME: Need to implement sleep handling to electrically turn the display on/off
|
||||||
display.clear(BinaryColor::Off).unwrap();
|
|
||||||
if !ui_state.sleep {
|
if !ui_state.sleep {
|
||||||
|
painter.blank();
|
||||||
// Sensor indicators
|
painter.draw_screen(Screen::Home, 0, &ui_state);
|
||||||
let gps_img = if ui_state.gps_online {
|
painter.commit().await;
|
||||||
&images::GPS_ON
|
|
||||||
} else {
|
|
||||||
&images::GPS_OFF
|
|
||||||
};
|
|
||||||
let imu_img = if ui_state.imu_online {
|
|
||||||
&images::IMU_ON
|
|
||||||
} else {
|
|
||||||
&images::IMU_OFF
|
|
||||||
};
|
|
||||||
Image::new(gps_img, Point::zero()).draw(&mut display).unwrap();
|
|
||||||
Image::new(imu_img, Point::new((gps_img.size().width + 2) as i32, 0)).draw(&mut display).unwrap();
|
|
||||||
|
|
||||||
Image::new(&images::BIKE, Point::new((128 / 2 - images::BIKE.size().width / 2) as i32, 24)).draw(&mut display).unwrap();
|
|
||||||
|
|
||||||
let headlight_img = if ui_state.headlight {
|
|
||||||
&images::HEADLIGHT_ON
|
|
||||||
} else {
|
|
||||||
&images::HEADLIGHT_OFF
|
|
||||||
};
|
|
||||||
let brakelight_img = if ui_state.brakelight {
|
|
||||||
&images::BRAKELIGHT_ON
|
|
||||||
} else {
|
|
||||||
&images::BRAKELIGHT_OFF
|
|
||||||
};
|
|
||||||
Image::new(headlight_img, Point::new(((128 / 2 - images::BIKE.size().width / 2) - 18) as i32, 28)).draw(&mut display).unwrap();
|
|
||||||
Image::new(brakelight_img, Point::new(((128 / 2 + images::BIKE.size().width / 2) + 2) as i32, 28)).draw(&mut display).unwrap();
|
|
||||||
|
|
||||||
Text::with_alignment(&format!("{}", ui_state.velocity), Point::new(128 / 2, 12), speed_style, Alignment::Center)
|
|
||||||
.draw(&mut display)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// TODO: Replace the state texts with cute animations or smth
|
|
||||||
Text::with_alignment(&format!("{:?}", ui_state.motion), Point::new(128 / 2, 64 - 3), text_style, Alignment::Center)
|
|
||||||
.draw(&mut display)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
Text::with_alignment(&format!("{:?}", ui_state.scene), Point::new(128 / 2, 64 - 13), text_style, Alignment::Center)
|
|
||||||
.draw(&mut display)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
Line::new(Point::new(0, 18), Point::new(128, 18)).draw_styled(&inactive_style, &mut display).unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
display.flush().await.unwrap();
|
|
||||||
|
|
||||||
let evt = events.next_message_pure().await;
|
let evt = events.next_message_pure().await;
|
||||||
match evt {
|
match evt {
|
||||||
Telemetry::Prediction(Prediction::Velocity(v)) => ui_state.velocity = v,
|
Telemetry::Prediction(Prediction::Velocity(v)) => ui_state.velocity = v,
|
||||||
Telemetry::Prediction(Prediction::Location(loc)) => ui_state.location = loc,
|
Telemetry::Prediction(Prediction::Location(loc)) => ui_state.location = loc,
|
||||||
Telemetry::Prediction(Prediction::Motion(motion)) => ui_state.motion = motion,
|
Telemetry::Prediction(Prediction::Motion(motion)) => ui_state.motion = motion,
|
||||||
Telemetry::Notification(crate::events::Notification::SceneChange(scene)) => ui_state.scene = scene,
|
Telemetry::Notification(Notification::SceneChange(scene)) => ui_state.scene = scene,
|
||||||
Telemetry::Notification(Notification::SetBrakelight(b)) => ui_state.brakelight = b,
|
Telemetry::Notification(Notification::SetBrakelight(b)) => ui_state.brakelight = b,
|
||||||
Telemetry::Notification(Notification::SetHeadlight(b)) => ui_state.headlight = b,
|
Telemetry::Notification(Notification::SetHeadlight(b)) => ui_state.headlight = b,
|
||||||
Telemetry::Notification(Notification::SensorOffline(SensorSource::IMU)) => ui_state.imu_online = false,
|
Telemetry::Notification(Notification::SensorOffline(SensorSource::IMU)) => ui_state.imu_online = false,
|
||||||
Telemetry::Notification(Notification::SensorOnline(SensorSource::IMU)) => ui_state.imu_online = true,
|
Telemetry::Notification(Notification::SensorOnline(SensorSource::IMU)) => ui_state.imu_online = true,
|
||||||
Telemetry::Notification(Notification::SensorOffline(SensorSource::GPS)) => ui_state.gps_online = false,
|
Telemetry::Notification(Notification::SensorOffline(SensorSource::GPS)) => ui_state.gps_online = false,
|
||||||
Telemetry::Notification(Notification::SensorOnline(SensorSource::GPS)) => ui_state.gps_online = true,
|
Telemetry::Notification(Notification::SensorOnline(SensorSource::GPS)) => ui_state.gps_online = true,
|
||||||
Telemetry::Notification(Notification::Sleep) => ui_state.sleep = true,
|
Telemetry::Notification(Notification::Sleep) => {
|
||||||
Telemetry::Notification(Notification::WakeUp) => ui_state.sleep = false,
|
painter.screen_transition(Screen::Home, Screen::Sleeping, &ui_state).await;
|
||||||
|
Timer::after_secs(1).await;
|
||||||
|
painter.screen_transition(Screen::Sleeping, Screen::Blank, &ui_state).await;
|
||||||
|
ui_state.sleep = true
|
||||||
|
},
|
||||||
|
Telemetry::Notification(Notification::WakeUp) => {
|
||||||
|
painter.screen_transition(Screen::Blank, Screen::Waking, &ui_state).await;
|
||||||
|
Timer::after_secs(1).await;
|
||||||
|
painter.screen_transition(Screen::Waking, Screen::Home, &ui_state).await;
|
||||||
|
ui_state.sleep = false
|
||||||
|
},
|
||||||
_ => ()
|
_ => ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,9 +45,8 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
|
|||||||
|
|
||||||
pub async fn sleep(&mut self) {
|
pub async fn sleep(&mut self) {
|
||||||
info!("Running sleep sequence");
|
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);
|
let mut disp_anim = AnimDisplay(&mut self.display);
|
||||||
fade_out.apply(&mut disp_anim).await;
|
TURN_OFF.apply(&mut disp_anim).await;
|
||||||
|
|
||||||
warn!("Resetting safety lights");
|
warn!("Resetting safety lights");
|
||||||
self.brakelight.set_visible(false);
|
self.brakelight.set_visible(false);
|
||||||
@@ -87,15 +86,14 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
|
|||||||
fade_in.apply(&mut self.brakelight)
|
fade_in.apply(&mut self.brakelight)
|
||||||
);
|
);
|
||||||
|
|
||||||
let fade_out = Animation::default().duration(Duration::from_secs(1)).from(255).to(0);
|
|
||||||
info!("Fade out overlay");
|
info!("Fade out overlay");
|
||||||
fade_out.apply(&mut self.overlay).await;
|
TURN_OFF.apply(&mut self.overlay).await;
|
||||||
self.overlay.set_visible(false);
|
self.overlay.set_visible(false);
|
||||||
Timer::after_secs(3).await;
|
Timer::after_secs(3).await;
|
||||||
warn!("Turning off safety lights");
|
warn!("Turning off safety lights");
|
||||||
join!(
|
join!(
|
||||||
fade_out.apply(&mut self.headlight),
|
TURN_OFF.apply(&mut self.headlight),
|
||||||
fade_out.apply(&mut self.brakelight)
|
TURN_OFF.apply(&mut self.brakelight)
|
||||||
);
|
);
|
||||||
info!("Wakeup complete!");
|
info!("Wakeup complete!");
|
||||||
}
|
}
|
||||||
@@ -109,8 +107,20 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
|
|||||||
match event {
|
match event {
|
||||||
// Toggling head and brake lights
|
// Toggling head and brake lights
|
||||||
// FIXME: These should be a Off/Low/High enum, so the stopping brake looks different from the dayrunning brake.
|
// 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::SetBrakelight(is_on) => {
|
||||||
Notification::SetHeadlight(is_on) => self.headlight.set_on(is_on).await,
|
if is_on {
|
||||||
|
TURN_ON.apply(&mut self.brakelight).await;
|
||||||
|
} else {
|
||||||
|
TURN_OFF.apply(&mut self.brakelight).await;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Notification::SetHeadlight(is_on) => {
|
||||||
|
if is_on {
|
||||||
|
TURN_ON.apply(&mut self.headlight).await;
|
||||||
|
} else {
|
||||||
|
TURN_OFF.apply(&mut self.headlight).await;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
Notification::Sleep => self.sleep().await,
|
Notification::Sleep => self.sleep().await,
|
||||||
Notification::WakeUp => self.wake().await,
|
Notification::WakeUp => self.wake().await,
|
||||||
@@ -119,6 +129,9 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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]
|
#[embassy_executor::task]
|
||||||
pub async fn safety_ui_main(mut events: DynSubscriber<'static, Notification>, mut ui: SafetyUi<<UiSurfacePool as Surfaces<SegmentSpace>>::Surface>) {
|
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
|
// Wait for the renderer to start running
|
||||||
|
|||||||
Reference in New Issue
Block a user