diff --git a/Readme.md b/Readme.md index 2be427e..97d547d 100644 --- a/Readme.md +++ b/Readme.md @@ -132,7 +132,6 @@ 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) @@ -146,10 +145,7 @@ 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 -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 +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 ``` + diff --git a/graphics/Stationary_Emergency_Vehicle-140x105.svg b/graphics/Stationary_Emergency_Vehicle-140x105.svg deleted file mode 100644 index f5b5aa0..0000000 --- a/graphics/Stationary_Emergency_Vehicle-140x105.svg +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/graphics/Stationary_Emergency_Vehicle-70px.tga b/graphics/Stationary_Emergency_Vehicle-70px.tga deleted file mode 100644 index 3076adb..0000000 Binary files a/graphics/Stationary_Emergency_Vehicle-70px.tga and /dev/null differ diff --git a/graphics/Stationary_Vehicle-140x70.svg b/graphics/Stationary_Vehicle-140x70.svg deleted file mode 100644 index 3ff1cd7..0000000 --- a/graphics/Stationary_Vehicle-140x70.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/graphics/Stationary_Vehicle-80px.tga b/graphics/Stationary_Vehicle-80px.tga deleted file mode 100644 index 475ef75..0000000 Binary files a/graphics/Stationary_Vehicle-80px.tga and /dev/null differ diff --git a/graphics/Work_Zone-140x95.svg b/graphics/Work_Zone-140x95.svg deleted file mode 100644 index bbbe6fb..0000000 --- a/graphics/Work_Zone-140x95.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/graphics/Work_Zone-80px.tga b/graphics/Work_Zone-80px.tga deleted file mode 100644 index a397e21..0000000 Binary files a/graphics/Work_Zone-80px.tga and /dev/null differ diff --git a/hardware/housing/c_its-companion-housing-bottom-2mm.3mf b/hardware/housing/c_its-companion-housing-bottom-2mm.3mf deleted file mode 100644 index 2244f69..0000000 Binary files a/hardware/housing/c_its-companion-housing-bottom-2mm.3mf and /dev/null differ diff --git a/hardware/housing/c_its-companion-housing-bottom-7mm.3mf b/hardware/housing/c_its-companion-housing-bottom-7mm.3mf deleted file mode 100644 index aa4c7f9..0000000 Binary files a/hardware/housing/c_its-companion-housing-bottom-7mm.3mf and /dev/null differ diff --git a/hardware/housing/c_its-companion-housing-bottom-9mm.3mf b/hardware/housing/c_its-companion-housing-bottom-9mm.3mf deleted file mode 100644 index 2ba2eee..0000000 Binary files a/hardware/housing/c_its-companion-housing-bottom-9mm.3mf and /dev/null 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 new file mode 100644 index 0000000..53686ab Binary files /dev/null and b/hardware/housing/c_its-companion-housing-bottom-v1.1-7mm.3mf 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 new file mode 100644 index 0000000..cc7504d Binary files /dev/null and b/hardware/housing/c_its-companion-housing-bottom-v1.1-9mm.3mf 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 new file mode 100644 index 0000000..25b8bc6 Binary files /dev/null and b/hardware/housing/c_its-companion-housing-lid-v1.1.3mf differ diff --git a/hardware/housing/c_its-companion-housing-lid.3mf b/hardware/housing/c_its-companion-housing-lid.3mf deleted file mode 100644 index 2ec16e3..0000000 Binary files a/hardware/housing/c_its-companion-housing-lid.3mf and /dev/null differ diff --git a/hardware/housing/c_its-companion-housing.FCStd b/hardware/housing/c_its-companion-housing.FCStd index 64edf0c..9ea4657 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 deleted file mode 100755 index 70a0905..0000000 --- a/run-standalone-denm.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/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 deleted file mode 100755 index fa0f975..0000000 --- a/run-standalone-glosa+denm.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/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 d4a5516..9d46dbb 100644 --- a/src/applogic/denm.rs +++ b/src/applogic/denm.rs @@ -1,14 +1,13 @@ //! 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) -> Option { +pub fn handle_denm(etsi: &denm_pdu_description::DENM) { let mgmt = &etsi.denm.management; if mgmt.termination.is_some() { - return None; + return; } if let (Some(sit), Some(loc)) = (&etsi.denm.situation, &etsi.denm.location) { @@ -39,11 +38,7 @@ pub fn handle_denm(etsi: &denm_pdu_description::DENM) -> Option { + // 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)) => { 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. @@ -162,15 +165,17 @@ pub async fn run( let _ = value_ref.replace(false); }); } - Err(e) => { + embassy_futures::select::Either::First(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 @@ -186,14 +191,32 @@ 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. 12 characters + // name in advertisement, max. 8 characters short_name: &'values str, ) -> Result, BleHostError> { - let mut advertiser_data = [0; 35]; + let mut advertiser_data = [0; 31]; // 37 bytes payload - 6 byte address let service_uuid = Uuid::from(0xc0b7_0000_d4f4_4000_ada8_f99a_02ee_315c_u128) .as_raw() .try_into() @@ -202,7 +225,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::CompleteLocalName(short_name.as_bytes()), // 2+ 12 byte = 35 + AdStructure::ShortenedLocalName(short_name.as_bytes()), // 2+ 8 byte = 31 ], &mut advertiser_data[..], )?; @@ -210,7 +233,11 @@ async fn advertise<'values, 'server, C: Controller>( let advertiser = peripheral .advertise( &trouble_host::advertise::AdvertisementParameters { - timeout: Some(embassy_time::Duration::from_secs(2)), + 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), ..Default::default() }, Advertisement::ConnectableScannableUndirected { @@ -410,6 +437,33 @@ 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 e99012e..26782d5 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!( - "ESP32 C-ITS {:02x}{:02x}{:02x}", + "C-ITS ESP {:02x}:{:02x}:{:02x}", serial[0], serial[1], serial[2] ); - let short_name = alloc::format!("V2X {:02x}{:02x}{:02x}", serial[0], serial[1], serial[2]); // max 8 chars! -> 10 chars + let short_name = alloc::format!("V2X {:02x}{:02x}", serial[1], serial[2]); // max 8 chars! spawner.spawn(ble::run(controller, name, short_name).expect("Failed to spawn BLE task")); } @@ -280,7 +280,10 @@ async fn main(spawner: Spawner) -> ! { info!("GNSS task started, waiting for GNSS fix..."); // Setup main loop - info!("Battery: {} %", battery.read_percent()); + 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)); let mut last_bat_update = esp_hal::time::Instant::now(); #[cfg(any(feature = "cam_tx", feature = "uart"))] @@ -400,16 +403,7 @@ async fn main(spawner: Spawner) -> ! { } // update GNSS HB on screen - #[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); - } + let _ = screen.update_gnss(pvt); } ublox::proto27::PacketRef::AckNak(nak) => { error!("UBX: {nak:?}"); @@ -599,15 +593,7 @@ async fn main(spawner: Spawner) -> ! { transport: _, ref etsi, } => { - match applogic::denm::handle_denm(etsi) { - #[cfg(feature = "screen")] - Some(cc) => { - let _ = screen - .update(screen::ScreenState::DenmWarning(cc)) - .inspect_err(log_screen_error); - } - _ => {} - } + applogic::denm::handle_denm(etsi); // Send data to BLE thread #[cfg(feature = "ble")] @@ -645,11 +631,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 = battery.read_percent(); - info!("Battery: {bat_percent} %"); + let (bat_percent, bat_millivolt) = battery.read(); + log::info!("Battery: {bat_millivolt} mV, {bat_percent} %"); #[cfg(feature = "ble")] - ble::update(ble::BleSendable::BatteryLevel(bat_percent)); + ble::set(ble::BleSendable::BatteryLevel(bat_percent)); last_bat_update = cpu_now; } diff --git a/src/screen.rs b/src/screen.rs index 4f9d01d..32bcfc7 100644 --- a/src/screen.rs +++ b/src/screen.rs @@ -1,16 +1,11 @@ //! 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, @@ -29,20 +24,10 @@ 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>, // 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) + bike_image: tinytga::Tga<'static, pixelcolor::Rgb565>, } #[derive(Debug, Default, PartialEq, Eq)] @@ -58,10 +43,6 @@ 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")] @@ -120,23 +101,9 @@ where pub fn new(target: D, display_size: DisplaySize) -> Self { #[cfg(feature = "spat")] - 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"); + 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"); Self { target, @@ -144,53 +111,15 @@ 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")] @@ -207,18 +136,10 @@ 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 = @@ -556,100 +477,6 @@ 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()?; @@ -692,13 +519,6 @@ 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 6cbff88..8e6d8be 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,10 +58,19 @@ 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.