Skip to content

extmod/modbluetooth: Add send_update arg to gatts_write. #7564

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
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
19 changes: 13 additions & 6 deletions docs/library/bluetooth.rst
Original file line number Diff line number Diff line change
Expand Up @@ -485,10 +485,14 @@ writes from a client to a given characteristic, use
Reads the local value for this handle (which has either been written by
:meth:`gatts_write <BLE.gatts_write>` or by a remote client).

.. method:: BLE.gatts_write(value_handle, data, /)
.. method:: BLE.gatts_write(value_handle, data, send_update=False, /)

Writes the local value for this handle, which can be read by a client.

If *send_update* is ``True``, then any subscribed clients will be notified
(or indicated, depending on what they're subscribed to and which operations
the characteristic supports) about this write.

.. method:: BLE.gatts_notify(conn_handle, value_handle, data=None, /)

Sends a notification request to a connected client.
Expand All @@ -499,17 +503,20 @@ writes from a client to a given characteristic, use
Otherwise, if *data* is ``None``, then the current local value (as
set with :meth:`gatts_write <BLE.gatts_write>`) will be sent.

.. method:: BLE.gatts_indicate(conn_handle, value_handle, /)
**Note:** The notification will be sent regardless of the subscription
status of the client to this characteristic.

Sends an indication request to a connected client.
.. method:: BLE.gatts_indicate(conn_handle, value_handle, /)

**Note:** This does not currently support sending a custom value, it will
always send the current local value (as set with :meth:`gatts_write
<BLE.gatts_write>`).
Sends an indication request containing the characteristic's current value to
a connected client.

On acknowledgment (or failure, e.g. timeout), the
``_IRQ_GATTS_INDICATE_DONE`` event will be raised.

**Note:** The indication will be sent regardless of the subscription
status of the client to this characteristic.

.. method:: BLE.gatts_set_buffer(value_handle, len, append=False, /)

Sets the internal buffer size for a value in bytes. This will limit the
Expand Down
5 changes: 4 additions & 1 deletion extmod/btstack/modbluetooth_btstack.c
Original file line number Diff line number Diff line change
Expand Up @@ -1085,11 +1085,14 @@ int mp_bluetooth_gatts_read(uint16_t value_handle, uint8_t **value, size_t *valu
return mp_bluetooth_gatts_db_read(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, value_handle, value, value_len);
}

int mp_bluetooth_gatts_write(uint16_t value_handle, const uint8_t *value, size_t value_len) {
int mp_bluetooth_gatts_write(uint16_t value_handle, const uint8_t *value, size_t value_len, bool send_update) {
DEBUG_printf("mp_bluetooth_gatts_write\n");
if (!mp_bluetooth_is_active()) {
return ERRNO_BLUETOOTH_NOT_ACTIVE;
}
if (send_update) {
return MP_EOPNOTSUPP;
}
return mp_bluetooth_gatts_db_write(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, value_handle, value, value_len);
}

Expand Down
14 changes: 8 additions & 6 deletions extmod/modbluetooth.c
Original file line number Diff line number Diff line change
Expand Up @@ -717,15 +717,17 @@ STATIC mp_obj_t bluetooth_ble_gatts_read(mp_obj_t self_in, mp_obj_t value_handle
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(bluetooth_ble_gatts_read_obj, bluetooth_ble_gatts_read);

STATIC mp_obj_t bluetooth_ble_gatts_write(mp_obj_t self_in, mp_obj_t value_handle_in, mp_obj_t data) {
(void)self_in;
STATIC mp_obj_t bluetooth_ble_gatts_write(size_t n_args, const mp_obj_t *args) {
mp_buffer_info_t bufinfo = {0};
mp_get_buffer_raise(data, &bufinfo, MP_BUFFER_READ);
int err = mp_bluetooth_gatts_write(mp_obj_get_int(value_handle_in), bufinfo.buf, bufinfo.len);
bluetooth_handle_errno(err);
mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_READ);
bool send_update = false;
if (n_args > 3) {
send_update = mp_obj_is_true(args[3]);
}
bluetooth_handle_errno(mp_bluetooth_gatts_write(mp_obj_get_int(args[1]), bufinfo.buf, bufinfo.len, send_update));
return MP_OBJ_NEW_SMALL_INT(bufinfo.len);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_3(bluetooth_ble_gatts_write_obj, bluetooth_ble_gatts_write);
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gatts_write_obj, 3, 4, bluetooth_ble_gatts_write);

STATIC mp_obj_t bluetooth_ble_gatts_notify(size_t n_args, const mp_obj_t *args) {
mp_int_t conn_handle = mp_obj_get_int(args[1]);
Expand Down
4 changes: 2 additions & 2 deletions extmod/modbluetooth.h
Original file line number Diff line number Diff line change
Expand Up @@ -334,8 +334,8 @@ int mp_bluetooth_gatts_register_service_end(void);

// Read the value from the local gatts db (likely this has been written by a central).
int mp_bluetooth_gatts_read(uint16_t value_handle, uint8_t **value, size_t *value_len);
// Write a value to the local gatts db (ready to be queried by a central).
int mp_bluetooth_gatts_write(uint16_t value_handle, const uint8_t *value, size_t value_len);
// Write a value to the local gatts db (ready to be queried by a central). Optionally send notifications/indications.
int mp_bluetooth_gatts_write(uint16_t value_handle, const uint8_t *value, size_t value_len, bool send_update);
// Notify the central that it should do a read.
int mp_bluetooth_gatts_notify(uint16_t conn_handle, uint16_t value_handle);
// Notify the central, including a data payload. (Note: does not set the gatts db value).
Expand Down
13 changes: 11 additions & 2 deletions extmod/nimble/modbluetooth_nimble.c
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,11 @@ STATIC int central_gap_event_cb(struct ble_gap_event *event, void *arg) {

return 0;
}

case BLE_GAP_EVENT_SUBSCRIBE: {
DEBUG_printf("central_gap_event_cb: subscribe: handle=%d, reason=%d notify=%d indicate=%d \n", event->subscribe.attr_handle, event->subscribe.reason, event->subscribe.cur_notify, event->subscribe.cur_indicate);
return 0;
}
}

return commmon_gap_event_cb(event, arg);
Expand Down Expand Up @@ -1004,11 +1009,15 @@ int mp_bluetooth_gatts_read(uint16_t value_handle, uint8_t **value, size_t *valu
return mp_bluetooth_gatts_db_read(MP_STATE_PORT(bluetooth_nimble_root_pointers)->gatts_db, value_handle, value, value_len);
}

int mp_bluetooth_gatts_write(uint16_t value_handle, const uint8_t *value, size_t value_len) {
int mp_bluetooth_gatts_write(uint16_t value_handle, const uint8_t *value, size_t value_len, bool send_update) {
if (!mp_bluetooth_is_active()) {
return ERRNO_BLUETOOTH_NOT_ACTIVE;
}
return mp_bluetooth_gatts_db_write(MP_STATE_PORT(bluetooth_nimble_root_pointers)->gatts_db, value_handle, value, value_len);
int err = mp_bluetooth_gatts_db_write(MP_STATE_PORT(bluetooth_nimble_root_pointers)->gatts_db, value_handle, value, value_len);
if (err == 0 && send_update) {
ble_gatts_chr_updated(value_handle);
}
return err;
}

// TODO: Could use ble_gatts_chr_updated to send to all subscribed centrals.
Expand Down
5 changes: 4 additions & 1 deletion ports/zephyr/modbluetooth_zephyr.c
Original file line number Diff line number Diff line change
Expand Up @@ -308,10 +308,13 @@ int mp_bluetooth_gatts_read(uint16_t value_handle, uint8_t **value, size_t *valu
return mp_bluetooth_gatts_db_read(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, value_handle, value, value_len);
}

int mp_bluetooth_gatts_write(uint16_t value_handle, const uint8_t *value, size_t value_len) {
int mp_bluetooth_gatts_write(uint16_t value_handle, const uint8_t *value, size_t value_len, bool send_update) {
if (!mp_bluetooth_is_active()) {
return ERRNO_BLUETOOTH_NOT_ACTIVE;
}
if (send_update) {
return MP_EOPNOTSUPP;
}
return mp_bluetooth_gatts_db_write(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, value_handle, value, value_len);
}

Expand Down
246 changes: 246 additions & 0 deletions tests/multi_bluetooth/ble_subscribe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
# Test for sending notifications to subscribed clients.

from micropython import const
import time, machine, bluetooth

TIMEOUT_MS = 5000

_IRQ_CENTRAL_CONNECT = const(1)
_IRQ_CENTRAL_DISCONNECT = const(2)
_IRQ_GATTS_WRITE = const(3)
_IRQ_PERIPHERAL_CONNECT = const(7)
_IRQ_PERIPHERAL_DISCONNECT = const(8)
_IRQ_GATTC_CHARACTERISTIC_RESULT = const(11)
_IRQ_GATTC_CHARACTERISTIC_DONE = const(12)
_IRQ_GATTC_DESCRIPTOR_RESULT = const(13)
_IRQ_GATTC_DESCRIPTOR_DONE = const(14)
_IRQ_GATTC_READ_RESULT = const(15)
_IRQ_GATTC_READ_DONE = const(16)
_IRQ_GATTC_WRITE_DONE = const(17)
_IRQ_GATTC_NOTIFY = const(18)
_IRQ_GATTC_INDICATE = const(19)

_CCCD_UUID = bluetooth.UUID(const(0x2902))

SERVICE_UUID = bluetooth.UUID("A5A5A5A5-FFFF-9999-1111-5A5A5A5A5A5A")
CHAR_UUID = bluetooth.UUID("00000000-1111-2222-3333-444444444444")
CHAR = (
CHAR_UUID,
bluetooth.FLAG_READ | bluetooth.FLAG_WRITE | bluetooth.FLAG_NOTIFY | bluetooth.FLAG_INDICATE,
)
SERVICE = (
SERVICE_UUID,
(CHAR,),
)
SERVICES = (SERVICE,)

waiting_events = {}


def irq(event, data):
if event == _IRQ_CENTRAL_CONNECT:
print("_IRQ_CENTRAL_CONNECT")
waiting_events[event] = data[0]
elif event == _IRQ_CENTRAL_DISCONNECT:
print("_IRQ_CENTRAL_DISCONNECT")
elif event == _IRQ_GATTS_WRITE:
print("_IRQ_GATTS_WRITE", ble.gatts_read(data[-1]))
elif event == _IRQ_PERIPHERAL_CONNECT:
print("_IRQ_PERIPHERAL_CONNECT")
waiting_events[event] = data[0]
elif event == _IRQ_PERIPHERAL_DISCONNECT:
print("_IRQ_PERIPHERAL_DISCONNECT")
elif event == _IRQ_GATTC_CHARACTERISTIC_RESULT:
# conn_handle, def_handle, value_handle, properties, uuid = data
if data[-1] == CHAR_UUID:
print("_IRQ_GATTC_CHARACTERISTIC_RESULT", data[-1])
waiting_events[event] = data[2]
else:
return
elif event == _IRQ_GATTC_CHARACTERISTIC_DONE:
print("_IRQ_GATTC_CHARACTERISTIC_DONE")
elif event == _IRQ_GATTC_DESCRIPTOR_RESULT:
# conn_handle, dsc_handle, uuid = data
if data[-1] == _CCCD_UUID:
print("_IRQ_GATTC_DESCRIPTOR_RESULT", data[-1])
waiting_events[event] = data[1]
else:
return
elif event == _IRQ_GATTC_DESCRIPTOR_DONE:
print("_IRQ_GATTC_DESCRIPTOR_DONE")
elif event == _IRQ_GATTC_READ_RESULT:
print("_IRQ_GATTC_READ_RESULT", bytes(data[-1]))
elif event == _IRQ_GATTC_READ_DONE:
print("_IRQ_GATTC_READ_DONE", data[-1])
elif event == _IRQ_GATTC_WRITE_DONE:
print("_IRQ_GATTC_WRITE_DONE", data[-1])
elif event == _IRQ_GATTC_NOTIFY:
print("_IRQ_GATTC_NOTIFY", bytes(data[-1]))
elif event == _IRQ_GATTC_INDICATE:
print("_IRQ_GATTC_NOTIFY", bytes(data[-1]))

if event not in waiting_events:
waiting_events[event] = None


def wait_for_event(event, timeout_ms):
t0 = time.ticks_ms()
while time.ticks_diff(time.ticks_ms(), t0) < timeout_ms:
if event in waiting_events:
return waiting_events.pop(event)
machine.idle()
raise ValueError("Timeout waiting for {}".format(event))


# Acting in peripheral role.
def instance0():
multitest.globals(BDADDR=ble.config("mac"))
((char_handle,),) = ble.gatts_register_services(SERVICES)
print("gap_advertise")
ble.gap_advertise(20_000, b"\x02\x01\x06\x04\xffMPY") # \x04\x09MPY
multitest.next()
try:
# Write initial characteristic value (will be read by client).
ble.gatts_write(char_handle, "periph0") ###

# Wait for central to connect to us.
conn_handle = wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS * 10)

# A
# Wait for a write to the characteristic from the central (to synchronise).
wait_for_event(_IRQ_GATTS_WRITE, TIMEOUT_MS)
print("sync A")
# This should be local-only.
ble.gatts_write(char_handle, "periph1")
time.sleep_ms(100)
# Update local-only, then force notify.
ble.gatts_write(char_handle, "periph2")
ble.gatts_notify(conn_handle, char_handle) ###
time.sleep_ms(100)
# Update local and notify subscribers. No notification should be sent.
ble.gatts_write(char_handle, "periph3", True)
time.sleep_ms(100)
multitest.broadcast("A")

# B
# Synchronise with the client (which should now be subscribed for notify).
wait_for_event(_IRQ_GATTS_WRITE, TIMEOUT_MS)
print("sync B")
# This should be local-only (send_update=False).
ble.gatts_write(char_handle, "periph4", False)
time.sleep_ms(100)
# This should notify the subscribed client.
ble.gatts_write(char_handle, "periph5", True) ###
time.sleep_ms(100)
multitest.broadcast("B")

# C
# Synchronise with the client (which should now be subscribed for indicate).
wait_for_event(_IRQ_GATTS_WRITE, TIMEOUT_MS)
print("sync C")
# This should be local-only (send_update=False).
ble.gatts_write(char_handle, "periph6", False)
time.sleep_ms(100)
# This should indicate the subscribed client.
ble.gatts_write(char_handle, "periph7", True) ###
time.sleep_ms(100)
multitest.broadcast("C")

# D
# Synchronise with the client (which should now be unsubscribed).
wait_for_event(_IRQ_GATTS_WRITE, TIMEOUT_MS)
print("sync D")
# This should be local-only (send_update=False).
ble.gatts_write(char_handle, "periph8", False)
time.sleep_ms(100)
# This should be local-only (no more subscribers).
ble.gatts_write(char_handle, "periph9", True)
time.sleep_ms(100)
# Update local-only, then another force notify.
ble.gatts_write(char_handle, "periph10")
ble.gatts_notify(conn_handle, char_handle) ###
time.sleep_ms(100)
multitest.broadcast("D")

# Wait for the central to disconnect.
wait_for_event(_IRQ_CENTRAL_DISCONNECT, TIMEOUT_MS)
finally:
ble.active(0)


# Acting in central role.
def instance1():
multitest.next()
try:
# Connect to peripheral and then disconnect.
print("gap_connect")
ble.gap_connect(*BDADDR)
conn_handle = wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS)

# Discover characteristics.
ble.gattc_discover_characteristics(conn_handle, 1, 65535)
value_handle = wait_for_event(_IRQ_GATTC_CHARACTERISTIC_RESULT, TIMEOUT_MS)
wait_for_event(_IRQ_GATTC_CHARACTERISTIC_DONE, TIMEOUT_MS)

# Discover CCCD.
ble.gattc_discover_descriptors(conn_handle, value_handle, value_handle + 5)
cccd_handle = wait_for_event(_IRQ_GATTC_DESCRIPTOR_RESULT, TIMEOUT_MS)
wait_for_event(_IRQ_GATTC_DESCRIPTOR_DONE, TIMEOUT_MS)

# Issue read of characteristic, should get initial value.
print("gattc_read")
ble.gattc_read(conn_handle, value_handle)
wait_for_event(_IRQ_GATTC_READ_RESULT, TIMEOUT_MS)

# While the four states are active, all incoming notifications
# and indications will be printed by the event handler. We
# should only expect to see certain ones.

# Start unsubscribed.
# Write to the characteristic (triggers A).
print("gattc_write")
ble.gattc_write(conn_handle, value_handle, "central0", 1)
wait_for_event(_IRQ_GATTC_WRITE_DONE, TIMEOUT_MS)
# Wait for A to complete.
multitest.wait("A")

# Subscribe for notify.
ble.gattc_write(conn_handle, cccd_handle, b"\x01\x00", 1)
wait_for_event(_IRQ_GATTC_WRITE_DONE, TIMEOUT_MS)
# Write to the characteristic (triggers B).
print("gattc_write")
ble.gattc_write(conn_handle, value_handle, "central1", 1)
wait_for_event(_IRQ_GATTC_WRITE_DONE, TIMEOUT_MS)
# Wait for B to complete.
multitest.wait("B")

# Subscribe for indicate.
ble.gattc_write(conn_handle, cccd_handle, b"\x02\x00", 1)
wait_for_event(_IRQ_GATTC_WRITE_DONE, TIMEOUT_MS)
# Write to the characteristic (triggers C).
print("gattc_write")
ble.gattc_write(conn_handle, value_handle, "central2", 1)
wait_for_event(_IRQ_GATTC_WRITE_DONE, TIMEOUT_MS)
# Wait for C to complete.
multitest.wait("C")

# Unsubscribe.
ble.gattc_write(conn_handle, cccd_handle, b"\x00\x00", 1)
wait_for_event(_IRQ_GATTC_WRITE_DONE, TIMEOUT_MS)
# Write to the characteristic (triggers D).
print("gattc_write")
ble.gattc_write(conn_handle, value_handle, "central3", 1)
wait_for_event(_IRQ_GATTC_WRITE_DONE, TIMEOUT_MS)
# Wait for D to complete.
multitest.wait("D")

# Disconnect from peripheral.
print("gap_disconnect:", ble.gap_disconnect(conn_handle))
wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, TIMEOUT_MS)
finally:
ble.active(0)


ble = bluetooth.BLE()
ble.active(1)
ble.irq(irq)
Loading