From fc910a8cfe12084e67a574b7dfbe81669c0d5e73 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 23 Jul 2018 21:41:54 +0200 Subject: [PATCH 01/28] all: Add bluetooth module for BLE. Rebased to master with the help of Mirko Vogt. --- extmod/modbluetooth.c | 150 +++++++++++++ extmod/modbluetooth.h | 55 +++++ ports/esp32/Makefile | 166 ++++++++++++++ ports/esp32/bluetooth/bluetooth.c | 205 +++++++++++++++++ ports/esp32/bluetooth/bluetooth.h | 36 +++ ports/esp32/boards/sdkconfig | 6 + ports/esp32/main.c | 2 + ports/esp32/mpconfigport.h | 2 + ports/nrf/Makefile | 23 +- ports/nrf/bluetooth/bluetooth.c | 316 +++++++++++++++++++++++++++ ports/nrf/bluetooth/bluetooth.h | 47 ++++ ports/nrf/drivers/flash.c | 4 +- ports/nrf/modules/machine/temp.c | 6 +- ports/nrf/modules/random/modrandom.c | 4 +- ports/nrf/mpconfigport.h | 24 +- ports/nrf/nrfx_glue.h | 13 +- py/py.mk | 5 + 17 files changed, 1011 insertions(+), 53 deletions(-) create mode 100644 extmod/modbluetooth.c create mode 100644 extmod/modbluetooth.h create mode 100644 ports/esp32/bluetooth/bluetooth.c create mode 100644 ports/esp32/bluetooth/bluetooth.h create mode 100644 ports/nrf/bluetooth/bluetooth.c create mode 100644 ports/nrf/bluetooth/bluetooth.h diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c new file mode 100644 index 0000000000000..4c57ecc88791f --- /dev/null +++ b/extmod/modbluetooth.c @@ -0,0 +1,150 @@ +/* + * 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/runtime.h" +#include "extmod/modbluetooth.h" + +#if MICROPY_PY_BLUETOOTH + +STATIC const mp_obj_type_t bluetooth_type; + +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; +} + +STATIC mp_obj_t bluetooth_make_new() { + 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])) { + 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_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_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 + } + + size_t name_len; + const char *name = NULL; + if (args[ARG_name].u_obj != mp_const_none) { + name = mp_obj_str_get_data(args[ARG_name].u_obj, &name_len); + } + + uint8_t adv_data[31]; + size_t adv_data_len = 0; + + if (name != NULL) { + adv_data[adv_data_len++] = 2; // 1 byte type + 1 byte flags data + adv_data[adv_data_len++] = MP_BLE_GAP_AD_TYPE_FLAG; + adv_data[adv_data_len++] = MP_BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE; + + if (name_len + 3 > sizeof(adv_data) - adv_data_len) { + mp_raise_ValueError("advertisement packet overflow"); + } + adv_data[adv_data_len++] = name_len + 1; + adv_data[adv_data_len++] = MP_BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME; + for (size_t i=0; i +#include "bluetooth/bluetooth.h" + +// 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); + +// 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, uint8_t *adv_data, size_t adv_data_len, uint8_t *sr_data, size_t sr_data_len); + +// Stop advertisement. No-op when already stopped. +void mp_bt_advertise_stop(void); + +// 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) diff --git a/ports/esp32/Makefile b/ports/esp32/Makefile index ec6d29695b13d..edb22923e28ad 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 \ @@ -185,6 +206,7 @@ SRC_C = \ machine_uart.c \ modmachine.c \ modnetwork.c \ + bluetooth/bluetooth.c \ network_lan.c \ network_ppp.c \ modsocket.c \ @@ -676,6 +698,146 @@ ESPIDF_SDMMC_O = $(addprefix $(ESPCOMP)/sdmmc/,\ sdmmc_io.o \ ) +ESPIDF_BT_O = $(addprefix $(ESPCOMP)/bt/,\ + bt.o \ + bluedroid/api/esp_a2dp_api.o \ + bluedroid/api/esp_avrc_api.o \ + bluedroid/api/esp_blufi_api.o \ + bluedroid/api/esp_bt_device.o \ + bluedroid/api/esp_bt_main.o \ + bluedroid/api/esp_gap_ble_api.o \ + bluedroid/api/esp_gap_bt_api.o \ + bluedroid/api/esp_gattc_api.o \ + bluedroid/api/esp_gatt_common_api.o \ + bluedroid/api/esp_gatts_api.o \ + bluedroid/api/esp_hf_client_api.o \ + bluedroid/api/esp_spp_api.o \ + bluedroid/bta/dm/bta_dm_act.o \ + bluedroid/bta/dm/bta_dm_api.o \ + bluedroid/bta/dm/bta_dm_cfg.o \ + bluedroid/bta/dm/bta_dm_ci.o \ + bluedroid/bta/dm/bta_dm_co.o \ + bluedroid/bta/dm/bta_dm_main.o \ + bluedroid/bta/dm/bta_dm_pm.o \ + bluedroid/bta/dm/bta_dm_sco.o \ + bluedroid/bta/gatt/bta_gattc_act.o \ + bluedroid/bta/gatt/bta_gattc_api.o \ + bluedroid/bta/gatt/bta_gattc_cache.o \ + bluedroid/bta/gatt/bta_gattc_ci.o \ + bluedroid/bta/gatt/bta_gattc_co.o \ + bluedroid/bta/gatt/bta_gattc_main.o \ + bluedroid/bta/gatt/bta_gatt_common.o \ + bluedroid/bta/gatt/bta_gattc_utils.o \ + bluedroid/bta/gatt/bta_gatts_act.o \ + bluedroid/bta/gatt/bta_gatts_api.o \ + bluedroid/bta/gatt/bta_gatts_co.o \ + bluedroid/bta/gatt/bta_gatts_main.o \ + bluedroid/bta/gatt/bta_gatts_utils.o \ + bluedroid/bta/sys/bta_sys_conn.o \ + bluedroid/bta/sys/bta_sys_main.o \ + bluedroid/bta/sys/utl.o \ + bluedroid/btc/core/btc_alarm.o \ + bluedroid/btc/core/btc_ble_storage.o \ + bluedroid/btc/core/btc_config.o \ + bluedroid/btc/core/btc_dev.o \ + bluedroid/btc/core/btc_dm.o \ + bluedroid/btc/core/btc_main.o \ + bluedroid/btc/core/btc_manage.o \ + bluedroid/btc/core/btc_profile_queue.o \ + bluedroid/btc/core/btc_sec.o \ + bluedroid/btc/core/btc_sm.o \ + bluedroid/btc/core/btc_storage.o \ + bluedroid/btc/core/btc_task.o \ + bluedroid/btc/core/btc_util.o \ + bluedroid/btc/profile/esp/blufi/blufi_prf.o \ + bluedroid/btc/profile/esp/blufi/blufi_protocol.o \ + bluedroid/btc/profile/std/gap/btc_gap_ble.o \ + bluedroid/btc/profile/std/gap/btc_gap_bt.o \ + bluedroid/btc/profile/std/gatt/btc_gattc.o \ + bluedroid/btc/profile/std/gatt/btc_gatt_common.o \ + bluedroid/btc/profile/std/gatt/btc_gatts.o \ + bluedroid/btc/profile/std/gatt/btc_gatt_util.o \ + bluedroid/device/bdaddr.o \ + bluedroid/device/controller.o \ + bluedroid/device/interop.o \ + bluedroid/hci/hci_audio.o \ + bluedroid/hci/hci_hal_h4.o \ + bluedroid/hci/hci_layer.o \ + bluedroid/hci/hci_packet_factory.o \ + bluedroid/hci/hci_packet_parser.o \ + bluedroid/hci/packet_fragmenter.o \ + bluedroid/main/bte_init.o \ + bluedroid/main/bte_main.o \ + bluedroid/osi/alarm.o \ + bluedroid/osi/allocator.o \ + bluedroid/osi/buffer.o \ + bluedroid/osi/config.o \ + bluedroid/osi/fixed_queue.o \ + bluedroid/osi/future.o \ + bluedroid/osi/hash_functions.o \ + bluedroid/osi/hash_map.o \ + bluedroid/osi/list.o \ + bluedroid/osi/mutex.o \ + bluedroid/osi/osi.o \ + bluedroid/osi/semaphore.o \ + bluedroid/stack/btm/btm_acl.o \ + bluedroid/stack/btm/btm_ble_addr.o \ + bluedroid/stack/btm/btm_ble_adv_filter.o \ + bluedroid/stack/btm/btm_ble_batchscan.o \ + bluedroid/stack/btm/btm_ble_bgconn.o \ + bluedroid/stack/btm/btm_ble.o \ + bluedroid/stack/btm/btm_ble_cont_energy.o \ + bluedroid/stack/btm/btm_ble_gap.o \ + bluedroid/stack/btm/btm_ble_multi_adv.o \ + bluedroid/stack/btm/btm_ble_privacy.o \ + bluedroid/stack/btm/btm_dev.o \ + bluedroid/stack/btm/btm_devctl.o \ + bluedroid/stack/btm/btm_inq.o \ + bluedroid/stack/btm/btm_main.o \ + bluedroid/stack/btm/btm_pm.o \ + bluedroid/stack/btm/btm_sco.o \ + bluedroid/stack/btm/btm_sec.o \ + bluedroid/stack/btu/btu_hcif.o \ + bluedroid/stack/btu/btu_init.o \ + bluedroid/stack/btu/btu_task.o \ + bluedroid/stack/gap/gap_api.o \ + bluedroid/stack/gap/gap_ble.o \ + bluedroid/stack/gap/gap_conn.o \ + bluedroid/stack/gap/gap_utils.o \ + bluedroid/stack/gatt/att_protocol.o \ + bluedroid/stack/gatt/gatt_api.o \ + bluedroid/stack/gatt/gatt_attr.o \ + bluedroid/stack/gatt/gatt_auth.o \ + bluedroid/stack/gatt/gatt_cl.o \ + bluedroid/stack/gatt/gatt_db.o \ + bluedroid/stack/gatt/gatt_main.o \ + bluedroid/stack/gatt/gatt_sr.o \ + bluedroid/stack/gatt/gatt_utils.o \ + bluedroid/stack/hcic/hciblecmds.o \ + bluedroid/stack/hcic/hcicmds.o \ + bluedroid/stack/l2cap/l2cap_client.o \ + bluedroid/stack/l2cap/l2c_api.o \ + bluedroid/stack/l2cap/l2c_ble.o \ + bluedroid/stack/l2cap/l2c_csm.o \ + bluedroid/stack/l2cap/l2c_fcr.o \ + bluedroid/stack/l2cap/l2c_link.o \ + bluedroid/stack/l2cap/l2c_main.o \ + bluedroid/stack/l2cap/l2c_ucd.o \ + bluedroid/stack/l2cap/l2c_utils.o \ + bluedroid/stack/smp/aes.o \ + bluedroid/stack/smp/p_256_curvepara.o \ + bluedroid/stack/smp/p_256_ecc_pp.o \ + bluedroid/stack/smp/p_256_multprecision.o \ + bluedroid/stack/smp/smp_act.o \ + bluedroid/stack/smp/smp_api.o \ + bluedroid/stack/smp/smp_br_main.o \ + bluedroid/stack/smp/smp_cmac.o \ + bluedroid/stack/smp/smp_keys.o \ + bluedroid/stack/smp/smp_l2c.o \ + bluedroid/stack/smp/smp_main.o \ + bluedroid/stack/smp/smp_utils.o \ + ) + OBJ_ESPIDF = OBJ_ESPIDF += $(addprefix $(BUILD)/, $(ESPIDF_NEWLIB_O)) OBJ_ESPIDF += $(addprefix $(BUILD)/, $(ESPIDF_DRIVER_O)) @@ -706,6 +868,7 @@ OBJ_ESPIDF += $(addprefix $(BUILD)/, $(ESPIDF_SPI_FLASH_O)) OBJ_ESPIDF += $(addprefix $(BUILD)/, $(ESPIDF_ULP_O)) OBJ_ESPIDF += $(addprefix $(BUILD)/, $(ESPIDF_WPA_SUPPLICANT_O)) OBJ_ESPIDF += $(addprefix $(BUILD)/, $(ESPIDF_SDMMC_O)) +OBJ_ESPIDF += $(addprefix $(BUILD)/, $(ESPIDF_BT_O)) $(OBJ_ESPIDF): $(SDKCONFIG_H) @@ -738,6 +901,7 @@ LIB_ESPIDF += lwip LIB_ESPIDF += mbedtls LIB_ESPIDF += wpa_supplicant LIB_ESPIDF += sdmmc +LIB_ESPIDF += bt BUILD_ESPIDF_LIB = $(BUILD)/esp-idf @@ -780,6 +944,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))) LIB = $(foreach lib,$(LIB_ESPIDF),$(BUILD_ESPIDF_LIB)/$(lib)/lib$(lib).a) @@ -849,6 +1014,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..4eb3039283557 --- /dev/null +++ b/ports/esp32/bluetooth/bluetooth.c @@ -0,0 +1,205 @@ +/* + * 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_gatts_api.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" + +#include "py/mperrno.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 mp_bt_adv_type_t bluetooth_adv_type; +STATIC uint16_t bluetooth_adv_interval; + +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); + +// Convert an esp_err_t into an errno number. +STATIC int mp_bt_esp_errno(esp_err_t err) { + if (err != 0) { + return MP_EPERM; + } + return 0; +} + +// Convert the result of an asynchronous call to an errno value. +STATIC int mp_bt_status_errno() { + if (mp_bt_call_status != ESP_BT_STATUS_SUCCESS) { + return MP_EPERM; + } + return 0; +} + +// 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); + } + err = esp_ble_gatts_app_register(0); + if (err != 0) { + return mp_bt_esp_errno(err); + } + 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; +} + +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, uint8_t *adv_data, size_t adv_data_len, uint8_t *sr_data, size_t sr_data_len) { + if (adv_data != NULL) { + esp_err_t err = esp_ble_gap_config_adv_data_raw(adv_data, adv_data_len); + if (err != 0) { + return mp_bt_esp_errno(err); + } + xSemaphoreTake(mp_bt_call_complete, portMAX_DELAY); + } + + if (sr_data != NULL) { + esp_err_t err = esp_ble_gap_config_scan_rsp_data_raw(sr_data, sr_data_len); + if (err != 0) { + return mp_bt_esp_errno(err); + } + xSemaphoreTake(mp_bt_call_complete, portMAX_DELAY); + } + + 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); + } + 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; + } + xSemaphoreTake(mp_bt_call_complete, portMAX_DELAY); +} + +// 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: + xSemaphoreGive(mp_bt_call_complete); + break; + case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT: + 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_REG_EVT: + break; + case ESP_GATTS_CONNECT_EVT: + break; + case ESP_GATTS_DISCONNECT_EVT: + // restart advertisement + mp_bt_advertise_start_internal(); + 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..4021b5d922b77 --- /dev/null +++ b/ports/esp32/bluetooth/bluetooth.h @@ -0,0 +1,36 @@ +/* + * 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 + +void mp_bt_init(void); 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 d4f79646f6c89..7902f8a840a12 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(); xTaskCreate(mp_task, "mp_task", MP_TASK_STACK_LEN, NULL, MP_TASK_PRIORITY, &mp_main_task_handle); } diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index bd19c6bfbdb2d..abf1df2d76268 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -178,6 +178,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 \ @@ -188,6 +189,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..896b5f3d0de33 --- /dev/null +++ b/ports/nrf/bluetooth/bluetooth.c @@ -0,0 +1,316 @@ +/* + * 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" +#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[31]; +static uint8_t bluetooth_sr_data[31]; +#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) { + if (err_code == NRF_ERROR_INVALID_PARAM) { + return MP_EINVAL; + } else if (err_code != 0) { + return MP_EPERM; + } + return 0; +} + +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; +} + +#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, uint8_t *adv_data, size_t adv_data_len, 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) { + 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_DISCONNECTED: +#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 + } +} + +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..9d5b23028990a --- /dev/null +++ b/ports/nrf/bluetooth/bluetooth.h @@ -0,0 +1,47 @@ +/* + * 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" + +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 +#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 +#endif + +#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..214ea10678de7 100644 --- a/ports/nrf/mpconfigport.h +++ b/ports/nrf/mpconfigport.h @@ -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/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 \ From eab2228eb41ebdbca75898b6e9027daec302f262 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 1 Aug 2018 13:34:59 +0200 Subject: [PATCH 02/28] extmod/modbluetooth: Add Bluetooth.advertise_raw(). This is useful for more advanced use cases, like iBeacon/Eddystone. --- extmod/modbluetooth.c | 44 +++++++++++++++++++++++++++++++ extmod/modbluetooth.h | 2 +- ports/esp32/bluetooth/bluetooth.c | 6 ++--- ports/nrf/bluetooth/bluetooth.c | 2 +- 4 files changed, 49 insertions(+), 5 deletions(-) diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index 4c57ecc88791f..a04852c76db43 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -123,9 +123,53 @@ STATIC mp_obj_t bluetooth_advertise(size_t n_args, const mp_obj_t *pos_args, mp_ } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(bluetooth_advertise_obj, 1, bluetooth_advertise); +STATIC mp_obj_t bluetooth_advertise_raw(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_interval, ARG_adv_data, ARG_sr_data, ARG_connectable }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_interval, MP_ARG_INT, {.u_int = 100} }, + { MP_QSTR_adv_data, MP_ARG_OBJ, {.u_obj = mp_const_none } }, + { MP_QSTR_sr_data, MP_ARG_OBJ, {.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 + } + + size_t adv_data_len; + const uint8_t *adv_data = NULL; + if (args[ARG_adv_data].u_obj != mp_const_none) { + adv_data = (const uint8_t*)mp_obj_str_get_data(args[ARG_adv_data].u_obj, &adv_data_len); + } + + size_t sr_data_len; + const uint8_t *sr_data = NULL; + if (args[ARG_sr_data].u_obj != mp_const_none) { + sr_data = (const uint8_t*)mp_obj_str_get_data(args[ARG_sr_data].u_obj, &sr_data_len); + } + + int errno_ = mp_bt_advertise_start(adv_type, interval, adv_data_len ? adv_data : NULL, adv_data_len, sr_data_len ? sr_data : NULL, sr_data_len); + return bluetooth_handle_errno(errno_); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(bluetooth_advertise_raw_obj, 1, bluetooth_advertise_raw); + 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_advertise_raw), MP_ROM_PTR(&bluetooth_advertise_raw_obj) }, }; STATIC MP_DEFINE_CONST_DICT(bluetooth_locals_dict, bluetooth_locals_dict_table); diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index b8987d5595f01..f7fa7638293d0 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -40,7 +40,7 @@ bool mp_bt_is_enabled(void); // 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, uint8_t *adv_data, size_t adv_data_len, uint8_t *sr_data, size_t sr_data_len); +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); diff --git a/ports/esp32/bluetooth/bluetooth.c b/ports/esp32/bluetooth/bluetooth.c index 4eb3039283557..d9a7c598c4fe0 100644 --- a/ports/esp32/bluetooth/bluetooth.c +++ b/ports/esp32/bluetooth/bluetooth.c @@ -124,9 +124,9 @@ STATIC esp_err_t mp_bt_advertise_start_internal(void) { return esp_ble_gap_start_advertising(&ble_adv_params); } -int mp_bt_advertise_start(mp_bt_adv_type_t type, uint16_t interval, uint8_t *adv_data, size_t adv_data_len, uint8_t *sr_data, size_t sr_data_len) { +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(adv_data, adv_data_len); + 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); } @@ -134,7 +134,7 @@ int mp_bt_advertise_start(mp_bt_adv_type_t type, uint16_t interval, uint8_t *adv } if (sr_data != NULL) { - esp_err_t err = esp_ble_gap_config_scan_rsp_data_raw(sr_data, sr_data_len); + 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); } diff --git a/ports/nrf/bluetooth/bluetooth.c b/ports/nrf/bluetooth/bluetooth.c index 896b5f3d0de33..ec2f5f0792807 100644 --- a/ports/nrf/bluetooth/bluetooth.c +++ b/ports/nrf/bluetooth/bluetooth.c @@ -198,7 +198,7 @@ STATIC uint32_t mp_bt_advertise_start_internal(void) { } #endif -int mp_bt_advertise_start(mp_bt_adv_type_t type, uint16_t interval, uint8_t *adv_data, size_t adv_data_len, uint8_t *sr_data, size_t sr_data_len) { +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 From 986d84e5fe5d84489f31c3d0b57d4b74f63db54b Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 1 Mar 2019 11:08:55 +0100 Subject: [PATCH 03/28] extmod/modbluetooth: Add Bluetooth.add_service(). At the moment, only a service is created. Characteristics within a service cannot yet be added. --- extmod/modbluetooth.c | 25 +++++++++ extmod/modbluetooth.h | 14 +++++ ports/esp32/bluetooth/bluetooth.c | 86 ++++++++++++++++++++++++++++--- ports/esp32/bluetooth/bluetooth.h | 4 ++ ports/nrf/bluetooth/bluetooth.c | 16 ++++++ ports/nrf/bluetooth/bluetooth.h | 4 ++ 6 files changed, 143 insertions(+), 6 deletions(-) diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index a04852c76db43..449dfcf8f9644 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -31,6 +31,7 @@ #if MICROPY_PY_BLUETOOTH STATIC const mp_obj_type_t bluetooth_type; +STATIC const mp_obj_type_t service_type; typedef struct _mp_obj_bluetooth_t { mp_obj_base_t base; @@ -166,10 +167,33 @@ STATIC mp_obj_t bluetooth_advertise_raw(size_t n_args, const mp_obj_t *pos_args, } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(bluetooth_advertise_raw_obj, 1, bluetooth_advertise_raw); +STATIC mp_obj_t bluetooth_add_service(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_interval, ARG_adv_data, ARG_sr_data, ARG_connectable }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_uuid, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = mp_const_none } }, + }; + 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_bt_service_t *service = m_new_obj(mp_bt_service_t); + service->base.type = &service_type; + bluetooth_parse_uuid(args[0].u_obj, &service->uuid); + int errno_ = mp_bt_add_service(service); + bluetooth_handle_errno(errno_); + return service; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(bluetooth_add_service_obj, 1, bluetooth_add_service); + +STATIC const mp_obj_type_t service_type = { + { &mp_type_type }, + .name = MP_QSTR_Service, +}; + 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_advertise_raw), MP_ROM_PTR(&bluetooth_advertise_raw_obj) }, + { MP_ROM_QSTR(MP_QSTR_add_service), MP_ROM_PTR(&bluetooth_add_service_obj) }, }; STATIC MP_DEFINE_CONST_DICT(bluetooth_locals_dict, bluetooth_locals_dict_table); @@ -183,6 +207,7 @@ STATIC const mp_obj_type_t bluetooth_type = { 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_Service), MP_ROM_PTR(&service_type) }, }; STATIC MP_DEFINE_CONST_DICT(mp_module_bluetooth_globals, mp_module_bluetooth_globals_table); diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index f7fa7638293d0..c20691485e60e 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -28,6 +28,13 @@ #include #include "bluetooth/bluetooth.h" +#include "py/obj.h" + +typedef struct { + mp_obj_base_t base; + mp_bt_uuid_t uuid; + mp_bt_service_handle_t handle; +} mp_bt_service_t; // Enables the Bluetooth stack. Returns errno on failure. int mp_bt_enable(void); @@ -45,6 +52,13 @@ int mp_bt_advertise_start(mp_bt_adv_type_t type, uint16_t interval, const uint8_ // Stop advertisement. No-op when already stopped. void mp_bt_advertise_stop(void); +int mp_bt_add_service(mp_bt_service_t *service); + +// 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 bluetooth_parse_uuid(mp_obj_t obj, mp_bt_uuid_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) diff --git a/ports/esp32/bluetooth/bluetooth.c b/ports/esp32/bluetooth/bluetooth.c index d9a7c598c4fe0..0dbfcb89b3ec4 100644 --- a/ports/esp32/bluetooth/bluetooth.c +++ b/ports/esp32/bluetooth/bluetooth.c @@ -34,14 +34,21 @@ #include "freertos/semphr.h" #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; +} mp_bt_call_result; STATIC mp_bt_adv_type_t bluetooth_adv_type; STATIC uint16_t bluetooth_adv_interval; +STATIC uint16_t bluetooth_app_id = 0; // provide unique number for each application profile 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); @@ -94,10 +101,6 @@ int mp_bt_enable(void) { if (err != 0) { return mp_bt_esp_errno(err); } - err = esp_ble_gatts_app_register(0); - if (err != 0) { - return mp_bt_esp_errno(err); - } return 0; } @@ -159,6 +162,62 @@ void mp_bt_advertise_stop(void) { xSemaphoreTake(mp_bt_call_complete, portMAX_DELAY); } +int mp_bt_add_service(mp_bt_service_t *service) { + // In ESP-IDF, a service is more than just a service, it's an + // "application profile". One application profile contains exactly one + // service. For details, see: + // https://github.com/espressif/esp-idf/blob/master/examples/bluetooth/gatt_server/tutorial/Gatt_Server_Example_Walkthrough.md + + // Register an application profile. + esp_err_t err = esp_ble_gatts_app_register(bluetooth_app_id); + if (err != 0) { + return mp_bt_esp_errno(err); + } + bluetooth_app_id++; + // Wait for ESP_GATTS_REG_EVT + xSemaphoreTake(mp_bt_call_complete, portMAX_DELAY); + if (mp_bt_call_status != 0) { + return mp_bt_status_errno(); + } + esp_gatt_if_t gatts_if = mp_bt_call_result.gatts_if; + + // 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; + err = esp_ble_gatts_create_service(gatts_if, &bluetooth_service_id, 1); + 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); + return mp_bt_status_errno(); +} + +// Parse a UUID object from the caller. +void bluetooth_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 { + mp_raise_ValueError("cannot parse UUID"); + } +} + // 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) { @@ -188,14 +247,29 @@ STATIC void mp_bt_gap_callback(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_para 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_REG_EVT: - break; case ESP_GATTS_CONNECT_EVT: break; case ESP_GATTS_DISCONNECT_EVT: // 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; default: ESP_LOGI("bluetooth", "GATTS: unknown event: %d", event); break; diff --git a/ports/esp32/bluetooth/bluetooth.h b/ports/esp32/bluetooth/bluetooth.h index 4021b5d922b77..3383b2f0b3188 100644 --- a/ports/esp32/bluetooth/bluetooth.h +++ b/ports/esp32/bluetooth/bluetooth.h @@ -34,3 +34,7 @@ typedef esp_ble_adv_type_t mp_bt_adv_type_t; #define MP_BT_ADV_TYPE_ADV_NONCONN_IND ADV_TYPE_NONCONN_IND void mp_bt_init(void); + +typedef esp_bt_uuid_t mp_bt_uuid_t; + +typedef uint16_t mp_bt_service_handle_t; diff --git a/ports/nrf/bluetooth/bluetooth.c b/ports/nrf/bluetooth/bluetooth.c index ec2f5f0792807..fcfb3c9420454 100644 --- a/ports/nrf/bluetooth/bluetooth.c +++ b/ports/nrf/bluetooth/bluetooth.c @@ -267,6 +267,22 @@ static void ble_evt_handler(ble_evt_t * p_ble_evt) { } } +int mp_bt_add_service(mp_bt_service_t *service) { + uint32_t err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, &service->uuid, &service->handle); + return mp_bt_errno(err_code); +} + +// Parse a UUID object from the caller. +void bluetooth_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 { + mp_raise_ValueError("cannot parse UUID"); + } +} + static void sd_evt_handler(uint32_t evt_id) { switch (evt_id) { #if MICROPY_MBFS diff --git a/ports/nrf/bluetooth/bluetooth.h b/ports/nrf/bluetooth/bluetooth.h index 9d5b23028990a..af1dcc309f1e5 100644 --- a/ports/nrf/bluetooth/bluetooth.h +++ b/ports/nrf/bluetooth/bluetooth.h @@ -44,4 +44,8 @@ typedef uint8_t mp_bt_adv_type_t; #define MP_BT_ADV_TYPE_ADV_NONCONN_IND BLE_GAP_ADV_TYPE_NONCONNECTABLE_SCANNABLE_UNDIRECTED #endif +typedef ble_uuid_t mp_bt_uuid_t; + +typedef uint16_t mp_bt_service_handle_t; + #endif // MICROPY_PY_BLUETOOTH From 3558d22f429b891b57d1d199b0bae8f037d1865a Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 1 Mar 2019 14:31:15 +0100 Subject: [PATCH 04/28] extmod/modbluetooth: Add support for 128-bit UUIDs. 128-bit proprietary UUIDs can be passed using strings. Dashes and capitalization are ignored. --- extmod/modbluetooth.c | 36 +++++++++++++++++++++++++++++++ extmod/modbluetooth.h | 4 ++++ ports/esp32/bluetooth/bluetooth.c | 4 ++++ ports/nrf/bluetooth/bluetooth.c | 12 +++++++++++ 4 files changed, 56 insertions(+) diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index 449dfcf8f9644..167441a1d0f88 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -25,6 +25,7 @@ */ #include "py/obj.h" +#include "py/objstr.h" #include "py/runtime.h" #include "extmod/modbluetooth.h" @@ -51,6 +52,41 @@ STATIC mp_obj_t bluetooth_handle_errno(int errno_) { return mp_const_none; } +// Parse string UUIDs, which are probably 128-bit UUIDs. +void bluetooth_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"); + } +} + STATIC mp_obj_t bluetooth_make_new() { return MP_OBJ_FROM_PTR(&bluetooth_obj); } diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index c20691485e60e..7f2deb1ea26fb 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -59,6 +59,10 @@ int mp_bt_add_service(mp_bt_service_t *service); // UUIDs. void bluetooth_parse_uuid(mp_obj_t obj, 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 bluetooth_parse_uuid_str(mp_obj_t obj, 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) diff --git a/ports/esp32/bluetooth/bluetooth.c b/ports/esp32/bluetooth/bluetooth.c index 0dbfcb89b3ec4..3cdf45dbb6d97 100644 --- a/ports/esp32/bluetooth/bluetooth.c +++ b/ports/esp32/bluetooth/bluetooth.c @@ -213,6 +213,10 @@ void bluetooth_parse_uuid(mp_obj_t obj, mp_bt_uuid_t *uuid) { // 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; + bluetooth_parse_uuid_str(obj, &uuid->uuid.uuid128[0]); } else { mp_raise_ValueError("cannot parse UUID"); } diff --git a/ports/nrf/bluetooth/bluetooth.c b/ports/nrf/bluetooth/bluetooth.c index fcfb3c9420454..3a1e130b0c464 100644 --- a/ports/nrf/bluetooth/bluetooth.c +++ b/ports/nrf/bluetooth/bluetooth.c @@ -111,6 +111,8 @@ STATIC const ble_gap_conn_params_t gap_conn_params = { STATIC int mp_bt_errno(uint32_t err_code) { if (err_code == NRF_ERROR_INVALID_PARAM) { return MP_EINVAL; + } else if (err_code == NRF_ERROR_NO_MEM) { + return MP_ENOMEM; } else if (err_code != 0) { return MP_EPERM; } @@ -278,6 +280,16 @@ void bluetooth_parse_uuid(mp_obj_t obj, mp_bt_uuid_t *uuid) { // 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. + uuid->type = BLE_UUID_TYPE_BLE; + ble_uuid128_t buf; + bluetooth_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"); } From 346e5a7b6350db2b095de920adc37cf00d01bd6d Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 3 Mar 2019 17:37:25 +0100 Subject: [PATCH 05/28] extmod/modbluetooth: Add support for characteristics. Add support for adding characteristics to services. They can be read and written on both the esp32 and the nrf. Events of any kind (notifications etc.) haven't been implemented yet. --- extmod/modbluetooth.c | 156 +++++++++++++++++++++++++++--- extmod/modbluetooth.h | 35 ++++++- ports/esp32/bluetooth/bluetooth.c | 114 +++++++++++++++++++++- ports/esp32/bluetooth/bluetooth.h | 4 + ports/nrf/bluetooth/bluetooth.c | 115 ++++++++++++++++++++-- ports/nrf/bluetooth/bluetooth.h | 5 + 6 files changed, 400 insertions(+), 29 deletions(-) diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index 167441a1d0f88..f6c3de873b401 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -33,6 +33,7 @@ STATIC const mp_obj_type_t bluetooth_type; STATIC const mp_obj_type_t service_type; +STATIC const mp_obj_type_t characteristic_type; typedef struct _mp_obj_bluetooth_t { mp_obj_base_t base; @@ -53,7 +54,7 @@ STATIC mp_obj_t bluetooth_handle_errno(int errno_) { } // Parse string UUIDs, which are probably 128-bit UUIDs. -void bluetooth_parse_uuid_str(mp_obj_t obj, uint8_t *uuid) { +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++) { @@ -87,7 +88,36 @@ void bluetooth_parse_uuid_str(mp_obj_t obj, uint8_t *uuid) { } } -STATIC mp_obj_t bluetooth_make_new() { +// Format string UUID. Example output: +// '6e400001-b5a3-f393-e0a9-e50e24dcca9e' +mp_obj_t mp_bt_format_uuid_str(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)); +} + +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); } @@ -204,32 +234,130 @@ STATIC mp_obj_t bluetooth_advertise_raw(size_t n_args, const mp_obj_t *pos_args, STATIC MP_DEFINE_CONST_FUN_OBJ_KW(bluetooth_advertise_raw_obj, 1, bluetooth_advertise_raw); STATIC mp_obj_t bluetooth_add_service(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_interval, ARG_adv_data, ARG_sr_data, ARG_connectable }; + enum { ARG_uuid, ARG_characteristics }; static const mp_arg_t allowed_args[] = { - { MP_QSTR_uuid, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = mp_const_none } }, + { MP_QSTR_uuid, MP_ARG_OBJ | MP_ARG_REQUIRED }, + { MP_QSTR_characteristics, MP_ARG_OBJ | MP_ARG_REQUIRED }, }; 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_list_t *characteristics = args[ARG_characteristics].u_obj; + if (characteristics == NULL || !mp_obj_is_type(args[ARG_characteristics].u_obj, &mp_type_list)) { + mp_raise_ValueError("characteristics must be a list"); + } + for (int i = 0; i < characteristics->len; 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; - bluetooth_parse_uuid(args[0].u_obj, &service->uuid); - int errno_ = mp_bt_add_service(service); + 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 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_ = mp_bt_characteristic_value_set(characteristic->value_handle, 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->value_handle, 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 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) }, +}; +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_active), MP_ROM_PTR(&bluetooth_active_obj) }, + { MP_ROM_QSTR(MP_QSTR_advertise), MP_ROM_PTR(&bluetooth_advertise_obj) }, { MP_ROM_QSTR(MP_QSTR_advertise_raw), MP_ROM_PTR(&bluetooth_advertise_raw_obj) }, - { MP_ROM_QSTR(MP_QSTR_add_service), MP_ROM_PTR(&bluetooth_add_service_obj) }, + { MP_ROM_QSTR(MP_QSTR_add_service), MP_ROM_PTR(&bluetooth_add_service_obj) }, }; STATIC MP_DEFINE_CONST_DICT(bluetooth_locals_dict, bluetooth_locals_dict_table); @@ -241,9 +369,13 @@ STATIC const mp_obj_type_t bluetooth_type = { }; 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_Service), MP_ROM_PTR(&service_type) }, + { 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_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) }, }; STATIC MP_DEFINE_CONST_DICT(mp_module_bluetooth_globals, mp_module_bluetooth_globals_table); diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index 7f2deb1ea26fb..d62112535ddd5 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -36,6 +36,16 @@ typedef struct { 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; + // Enables the Bluetooth stack. Returns errno on failure. int mp_bt_enable(void); @@ -52,16 +62,31 @@ int mp_bt_advertise_start(mp_bt_adv_type_t type, uint16_t interval, const uint8_ // Stop advertisement. No-op when already stopped. void mp_bt_advertise_stop(void); -int mp_bt_add_service(mp_bt_service_t *service); +// 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_handle_t 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_handle_t handle, void *value, size_t *value_len); // 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 bluetooth_parse_uuid(mp_obj_t obj, mp_bt_uuid_t *uuid); +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 bluetooth_parse_uuid_str(mp_obj_t obj, uint8_t *uuid); +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(uint8_t *uuid); // Data types of advertisement packet. #define MP_BLE_GAP_AD_TYPE_FLAG (0x01) @@ -71,3 +96,7 @@ void bluetooth_parse_uuid_str(mp_obj_t obj, uint8_t *uuid); #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) diff --git a/ports/esp32/bluetooth/bluetooth.c b/ports/esp32/bluetooth/bluetooth.c index 3cdf45dbb6d97..3ab9b0b16a7aa 100644 --- a/ports/esp32/bluetooth/bluetooth.c +++ b/ports/esp32/bluetooth/bluetooth.c @@ -32,6 +32,7 @@ #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" +#include #include "py/mperrno.h" #include "py/runtime.h" @@ -44,6 +45,7 @@ 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; @@ -62,7 +64,7 @@ STATIC int mp_bt_esp_errno(esp_err_t err) { } // Convert the result of an asynchronous call to an errno value. -STATIC int mp_bt_status_errno() { +STATIC int mp_bt_status_errno(void) { if (mp_bt_call_status != ESP_BT_STATUS_SUCCESS) { return MP_EPERM; } @@ -162,7 +164,7 @@ void mp_bt_advertise_stop(void) { xSemaphoreTake(mp_bt_call_complete, portMAX_DELAY); } -int mp_bt_add_service(mp_bt_service_t *service) { +int mp_bt_add_service(mp_bt_service_t *service, size_t num_characteristics, mp_bt_characteristic_t **characteristics) { // In ESP-IDF, a service is more than just a service, it's an // "application profile". One application profile contains exactly one // service. For details, see: @@ -181,12 +183,17 @@ int mp_bt_add_service(mp_bt_service_t *service) { } esp_gatt_if_t gatts_if = mp_bt_call_result.gatts_if; + // 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; + // 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; - err = esp_ble_gatts_create_service(gatts_if, &bluetooth_service_id, 1); + err = esp_ble_gatts_create_service(gatts_if, &bluetooth_service_id, num_handle); if (err != 0) { return mp_bt_esp_errno(err); } @@ -204,11 +211,79 @@ int mp_bt_add_service(mp_bt_service_t *service) { } // 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(); + } + + // 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_handle_t handle, const void *value, size_t value_len) { + esp_err_t err = esp_ble_gatts_set_attr_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_get(mp_bt_characteristic_handle_t handle, 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(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; +} + // Parse a UUID object from the caller. -void bluetooth_parse_uuid(mp_obj_t obj, mp_bt_uuid_t *uuid) { +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; @@ -216,12 +291,24 @@ void bluetooth_parse_uuid(mp_obj_t obj, mp_bt_uuid_t *uuid) { } else if (mp_obj_is_str(obj)) { // Guessing this is a 128-bit (proprietary) UUID. uuid->len = ESP_UUID_LEN_128; - bluetooth_parse_uuid_str(obj, &uuid->uuid.uuid128[0]); + 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) { @@ -274,6 +361,23 @@ STATIC void mp_bt_gatts_callback(esp_gatts_cb_event_t event, esp_gatt_if_t gatts 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_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. + break; default: ESP_LOGI("bluetooth", "GATTS: unknown event: %d", event); break; diff --git a/ports/esp32/bluetooth/bluetooth.h b/ports/esp32/bluetooth/bluetooth.h index 3383b2f0b3188..fb787ca228f81 100644 --- a/ports/esp32/bluetooth/bluetooth.h +++ b/ports/esp32/bluetooth/bluetooth.h @@ -33,8 +33,12 @@ 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) + 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/nrf/bluetooth/bluetooth.c b/ports/nrf/bluetooth/bluetooth.c index 3a1e130b0c464..85bd55e5c0322 100644 --- a/ports/nrf/bluetooth/bluetooth.c +++ b/ports/nrf/bluetooth/bluetooth.c @@ -109,14 +109,24 @@ STATIC const ble_gap_conn_params_t gap_conn_params = { }; STATIC int mp_bt_errno(uint32_t err_code) { - if (err_code == NRF_ERROR_INVALID_PARAM) { + switch (err_code) { + case 0: + return 0; // no error + case NRF_ERROR_INVALID_PARAM: return MP_EINVAL; - } else if (err_code == NRF_ERROR_NO_MEM) { + case NRF_ERROR_NO_MEM: return MP_ENOMEM; - } else if (err_code != 0) { - return MP_EPERM; + 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 } - return 0; } int mp_bt_enable(void) { @@ -269,22 +279,93 @@ static void ble_evt_handler(ble_evt_t * p_ble_evt) { } } -int mp_bt_add_service(mp_bt_service_t *service) { +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_handle_t handle, 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, handle, &data); + return mp_bt_errno(err_code); +} + +int mp_bt_characteristic_value_get(mp_bt_characteristic_handle_t handle, 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, handle, &data); + *value_len = data.len; return mp_bt_errno(err_code); } // Parse a UUID object from the caller. -void bluetooth_parse_uuid(mp_obj_t obj, mp_bt_uuid_t *uuid) { +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. - uuid->type = BLE_UUID_TYPE_BLE; ble_uuid128_t buf; - bluetooth_parse_uuid_str(obj, &buf.uuid128[0]); + 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)); @@ -295,6 +376,22 @@ void bluetooth_parse_uuid(mp_obj_t obj, mp_bt_uuid_t *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 diff --git a/ports/nrf/bluetooth/bluetooth.h b/ports/nrf/bluetooth/bluetooth.h index af1dcc309f1e5..9ed705f5c2e6f 100644 --- a/ports/nrf/bluetooth/bluetooth.h +++ b/ports/nrf/bluetooth/bluetooth.h @@ -31,6 +31,7 @@ #include #include "ble_gap.h" +#include "ble_gatt.h" typedef uint8_t mp_bt_adv_type_t; @@ -44,8 +45,12 @@ typedef uint8_t mp_bt_adv_type_t; #define MP_BT_ADV_TYPE_ADV_NONCONN_IND BLE_GAP_ADV_TYPE_NONCONNECTABLE_SCANNABLE_UNDIRECTED #endif +#define MP_BT_MAX_ATTR_SIZE (20) + 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 From 417c70061b99a58b010b8df5a35b2a156d931f66 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 20 Mar 2019 15:54:52 +0100 Subject: [PATCH 06/28] esp32: improve error message for invalid advertisement data Instead of raising EPERM, raise EINVAL. --- ports/esp32/bluetooth/bluetooth.c | 36 +++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/ports/esp32/bluetooth/bluetooth.c b/ports/esp32/bluetooth/bluetooth.c index 3ab9b0b16a7aa..17b6d74345d21 100644 --- a/ports/esp32/bluetooth/bluetooth.c +++ b/ports/esp32/bluetooth/bluetooth.c @@ -57,18 +57,30 @@ STATIC void mp_bt_gatts_callback(esp_gatts_cb_event_t event, esp_gatt_if_t gatts // Convert an esp_err_t into an errno number. STATIC int mp_bt_esp_errno(esp_err_t err) { - if (err != 0) { - return MP_EPERM; + 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 } - return 0; } // Convert the result of an asynchronous call to an errno value. STATIC int mp_bt_status_errno(void) { - if (mp_bt_call_status != ESP_BT_STATUS_SUCCESS) { - return MP_EPERM; + 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 } - return 0; } // Initialize at early boot. @@ -135,7 +147,11 @@ int mp_bt_advertise_start(mp_bt_adv_type_t type, uint16_t interval, const uint8_ 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) { @@ -143,7 +159,11 @@ int mp_bt_advertise_start(mp_bt_adv_type_t type, uint16_t interval, const uint8_ 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; @@ -152,6 +172,7 @@ int mp_bt_advertise_start(mp_bt_adv_type_t type, uint16_t interval, const uint8_ 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(); } @@ -161,6 +182,7 @@ void mp_bt_advertise_stop(void) { if (err != 0) { return; } + // Wait for ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT xSemaphoreTake(mp_bt_call_complete, portMAX_DELAY); } @@ -314,9 +336,11 @@ mp_obj_t mp_bt_format_uuid(mp_bt_uuid_t *uuid) { 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: From e941673838edc888d712f63a995e5677c5bebbf7 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 20 Mar 2019 17:08:58 +0100 Subject: [PATCH 07/28] nrf: check for the maximum size of advertisement packets --- ports/nrf/bluetooth/bluetooth.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ports/nrf/bluetooth/bluetooth.c b/ports/nrf/bluetooth/bluetooth.c index 85bd55e5c0322..71e2ec76079cf 100644 --- a/ports/nrf/bluetooth/bluetooth.c +++ b/ports/nrf/bluetooth/bluetooth.c @@ -66,8 +66,8 @@ STATIC uint16_t bluetooth_adv_interval; #include "nrf_nvic.h" nrf_nvic_state_t nrf_nvic_state = {0}; static uint8_t bluetooth_adv_handle; -static uint8_t bluetooth_adv_data[31]; -static uint8_t bluetooth_sr_data[31]; +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 @@ -113,6 +113,7 @@ STATIC int mp_bt_errno(uint32_t 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: return MP_ENOMEM; @@ -221,6 +222,9 @@ int mp_bt_advertise_start(mp_bt_adv_type_t type, uint16_t interval, const uint8_ 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); } From f2552bd37a8e70b4440310601a6c853d673d012a Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 28 Mar 2019 14:00:54 +0100 Subject: [PATCH 08/28] extmod/modbluetooth: Add support for characteristic notifications. Keep track of all connected centrals and notify them when a characteristic is written to. --- extmod/modbluetooth.c | 48 +++++++++++++++++++++++++-- extmod/modbluetooth.h | 17 ++++++++-- ports/esp32/bluetooth/bluetooth.c | 55 ++++++++++++++++++++++++++++--- ports/esp32/bluetooth/bluetooth.h | 4 +++ ports/nrf/bluetooth/bluetooth.c | 32 +++++++++++++++--- ports/nrf/bluetooth/bluetooth.h | 5 ++- 6 files changed, 147 insertions(+), 14 deletions(-) diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index f6c3de873b401..1f327e5e61d62 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -35,6 +35,8 @@ STATIC const mp_obj_type_t bluetooth_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]; + typedef struct _mp_obj_bluetooth_t { mp_obj_base_t base; } mp_obj_bluetooth_t; @@ -117,6 +119,26 @@ mp_obj_t mp_bt_format_uuid_str(uint8_t *uuid) { 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) { + 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; + } + } +} + +// Remove this connection handle from the list of connected centrals. +void mp_bt_disconnected(uint16_t conn_handle) { + 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; + } + } +} + 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); } @@ -124,6 +146,9 @@ STATIC mp_obj_t bluetooth_make_new(const mp_obj_type_t *type, size_t n_args, siz 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_); @@ -321,7 +346,26 @@ 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_ = mp_bt_characteristic_value_set(characteristic->value_handle, 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); @@ -330,7 +374,7 @@ 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->value_handle, data, &value_len); + int errno_ = mp_bt_characteristic_value_get(characteristic, data, &value_len); if (errno_ != 0) { mp_raise_OSError(errno_); } diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index d62112535ddd5..19f1adf0ac212 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -34,6 +34,9 @@ typedef struct { mp_obj_base_t base; mp_bt_uuid_t uuid; mp_bt_service_handle_t handle; + #if ESP_PLATFORM + uint8_t esp_gatts_if; /* Only for ESP-IDF */ + #endif } mp_bt_service_t; // A characteristic. @@ -62,15 +65,25 @@ int mp_bt_advertise_start(mp_bt_adv_type_t type, uint16_t interval, const uint8_ // 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); + +// Call this when a central connects. +void mp_bt_disconnected(uint16_t conn_handle); + // 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_handle_t handle, const void *value, size_t value_len); +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_handle_t handle, void *value, size_t *value_len); +int mp_bt_characteristic_value_get(mp_bt_characteristic_t *characteristic, void *value, size_t *value_len); // 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 diff --git a/ports/esp32/bluetooth/bluetooth.c b/ports/esp32/bluetooth/bluetooth.c index 17b6d74345d21..4316667fc3455 100644 --- a/ports/esp32/bluetooth/bluetooth.c +++ b/ports/esp32/bluetooth/bluetooth.c @@ -55,6 +55,12 @@ STATIC uint16_t bluetooth_app_id = 0; // provide unique number for each applicat 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) { @@ -204,18 +210,25 @@ int mp_bt_add_service(mp_bt_service_t *service, size_t num_characteristics, mp_b return mp_bt_status_errno(); } esp_gatt_if_t gatts_if = mp_bt_call_result.gatts_if; + service->esp_gatts_if = gatts_if; // 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; - err = esp_ble_gatts_create_service(gatts_if, &bluetooth_service_id, num_handle); + err = esp_ble_gatts_create_service(service->esp_gatts_if, &bluetooth_service_id, num_handle); if (err != 0) { return mp_bt_esp_errno(err); } @@ -268,6 +281,23 @@ int mp_bt_add_service(mp_bt_service_t *service, size_t num_characteristics, mp_b 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 @@ -279,8 +309,8 @@ int mp_bt_add_service(mp_bt_service_t *service, size_t num_characteristics, mp_b return 0; } -int mp_bt_characteristic_value_set(mp_bt_characteristic_handle_t handle, const void *value, size_t value_len) { - esp_err_t err = esp_ble_gatts_set_attr_value(handle, value_len, value); +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); } @@ -289,10 +319,15 @@ int mp_bt_characteristic_value_set(mp_bt_characteristic_handle_t handle, const v return mp_bt_status_errno(); } -int mp_bt_characteristic_value_get(mp_bt_characteristic_handle_t handle, void *value, size_t *value_len) { +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(characteristic->service->esp_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(handle, &bt_len, &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); } @@ -363,8 +398,10 @@ STATIC void mp_bt_gap_callback(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_para 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); break; case ESP_GATTS_DISCONNECT_EVT: + mp_bt_disconnected(param->disconnect.conn_id); // restart advertisement mp_bt_advertise_start_internal(); break; @@ -391,6 +428,11 @@ STATIC void mp_bt_gatts_callback(esp_gatts_cb_event_t event, esp_gatt_if_t gatts 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; @@ -402,6 +444,9 @@ STATIC void mp_bt_gatts_callback(esp_gatts_cb_event_t event, esp_gatt_if_t gatts case ESP_GATTS_WRITE_EVT: // Characteristic value written by connected device. break; + case ESP_GATTS_CONF_EVT: + // Characteristic notify confirmation received. + break; default: ESP_LOGI("bluetooth", "GATTS: unknown event: %d", event); break; diff --git a/ports/esp32/bluetooth/bluetooth.h b/ports/esp32/bluetooth/bluetooth.h index fb787ca228f81..43b5063ef78dd 100644 --- a/ports/esp32/bluetooth/bluetooth.h +++ b/ports/esp32/bluetooth/bluetooth.h @@ -35,6 +35,10 @@ typedef esp_ble_adv_type_t mp_bt_adv_type_t; #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; diff --git a/ports/nrf/bluetooth/bluetooth.c b/ports/nrf/bluetooth/bluetooth.c index 71e2ec76079cf..0577f81464f63 100644 --- a/ports/nrf/bluetooth/bluetooth.c +++ b/ports/nrf/bluetooth/bluetooth.c @@ -116,6 +116,11 @@ STATIC int mp_bt_errno(uint32_t err_code) { 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 @@ -267,7 +272,11 @@ void mp_bt_advertise_stop(void) { 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); + break; case BLE_GAP_EVT_DISCONNECTED: + mp_bt_disconnected(p_ble_evt->evt.gap_evt.conn_handle); #if NRF51 mp_bt_advertise_start_internal(); #else @@ -341,21 +350,36 @@ int mp_bt_add_service(mp_bt_service_t *service, size_t num_characteristics, mp_b return 0; } -int mp_bt_characteristic_value_set(mp_bt_characteristic_handle_t handle, const void *value, size_t value_len) { +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, handle, &data); + 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_handle_t handle, void *value, size_t *value_len) { +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, handle, &data); + 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); } diff --git a/ports/nrf/bluetooth/bluetooth.h b/ports/nrf/bluetooth/bluetooth.h index 9ed705f5c2e6f..177a581dd84fa 100644 --- a/ports/nrf/bluetooth/bluetooth.h +++ b/ports/nrf/bluetooth/bluetooth.h @@ -40,12 +40,15 @@ typedef uint8_t mp_bt_adv_type_t; #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_MAX_ATTR_SIZE (20) +#define MP_BT_INVALID_CONN_HANDLE (BLE_CONN_HANDLE_INVALID) typedef ble_uuid_t mp_bt_uuid_t; From 727ed76f96eb83f74246d9faae539752566d70aa Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 4 Apr 2019 21:49:15 +0200 Subject: [PATCH 09/28] extmod/modbluetooth: Add write callbacks on characteristics. --- extmod/modbluetooth.c | 71 +++++++++++++++++++++++++++++-- extmod/modbluetooth.h | 10 +++++ ports/esp32/bluetooth/bluetooth.c | 1 + ports/nrf/bluetooth/bluetooth.c | 3 ++ py/mpstate.h | 8 ++++ 5 files changed, 89 insertions(+), 4 deletions(-) diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index 1f327e5e61d62..10099aab90134 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -26,6 +26,8 @@ #include "py/obj.h" #include "py/objstr.h" +#include "py/objarray.h" +#include "py/binary.h" #include "py/runtime.h" #include "extmod/modbluetooth.h" @@ -139,6 +141,25 @@ void mp_bt_disconnected(uint16_t conn_handle) { } } +// Call the registered callback for this characteristic, if one has been +// registered. +void mp_bt_characteristic_on_write(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) { + // 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, (void*)value}; + mp_call_function_2_protected(item->callback, MP_OBJ_FROM_PTR(item->characteristic), MP_OBJ_FROM_PTR(&ar)); + break; + } + 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); } @@ -382,11 +403,53 @@ STATIC mp_obj_t characteristic_read(mp_obj_t self_in) { } STATIC MP_DEFINE_CONST_FUN_OBJ_1(characteristic_read_obj, characteristic_read); +STATIC mp_obj_t characteristic_on_update(mp_obj_t self_in, mp_obj_t callback) { + if (callback != mp_const_none && !mp_obj_is_fun(callback)) { + mp_raise_ValueError("invalid callback"); + } + mp_bt_characteristic_t *characteristic = self_in; + + // 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; + } + 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; + } + break; + } + entry = &(*entry)->next; + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(characteristic_on_update_obj, characteristic_on_update); + 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_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_on_update), MP_ROM_PTR(&characteristic_on_update_obj) }, }; STATIC MP_DEFINE_CONST_DICT(characteristic_locals_dict, characteristic_locals_dict_table); diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index 19f1adf0ac212..a4f4229bcf077 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -49,6 +49,13 @@ typedef struct { 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; +} mp_bt_characteristic_callback_t; + // Enables the Bluetooth stack. Returns errno on failure. int mp_bt_enable(void); @@ -85,6 +92,9 @@ int mp_bt_characteristic_value_notify(mp_bt_characteristic_t *characteristic, ui // 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 value_handle, const void *value, size_t value_len); + // 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. diff --git a/ports/esp32/bluetooth/bluetooth.c b/ports/esp32/bluetooth/bluetooth.c index 4316667fc3455..a0415fb1005e8 100644 --- a/ports/esp32/bluetooth/bluetooth.c +++ b/ports/esp32/bluetooth/bluetooth.c @@ -443,6 +443,7 @@ STATIC void mp_bt_gatts_callback(esp_gatts_cb_event_t event, esp_gatt_if_t gatts break; case ESP_GATTS_WRITE_EVT: // Characteristic value written by connected device. + mp_bt_characteristic_on_write(param->write.handle, param->write.value, param->write.len); break; case ESP_GATTS_CONF_EVT: // Characteristic notify confirmation received. diff --git a/ports/nrf/bluetooth/bluetooth.c b/ports/nrf/bluetooth/bluetooth.c index 0577f81464f63..069137f8dda6e 100644 --- a/ports/nrf/bluetooth/bluetooth.c +++ b/ports/nrf/bluetooth/bluetooth.c @@ -289,6 +289,9 @@ static void ble_evt_handler(ble_evt_t * p_ble_evt) { 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.params.write.handle, &p_ble_evt->evt.gatts_evt.params.write.data, p_ble_evt->evt.gatts_evt.params.write.len); + break; } } diff --git a/py/mpstate.h b/py/mpstate.h index a9c2b32d66e28..14d6353db8912 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. @@ -217,6 +221,10 @@ typedef struct _mp_state_vm_t { // This is a global mutex used to make the VM/runtime thread-safe. mp_thread_mutex_t gil_mutex; #endif + + #if MICROPY_PY_BLUETOOTH + mp_bt_characteristic_callback_t *bt_characteristic_callbacks; + #endif } mp_state_vm_t; // This structure holds state that is specific to a given thread. From 47165a014199b01d8461262200d0cfaf20571c76 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 11 Apr 2019 00:40:28 +0200 Subject: [PATCH 10/28] extmod/modbluetooth: Run write callbacks on the scheduler. Running write callbacks on the main thread using the scheduler has the advantage that you can do heap allocations. This is especially useful for the ESP32 which cannot easily run some code in a separate thread. --- extmod/modbluetooth.c | 117 +++++++++++++++++++++++++++++++++++++-- ports/nrf/mpconfigport.h | 3 + 2 files changed, 114 insertions(+), 6 deletions(-) diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index 10099aab90134..c078099cda0eb 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -39,6 +39,39 @@ STATIC const mp_obj_type_t characteristic_type; STATIC volatile uint16_t active_connections[MP_BT_MAX_CONNECTED_DEVICES]; +// 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; @@ -141,6 +174,59 @@ void mp_bt_disconnected(uint16_t conn_handle) { } } +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; + if (update_buf.dropped_packets) { + // Handle dropped packet. + update_buf.dropped_packets--; + value_len = (size_t)-1; + } else { + // Copy regular incoming packet. + value_len = update_buf.data[tail++ % UPDATE_BUF_SIZE]; + if (value_len > sizeof(value)) { + update_buf.tail += value_len + 1; // skip this packet + mp_raise_ValueError("incoming BLE packet too big"); + } + for (size_t i = 0; i < value_len; i++) { + value[i] = update_buf.data[tail++ % UPDATE_BUF_SIZE]; + } + update_buf.tail = tail; + } + 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_call_function_2_protected(item->callback, MP_OBJ_FROM_PTR(item->characteristic), mp_const_none); + } 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_call_function_2_protected(item->callback, MP_OBJ_FROM_PTR(item->characteristic), MP_OBJ_FROM_PTR(&ar)); + } + + 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 value_handle, const void *value, size_t value_len) { @@ -149,12 +235,31 @@ void mp_bt_characteristic_on_write(uint16_t value_handle, const void *value, siz mp_bt_characteristic_callback_t *item = MP_STATE_PORT(bt_characteristic_callbacks); while (item != NULL) { if (item->characteristic->value_handle == value_handle) { - // 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, (void*)value}; - mp_call_function_2_protected(item->callback, MP_OBJ_FROM_PTR(item->characteristic), MP_OBJ_FROM_PTR(&ar)); - break; + // Queue callback. + 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; + } + + // 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)); + while (bytes_left < value_len + 1) { + // 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)value_len; + for (size_t i=0; inext; } diff --git a/ports/nrf/mpconfigport.h b/ports/nrf/mpconfigport.h index 214ea10678de7..b9c0059dc3629 100644 --- a/ports/nrf/mpconfigport.h +++ b/ports/nrf/mpconfigport.h @@ -43,6 +43,9 @@ #define MICROPY_READER_VFS (MICROPY_VFS) #define MICROPY_ENABLE_GC (1) #define MICROPY_ENABLE_FINALISER (1) +#if BLUETOOTH_SD +#define MICROPY_ENABLE_SCHEDULER (1) +#endif #define MICROPY_STACK_CHECK (1) #define MICROPY_HELPER_REPL (1) #define MICROPY_REPL_EMACS_KEYS (0) From 71a31f71d120f946cf0ca9e613d86f67c61ac9a0 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 22 Apr 2019 01:54:27 +0200 Subject: [PATCH 11/28] extmod/modbluetooth: Rename use .irq() name+signature for .on_update(). This makes the method a bit more compatible with the Hardware API: https://github.com/micropython/micropython/wiki/Hardware-API#irqs Another advantage is that we can now add other triggers besides 'on write', like 'on read'. --- extmod/modbluetooth.c | 32 ++++++++++++++++++++++++-------- extmod/modbluetooth.h | 4 ++++ 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index c078099cda0eb..abd4503436edb 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -235,6 +235,11 @@ void mp_bt_characteristic_on_write(uint16_t value_handle, const void *value, siz 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_BLE_IRQ_WRITE) == 0) { + // This callback should not be called for writes. + break; + } + // Queue callback. if (!mp_sched_schedule(MP_OBJ_FROM_PTR(&bluetooth_write_callback_obj), item->characteristic)) { // Failed to schedule a callback: the queue is full. @@ -508,11 +513,19 @@ STATIC mp_obj_t characteristic_read(mp_obj_t self_in) { } STATIC MP_DEFINE_CONST_FUN_OBJ_1(characteristic_read_obj, characteristic_read); -STATIC mp_obj_t characteristic_on_update(mp_obj_t self_in, mp_obj_t callback) { +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"); } - mp_bt_characteristic_t *characteristic = self_in; // A singly linked list of callbacks. In pseudocode: // If the new callback is none: @@ -529,6 +542,7 @@ STATIC mp_obj_t characteristic_on_update(mp_obj_t self_in, mp_obj_t callback) { *entry = m_new_obj(mp_bt_characteristic_callback_t); (*entry)->characteristic = characteristic; (*entry)->callback = callback; + (*entry)->triggers = args[ARG_trigger].u_int; } break; } @@ -540,6 +554,7 @@ STATIC mp_obj_t characteristic_on_update(mp_obj_t self_in, mp_obj_t callback) { } else { // update the entry with the new callback (*entry)->callback = callback; + (*entry)->triggers = args[ARG_trigger].u_int; } break; } @@ -547,14 +562,14 @@ STATIC mp_obj_t characteristic_on_update(mp_obj_t self_in, mp_obj_t callback) { } return mp_const_none; } -STATIC MP_DEFINE_CONST_FUN_OBJ_2(characteristic_on_update_obj, characteristic_on_update); +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_on_update), MP_ROM_PTR(&characteristic_on_update_obj) }, + { 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); @@ -588,6 +603,7 @@ STATIC const mp_rom_map_elem_t mp_module_bluetooth_globals_table[] = { { 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_WRITE), MP_ROM_INT(MP_BLE_IRQ_WRITE) }, }; STATIC MP_DEFINE_CONST_DICT(mp_module_bluetooth_globals, mp_module_bluetooth_globals_table); diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index a4f4229bcf077..e657f356dc81e 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -54,6 +54,7 @@ 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. @@ -123,3 +124,6 @@ mp_obj_t mp_bt_format_uuid_str(uint8_t *uuid); #define MP_BLE_FLAG_READ (1 << 1) #define MP_BLE_FLAG_WRITE (1 << 3) #define MP_BLE_FLAG_NOTIFY (1 << 4) + +// IRQ flags when to call a callback on a characteristic. +#define MP_BLE_IRQ_WRITE (1 << 1) From 20ae5a63cdf8f232c8c27bbaedb94f1b6e112c1d Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 22 Apr 2019 02:58:03 +0200 Subject: [PATCH 12/28] extmod/modbluetooth: Add .address() to print device address. It may be useful to know the current device address. This method provides a way to read the device address from the BLE stack. --- extmod/modbluetooth.c | 37 ++++++++++++++++++++++++++++++- extmod/modbluetooth.h | 5 ++++- ports/esp32/bluetooth/bluetooth.c | 9 ++++++++ ports/nrf/bluetooth/bluetooth.c | 10 +++++++++ 4 files changed, 59 insertions(+), 2 deletions(-) diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index abd4503436edb..63d47119acaf4 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -127,7 +127,7 @@ void mp_bt_parse_uuid_str(mp_obj_t obj, uint8_t *uuid) { // Format string UUID. Example output: // '6e400001-b5a3-f393-e0a9-e50e24dcca9e' -mp_obj_t mp_bt_format_uuid_str(uint8_t *uuid) { +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--) { @@ -154,6 +154,33 @@ mp_obj_t mp_bt_format_uuid_str(uint8_t *uuid) { return mp_obj_new_str(str, MP_ARRAY_SIZE(str)); } +// Format 6-byte MAC address. Example output: +// '11:22:33:AA:BB:CC' +STATIC mp_obj_t mp_bt_format_mac(const uint8_t *mac) { + char str[18]; // includes last ':' for convenience (e.g. '11:22:33:AA:BB:CC:') + char *s = str; + for (int i = 5; i >= 0; i--) { + char nibble = mac[i] >> 4; + if (nibble >= 10) { + nibble += 'A' - 10; + } else { + nibble += '0'; + } + *(s++) = nibble; + + nibble = mac[i] & 0xf; + if (nibble >= 10) { + nibble += 'A' - 10; + } else { + nibble += '0'; + } + *(s++) = nibble; + + *(s++) = ':'; + } + return mp_obj_new_str(str, MP_ARRAY_SIZE(str)-1); +} + // Add this connection handle to the list of connected centrals. void mp_bt_connected(uint16_t conn_handle) { for (size_t i = 0; i < MP_BT_MAX_CONNECTED_DEVICES; i++) { @@ -421,6 +448,13 @@ STATIC mp_obj_t bluetooth_add_service(size_t n_args, const mp_obj_t *pos_args, m } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(bluetooth_add_service_obj, 1, bluetooth_add_service); +STATIC mp_obj_t bluetooth_address(mp_obj_t self_in) { + uint8_t address[6]; + mp_bt_get_address(address); + return mp_bt_format_mac(address); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(bluetooth_address_obj, bluetooth_address); + 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); @@ -585,6 +619,7 @@ STATIC const mp_rom_map_elem_t bluetooth_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_advertise), MP_ROM_PTR(&bluetooth_advertise_obj) }, { MP_ROM_QSTR(MP_QSTR_advertise_raw), MP_ROM_PTR(&bluetooth_advertise_raw_obj) }, { MP_ROM_QSTR(MP_QSTR_add_service), MP_ROM_PTR(&bluetooth_add_service_obj) }, + { MP_ROM_QSTR(MP_QSTR_address), MP_ROM_PTR(&bluetooth_address_obj) }, }; STATIC MP_DEFINE_CONST_DICT(bluetooth_locals_dict, bluetooth_locals_dict_table); diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index e657f356dc81e..a036ba7dc28f1 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -66,6 +66,9 @@ 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); @@ -110,7 +113,7 @@ mp_obj_t mp_bt_format_uuid(mp_bt_uuid_t *uuid); 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(uint8_t *uuid); +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) diff --git a/ports/esp32/bluetooth/bluetooth.c b/ports/esp32/bluetooth/bluetooth.c index a0415fb1005e8..60711e0e38b93 100644 --- a/ports/esp32/bluetooth/bluetooth.c +++ b/ports/esp32/bluetooth/bluetooth.c @@ -28,6 +28,7 @@ #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" @@ -135,6 +136,14 @@ 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, diff --git a/ports/nrf/bluetooth/bluetooth.c b/ports/nrf/bluetooth/bluetooth.c index 069137f8dda6e..1377f2a118d81 100644 --- a/ports/nrf/bluetooth/bluetooth.c +++ b/ports/nrf/bluetooth/bluetooth.c @@ -205,6 +205,16 @@ bool mp_bt_is_enabled(void) { 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; From 1e4d96f5b26298147ba44c504fa58ddbc9f3349f Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 29 Apr 2019 13:53:59 +0200 Subject: [PATCH 13/28] extmod/modbluetooth: better handling of big BLE packets --- extmod/modbluetooth.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index 63d47119acaf4..768956ac85bad 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -216,14 +216,14 @@ STATIC mp_obj_t bluetooth_write_callback(mp_obj_t char_in) { } else { // Copy regular incoming packet. value_len = update_buf.data[tail++ % UPDATE_BUF_SIZE]; + update_buf.tail = tail + value_len; if (value_len > sizeof(value)) { - update_buf.tail += value_len + 1; // skip this packet - mp_raise_ValueError("incoming BLE packet too big"); + // Packet was too big, only pass the first N bytes. + value_len = sizeof(value); } for (size_t i = 0; i < value_len; i++) { value[i] = update_buf.data[tail++ % UPDATE_BUF_SIZE]; } - update_buf.tail = tail; } MICROPY_END_ATOMIC_SECTION(atomic_state); From 48043bc32c60bf9d0bac9b559b182ea371e8e100 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 29 Apr 2019 14:00:19 +0200 Subject: [PATCH 14/28] extmod/modbluetooth: make write IRQs work for esp32 and nrf --- extmod/modbluetooth.c | 22 ++++++++++++++-------- ports/nrf/mpconfigport.h | 3 --- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index 768956ac85bad..5525995e373a7 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -30,6 +30,7 @@ #include "py/binary.h" #include "py/runtime.h" #include "extmod/modbluetooth.h" +#include #if MICROPY_PY_BLUETOOTH @@ -267,13 +268,6 @@ void mp_bt_characteristic_on_write(uint16_t value_handle, const void *value, siz break; } - // Queue callback. - 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; - } - // Insert packet into queue. uint16_t head = update_buf.head; uint16_t tail = update_buf.tail; @@ -287,10 +281,22 @@ void mp_bt_characteristic_on_write(uint16_t value_handle, const void *value, siz update_buf.dropped_packets++; } update_buf.data[head++ % UPDATE_BUF_SIZE] = (uint8_t)value_len; - for (size_t i=0; icharacteristic)) { + // 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; diff --git a/ports/nrf/mpconfigport.h b/ports/nrf/mpconfigport.h index b9c0059dc3629..214ea10678de7 100644 --- a/ports/nrf/mpconfigport.h +++ b/ports/nrf/mpconfigport.h @@ -43,9 +43,6 @@ #define MICROPY_READER_VFS (MICROPY_VFS) #define MICROPY_ENABLE_GC (1) #define MICROPY_ENABLE_FINALISER (1) -#if BLUETOOTH_SD -#define MICROPY_ENABLE_SCHEDULER (1) -#endif #define MICROPY_STACK_CHECK (1) #define MICROPY_HELPER_REPL (1) #define MICROPY_REPL_EMACS_KEYS (0) From e3f5d5395cef0882e446bf6116f000c0e24b1b94 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 29 Apr 2019 14:29:42 +0200 Subject: [PATCH 15/28] extmod/modbluetooth: implement connect and disconnect events --- extmod/modbluetooth.c | 118 +++++++++++++++++++++++++++++- extmod/modbluetooth.h | 17 ++++- ports/esp32/bluetooth/bluetooth.c | 4 +- ports/nrf/bluetooth/bluetooth.c | 4 +- py/mpstate.h | 5 ++ 5 files changed, 136 insertions(+), 12 deletions(-) diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index 5525995e373a7..3eb1422581848 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -35,11 +35,20 @@ #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 @@ -183,23 +192,63 @@ STATIC mp_obj_t mp_bt_format_mac(const uint8_t *mac) { } // Add this connection handle to the list of connected centrals. -void mp_bt_connected(uint16_t conn_handle) { +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) { +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) { @@ -263,7 +312,7 @@ void mp_bt_characteristic_on_write(uint16_t value_handle, const void *value, siz 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_BLE_IRQ_WRITE) == 0) { + if ((item->triggers & MP_BT_IRQ_WRITE) == 0) { // This callback should not be called for writes. break; } @@ -461,6 +510,63 @@ STATIC mp_obj_t bluetooth_address(mp_obj_t self_in) { } STATIC MP_DEFINE_CONST_FUN_OBJ_1(bluetooth_address_obj, bluetooth_address); +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_address(mp_obj_t self_in) { + mp_bt_device_t *device = self_in; + bool has_address = false; + for (int i = 0; i < 6; i++) { + if (device->address[i] != 0) { + has_address = true; + } + } + if (has_address) { + return mp_bt_format_mac(device->address); + } else { + return mp_const_none; + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(device_address_obj, device_address); + +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 const mp_rom_map_elem_t device_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_address), MP_ROM_PTR(&device_address_obj) }, + { MP_ROM_QSTR(MP_QSTR_connected), MP_ROM_PTR(&device_connected_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); @@ -626,6 +732,7 @@ STATIC const mp_rom_map_elem_t bluetooth_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_advertise_raw), MP_ROM_PTR(&bluetooth_advertise_raw_obj) }, { MP_ROM_QSTR(MP_QSTR_add_service), MP_ROM_PTR(&bluetooth_add_service_obj) }, { MP_ROM_QSTR(MP_QSTR_address), MP_ROM_PTR(&bluetooth_address_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); @@ -639,12 +746,15 @@ STATIC const mp_obj_type_t bluetooth_type = { 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_WRITE), MP_ROM_INT(MP_BLE_IRQ_WRITE) }, + { 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); diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index a036ba7dc28f1..79124e8ecb315 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -30,6 +30,13 @@ #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; @@ -77,10 +84,10 @@ int mp_bt_advertise_start(mp_bt_adv_type_t type, uint16_t interval, const uint8_ void mp_bt_advertise_stop(void); // Call this when a central disconnects. -void mp_bt_connected(uint16_t conn_handle); +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); +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); @@ -128,5 +135,7 @@ mp_obj_t mp_bt_format_uuid_str(const uint8_t *uuid); #define MP_BLE_FLAG_WRITE (1 << 3) #define MP_BLE_FLAG_NOTIFY (1 << 4) -// IRQ flags when to call a callback on a characteristic. -#define MP_BLE_IRQ_WRITE (1 << 1) +// 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/bluetooth/bluetooth.c b/ports/esp32/bluetooth/bluetooth.c index 60711e0e38b93..a5fc091278a33 100644 --- a/ports/esp32/bluetooth/bluetooth.c +++ b/ports/esp32/bluetooth/bluetooth.c @@ -407,10 +407,10 @@ STATIC void mp_bt_gap_callback(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_para 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); + mp_bt_connected(param->connect.conn_id, param->connect.remote_bda); break; case ESP_GATTS_DISCONNECT_EVT: - mp_bt_disconnected(param->disconnect.conn_id); + mp_bt_disconnected(param->disconnect.conn_id, param->disconnect.remote_bda); // restart advertisement mp_bt_advertise_start_internal(); break; diff --git a/ports/nrf/bluetooth/bluetooth.c b/ports/nrf/bluetooth/bluetooth.c index 1377f2a118d81..401ed6e27502f 100644 --- a/ports/nrf/bluetooth/bluetooth.c +++ b/ports/nrf/bluetooth/bluetooth.c @@ -283,10 +283,10 @@ void mp_bt_advertise_stop(void) { 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); + 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); + mp_bt_disconnected(p_ble_evt->evt.gap_evt.conn_handle, NULL); #if NRF51 mp_bt_advertise_start_internal(); #else diff --git a/py/mpstate.h b/py/mpstate.h index 14d6353db8912..617f0df1cb078 100644 --- a/py/mpstate.h +++ b/py/mpstate.h @@ -223,7 +223,12 @@ typedef struct _mp_state_vm_t { #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 } mp_state_vm_t; From fbfd9aa119083ee4b4e850b756ecf0bb83ce72b0 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 29 Apr 2019 16:05:07 +0200 Subject: [PATCH 16/28] extmod/modbluetooth: fix multiple services One application profile does not map to a single service, but it really maps to a single application. This resulted in a number of connect/disconnect events equal to the number of registered services, which of course is wrong unless exactly one service is registered. Make sure we create one application profile at start and use that the whole time. --- extmod/modbluetooth.h | 3 --- ports/esp32/bluetooth/bluetooth.c | 36 ++++++++++++------------------- 2 files changed, 14 insertions(+), 25 deletions(-) diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index 79124e8ecb315..1c2a2805683c9 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -41,9 +41,6 @@ typedef struct { mp_obj_base_t base; mp_bt_uuid_t uuid; mp_bt_service_handle_t handle; - #if ESP_PLATFORM - uint8_t esp_gatts_if; /* Only for ESP-IDF */ - #endif } mp_bt_service_t; // A characteristic. diff --git a/ports/esp32/bluetooth/bluetooth.c b/ports/esp32/bluetooth/bluetooth.c index a5fc091278a33..6300879f6e93b 100644 --- a/ports/esp32/bluetooth/bluetooth.c +++ b/ports/esp32/bluetooth/bluetooth.c @@ -51,7 +51,7 @@ STATIC union { STATIC mp_bt_adv_type_t bluetooth_adv_type; STATIC uint16_t bluetooth_adv_interval; -STATIC uint16_t bluetooth_app_id = 0; // provide unique number for each application profile +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); @@ -122,6 +122,17 @@ int mp_bt_enable(void) { 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; } @@ -202,25 +213,6 @@ void mp_bt_advertise_stop(void) { } int mp_bt_add_service(mp_bt_service_t *service, size_t num_characteristics, mp_bt_characteristic_t **characteristics) { - // In ESP-IDF, a service is more than just a service, it's an - // "application profile". One application profile contains exactly one - // service. For details, see: - // https://github.com/espressif/esp-idf/blob/master/examples/bluetooth/gatt_server/tutorial/Gatt_Server_Example_Walkthrough.md - - // Register an application profile. - esp_err_t err = esp_ble_gatts_app_register(bluetooth_app_id); - if (err != 0) { - return mp_bt_esp_errno(err); - } - bluetooth_app_id++; - // Wait for ESP_GATTS_REG_EVT - xSemaphoreTake(mp_bt_call_complete, portMAX_DELAY); - if (mp_bt_call_status != 0) { - return mp_bt_status_errno(); - } - esp_gatt_if_t gatts_if = mp_bt_call_result.gatts_if; - service->esp_gatts_if = gatts_if; - // 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. @@ -237,7 +229,7 @@ int mp_bt_add_service(mp_bt_service_t *service, size_t num_characteristics, mp_b bluetooth_service_id.is_primary = true; bluetooth_service_id.id.inst_id = 0; bluetooth_service_id.id.uuid = service->uuid; - err = esp_ble_gatts_create_service(service->esp_gatts_if, &bluetooth_service_id, num_handle); + 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); } @@ -329,7 +321,7 @@ int mp_bt_characteristic_value_set(mp_bt_characteristic_t *characteristic, const } 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(characteristic->service->esp_gatts_if, conn_handle, characteristic->value_handle, value_len, (void*)value, false); + 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); } From 9d68c507fd20cf6511d72cc655ee4df7f3ac8969 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 29 Apr 2019 17:13:11 +0200 Subject: [PATCH 17/28] extmod/modbluetooth: implement device.disconnect() This commit allows the Python code to explicitly disconnect a central device using device objects from connect or write events. --- extmod/modbluetooth.c | 55 ++++++++++++++++++++++++++----- extmod/modbluetooth.h | 5 ++- ports/esp32/bluetooth/bluetooth.c | 7 +++- ports/nrf/bluetooth/bluetooth.c | 8 ++++- 4 files changed, 63 insertions(+), 12 deletions(-) diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index 3eb1422581848..241d79c8cd2c7 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -259,18 +259,22 @@ STATIC mp_obj_t bluetooth_write_callback(mp_obj_t char_in) { 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. - value_len = update_buf.data[tail++ % UPDATE_BUF_SIZE]; - update_buf.tail = tail + value_len; + 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]; } @@ -292,12 +296,25 @@ STATIC mp_obj_t bluetooth_write_callback(mp_obj_t char_in) { if (value_len == (size_t)-1) { // Unfortunately, there was a dropped packet. // Report this event by passing None. - mp_call_function_2_protected(item->callback, MP_OBJ_FROM_PTR(item->characteristic), mp_const_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_call_function_2_protected(item->callback, MP_OBJ_FROM_PTR(item->characteristic), MP_OBJ_FROM_PTR(&ar)); + 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; @@ -306,7 +323,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(bluetooth_write_callback_obj, bluetooth_write_c // Call the registered callback for this characteristic, if one has been // registered. -void mp_bt_characteristic_on_write(uint16_t value_handle, const void *value, size_t value_len) { +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); @@ -321,7 +338,12 @@ void mp_bt_characteristic_on_write(uint16_t value_handle, const void *value, siz uint16_t head = update_buf.head; uint16_t tail = update_buf.tail; size_t bytes_left = ((uint16_t)UPDATE_BUF_SIZE - (head - tail)); - while (bytes_left < value_len + 1) { + // 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; @@ -329,7 +351,9 @@ void mp_bt_characteristic_on_write(uint16_t value_handle, const void *value, siz bytes_left = ((uint16_t)UPDATE_BUF_SIZE - (head - tail)); update_buf.dropped_packets++; } - update_buf.data[head++ % UPDATE_BUF_SIZE] = (uint8_t)value_len; + 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]; } @@ -555,9 +579,22 @@ STATIC mp_obj_t device_connected(mp_obj_t self_in) { } 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_address), MP_ROM_PTR(&device_address_obj) }, - { MP_ROM_QSTR(MP_QSTR_connected), MP_ROM_PTR(&device_connected_obj) }, + { MP_ROM_QSTR(MP_QSTR_address), MP_ROM_PTR(&device_address_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); diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index 1c2a2805683c9..bbdb5a7f67022 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -101,7 +101,10 @@ int mp_bt_characteristic_value_notify(mp_bt_characteristic_t *characteristic, ui 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 value_handle, const void *value, size_t value_len); +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 diff --git a/ports/esp32/bluetooth/bluetooth.c b/ports/esp32/bluetooth/bluetooth.c index 6300879f6e93b..cb661092dcf0f 100644 --- a/ports/esp32/bluetooth/bluetooth.c +++ b/ports/esp32/bluetooth/bluetooth.c @@ -340,6 +340,11 @@ int mp_bt_characteristic_value_get(mp_bt_characteristic_t *characteristic, void 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)) { @@ -444,7 +449,7 @@ STATIC void mp_bt_gatts_callback(esp_gatts_cb_event_t event, esp_gatt_if_t gatts break; case ESP_GATTS_WRITE_EVT: // Characteristic value written by connected device. - mp_bt_characteristic_on_write(param->write.handle, param->write.value, param->write.len); + 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. diff --git a/ports/nrf/bluetooth/bluetooth.c b/ports/nrf/bluetooth/bluetooth.c index 401ed6e27502f..ce958417a712b 100644 --- a/ports/nrf/bluetooth/bluetooth.c +++ b/ports/nrf/bluetooth/bluetooth.c @@ -37,6 +37,7 @@ #include "nrf_sdm.h" #include "ble.h" +#include "ble_hci.h" #if !NRF51 #include "nrf_nvic.h" #endif @@ -300,7 +301,7 @@ static void ble_evt_handler(ble_evt_t * p_ble_evt) { break; #endif case BLE_GATTS_EVT_WRITE: - mp_bt_characteristic_on_write(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); + 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; } } @@ -397,6 +398,11 @@ int mp_bt_characteristic_value_get(mp_bt_characteristic_t *characteristic, void 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)) { From 215a658f2cf689f88b0b9365ac9e4322dbc7a0aa Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 7 Jun 2019 00:28:39 +0200 Subject: [PATCH 18/28] extmod/modbluetooth: Merge advertise_raw() into advertise(). As requested by Damien. --- extmod/modbluetooth.c | 88 ++++++++++++++----------------------------- 1 file changed, 28 insertions(+), 60 deletions(-) diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index 241d79c8cd2c7..89b9426b18839 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -399,10 +399,12 @@ STATIC mp_obj_t bluetooth_active(size_t n_args, const mp_obj_t *args) { 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_connectable }; + 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)]; @@ -423,77 +425,44 @@ STATIC mp_obj_t bluetooth_advertise(size_t n_args, const mp_obj_t *pos_args, mp_ adv_type = MP_BT_ADV_TYPE_ADV_NONCONN_IND; // connectable=False } - size_t name_len; - const char *name = NULL; - if (args[ARG_name].u_obj != mp_const_none) { - name = mp_obj_str_get_data(args[ARG_name].u_obj, &name_len); - } - - uint8_t adv_data[31]; + uint8_t adv_data_buf[31]; + const uint8_t *adv_data = NULL; size_t adv_data_len = 0; - if (name != NULL) { - adv_data[adv_data_len++] = 2; // 1 byte type + 1 byte flags data - adv_data[adv_data_len++] = MP_BLE_GAP_AD_TYPE_FLAG; - adv_data[adv_data_len++] = MP_BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE; - - if (name_len + 3 > sizeof(adv_data) - adv_data_len) { + // 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[adv_data_len++] = name_len + 1; - adv_data[adv_data_len++] = MP_BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME; + 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; i 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 - } - - size_t adv_data_len; - const uint8_t *adv_data = NULL; - if (args[ARG_adv_data].u_obj != mp_const_none) { + adv_data = adv_data_buf; + } else if (args[ARG_adv_data].u_obj != mp_const_none) { + // Base the advertisement on the raw advertisement data field. adv_data = (const uint8_t*)mp_obj_str_get_data(args[ARG_adv_data].u_obj, &adv_data_len); } - size_t sr_data_len; - const uint8_t *sr_data = NULL; - if (args[ARG_sr_data].u_obj != mp_const_none) { - sr_data = (const uint8_t*)mp_obj_str_get_data(args[ARG_sr_data].u_obj, &sr_data_len); + // Pick scan response data, if provided. + const uint8_t *resp_data = NULL; + size_t resp_data_len = 0; + if (args[ARG_resp_data].u_obj != mp_const_none) { + resp_data = (const uint8_t*)mp_obj_str_get_data(args[ARG_resp_data].u_obj, &resp_data_len); } - int errno_ = mp_bt_advertise_start(adv_type, interval, adv_data_len ? adv_data : NULL, adv_data_len, sr_data_len ? sr_data : NULL, sr_data_len); + int errno_ = mp_bt_advertise_start(adv_type, interval, adv_data_len ? adv_data : NULL, adv_data_len, resp_data, resp_data_len); return bluetooth_handle_errno(errno_); } -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(bluetooth_advertise_raw_obj, 1, bluetooth_advertise_raw); +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(bluetooth_advertise_obj, 1, bluetooth_advertise); STATIC mp_obj_t bluetooth_add_service(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_uuid, ARG_characteristics }; @@ -766,7 +735,6 @@ STATIC const mp_obj_type_t characteristic_type = { 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_advertise_raw), MP_ROM_PTR(&bluetooth_advertise_raw_obj) }, { MP_ROM_QSTR(MP_QSTR_add_service), MP_ROM_PTR(&bluetooth_add_service_obj) }, { MP_ROM_QSTR(MP_QSTR_address), MP_ROM_PTR(&bluetooth_address_obj) }, { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&bluetooth_irq_obj) }, From f93d08ba3bcf94872a4af5a0cfb8f0175fae9e32 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 12 Jun 2019 23:03:20 +0200 Subject: [PATCH 19/28] nrf: Enable ubinascii module. This costs about 1kB in code size but is very useful to convert MAC addresses to hex format: MAC addresses will be returned in raw form in a future commit. --- ports/nrf/mpconfigport.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/nrf/mpconfigport.h b/ports/nrf/mpconfigport.h index 214ea10678de7..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) From 7c5aed0c464286a33040a280d9952e637beba620 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 12 Jun 2019 23:25:34 +0200 Subject: [PATCH 20/28] extmod/modbluetooth: Expose raw MAC as .config('mac'). Instead of returning a human-readable string with .address() on both the Bluetooth and Device object, return a raw MAC address with a .config('mac') call. This is more extensible and more in line with how the network module does it. The MAC address can easily be converted back to hex format with the ubinascii module. --- extmod/modbluetooth.c | 74 ++++++++++++++++++------------------------- 1 file changed, 30 insertions(+), 44 deletions(-) diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index 89b9426b18839..1fd6b78daa119 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -29,6 +29,7 @@ #include "py/objarray.h" #include "py/binary.h" #include "py/runtime.h" +#include "py/qstr.h" #include "extmod/modbluetooth.h" #include @@ -164,33 +165,6 @@ mp_obj_t mp_bt_format_uuid_str(const uint8_t *uuid) { return mp_obj_new_str(str, MP_ARRAY_SIZE(str)); } -// Format 6-byte MAC address. Example output: -// '11:22:33:AA:BB:CC' -STATIC mp_obj_t mp_bt_format_mac(const uint8_t *mac) { - char str[18]; // includes last ':' for convenience (e.g. '11:22:33:AA:BB:CC:') - char *s = str; - for (int i = 5; i >= 0; i--) { - char nibble = mac[i] >> 4; - if (nibble >= 10) { - nibble += 'A' - 10; - } else { - nibble += '0'; - } - *(s++) = nibble; - - nibble = mac[i] & 0xf; - if (nibble >= 10) { - nibble += 'A' - 10; - } else { - nibble += '0'; - } - *(s++) = nibble; - - *(s++) = ':'; - } - return mp_obj_new_str(str, MP_ARRAY_SIZE(str)-1); -} - // 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++) { @@ -496,12 +470,18 @@ STATIC mp_obj_t bluetooth_add_service(size_t n_args, const mp_obj_t *pos_args, m } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(bluetooth_add_service_obj, 1, bluetooth_add_service); -STATIC mp_obj_t bluetooth_address(mp_obj_t self_in) { - uint8_t address[6]; - mp_bt_get_address(address); - return mp_bt_format_mac(address); +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_1(bluetooth_address_obj, bluetooth_address); +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 }; @@ -526,21 +506,27 @@ STATIC mp_obj_t bluetooth_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(bluetooth_irq_obj, 1, bluetooth_irq); -STATIC mp_obj_t device_address(mp_obj_t self_in) { +STATIC mp_obj_t device_config(mp_obj_t self_in, mp_obj_t param) { mp_bt_device_t *device = self_in; - bool has_address = false; - for (int i = 0; i < 6; i++) { - if (device->address[i] != 0) { - has_address = true; + 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; } } - if (has_address) { - return mp_bt_format_mac(device->address); - } else { - return mp_const_none; + default: + mp_raise_ValueError("unknown config param"); } } -STATIC MP_DEFINE_CONST_FUN_OBJ_1(device_address_obj, device_address); +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; @@ -561,7 +547,7 @@ STATIC mp_obj_t device_disconnect(mp_obj_t self_in) { 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_address), MP_ROM_PTR(&device_address_obj) }, + { 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) }, }; @@ -736,7 +722,7 @@ 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_address), MP_ROM_PTR(&bluetooth_address_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); From 3e92b4b8699e903df14e4d123b41ba3e0ac2d913 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 4 Jul 2019 18:00:40 +1000 Subject: [PATCH 21/28] stm32/cywbt: Add low-level CYW43xx Bluetooth HCI UART driver. --- ports/stm32/Makefile | 2 +- ports/stm32/cywbt.c | 251 +++++++++++++++++++++++++++++++++++++++++++ ports/stm32/cywbt.h | 35 ++++++ 3 files changed, 287 insertions(+), 1 deletion(-) create mode 100644 ports/stm32/cywbt.c create mode 100644 ports/stm32/cywbt.h diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index 3967e631170d2..431a8b5c93b64 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -355,7 +355,7 @@ SRC_USBDEV = $(addprefix $(USBDEV_DIR)/,\ ifeq ($(MICROPY_PY_NETWORK_CYW43),1) CFLAGS_MOD += -DMICROPY_PY_NETWORK_CYW43=1 -SRC_C += sdio.c +SRC_C += sdio.c cywbt.c EXTMOD_SRC_C += extmod/network_cyw43.c DRIVERS_SRC_C += drivers/cyw43/cyw43_ctrl.c drivers/cyw43/cyw43_lwip.c LIBS += $(TOP)/drivers/cyw43/libcyw43.a diff --git a/ports/stm32/cywbt.c b/ports/stm32/cywbt.c new file mode 100644 index 0000000000000..779a33f23501c --- /dev/null +++ b/ports/stm32/cywbt.c @@ -0,0 +1,251 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Damien P. George + * + * 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 + +#include "py/runtime.h" +#include "py/mphal.h" +#include "pin_static_af.h" +#include "uart.h" +#include "cywbt.h" + +#if MICROPY_PY_NETWORK_CYW43 + +extern const char fw_4343WA1_7_45_98_50_start; +#define CYWBT_FW_ADDR (&fw_4343WA1_7_45_98_50_start + 749 * 512 + 29 * 256) + +/******************************************************************************/ +// UART + +pyb_uart_obj_t cywbt_hci_uart_obj; +static uint8_t hci_uart_rxbuf[512]; + +static int uart_init_(void) { + cywbt_hci_uart_obj.base.type = &pyb_uart_type; + cywbt_hci_uart_obj.uart_id = 6; + cywbt_hci_uart_obj.is_static = true; + cywbt_hci_uart_obj.timeout = 2; + cywbt_hci_uart_obj.timeout_char = 2; + MP_STATE_PORT(pyb_uart_obj_all)[cywbt_hci_uart_obj.uart_id - 1] = &cywbt_hci_uart_obj; + uart_init(&cywbt_hci_uart_obj, 115200, UART_WORDLENGTH_8B, UART_PARITY_NONE, UART_STOPBITS_1, UART_HWCONTROL_RTS | UART_HWCONTROL_CTS); + uart_set_rxbuf(&cywbt_hci_uart_obj, sizeof(hci_uart_rxbuf), hci_uart_rxbuf); + return 0; +} + +static int uart_set_baudrate(uint32_t baudrate) { + uart_init(&cywbt_hci_uart_obj, baudrate, UART_WORDLENGTH_8B, UART_PARITY_NONE, UART_STOPBITS_1, UART_HWCONTROL_RTS | UART_HWCONTROL_CTS); + uart_set_rxbuf(&cywbt_hci_uart_obj, sizeof(hci_uart_rxbuf), hci_uart_rxbuf); + return 0; +} + +/******************************************************************************/ +// CYW BT HCI low-level driver + +uint8_t cywbt_hci_cmd_buf[4 + 256]; + +static void cywbt_wait_cts_low(void) { + mp_hal_pin_config(pyb_pin_BT_CTS, MP_HAL_PIN_MODE_INPUT, MP_HAL_PIN_PULL_UP, 0); + for (int i = 0; i < 200; ++i) { + if (mp_hal_pin_read(pyb_pin_BT_CTS) == 0) { + break; + } + mp_hal_delay_ms(1); + } + mp_hal_pin_config_alt_static(pyb_pin_BT_CTS, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_UP, STATIC_AF_USART6_CTS); +} + +static int cywbt_hci_cmd_raw(size_t len, uint8_t *buf) { + uart_tx_strn(&cywbt_hci_uart_obj, (void*)buf, len); + for (int i = 0; i < 6; ++i) { + while (!uart_rx_any(&cywbt_hci_uart_obj)) { + MICROPY_EVENT_POLL_HOOK + } + buf[i] = uart_rx_char(&cywbt_hci_uart_obj); + } + + // expect a comand complete event (event 0x0e) + if (buf[0] != 0x04 || buf[1] != 0x0e) { + printf("unknown response: %02x %02x %02x %02x\n", buf[0], buf[1], buf[2], buf[3]); + return -1; + } + + /* + if buf[3:6] != cmd[:3]: + print('response doesn\'t match cmd:', cmd, ev) + return b'' + */ + + int sz = buf[2] - 3; + for (int i = 0; i < sz; ++i) { + while (!uart_rx_any(&cywbt_hci_uart_obj)) { + MICROPY_EVENT_POLL_HOOK + } + buf[i] = uart_rx_char(&cywbt_hci_uart_obj); + } + + return 0; +} + +static int cywbt_hci_cmd(int ogf, int ocf, size_t param_len, const uint8_t *param_buf) { + uint8_t *buf = cywbt_hci_cmd_buf; + buf[0] = 0x01; + buf[1] = ocf; + buf[2] = ogf << 2 | ocf >> 8; + buf[3] = param_len; + if (param_len) { + memcpy(buf + 4, param_buf, param_len); + } + return cywbt_hci_cmd_raw(4 + param_len, buf); +} + +static void put_le16(uint8_t *buf, uint16_t val) { + buf[0] = val; + buf[1] = val >> 8; +} + +static void put_le32(uint8_t *buf, uint32_t val) { + buf[0] = val; + buf[1] = val >> 8; + buf[2] = val >> 16; + buf[3] = val >> 24; +} + +static int cywbt_set_baudrate(uint32_t baudrate) { + uint8_t buf[6]; + put_le16(buf, 0); + put_le32(buf + 2, baudrate); + return cywbt_hci_cmd(0x3f, 0x18, 6, buf); +} + +// download firmware +static int cywbt_download_firmware(const uint8_t *firmware) { + cywbt_hci_cmd(0x3f, 0x2e, 0, NULL); + + uint32_t t0 = mp_hal_ticks_ms(); + bool last_packet = false; + while (!last_packet) { + uint8_t *buf = cywbt_hci_cmd_buf; + memcpy(buf + 1, firmware, 3); + firmware += 3; + last_packet = buf[1] == 0x4e; + if (buf[2] != 0xfc) { + printf("fail1 %02x\n", buf[2]); + break; + } + uint8_t len = buf[3]; + + memcpy(buf + 4, firmware, len); + firmware += len; + + buf[0] = 1; + cywbt_hci_cmd_raw(4 + len, buf); + if (buf[0] != 0) { + printf("fail3 %02x\n", buf[0]); + break; + } + } + uint32_t t1 = mp_hal_ticks_ms(); + + printf("downloaded firmware patch: %u ms\n", (uint)(t1 - t0)); + + // RF switch must select high path during BT patch boot + mp_hal_pin_config(pyb_pin_WL_GPIO_1, MP_HAL_PIN_MODE_INPUT, MP_HAL_PIN_PULL_UP, 0); + mp_hal_delay_ms(10); // give some time for CTS to go high + cywbt_wait_cts_low(); + mp_hal_pin_config(pyb_pin_WL_GPIO_1, MP_HAL_PIN_MODE_INPUT, MP_HAL_PIN_PULL_DOWN, 0); // Select chip antenna (could also select external) + + uart_set_baudrate(115200); + cywbt_set_baudrate(3000000); + uart_set_baudrate(3000000); + + return 0; +} + +int cywbt_init(void) { + uart_init_(); + + mp_hal_pin_output(pyb_pin_BT_REG_ON); + mp_hal_pin_low(pyb_pin_BT_REG_ON); + mp_hal_pin_input(pyb_pin_BT_HOST_WAKE); + mp_hal_pin_output(pyb_pin_BT_DEV_WAKE); + mp_hal_pin_low(pyb_pin_BT_DEV_WAKE); + + // TODO don't select antenna if wifi is enabled + mp_hal_pin_config(pyb_pin_WL_GPIO_4, MP_HAL_PIN_MODE_OUTPUT, MP_HAL_PIN_PULL_NONE, 0); // RF-switch power + mp_hal_pin_high(pyb_pin_WL_GPIO_4); // Turn the RF-switch on + + return 0; +} + +int cywbt_activate(void) { + uint8_t buf[256]; + + mp_hal_pin_low(pyb_pin_BT_REG_ON); + uart_set_baudrate(115200); + mp_hal_delay_ms(100); + mp_hal_pin_high(pyb_pin_BT_REG_ON); + cywbt_wait_cts_low(); + + // Reset + cywbt_hci_cmd(0x03, 0x0003, 0, NULL); + + // Change baudrate + cywbt_set_baudrate(3000000); + uart_set_baudrate(3000000); + + cywbt_download_firmware((const uint8_t*)CYWBT_FW_ADDR); + + // Reset + cywbt_hci_cmd(0x03, 0x0003, 0, NULL); + + // Set BD_ADDR (sent as little endian) + uint8_t bdaddr[6]; + mp_hal_get_mac(MP_HAL_MAC_BDADDR, bdaddr); + buf[0] = bdaddr[5]; + buf[1] = bdaddr[4]; + buf[2] = bdaddr[3]; + buf[3] = bdaddr[2]; + buf[4] = bdaddr[1]; + buf[5] = bdaddr[0]; + cywbt_hci_cmd(0x3f, 0x0001, 6, buf); + + // Set local name + memset(buf, 0, 248); + memcpy(buf, "PYBD-BLE", 8); + cywbt_hci_cmd(0x03, 0x0013, 248, buf); + + // Configure sleep mode + cywbt_hci_cmd(0x3f, 0x27, 12, (const uint8_t*)"\x01\x02\x02\x00\x00\x00\x01\x00\x00\x00\x00\x00"); + + // HCI_Write_LE_Host_Support + cywbt_hci_cmd(3, 109, 2, (const uint8_t*)"\x01\x00"); + + mp_hal_pin_high(pyb_pin_BT_DEV_WAKE); // let sleep + + return 0; +} + +#endif diff --git a/ports/stm32/cywbt.h b/ports/stm32/cywbt.h new file mode 100644 index 0000000000000..88608a7d7e71d --- /dev/null +++ b/ports/stm32/cywbt.h @@ -0,0 +1,35 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Damien P. George + * + * 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. + */ +#ifndef MICROPY_INCLUDED_STM32_CYWBT_H +#define MICROPY_INCLUDED_STM32_CYWBT_H + +extern uint8_t cywbt_hci_cmd_buf[4 + 256]; +extern pyb_uart_obj_t cywbt_hci_uart_obj; + +int cywbt_init(void); +int cywbt_activate(void); + +#endif // MICROPY_INCLUDED_STM32_CYWBT_H From cba39ecad9d20a046ab416345219f08260a5fa62 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 4 Jul 2019 18:02:49 +1000 Subject: [PATCH 22/28] lib/mynewt-nimble: Add Apache mynewt nimble as a submodule. Tag nimble_1_1_0_tag. --- .gitmodules | 3 +++ lib/mynewt-nimble | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/mynewt-nimble diff --git a/.gitmodules b/.gitmodules index 544815b3a3468..3880ea60a0df2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -27,3 +27,6 @@ [submodule "lib/tinyusb"] path = lib/tinyusb url = https://github.com/hathach/tinyusb +[submodule "lib/mynewt-nimble"] + path = lib/mynewt-nimble + url = https://github.com/apache/mynewt-nimble.git diff --git a/lib/mynewt-nimble b/lib/mynewt-nimble new file mode 160000 index 0000000000000..223714cb16c25 --- /dev/null +++ b/lib/mynewt-nimble @@ -0,0 +1 @@ +Subproject commit 223714cb16c255cfa701929c0de6d7579bfd2cdd From 57d08b02f3de1ccf99c2d098490fd2ab96a3cb18 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 4 Jul 2019 18:03:26 +1000 Subject: [PATCH 23/28] stm32/nimble: Add nimble bindings. --- ports/stm32/nimble/bsp/bsp.h | 1 + ports/stm32/nimble/hal/hal_gpio.h | 1 + ports/stm32/nimble/hal/hal_uart.h | 18 ++ ports/stm32/nimble/hci_uart.c | 116 ++++++++++++ ports/stm32/nimble/misc.c | 61 +++++++ ports/stm32/nimble/nimble.mk | 96 ++++++++++ ports/stm32/nimble/nimble_npl_os.h | 40 +++++ ports/stm32/nimble/npl_os.c | 271 +++++++++++++++++++++++++++++ ports/stm32/nimble/syscfg/syscfg.h | 146 ++++++++++++++++ 9 files changed, 750 insertions(+) create mode 100644 ports/stm32/nimble/bsp/bsp.h create mode 100644 ports/stm32/nimble/hal/hal_gpio.h create mode 100644 ports/stm32/nimble/hal/hal_uart.h create mode 100644 ports/stm32/nimble/hci_uart.c create mode 100644 ports/stm32/nimble/misc.c create mode 100644 ports/stm32/nimble/nimble.mk create mode 100644 ports/stm32/nimble/nimble_npl_os.h create mode 100644 ports/stm32/nimble/npl_os.c create mode 100644 ports/stm32/nimble/syscfg/syscfg.h diff --git a/ports/stm32/nimble/bsp/bsp.h b/ports/stm32/nimble/bsp/bsp.h new file mode 100644 index 0000000000000..8b1a393741c96 --- /dev/null +++ b/ports/stm32/nimble/bsp/bsp.h @@ -0,0 +1 @@ +// empty diff --git a/ports/stm32/nimble/hal/hal_gpio.h b/ports/stm32/nimble/hal/hal_gpio.h new file mode 100644 index 0000000000000..8b1a393741c96 --- /dev/null +++ b/ports/stm32/nimble/hal/hal_gpio.h @@ -0,0 +1 @@ +// empty diff --git a/ports/stm32/nimble/hal/hal_uart.h b/ports/stm32/nimble/hal/hal_uart.h new file mode 100644 index 0000000000000..38f54cb465005 --- /dev/null +++ b/ports/stm32/nimble/hal/hal_uart.h @@ -0,0 +1,18 @@ +#ifndef MICROPY_INCLUDED_STM32_NIMBLE_HAL_HAL_UART_H +#define MICROPY_INCLUDED_STM32_NIMBLE_HAL_HAL_UART_H + +#include + +#define SYSINIT_PANIC_ASSERT_MSG(cond, msg) + +#define HAL_UART_PARITY_NONE (0) + +typedef int (*hal_uart_tx_cb_t)(void *arg); +typedef int (*hal_uart_rx_cb_t)(void *arg, uint8_t data); + +int hal_uart_init_cbs(uint32_t port, hal_uart_tx_cb_t tx_cb, void *tx_arg, hal_uart_rx_cb_t rx_cb, void *rx_arg); +int hal_uart_config(uint32_t port, uint32_t baud, uint32_t bits, uint32_t stop, uint32_t parity, uint32_t flow); +void hal_uart_start_tx(uint32_t port); +int hal_uart_close(uint32_t port); + +#endif // MICROPY_INCLUDED_STM32_NIMBLE_HAL_HAL_UART_H diff --git a/ports/stm32/nimble/hci_uart.c b/ports/stm32/nimble/hci_uart.c new file mode 100644 index 0000000000000..841a43dd96406 --- /dev/null +++ b/ports/stm32/nimble/hci_uart.c @@ -0,0 +1,116 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018-2019 Damien P. George + * + * 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/runtime.h" +#include "py/mphal.h" +#include "pin_static_af.h" +#include "uart.h" +#include "nimble/ble.h" +#include "hal/hal_uart.h" +#include "cywbt.h" + +#if MICROPY_BLUETOOTH_NIMBLE + +/******************************************************************************/ +// Bindings CYWBT to Nimble + +static hal_uart_tx_cb_t hal_uart_tx_cb; +static void *hal_uart_tx_arg; +static hal_uart_rx_cb_t hal_uart_rx_cb; +static void *hal_uart_rx_arg; + +static uint32_t bt_sleep_ticks; + +int hal_uart_init_cbs(uint32_t port, hal_uart_tx_cb_t tx_cb, void *tx_arg, hal_uart_rx_cb_t rx_cb, void *rx_arg) { + hal_uart_tx_cb = tx_cb; + hal_uart_tx_arg = tx_arg; + hal_uart_rx_cb = rx_cb; + hal_uart_rx_arg = rx_arg; + return 0; // success +} + +int hal_uart_config(uint32_t port, uint32_t baud, uint32_t bits, uint32_t stop, uint32_t parity, uint32_t flow) { + cywbt_init(); + cywbt_activate(); + return 0; // success +} + +void hal_uart_start_tx(uint32_t port) { + size_t len = 0; + for (;;) { + int data = hal_uart_tx_cb(hal_uart_tx_arg); + if (data == -1) { + break; + } + cywbt_hci_cmd_buf[len++] = data; + } + + #if 0 + printf("[% 8d] BTUTX: %02x", mp_hal_ticks_ms(), hci_cmd_buf[0]); + for (int i = 1; i < len; ++i) { + printf(":%02x", hci_cmd_buf[i]); + } + printf("\n"); + #endif + + bt_sleep_ticks = mp_hal_ticks_ms(); + if (mp_hal_pin_read(pyb_pin_BT_DEV_WAKE) == 1) { + //printf("BT WAKE for TX\n"); + mp_hal_pin_low(pyb_pin_BT_DEV_WAKE); // wake up + mp_hal_delay_ms(5); // can't go lower than this + } + + uart_tx_strn(&cywbt_hci_uart_obj, (void*)cywbt_hci_cmd_buf, len); +} + +int hal_uart_close(uint32_t port) { + return 0; // success +} + +void nimble_uart_process(void) { + int host_wake = mp_hal_pin_read(pyb_pin_BT_HOST_WAKE); + /* + // this is just for info/tracing purposes + static int last_host_wake = 0; + if (host_wake != last_host_wake) { + printf("HOST_WAKE change %d -> %d\n", last_host_wake, host_wake); + last_host_wake = host_wake; + } + */ + while (uart_rx_any(&cywbt_hci_uart_obj)) { + uint8_t data = uart_rx_char(&cywbt_hci_uart_obj); + //printf("UART RX: %02x\n", data); + hal_uart_rx_cb(hal_uart_rx_arg, data); + } + if (host_wake == 1 && mp_hal_pin_read(pyb_pin_BT_DEV_WAKE) == 0) { + if (mp_hal_ticks_ms() - bt_sleep_ticks > 500) { + //printf("BT SLEEP\n"); + mp_hal_pin_high(pyb_pin_BT_DEV_WAKE); // let sleep + } + } +} + +#endif // MICROPY_BLUETOOTH_NIMBLE diff --git a/ports/stm32/nimble/misc.c b/ports/stm32/nimble/misc.c new file mode 100644 index 0000000000000..9f0b2f5b5df16 --- /dev/null +++ b/ports/stm32/nimble/misc.c @@ -0,0 +1,61 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018-2019 Damien P. George + * + * 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/runtime.h" + +#if MICROPY_BLUETOOTH_NIMBLE + +/******************************************************************************/ +// Misc functions needed by Nimble + +#include + +int sprintf(char *str, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + int ret = vsnprintf(str, 65535, fmt, ap); + va_end(ap); + return ret; +} + +// TODO deal with root pointers + +void *malloc(size_t size) { + //printf("NIMBLE malloc(%u)\n", (uint)size); + return m_malloc(size); +} + +void free(void *ptr) { + //printf("NIMBLE free(%p)\n", ptr); + return m_free(ptr); +} + +void *realloc(void *ptr, size_t size) { + //printf("NIMBLE realloc(%p, %u)\n", ptr, (uint)size); + return m_realloc(ptr, size); +} + +#endif diff --git a/ports/stm32/nimble/nimble.mk b/ports/stm32/nimble/nimble.mk new file mode 100644 index 0000000000000..36177ff261752 --- /dev/null +++ b/ports/stm32/nimble/nimble.mk @@ -0,0 +1,96 @@ +# Makefile directives for Apache mynewt nimble BLE component + +ifeq ($(MICROPY_BLUETOOTH_NIMBLE),1) + +NIMBLE_DIR = lib/mynewt-nimble + +SRC_NIMBLE += $(addprefix $(NIMBLE_DIR)/, \ + $(addprefix ext/tinycrypt/src/, \ + aes_encrypt.c \ + cmac_mode.c \ + ecc.c \ + ecc_dh.c \ + utils.c \ + ) \ + nimble/host/services/gap/src/ble_svc_gap.c \ + nimble/host/services/gatt/src/ble_svc_gatt.c \ + $(addprefix nimble/host/src/, \ + ble_att.c \ + ble_att_clt.c \ + ble_att_cmd.c \ + ble_att_svr.c \ + ble_eddystone.c \ + ble_gap.c \ + ble_gattc.c \ + ble_gatts.c \ + ble_hs_adv.c \ + ble_hs_atomic.c \ + ble_hs.c \ + ble_hs_cfg.c \ + ble_hs_conn.c \ + ble_hs_dbg.c \ + ble_hs_flow.c \ + ble_hs_hci.c \ + ble_hs_hci_cmd.c \ + ble_hs_hci_evt.c \ + ble_hs_hci_util.c \ + ble_hs_id.c \ + ble_hs_log.c \ + ble_hs_mbuf.c \ + ble_hs_misc.c \ + ble_hs_mqueue.c \ + ble_hs_pvcy.c \ + ble_hs_startup.c \ + ble_hs_stop.c \ + ble_ibeacon.c \ + ble_l2cap.c \ + ble_l2cap_coc.c \ + ble_l2cap_sig.c \ + ble_l2cap_sig_cmd.c \ + ble_monitor.c \ + ble_sm_alg.c \ + ble_sm.c \ + ble_sm_cmd.c \ + ble_sm_lgcy.c \ + ble_sm_sc.c \ + ble_store.c \ + ble_store_util.c \ + ble_uuid.c \ + ) \ + nimble/host/store/ram/src/ble_store_ram.c \ + nimble/host/util/src/addr.c \ + nimble/transport/uart/src/ble_hci_uart.c \ + $(addprefix porting/nimble/src/, \ + endian.c \ + mem.c \ + nimble_port.c \ + os_mbuf.c \ + os_mempool.c \ + os_msys_init.c \ + ) \ + ) + +SRC_NIMBLE += \ + nimble/misc.c \ + nimble/npl_os.c \ + nimble/hci_uart.c \ + +CFLAGS_MOD += -DMICROPY_BLUETOOTH_NIMBLE=1 + +INC += -Inimble +INC += -I$(TOP)/$(NIMBLE_DIR) +INC += -I$(TOP)/$(NIMBLE_DIR)/ext/tinycrypt/include +INC += -I$(TOP)/$(NIMBLE_DIR)/nimble/host/include +INC += -I$(TOP)/$(NIMBLE_DIR)/nimble/host/services/gap/include +INC += -I$(TOP)/$(NIMBLE_DIR)/nimble/host/services/gatt/include +INC += -I$(TOP)/$(NIMBLE_DIR)/nimble/host/store/ram/include +INC += -I$(TOP)/$(NIMBLE_DIR)/nimble/host/util/include +INC += -I$(TOP)/$(NIMBLE_DIR)/nimble/include +INC += -I$(TOP)/$(NIMBLE_DIR)/nimble/transport/uart/include +INC += -I$(TOP)/$(NIMBLE_DIR)/porting/nimble/include + +$(BUILD)/$(NIMBLE_DIR)/%.o: CFLAGS += -Wno-maybe-uninitialized -Wno-pointer-arith -Wno-unused-but-set-variable -Wno-format + +OBJ += $(addprefix $(BUILD)/, $(SRC_NIMBLE:.c=.o)) + +endif diff --git a/ports/stm32/nimble/nimble_npl_os.h b/ports/stm32/nimble/nimble_npl_os.h new file mode 100644 index 0000000000000..42b4c1ef16aac --- /dev/null +++ b/ports/stm32/nimble/nimble_npl_os.h @@ -0,0 +1,40 @@ +#ifndef MICROPY_INCLUDED_STM32_NIMBLE_NIMBLE_NPL_OS_H +#define MICROPY_INCLUDED_STM32_NIMBLE_NIMBLE_NPL_OS_H + +#include + +#define BLE_NPL_OS_ALIGNMENT (4) +#define BLE_NPL_TIME_FOREVER (0xffffffff) + +typedef uint32_t ble_npl_time_t; +typedef int32_t ble_npl_stime_t; + +struct ble_npl_event { + ble_npl_event_fn *fn; + void *arg; + struct ble_npl_event *prev; + struct ble_npl_event *next; +}; + +struct ble_npl_eventq { + struct ble_npl_event *head; + struct ble_npl_eventq *nextq; +}; + +struct ble_npl_callout { + bool active; + uint32_t ticks; + struct ble_npl_eventq *evq; + struct ble_npl_event ev; + struct ble_npl_callout *nextc; +}; + +struct ble_npl_mutex { + volatile uint8_t locked; +}; + +struct ble_npl_sem { + volatile uint16_t count; +}; + +#endif // MICROPY_INCLUDED_STM32_NIMBLE_NIMBLE_NPL_OS_H diff --git a/ports/stm32/nimble/npl_os.c b/ports/stm32/nimble/npl_os.c new file mode 100644 index 0000000000000..3adae0caa079a --- /dev/null +++ b/ports/stm32/nimble/npl_os.c @@ -0,0 +1,271 @@ +#include +#include "py/mphal.h" +#include "nimble/nimble_npl.h" + +#define DEBUG_OS_printf(...) //printf(__VA_ARGS__) +#define DEBUG_EVENT_printf(...) //printf(__VA_ARGS__) +#define DEBUG_MUTEX_printf(...) //printf(__VA_ARGS__) +#define DEBUG_SEM_printf(...) //printf(__VA_ARGS__) +#define DEBUG_CALLOUT_printf(...) //printf(__VA_ARGS__) +#define DEBUG_TIME_printf(...) //printf(__VA_ARGS__) +#define DEBUG_CRIT_printf(...) //printf(__VA_ARGS__) + +bool ble_npl_os_started(void) { + DEBUG_OS_printf("ble_npl_os_started\n"); + return true; +} + +void *ble_npl_get_current_task_id(void) { + DEBUG_OS_printf("ble_npl_get_current_task_id\n"); + return NULL; +} + +/******************************************************************************/ +// EVENTQ + +struct ble_npl_eventq *global_eventq = NULL; + +void os_eventq_run_all(void) { + for (struct ble_npl_eventq *evq = global_eventq; evq != NULL; evq = evq->nextq) { + while (evq->head != NULL) { + struct ble_npl_event *ev = evq->head; + evq->head = ev->next; + if (ev->next) { + ev->next->prev = NULL; + } + DEBUG_EVENT_printf("event_run(%p)\n", ev); + ev->fn(ev); + DEBUG_EVENT_printf("event_run(%p) done\n", ev); + } + } +} + +void ble_npl_eventq_init(struct ble_npl_eventq *evq) { + DEBUG_EVENT_printf("ble_npl_eventq_init(%p)\n", evq); + evq->head = NULL; + struct ble_npl_eventq **evq2; + for (evq2 = &global_eventq; *evq2 != NULL; evq2 = &(*evq2)->nextq) { + } + *evq2 = evq; + evq->nextq = NULL; +} + +void ble_npl_eventq_put(struct ble_npl_eventq *evq, struct ble_npl_event *ev) { + DEBUG_EVENT_printf("ble_npl_eventq_put(%p, %p (%p, %p))\n", evq, ev, ev->fn, ev->arg); + ev->next = NULL; + if (evq->head == NULL) { + evq->head = ev; + ev->prev = NULL; + } else { + struct ble_npl_event *ev2 = evq->head; + while (ev2->next != NULL) { + ev2 = ev2->next; + } + ev2->next = ev; + ev->prev = ev2; + } +} + +void ble_npl_event_init(struct ble_npl_event *ev, ble_npl_event_fn *fn, void *arg) { + DEBUG_EVENT_printf("ble_npl_event_init(%p, %p, %p)\n", ev, fn, arg); + ev->fn = fn; + ev->arg = arg; + ev->next = NULL; +} + +void *ble_npl_event_get_arg(struct ble_npl_event *ev) { + DEBUG_EVENT_printf("ble_npl_event_get_arg(%p) -> %p\n", ev, ev->arg); + return ev->arg; +} + +void ble_npl_event_set_arg(struct ble_npl_event *ev, void *arg) { + DEBUG_EVENT_printf("ble_npl_event_set_arg(%p, %p)\n", ev, arg); + ev->arg = arg; +} + +/******************************************************************************/ +// MUTEX + +ble_npl_error_t ble_npl_mutex_init(struct ble_npl_mutex *mu) { + DEBUG_MUTEX_printf("ble_npl_mutex_init(%p)\n", mu); + mu->locked = 0; + return BLE_NPL_OK; +} + +ble_npl_error_t ble_npl_mutex_pend(struct ble_npl_mutex *mu, ble_npl_time_t timeout) { + DEBUG_MUTEX_printf("ble_npl_mutex_pend(%p, %u) locked=%u\n", mu, (uint)timeout, (uint)mu->locked); + mu->locked = 1; + return BLE_NPL_OK; +} + +ble_npl_error_t ble_npl_mutex_release(struct ble_npl_mutex *mu) { + DEBUG_MUTEX_printf("ble_npl_mutex_release(%p) locked=%u\n", mu, (uint)mu->locked); + mu->locked = 0; + return BLE_NPL_OK; +} + +/******************************************************************************/ +// SEM + +ble_npl_error_t ble_npl_sem_init(struct ble_npl_sem *sem, uint16_t tokens) { + DEBUG_SEM_printf("ble_npl_sem_init(%p, %u)\n", sem, (uint)tokens); + sem->count = tokens; + return BLE_NPL_OK; +} + +ble_npl_error_t ble_npl_sem_pend(struct ble_npl_sem *sem, ble_npl_time_t timeout) { + DEBUG_SEM_printf("ble_npl_sem_pend(%p, %u) count=%u\n", sem, (uint)timeout, (uint)sem->count); + if (sem->count == 0) { + uint32_t t0 = mp_hal_ticks_ms(); + while (sem->count == 0 && mp_hal_ticks_ms() - t0 < timeout) { + extern void nimble_uart_process(void); + nimble_uart_process(); + if (sem->count != 0) { + break; + } + __WFI(); + } + if (sem->count == 0) { + printf("timeout\n"); + return BLE_NPL_TIMEOUT; + } + DEBUG_SEM_printf("got response in %u ms\n", (int)(mp_hal_ticks_ms() - t0)); + } + sem->count -= 1; + return BLE_NPL_OK; +} + +ble_npl_error_t ble_npl_sem_release(struct ble_npl_sem *sem) { + DEBUG_SEM_printf("ble_npl_sem_release(%p)\n", sem); + sem->count += 1; + return BLE_NPL_OK; +} + +uint16_t ble_npl_sem_get_count(struct ble_npl_sem *sem) { + DEBUG_SEM_printf("ble_npl_sem_get_count(%p)\n", sem); + return sem->count; +} + +/******************************************************************************/ +// CALLOUT + +static struct ble_npl_callout *global_callout = NULL; + +void os_callout_process(void) { + uint32_t tnow = mp_hal_ticks_ms(); + for (struct ble_npl_callout *c = global_callout; c != NULL; c = c->nextc) { + if (!c->active) { + continue; + } + if ((int32_t)(tnow - c->ticks) >= 0) { + DEBUG_CALLOUT_printf("callout_run(%p) tnow=%u ticks=%u evq=%p\n", c, (uint)tnow, (uint)c->ticks, c->evq); + c->active = false; + if (c->evq) { + ble_npl_eventq_put(c->evq, &c->ev); + } else { + c->ev.fn(&c->ev); + } + DEBUG_CALLOUT_printf("callout_run(%p) done\n", c); + } + } +} + +void ble_npl_callout_init(struct ble_npl_callout *c, struct ble_npl_eventq *evq, ble_npl_event_fn *ev_cb, void *ev_arg) { + DEBUG_CALLOUT_printf("ble_npl_callout_init(%p, %p, %p, %p)\n", c, evq, ev_cb, ev_arg); + c->active = false; + c->ticks = 0; + c->evq = evq; + ble_npl_event_init(&c->ev, ev_cb, ev_arg); + + struct ble_npl_callout **c2; + for (c2 = &global_callout; *c2 != NULL; c2 = &(*c2)->nextc) { + if (c == *c2) { + // callout already in linked list so don't link it in again + return; + } + } + *c2 = c; + c->nextc = NULL; +} + +ble_npl_error_t ble_npl_callout_reset(struct ble_npl_callout *c, ble_npl_time_t ticks) { + DEBUG_CALLOUT_printf("ble_npl_callout_reset(%p, %u) tnow=%u\n", c, (uint)ticks, (uint)mp_hal_ticks_ms()); + c->active = true; + c->ticks = ble_npl_time_get() + ticks; + return BLE_NPL_OK; +} + +void ble_npl_callout_stop(struct ble_npl_callout *c) { + DEBUG_CALLOUT_printf("ble_npl_callout_stop(%p)\n", c); + c->active = false; +} + +bool ble_npl_callout_is_active(struct ble_npl_callout *c) { + DEBUG_CALLOUT_printf("ble_npl_callout_is_active(%p)\n", c); + return c->active; +} + +ble_npl_time_t ble_npl_callout_get_ticks(struct ble_npl_callout *c) { + DEBUG_CALLOUT_printf("ble_npl_callout_get_ticks(%p)\n", c); + return c->ticks; +} + +ble_npl_time_t ble_npl_callout_remaining_ticks(struct ble_npl_callout *c, ble_npl_time_t now) { + DEBUG_CALLOUT_printf("ble_npl_callout_remaining_ticks(%p, %u)\n", c, (uint)now); + if (c->ticks > now) { + return c->ticks - now; + } else { + return 0; + } +} + +void *ble_npl_callout_get_arg(struct ble_npl_callout *c) { + DEBUG_CALLOUT_printf("ble_npl_callout_get_arg(%p)\n", c); + return ble_npl_event_get_arg(&c->ev); +} + +void ble_npl_callout_set_arg(struct ble_npl_callout *c, void *arg) { + DEBUG_CALLOUT_printf("ble_npl_callout_set_arg(%p, %p)\n", c, arg); + ble_npl_event_set_arg(&c->ev, arg); +} + +/******************************************************************************/ +// TIME + +uint32_t ble_npl_time_get(void) { + DEBUG_TIME_printf("ble_npl_time_get -> %u\n", (uint)mp_hal_ticks_ms()); + return mp_hal_ticks_ms(); +} + +ble_npl_error_t ble_npl_time_ms_to_ticks(uint32_t ms, ble_npl_time_t *out_ticks) { + DEBUG_TIME_printf("ble_npl_time_ms_to_ticks(%u)\n", (uint)ms); + *out_ticks = ms; + return BLE_NPL_OK; +} + +ble_npl_time_t ble_npl_time_ms_to_ticks32(uint32_t ms) { + DEBUG_TIME_printf("ble_npl_time_ms_to_ticks32(%u)\n", (uint)ms); + return ms; +} + +uint32_t ble_npl_time_ticks_to_ms32(ble_npl_time_t ticks) { + DEBUG_TIME_printf("ble_npl_time_ticks_to_ms32(%u)\n", (uint)ticks); + return ticks; +} + +void ble_npl_time_delay(ble_npl_time_t ticks) { + mp_hal_delay_ms(ticks + 1); +} + +/******************************************************************************/ +// CRITICAL + +uint32_t ble_npl_hw_enter_critical(void) { + DEBUG_CRIT_printf("ble_npl_hw_enter_critical()\n"); + return raise_irq_pri(15); +} + +void ble_npl_hw_exit_critical(uint32_t ctx) { + DEBUG_CRIT_printf("ble_npl_hw_exit_critical(%u)\n", (uint)ctx); + restore_irq_pri(ctx); +} diff --git a/ports/stm32/nimble/syscfg/syscfg.h b/ports/stm32/nimble/syscfg/syscfg.h new file mode 100644 index 0000000000000..23ef81332fb07 --- /dev/null +++ b/ports/stm32/nimble/syscfg/syscfg.h @@ -0,0 +1,146 @@ +#ifndef INCLUDED_SYSCFG_H +#define INCLUDED_SYSCFG_H + +#define MYNEWT_VAL(x) MYNEWT_VAL_ ## x + +#define MYNEWT_VAL_LOG_LEVEL (255) + +/*** compiler/arm-none-eabi-m4 */ +#define MYNEWT_VAL_HARDFLOAT (1) + +/*** kernel/os */ +#define MYNEWT_VAL_FLOAT_USER (0) +#define MYNEWT_VAL_MSYS_1_BLOCK_COUNT (12) +#define MYNEWT_VAL_MSYS_1_BLOCK_SIZE (292) +#define MYNEWT_VAL_MSYS_2_BLOCK_COUNT (0) +#define MYNEWT_VAL_MSYS_2_BLOCK_SIZE (0) +#define MYNEWT_VAL_OS_CPUTIME_FREQ (1000000) +#define MYNEWT_VAL_OS_CPUTIME_TIMER_NUM (0) +#define MYNEWT_VAL_OS_CTX_SW_STACK_CHECK (0) +#define MYNEWT_VAL_OS_CTX_SW_STACK_GUARD (4) +#define MYNEWT_VAL_OS_MAIN_STACK_SIZE (1024) +#define MYNEWT_VAL_OS_MAIN_TASK_PRIO (127) +#define MYNEWT_VAL_OS_MEMPOOL_CHECK (0) +#define MYNEWT_VAL_OS_MEMPOOL_POISON (0) + +/*** nimble */ +#define MYNEWT_VAL_BLE_EXT_ADV (0) +#define MYNEWT_VAL_BLE_EXT_ADV_MAX_SIZE (31) +#define MYNEWT_VAL_BLE_MAX_CONNECTIONS (1) +#define MYNEWT_VAL_BLE_MULTI_ADV_INSTANCES (0) +#define MYNEWT_VAL_BLE_ROLE_BROADCASTER (1) +#define MYNEWT_VAL_BLE_ROLE_CENTRAL (1) +#define MYNEWT_VAL_BLE_ROLE_OBSERVER (1) +#define MYNEWT_VAL_BLE_ROLE_PERIPHERAL (1) +#define MYNEWT_VAL_BLE_WHITELIST (1) + +/*** nimble/host */ +#define MYNEWT_VAL_BLE_ATT_PREFERRED_MTU (256) +#define MYNEWT_VAL_BLE_ATT_SVR_FIND_INFO (1) +#define MYNEWT_VAL_BLE_ATT_SVR_FIND_TYPE (1) +#define MYNEWT_VAL_BLE_ATT_SVR_INDICATE (1) +#define MYNEWT_VAL_BLE_ATT_SVR_MAX_PREP_ENTRIES (64) +#define MYNEWT_VAL_BLE_ATT_SVR_NOTIFY (1) +#define MYNEWT_VAL_BLE_ATT_SVR_QUEUED_WRITE (1) +#define MYNEWT_VAL_BLE_ATT_SVR_QUEUED_WRITE_TMO (30000) +#define MYNEWT_VAL_BLE_ATT_SVR_READ (1) +#define MYNEWT_VAL_BLE_ATT_SVR_READ_BLOB (1) +#define MYNEWT_VAL_BLE_ATT_SVR_READ_GROUP_TYPE (1) +#define MYNEWT_VAL_BLE_ATT_SVR_READ_MULT (1) +#define MYNEWT_VAL_BLE_ATT_SVR_READ_TYPE (1) +#define MYNEWT_VAL_BLE_ATT_SVR_SIGNED_WRITE (1) +#define MYNEWT_VAL_BLE_ATT_SVR_WRITE (1) +#define MYNEWT_VAL_BLE_ATT_SVR_WRITE_NO_RSP (1) +#define MYNEWT_VAL_BLE_GAP_MAX_PENDING_CONN_PARAM_UPDATE (1) +#define MYNEWT_VAL_BLE_GATT_DISC_ALL_CHRS (MYNEWT_VAL_BLE_ROLE_CENTRAL) +#define MYNEWT_VAL_BLE_GATT_DISC_ALL_DSCS (MYNEWT_VAL_BLE_ROLE_CENTRAL) +#define MYNEWT_VAL_BLE_GATT_DISC_ALL_SVCS (MYNEWT_VAL_BLE_ROLE_CENTRAL) +#define MYNEWT_VAL_BLE_GATT_DISC_CHR_UUID (MYNEWT_VAL_BLE_ROLE_CENTRAL) +#define MYNEWT_VAL_BLE_GATT_DISC_SVC_UUID (MYNEWT_VAL_BLE_ROLE_CENTRAL) +#define MYNEWT_VAL_BLE_GATT_FIND_INC_SVCS (MYNEWT_VAL_BLE_ROLE_CENTRAL) +#define MYNEWT_VAL_BLE_GATT_INDICATE (1) +#define MYNEWT_VAL_BLE_GATT_MAX_PROCS (4) +#define MYNEWT_VAL_BLE_GATT_NOTIFY (1) +#define MYNEWT_VAL_BLE_GATT_READ (MYNEWT_VAL_BLE_ROLE_CENTRAL) +#define MYNEWT_VAL_BLE_GATT_READ_LONG (MYNEWT_VAL_BLE_ROLE_CENTRAL) +#define MYNEWT_VAL_BLE_GATT_READ_MAX_ATTRS (8) +#define MYNEWT_VAL_BLE_GATT_READ_MULT (MYNEWT_VAL_BLE_ROLE_CENTRAL) +#define MYNEWT_VAL_BLE_GATT_READ_UUID (MYNEWT_VAL_BLE_ROLE_CENTRAL) +#define MYNEWT_VAL_BLE_GATT_RESUME_RATE (1000) +#define MYNEWT_VAL_BLE_GATT_SIGNED_WRITE (MYNEWT_VAL_BLE_ROLE_CENTRAL) +#define MYNEWT_VAL_BLE_GATT_WRITE (MYNEWT_VAL_BLE_ROLE_CENTRAL) +#define MYNEWT_VAL_BLE_GATT_WRITE_LONG (MYNEWT_VAL_BLE_ROLE_CENTRAL) +#define MYNEWT_VAL_BLE_GATT_WRITE_MAX_ATTRS (4) +#define MYNEWT_VAL_BLE_GATT_WRITE_NO_RSP (MYNEWT_VAL_BLE_ROLE_CENTRAL) +#define MYNEWT_VAL_BLE_GATT_WRITE_RELIABLE (MYNEWT_VAL_BLE_ROLE_CENTRAL) +#define MYNEWT_VAL_BLE_HOST (1) +#define MYNEWT_VAL_BLE_HS_DEBUG (0) +#define MYNEWT_VAL_BLE_HS_FLOW_CTRL (0) +#define MYNEWT_VAL_BLE_HS_FLOW_CTRL_ITVL (1000) +#define MYNEWT_VAL_BLE_HS_FLOW_CTRL_THRESH (2) +#define MYNEWT_VAL_BLE_HS_FLOW_CTRL_TX_ON_DISCONNECT (0) +#define MYNEWT_VAL_BLE_HS_PHONY_HCI_ACKS (0) +#define MYNEWT_VAL_BLE_HS_REQUIRE_OS (1) +#define MYNEWT_VAL_BLE_L2CAP_COC_MAX_NUM (0) +#define MYNEWT_VAL_BLE_L2CAP_JOIN_RX_FRAGS (1) +#define MYNEWT_VAL_BLE_L2CAP_MAX_CHANS (3*MYNEWT_VAL_BLE_MAX_CONNECTIONS) +#define MYNEWT_VAL_BLE_L2CAP_RX_FRAG_TIMEOUT (30000) +#define MYNEWT_VAL_BLE_L2CAP_SIG_MAX_PROCS (1) +#define MYNEWT_VAL_BLE_MONITOR_CONSOLE_BUFFER_SIZE (128) +#define MYNEWT_VAL_BLE_MONITOR_RTT (0) +#define MYNEWT_VAL_BLE_MONITOR_RTT_BUFFERED (1) +#define MYNEWT_VAL_BLE_MONITOR_RTT_BUFFER_NAME ("monitor") +#define MYNEWT_VAL_BLE_MONITOR_RTT_BUFFER_SIZE (256) +#define MYNEWT_VAL_BLE_MONITOR_UART (0) +#define MYNEWT_VAL_BLE_MONITOR_UART_BAUDRATE (1000000) +#define MYNEWT_VAL_BLE_MONITOR_UART_BUFFER_SIZE (64) +#define MYNEWT_VAL_BLE_MONITOR_UART_DEV ("uart0") +#define MYNEWT_VAL_BLE_RPA_TIMEOUT (300) +#define MYNEWT_VAL_BLE_SM_BONDING (0) +#define MYNEWT_VAL_BLE_SM_IO_CAP (BLE_HS_IO_NO_INPUT_OUTPUT) +#define MYNEWT_VAL_BLE_SM_KEYPRESS (0) +#define MYNEWT_VAL_BLE_SM_LEGACY (1) +#define MYNEWT_VAL_BLE_SM_MAX_PROCS (1) +#define MYNEWT_VAL_BLE_SM_MITM (0) +#define MYNEWT_VAL_BLE_SM_OOB_DATA_FLAG (0) +#define MYNEWT_VAL_BLE_SM_OUR_KEY_DIST (0) +#define MYNEWT_VAL_BLE_SM_SC (1) +#define MYNEWT_VAL_BLE_SM_THEIR_KEY_DIST (0) +#define MYNEWT_VAL_BLE_STORE_MAX_BONDS (3) +#define MYNEWT_VAL_BLE_STORE_MAX_CCCDS (8) + +/*** nimble/host/services/gap */ +#define MYNEWT_VAL_BLE_SVC_GAP_APPEARANCE (0) +#define MYNEWT_VAL_BLE_SVC_GAP_APPEARANCE_WRITE_PERM (-1) +#define MYNEWT_VAL_BLE_SVC_GAP_CENTRAL_ADDRESS_RESOLUTION (-1) +#define MYNEWT_VAL_BLE_SVC_GAP_DEVICE_NAME ("pybd") +#define MYNEWT_VAL_BLE_SVC_GAP_DEVICE_NAME_MAX_LENGTH (31) +#define MYNEWT_VAL_BLE_SVC_GAP_DEVICE_NAME_WRITE_PERM (-1) +#define MYNEWT_VAL_BLE_SVC_GAP_PPCP_MAX_CONN_INTERVAL (0) +#define MYNEWT_VAL_BLE_SVC_GAP_PPCP_MIN_CONN_INTERVAL (0) +#define MYNEWT_VAL_BLE_SVC_GAP_PPCP_SLAVE_LATENCY (0) +#define MYNEWT_VAL_BLE_SVC_GAP_PPCP_SUPERVISION_TMO (0) + +/* Overridden by targets/porting-nimble (defined by nimble/transport) */ +#define MYNEWT_VAL_BLE_HCI_TRANSPORT_NIMBLE_BUILTIN (0) +#define MYNEWT_VAL_BLE_HCI_TRANSPORT_RAM (0) +#define MYNEWT_VAL_BLE_HCI_TRANSPORT_SOCKET (0) +#define MYNEWT_VAL_BLE_HCI_TRANSPORT_UART (1) + +/*** nimble/transport/uart */ +#define MYNEWT_VAL_BLE_ACL_BUF_COUNT (12) +#define MYNEWT_VAL_BLE_ACL_BUF_SIZE (255) +#define MYNEWT_VAL_BLE_HCI_ACL_OUT_COUNT (12) +#define MYNEWT_VAL_BLE_HCI_EVT_BUF_SIZE (70) +#define MYNEWT_VAL_BLE_HCI_EVT_HI_BUF_COUNT (8) +#define MYNEWT_VAL_BLE_HCI_EVT_LO_BUF_COUNT (8) + +/* Overridden by targets/porting-nimble (defined by nimble/transport/uart) */ +#define MYNEWT_VAL_BLE_HCI_UART_BAUD (115200) +#define MYNEWT_VAL_BLE_HCI_UART_DATA_BITS (8) +#define MYNEWT_VAL_BLE_HCI_UART_FLOW_CTRL (0) +#define MYNEWT_VAL_BLE_HCI_UART_PARITY (HAL_UART_PARITY_NONE) +#define MYNEWT_VAL_BLE_HCI_UART_PORT (0) +#define MYNEWT_VAL_BLE_HCI_UART_STOP_BITS (1) + +#endif // INCLUDED_SYSCFG_H From 32746e771f16e31c5c48539492a243a098db5bc3 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 4 Jul 2019 18:05:57 +1000 Subject: [PATCH 24/28] stm32: Integrate nimble. --- ports/stm32/Makefile | 2 ++ ports/stm32/modnetwork.c | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index 431a8b5c93b64..f3b7a29f6bade 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -415,6 +415,8 @@ OBJ += $(addprefix $(BUILD)/, $(SRC_USBDEV:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_MOD:.c=.o)) OBJ += $(BUILD)/pins_$(BOARD).o +include nimble/nimble.mk + # This file contains performance critical functions so turn up the optimisation # level. It doesn't add much to the code size and improves performance a bit. # Don't use -O3 with this file because gcc tries to optimise memset in terms of itself. diff --git a/ports/stm32/modnetwork.c b/ports/stm32/modnetwork.c index 80e5a5a16257a..38f8b638a017a 100644 --- a/ports/stm32/modnetwork.c +++ b/ports/stm32/modnetwork.c @@ -63,6 +63,11 @@ STATIC void pyb_lwip_poll(void) { // Run the lwIP internal updates sys_check_timeouts(); + + #if MICROPY_BLUETOOTH_NIMBLE + extern void nimble_poll(void); + nimble_poll(); + #endif } void mod_network_lwip_poll_wrapper(uint32_t ticks_ms) { From a599be70bf8f1f91aaf9e5bbe1c8307bb20bf8cb Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 4 Jul 2019 18:06:27 +1000 Subject: [PATCH 25/28] extmod/modbluetooth: Fix mp_bt_characteristic_on_write value_handle type --- extmod/modbluetooth.c | 2 +- extmod/modbluetooth.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index 1fd6b78daa119..bd17f2342bb53 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -297,7 +297,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(bluetooth_write_callback_obj, bluetooth_write_c // 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) { +void mp_bt_characteristic_on_write(uint16_t conn_handle, mp_bt_characteristic_handle_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); diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index bbdb5a7f67022..00edbcd9189e3 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -101,7 +101,7 @@ int mp_bt_characteristic_value_notify(mp_bt_characteristic_t *characteristic, ui 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); +void mp_bt_characteristic_on_write(uint16_t conn_handle, mp_bt_characteristic_handle_t value_handle, const void *value, size_t value_len); // Disconnect a connected central. int mp_bt_device_disconnect(uint16_t conn_handle); From c616d2ad7ba7dc6e3c295ffa21102c14024fcfde Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 4 Jul 2019 18:07:02 +1000 Subject: [PATCH 26/28] extmod/modbluetooth: Remove need to include bt header in mpstate.h. --- extmod/modbluetooth.c | 2 +- py/mpstate.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index bd17f2342bb53..d706ed440a163 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -671,7 +671,7 @@ STATIC mp_obj_t characteristic_irq(size_t n_args, const mp_obj_t *pos_args, mp_m // 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); + mp_bt_characteristic_callback_t **entry = (mp_bt_characteristic_callback_t**)&MP_STATE_PORT(bt_characteristic_callbacks); while (1) { if (*entry == NULL) { // found the end of the list diff --git a/py/mpstate.h b/py/mpstate.h index 617f0df1cb078..3a669f5e2f5f7 100644 --- a/py/mpstate.h +++ b/py/mpstate.h @@ -37,7 +37,7 @@ #include "py/objexcept.h" #if MICROPY_PY_BLUETOOTH -#include "extmod/modbluetooth.h" +//#include "extmod/modbluetooth.h" #endif // This file contains structures defining the state of the MicroPython @@ -224,8 +224,8 @@ typedef struct _mp_state_vm_t { #if MICROPY_PY_BLUETOOTH // This is a linked list of callbacks registered in the Bluetooth - // object. - mp_bt_characteristic_callback_t *bt_characteristic_callbacks; + // object, type mp_bt_characteristic_callback_t* + void *bt_characteristic_callbacks; // This is a function object called for global events (device // connect/disconnect). mp_obj_t bt_event_handler; From 46e02de67061756a6faa36c0dc0f81ee39ad382a Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 4 Jul 2019 18:07:42 +1000 Subject: [PATCH 27/28] stm32: Integrate Python bluetooth interface. --- ports/stm32/Makefile | 1 + ports/stm32/bluetooth/bluetooth.c | 404 ++++++++++++++++++++++++++++++ ports/stm32/bluetooth/bluetooth.h | 43 ++++ ports/stm32/mpconfigport.h | 8 + 4 files changed, 456 insertions(+) create mode 100644 ports/stm32/bluetooth/bluetooth.c create mode 100644 ports/stm32/bluetooth/bluetooth.h diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index f3b7a29f6bade..557632b232361 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -280,6 +280,7 @@ SRC_C = \ servo.c \ dac.c \ adc.c \ + bluetooth/bluetooth.c \ $(wildcard $(BOARD_DIR)/*.c) ifeq ($(MCU_SERIES),f0) diff --git a/ports/stm32/bluetooth/bluetooth.c b/ports/stm32/bluetooth/bluetooth.c new file mode 100644 index 0000000000000..4766d9cd71045 --- /dev/null +++ b/ports/stm32/bluetooth/bluetooth.c @@ -0,0 +1,404 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Damien P. George + * + * 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/runtime.h" +#include "py/mperrno.h" +#include "py/mphal.h" + +#if MICROPY_PY_BLUETOOTH + +#include "extmod/modbluetooth.h" + +#include "host/ble_hs.h" +#include "host/util/util.h" +#include "nimble/ble.h" +#include "nimble/nimble_port.h" +#include "services/gap/ble_svc_gap.h" +#include "transport/uart/ble_hci_uart.h" + +STATIC int8_t ble_hs_err_to_errno_table[] = { + [BLE_HS_EAGAIN] = MP_EAGAIN, + [BLE_HS_EALREADY] = MP_EALREADY, + [BLE_HS_EINVAL] = MP_EINVAL, + [BLE_HS_EMSGSIZE] = MP_EIO, + [BLE_HS_ENOENT] = MP_ENOENT, + [BLE_HS_ENOMEM] = MP_ENOMEM, + [BLE_HS_ENOTCONN] = MP_ENOTCONN, + [BLE_HS_ENOTSUP] = MP_EOPNOTSUPP, + [BLE_HS_EAPP] = MP_EIO, + [BLE_HS_EBADDATA] = MP_EIO, + [BLE_HS_EOS] = MP_EIO, + [BLE_HS_ECONTROLLER] = MP_EIO, + [BLE_HS_ETIMEOUT] = MP_ETIMEDOUT, + [BLE_HS_EDONE] = MP_EIO, + [BLE_HS_EBUSY] = MP_EBUSY, + [BLE_HS_EREJECT] = MP_EIO, + [BLE_HS_EUNKNOWN] = MP_EIO, + [BLE_HS_EROLE] = MP_EIO, + [BLE_HS_ETIMEOUT_HCI] = MP_EIO, + [BLE_HS_ENOMEM_EVT] = MP_EIO, + [BLE_HS_ENOADDR] = MP_EIO, + [BLE_HS_ENOTSYNCED] = MP_EIO, + [BLE_HS_EAUTHEN] = MP_EIO, + [BLE_HS_EAUTHOR] = MP_EIO, + [BLE_HS_EENCRYPT] = MP_EIO, + [BLE_HS_EENCRYPT_KEY_SZ] = MP_EIO, + [BLE_HS_ESTORE_CAP] = MP_EIO, + [BLE_HS_ESTORE_FAIL] = MP_EIO, + [BLE_HS_EPREEMPTED] = MP_EIO, + [BLE_HS_EDISABLED] = MP_EIO, +}; + +STATIC int ble_hs_err_to_errno(int err) { + if (0 <= err && err < MP_ARRAY_SIZE(ble_hs_err_to_errno_table)) { + return ble_hs_err_to_errno_table[err]; + } else { + return MP_EIO; + } +} + +/******************************************************************************/ +// RUN LOOP + +enum { + BLE_STATE_OFF, + BLE_STATE_STARTING, + BLE_STATE_ACTIVE, +}; + +static volatile int ble_state = BLE_STATE_OFF; + +extern void nimble_uart_process(void); +extern void os_eventq_run_all(void); +extern void os_callout_process(void); + +// hook for network poller to run this periodically +void nimble_poll(void) { + if (ble_state == BLE_STATE_OFF) { + return; + } + + nimble_uart_process(); + os_callout_process(); + os_eventq_run_all(); +} + +/******************************************************************************/ +// BINDINGS + +STATIC uint16_t active_conn_handle[MP_BT_MAX_CONNECTED_DEVICES]; + +STATIC void reset_cb(int reason) { + (void)reason; +} + +STATIC void sync_cb(void) { + ble_hs_util_ensure_addr(0); // prefer public address + ble_svc_gap_device_name_set("PYBD"); + + ble_state = BLE_STATE_ACTIVE; +} + +STATIC void gatts_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg) { + switch (ctxt->op) { + case BLE_GATT_REGISTER_OP_SVC: + printf("gatts_register_cb: svc uuid=%p handle=%d\n", &ctxt->svc.svc_def->uuid, ctxt->svc.handle); + break; + + case BLE_GATT_REGISTER_OP_CHR: + printf("gatts_register_cb: chr uuid=%p def_handle=%d val_handle=%d\n", &ctxt->chr.chr_def->uuid, ctxt->chr.def_handle, ctxt->chr.val_handle); + break; + + case BLE_GATT_REGISTER_OP_DSC: + printf("gatts_register_cb: dsc uuid=%p handle=%d\n", &ctxt->dsc.dsc_def->uuid, ctxt->dsc.handle); + break; + } +} + +STATIC int gap_event_cb(struct ble_gap_event *event, void *arg) { + printf("gap_event_cb: type=%d\n", event->type); + + switch (event->type) { + case BLE_GAP_EVENT_CONNECT: + printf(" connect: handle=%u\n", event->connect.conn_handle); + if (event->connect.status == 0) { + // Connection established + for (size_t i = 0; i < MP_BT_MAX_CONNECTED_DEVICES; ++i) { + if (active_conn_handle[i] == MP_BT_INVALID_CONN_HANDLE) { + active_conn_handle[i] = event->connect.conn_handle; + break; + } + } + } else { + // Connection failed + for (size_t i = 0; i < MP_BT_MAX_CONNECTED_DEVICES; ++i) { + if (active_conn_handle[i] == event->connect.conn_handle) { + active_conn_handle[i] = MP_BT_INVALID_CONN_HANDLE; + break; + } + } + } + break; + + case BLE_GAP_EVENT_DISCONNECT: + printf(" disconnect: handle=%u\n", event->disconnect.conn.conn_handle); + for (size_t i = 0; i < MP_BT_MAX_CONNECTED_DEVICES; ++i) { + if (active_conn_handle[i] == event->disconnect.conn.conn_handle) { + active_conn_handle[i] = MP_BT_INVALID_CONN_HANDLE; + break; + } + } + break; + } + + return 0; +} + +int mp_bt_enable(void) { + if (ble_state != BLE_STATE_OFF) { + return 0; + } + + for (size_t i = 0; i < MP_BT_MAX_CONNECTED_DEVICES; ++i) { + active_conn_handle[i] = MP_BT_INVALID_CONN_HANDLE; + } + + ble_state = BLE_STATE_STARTING; + + ble_hs_cfg.reset_cb = reset_cb; + ble_hs_cfg.sync_cb = sync_cb; + ble_hs_cfg.gatts_register_cb = gatts_register_cb; + ble_hs_cfg.store_status_cb = ble_store_util_status_rr; + + ble_hci_uart_init(); + nimble_port_init(); + ble_hs_sched_start(); + + // Wait for sync callback + while (ble_state != BLE_STATE_ACTIVE) { + MICROPY_EVENT_POLL_HOOK + } + + return 0; +} + +void mp_bt_disable(void) { + ble_state = BLE_STATE_OFF; + mp_hal_pin_low(pyb_pin_BT_REG_ON); +} + +bool mp_bt_is_enabled(void) { + return ble_state == BLE_STATE_ACTIVE; +} + +void mp_bt_get_address(uint8_t *address) { + mp_hal_get_mac(MP_HAL_MAC_BDADDR, address); + // TODO: need to convert MSB/LSB? +} + +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) { + int ret; + + mp_bt_advertise_stop(); + + if (adv_data != NULL) { + ret = ble_gap_adv_set_data(adv_data, adv_data_len); + if (ret != 0) { + printf("ble_gap_adv_set_data: fail with %u\n", ret); + return ble_hs_err_to_errno(ret); + } + } + + if (sr_data != NULL) { + ret = ble_gap_adv_rsp_set_data(sr_data, sr_data_len); + if (ret != 0) { + printf("ble_gap_adv_rsp_set_data: fail with %u\n", ret); + return ble_hs_err_to_errno(ret); + } + } + + struct ble_gap_adv_params adv_params = { + .conn_mode = type, + .disc_mode = BLE_GAP_DISC_MODE_GEN, + .itvl_min = interval, + .itvl_max = interval, + .channel_map = 7, // all 3 channels + }; + + ret = ble_gap_adv_start(BLE_OWN_ADDR_PUBLIC, NULL, BLE_HS_FOREVER, &adv_params, gap_event_cb, NULL); + if (ret != 0) { + printf("ble_gap_adv_start: fail with %u\n", ret); + return ble_hs_err_to_errno(ret); + } + + return 0; +} + +void mp_bt_advertise_stop(void) { + if (ble_gap_adv_active()) { + ble_gap_adv_stop(); + } +} + +static int chr_access_cb(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) { + printf("chr_access_cb: conn_handle=%u attr_handle=%u\n", conn_handle, attr_handle); + if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) { + for (struct os_mbuf *om = ctxt->om; om; om = SLIST_NEXT(om, om_next)) { + printf("write_chr: data='%.*s'\n", om->om_len, om->om_data); + } + return 0; + } + return BLE_ATT_ERR_UNLIKELY; +} + +int mp_bt_add_service(mp_bt_service_t *service, size_t num_characteristics, mp_bt_characteristic_t **characteristics) { + void *svc_data = m_malloc(2 * sizeof(struct ble_gatt_svc_def) + (num_characteristics + 1) * sizeof(struct ble_gatt_chr_def)); + + struct ble_gatt_chr_def *chr = (void*)((uint8_t*)svc_data + 2 * sizeof(struct ble_gatt_svc_def)); + for (size_t i = 0; i < num_characteristics; ++i) { + chr[i].uuid = &characteristics[i]->uuid.u; + chr[i].access_cb = chr_access_cb; + chr[i].arg = NULL; + chr[i].descriptors = NULL; + chr[i].flags = characteristics[i]->flags; + chr[i].min_key_size = 0; + chr[i].val_handle = &characteristics[i]->value_handle; + characteristics[i]->value_handle = 0xffff; // will be set at registration time + } + chr[num_characteristics].uuid = NULL; // no more characteristic + + struct ble_gatt_svc_def *svc = svc_data; + svc[0].type = BLE_GATT_SVC_TYPE_PRIMARY; + svc[0].uuid = &service->uuid.u; + svc[0].includes = NULL; + svc[0].characteristics = chr; + svc[1].type = 0; // no more services + service->handle = svc_data; // to prevent GC + + // Note: advertising must be stopped for gatts registration to work + + int ret; + + ret = ble_gatts_reset(); + if (ret != 0) { + printf("ble_gatts_reset: fail with %d\n", ret); + return ble_hs_err_to_errno(ret); + } + + ret = ble_gatts_count_cfg(svc); + if (ret != 0) { + printf("ble_gatts_count_cfg: fail with %d\n", ret); + return ble_hs_err_to_errno(ret); + } + + ret = ble_gatts_add_svcs(svc); + if (ret != 0) { + printf("ble_gatts_add_svcs: fail with %d\n", ret); + return ble_hs_err_to_errno(ret); + } + + ret = ble_gatts_start(); + if (ret != 0) { + printf("ble_gatts_start: fail with %d\n", ret); + return ble_hs_err_to_errno(ret); + } + + return 0; +} + +int mp_bt_characteristic_value_set(mp_bt_characteristic_t *characteristic, const void *value, size_t value_len) { + printf("mp_bt_characteristic_value_set: value_handle=%u\n", characteristic->value_handle); + //ENTER(); + for (size_t conn = 0; conn < MP_BT_MAX_CONNECTED_DEVICES; ++conn) { + size_t len = value_len; + const uint8_t *buf = value; + while (len && active_conn_handle[conn] != MP_BT_INVALID_CONN_HANDLE) { + size_t l = len < 20 ? len : 20; + struct os_mbuf *om = ble_hs_mbuf_from_flat(buf, l); + if (om == NULL) { + break; + } + ble_gattc_notify_custom(active_conn_handle[conn], characteristic->value_handle, om); + len -= l; + buf += l; + if (len) { + //EXIT(); + mp_hal_delay_ms(100); + //REENTER(); + } + } + } + //EXIT(); + return 0; +} + +int mp_bt_characteristic_value_notify(mp_bt_characteristic_t *characteristic, uint16_t conn_handle, const void *value, size_t value_len) { + printf("mp_bt_characteristic_value_notify\n"); + return 0; +} + +int mp_bt_characteristic_value_get(mp_bt_characteristic_t *characteristic, void *value, size_t *value_len) { + printf("mp_bt_characteristic_value_get\n"); + return 0; +} + +int mp_bt_device_disconnect(uint16_t conn_handle) { + printf("mp_bt_device_disconnect\n"); + return 0; +} + +void mp_bt_parse_uuid(mp_obj_t obj, mp_bt_uuid_t *uuid) { + if (mp_obj_is_int(obj)) { + mp_uint_t value = mp_obj_get_int_truncated(obj); + if (value <= 0xffff) { + uuid->u.type = BLE_UUID_TYPE_16; + uuid->u16.value = value; + } else { + uuid->u.type = BLE_UUID_TYPE_32; + uuid->u32.value = value; + } + } else if (mp_obj_is_str(obj)) { + uuid->u.type = BLE_UUID_TYPE_128; + mp_bt_parse_uuid_str(obj, &uuid->u128.value[0]); + } else { + mp_raise_ValueError("can't parse UUID"); + } +} + +mp_obj_t mp_bt_format_uuid(mp_bt_uuid_t *uuid) { + switch (uuid->u.type) { + case BLE_UUID_TYPE_16: + return MP_OBJ_NEW_SMALL_INT(uuid->u16.value); + case BLE_UUID_TYPE_32: + return mp_obj_new_int(uuid->u32.value); + case BLE_UUID_TYPE_128: + return mp_bt_format_uuid_str(uuid->u128.value); + default: + return mp_const_none; + } +} + +#endif // MICROPY_PY_BLUETOOTH diff --git a/ports/stm32/bluetooth/bluetooth.h b/ports/stm32/bluetooth/bluetooth.h new file mode 100644 index 0000000000000..2a104b8132038 --- /dev/null +++ b/ports/stm32/bluetooth/bluetooth.h @@ -0,0 +1,43 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Damien P. George + * + * 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. + */ +#ifndef MICROPY_INCLUDED_STM32_BLUETOOTH_BLUETOOTH_H +#define MICROPY_INCLUDED_STM32_BLUETOOTH_BLUETOOTH_H + +#include "nimble/host/include/host/ble_gap.h" + +#define MP_BT_ADV_TYPE_ADV_IND (BLE_GAP_CONN_MODE_UND) +#define MP_BT_ADV_TYPE_ADV_NONCONN_IND (BLE_GAP_CONN_MODE_NON) + +#define MP_BT_MAX_ATTR_SIZE (20) // TODO +#define MP_BT_MAX_CONNECTED_DEVICES (8) +#define MP_BT_INVALID_CONN_HANDLE (0xffff) + +typedef uint8_t mp_bt_adv_type_t; +typedef ble_uuid_any_t mp_bt_uuid_t; +typedef void *mp_bt_service_handle_t; +typedef uint16_t mp_bt_characteristic_handle_t; + +#endif // MICROPY_INCLUDED_STM32_BLUETOOTH_BLUETOOTH_H diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index 5095dde52b8d8..245f155f982b1 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -207,6 +207,7 @@ extern const struct _mp_obj_module_t mp_module_uos; extern const struct _mp_obj_module_t mp_module_utime; extern const struct _mp_obj_module_t mp_module_usocket; 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; #if MICROPY_PY_STM @@ -241,6 +242,12 @@ extern const struct _mp_obj_module_t mp_module_onewire; #define NETWORK_BUILTIN_MODULE #endif +#if MICROPY_PY_NETWORK +#define BLUETOOTH_BUILTIN_MODULE { MP_ROM_QSTR(MP_QSTR_bluetooth), MP_ROM_PTR(&mp_module_bluetooth) }, +#else +#define BLUETOOTH_BUILTIN_MODULE +#endif + #define MICROPY_PORT_BUILTIN_MODULES \ { MP_ROM_QSTR(MP_QSTR_umachine), MP_ROM_PTR(&machine_module) }, \ { MP_ROM_QSTR(MP_QSTR_pyb), MP_ROM_PTR(&pyb_module) }, \ @@ -249,6 +256,7 @@ extern const struct _mp_obj_module_t mp_module_onewire; { MP_ROM_QSTR(MP_QSTR_utime), MP_ROM_PTR(&mp_module_utime) }, \ SOCKET_BUILTIN_MODULE \ NETWORK_BUILTIN_MODULE \ + BLUETOOTH_BUILTIN_MODULE \ { MP_ROM_QSTR(MP_QSTR__onewire), MP_ROM_PTR(&mp_module_onewire) }, \ #define MICROPY_PORT_BUILTIN_MODULE_WEAK_LINKS \ From 84cb47f73b184671348b901b0bd9864d07102737 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 4 Jul 2019 18:07:59 +1000 Subject: [PATCH 28/28] stm32/boards/PYBD_SFx: Enable bluetooth module. --- ports/stm32/boards/PYBD_SF2/mpconfigboard.mk | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ports/stm32/boards/PYBD_SF2/mpconfigboard.mk b/ports/stm32/boards/PYBD_SF2/mpconfigboard.mk index 489d2f893a915..834a60b029ee8 100644 --- a/ports/stm32/boards/PYBD_SF2/mpconfigboard.mk +++ b/ports/stm32/boards/PYBD_SF2/mpconfigboard.mk @@ -10,6 +10,8 @@ TEXT0_SECTIONS = .isr_vector .text .data TEXT1_SECTIONS = .text_ext # MicroPython settings +MICROPY_PY_BLUETOOTH = 1 +MICROPY_BLUETOOTH_NIMBLE = 1 MICROPY_PY_LWIP = 1 MICROPY_PY_NETWORK_CYW43 = 1 MICROPY_PY_USSL = 1 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