tasks: write an oled information driver

This commit is contained in:
2025-10-17 14:39:26 +02:00
parent eb9f949e4e
commit 818f8af49a
3 changed files with 1114 additions and 3 deletions

171
src/tasks/oled.rs Normal file
View File

@@ -0,0 +1,171 @@
use core::{cell::RefCell, fmt::Binary};
use embassy_sync::pubsub::DynSubscriber;
use embassy_time::Timer;
use embedded_graphics::{image::{Image, ImageRaw}, mono_font::{ascii::{FONT_6X10, FONT_10X20, FONT_9X18_BOLD}, MonoTextStyleBuilder}, pixelcolor::BinaryColor, prelude::*, primitives::{Circle, Line, Polyline, PrimitiveStyle, PrimitiveStyleBuilder, Rectangle, StyledDrawable}, text::{self, Alignment, Baseline, Text}};
use esp_hal::{i2c::master::I2c, Async};
use nalgebra::Vector2;
use ssd1306::{mode::DisplayConfigAsync, prelude::DisplayRotation, size::DisplaySize128x64, I2CDisplayInterface, Ssd1306Async};
use log::*;
use alloc::format;
use crate::{backoff::Backoff, ego::engine::MotionState, events::{Notification, Prediction, Scene, SensorSource, Telemetry}};
mod images {
use embedded_graphics::{
image::ImageRaw,
pixelcolor::BinaryColor
};
include!("../../target/images.rs");
}
#[derive(Default)]
struct OledUI {
scene: Scene,
motion: MotionState,
brakelight: bool,
headlight: bool,
gps_online: bool,
imu_online: bool,
velocity: f32,
location: Vector2<f64>,
sleep: bool
}
#[embassy_executor::task]
pub async fn oled_task(i2c: I2c<'static, Async>, mut events: DynSubscriber<'static, Telemetry>) {
let interface = RefCell::new(Some(I2CDisplayInterface::new(i2c)));
// initialize the display
let mut display = Backoff::from_secs(3).forever().attempt(async || {
info!("Setting up OLED display");
let mut display = Ssd1306Async::new(interface.replace(None).unwrap(), DisplaySize128x64, DisplayRotation::Rotate0)
.into_buffered_graphics_mode();
if let Err(e) = display.init().await {
interface.replace(Some(display.release()));
Err(())
} else {
Ok(display)
}
}).await.unwrap();
display.clear(BinaryColor::Off).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");
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(&draw_style, &mut display).unwrap();
display.flush().await.unwrap();
}
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!");
loop {
// FIXME: Need to implement sleep handling to electrically turn the display on/off and do a sleep animation
display.clear(BinaryColor::Off).unwrap();
if !ui_state.sleep {
// 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(&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;
match evt {
Telemetry::Prediction(Prediction::Velocity(v)) => ui_state.velocity = v,
Telemetry::Prediction(Prediction::Location(loc)) => ui_state.location = loc,
Telemetry::Prediction(Prediction::Motion(motion)) => ui_state.motion = motion,
Telemetry::Notification(crate::events::Notification::SceneChange(scene)) => ui_state.scene = scene,
Telemetry::Notification(Notification::SetBrakelight(b)) => ui_state.brakelight = b,
Telemetry::Notification(Notification::SetHeadlight(b)) => ui_state.headlight = b,
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::SensorOffline(SensorSource::GPS)) => ui_state.gps_online = false,
Telemetry::Notification(Notification::SensorOnline(SensorSource::GPS)) => ui_state.gps_online = true,
Telemetry::Notification(Notification::Sleep) => ui_state.sleep = true,
Telemetry::Notification(Notification::WakeUp) => ui_state.sleep = false,
_ => ()
}
}
}