diff options
Diffstat (limited to 'arduino/libraries/Bluefruit52Lib/src/BLECharacteristic.cpp')
-rwxr-xr-x | arduino/libraries/Bluefruit52Lib/src/BLECharacteristic.cpp | 747 |
1 files changed, 747 insertions, 0 deletions
diff --git a/arduino/libraries/Bluefruit52Lib/src/BLECharacteristic.cpp b/arduino/libraries/Bluefruit52Lib/src/BLECharacteristic.cpp new file mode 100755 index 0000000..6474e39 --- /dev/null +++ b/arduino/libraries/Bluefruit52Lib/src/BLECharacteristic.cpp @@ -0,0 +1,747 @@ +/**************************************************************************/ +/*! + @file BLECharacteristic.cpp + @author hathach (tinyusb.org) + + @section LICENSE + + Software License Agreement (BSD License) + + Copyright (c) 2018, Adafruit Industries (adafruit.com) + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holders nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/**************************************************************************/ + +#include "bluefruit.h" + +void BLECharacteristic::_init(void) +{ + _is_temp = false; + _max_len = BLE_GATT_ATT_MTU_DEFAULT-3; + _service = NULL; + + _usr_descriptor = NULL; + varclr(&_report_ref_desc); + varclr(&_format_desc); + + varclr(&_properties); + + varclr(&_attr_meta); + _attr_meta.read_perm = _attr_meta.write_perm = BLE_SECMODE_OPEN; + _attr_meta.vlen = 1; + _attr_meta.vloc = BLE_GATTS_VLOC_STACK; + + _handles.value_handle = BLE_GATT_HANDLE_INVALID; + _handles.user_desc_handle = BLE_GATT_HANDLE_INVALID; + _handles.sccd_handle = BLE_GATT_HANDLE_INVALID; + _handles.cccd_handle = BLE_GATT_HANDLE_INVALID; + + _long_wr.buffer = NULL; + _long_wr.bufsize = 0; + _long_wr.count = 0; + + _rd_authorize_cb = NULL; + _wr_authorize_cb = NULL; + _wr_cb = NULL; + _cccd_wr_cb = NULL; +} + +BLECharacteristic::BLECharacteristic(void) + : uuid() +{ + _init(); +} + +BLECharacteristic::BLECharacteristic(BLEUuid bleuuid) + : uuid(bleuuid) +{ + _init(); +} + +void BLECharacteristic::setUuid(BLEUuid bleuuid) +{ + uuid = bleuuid; +} + +BLEService& BLECharacteristic::parentService (void) +{ + return *_service; +} + +/** + * Destructor + */ +BLECharacteristic::~BLECharacteristic() +{ +// Bluefruit.Gatt._removeCharacteristic(this); +} + +/** + * Must be set when Charactertistic is declared locally (e.g insdie function) + * and is not last throughout programs. Useful for one-shot set-and-forget + * Characteristics such as read-only one. Where there is no need for interactions + * later on. This call will prevent the Characteristics to be hooked into + * managing chars list of AdafruitBluefruit + */ +void BLECharacteristic::setTempMemory(void) +{ + _is_temp = true; +} + +void BLECharacteristic::setProperties(uint8_t prop) +{ + memcpy(&_properties, &prop, 1); +} + +void BLECharacteristic::setMaxLen(uint16_t max_len) +{ + _max_len = max_len; +} + +void BLECharacteristic::setFixedLen(uint16_t fixed_len) +{ + if ( fixed_len ) + { + _max_len = fixed_len; + _attr_meta.vlen = 0; + }else + { + _attr_meta.vlen = 1; + } +} + +void BLECharacteristic::setPermission(BleSecurityMode read_perm, BleSecurityMode write_perm) +{ + memcpy(&_attr_meta.read_perm , &read_perm, 1); + memcpy(&_attr_meta.write_perm, &write_perm, 1); +} + +void BLECharacteristic::setWriteCallback(write_cb_t fp) +{ + _wr_cb = fp; +} + +void BLECharacteristic::setCccdWriteCallback(write_cccd_cb_t fp) +{ + _cccd_wr_cb = fp; +} + +void BLECharacteristic::setReadAuthorizeCallback(read_authorize_cb_t fp) +{ + _attr_meta.rd_auth = (fp ? 1 : 0); + _rd_authorize_cb = fp; +} + +void BLECharacteristic::setWriteAuthorizeCallback(write_authorize_cb_t fp) +{ + _attr_meta.wr_auth = (fp ? 1 : 0); + _wr_authorize_cb = fp; +} + +void BLECharacteristic::setUserDescriptor(const char* descriptor) +{ + _usr_descriptor = descriptor; +} + +void BLECharacteristic::setReportRefDescriptor(uint8_t id, uint8_t type) +{ + _report_ref_desc.id = id; + _report_ref_desc.type = type; +} + +/** + * https://developer.bluetooth.org/gatt/descriptors/Pages/DescriptorViewer.aspx?u=org.bluetooth.descriptor.gatt.characteristic_presentation_format.xml + * @param type BLE_GATT_CPF_FORMAT_x value, see ble_gatt.h + * @param exponent exponent + * @param unit UUID16_UNIT_x, see BLEUuid.h + */ +void BLECharacteristic::setPresentationFormatDescriptor(uint8_t type, int8_t exponent, uint16_t unit, uint8_t name_space, uint16_t descritpor) +{ + _format_desc.format = type; + _format_desc.exponent = exponent; + _format_desc.unit = unit; + _format_desc.name_space = name_space; + _format_desc.desc = descritpor; +} + +ble_gatts_char_handles_t BLECharacteristic::handles(void) +{ + return _handles; +} + +err_t BLECharacteristic::begin(void) +{ + _service = BLEService::lastService; + + // Add UUID128 if needed + (void) uuid.begin(); + + // Permission is OPEN if passkey is disabled. +// if (!nvm_data.core.passkey_enable) BLE_GAP_CONN_SEC_MODE_SET_OPEN(&p_char_def->permission); + + // Correct Read/Write permission according to properties + if ( !(_properties.read || _properties.notify || _properties.indicate ) ) + { + _attr_meta.read_perm = BLE_SECMODE_NO_ACCESS; + } + + if ( !(_properties.write || _properties.write_wo_resp ) ) + { + _attr_meta.write_perm = BLE_SECMODE_NO_ACCESS; + } + + /* CCCD attribute metadata */ + ble_gatts_attr_md_t cccd_md; + + if ( _properties.notify || _properties.indicate ) + { + /* Notification & Indication require CCCD */ + memclr( &cccd_md, sizeof(ble_gatts_attr_md_t) ); + cccd_md.vloc = BLE_GATTS_VLOC_STACK; + + cccd_md.read_perm = BLE_SECMODE_OPEN; + cccd_md.write_perm = _attr_meta.read_perm; + } + + /* Characteristic metadata */ + ble_gatts_char_md_t char_md; + varclr(&char_md); + + char_md.char_props = _properties; + char_md.p_cccd_md = (_properties.notify || _properties.indicate) ? &cccd_md : NULL; + + /* Characteristic extended properties (for user description) */ + ble_gatts_attr_md_t desc_md = + { + .read_perm = _attr_meta.read_perm, + .write_perm = BLE_SECMODE_NO_ACCESS, + .vlen = 0, + .vloc = BLE_GATTS_VLOC_STACK, + }; + + if (_usr_descriptor != NULL && _usr_descriptor[0] != 0) + { + char_md.p_char_user_desc = (uint8_t*) _usr_descriptor; + char_md.char_user_desc_size = char_md.char_user_desc_max_size = strlen(_usr_descriptor); + char_md.p_user_desc_md = &desc_md; + //char_md.char_ext_props = ext_props, + } + + /* Presentation Format Descriptor */ + if ( _format_desc.format != 0 ) + { + char_md.p_char_pf = &_format_desc; + } + + /* GATT attribute declaration */ + ble_gatts_attr_t attr_char_value = + { + .p_uuid = &uuid._uuid, + .p_attr_md = &_attr_meta, + .init_len = (_attr_meta.vlen == 1) ? (uint16_t) 0 : _max_len, + .init_offs = 0, + .max_len = _max_len, + .p_value = NULL + }; + + VERIFY_STATUS( sd_ble_gatts_characteristic_add(BLE_GATT_HANDLE_INVALID, &char_md, &attr_char_value, &_handles) ); + + // Report Reference Descriptor if any (required by HID) + if ( _report_ref_desc.type ) + { + // Reference Descriptor + ble_gatts_attr_md_t ref_md = + { + .read_perm = _attr_meta.read_perm, + .write_perm = BLE_SECMODE_NO_ACCESS, + .vlen = 0, + .vloc = BLE_GATTS_VLOC_STACK + }; + + ble_uuid_t ref_uuid = { .uuid = UUID16_REPORT_REF_DESCR, .type = BLE_UUID_TYPE_BLE }; + ble_gatts_attr_t ref_desc = + { + .p_uuid = &ref_uuid, + .p_attr_md = &ref_md, + .init_len = sizeof(_report_ref_desc), + .init_offs = 0, + .max_len = sizeof(_report_ref_desc), + .p_value = (uint8_t*) &_report_ref_desc + }; + + uint16_t ref_hdl; + VERIFY_STATUS ( sd_ble_gatts_descriptor_add(BLE_GATT_HANDLE_INVALID, &ref_desc, &ref_hdl) ); + + (void) ref_hdl; // not used + } + + // Currently Only register to Bluefruit if The Characteristic is not temporary memory i.e local variable + if ( !_is_temp ) + { + (void) Bluefruit.Gatt._addCharacteristic(this); + } + + return ERROR_NONE; +} + +err_t BLECharacteristic::addDescriptor(BLEUuid bleuuid, void const * content, uint16_t len, BleSecurityMode read_perm, BleSecurityMode write_perm) +{ + // Meta Data + ble_gatts_attr_md_t meta; + varclr(&meta); + + memcpy(&meta.read_perm , &read_perm , 1); + memcpy(&meta.write_perm, &write_perm, 1); + meta.vlen = 0; + meta.vloc = BLE_GATTS_VLOC_STACK; + + // Descriptor + (void) bleuuid.begin(); + + ble_gatts_attr_t desc = + { + .p_uuid = &bleuuid._uuid, + .p_attr_md = &meta, + .init_len = len, + .init_offs = 0, + .max_len = len, + .p_value = (uint8_t*) content + }; + + uint16_t hdl; + VERIFY_STATUS ( sd_ble_gatts_descriptor_add(BLE_GATT_HANDLE_INVALID, &desc, &hdl) ); + + return ERROR_NONE; +} + +/** + * @param event + */ +void BLECharacteristic::_eventHandler(ble_evt_t* event) +{ + uint16_t const conn_hdl = event->evt.common_evt.conn_handle; + + switch(event->header.evt_id) + { + case BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST: + { + ble_gatts_evt_rw_authorize_request_t * request = &event->evt.gatts_evt.params.authorize_request; + + if (request->type == BLE_GATTS_AUTHORIZE_TYPE_WRITE) + { + ble_gatts_evt_write_t * wr_req = &request->request.write; + + switch(wr_req->op) + { + case BLE_GATTS_OP_PREP_WRITE_REQ: + { + // Prepare Long Write + if ( !_long_wr.buffer ) + { + // Allocate long write buffer if not previously + _long_wr.bufsize = 1024; // TODO bufsize is 10x MTU + _long_wr.buffer = (uint8_t*) rtos_malloc(_long_wr.bufsize); + _long_wr.count = 0; + } + + ble_gatts_rw_authorize_reply_params_t reply = { .type = BLE_GATTS_AUTHORIZE_TYPE_WRITE }; + + if ( wr_req->offset + wr_req->len > _long_wr.bufsize ) + { + reply.params.write.gatt_status = BLE_GATT_STATUS_ATTERR_PREPARE_QUEUE_FULL; + }else + { + reply.params.write = ((ble_gatts_authorize_params_t) { + .gatt_status = BLE_GATT_STATUS_SUCCESS, + .update = 1, + .offset = wr_req->offset, + .len = wr_req->len, + .p_data = wr_req->data + }); + + memcpy(_long_wr.buffer+wr_req->offset, wr_req->data, wr_req->len); + _long_wr.count = max16(_long_wr.count, wr_req->offset + wr_req->len); + } + + sd_ble_gatts_rw_authorize_reply(conn_hdl, &reply); + } + break; + + case BLE_GATTS_OP_EXEC_WRITE_REQ_NOW: + // Execute Long Write + if ( _long_wr.buffer ) + { + ble_gatts_rw_authorize_reply_params_t reply = { .type = BLE_GATTS_AUTHORIZE_TYPE_WRITE }; + reply.params.write.gatt_status = BLE_GATT_STATUS_SUCCESS; + + sd_ble_gatts_rw_authorize_reply(conn_hdl, &reply); + + // Long write complete, call write callback if set + if (_wr_cb) _wr_cb(*this, _long_wr.buffer, _long_wr.count, 0); + + // free up memory + rtos_free(_long_wr.buffer); + _long_wr.buffer = NULL; + _long_wr.bufsize = _long_wr.count = 0; + } + break; + + case BLE_GATTS_OP_EXEC_WRITE_REQ_CANCEL: + // Cancel Long Write + break; + + case BLE_GATTS_OP_WRITE_REQ: + // Write Request with authorization + if (_wr_authorize_cb != NULL) _wr_authorize_cb(*this, &request->request.write); + break; + + default: break; + } + } + + if ( (request->type == BLE_GATTS_AUTHORIZE_TYPE_READ) && (_rd_authorize_cb != NULL)) + { + _rd_authorize_cb(*this, &request->request.read); + } + } + break; + + case BLE_GATTS_EVT_WRITE: + { + ble_gatts_evt_write_t* request = &event->evt.gatts_evt.params.write; + + LOG_LV2_BUFFER(NULL, request->data, request->len); + + // Value write + if (request->handle == _handles.value_handle) + { + LOG_LV2("GATTS", "attr's value, uuid = 0x%04X", request->uuid.uuid); + // TODO Ada callback + if (_wr_cb) + { +// uint8_t* data = (uint8_t*) rtos_malloc(request->len); +// +// if (data) +// { +// ada_callback(data, _wr_cb, *this, data, request->len, request->offset); +// }else + { + // invoke directly if cannot allocate memory for data + _wr_cb(*this, request->data, request->len, request->offset); + } + } + } + + // CCCD write + if ( request->handle == _handles.cccd_handle ) + { + LOG_LV2("GATTS", "attr's cccd"); + + // Invoke callback if set + if (_cccd_wr_cb) + { + uint16_t value; + memcpy(&value, request->data, 2); + _cccd_wr_cb(*this, value); + } + } + } + break; + + default: break; + } +} + +/*------------------------------------------------------------------*/ +/* WRITE + *------------------------------------------------------------------*/ +uint16_t BLECharacteristic::write(const char * str) +{ + return write((const uint8_t*) str, strlen(str)); +} + +uint16_t BLECharacteristic::write(const void* data, uint16_t len) +{ + ble_gatts_value_t value = + { + .len = min16(len, _max_len), // could not exceed max len + .offset = 0 , // TODO gatts long write + .p_value = (uint8_t*) data + }; + + // conn handle only needed for system attribute + VERIFY_STATUS( sd_ble_gatts_value_set(BLE_CONN_HANDLE_INVALID, _handles.value_handle, &value), 0 ); + + return value.len; +} + +uint16_t BLECharacteristic::write8(uint8_t num) +{ + return write( (uint8_t*) &num, sizeof(num)); +} + +uint16_t BLECharacteristic::write16(uint16_t num) +{ + return write( (uint8_t*) &num, sizeof(num)); +} + +uint16_t BLECharacteristic::write32(uint32_t num) +{ + return write( (uint8_t*) &num, sizeof(num)); +} + +uint16_t BLECharacteristic::write32(int num) +{ + return write32( (uint32_t) num ); +} + +/*------------------------------------------------------------------*/ +/* READ + *------------------------------------------------------------------*/ +/** + * Read Characteristic's value + * @param buffer memory to hold value + * @param len size of memory + * @param offset offset of value (dfeault is 0) + * @return number of read bytes + */ +uint16_t BLECharacteristic::read(void* buffer, uint16_t bufsize, uint16_t offset) +{ + ble_gatts_value_t value = + { + .len = bufsize, + .offset = offset, + .p_value = (uint8_t*) buffer + }; + + // conn handle only needed for system attribute + VERIFY_STATUS(sd_ble_gatts_value_get(BLE_CONN_HANDLE_INVALID, _handles.value_handle, &value), 0); + + return value.len; +} + +uint8_t BLECharacteristic::read8(void) +{ + uint8_t num; + return read(&num, sizeof(num)) ? num : 0; +} + +uint16_t BLECharacteristic::read16(void) +{ + uint16_t num; + return read(&num, sizeof(num)) ? num : 0; +} + +uint32_t BLECharacteristic::read32(void) +{ + uint32_t num; + return read(&num, sizeof(num)) ? num : 0; +} + + +uint16_t BLECharacteristic::getCccd(void) +{ + VERIFY( Bluefruit.connected() && (_handles.cccd_handle != BLE_GATT_HANDLE_INVALID), 0 ); + + uint16_t cccd; + ble_gatts_value_t value = + { + .len = 2, + .offset = 0, + .p_value = (uint8_t*) &cccd + }; + + err_t err = sd_ble_gatts_value_get(Bluefruit.connHandle(), _handles.cccd_handle, &value); + + // CCCD is not set, count as not enabled + if ( BLE_ERROR_GATTS_SYS_ATTR_MISSING == err ) + { + cccd = 0; + }else + { + VERIFY_STATUS(err); + } + + return cccd; +} + +/*------------------------------------------------------------------*/ +/* NOTIFY + *------------------------------------------------------------------*/ +bool BLECharacteristic::notifyEnabled(void) +{ + VERIFY( _properties.notify ); + return (getCccd() & BLE_GATT_HVX_NOTIFICATION); +} + +bool BLECharacteristic::notify(const void* data, uint16_t len) +{ + VERIFY( _properties.notify ); + + // could not exceed max len + uint16_t remaining = min16(len, _max_len); + + if ( notifyEnabled() ) + { + uint16_t const max_payload = Bluefruit.Gap.getMTU( Bluefruit.connHandle() ) - 3; + const uint8_t* u8data = (const uint8_t*) data; + + while ( remaining ) + { + // TODO multiple connection support + // Failed if there is no free buffer + if ( !Bluefruit.Gap.getHvnPacket( Bluefruit.connHandle() ) ) return false; + + uint16_t packet_len = min16(max_payload, remaining); + + ble_gatts_hvx_params_t hvx_params = + { + .handle = _handles.value_handle, + .type = BLE_GATT_HVX_NOTIFICATION, + .offset = 0, + .p_len = &packet_len, + .p_data = (uint8_t*) u8data, + }; + + LOG_LV2("CHR", "Notify %d bytes", packet_len); + VERIFY_STATUS( sd_ble_gatts_hvx(Bluefruit.connHandle(), &hvx_params), false ); + + remaining -= packet_len; + u8data += packet_len; + } + } + else + { + write(data, remaining); + return false; + } + + return true; +} + +bool BLECharacteristic::notify(const char * str) +{ + return notify( (const uint8_t*) str, strlen(str) ); +} + +bool BLECharacteristic::notify8(uint8_t num) +{ + return notify( (uint8_t*) &num, sizeof(num)); +} + +bool BLECharacteristic::notify16(uint16_t num) +{ + return notify( (uint8_t*) &num, sizeof(num)); +} + +bool BLECharacteristic::notify32(uint32_t num) +{ + return notify( (uint8_t*) &num, sizeof(num)); +} + +bool BLECharacteristic::notify32(int num) +{ + return notify32( (uint32_t) num); +} + +/*------------------------------------------------------------------*/ +/* INDICATE + *------------------------------------------------------------------*/ +bool BLECharacteristic::indicateEnabled(void) +{ + VERIFY( _properties.indicate ); + return (getCccd() & BLE_GATT_HVX_INDICATION); +} + +bool BLECharacteristic::indicate(const void* data, uint16_t len) +{ + VERIFY( _properties.indicate ); + + // could not exceed max len + uint16_t remaining = min16(len, _max_len); + + if ( indicateEnabled() ) + { + uint16_t conn_hdl = Bluefruit.connHandle(); + + uint16_t const max_payload = Bluefruit.Gap.getMTU( conn_hdl ) - 3; + const uint8_t* u8data = (const uint8_t*) data; + + while ( remaining ) + { + uint16_t packet_len = min16(max_payload, remaining); + + ble_gatts_hvx_params_t hvx_params = + { + .handle = _handles.value_handle, + .type = BLE_GATT_HVX_INDICATION, + .offset = 0, + .p_len = &packet_len, + .p_data = (uint8_t*) u8data, + }; + + LOG_LV2("CHR", "Indicate %d bytes", packet_len); + + // Blocking wait until receiving confirmation from peer + VERIFY_STATUS( sd_ble_gatts_hvx( conn_hdl, &hvx_params), false ); + VERIFY ( Bluefruit.Gatt.waitForIndicateConfirm(conn_hdl) ); + + remaining -= packet_len; + u8data += packet_len; + } + } + else + { + write(data, remaining); + return false; + } + + return true; +} + +bool BLECharacteristic::indicate(const char * str) +{ + return indicate( (const uint8_t*) str, strlen(str) ); +} + +bool BLECharacteristic::indicate8(uint8_t num) +{ + return indicate( (uint8_t*) &num, sizeof(num)); +} + +bool BLECharacteristic::indicate16(uint16_t num) +{ + return indicate( (uint8_t*) &num, sizeof(num)); +} + +bool BLECharacteristic::indicate32(uint32_t num) +{ + return indicate( (uint8_t*) &num, sizeof(num)); +} + +bool BLECharacteristic::indicate32(int num) +{ + return indicate32( (uint32_t) num); +} |