Skip to content

Commit

Permalink
Merge pull request #195 from Univa/master
Browse files Browse the repository at this point in the history
GATT Client support for receiving notifications
  • Loading branch information
alexmoon authored Oct 16, 2023
2 parents 3b08bda + 7d424f3 commit 487f98e
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 1 deletion.
11 changes: 11 additions & 0 deletions examples/src/bin/ble_bas_central.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,15 @@ async fn main(spawner: Spawner) {
// Read to check it's changed
let val = unwrap!(client.battery_level_read().await);
info!("read battery level: {}", val);

// Enable battery level notifications from the peripheral
client.battery_level_cccd_write(true).await.unwrap();

// Receive notifications
gatt_client::run(&conn, &client, |event| match event {
BatteryServiceClientEvent::BatteryLevelNotification(val) => {
info!("battery level notification: {}", val);
}
})
.await;
}
60 changes: 59 additions & 1 deletion nrf-softdevice-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,7 @@ pub fn gatt_client(args: TokenStream, item: TokenStream) -> TokenStream {
let mut code_disc_char = TokenStream2::new();
let mut code_disc_done = TokenStream2::new();
let mut code_event_enum = TokenStream2::new();
let mut code_on_hvx = TokenStream2::new();

let ble = quote!(::nrf_softdevice::ble);

Expand All @@ -537,6 +538,7 @@ pub fn gatt_client(args: TokenStream, item: TokenStream) -> TokenStream {
let write_fn = format_ident!("{}_write", ch.name);
let write_wor_fn = format_ident!("{}_write_without_response", ch.name);
let write_try_wor_fn = format_ident!("{}_try_write_without_response", ch.name);
let cccd_write_fn = format_ident!("{}_cccd_write", ch.name);
let fn_vis = ch.vis.clone();

let uuid = ch.args.uuid;
Expand Down Expand Up @@ -647,6 +649,55 @@ pub fn gatt_client(args: TokenStream, item: TokenStream) -> TokenStream {
code_event_enum.extend(quote_spanned!(ch.span=>
#case_notification(#ty),
));
code_on_hvx.extend(quote_spanned!(ch.span=>
if handle == self.#value_handle && type_ == ::nrf_softdevice::ble::gatt_client::HvxType::Notification {
if data.len() < #ty_as_val::MIN_SIZE {
return None;
} else {
return Some(#event_enum_name::#case_notification(#ty_as_val::from_gatt(data)));
}
}
));

if !indicate {
code_impl.extend(quote_spanned!(ch.span=>
#fn_vis async fn #cccd_write_fn(&self, notifications: bool) -> Result<(), #ble::gatt_client::WriteError> {
#ble::gatt_client::write(&self.conn, self.#cccd_handle, &[if notifications { 0x01 } else { 0x00 }, 0x00]).await
}
));
}
}

if indicate {
let case_indication = format_ident!("{}Indication", name_pascal);
code_event_enum.extend(quote_spanned!(ch.span=>
#case_indication(#ty),
));
code_on_hvx.extend(quote_spanned!(ch.span=>
if handle == self.#value_handle && type_ == ::nrf_softdevice::ble::gatt_client::HvxType::Indication {
if data.len() < #ty_as_val::MIN_SIZE {
return None;
} else {
return Some(#event_enum_name::#case_indication(#ty_as_val::from_gatt(data)));
}
}
));

if !notify {
code_impl.extend(quote_spanned!(ch.span=>
#fn_vis async fn #cccd_write_fn(&self, indications: bool) -> Result<(), #ble::gatt_client::WriteError> {
#ble::gatt_client::write(&self.conn, self.#cccd_handle, &[if indications { 0x02 } else { 0x00 }, 0x00]).await
}
));
}
}

if indicate && notify {
code_impl.extend(quote_spanned!(ch.span=>
#fn_vis async fn #cccd_write_fn(&self, indications: bool, notifications: bool) -> Result<(), #ble::gatt_client::WriteError> {
#ble::gatt_client::write(&self.conn, self.#cccd_handle, &[if indications { 0x02 } else { 0x00 } | if notifications { 0x01 } else { 0x00 }, 0x00]).await
}
));
}
}

Expand All @@ -662,7 +713,14 @@ pub fn gatt_client(args: TokenStream, item: TokenStream) -> TokenStream {
}

impl #ble::gatt_client::Client for #struct_name {
//type Event = #event_enum_name;
type Event = #event_enum_name;

fn on_hvx(&self, _conn: &::nrf_softdevice::ble::Connection, type_: ::nrf_softdevice::ble::gatt_client::HvxType, handle: u16, data: &[u8]) -> Option<Self::Event> {
use #ble::gatt_client::Client;

#code_on_hvx
None
}

fn uuid() -> #ble::Uuid {
#uuid
Expand Down
81 changes: 81 additions & 0 deletions nrf-softdevice/src/ble/gatt_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,38 @@ pub struct Descriptor {
pub handle: u16,
}

#[repr(u8)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum HvxType {
Invalid = 0,
Notification,
Indication,
}

pub struct InvalidHvxTypeError;

impl TryFrom<u8> for HvxType {
type Error = InvalidHvxTypeError;

fn try_from(value: u8) -> Result<Self, Self::Error> {
match u32::from(value) {
raw::BLE_GATT_HVX_INVALID => Ok(HvxType::Invalid),
raw::BLE_GATT_HVX_NOTIFICATION => Ok(HvxType::Notification),
raw::BLE_GATT_HVX_INDICATION => Ok(HvxType::Indication),
_ => Err(InvalidHvxTypeError),
}
}
}

/// Trait for implementing GATT clients.
pub trait Client {
type Event;

/// Handles notification and indication events from the GATT server.
fn on_hvx(&self, conn: &Connection, type_: HvxType, handle: u16, data: &[u8]) -> Option<Self::Event>;

/// Get the UUID of the GATT service. This is used by [`discover`] to search for the
/// service in the GATT server.
fn uuid() -> Uuid;
Expand Down Expand Up @@ -579,3 +609,54 @@ static PORTALS: [Portal<*const raw::ble_evt_t>; CONNS_MAX] = [PORTAL_NEW; CONNS_
pub(crate) fn portal(conn_handle: u16) -> &'static Portal<*const raw::ble_evt_t> {
&PORTALS[conn_handle as usize]
}

pub async fn run<'a, F, C>(conn: &Connection, client: &C, mut f: F) -> DisconnectedError
where
F: FnMut(C::Event),
C: Client,
{
let handle = match conn.with_state(|state| state.check_connected()) {
Ok(handle) => handle,
Err(e) => return e,
};

portal(handle)
.wait_many(|ble_evt| unsafe {
let ble_evt = &*ble_evt;
if u32::from(ble_evt.header.evt_id) == raw::BLE_GAP_EVTS_BLE_GAP_EVT_DISCONNECTED {
return Some(DisconnectedError);
}

// We have a GATTC event
let gattc_evt = get_union_field(ble_evt, &ble_evt.evt.gattc_evt);
let conn = unwrap!(Connection::from_handle(gattc_evt.conn_handle));
let evt = match ble_evt.header.evt_id as u32 {
raw::BLE_GATTC_EVTS_BLE_GATTC_EVT_HVX => {
let params = get_union_field(ble_evt, &gattc_evt.params.hvx);
let v = get_flexarray(ble_evt, &params.data, params.len as usize);
trace!(
"GATT_HVX write handle={:?} type={:?} data={:?}",
params.handle,
params.type_,
v
);

match params.type_.try_into() {
Ok(type_) => client.on_hvx(&conn, type_, params.handle, v),
Err(_) => {
error!("gatt_client invalid hvx type: {}", params.type_);
None
}
}
}
_ => None,
};

if let Some(evt) = evt {
f(evt);
}

None
})
.await
}

0 comments on commit 487f98e

Please sign in to comment.