commit - /dev/null
commit + 74f9786fa7029f1b31cac153273cb3cb43c27f53
blob - /dev/null
blob + 7faf2d657815c7338a693b5e5adb7e59363992d4 (mode 644)
--- /dev/null
+++ .cargo/config.toml
+[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
+/target
+Cargo.lock
+.embuild/
+.vscode/
\ No newline at end of file
blob - /dev/null
blob + e54ac7c0185a87e4f85c3c9693198e05b7023a0b (mode 644)
--- /dev/null
+++ Cargo.toml
+[package]
+name = "ble_esp32c3"
+version = "0.1.0"
+authors = ["Thomas Böhler <witcher@wiredspace.de>"]
+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
+# 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
+// 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
+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
+[toolchain]
+channel = "nightly"
\ No newline at end of file
blob - /dev/null
blob + dd77241baf67be4869a6ab45088c55b8142416e5 (mode 644)
--- /dev/null
+++ sdkconfig.defaults
+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
+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
+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
+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
+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<Vec<u8>> {
+ 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
+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<Descriptor>,
+ pub max_length: u16,
+ initial_value: Vec<u8>,
+ pub subscribed_connections: Vec<u16>,
+ pub on_change_callback: Option<fn(Vec<u8>)>,
+}
+
+impl Characteristic {
+ pub fn new(
+ uuid: UUID,
+ perms: Permissions,
+ max_length: u16,
+ initial_value: Option<Vec<u8>>,
+ descriptors: Vec<Descriptor>,
+ on_change_callback: Option<fn(Vec<u8>)>,
+ ) -> 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
+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<u8>,
+ pub perms: esp_gatt_perm_t,
+}
+
+impl Descriptor {
+ pub fn new(
+ uuid: UUID,
+ max_length: u16,
+ initial_value: Option<Vec<u8>>,
+ permissions: Option<Permissions>,
+ ) -> 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
+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<Vec<u8>> {
+ 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
+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<Service> = 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<esp_gatts_attr_db_t> {
+ get_attribute_table_for_services(&mut SERVICES)
+}
+
+unsafe fn get_attribute_table_for_services(services: &mut [Service]) -> Vec<esp_gatts_attr_db_t> {
+ 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
+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
+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<esp_gatt_srvc_id_t>,
+ pub uuid: UUID,
+ pub handle: u16,
+ pub characteristics: Vec<Characteristic>,
+}
+
+impl Service {
+ pub const fn new(uuid: UUID, characteristics: Vec<Characteristic>) -> 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::<usize>();
+ 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
+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<u8> {
+ 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<esp_gatt_srvc_id_t> for UUID {
+ fn eq(&self, rhs: &esp_gatt_srvc_id_t) -> bool {
+ self.eq(&rhs.id.uuid)
+ }
+}
+
+impl PartialEq<esp_bt_uuid_t> 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<u16> for UUID {
+ fn from(uuid: u16) -> Self {
+ UUID::Short(uuid)
+ }
+}
+
+impl From<u32> 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
+#![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<T> = std::result::Result<T, Error>;
+
+use esp_idf_sys::*;
+use std::ffi::CString;
+
+pub static mut STATE: BleState = BleState::Disconnected;
+static mut UPDATE_RUNNING: Option<std::thread::JoinHandle<()>> = 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<i8> {
+ match self {
+ Self::ConnectedRssi(_, r, _) => {
+ Some((r.iter().map(|rssi| *rssi as i32).sum::<i32>() / 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<S>(name: S) -> Self
+ where
+ S: AsRef<str>,
+ {
+ Self {
+ name: CString::new(name.as_ref()).unwrap(),
+ ..Default::default()
+ }
+ }
+
+ pub fn find_attribute_by_uuid(uuid: UUID) -> Option<Box<&'static dyn IsAttribute>> {
+ 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<i8> {
+ 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)?;
+ }
+ };
+}