Enable V2X transmission, add optional stand-alone CAM transmission
This commit is contained in:
parent
1d4e82335d
commit
2379a7eefc
9 changed files with 543 additions and 14 deletions
22
Cargo.lock
generated
22
Cargo.lock
generated
|
|
@ -73,6 +73,12 @@ version = "1.0.102"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arbitrary-int"
|
||||||
|
version = "2.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "993a810118f8f37e9c4411c86f1c4c940a09a7ab34b7bf2d88d06f50c553fab7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayvec"
|
name = "arrayvec"
|
||||||
version = "0.7.6"
|
version = "0.7.6"
|
||||||
|
|
@ -186,9 +192,9 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "c-its-parser"
|
name = "c-its-parser"
|
||||||
version = "2.3.0"
|
version = "2.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f9bd739e1f76c017fc9fc6c21b9ff2aee4536bef357075d454e387e94a4d6287"
|
checksum = "cd4db66f495280ec88eef8712dd9ab7d1aacc590f62485e3e03a89b3ce4f8b14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"etherparse",
|
"etherparse",
|
||||||
|
|
@ -1239,9 +1245,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "etherparse"
|
name = "etherparse"
|
||||||
version = "0.20.1"
|
version = "0.21.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3ac016aaf11dfe643edcd088a166234bfcb72e7f06691abfcf21e9af524037f4"
|
checksum = "17304d06addb3283cdc4bd528e42dd95e73c8ee2d6492ffce415e93660885449"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
]
|
]
|
||||||
|
|
@ -1371,13 +1377,11 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "geonetworking"
|
name = "geonetworking"
|
||||||
version = "0.3.0"
|
version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6f6d36111f5057d3be7eb4e312a07edac4737fc791e4b0a73d812c6f710161e7"
|
checksum = "6da6fde770112587d5f5f7af439546e35bd35aa77a9e3e47e9968056783fe50d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitvec",
|
"arbitrary-int",
|
||||||
"bitvec-nom2",
|
|
||||||
"bytes",
|
|
||||||
"nom",
|
"nom",
|
||||||
"num",
|
"num",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,8 @@ std = ["c-its-parser/std"]
|
||||||
gnss = ["dep:embassy_gps"]
|
gnss = ["dep:embassy_gps"]
|
||||||
# echo CAM data on serial
|
# echo CAM data on serial
|
||||||
cam = ["c-its-parser/cam"]
|
cam = ["c-its-parser/cam"]
|
||||||
|
# send CAM autonomously
|
||||||
|
cam_tx = ["c-its-parser/cam", "gnss"]
|
||||||
# echo DENM events on serial and BLE
|
# echo DENM events on serial and BLE
|
||||||
denm = ["c-its-parser/denm"]
|
denm = ["c-its-parser/denm"]
|
||||||
# evaluate SPAT information
|
# 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" }
|
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-hal-bus = { version = "0.3.0", optional = true }
|
||||||
embedded-graphics = { version = "0.8.2", 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",
|
"time",
|
||||||
"geo",
|
"geo",
|
||||||
"libm",
|
"libm",
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,10 @@ Shall "publish" the most relevant information of a DENMs via BLE
|
||||||
### CAM
|
### CAM
|
||||||
Just prints a line for each received 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
|
## Usage
|
||||||
|
|
||||||
|
|
@ -60,9 +64,12 @@ Additional features are available through feature flags:
|
||||||
- `spat_debug`: Enable additional debug information for the SPAT/ GLOSA use case
|
- `spat_debug`: Enable additional debug information for the SPAT/ GLOSA use case
|
||||||
- `denm`: Enable DENMs
|
- `denm`: Enable DENMs
|
||||||
- `cam`: Enable CAMs
|
- `cam`: Enable CAMs
|
||||||
|
- `cam_tx`: Enable CAM generation and transmission
|
||||||
|
|
||||||
Officially supported feature combinations are:
|
Officially supported feature combinations are:
|
||||||
- `esp,spat,gnss,screen` (default features)
|
- `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
|
- To be implemented: No GNSS and screen when BLE/ UART interface is implemented
|
||||||
|
|
||||||
Connect your ESP32-C5 and run:
|
Connect your ESP32-C5 and run:
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,11 @@ extended=${2:-false}
|
||||||
features_base=(
|
features_base=(
|
||||||
#cam,denm,uart
|
#cam,denm,uart
|
||||||
spat,gnss,screen
|
spat,gnss,screen
|
||||||
|
spat,gnss,screen,cam_tx
|
||||||
)
|
)
|
||||||
features_more=(
|
features_more=(
|
||||||
spat,gnss,screen,cam,denm
|
spat,gnss,screen,cam,denm
|
||||||
|
spat,gnss,screen,cam_tx,cam,denm
|
||||||
)
|
)
|
||||||
features_test=(
|
features_test=(
|
||||||
spat
|
spat
|
||||||
|
|
|
||||||
197
src/applogic/cam_tx.rs
Normal file
197
src/applogic/cam_tx.rs
Normal file
|
|
@ -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<chrono::Utc>,
|
||||||
|
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::<chrono::Utc>::default(),
|
||||||
|
pos_state: super::PositionState::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, time: chrono::DateTime<chrono::Utc>, pos: super::PositionState) {
|
||||||
|
self.time = time;
|
||||||
|
self.pos_state = pos;
|
||||||
|
|
||||||
|
// TODO: build CAM trace
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
|
fn make_cam(&self) -> Result<cam_pdu_descriptions::CAM, alloc::string::String> {
|
||||||
|
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<chrono::Utc>,
|
||||||
|
_seq_no: &mut u16,
|
||||||
|
) -> (
|
||||||
|
geonetworking::en302636_4_1::ExtendedHeader,
|
||||||
|
Option<geonetworking::en302636_4_1::AreaType>,
|
||||||
|
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<u8>, 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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,6 +18,8 @@ pub mod v2x;
|
||||||
|
|
||||||
#[cfg(feature = "cam")]
|
#[cfg(feature = "cam")]
|
||||||
pub mod cam;
|
pub mod cam;
|
||||||
|
#[cfg(feature = "cam_tx")]
|
||||||
|
pub mod cam_tx;
|
||||||
#[cfg(feature = "denm")]
|
#[cfg(feature = "denm")]
|
||||||
pub mod denm;
|
pub mod denm;
|
||||||
#[cfg(feature = "spat")]
|
#[cfg(feature = "spat")]
|
||||||
|
|
|
||||||
68
src/main.rs
68
src/main.rs
|
|
@ -14,6 +14,8 @@ use core::cell::RefCell;
|
||||||
use core::cell::UnsafeCell;
|
use core::cell::UnsafeCell;
|
||||||
|
|
||||||
use c_its_parser::gn as geonetworking;
|
use c_its_parser::gn as geonetworking;
|
||||||
|
#[cfg(feature = "cam_tx")]
|
||||||
|
use c_its_parser::standards::extensions::ItsStationType;
|
||||||
use critical_section::Mutex;
|
use critical_section::Mutex;
|
||||||
use crossbeam_queue::ArrayQueue;
|
use crossbeam_queue::ArrayQueue;
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
|
|
@ -37,10 +39,20 @@ mod io;
|
||||||
mod radio;
|
mod radio;
|
||||||
#[cfg(feature = "screen")]
|
#[cfg(feature = "screen")]
|
||||||
mod screen;
|
mod screen;
|
||||||
|
#[cfg(feature = "cam_tx")]
|
||||||
|
mod v2x_tx;
|
||||||
|
|
||||||
const WIFI_CHANNEL: radio::Channel = 180;
|
const WIFI_CHANNEL: radio::Channel = 180;
|
||||||
#[cfg(feature = "spat")]
|
#[cfg(feature = "spat")]
|
||||||
const SPAT_RATE_LIMIT: u8 = 5; // keep every n-th message
|
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.
|
// This creates a default app-descriptor required by the esp-idf bootloader.
|
||||||
// For more information see: <https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/app_image_format.html#application-description>
|
// For more information see: <https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/app_image_format.html#application-description>
|
||||||
|
|
@ -119,7 +131,7 @@ async fn main(spawner: Spawner) -> ! {
|
||||||
|
|
||||||
// Setup WiFi
|
// Setup WiFi
|
||||||
// sadly, it doesn't work any more when we move `esp_radio::wifi::new` to `setup_wifi_sniffer`
|
// 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,
|
peripherals.WIFI,
|
||||||
esp_radio::wifi::ControllerConfig::default(),
|
esp_radio::wifi::ControllerConfig::default(),
|
||||||
)
|
)
|
||||||
|
|
@ -131,7 +143,8 @@ async fn main(spawner: Spawner) -> ! {
|
||||||
critical_section::with(|cs| {
|
critical_section::with(|cs| {
|
||||||
WIFI_RX_QUEUE.borrow(cs).replace(Some(ArrayQueue::new(2)));
|
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");
|
.expect("Fatal error initializing 802.11p sniffer");
|
||||||
|
|
||||||
info!("WiFi promiscuous mode on channel {WIFI_CHANNEL} running");
|
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...");
|
info!("GNSS task started, waiting for GNSS fix...");
|
||||||
|
|
||||||
let mut state = applogic::State::new();
|
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 {
|
loop {
|
||||||
embassy_time::Timer::after(embassy_time::Duration::from_millis(1)).await;
|
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) {
|
if let Some(fix) = gnss_update_ref.replace(None) {
|
||||||
state.update_with_gpsfix(&fix);
|
state.update_with_gpsfix(&fix);
|
||||||
new_position = true;
|
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 {
|
ItsMessage::Cam {
|
||||||
geonetworking: _,
|
geonetworking: _,
|
||||||
transport: _,
|
transport: _,
|
||||||
etsi,
|
etsi,
|
||||||
} => applogic::cam::handle_cam(&etsi),
|
} => {
|
||||||
|
#[cfg(feature = "cam")]
|
||||||
|
applogic::cam::handle_cam(&etsi);
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "denm")]
|
#[cfg(feature = "denm")]
|
||||||
ItsMessage::DenmV2 {
|
ItsMessage::DenmV2 {
|
||||||
|
|
|
||||||
94
src/v2x_tx/gn.rs
Normal file
94
src/v2x_tx/gn.rs
Normal file
|
|
@ -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<chrono::Utc>,
|
||||||
|
) -> 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<chrono::Utc>,
|
||||||
|
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<chrono::Utc>,
|
||||||
|
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<chrono::Utc>,
|
||||||
|
) -> 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")
|
||||||
|
}
|
||||||
160
src/v2x_tx/mod.rs
Normal file
160
src/v2x_tx/mod.rs
Normal file
|
|
@ -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<chrono::Utc>,
|
||||||
|
seq_no: &mut u16,
|
||||||
|
) -> (
|
||||||
|
geonetworking::en302636_4_1::ExtendedHeader,
|
||||||
|
Option<geonetworking::en302636_4_1::AreaType>,
|
||||||
|
u8,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Creates the ITS payload (without BTP header)
|
||||||
|
fn make_payload(
|
||||||
|
&self,
|
||||||
|
) -> Result<(Vec<u8>, 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<chrono::Utc>,
|
||||||
|
is_mobile: bool,
|
||||||
|
seq_no: &mut u16,
|
||||||
|
) -> Result<Vec<u8>, 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
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue