Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add integration with bt-hci crate #1971

Merged
merged 15 commits into from
Sep 6, 2024
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions esp-hal/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion esp-hal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions esp-wifi/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,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

Expand Down
3 changes: 3 additions & 0 deletions esp-wifi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"]
Expand Down
67 changes: 67 additions & 0 deletions esp-wifi/src/ble/controller/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -177,4 +184,64 @@ pub mod asynch {
}
}
}

fn parse_hci(data: &[u8]) -> Result<Option<ControllerToHostPacket<'_>>, 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<ControllerToHostPacket<'a>, 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<T: HostToControllerPacket>(&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(())
}
}
}
2 changes: 2 additions & 0 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down Expand Up @@ -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"

Expand Down
157 changes: 157 additions & 0 deletions examples/src/bin/wifi_embassy_trouble.rs
Original file line number Diff line number Diff line change
@@ -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::timer::systimer::Target>();
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<BleHostResources<8, 8, 256>> = 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::<NoopRawMutex, 10, 256>(&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;
}