diff --git a/Readme.md b/Readme.md index 97d547d..2be427e 100644 --- a/Readme.md +++ b/Readme.md @@ -132,6 +132,7 @@ Additional hardware: The housing is a FreeCAD model at [hardware/housing/c_its-companion-housing.FCStd](./hardware/housing/c_its-companion-housing.FCStd), but the same folder also contains finished 3mf models that can be loaded into a 3D printer slicer. Exports are available for: +- 2mm: no battery (just display standoffs) - 7mm: EREMIT 654060 (6.5x40x60 mm) LiPo battery - 9mm: Some 9x40x60 mm LiPo battery, e.g. LP803860 (2000 mAh) @@ -145,7 +146,10 @@ Images are loaded as TGA assets, but shall not have an alpha channel. You can convert them using [ImageMagick](https://imagemagick.org) like this: ```shell # resize to 25px height, adding a black background to the white symbol and vice versa -magick Sinnbild_Radfahrer_StVO_1992-200px-white.png -resize x25 -background black -alpha background -flatten -alpha off -verbose Sinnbild_Radfahrer_StVO_1992-25px-white.tga -magick Sinnbild_Radfahrer_StVO_1992-200px-black.png -resize x25 -background white -alpha background -flatten -alpha off -verbose Sinnbild_Radfahrer_StVO_1992-25px-black.tga -``` +magick Sinnbild_Radfahrer_StVO_1992-200px-white.png -resize x25 -background black -alpha background -flatten -alpha off -colorspace Gray -verbose Sinnbild_Radfahrer_StVO_1992-25px-white.tga +magick Sinnbild_Radfahrer_StVO_1992-200px-black.png -resize x25 -background white -alpha background -flatten -alpha off -colorspace Gray -verbose Sinnbild_Radfahrer_StVO_1992-25px-black.tga +magick Stationary_Emergency_Vehicle-140x105.svg -resize 70 -background black -alpha background -flatten -alpha off -colorspace Gray -verbose Stationary_Emergency_Vehicle-70px.tga +magick Stationary_Vehicle-140x70.svg -resize 80 -background black -alpha background -flatten -alpha off -colorspace Gray -verbose Stationary_Vehicle-80px.tga +magick Work_Zone-140x95.svg -resize 80 -background black -alpha background -flatten -alpha off -colorspace Gray -verbose Work_Zone-80px.tga +``` diff --git a/graphics/Stationary_Emergency_Vehicle-140x105.svg b/graphics/Stationary_Emergency_Vehicle-140x105.svg new file mode 100644 index 0000000..f5b5aa0 --- /dev/null +++ b/graphics/Stationary_Emergency_Vehicle-140x105.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/graphics/Stationary_Emergency_Vehicle-70px.tga b/graphics/Stationary_Emergency_Vehicle-70px.tga new file mode 100644 index 0000000..3076adb Binary files /dev/null and b/graphics/Stationary_Emergency_Vehicle-70px.tga differ diff --git a/graphics/Stationary_Vehicle-140x70.svg b/graphics/Stationary_Vehicle-140x70.svg new file mode 100644 index 0000000..3ff1cd7 --- /dev/null +++ b/graphics/Stationary_Vehicle-140x70.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/graphics/Stationary_Vehicle-80px.tga b/graphics/Stationary_Vehicle-80px.tga new file mode 100644 index 0000000..475ef75 Binary files /dev/null and b/graphics/Stationary_Vehicle-80px.tga differ diff --git a/graphics/Work_Zone-140x95.svg b/graphics/Work_Zone-140x95.svg new file mode 100644 index 0000000..bbbe6fb --- /dev/null +++ b/graphics/Work_Zone-140x95.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/graphics/Work_Zone-80px.tga b/graphics/Work_Zone-80px.tga new file mode 100644 index 0000000..a397e21 Binary files /dev/null and b/graphics/Work_Zone-80px.tga differ diff --git a/hardware/housing/c_its-companion-housing-bottom-2mm.3mf b/hardware/housing/c_its-companion-housing-bottom-2mm.3mf new file mode 100644 index 0000000..2244f69 Binary files /dev/null and b/hardware/housing/c_its-companion-housing-bottom-2mm.3mf differ diff --git a/hardware/housing/c_its-companion-housing-bottom-7mm.3mf b/hardware/housing/c_its-companion-housing-bottom-7mm.3mf new file mode 100644 index 0000000..aa4c7f9 Binary files /dev/null and b/hardware/housing/c_its-companion-housing-bottom-7mm.3mf differ diff --git a/hardware/housing/c_its-companion-housing-bottom-9mm.3mf b/hardware/housing/c_its-companion-housing-bottom-9mm.3mf new file mode 100644 index 0000000..2ba2eee Binary files /dev/null and b/hardware/housing/c_its-companion-housing-bottom-9mm.3mf differ diff --git a/hardware/housing/c_its-companion-housing-bottom-v1.1-7mm.3mf b/hardware/housing/c_its-companion-housing-bottom-v1.1-7mm.3mf deleted file mode 100644 index 53686ab..0000000 Binary files a/hardware/housing/c_its-companion-housing-bottom-v1.1-7mm.3mf and /dev/null differ diff --git a/hardware/housing/c_its-companion-housing-bottom-v1.1-9mm.3mf b/hardware/housing/c_its-companion-housing-bottom-v1.1-9mm.3mf deleted file mode 100644 index cc7504d..0000000 Binary files a/hardware/housing/c_its-companion-housing-bottom-v1.1-9mm.3mf and /dev/null differ diff --git a/hardware/housing/c_its-companion-housing-lid-v1.1.3mf b/hardware/housing/c_its-companion-housing-lid-v1.1.3mf deleted file mode 100644 index 25b8bc6..0000000 Binary files a/hardware/housing/c_its-companion-housing-lid-v1.1.3mf and /dev/null differ diff --git a/hardware/housing/c_its-companion-housing-lid.3mf b/hardware/housing/c_its-companion-housing-lid.3mf new file mode 100644 index 0000000..2ec16e3 Binary files /dev/null and b/hardware/housing/c_its-companion-housing-lid.3mf differ diff --git a/hardware/housing/c_its-companion-housing.FCStd b/hardware/housing/c_its-companion-housing.FCStd index 9ea4657..64edf0c 100644 Binary files a/hardware/housing/c_its-companion-housing.FCStd and b/hardware/housing/c_its-companion-housing.FCStd differ diff --git a/run-standalone-denm.sh b/run-standalone-denm.sh new file mode 100755 index 0000000..70a0905 --- /dev/null +++ b/run-standalone-denm.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +## Builds common config for standalone device +## -> enable DENM with GNSS and a screen (no GLOSA) + +cargo run --release --no-default-features -F esp,screen,gnss_ubx,denm,ble diff --git a/run-standalone-glosa+denm.sh b/run-standalone-glosa+denm.sh new file mode 100755 index 0000000..fa0f975 --- /dev/null +++ b/run-standalone-glosa+denm.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +## Builds common config for standalone device +## -> enable SPAT/ GLOSA and DENM icons with GNSS and a screen and BLE + +cargo run --release -F denm,ble diff --git a/src/applogic/denm.rs b/src/applogic/denm.rs index 9d46dbb..d4a5516 100644 --- a/src/applogic/denm.rs +++ b/src/applogic/denm.rs @@ -1,13 +1,14 @@ //! C-ITS DENM Evaluation +use c_its_parser::standards::cdd_2_2_1::etsi_its_cdd; use c_its_parser::standards::denm_2_2_1::denm_pdu_description; use log::info; -pub fn handle_denm(etsi: &denm_pdu_description::DENM) { +pub fn handle_denm(etsi: &denm_pdu_description::DENM) -> Option { let mgmt = &etsi.denm.management; if mgmt.termination.is_some() { - return; + return None; } if let (Some(sit), Some(loc)) = (&etsi.denm.situation, &etsi.denm.location) { @@ -38,7 +39,11 @@ pub fn handle_denm(etsi: &denm_pdu_description::DENM) { "DENN: {station_id}/{seq_num} CC {cc}/{scc} at {lat_deg:.3}°N, {lon_deg:.3}°E until {end_time:?}" ); } + + Some(sit.event_type.cc_and_scc.clone()) } else { info!("DENM: One of situation or location container not available"); + + None } } diff --git a/src/ble.rs b/src/ble.rs index 80e36bc..8fd2e65 100644 --- a/src/ble.rs +++ b/src/ble.rs @@ -135,7 +135,7 @@ pub async fn run( info!("BLE name: {name} (short: {short_name})"); info!( - "Starting advertising and GATT service for {}", + "Starting advertising and GATT service for {} as '{name}'/ '{short_name}'", crate::util::get_rx_feature_list().join(",") ); let server = Server::new_with_config(GapConfig::Peripheral(PeripheralConfig { @@ -146,11 +146,8 @@ pub async fn run( let _ = embassy_futures::join::join(ble_task(runner), async { loop { - // run advertisement and set data until a client connects - let a = advertise(&mut peripheral, &server, &short_name); - let b = data_update_task(&server); - match embassy_futures::select::select(a, b).await { - embassy_futures::select::Either::First(Ok(conn)) => { + match advertise(&mut peripheral, &server, &short_name).await { + Ok(conn) => { info!("BLE: New client connected"); // set up tasks when the connection is established to a central, so they don't run when no one is connected. @@ -165,17 +162,15 @@ pub async fn run( let _ = value_ref.replace(false); }); } - embassy_futures::select::Either::First(Err(e)) => { + Err(e) => { panic!("BLE ADV: fatal error: {e:?}"); } - _ => {} } } }) .await; } -/// Queues data for BLE notification (only when client is connected) pub fn update(payload: BleSendable) { critical_section::with(|cs| { // only add to queue when someone is connected @@ -191,32 +186,14 @@ pub fn update(payload: BleSendable) { }); } -/// Queues data for characteristic update (may notify a connected client) -/// -/// This can be used instead of `update()`, but will always write the data to the characteristic even when no client is connected. -/// So a newly connected client will be able to read an up to date value at connection and not just when new data was received. -/// -/// This is useful for (device) information which is not time-critical (like received V2X data). -pub fn set(payload: BleSendable) { - critical_section::with(|cs| { - let queue_rc = TX_DATA.borrow(cs).borrow(); - // unwrap is fine b/c we stored something in it before - let queue = queue_rc.as_ref().unwrap(); - - if queue.push(payload).is_err() { - log::error!("BLE TX queue is full"); - } - }); -} - /// Create an advertiser to use to connect to a BLE Central, and wait for it to connect. async fn advertise<'values, 'server, C: Controller>( peripheral: &mut Peripheral<'values, C, DefaultPacketPool>, server: &'server Server<'values>, - // name in advertisement, max. 8 characters + // name in advertisement, max. 12 characters short_name: &'values str, ) -> Result, BleHostError> { - let mut advertiser_data = [0; 31]; // 37 bytes payload - 6 byte address + let mut advertiser_data = [0; 35]; let service_uuid = Uuid::from(0xc0b7_0000_d4f4_4000_ada8_f99a_02ee_315c_u128) .as_raw() .try_into() @@ -225,7 +202,7 @@ async fn advertise<'values, 'server, C: Controller>( &[ AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED), // 2+ 1 byte AdStructure::ServiceUuids128(&[service_uuid]), // 2+16 byte = 21 - AdStructure::ShortenedLocalName(short_name.as_bytes()), // 2+ 8 byte = 31 + AdStructure::CompleteLocalName(short_name.as_bytes()), // 2+ 12 byte = 35 ], &mut advertiser_data[..], )?; @@ -233,11 +210,7 @@ async fn advertise<'values, 'server, C: Controller>( let advertiser = peripheral .advertise( &trouble_host::advertise::AdvertisementParameters { - primary_phy: PhyKind::Le2M, - secondary_phy: PhyKind::Le2M, - timeout: Some(embassy_time::Duration::from_secs(3)), - interval_min: embassy_time::Duration::from_millis(150), - interval_max: embassy_time::Duration::from_secs(1), + timeout: Some(embassy_time::Duration::from_secs(2)), ..Default::default() }, Advertisement::ConnectableScannableUndirected { @@ -437,33 +410,6 @@ async fn publishing_task(server: &Server<'_>, conn: &GattConnecti } } -async fn data_update_task(server: &Server<'_>) { - loop { - embassy_time::Timer::after(embassy_time::Duration::from_millis(10)).await; - - let mut new_data = None; - critical_section::with(|cs| { - let queue_rc = TX_DATA.borrow(cs).borrow(); - // unwrap is fine b/c we stored something in it before - let queue = queue_rc.as_ref().unwrap(); - - if let Some(data) = queue.pop() { - new_data = Some(data); - } - }); - - #[allow(clippy::single_match)] - match new_data { - Some(BleSendable::BatteryLevel(value)) => { - if let Err(err) = server.battery_service.level.set(server, &value) { - warn!("BLE: failed setting new battery level: {err:?}"); - } - } - _ => {} - } - } -} - async fn send_raw_notify( server: &Server<'_>, conn: &GattConnection<'_, '_, P>, diff --git a/src/main.rs b/src/main.rs index 26782d5..e99012e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -228,12 +228,12 @@ async fn main(spawner: Spawner) -> ! { let controller = ble::make_controller(peripherals.BT); let serial = &mac.as_bytes()[3..6]; let name = alloc::format!( - "C-ITS ESP {:02x}:{:02x}:{:02x}", + "ESP32 C-ITS {:02x}{:02x}{:02x}", serial[0], serial[1], serial[2] ); - let short_name = alloc::format!("V2X {:02x}{:02x}", serial[1], serial[2]); // max 8 chars! + let short_name = alloc::format!("V2X {:02x}{:02x}{:02x}", serial[0], serial[1], serial[2]); // max 8 chars! -> 10 chars spawner.spawn(ble::run(controller, name, short_name).expect("Failed to spawn BLE task")); } @@ -280,10 +280,7 @@ async fn main(spawner: Spawner) -> ! { info!("GNSS task started, waiting for GNSS fix..."); // Setup main loop - let (bat_percent, bat_millivolt) = battery.read(); - log::info!("Battery: {bat_millivolt} mV, {bat_percent} %"); - #[cfg(feature = "ble")] - ble::set(ble::BleSendable::BatteryLevel(bat_percent)); + info!("Battery: {} %", battery.read_percent()); let mut last_bat_update = esp_hal::time::Instant::now(); #[cfg(any(feature = "cam_tx", feature = "uart"))] @@ -403,7 +400,16 @@ async fn main(spawner: Spawner) -> ! { } // update GNSS HB on screen - let _ = screen.update_gnss(pvt); + #[cfg(feature = "screen")] + { + let _ = screen.update_gnss(pvt); + + // revert to waiting for DENMs, if SPAT is not enabled + #[cfg(all(not(feature = "spat"), feature = "denm"))] + let _ = screen + .update(screen::ScreenState::DenmWaiting()) + .inspect_err(log_screen_error); + } } ublox::proto27::PacketRef::AckNak(nak) => { error!("UBX: {nak:?}"); @@ -593,7 +599,15 @@ async fn main(spawner: Spawner) -> ! { transport: _, ref etsi, } => { - applogic::denm::handle_denm(etsi); + match applogic::denm::handle_denm(etsi) { + #[cfg(feature = "screen")] + Some(cc) => { + let _ = screen + .update(screen::ScreenState::DenmWarning(cc)) + .inspect_err(log_screen_error); + } + _ => {} + } // Send data to BLE thread #[cfg(feature = "ble")] @@ -631,11 +645,11 @@ async fn main(spawner: Spawner) -> ! { let cpu_now = esp_hal::time::Instant::now(); if last_bat_update + BAT_UPDATE_INTERVAL < cpu_now { - let (bat_percent, bat_millivolt) = battery.read(); - log::info!("Battery: {bat_millivolt} mV, {bat_percent} %"); + let bat_percent = battery.read_percent(); + info!("Battery: {bat_percent} %"); #[cfg(feature = "ble")] - ble::set(ble::BleSendable::BatteryLevel(bat_percent)); + ble::update(ble::BleSendable::BatteryLevel(bat_percent)); last_bat_update = cpu_now; } diff --git a/src/screen.rs b/src/screen.rs index 32bcfc7..4f9d01d 100644 --- a/src/screen.rs +++ b/src/screen.rs @@ -1,11 +1,16 @@ //! Screen Output +#[cfg(feature = "denm")] +use c_its_parser::standards::cdd_2_2_1; use embedded_graphics::prelude::*; use embedded_graphics::{geometry, mono_font, pixelcolor, primitives, text}; #[cfg(feature = "spat")] use crate::applogic; +#[cfg(feature = "denm")] +const DENM_MIN_DISPLAY_TIME: esp_hal::time::Duration = esp_hal::time::Duration::from_secs(3); + pub struct DisplaySize { width: u16, height: u16, @@ -24,10 +29,20 @@ where prev_state: ScreenState, hb_state: bool, gnss_hb_state: bool, + #[cfg(feature = "denm")] + denm_display_start_time: esp_hal::time::Instant, // assets #[cfg(feature = "spat")] - bike_image: tinytga::Tga<'static, pixelcolor::Rgb565>, + bike_image: tinytga::Tga<'static, pixelcolor::Rgb565>, // 45x25px + // #[cfg(feature = "denm")] + // warn_icon: tinytga::Tga<'static, pixelcolor::Rgb565>, // 200x153 px + #[cfg(feature = "denm")] + sv_insert: tinytga::Tga<'static, pixelcolor::Rgb565>, // 80px wide (original 140x70) + #[cfg(feature = "denm")] + ev_insert: tinytga::Tga<'static, pixelcolor::Rgb565>, // 60px wide (original 140x105) + #[cfg(feature = "denm")] + rww_insert: tinytga::Tga<'static, pixelcolor::Rgb565>, // 80px wide (original 140x95) } #[derive(Debug, Default, PartialEq, Eq)] @@ -43,6 +58,10 @@ pub enum ScreenState { GlosaFound(applogic::GlosaFound), #[cfg(feature = "spat")] GlosaLocked(applogic::GlosaSignalInfo), + #[cfg(all(not(feature = "spat"), feature = "denm"))] + DenmWaiting(), + #[cfg(feature = "denm")] + DenmWarning(cdd_2_2_1::etsi_its_cdd::CauseCodeChoice), } #[cfg(feature = "spat")] @@ -101,9 +120,23 @@ where pub fn new(target: D, display_size: DisplaySize) -> Self { #[cfg(feature = "spat")] - let data = include_bytes!("../graphics/Sinnbild_Radfahrer_StVO_1992-25px-white.tga"); - #[cfg(feature = "spat")] - let bike_image = tinytga::Tga::from_slice(data).expect("Failed to parse TGA image"); + let bike_image = tinytga::Tga::from_slice(include_bytes!( + "../graphics/Sinnbild_Radfahrer_StVO_1992-25px-white.tga" + )) + .expect("Failed to parse TGA image"); + + #[cfg(feature = "denm")] + let sv_insert = + tinytga::Tga::from_slice(include_bytes!("../graphics/Stationary_Vehicle-80px.tga")) + .expect("Failed to parse TGA image"); + #[cfg(feature = "denm")] + let ev_insert = tinytga::Tga::from_slice(include_bytes!( + "../graphics/Stationary_Emergency_Vehicle-70px.tga" + )) + .expect("Failed to parse TGA image"); + #[cfg(feature = "denm")] + let rww_insert = tinytga::Tga::from_slice(include_bytes!("../graphics/Work_Zone-80px.tga")) + .expect("Failed to parse TGA image"); Self { target, @@ -111,15 +144,53 @@ where prev_state: ScreenState::Uninitialized, hb_state: false, gnss_hb_state: false, + #[cfg(feature = "denm")] + denm_display_start_time: esp_hal::time::Instant::EPOCH, #[cfg(feature = "spat")] bike_image, + // #[cfg(feature = "denm")] + // warn_icon, + #[cfg(feature = "denm")] + sv_insert, + #[cfg(feature = "denm")] + ev_insert, + #[cfg(feature = "denm")] + rww_insert, } } pub fn update(&mut self, state: ScreenState) -> Result<(), D::Error> { + #[cfg(feature = "denm")] + let now = esp_hal::time::Instant::now(); + + #[cfg(feature = "denm")] + // when a DENM was shown before, make sure it's shown for a minimum time + if let ScreenState::DenmWarning(_) = self.prev_state { + use core::ops::Sub; + + // update DENM display, if there are updates + if let ScreenState::DenmWarning(cause_code_choice) = &state { + // TODO: find a way to immediately turn off the DENM display when the DENM is over, but keep it for a certain time initially + self.denm_display_start_time = now; + + if self.prev_state != state { + self.draw_denm_warning(cause_code_choice)?; + self.prev_state = state; + + return Ok(()); + } + } + + // otherwise early return when timer isn't expired yet + if now.sub(self.denm_display_start_time) < DENM_MIN_DISPLAY_TIME { + return Ok(()); + } + } + if self.prev_state != state { match &state { ScreenState::Uninitialized | ScreenState::Initial => self.draw_slash_screen()?, + #[cfg(feature = "spat")] ScreenState::GlosaSearching(_glosa_searching) => self.draw_glosa_searching()?, #[cfg(feature = "spat")] @@ -136,10 +207,18 @@ where self.draw_glosa(glosa_data, prev_glosa_data)?; } ScreenState::Error(msg) => self.draw_message(msg)?, + #[cfg(all(not(feature = "spat"), feature = "denm"))] + ScreenState::DenmWaiting() => self.draw_denm_waiting()?, + #[cfg(feature = "denm")] + ScreenState::DenmWarning(cause_code_choice) => { + self.denm_display_start_time = now; + self.draw_denm_warning(cause_code_choice)?; + } } } // Update indicator/ heartbeat + #[cfg(feature = "spat")] { let update_ind_dia = 10; let circle_x_pos = @@ -477,6 +556,100 @@ where Ok(()) } + #[cfg(all(not(feature = "spat"), feature = "denm"))] + fn draw_denm_waiting(&mut self) -> Result<(), D::Error> { + // clear whole display and draw text + self.clear()?; + + text::Text::with_alignment( + "consider\ninnovation", + geometry::Point::new(i32::from(self.size.width) / 2, 40 + self.size.offset_top), + Self::MID_TEXT_STYLE, + text::Alignment::Center, + ) + .draw(&mut self.target)?; + + text::Text::with_alignment( + "Waiting\nfor DENM...", + geometry::Point::new(i32::from(self.size.width) / 2, 130 + self.size.offset_top), + Self::DEFAULT_TEXT_STYLE, + text::Alignment::Center, + ) + .draw(&mut self.target)?; + + Ok(()) + } + + #[cfg(feature = "denm")] + pub fn draw_denm_warning( + &mut self, + cc: &cdd_2_2_1::etsi_its_cdd::CauseCodeChoice, + ) -> Result<(), D::Error> { + // clear whole display and draw image + self.clear()?; + + let centerline = i32::from(self.size.width) / 2; + let sign_top = self.size.offset_top + 40; + + // build triangle shape + { + let point1 = geometry::Point::new(centerline, sign_top); + let point2 = geometry::Point::new(centerline - 100, sign_top + 150); + let point3 = geometry::Point::new(centerline + 100, sign_top + 150); + let mut line_style = + primitives::PrimitiveStyle::with_stroke(pixelcolor::Rgb565::CSS_RED, 10); + line_style.fill_color = Some(pixelcolor::Rgb565::WHITE); + + primitives::Triangle::new(point1, point2, point3) + .into_styled(line_style) + .draw(&mut self.target)?; + } + + // add icon inside the triangle + match cc { + cdd_2_2_1::etsi_its_cdd::CauseCodeChoice::slowVehicle26(_) + | cdd_2_2_1::etsi_its_cdd::CauseCodeChoice::stationaryVehicle94(_) => { + embedded_graphics::image::Image::new( + &self.sv_insert, + geometry::Point::new(centerline - (80 / 2), sign_top + 90), + ) + .draw(&mut self.target)?; + } + + cdd_2_2_1::etsi_its_cdd::CauseCodeChoice::rescueAndRecoveryWorkInProgress15(_) + | cdd_2_2_1::etsi_its_cdd::CauseCodeChoice::emergencyVehicleApproaching95(_) => { + embedded_graphics::image::Image::new( + &self.ev_insert, + geometry::Point::new(centerline - (70 / 2), sign_top + 85), + ) + .draw(&mut self.target)?; + } + + cdd_2_2_1::etsi_its_cdd::CauseCodeChoice::roadworks3(_) => { + embedded_graphics::image::Image::new( + &self.rww_insert, + geometry::Point::new(centerline - (80 / 2), sign_top + 85), + ) + .draw(&mut self.target)?; + } + _ => {} + } + + let (cc, scc) = cc.to_u8_tuple(); + text::Text::with_alignment( + &alloc::format!("DENM CC {cc}/{scc}"), + geometry::Point::new( + i32::from(self.size.width) / 2, + sign_top + 150 + (10 / 2) + 30, + ), + Self::MID_TEXT_STYLE, + text::Alignment::Center, + ) + .draw(&mut self.target)?; + + Ok(()) + } + #[allow(unused)] fn draw_slash_screen(&mut self) -> Result<(), D::Error> { self.clear_all()?; @@ -519,6 +692,13 @@ where ) .draw(&mut self.target)?; } else { + text::Text::with_alignment( + "consider\ninnovation", + geometry::Point::new(centerline, 40 + self.size.offset_top), + Self::MID_TEXT_STYLE, + text::Alignment::Center, + ) + .draw(&mut self.target)?; text::Text::with_alignment( "C-ITS Companion", geometry::Point::new(centerline, 130 + self.size.offset_top), diff --git a/src/xiao.rs b/src/xiao.rs index 8e6d8be..6cbff88 100644 --- a/src/xiao.rs +++ b/src/xiao.rs @@ -44,7 +44,7 @@ where match self.adc.read_oneshot(&mut self.adc_pin) { Ok(value) => break value, Err(err) => { - debug!("Failed to read from ADC: {err:?}"); + debug!("Failed to read from ADC: {err:?}") } } }; @@ -58,19 +58,10 @@ where } /// Returns estimated battery charge state in percent - #[allow(unused)] pub fn read_percent(&mut self) -> u8 { Self::millivolts_to_percent(self.read_millivolts()) } - /// Returns estimated battery charge state in percent and millivolts - pub fn read(&mut self) -> (u8, u16) { - let millivolt = self.read_millivolts(); - let percent = Self::millivolts_to_percent(millivolt); - - (percent, millivolt) - } - /// Converts single cell LiPo battery voltage to charge state in percent /// /// Note: The implementation is a drastically simplified linear estimation.