screen, spat: Add more information and update indicator
This commit is contained in:
parent
c1315f10aa
commit
fc0c9b61da
3 changed files with 403 additions and 175 deletions
|
|
@ -65,7 +65,46 @@ pub struct State {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "spat")]
|
#[cfg(feature = "spat")]
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug)]
|
||||||
|
pub enum GlosaOutput {
|
||||||
|
Error(alloc::string::String),
|
||||||
|
Searching(GlosaSearching),
|
||||||
|
Found(GlosaFound),
|
||||||
|
Locked(GlosaSignalInfo),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "spat")]
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct GlosaSearching {
|
||||||
|
pub known_intersection_ids: Vec<u16>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "spat")]
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct GlosaFound {
|
||||||
|
pub intersection_id: u16,
|
||||||
|
pub approach_id: Option<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "spat")]
|
||||||
|
impl From<&TlData> for GlosaFound {
|
||||||
|
fn from(value: &TlData) -> Self {
|
||||||
|
Self {
|
||||||
|
intersection_id: value.intersection_id,
|
||||||
|
approach_id: value.approach_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "spat")]
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct GlosaSignalInfo {
|
||||||
|
pub intersection_id: u16,
|
||||||
|
pub signal_groups: Vec<SignalGroupData>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "spat")]
|
||||||
|
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||||
pub struct SignalGroupData {
|
pub struct SignalGroupData {
|
||||||
pub phase: SignalPhase,
|
pub phase: SignalPhase,
|
||||||
pub maneuver: map::Maneuvers,
|
pub maneuver: map::Maneuvers,
|
||||||
|
|
@ -73,7 +112,7 @@ pub struct SignalGroupData {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "spat")]
|
#[cfg(feature = "spat")]
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||||
pub enum SignalPhase {
|
pub enum SignalPhase {
|
||||||
#[default]
|
#[default]
|
||||||
Unknown,
|
Unknown,
|
||||||
|
|
@ -212,17 +251,20 @@ impl State {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "spat")]
|
#[cfg(feature = "spat")]
|
||||||
pub fn run_glosa(&mut self) -> Vec<SignalGroupData> {
|
pub fn run_glosa(&mut self) -> GlosaOutput {
|
||||||
let Some(own_heading) = self.heading_deg else {
|
let Some(own_heading) = self.heading_deg else {
|
||||||
return alloc::vec![];
|
use alloc::string::ToString;
|
||||||
|
|
||||||
|
return GlosaOutput::Error("Heading unknown".to_string());
|
||||||
};
|
};
|
||||||
|
|
||||||
let (state_update, sig_grps) = match &self.glosa_state {
|
let known_intersection_ids = self.tlc_cache.known_intersections();
|
||||||
|
let (state_update, output_data) = match &self.glosa_state {
|
||||||
// search for upcoming intersection
|
// search for upcoming intersection
|
||||||
GlosaState::Idle => {
|
GlosaState::Idle => {
|
||||||
debug!(
|
debug!(
|
||||||
"GLOSA: Searching with {} known intersections",
|
"GLOSA: Searching with {} known intersections",
|
||||||
self.tlc_cache.known_intersections().len()
|
known_intersection_ids.len()
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(search_result) =
|
if let Some(search_result) =
|
||||||
|
|
@ -233,9 +275,18 @@ impl State {
|
||||||
search_result.signal_groups
|
search_result.signal_groups
|
||||||
);
|
);
|
||||||
|
|
||||||
(Some(GlosaState::Locked(search_result)), None)
|
let glosa_found_data = (&search_result).into();
|
||||||
|
(
|
||||||
|
Some(GlosaState::Locked(search_result)),
|
||||||
|
GlosaOutput::Found(glosa_found_data),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
(None, None)
|
(
|
||||||
|
None,
|
||||||
|
GlosaOutput::Searching(GlosaSearching {
|
||||||
|
known_intersection_ids,
|
||||||
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GlosaState::Locked(info) => {
|
GlosaState::Locked(info) => {
|
||||||
|
|
@ -251,7 +302,12 @@ impl State {
|
||||||
.is_err()
|
.is_err()
|
||||||
{
|
{
|
||||||
info!("GLOSA: Intersection not known any more");
|
info!("GLOSA: Intersection not known any more");
|
||||||
(Some(GlosaState::Idle), None)
|
(
|
||||||
|
Some(GlosaState::Idle),
|
||||||
|
GlosaOutput::Searching(GlosaSearching {
|
||||||
|
known_intersection_ids,
|
||||||
|
}),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
// determine if intersection is left
|
// determine if intersection is left
|
||||||
let intersection = info.intersection.borrow();
|
let intersection = info.intersection.borrow();
|
||||||
|
|
@ -262,7 +318,12 @@ impl State {
|
||||||
&self.position,
|
&self.position,
|
||||||
) {
|
) {
|
||||||
info!("GLOSA: Intersection was left -> back to idle");
|
info!("GLOSA: Intersection was left -> back to idle");
|
||||||
(Some(GlosaState::Idle), None)
|
(
|
||||||
|
Some(GlosaState::Idle),
|
||||||
|
GlosaOutput::Searching(GlosaSearching {
|
||||||
|
known_intersection_ids,
|
||||||
|
}),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
// get signal groups which are in our approach
|
// get signal groups which are in our approach
|
||||||
let mut approach_sig_grps = intersection
|
let mut approach_sig_grps = intersection
|
||||||
|
|
@ -317,7 +378,13 @@ impl State {
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
(None, Some(sig_grps)) // keep state, return signal groups
|
(
|
||||||
|
None,
|
||||||
|
GlosaOutput::Locked(GlosaSignalInfo {
|
||||||
|
intersection_id: intersection.id(),
|
||||||
|
signal_groups: sig_grps,
|
||||||
|
}),
|
||||||
|
) // keep state, return signal groups
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -328,7 +395,7 @@ impl State {
|
||||||
self.glosa_state = new;
|
self.glosa_state = new;
|
||||||
}
|
}
|
||||||
|
|
||||||
sig_grps.unwrap_or_default()
|
output_data
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "spat")]
|
#[cfg(feature = "spat")]
|
||||||
|
|
|
||||||
39
src/main.rs
39
src/main.rs
|
|
@ -36,6 +36,10 @@ mod radio;
|
||||||
mod screen;
|
mod screen;
|
||||||
|
|
||||||
const WIFI_CHANNEL: radio::Channel = 180;
|
const WIFI_CHANNEL: radio::Channel = 180;
|
||||||
|
#[cfg(feature = "screen")]
|
||||||
|
pub const DISPLAY_WIDTH: u16 = 320;
|
||||||
|
#[cfg(feature = "screen")]
|
||||||
|
pub const DISPLAY_HEIGHT: u16 = 172;
|
||||||
|
|
||||||
// This creates a default app-descriptor required by the esp-idf bootloader.
|
// This creates a default app-descriptor required by the esp-idf bootloader.
|
||||||
// For more information see: <https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/app_image_format.html#application-description>
|
// For more information see: <https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/app_image_format.html#application-description>
|
||||||
|
|
@ -84,13 +88,13 @@ async fn main(spawner: Spawner) -> ! {
|
||||||
// SCL: D8/ GPIO8 (SPI_SCK)
|
// SCL: D8/ GPIO8 (SPI_SCK)
|
||||||
// SDA: D10/ GPIO10 (SPI_MOSI)
|
// SDA: D10/ GPIO10 (SPI_MOSI)
|
||||||
// RES: D3/ GPIO7
|
// RES: D3/ GPIO7
|
||||||
// DC: D4/ GPIO23
|
// DC: D4/ GPIO23 (display clear)
|
||||||
// CS: D5/ GPIO24
|
// CS: D5/ GPIO24 (chip select)
|
||||||
// BL: TODO (hard-wire to high? or D1/ GPIO0)
|
// BL: D1/ GPIO0 (backlight)
|
||||||
#[cfg(feature = "screen")]
|
#[cfg(feature = "screen")]
|
||||||
let mut spi_buffer = [0_u8; 512];
|
let mut spi_buffer = [0_u8; 512];
|
||||||
#[cfg(feature = "screen")]
|
#[cfg(feature = "screen")]
|
||||||
let mut display = {
|
let mut screen = {
|
||||||
let (spi, cs) = setup_st7789_spi(
|
let (spi, cs) = setup_st7789_spi(
|
||||||
peripherals.GPIO8,
|
peripherals.GPIO8,
|
||||||
peripherals.GPIO10,
|
peripherals.GPIO10,
|
||||||
|
|
@ -109,20 +113,24 @@ async fn main(spawner: Spawner) -> ! {
|
||||||
let spi_device = embedded_hal_bus::spi::ExclusiveDevice::new_no_delay(spi, cs)
|
let spi_device = embedded_hal_bus::spi::ExclusiveDevice::new_no_delay(spi, cs)
|
||||||
.expect("Failed to initialize SPI device");
|
.expect("Failed to initialize SPI device");
|
||||||
|
|
||||||
mipidsi::Builder::new(
|
let display = mipidsi::Builder::new(
|
||||||
mipidsi::models::ST7789,
|
mipidsi::models::ST7789,
|
||||||
mipidsi::interface::SpiInterface::new(spi_device, dc_output, &mut spi_buffer),
|
mipidsi::interface::SpiInterface::new(spi_device, dc_output, &mut spi_buffer),
|
||||||
)
|
)
|
||||||
.display_size(screen::HEIGHT + 34, screen::WIDTH) // somehow this display has 34 px of invisible space on the left (in native orientation)
|
.display_size(DISPLAY_HEIGHT + 34, DISPLAY_WIDTH) // somehow this display has 34 px of invisible space on the left (in native orientation)
|
||||||
.reset_pin(rst_output)
|
.reset_pin(rst_output)
|
||||||
.invert_colors(mipidsi::options::ColorInversion::Inverted)
|
.invert_colors(mipidsi::options::ColorInversion::Inverted)
|
||||||
.orientation(mipidsi::options::Orientation::new().rotate(mipidsi::options::Rotation::Deg90))
|
.orientation(mipidsi::options::Orientation::new().rotate(mipidsi::options::Rotation::Deg90))
|
||||||
.init(&mut display_delay)
|
.init(&mut display_delay)
|
||||||
.expect("Failed to initialize display")
|
.expect("Failed to initialize display");
|
||||||
|
|
||||||
|
screen::Handler::new(display, DISPLAY_WIDTH, DISPLAY_HEIGHT)
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "screen")]
|
#[cfg(feature = "screen")]
|
||||||
let _ = screen::draw_slash_screen(&mut display).inspect_err(log_screen_error);
|
let _ = screen
|
||||||
|
.update(screen::ScreenState::Initial)
|
||||||
|
.inspect_err(log_screen_error);
|
||||||
|
|
||||||
// Setup WiFi
|
// Setup WiFi
|
||||||
let (_wifi_controller, interfaces) = esp_radio::wifi::new(
|
let (_wifi_controller, interfaces) = esp_radio::wifi::new(
|
||||||
|
|
@ -171,13 +179,7 @@ async fn main(spawner: Spawner) -> ! {
|
||||||
let gnss_update_ref = GNSS_UPDATE.borrow(cs);
|
let gnss_update_ref = GNSS_UPDATE.borrow(cs);
|
||||||
|
|
||||||
if let Some(fix) = gnss_update_ref.replace(None) {
|
if let Some(fix) = gnss_update_ref.replace(None) {
|
||||||
let prev_state = state.initialized();
|
|
||||||
state.update_with_gpsfix(&fix);
|
state.update_with_gpsfix(&fix);
|
||||||
|
|
||||||
if prev_state != state.initialized() {
|
|
||||||
let _ = screen::clear(&mut display).inspect_err(log_screen_error);
|
|
||||||
}
|
|
||||||
|
|
||||||
new_position = true;
|
new_position = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -188,18 +190,21 @@ async fn main(spawner: Spawner) -> ! {
|
||||||
// run GLOSA algorithm
|
// run GLOSA algorithm
|
||||||
#[cfg(feature = "spat")]
|
#[cfg(feature = "spat")]
|
||||||
{
|
{
|
||||||
let signal_groups = state.run_glosa();
|
let glosa_data = state.run_glosa();
|
||||||
|
|
||||||
for sig in &signal_groups {
|
if let applogic::GlosaOutput::Locked(data) = &glosa_data {
|
||||||
|
for sig in &data.signal_groups {
|
||||||
let duration_str = sig
|
let duration_str = sig
|
||||||
.end_sec
|
.end_sec
|
||||||
.map(|v| alloc::format!("for {v} s"))
|
.map(|v| alloc::format!("for {v} s"))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
println!("GLOSA: {} {} {duration_str}", sig.maneuver, sig.phase);
|
println!("GLOSA: {} {} {duration_str}", sig.maneuver, sig.phase);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "screen")]
|
#[cfg(feature = "screen")]
|
||||||
let _ = screen::draw_glosa(&mut display, &signal_groups)
|
let _ = screen
|
||||||
|
.update(glosa_data.into())
|
||||||
.inspect_err(log_screen_error);
|
.inspect_err(log_screen_error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
344
src/screen.rs
344
src/screen.rs
|
|
@ -5,87 +5,255 @@ use embedded_graphics::{geometry, mono_font, pixelcolor, primitives, text};
|
||||||
|
|
||||||
use crate::applogic;
|
use crate::applogic;
|
||||||
|
|
||||||
pub const WIDTH: u16 = 320;
|
pub struct Handler<D>
|
||||||
pub const HEIGHT: u16 = 172;
|
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
pub fn clear<D>(target: &mut D) -> Result<(), D::Error>
|
|
||||||
where
|
where
|
||||||
D: DrawTarget<Color = pixelcolor::Rgb565>,
|
D: DrawTarget<Color = pixelcolor::Rgb565>,
|
||||||
{
|
{
|
||||||
let style = primitives::PrimitiveStyleBuilder::new()
|
target: D,
|
||||||
.fill_color(pixelcolor::Rgb565::BLACK)
|
width: u16,
|
||||||
.build();
|
height: u16,
|
||||||
primitives::Rectangle::new(
|
|
||||||
geometry::Point::new(0, 0),
|
|
||||||
geometry::Size::new(WIDTH.into(), HEIGHT.into()),
|
|
||||||
)
|
|
||||||
.into_styled(style)
|
|
||||||
.draw(target)?;
|
|
||||||
|
|
||||||
Ok(())
|
// internal state
|
||||||
|
prev_state: ScreenState,
|
||||||
|
hb_state: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[derive(Debug, Default, PartialEq, Eq)]
|
||||||
pub fn test_img<C, D>(target: &mut D) -> Result<(), D::Error>
|
pub enum ScreenState {
|
||||||
where
|
#[default]
|
||||||
D: DrawTarget<Color = C>,
|
Uninitialized,
|
||||||
C: RgbColor,
|
/// aka. "no GNSS fix" or "splash screen"
|
||||||
{
|
Initial,
|
||||||
mipidsi::TestImage::new().draw(target)?;
|
Error(alloc::string::String),
|
||||||
|
#[cfg(feature = "spat")]
|
||||||
Ok(())
|
GlosaSearching(applogic::GlosaSearching),
|
||||||
|
#[cfg(feature = "spat")]
|
||||||
|
GlosaFound(applogic::GlosaFound),
|
||||||
|
#[cfg(feature = "spat")]
|
||||||
|
GlosaLocked(applogic::GlosaSignalInfo),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[cfg(feature = "spat")]
|
||||||
pub fn draw_glosa<D>(target: &mut D, data: &[applogic::SignalGroupData]) -> Result<(), D::Error>
|
impl From<applogic::GlosaOutput> for ScreenState {
|
||||||
|
fn from(value: applogic::GlosaOutput) -> Self {
|
||||||
|
match value {
|
||||||
|
applogic::GlosaOutput::Searching(glosa_searching) => {
|
||||||
|
Self::GlosaSearching(glosa_searching)
|
||||||
|
}
|
||||||
|
applogic::GlosaOutput::Found(glosa_found) => Self::GlosaFound(glosa_found),
|
||||||
|
applogic::GlosaOutput::Locked(glosa_signal_info) => {
|
||||||
|
Self::GlosaLocked(glosa_signal_info)
|
||||||
|
}
|
||||||
|
applogic::GlosaOutput::Error(err_str) => Self::Error(err_str),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D> Handler<D>
|
||||||
where
|
where
|
||||||
D: DrawTarget<Color = pixelcolor::Rgb565>,
|
D: DrawTarget<Color = pixelcolor::Rgb565>,
|
||||||
{
|
{
|
||||||
let num_signals = data.len();
|
const COLOR_BG: pixelcolor::Rgb565 = pixelcolor::Rgb565::BLACK;
|
||||||
|
const COLOR_FG: pixelcolor::Rgb565 = pixelcolor::Rgb565::WHITE;
|
||||||
|
|
||||||
let text_style =
|
const BK_STYLE: primitives::PrimitiveStyle<pixelcolor::Rgb565> =
|
||||||
mono_font::MonoTextStyle::new(&profont::PROFONT_24_POINT, pixelcolor::Rgb565::WHITE);
|
primitives::PrimitiveStyleBuilder::new()
|
||||||
let blank_style = primitives::PrimitiveStyleBuilder::new()
|
.fill_color(Self::COLOR_BG)
|
||||||
.fill_color(pixelcolor::Rgb565::CSS_GRAY)
|
|
||||||
.build();
|
.build();
|
||||||
let rd_style = primitives::PrimitiveStyleBuilder::new()
|
const RD_STYLE: primitives::PrimitiveStyle<pixelcolor::Rgb565> =
|
||||||
|
primitives::PrimitiveStyleBuilder::new()
|
||||||
.fill_color(pixelcolor::Rgb565::CSS_RED)
|
.fill_color(pixelcolor::Rgb565::CSS_RED)
|
||||||
.build();
|
.build();
|
||||||
let ye_style = primitives::PrimitiveStyleBuilder::new()
|
const YE_STYLE: primitives::PrimitiveStyle<pixelcolor::Rgb565> =
|
||||||
.fill_color(pixelcolor::Rgb565::CSS_YELLOW) // TODO: select nicer color
|
primitives::PrimitiveStyleBuilder::new()
|
||||||
|
.fill_color(pixelcolor::Rgb565::new(31, 55, 0))
|
||||||
.build();
|
.build();
|
||||||
let gn_style = primitives::PrimitiveStyleBuilder::new()
|
const GN_STYLE: primitives::PrimitiveStyle<pixelcolor::Rgb565> =
|
||||||
.fill_color(pixelcolor::Rgb565::CSS_GREEN)
|
primitives::PrimitiveStyleBuilder::new()
|
||||||
|
.fill_color(pixelcolor::Rgb565::new(10, 50, 0))
|
||||||
|
.build();
|
||||||
|
const BLANK_SIGNAL_STYLE: primitives::PrimitiveStyle<pixelcolor::Rgb565> =
|
||||||
|
primitives::PrimitiveStyleBuilder::new()
|
||||||
|
.fill_color(pixelcolor::Rgb565::CSS_GRAY)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// clear maneuver and timing area
|
const DEFAULT_TEXT_STYLE: mono_font::MonoTextStyle<'static, pixelcolor::Rgb565> =
|
||||||
let style = primitives::PrimitiveStyleBuilder::new()
|
mono_font::MonoTextStyle::new(&profont::PROFONT_24_POINT, Self::COLOR_FG);
|
||||||
.fill_color(pixelcolor::Rgb565::BLACK)
|
const SMALL_TEXT_STYLE: mono_font::MonoTextStyle<'static, pixelcolor::Rgb565> =
|
||||||
.build();
|
mono_font::MonoTextStyle::new(&profont::PROFONT_14_POINT, Self::COLOR_FG);
|
||||||
primitives::Rectangle::new(
|
|
||||||
geometry::Point::new(0, 105),
|
|
||||||
geometry::Size::new(WIDTH.into(), 67),
|
|
||||||
)
|
|
||||||
.into_styled(style)
|
|
||||||
.draw(target)?;
|
|
||||||
|
|
||||||
if num_signals == 0 {
|
pub fn new(target: D, width: u16, height: u16) -> Self {
|
||||||
text::Text::with_alignment(
|
Self {
|
||||||
"No upcoming SGs",
|
target,
|
||||||
geometry::Point::new(i32::from(WIDTH) / 2, 130),
|
width,
|
||||||
text_style,
|
height,
|
||||||
text::Alignment::Center,
|
prev_state: ScreenState::Uninitialized,
|
||||||
)
|
hb_state: false,
|
||||||
.draw(target)?;
|
}
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
|
pub fn update(&mut self, state: ScreenState) -> Result<(), D::Error> {
|
||||||
let sig_width = i32::from(WIDTH) / (num_signals as i32);
|
if self.prev_state != state {
|
||||||
|
match &state {
|
||||||
|
ScreenState::Uninitialized | ScreenState::Initial => self.draw_slash_screen()?,
|
||||||
|
ScreenState::GlosaSearching(_glosa_searching) => self.draw_glosa_searching()?,
|
||||||
|
ScreenState::GlosaFound(glosa_found) => self.draw_glosa_found(glosa_found)?,
|
||||||
|
ScreenState::GlosaLocked(glosa_data) => {
|
||||||
|
let prev_glosa_data =
|
||||||
|
if let ScreenState::GlosaLocked(prev_glosa_data) = &self.prev_state {
|
||||||
|
Some(prev_glosa_data.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
for (idx, sig) in data.iter().enumerate() {
|
self.draw_glosa(glosa_data, prev_glosa_data)?;
|
||||||
|
}
|
||||||
|
ScreenState::Error(msg) => self.draw_message(msg)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update indicator/ heartbeat
|
||||||
|
{
|
||||||
|
let update_ind_dia = 10;
|
||||||
|
let circle_x_pos = i32::from(self.width) - update_ind_dia - 10;
|
||||||
|
|
||||||
|
text::Text::with_alignment(
|
||||||
|
"HB",
|
||||||
|
geometry::Point::new(circle_x_pos - 5, 10),
|
||||||
|
Self::SMALL_TEXT_STYLE,
|
||||||
|
text::Alignment::Right,
|
||||||
|
)
|
||||||
|
.draw(&mut self.target)?;
|
||||||
|
|
||||||
|
primitives::Circle::new(
|
||||||
|
geometry::Point::new(circle_x_pos, 2),
|
||||||
|
update_ind_dia.cast_unsigned(),
|
||||||
|
)
|
||||||
|
.into_styled(if self.hb_state {
|
||||||
|
Self::GN_STYLE
|
||||||
|
} else {
|
||||||
|
Self::BK_STYLE
|
||||||
|
})
|
||||||
|
.draw(&mut self.target)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.prev_state = state;
|
||||||
|
self.hb_state = !self.hb_state;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear(&mut self) -> Result<(), D::Error>
|
||||||
|
where
|
||||||
|
D: DrawTarget<Color = pixelcolor::Rgb565>,
|
||||||
|
{
|
||||||
|
primitives::Rectangle::new(
|
||||||
|
geometry::Point::new(0, 0),
|
||||||
|
geometry::Size::new(self.width.into(), self.height.into()),
|
||||||
|
)
|
||||||
|
.into_styled(Self::BK_STYLE)
|
||||||
|
.draw(&mut self.target)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn test_img(&mut self) -> Result<(), D::Error> {
|
||||||
|
mipidsi::TestImage::new().draw(&mut self.target)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn draw_message(&mut self, msg: &str) -> Result<(), D::Error> {
|
||||||
|
// clear whole display and draw text
|
||||||
|
self.clear()?;
|
||||||
|
|
||||||
|
text::Text::with_alignment(
|
||||||
|
msg,
|
||||||
|
geometry::Point::new(i32::from(self.width) / 2, 40),
|
||||||
|
Self::DEFAULT_TEXT_STYLE,
|
||||||
|
text::Alignment::Center,
|
||||||
|
)
|
||||||
|
.draw(&mut self.target)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
fn draw_glosa_searching(&mut self) -> Result<(), D::Error> {
|
||||||
|
// clear whole display and draw text
|
||||||
|
self.clear()?;
|
||||||
|
|
||||||
|
text::Text::with_alignment(
|
||||||
|
"No upcoming SPAT",
|
||||||
|
geometry::Point::new(i32::from(self.width) / 2, 130),
|
||||||
|
Self::DEFAULT_TEXT_STYLE,
|
||||||
|
text::Alignment::Center,
|
||||||
|
)
|
||||||
|
.draw(&mut self.target)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
fn draw_glosa_found(&mut self, data: &applogic::GlosaFound) -> Result<(), D::Error> {
|
||||||
|
// clear whole display and draw text
|
||||||
|
self.clear()?;
|
||||||
|
|
||||||
|
let text = if let Some(appr) = data.approach_id {
|
||||||
|
alloc::format!("Int. {}\nApproach {appr}", data.intersection_id)
|
||||||
|
} else {
|
||||||
|
alloc::format!("Int. {}\nno approach ID available", data.intersection_id)
|
||||||
|
};
|
||||||
|
|
||||||
|
text::Text::with_alignment(
|
||||||
|
&text,
|
||||||
|
geometry::Point::new(i32::from(self.width) / 2, 130),
|
||||||
|
Self::DEFAULT_TEXT_STYLE,
|
||||||
|
text::Alignment::Center,
|
||||||
|
)
|
||||||
|
.draw(&mut self.target)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
fn draw_glosa(
|
||||||
|
&mut self,
|
||||||
|
data: &applogic::GlosaSignalInfo,
|
||||||
|
prev_data: Option<applogic::GlosaSignalInfo>,
|
||||||
|
) -> Result<(), D::Error> {
|
||||||
|
let num_signals = data.signal_groups.len();
|
||||||
|
|
||||||
|
if let Some(prev) = prev_data
|
||||||
|
&& prev.signal_groups.len() != num_signals
|
||||||
|
{
|
||||||
|
// clear everything if number of signals has changed
|
||||||
|
self.clear();
|
||||||
|
} else {
|
||||||
|
// only clear maneuver and timing area otherwise
|
||||||
|
|
||||||
|
primitives::Rectangle::new(
|
||||||
|
geometry::Point::new(0, 105),
|
||||||
|
geometry::Size::new(self.width.into(), 67),
|
||||||
|
)
|
||||||
|
.into_styled(Self::BK_STYLE)
|
||||||
|
.draw(&mut self.target)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw intersection ID
|
||||||
|
text::Text::new(
|
||||||
|
&alloc::format!("#{}", data.intersection_id),
|
||||||
|
geometry::Point::new(15, 10),
|
||||||
|
Self::SMALL_TEXT_STYLE,
|
||||||
|
)
|
||||||
|
.draw(&mut self.target)?;
|
||||||
|
|
||||||
|
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
|
||||||
|
let sig_width = i32::from(self.width) / (num_signals as i32);
|
||||||
|
|
||||||
|
for (idx, sig) in data.signal_groups.iter().enumerate() {
|
||||||
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
|
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
|
||||||
let offset = (idx as i32) * sig_width;
|
let offset = (idx as i32) * sig_width;
|
||||||
|
|
||||||
|
|
@ -95,17 +263,17 @@ where
|
||||||
|
|
||||||
// draw phase color
|
// draw phase color
|
||||||
let style = match sig.phase {
|
let style = match sig.phase {
|
||||||
applogic::SignalPhase::Unknown => blank_style,
|
applogic::SignalPhase::Unknown => Self::BLANK_SIGNAL_STYLE,
|
||||||
applogic::SignalPhase::Red => rd_style,
|
applogic::SignalPhase::Red => Self::RD_STYLE,
|
||||||
applogic::SignalPhase::Green => gn_style,
|
applogic::SignalPhase::Green => Self::GN_STYLE,
|
||||||
applogic::SignalPhase::RedYellow | applogic::SignalPhase::Yellow => ye_style,
|
applogic::SignalPhase::RedYellow | applogic::SignalPhase::Yellow => Self::YE_STYLE,
|
||||||
};
|
};
|
||||||
primitives::Circle::new(
|
primitives::Circle::new(
|
||||||
geometry::Point::new(offset + circle_margin, 10),
|
geometry::Point::new(offset + circle_margin, 15),
|
||||||
circle_dia.cast_unsigned(),
|
circle_dia.cast_unsigned(),
|
||||||
)
|
)
|
||||||
.into_styled(style)
|
.into_styled(style)
|
||||||
.draw(target)?;
|
.draw(&mut self.target)?;
|
||||||
|
|
||||||
// TODO: draw lane type?
|
// TODO: draw lane type?
|
||||||
|
|
||||||
|
|
@ -113,20 +281,20 @@ where
|
||||||
text::Text::with_alignment(
|
text::Text::with_alignment(
|
||||||
&maneuver_to_str(sig.maneuver),
|
&maneuver_to_str(sig.maneuver),
|
||||||
geometry::Point::new(centerline, 130),
|
geometry::Point::new(centerline, 130),
|
||||||
text_style,
|
Self::DEFAULT_TEXT_STYLE,
|
||||||
text::Alignment::Center,
|
text::Alignment::Center,
|
||||||
)
|
)
|
||||||
.draw(target)?;
|
.draw(&mut self.target)?;
|
||||||
|
|
||||||
// draw timing indicator
|
// draw timing indicator
|
||||||
if let Some(end_sec) = sig.end_sec {
|
if let Some(end_sec) = sig.end_sec {
|
||||||
text::Text::with_alignment(
|
text::Text::with_alignment(
|
||||||
&alloc::format!("{end_sec}s"),
|
&alloc::format!("{end_sec}s"),
|
||||||
geometry::Point::new(centerline, 160),
|
geometry::Point::new(centerline, 160),
|
||||||
text_style,
|
Self::DEFAULT_TEXT_STYLE,
|
||||||
text::Alignment::Center,
|
text::Alignment::Center,
|
||||||
)
|
)
|
||||||
.draw(target)?;
|
.draw(&mut self.target)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -134,55 +302,43 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub fn draw_slash_screen<D>(target: &mut D) -> Result<(), D::Error>
|
fn draw_slash_screen(&mut self) -> Result<(), D::Error> {
|
||||||
where
|
let text_style = mono_font::MonoTextStyle::new(&profont::PROFONT_18_POINT, Self::COLOR_FG);
|
||||||
D: DrawTarget<Color = pixelcolor::Rgb565>,
|
|
||||||
{
|
|
||||||
let text_style =
|
|
||||||
mono_font::MonoTextStyle::new(&profont::PROFONT_18_POINT, pixelcolor::Rgb565::WHITE);
|
|
||||||
let blank_style = primitives::PrimitiveStyleBuilder::new()
|
|
||||||
.fill_color(pixelcolor::Rgb565::CSS_GRAY)
|
|
||||||
.build();
|
|
||||||
let rd_style = primitives::PrimitiveStyleBuilder::new()
|
|
||||||
.fill_color(pixelcolor::Rgb565::CSS_RED)
|
|
||||||
.build();
|
|
||||||
let ye_style = primitives::PrimitiveStyleBuilder::new()
|
|
||||||
.fill_color(pixelcolor::Rgb565::CSS_YELLOW) // TODO: select nicer color
|
|
||||||
.build();
|
|
||||||
let gn_style = primitives::PrimitiveStyleBuilder::new()
|
|
||||||
.fill_color(pixelcolor::Rgb565::CSS_GREEN)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
clear(target)?;
|
self.clear()?;
|
||||||
|
|
||||||
let sig_width = i32::from(WIDTH) / 3;
|
let sig_width = i32::from(self.width) / 3;
|
||||||
let circle_dia = 50;
|
let circle_dia = 50;
|
||||||
let circle_margin = (sig_width - circle_dia) / 2;
|
let circle_margin = (sig_width - circle_dia) / 2;
|
||||||
|
|
||||||
for (idx, style) in [rd_style, ye_style, gn_style].iter().enumerate() {
|
for (idx, style) in [Self::RD_STYLE, Self::YE_STYLE, Self::GN_STYLE]
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
|
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
|
||||||
let offset = (idx as i32) * sig_width;
|
let offset = (idx as i32) * sig_width;
|
||||||
|
|
||||||
primitives::Circle::new(
|
primitives::Circle::new(
|
||||||
geometry::Point::new(offset + circle_margin, 10),
|
geometry::Point::new(offset + circle_margin, 15),
|
||||||
circle_dia.cast_unsigned(),
|
circle_dia.cast_unsigned(),
|
||||||
)
|
)
|
||||||
.into_styled(*style)
|
.into_styled(*style)
|
||||||
.draw(target)?;
|
.draw(&mut self.target)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw welcome text
|
// draw welcome text
|
||||||
let centerline = i32::from(WIDTH) / 2;
|
let centerline = i32::from(self.width) / 2;
|
||||||
text::Text::with_alignment(
|
text::Text::with_alignment(
|
||||||
"C-ITS Signal Phase Display\nWaiting for GNSS fix...",
|
"C-ITS Signal Phase Display\nWaiting for GNSS fix...",
|
||||||
geometry::Point::new(centerline, 130),
|
geometry::Point::new(centerline, 130),
|
||||||
text_style,
|
text_style,
|
||||||
text::Alignment::Center,
|
text::Alignment::Center,
|
||||||
)
|
)
|
||||||
.draw(target)?;
|
.draw(&mut self.target)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn maneuver_to_str(value: applogic::v2x::map::Maneuvers) -> alloc::string::String {
|
fn maneuver_to_str(value: applogic::v2x::map::Maneuvers) -> alloc::string::String {
|
||||||
alloc::format!(
|
alloc::format!(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue