From dc54b55230fa2851b9caa2013c08a53b24152c26 Mon Sep 17 00:00:00 2001 From: Jannik Beyerstedt Date: Tue, 29 Sep 2026 16:38:52 +0200 Subject: [PATCH] ble: Add device information service --- build.rs | 22 +++++++++++++++ src/ble.rs | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 13 ++++++++- src/util.rs | 38 +++++++++++++++++++++++++ 4 files changed, 152 insertions(+), 1 deletion(-) diff --git a/build.rs b/build.rs index f376e0e..00deee1 100644 --- a/build.rs +++ b/build.rs @@ -9,6 +9,7 @@ fn main() -> std::io::Result<()> { println!("cargo:rustc-link-arg=-Tlinkall.x"); } + // compile protobuf definition to rust code let descriptor_path = std::path::PathBuf::from(env::var("OUT_DIR").unwrap()).join("proto_descriptor.bin"); @@ -17,6 +18,27 @@ fn main() -> std::io::Result<()> { .compile_well_known_types() .compile_protos(&["docs/c_its-messages.proto"], &["docs/"])?; + // get git revision + println!("cargo:rustc-rerun-if-changed=.git/HEAD"); + + // length: + // - tag name: 6-8 chars (v0.1.0 to v1.23.42) + // - offset+hash: 0-12 chars (up to 99 commits from tag + 8 char hash) + // - SUM: 6-20 + let git_output = std::process::Command::new("git") + .args(["describe", "--tags", "--match", "v*", "--always"]) + .output() + .unwrap(); + assert!( + git_output.stderr.is_empty(), + "{}", + String::from_utf8(git_output.stderr).unwrap() + ); + println!( + "cargo:rustc-env=GIT_REVISION={}", + String::from_utf8(git_output.stdout).unwrap() + ); + Ok(()) } diff --git a/src/ble.rs b/src/ble.rs index f72e972..8ff24c4 100644 --- a/src/ble.rs +++ b/src/ble.rs @@ -39,9 +39,73 @@ pub enum BleSendable { SpatemRaw(io::msg::RawMsgRx), } +pub const DIS_SERIAL_MAX_LEN: usize = 17; +pub const DIS_FW_REVISION_MAX_LEN: usize = 20; // git-describe +pub const DIS_SW_REVISION_MAX_LEN: usize = 66; // max length of get_feature_list, joined with comma + +pub struct DeviceInfo { + /// WiFi MAC + pub serial_number: [u8; DIS_SERIAL_MAX_LEN], + /// git tag/ revision + pub fw_revision: [u8; DIS_FW_REVISION_MAX_LEN], + /// used for feature flags + pub sw_revision: [u8; DIS_SW_REVISION_MAX_LEN], +} + +impl DeviceInfo { + pub fn new(serial_number: &str, fw_revision: &str, sw_revision: &str) -> Self { + Self { + serial_number: string_as_padded_buf(serial_number, DIS_SERIAL_MAX_LEN) + .try_into() + .unwrap(), + fw_revision: string_as_padded_buf(fw_revision, DIS_FW_REVISION_MAX_LEN) + .try_into() + .unwrap(), + sw_revision: string_as_padded_buf(sw_revision, DIS_SW_REVISION_MAX_LEN) + .try_into() + .unwrap(), + } + } +} + +pub fn string_as_padded_buf(value: &str, length: usize) -> alloc::vec::Vec { + let buf = value.as_bytes(); + let buf_len = buf.len(); + + // cut buffer at maximum length or pad with whitespace + if buf_len > length { + buf[..length].to_vec() + } else { + let padding_len = length - buf_len; + let padding = alloc::vec![b' '; padding_len]; + + [buf, &padding].concat() + } +} + #[gatt_server] struct Server { c_its_events: CITSEvents, + device_info_service: DeviceInfoService, +} + +#[gatt_service(uuid = service::DEVICE_INFORMATION)] +struct DeviceInfoService { + /// Manufacturer name + #[characteristic(uuid = characteristic::MANUFACTURER_NAME_STRING, read, value = "jtbx")] + mfg_name: &'static str, + /// Model number + #[characteristic(uuid = characteristic::MODEL_NUMBER_STRING, read, value = "ESP32-C5 C-ITS Companion")] + model_number: &'static str, + // Serial Number String + #[characteristic(uuid = characteristic::SERIAL_NUMBER_STRING, read, value = [0u8; DIS_SERIAL_MAX_LEN])] + serial_number: [u8; DIS_SERIAL_MAX_LEN], + // Firmware Revision String + #[characteristic(uuid = characteristic::FIRMWARE_REVISION_STRING, read, value = [0u8; DIS_FW_REVISION_MAX_LEN])] + fw_revision: [u8; DIS_FW_REVISION_MAX_LEN], + // Software Revision String + #[characteristic(uuid = characteristic::SOFTWARE_REVISION_STRING, read, value = [0u8; DIS_SW_REVISION_MAX_LEN])] + sw_revision: [u8; DIS_SW_REVISION_MAX_LEN], } #[gatt_service(uuid = "c0b70000-d4f4-4000-ada8-f99a02ee315c")] @@ -111,6 +175,7 @@ pub async fn run( >, name: alloc::string::String, short_name: alloc::string::String, + device_info: DeviceInfo, ) { let mut resources: HostResources = HostResources::new(); @@ -132,6 +197,21 @@ pub async fn run( })) .unwrap(); + // set "dynamic" device info data + let _ = server + .device_info_service + .serial_number + .set(&server, &device_info.serial_number); + let _ = server + .device_info_service + .fw_revision + .set(&server, &device_info.fw_revision); + let _ = server + .device_info_service + .sw_revision + .set(&server, &device_info.sw_revision); + + // advertise and run update logic let _ = embassy_futures::join::join(ble_task(runner), async { loop { match advertise(&mut peripheral, &server, &short_name).await { diff --git a/src/main.rs b/src/main.rs index 57a8963..d5956b7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -228,7 +228,18 @@ async fn main(spawner: Spawner) -> ! { serial[2] ); let short_name = alloc::format!("V2X {:02x}{:02x}", serial[1], serial[2]); // max 8 chars! - spawner.spawn(ble::run(controller, name, short_name).expect("Failed to spawn BLE task")); + + let fw_revision = core::env!("GIT_REVISION"); + let sw_revision = util::get_feature_list().join(","); + spawner.spawn( + ble::run( + controller, + name, + short_name, + ble::DeviceInfo::new(&mac.to_string(), fw_revision, &sw_revision), + ) + .expect("Failed to spawn BLE task"), + ); } // Setup GNSS: diff --git a/src/util.rs b/src/util.rs index 4b0dd89..e92a760 100644 --- a/src/util.rs +++ b/src/util.rs @@ -31,3 +31,41 @@ pub(crate) fn get_tx_feature_list() -> Vec<&'static str> { features } + +#[allow(unused)] +pub(crate) fn get_feature_list() -> Vec<&'static str> { + let mut features = alloc::vec![]; + + if cfg!(feature = "gnss_ubx") { + features.push("gnss_ubx"); + } + if cfg!(feature = "gnss_l67k") { + features.push("gnss_l67k"); + } + if cfg!(feature = "cam_tx") { + features.push("cam_tx"); + } + if cfg!(feature = "cam") { + features.push("cam"); + } + if cfg!(feature = "denm") { + features.push("denm"); + } + if cfg!(feature = "spat") { + features.push("spat"); + } + if cfg!(feature = "spat_debug") { + features.push("spat_debug"); + } + if cfg!(feature = "screen") { + features.push("screen"); + } + if cfg!(feature = "uart") { + features.push("uart"); + } + if cfg!(feature = "ble") { + features.push("ble"); + } + + features +}