From c785ab374ca76397a6f624ba1f8bd8ccb245f5cf Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Tue, 14 Jan 2025 13:47:56 +0100 Subject: [PATCH 01/20] fix: add missing import for scan feature Fix an issue where the scan feature did not import the required dependency, and add combination to CI. --- ci.sh | 1 + host/src/host.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ci.sh b/ci.sh index 7b3f1a4a..174560a7 100755 --- a/ci.sh +++ b/ci.sh @@ -18,6 +18,7 @@ fi cargo batch \ --- build --release --manifest-path host/Cargo.toml --no-default-features --features peripheral \ --- build --release --manifest-path host/Cargo.toml --no-default-features --features central \ + --- build --release --manifest-path host/Cargo.toml --no-default-features --features central,scan \ --- build --release --manifest-path host/Cargo.toml --no-default-features --features central,peripheral \ --- build --release --manifest-path host/Cargo.toml --no-default-features --features central,peripheral,defmt \ --- build --release --manifest-path host/Cargo.toml --no-default-features --features gatt,peripheral \ diff --git a/host/src/host.rs b/host/src/host.rs index 417b74e2..94790f98 100644 --- a/host/src/host.rs +++ b/host/src/host.rs @@ -28,7 +28,7 @@ use bt_hci::{ControllerToHostPacket, FromHciBytes, WriteHci}; use embassy_futures::select::{select3, select4, Either3, Either4}; use embassy_sync::once_lock::OnceLock; use embassy_sync::waitqueue::WakerRegistration; -#[cfg(feature = "gatt")] +#[cfg(any(feature = "gatt", feature = "scan"))] use embassy_sync::{blocking_mutex::raw::NoopRawMutex, channel::Channel}; use futures::pin_mut; From 9058c06638fbe36867dd8f0f20ae6544579fdc80 Mon Sep 17 00:00:00 2001 From: Pete Kubiak Date: Tue, 14 Jan 2025 15:28:49 +0000 Subject: [PATCH 02/20] Consolidate UUID from slice functionality into TryFrom implementation --- host/src/attribute.rs | 2 +- host/src/types/uuid.rs | 33 ++++++++++++--------------------- 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/host/src/attribute.rs b/host/src/attribute.rs index dbf63b76..2773ab7d 100644 --- a/host/src/attribute.rs +++ b/host/src/attribute.rs @@ -253,7 +253,7 @@ impl AttributeData<'_> { Ok(Self::Declaration { props: CharacteristicProps(r.read()?), handle: r.read()?, - uuid: Uuid::from_slice(r.remaining()), + uuid: Uuid::try_from(r.remaining())?, }) } } diff --git a/host/src/types/uuid.rs b/host/src/types/uuid.rs index 5220a13d..1a5fa88d 100644 --- a/host/src/types/uuid.rs +++ b/host/src/types/uuid.rs @@ -1,4 +1,5 @@ //! UUID types. + use bt_hci::uuid::BluetoothUuid16; use crate::codec::{Decode, Encode, Error, Type}; @@ -30,20 +31,6 @@ impl Uuid { Self::Uuid128(val) } - /// Create a UUID from a slice, either 2 or 16 bytes long. - pub fn from_slice(val: &[u8]) -> Self { - if val.len() == 2 { - Self::Uuid16([val[0], val[1]]) - } else if val.len() == 16 { - Self::Uuid128([ - val[0], val[1], val[2], val[3], val[4], val[5], val[6], val[7], val[8], val[9], val[10], val[11], - val[12], val[13], val[14], val[15], - ]) - } else { - panic!("unexpected input"); - } - } - /// Copy the UUID bytes into a slice. pub fn bytes(&self, data: &mut [u8]) { match self { @@ -90,15 +77,19 @@ impl From for Uuid { } } -impl From<&[u8]> for Uuid { - fn from(data: &[u8]) -> Self { - match data.len() { - 2 => Uuid::Uuid16(data.try_into().unwrap()), +impl TryFrom<&[u8]> for Uuid { + type Error = crate::Error; + + fn try_from(value: &[u8]) -> Result { + match value.len() { + // Slice length has already been verified, so unwrap can be used + 2 => Ok(Uuid::Uuid16(value.try_into().unwrap())), 16 => { - let bytes: [u8; 16] = data.try_into().unwrap(); - Uuid::Uuid128(bytes) + let mut bytes = [0; 16]; + bytes.copy_from_slice(value); + Ok(Uuid::Uuid128(bytes)) } - _ => panic!(), + _ => Err(crate::Error::InvalidValue), } } } From 3f63c233904191eb750a2c37bb988901495400a1 Mon Sep 17 00:00:00 2001 From: demo Date: Tue, 14 Jan 2025 23:42:52 +0200 Subject: [PATCH 03/20] Example's ESP32 L2CAP_MTU workaround feature ('esp'): not applied to chips we *think* don't need it, documented --- examples/esp32/Cargo.toml | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/examples/esp32/Cargo.toml b/examples/esp32/Cargo.toml index 6465e10d..c5278c47 100644 --- a/examples/esp32/Cargo.toml +++ b/examples/esp32/Cargo.toml @@ -12,18 +12,32 @@ esp-hal-embassy = { version = "0.5.0" } esp-alloc = { version = "0.5.0" } esp-println = { version = "0.12.0", features = ["log"] } esp-wifi = { version = "0.11.0", features = [ "ble" ] } -trouble-example-apps = { version = "0.1.0", path = "../apps", features = ["esp", "log"] } +trouble-example-apps = { version = "0.1.0", path = "../apps", features = ["log"] } bt-hci = { version = "0.2" } embassy-futures = "0.1.1" embassy-time = { version = "0.4", features = ["generic-queue-8"] } [features] default = ["esp32c3"] + +# trouble-example-apps/esp: +# +# 'esp-hal' likely has a bug, causing _some_ ESP32 chips to give "Invalid HCI Command Parameters" BLE errors at launch, +# if the L2CAP MTU is set low enough. +# +# The error producing ranges go: +# - ESP32-C6: x..<255 // examples with 128, 251 would fail +# - ESP32-C2: RANGE NOT KNOWN // not having the hardware +# - ESP32-H2: RANGE NOT KNOWN // not having the hardware +# - ESP32, -C3, -S2, -S3: claimed not to be affected [1], so the behaviour-altering feature is not enabled for them. +# +# [1]: https://github.com/embassy-rs/trouble/pull/236#issuecomment-2586457641 +# esp32 = ["esp-hal/esp32", "esp-backtrace/esp32", "esp-hal-embassy/esp32", "esp-println/esp32", "esp-wifi/esp32"] -esp32c2 = ["esp-hal/esp32c2", "esp-backtrace/esp32c2", "esp-hal-embassy/esp32c2", "esp-println/esp32c2", "esp-wifi/esp32c2"] +esp32c2 = ["esp-hal/esp32c2", "esp-backtrace/esp32c2", "esp-hal-embassy/esp32c2", "esp-println/esp32c2", "esp-wifi/esp32c2", "trouble-example-apps/esp"] esp32c3 = ["esp-hal/esp32c3", "esp-backtrace/esp32c3", "esp-hal-embassy/esp32c3", "esp-println/esp32c3", "esp-wifi/esp32c3"] -esp32c6 = ["esp-hal/esp32c6", "esp-backtrace/esp32c6", "esp-hal-embassy/esp32c6", "esp-println/esp32c6", "esp-wifi/esp32c6"] -esp32h2 = ["esp-hal/esp32h2", "esp-backtrace/esp32h2", "esp-hal-embassy/esp32h2", "esp-println/esp32h2", "esp-wifi/esp32h2"] +esp32c6 = ["esp-hal/esp32c6", "esp-backtrace/esp32c6", "esp-hal-embassy/esp32c6", "esp-println/esp32c6", "esp-wifi/esp32c6", "trouble-example-apps/esp"] +esp32h2 = ["esp-hal/esp32h2", "esp-backtrace/esp32h2", "esp-hal-embassy/esp32h2", "esp-println/esp32h2", "esp-wifi/esp32h2", "trouble-example-apps/esp"] esp32s3 = ["esp-hal/esp32s3", "esp-backtrace/esp32s3", "esp-hal-embassy/esp32s3", "esp-println/esp32s3", "esp-wifi/esp32s3"] [profile.dev] From a44755667d0756dc1acf1eaca467f711f43b5339 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Thu, 16 Jan 2025 09:25:57 +0100 Subject: [PATCH 04/20] refactor: change api and support varying scan buffer sizes (#241) * refactor: change api and support varying scan buffer sizes * Add Scanner type which wraps the central to provide scan features * Make scanner buffer configurable size in order to handle large number of reports. * Only add valid advertising reports to scan buffer. --- ci.sh | 2 +- examples/esp32/Cargo.lock | 4 +- examples/nrf-sdc/Cargo.lock | 4 +- examples/rp-pico-2-w/Cargo.toml | 39 +--- examples/rp-pico-w/Cargo.lock | 4 +- examples/serial-hci/Cargo.lock | 4 +- examples/serial-hci/Cargo.toml | 2 +- host/Cargo.toml | 2 +- host/src/advertise.rs | 3 + host/src/central.rs | 233 +++++++-------------- host/src/connection.rs | 2 +- host/src/host.rs | 160 ++++++++++---- host/src/lib.rs | 7 +- host/src/scan.rs | 361 ++++++++++++++++++++------------ 14 files changed, 449 insertions(+), 378 deletions(-) diff --git a/ci.sh b/ci.sh index 174560a7..7d515f87 100755 --- a/ci.sh +++ b/ci.sh @@ -35,4 +35,4 @@ cargo batch \ cargo fmt --check --manifest-path ./host/Cargo.toml cargo clippy --manifest-path ./host/Cargo.toml --features gatt,peripheral,central -cargo test --manifest-path ./host/Cargo.toml --lib -- --nocapture \ No newline at end of file +cargo test --manifest-path ./host/Cargo.toml --lib -- --nocapture diff --git a/examples/esp32/Cargo.lock b/examples/esp32/Cargo.lock index 5a270355..ab3e6d8d 100644 --- a/examples/esp32/Cargo.lock +++ b/examples/esp32/Cargo.lock @@ -206,9 +206,9 @@ checksum = "1f878075b9794c1e4ac788c95b728f26aa6366d32eeb10c7051389f898f7d067" [[package]] name = "embassy-sync" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3899a6e39fa3f54bf8aaf00979f9f9c0145a522f7244810533abbb748be6ce82" +checksum = "8d2c8cdff05a7a51ba0087489ea44b0b1d97a296ca6b1d6d1a33ea7423d34049" dependencies = [ "cfg-if", "critical-section", diff --git a/examples/nrf-sdc/Cargo.lock b/examples/nrf-sdc/Cargo.lock index 29e661d5..5a6f6e84 100644 --- a/examples/nrf-sdc/Cargo.lock +++ b/examples/nrf-sdc/Cargo.lock @@ -373,9 +373,9 @@ dependencies = [ [[package]] name = "embassy-sync" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3899a6e39fa3f54bf8aaf00979f9f9c0145a522f7244810533abbb748be6ce82" +checksum = "8d2c8cdff05a7a51ba0087489ea44b0b1d97a296ca6b1d6d1a33ea7423d34049" dependencies = [ "cfg-if", "critical-section", diff --git a/examples/rp-pico-2-w/Cargo.toml b/examples/rp-pico-2-w/Cargo.toml index feef90ae..74755627 100644 --- a/examples/rp-pico-2-w/Cargo.toml +++ b/examples/rp-pico-2-w/Cargo.toml @@ -5,23 +5,23 @@ edition = "2021" resolver = "2" [dependencies] -embassy-executor = { version = "0.6.1", default-features = false, features = ["task-arena-size-98304", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } -embassy-time = { version = "0.4.0", default-features = false, features = ["defmt", "defmt-timestamp-uptime"] } -embassy-rp = { version = "0.2.0", features = ["defmt", "unstable-pac", "time-driver", "critical-section-impl", "rp235xa", "binary-info"] } +embassy-executor = { version = "0.7", default-features = false, features = ["task-arena-size-98304", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.4", default-features = false, features = ["defmt", "defmt-timestamp-uptime"] } +embassy-rp = { version = "0.3", features = ["defmt", "unstable-pac", "time-driver", "critical-section-impl", "rp235xa", "binary-info"] } embassy-futures = "0.1.1" -embassy-sync = { version = "0.6.1", features = ["defmt"] } +embassy-sync = { version = "0.6", features = ["defmt"] } futures = { version = "0.3", default-features = false, features = ["async-await"]} bt-hci = { version = "0.2.0", default-features = false, features = ["defmt"] } trouble-example-apps= { version = "0.1.0", path = "../apps", features = ["defmt"] } -cyw43 = { version = "0.2.0", features = ["defmt", "firmware-logs", "bluetooth"] } -cyw43-pio = { version = "0.2.0", features = ["defmt"] } +cyw43 = { version = "0.3", features = ["defmt", "firmware-logs", "bluetooth"] } +cyw43-pio = { version = "0.3", features = ["defmt"] } defmt = "0.3" defmt-rtt = "0.4.0" -cortex-m = { version = "0.7.6" } -cortex-m-rt = "0.7.0" +cortex-m = { version = "0.7" } +cortex-m-rt = "0.7" panic-probe = { version = "0.3", features = ["print-defmt"] } static_cell = "2" portable-atomic = { version = "1.5", features = ["critical-section"] } @@ -34,26 +34,3 @@ skip-cyw43-firmware = [] [profile.release] debug = 2 - -[patch.crates-io] -embassy-executor = { git = "https://github.com/embassy-rs/embassy.git", rev = "e68efc2d7cdea195aec112ecb61231e148a282c2" } -embassy-rp = { git = "https://github.com/embassy-rs/embassy.git", rev = "e68efc2d7cdea195aec112ecb61231e148a282c2" } -embassy-sync = { git = "https://github.com/embassy-rs/embassy.git", rev = "e68efc2d7cdea195aec112ecb61231e148a282c2" } -embassy-futures = { git = "https://github.com/embassy-rs/embassy.git", rev = "e68efc2d7cdea195aec112ecb61231e148a282c2" } -embassy-time = { git = "https://github.com/embassy-rs/embassy.git", rev = "e68efc2d7cdea195aec112ecb61231e148a282c2" } -embassy-time-driver = { git = "https://github.com/embassy-rs/embassy.git", rev = "e68efc2d7cdea195aec112ecb61231e148a282c2" } -embassy-embedded-hal = { git = "https://github.com/embassy-rs/embassy.git", rev = "e68efc2d7cdea195aec112ecb61231e148a282c2" } -cyw43 = { git = "https://github.com/embassy-rs/embassy.git", rev = "e68efc2d7cdea195aec112ecb61231e148a282c2" } -cyw43-pio = { git = "https://github.com/embassy-rs/embassy.git", rev = "e68efc2d7cdea195aec112ecb61231e148a282c2" } - -#embassy-executor = {path = "../../../embassy/embassy-executor"} -#embassy-nrf = {path = "../../../embassy/embassy-nrf"} -#embassy-sync = {path = "../../../embassy/embassy-sync"} -#embassy-futures = {path = "../../../embassy/embassy-futures"} -#embassy-time = {path = "../../../embassy/embassy-time"} -#embassy-time-driver = {path = "../../../embassy/embassy-time-driver"} -#embassy-embedded-hal = {path = "../../../embassy/embassy-embedded-hal"} -#embassy-hal-internal = {path = "../../../embassy/embassy-hal-internal"} -#nrf-sdc = { path = "../../../nrf-sdc/nrf-sdc" } -#nrf-mpsl = { path = "../../../nrf-sdc/nrf-mpsl" } -#bt-hci = { path = "../../../bt-hci" } diff --git a/examples/rp-pico-w/Cargo.lock b/examples/rp-pico-w/Cargo.lock index 38f88e22..266ffdd6 100644 --- a/examples/rp-pico-w/Cargo.lock +++ b/examples/rp-pico-w/Cargo.lock @@ -565,9 +565,9 @@ dependencies = [ [[package]] name = "embassy-sync" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3899a6e39fa3f54bf8aaf00979f9f9c0145a522f7244810533abbb748be6ce82" +checksum = "8d2c8cdff05a7a51ba0087489ea44b0b1d97a296ca6b1d6d1a33ea7423d34049" dependencies = [ "cfg-if", "critical-section", diff --git a/examples/serial-hci/Cargo.lock b/examples/serial-hci/Cargo.lock index 4091100e..0ed576c9 100644 --- a/examples/serial-hci/Cargo.lock +++ b/examples/serial-hci/Cargo.lock @@ -200,9 +200,9 @@ checksum = "1f878075b9794c1e4ac788c95b728f26aa6366d32eeb10c7051389f898f7d067" [[package]] name = "embassy-sync" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3899a6e39fa3f54bf8aaf00979f9f9c0145a522f7244810533abbb748be6ce82" +checksum = "8d2c8cdff05a7a51ba0087489ea44b0b1d97a296ca6b1d6d1a33ea7423d34049" dependencies = [ "cfg-if", "critical-section", diff --git a/examples/serial-hci/Cargo.toml b/examples/serial-hci/Cargo.toml index d5b6939d..dab90a35 100644 --- a/examples/serial-hci/Cargo.toml +++ b/examples/serial-hci/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" env_logger = "0.10.0" log = "0.4" embedded-io-adapters = { version = "0.6.1", features = ["tokio-1"] } -embassy-sync = { version = "0.6.0", features = ["log"] } +embassy-sync = { version = "0.6", features = ["log"] } embassy-time = { version = "0.4", features = ["log", "std", "generic-queue-8"] } critical-section = { version = "1.1", features = ["std"] } tokio = { version = "1", features = ["full"] } diff --git a/host/Cargo.toml b/host/Cargo.toml index e1417c0d..af5526bb 100644 --- a/host/Cargo.toml +++ b/host/Cargo.toml @@ -11,7 +11,7 @@ resolver = "2" [dependencies] bt-hci = { version = "0.2", features = ["embassy-time", "uuid"] } embedded-io = { version = "0.6" } -embassy-sync = "0.6" +embassy-sync = "0.6.2" embassy-time = "0.4" embassy-futures = "0.1" futures = { version = "0.3", default-features = false } diff --git a/host/src/advertise.rs b/host/src/advertise.rs index 78938266..8d007359 100644 --- a/host/src/advertise.rs +++ b/host/src/advertise.rs @@ -475,6 +475,9 @@ pub struct AdStructureIter<'d> { impl<'d> AdStructureIter<'d> { fn read(&mut self) -> Result, codec::Error> { let len: u8 = self.cursor.read()?; + if len < 2 { + return Err(codec::Error::InvalidValue); + } let code: u8 = self.cursor.read()?; let data = self.cursor.slice(len as usize - 1)?; match code { diff --git a/host/src/central.rs b/host/src/central.rs index ac80a5db..f3249694 100644 --- a/host/src/central.rs +++ b/host/src/central.rs @@ -1,25 +1,17 @@ //! Functionality for the BLE central role. -#[cfg(feature = "scan")] -use bt_hci::cmd::le::LeSetScanParams; -use bt_hci::cmd::le::{ - LeAddDeviceToFilterAcceptList, LeClearFilterAcceptList, LeCreateConn, LeExtCreateConn, LeSetExtScanEnable, - LeSetExtScanParams, LeSetScanEnable, -}; +use crate::connection::{ConnectConfig, Connection}; +use crate::{BleHostError, Error, Stack}; +use bt_hci::cmd::le::{LeAddDeviceToFilterAcceptList, LeClearFilterAcceptList, LeCreateConn, LeExtCreateConn}; use bt_hci::controller::{Controller, ControllerCmdAsync, ControllerCmdSync}; -use bt_hci::param::{AddrKind, BdAddr, FilterDuplicates, InitiatingPhy, LeConnRole, PhyParams, ScanningPhy}; +use bt_hci::param::{AddrKind, BdAddr, InitiatingPhy, LeConnRole, PhyParams}; #[cfg(feature = "controller-host-flow-control")] use bt_hci::param::{ConnHandleCompletedPackets, ControllerToHostFlowControl}; use embassy_futures::select::{select, Either}; - -use crate::connection::{ConnectConfig, Connection}; -#[cfg(feature = "scan")] -use crate::scan::ScanReport; -use crate::scan::{PhySet, ScanConfig}; -use crate::{BleHostError, Error, Stack}; +use embassy_time::Duration; /// A type implementing the BLE central role. pub struct Central<'d, C: Controller> { - stack: Stack<'d, C>, + pub(crate) stack: Stack<'d, C>, } impl<'d, C: Controller> Central<'d, C> { @@ -82,9 +74,7 @@ impl<'d, C: Controller> Central<'d, C> { where C: ControllerCmdSync + ControllerCmdSync - + ControllerCmdAsync - + ControllerCmdSync - + ControllerCmdSync, + + ControllerCmdAsync, { if config.scan_config.filter_accept_list.is_empty() { return Err(Error::InvalidValue.into()); @@ -109,7 +99,7 @@ impl<'d, C: Controller> Central<'d, C> { min_ce_len: config.connect_params.event_length.into(), max_ce_len: config.connect_params.event_length.into(), }; - let phy_params = Self::create_phy_params(initiating, config.scan_config.phys); + let phy_params = create_phy_params(initiating, config.scan_config.phys); host.async_command(LeExtCreateConn::new( true, @@ -136,24 +126,6 @@ impl<'d, C: Controller> Central<'d, C> { } } - fn create_phy_params(phy: P, phys: PhySet) -> PhyParams

{ - let phy_params: PhyParams

= PhyParams { - le_1m_phy: match phys { - PhySet::M1 | PhySet::M1M2 | PhySet::M1Coded | PhySet::M1M2Coded => Some(phy), - _ => None, - }, - le_2m_phy: match phys { - PhySet::M2 | PhySet::M1M2 | PhySet::M2Coded | PhySet::M1M2Coded => Some(phy), - _ => None, - }, - le_coded_phy: match phys { - PhySet::M2Coded | PhySet::Coded | PhySet::M1Coded | PhySet::M1M2Coded => Some(phy), - _ => None, - }, - }; - phy_params - } - pub(crate) async fn set_accept_filter( &mut self, filter_accept_list: &[(AddrKind, &BdAddr)], @@ -169,135 +141,72 @@ impl<'d, C: Controller> Central<'d, C> { } Ok(()) } +} - #[cfg(feature = "scan")] - async fn start_scan(&mut self, config: &ScanConfig<'_>) -> Result<(), BleHostError> - where - C: ControllerCmdSync - + ControllerCmdSync - + ControllerCmdSync - + ControllerCmdSync, - { - let host = self.stack.host; - self.set_accept_filter(config.filter_accept_list).await?; - - let params = LeSetScanParams::new( - if config.active { - bt_hci::param::LeScanKind::Active - } else { - bt_hci::param::LeScanKind::Passive - }, - config.interval.into(), - config.interval.into(), - bt_hci::param::AddrKind::PUBLIC, - if config.filter_accept_list.is_empty() { - bt_hci::param::ScanningFilterPolicy::BasicUnfiltered - } else { - bt_hci::param::ScanningFilterPolicy::BasicFiltered - }, - ); - host.command(params).await?; - host.command(LeSetScanEnable::new(true, true)).await?; - Ok(()) - } - - async fn start_scan_ext(&mut self, config: &ScanConfig<'_>) -> Result<(), BleHostError> - where - C: ControllerCmdSync - + ControllerCmdSync - + ControllerCmdSync - + ControllerCmdSync, - { - self.set_accept_filter(config.filter_accept_list).await?; - - let scanning = ScanningPhy { - active_scan: config.active, - scan_interval: config.interval.into(), - scan_window: config.window.into(), - }; - let phy_params = Self::create_phy_params(scanning, config.phys); - let host = self.stack.host; - host.command(LeSetExtScanParams::new( - host.address.map(|s| s.kind).unwrap_or(AddrKind::PUBLIC), - if config.filter_accept_list.is_empty() { - bt_hci::param::ScanningFilterPolicy::BasicUnfiltered - } else { - bt_hci::param::ScanningFilterPolicy::BasicFiltered - }, - phy_params, - )) - .await?; - host.command(LeSetExtScanEnable::new( - true, - FilterDuplicates::Disabled, - config.timeout.into(), - bt_hci::param::Duration::from_secs(0), - )) - .await?; - Ok(()) - } - - async fn stop_scan(&mut self) -> Result<(), BleHostError> - where - C: ControllerCmdSync, - { - let host = self.stack.host; - host.command(LeSetScanEnable::new(false, false)).await?; - Ok(()) - } +pub(crate) fn create_phy_params(phy: P, phys: PhySet) -> PhyParams

{ + let phy_params: PhyParams

= PhyParams { + le_1m_phy: match phys { + PhySet::M1 | PhySet::M1M2 | PhySet::M1Coded | PhySet::M1M2Coded => Some(phy), + _ => None, + }, + le_2m_phy: match phys { + PhySet::M2 | PhySet::M1M2 | PhySet::M2Coded | PhySet::M1M2Coded => Some(phy), + _ => None, + }, + le_coded_phy: match phys { + PhySet::M2Coded | PhySet::Coded | PhySet::M1Coded | PhySet::M1M2Coded => Some(phy), + _ => None, + }, + }; + phy_params +} - async fn stop_scan_ext(&mut self) -> Result<(), BleHostError> - where - C: ControllerCmdSync, - { - let host = self.stack.host; - host.command(LeSetExtScanEnable::new( - false, - FilterDuplicates::Disabled, - bt_hci::param::Duration::from_secs(0), - bt_hci::param::Duration::from_secs(0), - )) - .await?; - Ok(()) - } +/// Scanner configuration. +pub struct ScanConfig<'d> { + /// Active scanning. + pub active: bool, + /// List of addresses to accept. + pub filter_accept_list: &'d [(AddrKind, &'d BdAddr)], + /// PHYs to scan on. + pub phys: PhySet, + /// Scan interval. + pub interval: Duration, + /// Scan window. + pub window: Duration, + /// Scan timeout. + pub timeout: Duration, +} - /// Performs an extended BLE scan, return a report for discovering peripherals. - /// - /// Scan is stopped when a report is received. Call this method repeatedly to continue scanning. - #[cfg(feature = "scan")] - pub async fn scan_ext(&mut self, config: &ScanConfig<'_>) -> Result> - where - C: ControllerCmdSync - + ControllerCmdSync - + ControllerCmdSync - + ControllerCmdSync, - { - let host = self.stack.host; - self.start_scan_ext(config).await?; - let Some(report) = host.scanner.receive().await else { - return Err(Error::Timeout.into()); - }; - self.stop_scan_ext().await?; - Ok(report) +impl Default for ScanConfig<'_> { + fn default() -> Self { + Self { + active: true, + filter_accept_list: &[], + phys: PhySet::M1, + interval: Duration::from_secs(1), + window: Duration::from_secs(1), + timeout: Duration::from_secs(0), + } } +} - /// Performs a BLE scan, return a report for discovering peripherals. - /// - /// Scan is stopped when a report is received. Call this method repeatedly to continue scanning. - #[cfg(feature = "scan")] - pub async fn scan(&mut self, config: &ScanConfig<'_>) -> Result> - where - C: ControllerCmdSync - + ControllerCmdSync - + ControllerCmdSync - + ControllerCmdSync, - { - let host = self.stack.host; - self.start_scan(config).await?; - let Some(report) = host.scanner.receive().await else { - return Err(Error::Timeout.into()); - }; - self.stop_scan().await?; - Ok(report) - } +/// PHYs to scan on. +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Eq, PartialEq, Copy, Clone)] +#[repr(u8)] +pub enum PhySet { + /// 1Mbps phy + M1 = 1, + /// 2Mbps phy + M2 = 2, + /// 1Mbps + 2Mbps phys + M1M2 = 3, + /// Coded phy (125kbps, S=8) + Coded = 4, + /// 1Mbps and Coded phys + M1Coded = 5, + /// 2Mbps and Coded phys + M2Coded = 6, + /// 1Mbps, 2Mbps and Coded phys + M1M2Coded = 7, } diff --git a/host/src/connection.rs b/host/src/connection.rs index dc358c9c..5575386b 100644 --- a/host/src/connection.rs +++ b/host/src/connection.rs @@ -5,9 +5,9 @@ use bt_hci::controller::{ControllerCmdAsync, ControllerCmdSync}; use bt_hci::param::{BdAddr, ConnHandle, DisconnectReason, LeConnRole, Status}; use embassy_time::Duration; +use crate::central::ScanConfig; use crate::connection_manager::ConnectionManager; use crate::pdu::Pdu; -use crate::scan::ScanConfig; use crate::{BleHostError, Error, Stack}; /// Connection configuration. diff --git a/host/src/host.rs b/host/src/host.rs index 94790f98..e6623dcf 100644 --- a/host/src/host.rs +++ b/host/src/host.rs @@ -11,7 +11,7 @@ use bt_hci::cmd::controller_baseband::{ }; use bt_hci::cmd::le::{ LeConnUpdate, LeCreateConnCancel, LeReadBufferSize, LeReadFilterAcceptListSize, LeSetAdvEnable, LeSetEventMask, - LeSetExtAdvEnable, LeSetRandomAddr, + LeSetExtAdvEnable, LeSetExtScanEnable, LeSetRandomAddr, LeSetScanEnable, }; use bt_hci::cmd::link_control::Disconnect; use bt_hci::cmd::{AsyncCmd, SyncCmd}; @@ -20,15 +20,16 @@ use bt_hci::data::{AclBroadcastFlag, AclPacket, AclPacketBoundary}; use bt_hci::event::le::LeEvent; use bt_hci::event::{Event, Vendor}; use bt_hci::param::{ - AddrKind, AdvHandle, AdvSet, BdAddr, ConnHandle, DisconnectReason, EventMask, LeConnRole, LeEventMask, Status, + AddrKind, AdvHandle, AdvSet, BdAddr, ConnHandle, DisconnectReason, EventMask, FilterDuplicates, LeConnRole, + LeEventMask, Status, }; #[cfg(feature = "controller-host-flow-control")] use bt_hci::param::{ConnHandleCompletedPackets, ControllerToHostFlowControl}; use bt_hci::{ControllerToHostPacket, FromHciBytes, WriteHci}; -use embassy_futures::select::{select3, select4, Either3, Either4}; +use embassy_futures::select::{select3, Either3}; use embassy_sync::once_lock::OnceLock; use embassy_sync::waitqueue::WakerRegistration; -#[cfg(any(feature = "gatt", feature = "scan"))] +#[cfg(feature = "gatt")] use embassy_sync::{blocking_mutex::raw::NoopRawMutex, channel::Channel}; use futures::pin_mut; @@ -41,8 +42,6 @@ use crate::cursor::WriteCursor; use crate::l2cap::sar::{PacketReassembly, SarType}; use crate::packet_pool::{AllocId, DynamicPacketPool}; use crate::pdu::Pdu; -#[cfg(feature = "scan")] -use crate::scan::ScanReport; use crate::types::l2cap::{ L2capHeader, L2capSignal, L2capSignalHeader, L2CAP_CID_ATT, L2CAP_CID_DYN_START, L2CAP_CID_LE_U_SIGNAL, }; @@ -69,11 +68,12 @@ pub(crate) struct BleHost<'d, T> { #[cfg(feature = "gatt")] pub(crate) tx_pool: &'d dyn DynamicPacketPool<'d>, - #[cfg(feature = "scan")] - pub(crate) scanner: Channel, 1>, pub(crate) advertise_state: AdvState<'d>, pub(crate) advertise_command_state: CommandState, pub(crate) connect_command_state: CommandState, + pub(crate) scan_command_state: CommandState, + #[cfg(feature = "scan")] + pub(crate) scan_state: ScanState, } #[derive(Clone, Copy)] @@ -174,6 +174,34 @@ impl<'d> AdvState<'d> { } } +pub(crate) struct ScanState { + writer: RefCell>>, +} + +impl ScanState { + pub(crate) fn new() -> Self { + Self { + writer: RefCell::new(None), + } + } + + pub(crate) fn reset(&self, writer: embassy_sync::pipe::DynamicWriter<'static>) { + self.writer.borrow_mut().replace(writer); + } + + pub(crate) fn try_push(&self, _n_reports: u8, data: &[u8]) -> Result<(), ()> { + let mut writer = self.writer.borrow_mut(); + if let Some(writer) = writer.as_mut() { + writer.try_write(data).map_err(|_| ())?; + } + Ok(()) + } + + pub(crate) fn stop(&self) { + let _ = self.writer.borrow_mut().take(); + } +} + /// Host metrics #[derive(Default, Clone)] pub struct HostMetrics { @@ -222,9 +250,10 @@ where #[cfg(feature = "gatt")] att_client: Channel::new(), #[cfg(feature = "scan")] - scanner: Channel::new(), + scan_state: ScanState::new(), advertise_state: AdvState::new(advertise_handles), advertise_command_state: CommandState::new(), + scan_command_state: CommandState::new(), connect_command_state: CommandState::new(), } } @@ -514,6 +543,8 @@ impl<'d, C: Controller> Runner<'d, C> { + ControllerCmdSync + ControllerCmdSync + ControllerCmdSync + + ControllerCmdSync + + ControllerCmdSync + for<'t> ControllerCmdSync + for<'t> ControllerCmdSync> + for<'t> ControllerCmdSync> @@ -536,6 +567,8 @@ impl<'d, C: Controller> Runner<'d, C> { + for<'t> ControllerCmdSync + for<'t> ControllerCmdSync> + for<'t> ControllerCmdSync> + + ControllerCmdSync + + ControllerCmdSync + ControllerCmdSync + ControllerCmdSync + ControllerCmdSync, @@ -671,22 +704,54 @@ impl<'d, C: Controller> RxRunner<'d, C> { } LeEvent::LeScanTimeout(_) => { #[cfg(feature = "scan")] - let _ = host.scanner.try_send(None); + host.scan_state.stop(); } LeEvent::LeAdvertisingSetTerminated(set) => { host.advertise_state.terminate(set.adv_handle); } LeEvent::LeExtendedAdvertisingReport(data) => { #[cfg(feature = "scan")] - let _ = host - .scanner - .try_send(Some(ScanReport::new(data.reports.num_reports, &data.reports.bytes))); + { + let mut bytes = &data.reports.bytes[..]; + let mut n = 0; + while n < data.reports.num_reports { + match bt_hci::param::LeExtAdvReport::from_hci_bytes(bytes) { + Ok((_, remaining)) => { + n += 1; + bytes = remaining; + } + Err(_) => { + break; + } + } + } + let consumed = data.reports.bytes.len() - bytes.len(); + if let Err(_) = host.scan_state.try_push(n, &data.reports.bytes[..consumed]) { + warn!("[host] scan buffer overflow"); + } + } } LeEvent::LeAdvertisingReport(data) => { #[cfg(feature = "scan")] - let _ = host - .scanner - .try_send(Some(ScanReport::new(data.reports.num_reports, &data.reports.bytes))); + { + let mut bytes = &data.reports.bytes[..]; + let mut n = 0; + while n < data.reports.num_reports { + match bt_hci::param::LeAdvReport::from_hci_bytes(bytes) { + Ok((_, remaining)) => { + n += 1; + bytes = remaining; + } + Err(_) => { + break; + } + } + } + let consumed = data.reports.bytes.len() - bytes.len(); + if let Err(_) = host.scan_state.try_push(n, &data.reports.bytes[..consumed]) { + warn!("[host] scan buffer overflow"); + } + } } _ => { warn!("Unknown LE event!"); @@ -762,6 +827,8 @@ impl<'d, C: Controller> ControlRunner<'d, C> { + ControllerCmdSync + for<'t> ControllerCmdSync + for<'t> ControllerCmdSync> + + ControllerCmdSync + + ControllerCmdSync + for<'t> ControllerCmdSync> + ControllerCmdSync, { @@ -834,43 +901,64 @@ impl<'d, C: Controller> ControlRunner<'d, C> { info!("[host] initialized"); loop { - match select4( + match select3( poll_fn(|cx| host.connections.poll_disconnecting(Some(cx))), poll_fn(|cx| host.channels.poll_disconnecting(Some(cx))), - poll_fn(|cx| host.connect_command_state.poll_cancelled(cx)), - poll_fn(|cx| host.advertise_command_state.poll_cancelled(cx)), + select3( + poll_fn(|cx| host.connect_command_state.poll_cancelled(cx)), + poll_fn(|cx| host.advertise_command_state.poll_cancelled(cx)), + poll_fn(|cx| host.scan_command_state.poll_cancelled(cx)), + ), ) .await { - Either4::First(request) => { + Either3::First(request) => { trace!("[host] poll disconnecting links"); host.command(Disconnect::new(request.handle(), request.reason())) .await?; request.confirm(); } - Either4::Second(request) => { + Either3::Second(request) => { trace!("[host] poll disconnecting channels"); let mut grant = host.acl(request.handle(), 1).await?; request.send(&mut grant).await?; request.confirm(); } - Either4::Third(_) => { - trace!("[host] cancel connection create"); - // trace!("[host] cancelling create connection"); - if host.command(LeCreateConnCancel::new()).await.is_err() { - // Signal to ensure no one is stuck - host.connect_command_state.canceled(); + Either3::Third(states) => match states { + Either3::First(_) => { + trace!("[host] cancel connection create"); + // trace!("[host] cancelling create connection"); + if host.command(LeCreateConnCancel::new()).await.is_err() { + // Signal to ensure no one is stuck + host.connect_command_state.canceled(); + } } - } - Either4::Fourth(ext) => { - trace!("[host] disabling advertising"); - if ext { - host.command(LeSetExtAdvEnable::new(false, &[])).await? - } else { - host.command(LeSetAdvEnable::new(false)).await? + Either3::Second(ext) => { + trace!("[host] disabling advertising"); + if ext { + host.command(LeSetExtAdvEnable::new(false, &[])).await? + } else { + host.command(LeSetAdvEnable::new(false)).await? + } + host.advertise_command_state.canceled(); } - host.advertise_command_state.canceled(); - } + Either3::Third(ext) => { + trace!("[host] disabling scanning"); + if ext { + // TODO: A bit opinionated but not more than before + host.command(LeSetExtScanEnable::new( + false, + FilterDuplicates::Disabled, + bt_hci::param::Duration::from_secs(0), + bt_hci::param::Duration::from_secs(0), + )) + .await?; + } else { + host.command(LeSetScanEnable::new(false, false)).await?; + } + host.scan_command_state.canceled(); + } + }, } } } diff --git a/host/src/lib.rs b/host/src/lib.rs index 3623f5ca..9ea913f2 100644 --- a/host/src/lib.rs +++ b/host/src/lib.rs @@ -49,6 +49,7 @@ pub mod connection; #[cfg(feature = "gatt")] pub mod gap; pub mod l2cap; +#[cfg(feature = "scan")] pub mod scan; #[cfg(test)] @@ -87,7 +88,7 @@ pub mod prelude { pub use crate::packet_pool::{PacketPool, Qos as PacketQos}; #[cfg(feature = "peripheral")] pub use crate::peripheral::*; - #[cfg(feature = "peripheral")] + #[cfg(feature = "scan")] pub use crate::scan::*; #[cfg(feature = "gatt")] pub use crate::types::gatt_traits::{FixedGattValue, GattValue}; @@ -247,6 +248,8 @@ pub trait Controller: + ControllerCmdSync + ControllerCmdSync + ControllerCmdSync + + ControllerCmdSync + + ControllerCmdSync + ControllerCmdAsync + ControllerCmdSync + ControllerCmdSync @@ -277,6 +280,8 @@ impl< + ControllerCmdSync + ControllerCmdSync + ControllerCmdSync + + ControllerCmdSync + + ControllerCmdSync + ControllerCmdSync + ControllerCmdAsync + for<'t> ControllerCmdSync diff --git a/host/src/scan.rs b/host/src/scan.rs index b4976ff5..d84a30dc 100644 --- a/host/src/scan.rs +++ b/host/src/scan.rs @@ -1,168 +1,257 @@ //! Scan config. -use core::iter::FusedIterator; - -use bt_hci::param::{AddrKind, BdAddr, LeAdvReport, LeExtAdvReport}; -use bt_hci::{FromHciBytes, FromHciBytesError}; -use embassy_time::Duration; -use heapless::Vec; - -/// Scanner configuration. -pub struct ScanConfig<'d> { - /// Active scanning. - pub active: bool, - /// List of addresses to accept. - pub filter_accept_list: &'d [(AddrKind, &'d BdAddr)], - /// PHYs to scan on. - pub phys: PhySet, - /// Scan interval. - pub interval: Duration, - /// Scan window. - pub window: Duration, - /// Scan timeout. - pub timeout: Duration, -} +use crate::central::ScanConfig; +use crate::command::CommandState; +use crate::host::ScanState; +use crate::BleHostError; +use crate::Error; +use bt_hci::cmd::le::LeSetScanParams; +use bt_hci::cmd::le::{ + LeAddDeviceToFilterAcceptList, LeClearFilterAcceptList, LeSetExtScanEnable, LeSetExtScanParams, LeSetScanEnable, +}; +use bt_hci::controller::{Controller, ControllerCmdSync}; +use bt_hci::param::{AddrKind, FilterDuplicates, LeAdvReport, LeExtAdvReport, ScanningPhy}; +use bt_hci::FromHciBytes; +use embassy_futures::yield_now; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::pipe::DynamicWriter; +use embassy_sync::pipe::{DynamicReader, Pipe}; +use embassy_time::with_deadline; +use embassy_time::Instant; -impl Default for ScanConfig<'_> { - fn default() -> Self { - Self { - active: true, - filter_accept_list: &[], - phys: PhySet::M1, - interval: Duration::from_secs(1), - window: Duration::from_secs(1), - timeout: Duration::from_secs(0), - } - } -} +use crate::Central; -/// Scan reports. -pub struct ScanReport { - num_reports: u8, - reports: Vec, +/// A scanner that wraps a central to provide additional functionality +/// around BLE scanning. +/// +/// The buffer size can be tuned if in a noisy environment that +/// returns a lot of results. +pub struct Scanner<'d, C: Controller, const BUFFER_SIZE: usize> { + buffer: Pipe, + central: Central<'d, C>, } -/// PHYs to scan on. -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[derive(Eq, PartialEq, Copy, Clone)] -#[repr(u8)] -pub enum PhySet { - /// 1Mbps phy - M1 = 1, - /// 2Mbps phy - M2 = 2, - /// 1Mbps + 2Mbps phys - M1M2 = 3, - /// Coded phy (125kbps, S=8) - Coded = 4, - /// 1Mbps and Coded phys - M1Coded = 5, - /// 2Mbps and Coded phys - M2Coded = 6, - /// 1Mbps, 2Mbps and Coded phys - M1M2Coded = 7, -} - -impl ScanReport { - pub(crate) fn new(num_reports: u8, reports: &[u8]) -> Self { +impl<'d, C: Controller, const BUFFER_SIZE: usize> Scanner<'d, C, BUFFER_SIZE> { + /// Create a new scanner with the provided central. + pub fn new(central: Central<'d, C>) -> Self { Self { - num_reports, - reports: Vec::from_slice(reports).unwrap(), + central, + buffer: Pipe::new(), } } - /// Iterate over the scan reports. - pub fn iter(&self) -> ScanReportIter<'_> { - ScanReportIter { - len: self.num_reports as usize, - bytes: &self.reports, - } + /// Retrieve the underlying central + pub fn into_inner(self) -> Central<'d, C> { + self.central } - /// Iterate over the extended scan reports. - pub fn iter_ext(&self) -> ExtScanReportIter<'_> { - ExtScanReportIter { - len: self.num_reports as usize, - bytes: &self.reports, - } - } -} + /// Performs an extended BLE scan, return a report for discovering peripherals. + /// + /// Scan is stopped when a report is received. Call this method repeatedly to continue scanning. + pub async fn scan_ext(&mut self, config: &ScanConfig<'_>) -> Result, BleHostError> + where + C: ControllerCmdSync + + ControllerCmdSync + + ControllerCmdSync + + ControllerCmdSync, + { + let host = self.central.stack.host; + let drop = crate::host::OnDrop::new(|| { + host.scan_command_state.cancel(false); + host.scan_state.stop(); + }); + host.scan_command_state.request().await; + self.central.set_accept_filter(config.filter_accept_list).await?; -/// Iterator over scan reports. -pub struct ScanReportIter<'a> { - len: usize, - bytes: &'a [u8], -} + let scanning = ScanningPhy { + active_scan: config.active, + scan_interval: config.interval.into(), + scan_window: config.window.into(), + }; + let phy_params = crate::central::create_phy_params(scanning, config.phys); + let host = self.central.stack.host; + host.command(LeSetExtScanParams::new( + host.address.map(|s| s.kind).unwrap_or(AddrKind::PUBLIC), + if config.filter_accept_list.is_empty() { + bt_hci::param::ScanningFilterPolicy::BasicUnfiltered + } else { + bt_hci::param::ScanningFilterPolicy::BasicFiltered + }, + phy_params, + )) + .await?; -impl<'a> Iterator for ScanReportIter<'a> { - type Item = Result, FromHciBytesError>; + self.buffer.clear(); + let (reader, writer) = self.buffer.split(); + let writer: DynamicWriter<'_> = writer.into(); + // Safety: writer and reader is dropped by this or scan session + let writer = unsafe { core::mem::transmute(writer) }; + self.central.stack.host.scan_state.reset(writer); - fn next(&mut self) -> Option { - if self.len == 0 { - None - } else { - match LeAdvReport::from_hci_bytes(self.bytes) { - Ok((report, rest)) => { - self.bytes = rest; - self.len -= 1; - Some(Ok(report)) - } - Err(err) => { - self.len = 0; - Some(Err(err)) - } - } - } + host.command(LeSetExtScanEnable::new( + true, + FilterDuplicates::Disabled, + config.timeout.into(), + bt_hci::param::Duration::from_secs(0), + )) + .await?; + drop.defuse(); + Ok(ScanSession { + reader: reader.into(), + command_state: &self.central.stack.host.scan_command_state, + scan_state: &self.central.stack.host.scan_state, + deadline: if config.timeout.as_ticks() == 0 { + None + } else { + Some(Instant::now() + config.timeout.into()) + }, + done: false, + }) } - fn size_hint(&self) -> (usize, Option) { - (self.len, Some(self.len)) - } -} + /// Performs a BLE scan, return a report for discovering peripherals. + /// + /// Scan is stopped when a report is received. Call this method repeatedly to continue scanning. + pub async fn scan(&mut self, config: &ScanConfig<'_>) -> Result, BleHostError> + where + C: ControllerCmdSync + + ControllerCmdSync + + ControllerCmdSync + + ControllerCmdSync, + { + let host = self.central.stack.host; + let drop = crate::host::OnDrop::new(|| { + host.scan_command_state.cancel(false); + host.scan_state.stop(); + }); + host.scan_command_state.request().await; -impl ExactSizeIterator for ScanReportIter<'_> { - fn len(&self) -> usize { - self.len - } -} + self.central.set_accept_filter(config.filter_accept_list).await?; -impl FusedIterator for ScanReportIter<'_> {} + let params = LeSetScanParams::new( + if config.active { + bt_hci::param::LeScanKind::Active + } else { + bt_hci::param::LeScanKind::Passive + }, + config.interval.into(), + config.interval.into(), + host.address.map(|a| a.kind).unwrap_or(AddrKind::PUBLIC), + if config.filter_accept_list.is_empty() { + bt_hci::param::ScanningFilterPolicy::BasicUnfiltered + } else { + bt_hci::param::ScanningFilterPolicy::BasicFiltered + }, + ); + host.command(params).await?; -/// Iterator over extended scan reports. -pub struct ExtScanReportIter<'a> { - len: usize, - bytes: &'a [u8], + self.buffer.clear(); + let (reader, writer) = self.buffer.split(); + // Safety: writer and reader is dropped before we are permitted to create a reader again. + let writer: DynamicWriter<'_> = writer.into(); + let writer = unsafe { core::mem::transmute(writer) }; + self.central.stack.host.scan_state.reset(writer); + + host.command(LeSetScanEnable::new(true, true)).await?; + drop.defuse(); + Ok(ScanSession { + reader: reader.into(), + command_state: &self.central.stack.host.scan_command_state, + scan_state: &self.central.stack.host.scan_state, + deadline: if config.timeout.as_ticks() == 0 { + None + } else { + Some(Instant::now() + config.timeout.into()) + }, + done: false, + }) + } } -impl<'a> Iterator for ExtScanReportIter<'a> { - type Item = Result, FromHciBytesError>; +/// Handle to an active advertiser which can accept connections. +pub struct ScanSession<'d, const EXTENDED: bool> { + reader: DynamicReader<'d>, + scan_state: &'d ScanState, + command_state: &'d CommandState, + deadline: Option, + done: bool, +} - fn next(&mut self) -> Option { - if self.len == 0 { - None - } else { - match LeExtAdvReport::from_hci_bytes(self.bytes) { - Ok((report, rest)) => { - self.bytes = rest; - self.len -= 1; - Some(Ok(report)) - } - Err(err) => { - self.len = 0; - Some(Err(err)) +impl<'d> ScanSession<'d, false> { + /// Process the advertising reports in the provided closure. + pub async fn process(&mut self, mut f: impl FnMut(LeAdvReport)) -> Result<(), Error> { + self.do_process(|data| { + let mut remaining = data; + loop { + match LeAdvReport::from_hci_bytes(remaining) { + Ok((report, rest)) => { + f(report); + remaining = rest; + } + Err(err) => { + //warn!("[scan] error: {:?}, available {}", err, data.len()); + break; + } } } - } + remaining + }) + .await } +} - fn size_hint(&self) -> (usize, Option) { - (self.len, Some(self.len)) +impl<'d> ScanSession<'d, true> { + /// Process the advertising reports in the provided closure. + pub async fn process(&mut self, mut f: impl FnMut(LeExtAdvReport)) -> Result<(), Error> { + self.do_process(|data| { + let mut remaining = data; + loop { + match LeExtAdvReport::from_hci_bytes(remaining) { + Ok((report, rest)) => { + f(report); + remaining = rest; + } + Err(err) => { + //warn!("[scan] error: {:?}, available {}", err, data.len()); + break; + } + } + } + remaining + }) + .await } } -impl ExactSizeIterator for ExtScanReportIter<'_> { - fn len(&self) -> usize { - self.len +impl ScanSession<'_, EXTENDED> { + async fn do_process(&mut self, mut f: impl FnMut(&[u8]) -> &[u8]) -> Result<(), Error> { + let process_fut = async { + loop { + let data = self.reader.fill_buf().await; + let remaining = f(data); + let consumed = data.len() - remaining.len(); + self.reader.consume(consumed); + yield_now().await; + } + }; + if let Some(deadline) = self.deadline { + let r = with_deadline(deadline, process_fut).await.map_err(|_| Error::Timeout); + self.command_state.cancel(EXTENDED); + self.done = true; + r + } else { + process_fut.await; + self.command_state.cancel(EXTENDED); + self.done = true; + Ok(()) + } } } -impl FusedIterator for ExtScanReportIter<'_> {} +impl Drop for ScanSession<'_, EXTENDED> { + fn drop(&mut self) { + if !self.done { + self.command_state.cancel(EXTENDED); + } + self.scan_state.stop(); + } +} From 5738850f27cdfc02b5103a4eb2d9c74f84339397 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Thu, 16 Jan 2025 09:26:19 +0100 Subject: [PATCH 05/20] Update esp-hal to released versions. (#240) The esp-hal embassy update is released, no need to use git anymore. Co-authored-by: Ulf Lilleengen --- examples/esp32/Cargo.lock | 152 +++++++++++++++++++++----------------- examples/esp32/Cargo.toml | 20 ++--- host/src/lib.rs | 1 + rust-toolchain.toml | 2 +- 4 files changed, 94 insertions(+), 81 deletions(-) diff --git a/examples/esp32/Cargo.lock b/examples/esp32/Cargo.lock index ab3e6d8d..d35f9b65 100644 --- a/examples/esp32/Cargo.lock +++ b/examples/esp32/Cargo.lock @@ -50,9 +50,9 @@ checksum = "f798d2d157e547aa99aab0967df39edd0b70307312b6f8bd2848e6abe40896e0" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" [[package]] name = "bt-hci" @@ -140,9 +140,9 @@ dependencies = [ [[package]] name = "delegate" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc2323e10c92e1cf4d86e11538512e6dc03ceb586842970b6332af3d4046a046" +checksum = "297806318ef30ad066b15792a8372858020ae3ca2e414ee6c2133b1eb9e9e945" dependencies = [ "proc-macro2", "quote", @@ -386,8 +386,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "esp-alloc" -version = "0.5.0" -source = "git+https://github.com/esp-rs/esp-hal.git?rev=7288d0367e0a820db4ca25a22496a35beff1bb61#7288d0367e0a820db4ca25a22496a35beff1bb61" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "408c0d4c7f51efb256af18641047b0d83b58a485be9edf5a559da796abef0b63" dependencies = [ "cfg-if", "critical-section", @@ -398,8 +399,9 @@ dependencies = [ [[package]] name = "esp-backtrace" -version = "0.14.2" -source = "git+https://github.com/esp-rs/esp-hal.git?rev=7288d0367e0a820db4ca25a22496a35beff1bb61#7288d0367e0a820db4ca25a22496a35beff1bb61" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c83ca63fd02ca40644ae91ae63362e3a6e7f53458f6c1356decf892343d2418" dependencies = [ "esp-build", "esp-println", @@ -408,8 +410,9 @@ dependencies = [ [[package]] name = "esp-build" -version = "0.1.0" -source = "git+https://github.com/esp-rs/esp-hal.git?rev=7288d0367e0a820db4ca25a22496a35beff1bb61#7288d0367e0a820db4ca25a22496a35beff1bb61" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8aa1c8f9954c9506699cf1ca10a2adcc226ff10b6ae3cb9e875cf2c6a0b9a372" dependencies = [ "quote", "syn", @@ -418,16 +421,18 @@ dependencies = [ [[package]] name = "esp-config" -version = "0.2.0" -source = "git+https://github.com/esp-rs/esp-hal.git?rev=7288d0367e0a820db4ca25a22496a35beff1bb61#7288d0367e0a820db4ca25a22496a35beff1bb61" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd428a3b4b2975772f24eabea123d45cf6a5e28020396ed5dcb331ee3a904046" dependencies = [ "document-features", ] [[package]] name = "esp-hal" -version = "0.22.0" -source = "git+https://github.com/esp-rs/esp-hal.git?rev=7288d0367e0a820db4ca25a22496a35beff1bb61#7288d0367e0a820db4ca25a22496a35beff1bb61" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0219639e5154b0513d8a67f5431dd442f31479c5c1f0b21b377fd61f14e415c3" dependencies = [ "basic-toml", "bitfield", @@ -480,8 +485,9 @@ dependencies = [ [[package]] name = "esp-hal-embassy" -version = "0.5.0" -source = "git+https://github.com/esp-rs/esp-hal.git?rev=7288d0367e0a820db4ca25a22496a35beff1bb61#7288d0367e0a820db4ca25a22496a35beff1bb61" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cea15ef146c7689fede0c3a7d765e07b1eb22ef462f1203a137dacc615b031a" dependencies = [ "critical-section", "document-features", @@ -501,8 +507,9 @@ dependencies = [ [[package]] name = "esp-hal-procmacros" -version = "0.15.0" -source = "git+https://github.com/esp-rs/esp-hal.git?rev=7288d0367e0a820db4ca25a22496a35beff1bb61#7288d0367e0a820db4ca25a22496a35beff1bb61" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a3297005c2b31cd00e2ba50037edc9bddf99da3afe1c97a2d1b0165a312eab" dependencies = [ "darling", "document-features", @@ -517,8 +524,9 @@ dependencies = [ [[package]] name = "esp-metadata" -version = "0.4.0" -source = "git+https://github.com/esp-rs/esp-hal.git?rev=7288d0367e0a820db4ca25a22496a35beff1bb61#7288d0367e0a820db4ca25a22496a35beff1bb61" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb15c17e50f4cccb0d88305c19eae2d5533d750f0a05b6a05f1c99864974758e" dependencies = [ "anyhow", "basic-toml", @@ -528,8 +536,9 @@ dependencies = [ [[package]] name = "esp-println" -version = "0.12.0" -source = "git+https://github.com/esp-rs/esp-hal.git?rev=7288d0367e0a820db4ca25a22496a35beff1bb61#7288d0367e0a820db4ca25a22496a35beff1bb61" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645e54eb592ca0a3d60213b1695e2a5fc0b51ca6d693c08d6983857224a629ed" dependencies = [ "critical-section", "esp-build", @@ -540,7 +549,8 @@ dependencies = [ [[package]] name = "esp-riscv-rt" version = "0.9.1" -source = "git+https://github.com/esp-rs/esp-hal.git?rev=7288d0367e0a820db4ca25a22496a35beff1bb61#7288d0367e0a820db4ca25a22496a35beff1bb61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94aca65db6157aa5f42d9df6595b21462f28207ca4230b799aa3620352ef6a72" dependencies = [ "document-features", "riscv", @@ -562,8 +572,9 @@ dependencies = [ [[package]] name = "esp-wifi" -version = "0.11.0" -source = "git+https://github.com/esp-rs/esp-hal.git?rev=7288d0367e0a820db4ca25a22496a35beff1bb61#7288d0367e0a820db4ca25a22496a35beff1bb61" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "321b112db6629693fae49644b1ba28e8b917b58829533e9cdd0400183af1877d" dependencies = [ "bt-hci", "cfg-if", @@ -591,17 +602,18 @@ dependencies = [ [[package]] name = "esp-wifi-sys" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7730d6093853119088bbb793e3383801965ebe8fcf91e0817781d94628aaf718" +checksum = "c6b5438361891c431970194a733415006fb3d00b6eb70b3dcb66fd58f04d9b39" dependencies = [ "anyhow", ] [[package]] name = "esp32" -version = "0.34.0" -source = "git+https://github.com/esp-rs/esp-pacs?rev=ffbee35069d137ef611097d39fa7734c299ce124#ffbee35069d137ef611097d39fa7734c299ce124" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d3bff1d268a4b8d34b494c0e88466cd59a827bb330189773db299ff525ea13" dependencies = [ "critical-section", "vcell", @@ -609,8 +621,9 @@ dependencies = [ [[package]] name = "esp32c2" -version = "0.23.0" -source = "git+https://github.com/esp-rs/esp-pacs?rev=ffbee35069d137ef611097d39fa7734c299ce124#ffbee35069d137ef611097d39fa7734c299ce124" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0285be5b9dc4018d7f31fefe4c3d17f56461ef3ab46300ea1bf9d760968957f0" dependencies = [ "critical-section", "vcell", @@ -618,8 +631,9 @@ dependencies = [ [[package]] name = "esp32c3" -version = "0.26.0" -source = "git+https://github.com/esp-rs/esp-pacs?rev=ffbee35069d137ef611097d39fa7734c299ce124#ffbee35069d137ef611097d39fa7734c299ce124" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61655d48e45039dfac5ae769581fb50ea7f61dea3227b4b744a1a900d03fbbd4" dependencies = [ "critical-section", "vcell", @@ -627,8 +641,9 @@ dependencies = [ [[package]] name = "esp32c6" -version = "0.17.0" -source = "git+https://github.com/esp-rs/esp-pacs?rev=ffbee35069d137ef611097d39fa7734c299ce124#ffbee35069d137ef611097d39fa7734c299ce124" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd38a7771b65cb640cc4a79324a6301ba4ac3bf2987caca5d3aa34492238fdb9" dependencies = [ "critical-section", "vcell", @@ -636,8 +651,9 @@ dependencies = [ [[package]] name = "esp32h2" -version = "0.13.0" -source = "git+https://github.com/esp-rs/esp-pacs?rev=ffbee35069d137ef611097d39fa7734c299ce124#ffbee35069d137ef611097d39fa7734c299ce124" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a05aafc25d8c68ce504d8025750fc37915a2fc7d2605be3d3b51f8886a43411a" dependencies = [ "critical-section", "vcell", @@ -645,8 +661,9 @@ dependencies = [ [[package]] name = "esp32s3" -version = "0.29.0" -source = "git+https://github.com/esp-rs/esp-pacs?rev=ffbee35069d137ef611097d39fa7734c299ce124#ffbee35069d137ef611097d39fa7734c299ce124" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f0ab39d5ae3b61b3a83f5616a03220a7dc9c4d6e4ed16d2da73d50bf8d798d7" dependencies = [ "critical-section", "vcell", @@ -800,9 +817,9 @@ checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" [[package]] name = "instability" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "894813a444908c0c8c0e221b041771d107c4a21de1d317dc49bcc66e3c9e5b3f" +checksum = "0bf9fed6d91cfb734e7476a06bde8300a1b94e217e1b523b6f0cd1a01998c71d" dependencies = [ "darling", "indoc", @@ -850,9 +867,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] name = "memchr" @@ -862,9 +879,9 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "minijinja" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c37e1b517d1dcd0e51dc36c4567b9d5a29262b3ec8da6cb5d35e27a8fb529b5" +checksum = "212b4cab3aad057bc6e611814472905546c533295723b9e26a31c7feb19a8e65" dependencies = [ "serde", ] @@ -941,9 +958,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pin-project-lite" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -1011,9 +1028,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] @@ -1129,9 +1146,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "semihosting" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1004ff204633065b824d4b952bf5ac84c185d3bd4d43f6b3ae2f049ee318944e" +checksum = "5d00d0037a88d97379cc27d815a471350923a1dc5880d5325c49695edcdc0d37" [[package]] name = "serde" @@ -1207,9 +1224,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.93" +version = "2.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c786062daee0d6db1132800e623df74274a0a87322d8e183338e01b3d98d058" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" dependencies = [ "proc-macro2", "quote", @@ -1227,18 +1244,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.9" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.9" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ "proc-macro2", "quote", @@ -1367,9 +1384,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +checksum = "744018581f9a3454a9e15beb8a33b017183f1e7c0cd170232a2d1453b23a51c4" [[package]] name = "vcell" @@ -1467,17 +1484,18 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.20" +version = "0.6.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" dependencies = [ "memchr", ] [[package]] name = "xtensa-lx" -version = "0.9.0" -source = "git+https://github.com/esp-rs/esp-hal.git?rev=7288d0367e0a820db4ca25a22496a35beff1bb61#7288d0367e0a820db4ca25a22496a35beff1bb61" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51cbb46c78cfd284c9378070ab90bae9d14d38b3766cb853a97c0a137f736d5b" dependencies = [ "critical-section", "document-features", @@ -1485,8 +1503,9 @@ dependencies = [ [[package]] name = "xtensa-lx-rt" -version = "0.17.2" -source = "git+https://github.com/esp-rs/esp-hal.git?rev=7288d0367e0a820db4ca25a22496a35beff1bb61#7288d0367e0a820db4ca25a22496a35beff1bb61" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "689c2ef159d9cd4fc9503603e9999968a84a30db9bde0f0f880d0cceea0190a9" dependencies = [ "anyhow", "document-features", @@ -1503,7 +1522,8 @@ dependencies = [ [[package]] name = "xtensa-lx-rt-proc-macros" version = "0.2.2" -source = "git+https://github.com/esp-rs/esp-hal.git?rev=7288d0367e0a820db4ca25a22496a35beff1bb61#7288d0367e0a820db4ca25a22496a35beff1bb61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11277b1e4cbb7ffe44678c668518b249c843c81df249b8f096701757bc50d7ee" dependencies = [ "darling", "proc-macro2", diff --git a/examples/esp32/Cargo.toml b/examples/esp32/Cargo.toml index c5278c47..fdd3b442 100644 --- a/examples/esp32/Cargo.toml +++ b/examples/esp32/Cargo.toml @@ -6,12 +6,12 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-executor = { version = "0.7.0", features = ["task-arena-size-32768"] } -esp-backtrace = { version = "0.14.2", features = [ "exception-handler", "panic-handler", "println" ] } -esp-hal = { version = "0.22.0", features = [ "unstable" ] } -esp-hal-embassy = { version = "0.5.0" } -esp-alloc = { version = "0.5.0" } -esp-println = { version = "0.12.0", features = ["log"] } -esp-wifi = { version = "0.11.0", features = [ "ble" ] } +esp-backtrace = { version = "0.15", features = [ "exception-handler", "panic-handler", "println" ] } +esp-hal = { version = "0.23.0", features = [ "unstable" ] } +esp-hal-embassy = { version = "0.6.0" } +esp-alloc = { version = "0.6.0" } +esp-println = { version = "0.13.0", features = ["log"] } +esp-wifi = { version = "0.12.0", features = [ "ble" ] } trouble-example-apps = { version = "0.1.0", path = "../apps", features = ["log"] } bt-hci = { version = "0.2" } embassy-futures = "0.1.1" @@ -53,11 +53,3 @@ incremental = false lto = 'thin' opt-level = 3 overflow-checks = false - -[patch.crates-io] -esp-hal = { git = "https://github.com/esp-rs/esp-hal.git", rev = "7288d0367e0a820db4ca25a22496a35beff1bb61" } -esp-hal-embassy = { git = "https://github.com/esp-rs/esp-hal.git", rev = "7288d0367e0a820db4ca25a22496a35beff1bb61" } -esp-wifi = { git = "https://github.com/esp-rs/esp-hal.git", rev = "7288d0367e0a820db4ca25a22496a35beff1bb61" } -esp-println = { git = "https://github.com/esp-rs/esp-hal.git", rev = "7288d0367e0a820db4ca25a22496a35beff1bb61" } -esp-backtrace = { git = "https://github.com/esp-rs/esp-hal.git", rev = "7288d0367e0a820db4ca25a22496a35beff1bb61" } -esp-alloc = { git = "https://github.com/esp-rs/esp-hal.git", rev = "7288d0367e0a820db4ca25a22496a35beff1bb61" } diff --git a/host/src/lib.rs b/host/src/lib.rs index 9ea913f2..2c7de96d 100644 --- a/host/src/lib.rs +++ b/host/src/lib.rs @@ -7,6 +7,7 @@ #![no_std] #![allow(dead_code)] #![allow(unused_variables)] +#![allow(clippy::needless_lifetimes)] #![warn(missing_docs)] use core::mem::MaybeUninit; diff --git a/rust-toolchain.toml b/rust-toolchain.toml index da3fe7f6..e024e7c4 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,7 +1,7 @@ # Before upgrading check that everything is available on all tier1 targets here: # https://rust-lang.github.io/rustup-components-history [toolchain] -channel = "1.83" +channel = "1.84" components = [ "rust-src", "rustfmt", "llvm-tools-preview" ] targets = [ "thumbv8m.main-none-eabihf", From 036423424c6c347dccef30b602ef46c5de38fa0b Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Thu, 16 Jan 2025 14:08:11 +0100 Subject: [PATCH 06/20] refactor: use hilprobe library Using probe-rs CLI makes test library simpler. We don't need fancy control over the probe. --- examples/tests/Cargo.toml | 20 +- examples/tests/src/lib.rs | 31 ++ examples/tests/src/probe/mod.rs | 184 +++----- examples/tests/src/probe/run.rs | 409 ------------------ .../{l2cap.rs => ble_l2cap_peripheral.rs} | 77 ++-- 5 files changed, 132 insertions(+), 589 deletions(-) delete mode 100644 examples/tests/src/probe/run.rs rename examples/tests/tests/{l2cap.rs => ble_l2cap_peripheral.rs} (60%) diff --git a/examples/tests/Cargo.toml b/examples/tests/Cargo.toml index 3735e63c..0b6a6623 100644 --- a/examples/tests/Cargo.toml +++ b/examples/tests/Cargo.toml @@ -15,22 +15,22 @@ tokio = { version = "1", default-features = false, features = [ "time", "rt-multi-thread", "macros", + "process" ] } +pretty_env_logger = "0.5.0" reqwest = "0.12" +hilbench-agent = "0.1.0" embedded-io-adapters = { version = "0.6.1", features = ["tokio-1"] } embedded-io-async = { version = "0.6.1" } embedded-io = { version = "0.6.1" } -embassy-sync = { version = "0.6" } -tokio-serial = "5.4" critical-section = { version = "1", features = ["std"] } -probe-rs = "0.25.0" +embassy-sync = "0.6" +tokio-serial = "5.4" +tokio-util = "0.7" rand = "0.8.5" heapless = "0.8.0" anyhow = "1" -object = "0.32.2" -defmt-decoder = { version = "0.3.9", features = ["unstable"] } -bytes = "1.5.0" -backtrace = "0.3.69" -pretty_env_logger = "0.5.0" -pin-project-lite = "0.2.13" -chrono = { version = "0.4.31", features = ["serde"] } +tempfile = "3.15" + +[patch.crates-io] +hilbench-agent = { git = "https://github.com/lulf/hilbench.git", rev = "700693cec2f813967f6717341296828d4c2971ae" } diff --git a/examples/tests/src/lib.rs b/examples/tests/src/lib.rs index 6b814ded..dbccd3ee 100644 --- a/examples/tests/src/lib.rs +++ b/examples/tests/src/lib.rs @@ -1,2 +1,33 @@ +use anyhow::anyhow; +use hilbench_agent::ProbeConfig; +use probe::DeviceUnderTest; + pub mod probe; pub mod serial; + +pub struct TestContext { + pub serial_adapters: Vec, + pub probe_config: ProbeConfig, +} + +impl TestContext { + pub fn new() -> Self { + let serial_adapters = serial::find_controllers(); + let config = std::env::var("PROBE_CONFIG").unwrap(); + log::info!("Using probe config {}", config); + let probe_config = serde_json::from_str(&config).unwrap(); + + Self { + serial_adapters, + probe_config, + } + } + + pub fn find_dut(&self, labels: &[(&str, &str)]) -> Result, anyhow::Error> { + let selector = hilbench_agent::init(self.probe_config.clone()); + let target = selector + .select(labels) + .ok_or(anyhow!("Unable to find DUT for {:?}", labels))?; + Ok(DeviceUnderTest::new(target)) + } +} diff --git a/examples/tests/src/probe/mod.rs b/examples/tests/src/probe/mod.rs index e5e94bf4..d147fd3b 100644 --- a/examples/tests/src/probe/mod.rs +++ b/examples/tests/src/probe/mod.rs @@ -1,136 +1,80 @@ -use probe_rs::flashing::Format; -use probe_rs::probe::list::Lister; -use probe_rs::probe::DebugProbeSelector; -use probe_rs::{Permissions, Session}; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::OnceLock; -use tokio::sync::oneshot; -use tokio::task::spawn_blocking; - -mod run; - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct ProbeConfig { - pub targets: Vec, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct TargetConfig { - pub chip: String, - pub probe: String, - pub labels: HashMap, -} - -static SELECTOR: OnceLock = OnceLock::new(); - -pub fn init(config: ProbeConfig) -> &'static ProbeSelector { - SELECTOR.get_or_init(|| ProbeSelector::new(config)) -} - -pub struct ProbeSelector { - targets: Vec<(AtomicBool, TargetConfig)>, -} - -#[derive(Debug)] -pub struct Target<'d> { - config: TargetConfig, - taken: &'d AtomicBool, +use std::io::Write; +use std::process::Stdio; +use tokio::io::AsyncBufReadExt; +use tokio::io::BufReader; +use tokio::process::Command; +use tokio::select; +use tokio_util::sync::CancellationToken; + +use hilbench_agent::ProbeConfig; +use hilbench_agent::Target; + +pub fn init(config: ProbeConfig) { + hilbench_agent::init(config); } pub struct Firmware { pub data: Vec, - pub format: Format, } -impl ProbeSelector { - fn new(config: ProbeConfig) -> Self { - let mut targets = Vec::new(); - for t in config.targets { - targets.push((AtomicBool::new(false), t)); - } - Self { targets } - } - - /// Select a target with the provided labels - pub fn select<'m>(&'m self, labels: &[(&str, &str)]) -> Option> { - for (taken, config) in &self.targets { - let mut matched = true; - for (key, value) in labels { - let v = config.labels.get(*key); - if let Some(v) = v { - if v != value { - matched = false; - break; - } - } - } - if matched && taken.swap(true, Ordering::Acquire) == false { - return Some(Target { - config: config.clone(), - taken, - }); - } - } - None - } +pub struct DeviceUnderTest<'d> { + target: Target<'d>, + token: CancellationToken, } -impl<'d> Target<'d> { - pub fn flash(self, fw: Firmware) -> Result, anyhow::Error> { - let probe = self.config.probe.clone(); - let p: DebugProbeSelector = probe.try_into()?; - log::info!("Debug probe selector created"); - let t = probe_rs::config::get_target_by_name(&self.config.chip)?; - log::info!("Target created"); - - let lister = Lister::new(); - log::info!("Opening probe"); - let probe = lister.open(p)?; - - let perms = Permissions::new().allow_erase_all(); - log::info!("Attaching probe"); - let mut session = probe.attach(t, perms)?; - let mut flasher = run::Flasher::new(fw); - flasher.flash(&mut session)?; - Ok(TargetRunner { - _target: self, - flasher, - session, - }) +impl<'d> DeviceUnderTest<'d> { + pub(crate) fn new(target: Target<'d>) -> Self { + Self { + target, + token: CancellationToken::new(), + } } -} - -impl<'d> Drop for Target<'d> { - fn drop(&mut self) { - self.taken.store(false, Ordering::Release); + pub fn token(&self) -> CancellationToken { + self.token.clone() } -} - -pub struct TargetRunner<'d> { - _target: Target<'d>, - flasher: run::Flasher, - session: Session, -} -impl<'d> TargetRunner<'d> { - pub async fn run(mut self, cancel: oneshot::Receiver<()>) -> Result<(), anyhow::Error> { - let result = spawn_blocking(move || { - let mut runner = self.flasher.start(&mut self.session).unwrap(); - runner.run(&mut self.session, cancel) - }) - .await - .unwrap(); - match result { - Ok(halted) => { - if halted { - Err(anyhow::anyhow!("Firmware stopped")) - } else { - Ok(()) + pub async fn run(self, fw: Firmware) -> Result { + let mut temp = tempfile::NamedTempFile::new()?; + temp.write_all(&fw.data)?; + let path = temp.path().to_str().unwrap().to_string(); + drop(temp); + let mut flasher = Command::new("probe-rs") + .env("RUST_LOG", "info") + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .arg("run") + .arg("--elf") + .arg(&path) + .arg("--chip") + .arg(&self.target.config().chip) + .arg("--probe") + .arg(&self.target.config().probe) + .spawn() + .unwrap(); + + let stderr = flasher.stderr.as_mut().unwrap(); + let mut stderr_reader = BufReader::new(stderr); + + let mut lines: Vec = Vec::new(); + select! { + _ = self.token.cancelled() => { + flasher.kill().await.unwrap(); + } + _ = async { + loop { + let mut line = String::new(); + stderr_reader.read_line(&mut line).await.unwrap(); + lines.push(line); } + } => { + } - Err(e) => Err(e.into()), } + flasher.wait().await.unwrap(); + Ok(FirmwareLogs { lines }) } } + +pub struct FirmwareLogs { + pub lines: Vec, +} diff --git a/examples/tests/src/probe/run.rs b/examples/tests/src/probe/run.rs deleted file mode 100644 index 54dcd080..00000000 --- a/examples/tests/src/probe/run.rs +++ /dev/null @@ -1,409 +0,0 @@ -use std::collections::BTreeMap; -use std::fmt::Write; -use std::io::Cursor; -use std::time::Duration; - -use super::Firmware; -use anyhow::{anyhow, bail}; -use defmt_decoder::{DecodeError, Location, StreamDecoder, Table}; -use log::info; -use object::read::{File as ElfFile, Object as _}; -use object::ObjectSymbol; -use probe_rs::debug::{DebugInfo, DebugRegisters}; -use probe_rs::flashing::{DownloadOptions, Format}; -use probe_rs::rtt::{Rtt, ScanRegion}; -use probe_rs::{Core, MemoryInterface, Session}; -use tokio::sync::oneshot; - -const THUMB_BIT: u32 = 1; -const TIMEOUT: Duration = Duration::from_secs(1); - -const POLL_SLEEP_MILLIS: u64 = 100; - -pub(crate) struct Flasher { - firmware: Firmware, -} - -pub(crate) struct Runner { - elf: Option, -} - -struct ElfRunner { - rtt: Rtt, - di: DebugInfo, - defmt_table: Box, - defmt_locs: BTreeMap, - defmt_stream: Box, -} - -unsafe fn fuck_it<'a, 'b, T>(wtf: &'a T) -> &'b T { - std::mem::transmute(wtf) -} - -impl Flasher { - pub fn new(firmware: Firmware) -> Self { - Self { firmware } - } - - pub fn flash(&mut self, sess: &mut Session) -> anyhow::Result<()> { - // reset ALL cores other than the main one. - // This is needed for rp2040 core1. - for (i, _) in sess.list_cores() { - if i != 0 { - sess.core(i)?.reset()?; - } - } - - sess.core(0)?.reset_and_halt(TIMEOUT)?; - - log::info!("flashing program..."); - let mut dopts = DownloadOptions::new(); - dopts.keep_unwritten_bytes = true; - dopts.verify = true; - - let mut loader = sess.target().flash_loader(); - let instruction_set = sess.core(0)?.instruction_set().ok(); - loader.load_image( - sess, - &mut Cursor::new(&self.firmware.data), - self.firmware.format.clone(), - instruction_set, - )?; - loader.commit(sess, dopts)?; - - //flashing::download_file_with_options(sess, &opts.elf, Format::Elf, dopts)?; - log::info!("flashing done!"); - - Ok(()) - } - - pub(crate) fn start(&mut self, sess: &mut Session) -> anyhow::Result { - if self.firmware.format == Format::Elf { - let elf_bytes = &self.firmware.data[..]; - let elf = ElfFile::parse(elf_bytes)?; - let di = DebugInfo::from_raw(elf_bytes)?; - - let table = Box::new(defmt_decoder::Table::parse(elf_bytes)?.unwrap()); - let locs = table.get_locations(elf_bytes)?; - if !table.is_empty() && locs.is_empty() { - log::warn!("insufficient DWARF info; compile your program with `debug = 2` to enable location info"); - } - - let (rtt_addr, main_addr) = get_rtt_main_from(&elf)?; - let rtt_addr = rtt_addr.ok_or_else(|| anyhow!("RTT is missing"))?; - - { - let mut core = sess.core(0)?; - - core.reset_and_halt(TIMEOUT)?; - - log::debug!("starting device"); - if core.available_breakpoint_units()? == 0 { - bail!("RTT not supported on device without HW breakpoints"); - } - - // Corrupt the rtt control block so that it's setup fresh again - // Only do this when running from flash, because when running from RAM the - // "fake-flashing to RAM" is what initializes it. - core.write_word_32(rtt_addr as _, 0xdeadc0de)?; - - // RTT control block is initialized pre-main. Run until main before - // changing to BlockIfFull. - core.set_hw_breakpoint(main_addr as _)?; - core.run()?; - core.wait_for_core_halted(Duration::from_secs(5))?; - core.clear_hw_breakpoint(main_addr as _)?; - - const OFFSET: u32 = 44; - const FLAG: u32 = 2; // BLOCK_IF_FULL - core.write_word_32((rtt_addr + OFFSET) as _, FLAG)?; - - core.run()?; - } - - let rtt = setup_logging_channel(rtt_addr as u64, sess)?; - let defmt_stream = unsafe { fuck_it(&table) }.new_stream_decoder(); - - Ok(Runner { - elf: Some(ElfRunner { - defmt_table: table, - defmt_locs: locs, - rtt, - defmt_stream, - di, - }), - }) - } else { - let mut core = sess.core(0)?; - core.reset_and_halt(TIMEOUT)?; - core.run()?; - Ok(Runner { elf: None }) - } - } -} - -impl ElfRunner { - fn poll(&mut self, sess: &mut Session) -> anyhow::Result<()> { - let current_dir = std::env::current_dir()?; - - let mut read_buf = [0; 1024]; - let defmt = self - .rtt - .up_channel(0) - .ok_or_else(|| anyhow!("RTT up channel 0 not found"))?; - match defmt.read(&mut sess.core(0).unwrap(), &mut read_buf)? { - 0 => { - // Sleep to reduce CPU usage when defmt didn't return any data. - std::thread::sleep(Duration::from_millis(POLL_SLEEP_MILLIS)); - return Ok(()); - } - n => self.defmt_stream.received(&read_buf[..n]), - } - - loop { - match self.defmt_stream.decode() { - Ok(frame) => { - let loc = self.defmt_locs.get(&frame.index()); - - let (mut file, mut line) = (None, None); - if let Some(loc) = loc { - let relpath = if let Ok(relpath) = loc.file.strip_prefix(¤t_dir) { - relpath - } else { - // not relative; use full path - &loc.file - }; - file = Some(relpath.display().to_string()); - line = Some(loc.line as u32); - }; - - let mut timestamp = String::new(); - if let Some(ts) = frame.display_timestamp() { - timestamp = format!("{} ", ts); - } - - log::logger().log( - &log::Record::builder() - .level(match frame.level() { - Some(level) => match level.as_str() { - "trace" => log::Level::Trace, - "debug" => log::Level::Debug, - "info" => log::Level::Info, - "warn" => log::Level::Warn, - "error" => log::Level::Error, - _ => log::Level::Error, - }, - None => log::Level::Info, - }) - .file(file.as_deref()) - .line(line) - .target("device") - //.args(format_args!("{} {:?} {:?}", frame.display_message(), file, line)) - .args(format_args!("{}{}", timestamp, frame.display_message())) - .build(), - ); - } - Err(DecodeError::UnexpectedEof) => break, - Err(DecodeError::Malformed) => match self.defmt_table.encoding().can_recover() { - // if recovery is impossible, abort - false => bail!("failed to decode defmt data"), - // if recovery is possible, skip the current frame and continue with new data - true => log::warn!("failed to decode defmt data"), - }, - } - } - - Ok(()) - } - - fn traceback(&mut self, core: &mut Core) -> anyhow::Result<()> { - let mut r = [0; 17]; - for (i, val) in r.iter_mut().enumerate() { - *val = core.read_core_reg::(i as u16)?; - } - info!( - " R0: {:08x} R1: {:08x} R2: {:08x} R3: {:08x}", - r[0], r[1], r[2], r[3], - ); - info!( - " R4: {:08x} R5: {:08x} R6: {:08x} R7: {:08x}", - r[4], r[5], r[6], r[7], - ); - info!( - " R8: {:08x} R9: {:08x} R10: {:08x} R11: {:08x}", - r[8], r[9], r[10], r[11], - ); - info!( - " R12: {:08x} SP: {:08x} LR: {:08x} PC: {:08x}", - r[12], r[13], r[14], r[15], - ); - info!("XPSR: {:08x}", r[16]); - - info!(""); - info!("Stack:"); - let mut stack = [0u32; 32]; - core.read_32(r[13] as _, &mut stack)?; - for i in 0..(stack.len() / 4) { - info!( - "{:08x}: {:08x} {:08x} {:08x} {:08x}", - r[13] + i as u32 * 16, - stack[i * 4 + 0], - stack[i * 4 + 1], - stack[i * 4 + 2], - stack[i * 4 + 3], - ); - } - - info!(""); - info!("Backtrace:"); - let di = &self.di; - let initial_registers = DebugRegisters::from_core(core); - let exception_handler = probe_rs::exception_handler_for_core(core.core_type()); - let instruction_set = core.instruction_set().ok(); - let stack_frames = di.unwind(core, initial_registers, exception_handler.as_ref(), instruction_set)?; - - for (i, frame) in stack_frames.iter().enumerate() { - let mut s = String::new(); - write!(&mut s, "Frame {}: {} @ {}", i, frame.function_name, frame.pc).unwrap(); - - if frame.is_inlined { - write!(&mut s, " inline").unwrap(); - } - info!("{}", s); - - if let Some(location) = &frame.source_location { - let mut s = String::new(); - let file = location.path.to_string_lossy(); - write!(&mut s, " {file}").unwrap(); - - if let Some(line) = location.line { - write!(&mut s, ":{line}").unwrap(); - if let Some(col) = location.column { - match col { - probe_rs::debug::ColumnType::LeftEdge => { - write!(&mut s, ":1").unwrap(); - } - probe_rs::debug::ColumnType::Column(c) => { - write!(&mut s, ":{c}").unwrap(); - } - } - } - } - info!("{}", s); - } - } - - Ok(()) - } -} - -impl Runner { - fn poll(&mut self, sess: &mut Session) -> anyhow::Result<()> { - if let Some(elf) = self.elf.as_mut() { - return elf.poll(sess); - } - Ok(()) - } - - pub(crate) fn run(&mut self, sess: &mut Session, mut cancel: oneshot::Receiver<()>) -> anyhow::Result { - let mut was_halted = false; - - loop { - match cancel.try_recv() { - Ok(_) | Err(oneshot::error::TryRecvError::Closed) => { - break; - } - _ => {} - } - - self.poll(sess)?; - - let mut core = sess.core(0)?; - let is_halted = core.core_halted()?; - - if is_halted && was_halted { - break; - } - was_halted = is_halted; - } - if was_halted { - let mut core = sess.core(0)?; - if let Some(elf) = self.elf.as_mut() { - elf.traceback(&mut core)?; - } - } - - Ok(was_halted) - } -} - -fn setup_logging_channel(rtt_addr: u64, sess: &mut Session) -> anyhow::Result { - const NUM_RETRIES: usize = 10; // picked at random, increase if necessary - let mut rtt_res: Result = Err(probe_rs::rtt::Error::ControlBlockNotFound); - - let mut core = sess.core(0).unwrap(); - - for try_index in 0..=NUM_RETRIES { - rtt_res = Rtt::attach_region(&mut core, &ScanRegion::Exact(rtt_addr)); - match rtt_res { - Ok(_) => { - log::debug!("Successfully attached RTT"); - break; - } - Err(probe_rs::rtt::Error::ControlBlockNotFound) => { - if try_index < NUM_RETRIES { - log::trace!( - "Could not attach because the target's RTT control block isn't initialized (yet). retrying" - ); - } else { - log::error!("Max number of RTT attach retries exceeded."); - return Err(anyhow!(probe_rs::rtt::Error::ControlBlockNotFound)); - } - } - Err(e) => { - return Err(anyhow!(e)); - } - } - } - - // this block is only executed when rtt was successfully attached before - let mut rtt = rtt_res.expect("unreachable"); - for ch in rtt.up_channels().iter() { - log::debug!( - "up channel {}: {:?}, buffer size {} bytes", - ch.number(), - ch.name(), - ch.buffer_size() - ); - } - for ch in rtt.down_channels().iter() { - log::debug!( - "down channel {}: {:?}, buffer size {} bytes", - ch.number(), - ch.name(), - ch.buffer_size() - ); - } - - Ok(rtt) -} - -fn get_rtt_main_from(elf: &ElfFile) -> anyhow::Result<(Option, u32)> { - let mut rtt = None; - let mut main = None; - - for symbol in elf.symbols() { - let name = match symbol.name() { - Ok(name) => name, - Err(_) => continue, - }; - - match name { - "main" => main = Some(symbol.address() as u32 & !THUMB_BIT), - "_SEGGER_RTT" => rtt = Some(symbol.address() as u32), - _ => {} - } - } - - Ok((rtt, main.ok_or_else(|| anyhow!("`main` symbol not found"))?)) -} diff --git a/examples/tests/tests/l2cap.rs b/examples/tests/tests/ble_l2cap_peripheral.rs similarity index 60% rename from examples/tests/tests/l2cap.rs rename to examples/tests/tests/ble_l2cap_peripheral.rs index 54a56485..92ee43be 100644 --- a/examples/tests/tests/l2cap.rs +++ b/examples/tests/tests/ble_l2cap_peripheral.rs @@ -1,19 +1,21 @@ -use probe_rs::flashing::Format; +use futures::future::select; use std::time::Duration; use tokio::select; -use tokio::sync::oneshot; -use trouble_example_tests::{probe, serial}; +use trouble_example_tests::{probe, serial, TestContext}; use trouble_host::prelude::*; -#[tokio::test(flavor = "multi_thread")] -async fn l2cap_peripheral_nrf52() { +#[tokio::test] +async fn ble_l2cap_peripheral_nrf52() { let _ = pretty_env_logger::try_init(); let fw = std::fs::read("bins/nrf-sdc/ble_l2cap_peripheral").unwrap(); - let firmware = probe::Firmware { - data: fw, - format: Format::Elf, - }; - run_l2cap_peripheral_test(&[("target", "nrf52"), ("board", "microbit")], firmware).await; + let firmware = probe::Firmware { data: fw }; + let local = tokio::task::LocalSet::new(); + local + .run_until(run_l2cap_peripheral_test( + &[("target", "nrf52"), ("board", "microbit")], + firmware, + )) + .await; } /* @@ -30,28 +32,20 @@ async fn l2cap_peripheral_esp32() { */ async fn run_l2cap_peripheral_test(labels: &[(&str, &str)], firmware: probe::Firmware) { - let adapters = serial::find_controllers(); - let central = adapters[0].clone(); - let config = std::env::var("PROBE_CONFIG").unwrap(); - log::info!("Using probe config {}", config); - let config = serde_json::from_str(&config).unwrap(); - - let selector = probe::init(config); - let target = selector.select(labels).expect("no suitable probe found"); - log::info!("Found target {:?}", target); + let ctx = TestContext::new(); + let central = ctx.serial_adapters[0].clone(); - let (cancel_tx, cancel_rx) = oneshot::channel(); + let dut = ctx.find_dut(labels).unwrap(); // Flash the binary to the target - let runner = target.flash(firmware).unwrap(); + let token = dut.token(); // Spawn a runner for the target - let peripheral = tokio::task::spawn(async move { runner.run(cancel_rx).await }); - //let peripheral = tokio::task::spawn(async move {}); + let peripheral = tokio::task::spawn_local(dut.run(firmware)); // Run the central in the test using the serial adapter to verify let peripheral_address: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]); - let central_fut = async { + let central = tokio::task::spawn_local(async move { let controller_central = serial::create_controller(¢ral).await; let mut resources: HostResources = HostResources::new(PacketQos::None); let (stack, _peripheral, mut central, mut runner) = @@ -89,7 +83,7 @@ async fn run_l2cap_peripheral_test(labels: &[(&str, &str)], firmware: probe::Fir assert_eq!(rx, [i; PAYLOAD_LEN]); } log::info!("[central] data received"); - cancel_tx.send(()).unwrap(); + token.cancel(); break; } Ok(()) @@ -97,30 +91,13 @@ async fn run_l2cap_peripheral_test(labels: &[(&str, &str)], firmware: probe::Fir r } } - }; + }); - match tokio::time::timeout(Duration::from_secs(30), async { tokio::join!(central_fut, peripheral) }).await { - Ok(result) => match result { - (Err(e1), Err(e2)) => { - println!("Central error: {:?}", e1); - println!("Peripheral error: {:?}", e2); - panic!(); - } - (Err(e), _) => { - println!("Central error: {:?}", e); - panic!(); - } - (_, Err(e)) => { - println!("Peripheral error: {:?}", e); - panic!(); - } - _ => { - println!("Test completed successfully"); - } - }, - Err(e) => { - println!("Test timed out: {:?}", e); - panic!(); - } - } + tokio::time::timeout(Duration::from_secs(60), select(peripheral, central)) + .await + .map_err(|_| { + println!("Test timed out"); + assert!(false); + }) + .unwrap(); } From 17c1e3126f84a04ea45adf49246db7980441688a Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Thu, 16 Jan 2025 15:07:54 +0100 Subject: [PATCH 07/20] test: reenable esp32c3 hil --- examples/tests/tests/ble_l2cap_peripheral.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/examples/tests/tests/ble_l2cap_peripheral.rs b/examples/tests/tests/ble_l2cap_peripheral.rs index 92ee43be..0a9e0e50 100644 --- a/examples/tests/tests/ble_l2cap_peripheral.rs +++ b/examples/tests/tests/ble_l2cap_peripheral.rs @@ -18,18 +18,19 @@ async fn ble_l2cap_peripheral_nrf52() { .await; } -/* -#[tokio::test(flavor = "multi_thread")] -async fn l2cap_peripheral_esp32() { +#[tokio::test] +async fn ble_l2cap_peripheral_esp32c3() { let _ = pretty_env_logger::try_init(); let fw = std::fs::read("bins/esp32/ble_l2cap_peripheral").unwrap(); - let firmware = probe::Firmware { - data: fw, - format: Format::Idf(Default::default()), - }; - run_l2cap_peripheral_test(&[("target", "esp32"), ("board", "esp-rust-board")], firmware).await; + let firmware = probe::Firmware { data: fw }; + let local = tokio::task::LocalSet::new(); + local + .run_until(run_l2cap_peripheral_test( + &[("target", "esp32"), ("board", "esp-rust-board")], + firmware, + )) + .await; } -*/ async fn run_l2cap_peripheral_test(labels: &[(&str, &str)], firmware: probe::Firmware) { let ctx = TestContext::new(); From 465c134f95089135a480da879789dd94d0e43c1c Mon Sep 17 00:00:00 2001 From: demo Date: Thu, 16 Jan 2025 16:42:05 +0200 Subject: [PATCH 08/20] last three to potentially drop --- examples/apps/Cargo.toml | 1 - examples/esp32/Cargo.lock | 4 ---- examples/esp32/Cargo.toml | 2 -- examples/serial-hci/Cargo.lock | 1 - 4 files changed, 8 deletions(-) diff --git a/examples/apps/Cargo.toml b/examples/apps/Cargo.toml index 56a93de1..b93ccf23 100644 --- a/examples/apps/Cargo.toml +++ b/examples/apps/Cargo.toml @@ -12,7 +12,6 @@ embassy-futures = "0.1.1" embassy-sync = { version = "0.6" } embassy-time = "0.4" embedded-hal = "1.0" -embedded-hal-async = "1.0" static_cell = "2" embedded-io = "0.6" diff --git a/examples/esp32/Cargo.lock b/examples/esp32/Cargo.lock index d35f9b65..60bf7aca 100644 --- a/examples/esp32/Cargo.lock +++ b/examples/esp32/Cargo.lock @@ -228,7 +228,6 @@ dependencies = [ "critical-section", "document-features", "embassy-time-driver", - "embassy-time-queue-utils", "embedded-hal 0.2.7", "embedded-hal 1.0.0", "embedded-hal-async", @@ -1302,8 +1301,6 @@ version = "0.1.0" dependencies = [ "bt-hci", "embassy-executor", - "embassy-futures", - "embassy-time", "esp-alloc", "esp-backtrace", "esp-hal", @@ -1323,7 +1320,6 @@ dependencies = [ "embassy-sync", "embassy-time", "embedded-hal 1.0.0", - "embedded-hal-async", "embedded-io", "log", "static_cell", diff --git a/examples/esp32/Cargo.toml b/examples/esp32/Cargo.toml index fdd3b442..cd7c2e84 100644 --- a/examples/esp32/Cargo.toml +++ b/examples/esp32/Cargo.toml @@ -14,8 +14,6 @@ esp-println = { version = "0.13.0", features = ["log"] } esp-wifi = { version = "0.12.0", features = [ "ble" ] } trouble-example-apps = { version = "0.1.0", path = "../apps", features = ["log"] } bt-hci = { version = "0.2" } -embassy-futures = "0.1.1" -embassy-time = { version = "0.4", features = ["generic-queue-8"] } [features] default = ["esp32c3"] diff --git a/examples/serial-hci/Cargo.lock b/examples/serial-hci/Cargo.lock index 0ed576c9..57457610 100644 --- a/examples/serial-hci/Cargo.lock +++ b/examples/serial-hci/Cargo.lock @@ -953,7 +953,6 @@ dependencies = [ "embassy-sync", "embassy-time", "embedded-hal 1.0.0", - "embedded-hal-async", "embedded-io", "log", "static_cell", From 7749d64118830bcbc34aaf4fe544db5d80e75d41 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Fri, 17 Jan 2025 15:31:05 +0100 Subject: [PATCH 09/20] fix: fix interaction between central and peripheral in nrf * Make l2cap_mtu a const generic pass in from application * Move esp32-specific logic into esp32 examples --- ci.sh | 4 +-- .../src/bin/ble_bas_peripheral.rs | 2 +- examples/apps/Cargo.toml | 1 - examples/apps/src/ble_advertise_multiple.rs | 10 ++----- examples/apps/src/ble_bas_central.rs | 23 +++++++-------- examples/apps/src/ble_bas_peripheral.rs | 16 +++------- examples/apps/src/ble_l2cap_central.rs | 29 +++++++------------ examples/apps/src/ble_l2cap_peripheral.rs | 18 ++++-------- examples/esp32/Cargo.toml | 6 ++-- examples/esp32/src/bin/ble_bas_central.rs | 5 +++- examples/esp32/src/bin/ble_bas_peripheral.rs | 5 +++- examples/esp32/src/bin/ble_l2cap_central.rs | 5 +++- .../esp32/src/bin/ble_l2cap_peripheral.rs | 5 +++- examples/esp32/src/consts.rs | 6 ++++ examples/nrf-sdc/Cargo.lock | 1 - examples/nrf-sdc/src/bin/ble_bas_central.rs | 2 +- .../nrf-sdc/src/bin/ble_bas_peripheral.rs | 12 +++++++- examples/nrf-sdc/src/bin/ble_l2cap_central.rs | 2 +- .../nrf-sdc/src/bin/ble_l2cap_peripheral.rs | 5 ++-- .../rp-pico-2-w/src/bin/ble_bas_central.rs | 2 +- .../rp-pico-2-w/src/bin/ble_bas_peripheral.rs | 2 +- examples/rp-pico-w/Cargo.lock | 1 - examples/rp-pico-w/src/bin/ble_bas_central.rs | 2 +- .../rp-pico-w/src/bin/ble_bas_peripheral.rs | 2 +- .../serial-hci/src/bin/ble_bas_peripheral.rs | 2 +- 25 files changed, 82 insertions(+), 86 deletions(-) create mode 100644 examples/esp32/src/consts.rs diff --git a/ci.sh b/ci.sh index 7d515f87..453d144b 100755 --- a/ci.sh +++ b/ci.sh @@ -25,9 +25,9 @@ cargo batch \ --- build --release --manifest-path host/Cargo.toml --no-default-features --features gatt,central \ --- build --release --manifest-path host/Cargo.toml --no-default-features --features gatt,peripheral,central,scan \ --- build --release --manifest-path examples/nrf-sdc/Cargo.toml --target thumbv7em-none-eabihf --features nrf52840 \ - --- build --release --manifest-path examples/nrf-sdc/Cargo.toml --target thumbv7em-none-eabihf --features nrf52833 --out-dir tests/nrf-sdc \ + --- build --release --manifest-path examples/nrf-sdc/Cargo.toml --target thumbv7em-none-eabihf --features nrf52833 --artifact-dir tests/nrf-sdc \ --- build --release --manifest-path examples/nrf-sdc/Cargo.toml --target thumbv7em-none-eabihf --features nrf52832 \ - --- build --release --manifest-path examples/esp32/Cargo.toml --target riscv32imc-unknown-none-elf --out-dir tests/esp32 \ + --- build --release --manifest-path examples/esp32/Cargo.toml --target riscv32imc-unknown-none-elf --artifact-dir tests/esp32 \ --- build --release --manifest-path examples/serial-hci/Cargo.toml \ --- build --release --manifest-path examples/rp-pico-w/Cargo.toml --target thumbv6m-none-eabi --features skip-cyw43-firmware \ --- build --release --manifest-path examples/rp-pico-2-w/Cargo.toml --target thumbv8m.main-none-eabihf --features skip-cyw43-firmware diff --git a/examples/apache-nimble/src/bin/ble_bas_peripheral.rs b/examples/apache-nimble/src/bin/ble_bas_peripheral.rs index 05f2ee3f..36ee65e7 100644 --- a/examples/apache-nimble/src/bin/ble_bas_peripheral.rs +++ b/examples/apache-nimble/src/bin/ble_bas_peripheral.rs @@ -31,7 +31,7 @@ async fn main(spawner: embassy_executor::Spawner) { // wait for RNG to calm down Timer::after(Duration::from_secs(1)).await; - ble_bas_peripheral::run(controller).await; + ble_bas_peripheral::run::<_, 128>(controller).await; } #[embassy_executor::task] diff --git a/examples/apps/Cargo.toml b/examples/apps/Cargo.toml index b93ccf23..ea9ab42f 100644 --- a/examples/apps/Cargo.toml +++ b/examples/apps/Cargo.toml @@ -27,7 +27,6 @@ defmt = [ "embedded-io/defmt-03", "embedded-hal/defmt-03" ] -esp = [] log = [ "dep:log", "trouble-host/log", diff --git a/examples/apps/src/ble_advertise_multiple.rs b/examples/apps/src/ble_advertise_multiple.rs index 3969b7ff..36cef61b 100644 --- a/examples/apps/src/ble_advertise_multiple.rs +++ b/examples/apps/src/ble_advertise_multiple.rs @@ -4,18 +4,13 @@ use embassy_futures::join::join; use embassy_time::{Duration, Instant, Timer}; use trouble_host::prelude::*; -/// Size of L2CAP packets (ATT MTU is this - 4) -const L2CAP_MTU: usize = 27; - /// Max number of connections const CONNECTIONS_MAX: usize = 1; /// Max number of L2CAP channels. const L2CAP_CHANNELS_MAX: usize = 2; // Signal + att -type Resources = HostResources; - -pub async fn run(controller: C) +pub async fn run(controller: C) where C: Controller + for<'t> ControllerCmdSync> @@ -29,7 +24,8 @@ where let address: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]); info!("Our address = {:?}", address); - let mut resources = Resources::new(PacketQos::None); + let mut resources: HostResources = + HostResources::new(PacketQos::None); let (_, mut peripheral, _, mut runner) = trouble_host::new(controller, &mut resources) .set_random_address(address) .build(); diff --git a/examples/apps/src/ble_bas_central.rs b/examples/apps/src/ble_bas_central.rs index 2f28276b..6b1c2e3e 100644 --- a/examples/apps/src/ble_bas_central.rs +++ b/examples/apps/src/ble_bas_central.rs @@ -2,27 +2,26 @@ use embassy_futures::join::join; use embassy_time::{Duration, Timer}; use trouble_host::prelude::*; -/// Size of L2CAP packets -#[cfg(not(feature = "esp"))] -const L2CAP_MTU: usize = 128; -#[cfg(feature = "esp")] -// Some esp chips only accept an MTU >= 1017 -const L2CAP_MTU: usize = 1017; - /// Max number of connections const CONNECTIONS_MAX: usize = 1; /// Max number of L2CAP channels. const L2CAP_CHANNELS_MAX: usize = 3; // Signal + att + CoC -type Resources = HostResources; - -pub async fn run(controller: C) +pub async fn run(controller: C) where C: Controller, { - let mut resources = Resources::new(PacketQos::None); - let (stack, _, mut central, mut runner) = trouble_host::new(controller, &mut resources).build(); + // 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); + + let mut resources: HostResources = + HostResources::new(PacketQos::None); + let (stack, _, mut central, mut runner) = trouble_host::new(controller, &mut resources) + .set_random_address(address) + .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 diff --git a/examples/apps/src/ble_bas_peripheral.rs b/examples/apps/src/ble_bas_peripheral.rs index 0eb5ef12..366db80b 100644 --- a/examples/apps/src/ble_bas_peripheral.rs +++ b/examples/apps/src/ble_bas_peripheral.rs @@ -2,13 +2,6 @@ use embassy_futures::{join::join, select::select}; use embassy_time::Timer; use trouble_host::prelude::*; -/// Size of L2CAP packets -#[cfg(not(feature = "esp"))] -pub const L2CAP_MTU: usize = 251; -#[cfg(feature = "esp")] -// Some esp chips only accept an MTU >= 1017 -pub const L2CAP_MTU: usize = 1017; - /// Max number of connections const CONNECTIONS_MAX: usize = 1; @@ -17,8 +10,6 @@ const L2CAP_CHANNELS_MAX: usize = 2; // Signal + att const MAX_ATTRIBUTES: usize = 10; -type Resources = HostResources; - // GATT Server definition #[gatt_server] struct Server { @@ -38,16 +29,17 @@ struct BatteryService { } /// Run the BLE stack. -pub async fn run(controller: C) +pub async fn run(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::random([0x41, 0x5A, 0xE3, 0x1E, 0x83, 0xE7]); + let address: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]); info!("Our address = {:?}", address); - let mut resources = Resources::new(PacketQos::None); + let mut resources: HostResources = + HostResources::new(PacketQos::None); let (stack, mut peripheral, _, runner) = trouble_host::new(controller, &mut resources) .set_random_address(address) .build(); diff --git a/examples/apps/src/ble_l2cap_central.rs b/examples/apps/src/ble_l2cap_central.rs index 18fc0267..fa1caf06 100644 --- a/examples/apps/src/ble_l2cap_central.rs +++ b/examples/apps/src/ble_l2cap_central.rs @@ -2,34 +2,27 @@ use embassy_futures::join::join; use embassy_time::{Duration, Timer}; use trouble_host::prelude::*; -/// How many outgoing L2CAP buffers per link -const L2CAP_TXQ: u8 = 20; - -/// How many incoming L2CAP buffers per link -const L2CAP_RXQ: u8 = 20; - -/// Size of L2CAP packets -#[cfg(not(feature = "esp"))] -const L2CAP_MTU: usize = 128; -#[cfg(feature = "esp")] -// Some esp chips only accept an MTU >= 1017 -const L2CAP_MTU: usize = 1017; - /// Max number of connections const CONNECTIONS_MAX: usize = 1; /// Max number of L2CAP channels. const L2CAP_CHANNELS_MAX: usize = 3; // Signal + att + CoC -type Resources = HostResources; - -pub async fn run(controller: C) +pub async fn run(controller: C) where C: Controller, { - let mut resources = Resources::new(PacketQos::None); + // 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); + + let mut resources: HostResources = + HostResources::new(PacketQos::None); - let (stack, _, mut central, mut runner) = trouble_host::new(controller, &mut resources).build(); + let (stack, _, mut central, mut runner) = trouble_host::new(controller, &mut resources) + .set_random_address(address) + .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 diff --git a/examples/apps/src/ble_l2cap_peripheral.rs b/examples/apps/src/ble_l2cap_peripheral.rs index e02dde2f..f66389c7 100644 --- a/examples/apps/src/ble_l2cap_peripheral.rs +++ b/examples/apps/src/ble_l2cap_peripheral.rs @@ -2,26 +2,18 @@ use embassy_futures::join::join; use embassy_time::{Duration, Timer}; use trouble_host::prelude::*; -/// Size of L2CAP packets -#[cfg(not(feature = "esp"))] -pub const L2CAP_MTU: usize = 128; -#[cfg(feature = "esp")] -// Some esp chips only accept an MTU >= 1017 -pub const L2CAP_MTU: usize = 1017; - /// Max number of connections -pub const CONNECTIONS_MAX: usize = 1; +const CONNECTIONS_MAX: usize = 1; /// Max number of L2CAP channels. -pub const L2CAP_CHANNELS_MAX: usize = 3; // Signal + att + CoC - -type Resources = HostResources; +const L2CAP_CHANNELS_MAX: usize = 3; // Signal + att + CoC -pub async fn run(controller: C) +pub async fn run(controller: C) where C: Controller, { - let mut resources = Resources::new(PacketQos::None); + let mut resources: HostResources = + HostResources::new(PacketQos::None); // Hardcoded peripheral address let address: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]); diff --git a/examples/esp32/Cargo.toml b/examples/esp32/Cargo.toml index cd7c2e84..4a857865 100644 --- a/examples/esp32/Cargo.toml +++ b/examples/esp32/Cargo.toml @@ -32,10 +32,10 @@ default = ["esp32c3"] # [1]: https://github.com/embassy-rs/trouble/pull/236#issuecomment-2586457641 # esp32 = ["esp-hal/esp32", "esp-backtrace/esp32", "esp-hal-embassy/esp32", "esp-println/esp32", "esp-wifi/esp32"] -esp32c2 = ["esp-hal/esp32c2", "esp-backtrace/esp32c2", "esp-hal-embassy/esp32c2", "esp-println/esp32c2", "esp-wifi/esp32c2", "trouble-example-apps/esp"] +esp32c2 = ["esp-hal/esp32c2", "esp-backtrace/esp32c2", "esp-hal-embassy/esp32c2", "esp-println/esp32c2", "esp-wifi/esp32c2"] esp32c3 = ["esp-hal/esp32c3", "esp-backtrace/esp32c3", "esp-hal-embassy/esp32c3", "esp-println/esp32c3", "esp-wifi/esp32c3"] -esp32c6 = ["esp-hal/esp32c6", "esp-backtrace/esp32c6", "esp-hal-embassy/esp32c6", "esp-println/esp32c6", "esp-wifi/esp32c6", "trouble-example-apps/esp"] -esp32h2 = ["esp-hal/esp32h2", "esp-backtrace/esp32h2", "esp-hal-embassy/esp32h2", "esp-println/esp32h2", "esp-wifi/esp32h2", "trouble-example-apps/esp"] +esp32c6 = ["esp-hal/esp32c6", "esp-backtrace/esp32c6", "esp-hal-embassy/esp32c6", "esp-println/esp32c6", "esp-wifi/esp32c6"] +esp32h2 = ["esp-hal/esp32h2", "esp-backtrace/esp32h2", "esp-hal-embassy/esp32h2", "esp-println/esp32h2", "esp-wifi/esp32h2"] esp32s3 = ["esp-hal/esp32s3", "esp-backtrace/esp32s3", "esp-hal-embassy/esp32s3", "esp-println/esp32s3", "esp-wifi/esp32s3"] [profile.dev] diff --git a/examples/esp32/src/bin/ble_bas_central.rs b/examples/esp32/src/bin/ble_bas_central.rs index bfae6d64..cc016c95 100644 --- a/examples/esp32/src/bin/ble_bas_central.rs +++ b/examples/esp32/src/bin/ble_bas_central.rs @@ -8,6 +8,9 @@ use esp_wifi::ble::controller::BleConnector; use trouble_example_apps::ble_bas_central; use {esp_alloc as _, esp_backtrace as _}; +#[path = "../consts.rs"] +mod consts; + #[esp_hal_embassy::main] async fn main(_s: Spawner) { esp_println::logger::init_logger_from_env(); @@ -40,5 +43,5 @@ async fn main(_s: Spawner) { let connector = BleConnector::new(&init, bluetooth); let controller: ExternalController<_, 20> = ExternalController::new(connector); - ble_bas_central::run(controller).await; + ble_bas_central::run::<_, { consts::L2CAP_MTU }>(controller).await; } diff --git a/examples/esp32/src/bin/ble_bas_peripheral.rs b/examples/esp32/src/bin/ble_bas_peripheral.rs index 2552ed43..ded57aa5 100644 --- a/examples/esp32/src/bin/ble_bas_peripheral.rs +++ b/examples/esp32/src/bin/ble_bas_peripheral.rs @@ -8,6 +8,9 @@ use esp_wifi::ble::controller::BleConnector; use trouble_example_apps::ble_bas_peripheral; use {esp_alloc as _, esp_backtrace as _}; +#[path = "../consts.rs"] +mod consts; + #[esp_hal_embassy::main] async fn main(_s: Spawner) { esp_println::logger::init_logger_from_env(); @@ -40,5 +43,5 @@ async fn main(_s: Spawner) { let connector = BleConnector::new(&init, bluetooth); let controller: ExternalController<_, 20> = ExternalController::new(connector); - ble_bas_peripheral::run(controller).await; + ble_bas_peripheral::run::<_, { consts::L2CAP_MTU }>(controller).await; } diff --git a/examples/esp32/src/bin/ble_l2cap_central.rs b/examples/esp32/src/bin/ble_l2cap_central.rs index 5414df38..03024fe3 100644 --- a/examples/esp32/src/bin/ble_l2cap_central.rs +++ b/examples/esp32/src/bin/ble_l2cap_central.rs @@ -8,6 +8,9 @@ use esp_wifi::ble::controller::BleConnector; use trouble_example_apps::ble_l2cap_central; use {esp_alloc as _, esp_backtrace as _}; +#[path = "../consts.rs"] +mod consts; + #[esp_hal_embassy::main] async fn main(_s: Spawner) { esp_println::logger::init_logger_from_env(); @@ -40,5 +43,5 @@ async fn main(_s: Spawner) { let connector = BleConnector::new(&init, bluetooth); let controller: ExternalController<_, 20> = ExternalController::new(connector); - ble_l2cap_central::run(controller).await; + ble_l2cap_central::run::<_, { consts::L2CAP_MTU }>(controller).await; } diff --git a/examples/esp32/src/bin/ble_l2cap_peripheral.rs b/examples/esp32/src/bin/ble_l2cap_peripheral.rs index 67699850..4a0d19e0 100644 --- a/examples/esp32/src/bin/ble_l2cap_peripheral.rs +++ b/examples/esp32/src/bin/ble_l2cap_peripheral.rs @@ -8,6 +8,9 @@ use esp_wifi::ble::controller::BleConnector; use trouble_example_apps::ble_l2cap_peripheral; use {esp_alloc as _, esp_backtrace as _}; +#[path = "../consts.rs"] +mod consts; + #[esp_hal_embassy::main] async fn main(_s: Spawner) { esp_println::logger::init_logger_from_env(); @@ -40,5 +43,5 @@ async fn main(_s: Spawner) { let connector = BleConnector::new(&init, bluetooth); let controller: ExternalController<_, 20> = ExternalController::new(connector); - ble_l2cap_peripheral::run(controller).await; + ble_l2cap_peripheral::run::<_, { consts::L2CAP_MTU }>(controller).await; } diff --git a/examples/esp32/src/consts.rs b/examples/esp32/src/consts.rs new file mode 100644 index 00000000..31a61257 --- /dev/null +++ b/examples/esp32/src/consts.rs @@ -0,0 +1,6 @@ +/// Size of L2CAP packets +#[cfg(not(any(feature = "esp32c2", feature = "esp32c6", feature = "esp32h2")))] +pub const L2CAP_MTU: usize = 128; +// Some esp chips only accept an MTU >= 1017 +#[cfg(any(feature = "esp32c2", feature = "esp32c6", feature = "esp32h2"))] +pub const L2CAP_MTU: usize = 1017; diff --git a/examples/nrf-sdc/Cargo.lock b/examples/nrf-sdc/Cargo.lock index 5a6f6e84..1b60c573 100644 --- a/examples/nrf-sdc/Cargo.lock +++ b/examples/nrf-sdc/Cargo.lock @@ -1093,7 +1093,6 @@ dependencies = [ "embassy-sync", "embassy-time", "embedded-hal 1.0.0", - "embedded-hal-async", "embedded-io", "static_cell", "trouble-host", diff --git a/examples/nrf-sdc/src/bin/ble_bas_central.rs b/examples/nrf-sdc/src/bin/ble_bas_central.rs index 6063774f..2f92719e 100644 --- a/examples/nrf-sdc/src/bin/ble_bas_central.rs +++ b/examples/nrf-sdc/src/bin/ble_bas_central.rs @@ -73,5 +73,5 @@ async fn main(spawner: Spawner) { let mut sdc_mem = sdc::Mem::<6544>::new(); let sdc = unwrap!(build_sdc(sdc_p, &mut rng, mpsl, &mut sdc_mem)); - ble_bas_central::run(sdc).await; + ble_bas_central::run::<_, L2CAP_MTU>(sdc).await; } diff --git a/examples/nrf-sdc/src/bin/ble_bas_peripheral.rs b/examples/nrf-sdc/src/bin/ble_bas_peripheral.rs index aa437e8a..01a4586d 100644 --- a/examples/nrf-sdc/src/bin/ble_bas_peripheral.rs +++ b/examples/nrf-sdc/src/bin/ble_bas_peripheral.rs @@ -25,6 +25,15 @@ async fn mpsl_task(mpsl: &'static MultiprotocolServiceLayer<'static>) -> ! { mpsl.run().await } +/// How many outgoing L2CAP buffers per link +const L2CAP_TXQ: u8 = 3; + +/// How many incoming L2CAP buffers per link +const L2CAP_RXQ: u8 = 3; + +/// Size of L2CAP packets +const L2CAP_MTU: usize = 27; + fn build_sdc<'d, const N: usize>( p: nrf_sdc::Peripherals<'d>, rng: &'d mut rng::Rng, @@ -35,6 +44,7 @@ fn build_sdc<'d, const N: usize>( .support_adv()? .support_peripheral()? .peripheral_count(1)? + .buffer_cfg(L2CAP_MTU as u8, L2CAP_MTU as u8, L2CAP_TXQ, L2CAP_RXQ)? .build(p, rng, mpsl, mem) } @@ -63,5 +73,5 @@ async fn main(spawner: Spawner) { let mut sdc_mem = sdc::Mem::<3312>::new(); let sdc = unwrap!(build_sdc(sdc_p, &mut rng, mpsl, &mut sdc_mem)); - ble_bas_peripheral::run(sdc).await; + ble_bas_peripheral::run::<_, L2CAP_MTU>(sdc).await; } diff --git a/examples/nrf-sdc/src/bin/ble_l2cap_central.rs b/examples/nrf-sdc/src/bin/ble_l2cap_central.rs index 9b0b829e..50661827 100644 --- a/examples/nrf-sdc/src/bin/ble_l2cap_central.rs +++ b/examples/nrf-sdc/src/bin/ble_l2cap_central.rs @@ -76,5 +76,5 @@ async fn main(spawner: Spawner) { Timer::after(Duration::from_millis(200)).await; - ble_l2cap_central::run(sdc).await; + ble_l2cap_central::run::<_, L2CAP_MTU>(sdc).await; } diff --git a/examples/nrf-sdc/src/bin/ble_l2cap_peripheral.rs b/examples/nrf-sdc/src/bin/ble_l2cap_peripheral.rs index 0ad784c5..a102dc18 100644 --- a/examples/nrf-sdc/src/bin/ble_l2cap_peripheral.rs +++ b/examples/nrf-sdc/src/bin/ble_l2cap_peripheral.rs @@ -31,8 +31,7 @@ const L2CAP_TXQ: u8 = 20; /// How many incoming L2CAP buffers per link const L2CAP_RXQ: u8 = 20; -/// Size of L2CAP packets -const L2CAP_MTU: usize = ble_l2cap_peripheral::L2CAP_MTU; +const L2CAP_MTU: usize = 27; fn build_sdc<'d, const N: usize>( p: nrf_sdc::Peripherals<'d>, @@ -73,5 +72,5 @@ async fn main(spawner: Spawner) { let mut sdc_mem = sdc::Mem::<12848>::new(); let sdc = unwrap!(build_sdc(sdc_p, &mut rng, mpsl, &mut sdc_mem)); - ble_l2cap_peripheral::run(sdc).await; + ble_l2cap_peripheral::run::<_, L2CAP_MTU>(sdc).await; } diff --git a/examples/rp-pico-2-w/src/bin/ble_bas_central.rs b/examples/rp-pico-2-w/src/bin/ble_bas_central.rs index 29b721c7..d2f155a8 100644 --- a/examples/rp-pico-2-w/src/bin/ble_bas_central.rs +++ b/examples/rp-pico-2-w/src/bin/ble_bas_central.rs @@ -65,5 +65,5 @@ async fn main(spawner: Spawner) { let controller: ExternalController<_, 10> = ExternalController::new(bt_device); - ble_bas_central::run(controller).await; + ble_bas_central::run::<_, 128>(controller).await; } diff --git a/examples/rp-pico-2-w/src/bin/ble_bas_peripheral.rs b/examples/rp-pico-2-w/src/bin/ble_bas_peripheral.rs index 49dd0ac7..91e28e3e 100644 --- a/examples/rp-pico-2-w/src/bin/ble_bas_peripheral.rs +++ b/examples/rp-pico-2-w/src/bin/ble_bas_peripheral.rs @@ -65,5 +65,5 @@ async fn main(spawner: Spawner) { let controller: ExternalController<_, 10> = ExternalController::new(bt_device); - ble_bas_peripheral::run(controller).await; + ble_bas_peripheral::run::<_, 128>(controller).await; } diff --git a/examples/rp-pico-w/Cargo.lock b/examples/rp-pico-w/Cargo.lock index 266ffdd6..bca8bced 100644 --- a/examples/rp-pico-w/Cargo.lock +++ b/examples/rp-pico-w/Cargo.lock @@ -2414,7 +2414,6 @@ dependencies = [ "embassy-sync", "embassy-time", "embedded-hal 1.0.0", - "embedded-hal-async", "embedded-io", "static_cell", "trouble-host", diff --git a/examples/rp-pico-w/src/bin/ble_bas_central.rs b/examples/rp-pico-w/src/bin/ble_bas_central.rs index 401747b0..3e2cd7f4 100644 --- a/examples/rp-pico-w/src/bin/ble_bas_central.rs +++ b/examples/rp-pico-w/src/bin/ble_bas_central.rs @@ -65,5 +65,5 @@ async fn main(spawner: Spawner) { let controller: ExternalController<_, 10> = ExternalController::new(bt_device); - ble_bas_central::run(controller).await; + ble_bas_central::run::<_, 128>(controller).await; } diff --git a/examples/rp-pico-w/src/bin/ble_bas_peripheral.rs b/examples/rp-pico-w/src/bin/ble_bas_peripheral.rs index f8df355c..86084861 100644 --- a/examples/rp-pico-w/src/bin/ble_bas_peripheral.rs +++ b/examples/rp-pico-w/src/bin/ble_bas_peripheral.rs @@ -65,5 +65,5 @@ async fn main(spawner: Spawner) { let controller: ExternalController<_, 10> = ExternalController::new(bt_device); - ble_bas_peripheral::run(controller).await; + ble_bas_peripheral::run::<_, 128>(controller).await; } diff --git a/examples/serial-hci/src/bin/ble_bas_peripheral.rs b/examples/serial-hci/src/bin/ble_bas_peripheral.rs index 81bc8eba..35fe6612 100644 --- a/examples/serial-hci/src/bin/ble_bas_peripheral.rs +++ b/examples/serial-hci/src/bin/ble_bas_peripheral.rs @@ -51,5 +51,5 @@ async fn main() { let driver: SerialTransport = SerialTransport::new(reader, writer); let controller: ExternalController<_, 10> = ExternalController::new(driver); - ble_bas_peripheral::run(controller).await; + ble_bas_peripheral::run::<_, 128>(controller).await; } From bc32dba7a76a42219f51ada48b87d3517aaa6031 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Fri, 17 Jan 2025 15:37:26 +0100 Subject: [PATCH 10/20] fix: bump cargo batch version The new version supports the --artifact-dir flag --- ci.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci.sh b/ci.sh index 453d144b..1ee4ca77 100755 --- a/ci.sh +++ b/ci.sh @@ -4,7 +4,7 @@ set -eo pipefail if ! command -v cargo-batch &> /dev/null; then mkdir -p $HOME/.cargo/bin - curl -L https://github.com/embassy-rs/cargo-batch/releases/download/batch-0.5.0/cargo-batch > $HOME/.cargo/bin/cargo-batch + curl -L https://github.com/embassy-rs/cargo-batch/releases/download/batch-0.6.0/cargo-batch > $HOME/.cargo/bin/cargo-batch chmod +x $HOME/.cargo/bin/cargo-batch fi From 907e1f05531472b53be64f5922c2d531c2f88baf Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Fri, 17 Jan 2025 16:17:03 +0100 Subject: [PATCH 11/20] chore: reduce min mtu for some esp32 chips --- examples/esp32/src/consts.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/esp32/src/consts.rs b/examples/esp32/src/consts.rs index 31a61257..934e4fa2 100644 --- a/examples/esp32/src/consts.rs +++ b/examples/esp32/src/consts.rs @@ -1,6 +1,6 @@ /// Size of L2CAP packets #[cfg(not(any(feature = "esp32c2", feature = "esp32c6", feature = "esp32h2")))] pub const L2CAP_MTU: usize = 128; -// Some esp chips only accept an MTU >= 1017 +// Some esp chips only accept an MTU >= 255 #[cfg(any(feature = "esp32c2", feature = "esp32c6", feature = "esp32h2"))] -pub const L2CAP_MTU: usize = 1017; +pub const L2CAP_MTU: usize = 255; From c022705d9b770ff9130fe85abe9a8925f30a441e Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Fri, 17 Jan 2025 17:09:06 +0100 Subject: [PATCH 12/20] fix: refactor hil tests --- examples/tests/src/probe/mod.rs | 36 ++++++++++++-------- examples/tests/tests/ble_l2cap_peripheral.rs | 12 +++---- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/examples/tests/src/probe/mod.rs b/examples/tests/src/probe/mod.rs index d147fd3b..53085b52 100644 --- a/examples/tests/src/probe/mod.rs +++ b/examples/tests/src/probe/mod.rs @@ -1,4 +1,3 @@ -use std::io::Write; use std::process::Stdio; use tokio::io::AsyncBufReadExt; use tokio::io::BufReader; @@ -33,18 +32,12 @@ impl<'d> DeviceUnderTest<'d> { self.token.clone() } - pub async fn run(self, fw: Firmware) -> Result { - let mut temp = tempfile::NamedTempFile::new()?; - temp.write_all(&fw.data)?; - let path = temp.path().to_str().unwrap().to_string(); - drop(temp); + pub async fn run(self, firmware: String) -> Result { let mut flasher = Command::new("probe-rs") - .env("RUST_LOG", "info") .stdout(Stdio::piped()) .stderr(Stdio::piped()) .arg("run") - .arg("--elf") - .arg(&path) + .arg(&firmware) .arg("--chip") .arg(&self.target.config().chip) .arg("--probe") @@ -52,24 +45,39 @@ impl<'d> DeviceUnderTest<'d> { .spawn() .unwrap(); - let stderr = flasher.stderr.as_mut().unwrap(); - let mut stderr_reader = BufReader::new(stderr); + let stdout = flasher.stdout.take().unwrap(); + let stderr = flasher.stderr.take().unwrap(); + let mut stdout_reader = BufReader::new(stdout).lines(); + let mut stderr_reader = BufReader::new(stderr).lines(); let mut lines: Vec = Vec::new(); select! { + r = flasher.wait() => { + log::warn!("flasher exited unexpectedly: {:?}", r); + } _ = self.token.cancelled() => { flasher.kill().await.unwrap(); } _ = async { loop { - let mut line = String::new(); - stderr_reader.read_line(&mut line).await.unwrap(); - lines.push(line); + select! { + r = stdout_reader.next_line() => { + if let Ok(Some(r)) = r { + lines.push(r); + } + } + r = stderr_reader.next_line() => { + if let Ok(Some(r)) = r { + lines.push(r); + } + } + } } } => { } } + log::info!("waiting for process exit"); flasher.wait().await.unwrap(); Ok(FirmwareLogs { lines }) } diff --git a/examples/tests/tests/ble_l2cap_peripheral.rs b/examples/tests/tests/ble_l2cap_peripheral.rs index 0a9e0e50..0c112158 100644 --- a/examples/tests/tests/ble_l2cap_peripheral.rs +++ b/examples/tests/tests/ble_l2cap_peripheral.rs @@ -1,14 +1,13 @@ use futures::future::select; use std::time::Duration; use tokio::select; -use trouble_example_tests::{probe, serial, TestContext}; +use trouble_example_tests::{serial, TestContext}; use trouble_host::prelude::*; #[tokio::test] async fn ble_l2cap_peripheral_nrf52() { let _ = pretty_env_logger::try_init(); - let fw = std::fs::read("bins/nrf-sdc/ble_l2cap_peripheral").unwrap(); - let firmware = probe::Firmware { data: fw }; + let firmware = "bins/nrf-sdc/ble_l2cap_peripheral"; let local = tokio::task::LocalSet::new(); local .run_until(run_l2cap_peripheral_test( @@ -21,8 +20,7 @@ async fn ble_l2cap_peripheral_nrf52() { #[tokio::test] async fn ble_l2cap_peripheral_esp32c3() { let _ = pretty_env_logger::try_init(); - let fw = std::fs::read("bins/esp32/ble_l2cap_peripheral").unwrap(); - let firmware = probe::Firmware { data: fw }; + let firmware = "bins/esp32/ble_l2cap_peripheral"; let local = tokio::task::LocalSet::new(); local .run_until(run_l2cap_peripheral_test( @@ -32,7 +30,7 @@ async fn ble_l2cap_peripheral_esp32c3() { .await; } -async fn run_l2cap_peripheral_test(labels: &[(&str, &str)], firmware: probe::Firmware) { +async fn run_l2cap_peripheral_test(labels: &[(&str, &str)], firmware: &str) { let ctx = TestContext::new(); let central = ctx.serial_adapters[0].clone(); @@ -42,7 +40,7 @@ async fn run_l2cap_peripheral_test(labels: &[(&str, &str)], firmware: probe::Fir let token = dut.token(); // Spawn a runner for the target - let peripheral = tokio::task::spawn_local(dut.run(firmware)); + let peripheral = tokio::task::spawn_local(dut.run(firmware.to_string())); // Run the central in the test using the serial adapter to verify let peripheral_address: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]); From c2dc47b96e293d14427039f80fea9c57b6d778c9 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Fri, 17 Jan 2025 17:16:32 +0100 Subject: [PATCH 13/20] fix: print log lines on exit --- examples/tests/src/probe/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/tests/src/probe/mod.rs b/examples/tests/src/probe/mod.rs index 53085b52..5c4a2bce 100644 --- a/examples/tests/src/probe/mod.rs +++ b/examples/tests/src/probe/mod.rs @@ -54,6 +54,9 @@ impl<'d> DeviceUnderTest<'d> { select! { r = flasher.wait() => { log::warn!("flasher exited unexpectedly: {:?}", r); + for line in lines { + log::warn!("{}", line); + } } _ = self.token.cancelled() => { flasher.kill().await.unwrap(); From 2370ca1be344bf49acb02156ba9d675fc9bab9dd Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Fri, 17 Jan 2025 15:42:27 +0100 Subject: [PATCH 14/20] test: add HIL test for l2cap central for nrf and esp32 * Fix bug in tests passing even if failing --- ci.sh | 2 +- examples/tests/src/probe/mod.rs | 2 +- examples/tests/tests/ble_l2cap_central.rs | 117 ++++++++++++++++++++++ 3 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 examples/tests/tests/ble_l2cap_central.rs diff --git a/ci.sh b/ci.sh index 1ee4ca77..049dcfa8 100755 --- a/ci.sh +++ b/ci.sh @@ -27,7 +27,7 @@ cargo batch \ --- build --release --manifest-path examples/nrf-sdc/Cargo.toml --target thumbv7em-none-eabihf --features nrf52840 \ --- build --release --manifest-path examples/nrf-sdc/Cargo.toml --target thumbv7em-none-eabihf --features nrf52833 --artifact-dir tests/nrf-sdc \ --- build --release --manifest-path examples/nrf-sdc/Cargo.toml --target thumbv7em-none-eabihf --features nrf52832 \ - --- build --release --manifest-path examples/esp32/Cargo.toml --target riscv32imc-unknown-none-elf --artifact-dir tests/esp32 \ + --- build --release --manifest-path examples/esp32/Cargo.toml --features esp32c3 --target riscv32imc-unknown-none-elf --artifact-dir tests/esp32 \ --- build --release --manifest-path examples/serial-hci/Cargo.toml \ --- build --release --manifest-path examples/rp-pico-w/Cargo.toml --target thumbv6m-none-eabi --features skip-cyw43-firmware \ --- build --release --manifest-path examples/rp-pico-2-w/Cargo.toml --target thumbv8m.main-none-eabihf --features skip-cyw43-firmware diff --git a/examples/tests/src/probe/mod.rs b/examples/tests/src/probe/mod.rs index 5c4a2bce..b81d829e 100644 --- a/examples/tests/src/probe/mod.rs +++ b/examples/tests/src/probe/mod.rs @@ -53,10 +53,10 @@ impl<'d> DeviceUnderTest<'d> { let mut lines: Vec = Vec::new(); select! { r = flasher.wait() => { - log::warn!("flasher exited unexpectedly: {:?}", r); for line in lines { log::warn!("{}", line); } + return Err(anyhow::anyhow!("flasher exited unexpectedly: {:?}", r)); } _ = self.token.cancelled() => { flasher.kill().await.unwrap(); diff --git a/examples/tests/tests/ble_l2cap_central.rs b/examples/tests/tests/ble_l2cap_central.rs new file mode 100644 index 00000000..80bd544b --- /dev/null +++ b/examples/tests/tests/ble_l2cap_central.rs @@ -0,0 +1,117 @@ +use futures::future::select; +use std::time::Duration; +use tokio::select; +use trouble_example_tests::{serial, TestContext}; +use trouble_host::prelude::*; + +#[tokio::test] +async fn ble_l2cap_central_nrf52() { + let _ = pretty_env_logger::try_init(); + let firmware = "bins/nrf-sdc/ble_l2cap_central"; + let local = tokio::task::LocalSet::new(); + local + .run_until(run_l2cap_central_test( + &[("target", "nrf52"), ("board", "microbit")], + firmware, + )) + .await; +} + +#[tokio::test] +async fn ble_l2cap_central_esp32c3() { + let _ = pretty_env_logger::try_init(); + let firmware = "bins/esp32/ble_l2cap_central"; + let local = tokio::task::LocalSet::new(); + local + .run_until(run_l2cap_central_test( + &[("target", "esp32"), ("board", "esp-rust-board")], + firmware, + )) + .await; +} + +async fn run_l2cap_central_test(labels: &[(&str, &str)], firmware: &str) { + let ctx = TestContext::new(); + let peripheral = ctx.serial_adapters[0].clone(); + + let dut = ctx.find_dut(labels).unwrap(); + + // Flash the binary to the target + let token = dut.token(); + + // Spawn a runner for the target + let central = tokio::task::spawn_local(dut.run(firmware.to_string())); + + // Run the central in the test using the serial adapter to verify + let peripheral_address: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]); + let peripheral = tokio::task::spawn_local(async move { + let controller_peripheral = serial::create_controller(&peripheral).await; + + let mut resources: HostResources = HostResources::new(PacketQos::None); + let (stack, mut peripheral, _central, mut runner) = trouble_host::new(controller_peripheral, &mut resources) + .set_random_address(peripheral_address) + .build(); + + select! { + r = runner.run() => { + r + } + r = async { + let mut adv_data = [0; 31]; + AdStructure::encode_slice( + &[AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED)], + &mut adv_data[..], + ).unwrap(); + + let mut scan_data = [0; 31]; + AdStructure::encode_slice( + &[AdStructure::CompleteLocalName(b"trouble-l2cap-example")], + &mut scan_data[..], + ).unwrap(); + + loop { + println!("[peripheral] advertising"); + let acceptor = peripheral.advertise(&Default::default(), Advertisement::ConnectableScannableUndirected { + adv_data: &adv_data[..], + scan_data: &scan_data[..], + }).await?; + let conn = acceptor.accept().await?; + println!("[peripheral] connected"); + + let mut ch1 = L2capChannel::accept(stack, &conn, &[0x2349], &Default::default()).await?; + + println!("[peripheral] channel created"); + + const PAYLOAD_LEN: usize = 27; + // Size of payload we're expecting + let mut rx = [0; PAYLOAD_LEN]; + for i in 0..10 { + let len = ch1.receive(stack, &mut rx).await?; + assert_eq!(len, rx.len()); + assert_eq!(rx, [i; PAYLOAD_LEN]); + } + println!("[peripheral] data received"); + + for i in 0..10 { + let tx = [i; PAYLOAD_LEN]; + ch1.send::<_, PAYLOAD_LEN>(stack, &tx).await?; + } + println!("[peripheral] data sent"); + token.cancel(); + break; + } + Ok(()) + } => { + r + } + } + }); + + tokio::time::timeout(Duration::from_secs(60), select(central, peripheral)) + .await + .map_err(|_| { + println!("Test timed out"); + assert!(false); + }) + .unwrap(); +} From c5c2eaba230079662bc9be92e41b279492e78f58 Mon Sep 17 00:00:00 2001 From: James Sizeland Date: Fri, 17 Jan 2025 18:35:28 +0000 Subject: [PATCH 15/20] patch esp-wifi for c6 ble initialization bug --- examples/esp32/Cargo.lock | 44 +++++++++++++-------------------------- examples/esp32/Cargo.toml | 10 ++++++++- 2 files changed, 24 insertions(+), 30 deletions(-) diff --git a/examples/esp32/Cargo.lock b/examples/esp32/Cargo.lock index 60bf7aca..8cc0456f 100644 --- a/examples/esp32/Cargo.lock +++ b/examples/esp32/Cargo.lock @@ -386,8 +386,7 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "esp-alloc" version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "408c0d4c7f51efb256af18641047b0d83b58a485be9edf5a559da796abef0b63" +source = "git+https://github.com/esp-rs/esp-hal.git?rev=5d0145eca901f42cbebe1e41cde10e79afba3af8#5d0145eca901f42cbebe1e41cde10e79afba3af8" dependencies = [ "cfg-if", "critical-section", @@ -399,8 +398,7 @@ dependencies = [ [[package]] name = "esp-backtrace" version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c83ca63fd02ca40644ae91ae63362e3a6e7f53458f6c1356decf892343d2418" +source = "git+https://github.com/esp-rs/esp-hal.git?rev=5d0145eca901f42cbebe1e41cde10e79afba3af8#5d0145eca901f42cbebe1e41cde10e79afba3af8" dependencies = [ "esp-build", "esp-println", @@ -410,8 +408,7 @@ dependencies = [ [[package]] name = "esp-build" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8aa1c8f9954c9506699cf1ca10a2adcc226ff10b6ae3cb9e875cf2c6a0b9a372" +source = "git+https://github.com/esp-rs/esp-hal.git?rev=5d0145eca901f42cbebe1e41cde10e79afba3af8#5d0145eca901f42cbebe1e41cde10e79afba3af8" dependencies = [ "quote", "syn", @@ -421,17 +418,15 @@ dependencies = [ [[package]] name = "esp-config" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd428a3b4b2975772f24eabea123d45cf6a5e28020396ed5dcb331ee3a904046" +source = "git+https://github.com/esp-rs/esp-hal.git?rev=5d0145eca901f42cbebe1e41cde10e79afba3af8#5d0145eca901f42cbebe1e41cde10e79afba3af8" dependencies = [ "document-features", ] [[package]] name = "esp-hal" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0219639e5154b0513d8a67f5431dd442f31479c5c1f0b21b377fd61f14e415c3" +version = "0.23.1" +source = "git+https://github.com/esp-rs/esp-hal.git?rev=5d0145eca901f42cbebe1e41cde10e79afba3af8#5d0145eca901f42cbebe1e41cde10e79afba3af8" dependencies = [ "basic-toml", "bitfield", @@ -485,8 +480,7 @@ dependencies = [ [[package]] name = "esp-hal-embassy" version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cea15ef146c7689fede0c3a7d765e07b1eb22ef462f1203a137dacc615b031a" +source = "git+https://github.com/esp-rs/esp-hal.git?rev=5d0145eca901f42cbebe1e41cde10e79afba3af8#5d0145eca901f42cbebe1e41cde10e79afba3af8" dependencies = [ "critical-section", "document-features", @@ -507,8 +501,7 @@ dependencies = [ [[package]] name = "esp-hal-procmacros" version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a3297005c2b31cd00e2ba50037edc9bddf99da3afe1c97a2d1b0165a312eab" +source = "git+https://github.com/esp-rs/esp-hal.git?rev=5d0145eca901f42cbebe1e41cde10e79afba3af8#5d0145eca901f42cbebe1e41cde10e79afba3af8" dependencies = [ "darling", "document-features", @@ -524,8 +517,7 @@ dependencies = [ [[package]] name = "esp-metadata" version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb15c17e50f4cccb0d88305c19eae2d5533d750f0a05b6a05f1c99864974758e" +source = "git+https://github.com/esp-rs/esp-hal.git?rev=5d0145eca901f42cbebe1e41cde10e79afba3af8#5d0145eca901f42cbebe1e41cde10e79afba3af8" dependencies = [ "anyhow", "basic-toml", @@ -536,8 +528,7 @@ dependencies = [ [[package]] name = "esp-println" version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645e54eb592ca0a3d60213b1695e2a5fc0b51ca6d693c08d6983857224a629ed" +source = "git+https://github.com/esp-rs/esp-hal.git?rev=5d0145eca901f42cbebe1e41cde10e79afba3af8#5d0145eca901f42cbebe1e41cde10e79afba3af8" dependencies = [ "critical-section", "esp-build", @@ -548,8 +539,7 @@ dependencies = [ [[package]] name = "esp-riscv-rt" version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94aca65db6157aa5f42d9df6595b21462f28207ca4230b799aa3620352ef6a72" +source = "git+https://github.com/esp-rs/esp-hal.git?rev=5d0145eca901f42cbebe1e41cde10e79afba3af8#5d0145eca901f42cbebe1e41cde10e79afba3af8" dependencies = [ "document-features", "riscv", @@ -572,8 +562,7 @@ dependencies = [ [[package]] name = "esp-wifi" version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "321b112db6629693fae49644b1ba28e8b917b58829533e9cdd0400183af1877d" +source = "git+https://github.com/esp-rs/esp-hal.git?rev=5d0145eca901f42cbebe1e41cde10e79afba3af8#5d0145eca901f42cbebe1e41cde10e79afba3af8" dependencies = [ "bt-hci", "cfg-if", @@ -1490,8 +1479,7 @@ dependencies = [ [[package]] name = "xtensa-lx" version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51cbb46c78cfd284c9378070ab90bae9d14d38b3766cb853a97c0a137f736d5b" +source = "git+https://github.com/esp-rs/esp-hal.git?rev=5d0145eca901f42cbebe1e41cde10e79afba3af8#5d0145eca901f42cbebe1e41cde10e79afba3af8" dependencies = [ "critical-section", "document-features", @@ -1500,8 +1488,7 @@ dependencies = [ [[package]] name = "xtensa-lx-rt" version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "689c2ef159d9cd4fc9503603e9999968a84a30db9bde0f0f880d0cceea0190a9" +source = "git+https://github.com/esp-rs/esp-hal.git?rev=5d0145eca901f42cbebe1e41cde10e79afba3af8#5d0145eca901f42cbebe1e41cde10e79afba3af8" dependencies = [ "anyhow", "document-features", @@ -1518,8 +1505,7 @@ dependencies = [ [[package]] name = "xtensa-lx-rt-proc-macros" version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11277b1e4cbb7ffe44678c668518b249c843c81df249b8f096701757bc50d7ee" +source = "git+https://github.com/esp-rs/esp-hal.git?rev=5d0145eca901f42cbebe1e41cde10e79afba3af8#5d0145eca901f42cbebe1e41cde10e79afba3af8" dependencies = [ "darling", "proc-macro2", diff --git a/examples/esp32/Cargo.toml b/examples/esp32/Cargo.toml index 4a857865..363741b7 100644 --- a/examples/esp32/Cargo.toml +++ b/examples/esp32/Cargo.toml @@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-executor = { version = "0.7.0", features = ["task-arena-size-32768"] } esp-backtrace = { version = "0.15", features = [ "exception-handler", "panic-handler", "println" ] } -esp-hal = { version = "0.23.0", features = [ "unstable" ] } +esp-hal = { version = "0.23.1", features = [ "unstable" ] } esp-hal-embassy = { version = "0.6.0" } esp-alloc = { version = "0.6.0" } esp-println = { version = "0.13.0", features = ["log"] } @@ -51,3 +51,11 @@ incremental = false lto = 'thin' opt-level = 3 overflow-checks = false + +[patch.crates-io] +esp-wifi = {git = "https://github.com/esp-rs/esp-hal.git", rev = "5d0145eca901f42cbebe1e41cde10e79afba3af8"} +esp-backtrace = {git = "https://github.com/esp-rs/esp-hal.git", rev = "5d0145eca901f42cbebe1e41cde10e79afba3af8"} +esp-hal = {git = "https://github.com/esp-rs/esp-hal.git", rev = "5d0145eca901f42cbebe1e41cde10e79afba3af8"} +esp-hal-embassy = {git = "https://github.com/esp-rs/esp-hal.git", rev = "5d0145eca901f42cbebe1e41cde10e79afba3af8"} +esp-alloc = {git = "https://github.com/esp-rs/esp-hal.git", rev = "5d0145eca901f42cbebe1e41cde10e79afba3af8"} +esp-println = {git = "https://github.com/esp-rs/esp-hal.git", rev = "5d0145eca901f42cbebe1e41cde10e79afba3af8"} \ No newline at end of file From 90756d3f176a9b9ccdae6584d68b526f7f902039 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Fri, 17 Jan 2025 17:48:36 +0100 Subject: [PATCH 16/20] tests: disable esp32 hil and fix false passing test --- examples/esp32/src/consts.rs | 2 +- examples/tests/src/probe/mod.rs | 34 +++++++++++++------- examples/tests/tests/ble_l2cap_central.rs | 24 ++++++++------ examples/tests/tests/ble_l2cap_peripheral.rs | 26 ++++++++------- 4 files changed, 54 insertions(+), 32 deletions(-) diff --git a/examples/esp32/src/consts.rs b/examples/esp32/src/consts.rs index 934e4fa2..f964255f 100644 --- a/examples/esp32/src/consts.rs +++ b/examples/esp32/src/consts.rs @@ -1,6 +1,6 @@ /// Size of L2CAP packets #[cfg(not(any(feature = "esp32c2", feature = "esp32c6", feature = "esp32h2")))] -pub const L2CAP_MTU: usize = 128; +pub const L2CAP_MTU: usize = 251; // Some esp chips only accept an MTU >= 255 #[cfg(any(feature = "esp32c2", feature = "esp32c6", feature = "esp32h2"))] pub const L2CAP_MTU: usize = 255; diff --git a/examples/tests/src/probe/mod.rs b/examples/tests/src/probe/mod.rs index b81d829e..df369ad3 100644 --- a/examples/tests/src/probe/mod.rs +++ b/examples/tests/src/probe/mod.rs @@ -33,17 +33,28 @@ impl<'d> DeviceUnderTest<'d> { } pub async fn run(self, firmware: String) -> Result { - let mut flasher = Command::new("probe-rs") - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .arg("run") - .arg(&firmware) - .arg("--chip") - .arg(&self.target.config().chip) - .arg("--probe") - .arg(&self.target.config().probe) - .spawn() - .unwrap(); + let mut flasher = if self.target.config().chip.starts_with("esp32") { + Command::new("espflash") + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .arg("flash") + .arg(&firmware) + .arg("--monitor") + .spawn() + .unwrap() + } else { + Command::new("probe-rs") + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .arg("run") + .arg(&firmware) + .arg("--chip") + .arg(&self.target.config().chip) + .arg("--probe") + .arg(&self.target.config().probe) + .spawn() + .unwrap() + }; let stdout = flasher.stdout.take().unwrap(); let stderr = flasher.stderr.take().unwrap(); @@ -53,6 +64,7 @@ impl<'d> DeviceUnderTest<'d> { let mut lines: Vec = Vec::new(); select! { r = flasher.wait() => { + log::warn!("flasher exited unexpectedly: {:?}", r); for line in lines { log::warn!("{}", line); } diff --git a/examples/tests/tests/ble_l2cap_central.rs b/examples/tests/tests/ble_l2cap_central.rs index 80bd544b..a1a01cc6 100644 --- a/examples/tests/tests/ble_l2cap_central.rs +++ b/examples/tests/tests/ble_l2cap_central.rs @@ -1,4 +1,4 @@ -use futures::future::select; +use futures::future::join; use std::time::Duration; use tokio::select; use trouble_example_tests::{serial, TestContext}; @@ -17,6 +17,7 @@ async fn ble_l2cap_central_nrf52() { .await; } +/* #[tokio::test] async fn ble_l2cap_central_esp32c3() { let _ = pretty_env_logger::try_init(); @@ -29,18 +30,18 @@ async fn ble_l2cap_central_esp32c3() { )) .await; } +*/ async fn run_l2cap_central_test(labels: &[(&str, &str)], firmware: &str) { let ctx = TestContext::new(); let peripheral = ctx.serial_adapters[0].clone(); let dut = ctx.find_dut(labels).unwrap(); - - // Flash the binary to the target let token = dut.token(); + let token2 = token.clone(); // Spawn a runner for the target - let central = tokio::task::spawn_local(dut.run(firmware.to_string())); + let mut dut = tokio::task::spawn_local(dut.run(firmware.to_string())); // Run the central in the test using the serial adapter to verify let peripheral_address: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]); @@ -107,11 +108,16 @@ async fn run_l2cap_central_test(labels: &[(&str, &str)], firmware: &str) { } }); - tokio::time::timeout(Duration::from_secs(60), select(central, peripheral)) - .await - .map_err(|_| { + match tokio::time::timeout(Duration::from_secs(30), join(&mut dut, peripheral)).await { + Err(_) => { println!("Test timed out"); + token2.cancel(); + let _ = tokio::time::timeout(Duration::from_secs(1), dut).await; assert!(false); - }) - .unwrap(); + } + Ok((c, p)) => { + p.expect("peripheral failed").unwrap(); + c.expect("central failed").unwrap(); + } + } } diff --git a/examples/tests/tests/ble_l2cap_peripheral.rs b/examples/tests/tests/ble_l2cap_peripheral.rs index 0c112158..726947dc 100644 --- a/examples/tests/tests/ble_l2cap_peripheral.rs +++ b/examples/tests/tests/ble_l2cap_peripheral.rs @@ -1,4 +1,4 @@ -use futures::future::select; +use futures::future::join; use std::time::Duration; use tokio::select; use trouble_example_tests::{serial, TestContext}; @@ -17,7 +17,7 @@ async fn ble_l2cap_peripheral_nrf52() { .await; } -#[tokio::test] +/*#[tokio::test] async fn ble_l2cap_peripheral_esp32c3() { let _ = pretty_env_logger::try_init(); let firmware = "bins/esp32/ble_l2cap_peripheral"; @@ -28,19 +28,18 @@ async fn ble_l2cap_peripheral_esp32c3() { firmware, )) .await; -} +}*/ async fn run_l2cap_peripheral_test(labels: &[(&str, &str)], firmware: &str) { let ctx = TestContext::new(); let central = ctx.serial_adapters[0].clone(); let dut = ctx.find_dut(labels).unwrap(); - - // Flash the binary to the target let token = dut.token(); + let token2 = token.clone(); // Spawn a runner for the target - let peripheral = tokio::task::spawn_local(dut.run(firmware.to_string())); + let mut dut = tokio::task::spawn_local(dut.run(firmware.to_string())); // Run the central in the test using the serial adapter to verify let peripheral_address: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]); @@ -92,11 +91,16 @@ async fn run_l2cap_peripheral_test(labels: &[(&str, &str)], firmware: &str) { } }); - tokio::time::timeout(Duration::from_secs(60), select(peripheral, central)) - .await - .map_err(|_| { + match tokio::time::timeout(Duration::from_secs(30), join(&mut dut, central)).await { + Err(_) => { println!("Test timed out"); + token2.cancel(); + let _ = tokio::time::timeout(Duration::from_secs(1), dut).await; assert!(false); - }) - .unwrap(); + } + Ok((p, c)) => { + p.expect("peripheral failed").unwrap(); + c.expect("central failed").unwrap(); + } + } } From 0d179c7499cdd39a9c3951d65e8932ebae0ea87d Mon Sep 17 00:00:00 2001 From: petekubiak <37507920+petekubiak@users.noreply.github.com> Date: Mon, 20 Jan 2025 13:02:43 +0000 Subject: [PATCH 17/20] Add paths to types in server and service macros (#254) --- host-macros/src/characteristic.rs | 2 +- host-macros/src/server.rs | 28 ++++++++++----------- host-macros/src/service.rs | 42 +++++++++++++++++++++---------- 3 files changed, 44 insertions(+), 28 deletions(-) diff --git a/host-macros/src/characteristic.rs b/host-macros/src/characteristic.rs index 05200fd3..68c37448 100644 --- a/host-macros/src/characteristic.rs +++ b/host-macros/src/characteristic.rs @@ -106,7 +106,7 @@ pub fn parse_uuid(meta: &ParseNestedMeta<'_>) -> Result { let span = expr.span(); // span will highlight if the value does not impl Into Ok(quote::quote_spanned! { span => { - let uuid: Uuid = #expr.into(); + let uuid: trouble_host::types::uuid::Uuid = #expr.into(); uuid } }) diff --git a/host-macros/src/server.rs b/host-macros/src/server.rs index c0f7b121..bb33d9ca 100644 --- a/host-macros/src/server.rs +++ b/host-macros/src/server.rs @@ -101,19 +101,19 @@ impl ServerBuilder { let attribute_table_size = if let Some(value) = self.arguments.attribute_table_size { value } else { - parse_quote!(GAP_SERVICE_ATTRIBUTE_COUNT #code_attribute_summation) + parse_quote!(trouble_host::gap::GAP_SERVICE_ATTRIBUTE_COUNT #code_attribute_summation) }; quote! { const _ATTRIBUTE_TABLE_SIZE: usize = #attribute_table_size; // This pattern causes the assertion to happen at compile time const _: () = { - core::assert!(_ATTRIBUTE_TABLE_SIZE >= GAP_SERVICE_ATTRIBUTE_COUNT #code_attribute_summation, "Specified attribute table size is insufficient. Please increase attribute_table_size or remove the argument entirely to allow automatic sizing of the attribute table."); + core::assert!(_ATTRIBUTE_TABLE_SIZE >= trouble_host::gap::GAP_SERVICE_ATTRIBUTE_COUNT #code_attribute_summation, "Specified attribute table size is insufficient. Please increase attribute_table_size or remove the argument entirely to allow automatic sizing of the attribute table."); }; #visibility struct #name<'values> { - server: AttributeServer<'values, #mutex_type, _ATTRIBUTE_TABLE_SIZE>, + server: trouble_host::prelude::AttributeServer<'values, #mutex_type, _ATTRIBUTE_TABLE_SIZE>, #code_service_definition } @@ -122,12 +122,12 @@ impl ServerBuilder { /// Create a new Gatt Server instance. /// /// Requires you to add your own GAP Service. Use `new_default(name)` or `new_with_config(name, gap_config)` if you want to add a GAP Service. - #visibility fn new(mut table: AttributeTable<'values, #mutex_type, _ATTRIBUTE_TABLE_SIZE>) -> Self { + #visibility fn new(mut table: trouble_host::attribute::AttributeTable<'values, #mutex_type, _ATTRIBUTE_TABLE_SIZE>) -> Self { #code_service_init Self { - server: AttributeServer::new(table), + server: trouble_host::prelude::AttributeServer::new(table), #code_server_populate } } @@ -137,14 +137,14 @@ impl ServerBuilder { /// The maximum length which the name can be is 22 bytes (limited by the size of the advertising packet). /// If a name longer than this is passed, Err() is returned. #visibility fn new_default(name: &'values str) -> Result { - let mut table: AttributeTable<'_, #mutex_type, _ATTRIBUTE_TABLE_SIZE> = AttributeTable::new(); + let mut table: trouble_host::attribute::AttributeTable<'_, #mutex_type, _ATTRIBUTE_TABLE_SIZE> = trouble_host::attribute::AttributeTable::new(); - GapConfig::default(name).build(&mut table)?; + trouble_host::gap::GapConfig::default(name).build(&mut table)?; #code_service_init Ok(Self { - server: AttributeServer::new(table), + server: trouble_host::prelude::AttributeServer::new(table), #code_server_populate }) } @@ -154,31 +154,31 @@ impl ServerBuilder { /// This function will add a GAP Service. /// The maximum length which the device name can be is 22 bytes (limited by the size of the advertising packet). /// If a name longer than this is passed, Err() is returned. - #visibility fn new_with_config(gap: GapConfig<'values>) -> Result { - let mut table: AttributeTable<'_, #mutex_type, _ATTRIBUTE_TABLE_SIZE> = AttributeTable::new(); + #visibility fn new_with_config(gap: trouble_host::gap::GapConfig<'values>) -> Result { + let mut table: trouble_host::attribute::AttributeTable<'_, #mutex_type, _ATTRIBUTE_TABLE_SIZE> = trouble_host::attribute::AttributeTable::new(); gap.build(&mut table)?; #code_service_init Ok(Self { - server: AttributeServer::new(table), + server: trouble_host::prelude::AttributeServer::new(table), #code_server_populate }) } - #visibility fn get(&self, characteristic: &Characteristic) -> Result { + #visibility fn get(&self, characteristic: &trouble_host::attribute::Characteristic) -> Result { self.server.table().get(characteristic) } - #visibility fn set(&self, characteristic: &Characteristic, input: &T) -> Result<(), Error> { + #visibility fn set(&self, characteristic: &trouble_host::attribute::Characteristic, input: &T) -> Result<(), trouble_host::Error> { self.server.table().set(characteristic, input) } } impl<'values> core::ops::Deref for #name<'values> { - type Target = AttributeServer<'values, #mutex_type, _ATTRIBUTE_TABLE_SIZE>; + type Target = trouble_host::prelude::AttributeServer<'values, #mutex_type, _ATTRIBUTE_TABLE_SIZE>; fn deref(&self) -> &Self::Target { &self.server diff --git a/host-macros/src/service.rs b/host-macros/src/service.rs index 13e5ca7d..d9bc6e65 100644 --- a/host-macros/src/service.rs +++ b/host-macros/src/service.rs @@ -51,7 +51,7 @@ fn parse_arg_uuid(value: &Expr) -> Result { let span = other.span(); // span will highlight if the value does not impl Into Ok(quote::quote_spanned! { span => { - let uuid: Uuid = #other.into(); + let uuid: trouble_host::types::uuid::Uuid = #other.into(); uuid } }) @@ -153,18 +153,18 @@ impl ServiceBuilder { quote! { #visibility struct #struct_name { #fields - handle: AttributeHandle, + handle: trouble_host::attribute::AttributeHandle, } #[allow(unused)] impl #struct_name { #visibility const ATTRIBUTE_COUNT: usize = #attribute_count; - #visibility fn new(table: &mut AttributeTable<'_, M, MAX_ATTRIBUTES>) -> Self + #visibility fn new(table: &mut trouble_host::attribute::AttributeTable<'_, M, MAX_ATTRIBUTES>) -> Self where M: embassy_sync::blocking_mutex::raw::RawMutex, { - let mut service = table.add_service(Service::new(#uuid)); + let mut service = table.add_service(trouble_host::attribute::Service::new(#uuid)); #code_build_chars Self { @@ -193,10 +193,10 @@ impl ServiceBuilder { self.code_build_chars.extend(quote_spanned! {characteristic.span=> let #char_name = { - static #name_screaming: static_cell::StaticCell<[u8; <#ty as GattValue>::MAX_SIZE]> = static_cell::StaticCell::new(); + static #name_screaming: static_cell::StaticCell<[u8; <#ty as trouble_host::types::gatt_traits::GattValue>::MAX_SIZE]> = static_cell::StaticCell::new(); let mut val = <#ty>::default(); // constrain the type of the value here val = #default_value; // update the temporary value with our new default - let store = #name_screaming.init([0; <#ty as GattValue>::MAX_SIZE]); + let store = #name_screaming.init([0; <#ty as trouble_host::types::gatt_traits::GattValue>::MAX_SIZE]); let mut builder = service .add_characteristic(#uuid, &[#(#properties),*], val, store); #descriptors @@ -235,7 +235,7 @@ impl ServiceBuilder { // add fields for each characteristic value handle fields.push(syn::Field { ident: Some(char_name.clone()), - ty: syn::Type::Verbatim(quote!(Characteristic<#ty>)), + ty: syn::Type::Verbatim(quote!(trouble_host::attribute::Characteristic<#ty>)), attrs: Vec::new(), colon_token: Default::default(), vis: ch.vis.clone(), @@ -300,7 +300,7 @@ impl ServiceBuilder { const CAPACITY: u8 = if (#capacity) < 16 { 16 } else { #capacity }; // minimum capacity is 16 bytes static #name_screaming: static_cell::StaticCell<[u8; CAPACITY as usize]> = static_cell::StaticCell::new(); let store = #name_screaming.init([0; CAPACITY as usize]); - let value = GattValue::to_gatt(&value); + let value = trouble_host::types::gatt_traits::GattValue::to_gatt(&value); store[..value.len()].copy_from_slice(value); builder.add_descriptor( #uuid, @@ -323,14 +323,30 @@ fn parse_property_into_list(property: bool, variant: TokenStream2, properties: & /// Parse the properties of a characteristic and return a list of properties fn set_access_properties(args: &AccessArgs) -> Vec { let mut properties = Vec::new(); - parse_property_into_list(args.read, quote! {CharacteristicProp::Read}, &mut properties); - parse_property_into_list(args.write, quote! {CharacteristicProp::Write}, &mut properties); + parse_property_into_list( + args.read, + quote! {trouble_host::attribute::CharacteristicProp::Read}, + &mut properties, + ); + parse_property_into_list( + args.write, + quote! {trouble_host::attribute::CharacteristicProp::Write}, + &mut properties, + ); parse_property_into_list( args.write_without_response, - quote! {CharacteristicProp::WriteWithoutResponse}, + quote! {trouble_host::attribute::CharacteristicProp::WriteWithoutResponse}, + &mut properties, + ); + parse_property_into_list( + args.notify, + quote! {trouble_host::attribute::CharacteristicProp::Notify}, + &mut properties, + ); + parse_property_into_list( + args.indicate, + quote! {trouble_host::attribute::CharacteristicProp::Indicate}, &mut properties, ); - parse_property_into_list(args.notify, quote! {CharacteristicProp::Notify}, &mut properties); - parse_property_into_list(args.indicate, quote! {CharacteristicProp::Indicate}, &mut properties); properties } From c113df2154ed47a2d3b340883668b3586d4662e3 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Mon, 20 Jan 2025 13:11:45 +0100 Subject: [PATCH 18/20] refactor: remove C from HostResources * Refactor API to remove C: Controller from HostResources * Remove Clone + Copy on Stack and use it to hold the BleHost * Remove all 'internal' lifetime of elements within channels. Guarantee their lifetime in types that is handed ownership of elements from channels. * Simplify packet pool, removing the QoS flag. Issue #252 --- examples/apps/src/ble_advertise_multiple.rs | 8 +- examples/apps/src/ble_bas_central.rs | 10 +- examples/apps/src/ble_bas_peripheral.rs | 12 +- examples/apps/src/ble_l2cap_central.rs | 15 +- examples/apps/src/ble_l2cap_peripheral.rs | 15 +- examples/tests/tests/ble_l2cap_central.rs | 13 +- examples/tests/tests/ble_l2cap_peripheral.rs | 12 +- host/src/central.rs | 74 +---- host/src/channel_manager.rs | 45 ++- host/src/connection.rs | 127 ++++++-- host/src/connection_manager.rs | 65 ++-- host/src/gatt.rs | 179 ++++------- host/src/host.rs | 54 ++-- host/src/l2cap.rs | 20 +- host/src/l2cap/sar.rs | 20 +- host/src/lib.rs | 157 +++++----- host/src/packet_pool.rs | 299 ++++--------------- host/src/pdu.rs | 12 +- host/src/peripheral.rs | 10 +- host/src/scan.rs | 8 +- host/tests/common.rs | 1 + host/tests/gatt.rs | 17 +- host/tests/gatt_derive.rs | 19 +- host/tests/l2cap.rs | 28 +- 24 files changed, 508 insertions(+), 712 deletions(-) diff --git a/examples/apps/src/ble_advertise_multiple.rs b/examples/apps/src/ble_advertise_multiple.rs index 36cef61b..874ab6e9 100644 --- a/examples/apps/src/ble_advertise_multiple.rs +++ b/examples/apps/src/ble_advertise_multiple.rs @@ -24,11 +24,9 @@ where let address: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]); info!("Our address = {:?}", address); - let mut resources: HostResources = - HostResources::new(PacketQos::None); - let (_, mut peripheral, _, mut runner) = trouble_host::new(controller, &mut resources) - .set_random_address(address) - .build(); + let mut resources: HostResources = HostResources::new(); + let stack = trouble_host::new(controller, &mut resources).set_random_address(address); + let (mut peripheral, _, mut runner) = stack.build(); let mut adv_data = [0; 31]; let len = AdStructure::encode_slice( diff --git a/examples/apps/src/ble_bas_central.rs b/examples/apps/src/ble_bas_central.rs index 6b1c2e3e..2b260563 100644 --- a/examples/apps/src/ble_bas_central.rs +++ b/examples/apps/src/ble_bas_central.rs @@ -17,11 +17,9 @@ where let address: Address = Address::random([0xff, 0x8f, 0x1b, 0x05, 0xe4, 0xff]); info!("Our address = {:?}", address); - let mut resources: HostResources = - HostResources::new(PacketQos::None); - let (stack, _, mut central, mut runner) = trouble_host::new(controller, &mut resources) - .set_random_address(address) - .build(); + let mut resources: HostResources = HostResources::new(); + let stack = trouble_host::new(controller, &mut resources).set_random_address(address); + let (_, 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 @@ -42,7 +40,7 @@ where let conn = central.connect(&config).await.unwrap(); info!("Connected, creating gatt client"); - let client = GattClient::::new(stack, &conn).await.unwrap(); + let client = GattClient::::new(&stack, &conn).await.unwrap(); let _ = join(client.task(), async { info!("Looking for battery service"); diff --git a/examples/apps/src/ble_bas_peripheral.rs b/examples/apps/src/ble_bas_peripheral.rs index 366db80b..fab0cd0c 100644 --- a/examples/apps/src/ble_bas_peripheral.rs +++ b/examples/apps/src/ble_bas_peripheral.rs @@ -38,11 +38,9 @@ where let address: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]); info!("Our address = {:?}", address); - let mut resources: HostResources = - HostResources::new(PacketQos::None); - let (stack, mut peripheral, _, runner) = trouble_host::new(controller, &mut resources) - .set_random_address(address) - .build(); + let mut resources: HostResources = HostResources::new(); + let stack = trouble_host::new(controller, &mut resources).set_random_address(address); + let (mut peripheral, _, runner) = stack.build(); info!("Starting advertising and GATT service"); let server = Server::new_with_config(GapConfig::Peripheral(PeripheralConfig { @@ -57,7 +55,7 @@ where Ok(conn) => { // set up tasks when the connection is established to a central, so they don't run when no one is connected. let a = gatt_events_task(&server, &conn); - let b = custom_task(&server, &conn, stack); + let b = custom_task(&server, &conn, &stack); // run until any task ends (usually because the connection has been closed), // then return to advertising state. select(a, b).await; @@ -176,7 +174,7 @@ async fn advertise<'a, C: Controller>( /// This task will notify the connected central of a counter value every 2 seconds. /// It will also read the RSSI value every 2 seconds. /// and will stop when the connection is closed by the central or an error occurs. -async fn custom_task(server: &Server<'_>, conn: &Connection<'_>, stack: Stack<'_, C>) { +async fn custom_task(server: &Server<'_>, conn: &Connection<'_>, stack: &Stack<'_, C>) { let mut tick: u8 = 0; let level = server.battery_service.level; loop { diff --git a/examples/apps/src/ble_l2cap_central.rs b/examples/apps/src/ble_l2cap_central.rs index fa1caf06..96e8b47c 100644 --- a/examples/apps/src/ble_l2cap_central.rs +++ b/examples/apps/src/ble_l2cap_central.rs @@ -17,12 +17,9 @@ where let address: Address = Address::random([0xff, 0x8f, 0x1b, 0x05, 0xe4, 0xff]); info!("Our address = {:?}", address); - let mut resources: HostResources = - HostResources::new(PacketQos::None); - - let (stack, _, mut central, mut runner) = trouble_host::new(controller, &mut resources) - .set_random_address(address) - .build(); + let mut resources: HostResources = HostResources::new(); + let stack = trouble_host::new(controller, &mut resources).set_random_address(address); + let (_, 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 @@ -42,18 +39,18 @@ where let conn = central.connect(&config).await.unwrap(); info!("Connected, creating l2cap channel"); const PAYLOAD_LEN: usize = 27; - let mut ch1 = L2capChannel::create(stack, &conn, 0x2349, &Default::default()) + let mut ch1 = L2capChannel::create(&stack, &conn, 0x2349, &Default::default()) .await .unwrap(); info!("New l2cap channel created, sending some data!"); for i in 0..10 { let tx = [i; PAYLOAD_LEN]; - ch1.send::<_, PAYLOAD_LEN>(stack, &tx).await.unwrap(); + ch1.send::<_, PAYLOAD_LEN>(&stack, &tx).await.unwrap(); } info!("Sent data, waiting for them to be sent back"); let mut rx = [0; PAYLOAD_LEN]; for i in 0..10 { - let len = ch1.receive(stack, &mut rx).await.unwrap(); + let len = ch1.receive(&stack, &mut rx).await.unwrap(); assert_eq!(len, rx.len()); assert_eq!(rx, [i; PAYLOAD_LEN]); } diff --git a/examples/apps/src/ble_l2cap_peripheral.rs b/examples/apps/src/ble_l2cap_peripheral.rs index f66389c7..483f5ce2 100644 --- a/examples/apps/src/ble_l2cap_peripheral.rs +++ b/examples/apps/src/ble_l2cap_peripheral.rs @@ -12,16 +12,13 @@ pub async fn run(controller: C) where C: Controller, { - let mut resources: HostResources = - HostResources::new(PacketQos::None); - // Hardcoded peripheral address let address: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]); info!("Our address = {:?}", address); - let (stack, mut peripheral, _, mut runner) = trouble_host::new(controller, &mut resources) - .set_random_address(address) - .build(); + let mut resources: HostResources = HostResources::new(); + let stack = trouble_host::new(controller, &mut resources).set_random_address(address); + let (mut peripheral, _, mut runner) = stack.build(); let mut adv_data = [0; 31]; AdStructure::encode_slice( @@ -50,7 +47,7 @@ where info!("Connection established"); - let mut ch1 = L2capChannel::accept(stack, &conn, &[0x2349], &Default::default()) + let mut ch1 = L2capChannel::accept(&stack, &conn, &[0x2349], &Default::default()) .await .unwrap(); @@ -60,7 +57,7 @@ where const PAYLOAD_LEN: usize = 27; let mut rx = [0; PAYLOAD_LEN]; for i in 0..10 { - let len = ch1.receive(stack, &mut rx).await.unwrap(); + let len = ch1.receive(&stack, &mut rx).await.unwrap(); assert_eq!(len, rx.len()); assert_eq!(rx, [i; PAYLOAD_LEN]); } @@ -69,7 +66,7 @@ where Timer::after(Duration::from_secs(1)).await; for i in 0..10 { let tx = [i; PAYLOAD_LEN]; - ch1.send::<_, PAYLOAD_LEN>(stack, &tx).await.unwrap(); + ch1.send::<_, PAYLOAD_LEN>(&stack, &tx).await.unwrap(); } info!("L2CAP data echoed"); diff --git a/examples/tests/tests/ble_l2cap_central.rs b/examples/tests/tests/ble_l2cap_central.rs index a1a01cc6..c734acc7 100644 --- a/examples/tests/tests/ble_l2cap_central.rs +++ b/examples/tests/tests/ble_l2cap_central.rs @@ -48,10 +48,9 @@ async fn run_l2cap_central_test(labels: &[(&str, &str)], firmware: &str) { let peripheral = tokio::task::spawn_local(async move { let controller_peripheral = serial::create_controller(&peripheral).await; - let mut resources: HostResources = HostResources::new(PacketQos::None); - let (stack, mut peripheral, _central, mut runner) = trouble_host::new(controller_peripheral, &mut resources) - .set_random_address(peripheral_address) - .build(); + let mut resources: HostResources<2, 4, 27> = HostResources::new(); + let stack = trouble_host::new(controller_peripheral, &mut resources).set_random_address(peripheral_address); + let (mut peripheral, _central, mut runner) = stack.build(); select! { r = runner.run() => { @@ -79,7 +78,7 @@ async fn run_l2cap_central_test(labels: &[(&str, &str)], firmware: &str) { let conn = acceptor.accept().await?; println!("[peripheral] connected"); - let mut ch1 = L2capChannel::accept(stack, &conn, &[0x2349], &Default::default()).await?; + let mut ch1 = L2capChannel::accept(&stack, &conn, &[0x2349], &Default::default()).await?; println!("[peripheral] channel created"); @@ -87,7 +86,7 @@ async fn run_l2cap_central_test(labels: &[(&str, &str)], firmware: &str) { // Size of payload we're expecting let mut rx = [0; PAYLOAD_LEN]; for i in 0..10 { - let len = ch1.receive(stack, &mut rx).await?; + let len = ch1.receive(&stack, &mut rx).await?; assert_eq!(len, rx.len()); assert_eq!(rx, [i; PAYLOAD_LEN]); } @@ -95,7 +94,7 @@ async fn run_l2cap_central_test(labels: &[(&str, &str)], firmware: &str) { for i in 0..10 { let tx = [i; PAYLOAD_LEN]; - ch1.send::<_, PAYLOAD_LEN>(stack, &tx).await?; + ch1.send::<_, PAYLOAD_LEN>(&stack, &tx).await?; } println!("[peripheral] data sent"); token.cancel(); diff --git a/examples/tests/tests/ble_l2cap_peripheral.rs b/examples/tests/tests/ble_l2cap_peripheral.rs index 726947dc..05fede83 100644 --- a/examples/tests/tests/ble_l2cap_peripheral.rs +++ b/examples/tests/tests/ble_l2cap_peripheral.rs @@ -45,9 +45,9 @@ async fn run_l2cap_peripheral_test(labels: &[(&str, &str)], firmware: &str) { let peripheral_address: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]); let central = tokio::task::spawn_local(async move { let controller_central = serial::create_controller(¢ral).await; - let mut resources: HostResources = HostResources::new(PacketQos::None); - let (stack, _peripheral, mut central, mut runner) = - trouble_host::new(controller_central, &mut resources).build(); + let mut resources: HostResources<2, 4, 27> = HostResources::new(); + let stack = trouble_host::new(controller_central, &mut resources); + let (_peripheral, mut central, mut runner) = stack.build(); select! { r = runner.run() => { r @@ -67,16 +67,16 @@ async fn run_l2cap_peripheral_test(labels: &[(&str, &str)], firmware: &str) { let conn = central.connect(&config).await.unwrap(); log::info!("[central] connected"); const PAYLOAD_LEN: usize = 27; - let mut ch1 = L2capChannel::create(stack, &conn, 0x2349, &Default::default()).await?; + let mut ch1 = L2capChannel::create(&stack, &conn, 0x2349, &Default::default()).await?; log::info!("[central] channel created"); for i in 0..10 { let tx = [i; PAYLOAD_LEN]; - ch1.send::<_, PAYLOAD_LEN>(stack, &tx).await?; + ch1.send::<_, PAYLOAD_LEN>(&stack, &tx).await?; } log::info!("[central] data sent"); let mut rx = [0; PAYLOAD_LEN]; for i in 0..10 { - let len = ch1.receive(stack, &mut rx).await?; + let len = ch1.receive(&stack, &mut rx).await?; assert_eq!(len, rx.len()); assert_eq!(rx, [i; PAYLOAD_LEN]); } diff --git a/host/src/central.rs b/host/src/central.rs index f3249694..446c1ce0 100644 --- a/host/src/central.rs +++ b/host/src/central.rs @@ -1,5 +1,5 @@ //! Functionality for the BLE central role. -use crate::connection::{ConnectConfig, Connection}; +use crate::connection::{ConnectConfig, Connection, PhySet}; use crate::{BleHostError, Error, Stack}; use bt_hci::cmd::le::{LeAddDeviceToFilterAcceptList, LeClearFilterAcceptList, LeCreateConn, LeExtCreateConn}; use bt_hci::controller::{Controller, ControllerCmdAsync, ControllerCmdSync}; @@ -7,20 +7,19 @@ use bt_hci::param::{AddrKind, BdAddr, InitiatingPhy, LeConnRole, PhyParams}; #[cfg(feature = "controller-host-flow-control")] use bt_hci::param::{ConnHandleCompletedPackets, ControllerToHostFlowControl}; use embassy_futures::select::{select, Either}; -use embassy_time::Duration; /// A type implementing the BLE central role. -pub struct Central<'d, C: Controller> { - pub(crate) stack: Stack<'d, C>, +pub struct Central<'stack, C: Controller> { + pub(crate) stack: &'stack Stack<'stack, C>, } -impl<'d, C: Controller> Central<'d, C> { - pub(crate) fn new(stack: Stack<'d, C>) -> Self { +impl<'stack, C: Controller> Central<'stack, C> { + pub(crate) fn new(stack: &'stack Stack<'stack, C>) -> Self { Self { stack } } /// Attempt to create a connection with the provided config. - pub async fn connect(&mut self, config: &ConnectConfig<'_>) -> Result, BleHostError> + pub async fn connect(&mut self, config: &ConnectConfig<'_>) -> Result, BleHostError> where C: ControllerCmdSync + ControllerCmdSync @@ -30,7 +29,7 @@ impl<'d, C: Controller> Central<'d, C> { return Err(Error::InvalidValue.into()); } - let host = self.stack.host; + let host = &self.stack.host; let _drop = crate::host::OnDrop::new(|| { host.connect_command_state.cancel(true); }); @@ -70,7 +69,10 @@ impl<'d, C: Controller> Central<'d, C> { } /// Attempt to create a connection with the provided config. - pub async fn connect_ext(&mut self, config: &ConnectConfig<'_>) -> Result, BleHostError> + pub async fn connect_ext( + &mut self, + config: &ConnectConfig<'_>, + ) -> Result, BleHostError> where C: ControllerCmdSync + ControllerCmdSync @@ -80,7 +82,7 @@ impl<'d, C: Controller> Central<'d, C> { return Err(Error::InvalidValue.into()); } - let host = self.stack.host; + let host = &self.stack.host; // Ensure no other connect ongoing. let _drop = crate::host::OnDrop::new(|| { host.connect_command_state.cancel(true); @@ -133,7 +135,7 @@ impl<'d, C: Controller> Central<'d, C> { where C: ControllerCmdSync + ControllerCmdSync, { - let host = self.stack.host; + let host = &self.stack.host; host.command(LeClearFilterAcceptList::new()).await?; for entry in filter_accept_list { host.command(LeAddDeviceToFilterAcceptList::new(entry.0, *entry.1)) @@ -160,53 +162,3 @@ pub(crate) fn create_phy_params(phy: P, phys: PhySet) -> PhyParams

{ }; phy_params } - -/// Scanner configuration. -pub struct ScanConfig<'d> { - /// Active scanning. - pub active: bool, - /// List of addresses to accept. - pub filter_accept_list: &'d [(AddrKind, &'d BdAddr)], - /// PHYs to scan on. - pub phys: PhySet, - /// Scan interval. - pub interval: Duration, - /// Scan window. - pub window: Duration, - /// Scan timeout. - pub timeout: Duration, -} - -impl Default for ScanConfig<'_> { - fn default() -> Self { - Self { - active: true, - filter_accept_list: &[], - phys: PhySet::M1, - interval: Duration::from_secs(1), - window: Duration::from_secs(1), - timeout: Duration::from_secs(0), - } - } -} - -/// PHYs to scan on. -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[derive(Eq, PartialEq, Copy, Clone)] -#[repr(u8)] -pub enum PhySet { - /// 1Mbps phy - M1 = 1, - /// 2Mbps phy - M2 = 2, - /// 1Mbps + 2Mbps phys - M1M2 = 3, - /// Coded phy (125kbps, S=8) - Coded = 4, - /// 1Mbps and Coded phys - M1Coded = 5, - /// 2Mbps and Coded phys - M2Coded = 6, - /// 1Mbps, 2Mbps and Coded phys - M1M2Coded = 7, -} diff --git a/host/src/channel_manager.rs b/host/src/channel_manager.rs index 509885cf..9fe5f7ea 100644 --- a/host/src/channel_manager.rs +++ b/host/src/channel_manager.rs @@ -12,13 +12,13 @@ use embassy_sync::waitqueue::WakerRegistration; use crate::cursor::WriteCursor; use crate::host::{AclSender, BleHost}; use crate::l2cap::L2capChannel; -use crate::packet_pool::{AllocId, DynamicPacketPool, Packet}; +use crate::packet_pool::{Packet, Pool}; use crate::pdu::Pdu; use crate::types::l2cap::{ CommandRejectRes, ConnParamUpdateReq, ConnParamUpdateRes, DisconnectionReq, DisconnectionRes, L2capHeader, L2capSignalCode, L2capSignalHeader, LeCreditConnReq, LeCreditConnRes, LeCreditConnResultCode, LeCreditFlowInd, }; -use crate::{BleHostError, Error}; +use crate::{config, BleHostError, Error}; const BASE_ID: u16 = 0x40; @@ -31,37 +31,37 @@ struct State<'d> { } /// Channel manager for L2CAP channels used directly by clients. -pub struct ChannelManager<'d, const RXQ: usize> { - pool: &'d dyn DynamicPacketPool<'d>, +pub struct ChannelManager<'d> { + pool: &'d dyn Pool, state: RefCell>, - inbound: &'d mut [PacketChannel<'d, RXQ>], + inbound: &'d mut [PacketChannel<{ config::L2CAP_RX_QUEUE_SIZE }>], } -pub(crate) struct PacketChannel<'d, const QLEN: usize> { - chan: Channel>, QLEN>, +pub(crate) struct PacketChannel { + chan: Channel, QLEN>, } #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct ChannelIndex(u8); -impl<'d, const QLEN: usize> PacketChannel<'d, QLEN> { +impl PacketChannel { #[allow(clippy::declare_interior_mutable_const)] - pub(crate) const NEW: PacketChannel<'d, QLEN> = PacketChannel { chan: Channel::new() }; + pub(crate) const NEW: PacketChannel = PacketChannel { chan: Channel::new() }; pub fn close(&self) -> Result<(), ()> { self.chan.try_send(None).map_err(|_| ()) } - pub async fn send(&self, pdu: Pdu<'d>) { + pub async fn send(&self, pdu: Pdu) { self.chan.send(Some(pdu)).await; } - pub fn try_send(&self, pdu: Pdu<'d>) -> Result<(), Error> { + pub fn try_send(&self, pdu: Pdu) -> Result<(), Error> { self.chan.try_send(Some(pdu)).map_err(|_| Error::OutOfMemory) } - pub async fn receive(&self) -> Option> { + pub async fn receive(&self) -> Option { self.chan.receive().await } @@ -94,11 +94,11 @@ impl State<'_> { } } -impl<'d, const RXQ: usize> ChannelManager<'d, RXQ> { +impl<'d> ChannelManager<'d> { pub fn new( - pool: &'d dyn DynamicPacketPool<'d>, + pool: &'d dyn Pool, channels: &'d mut [ChannelStorage], - inbound: &'d mut [PacketChannel<'d, RXQ>], + inbound: &'d mut [PacketChannel<{ config::L2CAP_RX_QUEUE_SIZE }>], ) -> Self { Self { pool, @@ -178,7 +178,7 @@ impl<'d, const RXQ: usize> ChannelManager<'d, RXQ> { chan.mtu = mtu; chan.flow_control = CreditFlowControl::new( credit_flow, - initial_credits.unwrap_or(self.pool.min_available(AllocId::from_channel(chan.cid)) as u16), + initial_credits.unwrap_or(self.pool.available() as u16), ); chan.state = ChannelState::Connected; let mps = chan.mps; @@ -237,7 +237,7 @@ impl<'d, const RXQ: usize> ChannelManager<'d, RXQ> { // Allocate space for our new channel. let idx = self.alloc(conn, |storage| { cid = storage.cid; - credits = initial_credits.unwrap_or(self.pool.min_available(AllocId::from_channel(storage.cid)) as u16); + credits = initial_credits.unwrap_or(self.pool.available() as u16); storage.psm = psm; storage.mps = mps; storage.mtu = mtu; @@ -300,7 +300,7 @@ impl<'d, const RXQ: usize> ChannelManager<'d, RXQ> { } /// Dispatch an incoming L2CAP packet to the appropriate channel. - pub(crate) fn dispatch(&self, header: L2capHeader, packet: Packet<'d>) -> Result<(), Error> { + pub(crate) fn dispatch(&self, header: L2capHeader, packet: Packet) -> Result<(), Error> { if header.channel < BASE_ID { return Err(Error::InvalidChannelId); } @@ -522,7 +522,7 @@ impl<'d, const RXQ: usize> ChannelManager<'d, RXQ> { Ok(pos) } - async fn receive_pdu(&self, chan: ChannelIndex) -> Result, Error> { + async fn receive_pdu(&self, chan: ChannelIndex) -> Result { match self.inbound[chan.0 as usize].receive().await { Some(pdu) => Ok(pdu), None => Err(Error::ChannelClosed), @@ -625,7 +625,7 @@ impl<'d, const RXQ: usize> ChannelManager<'d, RXQ> { &self, index: ChannelIndex, ble: &BleHost<'d, T>, - mut packet: Packet<'d>, + mut packet: Packet, ) -> Result<(), BleHostError> { let (conn, cid, credits) = self.with_mut(|state| { let chan = &mut state.channels[index.0 as usize]; @@ -800,7 +800,7 @@ pub(crate) trait DynamicChannelManager { fn print(&self, index: ChannelIndex, f: defmt::Formatter); } -impl DynamicChannelManager for ChannelManager<'_, RXQ> { +impl DynamicChannelManager for ChannelManager<'_> { fn inc_ref(&self, index: ChannelIndex) { ChannelManager::inc_ref(self, index) } @@ -1010,12 +1010,11 @@ mod tests { use super::*; use crate::mock_controller::MockController; - use crate::packet_pool::Qos; use crate::HostResources; #[test] fn channel_refcount() { - let mut resources: HostResources = HostResources::new(Qos::None); + let mut resources: HostResources<2, 2, 27> = HostResources::new(); let ble = MockController::new(); let builder = crate::new(ble, &mut resources); diff --git a/host/src/connection.rs b/host/src/connection.rs index 5575386b..47e6a0af 100644 --- a/host/src/connection.rs +++ b/host/src/connection.rs @@ -1,11 +1,11 @@ //! BLE connection. + use bt_hci::cmd::le::LeConnUpdate; use bt_hci::cmd::status::ReadRssi; use bt_hci::controller::{ControllerCmdAsync, ControllerCmdSync}; -use bt_hci::param::{BdAddr, ConnHandle, DisconnectReason, LeConnRole, Status}; +use bt_hci::param::{AddrKind, BdAddr, ConnHandle, DisconnectReason, LeConnRole, Status}; use embassy_time::Duration; -use crate::central::ScanConfig; use crate::connection_manager::ConnectionManager; use crate::pdu::Pdu; use crate::{BleHostError, Error, Stack}; @@ -18,6 +18,56 @@ pub struct ConnectConfig<'d> { pub connect_params: ConnectParams, } +/// Scan/connect configuration. +pub struct ScanConfig<'d> { + /// Active scanning. + pub active: bool, + /// List of addresses to accept. + pub filter_accept_list: &'d [(AddrKind, &'d BdAddr)], + /// PHYs to scan on. + pub phys: PhySet, + /// Scan interval. + pub interval: Duration, + /// Scan window. + pub window: Duration, + /// Scan timeout. + pub timeout: Duration, +} + +impl Default for ScanConfig<'_> { + fn default() -> Self { + Self { + active: true, + filter_accept_list: &[], + phys: PhySet::M1, + interval: Duration::from_secs(1), + window: Duration::from_secs(1), + timeout: Duration::from_secs(0), + } + } +} + +/// PHYs to scan on. +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Eq, PartialEq, Copy, Clone)] +#[repr(u8)] +pub enum PhySet { + /// 1Mbps phy + M1 = 1, + /// 2Mbps phy + M2 = 2, + /// 1Mbps + 2Mbps phys + M1M2 = 3, + /// Coded phy (125kbps, S=8) + Coded = 4, + /// 1Mbps and Coded phys + M1Coded = 5, + /// 2Mbps and Coded phys + M2Coded = 6, + /// 1Mbps, 2Mbps and Coded phys + M1M2Coded = 7, +} + /// Connection parameters. pub struct ConnectParams { /// Minimum connection interval. @@ -32,8 +82,19 @@ pub struct ConnectParams { pub supervision_timeout: Duration, } -/// An event -pub enum ConnectionEvent<'d> { +#[cfg(not(feature = "gatt"))] +/// A connection event. +pub enum ConnectionEvent { + /// Connection disconnected. + Disconnected { + /// The reason (status code) for the disconnect. + reason: Status, + }, +} + +/// A connection event. +#[cfg(feature = "gatt")] +pub enum ConnectionEvent<'stack> { /// Connection disconnected. Disconnected { /// The reason (status code) for the disconnect. @@ -42,11 +103,20 @@ pub enum ConnectionEvent<'d> { /// GATT event. Gatt { /// The event that was returned - #[cfg(feature = "gatt")] - data: crate::gatt::GattData<'d>, - #[cfg(not(feature = "gatt"))] - /// Connection handle for the event - connection: Connection<'d>, + data: crate::gatt::GattData<'stack>, + }, +} + +pub(crate) enum ConnectionEventData { + /// Connection disconnected. + Disconnected { + /// The reason (status code) for the disconnect. + reason: Status, + }, + /// GATT event. + Gatt { + /// The event that was returned + data: Pdu, }, } @@ -65,9 +135,9 @@ impl Default for ConnectParams { /// Handle to a BLE connection. /// /// When the last reference to a connection is dropped, the connection is automatically disconnected. -pub struct Connection<'d> { +pub struct Connection<'stack> { index: u8, - manager: &'d ConnectionManager<'d>, + manager: &'stack ConnectionManager<'stack>, } impl Clone for Connection<'_> { @@ -83,8 +153,8 @@ impl Drop for Connection<'_> { } } -impl<'d> Connection<'d> { - pub(crate) fn new(index: u8, manager: &'d ConnectionManager<'d>) -> Self { +impl<'stack> Connection<'stack> { + pub(crate) fn new(index: u8, manager: &'stack ConnectionManager<'stack>) -> Self { Self { index, manager } } @@ -96,26 +166,41 @@ impl<'d> Connection<'d> { self.manager.get_att_mtu(self.index) } - pub(crate) async fn send(&self, pdu: Pdu<'d>) { + pub(crate) async fn send(&self, pdu: Pdu) { self.manager.send(self.index, pdu).await } - pub(crate) fn try_send(&self, pdu: Pdu<'d>) -> Result<(), Error> { + pub(crate) fn try_send(&self, pdu: Pdu) -> Result<(), Error> { self.manager.try_send(self.index, pdu) } - pub(crate) async fn post_event(&self, event: ConnectionEvent<'d>) { + pub(crate) async fn post_event(&self, event: ConnectionEventData) { self.manager.post_event(self.index, event).await } #[cfg(feature = "gatt")] - pub(crate) fn alloc_tx(&self) -> Result, Error> { + pub(crate) fn alloc_tx(&self) -> Result { self.manager.alloc_tx() } /// Wait for next connection event. - pub async fn next(&self) -> ConnectionEvent<'d> { - self.manager.next(self.index).await + #[cfg(not(feature = "gatt"))] + pub async fn next(&self) -> ConnectionEvent { + match self.manager.next(self.index).await { + ConnectionEventData::Disconnected { reason } => ConnectionEvent::Disconnected { reason }, + ConnectionEventData::Gatt { data } => unreachable!(), + } + } + + /// Wait for next connection event. + #[cfg(feature = "gatt")] + pub async fn next(&self) -> ConnectionEvent<'stack> { + match self.manager.next(self.index).await { + ConnectionEventData::Disconnected { reason } => ConnectionEvent::Disconnected { reason }, + ConnectionEventData::Gatt { data } => ConnectionEvent::Gatt { + data: crate::gatt::GattData::new(data, self.clone()), + }, + } } /// Check if still connected @@ -150,7 +235,7 @@ impl<'d> Connection<'d> { } /// The RSSI value for this connection. - pub async fn rssi(&self, stack: Stack<'_, T>) -> Result> + pub async fn rssi(&self, stack: &Stack<'_, T>) -> Result> where T: ControllerCmdSync, { @@ -162,7 +247,7 @@ impl<'d> Connection<'d> { /// Update connection parameters for this connection. pub async fn update_connection_params( &self, - stack: Stack<'_, T>, + stack: &Stack<'_, T>, params: ConnectParams, ) -> Result<(), BleHostError> where diff --git a/host/src/connection_manager.rs b/host/src/connection_manager.rs index f8286ada..c2e76ac6 100644 --- a/host/src/connection_manager.rs +++ b/host/src/connection_manager.rs @@ -7,9 +7,9 @@ use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_sync::channel::Channel; use embassy_sync::waitqueue::WakerRegistration; -use crate::connection::{Connection, ConnectionEvent}; +use crate::connection::{Connection, ConnectionEventData}; #[cfg(feature = "gatt")] -use crate::packet_pool::{DynamicPacketPool, Packet, GENERIC_ID}; +use crate::packet_pool::{Packet, Pool}; use crate::pdu::Pdu; use crate::{config, Error}; @@ -40,22 +40,45 @@ impl State<'_> { } } -pub(crate) type EventChannel<'d> = Channel, { config::CONNECTION_EVENT_QUEUE_SIZE }>; +pub(crate) struct EventChannel { + chan: Channel, +} + +impl EventChannel { + #[allow(clippy::declare_interior_mutable_const)] + pub(crate) const NEW: EventChannel = EventChannel { chan: Channel::new() }; + + pub async fn receive(&self) -> ConnectionEventData { + self.chan.receive().await + } + + pub async fn send(&self, event: ConnectionEventData) { + self.chan.send(event).await; + } + + pub fn try_send(&self, event: ConnectionEventData) -> Result<(), Error> { + self.chan.try_send(event).map_err(|_| Error::OutOfMemory) + } + + pub fn clear(&self) { + self.chan.clear(); + } +} pub(crate) struct ConnectionManager<'d> { state: RefCell>, - events: &'d mut [EventChannel<'d>], - outbound: Channel), { config::L2CAP_TX_QUEUE_SIZE }>, + events: &'d mut [EventChannel], + outbound: Channel, #[cfg(feature = "gatt")] - tx_pool: &'d dyn DynamicPacketPool<'d>, + tx_pool: &'d dyn Pool, } impl<'d> ConnectionManager<'d> { pub(crate) fn new( connections: &'d mut [ConnectionStorage], - events: &'d mut [EventChannel<'d>], + events: &'d mut [EventChannel], default_att_mtu: u16, - #[cfg(feature = "gatt")] tx_pool: &'d dyn DynamicPacketPool<'d>, + #[cfg(feature = "gatt")] tx_pool: &'d dyn Pool, ) -> Self { Self { state: RefCell::new(State { @@ -94,15 +117,15 @@ impl<'d> ConnectionManager<'d> { }) } - pub(crate) async fn next(&self, index: u8) -> ConnectionEvent<'d> { + pub(crate) async fn next(&self, index: u8) -> ConnectionEventData { self.events[index as usize].receive().await } - pub(crate) async fn post_event(&self, index: u8, event: ConnectionEvent<'d>) { + pub(crate) async fn post_event(&self, index: u8, event: ConnectionEventData) { self.events[index as usize].send(event).await } - pub(crate) fn post_handle_event(&self, handle: ConnHandle, event: ConnectionEvent<'d>) -> Result<(), Error> { + pub(crate) fn post_handle_event(&self, handle: ConnHandle, event: ConnectionEventData) -> Result<(), Error> { let index = self.with_mut(|state| { for (index, entry) in state.connections.iter().enumerate() { if entry.state == ConnectionState::Connected && Some(handle) == entry.handle { @@ -218,7 +241,7 @@ impl<'d> ConnectionManager<'d> { for (idx, storage) in state.connections.iter_mut().enumerate() { if Some(h) == storage.handle && storage.state != ConnectionState::Disconnected { storage.state = ConnectionState::Disconnected; - let _ = self.events[idx].try_send(ConnectionEvent::Disconnected { reason }); + let _ = self.events[idx].try_send(ConnectionEventData::Disconnected { reason }); #[cfg(feature = "connection-metrics")] storage.metrics.reset(); return Ok(()); @@ -417,26 +440,26 @@ impl<'d> ConnectionManager<'d> { self.with_mut(|state| state.connections[index as usize].att_mtu) } - pub(crate) async fn send(&self, index: u8, pdu: Pdu<'d>) { + pub(crate) async fn send(&self, index: u8, pdu: Pdu) { let handle = self.with_mut(|state| state.connections[index as usize].handle.unwrap()); self.outbound.send((handle, pdu)).await } #[cfg(feature = "gatt")] - pub(crate) fn alloc_tx(&self) -> Result, Error> { - self.tx_pool.alloc(GENERIC_ID).ok_or(Error::OutOfMemory) + pub(crate) fn alloc_tx(&self) -> Result { + self.tx_pool.alloc().ok_or(Error::OutOfMemory) } - pub(crate) fn try_send(&self, index: u8, pdu: Pdu<'d>) -> Result<(), Error> { + pub(crate) fn try_send(&self, index: u8, pdu: Pdu) -> Result<(), Error> { let handle = self.with_mut(|state| state.connections[index as usize].handle.unwrap()); self.outbound.try_send((handle, pdu)).map_err(|_| Error::OutOfMemory) } - pub(crate) fn try_outbound(&self, handle: ConnHandle, pdu: Pdu<'d>) -> Result<(), Error> { + pub(crate) fn try_outbound(&self, handle: ConnHandle, pdu: Pdu) -> Result<(), Error> { self.outbound.try_send((handle, pdu)).map_err(|_| Error::OutOfMemory) } - pub(crate) async fn outbound(&self) -> (ConnHandle, Pdu<'d>) { + pub(crate) async fn outbound(&self) -> (ConnHandle, Pdu) { self.outbound.receive().await } @@ -663,7 +686,6 @@ impl Drop for PacketGrant<'_, '_> { mod tests { use super::*; use crate::prelude::PacketPool; - use crate::PacketQos; extern crate std; use std::boxed::Box; @@ -674,8 +696,8 @@ mod tests { fn setup() -> &'static ConnectionManager<'static> { let storage = Box::leak(Box::new([ConnectionStorage::DISCONNECTED; 3])); - let events = Box::leak(Box::new([const { EventChannel::new() }; 3])); - let pool = Box::leak(Box::new(PacketPool::::new(PacketQos::None))); + let events = Box::leak(Box::new([EventChannel::NEW; 3])); + let pool = Box::leak(Box::new(PacketPool::<27, 8>::new())); let mgr = ConnectionManager::new(&mut storage[..], &mut events[..], 23, pool); Box::leak(Box::new(mgr)) } @@ -820,6 +842,7 @@ mod tests { unwrap!(mgr.disconnected(ConnHandle::new(2), Status::UNSPECIFIED)); // Check that we get an event + use crate::connection::ConnectionEvent; assert!(matches!( block_on(peripheral.next()), ConnectionEvent::Disconnected { diff --git a/host/src/gatt.rs b/host/src/gatt.rs index 53b71c69..804ca348 100644 --- a/host/src/gatt.rs +++ b/host/src/gatt.rs @@ -19,26 +19,20 @@ use crate::attribute::{AttributeData, Characteristic, CharacteristicProp, Uuid, use crate::attribute_server::{AttributeServer, DynamicAttributeServer}; use crate::connection::Connection; use crate::cursor::{ReadCursor, WriteCursor}; -use crate::packet_pool::{DynamicPacketPool, GENERIC_ID}; use crate::pdu::Pdu; use crate::types::gatt_traits::{FromGattError, GattValue}; use crate::types::l2cap::L2capHeader; use crate::{config, BleHostError, Error, Stack}; /// A GATT payload ready for processing. -pub struct GattData<'d> { - pdu: Pdu<'d>, - tx_pool: &'d dyn DynamicPacketPool<'d>, - connection: Connection<'d>, +pub struct GattData<'stack> { + pdu: Pdu, + connection: Connection<'stack>, } -impl<'d> GattData<'d> { - pub(crate) fn new(pdu: Pdu<'d>, tx_pool: &'d dyn DynamicPacketPool<'d>, connection: Connection<'d>) -> Self { - Self { - pdu, - tx_pool, - connection, - } +impl<'stack> GattData<'stack> { + pub(crate) fn new(pdu: Pdu, connection: Connection<'stack>) -> Self { + Self { pdu, connection } } /// Get the raw request. @@ -49,7 +43,7 @@ impl<'d> GattData<'d> { /// Respond directly to request. pub async fn reply(self, rsp: AttRsp<'_>) -> Result<(), Error> { - let pdu = respond(&self.connection, self.tx_pool, rsp)?; + let pdu = respond(&self.connection, rsp)?; self.connection.send(pdu).await; Ok(()) } @@ -61,12 +55,11 @@ impl<'d> GattData<'d> { pub async fn process<'m, 'server, M: RawMutex, const MAX: usize>( self, server: &'m AttributeServer<'server, M, MAX>, - ) -> Result>, Error> { + ) -> Result>, Error> { let att = self.request(); match att { AttReq::Write { handle, data: _ } => Ok(Some(GattEvent::Write(WriteEvent { value_handle: handle, - tx_pool: self.tx_pool, pdu: Some(self.pdu), connection: self.connection, server, @@ -74,7 +67,6 @@ impl<'d> GattData<'d> { AttReq::WriteCmd { handle, data: _ } => Ok(Some(GattEvent::Write(WriteEvent { value_handle: handle, - tx_pool: self.tx_pool, pdu: Some(self.pdu), connection: self.connection, server, @@ -82,7 +74,6 @@ impl<'d> GattData<'d> { AttReq::Read { handle } => Ok(Some(GattEvent::Read(ReadEvent { value_handle: handle, - tx_pool: self.tx_pool, pdu: Some(self.pdu), connection: self.connection, server, @@ -90,14 +81,13 @@ impl<'d> GattData<'d> { AttReq::ReadBlob { handle, offset } => Ok(Some(GattEvent::Read(ReadEvent { value_handle: handle, - tx_pool: self.tx_pool, pdu: Some(self.pdu), connection: self.connection, server, }))), _ => { // Process it now since the user will not - let reply = process_accept(self.pdu, &self.connection, server, self.tx_pool)?; + let reply = process_accept(self.pdu, &self.connection, server)?; reply.send().await; Ok(None) } @@ -106,23 +96,22 @@ impl<'d> GattData<'d> { } /// An event returned while processing GATT requests. -pub enum GattEvent<'d, 'server> { +pub enum GattEvent<'stack, 'server> { /// A characteristic was read. - Read(ReadEvent<'d, 'server>), + Read(ReadEvent<'stack, 'server>), /// A characteristic was written. - Write(WriteEvent<'d, 'server>), + Write(WriteEvent<'stack, 'server>), } /// An event returned while processing GATT requests. -pub struct ReadEvent<'d, 'server> { +pub struct ReadEvent<'stack, 'server> { value_handle: u16, - connection: Connection<'d>, + connection: Connection<'stack>, server: &'server dyn DynamicAttributeServer, - tx_pool: &'d dyn DynamicPacketPool<'d>, - pdu: Option>, + pdu: Option, } -impl<'d> ReadEvent<'d, '_> { +impl<'stack> ReadEvent<'stack, '_> { /// Characteristic handle that was read pub fn handle(&self) -> u16 { self.value_handle @@ -131,57 +120,35 @@ impl<'d> ReadEvent<'d, '_> { /// Accept the event, making it processed by the server. /// /// Automatically called if drop() is invoked. - pub fn accept(mut self) -> Result, Error> { + pub fn accept(mut self) -> Result, Error> { let handle = self.handle(); - process( - &mut self.pdu, - handle, - &self.connection, - self.server, - self.tx_pool, - Ok(()), - ) + process(&mut self.pdu, handle, &self.connection, self.server, Ok(())) } /// Reject the event with the provided error code, it will not be processed by the attribute server. - pub fn reject(mut self, err: AttErrorCode) -> Result, Error> { + pub fn reject(mut self, err: AttErrorCode) -> Result, Error> { let handle = self.handle(); - process( - &mut self.pdu, - handle, - &self.connection, - self.server, - self.tx_pool, - Err(err), - ) + process(&mut self.pdu, handle, &self.connection, self.server, Err(err)) } } impl Drop for ReadEvent<'_, '_> { fn drop(&mut self) { let handle = self.handle(); - let _ = process( - &mut self.pdu, - handle, - &self.connection, - self.server, - self.tx_pool, - Ok(()), - ); + let _ = process(&mut self.pdu, handle, &self.connection, self.server, Ok(())); } } /// An event returned while processing GATT requests. -pub struct WriteEvent<'d, 'server> { +pub struct WriteEvent<'stack, 'server> { /// Characteristic handle that was written. value_handle: u16, - pdu: Option>, - connection: Connection<'d>, - tx_pool: &'d dyn DynamicPacketPool<'d>, + pdu: Option, + connection: Connection<'stack>, server: &'server dyn DynamicAttributeServer, } -impl<'d> WriteEvent<'d, '_> { +impl<'stack> WriteEvent<'stack, '_> { /// Characteristic handle that was read pub fn handle(&self) -> u16 { self.value_handle @@ -201,72 +168,49 @@ impl<'d> WriteEvent<'d, '_> { /// Accept the event, making it processed by the server. /// /// Automatically called if drop() is invoked. - pub fn accept(mut self) -> Result, Error> { + pub fn accept(mut self) -> Result, Error> { let handle = self.handle(); - process( - &mut self.pdu, - handle, - &self.connection, - self.server, - self.tx_pool, - Ok(()), - ) + process(&mut self.pdu, handle, &self.connection, self.server, Ok(())) } /// Reject the event with the provided error code, it will not be processed by the attribute server. - pub fn reject(mut self, err: AttErrorCode) -> Result, Error> { + pub fn reject(mut self, err: AttErrorCode) -> Result, Error> { let handle = self.handle(); - process( - &mut self.pdu, - handle, - &self.connection, - self.server, - self.tx_pool, - Err(err), - ) + process(&mut self.pdu, handle, &self.connection, self.server, Err(err)) } } impl Drop for WriteEvent<'_, '_> { fn drop(&mut self) { let handle = self.handle(); - let _ = process( - &mut self.pdu, - handle, - &self.connection, - self.server, - self.tx_pool, - Ok(()), - ); + let _ = process(&mut self.pdu, handle, &self.connection, self.server, Ok(())); } } -fn process<'d>( - pdu: &mut Option>, +fn process<'stack>( + pdu: &mut Option, handle: u16, - connection: &Connection<'d>, + connection: &Connection<'stack>, server: &dyn DynamicAttributeServer, - tx_pool: &'d dyn DynamicPacketPool<'d>, result: Result<(), AttErrorCode>, -) -> Result, Error> { +) -> Result, Error> { if let Some(pdu) = pdu.take() { match result { - Ok(_) => process_accept(pdu, connection, server, tx_pool), - Err(code) => process_reject(pdu, handle, connection, tx_pool, code), + Ok(_) => process_accept(pdu, connection, server), + Err(code) => process_reject(pdu, handle, connection, code), } } else { Ok(Reply::new(connection.clone(), None)) } } -fn process_accept<'d>( - pdu: Pdu<'d>, - connection: &Connection<'d>, +fn process_accept<'stack>( + pdu: Pdu, + connection: &Connection<'stack>, server: &dyn DynamicAttributeServer, - tx_pool: &'d dyn DynamicPacketPool<'d>, -) -> Result, Error> { +) -> Result, Error> { let att = unwrap!(AttReq::decode(pdu.as_ref())); - let mut tx = tx_pool.alloc(GENERIC_ID).ok_or(Error::OutOfMemory)?; + let mut tx = connection.alloc_tx()?; let mut w = WriteCursor::new(tx.as_mut()); let (mut header, mut data) = w.split(4)?; if let Some(written) = server.process(connection, &att, data.write_buf())? { @@ -283,26 +227,21 @@ fn process_accept<'d>( } } -fn process_reject<'d>( - pdu: Pdu<'d>, +fn process_reject<'stack>( + pdu: Pdu, handle: u16, - connection: &Connection<'d>, - tx_pool: &'d dyn DynamicPacketPool<'d>, + connection: &Connection<'stack>, code: AttErrorCode, -) -> Result, Error> { +) -> Result, Error> { // We know it has been checked, therefore this cannot fail let request = pdu.as_ref()[0]; let rsp = AttRsp::Error { request, handle, code }; - let pdu = respond(connection, tx_pool, rsp)?; + let pdu = respond(connection, rsp)?; Ok(Reply::new(connection.clone(), Some(pdu))) } -fn respond<'d>( - conn: &Connection<'d>, - tx_pool: &'d dyn DynamicPacketPool<'d>, - rsp: AttRsp<'_>, -) -> Result, Error> { - let mut tx = tx_pool.alloc(GENERIC_ID).ok_or(Error::OutOfMemory)?; +fn respond<'stack>(conn: &Connection<'stack>, rsp: AttRsp<'_>) -> Result { + let mut tx = conn.alloc_tx()?; let mut w = WriteCursor::new(tx.as_mut()); let (mut header, mut data) = w.split(4)?; data.write(rsp)?; @@ -319,13 +258,13 @@ fn respond<'d>( /// /// The reply may be sent immediately or queued for sending later. To guarantee delivery of a reply /// in case of a full outbound queue, the async send() should be used rather than relying on the Drop implementation. -pub struct Reply<'d> { - connection: Connection<'d>, - pdu: Option>, +pub struct Reply<'stack> { + connection: Connection<'stack>, + pdu: Option, } -impl<'d> Reply<'d> { - fn new(connection: Connection<'d>, pdu: Option>) -> Self { +impl<'stack> Reply<'stack> { + fn new(connection: Connection<'stack>, pdu: Option) -> Self { Self { connection, pdu } } @@ -382,10 +321,10 @@ const NOTIF_QSIZE: usize = config::GATT_CLIENT_NOTIFICATION_QUEUE_SIZE; /// A GATT client capable of using the GATT protocol. pub struct GattClient<'reference, T: Controller, const MAX_SERVICES: usize, const L2CAP_MTU: usize = 27> { known_services: RefCell>, - rx: DynamicReceiver<'reference, (ConnHandle, Pdu<'reference>)>, - stack: Stack<'reference, T>, + rx: DynamicReceiver<'reference, (ConnHandle, Pdu)>, + stack: &'reference Stack<'reference, T>, connection: Connection<'reference>, - response_channel: Channel), 1>, + response_channel: Channel, notifications: PubSubChannel, NOTIF_QSIZE, MAX_NOTIF, 1>, } @@ -417,13 +356,13 @@ pub struct ServiceHandle { /// Trait with behavior for a gatt client. pub(crate) trait Client<'d, E> { /// Perform a gatt request and return the response. - fn request(&self, req: AttReq<'_>) -> impl Future, BleHostError>>; + fn request(&self, req: AttReq<'_>) -> impl Future>>; } impl<'reference, T: Controller, const MAX_SERVICES: usize, const L2CAP_MTU: usize> Client<'reference, T::Error> for GattClient<'reference, T, MAX_SERVICES, L2CAP_MTU> { - async fn request(&self, req: AttReq<'_>) -> Result, BleHostError> { + async fn request(&self, req: AttReq<'_>) -> Result> { let header = L2capHeader { channel: crate::types::l2cap::L2CAP_CID_ATT, length: req.size() as u16, @@ -449,7 +388,7 @@ impl<'reference, C: Controller, const MAX_SERVICES: usize, const L2CAP_MTU: usiz { /// Creates a GATT client capable of processing the GATT protocol using the provided table of attributes. pub async fn new( - stack: Stack<'reference, C>, + stack: &'reference Stack<'reference, C>, connection: &Connection<'reference>, ) -> Result, BleHostError> { let l2cap = L2capHeader { channel: 4, length: 3 }; diff --git a/host/src/host.rs b/host/src/host.rs index e6623dcf..f2d747fb 100644 --- a/host/src/host.rs +++ b/host/src/host.rs @@ -36,11 +36,11 @@ use futures::pin_mut; use crate::channel_manager::{ChannelManager, ChannelStorage, PacketChannel}; use crate::command::CommandState; #[cfg(feature = "gatt")] -use crate::connection::ConnectionEvent; +use crate::connection::ConnectionEventData; use crate::connection_manager::{ConnectionManager, ConnectionStorage, EventChannel, PacketGrant}; use crate::cursor::WriteCursor; use crate::l2cap::sar::{PacketReassembly, SarType}; -use crate::packet_pool::{AllocId, DynamicPacketPool}; +use crate::packet_pool::Pool; use crate::pdu::Pdu; use crate::types::l2cap::{ L2capHeader, L2capSignal, L2capSignalHeader, L2CAP_CID_ATT, L2CAP_CID_DYN_START, L2CAP_CID_LE_U_SIGNAL, @@ -61,12 +61,12 @@ pub(crate) struct BleHost<'d, T> { pub(crate) controller: T, pub(crate) connections: ConnectionManager<'d>, pub(crate) reassembly: PacketReassembly<'d>, - pub(crate) channels: ChannelManager<'d, { config::L2CAP_RX_QUEUE_SIZE }>, + pub(crate) channels: ChannelManager<'d>, #[cfg(feature = "gatt")] - pub(crate) att_client: Channel), { config::L2CAP_RX_QUEUE_SIZE }>, - pub(crate) rx_pool: &'d dyn DynamicPacketPool<'d>, + pub(crate) att_client: Channel, + pub(crate) rx_pool: &'d dyn Pool, #[cfg(feature = "gatt")] - pub(crate) tx_pool: &'d dyn DynamicPacketPool<'d>, + pub(crate) tx_pool: &'d dyn Pool, pub(crate) advertise_state: AdvState<'d>, pub(crate) advertise_command_state: CommandState, @@ -224,13 +224,13 @@ where #[allow(clippy::too_many_arguments)] pub(crate) fn new( controller: T, - rx_pool: &'d dyn DynamicPacketPool<'d>, - #[cfg(feature = "gatt")] tx_pool: &'d dyn DynamicPacketPool<'d>, + rx_pool: &'d dyn Pool, + #[cfg(feature = "gatt")] tx_pool: &'d dyn Pool, connections: &'d mut [ConnectionStorage], - events: &'d mut [EventChannel<'d>], + events: &'d mut [EventChannel], channels: &'d mut [ChannelStorage], - channels_rx: &'d mut [PacketChannel<'d, { config::L2CAP_RX_QUEUE_SIZE }>], - sar: &'d mut [SarType<'d>], + channels_rx: &'d mut [PacketChannel<{ config::L2CAP_RX_QUEUE_SIZE }>], + sar: &'d mut [SarType], advertise_handles: &'d mut [AdvHandleState], ) -> Self { Self { @@ -326,7 +326,7 @@ where true } - fn handle_acl(&'d self, acl: AclPacket<'_>) -> Result<(), Error> { + fn handle_acl(&self, acl: AclPacket<'_>) -> Result<(), Error> { self.connections.received(acl.handle())?; let (header, mut packet) = match acl.boundary_flag() { AclPacketBoundary::FirstFlushable => { @@ -347,7 +347,7 @@ where return Ok(()); } - let Some(mut p) = self.rx_pool.alloc(AllocId::from_channel(header.channel)) else { + let Some(mut p) = self.rx_pool.alloc() else { info!("No memory for packets on channel {}", header.channel); return Err(Error::OutOfMemory); }; @@ -403,16 +403,10 @@ where #[cfg(feature = "gatt")] match a { Ok(att::Att::Req(_)) => { - if let Some(connection) = self.connections.get_connected_handle(acl.handle()) { - let event = ConnectionEvent::Gatt { - data: crate::gatt::GattData::new( - Pdu::new(packet, header.length as usize), - self.tx_pool, - connection, - ), - }; - self.connections.post_handle_event(acl.handle(), event)?; - } + let event = ConnectionEventData::Gatt { + data: Pdu::new(packet, header.length as usize), + }; + self.connections.post_handle_event(acl.handle(), event)?; } Ok(att::Att::Rsp(_)) => { if let Err(e) = self @@ -503,21 +497,21 @@ pub struct Runner<'d, C: Controller> { /// The receiver part of the host runner. pub struct RxRunner<'d, C: Controller> { - stack: Stack<'d, C>, + stack: &'d Stack<'d, C>, } /// The control part of the host runner. pub struct ControlRunner<'d, C: Controller> { - stack: Stack<'d, C>, + stack: &'d Stack<'d, C>, } /// The transmit part of the host runner. pub struct TxRunner<'d, C: Controller> { - stack: Stack<'d, C>, + stack: &'d Stack<'d, C>, } impl<'d, C: Controller> Runner<'d, C> { - pub(crate) fn new(stack: Stack<'d, C>) -> Self { + pub(crate) fn new(stack: &'d Stack<'d, C>) -> Self { Self { rx: RxRunner { stack }, control: ControlRunner { stack }, @@ -610,7 +604,7 @@ impl<'d, C: Controller> RxRunner<'d, C> { C: ControllerCmdSync + for<'t> ControllerCmdSync>, { const MAX_HCI_PACKET_LEN: usize = 259; - let host = self.stack.host; + let host = &self.stack.host; // use embassy_time::Instant; // let mut last = Instant::now(); loop { @@ -832,7 +826,7 @@ impl<'d, C: Controller> ControlRunner<'d, C> { + for<'t> ControllerCmdSync> + ControllerCmdSync, { - let host = self.stack.host; + let host = &self.stack.host; Reset::new().exec(&host.controller).await?; if let Some(addr) = host.address { @@ -967,7 +961,7 @@ impl<'d, C: Controller> ControlRunner<'d, C> { impl<'d, C: Controller> TxRunner<'d, C> { /// Run the transmit loop for the host. pub async fn run(&mut self) -> Result<(), BleHostError> { - let host = self.stack.host; + let host = &self.stack.host; let params = host.initialized.get().await; loop { let (conn, pdu) = host.connections.outbound().await; diff --git a/host/src/l2cap.rs b/host/src/l2cap.rs index 0a758583..24b4e374 100644 --- a/host/src/l2cap.rs +++ b/host/src/l2cap.rs @@ -73,14 +73,14 @@ impl<'d> L2capChannel<'d> { /// If there are no available credits to send, waits until more credits are available. pub async fn send( &mut self, - stack: Stack<'_, T>, + stack: &Stack<'_, T>, buf: &[u8], ) -> Result<(), BleHostError> { let mut p_buf = [0u8; TX_MTU]; stack .host .channels - .send(self.index, buf, &mut p_buf[..], stack.host) + .send(self.index, buf, &mut p_buf[..], &stack.host) .await } @@ -92,14 +92,14 @@ impl<'d> L2capChannel<'d> { /// If there are no available credits to send, returns Error::Busy. pub fn try_send( &mut self, - stack: Stack<'_, T>, + stack: &Stack<'_, T>, buf: &[u8], ) -> Result<(), BleHostError> { let mut p_buf = [0u8; TX_MTU]; stack .host .channels - .try_send(self.index, buf, &mut p_buf[..], stack.host) + .try_send(self.index, buf, &mut p_buf[..], &stack.host) } /// Receive data on this channel and copy it into the buffer. @@ -107,15 +107,15 @@ impl<'d> L2capChannel<'d> { /// The length provided buffer slice must be equal or greater to the agreed MTU. pub async fn receive( &mut self, - stack: Stack<'_, T>, + stack: &Stack<'_, T>, buf: &mut [u8], ) -> Result> { - stack.host.channels.receive(self.index, buf, stack.host).await + stack.host.channels.receive(self.index, buf, &stack.host).await } /// Await an incoming connection request matching the list of PSM. pub async fn accept( - stack: Stack<'d, T>, + stack: &'d Stack<'d, T>, connection: &Connection<'_>, psm: &[u16], config: &L2capChannelConfig, @@ -130,14 +130,14 @@ impl<'d> L2capChannel<'d> { config.mtu, config.flow_policy, config.initial_credits, - stack.host, + &stack.host, ) .await } /// Create a new connection request with the provided PSM. pub async fn create( - stack: Stack<'d, T>, + stack: &'d Stack<'d, T>, connection: &Connection<'_>, psm: u16, config: &L2capChannelConfig, @@ -152,7 +152,7 @@ where { config.mtu, config.flow_policy, config.initial_credits, - stack.host, + &stack.host, ) .await } diff --git a/host/src/l2cap/sar.rs b/host/src/l2cap/sar.rs index 7b720395..52f7b2d8 100644 --- a/host/src/l2cap/sar.rs +++ b/host/src/l2cap/sar.rs @@ -6,13 +6,13 @@ use crate::packet_pool::Packet; use crate::types::l2cap::L2capHeader; use crate::Error; -pub(crate) struct AssembledPacket<'d> { - packet: Packet<'d>, +pub(crate) struct AssembledPacket { + packet: Packet, written: usize, } -impl<'d> AssembledPacket<'d> { - pub(crate) fn new(packet: Packet<'d>, initial: usize) -> Self { +impl AssembledPacket { + pub(crate) fn new(packet: Packet, initial: usize) -> Self { Self { packet, written: initial, @@ -32,7 +32,7 @@ impl<'d> AssembledPacket<'d> { self.written } - pub(crate) fn finalize(self, header: L2capHeader) -> Result<(L2capHeader, Packet<'d>), Error> { + pub(crate) fn finalize(self, header: L2capHeader) -> Result<(L2capHeader, Packet), Error> { if header.length as usize != self.written { return Err(Error::InvalidValue); } @@ -40,14 +40,14 @@ impl<'d> AssembledPacket<'d> { } } -pub(crate) type SarType<'d> = Option<(ConnHandle, L2capHeader, AssembledPacket<'d>)>; +pub(crate) type SarType = Option<(ConnHandle, L2capHeader, AssembledPacket)>; // Handles reassembling of packets pub struct PacketReassembly<'d> { - handles: RefCell<&'d mut [SarType<'d>]>, + handles: RefCell<&'d mut [SarType]>, } impl<'d> PacketReassembly<'d> { - pub fn new(handles: &'d mut [Option<(ConnHandle, L2capHeader, AssembledPacket<'d>)>]) -> Self { + pub fn new(handles: &'d mut [Option<(ConnHandle, L2capHeader, AssembledPacket)>]) -> Self { Self { handles: RefCell::new(handles), //[Self::EMPTY; CONNS]), } @@ -57,7 +57,7 @@ impl<'d> PacketReassembly<'d> { /// /// Returns InvalidState if there is already an ongoing reassembly for this connection /// Returns InsufficientSpace if there is no space for this reassembly - pub fn init(&self, handle: ConnHandle, header: L2capHeader, p: Packet<'d>, initial: usize) -> Result<(), Error> { + pub fn init(&self, handle: ConnHandle, header: L2capHeader, p: Packet, initial: usize) -> Result<(), Error> { let mut state = self.handles.borrow_mut(); // Sanity check @@ -93,7 +93,7 @@ impl<'d> PacketReassembly<'d> { /// Updates any in progress packet assembly for the connection /// /// If the reassembly is complete, the l2cap header + packet is returned. - pub fn update(&self, handle: ConnHandle, data: &[u8]) -> Result)>, Error> { + pub fn update(&self, handle: ConnHandle, data: &[u8]) -> Result, Error> { let mut state = self.handles.borrow_mut(); for entry in state.iter_mut() { diff --git a/host/src/lib.rs b/host/src/lib.rs index 2c7de96d..f01cab2f 100644 --- a/host/src/lib.rs +++ b/host/src/lib.rs @@ -17,13 +17,12 @@ use bt_hci::cmd::status::ReadRssi; use bt_hci::cmd::{AsyncCmd, SyncCmd}; pub use bt_hci::param::{AddrKind, BdAddr, LeConnRole as Role}; use bt_hci::FromHciBytesError; -use embassy_sync::blocking_mutex::raw::NoopRawMutex; use crate::att::AttErrorCode; use crate::channel_manager::{ChannelStorage, PacketChannel}; use crate::connection_manager::{ConnectionStorage, EventChannel}; use crate::l2cap::sar::SarType; -use crate::packet_pool::{PacketPool, Qos}; +use crate::packet_pool::PacketPool; mod fmt; @@ -31,6 +30,7 @@ mod fmt; compile_error!("Must enable at least one of the `central` or `peripheral` features"); mod att; +#[cfg(feature = "central")] pub mod central; mod channel_manager; mod codec; @@ -40,10 +40,15 @@ mod connection_manager; mod cursor; pub mod packet_pool; mod pdu; +#[cfg(feature = "peripheral")] pub mod peripheral; pub mod types; -pub use packet_pool::Qos as PacketQos; +#[cfg(feature = "peripheral")] +use peripheral::*; + +#[cfg(feature = "central")] +use central::*; pub mod advertise; pub mod connection; @@ -57,9 +62,7 @@ pub mod scan; pub(crate) mod mock_controller; pub(crate) mod host; -pub use central::*; use host::{AdvHandleState, BleHost, HostMetrics, Runner}; -pub use peripheral::*; #[allow(missing_docs)] pub mod prelude { @@ -86,7 +89,7 @@ pub mod prelude { pub use crate::gatt::*; pub use crate::host::{ControlRunner, HostMetrics, Runner, RxRunner, TxRunner}; pub use crate::l2cap::*; - pub use crate::packet_pool::{PacketPool, Qos as PacketQos}; + pub use crate::packet_pool::PacketPool; #[cfg(feature = "peripheral")] pub use crate::peripheral::*; #[cfg(feature = "scan")] @@ -301,33 +304,32 @@ impl< /// /// The l2cap packet pool is used by the host to handle inbound data, by allocating space for /// incoming packets and dispatching to the appropriate connection and channel. -pub struct HostResources< - C: Controller, - const CONNS: usize, - const CHANNELS: usize, - const L2CAP_MTU: usize, - const ADV_SETS: usize = 1, -> { - qos: Qos, - rx_pool: MaybeUninit>, +pub struct HostResources { + rx_pool: MaybeUninit>, #[cfg(feature = "gatt")] - tx_pool: MaybeUninit>, + tx_pool: MaybeUninit>, connections: MaybeUninit<[ConnectionStorage; CONNS]>, - events: MaybeUninit<[EventChannel<'static>; CONNS]>, + events: MaybeUninit<[EventChannel; CONNS]>, channels: MaybeUninit<[ChannelStorage; CHANNELS]>, - channels_rx: MaybeUninit<[PacketChannel<'static, { config::L2CAP_RX_QUEUE_SIZE }>; CHANNELS]>, - sar: MaybeUninit<[SarType<'static>; CONNS]>, + channels_rx: MaybeUninit<[PacketChannel<{ config::L2CAP_RX_QUEUE_SIZE }>; CHANNELS]>, + sar: MaybeUninit<[SarType; CONNS]>, advertise_handles: MaybeUninit<[AdvHandleState; ADV_SETS]>, - inner: MaybeUninit>, } -impl - HostResources +impl Default + for HostResources { - /// Create a new instance of host resources with the provided QoS requirements for packets. - pub fn new(qos: Qos) -> Self { + fn default() -> Self { + Self::new() + } +} + +impl + HostResources +{ + /// Create a new instance of host resources. + pub const fn new() -> Self { Self { - qos, rx_pool: MaybeUninit::uninit(), #[cfg(feature = "gatt")] tx_pool: MaybeUninit::uninit(), @@ -337,7 +339,6 @@ impl( controller: C, - resources: &'d mut HostResources, -) -> Builder<'d, C> { + resources: &'resources mut HostResources, +) -> Stack<'resources, C> { unsafe fn transmute_slice(x: &mut [T]) -> &'static mut [T] { core::mem::transmute(x) } // Safety: - // - HostResources has the same lifetime as the returned Builder. + // - HostResources has the exceeding lifetime as the returned Stack. // - Internal lifetimes are elided (made 'static) to simplify API usage // - This _should_ be OK, because there are no references held to the resources // when the stack is shut down. - use crate::packet_pool::DynamicPacketPool; - let rx_pool: &'d dyn DynamicPacketPool<'d> = &*resources.rx_pool.write(PacketPool::new(resources.qos)); - let rx_pool = unsafe { - core::mem::transmute::<&'d dyn DynamicPacketPool<'d>, &'static dyn DynamicPacketPool<'static>>(rx_pool) - }; + use crate::packet_pool::Pool; + let rx_pool: &'resources dyn Pool = &*resources.rx_pool.write(PacketPool::new()); + let rx_pool = unsafe { core::mem::transmute::<&'resources dyn Pool, &'static dyn Pool>(rx_pool) }; #[cfg(feature = "gatt")] - let tx_pool: &'d dyn DynamicPacketPool<'d> = &*resources.tx_pool.write(PacketPool::new(PacketQos::None)); + let tx_pool: &'resources dyn Pool = &*resources.tx_pool.write(PacketPool::new()); #[cfg(feature = "gatt")] - let tx_pool = unsafe { - core::mem::transmute::<&'d dyn DynamicPacketPool<'d>, &'static dyn DynamicPacketPool<'static>>(tx_pool) - }; - - let connections = &mut *resources.connections.write([ConnectionStorage::DISCONNECTED; CONNS]); - let connections = unsafe { transmute_slice(connections) }; - let events = &mut *resources.events.write([const { EventChannel::new() }; CONNS]); - let events = unsafe { transmute_slice(events) }; + let tx_pool = unsafe { core::mem::transmute::<&'resources dyn Pool, &'static dyn Pool>(tx_pool) }; + + use crate::l2cap::sar::AssembledPacket; + use crate::types::l2cap::L2capHeader; + use bt_hci::param::ConnHandle; + let connections: &mut [ConnectionStorage] = + &mut *resources.connections.write([ConnectionStorage::DISCONNECTED; CONNS]); + let connections: &'resources mut [ConnectionStorage] = unsafe { transmute_slice(connections) }; + + let events: &mut [EventChannel] = &mut *resources.events.write([EventChannel::NEW; CONNS]); + let events: &'resources mut [EventChannel] = unsafe { transmute_slice(events) }; + let channels = &mut *resources.channels.write([ChannelStorage::DISCONNECTED; CHANNELS]); - let channels = unsafe { transmute_slice(channels) }; - let channels_rx = &mut *resources.channels_rx.write([PacketChannel::NEW; CHANNELS]); - let channels_rx = unsafe { transmute_slice(channels_rx) }; + let channels: &'static mut [ChannelStorage] = unsafe { transmute_slice(channels) }; + + let channels_rx: &mut [PacketChannel<{ config::L2CAP_RX_QUEUE_SIZE }>] = + &mut *resources.channels_rx.write([PacketChannel::NEW; CHANNELS]); + let channels_rx: &'static mut [PacketChannel<{ config::L2CAP_RX_QUEUE_SIZE }>] = + unsafe { transmute_slice(channels_rx) }; let sar = &mut *resources.sar.write([const { None }; CONNS]); - let sar = unsafe { transmute_slice(sar) }; + let sar: &'static mut [Option<(ConnHandle, L2capHeader, AssembledPacket)>] = unsafe { transmute_slice(sar) }; let advertise_handles = &mut *resources.advertise_handles.write([AdvHandleState::None; ADV_SETS]); - let advertise_handles = unsafe { transmute_slice(advertise_handles) }; - let host = BleHost::new( + let advertise_handles: &'static mut [AdvHandleState] = unsafe { transmute_slice(advertise_handles) }; + let host: BleHost<'_, C> = BleHost::new( controller, rx_pool, #[cfg(feature = "gatt")] @@ -402,53 +408,40 @@ pub fn new< advertise_handles, ); - let host = &mut *resources.inner.write(host); - let host = unsafe { core::mem::transmute::<&mut BleHost<'_, C>, &'d mut BleHost<'d, C>>(host) }; - Builder { host } + Stack { host } } -/// Type for configuring the BLE host. -pub struct Builder<'d, C: Controller> { - host: &'d mut BleHost<'d, C>, +/// Contains the host stack +pub struct Stack<'stack, C> { + host: BleHost<'stack, C>, } -impl<'d, C: Controller> Builder<'d, C> { +impl<'stack, C: Controller> Stack<'stack, C> { /// Set the random address used by this host. - pub fn set_random_address(self, address: Address) -> Self { + pub fn set_random_address(mut self, address: Address) -> Self { self.host.address.replace(address); self } /// Build the stack. #[cfg(all(feature = "central", feature = "peripheral"))] - pub fn build(self) -> (Stack<'d, C>, Peripheral<'d, C>, Central<'d, C>, Runner<'d, C>) { - let stack = Stack::new(self.host); - (stack, Peripheral::new(stack), Central::new(stack), Runner::new(stack)) + pub fn build(&'stack self) -> (Peripheral<'stack, C>, Central<'stack, C>, Runner<'stack, C>) { + let stack = self; + (Peripheral::new(stack), Central::new(stack), Runner::new(stack)) } /// Build the stack. #[cfg(all(not(feature = "central"), feature = "peripheral"))] - pub fn build(self) -> (Stack<'d, C>, Peripheral<'d, C>, Runner<'d, C>) { - let stack = Stack::new(self.host); - (stack, Peripheral::new(stack), Runner::new(stack)) + pub fn build(&'stack self) -> (Peripheral<'stack, C>, Runner<'stack, C>) { + let stack = self; + (Peripheral::new(stack), Runner::new(stack)) } /// Build the stack. #[cfg(all(feature = "central", not(feature = "peripheral")))] - pub fn build(self) -> (Stack<'d, C>, Central<'d, C>, Runner<'d, C>) { - let stack = Stack::new(self.host); - (stack, Central::new(stack), Runner::new(stack)) - } -} - -/// Handle to the BLE stack. -pub struct Stack<'d, C> { - host: &'d BleHost<'d, C>, -} - -impl<'d, C: Controller> Stack<'d, C> { - pub(crate) fn new(host: &'d BleHost<'d, C>) -> Self { - Self { host } + pub fn build(&'stack self) -> (Central<'stack, C>, Runner<'stack, C>) { + let stack = self; + (Central::new(stack), Runner::new(stack)) } /// Run a HCI command and return the response. @@ -479,11 +472,3 @@ impl<'d, C: Controller> Stack<'d, C> { self.host.log_status(verbose); } } - -impl Clone for Stack<'_, C> { - fn clone(&self) -> Self { - *self - } -} - -impl Copy for Stack<'_, C> {} diff --git a/host/src/packet_pool.rs b/host/src/packet_pool.rs index 6723e16b..1272afcc 100644 --- a/host/src/packet_pool.rs +++ b/host/src/packet_pool.rs @@ -1,35 +1,9 @@ //! A packet pool for allocating and freeing packet buffers with quality of service policy. use core::cell::RefCell; -use embassy_sync::blocking_mutex::raw::RawMutex; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_sync::blocking_mutex::Mutex; -use crate::types::l2cap::{L2CAP_CID_ATT, L2CAP_CID_DYN_START}; - -// Generic client ID used by ATT PDU -pub(crate) const GENERIC_ID: AllocId = AllocId(0); - -#[derive(Clone, Copy, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub(crate) struct AllocId(usize); - -impl AllocId { - pub(crate) fn dynamic(idx: usize) -> AllocId { - // Dynamic range starts at 2 - AllocId(1 + idx) - } - - pub(crate) fn from_channel(cid: u16) -> AllocId { - match cid { - L2CAP_CID_ATT => GENERIC_ID, - cid if cid >= L2CAP_CID_DYN_START => Self::dynamic((cid - L2CAP_CID_DYN_START) as usize), - cid => { - panic!("unexpected channel id {}", cid); - } - } - } -} - struct PacketBuf { buf: [u8; MTU], free: bool, @@ -46,73 +20,23 @@ impl PacketBuf { } } -/// Quality of service policy for packet allocation -#[derive(Clone, Copy, Default)] -pub enum Qos { - /// Distribute evenly among client - Fair, - /// Reserve at least N packets for each client - Guaranteed(usize), - /// No guarantees - #[default] - None, -} - -struct State { +struct State { packets: [PacketBuf; N], - usage: [usize; CLIENTS], } -impl State { +impl State { pub(crate) const fn new() -> Self { Self { packets: [PacketBuf::NEW; N], - usage: [0; CLIENTS], } } - // Guaranteed available - fn min_available(&self, qos: Qos, client: AllocId) -> usize { - let min = match qos { - Qos::None => N.saturating_sub(self.usage.iter().sum()), - Qos::Fair => (N / CLIENTS).saturating_sub(self.usage[client.0]), - Qos::Guaranteed(n) => { - let usage = self.usage[client.0]; - n.saturating_sub(usage) - } - }; - // info!("Min available for {}: {} (usage: {})", client.0, min, usage[client.0]); - min - } - - fn available(&self, qos: Qos, client: AllocId) -> usize { - let available = match qos { - Qos::None => N.saturating_sub(self.usage.iter().sum()), - Qos::Fair => (N / CLIENTS).saturating_sub(self.usage[client.0]), - Qos::Guaranteed(n) => { - // Reserved for clients that should have minimum - let reserved = n * self.usage.iter().filter(|c| **c == 0).count(); - let reserved = reserved - - if self.usage[client.0] < n { - n - self.usage[client.0] - } else { - 0 - }; - let usage = reserved + self.usage.iter().sum::(); - N.saturating_sub(usage) - } - }; - // info!("Available for {}: {} (usage {})", client.0, available, usage[client.0]); - available - } - - fn alloc(&mut self, id: AllocId) -> Option { + fn alloc(&mut self) -> Option { for (idx, packet) in self.packets.iter_mut().enumerate() { if packet.free { // info!("[{}] alloc {}", id.0, idx); packet.free = false; packet.buf.iter_mut().for_each(|b| *b = 0); - self.usage[id.0] += 1; return Some(PacketRef { idx, buf: &mut packet.buf[..], @@ -122,105 +46,86 @@ impl State usize { + self.packets.iter().filter(|p| p.free).count() } } /// A packet pool holds a pool of packet buffers that can be dynamically allocated /// and free'd. -/// -/// The pool has a concept QoS to control quota for multiple clients. -pub struct PacketPool { - state: Mutex>>, - qos: Qos, +pub struct PacketPool { + state: Mutex>>, } -impl PacketPool { +impl Default for PacketPool { + fn default() -> Self { + Self::new() + } +} + +impl PacketPool { /// Create a new packet pool with the given QoS policy - pub fn new(qos: Qos) -> Self { - // Need at least 1 for gatt - assert!(CLIENTS >= 1); - match qos { - Qos::None => {} - Qos::Fair => { - assert!(N >= CLIENTS); - } - Qos::Guaranteed(n) => { - assert!(N >= n); - } - } + pub fn new() -> Self { Self { state: Mutex::new(RefCell::new(State::new())), - qos, } } - fn alloc(&self, id: AllocId) -> Option { + fn alloc(&self) -> Option { self.state.lock(|state| { let mut state = state.borrow_mut(); - let available = state.available(self.qos, id); - if available == 0 { - return None; - } - - state.alloc(id).map(|p_ref| Packet { - client: id, + state.alloc().map(|p_ref| Packet { p_ref: Some(p_ref), pool: self, }) }) } - fn free(&self, id: AllocId, p_ref: PacketRef) { + fn free(&self, p_ref: PacketRef) { self.state.lock(|state| { let mut state = state.borrow_mut(); - state.free(id, p_ref); + state.free(p_ref); }); } - fn min_available(&self, id: AllocId) -> usize { - self.state.lock(|state| { - let state = state.borrow(); - state.min_available(self.qos, id) - }) - } - - fn available(&self, id: AllocId) -> usize { + fn available(&self) -> usize { self.state.lock(|state| { - let state = state.borrow(); - state.available(self.qos, id) + let mut state = state.borrow_mut(); + state.available() }) } } -pub(crate) trait DynamicPacketPool<'d> { - fn alloc(&'d self, id: AllocId) -> Option>; - fn free(&self, id: AllocId, r: PacketRef); - fn available(&self, id: AllocId) -> usize; - fn min_available(&self, id: AllocId) -> usize; +/// Type erased packet pool +pub(crate) trait Pool { + /// Allocate a packet + /// + /// Returns None if out of memory. + fn alloc(&self) -> Option; + /// Free a packet given it's reference. + fn free(&self, r: PacketRef); + /// Check for available packets. + fn available(&self) -> usize; + /// Check packet size. fn mtu(&self) -> usize; } -impl<'d, M: RawMutex, const MTU: usize, const N: usize, const CLIENTS: usize> DynamicPacketPool<'d> - for PacketPool -{ - fn alloc(&'d self, id: AllocId) -> Option> { - PacketPool::alloc(self, id) +impl Pool for PacketPool { + fn alloc(&self) -> Option { + PacketPool::alloc(self) } - fn min_available(&self, id: AllocId) -> usize { - PacketPool::min_available(self, id) + fn free(&self, r: PacketRef) { + PacketPool::free(self, r) } - fn available(&self, id: AllocId) -> usize { - PacketPool::available(self, id) - } - - fn free(&self, id: AllocId, r: PacketRef) { - PacketPool::free(self, id, r) + fn available(&self) -> usize { + PacketPool::available(self) } fn mtu(&self) -> usize { @@ -228,18 +133,19 @@ impl<'d, M: RawMutex, const MTU: usize, const N: usize, const CLIENTS: usize> Dy } } +#[repr(C)] pub(crate) struct PacketRef { idx: usize, buf: *mut [u8], } -pub(crate) struct Packet<'d> { - client: AllocId, +#[repr(C)] +pub(crate) struct Packet { p_ref: Option, - pool: &'d dyn DynamicPacketPool<'d>, + pool: *const dyn Pool, } -impl Packet<'_> { +impl Packet { pub(crate) fn len(&self) -> usize { self.as_ref().len() } @@ -249,22 +155,23 @@ impl Packet<'_> { } } -impl Drop for Packet<'_> { +impl Drop for Packet { fn drop(&mut self) { if let Some(r) = self.p_ref.take() { - self.pool.free(self.client, r); + let pool = unsafe { &*self.pool }; + pool.free(r); } } } -impl AsRef<[u8]> for Packet<'_> { +impl AsRef<[u8]> for Packet { fn as_ref(&self) -> &[u8] { let p = self.p_ref.as_ref().unwrap(); unsafe { &(*p.buf)[..] } } } -impl AsMut<[u8]> for Packet<'_> { +impl AsMut<[u8]> for Packet { fn as_mut(&mut self) -> &mut [u8] { let p = self.p_ref.as_mut().unwrap(); unsafe { &mut (*p.buf)[..] } @@ -273,110 +180,34 @@ impl AsMut<[u8]> for Packet<'_> { #[cfg(test)] mod tests { - use embassy_sync::blocking_mutex::raw::NoopRawMutex; use static_cell::StaticCell; use super::*; - #[test] - fn test_fair_qos() { - static POOL: StaticCell> = StaticCell::new(); - let pool = POOL.init(PacketPool::new(Qos::Fair)); - - let a1 = pool.alloc(AllocId(0)); - assert!(a1.is_some()); - let a2 = pool.alloc(AllocId(0)); - assert!(a2.is_some()); - assert!(pool.alloc(AllocId(0)).is_none()); - drop(a2); - let a3 = pool.alloc(AllocId(0)); - assert!(a3.is_some()); - - let b1 = pool.alloc(AllocId(1)); - assert!(b1.is_some()); - - let c1 = pool.alloc(AllocId(2)); - assert!(c1.is_some()); - } - #[test] fn test_none_qos() { - static POOL: StaticCell> = StaticCell::new(); - let pool = POOL.init(PacketPool::new(Qos::None)); + static POOL: StaticCell> = StaticCell::new(); + let pool = POOL.init(PacketPool::new()); - let a1 = pool.alloc(AllocId(0)); + let a1 = pool.alloc(); assert!(a1.is_some()); - let a2 = pool.alloc(AllocId(0)); + let a2 = pool.alloc(); assert!(a2.is_some()); - let a3 = pool.alloc(AllocId(0)); + let a3 = pool.alloc(); assert!(a3.is_some()); - let a4 = pool.alloc(AllocId(0)); + let a4 = pool.alloc(); assert!(a4.is_some()); - let a5 = pool.alloc(AllocId(0)); + let a5 = pool.alloc(); assert!(a5.is_some()); - let a6 = pool.alloc(AllocId(0)); + let a6 = pool.alloc(); assert!(a6.is_some()); - let a7 = pool.alloc(AllocId(0)); + let a7 = pool.alloc(); assert!(a7.is_some()); - let b1 = pool.alloc(AllocId(1)); + let b1 = pool.alloc(); assert!(b1.is_some()); - let b2 = pool.alloc(AllocId(1)); + let b2 = pool.alloc(); assert!(b2.is_none()); } - - #[test] - fn test_guaranteed_qos() { - static POOL: StaticCell> = StaticCell::new(); - let pool = POOL.init(PacketPool::new(Qos::Guaranteed(1))); - - let a1 = pool.alloc(AllocId(0)); - assert!(a1.is_some()); - let a2 = pool.alloc(AllocId(0)); - assert!(a2.is_some()); - let a3 = pool.alloc(AllocId(0)); - assert!(a3.is_some()); - let a4 = pool.alloc(AllocId(0)); - assert!(a4.is_some()); - let a5 = pool.alloc(AllocId(0)); - assert!(a5.is_some()); - // Needs at least 3 for the other clients - assert!(pool.alloc(AllocId(0)).is_none()); - - let b1 = pool.alloc(AllocId(1)); - assert!(b1.is_some()); - assert!(pool.alloc(AllocId(1)).is_none()); - - let c1 = pool.alloc(AllocId(2)); - assert!(c1.is_some()); - assert!(pool.alloc(AllocId(2)).is_none()); - - let d1 = pool.alloc(AllocId(3)); - assert!(d1.is_some()); - assert!(pool.alloc(AllocId(3)).is_none()); - } - - #[test] - fn test_guaranteed_qos_many() { - static POOL: StaticCell> = StaticCell::new(); - let pool = POOL.init(PacketPool::new(Qos::Guaranteed(1))); - - let a1 = pool.alloc(AllocId(0)); - assert!(a1.is_some()); - // Needs at least 1 for the other clients - assert!(pool.alloc(AllocId(0)).is_none()); - - let b1 = pool.alloc(AllocId(1)); - assert!(b1.is_some()); - assert!(pool.alloc(AllocId(1)).is_none()); - - let c1 = pool.alloc(AllocId(2)); - assert!(c1.is_some()); - assert!(pool.alloc(AllocId(2)).is_none()); - - let d1 = pool.alloc(AllocId(3)); - assert!(d1.is_some()); - assert!(pool.alloc(AllocId(3)).is_none()); - } } diff --git a/host/src/pdu.rs b/host/src/pdu.rs index 7998bfad..551da23a 100644 --- a/host/src/pdu.rs +++ b/host/src/pdu.rs @@ -1,23 +1,23 @@ use crate::packet_pool::Packet; -pub(crate) struct Pdu<'d> { - pub packet: Packet<'d>, +pub(crate) struct Pdu { + pub packet: Packet, pub len: usize, } -impl<'d> Pdu<'d> { - pub(crate) fn new(packet: Packet<'d>, len: usize) -> Self { +impl Pdu { + pub(crate) fn new(packet: Packet, len: usize) -> Self { Self { packet, len } } } -impl AsRef<[u8]> for Pdu<'_> { +impl AsRef<[u8]> for Pdu { fn as_ref(&self) -> &[u8] { &self.packet.as_ref()[..self.len] } } -impl AsMut<[u8]> for Pdu<'_> { +impl AsMut<[u8]> for Pdu { fn as_mut(&mut self) -> &mut [u8] { &mut self.packet.as_mut()[..self.len] } diff --git a/host/src/peripheral.rs b/host/src/peripheral.rs index 4625b7c0..788ef3a6 100644 --- a/host/src/peripheral.rs +++ b/host/src/peripheral.rs @@ -14,11 +14,11 @@ use crate::{Address, BleHostError, Error, Stack}; /// Type which implements the BLE peripheral role. pub struct Peripheral<'d, C: Controller> { - stack: Stack<'d, C>, + stack: &'d Stack<'d, C>, } impl<'d, C: Controller> Peripheral<'d, C> { - pub(crate) fn new(stack: Stack<'d, C>) -> Self { + pub(crate) fn new(stack: &'d Stack<'d, C>) -> Self { Self { stack } } @@ -34,7 +34,7 @@ impl<'d, C: Controller> Peripheral<'d, C> { + for<'t> ControllerCmdSync + for<'t> ControllerCmdSync, { - let host = self.stack.host; + let host = &self.stack.host; // Ensure no other advertise ongoing. let drop = crate::host::OnDrop::new(|| { @@ -128,7 +128,7 @@ impl<'d, C: Controller> Peripheral<'d, C> { + for<'t> ControllerCmdSync>, { assert_eq!(sets.len(), handles.len()); - let host = self.stack.host; + let host = &self.stack.host; // Check host supports the required advertisement sets { let result = host.command(LeReadNumberOfSupportedAdvSets::new()).await?; @@ -219,7 +219,7 @@ impl<'d, C: Controller> Peripheral<'d, C> { /// Handle to an active advertiser which can accept connections. pub struct Advertiser<'d, C: Controller> { - stack: Stack<'d, C>, + stack: &'d Stack<'d, C>, extended: bool, done: bool, } diff --git a/host/src/scan.rs b/host/src/scan.rs index d84a30dc..a2e02b73 100644 --- a/host/src/scan.rs +++ b/host/src/scan.rs @@ -1,6 +1,6 @@ //! Scan config. -use crate::central::ScanConfig; use crate::command::CommandState; +use crate::connection::ScanConfig; use crate::host::ScanState; use crate::BleHostError; use crate::Error; @@ -54,7 +54,7 @@ impl<'d, C: Controller, const BUFFER_SIZE: usize> Scanner<'d, C, BUFFER_SIZE> { + ControllerCmdSync + ControllerCmdSync, { - let host = self.central.stack.host; + let host = &self.central.stack.host; let drop = crate::host::OnDrop::new(|| { host.scan_command_state.cancel(false); host.scan_state.stop(); @@ -68,7 +68,7 @@ impl<'d, C: Controller, const BUFFER_SIZE: usize> Scanner<'d, C, BUFFER_SIZE> { scan_window: config.window.into(), }; let phy_params = crate::central::create_phy_params(scanning, config.phys); - let host = self.central.stack.host; + let host = &self.central.stack.host; host.command(LeSetExtScanParams::new( host.address.map(|s| s.kind).unwrap_or(AddrKind::PUBLIC), if config.filter_accept_list.is_empty() { @@ -118,7 +118,7 @@ impl<'d, C: Controller, const BUFFER_SIZE: usize> Scanner<'d, C, BUFFER_SIZE> { + ControllerCmdSync + ControllerCmdSync, { - let host = self.central.stack.host; + let host = &self.central.stack.host; let drop = crate::host::OnDrop::new(|| { host.scan_command_state.cancel(false); host.scan_state.stop(); diff --git a/host/tests/common.rs b/host/tests/common.rs index 78d47d68..4004a754 100644 --- a/host/tests/common.rs +++ b/host/tests/common.rs @@ -7,6 +7,7 @@ use tokio::io::{ReadHalf, WriteHalf}; use tokio::time::Duration; use tokio_serial::{DataBits, Parity, SerialStream, StopBits}; +#[allow(dead_code)] pub type Controller = ExternalController< SerialTransport>, FromTokio>>, 10, diff --git a/host/tests/gatt.rs b/host/tests/gatt.rs index 02584ee1..f3c29460 100644 --- a/host/tests/gatt.rs +++ b/host/tests/gatt.rs @@ -31,10 +31,10 @@ async fn gatt_client_server() { let peripheral = local.spawn_local(async move { let controller_peripheral = common::create_controller(&peripheral).await; - let mut resources: HostResources = HostResources::new(PacketQos::None); - let (_stack, mut peripheral, _central, mut runner) = trouble_host::new(controller_peripheral, &mut resources) - .set_random_address(peripheral_address) - .build(); + let mut resources: HostResources = HostResources::new(); + let stack = trouble_host::new(controller_peripheral, &mut resources) + .set_random_address(peripheral_address); + let (mut peripheral, _, mut runner) = stack.build(); let id = b"Trouble"; let appearance = [0x80, 0x07]; @@ -127,10 +127,9 @@ async fn gatt_client_server() { // Spawn central let central = local.spawn_local(async move { let controller_central = common::create_controller(¢ral).await; - let mut resources: HostResources = - HostResources::new(PacketQos::None); - let (stack, _peripheral, mut central, mut runner) = - trouble_host::new(controller_central, &mut resources).build(); + let mut resources: HostResources = HostResources::new(); + let stack = trouble_host::new(controller_central, &mut resources); + let (_, mut central, mut runner) = stack.build(); select! { r = runner.run() => { @@ -152,7 +151,7 @@ async fn gatt_client_server() { tokio::time::sleep(Duration::from_secs(5)).await; println!("[central] creating gatt client"); - let client = GattClient::::new(stack, &conn).await.unwrap(); + let client = GattClient::::new(&stack, &conn).await.unwrap(); select! { r = async { diff --git a/host/tests/gatt_derive.rs b/host/tests/gatt_derive.rs index e14d3698..5ebf44cd 100644 --- a/host/tests/gatt_derive.rs +++ b/host/tests/gatt_derive.rs @@ -1,3 +1,4 @@ +#![allow(dead_code)] use std::time::Duration; use embassy_sync::blocking_mutex::raw::NoopRawMutex; @@ -70,10 +71,11 @@ async fn gatt_client_server() { let peripheral = local.spawn_local(async move { let controller_peripheral = common::create_controller(&peripheral).await; - let mut resources: HostResources = HostResources::new(PacketQos::None); - let (_, mut peripheral, _central, mut runner) = trouble_host::new(controller_peripheral, &mut resources) - .set_random_address(peripheral_address) - .build(); + let mut resources: HostResources = HostResources::new(); + let stack = trouble_host::new(controller_peripheral, &mut resources) + .set_random_address(peripheral_address); + let (mut peripheral, _, mut runner) = stack.build(); + let gap = GapConfig::Peripheral(PeripheralConfig { name: &name, appearance: &appearance::power_device::GENERIC_POWER_DEVICE, @@ -154,10 +156,9 @@ async fn gatt_client_server() { // Spawn central let central = local.spawn_local(async move { let controller_central = common::create_controller(¢ral).await; - let mut resources: HostResources = - HostResources::new(PacketQos::None); - let (stack, _peripheral, mut central, mut runner) = - trouble_host::new(controller_central, &mut resources).build(); + let mut resources: HostResources = HostResources::new(); + let stack = trouble_host::new(controller_central, &mut resources); + let (_, mut central, mut runner) = stack.build(); select! { r = runner.run() => { @@ -179,7 +180,7 @@ async fn gatt_client_server() { tokio::time::sleep(Duration::from_secs(5)).await; println!("[central] creating gatt client"); - let client = GattClient::::new(stack, &conn).await.unwrap(); + let client = GattClient::::new(&stack, &conn).await.unwrap(); select! { r = async { diff --git a/host/tests/l2cap.rs b/host/tests/l2cap.rs index 912f3f34..1a39a58c 100644 --- a/host/tests/l2cap.rs +++ b/host/tests/l2cap.rs @@ -26,10 +26,10 @@ async fn l2cap_connection_oriented_channels() { let peripheral = local.spawn_local(async move { let controller_peripheral = common::create_controller(&peripheral).await; - let mut resources: HostResources = HostResources::new(PacketQos::None); - let (stack, mut peripheral, _central, mut runner) = trouble_host::new(controller_peripheral, &mut resources) - .set_random_address(peripheral_address) - .build(); + let mut resources: HostResources = HostResources::new(); + let stack = trouble_host::new(controller_peripheral, &mut resources) + .set_random_address(peripheral_address); + let (mut peripheral, _, mut runner) = stack.build(); select! { r = runner.run() => { @@ -57,14 +57,14 @@ async fn l2cap_connection_oriented_channels() { let conn = acceptor.accept().await?; println!("[peripheral] connected"); - let mut ch1 = L2capChannel::accept(stack, &conn, &[0x2349], &Default::default()).await?; + let mut ch1 = L2capChannel::accept(&stack, &conn, &[0x2349], &Default::default()).await?; println!("[peripheral] channel created"); // Size of payload we're expecting let mut rx = [0; PAYLOAD_LEN]; for i in 0..10 { - let len = ch1.receive(stack, &mut rx).await?; + let len = ch1.receive(&stack, &mut rx).await?; assert_eq!(len, rx.len()); assert_eq!(rx, [i; PAYLOAD_LEN]); } @@ -72,7 +72,7 @@ async fn l2cap_connection_oriented_channels() { for i in 0..10 { let tx = [i; PAYLOAD_LEN]; - ch1.send::<_, MTU>(stack, &tx).await?; + ch1.send::<_, MTU>(&stack, &tx).await?; } println!("[peripheral] data sent"); break; @@ -87,10 +87,10 @@ async fn l2cap_connection_oriented_channels() { // Spawn central let central = local.spawn_local(async move { let controller_central = common::create_controller(¢ral).await; - let mut resources: HostResources = - HostResources::new(PacketQos::None); - let (stack, _peripheral, mut central, mut runner) = - trouble_host::new(controller_central, &mut resources).build(); + let mut resources: HostResources = HostResources::new(); + + let stack = trouble_host::new(controller_central, &mut resources); + let (_, mut central, mut runner) = stack.build(); select! { r = runner.run() => { @@ -110,16 +110,16 @@ async fn l2cap_connection_oriented_channels() { loop { let conn = central.connect(&config).await.unwrap(); println!("[central] connected"); - let mut ch1 = L2capChannel::create(stack, &conn, 0x2349, &Default::default()).await?; + let mut ch1 = L2capChannel::create(&stack, &conn, 0x2349, &Default::default()).await?; println!("[central] channel created"); for i in 0..10 { let tx = [i; PAYLOAD_LEN]; - ch1.send::<_, MTU>(stack, &tx).await?; + ch1.send::<_, MTU>(&stack, &tx).await?; } println!("[central] data sent"); let mut rx = [0; PAYLOAD_LEN]; for i in 0..10 { - let len = ch1.receive(stack, &mut rx).await?; + let len = ch1.receive(&stack, &mut rx).await?; assert_eq!(len, rx.len()); assert_eq!(rx, [i; PAYLOAD_LEN]); } From 51d9928c47151c016c801bd126a6bdf4e689d02d Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Mon, 20 Jan 2025 18:28:04 +0100 Subject: [PATCH 19/20] refactor: use a struct to hold the result of building the stack * Update examples to use destructuring. * Update tests to use destructuring * Add test compilation to ci.sh --- ci.sh | 3 ++ examples/apps/src/ble_advertise_multiple.rs | 6 ++- examples/apps/src/ble_bas_central.rs | 6 ++- examples/apps/src/ble_bas_peripheral.rs | 4 +- examples/apps/src/ble_l2cap_central.rs | 6 ++- examples/apps/src/ble_l2cap_peripheral.rs | 6 ++- examples/tests/tests/ble_l2cap_central.rs | 6 ++- examples/tests/tests/ble_l2cap_peripheral.rs | 6 ++- host/src/central.rs | 2 +- host/src/host.rs | 8 ++-- host/src/lib.rs | 40 +++++++++++--------- host/src/peripheral.rs | 2 +- host/tests/gatt.rs | 12 +++++- host/tests/gatt_derive.rs | 12 +++++- host/tests/l2cap.rs | 13 ++++++- 15 files changed, 95 insertions(+), 37 deletions(-) diff --git a/ci.sh b/ci.sh index 049dcfa8..f5361889 100755 --- a/ci.sh +++ b/ci.sh @@ -29,6 +29,7 @@ cargo batch \ --- build --release --manifest-path examples/nrf-sdc/Cargo.toml --target thumbv7em-none-eabihf --features nrf52832 \ --- build --release --manifest-path examples/esp32/Cargo.toml --features esp32c3 --target riscv32imc-unknown-none-elf --artifact-dir tests/esp32 \ --- build --release --manifest-path examples/serial-hci/Cargo.toml \ + --- build --release --manifest-path examples/tests/Cargo.toml \ --- build --release --manifest-path examples/rp-pico-w/Cargo.toml --target thumbv6m-none-eabi --features skip-cyw43-firmware \ --- build --release --manifest-path examples/rp-pico-2-w/Cargo.toml --target thumbv8m.main-none-eabihf --features skip-cyw43-firmware # --- build --release --manifest-path examples/apache-nimble/Cargo.toml --target thumbv7em-none-eabihf @@ -36,3 +37,5 @@ cargo batch \ cargo fmt --check --manifest-path ./host/Cargo.toml cargo clippy --manifest-path ./host/Cargo.toml --features gatt,peripheral,central cargo test --manifest-path ./host/Cargo.toml --lib -- --nocapture +cargo test --manifest-path ./host/Cargo.toml --no-run -- --nocapture +cargo test --manifest-path ./examples/tests/Cargo.toml --no-run -- --nocapture diff --git a/examples/apps/src/ble_advertise_multiple.rs b/examples/apps/src/ble_advertise_multiple.rs index 874ab6e9..557d5532 100644 --- a/examples/apps/src/ble_advertise_multiple.rs +++ b/examples/apps/src/ble_advertise_multiple.rs @@ -26,7 +26,11 @@ where let mut resources: HostResources = HostResources::new(); let stack = trouble_host::new(controller, &mut resources).set_random_address(address); - let (mut peripheral, _, mut runner) = stack.build(); + let Host { + mut peripheral, + mut runner, + .. + } = stack.build(); let mut adv_data = [0; 31]; let len = AdStructure::encode_slice( diff --git a/examples/apps/src/ble_bas_central.rs b/examples/apps/src/ble_bas_central.rs index 2b260563..9b605e18 100644 --- a/examples/apps/src/ble_bas_central.rs +++ b/examples/apps/src/ble_bas_central.rs @@ -19,7 +19,11 @@ where let mut resources: HostResources = HostResources::new(); let stack = trouble_host::new(controller, &mut resources).set_random_address(address); - let (_, mut central, mut runner) = stack.build(); + 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 diff --git a/examples/apps/src/ble_bas_peripheral.rs b/examples/apps/src/ble_bas_peripheral.rs index fab0cd0c..b3663b28 100644 --- a/examples/apps/src/ble_bas_peripheral.rs +++ b/examples/apps/src/ble_bas_peripheral.rs @@ -40,7 +40,9 @@ where let mut resources: HostResources = HostResources::new(); let stack = trouble_host::new(controller, &mut resources).set_random_address(address); - let (mut peripheral, _, runner) = stack.build(); + let Host { + mut peripheral, runner, .. + } = stack.build(); info!("Starting advertising and GATT service"); let server = Server::new_with_config(GapConfig::Peripheral(PeripheralConfig { diff --git a/examples/apps/src/ble_l2cap_central.rs b/examples/apps/src/ble_l2cap_central.rs index 96e8b47c..03919bd8 100644 --- a/examples/apps/src/ble_l2cap_central.rs +++ b/examples/apps/src/ble_l2cap_central.rs @@ -19,7 +19,11 @@ where let mut resources: HostResources = HostResources::new(); let stack = trouble_host::new(controller, &mut resources).set_random_address(address); - let (_, mut central, mut runner) = stack.build(); + 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 diff --git a/examples/apps/src/ble_l2cap_peripheral.rs b/examples/apps/src/ble_l2cap_peripheral.rs index 483f5ce2..f4c0de7d 100644 --- a/examples/apps/src/ble_l2cap_peripheral.rs +++ b/examples/apps/src/ble_l2cap_peripheral.rs @@ -18,7 +18,11 @@ where let mut resources: HostResources = HostResources::new(); let stack = trouble_host::new(controller, &mut resources).set_random_address(address); - let (mut peripheral, _, mut runner) = stack.build(); + let Host { + mut peripheral, + mut runner, + .. + } = stack.build(); let mut adv_data = [0; 31]; AdStructure::encode_slice( diff --git a/examples/tests/tests/ble_l2cap_central.rs b/examples/tests/tests/ble_l2cap_central.rs index c734acc7..ccc620c8 100644 --- a/examples/tests/tests/ble_l2cap_central.rs +++ b/examples/tests/tests/ble_l2cap_central.rs @@ -50,7 +50,11 @@ async fn run_l2cap_central_test(labels: &[(&str, &str)], firmware: &str) { let mut resources: HostResources<2, 4, 27> = HostResources::new(); let stack = trouble_host::new(controller_peripheral, &mut resources).set_random_address(peripheral_address); - let (mut peripheral, _central, mut runner) = stack.build(); + let Host { + mut peripheral, + mut runner, + .. + } = stack.build(); select! { r = runner.run() => { diff --git a/examples/tests/tests/ble_l2cap_peripheral.rs b/examples/tests/tests/ble_l2cap_peripheral.rs index 05fede83..4601c0bd 100644 --- a/examples/tests/tests/ble_l2cap_peripheral.rs +++ b/examples/tests/tests/ble_l2cap_peripheral.rs @@ -47,7 +47,11 @@ async fn run_l2cap_peripheral_test(labels: &[(&str, &str)], firmware: &str) { let controller_central = serial::create_controller(¢ral).await; let mut resources: HostResources<2, 4, 27> = HostResources::new(); let stack = trouble_host::new(controller_central, &mut resources); - let (_peripheral, mut central, mut runner) = stack.build(); + let Host { + mut central, + mut runner, + .. + } = stack.build(); select! { r = runner.run() => { r diff --git a/host/src/central.rs b/host/src/central.rs index 446c1ce0..5c8ca412 100644 --- a/host/src/central.rs +++ b/host/src/central.rs @@ -9,7 +9,7 @@ use bt_hci::param::{ConnHandleCompletedPackets, ControllerToHostFlowControl}; use embassy_futures::select::{select, Either}; /// A type implementing the BLE central role. -pub struct Central<'stack, C: Controller> { +pub struct Central<'stack, C> { pub(crate) stack: &'stack Stack<'stack, C>, } diff --git a/host/src/host.rs b/host/src/host.rs index f2d747fb..7a647d47 100644 --- a/host/src/host.rs +++ b/host/src/host.rs @@ -489,24 +489,24 @@ where } /// Runs the host with the given controller. -pub struct Runner<'d, C: Controller> { +pub struct Runner<'d, C> { rx: RxRunner<'d, C>, control: ControlRunner<'d, C>, tx: TxRunner<'d, C>, } /// The receiver part of the host runner. -pub struct RxRunner<'d, C: Controller> { +pub struct RxRunner<'d, C> { stack: &'d Stack<'d, C>, } /// The control part of the host runner. -pub struct ControlRunner<'d, C: Controller> { +pub struct ControlRunner<'d, C> { stack: &'d Stack<'d, C>, } /// The transmit part of the host runner. -pub struct TxRunner<'d, C: Controller> { +pub struct TxRunner<'d, C> { stack: &'d Stack<'d, C>, } diff --git a/host/src/lib.rs b/host/src/lib.rs index f01cab2f..9a0b0c3a 100644 --- a/host/src/lib.rs +++ b/host/src/lib.rs @@ -66,6 +66,7 @@ use host::{AdvHandleState, BleHost, HostMetrics, Runner}; #[allow(missing_docs)] pub mod prelude { + pub use super::Host; pub use bt_hci::uuid::*; #[cfg(feature = "derive")] pub use heapless::String as HeaplessString; @@ -416,6 +417,19 @@ pub struct Stack<'stack, C> { host: BleHost<'stack, C>, } +/// Host components. +#[non_exhaustive] +pub struct Host<'stack, C> { + /// Central role + #[cfg(feature = "central")] + pub central: Central<'stack, C>, + /// Peripheral role + #[cfg(feature = "peripheral")] + pub peripheral: Peripheral<'stack, C>, + /// Host runner + pub runner: Runner<'stack, C>, +} + impl<'stack, C: Controller> Stack<'stack, C> { /// Set the random address used by this host. pub fn set_random_address(mut self, address: Address) -> Self { @@ -424,24 +438,14 @@ impl<'stack, C: Controller> Stack<'stack, C> { } /// Build the stack. - #[cfg(all(feature = "central", feature = "peripheral"))] - pub fn build(&'stack self) -> (Peripheral<'stack, C>, Central<'stack, C>, Runner<'stack, C>) { - let stack = self; - (Peripheral::new(stack), Central::new(stack), Runner::new(stack)) - } - - /// Build the stack. - #[cfg(all(not(feature = "central"), feature = "peripheral"))] - pub fn build(&'stack self) -> (Peripheral<'stack, C>, Runner<'stack, C>) { - let stack = self; - (Peripheral::new(stack), Runner::new(stack)) - } - - /// Build the stack. - #[cfg(all(feature = "central", not(feature = "peripheral")))] - pub fn build(&'stack self) -> (Central<'stack, C>, Runner<'stack, C>) { - let stack = self; - (Central::new(stack), Runner::new(stack)) + pub fn build(&'stack self) -> Host<'stack, C> { + Host { + #[cfg(feature = "central")] + central: Central::new(self), + #[cfg(feature = "peripheral")] + peripheral: Peripheral::new(self), + runner: Runner::new(self), + } } /// Run a HCI command and return the response. diff --git a/host/src/peripheral.rs b/host/src/peripheral.rs index 788ef3a6..3f9d1205 100644 --- a/host/src/peripheral.rs +++ b/host/src/peripheral.rs @@ -13,7 +13,7 @@ use crate::connection::Connection; use crate::{Address, BleHostError, Error, Stack}; /// Type which implements the BLE peripheral role. -pub struct Peripheral<'d, C: Controller> { +pub struct Peripheral<'d, C> { stack: &'d Stack<'d, C>, } diff --git a/host/tests/gatt.rs b/host/tests/gatt.rs index f3c29460..6e3b48b8 100644 --- a/host/tests/gatt.rs +++ b/host/tests/gatt.rs @@ -34,7 +34,11 @@ async fn gatt_client_server() { let mut resources: HostResources = HostResources::new(); let stack = trouble_host::new(controller_peripheral, &mut resources) .set_random_address(peripheral_address); - let (mut peripheral, _, mut runner) = stack.build(); + let Host { + mut peripheral, + mut runner, + .. + } = stack.build(); let id = b"Trouble"; let appearance = [0x80, 0x07]; @@ -129,7 +133,11 @@ async fn gatt_client_server() { let controller_central = common::create_controller(¢ral).await; let mut resources: HostResources = HostResources::new(); let stack = trouble_host::new(controller_central, &mut resources); - let (_, mut central, mut runner) = stack.build(); + let Host { + mut central, + mut runner, + .. + } = stack.build(); select! { r = runner.run() => { diff --git a/host/tests/gatt_derive.rs b/host/tests/gatt_derive.rs index 5ebf44cd..2b989bfc 100644 --- a/host/tests/gatt_derive.rs +++ b/host/tests/gatt_derive.rs @@ -74,7 +74,11 @@ async fn gatt_client_server() { let mut resources: HostResources = HostResources::new(); let stack = trouble_host::new(controller_peripheral, &mut resources) .set_random_address(peripheral_address); - let (mut peripheral, _, mut runner) = stack.build(); + let Host { + mut peripheral, + mut runner, + .. + } = stack.build(); let gap = GapConfig::Peripheral(PeripheralConfig { name: &name, @@ -158,7 +162,11 @@ async fn gatt_client_server() { let controller_central = common::create_controller(¢ral).await; let mut resources: HostResources = HostResources::new(); let stack = trouble_host::new(controller_central, &mut resources); - let (_, mut central, mut runner) = stack.build(); + let Host { + mut central, + mut runner, + .. + } = stack.build(); select! { r = runner.run() => { diff --git a/host/tests/l2cap.rs b/host/tests/l2cap.rs index 1a39a58c..f50e9b0d 100644 --- a/host/tests/l2cap.rs +++ b/host/tests/l2cap.rs @@ -29,7 +29,12 @@ async fn l2cap_connection_oriented_channels() { let mut resources: HostResources = HostResources::new(); let stack = trouble_host::new(controller_peripheral, &mut resources) .set_random_address(peripheral_address); - let (mut peripheral, _, mut runner) = stack.build(); + let Host { + mut peripheral, + mut runner, + .. + } = stack.build(); + select! { r = runner.run() => { @@ -90,7 +95,11 @@ async fn l2cap_connection_oriented_channels() { let mut resources: HostResources = HostResources::new(); let stack = trouble_host::new(controller_central, &mut resources); - let (_, mut central, mut runner) = stack.build(); + let Host { + mut central, + mut runner, + .. + } = stack.build(); select! { r = runner.run() => { From e1940e89144096bc69b3c38745c3e13203135775 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Tue, 21 Jan 2025 09:16:04 +0100 Subject: [PATCH 20/20] refactor: add more stuff to prelude --- host/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/host/src/lib.rs b/host/src/lib.rs index 9a0b0c3a..648312bd 100644 --- a/host/src/lib.rs +++ b/host/src/lib.rs @@ -15,7 +15,6 @@ use core::mem::MaybeUninit; use advertise::AdvertisementDataError; use bt_hci::cmd::status::ReadRssi; use bt_hci::cmd::{AsyncCmd, SyncCmd}; -pub use bt_hci::param::{AddrKind, BdAddr, LeConnRole as Role}; use bt_hci::FromHciBytesError; use crate::att::AttErrorCode; @@ -23,6 +22,7 @@ use crate::channel_manager::{ChannelStorage, PacketChannel}; use crate::connection_manager::{ConnectionStorage, EventChannel}; use crate::l2cap::sar::SarType; use crate::packet_pool::PacketPool; +use bt_hci::param::{AddrKind, BdAddr}; mod fmt; @@ -67,6 +67,7 @@ use host::{AdvHandleState, BleHost, HostMetrics, Runner}; #[allow(missing_docs)] pub mod prelude { pub use super::Host; + pub use bt_hci::param::{AddrKind, BdAddr, LeConnRole as Role}; pub use bt_hci::uuid::*; #[cfg(feature = "derive")] pub use heapless::String as HeaplessString;