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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions nrf-softdevice-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ pub fn gatt_server(_args: TokenStream, item: TokenStream) -> TokenStream {

let mut code_register_init = TokenStream2::new();
let mut code_on_write = TokenStream2::new();
let mut code_on_notify_complete = TokenStream2::new();
let mut code_event_enum = TokenStream2::new();

let ble = quote!(::nrf_softdevice::ble);
Expand All @@ -96,6 +97,12 @@ pub fn gatt_server(_args: TokenStream, item: TokenStream) -> TokenStream {
return Some(#event_enum_name::#name_pascal(e));
}
));

code_on_notify_complete.extend(quote_spanned!(span=>
if let Some(e) = self.#name.on_notify_complete(handle) {
return Some(#event_enum_name::#name_pascal(e));
}
));
}
}

Expand Down Expand Up @@ -127,6 +134,13 @@ pub fn gatt_server(_args: TokenStream, item: TokenStream) -> TokenStream {
#code_on_write
None
}

fn on_notify_complete(&self, _conn: &::nrf_softdevice::ble::Connection, handle: u16) -> Option<Self::Event> {
use #ble::gatt_server::Service;

#code_on_notify_complete
None
}
}
};

Expand Down Expand Up @@ -213,6 +227,7 @@ pub fn gatt_service(args: TokenStream, item: TokenStream) -> TokenStream {
let mut code_build_chars = TokenStream2::new();
let mut code_struct_init = TokenStream2::new();
let mut code_on_write = TokenStream2::new();
let mut code_on_notify_complete = TokenStream2::new();
let mut code_event_enum = TokenStream2::new();

let ble = quote!(::nrf_softdevice::ble);
Expand Down Expand Up @@ -333,6 +348,17 @@ pub fn gatt_service(args: TokenStream, item: TokenStream) -> TokenStream {
}
));

// Emit a per-char DidNotify event when the notification completes
let case_did_notify = format_ident!("{}DidNotify", name_pascal);
code_event_enum.extend(quote_spanned!(ch.span=>
#case_did_notify,
));
code_on_notify_complete.extend(quote_spanned!(ch.span=>
if handle == self.#value_handle {
return Some(#event_enum_name::#case_did_notify);
}
));

if !indicate {
let case_cccd_write = format_ident!("{}CccdWrite", name_pascal);

Expand Down Expand Up @@ -433,6 +459,11 @@ pub fn gatt_service(args: TokenStream, item: TokenStream) -> TokenStream {
#code_on_write
None
}

fn on_notify_complete(&self, handle: u16) -> Option<Self::Event> {
#code_on_notify_complete
None
}
}

#[allow(unused)]
Expand Down
121 changes: 120 additions & 1 deletion nrf-softdevice/src/ble/gatt_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//! Typically the peripheral device is the GATT server, but it is not necessary.
//! In a connection any device can be server and client, and even both can be both at the same time.

use core::cell::UnsafeCell;
use core::convert::TryFrom;

use crate::ble::*;
Expand Down Expand Up @@ -127,6 +128,15 @@ pub trait Server: Sized {
None
}

/// Callback for a per-characteristic notification completion.
///
/// Called once for each completed notification with the characteristic's value handle.
/// Default implementation does nothing.
fn on_notify_complete(&self, conn: &Connection, handle: u16) -> Option<Self::Event> {
let _ = (conn, handle);
None
}

/// Callback to indicate that the indication of a characteristic has been received by the client.
fn on_indicate_confirm(&self, conn: &Connection, handle: u16) -> Option<Self::Event> {
let _ = (conn, handle);
Expand All @@ -149,6 +159,13 @@ pub trait Service: Sized {
type Event;

fn on_write(&self, handle: u16, data: &[u8]) -> Option<Self::Event>;

/// Handle per-characteristic notification completion.
///
/// Default implementation does nothing.
fn on_notify_complete(&self, _handle: u16) -> Option<Self::Event> {
None
}
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
Expand Down Expand Up @@ -177,6 +194,8 @@ where
.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 {
// Clear any pending notify tracking for this connection
notify_tracker_clear(conn_handle);
return Some(DisconnectedError);
}

Expand Down Expand Up @@ -247,7 +266,20 @@ where
}
raw::BLE_GATTS_EVTS_BLE_GATTS_EVT_HVN_TX_COMPLETE => {
let params = get_union_field(ble_evt, &gatts_evt.params.hvn_tx_complete);
server.on_notify_tx_complete(&conn, params.count)
// Emit per-notification completion events using the queued handles
let count = params.count;
for _ in 0..count {
if let Some(handle) = notify_tracker_pop(gatts_evt.conn_handle) {
if let Some(evt) = server.on_notify_complete(&conn, handle) {
f(evt)
}
} else {
// Queue underrun, nothing to pop
break;
}
}
// Backwards-compatible aggregate callback
server.on_notify_tx_complete(&conn, count)
}
raw::BLE_GATTS_EVTS_BLE_GATTS_EVT_HVC => {
let params = get_union_field(ble_evt, &gatts_evt.params.hvc);
Expand Down Expand Up @@ -353,6 +385,8 @@ pub fn notify_value(conn: &Connection, handle: u16, val: &[u8]) -> Result<(), No
};
let ret = unsafe { raw::sd_ble_gatts_hvx(conn_handle, &params) };
RawError::convert(ret)?;
// Track the handle so we can emit per-characteristic completion events when TX completes.
notify_tracker_push(conn_handle, handle);

Ok(())
}
Expand Down Expand Up @@ -473,3 +507,88 @@ 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]
}

// Simple per-connection FIFO to track the order of notified handles so we can
// emit per-characteristic completion callbacks upon BLE_GATTS_EVT_HVN_TX_COMPLETE.
const NOTIFY_TRACK_CAPACITY: usize = 16;

struct NotifyQueue {
buf: [u16; NOTIFY_TRACK_CAPACITY],
head: u8,
tail: u8,
}

impl NotifyQueue {
const fn new() -> Self {
Self {
buf: [0; NOTIFY_TRACK_CAPACITY],
head: 0,
tail: 0,
}
}

#[inline(always)]
fn is_full(&self) -> bool {
self.len() == NOTIFY_TRACK_CAPACITY as u8
}

#[inline(always)]
fn is_empty(&self) -> bool {
self.head == self.tail
}

#[inline(always)]
fn len(&self) -> u8 {
self.head.wrapping_sub(self.tail)
}

#[inline(always)]
fn push(&mut self, handle: u16) {
if self.is_full() {
// Drop oldest to make room
self.pop();
}
let idx = (self.head as usize) % NOTIFY_TRACK_CAPACITY;
unsafe { *self.buf.get_unchecked_mut(idx) = handle };
self.head = self.head.wrapping_add(1);
}

#[inline(always)]
fn pop(&mut self) -> Option<u16> {
if self.is_empty() {
return None;
}
let idx = (self.tail as usize) % NOTIFY_TRACK_CAPACITY;
let val = *unsafe { self.buf.get_unchecked(idx) };
self.tail = self.tail.wrapping_add(1);
Some(val)
}

#[inline(always)]
fn clear(&mut self) {
self.head = 0;
self.tail = 0;
}
}

const NOTIFY_QUEUE_INIT: UnsafeCell<NotifyQueue> = UnsafeCell::new(NotifyQueue::new());
static mut NOTIFY_QUEUES: [UnsafeCell<NotifyQueue>; CONNS_MAX] = [NOTIFY_QUEUE_INIT; CONNS_MAX];

fn notify_queue(conn_handle: u16) -> &'static mut NotifyQueue {
unsafe { &mut *NOTIFY_QUEUES[conn_handle as usize].get() }
}

fn notify_tracker_push(conn_handle: u16, handle: u16) {
let q = notify_queue(conn_handle);
q.push(handle);
}

fn notify_tracker_pop(conn_handle: u16) -> Option<u16> {
let q = notify_queue(conn_handle);
q.pop()
}

fn notify_tracker_clear(conn_handle: u16) {
let q = notify_queue(conn_handle);
q.clear();
}