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 \ pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy