Watch
0
0
Fork
You've already forked esp32-c_its-companion
0

WIP ble: Add device information service

TODO: Determine "git describe" size
This commit is contained in:
Jannik Beyerstedt 2026-09-29 16:38:52 +02:00
commit 2900f3a8af
4 changed files with 142 additions and 1 deletions

View file

@ -17,6 +17,21 @@ 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");
println!(
"cargo:rustc-env=GIT_REVISION={}",
String::from_utf8(
std::process::Command::new("git")
.args(&["describe", "--always"])
.output()
.unwrap()
.stdout,
)
.unwrap()
);
Ok(())
}

View file

@ -41,10 +41,55 @@ pub enum BleSendable {
SpatemRaw(io::msg::RawMsgRx),
}
pub const DIS_SERIAL_MAX_LEN: usize = 17;
pub const DIS_FW_REVISION_MAX_LEN: usize = 16; // TODO: what do we need?
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<u8> {
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,
battery_service: BatteryService,
device_info_service: DeviceInfoService,
}
#[gatt_service(uuid = service::BATTERY)]
@ -56,6 +101,25 @@ struct BatteryService {
level: u8,
}
#[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")]
struct CITSEvents {
#[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, name = "Position", read, value = "PositionState proto msg", type = &'static str)]
@ -123,6 +187,7 @@ pub async fn run(
>,
name: alloc::string::String,
short_name: alloc::string::String,
device_info: DeviceInfo,
) {
let mut resources: HostResources<DefaultPacketPool, CONNECTIONS_MAX, L2CAP_CHANNELS_MAX> =
HostResources::new();
@ -144,6 +209,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 {
// run advertisement and set data until a client connects

View file

@ -234,7 +234,15 @@ 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 = env!("GIT_REVISION");
let sw_revision = util::get_feature_list().join(",");
let device_info = ble::DeviceInfo::new(&mac.to_string(), &fw_revision, &sw_revision);
spawner.spawn(
ble::run(controller, name, short_name, device_info).expect("Failed to spawn BLE task"),
);
}
// Setup GNSS:

View file

@ -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
}