diff --git a/ports/nrf/common-hal/bleio/Characteristic.c b/ports/nrf/common-hal/bleio/Characteristic.c index 41fcdd1f19..e0ddf1e813 100644 --- a/ports/nrf/common-hal/bleio/Characteristic.c +++ b/ports/nrf/common-hal/bleio/Characteristic.c @@ -25,30 +25,23 @@ * THE SOFTWARE. */ -#include -#include - -#include "ble_drv.h" -#include "ble_gatts.h" -#include "nrf_soc.h" - #include "py/runtime.h" #include "shared-bindings/bleio/__init__.h" #include "shared-bindings/bleio/Characteristic.h" +#include "shared-bindings/bleio/Descriptor.h" #include "shared-bindings/bleio/Service.h" -STATIC volatile bleio_characteristic_obj_t *m_read_characteristic; +static volatile bleio_characteristic_obj_t *m_read_characteristic; -STATIC uint16_t get_cccd(bleio_characteristic_obj_t *characteristic) { - const uint16_t conn_handle = common_hal_bleio_device_get_conn_handle(characteristic->service->device); +STATIC uint16_t characteristic_get_cccd(uint16_t cccd_handle, uint16_t conn_handle) { uint16_t cccd; ble_gatts_value_t value = { .p_value = (uint8_t*) &cccd, .len = 2, }; - const uint32_t err_code = sd_ble_gatts_value_get(conn_handle, characteristic->cccd_handle, &value); + const uint32_t err_code = sd_ble_gatts_value_get(conn_handle, cccd_handle, &value); if (err_code == BLE_ERROR_GATTS_SYS_ATTR_MISSING) { @@ -61,64 +54,39 @@ STATIC uint16_t get_cccd(bleio_characteristic_obj_t *characteristic) { return cccd; } -STATIC void gatts_read(bleio_characteristic_obj_t *characteristic) { - // This might be BLE_CONN_HANDLE_INVALID if we're not connected, but that's OK, because - // we can still read and write the local value. - const uint16_t conn_handle = common_hal_bleio_device_get_conn_handle(characteristic->service->device); +STATIC void characteristic_on_gattc_read_rsp_evt(ble_evt_t *ble_evt, void *param) { + switch (ble_evt->header.evt_id) { - mp_buffer_info_t bufinfo; - ble_gatts_value_t gatts_value = { - .p_value = NULL, - .len = 0, - }; + // More events may be handled later, so keep this as a switch. - // Read once to find out what size buffer we need, then read again to fill buffer. + case BLE_GATTC_EVT_READ_RSP: { + ble_gattc_evt_read_rsp_t *response = &ble_evt->evt.gattc_evt.params.read_rsp; + if (m_read_characteristic) { + m_read_characteristic->value = mp_obj_new_bytearray(response->len, response->data); + } + // Indicate to busy-wait loop that we've read the attribute value. + m_read_characteristic = NULL; + break; + } - uint32_t err_code = sd_ble_gatts_value_get(conn_handle, characteristic->handle, &gatts_value); - if (err_code == NRF_SUCCESS) { - characteristic->value_data = mp_obj_new_bytearray_of_zeros(gatts_value.len); - mp_get_buffer_raise(characteristic->value_data, &bufinfo, MP_BUFFER_WRITE); - gatts_value.p_value = bufinfo.buf; - - // Read again, with the correct size of buffer. - err_code = sd_ble_gatts_value_get(conn_handle, characteristic->handle, &gatts_value); - } - - if (err_code != NRF_SUCCESS) { - mp_raise_OSError_msg_varg(translate("Failed to read gatts value, err 0x%04x"), err_code); + default: + // For debugging. + // mp_printf(&mp_plat_print, "Unhandled characteristic event: 0x%04x\n", ble_evt->header.evt_id); + break; } } - -STATIC void gatts_write(bleio_characteristic_obj_t *characteristic, mp_buffer_info_t *bufinfo) { - // This might be BLE_CONN_HANDLE_INVALID if we're not conected, but that's OK, because - // we can still read and write the local value. - const uint16_t conn_handle = common_hal_bleio_device_get_conn_handle(characteristic->service->device); - - ble_gatts_value_t gatts_value = { - .p_value = bufinfo->buf, - .len = bufinfo->len, - }; - - const uint32_t err_code = sd_ble_gatts_value_set(conn_handle, characteristic->handle, &gatts_value); - if (err_code != NRF_SUCCESS) { - mp_raise_OSError_msg_varg(translate("Failed to write gatts value, err 0x%04x"), err_code); - } -} - -STATIC void gatts_notify_indicate(bleio_characteristic_obj_t *characteristic, mp_buffer_info_t *bufinfo, uint16_t hvx_type) { +STATIC void characteristic_gatts_notify_indicate(uint16_t handle, uint16_t conn_handle, mp_buffer_info_t *bufinfo, uint16_t hvx_type) { uint16_t hvx_len = bufinfo->len; ble_gatts_hvx_params_t hvx_params = { - .handle = characteristic->handle, + .handle = handle, .type = hvx_type, .offset = 0, .p_len = &hvx_len, .p_data = bufinfo->buf, }; - const uint16_t conn_handle = common_hal_bleio_device_get_conn_handle(characteristic->service->device); - while (1) { const uint32_t err_code = sd_ble_gatts_hvx(conn_handle, &hvx_params); if (err_code == NRF_SUCCESS) { @@ -134,21 +102,17 @@ STATIC void gatts_notify_indicate(bleio_characteristic_obj_t *characteristic, mp // Some real error has occurred. mp_raise_OSError_msg_varg(translate("Failed to notify or indicate attribute value, err 0x%04x"), err_code); } - } -STATIC void check_connected(uint16_t conn_handle) { - if (conn_handle == BLE_CONN_HANDLE_INVALID) { - mp_raise_OSError_msg(translate("Not connected")); - } -} - -STATIC void gattc_read(bleio_characteristic_obj_t *characteristic) { +STATIC void characteristic_gattc_read(bleio_characteristic_obj_t *characteristic) { const uint16_t conn_handle = common_hal_bleio_device_get_conn_handle(characteristic->service->device); - check_connected(conn_handle); + common_hal_bleio_check_connected(conn_handle); + // Set to NULL in event loop after event. m_read_characteristic = characteristic; + ble_drv_add_event_handler(characteristic_on_gattc_read_rsp_evt, characteristic); + const uint32_t err_code = sd_ble_gattc_read(conn_handle, characteristic->handle, 0); if (err_code != NRF_SUCCESS) { mp_raise_OSError_msg_varg(translate("Failed to read attribute value, err 0x%04x"), err_code); @@ -157,91 +121,54 @@ STATIC void gattc_read(bleio_characteristic_obj_t *characteristic) { while (m_read_characteristic != NULL) { MICROPY_VM_HOOK_LOOP; } + + ble_drv_remove_event_handler(characteristic_on_gattc_read_rsp_evt, characteristic); } -STATIC void gattc_write(bleio_characteristic_obj_t *characteristic, mp_buffer_info_t *bufinfo) { - const uint16_t conn_handle = common_hal_bleio_device_get_conn_handle(characteristic->service->device); - check_connected(conn_handle); - - ble_gattc_write_params_t write_params = { - .write_op = (characteristic->props & CHAR_PROP_WRITE_NO_RESPONSE) - ? BLE_GATT_OP_WRITE_CMD: BLE_GATT_OP_WRITE_REQ, - .handle = characteristic->handle, - .p_value = bufinfo->buf, - .len = bufinfo->len, - }; - - while (1) { - uint32_t err_code = sd_ble_gattc_write(conn_handle, &write_params); - if (err_code == NRF_SUCCESS) { - break; - } - - // Write with response will return NRF_ERROR_BUSY if the response has not been received. - // Write without reponse will return NRF_ERROR_RESOURCES if too many writes are pending. - if (err_code == NRF_ERROR_BUSY || err_code == NRF_ERROR_RESOURCES) { - // We could wait for an event indicating the write is complete, but just retrying is easier. - MICROPY_VM_HOOK_LOOP; - continue; - } - - // Some real error occurred. - mp_raise_OSError_msg_varg(translate("Failed to write attribute value, err 0x%04x"), err_code); - } - -} - -STATIC void characteristic_on_ble_evt(ble_evt_t *ble_evt, void *param) { - switch (ble_evt->header.evt_id) { - - // More events may be handled later, so keep this as a switch. - - case BLE_GATTC_EVT_READ_RSP: { - ble_gattc_evt_read_rsp_t *response = &ble_evt->evt.gattc_evt.params.read_rsp; - m_read_characteristic->value_data = mp_obj_new_bytearray(response->len, response->data); - // Indicate to busy-wait loop that we've read the characteristic. - m_read_characteristic = NULL; - break; - } - - default: - // For debugging. - // mp_printf(&mp_plat_print, "Unhandled characteristic event: 0x%04x\n", ble_evt->header.evt_id); - break; - } - -} - -void common_hal_bleio_characteristic_construct(bleio_characteristic_obj_t *self, bleio_uuid_obj_t *uuid, bleio_characteristic_properties_t props, bleio_attribute_security_mode_t security_mode, mp_obj_t descriptors) { - self->service = mp_const_none; +void common_hal_bleio_characteristic_construct(bleio_characteristic_obj_t *self, bleio_uuid_obj_t *uuid, bleio_characteristic_properties_t props, bleio_attribute_security_mode_t read_perm, bleio_attribute_security_mode_t write_perm, mp_obj_list_t *descriptor_list) { + self->service = MP_OBJ_NULL; self->uuid = uuid; - self->value_data = mp_const_none; - self->props = props; - self->security_mode = security_mode; - self->descriptor_list = mp_obj_new_list_from_iter(descriptors); - + self->value = mp_const_none; self->handle = BLE_GATT_HANDLE_INVALID; + self->props = props; + self->read_perm = read_perm; + self->write_perm = write_perm; + self->descriptor_list = descriptor_list; - ble_drv_add_event_handler(characteristic_on_ble_evt, self); + for (size_t descriptor_idx = 0; descriptor_idx < descriptor_list->len; ++descriptor_idx) { + bleio_descriptor_obj_t *descriptor = + MP_OBJ_TO_PTR(descriptor_list->items[descriptor_idx]); + descriptor->characteristic = self; + } } mp_obj_list_t *common_hal_bleio_characteristic_get_descriptor_list(bleio_characteristic_obj_t *self) { return self->descriptor_list; } +bleio_service_obj_t *common_hal_bleio_characteristic_get_service(bleio_characteristic_obj_t *self) { + return self->service; +} + mp_obj_t common_hal_bleio_characteristic_get_value(bleio_characteristic_obj_t *self) { + uint16_t conn_handle = common_hal_bleio_device_get_conn_handle(self->service->device); if (common_hal_bleio_service_get_is_remote(self->service)) { - gattc_read(self); + // self->value is set by evt handler. + characteristic_gattc_read(self); } else { - gatts_read(self); + self->value = common_hal_bleio_gatts_read(self->handle, conn_handle); } - return self->value_data; + return self->value; } void common_hal_bleio_characteristic_set_value(bleio_characteristic_obj_t *self, mp_buffer_info_t *bufinfo) { + uint16_t conn_handle = common_hal_bleio_device_get_conn_handle(self->service->device); + if (common_hal_bleio_service_get_is_remote(self->service)) { - gattc_write(self, bufinfo); + // Last argument is true if write-no-reponse desired. + common_hal_bleio_gattc_write(self->handle, conn_handle, bufinfo, + (self->props & CHAR_PROP_WRITE_NO_RESPONSE)); } else { bool sent = false; uint16_t cccd = 0; @@ -249,26 +176,25 @@ void common_hal_bleio_characteristic_set_value(bleio_characteristic_obj_t *self, const bool notify = self->props & CHAR_PROP_NOTIFY; const bool indicate = self->props & CHAR_PROP_INDICATE; if (notify | indicate) { - cccd = get_cccd(self); + cccd = characteristic_get_cccd(self->cccd_handle, conn_handle); } // It's possible that both notify and indicate are set. if (notify && (cccd & BLE_GATT_HVX_NOTIFICATION)) { - gatts_notify_indicate(self, bufinfo, BLE_GATT_HVX_NOTIFICATION); + characteristic_gatts_notify_indicate(self->handle, conn_handle, bufinfo, BLE_GATT_HVX_NOTIFICATION); sent = true; } if (indicate && (cccd & BLE_GATT_HVX_INDICATION)) { - gatts_notify_indicate(self, bufinfo, BLE_GATT_HVX_INDICATION); + characteristic_gatts_notify_indicate(self->handle, conn_handle, bufinfo, BLE_GATT_HVX_INDICATION); sent = true; } if (!sent) { - gatts_write(self, bufinfo); + common_hal_bleio_gatts_write(self->handle, conn_handle, bufinfo); } } } - bleio_uuid_obj_t *common_hal_bleio_characteristic_get_uuid(bleio_characteristic_obj_t *self) { return self->uuid; } @@ -286,14 +212,13 @@ void common_hal_bleio_characteristic_set_cccd(bleio_characteristic_obj_t *self, mp_raise_ValueError(translate("Can't set CCCD on local Characteristic")); } + const uint16_t conn_handle = common_hal_bleio_device_get_conn_handle(self->service->device); + common_hal_bleio_check_connected(conn_handle); + uint16_t cccd_value = (notify ? BLE_GATT_HVX_NOTIFICATION : 0) | (indicate ? BLE_GATT_HVX_INDICATION : 0); - const uint16_t conn_handle = common_hal_bleio_device_get_conn_handle(self->service->device); - check_connected(conn_handle); - - ble_gattc_write_params_t write_params = { .write_op = BLE_GATT_OP_WRITE_REQ, .handle = self->cccd_handle, diff --git a/ports/nrf/common-hal/bleio/Characteristic.h b/ports/nrf/common-hal/bleio/Characteristic.h index 8c8309f09d..46f46eee54 100644 --- a/ports/nrf/common-hal/bleio/Characteristic.h +++ b/ports/nrf/common-hal/bleio/Characteristic.h @@ -35,12 +35,14 @@ typedef struct { mp_obj_base_t base; + // Will be MP_OBJ_NULL before being assigned to a Service. bleio_service_obj_t *service; bleio_uuid_obj_t *uuid; - mp_obj_t value_data; + mp_obj_t value; uint16_t handle; bleio_characteristic_properties_t props; - bleio_attribute_security_mode_t security_mode; + bleio_attribute_security_mode_t read_perm; + bleio_attribute_security_mode_t write_perm; mp_obj_list_t *descriptor_list; uint16_t user_desc_handle; uint16_t cccd_handle; diff --git a/ports/nrf/common-hal/bleio/Descriptor.c b/ports/nrf/common-hal/bleio/Descriptor.c index ad62b25355..d6473a64fd 100644 --- a/ports/nrf/common-hal/bleio/Descriptor.c +++ b/ports/nrf/common-hal/bleio/Descriptor.c @@ -26,13 +26,93 @@ * THE SOFTWARE. */ -#include "common-hal/bleio/Descriptor.h" +#include "py/runtime.h" + +#include "shared-bindings/bleio/__init__.h" +#include "shared-bindings/bleio/Descriptor.h" +#include "shared-bindings/bleio/Service.h" #include "shared-bindings/bleio/UUID.h" -void common_hal_bleio_descriptor_construct(bleio_descriptor_obj_t *self, bleio_uuid_obj_t *uuid) { +static volatile bleio_descriptor_obj_t *m_read_descriptor; + +void common_hal_bleio_descriptor_construct(bleio_descriptor_obj_t *self, bleio_uuid_obj_t *uuid, bleio_attribute_security_mode_t read_perm, bleio_attribute_security_mode_t write_perm) { + self->characteristic = MP_OBJ_NULL; self->uuid = uuid; + self->value = mp_const_none; + self->handle = BLE_CONN_HANDLE_INVALID; + self->read_perm = read_perm; + self->write_perm = write_perm; } bleio_uuid_obj_t *common_hal_bleio_descriptor_get_uuid(bleio_descriptor_obj_t *self) { return self->uuid; } + +mp_obj_t common_hal_bleio_descriptor_get_characteristic(bleio_descriptor_obj_t *self) { + return self->characteristic; +} + +STATIC void descriptor_on_gattc_read_rsp_evt(ble_evt_t *ble_evt, void *param) { + switch (ble_evt->header.evt_id) { + + // More events may be handled later, so keep this as a switch. + + case BLE_GATTC_EVT_READ_RSP: { + ble_gattc_evt_read_rsp_t *response = &ble_evt->evt.gattc_evt.params.read_rsp; + if (m_read_descriptor) { + m_read_descriptor->value = mp_obj_new_bytearray(response->len, response->data); + } + // Indicate to busy-wait loop that we've read the attribute value. + m_read_descriptor = NULL; + break; + } + + default: + // For debugging. + // mp_printf(&mp_plat_print, "Unhandled descriptor event: 0x%04x\n", ble_evt->header.evt_id); + break; + } +} + +STATIC void descriptor_gattc_read(bleio_descriptor_obj_t *descriptor) { + const uint16_t conn_handle = + common_hal_bleio_device_get_conn_handle(descriptor->characteristic->service->device); + common_hal_bleio_check_connected(conn_handle); + + // Set to NULL in event loop after event. + m_read_descriptor = descriptor; + + ble_drv_add_event_handler(descriptor_on_gattc_read_rsp_evt, descriptor); + + const uint32_t err_code = sd_ble_gattc_read(conn_handle, descriptor->handle, 0); + if (err_code != NRF_SUCCESS) { + mp_raise_OSError_msg_varg(translate("Failed to read attribute value, err 0x%04x"), err_code); + } + + while (m_read_descriptor != NULL) { + MICROPY_VM_HOOK_LOOP; + } + + ble_drv_remove_event_handler(descriptor_on_gattc_read_rsp_evt, descriptor); +} + +mp_obj_t common_hal_bleio_descriptor_get_value(bleio_descriptor_obj_t *self) { + if (common_hal_bleio_service_get_is_remote(self->characteristic->service)) { + descriptor_gattc_read(self); + } else { + self->value = common_hal_bleio_gatts_read( + self->handle, common_hal_bleio_device_get_conn_handle(self->characteristic->service->device)); + } + + return self->value; +} + +void common_hal_bleio_descriptor_set_value(bleio_descriptor_obj_t *self, mp_buffer_info_t *bufinfo) { + uint16_t conn_handle = common_hal_bleio_device_get_conn_handle(self->characteristic->service->device); + if (common_hal_bleio_service_get_is_remote(self->characteristic->service)) { + // false means WRITE_REQ, not write-no-response + common_hal_bleio_gattc_write(self->handle, conn_handle, bufinfo, false); + } else { + common_hal_bleio_gatts_write(self->handle, conn_handle, bufinfo); + } +} diff --git a/ports/nrf/common-hal/bleio/Descriptor.h b/ports/nrf/common-hal/bleio/Descriptor.h index ad94a0bb98..2fae8cba6f 100644 --- a/ports/nrf/common-hal/bleio/Descriptor.h +++ b/ports/nrf/common-hal/bleio/Descriptor.h @@ -36,11 +36,13 @@ typedef struct { mp_obj_base_t base; + // Will be MP_OBJ_NULL before being assigned to a Characteristic. bleio_characteristic_obj_t *characteristic; bleio_uuid_obj_t *uuid; - mp_obj_t value_data; + mp_obj_t value; uint16_t handle; - bleio_attribute_security_mode_t security_mode; + bleio_attribute_security_mode_t read_perm; + bleio_attribute_security_mode_t write_perm; } bleio_descriptor_obj_t; #endif // MICROPY_INCLUDED_NRF_COMMON_HAL_BLEIO_DESCRIPTOR_H diff --git a/ports/nrf/common-hal/bleio/Service.c b/ports/nrf/common-hal/bleio/Service.c index cdcbad7c41..6d2d9d74f8 100644 --- a/ports/nrf/common-hal/bleio/Service.c +++ b/ports/nrf/common-hal/bleio/Service.c @@ -47,7 +47,6 @@ void common_hal_bleio_service_construct(bleio_service_obj_t *self, bleio_uuid_ob MP_OBJ_TO_PTR(characteristic_list->items[characteristic_idx]); characteristic->service = self; } - } bleio_uuid_obj_t *common_hal_bleio_service_get_uuid(bleio_service_obj_t *self) { @@ -146,7 +145,7 @@ void common_hal_bleio_service_add_all_characteristics(bleio_service_obj_t *self) BLE_GAP_CONN_SEC_MODE_SET_OPEN(&desc_attr_md.write_perm); mp_buffer_info_t bufinfo; - mp_get_buffer_raise(descriptor->value_data, &bufinfo, MP_BUFFER_READ); + mp_get_buffer_raise(descriptor->value, &bufinfo, MP_BUFFER_READ); ble_gatts_attr_t desc_attr = { .p_uuid = &desc_uuid, diff --git a/ports/nrf/common-hal/bleio/__init__.c b/ports/nrf/common-hal/bleio/__init__.c index 303e707978..9879a9ff43 100644 --- a/ports/nrf/common-hal/bleio/__init__.c +++ b/ports/nrf/common-hal/bleio/__init__.c @@ -59,13 +59,19 @@ const super_adapter_obj_t common_hal_bleio_adapter_obj = { }, }; +void common_hal_bleio_check_connected(uint16_t conn_handle) { + if (conn_handle == BLE_CONN_HANDLE_INVALID) { + mp_raise_OSError_msg(translate("Not connected")); + } +} + uint16_t common_hal_bleio_device_get_conn_handle(mp_obj_t device) { if (MP_OBJ_IS_TYPE(device, &bleio_peripheral_type)) { return ((bleio_peripheral_obj_t*) MP_OBJ_TO_PTR(device))->conn_handle; } else if (MP_OBJ_IS_TYPE(device, &bleio_central_type)) { return ((bleio_central_obj_t*) MP_OBJ_TO_PTR(device))->conn_handle; } else { - return 0; + return BLE_CONN_HANDLE_INVALID; } } @@ -213,7 +219,7 @@ STATIC void on_char_discovery_rsp(ble_gattc_evt_char_disc_rsp_t *response, mp_ob (gattc_char->char_props.write_wo_resp ? CHAR_PROP_WRITE_NO_RESPONSE : 0); // Call common_hal_bleio_characteristic_construct() to initalize some fields and set up evt handler. - common_hal_bleio_characteristic_construct(characteristic, uuid, props, SEC_MODE_OPEN, mp_const_empty_tuple); + common_hal_bleio_characteristic_construct(characteristic, uuid, props, SEC_MODE_OPEN, SEC_MODE_OPEN, mp_const_empty_tuple); characteristic->handle = gattc_char->handle_value; characteristic->service = m_char_discovery_service; @@ -267,8 +273,7 @@ STATIC void on_desc_discovery_rsp(ble_gattc_evt_desc_disc_rsp_t *response, mp_ob // For now, just leave the UUID as NULL. } - // TODO: can we find out security mode? - common_hal_bleio_descriptor_construct(descriptor, uuid, SEC_MODE_OPEN); + common_hal_bleio_descriptor_construct(descriptor, uuid, SEC_MODE_OPEN, SEC_MODE_OPEN); descriptor->handle = gattc_desc->handle; descriptor->characteristic = m_desc_discovery_characteristic; @@ -418,3 +423,79 @@ void common_hal_bleio_device_discover_remote_services(mp_obj_t device, mp_obj_t ble_drv_remove_event_handler(discovery_on_ble_evt, device); } + +// GATTS read of a Characteristic or Descriptor. +mp_obj_t common_hal_bleio_gatts_read(uint16_t handle, uint16_t conn_handle) { + // conn_handle might be BLE_CONN_HANDLE_INVALID if we're not connected, but that's OK, because + // we can still read and write the local value. + + mp_buffer_info_t bufinfo; + ble_gatts_value_t gatts_value = { + .p_value = NULL, + .len = 0, + }; + + // Read once to find out what size buffer we need, then read again to fill buffer. + + mp_obj_t value = mp_const_none; + uint32_t err_code = sd_ble_gatts_value_get(conn_handle, handle, &gatts_value); + if (err_code == NRF_SUCCESS) { + value = mp_obj_new_bytearray_of_zeros(gatts_value.len); + mp_get_buffer_raise(value, &bufinfo, MP_BUFFER_WRITE); + gatts_value.p_value = bufinfo.buf; + + // Read again, with the correct size of buffer. + err_code = sd_ble_gatts_value_get(conn_handle, handle, &gatts_value); + } + + if (err_code != NRF_SUCCESS) { + mp_raise_OSError_msg_varg(translate("Failed to read gatts value, err 0x%04x"), err_code); + } + + return value; +} + +void common_hal_bleio_gatts_write(uint16_t handle, uint16_t conn_handle, mp_buffer_info_t *bufinfo) { + // conn_handle might be BLE_CONN_HANDLE_INVALID if we're not connected, but that's OK, because + // we can still read and write the local value. + + ble_gatts_value_t gatts_value = { + .p_value = bufinfo->buf, + .len = bufinfo->len, + }; + + const uint32_t err_code = sd_ble_gatts_value_set(conn_handle, handle, &gatts_value); + if (err_code != NRF_SUCCESS) { + mp_raise_OSError_msg_varg(translate("Failed to write gatts value, err 0x%04x"), err_code); + } +} + +void common_hal_bleio_gattc_write(uint16_t handle, uint16_t conn_handle, mp_buffer_info_t *bufinfo, bool write_no_response) { + common_hal_bleio_check_connected(conn_handle); + + ble_gattc_write_params_t write_params = { + .write_op = write_no_response ? BLE_GATT_OP_WRITE_CMD: BLE_GATT_OP_WRITE_REQ, + .handle = handle, + .p_value = bufinfo->buf, + .len = bufinfo->len, + }; + + while (1) { + uint32_t err_code = sd_ble_gattc_write(conn_handle, &write_params); + if (err_code == NRF_SUCCESS) { + break; + } + + // Write with response will return NRF_ERROR_BUSY if the response has not been received. + // Write without reponse will return NRF_ERROR_RESOURCES if too many writes are pending. + if (err_code == NRF_ERROR_BUSY || err_code == NRF_ERROR_RESOURCES) { + // We could wait for an event indicating the write is complete, but just retrying is easier. + MICROPY_VM_HOOK_LOOP; + continue; + } + + // Some real error occurred. + mp_raise_OSError_msg_varg(translate("Failed to write attribute value, err 0x%04x"), err_code); + } + +} diff --git a/shared-bindings/bleio/Address.c b/shared-bindings/bleio/Address.c index d77cfa4048..baeb6d7c0a 100644 --- a/shared-bindings/bleio/Address.c +++ b/shared-bindings/bleio/Address.c @@ -83,7 +83,21 @@ STATIC mp_obj_t bleio_address_make_new(const mp_obj_type_t *type, size_t n_args, //| .. attribute:: address_bytes //| -//| The bytes that make up the device address (read-only) +//| The bytes that make up the device address (read-only). +//| +//| Note that the ``bytes`` object returned is in little-endian order: +//| The least significant byte is ``address_bytes[0]``. So the address will +//| appear to be reversed if you print the raw ``bytes`` object. If you print +//| or use `str()` on the :py:class:`~bleio.Attribute` object itself, the address will be printed +//| in the expected order. For example: +//| +//| .. code-block:: pycon +//| +//| >>> import bleio +//| >>> bleio.adapter.address +//|
+//| >>> bleio.adapter.address.address_bytes +//| b'5\xa8\xed\xf5\x1d\xc8' //| STATIC mp_obj_t bleio_address_get_address_bytes(mp_obj_t self_in) { bleio_address_obj_t *self = MP_OBJ_TO_PTR(self_in); diff --git a/shared-bindings/bleio/Attribute.c b/shared-bindings/bleio/Attribute.c index f640cc05c6..ef6b17253b 100644 --- a/shared-bindings/bleio/Attribute.c +++ b/shared-bindings/bleio/Attribute.c @@ -37,12 +37,12 @@ //| ========================================================= //| //| Definitions associated with all BLE attributes: characteristics, descriptors, etc. -//| `Attribute` is, notionally, a superclass of `Characteristic` and `Descriptor`, +//| :py:class:`~bleio.Attribute` is, notionally, a superclass of `Characteristic` and `Descriptor`, //| but is not defined as a Python superclass of those classes. //| //| .. class:: Attribute() //| -//| You cannot create an instance of `Attribute`. +//| You cannot create an instance of :py:class:`~bleio.Attribute`. //| STATIC const mp_rom_map_elem_t bleio_attribute_locals_dict_table[] = { diff --git a/shared-bindings/bleio/Characteristic.c b/shared-bindings/bleio/Characteristic.c index 09ed48ef95..45bec6c28d 100644 --- a/shared-bindings/bleio/Characteristic.c +++ b/shared-bindings/bleio/Characteristic.c @@ -30,6 +30,7 @@ #include "py/runtime.h" #include "shared-bindings/bleio/Attribute.h" #include "shared-bindings/bleio/Characteristic.h" +#include "shared-bindings/bleio/Descriptor.h" #include "shared-bindings/bleio/UUID.h" //| .. currentmodule:: bleio @@ -41,24 +42,28 @@ //| and writing of the characteristic's value. //| //| -//| .. class:: Characteristic(uuid, *, properties=0, security_mode=`Attribute.OPEN`, descriptors=None) +//| .. class:: Characteristic(uuid, *, properties=0, read_perm=`Attribute.OPEN`, write_perm=`Attribute.OPEN`, descriptors=None) //| //| Create a new Characteristic object identified by the specified UUID. //| //| :param bleio.UUID uuid: The uuid of the characteristic //| :param int properties: bitmask of these values bitwise-or'd together: `BROADCAST`, `INDICATE`, //| `NOTIFY`, `READ`, `WRITE`, `WRITE_NO_RESPONSE` -//| :param int security_mode: one of the integer values `Attribute.NO_ACCESS`, `Attribute.OPEN`, +//| :param int read_perm: Specifies whether the characteristic can be read by a client, and if so, which +//| security mode is required. Must be one of the integer values `Attribute.NO_ACCESS`, `Attribute.OPEN`, //| `Attribute.ENCRYPT_NO_MITM`, `Attribute.ENCRYPT_WITH_MITM`, `Attribute.LESC_ENCRYPT_WITH_MITM`, -//| `Attribute.SIGNED_NO_MITM`, `Attribute.SIGNED_WITH_MITM`. +//| `Attribute.SIGNED_NO_MITM`, or `Attribute.SIGNED_WITH_MITM`. +//| :param int write_perm: Specifies whether the characteristic can be written by a client, and if so, which +//| security mode is required. Values allowed are the same as `read_perm`. //| :param iterable descriptors: BLE descriptors for this characteristic. //| STATIC mp_obj_t bleio_characteristic_make_new(const mp_obj_type_t *type, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_uuid, ARG_properties, ARG_security_mode, ARG_descriptors }; + enum { ARG_uuid, ARG_properties, ARG_read_perm, ARG_write_perm, ARG_descriptors }; static const mp_arg_t allowed_args[] = { { MP_QSTR_uuid, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = mp_const_none} }, { MP_QSTR_properties, MP_ARG_KW_ONLY| MP_ARG_INT, {.u_int = 0 } }, - { MP_QSTR_security_mode, MP_ARG_KW_ONLY| MP_ARG_INT, {.u_int = SEC_MODE_OPEN } }, + { MP_QSTR_read_perm, MP_ARG_KW_ONLY| MP_ARG_INT, {.u_int = SEC_MODE_OPEN } }, + { MP_QSTR_write_perm, MP_ARG_KW_ONLY| MP_ARG_INT, {.u_int = SEC_MODE_OPEN } }, { MP_QSTR_descriptors, MP_ARG_KW_ONLY| MP_ARG_OBJ, {.u_obj = mp_const_none} }, }; @@ -77,8 +82,11 @@ STATIC mp_obj_t bleio_characteristic_make_new(const mp_obj_type_t *type, size_t mp_raise_ValueError(translate("Invalid properties")); } - const bleio_attribute_security_mode_t security_mode = args[ARG_security_mode].u_int; - common_hal_bleio_attribute_security_mode_check_valid(security_mode); + const bleio_attribute_security_mode_t read_perm = args[ARG_read_perm].u_int; + common_hal_bleio_attribute_security_mode_check_valid(read_perm); + + const bleio_attribute_security_mode_t write_perm = args[ARG_write_perm].u_int; + common_hal_bleio_attribute_security_mode_check_valid(write_perm); mp_obj_t descriptors = args[ARG_descriptors].u_obj; if (descriptors == mp_const_none) { @@ -88,7 +96,27 @@ STATIC mp_obj_t bleio_characteristic_make_new(const mp_obj_type_t *type, size_t bleio_characteristic_obj_t *self = m_new_obj(bleio_characteristic_obj_t); self->base.type = &bleio_characteristic_type; - common_hal_bleio_characteristic_construct(self, uuid, properties, security_mode, descriptors); + // If descriptors is not an iterable, an exception will be thrown. + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(args[ARG_descriptors].u_obj, &iter_buf); + +// Copy the descriptors list and validate its items. + mp_obj_t desc_list_obj = mp_obj_new_list(0, NULL); + mp_obj_list_t *desc_list = MP_OBJ_TO_PTR(desc_list_obj); + + mp_obj_t descriptor_obj; + while ((descriptor_obj = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + if (!MP_OBJ_IS_TYPE(descriptor_obj, &bleio_descriptor_type)) { + mp_raise_ValueError(translate("descriptors includes an object that is not a Descriptors")); + } + bleio_descriptor_obj_t *descriptor = MP_OBJ_TO_PTR(descriptor_obj); + if (common_hal_bleio_descriptor_get_characteristic(descriptor) != mp_const_none) { + mp_raise_ValueError(translate("Descriptor is already attached to a Characteristic")); + } + mp_obj_list_append(desc_list_obj, descriptor_obj); + } + + common_hal_bleio_characteristic_construct(self, uuid, properties, read_perm, write_perm, desc_list); return MP_OBJ_FROM_PTR(self); } @@ -182,6 +210,24 @@ const mp_obj_property_t bleio_characteristic_descriptors_obj = { (mp_obj_t)&mp_const_none_obj }, }; +//| .. attribute:: service (read-only) +//| +//| The Service this Characteristic is a part of. None if not yet assigned to a Service. +//| +STATIC mp_obj_t bleio_characteristic_get_service(mp_obj_t self_in) { + bleio_characteristic_obj_t *self = MP_OBJ_TO_PTR(self_in); + + return common_hal_bleio_characteristic_get_service(self); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(bleio_characteristic_get_service_obj, bleio_characteristic_get_service); + +const mp_obj_property_t bleio_characteristic_service_obj = { + .base.type = &mp_type_property, + .proxy = { (mp_obj_t)&bleio_characteristic_get_service_obj, + (mp_obj_t)&mp_const_none_obj, + (mp_obj_t)&mp_const_none_obj }, +}; + //| .. method:: set_cccd(*, notify=False, indicate=False) //| //| Set the remote characteristic's CCCD to enable or disable notification and indication. diff --git a/shared-bindings/bleio/Characteristic.h b/shared-bindings/bleio/Characteristic.h index 379f9b4bfd..1d532d7489 100644 --- a/shared-bindings/bleio/Characteristic.h +++ b/shared-bindings/bleio/Characteristic.h @@ -34,12 +34,13 @@ extern const mp_obj_type_t bleio_characteristic_type; -extern void common_hal_bleio_characteristic_construct(bleio_characteristic_obj_t *self, bleio_uuid_obj_t *uuid, bleio_characteristic_properties_t props, bleio_attribute_security_mode_t security_mode, mp_obj_t descriptors); +extern void common_hal_bleio_characteristic_construct(bleio_characteristic_obj_t *self, bleio_uuid_obj_t *uuid, bleio_characteristic_properties_t props, bleio_attribute_security_mode_t read_perm, bleio_attribute_security_mode_t write_perm, mp_obj_list_t *descriptor_list); extern mp_obj_t common_hal_bleio_characteristic_get_value(bleio_characteristic_obj_t *self); extern void common_hal_bleio_characteristic_set_value(bleio_characteristic_obj_t *self, mp_buffer_info_t *bufinfo); extern bleio_characteristic_properties_t common_hal_bleio_characteristic_get_properties(bleio_characteristic_obj_t *self); extern bleio_uuid_obj_t *common_hal_bleio_characteristic_get_uuid(bleio_characteristic_obj_t *self); extern mp_obj_list_t *common_hal_bleio_characteristic_get_descriptor_list(bleio_characteristic_obj_t *self); +extern bleio_service_obj_t *common_hal_bleio_characteristic_get_service(bleio_characteristic_obj_t *self); extern void common_hal_bleio_characteristic_set_cccd(bleio_characteristic_obj_t *self, bool notify, bool indicate); #endif // MICROPY_INCLUDED_SHARED_BINDINGS_BLEIO_CHARACTERISTIC_H diff --git a/shared-bindings/bleio/Descriptor.c b/shared-bindings/bleio/Descriptor.c index b709f3aa78..1ab8c9ecdb 100644 --- a/shared-bindings/bleio/Descriptor.c +++ b/shared-bindings/bleio/Descriptor.c @@ -42,15 +42,24 @@ //| information about the characteristic. //| -//| .. class:: Descriptor(uuid, security_mode=`Attribute.OPEN`) +//| .. class:: Descriptor(uuid, *, read_perm=`Attribute.OPEN`, write_perm=`Attribute.OPEN`) +//| +//| Create a new descriptor object with the UUID uuid +//| +//| :param bleio.UUID uuid: The uuid of the descriptor +//| :param int read_perm: Specifies whether the descriptor can be read by a client, and if so, which +//| security mode is required. Must be one of the integer values `Attribute.NO_ACCESS`, `Attribute.OPEN`, +//| `Attribute.ENCRYPT_NO_MITM`, `Attribute.ENCRYPT_WITH_MITM`, `Attribute.LESC_ENCRYPT_WITH_MITM`, +//| `Attribute.SIGNED_NO_MITM`, or `Attribute.SIGNED_WITH_MITM`. +//| :param int write_perm: Specifies whether the descriptor can be written by a client, and if so, which +//| security mode is required. Values allowed are the same as `read_perm`. //| -//| Create a new descriptor object with the UUID uuid and the given value, if any. - STATIC mp_obj_t bleio_descriptor_make_new(const mp_obj_type_t *type, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_uuid, ARG_security_mode }; + enum { ARG_uuid, ARG_read_perm, ARG_write_perm }; static const mp_arg_t allowed_args[] = { { MP_QSTR_uuid, MP_ARG_REQUIRED | MP_ARG_OBJ }, - { MP_QSTR_security_mode, MP_ARG_KW_ONLY| MP_ARG_INT, {.u_int = SEC_MODE_OPEN } }, + { MP_QSTR_read_perm, MP_ARG_KW_ONLY| MP_ARG_INT, {.u_int = SEC_MODE_OPEN } }, + { MP_QSTR_write_perm, MP_ARG_KW_ONLY| MP_ARG_INT, {.u_int = SEC_MODE_OPEN } }, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; @@ -62,14 +71,17 @@ STATIC mp_obj_t bleio_descriptor_make_new(const mp_obj_type_t *type, size_t n_ar mp_raise_ValueError(translate("Expected a UUID")); } - const bleio_attribute_security_mode_t security_mode = args[ARG_security_mode].u_int; - common_hal_bleio_attribute_security_mode_check_valid(security_mode); + const bleio_attribute_security_mode_t read_perm = args[ARG_read_perm].u_int; + common_hal_bleio_attribute_security_mode_check_valid(read_perm); + + const bleio_attribute_security_mode_t write_perm = args[ARG_write_perm].u_int; + common_hal_bleio_attribute_security_mode_check_valid(write_perm); bleio_descriptor_obj_t *self = m_new_obj(bleio_descriptor_obj_t); self->base.type = type; bleio_uuid_obj_t *uuid = MP_OBJ_TO_PTR(uuid_arg); - common_hal_bleio_descriptor_construct(self, uuid, security_mode); + common_hal_bleio_descriptor_construct(self, uuid, read_perm, write_perm); return MP_OBJ_FROM_PTR(self); } @@ -93,6 +105,24 @@ const mp_obj_property_t bleio_descriptor_uuid_obj = { (mp_obj_t)&mp_const_none_obj}, }; +//| .. attribute:: characteristic (read-only) +//| +//| The Characteristic this Descriptor is a part of. None if not yet assigned to a Characteristic. +//| +STATIC mp_obj_t bleio_descriptor_get_characteristic(mp_obj_t self_in) { + bleio_descriptor_obj_t *self = MP_OBJ_TO_PTR(self_in); + + return common_hal_bleio_descriptor_get_characteristic(self); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(bleio_descriptor_get_characteristic_obj, bleio_descriptor_get_characteristic); + +const mp_obj_property_t bleio_descriptor_characteristic_obj = { + .base.type = &mp_type_property, + .proxy = { (mp_obj_t)&bleio_descriptor_get_characteristic_obj, + (mp_obj_t)&mp_const_none_obj, + (mp_obj_t)&mp_const_none_obj }, +}; + STATIC const mp_rom_map_elem_t bleio_descriptor_locals_dict_table[] = { // Properties { MP_ROM_QSTR(MP_QSTR_uuid), MP_ROM_PTR(&bleio_descriptor_uuid_obj) }, diff --git a/shared-bindings/bleio/Descriptor.h b/shared-bindings/bleio/Descriptor.h index a7daee574d..eefce03d11 100644 --- a/shared-bindings/bleio/Descriptor.h +++ b/shared-bindings/bleio/Descriptor.h @@ -34,8 +34,8 @@ extern const mp_obj_type_t bleio_descriptor_type; -extern mp_int_t common_hal_bleio_descriptor_get_handle(bleio_descriptor_obj_t *self); -extern mp_obj_t common_hal_bleio_descriptor_get_uuid(bleio_descriptor_obj_t *self); -extern void common_hal_bleio_descriptor_construct(bleio_descriptor_obj_t *self, bleio_uuid_obj_t *uuid, bleio_attribute_security_mode_t security_mode); +extern void common_hal_bleio_descriptor_construct(bleio_descriptor_obj_t *self, bleio_uuid_obj_t *uuid, bleio_attribute_security_mode_t read_perm, bleio_attribute_security_mode_t write_perm); +extern bleio_uuid_obj_t *common_hal_bleio_descriptor_get_uuid(bleio_descriptor_obj_t *self); +extern mp_obj_t common_hal_bleio_descriptor_get_characteristic(bleio_descriptor_obj_t *self); #endif // MICROPY_INCLUDED_SHARED_BINDINGS_BLEIO_DESCRIPTOR_H diff --git a/shared-bindings/bleio/Service.c b/shared-bindings/bleio/Service.c index b4aeab2bd2..6961b0b522 100644 --- a/shared-bindings/bleio/Service.c +++ b/shared-bindings/bleio/Service.c @@ -96,6 +96,9 @@ STATIC mp_obj_t bleio_service_make_new(const mp_obj_type_t *type, size_t n_args, // The descriptor base UUID doesn't match the characteristic base UUID. mp_raise_ValueError(translate("Characteristic UUID doesn't match Service UUID")); } + if (common_hal_bleio_characteristic_get_service(characteristic) != MP_OBJ_NULL) { + mp_raise_ValueError(translate("Characteristic is already attached to a Service")); + } mp_obj_list_append(char_list_obj, characteristic_obj); } diff --git a/shared-bindings/bleio/__init__.c b/shared-bindings/bleio/__init__.c index 7a6f964363..0009cf1135 100644 --- a/shared-bindings/bleio/__init__.c +++ b/shared-bindings/bleio/__init__.c @@ -59,6 +59,7 @@ //| //| Address //| Adapter +//| Attribute //| Central //| Characteristic //| CharacteristicBuffer diff --git a/shared-bindings/bleio/__init__.h b/shared-bindings/bleio/__init__.h index 626b188a98..6a4c804f55 100644 --- a/shared-bindings/bleio/__init__.h +++ b/shared-bindings/bleio/__init__.h @@ -38,9 +38,15 @@ extern const super_adapter_obj_t common_hal_bleio_adapter_obj; +extern void common_hal_bleio_check_connected(uint16_t conn_handle); + extern uint16_t common_hal_bleio_device_get_conn_handle(mp_obj_t device); extern mp_obj_list_t *common_hal_bleio_device_get_remote_services_list(mp_obj_t device); extern void common_hal_bleio_device_discover_remote_services(mp_obj_t device, mp_obj_t service_uuids_whitelist); +extern mp_obj_t common_hal_bleio_gatts_read(uint16_t handle, uint16_t conn_handle); +extern void common_hal_bleio_gatts_write(uint16_t handle, uint16_t conn_handle, mp_buffer_info_t *bufinfo); +extern void common_hal_bleio_gattc_write(uint16_t handle, uint16_t conn_handle, mp_buffer_info_t *bufinfo, bool write_no_response); + #endif // MICROPY_INCLUDED_SHARED_BINDINGS_BLEIO___INIT___H