/* * This file is part of the MicroPython project, http://micropython.org/ * * The MIT License (MIT) * * Copyright (c) 2018 Artur Pacholec * * 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 #include "ble.h" #include "ble_drv.h" #include "ble_hci.h" #include "nrf_soc.h" #include "py/objstr.h" #include "py/runtime.h" #include "shared-bindings/bleio/Adapter.h" #include "shared-bindings/bleio/Characteristic.h" #include "shared-bindings/bleio/Device.h" #include "shared-bindings/bleio/Service.h" #include "shared-bindings/bleio/UUID.h" #define BLE_MIN_CONN_INTERVAL MSEC_TO_UNITS(15, UNIT_0_625_MS) #define BLE_MAX_CONN_INTERVAL MSEC_TO_UNITS(300, UNIT_0_625_MS) #define BLE_SLAVE_LATENCY 0 #define BLE_CONN_SUP_TIMEOUT MSEC_TO_UNITS(4000, UNIT_10_MS) #define BLE_ADV_LENGTH_FIELD_SIZE 1 #define BLE_ADV_AD_TYPE_FIELD_SIZE 1 #define BLE_AD_TYPE_FLAGS_DATA_SIZE 1 #ifndef BLE_GAP_ADV_MAX_SIZE #define BLE_GAP_ADV_MAX_SIZE 31 #endif static bleio_service_obj_t *m_char_discovery_service; static volatile bool m_discovery_successful; static nrf_mutex_t *m_discovery_mutex; #if (BLUETOOTH_SD == 140) static uint8_t m_adv_handle = BLE_GAP_ADV_SET_HANDLE_NOT_SET; static uint8_t m_scan_buffer_data[BLE_GAP_SCAN_BUFFER_MIN]; static ble_data_t m_scan_buffer = { .p_data = m_scan_buffer_data, .len = BLE_GAP_SCAN_BUFFER_MIN }; #endif STATIC uint32_t set_advertisement_data(bleio_device_obj_t *device, bool connectable, mp_buffer_info_t *raw_data) { common_hal_bleio_adapter_set_enabled(true); uint8_t adv_data[BLE_GAP_ADV_MAX_SIZE]; uint8_t byte_pos = 0; uint32_t err_code; #define ADD_FIELD(field, len) \ do { \ if (byte_pos + (len) > BLE_GAP_ADV_MAX_SIZE) { \ mp_raise_ValueError(translate("Data too large for the advertisement packet")); \ } \ adv_data[byte_pos] = (field); \ byte_pos += (len); \ } while (0) GET_STR_DATA_LEN(device->name, name_data, name_len); if (name_len > 0) { ble_gap_conn_sec_mode_t sec_mode; BLE_GAP_CONN_SEC_MODE_SET_OPEN(&sec_mode); err_code = sd_ble_gap_device_name_set(&sec_mode, name_data, name_len); if (err_code != NRF_SUCCESS) { return err_code; } // TODO: Shorten if too long ADD_FIELD(BLE_ADV_AD_TYPE_FIELD_SIZE + name_len, BLE_ADV_LENGTH_FIELD_SIZE); ADD_FIELD(BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME, BLE_ADV_AD_TYPE_FIELD_SIZE); memcpy(&adv_data[byte_pos], name_data, name_len); byte_pos += name_len; } // set flags, default to disc mode if (raw_data->len == 0) { ADD_FIELD(BLE_ADV_AD_TYPE_FIELD_SIZE + BLE_AD_TYPE_FLAGS_DATA_SIZE, BLE_ADV_LENGTH_FIELD_SIZE); ADD_FIELD(BLE_GAP_AD_TYPE_FLAGS, BLE_AD_TYPE_FLAGS_DATA_SIZE); ADD_FIELD(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE, BLE_AD_TYPE_FLAGS_DATA_SIZE); } else { if (byte_pos + raw_data->len > BLE_GAP_ADV_MAX_SIZE) { mp_raise_ValueError(translate("Data too large for the advertisement packet")); } memcpy(&adv_data[byte_pos], raw_data->buf, raw_data->len); byte_pos += raw_data->len; } const mp_obj_list_t *service_list = MP_OBJ_TO_PTR(device->service_list); if (service_list->len > 0) { bool has_128bit_services = false; bool has_16bit_services = false; for (size_t i = 0; i < service_list->len; ++i) { const bleio_service_obj_t *service = MP_OBJ_TO_PTR(service_list->items[i]); if (service->is_secondary) { continue; } switch (common_hal_bleio_uuid_get_size(service->uuid)) { case 16: has_16bit_services = true; break; case 128: has_128bit_services = true; break; } } if (has_16bit_services) { const uint8_t size_byte_pos = byte_pos; uint8_t uuid_total_size = 0; // skip length byte for now, apply total length post calculation byte_pos += BLE_ADV_LENGTH_FIELD_SIZE; ADD_FIELD(BLE_GAP_AD_TYPE_16BIT_SERVICE_UUID_COMPLETE, BLE_ADV_AD_TYPE_FIELD_SIZE); for (size_t i = 0; i < service_list->len; ++i) { const bleio_service_obj_t *service = MP_OBJ_TO_PTR(service_list->items[i]); uint8_t encoded_size = 0; if (common_hal_bleio_uuid_get_size(service->uuid) != 16 || service->is_secondary) { continue; } ble_uuid_t uuid; bleio_uuid_convert_to_nrf_uuid(service->uuid, &uuid); err_code = sd_ble_uuid_encode(&uuid, &encoded_size, &adv_data[byte_pos]); if (err_code != NRF_SUCCESS) { return err_code; } uuid_total_size += encoded_size; byte_pos += encoded_size; } adv_data[size_byte_pos] = (BLE_ADV_AD_TYPE_FIELD_SIZE + uuid_total_size); } if (has_128bit_services) { const uint8_t size_byte_pos = byte_pos; uint8_t uuid_total_size = 0; // skip length byte for now, apply total length post calculation byte_pos += BLE_ADV_LENGTH_FIELD_SIZE; ADD_FIELD(BLE_GAP_AD_TYPE_128BIT_SERVICE_UUID_COMPLETE, BLE_ADV_AD_TYPE_FIELD_SIZE); for (size_t i = 0; i < service_list->len; ++i) { const bleio_service_obj_t *service = MP_OBJ_TO_PTR(service_list->items[i]); uint8_t encoded_size = 0; if (common_hal_bleio_uuid_get_size(service->uuid) != 16 || service->is_secondary) { continue; } ble_uuid_t uuid; bleio_uuid_convert_to_nrf_uuid(service->uuid, &uuid); err_code = sd_ble_uuid_encode(&uuid, &encoded_size, &adv_data[byte_pos]); if (err_code != NRF_SUCCESS) { return err_code; } uuid_total_size += encoded_size; byte_pos += encoded_size; } adv_data[size_byte_pos] = (BLE_ADV_AD_TYPE_FIELD_SIZE + uuid_total_size); } } #if (BLUETOOTH_SD == 132) err_code = sd_ble_gap_adv_data_set(adv_data, byte_pos, NULL, 0); if (err_code != NRF_SUCCESS) { return err_code; } #endif static ble_gap_adv_params_t m_adv_params = { .interval = MSEC_TO_UNITS(100, UNIT_0_625_MS), #if (BLUETOOTH_SD == 140) .properties.type = BLE_GAP_ADV_TYPE_CONNECTABLE_SCANNABLE_UNDIRECTED, .duration = BLE_GAP_ADV_TIMEOUT_GENERAL_UNLIMITED, .filter_policy = BLE_GAP_ADV_FP_ANY, .primary_phy = BLE_GAP_PHY_1MBPS, #else .type = BLE_GAP_ADV_TYPE_ADV_IND, .fp = BLE_GAP_ADV_FP_ANY, #endif }; if (!connectable) { #if (BLUETOOTH_SD == 140) m_adv_params.properties.type = BLE_GAP_ADV_TYPE_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED; #else m_adv_params.type = BLE_GAP_ADV_TYPE_ADV_NONCONN_IND; #endif } common_hal_bleio_device_stop_advertising(device); #if (BLUETOOTH_SD == 140) const ble_gap_adv_data_t ble_gap_adv_data = { .adv_data.p_data = adv_data, .adv_data.len = byte_pos, }; err_code = sd_ble_gap_adv_set_configure(&m_adv_handle, &ble_gap_adv_data, &m_adv_params); if (err_code != NRF_SUCCESS) { return err_code; } err_code = sd_ble_gap_adv_start(m_adv_handle, BLE_CONN_CFG_TAG_CUSTOM); #elif (BLUETOOTH_SD == 132 && BLE_API_VERSION == 4) err_code = sd_ble_gap_adv_start(&m_adv_params, BLE_CONN_CFG_TAG_CUSTOM); #else err_code = sd_ble_gap_adv_start(&m_adv_params); #endif return err_code; } STATIC bool discover_services(bleio_device_obj_t *device, uint16_t start_handle) { m_discovery_successful = false; uint32_t err_code = sd_ble_gattc_primary_services_discover(device->conn_handle, start_handle, NULL); if (err_code != NRF_SUCCESS) { mp_raise_OSError_msg(translate("Failed to discover services")); } err_code = sd_mutex_acquire(m_discovery_mutex); if (err_code != NRF_SUCCESS) { mp_raise_OSError_msg(translate("Failed to acquire mutex")); } while (sd_mutex_acquire(m_discovery_mutex) == NRF_ERROR_SOC_MUTEX_ALREADY_TAKEN) { #ifdef MICROPY_VM_HOOK_LOOP MICROPY_VM_HOOK_LOOP #endif } err_code = sd_mutex_release(m_discovery_mutex); if (err_code != NRF_SUCCESS) { mp_raise_OSError_msg(translate("Failed to release mutex")); } return m_discovery_successful; } STATIC bool discover_characteristics(bleio_device_obj_t *device, bleio_service_obj_t *service, uint16_t start_handle) { m_char_discovery_service = service; ble_gattc_handle_range_t handle_range; handle_range.start_handle = start_handle; handle_range.end_handle = service->end_handle; m_discovery_successful = false; uint32_t err_code = sd_ble_gattc_characteristics_discover(device->conn_handle, &handle_range); if (err_code != NRF_SUCCESS) { return false; } err_code = sd_mutex_acquire(m_discovery_mutex); if (err_code != NRF_SUCCESS) { mp_raise_OSError_msg(translate("Failed to acquire mutex")); } while (sd_mutex_acquire(m_discovery_mutex) == NRF_ERROR_SOC_MUTEX_ALREADY_TAKEN) { #ifdef MICROPY_VM_HOOK_LOOP MICROPY_VM_HOOK_LOOP #endif } err_code = sd_mutex_release(m_discovery_mutex); if (err_code != NRF_SUCCESS) { mp_raise_OSError_msg(translate("Failed to release mutex")); } return m_discovery_successful; } STATIC void on_primary_srv_discovery_rsp(ble_gattc_evt_prim_srvc_disc_rsp_t *response, bleio_device_obj_t *device) { for (size_t i = 0; i < response->count; ++i) { ble_gattc_service_t *gattc_service = &response->services[i]; bleio_service_obj_t *service = m_new_obj(bleio_service_obj_t); service->base.type = &bleio_service_type; service->device = device; service->char_list = mp_obj_new_list(0, NULL); service->start_handle = gattc_service->handle_range.start_handle; service->end_handle = gattc_service->handle_range.end_handle; service->handle = gattc_service->handle_range.start_handle; bleio_uuid_obj_t *uuid = m_new_obj(bleio_uuid_obj_t); bleio_uuid_construct_from_nrf_uuid(uuid, &gattc_service->uuid); service->uuid = uuid; mp_obj_list_append(device->service_list, service); } if (response->count > 0) { m_discovery_successful = true; } const uint32_t err_code = sd_mutex_release(m_discovery_mutex); if (err_code != NRF_SUCCESS) { mp_raise_OSError_msg(translate("Failed to release mutex")); } } STATIC void on_char_discovery_rsp(ble_gattc_evt_char_disc_rsp_t *response, bleio_device_obj_t *device) { for (size_t i = 0; i < response->count; ++i) { ble_gattc_char_t *gattc_char = &response->chars[i]; bleio_characteristic_obj_t *characteristic = m_new_obj(bleio_characteristic_obj_t); characteristic->base.type = &bleio_characteristic_type; bleio_uuid_obj_t *uuid = m_new_obj(bleio_uuid_obj_t); uuid->base.type = &bleio_uuid_type; bleio_uuid_construct_from_nrf_uuid(uuid, &gattc_char->uuid); characteristic->uuid = uuid; characteristic->props.broadcast = gattc_char->char_props.broadcast; characteristic->props.indicate = gattc_char->char_props.indicate; characteristic->props.notify = gattc_char->char_props.notify; characteristic->props.read = gattc_char->char_props.read; characteristic->props.write = gattc_char->char_props.write; characteristic->props.write_wo_resp = gattc_char->char_props.write_wo_resp; characteristic->handle = gattc_char->handle_value; characteristic->service = m_char_discovery_service; mp_obj_list_append(m_char_discovery_service->char_list, MP_OBJ_FROM_PTR(characteristic)); } if (response->count > 0) { m_discovery_successful = true; } const uint32_t err_code = sd_mutex_release(m_discovery_mutex); if (err_code != NRF_SUCCESS) { mp_raise_OSError_msg(translate("Failed to release mutex")); } } STATIC void on_adv_report(ble_gap_evt_adv_report_t *report, bleio_device_obj_t *device) { uint32_t err_code; if (memcmp(report->peer_addr.addr, device->address.value, BLEIO_ADDRESS_BYTES) != 0) { #if (BLUETOOTH_SD == 140) err_code = sd_ble_gap_scan_start(NULL, &m_scan_buffer); if (err_code != NRF_SUCCESS) { mp_raise_OSError_msg(translate("Failed to continue scanning")); } #endif return; } ble_gap_scan_params_t scan_params = { .active = 1, .interval = MSEC_TO_UNITS(100, UNIT_0_625_MS), .window = MSEC_TO_UNITS(100, UNIT_0_625_MS), }; ble_gap_addr_t addr; memset(&addr, 0, sizeof(addr)); addr.addr_type = report->peer_addr.addr_type; memcpy(addr.addr, report->peer_addr.addr, BLEIO_ADDRESS_BYTES); ble_gap_conn_params_t conn_params = { .min_conn_interval = BLE_MIN_CONN_INTERVAL, .max_conn_interval = BLE_MAX_CONN_INTERVAL, .conn_sup_timeout = BLE_CONN_SUP_TIMEOUT, .slave_latency = BLE_SLAVE_LATENCY, }; #if (BLE_API_VERSION == 2) err_code = sd_ble_gap_connect(&addr, &scan_params, &conn_params); #else err_code = sd_ble_gap_connect(&addr, &scan_params, &conn_params, BLE_CONN_CFG_TAG_CUSTOM); #endif if (err_code != NRF_SUCCESS) { mp_raise_OSError_msg(translate("Failed to connect:")); } } STATIC void on_ble_evt(ble_evt_t *ble_evt, void *device_in) { bleio_device_obj_t *device = (bleio_device_obj_t*)device_in; switch (ble_evt->header.evt_id) { case BLE_GAP_EVT_CONNECTED: { ble_gap_conn_params_t conn_params; device->conn_handle = ble_evt->evt.gap_evt.conn_handle; sd_ble_gap_ppcp_get(&conn_params); sd_ble_gap_conn_param_update(ble_evt->evt.gap_evt.conn_handle, &conn_params); break; } case BLE_GAP_EVT_DISCONNECTED: device->conn_handle = BLE_CONN_HANDLE_INVALID; break; case BLE_GAP_EVT_ADV_REPORT: on_adv_report(&ble_evt->evt.gap_evt.params.adv_report, device); break; case BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP: on_primary_srv_discovery_rsp(&ble_evt->evt.gattc_evt.params.prim_srvc_disc_rsp, device); break; case BLE_GATTC_EVT_CHAR_DISC_RSP: on_char_discovery_rsp(&ble_evt->evt.gattc_evt.params.char_disc_rsp, device); break; case BLE_GATTS_EVT_SYS_ATTR_MISSING: sd_ble_gatts_sys_attr_set(ble_evt->evt.gatts_evt.conn_handle, NULL, 0, 0); break; #if (BLE_API_VERSION == 4) case BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST: sd_ble_gatts_exchange_mtu_reply(device->conn_handle, BLE_GATT_ATT_MTU_DEFAULT); break; #endif case BLE_GAP_EVT_SEC_PARAMS_REQUEST: sd_ble_gap_sec_params_reply(device->conn_handle, BLE_GAP_SEC_STATUS_PAIRING_NOT_SUPP, NULL, NULL); break; case BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST: { ble_gap_evt_conn_param_update_request_t *request = &ble_evt->evt.gap_evt.params.conn_param_update_request; sd_ble_gap_conn_param_update(device->conn_handle, &request->conn_params); break; } } } void common_hal_bleio_device_add_service(bleio_device_obj_t *device, bleio_service_obj_t *service) { ble_uuid_t uuid; bleio_uuid_convert_to_nrf_uuid(service->uuid, &uuid); uint8_t service_type = BLE_GATTS_SRVC_TYPE_PRIMARY; if (service->is_secondary) { service_type = BLE_GATTS_SRVC_TYPE_SECONDARY; } common_hal_bleio_adapter_set_enabled(true); const uint32_t err_code = sd_ble_gatts_service_add(service_type, &uuid, &service->handle); if (err_code != NRF_SUCCESS) { mp_raise_OSError_msg(translate("Failed to add service")); } const mp_obj_list_t *char_list = MP_OBJ_TO_PTR(service->char_list); for (size_t i = 0; i < char_list->len; ++i) { bleio_characteristic_obj_t *characteristic = char_list->items[i]; common_hal_bleio_service_add_characteristic(service, characteristic); } } void common_hal_bleio_device_start_advertising(bleio_device_obj_t *device, bool connectable, mp_buffer_info_t *raw_data) { if (connectable) { ble_drv_add_event_handler(on_ble_evt, device); } const uint32_t err_code = set_advertisement_data(device, connectable, raw_data); if (err_code != NRF_SUCCESS) { mp_raise_OSError_msg(translate("Failed to start advertising")); } } void common_hal_bleio_device_stop_advertising(bleio_device_obj_t *device) { uint32_t err_code; #if (BLUETOOTH_SD == 140) if (m_adv_handle == BLE_GAP_ADV_SET_HANDLE_NOT_SET) return; err_code = sd_ble_gap_adv_stop(m_adv_handle); #else err_code = sd_ble_gap_adv_stop(); #endif if ((err_code != NRF_SUCCESS) && (err_code != NRF_ERROR_INVALID_STATE)) { mp_raise_OSError_msg(translate("Failed to stop advertising")); } } void common_hal_bleio_device_connect(bleio_device_obj_t *device) { ble_drv_add_event_handler(on_ble_evt, device); ble_gap_scan_params_t scan_params = { .interval = MSEC_TO_UNITS(100, UNIT_0_625_MS), .window = MSEC_TO_UNITS(100, UNIT_0_625_MS), #if (BLUETOOTH_SD == 140) .scan_phys = BLE_GAP_PHY_1MBPS, #endif }; common_hal_bleio_adapter_set_enabled(true); uint32_t err_code; #if (BLUETOOTH_SD == 140) err_code = sd_ble_gap_scan_start(&scan_params, &m_scan_buffer); #else err_code = sd_ble_gap_scan_start(&scan_params); #endif if (err_code != NRF_SUCCESS) { mp_raise_OSError_msg(translate("Failed to start scanning")); } while (device->conn_handle == BLE_CONN_HANDLE_INVALID) { #ifdef MICROPY_VM_HOOK_LOOP MICROPY_VM_HOOK_LOOP #endif } // TODO: read name if (m_discovery_mutex == NULL) { m_discovery_mutex = m_new_ll(nrf_mutex_t, 1); err_code = sd_mutex_new(m_discovery_mutex); if (err_code != NRF_SUCCESS) { mp_raise_OSError_msg(translate("Failed to create mutex")); } } // find services bool found_service = discover_services(device, BLE_GATT_HANDLE_START); while (found_service) { const mp_obj_list_t *service_list = MP_OBJ_TO_PTR(device->service_list); const bleio_service_obj_t *service = service_list->items[service_list->len - 1]; found_service = discover_services(device, service->end_handle + 1); } // find characteristics in each service const mp_obj_list_t *service_list = MP_OBJ_TO_PTR(device->service_list); for (size_t i = 0; i < service_list->len; ++i) { bleio_service_obj_t *service = service_list->items[i]; bool found_char = discover_characteristics(device, service, service->start_handle); while (found_char) { const mp_obj_list_t *char_list = MP_OBJ_TO_PTR(service->char_list); const bleio_characteristic_obj_t *characteristic = char_list->items[char_list->len - 1]; const uint16_t next_handle = characteristic->handle + 1; if (next_handle >= service->end_handle) { break; } found_char = discover_characteristics(device, service, next_handle); } } } void common_hal_bleio_device_disconnect(bleio_device_obj_t *device) { sd_ble_gap_disconnect(device->conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION); }