use core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU16, Ordering};
use embassy_futures::select::select;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use esp_idf_svc::ble::gap::{conn_find, BleAdvParams, GapEvent};
use esp_idf_svc::ble::gatt::att_mtu;
use esp_idf_svc::ble::gatt::server::{BleGattRegister, GattsEvent};
use esp_idf_svc::ble::{ensure_addr, BleDriver, BleError, BleUuid, HostEvent};
use esp_idf_svc::gatt_services;
use esp_idf_svc::hal::modem::BluetoothModemPeripheral;
use esp_idf_svc::sys::{
ble_gatt_svc_def, EspError, BLE_ATT_ERR_INSUFFICIENT_RES, BLE_GAP_CONN_MODE_UND,
BLE_GAP_DISC_MODE_GEN, BLE_HS_EDONE, BLE_OWN_ADDR_PUBLIC,
};
use ::log::{error, info, trace, warn};
use rs_matter_stack::ble::GattPeripheral;
use rs_matter_stack::matter::error::{Error, ErrorCode};
use rs_matter_stack::matter::transport::network::btp::{
AdvData, Btp, C1_CHARACTERISTIC_UUID, C2_CHARACTERISTIC_UUID, C3_CHARACTERISTIC_UUID,
MATTER_BLE_SERVICE_UUID16,
};
use rs_matter_stack::matter::transport::network::BtAddr;
use rs_matter_stack::matter::utils::cell::RefCell;
use rs_matter_stack::matter::utils::init::{init, Init};
use rs_matter_stack::matter::utils::select::Coalesce;
use rs_matter_stack::matter::utils::storage::Vec;
use rs_matter_stack::matter::utils::sync::blocking::Mutex;
use rs_matter_stack::matter::utils::sync::Signal;
const MAX_MTU_SIZE: usize = 512;
const MAX_ADV_DATA_SIZE: usize = 32;
const SVC_UUID: BleUuid = BleUuid::uuid16(MATTER_BLE_SERVICE_UUID16);
const C1_UUID: BleUuid = BleUuid::uuid128(C1_CHARACTERISTIC_UUID);
const C2_UUID: BleUuid = BleUuid::uuid128(C2_CHARACTERISTIC_UUID);
const C3_UUID: BleUuid = BleUuid::uuid128(C3_CHARACTERISTIC_UUID);
gatt_services!(SERVICES {
primary(SVC_UUID) {
chr(C1_UUID, Write);
chr(C2_UUID, Indicate);
chr(C3_UUID, Read);
}
});
static C2_VAL_HANDLE: AtomicU16 = AtomicU16::new(0);
static CONTEXT: AtomicPtr<EspBtpGattContext> = AtomicPtr::new(core::ptr::null_mut());
fn context() -> Option<&'static EspBtpGattContext> {
unsafe { CONTEXT.load(Ordering::SeqCst).as_ref() }
}
#[derive(Debug, Clone)]
struct Connection {
peer: BtAddr,
conn_handle: u16,
subscribed: bool,
mtu: Option<u16>,
}
struct State {
connection: Option<Connection>,
conn_gen: usize,
btp_gen: Option<usize>,
in_data: Vec<u8, MAX_MTU_SIZE>,
out_data: Vec<u8, MAX_MTU_SIZE>,
need_advertise: bool,
adv_data: Vec<u8, MAX_ADV_DATA_SIZE>,
}
impl State {
#[inline(always)]
const fn new() -> Self {
Self {
connection: None,
conn_gen: 0,
btp_gen: None,
in_data: Vec::new(),
out_data: Vec::new(),
need_advertise: false,
adv_data: Vec::new(),
}
}
fn init() -> impl Init<Self> {
init!(Self {
connection: None,
conn_gen: 0,
btp_gen: None,
in_data <- Vec::init(),
out_data <- Vec::init(),
need_advertise: false,
adv_data <- Vec::init(),
})
}
fn sync_btp(&mut self, btp: &Btp) {
if self.btp_gen != Some(self.conn_gen) {
btp.reset();
self.btp_gen = Some(self.conn_gen);
}
}
}
pub struct EspBtpGattContext {
state: Mutex<RefCell<State>, CriticalSectionRawMutex>,
out_nack: AtomicBool,
notify_process_incoming: Signal<Option<()>, CriticalSectionRawMutex>,
notify_process_outgoing: Signal<Option<()>, CriticalSectionRawMutex>,
}
impl EspBtpGattContext {
#[allow(clippy::large_stack_frames)]
#[inline(always)]
pub const fn new() -> Self {
Self {
state: Mutex::new(RefCell::new(State::new())),
out_nack: AtomicBool::new(false),
notify_process_incoming: Signal::new(None),
notify_process_outgoing: Signal::new(None),
}
}
#[allow(clippy::large_stack_frames)]
pub fn init() -> impl Init<Self> {
init!(Self {
state <- Mutex::init(RefCell::init(State::init())),
out_nack: AtomicBool::new(false),
notify_process_incoming <- Signal::init(None),
notify_process_outgoing <- Signal::init(None),
})
}
pub(crate) fn reset(&self) -> Result<(), EspError> {
self.state.lock(|state| {
let mut state = state.borrow_mut();
state.connection = None;
state.btp_gen = None;
state.in_data.clear();
state.out_data.clear();
state.adv_data.clear();
});
self.out_nack.store(false, Ordering::SeqCst);
self.notify_process_incoming.modify(|state| {
*state = None;
(false, ())
});
self.notify_process_outgoing.modify(|state| {
*state = None;
(false, ())
});
Ok(())
}
}
impl Default for EspBtpGattContext {
#[allow(clippy::large_stack_frames)]
fn default() -> Self {
Self::new()
}
}
type ServiceTable = &'static (dyn AsRef<[ble_gatt_svc_def]> + Sync);
pub struct EspBtpGattPeripheral<'a, 'd> {
driver: BleDriver<'d, ServiceTable>,
context: &'a EspBtpGattContext,
}
impl<'a, 'd> EspBtpGattPeripheral<'a, 'd> {
pub fn new<B: BluetoothModemPeripheral + 'd>(
modem: B,
context: &'a EspBtpGattContext,
) -> Result<Self, EspError> {
context.reset()?;
let driver = BleDriver::new_with_services(modem, &SERVICES as ServiceTable)?;
Ok(Self { driver, context })
}
pub async fn run(
&mut self,
btp: &Btp,
service_name: &str,
service_adv_data: &AdvData,
) -> Result<(), Error> {
self.context.state.lock(|state| {
let mut state = state.borrow_mut();
state.adv_data.clear();
for byte in service_adv_data.iter() {
state
.adv_data
.push(byte)
.map_err(|_| Error::new(ErrorCode::NoSpace))?;
}
Ok::<_, Error>(())
})?;
CONTEXT.store(
self.context as *const _ as *mut EspBtpGattContext,
Ordering::SeqCst,
);
self.subscribe_hooks();
self.driver
.set_device_name(service_name)
.map_err(to_matter_err_ble)?;
info!("BTP service registered, device name set to `{service_name}`");
self.driver.start().map_err(to_matter_err)?;
info!("NimBLE host task started");
select(self.process_incoming(btp), self.process_outgoing(btp))
.coalesce()
.await
}
fn subscribe_hooks(&self) {
self.driver.host_subscribe(|event| {
if matches!(event, HostEvent::Sync) {
if let Some(context) = context() {
context.state.lock(|s| s.borrow_mut().need_advertise = true);
context.notify_process_outgoing.signal(());
}
}
});
self.driver.gap_subscribe(|event| {
let Some(context) = context() else {
return 0;
};
match event {
GapEvent::Connect {
conn_handle,
status,
} => {
if status.is_err() {
warn!("BLE connection failed: {status:?}");
context.state.lock(|s| s.borrow_mut().need_advertise = true);
context.notify_process_outgoing.signal(());
return 0;
}
let peer = conn_find(conn_handle)
.map(|desc| BtAddr(desc.peer_addr().val()))
.unwrap_or(BtAddr([0; 6]));
let mtu = att_mtu(conn_handle).ok();
context.state.lock(|state| {
let mut state = state.borrow_mut();
state.conn_gen = state.conn_gen.wrapping_add(1);
state.in_data.clear();
state.out_data.clear();
state.connection = Some(Connection {
peer,
conn_handle,
subscribed: false,
mtu,
});
});
context.out_nack.store(false, Ordering::SeqCst);
info!("BLE connected, handle: {conn_handle}");
context.notify_process_incoming.signal(());
}
GapEvent::Disconnect { reason, .. } => {
context.state.lock(|state| {
let mut state = state.borrow_mut();
state.connection = None;
state.in_data.clear();
state.out_data.clear();
state.need_advertise = true;
});
context.out_nack.store(false, Ordering::SeqCst);
info!("BLE disconnected, reason: {reason}");
context.notify_process_incoming.signal(());
context.notify_process_outgoing.signal(());
}
GapEvent::Mtu { conn_handle, value } => {
context.state.lock(|state| {
let mut state = state.borrow_mut();
if let Some(conn) = state.connection.as_mut() {
if conn.conn_handle == conn_handle {
conn.mtu = Some(value);
}
}
});
trace!("MTU negotiated: {value}");
}
_ => {}
}
0
});
self.driver.gatts_subscribe(|event| {
let Some(context) = context() else {
return 0;
};
match event {
GattsEvent::Register(reg) => {
if let BleGattRegister::Characteristic {
uuid, val_handle, ..
} = reg
{
if uuid == C2_UUID {
C2_VAL_HANDLE.store(val_handle, Ordering::SeqCst);
}
}
}
GattsEvent::Write { data, .. } => {
let result = context.state.lock(|state| {
let mut state = state.borrow_mut();
if !state.in_data.is_empty() {
return Err(());
}
state.in_data.resize_default(MAX_MTU_SIZE).map_err(|_| ())?;
let len = data.read(&mut state.in_data).map_err(|_| ())?;
state.in_data.truncate(len);
Ok(())
});
if result.is_err() {
return BLE_ATT_ERR_INSUFFICIENT_RES as u8;
}
context.notify_process_incoming.signal(());
}
GattsEvent::Read { .. } => {}
GattsEvent::SubscriptionChanged {
conn_handle,
attr_handle,
cur_indicate,
..
} => {
if attr_handle == C2_VAL_HANDLE.load(Ordering::SeqCst) {
context.state.lock(|state| {
let mut state = state.borrow_mut();
if let Some(conn) = state.connection.as_mut() {
if conn.conn_handle == conn_handle {
conn.subscribed = cur_indicate;
}
}
});
info!(
"Peer {} to `C2`",
if cur_indicate {
"subscribed"
} else {
"unsubscribed"
}
);
context.notify_process_outgoing.signal(());
}
}
GattsEvent::NotifyComplete {
indication, status, ..
} => {
if indication && (status == BLE_HS_EDONE as i32 || status != 0) {
context.out_nack.store(false, Ordering::SeqCst);
context.notify_process_outgoing.signal(());
}
}
}
0
});
}
async fn process_incoming(&self, btp: &Btp) -> Result<(), Error> {
loop {
let processed = self.context.state.lock(|state| {
let mut state = state.borrow_mut();
let conn = state.connection.as_ref().map(|c| (c.mtu, c.peer));
if let Some((mtu, peer)) = conn {
state.sync_btp(btp);
if !state.in_data.is_empty() {
btp.process_incoming(mtu, peer, &state.in_data)?;
state.in_data.clear();
return Ok::<_, Error>(true);
}
}
Ok(false)
})?;
if !processed {
self.context.notify_process_incoming.wait_signalled().await;
}
}
}
async fn process_outgoing(&self, btp: &Btp) -> Result<(), Error> {
loop {
if self
.context
.state
.lock(|s| core::mem::take(&mut s.borrow_mut().need_advertise))
{
if let Err(e) = self.advertise() {
error!("Cannot start advertising: {e:?}");
}
}
let processed = self.context.state.lock(|state| {
let mut state = state.borrow_mut();
let state = &mut *state;
let (mtu, conn_handle) = match state.connection.as_ref() {
Some(conn) if conn.subscribed => (conn.mtu, conn.conn_handle),
_ => return Ok::<_, Error>(false),
};
state.sync_btp(btp);
if self.context.out_nack.load(Ordering::SeqCst) {
return Ok(false);
}
let c2_handle = C2_VAL_HANDLE.load(Ordering::SeqCst);
if c2_handle == 0 {
return Ok(false);
}
state.out_data.resize_default(MAX_MTU_SIZE).unwrap();
let len = btp.process_outgoing(mtu, &mut state.out_data)?;
if len == 0 {
return Ok(false);
}
self.context.out_nack.store(true, Ordering::SeqCst);
match self
.driver
.indicate(conn_handle, c2_handle, &state.out_data[..len])
{
Ok(()) => {
trace!("Indicated {len} bytes");
Ok(true)
}
Err(e) => {
self.context.out_nack.store(false, Ordering::SeqCst);
Err(to_matter_err_ble(e))
}
}
})?;
if !processed {
select(
btp.wait_outgoing(),
self.context.notify_process_outgoing.wait_signalled(),
)
.coalesce()
.await;
}
}
}
fn advertise(&self) -> Result<(), Error> {
ensure_addr(false).map_err(to_matter_err_ble)?;
let adv_data = self
.context
.state
.lock(|state| state.borrow().adv_data.clone());
self.driver
.adv_set_data(&adv_data)
.map_err(to_matter_err_ble)?;
let params = BleAdvParams {
conn_mode: BLE_GAP_CONN_MODE_UND as u8,
disc_mode: BLE_GAP_DISC_MODE_GEN as u8,
..Default::default()
};
self.driver
.adv_start(BLE_OWN_ADDR_PUBLIC as u8, ¶ms)
.map_err(to_matter_err_ble)?;
info!("Advertising started");
Ok(())
}
}
impl Drop for EspBtpGattPeripheral<'_, '_> {
fn drop(&mut self) {
CONTEXT.store(core::ptr::null_mut(), Ordering::SeqCst);
}
}
impl GattPeripheral for EspBtpGattPeripheral<'_, '_> {
async fn run(
&mut self,
btp: &Btp,
service_name: &str,
adv_data: &AdvData,
) -> Result<(), Error> {
EspBtpGattPeripheral::run(self, btp, service_name, adv_data).await
}
}
fn to_matter_err(e: EspError) -> Error {
error!("BLE error: {e:?}");
Error::new(ErrorCode::NoNetworkInterface)
}
fn to_matter_err_ble(e: BleError) -> Error {
error!("BLE error: {e:?}");
Error::new(ErrorCode::NoNetworkInterface)
}
#[cfg(esp_idf_bt_nimble_gatt_client)]
pub use central::{EspBtpGattClient, EspBtpGattClientContext};
#[cfg(esp_idf_bt_nimble_gatt_client)]
mod central {
use core::sync::atomic::{AtomicPtr, Ordering};
use embassy_futures::select::select;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use esp_idf_svc::ble::gap::GapEvent;
use esp_idf_svc::ble::gatt::client::GattcEvent;
use esp_idf_svc::ble::{ensure_addr, BleAddr, BleDriver, HostEvent};
use esp_idf_svc::hal::modem::BluetoothModemPeripheral;
use esp_idf_svc::sys::{EspError, BLE_OWN_ADDR_PUBLIC};
use ::log::{error, info, trace};
use rs_matter_stack::matter::error::{Error, ErrorCode};
use rs_matter_stack::matter::transport::network::btp::Btp;
use rs_matter_stack::matter::transport::network::BtAddr;
use rs_matter_stack::matter::utils::cell::RefCell;
use rs_matter_stack::matter::utils::init::{init, Init};
use rs_matter_stack::matter::utils::select::Coalesce;
use rs_matter_stack::matter::utils::storage::Vec;
use rs_matter_stack::matter::utils::sync::blocking::Mutex;
use rs_matter_stack::matter::utils::sync::Signal;
use super::{to_matter_err, to_matter_err_ble, C1_UUID, C2_UUID, MAX_MTU_SIZE, SVC_UUID};
const PEER_ADDR_TYPE: u8 = 0;
static CLIENT_CONTEXT: AtomicPtr<EspBtpGattClientContext> =
AtomicPtr::new(core::ptr::null_mut());
fn client_context() -> Option<&'static EspBtpGattClientContext> {
unsafe { CLIENT_CONTEXT.load(Ordering::SeqCst).as_ref() }
}
struct State {
synced: bool,
conn_handle: Option<u16>,
failed: bool,
mtu: Option<u16>,
peer: BtAddr,
matter_range: Option<(u16, u16)>,
services_done: bool,
c1_val: Option<u16>,
c2_val: Option<u16>,
chars_done: bool,
subscribed: bool,
in_data: Vec<u8, MAX_MTU_SIZE>,
out_data: Vec<u8, MAX_MTU_SIZE>,
out_inflight: bool,
}
impl State {
const fn new() -> Self {
Self {
synced: false,
conn_handle: None,
failed: false,
mtu: None,
peer: BtAddr([0; 6]),
matter_range: None,
services_done: false,
c1_val: None,
c2_val: None,
chars_done: false,
subscribed: false,
in_data: Vec::new(),
out_data: Vec::new(),
out_inflight: false,
}
}
fn init() -> impl Init<Self> {
init!(Self {
synced: false,
conn_handle: None,
failed: false,
mtu: None,
peer: BtAddr([0; 6]),
matter_range: None,
services_done: false,
c1_val: None,
c2_val: None,
chars_done: false,
subscribed: false,
in_data <- Vec::init(),
out_data <- Vec::init(),
out_inflight: false,
})
}
}
pub struct EspBtpGattClientContext {
state: Mutex<RefCell<State>, CriticalSectionRawMutex>,
notify_progress: Signal<Option<()>, CriticalSectionRawMutex>,
notify_in: Signal<Option<()>, CriticalSectionRawMutex>,
notify_out: Signal<Option<()>, CriticalSectionRawMutex>,
}
impl EspBtpGattClientContext {
#[inline(always)]
pub const fn new() -> Self {
Self {
state: Mutex::new(RefCell::new(State::new())),
notify_progress: Signal::new(None),
notify_in: Signal::new(None),
notify_out: Signal::new(None),
}
}
pub fn init() -> impl Init<Self> {
init!(Self {
state <- Mutex::init(RefCell::init(State::init())),
notify_progress <- Signal::init(None),
notify_in <- Signal::init(None),
notify_out <- Signal::init(None),
})
}
pub(crate) fn reset(&self) -> Result<(), EspError> {
self.state.lock(|state| *state.borrow_mut() = State::new());
for signal in [&self.notify_progress, &self.notify_in, &self.notify_out] {
signal.modify(|state| {
*state = None;
(false, ())
});
}
Ok(())
}
}
impl Default for EspBtpGattClientContext {
fn default() -> Self {
Self::new()
}
}
pub struct EspBtpGattClient<'a, 'd> {
driver: BleDriver<'d, ()>,
context: &'a EspBtpGattClientContext,
}
impl<'a, 'd> EspBtpGattClient<'a, 'd> {
pub fn new<B: BluetoothModemPeripheral + 'd>(
modem: B,
context: &'a EspBtpGattClientContext,
) -> Result<Self, EspError> {
context.reset()?;
let driver = BleDriver::new(modem)?;
Ok(Self { driver, context })
}
pub async fn run(&mut self, btp: &Btp, addr: BtAddr) -> Result<(), Error> {
self.context
.state
.lock(|state| state.borrow_mut().peer = addr);
CLIENT_CONTEXT.store(
self.context as *const _ as *mut EspBtpGattClientContext,
Ordering::SeqCst,
);
self.subscribe_hooks();
self.driver.start().map_err(to_matter_err)?;
self.wait_state(|s| s.synced).await;
ensure_addr(false).map_err(to_matter_err_ble)?;
let peer = BleAddr::new(PEER_ADDR_TYPE, addr.0);
self.driver
.connect(BLE_OWN_ADDR_PUBLIC as u8, &peer)
.map_err(to_matter_err_ble)?;
info!("Connecting to commissionable device {addr}");
self.wait_state(|s| s.conn_handle.is_some() || s.failed)
.await;
let conn = self
.connected_handle()
.ok_or_else(|| Error::new(ErrorCode::NoNetworkInterface))?;
self.driver
.discover_services(conn)
.map_err(to_matter_err_ble)?;
self.wait_state(|s| s.services_done || s.failed).await;
let (start, end) = self
.context
.state
.lock(|s| s.borrow().matter_range)
.ok_or_else(|| {
error!("Matter BTP service not found on peer");
Error::new(ErrorCode::NoNetworkInterface)
})?;
self.driver
.discover_characteristics(conn, start, end)
.map_err(to_matter_err_ble)?;
self.wait_state(|s| s.chars_done || s.failed).await;
let (c1_val, c2_val) = self.context.state.lock(|s| {
let s = s.borrow();
(s.c1_val, s.c2_val)
});
let (c1_val, c2_val) = match (c1_val, c2_val) {
(Some(c1), Some(c2)) => (c1, c2),
_ => {
error!("Matter C1/C2 characteristics not found on peer");
return Err(Error::new(ErrorCode::NoNetworkInterface));
}
};
info!("Discovered Matter C1 (handle {c1_val}) / C2 (handle {c2_val})");
self.driver
.write(conn, c2_val + 1, &[0x02, 0x00])
.map_err(to_matter_err_ble)?;
self.wait_state(|s| s.subscribed || s.failed).await;
if self.context.state.lock(|s| s.borrow().failed) {
return Err(Error::new(ErrorCode::NoNetworkInterface));
}
info!("Subscribed to C2; driving BTP as initiator");
btp.set_initiator(true);
select(
self.process_incoming(btp),
self.process_outgoing(btp, conn, c1_val),
)
.coalesce()
.await
}
fn subscribe_hooks(&self) {
self.driver.host_subscribe(|event| {
if matches!(event, HostEvent::Sync) {
if let Some(context) = client_context() {
context.state.lock(|s| s.borrow_mut().synced = true);
context.notify_progress.signal(());
}
}
});
self.driver.gap_subscribe(|event| {
let Some(context) = client_context() else {
return 0;
};
match event {
GapEvent::Connect {
conn_handle,
status,
} => {
context.state.lock(|s| {
let mut s = s.borrow_mut();
if status.is_ok() {
s.conn_handle = Some(conn_handle);
} else {
s.failed = true;
}
});
context.notify_progress.signal(());
}
GapEvent::Disconnect { .. } => {
context.state.lock(|s| s.borrow_mut().failed = true);
context.notify_progress.signal(());
context.notify_in.signal(());
context.notify_out.signal(());
}
GapEvent::Mtu { conn_handle, value } => {
context.state.lock(|s| {
let mut s = s.borrow_mut();
if s.conn_handle == Some(conn_handle) {
s.mtu = Some(value);
}
});
}
_ => {}
}
0
});
self.driver.gattc_subscribe(|event| {
let Some(context) = client_context() else {
return;
};
match event {
GattcEvent::Service { service, .. } => match service {
Some(service) => {
if service.uuid == SVC_UUID {
context.state.lock(|s| {
s.borrow_mut().matter_range =
Some((service.start_handle, service.end_handle));
});
}
}
None => {
context.state.lock(|s| s.borrow_mut().services_done = true);
context.notify_progress.signal(());
}
},
GattcEvent::Characteristic { chr, .. } => match chr {
Some(chr) => context.state.lock(|s| {
let mut s = s.borrow_mut();
if chr.uuid == C1_UUID {
s.c1_val = Some(chr.val_handle);
} else if chr.uuid == C2_UUID {
s.c2_val = Some(chr.val_handle);
}
}),
None => {
context.state.lock(|s| s.borrow_mut().chars_done = true);
context.notify_progress.signal(());
}
},
GattcEvent::WriteComplete {
attr_handle,
status,
..
} => {
let (c2_val, c1_val) = context
.state
.lock(|s| (s.borrow().c2_val, s.borrow().c1_val));
if Some(attr_handle) == c2_val.map(|h| h + 1) {
context
.state
.lock(|s| s.borrow_mut().subscribed = status == 0);
context.notify_progress.signal(());
} else if Some(attr_handle) == c1_val {
context.state.lock(|s| s.borrow_mut().out_inflight = false);
context.notify_out.signal(());
}
}
GattcEvent::Notify {
attr_handle, data, ..
} => {
let c2_val = context.state.lock(|s| s.borrow().c2_val);
if Some(attr_handle) == c2_val {
context.state.lock(|s| {
let mut s = s.borrow_mut();
if s.in_data.is_empty()
&& s.in_data.resize_default(MAX_MTU_SIZE).is_ok()
{
if let Ok(len) = data.read(&mut s.in_data) {
s.in_data.truncate(len);
} else {
s.in_data.clear();
}
}
});
context.notify_in.signal(());
}
}
GattcEvent::ReadComplete { .. } => {}
}
});
}
fn connected_handle(&self) -> Option<u16> {
self.context.state.lock(|s| s.borrow().conn_handle)
}
async fn wait_state(&self, pred: impl Fn(&State) -> bool) {
loop {
if self.context.state.lock(|s| pred(&s.borrow())) {
return;
}
self.context.notify_progress.wait_signalled().await;
}
}
async fn process_incoming(&self, btp: &Btp) -> Result<(), Error> {
loop {
let processed = self.context.state.lock(|state| {
let mut state = state.borrow_mut();
if state.failed {
return Err(Error::new(ErrorCode::NoNetworkInterface));
}
if state.in_data.is_empty() {
return Ok(false);
}
let mtu = state.mtu;
let peer = state.peer;
btp.process_incoming(mtu, peer, &state.in_data)?;
state.in_data.clear();
Ok::<_, Error>(true)
})?;
if !processed {
self.context.notify_in.wait_signalled().await;
}
}
}
async fn process_outgoing(&self, btp: &Btp, conn: u16, c1_val: u16) -> Result<(), Error> {
loop {
let processed = self.context.state.lock(|state| {
let mut state = state.borrow_mut();
if state.failed {
return Err(Error::new(ErrorCode::NoNetworkInterface));
}
if state.out_inflight {
return Ok(false);
}
let mtu = state.mtu;
state.out_data.resize_default(MAX_MTU_SIZE).unwrap();
let len = btp.process_outgoing(mtu, &mut state.out_data)?;
if len > 0 {
self.driver
.write(conn, c1_val, &state.out_data[..len])
.map_err(to_matter_err_ble)?;
state.out_inflight = true;
trace!("Wrote {len} bytes to C1");
Ok(true)
} else {
Ok::<_, Error>(false)
}
})?;
if !processed {
select(
btp.wait_outgoing(),
self.context.notify_out.wait_signalled(),
)
.coalesce()
.await;
}
}
}
}
impl Drop for EspBtpGattClient<'_, '_> {
fn drop(&mut self) {
CLIENT_CONTEXT.store(core::ptr::null_mut(), Ordering::SeqCst);
}
}
}