#![allow( clippy::needless_borrows_for_generic_args, reason = "gatt_service creates false positives" )] use core::cell::RefCell; use critical_section::Mutex; use crossbeam_queue::ArrayQueue; 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 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] struct Server { c_its_events: CITSEvents, } #[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-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-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 pub fn make_controller( bt_peripheral: esp_hal::peripherals::BT<'static>, ) -> bt_hci::controller::ExternalController< 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(), ) .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, >, name: alloc::string::String, ) { 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 for {}", crate::util::get_rx_feature_list().join(",") ); let server = Server::new_with_config(GapConfig::Peripheral(PeripheralConfig { name: &name, appearance: &appearance::UNKNOWN, })) .unwrap(); let _ = embassy_futures::join::join(ble_task(runner), async { loop { 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); // 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:?}"); } } } }) .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>( 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::ServiceUuids128(&[service_uuid]), ], &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(()) } #[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 queue_rc = TX_DATA.borrow(cs).borrow(); // unwrap is fine b/c we stored something in it before let queue = queue_rc.as_ref().unwrap(); if let Some(data) = queue.pop() { new_data = Some(data); } }); match new_data { #[cfg(feature = "cam")] 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:?}"); } } Err(err) => warn!("BLE: CAM event {err}"), }, #[cfg(feature = "denm")] 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:?}"); } } 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:?}")) }