events: rewrite how sensor statuses are reported, and implement some oled UI icons for it

This commit is contained in:
2025-11-08 12:04:22 +01:00
parent a36fe3d1ac
commit 092885f163
24 changed files with 845 additions and 111 deletions

View File

@@ -1,14 +1,15 @@
use core::f32::consts::PI;
use core::fmt::Binary;
use alloc::format;
use embedded_graphics::image::ImageRaw;
use embedded_graphics::mono_font::ascii::*;
use embedded_graphics::mono_font::{MonoTextStyle, MonoTextStyleBuilder};
use embedded_graphics::pixelcolor::BinaryColor;
use embedded_graphics::prelude::Size;
use embedded_graphics::prelude::{DrawTarget, Size};
use embedded_graphics::primitives::{Line, PrimitiveStyle, PrimitiveStyleBuilder, StyledDrawable};
use embedded_graphics::text::{Alignment, Text};
use embedded_graphics::{image::Image, prelude::Point, Drawable};
use enum_map::EnumMap;
use figments::liber8tion::trig::sin8;
use figments::mappings::embedded_graphics::Matrix2DSpace;
use figments::{liber8tion::trig::cos8, mappings::embedded_graphics::EmbeddedGraphicsSampler};
@@ -17,6 +18,7 @@ use nalgebra::Vector2;
use micromath::F32Ext;
use embedded_graphics::geometry::OriginDimensions;
use crate::events::{SensorSource, SensorState};
use crate::graphics::images;
use crate::{ego::engine::MotionState, events::Scene};
@@ -26,8 +28,7 @@ pub struct OledUI {
pub motion: MotionState,
pub brakelight: bool,
pub headlight: bool,
pub gps_online: bool,
pub imu_online: bool,
pub sensor_states: EnumMap<SensorSource, SensorState>,
pub velocity: f32,
pub location: Vector2<f64>,
pub sleep: bool,
@@ -50,6 +51,90 @@ pub enum Screen {
Waking
}
struct SensorImage {
source: SensorSource,
on: &'static ImageRaw<'static, BinaryColor>,
off: &'static ImageRaw<'static, BinaryColor>,
}
struct SensorIcon<'a> {
anchor: embedded_graphics::geometry::Point,
image: &'a SensorImage,
state: SensorState,
frame: usize
}
impl<'a> SensorIcon<'a> {
pub const fn new(image: &'a SensorImage, state: SensorState, frame: usize, anchor: embedded_graphics::geometry::Point) -> Self {
Self {
anchor,
image,
state,
frame
}
}
pub fn draw<T: DrawTarget<Color = BinaryColor>>(&self, target: &mut T) -> Result<(), T::Error> {
match self.state {
SensorState::AcquiringFix => {
if (self.frame / 10) % 2 == 0 {
Image::new(self.image.off, self.anchor).draw(target)
} else {
Ok(())
}
},
SensorState::Online => {
Image::new(self.image.on, self.anchor).draw(target)
},
SensorState::Offline => {
Image::new(self.image.off, self.anchor).draw(target)
},
SensorState::Degraded => {
if (self.frame / 10) % 2 == 0 {
Image::new(self.image.on, self.anchor).draw(target)
} else {
Ok(())
}
}
}
}
}
const SENSOR_IMAGES: &[SensorImage] = &[
SensorImage {
source: SensorSource::GPS,
on: &images::GPS_ON, off: &images::GPS_OFF,
},
SensorImage {
source: SensorSource::IMU,
on: &images::IMU_ON, off: &images::IMU_OFF,
},
SensorImage {
source: SensorSource::GravityReference,
on: &images::GRAVITY_LOCATED, off: &images::GRAVITY_MISSING,
},
SensorImage {
source: SensorSource::Location,
on: &images::LOCATION_ON, off: &images::LOCATION_OFF,
},
#[cfg(feature="demo")]
SensorImage {
source: SensorSource::Demo,
on: &images::DEMO, off: &images::DEMO,
},
#[cfg(feature="simulation")]
SensorImage {
source: SensorSource::Simulation,
on: &images::TAPE, off: &images::TAPE
},
];
const DISPLAY_SIZE: Size = Size::new(128, 64);
const SENSOR_ICON_SIZE: u32 = 16;
const SENSOR_ICON_SPACING: u32 = 2;
const SENSOR_TRAY_SIZE: Size = Size::new(SENSOR_IMAGES.len() as u32 * (SENSOR_ICON_SIZE + SENSOR_ICON_SPACING), SENSOR_ICON_SIZE + SENSOR_ICON_SPACING);
const SPEEDO_TRAY_SIZE: Size = Size::new(DISPLAY_SIZE.width - SENSOR_TRAY_SIZE.width, SENSOR_TRAY_SIZE.height);
impl Screen {
pub fn draw_screen<'a, T>(&self, sampler: &mut EmbeddedGraphicsSampler<'a, T>, state: &OledUniforms) where T: Sample<'a, Matrix2DSpace>, T::Output: PixelSink<BinaryColor> {
match self {
@@ -74,36 +159,22 @@ impl Screen {
Screen::Home => {
// Status bar
// Sensor indicators
let gps_img = if state.ui.gps_online {
&images::GPS_ON
} else {
&images::GPS_OFF
};
let imu_img = if state.ui.imu_online {
&images::IMU_ON
} else {
&images::IMU_OFF
};
Image::new(gps_img, Point::zero()).draw(sampler).unwrap();
Image::new(imu_img, Point::new((gps_img.size().width + 2) as i32, 0)).draw(sampler).unwrap();
for (idx, sensor) in SENSOR_IMAGES.iter().enumerate() {
let offset = idx * (16 + 2);
let position = Point::new(offset as i32, 0);
SensorIcon::new(sensor, state.ui.sensor_states[sensor.source], state.frame, position)
.draw(sampler).unwrap();
}
#[cfg(feature="demo")]
Text::with_alignment("Demo", Point::new(128, 10), TEXT_STYLE, Alignment::Right)
.draw(sampler)
.unwrap();
let speedo_center = SENSOR_TRAY_SIZE.width + SPEEDO_TRAY_SIZE.width / 2;
#[cfg(feature="simulation")]
Text::with_alignment("Sim", Point::new(128, 10), TEXT_STYLE, Alignment::Right)
// Speed display at the top
Text::with_alignment(&format!("{}", state.ui.velocity), Point::new(speedo_center as i32, 12), SPEED_STYLE, Alignment::Center)
.draw(sampler)
.unwrap();
// Separates the status bar from the UI
Line::new(Point::new(0, 18), Point::new(128, 18)).draw_styled(&INACTIVE_STYLE, sampler).unwrap();
// Speed display at the top
Text::with_alignment(&format!("{}", state.ui.velocity), Point::new(128 / 2, 12), SPEED_STYLE, Alignment::Center)
.draw(sampler)
.unwrap();
Line::new(Point::new(0, SENSOR_TRAY_SIZE.height as i32), Point::new(128, SENSOR_TRAY_SIZE.height as i32)).draw_styled(&INACTIVE_STYLE, sampler).unwrap();
// The main UI content
Image::new(&images::BIKE, Point::new((128 / 2 - images::BIKE.size().width / 2) as i32, 24)).draw(sampler).unwrap();