├── rust-toolchain.toml ├── .gitignore ├── src ├── lib.rs └── ble │ └── mod.rs ├── README.md ├── .cargo └── config.toml ├── sdkconfig.defaults ├── Cargo.toml └── examples ├── sync.rs └── async.rs /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "esp" 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.vscode 2 | /.embuild 3 | /target 4 | /Cargo.lock 5 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(async_fn_in_trait)] 2 | 3 | pub mod ble; 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Using bleps BLE stack on ESP32-Rust-Std 2 | 3 | This is an example to use [bleps](https://github.com/bjoernQ/bleps) on ESP32 Rust-STD. 4 | 5 | There is one example for sync and one for async. 6 | 7 | All it does is connecting ESP32's VHCI to the stack. 8 | 9 | In this example the TWDT is disabled (for simplicity) and the Bluetooth is enabled in sdkconfig. 10 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "xtensa-esp32-espidf" 3 | 4 | [target.xtensa-esp32-espidf] 5 | linker = "ldproxy" 6 | # runner = "espflash --monitor" # Select this runner for espflash v1.x.x 7 | runner = "espflash flash --monitor" # Select this runner for espflash v2.x.x 8 | rustflags = [ "--cfg", "espidf_time64"] # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110 9 | 10 | [unstable] 11 | build-std = ["std", "panic_abort"] 12 | 13 | [env] 14 | # Note: these variables are not used when using pio builder (`cargo build --features pio`) 15 | ESP_IDF_VERSION = "v5.1" 16 | 17 | -------------------------------------------------------------------------------- /sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | # Rust often needs a bit of an extra main task stack size compared to C (the default is 3K) 2 | CONFIG_ESP_MAIN_TASK_STACK_SIZE=7000 3 | 4 | # Use this to set FreeRTOS kernel tick frequency to 1000 Hz (100 Hz by default). 5 | # This allows to use 1 ms granuality for thread sleeps (10 ms by default). 6 | #CONFIG_FREERTOS_HZ=1000 7 | 8 | # Workaround for https://github.com/espressif/esp-idf/issues/7631 9 | #CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n 10 | #CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=n 11 | 12 | # Override some defaults so BT stack is enabled 13 | # in this example 14 | 15 | # 16 | # BT config 17 | # 18 | CONFIG_BT_ENABLED=y 19 | CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y 20 | CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n 21 | CONFIG_BTDM_CTRL_MODE_BTDM=n 22 | CONFIG_BT_BLUEDROID_ENABLED=n 23 | CONFIG_BT_CONTROLLER_ONLY=y 24 | 25 | CONFIG_ESP_TASK_WDT_EN=n 26 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bleps-std" 3 | version = "0.1.0" 4 | authors = ["bjoernQ "] 5 | edition = "2021" 6 | resolver = "2" 7 | rust-version = "1.66" 8 | 9 | [profile.release] 10 | opt-level = "s" 11 | 12 | [profile.dev] 13 | debug = true # Symbols are nice and they don't increase the size on Flash 14 | opt-level = "z" 15 | 16 | [features] 17 | default = ["std", "hal", "esp-idf-sys/native"] 18 | 19 | pio = ["esp-idf-sys/pio"] 20 | all = ["std", "nightly", "experimental", "embassy"] 21 | hal = ["esp-idf-hal", "embedded-svc", "esp-idf-svc"] 22 | std = [ 23 | "alloc", 24 | "esp-idf-sys/std", 25 | "esp-idf-sys/binstart", 26 | "embedded-svc?/std", 27 | "esp-idf-hal?/std", 28 | "esp-idf-svc?/std", 29 | ] 30 | alloc = ["embedded-svc?/alloc", "esp-idf-hal?/alloc", "esp-idf-svc?/alloc"] 31 | nightly = [ 32 | "embedded-svc?/nightly", 33 | "esp-idf-svc?/nightly", 34 | ] # Future: "esp-idf-hal?/nightly" 35 | experimental = ["embedded-svc?/experimental", "esp-idf-svc?/experimental"] 36 | embassy = [ 37 | "esp-idf-hal?/embassy-sync", 38 | "esp-idf-hal?/critical-section", 39 | "esp-idf-hal?/edge-executor", 40 | "esp-idf-svc?/embassy-time-driver", 41 | "esp-idf-svc?/embassy-time-isr-queue", 42 | ] 43 | 44 | [dependencies] 45 | log = { version = "0.4.17", default-features = false } 46 | esp-idf-sys = { version = "0.33", default-features = false } 47 | esp-idf-hal = { version = "0.41", optional = true, default-features = false } 48 | esp-idf-svc = { version = "0.46", optional = true, default-features = false } 49 | embedded-svc = { version = "0.25", optional = true, default-features = false } 50 | embassy-executor = { version = "0.3.0", features = [ 51 | "arch-std", 52 | "executor-thread", 53 | "log", 54 | "nightly", 55 | ] } 56 | 57 | bleps = { git = "https://github.com/bjoernQ/bleps", package = "bleps", rev = "b82f1e7009bef7e32f0918be5b186188aa5e7109", features = [ 58 | "macros", 59 | "async", 60 | ] } 61 | embedded-io = "0.4.0" 62 | critical-section = { version = "1.1", features = ["std"] } 63 | heapless = "0.7.16" 64 | embassy-sync = { version = "0.2.0" } 65 | 66 | [build-dependencies] 67 | embuild = "0.31.2" 68 | -------------------------------------------------------------------------------- /examples/sync.rs: -------------------------------------------------------------------------------- 1 | use std::time::SystemTime; 2 | 3 | use bleps::{ 4 | ad_structure::{ 5 | create_advertising_data, AdStructure, BR_EDR_NOT_SUPPORTED, LE_GENERAL_DISCOVERABLE, 6 | }, 7 | attribute_server::{AttributeServer, WorkResult}, 8 | gatt, Ble, HciConnector, 9 | }; 10 | use esp_idf_sys as _; // If using the `binstart` feature of `esp-idf-sys`, always keep this module imported 11 | use log::*; 12 | 13 | use bleps_std::ble; 14 | use bleps_std::ble::BleConnector; 15 | 16 | #[allow(non_upper_case_globals)] 17 | fn main() { 18 | // It is necessary to call this function once. Otherwise some patches to the runtime 19 | // implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71 20 | esp_idf_sys::link_patches(); 21 | // Bind the log crate to the ESP Logging facilities 22 | esp_idf_svc::log::EspLogger::initialize_default(); 23 | 24 | info!("Going to advertise"); 25 | 26 | ble::ble_init(); 27 | 28 | loop { 29 | let connector = BleConnector::new(); 30 | let hci = HciConnector::new(connector, current_millis); 31 | let mut ble = Ble::new(&hci); 32 | 33 | println!("{:?}", ble.init()); 34 | println!("{:?}", ble.cmd_set_le_advertising_parameters()); 35 | println!( 36 | "{:?}", 37 | ble.cmd_set_le_advertising_data( 38 | create_advertising_data(&[ 39 | AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED), 40 | AdStructure::ServiceUuids16(&[Uuid::Uuid16(0x1809)]), 41 | AdStructure::CompleteLocalName("ESP32"), 42 | ]) 43 | .unwrap() 44 | ) 45 | ); 46 | println!("{:?}", ble.cmd_set_le_advertise_enable(true)); 47 | 48 | println!("started advertising"); 49 | 50 | let mut rf = |_offset: usize, data: &mut [u8]| { 51 | data[..20].copy_from_slice(&b"Hello Bare-Metal BLE"[..]); 52 | 17 53 | }; 54 | let mut wf = |offset: usize, data: &[u8]| { 55 | println!("RECEIVED: {} {:?}", offset, data); 56 | }; 57 | 58 | let mut wf2 = |offset: usize, data: &[u8]| { 59 | println!("RECEIVED: {} {:?}", offset, data); 60 | }; 61 | 62 | let mut rf3 = |_offset: usize, data: &mut [u8]| { 63 | data[..5].copy_from_slice(&b"Hola!"[..]); 64 | 5 65 | }; 66 | let mut wf3 = |offset: usize, data: &[u8]| { 67 | println!("RECEIVED: Offset {}, data {:?}", offset, data); 68 | }; 69 | 70 | gatt!([service { 71 | uuid: "937312e0-2354-11eb-9f10-fbc30a62cf38", 72 | characteristics: [ 73 | characteristic { 74 | uuid: "937312e0-2354-11eb-9f10-fbc30a62cf38", 75 | read: rf, 76 | write: wf, 77 | }, 78 | characteristic { 79 | uuid: "957312e0-2354-11eb-9f10-fbc30a62cf38", 80 | write: wf2, 81 | }, 82 | characteristic { 83 | name: "my_characteristic", 84 | uuid: "987312e0-2354-11eb-9f10-fbc30a62cf38", 85 | notify: true, 86 | read: rf3, 87 | write: wf3, 88 | }, 89 | ], 90 | },]); 91 | 92 | let mut srv = AttributeServer::new(&mut ble, &mut gatt_attributes); 93 | 94 | loop { 95 | let notification = None; 96 | match srv.do_work_with_notification(notification) { 97 | Ok(res) => { 98 | if let WorkResult::GotDisconnected = res { 99 | break; 100 | } 101 | } 102 | Err(err) => { 103 | println!("{:?}", err); 104 | } 105 | } 106 | } 107 | } 108 | } 109 | 110 | fn current_millis() -> u64 { 111 | let start = SystemTime::now(); 112 | let since_the_epoch = start 113 | .duration_since(std::time::UNIX_EPOCH) 114 | .expect("Time went backwards"); 115 | since_the_epoch.as_millis() as u64 116 | } 117 | -------------------------------------------------------------------------------- /examples/async.rs: -------------------------------------------------------------------------------- 1 | #![feature(async_closure)] 2 | #![feature(type_alias_impl_trait)] 3 | 4 | use std::{cell::RefCell, task::Poll, time::SystemTime}; 5 | 6 | use bleps::{ 7 | ad_structure::{ 8 | create_advertising_data, AdStructure, BR_EDR_NOT_SUPPORTED, LE_GENERAL_DISCOVERABLE, 9 | }, 10 | async_attribute_server::AttributeServer, 11 | asynch::Ble, 12 | attribute_server::NotificationData, 13 | gatt, 14 | }; 15 | use bleps_std::ble::asynch::BleConnector; 16 | use embassy_executor::Spawner; 17 | use esp_idf_sys as _; // If using the `binstart` feature of `esp-idf-sys`, always keep this module imported 18 | 19 | #[embassy_executor::main] 20 | async fn main(spawner: Spawner) { 21 | // It is necessary to call this function once. Otherwise some patches to the runtime 22 | // implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71 23 | esp_idf_sys::link_patches(); 24 | // Bind the log crate to the ESP Logging facilities 25 | esp_idf_svc::log::EspLogger::initialize_default(); 26 | 27 | spawner.spawn(run_ble()).ok(); 28 | } 29 | 30 | fn current_millis() -> u64 { 31 | let start = SystemTime::now(); 32 | let since_the_epoch = start 33 | .duration_since(std::time::UNIX_EPOCH) 34 | .expect("Time went backwards"); 35 | since_the_epoch.as_millis() as u64 36 | } 37 | 38 | #[embassy_executor::task] 39 | #[allow(non_upper_case_globals)] 40 | async fn run_ble() { 41 | bleps_std::ble::ble_init(); 42 | 43 | let connector = BleConnector::new(); 44 | let mut ble = Ble::new(connector, current_millis); 45 | 46 | println!("Connector created"); 47 | 48 | loop { 49 | println!("{:?}", ble.init().await); 50 | println!("{:?}", ble.cmd_set_le_advertising_parameters().await); 51 | println!( 52 | "{:?}", 53 | ble.cmd_set_le_advertising_data( 54 | create_advertising_data(&[ 55 | AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED), 56 | AdStructure::ServiceUuids16(&[Uuid::Uuid16(0x1809)]), 57 | AdStructure::CompleteLocalName("ESP32"), 58 | ]) 59 | .unwrap() 60 | ) 61 | .await 62 | ); 63 | println!("{:?}", ble.cmd_set_le_advertise_enable(true).await); 64 | 65 | println!("started advertising"); 66 | 67 | let mut rf = |_offset: usize, data: &mut [u8]| { 68 | data[..20].copy_from_slice(&b"Hello Bare-Metal BLE"[..]); 69 | 17 70 | }; 71 | let mut wf = |offset: usize, data: &[u8]| { 72 | println!("RECEIVED: {} {:?}", offset, data); 73 | }; 74 | 75 | let mut wf2 = |offset: usize, data: &[u8]| { 76 | println!("RECEIVED: {} {:?}", offset, data); 77 | }; 78 | 79 | let mut rf3 = |_offset: usize, data: &mut [u8]| { 80 | data[..5].copy_from_slice(&b"Hola!"[..]); 81 | 5 82 | }; 83 | let mut wf3 = |offset: usize, data: &[u8]| { 84 | println!("RECEIVED: Offset {}, data {:?}", offset, data); 85 | }; 86 | 87 | gatt!([service { 88 | uuid: "937312e0-2354-11eb-9f10-fbc30a62cf38", 89 | characteristics: [ 90 | characteristic { 91 | uuid: "937312e0-2354-11eb-9f10-fbc30a62cf38", 92 | read: rf, 93 | write: wf, 94 | }, 95 | characteristic { 96 | uuid: "957312e0-2354-11eb-9f10-fbc30a62cf38", 97 | write: wf2, 98 | }, 99 | characteristic { 100 | name: "my_characteristic", 101 | uuid: "987312e0-2354-11eb-9f10-fbc30a62cf38", 102 | notify: true, 103 | read: rf3, 104 | write: wf3, 105 | }, 106 | ], 107 | },]); 108 | 109 | let mut srv = AttributeServer::new(&mut ble, &mut gatt_attributes); 110 | 111 | let counter = RefCell::new(0u8); 112 | let mut notifier = async || { 113 | // we could wait here for an event to send a notification 114 | // ... we don't have anything in this example so make sure we are not busy-looping the BLE stack 115 | // getting new notifications 116 | AlwaysPendingFuture {}.await; 117 | 118 | let mut data = [0u8; 13]; 119 | data.copy_from_slice(b"Notification0"); 120 | { 121 | let mut counter = counter.borrow_mut(); 122 | data[data.len() - 1] += *counter; 123 | *counter = (*counter + 1) % 10; 124 | } 125 | NotificationData::new(my_characteristic_handle, &data) 126 | }; 127 | 128 | srv.run(&mut notifier).await.unwrap(); 129 | } 130 | } 131 | 132 | pub(crate) struct AlwaysPendingFuture; 133 | 134 | impl core::future::Future for AlwaysPendingFuture { 135 | type Output = (); 136 | 137 | fn poll( 138 | self: core::pin::Pin<&mut Self>, 139 | _cx: &mut core::task::Context<'_>, 140 | ) -> Poll { 141 | Poll::Pending 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/ble/mod.rs: -------------------------------------------------------------------------------- 1 | //! Bad code ahead ... just copied some bits and pieces from the bare-metal / no-std implementation 2 | //! Not really a good fit - should get a re-implementation most probably 3 | 4 | use std::{cell::RefCell, mem::MaybeUninit}; 5 | 6 | use critical_section::Mutex; 7 | use embedded_io::{ 8 | blocking::{Read, Write}, 9 | Error, Io, 10 | }; 11 | use heapless::spsc::Queue as SimpleQueue; 12 | 13 | static VHCI_HOST_CALLBACK: esp_idf_sys::esp_vhci_host_callback = 14 | esp_idf_sys::esp_vhci_host_callback { 15 | notify_host_send_available: Some(notify_host_send_available), 16 | notify_host_recv: Some(notify_host_recv), 17 | }; 18 | 19 | extern "C" fn notify_host_send_available() {} 20 | 21 | extern "C" fn notify_host_recv(data: *mut u8, len: u16) -> i32 { 22 | unsafe { 23 | let mut buf = [0u8; 256]; 24 | buf[..len as usize].copy_from_slice(&core::slice::from_raw_parts(data, len as usize)); 25 | 26 | let packet = ReceivedPacket { 27 | len: len as u8, 28 | data: buf, 29 | }; 30 | 31 | critical_section::with(|cs| { 32 | let mut queue = BT_RECEIVE_QUEUE.borrow_ref_mut(cs); 33 | if queue.enqueue(packet).is_err() {} 34 | }); 35 | 36 | asynch::hci_read_data_available(); 37 | } 38 | 39 | 0 40 | } 41 | 42 | pub fn ble_init() { 43 | let mut bt_cfg: esp_idf_sys::esp_bt_controller_config_t = 44 | esp_idf_sys::esp_bt_controller_config_t { 45 | controller_task_stack_size: esp_idf_sys::ESP_TASK_BT_CONTROLLER_STACK as u16, 46 | controller_task_prio: esp_idf_sys::ESP_TASK_BT_CONTROLLER_PRIO as u8, 47 | hci_uart_no: esp_idf_sys::BT_HCI_UART_NO_DEFAULT as u8, 48 | hci_uart_baudrate: esp_idf_sys::BT_HCI_UART_BAUDRATE_DEFAULT as u32, 49 | scan_duplicate_mode: esp_idf_sys::SCAN_DUPLICATE_MODE as u8, 50 | scan_duplicate_type: esp_idf_sys::SCAN_DUPLICATE_TYPE_VALUE as u8, 51 | normal_adv_size: esp_idf_sys::NORMAL_SCAN_DUPLICATE_CACHE_SIZE as u16, 52 | mesh_adv_size: esp_idf_sys::MESH_DUPLICATE_SCAN_CACHE_SIZE as u16, 53 | send_adv_reserved_size: esp_idf_sys::SCAN_SEND_ADV_RESERVED_SIZE as u16, 54 | controller_debug_flag: esp_idf_sys::CONTROLLER_ADV_LOST_DEBUG_BIT as u32, 55 | mode: 1, 56 | ble_max_conn: esp_idf_sys::CONFIG_BTDM_CTRL_BLE_MAX_CONN_EFF as u8, 57 | bt_max_acl_conn: esp_idf_sys::CONFIG_BTDM_CTRL_BR_EDR_MAX_ACL_CONN_EFF as u8, 58 | bt_sco_datapath: esp_idf_sys::CONFIG_BTDM_CTRL_BR_EDR_SCO_DATA_PATH_EFF as u8, 59 | auto_latency: esp_idf_sys::BTDM_CTRL_AUTO_LATENCY_EFF != 0, 60 | bt_legacy_auth_vs_evt: esp_idf_sys::BTDM_CTRL_LEGACY_AUTH_VENDOR_EVT_EFF != 0, 61 | bt_max_sync_conn: esp_idf_sys::CONFIG_BTDM_CTRL_BR_EDR_MAX_SYNC_CONN_EFF as u8, 62 | ble_sca: esp_idf_sys::CONFIG_BTDM_BLE_SLEEP_CLOCK_ACCURACY_INDEX_EFF as u8, 63 | pcm_role: esp_idf_sys::CONFIG_BTDM_CTRL_PCM_ROLE_EFF as u8, 64 | pcm_polar: esp_idf_sys::CONFIG_BTDM_CTRL_PCM_POLAR_EFF as u8, 65 | hli: esp_idf_sys::BTDM_CTRL_HLI != 0, 66 | dup_list_refresh_period: esp_idf_sys::SCAN_DUPL_CACHE_REFRESH_PERIOD as u16, 67 | magic: esp_idf_sys::ESP_BT_CONTROLLER_CONFIG_MAGIC_VAL as u32, 68 | }; 69 | 70 | unsafe { 71 | esp_idf_sys::esp!(esp_idf_sys::nvs_flash_init()).unwrap(); 72 | esp_idf_sys::esp!(esp_idf_sys::esp_bt_controller_init(&mut bt_cfg)).unwrap(); 73 | esp_idf_sys::esp!(esp_idf_sys::esp_bt_controller_enable(1)).unwrap(); 74 | } 75 | } 76 | 77 | #[non_exhaustive] 78 | pub struct BleConnector {} 79 | 80 | impl<'d> BleConnector { 81 | pub fn new() -> BleConnector { 82 | unsafe { 83 | esp_idf_sys::esp_vhci_host_register_callback( 84 | &VHCI_HOST_CALLBACK as *const esp_idf_sys::esp_vhci_host_callback, 85 | ); 86 | } 87 | Self {} 88 | } 89 | } 90 | 91 | #[derive(Debug)] 92 | pub enum BleConnectorError {} 93 | 94 | impl Error for BleConnectorError { 95 | fn kind(&self) -> embedded_io::ErrorKind { 96 | embedded_io::ErrorKind::Other 97 | } 98 | } 99 | 100 | impl Io for BleConnector { 101 | type Error = BleConnectorError; 102 | } 103 | 104 | impl Read for BleConnector { 105 | fn read(&mut self, buf: &mut [u8]) -> Result { 106 | let mut total = 0; 107 | for b in buf { 108 | let mut buffer = [0u8]; 109 | let len = read_hci(&mut buffer); 110 | 111 | if len == 1 { 112 | *b = buffer[0]; 113 | total += 1; 114 | } else { 115 | return Ok(total); 116 | } 117 | } 118 | 119 | Ok(total) 120 | } 121 | } 122 | 123 | impl Write for BleConnector { 124 | fn write(&mut self, buf: &[u8]) -> Result { 125 | for b in buf { 126 | send_hci(&[*b]); 127 | } 128 | Ok(buf.len()) 129 | } 130 | 131 | fn flush(&mut self) -> Result<(), Self::Error> { 132 | // nothing to do 133 | Ok(()) 134 | } 135 | } 136 | 137 | static BT_RECEIVE_QUEUE: Mutex>> = 138 | Mutex::new(RefCell::new(SimpleQueue::new())); 139 | 140 | static mut BLE_HCI_READ_DATA: [u8; 256] = [0u8; 256]; 141 | static mut BLE_HCI_READ_DATA_INDEX: usize = 0; 142 | static mut BLE_HCI_READ_DATA_LEN: usize = 0; 143 | 144 | static mut HCI_OUT_COLLECTOR: MaybeUninit = MaybeUninit::uninit(); 145 | 146 | #[derive(PartialEq, Debug)] 147 | enum HciOutType { 148 | Unknown, 149 | Acl, 150 | Command, 151 | } 152 | 153 | struct HciOutCollector { 154 | data: [u8; 256], 155 | index: usize, 156 | ready: bool, 157 | kind: HciOutType, 158 | } 159 | 160 | impl HciOutCollector { 161 | fn _new() -> HciOutCollector { 162 | HciOutCollector { 163 | data: [0u8; 256], 164 | index: 0, 165 | ready: false, 166 | kind: HciOutType::Unknown, 167 | } 168 | } 169 | 170 | fn is_ready(&self) -> bool { 171 | self.ready 172 | } 173 | 174 | fn push(&mut self, data: &[u8]) { 175 | self.data[self.index..(self.index + data.len())].copy_from_slice(data); 176 | self.index += data.len(); 177 | 178 | if self.kind == HciOutType::Unknown { 179 | self.kind = match self.data[0] { 180 | 1 => HciOutType::Command, 181 | 2 => HciOutType::Acl, 182 | _ => HciOutType::Unknown, 183 | }; 184 | } 185 | 186 | if !self.ready { 187 | if self.kind == HciOutType::Command && self.index >= 4 { 188 | if self.index == self.data[3] as usize + 4 { 189 | self.ready = true; 190 | } 191 | } else if self.kind == HciOutType::Acl && self.index >= 5 { 192 | if self.index == (self.data[3] as usize) + ((self.data[4] as usize) << 8) + 5 { 193 | self.ready = true; 194 | } 195 | } 196 | } 197 | } 198 | 199 | fn reset(&mut self) { 200 | self.index = 0; 201 | self.ready = false; 202 | self.kind = HciOutType::Unknown; 203 | } 204 | 205 | fn packet(&self) -> &[u8] { 206 | &self.data[0..(self.index as usize)] 207 | } 208 | } 209 | 210 | #[derive(Debug, Clone, Copy)] 211 | pub struct ReceivedPacket { 212 | pub len: u8, 213 | pub data: [u8; 256], 214 | } 215 | 216 | pub fn read_hci(data: &mut [u8]) -> usize { 217 | unsafe { 218 | if BLE_HCI_READ_DATA_LEN == 0 { 219 | critical_section::with(|cs| { 220 | let mut queue = BT_RECEIVE_QUEUE.borrow_ref_mut(cs); 221 | 222 | if let Some(packet) = queue.dequeue() { 223 | BLE_HCI_READ_DATA[..packet.len as usize] 224 | .copy_from_slice(&packet.data[..packet.len as usize]); 225 | BLE_HCI_READ_DATA_LEN = packet.len as usize; 226 | BLE_HCI_READ_DATA_INDEX = 0; 227 | } 228 | }); 229 | } 230 | 231 | if BLE_HCI_READ_DATA_LEN > 0 { 232 | data[0] = BLE_HCI_READ_DATA[BLE_HCI_READ_DATA_INDEX]; 233 | BLE_HCI_READ_DATA_INDEX += 1; 234 | 235 | if BLE_HCI_READ_DATA_INDEX >= BLE_HCI_READ_DATA_LEN { 236 | BLE_HCI_READ_DATA_LEN = 0; 237 | BLE_HCI_READ_DATA_INDEX = 0; 238 | } 239 | return 1; 240 | } 241 | } 242 | 243 | 0 244 | } 245 | 246 | pub fn send_hci(data: &[u8]) { 247 | let hci_out = unsafe { &mut *HCI_OUT_COLLECTOR.as_mut_ptr() }; 248 | hci_out.push(data); 249 | 250 | if hci_out.is_ready() { 251 | let packet = hci_out.packet(); 252 | 253 | unsafe { 254 | loop { 255 | let can_send = esp_idf_sys::esp_vhci_host_check_send_available(); 256 | 257 | if !can_send { 258 | continue; 259 | } 260 | 261 | esp_idf_sys::esp_vhci_host_send_packet( 262 | packet.as_ptr() as *mut u8, 263 | packet.len() as u16, 264 | ); 265 | 266 | break; 267 | } 268 | } 269 | 270 | hci_out.reset(); 271 | } 272 | } 273 | 274 | pub fn have_hci_read_data() -> bool { 275 | critical_section::with(|cs| { 276 | let queue = BT_RECEIVE_QUEUE.borrow_ref_mut(cs); 277 | !queue.is_empty() 278 | || unsafe { 279 | BLE_HCI_READ_DATA_LEN > 0 && (BLE_HCI_READ_DATA_LEN >= BLE_HCI_READ_DATA_INDEX) 280 | } 281 | }) 282 | } 283 | 284 | pub mod asynch { 285 | use core::task::Poll; 286 | 287 | use super::have_hci_read_data; 288 | use super::VHCI_HOST_CALLBACK; 289 | 290 | use super::BleConnectorError; 291 | use super::{read_hci, send_hci}; 292 | use embassy_sync::waitqueue::AtomicWaker; 293 | use embedded_io::asynch; 294 | use embedded_io::Io; 295 | 296 | static HCI_WAKER: AtomicWaker = AtomicWaker::new(); 297 | 298 | pub(crate) fn hci_read_data_available() { 299 | HCI_WAKER.wake(); 300 | } 301 | 302 | #[non_exhaustive] 303 | pub struct BleConnector {} 304 | 305 | impl<'d> BleConnector { 306 | pub fn new() -> BleConnector { 307 | unsafe { 308 | esp_idf_sys::esp_vhci_host_register_callback( 309 | &VHCI_HOST_CALLBACK as *const esp_idf_sys::esp_vhci_host_callback, 310 | ); 311 | } 312 | 313 | Self {} 314 | } 315 | } 316 | 317 | impl Io for BleConnector { 318 | type Error = BleConnectorError; 319 | } 320 | 321 | impl asynch::Read for BleConnector { 322 | async fn read(&mut self, buf: &mut [u8]) -> Result { 323 | if !have_hci_read_data() { 324 | HciReadyEventFuture.await; 325 | } 326 | 327 | let mut total = 0; 328 | for b in buf { 329 | let mut buffer = [0u8]; 330 | let len = read_hci(&mut buffer); 331 | 332 | if len == 1 { 333 | *b = buffer[0]; 334 | total += 1; 335 | } else { 336 | return Ok(total); 337 | } 338 | } 339 | 340 | Ok(total) 341 | } 342 | } 343 | 344 | impl asynch::Write for BleConnector { 345 | async fn write(&mut self, buf: &[u8]) -> Result { 346 | send_hci(buf); 347 | Ok(buf.len()) 348 | } 349 | 350 | async fn flush(&mut self) -> Result<(), BleConnectorError> { 351 | // nothing to do 352 | Ok(()) 353 | } 354 | } 355 | 356 | pub(crate) struct HciReadyEventFuture; 357 | 358 | impl core::future::Future for HciReadyEventFuture { 359 | type Output = (); 360 | 361 | fn poll( 362 | self: core::pin::Pin<&mut Self>, 363 | cx: &mut core::task::Context<'_>, 364 | ) -> Poll { 365 | HCI_WAKER.register(cx.waker()); 366 | 367 | if have_hci_read_data() { 368 | Poll::Ready(()) 369 | } else { 370 | Poll::Pending 371 | } 372 | } 373 | } 374 | } 375 | --------------------------------------------------------------------------------