From 166a01b2020ccdf731f295b36f4db6c7b0005e8f Mon Sep 17 00:00:00 2001 From: Jannik Beyerstedt Date: Wed, 17 Jun 2026 00:12:43 +0200 Subject: [PATCH 01/27] WIP map: De-duplicate lanes on layered MAPEMs TODO: Actually implement it --- src/applogic/v2x/map.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/applogic/v2x/map.rs b/src/applogic/v2x/map.rs index bac8a69..6e5718c 100644 --- a/src/applogic/v2x/map.rs +++ b/src/applogic/v2x/map.rs @@ -138,6 +138,8 @@ impl Intersection { self.signal_groups = Self::collect_signal_groups(&self.lanes); self.center = Self::calculate_center(&self.ref_point, &self.lanes); self.is_complete = true; + + // TODO: de-duplicate lanes! } } From baaee5f6ffd1d51cb80b06eca6acbc830dabebec Mon Sep 17 00:00:00 2001 From: Jannik Beyerstedt Date: Wed, 22 Jul 2026 21:20:48 +0200 Subject: [PATCH 02/27] logging: Only print to jtag-serial Otherwise esp-println will use UART0 when no USB-JTAG/Serial is connected. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ac69615..f322c5b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,7 +72,7 @@ esp-backtrace = { version = "0.19.0", optional = true, features = [ "panic-handler", "println", ] } -esp-println = { version = "0.17.0", optional = true, features = ["esp32c5", "log-04"] } +esp-println = { version = "0.17.0", optional = true, default-features = false, features = ["colors", "critical-section", "jtag-serial", "esp32c5", "log-04"] } esp-radio = { version = "0.18.0", optional = true, features = [ "esp-alloc", "esp32c5", From 7668c9bb7bd5151879f5e3ff181eebd076f076c3 Mon Sep 17 00:00:00 2001 From: Jannik Beyerstedt Date: Fri, 24 Jul 2026 11:51:00 +0200 Subject: [PATCH 03/27] proto/RawMsgTx: Add station type for source position vector --- docs/c_its-messages.proto | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/c_its-messages.proto b/docs/c_its-messages.proto index c2ca54e..9848910 100644 --- a/docs/c_its-messages.proto +++ b/docs/c_its-messages.proto @@ -70,6 +70,8 @@ message RawMsgTx { required GNTransport gnTransport = 3; required GNArea gnArea = 4; required uint32 hopLimit = 5; + + optional uint32 stationType = 6; // for source position vector } // ------------------------------ From 1d4e82335d02e6b513933c6defd783a76898314a Mon Sep 17 00:00:00 2001 From: Jannik Beyerstedt Date: Fri, 24 Jul 2026 11:39:35 +0200 Subject: [PATCH 04/27] feat(docs): Explain supported feature combinations, add test script --- Readme.md | 9 ++++-- feat-permut.sh | 69 +++++++++++++++++++++++++++++++++++++++++++++ src/applogic/mod.rs | 12 ++++---- 3 files changed, 82 insertions(+), 8 deletions(-) create mode 100755 feat-permut.sh diff --git a/Readme.md b/Readme.md index 9cbf097..8659877 100644 --- a/Readme.md +++ b/Readme.md @@ -52,14 +52,19 @@ rustup component add --toolchain nightly rustfmt ### Build and Flash In default configuration only signal phases (SPAT) data is displayed for the upcoming intersection. -Additional features are available though feature flags: +Additional features are available through feature flags: - `esp`: Run on ESP (required as feature flag to enable unit tests on host) - `std`: Run on system with `std` library -- `spat`: Enable signal phase (and intersection map) data +- `spat`: Enable signal phase (and intersection map) data +- `spat_debug`: Enable additional debug information for the SPAT/ GLOSA use case - `denm`: Enable DENMs - `cam`: Enable CAMs +Officially supported feature combinations are: +- `esp,spat,gnss,screen` (default features) +- To be implemented: No GNSS and screen when BLE/ UART interface is implemented + Connect your ESP32-C5 and run: ``` diff --git a/feat-permut.sh b/feat-permut.sh new file mode 100755 index 0000000..cd20431 --- /dev/null +++ b/feat-permut.sh @@ -0,0 +1,69 @@ +#!/bin/bash +## Build/ Test/ Clippy all supported feature permutations +set -eu + +mode=${1:-help} +wasm_target="--chrome --headless" +extended=${2:-false} + + +features_base=( + #cam,denm,uart + spat,gnss,screen +) +features_more=( + spat,gnss,screen,cam,denm +) +features_test=( + spat + spat,cam,denm +) + + +runBuild () { + for feat in "${features[@]}"; do + echo "cargo build for $feat ..." + cargo build --release --no-default-features -F esp,$feat + done +} + +runTest () { + for feat in "${features_test[@]}"; do + echo "cargo test for $feat ..." + cargo test --target host-tuple --bin test --no-default-features -F std,$feat + done +} + +runClippy () { + for feat in "${features[@]}"; do + echo "clippy for $feat ..." + cargo clippy --no-default-features -F esp,$feat -- -Wclippy::pedantic + done +} + +if [ $extended == "--more" ]; then + echo "running with extended feature list" + features=( "${features_base[@]}" "${features_more[@]}" ) +else + echo "running standard feature list" + features=( "${features_base[@]}" ) +fi + +case $mode in +build) + runBuild + ;; +clippy) + runClippy + ;; +test) + runTest + ;; +all) + runBuild + runClippy + runTest + ;; +*) + echo "Usage: $0 [build | clippy | test | all] [--more]" +esac diff --git a/src/applogic/mod.rs b/src/applogic/mod.rs index bf3252a..98d100f 100644 --- a/src/applogic/mod.rs +++ b/src/applogic/mod.rs @@ -6,7 +6,7 @@ use alloc::rc::Rc; use alloc::vec::Vec; use log::info; -#[cfg(any(feature = "gnss", feature = "spat"))] +#[cfg(feature = "spat")] use log::warn; #[cfg(feature = "spat")] use log::{debug, error}; @@ -688,13 +688,13 @@ mod tests { let own_pos_passed = geo_types::Point::new(9.977217, 53.5560602); let own_heading = 90.; - state.heading_deg = Some(own_heading); + state.pos.heading_deg = Some(own_heading); - state.position = own_pos_infront; + state.pos.position = own_pos_infront; state.run_glosa(); assert!(matches!(state.glosa_state, GlosaState::Idle)); - state.position = own_pos_inside; + state.pos.position = own_pos_inside; state.run_glosa(); assert!(matches!(state.glosa_state, GlosaState::Locked(_))); if let GlosaState::Locked(info) = &state.glosa_state { @@ -702,7 +702,7 @@ mod tests { assert!(info.lane_id == 1 || info.lane_id == 2); } - state.position = own_pos_inside; + state.pos.position = own_pos_inside; state.run_glosa(); assert!(matches!(state.glosa_state, GlosaState::Locked(_))); if let GlosaState::Locked(info) = &state.glosa_state { @@ -710,7 +710,7 @@ mod tests { assert!(info.lane_id == 1 || info.lane_id == 2); } - state.position = own_pos_passed; + state.pos.position = own_pos_passed; state.run_glosa(); assert!(matches!(state.glosa_state, GlosaState::Idle)); } From 2379a7eefc69d2342b5041b7d35ee371864d4a71 Mon Sep 17 00:00:00 2001 From: Jannik Beyerstedt Date: Fri, 3 Jul 2026 13:38:54 +0200 Subject: [PATCH 05/27] Enable V2X transmission, add optional stand-alone CAM transmission --- Cargo.lock | 22 +++-- Cargo.toml | 5 +- Readme.md | 7 ++ feat-permut.sh | 2 + src/applogic/cam_tx.rs | 197 +++++++++++++++++++++++++++++++++++++++++ src/applogic/mod.rs | 2 + src/main.rs | 68 +++++++++++++- src/v2x_tx/gn.rs | 94 ++++++++++++++++++++ src/v2x_tx/mod.rs | 160 +++++++++++++++++++++++++++++++++ 9 files changed, 543 insertions(+), 14 deletions(-) create mode 100644 src/applogic/cam_tx.rs create mode 100644 src/v2x_tx/gn.rs create mode 100644 src/v2x_tx/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 359840b..3fba778 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,6 +73,12 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "arbitrary-int" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993a810118f8f37e9c4411c86f1c4c940a09a7ab34b7bf2d88d06f50c553fab7" + [[package]] name = "arrayvec" version = "0.7.6" @@ -186,9 +192,9 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "c-its-parser" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9bd739e1f76c017fc9fc6c21b9ff2aee4536bef357075d454e387e94a4d6287" +checksum = "cd4db66f495280ec88eef8712dd9ab7d1aacc590f62485e3e03a89b3ce4f8b14" dependencies = [ "chrono", "etherparse", @@ -1239,9 +1245,9 @@ dependencies = [ [[package]] name = "etherparse" -version = "0.20.1" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ac016aaf11dfe643edcd088a166234bfcb72e7f06691abfcf21e9af524037f4" +checksum = "17304d06addb3283cdc4bd528e42dd95e73c8ee2d6492ffce415e93660885449" dependencies = [ "arrayvec", ] @@ -1371,13 +1377,11 @@ dependencies = [ [[package]] name = "geonetworking" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f6d36111f5057d3be7eb4e312a07edac4737fc791e4b0a73d812c6f710161e7" +checksum = "6da6fde770112587d5f5f7af439546e35bd35aa77a9e3e47e9968056783fe50d" dependencies = [ - "bitvec", - "bitvec-nom2", - "bytes", + "arbitrary-int", "nom", "num", "num-traits", diff --git a/Cargo.toml b/Cargo.toml index f322c5b..834f164 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,8 @@ std = ["c-its-parser/std"] gnss = ["dep:embassy_gps"] # echo CAM data on serial cam = ["c-its-parser/cam"] +# send CAM autonomously +cam_tx = ["c-its-parser/cam", "gnss"] # echo DENM events on serial and BLE denm = ["c-its-parser/denm"] # evaluate SPAT information @@ -85,7 +87,8 @@ esp-radio = { version = "0.18.0", optional = true, features = [ embassy_gps = { version = "0.1.0", optional = true, default-features = false, features = ["esp", "log-04"], git = "https://github.com/jbeyerstedt/embassy_gps.git", branch = "feat/rework-gpsfix" } embedded-hal-bus = { version = "0.3.0", optional = true } embedded-graphics = { version = "0.8.2", optional = true } -c-its-parser = { version = "2.3.0", default-features = false, features = [ +# needs newer GN with removed bitvec dependency +c-its-parser = { version = "2.4.0", default-features = false, features = [ "time", "geo", "libm", diff --git a/Readme.md b/Readme.md index 8659877..281f995 100644 --- a/Readme.md +++ b/Readme.md @@ -28,6 +28,10 @@ Shall "publish" the most relevant information of a DENMs via BLE ### CAM Just prints a line for each received CAM. +### CAM-TX +Generates and transmits a CAM when a GNSS update was received. +Depends on the `gnss` feature. + ## Usage @@ -60,9 +64,12 @@ Additional features are available through feature flags: - `spat_debug`: Enable additional debug information for the SPAT/ GLOSA use case - `denm`: Enable DENMs - `cam`: Enable CAMs +- `cam_tx`: Enable CAM generation and transmission Officially supported feature combinations are: - `esp,spat,gnss,screen` (default features) +- `esp,spat,gnss,screen,cam_tx` +- `esp,spat,gnss,screen,cam_tx,cam,denm` - To be implemented: No GNSS and screen when BLE/ UART interface is implemented Connect your ESP32-C5 and run: diff --git a/feat-permut.sh b/feat-permut.sh index cd20431..a40a8ca 100755 --- a/feat-permut.sh +++ b/feat-permut.sh @@ -10,9 +10,11 @@ extended=${2:-false} features_base=( #cam,denm,uart spat,gnss,screen + spat,gnss,screen,cam_tx ) features_more=( spat,gnss,screen,cam,denm + spat,gnss,screen,cam_tx,cam,denm ) features_test=( spat diff --git a/src/applogic/cam_tx.rs b/src/applogic/cam_tx.rs new file mode 100644 index 0000000..7de554e --- /dev/null +++ b/src/applogic/cam_tx.rs @@ -0,0 +1,197 @@ +//! C-ITS CAM Transmission + +use alloc::vec::Vec; + +use c_its_parser::gn as geonetworking; +use c_its_parser::standards::cam_1_4_1::cam_pdu_descriptions; +use c_its_parser::standards::cdd_1_3_1_1::its_container; +use c_its_parser::standards::extensions; + +#[derive(Debug)] +pub struct CamState { + station_id: u32, + station_type: its_container::StationType, + + vehicle_length_dm: u16, // vehicle length is in 10cm steps + vehicle_width_dm: u8, // vehicle width is in 10cm steps + + time: chrono::DateTime, + pos_state: super::PositionState, +} + +impl CamState { + pub fn new( + station_id: u32, + station_type: c_its_parser::standards::extensions::ItsStationType, + vehicle_length_dm: u16, + vehicle_width_dm: u8, + ) -> Self { + Self { + station_id, + station_type: station_type.into(), + vehicle_length_dm, + vehicle_width_dm, + time: chrono::DateTime::::default(), + pos_state: super::PositionState::default(), + } + } + + pub fn update(&mut self, time: chrono::DateTime, pos: super::PositionState) { + self.time = time; + self.pos_state = pos; + + // TODO: build CAM trace + } + + #[allow(clippy::too_many_lines)] + fn make_cam(&self) -> Result { + let header = its_container::ItsPduHeader::new( + 2, + extensions::ItsMessageId::Cam.as_u8(), + its_container::StationID(self.station_id), + ); + + let generation_delta_time = { + let its_timestamp = c_its_parser::time_utils::TimestampIts::from(self.time); + + #[allow(clippy::cast_possible_truncation)] + let ts_mod = (its_timestamp.0 % 65_536) as u16; + + cam_pdu_descriptions::GenerationDeltaTime(ts_mod) + }; + + let heading = match self.pos_state.heading_deg { + Some(value) => its_container::Heading { + heading_value: its_container::HeadingValue::from_deg(value) + .map_err(|err| alloc::format!("Failed to make HeadingValue: {err}"))?, + heading_confidence: its_container::HeadingConfidence(127), // unavailable for now + }, + None => its_container::Heading { + heading_value: its_container::HeadingValue(0), + heading_confidence: its_container::HeadingConfidence(127), // unavailable for now + }, + }; + let speed = match self.pos_state.speed_mps { + Some(value) => its_container::Speed { + speed_value: its_container::SpeedValue::from_mps(value) + .map_err(|err| alloc::format!("Failed to make SpeedValue: {err}"))?, + speed_confidence: its_container::SpeedConfidence(127), // unavailable for now + }, + None => its_container::Speed { + speed_value: its_container::SpeedValue(0), + speed_confidence: its_container::SpeedConfidence(127), // unavailable for now + }, + }; + let reference_position = its_container::ReferencePosition { + latitude: its_container::Latitude::from_deg(self.pos_state.position.y()), + longitude: its_container::Longitude::from_deg(self.pos_state.position.x()), + position_confidence_ellipse: its_container::PosConfidenceEllipse { + semi_major_confidence: its_container::SemiAxisLength(4095), // unavailable for now + semi_minor_confidence: its_container::SemiAxisLength(4095), // unavailable for now + semi_major_orientation: its_container::HeadingValue::unavailable(), + }, + altitude: its_container::Altitude { + altitude_value: its_container::AltitudeValue(800_001), // unavailable for now + altitude_confidence: its_container::AltitudeConfidence::unavailable, + }, + }; + + let basic_container = cam_pdu_descriptions::BasicContainer::new( + self.station_type.clone(), + reference_position, + ); + let high_frequency_container = + cam_pdu_descriptions::HighFrequencyContainer::basicVehicleContainerHighFrequency( + cam_pdu_descriptions::BasicVehicleContainerHighFrequency { + heading, + speed, + drive_direction: its_container::DriveDirection::unavailable, + vehicle_length: its_container::VehicleLength { + vehicle_length_value: its_container::VehicleLengthValue( + self.vehicle_length_dm, + ), + vehicle_length_confidence_indication: + its_container::VehicleLengthConfidenceIndication::unavailable, + }, + vehicle_width: its_container::VehicleWidth(self.vehicle_width_dm), + longitudinal_acceleration: its_container::LongitudinalAcceleration { + longitudinal_acceleration_value: + its_container::LongitudinalAccelerationValue::unavailable(), + longitudinal_acceleration_confidence: its_container::AccelerationConfidence( + 102, // unavailable + ), + }, + curvature: its_container::Curvature { + curvature_value: its_container::CurvatureValue::unavailable(), + curvature_confidence: its_container::CurvatureConfidence::unavailable, + }, + curvature_calculation_mode: + its_container::CurvatureCalculationMode::unavailable, + yaw_rate: its_container::YawRate { + yaw_rate_value: its_container::YawRateValue::unavailable(), + yaw_rate_confidence: its_container::YawRateConfidence::unavailable, + }, + acceleration_control: None, + lane_position: None, + steering_wheel_angle: None, + lateral_acceleration: None, + vertical_acceleration: None, + performance_class: None, + cen_dsrc_tolling_zone: None, + }, + ); + let low_frequency_container = + cam_pdu_descriptions::LowFrequencyContainer::basicVehicleContainerLowFrequency( + cam_pdu_descriptions::BasicVehicleContainerLowFrequency { + vehicle_role: its_container::VehicleRole::default, + exterior_lights: its_container::ExteriorLights::default(), + path_history: its_container::PathHistory(alloc::vec![]), // TODO: fill trace + }, + ); + + let cam_parameters = cam_pdu_descriptions::CamParameters::new( + basic_container, + high_frequency_container, + Some(low_frequency_container), + None, + ); + + Ok(cam_pdu_descriptions::CAM::new( + header, + cam_pdu_descriptions::CoopAwareness::new(generation_delta_time, cam_parameters), + )) + } +} + +impl crate::v2x_tx::GnItsPayload for CamState { + fn make_eh( + &self, + address: [u8; 6], + own_position: &crate::applogic::PositionState, + time: chrono::DateTime, + _seq_no: &mut u16, + ) -> ( + geonetworking::en302636_4_1::ExtendedHeader, + Option, + u8, + ) { + let station_type = geonetworking::en302636_4_1::StationType::try_from(self.station_type.0) + .unwrap_or_default(); + + // CAM always uses SHB, hop-limit 1 + ( + crate::v2x_tx::gn::make_shb_eh(address, station_type, own_position, time), + None, + 1, + ) + } + + fn make_payload( + &self, + ) -> Result<(Vec, c_its_parser::standards::extensions::ItsMessageId), alloc::string::String> + { + let msg = self.make_cam()?; + let uper = msg.encode_to_uper()?; + Ok((uper, c_its_parser::standards::extensions::ItsMessageId::Cam)) + } +} diff --git a/src/applogic/mod.rs b/src/applogic/mod.rs index 98d100f..d1d082e 100644 --- a/src/applogic/mod.rs +++ b/src/applogic/mod.rs @@ -18,6 +18,8 @@ pub mod v2x; #[cfg(feature = "cam")] pub mod cam; +#[cfg(feature = "cam_tx")] +pub mod cam_tx; #[cfg(feature = "denm")] pub mod denm; #[cfg(feature = "spat")] diff --git a/src/main.rs b/src/main.rs index bddb837..daac5b8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,6 +14,8 @@ use core::cell::RefCell; use core::cell::UnsafeCell; use c_its_parser::gn as geonetworking; +#[cfg(feature = "cam_tx")] +use c_its_parser::standards::extensions::ItsStationType; use critical_section::Mutex; use crossbeam_queue::ArrayQueue; use embassy_executor::Spawner; @@ -37,10 +39,20 @@ mod io; mod radio; #[cfg(feature = "screen")] mod screen; +#[cfg(feature = "cam_tx")] +mod v2x_tx; const WIFI_CHANNEL: radio::Channel = 180; #[cfg(feature = "spat")] const SPAT_RATE_LIMIT: u8 = 5; // keep every n-th message +#[cfg(feature = "cam_tx")] +const GN_IS_MOBILE: bool = true; +#[cfg(feature = "cam_tx")] +const OWN_STATION_TYPE: ItsStationType = ItsStationType::Cyclist; +#[cfg(feature = "cam_tx")] +const OWN_VEHICLE_WIDTH: u8 = 5; // in 10cm steps! +#[cfg(feature = "cam_tx")] +const OWN_VEHICLE_LENGTH: u16 = 20; // in 10cm steps! // This creates a default app-descriptor required by the esp-idf bootloader. // For more information see: @@ -119,7 +131,7 @@ async fn main(spawner: Spawner) -> ! { // Setup WiFi // sadly, it doesn't work any more when we move `esp_radio::wifi::new` to `setup_wifi_sniffer` - let (mut wifi_controller, mut interfaces) = esp_radio::wifi::new( + let (mut wifi_controller, interfaces) = esp_radio::wifi::new( peripherals.WIFI, esp_radio::wifi::ControllerConfig::default(), ) @@ -131,7 +143,8 @@ async fn main(spawner: Spawner) -> ! { critical_section::with(|cs| { WIFI_RX_QUEUE.borrow(cs).replace(Some(ArrayQueue::new(2))); }); - radio::setup_wifi_sniffer(WIFI_CHANNEL, &mut interfaces.sniffer, handle_frame) + let mut wlan_iface = interfaces.sniffer; + radio::setup_wifi_sniffer(WIFI_CHANNEL, &mut wlan_iface, handle_frame) .expect("Fatal error initializing 802.11p sniffer"); info!("WiFi promiscuous mode on channel {WIFI_CHANNEL} running"); @@ -159,6 +172,23 @@ async fn main(spawner: Spawner) -> ! { info!("GNSS task started, waiting for GNSS fix..."); let mut state = applogic::State::new(); + #[cfg(feature = "cam_tx")] + let (mut seq_no, mut cam, mac_bytes) = { + let station_id = v2x_tx::make_station_id(mac); + info!("V2X Station ID: {station_id}"); + + // unwrap is fine since [`esp_hal::efuse::MacAddress`] is just an `[u8; 6]` + let mac_bytes = mac.as_bytes().try_into().unwrap(); + + let cam = applogic::cam_tx::CamState::new( + station_id, + OWN_STATION_TYPE, + OWN_VEHICLE_LENGTH, + OWN_VEHICLE_WIDTH, + ); + + (0, cam, mac_bytes) + }; loop { embassy_time::Timer::after(embassy_time::Duration::from_millis(1)).await; @@ -171,6 +201,33 @@ async fn main(spawner: Spawner) -> ! { if let Some(fix) = gnss_update_ref.replace(None) { state.update_with_gpsfix(&fix); new_position = true; + + #[cfg(feature = "cam_tx")] + { + use v2x_tx::GnItsPayload; + let time = state.time().and_utc(); + + cam.update(time, fix.clone().into()); + + match cam.encode_packet( + mac_bytes, + &fix.into(), + time, + GN_IS_MOBILE, + &mut seq_no, + ) { + Ok(data) => { + if let Err(err) = radio::send_80211_bcast_frame( + &mut wlan_iface, + mac.as_bytes(), + &data, + ) { + warn!("Failed to send 802.11 frame: {err}"); + } + } + Err(err) => warn!("Failed to create CAM: {err}"), + } + } } }); @@ -236,12 +293,15 @@ async fn main(spawner: Spawner) -> ! { } } - #[cfg(feature = "cam")] + #[cfg(any(feature = "cam", feature = "cam_tx"))] ItsMessage::Cam { geonetworking: _, transport: _, etsi, - } => applogic::cam::handle_cam(&etsi), + } => { + #[cfg(feature = "cam")] + applogic::cam::handle_cam(&etsi); + } #[cfg(feature = "denm")] ItsMessage::DenmV2 { diff --git a/src/v2x_tx/gn.rs b/src/v2x_tx/gn.rs new file mode 100644 index 0000000..828c74d --- /dev/null +++ b/src/v2x_tx/gn.rs @@ -0,0 +1,94 @@ +//! Geonetworking utilities + +use c_its_parser::gn as geonetworking; + +#[allow(unused)] +pub fn make_shb_eh( + address: [u8; 6], + station_type: geonetworking::en302636_4_1::StationType, + own_position: &crate::applogic::PositionState, + time: chrono::DateTime, +) -> geonetworking::en302636_4_1::ExtendedHeader { + let source_position_vector = make_lpv(address, station_type, own_position, time); + + geonetworking::en302636_4_1::ExtendedHeader::SHB( + geonetworking::en302636_4_1::SingleHopBroadcast { + source_position_vector, + media_dependent_data: [0; 4], + }, + ) +} + +#[allow(unused)] +pub fn make_tsb_eh( + address: [u8; 6], + station_type: geonetworking::en302636_4_1::StationType, + own_position: &crate::applogic::PositionState, + time: chrono::DateTime, + sequence_number: u16, +) -> geonetworking::en302636_4_1::ExtendedHeader { + let source_position_vector = make_lpv(address, station_type, own_position, time); + + geonetworking::en302636_4_1::ExtendedHeader::TSB( + geonetworking::en302636_4_1::TopologicallyScopedBroadcast::new( + sequence_number, + source_position_vector, + ), + ) +} + +#[allow(unused)] +pub fn make_gbc_eh( + address: [u8; 6], + station_type: geonetworking::en302636_4_1::StationType, + own_position: &crate::applogic::PositionState, + time: chrono::DateTime, + target_pos: geo_types::Point, + radius_m: u16, + sequence_number: u16, +) -> geonetworking::en302636_4_1::ExtendedHeader { + let source_position_vector = make_lpv(address, station_type, own_position, time); + #[allow(clippy::cast_possible_truncation)] + let latitude_deg = target_pos.x() as f32; + #[allow(clippy::cast_possible_truncation)] + let longitude_deg = target_pos.y() as f32; + + geonetworking::en302636_4_1::ExtendedHeader::GBC( + geonetworking::en302636_4_1::GeoBroadcast::try_from_values( + sequence_number, + source_position_vector, + latitude_deg, + longitude_deg, + radius_m, + 0, + 0, + ) + .expect("Failed to create circular GBC"), + ) +} + +fn make_lpv( + address: [u8; 6], + station_type: geonetworking::en302636_4_1::StationType, + own_position: &crate::applogic::PositionState, + time: chrono::DateTime, +) -> geonetworking::en302636_4_1::LongPositionVector { + let timestamp_its = c_its_parser::time_utils::TimestampIts::from(time); + #[allow(clippy::cast_possible_truncation)] + let latitude_deg = own_position.position.y() as f32; + #[allow(clippy::cast_possible_truncation)] + let longitude_deg = own_position.position.x() as f32; + let speed_mps = own_position.speed_mps.unwrap_or_default(); + let heading_deg = own_position.heading_deg.unwrap_or_default(); + + geonetworking::en302636_4_1::LongPositionVector::try_from_values( + geonetworking::en302636_4_1::Address::new(false, station_type, address), + timestamp_its.0, + latitude_deg, + longitude_deg, + true, + speed_mps, + heading_deg, + ) + .expect("Failed to create LongPositionVector") +} diff --git a/src/v2x_tx/mod.rs b/src/v2x_tx/mod.rs new file mode 100644 index 0000000..e9b1a28 --- /dev/null +++ b/src/v2x_tx/mod.rs @@ -0,0 +1,160 @@ +//! V2X Transmission + +use alloc::vec::Vec; + +use c_its_parser::gn::{self as geonetworking, Encode}; + +pub mod gn; + +#[cfg(feature = "esp")] +/// Creates a station ID from the WLAN MAC address +/// +/// Will use `0xC173` as static prefix and uses last 16 bit from MAC address for the remainder. +pub fn make_station_id(mac: esp_hal::efuse::MacAddress) -> u32 { + // unwrap is fine here since we can be sure that the MacAddress is 6 byte + // and a slice of 2 bytes can be converted to [u8; 2] + let mac_last_16bit = u16::from_be_bytes(mac.as_bytes().get(4..6).unwrap().try_into().unwrap()); + + 0xC173_0000 | u32::from(mac_last_16bit) +} + +pub trait GnItsPayload { + // common GN parameters + const LIFETIME_MILLIS: u32 = 60_000; // itsGnDefaultPacketLifetime is 60 seconds according to ETSI EN 302 636-4-1 V1.4.1 + + // --------------------------------------------- + // methods to build a GN packet with ITS payload + // --------------------------------------------- + + /// Builds the [`geonetworking::en302636_4_1::ExtendedHeader`] and additional information for the ITS message + /// + /// Output: + /// - Extended header + /// - Area type (when using GAC or GBC) + /// - hop limit + /// + /// Note: Make sure to increase the sequence number on multi-hop packets. + fn make_eh( + &self, + address: [u8; 6], + own_position: &crate::applogic::PositionState, + time: chrono::DateTime, + seq_no: &mut u16, + ) -> ( + geonetworking::en302636_4_1::ExtendedHeader, + Option, + u8, + ); + + /// Creates the ITS payload (without BTP header) + fn make_payload( + &self, + ) -> Result<(Vec, c_its_parser::standards::extensions::ItsMessageId), alloc::string::String>; + + // --------------------------------------------- + // common methods (with implementation) + // --------------------------------------------- + + /// Encodes the GN packet + fn encode_packet( + &self, + address: [u8; 6], + own_position: &crate::applogic::PositionState, + time: chrono::DateTime, + is_mobile: bool, + seq_no: &mut u16, + ) -> Result, alloc::string::String> { + // Build payload (including BTP header) + let (mut its_payload, msg_type) = self.make_payload()?; + let mut payload = make_btp_b(msg_type).encode()?; + + payload.append(&mut its_payload); + + #[allow(clippy::cast_possible_truncation)] + let payload_length = (payload.len()) as u16; + + // get extended header (depending on message type and sometimes content as well) + let (eh, area_type, maximum_hop_limit) = self.make_eh(address, own_position, time, seq_no); + + // Build GN headers + let traffic_class = geonetworking::en302636_4_1::TrafficClass::try_new( + false, + false, + make_traffic_class(msg_type), + ) + .map_err(|err| alloc::format!("Failed to create TrafficClass: {err:?}"))?; + let common = geonetworking::en302636_4_1::CommonHeader::from_values( + geonetworking::en302636_4_1::NextAfterCommon::BTPB, + geonetworking::en302636_4_1::HeaderType::new_from(&eh, area_type)?, + traffic_class, + is_mobile, + payload_length, + maximum_hop_limit, + ); + + let remaining_hop_limit = maximum_hop_limit; + let basic = geonetworking::en302636_4_1::BasicHeader::try_new( + 1, + geonetworking::en302636_4_1::NextAfterBasic::CommonHeader, + geonetworking::en302636_4_1::Lifetime::from_milliseconds(Self::LIFETIME_MILLIS), + remaining_hop_limit, + ) + .map_err(|err| alloc::format!("Failed to create BasicHeader: {err}"))?; + + // Assemble and encode packet + let packet = geonetworking::Packet::Unsecured { + basic, + common, + extended: Some(eh), + payload: &payload, + }; + + packet + .encode_to_vec() + .map_err(|err| alloc::format!("Failed to encode GN packet: {err:?}")) + } +} + +/// Creates a BTP-B header based on the ITS message ID (message type) +/// +/// Port numbers and destination port info according to ETSI TS 103 248 +fn make_btp_b( + msg_type: c_its_parser::standards::extensions::ItsMessageId, +) -> c_its_parser::transport::TransportHeader { + let (destination_port, destination_port_info) = match msg_type { + c_its_parser::standards::extensions::ItsMessageId::Denm => (2002, 0), + c_its_parser::standards::extensions::ItsMessageId::Cam => (2001, 0), + c_its_parser::standards::extensions::ItsMessageId::Mapem => (2003, 0), + c_its_parser::standards::extensions::ItsMessageId::Spatem => (2004, 0), + c_its_parser::standards::extensions::ItsMessageId::Saem => (2005, 0), + c_its_parser::standards::extensions::ItsMessageId::Ivim => (2006, 0), + c_its_parser::standards::extensions::ItsMessageId::Srem => (2007, 0), + c_its_parser::standards::extensions::ItsMessageId::Ssem => (2008, 0), + c_its_parser::standards::extensions::ItsMessageId::Cpm => (2009, 0), + c_its_parser::standards::extensions::ItsMessageId::Poi => (2010, 0), + c_its_parser::standards::extensions::ItsMessageId::EvRsr => (2012, 0), + c_its_parser::standards::extensions::ItsMessageId::Rtcmem => (2013, 0), + _ => (0, 0), // no information found for the subsequent types + }; + + c_its_parser::transport::TransportHeader::BtpB(c_its_parser::transport::BasicTransportBHeader { + destination_port, + destination_port_info, + }) +} + +/// Creates a traffic class ID based on the ITS message ID (message type) +fn make_traffic_class(msg_type: c_its_parser::standards::extensions::ItsMessageId) -> u8 { + #[allow(clippy::match_same_arms)] + match msg_type { + c_its_parser::standards::extensions::ItsMessageId::Denm => 1, + c_its_parser::standards::extensions::ItsMessageId::Cam => 2, + c_its_parser::standards::extensions::ItsMessageId::Mapem => 3, // from C-Roads RS_RSP_063(1) + c_its_parser::standards::extensions::ItsMessageId::Spatem => 3, // from C-Roads RS_RSP_063(1) + c_its_parser::standards::extensions::ItsMessageId::Ivim => 3, // from C-Roads RS_RSP_063(1) + c_its_parser::standards::extensions::ItsMessageId::Srem => 2, // from C-Roads RS_RSP_063(1) + c_its_parser::standards::extensions::ItsMessageId::Ssem => 2, // from C-Roads RS_RSP_063(1) + c_its_parser::standards::extensions::ItsMessageId::Rtcmem => 3, // CSP_MaxPrio=252 -> 3 + _ => 3, // fall-back to lowest priority + } +} From 7bbb47a844d96195abf2075eb388bc2faa9ef088 Mon Sep 17 00:00:00 2001 From: Jannik Beyerstedt Date: Fri, 24 Jul 2026 17:25:52 +0200 Subject: [PATCH 06/27] cam_tx: Fill path history --- Cargo.toml | 2 +- feat-permut.sh | 1 - src/applogic/cam_tx.rs | 230 ++++++++++++++++++++++++++++++++++++++++- src/applogic/mod.rs | 2 +- src/test.rs | 1 + 5 files changed, 231 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 834f164..fb0d588 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ path = "./src/main.rs" [[bin]] name = "test" path = "./src/test.rs" -required-features = ["std"] +required-features = ["std", "cam"] [features] default = ["esp", "spat", "gnss", "screen"] diff --git a/feat-permut.sh b/feat-permut.sh index a40a8ca..6ddd438 100755 --- a/feat-permut.sh +++ b/feat-permut.sh @@ -17,7 +17,6 @@ features_more=( spat,gnss,screen,cam_tx,cam,denm ) features_test=( - spat spat,cam,denm ) diff --git a/src/applogic/cam_tx.rs b/src/applogic/cam_tx.rs index 7de554e..0cd0a16 100644 --- a/src/applogic/cam_tx.rs +++ b/src/applogic/cam_tx.rs @@ -7,6 +7,10 @@ use c_its_parser::standards::cam_1_4_1::cam_pdu_descriptions; use c_its_parser::standards::cdd_1_3_1_1::its_container; use c_its_parser::standards::extensions; +const PH_MAX_LENGHT_M: f32 = 500.; +const PH_MAX_ITEMS: usize = 23; +const PH_MIN_DIST_M: f32 = 20.; + #[derive(Debug)] pub struct CamState { station_id: u32, @@ -17,6 +21,39 @@ pub struct CamState { time: chrono::DateTime, pos_state: super::PositionState, + + /// database for path history, oldest item at the back of the vector + path_history: Vec, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +struct PathPoint { + pos: geo_types::Point, + time: chrono::DateTime, + /// distance to previous point. Ignore on last point! + dist_m: f32, +} + +impl PathPoint { + fn new_from_pos(pos: &super::PositionState, time: chrono::DateTime) -> Self { + Self { + pos: pos.position, + time, + dist_m: 0., + } + } + + fn new_from_pos_with_dist( + pos: &super::PositionState, + time: chrono::DateTime, + dist_m: f32, + ) -> Self { + Self { + pos: pos.position, + time, + dist_m, + } + } } impl CamState { @@ -33,6 +70,7 @@ impl CamState { vehicle_width_dm, time: chrono::DateTime::::default(), pos_state: super::PositionState::default(), + path_history: Vec::with_capacity(PH_MAX_ITEMS + 1), } } @@ -40,7 +78,99 @@ impl CamState { self.time = time; self.pos_state = pos; - // TODO: build CAM trace + // Update path history + Self::update_ph(&mut self.path_history, time, &self.pos_state); + } + + // this is a function, not a method for easier testing + fn update_ph( + path_history: &mut Vec, + time: chrono::DateTime, + pos: &super::PositionState, + ) { + // Update path history + if path_history.is_empty() { + // prime with current position if empty + path_history.push(PathPoint::new_from_pos(pos, time)); + } else { + // unwrap is fine since we checked for non-empty before + let prev = path_history.first().unwrap(); + + // only add a point, if we moved more than PH_MIN_DIST_M + #[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)] + let dist = crate::geo_alg::haversine_dist(&prev.pos, &pos.position) as f32; + if dist > PH_MIN_DIST_M { + // new items shall be at the front of the vector + path_history.insert(0, PathPoint::new_from_pos_with_dist(pos, time, dist)); + } + } + + // check length (PH_MAX_LENGHT_M and PH_MAX_ITEMS points max) + if path_history.len() > PH_MAX_ITEMS { + let _ = path_history.pop(); + } + + let (_, cutoff_idx) = + path_history + .iter() + .enumerate() + .fold((0., None), |(len, cutoff_idx), (idx, item)| { + if cutoff_idx.is_none() { + let new_len = len + item.dist_m; + let new_cutoff_idx = if len > PH_MAX_LENGHT_M { + Some(idx) + } else { + None + }; + + (new_len, new_cutoff_idx) + } else { + (len, cutoff_idx) + } + }); + if let Some(idx) = cutoff_idx { + path_history.drain(idx..); + } + } + + fn make_ph(&self) -> Vec { + let mut ph = alloc::vec![]; + + self.path_history.iter().fold( + PathPoint::new_from_pos(&self.pos_state, self.time), + |prev_point, pt| { + // calculate delta position and append to `ph` + + let delta_time = pt.time - prev_point.time; + let delta_lat = pt.pos.y() - prev_point.pos.y(); + let delta_lon = pt.pos.x() - prev_point.pos.x(); + + // delta time is in 1/100 second / 10 ms steps + let path_delta_time = its_container::PathDeltaTime( + (delta_time.num_milliseconds().abs() / 10) + .min(65_535) // clamp to max value + .into(), + ); + let delta_latitude = its_container::DeltaLatitude::from_deg(delta_lat); + let delta_longitude = its_container::DeltaLongitude::from_deg(delta_lon); + let delta_altitude = its_container::DeltaAltitude::unavailable(); + + let ppoint = its_container::PathPoint::new( + its_container::DeltaReferencePosition::new( + delta_latitude, + delta_longitude, + delta_altitude, + ), + Some(path_delta_time), + ); + + ph.push(ppoint); + + *pt + }, + ); + + ph } #[allow(clippy::too_many_lines)] @@ -145,7 +275,7 @@ impl CamState { cam_pdu_descriptions::BasicVehicleContainerLowFrequency { vehicle_role: its_container::VehicleRole::default, exterior_lights: its_container::ExteriorLights::default(), - path_history: its_container::PathHistory(alloc::vec![]), // TODO: fill trace + path_history: its_container::PathHistory(self.make_ph()), }, ); @@ -195,3 +325,99 @@ impl crate::v2x_tx::GnItsPayload for CamState { Ok((uper, c_its_parser::standards::extensions::ItsMessageId::Cam)) } } + +#[cfg(test)] +mod tests { + use alloc::vec; + + use super::*; + use crate::init_test_env_logger; + + fn posstate_from_pos(position: geo_types::Point) -> crate::applogic::PositionState { + crate::applogic::PositionState { + position, + heading_deg: None, + speed_mps: None, + } + } + + #[test] + fn cam_path_history_update() { + init_test_env_logger(); + + let time = chrono::DateTime::from_timestamp(0, 0).unwrap(); + let p1 = geo_types::Point::new(10., 53.5); + let p2 = geo_types::Point::new(10., 53.5 + 0.000045); // ~5m further (< PH_MIN_DIST_M) + let p3 = geo_types::Point::new(10., 53.5 + 0.00027); // ~30m further (> PH_MIN_DIST_M) + + let ps1 = posstate_from_pos(p1); + let pp1 = PathPoint::new_from_pos(&ps1, time); + let ps2 = posstate_from_pos(p2); + let pp2 = PathPoint::new_from_pos(&ps2, time); + let ps3 = posstate_from_pos(p3); + let pp3 = PathPoint::new_from_pos_with_dist(&ps3, time, 30.02267); + + let mut path_history: Vec = vec![]; + + // add first point, expect one point in PH + CamState::update_ph(&mut path_history, time, &posstate_from_pos(p1)); + assert_eq!(1, path_history.len()); + assert_eq!(&pp1, path_history.first().unwrap()); + + // add insignificant point, expect still one point in PH + CamState::update_ph(&mut path_history, time, &posstate_from_pos(p2)); + assert_eq!(1, path_history.len()); + assert_eq!(&pp1, path_history.first().unwrap()); + + // add significantly moved point, expect [pp3, pp1] vector + CamState::update_ph(&mut path_history, time, &posstate_from_pos(p3)); + assert_eq!(2, path_history.len()); + assert_eq!(&pp3, path_history.first().unwrap()); + assert_eq!(&pp1, path_history.last().unwrap()); + } + + #[test] + fn cam_path_history_max_len() { + init_test_env_logger(); + + let time = chrono::DateTime::from_timestamp(0, 0).unwrap(); + let p1 = geo_types::Point::new(10., 53.5); + let p2 = geo_types::Point::new(10., 53.5 + 0.00023); // at ~25m from p1 (> PH_MIN_DIST_M) + let p3 = geo_types::Point::new(10., 53.5 + 0.00445); // at ~495m from p1 (slightly less than PH_MAX_LENGHT_M) + let p4 = geo_types::Point::new(10., 53.5 + 0.00477); // at ~530m from p1 (slightly more than PH_MAX_LENGHT_M) + + let ps1 = posstate_from_pos(p1); + let pp1 = PathPoint::new_from_pos(&ps1, time); + let ps2 = posstate_from_pos(p2); + let pp2 = PathPoint::new_from_pos(&ps2, time); + let ps3 = posstate_from_pos(p3); + let pp3 = PathPoint::new_from_pos(&ps3, time); + let ps4 = posstate_from_pos(p4); + let pp4 = PathPoint::new_from_pos(&ps4, time); + + let mut path_history: Vec = vec![]; + + // prime with p3, p4 (should give a length of ~470m) + CamState::update_ph(&mut path_history, time, &posstate_from_pos(p4)); + CamState::update_ph(&mut path_history, time, &posstate_from_pos(p3)); + assert_eq!(2, path_history.len()); + + // Add p2, should drop p4 from vector because of > PH_MAX_LENGHT_M + CamState::update_ph(&mut path_history, time, &posstate_from_pos(p2)); + assert_eq!(2, path_history.len()); + assert_eq!(p2, path_history.get(0).unwrap().pos); + assert_eq!(469.24323, path_history.get(0).unwrap().dist_m); + assert_eq!(p3, path_history.get(1).unwrap().pos); + assert_eq!(35.582424, path_history.get(1).unwrap().dist_m); + + // Add p1, should not drop anything from vector because still < PH_MAX_LENGHT_M + CamState::update_ph(&mut path_history, time, &posstate_from_pos(p1)); + assert_eq!(3, path_history.len()); + assert_eq!(p1, path_history.get(0).unwrap().pos); + assert_eq!(25.57487, path_history.get(0).unwrap().dist_m); + assert_eq!(p2, path_history.get(1).unwrap().pos); + assert_eq!(469.24323, path_history.get(1).unwrap().dist_m); + assert_eq!(p3, path_history.get(2).unwrap().pos); + assert_eq!(35.582424, path_history.get(2).unwrap().dist_m); + } +} diff --git a/src/applogic/mod.rs b/src/applogic/mod.rs index d1d082e..32ee4f6 100644 --- a/src/applogic/mod.rs +++ b/src/applogic/mod.rs @@ -18,7 +18,7 @@ pub mod v2x; #[cfg(feature = "cam")] pub mod cam; -#[cfg(feature = "cam_tx")] +#[cfg(any(feature = "cam_tx", test))] pub mod cam_tx; #[cfg(feature = "denm")] pub mod denm; diff --git a/src/test.rs b/src/test.rs index 5a04150..7de1bf8 100644 --- a/src/test.rs +++ b/src/test.rs @@ -8,6 +8,7 @@ mod cache; mod geo_alg; mod radio; mod testdata; +mod v2x_tx; fn main() { // env_logger::init(); From 6604670490cbb36874fd7e36d718b445da88a5bc Mon Sep 17 00:00:00 2001 From: Jannik Beyerstedt Date: Sun, 26 Jul 2026 21:40:55 +0200 Subject: [PATCH 07/27] main: Add UART I/O helpers --- Cargo.toml | 2 + src/main.rs | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index fb0d588..bb70ea2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,8 @@ spat_debug = [] # enable ST7789 based display screen = ["dep:mipidsi", "dep:embedded-graphics", "dep:embedded-hal-bus", "dep:profont"] +# internal feature, that any UART I/O is enabled +_uart = [] [dependencies] esp-hal = { version = "~1.1.0", optional = true, features = ["esp32c5", "log-04", "unstable"] } diff --git a/src/main.rs b/src/main.rs index daac5b8..03ef304 100644 --- a/src/main.rs +++ b/src/main.rs @@ -53,6 +53,8 @@ const OWN_STATION_TYPE: ItsStationType = ItsStationType::Cyclist; const OWN_VEHICLE_WIDTH: u8 = 5; // in 10cm steps! #[cfg(feature = "cam_tx")] const OWN_VEHICLE_LENGTH: u16 = 20; // in 10cm steps! +#[cfg(feature = "_uart")] +const SERIAL_FIFO_SIZE_THLD: u8 = 100; // hardware has max. 128 byte // This creates a default app-descriptor required by the esp-idf bootloader. // For more information see: @@ -61,6 +63,11 @@ esp_bootloader_esp_idf::esp_app_desc!(); #[cfg(feature = "gnss")] static GNSS_UPDATE: Mutex>> = Mutex::new(RefCell::new(None)); static WIFI_RX_QUEUE: Mutex>>>> = Mutex::new(RefCell::new(None)); +#[cfg(feature = "_uart")] +static UART_RX_BUF: Mutex>>> = Mutex::new(RefCell::new(None)); +#[cfg(feature = "_uart")] +static SERIAL: Mutex>>> = + Mutex::new(RefCell::new(None)); #[allow( clippy::large_stack_frames, @@ -192,6 +199,25 @@ async fn main(spawner: Spawner) -> ! { loop { embassy_time::Timer::after(embassy_time::Duration::from_millis(1)).await; + #[cfg(feature = "_uart")] + { + let mut new_data = None; + critical_section::with(|cs| { + let mut buf_rc = UART_RX_BUF.borrow(cs).borrow_mut(); + // unwrap is fine b/c we stored something in it before + let buf = buf_rc.as_mut().unwrap(); + + // we move the buffer to new_data to do the processing outside of the critical section lock + if !buf.is_empty() { + new_data = Some(core::mem::take(buf)); + } + }); + + if let Some(data) = new_data { + warn!("TODO: New UART data: {data:x?}"); + } + } + #[cfg(feature = "gnss")] { let mut new_position = false; @@ -573,3 +599,95 @@ where { error!("graphics failed: {error:?}"); } + +#[cfg(feature = "_uart")] +/// Configures UART0 with an interrupt handler and saves it to `SERIAL` +fn setup_uart0( + baud: u32, + cmd_char: Option, + gpio12: esp_hal::peripherals::GPIO12<'static>, + gpio11: esp_hal::peripherals::GPIO11<'static>, + uart0: esp_hal::peripherals::UART0<'static>, + handler: esp_hal::interrupt::InterruptHandler, +) -> Result<(), alloc::string::String> { + critical_section::with(|cs| { + UART_RX_BUF + .borrow(cs) + .replace(Some(alloc::vec::Vec::with_capacity(255))); + }); + + let mut uart = esp_hal::uart::Uart::new( + uart0, + esp_hal::uart::Config::default() + .with_baudrate(baud) + .with_rx( + esp_hal::uart::RxConfig::default() + .with_fifo_full_threshold(u16::from(SERIAL_FIFO_SIZE_THLD)) + .with_timeout(50), + ), + ) + .map_err(|err| alloc::format!("UART0 config error: {err}"))? + .with_rx(gpio12) + .with_tx(gpio11); + + if let Some(char) = cmd_char { + uart.set_at_cmd( + esp_hal::uart::AtCmdConfig::default() + .with_cmd_char(char) + .with_char_num(1) + .with_pre_idle_count(0) // command start can be right after other data + .with_post_idle_count(0) // we don't expect a gap between the command start and subsequent text + .with_gap_timeout(5), // needs to be >0 to work + ); + } + uart.set_interrupt_handler(handler); + uart.listen( + esp_hal::uart::UartInterrupt::AtCmd + | esp_hal::uart::UartInterrupt::RxTimeout + | esp_hal::uart::UartInterrupt::RxFifoFull, // just to be save + ); + + critical_section::with(|cs| SERIAL.borrow_ref_mut(cs).replace(uart)); + + Ok(()) +} + +#[cfg(feature = "_uart")] +#[esp_hal::handler] +fn serial_handler() { + critical_section::with(|cs| { + let mut serial = SERIAL.borrow_ref_mut(cs); + let mut uart_buf = UART_RX_BUF.borrow_ref_mut(cs); + + if let (Some(serial), Some(uart_buf)) = (serial.as_mut(), uart_buf.as_mut()) { + let mut buf = [0u8; SERIAL_FIFO_SIZE_THLD as usize]; + + match serial.read_buffered(&mut buf) { + Ok(0) => {} + Ok(len) => { + uart_buf.extend_from_slice(&buf[..len]); + } + Err(err) => error!("Failed to read from UART: {err}"), + } + + serial.clear_interrupts( + esp_hal::uart::UartInterrupt::RxFifoFull + | esp_hal::uart::UartInterrupt::RxTimeout + | esp_hal::uart::UartInterrupt::AtCmd, + ); + } + }); +} + +#[cfg(feature = "_uart")] +fn send_uart0(data: &[u8]) { + critical_section::with(|cs| { + let mut serial = SERIAL.borrow_ref_mut(cs); + + if let Some(serial) = serial.as_mut() + && let Err(err) = serial.write(data) + { + error!("Failed to write to UART: {err:?}"); + } + }); +} From ba71e718dfcd99e96ad441bf2347b8344f8f8432 Mon Sep 17 00:00:00 2001 From: Jannik Beyerstedt Date: Mon, 27 Jul 2026 11:14:14 +0200 Subject: [PATCH 08/27] proto/PositionState: Add time for geonetworking --- docs/c_its-messages.proto | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/c_its-messages.proto b/docs/c_its-messages.proto index 9848910..af8f802 100644 --- a/docs/c_its-messages.proto +++ b/docs/c_its-messages.proto @@ -35,18 +35,17 @@ enum GNAreaShape { ETSI_AREASHAPE_CIRCLE = 0; ETSI_AREASHAPE_RECTANGLE = 1; ETSI_AREASHAPE_ELLIPSIS = 2; - ETSI_AREASHAPE_UNKNOWN = 15; } // GeoNetworking Destination Area/ Source Position message ITSPosition { - optional int32 latitude = 1; // Latitude in 1/10th of micro degrees - optional int32 longitude = 2; // Longitude in 1/10th of micro degrees + required int32 latitude = 1; // Latitude in 1/10th of micro degrees + required int32 longitude = 2; // Longitude in 1/10th of micro degrees } // GeoNetworking Destination Area message GNArea { - optional GNAreaShape area = 1 [default = ETSI_AREASHAPE_UNKNOWN]; + optional GNAreaShape area = 1; optional ITSPosition position = 2; optional int32 distA = 3; // Distance A of the geometric shape in meters optional int32 distB = 4; // Distance B of the geometric shape in meters, mandatory for rectangle and ellipsis @@ -55,8 +54,6 @@ message GNArea { // GeoNetworking Transport enum GNTransport { - GN_TRANSPORT_DEFAULT = 0; - GN_TRANSPORT_GUC = 2; // GeoUnicast GN_TRANSPORT_GAC = 3; // Geographically-Scoped Anycast GN_TRANSPORT_GBC = 4; // Geographically-Scoped broadcast GN_TRANSPORT_TSB = 5; // Topologically-scoped broadcast @@ -79,7 +76,8 @@ message RawMsgTx { // ------------------------------ message PositionState { - required Position position = 1; + required sfixed64 timestamp_ms = 1; // current UNIX time in milliseconds + required Position position = 2; optional uint32 heading = 3; // heading in 1/10 deg optional uint32 speed = 4; // speed in 1/100 m/s } From 4d7bef58a165eb2f3a80a4d702034279ff3d50a4 Mon Sep 17 00:00:00 2001 From: Jannik Beyerstedt Date: Mon, 20 Jul 2026 11:58:44 +0200 Subject: [PATCH 09/27] Implement UART I/O protocol --- Cargo.toml | 10 +- Readme.md | 7 +- feat-permut.sh | 4 +- src/applogic/cam_tx.rs | 17 +- src/applogic/mod.rs | 5 + src/io.rs | 464 +++++++++++++++++++++++++++++++++++++++++ src/main.rs | 114 ++++++++-- src/test.rs | 1 + src/testdata.rs | 8 + src/v2x_tx/gn.rs | 2 +- src/v2x_tx/mod.rs | 18 +- 11 files changed, 612 insertions(+), 38 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bb70ea2..eef57d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,11 +34,11 @@ esp = [ std = ["c-its-parser/std"] # enable L67K GNSS module -gnss = ["dep:embassy_gps"] +gnss = ["_gnss", "dep:embassy_gps"] # echo CAM data on serial cam = ["c-its-parser/cam"] # send CAM autonomously -cam_tx = ["c-its-parser/cam", "gnss"] +cam_tx = ["c-its-parser/cam"] # echo DENM events on serial and BLE denm = ["c-its-parser/denm"] # evaluate SPAT information @@ -47,9 +47,13 @@ spat = ["c-its-parser/spatem", "c-its-parser/mapem"] spat_debug = [] # enable ST7789 based display screen = ["dep:mipidsi", "dep:embedded-graphics", "dep:embedded-hal-bus", "dep:profont"] +# enable I/O via UART +uart = ["_uart", "_gnss", "dep:prost"] # internal feature, that any UART I/O is enabled _uart = [] +# internal feature, that any GNSS source is enabled +_gnss = [] [dependencies] esp-hal = { version = "~1.1.0", optional = true, features = ["esp32c5", "log-04", "unstable"] } @@ -102,7 +106,7 @@ geo-types = { version = "0.7.19", default-features = false } mipidsi = { version = "0.10.0", optional = true } num-traits = { version = "0.2.19", default-features = false, features = ["libm"] } profont = { version = "0.7.0", optional = true } -prost = { version = "0.14", default-features = false, features = ["derive"] } +prost = { version = "0.14", optional = true, default-features = false, features = ["derive"] } [dev-dependencies] assert_float_eq = "1.2.0" diff --git a/Readme.md b/Readme.md index 281f995..c8dcf5c 100644 --- a/Readme.md +++ b/Readme.md @@ -32,6 +32,9 @@ Just prints a line for each received CAM. Generates and transmits a CAM when a GNSS update was received. Depends on the `gnss` feature. +### UART +Enable UART I/O protocol according to [docs/uart-protocol.md](./docs/uart-protocol.md). + ## Usage @@ -64,13 +67,13 @@ Additional features are available through feature flags: - `spat_debug`: Enable additional debug information for the SPAT/ GLOSA use case - `denm`: Enable DENMs - `cam`: Enable CAMs -- `cam_tx`: Enable CAM generation and transmission +- `cam_tx`: Enable CAM generation and transmission (needs time and position source) Officially supported feature combinations are: - `esp,spat,gnss,screen` (default features) - `esp,spat,gnss,screen,cam_tx` - `esp,spat,gnss,screen,cam_tx,cam,denm` -- To be implemented: No GNSS and screen when BLE/ UART interface is implemented +- `esp,uart,cam,denm` Connect your ESP32-C5 and run: diff --git a/feat-permut.sh b/feat-permut.sh index 6ddd438..2019bef 100755 --- a/feat-permut.sh +++ b/feat-permut.sh @@ -8,7 +8,7 @@ extended=${2:-false} features_base=( - #cam,denm,uart + uart,cam,denm spat,gnss,screen spat,gnss,screen,cam_tx ) @@ -17,7 +17,7 @@ features_more=( spat,gnss,screen,cam_tx,cam,denm ) features_test=( - spat,cam,denm + spat,cam,denm,uart ) diff --git a/src/applogic/cam_tx.rs b/src/applogic/cam_tx.rs index 0cd0a16..e15de27 100644 --- a/src/applogic/cam_tx.rs +++ b/src/applogic/cam_tx.rs @@ -300,20 +300,23 @@ impl crate::v2x_tx::GnItsPayload for CamState { own_position: &crate::applogic::PositionState, time: chrono::DateTime, _seq_no: &mut u16, - ) -> ( - geonetworking::en302636_4_1::ExtendedHeader, - Option, - u8, - ) { + ) -> Result< + ( + geonetworking::en302636_4_1::ExtendedHeader, + Option, + u8, + ), + alloc::string::String, + > { let station_type = geonetworking::en302636_4_1::StationType::try_from(self.station_type.0) .unwrap_or_default(); // CAM always uses SHB, hop-limit 1 - ( + Ok(( crate::v2x_tx::gn::make_shb_eh(address, station_type, own_position, time), None, 1, - ) + )) } fn make_payload( diff --git a/src/applogic/mod.rs b/src/applogic/mod.rs index 32ee4f6..7847010 100644 --- a/src/applogic/mod.rs +++ b/src/applogic/mod.rs @@ -639,6 +639,11 @@ impl State { pub fn time(&self) -> chrono::prelude::NaiveDateTime { self.time } + + #[allow(unused)] + pub fn pos(&self) -> &PositionState { + &self.pos + } } #[cfg(test)] diff --git a/src/io.rs b/src/io.rs index cb23055..4642253 100644 --- a/src/io.rs +++ b/src/io.rs @@ -5,6 +5,12 @@ pub mod msg { include!(concat!(env!("OUT_DIR"), "/c_its_io.rs")); } +use alloc::string::ToString; +use alloc::vec::Vec; + +use c_its_parser::gn as geonetworking; +use log::error; + impl From for msg::SerialOutputMsg { fn from(value: msg::RawMsgRx) -> Self { use msg::serial_output_msg::Payload; @@ -25,6 +31,144 @@ impl From for msg::SerialOutputMsg { } } +impl msg::RawMsgRx { + pub fn serialize_uart_proto(&self) -> Vec { + serialize_serial_msg(&Into::::into(self.clone())) + } +} +impl msg::DenmEvent { + #[allow(unused)] + pub fn serialize_uart_proto(&self) -> Vec { + serialize_serial_msg(&Into::::into(*self)) + } +} + +#[derive(Debug, Default)] +pub struct Parser { + buffer: alloc::vec::Vec, +} + +impl Parser { + const SOF_LEN: u16 = 4; + const SOF1: u8 = 0x56; + const SOF2: u8 = 0x32; + const SOF3: u8 = 0x58; + const SOF4: u8 = 0x2B; + const EOF_LEN: u16 = 2; + #[allow(unused)] + const EOF1: u8 = 0x0d; + #[allow(unused)] + const EOF2: u8 = 0x0a; + const LENGTH_LEN: u16 = 2; + + /// Parses incoming data and return list of messages + pub fn parse(&mut self, input: &[u8]) -> alloc::vec::Vec { + use prost::Message as _; + + // combine with existing data + self.buffer.append(&mut input.to_vec()); + + // find start of frame and parse + let mut output = alloc::vec![]; + while let Some(pos) = Self::find_sof(&self.buffer) { + // drop everything before the SOF + self.buffer.drain(..pos); + + // get length field and check buffer size + if let Some(len_bytes) = self + .buffer + .get(usize::from(Self::SOF_LEN)..usize::from(Self::SOF_LEN + Self::LENGTH_LEN)) + { + // unwrap is fine since we queried 2 bytes before, so we can also convert to 2-byte slice + let msg_size = u16::from_be_bytes(len_bytes.try_into().unwrap()); + + let required_buf_size = Self::SOF_LEN + Self::LENGTH_LEN + msg_size + Self::EOF_LEN; + if let Some(payload) = self.buffer.get( + usize::from(Self::SOF_LEN + Self::LENGTH_LEN) + ..usize::from(required_buf_size - 2), + ) { + match msg::SerialInputMsg::decode(payload) { + Ok(data) => { + output.push(data); + } + Err(err) => error!("Failed to parse input: {err:?}"), + } + + // consume buffer + self.buffer.drain(..usize::from(required_buf_size)); + } else { + break; + } + } + } + + output + } + + /// Returns the start index of the SOF sequence, if present + fn find_sof(input: &[u8]) -> Option { + // search for SOF + if let Some(sof1_pos) = input.iter().position(|i| *i == Self::SOF1) { + // drop everything before sof1_pos and check if remainder continues with SOF sequence + let (_, msg) = input.split_at(sof1_pos); + if msg.starts_with(&[Self::SOF1, Self::SOF2, Self::SOF3, Self::SOF4]) { + // found the SOF sequence + return Some(sof1_pos); + } + } + + // if we reach this position, SOF sequence wasn't found + None + } +} + +fn serialize_serial_msg(msg: &T) -> Vec +where + T: prost::Message, +{ + let mut msg_buf = msg.encode_to_vec(); + + let sof = [0x56u8, 0x32, 0x58, 0x2B]; + let eof = [0x0du8, 0x0a]; + #[allow(clippy::cast_possible_truncation)] + let len: [u8; 2] = (msg_buf.len() as u16).to_be_bytes(); + + let mut out = sof.to_vec(); + out.extend_from_slice(&len); + out.append(&mut msg_buf); + out.extend_from_slice(&eof); + + out +} + +impl From for crate::applogic::GnssFix { + fn from(value: msg::PositionState) -> Self { + let time = + chrono::DateTime::from_timestamp_millis(value.timestamp_ms).map(|v| v.naive_utc()); + + let latitude_deg = value.position.latitude_deg.into(); + let longitude_deg = value.position.longitude_deg.into(); + let heading_deg = value.heading.map(|v| { + #[allow(clippy::cast_precision_loss)] + let val = (v as f32) / 10.; + val + }); + let speed_mps = value.speed.map(|v| { + #[allow(clippy::cast_precision_loss)] + let val = (v as f32) / 100.; + val + }); + + Self { + time, + latitude_deg, + longitude_deg, + heading_deg, + speed_mps, + } + } +} + impl From for crate::applogic::PositionState { fn from(value: msg::PositionState) -> Self { let position = geo_types::Point::new( @@ -49,3 +193,323 @@ impl From for crate::applogic::PositionState { } } } + +impl From for crate::applogic::PositionState { + fn from(value: msg::ItsPosition) -> Self { + let latitude_deg = f64::from(value.latitude) / 10_000_000.; + let longitude_deg = f64::from(value.longitude) / 10_000_000.; + let position = geo_types::Point::new(longitude_deg, latitude_deg); + + Self { + position, + heading_deg: None, + speed_mps: None, + } + } +} + +impl crate::v2x_tx::GnItsPayload for msg::RawMsgTx { + fn make_eh( + &self, + address: [u8; 6], + own_position: &crate::applogic::PositionState, + time: chrono::DateTime, + seq_no: &mut u16, + ) -> Result< + ( + c_its_parser::gn::en302636_4_1::ExtendedHeader, + Option, + u8, + ), + alloc::string::String, + > { + let station_type = self + .station_type + .and_then(|v| { + #[allow(clippy::cast_possible_truncation)] + let raw_val = v as u8; + geonetworking::en302636_4_1::StationType::try_from(raw_val).ok() + }) + .unwrap_or_default(); + #[allow(clippy::cast_possible_truncation)] + let hop_limit = self.hop_limit as u8; + + Ok(match self.gn_transport() { + msg::GnTransport::Gac => { + if self.gn_area.position.is_none() { + return Err("Failed to create GAC: gnArea.position missing".to_string()); + } + + *seq_no += 1; + + let (atype, geoanycast) = Self::make_geobcast( + address, + station_type, + own_position, + time, + self.gn_area, + *seq_no, + )?; + let eh = geonetworking::en302636_4_1::ExtendedHeader::GAC(geoanycast); + (eh, Some(atype), hop_limit) + } + msg::GnTransport::Gbc => { + if self.gn_area.position.is_none() { + return Err("Failed to create GAC: gnArea.position missing".to_string()); + } + + *seq_no += 1; + + let (atype, geoanycast) = Self::make_geobcast( + address, + station_type, + own_position, + time, + self.gn_area, + *seq_no, + )?; + let eh = geonetworking::en302636_4_1::ExtendedHeader::GBC(geoanycast); + (eh, Some(atype), hop_limit) + } + msg::GnTransport::Tsb => { + *seq_no += 1; + + ( + crate::v2x_tx::gn::make_tsb_eh( + address, + station_type, + own_position, + time, + *seq_no, + ), + None, + hop_limit, + ) + } + msg::GnTransport::Shb => ( + crate::v2x_tx::gn::make_shb_eh(address, station_type, own_position, time), + None, + 1, + ), + }) + } + + fn make_payload( + &self, + ) -> Result<(Vec, c_its_parser::standards::extensions::ItsMessageId), alloc::string::String> + { + #[allow(clippy::cast_possible_truncation)] + let message_id = (self.message_type as u8).try_into()?; + Ok((self.payload.clone(), message_id)) + } +} + +impl msg::RawMsgTx { + fn make_geobcast( + address: [u8; 6], + station_type: geonetworking::en302636_4_1::StationType, + own_position: &crate::applogic::PositionState, + time: chrono::DateTime, + gn_area: msg::GnArea, + sequence_number: u16, + ) -> Result< + ( + c_its_parser::gn::en302636_4_1::AreaType, + geonetworking::en302636_4_1::GeoBroadcast, + ), + alloc::string::String, + > { + let source_position_vector = + crate::v2x_tx::gn::make_lpv(address, station_type, own_position, time); + + let (atype, dist_a, dist_b, angle) = match gn_area.area() { + msg::GnAreaShape::EtsiAreashapeCircle => ( + c_its_parser::gn::en302636_4_1::AreaType::Circular, + gn_area.dist_a, + None, + None, + ), + msg::GnAreaShape::EtsiAreashapeRectangle => ( + c_its_parser::gn::en302636_4_1::AreaType::Rectangular, + gn_area.dist_a, + gn_area.dist_b, + gn_area.angle, + ), + msg::GnAreaShape::EtsiAreashapeEllipsis => ( + c_its_parser::gn::en302636_4_1::AreaType::Ellipsoidal, + gn_area.dist_a, + gn_area.dist_b, + gn_area.angle, + ), + }; + + #[allow(clippy::cast_possible_truncation)] + let latitude_deg = own_position.position.y() as f32; + #[allow(clippy::cast_possible_truncation)] + let longitude_deg = own_position.position.x() as f32; + let distance_a = dist_a + .map(|v| { + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] + let val = v as u16; + val + }) + .unwrap_or_default(); + let distance_b = dist_b + .map(|v| { + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] + let val = v as u16; + val + }) + .unwrap_or_default(); + let angle = angle + .map(|v| { + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] + let val = v as u16; + val + }) + .unwrap_or_default(); + + let gac = geonetworking::en302636_4_1::GeoAnycast::try_from_values( + sequence_number, + source_position_vector, + latitude_deg, + longitude_deg, + distance_a, + distance_b, + angle, + ) + .map_err(|err| alloc::format!("{err}"))?; + + Ok((atype, gac)) + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::init_test_env_logger; + + impl From for msg::SerialInputMsg { + fn from(value: msg::RawMsgTx) -> Self { + use msg::serial_input_msg::Payload; + + Self { + payload: Some(Payload::TxMsg(value)), + } + } + } + + impl From for msg::SerialInputMsg { + fn from(value: msg::PositionState) -> Self { + use msg::serial_input_msg::Payload; + + Self { + payload: Some(Payload::Position(value)), + } + } + } + + // manual example data: + // SerialInputMsg(RawMsgTx): 5632582b0040123e0a340202c173256817284029d5b3400f1feaf01ffffffc23b7743e00000fc0007e8138250737feebfff600000dffff7ffff1ce40400010011807220028020d0a + // SerialInputMsg(PositionState): 5632582b00170a1509a0bbe2a29f010000120a0d00002842150000b8410d0a + + #[test] + fn uart_parser() { + // generate some test UART input messages + let raw_tx = msg::RawMsgTx { + payload: crate::testdata::CAM_SMALL.to_vec(), + message_type: 1, + gn_transport: msg::GnTransport::Shb.into(), + gn_area: msg::GnArea { + area: None, + position: None, + dist_a: None, + dist_b: None, + angle: None, + }, + hop_limit: 2, + station_type: None, + }; + let raw_tx_buf = serialize_serial_msg::(&raw_tx.clone().into()); + + let pos_state = msg::PositionState { + timestamp_ms: 1785144196000, + position: msg::Position { + latitude_deg: 42., + longitude_deg: 23., + }, + heading: None, + speed: None, + }; + let pos_state_buf = serialize_serial_msg::(&pos_state.clone().into()); + + let mut parser = Parser::default(); + + // feed individual complete messages to parser + let output = parser.parse(&raw_tx_buf); + assert_eq!(1, output.len()); + assert_eq!( + &msg::SerialInputMsg::from(raw_tx.clone()), + output.get(0).unwrap() + ); + + let output = parser.parse(&pos_state_buf); + assert_eq!(1, output.len()); + assert_eq!( + &msg::SerialInputMsg::from(pos_state.clone()), + output.get(0).unwrap() + ); + + // feed multiple complete messages to parser + let mut combined_buf = pos_state_buf.clone(); + combined_buf.extend_from_slice(&raw_tx_buf); + + let output = parser.parse(&combined_buf); + assert_eq!(2, output.len()); + assert_eq!( + &msg::SerialInputMsg::from(pos_state.clone()), + output.get(0).unwrap() + ); + assert_eq!( + &msg::SerialInputMsg::from(raw_tx.clone()), + output.get(1).unwrap() + ); + + // feed partial messages to parser + let buf1 = &raw_tx_buf[0..3]; + let buf2 = &raw_tx_buf[3..]; + + let output = parser.parse(buf1); + assert_eq!(0, output.len()); + + let output = parser.parse(buf2); + assert_eq!(1, output.len()); + assert_eq!( + &msg::SerialInputMsg::from(raw_tx.clone()), + output.get(0).unwrap() + ); + + // feed messages with garbage in between + let mut buf1 = vec![0xabu8, 0xcd]; // prepend some garbage + buf1.extend_from_slice(&raw_tx_buf[0..3]); + let mut buf2 = (&raw_tx_buf[3..]).to_vec(); + buf2.extend_from_slice(&[0xfeu8, 0x42]); // append some garbage + buf2.extend_from_slice(&pos_state_buf); // append second message + buf2.extend_from_slice(&[0x11u8, 0x22]); // append some more garbage + + let output = parser.parse(&buf1); + assert_eq!(0, output.len()); + + let output = parser.parse(&buf2); + assert_eq!(2, output.len()); + assert_eq!( + &msg::SerialInputMsg::from(raw_tx.clone()), + output.get(0).unwrap() + ); + assert_eq!( + &msg::SerialInputMsg::from(pos_state.clone()), + output.get(1).unwrap() + ); + } +} diff --git a/src/main.rs b/src/main.rs index 03ef304..772adb6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,13 @@ )] // #![deny(clippy::large_stack_frames)] +// we can't use UART I/O and GNSS at the same time +#[cfg(all(feature = "uart", feature = "gnss"))] +compile_error!("feature \"uart\" and feature \"gnss\" cannot be enabled at the same time"); +// autonomous CAM generation need a time and position source (aka _gnss) +#[cfg(all(feature = "cam_tx", not(feature = "_gnss")))] +compile_error!("feature \"cam_tx\" needs a time and position source (either GNSS or UART)"); + use alloc::string::ToString as _; use alloc::vec::Vec; use core::cell::RefCell; @@ -35,17 +42,18 @@ extern crate alloc; mod applogic; mod cache; mod geo_alg; +#[cfg(feature = "uart")] mod io; mod radio; #[cfg(feature = "screen")] mod screen; -#[cfg(feature = "cam_tx")] +#[cfg(any(feature = "cam_tx", feature = "uart"))] mod v2x_tx; const WIFI_CHANNEL: radio::Channel = 180; #[cfg(feature = "spat")] const SPAT_RATE_LIMIT: u8 = 5; // keep every n-th message -#[cfg(feature = "cam_tx")] +#[cfg(any(feature = "cam_tx", feature = "uart"))] const GN_IS_MOBILE: bool = true; #[cfg(feature = "cam_tx")] const OWN_STATION_TYPE: ItsStationType = ItsStationType::Cyclist; @@ -60,7 +68,7 @@ const SERIAL_FIFO_SIZE_THLD: u8 = 100; // hardware has max. 128 byte // For more information see: esp_bootloader_esp_idf::esp_app_desc!(); -#[cfg(feature = "gnss")] +#[cfg(feature = "_gnss")] static GNSS_UPDATE: Mutex>> = Mutex::new(RefCell::new(None)); static WIFI_RX_QUEUE: Mutex>>>> = Mutex::new(RefCell::new(None)); #[cfg(feature = "_uart")] @@ -136,6 +144,18 @@ async fn main(spawner: Spawner) -> ! { screen }; + // Setup UART I/O channel + #[cfg(feature = "uart")] + setup_uart0( + 115_200, + Some(b'\n'), + peripherals.GPIO12, + peripherals.GPIO11, + peripherals.UART0, + serial_handler, + ) + .expect("Failed to initialize UART"); + // Setup WiFi // sadly, it doesn't work any more when we move `esp_radio::wifi::new` to `setup_wifi_sniffer` let (mut wifi_controller, interfaces) = esp_radio::wifi::new( @@ -178,24 +198,29 @@ async fn main(spawner: Spawner) -> ! { #[cfg(feature = "gnss")] info!("GNSS task started, waiting for GNSS fix..."); - let mut state = applogic::State::new(); - #[cfg(feature = "cam_tx")] - let (mut seq_no, mut cam, mac_bytes) = { - let station_id = v2x_tx::make_station_id(mac); - info!("V2X Station ID: {station_id}"); - + #[cfg(any(feature = "cam_tx", feature = "uart"))] + let (mut seq_no, mac_bytes) = { // unwrap is fine since [`esp_hal::efuse::MacAddress`] is just an `[u8; 6]` let mac_bytes = mac.as_bytes().try_into().unwrap(); - let cam = applogic::cam_tx::CamState::new( + (0, mac_bytes) + }; + + let mut state = applogic::State::new(); + #[cfg(feature = "cam_tx")] + let mut cam = { + let station_id = v2x_tx::make_station_id(mac); + info!("V2X Station ID: {station_id}"); + + applogic::cam_tx::CamState::new( station_id, OWN_STATION_TYPE, OWN_VEHICLE_LENGTH, OWN_VEHICLE_WIDTH, - ); - - (0, cam, mac_bytes) + ) }; + #[cfg(feature = "uart")] + let mut uart_input = io::Parser::default(); loop { embassy_time::Timer::after(embassy_time::Duration::from_millis(1)).await; @@ -214,11 +239,56 @@ async fn main(spawner: Spawner) -> ! { }); if let Some(data) = new_data { - warn!("TODO: New UART data: {data:x?}"); + #[cfg(feature = "uart")] + { + for msg in uart_input.parse(&data) { + match msg.payload { + Some(io::msg::serial_input_msg::Payload::Position(fix)) => { + let pos_state = fix.into(); + + critical_section::with(|cs| { + let gnss_update_ref = GNSS_UPDATE.borrow(cs); + gnss_update_ref.replace(Some(pos_state)); + }); + } + Some(io::msg::serial_input_msg::Payload::TxMsg(tx_request)) => { + use v2x_tx::GnItsPayload as _; + + info!( + "UART: Got new TX message: Type {} with {} bytes", + tx_request.message_type, + tx_request.payload.len() + ); + let own_position = state.pos(); + let time = state.time().and_utc(); + + match tx_request.encode_packet( + mac_bytes, + own_position, + time, + GN_IS_MOBILE, + &mut seq_no, + ) { + Ok(data) => { + if let Err(err) = radio::send_80211_bcast_frame( + &mut wlan_iface, + &mac_bytes.clone(), + &data, + ) { + warn!("Failed to send 802.11 frame: {err}"); + } + } + Err(err) => warn!("Failed to create GN message: {err}"), + } + } + None => {} + } + } + } } } - #[cfg(feature = "gnss")] + #[cfg(feature = "_gnss")] { let mut new_position = false; critical_section::with(|cs| { @@ -245,7 +315,7 @@ async fn main(spawner: Spawner) -> ! { Ok(data) => { if let Err(err) = radio::send_80211_bcast_frame( &mut wlan_iface, - mac.as_bytes(), + &mac_bytes.clone(), &data, ) { warn!("Failed to send 802.11 frame: {err}"); @@ -472,6 +542,18 @@ fn handle_frame(frame: wifi::sniffer::PromiscuousPkt<'_>) { error!("V2X RX queue is full"); } }); + + // Send data to UART + #[cfg(feature = "uart")] + { + let message = io::msg::RawMsgRx { + msg_type: message_id.as_u8().into(), + payload: frame.data.to_vec(), + } + .serialize_uart_proto(); + + send_uart0(&message); + } } } } diff --git a/src/test.rs b/src/test.rs index 7de1bf8..6bb20d2 100644 --- a/src/test.rs +++ b/src/test.rs @@ -6,6 +6,7 @@ extern crate alloc; mod applogic; mod cache; mod geo_alg; +mod io; mod radio; mod testdata; mod v2x_tx; diff --git a/src/testdata.rs b/src/testdata.rs index 567410d..fa884d6 100644 --- a/src/testdata.rs +++ b/src/testdata.rs @@ -455,3 +455,11 @@ pub const CAM: &[u8] = &[ 0x00, 0x06, 0xc6, 0x70, 0x00, 0xdf, 0x80, 0x8d, 0x5f, 0xde, 0x66, 0x27, 0x00, 0x07, 0x3c, 0x04, 0x76, 0xfd, 0x67, 0x31, 0x9c, 0x00, 0x58, 0x60, 0x41, 0x37, 0xd3, 0x15, 0x89, 0xc0, 0x06, 0xdc, ]; + +#[cfg(any(feature = "cam_tx", feature = "uart"))] +pub const CAM_SMALL: &[u8] = &[ + 0x2, 0x2, 0xc1, 0x73, 0x25, 0x68, 0x17, 0x28, 0x40, 0x29, 0xd5, 0xb3, 0x40, 0xf, 0x1f, 0xea, + 0xf0, 0x1f, 0xff, 0xff, 0xfc, 0x23, 0xb7, 0x74, 0x3e, 0x0, 0x0, 0xf, 0xc0, 0x0, 0x7e, 0x81, + 0x38, 0x25, 0x7, 0x37, 0xfe, 0xeb, 0xff, 0xf6, 0x0, 0x0, 0xd, 0xff, 0xff, 0x7f, 0xff, 0xf1, + 0xce, 0x40, 0x40, 0x0, +]; diff --git a/src/v2x_tx/gn.rs b/src/v2x_tx/gn.rs index 828c74d..4b2b0b9 100644 --- a/src/v2x_tx/gn.rs +++ b/src/v2x_tx/gn.rs @@ -67,7 +67,7 @@ pub fn make_gbc_eh( ) } -fn make_lpv( +pub fn make_lpv( address: [u8; 6], station_type: geonetworking::en302636_4_1::StationType, own_position: &crate::applogic::PositionState, diff --git a/src/v2x_tx/mod.rs b/src/v2x_tx/mod.rs index e9b1a28..ee48578 100644 --- a/src/v2x_tx/mod.rs +++ b/src/v2x_tx/mod.rs @@ -6,7 +6,7 @@ use c_its_parser::gn::{self as geonetworking, Encode}; pub mod gn; -#[cfg(feature = "esp")] +#[cfg(all(feature = "esp", feature = "cam_tx"))] /// Creates a station ID from the WLAN MAC address /// /// Will use `0xC173` as static prefix and uses last 16 bit from MAC address for the remainder. @@ -40,11 +40,14 @@ pub trait GnItsPayload { own_position: &crate::applogic::PositionState, time: chrono::DateTime, seq_no: &mut u16, - ) -> ( - geonetworking::en302636_4_1::ExtendedHeader, - Option, - u8, - ); + ) -> Result< + ( + geonetworking::en302636_4_1::ExtendedHeader, + Option, + u8, + ), + alloc::string::String, + >; /// Creates the ITS payload (without BTP header) fn make_payload( @@ -74,7 +77,8 @@ pub trait GnItsPayload { let payload_length = (payload.len()) as u16; // get extended header (depending on message type and sometimes content as well) - let (eh, area_type, maximum_hop_limit) = self.make_eh(address, own_position, time, seq_no); + let (eh, area_type, maximum_hop_limit) = + self.make_eh(address, own_position, time, seq_no)?; // Build GN headers let traffic_class = geonetworking::en302636_4_1::TrafficClass::try_new( From 2c50668e7e74ed67d182440022407466963b7d25 Mon Sep 17 00:00:00 2001 From: Jannik Beyerstedt Date: Fri, 31 Jul 2026 12:04:46 +0200 Subject: [PATCH 10/27] 802.11-TX: Reduce transmit power, add notes about legal requirements --- Readme.md | 19 +++++++++++++++++++ src/main.rs | 5 +++++ 2 files changed, 24 insertions(+) diff --git a/Readme.md b/Readme.md index c8dcf5c..1dda26f 100644 --- a/Readme.md +++ b/Readme.md @@ -32,6 +32,25 @@ Just prints a line for each received CAM. Generates and transmits a CAM when a GNSS update was received. Depends on the `gnss` feature. +Note: In germany, using the 5875 to 5915 MHz frequency band is only permitted for C-ITS use cases which implement transmit power reduction measures with at least 30 dB reduction range (or equivalent measures) and have a maximum EIRP of 33 dBm. +This is based on EU regulation 2014/53/EU, so it applies to other EU countries in similar ways. +The relevant harmonized standard is ETSI EN 302 571 which mandates (excerpt of most relevant points): + +- a maximum power spectral density of 23 dBm/Mhz EIRP +- transmit power control (TCP) in a range from 3 dBm up to the maximum output power of the equipment + * used for CEN DSRC and HDR DSRC interference mitigation by implementing measures from ETSI TS 102 792 + + Mode A or B: Output power less than 10 dBm EIRP and less than -65 or -45 dBm/MHz EIRP in CEN DSRC band -> **needs to be measured** + + Mode C: Maximum transmission time of less than 1 ms and at least a few 100 ms between transmission (realistically) -> **can't be ensured with big messages** + + Mode D: "any" realistically possible transmission time, but at least 500 ms between transmissions for ~5 ITS stations/ 1000ms for 10 ITS stations -> **safe to assume when we only send CAMs at 1 Hz** + + But modes C and D also need to: + - use protected zone information from CAM messages + - use a protected zone database, e.g. from www.asecap-pzdb.com + * used for dynamic congestion control (DCC) + + mandatory mechanism described in ETSI EN 302 571, chapter 4.2.10 + +This is **not** implemented in this application! +**So legally, you're not allowed to send with this.** + ### UART Enable UART I/O protocol according to [docs/uart-protocol.md](./docs/uart-protocol.md). diff --git a/src/main.rs b/src/main.rs index 772adb6..64ed528 100644 --- a/src/main.rs +++ b/src/main.rs @@ -51,6 +51,7 @@ mod screen; mod v2x_tx; const WIFI_CHANNEL: radio::Channel = 180; +const WIFI_TXPOWER: i8 = 10; // dBm, 2..20 #[cfg(feature = "spat")] const SPAT_RATE_LIMIT: u8 = 5; // keep every n-th message #[cfg(any(feature = "cam_tx", feature = "uart"))] @@ -163,9 +164,13 @@ async fn main(spawner: Spawner) -> ! { esp_radio::wifi::ControllerConfig::default(), ) .expect("Failed to initialize Wi-Fi controller"); + if let Err(err) = wifi_controller.set_band_mode(esp_radio::wifi::BandMode::_5G) { error!("Failed to set WiFi to 5GHz only: {err}"); } + if let Err(err) = wifi_controller.set_max_tx_power(WIFI_TXPOWER * 4) { + error!("Failed to set WiFi TX power: {err}"); + } critical_section::with(|cs| { WIFI_RX_QUEUE.borrow(cs).replace(Some(ArrayQueue::new(2))); From 71161d1d991a49c81ecff9b0d0cfeb5034aaac0d Mon Sep 17 00:00:00 2001 From: Jannik Beyerstedt Date: Fri, 31 Jul 2026 12:13:54 +0200 Subject: [PATCH 11/27] Add scripts to run/ flash the supported feature-sets --- Readme.md | 6 +++--- run-smartphone.sh | 6 ++++++ run-standalone-cam.sh | 6 ++++++ run-standalone-no_cam.sh | 6 ++++++ 4 files changed, 21 insertions(+), 3 deletions(-) create mode 100755 run-smartphone.sh create mode 100755 run-standalone-cam.sh create mode 100755 run-standalone-no_cam.sh diff --git a/Readme.md b/Readme.md index 1dda26f..ace338e 100644 --- a/Readme.md +++ b/Readme.md @@ -89,10 +89,10 @@ Additional features are available through feature flags: - `cam_tx`: Enable CAM generation and transmission (needs time and position source) Officially supported feature combinations are: -- `esp,spat,gnss,screen` (default features) -- `esp,spat,gnss,screen,cam_tx` +- `esp,spat,gnss,screen` (default features, "standalone") +- `esp,spat,gnss,screen,cam_tx` ("standalone with CAM") - `esp,spat,gnss,screen,cam_tx,cam,denm` -- `esp,uart,cam,denm` +- `esp,uart,cam,denm` ("smartphone companion") Connect your ESP32-C5 and run: diff --git a/run-smartphone.sh b/run-smartphone.sh new file mode 100755 index 0000000..ee5170d --- /dev/null +++ b/run-smartphone.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +## Builds common config for smartphone connected device +## -> only receive CAM and DENMs via UART (TODO: and BLE) + +cargo run --release --no-default-features -F esp,cam,denm,uart diff --git a/run-standalone-cam.sh b/run-standalone-cam.sh new file mode 100755 index 0000000..9bfafbe --- /dev/null +++ b/run-standalone-cam.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +## Builds common config for standalone device with automatic CAM transmission +## -> enable SPAT/ GLOSA with GNSS and a screen + CAM transmission + +cargo run --release -F cam_tx diff --git a/run-standalone-no_cam.sh b/run-standalone-no_cam.sh new file mode 100755 index 0000000..e61d25c --- /dev/null +++ b/run-standalone-no_cam.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +## Builds common config for standalone device +## -> enable SPAT/ GLOSA with GNSS and a screen + +cargo run --release From ac6a3e398ecc091e603324887cdc89d4264113ff Mon Sep 17 00:00:00 2001 From: Jannik Beyerstedt Date: Sun, 12 Jul 2026 00:14:29 +0200 Subject: [PATCH 12/27] Hardware: Add PCB layout --- .gitignore | 4 + hardware/c_its-companion-v0.1-back.jpg | Bin 0 -> 148719 bytes hardware/c_its-companion-v0.1-front.jpg | Bin 0 -> 145854 bytes .../c_its-companion/c_its-companion.kicad_pcb | 8034 +++++++++++ .../c_its-companion/c_its-companion.kicad_prl | 138 + .../c_its-companion/c_its-companion.kicad_pro | 744 + .../c_its-companion/c_its-companion.kicad_sch | 4985 +++++++ .../fabrication-toolkit-options.json | 1 + hardware/c_its-companion/fp-lib-table | 4 + .../Seeed_Studio_XIAO_Series.kicad_sym | 11661 ++++++++++++++++ .../library/XIAO-ESP32-C5-DIP.kicad_mod | 542 + .../library/XIAO-ESP32-C5-SMD-DIP.kicad_mod | 558 + .../library/XIAO-ESP32-C5-SMD.kicad_mod | 445 + .../production/C-ITS_Companion_v0.1.zip | Bin 0 -> 100901 bytes hardware/c_its-companion/sym-lib-table | 4 + 15 files changed, 27120 insertions(+) create mode 100644 hardware/c_its-companion-v0.1-back.jpg create mode 100644 hardware/c_its-companion-v0.1-front.jpg create mode 100644 hardware/c_its-companion/c_its-companion.kicad_pcb create mode 100644 hardware/c_its-companion/c_its-companion.kicad_prl create mode 100644 hardware/c_its-companion/c_its-companion.kicad_pro create mode 100644 hardware/c_its-companion/c_its-companion.kicad_sch create mode 100644 hardware/c_its-companion/fabrication-toolkit-options.json create mode 100644 hardware/c_its-companion/fp-lib-table create mode 100644 hardware/c_its-companion/library/Seeed_Studio_XIAO_Series.kicad_sym create mode 100644 hardware/c_its-companion/library/XIAO-ESP32-C5-DIP.kicad_mod create mode 100644 hardware/c_its-companion/library/XIAO-ESP32-C5-SMD-DIP.kicad_mod create mode 100644 hardware/c_its-companion/library/XIAO-ESP32-C5-SMD.kicad_mod create mode 100644 hardware/c_its-companion/production/C-ITS_Companion_v0.1.zip create mode 100644 hardware/c_its-companion/sym-lib-table diff --git a/.gitignore b/.gitignore index deb0257..1acf1bd 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,7 @@ target/ # Ignore .DS_Store file in mac **/.DS_Store + +# Ignore KiCad backups and lock files +hardware/c_its-companion/c_its-companion-backups/ +*.kicad_pro.lck diff --git a/hardware/c_its-companion-v0.1-back.jpg b/hardware/c_its-companion-v0.1-back.jpg new file mode 100644 index 0000000000000000000000000000000000000000..969862400169b2debe51bf728ca9ccf0a0f6abcc GIT binary patch literal 148719 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≦W^vFvsT%isj-`H-(H*|n$gt5H{)l;pPzqT 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 literal 0 HcmV?d00001 diff --git a/hardware/c_its-companion-v0.1-front.jpg b/hardware/c_its-companion-v0.1-front.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2386379ce7aa76af99918f3f004c5e94111d7310 GIT binary patch literal 145854 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≷oBCMy-*bi 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 z%0D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 Date: Sun, 26 Jul 2026 21:34:18 +0200 Subject: [PATCH 13/27] gnss: Add support for ublox GNSS --- Cargo.lock | 24 ++++++++++ Cargo.toml | 7 ++- Readme.md | 13 ++--- feat-permut.sh | 8 ++-- src/main.rs | 125 +++++++++++++++++++++++++++++++++++++++++++++---- src/ubx.rs | 106 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 262 insertions(+), 21 deletions(-) create mode 100644 src/ubx.rs diff --git a/Cargo.lock b/Cargo.lock index 3fba778..b3d5c4c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1178,6 +1178,7 @@ dependencies = [ "profont", "prost", "prost-build", + "ublox", ] [[package]] @@ -2490,6 +2491,29 @@ version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" +[[package]] +name = "ublox" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bcc96bd937066775fb87034e1dbf6d7165a8975c74ddec1105e31b4a050538b" +dependencies = [ + "bitflags", + "chrono", + "num-traits", + "ublox_derive", +] + +[[package]] +name = "ublox_derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5eda7d1c1151a40ae85c7c065a19959909573535034e18e1015b738993cd2c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "ufmt-write" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index eef57d9..55f42c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ path = "./src/test.rs" required-features = ["std", "cam"] [features] -default = ["esp", "spat", "gnss", "screen"] +default = ["esp", "spat", "gnss_ubx", "screen"] # build for ESP (needed to build test for `host-tuple`) esp = [ @@ -33,8 +33,10 @@ esp = [ ] std = ["c-its-parser/std"] +# enable u-blox GNSS module +gnss_ubx = ["_gnss", "_uart", "dep:ublox"] # enable L67K GNSS module -gnss = ["_gnss", "dep:embassy_gps"] +gnss_l67k = ["_gnss", "dep:embassy_gps"] # echo CAM data on serial cam = ["c-its-parser/cam"] # send CAM autonomously @@ -107,6 +109,7 @@ mipidsi = { version = "0.10.0", optional = true } num-traits = { version = "0.2.19", default-features = false, features = ["libm"] } profont = { version = "0.7.0", optional = true } prost = { version = "0.14", optional = true, default-features = false, features = ["derive"] } +ublox = { version = "0.10", optional = true, default-features = false, features = ["alloc", "ubx_proto27"] } [dev-dependencies] assert_float_eq = "1.2.0" diff --git a/Readme.md b/Readme.md index ace338e..2ae3aab 100644 --- a/Readme.md +++ b/Readme.md @@ -5,15 +5,16 @@ Displays different C-ITS receive-only use cases for cheap. ## Hardware Requirements The code is currently tailored to: - Seeed XIAO ESP32-C5 (8MB PSRAM) -- Seeed XIAO L67K (connected on GPIO12 (RX), GPIO11 (TX), GPIO1 (wakeup) and GPIO25 (reset)) -only available with `gnss` feature (which is included in `spat` feature) +- U-Blox M10Q GNSS (connected on GPIO12 (RX), GPIO11 (TX), and GPIO25 (reset)) +or Seeed XIAO L67K (connected on GPIO12 (RX), GPIO11 (TX), GPIO1 (wakeup) and GPIO25 (reset)) +only available with `gnss_ubx` or `gnss_l67k` feature (which is included in `spat` feature) - Some ST7789 172*320px screen (connected on GPIO8 (SCL/ SCK), GPIO10 (SDA/ MOSI), GPIO7 (reset), GPIO23 (DC), GPIO24 (CS), TODO GPIO0 (BL)) only available with `screen` feature (which is included in `spat` feature) ## Features ### GNSS -Enable L67K GNSS module and in main application logic/ state. +Enable u-blox or L67K GNSS module and in main application logic/ state. ### SPAT Takes the current position and heading from the GNSS receiver and displays the signal phases of the upcoming intersection. @@ -89,9 +90,9 @@ Additional features are available through feature flags: - `cam_tx`: Enable CAM generation and transmission (needs time and position source) Officially supported feature combinations are: -- `esp,spat,gnss,screen` (default features, "standalone") -- `esp,spat,gnss,screen,cam_tx` ("standalone with CAM") -- `esp,spat,gnss,screen,cam_tx,cam,denm` +- `esp,spat,gnss_ubx,screen` (default features, "standalone") +- `esp,spat,gnss_ubx,screen,cam_tx` ("standalone with CAM") +- `esp,spat,gnss_ubx,screen,cam_tx,cam,denm` - `esp,uart,cam,denm` ("smartphone companion") Connect your ESP32-C5 and run: diff --git a/feat-permut.sh b/feat-permut.sh index 2019bef..9c08d65 100755 --- a/feat-permut.sh +++ b/feat-permut.sh @@ -9,12 +9,12 @@ extended=${2:-false} features_base=( uart,cam,denm - spat,gnss,screen - spat,gnss,screen,cam_tx + spat,gnss_ubx,screen + spat,gnss_ubx,screen,cam_tx ) features_more=( - spat,gnss,screen,cam,denm - spat,gnss,screen,cam_tx,cam,denm + spat,gnss_ubx,screen,cam,denm + spat,gnss_ubx,screen,cam_tx,cam,denm ) features_test=( spat,cam,denm,uart diff --git a/src/main.rs b/src/main.rs index 64ed528..5320951 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,11 +8,16 @@ // #![deny(clippy::large_stack_frames)] // we can't use UART I/O and GNSS at the same time -#[cfg(all(feature = "uart", feature = "gnss"))] -compile_error!("feature \"uart\" and feature \"gnss\" cannot be enabled at the same time"); +#[cfg(all(feature = "uart", feature = "gnss_ubx", feature = "gnss_l67k"))] +compile_error!( + "feature \"uart\" and features \"gnss_ubx\" or \"gnss_l67k\" cannot be enabled at the same time" +); // autonomous CAM generation need a time and position source (aka _gnss) #[cfg(all(feature = "cam_tx", not(feature = "_gnss")))] compile_error!("feature \"cam_tx\" needs a time and position source (either GNSS or UART)"); +// we can't have both GNSS drivers enabled at the same time +#[cfg(all(feature = "gnss_ubx", feature = "gnss_l67k"))] +compile_error!("feature \"gnss_ubx\" and feature \"gnss_l67k\" cannot be enabled at the same time"); use alloc::string::ToString as _; use alloc::vec::Vec; @@ -26,7 +31,7 @@ use c_its_parser::standards::extensions::ItsStationType; use critical_section::Mutex; use crossbeam_queue::ArrayQueue; use embassy_executor::Spawner; -#[cfg(feature = "gnss")] +#[cfg(feature = "gnss_l67k")] use embassy_gps::gps::l76k; use esp_backtrace as _; use esp_hal::clock::CpuClock; @@ -47,6 +52,8 @@ mod io; mod radio; #[cfg(feature = "screen")] mod screen; +#[cfg(feature = "gnss_ubx")] +mod ubx; #[cfg(any(feature = "cam_tx", feature = "uart"))] mod v2x_tx; @@ -184,14 +191,15 @@ async fn main(spawner: Spawner) -> ! { let mac = esp_hal::efuse::base_mac_address(); info!("WiFi MAC: {mac}"); - // Setup GNSS: Seeed XIAO L67K: + // Setup GNSS: + // Seeed XIAO L67K: // RX: D7/ GPIO12 // TX: D6/ GPIO11 // WAKEUP: D0/ GPIO1 -> HIGH for active, LOW for Sleep // RESET: D2/ GPIO25 -> HIGH for normal, LOW for Reset - #[cfg(feature = "gnss")] + #[cfg(feature = "gnss_l67k")] spawner.spawn( - gnss_task( + gnss_l67k_task( peripherals.GPIO1, peripherals.GPIO25, peripherals.UART0, @@ -200,7 +208,29 @@ async fn main(spawner: Spawner) -> ! { ) .expect("Failed to spawn GNSS task"), ); - #[cfg(feature = "gnss")] + + // U-Blox module: + // RX: D7/ GPIO12 + // TX: D6/ GPIO11 + // RESET: D2/ GPIO25 -> HIGH for normal, LOW for Reset + #[cfg(feature = "gnss_ubx")] + { + setup_uart0( + 9600, + Some(b'\n'), + peripherals.GPIO12, + peripherals.GPIO11, + peripherals.UART0, + serial_handler, + ) + .expect("Failed to initialize UART for GNSS"); + + // do some settings (UART receive and parsing runs in main loop) + spawner + .spawn(gnss_ubx_config(peripherals.GPIO25).expect("Failed to spawn GNSS config task")); + } + + #[cfg(feature = "_gnss")] info!("GNSS task started, waiting for GNSS fix..."); #[cfg(any(feature = "cam_tx", feature = "uart"))] @@ -226,6 +256,8 @@ async fn main(spawner: Spawner) -> ! { }; #[cfg(feature = "uart")] let mut uart_input = io::Parser::default(); + #[cfg(feature = "gnss_ubx")] + let mut ubx_parser = ublox::Parser::default(); loop { embassy_time::Timer::after(embassy_time::Duration::from_millis(1)).await; @@ -290,6 +322,49 @@ async fn main(spawner: Spawner) -> ! { } } } + #[cfg(feature = "gnss_ubx")] + { + let mut it = ubx_parser.consume_ubx(&data); + loop { + match it.next() { + Some(Ok(ublox::UbxPacket::Proto27(packet_ref))) => match &packet_ref { + ublox::proto27::PacketRef::MonVer(packet) => { + println!("{}", ubx::pretty_print_mon_ver(packet)); + } + ublox::proto27::PacketRef::MonComms(packet) => { + println!("{}", ubx::pretty_print_mon_comms(packet)); + } + ublox::proto27::PacketRef::MonRf(packet) => { + println!("{}", ubx::pretty_print_mon_rf(packet)); + } + ublox::proto27::PacketRef::NavSat(packet) => { + println!("{}", ubx::pretty_print_nav_sat(packet)); + } + ublox::proto27::PacketRef::NavPvt(pvt) => { + match applogic::GnssFix::try_from(pvt) { + Ok(pos_state) => { + critical_section::with(|cs| { + let gnss_update_ref = GNSS_UPDATE.borrow(cs); + gnss_update_ref.replace(Some(pos_state)); + }); + } + Err(err) => warn!("{err}"), + } + } + _ => { + println!("UBX: {packet_ref:?}"); + } + }, + Some(Err(_)) => { + // Received a malformed packet + } + None => { + // The internal buffer is now empty + break; + } + } + } + } } } @@ -626,10 +701,10 @@ fn get_its_header<'p>( } } -#[cfg(feature = "gnss")] +#[cfg(feature = "gnss_l67k")] #[allow(clippy::large_stack_frames, reason = "GNSS driver needs some space")] #[embassy_executor::task] -async fn gnss_task( +async fn gnss_l67k_task( gps_wakeup: esp_hal::peripherals::GPIO1<'static>, gps_reset: esp_hal::peripherals::GPIO25<'static>, uart: esp_hal::peripherals::UART0<'static>, @@ -679,6 +754,38 @@ async fn gnss_task( } } +#[cfg(feature = "gnss_ubx")] +#[embassy_executor::task] +async fn gnss_ubx_config(reset_pin: esp_hal::peripherals::GPIO25<'static>) { + // reset the module + let mut gnss_reset = esp_hal::gpio::Output::new( + reset_pin, + esp_hal::gpio::Level::Low, + esp_hal::gpio::OutputConfig::default(), + ); + embassy_time::Timer::after(embassy_time::Duration::from_millis(2)).await; + gnss_reset.set_high(); + + embassy_time::Timer::after(embassy_time::Duration::from_secs(1)).await; + + info!("UBX: Enabling ubx protocol with NAV-PVT and NAV-SAT"); + let buffer = ublox::packets::cfg_val::CfgValSetBuilder { + version: 1, + layers: ublox::packets::cfg_val::CfgLayerSet::RAM, + reserved1: 0, + cfg_data: &[ + ublox::cfg_val::CfgVal::Uart1OutProtNmea(false), + ublox::cfg_val::CfgVal::Uart1OutProtUbx(true), + // CFG-MSGOUT-UBX_NAV_PVT_UART1 + ublox::cfg_val::CfgVal::MsgOutUbxNavPvtUart1(1), + // CFG-MSGOUT-UBX_NAV_SAT_UART1 + // ublox::cfg_val::CfgVal::MsgOutUbxNavSatUart1(10), + ], + } + .into_packet_vec(); + send_uart0(&buffer); +} + #[cfg(feature = "screen")] fn log_screen_error(error: &E) where diff --git a/src/ubx.rs b/src/ubx.rs new file mode 100644 index 0000000..934af7b --- /dev/null +++ b/src/ubx.rs @@ -0,0 +1,106 @@ +//! u-blox helpers +#![allow(unused)] + +use alloc::vec::Vec; + +pub fn pretty_print_mon_ver(packet: &ublox::mon_ver::MonVerRef) -> alloc::string::String { + alloc::format!( + "UBX-MON-VER: SW version: {} HW version: {}; Extensions: {:?}", + packet.software_version(), + packet.hardware_version(), + packet.extension().collect::>() + ) +} + +pub fn pretty_print_mon_comms(packet: &ublox::mon_comms::MonCommsRef) -> alloc::string::String { + alloc::format!( + "UBX-MON-COMMS: {} ports ({:?}), {} tx err, info {:?}", + packet.n_ports(), + packet.prot_ids(), + packet.tx_errors(), + packet + .ports() + .map(|v| alloc::format!("{v:?}")) + .collect::>() + ) +} + +pub fn pretty_print_mon_rf(packet: &ublox::mon_rf::MonRfRef) -> alloc::string::String { + alloc::format!( + "UBX-MON-RF: {:?}", + packet + .blocks() + .map(|v| alloc::format!("{v:?}")) + .collect::>() + ) +} + +pub fn pretty_print_nav_sat(packet: &ublox::nav_sat::NavSatRef) -> alloc::string::String { + // sort by quality flags + let mut sat_infos = packet.svs().collect::>(); + sat_infos.sort_unstable_by_key(|v| v.flags_raw() & 0x7); + sat_infos.reverse(); + + let sat_info_strings = sat_infos + .iter() + .map(|v| { + alloc::format!( + "- {}/{:3}: E {:3.0}, A {:3.0}, CNO {:2.1} dBHz, {:?}, {:?}", + v.gnss_id(), + v.sv_id(), + v.elev(), + v.azim(), + v.cno(), + v.flags().quality_ind(), + v.flags().health(), + ) + }) + .collect::>(); + + alloc::format!( + "UBX-NAV-SAT: num {:?}\n{}", + packet.num_svs(), + sat_info_strings.join("\n") + ) +} + +impl<'a> TryFrom<&ublox::nav_pvt::proto27::NavPvtRef<'a>> for crate::applogic::GnssFix { + type Error = alloc::string::String; + + fn try_from(value: &ublox::nav_pvt::proto27::NavPvtRef<'a>) -> Result { + let has_time = value.fix_type() == ublox::GnssFixType::Fix3D + || value.fix_type() == ublox::GnssFixType::GPSPlusDeadReckoning + || value.fix_type() == ublox::GnssFixType::TimeOnlyFix; + let has_posvel = value.fix_type() == ublox::GnssFixType::Fix3D + || value.fix_type() == ublox::GnssFixType::GPSPlusDeadReckoning; + + if !has_posvel || !has_time { + return Err(alloc::format!( + "NAV-PVT without valid fix: {:?}", + value.fix_type() + )); + } + + let pos: ublox::PositionLLA = value.into(); + let vel: ublox::Velocity = value.into(); + + let time: chrono::DateTime = value + .try_into() + .map_err(|err| alloc::format!("Failed to convert time: {err}"))?; + + let latitude_deg = pos.lat; + let longitude_deg = pos.lon; + #[allow(clippy::cast_precision_loss)] + let heading_deg = vel.heading as f32; + #[allow(clippy::cast_precision_loss)] + let speed_mps = vel.speed as f32; + + Ok(Self { + time: Some(time.naive_utc()), + latitude_deg, + longitude_deg, + heading_deg: Some(heading_deg), + speed_mps: Some(speed_mps), + }) + } +} From 666499c240bb22f118c6cf6d2ad92b1c9e254b0b Mon Sep 17 00:00:00 2001 From: Jannik Beyerstedt Date: Fri, 31 Jul 2026 16:01:43 +0200 Subject: [PATCH 14/27] Move core ESP32 V2X to own repo --- Cargo.lock | 17 ++- Cargo.toml | 5 +- src/applogic/cam_tx.rs | 61 +++++++-- src/applogic/mod.rs | 18 +-- src/io.rs | 18 +-- src/main.rs | 62 ++++----- src/radio.rs | 288 ----------------------------------------- src/test.rs | 2 - src/ubx.rs | 4 +- src/v2x_tx/gn.rs | 94 -------------- src/v2x_tx/mod.rs | 164 ----------------------- 11 files changed, 118 insertions(+), 615 deletions(-) delete mode 100644 src/radio.rs delete mode 100644 src/v2x_tx/gn.rs delete mode 100644 src/v2x_tx/mod.rs diff --git a/Cargo.lock b/Cargo.lock index b3d5c4c..98a4c6a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -192,9 +192,9 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "c-its-parser" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd4db66f495280ec88eef8712dd9ab7d1aacc590f62485e3e03a89b3ce4f8b14" +checksum = "7e94d9990b20498f621f164f23ae198c4365115eb4191ae106352ec74d4a4110" dependencies = [ "chrono", "etherparse", @@ -1171,6 +1171,7 @@ dependencies = [ "esp-println", "esp-radio", "esp-rtos", + "esp32-cits-core", "geo-types", "log", "mipidsi", @@ -1181,6 +1182,18 @@ dependencies = [ "ublox", ] +[[package]] +name = "esp32-cits-core" +version = "0.1.0" +source = "git+https://git.hamburg.ccc.de/jtbx/esp32-cits-core?branch=main#a6d0ea6ca2da130fde2b84060a34ef45e772a459" +dependencies = [ + "c-its-parser", + "chrono", + "esp-hal", + "esp-radio", + "geo-types", +] + [[package]] name = "esp32c2" version = "0.29.2" diff --git a/Cargo.toml b/Cargo.toml index 55f42c5..9b3cbd9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,8 +30,10 @@ esp = [ "dep:esp-backtrace", "dep:esp-println", "dep:esp-radio", + "esp32-cits-core/esp32c5", + "esp32-cits-core/log-04", ] -std = ["c-its-parser/std"] +std = ["c-its-parser/std", "esp32-cits-core/std"] # enable u-blox GNSS module gnss_ubx = ["_gnss", "_uart", "dep:ublox"] @@ -93,6 +95,7 @@ esp-radio = { version = "0.18.0", optional = true, features = [ ] } embassy_gps = { version = "0.1.0", optional = true, default-features = false, features = ["esp", "log-04"], git = "https://github.com/jbeyerstedt/embassy_gps.git", branch = "feat/rework-gpsfix" } +esp32-cits-core = { version = "0.1.0", default-features = false, git = "https://git.hamburg.ccc.de/jtbx/esp32-cits-core", branch = "main" } embedded-hal-bus = { version = "0.3.0", optional = true } embedded-graphics = { version = "0.8.2", optional = true } # needs newer GN with removed bitvec dependency diff --git a/src/applogic/cam_tx.rs b/src/applogic/cam_tx.rs index e15de27..850ae40 100644 --- a/src/applogic/cam_tx.rs +++ b/src/applogic/cam_tx.rs @@ -11,6 +11,45 @@ const PH_MAX_LENGHT_M: f32 = 500.; const PH_MAX_ITEMS: usize = 23; const PH_MIN_DIST_M: f32 = 20.; +/// Stand-alone CAM generator +/// +/// Generates CAM messages from time and [`PosVel`] updates +/// +/// # Send a message +/// ``` +/// use esp32_cits_core::tx::GnItsPayload as _; +/// +/// let (mut seq_no, mac_bytes) = { +/// // unwrap is fine since [`esp_hal::efuse::MacAddress`] is just an `[u8; 6]` +/// let mac_bytes = mac.as_bytes().try_into().unwrap(); +/// +/// (0, mac_bytes) +/// }; +/// +/// let mut cam = { +/// let station_id = make_station_id(mac); +/// info!("V2X Station ID: {station_id}"); +/// +/// applogic::cam_tx::CamState::new( +/// station_id, +/// OWN_STATION_TYPE, +/// OWN_VEHICLE_LENGTH, +/// OWN_VEHICLE_WIDTH, +/// ) +/// }; +/// +/// cam.update(time, fix.clone().into()); +/// match cam.encode_packet(mac_bytes, pos_vel, time, GN_IS_MOBILE, &mut seq_no) { +/// Ok(data) => { +/// if let Err(err) = +/// radio::send_80211_bcast_frame(&mut wlan_iface, &mac_bytes.clone(), &data) +/// { +/// warn!("Failed to send 802.11 frame: {err}"); +/// } +/// } +/// Err(err) => warn!("Failed to create CAM: {err}"), +/// } +/// ``` #[derive(Debug)] pub struct CamState { station_id: u32, @@ -20,7 +59,7 @@ pub struct CamState { vehicle_width_dm: u8, // vehicle width is in 10cm steps time: chrono::DateTime, - pos_state: super::PositionState, + pos_state: esp32_cits_core::PosVel, /// database for path history, oldest item at the back of the vector path_history: Vec, @@ -35,7 +74,7 @@ struct PathPoint { } impl PathPoint { - fn new_from_pos(pos: &super::PositionState, time: chrono::DateTime) -> Self { + fn new_from_pos(pos: &esp32_cits_core::PosVel, time: chrono::DateTime) -> Self { Self { pos: pos.position, time, @@ -44,7 +83,7 @@ impl PathPoint { } fn new_from_pos_with_dist( - pos: &super::PositionState, + pos: &esp32_cits_core::PosVel, time: chrono::DateTime, dist_m: f32, ) -> Self { @@ -69,12 +108,12 @@ impl CamState { vehicle_length_dm, vehicle_width_dm, time: chrono::DateTime::::default(), - pos_state: super::PositionState::default(), + pos_state: esp32_cits_core::PosVel::default(), path_history: Vec::with_capacity(PH_MAX_ITEMS + 1), } } - pub fn update(&mut self, time: chrono::DateTime, pos: super::PositionState) { + pub fn update(&mut self, time: chrono::DateTime, pos: esp32_cits_core::PosVel) { self.time = time; self.pos_state = pos; @@ -86,7 +125,7 @@ impl CamState { fn update_ph( path_history: &mut Vec, time: chrono::DateTime, - pos: &super::PositionState, + pos: &esp32_cits_core::PosVel, ) { // Update path history if path_history.is_empty() { @@ -293,11 +332,11 @@ impl CamState { } } -impl crate::v2x_tx::GnItsPayload for CamState { +impl esp32_cits_core::tx::GnItsPayload for CamState { fn make_eh( &self, address: [u8; 6], - own_position: &crate::applogic::PositionState, + own_position: &esp32_cits_core::PosVel, time: chrono::DateTime, _seq_no: &mut u16, ) -> Result< @@ -313,7 +352,7 @@ impl crate::v2x_tx::GnItsPayload for CamState { // CAM always uses SHB, hop-limit 1 Ok(( - crate::v2x_tx::gn::make_shb_eh(address, station_type, own_position, time), + esp32_cits_core::tx::gn::make_shb_eh(address, station_type, own_position, time)?, None, 1, )) @@ -336,8 +375,8 @@ mod tests { use super::*; use crate::init_test_env_logger; - fn posstate_from_pos(position: geo_types::Point) -> crate::applogic::PositionState { - crate::applogic::PositionState { + fn posstate_from_pos(position: geo_types::Point) -> esp32_cits_core::PosVel { + esp32_cits_core::PosVel { position, heading_deg: None, speed_mps: None, diff --git a/src/applogic/mod.rs b/src/applogic/mod.rs index 7847010..70cc086 100644 --- a/src/applogic/mod.rs +++ b/src/applogic/mod.rs @@ -49,7 +49,7 @@ pub struct State { time: chrono::NaiveDateTime, #[allow(unused)] - pos: PositionState, + pos: esp32_cits_core::PosVel, #[cfg(feature = "spat")] last_prune: chrono::NaiveDateTime, @@ -130,15 +130,7 @@ pub struct GnssFix { pub speed_mps: Option, } -#[allow(unused)] -#[derive(Debug, Default, Clone, PartialEq)] -pub struct PositionState { - pub position: geo_types::Point, - pub heading_deg: Option, - pub speed_mps: Option, -} - -impl From<&GnssFix> for PositionState { +impl From<&GnssFix> for esp32_cits_core::PosVel { fn from(value: &GnssFix) -> Self { let position = geo_types::Point::new(value.longitude_deg, value.latitude_deg); @@ -149,7 +141,7 @@ impl From<&GnssFix> for PositionState { } } } -impl From for PositionState { +impl From for esp32_cits_core::PosVel { fn from(value: GnssFix) -> Self { (&value).into() } @@ -212,7 +204,7 @@ impl State { Self { initialized: false, time: chrono::NaiveDateTime::default(), - pos: PositionState::default(), + pos: esp32_cits_core::PosVel::default(), #[cfg(feature = "spat")] last_prune: chrono::NaiveDateTime::default(), #[cfg(feature = "spat")] @@ -641,7 +633,7 @@ impl State { } #[allow(unused)] - pub fn pos(&self) -> &PositionState { + pub fn pos(&self) -> &esp32_cits_core::PosVel { &self.pos } } diff --git a/src/io.rs b/src/io.rs index 4642253..44997ee 100644 --- a/src/io.rs +++ b/src/io.rs @@ -169,7 +169,7 @@ impl From for crate::applogic::GnssFix { } } -impl From for crate::applogic::PositionState { +impl From for esp32_cits_core::PosVel { fn from(value: msg::PositionState) -> Self { let position = geo_types::Point::new( value.position.longitude_deg.into(), @@ -194,7 +194,7 @@ impl From for crate::applogic::PositionState { } } -impl From for crate::applogic::PositionState { +impl From for esp32_cits_core::PosVel { fn from(value: msg::ItsPosition) -> Self { let latitude_deg = f64::from(value.latitude) / 10_000_000.; let longitude_deg = f64::from(value.longitude) / 10_000_000.; @@ -208,11 +208,11 @@ impl From for crate::applogic::PositionState { } } -impl crate::v2x_tx::GnItsPayload for msg::RawMsgTx { +impl esp32_cits_core::tx::GnItsPayload for msg::RawMsgTx { fn make_eh( &self, address: [u8; 6], - own_position: &crate::applogic::PositionState, + own_position: &esp32_cits_core::PosVel, time: chrono::DateTime, seq_no: &mut u16, ) -> Result< @@ -275,19 +275,19 @@ impl crate::v2x_tx::GnItsPayload for msg::RawMsgTx { *seq_no += 1; ( - crate::v2x_tx::gn::make_tsb_eh( + esp32_cits_core::tx::gn::make_tsb_eh( address, station_type, own_position, time, *seq_no, - ), + )?, None, hop_limit, ) } msg::GnTransport::Shb => ( - crate::v2x_tx::gn::make_shb_eh(address, station_type, own_position, time), + esp32_cits_core::tx::gn::make_shb_eh(address, station_type, own_position, time)?, None, 1, ), @@ -308,7 +308,7 @@ impl msg::RawMsgTx { fn make_geobcast( address: [u8; 6], station_type: geonetworking::en302636_4_1::StationType, - own_position: &crate::applogic::PositionState, + own_position: &esp32_cits_core::PosVel, time: chrono::DateTime, gn_area: msg::GnArea, sequence_number: u16, @@ -320,7 +320,7 @@ impl msg::RawMsgTx { alloc::string::String, > { let source_position_vector = - crate::v2x_tx::gn::make_lpv(address, station_type, own_position, time); + esp32_cits_core::tx::gn::make_lpv(address, station_type, own_position, time)?; let (atype, dist_a, dist_b, angle) = match gn_area.area() { msg::GnAreaShape::EtsiAreashapeCircle => ( diff --git a/src/main.rs b/src/main.rs index 5320951..67e181c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,7 +38,7 @@ use esp_hal::clock::CpuClock; use esp_hal::timer::timg::TimerGroup; #[cfg(all(target_arch = "riscv32", feature = "spat"))] use esp_println::println; -use esp_radio::wifi; +use esp32_cits_core::radio; use geonetworking::Decode as _; use log::{error, info, warn}; @@ -49,13 +49,10 @@ mod cache; mod geo_alg; #[cfg(feature = "uart")] mod io; -mod radio; #[cfg(feature = "screen")] mod screen; #[cfg(feature = "gnss_ubx")] mod ubx; -#[cfg(any(feature = "cam_tx", feature = "uart"))] -mod v2x_tx; const WIFI_CHANNEL: radio::Channel = 180; const WIFI_TXPOWER: i8 = 10; // dBm, 2..20 @@ -244,7 +241,7 @@ async fn main(spawner: Spawner) -> ! { let mut state = applogic::State::new(); #[cfg(feature = "cam_tx")] let mut cam = { - let station_id = v2x_tx::make_station_id(mac); + let station_id = make_station_id(mac); info!("V2X Station ID: {station_id}"); applogic::cam_tx::CamState::new( @@ -289,7 +286,7 @@ async fn main(spawner: Spawner) -> ! { }); } Some(io::msg::serial_input_msg::Payload::TxMsg(tx_request)) => { - use v2x_tx::GnItsPayload as _; + use esp32_cits_core::tx::GnItsPayload as _; info!( "UART: Got new TX message: Type {} with {} bytes", @@ -380,7 +377,7 @@ async fn main(spawner: Spawner) -> ! { #[cfg(feature = "cam_tx")] { - use v2x_tx::GnItsPayload; + use esp32_cits_core::tx::GnItsPayload as _; let time = state.time().and_utc(); cam.update(time, fix.clone().into()); @@ -513,7 +510,7 @@ async fn main(spawner: Spawner) -> ! { reason = "adhering to callback interface" )] #[allow(clippy::too_many_lines)] -fn handle_frame(frame: wifi::sniffer::PromiscuousPkt<'_>) { +fn handle_frame(frame: esp_radio::wifi::sniffer::PromiscuousPkt<'_>) { #[cfg(feature = "spat")] const PRUNE_INTERVAL: chrono::Duration = chrono::Duration::seconds(60); #[cfg(feature = "spat")] @@ -534,27 +531,18 @@ fn handle_frame(frame: wifi::sniffer::PromiscuousPkt<'_>) { .naive_utc() }; - // Ignore frames with errors and non-data frames - if frame.rx_cntl.rx_state != 0 { - warn!("Received frame has RX error: {}", frame.rx_cntl.rx_state); - return; - } - if !radio::PromiscuousPktType::Data.matches(frame.frame_type) { - return; - } - - // Ignore non-broadcast frames - if frame.len < (4 + 6) { - warn!( - "Received frame too small to be a valid broadcast frame: {} bytes", - frame.len - ); - return; - } - let dest_mac = &frame.data[4..10]; - if dest_mac != [0xff; 6] { - warn!("Received frame is not broadcast frame: Dest MAC {dest_mac:02x?}"); - return; + match esp32_cits_core::rx::is_broadcast_frame(&frame) { + Err(err) => { + warn!("{err}"); + return; + } + Ok(false) => { + // drop frame + return; + } + Ok(true) => { + // continue + } } // info!("Received frame with {} bytes", frame.len); @@ -885,3 +873,19 @@ fn send_uart0(data: &[u8]) { } }); } + +#[cfg(all(feature = "esp", feature = "cam_tx"))] +/// Creates a station ID from the WLAN MAC address +/// +/// Will use `0xC173` as static prefix and uses last 16 bit from MAC address for the remainder. +/// +/// # Panics +/// Won't panic in practive b/c of pre-conditions +#[must_use] +pub fn make_station_id(mac: esp_hal::efuse::MacAddress) -> u32 { + // unwrap is fine here since we can be sure that the MacAddress is 6 byte + // and a slice of 2 bytes can be converted to [u8; 2] + let mac_last_16bit = u16::from_be_bytes(mac.as_bytes().get(4..6).unwrap().try_into().unwrap()); + + 0xC173_0000 | u32::from(mac_last_16bit) +} diff --git a/src/radio.rs b/src/radio.rs deleted file mode 100644 index 46cd72a..0000000 --- a/src/radio.rs +++ /dev/null @@ -1,288 +0,0 @@ -//! IEEE 802.11p C-ITS Capture with ESP32-C5 -#![allow(unused)] - -// ----------------------------- -// missing interface definitions -// ----------------------------- - -pub type Channel = core::ffi::c_int; - -unsafe extern "C" { - /// Changes the 802.11 PHY channel - /// - /// C function signature: `void phy_change_channel(int,int,int,int)` - /// - /// Options: - /// - `primary`: Channel frequency in MHz - /// - `ignored1`: Ignored (set to 1) - /// - `ignored2`: Ignored (set to 0) - /// - `ht_mode`: Probably HT mode - unsafe fn phy_change_channel( - primary: Channel, - ignored1: core::ffi::c_int, - ignored2: core::ffi::c_int, - ht_mode: core::ffi::c_int, - ) -> core::ffi::c_int; - - /// Enables 802.11p mode on the ESP32-C5 - /// - /// C function signature: `void phy_11p_set(int,int)` - /// - /// Options: - /// - `enable`: Boolean option to enable 802.11p mode - /// - `half_rate`: 5 MHz mode - unsafe fn phy_11p_set(enable: u8, half_rate: u8); - -} - -/// Enum values for `wifi_promiscuous_pkt_type_t` used in `esp_radio::wifi::sniffer::PromiscuousPkt::frame_type` -/// -/// Original value is actually `core::ffi::c_uint`, but that can't be used in `#[repr()]` attributes, -/// so we're using `u8` as the smallest suitable type here that can be casted to a bigger type. -#[repr(u8)] -#[derive(Debug, Clone, Copy)] -#[allow(dead_code)] -pub(crate) enum PromiscuousPktType { - /// Management frame, indicates 'buf' argument is `wifi_promiscuous_pkt_t` - Mgmt = 0, - /// Control frame, indicates 'buf' argument is `wifi_promiscuous_pkt_t` - Ctrl = 1, - /// Data frame, indicates 'buf' argument is `wifi_promiscuous_pkt_t` - Data = 2, - /// Other type, such as MIMO etc. 'buf' argument is `wifi_promiscuous_pkt_t` but the payload is zero length. - Misc = 3, -} - -impl PromiscuousPktType { - #[allow(dead_code)] - pub fn as_repr(self) -> u8 { - self as u8 - } - - /// Converts to underlying type of `wifi_promiscuous_pkt_type_t` (used by `esp_radio::wifi::sniffer::PromiscuousPkt::frame_type`) - pub fn as_frame_type(self) -> core::ffi::c_uint { - core::ffi::c_uint::from(self.as_repr()) - } - - /// Determines if we are the same frame type as `esp_radio::wifi::sniffer::PromiscuousPkt::frame_type` - pub fn matches(self, frame_type: core::ffi::c_uint) -> bool { - self.as_frame_type() == frame_type - } -} - -#[cfg(feature = "esp")] -/// Configure WiFi Sniffer to a certain 802.11p channel -pub fn setup_wifi_sniffer( - channel: Channel, - sniffer: &mut esp_radio::wifi::sniffer::Sniffer, - callback: fn(esp_radio::wifi::sniffer::PromiscuousPkt<'_>), -) -> Result<(), alloc::string::String> { - sniffer - .set_promiscuous_mode(true) - .map_err(|err| alloc::format!("Failed to enable promiscuous mode: {err:?}"))?; - - sniffer.set_receive_cb(callback); - - // convert channel number to frequency in MHz - let primary = 5000 + (channel * 5); - - unsafe { - phy_11p_set(1, 0); - match phy_change_channel(primary, 1, 0, 0) { - 0 => Ok(()), - ret => Err(alloc::format!("Failed to change channel: {ret}")), - } - } -} - -#[cfg(feature = "esp")] -/// Sends 802.11p broadcast frame -/// -/// Expects `data` to contain GN header (BTP header and ITS payload) to be a valid C-ITS/ ITS-G5 packet/ frame. -/// -/// # Errors -/// Fails when MAC is not the right size or returns frame send error -pub fn send_80211_bcast_frame( - sniffer: &mut esp_radio::wifi::sniffer::Sniffer, - mac_addr: &[u8], - data: &[u8], -) -> Result<(), alloc::string::String> { - const HEADER_SIZE: usize = 24 + 8; - - let mac = mac_addr - .try_into() - .map_err(|err| alloc::format!("Unexpected MAC address size: {err}"))?; - - // assemble frame - let mut frame_buf = alloc::vec::Vec::with_capacity(HEADER_SIZE + data.len()); - frame_buf.extend_from_slice(&DataFrameHeader::new_80211p(mac).to_binary()); - frame_buf.extend_from_slice(&LlcHeader::new_80211p().to_binary()); - frame_buf.extend_from_slice(data); - - // send - sniffer - .send_raw_frame(true, &frame_buf, true) - .map_err(|err| alloc::format!("Failed to send 802.11p frame: {err:?}")) -} - -/// IEEE 802.11p frame header -#[derive(Debug, Default)] -struct DataFrameHeader { - /// byte 0: subtype in upper nibble, type in lower nibble - /// "normal" data frame: 0x08, QoS data frame: 0x88 - pub type_and_subtype: u8, - /// byte 1: frame contol field, 0x00 for 802.11p - pub fcf_flags: u8, - /// byte 2..3: duration in micros, usually 0x0000 for 802.11p (toDS = 0, fromDS = 0) - pub duration: u16, - /// byte 4..9: receiver/ destination for 802.11p - pub address_1: [u8; 6], - /// byte 10..15: transmitter/ source for 802.11p - pub address_2: [u8; 6], - /// byte 16..21: BSSID for 802.11p - pub address_3: [u8; 6], - /// byte 22..24: upper 12 bits: seq. no, lower 4 bits: fragment no - pub sequence_control: u16, - // Note: address 4 is not used in 802.11p - // pub qos: Option<[u8; 2]>, // QoS frames are supported by radio library - // Note: 802.11p does not use HT mode(s) -} - -impl DataFrameHeader { - const BCAST_MAC: [u8; 6] = [0xFF; 6]; - - /// Create an 802.11p (non-QoS) data frame header with a source MAC address - pub(crate) fn new_80211p(source_mac: &[u8; 6]) -> Self { - Self { - type_and_subtype: 0x08, - address_1: Self::BCAST_MAC, - address_2: *source_mac, - address_3: Self::BCAST_MAC, - ..Default::default() - } - } - - pub(crate) fn to_binary(&self) -> [u8; 24] { - let mut buf = [0u8; 24]; - - buf[0] = self.type_and_subtype; - - buf[1] = self.fcf_flags; - - Self::write_u16(&mut buf, 2, self.duration); - Self::write_mac_addr(&mut buf, 4, &self.address_1); - Self::write_mac_addr(&mut buf, 10, &self.address_2); - Self::write_mac_addr(&mut buf, 16, &self.address_3); - Self::write_u16(&mut buf, 22, self.sequence_control); - - buf - } - - fn write_u16(buf: &mut [u8; 24], offset: usize, data: u16) { - for (idx, byte) in data.to_be_bytes().iter().enumerate() { - buf[idx + offset] = *byte; - } - } - - fn write_mac_addr(buf: &mut [u8; 24], offset: usize, data: &[u8; 6]) { - for (idx, byte) in data.iter().enumerate() { - buf[idx + offset] = *byte; - } - } -} - -/// IEEE 802.11p LLC header -#[derive(Debug, Default)] -struct LlcHeader { - /// byte 0: SNAP (0xaa) in 802.11p - pub dsap: u8, // SNAP in - /// byte 1: SNAP (0xaa) in 802.11p - pub lsap: u8, // SNAP in - /// byte 2: SNAP (0xaa) in 802.11p - pub control_1: u8, // 0x03 - // Note: optional second control byte not used in 802.11p - - // SNAP fields - /// set to 0 since ethertype is used for protocol ID in 802.11p - pub oui: [u8; 3], - /// ethertype GeoNetworking (0x8947) in 802.11p - pub protocol_id: u16, -} - -impl LlcHeader { - pub(crate) fn new_80211p() -> Self { - Self { - dsap: 0xaa, - lsap: 0xaa, - control_1: 0x03, - oui: [0; 3], - protocol_id: 0x8947, - } - } - - pub(crate) fn to_binary(&self) -> [u8; 8] { - let mut buf: [u8; 8] = [ - self.dsap, - self.lsap, - self.control_1, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ]; - - buf[3] = self.oui[0]; - buf[4] = self.oui[1]; - buf[5] = self.oui[2]; - - buf[6] = self.protocol_id.to_be_bytes()[0]; - buf[7] = self.protocol_id.to_be_bytes()[1]; - - buf - } -} - -#[cfg(test)] -mod tests { - - use super::*; - - #[test] - fn data_frame_test() { - // build a typical IEEE 802.11p frame - const OWN_MAC: [u8; 6] = [0x12, 0x34, 0x56, 0x78, 0xab, 0xcd]; - const REF_HEADER: [u8; 24] = [ - 0x08, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x12, 0x34, 0x56, 0x78, - 0xab, 0xcd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, - ]; - - let frame = DataFrameHeader::new_80211p(&OWN_MAC); - - assert_eq!(0x08, frame.type_and_subtype); // data frame, subtype 0 - assert_eq!(0x0000, frame.fcf_flags); - assert_eq!(0x0000, frame.duration); - assert_eq!([0xFF; 6], frame.address_1); - assert_eq!(OWN_MAC, frame.address_2); - assert_eq!([0xFF; 6], frame.address_3); - assert_eq!(0x0000, frame.sequence_control); - - assert_eq!(REF_HEADER, frame.to_binary()); - } - - #[test] - fn llc_test() { - // build a typical IEEE 802.11p LLC header - const REF_HEADER: [u8; 8] = [0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00, 0x89, 0x47]; - - let llc = LlcHeader::new_80211p(); - - assert_eq!(0xaa, llc.dsap); - assert_eq!(0xaa, llc.lsap); - assert_eq!(0x03, llc.control_1); - assert_eq!([0x00; 3], llc.oui); - assert_eq!(0x8947, llc.protocol_id); - - assert_eq!(REF_HEADER, llc.to_binary()); - } -} diff --git a/src/test.rs b/src/test.rs index 6bb20d2..a7a5bb5 100644 --- a/src/test.rs +++ b/src/test.rs @@ -7,9 +7,7 @@ mod applogic; mod cache; mod geo_alg; mod io; -mod radio; mod testdata; -mod v2x_tx; fn main() { // env_logger::init(); diff --git a/src/ubx.rs b/src/ubx.rs index 934af7b..8e1f32e 100644 --- a/src/ubx.rs +++ b/src/ubx.rs @@ -90,9 +90,9 @@ impl<'a> TryFrom<&ublox::nav_pvt::proto27::NavPvtRef<'a>> for crate::applogic::G let latitude_deg = pos.lat; let longitude_deg = pos.lon; - #[allow(clippy::cast_precision_loss)] + #[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)] let heading_deg = vel.heading as f32; - #[allow(clippy::cast_precision_loss)] + #[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)] let speed_mps = vel.speed as f32; Ok(Self { diff --git a/src/v2x_tx/gn.rs b/src/v2x_tx/gn.rs deleted file mode 100644 index 4b2b0b9..0000000 --- a/src/v2x_tx/gn.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Geonetworking utilities - -use c_its_parser::gn as geonetworking; - -#[allow(unused)] -pub fn make_shb_eh( - address: [u8; 6], - station_type: geonetworking::en302636_4_1::StationType, - own_position: &crate::applogic::PositionState, - time: chrono::DateTime, -) -> geonetworking::en302636_4_1::ExtendedHeader { - let source_position_vector = make_lpv(address, station_type, own_position, time); - - geonetworking::en302636_4_1::ExtendedHeader::SHB( - geonetworking::en302636_4_1::SingleHopBroadcast { - source_position_vector, - media_dependent_data: [0; 4], - }, - ) -} - -#[allow(unused)] -pub fn make_tsb_eh( - address: [u8; 6], - station_type: geonetworking::en302636_4_1::StationType, - own_position: &crate::applogic::PositionState, - time: chrono::DateTime, - sequence_number: u16, -) -> geonetworking::en302636_4_1::ExtendedHeader { - let source_position_vector = make_lpv(address, station_type, own_position, time); - - geonetworking::en302636_4_1::ExtendedHeader::TSB( - geonetworking::en302636_4_1::TopologicallyScopedBroadcast::new( - sequence_number, - source_position_vector, - ), - ) -} - -#[allow(unused)] -pub fn make_gbc_eh( - address: [u8; 6], - station_type: geonetworking::en302636_4_1::StationType, - own_position: &crate::applogic::PositionState, - time: chrono::DateTime, - target_pos: geo_types::Point, - radius_m: u16, - sequence_number: u16, -) -> geonetworking::en302636_4_1::ExtendedHeader { - let source_position_vector = make_lpv(address, station_type, own_position, time); - #[allow(clippy::cast_possible_truncation)] - let latitude_deg = target_pos.x() as f32; - #[allow(clippy::cast_possible_truncation)] - let longitude_deg = target_pos.y() as f32; - - geonetworking::en302636_4_1::ExtendedHeader::GBC( - geonetworking::en302636_4_1::GeoBroadcast::try_from_values( - sequence_number, - source_position_vector, - latitude_deg, - longitude_deg, - radius_m, - 0, - 0, - ) - .expect("Failed to create circular GBC"), - ) -} - -pub fn make_lpv( - address: [u8; 6], - station_type: geonetworking::en302636_4_1::StationType, - own_position: &crate::applogic::PositionState, - time: chrono::DateTime, -) -> geonetworking::en302636_4_1::LongPositionVector { - let timestamp_its = c_its_parser::time_utils::TimestampIts::from(time); - #[allow(clippy::cast_possible_truncation)] - let latitude_deg = own_position.position.y() as f32; - #[allow(clippy::cast_possible_truncation)] - let longitude_deg = own_position.position.x() as f32; - let speed_mps = own_position.speed_mps.unwrap_or_default(); - let heading_deg = own_position.heading_deg.unwrap_or_default(); - - geonetworking::en302636_4_1::LongPositionVector::try_from_values( - geonetworking::en302636_4_1::Address::new(false, station_type, address), - timestamp_its.0, - latitude_deg, - longitude_deg, - true, - speed_mps, - heading_deg, - ) - .expect("Failed to create LongPositionVector") -} diff --git a/src/v2x_tx/mod.rs b/src/v2x_tx/mod.rs deleted file mode 100644 index ee48578..0000000 --- a/src/v2x_tx/mod.rs +++ /dev/null @@ -1,164 +0,0 @@ -//! V2X Transmission - -use alloc::vec::Vec; - -use c_its_parser::gn::{self as geonetworking, Encode}; - -pub mod gn; - -#[cfg(all(feature = "esp", feature = "cam_tx"))] -/// Creates a station ID from the WLAN MAC address -/// -/// Will use `0xC173` as static prefix and uses last 16 bit from MAC address for the remainder. -pub fn make_station_id(mac: esp_hal::efuse::MacAddress) -> u32 { - // unwrap is fine here since we can be sure that the MacAddress is 6 byte - // and a slice of 2 bytes can be converted to [u8; 2] - let mac_last_16bit = u16::from_be_bytes(mac.as_bytes().get(4..6).unwrap().try_into().unwrap()); - - 0xC173_0000 | u32::from(mac_last_16bit) -} - -pub trait GnItsPayload { - // common GN parameters - const LIFETIME_MILLIS: u32 = 60_000; // itsGnDefaultPacketLifetime is 60 seconds according to ETSI EN 302 636-4-1 V1.4.1 - - // --------------------------------------------- - // methods to build a GN packet with ITS payload - // --------------------------------------------- - - /// Builds the [`geonetworking::en302636_4_1::ExtendedHeader`] and additional information for the ITS message - /// - /// Output: - /// - Extended header - /// - Area type (when using GAC or GBC) - /// - hop limit - /// - /// Note: Make sure to increase the sequence number on multi-hop packets. - fn make_eh( - &self, - address: [u8; 6], - own_position: &crate::applogic::PositionState, - time: chrono::DateTime, - seq_no: &mut u16, - ) -> Result< - ( - geonetworking::en302636_4_1::ExtendedHeader, - Option, - u8, - ), - alloc::string::String, - >; - - /// Creates the ITS payload (without BTP header) - fn make_payload( - &self, - ) -> Result<(Vec, c_its_parser::standards::extensions::ItsMessageId), alloc::string::String>; - - // --------------------------------------------- - // common methods (with implementation) - // --------------------------------------------- - - /// Encodes the GN packet - fn encode_packet( - &self, - address: [u8; 6], - own_position: &crate::applogic::PositionState, - time: chrono::DateTime, - is_mobile: bool, - seq_no: &mut u16, - ) -> Result, alloc::string::String> { - // Build payload (including BTP header) - let (mut its_payload, msg_type) = self.make_payload()?; - let mut payload = make_btp_b(msg_type).encode()?; - - payload.append(&mut its_payload); - - #[allow(clippy::cast_possible_truncation)] - let payload_length = (payload.len()) as u16; - - // get extended header (depending on message type and sometimes content as well) - let (eh, area_type, maximum_hop_limit) = - self.make_eh(address, own_position, time, seq_no)?; - - // Build GN headers - let traffic_class = geonetworking::en302636_4_1::TrafficClass::try_new( - false, - false, - make_traffic_class(msg_type), - ) - .map_err(|err| alloc::format!("Failed to create TrafficClass: {err:?}"))?; - let common = geonetworking::en302636_4_1::CommonHeader::from_values( - geonetworking::en302636_4_1::NextAfterCommon::BTPB, - geonetworking::en302636_4_1::HeaderType::new_from(&eh, area_type)?, - traffic_class, - is_mobile, - payload_length, - maximum_hop_limit, - ); - - let remaining_hop_limit = maximum_hop_limit; - let basic = geonetworking::en302636_4_1::BasicHeader::try_new( - 1, - geonetworking::en302636_4_1::NextAfterBasic::CommonHeader, - geonetworking::en302636_4_1::Lifetime::from_milliseconds(Self::LIFETIME_MILLIS), - remaining_hop_limit, - ) - .map_err(|err| alloc::format!("Failed to create BasicHeader: {err}"))?; - - // Assemble and encode packet - let packet = geonetworking::Packet::Unsecured { - basic, - common, - extended: Some(eh), - payload: &payload, - }; - - packet - .encode_to_vec() - .map_err(|err| alloc::format!("Failed to encode GN packet: {err:?}")) - } -} - -/// Creates a BTP-B header based on the ITS message ID (message type) -/// -/// Port numbers and destination port info according to ETSI TS 103 248 -fn make_btp_b( - msg_type: c_its_parser::standards::extensions::ItsMessageId, -) -> c_its_parser::transport::TransportHeader { - let (destination_port, destination_port_info) = match msg_type { - c_its_parser::standards::extensions::ItsMessageId::Denm => (2002, 0), - c_its_parser::standards::extensions::ItsMessageId::Cam => (2001, 0), - c_its_parser::standards::extensions::ItsMessageId::Mapem => (2003, 0), - c_its_parser::standards::extensions::ItsMessageId::Spatem => (2004, 0), - c_its_parser::standards::extensions::ItsMessageId::Saem => (2005, 0), - c_its_parser::standards::extensions::ItsMessageId::Ivim => (2006, 0), - c_its_parser::standards::extensions::ItsMessageId::Srem => (2007, 0), - c_its_parser::standards::extensions::ItsMessageId::Ssem => (2008, 0), - c_its_parser::standards::extensions::ItsMessageId::Cpm => (2009, 0), - c_its_parser::standards::extensions::ItsMessageId::Poi => (2010, 0), - c_its_parser::standards::extensions::ItsMessageId::EvRsr => (2012, 0), - c_its_parser::standards::extensions::ItsMessageId::Rtcmem => (2013, 0), - _ => (0, 0), // no information found for the subsequent types - }; - - c_its_parser::transport::TransportHeader::BtpB(c_its_parser::transport::BasicTransportBHeader { - destination_port, - destination_port_info, - }) -} - -/// Creates a traffic class ID based on the ITS message ID (message type) -fn make_traffic_class(msg_type: c_its_parser::standards::extensions::ItsMessageId) -> u8 { - #[allow(clippy::match_same_arms)] - match msg_type { - c_its_parser::standards::extensions::ItsMessageId::Denm => 1, - c_its_parser::standards::extensions::ItsMessageId::Cam => 2, - c_its_parser::standards::extensions::ItsMessageId::Mapem => 3, // from C-Roads RS_RSP_063(1) - c_its_parser::standards::extensions::ItsMessageId::Spatem => 3, // from C-Roads RS_RSP_063(1) - c_its_parser::standards::extensions::ItsMessageId::Ivim => 3, // from C-Roads RS_RSP_063(1) - c_its_parser::standards::extensions::ItsMessageId::Srem => 2, // from C-Roads RS_RSP_063(1) - c_its_parser::standards::extensions::ItsMessageId::Ssem => 2, // from C-Roads RS_RSP_063(1) - c_its_parser::standards::extensions::ItsMessageId::Rtcmem => 3, // CSP_MaxPrio=252 -> 3 - _ => 3, // fall-back to lowest priority - } -} From 17b84d9aece37ff08d5315ae3fb0f8d4494e625c Mon Sep 17 00:00:00 2001 From: Jannik Beyerstedt Date: Mon, 11 May 2026 21:08:23 +0200 Subject: [PATCH 15/27] Add basic BLE I/O --- Cargo.lock | 183 ++++++++++++++ Cargo.toml | 18 +- Readme.md | 11 +- docs/ble-protocol.md | 50 ++++ docs/c_its-messages.proto | 2 +- feat-permut.sh | 2 + run-smartphone-ble.sh | 6 + run-smartphone-both.sh | 6 + run-smartphone.sh => run-smartphone-uart.sh | 2 +- src/applogic/mod.rs | 1 + src/ble.rs | 263 ++++++++++++++++++++ src/io.rs | 238 +++++++++++++++++- src/main.rs | 47 +++- 13 files changed, 806 insertions(+), 23 deletions(-) create mode 100755 run-smartphone-ble.sh create mode 100755 run-smartphone-both.sh rename run-smartphone.sh => run-smartphone-uart.sh (69%) create mode 100644 src/ble.rs diff --git a/Cargo.lock b/Cargo.lock index 98a4c6a..4d9cb80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -166,6 +166,29 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bt-hci" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "211713d2e9fb4793ce4360a712c0764264aff6be48932ccf02ca2a331c0436a9" +dependencies = [ + "btuuid", + "embassy-sync 0.8.0", + "embedded-io 0.7.1", + "embedded-io-async 0.7.0", + "futures-intrusive", + "heapless 0.9.2", +] + +[[package]] +name = "btuuid" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5f48f1e9b0aad0a4f05d17bdeae0fa20ff798e272a03a6940ca27ad9c5a6ae7" +dependencies = [ + "uuid", +] + [[package]] name = "bumpalo" version = "3.20.2" @@ -244,6 +267,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b396d1f76d455557e1218ec8066ae14bba60b4b36ecd55577ba979f5db7ecaa" +[[package]] +name = "convert_case" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "cordyceps" version = "0.3.4" @@ -956,6 +988,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23fbff98b06a96b6ce3791ecec5c668524052a068e23aacd23afe17ddba844ce" dependencies = [ "allocator-api2", + "bt-hci", "cfg-if", "docsplay", "document-features", @@ -1152,11 +1185,14 @@ name = "esp32-c_its-companion" version = "0.1.0" dependencies = [ "assert_float_eq", + "bt-hci", "c-its-parser", "chrono", "critical-section", "crossbeam-queue", "embassy-executor", + "embassy-futures", + "embassy-sync 0.7.2", "embassy-time", "embassy_gps", "embedded-graphics", @@ -1179,6 +1215,8 @@ dependencies = [ "profont", "prost", "prost-build", + "static_cell", + "trouble-host", "ublox", ] @@ -1320,12 +1358,52 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + [[package]] name = "futures-core" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + [[package]] name = "futures-sink" version = "0.3.32" @@ -1345,8 +1423,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-core", + "futures-sink", "futures-task", "pin-project-lite", + "slab", ] [[package]] @@ -1561,6 +1641,18 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "js-sys" +version = "0.3.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1603,6 +1695,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.29" @@ -2184,6 +2285,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "semver" version = "1.0.28" @@ -2261,6 +2368,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + [[package]] name = "smallvec" version = "1.15.1" @@ -2309,6 +2422,15 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "static_cell" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0530892bb4fa575ee0da4b86f86c667132a94b74bb72160f58ee5a4afec74c23" +dependencies = [ + "portable-atomic", +] + [[package]] name = "strsim" version = "0.11.1" @@ -2498,6 +2620,39 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "trouble-host" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df7817cead4b83dfbeeaa59736ecbc97c30b21dc3bfc0cc2ce8a6687f70b37a" +dependencies = [ + "bt-hci", + "embassy-futures", + "embassy-sync 0.7.2", + "embassy-time", + "embedded-io 0.7.1", + "futures", + "heapless 0.9.2", + "rand_core 0.6.4", + "static_cell", + "trouble-host-macros", + "zerocopy", +] + +[[package]] +name = "trouble-host-macros" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d809ae05f510cf8f1d863749281eb28e049d9ae4ad52d92ea7a0ac068a0e17c" +dependencies = [ + "convert_case", + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.117", + "uuid", +] + [[package]] name = "typenum" version = "1.20.0" @@ -2539,6 +2694,12 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-segmentation" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" + [[package]] name = "unicode-width" version = "0.1.14" @@ -2570,6 +2731,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" dependencies = [ "getrandom", + "js-sys", + "wasm-bindgen", ] [[package]] @@ -2875,6 +3038,26 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "zerocopy" +version = "0.8.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bce33a6288fa3f072a8c2c7d0f2fdbb90e28298f0135c1f99b96c3db2efcc60b" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd425244944f4ab65ccff928e7323354c5a018c75838362fdce749dfad2ee1e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "zmij" version = "1.0.21" diff --git a/Cargo.toml b/Cargo.toml index 9b3cbd9..aae53d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ path = "./src/test.rs" required-features = ["std", "cam"] [features] -default = ["esp", "spat", "gnss_ubx", "screen"] +default = ["esp", "spat", "gnss_ubx", "screen", "ble"] # build for ESP (needed to build test for `host-tuple`) esp = [ @@ -23,6 +23,8 @@ esp = [ "dep:esp-rtos", "dep:esp-bootloader-esp-idf", "dep:embassy-executor", + "dep:embassy-futures", + "dep:embassy-sync", "dep:embassy-time", "dep:embedded-io", "dep:embedded-io-async", @@ -30,6 +32,7 @@ esp = [ "dep:esp-backtrace", "dep:esp-println", "dep:esp-radio", + "dep:bt-hci", "esp32-cits-core/esp32c5", "esp32-cits-core/log-04", ] @@ -52,7 +55,9 @@ spat_debug = [] # enable ST7789 based display screen = ["dep:mipidsi", "dep:embedded-graphics", "dep:embedded-hal-bus", "dep:profont"] # enable I/O via UART -uart = ["_uart", "_gnss", "dep:prost"] +uart = ["_uart", "_gnss"] +# enable I/O via BLE +ble = ["_gnss", "denm"] # internal feature, that any UART I/O is enabled _uart = [] @@ -66,6 +71,7 @@ esp-rtos = { version = "0.3.0", optional = true, features = [ "embassy", "esp-alloc", "esp-radio", + "embassy", "esp32c5", "log-04", ] } @@ -74,7 +80,10 @@ esp-bootloader-esp-idf = { version = "0.5.0", optional = true, features = ["esp3 log = "0.4.27" critical-section = "1.2.0" +bt-hci = { version = "0.8.0", optional = true } embassy-executor = { version = "0.10.0", optional = true, features = ["log"] } +embassy-futures = { version = "0.1.2", optional = true } +embassy-sync = { version = "0.7", optional = true } embassy-time = { version = "0.5.0", optional = true, features = ["log"] } embedded-io = { version = "0.7.1", optional = true } embedded-io-async = { version = "0.7.0", optional = true } @@ -92,7 +101,10 @@ esp-radio = { version = "0.18.0", optional = true, features = [ "unstable", "wifi", "sniffer", + "ble", ] } +trouble-host = { version = "0.6.0", features = ["gatt", "central", "derive", "default-packet-pool"] } +static_cell = "2" embassy_gps = { version = "0.1.0", optional = true, default-features = false, features = ["esp", "log-04"], git = "https://github.com/jbeyerstedt/embassy_gps.git", branch = "feat/rework-gpsfix" } esp32-cits-core = { version = "0.1.0", default-features = false, git = "https://git.hamburg.ccc.de/jtbx/esp32-cits-core", branch = "main" } @@ -111,7 +123,7 @@ geo-types = { version = "0.7.19", default-features = false } mipidsi = { version = "0.10.0", optional = true } num-traits = { version = "0.2.19", default-features = false, features = ["libm"] } profont = { version = "0.7.0", optional = true } -prost = { version = "0.14", optional = true, default-features = false, features = ["derive"] } +prost = { version = "0.14", default-features = false, features = ["derive"] } ublox = { version = "0.10", optional = true, default-features = false, features = ["alloc", "ubx_proto27"] } [dev-dependencies] diff --git a/Readme.md b/Readme.md index 2ae3aab..c2ecbab 100644 --- a/Readme.md +++ b/Readme.md @@ -55,6 +55,12 @@ This is **not** implemented in this application! ### UART Enable UART I/O protocol according to [docs/uart-protocol.md](./docs/uart-protocol.md). +### BLE +Enable BLE I/O protocol according to [docs/ble-protocol.md](./docs/ble-protocol.md). +Will enable `denm` feature, but also enabling `cam` is recommended for full functionality. + +Note: Raw message reception is currently not implemented! + ## Usage @@ -70,7 +76,7 @@ rustup target add riscv32imac-unknown-none-elf cargo install espflash --locked ``` -(Project was setup using esp-generate with the options `--chip esp32c5 -o esp32c5-wroom-1-psram -o alloc -o log -o unstable-hal -o wifi -o esp-backtrace -o embassy`.) +(Project was setup using esp-generate with the options `--chip esp32c5 -o esp32c5-wroom-1-psram -o alloc -o log -o unstable-hal -o wifi -o esp-backtrace -o embassy -o ble-trouble`.) To use the proper rustfmt features, enable the nightly toolchain: ```shell @@ -93,7 +99,8 @@ Officially supported feature combinations are: - `esp,spat,gnss_ubx,screen` (default features, "standalone") - `esp,spat,gnss_ubx,screen,cam_tx` ("standalone with CAM") - `esp,spat,gnss_ubx,screen,cam_tx,cam,denm` -- `esp,uart,cam,denm` ("smartphone companion") +- `esp,uart,cam,denm` ("smartphone companion with UART") +- `esp,ble,cam,denm,cam_tx` ("smartphone companion with BLE") Connect your ESP32-C5 and run: diff --git a/docs/ble-protocol.md b/docs/ble-protocol.md index 65f68ff..4560424 100644 --- a/docs/ble-protocol.md +++ b/docs/ble-protocol.md @@ -35,15 +35,65 @@ All data types are defined in one `.proto` file: [c_its-messages.proto](./c_its- When there's no GNSS receiver connected to the ESP, position, heading and speed information can be set using a `PositionState` message (see [c_its-messages.proto](./c_its-messages.proto)). +Maximum message size (assuming realistic value ranges): + +field name | tag size | (max.) data size +----------------- | -------- | --------- +timestamp_ms | 1 | 8 (I64) +position | 1 | 1 (LEN(10)) + sub-fields below +\+ latitude_deg | 1 | 4 (I32) +\+ longitude_deg | 1 | 4 (I32) +heading | 1 | 2 (VARINT(3600)) +speed | 1 | 2 (VARINT(~163 m/s)) +**SUM** | 6 + | 21 = 27 byte + + ### DENM Event Each received DENM will be "published" as a `DenmEvent` message (see [c_its-messages.proto](./c_its-messages.proto)). Repetitions of the same message (e.g. no changes to the validity timeframe and location) may be dropped by the BLE server. +Maximum message size (assuming realistic value ranges): + +field name | tag size | (max.) data size +----------------- | -------- | --------- +timestamp | 1 | 8 (I64) +validity_end_ts | 1 | 8 (I64) +station_id | 1 | 8 (I64) +seq_num | 1 | 3 (VARINT(65535)) +event_position | 1 | 1 (LEN(10)) + sub-fields below +\+ latitude_deg | 1 | 4 (I32) +\+ longitude_deg | 1 | 4 (I32) +event_heading_deg | 1 | 1 (VARINT(360)) +direction | 1 | 1 (VARINT(3)) +cause_code | 1 | 1 (VARINT(127)) +sub_cause_code | 1 | 1 (VARINT(127)) +**SUM** | 11 + | 40 = 51 byte + +Note: Cause code and sub-cause code are technically 0..255 in the ASN.1 spec, but that's mainly to ensure backwards-compatibility of the CHOICE with the old wire format. +Realistically only cause codes up to 100 are defined and common sub-cause codes use values up to 22. + ### CAM Event Each received (low-frequency) CAM will be "published" as a `CamEvent` message (see [c_its-messages.proto](./c_its-messages.proto)). +Maximum message size (assuming realistic value ranges): + +field name | tag size | (max.) data size +--------------------- | -------- | --------- +station_id | 1 | 8 (I64) +station_type | 1 | 1 (VARINT(15)) +position | 1 | 1 (LEN(10)) + sub-fields below +\+ latitude_deg | 1 | 4 (I32) +\+ longitude_deg | 1 | 4 (I32) +vehicle_role | 1 | 1 (VARINT(15)) +vehicle_data | 1 | 1 (LEN(11)) + sub-fields below +\+ heading_deg | 1 | 2 (VARINT(360)) +\+ speed | 1 | 2 (VARINT(~163 m/s)) +\+ vehicle_length_dm | 1 | 2 (VARINT(1023)) +\+ vehicle_width_dm | 1 | 1 (VARINT(62)) +**SUM** | 11 + | 27 = 38 byte + ### Raw Received Message Received V2X messages will be "published" as a `RawMsgRx` message (see [c_its-messages.proto](./c_its-messages.proto)). diff --git a/docs/c_its-messages.proto b/docs/c_its-messages.proto index af8f802..47e523d 100644 --- a/docs/c_its-messages.proto +++ b/docs/c_its-messages.proto @@ -83,7 +83,7 @@ message PositionState { } message DenmEvent { - required fixed64 timestamp = 1; // DENM reference time as UNIX time + required fixed64 timestamp = 1; // DENM reference time as UNIX time (in seconds) required fixed64 validity_end_ts = 2; // UNIX time when event validity ends (from denm.management.validityDuration) required fixed32 station_id = 3; // unique ID of the event when combined with `seq_num` required uint32 seq_num = 4; // unique ID of the event when combined with `station_id` diff --git a/feat-permut.sh b/feat-permut.sh index 9c08d65..1ef6b62 100755 --- a/feat-permut.sh +++ b/feat-permut.sh @@ -9,6 +9,8 @@ extended=${2:-false} features_base=( uart,cam,denm + ble,cam,denm,cam_tx + uart,ble,cam,denm spat,gnss_ubx,screen spat,gnss_ubx,screen,cam_tx ) diff --git a/run-smartphone-ble.sh b/run-smartphone-ble.sh new file mode 100755 index 0000000..266ee7e --- /dev/null +++ b/run-smartphone-ble.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +## Builds common config for smartphone connected device +## -> receive CAM and DENMs via BLE and send CAMs based on position data received from BLE + +cargo run --release --no-default-features -F esp,cam,denm,ble,cam_tx diff --git a/run-smartphone-both.sh b/run-smartphone-both.sh new file mode 100755 index 0000000..e6c067b --- /dev/null +++ b/run-smartphone-both.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +## Builds common config for smartphone connected device +## -> only receive CAM and DENMs via UART and BLE + +cargo run --release --no-default-features -F esp,cam,denm,uart,ble diff --git a/run-smartphone.sh b/run-smartphone-uart.sh similarity index 69% rename from run-smartphone.sh rename to run-smartphone-uart.sh index ee5170d..b54fb6d 100755 --- a/run-smartphone.sh +++ b/run-smartphone-uart.sh @@ -1,6 +1,6 @@ #!/bin/sh ## Builds common config for smartphone connected device -## -> only receive CAM and DENMs via UART (TODO: and BLE) +## -> only receive CAM and DENMs via UART cargo run --release --no-default-features -F esp,cam,denm,uart diff --git a/src/applogic/mod.rs b/src/applogic/mod.rs index 70cc086..7e7ebc4 100644 --- a/src/applogic/mod.rs +++ b/src/applogic/mod.rs @@ -263,6 +263,7 @@ impl State { } #[cfg(feature = "spat")] + #[allow(clippy::too_many_lines)] pub fn run_glosa(&mut self) -> GlosaOutput { let Some(own_heading) = self.pos.heading_deg else { use alloc::string::ToString; diff --git a/src/ble.rs b/src/ble.rs new file mode 100644 index 0000000..b585e86 --- /dev/null +++ b/src/ble.rs @@ -0,0 +1,263 @@ +#![allow( + clippy::needless_borrows_for_generic_args, + reason = "gatt_service creates false positives" +)] + +use core::cell::RefCell; + +use critical_section::Mutex; +use esp_backtrace as _; +use log::{info, warn}; +use trouble_host::prelude::*; + +use crate::io; + +pub const CONTROLLER_SLOTS: usize = 20; +const CONNECTIONS_MAX: usize = 1; +const L2CAP_CHANNELS_MAX: usize = 1; + +pub static TX_DATA: Mutex>> = Mutex::new(RefCell::new(None)); +pub static RX_DATA: Mutex>> = Mutex::new(RefCell::new(None)); + +pub enum BleSendable { + #[cfg(feature = "cam")] + Cam(io::msg::CamEvent), + #[cfg(feature = "denm")] + Denm(io::msg::DenmEvent), +} + +#[gatt_server] +struct Server { + c_its_events: CITSEvents, +} + +#[cfg(feature = "cam")] +const CAM_EVENT_SIZE: usize = 38; +#[cfg(feature = "denm")] +const DENM_EVENT_SIZE: usize = 51; + +#[gatt_service(uuid = "c0b70000-d4f4-4000-ada8-f99a02ee315c")] +struct CITSEvents { + #[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, name = "Position", read, value = "PositionState proto msg", type = &'static str)] + #[characteristic(uuid = "c0b70001-d4f4-4001-ada8-f99a02ee315c", write)] + position: [u8; 27], + + #[cfg(feature = "cam")] + #[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, name = "CAM_Event", read, value = "CamEvent proto msg", type = &'static str)] + #[characteristic(uuid = "c0b70001-d4f4-4002-ada8-f99a02ee315c", read, notify, value = [0u8; CAM_EVENT_SIZE])] + cam_event: [u8; CAM_EVENT_SIZE], + + #[cfg(feature = "denm")] + #[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, name = "DENM_Event", read, value = "DenmEvent proto msg", type = &'static str)] + #[characteristic(uuid = "c0b70001-d4f4-4004-ada8-f99a02ee315c", read, notify, value = [0u8; DENM_EVENT_SIZE])] + denm_event: [u8; DENM_EVENT_SIZE], +} + +/// Creates the BLE controller +pub fn make_controller( + bt_peripheral: esp_hal::peripherals::BT<'static>, +) -> bt_hci::controller::ExternalController< + esp_radio::ble::controller::BleConnector<'static>, + CONTROLLER_SLOTS, +> { + let transport = esp_radio::ble::controller::BleConnector::new( + bt_peripheral, + esp_radio::ble::Config::default(), + ) + .unwrap(); + bt_hci::controller::ExternalController::<_, { CONTROLLER_SLOTS }>::new(transport) +} + +/// Run the BLE stack. +#[embassy_executor::task] +pub async fn run( + controller: bt_hci::controller::ExternalController< + esp_radio::ble::controller::BleConnector<'static>, + CONTROLLER_SLOTS, + >, +) { + let mut resources: HostResources = + HostResources::new(); + let stack = trouble_host::new(controller, &mut resources); + let Host { + mut peripheral, + runner, + .. + } = stack.build(); + + info!("Starting advertising and GATT service"); + let server = Server::new_with_config(GapConfig::Peripheral(PeripheralConfig { + name: "ESP32 C-ITS", + appearance: &appearance::UNKNOWN, + })) + .unwrap(); + + let _ = embassy_futures::join::join(ble_task(runner), async { + loop { + match advertise("ESP32 C-ITS", &mut peripheral, &server).await { + Ok(conn) => { + // set up tasks when the connection is established to a central, so they don't run when no one is connected. + let a = gatt_events_task(&server, &conn); + let b = custom_task(&server, &conn); + // run until any task ends (usually because the connection has been closed), + // then return to advertising state. + embassy_futures::select::select(a, b).await; + } + Err(e) => { + panic!("BLE ADV: fatal error {e:?}"); + } + } + } + }) + .await; +} + +/// Create an advertiser to use to connect to a BLE Central, and wait for it to connect. +async fn advertise<'values, 'server, C: Controller>( + name: &'values str, + peripheral: &mut Peripheral<'values, C, DefaultPacketPool>, + server: &'server Server<'values>, +) -> Result, BleHostError> { + let mut advertiser_data = [0; 35]; + let len = AdStructure::encode_slice( + &[ + AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED), + AdStructure::CompleteLocalName(name.as_bytes()), + ], + &mut advertiser_data[..], + )?; + + let advertiser = peripheral + .advertise( + &trouble_host::advertise::AdvertisementParameters { + timeout: Some(embassy_time::Duration::from_secs(2)), + ..Default::default() + }, + Advertisement::ConnectableScannableUndirected { + adv_data: &advertiser_data[..len], + scan_data: &[], + }, + ) + .await?; + // info!("[adv] advertising {len} bytes"); + let conn = advertiser.accept().await?.with_attribute_server(server)?; + // info!("[adv] connection established"); + Ok(conn) +} + +async fn ble_task(mut runner: Runner<'_, C, P>) { + loop { + if let Err(e) = runner.run().await { + panic!("BLE Task: fatal error {e:?}"); + } + } +} + +/// Stream Events until the connection closes. +/// +/// This function will handle the GATT events and process them. +/// This is how we interact with read and write requests. +async fn gatt_events_task( + server: &Server<'_>, + conn: &GattConnection<'_, '_, P>, +) -> Result<(), Error> { + let reason = loop { + match conn.next().await { + GattConnectionEvent::Disconnected { reason } => break reason, + GattConnectionEvent::Gatt { event } => { + match &event { + GattEvent::Write(event) + if event.handle() == server.c_its_events.position.handle => + { + use prost::Message as _; + + // parse and send to main thread + match io::msg::PositionState::decode(event.data()) { + Ok(data) => { + critical_section::with(|cs| { + let rx_data_ref = RX_DATA.borrow(cs); + let _ = rx_data_ref.replace(Some(data)); + }); + } + Err(err) => log::error!("BLE: Failed to parse input: {err:?}"), + } + } + + _ => {} + } + + // This step is also performed at drop(), but writing it explicitly is necessary + // in order to ensure reply is sent. + match event.accept() { + Ok(reply) => reply.send().await, + Err(e) => warn!("BLE: error sending response: {e:?}"), + } + } + _ => {} // ignore other Gatt Connection Events + } + }; + + info!("BLE: disconnected b/c {reason:?}"); + Ok(()) +} + +/// Example task to use the BLE notifier interface. +/// This task will notify the connected central of a counter value every 2 seconds. +/// It will also read the RSSI value every 2 seconds. +/// and will stop when the connection is closed by the central or an error occurs. +async fn custom_task(server: &Server<'_>, conn: &GattConnection<'_, '_, P>) { + loop { + embassy_time::Timer::after(embassy_time::Duration::from_millis(10)).await; + + let mut new_data = None; + critical_section::with(|cs| { + let tx_data_ref = TX_DATA.borrow(cs); + + if let Some(tx_data) = tx_data_ref.replace(None) { + new_data = Some(tx_data); + } + }); + + match new_data { + #[cfg(feature = "cam")] + Some(BleSendable::Cam(cam_event)) => { + let mut buf = cam_event.serialize_ble_proto(); + + if buf.len() <= CAM_EVENT_SIZE { + // pad with zeros + let pad = alloc::vec![0u8; CAM_EVENT_SIZE - buf.len()]; + buf.extend_from_slice(&pad); + + // unwrap should be fine since we ensured the vector size before + let value = buf.try_into().unwrap(); + + if let Err(err) = server.c_its_events.cam_event.notify(conn, &value).await { + warn!("BLE: failed notifying connection of new CAM event: {err:?}"); + } + } else { + warn!("BLE: CAM event too big with {} bytes", buf.len()); + } + } + #[cfg(feature = "denm")] + Some(BleSendable::Denm(denm_event)) => { + let mut buf = denm_event.serialize_ble_proto(); + + if buf.len() <= DENM_EVENT_SIZE { + // pad with zeros + let pad = alloc::vec![0u8; DENM_EVENT_SIZE - buf.len()]; + buf.extend_from_slice(&pad); + + // unwrap should be fine since we ensured the vector size before + let value = buf.try_into().unwrap(); + + if let Err(err) = server.c_its_events.denm_event.notify(conn, &value).await { + warn!("BLE: failed notifying connection of new DENM event: {err:?}"); + } + } else { + warn!("BLE: DENM event too big with {} bytes", buf.len()); + } + } + None => {} + } + } +} diff --git a/src/io.rs b/src/io.rs index 44997ee..3ef8c6d 100644 --- a/src/io.rs +++ b/src/io.rs @@ -1,16 +1,14 @@ -//! UART/ BLE I/O Protocol +//! UART and BLE I/O Protocol pub mod msg { #![allow(clippy::all, clippy::pedantic, clippy::nursery, dead_code)] include!(concat!(env!("OUT_DIR"), "/c_its_io.rs")); } -use alloc::string::ToString; -use alloc::vec::Vec; - +#[cfg(feature = "uart")] use c_its_parser::gn as geonetworking; -use log::error; +#[cfg(feature = "uart")] impl From for msg::SerialOutputMsg { fn from(value: msg::RawMsgRx) -> Self { use msg::serial_output_msg::Payload; @@ -21,6 +19,7 @@ impl From for msg::SerialOutputMsg { } } +#[cfg(feature = "uart")] impl From for msg::SerialOutputMsg { fn from(value: msg::DenmEvent) -> Self { use msg::serial_output_msg::Payload; @@ -32,22 +31,39 @@ impl From for msg::SerialOutputMsg { } impl msg::RawMsgRx { - pub fn serialize_uart_proto(&self) -> Vec { + #[cfg(feature = "uart")] + pub fn serialize_uart_proto(&self) -> alloc::vec::Vec { serialize_serial_msg(&Into::::into(self.clone())) } } + +#[cfg(feature = "denm")] impl msg::DenmEvent { - #[allow(unused)] - pub fn serialize_uart_proto(&self) -> Vec { - serialize_serial_msg(&Into::::into(*self)) + #[cfg(feature = "ble")] + pub fn serialize_ble_proto(&self) -> alloc::vec::Vec { + use prost::Message as _; + + self.encode_to_vec() } } +#[cfg(feature = "cam")] +impl msg::CamEvent { + #[cfg(feature = "ble")] + pub fn serialize_ble_proto(&self) -> alloc::vec::Vec { + use prost::Message as _; + + self.encode_to_vec() + } +} + +#[cfg(feature = "uart")] #[derive(Debug, Default)] pub struct Parser { buffer: alloc::vec::Vec, } +#[cfg(feature = "uart")] impl Parser { const SOF_LEN: u16 = 4; const SOF1: u8 = 0x56; @@ -91,7 +107,7 @@ impl Parser { Ok(data) => { output.push(data); } - Err(err) => error!("Failed to parse input: {err:?}"), + Err(err) => log::error!("Failed to parse input: {err:?}"), } // consume buffer @@ -122,7 +138,8 @@ impl Parser { } } -fn serialize_serial_msg(msg: &T) -> Vec +#[cfg(feature = "uart")] +fn serialize_serial_msg(msg: &T) -> alloc::vec::Vec where T: prost::Message, { @@ -141,6 +158,7 @@ where out } +#[cfg(any(feature = "uart", feature = "ble"))] impl From for crate::applogic::GnssFix { fn from(value: msg::PositionState) -> Self { let time = @@ -169,6 +187,7 @@ impl From for crate::applogic::GnssFix { } } +#[cfg(feature = "uart")] impl From for esp32_cits_core::PosVel { fn from(value: msg::PositionState) -> Self { let position = geo_types::Point::new( @@ -194,6 +213,7 @@ impl From for esp32_cits_core::PosVel { } } +#[cfg(feature = "uart")] impl From for esp32_cits_core::PosVel { fn from(value: msg::ItsPosition) -> Self { let latitude_deg = f64::from(value.latitude) / 10_000_000.; @@ -208,6 +228,7 @@ impl From for esp32_cits_core::PosVel { } } +#[cfg(feature = "uart")] impl esp32_cits_core::tx::GnItsPayload for msg::RawMsgTx { fn make_eh( &self, @@ -223,6 +244,8 @@ impl esp32_cits_core::tx::GnItsPayload for msg::RawMsgTx { ), alloc::string::String, > { + use alloc::string::ToString as _; + let station_type = self .station_type .and_then(|v| { @@ -296,14 +319,20 @@ impl esp32_cits_core::tx::GnItsPayload for msg::RawMsgTx { fn make_payload( &self, - ) -> Result<(Vec, c_its_parser::standards::extensions::ItsMessageId), alloc::string::String> - { + ) -> Result< + ( + alloc::vec::Vec, + c_its_parser::standards::extensions::ItsMessageId, + ), + alloc::string::String, + > { #[allow(clippy::cast_possible_truncation)] let message_id = (self.message_type as u8).try_into()?; Ok((self.payload.clone(), message_id)) } } +#[cfg(feature = "uart")] impl msg::RawMsgTx { fn make_geobcast( address: [u8; 6], @@ -384,12 +413,192 @@ impl msg::RawMsgTx { } } +#[cfg(feature = "cam")] +impl From> + for msg::CamEvent +{ + fn from( + value: alloc::boxed::Box, + ) -> Self { + use c_its_parser::standards::cam_1_4_1::cam_pdu_descriptions; + + let station_id = value.header.station_id.0; + let station_type = u32::from(value.cam.cam_parameters.basic_container.station_type.0); + let position = value + .cam + .cam_parameters + .basic_container + .reference_position + .into(); + + let vehicle_role = value + .cam + .cam_parameters + .low_frequency_container + .and_then(|v| match v { + cam_pdu_descriptions::LowFrequencyContainer::basicVehicleContainerLowFrequency( + basic_vehicle_container_low_frequency, + ) => Some(basic_vehicle_container_low_frequency.vehicle_role as u32), + _ => None, + }); + + let vehicle_data = match value.cam.cam_parameters.high_frequency_container { + cam_pdu_descriptions::HighFrequencyContainer::basicVehicleContainerHighFrequency( + basic_vehicle_container_high_frequency, + ) => Some(basic_vehicle_container_high_frequency.into()), + _ => None, + }; + + Self { + station_id, + station_type, + position, + vehicle_role, + vehicle_data, + } + } +} + +#[cfg(feature = "cam")] +impl From for msg::VehicleContainer { + fn from(value: c_its_parser::standards::cam_1_4_1::cam_pdu_descriptions::BasicVehicleContainerHighFrequency) -> Self { + let heading_deg = if value.heading.heading_value.is_unavailable() { + None + } else { + Some(u32::from(value.heading.heading_value.0)) + }; + let speed = if value.speed.speed_value.is_unavailable() { + None + } else { + Some(u32::from(value.speed.speed_value.0)) + }; + let vehicle_length_dm = if value.vehicle_length.vehicle_length_value.is_unavailable() { + None + } else { + Some(u32::from(value.vehicle_length.vehicle_length_value.0)) + }; + let vehicle_width_dm = if value.vehicle_width.is_unavailable() { + None + } else { + Some(u32::from(value.vehicle_width.0)) + }; + + + Self { heading_deg, speed, vehicle_length_dm, vehicle_width_dm } + } +} + +#[cfg(feature = "denm")] +impl From> + for msg::DenmEvent +{ + fn from( + value: alloc::boxed::Box, + ) -> Self { + let denm_mgmt = &value.denm.management; + + let station_id = value.header.station_id.0; // TODO: or from action ID? + + let ref_time = chrono::DateTime::::from(denm_mgmt.reference_time.clone()); + let end_time = ref_time + chrono::Duration::seconds(denm_mgmt.validity_duration.0.into()); + + #[allow(clippy::cast_sign_loss)] + let timestamp = ref_time.timestamp_millis() as u64; + #[allow(clippy::cast_sign_loss)] + let validity_end_ts = end_time.timestamp_millis() as u64; + let seq_num = denm_mgmt.action_id.sequence_number.0.into(); + + let event_position = Some(denm_mgmt.event_position.clone().into()); + let event_heading_deg = value + .denm + .location + .and_then(|v| v.event_position_heading) + .map(|v| { + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] + let conv = v.value.as_deg() as u32; + conv + }); + let direction = denm_mgmt + .traffic_direction + .map(|v| msg::TrafficDir::from(v) as i32); + let cc_tuple = value + .denm + .situation + .map(|v| v.event_type.cc_and_scc.to_u8_tuple()); + let cause_code = cc_tuple.map(|v| v.0.into()); + let sub_cause_code = cc_tuple.map(|v| v.1.into()); + + Self { + timestamp, + validity_end_ts, + station_id, + seq_num, + event_position, + event_heading_deg, + direction, + cause_code, + sub_cause_code, + } + } +} + +#[cfg(feature = "cam")] +impl From + for msg::Position +{ + fn from(value: c_its_parser::standards::cdd_1_3_1_1::its_container::ReferencePosition) -> Self { + #[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)] + let latitude_deg = value.latitude.as_deg() as f32; + #[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)] + let longitude_deg = value.longitude.as_deg() as f32; + + Self { + latitude_deg, + longitude_deg, + } + } +} + +#[cfg(feature = "denm")] +impl From for msg::Position { + fn from(value: c_its_parser::standards::cdd_2_2_1::etsi_its_cdd::ReferencePosition) -> Self { + #[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)] + let latitude_deg = value.latitude.as_deg() as f32; + #[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)] + let longitude_deg = value.longitude.as_deg() as f32; + + Self { + latitude_deg, + longitude_deg, + } + } +} + +#[cfg(feature = "denm")] +impl From for msg::TrafficDir { + fn from(value: c_its_parser::standards::cdd_2_2_1::etsi_its_cdd::TrafficDirection) -> Self { + use c_its_parser::standards::cdd_2_2_1::etsi_its_cdd::TrafficDirection; + + match value { + TrafficDirection::allTrafficDirections => Self::AllDirections, + TrafficDirection::sameAsReferenceDirection_upstreamOfReferencePosition => { + Self::Upstream + } + TrafficDirection::sameAsReferenceDirection_downstreamOfReferencePosition => { + Self::Downstream + } + TrafficDirection::oppositeToReferenceDirection => Self::Opposite, + } + } +} + #[cfg(test)] mod tests { use super::*; use crate::init_test_env_logger; + #[cfg(feature = "uart")] impl From for msg::SerialInputMsg { fn from(value: msg::RawMsgTx) -> Self { use msg::serial_input_msg::Payload; @@ -400,6 +609,7 @@ mod tests { } } + #[cfg(feature = "uart")] impl From for msg::SerialInputMsg { fn from(value: msg::PositionState) -> Self { use msg::serial_input_msg::Payload; @@ -413,7 +623,9 @@ mod tests { // manual example data: // SerialInputMsg(RawMsgTx): 5632582b0040123e0a340202c173256817284029d5b3400f1feaf01ffffffc23b7743e00000fc0007e8138250737feebfff600000dffff7ffff1ce40400010011807220028020d0a // SerialInputMsg(PositionState): 5632582b00170a1509a0bbe2a29f010000120a0d00002842150000b8410d0a + // PositionState: 09a0bbe2a29f010000120a0d00002842150000b841 + #[cfg(feature = "uart")] #[test] fn uart_parser() { // generate some test UART input messages diff --git a/src/main.rs b/src/main.rs index 67e181c..e37ba83 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,6 +18,9 @@ compile_error!("feature \"cam_tx\" needs a time and position source (either GNSS // we can't have both GNSS drivers enabled at the same time #[cfg(all(feature = "gnss_ubx", feature = "gnss_l67k"))] compile_error!("feature \"gnss_ubx\" and feature \"gnss_l67k\" cannot be enabled at the same time"); +// at least one V2X feature needs to be enabled +#[cfg(not(any(feature = "spat", feature = "cam", feature = "denm")))] +compile_error!("at least one V2X feature (\"spat\", \"cam\" or \"denm\") needs to be enabled"); use alloc::string::ToString as _; use alloc::vec::Vec; @@ -45,9 +48,11 @@ use log::{error, info, warn}; extern crate alloc; mod applogic; +#[cfg(feature = "ble")] +mod ble; mod cache; mod geo_alg; -#[cfg(feature = "uart")] +#[cfg(any(feature = "uart", feature = "ble"))] mod io; #[cfg(feature = "screen")] mod screen; @@ -89,7 +94,7 @@ static SERIAL: Mutex ! { // generator version: 1.3.0 - // generator parameters: --chip esp32c5 -o esp32c5-wroom-1-psram -o alloc -o log -o unstable-hal -o wifi -o esp-backtrace -o embassy + // generator parameters: --chip esp32c5 -o esp32c5-wroom-1-psram -o alloc -o log -o unstable-hal -o wifi -o esp-backtrace -o embassy -o ble-trouble esp_println::logger::init_logger_from_env(); @@ -107,6 +112,8 @@ async fn main(spawner: Spawner) -> ! { // - GPIO28 esp_alloc::heap_allocator!(#[esp_hal::ram(reclaimed)] size: 65536); + // COEX needs more RAM - so we've added some more + esp_alloc::heap_allocator!(size: 64 * 1024); esp_alloc::psram_allocator!(peripherals.PSRAM, esp_hal::psram); let timg0 = TimerGroup::new(peripherals.TIMG0); @@ -188,6 +195,13 @@ async fn main(spawner: Spawner) -> ! { let mac = esp_hal::efuse::base_mac_address(); info!("WiFi MAC: {mac}"); + // Setup BLE + #[cfg(feature = "ble")] + { + let controller = ble::make_controller(peripherals.BT); + spawner.spawn(ble::run(controller).expect("Failed to spawn BLE task")); + } + // Setup GNSS: // Seeed XIAO L67K: // RX: D7/ GPIO12 @@ -365,6 +379,17 @@ async fn main(spawner: Spawner) -> ! { } } + #[cfg(feature = "ble")] + { + critical_section::with(|cs| { + let mut data_rc = ble::RX_DATA.borrow(cs).borrow_mut(); + if let Some(pos) = data_rc.take() { + let gnss_update_ref = GNSS_UPDATE.borrow(cs); + gnss_update_ref.replace(Some(pos.into())); + } + }); + } + #[cfg(feature = "_gnss")] { let mut new_position = false; @@ -474,6 +499,13 @@ async fn main(spawner: Spawner) -> ! { } => { #[cfg(feature = "cam")] applogic::cam::handle_cam(&etsi); + + // Send data to BLE thread + #[cfg(all(feature = "ble", feature = "cam"))] + critical_section::with(|cs| { + let ble_update_ref = ble::TX_DATA.borrow(cs); + ble_update_ref.replace(Some(ble::BleSendable::Cam(etsi.into()))); + }); } #[cfg(feature = "denm")] @@ -481,7 +513,16 @@ async fn main(spawner: Spawner) -> ! { geonetworking: _, transport: _, etsi, - } => applogic::denm::handle_denm(&etsi), + } => { + applogic::denm::handle_denm(&etsi); + + // Send data to BLE thread + #[cfg(feature = "ble")] + critical_section::with(|cs| { + let ble_update_ref = ble::TX_DATA.borrow(cs); + ble_update_ref.replace(Some(ble::BleSendable::Denm(etsi.into()))); + }); + } #[cfg(feature = "denm")] ItsMessage::DenmV1 { geonetworking: _, From 4583ad8322ed5849d52d08323a9827c394fca3cf Mon Sep 17 00:00:00 2001 From: Jannik Beyerstedt Date: Mon, 3 Aug 2026 11:15:19 +0200 Subject: [PATCH 16/27] main: reboot on panic --- Cargo.toml | 1 - src/main.rs | 22 +++++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index aae53d0..0136b8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,7 +90,6 @@ embedded-io-async = { version = "0.7.0", optional = true } esp-alloc = { version = "0.10.0", optional = true } esp-backtrace = { version = "0.19.0", optional = true, features = [ "esp32c5", - "panic-handler", "println", ] } esp-println = { version = "0.17.0", optional = true, default-features = false, features = ["colors", "critical-section", "jtag-serial", "esp32c5", "log-04"] } diff --git a/src/main.rs b/src/main.rs index e37ba83..28c823e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -39,7 +39,7 @@ use embassy_gps::gps::l76k; use esp_backtrace as _; use esp_hal::clock::CpuClock; use esp_hal::timer::timg::TimerGroup; -#[cfg(all(target_arch = "riscv32", feature = "spat"))] +#[cfg(target_arch = "riscv32")] use esp_println::println; use esp32_cits_core::radio; use geonetworking::Decode as _; @@ -78,6 +78,26 @@ const SERIAL_FIFO_SIZE_THLD: u8 = 100; // hardware has max. 128 byte // For more information see: esp_bootloader_esp_idf::esp_app_desc!(); +// Print a backtrace and reset (instead of halting) +#[panic_handler] +fn panic_handler(info: &core::panic::PanicInfo) -> ! { + println!(""); + println!("====================== PANIC ======================"); + println!("{}", info); + + println!("\nBacktrace:"); + let backtrace = esp_backtrace::Backtrace::capture(); + for frame in backtrace.frames() { + println!("- 0x{:x}", frame.program_counter()); + } + + println!("================== BACKTRACE END =================="); + + // reboot + println!("-> Doing a SW reset after panic...\n"); + esp_hal::system::software_reset(); +} + #[cfg(feature = "_gnss")] static GNSS_UPDATE: Mutex>> = Mutex::new(RefCell::new(None)); static WIFI_RX_QUEUE: Mutex>>>> = Mutex::new(RefCell::new(None)); From 10653429e578a40f58dd23854f08c3d10e5b3880 Mon Sep 17 00:00:00 2001 From: Jannik Beyerstedt Date: Sat, 15 Aug 2026 14:18:06 +0200 Subject: [PATCH 17/27] GNSS: Increase update rate to 2 Hz --- src/main.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main.rs b/src/main.rs index 28c823e..02e4855 100644 --- a/src/main.rs +++ b/src/main.rs @@ -825,6 +825,8 @@ async fn gnss_ubx_config(reset_pin: esp_hal::peripherals::GPIO25<'static>) { cfg_data: &[ ublox::cfg_val::CfgVal::Uart1OutProtNmea(false), ublox::cfg_val::CfgVal::Uart1OutProtUbx(true), + // CFG-RATE-MEAS + ublox::cfg_val::CfgVal::RateMeas(500), // CFG-MSGOUT-UBX_NAV_PVT_UART1 ublox::cfg_val::CfgVal::MsgOutUbxNavPvtUart1(1), // CFG-MSGOUT-UBX_NAV_SAT_UART1 From 38434b0d864ade6d784285ee6fdaafd75bfca70c Mon Sep 17 00:00:00 2001 From: Jannik Beyerstedt Date: Sat, 15 Aug 2026 21:08:50 +0200 Subject: [PATCH 18/27] map: Extract max end time --- src/applogic/v2x/map.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/applogic/v2x/map.rs b/src/applogic/v2x/map.rs index bac8a69..d7e2195 100644 --- a/src/applogic/v2x/map.rs +++ b/src/applogic/v2x/map.rs @@ -173,6 +173,12 @@ impl Intersection { { sig_grp.likely_time = Some(likely.to_datetime_from_moy(moy, year)); } + if let Some(max) = &timing.max_end_time + && !max.is_out_of_range() + && !max.is_unknown() + { + sig_grp.max_end_time = Some(max.to_datetime_from_moy(moy, year)); + } } } } else { @@ -223,6 +229,7 @@ impl Intersection { target_lanes, phase: None, min_end_time: None, + max_end_time: None, likely_time: None, } }) @@ -410,7 +417,7 @@ pub struct SignalGroup { /// current signal phase phase: Option, min_end_time: Option>, - // max_end_time: Option>, + max_end_time: Option>, likely_time: Option>, } @@ -443,6 +450,9 @@ impl SignalGroup { pub fn likely_time(&self) -> Option> { self.likely_time } + pub fn max_end_time(&self) -> Option> { + self.max_end_time + } pub fn phase_to_str(&self) -> Option { self.phase.map(|v| { From 1a5012dec51d5daa7fe0cd2727c79b20b4ec63cf Mon Sep 17 00:00:00 2001 From: Jannik Beyerstedt Date: Sat, 1 Aug 2026 21:31:04 +0200 Subject: [PATCH 19/27] Adapt to new screen, add more GNSS and timing info --- Readme.md | 2 +- src/applogic/mod.rs | 70 +++++++---- src/main.rs | 20 +++- src/screen.rs | 277 +++++++++++++++++++++++++++++++++++++------- 4 files changed, 300 insertions(+), 69 deletions(-) diff --git a/Readme.md b/Readme.md index c2ecbab..5fb34e6 100644 --- a/Readme.md +++ b/Readme.md @@ -8,7 +8,7 @@ The code is currently tailored to: - U-Blox M10Q GNSS (connected on GPIO12 (RX), GPIO11 (TX), and GPIO25 (reset)) or Seeed XIAO L67K (connected on GPIO12 (RX), GPIO11 (TX), GPIO1 (wakeup) and GPIO25 (reset)) only available with `gnss_ubx` or `gnss_l67k` feature (which is included in `spat` feature) -- Some ST7789 172*320px screen (connected on GPIO8 (SCL/ SCK), GPIO10 (SDA/ MOSI), GPIO7 (reset), GPIO23 (DC), GPIO24 (CS), TODO GPIO0 (BL)) +- Some ST7789 240*280px screen (connected on GPIO8 (SCL/ SCK), GPIO10 (SDA/ MOSI), GPIO7 (reset), GPIO23 (DC), GPIO24 (CS), GPIO0 (BL)) only available with `screen` feature (which is included in `spat` feature) ## Features diff --git a/src/applogic/mod.rs b/src/applogic/mod.rs index 7e7ebc4..55672a7 100644 --- a/src/applogic/mod.rs +++ b/src/applogic/mod.rs @@ -106,7 +106,51 @@ pub struct GlosaSignalInfo { pub struct SignalGroupData { pub phase: SignalPhase, pub maneuver: map::Maneuvers, - pub end_sec: Option, + pub timing: Option, +} + +#[cfg(feature = "spat")] +#[derive(Debug, Default, Clone, PartialEq, Eq)] +#[allow(clippy::struct_field_names)] +pub struct TimingData { + pub min_end_sec: i16, + pub likely_end_sec: Option, + pub max_end_sec: Option, +} + +impl TimingData { + pub fn new(value: &map::SignalGroup, now: chrono::DateTime) -> Option { + let min = value + .min_end_time() + .map(|val| Self::time_to_duration_sec(now, val)); + let likely_end_sec = value + .likely_time() + .map(|val| Self::time_to_duration_sec(now, val)); + let max_end_sec = value + .max_end_time() + .map(|val| Self::time_to_duration_sec(now, val)); + + min.map(|min_end_sec| Self { + min_end_sec, + likely_end_sec, + max_end_sec, + }) + } + + fn time_to_duration_sec( + now: chrono::DateTime, + time_point: chrono::DateTime, + ) -> i16 { + use core::ops::Sub as _; + + use num_traits::Float as _; + + let dur = time_point.sub(now).as_seconds_f32(); + #[allow(clippy::cast_possible_truncation)] + let duration_sec = dur.round() as i16; + + duration_sec + } } #[cfg(feature = "spat")] @@ -357,33 +401,17 @@ impl State { let sig_grps = approach_sig_grps .iter() .filter_map(|sig| { - if let Some(manv) = sig.maneuvers() { - use core::ops::Sub; - - use num_traits::Float; - + if let Some(maneuver) = sig.maneuvers() { let phase = sig .phase() .map(core::convert::Into::into) .unwrap_or_default(); - - let end_sec = sig - .likely_time() - .or_else(|| sig.min_end_time()) - .map(|time| { - let dur = - time.sub(self.time.and_utc()).as_seconds_f32(); - - #[allow(clippy::cast_possible_truncation)] - let end_sec = dur.round() as i16; - - end_sec - }); + let timing = TimingData::new(sig, self.time.and_utc()); Some(SignalGroupData { phase, - maneuver: manv, - end_sec, + maneuver, + timing, }) } else { None diff --git a/src/main.rs b/src/main.rs index 02e4855..13be5d6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -154,8 +154,8 @@ async fn main(spawner: Spawner) -> ! { let mut spi_buffer = [0_u8; 512]; #[cfg(feature = "screen")] let mut screen = { - // 172*320px in landscape orientation - let (display, width, height) = screen::make_small_display( + // 240*280px in portrait orientation + let (display, size) = screen::make_large_display( peripherals.GPIO8, peripherals.GPIO10, peripherals.GPIO7, @@ -167,7 +167,7 @@ async fn main(spawner: Spawner) -> ! { ) .expect("Fatal error building screen"); - let mut screen = screen::Handler::new(display, width, height); + let mut screen = screen::Handler::new(display, size); let _ = screen .update(screen::ScreenState::Initial) @@ -381,6 +381,15 @@ async fn main(spawner: Spawner) -> ! { } Err(err) => warn!("{err}"), } + + // update GNSS HB on screen + let _ = screen.update_gnss(pvt); + } + ublox::proto27::PacketRef::AckNak(nak) => { + error!("UBX: {nak:?}"); + } + ublox::proto27::PacketRef::AckAck(ack) => { + info!("UBX: {ack:?}"); } _ => { println!("UBX: {packet_ref:?}"); @@ -460,8 +469,9 @@ async fn main(spawner: Spawner) -> ! { if let applogic::GlosaOutput::Locked(data) = &glosa_data { for sig in &data.signal_groups { let duration_str = sig - .end_sec - .map(|v| alloc::format!("for {v} s")) + .timing + .as_ref() + .map(|v| alloc::format!("for {} s", v.min_end_sec)) .unwrap_or_default(); println!("GLOSA: {} {} {duration_str}", sig.maneuver, sig.phase); } diff --git a/src/screen.rs b/src/screen.rs index b2ddf90..791d086 100644 --- a/src/screen.rs +++ b/src/screen.rs @@ -5,17 +5,24 @@ use embedded_graphics::{geometry, mono_font, pixelcolor, primitives, text}; use crate::applogic; +pub struct DisplaySize { + width: u16, + height: u16, + offset_top: i32, + corner_radius: i32, +} + pub struct Handler where D: DrawTarget, { target: D, - width: u16, - height: u16, + size: DisplaySize, // internal state prev_state: ScreenState, hb_state: bool, + gnss_hb_state: bool, } #[derive(Debug, Default, PartialEq, Eq)] @@ -79,16 +86,20 @@ where const DEFAULT_TEXT_STYLE: mono_font::MonoTextStyle<'static, pixelcolor::Rgb565> = mono_font::MonoTextStyle::new(&profont::PROFONT_24_POINT, Self::COLOR_FG); + const MID_TEXT_STYLE: mono_font::MonoTextStyle<'static, pixelcolor::Rgb565> = + mono_font::MonoTextStyle::new(&profont::PROFONT_18_POINT, Self::COLOR_FG); const SMALL_TEXT_STYLE: mono_font::MonoTextStyle<'static, pixelcolor::Rgb565> = mono_font::MonoTextStyle::new(&profont::PROFONT_14_POINT, Self::COLOR_FG); - pub fn new(target: D, width: u16, height: u16) -> Self { + const BIG_HEIGHT_THLD: u16 = 172; + + pub fn new(target: D, display_size: DisplaySize) -> Self { Self { target, - width, - height, + size: display_size, prev_state: ScreenState::Uninitialized, hb_state: false, + gnss_hb_state: false, } } @@ -115,18 +126,19 @@ where // Update indicator/ heartbeat { let update_ind_dia = 10; - let circle_x_pos = i32::from(self.width) - update_ind_dia - 10; + let circle_x_pos = + i32::from(self.size.width) - update_ind_dia - self.size.corner_radius; text::Text::with_alignment( "HB", - geometry::Point::new(circle_x_pos - 5, 10), + geometry::Point::new(circle_x_pos - 5, 10 + self.size.offset_top), Self::SMALL_TEXT_STYLE, text::Alignment::Right, ) .draw(&mut self.target)?; primitives::Circle::new( - geometry::Point::new(circle_x_pos, 2), + geometry::Point::new(circle_x_pos, 2 + self.size.offset_top), update_ind_dia.cast_unsigned(), ) .into_styled(if self.hb_state { @@ -143,13 +155,67 @@ where Ok(()) } + // on big screns: uses full screen width and height from 250 to 280 pixels + pub fn update_gnss( + &mut self, + pvt: &ublox::nav_pvt::proto27::NavPvtRef, + ) -> Result<(), D::Error> { + use alloc::string::ToString as _; + + // this only applies to big screens + if !self.is_big_height() { + return Ok(()); + } + + let num_sat = pvt.num_satellites(); + let fix_str = match pvt.fix_type() { + ublox::GnssFixType::NoFix + | ublox::GnssFixType::DeadReckoningOnly + | ublox::GnssFixType::TimeOnlyFix => { + alloc::format!("{num_sat:2} sat") + } + ublox::GnssFixType::Fix2D => "2D fix".to_string(), + ublox::GnssFixType::Fix3D | ublox::GnssFixType::GPSPlusDeadReckoning => { + "3D fix".to_string() + } + _ => " ".to_string(), + }; + + // clear area and write information + // note: text position is x baseline, not top left corner + primitives::Rectangle::new( + geometry::Point::new(0, 250 + self.size.offset_top), + geometry::Size::new(self.size.width.into(), 20), + ) + .into_styled(Self::BK_STYLE) + .draw(&mut self.target)?; + + let colon = if self.gnss_hb_state { ":" } else { " " }; + let message = alloc::format!( + "{fix_str}{colon} {:2.0}km/h, {:3.0}°", + pvt.ground_speed_2d() * 3.6, + pvt.heading_motion() + ); + text::Text::with_alignment( + &message, + geometry::Point::new(self.size.corner_radius / 2, 265 + self.size.offset_top), + Self::SMALL_TEXT_STYLE, + text::Alignment::Left, + ) + .draw(&mut self.target)?; + + self.gnss_hb_state = !self.gnss_hb_state; + + Ok(()) + } + fn clear(&mut self) -> Result<(), D::Error> where D: DrawTarget, { primitives::Rectangle::new( geometry::Point::new(0, 0), - geometry::Size::new(self.width.into(), self.height.into()), + geometry::Size::new(self.size.width.into(), self.size.height.into()), ) .into_styled(Self::BK_STYLE) .draw(&mut self.target)?; @@ -157,6 +223,10 @@ where Ok(()) } + fn is_big_height(&self) -> bool { + self.size.height > Self::BIG_HEIGHT_THLD + } + #[allow(unused)] pub fn test_img(&mut self) -> Result<(), D::Error> { mipidsi::TestImage::new().draw(&mut self.target)?; @@ -171,7 +241,7 @@ where text::Text::with_alignment( msg, - geometry::Point::new(i32::from(self.width) / 2, 40), + geometry::Point::new(i32::from(self.size.width) / 2, 40 + self.size.offset_top), Self::DEFAULT_TEXT_STYLE, text::Alignment::Center, ) @@ -186,8 +256,8 @@ where self.clear()?; text::Text::with_alignment( - "No upcoming SPAT", - geometry::Point::new(i32::from(self.width) / 2, 130), + "Searching...", + geometry::Point::new(i32::from(self.size.width) / 2, 130 + self.size.offset_top), Self::DEFAULT_TEXT_STYLE, text::Alignment::Center, ) @@ -209,7 +279,7 @@ where text::Text::with_alignment( &text, - geometry::Point::new(i32::from(self.width) / 2, 130), + geometry::Point::new(i32::from(self.size.width) / 2, 130 + self.size.offset_top), Self::DEFAULT_TEXT_STYLE, text::Alignment::Center, ) @@ -218,6 +288,9 @@ where Ok(()) } + // uses full screen width and height + // - from 0 to 105+67 = 172 pixels for small screens + // - from 0 to 105+67+20 = 192 pixels for big screens #[allow(unused)] fn draw_glosa( &mut self, @@ -226,6 +299,8 @@ where ) -> Result<(), D::Error> { let num_signals = data.signal_groups.len(); + let is_big_height = self.is_big_height(); + if let Some(prev) = prev_data && prev.signal_groups.len() != num_signals { @@ -233,25 +308,30 @@ where self.clear(); } else { // only clear maneuver and timing area otherwise - + let sig_total_height = 67 + if is_big_height { 20 } else { 0 }; primitives::Rectangle::new( - geometry::Point::new(0, 105), - geometry::Size::new(self.width.into(), 67), + geometry::Point::new(0, 105 + self.size.offset_top), + geometry::Size::new(self.size.width.into(), sig_total_height), ) .into_styled(Self::BK_STYLE) .draw(&mut self.target)?; } + // do nothing more when no signals + if num_signals == 0 { + return Ok(()); + } + // draw intersection ID text::Text::new( &alloc::format!("#{}", data.intersection_id), - geometry::Point::new(15, 10), + geometry::Point::new(self.size.corner_radius + 5, 10 + self.size.offset_top), 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); + let sig_width = i32::from(self.size.width) / (num_signals as i32); for (idx, sig) in data.signal_groups.iter().enumerate() { #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] @@ -269,7 +349,7 @@ where applogic::SignalPhase::RedYellow | applogic::SignalPhase::Yellow => Self::YE_STYLE, }; primitives::Circle::new( - geometry::Point::new(offset + circle_margin, 15), + geometry::Point::new(offset + circle_margin, 15 + self.size.offset_top), circle_dia.cast_unsigned(), ) .into_styled(style) @@ -280,24 +360,50 @@ where // draw maneuver text::Text::with_alignment( &maneuver_to_str(sig.maneuver), - geometry::Point::new(centerline, 130), + geometry::Point::new(centerline, 130 + self.size.offset_top), Self::DEFAULT_TEXT_STYLE, text::Alignment::Center, ) .draw(&mut self.target)?; - // draw timing indicator - if let Some(end_sec) = sig.end_sec { - text::Text::with_alignment( - &alloc::format!("{end_sec}s"), - geometry::Point::new(centerline, 160), - Self::DEFAULT_TEXT_STYLE, - text::Alignment::Center, - ) - .draw(&mut self.target)?; + // draw timing indicator(s) + if let Some(timing) = &sig.timing { + if let (Some(likely), Some(max)) = (timing.likely_end_sec, timing.max_end_sec) { + text::Text::with_alignment( + &alloc::format!("{likely}s"), + geometry::Point::new(centerline, 160 + self.size.offset_top), + Self::DEFAULT_TEXT_STYLE, + text::Alignment::Center, + ) + .draw(&mut self.target)?; + + // only for big screen + if is_big_height { + // TODO: show as diff to likely time? + text::Text::with_alignment( + &alloc::format!("{}-{max}", timing.min_end_sec), + geometry::Point::new(centerline, 185 + self.size.offset_top), + Self::MID_TEXT_STYLE, + text::Alignment::Center, + ) + .draw(&mut self.target)?; + } + } else { + text::Text::with_alignment( + &alloc::format!("{}s", timing.min_end_sec), + geometry::Point::new(centerline, 160 + self.size.offset_top), + Self::DEFAULT_TEXT_STYLE, + text::Alignment::Center, + ) + .draw(&mut self.target)?; + } } } + // if is_big_height { + // // TODO: Add ETA + // } + Ok(()) } @@ -307,7 +413,7 @@ where self.clear()?; - let sig_width = i32::from(self.width) / 3; + let sig_width = i32::from(self.size.width) / 3; let circle_dia = 50; let circle_margin = (sig_width - circle_dia) / 2; @@ -319,7 +425,7 @@ where let offset = (idx as i32) * sig_width; primitives::Circle::new( - geometry::Point::new(offset + circle_margin, 15), + geometry::Point::new(offset + circle_margin, 15 + self.size.offset_top), circle_dia.cast_unsigned(), ) .into_styled(*style) @@ -327,10 +433,10 @@ where } // draw welcome text - let centerline = i32::from(self.width) / 2; + let centerline = i32::from(self.size.width) / 2; text::Text::with_alignment( - "C-ITS Signal Phase Display\nWaiting for GNSS fix...", - geometry::Point::new(centerline, 130), + "C-ITS Signal Phase\nWaiting for GNSS...", + geometry::Point::new(centerline, 130 + self.size.offset_top), text_style, text::Alignment::Center, ) @@ -353,7 +459,7 @@ fn maneuver_to_str(value: applogic::v2x::map::Maneuvers) -> alloc::string::Strin /// /// # Errors /// Human-readable fatal errors -#[allow(clippy::too_many_arguments, clippy::type_complexity)] +#[allow(unused, clippy::too_many_arguments, clippy::type_complexity)] pub fn make_small_display( scl: IoScl, sda: IoSda, @@ -378,8 +484,7 @@ pub fn make_small_display( mipidsi::models::ST7789, esp_hal::gpio::Output<'_>, >, - u16, - u16, + DisplaySize, ), alloc::string::String, > @@ -396,10 +501,98 @@ where esp_hal::gpio::interconnect::PeripheralOutput<'static> + esp_hal::gpio::OutputPin + 'static, Spi: esp_hal::spi::master::Instance + 'static, { - const DISPLAY_WIDTH: u16 = 320; - const DISPLAY_HEIGHT: u16 = 172; const DISPLAY_HIDDEN_X: u16 = 34; // somehow this display has 34 px of invisible space on the left (in native orientation) + let size = DisplaySize { + width: 320, + height: 172, + offset_top: 0, + corner_radius: 10, + }; + + let (spi, cs) = setup_st7789_spi(scl, sda, cs, spi) + .map_err(|err| alloc::format!("Failed to build SPI config: {err}"))?; + + let output_config = esp_hal::gpio::OutputConfig::default(); + let dc_output = esp_hal::gpio::Output::new(dc, esp_hal::gpio::Level::Low, output_config); + let rst_output = esp_hal::gpio::Output::new(res, esp_hal::gpio::Level::High, output_config); + let _ = esp_hal::gpio::Output::new(bl, esp_hal::gpio::Level::High, output_config); // enable backlight + + let mut display_delay = esp_hal::delay::Delay::new(); + + let spi_device = embedded_hal_bus::spi::ExclusiveDevice::new_no_delay(spi, cs) + .map_err(|err| alloc::format!("Failed to initialize SPI device: {err}"))?; + + // Note: height and width are swapped b/c of 90° display rotation + let display = mipidsi::Builder::new( + mipidsi::models::ST7789, + mipidsi::interface::SpiInterface::new(spi_device, dc_output, spi_buffer), + ) + .display_size(size.height + DISPLAY_HIDDEN_X, size.width) + .reset_pin(rst_output) + .invert_colors(mipidsi::options::ColorInversion::Inverted) + .orientation(mipidsi::options::Orientation::new().rotate(mipidsi::options::Rotation::Deg90)) + .init(&mut display_delay) + .map_err(|err| alloc::format!("Failed to initialize display: {err:?}"))?; + + Ok((display, size)) +} + +/// Creates an [`mipidsi::Display`], width, height tuple for an ST7789 240*280px screen in portrait orientation +/// +/// # Errors +/// Human-readable fatal errors +#[allow(unused, clippy::too_many_arguments, clippy::type_complexity)] +pub fn make_large_display( + scl: IoScl, + sda: IoSda, + res: IoRes, + dc: IoDc, + cs: IoCs, + bl: IoBl, + spi: Spi, + spi_buffer: &mut [u8], +) -> Result< + ( + mipidsi::Display< + mipidsi::interface::SpiInterface< + '_, + embedded_hal_bus::spi::ExclusiveDevice< + esp_hal::spi::master::Spi<'_, esp_hal::Blocking>, + esp_hal::gpio::Output<'_>, + embedded_hal_bus::spi::NoDelay, + >, + esp_hal::gpio::Output<'_>, + >, + mipidsi::models::ST7789, + esp_hal::gpio::Output<'_>, + >, + DisplaySize, + ), + alloc::string::String, +> +where + IoScl: esp_hal::gpio::interconnect::PeripheralOutput<'static>, + IoSda: esp_hal::gpio::interconnect::PeripheralOutput<'static>, + IoRes: + esp_hal::gpio::interconnect::PeripheralOutput<'static> + esp_hal::gpio::OutputPin + 'static, + IoDc: + esp_hal::gpio::interconnect::PeripheralOutput<'static> + esp_hal::gpio::OutputPin + 'static, + IoCs: + esp_hal::gpio::interconnect::PeripheralOutput<'static> + esp_hal::gpio::OutputPin + 'static, + IoBl: + esp_hal::gpio::interconnect::PeripheralOutput<'static> + esp_hal::gpio::OutputPin + 'static, + Spi: esp_hal::spi::master::Instance + 'static, +{ + const DISPLAY_HIDDEN_Y: u16 = 20; // somehow this display has 20 px of invisible space on the top (in native orientation) + + let size = DisplaySize { + width: 240, + height: 280 + DISPLAY_HIDDEN_Y, + offset_top: DISPLAY_HIDDEN_Y.into(), + corner_radius: 30, + }; + let (spi, cs) = setup_st7789_spi(scl, sda, cs, spi) .map_err(|err| alloc::format!("Failed to build SPI config: {err}"))?; @@ -417,14 +610,14 @@ where mipidsi::models::ST7789, mipidsi::interface::SpiInterface::new(spi_device, dc_output, spi_buffer), ) - .display_size(DISPLAY_HEIGHT + DISPLAY_HIDDEN_X, DISPLAY_WIDTH) + .display_size(size.width, size.height + DISPLAY_HIDDEN_Y) .reset_pin(rst_output) .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::Deg0)) .init(&mut display_delay) .map_err(|err| alloc::format!("Failed to initialize display: {err:?}"))?; - Ok((display, DISPLAY_WIDTH, DISPLAY_HEIGHT)) + Ok((display, size)) } /// Builds the SPI interface for the ST7789 display From fb47f17c3fb7ffe3807a12a91635873c2bf8d6e3 Mon Sep 17 00:00:00 2001 From: Jannik Beyerstedt Date: Sat, 15 Aug 2026 21:29:41 +0200 Subject: [PATCH 20/27] GNSS: Don't print warnings on no fix --- src/main.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/main.rs b/src/main.rs index 13be5d6..5e4e0c5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -372,14 +372,12 @@ async fn main(spawner: Spawner) -> ! { println!("{}", ubx::pretty_print_nav_sat(packet)); } ublox::proto27::PacketRef::NavPvt(pvt) => { - match applogic::GnssFix::try_from(pvt) { - Ok(pos_state) => { - critical_section::with(|cs| { - let gnss_update_ref = GNSS_UPDATE.borrow(cs); - gnss_update_ref.replace(Some(pos_state)); - }); - } - Err(err) => warn!("{err}"), + // error can be ignored b/c it's just when no GNSS fix yet + if let Ok(pos_state) = applogic::GnssFix::try_from(pvt) { + critical_section::with(|cs| { + let gnss_update_ref = GNSS_UPDATE.borrow(cs); + gnss_update_ref.replace(Some(pos_state)); + }); } // update GNSS HB on screen From a2b0819fb011a4a333bbbdc5b2bd806f05f0f0a1 Mon Sep 17 00:00:00 2001 From: Jannik Beyerstedt Date: Mon, 17 Aug 2026 15:00:00 +0200 Subject: [PATCH 21/27] main: Tweak vehicle size --- src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 5e4e0c5..6eb670c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -68,9 +68,9 @@ const GN_IS_MOBILE: bool = true; #[cfg(feature = "cam_tx")] const OWN_STATION_TYPE: ItsStationType = ItsStationType::Cyclist; #[cfg(feature = "cam_tx")] -const OWN_VEHICLE_WIDTH: u8 = 5; // in 10cm steps! +const OWN_VEHICLE_WIDTH: u8 = 7; // in 10cm steps! #[cfg(feature = "cam_tx")] -const OWN_VEHICLE_LENGTH: u16 = 20; // in 10cm steps! +const OWN_VEHICLE_LENGTH: u16 = 18; // in 10cm steps! #[cfg(feature = "_uart")] const SERIAL_FIFO_SIZE_THLD: u8 = 100; // hardware has max. 128 byte From 352ef6fb7a1c945f4fddf8d7b8942b8a567daecc Mon Sep 17 00:00:00 2001 From: Jannik Beyerstedt Date: Mon, 17 Aug 2026 15:20:49 +0200 Subject: [PATCH 22/27] applogic: Add missing feature flag for `TimingData` impl --- src/applogic/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/applogic/mod.rs b/src/applogic/mod.rs index 55672a7..0ced524 100644 --- a/src/applogic/mod.rs +++ b/src/applogic/mod.rs @@ -118,6 +118,7 @@ pub struct TimingData { pub max_end_sec: Option, } +#[cfg(feature = "spat")] impl TimingData { pub fn new(value: &map::SignalGroup, now: chrono::DateTime) -> Option { let min = value From 2dfcfef58d568bbf8686386acb0cbafb72fd7b92 Mon Sep 17 00:00:00 2001 From: Jannik Beyerstedt Date: Wed, 19 Aug 2026 15:35:38 +0200 Subject: [PATCH 23/27] BLE: Remove denm dependency, align default features with readme --- Cargo.toml | 4 ++-- Readme.md | 4 +--- feat-permut.sh | 2 ++ 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0136b8e..6dc5d8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ path = "./src/test.rs" required-features = ["std", "cam"] [features] -default = ["esp", "spat", "gnss_ubx", "screen", "ble"] +default = ["esp", "spat", "gnss_ubx", "screen"] # build for ESP (needed to build test for `host-tuple`) esp = [ @@ -57,7 +57,7 @@ screen = ["dep:mipidsi", "dep:embedded-graphics", "dep:embedded-hal-bus", "dep:p # enable I/O via UART uart = ["_uart", "_gnss"] # enable I/O via BLE -ble = ["_gnss", "denm"] +ble = ["_gnss"] # internal feature, that any UART I/O is enabled _uart = [] diff --git a/Readme.md b/Readme.md index 5fb34e6..16453c6 100644 --- a/Readme.md +++ b/Readme.md @@ -57,9 +57,7 @@ Enable UART I/O protocol according to [docs/uart-protocol.md](./docs/uart-protoc ### BLE Enable BLE I/O protocol according to [docs/ble-protocol.md](./docs/ble-protocol.md). -Will enable `denm` feature, but also enabling `cam` is recommended for full functionality. - -Note: Raw message reception is currently not implemented! +Enabling `denm` feature or `cam` feature is recommended, otherwise no data will be received. ## Usage diff --git a/feat-permut.sh b/feat-permut.sh index 1ef6b62..287dbcb 100755 --- a/feat-permut.sh +++ b/feat-permut.sh @@ -15,6 +15,8 @@ features_base=( spat,gnss_ubx,screen,cam_tx ) features_more=( + spat,gnss_ubx,screen,ble + spat,gnss_ubx,screen,ble,cam_tx spat,gnss_ubx,screen,cam,denm spat,gnss_ubx,screen,cam_tx,cam,denm ) From 9211c82d6fb395961223013c8b5176001b5b745a Mon Sep 17 00:00:00 2001 From: Jannik Beyerstedt Date: Wed, 19 Aug 2026 14:37:49 +0200 Subject: [PATCH 24/27] BLE: Use unique name --- src/ble.rs | 3 ++- src/main.rs | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/ble.rs b/src/ble.rs index b585e86..eb95c93 100644 --- a/src/ble.rs +++ b/src/ble.rs @@ -75,6 +75,7 @@ pub async fn run( esp_radio::ble::controller::BleConnector<'static>, CONTROLLER_SLOTS, >, + name: alloc::string::String, ) { let mut resources: HostResources = HostResources::new(); @@ -87,7 +88,7 @@ pub async fn run( info!("Starting advertising and GATT service"); let server = Server::new_with_config(GapConfig::Peripheral(PeripheralConfig { - name: "ESP32 C-ITS", + name: &name, appearance: &appearance::UNKNOWN, })) .unwrap(); diff --git a/src/main.rs b/src/main.rs index 6eb670c..d3adbbd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -219,7 +219,14 @@ async fn main(spawner: Spawner) -> ! { #[cfg(feature = "ble")] { let controller = ble::make_controller(peripherals.BT); - spawner.spawn(ble::run(controller).expect("Failed to spawn BLE task")); + let serial = &mac.as_bytes()[3..6]; + let name = alloc::format!( + "ESP32 C-ITS {:02x}{:02x}{:02x}", + serial[0], + serial[1], + serial[2] + ); + spawner.spawn(ble::run(controller, name).expect("Failed to spawn BLE task")); } // Setup GNSS: From 610d4a925ce8b04e240433a2f3b876b1d49a971a Mon Sep 17 00:00:00 2001 From: Jannik Beyerstedt Date: Mon, 17 Aug 2026 11:35:00 +0200 Subject: [PATCH 25/27] BLE: Fix UUIDs --- docs/ble-protocol.md | 12 ++++++------ src/ble.rs | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/ble-protocol.md b/docs/ble-protocol.md index 4560424..8d742e2 100644 --- a/docs/ble-protocol.md +++ b/docs/ble-protocol.md @@ -19,12 +19,12 @@ V2X data is exposed via the service UUID `c0b70000-d4f4-4000-ada8-f99a02ee315c` UUID | Type | Description ------------------------------------- | --------------- | ----------- c0b70001-d4f4-4001-ada8-f99a02ee315c | `PositionState` | Set position from external source -c0b70001-d4f4-4001-ada8-f99a02ee315c | `RawMsgRx` | Raw CAM message (de-duplicated/ rate-limited) -c0b70001-d4f4-4002-ada8-f99a02ee315c | `CamEvent` | Latest received CAM -c0b70001-d4f4-4003-ada8-f99a02ee315c | `RawMsgRx` | Raw DENM message (de-duplicated/ rate-limited) -c0b70001-d4f4-4004-ada8-f99a02ee315c | `DenmEvent` | Latest received DENM -c0b70001-d4f4-4005-ada8-f99a02ee315c | `RawMsgRx` | Raw MAPEM message (de-duplicated/ rate-limited) -c0b70001-d4f4-4006-ada8-f99a02ee315c | `RawMsgRx` | Raw SPATEM message (de-duplicated/ rate-limited) +c0b70001-d4f4-4002-ada8-f99a02ee315c | `RawMsgRx` | Raw CAM message (de-duplicated/ rate-limited) +c0b70001-d4f4-4003-ada8-f99a02ee315c | `CamEvent` | Latest received CAM +c0b70001-d4f4-4004-ada8-f99a02ee315c | `RawMsgRx` | Raw DENM message (de-duplicated/ rate-limited) +c0b70001-d4f4-4005-ada8-f99a02ee315c | `DenmEvent` | Latest received DENM +c0b70001-d4f4-4006-ada8-f99a02ee315c | `RawMsgRx` | Raw MAPEM message (de-duplicated/ rate-limited) +c0b70001-d4f4-4007-ada8-f99a02ee315c | `RawMsgRx` | Raw SPATEM message (de-duplicated/ rate-limited) ## Message Format diff --git a/src/ble.rs b/src/ble.rs index eb95c93..030d843 100644 --- a/src/ble.rs +++ b/src/ble.rs @@ -44,12 +44,12 @@ struct CITSEvents { #[cfg(feature = "cam")] #[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, name = "CAM_Event", read, value = "CamEvent proto msg", type = &'static str)] - #[characteristic(uuid = "c0b70001-d4f4-4002-ada8-f99a02ee315c", read, notify, value = [0u8; CAM_EVENT_SIZE])] + #[characteristic(uuid = "c0b70001-d4f4-4003-ada8-f99a02ee315c", read, notify, value = [0u8; CAM_EVENT_SIZE])] cam_event: [u8; CAM_EVENT_SIZE], #[cfg(feature = "denm")] #[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, name = "DENM_Event", read, value = "DenmEvent proto msg", type = &'static str)] - #[characteristic(uuid = "c0b70001-d4f4-4004-ada8-f99a02ee315c", read, notify, value = [0u8; DENM_EVENT_SIZE])] + #[characteristic(uuid = "c0b70001-d4f4-4005-ada8-f99a02ee315c", read, notify, value = [0u8; DENM_EVENT_SIZE])] denm_event: [u8; DENM_EVENT_SIZE], } From 059f47df561788403db8c8325ac677b42aaef0b1 Mon Sep 17 00:00:00 2001 From: Jannik Beyerstedt Date: Mon, 17 Aug 2026 14:53:48 +0200 Subject: [PATCH 26/27] BLE: Add raw messages BLE can only fit 251 bytes of payload in notifications which isn't nearly enough for a raw V2X message. So only publish a notification which message was received and let the client poll the content from the characteristic (since reads can get data in fragments) --- docs/ble-protocol.md | 33 ++++- docs/c_its-messages.proto | 4 + src/ble.rs | 247 ++++++++++++++++++++++++++++++-------- src/io.rs | 146 ++++++++++++++++++++-- src/main.rs | 67 ++++++++--- 5 files changed, 413 insertions(+), 84 deletions(-) diff --git a/docs/ble-protocol.md b/docs/ble-protocol.md index 8d742e2..e8ad492 100644 --- a/docs/ble-protocol.md +++ b/docs/ble-protocol.md @@ -19,12 +19,16 @@ V2X data is exposed via the service UUID `c0b70000-d4f4-4000-ada8-f99a02ee315c` UUID | Type | Description ------------------------------------- | --------------- | ----------- c0b70001-d4f4-4001-ada8-f99a02ee315c | `PositionState` | Set position from external source -c0b70001-d4f4-4002-ada8-f99a02ee315c | `RawMsgRx` | Raw CAM message (de-duplicated/ rate-limited) -c0b70001-d4f4-4003-ada8-f99a02ee315c | `CamEvent` | Latest received CAM -c0b70001-d4f4-4004-ada8-f99a02ee315c | `RawMsgRx` | Raw DENM message (de-duplicated/ rate-limited) -c0b70001-d4f4-4005-ada8-f99a02ee315c | `DenmEvent` | Latest received DENM -c0b70001-d4f4-4006-ada8-f99a02ee315c | `RawMsgRx` | Raw MAPEM message (de-duplicated/ rate-limited) -c0b70001-d4f4-4007-ada8-f99a02ee315c | `RawMsgRx` | Raw SPATEM message (de-duplicated/ rate-limited) +c0b70001-d4f4-4002-ada8-f99a02ee315c | `RawNotify` | Raw message notification +c0b70001-d4f4-4003-ada8-f99a02ee315c | `RawMsgRx` | Raw CAM message (de-duplicated/ rate-limited) +c0b70001-d4f4-4004-ada8-f99a02ee315c | `CamEvent` | Latest received CAM +c0b70001-d4f4-4005-ada8-f99a02ee315c | `RawMsgRx` | Raw DENM message (de-duplicated/ rate-limited) +c0b70001-d4f4-4006-ada8-f99a02ee315c | `DenmEvent` | Latest received DENM +c0b70001-d4f4-4007-ada8-f99a02ee315c | `RawMsgRx` | Raw MAPEM message (de-duplicated/ rate-limited) +c0b70001-d4f4-4008-ada8-f99a02ee315c | `RawMsgRx` | Raw SPATEM message (de-duplicated/ rate-limited) + +Since a modern BLE notification can only fit up tp 251 bytes of payload, the raw messages can only be polled. +Please subscribe to `c0b70001-d4f4-4002-ada8-f99a02ee315c` and use the message ID to determine from which service to read the incoming value. ## Message Format @@ -101,3 +105,20 @@ Repetitions of the same message should be dropped by the BLE server. Consecutive messages with no relevant changes may be dropped by the BLE server while keeping some minimal publishing rate, e.g.: - only publish MAPEMs every 10 seconds (since content doesn't change, but client may be restarted after first MAPEM from an intersection was received) - drop high-frequency CAMs (only publish "full" CAMs which include the low-frequency container) + +Maximum message size (assuming realistic value ranges): + +field name | tag size | (max.) data size +--------------------- | -------- | --------- +msg_type | 1 | 1 (VARINT(21)) +payload | 1 | 2+1394 (LEN(1394) + payload) +**SUM** | 2 + | 1397 = 1399 byte + +### Raw Message Notification + +Maximum message size (assuming realistic value ranges): + +field name | tag size | (max.) data size +--------------------- | -------- | --------- +msg_type | 1 | 1 (VARINT(21)) +**SUM** | 1 + | 1 = 2 byte diff --git a/docs/c_its-messages.proto b/docs/c_its-messages.proto index 47e523d..9323b3e 100644 --- a/docs/c_its-messages.proto +++ b/docs/c_its-messages.proto @@ -71,6 +71,10 @@ message RawMsgTx { optional uint32 stationType = 6; // for source position vector } +message RawNotify { + required uint32 message_type = 1; // ITS `MessageId` integer value +} + // ------------------------------ // Messages // ------------------------------ diff --git a/src/ble.rs b/src/ble.rs index 030d843..8fa5ead 100644 --- a/src/ble.rs +++ b/src/ble.rs @@ -6,6 +6,7 @@ use core::cell::RefCell; use critical_section::Mutex; +use crossbeam_queue::ArrayQueue; use esp_backtrace as _; use log::{info, warn}; use trouble_host::prelude::*; @@ -16,14 +17,26 @@ pub const CONTROLLER_SLOTS: usize = 20; const CONNECTIONS_MAX: usize = 1; const L2CAP_CHANNELS_MAX: usize = 1; -pub static TX_DATA: Mutex>> = Mutex::new(RefCell::new(None)); +pub static CLIENT_CONNECTED: Mutex> = Mutex::new(RefCell::new(false)); +pub static TX_DATA: Mutex>>> = + Mutex::new(RefCell::new(None)); pub static RX_DATA: Mutex>> = Mutex::new(RefCell::new(None)); pub enum BleSendable { + #[cfg(feature = "cam")] + CamRaw(io::msg::RawMsgRx), #[cfg(feature = "cam")] Cam(io::msg::CamEvent), + + #[cfg(feature = "denm")] + DenmRaw(io::msg::RawMsgRx), #[cfg(feature = "denm")] Denm(io::msg::DenmEvent), + + #[cfg(feature = "spat")] + MapemRaw(io::msg::RawMsgRx), + #[cfg(feature = "spat")] + SpatemRaw(io::msg::RawMsgRx), } #[gatt_server] @@ -31,26 +44,43 @@ struct Server { c_its_events: CITSEvents, } -#[cfg(feature = "cam")] -const CAM_EVENT_SIZE: usize = 38; -#[cfg(feature = "denm")] -const DENM_EVENT_SIZE: usize = 51; - #[gatt_service(uuid = "c0b70000-d4f4-4000-ada8-f99a02ee315c")] struct CITSEvents { #[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, name = "Position", read, value = "PositionState proto msg", type = &'static str)] #[characteristic(uuid = "c0b70001-d4f4-4001-ada8-f99a02ee315c", write)] position: [u8; 27], + #[cfg(any(feature = "cam", feature = "denm", feature = "spat"))] + #[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, name = "Raw_Notify", read, value = "Raw Message Notification", type = &'static str)] + #[characteristic(uuid = "c0b70001-d4f4-4002-ada8-f99a02ee315c", read, notify, value = [0u8; io::msg::RawNotify::PROTO_SIZE])] + raw_notify: [u8; io::msg::RawNotify::PROTO_SIZE], + + #[cfg(feature = "cam")] + #[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, name = "CAM", read, value = "RawMsgRx proto msg", type = &'static str)] + #[characteristic(uuid = "c0b70001-d4f4-4003-ada8-f99a02ee315c", read, value = [0u8; io::msg::RawMsgRx::PROTO_SIZE])] + cam_raw: [u8; io::msg::RawMsgRx::PROTO_SIZE], #[cfg(feature = "cam")] #[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, name = "CAM_Event", read, value = "CamEvent proto msg", type = &'static str)] - #[characteristic(uuid = "c0b70001-d4f4-4003-ada8-f99a02ee315c", read, notify, value = [0u8; CAM_EVENT_SIZE])] - cam_event: [u8; CAM_EVENT_SIZE], + #[characteristic(uuid = "c0b70001-d4f4-4004-ada8-f99a02ee315c", read, notify, value = [0u8; io::msg::CamEvent::PROTO_SIZE])] + cam_event: [u8; io::msg::CamEvent::PROTO_SIZE], + #[cfg(feature = "denm")] + #[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, name = "DENM", read, value = "RawMsgRx proto msg", type = &'static str)] + #[characteristic(uuid = "c0b70001-d4f4-4005-ada8-f99a02ee315c", read, value = [0u8; io::msg::RawMsgRx::PROTO_SIZE])] + denm_raw: [u8; io::msg::RawMsgRx::PROTO_SIZE], #[cfg(feature = "denm")] #[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, name = "DENM_Event", read, value = "DenmEvent proto msg", type = &'static str)] - #[characteristic(uuid = "c0b70001-d4f4-4005-ada8-f99a02ee315c", read, notify, value = [0u8; DENM_EVENT_SIZE])] - denm_event: [u8; DENM_EVENT_SIZE], + #[characteristic(uuid = "c0b70001-d4f4-4006-ada8-f99a02ee315c", read, notify, value = [0u8; io::msg::DenmEvent::PROTO_SIZE])] + denm_event: [u8; io::msg::DenmEvent::PROTO_SIZE], + + #[cfg(feature = "spat")] + #[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, name = "MAPEM", read, value = "RawMsgRx proto msg", type = &'static str)] + #[characteristic(uuid = "c0b70001-d4f4-4007-ada8-f99a02ee315c", read, value = [0u8; io::msg::RawMsgRx::PROTO_SIZE])] + mapem_raw: [u8; io::msg::RawMsgRx::PROTO_SIZE], + #[cfg(feature = "spat")] + #[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, name = "SPATEM", read, value = "RawMsgRx proto msg", type = &'static str)] + #[characteristic(uuid = "c0b70001-d4f4-4008-ada8-f99a02ee315c", read, value = [0u8; io::msg::RawMsgRx::PROTO_SIZE])] + spatem_raw: [u8; io::msg::RawMsgRx::PROTO_SIZE], } /// Creates the BLE controller @@ -60,6 +90,10 @@ pub fn make_controller( esp_radio::ble::controller::BleConnector<'static>, CONTROLLER_SLOTS, > { + critical_section::with(|cs| { + TX_DATA.borrow(cs).replace(Some(ArrayQueue::new(2))); + }); + let transport = esp_radio::ble::controller::BleConnector::new( bt_peripheral, esp_radio::ble::Config::default(), @@ -86,7 +120,21 @@ pub async fn run( .. } = stack.build(); - info!("Starting advertising and GATT service"); + let mut features = alloc::vec![]; + if cfg!(feature = "cam") { + features.push("cam"); + } + if cfg!(feature = "denm") { + features.push("denm"); + } + if cfg!(feature = "spat") { + features.push("mapem"); + features.push("spatem"); + } + info!( + "Starting advertising and GATT service for {}", + features.join(",") + ); let server = Server::new_with_config(GapConfig::Peripheral(PeripheralConfig { name: &name, appearance: &appearance::UNKNOWN, @@ -99,10 +147,15 @@ pub async fn run( Ok(conn) => { // set up tasks when the connection is established to a central, so they don't run when no one is connected. let a = gatt_events_task(&server, &conn); - let b = custom_task(&server, &conn); - // run until any task ends (usually because the connection has been closed), - // then return to advertising state. + let b = publishing_task(&server, &conn); + + // run until any task ends (usually because the connection has been closed), then return to advertising state. embassy_futures::select::select(a, b).await; + + critical_section::with(|cs| { + let value_ref = CLIENT_CONNECTED.borrow(cs); + let _ = value_ref.replace(false); + }); } Err(e) => { panic!("BLE ADV: fatal error {e:?}"); @@ -113,6 +166,21 @@ pub async fn run( .await; } +pub fn update(payload: BleSendable) { + critical_section::with(|cs| { + // only add to queue when someone is connected + if *CLIENT_CONNECTED.borrow(cs).borrow() { + 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>( name: &'values str, @@ -202,63 +270,142 @@ async fn gatt_events_task( Ok(()) } -/// Example task to use the BLE notifier interface. -/// This task will notify the connected central of a counter value every 2 seconds. -/// It will also read the RSSI value every 2 seconds. -/// and will stop when the connection is closed by the central or an error occurs. -async fn custom_task(server: &Server<'_>, conn: &GattConnection<'_, '_, P>) { +#[allow(clippy::too_many_lines)] +async fn publishing_task(server: &Server<'_>, conn: &GattConnection<'_, '_, P>) { + critical_section::with(|cs| { + let value_ref = CLIENT_CONNECTED.borrow(cs); + let _ = value_ref.replace(true); + }); + loop { embassy_time::Timer::after(embassy_time::Duration::from_millis(10)).await; let mut new_data = None; critical_section::with(|cs| { - let tx_data_ref = TX_DATA.borrow(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(tx_data) = tx_data_ref.replace(None) { - new_data = Some(tx_data); + if let Some(data) = queue.pop() { + new_data = Some(data); } }); match new_data { #[cfg(feature = "cam")] - Some(BleSendable::Cam(cam_event)) => { - let mut buf = cam_event.serialize_ble_proto(); - - if buf.len() <= CAM_EVENT_SIZE { - // pad with zeros - let pad = alloc::vec![0u8; CAM_EVENT_SIZE - buf.len()]; - buf.extend_from_slice(&pad); - - // unwrap should be fine since we ensured the vector size before - let value = buf.try_into().unwrap(); - + Some(BleSendable::Cam(cam_event)) => match cam_event.serialize_ble_proto() { + Ok(value) => { if let Err(err) = server.c_its_events.cam_event.notify(conn, &value).await { warn!("BLE: failed notifying connection of new CAM event: {err:?}"); } - } else { - warn!("BLE: CAM event too big with {} bytes", buf.len()); } - } + Err(err) => warn!("BLE: CAM event {err}"), + }, + #[cfg(feature = "denm")] - Some(BleSendable::Denm(denm_event)) => { - let mut buf = denm_event.serialize_ble_proto(); - - if buf.len() <= DENM_EVENT_SIZE { - // pad with zeros - let pad = alloc::vec![0u8; DENM_EVENT_SIZE - buf.len()]; - buf.extend_from_slice(&pad); - - // unwrap should be fine since we ensured the vector size before - let value = buf.try_into().unwrap(); - + Some(BleSendable::Denm(denm_event)) => match denm_event.serialize_ble_proto() { + Ok(value) => { if let Err(err) = server.c_its_events.denm_event.notify(conn, &value).await { warn!("BLE: failed notifying connection of new DENM event: {err:?}"); } - } else { - warn!("BLE: DENM event too big with {} bytes", buf.len()); } - } + Err(err) => warn!("BLE: DENM event {err}"), + }, + + #[cfg(feature = "cam")] + Some(BleSendable::CamRaw(raw_msg)) => match raw_msg.serialize_ble_proto() { + Ok(value) => { + if let Err(err) = server.c_its_events.cam_raw.set(server, &value) { + warn!("BLE: failed setting new raw CAM value: {err:?}"); + } + + if let Err(err) = send_raw_notify( + server, + conn, + c_its_parser::standards::extensions::ItsMessageId::Cam, + ) + .await + { + warn!("BLE: {err}"); + } + } + Err(err) => warn!("BLE: Raw CAM too big: {err}"), + }, + #[cfg(feature = "denm")] + Some(BleSendable::DenmRaw(raw_msg)) => match raw_msg.serialize_ble_proto() { + Ok(value) => { + if let Err(err) = server.c_its_events.denm_raw.set(server, &value) { + warn!("BLE: failed setting new raw DENM value: {err:?}"); + } + + if let Err(err) = send_raw_notify( + server, + conn, + c_its_parser::standards::extensions::ItsMessageId::Denm, + ) + .await + { + warn!("BLE: {err}"); + } + } + Err(err) => warn!("BLE: Raw DENM {err}"), + }, + #[cfg(feature = "spat")] + Some(BleSendable::MapemRaw(raw_msg)) => match raw_msg.serialize_ble_proto() { + Ok(value) => { + if let Err(err) = server.c_its_events.mapem_raw.set(server, &value) { + warn!("BLE: failed setting new raw MAPEM value: {err:?}"); + } + + if let Err(err) = send_raw_notify( + server, + conn, + c_its_parser::standards::extensions::ItsMessageId::Mapem, + ) + .await + { + warn!("BLE: {err}"); + } + } + Err(err) => warn!("BLE: Raw MAPEM {err}"), + }, + #[cfg(feature = "spat")] + Some(BleSendable::SpatemRaw(raw_msg)) => match raw_msg.serialize_ble_proto() { + Ok(value) => { + if let Err(err) = server.c_its_events.spatem_raw.set(server, &value) { + warn!("BLE: failed setting new raw SPATEM value: {err:?}"); + } + + if let Err(err) = send_raw_notify( + server, + conn, + c_its_parser::standards::extensions::ItsMessageId::Spatem, + ) + .await + { + warn!("BLE: {err}"); + } + } + Err(err) => warn!("BLE: Raw SPATEM {err}"), + }, None => {} } } } + +async fn send_raw_notify( + server: &Server<'_>, + conn: &GattConnection<'_, '_, P>, + msg_id: c_its_parser::standards::extensions::ItsMessageId, +) -> Result<(), alloc::string::String> { + let notify_buf = io::msg::RawNotify::from(msg_id) + .serialize_ble_proto() + .expect("BLE: Raw notify too big"); + + server + .c_its_events + .raw_notify + .notify(conn, ¬ify_buf) + .await + .map_err(|err| alloc::format!("failed notifying connection of new raw {msg_id:?}: {err:?}")) +} diff --git a/src/io.rs b/src/io.rs index 3ef8c6d..642807f 100644 --- a/src/io.rs +++ b/src/io.rs @@ -40,20 +40,91 @@ impl msg::RawMsgRx { #[cfg(feature = "denm")] impl msg::DenmEvent { #[cfg(feature = "ble")] - pub fn serialize_ble_proto(&self) -> alloc::vec::Vec { + pub const PROTO_SIZE: usize = 51; + + #[cfg(feature = "ble")] + pub fn serialize_ble_proto(&self) -> Result<[u8; Self::PROTO_SIZE], alloc::string::String> { use prost::Message as _; - self.encode_to_vec() + let mut data = [0u8; Self::PROTO_SIZE]; + + let mut mut_buf = &mut data[..]; + if let Err(err) = self.encode(&mut mut_buf) { + Err(alloc::format!("Buffer too small: {err:?}")) + } else { + Ok(data) + } } } #[cfg(feature = "cam")] impl msg::CamEvent { #[cfg(feature = "ble")] - pub fn serialize_ble_proto(&self) -> alloc::vec::Vec { + pub const PROTO_SIZE: usize = 38; + + #[cfg(feature = "ble")] + pub fn serialize_ble_proto(&self) -> Result<[u8; Self::PROTO_SIZE], alloc::string::String> { use prost::Message as _; - self.encode_to_vec() + let mut data = [0u8; Self::PROTO_SIZE]; + + let mut mut_buf = &mut data[..]; + if let Err(err) = self.encode(&mut mut_buf) { + Err(alloc::format!("Buffer too small: {err:?}")) + } else { + Ok(data) + } + } +} + +#[cfg(any(feature = "cam", feature = "denm", feature = "spat"))] +impl msg::RawMsgRx { + #[cfg(feature = "ble")] + pub const PROTO_SIZE: usize = 1400; + + #[cfg(feature = "ble")] + pub fn serialize_ble_proto(&self) -> Result<[u8; Self::PROTO_SIZE], alloc::string::String> { + use prost::Message as _; + + let mut data = [0u8; Self::PROTO_SIZE]; + + let mut mut_buf = &mut data[..]; + if let Err(err) = self.encode(&mut mut_buf) { + Err(alloc::format!("Buffer too small: {err:?}")) + } else { + Ok(data) + } + } +} + +#[cfg(all( + feature = "ble", + any(feature = "cam", feature = "denm", feature = "spat") +))] +impl From for msg::RawNotify { + fn from(value: c_its_parser::standards::extensions::ItsMessageId) -> Self { + let message_type = value.as_u8().into(); + Self { message_type } + } +} + +#[cfg(any(feature = "cam", feature = "denm", feature = "spat"))] +impl msg::RawNotify { + #[cfg(feature = "ble")] + pub const PROTO_SIZE: usize = 2; + + #[cfg(feature = "ble")] + pub fn serialize_ble_proto(self) -> Result<[u8; Self::PROTO_SIZE], alloc::string::String> { + use prost::Message as _; + + let mut data = [0u8; Self::PROTO_SIZE]; + + let mut mut_buf = &mut data[..]; + if let Err(err) = self.encode(&mut mut_buf) { + Err(alloc::format!("Buffer too small: {err:?}")) + } else { + Ok(data) + } } } @@ -414,11 +485,11 @@ impl msg::RawMsgTx { } #[cfg(feature = "cam")] -impl From> +impl From<&alloc::boxed::Box> for msg::CamEvent { fn from( - value: alloc::boxed::Box, + value: &alloc::boxed::Box, ) -> Self { use c_its_parser::standards::cam_1_4_1::cam_pdu_descriptions; @@ -429,12 +500,14 @@ impl From None, }); - let vehicle_data = match value.cam.cam_parameters.high_frequency_container { + let vehicle_data = match &value.cam.cam_parameters.high_frequency_container { cam_pdu_descriptions::HighFrequencyContainer::basicVehicleContainerHighFrequency( basic_vehicle_container_high_frequency, ) => Some(basic_vehicle_container_high_frequency.into()), @@ -460,8 +533,8 @@ impl From for msg::VehicleContainer { - fn from(value: c_its_parser::standards::cam_1_4_1::cam_pdu_descriptions::BasicVehicleContainerHighFrequency) -> Self { +impl From<&c_its_parser::standards::cam_1_4_1::cam_pdu_descriptions::BasicVehicleContainerHighFrequency> for msg::VehicleContainer { + fn from(value: &c_its_parser::standards::cam_1_4_1::cam_pdu_descriptions::BasicVehicleContainerHighFrequency) -> Self { let heading_deg = if value.heading.heading_value.is_unavailable() { None } else { @@ -489,11 +562,11 @@ impl From> +impl From<&alloc::boxed::Box> for msg::DenmEvent { fn from( - value: alloc::boxed::Box, + value: &alloc::boxed::Box, ) -> Self { let denm_mgmt = &value.denm.management; @@ -512,7 +585,8 @@ impl From fo } } +#[cfg(any(feature = "cam", feature = "denm", feature = "spat"))] +impl TryFrom<&c_its_parser::ItsMessage<'_>> for msg::RawMsgRx { + type Error = alloc::string::String; + + fn try_from(value: &c_its_parser::ItsMessage<'_>) -> Result { + let msg_id = c_its_parser::standards::extensions::ItsMessageId::from(value); + + let payload = match value { + #[cfg(feature = "denm")] + c_its_parser::ItsMessage::DenmV1 { + geonetworking: _, + transport: _, + etsi, + } => etsi.encode_to_uper(), + #[cfg(feature = "denm")] + c_its_parser::ItsMessage::DenmV2 { + geonetworking: _, + transport: _, + etsi, + } => etsi.encode_to_uper(), + #[cfg(any(feature = "cam", feature = "cam_tx"))] + c_its_parser::ItsMessage::Cam { + geonetworking: _, + transport: _, + etsi, + } => etsi.encode_to_uper(), + #[cfg(feature = "spat")] + c_its_parser::ItsMessage::Spatem { + geonetworking: _, + transport: _, + etsi, + } => etsi.encode_to_uper(), + #[cfg(feature = "spat")] + c_its_parser::ItsMessage::Mapem { + geonetworking: _, + transport: _, + etsi, + } => etsi.encode_to_uper(), + }?; + + Ok(Self { + msg_type: msg_id.as_u8().into(), + payload, + }) + } +} + #[cfg(test)] mod tests { diff --git a/src/main.rs b/src/main.rs index d3adbbd..ae2a14e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -513,16 +513,36 @@ async fn main(spawner: Spawner) -> ! { ItsMessage::Mapem { geonetworking: _, transport: _, - etsi, - } => state.handle_mapem(&etsi), + ref etsi, + } => { + state.handle_mapem(etsi); + + // Send data to BLE thread + #[cfg(feature = "ble")] + match io::msg::RawMsgRx::try_from(&msg) { + Ok(data) => { + ble::update(ble::BleSendable::MapemRaw(data)); + } + Err(err) => warn!("Failed to create RawMsgRx: {err}"), + } + } #[cfg(feature = "spat")] ItsMessage::Spatem { geonetworking: _, transport: _, - etsi, + ref etsi, } => { if state.initialized() { - state.handle_spatem(&etsi); + state.handle_spatem(etsi); + } + + // Send data to BLE thread + #[cfg(feature = "ble")] + match io::msg::RawMsgRx::try_from(&msg) { + Ok(data) => { + ble::update(ble::BleSendable::SpatemRaw(data)); + } + Err(err) => warn!("Failed to create RawMsgRx: {err}"), } } @@ -530,33 +550,48 @@ async fn main(spawner: Spawner) -> ! { ItsMessage::Cam { geonetworking: _, transport: _, - etsi, + ref etsi, } => { #[cfg(feature = "cam")] - applogic::cam::handle_cam(&etsi); + applogic::cam::handle_cam(etsi); // Send data to BLE thread #[cfg(all(feature = "ble", feature = "cam"))] - critical_section::with(|cs| { - let ble_update_ref = ble::TX_DATA.borrow(cs); - ble_update_ref.replace(Some(ble::BleSendable::Cam(etsi.into()))); - }); + { + ble::update(ble::BleSendable::Cam(etsi.into())); + + // raw data only when it's a full CAM + if etsi.cam.cam_parameters.low_frequency_container.is_some() { + match io::msg::RawMsgRx::try_from(&msg) { + Ok(data) => { + ble::update(ble::BleSendable::CamRaw(data)); + } + Err(err) => warn!("Failed to create RawMsgRx: {err}"), + } + } + } } #[cfg(feature = "denm")] ItsMessage::DenmV2 { geonetworking: _, transport: _, - etsi, + ref etsi, } => { - applogic::denm::handle_denm(&etsi); + applogic::denm::handle_denm(etsi); // Send data to BLE thread #[cfg(feature = "ble")] - critical_section::with(|cs| { - let ble_update_ref = ble::TX_DATA.borrow(cs); - ble_update_ref.replace(Some(ble::BleSendable::Denm(etsi.into()))); - }); + { + ble::update(ble::BleSendable::Denm(etsi.into())); + + match io::msg::RawMsgRx::try_from(&msg) { + Ok(data) => { + ble::update(ble::BleSendable::DenmRaw(data)); + } + Err(err) => warn!("Failed to create RawMsgRx: {err}"), + } + } } #[cfg(feature = "denm")] ItsMessage::DenmV1 { From 3213667eead4e77c7cb3c72ac4b0b320b0424f2c Mon Sep 17 00:00:00 2001 From: Jannik Beyerstedt Date: Mon, 24 Aug 2026 15:16:36 +0200 Subject: [PATCH 27/27] BLE: Add service UUID to advertisement, remove descriptors to save space --- Readme.md | 3 +++ src/ble.rs | 21 +++++++++++++-------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/Readme.md b/Readme.md index 16453c6..97dfd2b 100644 --- a/Readme.md +++ b/Readme.md @@ -59,6 +59,9 @@ Enable UART I/O protocol according to [docs/uart-protocol.md](./docs/uart-protoc Enable BLE I/O protocol according to [docs/ble-protocol.md](./docs/ble-protocol.md). Enabling `denm` feature or `cam` feature is recommended, otherwise no data will be received. +Note: When changing the set of supported messages on the BLE interface, you may need to turn bluetooth off and on again on your client device. +Otherwise the client may not re-scan the service's characteristics and still use the previous set of characteristics. + ## Usage diff --git a/src/ble.rs b/src/ble.rs index 8fa5ead..bdb32be 100644 --- a/src/ble.rs +++ b/src/ble.rs @@ -56,7 +56,7 @@ struct CITSEvents { raw_notify: [u8; io::msg::RawNotify::PROTO_SIZE], #[cfg(feature = "cam")] - #[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, name = "CAM", read, value = "RawMsgRx proto msg", type = &'static str)] + // #[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, name = "CAM", read, value = "RawMsgRx proto msg", type = &'static str)] #[characteristic(uuid = "c0b70001-d4f4-4003-ada8-f99a02ee315c", read, value = [0u8; io::msg::RawMsgRx::PROTO_SIZE])] cam_raw: [u8; io::msg::RawMsgRx::PROTO_SIZE], #[cfg(feature = "cam")] @@ -65,7 +65,7 @@ struct CITSEvents { cam_event: [u8; io::msg::CamEvent::PROTO_SIZE], #[cfg(feature = "denm")] - #[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, name = "DENM", read, value = "RawMsgRx proto msg", type = &'static str)] + // #[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, name = "DENM", read, value = "RawMsgRx proto msg", type = &'static str)] #[characteristic(uuid = "c0b70001-d4f4-4005-ada8-f99a02ee315c", read, value = [0u8; io::msg::RawMsgRx::PROTO_SIZE])] denm_raw: [u8; io::msg::RawMsgRx::PROTO_SIZE], #[cfg(feature = "denm")] @@ -74,11 +74,11 @@ struct CITSEvents { denm_event: [u8; io::msg::DenmEvent::PROTO_SIZE], #[cfg(feature = "spat")] - #[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, name = "MAPEM", read, value = "RawMsgRx proto msg", type = &'static str)] + // #[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, name = "MAPEM", read, value = "RawMsgRx proto msg", type = &'static str)] #[characteristic(uuid = "c0b70001-d4f4-4007-ada8-f99a02ee315c", read, value = [0u8; io::msg::RawMsgRx::PROTO_SIZE])] mapem_raw: [u8; io::msg::RawMsgRx::PROTO_SIZE], #[cfg(feature = "spat")] - #[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, name = "SPATEM", read, value = "RawMsgRx proto msg", type = &'static str)] + // #[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, name = "SPATEM", read, value = "RawMsgRx proto msg", type = &'static str)] #[characteristic(uuid = "c0b70001-d4f4-4008-ada8-f99a02ee315c", read, value = [0u8; io::msg::RawMsgRx::PROTO_SIZE])] spatem_raw: [u8; io::msg::RawMsgRx::PROTO_SIZE], } @@ -143,8 +143,10 @@ pub async fn run( let _ = embassy_futures::join::join(ble_task(runner), async { loop { - match advertise("ESP32 C-ITS", &mut peripheral, &server).await { + match advertise(&mut peripheral, &server).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. let a = gatt_events_task(&server, &conn); let b = publishing_task(&server, &conn); @@ -158,7 +160,7 @@ pub async fn run( }); } Err(e) => { - panic!("BLE ADV: fatal error {e:?}"); + panic!("BLE ADV: fatal error: {e:?}"); } } } @@ -183,15 +185,18 @@ pub fn update(payload: BleSendable) { /// Create an advertiser to use to connect to a BLE Central, and wait for it to connect. async fn advertise<'values, 'server, C: Controller>( - name: &'values str, peripheral: &mut Peripheral<'values, C, DefaultPacketPool>, server: &'server Server<'values>, ) -> Result, BleHostError> { let mut advertiser_data = [0; 35]; + let service_uuid = Uuid::from(0xc0b7_0000_d4f4_4000_ada8_f99a_02ee_315c_u128) + .as_raw() + .try_into() + .unwrap(); let len = AdStructure::encode_slice( &[ AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED), - AdStructure::CompleteLocalName(name.as_bytes()), + AdStructure::ServiceUuids128(&[service_uuid]), ], &mut advertiser_data[..], )?;