Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/configs/sdkconfig_trouble.defaults
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Examples often require a larger than the default stack size for the main thread and for the sysloop event task.
CONFIG_ESP_TIMER_TASK_STACK_SIZE=4096 # Necessary when the `embassy-time-driver` feature is enabled
CONFIG_ESP_MAIN_TASK_STACK_SIZE=50000

# BT-HCI only ( tested for esp32c3) - they may have different names on xtensa modems with dual stack bt
CONFIG_BT_ENABLED=y

CONFIG_BT_BLUEDROID_ENABLED=n
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n
CONFIG_BTDM_CTRL_MODE_BTDM=n
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y

CONFIG_BT_CONTROLLER_ONLY=y
#CONFIG_BT_CONTROLLER_ENABLED=y
#CONFIG_BT_CTRL_HCI_MODE_VHCI=y
CONFIG_BT_CTRL_RUN_IN_FLASH_ONLY=n
# following are only needed in flash only mode, and then the bt_config struct also needs to set the following values
#CONFIG_BT_CTRL_BLE_SCAN=y
#CONFIG_BT_CTRL_BLE_MASTER=y

CONFIG_BT_HCI_LOG_DEBUG_EN=y
CONFIG_LOG_DEFAULT_LEVEL_INFO=y
CONFIG_LOG_MAXIMUM_LEVEL_VERBOSE=y



#CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=n
19 changes: 17 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ jobs:
- v5.1.2
- v5.2.3
- v5.3.2
#include:
# - mcu: esp32c3
# target: riscv32imc-esp-espidf
# - mcu: esp32c6
# target: riscv32imac-esp-espidf
steps:
- name: Setup | Checkout
uses: actions/checkout@v3
Expand Down Expand Up @@ -101,7 +106,8 @@ jobs:
ESP_DEVICE_IP: "192.168.1.250"
GATEWAY_IP: "192.168.1.1"
GATEWAY_NETMASK: "24"
run: cargo clippy --examples --features experimental --target ${{ matrix.target }} -Zbuild-std=std,panic_abort -Zbuild-std-features=panic_immediate_abort -- -Dwarnings
# excluded examples can only be run with the sdkconfig_trouble.default file
run: cargo clippy --examples --features experimental --target ${{ matrix.target }} -Zbuild-std=std,panic_abort -Zbuild-std-features=panic_immediate_abort -- -Dwarnings

- name: Build | Examples
if: matrix.target != 'riscv32imac-esp-espidf' || matrix.idf-version != 'v4.4.8'
Expand All @@ -114,4 +120,13 @@ jobs:
ESP_DEVICE_IP: "192.168.1.250"
GATEWAY_IP: "192.168.1.1"
GATEWAY_NETMASK: "24"
run: cargo build --examples --features experimental --target ${{ matrix.target }} -Zbuild-std=std,panic_abort -Zbuild-std-features=panic_immediate_abort
run: cargo build --examples --features experimental --target ${{ matrix.target}} -Zbuild-std=std,panic_abort -Zbuild-std-features=panic_immediate_abort


- name: Build | Example Trouble BLE HCI
if: matrix.target!= 'riscv32imac-esp-espidf' || matrix.idf-version != 'v4.4.8'
env:
ESP_IDF_VERSION: ${{ matrix.idf-version }}
ESP_IDF_SDKCONFIG_DEFAULTS: "${{ github.workspace }}/.github/configs/sdkconfig_trouble.defaults"
RUSTFLAGS: "${{ !startsWith(matrix.idf-version, 'v4') && '--cfg espidf_time64' || startsWith(matrix.idf-version, 'v4') && '--cfg espidf_time32' }}"
run: cargo build --features trouble,critical-section --example bt_trouble_gatt_central --example bt_trouble_scan --example bt_trouble_gatt_peripheral --target ${{ matrix.target }} -Zbuild-std=std,panic_abort -Zbuild-std-features=panic_immediate_abort
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/.vscode
/.idea
/.espressif
/.embuild
/target
Expand Down
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- OTA: New method - `EspFirmwareInfoLoad::fetch_native` - returning the full native ESP-IDF image descriptor structures
- Added `use_serde` feature, which enables the `use_serde` feature of `embedded-svc` crate, allowing to deserialize configuration structs.
- OTA: Allow specifying image size to speed up erase
- Bluetooth: New methods `EspBleGap::start_scanning` and `EspBleGap::stop_scanning`
- New example, `bt_ble_gap_scanner` to demonstrate usage of added ble scanning methods
- BT (Bluedroid): New methods `EspBleGap::start_scanning` and `EspBleGap::stop_scanning`
- BT (Bluedroid): New example, `bt_ble_gap_scanner` to demonstrate usage of added ble scanning methods
- BT: Add embassy trouble / bt_hci support ( with 3 examples). For more examples visit the embassy trouble host examples.


## [0.51.0] - 2025-01-15

Expand Down
18 changes: 18 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ esp-idf-hal = { git = "https://github.com/esp-rs/esp-idf-hal" }
[lib]
harness = false

[profile.dev]
debug = true # Symbols are nice and they don't increase the size on Flash
opt-level = "z"

[profile.dev.package."*"]
debug = true # Symbols are nice and they don't increase the size on Flash
opt-level = "z"

[features]
default = ["std", "binstart"]

Expand All @@ -31,6 +39,9 @@ alloc = ["esp-idf-hal/alloc", "embedded-svc/alloc", "uncased/alloc"]
nightly = ["embedded-svc/nightly", "esp-idf-hal/nightly"]
experimental = ["embedded-svc/experimental", "esp-idf-hal/experimental"]

# Provide a ble controller for use with embassy trouble / bt-hci
trouble = ["dep:bt-hci", "experimental", "embassy-time-driver"]

# Propagated esp-idf-hal features
critical-section = ["esp-idf-hal/critical-section"]
wake-from-isr = ["esp-idf-hal/wake-from-isr"]
Expand Down Expand Up @@ -61,6 +72,7 @@ embassy-time-queue-utils = { version = "0.1", optional = true }
embassy-futures = "0.1"
embedded-storage = { version = "0.3", optional = true }
futures-io = { version = "0.3", optional = true }
bt-hci = {version = "0.3", optional = true, features = ["log", "uuid", "embassy-time"]}

[build-dependencies]
embuild = "0.33"
Expand All @@ -73,3 +85,9 @@ serde = { version = "1", default-features = false, features = ["derive"] }
serde_json = { version = "1", default-features = false, features = ["alloc"] }
postcard = "1"
async-io = "2"
# used for trouble bt examples
trouble-host = { git = "https://github.com/embassy-rs/trouble" , features = ["derive","scan","central","peripheral","log", "gatt"], default-features = false}
embassy-time = { version = "0.4.0", features = ["generic-queue-32"] }
embassy-sync = { version = "0.6.2"}
static_cell = "2.1.0"
# end trouble bt example usage
2 changes: 1 addition & 1 deletion examples/bt_gatt_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
//! but also how to broadcast data to all clients that have subscribed to a characteristic, including
//! handling indication confirmations.
//!
//! Note that the Buedroid stack consumes a lot of memory, so `sdkconfig.defaults` should be carefully configured
//! Note that the Bluedroid stack consumes a lot of memory, so `sdkconfig.defaults` should be carefully configured
//! to avoid running out of memory.
//!
//! Here's a working configuration, but you might need to adjust further to your concrete use-case:
Expand Down
173 changes: 173 additions & 0 deletions examples/bt_trouble_gatt_central.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
#![allow(unexpected_cfgs)]
//! Example using the embassy trouble BLE Host stack over VHCI
//! on top of the esp ble controller.
//!
//! Build with `--features trouble,critical-section` (for now).
//!
//! This examples aims to show how in general the trouble BLE stack
//! can run ontop of esp-idf-svc. For more examples visit the
//! trouble git repository https://github.com/embassy-rs/trouble/tree/main/examples
//!
//! Note that initializing the stack consumes a lot of memory, so `sdkconfig.defaults` should be carefully configured
//! to avoid running out of memory.
//!
//! Here's a working configuration, but you might need to adjust further to your concrete use-case:
//!
//! CONFIG_ESP_MAIN_TASK_STACK_SIZE=50000
//! CONFIG_BT_ENABLED=y
//! CONFIG_BT_BLUEDROID_ENABLED=n
//! CONFIG_BT_CONTROLLER_ONLY=y
//! CONFIG_BT_CTRL_HCI_MODE_VHCI=y
//! CONFIG_BT_CONTROLLER_ENABLED=y
//! CONFIG_BT_CTRL_BLE_SCAN=y
//!
//!
#[cfg(all(not(esp32s2), feature = "critical-section", feature = "trouble"))]
fn main() -> anyhow::Result<()> {
example::main()
}

#[cfg(any(esp32s2, not(feature = "critical-section"), not(feature = "trouble")))]
fn main() -> anyhow::Result<()> {
#[cfg(esp32s2)]
panic!("ESP32-S2 does not have a BLE radio");

#[cfg(all(
not(esp32s2),
any(not(feature = "critical-section"), not(feature = "trouble"))
))]
panic!("Use `--features trouble,critical-section` when building this example");
}

#[cfg(all(not(esp32s2), feature = "critical-section", feature = "trouble"))]
mod example {
use bt_hci::controller::ExternalController;
use esp_idf_hal::peripherals::Peripherals;
use esp_idf_hal::task::block_on;
use esp_idf_svc::bt::{Ble, BtDriver};
use esp_idf_svc::nvs::EspDefaultNvsPartition;

pub fn main() -> anyhow::Result<()> {
esp_idf_svc::sys::link_patches();
EspLogger::initialize_default();

let per = Peripherals::take()?;
let nvs = EspDefaultNvsPartition::take()?;

let bt: BtDriver<'_, Ble> = BtDriver::new(per.modem, Some(nvs))?;

let controller: ExternalController<BtDriver<'_, Ble>, 20> = ExternalController::new(bt);

block_on(async { run::<_, 255>(controller).await });

Ok(())
}

use embassy_futures::join::join;
use embassy_time::{Duration, Timer};
use esp_idf_svc::log::EspLogger;
use log::info;
use trouble_host::prelude::*;

/// Max number of connections
const CONNECTIONS_MAX: usize = 2;

/// Max number of L2CAP channels.
const L2CAP_CHANNELS_MAX: usize = 2; // Signal + att

pub async fn run<C, const L2CAP_MTU: usize>(controller: C)
where
C: Controller,
{
// Using a fixed "random" address can be useful for testing. In real scenarios, one would
// use e.g. the MAC 6 byte array as the address (how to get that varies by the platform).
let address: Address = Address::random([0xff, 0x8f, 0x1b, 0x05, 0xe4, 0xff]);
info!("Our address = {:?}", address);

// On esp-idf-svc the device unique BL radio MAC can be retrieved via the following code

// use esp_idf_sys::{esp, esp_mac_type_t_ESP_MAC_BT};
// let mut raw_mac: [u8; 6] = [0; 6];
// let _ = esp!(unsafe {
// esp_idf_svc::sys::esp_read_mac(raw_mac.as_mut_ptr(), esp_mac_type_t_ESP_MAC_BT)
// });
// let address: Address = Address {
// kind: AddrKind::PUBLIC,
// addr: BdAddr::new(raw_mac),
// };

let mut resources: HostResources<CONNECTIONS_MAX, L2CAP_CHANNELS_MAX, L2CAP_MTU> =
HostResources::new();
let stack = trouble_host::new(controller, &mut resources).set_random_address(address);
let Host {
mut central,
mut runner,
..
} = stack.build();

// NOTE: Modify this to match the address of the peripheral you want to connect to.
// Currently it matches the address used by the peripheral examples
let target: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]);

let config = ConnectConfig {
connect_params: Default::default(),
scan_config: ScanConfig {
filter_accept_list: &[(target.kind, &target.addr)],
..Default::default()
},
};

info!("Scanning for peripheral...");
let _ = join(runner.run(), async {
info!("Connecting");

let conn = central.connect(&config).await.unwrap();
info!("Connected, creating gatt client");

let client = GattClient::<C, 10, 24>::new(&stack, &conn).await.unwrap();

let _ = join(client.task(), async {
info!("Looking for battery service");
let services = client
.services_by_uuid(&Uuid::new_short(0x180f))
.await
.unwrap();

let service = services.first().unwrap().clone();

info!("Looking for value handle");
let c: Characteristic<u8> = client
.characteristic_by_uuid(&service, &Uuid::new_short(0x2a19))
.await
.unwrap();

info!("Subscribing notifications");
let mut listener = client.subscribe(&c, false).await.unwrap();

let _ = join(
async {
loop {
let mut data = [0; 1];
client.read_characteristic(&c, &mut data[..]).await.unwrap();
info!("Read value: {}", data[0]);
Timer::after(Duration::from_secs(10)).await;
}
},
async {
loop {
let data = listener.next().await;
info!(
"Got notification: {:?} (val: {})",
data.as_ref(),
data.as_ref()[0]
);
}
},
)
.await;
})
.await;
})
.await;
}
}
Loading