From 93f10126630851d1d5bb9eeeec3ef69751b8349f Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Fri, 6 Sep 2024 15:08:25 +0200 Subject: [PATCH] Add integration with bt-hci crate (#1971) * Add integration with bt-hci crate Implementing traits from bt-hci allows the BleConnector to be used with the Trouble BLE stack. * use packed based read interface * Improve example to allow another connection after disconnect * update trouble version * Workaround for spurious command complete events * fix formatting * ignore notify errors in example * fix clippy warnings * remove async feature from hal dependency * remove deprecated feature from example * Adopt to api changes * Api fix for esp32 * Set rust-version of esp-wifi * bump MSRV to 1.77 for CI and esp-hal * Add changelog entry --- .github/workflows/ci.yml | 2 +- esp-hal/CHANGELOG.md | 2 + esp-hal/Cargo.toml | 2 +- esp-wifi/CHANGELOG.md | 1 + esp-wifi/Cargo.toml | 3 + esp-wifi/src/ble/controller/mod.rs | 67 ++++++++++ examples/Cargo.toml | 2 + examples/src/bin/wifi_embassy_trouble.rs | 157 +++++++++++++++++++++++ 8 files changed, 234 insertions(+), 2 deletions(-) create mode 100644 examples/src/bin/wifi_embassy_trouble.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cd388c03ff..683e4ceebf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ on: env: CARGO_TERM_COLOR: always GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - MSRV: "1.76.0" + MSRV: "1.77.0" RUSTDOCFLAGS: -Dwarnings # Cancel any currently running workflows from the same PR, branch, or diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index 08040080bf..9aba2306a2 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Bump MSRV to 1.77.0 (#1971) + ### Added - Implement `embedded-hal` output pin traits for `DummyPin` (#2019) diff --git a/esp-hal/Cargo.toml b/esp-hal/Cargo.toml index c935dfd484..ce9ba0ec8f 100644 --- a/esp-hal/Cargo.toml +++ b/esp-hal/Cargo.toml @@ -2,7 +2,7 @@ name = "esp-hal" version = "0.20.1" edition = "2021" -rust-version = "1.76.0" +rust-version = "1.77.0" description = "Bare-metal HAL for Espressif devices" documentation = "https://docs.esp-rs.org/esp-hal/" repository = "https://github.com/esp-rs/esp-hal" diff --git a/esp-wifi/CHANGELOG.md b/esp-wifi/CHANGELOG.md index 9db54114aa..8a1064b758 100644 --- a/esp-wifi/CHANGELOG.md +++ b/esp-wifi/CHANGELOG.md @@ -50,6 +50,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Implement `embedded_io::{ReadReady, WriteReady}` traits for `WifiStack` (#1882) - Implement `queue_msg_waiting` on the os_adapter (#1925) - Added API for promiscuous mode (#1935) +- Implement `bt_hci::transport::Transport` traits for BLE (#1933) ### Changed diff --git a/esp-wifi/Cargo.toml b/esp-wifi/Cargo.toml index 6165c12c62..e935fe2b4f 100644 --- a/esp-wifi/Cargo.toml +++ b/esp-wifi/Cargo.toml @@ -2,6 +2,7 @@ name = "esp-wifi" version = "0.9.1" edition = "2021" +rust-version = "1.77.0" authors = ["The ESP-RS team"] description = "A WiFi, Bluetooth and ESP-NOW driver for use with Espressif chips and bare-metal Rust" repository = "https://github.com/esp-rs/esp-hal" @@ -51,6 +52,7 @@ futures-util = { version = "0.3.30", default-features = false, features = [ atomic-waker = { version = "1.1.2", default-features = false, features = [ "portable-atomic", ] } +bt-hci = { version = "0.1.0", optional = true } [build-dependencies] toml-cfg = "0.2.0" @@ -103,6 +105,7 @@ async = [ "dep:embassy-futures", "dep:embedded-io-async", "dep:esp-hal-embassy", + "dep:bt-hci", ] embassy-net = ["dep:embassy-net-driver", "async"] diff --git a/esp-wifi/src/ble/controller/mod.rs b/esp-wifi/src/ble/controller/mod.rs index 0962330fe3..45eb991e31 100644 --- a/esp-wifi/src/ble/controller/mod.rs +++ b/esp-wifi/src/ble/controller/mod.rs @@ -83,6 +83,13 @@ impl Write for BleConnector<'_> { pub mod asynch { use core::task::Poll; + use bt_hci::{ + transport::{Transport, WithIndicator}, + ControllerToHostPacket, + FromHciBytes, + HostToControllerPacket, + WriteHci, + }; use embassy_sync::waitqueue::AtomicWaker; use embedded_io::ErrorType; @@ -177,4 +184,64 @@ pub mod asynch { } } } + + fn parse_hci(data: &[u8]) -> Result>, BleConnectorError> { + match ControllerToHostPacket::from_hci_bytes_complete(data) { + Ok(p) => Ok(Some(p)), + Err(e) => { + if e == bt_hci::FromHciBytesError::InvalidSize { + use bt_hci::{event::EventPacketHeader, PacketKind}; + + // Some controllers emit a suprious command complete event at startup. + let (kind, data) = + PacketKind::from_hci_bytes(data).map_err(|_| BleConnectorError::Unknown)?; + if kind == PacketKind::Event { + let (header, _) = EventPacketHeader::from_hci_bytes(data) + .map_err(|_| BleConnectorError::Unknown)?; + const COMMAND_COMPLETE: u8 = 0x0E; + if header.code == COMMAND_COMPLETE && header.params_len < 4 { + return Ok(None); + } + } + } + warn!("[hci] error parsing packet: {:?}", e); + Err(BleConnectorError::Unknown) + } + } + } + + impl<'d> Transport for BleConnector<'d> { + /// Read a complete HCI packet into the rx buffer + async fn read<'a>( + &self, + rx: &'a mut [u8], + ) -> Result, Self::Error> { + loop { + if !have_hci_read_data() { + HciReadyEventFuture.await; + } + + // Workaround for borrow checker. + // Safety: we only return a reference to x once, if parsing is successful. + let rx = + unsafe { &mut *core::ptr::slice_from_raw_parts_mut(rx.as_mut_ptr(), rx.len()) }; + + let len = crate::ble::read_next(rx); + if let Some(packet) = parse_hci(&rx[..len])? { + return Ok(packet); + } + } + } + + /// Write a complete HCI packet from the tx buffer + async fn write(&self, val: &T) -> Result<(), Self::Error> { + let mut buf: [u8; 259] = [0; 259]; + let w = WithIndicator::new(val); + let len = w.size(); + w.write_hci(&mut buf[..]) + .map_err(|_| BleConnectorError::Unknown)?; + send_hci(&buf[..len]); + Ok(()) + } + } } diff --git a/examples/Cargo.toml b/examples/Cargo.toml index b2009458a5..eeb9e384ac 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -9,6 +9,7 @@ publish = false aes = "0.8.4" aligned = { version = "0.4.2", optional = true } bleps = { git = "https://github.com/bjoernQ/bleps", package = "bleps", rev = "a5148d8ae679e021b78f53fd33afb8bb35d0b62e", features = [ "macros", "async"] } +bt-hci = "0.1.0" cfg-if = "1.0.0" critical-section = "1.1.2" crypto-bigint = { version = "0.5.5", default-features = false } @@ -55,6 +56,7 @@ smart-leds = "0.4.0" smoltcp = { version = "0.11.0", default-features = false, features = [ "medium-ethernet", "socket-raw"] } ssd1306 = "0.8.4" static_cell = { version = "2.1.0", features = ["nightly"] } +trouble-host = { git = "https://github.com/embassy-rs/trouble", package = "trouble-host", rev = "4f1114ce58e96fe54f5ed7e726f66e1ad8d9ce54", features = [ "log", "gatt" ] } usb-device = "0.3.2" usbd-serial = "0.2.2" diff --git a/examples/src/bin/wifi_embassy_trouble.rs b/examples/src/bin/wifi_embassy_trouble.rs new file mode 100644 index 0000000000..ce71add3f7 --- /dev/null +++ b/examples/src/bin/wifi_embassy_trouble.rs @@ -0,0 +1,157 @@ +//! Embassy BLE Example using Trouble +//! +//! - starts Bluetooth advertising +//! - offers a GATT service providing a battery percentage reading +//! - automatically notifies subscribers every second +//! + +//% FEATURES: embassy embassy-generic-timers esp-wifi esp-wifi/async esp-wifi/ble +//% CHIPS: esp32 esp32s3 esp32c2 esp32c3 esp32c6 esp32h2 + +#![no_std] +#![no_main] + +use bt_hci::controller::ExternalController; +use embassy_executor::Spawner; +use embassy_futures::join::join3; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_time::{Duration, Timer}; +use esp_backtrace as _; +use esp_hal::{prelude::*, timer::timg::TimerGroup}; +use esp_wifi::ble::controller::asynch::BleConnector; +use log::*; +use static_cell::StaticCell; +use trouble_host::{ + advertise::{AdStructure, Advertisement, BR_EDR_NOT_SUPPORTED, LE_GENERAL_DISCOVERABLE}, + attribute::{AttributeTable, CharacteristicProp, Service, Uuid}, + Address, + BleHost, + BleHostResources, + PacketQos, +}; + +#[esp_hal_embassy::main] +async fn main(_s: Spawner) { + esp_println::logger::init_logger_from_env(); + let peripherals = esp_hal::init({ + let mut config = esp_hal::Config::default(); + config.cpu_clock = CpuClock::max(); + config + }); + let timg0 = TimerGroup::new(peripherals.TIMG0); + + let init = esp_wifi::initialize( + esp_wifi::EspWifiInitFor::Ble, + timg0.timer0, + esp_hal::rng::Rng::new(peripherals.RNG), + peripherals.RADIO_CLK, + ) + .unwrap(); + + #[cfg(feature = "esp32")] + { + let timg1 = TimerGroup::new(peripherals.TIMG1); + esp_hal_embassy::init(timg1.timer0); + } + + #[cfg(not(feature = "esp32"))] + { + let systimer = esp_hal::timer::systimer::SystemTimer::new(peripherals.SYSTIMER) + .split::(); + esp_hal_embassy::init(systimer.alarm0); + } + + let mut bluetooth = peripherals.BT; + let connector = BleConnector::new(&init, &mut bluetooth); + let controller: ExternalController<_, 20> = ExternalController::new(connector); + + static HOST_RESOURCES: StaticCell> = StaticCell::new(); + let host_resources = HOST_RESOURCES.init(BleHostResources::new(PacketQos::None)); + + let mut ble: BleHost<'_, _> = BleHost::new(controller, host_resources); + + ble.set_random_address(Address::random([0xff, 0x9f, 0x1a, 0x05, 0xe4, 0xff])); + let mut table: AttributeTable<'_, NoopRawMutex, 10> = AttributeTable::new(); + + let id = b"Trouble ESP32"; + let appearance = [0x80, 0x07]; + let mut bat_level = [0; 1]; + // Generic Access Service (mandatory) + let mut svc = table.add_service(Service::new(0x1800)); + let _ = svc.add_characteristic_ro(0x2a00, id); + let _ = svc.add_characteristic_ro(0x2a01, &appearance[..]); + svc.build(); + + // Generic attribute service (mandatory) + table.add_service(Service::new(0x1801)); + + // Battery service + let bat_level_handle = table.add_service(Service::new(0x180f)).add_characteristic( + 0x2a19, + &[CharacteristicProp::Read, CharacteristicProp::Notify], + &mut bat_level, + ); + + let mut adv_data = [0; 31]; + AdStructure::encode_slice( + &[ + AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED), + AdStructure::ServiceUuids16(&[Uuid::Uuid16([0x0f, 0x18])]), + AdStructure::CompleteLocalName(b"Trouble ESP32"), + ], + &mut adv_data[..], + ) + .unwrap(); + + let server = ble.gatt_server::(&table); + + info!("Starting advertising and GATT service"); + // Run all 3 tasks in a join. They can also be separate embassy tasks. + let _ = join3( + // Runs the BLE host task + ble.run(), + // Processing events from GATT server (if an attribute was written) + async { + loop { + match server.next().await { + Ok(_event) => { + info!("Gatt event!"); + } + Err(e) => { + warn!("Error processing GATT events: {:?}", e); + } + } + } + }, + // Advertise our presence to the world. + async { + loop { + let mut advertiser = ble + .advertise( + &Default::default(), + Advertisement::ConnectableScannableUndirected { + adv_data: &adv_data[..], + scan_data: &[], + }, + ) + .await + .unwrap(); + let conn = advertiser.accept().await.unwrap(); + // Keep connection alive and notify with value change + let mut tick: u8 = 0; + loop { + if !conn.is_connected() { + break; + } + Timer::after(Duration::from_secs(1)).await; + tick = tick.wrapping_add(1); + server + .notify(&ble, bat_level_handle, &conn, &[tick]) + .await + .ok(); + } + } + }, + ) + .await; +}