diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c new file mode 100644 index 0000000000000..1fd6b78daa119 --- /dev/null +++ b/extmod/modbluetooth.c @@ -0,0 +1,757 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 Ayke van Laethem + * + * 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/obj.h" +#include "py/objstr.h" +#include "py/objarray.h" +#include "py/binary.h" +#include "py/runtime.h" +#include "py/qstr.h" +#include "extmod/modbluetooth.h" +#include + +#if MICROPY_PY_BLUETOOTH + +STATIC const mp_obj_type_t bluetooth_type; +STATIC const mp_obj_type_t device_type; +STATIC const mp_obj_type_t service_type; +STATIC const mp_obj_type_t characteristic_type; + +STATIC volatile uint16_t active_connections[MP_BT_MAX_CONNECTED_DEVICES]; + +// A device that has just connected/disconnected. +#if MICROPY_ENABLE_SCHEDULER +STATIC mp_bt_device_t global_device_object = { + { &device_type }, +}; +#endif +STATIC uint8_t bt_event_trigger; + +// This is a circular buffer of incoming packets. +// Packets are length-prefixed chunks of data in the circular buffer. The +// length is a single byte with the size of the packet excluding the +// length byte itself. +// The head/tail indices are like a normal ring buffer, except that they +// do not wrap around at UPDATE_BUF_SIZE but instead continue to increase +// in size. This means that the head/tail indices rely on unsigned +// wraparound. That is, if there is no data in the buffer head equals +// tail. If there is data in the queue, head is always ahead of tail +// modulo 2**16. +// If there is data in the buffer, this is the number of bytes currently +// in the buffer: +// head - tail +// and this is the size of the first packet: +// data[tail % UPDATE_BUF_SIZE] +// Similarly, head always points to the first unused byte (or the same +// byte if the buffer is exactly filled). +// +// Additionally, there is a counter of dropped packets. When packets are +// dropped, it are always the oldest packets. So by incrementing the count +// of dropped packets when the oldest packet is dropped, the next event +// that is handled knows that its packet was dropped due to a buffer +// overrun. This ensures that it is known exactly how many packets are +// dropped and the buffer can keep on accepting new packets. +// +#define UPDATE_BUF_SIZE 32 +STATIC struct { + volatile uint16_t head; + volatile uint16_t tail; + volatile uint16_t dropped_packets; + volatile uint8_t data[UPDATE_BUF_SIZE]; +} update_buf; + +typedef struct _mp_obj_bluetooth_t { + mp_obj_base_t base; +} mp_obj_bluetooth_t; + + +// instantiated Bluetooth object +STATIC const mp_obj_bluetooth_t bluetooth_obj = { + { &bluetooth_type }, +}; + +// Easier (hopefully tail-called) error handling. +STATIC mp_obj_t bluetooth_handle_errno(int errno_) { + if (errno_ != 0) { + mp_raise_OSError(errno_); + } + return mp_const_none; +} + +// Parse string UUIDs, which are probably 128-bit UUIDs. +void mp_bt_parse_uuid_str(mp_obj_t obj, uint8_t *uuid) { + GET_STR_DATA_LEN(obj, str_data, str_len); + int uuid_i = 32; + for (int i = 0; i < str_len; i++) { + char c = str_data[i]; + if (c == '-') { + continue; + } + if (c >= '0' && c <= '9') { + c = c - '0'; + } else if (c >= 'a' && c <= 'f') { + c = c - 'a' + 10; + } else if (c >= 'A' && c <= 'F') { + c = c - 'A' + 10; + } else { + mp_raise_ValueError("unknown char in UUID"); + } + uuid_i--; + if (uuid_i < 0) { + mp_raise_ValueError("UUID too long"); + } + if (uuid_i % 2 == 0) { + // lower nibble + uuid[uuid_i/2] |= c; + } else { + // upper nibble + uuid[uuid_i/2] = c << 4; + } + } + if (uuid_i > 0) { + mp_raise_ValueError("UUID too short"); + } +} + +// Format string UUID. Example output: +// '6e400001-b5a3-f393-e0a9-e50e24dcca9e' +mp_obj_t mp_bt_format_uuid_str(const uint8_t *uuid) { + char str[36]; + char *s = str; + for (int i = 15; i >= 0; i--) { + char nibble = uuid[i] >> 4; + if (nibble >= 10) { + nibble += 'a' - 10; + } else { + nibble += '0'; + } + *(s++) = nibble; + + nibble = uuid[i] & 0xf; + if (nibble >= 10) { + nibble += 'a' - 10; + } else { + nibble += '0'; + } + *(s++) = nibble; + + if (i == 12 || i == 10 || i == 8 || i == 6) { + *(s++) = '-'; + } + } + return mp_obj_new_str(str, MP_ARRAY_SIZE(str)); +} + +// Add this connection handle to the list of connected centrals. +void mp_bt_connected(uint16_t conn_handle, const uint8_t *address) { + for (size_t i = 0; i < MP_BT_MAX_CONNECTED_DEVICES; i++) { + if (active_connections[i] == MP_BT_INVALID_CONN_HANDLE) { + active_connections[i] = conn_handle; + break; + } + } + + mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); + if ((bt_event_trigger & MP_BT_IRQ_CONNECT) != 0 && MP_STATE_PORT(bt_event_handler) != mp_const_none) { + #if MICROPY_ENABLE_SCHEDULER + global_device_object.conn_handle = conn_handle; + memcpy(global_device_object.address, address, 6); + mp_sched_schedule(MP_STATE_PORT(bt_event_handler), &global_device_object); + #else + mp_bt_device_t device = {0}; + device.base.type = &device_type; + device.conn_handle = conn_handle; + memcpy(device.address, address, 6); + mp_call_function_1_protected(MP_STATE_PORT(bt_event_handler), &device); + #endif + } + MICROPY_END_ATOMIC_SECTION(atomic_state); +} + +// Remove this connection handle from the list of connected centrals. +void mp_bt_disconnected(uint16_t conn_handle, const uint8_t *address) { + for (size_t i = 0; i < MP_BT_MAX_CONNECTED_DEVICES; i++) { + if (active_connections[i] == conn_handle) { + active_connections[i] = MP_BT_INVALID_CONN_HANDLE; + break; + } + } + + mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); + if (MP_STATE_PORT(bt_event_handler) != mp_const_none && (bt_event_trigger & MP_BT_IRQ_DISCONNECT) != 0) { + #if MICROPY_ENABLE_SCHEDULER + if (address != NULL) { + memcpy(global_device_object.address, address, 6); + } else { + memset(global_device_object.address, 0, 6); + } + global_device_object.conn_handle = MP_BT_INVALID_CONN_HANDLE; + mp_sched_schedule(MP_STATE_PORT(bt_event_handler), &global_device_object); + #else + mp_bt_device_t device = {0}; + device.base.type = &device_type; + device.conn_handle = MP_BT_INVALID_CONN_HANDLE; + if (address != NULL) { + memcpy(device.address, address, 6); + } else { + memset(device.address, 0, 6); + } + mp_call_function_1_protected(MP_STATE_PORT(bt_event_handler), &device); + #endif + } + MICROPY_END_ATOMIC_SECTION(atomic_state); +} + +STATIC mp_obj_t bluetooth_write_callback(mp_obj_t char_in) { + mp_bt_characteristic_t *characteristic = char_in; + + // Copy the incoming buffer. + mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); + uint8_t value[20]; // maximum BLE packet size + uint16_t tail = update_buf.tail; + size_t value_len; + uint16_t conn_handle = MP_BT_INVALID_CONN_HANDLE; + if (update_buf.dropped_packets) { + // Handle dropped packet. + update_buf.dropped_packets--; + value_len = (size_t)-1; + } else { + // Copy regular incoming packet. + size_t data_len = update_buf.data[tail++ % UPDATE_BUF_SIZE]; + value_len = data_len - 2; + update_buf.tail = tail + data_len; + if (value_len > sizeof(value)) { + // Packet was too big, only pass the first N bytes. + value_len = sizeof(value); + } + conn_handle = update_buf.data[tail++ % UPDATE_BUF_SIZE]; + conn_handle |= update_buf.data[tail++ % UPDATE_BUF_SIZE] << 8; + for (size_t i = 0; i < value_len; i++) { + value[i] = update_buf.data[tail++ % UPDATE_BUF_SIZE]; + } + } + MICROPY_END_ATOMIC_SECTION(atomic_state); + + // Look for the callback in the linked list of callbacks. + mp_bt_characteristic_callback_t *item = MP_STATE_PORT(bt_characteristic_callbacks); + while (item != NULL && item->characteristic->value_handle != characteristic->value_handle) { + item = item->next; + } + if (item == NULL) { + // Callback has been removed? + // This can happen when the callback is removed between the + // interrupt and handling the interrupt. + return mp_const_none; + } + + if (value_len == (size_t)-1) { + // Unfortunately, there was a dropped packet. + // Report this event by passing None. + mp_obj_t args[3] = { + mp_const_none, + MP_OBJ_FROM_PTR(item->characteristic), + mp_const_none, + }; + mp_call_function_n_kw(item->callback, 3, 0, args); + } else { + // Pass the written data directly as a bytearray to the callback. + // WARNING: this array must not be modified by the callee. + mp_obj_array_t ar = {{&mp_type_bytearray}, BYTEARRAY_TYPECODE, 0, value_len, value}; + mp_bt_device_t device = {0}; + device.base.type = &device_type; + device.conn_handle = conn_handle; + mp_obj_t args[3] = { + &device, + MP_OBJ_FROM_PTR(item->characteristic), + MP_OBJ_FROM_PTR(&ar), + }; + mp_call_function_n_kw(item->callback, 3, 0, args); + } + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(bluetooth_write_callback_obj, bluetooth_write_callback); + +// Call the registered callback for this characteristic, if one has been +// registered. +void mp_bt_characteristic_on_write(uint16_t conn_handle, uint16_t value_handle, const void *value, size_t value_len) { + // Iterate through the linked list to find to find the characteristic + // with the given handle. + mp_bt_characteristic_callback_t *item = MP_STATE_PORT(bt_characteristic_callbacks); + while (item != NULL) { + if (item->characteristic->value_handle == value_handle) { + if ((item->triggers & MP_BT_IRQ_WRITE) == 0) { + // This callback should not be called for writes. + break; + } + + // Insert packet into queue. + uint16_t head = update_buf.head; + uint16_t tail = update_buf.tail; + size_t bytes_left = ((uint16_t)UPDATE_BUF_SIZE - (head - tail)); + // A packet has the following components: + // - 1 byte packet size (excluding this byte) + // - 2 byte conn_handle + // - N bytes data + size_t packet_len = value_len + 3; + while (bytes_left < packet_len) { + // Drop oldest packet. + uint8_t packet_len = update_buf.data[tail % UPDATE_BUF_SIZE]; + tail += packet_len + 1; + update_buf.tail = tail; + bytes_left = ((uint16_t)UPDATE_BUF_SIZE - (head - tail)); + update_buf.dropped_packets++; + } + update_buf.data[head++ % UPDATE_BUF_SIZE] = (uint8_t)(packet_len - 1); + update_buf.data[head++ % UPDATE_BUF_SIZE] = (uint8_t)(conn_handle & 0xff); // low bits + update_buf.data[head++ % UPDATE_BUF_SIZE] = (uint8_t)(conn_handle >> 8); // high bits + for (size_t i = 0; i < value_len; i++) { + update_buf.data[head++ % UPDATE_BUF_SIZE] = ((uint8_t*)value)[i]; + } + update_buf.head = head; + + // Queue callback. + #if MICROPY_ENABLE_SCHEDULER + if (!mp_sched_schedule(MP_OBJ_FROM_PTR(&bluetooth_write_callback_obj), item->characteristic)) { + // Failed to schedule a callback: the queue is full. + // There's not much we can do now. + return; + } + #else + mp_call_function_1_protected(MP_OBJ_FROM_PTR(&bluetooth_write_callback_obj), item->characteristic); + #endif + + return; + } + item = item->next; + } +} + +STATIC mp_obj_t bluetooth_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + return MP_OBJ_FROM_PTR(&bluetooth_obj); +} + +STATIC mp_obj_t bluetooth_active(size_t n_args, const mp_obj_t *args) { + if (n_args == 2) { // boolean enable/disable argument supplied + if (mp_obj_is_true(args[1])) { + for (size_t i = 0; i < MP_BT_MAX_CONNECTED_DEVICES; i++) { + active_connections[i] = MP_BT_INVALID_CONN_HANDLE; + } + int errno_ = mp_bt_enable(); + if (errno_ != 0) { + mp_raise_OSError(errno_); + } + } else { + mp_bt_disable(); + } + } + return mp_obj_new_bool(mp_bt_is_enabled()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_active_obj, 1, 2, bluetooth_active); + +STATIC mp_obj_t bluetooth_advertise(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_interval, ARG_name, ARG_adv_data, ARG_resp_data, ARG_connectable }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_interval, MP_ARG_INT, {.u_int = 100} }, + { MP_QSTR_name, MP_ARG_OBJ, {.u_obj = mp_const_none } }, + { MP_QSTR_adv_data, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = mp_const_none } }, + { MP_QSTR_resp_data, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = mp_const_none } }, + { MP_QSTR_connectable, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = mp_const_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); + + mp_int_t interval = args[ARG_interval].u_int; + if (interval == 0) { + mp_bt_advertise_stop(); + return mp_const_none; + } + interval = interval * 8 / 5; // convert from 1ms to 0.625ms units + if (interval < 0x20 || interval > 0x4000) { + mp_raise_ValueError("interval out of range"); + } + + mp_bt_adv_type_t adv_type = MP_BT_ADV_TYPE_ADV_IND; // connectable=True + if (!mp_obj_is_true(args[ARG_connectable].u_obj)) { + adv_type = MP_BT_ADV_TYPE_ADV_NONCONN_IND; // connectable=False + } + + uint8_t adv_data_buf[31]; + const uint8_t *adv_data = NULL; + size_t adv_data_len = 0; + + // Pick advertisement data. + if (args[ARG_name].u_obj != mp_const_none) { + // Base the advertisement on the 'name' keyword argument. + size_t name_len; + const char *name = mp_obj_str_get_data(args[ARG_name].u_obj, &name_len); + adv_data_buf[adv_data_len++] = 2; // 1 byte type + 1 byte flags data + adv_data_buf[adv_data_len++] = MP_BLE_GAP_AD_TYPE_FLAG; + adv_data_buf[adv_data_len++] = MP_BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE; + + if (name_len + 3 > sizeof(adv_data_buf) - adv_data_len) { + mp_raise_ValueError("advertisement packet overflow"); + } + adv_data_buf[adv_data_len++] = name_len + 1; + adv_data_buf[adv_data_len++] = MP_BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME; + for (size_t i=0; ilen; i++) { + mp_obj_t characteristic = characteristics->items[i]; + if (characteristic == NULL || !mp_obj_is_type(characteristic, &characteristic_type)) { + mp_raise_ValueError("not a Characteristic"); + } + if (((mp_bt_characteristic_t*)characteristic)->service != NULL) { + mp_raise_ValueError("Characteristic already added to Service"); + } + } + + mp_bt_service_t *service = m_new_obj(mp_bt_service_t); + service->base.type = &service_type; + mp_bt_parse_uuid(args[ARG_uuid].u_obj, &service->uuid); + int errno_ = mp_bt_add_service(service, characteristics->len, (mp_bt_characteristic_t**)characteristics->items); + bluetooth_handle_errno(errno_); + return service; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(bluetooth_add_service_obj, 1, bluetooth_add_service); + +STATIC mp_obj_t bluetooth_config(mp_obj_t self_in, mp_obj_t param) { + switch ((uintptr_t)param) { + case (uintptr_t)MP_OBJ_NEW_QSTR(MP_QSTR_mac): { + uint8_t address[6]; + mp_bt_get_address(address); + return mp_obj_new_bytes(address, MP_ARRAY_SIZE(address)); + } + default: + mp_raise_ValueError("unknown config param"); + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(bluetooth_config_obj, bluetooth_config); + +STATIC mp_obj_t bluetooth_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_obj = mp_const_none} }, + { MP_QSTR_trigger, MP_ARG_INT|MP_ARG_REQUIRED, {.u_int = 0} }, + }; + 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_fun(callback)) { + mp_raise_ValueError("invalid callback"); + } + + // Update the callback. + mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); + MP_STATE_PORT(bt_event_handler) = callback; + bt_event_trigger = args[ARG_trigger].u_int; + MICROPY_END_ATOMIC_SECTION(atomic_state); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(bluetooth_irq_obj, 1, bluetooth_irq); + +STATIC mp_obj_t device_config(mp_obj_t self_in, mp_obj_t param) { + mp_bt_device_t *device = self_in; + switch ((uintptr_t)param) { + case (uintptr_t)MP_OBJ_NEW_QSTR(MP_QSTR_mac): { + bool has_address = false; + for (int i = 0; i < 6; i++) { + if (device->address[i] != 0) { + has_address = true; + } + } + if (has_address) { + return mp_obj_new_bytes(device->address, 6); + } else { + return mp_const_none; + } + } + default: + mp_raise_ValueError("unknown config param"); + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(device_config_obj, device_config); + +STATIC mp_obj_t device_connected(mp_obj_t self_in) { + mp_bt_device_t *device = self_in; + return mp_obj_new_bool(device->conn_handle != MP_BT_INVALID_CONN_HANDLE); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(device_connected_obj, device_connected); + +STATIC mp_obj_t device_disconnect(mp_obj_t self_in) { + mp_bt_device_t *device = self_in; + if (device->conn_handle != MP_BT_INVALID_CONN_HANDLE) { + uint16_t conn_handle = device->conn_handle; + device->conn_handle = MP_BT_INVALID_CONN_HANDLE; + int errno_ = mp_bt_device_disconnect(conn_handle); + return bluetooth_handle_errno(errno_); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(device_disconnect_obj, device_disconnect); + +STATIC const mp_rom_map_elem_t device_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&device_config_obj) }, + { MP_ROM_QSTR(MP_QSTR_connected), MP_ROM_PTR(&device_connected_obj) }, + { MP_ROM_QSTR(MP_QSTR_disconnect), MP_ROM_PTR(&device_disconnect_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(device_locals_dict, device_locals_dict_table); + +STATIC const mp_obj_type_t device_type = { + { &mp_type_type }, + .name = MP_QSTR_Device, + .locals_dict = (void*)&device_locals_dict, +}; + +STATIC mp_obj_t service_uuid(mp_obj_t self_in) { + mp_bt_service_t *service = self_in; + return mp_bt_format_uuid(&service->uuid); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(service_uuid_obj, service_uuid); + +STATIC const mp_rom_map_elem_t service_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_uuid), MP_ROM_PTR(&service_uuid_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(service_locals_dict, service_locals_dict_table); + +STATIC const mp_obj_type_t service_type = { + { &mp_type_type }, + .name = MP_QSTR_Service, + .locals_dict = (void*)&service_locals_dict, +}; + +STATIC mp_obj_t characteristic_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_uuid, ARG_flags }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_uuid, MP_ARG_OBJ | MP_ARG_REQUIRED }, + { MP_QSTR_flags, MP_ARG_INT | MP_ARG_REQUIRED }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if ((uint8_t)(args[ARG_flags].u_int) != args[ARG_flags].u_int) { + // Flags don't fit in 8 bits. + mp_raise_ValueError("invalid flags"); + } + + mp_bt_characteristic_t *characteristic = m_new_obj(mp_bt_characteristic_t); + characteristic->base.type = &characteristic_type; + mp_bt_parse_uuid(args[0].u_obj, &characteristic->uuid); + characteristic->flags = (uint8_t)(args[ARG_flags].u_int); + return characteristic; +} + +STATIC mp_obj_t characteristic_service(mp_obj_t self_in) { + mp_bt_characteristic_t *characteristic = self_in; + if (characteristic->service == NULL) { + return mp_const_none; + } + return characteristic->service; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(characteristic_service_obj, characteristic_service); + +STATIC mp_obj_t characteristic_uuid(mp_obj_t self_in) { + mp_bt_characteristic_t *characteristic = self_in; + return mp_bt_format_uuid(&characteristic->uuid); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(characteristic_uuid_obj, characteristic_uuid); + +STATIC mp_obj_t characteristic_write(mp_obj_t self_in, mp_obj_t value_in) { + mp_bt_characteristic_t *characteristic = self_in; + GET_STR_DATA_LEN(value_in, str_data, str_len); + int errno_; + if ((characteristic->flags & MP_BLE_FLAG_NOTIFY) != 0) { + bool updated = false; + for (size_t i = 0; i < MP_BT_MAX_CONNECTED_DEVICES; i++) { + uint16_t conn_handle = active_connections[i]; + if (conn_handle == MP_BT_INVALID_CONN_HANDLE) { + continue; + } + errno_ = mp_bt_characteristic_value_notify(characteristic, conn_handle, str_data, str_len); + if (errno_ != 0) { + break; + } + updated = true; + } + if (!updated) { + errno_ = mp_bt_characteristic_value_set(characteristic, str_data, str_len); + } + } else { + errno_ = mp_bt_characteristic_value_set(characteristic, str_data, str_len); + } + return bluetooth_handle_errno(errno_); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(characteristic_write_obj, characteristic_write); + +STATIC mp_obj_t characteristic_read(mp_obj_t self_in) { + mp_bt_characteristic_t *characteristic = self_in; + uint8_t data[MP_BT_MAX_ATTR_SIZE]; + size_t value_len = MP_BT_MAX_ATTR_SIZE; + int errno_ = mp_bt_characteristic_value_get(characteristic, data, &value_len); + if (errno_ != 0) { + mp_raise_OSError(errno_); + } + return mp_obj_new_bytes(data, value_len); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(characteristic_read_obj, characteristic_read); + +STATIC mp_obj_t characteristic_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_obj = mp_const_none} }, + { MP_QSTR_trigger, MP_ARG_INT|MP_ARG_REQUIRED, {.u_int = 0} }, + }; + mp_bt_characteristic_t *characteristic = MP_OBJ_TO_PTR(pos_args[0]); + 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_fun(callback)) { + mp_raise_ValueError("invalid callback"); + } + + // A singly linked list of callbacks. In pseudocode: + // If the new callback is none: + // Find a registered callback for this characteristic and remove it. + // Else: + // Replace a registered callback for this characteristic. + // If none exists, add it at the end of the list. + mp_bt_characteristic_callback_t **entry = &MP_STATE_PORT(bt_characteristic_callbacks); + while (1) { + if (*entry == NULL) { + // found the end of the list + if (callback != mp_const_none) { + // add callback to the end of the list + *entry = m_new_obj(mp_bt_characteristic_callback_t); + (*entry)->characteristic = characteristic; + (*entry)->callback = callback; + (*entry)->triggers = args[ARG_trigger].u_int; + } + break; + } + if ((*entry)->characteristic == characteristic) { + // found existing entry + if (callback == mp_const_none) { + // delete this callback + *entry = (*entry)->next; + } else { + // update the entry with the new callback + (*entry)->callback = callback; + (*entry)->triggers = args[ARG_trigger].u_int; + } + break; + } + entry = &(*entry)->next; + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(characteristic_irq_obj, 1, characteristic_irq); + +STATIC const mp_rom_map_elem_t characteristic_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_service), MP_ROM_PTR(&characteristic_service_obj) }, + { MP_ROM_QSTR(MP_QSTR_uuid), MP_ROM_PTR(&characteristic_uuid_obj) }, + { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&characteristic_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&characteristic_read_obj) }, + { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&characteristic_irq_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(characteristic_locals_dict, characteristic_locals_dict_table); + +STATIC const mp_obj_type_t characteristic_type = { + { &mp_type_type }, + .name = MP_QSTR_Characteristic, + .make_new = characteristic_make_new, + .locals_dict = (void*)&characteristic_locals_dict, +}; + +STATIC const mp_rom_map_elem_t bluetooth_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&bluetooth_active_obj) }, + { MP_ROM_QSTR(MP_QSTR_advertise), MP_ROM_PTR(&bluetooth_advertise_obj) }, + { MP_ROM_QSTR(MP_QSTR_add_service), MP_ROM_PTR(&bluetooth_add_service_obj) }, + { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&bluetooth_config_obj) }, + { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&bluetooth_irq_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(bluetooth_locals_dict, bluetooth_locals_dict_table); + +STATIC const mp_obj_type_t bluetooth_type = { + { &mp_type_type }, + .name = MP_QSTR_Bluetooth, + .make_new = bluetooth_make_new, + .locals_dict = (void*)&bluetooth_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_bluetooth) }, + { MP_ROM_QSTR(MP_QSTR_Bluetooth), MP_ROM_PTR(&bluetooth_type) }, + { MP_ROM_QSTR(MP_QSTR_Device), MP_ROM_PTR(&device_type) }, + { MP_ROM_QSTR(MP_QSTR_Service), MP_ROM_PTR(&service_type) }, + { MP_ROM_QSTR(MP_QSTR_Characteristic), MP_ROM_PTR(&characteristic_type) }, + { MP_ROM_QSTR(MP_QSTR_FLAG_READ), MP_ROM_INT(MP_BLE_FLAG_READ) }, + { MP_ROM_QSTR(MP_QSTR_FLAG_WRITE), MP_ROM_INT(MP_BLE_FLAG_WRITE) }, + { MP_ROM_QSTR(MP_QSTR_FLAG_NOTIFY), MP_ROM_INT(MP_BLE_FLAG_NOTIFY) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_CONNECT), MP_ROM_INT(MP_BT_IRQ_CONNECT) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_DISCONNECT), MP_ROM_INT(MP_BT_IRQ_DISCONNECT) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_WRITE), MP_ROM_INT(MP_BT_IRQ_WRITE) }, +}; +STATIC MP_DEFINE_CONST_DICT(mp_module_bluetooth_globals, mp_module_bluetooth_globals_table); + +const mp_obj_module_t mp_module_bluetooth = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_bluetooth_globals, +}; + +#endif //MICROPY_PY_BLUETOOTH diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h new file mode 100644 index 0000000000000..bbdb5a7f67022 --- /dev/null +++ b/extmod/modbluetooth.h @@ -0,0 +1,141 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 Ayke van Laethem + * + * 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. + */ + +#pragma once + +#include +#include "bluetooth/bluetooth.h" +#include "py/obj.h" + +// A remote device. +typedef struct { + mp_obj_base_t base; + uint8_t address[6]; + uint16_t conn_handle; +} mp_bt_device_t; + +typedef struct { + mp_obj_base_t base; + mp_bt_uuid_t uuid; + mp_bt_service_handle_t handle; +} mp_bt_service_t; + +// A characteristic. +// Object fits in 4 words (1 GC object), with 1 byte unused at the end. +typedef struct { + mp_obj_base_t base; + mp_bt_uuid_t uuid; + mp_bt_service_t *service; + mp_bt_characteristic_handle_t value_handle; + uint8_t flags; +} mp_bt_characteristic_t; + +// One entry in the linked list of write callbacks. +typedef struct _mp_bt_characteristic_callback_t { + struct _mp_bt_characteristic_callback_t *next; + mp_bt_characteristic_t *characteristic; + mp_obj_t callback; + uint8_t triggers; +} mp_bt_characteristic_callback_t; + +// Enables the Bluetooth stack. Returns errno on failure. +int mp_bt_enable(void); + +// Disables the Bluetooth stack. Is a no-op when not enabled. +void mp_bt_disable(void); + +// Returns true when the Bluetooth stack is enabled. +bool mp_bt_is_enabled(void); + +// Gets the MAC address of this device in LSB format. +void mp_bt_get_address(uint8_t *address); + +// Start advertisement. Will re-start advertisement when already enabled. +// Returns errno on failure. +int mp_bt_advertise_start(mp_bt_adv_type_t type, uint16_t interval, const uint8_t *adv_data, size_t adv_data_len, const uint8_t *sr_data, size_t sr_data_len); + +// Stop advertisement. No-op when already stopped. +void mp_bt_advertise_stop(void); + +// Call this when a central disconnects. +void mp_bt_connected(uint16_t conn_handle, const uint8_t *address); + +// Call this when a central connects. +void mp_bt_disconnected(uint16_t conn_handle, const uint8_t *address); + +// Add a service with the given list of characteristics. +int mp_bt_add_service(mp_bt_service_t *service, size_t num_characteristics, mp_bt_characteristic_t **characteristics); + +// Set the given characteristic to the given value. +int mp_bt_characteristic_value_set(mp_bt_characteristic_t *characteristic, const void *value, size_t value_len); + +// Set the given characteristic and notify a central using the given +// connection handle. +int mp_bt_characteristic_value_notify(mp_bt_characteristic_t *characteristic, uint16_t conn_handle, const void *value, size_t value_len); + +// Read the characteristic value. The size of the buffer must be given in +// value_len, which will be updated with the actual value. +int mp_bt_characteristic_value_get(mp_bt_characteristic_t *characteristic, void *value, size_t *value_len); + +// Call this when a characteristic is written to. +void mp_bt_characteristic_on_write(uint16_t conn_handle, uint16_t value_handle, const void *value, size_t value_len); + +// Disconnect a connected central. +int mp_bt_device_disconnect(uint16_t conn_handle); + +// Parse an UUID object from the caller and stores the result in the uuid +// parameter. Must accept both strings and integers for 128-bit and 16-bit +// UUIDs. +void mp_bt_parse_uuid(mp_obj_t obj, mp_bt_uuid_t *uuid); + +// Format an UUID object to be returned from a .uuid() call. May result in +// a small int or a string. +mp_obj_t mp_bt_format_uuid(mp_bt_uuid_t *uuid); + +// Parse a string UUID object into the 16-byte buffer. The string must be +// the correct size, otherwise this function will throw an error. +void mp_bt_parse_uuid_str(mp_obj_t obj, uint8_t *uuid); + +// Format a 128-bit UUID from the 16-byte buffer as a string. +mp_obj_t mp_bt_format_uuid_str(const uint8_t *uuid); + +// Data types of advertisement packet. +#define MP_BLE_GAP_AD_TYPE_FLAG (0x01) +#define MP_BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME (0x09) + +// Flags element of advertisement packet. +#define MP_BLE_GAP_ADV_FLAG_LE_GENERAL_DISC_MODE (0x02) // discoverable for everyone +#define MP_BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED (0x04) // BLE only - no classic BT supported +#define MP_BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE (MP_BLE_GAP_ADV_FLAG_LE_GENERAL_DISC_MODE | MP_BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED) + +#define MP_BLE_FLAG_READ (1 << 1) +#define MP_BLE_FLAG_WRITE (1 << 3) +#define MP_BLE_FLAG_NOTIFY (1 << 4) + +// IRQ flags to select on which event a callback should be called. +#define MP_BT_IRQ_CONNECT (1 << 1) +#define MP_BT_IRQ_DISCONNECT (1 << 2) +#define MP_BT_IRQ_WRITE (1 << 3) diff --git a/ports/esp32/Makefile b/ports/esp32/Makefile index c6ef04b465225..244805cbcbf09 100644 --- a/ports/esp32/Makefile +++ b/ports/esp32/Makefile @@ -7,6 +7,7 @@ MICROPY_PY_USSL = 0 MICROPY_SSL_AXTLS = 0 MICROPY_FATFS = 1 MICROPY_PY_BTREE = 1 +MICROPY_PY_BLUETOOTH = 1 #FROZEN_DIR = scripts FROZEN_MPY_DIR = modules @@ -115,6 +116,26 @@ INC_ESPCOMP += -I$(ESPCOMP)/app_update/include INC_ESPCOMP += -I$(ESPCOMP)/pthread/include INC_ESPCOMP += -I$(ESPCOMP)/smartconfig_ack/include INC_ESPCOMP += -I$(ESPCOMP)/sdmmc/include +INC_ESPCOMP += -I$(ESPCOMP)/bt/include +INC_ESPCOMP += -I$(ESPCOMP)/bt/bluedroid/api/include/api +INC_ESPCOMP += -I$(ESPCOMP)/bt/bluedroid/bta/include +INC_ESPCOMP += -I$(ESPCOMP)/bt/bluedroid/bta/dm/include +INC_ESPCOMP += -I$(ESPCOMP)/bt/bluedroid/bta/gatt/include +INC_ESPCOMP += -I$(ESPCOMP)/bt/bluedroid/bta/sys/include +INC_ESPCOMP += -I$(ESPCOMP)/bt/bluedroid/btc/include +INC_ESPCOMP += -I$(ESPCOMP)/bt/bluedroid/btc/profile/esp/include +INC_ESPCOMP += -I$(ESPCOMP)/bt/bluedroid/btc/profile/esp/blufi/include +INC_ESPCOMP += -I$(ESPCOMP)/bt/bluedroid/btc/profile/std/include +INC_ESPCOMP += -I$(ESPCOMP)/bt/bluedroid/common/include +INC_ESPCOMP += -I$(ESPCOMP)/bt/bluedroid/device/include +INC_ESPCOMP += -I$(ESPCOMP)/bt/bluedroid/hci/include +INC_ESPCOMP += -I$(ESPCOMP)/bt/bluedroid/osi/include +INC_ESPCOMP += -I$(ESPCOMP)/bt/bluedroid/stack/include +INC_ESPCOMP += -I$(ESPCOMP)/bt/bluedroid/stack/btm/include +INC_ESPCOMP += -I$(ESPCOMP)/bt/bluedroid/stack/gap/include +INC_ESPCOMP += -I$(ESPCOMP)/bt/bluedroid/stack/gatt/include +INC_ESPCOMP += -I$(ESPCOMP)/bt/bluedroid/stack/l2cap/include +INC_ESPCOMP += -I$(ESPCOMP)/bt/bluedroid/stack/smp/include # these flags are common to C and C++ compilation CFLAGS_COMMON = -Os -ffunction-sections -fdata-sections -fstrict-volatile-bitfields \ @@ -186,6 +207,7 @@ SRC_C = \ machine_uart.c \ modmachine.c \ modnetwork.c \ + bluetooth/bluetooth.c \ network_lan.c \ network_ppp.c \ modsocket.c \ @@ -354,6 +376,29 @@ ESPIDF_WPA_SUPPLICANT_O = $(patsubst %.c,%.o,\ ESPIDF_SDMMC_O = $(patsubst %.c,%.o,$(wildcard $(ESPCOMP)/sdmmc/*.c)) +ESPIDF_BT_O = $(patsubst %.c,%.o,\ + $(ESPCOMP)/bt/bt.c \ + $(wildcard $(ESPCOMP)/bt/bluedroid/api/*.c) \ + $(wildcard $(ESPCOMP)/bt/bluedroid/bta/dm/*.c) \ + $(wildcard $(ESPCOMP)/bt/bluedroid/bta/gatt/*.c) \ + $(wildcard $(ESPCOMP)/bt/bluedroid/bta/sys/*.c) \ + $(wildcard $(ESPCOMP)/bt/bluedroid/btc/core/*.c) \ + $(wildcard $(ESPCOMP)/bt/bluedroid/btc/profile/esp/blufi/*.c) \ + $(wildcard $(ESPCOMP)/bt/bluedroid/btc/profile/std/gap/*.c) \ + $(wildcard $(ESPCOMP)/bt/bluedroid/btc/profile/std/gatt/*.c) \ + $(wildcard $(ESPCOMP)/bt/bluedroid/device/*.c) \ + $(wildcard $(ESPCOMP)/bt/bluedroid/hci/*.c) \ + $(wildcard $(ESPCOMP)/bt/bluedroid/main/*.c) \ + $(wildcard $(ESPCOMP)/bt/bluedroid/osi/*.c) \ + $(wildcard $(ESPCOMP)/bt/bluedroid/stack/btm/*.c) \ + $(wildcard $(ESPCOMP)/bt/bluedroid/stack/btu/*.c) \ + $(wildcard $(ESPCOMP)/bt/bluedroid/stack/gap/*.c) \ + $(wildcard $(ESPCOMP)/bt/bluedroid/stack/gatt/*.c) \ + $(wildcard $(ESPCOMP)/bt/bluedroid/stack/hcic/*.c) \ + $(wildcard $(ESPCOMP)/bt/bluedroid/stack/l2cap/*.c) \ + $(wildcard $(ESPCOMP)/bt/bluedroid/stack/smp/*.c) \ + ) + OBJ_ESPIDF = LIB_ESPIDF = BUILD_ESPIDF_LIB = $(BUILD)/esp-idf @@ -392,6 +437,7 @@ $(eval $(call gen_espidf_lib_rule,lwip,$(ESPIDF_LWIP_O))) $(eval $(call gen_espidf_lib_rule,mbedtls,$(ESPIDF_MBEDTLS_O))) $(eval $(call gen_espidf_lib_rule,wpa_supplicant,$(ESPIDF_WPA_SUPPLICANT_O))) $(eval $(call gen_espidf_lib_rule,sdmmc,$(ESPIDF_SDMMC_O))) +$(eval $(call gen_espidf_lib_rule,bt,$(ESPIDF_BT_O))) # Create all destination build dirs before compiling IDF source OBJ_ESPIDF_DIRS = $(sort $(dir $(OBJ_ESPIDF))) $(BUILD_ESPIDF_LIB) $(addprefix $(BUILD_ESPIDF_LIB)/,$(LIB_ESPIDF)) @@ -471,6 +517,7 @@ APP_LD_ARGS += -L$(dir $(LIBGCC_FILE_NAME)) -lgcc APP_LD_ARGS += -L$(dir $(LIBSTDCXX_FILE_NAME)) -lstdc++ APP_LD_ARGS += $(LIBC_LIBM) APP_LD_ARGS += $(ESPCOMP)/esp32/libhal.a +APP_LD_ARGS += $(ESPCOMP)/bt/lib/libbtdm_app.a APP_LD_ARGS += -L$(ESPCOMP)/esp32/lib -lcore -lmesh -lnet80211 -lphy -lrtc -lpp -lwpa -lsmartconfig -lcoexist -lwps -lwpa2 APP_LD_ARGS += $(OBJ) APP_LD_ARGS += $(LIB) diff --git a/ports/esp32/bluetooth/bluetooth.c b/ports/esp32/bluetooth/bluetooth.c new file mode 100644 index 0000000000000..cb661092dcf0f --- /dev/null +++ b/ports/esp32/bluetooth/bluetooth.c @@ -0,0 +1,463 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 Ayke van Laethem + * + * 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. + */ + +#if MICROPY_PY_BLUETOOTH + +#include "esp_bt.h" +#include "esp_bt_main.h" +#include "esp_bt_device.h" +#include "esp_gatts_api.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include + +#include "py/mperrno.h" +#include "py/runtime.h" +#include "extmod/modbluetooth.h" + +// Semaphore to serialze asynchronous calls. +STATIC SemaphoreHandle_t mp_bt_call_complete; +STATIC esp_bt_status_t mp_bt_call_status; +STATIC union { + // Ugly hack to return values from an event handler back to a caller. + esp_gatt_if_t gatts_if; + uint16_t service_handle; + uint16_t attr_handle; +} mp_bt_call_result; + +STATIC mp_bt_adv_type_t bluetooth_adv_type; +STATIC uint16_t bluetooth_adv_interval; +STATIC esp_gatt_if_t bluetooth_gatts_if; + +STATIC void mp_bt_gap_callback(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); +STATIC void mp_bt_gatts_callback(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); + +STATIC const esp_bt_uuid_t notify_descr_uuid = { + .len = ESP_UUID_LEN_16, + .uuid.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG +}; +STATIC const uint8_t descr_value_buf[2]; + +// Convert an esp_err_t into an errno number. +STATIC int mp_bt_esp_errno(esp_err_t err) { + switch (err) { + case 0: + return 0; + case ESP_ERR_NO_MEM: + return MP_ENOMEM; + case ESP_ERR_INVALID_ARG: + return MP_EINVAL; + default: + return MP_EPERM; // fallback + } +} + +// Convert the result of an asynchronous call to an errno value. +STATIC int mp_bt_status_errno(void) { + switch (mp_bt_call_status) { + case ESP_BT_STATUS_SUCCESS: + return 0; + case ESP_BT_STATUS_NOMEM: + return MP_ENOMEM; + case ESP_BT_STATUS_PARM_INVALID: + return MP_EINVAL; + default: + return MP_EPERM; // fallback + } +} + +// Initialize at early boot. +void mp_bt_init(void) { + esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); + mp_bt_call_complete = xSemaphoreCreateBinary(); +} + +int mp_bt_enable(void) { + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + esp_err_t err = esp_bt_controller_init(&bt_cfg); + if (err != 0) { + return mp_bt_esp_errno(err); + } + err = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (err != 0) { + return mp_bt_esp_errno(err); + } + err = esp_bluedroid_init(); + if (err != 0) { + return mp_bt_esp_errno(err); + } + err = esp_bluedroid_enable(); + if (err != 0) { + return mp_bt_esp_errno(err); + } + err = esp_ble_gap_register_callback(mp_bt_gap_callback); + if (err != 0) { + return mp_bt_esp_errno(err); + } + err = esp_ble_gatts_register_callback(mp_bt_gatts_callback); + if (err != 0) { + return mp_bt_esp_errno(err); + } + // Register an application profile. + err = esp_ble_gatts_app_register(0); + if (err != 0) { + return mp_bt_esp_errno(err); + } + // Wait for ESP_GATTS_REG_EVT + xSemaphoreTake(mp_bt_call_complete, portMAX_DELAY); + if (mp_bt_call_status != 0) { + return mp_bt_status_errno(); + } + bluetooth_gatts_if = mp_bt_call_result.gatts_if; + return 0; +} + +void mp_bt_disable(void) { + esp_bluedroid_disable(); + esp_bluedroid_deinit(); + esp_bt_controller_disable(); + esp_bt_controller_deinit(); +} + +bool mp_bt_is_enabled(void) { + return esp_bluedroid_get_status() == ESP_BLUEDROID_STATUS_ENABLED; +} + +void mp_bt_get_address(uint8_t *address) { + const uint8_t *addr = esp_bt_dev_get_address(); + // Convert from MSB to LSB. + for (int i = 5; i >= 0; i--) { + address[i] = addr[5-i]; + } +} + +STATIC esp_err_t mp_bt_advertise_start_internal(void) { + esp_ble_adv_params_t ble_adv_params = {0, + .adv_int_min = bluetooth_adv_interval, + .adv_int_max = bluetooth_adv_interval, + .adv_type = bluetooth_adv_type, + .own_addr_type = BLE_ADDR_TYPE_PUBLIC, + .channel_map = ADV_CHNL_ALL, + .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, + }; + return esp_ble_gap_start_advertising(&ble_adv_params); +} + +int mp_bt_advertise_start(mp_bt_adv_type_t type, uint16_t interval, const uint8_t *adv_data, size_t adv_data_len, const uint8_t *sr_data, size_t sr_data_len) { + if (adv_data != NULL) { + esp_err_t err = esp_ble_gap_config_adv_data_raw((uint8_t*)adv_data, adv_data_len); + if (err != 0) { + return mp_bt_esp_errno(err); + } + // Wait for ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT + xSemaphoreTake(mp_bt_call_complete, portMAX_DELAY); + if (mp_bt_call_status != 0) { + return mp_bt_status_errno(); + } + } + + if (sr_data != NULL) { + esp_err_t err = esp_ble_gap_config_scan_rsp_data_raw((uint8_t*)sr_data, sr_data_len); + if (err != 0) { + return mp_bt_esp_errno(err); + } + // Wait for ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT + xSemaphoreTake(mp_bt_call_complete, portMAX_DELAY); + if (mp_bt_call_status != 0) { + return mp_bt_status_errno(); + } + } + + bluetooth_adv_type = type; + bluetooth_adv_interval = interval; + esp_err_t err = mp_bt_advertise_start_internal(); + if (err != 0) { + return mp_bt_esp_errno(err); + } + // Wait for ESP_GAP_BLE_ADV_START_COMPLETE_EVT + xSemaphoreTake(mp_bt_call_complete, portMAX_DELAY); + return mp_bt_status_errno(); +} + +void mp_bt_advertise_stop(void) { + esp_err_t err = esp_ble_gap_stop_advertising(); + if (err != 0) { + return; + } + // Wait for ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT + xSemaphoreTake(mp_bt_call_complete, portMAX_DELAY); +} + +int mp_bt_add_service(mp_bt_service_t *service, size_t num_characteristics, mp_bt_characteristic_t **characteristics) { + // Calculate the number of required handles. + // This formula is a guess. I can't seem to find any documentation for + // the required number of handles. + uint16_t num_handle = 1 + num_characteristics * 2; + for (size_t i = 0; i < num_characteristics; i++) { + mp_bt_characteristic_t *characteristic = characteristics[i]; + if ((characteristic->flags & MP_BLE_FLAG_NOTIFY) != 0) { + num_handle += 1; + } + } + + // Create the service. + esp_gatt_srvc_id_t bluetooth_service_id; + bluetooth_service_id.is_primary = true; + bluetooth_service_id.id.inst_id = 0; + bluetooth_service_id.id.uuid = service->uuid; + esp_err_t err = esp_ble_gatts_create_service(bluetooth_gatts_if, &bluetooth_service_id, num_handle); + if (err != 0) { + return mp_bt_esp_errno(err); + } + // Wait for ESP_GATTS_CREATE_EVT + xSemaphoreTake(mp_bt_call_complete, portMAX_DELAY); + if (mp_bt_call_status != 0) { + return mp_bt_status_errno(); + } + service->handle = mp_bt_call_result.service_handle; + + // Start the service. + err = esp_ble_gatts_start_service(service->handle); + if (err != 0) { + return mp_bt_esp_errno(err); + } + // Wait for ESP_GATTS_START_EVT + xSemaphoreTake(mp_bt_call_complete, portMAX_DELAY); + if (mp_bt_call_status != 0) { + return mp_bt_status_errno(); + } + + // Add each characteristic. + for (size_t i = 0; i < num_characteristics; i++) { + mp_bt_characteristic_t *characteristic = characteristics[i]; + + esp_gatt_perm_t perm = 0; + perm |= (characteristic->flags & MP_BLE_FLAG_READ) ? ESP_GATT_PERM_READ : 0; + perm |= (characteristic->flags & MP_BLE_FLAG_WRITE) ? ESP_GATT_PERM_WRITE : 0; + + esp_gatt_char_prop_t property = 0; + property |= (characteristic->flags & MP_BLE_FLAG_READ) ? ESP_GATT_CHAR_PROP_BIT_READ : 0; + property |= (characteristic->flags & MP_BLE_FLAG_WRITE) ? ESP_GATT_CHAR_PROP_BIT_WRITE : 0; + property |= (characteristic->flags & MP_BLE_FLAG_NOTIFY) ? ESP_GATT_CHAR_PROP_BIT_NOTIFY : 0; + + esp_attr_value_t char_val = {0}; + char_val.attr_max_len = MP_BT_MAX_ATTR_SIZE; + char_val.attr_len = 0; + char_val.attr_value = NULL; + + esp_attr_control_t control = {0}; + control.auto_rsp = ESP_GATT_AUTO_RSP; + + esp_err_t err = esp_ble_gatts_add_char(service->handle, &characteristic->uuid, perm, property, &char_val, &control); + if (err != 0) { + return mp_bt_esp_errno(err); + } + // Wait for ESP_GATTS_ADD_CHAR_EVT + xSemaphoreTake(mp_bt_call_complete, portMAX_DELAY); + if (mp_bt_call_status != 0) { + return mp_bt_status_errno(); + } + + // Add descriptor if needed. + if ((characteristic->flags & MP_BLE_FLAG_NOTIFY) != 0) { + esp_attr_value_t descr_value = {0}; + descr_value.attr_max_len = 2; + descr_value.attr_len = 2; + descr_value.attr_value = (uint8_t*)descr_value_buf; // looks like this buffer is never written to + esp_err_t err = esp_ble_gatts_add_char_descr(service->handle, (esp_bt_uuid_t*)¬ify_descr_uuid, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, &descr_value, &control); + if (err != 0) { + return mp_bt_esp_errno(err); + } + // Wait for ESP_GATTS_ADD_CHAR_DESCR_EVT + xSemaphoreTake(mp_bt_call_complete, portMAX_DELAY); + if (mp_bt_call_status != 0) { + return mp_bt_status_errno(); + } + } + + // Now that the characteristic has been added successfully to the + // service, update the characteristic's service. + // Note that the caller has already ensured that + // characteristic->service is NULL. + characteristic->service = service; + characteristic->value_handle = mp_bt_call_result.attr_handle; + } + + return 0; +} + +int mp_bt_characteristic_value_set(mp_bt_characteristic_t *characteristic, const void *value, size_t value_len) { + esp_err_t err = esp_ble_gatts_set_attr_value(characteristic->value_handle, value_len, value); + if (err != 0) { + return mp_bt_esp_errno(err); + } + // Wait for ESP_GATTS_SET_ATTR_VAL_EVT + xSemaphoreTake(mp_bt_call_complete, portMAX_DELAY); + return mp_bt_status_errno(); +} + +int mp_bt_characteristic_value_notify(mp_bt_characteristic_t *characteristic, uint16_t conn_handle, const void *value, size_t value_len) { + esp_err_t err = esp_ble_gatts_send_indicate(bluetooth_gatts_if, conn_handle, characteristic->value_handle, value_len, (void*)value, false); + return mp_bt_esp_errno(err); +} + +int mp_bt_characteristic_value_get(mp_bt_characteristic_t *characteristic, void *value, size_t *value_len) { + uint16_t bt_len; + const uint8_t *bt_ptr; + esp_err_t err = esp_ble_gatts_get_attr_value(characteristic->value_handle, &bt_len, &bt_ptr); + if (err != 0) { + return mp_bt_esp_errno(err); + } + if (*value_len > bt_len) { + // Copy up to *value_len bytes. + *value_len = bt_len; + } + memcpy(value, bt_ptr, *value_len); + return 0; +} + +int mp_bt_device_disconnect(uint16_t conn_handle) { + esp_err_t err = esp_ble_gatts_close(bluetooth_gatts_if, conn_handle); + return mp_bt_esp_errno(err); +} + +// Parse a UUID object from the caller. +void mp_bt_parse_uuid(mp_obj_t obj, mp_bt_uuid_t *uuid) { + if (MP_OBJ_IS_SMALL_INT(obj) && MP_OBJ_SMALL_INT_VALUE(obj) == (uint32_t)(uint16_t)MP_OBJ_SMALL_INT_VALUE(obj)) { + // Integer fits inside 16 bits, assume it's a standard UUID. + uuid->len = ESP_UUID_LEN_16; + uuid->uuid.uuid16 = MP_OBJ_SMALL_INT_VALUE(obj); + } else if (mp_obj_is_str(obj)) { + // Guessing this is a 128-bit (proprietary) UUID. + uuid->len = ESP_UUID_LEN_128; + mp_bt_parse_uuid_str(obj, &uuid->uuid.uuid128[0]); + } else { + mp_raise_ValueError("cannot parse UUID"); + } +} + +// Format a UUID object to be returned from a .uuid() call. +mp_obj_t mp_bt_format_uuid(mp_bt_uuid_t *uuid) { + switch (uuid->len) { + case ESP_UUID_LEN_16: + return MP_OBJ_NEW_SMALL_INT(uuid->uuid.uuid16); + case ESP_UUID_LEN_128: + return mp_bt_format_uuid_str(uuid->uuid.uuid128); + default: + return mp_const_none; + } +} + +// Event callbacks. Most API calls generate an event here to report the +// result. +STATIC void mp_bt_gap_callback(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { + switch (event) { + case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: + mp_bt_call_status = param->adv_data_raw_cmpl.status; + xSemaphoreGive(mp_bt_call_complete); + break; + case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT: + mp_bt_call_status = param->scan_rsp_data_raw_cmpl.status; + xSemaphoreGive(mp_bt_call_complete); + break; + case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: + mp_bt_call_status = param->adv_start_cmpl.status; + // May return an error (queue full) when called from + // mp_bt_gatts_callback, but that's OK. + xSemaphoreGive(mp_bt_call_complete); + break; + case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: + xSemaphoreGive(mp_bt_call_complete); + break; + case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT: + break; + default: + ESP_LOGI("bluetooth", "GAP: unknown event: %d", event); + break; + } +} + +STATIC void mp_bt_gatts_callback(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) { + switch (event) { + case ESP_GATTS_CONNECT_EVT: + mp_bt_connected(param->connect.conn_id, param->connect.remote_bda); + break; + case ESP_GATTS_DISCONNECT_EVT: + mp_bt_disconnected(param->disconnect.conn_id, param->disconnect.remote_bda); + // restart advertisement + mp_bt_advertise_start_internal(); + break; + case ESP_GATTS_REG_EVT: + // Application profile created. + mp_bt_call_status = param->reg.status; + mp_bt_call_result.gatts_if = gatts_if; + xSemaphoreGive(mp_bt_call_complete); + break; + case ESP_GATTS_CREATE_EVT: + // Service created. + mp_bt_call_status = param->create.status; + mp_bt_call_result.service_handle = param->create.service_handle; + xSemaphoreGive(mp_bt_call_complete); + break; + case ESP_GATTS_START_EVT: + // Service started. + mp_bt_call_status = param->start.status; + xSemaphoreGive(mp_bt_call_complete); + break; + case ESP_GATTS_ADD_CHAR_EVT: + // Characteristic added. + mp_bt_call_status = param->add_char.status; + mp_bt_call_result.attr_handle = param->add_char.attr_handle; + xSemaphoreGive(mp_bt_call_complete); + break; + case ESP_GATTS_ADD_CHAR_DESCR_EVT: + // Characteristic descriptor added. + mp_bt_call_status = param->add_char_descr.status; + xSemaphoreGive(mp_bt_call_complete); + break; + case ESP_GATTS_SET_ATTR_VAL_EVT: + // Characteristic value set by application. + mp_bt_call_status = param->set_attr_val.status; + xSemaphoreGive(mp_bt_call_complete); + break; + case ESP_GATTS_READ_EVT: + // Characteristic value read by connected device. + break; + case ESP_GATTS_WRITE_EVT: + // Characteristic value written by connected device. + mp_bt_characteristic_on_write(param->write.conn_id, param->write.handle, param->write.value, param->write.len); + break; + case ESP_GATTS_CONF_EVT: + // Characteristic notify confirmation received. + break; + default: + ESP_LOGI("bluetooth", "GATTS: unknown event: %d", event); + break; + } +} + +#endif // MICROPY_PY_BLUETOOTH diff --git a/ports/esp32/bluetooth/bluetooth.h b/ports/esp32/bluetooth/bluetooth.h new file mode 100644 index 0000000000000..43b5063ef78dd --- /dev/null +++ b/ports/esp32/bluetooth/bluetooth.h @@ -0,0 +1,48 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 Ayke van Laethem + * + * 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. + */ + +#pragma once + +#include "esp_gap_ble_api.h" + +typedef esp_ble_adv_type_t mp_bt_adv_type_t; + +#define MP_BT_ADV_TYPE_ADV_IND ADV_TYPE_IND +#define MP_BT_ADV_TYPE_ADV_NONCONN_IND ADV_TYPE_NONCONN_IND + +#define MP_BT_MAX_ATTR_SIZE (20) + +#define MP_BT_MAX_CONNECTED_DEVICES CONFIG_BT_ACL_CONNECTIONS + +#define MP_BT_INVALID_CONN_HANDLE (0xffff) + +void mp_bt_init(void); + +typedef esp_bt_uuid_t mp_bt_uuid_t; + +typedef uint16_t mp_bt_service_handle_t; + +typedef uint16_t mp_bt_characteristic_handle_t; diff --git a/ports/esp32/boards/sdkconfig b/ports/esp32/boards/sdkconfig index 7bd731a66b6c5..61268cff8ea53 100644 --- a/ports/esp32/boards/sdkconfig +++ b/ports/esp32/boards/sdkconfig @@ -28,3 +28,9 @@ CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK=y CONFIG_PPP_SUPPORT=y CONFIG_PPP_PAP_SUPPORT=y CONFIG_PPP_CHAP_SUPPORT=y + +# Bluetooth +CONFIG_BT_ENABLED=y +CONFIG_BLUEDROID_PINNED_TO_CORE=0 +CONFIG_BT_ACL_CONNECTIONS=4 +CONFIG_GATTS_ENABLE=y diff --git a/ports/esp32/main.c b/ports/esp32/main.c index c8dde337c5d95..38bb1ac0e9b25 100644 --- a/ports/esp32/main.c +++ b/ports/esp32/main.c @@ -52,6 +52,7 @@ #include "modmachine.h" #include "modnetwork.h" #include "mpthreadport.h" +#include "bluetooth/bluetooth.h" // MicroPython runs as a task under FreeRTOS #define MP_TASK_PRIORITY (ESP_TASK_PRIO_MIN + 1) @@ -151,6 +152,7 @@ void mp_task(void *pvParameter) { void app_main(void) { nvs_flash_init(); + mp_bt_init(); xTaskCreatePinnedToCore(mp_task, "mp_task", MP_TASK_STACK_LEN, NULL, MP_TASK_PRIORITY, &mp_main_task_handle, MP_TASK_COREID); } diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index 9ffe380fca063..1f88267ba9170 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -179,6 +179,7 @@ extern const struct _mp_obj_module_t uos_module; extern const struct _mp_obj_module_t mp_module_usocket; extern const struct _mp_obj_module_t mp_module_machine; extern const struct _mp_obj_module_t mp_module_network; +extern const struct _mp_obj_module_t mp_module_bluetooth; extern const struct _mp_obj_module_t mp_module_onewire; #define MICROPY_PORT_BUILTIN_MODULES \ @@ -189,6 +190,7 @@ extern const struct _mp_obj_module_t mp_module_onewire; { MP_OBJ_NEW_QSTR(MP_QSTR_usocket), (mp_obj_t)&mp_module_usocket }, \ { MP_OBJ_NEW_QSTR(MP_QSTR_machine), (mp_obj_t)&mp_module_machine }, \ { MP_OBJ_NEW_QSTR(MP_QSTR_network), (mp_obj_t)&mp_module_network }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_bluetooth), (mp_obj_t)&mp_module_bluetooth }, \ { MP_OBJ_NEW_QSTR(MP_QSTR__onewire), (mp_obj_t)&mp_module_onewire }, \ { MP_OBJ_NEW_QSTR(MP_QSTR_uhashlib), (mp_obj_t)&mp_module_uhashlib }, \ diff --git a/ports/nrf/Makefile b/ports/nrf/Makefile index cc7b4f1260b77..3709cfb6c1429 100644 --- a/ports/nrf/Makefile +++ b/ports/nrf/Makefile @@ -38,14 +38,17 @@ endif # qstr definitions (must come before including py.mk) QSTR_DEFS = qstrdefsport.h $(BUILD)/pins_qstr.h -# include py core make definitions -include ../../py/py.mk - +ifneq ($(SD), ) +MICROPY_PY_BLUETOOTH = 1 +endif MICROPY_FATFS ?= 0 FATFS_DIR = lib/oofatfs MPY_CROSS = ../../mpy-cross/mpy-cross MPY_TOOL = ../../tools/mpy-tool.py +# include py core make definitions +include ../../py/py.mk + CROSS_COMPILE = arm-none-eabi- INC += -I. @@ -197,8 +200,7 @@ SRC_C += \ drivers/flash.c \ drivers/softpwm.c \ drivers/ticker.c \ - drivers/bluetooth/ble_drv.c \ - drivers/bluetooth/ble_uart.c \ + bluetooth/bluetooth.c \ DRIVERS_SRC_C += $(addprefix modules/,\ machine/modmachine.c \ @@ -216,19 +218,8 @@ DRIVERS_SRC_C += $(addprefix modules/,\ utime/modutime.c \ board/modboard.c \ board/led.c \ - ubluepy/modubluepy.c \ - ubluepy/ubluepy_peripheral.c \ - ubluepy/ubluepy_service.c \ - ubluepy/ubluepy_characteristic.c \ - ubluepy/ubluepy_uuid.c \ - ubluepy/ubluepy_delegate.c \ - ubluepy/ubluepy_constants.c \ - ubluepy/ubluepy_descriptor.c \ - ubluepy/ubluepy_scanner.c \ - ubluepy/ubluepy_scan_entry.c \ music/modmusic.c \ music/musictunes.c \ - ble/modble.c \ random/modrandom.c \ ) diff --git a/ports/nrf/bluetooth/bluetooth.c b/ports/nrf/bluetooth/bluetooth.c new file mode 100644 index 0000000000000..ce958417a712b --- /dev/null +++ b/ports/nrf/bluetooth/bluetooth.c @@ -0,0 +1,488 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016 - 2018 Glenn Ruben Bakke + * Copyright (c) 2018 Ayke van Laethem + * + * 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. + */ + +#if MICROPY_PY_BLUETOOTH + +#include +#include + +#include "py/runtime.h" +#include "py/mperrno.h" +#include "extmod/modbluetooth.h" +#include "drivers/flash.h" + +#include "nrf_sdm.h" +#include "ble.h" +#include "ble_hci.h" +#if !NRF51 +#include "nrf_nvic.h" +#endif + +#define MSEC_TO_UNITS(TIME, RESOLUTION) (((TIME) * 1000) / (RESOLUTION)) +#define UNIT_0_625_MS (625) +#define UNIT_10_MS (10000) + +#define BLE_MIN_CONN_INTERVAL MSEC_TO_UNITS(12, UNIT_0_625_MS) +#define BLE_MAX_CONN_INTERVAL MSEC_TO_UNITS(12, UNIT_0_625_MS) +#define BLE_SLAVE_LATENCY 0 +#define BLE_CONN_SUP_TIMEOUT MSEC_TO_UNITS(4000, UNIT_10_MS) + +#if NRF51 + #define MAX_TX_IN_PROGRESS (6) +#else + #define MAX_TX_IN_PROGRESS (10) +#endif +#if !defined(GATT_MTU_SIZE_DEFAULT) && defined(BLE_GATT_ATT_MTU_DEFAULT) + #define GATT_MTU_SIZE_DEFAULT BLE_GATT_ATT_MTU_DEFAULT +#endif + +#if NRF51 +STATIC mp_bt_adv_type_t bluetooth_adv_type; +STATIC uint16_t bluetooth_adv_interval; +#else +#include "nrf_nvic.h" +nrf_nvic_state_t nrf_nvic_state = {0}; +static uint8_t bluetooth_adv_handle; +static uint8_t bluetooth_adv_data[BLE_GAP_ADV_SET_DATA_SIZE_MAX]; +static uint8_t bluetooth_sr_data[BLE_GAP_ADV_SET_DATA_SIZE_MAX]; +#endif + +#if NRF51 +void softdevice_assert_handler(uint32_t pc, uint16_t line_number, const uint8_t * p_file_name) { + printf("ERROR: SoftDevice assert!!!\n"); +} +#else +void softdevice_assert_handler(uint32_t id, uint32_t pc, uint32_t info) { + printf("ERROR: SoftDevice assert!!!\n"); +} +#endif + +#if !NRF51 +#if BLUETOOTH_LFCLK_RC +STATIC const nrf_clock_lf_cfg_t clock_config = { + .source = NRF_CLOCK_LF_SRC_RC, + .rc_ctiv = 16, + .rc_temp_ctiv = 2, + .accuracy = NRF_CLOCK_LF_ACCURACY_250_PPM +}; +#else +STATIC const nrf_clock_lf_cfg_t clock_config = { + .source = NRF_CLOCK_LF_SRC_XTAL, + .rc_ctiv = 0, + .rc_temp_ctiv = 0, + .accuracy = NRF_CLOCK_LF_ACCURACY_20_PPM +}; +#endif // BLUETOOTH_LFCLK_RC +#endif // !NRF51 + +STATIC const uint8_t ble_default_device_name[] = "MPY"; + +// Connection params for sd_ble_gap_ppcp_set. +STATIC const ble_gap_conn_params_t gap_conn_params = { + .min_conn_interval = BLE_MIN_CONN_INTERVAL, + .max_conn_interval = BLE_MAX_CONN_INTERVAL, + .slave_latency = BLE_SLAVE_LATENCY, + .conn_sup_timeout = BLE_CONN_SUP_TIMEOUT, +}; + +STATIC int mp_bt_errno(uint32_t err_code) { + switch (err_code) { + case 0: + return 0; // no error + case NRF_ERROR_INVALID_PARAM: + case NRF_ERROR_INVALID_LENGTH: + return MP_EINVAL; + case NRF_ERROR_NO_MEM: + #if NRF51 + case BLE_ERROR_NO_TX_BUFFERS: + #else + case NRF_ERROR_RESOURCES: + #endif + return MP_ENOMEM; + case NRF_ERROR_INVALID_ADDR: + return MP_EFAULT; // bad address + case NRF_ERROR_NOT_FOUND: + return MP_ENOENT; + case NRF_ERROR_DATA_SIZE: + return MP_E2BIG; + case NRF_ERROR_FORBIDDEN: + return MP_EACCES; + default: + return MP_EPERM; // catch-all + } +} + +int mp_bt_enable(void) { + if (mp_bt_is_enabled()) { + return 0; + } + + // initialize our state +#if !NRF51 + bluetooth_adv_handle = BLE_GAP_ADV_SET_HANDLE_NOT_SET; +#endif + +#if NRF51 + #if BLUETOOTH_LFCLK_RC + uint32_t err_code = sd_softdevice_enable(NRF_CLOCK_LFCLKSRC_RC_250_PPM_250MS_CALIBRATION, + softdevice_assert_handler); + #else + uint32_t err_code = sd_softdevice_enable(NRF_CLOCK_LFCLKSRC_XTAL_20_PPM, + softdevice_assert_handler); + #endif // BLUETOOTH_LFCLK_RC + +#else // NRF52xxx + uint32_t err_code = sd_softdevice_enable(&clock_config, + softdevice_assert_handler); +#endif + + if (err_code != 0) { // sd_softdevice_enable + return mp_bt_errno(err_code); + } + + sd_nvic_EnableIRQ(SD_EVT_IRQn); + +#if NRF51 + ble_enable_params_t ble_enable_params = {0}; + ble_enable_params.gatts_enable_params.attr_tab_size = BLE_GATTS_ATTR_TAB_SIZE_DEFAULT; + ble_enable_params.gatts_enable_params.service_changed = 0; + err_code = sd_ble_enable(&ble_enable_params); +#else + uint32_t app_ram_start_cfg = 0x200039c0; + err_code = sd_ble_enable(&app_ram_start_cfg); // 8K SD headroom from linker script. +#endif + if (err_code != 0) { // sd_ble_enable + return mp_bt_errno(err_code); + } + + // set up security mode + ble_gap_conn_sec_mode_t sec_mode; + BLE_GAP_CONN_SEC_MODE_SET_OPEN(&sec_mode); + + if ((err_code = sd_ble_gap_device_name_set(&sec_mode, + ble_default_device_name, + sizeof(ble_default_device_name) - 1)) != 0) { + return mp_bt_errno(err_code); + } + + if ((err_code = sd_ble_gap_ppcp_set(&gap_conn_params)) != 0) { + return mp_bt_errno(err_code); + } + + return 0; // success +} + +void mp_bt_disable(void) { + sd_softdevice_disable(); +} + +bool mp_bt_is_enabled(void) { + uint8_t is_enabled; + sd_softdevice_is_enabled(&is_enabled); + return is_enabled != 0; +} + +void mp_bt_get_address(uint8_t *address) { + ble_gap_addr_t addr; + #if NRF51 + sd_ble_gap_address_get(&addr); + #else + sd_ble_gap_addr_get(&addr); + #endif + memcpy(address, &addr.addr, 6); +} + +#if NRF51 +STATIC uint32_t mp_bt_advertise_start_internal(void) { + ble_gap_adv_params_t adv_params; + adv_params.type = bluetooth_adv_type; + adv_params.p_peer_addr = NULL; + adv_params.fp = BLE_GAP_ADV_FP_ANY; // no filter policy + adv_params.interval = bluetooth_adv_interval; + return sd_ble_gap_adv_start(&adv_params); +} +#endif + +int mp_bt_advertise_start(mp_bt_adv_type_t type, uint16_t interval, const uint8_t *adv_data, size_t adv_data_len, const uint8_t *sr_data, size_t sr_data_len) { + mp_bt_advertise_stop(); // restart if already started + +#if NRF51 + sd_ble_gap_adv_data_set(adv_data, adv_data_len, sr_data, sr_data_len); + bluetooth_adv_type = type; + bluetooth_adv_interval = interval; + uint32_t err_code = mp_bt_advertise_start_internal(); + return mp_bt_errno(err_code); + +#elif NRF52 + if (adv_data_len > sizeof(bluetooth_adv_data) || sr_data_len > sizeof(bluetooth_sr_data)) { + return MP_EINVAL; + } + if (adv_data_len) { + memcpy(bluetooth_adv_data, adv_data, adv_data_len); + } + if (sr_data_len) { + memcpy(bluetooth_sr_data, sr_data, sr_data_len); + } + + ble_gap_adv_data_t ble_adv_data = {0}; + ble_adv_data.adv_data.p_data = bluetooth_adv_data; + ble_adv_data.adv_data.len = adv_data_len; + ble_adv_data.scan_rsp_data.p_data = bluetooth_sr_data; + ble_adv_data.scan_rsp_data.len = sr_data_len; + + ble_gap_adv_params_t adv_params = {0}; + adv_params.properties.type = type; + adv_params.filter_policy = BLE_GAP_ADV_FP_ANY; // no filter policy + adv_params.interval = interval; + adv_params.max_adv_evts = 0; // infinite advertisment + adv_params.primary_phy = BLE_GAP_PHY_AUTO; + adv_params.secondary_phy = BLE_GAP_PHY_AUTO; + adv_params.scan_req_notification = 0; // Do not raise scan request notifications when scanned. + + uint32_t err_code = sd_ble_gap_adv_set_configure(&bluetooth_adv_handle, &ble_adv_data, &adv_params); + if (err_code != 0) { + return mp_bt_errno(err_code); + } + + err_code = sd_ble_gap_adv_start(bluetooth_adv_handle, BLE_CONN_CFG_TAG_DEFAULT); + return mp_bt_errno(err_code); +#endif +} + +void mp_bt_advertise_stop(void) { +#if NRF51 + sd_ble_gap_adv_stop(); +#else + sd_ble_gap_adv_stop(bluetooth_adv_handle); +#endif +} + +static void ble_evt_handler(ble_evt_t * p_ble_evt) { + switch (p_ble_evt->header.evt_id) { + case BLE_GAP_EVT_CONNECTED: + mp_bt_connected(p_ble_evt->evt.gap_evt.conn_handle, p_ble_evt->evt.gap_evt.params.connected.peer_addr.addr); + break; + case BLE_GAP_EVT_DISCONNECTED: + mp_bt_disconnected(p_ble_evt->evt.gap_evt.conn_handle, NULL); +#if NRF51 + mp_bt_advertise_start_internal(); +#else + sd_ble_gap_adv_start(bluetooth_adv_handle, BLE_CONN_CFG_TAG_DEFAULT); +#endif + break; + +#if NRF52 + case BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST: + sd_ble_gatts_exchange_mtu_reply(p_ble_evt->evt.gatts_evt.conn_handle, GATT_MTU_SIZE_DEFAULT); + break; +#endif + case BLE_GATTS_EVT_WRITE: + mp_bt_characteristic_on_write(p_ble_evt->evt.gatts_evt.conn_handle, p_ble_evt->evt.gatts_evt.params.write.handle, &p_ble_evt->evt.gatts_evt.params.write.data, p_ble_evt->evt.gatts_evt.params.write.len); + break; + } +} + +int mp_bt_add_service(mp_bt_service_t *service, size_t num_characteristics, mp_bt_characteristic_t **characteristics) { + uint32_t err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, &service->uuid, &service->handle); + if (err_code != 0) { + return mp_bt_errno(err_code); + } + + // Add each characteristic. + for (size_t i = 0; i < num_characteristics; i++) { + mp_bt_characteristic_t *characteristic = characteristics[i]; + + // Create characteristic metadata. + ble_gatts_char_md_t char_md = {0}; + char_md.char_props.read = (characteristic->flags & MP_BLE_FLAG_READ) ? 1 : 0; + char_md.char_props.write = (characteristic->flags & MP_BLE_FLAG_WRITE) ? 1 : 0; + char_md.char_props.notify = (characteristic->flags & MP_BLE_FLAG_NOTIFY) ? 1 : 0; + + // Create attribute metadata. + ble_gatts_attr_md_t attr_md = {0}; + attr_md.vlen = 1; + attr_md.vloc = BLE_GATTS_VLOC_STACK; + attr_md.rd_auth = 0; + attr_md.wr_auth = 0; + if (characteristic->flags & MP_BLE_FLAG_READ) { + BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.read_perm); + } + if (characteristic->flags & MP_BLE_FLAG_WRITE) { + BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.write_perm); + } + + // Create characteristic value. + ble_gatts_attr_t attr_char_value = {0}; + attr_char_value.p_uuid = &characteristic->uuid; + attr_char_value.p_attr_md = &attr_md; + attr_char_value.init_len = 0; + attr_char_value.init_offs = 0; + attr_char_value.max_len = MP_BT_MAX_ATTR_SIZE; + attr_char_value.p_value = NULL; + + // Output handles. + ble_gatts_char_handles_t handles; + + // BLE_GATT_HANDLE_INVALID: add to previously added service. + uint32_t err_code = sd_ble_gatts_characteristic_add(BLE_GATT_HANDLE_INVALID, &char_md, &attr_char_value, &handles); + if (err_code != 0) { + return mp_bt_errno(err_code); + } + + // Now that the characteristic has been added successfully to the + // service, update the characteristic's service. + // Note that the caller has already ensured that + // characteristic->service is NULL. + characteristic->service = service; + characteristic->value_handle = handles.value_handle; + } + + return 0; +} + +int mp_bt_characteristic_value_set(mp_bt_characteristic_t *characteristic, const void *value, size_t value_len) { + ble_gatts_value_t data = {0}; + data.len = value_len; + data.offset = 0; + data.p_value = (void*)value; // value is only read so we can discard const + uint32_t err_code = sd_ble_gatts_value_set(BLE_CONN_HANDLE_INVALID, characteristic->value_handle, &data); + return mp_bt_errno(err_code); +} + +int mp_bt_characteristic_value_notify(mp_bt_characteristic_t *characteristic, uint16_t conn_handle, const void *value, size_t value_len) { + uint16_t len = value_len; + ble_gatts_hvx_params_t hvx_params = {0}; + hvx_params.handle = characteristic->value_handle; + hvx_params.type = BLE_GATT_HVX_NOTIFICATION; + hvx_params.p_len = &len; + hvx_params.p_data = (void*)value; + uint32_t err_code = sd_ble_gatts_hvx(conn_handle, &hvx_params); + if (err_code == BLE_ERROR_GATTS_SYS_ATTR_MISSING) { + // may happen when not subscribed + err_code = 0; + } + return mp_bt_errno(err_code); +} + +int mp_bt_characteristic_value_get(mp_bt_characteristic_t *characteristic, void *value, size_t *value_len) { + ble_gatts_value_t data = {0}; + data.len = *value_len; + data.offset = 0; + data.p_value = value; + uint32_t err_code = sd_ble_gatts_value_get(BLE_CONN_HANDLE_INVALID, characteristic->value_handle, &data); + *value_len = data.len; + return mp_bt_errno(err_code); +} + +int mp_bt_device_disconnect(uint16_t conn_handle) { + uint32_t err_code = sd_ble_gap_disconnect(conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION); + return mp_bt_errno(err_code); +} + +// Parse a UUID object from the caller. +void mp_bt_parse_uuid(mp_obj_t obj, mp_bt_uuid_t *uuid) { + if (MP_OBJ_IS_SMALL_INT(obj) && MP_OBJ_SMALL_INT_VALUE(obj) == (uint32_t)(uint16_t)MP_OBJ_SMALL_INT_VALUE(obj)) { + // Integer fits inside 16 bits. + uuid->type = BLE_UUID_TYPE_BLE; + uuid->uuid = MP_OBJ_SMALL_INT_VALUE(obj); + } else if (mp_obj_is_str(obj)) { + // Guessing this is a 128-bit (proprietary) UUID. + ble_uuid128_t buf; + mp_bt_parse_uuid_str(obj, &buf.uuid128[0]); + uint32_t err_code = sd_ble_uuid_vs_add(&buf, &uuid->type); + if (err_code != 0) { + mp_raise_OSError(mp_bt_errno(err_code)); + } + uuid->uuid = (uint16_t)(buf.uuid128[12]) | ((uint16_t)(buf.uuid128[13]) << 8); + } else { + mp_raise_ValueError("cannot parse UUID"); + } +} + +mp_obj_t mp_bt_format_uuid(mp_bt_uuid_t *uuid) { + uint8_t raw[16]; + uint8_t raw_len; + if (sd_ble_uuid_encode(uuid, &raw_len, raw) != 0) { + return mp_const_none; + } + switch (raw_len) { + case 2: + return MP_OBJ_NEW_SMALL_INT((int)(raw[0]) | ((int)(raw[1]) << 8)); + case 16: + return mp_bt_format_uuid_str(raw); + default: + return mp_const_none; + } +} + +static void sd_evt_handler(uint32_t evt_id) { + switch (evt_id) { +#if MICROPY_MBFS + case NRF_EVT_FLASH_OPERATION_SUCCESS: + flash_operation_finished(FLASH_STATE_SUCCESS); + break; + case NRF_EVT_FLASH_OPERATION_ERROR: + flash_operation_finished(FLASH_STATE_ERROR); + break; +#endif + default: + // unhandled event! + break; + } +} + +static uint8_t m_ble_evt_buf[sizeof(ble_evt_t) + (GATT_MTU_SIZE_DEFAULT)] __attribute__ ((aligned (4))); + +#ifdef NRF51 +void SWI2_IRQHandler(void) { +#else +void SWI2_EGU2_IRQHandler(void) { +#endif + + uint32_t evt_id; + while (sd_evt_get(&evt_id) != NRF_ERROR_NOT_FOUND) { + sd_evt_handler(evt_id); + } + + while (1) { + uint16_t evt_len = sizeof(m_ble_evt_buf); + uint32_t err_code = sd_ble_evt_get(m_ble_evt_buf, &evt_len); + if (err_code != NRF_SUCCESS) { + // Possible error conditions: + // * NRF_ERROR_NOT_FOUND: no events left, break + // * NRF_ERROR_DATA_SIZE: retry with a bigger data buffer + // (currently not handled, TODO) + // * NRF_ERROR_INVALID_ADDR: pointer is not aligned, should + // not happen. + // In all cases, it's best to simply stop now. + break; + } + ble_evt_handler((ble_evt_t *)m_ble_evt_buf); + } +} + +#endif // MICROPY_PY_BLUETOOTH diff --git a/ports/nrf/bluetooth/bluetooth.h b/ports/nrf/bluetooth/bluetooth.h new file mode 100644 index 0000000000000..177a581dd84fa --- /dev/null +++ b/ports/nrf/bluetooth/bluetooth.h @@ -0,0 +1,59 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 Ayke van Laethem + * + * 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. + */ + +#pragma once + +#if MICROPY_PY_BLUETOOTH + +#include + +#include "ble_gap.h" +#include "ble_gatt.h" + +typedef uint8_t mp_bt_adv_type_t; + +#if NRF51 +#define MP_BT_ADV_TYPE_ADV_IND BLE_GAP_ADV_TYPE_ADV_IND +#define MP_BT_ADV_TYPE_ADV_DIRECT_IND BLE_GAP_ADV_TYPE_ADV_DIRECT_IND +#define MP_BT_ADV_TYPE_ADV_SCAN_IND BLE_GAP_ADV_TYPE_ADV_SCAN_IND +#define MP_BT_ADV_TYPE_ADV_NONCONN_IND BLE_GAP_ADV_TYPE_ADV_NONCONN_IND +#define MP_BT_MAX_CONNECTED_DEVICES 1 +#else +#define MP_BT_ADV_TYPE_ADV_IND BLE_GAP_ADV_TYPE_CONNECTABLE_SCANNABLE_UNDIRECTED +#define MP_BT_ADV_TYPE_ADV_NONCONN_IND BLE_GAP_ADV_TYPE_NONCONNECTABLE_SCANNABLE_UNDIRECTED +#define MP_BT_MAX_CONNECTED_DEVICES BLE_GAP_ROLE_COUNT_PERIPH_DEFAULT +#endif + +#define MP_BT_MAX_ATTR_SIZE (20) +#define MP_BT_INVALID_CONN_HANDLE (BLE_CONN_HANDLE_INVALID) + +typedef ble_uuid_t mp_bt_uuid_t; + +typedef uint16_t mp_bt_service_handle_t; + +typedef uint16_t mp_bt_characteristic_handle_t; + +#endif // MICROPY_PY_BLUETOOTH diff --git a/ports/nrf/drivers/flash.c b/ports/nrf/drivers/flash.c index 5a7256a0c6227..4e032227bc7ce 100644 --- a/ports/nrf/drivers/flash.c +++ b/ports/nrf/drivers/flash.c @@ -29,7 +29,7 @@ #if MICROPY_MBFS && BLUETOOTH_SD #include "drivers/flash.h" -#include "drivers/bluetooth/ble_drv.h" +#include "extmod/modbluetooth.h" #include "nrf_soc.h" // Rotates bits in `value` left `shift` times. @@ -48,7 +48,7 @@ void flash_operation_finished(flash_state_t result) { } STATIC bool operation_wait(uint32_t result) { - if (ble_drv_stack_enabled() != 1) { + if (!mp_bt_is_enabled()) { // SoftDevice is not enabled, no event will be generated. return result == NRF_SUCCESS; } diff --git a/ports/nrf/modules/machine/temp.c b/ports/nrf/modules/machine/temp.c index 361d988857890..7e2eaa5a574a4 100644 --- a/ports/nrf/modules/machine/temp.c +++ b/ports/nrf/modules/machine/temp.c @@ -27,17 +27,15 @@ #include #include -#include "py/nlr.h" #include "py/runtime.h" #include "py/mphal.h" #include "temp.h" #include "nrf_temp.h" #if BLUETOOTH_SD -#include "py/nlr.h" -#include "ble_drv.h" +#include "extmod/modbluetooth.h" #include "nrf_soc.h" -#define BLUETOOTH_STACK_ENABLED() (ble_drv_stack_enabled()) +#define BLUETOOTH_STACK_ENABLED() (mp_bt_is_enabled()) #endif // BLUETOOTH_SD #if MICROPY_PY_MACHINE_TEMP diff --git a/ports/nrf/modules/random/modrandom.c b/ports/nrf/modules/random/modrandom.c index f67bffb27786a..1a45862200db8 100644 --- a/ports/nrf/modules/random/modrandom.c +++ b/ports/nrf/modules/random/modrandom.c @@ -36,9 +36,9 @@ #include "modrandom.h" #if BLUETOOTH_SD +#include "extmod/modbluetooth.h" #include "nrf_soc.h" -#include "ble_drv.h" -#define BLUETOOTH_STACK_ENABLED() (ble_drv_stack_enabled()) +#define BLUETOOTH_STACK_ENABLED() (mp_bt_is_enabled()) #endif static inline uint32_t generate_hw_random(void) { diff --git a/ports/nrf/mpconfigport.h b/ports/nrf/mpconfigport.h index da9a03e1d03b5..d2cfb7c5b72a3 100644 --- a/ports/nrf/mpconfigport.h +++ b/ports/nrf/mpconfigport.h @@ -117,7 +117,7 @@ #define MICROPY_PY_IO (0) #define MICROPY_PY_IO_FILEIO (0) #define MICROPY_PY_UERRNO (0) -#define MICROPY_PY_UBINASCII (0) +#define MICROPY_PY_UBINASCII (1) #define MICROPY_PY_URANDOM (0) #define MICROPY_PY_URANDOM_EXTRA_FUNCS (0) #define MICROPY_PY_UCTYPES (0) @@ -187,10 +187,6 @@ #include "bluetooth_conf.h" #endif -#ifndef MICROPY_PY_UBLUEPY -#define MICROPY_PY_UBLUEPY (0) -#endif - #ifndef MICROPY_PY_BLE_NUS #define MICROPY_PY_BLE_NUS (0) #endif @@ -216,16 +212,10 @@ extern const struct _mp_obj_module_t board_module; extern const struct _mp_obj_module_t machine_module; extern const struct _mp_obj_module_t mp_module_utime; extern const struct _mp_obj_module_t mp_module_uos; -extern const struct _mp_obj_module_t mp_module_ubluepy; +extern const struct _mp_obj_module_t mp_module_bluetooth; extern const struct _mp_obj_module_t music_module; extern const struct _mp_obj_module_t random_module; -#if MICROPY_PY_UBLUEPY -#define UBLUEPY_MODULE { MP_ROM_QSTR(MP_QSTR_ubluepy), MP_ROM_PTR(&mp_module_ubluepy) }, -#else -#define UBLUEPY_MODULE -#endif - #if MICROPY_PY_MUSIC #define MUSIC_MODULE { MP_ROM_QSTR(MP_QSTR_music), MP_ROM_PTR(&music_module) }, #else @@ -247,28 +237,19 @@ extern const struct _mp_obj_module_t random_module; #if BLUETOOTH_SD -#if MICROPY_PY_BLE -extern const struct _mp_obj_module_t ble_module; -#define BLE_MODULE { MP_ROM_QSTR(MP_QSTR_ble), MP_ROM_PTR(&ble_module) }, -#else -#define BLE_MODULE -#endif - #define MICROPY_PORT_BUILTIN_MODULES \ { MP_ROM_QSTR(MP_QSTR_board), MP_ROM_PTR(&board_module) }, \ { MP_ROM_QSTR(MP_QSTR_machine), MP_ROM_PTR(&machine_module) }, \ { MP_ROM_QSTR(MP_QSTR_utime), MP_ROM_PTR(&mp_module_utime) }, \ { MP_ROM_QSTR(MP_QSTR_time), MP_ROM_PTR(&mp_module_utime) }, \ { MP_ROM_QSTR(MP_QSTR_uos), MP_ROM_PTR(&mp_module_uos) }, \ - BLE_MODULE \ + { MP_ROM_QSTR(MP_QSTR_bluetooth), MP_ROM_PTR(&mp_module_bluetooth) }, \ MUSIC_MODULE \ - UBLUEPY_MODULE \ RANDOM_MODULE \ MICROPY_BOARD_BUILTINS \ #else -extern const struct _mp_obj_module_t ble_module; #define MICROPY_PORT_BUILTIN_MODULES \ { MP_ROM_QSTR(MP_QSTR_board), MP_ROM_PTR(&board_module) }, \ { MP_ROM_QSTR(MP_QSTR_machine), MP_ROM_PTR(&machine_module) }, \ @@ -294,7 +275,6 @@ extern const struct _mp_obj_module_t ble_module; #define MICROPY_PORT_CONSTANTS \ { MP_ROM_QSTR(MP_QSTR_board), MP_ROM_PTR(&board_module) }, \ { MP_ROM_QSTR(MP_QSTR_machine), MP_ROM_PTR(&machine_module) }, \ - BLE_MODULE \ #define MP_STATE_PORT MP_STATE_VM diff --git a/ports/nrf/nrfx_glue.h b/ports/nrf/nrfx_glue.h index 316e02df1d54c..36d336f979d34 100644 --- a/ports/nrf/nrfx_glue.h +++ b/ports/nrf/nrfx_glue.h @@ -36,18 +36,17 @@ #if BLUETOOTH_SD +#include "extmod/modbluetooth.h" #if NRF51 #include "nrf_soc.h" #else #include "nrf_nvic.h" #endif -#include "ble_drv.h" - #if (BLUETOOTH_SD == 110) #define NRFX_IRQ_ENABLE(irq_number) \ do { \ - if (ble_drv_stack_enabled() == 1) \ + if (mp_bt_is_enabled()) \ { \ sd_nvic_EnableIRQ(irq_number); \ } else { \ @@ -61,7 +60,7 @@ #if (BLUETOOTH_SD == 110) #define NRFX_IRQ_DISABLE(irq_number) \ do { \ - if (ble_drv_stack_enabled() == 1) \ + if (mp_bt_is_enabled()) \ { \ sd_nvic_DisableIRQ(irq_number); \ } else { \ @@ -75,7 +74,7 @@ #if (BLUETOOTH_SD == 110) #define NRFX_IRQ_PRIORITY_SET(irq_number, priority) \ do { \ - if (ble_drv_stack_enabled() == 1) \ + if (mp_bt_is_enabled()) \ { \ sd_nvic_SetPriority(irq_number, priority); \ } else { \ @@ -89,7 +88,7 @@ #if (BLUETOOTH_SD == 110) #define NRFX_IRQ_PENDING_SET(irq_number) \ do { \ - if (ble_drv_stack_enabled() == 1) \ + if (mp_bt_is_enabled()) \ { \ sd_nvic_SetPendingIRQ(irq_number); \ } else { \ @@ -103,7 +102,7 @@ #if (BLUETOOTH_SD == 110) #define NRFX_IRQ_PENDING_CLEAR(irq_number) \ do { \ - if (ble_drv_stack_enabled() == 1) \ + if (mp_bt_is_enabled()) \ { \ sd_nvic_ClearPendingIRQ(irq_number); \ } else { \ diff --git a/py/mpstate.h b/py/mpstate.h index b7eb6bdeb1505..bfc7b0632df8b 100644 --- a/py/mpstate.h +++ b/py/mpstate.h @@ -36,6 +36,10 @@ #include "py/objlist.h" #include "py/objexcept.h" +#if MICROPY_PY_BLUETOOTH +#include "extmod/modbluetooth.h" +#endif + // This file contains structures defining the state of the MicroPython // memory system, runtime and virtual machine. The state is a global // variable, but in the future it is hoped that the state can become local. @@ -183,6 +187,15 @@ typedef struct _mp_state_vm_t { struct _mp_vfs_mount_t *vfs_mount_table; #endif + #if MICROPY_PY_BLUETOOTH + // This is a linked list of callbacks registered in the Bluetooth + // object. + mp_bt_characteristic_callback_t *bt_characteristic_callbacks; + // This is a function object called for global events (device + // connect/disconnect). + mp_obj_t bt_event_handler; + #endif + // // END ROOT POINTER SECTION //////////////////////////////////////////////////////////// diff --git a/py/py.mk b/py/py.mk index e649297e680c9..1d50088f84d45 100644 --- a/py/py.mk +++ b/py/py.mk @@ -37,6 +37,11 @@ CFLAGS_MOD += $(CFLAGS_USERMOD) LDFLAGS_MOD += $(LDFLAGS_USERMOD) endif +ifeq ($(MICROPY_PY_BLUETOOTH),1) +SRC_MOD += extmod/modbluetooth.c +CFLAGS_MOD += -DMICROPY_PY_BLUETOOTH=1 +endif + # py object files PY_CORE_O_BASENAME = $(addprefix py/,\ mpstate.o \