c5a21a94f8
This flag is supported and needs to be set if characteristics are write- without-response.
1147 lines
48 KiB
C
1147 lines
48 KiB
C
/*
|
|
* This file is part of the MicroPython project, http://micropython.org/
|
|
*
|
|
* The MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2018 Ayke van Laethem
|
|
* Copyright (c) 2019 Jim Mussared
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
|
|
#include "py/binary.h"
|
|
#include "py/misc.h"
|
|
#include "py/mperrno.h"
|
|
#include "py/obj.h"
|
|
#include "py/objstr.h"
|
|
#include "py/objarray.h"
|
|
#include "py/qstr.h"
|
|
#include "py/runtime.h"
|
|
#include "py/mphal.h"
|
|
#include "extmod/modbluetooth.h"
|
|
#include <string.h>
|
|
|
|
#if MICROPY_PY_BLUETOOTH
|
|
|
|
#if !MICROPY_ENABLE_SCHEDULER
|
|
#error modbluetooth requires MICROPY_ENABLE_SCHEDULER
|
|
#endif
|
|
|
|
#define MP_BLUETOOTH_CONNECT_DEFAULT_SCAN_DURATION_MS 2000
|
|
|
|
#define MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_TUPLE_LEN 5
|
|
|
|
// This formula is intended to allow queuing the data of a large characteristic
|
|
// while still leaving room for a couple of normal (small, fixed size) events.
|
|
#define MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_BYTES_LEN(ringbuf_size) (MAX((int)((ringbuf_size) / 2), (int)(ringbuf_size) - 64))
|
|
|
|
STATIC const mp_obj_type_t bluetooth_ble_type;
|
|
STATIC const mp_obj_type_t bluetooth_uuid_type;
|
|
|
|
typedef struct {
|
|
mp_obj_base_t base;
|
|
mp_obj_t irq_handler;
|
|
uint16_t irq_trigger;
|
|
bool irq_scheduled;
|
|
mp_obj_t irq_data_tuple;
|
|
uint8_t irq_data_addr_bytes[6];
|
|
uint16_t irq_data_data_alloc;
|
|
uint8_t *irq_data_data_bytes;
|
|
mp_obj_str_t irq_data_addr;
|
|
mp_obj_str_t irq_data_data;
|
|
mp_obj_bluetooth_uuid_t irq_data_uuid;
|
|
ringbuf_t ringbuf;
|
|
} mp_obj_bluetooth_ble_t;
|
|
|
|
// TODO: this seems like it could be generic?
|
|
STATIC mp_obj_t bluetooth_handle_errno(int err) {
|
|
if (err != 0) {
|
|
mp_raise_OSError(err);
|
|
}
|
|
return mp_const_none;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// UUID object
|
|
// ----------------------------------------------------------------------------
|
|
|
|
STATIC mp_obj_t bluetooth_uuid_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
|
|
mp_arg_check_num(n_args, n_kw, 1, 1, false);
|
|
|
|
mp_obj_bluetooth_uuid_t *self = m_new_obj(mp_obj_bluetooth_uuid_t);
|
|
self->base.type = &bluetooth_uuid_type;
|
|
|
|
if (mp_obj_is_int(all_args[0])) {
|
|
self->type = MP_BLUETOOTH_UUID_TYPE_16;
|
|
mp_int_t value = mp_obj_get_int(all_args[0]);
|
|
if (value > 65535) {
|
|
mp_raise_ValueError(MP_ERROR_TEXT("invalid UUID"));
|
|
}
|
|
self->data[0] = value & 0xff;
|
|
self->data[1] = (value >> 8) & 0xff;
|
|
} else {
|
|
mp_buffer_info_t uuid_bufinfo = {0};
|
|
mp_get_buffer_raise(all_args[0], &uuid_bufinfo, MP_BUFFER_READ);
|
|
if (uuid_bufinfo.len == 2 || uuid_bufinfo.len == 4 || uuid_bufinfo.len == 16) {
|
|
// Bytes data -- infer UUID type from length and copy data.
|
|
self->type = uuid_bufinfo.len;
|
|
memcpy(self->data, uuid_bufinfo.buf, self->type);
|
|
} else {
|
|
// Assume UUID string (e.g. '6E400001-B5A3-F393-E0A9-E50E24DCCA9E')
|
|
self->type = MP_BLUETOOTH_UUID_TYPE_128;
|
|
int uuid_i = 32;
|
|
for (int i = 0; i < uuid_bufinfo.len; i++) {
|
|
char c = ((char *)uuid_bufinfo.buf)[i];
|
|
if (c == '-') {
|
|
continue;
|
|
}
|
|
if (!unichar_isxdigit(c)) {
|
|
mp_raise_ValueError(MP_ERROR_TEXT("invalid char in UUID"));
|
|
}
|
|
c = unichar_xdigit_value(c);
|
|
uuid_i--;
|
|
if (uuid_i < 0) {
|
|
mp_raise_ValueError(MP_ERROR_TEXT("UUID too long"));
|
|
}
|
|
if (uuid_i % 2 == 0) {
|
|
// lower nibble
|
|
self->data[uuid_i / 2] |= c;
|
|
} else {
|
|
// upper nibble
|
|
self->data[uuid_i / 2] = c << 4;
|
|
}
|
|
}
|
|
if (uuid_i > 0) {
|
|
mp_raise_ValueError(MP_ERROR_TEXT("UUID too short"));
|
|
}
|
|
}
|
|
}
|
|
|
|
return MP_OBJ_FROM_PTR(self);
|
|
}
|
|
|
|
STATIC mp_obj_t bluetooth_uuid_unary_op(mp_unary_op_t op, mp_obj_t self_in) {
|
|
mp_obj_bluetooth_uuid_t *self = MP_OBJ_TO_PTR(self_in);
|
|
switch (op) {
|
|
case MP_UNARY_OP_HASH: {
|
|
// Use the QSTR hash function.
|
|
return MP_OBJ_NEW_SMALL_INT(qstr_compute_hash(self->data, self->type));
|
|
}
|
|
default:
|
|
return MP_OBJ_NULL; // op not supported
|
|
}
|
|
}
|
|
|
|
STATIC mp_obj_t bluetooth_uuid_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) {
|
|
if (!mp_obj_is_type(rhs_in, &bluetooth_uuid_type)) {
|
|
return MP_OBJ_NULL;
|
|
}
|
|
|
|
mp_obj_bluetooth_uuid_t *lhs = MP_OBJ_TO_PTR(lhs_in);
|
|
mp_obj_bluetooth_uuid_t *rhs = MP_OBJ_TO_PTR(rhs_in);
|
|
switch (op) {
|
|
case MP_BINARY_OP_EQUAL:
|
|
case MP_BINARY_OP_LESS:
|
|
case MP_BINARY_OP_LESS_EQUAL:
|
|
case MP_BINARY_OP_MORE:
|
|
case MP_BINARY_OP_MORE_EQUAL:
|
|
if (lhs->type == rhs->type) {
|
|
return mp_obj_new_bool(mp_seq_cmp_bytes(op, lhs->data, lhs->type, rhs->data, rhs->type));
|
|
} else {
|
|
return mp_binary_op(op, MP_OBJ_NEW_SMALL_INT(lhs->type), MP_OBJ_NEW_SMALL_INT(rhs->type));
|
|
}
|
|
|
|
default:
|
|
return MP_OBJ_NULL; // op not supported
|
|
}
|
|
}
|
|
|
|
STATIC void bluetooth_uuid_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
|
|
mp_obj_bluetooth_uuid_t *self = MP_OBJ_TO_PTR(self_in);
|
|
mp_printf(print, "UUID%u(%s", self->type * 8, self->type <= 4 ? "0x" : "'");
|
|
for (int i = 0; i < self->type; ++i) {
|
|
if (i == 4 || i == 6 || i == 8 || i == 10) {
|
|
mp_printf(print, "-");
|
|
}
|
|
mp_printf(print, "%02x", self->data[self->type - 1 - i]);
|
|
}
|
|
if (self->type == MP_BLUETOOTH_UUID_TYPE_128) {
|
|
mp_printf(print, "'");
|
|
}
|
|
mp_printf(print, ")");
|
|
}
|
|
|
|
mp_int_t bluetooth_uuid_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) {
|
|
mp_obj_bluetooth_uuid_t *self = MP_OBJ_TO_PTR(self_in);
|
|
|
|
if (flags != MP_BUFFER_READ) {
|
|
return 1;
|
|
}
|
|
|
|
bufinfo->buf = self->data;
|
|
bufinfo->len = self->type;
|
|
bufinfo->typecode = 'B';
|
|
return 0;
|
|
}
|
|
|
|
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
|
|
|
|
STATIC void ringbuf_put_uuid(ringbuf_t *ringbuf, mp_obj_bluetooth_uuid_t *uuid) {
|
|
assert(ringbuf_free(ringbuf) >= uuid->type + 1);
|
|
ringbuf_put(ringbuf, uuid->type);
|
|
for (int i = 0; i < uuid->type; ++i) {
|
|
ringbuf_put(ringbuf, uuid->data[i]);
|
|
}
|
|
}
|
|
|
|
STATIC void ringbuf_get_uuid(ringbuf_t *ringbuf, mp_obj_bluetooth_uuid_t *uuid) {
|
|
assert(ringbuf_avail(ringbuf) >= 1);
|
|
uuid->type = ringbuf_get(ringbuf);
|
|
assert(ringbuf_avail(ringbuf) >= uuid->type);
|
|
for (int i = 0; i < uuid->type; ++i) {
|
|
uuid->data[i] = ringbuf_get(ringbuf);
|
|
}
|
|
}
|
|
#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
|
|
|
|
STATIC const mp_obj_type_t bluetooth_uuid_type = {
|
|
{ &mp_type_type },
|
|
.name = MP_QSTR_UUID,
|
|
.make_new = bluetooth_uuid_make_new,
|
|
.unary_op = bluetooth_uuid_unary_op,
|
|
.binary_op = bluetooth_uuid_binary_op,
|
|
.locals_dict = NULL,
|
|
.print = bluetooth_uuid_print,
|
|
.buffer_p = { .get_buffer = bluetooth_uuid_get_buffer },
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Bluetooth object: General
|
|
// ----------------------------------------------------------------------------
|
|
|
|
STATIC mp_obj_t bluetooth_ble_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
|
|
if (MP_STATE_VM(bluetooth) == MP_OBJ_NULL) {
|
|
mp_obj_bluetooth_ble_t *o = m_new0(mp_obj_bluetooth_ble_t, 1);
|
|
o->base.type = &bluetooth_ble_type;
|
|
|
|
o->irq_handler = mp_const_none;
|
|
o->irq_trigger = 0;
|
|
|
|
// Pre-allocate the event data tuple to prevent needing to allocate in the IRQ handler.
|
|
o->irq_data_tuple = mp_obj_new_tuple(MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_TUPLE_LEN, NULL);
|
|
|
|
// Pre-allocated buffers for address, payload and uuid.
|
|
o->irq_data_addr.base.type = &mp_type_bytes;
|
|
o->irq_data_addr.data = o->irq_data_addr_bytes;
|
|
o->irq_data_data_alloc = MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_BYTES_LEN(MICROPY_PY_BLUETOOTH_RINGBUF_SIZE);
|
|
o->irq_data_data.base.type = &mp_type_bytes;
|
|
o->irq_data_data.data = m_new(uint8_t, o->irq_data_data_alloc);
|
|
o->irq_data_uuid.base.type = &bluetooth_uuid_type;
|
|
|
|
// Allocate the default ringbuf.
|
|
ringbuf_alloc(&o->ringbuf, MICROPY_PY_BLUETOOTH_RINGBUF_SIZE);
|
|
|
|
MP_STATE_VM(bluetooth) = MP_OBJ_FROM_PTR(o);
|
|
}
|
|
return MP_STATE_VM(bluetooth);
|
|
}
|
|
|
|
STATIC mp_obj_t bluetooth_ble_active(size_t n_args, const mp_obj_t *args) {
|
|
if (n_args == 2) {
|
|
// Boolean enable/disable argument supplied, set current state.
|
|
if (mp_obj_is_true(args[1])) {
|
|
int err = mp_bluetooth_init();
|
|
bluetooth_handle_errno(err);
|
|
} else {
|
|
mp_bluetooth_deinit();
|
|
}
|
|
}
|
|
// Return current state.
|
|
return mp_obj_new_bool(mp_bluetooth_is_active());
|
|
}
|
|
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_active_obj, 1, 2, bluetooth_ble_active);
|
|
|
|
STATIC mp_obj_t bluetooth_ble_config(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) {
|
|
mp_obj_bluetooth_ble_t *self = MP_OBJ_TO_PTR(args[0]);
|
|
|
|
if (kwargs->used == 0) {
|
|
// Get config value
|
|
if (n_args != 2) {
|
|
mp_raise_TypeError(MP_ERROR_TEXT("must query one param"));
|
|
}
|
|
|
|
switch (mp_obj_str_get_qstr(args[1])) {
|
|
case MP_QSTR_mac: {
|
|
uint8_t addr[6];
|
|
mp_bluetooth_get_device_addr(addr);
|
|
return mp_obj_new_bytes(addr, MP_ARRAY_SIZE(addr));
|
|
}
|
|
case MP_QSTR_rxbuf:
|
|
return mp_obj_new_int(self->ringbuf.size);
|
|
default:
|
|
mp_raise_ValueError(MP_ERROR_TEXT("unknown config param"));
|
|
}
|
|
} else {
|
|
// Set config value(s)
|
|
if (n_args != 1) {
|
|
mp_raise_TypeError(MP_ERROR_TEXT("can't specify pos and kw args"));
|
|
}
|
|
|
|
for (size_t i = 0; i < kwargs->alloc; ++i) {
|
|
if (MP_MAP_SLOT_IS_FILLED(kwargs, i)) {
|
|
mp_map_elem_t *e = &kwargs->table[i];
|
|
switch (mp_obj_str_get_qstr(e->key)) {
|
|
case MP_QSTR_rxbuf: {
|
|
// Determine new buffer sizes
|
|
mp_int_t ringbuf_alloc = mp_obj_get_int(e->value);
|
|
if (ringbuf_alloc < 16 || ringbuf_alloc > 0xffff) {
|
|
mp_raise_ValueError(NULL);
|
|
}
|
|
size_t irq_data_alloc = MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_BYTES_LEN(ringbuf_alloc);
|
|
|
|
// Allocate new buffers
|
|
uint8_t *ringbuf = m_new(uint8_t, ringbuf_alloc);
|
|
uint8_t *irq_data = m_new(uint8_t, irq_data_alloc);
|
|
|
|
// Get old buffer sizes and pointers
|
|
uint8_t *old_ringbuf_buf = self->ringbuf.buf;
|
|
size_t old_ringbuf_alloc = self->ringbuf.size;
|
|
uint8_t *old_irq_data_buf = (uint8_t *)self->irq_data_data.data;
|
|
size_t old_irq_data_alloc = self->irq_data_data_alloc;
|
|
|
|
// Atomically update the ringbuf and irq data
|
|
MICROPY_PY_BLUETOOTH_ENTER
|
|
self->ringbuf.size = ringbuf_alloc;
|
|
self->ringbuf.buf = ringbuf;
|
|
self->ringbuf.iget = 0;
|
|
self->ringbuf.iput = 0;
|
|
self->irq_data_data_alloc = irq_data_alloc;
|
|
self->irq_data_data.data = irq_data;
|
|
MICROPY_PY_BLUETOOTH_EXIT
|
|
|
|
// Free old buffers
|
|
m_del(uint8_t, old_ringbuf_buf, old_ringbuf_alloc);
|
|
m_del(uint8_t, old_irq_data_buf, old_irq_data_alloc);
|
|
break;
|
|
}
|
|
default:
|
|
mp_raise_ValueError(MP_ERROR_TEXT("unknown config param"));
|
|
}
|
|
}
|
|
}
|
|
|
|
return mp_const_none;
|
|
}
|
|
}
|
|
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(bluetooth_ble_config_obj, 1, bluetooth_ble_config);
|
|
|
|
STATIC mp_obj_t bluetooth_ble_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
|
|
enum { ARG_handler, ARG_trigger };
|
|
static const mp_arg_t allowed_args[] = {
|
|
{ MP_QSTR_handler, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_rom_obj = MP_ROM_NONE} },
|
|
{ MP_QSTR_trigger, MP_ARG_INT, {.u_int = MP_BLUETOOTH_IRQ_ALL} },
|
|
};
|
|
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
|
|
mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
|
|
mp_obj_t callback = args[ARG_handler].u_obj;
|
|
if (callback != mp_const_none && !mp_obj_is_callable(callback)) {
|
|
mp_raise_ValueError(MP_ERROR_TEXT("invalid callback"));
|
|
}
|
|
|
|
// Update the callback.
|
|
MICROPY_PY_BLUETOOTH_ENTER
|
|
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
|
|
o->irq_handler = callback;
|
|
o->irq_trigger = args[ARG_trigger].u_int;
|
|
MICROPY_PY_BLUETOOTH_EXIT
|
|
|
|
return mp_const_none;
|
|
}
|
|
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(bluetooth_ble_irq_obj, 1, bluetooth_ble_irq);
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Bluetooth object: GAP
|
|
// ----------------------------------------------------------------------------
|
|
|
|
STATIC mp_obj_t bluetooth_ble_gap_advertise(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
|
|
enum { ARG_interval_us, ARG_adv_data, ARG_resp_data, ARG_connectable };
|
|
static const mp_arg_t allowed_args[] = {
|
|
{ MP_QSTR_interval_us, MP_ARG_OBJ, {.u_obj = MP_OBJ_NEW_SMALL_INT(500000)} },
|
|
{ MP_QSTR_adv_data, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} },
|
|
{ MP_QSTR_resp_data, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} },
|
|
{ MP_QSTR_connectable, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_TRUE} },
|
|
};
|
|
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
|
|
mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
|
|
|
|
if (args[ARG_interval_us].u_obj == mp_const_none) {
|
|
mp_bluetooth_gap_advertise_stop();
|
|
return mp_const_none;
|
|
}
|
|
|
|
mp_int_t interval_us = mp_obj_get_int(args[ARG_interval_us].u_obj);
|
|
bool connectable = mp_obj_is_true(args[ARG_connectable].u_obj);
|
|
|
|
mp_buffer_info_t adv_bufinfo = {0};
|
|
if (args[ARG_adv_data].u_obj != mp_const_none) {
|
|
mp_get_buffer_raise(args[ARG_adv_data].u_obj, &adv_bufinfo, MP_BUFFER_READ);
|
|
}
|
|
|
|
mp_buffer_info_t resp_bufinfo = {0};
|
|
if (args[ARG_resp_data].u_obj != mp_const_none) {
|
|
mp_get_buffer_raise(args[ARG_resp_data].u_obj, &resp_bufinfo, MP_BUFFER_READ);
|
|
}
|
|
|
|
return bluetooth_handle_errno(mp_bluetooth_gap_advertise_start(connectable, interval_us, adv_bufinfo.buf, adv_bufinfo.len, resp_bufinfo.buf, resp_bufinfo.len));
|
|
}
|
|
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(bluetooth_ble_gap_advertise_obj, 1, bluetooth_ble_gap_advertise);
|
|
|
|
STATIC int bluetooth_gatts_register_service(mp_obj_t uuid_in, mp_obj_t characteristics_in, uint16_t **handles, size_t *num_handles) {
|
|
if (!mp_obj_is_type(uuid_in, &bluetooth_uuid_type)) {
|
|
mp_raise_ValueError(MP_ERROR_TEXT("invalid service UUID"));
|
|
}
|
|
mp_obj_bluetooth_uuid_t *service_uuid = MP_OBJ_TO_PTR(uuid_in);
|
|
|
|
mp_obj_t len_in = mp_obj_len(characteristics_in);
|
|
size_t len = mp_obj_get_int(len_in);
|
|
mp_obj_iter_buf_t iter_buf;
|
|
mp_obj_t iterable = mp_getiter(characteristics_in, &iter_buf);
|
|
mp_obj_t characteristic_obj;
|
|
|
|
// Lists of characteristic uuids and flags.
|
|
mp_obj_bluetooth_uuid_t **characteristic_uuids = m_new(mp_obj_bluetooth_uuid_t *, len);
|
|
uint8_t *characteristic_flags = m_new(uint8_t, len);
|
|
|
|
// Flattened list of descriptor uuids and flags. Grows (realloc) as more descriptors are encountered.
|
|
mp_obj_bluetooth_uuid_t **descriptor_uuids = NULL;
|
|
uint8_t *descriptor_flags = NULL;
|
|
// How many descriptors in the flattened list per characteristic.
|
|
uint8_t *num_descriptors = m_new(uint8_t, len);
|
|
|
|
// Inititally allocate enough room for the number of characteristics.
|
|
// Will be grown to accommodate descriptors as necessary.
|
|
*num_handles = len;
|
|
*handles = m_new(uint16_t, *num_handles);
|
|
|
|
// Extract out characteristic uuids & flags.
|
|
|
|
int characteristic_index = 0; // characteristic index.
|
|
int handle_index = 0; // handle index.
|
|
int descriptor_index = 0; // descriptor index.
|
|
while ((characteristic_obj = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) {
|
|
// (uuid, flags, (optional descriptors),)
|
|
size_t characteristic_len;
|
|
mp_obj_t *characteristic_items;
|
|
mp_obj_get_array(characteristic_obj, &characteristic_len, &characteristic_items);
|
|
|
|
if (characteristic_len < 2 || characteristic_len > 3) {
|
|
mp_raise_ValueError(MP_ERROR_TEXT("invalid characteristic tuple"));
|
|
}
|
|
mp_obj_t uuid_obj = characteristic_items[0];
|
|
if (!mp_obj_is_type(uuid_obj, &bluetooth_uuid_type)) {
|
|
mp_raise_ValueError(MP_ERROR_TEXT("invalid characteristic UUID"));
|
|
}
|
|
|
|
(*handles)[handle_index++] = 0xffff;
|
|
|
|
// Optional third element, iterable of descriptors.
|
|
if (characteristic_len >= 3) {
|
|
mp_obj_t descriptors_len_in = mp_obj_len(characteristic_items[2]);
|
|
num_descriptors[characteristic_index] = mp_obj_get_int(descriptors_len_in);
|
|
|
|
if (num_descriptors[characteristic_index] == 0) {
|
|
continue;
|
|
}
|
|
|
|
// Grow the flattened uuids and flags arrays with this many more descriptors.
|
|
descriptor_uuids = m_renew(mp_obj_bluetooth_uuid_t *, descriptor_uuids, descriptor_index, descriptor_index + num_descriptors[characteristic_index]);
|
|
descriptor_flags = m_renew(uint8_t, descriptor_flags, descriptor_index, descriptor_index + num_descriptors[characteristic_index]);
|
|
|
|
// Also grow the handles array.
|
|
*handles = m_renew(uint16_t, *handles, *num_handles, *num_handles + num_descriptors[characteristic_index]);
|
|
|
|
mp_obj_iter_buf_t iter_buf_desc;
|
|
mp_obj_t iterable_desc = mp_getiter(characteristic_items[2], &iter_buf_desc);
|
|
mp_obj_t descriptor_obj;
|
|
|
|
// Extract out descriptors for this characteristic.
|
|
while ((descriptor_obj = mp_iternext(iterable_desc)) != MP_OBJ_STOP_ITERATION) {
|
|
// (uuid, flags,)
|
|
mp_obj_t *descriptor_items;
|
|
mp_obj_get_array_fixed_n(descriptor_obj, 2, &descriptor_items);
|
|
mp_obj_t desc_uuid_obj = descriptor_items[0];
|
|
if (!mp_obj_is_type(desc_uuid_obj, &bluetooth_uuid_type)) {
|
|
mp_raise_ValueError(MP_ERROR_TEXT("invalid descriptor UUID"));
|
|
}
|
|
|
|
descriptor_uuids[descriptor_index] = MP_OBJ_TO_PTR(desc_uuid_obj);
|
|
descriptor_flags[descriptor_index] = mp_obj_get_int(descriptor_items[1]);
|
|
++descriptor_index;
|
|
|
|
(*handles)[handle_index++] = 0xffff;
|
|
}
|
|
|
|
// Reflect that we've grown the handles array.
|
|
*num_handles += num_descriptors[characteristic_index];
|
|
}
|
|
|
|
characteristic_uuids[characteristic_index] = MP_OBJ_TO_PTR(uuid_obj);
|
|
characteristic_flags[characteristic_index] = mp_obj_get_int(characteristic_items[1]);
|
|
++characteristic_index;
|
|
}
|
|
|
|
// Add service.
|
|
return mp_bluetooth_gatts_register_service(service_uuid, characteristic_uuids, characteristic_flags, descriptor_uuids, descriptor_flags, num_descriptors, *handles, len);
|
|
}
|
|
|
|
STATIC mp_obj_t bluetooth_ble_gatts_register_services(mp_obj_t self_in, mp_obj_t services_in) {
|
|
mp_obj_t len_in = mp_obj_len(services_in);
|
|
size_t len = mp_obj_get_int(len_in);
|
|
mp_obj_iter_buf_t iter_buf;
|
|
mp_obj_t iterable = mp_getiter(services_in, &iter_buf);
|
|
mp_obj_t service_tuple_obj;
|
|
|
|
mp_obj_tuple_t *result = MP_OBJ_TO_PTR(mp_obj_new_tuple(len, NULL));
|
|
|
|
uint16_t **handles = m_new0(uint16_t *, len);
|
|
size_t *num_handles = m_new0(size_t, len);
|
|
|
|
// TODO: Add a `append` kwarg (defaulting to False) to make this behavior optional.
|
|
bool append = false;
|
|
int err = mp_bluetooth_gatts_register_service_begin(append);
|
|
if (err != 0) {
|
|
return bluetooth_handle_errno(err);
|
|
}
|
|
|
|
int i = 0;
|
|
while ((service_tuple_obj = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) {
|
|
// (uuid, chars)
|
|
mp_obj_t *service_items;
|
|
mp_obj_get_array_fixed_n(service_tuple_obj, 2, &service_items);
|
|
err = bluetooth_gatts_register_service(service_items[0], service_items[1], &handles[i], &num_handles[i]);
|
|
if (err != 0) {
|
|
return bluetooth_handle_errno(err);
|
|
}
|
|
|
|
++i;
|
|
}
|
|
|
|
// On Nimble, this will actually perform the registration, making the handles valid.
|
|
err = mp_bluetooth_gatts_register_service_end();
|
|
if (err != 0) {
|
|
return bluetooth_handle_errno(err);
|
|
}
|
|
|
|
// Return tuple of tuple of value handles.
|
|
// TODO: Also the Generic Access service characteristics?
|
|
for (i = 0; i < len; ++i) {
|
|
mp_obj_tuple_t *service_handles = MP_OBJ_TO_PTR(mp_obj_new_tuple(num_handles[i], NULL));
|
|
for (int j = 0; j < num_handles[i]; ++j) {
|
|
service_handles->items[j] = MP_OBJ_NEW_SMALL_INT(handles[i][j]);
|
|
}
|
|
result->items[i] = MP_OBJ_FROM_PTR(service_handles);
|
|
}
|
|
return MP_OBJ_FROM_PTR(result);
|
|
}
|
|
STATIC MP_DEFINE_CONST_FUN_OBJ_2(bluetooth_ble_gatts_register_services_obj, bluetooth_ble_gatts_register_services);
|
|
|
|
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
|
|
STATIC mp_obj_t bluetooth_ble_gap_connect(size_t n_args, const mp_obj_t *args) {
|
|
uint8_t addr_type = mp_obj_get_int(args[1]);
|
|
mp_buffer_info_t bufinfo = {0};
|
|
mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_READ);
|
|
if (bufinfo.len != 6) {
|
|
mp_raise_ValueError(MP_ERROR_TEXT("invalid addr"));
|
|
}
|
|
mp_int_t scan_duration_ms = MP_BLUETOOTH_CONNECT_DEFAULT_SCAN_DURATION_MS;
|
|
if (n_args == 4) {
|
|
scan_duration_ms = mp_obj_get_int(args[3]);
|
|
}
|
|
|
|
int err = mp_bluetooth_gap_peripheral_connect(addr_type, bufinfo.buf, scan_duration_ms);
|
|
return bluetooth_handle_errno(err);
|
|
}
|
|
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gap_connect_obj, 3, 4, bluetooth_ble_gap_connect);
|
|
|
|
STATIC mp_obj_t bluetooth_ble_gap_scan(size_t n_args, const mp_obj_t *args) {
|
|
// Default is indefinite scan, with the NimBLE "background scan" interval and window.
|
|
mp_int_t duration_ms = 0;
|
|
mp_int_t interval_us = 1280000;
|
|
mp_int_t window_us = 11250;
|
|
if (n_args > 1) {
|
|
if (args[1] == mp_const_none) {
|
|
// scan(None) --> stop scan.
|
|
return bluetooth_handle_errno(mp_bluetooth_gap_scan_stop());
|
|
}
|
|
duration_ms = mp_obj_get_int(args[1]);
|
|
if (n_args > 2) {
|
|
interval_us = mp_obj_get_int(args[2]);
|
|
if (n_args > 3) {
|
|
window_us = mp_obj_get_int(args[3]);
|
|
}
|
|
}
|
|
}
|
|
return bluetooth_handle_errno(mp_bluetooth_gap_scan_start(duration_ms, interval_us, window_us));
|
|
}
|
|
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gap_scan_obj, 1, 4, bluetooth_ble_gap_scan);
|
|
#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
|
|
|
|
STATIC mp_obj_t bluetooth_ble_gap_disconnect(mp_obj_t self_in, mp_obj_t conn_handle_in) {
|
|
uint16_t conn_handle = mp_obj_get_int(conn_handle_in);
|
|
int err = mp_bluetooth_gap_disconnect(conn_handle);
|
|
if (err == 0) {
|
|
return mp_const_true;
|
|
} else if (err == MP_ENOTCONN) {
|
|
return mp_const_false;
|
|
} else {
|
|
return bluetooth_handle_errno(err);
|
|
}
|
|
}
|
|
STATIC MP_DEFINE_CONST_FUN_OBJ_2(bluetooth_ble_gap_disconnect_obj, bluetooth_ble_gap_disconnect);
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Bluetooth object: GATTS (Peripheral/Advertiser role)
|
|
// ----------------------------------------------------------------------------
|
|
|
|
STATIC mp_obj_t bluetooth_ble_gatts_read(mp_obj_t self_in, mp_obj_t value_handle_in) {
|
|
size_t len = 0;
|
|
uint8_t *buf;
|
|
mp_bluetooth_gatts_read(mp_obj_get_int(value_handle_in), &buf, &len);
|
|
return mp_obj_new_bytes(buf, len);
|
|
}
|
|
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) {
|
|
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);
|
|
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_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]);
|
|
mp_int_t value_handle = mp_obj_get_int(args[2]);
|
|
|
|
if (n_args == 4) {
|
|
mp_buffer_info_t bufinfo = {0};
|
|
mp_get_buffer_raise(args[3], &bufinfo, MP_BUFFER_READ);
|
|
size_t len = bufinfo.len;
|
|
int err = mp_bluetooth_gatts_notify_send(conn_handle, value_handle, bufinfo.buf, &len);
|
|
bluetooth_handle_errno(err);
|
|
return MP_OBJ_NEW_SMALL_INT(len);
|
|
} else {
|
|
int err = mp_bluetooth_gatts_notify(conn_handle, value_handle);
|
|
return bluetooth_handle_errno(err);
|
|
}
|
|
}
|
|
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gatts_notify_obj, 3, 4, bluetooth_ble_gatts_notify);
|
|
|
|
STATIC mp_obj_t bluetooth_ble_gatts_set_buffer(size_t n_args, const mp_obj_t *args) {
|
|
mp_int_t value_handle = mp_obj_get_int(args[1]);
|
|
mp_int_t len = mp_obj_get_int(args[2]);
|
|
bool append = n_args >= 4 && mp_obj_is_true(args[3]);
|
|
return bluetooth_handle_errno(mp_bluetooth_gatts_set_buffer(value_handle, len, append));
|
|
}
|
|
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gatts_set_buffer_obj, 3, 4, bluetooth_ble_gatts_set_buffer);
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Bluetooth object: GATTC (Central/Scanner role)
|
|
// ----------------------------------------------------------------------------
|
|
|
|
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
|
|
|
|
STATIC mp_obj_t bluetooth_ble_gattc_discover_services(mp_obj_t self_in, mp_obj_t conn_handle_in) {
|
|
mp_int_t conn_handle = mp_obj_get_int(conn_handle_in);
|
|
return bluetooth_handle_errno(mp_bluetooth_gattc_discover_primary_services(conn_handle));
|
|
}
|
|
STATIC MP_DEFINE_CONST_FUN_OBJ_2(bluetooth_ble_gattc_discover_services_obj, bluetooth_ble_gattc_discover_services);
|
|
|
|
STATIC mp_obj_t bluetooth_ble_gattc_discover_characteristics(size_t n_args, const mp_obj_t *args) {
|
|
mp_int_t conn_handle = mp_obj_get_int(args[1]);
|
|
mp_int_t start_handle = mp_obj_get_int(args[2]);
|
|
mp_int_t end_handle = mp_obj_get_int(args[3]);
|
|
return bluetooth_handle_errno(mp_bluetooth_gattc_discover_characteristics(conn_handle, start_handle, end_handle));
|
|
}
|
|
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gattc_discover_characteristics_obj, 4, 4, bluetooth_ble_gattc_discover_characteristics);
|
|
|
|
STATIC mp_obj_t bluetooth_ble_gattc_discover_descriptors(size_t n_args, const mp_obj_t *args) {
|
|
mp_int_t conn_handle = mp_obj_get_int(args[1]);
|
|
mp_int_t start_handle = mp_obj_get_int(args[2]);
|
|
mp_int_t end_handle = mp_obj_get_int(args[3]);
|
|
return bluetooth_handle_errno(mp_bluetooth_gattc_discover_descriptors(conn_handle, start_handle, end_handle));
|
|
}
|
|
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gattc_discover_descriptors_obj, 4, 4, bluetooth_ble_gattc_discover_descriptors);
|
|
|
|
STATIC mp_obj_t bluetooth_ble_gattc_read(mp_obj_t self_in, mp_obj_t conn_handle_in, mp_obj_t value_handle_in) {
|
|
mp_int_t conn_handle = mp_obj_get_int(conn_handle_in);
|
|
mp_int_t value_handle = mp_obj_get_int(value_handle_in);
|
|
return bluetooth_handle_errno(mp_bluetooth_gattc_read(conn_handle, value_handle));
|
|
}
|
|
STATIC MP_DEFINE_CONST_FUN_OBJ_3(bluetooth_ble_gattc_read_obj, bluetooth_ble_gattc_read);
|
|
|
|
STATIC mp_obj_t bluetooth_ble_gattc_write(size_t n_args, const mp_obj_t *args) {
|
|
mp_int_t conn_handle = mp_obj_get_int(args[1]);
|
|
mp_int_t value_handle = mp_obj_get_int(args[2]);
|
|
mp_obj_t data = args[3];
|
|
mp_buffer_info_t bufinfo = {0};
|
|
mp_get_buffer_raise(data, &bufinfo, MP_BUFFER_READ);
|
|
size_t len = bufinfo.len;
|
|
unsigned int mode = MP_BLUETOOTH_WRITE_MODE_NO_RESPONSE;
|
|
if (n_args == 5) {
|
|
mode = mp_obj_get_int(args[4]);
|
|
}
|
|
return bluetooth_handle_errno(mp_bluetooth_gattc_write(conn_handle, value_handle, bufinfo.buf, &len, mode));
|
|
}
|
|
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gattc_write_obj, 4, 5, bluetooth_ble_gattc_write);
|
|
|
|
#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Bluetooth object: Definition
|
|
// ----------------------------------------------------------------------------
|
|
|
|
STATIC const mp_rom_map_elem_t bluetooth_ble_locals_dict_table[] = {
|
|
// General
|
|
{ MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&bluetooth_ble_active_obj) },
|
|
{ MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&bluetooth_ble_config_obj) },
|
|
{ MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&bluetooth_ble_irq_obj) },
|
|
// GAP
|
|
{ MP_ROM_QSTR(MP_QSTR_gap_advertise), MP_ROM_PTR(&bluetooth_ble_gap_advertise_obj) },
|
|
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
|
|
{ MP_ROM_QSTR(MP_QSTR_gap_connect), MP_ROM_PTR(&bluetooth_ble_gap_connect_obj) },
|
|
{ MP_ROM_QSTR(MP_QSTR_gap_scan), MP_ROM_PTR(&bluetooth_ble_gap_scan_obj) },
|
|
#endif
|
|
{ MP_ROM_QSTR(MP_QSTR_gap_disconnect), MP_ROM_PTR(&bluetooth_ble_gap_disconnect_obj) },
|
|
// GATT Server (i.e. peripheral/advertiser role)
|
|
{ MP_ROM_QSTR(MP_QSTR_gatts_register_services), MP_ROM_PTR(&bluetooth_ble_gatts_register_services_obj) },
|
|
{ MP_ROM_QSTR(MP_QSTR_gatts_read), MP_ROM_PTR(&bluetooth_ble_gatts_read_obj) },
|
|
{ MP_ROM_QSTR(MP_QSTR_gatts_write), MP_ROM_PTR(&bluetooth_ble_gatts_write_obj) },
|
|
{ MP_ROM_QSTR(MP_QSTR_gatts_notify), MP_ROM_PTR(&bluetooth_ble_gatts_notify_obj) },
|
|
{ MP_ROM_QSTR(MP_QSTR_gatts_set_buffer), MP_ROM_PTR(&bluetooth_ble_gatts_set_buffer_obj) },
|
|
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
|
|
// GATT Client (i.e. central/scanner role)
|
|
{ MP_ROM_QSTR(MP_QSTR_gattc_discover_services), MP_ROM_PTR(&bluetooth_ble_gattc_discover_services_obj) },
|
|
{ MP_ROM_QSTR(MP_QSTR_gattc_discover_characteristics), MP_ROM_PTR(&bluetooth_ble_gattc_discover_characteristics_obj) },
|
|
{ MP_ROM_QSTR(MP_QSTR_gattc_discover_descriptors), MP_ROM_PTR(&bluetooth_ble_gattc_discover_descriptors_obj) },
|
|
{ MP_ROM_QSTR(MP_QSTR_gattc_read), MP_ROM_PTR(&bluetooth_ble_gattc_read_obj) },
|
|
{ MP_ROM_QSTR(MP_QSTR_gattc_write), MP_ROM_PTR(&bluetooth_ble_gattc_write_obj) },
|
|
#endif
|
|
};
|
|
STATIC MP_DEFINE_CONST_DICT(bluetooth_ble_locals_dict, bluetooth_ble_locals_dict_table);
|
|
|
|
STATIC const mp_obj_type_t bluetooth_ble_type = {
|
|
{ &mp_type_type },
|
|
.name = MP_QSTR_BLE,
|
|
.make_new = bluetooth_ble_make_new,
|
|
.locals_dict = (void *)&bluetooth_ble_locals_dict,
|
|
};
|
|
|
|
STATIC const mp_rom_map_elem_t mp_module_bluetooth_globals_table[] = {
|
|
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_ubluetooth) },
|
|
{ MP_ROM_QSTR(MP_QSTR_BLE), MP_ROM_PTR(&bluetooth_ble_type) },
|
|
{ MP_ROM_QSTR(MP_QSTR_UUID), MP_ROM_PTR(&bluetooth_uuid_type) },
|
|
{ MP_ROM_QSTR(MP_QSTR_FLAG_READ), MP_ROM_INT(MP_BLUETOOTH_CHARACTERISTIC_FLAG_READ) },
|
|
{ MP_ROM_QSTR(MP_QSTR_FLAG_WRITE), MP_ROM_INT(MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE) },
|
|
{ MP_ROM_QSTR(MP_QSTR_FLAG_NOTIFY), MP_ROM_INT(MP_BLUETOOTH_CHARACTERISTIC_FLAG_NOTIFY) },
|
|
{ MP_ROM_QSTR(MP_QSTR_FLAG_WRITE_NO_RESPONSE), MP_ROM_INT(MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE_NO_RESPONSE) },
|
|
};
|
|
|
|
STATIC MP_DEFINE_CONST_DICT(mp_module_bluetooth_globals, mp_module_bluetooth_globals_table);
|
|
|
|
const mp_obj_module_t mp_module_ubluetooth = {
|
|
.base = { &mp_type_module },
|
|
.globals = (mp_obj_dict_t *)&mp_module_bluetooth_globals,
|
|
};
|
|
|
|
// Helpers
|
|
|
|
#include <stdio.h>
|
|
|
|
STATIC void ringbuf_extract(ringbuf_t *ringbuf, mp_obj_tuple_t *data_tuple, size_t n_u16, size_t n_u8, mp_obj_str_t *bytes_addr, size_t n_i8, mp_obj_bluetooth_uuid_t *uuid, mp_obj_str_t *bytes_data) {
|
|
assert(ringbuf_avail(ringbuf) >= n_u16 * 2 + n_u8 + (bytes_addr ? 6 : 0) + n_i8 + (uuid ? 1 : 0) + (bytes_data ? 1 : 0));
|
|
int j = 0;
|
|
|
|
for (int i = 0; i < n_u16; ++i) {
|
|
data_tuple->items[j++] = MP_OBJ_NEW_SMALL_INT(ringbuf_get16(ringbuf));
|
|
}
|
|
if (n_u8) {
|
|
data_tuple->items[j++] = MP_OBJ_NEW_SMALL_INT(ringbuf_get(ringbuf));
|
|
}
|
|
if (bytes_addr) {
|
|
bytes_addr->len = 6;
|
|
for (int i = 0; i < bytes_addr->len; ++i) {
|
|
// cast away const, this is actually bt->irq_addr_bytes.
|
|
((uint8_t *)bytes_addr->data)[i] = ringbuf_get(ringbuf);
|
|
}
|
|
data_tuple->items[j++] = MP_OBJ_FROM_PTR(bytes_addr);
|
|
}
|
|
for (int i = 0; i < n_i8; ++i) {
|
|
// Note the int8_t got packed into the ringbuf as a uint8_t.
|
|
data_tuple->items[j++] = MP_OBJ_NEW_SMALL_INT((int8_t)ringbuf_get(ringbuf));
|
|
}
|
|
if (uuid) {
|
|
ringbuf_get_uuid(ringbuf, uuid);
|
|
data_tuple->items[j++] = MP_OBJ_FROM_PTR(uuid);
|
|
}
|
|
// The code that enqueues into the ringbuf should ensure that it doesn't
|
|
// put more than bt->irq_data_data_alloc bytes into the ringbuf, because
|
|
// that's what's available here in bt->irq_data_bytes.
|
|
if (bytes_data) {
|
|
bytes_data->len = ringbuf_get(ringbuf);
|
|
for (int i = 0; i < bytes_data->len; ++i) {
|
|
// cast away const, this is actually bt->irq_data_bytes.
|
|
((uint8_t *)bytes_data->data)[i] = ringbuf_get(ringbuf);
|
|
}
|
|
data_tuple->items[j++] = MP_OBJ_FROM_PTR(bytes_data);
|
|
}
|
|
|
|
data_tuple->len = j;
|
|
}
|
|
|
|
STATIC mp_obj_t bluetooth_ble_invoke_irq(mp_obj_t none_in) {
|
|
// This is always executing in schedule context.
|
|
|
|
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
|
|
o->irq_scheduled = false;
|
|
|
|
for (;;) {
|
|
MICROPY_PY_BLUETOOTH_ENTER
|
|
|
|
mp_int_t event = event = ringbuf_get16(&o->ringbuf);
|
|
if (event < 0) {
|
|
// Nothing available in ringbuf.
|
|
MICROPY_PY_BLUETOOTH_EXIT
|
|
break;
|
|
}
|
|
|
|
// Although we're in schedule context, this code still avoids using any allocations:
|
|
// - IRQs are disabled (to protect the ringbuf), and we need to avoid triggering GC
|
|
// - The user's handler might not alloc, so we shouldn't either.
|
|
|
|
mp_obj_t handler = handler = o->irq_handler;
|
|
mp_obj_tuple_t *data_tuple = MP_OBJ_TO_PTR(o->irq_data_tuple);
|
|
|
|
if (event == MP_BLUETOOTH_IRQ_CENTRAL_CONNECT || event == MP_BLUETOOTH_IRQ_PERIPHERAL_CONNECT || event == MP_BLUETOOTH_IRQ_CENTRAL_DISCONNECT || event == MP_BLUETOOTH_IRQ_PERIPHERAL_DISCONNECT) {
|
|
// conn_handle, addr_type, addr
|
|
ringbuf_extract(&o->ringbuf, data_tuple, 1, 1, &o->irq_data_addr, 0, NULL, NULL);
|
|
} else if (event == MP_BLUETOOTH_IRQ_GATTS_WRITE) {
|
|
// conn_handle, value_handle
|
|
ringbuf_extract(&o->ringbuf, data_tuple, 2, 0, NULL, 0, NULL, NULL);
|
|
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
|
|
} else if (event == MP_BLUETOOTH_IRQ_SCAN_RESULT) {
|
|
// addr_type, addr, adv_type, rssi, adv_data
|
|
ringbuf_extract(&o->ringbuf, data_tuple, 0, 1, &o->irq_data_addr, 2, NULL, &o->irq_data_data);
|
|
} else if (event == MP_BLUETOOTH_IRQ_SCAN_COMPLETE) {
|
|
// No params required.
|
|
data_tuple->len = 0;
|
|
} else if (event == MP_BLUETOOTH_IRQ_GATTC_SERVICE_RESULT) {
|
|
// conn_handle, start_handle, end_handle, uuid
|
|
ringbuf_extract(&o->ringbuf, data_tuple, 3, 0, NULL, 0, &o->irq_data_uuid, NULL);
|
|
} else if (event == MP_BLUETOOTH_IRQ_GATTC_CHARACTERISTIC_RESULT) {
|
|
// conn_handle, def_handle, value_handle, properties, uuid
|
|
ringbuf_extract(&o->ringbuf, data_tuple, 3, 1, NULL, 0, &o->irq_data_uuid, NULL);
|
|
} else if (event == MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_RESULT) {
|
|
// conn_handle, handle, uuid
|
|
ringbuf_extract(&o->ringbuf, data_tuple, 2, 0, NULL, 0, &o->irq_data_uuid, NULL);
|
|
} else if (event == MP_BLUETOOTH_IRQ_GATTC_READ_RESULT || event == MP_BLUETOOTH_IRQ_GATTC_NOTIFY || event == MP_BLUETOOTH_IRQ_GATTC_INDICATE) {
|
|
// conn_handle, value_handle, data
|
|
ringbuf_extract(&o->ringbuf, data_tuple, 2, 0, NULL, 0, NULL, &o->irq_data_data);
|
|
} else if (event == MP_BLUETOOTH_IRQ_GATTC_WRITE_STATUS) {
|
|
// conn_handle, value_handle, status
|
|
ringbuf_extract(&o->ringbuf, data_tuple, 3, 0, NULL, 0, NULL, NULL);
|
|
#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
|
|
}
|
|
|
|
MICROPY_PY_BLUETOOTH_EXIT
|
|
|
|
mp_call_function_2(handler, MP_OBJ_NEW_SMALL_INT(event), MP_OBJ_FROM_PTR(data_tuple));
|
|
}
|
|
|
|
return mp_const_none;
|
|
}
|
|
STATIC MP_DEFINE_CONST_FUN_OBJ_1(bluetooth_ble_invoke_irq_obj, bluetooth_ble_invoke_irq);
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Port API
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// Callbacks are called in interrupt context (i.e. can't allocate), so we need to push the data
|
|
// into the ringbuf and schedule the callback via mp_sched_schedule.
|
|
|
|
STATIC bool enqueue_irq(mp_obj_bluetooth_ble_t *o, size_t len, uint16_t event) {
|
|
if (!o || !(o->irq_trigger & event) || o->irq_handler == mp_const_none) {
|
|
return false;
|
|
}
|
|
|
|
if (ringbuf_free(&o->ringbuf) < len + 2) {
|
|
// Ringbuffer doesn't have room (and is therefore non-empty).
|
|
|
|
// If this is another scan result, or the front of the ringbuffer isn't a scan result, then nothing to do.
|
|
if (event == MP_BLUETOOTH_IRQ_SCAN_RESULT || ringbuf_peek16(&o->ringbuf) != MP_BLUETOOTH_IRQ_SCAN_RESULT) {
|
|
return false;
|
|
}
|
|
|
|
// Front of the queue is a scan result, remove it.
|
|
|
|
// event, addr_type, addr, adv_type, rssi
|
|
int n = 2 + 1 + 6 + 1 + 1;
|
|
for (int i = 0; i < n; ++i) {
|
|
ringbuf_get(&o->ringbuf);
|
|
}
|
|
// adv_data
|
|
n = ringbuf_get(&o->ringbuf);
|
|
for (int i = 0; i < n; ++i) {
|
|
ringbuf_get(&o->ringbuf);
|
|
}
|
|
}
|
|
|
|
// Append this event, the caller will then append the arguments.
|
|
ringbuf_put16(&o->ringbuf, event);
|
|
return true;
|
|
}
|
|
|
|
STATIC void schedule_ringbuf(void) {
|
|
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
|
|
if (!o->irq_scheduled) {
|
|
o->irq_scheduled = true;
|
|
mp_sched_schedule(MP_OBJ_FROM_PTR(&bluetooth_ble_invoke_irq_obj), mp_const_none);
|
|
}
|
|
}
|
|
|
|
void mp_bluetooth_gap_on_connected_disconnected(uint16_t event, uint16_t conn_handle, uint8_t addr_type, const uint8_t *addr) {
|
|
MICROPY_PY_BLUETOOTH_ENTER
|
|
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
|
|
if (enqueue_irq(o, 2 + 1 + 6, event)) {
|
|
ringbuf_put16(&o->ringbuf, conn_handle);
|
|
ringbuf_put(&o->ringbuf, addr_type);
|
|
for (int i = 0; i < 6; ++i) {
|
|
ringbuf_put(&o->ringbuf, addr[i]);
|
|
}
|
|
}
|
|
schedule_ringbuf();
|
|
MICROPY_PY_BLUETOOTH_EXIT
|
|
}
|
|
|
|
void mp_bluetooth_gatts_on_write(uint16_t conn_handle, uint16_t value_handle) {
|
|
MICROPY_PY_BLUETOOTH_ENTER
|
|
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
|
|
if (enqueue_irq(o, 2 + 2, MP_BLUETOOTH_IRQ_GATTS_WRITE)) {
|
|
ringbuf_put16(&o->ringbuf, conn_handle);
|
|
ringbuf_put16(&o->ringbuf, value_handle);
|
|
}
|
|
schedule_ringbuf();
|
|
MICROPY_PY_BLUETOOTH_EXIT
|
|
}
|
|
|
|
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
|
|
void mp_bluetooth_gap_on_scan_complete(void) {
|
|
MICROPY_PY_BLUETOOTH_ENTER
|
|
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
|
|
if (enqueue_irq(o, 0, MP_BLUETOOTH_IRQ_SCAN_COMPLETE)) {
|
|
}
|
|
schedule_ringbuf();
|
|
MICROPY_PY_BLUETOOTH_EXIT
|
|
}
|
|
|
|
void mp_bluetooth_gap_on_scan_result(uint8_t addr_type, const uint8_t *addr, uint8_t adv_type, const int8_t rssi, const uint8_t *data, size_t data_len) {
|
|
MICROPY_PY_BLUETOOTH_ENTER
|
|
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
|
|
data_len = MIN(o->irq_data_data_alloc, data_len);
|
|
if (enqueue_irq(o, 1 + 6 + 1 + 1 + 1 + data_len, MP_BLUETOOTH_IRQ_SCAN_RESULT)) {
|
|
ringbuf_put(&o->ringbuf, addr_type);
|
|
for (int i = 0; i < 6; ++i) {
|
|
ringbuf_put(&o->ringbuf, addr[i]);
|
|
}
|
|
// The adv_type will get extracted as an int8_t but that's ok because valid values are 0x00-0x04.
|
|
ringbuf_put(&o->ringbuf, adv_type);
|
|
// Note conversion of int8_t rssi to uint8_t. Must un-convert on the way out.
|
|
ringbuf_put(&o->ringbuf, (uint8_t)rssi);
|
|
ringbuf_put(&o->ringbuf, data_len);
|
|
for (int i = 0; i < data_len; ++i) {
|
|
ringbuf_put(&o->ringbuf, data[i]);
|
|
}
|
|
}
|
|
schedule_ringbuf();
|
|
MICROPY_PY_BLUETOOTH_EXIT
|
|
}
|
|
|
|
void mp_bluetooth_gattc_on_primary_service_result(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle, mp_obj_bluetooth_uuid_t *service_uuid) {
|
|
MICROPY_PY_BLUETOOTH_ENTER
|
|
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
|
|
if (enqueue_irq(o, 2 + 2 + 2 + 1 + service_uuid->type, MP_BLUETOOTH_IRQ_GATTC_SERVICE_RESULT)) {
|
|
ringbuf_put16(&o->ringbuf, conn_handle);
|
|
ringbuf_put16(&o->ringbuf, start_handle);
|
|
ringbuf_put16(&o->ringbuf, end_handle);
|
|
ringbuf_put_uuid(&o->ringbuf, service_uuid);
|
|
}
|
|
schedule_ringbuf();
|
|
MICROPY_PY_BLUETOOTH_EXIT
|
|
}
|
|
|
|
void mp_bluetooth_gattc_on_characteristic_result(uint16_t conn_handle, uint16_t def_handle, uint16_t value_handle, uint8_t properties, mp_obj_bluetooth_uuid_t *characteristic_uuid) {
|
|
MICROPY_PY_BLUETOOTH_ENTER
|
|
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
|
|
if (enqueue_irq(o, 2 + 2 + 2 + 1 + characteristic_uuid->type, MP_BLUETOOTH_IRQ_GATTC_CHARACTERISTIC_RESULT)) {
|
|
ringbuf_put16(&o->ringbuf, conn_handle);
|
|
ringbuf_put16(&o->ringbuf, def_handle);
|
|
ringbuf_put16(&o->ringbuf, value_handle);
|
|
ringbuf_put(&o->ringbuf, properties);
|
|
ringbuf_put_uuid(&o->ringbuf, characteristic_uuid);
|
|
}
|
|
schedule_ringbuf();
|
|
MICROPY_PY_BLUETOOTH_EXIT
|
|
}
|
|
|
|
void mp_bluetooth_gattc_on_descriptor_result(uint16_t conn_handle, uint16_t handle, mp_obj_bluetooth_uuid_t *descriptor_uuid) {
|
|
MICROPY_PY_BLUETOOTH_ENTER
|
|
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
|
|
if (enqueue_irq(o, 2 + 2 + 1 + descriptor_uuid->type, MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_RESULT)) {
|
|
ringbuf_put16(&o->ringbuf, conn_handle);
|
|
ringbuf_put16(&o->ringbuf, handle);
|
|
ringbuf_put_uuid(&o->ringbuf, descriptor_uuid);
|
|
}
|
|
schedule_ringbuf();
|
|
MICROPY_PY_BLUETOOTH_EXIT
|
|
}
|
|
|
|
size_t mp_bluetooth_gattc_on_data_available_start(uint16_t event, uint16_t conn_handle, uint16_t value_handle, size_t data_len) {
|
|
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
|
|
data_len = MIN(o->irq_data_data_alloc, data_len);
|
|
if (enqueue_irq(o, 2 + 2 + 1 + data_len, event)) {
|
|
ringbuf_put16(&o->ringbuf, conn_handle);
|
|
ringbuf_put16(&o->ringbuf, value_handle);
|
|
ringbuf_put(&o->ringbuf, data_len);
|
|
return data_len;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void mp_bluetooth_gattc_on_data_available_chunk(const uint8_t *data, size_t data_len) {
|
|
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
|
|
for (int i = 0; i < data_len; ++i) {
|
|
ringbuf_put(&o->ringbuf, data[i]);
|
|
}
|
|
}
|
|
|
|
void mp_bluetooth_gattc_on_data_available_end(void) {
|
|
schedule_ringbuf();
|
|
}
|
|
|
|
void mp_bluetooth_gattc_on_write_status(uint16_t conn_handle, uint16_t value_handle, uint16_t status) {
|
|
MICROPY_PY_BLUETOOTH_ENTER
|
|
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
|
|
if (enqueue_irq(o, 2 + 2 + 2, MP_BLUETOOTH_IRQ_GATTC_WRITE_STATUS)) {
|
|
ringbuf_put16(&o->ringbuf, conn_handle);
|
|
ringbuf_put16(&o->ringbuf, value_handle);
|
|
ringbuf_put16(&o->ringbuf, status);
|
|
}
|
|
schedule_ringbuf();
|
|
MICROPY_PY_BLUETOOTH_EXIT
|
|
}
|
|
|
|
#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
|
|
|
|
#if MICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK
|
|
// This can only be enabled when the thread invoking this is a MicroPython thread.
|
|
// On ESP32, for example, this is not the case.
|
|
bool mp_bluetooth_gatts_on_read_request(uint16_t conn_handle, uint16_t value_handle) {
|
|
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
|
|
if ((o->irq_trigger & MP_BLUETOOTH_IRQ_GATTS_READ_REQUEST) && o->irq_handler != mp_const_none) {
|
|
// Use pre-allocated tuple because this is a hard IRQ.
|
|
mp_obj_tuple_t *data = MP_OBJ_TO_PTR(o->irq_data_tuple);
|
|
data->items[0] = MP_OBJ_NEW_SMALL_INT(conn_handle);
|
|
data->items[1] = MP_OBJ_NEW_SMALL_INT(value_handle);
|
|
data->len = 2;
|
|
mp_obj_t irq_ret = mp_call_function_2_protected(o->irq_handler, MP_OBJ_NEW_SMALL_INT(MP_BLUETOOTH_IRQ_GATTS_READ_REQUEST), o->irq_data_tuple);
|
|
// If the IRQ handler explicitly returned false, then deny the read. Otherwise if it returns None/True, allow it.
|
|
return irq_ret != MP_OBJ_NULL && (irq_ret == mp_const_none || mp_obj_is_true(irq_ret));
|
|
} else {
|
|
// No IRQ handler, allow the read.
|
|
return true;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void mp_bluetooth_gatts_db_create_entry(mp_gatts_db_t db, uint16_t handle, size_t len) {
|
|
mp_map_elem_t *elem = mp_map_lookup(db, MP_OBJ_NEW_SMALL_INT(handle), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND);
|
|
mp_bluetooth_gatts_db_entry_t *entry = m_new(mp_bluetooth_gatts_db_entry_t, 1);
|
|
entry->data = m_new(uint8_t, len);
|
|
entry->data_alloc = len;
|
|
entry->data_len = 0;
|
|
entry->append = false;
|
|
elem->value = MP_OBJ_FROM_PTR(entry);
|
|
}
|
|
|
|
mp_bluetooth_gatts_db_entry_t *mp_bluetooth_gatts_db_lookup(mp_gatts_db_t db, uint16_t handle) {
|
|
mp_map_elem_t *elem = mp_map_lookup(db, MP_OBJ_NEW_SMALL_INT(handle), MP_MAP_LOOKUP);
|
|
if (!elem) {
|
|
return NULL;
|
|
}
|
|
return MP_OBJ_TO_PTR(elem->value);
|
|
}
|
|
|
|
int mp_bluetooth_gatts_db_read(mp_gatts_db_t db, uint16_t handle, uint8_t **value, size_t *value_len) {
|
|
mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(db, handle);
|
|
if (!entry) {
|
|
return MP_EINVAL;
|
|
}
|
|
|
|
*value = entry->data;
|
|
*value_len = entry->data_len;
|
|
if (entry->append) {
|
|
entry->data_len = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int mp_bluetooth_gatts_db_write(mp_gatts_db_t db, uint16_t handle, const uint8_t *value, size_t value_len) {
|
|
mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(db, handle);
|
|
if (!entry) {
|
|
return MP_EINVAL;
|
|
}
|
|
|
|
if (value_len > entry->data_alloc) {
|
|
entry->data = m_new(uint8_t, value_len);
|
|
entry->data_alloc = value_len;
|
|
}
|
|
|
|
memcpy(entry->data, value, value_len);
|
|
entry->data_len = value_len;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int mp_bluetooth_gatts_db_resize(mp_gatts_db_t db, uint16_t handle, size_t len, bool append) {
|
|
mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(db, handle);
|
|
if (!entry) {
|
|
return MP_EINVAL;
|
|
}
|
|
entry->data = m_renew(uint8_t, entry->data, entry->data_alloc, len);
|
|
entry->data_alloc = len;
|
|
entry->data_len = 0;
|
|
entry->append = append;
|
|
return 0;
|
|
}
|
|
|
|
#endif // MICROPY_PY_BLUETOOTH
|