commit 74f9786fa7029f1b31cac153273cb3cb43c27f53 from: Thomas Böhler date: Tue Dec 6 14:24:34 2022 UTC Initial commit commit - /dev/null commit + 74f9786fa7029f1b31cac153273cb3cb43c27f53 blob - /dev/null blob + 7faf2d657815c7338a693b5e5adb7e59363992d4 (mode 644) --- /dev/null +++ .cargo/config.toml @@ -0,0 +1,26 @@ +[build] +target = "riscv32imc-esp-espidf" + +[target.riscv32imc-esp-espidf] +linker = "ldproxy" + +# Future - necessary for the experimental "native build" of esp-idf-sys with ESP32C3 +# See also https://github.com/ivmarkov/embuild/issues/16 +rustflags = ["-C", "default-linker-libraries"] + +[unstable] +build-std = ["std", "panic_abort"] + +[env] +# Uncomment this and use the esp-idf-sys "native" build feature (`cargo build --features native`) to build against ESP-IDF 5.0 (master) +#ESP_IDF_VERSION = { value = "master" } + +# Uncomment this and use the esp-idf-sys "native" build feature (`cargo build --features native`) to build against ESP-IDF v4.4 +ESP_IDF_VERSION = { value = "v4.4.3" } + +# These configurations will pick up your custom "sdkconfig.release", "sdkconfig.debug" or "sdkconfig.defaults[.*]" files +# that you might put in the root of the project +# The easiest way to generate a full "sdkconfig" configuration (as opposed to manually enabling only the necessary flags via "sdkconfig.defaults[.*]" +# is by running "cargo pio espidf menuconfig" (that is, if using the pio builder) +ESP_IDF_SDKCONFIG = { value = "sdkconfig" } +ESP_IDF_SDKCONFIG_DEFAULTS = { value = "sdkconfig.defaults" } blob - /dev/null blob + d96c51339af170c30fd00e154a40176b38b00e91 (mode 644) --- /dev/null +++ .gitignore @@ -0,0 +1,4 @@ +/target +Cargo.lock +.embuild/ +.vscode/ \ No newline at end of file blob - /dev/null blob + e54ac7c0185a87e4f85c3c9693198e05b7023a0b (mode 644) --- /dev/null +++ Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "ble_esp32c3" +version = "0.1.0" +authors = ["Thomas Böhler "] +edition = "2021" + +[dependencies] +esp-idf-sys = { version = "0.31", features = ["binstart", "native"] } +esp-idf-svc = "0.42" +log = "0.4" +log-panics = "2.0" + +[build-dependencies] +embuild = "0.29" +anyhow = "1" + +[profile.release] +strip = true +lto = true + +[profile.dev] +debug = true # Symbols are nice and they don't increase the size on Flash +opt-level = "z" blob - /dev/null blob + 43e08d2580248b83cd9dde8b77c68e243d67d44c (mode 644) --- /dev/null +++ README.md @@ -0,0 +1,27 @@ +# ble-esp32c3 + +Primitive Rust crate for interfacing with Bluetooth Low Energy on the ESP32-C3. +it was developed as part of my +[bachelor-thesis](https://sr.ht/~witcher/bachelor-thesis) and doesn't allow a +lot of configuration. + +## Compiling + +Install the dependencies: + +- Rust (nightly, for RISCV on the ESP32-C3) + +```sh +cargo build +``` + +## Resources + +Send patches and questions to +[~witcher/bachelor-thesis@lists.sr.ht](https://lists.sr.ht/~witcher/bachelor-thesis). + +Instructions for preparing a patch are available at +[git-send-email.io](https://git-send-email.io/). + +Bugs and todo can be found at +[~witcher/bachelor-thesis](https://todo.sr.ht/~witcher/bachelor-thesis). blob - /dev/null blob + 4dd5e1f41a617a45cffc47a1f386b099ab8bec74 (mode 644) --- /dev/null +++ build.rs @@ -0,0 +1,5 @@ +// Necessary because of this issue: https://github.com/rust-lang/cargo/issues/9641 +fn main() -> anyhow::Result<()> { + embuild::build::CfgArgs::output_propagated("ESP_IDF")?; + embuild::build::LinkArgs::output_propagated("ESP_IDF") +} blob - /dev/null blob + f524b17c05428e88e04155bc26b138bdc0fa1785 (mode 644) --- /dev/null +++ examples/simple.rs @@ -0,0 +1,75 @@ +use ble_esp32c3::gatt::*; + +const SERVICE_A_UUID: u16 = 0x00FF; +const SERVICE_A_CHAR_1_UUID: u16 = 0xFF01; +const SERVICE_A_CHAR_2_UUID: u16 = 0xFF02; +const SERVICE_A_CHAR_1_DESC_1_UUID: u16 = 0xABCD as u16; + +fn setup(ble: &mut ble_esp32c3::Ble) { + let descriptor1 = Descriptor::new( + SERVICE_A_CHAR_1_DESC_1_UUID.into(), + 2, + Some(vec![12, 34]), + Some(Permissions::Read), + ); + + let mut characteristic1 = Characteristic::new( + SERVICE_A_CHAR_1_UUID.into(), + Permissions::ReadWrite, + 4, + Some(vec![0, 0, 0, 0]), + vec![ + descriptor1, + Descriptor::client_characteristic_configuration(), + ], + None, + ); + characteristic1.add_prop_read(); + characteristic1.add_prop_notify(); + + let mut characteristic2 = Characteristic::new( + SERVICE_A_CHAR_2_UUID.into(), + Permissions::ReadWrite, + 4, + Some(vec![0, 0, 0, 0]), + vec![], + None, + ); + characteristic2.add_prop_read(); + characteristic2.add_prop_write(); + + let service1 = Service::new( + SERVICE_A_UUID.into(), + vec![characteristic1, characteristic2], + ); + + ble.add_service(service1); +} + +fn main() { + esp_idf_sys::link_patches(); + + // Bind the log crate to the ESP Logging facilities + esp_idf_svc::log::EspLogger::initialize_default(); + + let mut ble = ble_esp32c3::Ble::default(); + setup(&mut ble); + log::error!("ble: {:?}", ble.init()); + + let char1_handle = + ble_esp32c3::Ble::find_attribute_by_uuid(SERVICE_A_CHAR_1_UUID.into()).unwrap(); + let mut frame_cnt = 0u32; + loop { + if let Some(rssi) = ble_esp32c3::Ble::rssi() { + log::info!("RSSI {}", rssi); + } else { + log::info!("Currently not connected."); + } + + frame_cnt = frame_cnt.wrapping_add(1); + char1_handle + .write_value(&mut frame_cnt.to_be_bytes()) + .unwrap(); + std::thread::sleep(std::time::Duration::from_millis(1000)); + } +} blob - /dev/null blob + 271800cb2f3791b3adc24328e71c9e2550b439db (mode 644) --- /dev/null +++ rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" \ No newline at end of file blob - /dev/null blob + dd77241baf67be4869a6ab45088c55b8142416e5 (mode 644) --- /dev/null +++ sdkconfig.defaults @@ -0,0 +1,7 @@ +CONFIG_ESP_MAIN_TASK_STACK_SIZE=7000 + +CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=4096 + +CONFIG_BT_ENABLED=y +CONFIG_BT_BLUEDROID_ENABLED=y +CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y blob - /dev/null blob + 0f7d620753ebbcb11fa104d2ffa32d19a5250b37 (mode 644) --- /dev/null +++ sdkconfig.release @@ -0,0 +1,6 @@ +CONFIG_LOG_DEFAULT_LEVEL_NONE=y +CONFIG_LOG_DEFAULT_LEVEL_ERROR=y +CONFIG_LOG_DEFAULT_LEVEL_WARN=n +CONFIG_LOG_DEFAULT_LEVEL_INFO=n +CONFIG_LOG_DEFAULT_LEVEL_DEBUG=n +CONFIG_LOG_DEFAULT_LEVEL_VERBOSE=n blob - /dev/null blob + 8f1a8bb4780212a62fe4f6561b3fcbcfde63b758 (mode 644) --- /dev/null +++ src/error.rs @@ -0,0 +1,25 @@ +use esp_idf_sys::esp_gatt_status_t; + +#[derive(Debug)] +pub enum Error { + GATT(esp_gatt_status_t), + + // TODO + ESP() +} + +impl Error { + pub fn get_as_u32(&self,) -> u32 { + match self { + Error::GATT(err_code) => *err_code, + Error::ESP() => 0, + } + } + + pub fn get_as_i32(&self,) -> i32 { + match self { + Error::GATT(err_code) => *err_code as i32, + Error::ESP() => 0, + } + } +} \ No newline at end of file blob - /dev/null blob + 331b6953336add9937314b99cecd96d917aa8753 (mode 644) --- /dev/null +++ src/gap.rs @@ -0,0 +1,121 @@ +use esp_idf_sys::*; + +use super::STATE; + +#[allow(non_snake_case)] +pub unsafe extern "C" fn ble_gap_event_handler( + event: esp_gap_ble_cb_event_t, + param: *mut esp_ble_gap_cb_param_t, +) { + log::trace!("GAP Event: {}", event); + + match event { + esp_gap_ble_cb_event_t_ESP_GAP_BLE_ADV_START_COMPLETE_EVT => { + let param = (*param).adv_data_cmpl; + if param.status == esp_bt_status_t_ESP_BT_STATUS_SUCCESS { + log::debug!("Advertising started successfully"); + } else { + log::error!("Advertising start failed"); + } + } + esp_gap_ble_cb_event_t_ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT => { + let param = (*param).adv_data_cmpl; + if param.status == esp_bt_status_t_ESP_BT_STATUS_SUCCESS { + log::debug!("Advertising stopped successfully"); + } else { + log::error!("Advertising stop failed"); + } + } + esp_gap_ble_cb_event_t_ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT => { + let param = (*param).adv_data_cmpl; + if param.status == esp_bt_status_t_ESP_BT_STATUS_SUCCESS { + super::gatt::start_advertising(); + } else { + log::error!("Advertising failed: {:x}", param.status); + } + } + esp_gap_ble_cb_event_t_ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT => { + let param = (*param).read_rssi_cmpl; + + if param.status == esp_bt_status_t_ESP_BT_STATUS_SUCCESS { + STATE.set_rssi(param.rssi); + } + } + esp_gap_ble_cb_event_t_ESP_GAP_BLE_SET_PKT_LENGTH_COMPLETE_EVT => { + let param = (*param).pkt_data_lenth_cmpl; + + if param.status == esp_bt_status_t_ESP_BT_STATUS_SUCCESS { + log::debug!( + "ESP_GAP_BLE_SET_PKT_LENGTH_COMPLETE_EVT: rx_len {}, tx_len {}", + param.params.rx_len, + param.params.tx_len + ); + } else { + log::error!("Setting packet length failed: {:x}", param.status); + } + } + esp_gap_ble_cb_event_t_ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT => { + let param = (*param).update_conn_params; + log::debug!( + "update connection params status = {}, min_int = {}, max_int = {}, conn_int + = {}, latency = {}, timeout = {}", + param.status, + param.min_int, + param.max_int, + param.conn_int, + param.latency, + param.timeout + ); + } + esp_gap_ble_cb_event_t_ESP_GAP_BLE_READ_PHY_COMPLETE_EVT => { + let param = (*param).read_phy; + + if param.status == esp_bt_status_t_ESP_BT_STATUS_SUCCESS { + log::debug!( + "ESP_GAP_BLE_READ_PHY_COMPLETE_EVT: bda = {:?}, tx_phy = {}, rx_phy = {}", + param.bda, + param.tx_phy, + param.rx_phy + ); + } else { + log::error!("PHY read not successful: {:x}", param.status); + } + } + esp_gap_ble_cb_event_t_ESP_GAP_BLE_SET_PREFERED_DEFAULT_PHY_COMPLETE_EVT => { + let param = (*param).set_perf_def_phy; + + if param.status == esp_bt_status_t_ESP_BT_STATUS_SUCCESS { + log::debug!("Setting preferred default PHY successful!"); + } else { + log::error!( + "Setting preferred default PHY not successful: {:x}", + param.status + ); + } + } + esp_gap_ble_cb_event_t_ESP_GAP_BLE_SET_PREFERED_PHY_COMPLETE_EVT => { + let param = (*param).set_perf_phy; + + if param.status == esp_bt_status_t_ESP_BT_STATUS_SUCCESS { + log::debug!("Setting preferred PHY successful!"); + } else { + log::error!("Setting preferred PHY not successful: {:x}", param.status); + } + } + esp_gap_ble_cb_event_t_ESP_GAP_BLE_PHY_UPDATE_COMPLETE_EVT => { + let param = (*param).phy_update; + + if param.status == esp_bt_status_t_ESP_BT_STATUS_SUCCESS { + log::debug!( + "ESP_GAP_BLE_PHY_UPDATE_COMPLETE_EVT: bda = {:?}, tx_phy = {}, rx_phy = {}", + param.bda, + param.tx_phy, + param.rx_phy + ); + } else { + log::error!("PHY update was not successful: {:x}", param.status); + } + } + _ => (), + }; +} blob - /dev/null blob + 373520950cd5211f914a08a0cba438b6833ad166 (mode 644) --- /dev/null +++ src/gatt/attribute.rs @@ -0,0 +1,71 @@ +use esp_idf_sys::{ + esp_ble_gatts_get_attr_value, esp_ble_gatts_set_attr_value, esp_gatt_perm_t, + esp_gatt_status_t_ESP_GATT_ILLEGAL_PARAMETER, esp_gatt_status_t_ESP_GATT_OK, + ESP_GATT_PERM_READ, ESP_GATT_PERM_WRITE, +}; + +use crate::{Error, Result}; + +pub trait IsAttribute { + fn handle(&self) -> u16; + fn value_max_length(&self) -> u16; + fn initial_value_length(&self) -> u16; + fn initial_value_pointer_mut(&mut self) -> *mut u8; + fn permissions(&self) -> esp_gatt_perm_t; + fn after_write_value(&self, new_value: &mut [u8]); + + fn can_read(&self) -> bool { + (self.permissions() & ESP_GATT_PERM_READ as u16) > 0 + } + + fn can_write(&self) -> bool { + (self.permissions() & ESP_GATT_PERM_WRITE as u16) > 0 + } + + fn write_value(&self, new_value: &mut [u8]) -> Result<()> { + let length = new_value.len() as u16; + let max_length = self.value_max_length(); + let handle = self.handle(); + + if length > max_length { + debug_assert!(length <= max_length); + // TODO: is there a more fitting error code? + return Err(Error::GATT(esp_gatt_status_t_ESP_GATT_ILLEGAL_PARAMETER)); + } + + unsafe { + let status_code = + esp_ble_gatts_set_attr_value(handle, length, new_value.as_ptr()) as u32; + if status_code != esp_gatt_status_t_ESP_GATT_OK { + debug_assert!(false, "Failed to update value of a attribute!"); + + Err(Error::GATT(status_code)) + } else { + self.after_write_value(new_value); + + Ok(()) + } + } + } + + fn read_value(&self) -> Result> { + let handle = self.handle(); + + unsafe { + let mut length: u16 = 0; + let mut data: *const u8 = std::ptr::null_mut(); + + let status_code = esp_ble_gatts_get_attr_value(handle, &mut length, &mut data); + if status_code != esp_gatt_status_t_ESP_GATT_OK { + debug_assert!(false, "Failed to read value of an attribute!"); + + Err(Error::GATT(status_code)) + } else { + let mut result = Vec::with_capacity(length.into()); + result.extend_from_slice(std::slice::from_raw_parts(data, length.into())); + + Ok(result) + } + } + } +} blob - /dev/null blob + 52e549e5a532985718d93bc008897aeabe4534cd (mode 644) --- /dev/null +++ src/gatt/char.rs @@ -0,0 +1,149 @@ +use esp_idf_sys::{ + esp_ble_gatts_send_indicate, esp_gatt_char_prop_t, esp_gatt_perm_t, + ESP_GATT_CHAR_PROP_BIT_INDICATE, ESP_GATT_CHAR_PROP_BIT_NOTIFY, ESP_GATT_CHAR_PROP_BIT_READ, + ESP_GATT_CHAR_PROP_BIT_WRITE, +}; + +use super::Descriptor; +use super::Permissions; +use super::UUID; + +const CHARACTERISTIC_VALUE_LENGTH_MAX: u16 = 0x40; + +pub struct Characteristic { + pub uuid: UUID, + pub props: esp_gatt_char_prop_t, + pub attr_perms: esp_gatt_perm_t, + pub declaration_handle: u16, + pub value_handle: u16, + pub descriptors: Vec, + pub max_length: u16, + initial_value: Vec, + pub subscribed_connections: Vec, + pub on_change_callback: Option)>, +} + +impl Characteristic { + pub fn new( + uuid: UUID, + perms: Permissions, + max_length: u16, + initial_value: Option>, + descriptors: Vec, + on_change_callback: Option)>, + ) -> Self { + // debug_assert!(!perms.can_read() || initial_value.is_some()); + + let mut value = Vec::with_capacity(max_length as usize); + if let Some(mut init) = initial_value { + debug_assert!(init.len() <= max_length as usize); + value.append(&mut init); + } + + Self { + uuid, + props: 0, + attr_perms: perms.get_permissions_u16(), + value_handle: 0, + declaration_handle: 0, + descriptors, + max_length, + initial_value: value, + subscribed_connections: vec![], + on_change_callback, + } + } + + pub fn add_prop_read(&mut self) { + self.props |= ESP_GATT_CHAR_PROP_BIT_READ as u8; + } + + pub fn remove_prop_read(&mut self) { + self.props &= !ESP_GATT_CHAR_PROP_BIT_READ as u8; + } + + pub fn has_prop_read(&self) -> bool { + (self.props & ESP_GATT_CHAR_PROP_BIT_READ as u8) > 0 + } + + pub fn add_prop_write(&mut self) { + self.props |= ESP_GATT_CHAR_PROP_BIT_WRITE as u8 + } + + pub fn remove_prop_write(&mut self) { + self.props &= !ESP_GATT_CHAR_PROP_BIT_WRITE as u8 + } + + pub fn has_prop_write(&self) -> bool { + (self.props & ESP_GATT_CHAR_PROP_BIT_WRITE as u8) > 0 + } + + pub fn add_prop_notify(&mut self) { + self.props |= ESP_GATT_CHAR_PROP_BIT_NOTIFY as u8 + } + + pub fn remove_prop_notify(&mut self) { + self.props &= !ESP_GATT_CHAR_PROP_BIT_NOTIFY as u8 + } + + pub fn has_prop_notify(&self) -> bool { + (self.props & ESP_GATT_CHAR_PROP_BIT_NOTIFY as u8) > 0 + } + + pub fn add_prop_indicate(&mut self) { + self.props |= ESP_GATT_CHAR_PROP_BIT_INDICATE as u8 + } + + pub fn remove_prop_indicate(&mut self) { + self.props &= !ESP_GATT_CHAR_PROP_BIT_INDICATE as u8 + } + + pub fn has_prop_indicate(&self) -> bool { + (self.props & ESP_GATT_CHAR_PROP_BIT_INDICATE as u8) > 0 + } + + pub fn add_descriptor(&mut self, desc: Descriptor) { + self.descriptors.push(desc); + } +} + +impl super::IsAttribute for Characteristic { + fn handle(&self) -> u16 { + self.value_handle + } + + fn initial_value_pointer_mut(&mut self) -> *mut u8 { + self.initial_value.as_mut_ptr() + } + + fn initial_value_length(&self) -> u16 { + self.initial_value.len() as u16 + } + + fn value_max_length(&self) -> u16 { + self.max_length + } + + fn permissions(&self) -> esp_gatt_perm_t { + self.attr_perms + } + + fn after_write_value(&self, new_value: &mut [u8]) { + if self.has_prop_notify() || self.has_prop_indicate() { + let need_confirm = !self.has_prop_notify(); + self.subscribed_connections + .iter() + .for_each(|conn_id| unsafe { + esp_ble_gatts_send_indicate( + crate::MAIN_PROFILE_GATTS_IF, + *conn_id, + self.handle(), + new_value.len() as u16, + new_value.as_mut_ptr(), + need_confirm, + ); + }); + // TODO: check if 'indicate' requires additional work to be done here + } + } +} blob - /dev/null blob + 51de7c633ec1f32b6094228d736b58effb33ce68 (mode 644) --- /dev/null +++ src/gatt/descriptor.rs @@ -0,0 +1,72 @@ +use esp_idf_sys::esp_gatt_perm_t; + +use super::Permissions; +use super::UUID; + +pub struct Descriptor { + pub uuid: UUID, + pub handle: u16, + pub max_length: u16, + initial_value: Vec, + pub perms: esp_gatt_perm_t, +} + +impl Descriptor { + pub fn new( + uuid: UUID, + max_length: u16, + initial_value: Option>, + permissions: Option, + ) -> Self { + let mut value = Vec::with_capacity(max_length as usize); + if let Some(mut init) = initial_value { + debug_assert!(init.len() <= max_length as usize); + value.append(&mut init); + } + Self { + uuid, + handle: 0, + max_length, + initial_value: value, + perms: permissions + .map(|p| p.get_permissions_u16()) + .unwrap_or_default(), + } + } + + pub fn client_characteristic_configuration() -> Self { + Descriptor { + uuid: super::uuid::CLIENT_CHARACTERISTIC_CONFIGURATION, + handle: 0, + max_length: 2, + initial_value: vec![0, 0], + perms: Permissions::ReadWrite.get_permissions_u16(), + } + } +} + +impl super::IsAttribute for Descriptor { + fn handle(&self) -> u16 { + self.handle + } + + fn initial_value_pointer_mut(&mut self) -> *mut u8 { + self.initial_value.as_mut_ptr() + } + + fn initial_value_length(&self) -> u16 { + self.initial_value.len() as u16 + } + + fn value_max_length(&self) -> u16 { + self.max_length + } + + fn permissions(&self) -> esp_gatt_perm_t { + self.perms + } + + fn after_write_value(&self, _: &mut [u8]) { + // do nothing + } +} blob - /dev/null blob + a5283b82f3b3c9f72deb4d6061bf50043fb0dcd0 (mode 644) --- /dev/null +++ src/gatt/event_handler.rs @@ -0,0 +1,273 @@ +use esp_idf_sys::*; + +use super::{enter_handles, start_advertising, IsAttribute, SERVICES}; +use crate::{Error, Result, MAIN_PROFILE_GATTS_IF, STATE, UPDATE_RUNNING}; + +static mut TEST_TABLE: Option<[esp_gatts_attr_db_t; 4]> = None; + +#[allow(clippy::missing_safety_doc)] +#[allow(non_snake_case)] +pub unsafe extern "C" fn ble_gatt_event_handler( + event: esp_gatts_cb_event_t, + gatts_if: esp_gatt_if_t, + param: *mut esp_ble_gatts_cb_param_t, +) { + match event { + esp_gatts_cb_event_t_ESP_GATTS_MTU_EVT => { + let param = (*param).mtu; + log::debug!( + "ESP_GATTS_MTU_EVT, MTU size {}, conn_id {}", + param.mtu, + param.conn_id + ); + } + esp_gatts_cb_event_t_ESP_GATTS_REG_EVT => { + let param = (*param).reg; + + if param.status == esp_bt_status_t_ESP_BT_STATUS_SUCCESS { + debug_assert!(crate::MAIN_PROFILE_GATTS_IF == ESP_GATT_IF_NONE as u8); + crate::MAIN_PROFILE_GATTS_IF = gatts_if; + + let attr_table = super::get_attribute_table(); + debug_assert!(attr_table.len() <= 255); + if !attr_table.is_empty() { + let res = esp_ble_gatts_create_attr_tab( + attr_table.as_ptr(), + gatts_if, + attr_table.len() as u8, + 0, + ); + debug_assert!(res == ESP_OK); + } + } + } + + esp_gatts_cb_event_t_ESP_GATTS_START_EVT => { + let param = (*param).start; + debug_assert!(param.status == esp_bt_status_t_ESP_BT_STATUS_SUCCESS); + } + + esp_gatts_cb_event_t_ESP_GATTS_READ_EVT => { + let param = (*param).read; + + if match handle_read(¶m) { + Err(error_code) => esp_ble_gatts_send_response( + gatts_if, + param.conn_id, + param.trans_id, + error_code.get_as_u32(), + std::ptr::null_mut(), + ), + Ok(value) => { + let mut val: [u8; 600] = [0; 600]; + + val[..value.len()].copy_from_slice(&value[..]); + + let rsp_value = esp_gatt_value_t { + handle: param.handle, + value: val, + len: value.len() as u16, + ..Default::default() + }; + let mut response = esp_gatt_rsp_t { + attr_value: rsp_value, + }; + + esp_ble_gatts_send_response( + gatts_if, + param.conn_id, + param.trans_id, + esp_gatt_status_t_ESP_GATT_OK, + &mut response, + ) + } + } != ESP_OK + { + debug_assert!(false, "Failed to respond."); + } + } + + esp_gatts_cb_event_t_ESP_GATTS_CREAT_ATTR_TAB_EVT => { + let param = (*param).add_attr_tab; + + if param.status != esp_gatt_status_t_ESP_GATT_OK { + panic!("failure: {}", param.status); + } + + enter_handles(std::slice::from_raw_parts( + param.handles, + param.num_handle as usize, + )); + SERVICES.iter().for_each(|service| { + let retval = esp_ble_gatts_start_service(service.handle); + debug_assert!(retval == ESP_OK) + }); + } + + esp_gatts_cb_event_t_ESP_GATTS_WRITE_EVT => { + let param = (*param).write; + + if !param.is_prep { + // this is a 'normal' write + debug_assert_eq!(gatts_if, MAIN_PROFILE_GATTS_IF); + let status = handle_write(¶m); + if param.need_rsp { + esp_ble_gatts_send_response( + gatts_if, + param.conn_id, + param.trans_id, + status, + std::ptr::null_mut(), + ); + } + } else { + // TODO: handle long-write ? + } + } + + esp_gatts_cb_event_t_ESP_GATTS_CONNECT_EVT => { + let param = (*param).connect; + + STATE.set_addr(param.remote_bda); + + if UPDATE_RUNNING.is_none() { + UPDATE_RUNNING = Some(std::thread::spawn(move || { + while let Some(addr) = STATE.addr() { + esp_ble_gap_read_rssi(addr); + std::thread::sleep(std::time::Duration::from_millis(50)); + } + })); + } + } + esp_gatts_cb_event_t_ESP_GATTS_DISCONNECT_EVT => { + let param = (*param).disconnect; + + STATE.set_disconnected(); + + SERVICES.iter_mut().for_each(|service| { + service.characteristics.iter_mut().for_each(|char| { + if let Some(index) = char + .subscribed_connections + .iter() + .enumerate() + .find_map(|(i, e)| if *e == param.conn_id { Some(i) } else { None }) + { + char.subscribed_connections.remove(index); + } + }) + }); + + // TODO: ensure that dropping the handle terminates the thread + UPDATE_RUNNING.take(); + + start_advertising(); + } + + _ => {} + } +} + +unsafe fn handle_read(param: &esp_ble_gatts_cb_param_t_gatts_read_evt_param) -> Result> { + for service in SERVICES.iter() { + if service.handle == param.handle { + // what to do? + } + for char in service.characteristics.iter() { + if char.handle() == param.handle { + if !char.has_prop_read() { + // it would make little sense to create a characteritic than the client can write but the attribute cant be written to + debug_assert!(char.can_read()); + + return Err(Error::GATT(esp_gatt_status_t_ESP_GATT_READ_NOT_PERMIT)); + } else { + return char.read_value(); + } + } + for desc in char.descriptors.iter() { + if desc.handle() == param.handle { + if !(char.has_prop_write() && desc.can_write()) { + return Err(Error::GATT(esp_gatt_status_t_ESP_GATT_READ_NOT_PERMIT)); + } else { + return desc.read_value(); + } + } + } + } + } + + // TODO: use esp_gatt_status_t_ESP_GATT_INVALID_HANDLE instead? + Err(Error::GATT(esp_gatt_status_t_ESP_GATT_NOT_FOUND)) +} + +unsafe fn handle_write( + param: &esp_ble_gatts_cb_param_t_gatts_write_evt_param, +) -> esp_gatt_status_t { + // TODO: use esp_gatt_status_t_ESP_GATT_INVALID_HANDLE instead? + let mut status = esp_gatt_status_t_ESP_GATT_NOT_FOUND; + for service in SERVICES.iter_mut() { + if service.handle == param.handle { + // what to do? + } + for char in service.characteristics.iter_mut() { + if char.handle() == param.handle { + if !char.has_prop_write() { + // it would make little sense to create a characteritic that the client can write but the attribute cant be written to + debug_assert!(char.can_write()); + + return esp_gatt_status_t_ESP_GATT_WRITE_NOT_PERMIT; + } else { + let was_written = char.write_value(std::slice::from_raw_parts_mut( + param.value, + param.len as usize, + )); + + if let Some(cb) = char.on_change_callback { + match char.read_value() { + Ok(new_value) => cb(new_value), + Err(error) => println!( + "Failed to read updated value of characteristic \"{}\": {}", + char.uuid, + error.get_as_u32() + ), + } + } + + status = match was_written { + Err(error) => error.get_as_u32(), + Ok(()) => esp_gatt_status_t_ESP_GATT_OK, + }; + + break; + } + } + for desc in char.descriptors.iter() { + if desc.handle() == param.handle { + if !desc.can_write() { + return esp_gatt_status_t_ESP_GATT_WRITE_NOT_PERMIT; + } else { + let was_written = desc.write_value(std::slice::from_raw_parts_mut( + param.value, + param.len as usize, + )); + + if was_written.is_ok() + && desc.uuid == super::uuid::CLIENT_CHARACTERISTIC_CONFIGURATION + && !char.subscribed_connections.contains(¶m.conn_id) + { + char.subscribed_connections.push(param.conn_id); + } + + status = match was_written { + Ok(()) => esp_gatt_status_t_ESP_GATT_OK, + Err(error) => error.get_as_u32(), + }; + + break; + } + } + } + } + } + + status +} blob - /dev/null blob + 08b4b8e9f0bb431b67603ac729bc1d7cc6f1ede5 (mode 644) --- /dev/null +++ src/gatt/mod.rs @@ -0,0 +1,133 @@ +mod attribute; +mod char; +mod descriptor; +mod event_handler; +mod permissions; +mod service; +mod uuid; + +pub use self::char::Characteristic; +use crate::gatt::uuid::{ATTRIBUTE_TABLE_CHARACTER_DECLARATION, ATTRIBUTE_TABLE_PRIMARY_SERVICE}; +pub use attribute::IsAttribute; +pub use descriptor::Descriptor; +pub use event_handler::ble_gatt_event_handler; +pub use permissions::Permissions; +pub use service::Service; +pub use uuid::UUID; + +use esp_idf_sys::*; + +const MAX_SERVICES: usize = 10; +pub static mut SERVICES: Vec = vec![]; + +pub fn start_advertising() { + let mut adv_params = esp_ble_adv_params_t { + adv_int_min: 0x0800, // 1.28 seconds, before: 0x20 + adv_int_max: 0x0800, // 1.28 seconds, before: 0x40 + adv_type: esp_ble_adv_type_t_ADV_TYPE_IND, + own_addr_type: esp_ble_addr_type_t_BLE_ADDR_TYPE_PUBLIC, + peer_addr: [0, 0, 0, 0, 0, 0], + peer_addr_type: esp_ble_addr_type_t_BLE_ADDR_TYPE_PUBLIC, + channel_map: esp_ble_adv_channel_t_ADV_CHNL_ALL, + adv_filter_policy: esp_ble_adv_filter_t_ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, + }; + unsafe { esp!(esp_ble_gap_start_advertising(&mut adv_params)) } + .expect("Could not start advertising: {err:?}"); +} + +unsafe fn enter_handles(handles: &[u16]) { + let mut it = 0; + for service in SERVICES.iter_mut() { + debug_assert!(it < handles.len()); + service.handle = handles[it]; + it += 1; + for char in service.characteristics.iter_mut() { + debug_assert!(it + 1 < handles.len()); + char.declaration_handle = handles[it]; + char.value_handle = handles[it + 1]; + it += 2; + for desc in char.descriptors.iter_mut() { + debug_assert!(it < handles.len()); + desc.handle = handles[it]; + it += 1; + } + } + } + + debug_assert_eq!(it, handles.len()); +} + +unsafe fn get_attribute_table() -> Vec { + get_attribute_table_for_services(&mut SERVICES) +} + +unsafe fn get_attribute_table_for_services(services: &mut [Service]) -> Vec { + let mut result = vec![]; + + for service in services { + result.push(esp_gatts_attr_db_t { + att_desc: esp_attr_desc_t { + uuid_length: 2, + uuid_p: std::ptr::addr_of_mut!(ATTRIBUTE_TABLE_PRIMARY_SERVICE) as *mut u8, + perm: ESP_GATT_PERM_READ as u16, + max_length: service.uuid.length(), + length: service.uuid.length(), + value: service.uuid.pointer_mut(), + }, + attr_control: esp_attr_control_t { + auto_rsp: ESP_GATT_AUTO_RSP as u8, + }, + }); + + for char in service.characteristics.iter_mut() { + // char declaration + result.push(esp_gatts_attr_db_t { + att_desc: esp_attr_desc_t { + uuid_length: 2, + uuid_p: std::ptr::addr_of_mut!(ATTRIBUTE_TABLE_CHARACTER_DECLARATION) + as *mut u8, + perm: ESP_GATT_PERM_READ as u16, + max_length: 1, + length: 1, + value: std::ptr::addr_of_mut!(char.props), + }, + attr_control: esp_attr_control_t { + auto_rsp: ESP_GATT_AUTO_RSP as u8, + }, + }); + + // char value + result.push(esp_gatts_attr_db_t { + att_desc: esp_attr_desc_t { + uuid_length: char.uuid.length(), + uuid_p: char.uuid.pointer_mut(), + perm: (ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE) as u16, + max_length: char.max_length, + length: char.initial_value_length(), + value: char.initial_value_pointer_mut(), + }, + attr_control: esp_attr_control_t { + auto_rsp: ESP_GATT_AUTO_RSP as u8, + }, + }); + + for desc in char.descriptors.iter_mut() { + result.push(esp_gatts_attr_db_t { + att_desc: esp_attr_desc_t { + uuid_length: desc.uuid.length(), + uuid_p: desc.uuid.pointer_mut(), + perm: (ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE) as u16, + max_length: desc.max_length, + length: desc.initial_value_length(), + value: desc.initial_value_pointer_mut(), + }, + attr_control: esp_attr_control_t { + auto_rsp: ESP_GATT_AUTO_RSP as u8, + }, + }); + } + } + } + + result +} blob - /dev/null blob + bfaf48df91fcb68959ec7e165f67fe7e3262dd92 (mode 644) --- /dev/null +++ src/gatt/permissions.rs @@ -0,0 +1,39 @@ +use esp_idf_sys::{ESP_GATT_PERM_READ, ESP_GATT_PERM_WRITE}; + +pub enum Permissions { + None, + Read, + Write, + ReadWrite, +} + +impl Default for Permissions { + fn default() -> Self { + Permissions::Read + } +} + +impl Permissions { + pub fn get_permissions_u16(&self) -> u16 { + match self { + Permissions::None => 0, + Permissions::Read => ESP_GATT_PERM_READ as u16, + Permissions::Write => ESP_GATT_PERM_WRITE as u16, + Permissions::ReadWrite => (ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE) as u16, + } + } + + pub fn can_read(&self) -> bool { + match self { + Permissions::Read | Permissions::ReadWrite => true, + _ => false, + } + } + + pub fn can_write(&self) -> bool { + match self { + Permissions::Write | Permissions::ReadWrite => true, + _ => false, + } + } +} blob - /dev/null blob + a47e5b3c649c50fa4ada1a376372ed8f50edbcf4 (mode 644) --- /dev/null +++ src/gatt/service.rs @@ -0,0 +1,67 @@ +use esp_idf_sys::{ + esp_ble_gatts_create_service, esp_ble_gatts_start_service, esp_gatt_id_t, esp_gatt_if_t, + esp_gatt_srvc_id_t, ESP_OK, +}; + +use super::char::Characteristic; +use super::uuid::UUID; + +pub struct Service { + pub id: Option, + pub uuid: UUID, + pub handle: u16, + pub characteristics: Vec, +} + +impl Service { + pub const fn new(uuid: UUID, characteristics: Vec) -> Self { + Self { + id: None, + uuid, + handle: 0, + characteristics, + } + } + + pub unsafe fn create(&mut self, gatts_if: esp_gatt_if_t) { + debug_assert!(self.id.is_none(), "The service was already created!"); + + let mut service_id = esp_gatt_srvc_id_t { + is_primary: true, + id: esp_gatt_id_t { + inst_id: 0x00, + uuid: self.uuid.get(), + }, + }; + + // Note: the service itself + all characteristics + all descriptors + let num_handles = 1 + + self.characteristics.len() + + self + .characteristics + .iter() + .map(|char| char.descriptors.len()) + .sum::(); + debug_assert!(num_handles < u16::MAX.into()); + + if esp_ble_gatts_create_service(gatts_if, &mut service_id, num_handles as u16) != ESP_OK { + panic!("Failed to start service \"{}\"!", self.uuid); + } + self.id = Some(service_id); + } + + pub unsafe fn start(&mut self) { + debug_assert!(self.handle != 0, "The service was not created yet!"); + if esp_ble_gatts_start_service(self.handle) != ESP_OK { + panic!("Failed to start service \"{}\"", self.uuid); + } + } + + pub fn add_characteristic(&mut self, char: Characteristic) { + debug_assert!( + self.handle == 0, + "Please add all characteristics before running .create()!" + ); + self.characteristics.push(char); + } +} blob - /dev/null blob + 3e0776d7df082d42829fc967df1f8e8c550007ac (mode 644) --- /dev/null +++ src/gatt/uuid.rs @@ -0,0 +1,149 @@ +use esp_idf_sys::{ + esp_bt_uuid_t, esp_bt_uuid_t__bindgen_ty_1, esp_gatt_srvc_id_t, ESP_GATT_UUID_CHAR_DECLARE, + ESP_GATT_UUID_PRI_SERVICE, ESP_UUID_LEN_128, ESP_UUID_LEN_16, ESP_UUID_LEN_32, +}; + +pub const CLIENT_CHARACTERISTIC_CONFIGURATION: UUID = UUID::Short(0x2902); + +pub static mut ATTRIBUTE_TABLE_PRIMARY_SERVICE: u16 = ESP_GATT_UUID_PRI_SERVICE as u16; +pub static mut ATTRIBUTE_TABLE_CHARACTER_DECLARATION: u16 = ESP_GATT_UUID_CHAR_DECLARE as u16; + +pub enum UUID { + Short(u16), + Medium(u32), + Long([u8; 16]), +} + +impl UUID { + pub fn get(&self) -> esp_bt_uuid_t { + match self { + UUID::Short(s) => uuid_from_u16(*s), + UUID::Medium(m) => uuid_from_u32(*m), + UUID::Long(l) => uuid_from_u8x16(l.clone()), + } + } + + pub fn length(&self) -> u16 { + match self { + UUID::Short(_) => 2, + UUID::Medium(_) => 4, + UUID::Long(_) => 16, + } + } + + pub fn pointer_mut(&mut self) -> *mut u8 { + match self { + UUID::Short(s) => std::ptr::addr_of_mut!(*s) as *mut u8, + UUID::Medium(m) => std::ptr::addr_of_mut!(*m) as *mut u8, + UUID::Long(l) => std::ptr::addr_of_mut!(*l) as *mut u8, + } + } + + pub fn to_le(&self) -> Vec { + match self { + UUID::Short(s) => s.to_le_bytes().to_vec(), + UUID::Medium(m) => m.to_le_bytes().to_vec(), + UUID::Long(l) => { + let mut l = l.clone(); + l.reverse(); + l.to_vec() + } + } + } +} + +impl PartialEq for UUID { + fn eq(&self, rhs: &esp_gatt_srvc_id_t) -> bool { + self.eq(&rhs.id.uuid) + } +} + +impl PartialEq for UUID { + fn eq(&self, rhs: &esp_bt_uuid_t) -> bool { + match self { + UUID::Short(lhs) => { + if rhs.len == ESP_UUID_LEN_16 as u16 { + return unsafe { rhs.uuid.uuid16 == *lhs }; + } + } + UUID::Medium(lhs) => { + if rhs.len == ESP_UUID_LEN_32 as u16 { + return unsafe { rhs.uuid.uuid32 == *lhs }; + } + } + UUID::Long(lhs) => { + if rhs.len == ESP_UUID_LEN_128 as u16 { + return unsafe { rhs.uuid.uuid128 == *lhs }; + } + } + } + + false + } +} + +impl PartialEq for UUID { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Short(lhs), Self::Short(rhs)) => lhs == rhs, + (Self::Medium(lhs), Self::Medium(rhs)) => lhs == rhs, + (Self::Long(lhs), Self::Long(rhs)) => lhs == rhs, + _ => false, + } + } +} + +impl std::fmt::Display for UUID { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + UUID::Short(s) => s.fmt(f), + UUID::Medium(m) => m.fmt(f), + UUID::Long(l) => { + for x in l { + f.write_fmt(format_args!("{}", x))?; + } + + Ok(()) + } + } + } +} + +impl From for UUID { + fn from(uuid: u16) -> Self { + UUID::Short(uuid) + } +} + +impl From for UUID { + fn from(uuid: u32) -> Self { + UUID::Medium(uuid) + } +} + +impl From<[u8; 16]> for UUID { + fn from(uuid: [u8; 16]) -> Self { + UUID::Long(uuid) + } +} + +const fn uuid_from_u16(uuid: u16) -> esp_bt_uuid_t { + esp_bt_uuid_t { + len: ESP_UUID_LEN_16 as u16, + uuid: esp_bt_uuid_t__bindgen_ty_1 { uuid16: uuid }, + } +} + +const fn uuid_from_u32(uuid: u32) -> esp_bt_uuid_t { + esp_bt_uuid_t { + len: ESP_UUID_LEN_32 as u16, + uuid: esp_bt_uuid_t__bindgen_ty_1 { uuid32: uuid }, + } +} + +const fn uuid_from_u8x16(uuid: [u8; 16]) -> esp_bt_uuid_t { + esp_bt_uuid_t { + len: ESP_UUID_LEN_128 as u16, + uuid: esp_bt_uuid_t__bindgen_ty_1 { uuid128: uuid }, + } +} blob - /dev/null blob + 6e4795bf46c193e94ee533ef22a6e56d5b1d7e6a (mode 644) --- /dev/null +++ src/lib.rs @@ -0,0 +1,286 @@ +#![allow(non_upper_case_globals)] +#![allow(dead_code)] + +mod error; +mod gap; +pub mod gatt; + +pub use error::Error; +use gap::ble_gap_event_handler; +use gatt::*; + +type Result = std::result::Result; + +use esp_idf_sys::*; +use std::ffi::CString; + +pub static mut STATE: BleState = BleState::Disconnected; +static mut UPDATE_RUNNING: Option> = None; + +const MAIN_PROFILE_ID: u16 = 0x12; +static mut MAIN_PROFILE_GATTS_IF: u8 = ESP_GATT_IF_NONE as u8; + +pub enum BleState { + Disconnected, + Connected(esp_bd_addr_t), + ConnectedRssi(esp_bd_addr_t, [i8; 8], usize), +} + +impl BleState { + fn set_disconnected(&mut self) { + *self = Self::Disconnected; + + start_advertising(); + } + + fn set_addr(&mut self, mut addr: esp_bd_addr_t) { + *self = Self::Connected(addr); + + // NOTE: updating to 2M PHY when connecting to prevent `LL_PHY_UPDATE_IND` possibly being + // sent by the master before `LL_FEATURE_RSP` arrived. if this is not prevented, the master + // will *not* upgrade the connection to 2M because it is not yet aware that the slave + // supports this mode. + if let Err(e) = esp!(unsafe { + esp_ble_gap_set_prefered_phy( + addr.as_mut_ptr(), + 0u8, + ESP_BLE_GAP_PHY_2M_PREF_MASK as u8, + ESP_BLE_GAP_PHY_2M_PREF_MASK as u8, + ESP_BLE_GAP_PHY_OPTIONS_NO_PREF as u16, + ) + }) { + log::error!("Unable to set preferred PHY for connection: {}", e); + } + + // TODO: tidy up + // set maximum LE data packet size to theoretical maximum + // BTM_BLE_DATA_SIZE_MAX = 0x00fb = 251 + log::debug!("Setting BLE DLE to 251"); + if let Err(e) = esp!(unsafe { esp_ble_gap_set_pkt_data_len(addr.as_mut_ptr(), 251) }) { + log::error!("Unable to set BLE DLE to 251: {}", e); + } + + //let mut params: esp_ble_conn_update_params_t = esp_ble_conn_update_params_t { + // bda: addr, + // latency: 0, + // min_int: 0x10, + // max_int: 0x20, + // timeout: 400, + //}; + //if let Err(e) = esp!(unsafe { esp_ble_gap_update_conn_params(&mut params) }) { + // log::error!("Unable to update connection parameters: {}", e); + //} + } + + fn set_rssi(&mut self, rssi: i8) { + match self { + Self::Disconnected => panic!("Not connected, can not set rssi!"), + Self::Connected(addr) => *self = Self::ConnectedRssi(*addr, [rssi; 8], 0), + Self::ConnectedRssi(_addr, r, count) => { + *count = count.wrapping_add(1); + r[*count & 0b111] = rssi; + } + }; + } + + fn addr(&mut self) -> Option<*mut u8> { + match self { + Self::Disconnected => None, + Self::Connected(addr) | Self::ConnectedRssi(addr, _, _) => Some(addr.as_mut_ptr()), + } + } + + fn rssi(&self) -> Option { + match self { + Self::ConnectedRssi(_, r, _) => { + Some((r.iter().map(|rssi| *rssi as i32).sum::() / 8) as i8) + } + _ => None, + } + } +} + +pub struct Ble { + config: esp_bt_controller_config_t, + adv_data: esp_ble_adv_data_t, + name: CString, + was_initialized: bool, +} + +impl Default for Ble { + fn default() -> Self { + let config = esp_bt_controller_config_t { + magic: ESP_BT_CTRL_CONFIG_MAGIC_VAL, + version: ESP_BT_CTRL_CONFIG_VERSION, + controller_task_stack_size: ESP_TASK_BT_CONTROLLER_STACK as u16, + controller_task_prio: ESP_TASK_BT_CONTROLLER_PRIO as u8, + controller_task_run_cpu: CONFIG_BT_CTRL_PINNED_TO_CORE as u8, + bluetooth_mode: CONFIG_BT_CTRL_MODE_EFF as u8, + ble_max_act: CONFIG_BT_CTRL_BLE_MAX_ACT_EFF as u8, + sleep_mode: CONFIG_BT_CTRL_SLEEP_MODE_EFF as u8, + sleep_clock: CONFIG_BT_CTRL_SLEEP_CLOCK_EFF as u8, + ble_st_acl_tx_buf_nb: CONFIG_BT_CTRL_BLE_STATIC_ACL_TX_BUF_NB as u8, + ble_hw_cca_check: CONFIG_BT_CTRL_HW_CCA_EFF as u8, + ble_adv_dup_filt_max: CONFIG_BT_CTRL_ADV_DUP_FILT_MAX as u16, + coex_param_en: false, + ce_len_type: CONFIG_BT_CTRL_CE_LENGTH_TYPE_EFF as u8, + coex_use_hooks: false, + hci_tl_type: CONFIG_BT_CTRL_HCI_TL_EFF as u8, + hci_tl_funcs: std::ptr::null_mut(), + txant_dft: CONFIG_BT_CTRL_TX_ANTENNA_INDEX_EFF as u8, + rxant_dft: CONFIG_BT_CTRL_RX_ANTENNA_INDEX_EFF as u8, + txpwr_dft: CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_EFF as u8, + cfg_mask: CFG_NASK, + scan_duplicate_mode: SCAN_DUPLICATE_MODE as u8, + scan_duplicate_type: SCAN_DUPLICATE_TYPE_VALUE as u8, + normal_adv_size: NORMAL_SCAN_DUPLICATE_CACHE_SIZE as u16, + mesh_adv_size: MESH_DUPLICATE_SCAN_CACHE_SIZE as u16, + coex_phy_coded_tx_rx_time_limit: CONFIG_BT_CTRL_COEX_PHY_CODED_TX_RX_TLIM_EFF as u8, + hw_target_code: BLE_HW_TARGET_CODE_ESP32C3_CHIP_ECO0, + slave_ce_len_min: SLAVE_CE_LEN_MIN_DEFAULT as u8, + hw_recorrect_en: AGC_RECORRECT_EN as u8, + cca_thresh: CONFIG_BT_CTRL_HW_CCA_VAL as u8, + }; + + let adv_data = esp_ble_adv_data_t { + set_scan_rsp: false, + include_name: true, + include_txpower: true, + min_interval: 0x0006, + max_interval: 0x0010, + appearance: 0x00, + manufacturer_len: 0, //TEST_MANUFACTURER_DATA_LEN, + p_manufacturer_data: std::ptr::null_mut(), //&test_manufacturer[0], + service_data_len: 0, + p_service_data: std::ptr::null_mut(), + service_uuid_len: 0, + p_service_uuid: std::ptr::null_mut(), + // service_uuid_len: 2, + // p_service_uuid: unsafe {std::ptr::addr_of_mut!(SERVICE_A_UUID) as *mut u8}, + flag: (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT) as u8, + }; + + Self { + config, + adv_data, + name: CString::new("").unwrap(), + was_initialized: false, + } + } +} + +impl Ble { + pub fn new_with_name(name: S) -> Self + where + S: AsRef, + { + Self { + name: CString::new(name.as_ref()).unwrap(), + ..Default::default() + } + } + + pub fn find_attribute_by_uuid(uuid: UUID) -> Option> { + unsafe { + for service in SERVICES.iter() { + if service.uuid == uuid { + return None; + } + for char in service.characteristics.iter() { + if char.uuid == uuid { + return Some(Box::new(char)); + } + for desc in char.descriptors.iter() { + if desc.uuid == uuid { + return Some(Box::new(desc)); + } + } + } + } + } + + None + } + + pub fn add_service(&mut self, service: Service) { + debug_assert!( + !self.was_initialized, + "You have to add all services before initializing!" + ); + + // TODO: UUIDs are not advertised + //let mut uuid = std::mem::ManuallyDrop::new(service.uuid.to_le()); + //self.adv_data.service_uuid_len = uuid.len() as _; + //self.adv_data.p_service_uuid = uuid.as_mut_ptr(); + + unsafe { + SERVICES.push(service); + } + } + + pub fn init(&mut self) -> std::result::Result<(), EspError> { + esp_try!(nvs_flash_init(), "Initializing nvs flash"); + + esp_try!( + esp_bt_controller_init(&mut self.config), + "Initializing BT controller" + ); + esp_try!( + esp_bt_controller_enable(esp_bt_mode_t_ESP_BT_MODE_BLE), + "Enabling BT controller" + ); + esp_try!(esp_bluedroid_init(), "Initializing Bluedroid"); + esp_try!(esp_bluedroid_enable(), "Enabling Bluedroid"); + + esp_try!( + esp_ble_gap_register_callback(Some(ble_gap_event_handler)), + "Registering BLE GAP event handler" + ); + + esp_try!( + esp_ble_gap_set_device_name(self.name.as_ptr()), + "Setting device name" + ); + + esp_try!( + esp_ble_gap_config_adv_data(&mut self.adv_data), + "Setting BLE GAP advertising data" + ); + + esp_try!( + esp_ble_gatts_register_callback(Some(ble_gatt_event_handler)), + "Registering GATT callback" + ); + + esp_try!( + esp_ble_gatts_app_register(MAIN_PROFILE_ID), + "Registering GATT server application" + ); + + log::debug!( + "Setting MTU size to ESP_GATT_MAX_MTU_SIZE ({})", + ESP_GATT_MAX_MTU_SIZE + ); + esp_try!( + esp_ble_gatt_set_local_mtu(ESP_GATT_MAX_MTU_SIZE as _), + "Setting max MTU size to ESP_GATT_MAX_MTU_SIZE" + ); + + Ok(()) + } + + pub fn rssi() -> Option { + unsafe { STATE.rssi() } + } +} + +#[macro_export] +macro_rules! esp_try { + ($cmd:expr, $str: tt) => { + // catch error, log, and rethrow + if let Err(e) = unsafe { esp!($cmd) } { + log::error!("Error in \"{}\": {}", $str, e); + Err(e)?; + } + }; +}