From 19a56343c34bed2aac2d209c5d9ca5a1bcaacea3 Mon Sep 17 00:00:00 2001 From: lianggao Date: Mon, 17 Oct 2016 16:11:18 +0800 Subject: [PATCH 01/22] Arduino BLE new library init version Know issues: 1. Memory issue. Some times the memcmp not work correct. 2. Central not complete. But can do scan. --- libraries/BLE/examples/test/test.ino | 74 ++ libraries/BLE/src/ArduinoBLE.h | 42 + libraries/BLE/src/BLEAttribute.cpp | 64 ++ libraries/BLE/src/BLEAttribute.h | 73 ++ libraries/BLE/src/BLEAttributeWithValue.h | 63 ++ libraries/BLE/src/BLECallbacks.cpp | 190 ++++ libraries/BLE/src/BLECallbacks.h | 53 ++ libraries/BLE/src/BLECharacteristic.cpp | 365 ++++++++ libraries/BLE/src/BLECharacteristic.h | 506 ++++++++++ libraries/BLE/src/BLECharacteristicImp.cpp | 560 +++++++++++ libraries/BLE/src/BLECharacteristicImp.h | 313 +++++++ libraries/BLE/src/BLECommon.h | 139 +++ libraries/BLE/src/BLEDescriptor.cpp | 127 +++ libraries/BLE/src/BLEDescriptor.h | 98 ++ libraries/BLE/src/BLEDescriptorImp.cpp | 74 ++ libraries/BLE/src/BLEDescriptorImp.h | 69 ++ libraries/BLE/src/BLEDevice.cpp | 433 +++++++++ libraries/BLE/src/BLEDevice.h | 414 ++++++++ libraries/BLE/src/BLEDeviceManager.cpp | 803 ++++++++++++++++ libraries/BLE/src/BLEDeviceManager.h | 415 ++++++++ libraries/BLE/src/BLEProfileManager.cpp | 883 ++++++++++++++++++ libraries/BLE/src/BLEProfileManager.h | 188 ++++ libraries/BLE/src/BLEService.cpp | 194 ++++ libraries/BLE/src/BLEService.h | 137 +++ libraries/BLE/src/BLEServiceImp.cpp | 308 ++++++ libraries/BLE/src/BLEServiceImp.h | 96 ++ libraries/BLE/src/BLEUtils.cpp | 148 +++ libraries/BLE/src/BLEUtils.h | 13 + libraries/BLE/src/LinkList.h | 78 ++ libraries/BLE/src/internal/ble_client.c | 243 +++++ libraries/BLE/src/internal/ble_client.h | 119 +++ libraries/CurieBLE/src/BLECharacteristic.h | 2 +- libraries/CurieBLE/src/BLEPeripheral.h | 2 +- libraries/CurieBLE/src/BLEProfile.cpp | 2 +- system/libarc32_arduino101/bootcode/init.S | 5 +- .../drivers/ipc_uart_ns16550.c | 6 +- .../framework/src/cfw/service_api.c | 5 + .../framework/src/infra/port.c | 3 +- .../framework/src/os/panic.c | 16 + .../framework/src/services/ble/gatt.c | 3 +- .../src/services/ble_service/ble_service.c | 3 + variants/arduino_101/linker_scripts/flash.ld | 8 +- 42 files changed, 7323 insertions(+), 14 deletions(-) create mode 100644 libraries/BLE/examples/test/test.ino create mode 100644 libraries/BLE/src/ArduinoBLE.h create mode 100644 libraries/BLE/src/BLEAttribute.cpp create mode 100644 libraries/BLE/src/BLEAttribute.h create mode 100644 libraries/BLE/src/BLEAttributeWithValue.h create mode 100644 libraries/BLE/src/BLECallbacks.cpp create mode 100644 libraries/BLE/src/BLECallbacks.h create mode 100644 libraries/BLE/src/BLECharacteristic.cpp create mode 100644 libraries/BLE/src/BLECharacteristic.h create mode 100644 libraries/BLE/src/BLECharacteristicImp.cpp create mode 100644 libraries/BLE/src/BLECharacteristicImp.h create mode 100644 libraries/BLE/src/BLECommon.h create mode 100644 libraries/BLE/src/BLEDescriptor.cpp create mode 100644 libraries/BLE/src/BLEDescriptor.h create mode 100644 libraries/BLE/src/BLEDescriptorImp.cpp create mode 100644 libraries/BLE/src/BLEDescriptorImp.h create mode 100644 libraries/BLE/src/BLEDevice.cpp create mode 100644 libraries/BLE/src/BLEDevice.h create mode 100644 libraries/BLE/src/BLEDeviceManager.cpp create mode 100644 libraries/BLE/src/BLEDeviceManager.h create mode 100644 libraries/BLE/src/BLEProfileManager.cpp create mode 100644 libraries/BLE/src/BLEProfileManager.h create mode 100644 libraries/BLE/src/BLEService.cpp create mode 100644 libraries/BLE/src/BLEService.h create mode 100644 libraries/BLE/src/BLEServiceImp.cpp create mode 100644 libraries/BLE/src/BLEServiceImp.h create mode 100644 libraries/BLE/src/BLEUtils.cpp create mode 100644 libraries/BLE/src/BLEUtils.h create mode 100644 libraries/BLE/src/LinkList.h create mode 100644 libraries/BLE/src/internal/ble_client.c create mode 100644 libraries/BLE/src/internal/ble_client.h diff --git a/libraries/BLE/examples/test/test.ino b/libraries/BLE/examples/test/test.ino new file mode 100644 index 00000000..9a32ea8f --- /dev/null +++ b/libraries/BLE/examples/test/test.ino @@ -0,0 +1,74 @@ + +#include "ArduinoBLE.h" +#include "BLEAttribute.h" +#include "BLECharacteristicImp.h" +#include "BLEProfileManager.h" + +// LED pin +#define LED_PIN 13 + +// create service +BLEService ledService("19b10100e8f2537e4f6cd104768a1214"); + +BLECharacteristic switchCharacteristic("19b10101e8f2537e4f6cd104768a1214", BLERead | BLEWrite | BLENotify, 1); + +BLEDescriptor switchDescriptor("2901", "switch"); + +void setup() { + Serial1.begin(115200); + Serial1.println("test---"); + + // set LED pin to output mode + pinMode(LED_PIN, OUTPUT); + + // begin initialization + BLE.begin(); + Serial1.println(BLE.address()); + + // set advertised local name and service UUID + BLE.setLocalName("LED"); + BLE.setAdvertisedServiceUuid(ledService.uuid()); + + // add service and characteristic + BLE.addService(ledService); + ledService.addCharacteristic(switchCharacteristic); + switchCharacteristic.addDescriptor(switchDescriptor); + unsigned char test = 1; + switchCharacteristic.writeValue(&test,1); + BLE.startAdvertising(); +} + +void loop() { + static int i = 0; + BLEDevice central = BLE.central(); + bool temp = central; +i++; + if (temp) { + // central connected to peripheral + Serial1.print(i); + Serial1.print(F("Connected to central: ")); + Serial1.println(central.address()); + + Serial1.print(temp); + + while (central.connected()) { + // central still connected to peripheral + if (switchCharacteristic.written()) { + char ledValue = *switchCharacteristic.value(); + // central wrote new value to characteristic, update LED + if (ledValue) { + Serial1.println(F("LED on")); + digitalWrite(LED_PIN, HIGH); + } else { + Serial1.println(F("LED off")); + digitalWrite(LED_PIN, LOW); + } + } + } + + // central disconnected + Serial1.print(F("Disconnected from central: ")); + Serial1.println(central.address()); + } + //delay (1000); +} diff --git a/libraries/BLE/src/ArduinoBLE.h b/libraries/BLE/src/ArduinoBLE.h new file mode 100644 index 00000000..14fbddcc --- /dev/null +++ b/libraries/BLE/src/ArduinoBLE.h @@ -0,0 +1,42 @@ +/* + BLE API + Copyright (c) 2016 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef ARDUINO_BLE_H +#define ARDUINO_BLE_H + +#define ARDUINO_BLE_API_VERSION 10000 // version 1.0.0 + +class BLEDevice; +class BLECharacteristic; +class BLEDescriptor; +class BLEService; +class BLECharacteristicImp; + +#include "BLECommon.h" + +#include "BLEDevice.h" + +#include "BLECharacteristic.h" +#include "BLEDescriptor.h" +#include "BLEService.h" + + +extern BLEDevice BLE; + +#endif diff --git a/libraries/BLE/src/BLEAttribute.cpp b/libraries/BLE/src/BLEAttribute.cpp new file mode 100644 index 00000000..01eef6fe --- /dev/null +++ b/libraries/BLE/src/BLEAttribute.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2015 Intel Corporation. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "ArduinoBLE.h" +#include "BLEAttribute.h" + +#include "BLEUtils.h" + + +BLEAttribute::BLEAttribute(const char* uuid, BLEAttributeType type) : + _type(type) +{ + memset(&_uuid, 0, sizeof (_uuid)); + BLEUtils::uuidString2BT(uuid, (bt_uuid_t*)&_uuid); +} + +BLEAttribute::BLEAttribute(const bt_uuid_t* uuid, BLEAttributeType type) : + _type(type) +{ + memcpy(&_uuid, uuid, sizeof (_uuid)); +} + +const bt_uuid_t *BLEAttribute::bt_uuid(void) +{ + return (bt_uuid_t *)&_uuid; +} + + +BLEAttributeType +BLEAttribute::type() const { + return this->_type; +} + +bool BLEAttribute::compareUuid(const bt_uuid_t* uuid) +{ + int cmpresult = 0; + cmpresult = bt_uuid_cmp(uuid, (const bt_uuid_t*)&_uuid); + return (cmpresult == 0); + +} + +bool BLEAttribute::compareUuid(const char* uuid) +{ + bt_uuid_128_t temp; + BLEUtils::uuidString2BT(uuid,(bt_uuid_t *)&temp); + return compareUuid((bt_uuid_t *)&temp); +} + diff --git a/libraries/BLE/src/BLEAttribute.h b/libraries/BLE/src/BLEAttribute.h new file mode 100644 index 00000000..db69cf32 --- /dev/null +++ b/libraries/BLE/src/BLEAttribute.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2015 Intel Corporation. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef _BLE_ATTRIBUTE_H_INCLUDED +#define _BLE_ATTRIBUTE_H_INCLUDED + +#include "BLECommon.h" + +/// BLE attribute tyep enum +typedef enum { + BLETypeService = 0x2800, ///< the service type + BLETypeCharacteristic = 0x2803, ///< the characteristic type + BLETypeDescriptor = 0x2900 ///< the descriptor type +}BLEAttributeType; + + +class BLEAttribute { +public: + /** + * @brief Get the UUID raw data + * + * @param none + * + * @return bt_uuid_t* The pointer of UUID + * + * @note none + */ + const bt_uuid_t *bt_uuid(void); + + /** + * @brief Compare the UUID with the paramater data + * + * @param[in] data The pointer of data + * + * @param[in] uuidsize The max size of UUID + * + * @return bool true - UUID is the same with data + * false- UUID is not the same with data + * + * @note none + */ + bool compareUuid(const char* uuid); + bool compareUuid(const bt_uuid_t* uuid); + + BLEAttributeType type(void) const; + +protected: + BLEAttribute(const char* uuid, BLEAttributeType type); + BLEAttribute(const bt_uuid_t* uuid, BLEAttributeType type); +private: + bt_uuid_128_t _uuid; + + BLEAttributeType _type; + +}; + +#endif // _BLE_ATTRIBUTE_H_INCLUDED diff --git a/libraries/BLE/src/BLEAttributeWithValue.h b/libraries/BLE/src/BLEAttributeWithValue.h new file mode 100644 index 00000000..d1e2c152 --- /dev/null +++ b/libraries/BLE/src/BLEAttributeWithValue.h @@ -0,0 +1,63 @@ +/* + BLE Attribute with value API + Copyright (c) 2016 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef ARDUINO_BLE_ATTRIBUTE_WITH_VALUE__H +#define ARDUINO_BLE_ATTRIBUTE_WITH_VALUE__H + +class BLEAttributeWithValue +{ + public: + BLEAttributeWithValue(); + + virtual int valueLength() const; // returns the length of the attribute value + virtual const byte* value() const; // returns the value of the attribute array + virtual byte operator[] (int offset) const; // access an attribute value at the specified offset + virtual bool writeValue(const byte value[], int length); + + // intepret the value of the attribute with the specified type + String stringValue() const; + char charValue() const; + unsigned char unsignedCharValue() const; + byte byteValue() const; + short shortValue() const; + unsigned short unsignedShortValue() const; + int intValue() const; + unsigned int unsignedIntValue() const; + long longValue() const; + unsigned long unsignedLongValue() const; + float floatValue() const; + double doubleValue() const; + + // write the value of the attribute with the specified type + bool writeString(const String& s); + bool writeString(const char* s); + bool writeChar(char c); + bool writeUnsignedChar(unsigned char c); + bool writeByte(byte b); + bool writeShort(short s); + bool writeUnsignedShort(unsigned short s); + bool writeInt(int i); + bool writeUnsignedInt(unsigned int i); + bool writeLong(long l); + bool writeUnsignedLong(unsigned int l); + bool writeFloat(float f); + bool writeDouble(double d); +}; + +#endif diff --git a/libraries/BLE/src/BLECallbacks.cpp b/libraries/BLE/src/BLECallbacks.cpp new file mode 100644 index 00000000..db298071 --- /dev/null +++ b/libraries/BLE/src/BLECallbacks.cpp @@ -0,0 +1,190 @@ + + +#include + +#include "ArduinoBLE.h" + +#include "BLEAttribute.h" +#include "BLECharacteristicImp.h" +#include "BLEDeviceManager.h" +#include "BLEProfileManager.h" + +// GATT Server Only +ssize_t profile_read_process(bt_conn_t *conn, + const bt_gatt_attr_t *attr, + void *buf, uint16_t len, + uint16_t offset) +{ + const unsigned char *pvalue; + BLEAttribute *bleattr = (BLEAttribute *)attr->user_data; + BLEAttributeType type = bleattr->type(); + if (BLETypeCharacteristic == type) + { + BLECharacteristicImp* blecharacteritic = (BLECharacteristicImp*)bleattr; + pvalue = blecharacteritic->value(); + return bt_gatt_attr_read(conn, attr, buf, len, offset, pvalue, + blecharacteritic->valueLength()); + } + else if (BLETypeDescriptor == type) + { + BLEDescriptorImp* bledescriptor = (BLEDescriptorImp*)bleattr; + pvalue = bledescriptor->value(); + return bt_gatt_attr_read(conn, attr, buf, len, offset, pvalue, bledescriptor->valueLength()); + } + return 0; +} + +// GATT server only +ssize_t profile_write_process(bt_conn_t *conn, + const bt_gatt_attr_t *attr, + const void *buf, uint16_t len, + uint16_t offset) +{ + pr_info(LOG_MODULE_BLE, "%s1", __FUNCTION__); + BLEAttribute *bleattr = (BLEAttribute *)attr->user_data; + BLECharacteristicImp* blecharacteritic; + BLEAttributeType type = bleattr->type(); + if ((BLETypeCharacteristic != type) || 0 != offset) + { + return 0; + } + + blecharacteritic = (BLECharacteristicImp*)bleattr; + blecharacteritic->setValue((const uint8_t *) buf, len); + return len; +} + +ssize_t profile_longwrite_process(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + const void *buf, uint16_t len, + uint16_t offset) +{ + BLECharacteristicImp *blecharacteritic = (BLECharacteristicImp*)attr->user_data; + + blecharacteritic->setBuffer((const uint8_t *) buf, len, offset); + + return len; +} + +int profile_longflush_process(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + uint8_t flags) +{ + BLECharacteristicImp *blecharacteritic = (BLECharacteristicImp*)attr->user_data; + + switch (flags) + { + case BT_GATT_FLUSH_DISCARD: + /* Discard buffer reseting it back with data */ + blecharacteritic->discardBuffer(); + return 0; + case BT_GATT_FLUSH_SYNC: + /* Sync buffer to data */ + blecharacteritic->syncupBuffer2Value(); + return 0; + } + + return -EINVAL; +} + + +// GATT client only +uint8_t profile_notify_process (bt_conn_t *conn, + bt_gatt_subscribe_params_t *params, + const void *data, uint16_t length) +{ + //BLEPeripheralHelper* peripheral = BLECentralRole::instance()->peripheral(conn);// Find peripheral by bt_conn + //BLEAttribute* notifyatt = peripheral->attribute(params); // Find attribute by params + BLECharacteristicImp* chrc = NULL; + BLEDevice bleDevice(bt_conn_get_dst(conn)); + BLEProfileManager::instance()->characteristic(bleDevice, params->value_handle); + + //assert(notifyatt->type() == BLETypeCharacteristic); + pr_debug(LOG_MODULE_APP, "%s1", __FUNCTION__); + + chrc->setValue((const unsigned char *)data, length); + return BT_GATT_ITER_CONTINUE; +} + +// GATT client only +uint8_t profile_discover_process(bt_conn_t *conn, + const bt_gatt_attr_t *attr, + bt_gatt_discover_params_t *params) +{ + pr_debug(LOG_MODULE_BLE, "%s-%d", __FUNCTION__, __LINE__); + return BLEProfileManager::instance()->discoverResponseProc(conn, attr, params); +} + +// GATT Client only +uint8_t profile_read_rsp_process(bt_conn_t *conn, + int err, + bt_gatt_read_params_t *params, + const void *data, + uint16_t length) +{ + if (NULL == data) + { + return BT_GATT_ITER_STOP; + } + BLECharacteristicImp *chrc = NULL; + BLEDevice bleDevice(bt_conn_get_dst(conn)); + + // Get characteristic by handle params->single.handle + chrc = BLEProfileManager::instance()->characteristic(bleDevice, params->single.handle); + + chrc->setValue((const unsigned char *)data, length); + return BT_GATT_ITER_STOP; +} + + + +void bleConnectEventHandler(bt_conn_t *conn, + uint8_t err, + void *param) +{ + BLEDeviceManager* p = (BLEDeviceManager*)param; + + p->handleConnectEvent(conn, err); +} + + +void bleDisconnectEventHandler(bt_conn_t *conn, + uint8_t reason, + void *param) +{ + BLEDeviceManager* p = (BLEDeviceManager*)param; + + pr_info(LOG_MODULE_BLE, "Connect lost. Reason: %d", reason); + + p->handleDisconnectEvent(conn, reason); +} + +void bleParamUpdatedEventHandler(bt_conn_t *conn, + uint16_t interval, + uint16_t latency, + uint16_t timeout, + void *param) +{ + BLEDeviceManager* p = (BLEDeviceManager*)param; + + p->handleParamUpdated(conn, interval, latency, timeout); +} + + +void ble_central_device_found(const bt_addr_le_t *addr, + int8_t rssi, + uint8_t type, + const uint8_t *ad, + uint8_t len) +{ + char dev[BT_ADDR_LE_STR_LEN]; + + bt_addr_le_to_str(addr, dev, sizeof(dev)); + //pr_debug(LOG_MODULE_BLE, "[DEVICE]: %s, AD evt type %u, AD data len %u, RSSI %i\n", + // dev, type, len, rssi); + + BLEDeviceManager::instance()->handleDeviceFound(addr, rssi, type, + ad, len); +} + + diff --git a/libraries/BLE/src/BLECallbacks.h b/libraries/BLE/src/BLECallbacks.h new file mode 100644 index 00000000..76144392 --- /dev/null +++ b/libraries/BLE/src/BLECallbacks.h @@ -0,0 +1,53 @@ + +#ifndef __BLECALLBACKS_H__ +#define __BLECALLBACKS_H__ + +uint8_t profile_notify_process (bt_conn_t *conn, + bt_gatt_subscribe_params_t *params, + const void *data, uint16_t length); +uint8_t profile_read_rsp_process(bt_conn_t *conn, int err, + bt_gatt_read_params_t *params, + const void *data, + uint16_t length); +int profile_longflush_process(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + uint8_t flags); +ssize_t profile_longwrite_process(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + const void *buf, uint16_t len, + uint16_t offset); +ssize_t profile_write_process(bt_conn_t *conn, + const bt_gatt_attr_t *attr, + const void *buf, uint16_t len, + uint16_t offset); +ssize_t profile_read_process(bt_conn_t *conn, + const bt_gatt_attr_t *attr, + void *buf, uint16_t len, + uint16_t offset); + +uint8_t profile_discover_process(bt_conn_t *conn, + const bt_gatt_attr_t *attr, + bt_gatt_discover_params_t *params); + +void bleConnectEventHandler(bt_conn_t *conn, + uint8_t err, + void *param); + +void bleDisconnectEventHandler(bt_conn_t *conn, + uint8_t reason, + void *param); + +void bleParamUpdatedEventHandler(bt_conn_t *conn, + uint16_t interval, + uint16_t latency, + uint16_t timeout, + void *param); + +void ble_central_device_found(const bt_addr_le_t *addr, + int8_t rssi, + uint8_t type, + const uint8_t *ad, + uint8_t len); + +#endif + diff --git a/libraries/BLE/src/BLECharacteristic.cpp b/libraries/BLE/src/BLECharacteristic.cpp new file mode 100644 index 00000000..88033529 --- /dev/null +++ b/libraries/BLE/src/BLECharacteristic.cpp @@ -0,0 +1,365 @@ + +//#include "internal/ble_client.h" + +#include "BLECharacteristic.h" +#include "BLEProfileManager.h" +#include "BLEUtils.h" + +#include "BLECharacteristicImp.h" + +BLECharacteristic::BLECharacteristic(): + _bledev(), _internal(NULL), _properties(0), + _value_size(0), _value(NULL) +{ + memset(_uuid_cstr, 0, sizeof(_uuid_cstr)); +} + +BLECharacteristic::BLECharacteristic(const char* uuid, + unsigned char properties, + unsigned char valueSize): + _bledev(), _internal(NULL), _properties(properties), _value(NULL) +{ + bt_uuid_128 bt_uuid_tmp; + _value_size = valueSize > BLE_MAX_ATTR_LONGDATA_LEN ? BLE_MAX_ATTR_LONGDATA_LEN : valueSize; + BLEUtils::uuidString2BT(uuid, (bt_uuid_t *)&bt_uuid_tmp); + BLEUtils::uuidBT2String((const bt_uuid_t *)&bt_uuid_tmp, _uuid_cstr); + _bledev.setAddress(*BLEUtils::bleGetLoalAddress()); +} + +BLECharacteristic::BLECharacteristic(const char* uuid, + unsigned char properties, + const char* value): + BLECharacteristic(uuid, properties, strlen(value)) +{ + _setValue((const uint8_t*)value, strlen(value)); +} + +BLECharacteristic::BLECharacteristic(BLECharacteristicImp *characteristicImp, + const BLEDevice *bleDev): + _bledev(bleDev), _internal(characteristicImp), + _value(NULL) +{ + _properties = characteristicImp->properties(); + _value_size = characteristicImp->valueSize(); +} + +BLECharacteristic::~BLECharacteristic() +{ + if (_value) + { + bfree(_value); + _value = NULL; + } +} + +const char* BLECharacteristic::uuid() const +{ + return _uuid_cstr; +} + +unsigned char BLECharacteristic::properties() +{ + unsigned char property = 0; + BLECharacteristicImp *characteristicImp = getImplementation(); + if (NULL != characteristicImp) + { + property = characteristicImp->properties(); + } + return property; +} + +int BLECharacteristic::valueSize() //const +{ + int valuesize = 0; + BLECharacteristicImp *characteristicImp = getImplementation(); + if (NULL != characteristicImp) + { + valuesize = characteristicImp->valueSize(); + } + return valuesize; +} + +const byte* BLECharacteristic::value() //const +{ + const byte* value_temp = NULL; + BLECharacteristicImp *characteristicImp = getImplementation(); + if (NULL != characteristicImp) + { + value_temp = characteristicImp->value(); + } + return value_temp; +} + +int BLECharacteristic::valueLength() //const +{ + int valueLength = 0; + BLECharacteristicImp *characteristicImp = getImplementation(); + if (NULL != characteristicImp) + { + valueLength = characteristicImp->valueLength(); + } + return valueLength; +} + +BLECharacteristic::operator bool() const +{ + return (strlen(_uuid_cstr) > 3); +} + + +byte BLECharacteristic::operator[] (int offset) //const +{ + byte data = 0; + BLECharacteristicImp *characteristicImp = getImplementation(); + + if (NULL != characteristicImp) + { + data = (*characteristicImp)[offset]; + } + return data; +} + +bool BLECharacteristic::writeValue(const byte value[], int length) +{ + bool retVar = false; + BLECharacteristicImp *characteristicImp = getImplementation(); + + if (NULL != characteristicImp) + { + characteristicImp->setValue((const unsigned char *)value, (uint16_t)length); + retVar = true; + } + return retVar; +} + +bool BLECharacteristic::writeValue(const byte value[], int length, int offset) +{ + // TODO: Not support it now. + // Will add this feature. + return false; +} + +bool BLECharacteristic::writeValue(const char* value) +{ + return writeValue((const byte*)value, strlen(value)); +} + +bool BLECharacteristic::broadcast() +{ + // TODO: Need more information + return false; +} + +bool BLECharacteristic::written() +{ + bool retVar = false; + BLECharacteristicImp *characteristicImp = getImplementation(); + + if (NULL != characteristicImp) + { + retVar = characteristicImp->written(); + } + return retVar; +} + +bool BLECharacteristic::subscribed() +{ + bool retVar = false; + BLECharacteristicImp *characteristicImp = getImplementation(); + + if (NULL != characteristicImp) + { + retVar = characteristicImp->subscribed(); + } + return retVar; +} + +bool BLECharacteristic::canNotify() +{ + bool retVar = false; + BLECharacteristicImp *characteristicImp = getImplementation(); + + if (NULL != characteristicImp) + { + retVar = characteristicImp->canNotify(); + } + return retVar; +} + +bool BLECharacteristic::canIndicate() +{ + // TODO: Need more confirmation + return false; +} + +bool BLECharacteristic::canRead() +{ + // TODO: Need more confirmation + return false; +} +bool BLECharacteristic::canWrite() +{ + // TODO: Need more confirmation + return false; +} +bool BLECharacteristic::canSubscribe() +{ + // TODO: Need more confirmation + return false; +} +bool BLECharacteristic::canUnsubscribe() +{ + // TODO: Need more confirmation + return false; +} + +bool BLECharacteristic::read() +{ + bool retVar = false; + BLECharacteristicImp *characteristicImp = getImplementation(); + + if (NULL != characteristicImp) + { + retVar = characteristicImp->read(); + } + return retVar; +} + +bool BLECharacteristic::write(const unsigned char* value, int length) +{ + bool retVar = false; + BLECharacteristicImp *characteristicImp = getImplementation(); + + if (NULL != characteristicImp) + { + retVar = characteristicImp->write(value, (uint16_t)length); + } + return retVar; +} + +bool BLECharacteristic::subscribe() +{ + bool retVar = false; + BLECharacteristicImp *characteristicImp = getImplementation(); + + if (NULL != characteristicImp) + { + // TODO: Central feature. Peripheral first + //retVar = characteristicImp->s(); + } + return retVar; +} + +bool BLECharacteristic::unsubscribe() +{ + bool retVar = false; + BLECharacteristicImp *characteristicImp = getImplementation(); + + if (NULL != characteristicImp) + { + // TODO: Central feature + //retVar = characteristicImp->canNotify(); + } + return retVar; +} + +bool BLECharacteristic::valueUpdated() +{ + bool retVar = false; + BLECharacteristicImp *characteristicImp = getImplementation(); + + if (NULL != characteristicImp) + { + retVar = characteristicImp->valueUpdated(); + } + return retVar; +} + +int BLECharacteristic::addDescriptor(BLEDescriptor& descriptor) +{ + bool retVar = false; + BLECharacteristicImp *characteristicImp = getImplementation(); + + if (NULL != characteristicImp) + { + retVar = characteristicImp->addDescriptor(descriptor); + } + return retVar; +} + +int BLECharacteristic::descriptorCount() //const +{ + int count = 0; + BLECharacteristicImp *characteristicImp = getImplementation(); + + if (NULL != characteristicImp) + { + count = characteristicImp->descriptorCount(); + } + return count; +} + +bool BLECharacteristic::hasDescriptor(const char* uuid) const +{ + // TODO: Not support now + return false; +} +bool BLECharacteristic::hasDescriptor(const char* uuid, int index) const +{ + // TODO: Not support now + return false; +} +BLEDescriptor BLECharacteristic::descriptor(int index) const +{ + // TODO: Not support now + return BLEDescriptor(); +} +BLEDescriptor BLECharacteristic::descriptor(const char * uuid) const +{ + // TODO: Not support now + return BLEDescriptor(); +} +BLEDescriptor BLECharacteristic::descriptor(const char * uuid, int index) const +{ + // TODO: Not support now + return BLEDescriptor(); +} +void BLECharacteristic::setEventHandler(BLECharacteristicEvent event, + BLECharacteristicEventHandler eventHandler) +{} + + +void +BLECharacteristic::_setValue(const uint8_t value[], uint16_t length) +{ + if (length > _value_size) { + length = _value_size; + } + + if (NULL == _value) + { + // Allocate the buffer for characteristic + _value = (unsigned char*)balloc(_value_size, NULL);//malloc(_value_size) + } + if (NULL == _value) + { + return; + } + memcpy(_value, value, length); +} + +BLECharacteristicImp* BLECharacteristic::getImplementation() +{ + if (NULL == _internal) + { + _internal = BLEProfileManager::instance()->characteristic(_bledev, (const char*)_uuid_cstr); + } + return _internal; +} + +void BLECharacteristic::setBLECharacteristicImp(BLECharacteristicImp *characteristicImp) +{ + _internal = characteristicImp; +} + + diff --git a/libraries/BLE/src/BLECharacteristic.h b/libraries/BLE/src/BLECharacteristic.h new file mode 100644 index 00000000..19a10266 --- /dev/null +++ b/libraries/BLE/src/BLECharacteristic.h @@ -0,0 +1,506 @@ +/* + BLE Characteristic API + Copyright (c) 2016 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef ARDUINO_BLE_CHARACTERISTIC_H +#define ARDUINO_BLE_CHARACTERISTIC_H + +//#include "BLEAttributeWithValue.h" +#include "BLECommon.h" +//#include "BLEDescriptor.h" +#include "ArduinoBLE.h" + +#include "BLEDevice.h" +//#include "BLECharacteristicImp.h" +//class BLEDescriptorImp; + +enum BLECharacteristicEvent { + BLEWritten = 0, + BLESubscribed = 1, + BLEUnsubscribed = 2, + BLEValueUpdated = 3, + BLECharacteristicEventLast +}; + +enum BLEProperty { + BLEBroadcast = 0x01, + BLERead = 0x02, + BLEWriteWithoutResponse = 0x04, + BLEWrite = 0x08, + BLENotify = 0x10, + BLEIndicate = 0x20 +}; + +typedef void (*BLECharacteristicEventHandler)(BLEDevice& bledev, BLECharacteristic& characteristic); + +//#include "BLECharacteristicImp.h" + +class BLECharacteristic //: public BLEAttributeWithValue +{ +public: + BLECharacteristic(); + /** + * @brief Create a characteristic with specified value size + * + * @param uuid The UUID of the characteristic + * + * @param properties The properties of the characteristic + * + * @param valueSize The size of the characteristic data + * + * @return none + * + * @note none + */ + BLECharacteristic(const char* uuid, + unsigned char properties, + unsigned char valueSize); + + /** + * @brief Create a characteristic with string value + * + * @param uuid The UUID of the characteristic + * + * @param properties The properties of the characteristic + * + * @param value The string of the characteristic data + * + * @return none + * + * @note The data length is string's size. Can't set a string that is longger + * than the this input + */ + BLECharacteristic(const char* uuid, + unsigned char properties, + const char* value); + + virtual ~BLECharacteristic(); + + /** + * @brief Is the characteristic valid + * + * @param none + * + * @return bool true/false + * + * @note Invalid characteristic is NULL pointer or all zero with UUID + */ + virtual operator bool() const; // + + /** + * @brief Get the characteristic's UUID string + * + * @param none + * + * @return const char* The UUID string + * + * @note none + */ + const char* uuid() const; + + /** + * @brief Get the property mask of the characteristic + * + * @param none + * + * @return unsigned char The property mask of the characteristic + * + * @note none + */ + unsigned char properties(); + + /** + * @brief Get the maximum size of the value + * + * @param none + * + * @return int The maximum size of the value + * + * @note none + */ + int valueSize();//const + + /** + * @brief Get the value buffer + * + * @param none + * + * @return const byte* The value buffer + * + * @note none + */ + virtual const byte* value();//const + + /** + * @brief Get the current length of the value + * + * @param none + * + * @return int The current length of the value string + * + * @note TODO: How to handle if the data is RAW data? This API is danger + */ + virtual int valueLength();//const + + /** + * @brief Get a byte of the value at the specified offset + * + * @param none + * + * @return byte A byte of the value at the specified offset + * + * @note none + */ + virtual byte operator[] (int offset);//const + + /** + * @brief Write the value of the characteristic + * + * @param value The value buffer that want to write to characteristic + * + * @param length The value buffer's length + * + * @return bool true - Success, false - Failed + * + * @note none + */ + virtual bool writeValue(const byte value[], int length); + + /** + * @brief Write the value of the characteristic + * + * @param value The value buffer that want to write to characteristic + * + * @param length The value buffer's length + * + * @param offset The offset in the characteristic's data + * + * @return bool true - Success, false - Failed + * + * @note none + */ + bool writeValue(const byte value[], int length, int offset); + + /** + * @brief Write the value of the characteristic + * + * @param value The value string that want to write to characteristic + * + * @return bool true - Success, false - Failed + * + * @note none + */ + bool writeValue(const char* value); + + // peripheral mode + bool broadcast(); // broadcast the characteristic value in the advertisement data + + // GATT server + /** + * @brief Has the GATT client written a new value + * + * @param none + * + * @return bool true - Written, false - Not changed + * + * @note GATT server only. GATT client always return false. + */ + bool written(); + + /** + * @brief Is the GATT client subscribed + * + * @param none + * + * @return bool true - Subscribed, false - Not subscribed + * + * @note GATT server and client + */ + bool subscribed(); + + /** + * @brief Can a notification be sent to the GATT client + * + * @param none + * + * @return true - Yes, false - No + * + * @note GATT server only + */ + bool canNotify(); + + /** + * @brief Can a indication be sent to the GATT client + * + * @param none + * + * @return true - Yes, false - No + * + * @note GATT server only + */ + bool canIndicate(); + + // GATT + /** + * @brief Can the characteristic be read (based on properties) + * + * @param none + * + * @return true - readable, false - None + * + * @note none + */ + bool canRead(); + + /** + * @brief Can the characteristic be written (based on properties) + * + * @param none + * + * @return true - writable, false - None + * + * @note none + */ + bool canWrite(); + + /** + * @brief Can the characteristic be subscribed to (based on properties) + * + * @param none + * + * @return true - Can be subscribed, false - No + * + * @note What different with canUnsubscribe? + */ + bool canSubscribe(); + + /** + * @brief Can the characteristic be unsubscribed to (based on properties) + * + * @param none + * + * @return true - Can be unsubscribed, false - No + * + * @note none + */ + bool canUnsubscribe(); + + /** + * @brief Read the characteristic value + * + * @param none + * + * @return bool true - Success, false - Failed + * + * @note Only for GATT client. Schedule read request to the GATT server + */ + virtual bool read(); + + /** + * @brief Write the charcteristic value + * + * @param value The value buffer that want to write to characteristic + * + * @param length The value buffer's length + * + * @return bool true - Success, false - Failed + * + * @note Only for GATT client. Schedule write request to the GATT server + */ + virtual bool write(const unsigned char* value, int length); + + /** + * @brief Subscribe to the characteristic + * + * @param none + * + * @return bool true - Success, false - Failed + * + * @note Only for GATT client. Schedule CCCD to the GATT server + */ + bool subscribe(); + + /** + * @brief Unsubscribe to the characteristic + * + * @param none + * + * @return bool true - Success, false - Failed + * + * @note Only for GATT client. Schedule CCCD to the GATT server + */ + bool unsubscribe(); + + bool valueUpdated(); // Read response updated the characteristic + + /** + * @brief Add the characteristic's descriptor + * + * @param descriptor The descriptor for characteristic + * + * @return none + * + * @note none + */ + int addDescriptor(BLEDescriptor& descriptor); + + /** + * @brief Get the number of descriptors the characteristic has + * + * @param none + * + * @return int the number of descriptors the characteristic has + * + * @note none + */ + int descriptorCount();//const + + /** + * @brief Does the characteristic have a descriptor with the specified UUID + * + * @param uuid The descriptor's UUID + * + * @return bool true - Yes. false - No + * + * @note none + */ + bool hasDescriptor(const char* uuid) const; + + /** + * @brief Does the characteristic have an nth descriptor with the specified UUID + * + * @param uuid The descriptor's UUID + * + * @param index The index of descriptor + * + * @return bool true - Yes. false - No + * + * @note none + */ + bool hasDescriptor(const char* uuid, int index) const; + + /** + * @brief Get the nth descriptor of the characteristic + * + * @param index The index of descriptor + * + * @return BLEDescriptor The descriptor + * + * @note none + */ + BLEDescriptor descriptor(int index) const; + + /** + * @brief Get the descriptor with the specified UUID + * + * @param uuid The descriptor's UUID + * + * @return BLEDescriptor The descriptor + * + * @note none + */ + BLEDescriptor descriptor(const char * uuid) const; + + /** + * @brief Get the nth descriptor with the specified UUID + * + * @param uuid The descriptor's UUID + * + * @param index The index of descriptor + * + * @return BLEDescriptor The descriptor + * + * @note none + */ + BLEDescriptor descriptor(const char * uuid, int index) const; + + /** + * @brief Set an event handler (callback) + * + * @param event Characteristic event + * + * @param eventHandler The handler of characteristic + * + * @return none + * + * @note none + */ + void setEventHandler(BLECharacteristicEvent event, + BLECharacteristicEventHandler eventHandler); + +protected: + friend class BLEDevice; + friend class BLEService; + /** + * @brief Create a characteristic with specified value size + * + * @param characteristicImp The implementation of the characteristic + * + * @param bleDev The peer BLE device + * + * @return none + * + * @note none + */ + BLECharacteristic(BLECharacteristicImp *characteristicImp, + const BLEDevice *bleDev); + + /** + * @brief Create a characteristic with string value + * + * @param uuid The UUID of the characteristic + * + * @param properties The properties of the characteristic + * + * @param value The string of the characteristic data + * + * @param bleDev The peer BLE device + * + * @return none + * + * @note The data length is string's size. Can't set a string that is longger + * than the this input + */ + //BLECharacteristic(const char* uuid, + // unsigned char properties, + // const char* value, + // BLEDevice *bleDev); + + // For GATT + void setBLECharacteristicImp(BLECharacteristicImp *characteristicImp); + +private: + void _setValue(const uint8_t value[], uint16_t length); + BLECharacteristicImp *getImplementation(); + +private: + char _uuid_cstr[37]; // The characteristic UUID + BLEDevice _bledev; // The GATT server BLE object. Only for GATT client to read/write + // NULL - GATT server + // None-NULL - GATT client + BLECharacteristicImp *_internal; // The real implementation of characteristic. +protected: + friend class BLECharacteristicImp; + unsigned char _properties; // The characteristic property + + unsigned char _value_size; // The value size + unsigned char* _value; // The value. Will delete after create the _internal +}; + +#endif + diff --git a/libraries/BLE/src/BLECharacteristicImp.cpp b/libraries/BLE/src/BLECharacteristicImp.cpp new file mode 100644 index 00000000..34758015 --- /dev/null +++ b/libraries/BLE/src/BLECharacteristicImp.cpp @@ -0,0 +1,560 @@ +/* + * Copyright (c) 2015 Intel Corporation. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "BLEAttribute.h" +#include "BLECharacteristicImp.h" + +#include "BLECallbacks.h" +#include "BLEUtils.h" + +bt_uuid_16_t BLECharacteristicImp::_gatt_chrc_uuid = {BT_UUID_TYPE_16, BT_UUID_GATT_CHRC_VAL}; +bt_uuid_16_t BLECharacteristicImp::_gatt_ccc_uuid = {BT_UUID_TYPE_16, BT_UUID_GATT_CCC_VAL}; + +BLECharacteristicImp::BLECharacteristicImp(BLECharacteristic& characteristic, + const BLEDevice& bledevice): + BLEAttribute(characteristic.uuid(), BLETypeCharacteristic), + _value_length(0), + _value_buffer(NULL), + _value_updated(false), + _attr_chrc_declaration(NULL), + _attr_chrc_value(NULL), + _attr_cccd(NULL), + _ble_device() +{ + unsigned char properties = characteristic._properties; + _value_size = characteristic._value_size; + _value = (unsigned char*)balloc(_value_size, NULL); + if (_value_size > BLE_MAX_ATTR_DATA_LEN) + { + _value_buffer = (unsigned char*)balloc(_value_size, NULL); + } + + memset(&_ccc_cfg, 0, sizeof(_ccc_cfg)); + memset(&_ccc_value, 0, sizeof(_ccc_value)); + memset(&_gatt_chrc, 0, sizeof(_gatt_chrc)); + memset(&_sub_params, 0, sizeof(_sub_params)); + + _ccc_value.cfg = &_ccc_cfg; + _ccc_value.cfg_len = 1; + if (BLERead & properties) + { + _gatt_chrc.properties |= BT_GATT_CHRC_READ; + } + if (BLEWrite & properties) + { + _gatt_chrc.properties |= BT_GATT_CHRC_WRITE; + } + if (BLEWriteWithoutResponse & properties) + { + _gatt_chrc.properties |= BT_GATT_CHRC_WRITE_WITHOUT_RESP; + } + if (BLENotify & properties) + { + _gatt_chrc.properties |= BT_GATT_CHRC_NOTIFY; + _sub_params.value |= BT_GATT_CCC_NOTIFY; + } + if (BLEIndicate & properties) + { + _gatt_chrc.properties |= BT_GATT_CHRC_INDICATE; + _sub_params.value |= BT_GATT_CCC_INDICATE; + } + _gatt_chrc.uuid = (bt_uuid_t*)this->bt_uuid();//&_characteristic_uuid;//this->uuid(); + memset(_event_handlers, 0, sizeof(_event_handlers)); + + _sub_params.notify = profile_notify_process; + + if (NULL != characteristic._value) + { + memcpy(_value, characteristic._value, _value_size); + } + + // Update BLE device object + _ble_device.setAddress(*bledevice.bt_le_address()); + + characteristic.setBLECharacteristicImp(this); + memset(&_descriptors_header, 0, sizeof(_descriptors_header)); +} + +BLECharacteristicImp::~BLECharacteristicImp() +{ + releaseDescriptors(); + if (_value) { + bfree(_value); + _value = NULL; + } + if (_value_buffer) + { + bfree(_value_buffer); + _value_buffer = NULL; + } +} + +unsigned char +BLECharacteristicImp::properties() const +{ + return _gatt_chrc.properties; +} + +bool BLECharacteristicImp::writeValue(const byte value[], int length) +{ + int status; + + _setValue(value, length); + + // Address same is GATT server. Send notification if CCCD enabled + // Different is GATT client. Send write request + if (true == BLEUtils::isLocalBLE(_ble_device) && + NULL != _attr_chrc_value) + { + // Notify for peripheral. + status = bt_gatt_notify(NULL, _attr_chrc_value, value, length, NULL); + if (0 != status) + { + return false; + } + } + + //Not schedule write request for central + // The write request may failed. + // If user want to get latest set value. Call read and get the real value + return true; +} + +bool +BLECharacteristicImp::setValue(const unsigned char value[], uint16_t length) +{ + _setValue(value, length); + // Read response/Notification/Indication for GATT client + // Write request for GATT server + if (_event_handlers[BLEWritten]) + { + BLECharacteristic chrcTmp(this, &_ble_device); + _event_handlers[BLEWritten](_ble_device, chrcTmp); + } + + return true; +} + +unsigned short +BLECharacteristicImp::valueSize() const +{ + return _value_size; +} + +const unsigned char* +BLECharacteristicImp::value() const +{ + return _value; +} + +unsigned short +BLECharacteristicImp::valueLength() const +{ + return _value_length; +} + +unsigned char +BLECharacteristicImp::operator[] (int offset) const +{ + return _value[offset]; +} + +bool +BLECharacteristicImp::written() +{ + bool written = false; + if (true == BLEUtils::isLocalBLE(_ble_device)) + { + // GATT server. The characteristic on local device + written = _value_updated; + _value_updated = false; + } + + return written; +} + +bool BLECharacteristicImp::valueUpdated() +{ + bool updated = false; + if (false == BLEUtils::isLocalBLE(_ble_device)) + { + // GATT client. The characteristic on remote device. + updated = _value_updated; + _value_updated = false; + } + return updated; +} + +bool +BLECharacteristicImp::subscribed() +{ + return (_gatt_chrc.properties & (BT_GATT_CHRC_NOTIFY | BT_GATT_CHRC_INDICATE)); +} + +void +BLECharacteristicImp::setEventHandler(BLECharacteristicEvent event, BLECharacteristicEventHandler callback) +{ + noInterrupts(); + if (event < sizeof(_event_handlers)) { + _event_handlers[event] = callback; + } + interrupts(); +} + +uint16_t +BLECharacteristicImp::valueHandle() +{ + uint16_t handle = 0; + if (NULL != _attr_chrc_value) + { + handle = _attr_chrc_value->handle; + } + + return handle; +} + +uint16_t +BLECharacteristicImp::cccdHandle() +{ + uint16_t handle = 0; + if (NULL != _attr_cccd) + { + handle = _attr_cccd->handle; + } + return handle; +} + +void +BLECharacteristicImp::_setValue(const uint8_t value[], uint16_t length) +{ + if (length > _value_size) { + length = _value_size; + } + + _value_updated = true; + memcpy(_value, value, length); + _value_length = length; +} + +_bt_gatt_ccc_t* BLECharacteristicImp::getCccCfg(void) +{ + return &_ccc_value; +} + +bt_gatt_chrc_t* BLECharacteristicImp::getCharacteristicAttValue(void) +{ + return &_gatt_chrc; +} + +uint8_t BLECharacteristicImp::getPermission(void) +{ + uint8_t perm = 0; + if (_gatt_chrc.properties & BT_GATT_CHRC_READ) + { + perm |= BT_GATT_PERM_READ; + } + if (_gatt_chrc.properties & (BT_GATT_CHRC_WRITE | BT_GATT_CHRC_WRITE_WITHOUT_RESP)) + { + perm |= BT_GATT_PERM_WRITE; + } + return perm; +} + +bt_uuid_t* BLECharacteristicImp::getCharacteristicAttributeUuid(void) +{ + return (bt_uuid_t*) &_gatt_chrc_uuid; +} + +bt_uuid_t* BLECharacteristicImp::getClientCharacteristicConfigUuid(void) +{ + return (bt_uuid_t*) &_gatt_ccc_uuid; +} + +#if 0 +void BLECharacteristicImp::discover(bt_gatt_discover_params_t *params) +{ + params->type = BT_GATT_DISCOVER_CHARACTERISTIC; + params->uuid = this->uuid(); + // Start discovering + _discoverying = true; + // Re-Init the read/write parameter + _reading = false; +} + + +void BLECharacteristicImp::discover(const bt_gatt_attr_t *attr, + bt_gatt_discover_params_t *params) +{ + if (!attr) + { + // Discovery complete + _discoverying = false; + return; + } + + // Chracteristic Char + if (params->uuid == this->uuid()) + { + // Set Discover CCCD parameter + params->start_handle = attr->handle + 2; + if (subscribed()) + { + // Include CCCD + params->type = BT_GATT_DISCOVER_DESCRIPTOR; + params->uuid = this->getClientCharacteristicConfigUuid(); + } + else + { + // Complete the discover + _discoverying = false; + } + } + else if (params->uuid == this->getClientCharacteristicConfigUuid()) + { + params->start_handle = attr->handle + 1; + _discoverying = false; + } +} +#endif + +bt_gatt_subscribe_params_t *BLECharacteristicImp::getSubscribeParams() +{ + return &_sub_params; +} + +bool BLECharacteristicImp::read() +{ + int retval = 0; + bt_conn_t* conn = NULL; + + if (true == BLEUtils::isLocalBLE(_ble_device)) + { + // GATT server can't write + return false; + } + + if (_reading) + { + // Already in reading state + return false; + } + + _read_params.func = profile_read_rsp_process; + _read_params.handle_count = 1; + _read_params.single.handle = _value_handle; + _read_params.single.offset = 0; + + if (0 == _read_params.single.handle) + { + // Discover not complete + return false; + } + + conn = bt_conn_lookup_addr_le(_ble_device.bt_le_address()); + if (NULL == conn) + { + return false; + } + + // Send read request + retval = bt_gatt_read(conn, &_read_params); + bt_conn_unref(conn); + if (0 == retval) + { + _reading = true; + } + return _reading; +} + +bool BLECharacteristicImp::write(const unsigned char value[], + uint16_t length) +{ + int retval = 0; + bt_conn_t* conn = NULL; + + if (true == BLEUtils::isLocalBLE(_ble_device)) + { + // GATT server can't write + return false; + } + + conn = bt_conn_lookup_addr_le(_ble_device.bt_le_address()); + if (NULL == conn) + { + return false; + } + + // Send read request + retval = bt_gatt_write_without_response(conn, + _value_handle, + value, + length, + false); + bt_conn_unref(conn); + return (0 == retval); +} + +void BLECharacteristicImp::setBuffer(const uint8_t value[], + uint16_t length, + uint16_t offset) +{ + if (length + offset > _value_size) { + // Ignore the data + return; + } + + memcpy(_value_buffer + offset, value, length); +} + +void BLECharacteristicImp::syncupBuffer2Value() +{ + setValue(_value_buffer, _value_size); +} + +void BLECharacteristicImp::discardBuffer() +{ + memcpy(_value_buffer, _value, _value_size); +} + +bool BLECharacteristicImp::longCharacteristic() +{ + return (_value_size > BLE_MAX_ATTR_DATA_LEN); +} + +int BLECharacteristicImp::updateProfile(bt_gatt_attr_t *attr_start, int& index) +{ + bt_gatt_attr_t *start = attr_start; + int base_index = index; + int offset = 0; + int counter = 0; + + // Characteristic declare + memset(start, 0, sizeof(bt_gatt_attr_t)); + start->uuid = getCharacteristicAttributeUuid(); + start->perm = BT_GATT_PERM_READ; + start->read = bt_gatt_attr_read_chrc; + start->user_data = this->getCharacteristicAttValue(); + pr_info(LOG_MODULE_BLE, "chrc-%p, uuid type-%d", start, start->uuid->type); + + start++; + index++; + counter++; + + // Descriptor + memset(start, 0, sizeof(bt_gatt_attr_t)); + start->uuid = (bt_uuid_t *)bt_uuid(); + start->perm = this->getPermission(); + start->user_data = (void*)((BLEAttribute*)this); + start->read = profile_read_process; + + if (this->longCharacteristic() == false) + { + // Normal characteristic MAX. 20 + start->write = profile_write_process; + } + else + { + // Long characteristic. MAX. 512 + start->write = profile_longwrite_process; + start->flush = profile_longflush_process; + } + _attr_chrc_value = start; + pr_debug(LOG_MODULE_BLE, "chrcdescripor-%p, chimp-%p type-%d", start, this, this->type()); + + start++; + index++; + counter++; + + if (this->subscribed()) + { + // Descriptor + memset(start, 0, sizeof(bt_gatt_attr_t)); + start->uuid = this->getClientCharacteristicConfigUuid(); + start->perm = BT_GATT_PERM_READ | BT_GATT_PERM_WRITE; + start->read = bt_gatt_attr_read_ccc; + start->write = bt_gatt_attr_write_ccc; + start->user_data = this->getCccCfg(); + + pr_info(LOG_MODULE_BLE, "cccd-%p", start); + + start++; + index++; + counter++; + } + + BLEDescriptorNodePtr node = _descriptors_header.next; + while (NULL != node) + { + BLEDescriptorImp *descriptorImp = node->value; + start = attr_start + index - base_index; + offset = descriptorImp->updateProfile(start, index); + counter += offset; + node = node->next; + } + pr_debug(LOG_MODULE_BLE, "%s:type-%d", __FUNCTION__, this->type()); + return counter; +} + +int BLECharacteristicImp::addDescriptor(BLEDescriptor& descriptor) +{ + BLEDescriptorImp* descriptorImp = new BLEDescriptorImp(_ble_device, descriptor); + pr_debug(LOG_MODULE_BLE, "%s-%d",__FUNCTION__, __LINE__); + if (NULL == descriptorImp) + { + return BLE_STATUS_NO_MEMORY; + } + + pr_debug(LOG_MODULE_BLE, "%s-%d",__FUNCTION__, __LINE__); + BLEDescriptorNodePtr node = link_node_create(descriptorImp); + if (NULL == node) + { + delete[] descriptorImp; + return BLE_STATUS_NO_MEMORY; + } + link_node_insert_last(&_descriptors_header, node); + pr_debug(LOG_MODULE_BLE, "%s-%d",__FUNCTION__, __LINE__); + return BLE_STATUS_SUCCESS; +} + +void BLECharacteristicImp::releaseDescriptors() +{ + BLEDescriptorNodePtr node = link_node_get_first(&_descriptors_header); + + while (NULL != node) + { + BLEDescriptorImp* descriptorImp = node->value; + delete[] descriptorImp; + link_node_remove_first(&_descriptors_header); + node = link_node_get_first(&_descriptors_header); + } +} + +int BLECharacteristicImp::getAttributeCount() +{ + int counter = link_list_size(&_descriptors_header) + 2; // Declaration and descriptor + return counter; +} + +int BLECharacteristicImp::descriptorCount() const +{ + int counter = link_list_size(&_descriptors_header); + return counter; +} + + diff --git a/libraries/BLE/src/BLECharacteristicImp.h b/libraries/BLE/src/BLECharacteristicImp.h new file mode 100644 index 00000000..be95fe59 --- /dev/null +++ b/libraries/BLE/src/BLECharacteristicImp.h @@ -0,0 +1,313 @@ +/* + * Copyright (c) 2015 Intel Corporation. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef _BLE_CHARACTERISTICIMP_H_INCLUDED +#define _BLE_CHARACTERISTICIMP_H_INCLUDED + +//#include "BLECommon.h" + +//#include "BLEDevice.h" +//#include "BLEDescriptor.h" + +#include "ArduinoBLE.h" +#include "BLEDescriptorImp.h" + +#include "BLEDevice.h" + +#include "LinkList.h" +class BLEDescriptorImp; +/** + * BLE GATT Characteristic : public BLEAttribute + */ +class BLECharacteristicImp: public BLEAttribute{ +public: + + virtual ~BLECharacteristicImp(); + + + /** + * @brief Add the characteristic's descriptor + * + * @param descriptor The descriptor for characteristic + * + * @return none + * + * @note none + */ + int addDescriptor(BLEDescriptor& descriptor); + + void releaseDescriptors(); + + /** + * @brief Write the value of the characteristic + * + * @param value The value buffer that want to write to characteristic + * + * @param length The value buffer's length + * + * @param offset The offset in the characteristic's data + * + * @return bool true - Success, false - Failed + * + * @note none + */ + bool writeValue(const byte value[], int length); + + /** + * Set the current value of the Characteristic + * + * @param[in] value New value to set, as a byte array. Data is stored in internal copy. + * @param[in] length Length, in bytes, of valid data in the array to write. + * Must not exceed maxLength set for this characteristic. + * + * @return bool true set value success, false on error + */ + bool setValue(const unsigned char value[], unsigned short length); + + /** + * Get the property mask of the Characteristic + * + * @return unsigned char property mask of the Characteristic + */ + unsigned char properties(void) const; + + /** + * Get the (maximum) size of the Characteristic + * + * @return unsigned size of characateristic in bytes + */ + unsigned short valueSize(void) const; + + /** + * Get data pointer to the value of the Characteristic + * + * @return const unsigned char* pointer to the value of the Characteristic + */ + const unsigned char* value(void) const; + + /** + * Get the current length of the value of the Characteristic + * + * @return unsigned short size of characateristic value in bytes + */ + unsigned short valueLength() const; + + unsigned char operator[] (int offset) const; + + /** + * Has the value of the Characteristic been written by a central + * + * @return bool true is central has updated characteristic value, otherwise false + */ + bool written(void); + bool valueUpdated(); + + /** + * Is a central listening for notifications or indications of the Characteristic + * + * @return bool true is central is subscribed, otherwise false + */ + bool subscribed(void); + bool canNotify(); + + /** + * Provide a function to be called when events related to this Characteristic are raised + * + * @param[in] event Event type to set event handler for + * @param[in] callback Pointer to callback function to invoke when the event occurs. + */ + void setEventHandler(BLECharacteristicEvent event, BLECharacteristicEventHandler callback); + + /** + * @brief Schedule the read request to read the characteristic in peripheral + * + * @param[in] none + * + * @return bool Indicate the success or error + * + * @note Only for central device + */ + bool read(); + + /** + * @brief Schedule the write request to update the characteristic in peripheral + * + * @param[in] peripheral The peripheral device that want to be updated + * @param[in] value New value to set, as a byte array. Data is stored in internal copy. + * @param[in] length Length, in bytes, of valid data in the array to write. + * Must not exceed maxLength set for this characteristic. + * + * @return bool true set value success, false on error + * + * @note none + */ + bool write(const unsigned char value[], + uint16_t length); + + int descriptorCount() const; + +protected: + friend class BLEProfileManager; + friend class BLEServiceImp; + /** + * Constructor for BLE Characteristic + * + * @param[in] characteristic The characteristic + * @param[in] bledevice The device that has this characteristic + */ + BLECharacteristicImp(BLECharacteristic& characteristic, const BLEDevice& bledevice); + + friend int profile_longflush_process(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + uint8_t flags); + friend ssize_t profile_longwrite_process(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + const void *buf, uint16_t len, + uint16_t offset); + + int updateProfile(bt_gatt_attr_t *attr_start, int& index); + + int getAttributeCount(); + + bool longCharacteristic(); + + void setBuffer(const uint8_t value[], + uint16_t length, + uint16_t offset); + void discardBuffer(); + void syncupBuffer2Value(); + + /** + * @brief Get the characteristic value handle + * + * @param none + * + * @return none + * + * @note Only for peripheral + */ + uint16_t valueHandle(void); + + /** + * @brief Get characteristic configuration descriptor value handle + * + * @param none + * + * @return uint16_t The value handle + * 0 is invalid handle + * + * @note Only for peripheral + */ + uint16_t cccdHandle(void); + + + void setUserDescription(BLEDescriptor *descriptor); + void setPresentationFormat(BLEDescriptor *descriptor); + + _bt_gatt_ccc_t* getCccCfg(void); + bt_gatt_chrc_t* getCharacteristicAttValue(void); + static bt_uuid_t* getCharacteristicAttributeUuid(void); + static bt_uuid_t* getClientCharacteristicConfigUuid(void); + + /** + * @brief Get the characteristic permission + * + * @param none + * + * @return uint8_t The characteristic permission + * + * @note none + */ + uint8_t getPermission(void); + + /** + * @brief For central to discover the peripherial profile + * + * @param[in] attr The discover response + * + * @param[in] params The discover parameter that need to fill + * + * @return none + * + * @note Only for central + */ + void discover(const bt_gatt_attr_t *attr, + bt_gatt_discover_params_t *params); + + /** + * @brief For central to discover the peripherial profile + * + * @param[in] params The discover parameter that need to fill + * + * @return none + * + * @note Only for central + */ + void discover(bt_gatt_discover_params_t *params); + + /** + * @brief Get the subscribe parameter + * + * @param none + * + * @return bt_gatt_subscribe_params_t * the subscribe parameter + * + * @note Only for central + */ + bt_gatt_subscribe_params_t* getSubscribeParams(); + +private: + void _setValue(const uint8_t value[], uint16_t length); + +private: + // Those 2 UUIDs are used for define the characteristic. + static bt_uuid_16_t _gatt_chrc_uuid; // Characteristic UUID + static bt_uuid_16_t _gatt_ccc_uuid; // CCCD UUID + + unsigned short _value_size; + unsigned short _value_length; + unsigned char* _value; + unsigned char* _value_buffer; + bool _value_updated; + + uint16_t _value_handle; + bt_gatt_ccc_cfg_t _ccc_cfg; + _bt_gatt_ccc_t _ccc_value; + bt_gatt_chrc_t _gatt_chrc; + + bt_gatt_attr_t *_attr_chrc_declaration; + bt_gatt_attr_t *_attr_chrc_value; + bt_gatt_attr_t *_attr_cccd; + + // For GATT Client to subscribe the Notification/Indication + bt_gatt_subscribe_params_t _sub_params; + + bool _reading; + bt_gatt_read_params_t _read_params; // GATT read parameter + + typedef LinkNode BLEDescriptorLinkNodeHeader; + typedef LinkNode* BLEDescriptorNodePtr; + typedef LinkNode BLEDescriptorNode; + + BLECharacteristicEventHandler _event_handlers[BLECharacteristicEventLast]; + BLEDescriptorLinkNodeHeader _descriptors_header; + BLEDevice _ble_device; +}; + +#endif // _BLE_CHARACTERISTIC_H_INCLUDED diff --git a/libraries/BLE/src/BLECommon.h b/libraries/BLE/src/BLECommon.h new file mode 100644 index 00000000..ff315dad --- /dev/null +++ b/libraries/BLE/src/BLECommon.h @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2015 Intel Corporation. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef _BLE_COMMON_H_INCLUDED +#define _BLE_COMMON_H_INCLUDED + +#include "Arduino.h" +//#include "ArduinoBLE.h" + +#include "../src/services/ble_service/ble_protocol.h" + + +#include "infra/log.h" + + +#include +#include +#include +#include +//#include + +#define BLE_ADDR_LEN 6 + +#define UUID_SIZE_128 16 +#define UUID_SIZE_16 2 +#define MAX_UUID_SIZE UUID_SIZE_128 + +/* Theoretically we should be able to support attribute lengths up to 512 bytes + * but this involves splitting it across multiple packets. For simplicity, + * we will just limit this to 20 bytes for now, which will fit in a single packet + */ +#define BLE_MAX_ATTR_DATA_LEN 20 +#define BLE_MAX_ATTR_LONGDATA_LEN 512 + +/* Default device name prefix, applied only if user does not provide a name + * If a factory-configured MAC address is defined, the last 2 bytes of the + * address will be appended to the device name */ +#define BLE_DEVICE_NAME_DEFAULT_PREFIX "Arduino101" + +/* Invalid BLE Address type */ +#define BLE_DEVICE_ADDR_INVALID 0xFF + +#if 0 +/** BLE response/event status codes. */ +enum BLE_STATUS { + BLE_STATUS_SUCCESS = 0, /**< General BLE Success code */ + BLE_STATUS_PENDING, /**< Request received and execution started, response pending */ + BLE_STATUS_TIMEOUT, /**< Request timed out */ + BLE_STATUS_NOT_SUPPORTED, /**< Request/feature/parameter not supported */ + BLE_STATUS_NOT_ALLOWED, /**< Request not allowed */ + BLE_STATUS_LINK_TIMEOUT, /**< Link timeout (link loss) */ + BLE_STATUS_NOT_ENABLED, /**< BLE not enabled, @ref ble_enable */ + BLE_STATUS_ERROR, /**< Generic Error */ + BLE_STATUS_ALREADY_REGISTERED, /**< BLE service already registered */ + BLE_STATUS_WRONG_STATE, /**< Wrong state for request */ + BLE_STATUS_ERROR_PARAMETER, /**< Parameter in request is wrong */ + BLE_STATUS_NO_MEMORY, /**< System doesn't have memory */ + BLE_STATUS_GAP_BASE = 0x100, /**< GAP specific error base */ + BLE_STATUS_GATT_BASE = 0x200, /**< GATT specific Error base */ +}; +#endif + +typedef enum +{ + BLE_STATUS_SUCCESS = 0, + BLE_STATUS_FORBIDDEN, /**< The operation is forbidden. Central mode call peripheral API and vice versa */ + BLE_STATUS_PENDING, /**< Request received and execution started, response pending */ + BLE_STATUS_TIMEOUT, /**< Request timed out */ + BLE_STATUS_NOT_SUPPORTED, /**< Request/feature/parameter not supported */ + BLE_STATUS_NOT_FOUND, + BLE_STATUS_NOT_ALLOWED, /**< Request not allowed */ + BLE_STATUS_LINK_TIMEOUT, /**< Link timeout (link loss) */ + BLE_STATUS_NOT_ENABLED, /**< BLE not enabled, @ref ble_enable */ + BLE_STATUS_ERROR, /**< Generic Error */ + BLE_STATUS_ALREADY_REGISTERED, /**< BLE service already registered */ + BLE_STATUS_WRONG_STATE, /**< Wrong state for request */ + BLE_STATUS_ERROR_PARAMETER, /**< Parameter in request is wrong */ + BLE_STATUS_NO_MEMORY, /**< System doesn't have memory */ + BLE_STATUS_NO_SERVICE, /**< System doesn't have service */ +}BLE_STATUS_T; + +typedef uint16_t ble_status_t; /**< Response and event BLE service status type @ref BLE_STATUS */ + +typedef ble_status_t BleStatus; + +#define BLE_MAX_CONN_CFG 2 + +typedef bool (*ble_advertise_handle_cb_t)(uint8_t type, const uint8_t *dataPtr, + uint8_t data_len, const bt_addr_le_t *addrPtr); + + +typedef struct ble_conn_param { + float interval_min; // millisecond 7.5 - 4000ms + float interval_max; // millisecond 7.5 - 4000ms + uint16_t latency; // 0x0000 - 0x01F4 + uint16_t timeout; // millisecond 100 - 32000ms +}ble_conn_param_t; +#ifdef __cplusplus +extern "C" { +#endif + +#include "os/os.h" + +/// Define the structure for app +typedef struct bt_uuid bt_uuid_t; +typedef struct bt_uuid_16 bt_uuid_16_t; +typedef struct bt_uuid_128 bt_uuid_128_t; +typedef struct bt_conn bt_conn_t; +typedef struct bt_gatt_attr bt_gatt_attr_t; +typedef struct bt_gatt_discover_params bt_gatt_discover_params_t; +typedef struct bt_le_scan_param bt_le_scan_param_t; +typedef struct bt_le_conn_param bt_le_conn_param_t; +typedef struct bt_gatt_subscribe_params bt_gatt_subscribe_params_t; +typedef struct bt_gatt_read_params bt_gatt_read_params_t; +typedef struct _bt_gatt_ccc _bt_gatt_ccc_t; +typedef struct bt_gatt_chrc bt_gatt_chrc_t; +typedef struct bt_gatt_ccc_cfg bt_gatt_ccc_cfg_t; +typedef struct bt_data bt_data_t; + +#ifdef __cplusplus +} +#endif +#endif // _BLE_COMMON_H_INCLUDED diff --git a/libraries/BLE/src/BLEDescriptor.cpp b/libraries/BLE/src/BLEDescriptor.cpp new file mode 100644 index 00000000..88aac259 --- /dev/null +++ b/libraries/BLE/src/BLEDescriptor.cpp @@ -0,0 +1,127 @@ +/* + BLE Descriptor API + Copyright (c) 2016 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "BLEDescriptor.h" +#include "BLEUtils.h" + +BLEDescriptor::BLEDescriptor() +{} + +BLEDescriptor::BLEDescriptor(const char* uuid, + const unsigned char value[], + unsigned char valueLength): + _bledev() +{ + bt_uuid_128_t uuid_tmp; + memset(_uuid_cstr, 0, sizeof (_uuid_cstr)); + BLEUtils::uuidString2BT(uuid, (bt_uuid_t *)&uuid_tmp); + BLEUtils::uuidBT2String((const bt_uuid_t *)&uuid_tmp, _uuid_cstr); + + _bledev.setAddress(*BLEUtils::bleGetLoalAddress()); + + _value_size = valueLength > BLE_MAX_ATTR_LONGDATA_LEN ? BLE_MAX_ATTR_LONGDATA_LEN : valueLength; + _value = (unsigned char*)balloc(_value_size, NULL); + memcpy(_value, value, _value_size); +} + +BLEDescriptor::BLEDescriptor(const char* uuid, + const char* value): + BLEDescriptor(uuid, (const unsigned char*)value, strlen(value)) +{} + +BLEDescriptor::~BLEDescriptor() +{ + if (_value) + { + bfree(_value); + _value = NULL; + } +} + +const char* BLEDescriptor::uuid() const +{ + return _uuid_cstr; +} + +const byte* BLEDescriptor::value() const +{ + // TODO: Not support now + return _value; +} + +int BLEDescriptor::valueLength() const +{ + // TODO: Not support now + return _value_size; +} + +byte BLEDescriptor::operator[] (int offset) const +{ + // TODO: Not support now + return 0; +} + +BLEDescriptor::operator bool() const +{ + // TODO: Not support now + return false; +} + +bool BLEDescriptor::writeValue(const byte value[], int length) +{ + // TODO: Not support now + return false; +} + +bool BLEDescriptor::writeValue(const byte value[], int length, int offset) +{ + // TODO: Not support now + return false; +} + +bool BLEDescriptor::writeValue(const char* value) +{ + // TODO: Not support now + return false; +} + +// GATT client Write the value of the descriptor +bool BLEDescriptor::write(const byte value[], int length) +{ + // TODO: Not support now + return false; +} + +bool BLEDescriptor::write(const byte value[], int length, int offset) +{ + // TODO: Not support now + return false; +} + +bool BLEDescriptor::write(const char* value) +{ + // TODO: Not support now + return false; +} + +bool BLEDescriptor::read() +{ + // TODO: Not support now + return false; +} + diff --git a/libraries/BLE/src/BLEDescriptor.h b/libraries/BLE/src/BLEDescriptor.h new file mode 100644 index 00000000..8c0391e3 --- /dev/null +++ b/libraries/BLE/src/BLEDescriptor.h @@ -0,0 +1,98 @@ +/* + BLE Descriptor API + Copyright (c) 2016 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef ARDUINO_BLE_DESCRIPTOR_H +#define ARDUINO_BLE_DESCRIPTOR_H + +#include "ArduinoBLE.h" + +#include "BLEDevice.h" + +class BLEDescriptor +{ + public: + BLEDescriptor(); + BLEDescriptor(const char* uuid, const unsigned char value[], unsigned char valueLength); // create a descriptor the specified uuid and value + BLEDescriptor(const char* uuid, const char* value); // create a descriptor the specified uuid and string value + + virtual ~BLEDescriptor(); + + const char* uuid() const; + + virtual const byte* value() const; // returns the value buffer + virtual int valueLength() const; // returns the current length of the value + virtual byte operator[] (int offset) const; // returns a byte of the value at the specified offset + + virtual operator bool() const; // is the descriptor valid (discovered from peripheral) + + /** + * @brief Write the value of the descriptor + * + * @param value The value buffer that want to write to descriptor + * + * @param length The value buffer's length + * + * @return bool true - Success, false - Failed + * + * @note none + */ + virtual bool writeValue(const byte value[], int length); + + /** + * @brief Write the value of the descriptor + * + * @param value The value buffer that want to write to descriptor + * + * @param length The value buffer's length + * + * @param offset The offset in the descriptor's data + * + * @return bool true - Success, false - Failed + * + * @note none + */ + bool writeValue(const byte value[], int length, int offset); + + /** + * @brief Write the value of the descriptor + * + * @param value The value string that want to write to descriptor + * + * @return bool true - Success, false - Failed + * + * @note none + */ + bool writeValue(const char* value); + + // GATT client Write the value of the descriptor + virtual bool write(const byte value[], int length); + bool write(const byte value[], int length, int offset); + bool write(const char* value); + bool read(); +private: + char _uuid_cstr[37]; // The characteristic UUID + BLEDevice _bledev; + + unsigned char _properties; // The characteristic property + + unsigned char _value_size; // The value size + unsigned char* _value; // The value. Will delete after create the _internal +}; + +#endif diff --git a/libraries/BLE/src/BLEDescriptorImp.cpp b/libraries/BLE/src/BLEDescriptorImp.cpp new file mode 100644 index 00000000..9cdef95c --- /dev/null +++ b/libraries/BLE/src/BLEDescriptorImp.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2015 Intel Corporation. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "BLEAttribute.h" +#include "BLEDescriptorImp.h" + +#include "internal/ble_client.h" + +#include "BLECallbacks.h" + +BLEDescriptorImp::BLEDescriptorImp(BLEDevice& bledevice, BLEDescriptor &descriptor): + BLEAttribute(descriptor.uuid(), BLETypeDescriptor) +{ + _value_length = descriptor.valueLength(); + _value = (unsigned char*)balloc(_value_length, NULL); + + memcpy(_value, descriptor.value(), _value_length); +} + +BLEDescriptorImp::~BLEDescriptorImp() { + if (_value) { + bfree(_value); + _value = NULL; + } +} + +const unsigned char* +BLEDescriptorImp::value() const +{ + return _value; +} + +unsigned short +BLEDescriptorImp::valueLength() const +{ + return _value_length; +} + +unsigned char +BLEDescriptorImp::operator[] (int offset) const +{ + return _value[offset]; +} + +int BLEDescriptorImp::updateProfile(bt_gatt_attr_t *attr_start, int& index) +{ + bt_gatt_attr_t *start = attr_start; + start->uuid = (struct bt_uuid *)bt_uuid(); + start->perm = BT_GATT_PERM_READ; + start->read = profile_read_process; + start->user_data = (void*)((BLEAttribute*)this); + + pr_debug(LOG_MODULE_BLE, "Descriptor-%p", start); + index++; + return 1; +} + + diff --git a/libraries/BLE/src/BLEDescriptorImp.h b/libraries/BLE/src/BLEDescriptorImp.h new file mode 100644 index 00000000..5ea27caf --- /dev/null +++ b/libraries/BLE/src/BLEDescriptorImp.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2015 Intel Corporation. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef _BLE_DESCRIPTORIMP_H_INCLUDED +#define _BLE_DESCRIPTORIMP_H_INCLUDED + +#include "ArduinoBLE.h" + +/** + * BLE GATT Descriptor class + */ +class BLEDescriptorImp: public BLEAttribute{ +public: + /** + * Constructor for BLE Descriptor + * + * @param[in] uuid 16-bit UUID (in string form) defined by BLE standard + * @param[in] value Value of descriptor, as a byte array. Data is stored in internal copy. + * @param[in] valueLength Data length required for descriptor value (<= BLE_MAX_ATTR_DATA_LEN) + */ + BLEDescriptorImp(BLEDevice& bledevice, BLEDescriptor &descriptor); + + virtual ~BLEDescriptorImp(); + + /** + * Get data pointer to the value of the Descriptor + * + * @return const unsigned char* pointer to the value of the Descriptor + */ + const unsigned char* value(void) const; + + /** + * Get the length of the value of the Descriptor + * + * @return unsigned short size of Descriptor value in bytes + */ + unsigned short valueLength(void) const; + + int updateProfile(bt_gatt_attr_t *attr_start, int& index); + + unsigned char operator[] (int offset) const; + +protected: + + +private: + unsigned short _value_length; + unsigned char* _value; + + bt_uuid_128 _descriptor_uuid; +}; + +#endif // _BLE_DESCRIPTOR_H_INCLUDED diff --git a/libraries/BLE/src/BLEDevice.cpp b/libraries/BLE/src/BLEDevice.cpp new file mode 100644 index 00000000..ece51365 --- /dev/null +++ b/libraries/BLE/src/BLEDevice.cpp @@ -0,0 +1,433 @@ +/* + BLE Device API + Copyright (c) 2016 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "ArduinoBLE.h" +#include "BLEDevice.h" + +#include "BLEUtils.h" + +#include "BLEProfileManager.h" +#include "BLEDeviceManager.h" +#include "BLECharacteristicImp.h" + +BLEDevice::BLEDevice() +{ + memset(&_bt_addr, 0, sizeof(_bt_addr)); + _conn_param.interval_max = BT_GAP_INIT_CONN_INT_MAX; + _conn_param.interval_min = BT_GAP_INIT_CONN_INT_MIN; + _conn_param.latency = 0; + _conn_param.timeout = 400; +} + +/* +BLEDevice::BLEDevice(String bleaddress) +{ + BLEUtils::macAddressString2BT(bleaddress.c_str(), _bt_addr); +} + +BLEDevice::BLEDevice(const char* bleaddress) +{ + BLEUtils::macAddressString2BT(bleaddress, _bt_addr); +} + +*/ + +BLEDevice::BLEDevice(const bt_addr_le_t* bleaddress): + BLEDevice() +{ + memcpy(&_bt_addr, bleaddress, sizeof(bt_addr_le_t)); +} + +BLEDevice::BLEDevice(const BLEDevice* bleaddress) +{ + memcpy(&_bt_addr, bleaddress->bt_le_address(), sizeof(bt_addr_le_t)); + memcpy(&_conn_param, bleaddress->bt_conn_param(), sizeof (ble_conn_param_t)); +} + +BLEDevice::~BLEDevice() +{ + //pr_debug(LOG_MODULE_BLE, "%s-%d", __FUNCTION__, __LINE__); +} + +bool BLEDevice::begin() +{ + return BLEDeviceManager::instance()->begin(this); +} + +void BLEDevice::poll() +{} + +void BLEDevice::end() +{} + +bool BLEDevice::connected() +{ + return BLEDeviceManager::instance()->connected(this); +} + +bool BLEDevice::disconnect() +{ + return BLEDeviceManager::instance()->disconnect(this); +} + +String BLEDevice::address() const +{ + return BLEUtils::macAddressBT2String(_bt_addr); +} + +void BLEDevice::setAddress(const bt_addr_le_t& addr) +{ + memcpy(&_bt_addr, &addr, sizeof(_bt_addr)); +} + +void BLEDevice::setAdvertisedServiceUuid(const char* advertisedServiceUuid) +{ + BLEDeviceManager::instance()->setAdvertisedServiceUuid(advertisedServiceUuid); +} + +void BLEDevice::setAdvertisedService(const BLEService& service) +{} + +void BLEDevice::setServiceSolicitationUuid(const char* serviceSolicitationUuid) +{} + +void BLEDevice::setManufacturerData(const unsigned char manufacturerData[], + unsigned char manufacturerDataLength) +{} + +void BLEDevice::setLocalName(const char *localName) +{ + BLEDeviceManager::instance()->setLocalName(localName); +} + +void BLEDevice::setAdvertisingInterval(float advertisingInterval) +{ + BLEDeviceManager::instance()->setAdvertisingInterval(advertisingInterval); +} + +void BLEDevice::setConnectionInterval(float minimumConnectionInterval, + float maximumConnectionInterval, + uint16_t latency, + uint16_t timeout) +{} + +void BLEDevice::setConnectionInterval(float minimumConnectionInterval, + float maximumConnectionInterval) +{} + +bool BLEDevice::setTxPower(int txPower) +{ + return BLEDeviceManager::instance()->setTxPower(txPower); +} + +void BLEDevice::setConnectable(bool connectable) +{ + BLEDeviceManager::instance()->setConnectable(connectable); +} + +void BLEDevice::setDeviceName(const char* deviceName) +{ + BLEDeviceManager::instance()->setDeviceName(deviceName); +} + +void BLEDevice::setAppearance(unsigned short appearance) +{ + BLEDeviceManager::instance()->setAppearance(appearance); +} + +BLE_STATUS_T BLEDevice::addService(BLEService& attribute) +{ + return BLEProfileManager::instance()->addService(*this, attribute); +} + +BLE_STATUS_T BLEDevice::startAdvertising() +{ + preCheckProfile(); + return BLEDeviceManager::instance()->startAdvertising(); +} + +void BLEDevice::stopAdvertising() +{ + BLEDeviceManager::instance()->stopAdvertising(); +} + +BLEDevice BLEDevice::central() +{ + return BLEDeviceManager::instance()->central(); +} + +BLEDevice BLEDevice::peripheral() +{ + // TODO + BLEDevice temp; + return temp; +} + +void BLEDevice::linkLost() +{} + +BLEDevice::operator bool() const +{ + return BLEUtils::macAddressValid(_bt_addr); +} + +BLEDevice& BLEDevice::operator=(const BLEDevice& device) +{ + if (*this != device) + { + memcpy(&(this->_bt_addr), &(device._bt_addr), sizeof (bt_addr_le_t)); + } + return *this; +} + +bool BLEDevice::operator==(const BLEDevice& device) const +{ + return (memcmp(this->_bt_addr.val, device._bt_addr.val, 6) == 0); +} + +bool BLEDevice::operator!=(const BLEDevice& device) const +{ + return (memcmp(this->_bt_addr.val, device._bt_addr.val, 6) != 0); +} + + +void BLEDevice::startScanning(String name) +{ + preCheckProfile(); + BLEDeviceManager::instance()->setAdvertiseCritical(name); + BLEDeviceManager::instance()->startScanning(); +} + +void BLEDevice::startScanningWithDuplicates() +{} + +void BLEDevice::stopScanning() +{ + BLEDeviceManager::instance()->stopScanning(); +} + +//void setAcceptAdvertiseLocalName(String name); +//void setAcceptAdvertiseLocalName(BLEService& service); +//void setAcceptAdvertiseCallback(String name); + +BLEDevice BLEDevice::available() +{ + return BLEDeviceManager::instance()->available(); +} + +bool BLEDevice::hasLocalName() const +{ + return BLEDeviceManager::instance()->hasLocalName(); +} + +bool BLEDevice::hasAdvertisedServiceUuid() const +{ + return BLEDeviceManager::instance()->hasAdvertisedServiceUuid(); +} + +bool BLEDevice::hasAdvertisedServiceUuid(int index) const +{ + return BLEDeviceManager::instance()->hasAdvertisedServiceUuid(index); +} + +int BLEDevice::advertisedServiceUuidCount() const +{ + return BLEDeviceManager::instance()->advertisedServiceUuidCount(); +} + +String BLEDevice::localName() const +{ + return BLEDeviceManager::instance()->localName(); +} + +String BLEDevice::advertisedServiceUuid() const +{ + return BLEDeviceManager::instance()->advertisedServiceUuid(); +} + +String BLEDevice::advertisedServiceUuid(int index) const +{ + return BLEDeviceManager::instance()->advertisedServiceUuid(index); +} + +int BLEDevice::rssi() const +{ + return BLEDeviceManager::instance()->rssi(); +} + +bool BLEDevice::connect() +{ + return BLEDeviceManager::instance()->connect(*this); +} + +bool BLEDevice::discoverAttributes() +{ + return BLEProfileManager::instance()->discoverAttributes(this); +} + +String BLEDevice::deviceName() +{ + return BLEDeviceManager::instance()->deviceName(); +} + +int BLEDevice::appearance() +{ + return BLEDeviceManager::instance()->appearance(); +} + +// For GATT +int BLEDevice::serviceCount() const +{ + return BLEProfileManager::instance()->serviceCount(*this); +} + +bool BLEDevice::hasService(const char* uuid) const +{ + BLEServiceImp* serviceImp = BLEProfileManager::instance()->service(*this, uuid); + return (NULL != serviceImp); +} + +bool BLEDevice::hasService(const char* uuid, int index) const +{ + BLEServiceImp* serviceImp = BLEProfileManager::instance()->service(*this, index); + return serviceImp->compareUuid(uuid); +} + +BLEService BLEDevice::service(int index) const +{ + BLEServiceImp* serviceImp = BLEProfileManager::instance()->service(*this, index); + if (serviceImp != NULL) + { + BLEService temp(serviceImp, this); + return temp; + } + BLEService temp; + return temp; +} + +BLEService BLEDevice::service(const char * uuid) const +{ + BLEServiceImp* serviceImp = BLEProfileManager::instance()->service(*this, uuid); + if (serviceImp != NULL) + { + BLEService temp(serviceImp, this); + return temp; + } + BLEService temp; + return temp; +} + +BLEService BLEDevice::service(const char * uuid, int index) const +{ + BLEServiceImp* serviceImp = BLEProfileManager::instance()->service(*this, index); + if (serviceImp != NULL && serviceImp->compareUuid(uuid)) + { + BLEService temp(serviceImp, this); + return temp; + } + BLEService temp; + return temp; +} + +int BLEDevice::characteristicCount() const +{ + return BLEProfileManager::instance()->characteristicCount(*this); +} + +bool BLEDevice::hasCharacteristic(const char* uuid) const +{ + BLECharacteristicImp* characteristicImp = BLEProfileManager::instance()->characteristic(*this, uuid); + return (NULL != characteristicImp); +} + +bool BLEDevice::hasCharacteristic(const char* uuid, int index) const +{ + BLECharacteristicImp* characteristicImp = BLEProfileManager::instance()->characteristic(*this, uuid, index); + return (NULL != characteristicImp); +} + +BLECharacteristic BLEDevice::characteristic(int index) const +{ + BLECharacteristicImp* characteristicImp = BLEProfileManager::instance()->characteristic(*this, index); + + if (NULL == characteristicImp) + { + BLECharacteristic temp; + return temp; + } + BLECharacteristic temp(characteristicImp, this); + return temp; +} + +BLECharacteristic BLEDevice::characteristic(const char * uuid) const +{ + BLECharacteristicImp* characteristicImp = BLEProfileManager::instance()->characteristic(*this, uuid); + + if (NULL == characteristicImp) + { + BLECharacteristic temp; + return temp; + } + BLECharacteristic temp(characteristicImp, this); + return temp; +} + +BLECharacteristic BLEDevice::characteristic(const char * uuid, int index) const +{ + BLECharacteristicImp* characteristicImp = BLEProfileManager::instance()->characteristic(*this, index); + if (false == characteristicImp->compareUuid(uuid)) + { + // UUID not matching + characteristicImp = NULL; + } + + if (NULL == characteristicImp) + { + BLECharacteristic temp; + return temp; + } + BLECharacteristic temp(characteristicImp, this); + return temp; +} + +// event handler +void BLEDevice::setEventHandler(BLEDeviceEvent event, + BLEDeviceEventHandler eventHandler) +{ + // TODO: +} + +const bt_addr_le_t* BLEDevice::bt_le_address() const +{ + return &_bt_addr; +} +const bt_le_conn_param* BLEDevice::bt_conn_param() const +{ + return &_conn_param; +} + +void BLEDevice::preCheckProfile() +{ + if (false == BLEProfileManager::instance()->hasRegisterProfile() && + BLEProfileManager::instance()->serviceCount(*this) > 0) + { + BLEProfileManager::instance()->registerProfile(*this); + delay(8); + } +} + diff --git a/libraries/BLE/src/BLEDevice.h b/libraries/BLE/src/BLEDevice.h new file mode 100644 index 00000000..8beebf64 --- /dev/null +++ b/libraries/BLE/src/BLEDevice.h @@ -0,0 +1,414 @@ +/* + BLE Device API + Copyright (c) 2016 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef ARDUINO_BLE_DEVICE_H +#define ARDUINO_BLE_DEVICE_H + +#include + +//#include "BLEService.h" +//#include "BLECharacteristic.h" + +//class BLEDevice; + +enum BLEDeviceEvent { + BLEDiscovered = 0, // Discover profile completed + BLEConnected = 1, // BLE device connected + BLEDisconnected = 2, // BLE device disconnected + BLEConParamUpdate = 3, // Update the connection parameter + // Connection update request in central + // Connection parameter updated in peripheral +}; + +typedef void (*BLEDeviceEventHandler)(BLEDevice& device); + +class BLEDevice +{ + public: + /** + * @brief The BLE device constructure + * + * @param bleaddress BLE device address + * + * @return none + * + * @note none + */ + BLEDevice(); + //BLEDevice(String bleaddress); + //BLEDevice(const char* bleaddress); + BLEDevice(const bt_addr_le_t* bleaddress); + BLEDevice(const BLEDevice* bleaddress); + + virtual ~BLEDevice(); + + + /** + * @brief Initiliaze the BLE hardware + * + * @return bool indicating success or error + * + * @note This method are for real BLE device. + * Not for peer BLE device. + */ + bool begin(); + + /** + * @brief Poll for events + * + * @param none + * + * @return none + * + * @note This method are for real BLE device. + * Not for peer BLE device. + */ + void poll(); // Do we need add the return value or + // input parameter to get the events? + // Events may inlcue: + // GAP : Connected, Disconnected, Update connetion parameter + // GATT: Discovered + + /** + * @brief Deinitiliaze the BLE hardware + * + * @param none + * + * @return none + * + * @note This method are for real BLE device. + * Not for peer BLE device. + */ + void end(); + + /** + * @brief Is the device connected with another BLE device. + * + * @param none + * + * @return none + * + * @note none + */ + bool connected(); + + /** + * @brief Disconnect the connected device/s. + * + * @param none + * + * @return none + * + * @note The BLE may connected multiple devices. + * This call will disconnect all conected devices. + */ + bool disconnect(); + + + /** + * @brief Get the BLE address of the BLE in string format + * + * @return const char* address of the BLE in string format + */ + String address() const; + + /** + * @brief Set the service UUID that the BLE Peripheral Device advertises + * + * @param[in] advertisedServiceUuid 16-bit or 128-bit UUID to advertis + * (in string form) + * + * @note This method must be called before the begin method + * Only for peripheral mode. + */ + void setAdvertisedServiceUuid(const char* advertisedServiceUuid); + + /** + * @brief Set the service that the BLE Peripheral Device will advertise this UUID + * + * @param[in] service The service the will in advertise data. + * + * @note This method must be called before the begin method + * Only for peripheral mode. + */ + void setAdvertisedService(const BLEService& service); + + /** + * @brief Set the service UUID that is solicited in the BLE Peripheral + * Device advertises + * + * @param[in] advertisedServiceUuid 16-bit or 128-bit UUID to advertis + * (in string form) + * + * @note This method must be called before the begin method + * Only for peripheral mode. + */ + void setServiceSolicitationUuid(const char* serviceSolicitationUuid); + + /** + * @brief Set the manufacturer data in the BLE Peripheral Device advertises + * + * @param[in] manufacturerData The data about manufacturer will + * be set in advertisement + * @param[in] manufacturerDataLength The length of the manufacturer data + * + * @note This method must be called before the begin method + * Only for peripheral mode. + */ + void setManufacturerData(const unsigned char manufacturerData[], + unsigned char manufacturerDataLength); + + /** + * Set the local name that the BLE Peripheral Device advertises + * + * @param[in] localName local name to advertise + * + * @note This method must be called before the begin method + */ + void setLocalName(const char *localName); + + /** + * @brief Set advertising interval + * + * @param[in] advertisingInterval Advertising Interval in ms + * + * @return none + * + * @note none + */ + void setAdvertisingInterval(float advertisingInterval); + + /** + * @brief Set the connection parameters and send connection + * update request in both BLE peripheral and central + * + * @param[in] intervalmin Minimum Connection Interval (ms) + * + * @param[in] intervalmax Maximum Connection Interval (ms) + * + * @param[in] latency Connection Latency + * + * @param[in] timeout Supervision Timeout (ms) + * + * @return none + * + * @note none + */ + void setConnectionInterval(float minimumConnectionInterval, + float maximumConnectionInterval, + uint16_t latency, + uint16_t timeout); + + /** + * @brief Set the min and max connection interval and send connection + * update request in both BLE peripheral and central + * + * @param[in] intervalmin Minimum Connection Interval (ms) + * + * @param[in] intervalmax Maximum Connection Interval (ms) + * + * @return none + * + * @note none + */ + void setConnectionInterval(float minimumConnectionInterval, + float maximumConnectionInterval); + + /** + * @brief Set TX power of the radio in dBM + * + * @param[in] tx_power The antenna TX power + * + * @return boolean_t true if established connection, otherwise false + */ + bool setTxPower(int txPower); + + /** + * @brief Set advertising type as connectable/non-connectable + * + * @param[in] connectable true - The device connectable + * false - The device non-connectable + * + * @return none + * + * @note Only for peripheral mode. + * Default value is connectable + */ + void setConnectable(bool connectable); + + /** + * @brief Set the value of the device name characteristic + * + * @param[in] device User-defined name string for this device. Truncated if + * more than maximum allowed string length (20 bytes). + * + * @note This method must be called before the begin method + * If device name is not set, a default name will be used + */ + void setDeviceName(const char* deviceName); + + /** + * @brief Set the appearance type for the BLE Peripheral Device + * + * See https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.gap.appearance.xml + * for available options. + * + * @param[in] appearance Appearance category identifier as defined by BLE Standard + * + * @return BleStatus indicating success or error + * + * @note This method must be called before the begin method + */ + void setAppearance(unsigned short appearance); + + /** + * @brief Add a Service to the BLE Peripheral Device + * + * @param[in] attribute The service that will add to Peripheral + * + * @return BLE_STATUS_T Indicating success or error type + * + * @note This method must be called before the begin method + */ + BLE_STATUS_T addService(BLEService& attribute); + + /** + * @brief Construct the ADV data and start send advertisement + * + * @param none + * + * @return BLE_STATUS_T 0 - Success. Others - error code + * + * @note none + */ + BLE_STATUS_T startAdvertising(); + + /** + * @brief Stop send advertisement + * + * @param none + * + * @return none + * + * @note none + */ + void stopAdvertising(); + + /** + * @brief Get currently connected central + * + * @return BLEDevice Connected central device + * + * @note Peripheral mode only + */ + BLEDevice central(); + + /** + * @brief Get currently connected peripheral + * + * @param none + * + * @return none + * + * @note Central mode only. How to distinguish the peripheral? + */ + BLEDevice peripheral(); + + /** + * @brief Release the resources when link lost + * + * @param none + * + * @return none + * + * @note Peer devices only. Do nothing if local BLE device called. + */ + void linkLost(); + + operator bool() const; + bool operator==(const BLEDevice& device) const; + bool operator!=(const BLEDevice& device) const; + BLEDevice& operator=(const BLEDevice& device); + // central mode + void startScanning(String name); // start scanning for peripherals + void startScanningWithDuplicates(); // start scanning for peripherals, and report all duplicates + void stopScanning(); // stop scanning for peripherals + + void setAcceptAdvertiseLocalName(String name); + void setAcceptAdvertiseLocalName(BLEService& service); + void setAcceptAdvertiseCallback(String name); + + BLEDevice available(); // retrieve a discovered peripheral + + bool hasLocalName() const; // does the peripheral advertise a local name + bool hasAdvertisedServiceUuid() const; // does the peripheral advertise a service + bool hasAdvertisedServiceUuid(int index) const; // does the peripheral advertise a service n + int advertisedServiceUuidCount() const; // number of services the peripheral is advertising + + String localName() const; // returns the advertised local name as a String + String advertisedServiceUuid() const; // returns the advertised service as a UUID String + String advertisedServiceUuid(int index) const; // returns the nth advertised service as a UUID String + + int rssi() const; // returns the RSSI of the peripheral at discovery + + bool connect(); // connect to the peripheral + bool discoverAttributes(); // discover the peripheral's attributes + + String deviceName(); // read the device name attribute of the peripheral, and return String value + int appearance(); // read the appearance attribute of the peripheral and return value as int + + // For GATT + int serviceCount() const; // returns the number of services the peripheral has + bool hasService(const char* uuid) const; // does the peripheral have a service with the specified UUID + bool hasService(const char* uuid, int index) const; // does the peripheral have an nth service with the specified UUID + BLEService service(int index) const; // return the nth service of the peripheral + BLEService service(const char * uuid) const; // return the service with the specified UUID + BLEService service(const char * uuid, int index) const; // return the nth service with the specified UUID + + int characteristicCount() const; // returns the number of characteristics the peripheral has + bool hasCharacteristic(const char* uuid) const; // does the peripheral have a characteristic with the specified UUID + bool hasCharacteristic(const char* uuid, int index) const; // does the peripheral have an nth characteristic with the specified UUID + BLECharacteristic characteristic(int index) const; // return the nth characteristic of the peripheral + BLECharacteristic characteristic(const char * uuid) const; // return the characteristic with the specified UUID + BLECharacteristic characteristic(const char * uuid, int index) const; // return the nth characteristic with the specified UUID + + // event handler + void setEventHandler(BLEDeviceEvent event, BLEDeviceEventHandler eventHandler); // set an event handler (callback) + +protected: + friend class BLECharacteristicImp; + friend class BLEServiceImp; + friend class BLEDeviceManager; + friend class BLEProfileManager; + friend class BLECharacteristic; + friend class BLEDescriptor; + friend class BLEService; + const bt_addr_le_t* bt_le_address() const; + const bt_le_conn_param* bt_conn_param() const; + void setAddress(const bt_addr_le_t& addr); +private: + void preCheckProfile(); + +private: + bt_addr_le_t _bt_addr; + + bt_le_conn_param _conn_param; +}; + +#endif diff --git a/libraries/BLE/src/BLEDeviceManager.cpp b/libraries/BLE/src/BLEDeviceManager.cpp new file mode 100644 index 00000000..72db8c45 --- /dev/null +++ b/libraries/BLE/src/BLEDeviceManager.cpp @@ -0,0 +1,803 @@ +/* + BLE Device API + Copyright (c) 2016 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "ArduinoBLE.h" +#include "BLEDeviceManager.h" +#include "BLEProfileManager.h" + +#include "internal/ble_client.h" + +#include +#include "../src/services/ble/conn_internal.h" + +#include "BLEUtils.h" +#include "BLECallbacks.h" + +BLEDeviceManager* BLEDeviceManager::_instance; + +BLEDeviceManager::BLEDeviceManager(): + _min_conn_interval(0), + _max_conn_interval(0), + _adv_critical_local_name(""), + _has_service_uuid(false), + _appearance(0), + _adv_type(0), + _adv_data_idx(0), + _local_name(""), + _state(BLE_PERIPH_STATE_NOT_READY), + _local_ble(NULL) +{ + memset(&_local_bda, 0, sizeof(_local_bda)); + + memset(&_service_uuid, 0, sizeof(_service_uuid)); + memset(&_service_solicit_uuid, 0, sizeof(_service_solicit_uuid)); + memset(_adv_data, 0, sizeof(_adv_data)); + + memset(&_peer_central, 0, sizeof (bt_addr_le_t)); + + ble_client_get_factory_config(&_local_bda, _device_name); + + _adv_param.type = BT_LE_ADV_IND; + _adv_param.addr_type = _local_bda.type; + _adv_param.interval_min = 0xA0; + _adv_param.interval_max = 0xF0; + + _scan_param.type = BT_HCI_LE_SCAN_ACTIVE; + _scan_param.filter_dup = BT_HCI_LE_SCAN_FILTER_DUP_ENABLE; + _scan_param.interval = BT_GAP_SCAN_FAST_INTERVAL; + _scan_param.window = BT_GAP_SCAN_FAST_WINDOW; + + memset(_peer_adv_buffer, 0, sizeof(_peer_adv_buffer)); + memset(_peer_adv_mill, 0, sizeof(_peer_adv_mill)); + memset(&_adv_accept_critical, 0, sizeof(_adv_accept_critical)); + + memset(_peer_peripheral, 0, sizeof(_peer_peripheral)); +} + +BLEDeviceManager::~BLEDeviceManager() +{ + +} + +bool BLEDeviceManager::begin(BLEDevice *device) +{ + if (NULL == _local_ble && false == *device) + { + _local_ble = device; + _local_ble->setAddress(_local_bda); + + // Set device name + setDeviceName(); + _state = BLE_PERIPH_STATE_READY; + delay(4); + // TODO: Olny allow call one time + ble_client_init (bleConnectEventHandler, this, + bleDisconnectEventHandler, this, + bleParamUpdatedEventHandler, this); + return true; + } + else + { + return false; + } +} + +void BLEDeviceManager::poll() +{} + +void BLEDeviceManager::end() +{} + +bool BLEDeviceManager::connected(BLEDevice *device) +{ + bt_conn_t* conn = bt_conn_lookup_addr_le(device->bt_le_address()); + //pr_debug(LOG_MODULE_BLE, "%s-%d: add-%s", __FUNCTION__, __LINE__, device->address().c_str()); + if (NULL != conn) + { + //pr_debug(LOG_MODULE_BLE, "%s-%d: state-%d", __FUNCTION__, __LINE__,conn->state); + if (conn->state == BT_CONN_CONNECTED) + { + return true; + } + bt_conn_unref(conn); + } + return false; +} + +bool BLEDeviceManager::disconnect(BLEDevice *device) +{ + int err = 0; + bt_conn_t* conn = bt_conn_lookup_addr_le(device->bt_le_address()); + if (NULL == conn) + { + return false; + } + + err = bt_conn_disconnect (conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + bt_conn_unref(conn); + return (err == 0); +} + +void BLEDeviceManager::setAdvertisedServiceUuid(const char* advertisedServiceUuid) +{ + _has_service_uuid = true; + BLEUtils::uuidString2BT(advertisedServiceUuid, (bt_uuid_t *)&_service_uuid); +} + +void BLEDeviceManager::setAdvertisedService(const BLEService& service) +{ + //TODO: Different with setAdvertisedServiceUuid? +} + +void BLEDeviceManager::setServiceSolicitationUuid(const char* serviceSolicitationUuid) +{ + BLEUtils::uuidString2BT(serviceSolicitationUuid, (bt_uuid_t *)&_service_solicit_uuid); +} + +void BLEDeviceManager::setManufacturerData(const unsigned char manufacturerData[], + unsigned char manufacturerDataLength) +{ + // TODO: Add late +} + +void BLEDeviceManager::setLocalName(const char *localName) +{ + _local_name = localName; +} + +void BLEDeviceManager::setAdvertisingInterval(float advertisingInterval) +{ + uint16_t interval = (uint16_t) MSEC_TO_UNITS(advertisingInterval, UNIT_0_625_MS); + + _adv_param.interval_min = interval; + _adv_param.interval_max = interval; +} + +void BLEDeviceManager::setConnectionInterval(float minimumConnectionInterval, + float maximumConnectionInterval, + uint16_t latency, + uint16_t timeout) +{ +} + +void BLEDeviceManager::setConnectionInterval(float minimumConnectionInterval, + float maximumConnectionInterval) +{ + +} + +bool BLEDeviceManager::setTxPower(int txPower) +{ + ble_gap_set_tx_power(txPower); + return true; +} + +void BLEDeviceManager::setConnectable(bool connectable) +{ + uint8_t type = BT_LE_ADV_IND; + if (connectable == false) + { + type = BT_LE_ADV_NONCONN_IND; + } + _adv_param.type = type; +} + +void BLEDeviceManager::setDeviceName(const char* deviceName) +{ + memset(_device_name, 0, sizeof(_device_name)); + if (deviceName && deviceName[0]) + { + int len = strlen(deviceName); + if (len > BLE_MAX_DEVICE_NAME) + len = BLE_MAX_DEVICE_NAME; + memcpy(_device_name, deviceName, len); + setDeviceName(); + } +} + +void +BLEDeviceManager::setDeviceName() +{ + int len = strlen(_device_name); + bt_le_set_device_name(_device_name, len); +} + +void BLEDeviceManager::setAppearance(unsigned short appearance) +{ + _appearance = appearance; +} + +BLE_STATUS_T +BLEDeviceManager::_advDataInit(void) +{ + uint8_t lengthTotal = 2; // Flags data length + _adv_data_idx = 0; + + /* Add flags */ + _adv_type = (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR); + _adv_data[_adv_data_idx].type = BT_DATA_FLAGS; + _adv_data[_adv_data_idx].data = &_adv_type; + _adv_data[_adv_data_idx].data_len = 1; + _adv_data_idx++; + + if (_has_service_uuid) + { + uint8_t type; + uint8_t length; + uint8_t *data = NULL; + + pr_info(LOG_MODULE_BLE, "ADV Type-%d", _service_uuid.uuid.type); + if (BT_UUID_TYPE_16 == _service_uuid.uuid.type) + { + //UINT16_TO_LESTREAM(adv_tmp, uuid.uuid16); + data = (uint8_t *)&(((bt_uuid_16_t *)&_service_uuid)->val); + length = UUID_SIZE_16; + type = BT_DATA_UUID16_ALL; + } + else if (BT_UUID_TYPE_128 == _service_uuid.uuid.type) + { + data = _service_uuid.val; + length = UUID_SIZE_128; + type = BT_DATA_UUID128_ALL; + } + if (NULL != data) + { + _adv_data[_adv_data_idx].type = type; + _adv_data[_adv_data_idx].data = data; + _adv_data[_adv_data_idx].data_len = length; + _adv_data_idx++; + lengthTotal += length; + + pr_info(LOG_MODULE_BLE, "Service UUID Len -%d", length); + } + } + + + if (_local_name.length() > 0) + { + /* Add device name (truncated if too long) */ + _adv_data[_adv_data_idx].type = BT_DATA_NAME_COMPLETE; + _adv_data[_adv_data_idx].data = (const uint8_t*)_local_name.c_str(); + _adv_data[_adv_data_idx].data_len = _local_name.length(); + _adv_data_idx++; + + lengthTotal += _local_name.length(); + pr_info(LOG_MODULE_BLE, "Local Name -%s", _local_name.c_str()); + pr_info(LOG_MODULE_BLE, "Local Name Len -%d", _local_name.length()); + } + +#if 0 + + if (_service_data) + { + /* Add Service Data (if it will fit) */ + + /* A 128-bit Service Data UUID won't fit in an Advertising packet */ + if (BT_UUID_TYPE_16 != _service_data_uuid->type) + { + /* We support service data only for 16-bit service UUID */ + return BLE_STATUS_NOT_SUPPORTED; + } + + uint8_t block_len = sizeof(uint16_t) + _service_data_length; + if (1 + block_len > BLE_MAX_ADV_SIZE) + { + // Service data block is too large. + return BLE_STATUS_ERROR_PARAMETER; + } + + _adv_data[_adv_data_idx].type = BT_DATA_SVC_DATA16; + _adv_data[_adv_data_idx].data = _service_data_buf; + _adv_data[_adv_data_idx].data_len = block_len; + _adv_data_idx++; + + uint8_t *adv_tmp = _service_data_buf; + + UINT16_TO_LESTREAM(adv_tmp, (((bt_uuid_16_t *)_service_data_uuid)->val)); + memcpy(adv_tmp, _service_data, _service_data_length); + + lengthTotal += block_len; + pr_info(LOG_MODULE_BLE, "SVC Len -%d", block_len); + } +#endif + if (lengthTotal > BLE_MAX_ADV_SIZE) + { + pr_error(LOG_MODULE_BLE, "ADV Total length-%d", lengthTotal); + // Service data block is too large. + return BLE_STATUS_ERROR_PARAMETER; + } + return BLE_STATUS_SUCCESS; +} + +BLE_STATUS_T BLEDeviceManager::startAdvertising() +{ + int ret; + BLE_STATUS_T status; + status = _advDataInit(); + if (BLE_STATUS_SUCCESS != status) + { + return status; + } + + pr_info(LOG_MODULE_BLE, "%s-ad_len%d", __FUNCTION__, _adv_data_idx); + if (_state != BLE_PERIPH_STATE_READY) + return BLE_STATUS_WRONG_STATE; + + ret = bt_le_adv_start(&_adv_param, _adv_data, _adv_data_idx, NULL, 0); + if (0 != ret) + { + pr_error(LOG_MODULE_APP, "[ADV] Start failed. Error: %d", ret); + return BLE_STATUS_WRONG_STATE; + } + delay(10); + _state = BLE_PERIPH_STATE_ADVERTISING; + return BLE_STATUS_SUCCESS; +} + +BLE_STATUS_T BLEDeviceManager::stopAdvertising() +{ + int err_code = 0; + BLE_STATUS_T status = BLE_STATUS_WRONG_STATE; + + if (BLE_PERIPH_STATE_ADVERTISING == _state) + { + err_code = bt_le_adv_stop(); + status = errorno_to_ble_status(err_code); + } + + if (BLE_STATUS_SUCCESS != status) + return status; + + _state = BLE_PERIPH_STATE_READY; + return BLE_STATUS_SUCCESS; +} + +BLEDevice BLEDeviceManager::central() +{ + BLEDevice temp(&_peer_central); + return temp; +} + +BLEDevice BLEDeviceManager::peripheral() +{ + // TODO + BLEDevice temp; + return temp; +} + +void BLEDeviceManager::linkLost() +{} + +bool BLEDeviceManager::startScanning() +{ + int err = bt_le_scan_start(&_scan_param, ble_central_device_found); + if (err) + { + pr_info(LOG_MODULE_BLE, "Scanning failed to start (err %d)\n", err); + return false; + } + return true; +} + +bool BLEDeviceManager::startScanningWithDuplicates() +{ + // TODO: enable disable duplicate + return false; +} + +bool BLEDeviceManager::stopScanning() +{ + int err = bt_le_scan_stop(); + if (err) + { + pr_info(LOG_MODULE_BLE, "Stop LE scan failed (err %d)\n", err); + return false; + } + return true; +} + +void BLEDeviceManager::setAdvertiseCritical(String name) +{ + _adv_critical_local_name = name; + _adv_accept_critical.type = BT_DATA_NAME_COMPLETE; + _adv_accept_critical.data_len = name.length(); + _adv_accept_critical.data = (const uint8_t*)_adv_critical_local_name.c_str(); +} + +void BLEDeviceManager::setAdvertiseCritical(BLEService& service) +{ + BLEUtils::uuidString2BT(service.uuid(),(bt_uuid_t *)&_dv_critical_service_uuid); + uint8_t type = 0; + uint8_t length = 0; + uint8_t *data = NULL; + + pr_info(LOG_MODULE_BLE, "ADV Type-%d", _service_uuid.uuid.type); + if (BT_UUID_TYPE_16 == _service_uuid.uuid.type) + { + //UINT16_TO_LESTREAM(adv_tmp, uuid.uuid16); + data = (uint8_t *)&(((bt_uuid_16_t *)&_service_uuid)->val); + length = UUID_SIZE_16; + type = BT_DATA_UUID16_ALL; + } + else if (BT_UUID_TYPE_128 == _service_uuid.uuid.type) + { + data = _service_uuid.val; + length = UUID_SIZE_128; + type = BT_DATA_UUID128_ALL; + } + _adv_accept_critical.type = type; + _adv_accept_critical.data_len = length; + _adv_accept_critical.data = data; +} + + +//void setAcceptAdvertiseLocalName(String name); +//void setAcceptAdvertiseLocalName(BLEService& service); +//void setAcceptAdvertiseCallback(String name); + +bool BLEDeviceManager::hasLocalName() const +{ + return (_local_name.length() != 0); +} + +bool BLEDeviceManager::hasAdvertisedServiceUuid() const +{ + // TODO: + return false; +} + +bool BLEDeviceManager::hasAdvertisedServiceUuid(int index) const +{ + // TODO: + return false; +} + +int BLEDeviceManager::advertisedServiceUuidCount() const +{ + return 0; +} + +String BLEDeviceManager::localName() const +{ + return _local_name; +} + +String BLEDeviceManager::advertisedServiceUuid() const +{ + // TODO + return ""; +} + +String BLEDeviceManager::advertisedServiceUuid(int index) const +{ + // TODO + return ""; +} + +int BLEDeviceManager::rssi() const +{ + return 0; +} + +bool BLEDeviceManager::connect(BLEDevice &device) +{ + bt_addr_le_t* temp = NULL; + bt_addr_le_t* unused = NULL; + bool link_existed = false; + bool retval = false; + + pr_debug(LOG_MODULE_BLE, "%s-%d-1", __FUNCTION__, __LINE__); + // Find free peripheral Items + for (int i = 0; i < BLE_MAX_CONN_CFG; i++) + { + temp = &_peer_peripheral[i]; + if (true == BLEUtils::macAddressValid(*temp)) + { + if (bt_addr_le_cmp(temp, device.bt_le_address()) == 0) + { + // Connect request has scheduled but connection don't established. + // The central can see the ADV and no need to send connect request. + link_existed = true; + break; + } + } + else + { + if (NULL == unused) + { + unused = temp; + } + } + } + pr_debug(LOG_MODULE_BLE, "%s-%d:link_existed-%d unused-%p", __FUNCTION__, __LINE__, link_existed, unused); + + if (!link_existed) + { + pr_debug(LOG_MODULE_BLE, "%s-%d-Device:%s", __FUNCTION__, __LINE__, device.address().c_str()); + // Send connect request + bt_conn_t* conn = bt_conn_create_le(device.bt_le_address(), device.bt_conn_param()); + if (NULL != conn) + { + memcpy(unused, device.bt_le_address(), sizeof(bt_addr_le_t)); + retval = true; + bt_conn_unref(conn); + } + } + return retval; +} + +String BLEDeviceManager::deviceName() +{ + return _device_name; +} + +int BLEDeviceManager::appearance() +{ + return _appearance; +} + +BLEDeviceManager* BLEDeviceManager::instance() +{ + if (_instance == NULL) + { + _instance = new BLEDeviceManager(); + } + return _instance; +} + +void BLEDeviceManager::handleConnectEvent(bt_conn_t *conn, uint8_t err) +{ + struct bt_conn_info role_info; + bt_conn_get_info(conn, &role_info); + pr_info(LOG_MODULE_BLE, "%s-%d: role-%d", __FUNCTION__, __LINE__, role_info.role); + if (BT_CONN_ROLE_SLAVE == role_info.role) + { + // Central has established the connection with this peripheral device + memcpy(&_peer_central, bt_conn_get_dst(conn), sizeof (bt_addr_le_t)); + } + else + { + // Peripheral has established the connection with this Central device + BLEProfileManager::instance()->handleConnectedEvent(bt_conn_get_dst(conn)); + } +} + +void BLEDeviceManager::handleDisconnectEvent(bt_conn_t *conn, uint8_t reason) +{ + struct bt_conn_info role_info; + bt_conn_get_info(conn, &role_info); + pr_info(LOG_MODULE_BLE, "%s-%d: role-%d", __FUNCTION__, __LINE__, role_info.role); + if (BT_CONN_ROLE_SLAVE == role_info.role) + { + // Central has established the connection with this peripheral device + memset(&_peer_central, 0, sizeof (bt_addr_le_t)); + } + else + { + // Peripheral has established the connection with this Central device + } +} + +void BLEDeviceManager::handleParamUpdated (bt_conn_t *conn, + uint16_t interval, + uint16_t latency, + uint16_t timeout) +{ + +} + +bool BLEDeviceManager::advertiseDataProc(uint8_t type, + const uint8_t *dataPtr, + uint8_t data_len) +{ + //Serial1.print("[AD]:"); + //Serial1.print(type); + //Serial1.print(" data_len "); + //Serial1.println(data_len); + + //const bt_data_t zero = {0, 0,0}; + if (_adv_accept_critical.type == 0 && + _adv_accept_critical.data_len == 0 && + _adv_accept_critical.data == NULL) + { + // Not set the critical. Accept all. + return true; + } + if (type == _adv_accept_critical.type && + data_len == _adv_accept_critical.data_len && + 0 == memcmp(dataPtr, _adv_accept_critical.data, data_len)) + { + // Now Only support 1 critical. Change those code if want support multi-criticals + return true; + } + // Please see https://www.bluetooth.org/en-us/specification/assigned-numbers/generic-access-profile + // To decode the data the central device cares. + // This example use UUID as identity. +#if 0 + switch (type) + { + case BT_DATA_UUID128_SOME: + case BT_DATA_UUID128_ALL: + { + if (data_len % UUID_SIZE_128 != 0) + { + Serial.println("AD malformed"); + return true; + } + for (i = 0; i < data_len; i += UUID_SIZE_128) + { + if (bleImuService.uuidCompare(dataPtr + i, UUID_SIZE_128) == false) + { + continue; + } + + // Accept the advertisement + if (!bleCentral.stopScan()) + { + Serial.println("Stop LE scan failed"); + continue; + } + Serial.println("Connecting"); + return false; + } + } + case BT_DATA_NAME_COMPLETE: + { + break; + } + } +#endif + + return false; +} + +BLEDevice BLEDeviceManager::available() +{ + BLEDevice tempdevice; + bt_addr_le_t* temp = NULL; + uint64_t timestamp = millis(); + uint8_t index = 3; + uint8_t i = 0; + uint64_t max_delta = 0; + + for (i = 0; i < 3; i++) + { + uint64_t timestamp_delta = timestamp - _peer_adv_mill[i]; + temp = &_peer_adv_buffer[i]; + if ((timestamp_delta <= 2000) && (max_delta < timestamp_delta)) + { + max_delta = timestamp_delta; + index = i; + } + } + //pr_debug(LOG_MODULE_BLE, "%s-%d:index %d, i-%d", __FUNCTION__, __LINE__, index, i); + + if (index < 3) + { + temp = &_peer_adv_buffer[index]; + if (true == BLEUtils::macAddressValid(*temp)) + { + tempdevice.setAddress(*temp); + //pr_debug(LOG_MODULE_BLE, "%s-%d:Con addr-%s", __FUNCTION__, __LINE__, BLEUtils::macAddressBT2String(*temp).c_str()); + _peer_adv_mill[index] -= 2000; // Set it as expired + } + } + return tempdevice; +} + +bool BLEDeviceManager::setAdvertiseBuffer(const bt_addr_le_t* bt_addr) +{ + bt_addr_le_t* temp = NULL; + uint64_t timestamp = millis(); + uint8_t index = 3; + uint8_t i = 0; + uint64_t max_delta = 0; + bool retval = false; + //pr_debug(LOG_MODULE_BLE, "%s-%d-1", __FUNCTION__, __LINE__); + for (i = 0; i < 3; i++) + { + uint64_t timestamp_delta = timestamp - _peer_adv_mill[i]; + temp = &_peer_adv_buffer[i]; + if (max_delta < timestamp_delta) + { + max_delta = timestamp_delta; + if (max_delta > 2000) // expired + index = i; + } + + if (bt_addr_le_cmp(temp, bt_addr) == 0) + //if (memcpy(temp->val, bt_addr->val, 6) == 0) + { + // The device alread in the buffer + index = i; + break; + } + } + //pr_debug(LOG_MODULE_BLE, "%s-%d:index %d, i-%d", __FUNCTION__, __LINE__, index, i); + + //pr_debug(LOG_MODULE_BLE, "%s-%d-2", __FUNCTION__, __LINE__); + if (index < 3) + { + temp = &_peer_adv_buffer[index]; + if (i >= 3) + { + memcpy(temp, bt_addr, sizeof (bt_addr_le_t)); + } + // Update the timestamp + _peer_adv_mill[index] = timestamp; + retval = true; + } + + return retval; +} + +void BLEDeviceManager::handleDeviceFound(const bt_addr_le_t *addr, + int8_t rssi, + uint8_t type, + const uint8_t *ad, + uint8_t data_len) +{ + const uint8_t *data = ad; + + /* We're only interested in connectable events */ + if (type == BT_LE_ADV_IND || type == BT_LE_ADV_DIRECT_IND) + { + //pr_debug(LOG_MODULE_BLE, "%s-%d", __FUNCTION__, __LINE__); + while (data_len > 1) + { + uint8_t len = data[0]; + + /* Check for early termination */ + if (len == 0) + { + return; + } + + if ((len + 1 > data_len) || (data_len < 2)) { + pr_info(LOG_MODULE_BLE, "AD malformed\n"); + return; + } + + if (true == advertiseDataProc(data[1], &data[2], len - 1)) + { + // The critical is accepted + // Find the oldest and expired buffer + if(false == setAdvertiseBuffer(addr)) + { + pr_info(LOG_MODULE_BLE, "No buffer to store the ADV\n"); + } + else + { + BLEDevice testdev(addr); + stopScanning(); + connect(testdev); + } + pr_debug(LOG_MODULE_BLE, "%s-%d: Done", __FUNCTION__, __LINE__); + return; + } + + data_len -= len + 1; + data += len + 1; + } + //pr_debug(LOG_MODULE_BLE, "%s: done", __FUNCTION__); + } + +} + + + diff --git a/libraries/BLE/src/BLEDeviceManager.h b/libraries/BLE/src/BLEDeviceManager.h new file mode 100644 index 00000000..cbb597ed --- /dev/null +++ b/libraries/BLE/src/BLEDeviceManager.h @@ -0,0 +1,415 @@ +/* + BLE Device API + Copyright (c) 2016 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef ARDUINO_BLE_DEVICE_MANAGER_H +#define ARDUINO_BLE_DEVICE_MANAGER_H + +#include + +class BLEDeviceManager +{ + public: + /** + * @brief The BLE device constructure + * + * @param bleaddress BLE device address + * + * @return none + * + * @note none + */ + BLEDeviceManager(); + + virtual ~BLEDeviceManager(); + + + /** + * @brief Initiliaze the BLE hardware + * + * @return bool indicating success or error + * + * @note This method are for real BLE device. + * Not for peer BLE device. + */ + bool begin(BLEDevice *device); + + /** + * @brief Poll for events + * + * @param none + * + * @return none + * + * @note This method are for real BLE device. + * Not for peer BLE device. + */ + void poll(); // Do we need add the return value or + // input parameter to get the events? + // Events may inlcue: + // GAP : Connected, Disconnected, Update connetion parameter + // GATT: Discovered + + /** + * @brief Deinitiliaze the BLE hardware + * + * @param none + * + * @return none + * + * @note This method are for real BLE device. + * Not for peer BLE device. + */ + void end(); + + /** + * @brief Is the device connected with another BLE device. + * + * @param none + * + * @return none + * + * @note none + */ + bool connected(BLEDevice *device); + + /** + * @brief Disconnect the connected device/s. + * + * @param none + * + * @return none + * + * @note The BLE may connected multiple devices. + * This call will disconnect all conected devices. + */ + bool disconnect(BLEDevice *device); + + /** + * @brief Set the service UUID that the BLE Peripheral Device advertises + * + * @param[in] advertisedServiceUuid 16-bit or 128-bit UUID to advertis + * (in string form) + * + * @note This method must be called before the begin method + * Only for peripheral mode. + */ + void setAdvertisedServiceUuid(const char* advertisedServiceUuid); + + /** + * @brief Set the service that the BLE Peripheral Device will advertise this UUID + * + * @param[in] service The service the will in advertise data. + * + * @note This method must be called before the begin method + * Only for peripheral mode. + */ + void setAdvertisedService(const BLEService& service); + + /** + * @brief Set the service UUID that is solicited in the BLE Peripheral + * Device advertises + * + * @param[in] advertisedServiceUuid 16-bit or 128-bit UUID to advertis + * (in string form) + * + * @note This method must be called before the begin method + * Only for peripheral mode. + */ + void setServiceSolicitationUuid(const char* serviceSolicitationUuid); + + /** + * @brief Set the manufacturer data in the BLE Peripheral Device advertises + * + * @param[in] manufacturerData The data about manufacturer will + * be set in advertisement + * @param[in] manufacturerDataLength The length of the manufacturer data + * + * @note This method must be called before the begin method + * Only for peripheral mode. + */ + void setManufacturerData(const unsigned char manufacturerData[], + unsigned char manufacturerDataLength); + + /** + * Set the local name that the BLE Peripheral Device advertises + * + * @param[in] localName local name to advertise + * + * @note This method must be called before the begin method + */ + void setLocalName(const char *localName); + + /** + * @brief Set advertising interval + * + * @param[in] advertisingInterval Advertising Interval in ms + * + * @return none + * + * @note none + */ + void setAdvertisingInterval(float advertisingInterval); + + /** + * @brief Set the connection parameters and send connection + * update request in both BLE peripheral and central + * + * @param[in] intervalmin Minimum Connection Interval (ms) + * + * @param[in] intervalmax Maximum Connection Interval (ms) + * + * @param[in] latency Connection Latency + * + * @param[in] timeout Supervision Timeout (ms) + * + * @return none + * + * @note none + */ + void setConnectionInterval(float minimumConnectionInterval, + float maximumConnectionInterval, + uint16_t latency, + uint16_t timeout); + + /** + * @brief Set the min and max connection interval and send connection + * update request in both BLE peripheral and central + * + * @param[in] intervalmin Minimum Connection Interval (ms) + * + * @param[in] intervalmax Maximum Connection Interval (ms) + * + * @return none + * + * @note none + */ + void setConnectionInterval(float minimumConnectionInterval, + float maximumConnectionInterval); + + /** + * @brief Set TX power of the radio in dBM + * + * @param[in] tx_power The antenna TX power + * + * @return boolean_t true if established connection, otherwise false + */ + bool setTxPower(int txPower); + + /** + * @brief Set advertising type as connectable/non-connectable + * + * @param[in] connectable true - The device connectable + * false - The device non-connectable + * + * @return none + * + * @note Only for peripheral mode. + * Default value is connectable + */ + void setConnectable(bool connectable); + + /** + * @brief Set the value of the device name characteristic + * + * @param[in] device User-defined name string for this device. Truncated if + * more than maximum allowed string length (20 bytes). + * + * @note This method must be called before the begin method + * If device name is not set, a default name will be used + */ + void setDeviceName(const char* deviceName); + void setDeviceName(); + /** + * @brief Set the appearance type for the BLE Peripheral Device + * + * See https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.gap.appearance.xml + * for available options. + * + * @param[in] appearance Appearance category identifier as defined by BLE Standard + * + * @return BleStatus indicating success or error + * + * @note This method must be called before the begin method + */ + void setAppearance(unsigned short appearance); + + /** + * @brief Add a Service to the BLE Peripheral Device + * + * @param[in] attribute The service that will add to Peripheral + * + * @return BLE_STATUS_T Indicating success or error type + * + * @note This method must be called before the begin method + */ + BLE_STATUS_T addService(BLEService& attribute); + + /** + * @brief Construct the ADV data and start send advertisement + * + * @param none + * + * @return BLE_STATUS_T 0 - Success. Others - error code + * + * @note none + */ + BLE_STATUS_T startAdvertising(); + + /** + * @brief Stop send advertisement + * + * @param none + * + * @return none + * + * @note none + */ + BLE_STATUS_T stopAdvertising(); + + /** + * @brief Get currently connected central + * + * @return BLEDeviceManager Connected central device + * + * @note Peripheral mode only + */ + BLEDevice central(); + + /** + * @brief Get currently connected peripheral + * + * @param none + * + * @return none + * + * @note Central mode only. How to distinguish the peripheral? + */ + BLEDevice peripheral(); + + /** + * @brief Release the resources when link lost + * + * @param none + * + * @return none + * + * @note Peer devices only. Do nothing if local BLE device called. + */ + void linkLost(); + + operator bool() const; + + // central mode + void setAdvertiseCritical(String name); + void setAdvertiseCritical(BLEService& service); + bool startScanning(); // start scanning for peripherals + bool startScanningWithDuplicates(); // start scanning for peripherals, and report all duplicates + bool stopScanning(); // stop scanning for peripherals + + void setAcceptAdvertiseLocalName(String name); + void setAcceptAdvertiseLocalName(BLEService& service); + void setAcceptAdvertiseCallback(String name); + + BLEDevice available(); // retrieve a discovered peripheral + + bool hasLocalName() const; // does the peripheral advertise a local name + bool hasAdvertisedServiceUuid() const; // does the peripheral advertise a service + bool hasAdvertisedServiceUuid(int index) const; // does the peripheral advertise a service n + int advertisedServiceUuidCount() const; // number of services the peripheral is advertising + + String localName() const; // returns the advertised local name as a String + String advertisedServiceUuid() const; // returns the advertised service as a UUID String + String advertisedServiceUuid(int index) const; // returns the nth advertised service as a UUID String + + int rssi() const; // returns the RSSI of the peripheral at discovery + + bool connect(BLEDevice &device); // connect to the peripheral + + String deviceName(); // read the device name attribute of the peripheral, and return String value + int appearance(); // read the appearance attribute of the peripheral and return value as int + + static BLEDeviceManager* instance(); + + void handleConnectEvent(bt_conn_t *conn, uint8_t err); + void handleDisconnectEvent(bt_conn_t *conn, uint8_t reason); + void handleParamUpdated (bt_conn_t *conn, + uint16_t interval, + uint16_t latency, + uint16_t timeout); + void handleDeviceFound(const bt_addr_le_t *addr, + int8_t rssi, + uint8_t type, + const uint8_t *ad, + uint8_t data_len); + +protected: + +private: + BLE_STATUS_T _advDataInit(void); + bool advertiseDataProc(uint8_t type, + const uint8_t *dataPtr, + uint8_t data_len); + bool setAdvertiseBuffer(const bt_addr_le_t* bt_addr); + +private: + uint16_t _min_conn_interval; + uint16_t _max_conn_interval; + bt_addr_le_t _local_bda; + char _device_name[BLE_MAX_DEVICE_NAME+1]; + + // For Central + bt_le_scan_param_t _scan_param; // Scan parameter + bt_addr_le_t _peer_adv_buffer[3]; // Accepted peer device adress + uint64_t _peer_adv_mill[3]; // The ADV found time stamp + bt_data_t _adv_accept_critical; // The filters for central device + String _adv_critical_local_name; + bt_uuid_128_t _dv_critical_service_uuid; + + // For peripheral + struct bt_le_adv_param _adv_param; + bool _has_service_uuid; + bt_uuid_128_t _service_uuid; + bt_uuid_128_t _service_solicit_uuid; + uint16_t _appearance; + + // ADV data for peripheral + uint8_t _adv_type; + bt_data_t _adv_data[4]; + size_t _adv_data_idx; + + String _local_name; + // Peripheral states + enum BLEPeripheralState { + BLE_PERIPH_STATE_NOT_READY = 0, + BLE_PERIPH_STATE_READY, + BLE_PERIPH_STATE_ADVERTISING, + BLE_PERIPH_STATE_CONNECTED, + }; + + BLEPeripheralState _state; + + // Local + static BLEDeviceManager* _instance; + BLEDevice *_local_ble; + // Connected device object + bt_addr_le_t _peer_central; + bt_addr_le_t _peer_peripheral[BLE_MAX_CONN_CFG]; +}; + +#endif diff --git a/libraries/BLE/src/BLEProfileManager.cpp b/libraries/BLE/src/BLEProfileManager.cpp new file mode 100644 index 00000000..da9ebc96 --- /dev/null +++ b/libraries/BLE/src/BLEProfileManager.cpp @@ -0,0 +1,883 @@ +/* + * Copyright (c) 2016 Intel Corporation. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include "BLECommon.h" +#include "BLEProfileManager.h" +#include "BLECharacteristicImp.h" + +#include "BLECallbacks.h" +#include "BLEUtils.h" + +BLEDevice BLE; + +BLEProfileManager* BLEProfileManager::_instance = NULL; + +BLEProfileManager* BLEProfileManager::instance() +{ + if (NULL == _instance) + { + _instance = new BLEProfileManager(); + } + pr_debug(LOG_MODULE_BLE, "%s-%d: %p", __FUNCTION__, __LINE__, _instance); + return _instance; +} + +BLEProfileManager::BLEProfileManager (): + _cur_discover_service(NULL), + _attr_base(NULL), + _attr_index(0), + _sub_param(NULL), + _sub_param_idx(0), + _profile_registered(false) +{ + memset(_service_header_array, 0, sizeof(_service_header_array)); + memset(_discover_params, 0, sizeof(_discover_params)); + + memset(_addresses, 0, sizeof(_addresses)); + + bt_addr_le_copy(&_addresses[BLE_MAX_CONN_CFG], BLEUtils::bleGetLoalAddress()); + + pr_debug(LOG_MODULE_BLE, "%s-%d: Construct", __FUNCTION__, __LINE__); +} + +BLEProfileManager::~BLEProfileManager (void) +{ + if (this->_attr_base) + { + bfree(this->_attr_base); + } +} + +BLE_STATUS_T +BLEProfileManager::addService (BLEDevice &bledevice, BLEService& service) +{ + + BLEServiceLinkNodeHeader* serviceheader = getServiceHeader(bledevice); + if (NULL == serviceheader) + { + int index = getUnusedIndex(); + if (index >= BLE_MAX_CONN_CFG) + { + return BLE_STATUS_NO_MEMORY; + } + serviceheader = &_service_header_array[index]; + bt_addr_le_copy(&_addresses[index], bledevice.bt_le_address()); + } + + BLEServiceImp *serviceImp = new BLEServiceImp(service); + if (NULL == serviceImp) + { + return BLE_STATUS_NO_MEMORY; + } + BLEServiceNodePtr node = link_node_create(serviceImp); + if (NULL == node) + { + delete[] serviceImp; + return BLE_STATUS_NO_MEMORY; + } + link_node_insert_last(serviceheader, node); + return BLE_STATUS_SUCCESS; +} + +BLE_STATUS_T +BLEProfileManager::addService (BLEDevice &bledevice, const bt_uuid_t* uuid) +{ + BLEServiceLinkNodeHeader* serviceheader = getServiceHeader(bledevice); + if (NULL == serviceheader) + { + int index = getUnusedIndex(); + if (index >= BLE_MAX_CONN_CFG) + { + return BLE_STATUS_NO_MEMORY; + } + serviceheader = &_service_header_array[index]; + bt_addr_le_copy(&_addresses[index], bledevice.bt_le_address()); + } + + BLEServiceImp *serviceImp = new BLEServiceImp(uuid); + if (NULL == serviceImp) + { + return BLE_STATUS_NO_MEMORY; + } + BLEServiceNodePtr node = link_node_create(serviceImp); + if (NULL == node) + { + delete[] serviceImp; + return BLE_STATUS_NO_MEMORY; + } + link_node_insert_last(serviceheader, node); + return BLE_STATUS_SUCCESS; +} + +BLEProfileManager::BLEServiceLinkNodeHeader* BLEProfileManager::getServiceHeader(const BLEDevice &bledevice) +{ + String address = bledevice.address(); + int i; + for (i = 0; i <= BLE_MAX_CONN_CFG; i++) + { + if ((bt_addr_le_cmp(bledevice.bt_le_address(), &_addresses[i]) == 0)) + { + break; + } + } + if (i > BLE_MAX_CONN_CFG) + { + return NULL; + } + return &_service_header_array[i]; +} + +const BLEProfileManager::BLEServiceLinkNodeHeader* BLEProfileManager::getServiceHeader(const BLEDevice &bledevice) const +{ + String address = bledevice.address(); + int i; + for (i = 0; i <= BLE_MAX_CONN_CFG; i++) + { + if ((bt_addr_le_cmp(bledevice.bt_le_address(), &_addresses[i]) == 0)) + { + break; + } + } + if (i > BLE_MAX_CONN_CFG) + { + return NULL; + } + return &_service_header_array[i]; +} + +int BLEProfileManager::getUnusedIndex() +{ + int i; + for (i = 0; i < BLE_MAX_CONN_CFG; i++) + { + if (BLEUtils::macAddressValid(_addresses[i]) == false) + { + break; + } + } + + return i; +} + +int BLEProfileManager::getAttributeCount(BLEDevice &bledevice) +{ + BLEServiceLinkNodeHeader* serviceHeader = getServiceHeader(bledevice); + if (NULL == serviceHeader) + { + return 0; + } + + int attrCounter = 0; + + BLEServiceNodePtr node = serviceHeader->next; + while (node) + { + BLEServiceImp *service = node->value; + attrCounter += service->getAttributeCount(); + node = node->next; + } + return attrCounter; +} + +int BLEProfileManager::characteristicCount(const BLEDevice &bledevice) const +{ + const BLEServiceLinkNodeHeader* serviceHeader = getServiceHeader(bledevice); + if (NULL == serviceHeader) + { + return 0; + } + + int counter = 0; + + BLEServiceNodePtr node = serviceHeader->next; + while (node) + { + BLEServiceImp *service = node->value; + counter += service->getCharacteristicCount(); + node = node->next; + } + return counter; +} + +int BLEProfileManager::serviceCount(const BLEDevice &bledevice) const +{ + const BLEServiceLinkNodeHeader* serviceHeader = getServiceHeader(bledevice); + if (NULL == serviceHeader) + { + return 0; + } + return link_list_size(serviceHeader); +} + +int BLEProfileManager::registerProfile(BLEDevice &bledevice) +{ + int ret = 0; + + bt_gatt_attr_t *start; + BleStatus err_code = BLE_STATUS_SUCCESS; + + // The device is local BLE device. Register the service only allow local BLE device + BLEServiceLinkNodeHeader* serviceHeader = &_service_header_array[BLE_MAX_CONN_CFG]; + if ((bt_addr_le_cmp(bledevice.bt_le_address(), &_addresses[BLE_MAX_CONN_CFG]) != 0)) + { + return BLE_STATUS_FORBIDDEN; + } + + int attr_counter = getAttributeCount(bledevice); + if (0 == attr_counter) + { + return BLE_STATUS_NO_SERVICE; + } + + if (NULL == _attr_base) + { + _attr_base = (bt_gatt_attr_t *)balloc(attr_counter * sizeof(bt_gatt_attr_t), NULL); + memset(_attr_base, 0x00, (attr_counter * sizeof(bt_gatt_attr_t))); + pr_info(LOG_MODULE_BLE, "_attr_base_-%p, size-%d, attr_counter-%d", _attr_base, sizeof(_attr_base), attr_counter); + if (NULL == _attr_base) + { + err_code = BLE_STATUS_NO_MEMORY; + } + } + + if (BLE_STATUS_SUCCESS != err_code) + { + if (NULL != _attr_base) + { + bfree(_attr_base); + } + return err_code; + } + + pr_info(LOG_MODULE_BLE, "_attr_base_-%p", _attr_base); + + BLEServiceNodePtr node = serviceHeader->next; + while (node) + { + BLEServiceImp *service = node->value; + start = _attr_base + _attr_index; + service->updateProfile(start, _attr_index); + node = node->next; + } + +#if 0 + // Start debug + int i; + + for (i = 0; i < _attr_index; i++) { + { + pr_info(LOG_MODULE_APP, "gatt-: i %d, type %d, u16 0x%x", + i, + _attr_base[i].uuid->type, + BT_UUID_16(_attr_base[i].uuid)->val); + } + } + + delay(1000); + // End for debug +#endif + + ret = bt_gatt_register(_attr_base, + _attr_index); + pr_debug(LOG_MODULE_APP, "%s: ret, %d", __FUNCTION__, ret); + if (0 == ret) + { + _profile_registered = true; + } + return ret; +} + +void BLEProfileManager::clearProfile(BLEDevice &bledevice) +{ + BLEServiceLinkNodeHeader* serviceHeader = getServiceHeader(bledevice); + if (NULL == serviceHeader) + { + return; + } + + BLEServiceNodePtr node = link_node_get_first(serviceHeader); + + while (NULL != node) + { + BLEServiceImp *service = node->value; + delete[] service; + link_node_remove_first(serviceHeader); + node = link_node_get_first(serviceHeader); + } +} + +BLECharacteristicImp* BLEProfileManager::characteristic(const BLEDevice &bledevice, int index) +{ + BLECharacteristicImp* characteristicImp = NULL; + BLEServiceLinkNodeHeader* serviceHeader = getServiceHeader(bledevice); + if (NULL == serviceHeader) + { + // Doesn't find the service + return NULL; + } + int counter = 0; + BLEServiceNodePtr node = serviceHeader->next; + while (node != NULL) + { + BLEServiceImp *service = node->value; + int counterTmp = service->getCharacteristicCount(); + if (counter + counterTmp > index) + { + break; + } + counter += counterTmp; + node = node->next; + } + + if (NULL != node) + { + BLEServiceImp *service = node->value; + characteristicImp = service->characteristic(index - counter); + } + return characteristicImp; +} + +BLECharacteristicImp* BLEProfileManager::characteristic(const BLEDevice &bledevice, + const char* uuid, + int index) +{ + BLECharacteristicImp* characteristicImp = characteristic(bledevice, index); + if (NULL != characteristicImp) + { + if (false == characteristicImp->compareUuid(uuid)) + { + // UUID not align + characteristicImp = NULL; + } + } + return characteristicImp; +} + +BLECharacteristicImp* BLEProfileManager::characteristic(const BLEDevice &bledevice, + const char* uuid) +{ + BLECharacteristicImp* characteristicImp = NULL; + BLEServiceLinkNodeHeader* serviceHeader = getServiceHeader(bledevice); + if (NULL == serviceHeader) + { + // Doesn't find the service + return NULL; + } + BLEServiceNodePtr node = serviceHeader->next; + while (node != NULL) + { + BLEServiceImp *service = node->value; + characteristicImp = service->characteristic(uuid); + if (NULL != characteristicImp) + { + break; + } + node = node->next; + } + + return characteristicImp; +} + +BLEServiceImp* BLEProfileManager::service(const BLEDevice &bledevice, const char * uuid) const +{ + bt_uuid_128_t uuid_tmp; + + BLEUtils::uuidString2BT(uuid, (bt_uuid_t *)&uuid_tmp); + return service(bledevice, (const bt_uuid_t *)&uuid_tmp); +} + +BLEServiceImp* BLEProfileManager::service(const BLEDevice &bledevice, const bt_uuid_t* uuid) const +{ + BLEServiceImp* serviceImp = NULL; + const BLEServiceLinkNodeHeader* serviceHeader = getServiceHeader(bledevice); + if (NULL == serviceHeader) + { + // Doesn't find the service + return NULL; + } + BLEServiceNodePtr node = serviceHeader->next; + char uuid_tmp[37]; + BLEUtils::uuidBT2String(uuid, uuid_tmp); + pr_debug(LOG_MODULE_BLE, "%s-%d: %s", __FUNCTION__, __LINE__, uuid_tmp); + while (node != NULL) + { + serviceImp = node->value; + if (true == serviceImp->compareUuid(uuid)) + { + break; + } + node = node->next; + } + if (NULL == node) + { + serviceImp = NULL; + } + return serviceImp; +} + +BLEServiceImp* BLEProfileManager::service(const BLEDevice &bledevice, int index) const +{ + BLEServiceImp* serviceImp = NULL; + const BLEServiceLinkNodeHeader* serviceHeader = getServiceHeader(bledevice); + if (NULL == serviceHeader) + { + // Doesn't find the service + return NULL; + } + BLEServiceNodePtr node = serviceHeader->next; + + while (node != NULL) + { + if (0 == index) + { + break; + } + index--; + node = node->next; + } + if (NULL == node) + { + serviceImp = NULL; + } + else + { + serviceImp = node->value; + } + return serviceImp; +} + +void BLEProfileManager::handleConnectedEvent(const bt_addr_le_t* deviceAddr) +{ + int index = getUnusedIndex(); + if (index >= BLE_MAX_CONN_CFG) + { + //BLE_STATUS_NO_MEMORY + return; + } + bt_addr_le_copy(&_addresses[index], deviceAddr); +} + +bool BLEProfileManager::discoverAttributes(BLEDevice* device) +{ + int err; + bt_conn_t* conn; + int i = getDeviceIndex(device); + bool ret = false; + bt_gatt_discover_params_t* temp = NULL; + + pr_debug(LOG_MODULE_BLE, "%s-%d: index-%d,fun-%p", __FUNCTION__, __LINE__, i,profile_discover_process); + + if (i >= BLE_MAX_CONN_CFG) + { + // The device already in the buffer. + // This function only be called after connection established. + return ret; + } + + conn = bt_conn_lookup_addr_le(device->bt_le_address()); + if (NULL == conn) + { + // Link lost + pr_debug(LOG_MODULE_BLE, "Can't find connection\n"); + return ret; + } + temp = &_discover_params[i]; + temp->start_handle = 1; + temp->end_handle = 0xFFFF; + temp->uuid = NULL; + temp->type = BT_GATT_DISCOVER_PRIMARY; + temp->func = profile_discover_process; + + err = bt_gatt_discover(conn, temp); + bt_conn_unref(conn); + if (err) + { + pr_debug(LOG_MODULE_BLE, "Discover failed(err %d)\n", err); + return ret; + } + return true; +} + +int BLEProfileManager::getDeviceIndex(const bt_addr_le_t* macAddr) +{ + int i; + for (i = 0; i < BLE_MAX_CONN_CFG; i++) + { + if ((bt_addr_le_cmp(macAddr, &_addresses[i]) == 0)) + { + break; + } + } + return i; +} + +int BLEProfileManager::getDeviceIndex(const BLEDevice* device) +{ + return getDeviceIndex(device->bt_le_address()); +} + +uint8_t BLEProfileManager::discoverResponseProc(bt_conn_t *conn, + const bt_gatt_attr_t *attr, + bt_gatt_discover_params_t *params) +{ + const bt_addr_le_t* dst_addr = bt_conn_get_dst(conn); + int i = getDeviceIndex(dst_addr); + BLEDevice device(dst_addr); + uint8_t retVal = BT_GATT_ITER_STOP; + + pr_debug(LOG_MODULE_BLE, "%s-%d: index-%d", __FUNCTION__, __LINE__, i); + + if (i >= BLE_MAX_CONN_CFG) + { + return BT_GATT_ITER_STOP; + } + + // Process the service + switch (params->type) + { + case BT_GATT_DISCOVER_CHARACTERISTIC: + { + if (NULL != _cur_discover_service) + { + retVal = _cur_discover_service->discoverResponseProc(conn, + attr, + params); + } + break; + } + case BT_GATT_DISCOVER_DESCRIPTOR: + { + //descriptorDiscoverRsp(attr, attribute_tmp); + break; + } + case BT_GATT_DISCOVER_PRIMARY: + { + if (NULL != attr) + { + struct bt_gatt_service *svc_value = (struct bt_gatt_service *)attr->user_data; + int retval = (int)addService(device, svc_value->uuid); + BLEServiceImp* service_tmp = service(device, svc_value->uuid); + if (NULL != service_tmp) + { + pr_debug(LOG_MODULE_BLE, "%s-%d: handle-%d", + __FUNCTION__, __LINE__, attr->handle); + service_tmp->setHandle(attr->handle); + } + retVal = BT_GATT_ITER_CONTINUE; + } + else + { + // Service discover complete + serviceDiscoverComplete(device); + retVal = BT_GATT_ITER_STOP; + } + } + + default: + { + //attribute_tmp->discover(attr, &_discover_params); + break; + } + } + + if (retVal == BT_GATT_ITER_STOP) + { + const BLEServiceLinkNodeHeader* serviceHeader = getServiceHeader(device); + BLEServiceImp* serviceCurImp = NULL; + if (NULL == serviceHeader) + { + // Doesn't find the service + return BT_GATT_ITER_STOP; + } + BLEServiceNodePtr node = serviceHeader->next; + + // Discover next service + while (node != NULL) + { + serviceCurImp = node->value; + + if (NULL == _cur_discover_service) + { + bool result = serviceCurImp->discoverAttributes(&device); + if (result == true) + { + // Record the current discovering service + _cur_discover_service = serviceCurImp; + break; + } + } + else if (_cur_discover_service == serviceCurImp) + { + // Find next discoverable service + _cur_discover_service = NULL; + } + + node = node->next; + } + } + return retVal; +} + +void BLEProfileManager::serviceDiscoverComplete(const BLEDevice &bledevice) +{ + BLEServiceImp* serviceCurImp = NULL; + BLEServiceImp* servicePrevImp = NULL; + const BLEServiceLinkNodeHeader* serviceHeader = getServiceHeader(bledevice); + if (NULL == serviceHeader) + { + // Doesn't find the service + return ; + } + + BLEServiceNodePtr node = serviceHeader->next; + if (NULL != node) + { + servicePrevImp = node->value; + node = node->next; + } + + // Update the service handles + while (node != NULL) + { + serviceCurImp = node->value; + if (NULL != serviceCurImp) + { + servicePrevImp->setEndHandle(serviceCurImp->startHandle() - 1); + } + + pr_debug(LOG_MODULE_BLE, "Curr: start-%d, end-%d", servicePrevImp->startHandle(), servicePrevImp->endHandle()); + servicePrevImp = serviceCurImp; + pr_debug(LOG_MODULE_BLE, "Curr: start-%d, end-%d", servicePrevImp->startHandle(), servicePrevImp->endHandle()); + node = node->next; + } + return; +} + +#if 0 +void BLEProfileManager::characteristicDiscoverRsp(const bt_gatt_attr_t *attr, BLEAttribute* bleattr) +{ + bt_gatt_attr_t *attr_dec = declarationAttr(bleattr); + if ((NULL != attr) && (NULL != attr_dec)) + { + if (bt_uuid_cmp (attr_dec->uuid, attr->uuid) == 0) + { + attr_dec++; + attr_dec->handle = attr->handle + 1; + } + } + bleattr->discover(attr, &_discover_params); +} + +void BLEProfileManager::descriptorDiscoverRsp(const bt_gatt_attr_t *attr, BLEAttribute* bleattr) +{ + int err; + bt_gatt_attr_t *attr_dec = declarationAttr(bleattr); + if (BLETypeCharacteristic == bleattr->type()) + { + BLECharacteristic *chrc = (BLECharacteristic *)bleattr; + if (bt_uuid_cmp (chrc->getClientCharacteristicConfigUuid(), attr->uuid) == 0) + { + //CCCD + bt_gatt_attr_t *attr_chrc = attr_dec + 1; + bt_gatt_attr_t *attr_cccd = attr_dec + 2; + bt_gatt_subscribe_params_t *sub_param_tmp = chrc->getSubscribeParams(); + bt_gatt_subscribe_params_t *sub_param = _sub_param + _sub_param_idx; + bt_conn_t *conn = bt_conn_lookup_addr_le(_peripheral->bt_le_address()); + if (NULL == conn) + { + // Link lost + return; + } + + _sub_param_idx++; + attr_cccd->handle = attr->handle; + memcpy(sub_param, sub_param_tmp, sizeof(bt_gatt_subscribe_params_t)); + sub_param->ccc_handle = attr_cccd->handle; + sub_param->value_handle = attr_chrc->handle; + + // Enable CCCD to allow peripheral send Notification/Indication + err = bt_gatt_subscribe(conn, sub_param); + bt_conn_unref(conn); + if (err && err != -EALREADY) + { + pr_debug(LOG_MODULE_APP, "Subscribe failed (err %d)\n", err); + } + bleattr->discover(attr, &_discover_params); + } + else + { + // Not CCCD + // If want to support more descriptor, + // change the offset 3 as a loop to search the ATTR + bt_gatt_attr_t *attr_descriptor = attr_dec + 3; + if (attr_descriptor->uuid != NULL && + bt_uuid_cmp (attr_descriptor->uuid, attr->uuid) == 0) + { + attr_descriptor->handle = attr->handle; + } + } + } + else if (BLETypeDescriptor == bleattr->type()) + { + bt_gatt_attr_t *attr_descriptor = attr_dec++; // The descriptor is separate + if (bt_uuid_cmp (attr_dec->uuid, attr->uuid) == 0) + { + attr_descriptor->handle = attr->handle; + } + bleattr->discover(attr, &_discover_params); + } +} + +uint8_t BLEProfileManager::discover(const bt_gatt_attr_t *attr) +{ + BLEAttribute* attribute_tmp = NULL; + int i; + int err; + uint8_t ret = BT_GATT_ITER_STOP; + bool send_discover = false; + + for (i = 0; i < _num_attributes; i++) + { + // Find the discovering attribute + attribute_tmp = _attributes[i]; + if (attribute_tmp->discovering()) + { + if (NULL == attr) + { + attribute_tmp->discover(attr, &_discover_params); + break; + } + // Discover success + switch (_discover_params.type) + { + case BT_GATT_DISCOVER_CHARACTERISTIC: + { + characteristicDiscoverRsp(attr, attribute_tmp); + send_discover = true; + break; + } + case BT_GATT_DISCOVER_DESCRIPTOR: + { + descriptorDiscoverRsp(attr, attribute_tmp); + break; + } + case BT_GATT_DISCOVER_PRIMARY: + send_discover = true; + default: + { + attribute_tmp->discover(attr, &_discover_params); + break; + } + } + break; + } + } + + // Find next attribute to discover + if (attribute_tmp->discovering() == false) + { + // Current attribute complete discovery + i++; + while (i < _num_attributes) + { + attribute_tmp = _attributes[i]; + if (attribute_tmp->type() == BLETypeDescriptor) + { + // The descriptor may have been discovered by previous descriptor + bt_gatt_attr_t *attr_gatt = NULL; + for (int j = 0; j < _attr_index; j++) + { + attr_gatt = _attr_base + i; + if (attribute_tmp->uuid() == attr_gatt->uuid) + { + break; + } + } + + if (attr_gatt->handle != 0) + { + // Skip discovered descriptor + i++; + continue; + } + } + + attribute_tmp->discover(&_discover_params); + ret = BT_GATT_ITER_CONTINUE; + break; + } + } + else + { + ret = BT_GATT_ITER_CONTINUE; + } + + // Send the discover request if necessary + if (send_discover && attribute_tmp->discovering()) + { + bt_conn_t *conn = bt_conn_lookup_addr_le(_peripheral->bt_le_address()); + + ret = BT_GATT_ITER_STOP; + if (NULL == conn) + { + // Link lost + pr_debug(LOG_MODULE_APP, "Can't find connection\n"); + return ret; + } + err = bt_gatt_discover(conn, &_discover_params); + bt_conn_unref(conn); + if (err) + { + pr_debug(LOG_MODULE_APP, "Discover failed(err %d)\n", err); + return ret; + } + } + return ret; +} + + +void BLEProfileManager::discover() +{ + int err; + BLEService *serviceattr = (BLEService *)_attributes[0]; + bt_conn_t *conn = bt_conn_lookup_addr_le(_peripheral->bt_le_address()); + + if (NULL == conn) + { + // Link lost + pr_debug(LOG_MODULE_APP, "Can't find connection\n"); + return; + } + + // Reset start handle + _discover_params.start_handle = 0x0001; + serviceattr->discover(&_discover_params); + + err = bt_gatt_discover(conn, &_discover_params); + bt_conn_unref(conn); + if (err) + { + pr_debug(LOG_MODULE_APP, "Discover failed(err %d)\n", err); + return; + } +} +#endif + + diff --git a/libraries/BLE/src/BLEProfileManager.h b/libraries/BLE/src/BLEProfileManager.h new file mode 100644 index 00000000..70362c26 --- /dev/null +++ b/libraries/BLE/src/BLEProfileManager.h @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2016 Intel Corporation. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __BLE_PROFILE_MANAGER_H__ +#define __BLE_PROFILE_MANAGER_H__ + +#include "ArduinoBLE.h" + +#include "BLEServiceImp.h" + +//#include "BLECommon.h" +//#include "BLEDevice.h" +//#include "BLEService.h" + +class BLEProfileManager{ +public: + /** + * @brief Get the BLEProfile Manager instance + * + * @param none + * + * @return BLEProfileManager* BLE Profile manager + * + * @note none + */ + static BLEProfileManager* instance(); + + /** + * @brief Add an service to the BLE Device + * + * @param[in] bledevice The BLE device that owned the service + * + * @param[in] service The service to add to BLE device profile + * + * @return BleStatus indicating success or error + * + * @note This method must be called before the begin method in GATT server role + * Or be called in discover process. + */ + BLE_STATUS_T addService (BLEDevice &bledevice, BLEService& service); + + /** + * @brief Register the profile to Nordic BLE stack + * + * @param[in] bledevice The BLE Device + * + * @return int std C errno + * + * @note none + */ + int registerProfile(BLEDevice &bledevice); + + inline bool hasRegisterProfile(){return _profile_registered;} + + /** + * @brief Get the BLE's Characteristic implementation object by uuid and index + * + * @param[in] bledevice The BLE device + * + * @param[in] uuid The characteristic UUID + * + * @param[in] index The characteristic index in the profile + * + * @return BLECharacteristicImp* The BLE characteristic implementation object + * + * @note none + */ + BLECharacteristicImp* characteristic(const BLEDevice &bledevice, + const char* uuid, + int index); + BLECharacteristicImp* characteristic(const BLEDevice &bledevice, + const char* uuid); + BLECharacteristicImp* characteristic(const BLEDevice &bledevice, + int index); + BLEServiceImp* service(const BLEDevice &bledevice, const char * uuid) const; + BLEServiceImp* service(const BLEDevice &bledevice, int index) const; + BLEServiceImp* service(const BLEDevice &bledevice, const bt_uuid_t* uuid) const; + int serviceCount(const BLEDevice &bledevice) const; + int characteristicCount(const BLEDevice &bledevice) const; + + uint8_t discoverResponseProc(bt_conn_t *conn, + const bt_gatt_attr_t *attr, + bt_gatt_discover_params_t *params); + + bool discoverAttributes(BLEDevice* device); + void handleConnectedEvent(const bt_addr_le_t* deviceAddr); +protected: + friend ssize_t profile_write_process(bt_conn_t *conn, + const bt_gatt_attr_t *attr, + const void *buf, uint16_t len, + uint16_t offset); +private: + typedef LinkNode BLEServiceLinkNodeHeader; + typedef LinkNode* BLEServiceNodePtr; + typedef LinkNode BLEServiceNode; + + BLEProfileManager(); + ~BLEProfileManager (void); + + BLE_STATUS_T addService (BLEDevice &bledevice, const bt_uuid_t* uuid); + void serviceDiscoverComplete(const BLEDevice &bledevice); + + int getDeviceIndex(const bt_addr_le_t* macAddr); + int getDeviceIndex(const BLEDevice* device); + /** + * @brief Get the unused service header index + * + * @param none + * + * @return int The unused BLE profile index + * + * @note This object has a buffer to manage all devices profile. + * The buffer is an array. The different profiles + * distinguished by BLE address. + */ + int getUnusedIndex(); + + /** + * @brief Get the Service header by BLE device + * + * @param[in] bledevice The BLE device + * + * @return none + * + * @note none + */ + BLEServiceLinkNodeHeader* getServiceHeader(const BLEDevice &bledevice); + const BLEServiceLinkNodeHeader* getServiceHeader(const BLEDevice &bledevice) const; + + /** + * @brief Get the BLE attribute counter based on services, characteristics + * and descriptors. + * + * @param none + * + * @return none + * + * @note none + */ + int getAttributeCount(BLEDevice &bledevice); + + /** + * @brief Discard the profile by BLE device + * + * @param[in] bledevice The BLE device + * + * @return none + * + * @note none + */ + void clearProfile(BLEDevice &bledevice); + +private: + // The last header is for local BLE + BLEServiceLinkNodeHeader _service_header_array[BLE_MAX_CONN_CFG + 1]; // The connected devices' service and self service + bt_addr_le_t _addresses[BLE_MAX_CONN_CFG + 1]; // The BLE devices' address + + bt_gatt_discover_params_t _discover_params[BLE_MAX_CONN_CFG]; + BLEServiceImp* _cur_discover_service; + + bt_gatt_attr_t *_attr_base; // Allocate the memory for BLE stack + int _attr_index; + + bt_gatt_subscribe_params_t *_sub_param; + int _sub_param_idx; + + static BLEProfileManager* _instance; // The profile manager instance + bool _profile_registered; +}; + +#endif + diff --git a/libraries/BLE/src/BLEService.cpp b/libraries/BLE/src/BLEService.cpp new file mode 100644 index 00000000..2c2e80c5 --- /dev/null +++ b/libraries/BLE/src/BLEService.cpp @@ -0,0 +1,194 @@ +/* + BLE Service API + Copyright (c) 2016 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "BLEService.h" + +#include "BLEProfileManager.h" +#include "BLECharacteristicImp.h" + +#include "BLEUtils.h" + +BLEService::BLEService():_bledevice(),_service_imp(NULL) +{ + memset(_uuid_cstr, 0, sizeof (_uuid_cstr)); +} + +BLEService::BLEService(const char* uuid):_bledevice(),_service_imp(NULL) +{ + bt_uuid_128_t uuid_tmp; + memset(_uuid_cstr, 0, sizeof (_uuid_cstr)); + BLEUtils::uuidString2BT(uuid, (bt_uuid_t *)&uuid_tmp); + BLEUtils::uuidBT2String((const bt_uuid_t *)&uuid_tmp, _uuid_cstr); + + _bledevice.setAddress(*BLEUtils::bleGetLoalAddress()); +} + +BLEService::BLEService(BLEServiceImp* serviceImp, const BLEDevice* bledev): + _bledevice(bledev),_service_imp(serviceImp) +{ + memset(_uuid_cstr, 0, sizeof (_uuid_cstr)); + BLEUtils::uuidBT2String(serviceImp->bt_uuid(), _uuid_cstr); +} + +BLEService::~BLEService() +{ +} + +BLEService::operator bool() const +{ + return (strlen(_uuid_cstr) > 3); +} + +const char* BLEService::uuid() const +{ + return _uuid_cstr; +} + +void BLEService::addCharacteristic(BLECharacteristic& characteristic) +{ + BLEServiceImp* serviceImp = getServiceImp(); + + if (NULL != serviceImp) + { + serviceImp->addCharacteristic(_bledevice, characteristic); + } +} + +int BLEService::characteristicCount() const +{ + int count = 0; + BLEServiceImp* serviceImp = getServiceImp(); + if (NULL != serviceImp) + { + count = serviceImp->getCharacteristicCount(); + } + return count; +} + +bool BLEService::hasCharacteristic(const char* uuid) const +{ + BLECharacteristicImp* characteristicImp = NULL; + BLEServiceImp* serviceImp = getServiceImp(); + if (NULL != serviceImp) + { + characteristicImp = serviceImp->characteristic(uuid); + } + return (NULL != characteristicImp); +} + +bool BLEService::hasCharacteristic(const char* uuid, int index) const +{ + BLECharacteristicImp* characteristicImp = NULL; + BLEServiceImp* serviceImp = getServiceImp(); + if (NULL != serviceImp) + { + characteristicImp = serviceImp->characteristic(index); + if (false == characteristicImp->compareUuid(uuid)) + { + // UUID not align + characteristicImp = NULL; + } + } + return (NULL != characteristicImp); +} + +BLECharacteristic BLEService::characteristic(int index) const +{ + BLECharacteristicImp* characteristicImp = NULL; + BLEServiceImp* serviceImp = getServiceImp(); + if (NULL != serviceImp) + { + characteristicImp = serviceImp->characteristic(index); + } + if (NULL == characteristicImp) + { + BLECharacteristic temp; + return temp; + } + else + { + BLECharacteristic temp(characteristicImp, &_bledevice); + return temp; + } +} + +BLECharacteristic BLEService::characteristic(const char * uuid) const +{ + BLECharacteristicImp* characteristicImp = NULL; + BLEServiceImp* serviceImp = getServiceImp(); + if (NULL != serviceImp) + { + characteristicImp = serviceImp->characteristic(uuid); + } + + if (NULL == characteristicImp) + { + BLECharacteristic temp; + return temp; + } + else + { + BLECharacteristic temp(characteristicImp, &_bledevice); + return temp; + } +} + +BLECharacteristic BLEService::characteristic(const char * uuid, int index) const +{ + BLECharacteristicImp* characteristicImp = NULL; + BLEServiceImp* serviceImp = getServiceImp(); + if (NULL != serviceImp) + { + characteristicImp = serviceImp->characteristic(index); + if (false == characteristicImp->compareUuid(uuid)) + { + // UUID not align + characteristicImp = NULL; + } + } + if (NULL == characteristicImp) + { + BLECharacteristic temp; + return temp; + } + else + { + BLECharacteristic temp(characteristicImp, &_bledevice); + return temp; + } +} + +BLEServiceImp* BLEService::getServiceImp() +{ + if (NULL == _service_imp) + { + _service_imp = BLEProfileManager::instance()->service(_bledevice, uuid()); + } + return _service_imp; +} + +BLEServiceImp* BLEService::getServiceImp() const +{ + return _service_imp; +} + +void BLEService::setServiceImp(BLEServiceImp* serviceImp) +{ + _service_imp = serviceImp; +} + diff --git a/libraries/BLE/src/BLEService.h b/libraries/BLE/src/BLEService.h new file mode 100644 index 00000000..de4973dd --- /dev/null +++ b/libraries/BLE/src/BLEService.h @@ -0,0 +1,137 @@ +/* + BLE Service API + Copyright (c) 2016 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef ARDUINO_BLE_SERVICE_H +#define ARDUINO_BLE_SERVICE_H + +#include "ArduinoBLE.h" +#include "BLEDevice.h" + +//class BLECharacteristic; +class BLEServiceImp; + +class BLEService +{ +public: + BLEService(); + BLEService(const char* uuid); + virtual ~BLEService(); + + virtual operator bool() const; // is the service valid + + const char* uuid() const; + + /** + * @brief Add a characteristic in service + * + * @param characteristic The characteristic want to be added to service + * + * @return none + * + * @note none + */ + void addCharacteristic(BLECharacteristic& characteristic); + + /** + * @brief Get the number of characteristics the service has + * + * @param none + * + * @return none + * + * @note none + */ + int characteristicCount() const; + + /** + * @brief Does the service have a characteristic with the specified UUID + * + * @param uuid The UUID of the characteristic + * + * @return bool true - Yes. false - No + * + * @note none + */ + bool hasCharacteristic(const char* uuid) const; + + /** + * @brief Does the service have an nth characteristic with the + * specified UUID + * + * @param uuid The UUID of the characteristic + * + * @param index The index of characteristic + * + * @return bool true - Yes. false - No + * + * @note none + */ + bool hasCharacteristic(const char* uuid, int index) const; + + /** + * @brief Return the nth characteristic of the service + * + * @param index The index of characteristic + * + * @return BLECharacteristic The characteristic + * + * @note none + */ + BLECharacteristic characteristic(int index) const; + + /** + * @brief Return the characteristic with the specified UUID + * + * @param uuid The UUID of the characteristic + * + * @return BLECharacteristic The characteristic + * + * @note none + */ + BLECharacteristic characteristic(const char * uuid) const; + + /** + * @brief return the nth characteristic with the specified UUID + * + * @param uuid The UUID of the characteristic + * + * @param index The index of characteristic + * + * @return BLECharacteristic The characteristic + * + * @note none + */ + BLECharacteristic characteristic(const char * uuid, int index) const; + +protected: + friend class BLEDevice; + friend class BLEServiceImp; + BLEService(BLEServiceImp* serviceImp, const BLEDevice* bledev); + void setServiceImp(BLEServiceImp* serviceImp); +private: + BLEServiceImp* getServiceImp(); + BLEServiceImp* getServiceImp() const; + +private: + BLEDevice _bledevice; + BLEServiceImp* _service_imp; + char _uuid_cstr[37]; +}; + +#endif diff --git a/libraries/BLE/src/BLEServiceImp.cpp b/libraries/BLE/src/BLEServiceImp.cpp new file mode 100644 index 00000000..15628c15 --- /dev/null +++ b/libraries/BLE/src/BLEServiceImp.cpp @@ -0,0 +1,308 @@ +/* + * Copyright (c) 2015 Intel Corporation. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "internal/ble_client.h" + +#include "BLEServiceImp.h" +#include "BLECallbacks.h" +#include "BLEUtils.h" +#include "BLECharacteristicImp.h" + +bt_uuid_16_t BLEServiceImp::_gatt_primary_uuid = {BT_UUID_TYPE_16, BT_UUID_GATT_PRIMARY_VAL}; + +bt_uuid_t *BLEServiceImp::getPrimayUuid(void) +{ + return (bt_uuid_t *)&_gatt_primary_uuid; +} + +BLEServiceImp::BLEServiceImp(BLEService& service): + BLEAttribute(service.uuid(), BLETypeService), + _start_handle(0), + _end_handle(0xFFFF) +{ + memset(&_characteristics_header, 0, sizeof(_characteristics_header)); + service.setServiceImp(this); +} + +BLEServiceImp::BLEServiceImp(const bt_uuid_t* uuid): + BLEAttribute(uuid, BLETypeService), + _start_handle(0), + _end_handle(0xFFFF) +{ + memset(&_characteristics_header, 0, sizeof(_characteristics_header)); +} + +BLEServiceImp::~BLEServiceImp() +{ + releaseCharacteristic(); +} + + +int BLEServiceImp::addCharacteristic(BLEDevice& bledevice, BLECharacteristic& characteristic) +{ + BLECharacteristicImp* characteristicImp = new BLECharacteristicImp(characteristic, bledevice); + pr_debug(LOG_MODULE_BLE, "%s-%d",__FUNCTION__, __LINE__); + if (NULL == characteristicImp) + { + return BLE_STATUS_NO_MEMORY; + } + + BLECharacteristicNodePtr node = link_node_create(characteristicImp); + if (NULL == node) + { + delete[] characteristicImp; + return BLE_STATUS_NO_MEMORY; + } + link_node_insert_last(&_characteristics_header, node); + pr_debug(LOG_MODULE_BLE, "%s-%d",__FUNCTION__, __LINE__); + return BLE_STATUS_SUCCESS; +} + +int BLEServiceImp::updateProfile(bt_gatt_attr_t *attr_start, int& index) +{ + bt_gatt_attr_t *start = attr_start; + int base_index = index; + int offset = 0; + int counter = 0; + start->uuid = BLEServiceImp::getPrimayUuid(); + start->perm = BT_GATT_PERM_READ; + start->read = bt_gatt_attr_read_service; + start->user_data = (void *)bt_uuid(); + + pr_debug(LOG_MODULE_BLE, "service-%p", start); + start++; + index++; + counter++; + + BLECharacteristicNodePtr node = _characteristics_header.next; + while (NULL != node) + { + BLECharacteristicImp *characteristicImp = node->value; + start = attr_start + index - base_index; + offset = characteristicImp->updateProfile(start, index); + counter += offset; + node = node->next; + } + return counter; +} + +int BLEServiceImp::getAttributeCount() +{ + int counter = 1; // Service itself + + BLECharacteristicNodePtr node = _characteristics_header.next; + while (NULL != node) + { + BLECharacteristicImp *characteristicImp = node->value; + + counter += characteristicImp->getAttributeCount(); + node = node->next; + } + return counter; +} + +int BLEServiceImp::getCharacteristicCount() +{ + return link_list_size(&_characteristics_header); +} + +void BLEServiceImp::releaseCharacteristic() +{ + BLECharacteristicNodePtr node = link_node_get_first(&_characteristics_header); + + while (NULL != node) + { + BLECharacteristicImp* characteristicImp = node->value; + delete[] characteristicImp; + link_node_remove_first(&_characteristics_header); + node = link_node_get_first(&_characteristics_header); + } +} + + +BLECharacteristicImp* BLEServiceImp::characteristic(int index) +{ + BLECharacteristicImp* characteristicImp = NULL; + BLECharacteristicNodePtr node = link_node_get_first(&_characteristics_header); + while (NULL != node) + { + if (0 >= index) + { + characteristicImp = node->value; + break; + } + index--; + node = node->next; + } + return characteristicImp; +} + +BLECharacteristicImp* BLEServiceImp::characteristic(uint16_t handle) +{ + BLECharacteristicImp* characteristicImp = NULL; + BLECharacteristicNodePtr node = link_node_get_first(&_characteristics_header); + while (NULL != node) + { + characteristicImp = node->value; + if (handle == characteristicImp->valueHandle()) + { + break; + } + node = node->next; + } + if (NULL == node) + { + characteristicImp = NULL; + } + return characteristicImp; +} + +BLECharacteristicImp* BLEServiceImp::characteristic(const char* uuid) +{ + BLECharacteristicImp* characteristicImp = NULL; + BLECharacteristicNodePtr node = link_node_get_first(&_characteristics_header); + while (NULL != node) + { + characteristicImp = node->value; + if (true == characteristicImp->compareUuid(uuid)) + { + break; + } + node = node->next; + } + + if (NULL == node) + { + characteristicImp = NULL; + } + return characteristicImp; +} + +bool BLEServiceImp::discoverAttributes(BLEDevice* device) +{ + + int err; + bt_conn_t* conn; + bt_gatt_discover_params_t* temp = NULL; + const bt_uuid_t* service_uuid = bt_uuid(); + + if (service_uuid->type == BT_UUID_TYPE_16) + { + uint16_t uuid_tmp = ((bt_uuid_16_t*)service_uuid)->val; + if (BT_UUID_GAP_VAL == uuid_tmp || + BT_UUID_GATT_VAL == uuid_tmp) + { + return false; + } + } + + conn = bt_conn_lookup_addr_le(device->bt_le_address()); + if (NULL == conn) + { + // Link lost + pr_debug(LOG_MODULE_BLE, "Can't find connection\n"); + return false; + } + temp = &_discover_params; + temp->start_handle = _start_handle; + temp->end_handle = _end_handle; + temp->uuid = NULL; + temp->type = BT_GATT_DISCOVER_CHARACTERISTIC; + temp->func = profile_discover_process; + + err = bt_gatt_discover(conn, temp); + bt_conn_unref(conn); + if (err) + { + pr_debug(LOG_MODULE_BLE, "Discover failed(err %d)\n", err); + return false; + } + return true; +} + +uint8_t BLEServiceImp::discoverResponseProc(bt_conn_t *conn, + const bt_gatt_attr_t *attr, + bt_gatt_discover_params_t *params) +{ + const bt_addr_le_t* dst_addr = bt_conn_get_dst(conn); + BLEDevice device(dst_addr); + uint8_t retVal = BT_GATT_ITER_STOP; + + pr_debug(LOG_MODULE_BLE, "%s-%d", __FUNCTION__, __LINE__); + if (NULL == attr) + { + return BT_GATT_ITER_STOP; + } + const bt_uuid_t* chrc_uuid = attr->uuid; + struct bt_gatt_chrc* psttemp = (struct bt_gatt_chrc*)attr->user_data; + char uuidbuffer[37]; + BLEUtils::uuidBT2String(chrc_uuid, uuidbuffer); + pr_debug(LOG_MODULE_BLE, "%s-%d: uuid type: %d\r\n%s", __FUNCTION__, __LINE__, chrc_uuid->type, uuidbuffer); + + BLEUtils::uuidBT2String(psttemp->uuid, uuidbuffer); + pr_debug(LOG_MODULE_BLE, "%s-%d:%p uuid type: %d properties: %d\r\n%s", __FUNCTION__, __LINE__,psttemp, psttemp->uuid->type, psttemp->properties, uuidbuffer); + // Process the service + switch (params->type) + { + case BT_GATT_DISCOVER_CHARACTERISTIC: + { + //characteristicDiscoverRsp(attr, attribute_tmp); + //send_discover = true; + break; + } + default: + { + //attribute_tmp->discover(attr, &_discover_params); + break; + } + } + + if (retVal == BT_GATT_ITER_STOP) + { + const BLECharacteristicLinkNodeHeader* serviceHeader = &_characteristics_header; + BLECharacteristicImp* chrcCurImp = NULL; + BLECharacteristicNodePtr node = serviceHeader->next; + + // Discover next service + while (node != NULL) + { + chrcCurImp = node->value; + + if (NULL == _cur_discover_chrc) + { + bool result = true;//serviceCurImp->discoverAttributes(&device); + if (result == true) + { + // Record the current discovering service + _cur_discover_chrc = chrcCurImp; + break; + } + } + else if (_cur_discover_chrc == chrcCurImp) + { + // Find next discoverable service + _cur_discover_chrc = NULL; + } + node = node->next; + } + } + return retVal; +} + + diff --git a/libraries/BLE/src/BLEServiceImp.h b/libraries/BLE/src/BLEServiceImp.h new file mode 100644 index 00000000..d9706edf --- /dev/null +++ b/libraries/BLE/src/BLEServiceImp.h @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2015 Intel Corporation. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef _BLE_SERVICE_IMP_H_INCLUDED +#define _BLE_SERVICE_IMP_H_INCLUDED + +#include "ArduinoBLE.h" + +#include "BLEAttribute.h" + +#include "LinkList.h" + +/** + * BLE GATT Service + */ +class BLEServiceImp: public BLEAttribute{ +public: + /** + * Constructor for BLE Service + * + * @param[in] uuid 16-bit or 128-bit UUID (in string form) defined by BLE standard + */ + BLEServiceImp(BLEService& service); + BLEServiceImp(const bt_uuid_t* uuid); + ~BLEServiceImp(); + + /** + * @brief Add a characteristic in service + * + * @param[in] bledevice The BLE device want to add the characteristic + * + * @param[in] characteristic The characteristic want to be added to service + * + * @return none + * + * @note none + */ + int addCharacteristic(BLEDevice& bledevice, BLECharacteristic& characteristic); + + int getCharacteristicCount(); + + BLECharacteristicImp* characteristic(const char* uuid); + BLECharacteristicImp* characteristic(int index); + BLECharacteristicImp* characteristic(uint16_t handle); + inline void setHandle(uint16_t handle){_start_handle = handle;} + inline void setEndHandle(uint16_t handle){_end_handle = handle;} + inline uint16_t endHandle(){return _end_handle;} + inline uint16_t startHandle(){return _start_handle;} + + bool discoverAttributes(BLEDevice* device); + uint8_t discoverResponseProc(bt_conn_t *conn, + const bt_gatt_attr_t *attr, + bt_gatt_discover_params_t *params); +protected: + friend class BLEProfileManager; + + int getAttributeCount(); + + + int updateProfile(bt_gatt_attr_t *attr_start, int& index); + + static bt_uuid_t *getPrimayUuid(void); +private: + typedef LinkNode BLECharacteristicLinkNodeHeader; + typedef LinkNode* BLECharacteristicNodePtr; + typedef LinkNode BLECharacteristicNode; + + uint16_t _start_handle; + uint16_t _end_handle; + + void releaseCharacteristic(); + BLECharacteristicImp *_cur_discover_chrc; + + static bt_uuid_16_t _gatt_primary_uuid; + bt_gatt_discover_params_t _discover_params; + + BLECharacteristicLinkNodeHeader _characteristics_header; // The characteristic link list +}; + +#endif // _BLE_SERVICE_H_INCLUDED diff --git a/libraries/BLE/src/BLEUtils.cpp b/libraries/BLE/src/BLEUtils.cpp new file mode 100644 index 00000000..461b5485 --- /dev/null +++ b/libraries/BLE/src/BLEUtils.cpp @@ -0,0 +1,148 @@ + +#include "ArduinoBLE.h" +#include "BLEUtils.h" +#include "internal/ble_client.h" + +String BLEUtils::macAddressBT2String(const bt_addr_le_t &bd_addr) +{ + char mac_string[BT_ADDR_STR_LEN]; + snprintf(mac_string, BT_ADDR_STR_LEN, "%2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X", + bd_addr.val[5], bd_addr.val[4], bd_addr.val[3], + bd_addr.val[2], bd_addr.val[1], bd_addr.val[0]); + String temp(mac_string); + return temp; +} + +void BLEUtils::macAddressString2BT(const char* mac_str, bt_addr_le_t &bd_addr) +{ + char temp[] = {0, 0, 0}; + int strLength = strlen(mac_str); + int length = 0; + + bd_addr.type = BT_ADDR_LE_PUBLIC; + + for (int i = strLength - 1; i >= 0 && length < MAX_UUID_SIZE; i -= 2) + { + if (mac_str[i] == ':') + { + i++; + continue; + } + + temp[0] = mac_str[i - 1]; + temp[1] = mac_str[i]; + + bd_addr.val[length] = strtoul(temp, NULL, 16); + + length++; + } + +} + +bool BLEUtils::macAddressValid(const bt_addr_le_t &bd_addr) +{ + static const bt_addr_le_t zero = {0,{0,0,0,0,0,0}}; + bool temp = (memcmp(bd_addr.val, zero.val, 6) != 0);//false;// + #if 0 + for (int i = 0; i < 6; i++) + { + if (bd_addr.val[i] != zero.val[i]) + { + +pr_info(LOG_MODULE_BLE, "%s-idx %d-%.2x:%.2x", __FUNCTION__, i ,bd_addr.val[i], zero.val[i]); +pr_info(LOG_MODULE_BLE,"%s",BLEUtils::macAddressBT2String(zero).c_str()); + temp = true; + break; + } + } + #endif + return temp; +} + + +bt_addr_le_t* BLEUtils::bleGetLoalAddress() +{ + static bt_addr_le_t board_addr; + if (false == macAddressValid(board_addr)) + ble_client_get_mac_address(&board_addr); + return &board_addr; +} + + + +void BLEUtils::uuidString2BT(const char* uuid, bt_uuid_t* pstuuid) +{ + char temp[] = {0, 0, 0}; + int strLength = strlen(uuid); + int length = 0; + bt_uuid_128_t uuid_tmp; + + memset (&uuid_tmp, 0x00, sizeof(uuid_tmp)); + + for (int i = strLength - 1; i >= 0 && length < MAX_UUID_SIZE; i -= 2) + { + if (uuid[i] == '-') + { + i++; + continue; + } + + temp[0] = uuid[i - 1]; + temp[1] = uuid[i]; + + uuid_tmp.val[length] = strtoul(temp, NULL, 16); + + length++; + } + + if (length == 2) + { + uint16_t temp = (uuid_tmp.val[1] << 8)| uuid_tmp.val[0]; + uuid_tmp.uuid.type = BT_UUID_TYPE_16; + ((bt_uuid_16_t*)(&uuid_tmp.uuid))->val = temp; + } + else + { + uuid_tmp.uuid.type = BT_UUID_TYPE_128; + } + memcpy(pstuuid, &uuid_tmp, sizeof (uuid_tmp)); +} + +void BLEUtils::uuidBT2String(const bt_uuid_t* pstuuid, char* uuid) +{ + unsigned int tmp1, tmp5; + uint16_t tmp0, tmp2, tmp3, tmp4; + // TODO: Change the magic number 37 + switch (pstuuid->type) { + case BT_UUID_TYPE_16: + memcpy(&tmp0, &BT_UUID_16(pstuuid)->val, sizeof(tmp0)); + snprintf(uuid, 37, "%.4x", tmp0); + break; + case BT_UUID_TYPE_128: + memcpy(&tmp0, &BT_UUID_128(pstuuid)->val[0], sizeof(tmp0)); + memcpy(&tmp1, &BT_UUID_128(pstuuid)->val[2], sizeof(tmp1)); + memcpy(&tmp2, &BT_UUID_128(pstuuid)->val[6], sizeof(tmp2)); + memcpy(&tmp3, &BT_UUID_128(pstuuid)->val[8], sizeof(tmp3)); + memcpy(&tmp4, &BT_UUID_128(pstuuid)->val[10], sizeof(tmp4)); + memcpy(&tmp5, &BT_UUID_128(pstuuid)->val[12], sizeof(tmp5)); + snprintf(uuid, 37, "%.8x-%.4x-%.4x-%.4x-%.8x%.4x", + tmp5, tmp4, tmp3, tmp2, tmp1, tmp0); + break; + default: + memset(uuid, 0, 37); + return; + } +} + +BLEDevice& BLEUtils::getLoacalBleDevice() +{ + return BLE; +} + +bool BLEUtils::isLocalBLE(BLEDevice& device) +{ + return (device == BLE); +} + + + diff --git a/libraries/BLE/src/BLEUtils.h b/libraries/BLE/src/BLEUtils.h new file mode 100644 index 00000000..e3bc241f --- /dev/null +++ b/libraries/BLE/src/BLEUtils.h @@ -0,0 +1,13 @@ + +namespace BLEUtils +{ + String macAddressBT2String(const bt_addr_le_t &bd_addr); + void macAddressString2BT(const char* mac_str, bt_addr_le_t &bd_addr); + bool macAddressValid(const bt_addr_le_t &bd_addr); + bt_addr_le_t* bleGetLoalAddress(); + void uuidString2BT(const char* uuid, bt_uuid_t* pstuuid); + void uuidBT2String(const bt_uuid_t* pstuuid, char* uuid); + BLEDevice& getLoacalBleDevice(); + bool isLocalBLE(BLEDevice& device); +} + diff --git a/libraries/BLE/src/LinkList.h b/libraries/BLE/src/LinkList.h new file mode 100644 index 00000000..65c34fe4 --- /dev/null +++ b/libraries/BLE/src/LinkList.h @@ -0,0 +1,78 @@ +#ifndef _LINKLIST_H_ +#define _LINKLIST_H_ + +template struct LinkNode { + LinkNode *next; + T value; +}; + +template LinkNode* link_node_create(T value) +{ + LinkNode* node = (LinkNode*)malloc(sizeof(LinkNode)); + node->value = value; + node->next = NULL; + return node; +} + +template void link_node_insert_last(LinkNode *root, LinkNode *node) +{ + while(root->next != 0) + { + root = root->next; + } + root->next = node; +} + +template void link_node_remove_last(LinkNode *root) +{ + LinkNode *temp1, *temp2; + if (root->next != NULL) + { + temp1 = root->next; + while(temp1->next != NULL) + { + temp2 = temp1; + temp1 = temp1->next; + } + + free(temp1); + temp2->next = NULL; + } +} + +template void link_node_remove_first(LinkNode *root) +{ + LinkNode *temp1; + if (root->next != NULL) + { + temp1 = root->next; + root->next = temp1->next; + free(temp1); + } +} + +template LinkNode * link_node_get_first(LinkNode *root) +{ + return root->next; +} + +template void link_node_insert_first(LinkNode *root, LinkNode *node) +{ + LinkNode* temp = root->next; + root->next = node; + node->next = temp; +} + +template int link_list_size(const LinkNode *root) +{ + int counter = 0; + while(root->next != 0) + { + root = root->next; + counter++; + } + return counter; +} + +#endif + diff --git a/libraries/BLE/src/internal/ble_client.c b/libraries/BLE/src/internal/ble_client.c new file mode 100644 index 00000000..5a9e6164 --- /dev/null +++ b/libraries/BLE/src/internal/ble_client.c @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2015, Intel Corporation. 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 holder 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 AND CONTRIBUTORS "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 OR CONTRIBUTORS 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 + +#include +#include "cfw/cfw.h" +#include "cfw/cfw_debug.h" +#include "cfw/cfw_messages.h" +#include "cfw/cfw_internal.h" +#include "cfw/cfw_service.h" +#include "cfw_platform.h" +#include "infra/time.h" +#include "infra/factory_data.h" +#include "infra/version.h" +#include "curie_factory_data.h" +#include "portable.h" + +#include "uart.h" +#include "ipc_uart_ns16550.h" +#include "infra/ipc_uart.h" + +#include "ble_client.h" +#include "platform.h" + +#include "infra/log.h" + +// APP callback +static ble_client_connect_event_cb_t ble_client_connect_event_cb = NULL; +static void *ble_client_connect_event_param; + +static ble_client_disconnect_event_cb_t ble_client_disconnect_event_cb = NULL; +static void *ble_client_disconnect_event_param; + +static ble_client_update_param_event_cb_t ble_client_update_param_event_cb = NULL; +static void *ble_client_update_param_event_param; + + +#define NIBBLE_TO_CHAR(n) \ + ((n) >= 0xA ? ('A' + (n) - 0xA) : ('0' + (n))) + +#define BYTE_TO_STR(s, byte) \ + do { \ + *s++ = NIBBLE_TO_CHAR(byte >> 4); \ + *s++ = NIBBLE_TO_CHAR(byte & 0xF); \ + }while(0) + + +#ifdef __cplusplus +extern "C" { +#endif + +static void on_connected(bt_conn_t *conn, uint8_t err) +{ + if (ble_client_connect_event_cb) + { + ble_client_connect_event_cb(conn, err, ble_client_connect_event_param); + } +} + +static void on_disconnected(bt_conn_t *conn, uint8_t reason) +{ + if (ble_client_disconnect_event_cb) + { + ble_client_disconnect_event_cb(conn, reason, ble_client_disconnect_event_param); + } +} + +static void on_le_param_updated(bt_conn_t *conn, uint16_t interval, + uint16_t latency, uint16_t timeout) +{ + if (ble_client_update_param_event_cb) + { + ble_client_update_param_event_cb (conn, + interval, + latency, + timeout, + ble_client_update_param_event_param); + } +} + +static struct bt_conn_cb conn_callbacks = { + .connected = on_connected, + .disconnected = on_disconnected, + .le_param_updated = on_le_param_updated +}; + +void ble_client_get_mac_address(bt_addr_le_t *bda) +{ + struct curie_oem_data *p_oem = NULL; + unsigned i; + + /* Set the MAC address defined in Factory Data (if provided) + * Otherwise, the device will default to a static random address */ + if (bda) { + bda->type = BLE_DEVICE_ADDR_INVALID; + if (!strncmp((char*)global_factory_data->oem_data.magic, FACTORY_DATA_MAGIC, 4)) { + p_oem = (struct curie_oem_data *) &global_factory_data->oem_data.project_data; + if (p_oem->bt_mac_address_type < 2) { + bda->type = p_oem->bt_mac_address_type; + for (i = 0; i < BLE_ADDR_LEN; i++) + bda->val[i] = p_oem->bt_address[BLE_ADDR_LEN - 1 - i]; + } + } + } +} + +void ble_client_get_factory_config(bt_addr_le_t *bda, char *name) +{ + struct curie_oem_data *p_oem = NULL; + + ble_client_get_mac_address(bda); + + /* Set a default name if one has not been specified */ + if (name) { + + // Need to check in the OTP if there is some board name set + // If yes, let's read it, otherwise let's keep the default + // name set in BLE_DEVICE_NAME_DEFAULT_PREFIX + const struct customer_data* otp_data_ptr = (struct customer_data*)(FACTORY_DATA_ADDR + 0x200); + char *suffix; + + // checking the presence of key patterns + if ((otp_data_ptr->patternKeyStart == PATTERN_KEY_START) && + (otp_data_ptr->patternKeyEnd == PATTERN_KEY_END)) + { + // The board name is with OTP ar programmed + uint8_t len = otp_data_ptr->board_name_len; + + // We need to reserve 5 bytes for '-' and 4 last MAC address in ASCII + if (len > BLE_MAX_DEVICE_NAME - 5) len = BLE_MAX_DEVICE_NAME - 5; + strncpy(name, (const char *)otp_data_ptr->board_name, len); + suffix = name + len; + } + else + { + // There is no board name in the OTP area + suffix = name + strlen(BLE_DEVICE_NAME_DEFAULT_PREFIX); + strcpy(name, BLE_DEVICE_NAME_DEFAULT_PREFIX); + } + + // Adding the four last digits of MAC address separated by '-' sufix + if (bda && bda->type != BLE_DEVICE_ADDR_INVALID) + { + *suffix++ = '-'; + p_oem = (struct curie_oem_data *) &global_factory_data->oem_data.project_data; + BYTE_TO_STR(suffix, p_oem->bt_address[4]); + BYTE_TO_STR(suffix, p_oem->bt_address[5]); + *suffix = 0; /* NULL-terminate the string. Note the macro BYTE_TO_STR + automatically move the pointer */ + } + else + { + /* This code segment will be only reached if Curie module was not + provisioned properly with a BLE MAC address*/ + *suffix++ = 0; /* NULL-terminate the string */ + } + } +} + +void ble_client_init(ble_client_connect_event_cb_t connect_cb, void* connect_param, + ble_client_disconnect_event_cb_t disconnect_cb, void* disconnect_param, + ble_client_update_param_event_cb_t update_param_cb, void* update_param_param) +{ + //uint32_t delay_until; + pr_info(LOG_MODULE_BLE, "%s", __FUNCTION__); + ble_client_connect_event_cb = connect_cb; + ble_client_connect_event_param = connect_param; + + ble_client_disconnect_event_cb = disconnect_cb; + ble_client_disconnect_event_param = disconnect_param; + + ble_client_update_param_event_cb = update_param_cb; + ble_client_update_param_event_param = update_param_param; + + bt_conn_cb_register(&conn_callbacks); + return; +} + +BLE_STATUS_T errorno_to_ble_status(int err) +{ + BLE_STATUS_T err_code; + err = 0 - err; + + switch(err) { + case 0: + err_code = BLE_STATUS_SUCCESS; + break; + case EIO: + err_code = BLE_STATUS_WRONG_STATE; + break; + case EBUSY: + err_code = BLE_STATUS_TIMEOUT; + break; + case EFBIG: + case ENOTSUP: + err_code = BLE_STATUS_NOT_SUPPORTED; + break; + case EPERM: + case EACCES: + err_code = BLE_STATUS_NOT_ALLOWED; + break; + case ENOMEM: // No memeory + err_code = BLE_STATUS_NO_MEMORY; + break; + default: + err_code = BLE_STATUS_ERROR; + break; + } + return err_code; +} + + +#ifdef __cplusplus +} +#endif diff --git a/libraries/BLE/src/internal/ble_client.h b/libraries/BLE/src/internal/ble_client.h new file mode 100644 index 00000000..75c3d59f --- /dev/null +++ b/libraries/BLE/src/internal/ble_client.h @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2015, Intel Corporation. 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 holder 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 AND CONTRIBUTORS "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 OR CONTRIBUTORS 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. + */ + +#ifndef _BLE_CLIENT_H_INCLUDED +#define _BLE_CLIENT_H_INCLUDED + +#include "BLECommon.h" + +enum { + UNIT_0_625_MS = 625, /**< Number of microseconds in 0.625 milliseconds. */ + UNIT_1_25_MS = 1250, /**< Number of microseconds in 1.25 milliseconds. */ + UNIT_10_MS = 10000 /**< Number of microseconds in 10 milliseconds. */ +}; + +#define MSEC_TO_UNITS(TIME, RESOLUTION) (((TIME) * 1000) / (RESOLUTION)) +#define UNITS_TO_MSEC(TIME, RESOLUTION) (((TIME) * RESOLUTION) / 1000) + +/* Connection parameters used for Peripheral Preferred Connection Parameterss (PPCP) and update request */ +#define DEFAULT_MIN_CONN_INTERVAL MSEC_TO_UNITS(80, UNIT_1_25_MS) +#define DEFAULT_MAX_CONN_INTERVAL MSEC_TO_UNITS(150, UNIT_1_25_MS) +#define MIN_CONN_INTERVAL 0x0006 +#define MAX_CONN_INTERVAL 0x0C80 +#define SLAVE_LATENCY 0 +#define CONN_SUP_TIMEOUT MSEC_TO_UNITS(6000, UNIT_10_MS) + +/* Borrowed from ble_service_utils.h */ +#define UINT8_TO_LESTREAM(p, i) \ + do { *(p)++ = (uint8_t)(i); } \ + while (0) +#define UINT16_TO_LESTREAM(p, i) UINT8_TO_LESTREAM(p, i); UINT8_TO_LESTREAM(p, (i)>>8) +#define UINT32_TO_LESTREAM(p, i) UINT16_TO_LESTREAM(p, i); UINT16_TO_LESTREAM(p, (i)>>16) + +#define INT8_TO_LESTREAM(p, i) UINT8_TO_LESTREAM(p, (uint8_t)(i)) +#define INT16_TO_LESTREAM(p, i) UINT16_TO_LESTREAM(p, (uint16_t)(i)) +#define INT32_TO_LESTREAM(p, i) UINT32_TO_LESTREAM(p, (uint32_t)(i)) + +#define LESTREAM_TO_UINT8(p, i) \ + do { i = *p; p++; } \ + while (0) +#define LESTREAM_TO_UINT16(p, i) \ + do { uint16_t temp16; LESTREAM_TO_UINT8(p, i); LESTREAM_TO_UINT8(p, temp16); i |= (temp16 << 8); } \ + while (0) +#define LESTREAM_TO_UINT32(p, i) \ + do { uint32_t temp32; LESTREAM_TO_UINT16(p, i); LESTREAM_TO_UINT16(p, temp32); i |= (temp32 << 16); } \ + while (0) + +#define LESTREAM_TO_INT8(p, i) \ + do {uint8_t __i; LESTREAM_TO_UINT8(p, __i); i = (int8_t)__i; } while (0) +#define LESTREAM_TO_INT16(p, i) \ + do {uint16_t __i; LESTREAM_TO_UINT16(p, __i); i = (int16_t)__i; } while (0) +#define LESTREAM_TO_INT32(p, i) \ + do {uint32_t __i; LESTREAM_TO_UINT32(p, __i); i = (int32_t)__i; } while (0) + +#define BLE_BASE_UUID_OCTET_OFFSET 12 +#define BLE_UUID16_TO_UUID128(uuid, base) \ + do { \ + uint16_t uuid16 = uuid.uuid16; \ + memcpy(uuid.uuid128, base.uuid128, sizeof(uuid.uuid128)); \ + uint8_t *p = &uuid.uuid128[BLE_BASE_UUID_OCTET_OFFSET]; \ + UINT16_TO_LESTREAM(p, uuid16); \ + uuid.type = BT_UUID128; \ + } while(0) + + +typedef void (*ble_client_connect_event_cb_t)(struct bt_conn *conn, uint8_t err, void *param); +typedef void (*ble_client_disconnect_event_cb_t)(struct bt_conn *conn, uint8_t reason, void *param); +typedef void (*ble_client_update_param_event_cb_t)(struct bt_conn *conn, + uint16_t interval, + uint16_t latency, + uint16_t timeout, + void *param); + + +#ifdef __cplusplus +extern "C" { +#endif + +void ble_client_init(ble_client_connect_event_cb_t connect_cb, void* connect_param, + ble_client_disconnect_event_cb_t disconnect_cb, void* disconnect_param, + ble_client_update_param_event_cb_t update_param_cb, void* update_param_param); +void ble_client_get_factory_config(bt_addr_le_t *bda, char *name); +void ble_gap_set_tx_power(int8_t tx_power); +BLE_STATUS_T errorno_to_ble_status(int err); + +void ble_client_get_mac_address(bt_addr_le_t *bda); + +#ifdef __cplusplus +} +#endif + + +#endif // _BLE_CLIENT_H_INCLUDED diff --git a/libraries/CurieBLE/src/BLECharacteristic.h b/libraries/CurieBLE/src/BLECharacteristic.h index 58274f67..c80edd08 100644 --- a/libraries/CurieBLE/src/BLECharacteristic.h +++ b/libraries/CurieBLE/src/BLECharacteristic.h @@ -329,7 +329,7 @@ class BLECharacteristic : public BLEAttribute { bt_gatt_attr_t *_attr_cccd; // For central device to subscribe the Notification/Indication - bt_gatt_subscribe_params_t _sub_params; + bt_gatt_subscribe_params_t _sub_params; bool _reading; bt_gatt_read_params_t _read_params; diff --git a/libraries/CurieBLE/src/BLEPeripheral.h b/libraries/CurieBLE/src/BLEPeripheral.h index 33b8bef7..25c53395 100644 --- a/libraries/CurieBLE/src/BLEPeripheral.h +++ b/libraries/CurieBLE/src/BLEPeripheral.h @@ -92,7 +92,7 @@ class BLEPeripheral{ /** * @brief Set advertising interval * - * @param[in] advertisingInterval Advertising Interval (N * 0.625) + * @param[in] advertisingInterval Advertising Interval in ms * * @return none * diff --git a/libraries/CurieBLE/src/BLEProfile.cpp b/libraries/CurieBLE/src/BLEProfile.cpp index 10f0c91e..d333132f 100644 --- a/libraries/CurieBLE/src/BLEProfile.cpp +++ b/libraries/CurieBLE/src/BLEProfile.cpp @@ -146,7 +146,7 @@ uint8_t profile_discover_process(bt_conn_t *conn, return peripheral->discover(attr); } -// Only for central +// Only for GATT Client uint8_t profile_read_rsp_process(bt_conn_t *conn, int err, bt_gatt_read_params_t *params, const void *data, diff --git a/system/libarc32_arduino101/bootcode/init.S b/system/libarc32_arduino101/bootcode/init.S index 28aab7d6..b566b488 100644 --- a/system/libarc32_arduino101/bootcode/init.S +++ b/system/libarc32_arduino101/bootcode/init.S @@ -61,9 +61,10 @@ _do_reset: .balign 4 _do_fault: _exit_halt: - /* Set halt flag */ - flag 0x01 + /* Set halt flag + flag 0x0 */ nop + j @_Fault nop nop /* loop forever */ diff --git a/system/libarc32_arduino101/drivers/ipc_uart_ns16550.c b/system/libarc32_arduino101/drivers/ipc_uart_ns16550.c index b2f6c08f..9b9880a6 100644 --- a/system/libarc32_arduino101/drivers/ipc_uart_ns16550.c +++ b/system/libarc32_arduino101/drivers/ipc_uart_ns16550.c @@ -155,7 +155,7 @@ static void ipc_uart_push_frame(uint16_t len, uint8_t *p_data) // "len %d, src %d, channel %d", ipc.rx_hdr.len, len, // ipc.rx_hdr.src_cpu_id, // ipc.rx_hdr.channel); - pr_debug(LOG_MODULE_IPC,"data[0 - 1]: %x-%x", p_data[0], p_data[1]); + //pr_debug(LOG_MODULE_IPC,"data[0 - 1]: %x-%x", p_data[0], p_data[1]); if ((ipc.rx_hdr.channel < IPC_UART_MAX_CHANNEL) && (ipc.channels[ipc.rx_hdr.channel].cb != NULL)) { @@ -208,7 +208,7 @@ void ipc_uart_isr() if (ipc.rx_size == 0) { if (ipc.rx_state == STATUS_RX_HDR) { - pr_error(0, "%s-%d", __FUNCTION__, ipc.rx_hdr.len); + //pr_error(0, "%s-%d", __FUNCTION__, ipc.rx_hdr.len); ipc.rx_ptr = balloc( ipc.rx_hdr.len, NULL); @@ -368,7 +368,7 @@ int ipc_uart_ns16550_send_pdu(void *handle, int len, void *p_data) { struct ipc_uart_channels *chan = (struct ipc_uart_channels *)handle; - pr_debug(LOG_MODULE_IPC, "%s: %d", __FUNCTION__, ipc.tx_state); + //pr_debug(LOG_MODULE_IPC, "%s: %d", __FUNCTION__, ipc.tx_state); if (ipc.tx_state == STATUS_TX_BUSY) { return IPC_UART_TX_BUSY; diff --git a/system/libarc32_arduino101/framework/src/cfw/service_api.c b/system/libarc32_arduino101/framework/src/cfw/service_api.c index 28b5bfe5..183167b6 100644 --- a/system/libarc32_arduino101/framework/src/cfw/service_api.c +++ b/system/libarc32_arduino101/framework/src/cfw/service_api.c @@ -111,6 +111,11 @@ struct cfw_message * cfw_alloc_evt_msg(service_t *svc, int msg_id, int size) { struct cfw_message * cfw_alloc_internal_msg(int msg_id, int size, void * priv) { struct cfw_message * evt = (struct cfw_message *) cfw_alloc_message(size, NULL); + if (NULL == evt) + { + return NULL; + } + CFW_MESSAGE_TYPE(evt) = TYPE_INT; CFW_MESSAGE_ID(evt) = msg_id; CFW_MESSAGE_LEN(evt) = size; diff --git a/system/libarc32_arduino101/framework/src/infra/port.c b/system/libarc32_arduino101/framework/src/infra/port.c index e9f7eef6..b719d1d6 100644 --- a/system/libarc32_arduino101/framework/src/infra/port.c +++ b/system/libarc32_arduino101/framework/src/infra/port.c @@ -121,6 +121,7 @@ static struct port * get_port(uint16_t port_id) if (port_id == 0 || port_id > MAX_PORTS) { pr_error(LOG_MODULE_MAIN, "Invalid port: %d", port_id); panic(-1); /*TODO: replace with an assert */ + return NULL; } return &ports[port_id - 1]; } @@ -317,7 +318,7 @@ uint16_t queue_process_message(T_QUEUE queue) uint16_t id = 0; queue_get_message(queue, &m, OS_NO_WAIT, &err); message = (struct message *) m; - if ( message != NULL && err == E_OS_OK) { + if ( message != NULL) { // && err == E_OS_OK dismiss Klock scan issue id = MESSAGE_ID(message); port_process_message(message); } diff --git a/system/libarc32_arduino101/framework/src/os/panic.c b/system/libarc32_arduino101/framework/src/os/panic.c index 9fe88ef9..214d5bb5 100644 --- a/system/libarc32_arduino101/framework/src/os/panic.c +++ b/system/libarc32_arduino101/framework/src/os/panic.c @@ -1,6 +1,9 @@ #include "os/os.h" +#include "infra/log.h" +#include "aux_regs.h" + extern void _do_fault(); void panic(int x) { @@ -14,3 +17,16 @@ void __assert_fail() } +void __attribute__((weak)) _Fault(void) +{ + uint32_t exc_addr = aux_reg_read(ARC_V2_EFA); + uint32_t ecr = aux_reg_read(ARC_V2_ECR); + + pr_error(0, "Exception vector: 0x%x, cause code: 0x%x, parameter 0x%x\n", + ARC_V2_ECR_VECTOR(ecr), + ARC_V2_ECR_CODE(ecr), + ARC_V2_ECR_PARAMETER(ecr)); + pr_error(0, "Address 0x%x\n", exc_addr); + while (1); +} + diff --git a/system/libarc32_arduino101/framework/src/services/ble/gatt.c b/system/libarc32_arduino101/framework/src/services/ble/gatt.c index d3ef4c09..44c82e5c 100644 --- a/system/libarc32_arduino101/framework/src/services/ble/gatt.c +++ b/system/libarc32_arduino101/framework/src/services/ble/gatt.c @@ -1040,6 +1040,7 @@ void on_nble_gattc_discover_rsp(const struct nble_gattc_discover_rsp *rsp, struct bt_gatt_attr *attr = NULL; if (rsp->type == BT_GATT_DISCOVER_PRIMARY) { + //BT_DBG("%s-%d", __FUNCTION__, __LINE__); const struct nble_gattc_primary *gattr = (void *)&data[i * sizeof(*gattr)]; if ((gattr->range.start_handle < params->start_handle) && @@ -1052,7 +1053,7 @@ void on_nble_gattc_discover_rsp(const struct nble_gattc_discover_rsp *rsp, goto complete; } svc_value.end_handle = gattr->range.end_handle; - svc_value.uuid = params->uuid; + svc_value.uuid = (struct bt_uuid*)(&(gattr->uuid));//params->uuid; attr = (&(struct bt_gatt_attr)BT_GATT_PRIMARY_SERVICE(&svc_value)); attr->handle = gattr->handle; last_handle = svc_value.end_handle; diff --git a/system/libarc32_arduino101/framework/src/services/ble_service/ble_service.c b/system/libarc32_arduino101/framework/src/services/ble_service/ble_service.c index 405c984a..d6efdf63 100644 --- a/system/libarc32_arduino101/framework/src/services/ble_service/ble_service.c +++ b/system/libarc32_arduino101/framework/src/services/ble_service/ble_service.c @@ -114,8 +114,11 @@ static void handle_msg_id_ble_rpc_callin(struct message *msg, void *priv) struct ble_rpc_callin *rpc = container_of(msg, struct ble_rpc_callin, msg); /* handle incoming message */ rpc_deserialize(rpc->p_data, rpc->len); + //pr_debug(LOG_MODULE_BLE, "%s-%d", __FUNCTION__, __LINE__); bfree(rpc->p_data); + //pr_debug(LOG_MODULE_BLE, "%s-%d", __FUNCTION__, __LINE__); message_free(msg); + //pr_debug(LOG_MODULE_BLE, "%s-%d", __FUNCTION__, __LINE__); } static void ble_set_bda_cb(int status, void *user_data) diff --git a/variants/arduino_101/linker_scripts/flash.ld b/variants/arduino_101/linker_scripts/flash.ld index e75a7059..fca2693c 100644 --- a/variants/arduino_101/linker_scripts/flash.ld +++ b/variants/arduino_101/linker_scripts/flash.ld @@ -47,12 +47,12 @@ MEMORY /* Define default stack size and FIRQ stack size. * See below stack section for __stack_start and __firq_stack_start */ -__stack_size = 2048; -__firq_stack_size = 512; +__stack_size = 3072; +__firq_stack_size = 1024; -/* Minimum heap size to allocate +/* Minimum heap size to allocate 8192 * Actual heap size might be bigger due to page size alignment */ -__HEAP_SIZE_MIN = 8192; +__HEAP_SIZE_MIN = 9216; /* This should be set to the page size used by the malloc implementation */ __PAGE_SIZE = 4096; From 0b47a913c87d9216efea5361f222add07900d0c0 Mon Sep 17 00:00:00 2001 From: lianggao Date: Tue, 18 Oct 2016 08:36:36 +0800 Subject: [PATCH 02/22] Add BLE library.properties file --- libraries/BLE/library.properties | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 libraries/BLE/library.properties diff --git a/libraries/BLE/library.properties b/libraries/BLE/library.properties new file mode 100644 index 00000000..ecdb692d --- /dev/null +++ b/libraries/BLE/library.properties @@ -0,0 +1,9 @@ +name=BLE +version=0.0 +author=Lianggao +maintainer=Lianggao +sentence=Library to manage the Bluetooth Low Energy module with Curie Core boards. +paragraph=Using this library, it is possible to use BLE features to communicate and interact with other devices like smartphones and tablets. This library enables multiple types of functionalities through a number of different classes. +category=Communication +url= +architectures=arc32 From 9d3bab2e3ef51653bcfdc73eecb630ea76acf231 Mon Sep 17 00:00:00 2001 From: lianggao Date: Fri, 21 Oct 2016 22:22:24 +0800 Subject: [PATCH 03/22] Implement central feature 1. Add the auto discover 2. Resolve the connect request was not scheduled issue 3. Implement the read/write request --- libraries/BLE/src/BLECallbacks.cpp | 17 +- libraries/BLE/src/BLECallbacks.h | 6 + libraries/BLE/src/BLECharacteristic.cpp | 731 +++++++++--------- libraries/BLE/src/BLECharacteristicImp.cpp | 319 ++++++-- libraries/BLE/src/BLECharacteristicImp.h | 34 +- libraries/BLE/src/BLEDescriptorImp.cpp | 15 +- libraries/BLE/src/BLEDescriptorImp.h | 4 + libraries/BLE/src/BLEDevice.cpp | 16 +- libraries/BLE/src/BLEDevice.h | 2 +- libraries/BLE/src/BLEDeviceManager.cpp | 57 +- libraries/BLE/src/BLEDeviceManager.h | 2 + libraries/BLE/src/BLEProfileManager.cpp | 272 +++++-- libraries/BLE/src/BLEProfileManager.h | 19 +- libraries/BLE/src/BLEService.cpp | 7 + libraries/BLE/src/BLEService.h | 8 + libraries/BLE/src/BLEServiceImp.cpp | 125 ++- libraries/BLE/src/BLEServiceImp.h | 7 +- libraries/BLE/src/BLEUtils.cpp | 338 ++++---- libraries/BLE/src/BLEUtils.h | 6 +- .../framework/src/services/ble/uuid.c | 2 +- .../src/services/ble_service/ble_service.c | 3 +- 21 files changed, 1303 insertions(+), 687 deletions(-) diff --git a/libraries/BLE/src/BLECallbacks.cpp b/libraries/BLE/src/BLECallbacks.cpp index db298071..bd606b10 100644 --- a/libraries/BLE/src/BLECallbacks.cpp +++ b/libraries/BLE/src/BLECallbacks.cpp @@ -111,8 +111,11 @@ uint8_t profile_discover_process(bt_conn_t *conn, const bt_gatt_attr_t *attr, bt_gatt_discover_params_t *params) { + uint8_t ret = BT_GATT_ITER_STOP; pr_debug(LOG_MODULE_BLE, "%s-%d", __FUNCTION__, __LINE__); - return BLEProfileManager::instance()->discoverResponseProc(conn, attr, params); + ret = BLEProfileManager::instance()->discoverResponseProc(conn, attr, params); + pr_debug(LOG_MODULE_BLE, "%s-%d", __FUNCTION__, __LINE__); + return ret; } // GATT Client only @@ -133,9 +136,21 @@ uint8_t profile_read_rsp_process(bt_conn_t *conn, chrc = BLEProfileManager::instance()->characteristic(bleDevice, params->single.handle); chrc->setValue((const unsigned char *)data, length); + pr_debug(LOG_MODULE_BLE, "%s-%d", __FUNCTION__, __LINE__); return BT_GATT_ITER_STOP; } +uint8_t profile_service_read_rsp_process(bt_conn_t *conn, + int err, + bt_gatt_read_params_t *params, + const void *data, + uint16_t length) +{ + uint8_t ret = BLEProfileManager::instance()->serviceReadRspProc(conn, err, params, data, length); + pr_debug(LOG_MODULE_BLE, "%s-%d:ret-%d", __FUNCTION__, __LINE__, ret); + return ret; +} + void bleConnectEventHandler(bt_conn_t *conn, diff --git a/libraries/BLE/src/BLECallbacks.h b/libraries/BLE/src/BLECallbacks.h index 76144392..868fc3a6 100644 --- a/libraries/BLE/src/BLECallbacks.h +++ b/libraries/BLE/src/BLECallbacks.h @@ -49,5 +49,11 @@ void ble_central_device_found(const bt_addr_le_t *addr, const uint8_t *ad, uint8_t len); +uint8_t profile_service_read_rsp_process(bt_conn_t *conn, + int err, + bt_gatt_read_params_t *params, + const void *data, + uint16_t length); + #endif diff --git a/libraries/BLE/src/BLECharacteristic.cpp b/libraries/BLE/src/BLECharacteristic.cpp index 88033529..071089fc 100644 --- a/libraries/BLE/src/BLECharacteristic.cpp +++ b/libraries/BLE/src/BLECharacteristic.cpp @@ -1,365 +1,366 @@ - -//#include "internal/ble_client.h" - -#include "BLECharacteristic.h" -#include "BLEProfileManager.h" -#include "BLEUtils.h" - -#include "BLECharacteristicImp.h" - -BLECharacteristic::BLECharacteristic(): - _bledev(), _internal(NULL), _properties(0), - _value_size(0), _value(NULL) -{ - memset(_uuid_cstr, 0, sizeof(_uuid_cstr)); -} - -BLECharacteristic::BLECharacteristic(const char* uuid, - unsigned char properties, - unsigned char valueSize): - _bledev(), _internal(NULL), _properties(properties), _value(NULL) -{ - bt_uuid_128 bt_uuid_tmp; - _value_size = valueSize > BLE_MAX_ATTR_LONGDATA_LEN ? BLE_MAX_ATTR_LONGDATA_LEN : valueSize; - BLEUtils::uuidString2BT(uuid, (bt_uuid_t *)&bt_uuid_tmp); - BLEUtils::uuidBT2String((const bt_uuid_t *)&bt_uuid_tmp, _uuid_cstr); - _bledev.setAddress(*BLEUtils::bleGetLoalAddress()); -} - -BLECharacteristic::BLECharacteristic(const char* uuid, - unsigned char properties, - const char* value): - BLECharacteristic(uuid, properties, strlen(value)) -{ - _setValue((const uint8_t*)value, strlen(value)); -} - -BLECharacteristic::BLECharacteristic(BLECharacteristicImp *characteristicImp, - const BLEDevice *bleDev): - _bledev(bleDev), _internal(characteristicImp), - _value(NULL) -{ - _properties = characteristicImp->properties(); - _value_size = characteristicImp->valueSize(); -} - -BLECharacteristic::~BLECharacteristic() -{ - if (_value) - { - bfree(_value); - _value = NULL; - } -} - -const char* BLECharacteristic::uuid() const -{ - return _uuid_cstr; -} - -unsigned char BLECharacteristic::properties() -{ - unsigned char property = 0; - BLECharacteristicImp *characteristicImp = getImplementation(); - if (NULL != characteristicImp) - { - property = characteristicImp->properties(); - } - return property; -} - -int BLECharacteristic::valueSize() //const -{ - int valuesize = 0; - BLECharacteristicImp *characteristicImp = getImplementation(); - if (NULL != characteristicImp) - { - valuesize = characteristicImp->valueSize(); - } - return valuesize; -} - -const byte* BLECharacteristic::value() //const -{ - const byte* value_temp = NULL; - BLECharacteristicImp *characteristicImp = getImplementation(); - if (NULL != characteristicImp) - { - value_temp = characteristicImp->value(); - } - return value_temp; -} - -int BLECharacteristic::valueLength() //const -{ - int valueLength = 0; - BLECharacteristicImp *characteristicImp = getImplementation(); - if (NULL != characteristicImp) - { - valueLength = characteristicImp->valueLength(); - } - return valueLength; -} - -BLECharacteristic::operator bool() const -{ - return (strlen(_uuid_cstr) > 3); -} - - -byte BLECharacteristic::operator[] (int offset) //const -{ - byte data = 0; - BLECharacteristicImp *characteristicImp = getImplementation(); - - if (NULL != characteristicImp) - { - data = (*characteristicImp)[offset]; - } - return data; -} - -bool BLECharacteristic::writeValue(const byte value[], int length) -{ - bool retVar = false; - BLECharacteristicImp *characteristicImp = getImplementation(); - - if (NULL != characteristicImp) - { - characteristicImp->setValue((const unsigned char *)value, (uint16_t)length); - retVar = true; - } - return retVar; -} - -bool BLECharacteristic::writeValue(const byte value[], int length, int offset) -{ - // TODO: Not support it now. - // Will add this feature. - return false; -} - -bool BLECharacteristic::writeValue(const char* value) -{ - return writeValue((const byte*)value, strlen(value)); -} - -bool BLECharacteristic::broadcast() -{ - // TODO: Need more information - return false; -} - -bool BLECharacteristic::written() -{ - bool retVar = false; - BLECharacteristicImp *characteristicImp = getImplementation(); - - if (NULL != characteristicImp) - { - retVar = characteristicImp->written(); - } - return retVar; -} - -bool BLECharacteristic::subscribed() -{ - bool retVar = false; - BLECharacteristicImp *characteristicImp = getImplementation(); - - if (NULL != characteristicImp) - { - retVar = characteristicImp->subscribed(); - } - return retVar; -} - -bool BLECharacteristic::canNotify() -{ - bool retVar = false; - BLECharacteristicImp *characteristicImp = getImplementation(); - - if (NULL != characteristicImp) - { - retVar = characteristicImp->canNotify(); - } - return retVar; -} - -bool BLECharacteristic::canIndicate() -{ - // TODO: Need more confirmation - return false; -} - -bool BLECharacteristic::canRead() -{ - // TODO: Need more confirmation - return false; -} -bool BLECharacteristic::canWrite() -{ - // TODO: Need more confirmation - return false; -} -bool BLECharacteristic::canSubscribe() -{ - // TODO: Need more confirmation - return false; -} -bool BLECharacteristic::canUnsubscribe() -{ - // TODO: Need more confirmation - return false; -} - -bool BLECharacteristic::read() -{ - bool retVar = false; - BLECharacteristicImp *characteristicImp = getImplementation(); - - if (NULL != characteristicImp) - { - retVar = characteristicImp->read(); - } - return retVar; -} - -bool BLECharacteristic::write(const unsigned char* value, int length) -{ - bool retVar = false; - BLECharacteristicImp *characteristicImp = getImplementation(); - - if (NULL != characteristicImp) - { - retVar = characteristicImp->write(value, (uint16_t)length); - } - return retVar; -} - -bool BLECharacteristic::subscribe() -{ - bool retVar = false; - BLECharacteristicImp *characteristicImp = getImplementation(); - - if (NULL != characteristicImp) - { - // TODO: Central feature. Peripheral first - //retVar = characteristicImp->s(); - } - return retVar; -} - -bool BLECharacteristic::unsubscribe() -{ - bool retVar = false; - BLECharacteristicImp *characteristicImp = getImplementation(); - - if (NULL != characteristicImp) - { - // TODO: Central feature - //retVar = characteristicImp->canNotify(); - } - return retVar; -} - -bool BLECharacteristic::valueUpdated() -{ - bool retVar = false; - BLECharacteristicImp *characteristicImp = getImplementation(); - - if (NULL != characteristicImp) - { - retVar = characteristicImp->valueUpdated(); - } - return retVar; -} - -int BLECharacteristic::addDescriptor(BLEDescriptor& descriptor) -{ - bool retVar = false; - BLECharacteristicImp *characteristicImp = getImplementation(); - - if (NULL != characteristicImp) - { - retVar = characteristicImp->addDescriptor(descriptor); - } - return retVar; -} - -int BLECharacteristic::descriptorCount() //const -{ - int count = 0; - BLECharacteristicImp *characteristicImp = getImplementation(); - - if (NULL != characteristicImp) - { - count = characteristicImp->descriptorCount(); - } - return count; -} - -bool BLECharacteristic::hasDescriptor(const char* uuid) const -{ - // TODO: Not support now - return false; -} -bool BLECharacteristic::hasDescriptor(const char* uuid, int index) const -{ - // TODO: Not support now - return false; -} -BLEDescriptor BLECharacteristic::descriptor(int index) const -{ - // TODO: Not support now - return BLEDescriptor(); -} -BLEDescriptor BLECharacteristic::descriptor(const char * uuid) const -{ - // TODO: Not support now - return BLEDescriptor(); -} -BLEDescriptor BLECharacteristic::descriptor(const char * uuid, int index) const -{ - // TODO: Not support now - return BLEDescriptor(); -} -void BLECharacteristic::setEventHandler(BLECharacteristicEvent event, - BLECharacteristicEventHandler eventHandler) -{} - - -void -BLECharacteristic::_setValue(const uint8_t value[], uint16_t length) -{ - if (length > _value_size) { - length = _value_size; - } - - if (NULL == _value) - { - // Allocate the buffer for characteristic - _value = (unsigned char*)balloc(_value_size, NULL);//malloc(_value_size) - } - if (NULL == _value) - { - return; - } - memcpy(_value, value, length); -} - -BLECharacteristicImp* BLECharacteristic::getImplementation() -{ - if (NULL == _internal) - { - _internal = BLEProfileManager::instance()->characteristic(_bledev, (const char*)_uuid_cstr); - } - return _internal; -} - -void BLECharacteristic::setBLECharacteristicImp(BLECharacteristicImp *characteristicImp) -{ - _internal = characteristicImp; -} - - + +//#include "internal/ble_client.h" + +#include "BLECharacteristic.h" +#include "BLEProfileManager.h" +#include "BLEUtils.h" + +#include "BLECharacteristicImp.h" + +BLECharacteristic::BLECharacteristic(): + _bledev(), _internal(NULL), _properties(0), + _value_size(0), _value(NULL) +{ + memset(_uuid_cstr, 0, sizeof(_uuid_cstr)); +} + +BLECharacteristic::BLECharacteristic(const char* uuid, + unsigned char properties, + unsigned char valueSize): + _bledev(), _internal(NULL), _properties(properties), _value(NULL) +{ + bt_uuid_128 bt_uuid_tmp; + _value_size = valueSize > BLE_MAX_ATTR_LONGDATA_LEN ? BLE_MAX_ATTR_LONGDATA_LEN : valueSize; + BLEUtils::uuidString2BT(uuid, (bt_uuid_t *)&bt_uuid_tmp); + BLEUtils::uuidBT2String((const bt_uuid_t *)&bt_uuid_tmp, _uuid_cstr); + _bledev.setAddress(*BLEUtils::bleGetLoalAddress()); +} + +BLECharacteristic::BLECharacteristic(const char* uuid, + unsigned char properties, + const char* value): + BLECharacteristic(uuid, properties, strlen(value)) +{ + _setValue((const uint8_t*)value, strlen(value)); +} + +BLECharacteristic::BLECharacteristic(BLECharacteristicImp *characteristicImp, + const BLEDevice *bleDev): + _bledev(bleDev), _internal(characteristicImp), + _value(NULL) +{ + BLEUtils::uuidBT2String(characteristicImp->bt_uuid(), _uuid_cstr); + _properties = characteristicImp->properties(); + _value_size = characteristicImp->valueSize(); +} + +BLECharacteristic::~BLECharacteristic() +{ + if (_value) + { + bfree(_value); + _value = NULL; + } +} + +const char* BLECharacteristic::uuid() const +{ + return _uuid_cstr; +} + +unsigned char BLECharacteristic::properties() +{ + unsigned char property = 0; + BLECharacteristicImp *characteristicImp = getImplementation(); + if (NULL != characteristicImp) + { + property = characteristicImp->properties(); + } + return property; +} + +int BLECharacteristic::valueSize() //const +{ + int valuesize = 0; + BLECharacteristicImp *characteristicImp = getImplementation(); + if (NULL != characteristicImp) + { + valuesize = characteristicImp->valueSize(); + } + return valuesize; +} + +const byte* BLECharacteristic::value() //const +{ + const byte* value_temp = NULL; + BLECharacteristicImp *characteristicImp = getImplementation(); + if (NULL != characteristicImp) + { + value_temp = characteristicImp->value(); + } + return value_temp; +} + +int BLECharacteristic::valueLength() //const +{ + int valueLength = 0; + BLECharacteristicImp *characteristicImp = getImplementation(); + if (NULL != characteristicImp) + { + valueLength = characteristicImp->valueLength(); + } + return valueLength; +} + +BLECharacteristic::operator bool() const +{ + return (strlen(_uuid_cstr) > 3); +} + + +byte BLECharacteristic::operator[] (int offset) //const +{ + byte data = 0; + BLECharacteristicImp *characteristicImp = getImplementation(); + + if (NULL != characteristicImp) + { + data = (*characteristicImp)[offset]; + } + return data; +} + +bool BLECharacteristic::writeValue(const byte value[], int length) +{ + bool retVar = false; + BLECharacteristicImp *characteristicImp = getImplementation(); + + if (NULL != characteristicImp) + { + characteristicImp->setValue((const unsigned char *)value, (uint16_t)length); + retVar = true; + } + return retVar; +} + +bool BLECharacteristic::writeValue(const byte value[], int length, int offset) +{ + // TODO: Not support it now. + // Will add this feature. + return false; +} + +bool BLECharacteristic::writeValue(const char* value) +{ + return writeValue((const byte*)value, strlen(value)); +} + +bool BLECharacteristic::broadcast() +{ + // TODO: Need more information + return false; +} + +bool BLECharacteristic::written() +{ + bool retVar = false; + BLECharacteristicImp *characteristicImp = getImplementation(); + + if (NULL != characteristicImp) + { + retVar = characteristicImp->written(); + } + return retVar; +} + +bool BLECharacteristic::subscribed() +{ + bool retVar = false; + BLECharacteristicImp *characteristicImp = getImplementation(); + + if (NULL != characteristicImp) + { + retVar = characteristicImp->subscribed(); + } + return retVar; +} + +bool BLECharacteristic::canNotify() +{ + bool retVar = false; + BLECharacteristicImp *characteristicImp = getImplementation(); + + if (NULL != characteristicImp) + { + retVar = characteristicImp->canNotify(); + } + return retVar; +} + +bool BLECharacteristic::canIndicate() +{ + // TODO: Need more confirmation + return false; +} + +bool BLECharacteristic::canRead() +{ + // TODO: Need more confirmation + return false; +} +bool BLECharacteristic::canWrite() +{ + // TODO: Need more confirmation + return false; +} +bool BLECharacteristic::canSubscribe() +{ + // TODO: Need more confirmation + return false; +} +bool BLECharacteristic::canUnsubscribe() +{ + // TODO: Need more confirmation + return false; +} + +bool BLECharacteristic::read() +{ + bool retVar = false; + BLECharacteristicImp *characteristicImp = getImplementation(); + + if (NULL != characteristicImp) + { + retVar = characteristicImp->read(); + } + return retVar; +} + +bool BLECharacteristic::write(const unsigned char* value, int length) +{ + bool retVar = false; + BLECharacteristicImp *characteristicImp = getImplementation(); + + if (NULL != characteristicImp) + { + retVar = characteristicImp->write(value, (uint16_t)length); + } + return retVar; +} + +bool BLECharacteristic::subscribe() +{ + bool retVar = false; + BLECharacteristicImp *characteristicImp = getImplementation(); + + if (NULL != characteristicImp) + { + // TODO: Central feature. Peripheral first + //retVar = characteristicImp->s(); + } + return retVar; +} + +bool BLECharacteristic::unsubscribe() +{ + bool retVar = false; + BLECharacteristicImp *characteristicImp = getImplementation(); + + if (NULL != characteristicImp) + { + // TODO: Central feature + //retVar = characteristicImp->canNotify(); + } + return retVar; +} + +bool BLECharacteristic::valueUpdated() +{ + bool retVar = false; + BLECharacteristicImp *characteristicImp = getImplementation(); + + if (NULL != characteristicImp) + { + retVar = characteristicImp->valueUpdated(); + } + return retVar; +} + +int BLECharacteristic::addDescriptor(BLEDescriptor& descriptor) +{ + bool retVar = false; + BLECharacteristicImp *characteristicImp = getImplementation(); + + if (NULL != characteristicImp) + { + retVar = characteristicImp->addDescriptor(descriptor); + } + return retVar; +} + +int BLECharacteristic::descriptorCount() //const +{ + int count = 0; + BLECharacteristicImp *characteristicImp = getImplementation(); + + if (NULL != characteristicImp) + { + count = characteristicImp->descriptorCount(); + } + return count; +} + +bool BLECharacteristic::hasDescriptor(const char* uuid) const +{ + // TODO: Not support now + return false; +} +bool BLECharacteristic::hasDescriptor(const char* uuid, int index) const +{ + // TODO: Not support now + return false; +} +BLEDescriptor BLECharacteristic::descriptor(int index) const +{ + // TODO: Not support now + return BLEDescriptor(); +} +BLEDescriptor BLECharacteristic::descriptor(const char * uuid) const +{ + // TODO: Not support now + return BLEDescriptor(); +} +BLEDescriptor BLECharacteristic::descriptor(const char * uuid, int index) const +{ + // TODO: Not support now + return BLEDescriptor(); +} +void BLECharacteristic::setEventHandler(BLECharacteristicEvent event, + BLECharacteristicEventHandler eventHandler) +{} + + +void +BLECharacteristic::_setValue(const uint8_t value[], uint16_t length) +{ + if (length > _value_size) { + length = _value_size; + } + + if (NULL == _value) + { + // Allocate the buffer for characteristic + _value = (unsigned char*)balloc(_value_size, NULL);//malloc(_value_size) + } + if (NULL == _value) + { + return; + } + memcpy(_value, value, length); +} + +BLECharacteristicImp* BLECharacteristic::getImplementation() +{ + if (NULL == _internal) + { + _internal = BLEProfileManager::instance()->characteristic(_bledev, (const char*)_uuid_cstr); + } + return _internal; +} + +void BLECharacteristic::setBLECharacteristicImp(BLECharacteristicImp *characteristicImp) +{ + _internal = characteristicImp; +} + + diff --git a/libraries/BLE/src/BLECharacteristicImp.cpp b/libraries/BLE/src/BLECharacteristicImp.cpp index 34758015..fee461f4 100644 --- a/libraries/BLE/src/BLECharacteristicImp.cpp +++ b/libraries/BLE/src/BLECharacteristicImp.cpp @@ -26,13 +26,78 @@ bt_uuid_16_t BLECharacteristicImp::_gatt_chrc_uuid = {BT_UUID_TYPE_16, BT_UUID_GATT_CHRC_VAL}; bt_uuid_16_t BLECharacteristicImp::_gatt_ccc_uuid = {BT_UUID_TYPE_16, BT_UUID_GATT_CCC_VAL}; +BLECharacteristicImp::BLECharacteristicImp(const bt_uuid_t* uuid, + unsigned char properties, + uint16_t handle, + const BLEDevice& bledevice): + BLEAttribute(uuid, BLETypeCharacteristic), + _value_length(0), + _value_buffer(NULL), + _value_updated(false), + _value_handle(handle), + _cccd_handle(0), + _attr_chrc_value(NULL), + _attr_cccd(NULL), + _ble_device() +{ + _value_size = BLE_MAX_ATTR_DATA_LEN;// Set as MAX value. TODO: long read/write need to twist + _value = (unsigned char*)balloc(_value_size, NULL); + if (_value_size > BLE_MAX_ATTR_DATA_LEN) + { + _value_buffer = (unsigned char*)balloc(_value_size, NULL); + } + + memset(_value, 0, _value_size); + + memset(&_ccc_cfg, 0, sizeof(_ccc_cfg)); + memset(&_ccc_value, 0, sizeof(_ccc_value)); + memset(&_gatt_chrc, 0, sizeof(_gatt_chrc)); + memset(&_sub_params, 0, sizeof(_sub_params)); + memset(&_discover_params, 0, sizeof(_discover_params)); + + _ccc_value.cfg = &_ccc_cfg; + _ccc_value.cfg_len = 1; + if (BLERead & properties) + { + _gatt_chrc.properties |= BT_GATT_CHRC_READ; + } + if (BLEWrite & properties) + { + _gatt_chrc.properties |= BT_GATT_CHRC_WRITE; + } + if (BLEWriteWithoutResponse & properties) + { + _gatt_chrc.properties |= BT_GATT_CHRC_WRITE_WITHOUT_RESP; + } + if (BLENotify & properties) + { + _gatt_chrc.properties |= BT_GATT_CHRC_NOTIFY; + _sub_params.value |= BT_GATT_CCC_NOTIFY; + } + if (BLEIndicate & properties) + { + _gatt_chrc.properties |= BT_GATT_CHRC_INDICATE; + _sub_params.value |= BT_GATT_CCC_INDICATE; + } + _gatt_chrc.uuid = (bt_uuid_t*)this->bt_uuid();//&_characteristic_uuid;//this->uuid(); + memset(_event_handlers, 0, sizeof(_event_handlers)); + + _sub_params.notify = profile_notify_process; + + // Update BLE device object + _ble_device.setAddress(*bledevice.bt_le_address()); + + memset(&_descriptors_header, 0, sizeof(_descriptors_header)); +} + BLECharacteristicImp::BLECharacteristicImp(BLECharacteristic& characteristic, const BLEDevice& bledevice): BLEAttribute(characteristic.uuid(), BLETypeCharacteristic), _value_length(0), _value_buffer(NULL), _value_updated(false), - _attr_chrc_declaration(NULL), + _value_handle(0), + _cccd_handle(0), _attr_chrc_value(NULL), _attr_cccd(NULL), _ble_device() @@ -49,6 +114,7 @@ BLECharacteristicImp::BLECharacteristicImp(BLECharacteristic& characteristic, memset(&_ccc_value, 0, sizeof(_ccc_value)); memset(&_gatt_chrc, 0, sizeof(_gatt_chrc)); memset(&_sub_params, 0, sizeof(_sub_params)); + memset(&_discover_params, 0, sizeof(_discover_params)); _ccc_value.cfg = &_ccc_cfg; _ccc_value.cfg_len = 1; @@ -217,33 +283,43 @@ BLECharacteristicImp::setEventHandler(BLECharacteristicEvent event, BLECharacter interrupts(); } +void +BLECharacteristicImp::setHandle(uint16_t handle) +{ + // GATT client + _value_handle = handle; +} + +void +BLECharacteristicImp::setCCCDHandle(uint16_t handle) +{ + // GATT client + _cccd_handle = handle; +} + uint16_t BLECharacteristicImp::valueHandle() { uint16_t handle = 0; if (NULL != _attr_chrc_value) { + //GATT server handle = _attr_chrc_value->handle; } - - return handle; -} - -uint16_t -BLECharacteristicImp::cccdHandle() -{ - uint16_t handle = 0; - if (NULL != _attr_cccd) + else { - handle = _attr_cccd->handle; + // GATT client + handle = _value_handle; } + return handle; } void BLECharacteristicImp::_setValue(const uint8_t value[], uint16_t length) { - if (length > _value_size) { + if (length > _value_size) + { length = _value_size; } @@ -286,58 +362,6 @@ bt_uuid_t* BLECharacteristicImp::getClientCharacteristicConfigUuid(void) return (bt_uuid_t*) &_gatt_ccc_uuid; } -#if 0 -void BLECharacteristicImp::discover(bt_gatt_discover_params_t *params) -{ - params->type = BT_GATT_DISCOVER_CHARACTERISTIC; - params->uuid = this->uuid(); - // Start discovering - _discoverying = true; - // Re-Init the read/write parameter - _reading = false; -} - - -void BLECharacteristicImp::discover(const bt_gatt_attr_t *attr, - bt_gatt_discover_params_t *params) -{ - if (!attr) - { - // Discovery complete - _discoverying = false; - return; - } - - // Chracteristic Char - if (params->uuid == this->uuid()) - { - // Set Discover CCCD parameter - params->start_handle = attr->handle + 2; - if (subscribed()) - { - // Include CCCD - params->type = BT_GATT_DISCOVER_DESCRIPTOR; - params->uuid = this->getClientCharacteristicConfigUuid(); - } - else - { - // Complete the discover - _discoverying = false; - } - } - else if (params->uuid == this->getClientCharacteristicConfigUuid()) - { - params->start_handle = attr->handle + 1; - _discoverying = false; - } -} -#endif - -bt_gatt_subscribe_params_t *BLECharacteristicImp::getSubscribeParams() -{ - return &_sub_params; -} - bool BLECharacteristicImp::read() { int retval = 0; @@ -513,7 +537,13 @@ int BLECharacteristicImp::updateProfile(bt_gatt_attr_t *attr_start, int& index) int BLECharacteristicImp::addDescriptor(BLEDescriptor& descriptor) { - BLEDescriptorImp* descriptorImp = new BLEDescriptorImp(_ble_device, descriptor); + BLEDescriptorImp* descriptorImp = descrptor(descriptor.uuid()); + if (NULL != descriptorImp) + { + return BLE_STATUS_SUCCESS; + } + + descriptorImp = new BLEDescriptorImp(_ble_device, descriptor); pr_debug(LOG_MODULE_BLE, "%s-%d",__FUNCTION__, __LINE__); if (NULL == descriptorImp) { @@ -532,6 +562,61 @@ int BLECharacteristicImp::addDescriptor(BLEDescriptor& descriptor) return BLE_STATUS_SUCCESS; } +int BLECharacteristicImp::addDescriptor(const bt_uuid_t* uuid, + uint16_t handle) +{ + BLEDescriptorImp* descriptorImp = descrptor(uuid); + if (NULL != descriptorImp) + { + return BLE_STATUS_SUCCESS; + } + + descriptorImp = new BLEDescriptorImp(uuid, handle, _ble_device); + pr_debug(LOG_MODULE_BLE, "%s-%d",__FUNCTION__, __LINE__); + if (NULL == descriptorImp) + { + return BLE_STATUS_NO_MEMORY; + } + + BLEDescriptorNodePtr node = link_node_create(descriptorImp); + if (NULL == node) + { + delete[] descriptorImp; + return BLE_STATUS_NO_MEMORY; + } + link_node_insert_last(&_descriptors_header, node); + pr_debug(LOG_MODULE_BLE, "%s-%d",__FUNCTION__, __LINE__); + return BLE_STATUS_SUCCESS; +} + +BLEDescriptorImp* BLECharacteristicImp::descrptor(const bt_uuid_t* uuid) +{ + BLEDescriptorImp* descriptorImp = NULL; + BLEDescriptorNodePtr node = link_node_get_first(&_descriptors_header); + + while (NULL != node) + { + descriptorImp = node->value; + if (true == descriptorImp->compareUuid(uuid)) + { + break; + } + } + + if (NULL == node) + { + descriptorImp = NULL; + } + return descriptorImp; +} + +BLEDescriptorImp* BLECharacteristicImp::descrptor(const char* uuid) +{ + bt_uuid_128_t uuid_tmp; + BLEUtils::uuidString2BT(uuid, (bt_uuid_t *)&uuid_tmp); + return descrptor((const bt_uuid_t *)&uuid_tmp); +} + void BLECharacteristicImp::releaseDescriptors() { BLEDescriptorNodePtr node = link_node_get_first(&_descriptors_header); @@ -557,4 +642,108 @@ int BLECharacteristicImp::descriptorCount() const return counter; } +bool BLECharacteristicImp::discoverAttributes(BLEDevice* device) +{ + + int err; + bt_conn_t* conn; + bt_gatt_discover_params_t* temp = NULL; + const bt_uuid_t* service_uuid = bt_uuid(); + + if (service_uuid->type == BT_UUID_TYPE_16) + { + uint16_t uuid_tmp = ((bt_uuid_16_t*)service_uuid)->val; + if (BT_UUID_GAP_VAL == uuid_tmp || + BT_UUID_GATT_VAL == uuid_tmp) + { + return false; + } + } + + conn = bt_conn_lookup_addr_le(device->bt_le_address()); + if (NULL == conn) + { + // Link lost + pr_debug(LOG_MODULE_BLE, "Can't find connection\n"); + return false; + } + temp = &_discover_params; + temp->start_handle = _value_handle + 1; + temp->end_handle = _value_handle + 20; // TODO: the max descriptor is not more than 20 + temp->uuid = NULL; + temp->type = BT_GATT_DISCOVER_DESCRIPTOR; + temp->func = profile_discover_process; + pr_debug(LOG_MODULE_BLE, "%s-%d-charc",__FUNCTION__, __LINE__); + err = bt_gatt_discover(conn, temp); + bt_conn_unref(conn); + if (err) + { + pr_debug(LOG_MODULE_BLE, "Discover failed(err %d)\n", err); + return false; + } + return true; +} + +bool BLECharacteristicImp::isClientCharacteristicConfigurationDescriptor(const bt_uuid_t* uuid) +{ + bool ret = false; + uint16_t cccd_uuid = BT_UUID_GATT_CCC_VAL; + if (uuid->type == BT_UUID_TYPE_16) + { + if (0 == memcmp(&BT_UUID_16(uuid)->val, &cccd_uuid, sizeof(uint16_t))) + { + ret = true; + } + } + return ret; +} + +uint8_t BLECharacteristicImp::discoverResponseProc(bt_conn_t *conn, + const bt_gatt_attr_t *attr, + bt_gatt_discover_params_t *params) +{ + const bt_addr_le_t* dst_addr = bt_conn_get_dst(conn); + BLEDevice device(dst_addr); + uint8_t retVal = BT_GATT_ITER_STOP; + + pr_debug(LOG_MODULE_BLE, "%s-%d: type-%d", __FUNCTION__, __LINE__, params->type); + + // Process the service + switch (params->type) + { + case BT_GATT_DISCOVER_DESCRIPTOR: + { + if (NULL != attr) + { + const bt_uuid_t* desc_uuid = attr->uuid; + uint16_t desc_handle = attr->handle; + pr_debug(LOG_MODULE_BLE, "%s-%d:handle-%d:%d", __FUNCTION__, __LINE__,attr->handle, desc_handle); + if (isClientCharacteristicConfigurationDescriptor(desc_uuid)) + { + setCCCDHandle(desc_handle); + } + else + { + int retval = (int)addDescriptor(desc_uuid, + desc_handle); + + if (BLE_STATUS_SUCCESS != retval) + { + pr_error(LOG_MODULE_BLE, "%s-%d: Error-%d", + __FUNCTION__, __LINE__, retval); + } + + } + retVal = BT_GATT_ITER_CONTINUE; + } + break; + } + default: + { + break; + } + } + return retVal; +} + diff --git a/libraries/BLE/src/BLECharacteristicImp.h b/libraries/BLE/src/BLECharacteristicImp.h index be95fe59..c2ab8f22 100644 --- a/libraries/BLE/src/BLECharacteristicImp.h +++ b/libraries/BLE/src/BLECharacteristicImp.h @@ -51,6 +51,8 @@ class BLECharacteristicImp: public BLEAttribute{ * @note none */ int addDescriptor(BLEDescriptor& descriptor); + int addDescriptor(const bt_uuid_t* uuid, + uint16_t handle); void releaseDescriptors(); @@ -160,7 +162,16 @@ class BLECharacteristicImp: public BLEAttribute{ bool write(const unsigned char value[], uint16_t length); + void setCCCDHandle(uint16_t handle); + void setHandle(uint16_t handle); int descriptorCount() const; + uint8_t discoverResponseProc(bt_conn_t *conn, + const bt_gatt_attr_t *attr, + bt_gatt_discover_params_t *params); + bool discoverAttributes(BLEDevice* device); + + BLEDescriptorImp* descrptor(const bt_uuid_t* uuid); + BLEDescriptorImp* descrptor(const char* uuid); protected: friend class BLEProfileManager; @@ -172,6 +183,10 @@ class BLECharacteristicImp: public BLEAttribute{ * @param[in] bledevice The device that has this characteristic */ BLECharacteristicImp(BLECharacteristic& characteristic, const BLEDevice& bledevice); + BLECharacteristicImp(const bt_uuid_t* uuid, + unsigned char properties, + uint16_t handle, + const BLEDevice& bledevice); friend int profile_longflush_process(struct bt_conn *conn, const struct bt_gatt_attr *attr, @@ -215,13 +230,9 @@ class BLECharacteristicImp: public BLEAttribute{ * @note Only for peripheral */ uint16_t cccdHandle(void); - - void setUserDescription(BLEDescriptor *descriptor); - void setPresentationFormat(BLEDescriptor *descriptor); - - _bt_gatt_ccc_t* getCccCfg(void); - bt_gatt_chrc_t* getCharacteristicAttValue(void); + inline _bt_gatt_ccc_t* getCccCfg(void); + inline bt_gatt_chrc_t* getCharacteristicAttValue(void); static bt_uuid_t* getCharacteristicAttributeUuid(void); static bt_uuid_t* getClientCharacteristicConfigUuid(void); @@ -274,6 +285,7 @@ class BLECharacteristicImp: public BLEAttribute{ private: void _setValue(const uint8_t value[], uint16_t length); + bool isClientCharacteristicConfigurationDescriptor(const bt_uuid_t* uuid); private: // Those 2 UUIDs are used for define the characteristic. @@ -286,14 +298,16 @@ class BLECharacteristicImp: public BLEAttribute{ unsigned char* _value_buffer; bool _value_updated; - uint16_t _value_handle; + uint16_t _value_handle; // GATT client only + uint16_t _cccd_handle; // GATT client only + bt_gatt_discover_params_t _discover_params;// GATT client only + bt_gatt_ccc_cfg_t _ccc_cfg; _bt_gatt_ccc_t _ccc_value; bt_gatt_chrc_t _gatt_chrc; - bt_gatt_attr_t *_attr_chrc_declaration; - bt_gatt_attr_t *_attr_chrc_value; - bt_gatt_attr_t *_attr_cccd; + bt_gatt_attr_t *_attr_chrc_value; // GATT server only + bt_gatt_attr_t *_attr_cccd; // GATT server only // For GATT Client to subscribe the Notification/Indication bt_gatt_subscribe_params_t _sub_params; diff --git a/libraries/BLE/src/BLEDescriptorImp.cpp b/libraries/BLE/src/BLEDescriptorImp.cpp index 9cdef95c..b0e673b4 100644 --- a/libraries/BLE/src/BLEDescriptorImp.cpp +++ b/libraries/BLE/src/BLEDescriptorImp.cpp @@ -25,7 +25,8 @@ #include "BLECallbacks.h" BLEDescriptorImp::BLEDescriptorImp(BLEDevice& bledevice, BLEDescriptor &descriptor): - BLEAttribute(descriptor.uuid(), BLETypeDescriptor) + BLEAttribute(descriptor.uuid(), BLETypeDescriptor), + _value_handle(0) { _value_length = descriptor.valueLength(); _value = (unsigned char*)balloc(_value_length, NULL); @@ -33,6 +34,18 @@ BLEDescriptorImp::BLEDescriptorImp(BLEDevice& bledevice, BLEDescriptor &descript memcpy(_value, descriptor.value(), _value_length); } +BLEDescriptorImp::BLEDescriptorImp(const bt_uuid_t* uuid, + uint16_t handle, + BLEDevice& bledevice): + BLEAttribute(uuid, BLETypeDescriptor), + _value_handle(handle) +{ + _value_length = BLE_MAX_ATTR_DATA_LEN; + _value = (unsigned char*)balloc(_value_length, NULL); + + memset(_value, 0, _value_length); +} + BLEDescriptorImp::~BLEDescriptorImp() { if (_value) { bfree(_value); diff --git a/libraries/BLE/src/BLEDescriptorImp.h b/libraries/BLE/src/BLEDescriptorImp.h index 5ea27caf..8d3a9f1c 100644 --- a/libraries/BLE/src/BLEDescriptorImp.h +++ b/libraries/BLE/src/BLEDescriptorImp.h @@ -35,6 +35,9 @@ class BLEDescriptorImp: public BLEAttribute{ * @param[in] valueLength Data length required for descriptor value (<= BLE_MAX_ATTR_DATA_LEN) */ BLEDescriptorImp(BLEDevice& bledevice, BLEDescriptor &descriptor); + BLEDescriptorImp(const bt_uuid_t* uuid, + uint16_t handle, + BLEDevice& bledevice); virtual ~BLEDescriptorImp(); @@ -61,6 +64,7 @@ class BLEDescriptorImp: public BLEAttribute{ private: unsigned short _value_length; + unsigned short _value_handle; unsigned char* _value; bt_uuid_128 _descriptor_uuid; diff --git a/libraries/BLE/src/BLEDevice.cpp b/libraries/BLE/src/BLEDevice.cpp index ece51365..65c28eae 100644 --- a/libraries/BLE/src/BLEDevice.cpp +++ b/libraries/BLE/src/BLEDevice.cpp @@ -186,14 +186,14 @@ BLEDevice::operator bool() const return BLEUtils::macAddressValid(_bt_addr); } -BLEDevice& BLEDevice::operator=(const BLEDevice& device) -{ - if (*this != device) - { - memcpy(&(this->_bt_addr), &(device._bt_addr), sizeof (bt_addr_le_t)); - } - return *this; -} +//BLEDevice& BLEDevice::operator=(const BLEDevice& device) +//{ +// if (*this != device) +// { +// memcpy(&(this->_bt_addr), &(device._bt_addr), sizeof (bt_addr_le_t)); +// } +// return *this; +//} bool BLEDevice::operator==(const BLEDevice& device) const { diff --git a/libraries/BLE/src/BLEDevice.h b/libraries/BLE/src/BLEDevice.h index 8beebf64..4d6b8933 100644 --- a/libraries/BLE/src/BLEDevice.h +++ b/libraries/BLE/src/BLEDevice.h @@ -344,7 +344,7 @@ class BLEDevice operator bool() const; bool operator==(const BLEDevice& device) const; bool operator!=(const BLEDevice& device) const; - BLEDevice& operator=(const BLEDevice& device); + //BLEDevice& operator=(const BLEDevice& device); // central mode void startScanning(String name); // start scanning for peripherals void startScanningWithDuplicates(); // start scanning for peripherals, and report all duplicates diff --git a/libraries/BLE/src/BLEDeviceManager.cpp b/libraries/BLE/src/BLEDeviceManager.cpp index 72db8c45..bdeb4b49 100644 --- a/libraries/BLE/src/BLEDeviceManager.cpp +++ b/libraries/BLE/src/BLEDeviceManager.cpp @@ -43,6 +43,7 @@ BLEDeviceManager::BLEDeviceManager(): _local_ble(NULL) { memset(&_local_bda, 0, sizeof(_local_bda)); + memset(&_wait_for_connect_peripheral, 0, sizeof(_wait_for_connect_peripheral)); memset(&_service_uuid, 0, sizeof(_service_uuid)); memset(&_service_solicit_uuid, 0, sizeof(_service_solicit_uuid)); @@ -106,17 +107,18 @@ void BLEDeviceManager::end() bool BLEDeviceManager::connected(BLEDevice *device) { bt_conn_t* conn = bt_conn_lookup_addr_le(device->bt_le_address()); + bool retval = false; //pr_debug(LOG_MODULE_BLE, "%s-%d: add-%s", __FUNCTION__, __LINE__, device->address().c_str()); if (NULL != conn) { //pr_debug(LOG_MODULE_BLE, "%s-%d: state-%d", __FUNCTION__, __LINE__,conn->state); if (conn->state == BT_CONN_CONNECTED) { - return true; + retval = true; } bt_conn_unref(conn); } - return false; + return retval; } bool BLEDeviceManager::disconnect(BLEDevice *device) @@ -495,6 +497,33 @@ int BLEDeviceManager::rssi() const } bool BLEDeviceManager::connect(BLEDevice &device) +{ + // + uint64_t timestamp = millis(); + uint64_t timestampcur = timestamp; + bool ret = true; + bt_addr_le_copy(&_wait_for_connect_peripheral, device.bt_le_address()); + startScanning(); + + pr_debug(LOG_MODULE_BLE, "%s-%d", __FUNCTION__, __LINE__); + // Wait for the connection + while (ret && (true == BLEUtils::macAddressValid(_wait_for_connect_peripheral))) + { + timestampcur = millis(); + // TODO: dismiss the magic number + ret = (timestampcur - timestamp < 3000); // Time out + } + + pr_debug(LOG_MODULE_BLE, "%s-%d", __FUNCTION__, __LINE__); + + if (ret == false) + { + memset(&_wait_for_connect_peripheral, 0, sizeof(_wait_for_connect_peripheral)); + } + return ret; +} + +bool BLEDeviceManager::connectToDevice(BLEDevice &device) { bt_addr_le_t* temp = NULL; bt_addr_le_t* unused = NULL; @@ -572,6 +601,7 @@ void BLEDeviceManager::handleConnectEvent(bt_conn_t *conn, uint8_t err) } else { + memset(&_wait_for_connect_peripheral, 0, sizeof(_wait_for_connect_peripheral)); // Peripheral has established the connection with this Central device BLEProfileManager::instance()->handleConnectedEvent(bt_conn_get_dst(conn)); } @@ -693,7 +723,7 @@ BLEDevice BLEDeviceManager::available() if (true == BLEUtils::macAddressValid(*temp)) { tempdevice.setAddress(*temp); - //pr_debug(LOG_MODULE_BLE, "%s-%d:Con addr-%s", __FUNCTION__, __LINE__, BLEUtils::macAddressBT2String(*temp).c_str()); + pr_debug(LOG_MODULE_BLE, "%s-%d:Con addr-%s", __FUNCTION__, __LINE__, BLEUtils::macAddressBT2String(*temp).c_str()); _peer_adv_mill[index] -= 2000; // Set it as expired } } @@ -775,17 +805,24 @@ void BLEDeviceManager::handleDeviceFound(const bt_addr_le_t *addr, if (true == advertiseDataProc(data[1], &data[2], len - 1)) { - // The critical is accepted - // Find the oldest and expired buffer - if(false == setAdvertiseBuffer(addr)) + if (true == BLEUtils::macAddressValid(_wait_for_connect_peripheral)) { - pr_info(LOG_MODULE_BLE, "No buffer to store the ADV\n"); + // Not add to the buffer when try to establish the connection + if (true == BLEUtils::macAddressSame(*addr, _wait_for_connect_peripheral)) + { + BLEDevice testdev(addr); + stopScanning(); + connectToDevice(testdev); + } } else { - BLEDevice testdev(addr); - stopScanning(); - connect(testdev); + // The critical is accepted + // Find the oldest and expired buffer + if(false == setAdvertiseBuffer(addr)) + { + pr_info(LOG_MODULE_BLE, "No buffer to store the ADV\n"); + } } pr_debug(LOG_MODULE_BLE, "%s-%d: Done", __FUNCTION__, __LINE__); return; diff --git a/libraries/BLE/src/BLEDeviceManager.h b/libraries/BLE/src/BLEDeviceManager.h index cbb597ed..d095a7b2 100644 --- a/libraries/BLE/src/BLEDeviceManager.h +++ b/libraries/BLE/src/BLEDeviceManager.h @@ -340,6 +340,7 @@ class BLEDeviceManager int rssi() const; // returns the RSSI of the peripheral at discovery bool connect(BLEDevice &device); // connect to the peripheral + bool connectToDevice(BLEDevice &device); String deviceName(); // read the device name attribute of the peripheral, and return String value int appearance(); // read the appearance attribute of the peripheral and return value as int @@ -380,6 +381,7 @@ class BLEDeviceManager bt_data_t _adv_accept_critical; // The filters for central device String _adv_critical_local_name; bt_uuid_128_t _dv_critical_service_uuid; + bt_addr_le_t _wait_for_connect_peripheral; // For peripheral struct bt_le_adv_param _adv_param; diff --git a/libraries/BLE/src/BLEProfileManager.cpp b/libraries/BLE/src/BLEProfileManager.cpp index da9ebc96..9eb441ef 100644 --- a/libraries/BLE/src/BLEProfileManager.cpp +++ b/libraries/BLE/src/BLEProfileManager.cpp @@ -40,6 +40,9 @@ BLEProfileManager* BLEProfileManager::instance() } BLEProfileManager::BLEProfileManager (): + _start_discover(false), + _discovering(false), + _discover_timestamp(0), _cur_discover_service(NULL), _attr_base(NULL), _attr_index(0), @@ -47,12 +50,17 @@ BLEProfileManager::BLEProfileManager (): _sub_param_idx(0), _profile_registered(false) { - memset(_service_header_array, 0, sizeof(_service_header_array)); + //memset(_service_header_array, 0, sizeof(_service_header_array)); memset(_discover_params, 0, sizeof(_discover_params)); memset(_addresses, 0, sizeof(_addresses)); - + memset(&_read_params, 0, sizeof(_read_params)); bt_addr_le_copy(&_addresses[BLE_MAX_CONN_CFG], BLEUtils::bleGetLoalAddress()); + for (int i = 0; i <= BLE_MAX_CONN_CFG; i++) + { + _service_header_array[i].next = NULL; + _service_header_array[i].value = NULL; + } pr_debug(LOG_MODULE_BLE, "%s-%d: Construct", __FUNCTION__, __LINE__); } @@ -80,8 +88,13 @@ BLEProfileManager::addService (BLEDevice &bledevice, BLEService& service) serviceheader = &_service_header_array[index]; bt_addr_le_copy(&_addresses[index], bledevice.bt_le_address()); } - - BLEServiceImp *serviceImp = new BLEServiceImp(service); + BLEServiceImp *serviceImp = this->service(bledevice, service.uuid()); + if (NULL != serviceImp) + { + // The service alreay exist + return BLE_STATUS_SUCCESS; + } + serviceImp = new BLEServiceImp(service); if (NULL == serviceImp) { return BLE_STATUS_NO_MEMORY; @@ -99,31 +112,8 @@ BLEProfileManager::addService (BLEDevice &bledevice, BLEService& service) BLE_STATUS_T BLEProfileManager::addService (BLEDevice &bledevice, const bt_uuid_t* uuid) { - BLEServiceLinkNodeHeader* serviceheader = getServiceHeader(bledevice); - if (NULL == serviceheader) - { - int index = getUnusedIndex(); - if (index >= BLE_MAX_CONN_CFG) - { - return BLE_STATUS_NO_MEMORY; - } - serviceheader = &_service_header_array[index]; - bt_addr_le_copy(&_addresses[index], bledevice.bt_le_address()); - } - - BLEServiceImp *serviceImp = new BLEServiceImp(uuid); - if (NULL == serviceImp) - { - return BLE_STATUS_NO_MEMORY; - } - BLEServiceNodePtr node = link_node_create(serviceImp); - if (NULL == node) - { - delete[] serviceImp; - return BLE_STATUS_NO_MEMORY; - } - link_node_insert_last(serviceheader, node); - return BLE_STATUS_SUCCESS; + BLEService svc_obj(uuid); + return addService(bledevice, svc_obj); } BLEProfileManager::BLEServiceLinkNodeHeader* BLEProfileManager::getServiceHeader(const BLEDevice &bledevice) @@ -133,6 +123,7 @@ BLEProfileManager::BLEServiceLinkNodeHeader* BLEProfileManager::getServiceHeader for (i = 0; i <= BLE_MAX_CONN_CFG; i++) { if ((bt_addr_le_cmp(bledevice.bt_le_address(), &_addresses[i]) == 0)) + //if (true == BLEUtils::macAddressSame(*bledevice.bt_le_address(), _addresses[i])) { break; } @@ -151,6 +142,7 @@ const BLEProfileManager::BLEServiceLinkNodeHeader* BLEProfileManager::getService for (i = 0; i <= BLE_MAX_CONN_CFG; i++) { if ((bt_addr_le_cmp(bledevice.bt_le_address(), &_addresses[i]) == 0)) + //if (true == BLEUtils::macAddressSame(*bledevice.bt_le_address(), _addresses[i])) { break; } @@ -398,14 +390,15 @@ BLECharacteristicImp* BLEProfileManager::characteristic(const BLEDevice &bledevi BLEServiceImp* BLEProfileManager::service(const BLEDevice &bledevice, const char * uuid) const { bt_uuid_128_t uuid_tmp; - BLEUtils::uuidString2BT(uuid, (bt_uuid_t *)&uuid_tmp); + //pr_debug(LOG_MODULE_BLE, "%s-%d", __FUNCTION__, __LINE__); return service(bledevice, (const bt_uuid_t *)&uuid_tmp); } BLEServiceImp* BLEProfileManager::service(const BLEDevice &bledevice, const bt_uuid_t* uuid) const { BLEServiceImp* serviceImp = NULL; + #if 1 const BLEServiceLinkNodeHeader* serviceHeader = getServiceHeader(bledevice); if (NULL == serviceHeader) { @@ -413,9 +406,12 @@ BLEServiceImp* BLEProfileManager::service(const BLEDevice &bledevice, const bt_u return NULL; } BLEServiceNodePtr node = serviceHeader->next; + + // Just for debug char uuid_tmp[37]; BLEUtils::uuidBT2String(uuid, uuid_tmp); pr_debug(LOG_MODULE_BLE, "%s-%d: %s", __FUNCTION__, __LINE__, uuid_tmp); + while (node != NULL) { serviceImp = node->value; @@ -425,10 +421,12 @@ BLEServiceImp* BLEProfileManager::service(const BLEDevice &bledevice, const bt_u } node = node->next; } + if (NULL == node) { serviceImp = NULL; } + #endif return serviceImp; } @@ -512,6 +510,12 @@ bool BLEProfileManager::discoverAttributes(BLEDevice* device) pr_debug(LOG_MODULE_BLE, "Discover failed(err %d)\n", err); return ret; } + // Block it + _start_discover = true; + while (_start_discover) + { + delay(10); + } return true; } @@ -533,6 +537,26 @@ int BLEProfileManager::getDeviceIndex(const BLEDevice* device) return getDeviceIndex(device->bt_le_address()); } +bool BLEProfileManager::discovering() +{ + bool ret = (millis() - _discover_timestamp) < 1500; + ret = (_discovering && ret); + if (_cur_discover_service != NULL) + { + ret = ret || _cur_discover_service->discovering(); + } + return ret; +} + +void BLEProfileManager::setDiscovering(bool discover) +{ + if (discover) + { + _discover_timestamp = millis(); + } + _discovering = discover; +} + uint8_t BLEProfileManager::discoverResponseProc(bt_conn_t *conn, const bt_gatt_attr_t *attr, bt_gatt_discover_params_t *params) @@ -542,17 +566,18 @@ uint8_t BLEProfileManager::discoverResponseProc(bt_conn_t *conn, BLEDevice device(dst_addr); uint8_t retVal = BT_GATT_ITER_STOP; - pr_debug(LOG_MODULE_BLE, "%s-%d: index-%d", __FUNCTION__, __LINE__, i); + //pr_debug(LOG_MODULE_BLE, "%s-%d: index-%d", __FUNCTION__, __LINE__, i); if (i >= BLE_MAX_CONN_CFG) { return BT_GATT_ITER_STOP; } - + // Process the service switch (params->type) { case BT_GATT_DISCOVER_CHARACTERISTIC: + case BT_GATT_DISCOVER_DESCRIPTOR: { if (NULL != _cur_discover_service) { @@ -562,42 +587,55 @@ uint8_t BLEProfileManager::discoverResponseProc(bt_conn_t *conn, } break; } - case BT_GATT_DISCOVER_DESCRIPTOR: - { - //descriptorDiscoverRsp(attr, attribute_tmp); - break; - } case BT_GATT_DISCOVER_PRIMARY: { if (NULL != attr) { struct bt_gatt_service *svc_value = (struct bt_gatt_service *)attr->user_data; - int retval = (int)addService(device, svc_value->uuid); - BLEServiceImp* service_tmp = service(device, svc_value->uuid); - if (NULL != service_tmp) + const bt_uuid_t* svc_uuid = svc_value->uuid; + + setDiscovering(false); + // TODO: Pontential bugs + if (svc_uuid->type == BT_UUID_TYPE_16 && + ((const bt_uuid_16_t *)svc_uuid)->val == 0) { - pr_debug(LOG_MODULE_BLE, "%s-%d: handle-%d", - __FUNCTION__, __LINE__, attr->handle); - service_tmp->setHandle(attr->handle); + // Discover failed. The service may unknow type. + // Need read the value and discovery again. + readService(device, attr->handle); + } + else + { + int err_code = (int)addService(device, svc_value->uuid); + if (BLE_STATUS_SUCCESS == err_code) + { + BLEServiceImp* service_tmp = service(device, svc_value->uuid); + if (NULL != service_tmp) + { + service_tmp->setHandle(attr->handle); + service_tmp->setEndHandle(svc_value->end_handle); + } + } + else + { + pr_debug(LOG_MODULE_BLE, "%s-%d: Error-%d", + __FUNCTION__, __LINE__, err_code); + } } retVal = BT_GATT_ITER_CONTINUE; } else { // Service discover complete - serviceDiscoverComplete(device); retVal = BT_GATT_ITER_STOP; } - } - + } default: { - //attribute_tmp->discover(attr, &_discover_params); break; } } - if (retVal == BT_GATT_ITER_STOP) + if (retVal == BT_GATT_ITER_STOP && discovering() == false) { const BLEServiceLinkNodeHeader* serviceHeader = getServiceHeader(device); BLEServiceImp* serviceCurImp = NULL; @@ -631,6 +669,12 @@ uint8_t BLEProfileManager::discoverResponseProc(bt_conn_t *conn, node = node->next; } + if (NULL == node) + { + pr_debug(LOG_MODULE_BLE, "%s-%d: Discover completed", + __FUNCTION__, __LINE__); + _start_discover = false; + } } return retVal; } @@ -670,6 +714,138 @@ void BLEProfileManager::serviceDiscoverComplete(const BLEDevice &bledevice) return; } +void BLEProfileManager::readService(const BLEDevice &bledevice, uint16_t handle) +{ + int retval = 0; + bt_conn_t* conn = NULL; + + if (true == BLEUtils::isLocalBLE(bledevice)) + { + // GATT server can't write + return;// false; + } + + //if (_reading) + { + // Already in reading state + //return false; + } + + _read_params.func = profile_service_read_rsp_process; + _read_params.handle_count = 1; + _read_params.single.handle = handle; + _read_params.single.offset = 0; + + if (0 == _read_params.single.handle) + { + // Discover not complete + return ;//false; + } + + conn = bt_conn_lookup_addr_le(bledevice.bt_le_address()); + if (NULL == conn) + { + return ;//false; + } + setDiscovering(true); + // Send read request + retval = bt_gatt_read(conn, &_read_params); + bt_conn_unref(conn); + //if (0 == retval) + { + //_reading = true; + } + pr_debug(LOG_MODULE_BLE, "%s-%d", __FUNCTION__, __LINE__); + return ;//true; //_reading; +} + +bool BLEProfileManager::discoverService(BLEDevice* device, const bt_uuid_t* svc_uuid) +{ + int err = 0; + bt_conn_t* conn; + int i = getDeviceIndex(device); + bool ret = false; + bt_gatt_discover_params_t* temp = NULL; + + pr_debug(LOG_MODULE_BLE, "%s-%d: index-%d,fun-%p", __FUNCTION__, __LINE__, i,profile_discover_process); + + if (i >= BLE_MAX_CONN_CFG) + { + // The device already in the buffer. + // This function only be called after connection established. + return ret; + } + + BLEServiceImp* serviceImp = service(device, svc_uuid); + if (NULL == serviceImp) + { + return ret; + } + + conn = bt_conn_lookup_addr_le(device->bt_le_address()); + if (NULL == conn) + { + // Link lost + pr_debug(LOG_MODULE_BLE, "Can't find connection\n"); + return ret; + } + temp = &_discover_params[i]; + temp->start_handle = 1; + temp->end_handle = 0xFFFF; + temp->uuid = (bt_uuid_t*) serviceImp->bt_uuid(); + temp->type = BT_GATT_DISCOVER_PRIMARY; + temp->func = profile_discover_process; + + err = bt_gatt_discover(conn, temp); + bt_conn_unref(conn); + if (err) + { + pr_debug(LOG_MODULE_BLE, "Discover failed(err %d)\n", err); + return ret; + } + return true; +} + +uint8_t BLEProfileManager::serviceReadRspProc(bt_conn_t *conn, + int err, + bt_gatt_read_params_t *params, + const void *data, + uint16_t length) +{ + if (NULL == data) + { + return BT_GATT_ITER_STOP; + } + BLEDevice bleDevice(bt_conn_get_dst(conn)); + + pr_debug(LOG_MODULE_BLE, "%s-%d:length-%d", __FUNCTION__, __LINE__, length); + if (length == UUID_SIZE_128) + { + bt_uuid_128_t uuid_tmp; + uuid_tmp.uuid.type = BT_UUID_TYPE_128; + memcpy(uuid_tmp.val, data, UUID_SIZE_128); + /**/ + int retval = (int)BLEProfileManager::instance()->addService(bleDevice, (const bt_uuid_t *)&uuid_tmp); + if (BLE_STATUS_SUCCESS != retval) + { + pr_error(LOG_MODULE_BLE, "%s-%d: Error-%d", + __FUNCTION__, __LINE__, retval); + return BT_GATT_ITER_STOP; + } + + BLEServiceImp* serviceImp = service(&bleDevice, (const bt_uuid_t *)&uuid_tmp);//(BLEServiceImp*)testTTTTTTT();// + if (NULL == serviceImp) + { + pr_debug(LOG_MODULE_BLE, "%s-%d", __FUNCTION__, __LINE__); + return BT_GATT_ITER_STOP; + } + BLEProfileManager::instance()->discoverService(&bleDevice, (const bt_uuid_t *)&uuid_tmp); + } + pr_debug(LOG_MODULE_BLE, "%s-%d", __FUNCTION__, __LINE__); + + return BT_GATT_ITER_STOP; +} + #if 0 void BLEProfileManager::characteristicDiscoverRsp(const bt_gatt_attr_t *attr, BLEAttribute* bleattr) { diff --git a/libraries/BLE/src/BLEProfileManager.h b/libraries/BLE/src/BLEProfileManager.h index 70362c26..b203fedc 100644 --- a/libraries/BLE/src/BLEProfileManager.h +++ b/libraries/BLE/src/BLEProfileManager.h @@ -54,6 +54,7 @@ class BLEProfileManager{ * Or be called in discover process. */ BLE_STATUS_T addService (BLEDevice &bledevice, BLEService& service); + BLE_STATUS_T addService (BLEDevice &bledevice, const bt_uuid_t* uuid); /** * @brief Register the profile to Nordic BLE stack @@ -99,7 +100,13 @@ class BLEProfileManager{ bt_gatt_discover_params_t *params); bool discoverAttributes(BLEDevice* device); + bool discoverService(BLEDevice* device, const bt_uuid_t* svc_uuid); void handleConnectedEvent(const bt_addr_le_t* deviceAddr); + uint8_t serviceReadRspProc(bt_conn_t *conn, + int err, + bt_gatt_read_params_t *params, + const void *data, + uint16_t length); protected: friend ssize_t profile_write_process(bt_conn_t *conn, const bt_gatt_attr_t *attr, @@ -113,7 +120,6 @@ class BLEProfileManager{ BLEProfileManager(); ~BLEProfileManager (void); - BLE_STATUS_T addService (BLEDevice &bledevice, const bt_uuid_t* uuid); void serviceDiscoverComplete(const BLEDevice &bledevice); int getDeviceIndex(const bt_addr_le_t* macAddr); @@ -166,13 +172,22 @@ class BLEProfileManager{ */ void clearProfile(BLEDevice &bledevice); + void readService(const BLEDevice &bledevice, uint16_t handle); + bool discovering(); + void setDiscovering(bool discover); + private: // The last header is for local BLE BLEServiceLinkNodeHeader _service_header_array[BLE_MAX_CONN_CFG + 1]; // The connected devices' service and self service bt_addr_le_t _addresses[BLE_MAX_CONN_CFG + 1]; // The BLE devices' address - + + bool _start_discover; + + bool _discovering; + uint64_t _discover_timestamp; bt_gatt_discover_params_t _discover_params[BLE_MAX_CONN_CFG]; BLEServiceImp* _cur_discover_service; + bt_gatt_read_params_t _read_params; bt_gatt_attr_t *_attr_base; // Allocate the memory for BLE stack int _attr_index; diff --git a/libraries/BLE/src/BLEService.cpp b/libraries/BLE/src/BLEService.cpp index 2c2e80c5..b979511d 100644 --- a/libraries/BLE/src/BLEService.cpp +++ b/libraries/BLE/src/BLEService.cpp @@ -38,6 +38,13 @@ BLEService::BLEService(const char* uuid):_bledevice(),_service_imp(NULL) _bledevice.setAddress(*BLEUtils::bleGetLoalAddress()); } +BLEService::BLEService(const bt_uuid_t* uuid):_bledevice(),_service_imp(NULL) +{ + memset(_uuid_cstr, 0, sizeof (_uuid_cstr)); + BLEUtils::uuidBT2String(uuid, _uuid_cstr); + _bledevice.setAddress(*BLEUtils::bleGetLoalAddress()); +} + BLEService::BLEService(BLEServiceImp* serviceImp, const BLEDevice* bledev): _bledevice(bledev),_service_imp(serviceImp) { diff --git a/libraries/BLE/src/BLEService.h b/libraries/BLE/src/BLEService.h index de4973dd..538ea1d7 100644 --- a/libraries/BLE/src/BLEService.h +++ b/libraries/BLE/src/BLEService.h @@ -122,7 +122,15 @@ class BLEService protected: friend class BLEDevice; friend class BLEServiceImp; + friend class BLEProfileManager; + friend uint8_t profile_service_read_rsp_process(bt_conn_t *conn, + int err, + bt_gatt_read_params_t *params, + const void *data, + uint16_t length); + BLEService(BLEServiceImp* serviceImp, const BLEDevice* bledev); + BLEService(const bt_uuid_t* uuid); void setServiceImp(BLEServiceImp* serviceImp); private: BLEServiceImp* getServiceImp(); diff --git a/libraries/BLE/src/BLEServiceImp.cpp b/libraries/BLE/src/BLEServiceImp.cpp index 15628c15..5c0b4371 100644 --- a/libraries/BLE/src/BLEServiceImp.cpp +++ b/libraries/BLE/src/BLEServiceImp.cpp @@ -34,7 +34,8 @@ bt_uuid_t *BLEServiceImp::getPrimayUuid(void) BLEServiceImp::BLEServiceImp(BLEService& service): BLEAttribute(service.uuid(), BLETypeService), _start_handle(0), - _end_handle(0xFFFF) + _end_handle(0xFFFF), + _cur_discover_chrc(NULL) { memset(&_characteristics_header, 0, sizeof(_characteristics_header)); service.setServiceImp(this); @@ -43,7 +44,8 @@ BLEServiceImp::BLEServiceImp(BLEService& service): BLEServiceImp::BLEServiceImp(const bt_uuid_t* uuid): BLEAttribute(uuid, BLETypeService), _start_handle(0), - _end_handle(0xFFFF) + _end_handle(0xFFFF), + _cur_discover_chrc(NULL) { memset(&_characteristics_header, 0, sizeof(_characteristics_header)); } @@ -56,8 +58,46 @@ BLEServiceImp::~BLEServiceImp() int BLEServiceImp::addCharacteristic(BLEDevice& bledevice, BLECharacteristic& characteristic) { - BLECharacteristicImp* characteristicImp = new BLECharacteristicImp(characteristic, bledevice); + BLECharacteristicImp* characteristicImp = this->characteristic(characteristic.uuid()); + if (NULL != characteristicImp) + { + pr_info(LOG_MODULE_BLE, "%s-%d: Already exist",__FUNCTION__, __LINE__); + return BLE_STATUS_SUCCESS; + } + characteristicImp = new BLECharacteristicImp(characteristic, bledevice); + pr_debug(LOG_MODULE_BLE, "%s-%d",__FUNCTION__, __LINE__); + if (NULL == characteristicImp) + { + return BLE_STATUS_NO_MEMORY; + } + + BLECharacteristicNodePtr node = link_node_create(characteristicImp); + if (NULL == node) + { + delete[] characteristicImp; + return BLE_STATUS_NO_MEMORY; + } + link_node_insert_last(&_characteristics_header, node); pr_debug(LOG_MODULE_BLE, "%s-%d",__FUNCTION__, __LINE__); + return BLE_STATUS_SUCCESS; +} + +int BLEServiceImp::addCharacteristic(BLEDevice& bledevice, + const bt_uuid_t* uuid, + uint16_t handle, + unsigned char properties) +{ + BLECharacteristicImp* characteristicImp = this->characteristic(uuid); + if (NULL != characteristicImp) + { + pr_info(LOG_MODULE_BLE, "%s-%d: Already exist",__FUNCTION__, __LINE__); + return BLE_STATUS_SUCCESS; + } + pr_debug(LOG_MODULE_BLE, "%s-%d:handle-%d",__FUNCTION__, __LINE__,handle); + characteristicImp = new BLECharacteristicImp(uuid, + properties, + handle, + bledevice); if (NULL == characteristicImp) { return BLE_STATUS_NO_MEMORY; @@ -173,10 +213,15 @@ BLECharacteristicImp* BLEServiceImp::characteristic(uint16_t handle) return characteristicImp; } -BLECharacteristicImp* BLEServiceImp::characteristic(const char* uuid) +BLECharacteristicImp* BLEServiceImp::characteristic(const bt_uuid_t* uuid) { BLECharacteristicImp* characteristicImp = NULL; BLECharacteristicNodePtr node = link_node_get_first(&_characteristics_header); + // Just for debug + //char uuid_tmp[37]; + //BLEUtils::uuidBT2String(uuid, uuid_tmp); + //pr_debug(LOG_MODULE_BLE, "%s-%d: %s", __FUNCTION__, __LINE__, uuid_tmp); + while (NULL != node) { characteristicImp = node->value; @@ -184,6 +229,8 @@ BLECharacteristicImp* BLEServiceImp::characteristic(const char* uuid) { break; } + //BLEUtils::uuidBT2String(characteristicImp->bt_uuid(), uuid_tmp); + //pr_debug(LOG_MODULE_BLE, "%s-%d: %s", __FUNCTION__, __LINE__, uuid_tmp); node = node->next; } @@ -194,9 +241,21 @@ BLECharacteristicImp* BLEServiceImp::characteristic(const char* uuid) return characteristicImp; } +BLECharacteristicImp* BLEServiceImp::characteristic(const char* uuid) +{ + bt_uuid_128_t uuid_tmp; + BLEUtils::uuidString2BT(uuid, (bt_uuid_t *)&uuid_tmp); + return characteristic((const bt_uuid_t *)&uuid_tmp); +} + +bool BLEServiceImp::discovering() +{ + return (_cur_discover_chrc != NULL); +} + bool BLEServiceImp::discoverAttributes(BLEDevice* device) { - + pr_debug(LOG_MODULE_BLE, "%s-%d", __FUNCTION__, __LINE__); int err; bt_conn_t* conn; bt_gatt_discover_params_t* temp = NULL; @@ -244,26 +303,43 @@ uint8_t BLEServiceImp::discoverResponseProc(bt_conn_t *conn, BLEDevice device(dst_addr); uint8_t retVal = BT_GATT_ITER_STOP; - pr_debug(LOG_MODULE_BLE, "%s-%d", __FUNCTION__, __LINE__); - if (NULL == attr) - { - return BT_GATT_ITER_STOP; - } - const bt_uuid_t* chrc_uuid = attr->uuid; - struct bt_gatt_chrc* psttemp = (struct bt_gatt_chrc*)attr->user_data; - char uuidbuffer[37]; - BLEUtils::uuidBT2String(chrc_uuid, uuidbuffer); - pr_debug(LOG_MODULE_BLE, "%s-%d: uuid type: %d\r\n%s", __FUNCTION__, __LINE__, chrc_uuid->type, uuidbuffer); - - BLEUtils::uuidBT2String(psttemp->uuid, uuidbuffer); - pr_debug(LOG_MODULE_BLE, "%s-%d:%p uuid type: %d properties: %d\r\n%s", __FUNCTION__, __LINE__,psttemp, psttemp->uuid->type, psttemp->properties, uuidbuffer); + //pr_debug(LOG_MODULE_BLE, "%s-%d: type-%d", __FUNCTION__, __LINE__, params->type); + // Process the service switch (params->type) { case BT_GATT_DISCOVER_CHARACTERISTIC: { - //characteristicDiscoverRsp(attr, attribute_tmp); - //send_discover = true; + if (NULL != attr) + { + //const bt_uuid_t* chrc_uuid = attr->uuid; + uint16_t chrc_handle = attr->handle + 1; + struct bt_gatt_chrc* psttemp = (struct bt_gatt_chrc*)attr->user_data; + int retval = (int)addCharacteristic(device, + psttemp->uuid, + chrc_handle, + psttemp->properties); + + //pr_debug(LOG_MODULE_BLE, "%s-%d:handle-%d:%d", __FUNCTION__, __LINE__,attr->handle, chrc_handle); + if (BLE_STATUS_SUCCESS != retval) + { + pr_error(LOG_MODULE_BLE, "%s-%d: Error-%d", + __FUNCTION__, __LINE__, retval); + } + //retVal = BT_GATT_ITER_CONTINUE; + } + break; + } + case BT_GATT_DISCOVER_DESCRIPTOR: + { + // + + if (NULL != _cur_discover_chrc) + { + retVal = _cur_discover_chrc->discoverResponseProc(conn, + attr, + params); + } break; } default: @@ -273,12 +349,14 @@ uint8_t BLEServiceImp::discoverResponseProc(bt_conn_t *conn, } } + pr_debug(LOG_MODULE_BLE, "%s-%d:ret-%d",__FUNCTION__, __LINE__, retVal); if (retVal == BT_GATT_ITER_STOP) { - const BLECharacteristicLinkNodeHeader* serviceHeader = &_characteristics_header; + const BLECharacteristicLinkNodeHeader* chrcHeader = &_characteristics_header; BLECharacteristicImp* chrcCurImp = NULL; - BLECharacteristicNodePtr node = serviceHeader->next; + BLECharacteristicNodePtr node = chrcHeader->next; + pr_debug(LOG_MODULE_BLE, "%s-%d: node-%p",__FUNCTION__, __LINE__, node); // Discover next service while (node != NULL) { @@ -286,7 +364,8 @@ uint8_t BLEServiceImp::discoverResponseProc(bt_conn_t *conn, if (NULL == _cur_discover_chrc) { - bool result = true;//serviceCurImp->discoverAttributes(&device); + bool result = chrcCurImp->discoverAttributes(&device); + pr_debug(LOG_MODULE_BLE, "%s-%d",__FUNCTION__, __LINE__); if (result == true) { // Record the current discovering service diff --git a/libraries/BLE/src/BLEServiceImp.h b/libraries/BLE/src/BLEServiceImp.h index d9706edf..765abd62 100644 --- a/libraries/BLE/src/BLEServiceImp.h +++ b/libraries/BLE/src/BLEServiceImp.h @@ -52,9 +52,13 @@ class BLEServiceImp: public BLEAttribute{ * @note none */ int addCharacteristic(BLEDevice& bledevice, BLECharacteristic& characteristic); - + int addCharacteristic(BLEDevice& bledevice, + const bt_uuid_t* uuid, + uint16_t handle, + unsigned char properties); int getCharacteristicCount(); + BLECharacteristicImp* characteristic(const bt_uuid_t* uuid); BLECharacteristicImp* characteristic(const char* uuid); BLECharacteristicImp* characteristic(int index); BLECharacteristicImp* characteristic(uint16_t handle); @@ -67,6 +71,7 @@ class BLEServiceImp: public BLEAttribute{ uint8_t discoverResponseProc(bt_conn_t *conn, const bt_gatt_attr_t *attr, bt_gatt_discover_params_t *params); + bool discovering(); protected: friend class BLEProfileManager; diff --git a/libraries/BLE/src/BLEUtils.cpp b/libraries/BLE/src/BLEUtils.cpp index 461b5485..de2ba942 100644 --- a/libraries/BLE/src/BLEUtils.cpp +++ b/libraries/BLE/src/BLEUtils.cpp @@ -1,148 +1,190 @@ - -#include "ArduinoBLE.h" -#include "BLEUtils.h" -#include "internal/ble_client.h" - -String BLEUtils::macAddressBT2String(const bt_addr_le_t &bd_addr) -{ - char mac_string[BT_ADDR_STR_LEN]; - snprintf(mac_string, BT_ADDR_STR_LEN, "%2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X", - bd_addr.val[5], bd_addr.val[4], bd_addr.val[3], - bd_addr.val[2], bd_addr.val[1], bd_addr.val[0]); - String temp(mac_string); - return temp; -} - -void BLEUtils::macAddressString2BT(const char* mac_str, bt_addr_le_t &bd_addr) -{ - char temp[] = {0, 0, 0}; - int strLength = strlen(mac_str); - int length = 0; - - bd_addr.type = BT_ADDR_LE_PUBLIC; - - for (int i = strLength - 1; i >= 0 && length < MAX_UUID_SIZE; i -= 2) - { - if (mac_str[i] == ':') - { - i++; - continue; - } - - temp[0] = mac_str[i - 1]; - temp[1] = mac_str[i]; - - bd_addr.val[length] = strtoul(temp, NULL, 16); - - length++; - } - -} - -bool BLEUtils::macAddressValid(const bt_addr_le_t &bd_addr) -{ - static const bt_addr_le_t zero = {0,{0,0,0,0,0,0}}; - bool temp = (memcmp(bd_addr.val, zero.val, 6) != 0);//false;// - #if 0 - for (int i = 0; i < 6; i++) - { - if (bd_addr.val[i] != zero.val[i]) - { - -pr_info(LOG_MODULE_BLE, "%s-idx %d-%.2x:%.2x", __FUNCTION__, i ,bd_addr.val[i], zero.val[i]); -pr_info(LOG_MODULE_BLE,"%s",BLEUtils::macAddressBT2String(zero).c_str()); - temp = true; - break; - } - } - #endif - return temp; -} - - -bt_addr_le_t* BLEUtils::bleGetLoalAddress() -{ - static bt_addr_le_t board_addr; - if (false == macAddressValid(board_addr)) - ble_client_get_mac_address(&board_addr); - return &board_addr; -} - - - -void BLEUtils::uuidString2BT(const char* uuid, bt_uuid_t* pstuuid) -{ - char temp[] = {0, 0, 0}; - int strLength = strlen(uuid); - int length = 0; - bt_uuid_128_t uuid_tmp; - - memset (&uuid_tmp, 0x00, sizeof(uuid_tmp)); - - for (int i = strLength - 1; i >= 0 && length < MAX_UUID_SIZE; i -= 2) - { - if (uuid[i] == '-') - { - i++; - continue; - } - - temp[0] = uuid[i - 1]; - temp[1] = uuid[i]; - - uuid_tmp.val[length] = strtoul(temp, NULL, 16); - - length++; - } - - if (length == 2) - { - uint16_t temp = (uuid_tmp.val[1] << 8)| uuid_tmp.val[0]; - uuid_tmp.uuid.type = BT_UUID_TYPE_16; - ((bt_uuid_16_t*)(&uuid_tmp.uuid))->val = temp; - } - else - { - uuid_tmp.uuid.type = BT_UUID_TYPE_128; - } - memcpy(pstuuid, &uuid_tmp, sizeof (uuid_tmp)); -} - -void BLEUtils::uuidBT2String(const bt_uuid_t* pstuuid, char* uuid) -{ - unsigned int tmp1, tmp5; - uint16_t tmp0, tmp2, tmp3, tmp4; - // TODO: Change the magic number 37 - switch (pstuuid->type) { - case BT_UUID_TYPE_16: - memcpy(&tmp0, &BT_UUID_16(pstuuid)->val, sizeof(tmp0)); - snprintf(uuid, 37, "%.4x", tmp0); - break; - case BT_UUID_TYPE_128: - memcpy(&tmp0, &BT_UUID_128(pstuuid)->val[0], sizeof(tmp0)); - memcpy(&tmp1, &BT_UUID_128(pstuuid)->val[2], sizeof(tmp1)); - memcpy(&tmp2, &BT_UUID_128(pstuuid)->val[6], sizeof(tmp2)); - memcpy(&tmp3, &BT_UUID_128(pstuuid)->val[8], sizeof(tmp3)); - memcpy(&tmp4, &BT_UUID_128(pstuuid)->val[10], sizeof(tmp4)); - memcpy(&tmp5, &BT_UUID_128(pstuuid)->val[12], sizeof(tmp5)); - snprintf(uuid, 37, "%.8x-%.4x-%.4x-%.4x-%.8x%.4x", - tmp5, tmp4, tmp3, tmp2, tmp1, tmp0); - break; - default: - memset(uuid, 0, 37); - return; - } -} - -BLEDevice& BLEUtils::getLoacalBleDevice() -{ - return BLE; -} - -bool BLEUtils::isLocalBLE(BLEDevice& device) -{ - return (device == BLE); -} - - - + +#include "ArduinoBLE.h" +#include "BLEUtils.h" +#include "internal/ble_client.h" + +String BLEUtils::macAddressBT2String(const bt_addr_le_t &bd_addr) +{ + char mac_string[BT_ADDR_STR_LEN]; + snprintf(mac_string, BT_ADDR_STR_LEN, "%2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X", + bd_addr.val[5], bd_addr.val[4], bd_addr.val[3], + bd_addr.val[2], bd_addr.val[1], bd_addr.val[0]); + String temp(mac_string); + return temp; +} + +void BLEUtils::macAddressString2BT(const char* mac_str, bt_addr_le_t &bd_addr) +{ + char temp[] = {0, 0, 0}; + int strLength = strlen(mac_str); + int length = 0; + + bd_addr.type = BT_ADDR_LE_PUBLIC; + + for (int i = strLength - 1; i >= 0 && length < MAX_UUID_SIZE; i -= 2) + { + if (mac_str[i] == ':') + { + i++; + continue; + } + + temp[0] = mac_str[i - 1]; + temp[1] = mac_str[i]; + + bd_addr.val[length] = strtoul(temp, NULL, 16); + + length++; + } + +} + +bool BLEUtils::macAddressSame(const bt_addr_le_t &bd_addr1, + const bt_addr_le_t &bd_addr2) +{ + bool temp = true;//(memcmp(bd_addr1.val, bd_addr2.val, 6) != 0);// + #if 1 + for (int i = 0; i < 6; i++) + { + if (bd_addr1.val[i] != bd_addr2.val[i]) + { + +pr_info(LOG_MODULE_BLE, "%s-idx %d-%.2x:%.2x", __FUNCTION__, i ,bd_addr1.val[i], bd_addr2.val[i]); +pr_info(LOG_MODULE_BLE,"%s",BLEUtils::macAddressBT2String(bd_addr1).c_str()); +pr_info(LOG_MODULE_BLE,"%s",BLEUtils::macAddressBT2String(bd_addr2).c_str()); + temp = false; + break; + } + } + #endif + return temp; + +} + +bool BLEUtils::macAddressValid(const bt_addr_le_t &bd_addr) +{ + static const bt_addr_le_t zero = {0,{0,0,0,0,0,0}}; + bool temp = (memcmp(bd_addr.val, zero.val, 6) != 0);//false;// + #if 0 + for (int i = 0; i < 6; i++) + { + if (bd_addr.val[i] != zero.val[i]) + { + +pr_info(LOG_MODULE_BLE, "%s-idx %d-%.2x:%.2x", __FUNCTION__, i ,bd_addr.val[i], zero.val[i]); +pr_info(LOG_MODULE_BLE,"%s",BLEUtils::macAddressBT2String(zero).c_str()); + temp = true; + break; + } + } + #endif + return temp; +} + + +bt_addr_le_t* BLEUtils::bleGetLoalAddress() +{ + static bt_addr_le_t board_addr; + if (false == macAddressValid(board_addr)) + ble_client_get_mac_address(&board_addr); + return &board_addr; +} + + + +void BLEUtils::uuidString2BT(const char* uuid, bt_uuid_t* pstuuid) +{ + char temp[] = {0, 0, 0}; + int strLength = strlen(uuid); + int length = 0; + bt_uuid_128_t uuid_tmp; + + memset (&uuid_tmp, 0x00, sizeof(uuid_tmp)); + + for (int i = strLength - 1; i >= 0 && length < MAX_UUID_SIZE; i -= 2) + { + if (uuid[i] == '-') + { + i++; + continue; + } + + temp[0] = uuid[i - 1]; + temp[1] = uuid[i]; + + uuid_tmp.val[length] = strtoul(temp, NULL, 16); + + length++; + } + + if (length == 2) + { + uint16_t temp = (uuid_tmp.val[1] << 8)| uuid_tmp.val[0]; + uint8_t* uuid16_val = (uint8_t*)&((bt_uuid_16_t*)(&uuid_tmp.uuid))->val; + uuid_tmp.uuid.type = BT_UUID_TYPE_16; + memcpy(uuid16_val, &temp, sizeof (uint16_t)); + } + else + { + uuid_tmp.uuid.type = BT_UUID_TYPE_128; + } + memcpy(pstuuid, &uuid_tmp, sizeof (uuid_tmp)); +} + +void BLEUtils::uuidBT2String(const bt_uuid_t* pstuuid, char* uuid) +{ + unsigned int tmp1, tmp5; + uint16_t tmp0, tmp2, tmp3, tmp4; + // TODO: Change the magic number 37 + switch (pstuuid->type) { + case BT_UUID_TYPE_16: + memcpy(&tmp0, &BT_UUID_16(pstuuid)->val, sizeof(tmp0)); + snprintf(uuid, 37, "%.4x", tmp0); + break; + case BT_UUID_TYPE_128: + memcpy(&tmp0, &BT_UUID_128(pstuuid)->val[0], sizeof(tmp0)); + memcpy(&tmp1, &BT_UUID_128(pstuuid)->val[2], sizeof(tmp1)); + memcpy(&tmp2, &BT_UUID_128(pstuuid)->val[6], sizeof(tmp2)); + memcpy(&tmp3, &BT_UUID_128(pstuuid)->val[8], sizeof(tmp3)); + memcpy(&tmp4, &BT_UUID_128(pstuuid)->val[10], sizeof(tmp4)); + memcpy(&tmp5, &BT_UUID_128(pstuuid)->val[12], sizeof(tmp5)); + snprintf(uuid, 37, "%.8x-%.4x-%.4x-%.4x-%.8x%.4x", + tmp5, tmp4, tmp3, tmp2, tmp1, tmp0); + break; + default: + memset(uuid, 0, 37); + return; + } +} + +bool BLEUtils::uuidBTSame(const bt_uuid_t* pstuuid1, + const bt_uuid_t* pstuuid2) +{ + bool temp = (pstuuid1->type == pstuuid2->type); + if (true == temp) + { + if (pstuuid1->type == BT_UUID_TYPE_16) + { + temp = (0 == memcmp(&BT_UUID_16(pstuuid1)->val, &BT_UUID_16(pstuuid2)->val, 2)); + } + else + { + temp = (0 == memcmp(BT_UUID_128(pstuuid1)->val, BT_UUID_128(pstuuid2)->val, 16)); + } + } + return temp; + +} + +BLEDevice& BLEUtils::getLoacalBleDevice() +{ + return BLE; +} + +bool BLEUtils::isLocalBLE(const BLEDevice& device) +{ + return (device == BLE); +} + + + diff --git a/libraries/BLE/src/BLEUtils.h b/libraries/BLE/src/BLEUtils.h index e3bc241f..5fb70831 100644 --- a/libraries/BLE/src/BLEUtils.h +++ b/libraries/BLE/src/BLEUtils.h @@ -4,10 +4,14 @@ namespace BLEUtils String macAddressBT2String(const bt_addr_le_t &bd_addr); void macAddressString2BT(const char* mac_str, bt_addr_le_t &bd_addr); bool macAddressValid(const bt_addr_le_t &bd_addr); + bool macAddressSame(const bt_addr_le_t &bd_addr1, const bt_addr_le_t &bd_addr2); bt_addr_le_t* bleGetLoalAddress(); void uuidString2BT(const char* uuid, bt_uuid_t* pstuuid); void uuidBT2String(const bt_uuid_t* pstuuid, char* uuid); + bool uuidBTSame(const bt_uuid_t* pstuuid1, + const bt_uuid_t* pstuuid2); + BLEDevice& getLoacalBleDevice(); - bool isLocalBLE(BLEDevice& device); + bool isLocalBLE(const BLEDevice& device); } diff --git a/system/libarc32_arduino101/framework/src/services/ble/uuid.c b/system/libarc32_arduino101/framework/src/services/ble/uuid.c index dbacfd94..365ea2c7 100644 --- a/system/libarc32_arduino101/framework/src/services/ble/uuid.c +++ b/system/libarc32_arduino101/framework/src/services/ble/uuid.c @@ -89,7 +89,7 @@ int bt_uuid_cmp(const struct bt_uuid *u1, const struct bt_uuid *u2) switch (u1->type) { case BT_UUID_TYPE_16: - return (int)BT_UUID_16(u1)->val - (int)BT_UUID_16(u2)->val; + return memcmp(&BT_UUID_16(u1)->val, &BT_UUID_16(u2)->val, 2);//(int)BT_UUID_16(u1)->val - (int)BT_UUID_16(u2)->val; case BT_UUID_TYPE_128: return memcmp(BT_UUID_128(u1)->val, BT_UUID_128(u2)->val, 16); } diff --git a/system/libarc32_arduino101/framework/src/services/ble_service/ble_service.c b/system/libarc32_arduino101/framework/src/services/ble_service/ble_service.c index d6efdf63..a056a643 100644 --- a/system/libarc32_arduino101/framework/src/services/ble_service/ble_service.c +++ b/system/libarc32_arduino101/framework/src/services/ble_service/ble_service.c @@ -113,10 +113,9 @@ static void handle_msg_id_ble_rpc_callin(struct message *msg, void *priv) { struct ble_rpc_callin *rpc = container_of(msg, struct ble_rpc_callin, msg); /* handle incoming message */ - rpc_deserialize(rpc->p_data, rpc->len); //pr_debug(LOG_MODULE_BLE, "%s-%d", __FUNCTION__, __LINE__); + rpc_deserialize(rpc->p_data, rpc->len); bfree(rpc->p_data); - //pr_debug(LOG_MODULE_BLE, "%s-%d", __FUNCTION__, __LINE__); message_free(msg); //pr_debug(LOG_MODULE_BLE, "%s-%d", __FUNCTION__, __LINE__); } From 35027d3410c690bce347a6772d22a6e706bfbb4f Mon Sep 17 00:00:00 2001 From: lianggao Date: Fri, 21 Oct 2016 22:26:18 +0800 Subject: [PATCH 04/22] Add central example sketch Note: need change the compiler parameter in platform.txt. "compiler.c.flags=-c -std=gnu11 -mcpu=quarkse_em -mlittle-endian -g -Os ..." to "compiler.c.flags=-c -std=gnu11 -mcpu=quarkse_em -mlittle-endian -g -O0" "compiler.cpp.flags=-c -mcpu=quarkse_em -mlittle-endian -g -Os ..." to "compiler.cpp.flags=-c -mcpu=quarkse_em -mlittle-endian -g -O0 ..." --- libraries/BLE/examples/central/central.ino | 94 ++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 libraries/BLE/examples/central/central.ino diff --git a/libraries/BLE/examples/central/central.ino b/libraries/BLE/examples/central/central.ino new file mode 100644 index 00000000..212e1546 --- /dev/null +++ b/libraries/BLE/examples/central/central.ino @@ -0,0 +1,94 @@ + +#include "ArduinoBLE.h" +#include "BLEAttribute.h" +#include "BLECharacteristicImp.h" +#include "BLEProfileManager.h" + +// LED pin +#define LED_PIN 13 + +void setup() { + Serial1.begin(115200); + Serial1.println("test---"); + + // set LED pin to output mode + pinMode(LED_PIN, OUTPUT); + + // begin initialization + BLE.begin(); + Serial1.println(BLE.address()); + + BLE.startScanning("LED"); +} + +void controlLed(BLEDevice &peripheral) +{ + static bool discovered = false; + // connect to the peripheral + Serial1.print("Connecting ... "); + Serial1.println(peripheral.address()); + + if (peripheral.connect()) + { + Serial1.print("Connected: "); + Serial1.println(peripheral.address()); + } + else + { + Serial1.println("Failed to connect!"); + return; + } + + peripheral.discoverAttributes(); + + BLECharacteristic ledCharacteristic = peripheral.characteristic("19b10101-e8f2-537e-4f6c-d104768a1214"); + + if (!ledCharacteristic) + { + //peripheral.disconnect(); + while(1) + { + Serial1.println("Peripheral does not have LED characteristic!"); + delay(5000); + } + return; + } + + + unsigned char ledstate = 0; + + discovered = false; + while (peripheral.connected()) + { + if (ledstate == 1) + { + ledstate = 0; + } + else + { + ledstate = 1; + } + ledCharacteristic.write(&ledstate, sizeof(ledstate)); + delay(5000); + } + Serial1.print("Disconnected"); + Serial1.println(peripheral.address()); +} + +void loop() { + //pr_debug(LOG_MODULE_BLE, "%s-%d",__FUNCTION__, __LINE__); + BLEDevice peripheral = BLE.available(); + //pr_debug(LOG_MODULE_BLE, "%s-%d",__FUNCTION__, __LINE__); + if (peripheral) + { + Serial1.println(peripheral.address()); + BLE.stopScanning(); + delay (1000); + // central connected to peripheral + controlLed(peripheral); + delay (4000); + BLE.startScanning("LED"); + } +} + + From 521d1db76843326577933bcd008ce29314902bc8 Mon Sep 17 00:00:00 2001 From: lianggao Date: Mon, 24 Oct 2016 16:11:03 +0800 Subject: [PATCH 05/22] Support Notify/Indicate feature 1. Add the Notify/Indicate feature 2. Add the notify central/peripheral example sketches --- libraries/BLE/examples/central/central.ino | 24 +- .../examples/notification/notification.ino | 81 ++++++ .../examples/notifycentral/notifycentral.ino | 95 +++++++ libraries/BLE/examples/test/test.ino | 22 +- libraries/BLE/src/BLECallbacks.cpp | 8 +- libraries/BLE/src/BLECharacteristic.cpp | 18 +- libraries/BLE/src/BLECharacteristicImp.cpp | 116 ++++++++- libraries/BLE/src/BLECharacteristicImp.h | 11 +- libraries/BLE/src/BLEProfileManager.cpp | 233 ++---------------- libraries/BLE/src/BLEProfileManager.h | 2 + 10 files changed, 364 insertions(+), 246 deletions(-) create mode 100644 libraries/BLE/examples/notification/notification.ino create mode 100644 libraries/BLE/examples/notifycentral/notifycentral.ino diff --git a/libraries/BLE/examples/central/central.ino b/libraries/BLE/examples/central/central.ino index 212e1546..d308000e 100644 --- a/libraries/BLE/examples/central/central.ino +++ b/libraries/BLE/examples/central/central.ino @@ -8,15 +8,15 @@ #define LED_PIN 13 void setup() { - Serial1.begin(115200); - Serial1.println("test---"); + Serial.begin(115200); + Serial.println("test---"); // set LED pin to output mode pinMode(LED_PIN, OUTPUT); // begin initialization BLE.begin(); - Serial1.println(BLE.address()); + Serial.println(BLE.address()); BLE.startScanning("LED"); } @@ -25,17 +25,17 @@ void controlLed(BLEDevice &peripheral) { static bool discovered = false; // connect to the peripheral - Serial1.print("Connecting ... "); - Serial1.println(peripheral.address()); + Serial.print("Connecting ... "); + Serial.println(peripheral.address()); if (peripheral.connect()) { - Serial1.print("Connected: "); - Serial1.println(peripheral.address()); + Serial.print("Connected: "); + Serial.println(peripheral.address()); } else { - Serial1.println("Failed to connect!"); + Serial.println("Failed to connect!"); return; } @@ -48,7 +48,7 @@ void controlLed(BLEDevice &peripheral) //peripheral.disconnect(); while(1) { - Serial1.println("Peripheral does not have LED characteristic!"); + Serial.println("Peripheral does not have LED characteristic!"); delay(5000); } return; @@ -71,8 +71,8 @@ void controlLed(BLEDevice &peripheral) ledCharacteristic.write(&ledstate, sizeof(ledstate)); delay(5000); } - Serial1.print("Disconnected"); - Serial1.println(peripheral.address()); + Serial.print("Disconnected"); + Serial.println(peripheral.address()); } void loop() { @@ -81,7 +81,7 @@ void loop() { //pr_debug(LOG_MODULE_BLE, "%s-%d",__FUNCTION__, __LINE__); if (peripheral) { - Serial1.println(peripheral.address()); + Serial.println(peripheral.address()); BLE.stopScanning(); delay (1000); // central connected to peripheral diff --git a/libraries/BLE/examples/notification/notification.ino b/libraries/BLE/examples/notification/notification.ino new file mode 100644 index 00000000..6da0c096 --- /dev/null +++ b/libraries/BLE/examples/notification/notification.ino @@ -0,0 +1,81 @@ + +#include "ArduinoBLE.h" +#include "BLEAttribute.h" +#include "BLECharacteristicImp.h" +#include "BLEProfileManager.h" + +// LED pin +#define LED_PIN 13 + +// create service +BLEService ledService("19b10100e8f2537e4f6cd104768a1214"); + +BLECharacteristic switchCharacteristic("19b10101e8f2537e4f6cd104768a1214", BLERead | BLEWrite | BLENotify, 1); + +BLEDescriptor switchDescriptor("2901", "switch"); + +void setup() { + Serial.begin(115200); + Serial.println("test---"); + + // set LED pin to output mode + pinMode(LED_PIN, OUTPUT); + + // begin initialization + BLE.begin(); + Serial.println(BLE.address()); + + // set advertised local name and service UUID + BLE.setLocalName("LED"); + BLE.setAdvertisedServiceUuid(ledService.uuid()); + + // add service and characteristic + BLE.addService(ledService); + ledService.addCharacteristic(switchCharacteristic); + switchCharacteristic.addDescriptor(switchDescriptor); + unsigned char test = 1; + switchCharacteristic.writeValue(&test,1); + BLE.startAdvertising(); +} + +void loop() { + static int i = 0; + BLEDevice central = BLE.central(); + bool temp = central; +i++; + if (temp) { + // central connected to peripheral + Serial.print(i); + Serial.print(F("Connected to central: ")); + Serial.println(central.address()); + + Serial.print(temp); + + unsigned char ledstate = 0; + + while (central.connected()) { + // central still connected to peripheral + if (switchCharacteristic.canNotify()) + { + + if (ledstate == 1) + { + ledstate = 0; + digitalWrite(LED_PIN, LOW); + } + else + { + ledstate = 1; + digitalWrite(LED_PIN, HIGH); + } + switchCharacteristic.writeValue(&ledstate, sizeof(ledstate)); + delay(5000); + } + } + + // central disconnected + Serial.print(F("Disconnected from central: ")); + Serial.println(central.address()); + } + //delay (1000); +} diff --git a/libraries/BLE/examples/notifycentral/notifycentral.ino b/libraries/BLE/examples/notifycentral/notifycentral.ino new file mode 100644 index 00000000..a995a580 --- /dev/null +++ b/libraries/BLE/examples/notifycentral/notifycentral.ino @@ -0,0 +1,95 @@ + +#include "ArduinoBLE.h" +#include "BLEAttribute.h" +#include "BLECharacteristicImp.h" +#include "BLEProfileManager.h" + +// LED pin +#define LED_PIN 13 + +void setup() { + Serial.begin(115200); + Serial.println("test---"); + + // set LED pin to output mode + pinMode(LED_PIN, OUTPUT); + + // begin initialization + BLE.begin(); + Serial.println(BLE.address()); + + BLE.startScanning("LED"); +} + +void controlLed(BLEDevice &peripheral) +{ + static bool discovered = false; + // connect to the peripheral + Serial.print("Connecting ... "); + Serial.println(peripheral.address()); + + if (peripheral.connect()) + { + Serial.print("Connected: "); + Serial.println(peripheral.address()); + } + else + { + Serial.println("Failed to connect!"); + return; + } + + peripheral.discoverAttributes(); + + BLECharacteristic ledCharacteristic = peripheral.characteristic("19b10101-e8f2-537e-4f6c-d104768a1214"); + + if (!ledCharacteristic) + { + //peripheral.disconnect(); + while(1) + { + Serial.println("Peripheral does not have LED characteristic!"); + delay(5000); + } + return; + } + ledCharacteristic.subscribe(); + + + discovered = false; + while (peripheral.connected()) + { + if (ledCharacteristic.valueUpdated()) + { + char ledValue = *ledCharacteristic.value(); + + if (ledValue) { + Serial.println(F("LED on")); + digitalWrite(LED_PIN, HIGH); + } else { + Serial.println(F("LED off")); + digitalWrite(LED_PIN, LOW); + } + } + } + Serial.print("Disconnected"); + Serial.println(peripheral.address()); +} + +void loop() { + //pr_debug(LOG_MODULE_BLE, "%s-%d",__FUNCTION__, __LINE__); + BLEDevice peripheral = BLE.available(); + //pr_debug(LOG_MODULE_BLE, "%s-%d",__FUNCTION__, __LINE__); + if (peripheral) + { + Serial.println(peripheral.address()); + BLE.stopScanning(); + delay (1000); + // central connected to peripheral + controlLed(peripheral); + delay (4000); + BLE.startScanning("LED"); + } +} + + diff --git a/libraries/BLE/examples/test/test.ino b/libraries/BLE/examples/test/test.ino index 9a32ea8f..cfd9bffc 100644 --- a/libraries/BLE/examples/test/test.ino +++ b/libraries/BLE/examples/test/test.ino @@ -15,15 +15,15 @@ BLECharacteristic switchCharacteristic("19b10101e8f2537e4f6cd104768a1214", BLE BLEDescriptor switchDescriptor("2901", "switch"); void setup() { - Serial1.begin(115200); - Serial1.println("test---"); + Serial.begin(115200); + Serial.println("test---"); // set LED pin to output mode pinMode(LED_PIN, OUTPUT); // begin initialization BLE.begin(); - Serial1.println(BLE.address()); + Serial.println(BLE.address()); // set advertised local name and service UUID BLE.setLocalName("LED"); @@ -45,11 +45,11 @@ void loop() { i++; if (temp) { // central connected to peripheral - Serial1.print(i); - Serial1.print(F("Connected to central: ")); - Serial1.println(central.address()); + Serial.print(i); + Serial.print(F("Connected to central: ")); + Serial.println(central.address()); - Serial1.print(temp); + Serial.print(temp); while (central.connected()) { // central still connected to peripheral @@ -57,18 +57,18 @@ i++; char ledValue = *switchCharacteristic.value(); // central wrote new value to characteristic, update LED if (ledValue) { - Serial1.println(F("LED on")); + Serial.println(F("LED on")); digitalWrite(LED_PIN, HIGH); } else { - Serial1.println(F("LED off")); + Serial.println(F("LED off")); digitalWrite(LED_PIN, LOW); } } } // central disconnected - Serial1.print(F("Disconnected from central: ")); - Serial1.println(central.address()); + Serial.print(F("Disconnected from central: ")); + Serial.println(central.address()); } //delay (1000); } diff --git a/libraries/BLE/src/BLECallbacks.cpp b/libraries/BLE/src/BLECallbacks.cpp index bd606b10..80e8ed9e 100644 --- a/libraries/BLE/src/BLECallbacks.cpp +++ b/libraries/BLE/src/BLECallbacks.cpp @@ -97,12 +97,14 @@ uint8_t profile_notify_process (bt_conn_t *conn, //BLEAttribute* notifyatt = peripheral->attribute(params); // Find attribute by params BLECharacteristicImp* chrc = NULL; BLEDevice bleDevice(bt_conn_get_dst(conn)); - BLEProfileManager::instance()->characteristic(bleDevice, params->value_handle); + chrc = BLEProfileManager::instance()->characteristic(bleDevice, params->value_handle); //assert(notifyatt->type() == BLETypeCharacteristic); pr_debug(LOG_MODULE_APP, "%s1", __FUNCTION__); - - chrc->setValue((const unsigned char *)data, length); + if (NULL != chrc) + { + chrc->setValue((const unsigned char *)data, length); + } return BT_GATT_ITER_CONTINUE; } diff --git a/libraries/BLE/src/BLECharacteristic.cpp b/libraries/BLE/src/BLECharacteristic.cpp index 071089fc..57d4a7ab 100644 --- a/libraries/BLE/src/BLECharacteristic.cpp +++ b/libraries/BLE/src/BLECharacteristic.cpp @@ -127,7 +127,7 @@ bool BLECharacteristic::writeValue(const byte value[], int length) if (NULL != characteristicImp) { - characteristicImp->setValue((const unsigned char *)value, (uint16_t)length); + characteristicImp->writeValue(value, length); retVar = true; } return retVar; @@ -189,8 +189,14 @@ bool BLECharacteristic::canNotify() bool BLECharacteristic::canIndicate() { - // TODO: Need more confirmation - return false; + bool retVar = false; + BLECharacteristicImp *characteristicImp = getImplementation(); + + if (NULL != characteristicImp) + { + retVar = characteristicImp->canIndicate(); + } + return retVar; } bool BLECharacteristic::canRead() @@ -245,8 +251,7 @@ bool BLECharacteristic::subscribe() if (NULL != characteristicImp) { - // TODO: Central feature. Peripheral first - //retVar = characteristicImp->s(); + retVar = characteristicImp->subscribe(); } return retVar; } @@ -258,8 +263,7 @@ bool BLECharacteristic::unsubscribe() if (NULL != characteristicImp) { - // TODO: Central feature - //retVar = characteristicImp->canNotify(); + retVar = characteristicImp->unsubscribe(); } return retVar; } diff --git a/libraries/BLE/src/BLECharacteristicImp.cpp b/libraries/BLE/src/BLECharacteristicImp.cpp index fee461f4..eb3cdc4f 100644 --- a/libraries/BLE/src/BLECharacteristicImp.cpp +++ b/libraries/BLE/src/BLECharacteristicImp.cpp @@ -38,6 +38,7 @@ BLECharacteristicImp::BLECharacteristicImp(const bt_uuid_t* uuid, _cccd_handle(0), _attr_chrc_value(NULL), _attr_cccd(NULL), + _subscribed(false), _ble_device() { _value_size = BLE_MAX_ATTR_DATA_LEN;// Set as MAX value. TODO: long read/write need to twist @@ -100,6 +101,7 @@ BLECharacteristicImp::BLECharacteristicImp(BLECharacteristic& characteristic, _cccd_handle(0), _attr_chrc_value(NULL), _attr_cccd(NULL), + _subscribed(false), _ble_device() { unsigned char properties = characteristic._properties; @@ -270,7 +272,117 @@ bool BLECharacteristicImp::valueUpdated() bool BLECharacteristicImp::subscribed() { - return (_gatt_chrc.properties & (BT_GATT_CHRC_NOTIFY | BT_GATT_CHRC_INDICATE)); + return _subscribed; +} + +bool BLECharacteristicImp::canNotify() +{ + if (false == BLEUtils::isLocalBLE(_ble_device)) + { + // GATT server can't subscribe + return false; + } + return (_ccc_value.value & BT_GATT_CCC_NOTIFY); +} + +bool BLECharacteristicImp::canIndicate() +{ + if (false == BLEUtils::isLocalBLE(_ble_device)) + { + // GATT server can't subscribe + return false; + } + return (_ccc_value.value & BT_GATT_CCC_INDICATE); +} + +bool BLECharacteristicImp::unsubscribe(void) +{ + int retval = 0; + bt_conn_t* conn = NULL; + + if (true == BLEUtils::isLocalBLE(_ble_device)) + { + // GATT server can't subscribe + return false; + } + + if (false == _subscribed) + { + return true; + } + + _sub_params.value = 0; + + if (0 == (_gatt_chrc.properties & (BT_GATT_CHRC_NOTIFY | BT_GATT_CHRC_INDICATE))) + { + // The characteristic not support the Notify and Indicate + return false; + } + + conn = bt_conn_lookup_addr_le(_ble_device.bt_le_address()); + if (NULL == conn) + { + return false; + } + + bt_addr_le_copy(&_sub_params._peer, bt_conn_get_dst(conn)); + _sub_params.ccc_handle = _cccd_handle; + _sub_params.value_handle = _value_handle; + + // Enable CCCD to allow peripheral send Notification/Indication + retval = bt_gatt_unsubscribe(conn, &_sub_params); + bt_conn_unref(conn); + if (0 == retval) + { + _subscribed = false; + } + return _subscribed; +} + +bool BLECharacteristicImp::subscribe(void) +{ + int retval = 0; + bt_conn_t* conn = NULL; + + if (true == BLEUtils::isLocalBLE(_ble_device)) + { + // GATT server can't subscribe + return false; + } + + if (_gatt_chrc.properties & BT_GATT_CHRC_NOTIFY) + { + _sub_params.value |= BT_GATT_CCC_NOTIFY; + } + + if (_gatt_chrc.properties & BT_GATT_CHRC_INDICATE) + { + _sub_params.value |= BT_GATT_CCC_INDICATE; + } + + if (_sub_params.value == 0) + { + return false; + } + + conn = bt_conn_lookup_addr_le(_ble_device.bt_le_address()); + if (NULL == conn) + { + return false; + } + + bt_addr_le_copy(&_sub_params._peer, bt_conn_get_dst(conn)); + _sub_params.ccc_handle = _cccd_handle; + _sub_params.value_handle = _value_handle; + + // Enable CCCD to allow peripheral send Notification/Indication + retval = bt_gatt_subscribe(conn, &_sub_params); + bt_conn_unref(conn); + if (0 == retval) + { + _subscribed = true; + } + return _subscribed; } void @@ -505,7 +617,7 @@ int BLECharacteristicImp::updateProfile(bt_gatt_attr_t *attr_start, int& index) index++; counter++; - if (this->subscribed()) + if (0 != (_gatt_chrc.properties & (BT_GATT_CHRC_NOTIFY | BT_GATT_CHRC_INDICATE))) { // Descriptor memset(start, 0, sizeof(bt_gatt_attr_t)); diff --git a/libraries/BLE/src/BLECharacteristicImp.h b/libraries/BLE/src/BLECharacteristicImp.h index c2ab8f22..73caf6fb 100644 --- a/libraries/BLE/src/BLECharacteristicImp.h +++ b/libraries/BLE/src/BLECharacteristicImp.h @@ -127,6 +127,10 @@ class BLECharacteristicImp: public BLEAttribute{ */ bool subscribed(void); bool canNotify(); + bool canIndicate(); + + bool subscribe(void); + bool unsubscribe(void); /** * Provide a function to be called when events related to this Characteristic are raised @@ -162,12 +166,11 @@ class BLECharacteristicImp: public BLEAttribute{ bool write(const unsigned char value[], uint16_t length); - void setCCCDHandle(uint16_t handle); - void setHandle(uint16_t handle); int descriptorCount() const; uint8_t discoverResponseProc(bt_conn_t *conn, const bt_gatt_attr_t *attr, bt_gatt_discover_params_t *params); + bool discoverAttributes(BLEDevice* device); BLEDescriptorImp* descrptor(const bt_uuid_t* uuid); @@ -284,6 +287,9 @@ class BLECharacteristicImp: public BLEAttribute{ bt_gatt_subscribe_params_t* getSubscribeParams(); private: + + void setCCCDHandle(uint16_t handle); + void setHandle(uint16_t handle); void _setValue(const uint8_t value[], uint16_t length); bool isClientCharacteristicConfigurationDescriptor(const bt_uuid_t* uuid); @@ -311,6 +317,7 @@ class BLECharacteristicImp: public BLEAttribute{ // For GATT Client to subscribe the Notification/Indication bt_gatt_subscribe_params_t _sub_params; + bool _subscribed; bool _reading; bt_gatt_read_params_t _read_params; // GATT read parameter diff --git a/libraries/BLE/src/BLEProfileManager.cpp b/libraries/BLE/src/BLEProfileManager.cpp index 9eb441ef..52c4fe38 100644 --- a/libraries/BLE/src/BLEProfileManager.cpp +++ b/libraries/BLE/src/BLEProfileManager.cpp @@ -346,6 +346,30 @@ BLECharacteristicImp* BLEProfileManager::characteristic(const BLEDevice &bledevi return characteristicImp; } +BLECharacteristicImp* BLEProfileManager::characteristic(const BLEDevice &bledevice, uint16_t handle) +{ + BLECharacteristicImp* characteristicImp = NULL; + BLEServiceLinkNodeHeader* serviceHeader = getServiceHeader(bledevice); + if (NULL == serviceHeader) + { + // Doesn't find the service + return NULL; + } + + BLEServiceNodePtr node = serviceHeader->next; + while (node != NULL) + { + BLEServiceImp *service = node->value; + characteristicImp = service->characteristic(handle); + if (NULL != characteristicImp) + { + break; + } + node = node->next; + } + return characteristicImp; +} + BLECharacteristicImp* BLEProfileManager::characteristic(const BLEDevice &bledevice, const char* uuid, int index) @@ -846,214 +870,5 @@ uint8_t BLEProfileManager::serviceReadRspProc(bt_conn_t *conn, return BT_GATT_ITER_STOP; } -#if 0 -void BLEProfileManager::characteristicDiscoverRsp(const bt_gatt_attr_t *attr, BLEAttribute* bleattr) -{ - bt_gatt_attr_t *attr_dec = declarationAttr(bleattr); - if ((NULL != attr) && (NULL != attr_dec)) - { - if (bt_uuid_cmp (attr_dec->uuid, attr->uuid) == 0) - { - attr_dec++; - attr_dec->handle = attr->handle + 1; - } - } - bleattr->discover(attr, &_discover_params); -} - -void BLEProfileManager::descriptorDiscoverRsp(const bt_gatt_attr_t *attr, BLEAttribute* bleattr) -{ - int err; - bt_gatt_attr_t *attr_dec = declarationAttr(bleattr); - if (BLETypeCharacteristic == bleattr->type()) - { - BLECharacteristic *chrc = (BLECharacteristic *)bleattr; - if (bt_uuid_cmp (chrc->getClientCharacteristicConfigUuid(), attr->uuid) == 0) - { - //CCCD - bt_gatt_attr_t *attr_chrc = attr_dec + 1; - bt_gatt_attr_t *attr_cccd = attr_dec + 2; - bt_gatt_subscribe_params_t *sub_param_tmp = chrc->getSubscribeParams(); - bt_gatt_subscribe_params_t *sub_param = _sub_param + _sub_param_idx; - bt_conn_t *conn = bt_conn_lookup_addr_le(_peripheral->bt_le_address()); - if (NULL == conn) - { - // Link lost - return; - } - - _sub_param_idx++; - attr_cccd->handle = attr->handle; - memcpy(sub_param, sub_param_tmp, sizeof(bt_gatt_subscribe_params_t)); - sub_param->ccc_handle = attr_cccd->handle; - sub_param->value_handle = attr_chrc->handle; - - // Enable CCCD to allow peripheral send Notification/Indication - err = bt_gatt_subscribe(conn, sub_param); - bt_conn_unref(conn); - if (err && err != -EALREADY) - { - pr_debug(LOG_MODULE_APP, "Subscribe failed (err %d)\n", err); - } - bleattr->discover(attr, &_discover_params); - } - else - { - // Not CCCD - // If want to support more descriptor, - // change the offset 3 as a loop to search the ATTR - bt_gatt_attr_t *attr_descriptor = attr_dec + 3; - if (attr_descriptor->uuid != NULL && - bt_uuid_cmp (attr_descriptor->uuid, attr->uuid) == 0) - { - attr_descriptor->handle = attr->handle; - } - } - } - else if (BLETypeDescriptor == bleattr->type()) - { - bt_gatt_attr_t *attr_descriptor = attr_dec++; // The descriptor is separate - if (bt_uuid_cmp (attr_dec->uuid, attr->uuid) == 0) - { - attr_descriptor->handle = attr->handle; - } - bleattr->discover(attr, &_discover_params); - } -} - -uint8_t BLEProfileManager::discover(const bt_gatt_attr_t *attr) -{ - BLEAttribute* attribute_tmp = NULL; - int i; - int err; - uint8_t ret = BT_GATT_ITER_STOP; - bool send_discover = false; - - for (i = 0; i < _num_attributes; i++) - { - // Find the discovering attribute - attribute_tmp = _attributes[i]; - if (attribute_tmp->discovering()) - { - if (NULL == attr) - { - attribute_tmp->discover(attr, &_discover_params); - break; - } - // Discover success - switch (_discover_params.type) - { - case BT_GATT_DISCOVER_CHARACTERISTIC: - { - characteristicDiscoverRsp(attr, attribute_tmp); - send_discover = true; - break; - } - case BT_GATT_DISCOVER_DESCRIPTOR: - { - descriptorDiscoverRsp(attr, attribute_tmp); - break; - } - case BT_GATT_DISCOVER_PRIMARY: - send_discover = true; - default: - { - attribute_tmp->discover(attr, &_discover_params); - break; - } - } - break; - } - } - - // Find next attribute to discover - if (attribute_tmp->discovering() == false) - { - // Current attribute complete discovery - i++; - while (i < _num_attributes) - { - attribute_tmp = _attributes[i]; - if (attribute_tmp->type() == BLETypeDescriptor) - { - // The descriptor may have been discovered by previous descriptor - bt_gatt_attr_t *attr_gatt = NULL; - for (int j = 0; j < _attr_index; j++) - { - attr_gatt = _attr_base + i; - if (attribute_tmp->uuid() == attr_gatt->uuid) - { - break; - } - } - - if (attr_gatt->handle != 0) - { - // Skip discovered descriptor - i++; - continue; - } - } - - attribute_tmp->discover(&_discover_params); - ret = BT_GATT_ITER_CONTINUE; - break; - } - } - else - { - ret = BT_GATT_ITER_CONTINUE; - } - - // Send the discover request if necessary - if (send_discover && attribute_tmp->discovering()) - { - bt_conn_t *conn = bt_conn_lookup_addr_le(_peripheral->bt_le_address()); - - ret = BT_GATT_ITER_STOP; - if (NULL == conn) - { - // Link lost - pr_debug(LOG_MODULE_APP, "Can't find connection\n"); - return ret; - } - err = bt_gatt_discover(conn, &_discover_params); - bt_conn_unref(conn); - if (err) - { - pr_debug(LOG_MODULE_APP, "Discover failed(err %d)\n", err); - return ret; - } - } - return ret; -} - - -void BLEProfileManager::discover() -{ - int err; - BLEService *serviceattr = (BLEService *)_attributes[0]; - bt_conn_t *conn = bt_conn_lookup_addr_le(_peripheral->bt_le_address()); - - if (NULL == conn) - { - // Link lost - pr_debug(LOG_MODULE_APP, "Can't find connection\n"); - return; - } - - // Reset start handle - _discover_params.start_handle = 0x0001; - serviceattr->discover(&_discover_params); - - err = bt_gatt_discover(conn, &_discover_params); - bt_conn_unref(conn); - if (err) - { - pr_debug(LOG_MODULE_APP, "Discover failed(err %d)\n", err); - return; - } -} -#endif diff --git a/libraries/BLE/src/BLEProfileManager.h b/libraries/BLE/src/BLEProfileManager.h index b203fedc..00b12cd7 100644 --- a/libraries/BLE/src/BLEProfileManager.h +++ b/libraries/BLE/src/BLEProfileManager.h @@ -89,6 +89,8 @@ class BLEProfileManager{ const char* uuid); BLECharacteristicImp* characteristic(const BLEDevice &bledevice, int index); + BLECharacteristicImp* characteristic(const BLEDevice &bledevice, + uint16_t handle); BLEServiceImp* service(const BLEDevice &bledevice, const char * uuid) const; BLEServiceImp* service(const BLEDevice &bledevice, int index) const; BLEServiceImp* service(const BLEDevice &bledevice, const bt_uuid_t* uuid) const; From 30b88ef6d9292c664c20cb473d5d66b1136031b3 Mon Sep 17 00:00:00 2001 From: lianggao Date: Tue, 25 Oct 2016 16:35:26 +0800 Subject: [PATCH 06/22] Fix the callback issue and the Klock scan issues 1. Fix the callback doesn't work issue 2. Fix the Klock scan issue --- libraries/BLE/src/BLECharacteristic.cpp | 41 ++++++++++++++++--- libraries/BLE/src/BLECharacteristic.h | 6 ++- libraries/BLE/src/BLECharacteristicImp.cpp | 26 ++++++++---- libraries/BLE/src/BLEDescriptor.cpp | 11 +++-- libraries/BLE/src/BLEDescriptor.h | 4 +- libraries/BLE/src/BLEDevice.cpp | 4 +- libraries/BLE/src/BLEDevice.h | 8 ++-- libraries/BLE/src/BLEDeviceManager.cpp | 47 ++++++++++++++++++++-- libraries/BLE/src/BLEDeviceManager.h | 4 ++ libraries/BLE/src/BLEProfileManager.cpp | 28 +++++++++++-- libraries/BLE/src/BLEProfileManager.h | 3 +- libraries/BLE/src/BLEServiceImp.cpp | 9 +++-- libraries/BLE/src/BLEUtils.cpp | 2 +- 13 files changed, 153 insertions(+), 40 deletions(-) diff --git a/libraries/BLE/src/BLECharacteristic.cpp b/libraries/BLE/src/BLECharacteristic.cpp index 57d4a7ab..0ead104f 100644 --- a/libraries/BLE/src/BLECharacteristic.cpp +++ b/libraries/BLE/src/BLECharacteristic.cpp @@ -9,15 +9,16 @@ BLECharacteristic::BLECharacteristic(): _bledev(), _internal(NULL), _properties(0), - _value_size(0), _value(NULL) + _value_size(0), _value(NULL),_event_handlers(NULL) { memset(_uuid_cstr, 0, sizeof(_uuid_cstr)); } BLECharacteristic::BLECharacteristic(const char* uuid, unsigned char properties, - unsigned char valueSize): - _bledev(), _internal(NULL), _properties(properties), _value(NULL) + unsigned short valueSize): + _bledev(), _internal(NULL), _properties(properties), _value(NULL), + _event_handlers(NULL) { bt_uuid_128 bt_uuid_tmp; _value_size = valueSize > BLE_MAX_ATTR_LONGDATA_LEN ? BLE_MAX_ATTR_LONGDATA_LEN : valueSize; @@ -37,7 +38,7 @@ BLECharacteristic::BLECharacteristic(const char* uuid, BLECharacteristic::BLECharacteristic(BLECharacteristicImp *characteristicImp, const BLEDevice *bleDev): _bledev(bleDev), _internal(characteristicImp), - _value(NULL) + _value(NULL),_event_handlers(NULL) { BLEUtils::uuidBT2String(characteristicImp->bt_uuid(), _uuid_cstr); _properties = characteristicImp->properties(); @@ -51,6 +52,12 @@ BLECharacteristic::~BLECharacteristic() bfree(_value); _value = NULL; } + + if (_event_handlers != NULL) + { + bfree(_event_handlers); + _event_handlers = NULL; + } } const char* BLECharacteristic::uuid() const @@ -329,9 +336,33 @@ BLEDescriptor BLECharacteristic::descriptor(const char * uuid, int index) const // TODO: Not support now return BLEDescriptor(); } + void BLECharacteristic::setEventHandler(BLECharacteristicEvent event, BLECharacteristicEventHandler eventHandler) -{} +{ + BLECharacteristicImp *characteristicImp = getImplementation(); + if (event >= BLECharacteristicEventLast) + { + return; + } + + if (NULL != characteristicImp) + { + characteristicImp->setEventHandler(event, eventHandler); + } + else + { + if (_event_handlers == NULL) + { + _event_handlers = (BLECharacteristicEventHandler*)balloc(sizeof(BLECharacteristicEventHandler) * BLECharacteristicEventLast, NULL); + } + + if (_event_handlers != NULL) + { + _event_handlers[event] = eventHandler; + } + } +} void diff --git a/libraries/BLE/src/BLECharacteristic.h b/libraries/BLE/src/BLECharacteristic.h index 19a10266..0cf0836f 100644 --- a/libraries/BLE/src/BLECharacteristic.h +++ b/libraries/BLE/src/BLECharacteristic.h @@ -69,7 +69,7 @@ class BLECharacteristic //: public BLEAttributeWithValue */ BLECharacteristic(const char* uuid, unsigned char properties, - unsigned char valueSize); + unsigned short valueSize); /** * @brief Create a characteristic with string value @@ -498,8 +498,10 @@ class BLECharacteristic //: public BLEAttributeWithValue friend class BLECharacteristicImp; unsigned char _properties; // The characteristic property - unsigned char _value_size; // The value size + unsigned short _value_size; // The value size unsigned char* _value; // The value. Will delete after create the _internal + + BLECharacteristicEventHandler* _event_handlers; }; #endif diff --git a/libraries/BLE/src/BLECharacteristicImp.cpp b/libraries/BLE/src/BLECharacteristicImp.cpp index eb3cdc4f..d0d7893e 100644 --- a/libraries/BLE/src/BLECharacteristicImp.cpp +++ b/libraries/BLE/src/BLECharacteristicImp.cpp @@ -143,7 +143,16 @@ BLECharacteristicImp::BLECharacteristicImp(BLECharacteristic& characteristic, _sub_params.value |= BT_GATT_CCC_INDICATE; } _gatt_chrc.uuid = (bt_uuid_t*)this->bt_uuid();//&_characteristic_uuid;//this->uuid(); - memset(_event_handlers, 0, sizeof(_event_handlers)); + if (NULL != characteristic._event_handlers) + { + memcpy(_event_handlers, + characteristic._event_handlers, + sizeof(_event_handlers)); + } + else + { + memset(_event_handlers, 0, sizeof(_event_handlers)); + } _sub_params.notify = profile_notify_process; @@ -182,6 +191,7 @@ BLECharacteristicImp::properties() const bool BLECharacteristicImp::writeValue(const byte value[], int length) { int status; + bool retVal = false; _setValue(value, length); @@ -192,16 +202,16 @@ bool BLECharacteristicImp::writeValue(const byte value[], int length) { // Notify for peripheral. status = bt_gatt_notify(NULL, _attr_chrc_value, value, length, NULL); - if (0 != status) + if (0 == status) { - return false; + retVal = true; } } //Not schedule write request for central // The write request may failed. // If user want to get latest set value. Call read and get the real value - return true; + return retVal; } bool @@ -389,7 +399,7 @@ void BLECharacteristicImp::setEventHandler(BLECharacteristicEvent event, BLECharacteristicEventHandler callback) { noInterrupts(); - if (event < sizeof(_event_handlers)) { + if (event < BLECharacteristicEventLast) { _event_handlers[event] = callback; } interrupts(); @@ -666,7 +676,7 @@ int BLECharacteristicImp::addDescriptor(BLEDescriptor& descriptor) BLEDescriptorNodePtr node = link_node_create(descriptorImp); if (NULL == node) { - delete[] descriptorImp; + delete descriptorImp; return BLE_STATUS_NO_MEMORY; } link_node_insert_last(&_descriptors_header, node); @@ -693,7 +703,7 @@ int BLECharacteristicImp::addDescriptor(const bt_uuid_t* uuid, BLEDescriptorNodePtr node = link_node_create(descriptorImp); if (NULL == node) { - delete[] descriptorImp; + delete descriptorImp; return BLE_STATUS_NO_MEMORY; } link_node_insert_last(&_descriptors_header, node); @@ -736,7 +746,7 @@ void BLECharacteristicImp::releaseDescriptors() while (NULL != node) { BLEDescriptorImp* descriptorImp = node->value; - delete[] descriptorImp; + delete descriptorImp; link_node_remove_first(&_descriptors_header); node = link_node_get_first(&_descriptors_header); } diff --git a/libraries/BLE/src/BLEDescriptor.cpp b/libraries/BLE/src/BLEDescriptor.cpp index 88aac259..48f4fa5b 100644 --- a/libraries/BLE/src/BLEDescriptor.cpp +++ b/libraries/BLE/src/BLEDescriptor.cpp @@ -19,12 +19,17 @@ #include "BLEDescriptor.h" #include "BLEUtils.h" -BLEDescriptor::BLEDescriptor() -{} +BLEDescriptor::BLEDescriptor(): + _properties(0), + _value_size(0), + _value(NULL) +{ + memset(_uuid_cstr, 0, sizeof (_uuid_cstr)); +} BLEDescriptor::BLEDescriptor(const char* uuid, const unsigned char value[], - unsigned char valueLength): + unsigned short valueLength): _bledev() { bt_uuid_128_t uuid_tmp; diff --git a/libraries/BLE/src/BLEDescriptor.h b/libraries/BLE/src/BLEDescriptor.h index 8c0391e3..a61a80b4 100644 --- a/libraries/BLE/src/BLEDescriptor.h +++ b/libraries/BLE/src/BLEDescriptor.h @@ -28,7 +28,7 @@ class BLEDescriptor { public: BLEDescriptor(); - BLEDescriptor(const char* uuid, const unsigned char value[], unsigned char valueLength); // create a descriptor the specified uuid and value + BLEDescriptor(const char* uuid, const unsigned char value[], unsigned short valueLength); // create a descriptor the specified uuid and value BLEDescriptor(const char* uuid, const char* value); // create a descriptor the specified uuid and string value virtual ~BLEDescriptor(); @@ -91,7 +91,7 @@ class BLEDescriptor unsigned char _properties; // The characteristic property - unsigned char _value_size; // The value size + unsigned short _value_size; // The value size unsigned char* _value; // The value. Will delete after create the _internal }; diff --git a/libraries/BLE/src/BLEDevice.cpp b/libraries/BLE/src/BLEDevice.cpp index 65c28eae..17a65926 100644 --- a/libraries/BLE/src/BLEDevice.cpp +++ b/libraries/BLE/src/BLEDevice.cpp @@ -56,7 +56,7 @@ BLEDevice::BLEDevice(const bt_addr_le_t* bleaddress): BLEDevice::BLEDevice(const BLEDevice* bleaddress) { memcpy(&_bt_addr, bleaddress->bt_le_address(), sizeof(bt_addr_le_t)); - memcpy(&_conn_param, bleaddress->bt_conn_param(), sizeof (ble_conn_param_t)); + memcpy(&_conn_param, &bleaddress->_conn_param, sizeof (ble_conn_param_t)); } BLEDevice::~BLEDevice() @@ -409,7 +409,7 @@ BLECharacteristic BLEDevice::characteristic(const char * uuid, int index) const void BLEDevice::setEventHandler(BLEDeviceEvent event, BLEDeviceEventHandler eventHandler) { - // TODO: + BLEDeviceManager::instance()->setEventHandler(event, eventHandler); } const bt_addr_le_t* BLEDevice::bt_le_address() const diff --git a/libraries/BLE/src/BLEDevice.h b/libraries/BLE/src/BLEDevice.h index 4d6b8933..153c8577 100644 --- a/libraries/BLE/src/BLEDevice.h +++ b/libraries/BLE/src/BLEDevice.h @@ -28,12 +28,12 @@ //class BLEDevice; enum BLEDeviceEvent { - BLEDiscovered = 0, // Discover profile completed - BLEConnected = 1, // BLE device connected - BLEDisconnected = 2, // BLE device disconnected - BLEConParamUpdate = 3, // Update the connection parameter + BLEConnected = 0, // BLE device connected + BLEDisconnected = 1, // BLE device disconnected + BLEConParamUpdate = 2, // Update the connection parameter // Connection update request in central // Connection parameter updated in peripheral + BLEDeviceLastEvent }; typedef void (*BLEDeviceEventHandler)(BLEDevice& device); diff --git a/libraries/BLE/src/BLEDeviceManager.cpp b/libraries/BLE/src/BLEDeviceManager.cpp index bdeb4b49..6b7d6eaa 100644 --- a/libraries/BLE/src/BLEDeviceManager.cpp +++ b/libraries/BLE/src/BLEDeviceManager.cpp @@ -68,6 +68,7 @@ BLEDeviceManager::BLEDeviceManager(): memset(&_adv_accept_critical, 0, sizeof(_adv_accept_critical)); memset(_peer_peripheral, 0, sizeof(_peer_peripheral)); + memset(_device_events, 0, sizeof(_device_events)); } BLEDeviceManager::~BLEDeviceManager() @@ -383,7 +384,9 @@ BLEDevice BLEDeviceManager::peripheral() } void BLEDeviceManager::linkLost() -{} +{ + +} bool BLEDeviceManager::startScanning() { @@ -405,7 +408,7 @@ bool BLEDeviceManager::startScanningWithDuplicates() bool BLEDeviceManager::stopScanning() { int err = bt_le_scan_stop(); - if (err) + if (0 != err) { pr_info(LOG_MODULE_BLE, "Stop LE scan failed (err %d)\n", err); return false; @@ -531,6 +534,7 @@ bool BLEDeviceManager::connectToDevice(BLEDevice &device) bool retval = false; pr_debug(LOG_MODULE_BLE, "%s-%d-1", __FUNCTION__, __LINE__); + // Find free peripheral Items for (int i = 0; i < BLE_MAX_CONN_CFG; i++) { @@ -555,7 +559,7 @@ bool BLEDeviceManager::connectToDevice(BLEDevice &device) } pr_debug(LOG_MODULE_BLE, "%s-%d:link_existed-%d unused-%p", __FUNCTION__, __LINE__, link_existed, unused); - if (!link_existed) + if (!link_existed && NULL != unused) { pr_debug(LOG_MODULE_BLE, "%s-%d-Device:%s", __FUNCTION__, __LINE__, device.address().c_str()); // Send connect request @@ -589,6 +593,13 @@ BLEDeviceManager* BLEDeviceManager::instance() return _instance; } +void BLEDeviceManager::setEventHandler(BLEDeviceEvent event, + BLEDeviceEventHandler eventHandler) +{ + if (event < BLEDeviceLastEvent) + _device_events[event] = eventHandler; +} + void BLEDeviceManager::handleConnectEvent(bt_conn_t *conn, uint8_t err) { struct bt_conn_info role_info; @@ -605,6 +616,12 @@ void BLEDeviceManager::handleConnectEvent(bt_conn_t *conn, uint8_t err) // Peripheral has established the connection with this Central device BLEProfileManager::instance()->handleConnectedEvent(bt_conn_get_dst(conn)); } + + if (NULL != _device_events[BLEConnected]) + { + BLEDevice tempdev(bt_conn_get_dst(conn)); + _device_events[BLEConnected](tempdev); + } } void BLEDeviceManager::handleDisconnectEvent(bt_conn_t *conn, uint8_t reason) @@ -619,7 +636,25 @@ void BLEDeviceManager::handleDisconnectEvent(bt_conn_t *conn, uint8_t reason) } else { + bt_addr_le_t* temp = NULL; + const bt_addr_le_t* disConnAddr = bt_conn_get_dst(conn); + for (int i = 0; i < BLE_MAX_CONN_CFG; i++) + { + temp = &_peer_peripheral[i]; + if (bt_addr_le_cmp(temp, disConnAddr) == 0) + { + memset(temp, 0, sizeof(bt_addr_le_t)); + break; + } + } // Peripheral has established the connection with this Central device + BLEProfileManager::instance()->handleDisconnectedEvent(bt_conn_get_dst(conn)); + } + + if (NULL != _device_events[BLEDisconnected]) + { + BLEDevice tempdev(bt_conn_get_dst(conn)); + _device_events[BLEDisconnected](tempdev); } } @@ -628,7 +663,11 @@ void BLEDeviceManager::handleParamUpdated (bt_conn_t *conn, uint16_t latency, uint16_t timeout) { - + if (NULL != _device_events[BLEConParamUpdate]) + { + BLEDevice tempdev(bt_conn_get_dst(conn)); + _device_events[BLEConParamUpdate](tempdev); + } } bool BLEDeviceManager::advertiseDataProc(uint8_t type, diff --git a/libraries/BLE/src/BLEDeviceManager.h b/libraries/BLE/src/BLEDeviceManager.h index d095a7b2..1d5a8dcc 100644 --- a/libraries/BLE/src/BLEDeviceManager.h +++ b/libraries/BLE/src/BLEDeviceManager.h @@ -100,6 +100,8 @@ class BLEDeviceManager */ bool disconnect(BLEDevice *device); + void setEventHandler(BLEDeviceEvent event, + BLEDeviceEventHandler eventHandler); /** * @brief Set the service UUID that the BLE Peripheral Device advertises * @@ -412,6 +414,8 @@ class BLEDeviceManager // Connected device object bt_addr_le_t _peer_central; bt_addr_le_t _peer_peripheral[BLE_MAX_CONN_CFG]; + + BLEDeviceEventHandler _device_events[BLEDeviceLastEvent]; }; #endif diff --git a/libraries/BLE/src/BLEProfileManager.cpp b/libraries/BLE/src/BLEProfileManager.cpp index 52c4fe38..634a8ed3 100644 --- a/libraries/BLE/src/BLEProfileManager.cpp +++ b/libraries/BLE/src/BLEProfileManager.cpp @@ -102,7 +102,7 @@ BLEProfileManager::addService (BLEDevice &bledevice, BLEService& service) BLEServiceNodePtr node = link_node_create(serviceImp); if (NULL == node) { - delete[] serviceImp; + delete serviceImp; return BLE_STATUS_NO_MEMORY; } link_node_insert_last(serviceheader, node); @@ -296,9 +296,8 @@ int BLEProfileManager::registerProfile(BLEDevice &bledevice) return ret; } -void BLEProfileManager::clearProfile(BLEDevice &bledevice) +void BLEProfileManager::clearProfile(BLEServiceLinkNodeHeader* serviceHeader) { - BLEServiceLinkNodeHeader* serviceHeader = getServiceHeader(bledevice); if (NULL == serviceHeader) { return; @@ -309,7 +308,7 @@ void BLEProfileManager::clearProfile(BLEDevice &bledevice) while (NULL != node) { BLEServiceImp *service = node->value; - delete[] service; + delete service; link_node_remove_first(serviceHeader); node = link_node_get_first(serviceHeader); } @@ -496,6 +495,27 @@ void BLEProfileManager::handleConnectedEvent(const bt_addr_le_t* deviceAddr) bt_addr_le_copy(&_addresses[index], deviceAddr); } +void BLEProfileManager::handleDisconnectedEvent(const bt_addr_le_t* deviceAddr) +{ + BLEServiceLinkNodeHeader* serviceheader = NULL; + int i; + for (i = 0; i < BLE_MAX_CONN_CFG; i++) + { + if ((bt_addr_le_cmp(deviceAddr, &_addresses[i]) == 0)) + { + break; + } + } + if (i >= BLE_MAX_CONN_CFG) + { + return; + } + + serviceheader = &_service_header_array[i]; + clearProfile(serviceheader); + memset(&_addresses[i], 0, sizeof(bt_addr_le_t)); +} + bool BLEProfileManager::discoverAttributes(BLEDevice* device) { int err; diff --git a/libraries/BLE/src/BLEProfileManager.h b/libraries/BLE/src/BLEProfileManager.h index 00b12cd7..2e6cf833 100644 --- a/libraries/BLE/src/BLEProfileManager.h +++ b/libraries/BLE/src/BLEProfileManager.h @@ -104,6 +104,7 @@ class BLEProfileManager{ bool discoverAttributes(BLEDevice* device); bool discoverService(BLEDevice* device, const bt_uuid_t* svc_uuid); void handleConnectedEvent(const bt_addr_le_t* deviceAddr); + void handleDisconnectedEvent(const bt_addr_le_t* deviceAddr); uint8_t serviceReadRspProc(bt_conn_t *conn, int err, bt_gatt_read_params_t *params, @@ -172,7 +173,7 @@ class BLEProfileManager{ * * @note none */ - void clearProfile(BLEDevice &bledevice); + void clearProfile(BLEServiceLinkNodeHeader* serviceHeader); void readService(const BLEDevice &bledevice, uint16_t handle); bool discovering(); diff --git a/libraries/BLE/src/BLEServiceImp.cpp b/libraries/BLE/src/BLEServiceImp.cpp index 5c0b4371..b67d289a 100644 --- a/libraries/BLE/src/BLEServiceImp.cpp +++ b/libraries/BLE/src/BLEServiceImp.cpp @@ -74,7 +74,7 @@ int BLEServiceImp::addCharacteristic(BLEDevice& bledevice, BLECharacteristic& ch BLECharacteristicNodePtr node = link_node_create(characteristicImp); if (NULL == node) { - delete[] characteristicImp; + delete characteristicImp; return BLE_STATUS_NO_MEMORY; } link_node_insert_last(&_characteristics_header, node); @@ -106,7 +106,7 @@ int BLEServiceImp::addCharacteristic(BLEDevice& bledevice, BLECharacteristicNodePtr node = link_node_create(characteristicImp); if (NULL == node) { - delete[] characteristicImp; + delete characteristicImp; return BLE_STATUS_NO_MEMORY; } link_node_insert_last(&_characteristics_header, node); @@ -165,14 +165,15 @@ int BLEServiceImp::getCharacteristicCount() void BLEServiceImp::releaseCharacteristic() { BLECharacteristicNodePtr node = link_node_get_first(&_characteristics_header); - + pr_debug(LOG_MODULE_BLE, "%s-%d", __FUNCTION__, __LINE__); while (NULL != node) { BLECharacteristicImp* characteristicImp = node->value; - delete[] characteristicImp; + delete characteristicImp; link_node_remove_first(&_characteristics_header); node = link_node_get_first(&_characteristics_header); } + pr_debug(LOG_MODULE_BLE, "%s-%d", __FUNCTION__, __LINE__); } diff --git a/libraries/BLE/src/BLEUtils.cpp b/libraries/BLE/src/BLEUtils.cpp index de2ba942..ed9311ba 100644 --- a/libraries/BLE/src/BLEUtils.cpp +++ b/libraries/BLE/src/BLEUtils.cpp @@ -21,7 +21,7 @@ void BLEUtils::macAddressString2BT(const char* mac_str, bt_addr_le_t &bd_addr) bd_addr.type = BT_ADDR_LE_PUBLIC; - for (int i = strLength - 1; i >= 0 && length < MAX_UUID_SIZE; i -= 2) + for (int i = strLength - 1; i >= 0 && length < BLE_ADDR_LEN; i -= 2) { if (mac_str[i] == ':') { From 5fc779bb58eca4a2fc48e26250f580df20a9958b Mon Sep 17 00:00:00 2001 From: lianggao Date: Thu, 27 Oct 2016 09:20:18 +0800 Subject: [PATCH 07/22] Add BLEPeripheral library back compatible features --- libraries/BLE/src/ArduinoBLE.h | 4 + libraries/BLE/src/BLECentral.cpp | 61 +++++ libraries/BLE/src/BLECentral.h | 45 ++++ libraries/BLE/src/BLECharacteristic.cpp | 13 +- libraries/BLE/src/BLECharacteristic.h | 14 +- libraries/BLE/src/BLEDevice.h | 2 +- libraries/BLE/src/BLEPeripheral.cpp | 200 ++++++++++++++++ libraries/BLE/src/BLEPeripheral.h | 69 ++++++ libraries/BLE/src/BLETypedCharacteristic.h | 172 ++++++++++++++ libraries/BLE/src/BLETypedCharacteristics.cpp | 60 +++++ libraries/BLE/src/BLETypedCharacteristics.h | 223 ++++++++++++++++++ 11 files changed, 858 insertions(+), 5 deletions(-) create mode 100644 libraries/BLE/src/BLECentral.cpp create mode 100644 libraries/BLE/src/BLECentral.h create mode 100644 libraries/BLE/src/BLEPeripheral.cpp create mode 100644 libraries/BLE/src/BLEPeripheral.h create mode 100644 libraries/BLE/src/BLETypedCharacteristic.h create mode 100644 libraries/BLE/src/BLETypedCharacteristics.cpp create mode 100644 libraries/BLE/src/BLETypedCharacteristics.h diff --git a/libraries/BLE/src/ArduinoBLE.h b/libraries/BLE/src/ArduinoBLE.h index 14fbddcc..d643b426 100644 --- a/libraries/BLE/src/ArduinoBLE.h +++ b/libraries/BLE/src/ArduinoBLE.h @@ -36,6 +36,10 @@ class BLECharacteristicImp; #include "BLEDescriptor.h" #include "BLEService.h" +#include "BLETypedCharacteristics.h" + +#include "BLECentral.h" +#include "BLEPeripheral.h" extern BLEDevice BLE; diff --git a/libraries/BLE/src/BLECentral.cpp b/libraries/BLE/src/BLECentral.cpp new file mode 100644 index 00000000..a751060e --- /dev/null +++ b/libraries/BLE/src/BLECentral.cpp @@ -0,0 +1,61 @@ +/* + BLE Central API (deprecated) + Copyright (c) 2016 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "ArduinoBLE.h" + +BLECentral::BLECentral(BLEDevice& device) : + _device(&device) +{ + +} + +bool BLECentral::connected(void) +{ + return _device.connected(); +} + +const char* BLECentral::address(void) const +{ + return _device.address().c_str(); +} + +bool BLECentral::disconnect(void) +{ + return _device.disconnect(); +} + +void BLECentral::poll(void) +{ + _device.poll(); +} + +BLECentral::operator bool(void) const +{ + return _device; +} + +bool BLECentral::operator==(const BLECentral& rhs) const +{ + return (_device == rhs._device); +} + +bool BLECentral::operator!=(const BLECentral& rhs) const +{ + return (_device != rhs._device); +} \ No newline at end of file diff --git a/libraries/BLE/src/BLECentral.h b/libraries/BLE/src/BLECentral.h new file mode 100644 index 00000000..b6148e12 --- /dev/null +++ b/libraries/BLE/src/BLECentral.h @@ -0,0 +1,45 @@ +/* + BLE Central API (deprecated) + Copyright (c) 2016 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef ARDUINO_CENTRAL_H +#define ARDUINO_CENTRAL_H + +class BLECentral { + public: + bool connected(void); // is the central connected + + const char* address(void) const; // address of the Central in string form + + bool disconnect(void); // Disconnect the central if it is connected + void poll(void); // Poll the central for events + + operator bool(void) const; + bool operator==(const BLECentral& rhs) const; + bool operator!=(const BLECentral& rhs) const; +protected: + friend void bleBackCompatiblePeripheralConnectHandler(BLEDevice central); + friend void bleBackCompatiblePeripheralDisconnectHandler(BLEDevice central); + friend class BLEPeripheral; + BLECentral(BLEDevice& device); + private: + + BLEDevice _device; +}; + +#endif \ No newline at end of file diff --git a/libraries/BLE/src/BLECharacteristic.cpp b/libraries/BLE/src/BLECharacteristic.cpp index 0ead104f..8b122c01 100644 --- a/libraries/BLE/src/BLECharacteristic.cpp +++ b/libraries/BLE/src/BLECharacteristic.cpp @@ -1,9 +1,11 @@ //#include "internal/ble_client.h" +#include "ArduinoBLE.h" + +#include "BLEUtils.h" #include "BLECharacteristic.h" #include "BLEProfileManager.h" -#include "BLEUtils.h" #include "BLECharacteristicImp.h" @@ -87,10 +89,10 @@ int BLECharacteristic::valueSize() //const return valuesize; } -const byte* BLECharacteristic::value() //const +const byte* BLECharacteristic::value() const { const byte* value_temp = NULL; - BLECharacteristicImp *characteristicImp = getImplementation(); + BLECharacteristicImp *characteristicImp = _internal;//getImplementation(); if (NULL != characteristicImp) { value_temp = characteristicImp->value(); @@ -127,6 +129,11 @@ byte BLECharacteristic::operator[] (int offset) //const return data; } +bool BLECharacteristic::setValue(const unsigned char value[], unsigned short length) +{ + return writeValue(value, (int)length); +} + bool BLECharacteristic::writeValue(const byte value[], int length) { bool retVar = false; diff --git a/libraries/BLE/src/BLECharacteristic.h b/libraries/BLE/src/BLECharacteristic.h index 0cf0836f..96b96b13 100644 --- a/libraries/BLE/src/BLECharacteristic.h +++ b/libraries/BLE/src/BLECharacteristic.h @@ -144,7 +144,7 @@ class BLECharacteristic //: public BLEAttributeWithValue * * @note none */ - virtual const byte* value();//const + virtual const byte* value() const;// /** * @brief Get the current length of the value @@ -168,6 +168,18 @@ class BLECharacteristic //: public BLEAttributeWithValue */ virtual byte operator[] (int offset);//const + /** + * Set the current value of the Characteristic + * + * @param[in] value New value to set, as a byte array. Data is stored in internal copy. + * @param[in] length Length, in bytes, of valid data in the array to write. + * Must not exceed maxLength set for this characteristic. + * + * @return bool true set value success, false on error + * @note GATT Server only + */ + bool setValue(const unsigned char value[], unsigned short length); + /** * @brief Write the value of the characteristic * diff --git a/libraries/BLE/src/BLEDevice.h b/libraries/BLE/src/BLEDevice.h index 153c8577..4e04372c 100644 --- a/libraries/BLE/src/BLEDevice.h +++ b/libraries/BLE/src/BLEDevice.h @@ -36,7 +36,7 @@ enum BLEDeviceEvent { BLEDeviceLastEvent }; -typedef void (*BLEDeviceEventHandler)(BLEDevice& device); +typedef void (*BLEDeviceEventHandler)(BLEDevice device); class BLEDevice { diff --git a/libraries/BLE/src/BLEPeripheral.cpp b/libraries/BLE/src/BLEPeripheral.cpp new file mode 100644 index 00000000..174be75f --- /dev/null +++ b/libraries/BLE/src/BLEPeripheral.cpp @@ -0,0 +1,200 @@ +/* + BLE Peripheral API (deprecated) + Copyright (c) 2016 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "ArduinoBLE.h" + +#include "BLEPeripheral.h" + +static BLEPeripheralEventHandler m_eventHandlers[BLEDeviceLastEvent]; + +void bleBackCompatiblePeripheralConnectHandler(BLEDevice central) +{ + if (m_eventHandlers[BLEConnected]) + { + BLECentral temp(central); + m_eventHandlers[BLEConnected](temp); + } +} + +void bleBackCompatiblePeripheralDisconnectHandler(BLEDevice central) +{ + if (m_eventHandlers[BLEDisconnected]) + { + BLECentral temp(central); + m_eventHandlers[BLEDisconnected](temp); + } +} + + +BLEPeripheral::BLEPeripheral(void) : + _initCalled(false), + _lastService(NULL), + _lastCharacteristic(NULL) +{ +} + +BLEPeripheral::~BLEPeripheral(void) +{ +} + +void BLEPeripheral::setAdvertisedServiceUuid(const char* advertisedServiceUuid) +{ + if (!_initCalled) { + init(); + } + + BLE.setAdvertisedServiceUuid(advertisedServiceUuid); +} +void BLEPeripheral::setLocalName(const char* localName) +{ + if (!_initCalled) { + init(); + } + + BLE.setLocalName(localName); +} + + +void BLEPeripheral::setDeviceName(const char *deviceName) +{ + if (!_initCalled) { + init(); + } + + BLE.setDeviceName(deviceName); +} + +void BLEPeripheral::setAppearance(const unsigned short appearance) +{ + if (!_initCalled) { + init(); + } + + BLE.setAppearance(appearance); +} + +void BLEPeripheral::setConnectionInterval(const unsigned short minConnInterval, const unsigned short maxConnInterval) +{ + if (!_initCalled) { + init(); + } + + BLE.setConnectionInterval(minConnInterval, maxConnInterval); +} + +void BLEPeripheral::addAttribute(BLEService& service) +{ + if (!_initCalled) + { + init(); + } + + BLE.addService(service); + _lastService = &service; +} + +void BLEPeripheral::addAttribute(BLECharacteristic& characteristic) +{ + if (!_initCalled) + { + init(); + } + + if (_lastService) + { + _lastService->addCharacteristic(characteristic); + _lastCharacteristic = &characteristic; + } +} + +void BLEPeripheral::addAttribute(BLEDescriptor& descriptor) +{ + if (!_initCalled) + { + init(); + } + + if (_lastCharacteristic) + { + _lastCharacteristic->addDescriptor(descriptor); + } +} + +void BLEPeripheral::setEventHandler(BLEPeripheralEvent event, BLEPeripheralEventHandler callback) +{ + if (BLEConnected == event || BLEDisconnected == event) + { + m_eventHandlers[event] = callback; + } +} + +bool BLEPeripheral::begin(void) +{ + if (!_initCalled) + { + init(); + } + + if (_lastService) + { + BLE.addService(*_lastService); + } + + BLE.setEventHandler(BLEDisconnected, bleBackCompatiblePeripheralDisconnectHandler); + BLE.setEventHandler(BLEConnected, bleBackCompatiblePeripheralConnectHandler); + + BLE.startAdvertising(); + return true; +} + +void BLEPeripheral::poll(void) +{ + BLE.poll(); +} + +void BLEPeripheral::end(void) +{ + BLE.end(); +} + +bool BLEPeripheral::disconnect(void) +{ + return BLE.disconnect(); +} + +BLECentral BLEPeripheral::central(void) +{ + BLEDevice centralBle = BLE.central(); + return BLECentral(centralBle); +} + +bool BLEPeripheral::connected(void) +{ + return BLE.connected(); +} + +void BLEPeripheral::init() +{ + if (!_initCalled) + { + BLE.begin(); + _initCalled = true; + } +} + diff --git a/libraries/BLE/src/BLEPeripheral.h b/libraries/BLE/src/BLEPeripheral.h new file mode 100644 index 00000000..2b556011 --- /dev/null +++ b/libraries/BLE/src/BLEPeripheral.h @@ -0,0 +1,69 @@ +/* + BLE Peripheral API (deprecated) + Copyright (c) 2016 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef ARDUINO_BLE_PERIPHERAL_H +#define ARDUINO_BLE_PERIPHERAL_H + +typedef void (*BLEPeripheralEventHandler)(BLECentral ¢ral); + +typedef BLEDeviceEvent BLEPeripheralEvent; + +class BLEPeripheral { + public: + BLEPeripheral(void); + virtual ~BLEPeripheral(void); + + void setAdvertisedServiceUuid(const char* advertisedServiceUuid); // set the advertised service uuid + void setLocalName(const char* localName); // set the local name + + + void setDeviceName(const char *deviceName); // set the device name + void setAppearance(const unsigned short appearance); // set the appearance type + + // Set the min and max connection interval + void setConnectionInterval(const unsigned short minConnInterval, const unsigned short maxConnInterval); + + // Add an attribute to the BLE Peripheral Device + void addAttribute(BLEService& service); + void addAttribute(BLECharacteristic& characteristic); + void addAttribute(BLEDescriptor& descriptor); + + void setEventHandler(BLEDeviceEvent event, BLEPeripheralEventHandler callback); // register an event handler + + bool begin(void); // Setup attributes and start advertising + + void poll(void); // poll the BLE radio for events + + void end(void); // Stop advertising and disconnect a central if connected + + bool disconnect(void); // disconnect the central if connected + + + BLECentral central(void); + bool connected(void); // Is a central connected? + +private: + void init(); + + bool _initCalled; + BLEService* _lastService; + BLECharacteristic* _lastCharacteristic; +}; + +#endif // ARDUINO_BLE_PERIPHERAL_H \ No newline at end of file diff --git a/libraries/BLE/src/BLETypedCharacteristic.h b/libraries/BLE/src/BLETypedCharacteristic.h new file mode 100644 index 00000000..34df735e --- /dev/null +++ b/libraries/BLE/src/BLETypedCharacteristic.h @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2015 Intel Corporation. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef _BLE_TYPED_CHARACTERISTIC_H_INCLUDED +#define _BLE_TYPED_CHARACTERISTIC_H_INCLUDED + +#include "ArduinoBLE.h" + +#include "BLECharacteristic.h" + +template class BLETypedCharacteristic : public BLECharacteristic +{ +public: + /** + * @brief The constructor of the template BLE Characteristic + * + * @param[in] uuid The characteristic UUID 16/128 bits + * + * @param[in] properties The property of the characteristic (BLERead, + * BLEWrite or BLE Notify. Combine with | ) + * + * @return none + * + * @note none + */ + BLETypedCharacteristic(const char* uuid, unsigned char properties); + + /** + * @brief Set the characteristic value + * + * @param[in] value New value to set + * + * @return bool true - set value success, + * false - on error + * + * @note none + */ + bool setValue(T value); + /** + * @brief Get the value of the Characteristic + * + * @param none + * + * @return T The value of characteristic + * + * @note none + */ + T value(void); + + /** + * @brief Set the characteristic value in Little Endian + * + * @param[in] value New value to set + * + * @return bool true - set value success, + * false - on error + * + * @note none + */ + bool setValueLE(T value); + /** + * @brief Get the value of the Characteristic in Little Endian + * + * @param none + * + * @return T The value of characteristic + * + * @note none + */ + T valueLE(void); + + /** + * @brief Set the characteristic value in Big Endian + * + * @param[in] value New value to set + * + * @return bool true - set value success, + * false - on error + * + * @note none + */ + bool setValueBE(T value); + /** + * @brief Get the value of the Characteristic in Big Endian + * + * @param none + * + * @return T The value of characteristic + * + * @note none + */ + T valueBE(void); + +private: + /** + * @brief Swap the bytes + * + * @param value The typed value + * + * @return T The swapped value + * + * @note none + */ + T byteSwap(T value); +}; + +template BLETypedCharacteristic::BLETypedCharacteristic(const char* uuid, unsigned char properties) : + BLECharacteristic(uuid, properties, sizeof(T)) +{ + T value; + memset(&value, 0x00, sizeof(value)); + + setValue(value); +} + +template bool BLETypedCharacteristic::setValue(T value) { + return BLECharacteristic::setValue((unsigned char*)&value, sizeof(T)); +} + +template T BLETypedCharacteristic::value() { + T value; + + memcpy(&value, (unsigned char*)BLECharacteristic::value(), BLECharacteristic::valueSize()); + + return value; +} + +template bool BLETypedCharacteristic::setValueLE(T value) { + return setValue(value); +} + +template T BLETypedCharacteristic::valueLE() { + return value(); +} + +template bool BLETypedCharacteristic::setValueBE(T value) { + return setValue(byteSwap(value)); +} + +template T BLETypedCharacteristic::valueBE() { + return byteSwap(value()); +} + +template T BLETypedCharacteristic::byteSwap(T value) { + T result; + unsigned char* src = (unsigned char*)&value; + unsigned char* dst = (unsigned char*)&result; + + for (int i = 0; i < sizeof(T); i++) { + dst[i] = src[sizeof(T) - i - 1]; + } + + return result; +} + +#endif // _BLE_TYPED_CHARACTERISTIC_H_INCLUDED diff --git a/libraries/BLE/src/BLETypedCharacteristics.cpp b/libraries/BLE/src/BLETypedCharacteristics.cpp new file mode 100644 index 00000000..c9c89e24 --- /dev/null +++ b/libraries/BLE/src/BLETypedCharacteristics.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2015 Intel Corporation. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "BLETypedCharacteristics.h" + +BLECharCharacteristic::BLECharCharacteristic(const char* uuid, unsigned char properties) : + BLETypedCharacteristic(uuid, properties) { +} + +BLEUnsignedCharCharacteristic::BLEUnsignedCharCharacteristic(const char* uuid, unsigned char properties) : + BLETypedCharacteristic(uuid, properties) { +} + +BLEShortCharacteristic::BLEShortCharacteristic(const char* uuid, unsigned char properties) : + BLETypedCharacteristic(uuid, properties) { +} + +BLEUnsignedShortCharacteristic::BLEUnsignedShortCharacteristic(const char* uuid, unsigned char properties) : + BLETypedCharacteristic(uuid, properties) { +} + +BLEIntCharacteristic::BLEIntCharacteristic(const char* uuid, unsigned char properties) : + BLETypedCharacteristic(uuid, properties) { +} + +BLEUnsignedIntCharacteristic::BLEUnsignedIntCharacteristic(const char* uuid, unsigned char properties) : + BLETypedCharacteristic(uuid, properties) { +} + +BLELongCharacteristic::BLELongCharacteristic(const char* uuid, unsigned char properties) : + BLETypedCharacteristic(uuid, properties) { +} + +BLEUnsignedLongCharacteristic::BLEUnsignedLongCharacteristic(const char* uuid, unsigned char properties) : + BLETypedCharacteristic(uuid, properties) { +} + +BLEFloatCharacteristic::BLEFloatCharacteristic(const char* uuid, unsigned char properties) : + BLETypedCharacteristic(uuid, properties) { +} + +BLEDoubleCharacteristic::BLEDoubleCharacteristic(const char* uuid, unsigned char properties) : + BLETypedCharacteristic(uuid, properties) { +} diff --git a/libraries/BLE/src/BLETypedCharacteristics.h b/libraries/BLE/src/BLETypedCharacteristics.h new file mode 100644 index 00000000..1ecd2a6c --- /dev/null +++ b/libraries/BLE/src/BLETypedCharacteristics.h @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2015 Intel Corporation. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef _BLE_TYPED_CHARACTERISTICS_H_INCLUDED +#define _BLE_TYPED_CHARACTERISTICS_H_INCLUDED + +#include "BLETypedCharacteristic.h" + +class BLEBoolCharacteristic : public BLETypedCharacteristic { +public: + /** + * @brief Instantiate a bool Typed Characteristic. + * Default constructor for BLE bool Characteristic + * + * @param[in] uuid The characteristic UUID 16/128 bits + * + * @param[in] properties The property of the characteristic (BLERead, + * BLEWrite or BLE Notify. Combine with | ) + * + * @return none + * + * @note none + */ + BLEBoolCharacteristic(const char* uuid, unsigned char properties); +}; + +class BLECharCharacteristic : public BLETypedCharacteristic { +public: + /** + * @brief Instantiate a Char Typed Characteristic. + * Default constructor for BLE Char Characteristic + * + * @param[in] uuid The characteristic UUID 16/128 bits + * + * @param[in] properties The property of the characteristic (BLERead, + * BLEWrite or BLE Notify. Combine with | ) + * + * @return none + * + * @note none + */ + BLECharCharacteristic(const char* uuid, unsigned char properties); +}; + +class BLEUnsignedCharCharacteristic : public BLETypedCharacteristic { +public: + /** + * @brief Instantiate a Unsigned Char Typed Characteristic. + * Default constructor for BLE Unsigned Char Characteristic + * + * @param[in] uuid The characteristic UUID 16/128 bits + * + * @param[in] properties The property of the characteristic (BLERead, + * BLEWrite or BLE Notify. Combine with | ) + * + * @return none + * + * @note none + */ + BLEUnsignedCharCharacteristic(const char* uuid, unsigned char properties); +}; + +class BLEShortCharacteristic : public BLETypedCharacteristic { +public: + /** + * @brief Instantiate a Short Typed Characteristic. + * Default constructor for BLE short Characteristic + * + * @param[in] uuid The characteristic UUID 16/128 bits + * + * @param[in] properties The property of the characteristic (BLERead, + * BLEWrite or BLE Notify. Combine with | ) + * + * @return none + * + * @note none + */ + BLEShortCharacteristic(const char* uuid, unsigned char properties); +}; + +class BLEUnsignedShortCharacteristic : public BLETypedCharacteristic { +public: + /** + * @brief Instantiate a Unsigned Short Typed Characteristic. + * Default constructor for BLE Unsigned Short Characteristic + * + * @param[in] uuid The characteristic UUID 16/128 bits + * + * @param[in] properties The property of the characteristic (BLERead, + * BLEWrite or BLE Notify. Combine with | ) + * + * @return none + * + * @note none + */ + BLEUnsignedShortCharacteristic(const char* uuid, unsigned char properties); +}; + +class BLEIntCharacteristic : public BLETypedCharacteristic { +public: + /** + * @brief Instantiate a Int Typed Characteristic. + * Default constructor for BLE Int Characteristic + * + * @param[in] uuid The characteristic UUID 16/128 bits + * + * @param[in] properties The property of the characteristic (BLERead, + * BLEWrite or BLE Notify. Combine with | ) + * + * @return none + * + * @note none + */ + BLEIntCharacteristic(const char* uuid, unsigned char properties); +}; + +class BLEUnsignedIntCharacteristic : public BLETypedCharacteristic { +public: + /** + * @brief Instantiate a Unsigned Int Typed Characteristic. + * Default constructor for BLE Unsigned Int Characteristic + * + * @param[in] uuid The characteristic UUID 16/128 bits + * + * @param[in] properties The property of the characteristic (BLERead, + * BLEWrite or BLE Notify. Combine with | ) + * + * @return none + * + * @note none + */ + BLEUnsignedIntCharacteristic(const char* uuid, unsigned char properties); +}; + +class BLELongCharacteristic : public BLETypedCharacteristic { +public: + /** + * @brief Instantiate a Long Typed Characteristic. + * Default constructor for BLE Long Characteristic + * + * @param[in] uuid The characteristic UUID 16/128 bits + * + * @param[in] properties The property of the characteristic (BLERead, + * BLEWrite or BLE Notify. Combine with | ) + * + * @return none + * + * @note none + */ + BLELongCharacteristic(const char* uuid, unsigned char properties); +}; + +class BLEUnsignedLongCharacteristic : public BLETypedCharacteristic { +public: + /** + * @brief Instantiate a Unsigned Long Typed Characteristic. + * Default constructor for BLE Unsigned Long Characteristic + * + * @param[in] uuid The characteristic UUID 16/128 bits + * + * @param[in] properties The property of the characteristic (BLERead, + * BLEWrite or BLE Notify. Combine with | ) + * + * @return none + * + * @note none + */ + BLEUnsignedLongCharacteristic(const char* uuid, unsigned char properties); +}; + +class BLEFloatCharacteristic : public BLETypedCharacteristic { +public: + /** + * @brief Instantiate a Float Typed Characteristic. + * Default constructor for BLE Float Characteristic + * + * @param[in] uuid The characteristic UUID 16/128 bits + * + * @param[in] properties The property of the characteristic (BLERead, + * BLEWrite or BLE Notify. Combine with | ) + * + * @return none + * + * @note none + */ + BLEFloatCharacteristic(const char* uuid, unsigned char properties); +}; + +class BLEDoubleCharacteristic : public BLETypedCharacteristic { +public: + /** + * @brief Instantiate a Double Typed Characteristic. + * Default constructor for BLE Double Characteristic + * + * @param[in] uuid The characteristic UUID 16/128 bits + * + * @param[in] properties The property of the characteristic (BLERead, + * BLEWrite or BLE Notify. Combine with | ) + * + * @return none + * + * @note none + */ + BLEDoubleCharacteristic(const char* uuid, unsigned char properties); +}; + +#endif // _BLE_TYPED_CHARACTERISTICS_H_INCLUDED From 5b69b5a5e5fdaac9571ee07576d960fba1022eb2 Mon Sep 17 00:00:00 2001 From: lianggao Date: Mon, 31 Oct 2016 17:54:22 +0800 Subject: [PATCH 08/22] Fix the review issues and dismiss some TODOs Notice: Not test on IDE --- libraries/BLE/examples/central/central.ino | 12 +- .../examples/notification/notification.ino | 5 +- .../examples/notifycentral/notifycentral.ino | 10 +- libraries/BLE/examples/test/test.ino | 5 +- libraries/BLE/src/ArduinoBLE.h | 1 + libraries/BLE/src/BLECharacteristic.cpp | 124 ++++++-- libraries/BLE/src/BLECharacteristic.h | 31 +- libraries/BLE/src/BLECharacteristicImp.cpp | 71 ++++- libraries/BLE/src/BLECharacteristicImp.h | 5 +- libraries/BLE/src/BLEDescriptor.cpp | 28 ++ libraries/BLE/src/BLEDescriptor.h | 4 + libraries/BLE/src/BLEDescriptorImp.cpp | 19 +- libraries/BLE/src/BLEDescriptorImp.h | 6 + libraries/BLE/src/BLEDevice.cpp | 25 +- libraries/BLE/src/BLEDevice.h | 287 +++++++++++++++--- libraries/BLE/src/BLEDeviceManager.cpp | 46 ++- libraries/BLE/src/BLEDeviceManager.h | 17 +- 17 files changed, 554 insertions(+), 142 deletions(-) diff --git a/libraries/BLE/examples/central/central.ino b/libraries/BLE/examples/central/central.ino index d308000e..5b65b3bc 100644 --- a/libraries/BLE/examples/central/central.ino +++ b/libraries/BLE/examples/central/central.ino @@ -1,14 +1,11 @@ #include "ArduinoBLE.h" -#include "BLEAttribute.h" -#include "BLECharacteristicImp.h" -#include "BLEProfileManager.h" // LED pin #define LED_PIN 13 void setup() { - Serial.begin(115200); + Serial.begin(9600); Serial.println("test---"); // set LED pin to output mode @@ -45,12 +42,9 @@ void controlLed(BLEDevice &peripheral) if (!ledCharacteristic) { - //peripheral.disconnect(); - while(1) - { + peripheral.disconnect(); Serial.println("Peripheral does not have LED characteristic!"); delay(5000); - } return; } @@ -76,9 +70,7 @@ void controlLed(BLEDevice &peripheral) } void loop() { - //pr_debug(LOG_MODULE_BLE, "%s-%d",__FUNCTION__, __LINE__); BLEDevice peripheral = BLE.available(); - //pr_debug(LOG_MODULE_BLE, "%s-%d",__FUNCTION__, __LINE__); if (peripheral) { Serial.println(peripheral.address()); diff --git a/libraries/BLE/examples/notification/notification.ino b/libraries/BLE/examples/notification/notification.ino index 6da0c096..29d44f59 100644 --- a/libraries/BLE/examples/notification/notification.ino +++ b/libraries/BLE/examples/notification/notification.ino @@ -1,8 +1,5 @@ #include "ArduinoBLE.h" -#include "BLEAttribute.h" -#include "BLECharacteristicImp.h" -#include "BLEProfileManager.h" // LED pin #define LED_PIN 13 @@ -15,7 +12,7 @@ BLECharacteristic switchCharacteristic("19b10101e8f2537e4f6cd104768a1214", BLE BLEDescriptor switchDescriptor("2901", "switch"); void setup() { - Serial.begin(115200); + Serial.begin(9600); Serial.println("test---"); // set LED pin to output mode diff --git a/libraries/BLE/examples/notifycentral/notifycentral.ino b/libraries/BLE/examples/notifycentral/notifycentral.ino index a995a580..8f599d7b 100644 --- a/libraries/BLE/examples/notifycentral/notifycentral.ino +++ b/libraries/BLE/examples/notifycentral/notifycentral.ino @@ -1,14 +1,11 @@ #include "ArduinoBLE.h" -#include "BLEAttribute.h" -#include "BLECharacteristicImp.h" -#include "BLEProfileManager.h" // LED pin #define LED_PIN 13 void setup() { - Serial.begin(115200); + Serial.begin(9600); Serial.println("test---"); // set LED pin to output mode @@ -45,12 +42,9 @@ void controlLed(BLEDevice &peripheral) if (!ledCharacteristic) { - //peripheral.disconnect(); - while(1) - { + peripheral.disconnect(); Serial.println("Peripheral does not have LED characteristic!"); delay(5000); - } return; } ledCharacteristic.subscribe(); diff --git a/libraries/BLE/examples/test/test.ino b/libraries/BLE/examples/test/test.ino index cfd9bffc..dd15eb10 100644 --- a/libraries/BLE/examples/test/test.ino +++ b/libraries/BLE/examples/test/test.ino @@ -1,8 +1,5 @@ #include "ArduinoBLE.h" -#include "BLEAttribute.h" -#include "BLECharacteristicImp.h" -#include "BLEProfileManager.h" // LED pin #define LED_PIN 13 @@ -15,7 +12,7 @@ BLECharacteristic switchCharacteristic("19b10101e8f2537e4f6cd104768a1214", BLE BLEDescriptor switchDescriptor("2901", "switch"); void setup() { - Serial.begin(115200); + Serial.begin(9600); Serial.println("test---"); // set LED pin to output mode diff --git a/libraries/BLE/src/ArduinoBLE.h b/libraries/BLE/src/ArduinoBLE.h index d643b426..3a74f51f 100644 --- a/libraries/BLE/src/ArduinoBLE.h +++ b/libraries/BLE/src/ArduinoBLE.h @@ -27,6 +27,7 @@ class BLECharacteristic; class BLEDescriptor; class BLEService; class BLECharacteristicImp; +class BLEDescriptorImp; #include "BLECommon.h" diff --git a/libraries/BLE/src/BLECharacteristic.cpp b/libraries/BLE/src/BLECharacteristic.cpp index 8b122c01..7af20164 100644 --- a/libraries/BLE/src/BLECharacteristic.cpp +++ b/libraries/BLE/src/BLECharacteristic.cpp @@ -1,5 +1,4 @@ -//#include "internal/ble_client.h" #include "ArduinoBLE.h" #include "BLEUtils.h" @@ -67,7 +66,7 @@ const char* BLECharacteristic::uuid() const return _uuid_cstr; } -unsigned char BLECharacteristic::properties() +unsigned char BLECharacteristic::properties() const { unsigned char property = 0; BLECharacteristicImp *characteristicImp = getImplementation(); @@ -78,7 +77,7 @@ unsigned char BLECharacteristic::properties() return property; } -int BLECharacteristic::valueSize() //const +int BLECharacteristic::valueSize() const { int valuesize = 0; BLECharacteristicImp *characteristicImp = getImplementation(); @@ -100,7 +99,7 @@ const byte* BLECharacteristic::value() const return value_temp; } -int BLECharacteristic::valueLength() //const +int BLECharacteristic::valueLength() const { int valueLength = 0; BLECharacteristicImp *characteristicImp = getImplementation(); @@ -117,7 +116,7 @@ BLECharacteristic::operator bool() const } -byte BLECharacteristic::operator[] (int offset) //const +byte BLECharacteristic::operator[] (int offset) const { byte data = 0; BLECharacteristicImp *characteristicImp = getImplementation(); @@ -144,14 +143,20 @@ bool BLECharacteristic::writeValue(const byte value[], int length) characteristicImp->writeValue(value, length); retVar = true; } - return retVar; + return writeValue(value, length, 0); } bool BLECharacteristic::writeValue(const byte value[], int length, int offset) { - // TODO: Not support it now. - // Will add this feature. - return false; + bool retVar = false; + BLECharacteristicImp *characteristicImp = getImplementation(); + + if (NULL != characteristicImp) + { + characteristicImp->writeValue(value, length, offset); + retVar = true; + } + return retVar; } bool BLECharacteristic::writeValue(const char* value) @@ -306,7 +311,7 @@ int BLECharacteristic::addDescriptor(BLEDescriptor& descriptor) return retVar; } -int BLECharacteristic::descriptorCount() //const +int BLECharacteristic::descriptorCount() const { int count = 0; BLECharacteristicImp *characteristicImp = getImplementation(); @@ -320,28 +325,97 @@ int BLECharacteristic::descriptorCount() //const bool BLECharacteristic::hasDescriptor(const char* uuid) const { - // TODO: Not support now - return false; + BLEDescriptorImp* descriptorImp = NULL; + BLECharacteristicImp *characteristicImp = getImplementation(); + + if (NULL != characteristicImp) + { + descriptorImp = characteristicImp->descrptor(uuid); + } + + return (descriptorImp != NULL); } + bool BLECharacteristic::hasDescriptor(const char* uuid, int index) const { - // TODO: Not support now - return false; + bool retVal = false; + BLEDescriptorImp* descriptorImp = NULL; + BLECharacteristicImp *characteristicImp = getImplementation(); + + if (NULL != characteristicImp) + { + descriptorImp = characteristicImp->descrptor(index); + if (NULL != descriptorImp) + { + retVal = descriptorImp->compareUuid(uuid); + } + } + + return retVal; } + BLEDescriptor BLECharacteristic::descriptor(int index) const { - // TODO: Not support now - return BLEDescriptor(); + BLEDescriptorImp* descriptorImp = NULL; + BLECharacteristicImp *characteristicImp = getImplementation(); + + if (NULL != characteristicImp) + { + descriptorImp = characteristicImp->descrptor(index); + } + + if (descriptorImp != NULL) + { + return BLEDescriptor(descriptorImp, &_bledev); + } + else + { + return BLEDescriptor(); + } } BLEDescriptor BLECharacteristic::descriptor(const char * uuid) const { - // TODO: Not support now - return BLEDescriptor(); + BLEDescriptorImp* descriptorImp = NULL; + BLECharacteristicImp *characteristicImp = getImplementation(); + + if (NULL != characteristicImp) + { + descriptorImp = characteristicImp->descrptor(uuid); + } + + if (descriptorImp != NULL) + { + return BLEDescriptor(descriptorImp, &_bledev); + } + else + { + return BLEDescriptor(); + } } + BLEDescriptor BLECharacteristic::descriptor(const char * uuid, int index) const { - // TODO: Not support now - return BLEDescriptor(); + bool retVal = false; + BLEDescriptorImp* descriptorImp = NULL; + BLECharacteristicImp *characteristicImp = getImplementation(); + + if (NULL != characteristicImp) + { + descriptorImp = characteristicImp->descrptor(index); + if (NULL != descriptorImp) + { + retVal = descriptorImp->compareUuid(uuid); + } + } + + if (descriptorImp != NULL && true == retVal) + { + return BLEDescriptor(descriptorImp, &_bledev); + } + else + { + return BLEDescriptor(); + } } void BLECharacteristic::setEventHandler(BLECharacteristicEvent event, @@ -391,13 +465,15 @@ BLECharacteristic::_setValue(const uint8_t value[], uint16_t length) memcpy(_value, value, length); } -BLECharacteristicImp* BLECharacteristic::getImplementation() +BLECharacteristicImp* BLECharacteristic::getImplementation() const { - if (NULL == _internal) + BLECharacteristicImp* tmp = NULL; + tmp = _internal; + if (NULL == tmp) { - _internal = BLEProfileManager::instance()->characteristic(_bledev, (const char*)_uuid_cstr); + tmp = BLEProfileManager::instance()->characteristic(_bledev, (const char*)_uuid_cstr); } - return _internal; + return tmp; } void BLECharacteristic::setBLECharacteristicImp(BLECharacteristicImp *characteristicImp) diff --git a/libraries/BLE/src/BLECharacteristic.h b/libraries/BLE/src/BLECharacteristic.h index 96b96b13..076d168d 100644 --- a/libraries/BLE/src/BLECharacteristic.h +++ b/libraries/BLE/src/BLECharacteristic.h @@ -20,14 +20,9 @@ #ifndef ARDUINO_BLE_CHARACTERISTIC_H #define ARDUINO_BLE_CHARACTERISTIC_H -//#include "BLEAttributeWithValue.h" -#include "BLECommon.h" -//#include "BLEDescriptor.h" #include "ArduinoBLE.h" #include "BLEDevice.h" -//#include "BLECharacteristicImp.h" -//class BLEDescriptorImp; enum BLECharacteristicEvent { BLEWritten = 0, @@ -122,7 +117,7 @@ class BLECharacteristic //: public BLEAttributeWithValue * * @note none */ - unsigned char properties(); + unsigned char properties() const; /** * @brief Get the maximum size of the value @@ -133,7 +128,7 @@ class BLECharacteristic //: public BLEAttributeWithValue * * @note none */ - int valueSize();//const + int valueSize() const; /** * @brief Get the value buffer @@ -144,7 +139,7 @@ class BLECharacteristic //: public BLEAttributeWithValue * * @note none */ - virtual const byte* value() const;// + virtual const byte* value() const; /** * @brief Get the current length of the value @@ -155,7 +150,7 @@ class BLECharacteristic //: public BLEAttributeWithValue * * @note TODO: How to handle if the data is RAW data? This API is danger */ - virtual int valueLength();//const + virtual int valueLength() const; /** * @brief Get a byte of the value at the specified offset @@ -166,7 +161,7 @@ class BLECharacteristic //: public BLEAttributeWithValue * * @note none */ - virtual byte operator[] (int offset);//const + virtual byte operator[] (int offset) const; /** * Set the current value of the Characteristic @@ -358,7 +353,17 @@ class BLECharacteristic //: public BLEAttributeWithValue */ bool unsubscribe(); - bool valueUpdated(); // Read response updated the characteristic + + /** + * @brief Read response or notification updated the characteristic + * + * @param none + * + * @return bool true - Written, false - Not changed + * + * @note GATT client only. GATT server always return false. + */ + bool valueUpdated(); /** * @brief Add the characteristic's descriptor @@ -380,7 +385,7 @@ class BLECharacteristic //: public BLEAttributeWithValue * * @note none */ - int descriptorCount();//const + int descriptorCount() const; /** * @brief Does the characteristic have a descriptor with the specified UUID @@ -498,7 +503,7 @@ class BLECharacteristic //: public BLEAttributeWithValue private: void _setValue(const uint8_t value[], uint16_t length); - BLECharacteristicImp *getImplementation(); + BLECharacteristicImp *getImplementation() const; private: char _uuid_cstr[37]; // The characteristic UUID diff --git a/libraries/BLE/src/BLECharacteristicImp.cpp b/libraries/BLE/src/BLECharacteristicImp.cpp index d0d7893e..cb82047d 100644 --- a/libraries/BLE/src/BLECharacteristicImp.cpp +++ b/libraries/BLE/src/BLECharacteristicImp.cpp @@ -193,7 +193,33 @@ bool BLECharacteristicImp::writeValue(const byte value[], int length) int status; bool retVal = false; - _setValue(value, length); + _setValue(value, length, 0); + + // Address same is GATT server. Send notification if CCCD enabled + // Different is GATT client. Send write request + if (true == BLEUtils::isLocalBLE(_ble_device) && + NULL != _attr_chrc_value) + { + // Notify for peripheral. + status = bt_gatt_notify(NULL, _attr_chrc_value, value, length, NULL); + if (0 == status) + { + retVal = true; + } + } + + //Not schedule write request for central + // The write request may failed. + // If user want to get latest set value. Call read and get the real value + return retVal; +} + +bool BLECharacteristicImp::writeValue(const byte value[], int length, int offset) +{ + int status; + bool retVal = false; + + _setValue(value, length, offset); // Address same is GATT server. Send notification if CCCD enabled // Different is GATT client. Send write request @@ -217,7 +243,7 @@ bool BLECharacteristicImp::writeValue(const byte value[], int length) bool BLECharacteristicImp::setValue(const unsigned char value[], uint16_t length) { - _setValue(value, length); + _setValue(value, length, 0); // Read response/Notification/Indication for GATT client // Write request for GATT server if (_event_handlers[BLEWritten]) @@ -438,15 +464,26 @@ BLECharacteristicImp::valueHandle() } void -BLECharacteristicImp::_setValue(const uint8_t value[], uint16_t length) +BLECharacteristicImp::_setValue(const uint8_t value[], uint16_t length, uint16_t offset) { - if (length > _value_size) + if (length + offset > _value_size) { - length = _value_size; + if (_value_size > offset) + { + uint16_t temp_len = _value_size - offset; + if (length > temp_len) + { + length = temp_len; + } + } + else + { + return; + } } _value_updated = true; - memcpy(_value, value, length); + memcpy(_value + offset, value, length); _value_length = length; } @@ -685,6 +722,7 @@ int BLECharacteristicImp::addDescriptor(BLEDescriptor& descriptor) } int BLECharacteristicImp::addDescriptor(const bt_uuid_t* uuid, + unsigned char property, uint16_t handle) { BLEDescriptorImp* descriptorImp = descrptor(uuid); @@ -693,7 +731,7 @@ int BLECharacteristicImp::addDescriptor(const bt_uuid_t* uuid, return BLE_STATUS_SUCCESS; } - descriptorImp = new BLEDescriptorImp(uuid, handle, _ble_device); + descriptorImp = new BLEDescriptorImp(uuid, property, handle, _ble_device); pr_debug(LOG_MODULE_BLE, "%s-%d",__FUNCTION__, __LINE__); if (NULL == descriptorImp) { @@ -739,6 +777,24 @@ BLEDescriptorImp* BLECharacteristicImp::descrptor(const char* uuid) return descrptor((const bt_uuid_t *)&uuid_tmp); } + +BLEDescriptorImp* BLECharacteristicImp::descrptor(int index) +{ + BLEDescriptorImp* descriptorImp = NULL; + BLEDescriptorNodePtr node = link_node_get_first(&_descriptors_header); + while (NULL != node) + { + if (0 >= index) + { + descriptorImp = node->value; + break; + } + index--; + node = node->next; + } + return descriptorImp; +} + void BLECharacteristicImp::releaseDescriptors() { BLEDescriptorNodePtr node = link_node_get_first(&_descriptors_header); @@ -847,6 +903,7 @@ uint8_t BLECharacteristicImp::discoverResponseProc(bt_conn_t *conn, else { int retval = (int)addDescriptor(desc_uuid, + attr->perm, desc_handle); if (BLE_STATUS_SUCCESS != retval) diff --git a/libraries/BLE/src/BLECharacteristicImp.h b/libraries/BLE/src/BLECharacteristicImp.h index 73caf6fb..568597ef 100644 --- a/libraries/BLE/src/BLECharacteristicImp.h +++ b/libraries/BLE/src/BLECharacteristicImp.h @@ -52,6 +52,7 @@ class BLECharacteristicImp: public BLEAttribute{ */ int addDescriptor(BLEDescriptor& descriptor); int addDescriptor(const bt_uuid_t* uuid, + unsigned char property, uint16_t handle); void releaseDescriptors(); @@ -70,6 +71,7 @@ class BLECharacteristicImp: public BLEAttribute{ * @note none */ bool writeValue(const byte value[], int length); + bool writeValue(const byte value[], int length, int offset); /** * Set the current value of the Characteristic @@ -175,6 +177,7 @@ class BLECharacteristicImp: public BLEAttribute{ BLEDescriptorImp* descrptor(const bt_uuid_t* uuid); BLEDescriptorImp* descrptor(const char* uuid); + BLEDescriptorImp* descrptor(int index); protected: friend class BLEProfileManager; @@ -290,7 +293,7 @@ class BLECharacteristicImp: public BLEAttribute{ void setCCCDHandle(uint16_t handle); void setHandle(uint16_t handle); - void _setValue(const uint8_t value[], uint16_t length); + void _setValue(const uint8_t value[], uint16_t length, uint16_t offset); bool isClientCharacteristicConfigurationDescriptor(const bt_uuid_t* uuid); private: diff --git a/libraries/BLE/src/BLEDescriptor.cpp b/libraries/BLE/src/BLEDescriptor.cpp index 48f4fa5b..309b62c7 100644 --- a/libraries/BLE/src/BLEDescriptor.cpp +++ b/libraries/BLE/src/BLEDescriptor.cpp @@ -16,8 +16,10 @@ License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "BLEAttribute.h" #include "BLEDescriptor.h" #include "BLEUtils.h" +#include "BLEDescriptorImp.h" BLEDescriptor::BLEDescriptor(): _properties(0), @@ -26,6 +28,21 @@ BLEDescriptor::BLEDescriptor(): { memset(_uuid_cstr, 0, sizeof (_uuid_cstr)); } + +BLEDescriptor::BLEDescriptor(BLEDescriptorImp* descriptorImp, + const BLEDevice *bleDev): + _bledev(bleDev), + _value_size(0), + _value(NULL) +{ + _properties = descriptorImp->properties(); + memset(_uuid_cstr, 0, sizeof (_uuid_cstr)); + BLEUtils::uuidBT2String(descriptorImp->bt_uuid(), _uuid_cstr); + + _value_size = descriptorImp->valueSize(); + _value = (unsigned char*)balloc(_value_size, NULL); + memcpy(_value, descriptorImp->value(), _value_size); +} BLEDescriptor::BLEDescriptor(const char* uuid, const unsigned char value[], @@ -130,3 +147,14 @@ bool BLEDescriptor::read() return false; } +unsigned char BLEDescriptor::properties() const +{ + return _properties; +} + + +int BLEDescriptor::valueSize() const +{ + return _value_size; +} + diff --git a/libraries/BLE/src/BLEDescriptor.h b/libraries/BLE/src/BLEDescriptor.h index a61a80b4..608c3159 100644 --- a/libraries/BLE/src/BLEDescriptor.h +++ b/libraries/BLE/src/BLEDescriptor.h @@ -31,6 +31,8 @@ class BLEDescriptor BLEDescriptor(const char* uuid, const unsigned char value[], unsigned short valueLength); // create a descriptor the specified uuid and value BLEDescriptor(const char* uuid, const char* value); // create a descriptor the specified uuid and string value + BLEDescriptor(BLEDescriptorImp* descriptorImp, const BLEDevice *bleDev); + virtual ~BLEDescriptor(); const char* uuid() const; @@ -85,6 +87,8 @@ class BLEDescriptor bool write(const byte value[], int length, int offset); bool write(const char* value); bool read(); + unsigned char properties() const; + int valueSize() const; private: char _uuid_cstr[37]; // The characteristic UUID BLEDevice _bledev; diff --git a/libraries/BLE/src/BLEDescriptorImp.cpp b/libraries/BLE/src/BLEDescriptorImp.cpp index b0e673b4..e9a9c22d 100644 --- a/libraries/BLE/src/BLEDescriptorImp.cpp +++ b/libraries/BLE/src/BLEDescriptorImp.cpp @@ -24,10 +24,13 @@ #include "BLECallbacks.h" -BLEDescriptorImp::BLEDescriptorImp(BLEDevice& bledevice, BLEDescriptor &descriptor): +BLEDescriptorImp::BLEDescriptorImp(BLEDevice& bledevice, + BLEDescriptor &descriptor): BLEAttribute(descriptor.uuid(), BLETypeDescriptor), _value_handle(0) { + + _properties = descriptor.properties(); _value_length = descriptor.valueLength(); _value = (unsigned char*)balloc(_value_length, NULL); @@ -35,10 +38,12 @@ BLEDescriptorImp::BLEDescriptorImp(BLEDevice& bledevice, BLEDescriptor &descript } BLEDescriptorImp::BLEDescriptorImp(const bt_uuid_t* uuid, + unsigned char properties, uint16_t handle, BLEDevice& bledevice): BLEAttribute(uuid, BLETypeDescriptor), - _value_handle(handle) + _value_handle(handle), + _properties(properties) { _value_length = BLE_MAX_ATTR_DATA_LEN; _value = (unsigned char*)balloc(_value_length, NULL); @@ -84,4 +89,14 @@ int BLEDescriptorImp::updateProfile(bt_gatt_attr_t *attr_start, int& index) return 1; } +unsigned char BLEDescriptorImp::properties() const +{ + return _properties; +} + +int BLEDescriptorImp::valueSize() const +{ + return _value_length; +} + diff --git a/libraries/BLE/src/BLEDescriptorImp.h b/libraries/BLE/src/BLEDescriptorImp.h index 8d3a9f1c..3282333f 100644 --- a/libraries/BLE/src/BLEDescriptorImp.h +++ b/libraries/BLE/src/BLEDescriptorImp.h @@ -36,6 +36,7 @@ class BLEDescriptorImp: public BLEAttribute{ */ BLEDescriptorImp(BLEDevice& bledevice, BLEDescriptor &descriptor); BLEDescriptorImp(const bt_uuid_t* uuid, + unsigned char properties, uint16_t handle, BLEDevice& bledevice); @@ -58,6 +59,8 @@ class BLEDescriptorImp: public BLEAttribute{ int updateProfile(bt_gatt_attr_t *attr_start, int& index); unsigned char operator[] (int offset) const; + unsigned char properties() const; + int valueSize() const; protected: @@ -66,8 +69,11 @@ class BLEDescriptorImp: public BLEAttribute{ unsigned short _value_length; unsigned short _value_handle; unsigned char* _value; + unsigned char _properties; // The characteristic property bt_uuid_128 _descriptor_uuid; + + BLEDevice _bledev; }; #endif // _BLE_DESCRIPTOR_H_INCLUDED diff --git a/libraries/BLE/src/BLEDevice.cpp b/libraries/BLE/src/BLEDevice.cpp index 17a65926..f75a1cdc 100644 --- a/libraries/BLE/src/BLEDevice.cpp +++ b/libraries/BLE/src/BLEDevice.cpp @@ -53,10 +53,10 @@ BLEDevice::BLEDevice(const bt_addr_le_t* bleaddress): memcpy(&_bt_addr, bleaddress, sizeof(bt_addr_le_t)); } -BLEDevice::BLEDevice(const BLEDevice* bleaddress) +BLEDevice::BLEDevice(const BLEDevice* bledevice) { - memcpy(&_bt_addr, bleaddress->bt_le_address(), sizeof(bt_addr_le_t)); - memcpy(&_conn_param, &bleaddress->_conn_param, sizeof (ble_conn_param_t)); + memcpy(&_bt_addr, bledevice->bt_le_address(), sizeof(bt_addr_le_t)); + memcpy(&_conn_param, &bledevice->_conn_param, sizeof (ble_conn_param_t)); } BLEDevice::~BLEDevice() @@ -101,10 +101,14 @@ void BLEDevice::setAdvertisedServiceUuid(const char* advertisedServiceUuid) } void BLEDevice::setAdvertisedService(const BLEService& service) -{} +{ + setAdvertisedServiceUuid(service.uuid()); +} void BLEDevice::setServiceSolicitationUuid(const char* serviceSolicitationUuid) -{} +{ + BLEDeviceManager::instance()->setServiceSolicitationUuid(serviceSolicitationUuid); +} void BLEDevice::setManufacturerData(const unsigned char manufacturerData[], unsigned char manufacturerDataLength) @@ -213,6 +217,13 @@ void BLEDevice::startScanning(String name) BLEDeviceManager::instance()->startScanning(); } +void BLEDevice::startScanning(BLEService& service) +{ + preCheckProfile(); + BLEDeviceManager::instance()->setAdvertiseCritical(service); + BLEDeviceManager::instance()->startScanning(); +} + void BLEDevice::startScanningWithDuplicates() {} @@ -221,10 +232,6 @@ void BLEDevice::stopScanning() BLEDeviceManager::instance()->stopScanning(); } -//void setAcceptAdvertiseLocalName(String name); -//void setAcceptAdvertiseLocalName(BLEService& service); -//void setAcceptAdvertiseCallback(String name); - BLEDevice BLEDevice::available() { return BLEDeviceManager::instance()->available(); diff --git a/libraries/BLE/src/BLEDevice.h b/libraries/BLE/src/BLEDevice.h index 4e04372c..d1462ce0 100644 --- a/libraries/BLE/src/BLEDevice.h +++ b/libraries/BLE/src/BLEDevice.h @@ -22,11 +22,6 @@ #include -//#include "BLEService.h" -//#include "BLECharacteristic.h" - -//class BLEDevice; - enum BLEDeviceEvent { BLEConnected = 0, // BLE device connected BLEDisconnected = 1, // BLE device disconnected @@ -44,25 +39,42 @@ class BLEDevice /** * @brief The BLE device constructure * - * @param bleaddress BLE device address + * @param none * * @return none * * @note none */ BLEDevice(); - //BLEDevice(String bleaddress); - //BLEDevice(const char* bleaddress); + + /** + * @brief The BLE device constructure + * + * @param[in] bleaddress BLE device address + * + * @return none + * + * @note none + */ BLEDevice(const bt_addr_le_t* bleaddress); - BLEDevice(const BLEDevice* bleaddress); + /** + * @brief The BLE device constructure + * + * @param[in] bledevice BLE device + * + * @return none + * + * @note none + */ + BLEDevice(const BLEDevice* bledevice); virtual ~BLEDevice(); /** - * @brief Initiliaze the BLE hardware + * @brief Initiliaze the BLE hardware * - * @return bool indicating success or error + * @return bool indicating success or error * * @note This method are for real BLE device. * Not for peer BLE device. @@ -102,7 +114,7 @@ class BLEDevice * * @param none * - * @return none + * @return bool indicating success or error * * @note none */ @@ -113,10 +125,10 @@ class BLEDevice * * @param none * - * @return none + * @return bool indicating success or error * * @note The BLE may connected multiple devices. - * This call will disconnect all conected devices. + * This call will disconnect all conected devices. */ bool disconnect(); @@ -124,7 +136,11 @@ class BLEDevice /** * @brief Get the BLE address of the BLE in string format * - * @return const char* address of the BLE in string format + * @param none + * + * @return String The address of the BLE in string format + * + * @note none */ String address() const; @@ -344,19 +360,76 @@ class BLEDevice operator bool() const; bool operator==(const BLEDevice& device) const; bool operator!=(const BLEDevice& device) const; - //BLEDevice& operator=(const BLEDevice& device); + // central mode - void startScanning(String name); // start scanning for peripherals - void startScanningWithDuplicates(); // start scanning for peripherals, and report all duplicates - void stopScanning(); // stop scanning for peripherals + /** + * @brief Start scanning for peripherals and filter by device name in ADV + * + * @param name The device's local name. + * + * @return none + * + * @note none + */ + void startScanning(String name); - void setAcceptAdvertiseLocalName(String name); - void setAcceptAdvertiseLocalName(BLEService& service); - void setAcceptAdvertiseCallback(String name); + /** + * @brief Start scanning for peripherals and filter by service in ADV + * + * @param service The service + * + * @return none + * + * @note none + */ + void startScanning(BLEService& service); - BLEDevice available(); // retrieve a discovered peripheral + /** + * @brief start scanning for peripherals, and report all duplicates + * + * @param none + * + * @return none + * + * @note none + // Does this necessory? This will take more memory to store the ADV + // I suggest delete it. + */ + void startScanningWithDuplicates(); + + /** + * @brief Stop scanning for peripherals + * + * @param none + * + * @return none + * + * @note none + */ + void stopScanning(); + + /** + * @brief Retrieve a discovered peripheral + * + * @param none + * + * @return BLEDevice The BLE device that central scanned + * + * @note none + */ + BLEDevice available(); - bool hasLocalName() const; // does the peripheral advertise a local name + /** + * @brief Does the peripheral advertise a local name + * + * @param none + * + * @return none + * + * @note none //TODO: The implementation doesn't save the ADV's local name. + */ + bool hasLocalName() const; + bool hasAdvertisedServiceUuid() const; // does the peripheral advertise a service bool hasAdvertisedServiceUuid(int index) const; // does the peripheral advertise a service n int advertisedServiceUuidCount() const; // number of services the peripheral is advertising @@ -374,21 +447,163 @@ class BLEDevice int appearance(); // read the appearance attribute of the peripheral and return value as int // For GATT - int serviceCount() const; // returns the number of services the peripheral has - bool hasService(const char* uuid) const; // does the peripheral have a service with the specified UUID - bool hasService(const char* uuid, int index) const; // does the peripheral have an nth service with the specified UUID - BLEService service(int index) const; // return the nth service of the peripheral - BLEService service(const char * uuid) const; // return the service with the specified UUID - BLEService service(const char * uuid, int index) const; // return the nth service with the specified UUID + /** + * @brief returns the number of services the BLE device has + * + * @param none + * + * @return int The number of services + * + * @note none + */ + int serviceCount() const; + + /** + * @brief Does the peripheral have a service with the specified UUID + * + * @param uuid The 128/16 bits UUID + * + * @return bool true - Found + * false- Not found + * + * @note none + */ + bool hasService(const char* uuid) const; + + /** + * @brief Does the peripheral have an nth service with the specified UUID + * + * @param uuid The 128/16 bits UUID + * + * @param index The index + * + * @return bool true - Found + * false- Not found + * + * @note none + */ + bool hasService(const char* uuid, int index) const; + + /** + * @brief Return the nth service of the peripheral + * + * @param index The index + * + * @return BLEService The BLE service + * + * @note none + */ + BLEService service(int index) const; + + /** + * @brief Return the service with the specified UUID + * + * @param uuid The 128/16 bits UUID + * + * @return BLEService The BLE service + * + * @note none + */ + BLEService service(const char * uuid) const; + + /** + * @brief Return the nth service with the specified UUID + * + * @param uuid The 128/16 bits UUID + * + * @param index The index + * + * @return BLEService The BLE service + * + * @note none + */ + BLEService service(const char * uuid, int index) const; - int characteristicCount() const; // returns the number of characteristics the peripheral has - bool hasCharacteristic(const char* uuid) const; // does the peripheral have a characteristic with the specified UUID - bool hasCharacteristic(const char* uuid, int index) const; // does the peripheral have an nth characteristic with the specified UUID - BLECharacteristic characteristic(int index) const; // return the nth characteristic of the peripheral - BLECharacteristic characteristic(const char * uuid) const; // return the characteristic with the specified UUID - BLECharacteristic characteristic(const char * uuid, int index) const; // return the nth characteristic with the specified UUID + /** + * @brief Returns the number of characteristics the BLE device has + * + * @param none + * + * @return int The number of characteristics + * + * @note none + */ + int characteristicCount() const; + + /** + * @brief Does the device have a characteristic with the specified UUID + * + * @param uuid The 128/16 bits UUID + * + * @return bool true - Found + * false- Not found + * + * @note none + */ + bool hasCharacteristic(const char* uuid) const; + + /** + * @brief Does the device have an nth characteristic with the + * specified UUID + * + * @param uuid The 128/16 bits UUID + * + * @param index The index + * + * @return bool true - Found + * false- Not found + * + * @note none + */ + bool hasCharacteristic(const char* uuid, int index) const; + + /** + * @brief Return the nth characteristic of the BLE device + * + * @param index The index + * + * @return BLECharacteristic The BLE characteristic + * + * @note none + */ + BLECharacteristic characteristic(int index) const; + + /** + * @brief Return the characteristic with the specified UUID + * + * @param uuid The 128/16 bits UUID + * + * @return BLECharacteristic The BLE characteristic + * + * @note none + */ + BLECharacteristic characteristic(const char * uuid) const; + + /** + * @brief Return the nth characteristic with the specified UUID + * + * @param uuid The 128/16 bits UUID + * + * @param index The index + * + * @return BLECharacteristic The BLE characteristic + * + * @note none + */ + BLECharacteristic characteristic(const char * uuid, int index) const; // event handler + /** + * @brief Set the event callbacks + * + * @param event The BLE device event + * + * @param eventHandler The BLE device event handler + * + * @return none + * + * @note none + */ void setEventHandler(BLEDeviceEvent event, BLEDeviceEventHandler eventHandler); // set an event handler (callback) protected: diff --git a/libraries/BLE/src/BLEDeviceManager.cpp b/libraries/BLE/src/BLEDeviceManager.cpp index 6b7d6eaa..2ce7092d 100644 --- a/libraries/BLE/src/BLEDeviceManager.cpp +++ b/libraries/BLE/src/BLEDeviceManager.cpp @@ -35,6 +35,7 @@ BLEDeviceManager::BLEDeviceManager(): _max_conn_interval(0), _adv_critical_local_name(""), _has_service_uuid(false), + _has_service_solicit_uuid(false), _appearance(0), _adv_type(0), _adv_data_idx(0), @@ -66,6 +67,7 @@ BLEDeviceManager::BLEDeviceManager(): memset(_peer_adv_buffer, 0, sizeof(_peer_adv_buffer)); memset(_peer_adv_mill, 0, sizeof(_peer_adv_mill)); memset(&_adv_accept_critical, 0, sizeof(_adv_accept_critical)); + memset(&_adv_critical_service_uuid, 0, sizeof(_adv_critical_service_uuid)); memset(_peer_peripheral, 0, sizeof(_peer_peripheral)); memset(_device_events, 0, sizeof(_device_events)); @@ -142,13 +144,9 @@ void BLEDeviceManager::setAdvertisedServiceUuid(const char* advertisedServiceUui BLEUtils::uuidString2BT(advertisedServiceUuid, (bt_uuid_t *)&_service_uuid); } -void BLEDeviceManager::setAdvertisedService(const BLEService& service) -{ - //TODO: Different with setAdvertisedServiceUuid? -} - void BLEDeviceManager::setServiceSolicitationUuid(const char* serviceSolicitationUuid) { + _has_service_solicit_uuid = true; BLEUtils::uuidString2BT(serviceSolicitationUuid, (bt_uuid_t *)&_service_solicit_uuid); } @@ -270,6 +268,37 @@ BLEDeviceManager::_advDataInit(void) } } + if (_has_service_solicit_uuid) + { + uint8_t type; + uint8_t length; + uint8_t *data = NULL; + + pr_info(LOG_MODULE_BLE, "ADV Type-%d", _service_solicit_uuid.uuid.type); + if (BT_UUID_TYPE_16 == _service_solicit_uuid.uuid.type) + { + //UINT16_TO_LESTREAM(adv_tmp, uuid.uuid16); + data = (uint8_t *)&(((bt_uuid_16_t *)&_service_solicit_uuid)->val); + length = UUID_SIZE_16; + type = BT_DATA_SOLICIT16; + } + else if (BT_UUID_TYPE_128 == _service_solicit_uuid.uuid.type) + { + data = _service_solicit_uuid.val; + length = UUID_SIZE_128; + type = BT_DATA_SOLICIT128; + } + if (NULL != data) + { + _adv_data[_adv_data_idx].type = type; + _adv_data[_adv_data_idx].data = data; + _adv_data[_adv_data_idx].data_len = length; + _adv_data_idx++; + lengthTotal += length; + + pr_info(LOG_MODULE_BLE, "Service UUID Len -%d", length); + } + } if (_local_name.length() > 0) { @@ -426,7 +455,7 @@ void BLEDeviceManager::setAdvertiseCritical(String name) void BLEDeviceManager::setAdvertiseCritical(BLEService& service) { - BLEUtils::uuidString2BT(service.uuid(),(bt_uuid_t *)&_dv_critical_service_uuid); + BLEUtils::uuidString2BT(service.uuid(),(bt_uuid_t *)&_adv_critical_service_uuid); uint8_t type = 0; uint8_t length = 0; uint8_t *data = NULL; @@ -450,11 +479,6 @@ void BLEDeviceManager::setAdvertiseCritical(BLEService& service) _adv_accept_critical.data = data; } - -//void setAcceptAdvertiseLocalName(String name); -//void setAcceptAdvertiseLocalName(BLEService& service); -//void setAcceptAdvertiseCallback(String name); - bool BLEDeviceManager::hasLocalName() const { return (_local_name.length() != 0); diff --git a/libraries/BLE/src/BLEDeviceManager.h b/libraries/BLE/src/BLEDeviceManager.h index 1d5a8dcc..aba21217 100644 --- a/libraries/BLE/src/BLEDeviceManager.h +++ b/libraries/BLE/src/BLEDeviceManager.h @@ -112,17 +112,7 @@ class BLEDeviceManager * Only for peripheral mode. */ void setAdvertisedServiceUuid(const char* advertisedServiceUuid); - - /** - * @brief Set the service that the BLE Peripheral Device will advertise this UUID - * - * @param[in] service The service the will in advertise data. - * - * @note This method must be called before the begin method - * Only for peripheral mode. - */ - void setAdvertisedService(const BLEService& service); - + /** * @brief Set the service UUID that is solicited in the BLE Peripheral * Device advertises @@ -374,7 +364,7 @@ class BLEDeviceManager uint16_t _min_conn_interval; uint16_t _max_conn_interval; bt_addr_le_t _local_bda; - char _device_name[BLE_MAX_DEVICE_NAME+1]; + char _device_name[BLE_MAX_DEVICE_NAME + 1]; // For Central bt_le_scan_param_t _scan_param; // Scan parameter @@ -382,13 +372,14 @@ class BLEDeviceManager uint64_t _peer_adv_mill[3]; // The ADV found time stamp bt_data_t _adv_accept_critical; // The filters for central device String _adv_critical_local_name; - bt_uuid_128_t _dv_critical_service_uuid; + bt_uuid_128_t _adv_critical_service_uuid; bt_addr_le_t _wait_for_connect_peripheral; // For peripheral struct bt_le_adv_param _adv_param; bool _has_service_uuid; bt_uuid_128_t _service_uuid; + bool _has_service_solicit_uuid; bt_uuid_128_t _service_solicit_uuid; uint16_t _appearance; From e9161360b3bd87b10bd8f683aca3d247e94955e5 Mon Sep 17 00:00:00 2001 From: lianggao Date: Wed, 2 Nov 2016 14:29:14 +0800 Subject: [PATCH 09/22] Fix discover potential bugs 1. The discover can support mutiple services in GATT server. 2. The attributes doesn't include the CCCD and make regiter service failed --- libraries/BLE/src/BLECharacteristicImp.cpp | 13 ++- libraries/BLE/src/BLECommon.h | 5 +- libraries/BLE/src/BLEDevice.cpp | 26 +++-- libraries/BLE/src/BLEDevice.h | 19 +--- libraries/BLE/src/BLEDeviceManager.cpp | 92 +++++++--------- libraries/BLE/src/BLEDeviceManager.h | 23 ++-- libraries/BLE/src/BLEProfileManager.cpp | 121 +++++++++++++-------- libraries/BLE/src/BLEProfileManager.h | 16 ++- libraries/BLE/src/BLEServiceImp.h | 4 +- 9 files changed, 174 insertions(+), 145 deletions(-) diff --git a/libraries/BLE/src/BLECharacteristicImp.cpp b/libraries/BLE/src/BLECharacteristicImp.cpp index cb82047d..3483f07f 100644 --- a/libraries/BLE/src/BLECharacteristicImp.cpp +++ b/libraries/BLE/src/BLECharacteristicImp.cpp @@ -18,6 +18,7 @@ */ #include "BLEAttribute.h" +#include "BLEServiceImp.h" #include "BLECharacteristicImp.h" #include "BLECallbacks.h" @@ -811,6 +812,11 @@ void BLECharacteristicImp::releaseDescriptors() int BLECharacteristicImp::getAttributeCount() { int counter = link_list_size(&_descriptors_header) + 2; // Declaration and descriptor + // Notification/Indecation + if (_gatt_chrc.properties & (BT_GATT_CHRC_NOTIFY | BT_GATT_CHRC_INDICATE)) + { + counter++; + } return counter; } @@ -893,6 +899,7 @@ uint8_t BLECharacteristicImp::discoverResponseProc(bt_conn_t *conn, { if (NULL != attr) { + retVal = BT_GATT_ITER_CONTINUE; const bt_uuid_t* desc_uuid = attr->uuid; uint16_t desc_handle = attr->handle; pr_debug(LOG_MODULE_BLE, "%s-%d:handle-%d:%d", __FUNCTION__, __LINE__,attr->handle, desc_handle); @@ -900,6 +907,11 @@ uint8_t BLECharacteristicImp::discoverResponseProc(bt_conn_t *conn, { setCCCDHandle(desc_handle); } + else if (bt_uuid_cmp(BLEServiceImp::getPrimayUuid(), desc_uuid) == 0 || + bt_uuid_cmp(getCharacteristicAttributeUuid(), desc_uuid) == 0 ) + { + retVal = BT_GATT_ITER_STOP; + } else { int retval = (int)addDescriptor(desc_uuid, @@ -913,7 +925,6 @@ uint8_t BLECharacteristicImp::discoverResponseProc(bt_conn_t *conn, } } - retVal = BT_GATT_ITER_CONTINUE; } break; } diff --git a/libraries/BLE/src/BLECommon.h b/libraries/BLE/src/BLECommon.h index ff315dad..9589467e 100644 --- a/libraries/BLE/src/BLECommon.h +++ b/libraries/BLE/src/BLECommon.h @@ -99,7 +99,10 @@ typedef uint16_t ble_status_t; /**< Response and event BLE service status type @ typedef ble_status_t BleStatus; -#define BLE_MAX_CONN_CFG 2 +#define BLE_LIB_ASSERT(cond) ((cond) ? (void)0 : __assert_fail()) + +#define BLE_MAX_CONN_CFG 2 +#define BLE_MAX_ADV_BUFFER_CFG 3 typedef bool (*ble_advertise_handle_cb_t)(uint8_t type, const uint8_t *dataPtr, uint8_t data_len, const bt_addr_le_t *addrPtr); diff --git a/libraries/BLE/src/BLEDevice.cpp b/libraries/BLE/src/BLEDevice.cpp index f75a1cdc..c7e71e36 100644 --- a/libraries/BLE/src/BLEDevice.cpp +++ b/libraries/BLE/src/BLEDevice.cpp @@ -124,15 +124,20 @@ void BLEDevice::setAdvertisingInterval(float advertisingInterval) BLEDeviceManager::instance()->setAdvertisingInterval(advertisingInterval); } -void BLEDevice::setConnectionInterval(float minimumConnectionInterval, - float maximumConnectionInterval, +void BLEDevice::setConnectionInterval(int minimumConnectionInterval, + int maximumConnectionInterval, uint16_t latency, uint16_t timeout) -{} +{ + // TODO: Update the connection interval need more discussion +} -void BLEDevice::setConnectionInterval(float minimumConnectionInterval, - float maximumConnectionInterval) -{} +void BLEDevice::setConnectionInterval(int minimumConnectionInterval, + int maximumConnectionInterval) +{ + // TODO: Update the connection interval need more discussion + +} bool BLEDevice::setTxPower(int txPower) { @@ -177,14 +182,11 @@ BLEDevice BLEDevice::central() BLEDevice BLEDevice::peripheral() { - // TODO + // TODO: How to get the target devices BLEDevice temp; return temp; } -void BLEDevice::linkLost() -{} - BLEDevice::operator bool() const { return BLEUtils::macAddressValid(_bt_addr); @@ -225,7 +227,9 @@ void BLEDevice::startScanning(BLEService& service) } void BLEDevice::startScanningWithDuplicates() -{} +{ + // TODO +} void BLEDevice::stopScanning() { diff --git a/libraries/BLE/src/BLEDevice.h b/libraries/BLE/src/BLEDevice.h index d1462ce0..6d79b477 100644 --- a/libraries/BLE/src/BLEDevice.h +++ b/libraries/BLE/src/BLEDevice.h @@ -226,8 +226,8 @@ class BLEDevice * * @note none */ - void setConnectionInterval(float minimumConnectionInterval, - float maximumConnectionInterval, + void setConnectionInterval(int minimumConnectionInterval, + int maximumConnectionInterval, uint16_t latency, uint16_t timeout); @@ -243,8 +243,8 @@ class BLEDevice * * @note none */ - void setConnectionInterval(float minimumConnectionInterval, - float maximumConnectionInterval); + void setConnectionInterval(int minimumConnectionInterval, + int maximumConnectionInterval); /** * @brief Set TX power of the radio in dBM @@ -346,17 +346,6 @@ class BLEDevice */ BLEDevice peripheral(); - /** - * @brief Release the resources when link lost - * - * @param none - * - * @return none - * - * @note Peer devices only. Do nothing if local BLE device called. - */ - void linkLost(); - operator bool() const; bool operator==(const BLEDevice& device) const; bool operator!=(const BLEDevice& device) const; diff --git a/libraries/BLE/src/BLEDeviceManager.cpp b/libraries/BLE/src/BLEDeviceManager.cpp index 2ce7092d..e4b8875a 100644 --- a/libraries/BLE/src/BLEDeviceManager.cpp +++ b/libraries/BLE/src/BLEDeviceManager.cpp @@ -37,6 +37,7 @@ BLEDeviceManager::BLEDeviceManager(): _has_service_uuid(false), _has_service_solicit_uuid(false), _appearance(0), + _manufacturer_data_length(0), _adv_type(0), _adv_data_idx(0), _local_name(""), @@ -71,6 +72,9 @@ BLEDeviceManager::BLEDeviceManager(): memset(_peer_peripheral, 0, sizeof(_peer_peripheral)); memset(_device_events, 0, sizeof(_device_events)); + memset(_manufacturer_data, 0, sizeof(_manufacturer_data)); + memset(_peer_adv_data, 0, sizeof(_peer_adv_data)); + memset(_peer_adv_data_len, 0, sizeof(_peer_adv_data_len)); } BLEDeviceManager::~BLEDeviceManager() @@ -153,7 +157,12 @@ void BLEDeviceManager::setServiceSolicitationUuid(const char* serviceSolicitatio void BLEDeviceManager::setManufacturerData(const unsigned char manufacturerData[], unsigned char manufacturerDataLength) { - // TODO: Add late + if (manufacturerDataLength > BLE_MAX_ADV_SIZE) + { + manufacturerDataLength = BLE_MAX_ADV_SIZE; + } + _manufacturer_data_length = manufacturerDataLength; + memcpy(_manufacturer_data, manufacturerData, manufacturerDataLength); } void BLEDeviceManager::setLocalName(const char *localName) @@ -313,8 +322,18 @@ BLEDeviceManager::_advDataInit(void) pr_info(LOG_MODULE_BLE, "Local Name Len -%d", _local_name.length()); } + if (_manufacturer_data_length > 0) + { + // Add manufacturer data + _adv_data[_adv_data_idx].type = BT_DATA_MANUFACTURER_DATA; + _adv_data[_adv_data_idx].data = _manufacturer_data; + _adv_data[_adv_data_idx].data_len = _manufacturer_data_length; + _adv_data_idx++; + + lengthTotal += _manufacturer_data_length; + } + #if 0 - if (_service_data) { /* Add Service Data (if it will fit) */ @@ -347,6 +366,7 @@ BLEDeviceManager::_advDataInit(void) pr_info(LOG_MODULE_BLE, "SVC Len -%d", block_len); } #endif + if (lengthTotal > BLE_MAX_ADV_SIZE) { pr_error(LOG_MODULE_BLE, "ADV Total length-%d", lengthTotal); @@ -412,11 +432,6 @@ BLEDevice BLEDeviceManager::peripheral() return temp; } -void BLEDeviceManager::linkLost() -{ - -} - bool BLEDeviceManager::startScanning() { int err = bt_le_scan_start(&_scan_param, ble_central_device_found); @@ -718,43 +733,6 @@ bool BLEDeviceManager::advertiseDataProc(uint8_t type, // Now Only support 1 critical. Change those code if want support multi-criticals return true; } - // Please see https://www.bluetooth.org/en-us/specification/assigned-numbers/generic-access-profile - // To decode the data the central device cares. - // This example use UUID as identity. -#if 0 - switch (type) - { - case BT_DATA_UUID128_SOME: - case BT_DATA_UUID128_ALL: - { - if (data_len % UUID_SIZE_128 != 0) - { - Serial.println("AD malformed"); - return true; - } - for (i = 0; i < data_len; i += UUID_SIZE_128) - { - if (bleImuService.uuidCompare(dataPtr + i, UUID_SIZE_128) == false) - { - continue; - } - - // Accept the advertisement - if (!bleCentral.stopScan()) - { - Serial.println("Stop LE scan failed"); - continue; - } - Serial.println("Connecting"); - return false; - } - } - case BT_DATA_NAME_COMPLETE: - { - break; - } - } -#endif return false; } @@ -764,11 +742,11 @@ BLEDevice BLEDeviceManager::available() BLEDevice tempdevice; bt_addr_le_t* temp = NULL; uint64_t timestamp = millis(); - uint8_t index = 3; + uint8_t index = BLE_MAX_ADV_BUFFER_CFG; uint8_t i = 0; uint64_t max_delta = 0; - for (i = 0; i < 3; i++) + for (i = 0; i < BLE_MAX_ADV_BUFFER_CFG; i++) { uint64_t timestamp_delta = timestamp - _peer_adv_mill[i]; temp = &_peer_adv_buffer[i]; @@ -780,7 +758,7 @@ BLEDevice BLEDeviceManager::available() } //pr_debug(LOG_MODULE_BLE, "%s-%d:index %d, i-%d", __FUNCTION__, __LINE__, index, i); - if (index < 3) + if (index < BLE_MAX_ADV_BUFFER_CFG) { temp = &_peer_adv_buffer[index]; if (true == BLEUtils::macAddressValid(*temp)) @@ -793,16 +771,18 @@ BLEDevice BLEDeviceManager::available() return tempdevice; } -bool BLEDeviceManager::setAdvertiseBuffer(const bt_addr_le_t* bt_addr) +bool BLEDeviceManager::setAdvertiseBuffer(const bt_addr_le_t* bt_addr, + const uint8_t *ad, + uint8_t data_len) { bt_addr_le_t* temp = NULL; uint64_t timestamp = millis(); - uint8_t index = 3; + uint8_t index = BLE_MAX_ADV_BUFFER_CFG; uint8_t i = 0; uint64_t max_delta = 0; bool retval = false; //pr_debug(LOG_MODULE_BLE, "%s-%d-1", __FUNCTION__, __LINE__); - for (i = 0; i < 3; i++) + for (i = 0; i < BLE_MAX_ADV_BUFFER_CFG; i++) { uint64_t timestamp_delta = timestamp - _peer_adv_mill[i]; temp = &_peer_adv_buffer[i]; @@ -824,13 +804,19 @@ bool BLEDeviceManager::setAdvertiseBuffer(const bt_addr_le_t* bt_addr) //pr_debug(LOG_MODULE_BLE, "%s-%d:index %d, i-%d", __FUNCTION__, __LINE__, index, i); //pr_debug(LOG_MODULE_BLE, "%s-%d-2", __FUNCTION__, __LINE__); - if (index < 3) + if (index < BLE_MAX_ADV_BUFFER_CFG) { temp = &_peer_adv_buffer[index]; - if (i >= 3) + if (i >= BLE_MAX_ADV_BUFFER_CFG) { memcpy(temp, bt_addr, sizeof (bt_addr_le_t)); } + if (data_len > BLE_MAX_ADV_SIZE) + { + data_len = BLE_MAX_ADV_SIZE; + } + memcpy(_peer_adv_data[index], ad, data_len); + _peer_adv_data_len[index] = data_len; // Update the timestamp _peer_adv_mill[index] = timestamp; retval = true; @@ -882,7 +868,7 @@ void BLEDeviceManager::handleDeviceFound(const bt_addr_le_t *addr, { // The critical is accepted // Find the oldest and expired buffer - if(false == setAdvertiseBuffer(addr)) + if(false == setAdvertiseBuffer(addr, ad, data_len)) { pr_info(LOG_MODULE_BLE, "No buffer to store the ADV\n"); } diff --git a/libraries/BLE/src/BLEDeviceManager.h b/libraries/BLE/src/BLEDeviceManager.h index aba21217..e5af8feb 100644 --- a/libraries/BLE/src/BLEDeviceManager.h +++ b/libraries/BLE/src/BLEDeviceManager.h @@ -294,17 +294,6 @@ class BLEDeviceManager */ BLEDevice peripheral(); - /** - * @brief Release the resources when link lost - * - * @param none - * - * @return none - * - * @note Peer devices only. Do nothing if local BLE device called. - */ - void linkLost(); - operator bool() const; // central mode @@ -358,7 +347,9 @@ class BLEDeviceManager bool advertiseDataProc(uint8_t type, const uint8_t *dataPtr, uint8_t data_len); - bool setAdvertiseBuffer(const bt_addr_le_t* bt_addr); + bool setAdvertiseBuffer(const bt_addr_le_t* bt_addr, + const uint8_t *ad, + uint8_t data_len); private: uint16_t _min_conn_interval; @@ -368,8 +359,10 @@ class BLEDeviceManager // For Central bt_le_scan_param_t _scan_param; // Scan parameter - bt_addr_le_t _peer_adv_buffer[3]; // Accepted peer device adress - uint64_t _peer_adv_mill[3]; // The ADV found time stamp + bt_addr_le_t _peer_adv_buffer[BLE_MAX_ADV_BUFFER_CFG]; // Accepted peer device adress + uint64_t _peer_adv_mill[BLE_MAX_ADV_BUFFER_CFG]; // The ADV found time stamp + uint8_t _peer_adv_data[BLE_MAX_ADV_BUFFER_CFG][BLE_MAX_ADV_SIZE]; + uint8_t _peer_adv_data_len[BLE_MAX_ADV_BUFFER_CFG]; bt_data_t _adv_accept_critical; // The filters for central device String _adv_critical_local_name; bt_uuid_128_t _adv_critical_service_uuid; @@ -382,6 +375,8 @@ class BLEDeviceManager bool _has_service_solicit_uuid; bt_uuid_128_t _service_solicit_uuid; uint16_t _appearance; + uint8_t _manufacturer_data[BLE_MAX_ADV_SIZE]; + uint8_t _manufacturer_data_length; // ADV data for peripheral uint8_t _adv_type; diff --git a/libraries/BLE/src/BLEProfileManager.cpp b/libraries/BLE/src/BLEProfileManager.cpp index 634a8ed3..2d4c0608 100644 --- a/libraries/BLE/src/BLEProfileManager.cpp +++ b/libraries/BLE/src/BLEProfileManager.cpp @@ -44,10 +44,9 @@ BLEProfileManager::BLEProfileManager (): _discovering(false), _discover_timestamp(0), _cur_discover_service(NULL), + _reading(false), _attr_base(NULL), _attr_index(0), - _sub_param(NULL), - _sub_param_idx(0), _profile_registered(false) { //memset(_service_header_array, 0, sizeof(_service_header_array)); @@ -55,6 +54,7 @@ BLEProfileManager::BLEProfileManager (): memset(_addresses, 0, sizeof(_addresses)); memset(&_read_params, 0, sizeof(_read_params)); + memset(&_read_service_header, 0, sizeof(_read_service_header)); bt_addr_le_copy(&_addresses[BLE_MAX_CONN_CFG], BLEUtils::bleGetLoalAddress()); for (int i = 0; i <= BLE_MAX_CONN_CFG; i++) { @@ -71,6 +71,12 @@ BLEProfileManager::~BLEProfileManager (void) { bfree(this->_attr_base); } + ServiceReadLinkNodePtr node = link_node_get_first(&_read_service_header); + while (NULL != node) + { + link_node_remove_first(&_read_service_header); + node = link_node_get_first(&_read_service_header); + } } BLE_STATUS_T @@ -288,7 +294,7 @@ int BLEProfileManager::registerProfile(BLEDevice &bledevice) ret = bt_gatt_register(_attr_base, _attr_index); - pr_debug(LOG_MODULE_APP, "%s: ret, %d", __FUNCTION__, ret); + pr_debug(LOG_MODULE_APP, "%s: ret, %d,_attr_index-%d", __FUNCTION__, ret, _attr_index); if (0 == ret) { _profile_registered = true; @@ -650,6 +656,7 @@ uint8_t BLEProfileManager::discoverResponseProc(bt_conn_t *conn, else { int err_code = (int)addService(device, svc_value->uuid); + params->uuid = NULL; if (BLE_STATUS_SUCCESS == err_code) { BLEServiceImp* service_tmp = service(device, svc_value->uuid); @@ -679,45 +686,49 @@ uint8_t BLEProfileManager::discoverResponseProc(bt_conn_t *conn, } } - if (retVal == BT_GATT_ITER_STOP && discovering() == false) + if (retVal == BT_GATT_ITER_STOP) { - const BLEServiceLinkNodeHeader* serviceHeader = getServiceHeader(device); - BLEServiceImp* serviceCurImp = NULL; - if (NULL == serviceHeader) - { - // Doesn't find the service - return BT_GATT_ITER_STOP; - } - BLEServiceNodePtr node = serviceHeader->next; - - // Discover next service - while (node != NULL) + checkReadService(); + if (discovering() == false) { - serviceCurImp = node->value; + const BLEServiceLinkNodeHeader* serviceHeader = getServiceHeader(device); + BLEServiceImp* serviceCurImp = NULL; + if (NULL == serviceHeader) + { + // Doesn't find the service + return BT_GATT_ITER_STOP; + } + BLEServiceNodePtr node = serviceHeader->next; - if (NULL == _cur_discover_service) + // Discover next service + while (node != NULL) { - bool result = serviceCurImp->discoverAttributes(&device); - if (result == true) + serviceCurImp = node->value; + + if (NULL == _cur_discover_service) + { + bool result = serviceCurImp->discoverAttributes(&device); + if (result == true) + { + // Record the current discovering service + _cur_discover_service = serviceCurImp; + break; + } + } + else if (_cur_discover_service == serviceCurImp) { - // Record the current discovering service - _cur_discover_service = serviceCurImp; - break; + // Find next discoverable service + _cur_discover_service = NULL; } + + node = node->next; } - else if (_cur_discover_service == serviceCurImp) + if (NULL == node) { - // Find next discoverable service - _cur_discover_service = NULL; + pr_debug(LOG_MODULE_BLE, "%s-%d: Discover completed", + __FUNCTION__, __LINE__); + _start_discover = false; } - - node = node->next; - } - if (NULL == node) - { - pr_debug(LOG_MODULE_BLE, "%s-%d: Discover completed", - __FUNCTION__, __LINE__); - _start_discover = false; } } return retVal; @@ -758,7 +769,7 @@ void BLEProfileManager::serviceDiscoverComplete(const BLEDevice &bledevice) return; } -void BLEProfileManager::readService(const BLEDevice &bledevice, uint16_t handle) +bool BLEProfileManager::readService(const BLEDevice &bledevice, uint16_t handle) { int retval = 0; bt_conn_t* conn = NULL; @@ -766,13 +777,19 @@ void BLEProfileManager::readService(const BLEDevice &bledevice, uint16_t handle) if (true == BLEUtils::isLocalBLE(bledevice)) { // GATT server can't write - return;// false; + return false; } - //if (_reading) + if (_reading) { - // Already in reading state - //return false; + // Read response not back + // Add to buffer + ServiceRead_t temp; + bt_addr_le_copy(&temp.address, bledevice.bt_le_address()); + temp.handle = handle; + ServiceReadLinkNodePtr node = link_node_create(temp); + link_node_insert_last(&_read_service_header, node); + return true; } _read_params.func = profile_service_read_rsp_process; @@ -783,24 +800,39 @@ void BLEProfileManager::readService(const BLEDevice &bledevice, uint16_t handle) if (0 == _read_params.single.handle) { // Discover not complete - return ;//false; + return false; } conn = bt_conn_lookup_addr_le(bledevice.bt_le_address()); if (NULL == conn) { - return ;//false; + return false; } - setDiscovering(true); // Send read request retval = bt_gatt_read(conn, &_read_params); bt_conn_unref(conn); - //if (0 == retval) + if (0 == retval) { - //_reading = true; + setDiscovering(true); + _reading = true; } pr_debug(LOG_MODULE_BLE, "%s-%d", __FUNCTION__, __LINE__); - return ;//true; //_reading; + return _reading; +} + +void BLEProfileManager::checkReadService() +{ + ServiceReadLinkNodePtr node = link_node_get_first(&_read_service_header); + while (NULL != node) + { + BLEDevice temp(&node->value.address); + if (true == readService(temp, node->value.handle)) + { + break; + } + link_node_remove_first(&_read_service_header); + node = link_node_get_first(&_read_service_header); + } } bool BLEProfileManager::discoverService(BLEDevice* device, const bt_uuid_t* svc_uuid) @@ -856,6 +888,7 @@ uint8_t BLEProfileManager::serviceReadRspProc(bt_conn_t *conn, const void *data, uint16_t length) { + _reading = false; if (NULL == data) { return BT_GATT_ITER_STOP; diff --git a/libraries/BLE/src/BLEProfileManager.h b/libraries/BLE/src/BLEProfileManager.h index 2e6cf833..5c548f7d 100644 --- a/libraries/BLE/src/BLEProfileManager.h +++ b/libraries/BLE/src/BLEProfileManager.h @@ -27,6 +27,10 @@ //#include "BLECommon.h" //#include "BLEDevice.h" //#include "BLEService.h" +typedef struct { + bt_addr_le_t address; + uint16_t handle; +}ServiceRead_t; class BLEProfileManager{ public: @@ -120,6 +124,10 @@ class BLEProfileManager{ typedef LinkNode* BLEServiceNodePtr; typedef LinkNode BLEServiceNode; + typedef LinkNode ServiceReadLinkNodeHeader; + typedef LinkNode* ServiceReadLinkNodePtr; + typedef LinkNode ServiceReadLinkNode; + BLEProfileManager(); ~BLEProfileManager (void); @@ -175,9 +183,10 @@ class BLEProfileManager{ */ void clearProfile(BLEServiceLinkNodeHeader* serviceHeader); - void readService(const BLEDevice &bledevice, uint16_t handle); + bool readService(const BLEDevice &bledevice, uint16_t handle); bool discovering(); void setDiscovering(bool discover); + void checkReadService(); private: // The last header is for local BLE @@ -191,13 +200,12 @@ class BLEProfileManager{ bt_gatt_discover_params_t _discover_params[BLE_MAX_CONN_CFG]; BLEServiceImp* _cur_discover_service; bt_gatt_read_params_t _read_params; + bool _reading; + ServiceReadLinkNodeHeader _read_service_header; bt_gatt_attr_t *_attr_base; // Allocate the memory for BLE stack int _attr_index; - bt_gatt_subscribe_params_t *_sub_param; - int _sub_param_idx; - static BLEProfileManager* _instance; // The profile manager instance bool _profile_registered; }; diff --git a/libraries/BLE/src/BLEServiceImp.h b/libraries/BLE/src/BLEServiceImp.h index 765abd62..ccea6ab0 100644 --- a/libraries/BLE/src/BLEServiceImp.h +++ b/libraries/BLE/src/BLEServiceImp.h @@ -72,6 +72,8 @@ class BLEServiceImp: public BLEAttribute{ const bt_gatt_attr_t *attr, bt_gatt_discover_params_t *params); bool discovering(); + + static bt_uuid_t *getPrimayUuid(void); protected: friend class BLEProfileManager; @@ -79,8 +81,6 @@ class BLEServiceImp: public BLEAttribute{ int updateProfile(bt_gatt_attr_t *attr_start, int& index); - - static bt_uuid_t *getPrimayUuid(void); private: typedef LinkNode BLECharacteristicLinkNodeHeader; typedef LinkNode* BLECharacteristicNodePtr; From 18368568857c8747e2b329ff549a43108a76e220 Mon Sep 17 00:00:00 2001 From: lianggao Date: Mon, 7 Nov 2016 10:14:32 +0800 Subject: [PATCH 10/22] Enable some confirmed APIs 1. Enable buffer advertisment from peripheral 2. Enable ADV's RSSI 3. Enable scan without parameter --- libraries/BLE/src/BLEDevice.cpp | 23 +- libraries/BLE/src/BLEDevice.h | 12 + libraries/BLE/src/BLEDeviceManager.cpp | 398 ++++++++++++++++++++++--- libraries/BLE/src/BLEDeviceManager.h | 36 ++- 4 files changed, 418 insertions(+), 51 deletions(-) diff --git a/libraries/BLE/src/BLEDevice.cpp b/libraries/BLE/src/BLEDevice.cpp index c7e71e36..59e4463b 100644 --- a/libraries/BLE/src/BLEDevice.cpp +++ b/libraries/BLE/src/BLEDevice.cpp @@ -212,6 +212,13 @@ bool BLEDevice::operator!=(const BLEDevice& device) const } +void BLEDevice::startScanning() +{ + preCheckProfile(); + BLEDeviceManager::instance()->clearAdvertiseCritical(); + BLEDeviceManager::instance()->startScanning(); +} + void BLEDevice::startScanning(String name) { preCheckProfile(); @@ -243,42 +250,42 @@ BLEDevice BLEDevice::available() bool BLEDevice::hasLocalName() const { - return BLEDeviceManager::instance()->hasLocalName(); + return BLEDeviceManager::instance()->hasLocalName(this); } bool BLEDevice::hasAdvertisedServiceUuid() const { - return BLEDeviceManager::instance()->hasAdvertisedServiceUuid(); + return BLEDeviceManager::instance()->hasAdvertisedServiceUuid(this); } bool BLEDevice::hasAdvertisedServiceUuid(int index) const { - return BLEDeviceManager::instance()->hasAdvertisedServiceUuid(index); + return BLEDeviceManager::instance()->hasAdvertisedServiceUuid(this, index); } int BLEDevice::advertisedServiceUuidCount() const { - return BLEDeviceManager::instance()->advertisedServiceUuidCount(); + return BLEDeviceManager::instance()->advertisedServiceUuidCount(this); } String BLEDevice::localName() const { - return BLEDeviceManager::instance()->localName(); + return BLEDeviceManager::instance()->localName(this); } String BLEDevice::advertisedServiceUuid() const { - return BLEDeviceManager::instance()->advertisedServiceUuid(); + return BLEDeviceManager::instance()->advertisedServiceUuid(this); } String BLEDevice::advertisedServiceUuid(int index) const { - return BLEDeviceManager::instance()->advertisedServiceUuid(index); + return BLEDeviceManager::instance()->advertisedServiceUuid(this, index); } int BLEDevice::rssi() const { - return BLEDeviceManager::instance()->rssi(); + return BLEDeviceManager::instance()->rssi(this); } bool BLEDevice::connect() diff --git a/libraries/BLE/src/BLEDevice.h b/libraries/BLE/src/BLEDevice.h index 6d79b477..2ae6e693 100644 --- a/libraries/BLE/src/BLEDevice.h +++ b/libraries/BLE/src/BLEDevice.h @@ -351,6 +351,17 @@ class BLEDevice bool operator!=(const BLEDevice& device) const; // central mode + /** + * @brief Start scanning for peripherals without filter + * + * @param none + * + * @return none + * + * @note none + */ + void startScanning(); + /** * @brief Start scanning for peripherals and filter by device name in ADV * @@ -606,6 +617,7 @@ class BLEDevice const bt_addr_le_t* bt_le_address() const; const bt_le_conn_param* bt_conn_param() const; void setAddress(const bt_addr_le_t& addr); + void setAdvertiseData(const uint8_t* adv_data, uint8_t len); private: void preCheckProfile(); diff --git a/libraries/BLE/src/BLEDeviceManager.cpp b/libraries/BLE/src/BLEDeviceManager.cpp index e4b8875a..47f450ac 100644 --- a/libraries/BLE/src/BLEDeviceManager.cpp +++ b/libraries/BLE/src/BLEDeviceManager.cpp @@ -34,6 +34,8 @@ BLEDeviceManager::BLEDeviceManager(): _min_conn_interval(0), _max_conn_interval(0), _adv_critical_local_name(""), + _wait_for_connect_peripheral_adv_rssi(0), + _available_for_connect_peripheral_adv_rssi(0), _has_service_uuid(false), _has_service_solicit_uuid(false), _appearance(0), @@ -71,10 +73,15 @@ BLEDeviceManager::BLEDeviceManager(): memset(&_adv_critical_service_uuid, 0, sizeof(_adv_critical_service_uuid)); memset(_peer_peripheral, 0, sizeof(_peer_peripheral)); + memset(_peer_peripheral_adv_data, 0, sizeof(_peer_peripheral_adv_data)); + memset(_peer_peripheral_adv_data_len, 0, sizeof(_peer_peripheral_adv_data_len)); + memset(_peer_peripheral_adv_rssi, 0, sizeof(_peer_peripheral_adv_rssi)); + memset(_device_events, 0, sizeof(_device_events)); memset(_manufacturer_data, 0, sizeof(_manufacturer_data)); memset(_peer_adv_data, 0, sizeof(_peer_adv_data)); memset(_peer_adv_data_len, 0, sizeof(_peer_adv_data_len)); + memset(_peer_adv_rssi, 0, sizeof(_peer_adv_rssi)); } BLEDeviceManager::~BLEDeviceManager() @@ -131,14 +138,48 @@ bool BLEDeviceManager::connected(BLEDevice *device) bool BLEDeviceManager::disconnect(BLEDevice *device) { int err = 0; - bt_conn_t* conn = bt_conn_lookup_addr_le(device->bt_le_address()); - if (NULL == conn) + if (false == BLEUtils::isLocalBLE(*device)) { - return false; + // Remote device disconnect one + bt_conn_t* conn = bt_conn_lookup_addr_le(device->bt_le_address()); + if (NULL == conn) + { + return false; + } + + err = bt_conn_disconnect (conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + bt_conn_unref(conn); + } + else + { + if (true == BLEUtils::macAddressValid(_peer_central)) + { + // Remote device disconnect one + bt_conn_t* conn = bt_conn_lookup_addr_le(&_peer_central); + if (NULL != conn) + { + err = bt_conn_disconnect (conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + bt_conn_unref(conn); + } + } + + // Local device disconnect all connections + for (int i = 0; i < BLE_MAX_CONN_CFG; i++) + { + if (true == BLEUtils::macAddressValid(_peer_peripheral[i])) + { + // Remote device disconnect one + bt_conn_t* conn = bt_conn_lookup_addr_le(&_peer_peripheral[i]); + if (NULL == conn) + { + continue; + } + + err = bt_conn_disconnect (conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + bt_conn_unref(conn); + } + } } - - err = bt_conn_disconnect (conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); - bt_conn_unref(conn); return (err == 0); } @@ -434,6 +475,7 @@ BLEDevice BLEDeviceManager::peripheral() bool BLEDeviceManager::startScanning() { + _scan_param.filter_dup = BT_HCI_LE_SCAN_FILTER_DUP_ENABLE;//BT_HCI_LE_SCAN_FILTER_DUP_DISABLE; int err = bt_le_scan_start(&_scan_param, ble_central_device_found); if (err) { @@ -445,7 +487,13 @@ bool BLEDeviceManager::startScanning() bool BLEDeviceManager::startScanningWithDuplicates() { - // TODO: enable disable duplicate + _scan_param.filter_dup = BT_HCI_LE_SCAN_FILTER_DUP_ENABLE; + int err = bt_le_scan_start(&_scan_param, ble_central_device_found); + if (err) + { + pr_info(LOG_MODULE_BLE, "Scanning failed to start (err %d)\n", err); + return false; + } return false; } @@ -460,6 +508,12 @@ bool BLEDeviceManager::stopScanning() return true; } +void BLEDeviceManager::clearAdvertiseCritical() +{ + memset(&_adv_accept_critical, 0, sizeof(_adv_accept_critical)); + //memset(&_adv_critical_service_uuid, 0, sizeof(_adv_critical_service_uuid)); +} + void BLEDeviceManager::setAdvertiseCritical(String name) { _adv_critical_local_name = name; @@ -475,17 +529,16 @@ void BLEDeviceManager::setAdvertiseCritical(BLEService& service) uint8_t length = 0; uint8_t *data = NULL; - pr_info(LOG_MODULE_BLE, "ADV Type-%d", _service_uuid.uuid.type); - if (BT_UUID_TYPE_16 == _service_uuid.uuid.type) + pr_info(LOG_MODULE_BLE, "ADV Type-%d", _adv_critical_service_uuid.uuid.type); + if (BT_UUID_TYPE_16 == _adv_critical_service_uuid.uuid.type) { - //UINT16_TO_LESTREAM(adv_tmp, uuid.uuid16); - data = (uint8_t *)&(((bt_uuid_16_t *)&_service_uuid)->val); + data = (uint8_t *)&(((bt_uuid_16_t *)&_adv_critical_service_uuid)->val); length = UUID_SIZE_16; type = BT_DATA_UUID16_ALL; } - else if (BT_UUID_TYPE_128 == _service_uuid.uuid.type) + else if (BT_UUID_TYPE_128 == _adv_critical_service_uuid.uuid.type) { - data = _service_uuid.val; + data = _adv_critical_service_uuid.val; length = UUID_SIZE_128; type = BT_DATA_UUID128_ALL; } @@ -494,47 +547,303 @@ void BLEDeviceManager::setAdvertiseCritical(BLEService& service) _adv_accept_critical.data = data; } -bool BLEDeviceManager::hasLocalName() const +bool BLEDeviceManager::hasLocalName(const BLEDevice* device) const { - return (_local_name.length() != 0); + if (BLEUtils::isLocalBLE(*device) == true) + { + return (_local_name.length() != 0); + } + + const uint8_t* adv_data = NULL; + uint8_t adv_data_len = 0; + getDeviceAdvertiseBuffer(device->bt_le_address(), + adv_data, + adv_data_len); + if (NULL == adv_data) + { + return false; + } + + while (adv_data_len > 1) + { + uint8_t len = adv_data[0]; + uint8_t type = adv_data[1]; + + /* Check for early termination */ + if (len == 0) + { + return false; + } + + if ((len + 1 > adv_data_len) || (adv_data_len < 2)) { + pr_info(LOG_MODULE_BLE, "AD malformed\n"); + return false; + } + + if (type == BT_DATA_NAME_COMPLETE) + { + return true; + } + + adv_data_len -= len + 1; + adv_data += len + 1; + } + return false; } -bool BLEDeviceManager::hasAdvertisedServiceUuid() const +bool BLEDeviceManager::hasAdvertisedServiceUuid(const BLEDevice* device) const { - // TODO: - return false; + if (BLEUtils::isLocalBLE(*device) == true) + { + return _has_service_uuid; + } + + uint8_t service_cnt = advertisedServiceUuidCount(device); + return (service_cnt > 0); } -bool BLEDeviceManager::hasAdvertisedServiceUuid(int index) const +bool BLEDeviceManager::hasAdvertisedServiceUuid(const BLEDevice* device, int index) const { - // TODO: - return false; + uint8_t service_cnt = advertisedServiceUuidCount(device); + return (service_cnt > index); } -int BLEDeviceManager::advertisedServiceUuidCount() const +void BLEDeviceManager::getDeviceAdvertiseBuffer(const bt_addr_le_t* addr, + const uint8_t* &adv_data, + uint8_t &adv_len) const { - return 0; + const bt_addr_le_t* temp = NULL; + // Connected device + for (int i = 0; i < BLE_MAX_CONN_CFG; i++) + { + temp = &_peer_peripheral[i]; + if (bt_addr_le_cmp(temp, addr) == 0) + { + adv_data = _peer_peripheral_adv_data[i]; + adv_len = _peer_peripheral_adv_data_len[i]; + return; + } + } + + // Connecting device + if (bt_addr_le_cmp(&_wait_for_connect_peripheral, addr) == 0) + { + adv_data = _wait_for_connect_peripheral_adv_data; + adv_len = _wait_for_connect_peripheral_adv_data_len; + return; + } + + // Available device + if (bt_addr_le_cmp(&_available_for_connect_peripheral, addr) == 0) + { + adv_data = _available_for_connect_peripheral_adv_data; + adv_len = _available_for_connect_peripheral_adv_data_len; + return; + } + return; } -String BLEDeviceManager::localName() const +int BLEDeviceManager::advertisedServiceUuidCount(const BLEDevice* device) const { - return _local_name; + const uint8_t* adv_data = NULL; + uint8_t adv_data_len = 0; + uint8_t service_cnt = 0; + + if (BLEUtils::isLocalBLE(*device) == true) + { + if (_has_service_uuid) + service_cnt++; + return service_cnt; + } + + getDeviceAdvertiseBuffer(device->bt_le_address(), + adv_data, + adv_data_len); + if (NULL == adv_data) + { + return service_cnt; + } + + while (adv_data_len > 1) + { + uint8_t len = adv_data[0]; + uint8_t type = adv_data[1]; + + /* Check for early termination */ + if (len == 0) + { + return service_cnt; + } + + if ((len + 1 > adv_data_len) || (adv_data_len < 2)) { + pr_info(LOG_MODULE_BLE, "AD malformed\n"); + return service_cnt; + } + + if (type == BT_DATA_UUID16_ALL || + type == BT_DATA_UUID128_ALL) + { + service_cnt++; + } + + adv_data_len -= len + 1; + adv_data += len + 1; + } + return service_cnt; } -String BLEDeviceManager::advertisedServiceUuid() const +String BLEDeviceManager::localName(const BLEDevice* device) const { - // TODO - return ""; + if (BLEUtils::isLocalBLE(*device) == true) + { + return _local_name; + } + const uint8_t* adv_data = NULL; + uint8_t adv_data_len = 0; + char localname_string[BLE_MAX_ADV_SIZE]; + memset(localname_string, 0, sizeof(localname_string)); + + getDeviceAdvertiseBuffer(device->bt_le_address(), + adv_data, + adv_data_len); + if (NULL == adv_data) + { + return localname_string; + } + + while (adv_data_len > 1) + { + uint8_t len = adv_data[0]; + uint8_t type = adv_data[1]; + + /* Check for early termination */ + if (len == 0) + { + return localname_string; + } + + if ((len + 1 > adv_data_len) || (adv_data_len < 2)) { + pr_info(LOG_MODULE_BLE, "AD malformed\n"); + return localname_string; + } + + if (type == BT_DATA_NAME_COMPLETE) + { + memcpy(localname_string, &adv_data[2], len - 1); + //localname_string[len - 1] = '\0'; + break; + } + + adv_data_len -= len + 1; + adv_data += len + 1; + } + return localname_string; } -String BLEDeviceManager::advertisedServiceUuid(int index) const +String BLEDeviceManager::advertisedServiceUuid(const BLEDevice* device) const { - // TODO - return ""; + return advertisedServiceUuid(device, 0); +} + +String BLEDeviceManager::advertisedServiceUuid(const BLEDevice* device, int index) const +{ + const uint8_t* adv_data = NULL; + uint8_t adv_data_len = 0; + uint8_t service_cnt = 0; + bt_uuid_128_t service_uuid; + char uuid_string[37]; + memset(uuid_string, 0, sizeof(uuid_string)); + + if (BLEUtils::isLocalBLE(*device) == true) + { + // Local device only support advertise 1 service now. + if (_has_service_uuid && index == 0) + { + BLEUtils::uuidBT2String(&service_uuid.uuid, uuid_string); + } + return uuid_string; + } + + getDeviceAdvertiseBuffer(device->bt_le_address(), + adv_data, + adv_data_len); + + if (NULL == adv_data) + { + return uuid_string; + } + + while (adv_data_len > 1) + { + uint8_t len = adv_data[0]; + uint8_t type = adv_data[1]; + + /* Check for early termination */ + if (len == 0) + { + return uuid_string; + } + + if ((len + 1 > adv_data_len) || (adv_data_len < 2)) { + pr_info(LOG_MODULE_BLE, "AD malformed\n"); + return uuid_string; + } + + if (type == BT_DATA_UUID16_ALL || + type == BT_DATA_UUID128_ALL) + { + service_cnt++; + } + + if (index < service_cnt) + { + if (type == BT_DATA_UUID16_ALL) + { + service_uuid.uuid.type = BT_UUID_TYPE_16; + memcpy(&BT_UUID_16(&service_uuid.uuid)->val, &adv_data[2], 2); + } + else + { + service_uuid.uuid.type = BT_UUID_TYPE_128; + memcpy(service_uuid.val, &adv_data[2], 16); + } + + BLEUtils::uuidBT2String(&service_uuid.uuid, uuid_string); + + break; + } + + adv_data_len -= len + 1; + adv_data += len + 1; + } + return uuid_string; } -int BLEDeviceManager::rssi() const +int BLEDeviceManager::rssi(const BLEDevice* device) const { + const bt_addr_le_t* temp = NULL; + const bt_addr_le_t* addr = device->bt_le_address(); + // Connected device + for (int i = 0; i < BLE_MAX_CONN_CFG; i++) + { + temp = &_peer_peripheral[i]; + if (bt_addr_le_cmp(temp, addr) == 0) + { + return _peer_peripheral_adv_rssi[i]; + } + } + + // Connecting device + if (bt_addr_le_cmp(&_wait_for_connect_peripheral, addr) == 0) + { + return _wait_for_connect_peripheral_adv_rssi; + } + + // Available device + if (bt_addr_le_cmp(&_available_for_connect_peripheral, addr) == 0) + { + return _available_for_connect_peripheral_adv_rssi; + } return 0; } @@ -545,6 +854,11 @@ bool BLEDeviceManager::connect(BLEDevice &device) uint64_t timestampcur = timestamp; bool ret = true; bt_addr_le_copy(&_wait_for_connect_peripheral, device.bt_le_address()); + // Buffer the ADV data + memcpy(_wait_for_connect_peripheral_adv_data, _available_for_connect_peripheral_adv_data, BLE_MAX_ADV_SIZE); + _wait_for_connect_peripheral_adv_data_len = _available_for_connect_peripheral_adv_data_len; + _wait_for_connect_peripheral_adv_rssi = _available_for_connect_peripheral_adv_rssi; + startScanning(); pr_debug(LOG_MODULE_BLE, "%s-%d", __FUNCTION__, __LINE__); @@ -593,6 +907,12 @@ bool BLEDeviceManager::connectToDevice(BLEDevice &device) if (NULL == unused) { unused = temp; + // Buffer the ADV data + memcpy(_peer_peripheral_adv_data[i], + _wait_for_connect_peripheral_adv_data, + BLE_MAX_ADV_SIZE); + _peer_peripheral_adv_data_len[i] = _wait_for_connect_peripheral_adv_data_len; + _peer_peripheral_adv_rssi[i] = _wait_for_connect_peripheral_adv_rssi; } } } @@ -683,6 +1003,9 @@ void BLEDeviceManager::handleDisconnectEvent(bt_conn_t *conn, uint8_t reason) if (bt_addr_le_cmp(temp, disConnAddr) == 0) { memset(temp, 0, sizeof(bt_addr_le_t)); + memset(_peer_peripheral_adv_data[i], 0, BLE_MAX_ADV_SIZE); + _peer_peripheral_adv_data_len[i] = 0; + _peer_peripheral_adv_rssi[i] = 0; break; } } @@ -764,6 +1087,11 @@ BLEDevice BLEDeviceManager::available() if (true == BLEUtils::macAddressValid(*temp)) { tempdevice.setAddress(*temp); + bt_addr_le_copy(&_available_for_connect_peripheral, temp); + memcpy(_available_for_connect_peripheral_adv_data, _peer_adv_data[index], BLE_MAX_ADV_SIZE); + _available_for_connect_peripheral_adv_data_len = _peer_adv_data_len[index]; + _available_for_connect_peripheral_adv_rssi = _peer_adv_rssi[index]; + pr_debug(LOG_MODULE_BLE, "%s-%d:Con addr-%s", __FUNCTION__, __LINE__, BLEUtils::macAddressBT2String(*temp).c_str()); _peer_adv_mill[index] -= 2000; // Set it as expired } @@ -773,7 +1101,8 @@ BLEDevice BLEDeviceManager::available() bool BLEDeviceManager::setAdvertiseBuffer(const bt_addr_le_t* bt_addr, const uint8_t *ad, - uint8_t data_len) + uint8_t data_len, + int8_t rssi) { bt_addr_le_t* temp = NULL; uint64_t timestamp = millis(); @@ -817,6 +1146,7 @@ bool BLEDeviceManager::setAdvertiseBuffer(const bt_addr_le_t* bt_addr, } memcpy(_peer_adv_data[index], ad, data_len); _peer_adv_data_len[index] = data_len; + _peer_adv_rssi[index] = rssi; // Update the timestamp _peer_adv_mill[index] = timestamp; retval = true; @@ -868,7 +1198,7 @@ void BLEDeviceManager::handleDeviceFound(const bt_addr_le_t *addr, { // The critical is accepted // Find the oldest and expired buffer - if(false == setAdvertiseBuffer(addr, ad, data_len)) + if(false == setAdvertiseBuffer(addr, ad, data_len, rssi)) { pr_info(LOG_MODULE_BLE, "No buffer to store the ADV\n"); } diff --git a/libraries/BLE/src/BLEDeviceManager.h b/libraries/BLE/src/BLEDeviceManager.h index e5af8feb..84f584c1 100644 --- a/libraries/BLE/src/BLEDeviceManager.h +++ b/libraries/BLE/src/BLEDeviceManager.h @@ -297,6 +297,7 @@ class BLEDeviceManager operator bool() const; // central mode + void clearAdvertiseCritical(); void setAdvertiseCritical(String name); void setAdvertiseCritical(BLEService& service); bool startScanning(); // start scanning for peripherals @@ -309,16 +310,16 @@ class BLEDeviceManager BLEDevice available(); // retrieve a discovered peripheral - bool hasLocalName() const; // does the peripheral advertise a local name - bool hasAdvertisedServiceUuid() const; // does the peripheral advertise a service - bool hasAdvertisedServiceUuid(int index) const; // does the peripheral advertise a service n - int advertisedServiceUuidCount() const; // number of services the peripheral is advertising + bool hasLocalName(const BLEDevice* device) const; // does the peripheral advertise a local name + bool hasAdvertisedServiceUuid(const BLEDevice* device) const; // does the peripheral advertise a service + bool hasAdvertisedServiceUuid(const BLEDevice* device, int index) const; // does the peripheral advertise a service n + int advertisedServiceUuidCount(const BLEDevice* device) const; // number of services the peripheral is advertising - String localName() const; // returns the advertised local name as a String - String advertisedServiceUuid() const; // returns the advertised service as a UUID String - String advertisedServiceUuid(int index) const; // returns the nth advertised service as a UUID String + String localName(const BLEDevice* device) const; // returns the advertised local name as a String + String advertisedServiceUuid(const BLEDevice* device) const; // returns the advertised service as a UUID String + String advertisedServiceUuid(const BLEDevice* device, int index) const; // returns the nth advertised service as a UUID String - int rssi() const; // returns the RSSI of the peripheral at discovery + int rssi(const BLEDevice* device) const; // returns the RSSI of the peripheral at discovery bool connect(BLEDevice &device); // connect to the peripheral bool connectToDevice(BLEDevice &device); @@ -349,7 +350,11 @@ class BLEDeviceManager uint8_t data_len); bool setAdvertiseBuffer(const bt_addr_le_t* bt_addr, const uint8_t *ad, - uint8_t data_len); + uint8_t data_len, + int8_t rssi); + void getDeviceAdvertiseBuffer(const bt_addr_le_t* addr, + const uint8_t* &adv_data, + uint8_t &adv_len) const; private: uint16_t _min_conn_interval; @@ -363,10 +368,20 @@ class BLEDeviceManager uint64_t _peer_adv_mill[BLE_MAX_ADV_BUFFER_CFG]; // The ADV found time stamp uint8_t _peer_adv_data[BLE_MAX_ADV_BUFFER_CFG][BLE_MAX_ADV_SIZE]; uint8_t _peer_adv_data_len[BLE_MAX_ADV_BUFFER_CFG]; + int8_t _peer_adv_rssi[BLE_MAX_ADV_BUFFER_CFG]; bt_data_t _adv_accept_critical; // The filters for central device String _adv_critical_local_name; bt_uuid_128_t _adv_critical_service_uuid; + bt_addr_le_t _wait_for_connect_peripheral; + uint8_t _wait_for_connect_peripheral_adv_data[BLE_MAX_ADV_SIZE]; + uint8_t _wait_for_connect_peripheral_adv_data_len; + int8_t _wait_for_connect_peripheral_adv_rssi; + + bt_addr_le_t _available_for_connect_peripheral; + uint8_t _available_for_connect_peripheral_adv_data[BLE_MAX_ADV_SIZE]; + uint8_t _available_for_connect_peripheral_adv_data_len; + int8_t _available_for_connect_peripheral_adv_rssi; // For peripheral struct bt_le_adv_param _adv_param; @@ -400,6 +415,9 @@ class BLEDeviceManager // Connected device object bt_addr_le_t _peer_central; bt_addr_le_t _peer_peripheral[BLE_MAX_CONN_CFG]; + uint8_t _peer_peripheral_adv_data[BLE_MAX_CONN_CFG][BLE_MAX_ADV_SIZE]; + uint8_t _peer_peripheral_adv_data_len[BLE_MAX_CONN_CFG]; + uint8_t _peer_peripheral_adv_rssi[BLE_MAX_CONN_CFG]; BLEDeviceEventHandler _device_events[BLEDeviceLastEvent]; }; From bf78754e07730a11979fff2844dd9d2c8236aa02 Mon Sep 17 00:00:00 2001 From: lianggao Date: Mon, 7 Nov 2016 17:03:51 +0800 Subject: [PATCH 11/22] Update the code based on review 1. Support valueupdated event 2. Change the balloc/bfree to malloc/free 3. Change return type from BLE_STATUS_T to int --- libraries/BLE/src/BLECharacteristic.cpp | 8 ++--- libraries/BLE/src/BLECharacteristicImp.cpp | 36 +++++++++++++++------- libraries/BLE/src/BLEDescriptor.cpp | 6 ++-- libraries/BLE/src/BLEDescriptorImp.cpp | 6 ++-- libraries/BLE/src/BLEProfileManager.cpp | 6 ++-- 5 files changed, 38 insertions(+), 24 deletions(-) diff --git a/libraries/BLE/src/BLECharacteristic.cpp b/libraries/BLE/src/BLECharacteristic.cpp index 7af20164..11df1a92 100644 --- a/libraries/BLE/src/BLECharacteristic.cpp +++ b/libraries/BLE/src/BLECharacteristic.cpp @@ -50,13 +50,13 @@ BLECharacteristic::~BLECharacteristic() { if (_value) { - bfree(_value); + free(_value); _value = NULL; } if (_event_handlers != NULL) { - bfree(_event_handlers); + free(_event_handlers); _event_handlers = NULL; } } @@ -435,7 +435,7 @@ void BLECharacteristic::setEventHandler(BLECharacteristicEvent event, { if (_event_handlers == NULL) { - _event_handlers = (BLECharacteristicEventHandler*)balloc(sizeof(BLECharacteristicEventHandler) * BLECharacteristicEventLast, NULL); + _event_handlers = (BLECharacteristicEventHandler*)malloc(sizeof(BLECharacteristicEventHandler) * BLECharacteristicEventLast); } if (_event_handlers != NULL) @@ -456,7 +456,7 @@ BLECharacteristic::_setValue(const uint8_t value[], uint16_t length) if (NULL == _value) { // Allocate the buffer for characteristic - _value = (unsigned char*)balloc(_value_size, NULL);//malloc(_value_size) + _value = (unsigned char*)malloc(_value_size); } if (NULL == _value) { diff --git a/libraries/BLE/src/BLECharacteristicImp.cpp b/libraries/BLE/src/BLECharacteristicImp.cpp index 3483f07f..7183be53 100644 --- a/libraries/BLE/src/BLECharacteristicImp.cpp +++ b/libraries/BLE/src/BLECharacteristicImp.cpp @@ -43,10 +43,10 @@ BLECharacteristicImp::BLECharacteristicImp(const bt_uuid_t* uuid, _ble_device() { _value_size = BLE_MAX_ATTR_DATA_LEN;// Set as MAX value. TODO: long read/write need to twist - _value = (unsigned char*)balloc(_value_size, NULL); + _value = (unsigned char*)malloc(_value_size); if (_value_size > BLE_MAX_ATTR_DATA_LEN) { - _value_buffer = (unsigned char*)balloc(_value_size, NULL); + _value_buffer = (unsigned char*)malloc(_value_size); } memset(_value, 0, _value_size); @@ -107,10 +107,10 @@ BLECharacteristicImp::BLECharacteristicImp(BLECharacteristic& characteristic, { unsigned char properties = characteristic._properties; _value_size = characteristic._value_size; - _value = (unsigned char*)balloc(_value_size, NULL); + _value = (unsigned char*)malloc(_value_size); if (_value_size > BLE_MAX_ATTR_DATA_LEN) { - _value_buffer = (unsigned char*)balloc(_value_size, NULL); + _value_buffer = (unsigned char*)malloc(_value_size); } memset(&_ccc_cfg, 0, sizeof(_ccc_cfg)); @@ -173,12 +173,12 @@ BLECharacteristicImp::~BLECharacteristicImp() { releaseDescriptors(); if (_value) { - bfree(_value); + free(_value); _value = NULL; } if (_value_buffer) { - bfree(_value_buffer); + free(_value_buffer); _value_buffer = NULL; } } @@ -245,12 +245,26 @@ bool BLECharacteristicImp::setValue(const unsigned char value[], uint16_t length) { _setValue(value, length, 0); - // Read response/Notification/Indication for GATT client - // Write request for GATT server - if (_event_handlers[BLEWritten]) + if (BLEUtils::isLocalBLE(_ble_device) == true) { - BLECharacteristic chrcTmp(this, &_ble_device); - _event_handlers[BLEWritten](_ble_device, chrcTmp); + // GATT server + // Write request for GATT server + if (_event_handlers[BLEWritten]) + { + BLECharacteristic chrcTmp(this, &_ble_device); + _event_handlers[BLEWritten](_ble_device, chrcTmp); + } + } + else + { + // GATT client + // Discovered attribute + // Read response/Notification/Indication for GATT client + if (_event_handlers[BLEValueUpdated]) + { + BLECharacteristic chrcTmp(this, &_ble_device); + _event_handlers[BLEValueUpdated](_ble_device, chrcTmp); + } } return true; diff --git a/libraries/BLE/src/BLEDescriptor.cpp b/libraries/BLE/src/BLEDescriptor.cpp index 309b62c7..aa310531 100644 --- a/libraries/BLE/src/BLEDescriptor.cpp +++ b/libraries/BLE/src/BLEDescriptor.cpp @@ -40,7 +40,7 @@ BLEDescriptor::BLEDescriptor(BLEDescriptorImp* descriptorImp, BLEUtils::uuidBT2String(descriptorImp->bt_uuid(), _uuid_cstr); _value_size = descriptorImp->valueSize(); - _value = (unsigned char*)balloc(_value_size, NULL); + _value = (unsigned char*)malloc(_value_size); memcpy(_value, descriptorImp->value(), _value_size); } @@ -57,7 +57,7 @@ BLEDescriptor::BLEDescriptor(const char* uuid, _bledev.setAddress(*BLEUtils::bleGetLoalAddress()); _value_size = valueLength > BLE_MAX_ATTR_LONGDATA_LEN ? BLE_MAX_ATTR_LONGDATA_LEN : valueLength; - _value = (unsigned char*)balloc(_value_size, NULL); + _value = (unsigned char*)malloc(_value_size); memcpy(_value, value, _value_size); } @@ -70,7 +70,7 @@ BLEDescriptor::~BLEDescriptor() { if (_value) { - bfree(_value); + free(_value); _value = NULL; } } diff --git a/libraries/BLE/src/BLEDescriptorImp.cpp b/libraries/BLE/src/BLEDescriptorImp.cpp index e9a9c22d..df323272 100644 --- a/libraries/BLE/src/BLEDescriptorImp.cpp +++ b/libraries/BLE/src/BLEDescriptorImp.cpp @@ -32,7 +32,7 @@ BLEDescriptorImp::BLEDescriptorImp(BLEDevice& bledevice, _properties = descriptor.properties(); _value_length = descriptor.valueLength(); - _value = (unsigned char*)balloc(_value_length, NULL); + _value = (unsigned char*)malloc(_value_length); memcpy(_value, descriptor.value(), _value_length); } @@ -46,14 +46,14 @@ BLEDescriptorImp::BLEDescriptorImp(const bt_uuid_t* uuid, _properties(properties) { _value_length = BLE_MAX_ATTR_DATA_LEN; - _value = (unsigned char*)balloc(_value_length, NULL); + _value = (unsigned char*)malloc(_value_length); memset(_value, 0, _value_length); } BLEDescriptorImp::~BLEDescriptorImp() { if (_value) { - bfree(_value); + free(_value); _value = NULL; } } diff --git a/libraries/BLE/src/BLEProfileManager.cpp b/libraries/BLE/src/BLEProfileManager.cpp index 2d4c0608..eb641c74 100644 --- a/libraries/BLE/src/BLEProfileManager.cpp +++ b/libraries/BLE/src/BLEProfileManager.cpp @@ -69,7 +69,7 @@ BLEProfileManager::~BLEProfileManager (void) { if (this->_attr_base) { - bfree(this->_attr_base); + free(this->_attr_base); } ServiceReadLinkNodePtr node = link_node_get_first(&_read_service_header); while (NULL != node) @@ -246,7 +246,7 @@ int BLEProfileManager::registerProfile(BLEDevice &bledevice) if (NULL == _attr_base) { - _attr_base = (bt_gatt_attr_t *)balloc(attr_counter * sizeof(bt_gatt_attr_t), NULL); + _attr_base = (bt_gatt_attr_t *)malloc(attr_counter * sizeof(bt_gatt_attr_t)); memset(_attr_base, 0x00, (attr_counter * sizeof(bt_gatt_attr_t))); pr_info(LOG_MODULE_BLE, "_attr_base_-%p, size-%d, attr_counter-%d", _attr_base, sizeof(_attr_base), attr_counter); if (NULL == _attr_base) @@ -259,7 +259,7 @@ int BLEProfileManager::registerProfile(BLEDevice &bledevice) { if (NULL != _attr_base) { - bfree(_attr_base); + free(_attr_base); } return err_code; } From fd4e43ac1c58590140058e6779d339f0d8b5acb6 Mon Sep 17 00:00:00 2001 From: lianggao Date: Mon, 7 Nov 2016 17:15:23 +0800 Subject: [PATCH 12/22] Update code based on code review 1. Implement BLEAttributeWithValue class 2. Change the code structure 3. Change the return value --- libraries/BLE/src/ArduinoBLE.h | 2 +- libraries/BLE/src/BLEAttribute.cpp | 2 +- libraries/BLE/src/BLEAttributeWithValue.cpp | 181 ++++++++++++++++++ libraries/BLE/src/BLEAttributeWithValue.h | 8 +- libraries/BLE/src/BLECharacteristic.cpp | 6 +- libraries/BLE/src/BLECharacteristic.h | 2 +- libraries/BLE/src/BLEDescriptor.cpp | 4 +- libraries/BLE/src/BLEDevice.cpp | 12 +- libraries/BLE/src/BLEDevice.h | 8 +- libraries/BLE/src/BLEService.cpp | 6 +- .../BLE/src/{ => internal}/BLECallbacks.cpp | 0 .../BLE/src/{ => internal}/BLECallbacks.h | 0 .../{ => internal}/BLECharacteristicImp.cpp | 0 .../src/{ => internal}/BLECharacteristicImp.h | 0 .../src/{ => internal}/BLEDescriptorImp.cpp | 0 .../BLE/src/{ => internal}/BLEDescriptorImp.h | 0 .../src/{ => internal}/BLEDeviceManager.cpp | 0 .../BLE/src/{ => internal}/BLEDeviceManager.h | 0 .../src/{ => internal}/BLEProfileManager.cpp | 0 .../src/{ => internal}/BLEProfileManager.h | 0 .../BLE/src/{ => internal}/BLEServiceImp.cpp | 0 .../BLE/src/{ => internal}/BLEServiceImp.h | 0 libraries/BLE/src/{ => internal}/BLEUtils.cpp | 0 libraries/BLE/src/{ => internal}/BLEUtils.h | 0 libraries/BLE/src/{ => internal}/LinkList.h | 0 25 files changed, 206 insertions(+), 25 deletions(-) create mode 100644 libraries/BLE/src/BLEAttributeWithValue.cpp rename libraries/BLE/src/{ => internal}/BLECallbacks.cpp (100%) rename libraries/BLE/src/{ => internal}/BLECallbacks.h (100%) rename libraries/BLE/src/{ => internal}/BLECharacteristicImp.cpp (100%) rename libraries/BLE/src/{ => internal}/BLECharacteristicImp.h (100%) rename libraries/BLE/src/{ => internal}/BLEDescriptorImp.cpp (100%) rename libraries/BLE/src/{ => internal}/BLEDescriptorImp.h (100%) rename libraries/BLE/src/{ => internal}/BLEDeviceManager.cpp (100%) rename libraries/BLE/src/{ => internal}/BLEDeviceManager.h (100%) rename libraries/BLE/src/{ => internal}/BLEProfileManager.cpp (100%) rename libraries/BLE/src/{ => internal}/BLEProfileManager.h (100%) rename libraries/BLE/src/{ => internal}/BLEServiceImp.cpp (100%) rename libraries/BLE/src/{ => internal}/BLEServiceImp.h (100%) rename libraries/BLE/src/{ => internal}/BLEUtils.cpp (100%) rename libraries/BLE/src/{ => internal}/BLEUtils.h (100%) rename libraries/BLE/src/{ => internal}/LinkList.h (100%) diff --git a/libraries/BLE/src/ArduinoBLE.h b/libraries/BLE/src/ArduinoBLE.h index 3a74f51f..fc83d81b 100644 --- a/libraries/BLE/src/ArduinoBLE.h +++ b/libraries/BLE/src/ArduinoBLE.h @@ -32,7 +32,7 @@ class BLEDescriptorImp; #include "BLECommon.h" #include "BLEDevice.h" - +#include "BLEAttributeWithValue.h" #include "BLECharacteristic.h" #include "BLEDescriptor.h" #include "BLEService.h" diff --git a/libraries/BLE/src/BLEAttribute.cpp b/libraries/BLE/src/BLEAttribute.cpp index 01eef6fe..b3e3b711 100644 --- a/libraries/BLE/src/BLEAttribute.cpp +++ b/libraries/BLE/src/BLEAttribute.cpp @@ -20,7 +20,7 @@ #include "ArduinoBLE.h" #include "BLEAttribute.h" -#include "BLEUtils.h" +#include "./internal/BLEUtils.h" BLEAttribute::BLEAttribute(const char* uuid, BLEAttributeType type) : diff --git a/libraries/BLE/src/BLEAttributeWithValue.cpp b/libraries/BLE/src/BLEAttributeWithValue.cpp new file mode 100644 index 00000000..88c629f1 --- /dev/null +++ b/libraries/BLE/src/BLEAttributeWithValue.cpp @@ -0,0 +1,181 @@ +/* + BLE Attribute with value API + Copyright (c) 2016 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "Arduino.h" +#include "BLEAttributeWithValue.h" + +BLEAttributeWithValue::BLEAttributeWithValue() +{ + +} + + // intepret the value of the attribute with the specified type +String BLEAttributeWithValue::stringValue() const +{ + const char *retTemp = (const char *)this->value(); + return retTemp; +} + +char BLEAttributeWithValue::charValue() const +{ + char ret = this->operator[](0); + return ret; +} + +unsigned char BLEAttributeWithValue::unsignedCharValue() const +{ + unsigned char ret = this->operator[](0); + return ret; +} + +byte BLEAttributeWithValue::byteValue() const +{ + return this->operator[](0); +} + +short BLEAttributeWithValue::shortValue() const +{ + short retTmp = 0; + memcpy(&retTmp, this->value(), sizeof(retTmp)); + return retTmp; +} + +unsigned short BLEAttributeWithValue::unsignedShortValue() const +{ + unsigned short retTmp = 0; + memcpy(&retTmp, this->value(), sizeof(retTmp)); + return retTmp; +} + +int BLEAttributeWithValue::intValue() const +{ + int retTmp = 0; + memcpy(&retTmp, this->value(), sizeof(retTmp)); + return retTmp; +} + +unsigned int BLEAttributeWithValue::unsignedIntValue() const +{ + unsigned int retTmp = 0; + memcpy(&retTmp, this->value(), sizeof(retTmp)); + return retTmp; +} + +long BLEAttributeWithValue::longValue() const +{ + long retTmp = 0; + memcpy(&retTmp, this->value(), sizeof(retTmp)); + return retTmp; +} + +unsigned long BLEAttributeWithValue::unsignedLongValue() const +{ + unsigned long retTmp = 0; + memcpy(&retTmp, this->value(), sizeof(retTmp)); + return retTmp; +} + +float BLEAttributeWithValue::floatValue() const +{ + float retTmp = 0; + memcpy(&retTmp, this->value(), sizeof(retTmp)); + return retTmp; +} + +double BLEAttributeWithValue::doubleValue() const +{ + double retTmp = 0; + memcpy(&retTmp, this->value(), sizeof(retTmp)); + return retTmp; +} + +// write the value of the attribute with the specified type +bool BLEAttributeWithValue::writeString(const String& s) +{ + if (s.length() > (unsigned int)this->valueSize()) + { + return false; + } + return this->writeValue((const byte*)s.c_str(), s.length()); +} + +bool BLEAttributeWithValue::writeString(const char* s) +{ + if (strlen(s) > (unsigned int)this->valueSize()) + { + return false; + } + return this->writeValue((const byte*)s, strlen(s)); +} + +bool BLEAttributeWithValue::writeChar(char c) +{ + return this->writeValue((const byte*)&c, sizeof(c)); +} + +bool BLEAttributeWithValue::writeUnsignedChar(unsigned char c) +{ + return this->writeValue((const byte*)&c, sizeof(c)); +} + +bool BLEAttributeWithValue::writeByte(byte b) +{ + return this->writeValue((const byte*)&b, sizeof(b)); +} + +bool BLEAttributeWithValue::writeShort(short s) +{ + return this->writeValue((const byte*)&s, sizeof(s)); +} + +bool BLEAttributeWithValue::writeUnsignedShort(unsigned short s) +{ + return this->writeValue((const byte*)&s, sizeof(s)); +} + +bool BLEAttributeWithValue::writeInt(int i) +{ + return this->writeValue((const byte*)&i, sizeof(i)); +} + +bool BLEAttributeWithValue::writeUnsignedInt(unsigned int i) +{ + return this->writeValue((const byte*)&i, sizeof(i)); +} + +bool BLEAttributeWithValue::writeLong(long l) +{ + return this->writeValue((const byte*)&l, sizeof(l)); +} + +bool BLEAttributeWithValue::writeUnsignedLong(unsigned int l) +{ + return this->writeValue((const byte*)&l, sizeof(l)); +} + +bool BLEAttributeWithValue::writeFloat(float f) +{ + return this->writeValue((const byte*)&f, sizeof(f)); +} + +bool BLEAttributeWithValue::writeDouble(double d) +{ + return this->writeValue((const byte*)&d, sizeof(d)); +} + + diff --git a/libraries/BLE/src/BLEAttributeWithValue.h b/libraries/BLE/src/BLEAttributeWithValue.h index d1e2c152..96971d56 100644 --- a/libraries/BLE/src/BLEAttributeWithValue.h +++ b/libraries/BLE/src/BLEAttributeWithValue.h @@ -25,10 +25,10 @@ class BLEAttributeWithValue public: BLEAttributeWithValue(); - virtual int valueLength() const; // returns the length of the attribute value - virtual const byte* value() const; // returns the value of the attribute array - virtual byte operator[] (int offset) const; // access an attribute value at the specified offset - virtual bool writeValue(const byte value[], int length); + virtual int valueSize() const = 0; // returns the length of the attribute value + virtual const byte* value() const = 0; // returns the value of the attribute array + virtual byte operator[] (int offset) const = 0; // access an attribute value at the specified offset + virtual bool writeValue(const byte value[], int length) = 0; // intepret the value of the attribute with the specified type String stringValue() const; diff --git a/libraries/BLE/src/BLECharacteristic.cpp b/libraries/BLE/src/BLECharacteristic.cpp index 11df1a92..d37849be 100644 --- a/libraries/BLE/src/BLECharacteristic.cpp +++ b/libraries/BLE/src/BLECharacteristic.cpp @@ -1,12 +1,12 @@ #include "ArduinoBLE.h" -#include "BLEUtils.h" +#include "./internal/BLEUtils.h" #include "BLECharacteristic.h" -#include "BLEProfileManager.h" +#include "./internal/BLEProfileManager.h" -#include "BLECharacteristicImp.h" +#include "./internal/BLECharacteristicImp.h" BLECharacteristic::BLECharacteristic(): _bledev(), _internal(NULL), _properties(0), diff --git a/libraries/BLE/src/BLECharacteristic.h b/libraries/BLE/src/BLECharacteristic.h index 076d168d..b2de4f83 100644 --- a/libraries/BLE/src/BLECharacteristic.h +++ b/libraries/BLE/src/BLECharacteristic.h @@ -45,7 +45,7 @@ typedef void (*BLECharacteristicEventHandler)(BLEDevice& bledev, BLECharacterist //#include "BLECharacteristicImp.h" -class BLECharacteristic //: public BLEAttributeWithValue +class BLECharacteristic: public BLEAttributeWithValue { public: BLECharacteristic(); diff --git a/libraries/BLE/src/BLEDescriptor.cpp b/libraries/BLE/src/BLEDescriptor.cpp index aa310531..5cdc07b9 100644 --- a/libraries/BLE/src/BLEDescriptor.cpp +++ b/libraries/BLE/src/BLEDescriptor.cpp @@ -18,8 +18,8 @@ */ #include "BLEAttribute.h" #include "BLEDescriptor.h" -#include "BLEUtils.h" -#include "BLEDescriptorImp.h" +#include "./internal/BLEUtils.h" +#include "./internal/BLEDescriptorImp.h" BLEDescriptor::BLEDescriptor(): _properties(0), diff --git a/libraries/BLE/src/BLEDevice.cpp b/libraries/BLE/src/BLEDevice.cpp index 59e4463b..cc837f11 100644 --- a/libraries/BLE/src/BLEDevice.cpp +++ b/libraries/BLE/src/BLEDevice.cpp @@ -19,11 +19,11 @@ #include "ArduinoBLE.h" #include "BLEDevice.h" -#include "BLEUtils.h" +#include "./internal/BLEUtils.h" -#include "BLEProfileManager.h" -#include "BLEDeviceManager.h" -#include "BLECharacteristicImp.h" +#include "./internal/BLEProfileManager.h" +#include "./internal/BLEDeviceManager.h" +#include "./internal/BLECharacteristicImp.h" BLEDevice::BLEDevice() { @@ -159,12 +159,12 @@ void BLEDevice::setAppearance(unsigned short appearance) BLEDeviceManager::instance()->setAppearance(appearance); } -BLE_STATUS_T BLEDevice::addService(BLEService& attribute) +int BLEDevice::addService(BLEService& attribute) { return BLEProfileManager::instance()->addService(*this, attribute); } -BLE_STATUS_T BLEDevice::startAdvertising() +int BLEDevice::startAdvertising() { preCheckProfile(); return BLEDeviceManager::instance()->startAdvertising(); diff --git a/libraries/BLE/src/BLEDevice.h b/libraries/BLE/src/BLEDevice.h index 2ae6e693..d94736ff 100644 --- a/libraries/BLE/src/BLEDevice.h +++ b/libraries/BLE/src/BLEDevice.h @@ -298,22 +298,22 @@ class BLEDevice * * @param[in] attribute The service that will add to Peripheral * - * @return BLE_STATUS_T Indicating success or error type + * @return int Indicating success or error type @enum BLE_STATUS_T * * @note This method must be called before the begin method */ - BLE_STATUS_T addService(BLEService& attribute); + int addService(BLEService& attribute); /** * @brief Construct the ADV data and start send advertisement * * @param none * - * @return BLE_STATUS_T 0 - Success. Others - error code + * @return int 0 - Success. Others - error code @enum BLE_STATUS_T * * @note none */ - BLE_STATUS_T startAdvertising(); + int startAdvertising(); /** * @brief Stop send advertisement diff --git a/libraries/BLE/src/BLEService.cpp b/libraries/BLE/src/BLEService.cpp index b979511d..efa0f0b2 100644 --- a/libraries/BLE/src/BLEService.cpp +++ b/libraries/BLE/src/BLEService.cpp @@ -18,10 +18,10 @@ */ #include "BLEService.h" -#include "BLEProfileManager.h" -#include "BLECharacteristicImp.h" +#include "./internal/BLEProfileManager.h" +#include "./internal/BLECharacteristicImp.h" -#include "BLEUtils.h" +#include "./internal/BLEUtils.h" BLEService::BLEService():_bledevice(),_service_imp(NULL) { diff --git a/libraries/BLE/src/BLECallbacks.cpp b/libraries/BLE/src/internal/BLECallbacks.cpp similarity index 100% rename from libraries/BLE/src/BLECallbacks.cpp rename to libraries/BLE/src/internal/BLECallbacks.cpp diff --git a/libraries/BLE/src/BLECallbacks.h b/libraries/BLE/src/internal/BLECallbacks.h similarity index 100% rename from libraries/BLE/src/BLECallbacks.h rename to libraries/BLE/src/internal/BLECallbacks.h diff --git a/libraries/BLE/src/BLECharacteristicImp.cpp b/libraries/BLE/src/internal/BLECharacteristicImp.cpp similarity index 100% rename from libraries/BLE/src/BLECharacteristicImp.cpp rename to libraries/BLE/src/internal/BLECharacteristicImp.cpp diff --git a/libraries/BLE/src/BLECharacteristicImp.h b/libraries/BLE/src/internal/BLECharacteristicImp.h similarity index 100% rename from libraries/BLE/src/BLECharacteristicImp.h rename to libraries/BLE/src/internal/BLECharacteristicImp.h diff --git a/libraries/BLE/src/BLEDescriptorImp.cpp b/libraries/BLE/src/internal/BLEDescriptorImp.cpp similarity index 100% rename from libraries/BLE/src/BLEDescriptorImp.cpp rename to libraries/BLE/src/internal/BLEDescriptorImp.cpp diff --git a/libraries/BLE/src/BLEDescriptorImp.h b/libraries/BLE/src/internal/BLEDescriptorImp.h similarity index 100% rename from libraries/BLE/src/BLEDescriptorImp.h rename to libraries/BLE/src/internal/BLEDescriptorImp.h diff --git a/libraries/BLE/src/BLEDeviceManager.cpp b/libraries/BLE/src/internal/BLEDeviceManager.cpp similarity index 100% rename from libraries/BLE/src/BLEDeviceManager.cpp rename to libraries/BLE/src/internal/BLEDeviceManager.cpp diff --git a/libraries/BLE/src/BLEDeviceManager.h b/libraries/BLE/src/internal/BLEDeviceManager.h similarity index 100% rename from libraries/BLE/src/BLEDeviceManager.h rename to libraries/BLE/src/internal/BLEDeviceManager.h diff --git a/libraries/BLE/src/BLEProfileManager.cpp b/libraries/BLE/src/internal/BLEProfileManager.cpp similarity index 100% rename from libraries/BLE/src/BLEProfileManager.cpp rename to libraries/BLE/src/internal/BLEProfileManager.cpp diff --git a/libraries/BLE/src/BLEProfileManager.h b/libraries/BLE/src/internal/BLEProfileManager.h similarity index 100% rename from libraries/BLE/src/BLEProfileManager.h rename to libraries/BLE/src/internal/BLEProfileManager.h diff --git a/libraries/BLE/src/BLEServiceImp.cpp b/libraries/BLE/src/internal/BLEServiceImp.cpp similarity index 100% rename from libraries/BLE/src/BLEServiceImp.cpp rename to libraries/BLE/src/internal/BLEServiceImp.cpp diff --git a/libraries/BLE/src/BLEServiceImp.h b/libraries/BLE/src/internal/BLEServiceImp.h similarity index 100% rename from libraries/BLE/src/BLEServiceImp.h rename to libraries/BLE/src/internal/BLEServiceImp.h diff --git a/libraries/BLE/src/BLEUtils.cpp b/libraries/BLE/src/internal/BLEUtils.cpp similarity index 100% rename from libraries/BLE/src/BLEUtils.cpp rename to libraries/BLE/src/internal/BLEUtils.cpp diff --git a/libraries/BLE/src/BLEUtils.h b/libraries/BLE/src/internal/BLEUtils.h similarity index 100% rename from libraries/BLE/src/BLEUtils.h rename to libraries/BLE/src/internal/BLEUtils.h diff --git a/libraries/BLE/src/LinkList.h b/libraries/BLE/src/internal/LinkList.h similarity index 100% rename from libraries/BLE/src/LinkList.h rename to libraries/BLE/src/internal/LinkList.h From 31d1838c44d3bedb4c9e8de34c7b6e38e56f4dff Mon Sep 17 00:00:00 2001 From: lianggao Date: Mon, 7 Nov 2016 17:48:57 +0800 Subject: [PATCH 13/22] Fix Jira 747 BLE Central Mode peripheral.discoverAtrtributes() Hangs Change -Os to -O0. --- platform.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platform.txt b/platform.txt index f222a263..42991e80 100644 --- a/platform.txt +++ b/platform.txt @@ -14,12 +14,12 @@ compiler.prefix=arc-elf32 compiler.path={runtime.tools.arc-elf32.path}/bin/ compiler.c.cmd=arc-elf32-gcc -compiler.c.flags=-c -std=gnu11 -mcpu=quarkse_em -mlittle-endian -g -Os -Wall -fno-reorder-functions -fno-asynchronous-unwind-tables -fno-omit-frame-pointer -fno-defer-pop -Wno-unused-but-set-variable -Wno-main -ffreestanding -fno-stack-protector -mno-sdata -ffunction-sections -fdata-sections -fsigned-char -MMD -D__ARDUINO_ARC__ -DCONFIG_BLUETOOTH_PERIPHERAL -DCONFIG_BLUETOOTH_CENTRAL -DCONFIG_BLUETOOTH_GATT_CLIENT +compiler.c.flags=-c -std=gnu11 -mcpu=quarkse_em -mlittle-endian -g -O0 -Wall -fno-reorder-functions -fno-asynchronous-unwind-tables -fno-omit-frame-pointer -fno-defer-pop -Wno-unused-but-set-variable -Wno-main -ffreestanding -fno-stack-protector -mno-sdata -ffunction-sections -fdata-sections -fsigned-char -MMD -D__ARDUINO_ARC__ -DCONFIG_BLUETOOTH_PERIPHERAL -DCONFIG_BLUETOOTH_CENTRAL -DCONFIG_BLUETOOTH_GATT_CLIENT compiler.c.elf.cmd=arc-elf32-gcc compiler.c.elf.flags=-nostartfiles -nodefaultlibs -nostdlib -static -Wl,-X -Wl,-N -Wl,-mcpu=quarkse_em -Wl,-marcelf -Wl,--gc-sections compiler.S.flags=-c -g -x assembler-with-cpp compiler.cpp.cmd=arc-elf32-g++ -compiler.cpp.flags=-c -mcpu=quarkse_em -mlittle-endian -g -Os -Wall -fno-reorder-functions -fno-asynchronous-unwind-tables -fno-omit-frame-pointer -fno-defer-pop -Wno-unused-but-set-variable -Wno-main -ffreestanding -fno-stack-protector -mno-sdata -ffunction-sections -fdata-sections -fsigned-char -MMD -fno-rtti -fno-exceptions -D__ARDUINO_ARC__ -std=c++11 -DCONFIG_BLUETOOTH_PERIPHERAL -DCONFIG_BLUETOOTH_CENTRAL -DCONFIG_BLUETOOTH_GATT_CLIENT +compiler.cpp.flags=-c -mcpu=quarkse_em -mlittle-endian -g -O0 -Wall -fno-reorder-functions -fno-asynchronous-unwind-tables -fno-omit-frame-pointer -fno-defer-pop -Wno-unused-but-set-variable -Wno-main -ffreestanding -fno-stack-protector -mno-sdata -ffunction-sections -fdata-sections -fsigned-char -MMD -fno-rtti -fno-exceptions -D__ARDUINO_ARC__ -std=c++11 -DCONFIG_BLUETOOTH_PERIPHERAL -DCONFIG_BLUETOOTH_CENTRAL -DCONFIG_BLUETOOTH_GATT_CLIENT compiler.ar.cmd=arc-elf32-ar compiler.ar.flags=rcs compiler.objcopy.cmd=arc-elf32-objcopy From c8c36a35642fce9d4ad178f3d54e369bf50b5358 Mon Sep 17 00:00:00 2001 From: lianggao Date: Tue, 8 Nov 2016 09:13:36 +0800 Subject: [PATCH 14/22] Fix Jira 738 BLE Verify that BT Address in OTP is the same as advertised by device and on sticker --- .../BLE/src/internal/BLEDeviceManager.cpp | 2 +- .../drivers/bluetooth/bluetooth.h | 1 + .../framework/src/services/ble/gap.c | 11 +++++++++++ variants/arduino_101/libarc32drv_arduino101.a | Bin 782628 -> 788268 bytes 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/libraries/BLE/src/internal/BLEDeviceManager.cpp b/libraries/BLE/src/internal/BLEDeviceManager.cpp index 47f450ac..29e644de 100644 --- a/libraries/BLE/src/internal/BLEDeviceManager.cpp +++ b/libraries/BLE/src/internal/BLEDeviceManager.cpp @@ -95,7 +95,7 @@ bool BLEDeviceManager::begin(BLEDevice *device) { _local_ble = device; _local_ble->setAddress(_local_bda); - + bt_le_set_mac_address(_local_bda); // Set device name setDeviceName(); _state = BLE_PERIPH_STATE_READY; diff --git a/system/libarc32_arduino101/drivers/bluetooth/bluetooth.h b/system/libarc32_arduino101/drivers/bluetooth/bluetooth.h index e87cf2ab..faf03b2f 100644 --- a/system/libarc32_arduino101/drivers/bluetooth/bluetooth.h +++ b/system/libarc32_arduino101/drivers/bluetooth/bluetooth.h @@ -345,6 +345,7 @@ int bt_br_set_connectable(bool enable); #endif void bt_le_set_device_name(char *device_name, int len); +void bt_le_set_mac_address(bt_addr_le_t bda); #ifdef __cplusplus } diff --git a/system/libarc32_arduino101/framework/src/services/ble/gap.c b/system/libarc32_arduino101/framework/src/services/ble/gap.c index 4cf71d47..26858281 100644 --- a/system/libarc32_arduino101/framework/src/services/ble/gap.c +++ b/system/libarc32_arduino101/framework/src/services/ble/gap.c @@ -910,3 +910,14 @@ void bt_le_set_device_name(char *device_name, int len) nble_gap_service_write_req(&gap_service_params); } +void bt_le_set_mac_address(bt_addr_le_t bda) +{ + // Update the MAC addr + struct nble_set_bda_params params; + params.cb = NULL; + params.user_data = NULL; + params.bda = bda; + + nble_set_bda_req(¶ms); +} + diff --git a/variants/arduino_101/libarc32drv_arduino101.a b/variants/arduino_101/libarc32drv_arduino101.a index e0cb6b8ebca696bb969e406b16df05c24693f4f2..52ececf233255bbc5794d6e4596296fa65374a0d 100644 GIT binary patch delta 124893 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

2pbnV_2PE{x*!y#qpGPozB#A;9=Dtxbm4*0 z=W;Wv*imnHW?;X3{OP7uW<*Au$;SU#XR_n%%9&MFlzn@Pm-gp2AF7_ptZGHQ-G%y} zpU%dUW-agSR-+g@Dkq2G8;w&>H;S>MvTNB@V#^MdpX-nsGb6HkaJFiHXVtnn<1DtA zS<^jd6LN;$)o6V91qK?bf2*ddgm$d#t~tGOcFkRtU(mH;RbMqokB?Qc;oUDZt1>-u z?&)5$o5kB&$Ks6D)?dY{Ix+6Eh@>>4+(eQSQhsMgcjf9?31um9YJuwddI#n*EBbz` zP1xYb@*S+U1>>`-+b%28Qp-9tsBWXGvNkn@Yt@`Sj@te@2ph+E4(&L8_iU1hDjjdR+mXZl@*Hx6pL59-EpsKi zJ$0i{1plL{`LE1>SQFx(-r)EC?tn-04!`$M{w%vaT_(aKd`#h3EUCB^N%^zv>BeMj zlLb$AvdbU9>*@0(0{A`pN&r62c>3|c4lfvHdHU~yircElDj-G@uKBkDhgJnQBMNp` zNQ*%8zotw+!*`qi)6D<7%>O&h|2xe8+s*%}=KpdZ7U7r}TkkV5s;sQv$LOym8;Plw zz5i=3a{_Zz4sYG2a4MH5+@9<|fvUr=VE+kJ9a*dO!dQMwb>cxIf8frnjC4PDA!quz zYfo^NpG_5K``J`+Q$P2lPBTCEu7lpRxu5&6+FGOD=pV(?76H#nnvat%E4n5QBF#Tj zy)Fd>)-sh$_pO$yUf?P`*J>#BiLtqX3Us`g(DD z^UJrHCJvD=t|21k#%B=q%@;VT-d;r2n+epQ!fQB)$0eU()#PD3&LBoWP(Ofp)sH=) zU!i5Z>Z8(;84CKr_p%8|nF%o21n5hZ#2(aPeBF_^qykI5#457y^CiHs9)@NXJp2)w zz|Z;vLqk$Yq~aCq$vQ1rRrkFC{p2wdN-gy>qvDppe|r#c$0)*gn_#-hYe6nIaX8xld{km1qlAB)DH92em zQMccV+Cg;7??nS3DiM)SD!B+U$$1@?LXGBsj)kb@k3+XE57lV41J%d!t4yS<7bVMS z8>ft&RBWk6A~!u9mlk;g>R2-&{w<`RY^L69Rv$OyD82 z@$~N#q39J6U6a}UBDyBC$3@h{pMSQ4WJDjY{$hLU$E!gqBVG;kgypoa2-$${Dm`BH z4_py{Y$}=l%iuv%n!W)obIC;UDrUafH(248x3z9tSH=3$g8gG!I9Dog3L2rmMl)hB z&8;#2ss4(p+){Bd4N!75nfmAipKI!aWjJoi@K19prG1wD`j1An@6`)0fr)HJ8EPR~ zl$J&A$A7;ok-28y`$t=>;AZbtZJZS zCeAMdaq8nKs&d$uP)lcp%vE(Bu7_@bHw%AZqZTPx@+g5xOqpxIQUj|&;#ctg9f6xL z^`KXHLu186uHdl|x{7&q^rMiq>Hqg4D9i8fzF{5?#?zk=$u2k2$79P&fj{OojBp{9 zXhlSl*uz8D_`7eWDmyn5i3qz=$S%nr^V2vIpPT5zYs9Y(gBN$X-t2As0-rzDo>U7# zRrw>hN=P~qQZEbS*Bneub~pa;e&JszWQTLDa1e#;a;_Ejr;vS)2z@xT+p#|5`z(H32~+$?`sf{}tRD@ZZaG=MQ!xTagJCxe7|=1o_XB-@h+ggg;u`%5Y)3 zdqvLEQAa}R>W`;~RMp|ly+L|t0~MoB#ki~Lg1cNb^ zib{#?7EO%wSl!@6Ddj(|l!)WBQ2liSb!&rAH4u*qnCgsI8PQRyQiZ{xH<}2ld#sFT z8~5ZI^JXfAzw-t7~b)3~w!+ z9v@Vt>|kS6taN%;JI%@28@l9zD@0HK(3PYgPs0;ff-Xx_DXOdfEKN-a+K$WAwub#b zpGYu^FLcQuZzX+ehMJjsk7eV8<)p%~SMpzB&)KuKu5z`S=_*`d{0dUNxr7QMDva(u ztLW9OYkG8ZMRs>>_NA_TEd5?xKT~D9$GXC2>S38`Nwt`BuF&p9uF~$Vh}0g9(tFGv zmF5~Z({`7IHc{cKY*LmAQ~LF6)!34+ z(5B8U4vEp@cG?j-V!1b6ztT?iX_9PpH!=C`)k=8Y_=_CViNrqg564q<;~d1gS}$r7 zT1zjyBN!#g!IS+5dI987m;a#to4!HWwW1=?c?34bny5WXLTdRq8W`9p9xTT+jw+$& z;Cd@=w1g^IUTf|j^q0#q&5lZV{8{w}|7YZQzVQ#UcKqHy(=0@TpV^oJ4%c&I%~rx= zAJ=CDHu^{DUvgCwm8m6IPSgL+R>^bM6e;@=Ii!CpPRqw@I74b@-q)tMeP zn&jk0aC!2h67UaOD)y*EgI05LlAm+P$$stwr=ql&L6jTo(!YKts;*yW1m!7y_T2$D zknr8~sOR^1<{J5Hl-KvmPhKPcj`9Y6xrKVGZXTG>`-7tz`sFn#mx6H(OcYCnjq?&w zjm?xED*qY}wunH0vDffaG7_L2&y_!c1lEcU^UcYNK-k^XVU=O~@EZBo*YI}=_sT$k zFkG2|($oM)njs_qGd6X+h^P|wfPMrxDjol@vETuYyM`04VP-H8mJfU=J)c4SO)ruc z05&EPi0D5_fK7n4!uDViU?6O5u?K3XGq^f)j*S<8RF*+Ala(FF(J;u!JUxtq%*+HD zBhVzm#IUZdi>mCCGi;)~rkPb|5NyM-N3o#TMq`g^W)OEaf9yPB^#yXkTt#t`7#mE8 zt8#K+#1yx3%UvClDiHe2>Ixii48DqZaCtMIx83-c05_4$GlH!yb_V_xZWH17V2t4k z4@acA!uNu^3P-y!<}JJkM&nI>_F4C%g7U;;HZn0rm=h`Eg)hOqTR0Q?vxV8QIl_$m zQQ>OP*&tkw!t4`%4SYbjH8OOBO^2m<2~S5KiN|3?dR+K@AA!7^FeeRr3zsA8K;czjF6Hwv;Y}!IzIe1iVq7>#c_nbMFf%YgcqUHG z?ZRE4;}cFqq-K>c!g7|*%qM`mL4R=|EaYa93*=LSQGVWRnAjtN}u|1;O~WbPJa{b0oQ|4Q>O{Sh6*P@UP<^FxLiBou8#A627zmf2M4Lx`Wg69 zWP5gIk1`qb5cP=OD33!YdH}3}N=g3xuyA&O>H0770Y+-~;i9 zg!_pw-!k}I_!1I3BfJp&t#B%;^pbEa_-A3h=b&)R0*pf@f`k*1Rz+doB|wrKtVCk< zgxkPn#00WRc!X1&YJK-J|ZVmmj!iy0n z8!FQcv2p$z$-(2O(hkDxoq7x7k;dY@d|+m5xX3xKH3>_gx#ICxf+3c(vh=>mAWFb%|g!XY?K?~!pA;r6A-?*acPyc-;hgqTtwl!U^DT#KCy#f)j+BB7G}O4qAgd2=@XHBqOH}AQLx}aT#)(NJb_)A>kRqBfxV+ zKLtAM9|BXbOUMXIi-e15$js=gU>^^d(*xp>06rny5gZEtKtz?v2uVAsHW_;#a0Xdy zqkd#$`Ujk*A!J;7+{TEUwaaxcEC9NGpEXAg-bM-T6QyGa{IJN`mvj9NBTYd>evnbhcw~{d)#O*_o ze}fkLJvq`Z_aYoK>Osv_Cd(8ipMlu zGA{|ALIUp#pMlO-!X?NkH(uZCkucIQ%*;W>Ff+t;2h@2JTvhlym<^utVD|rsa==HI zI>Mal%o4r_E?YbUJPx;u@P}}F2-8Mn1EbD&aQW0o{yW@4Vd@tNQ=e_k&DjlyL%g3MUS5Gj!O-I8sA>@@cS-2Y2A$Tk#+pvl?*9l;Z>;VHt5$1Hb5SZ?!;9QRxXUL;E7CD#OAc?~}y%!trOS?(%2VsYqA zy7pkz$X6QzcFVk0d1135kZD#W2*WW$QIvB8vmWJ)30ouV50^O&R9jDB%KDJmPhuN@oyca}S4B_wcFdew}|fG&y`Be8-4ym~OLO zRref&Z&wq3+;-**8fwp-_)OR3!$PAA*$FaGVB^`n zC}*CsfZuokLRmQ!Vrzt)^38JA1MDc{7Y3J|XCMp?t;@KmzHoLv99?C}AACN+gwkK> zqZjTEj%<7ySI0!8zzEs%GBHND<`2OhL__bxmkiphg3q;Cx&;f@hav=HU_%PT!0r}* zSGUg)!%!2$i+_k=D`L0+J;qQIdmx5){t!bul=yXKJ^qb9#IPPuE~fDybb=m_rGlC4 z4Y=7T)$%}N$1$_S{d0*tptmBK@99)27cn%b+6HzgudUncY02sd+bUN7=jr^;4~& zXOOUMRs7Rt3h=}q#Mb053N4Tq2kE)p1ixB;lqC_N! ztxUY{)EV%uQwr~QMHJ$}Sf8lkqO(ql{E{wyRDG*v>mkcj3|0zHTBiEPE=Ir_*aP=S zu6$#;F6tA|vav?=r)4TNc01x&NH6yD*j8Ru%U!Nw!bjk6n}~{a;c}H1%a+juISJfM z+mlVipD@HOXb%-pu~v^ED?DXyQY3f$53hzOruTVxw}SN$FapfLjEV5*W2%eVpc7X} zJ-tn>M2s~e4fD1Gv^wg(@J6YNR$y7q9=*+f^xg^t{fmxYsbb<-#UBOvgK|LqI2l1p zEZu9Rs*13ME0GVH(>J0T0xQi&kxf1htyEoNHzS*Wqlnfmwok8mXRJd0xT4Vv8Q@mK zEg0ReKXm>ol@?nY>9(VYCJwe!k!CCllZ zN0xK>Bw0?}XXL8>1Z9d4g2J(KP&S^6+sy=oU?Y_vmlR1@|19jdpQu6NS8Uq|hPvsCB9(F-?*xb=%W zRZm=sY8T{NbzeIB^lCVI`fg@xm+Ghwl6t<5=+5dzuS4UqJ^{zBm#s5*BiXEMJ(F%u zwm!UDO}2Yx>+XBt*2vYn_TYVqrVCDNCsajyJ^lu74L5E*BX!jGcC?f5RAg{H z7{%jI&z*JOa+uspGtk2Ip}nrkStSK|!;AA%OVSd|mXX!$U%tzq*(_aND{Y-$)^?xT zTQ}$>#A{n6f11JK)p@<`%Kw?w!lgu&%D(?rZF5y9noycIVv;W3WOpmey{NWgm6kU+ zrfk#?Y!iRV=f56)$QA6B3N0wxd|BVy3YG2?6&&eI|2XWWD{5+J9Otf# zEtBLw)x0#_;LR2E;vhT8$FVPC%Rt8U$xX$-0CHo?K%N9{FY*)URC^15?UBb|=y0qg zU*yl(|9ByFIpUlk@;u1z5MBotT_&Et5x6xxmWaoExXXlhK)zadJosth5@dpg6vHaC zvrWP)ptD7|D>AoTcnmVPM>rHZZwv2Xhxe`=BqGw&!pjhVw_+whtN)_#ER^OaVHdk* z_)#YrTtS#M945?OzfypaQI2X*zYub+O>z5hkcphu7LV_t)Ij)N$a#7gfOpiU!X1#9 znZAa6Gcs)qFtA3X14QR5@KE8!HXe?JcR+4tprJn)@%R>r2j|Kk6TSkab;6vM+A6#b ziR}|!44osw`N-UH;kzJbjWN?}kicc(bCA1{5y~ku!(d6M|Gqe=As+jYiF(3wAQy7MU|KkmibCtnxk<-qdDNOef;qHiMjqqU>hQrttQnZWDd{ldRM|EO>qL`tKZI*&k}iA<6Cj%MKf+Y=~EPm$+=ZxntLY0;kbF~DjB z<`4im0VS9%JQ@i+76^by*9j*fFlPa&|0v`i3SU5GE(qU=IQjZ}z?kvinEH&fBAL(m zXtNE&>?*jW<%ZcYO6wcL>@>Sike(lAr`nf2`js#{Np;bmgxSezjaK1sJi1P}ofgiQ zn{Pl|j8p6UaC?*QJhD{}*$E^wSeV%zF3c7-PPiWUE@6(a&lMgB{fC5eAzvlTk=XUZ zAHaQCn6r2|u4xkH>)$8oA$C%^D2*=L7-4r%vFtnf)w6)@G=rRkdePyLsDj11PNY36 zb{0Z4#}h)ps_H^ET05Z?dj&Ze9uT1t_?8vDLv=a5S;wm=LLEO0UL)R%bz+pAm(dcD z*Q3QNrdcyGBZq2xGJ?&6P8X3c)*GYjR_d@m7iA~g=iRzeC7hkV*}8ot`&K*Js~@jq z&x{*^5XPG21QoUkure9z=SsJYwySnqh?-nTnfNaee_E%{2scI?ZwWi#^TK>EqovCD zO3NOLwwFcv?xGB5keeCUhd#IOC}+oj&5Wa?6x=eAPT)P;PUMPy`Dillkl5Ce<-~Co zGjNi)?kaF9)|0FF-&rs>pFlO%XKNP=w+mS;+~Tqmwe4@+u;>oex2NEJ)@}{#-nO@m zezbwz%dXr8tIFXfw9zda!tK*W&xZ@^^Jqh;;bp}JjUcYqR^LH4r>%Yqu3E2S8{56r z%esipF}($jU9FuCNrmS^ePb$8TB{GyIi0q-Hudu=@#j> z|8?39UOgh+PO`sf!m%*D3f`W2Fs7IKt;&5kw$q2`kJ9bBu!vQLouYQ=`g9iRz8Q9d zfBnU$%*;%DvxXlZsn)A(Z-$)}AH)}rZL9771uHSN?9<-1%UzZ{z@Gl+7-;&NS)n2N zxq)`?KhMw%hM_rfTv17Cf%*KO{$h|_rgz1As1evC;7*$}WX3Bfd)I%4N@HfOGS2%lCmO9J_Puo>P0 ze+-@BqT|I?TO_;)aZVQIS2v~!7ePMLn1=|w6M>C+2~mpG ze0(k39sSWI;pxakMfkC{*u7K{&PSZpg060MZA8jj5|#LE zTx0ryH-K*z`7cN;U-$>OqlCXi!gmTc2G0=AflFhHan3`AmI+q^cUmn6Txzpccn>@_ z3HJw^-Z>F=24rSz0$dFF2cq+&K2&JO_-;d?eiqTA$fFl|W|~hyZa&un=76Q?w84yy zw_NIY5S^Jo12byAwM+SAFuplp_(_!MW??qFk-|(D^O`pqB_>RJn1Oi)weVCh7dKL# zjYwIcEEI&CS>Zd+ z)Zs0;i*PkChlVKUqd+HxRWmBX2(Tq0Ka2veG5MGFL5@SeSqW&S-yDhif09R^A89AWoLQn3sOC)&v zAl+b;-BQicg`;3K%+jkz*?92Qi;D2sLU=w?1e0!r-d_Y;Zk8_hJ5@#-)xLgb!DxF* z+rjPN;`q6z)ATGJsGI z(EoABexpo`hX|TW{tUiG|N02ui86#A1>L^lzr)h!1MyE5e|Cd+ia+A7ITqnxS6JOh zS+qGl)+^$VqV*bUDtO*ly9-Nt*nb#af6ndgF0#v_3w=PPel%bfuXvp$2E@VQ^vgY@CTubG4cU=ONvf&MLi{ z&U#%==VhIH51ie45uJnj#69*(+sf24XWNtQ0d4h9vk}i+-E9txudZG6qjT&As;}R& z`fv_RrJQd1>p6B^&(n!`PSuHpq0zeDy^#M_&zM(zz@Cov(GB>`tEo7~0Q|gr?G(=~ zJ@A6E`D6*chjJ7Wby};r0Sl`|`ZeF)_p|u|e`fRatSmEccvsn^x%P*3gIb|w+18N% z8wOU!2KxqA*^W*2)C&4|cyLwy_$&6W*WDK4SeRb*s{O)skJ-#328Q#BGAE=Kl=a#Q zQy5lN&9a5t?B?Nh-a?~gTjtEb)9A8{K?S}aR~D~DcQT%_^@61;#>WwhG*RV!*4SB4 zU4fpjjmUq6yrVED%en~<#RWM?xI6T37Uqp{l<-JorC7KS$(x}!#9s;W8OFB4K`bKW zz!CGy?<77Z%psO1gqtDoI^n5E>;>Vq;LXB0a9F5S|ZrzwinVY%5xk zOn9J2K5q>E5&W6R&m-^`!dxkQR`?NA?B9iHf4Q(To*{6JodxDP`syO*AXYu$Gl+*K zkJ|^OFFbxbgMmvS94t!15coFX1xRd)@JS>(OPIrU4+_5mxv`rN4?i6Cw8#tK8dC}K z$*56dDuHPl`HZcEgT}}a1(uEU{zTeKUqu&WM+-e`WBmnz^f!y^+#U=BJyDFI$XM8+6`{3euatH>{d z_X%r+JtE8k7{dtq%;;H>vjFDK068=2apC+kM==P@7D+w^t|?5zy@7C5MAB4vFG_Hu zFiTS?%r&y(g?A&KX~H;r*4x6o0zMEP!B0SZE(gmH_y^&)5!l#8$WSRlM<6529Je@O z&WhC){up7+&>M7UyKxeo`kzDIUHCT?V307EGY$j$sKhZ?^DYE<5dz#HN-uiEx`LeJ zTAabA{t>ty#1ILMb`7 zPOc5Ns&EhzND}624XMIhmflkM6(rDEnB$0iu3|jDpjK`b?tnsa#ME5}=f5*NIPgrR zsz`XIaBW1&#ru@=Rfb1}%MqB53zT!*=&!7nb z4qiveW8le1=ja6oF$x!{*BpeEl&jyQ6RFQZ82daTn@v4rg4;t9CQ`>Ag4RKudkEI! zWlNVHf;}3kmr%M%Z=n;ZKY?d#N2L28%FLwL43-J6(RB`Eq-LIe?666F^I^pFnoECq z*iMT53Td&!3z$V_O+O-t)QRsxdx~!HE~1`~()>!TMD%JXkqfzGGOP8BckSf3NTk$? zB6!~g)g1Az6Rcl**G`JyfMc=9#s=y0@7kl)Mt$QESd{biydx+=u72)_9TUNb#?tCV z+{bKv_=w%o{@SA>-h&-IU$=P=Ia;j;z9&g`7n}87gz8O3C?vV~JxTH|ks--l@7bg6 zBbH8m-^4WJeWdxEp7Fk7_=?WhiQ{89wkzlAyLTdmMi=0jkc+-pAEM`7JZ_HTD>`u(j^E*N7Meqt z9?#<%#2)ay9jT5b=;^FaJ}=6Rs_J9~;}H+)CwzPsUjINPNLL-My$t%h%z^9kEz5qX|q<6-9t9$XAWfL=+=1nX+ z<8@W%V?rg)%$FsExEi_RxL(IRDpd9l?3hOd@C2ntM7WZCd|)=u1dwr{$UGB(i*cQ@ zx8udk4y1>0GjJc_a=1f;`Tp%NVGi2NMMcd zlW=zm?}ocy7~P!zmr4+JC?fhoSt47{T5Y z{s21f3BL?JA~R2q(b3QMf81EfgLA{i(w1p+8T!J9JhF^Awo%Ur2Yqr7J|al3+d5k8D&ZY zif01~-O;QP*V7YR_#u_r`q4~pUC)l%u7OpLJM3+9@dgpcPHK0?! zegpdq(C@c))w}K;_|;xEj5Aw#MI-VH3NgP`vyJPGibgW7q;%qhJp6{_1l_i+tFE5d z*0o7bc-VW|+0Z^j=Ui4nIxWX_Nhe%XiDk8OUG4vbJ3;v|SG*qC-gW68Q*2W^xUz!c zI1gdE`9yzrZn}B!7LVTB9o_slXv3zPhpZ*obn{?7!ga(YOOyRKu<7Q(Tolt!A=(4l@ESfw+}Fc1!&#KHaIu^9o(kPzh_7$73&1M+6! zRnRfRJi$H33zfRFFyegP$psC*;x zJ9q1Ix9S~_Ho6i z{(4>?+*TV@(9idARaI~4J$?MjKlH&}6oKOUx+c?I+}EGh0l2X#r00`GfcC=So!-^^ATdjYs++jgRpoM39=Ch+DHiv^wY;`nzJ{`lC)S;~|(cSbI_I zU&x3P!S3$wsv1j!ECqMyz~oI+vPttf1dsh#$(RCyb5QJd@s84;1Y%!75xhsj`w8(z z>=6SHdk>tFQlyLS(`^PIbJG!fRSrb3KgHINjKq_b9*f}Nob2bPJ1M)ZY|#MM zeO}){gbA1$v&r(fy_AgTxjjo3^Mcdofs@yo4J_QR^F3KAnqyUg$8a-M3!{nKos_fR z$M!I}lBLt`v^$rb7~*=@6+FEjEF^fP>BM4BEqyHCRZl&sJ;PvkY|sPgY}ISv1n;}5 zm_ODNts@IuS+Gnx6}a%Dn)UU=1+HFpPJR6)-JbPz)8TLj*Vl94s^R**;jn7P=^F}N zD>1cot`JAdb>RqO<7|dw*J!0HjC3_n&+3ekXb`nq>p>%3bv<3Gqd&nkF+{bJ%v{yh zk+6S;>-{5LDdB7J2qIAqduKTVo9WUGQ zkSj7Ijz3vrESLIdtGF7C0gZAJCD_m9`g|orNa)JEs z&^ML~_&Mks%LV)qV25Au=1pTaek3v_rVGnNb38yv7)aBv6*#&Q93sf@8)!2E{F zGZGOW;Wh|!g5o7%j+$=`$dS=q!tKEOgn5x35M}|72;WD`S{d3TZX6DNIy_d(8e0@UX>o6Ivb_$1`xME)^O`($ASo+i8#nfB2@VBi~Z z@UZY^nRp#+hD5>dg40F*7IJO+Zpc}M-9$bL?k&Q@k-1^QrC|Co&YO`~iSXiJ*mJ^* zke|iIa)DAuRNZ6ZaTH4Hgcl;$tPMug4heG=8~Jwdeqny_<*0Btjv=*;3sgZ;5;)@PR$ro2h544#v=nP@j;sUUb35O#A&H<7ChN@d5%tYCdQ{E2=&~ha|iOjtzd@D-!p73V~dqQ|R z_?+-6Wb{YjPm!<(;TaDuXP*@%2VbCM@xqTFQnM5nN-rS*zth72XM@p+2scND1_b2b zJYgU7`92i&HzUrwgt_)_u5c{kf5+cQXN2!VoR@^TtY2ZL&c|>ggrRQ5_LYNaIA|dJG;+$zg#q|0fm}&Qrku0v z7LmGSZr6*zYj@Pkk;M}L%&`H+$aO}H+^_(@(>8#iBFeOAE zz+uL-h~iNs5lD*9Bjgc~n@@joRaTE?xxc%KJ*VEL5!<`v^Y{u3xiM%yA+b;yuodD~hHDZ0>K z1ZLLw&PgZn?hAPz3C3C2fgs(PNXp42O{&n~nu!=~R{`{6)968Qkf%QfHwBESR71M1SqUlHxlIww_ zyNNf8GeG2HAiq)M%$|Au0Qw8SMI!I6qn^Q2QV4Rkogx^K+zyC0PS-Oq+*uiCC_?B8 z$k=)D4)yB0pMl-a*n?q!)1R}(W?dZ$(Oo}>*fV&m98T{VCUR>68QwfK{NOx;aV_mE zk$2bXS)9=eIPcrUo2y25iT65`eZR<8LvDTm4be@}`Oo5v9!F*(F~I1qg3o=?opRh~)M(+3`{B9L0(Icfn}F0@VL z7-Qqfs*b>PAWm`M+Q=hI!!m39QYl_`P)gRQ86l+&5euql5b3%-DEo=>Nn>aM8!EVX7?r6Bj=5{%5^G8|s-bt_Xv^ zJwA2aS62Ts*BjUWWnD8*c;SR=guFwWJ~FIq;^(d$cO30R^B{!lf~FkrX1qqiJ^H{` zco5nO86SozTM4(R@GH2w%|j97M>lqB z=JU=B8bVBX2!u<-!$BKcCcGNFTKEO<)55gKo)=C)CUy#UMx<{96+gv}5x zcrWB9MaRXJe5d6g2TJFK`SV2=h5Lbj622L%P&#J19ByUdbd)Skm=8w@!spTR)fK)A zndaa&!%l}zCfSDre!sQ3cr=1XTVXCs?>@C7`kZ^%; zKH{Ny$b_3gXTC6(mie?C@Hx};$x!+$0+=COFs<(GqVp~IZDCFqejq#uk$x`Bbxmg3 z2f{Xm%=Fb@E+-4Z>0u@~gd6Q=pT)UJKG@QO-+?vnO@Mj6%zG2y+aWjaO@QY^Zr+su z-wpXl2}_f4g0PK*eZmu=e_ucj9rM}*!oCFg6Xrn(2a}MA7euKX9_}$`FM@oY@JjGPVLnza6XwH@d6q#ud^+cF@Quvfae(=RGWbp;#@Q0e zsbnJ~55YT}0pAnRbNUhj#`5v$CyHReaN~rHKMNV> z5aDo?$SnUu7G@x~DdK&*&Zl=vgv_A`-Y4MQNxTp1Mdw^yR3&}%oU2vrK1ff~2Zj!} zzl&CX-S|B6{(xwx=+{t1xLsA?Z24e)N&rJ(z(CFIjtA{iYem(X3F^B zudYbF@E+T(PyOnu?b(96#&V9S;#W+6!(i$_z34!2jP45dEQK+<&;PMHdO__s%oSJ8 z#Iv=TJRYhKQ7|1==uv(_ZAqm2-^?9nW@k1t!>U+~w0W%Co8(ofJllHE)`{PHVz2kl zz2n!=y>&~+6Dumc&!1zcup0E@RCkP%d}EX@&2m?Do{y`f zk2P?QDjU<#eatRfo9e#z`kOA=jO1o#xTm_@lO`1n*Xy&~yZM{+Wz)0W`$FTm%-9$( zSf=m>E+}KbfCq)@VY%*GGdTBBh)aWOH+tdH7%f}{k0VorIevMU@L*k@>rS@&dUVzH z?j+w|@HDhVymsQbV{ae`qIzGL4_qgO`9qnPg!%hzKMR*dh-H9mtOj#!KeNPPH4cxF z`F-SQVcr?=0z(DkF-C7|?@m_F=o4_d<)M!;w`BzR70H<~N$|VK-jfpi0_2;8PlL@Y zD|9*|+Pxw#)Hif+r>Vc`X&n%Gp9r?PBfL*g@e(A4hlbk?0zC# z4{?S2Nwtq}*q3`G9A374Sg zgM=@k48w&_>H3|~++w}DS7&$CaL#=17O%hR@txf<85}Y^Dv|EAw0lM9XS%qP)GvBJ4(qTT z6d@*Re2SDN3BLsSOyT`HxT`z4&Asq8pL&OCV`QAaiq2rYq58Hk>-hs=>YNgOKo=p3 zI*h`Mcp+F1#9-TmZ`PL(Q(bz0 zca67sKNF#OMyjBojOk~uZrk0R6kY&57M`-6y12VLCawUjhCg~})Nn$~>znXy7p7Nq zcenJ_LW=yUL;BP3FA?VV(kBTYhHGBsgic*#XNJi0pmVPg|MFOSB(8fRjl&4JiArxe<@FJYtcZ3^5hZ$r6SWh1cw?L!+ zRG8BWr-W;OzZR}op*H$0IXH@he-NIHl3fwL7_5Kl;ZCi?#r|e|3<(vZB;C=Zm}m(i z94P#~zM-ePRR&eph^Q~}x%R{|0v}wL(931G@g+dOw|BWxqh^ishJUE^N_& zHF+}`iLBRs`{KTngUWbVM7b#VYT+jk|5o7}a8;ZW-i`|Sk&I}$MPs!CqkCR&Lv$G& zO+F~1x1bUMFM4MqxYaIR^I^N8a4zfoq3$W_iayGtEL}ezPEUP%zPlmTIPz0*`sI8#*FL_P?@sZg)xk)C zzLby7^MamH6(5359|qqnojc5(f>FC7IxvIj?9f~2T+k`slCfF|D#SA`q0 zAz?c17`m1txWYz@jNe{0_9^_E<0>-tDR`hRJ_f6xIj$Zv3%br!{P{Y51NHlZh;Bi0vFHq$To0($75)_?hYmo!K0?4#J zMI($4!Ut`5@Gvf^hT?HtZ#jYc!!rHJ37E1yD(N3izH5A4>w?h3uHsir~tS*Flb`lZIUN#N_A;C*ARb znxhuSi^uyY-!x&?`aIzq5O|63Z_s&Mco9ze^VjIVa#cqseU7JKhT|#&CdvWW%fepB zd3&dv=|l>%WyA}20P_l?4)wFI(a*g`znAa?#C?LN;*VdI4vwt;XglDS#< zDac0({~dBOQ;T>Qc2OuMIvD9II6Et@iO5*Cl8Ki^&P?nP=KI6Of`v{Oggq^CF5fvX z>a`I0kuATpLHa!x*^2$$&MFWfP0>fst{A)@{0 zAleB(0C^|jS5TfF!b?zZeTCoEI}ydO5y(}6hze1KW*QhNUqJ3kM7|Iy-z`jg{T|_; zVA{ z?GvJ%9i~gaawplb(fZ-9+^KP|pl&LnK-8Ox0#_y@wbU^EDfBY(aawXHLxpkcE&huj zzfr>NN4Q%>zD8%9cIVltQF`%dJSx}In@_uw^Vgsp)lmGv`rH&UQei9^WTehj?M+3_ z+|y{Md?@6dM4pX0=q>UYkPi^~8r|ZIdzRfkT)%b(Rk2Nwxd&!J0D zhHkKhxOSP_V6rKL?sX3FuF=!ZxtFOIbnJPU?jP$#=iROBPEq>6d3V3=ENG1we}QJ9 zO(c>Lm<4Sp@)saa|Nn@45Adpru5I}2ea_xzrzb!XLMS1jmo!?WhTfZ0K}zT-O$bE< zk2FP!fKdl9G(|)O1S|(s6cH3fP!tt>L_v`UR1^>q1@yn~+3Uo}`@G-d|6O{0b0uf) zS!>OjK701;DMDvL8w&kjp!1Rq%I8I>+kVi2t@y!*(~@IVm-D^^ggyK`-Y^=+s0HUy zxFBzF$@@sok5k_WIr@T@u6zMwB1nyMo%1e0s(HA2`hx5EkW2nrq|z^HPpvQd8f7yf zsmX3aN}^6xB}T;BBa&G{Zw7jOq2p}5?H7Fm5yQA2eOJw8aVq+fFF{w&Etp?t0S8cs z&k;ins%O(nI(=g=`3{&>V^qVRd=HqV!__-K`P$$dpSa7I8e$|qFYo%^j$-+Wn7OH% zgI$K1_nWZ8@oe#8hF&sn;$>)gLhZcl`y{I>+FoV~Sao!HdYTbK3$=G3M%;eFD_mEh zC#% z2(pnFwSY$NsX$*!^BL*V=&lkYSei(V)sVq7<%rSnXj&2@Cu#U@9XczXy-H}w`xN35 zn6jX`j}GJxjlQB3Nu|+qjvVvDyhND<7whvnPz`B5BVCrA=tY4XJHYT;P-xjn42N#0 z1vDwdC}Nr{uF(xG(P=>jii(Ec$3jbb^p~|1O?C&5(&+g0kl9JIjX2H4dx$H!_(S5# zEN+#Fy^k%fuNj z_T!=A%XD#R;w%?e2M+UPyN(gTvo);8BQOKIxW2`GkH=}9Qneevx~68rGM&llPy?$A zE@Uc~18O%lme#Fm7p+k$>LytEY5=W!)Mi?X)OA|RRp*AV)~i*twyJZqUQtaN!P>7D z(>kJ#(>kGQH-`0{nn~-5Ita`3O~GD1gd3~IHUYJ)+C{6HifRfgR}G-mNo}UpS6!zy zOm%JsYl2!uYp*&->wVQE7uIKLF)gedqIF5tZjK~D5)N7ZKax}hv+3i@3yvLk%7m-j(Yzsw{qT&0vV-uO`KP?+kmlY6rMn6mSezZ z!bL&Mo;KEhnK8-AyvhB*XY2@GXk*okaJU7^^o;#SW=yK5{e>@kfAy0czX0kZ`PCXD zJX?+L?UYbo%y*pN<{s8W?>{pEQK66ZhM}6@V!ise>*w~=+7W{$jLFL%F@EA$EK-VF zhC}D#`dMp(KlZbB8vGE)eg6vED#BF!0bimz0TjG&tM!akhA-aex%C3P3+O}GA969C z&r~lCu~MsWvKWsCO=BO{NHAB$wiBG>Rlh;jTxO}%p;kkqry4NSO3Z#0J-~Vq8I6(C zvx2!9ZjWGo?%OXo(yulQwNjJbfWH$$+lF9H3w}%l)`%%im; z1m6vuAovtIt5m@c!f!pnTj4cVFqgCN#82v}1>9dS$MCla=5EL722Z+#P+JH+Dh#fj z)kkiDUKMH6Yi)qLsCC1v#13~t#|aT#4g0iUe5!KKs)uMiWG)JQ3*vi4@J9&dH^I{_ zWe&Ge%kUY=BdHjT1n|oW?rN*1!>xp+I=c-xRq8W4y2w!8U}TFf)`n8HM}%= z5^lYyhABX59vA+4L`AQu0lhb(xJT&w5%oiY3)F?-RwJHfnLffwNaFp?=}^Jg@f?}v zf>)>kBdiQ`R*OeigQ9MS0= zldNtVi$_9ivU-Vj-DDL$4v&UQ>XvcVRMS%y=W1G0(3IMbM~{C>t$=0LN>ksAw`v#{ z^a_t^6VSb{S2sXz5O9XRg z&T4eXT2VFjO~QB?j4gsW#j00;ATX{H_)O?65Wr7@>q169Dj>55FcN_ zdeHgipeN~doMe(7c#|6Nnw2=V8M+dEJPSxCA=-jn3veqW7XwUn9;o z3sKSQCxMwIx~m7~hH|}361XwwT%$#uv((Agtc{@dN8o!Q3*%6{U>DB+&J%8SrM*f^UV+FVR;7Hw;sUUPp~- z1DbwAgDl?Iiw-vAvrX@$0=*-mr*~2TH-!8U@E8sk7)=!1Ue(@XB_^#!#w-yM-$)Au zW9kQNbo`U|;O*fpwPKIe7N4029dB5l7{93Yd#$!ftXG}!&&i~_5lQwJlzJri)LzSP812=zeO97bD@?t! z&$_wt_fTGuv%e5YLSVIsWkR5l(3h*`Z(5JyweR?w@Y&c2UVhW6Z22$(`M6(Qe8(CX z&C1#gg+NJE)`17CbgcDUd;s~n9NE5-ETpIh(!55*rl?&9Fv58%cOe~QB3!j;(8AT zfPgm$qZE|x7JMA^PXyNn{d2*L+*QF|5$d^)f~_N%r$cZ;iS%hmzMfzN4nhx& zJ7c7pz7`f9%ze-5TOu%py_)56mk~n)&1PbRLZb(#2%Y8<>8LI=dexc?!iFM*P<2{H zEb~hr6JrQPbA~v^#VjbyFOATyXyj37f~giUCgf;Z5u*%f@`;gr8jg}VmZV{q9hzU# zpX+3PNw;5_Uz!%2`K1+MV;|A|XE5eFua3boop!3mSquKD4ksP^B%+O1a5s-@1-%279+gM~%KwDx0#G^``4 z`uEmb7$9Ew9&-*`)#x8U9Hn;B8m7X|qgt<2vmUccs&?nCShMT|)$hDj#p;Y1pTlb2 zc`MB<-$+k9>;@abA3kqYH6!m(zd{PL3KcJ4pt(b}rKOi4kdLNk<3DhApo}$_{%AEVcy2_z8qgMLT=TC8yC+W2^ZebLiu4KL zC$fA=YRM%l`X8APa7TUN!}7-t${#v*7$yboc*OZe9sbEGrN-Q1MXB<0F@5{>PgYyC zdbyLXt~n9o)XI;X6g9mmX5Q-mY;{TEwm&@}MB6!qtY=N;%S?B*>Srq>uNs_scM(W! zfm;c?0_={0??E%XS@6fOZxLJ<_F%zmfg=TvhWt3eyHxEf=-vmJ>h3qg(=9#&UI^ZK z#roDTYuReVuhtaamGJqmXc}el?s*ljMZ38;U5&Ylx>?t+?!Jn;$>j|X6d22Kk|xN; z{rbvhNqdOEH#cKF0-nvwQjm*h1R@h z{pZu~>(;4%lins*^7&PGGNR{TGI8z?#>(A-$=9u0{OvC6zE*bsi;uQElZ=IC6E?j5 zU2^W$E5O(A-y+SZ@Bh?vQamlv@Qq09yIT25CD%Q?`^4Sj#|;`cWX$kElP6#Vm+!MT znID%6rd#&Yrk}UnMwaLo{K9XKFnGHghL6uwLLwa>k24J828nQVu%7V6h5V%R!c-AGyv}ibuNgDbCI%s!Nz%2`{6Q!t9K%+fqM!|}DnZ`jTF3Fi+8eN|?-UCs!qF5z}(3>FH* z?RI68k=MjCkF0=oP96YCG|zC9_Ist%Lp9b z3jBtx$4@waK;UFZ4Ats+d!KW}cpHvnCiEkNYJ`*G95uK!#tr?L@hn-%Q-)9UIG-By z?{hU6&euj{SyU8P@U+pCDqLQybI$03@k4~2iwAw3NV_T)@%5+mp_&tE*EO!H9dMax zjyf4>C-zB0shJ*&8tMS*1XYj(^)j1*2sVsbB*d6W!H`R5Hy|4er6pgwBtLsDwfBAmu zY?R$Cz|)u$#|-!Q+gW_1YOXJ=#JzCnsu5O_5Y>D$ez^KV$KD+*dW_yC!!n)+y&R=L zAVzw)Y9WTZ-8icB7!}OQw2p&X9>}&JYh=%286mkc5 z`ujr4ARWU)y{I9R)T~(unTAxOzL~ukZd;ebnmWtL)13yn^|)Zd;M^{BL~Ujm4TX)u^(c~@R-%~2WAtqSE1SFLL%Ii1nbu8#^xH)GCOAC9p|gR}ja`;D zIWGv8#cqs#2tFFV?vm0x4c`@}Rz`{RPl_v>o~-)Eq3|9`R#W2a@&SH3{d44+hJo~d z9l2VJ;s)kIV`<>%?z%6Ax!d zii_=?g0NE#jbPqGNRXtB8=zb(Dz5hXiahi8t#jl+}+6ZXi`Ox-f<<(neHr+hgA}- zqPLOPjKs*g(y&SS9-LSgquwiNSB^?TZFiITd{V43O4$j4Pz5XnEyhQ8 zz)vU`UbSzY9_miEapmQh#{ zqczuKEu&(bS`%+K8qVU>F&cq@6Cv;)B2yLQm?=dn+iI1Sv`Q|D=?LU8+8V5LMz`wP z)y^PC8^$Q4ky~>XrE8C(*sNJsPHb_8t6(r#!{0rLFU5TZhT;djPam31uu`; zPl9F)@N^IL<7}|Ta`O%F8nPp&X~Zx;|LY#= zDA>A&#Ky7Q0zV4>zpcd$?x>$JEq=oiB?xMWxPe(>P@|fXx5WkyGM^$9EUq zo&E9rF3J^M>ff0QpzVLj6%^3*t* zhCHoJ13LtM)uPB(U4`l4<{IFc3#aRP#*&=_Kf5|DbpA))Tkx7ONp5h`#qn<7QMdSlyWUqaosUgFHVU>kjd6 z6mBjGtdBR;w{{@gxWzj_-BZpk)AFk#kwIQ!n)dvGatq4S(5pJWv*5&BEqW{r*I}eU z$|$Uxq>e*T1HLht3j?~`>sPl-a` z>-c_!#~8i>x>1~~qj)oX8ifVwTzR`efL+`Wyxp(ty(E{q5T!D&W!Y-bey44*yt!fh<`UINuzILb}&Lteh3n+qqju1)E+&~jSh zpvNc+N9jN&xPj~n1u{X6sbH7U&Ch+{x#QcqtUNBEC)FAC`8wwkdXW&GxEQ~DmC)~z z;X6>^;kwHACfh%=GJdtBS!D;{bV~A_ONix3e27H8S_Q)C^z`A6V;CHpyRWBxHRZa? z7-YUi27gcv`(VXz819CWN)3EaYlW`9Tj<+J_57yp=Yymles)UW$j`OS+r;No4c6Sd6W6Unkl5>mnl%kNObKA25<(#RO6QIzG+=hAVZC(Tqa+hoM8VH6_`HxDscLTPeXG zFo%%I=It_%8+Ve)A20)CvWdD8>vZm6GWi4Mqh$VyN`EVyIwM$Ji`6ro5w6dl50S+m zcsxRn4b_FkR-<6oR6Ey~VtKqy!-z&!`;um9e1xs*@kYZL(Y|D}Bocb56U(epdA-v; z&5~SB)ui?%8z&*s>4uQ3s!5HL42+@r7MG&TEk!aBA(?(q>giu3sQn@x>djy5YAW;J zM#*5es`i`VMr1HC+x8kp<6z!KUk}571es`hZ~^Gwe=&m0OyBmqzsz4ajUu$PEnE$( zXP5c!YaHAYDL$-c--7QH7OFmmtIPbxuA1qcJ9a!e#|#fvZ)hJho&51*M-Lq|exkb6 z*v?RUmSM$QS`&M%HK+wn*eJNGWRgm2YFAMYFLGl3_UUXrTi(65ofz!j)LvrPlZNLH z${QB^v60?Us7Kxj#O-RkZV{*&-@=OjOxc|B6=K6T;&QcGvC}ytRv+ zVuoI#nDZX5r?Ch3oOQD+4C<^2=;mM>?wp2DD5iV)V(w;o)ddOY6(HZNh)3Y{!3J0v{=-sm{IZTjK@|YSXJr z%bxAKb{RA%y-rq6rrOxct`z*dmwm#gb~m;Cfj{TB=f)DEPFA$IhD7dMLtG;lkAM}+ z0AcvBiEE;vo@-6caB(WEWXXUkz%i0o_W7jqVW6id<8q0xL=OLPdKBdj$3mW|D6R!1 z(D~p$MCTCiPkvicLiDtDF=ayXcx&{;N+i%X5A`JLuAI~0V6?>#429zKXvC#cCIvJO zTSGETRGc2rxI>3HJ;cOl8T3$Ke4NMSs^9u!61)>IjLu9cMV(eXqHt$iLf*0=7b9uS z0gj_im^S!lRNBF&in#9hf$4|;P_)JXhYDx`u|zTOARXdiH9bgtnXyzl0X9o0PVyj= zbScIE5oyVrbjs^7i@WMHq@QsQ>1RY^d&xu=21%aSzW9NO#6JUG1Ur`h5^#vV5i}M= zat-{z=-d*S6u6VCkt&h)yO?@H^isfa16+C)HlP5kb|eEX@x2ovoN+3eqjE$*1)`u!$6XTfrTQf3%P|R|EXzHT_`^W6OWWa z8v{qECQJtowRv4oQbU;Waij4AqYI(7i<#nBDu7{cmMnBFKS7hVeB2}#qe&J;qCUe9 z)n(n>rno}7N-?@HIvb@!VnvCYrpbJ>^MtlDFrvN&IP?y=!7iLQJ6b-G0^5igh8P}S zJ-2ODB(9Gi_D+m2J#h6^L&49&ZZ0@C%JoDUA3|c067NL0^cLI+GOUTDe+2q4!R=9x zZWnwJ^<|>qc_x;000-!SXBaIPMk@%h(ve;k^qqo#K>hzr@b94iB)BfJA=Yr^cfoJ6 z;KyKB6-@a$f*(NScxEN_V=+=dpIJ=@XBqYBhQRbNUW7hGOz#q08T9)Eb4ABPfpkPj>=zD0Oe>5Vhm##?UX6qGsaPWkb4dQ_(sO%QZwc$K`myz29 znWKWg1pZnuk7l_b_+vy!pUw{XZU{IYb&7tgfS)b6D`fQcde8&x*7Q0)IGD2DBD4UW z_2L-NW8itb&?`fJvfy8!=q|wr5Fvf86XYub>w^n{Q^9{Y*tbdCw;?cS#Zb4Z|9;=u1=G&4V%7xny#>@6eMfGlsDC;cMuK*7v8t~Mt< z(n0yp5eJvXEft)PY|-2FA;dD?D0J2deUKsOOu=rUv%=~<{GfA>lwSD2oNeEpN zdKMyKK#mcr0GVjP@hHl&f~&&jZZOCM^jY)ug|QV`)lx9F@_V`oWmEFFz3N{ z3LX#pO~Fqi;3IP-T(6@Vcy=YQ-j#uE|?2( z=LsGLB@YVTh5*(Iei@h#BKl?1dQNmcA;OEy9mtU;vCPhQ-ZlE^9N!S7t)UnMZ(w% ztdF{{Ln`Kvk*(Y!Tpn1Tt^lQDL8~V8W}w#>%)v(^VkqqZ+>e;q07F02GXr>{(5Z92 zK0^#bbgU$UV=$Nv#e`lEoDRA^Lri2o6*|Xx`qTv!1I<;^fssh}sC{&VxbDBMZnSxJ<#q(Fi=sYWuDc~VMKM9=)Fi{CZ^5MkL$zggaV%7;1wLVG# zZkj-KVYGmvI>b<#2%IByw&@l^Zw0zO&UJdjgqY!>*Z?gqqX3_n~&IsZd28+cB#E2YUWvYuzJ2>kIokun|7WpoqcPEy> z1{9;;MhrbmAU|H{eVP6l#XQUvMrqJ-VY`k5-w9R|*K+3w)(gEA=scp75$yteK=3%= z6U0c&YSgVT@QL3;U5FuOlYyxQ9AJ#NXu7rtF=Y*iLB9yPK8pe#QbFh8B%ze$5et2a z;M*W`zxZv8&gwzIJm^}93|~Na90>iQWd%I%ii0_JLhxYV3&cn|z+ET31@mDxLa>9%d%KoL{v!;}y>tLf0A8dS?yJ=k#0Y>D ziRUzhR(QWji~x8V^Z}7!L_Ze#V9@_1bdLE?3!P_vek*i#j+X<%-~jU~F&Z&D9HxYr z@9L!mGl1H}h!8)4)+H_pB1}u6Q>MFMKEQ4zMnt*&WUSCH!|ya=%wOpCcb_om`9bmU zJ3J_%Q_*8WzX|=plf>}MIxfHV)x4HBk_{ zOa@=(VJxJabUwjiiIIY3kck&MBUDN7VU%l4;zZYPL!r-xvlVd-m)>4vba!4vNBW1- zff#Ls+-MP^pZUay#KXV^#Ed@7CSpWrJ0i4A=D z{!W7Vk~4@H5vqa+1%}drE|!VAOBk#NONi^>a78FuF8CPms$w!v3;i7E&kFr!_< zk>C!%PYCV?{5mlb^BbD<3{W!5$0EFjRhV#PC3Rd19pCJ*GhD zsH>jLV)BiJo&tI|k^d6%w+Q|fc;Fv00nhEih(?x95C*er4zZ+oq0pJ)hl!CIZc*PL zn9b=`!5CC}-X>;4hdEkI<||?(AQfR<)EMR85)OZ&>md>zB8clDRlv!_;-Rw0Fnj9> zeLnm)70i#ln(bA+CPCt*vWm%-?`ozUNaOb?+q0=+LW0%M$}ip&rU%kCyl0TE_#Y3cvzST3G# zh38e`fq`ul`h3v02%S0c0&zw9M0YFn2B;J7iTno09}yW?o*zW!Fd}i8IF&w;ehclN zS;RSjSYqVF7w{Y}bc$6Xh6iSQJ)!dicz!qbrBMR2XT{|E2|fgR9&sft;+Y_Hj<)Y2 zX8#`J=Z4*`Eo48Lry z91=6ZUjhdFbX0MbCJ5s;RH%x?2w*!rX9<2ExW35z8@bh*7=D?PLj|+N&Jo-O_#u(6 z2%V1-S9QbM>C$!m^XwN!4eR&iDQX#Ed0O<79+SMH7&k{vyPqzefn(f&lIkd=`;-STJvDeNym5G}kSH zHz0rmg5QSx*Me6==4ZhRQIwJB3mERo0AiXdj%>8iI)b@W>Sn=1@XQ`5n6sqQ1kXk# zdQkAYP`Xm^I^bY2{ITHbh}=2BFQGWtzw~1)dcZ?PIEcSM>Z=QW5wY$l*bmR61h)g8 zCzwZ0trN^#d{ywB;i_rSTSnzR9$qS#GtI7U7)^si?zAV-{&>1Q)G?Z>hI8y5upXFW zKL@MDTzd|fZ_c$B`iv~Kx}w=OxZ-}hnqeWKzN*2O7T6=afgJ6S@)O(`PCd7eoT4es z`hrN(G$clIrpY5lF4O4aIuKzRzLIf>Ps628p;teBEdo0mm`2EXjLO{#%Smbu&uKVY z8XAVLCdTuh<`rT*RcSsXW_yM?O`Paref%tYCzz7RNsdKm^kg+EKh3?Qqwdgb)hCFd z3~5f2f$Y`>DG&Ak{4N;k=c^N^xFWg46pA1fE`E~qD!@$8 z8+2548BDA%M?J8_PE5*iN$p6I+|PH}uNGskB}oclugm_v82da)QWm9HzB#J=(m#}s zB1wv5qs#76jLkRyP{HWycXQO;#n`P_xKcjDh;!7IV(djENinT=*`F6SF9%l2VX2FnqfN`%p3V ze3DXK_9mD8TQRo&Hc`oCpLf~m588>9rNl~yN*5CR0O$*qq%LT6=!&;#aOs2gGv3-0 zhmXli&&N3>>Hl#xjVFD`q)BA)KJq)&tVir*6H7w2^^ZtUgCDW0s!Fw;=hgSG7!Rlu z%B~dFX)vCx2(-J3T5h)un*e8~b`GF(Eu3uH-PL9~KZDa1zD}nn&;7#RU3FdoXC$1i z-&J%rhI5SeyY4_(1r^&b+^1q!BFK$kEG45L>*hq&bETabmNO)zc=1Yjy%kQ@;qL1A zN_%|RdN|Qr!rALlaJRzAI?!D`@hG_a;dJ9zt`J<`(2%OJbXJ3NqZZyp=lgK>)Xu0? za8?=?@;iXe!9~u^bQZ$t26TND0(!p4Ie9gl@51TE>fCAwA1!h=Sp)8=BIgTm);L7P;56iO%t+p`_25RypIF!mI875`8LSiYJN-%MF&pL$od#;7G%{c*+hXusa%p_MlJDp?tv&Z zw84K!1HX+G*Dw_i>vmd$K7|C5x%j6!gX%pE1wsw1#~}@G+@Rq#RE48Dc}-pgi( zry2YZPA-s0bbvdg;yqEp%0#0ukorL##t8r=KD~i;Z)fiOk3S=1S04MRJ0ppz;^TJ7 za$kXkzoI~oJdC)TZoIpw*Q1=WzU7$RR(CvZr$uGD`=cR$QFs@EU4 ztE9T82K+DEuyY#JubZP!sahM%T5sn?VRLc8HajhTam9=@f65!Vj2d@W4{08r7_AO$ zvn$pdf=`&?vG=6Bc;;HXvCK*I#FY%I?D;J|dRaK|ENAYVS`!u}&wXxwGLBu?Za2m& z{IXlpj5^OJ`?iihKhjy@xdzHZaQz1R5!jx^bI&Y=^vvycsu>@x)@`@r19P9-kA2>^ zq&3fuyyH-^xwX;x9?mbGQu(zeV8?f3#;NqDkrU>Eb1nX!rN1KhLg2_dPUH9M=JzJI zdg1-#dtNw_y!(aa$+^#+NcOyV3Xw`YOP}j2=24A0W(G_LtF5d*C8)O&2M`C60M-Q`_Z_CY&0z!Ny!25S`dDv4%HC-y$jBTbg} z+RdU@8kgVQ;`bk#>ijW!SPp6^jqcj&44sU<34Mx)uFxOux{RVW4BZ%@-Z^O3GmJ+L z+40Sd8-g}&2->_MXrHwWgLoP@6f4;Kkln)y4AZ$y`VjmZh2a>VxRD=L6hg?&ET{R; z?%@`<5dXm+aYMRBS(AjHdlSlqCY{aO7$2;4#J(2OXl&@+RiDaj6jsICo*UFyT|o5V zy2ttM)aBu>JM`crz@ew?e51EIddhw|X^1Z5!4x+}6vLy{rmyYVMkDp<*LL0HnP4}e z%qaXnSu>O`rF>UvsYg%SWmW6bc9Kz94LWV-c-#7-Ro-d4oC=<{XBl^^oBnMt=)*4C z^^X0CwNVH3{=`BbF-_KlVUs<^D&GRvDREz=s~1UbL9Fp*{i$HqsxoKnc{oyK?HM~M z^TZ7)KT#x&&JF4N%BL_=##Ut4c}kr=JF+SZUm%tfL$9AMUz0)YJM!wWe;U)=(Iy*_`{vp4z`=QT1i}HwyEzLW*nN zPex&wipIw}8n2>#yXl*B zLp1V>T1B1wvDnT!rO54O;8cs998K;zyh`c!PX{hzfmH!V&;DSis-552rH!@fgYWFh zI1KB`cR1yuK;@jXGXqZ)MdKxUDZH;vC>l@P5RDB*!Mf4dSmbu2@l;VXZglHtF@Bf_ z@ECin5eTF8ONeT{ZED&NcG4r?+n==m*fIvy4dvR$I_xz1F^=kRieq=%iQ!&PHI6cc z{p*FI?Ncc~*oj7%%K5>rV;omwey|TDM(Or4O^a^DFv-p6C>-|=(bB;Y=WVmH`qvs? zgi3kF3{&NwF(Zn8N~-fUBBIsxGe)@D74JVBJ#+dRMvTVmnW?rV_)8govVxx`_~(Ti zy@P`*`ad+C6Jcm>o;v^V0hT5I;?|A7AjU(E^?jp0HLJ3}=3m_6q5M_+FEzg&W{fm# zk70N$-Dtgs!;LvvSsv48*sJX<@1+?dGS+5!7kNhj8s53y5##G=6;9+tX=0w;jZfYSz=rNcSiD7i-obRUsWwemU4E z-QUBoMvTcDIVpG{!yo7KO&UFNoN8IuKj|OY`VxGxu79>K*z_j(4egAs$bQd?o*eXOH2%LPEOJ7iozL$8YOaHhSJsLqL zcg5#z(0KM^++bIR#~jBkadBf;ro4v-4dfOR;v`VqC|Bs_;PFBJXT3=|c+7It;=2_y zeVBbhho|u|^~6wrTA)A1BMpVbuNch*b5%n-!Q0W9BTCxaIrN^r1s{gY0Kq1L878<1 zE=G?)(?Rbm zm?_g!e2{q@0sk)aL=;T~A{&ZOqTr$MTS;(Tgq2xLW*}Fy(8EQfh8w?$>48UH67zae zJtqvzRC4;B^azyqbivh-vU`is9}tRXZvk__6+T!HMvEO7I}Hd89wF!#>ou>q7b$ns^Sx z8FyYi(poUX)OVMGehPWYB^8umWzwgD0~c60gxsGHXpi)47k(Y&IafzemM6f*Ail)> zfKXO&UC{XnhV+r}TVF6gP_z`h9%<_)nAy({R+Q(GkJ|-vWQMiGZlAzi;&X&CADPN2 z5_)(WiV6k)4x8%Ysf+_#H;J08O6wKvZJgtKA+*bUl;8&1&rvjYP&fi7y%kcKD402wCYbUW#bkISEak%ySi2A%xMwl>!GfuOEHV3pX>i{oQAnp=xC}jqQ3UaNu`(Ptqh5K? zb}7_J)w|7~)}Ev+Au;?WAvO};UNCj{7HlJoA%f{wuT$3Q*4>7dwhIMSP>j7sFjYSz zn0Xo$Ox1e@bJ-iu8Ds2NUY`kOE8@8tq&J5Cp9Hg}8%Q4Msh~yigeW@LG|LF%G;kHc z^pIW5LsP+&?zQ8R`K<}+x5;O?knGX%4XTrPMs8q9jVC>jAX5?h7Az+Mr|JlHRo75yW@ zd@7$5Ouy#@)9cO#-U0%5|KPuxsFO!@MHJ9*KxYlwq^)fddbS_)2qhkU^wLg_t% z*?}$+Or=i?ZVS4;4Gw`ZLVJZy{f7lpzrGQUO`E%8z7O_c#+2K_Yy?P5MLbO| zl$sR5RN7E5{k9X#g_OMn)9(<$%!%=W={GP#94yxd1@ku1<$@ECo%)J7M4~FH&U&Hq zvbLuMbJadKN-%Os{hf>408?3>kLoyw08SxGiX$KQW6594jPt%F_iWfqtK0b`B2-9;#Z8$EcT8pWCab ziF-dj7tB!&k540=&GvV}TTx~_&W-d-pz-!aVrF9%!R(vr2xisqEch*COFzM5kkD~4 z*mO+K-1o(M(ujY6QVxlUxpApL@H2?Sdch1}t6%(ys*{RLv(Kec{L{y=)lj z$j*VAs;Q!tV8(Hz;HMGCe8G(Gykhdajgx*EHxWa_9<_OrKg~R8s1GLj)6EUO zpqcM~%rI^ZJ~7$hGOHlTApBggNpN`9~mQD3%Br(}~xf)bHgGe^)m9eAy+~{*#@6_b(%i`H;8=%F+|h4(|>#5akP%)GOh&k zr;1=ES}%8kPxjv3h0eEsQ#-2`;o^_5+klOFAALvcy}3A2}G06 z>wUuD1qtsFgPy{!L+JZe(sX}XW2?_(49z(__k7c5H-37=S}yQ zG>rSzio4)!uXf$#pM#6cn$JM;Td6x{_|wW{qhwa%3o1h!i)gPQh6;>%x6JS-Bymi- zL->2Z=SeQ)H&MrDpyVG{m1k;Yyv{yYA@TX_7XWUO3GUW`) z@1XEGggPp8Uc7x===sXH2Q{9J`HHaHfqh-rtyI=MXcq(3*n5yktYph7x&bl^)(Xy3 zFMy3GehsS(IaaD0g}P`t;lzj{DslQOr1u+?TXoXl8}|DLjDYi>PQI*i-W+%VoOh2DkO4dd?Fm?j&i*3CwOlC{}M!O*&Jly9`)25 z|C7eUs{33txdrOnTqt}@m7Axtx86LI(CzA$dH$(U2hcihtLmz}T^*kXRaI1zd;Muq zx1+REs=1t2YV5sQ?Yw*a2cp?&1gg87k0aH<`}}Rpm6qB-yIhR={ys!&kBXZQAL~^0 z`3P*j>I_$zhf$=e20Wt3?nE;vBt}}$m)$)deb{rT6YmIZrP>UiO^&0UT_CA8>KL-{ zvamlz3%f3KKJ3hzuBxX{E5e8&-%3T@uM=GPezeKuNNFQ-5co4?p-rTgq6;1<_#HJC zJ^~cIPeczwbdj(>MFchqPJy=RfR1;@13FsEbu2r)kPX#t`$nxERc*2YQ+N7*qZ921^y*wPK+9{(BCHN zcNAN*bT?k>)Xs&-{ch^`LX>|^6}1RmOPX4?$Un%8iBT69p|)L66&Isfuqt29klaDD zyh@Cu?NQyy<})iH(`8=)JDC{ld^HPfK4KR8A2olqRs9G2=}GL|&x`cZig=3=={;)Z z1E>a67MtZNI}Ua{F_aBKcLwjqLUsHBMEfUYEb;d+M_Q`)6921cnej{gJ<5zkL`&6i zJ^L#mzlh;^hpA>SMFotHRqrg-74Y0r#H^;Wmicct=BPWCp-$h2TpWx~B=qpMTDQ#q znlVr1J&3^WQ}Z75Uo~z~vkH*hJ0bUdeaPK}5{L0(RR{eiE99?^X0Hy;ZN*@GR-AR=tM0)KDFCt?RF0#Fl`1dp{p*sJ5knmkOQ%%;V=s=cO`_ z3f=>}M(`nE9z{o)Gr&&^z6iWca2cfQ1;K3Wdj+#-uv9~>+3l|$^(#DJt%X)}O?|Z6 zpAq0xc!{zuX(MD41oQr(^2M-T>WlD8fL=@J#F>JbrUu1uBf(5x%VM~#V5YT8S-zo5 z!u6VAU@nd9Cql#n1v3-$T4Kl$-!Al1z>|t$o^VH!yb|HTacrN1v7`(^O4S;@icK+IG7de;>chZ zcvbNC!BuB)C`~ zYr#DdG{ew@Ld$~}5M$Iw^CU59Bh4OSq?_g(F@|R}`ltzvzG&JaA)(d5B2Xh)Yv^@CV#FbqkESK`=<-u@f5aU5lGnlxVi&qj?ckzqFc&O5F zYl9EZB^rG=10Fs!naE|9l1_h+4m@mVJ|fO=aSS>iA3A*+eS`secp81fRJM!ttROmX zn&p&7zfAKxF}_993`Q~P19^b&Cq}18^8#?N^k@EuB5_~N<}?1TxQypIty8M=S$|iv z)C+3$S$`K?!gCHz+~wHh8(4MKVp&%s)x2AuOx#VtG^(RD;s{vOtMYBH@WY9B1K>`s;N1N`Tz;Xi;nOuayB zzOv54+N*lOGE2XtHk|iQLCEDUfO=kyg=P9)QLkQr&%P?^BB&G809rfLW?G-A>#$7E zF4g5ne;2d#F0~pqG@Sbp+*7K_C0IFXF|Dn^*ECOssf$h*xOH+d*insh z44nUnlTlCtll0xht<-TcZhag@g=gGUFb3(niC_;H8V3d z_}H)h?mqv`Xziw_%0GKR;|u@D_ut-=^7HbC|Lu1W@z!8Du<$d}aa7)VEB=Ls&U!|S^+K+C&5tUIrbPof}Hj{+fs5kkjJEFabftskiy0Zb>3*3lo zV1AHpC-?_=>ngZA?4E*`!p;+XAN-CI%ry~X1+$|;r=oRocs5IL9AxHW35;gMfU!gv z{b8>byb1Kj1#gG_rr;`|e<65^npfFL>y-;<40MJzD6u~xZiXkJK1vVx-;k*#^p}8j z{{wmj(Ao(72he*69;>cbb`k?~phEX55Zws&d||IdEFKnoGi06+yccvHFU8<@gB~m< zzgO@P=s6C!9}`eVRP z2_6RfSz-)sXTFe~QPN7NtAzj1t4AGT1oNqCQq9T0<>+Ipp=Tea=F!z$ zy-*E)SbzLz50rZp>T3$V91N@Od}VZb1N_jFLKmt;^=r~4N|%Wp9Xn4Lvkl!dO?hH; z#5DRw;t2J9LBxpQ+M3Q_FCGv^2A&hARCidWlb{~UaJrZ&3F=~ov)ar_P!DE0Q!qvj z%YxKKbqg#L{lU{&P7R~IzSex{!LV3dCi6>{Q`Pzey?jtr&c>7CfjIr77@Y0I!uuWB zPF2%aPOZ##su-Kp>u`qcErIDDL@rgmKEzi6NKl{u9-g2I z>o~K^MxM9NmJ93d@!e5ztbN`$F?DRGiQe>n9+ITMvlxIpV4ak(8*(mkISEwfjtbP zhi6P0KY8NN;Tgk*PZ>UD{Ddi48AHcoC*bHI8DsNCj|({_)y|tZZ1U)FuO*X}H?Z z)JeM)`x;3c1nNIH{x4@{Jl_CQ!?w@7HrFqR9hgh)Ig znCtVN5zN^SJ~$a^&Y-_4_&fML5Te8WMDRM$&kN>E-L)b*+FWH2sOU2Y-6Hsus@=+Y zwDKg#aqgRJP7*Z}%;l*3U`hH>b*>f0&iz$fYxJ=9sp_qrj3!;+@n;d>=$k{3P&XMx z439q8S;cTS!9S|3V@^hJZ)<0(5oiHLe2%lfB~BCE4p=|)K_|`>dN*J$qNO}>Q^72z z*2Qpp!R&za`cCQ32MHbV^NcXCzLkpTxJ?*?f$u1WCl^zs-%B808Zrx2d^@Lq9QDtn z2%ar8D}uAyIVDY0(uX@ZHDSHp0hu>necb^a`F9YA)5G;j9Wm6u4(IJE?nkqv>I64> z@BtkmHOhzY8rV}Qn2>N@1jvhk`)C#Q-P zfsVPf*1mtRFV@VBS5rt%1NlX@t`j^=Q2RO|fMa-u)#IB-N#*N|cZ}#L{ZOd|Y<0uS zP`-ZsD1F@<`$&f4LsvetB;GZ4uw@mjTb>rR7czs9<}x6Z)Y}p(#h~u!pf7d)Z|@;^ z+GH1G4z6w_25;`-{9^lgaDnN${Lj6IsFcmV@V~H00J{p>s}t?Q>ZpVBoeFAwZ|9Tn zp(Caa8kIM0*qGr3C(D&o@AYv`{r#^77e2!+Of3gG<@ya8gl9>9&Y-{lzU4n2PHOFG zCrouZf_jxVVRZV?;HQ0^zNUJrztd6u-rt#7P<~{I;G$cdZf4ROC>q_lM!Lgc-MIp% zsk6Jo(*o?4GVt#MX+CV-w*r?z<aP8o- zANXOy zMP>CHz_gL*nllp$UIlqAu-yRNc3n%oIMPWqZ;#epHg;X}Ex{Ot$3WwRf`JXHSUnRk zEN~}^&SQS6Tfw#lX;vGX(->alYv&)ncG8#Y z>17JQPY`(xKdQ}SC#xZwqwI2i1?*%xjo%m>+ykdS{}?6;!|hazLS9NxPH$i~)wan_ zr;Iy%UW%*&ooP>NL${>}b;XW6TcbPI01*z}s|%L((X`j$*Go zcG5_X@iuow(72l34r!`RnsHz~vcYiGyvu~rxXy#;U6-fE_{q5A_Q#q$h?<5^SL1M6 z1m;0z3#sZ<0CjMx)7)=jz}zXR-06>VK!Rr2$`#^!Xe{ArF>y zV^ly$ld8A$J{*c~HJ9cwmaE+9PMd`7bTdDV!h=Pgx2wYGPMNCFI(vF+W-cmMUCJ8K zxUkbT;#S9}BZlQwg}a>iisg;RAW2hh$m9_oqrAcC9vWA=yy|tAQ@<+5edt^9Zxo&@ zQk`ZH)70aSQ8~E%E@xYW!y|-D&(?o($KqcbGXybaxNHP|Gj7;aINLNv&Y6`x+j(%* zuugbmHj1_zUiBEq<{iz<$efokDr4tJGrSn@yvMkANSBc#GYkK;=@0F%aUaO9+s-`c}uxH!eYj@9X|Cx85dTfEyqD%o|RC`2L=9QqDMWzqii+q zaVOmzZ>iOfJE=`BqfzVO40t?jNDpU#YXIMZ>gogj7i>M80p>=he4*d2u0QUi1$d_V zA|dff_@LlA=p*z?BE0p);D5EyIi0m$a4%pz*n!Obz*~jRn3Rt05gEZ9xR|zy-Jrbl)oU0PrZm{g7Yd1wW`RKt

39TrRaF=j5&3k%Z9v!emOzGI{8|W|dDum8>nL>@2}wGEfVfhS`tavjA^296 z^^}vC)c|b09}qn5-+4*cJCP^51@k88HwAZ5cRuB0WL-ufpAu3ZsQ5;321L&b<~0tN z1(#FDo^sNnKSCn(JthckhhK$1?KEuv4RS6U>KH*bRlS=KnA^*m3Vk#DwGzxTR67VR zgnE6o3FJGf#ZO}{X^VQ|X(z2RtJxZnLaIHR1eZf(cL?61&cTNnw3Ph}a%vpBusty% zH2no{QO%!m5_^?La_$il2kt!Li=vz%SuS`pa_vdM=V5OVJO}o3g8M`D0m0d7^D~%2 z*`N+11VgE_a8-F8O2$AbmGf4MIf9v_2LwOwRoR>H)OaOA4cX)jOk#Dbf=mj1NvKB* z|6A0tO->^d;t89bHs)HN8o1d>#lbAIHaknOh9qu_la_P@`LPDdxyb2B1J43tcpk1g zgKdtof)lqmBMfT!^jUmjP^$Y@#9FDzTb&+B2T^06Mc(M|h)A2hMgYE*I=R(pYm`yd zw>fRgoJ7RiVc5mbCNzDBA$7!73$`If&D7>?PFgm1puRws9@e9G5HW=edyg1VZ0njWU=Ewr(apDp#ewnzWi;ohQ zLRU}oUGVU$&Jv&5I7bb6!|9^B?R9+W`8S+S{KB*s?~~_McUWero77`_ohhcTp(?Y_ znW9eYgWN;=AU8vu+y}Y&D(g+>Ev#1hCj@ifc4DL2;iVwOK(~iT z*W}_QYg8Ys?@$#-dDFtmW}~M;vNF`k`^*YfWPL#WihJVi|2kQio$an-DqSkr^c`n~ z<>v=E)3f0J`D`IS-~5#?H+Vs4pt24-TmJsx?7BWU1?9wHC!s(MJFCuq;EY!*KXf|x zn>1?t#QYS@BmQ$AY$A&$5yxP-jpMrJf_YgcCGW`TsgG`>_m7tFj{1NEm1@kjM!=QeC2%RbT zJm_(EUrJ8=>bisy# z#b2Ox4p!C)G|S8qs`m*{2dgJe;Df@i{4k(Soj~K36!J9i?#6nYx3p2+9&p}~j|ryAl^_fg)moW~mJexdmCzfTlp z*U#h`gJ#)2!P#Fr`@R3jw^Y`&(!m!`I}6J=eBPR#QUCCJe^|{S<=Wb?(}#SEts4I<;;UY-fc{_o`bLhDWJjQ*T_bb6D6`Z!j_{?9;!0 z&6Cm}9(p_Y^P?*Ol=*xEk(Bqzzb>%)IN*xI`r|G4J+%Pl8#Nl7h#hFRQ z4s|TfOo!Tn5pNDQzW3zCn_~^kP^eQ0W=&^|p?*m)JH=6d+NPl<(VrJo31i`acF_&s?3QE&(3Am>gQ(cySj5&SE7 z*r<~}6cN(vvA{ngCz=U;6*?;21baXqMC=9=HxL$x!+l`VGXkJ{5Yu9zf8;{{NN@-6 zPZx|uYsX^2p@`gC!5kTE5!?g(?+Ip~P_KwU{u%gnYpoqv{y?n#5Fk^)YF%qw0jELe zojy8pMW3Yu`dI|P^&#??;W_5ZI@JKf?38U#?;NOYBS_u6h=-macgG{|B z1D?yk{Xus#7QBd&mv9l+11=QY6?mlJY~)ahV9wa-&N}$pB7iAEp9%VO!Ms0nw%`fC z3j}k_^SEG4v$-A1#j*~8trL6=spP1W5h63!^oXBDtPcz3u`|a7A3|CAP%tNME(_*0 zw%-YU7m>Rzn5%FO{QQ&g{$o(WBeVrt(RlZ$qF+!{F~Pcq?8!0&ZNhm#4|PiZ-{4Vyaz%s zg?<4#uZE$}Zfa9)bOpXwry;E2Qz+$}WTOkK^>uC*?0M(p4q7qr^@pYNlJPtP_?L! z{tS*m8&n^W`~(qPLpQwu>TqljzMo7rAMWmph}bEiJpwuUAQH%Fh6LRZ`aZ~r$D{-0 zUqp182o6Ft`wDIeI(MFv$Awh!Tvp=xkok;Yu3j7wd>!;pD@%Fi%-(fzcozXz4)uCI z0(wiq;dpaSKsIHOcfZ~qw2 z(FW%5Aoq9#Y=ITAT zIa@VXHCy7@xw$IElXEXEO>V3UYlX*Vm4}BjtEJl7%6tg>;6{MpPRBOM19$aWP zgzr-|vLXD%KAhJqJK$FcP(s;H>4+!HfNiEv+Jd4(+uwfQg*DT zQd_fEVBkPxtJ4v4%O{Y+)|(yG)Koi0jp+-G?}#qu^#3rnQ$1bSZf0~fzrOWzK^S4B zThLW2C>%0=7}hkeb~QVMj4dpkP*^JZYiiLZGeX@PWL8prx|#n_rB4K1R4)TAC}KYxreN2{Yf%!l-HYk9`7V9)To%xu3(Nq8xG5Qh)# zY3wGLfn|xhxk}A8~j5hYFrFWZ2JhsX4ow#g* zZ#}YtkLM6Qtw#pTuAJUx2K)e`aR=NewKM$PE10)d>H!q!_!1nP2P4lM(AfqMhaeV9 z1s8*7y#Xa!Iy?r;W^t&C0CouGT$k1(1J42Q=xqeavWg3Y_twXmRZI z1qeoc;RqHy08c%pf{59`HH4mqXKlf}T`5&?H^^xu_#4P+E;tuh>mEbSi--oa&s4-c zW*9oMvh4*9&4BL}4=+Qg9-M(b7&u?(90iUL+zs?Gf|~&AMg#o6gXbSY=MaViJ?7at z$p5Ec4uP@D@P6&USws%F$TW#LF8DWiSS@%s=o3bt2ATE#?>CfWHfogFP~T9p*>ir7Svl01?Y!0dF;FFEd#B}C zC3|*`F?$)Fo+aiA<3Ci5UzeK8Zhhs4I=Q81=2-K9p`IOY?z!~=_@?h-JdaH<0}Q=a z`Pm1}S6p!%eWUE?qShWo&R4hIlOIxATqk6u=(oN zM9_V8E7ems|HU9{t_Pa zggp2S2wEp}&R=X4%;Dzqf=42vFAAn!EE^GqH5t!01RtR0?qRVMVi0s%@Gd+r38u>M zAA(N+yZE_55Y;Pe!JMxO6Z|t0ptWJZ!?A5Gp+m&Ys(v8CYDc;oOl(1V3)2SRv4W2v zWp2T&^7Qfn`0Kzk>USN99ME4AOqJpTg5QLka~g+%@qp${f`5RA&4M{6;j25JL4?^YxNFrR|lc0_n>`)V2yN9d_>QGI&UxWtU*Rj^YN0}A zU=+xayzaJKGL*`ko@0jkXmb6VzUMC~)7aoYci;P*i1(3ZUQ5O`I=l|0vGm@l|m zPXW!Br5lM6EX^Te$e_7I41Y8sEFc)k(lqvbJjeXbiGj$md2BhWshacAQue8)O7S!n zsy*}3yuPTy7T|G1<>BGPhLNoc%n7b%Q_#awB};KAK=wkjnQL5q6l%4H_Jy!R_50vL zvp$ZJGfJ#TWiB!+yZSZY+Faf;uw*R4cxW8f>Q3uDCv&q^ z)?;R_GubR(RZ_>Z>oM~VfAnU4Ut*4k?1C&lhgTP6^fEHMn%g7PKbE4;F{=j4E8`WFycBUj@qy}0%pLXcK_y#@w~gztm#sfXZqP{&94FN zRN|9n2i4(8GgJ*dZPzM4dPlRM4#)#1hBd0jcdpo5xg?D4T&KTZTm8#|Y?V4AMh`8` z`yZ_1@=lp#wRx2bclbK3;QxtghhD_pxxwsiG(OcWHGSB~^wbfl=^5$iInH3mpBmrO z4P*FUb*X20DJrp%Q6)4VqxPc0v19Xw6*@RR_UwDwoNK7Xo6PWA-eaV4wR+UFs(bcs zG9z!#LT{QH5NHLd*Pk{6J)b^7=ts|A*it@Uyc?I-3IBYy&qyq}gvH9j&Qe6xBSSe<^hCR=zBlO7HIP#PSESh11yd zP>AOrf~mV#37$wFjc26bctkEv@cnplv_l^DYEuN)Mx+@j((eT3fQ6Xa%gkY575%Ci z;-<=O2XSOSp3_ofdwdEx`(Rf2HB1fK;DGlKN-kk?W0ScKD4 z@SDsW!FK_V6MR4TrwZn9a-QI~fS3A7Ch*L>4dQSe0dEn!9+-+644B&e2L&@hTCW{+ z8}#3Wei;1Ru1fx^2%w5!%1;!$1oZlX2l%Td+s#h6es#)r%e>!0Jhcc?HFugdW4}h!XVM8`_*KjiwtLmUoru!YYTizS`L)`z6T^vn)j`;t%>vYw zofv%3uU4*x-=`6sTm;D=?p0NHVUCcmb`nW&z5+Z;a4@{hBeuQWuP4P@W2E~z;$UPE z%nqR^0UscSoO12-sg1VIlG#E4@C(#pkt z;+63HfZ(y-`vJ27&J<}fibhDIxtV8xFnh+!h`G+N(7ia~Qv$N}?Te$L1w_1ZxU zv=Wn)b;#_YI=^iOtAayj2OJK$=@7(;bq-6Opbu z_*GST*RYti`>+}0s)bHUPtA6gNBW#q*C=-!sGYrXGme1#ifVhrtnbXhmwf~)=0U1# zs}-#_!pW6W6?MON#CH_-kXl*jWJo~CTGjdnC|5Y}!;m!dT}Xei2BStr(c*Va-*(@I ziHVsR85tG+H))cUnAiw6MQ7=G*mpdK-!(tW2nxiTcA_1-)&2L!uUXChm)-Y9f6xDC zzukj=`yqd|rMea6nGtBMH(Z0;L19y`d)=O%^fc3&e#<3um!j@NhnE}GVmwtXYkH7+ zzLinUvm@Nf36EWdk4$%b(@_XepX!b;aG0u*Y^A$TqSk9m7mzOETk9;iGrZj;crU``Ql6&wnlR|L;S#leYKCg5G9Z=c}TfDZ{?jEY3}jlpvnG~G7_&apB?-4T{u zNX^&cfxZ481#d^LV05Mf@EGdh6f4Tur5;bgffku?;=C!t{v2@{E%;Z2r+cZOzXe<- z^dwY-{}j9&@t!AmFVa>ncr~K2PB5EO-K&M13*cv8j$u(n5bIv(uff90q4eY~FngW4 z>kIq>u%74zcH)gN_sNf>c{L%)j|6-mbWVSAJ&Zgrga32EClH~p1V4qaxb{b$x*RlU z%}H2tA?SuUP|eDTADIpy5VBfr-NsxV7}5)!Mv!c zOz;t;exYD?FVu^5ti*^H$&_T$_+v!uPH0_s{d-Cw}2kBR_o zJ^bA#?qNvYP{9RA(Imk?AhvS_*8{y=@G}U9XB1L)J-pntf~h;TQ7|tEc|q`a$a&ch zOE~mE^(Wn*1#S!`y^09D16goFJTO9M1usw^*R`TU0uhc29~ed8Jc)W%bnFyJ$`I1) zki`ADbl#{k;oN08@{x<4BvIfv!3!X1s^FjSoF|xiylVu%h}_|XKK*{87C~Ifa>hwW z6n9B5#j(Fm)+V+(QqPL&dIRmt8YcQ zspqQqTEe{?$=V?9l*jdd`kR9|?G)StJlxAkI`?y)5$r?~e;0fN{3ddZJk#;wstBfh zZp0@24!jd=q4a+B%x*og4hIIT_euh@R1Fmm9(WilI2nTY0a6er;pYj?1&=;;9CY&U z5&AVm=&;~-5uuBMpQ9d_0Y?fNsk%47t7rI$B-O`@f=L$Vay13cxJl;m2G-rN3-KB@ z&;>sleZ?Y#tWrj*=88?Vl8gnab*hza6saN*V^<-tbV#RUKg3QSUk*RR)XG%rZljO7 zMlR-szNQddd%(pjj2OKIsz#a>?fk+|wMerD#m>QdYmTJ&x|E%XHQN@lHMG>BG%Kwu z$CLUdLF5?CI$`50p-vm)!lkJFLVpoh-yVp*9}O=uqu43|s$II3hRc&m(yb&XnvN&Z ztro^&b&OWH{+MnpLwXlwSV{U|m?=msmnUfE66?4f&OqjbtMlM89>fWdcnnjinO1bA z0|>|uktAP2u<1cJ zo#?GnBWo+4Z#1$7IgdM4%`B@0?SrzcT}~)TR%>k4M5Nm`w$fvpBj3lPTkG5TIfEFH znWknpw(iVqi5LAfNqTY{ZG|{bMTY6yyg0gn2}YW|-i#(lYq^STVm;_QgE`G6P&`A* zUT$I?id~J@@;O#Id^(Tc5hJkI)zYSjOD^6{JVtj+>1Je1A~BqYsB=w`LP!Z{X3dX# z68ZWQov`FV6M#V%*{TGq=bIsA;p$j36s!d*xw(}T_W@F{fgH8G+^-3DUF^+mZVhtB zz&#$R^Kb2SPb7vkj;yl<`y<}Df;p@lFPM9)=Lo)vj9e|4dgLz&=2+t^!BtSGtwv50#Z7L3@fQJd@y{MA~SA~apg0CVH+XTOj>^LNN8~lDMn0hdw7_&04 z@71C{e$k#CEv#8p+#TWI8`=#cMj$ktH1rMUmJuUGG`{gHv7YEbAT*zoM?M%m*~9TB zjGo}B?8SPBiZVl!!}cdk_ass6(1O=b!@V4w2B4`*j8{aHNsO$c=|GH>(cDLjl+kc@ z%Xi5b54!RdEzaBe3J4ny-_;S34o|yY)`0+HxH9`$cNyzde?E4oO?;eC*YR+M_EKH@ zTRogvz0@*3vwNwp@Wdod^ZUS643!%^&Oqe`j|)(_fgeY2_0av`IiU{1ia}rO06czH zBk*wg_krRK1@?jB4Va3dZlfh^!o%6>9`y^UGdx`eSqls-Hl7}gucyOE)jJpD&1!8f zm|s!Xa;^G?t;*H~VAU|s;>faXo>kjbxe2$$DXrR58?&Sk%Zo?Jct)Kg z`NVX5IeG|Mp7>NK)T}dyfckw7CgwzSbBN_ruKaJtj#+ZYfboIn@DS@Azd+7XIURMV zTUq7rhi<-=nwZ-%hN`DBPFn%$pTn$fw{j6mZ)JmIm+COm3RN+~t&Zl+4Yz8*aBJ!< zPYqXXoa4ur@2MW``C+*Ai=q0AwAPg;P9LovGVR3jYM+Ld_ecv>PmQv=##Y9MsE3F6 zVENtgli|lZs=Vy}Xg6oQYYXKwJktdC$BSz$n92z)1oNz>I|XyV)2997*Zn8Ucc1^MCa7ysUo}w|9hqY9432Lc~5Vfkrs%JDTKUWm3&X!og0oBn`weVNpmZ0hH zZ7Uj>H!H_smDjeV|F4b@uhQ;rqd(wzsAZS#U(^U&_ zOB9T@LcbUE&Vo5C=q~sh_`Ods3({c0>^qJU{5lFiiC~V(xN^v_azUS|s~lMNfmkjM zoH z>Tz=q9@jo8=riC)1TRIviv`bxXI)=`hdWC537va)j|n~qzwZkMoA-z=fFeZbGoeR; zu8-&foy(CpJ<=QSN3i@ZSg(n~&Ip|gK)Dgz7lJ|sb4fW$@EQnB5X`x^nu6CMLUja# z#gQg>GH{j`yB!OWjV;7sCj!eB%sIABf;YoMj^GvG=`EP4?I)NMcY_2s04@-m3;cj! zro31%&u5yTRSl8K&TyD44r}2sO|Tn*v9K{GJ_LQfV9vZeCb%m6t`z(b=<5V?Y2_8c z8}QsOxCWl@5>Sn9IZn@-x)TD>0_6qH3(dqzS}A7F+>%u zfo+3Yw8k3ALGABrtSBea7PZ!@8P^RFD+z#i8JTgddXDBOcp_jO1Q=dhlj zGy7vOobRIdg=z|%-35^T3Q01O*(uK4T&fp9!FmnexT~JtNIM|B^63eS7_P91#ANk=bi*wI%*p#dqK8KM} zpJX)+Pb_C0dk%x06RO7Za80PKN}soSgw&~xT!pMu{6baPH_mu<;d!f$Gpn{I`~~Y} z1KS@CZnhr8DMxL$z*|sbwRj7JjZ^2gV8}F6ZyZb9YK1zpny7|btrQoBlB-n>4v>mL z$BoWdeXH%dfQ7eQ@50+|k{?sYNbb`VGm-jWqma(kQPU$g*^92L4?>SgLKfrh$UD#<&EpIa|+%s{P)z2BX6R$$|Mo>by6voeuCC_%KuXdw1@(W5| zy3k&NdlSK|%sJ^pUXDjP2qe+^O@uxY`xQ(lo$?-uy+SkaNbF09-2^@A81?qwjyf7O zYUfNJo6I5A~mCtl_sj81G$e zXRPl2{PN4EeuQnL=d8(raqPnDuOC(Sbrebc^#gxlsNXN(>*uz?hM@CoLT(R4Rj%>d zcNgp=VN>rrGmEt>n|R3H}DLh??b#E#Y&;}FaZIu zvqaBS1Y0S%IU=-P@I?gnvS6;W>=oQ5K>hn72UVbPI6)bYBcQJY4+Z_IV6F=ND0nHJ zHw3>6ekXqP%URMO!R!lE5)4*-W)XRKBygP2pTrZx5L9Du*bb%?akv+P*eND`2?Xg{ z2DmBc&4m6qFeeYl^B9D75KNWJu7WRu-c#@<@axrh@UvlX(Dhgd{G9w=AoQ8wc}y^u_tyyC5B_I~`@zF(ShkA; zCzu%#20Rdvcw6xIpz8q>=ra(29wY&0AOO@Kn%@B)Tkv>w`fIFxu#2pxbC5AKJ;iyx zs&UDR&g2M}ns=PuZVb$-njxoxeWK9&g3h*x^l~-g61vCp)jar)oe$w>gzI^5ofEEy zaT_nY=Mch1lEPV%94LuiA(tuSpbEQ;fdZqTXYIk&4~Z=iE*FIVgIGF&bA--7A0r0; zB=GC?ZA9%(RRVFbSqOBeI4^_qUhy_rt%Y;n*AO4BgVHy%{3mhdK$x2X+0xL25<@Ea zxlfP$XTV=w=nRH)bmaNmp(UF**&fAfI-vx)Ll@ZQp&($l|=l<^2h^?8UX&xm;qer7lqjb1mCSCHHgEwD?`y8?D<=2C5$ss+Umdu3*yQS&)wf! zjs2ZD@oLV`XqP9e(?6sCjeW$|@px8E;$xpW$j2EK`wJcy)CfMlSFhsXbR;P2SG3&0 z3Fpei-t8pm#siLX!8XwIl~cp+ffd&(DG!5?R+rA2w=867&5 zR~;7NnHppdaK?U*cd8q96a`Lt>xLb8top&SliUgTo^-no5|=o1yADiR2mJW;evP-+ zO)wtbo;>Ie0QVOLnxJ~GK7Zg+u-(~6ZciF2 zOd8!XOZ7Cgf~X0e{*~;zcmvv|Q2R}+ZkB}E58|%)i(#Nf7?@^rXe+r_!DhTfiiOa#cs%R`z*t8Bs;m6?V+ns*GgL!H5u)k6_>3O?(#5q51X z+#bZk6^S%^b!~$p?Y~(`Xqbr`4XO9q$bFw4z`}pX*N&%d4XaW_N&@7*zq|!oc8=XT@ zG>m}5c$7UIV_@IjZZ*;7uX%Fax7Bnr z9a$L|E7&Veq}AB$i>_Gz;6yS_BL5kieUbLYa_irx-$ShonkM)eEBx@~CemtnlKFtC zt*=Y+(1j*R?`VD1=Pgc$KMZg5M??~D8XI$aA)odB+&c8YH_eLC^8XnBy{~c)9a`aM ztmp-~wYAJWKAA~)b$fmJU6-tUdG!cpL!I1v&6I0;xL*UzA}_e>x%c>@X$z{zTy)mtH@!rG=2bb zJ~}Z=XLl<@^4puIt#2lv@rVM4(dMR9owHjI5gxzD_%b7Vv-<7@S-!~BWhl&lM=fvq zvsfm)<**6co$uhg=`sM9RG6(l1I5eWt()4goQRKP6rf~;seSnA<18G;`6{4z8JdID z5m>}zvhAqMz`XxnHV}Rbea*ciD4o?%Wv0`ieFSZ|0LT(inbmb=U4@|$$9P~S^Sbg^ zq%xblPF_#1Q2=wGGJT8a;$t`YHDctwo>WGX{Si7pewChhz>Kc!xFgoDQVYAKT|2f* zM53(*t#c-g&Ws2%!UM8nRJ&LMBSMT%GcmS9Q(+r)KoOZe%>c}dpmY43X@1&S^Kp>Qa$jU0rhr#a2&blo*jNI;a|14!WXV(Fu~I=jdf>DMC0QU1c7y5*Y>#Hgv&{Y{=>P+#RP zaYcH*NU`(%(LL^3-~MNLv;11Bc%%Q+K&mV{9kXuFknd~*JM@36U80tx+M|NfR}O#w zkus;Hg465_cbQ?lKP>$(H@M-10e`38diQJohTo@-t{;@m@tZg;J-xTz*lD0Y<4yzp z3rAO6LH8GLn{m@)jbZ8Of4P+o>OJpIpZi&AK-PeXOG<~|W;-z`tkhIzvP$352{gs41iNo(_ z)ji#=Zakri((QzhSDm;YuhI5y8B3cu!}VDUo~`NjG(%0j*HuM5nrYvr^A%QR+O-tU zSE%N}lzW>0Z5%xZrWfO;56rTcR5qhL?k2Vii3_rW!&@LXaw_?Es1&r?Sng~W5`Uk1 zJ|`fmJot?1IoQ-Lcb4}X7OXNNEXx?GaI1RN^4+OD)tWm4t9e!}G{Ox}Xe)c7)5!MB zZewpVk}=WZh}R8kacs;s>ooy-%NKM~T8 zaQ<3wJ|gg=;C^Z+LNMM|=inoD3zFCg;WO1-v)3v)z+F^W2Ro@Xr{J{G4M^+|?Gx5X zplhWY(0Q_fo3!GZ3s+seyYo(yM0-ft~P|Ej(=@TWi#S z&RGksQ3K3lw>go@xNL$Ptx*G93amA1fM-HZf$$u}^8tN)0UVMLm{zF)<}UmR!gLG) zXpI`sd7FXOr~#&;xYnovt^@x0!v7kcTB8Q^b>P<;HNaO~unSKUB;ZxSFZ-!)JK9l3 zOJ#P#6w^CkYY1^nLnC-^w0YVF=0v zhn;br+Bc2dPv}el542!}vN-x0CJrs(foC_+;T{MoBSxo>qZcJSyWx2)F?v%pdX5~C zpkF;l4!`VB9uzrifRBh@eh6A)AAW;@uaHj7Sz3M-52t_wkXi=7IwY1DLN5YSQ;_sd zz!}8J>?7bujNSoFN1-#7cM-$y4bb}teID>2p;v`pcfL5dk>y-xq6g{*mkOOl$4!g? z(BW`AF7!H}uO?>fVV)N{X9Zp%hMfM8^QK^~Q=JmL9GG`xz^_|huB?xQhoD*zWI-@f zHX11iB@WT@9d(HzlywOsMI4EO#;hj~D|5Zk447kt-lRhgzkzBmggq zf?9)Vf;eE$p+oCZfzGdZp?GFFcuIIC18d!?$^bCiD+t|B48L>W_arfR==TfZ;URV0 z+oBgwSXllL2gZn(pV0Hm@Ej$WRbf1FsP`avJBK92Awl{Da7#0 zuZG8tktZ86+6d2T@bI)T^7I5xj>gFUp|IqNLn-isf)jzK3T^_th!~X;-{m@?KLY#$ zF#>Ca0QBxYmPDANq$6@$K|d)xjPzNdvtm2%rUf4OX@D7YT* zabo!7l(^nG$QZ+XFZAKaYMi#AR|!ey-6!N9fz*c)$CHZJBy`>GSiH7Ok6KZZ#i0;P z_X@5F&%*`x1fC|Cg?Xi5X5&`DClpRAiB`qm_{F$efCmZJZ3pWprkd?Ybv&7?zILIR zU<}^0ZYb%*?S)A)_M|n$4x*-JL6SG1J(zd(lb5Gtl(j`V0vI_!#j@5#bfS;;> zbzc@d4ME=`bYg1S(J$rbJ}%lG{gU6J11xZJP%p)Yj|;8``iB+pCxTN!KVJb;@0xzu zpnX>XbM1|E7GB*m7VJb#?(c?$1%s_4nNW>5Vgy$M=3*V`#7Tk^f$IroV7hxOa@aWf zTF-S>rsW=I7lt=s`RJr@Z*5qMk$tQ9>(5L-~To;13<3rxR{ z3!QkW;6&gxUhH_?4Q4OF zp{!*3`7FS?8;?(5F)&+7dPoEwBe*f}gbMf}!K^{cDqs#}=$B<{ZUwwhFy*tAb^Dsw zC&l4DIItCDBSymx3Ned_R*w<9N9esl=W!(DA$~{jK;YvQ@cV)Xfqw2!>~@?NOEDZS zRlrvTPXhg?3iwyS4}q@78wj{2aG1Y05^2CO6>x&!KA`I{3HTX_+M161FNK9ouCGaM zA()L|+X}d&;B3&@5|cj>SZnJ5*9F#t72pQI)Po}rBc#=LfN3TPoslkcSFp?!OhNN1 z;6;KdbV&ugLNEj18FIeBHVWpuf1v{2CO8{(bT=ISXkOK&k6mZVYc+~AT9jAKiS|sr z$DV4mB?ndde1pP&5wp_(vy<ADW}YXfA2AUK!dXnmBc=+>Um)=cmSR7iJ(a3lWSS zN=sd#H+l23fzfq^UPbnv^yd3~bv3{W1m-a1uv-UnmKcRx8z~mVRf%!_;rsX6Ro%=5 zf3{{Y+4%X=(T`Z@1BfL}<0{Z65=;8lSDvUkjiENon-txP0HB2p3`~aC zLE|i9$)Qmd=*7g6TPrHi*APq2XUAB*l7*HzL)GNLcChL^ z$WBl_FS`7V0(}bhXkieNP??p#f zr#VBNeNxqEkc|ZIId9LBH8)l5A}e#*%bL^N73v(7sw(9%YJZaRkcf$DU>-C|a&J;s zkUB#>M=IOi$8>xzk-AX*Mrtrh{$zFR8rCzihuFc+pfuHE2qez}JzI0n9fEiaNK>mo z#zEj^2XF(@8PI?)4?&jIQN3EDTizhw4s}sCHCOxUKA{*_?zj#zO^?Lb5v`5WYc_xaL~# z?rKM#L7?ycei3Cd5lLySuWmx%l`km$2V<+J?1t}=bP!{i^q*9Doo2SuhBn3Rhxk?^4bXetNws}hq`f( z^Wts1+FKtDy?Laa!~49sP@u3-5K(@%gTJTQD7&8#N2M}7nUAWJ-E!9btX7kq-kmim za8DOnJU);nf-TTn3&z8%j|YF>0e2I61bBK1#v+qf=N>%UKpP_TGkA^>ydTdgf>~3~ zV57>&EKL8b8_|6XJp>Uo#r*xi;Ev>VzU%-4f`dJ*L){*!moe66m4T)p}?d zrT7vqV511Wgc|s0mz5h!BGgn;x2r{<8n}$(iYrgw*!ljnNCOAZCQiN z7(Z^rD98WPb=PM*n=a45sdmY&-^;Gc;*l~t#xJj+z;m|Du6ygl&^o9#lb_DLYgj}J zn{GF873Y=a6?wjzZr8$6+J658PlK8ERR7o|e98E+_rtnQ7k<8ZRanU{$*p|l9i(KG z99`alsf&u*QG26~sxI%Ke++(h*ePxu#Ob*bU^YVRACn#d{*i*4fX=Qm=^U@IUrsy< z&zanOMoS4Sj3@C-Fg+=FF7OM2rvmHg4)9b$07ryghDe+iTmqgS1iu5Ucanksey2Jz z$Bt^lKslYxa55o7Zz4l^r|Bki@^D`Od8pJ_D7X*@P(5m=wc*f9PYOuKR!<6mm;R2B zSNcKSInt>fo@>{{$Gujp@4@Qq=%;?1Yqu~asLXk2^$_L0^AOxTH4|27sc)KRr=u%$ znvQq)d48W~&vE7H=^7V;XABro$&g}VNu=IhB0b&xq)YEsCro;@`qU5U#p?NlV6V*H zo{Q!7+kPhdb!em=i7M}qSzVn`b{%IzlnPj4uX1KZsi&6MxX~+0{kX)QfbFwIOHs$| zP&@H(W>r^}mf7`;aazAVXBoO8trFFsW$Y564(;t#x?KS}wmd(#40Yf>l=y6~f_-kd z!Al>1BY7%vxk%sl)!W?Ve_OpZ=C)Vp-5Q-e7nj>p!vfFZvJVZ%^+aaLm~BF=Ny6J!xC)>xP0BLxftq z&E9?ML-QRXvR&ULmiUr=BOneh$6NQsdZ$Ko-3R=spK7whuIV0!;;0*b(72+k8-C!m zz`7y?<~OY?LSQPd>xvNgQD9vW0uP0p{`hg%P6D381V0QOe&nQcxHd^}eaN4tCy?R5 zn*|q$13rGoJ(4djMY-{0jJW9|eN$1uX`CDD)BVBnhStMY>?F3G0dm zJSo7sqH)8r0~S323|t*p*E+y7lf`op=v;VW0NlIAW{mhEQm?0BfmbmBLT`=dCBa;n z*3+lp;T`}Be(0Axrk)OkrMnuq%Z^4pIer)F$*pSXF8fZSgF3eh)#X+d_Btxw0jm4! znx*h{5dWnff8Ab=6CW~nLsf#Z79f01D%=OmTc;StAe9Ga<0&<1w>>hBgRDr@v%Ud6 zhCdP$_4{tD)(=uu_ShpixY`3xo7B=hy0SjF$8H%n6}b_Edf7L)uT88|p{l)sc3`J! z`v#(%uV%l2sy)L{&%c58gUa3C3g=hq*c;FTA&FNq&@}*hoqq@6QtIV<(My|Ecjk6cqLf33+B8v@7tg^PLSxuKj2dD!p+gg0%JTc?zNi* zxz~cz*T-ey^))>ICWZtWJu4v%PXJ$?G(0iH((vfFC=E|G>C*7rODwY_THS*EZ5W<_ zi`kHB+Ct47Uz5F;n0+Of6Q0@c*zx`xULHkDbX5Jo#~Ib}7#JdI`C8|?=W+bYVFKavNev zL7$?&M(rfyZlvu;-KyO%B855HyHb~6R8$Y%%F@H=!p^u_FwT0sf zOXD;P-+0@Cf>K;MbkqLcEdLLTD9kT)7}2!QXx>-BGz-5fzCA2exQC^R-owHvGzS@~Xm#%Yibbvw3nW*J zcSndH)Rz%#94GU=|KvQK#+> zO3TvP$XKcTGa{|Ly36ck#{NmAnsY0BI&m&Xm-0P(qO}g~(JoF?{qxCx#fhwAyL5mkTi;JmT8)@t0~p?Mfa4=xx3Fn-iH$DhnS?& zz!l=zp<}yy?sV`B3Gen#RpX3ZMJ;O+6rzrN=L)VgxRXv;;l%vG7?Z285!Tla+&uQO zf}D=Md*9XDkv@D(Q6a|Fd83C7%Nvv4VNCw`qQcSR#-`_wFC9^sK6cFb()_~o0-jbe zrg%bTdj6Qw!cilJq!;Cl811u+P0cGUKw8FRW@IYeFD*F{l&?0Qu^V7+xk_uJ2HvB} zSZJ)m;g=Z5e1@}UqSZ$q+nLpC^9957z^|qwNUs8E0{&r?sO(SdI$@Hc3Q6AW3W{`x z;$xzzgI~O~nXJsibZy|>Q%O2!J3CdNvsL%CrEEIUHtO>P*b)+R$xQd0ffpm@_>^w8 zt2KyqeTSMWT|9u#tA7E2nI}hEp^pXbESM9~cL`1dy{}+?g7*t%<1$<@brHr0&IKMP znBDC%jZt&<0+=NZ`FJi9{5t}BTJSXB=LA=QplyQj@OGIIY3@9IUFaMK91zUD|5;*m z-DtkU&$n>%Gs4#n%nc_5FzeWK1Q9cjVd4eT+CVV*86f%Hc(OO?>;B{j{SiF#iP6oe z0si5{=pyk|P9nx@%>;I6wFfxl0P7VXIFN~F?=XNyz^@6uQ`K0HayRu;JE2Mq&?%5D z3nNX8lgdKuMry~sW~4d+CN-{JP-IPAGATjwAd^^<*MT@t4XR{UQ`!AYml3K4)enje zk|~j%YD?#!c52L7JJ}vrIB{H>w;-#N_Z$1w)g9PdfApMP%j5+PEQj&xw{!NL=BS(2 z?*GP&@{WOa5Kdd~#anQ=!V@wP?ipmqFGz(A@ry9gAJS&MI(N{Fcp~4%)jD=isOu`I zr}TtrP^b}_x)DC9fJ~o&4t5wAFpCh#H_ZVQnIXP;R{yYyPF(BAVo$5}6JV!+Z1J`R zZC7Vt3g;{K3I7*xMF|7okf9|!C0yBf(p-Q9U&5G!J_#(D6(!7tLn0+qeNDKs@uZ1C zz`i(ys~RCe5fJG$&%#q*rm6K|5GXp^>)^_IEw?_m@A*)$6y&H8tiFs`01&a z@QbikbaI(oZmIHnmYjgfET?6V$N? zySl3u-c)nlw{39#@A|flGI973`nH%zntk4G8Qe9+QJk1K72g5$sNOkm_cQ``13Ddx zZ^QabLiyQ#L8|OayIQP0t=sf&In({jt~2n+>4s}I{T+r8VyIWXv=eS|i<+Q9FW6DH zP^g~M9&!Oon4vZOtm1>utNex#$Y6R6(KVPtC7@gIWi+0Pf_RLr@EU|sns~7E@ zn%)L@@Zg~%N=pWh9ha9slK)G`IR(G{uQxq>CWeyE%JsB>2^blO;+_hh_E z{KgIj+>#otI(!3$jgS)RogW+pJ64IOe1mb*D=23BblKWL>KS#IRE{0P^yeRZ71Rr= z>QzuVl8)5814!Mdia-sCL1AV0lSM4}=F-Ysy|fZSg_jHj!6e%H(7m7W5$Kz^{tscc BSycc4 delta 120116 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 Aj{pDw From 19baf30e71a821d9146fe8bc98fd29c4fbc3577e Mon Sep 17 00:00:00 2001 From: lianggao Date: Tue, 8 Nov 2016 14:15:34 +0800 Subject: [PATCH 15/22] Update examples and change code based on code review 1. Update example sketches; 2. Check the NULL pointers; 3. Add the writeValue to the template; 4. Delete reference in callback parameters; --- .../central/led_control/led_control.ino | 127 +++++++++++++ .../peripheral_explorer.ino | 175 ++++++++++++++++++ libraries/BLE/examples/central/scan/scan.ino | 70 +++++++ .../central/scan_callback/scan_callback.ino | 72 +++++++ .../sensortag_button/sensortag_button.ino | 121 ++++++++++++ libraries/BLE/examples/peripheral/led/led.ino | 84 +++++++++ .../peripheral/led_callback/led_callback.ino | 90 +++++++++ .../examples/{ => test}/central/central.ino | 0 .../{ => test}/notification/notification.ino | 0 .../notifycentral/notifycentral.ino | 0 .../BLE/examples/test/{ => test}/test.ino | 0 libraries/BLE/src/BLECharacteristic.h | 2 +- libraries/BLE/src/BLECommon.h | 2 + libraries/BLE/src/BLEDevice.h | 1 + libraries/BLE/src/BLETypedCharacteristic.h | 17 ++ .../BLE/src/internal/BLEDeviceManager.cpp | 6 + .../BLE/src/internal/BLEProfileManager.cpp | 1 + 17 files changed, 767 insertions(+), 1 deletion(-) create mode 100644 libraries/BLE/examples/central/led_control/led_control.ino create mode 100644 libraries/BLE/examples/central/peripheral_explorer/peripheral_explorer.ino create mode 100644 libraries/BLE/examples/central/scan/scan.ino create mode 100644 libraries/BLE/examples/central/scan_callback/scan_callback.ino create mode 100644 libraries/BLE/examples/central/sensortag_button/sensortag_button.ino create mode 100644 libraries/BLE/examples/peripheral/led/led.ino create mode 100644 libraries/BLE/examples/peripheral/led_callback/led_callback.ino rename libraries/BLE/examples/{ => test}/central/central.ino (100%) rename libraries/BLE/examples/{ => test}/notification/notification.ino (100%) rename libraries/BLE/examples/{ => test}/notifycentral/notifycentral.ino (100%) rename libraries/BLE/examples/test/{ => test}/test.ino (100%) diff --git a/libraries/BLE/examples/central/led_control/led_control.ino b/libraries/BLE/examples/central/led_control/led_control.ino new file mode 100644 index 00000000..7d087b5d --- /dev/null +++ b/libraries/BLE/examples/central/led_control/led_control.ino @@ -0,0 +1,127 @@ +/* + Arduino BLE Central LED Control example + Copyright (c) 2016 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#include + +// variables for button +const int buttonPin = 2; +int oldButtonState = LOW; + + +void setup() { + Serial.begin(9600); + + // configure the button pin as input + pinMode(buttonPin, INPUT); + + // initialize the BLE hardware + BLE.begin(); + + Serial.println("BLE Central - LED control"); + + // start scanning for peripherals + BLE.startScanning(); +} + +void loop() { + // check if a peripheral has been discovered + BLEDevice peripheral = BLE.available(); + + if (peripheral) { + // discovered a peripheral, print out address, local name, and advertised service + Serial.print("Found "); + Serial.print(peripheral.address()); + Serial.print(" '"); + Serial.print(peripheral.localName()); + Serial.print("' "); + Serial.print(peripheral.advertisedServiceUuid()); + Serial.println(); + + // see if peripheral is advertising the LED service + if (peripheral.advertisedServiceUuid() == "19b10000-e8f2-537e-4f6c-d104768a1214") { + // stop scanning + BLE.stopScanning(); + + controlLed(peripheral); + + // peripheral disconnected, start scanning again + BLE.startScanning(); + } + } +} + +void controlLed(BLEDevice peripheral) { + // connect to the peripheral + Serial.println("Connecting ..."); + + if (peripheral.connect()) { + Serial.println("Connected"); + } else { + Serial.println("Failed to connect!"); + return; + } + + // discover peripheral attributes + Serial.println("Discovering attributes ..."); + if (peripheral.discoverAttributes()) { + Serial.println("Attributes discovered"); + } else { + Serial.println("Attribute discovery failed!"); + peripheral.disconnect(); + return; + } + + // retrieve the LED characteristic + BLECharacteristic ledCharacteristic = peripheral.characteristic("19b10001-e8f2-537e-4f6c-d104768a1214"); + + if (!ledCharacteristic) { + Serial.println("Peripheral does not have LED characteristic!"); + peripheral.disconnect(); + return; + } else if (!ledCharacteristic.canWrite()) { + Serial.println("Peripheral does not have a writable LED characteristic!"); + peripheral.disconnect(); + return; + } + + while (peripheral.connected()) { + // while the peripheral is connection + + // read the button pin + int buttonState = digitalRead(buttonPin); + + if (oldButtonState != buttonState) { + // button changed + oldButtonState = buttonState; + + if (buttonState) { + Serial.println("button pressed"); + + // button is pressed, write 0x01 to turn the LED on + ledCharacteristic.writeByte(0x01); + } else { + Serial.println("button released"); + + // button is released, write 0x00 to turn the LED of + ledCharacteristic.writeByte(0x00); + } + } + } +} diff --git a/libraries/BLE/examples/central/peripheral_explorer/peripheral_explorer.ino b/libraries/BLE/examples/central/peripheral_explorer/peripheral_explorer.ino new file mode 100644 index 00000000..41f11ea5 --- /dev/null +++ b/libraries/BLE/examples/central/peripheral_explorer/peripheral_explorer.ino @@ -0,0 +1,175 @@ +/* + Arduino BLE Central peripheral explorer example + Copyright (c) 2016 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include + +void setup() { + Serial.begin(9600); + + // initialize the BLE hardware + BLE.begin(); + + Serial.println("BLE Central - Peripheral Explorer"); + + // start scanning for peripherals + BLE.startScanning(); +} + +void loop() { + // check if a peripheral has been discovered + BLEDevice peripheral = BLE.available(); + + if (peripheral) { + // discovered a peripheral, print out address, local name, and advertised service + Serial.print("Found "); + Serial.print(peripheral.address()); + Serial.print(" '"); + Serial.print(peripheral.localName()); + Serial.print("' "); + Serial.print(peripheral.advertisedServiceUuid()); + Serial.println(); + + // see if peripheral is a SensorTag + if (peripheral.localName() == "SensorTag") { + // stop scanning + BLE.stopScanning(); + + explorerPeripheral(peripheral); + + // peripheral disconnected, we are done + while (1) { + // do nothing + } + } + } +} + +void explorerPeripheral(BLEDevice peripheral) { + // connect to the peripheral + Serial.println("Connecting ..."); + + if (peripheral.connect()) { + Serial.println("Connected"); + } else { + Serial.println("Failed to connect!"); + return; + } + + // discover peripheral attributes + Serial.println("Discovering attributes ..."); + if (peripheral.discoverAttributes()) { + Serial.println("Attributes discovered"); + } else { + Serial.println("Attribute discovery failed!"); + peripheral.disconnect(); + return; + } + + // read and print device name of peripheral + Serial.println(); + Serial.print("Device name: "); + Serial.println(peripheral.deviceName()); + + // read and print appearance of peripheral + Serial.print("Appearance: "); + Serial.println(peripheral.appearance()); + Serial.println(); + + // loop the services of the peripheral and explore each + for (int i = 0; i < peripheral.serviceCount(); i++) { + BLEService service = peripheral.service(i); + + exploreService(service); + } + + Serial.println(); + + // we are done exploring, disconnect + Serial.println("Disconnecting ..."); + peripheral.disconnect(); + Serial.println("Disconnected"); +} + +void exploreService(BLEService service) { + // print the UUID of the service + Serial.print("Service "); + Serial.println(service.uuid()); + + // loop the characteristics of the service and explore each + for (int i = 0; i < service.characteristicCount(); i++) { + BLECharacteristic characteristic = service.characteristic(i); + + exploreCharacteristic(characteristic); + } +} + +void exploreCharacteristic(BLECharacteristic characteristic) { + // print the UUID and properies of the characteristic + Serial.print("\tCharacteristic "); + Serial.print(characteristic.uuid()); + Serial.print(", properties 0x"); + Serial.print(characteristic.properties()); + + // check if the characteristic is readable + if (characteristic.canRead()) { + // read the characteristic value + characteristic.read(); + + // print out the value of the characteristic + Serial.print(", value 0x"); + printData(characteristic.value(), characteristic.valueLength()); + } + + Serial.println(); + + // loop the descriptors of the characteristic and explore each + for (int i = 0; i < characteristic.descriptorCount(); i++) { + BLEDescriptor descriptor = characteristic.descriptor(i); + + exploreDescriptor(descriptor); + } +} + +void exploreDescriptor(BLEDescriptor descriptor) { + // print the UUID of the descriptor + Serial.print("\t\tDescriptor "); + Serial.print(descriptor.uuid()); + + // read the descriptor value + descriptor.read(); + + // print out the value of the descriptor + Serial.print(", value 0x"); + printData(descriptor.value(), descriptor.valueLength()); + + Serial.println(); +} + +void printData(const unsigned char data[], int length) { + for (int i = 0; i < length; i++) { + unsigned char b = data[i]; + + if (b < 16) { + Serial.print("0"); + } + + Serial.print(b, HEX); + } +} + diff --git a/libraries/BLE/examples/central/scan/scan.ino b/libraries/BLE/examples/central/scan/scan.ino new file mode 100644 index 00000000..5bad0456 --- /dev/null +++ b/libraries/BLE/examples/central/scan/scan.ino @@ -0,0 +1,70 @@ +/* + Arduino BLE Central scan example + Copyright (c) 2016 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include + +void setup() { + Serial.begin(9600); + + // initialize the BLE hardware + BLE.begin(); + + Serial.println("BLE Central scan"); + + // start scanning for peripheral + BLE.startScanning(); +} + +void loop() { + // check if a peripheral has been discovered + BLEDevice peripheral = BLE.available(); + + if (peripheral) { + // discovered a peripheral + Serial.println("Discovered a peripheral"); + Serial.println("-----------------------"); + + // print address + Serial.print("Address: "); + Serial.println(peripheral.address()); + + // print the local name, if present + if (peripheral.hasLocalName()) { + Serial.print("Local Name: "); + Serial.println(peripheral.localName()); + } + + // print the advertised service UUID's, if present + if (peripheral.hasAdvertisedServiceUuid()) { + Serial.print("Service UUID's: "); + for (int i = 0; i < peripheral.advertisedServiceUuidCount(); i++) { + Serial.print(peripheral.advertisedServiceUuid(i)); + Serial.print(" "); + } + Serial.println(); + } + + // print the RSSI + Serial.print("RSSI: "); + Serial.println(peripheral.rssi()); + + Serial.println(); + } +} + diff --git a/libraries/BLE/examples/central/scan_callback/scan_callback.ino b/libraries/BLE/examples/central/scan_callback/scan_callback.ino new file mode 100644 index 00000000..983d3f40 --- /dev/null +++ b/libraries/BLE/examples/central/scan_callback/scan_callback.ino @@ -0,0 +1,72 @@ +/* + Arduino BLE Central scan callback example + Copyright (c) 2016 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include + +void setup() { + Serial.begin(9600); + + // initialize the BLE hardware + BLE.begin(); + + Serial.println("BLE Central scan callback"); + + // set the discovered event handle + BLE.setEventHandler(BLEDiscovered, bleCentralDiscoverHandler); + + // start scanning for peripherals with duplicates + BLE.startScanningWithDuplicates(); +} + +void loop() { + // poll the central for events + BLE.poll(); +} + +void bleCentralDiscoverHandler(BLEDevice peripheral) { + // discovered a peripheral + Serial.println("Discovered a peripheral"); + Serial.println("-----------------------"); + + // print address + Serial.print("Address: "); + Serial.println(peripheral.address()); + + // print the local name, if present + if (peripheral.hasLocalName()) { + Serial.print("Local Name: "); + Serial.println(peripheral.localName()); + } + + // print the advertised service UUID's, if present + if (peripheral.hasAdvertisedServiceUuid()) { + Serial.print("Service UUID's: "); + for (int i = 0; i < peripheral.advertisedServiceUuidCount(); i++) { + Serial.print(peripheral.advertisedServiceUuid(i)); + Serial.print(" "); + } + Serial.println(); + } + + // print the RSSI + Serial.print("RSSI: "); + Serial.println(peripheral.rssi()); + + Serial.println(); +} diff --git a/libraries/BLE/examples/central/sensortag_button/sensortag_button.ino b/libraries/BLE/examples/central/sensortag_button/sensortag_button.ino new file mode 100644 index 00000000..e72f2c99 --- /dev/null +++ b/libraries/BLE/examples/central/sensortag_button/sensortag_button.ino @@ -0,0 +1,121 @@ +/* + Arduino BLE Central SensorTag button example + Copyright (c) 2016 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include + +void setup() { + Serial.begin(9600); + + // initialize the BLE hardware + BLE.begin(); + + Serial.println("BLE Central - SensorTag button"); + + // start scanning for peripheral + BLE.startScanning(); +} + +void loop() { + // check if a peripheral has been discovered + BLEDevice peripheral = BLE.available(); + + if (peripheral) { + // discovered a peripheral, print out address, local name, and advertised service + Serial.print("Found "); + Serial.print(peripheral.address()); + Serial.print(" '"); + Serial.print(peripheral.localName()); + Serial.print("' "); + Serial.print(peripheral.advertisedServiceUuid()); + Serial.println(); + + // see if peripheral is a SensorTag + if (peripheral.localName() == "SensorTag") { + // stop scanning + BLE.stopScanning(); + + monitorSensorTagButtons(peripheral); + + // peripheral disconnected, start scanning again + BLE.startScanning(); + } + } +} + +void monitorSensorTagButtons(BLEDevice peripheral) { + // connect to the peripheral + Serial.println("Connecting ..."); + if (peripheral.connect()) { + Serial.println("Connected"); + } else { + Serial.println("Failed to connect!"); + return; + } + + // discover peripheral attributes + Serial.println("Discovering attributes ..."); + if (peripheral.discoverAttributes()) { + Serial.println("Attributes discovered"); + } else { + Serial.println("Attribute discovery failed!"); + peripheral.disconnect(); + return; + } + + // retrieve the simple key characteristic + BLECharacteristic simpleKeyCharacteristic = peripheral.characteristic("ffe1"); + + // subscribe to the simple key characteristic + Serial.println("Subscribing to simple key characteristic ..."); + if (!simpleKeyCharacteristic) { + Serial.println("no simple key characteristic found!"); + peripheral.disconnect(); + return; + } else if (!simpleKeyCharacteristic.canSubscribe()) { + Serial.println("simple key characteristic is not subscribable!"); + peripheral.disconnect(); + return; + } else if (!simpleKeyCharacteristic.subscribe()) { + Serial.println("subscription failed!"); + peripheral.disconnect(); + return; + } else { + Serial.println("Subscribed"); + } + + while (peripheral.connected()) { + // while the peripheral is connected + + // check if the value of the simple key characteristic has been updated + if (simpleKeyCharacteristic.valueUpdated()) { + // yes, get the value, characteristic is 1 byte so use char value + int value = simpleKeyCharacteristic.charValue(); + + if (value & 0x01) { + // first bit corresponds to the right button + Serial.println("Right button pressed"); + } + + if (value & 0x02) { + // second bit corresponds to the left button + Serial.println("Left button pressed"); + } + } + } +} diff --git a/libraries/BLE/examples/peripheral/led/led.ino b/libraries/BLE/examples/peripheral/led/led.ino new file mode 100644 index 00000000..eb708300 --- /dev/null +++ b/libraries/BLE/examples/peripheral/led/led.ino @@ -0,0 +1,84 @@ +/* + Arduino BLE Peripheral LED example + Copyright (c) 2016 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include + +// LED pin +#define LED_PIN 13 + +// create service +BLEService ledService("19b10000e8f2537e4f6cd104768a1214"); + +// create switch characteristic +BLEByteCharacteristic switchCharacteristic("19b10001e8f2537e4f6cd104768a1214", BLERead | BLEWrite); + +BLEDescriptor switchDescriptor("2901", "switch"); + +void setup() { + Serial.begin(9600); + + // set LED pin to output mode + pinMode(LED_PIN, OUTPUT); + + // begin initialization + BLE.begin(); + Serial.println(BLE.address()); + + // set advertised local name and service UUID + BLE.setLocalName("LED"); + BLE.setAdvertisedServiceUuid(ledService.uuid()); + + switchCharacteristic.addDescriptor(switchDescriptor); + ledService.addCharacteristic(switchCharacteristic); + + // add service and characteristic + BLE.addService(ledService); + + BLE.startAdvertising(); + + Serial.println(F("BLE LED Peripheral")); +} + +void loop() { + BLEDevice central = BLE.central(); + + if (central) { + // central connected to peripheral + Serial.print(F("Connected to central: ")); + Serial.println(central.address()); + + while (central.connected()) { + // central still connected to peripheral + if (switchCharacteristic.written()) { + // central wrote new value to characteristic, update LED + if (switchCharacteristic.value()) { + Serial.println(F("LED on")); + digitalWrite(LED_PIN, HIGH); + } else { + Serial.println(F("LED off")); + digitalWrite(LED_PIN, LOW); + } + } + } + + // central disconnected + Serial.print(F("Disconnected from central: ")); + Serial.println(central.address()); + } +} diff --git a/libraries/BLE/examples/peripheral/led_callback/led_callback.ino b/libraries/BLE/examples/peripheral/led_callback/led_callback.ino new file mode 100644 index 00000000..f2207837 --- /dev/null +++ b/libraries/BLE/examples/peripheral/led_callback/led_callback.ino @@ -0,0 +1,90 @@ +/* + Arduino BLE Peripheral LED callback example + Copyright (c) 2016 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// Import libraries +#include + +// LED pin +#define LED_PIN 13 + +// create service +BLEService ledService("19b10000e8f2537e4f6cd104768a1214"); + +// create switch characteristic +BLECharCharacteristic switchCharacteristic("19b10001e8f2537e4f6cd104768a1214", BLERead | BLEWrite); + +void setup() { + Serial.begin(9600); + + // set LED pin to output mode + pinMode(LED_PIN, OUTPUT); + + // begin initialization + BLE.begin(); + + // set advertised local name and service UUID + BLE.setLocalName("LED"); + BLE.setAdvertisedServiceUuid(ledService.uuid()); + + ledService.addCharacteristic(switchCharacteristic); + + // add service + BLE.addService(ledService); + + // assign event handlers for connected, disconnected to peripheral + BLE.setEventHandler(BLEConnected, bleDeviceConnectHandler); + BLE.setEventHandler(BLEDisconnected, bleDeviceDisconnectHandler); + + // assign event handlers for characteristic + switchCharacteristic.setEventHandler(BLEWritten, switchCharacteristicWritten); + + BLE.startAdvertising(); + + Serial.println(F("BLE LED Peripheral")); +} + +void loop() { + // poll peripheral + BLE.poll(); +} + +void bleDeviceConnectHandler(BLEDevice central) { + // central connected event handler + Serial.print(F("Connected event, central: ")); + Serial.println(central.address()); +} + +void bleDeviceDisconnectHandler(BLEDevice central) { + // central disconnected event handler + Serial.print(F("Disconnected event, central: ")); + Serial.println(central.address()); +} + +void switchCharacteristicWritten(BLEDevice central, BLECharacteristic characteristic) { + // central wrote new value to characteristic, update LED + Serial.print(F("Characteristic event, writen: ")); + + if (switchCharacteristic.value()) { + Serial.println(F("LED on")); + digitalWrite(LED_PIN, HIGH); + } else { + Serial.println(F("LED off")); + digitalWrite(LED_PIN, LOW); + } +} diff --git a/libraries/BLE/examples/central/central.ino b/libraries/BLE/examples/test/central/central.ino similarity index 100% rename from libraries/BLE/examples/central/central.ino rename to libraries/BLE/examples/test/central/central.ino diff --git a/libraries/BLE/examples/notification/notification.ino b/libraries/BLE/examples/test/notification/notification.ino similarity index 100% rename from libraries/BLE/examples/notification/notification.ino rename to libraries/BLE/examples/test/notification/notification.ino diff --git a/libraries/BLE/examples/notifycentral/notifycentral.ino b/libraries/BLE/examples/test/notifycentral/notifycentral.ino similarity index 100% rename from libraries/BLE/examples/notifycentral/notifycentral.ino rename to libraries/BLE/examples/test/notifycentral/notifycentral.ino diff --git a/libraries/BLE/examples/test/test.ino b/libraries/BLE/examples/test/test/test.ino similarity index 100% rename from libraries/BLE/examples/test/test.ino rename to libraries/BLE/examples/test/test/test.ino diff --git a/libraries/BLE/src/BLECharacteristic.h b/libraries/BLE/src/BLECharacteristic.h index b2de4f83..0d70b21b 100644 --- a/libraries/BLE/src/BLECharacteristic.h +++ b/libraries/BLE/src/BLECharacteristic.h @@ -41,7 +41,7 @@ enum BLEProperty { BLEIndicate = 0x20 }; -typedef void (*BLECharacteristicEventHandler)(BLEDevice& bledev, BLECharacteristic& characteristic); +typedef void (*BLECharacteristicEventHandler)(BLEDevice bledev, BLECharacteristic characteristic); //#include "BLECharacteristicImp.h" diff --git a/libraries/BLE/src/BLECommon.h b/libraries/BLE/src/BLECommon.h index 9589467e..cfd90a79 100644 --- a/libraries/BLE/src/BLECommon.h +++ b/libraries/BLE/src/BLECommon.h @@ -120,6 +120,8 @@ extern "C" { #include "os/os.h" +extern void __assert_fail(void); + /// Define the structure for app typedef struct bt_uuid bt_uuid_t; typedef struct bt_uuid_16 bt_uuid_16_t; diff --git a/libraries/BLE/src/BLEDevice.h b/libraries/BLE/src/BLEDevice.h index d94736ff..845add70 100644 --- a/libraries/BLE/src/BLEDevice.h +++ b/libraries/BLE/src/BLEDevice.h @@ -28,6 +28,7 @@ enum BLEDeviceEvent { BLEConParamUpdate = 2, // Update the connection parameter // Connection update request in central // Connection parameter updated in peripheral + BLEDiscovered, // The scanned BLE device BLEDeviceLastEvent }; diff --git a/libraries/BLE/src/BLETypedCharacteristic.h b/libraries/BLE/src/BLETypedCharacteristic.h index 34df735e..b3dc23c1 100644 --- a/libraries/BLE/src/BLETypedCharacteristic.h +++ b/libraries/BLE/src/BLETypedCharacteristic.h @@ -52,6 +52,19 @@ template class BLETypedCharacteristic : public BLECharacteristic * @note none */ bool setValue(T value); + + /** + * @brief Update the characteristic value + * + * @param[in] value New value to set + * + * @return bool true - set value success, + * false - on error + * + * @note none + */ + bool writeValue(T value); + /** * @brief Get the value of the Characteristic * @@ -133,6 +146,10 @@ template bool BLETypedCharacteristic::setValue(T value) { return BLECharacteristic::setValue((unsigned char*)&value, sizeof(T)); } +template bool BLETypedCharacteristic::writeValue(T value) { + return BLECharacteristic::writeValue((unsigned char*)&value, sizeof(T)); +} + template T BLETypedCharacteristic::value() { T value; diff --git a/libraries/BLE/src/internal/BLEDeviceManager.cpp b/libraries/BLE/src/internal/BLEDeviceManager.cpp index 29e644de..a00d11eb 100644 --- a/libraries/BLE/src/internal/BLEDeviceManager.cpp +++ b/libraries/BLE/src/internal/BLEDeviceManager.cpp @@ -948,6 +948,7 @@ BLEDeviceManager* BLEDeviceManager::instance() if (_instance == NULL) { _instance = new BLEDeviceManager(); + BLE_LIB_ASSERT(_instance != NULL); } return _instance; } @@ -1202,6 +1203,11 @@ void BLEDeviceManager::handleDeviceFound(const bt_addr_le_t *addr, { pr_info(LOG_MODULE_BLE, "No buffer to store the ADV\n"); } + else if (NULL != _device_events[BLEDiscovered]) + { + BLEDevice tempdev = available(); + _device_events[BLEDiscovered](tempdev); + } } pr_debug(LOG_MODULE_BLE, "%s-%d: Done", __FUNCTION__, __LINE__); return; diff --git a/libraries/BLE/src/internal/BLEProfileManager.cpp b/libraries/BLE/src/internal/BLEProfileManager.cpp index eb641c74..a013f546 100644 --- a/libraries/BLE/src/internal/BLEProfileManager.cpp +++ b/libraries/BLE/src/internal/BLEProfileManager.cpp @@ -34,6 +34,7 @@ BLEProfileManager* BLEProfileManager::instance() if (NULL == _instance) { _instance = new BLEProfileManager(); + BLE_LIB_ASSERT(_instance != NULL); } pr_debug(LOG_MODULE_BLE, "%s-%d: %p", __FUNCTION__, __LINE__, _instance); return _instance; From 619599a4a9d866ccb36fdd3c4c192ed7b427f242 Mon Sep 17 00:00:00 2001 From: Calvin Park Date: Thu, 27 Oct 2016 15:57:39 -0700 Subject: [PATCH 16/22] Use our own version of copy.exe so that we can copy without cmd.exe --- platform.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platform.txt b/platform.txt index 42991e80..eea2379c 100644 --- a/platform.txt +++ b/platform.txt @@ -71,8 +71,8 @@ recipe.ar.pattern="{compiler.path}{compiler.ar.cmd}" {compiler.ar.flags} {compil recipe.c.combine.pattern="{compiler.path}{compiler.c.elf.cmd}" {compiler.c.elf.flags} "-T{build.variant.path}/{build.ldscript}" "-Wl,-Map,{build.path}/{build.project_name}.map" {compiler.c.elf.extra_flags} -o "{build.path}/{build.project_name}.elf" "-L{build.path}" "-L{build.variant.path}" -Wl,--whole-archive "-l{build.variant_system_lib}" -Wl,--no-whole-archive -Wl,--start-group "-l{build.variant_system_lib}" -lnsim -lc -lm -lgcc {object_files} "{build.path}/{archive_file}" ## Save output with debug symbols (.debug.elf file). Uncomment if you wish to use OpenOCD to debug. -#recipe.hooks.objcopy.preobjcopy.1.pattern=cp -f "{build.path}/{build.project_name}.elf" "{build.path}/../arduino101_sketch.debug.elf" -#recipe.hooks.objcopy.preobjcopy.1.pattern.windows=cmd /C copy /y "{build.path}\{build.project_name}.elf" "{build.path}\..\arduino101_sketch.debug.elf" +recipe.hooks.objcopy.preobjcopy.1.pattern=cp -f "{build.path}/{build.project_name}.elf" "{build.path}/../arduino101_sketch.debug.elf" +recipe.hooks.objcopy.preobjcopy.1.pattern.windows={runtime.tools.arduino101load.path}/arduino101load/arduino101copy.exe "{build.path}\{build.project_name}.elf" "{build.path}\..\arduino101_sketch.debug.elf" ## Create output (.bin file) recipe.objcopy.bin.pattern="{compiler.path}{compiler.elf2bin.cmd}" {compiler.elf2bin.flags} {compiler.elf2hex.extra_flags} "{build.path}/{build.project_name}.elf" "{build.path}/{build.project_name}.bin" From 297728d6ee168430561c2ad6204e5eb6884a888c Mon Sep 17 00:00:00 2001 From: Erik Nyquist Date: Tue, 8 Nov 2016 15:38:02 -0800 Subject: [PATCH 17/22] Recompile libarc32drv_arduino101.a --- variants/arduino_101/libarc32drv_arduino101.a | Bin 788268 -> 782484 bytes variants/libarc32drv_arduino101.a | Bin 0 -> 782484 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 variants/libarc32drv_arduino101.a diff --git a/variants/arduino_101/libarc32drv_arduino101.a b/variants/arduino_101/libarc32drv_arduino101.a index 52ececf233255bbc5794d6e4596296fa65374a0d..74a50048d52f2439556a9c08bfcd4129795adff6 100644 GIT binary patch delta 84520 zcmdqKcYGDq_V<5g&Y2Wa2rVI@CZV?^lu$#H7Nj>pN(dx$2pv@LP!$mrJmLZr1XPL& zf~dy^0@74aP!RBff{F^#n<#?6&u5=?yguLGz25LVzx(|0o7d}Pzh~{W*DkYXX6-$D zCa*q``t+%k1tmk$Yt>6nud^_znz!x&m+RmCx*m7AvbO#6mv_nKx|`qPwJz67*Z%hB z{W}FKcBkv#{`xI&S^wuh@4q+lVL6wT&!1gwp6h?>H`80vWzW2e-=+R8`+xl0CBAq4 z?QdWum;2xSyo}gB(BBmi{Cz*(6%qJljm#c8N~Sk= z$I7Su?NHyz*26ZJ3td~Pthw>jEL%1$t#+NX)SB49sj2C;YnLrsyI#$DzW((pStU0Y zD__nN+QD+UY*!W8{-`^lZ0@KB#!MJGZhXqv+|haCQU>OY%^fjxz_{vZscF>*<&7OK zV}5Z;`nFXrkUS)BWNylz>i2YPac>I$RBxY>J$4{}wQBk;(eh* zRVzFzYrwd1S-GQfeC?}km5t{jLh@azu<(?DSwlw+9baSM3q`AYqN=!Du0r^EB3-Vp ze05eOd&k-?S5=kV53Z@41Fo-J7u;0292%#!atm+=<(lB`${&IUD!&XKqKv>bLAeHO z&UU%dO;JW-XRbzsB3z_A7KvY0E(Lx|xrp!Av`OA}NYw>WR>qI8uW}KDLzL$t9H$(Q z)DJ5^i|{dJ%GN5kgw0Qt??ZT4`C-V9E6+xV&3Ccpe5>x}skQc4R#9KeIvwqe5x!^Y zOthZ!MWuJNe~Tx6>;+;<#K3eUsE9| zgt=U`eTmSSS2xD0=UWC@r)Zb!V>vaoK(e*McOHWN1zfI8zRMkp_&Tli_)_b|{B5tU zn^rTGz1qGRyLoQCZK0t9P;A>Z#n-uM{(E)!3tu(0Dy00cUHd6R z{67$TO(h4wMce^H^&u;+{Lto%dqeHa$B;N!rzK>bYuMh#&x0Pw?GyZh6*xymr{fj_ z)4{T)Z=T=2qT5>Fd$UtV^meE7Mf;6{z8jq+JA7)-~BG;9HOVcBs`Mc718u(222+7xmrgTEUrB%;g#{(=AW3 zRo2(OTSey*&W7ct^Uv#6w9MaK&TH1M=gX>B*{ZbpOt(}!^k)>_cAaLoFZ~~{&WZA5 z{g48(u~0%FQV|a7gx8SKgpO<8sHl?)Ee) z*gL`*IdoLsSl`M)MSUlF#rj_FRXBgwe_yjBB||6XjvY5NZ4{4I3WDPKhRrg9eK+m)Gr;QU6Zdm`~Mjfe%GR(2!7W#!4>+sZuQ!?DkE zWG2E&%E^#7QEmX{eUdsXXeY9s$ZUDf=3{-uvRu9~S(EH}VbXYNf!L?|7qu>$zFw(+ zjMEe+@d95)|77ccZ_JuF**CR7jI$W0>yv?-Ls8$R{uQ0?VJyL%|F$60j=RIk{g2N4@7$fl&6RR5SiU8LGvtXIPJSJi1;@gCa|ajn z^;=Qems|Zl`CyA(YV(jG?QCnC@6BP!6{?~ktFrN;;1i(y=8geQg4}dL@LJ1vXIPRI z?@Jt>Vn1#9x(u&j)%8su-rAmN`92>0VDuRp&@24!F6j^xmh?<@$JzYAh>MneGR!w@ zR0sQ9nD5Cf&?k{U{WV7Vw=NUBL?T6&h2~D(S0_zaxCvW2z?p-Nh^|wN`p< zUv|BUR<+HC$K*zaK7=aTu4nJ^aBcqR;rs2ox}xzAq|S)xkv%?doI&{5-a$BvyadVJQ1yn(~>d6DDw$=7xIP+!v-ze$(&A| zz2tjyPKq_bcY01kyPoAsdaMR^IzQIX%JI#AtgD@D`M!N@hPBj}JC|CE=BC)OmT&jm zE_S5lOL&~hnU4?0-!~s`Xt%d~SBxleUPF7Rk~Cx;QsFHy+#@xzTU&6m$i0>$!Q| zQ^OtHi;FFuWG_hYJ+Zh09Lp1nJKBdz`)VwyVtJ)myj8?EVo8jBt&H#CB^4Z}Am-V8 z>y{*2<9+*=RCM0PaX4W5Z{*S#>Hl4X$5(b~Ip@Vh_+94SY|km|8@058^HWimOC)

d>oJ>UtxsjHH^UxNhqH*?e? z!2Olmfk!FxE-_A-cM#l+jQ&9IqpuWHt-hhYbVbDsTkTA7e?FF=`KdocXxx4vK#Ht&C3on3d!d&=CvnoeBSu%5m}SQ4jrGHHQ<5q zx#L_}&GYg`Z0>VpueEvYu`YLeu`Is%w|~5C$+iqDMke2~A~zp6akN1EWHh6>mL<7d zrQvLwYZ>?z-}ax9y%W*iDLk-{G=yfl@&Is#@@X8j=E?%@sJsQ%x+zCMKU;YzY?@0V zY<`aeXgU}8HypvqsxuWl8@+ennNkTZ=&x3JFW7liIXlF+^h{FPaENxPs2fy|Dwl%% zYvn}pY?NGT2=J8C_4~KU$2PrameG93&Us` z<%fKkKex_4ju!2#lFKMiKjoIFDknpjS4Z$LWz^V3SD5lRl)wB(Y_bag6MwLc#hwG?|zR1)bM1IcmdPg6-R-=g!$QD;%|Z7TW0x9j}O@`sUQ7qm5P zegGb-%+{EooaY;PA;mu8@h!iQWWDX%e&I>DR-G@VScQGlFQ%k@gev**3*IMplVR>t zWPgxcI^dUosq)&sQy06&FUMXt6eoB`9Y|&>U;9gO8MTr143*5cU9Kg{tiVgkpCH^w z#=Vr=2P*&6xAM|(`*?^i`f{xGsjvLyC*#*3swZyy9$q4_4JMn+_Frxdonlvp|dls>6 zHFk?{y{666*kWjc@fz#)owyojJ@31IwQGgRDD8JDuY%HERDK(le^-7a#5d|1t2g^v zpt#L&H)L@iLgH3rQ=t9V0tL#}*zXWKNMnclZeNSD+`eMhXU0!Om6GA-c#50$an&T7 z2HJEzF8(RRwxDRXlzm6DZ~y(u|{g_5X6qvShugxjfN;;`x^lZS5)T|bbk5M z!c@f4LScATv|k@h9Y;p)Zr}PF$<|Z8gEx|+d7`|m@=+n1?Oz@)>NV4lXku>0n?vom zt;ER@^zuS&FdB_p0vWaER-KGea2rmBZEl=?4ECrsWV*iCu9CI;M4~L7!e%zi<*LOU zOeb;cM}~*S&0Gn18^X4V^4I`>L554stqjk_V3YgXM_L~^R+vN;wmMoZq%VU$vYx?s zxn0hF3Ka3rOSgWr{ZS39&2Em){0B0uP6gsy z;PPNxni9ALqXCUe1MVmF+gQoo-st4DAof7?5l)~6$}vdROt}`gjdCmWwf4$8aZK)4 zu7|W)%5URj8llWP!x&}G{XeYyHBPeGE)0E`@o72L#)|Xqglwb6mxJxS%9nBEK2^>` z{)dzYK>sV{6zH5(4u{S~W!`gHZDunD`nQxV$T5p!kyr$(Y2ARu5ty~%TPXukONzqmqUA&7?Q@ItuiZ z>bwd0a^-TetgRIn^%~l1hl(1>_O@1vmycZ>6rIKU20271PlcT09?DN3EUkP7&0S7; zI!eKbAnLS2&U{Km?#bp-eiJ$^l&2y4KFXgVuOT+)9+6-fGMk_g%aOr!ak=Xc<;Ip#69Zs|(P&FayWmZatE}*J<7a5&~c{D{m8ew$0hf)w+dNY?gLpYiBDqegKX}QMzM=Q9Nv=$%yYJ=V8v<5tW&w zq9G`}D93?cRUQo!yOp=V#6OgIN}N-!j)pKk9!zueQk8xexG;*>RQVa$?5<3AV_1Ys zW_Ga(rCmbe1sYKqZSt1#0?5BqUIarwDzEbI>SFD2GMk}b!P%^%WTfSGiHx+|!cmyu zY*r;QGUnEojKXu9=|9}ldfaVIk?cOWlb4lE4C;y1*XkHGubRs>3rZF7Dvn3n* zTX^0o+xuG;okQ`s=g7+9R*`42AuTC|vLWrCjCPZiMXVyy3F4?}jd7-HDauJ=yj8-f z)WqewV+>sxm|r$+({8ZiAU`{!B(@|lQpD*ghGT171X zV{@znmT%RdBL1^;aYE5`5V#JJ{yFn3PecOe&y4?2KHxhT{{cMIC9B0s^749>f(G_L z76&$yBH(u+Gm|1@>cNc%Il%?vGQWbzf|6j@H=G=1&kK~`)83EJOq>1VuZ#ifPA=x zJA#=R#?C*W)JTZO@>-&$iGm23w(kM1)L+UZ&kih7A~lRusg+)($88otTWR7Su3tQ)m~P$KcM!Ssuf1KMLAv` zd)CT|e+4<$LS=#%oaSU$Tp`wTxc-lVc7GMcOZn%luJNfb_z^{VSvvY>iiM1x#bV+3 z2e0&P$;ROA=d9N8ePEE&RlxzyVs4YS_BI+-hy#Yf0nh ztyt5-XK)dxG1lxN8OA=w?GEu!pZ&a*5`O@B@=hHb7Iq**ZH4Sdw6$7pKabk5WJ?(X zW9w0q=T++uslLLR6#qD)2l37ni&|rwKsH9UtgyPKxzX3mjZ24>w}R0~7Q%ZBtgc3# zBFRYBPMWPW73#GTC8U4SlQFn(a~rH$D`X|2?RZN*Ux^+wL9A6KQT!?-qIXq-6A8E{ za;ri%CCXlfV_O<6`Yc5y1JN)48NGj%)jDb?q*Za?nBoyhd;$Gu3JjWg1&BVBgIBRC zYugJRfBzS(orSy?pdUPuIH@1JF^(alV%+Fk25*c<$Y?@trT%hbY)wY>xS82S6pNb~ z^lL|2OC6mI_=H^0JS%|L%pL6^;V5lzHlP|=y^*G5oebzr*2#d0WL|)=Z6fP|G=7>M zwI3A&}Ud)7s|!{q^6n9uKi<%dYp$ox9i;t7G^a3<_DTmSR00 zQby)&u`1b5*OIrkU`F88S`xAq;m%spgW-W%vYO%XT5^NoPqn1g2MDj#l4lTFE;;=H zEJsS6Z3v3XYzF1zFayjFY)8;YCNO9tyBKtnVmlD@ms|!T2Gm#eRF6;l{iWPdd^A};^JBYF#PS&x7U!RFEg(z7syu-h~?R1Tr(%WkfWFY@NyyyZnP|F-;HuH=i zL%u|L9^@}6Kg`P5IQ~q`nYhCm(Hus9ROWLPGo1__`hI_?d>v{W0mDo?13Gld$#20Z zoo4b^kf#K7V85X<3vs`4T`!bo>kjW)oFQW3$}qHDIS;&FnGYh4DR&3+c_Q^U!A?yS zihL6C;NzuaWY$jQD^W2l;x{^3NH+-VWrBB*c)UjJLuRv;XTZ=h`+WX3_ zAU~@72*OLsKG?iLM##-U$h5RskX+h`<+TXASp17HQiDQ-+%i=8H8~Msr`W%TNOYuK z!;T4&#*ucCbwaWuu`cCDna!Y-tc$c$qCP~L_At&&UX#<2_Ga%aRI(CG2J`Ky%<|_b zv&)TEZVH~F%wf|U<)5MdwDL^IS150X{MDcw;oHh55FQD|fQcC|l2XxjoHfH=C)&Qp zieHH8jit+2#MIO^oeZ@Zvalf3UY2zQu@);us>j%InQW{s^bpZl!@xbsQ07yf9A)Y} zq}(2bdxWh1%c7u;%!fG`e<{Y!ivIyAD#5i1dNy^*rU<198Lc{n?1t7^8B@qkwnH3w zrV!db*DbpX+5PMfLZw1sdxp0#>S&z6hJkvFqmKw9L zMfbAuF%;}$! z!78^nS}hphMr7V5u{}suC$S<*7d%n}$?7CNOfKe<51$Sx6n~35>KVqf%d2PDMDUoX zp?^g=`wdTcNwf#$wIzEwe7%^Q0v?%G*=`4yaDQdHqdljQl&)fTu%B)uqZqDeBwHCC zXe7~9ZTfe8t3qv7hHSvDy*5McFx-|Q9jYO^nk=hkbK>YcgFcc`9mi2M$@ zHBh2bZLEl?<#H{S%T#Zd3iXYBq+cr3i}%Cxa!&gsTTNv>HDem!PAHdmTXAv;nt#7W zrY7DzF>7R=ZtriKX4fti%13UtE9SrEyuXcgncfnRhwHyXzcT+GrrAuwkh??ngX2(t>f zlkz_3m^mTHH$k4Ga(Wy?m4AZGiOTd!rYdiPe1>u2kXVq|IB{UEJuyxkcrBDxYJy}W zUZeaLbk-|3KxW2?gZ@9j#)$(ziL>1}ao{_U?^6AbVdoR&<{@qH7(#crjOU0l-IEi_ zd?fUpGGB2xt9%3c*OiaBWowQd>y3h}1a#O^Ty~VCd<_Mtquc_OZK!+z`puL(LvH-B zL|lo%y;a@~X|t8@`E;lHRH=Va;9At+tOp$kK4nV3sY%%UN1FDRE$ zIWsU%oFJzcVxBmGnb|$6lLY2;KkfV=X@hWUi-YPo6)~Hql*_;zdlFSAqg*d5pMw5I zWzG}4rF>Nu!ko7zqR**_Lz0`yPcnpgnrjP=QKk=EQh6HE(nqEai^5TaDJe?bT6rh9 zi}DDhH8%t3k3m{IEW)iGJ5NaU!FF861?cWl-4w7{Mu2GYDV6i770;@md-RLSUz6!D z5x)n~=0Q)Xz+9to?O@^o*)$k8a(IX?`= zV-0I?5)H+3qg88>VQHNM1mxY$Uf+K3XpMQ=xEeRh!pdGs4; z$0i&``~}KT;wor|Ya0KnTb^J%YZMPBf$^;oUyY3P$Zad*Id1AhnK}l;H6B&H5VZfa zrhgsjS7^LrOX*Q|ESn^KlsO_}M%k^o`)*+O`Y1akeiGX7E6fB1r;aX?k^5rF$b(0= zQo85izzmWx1;gU}3-YjP-dhR<3U;1OWc7RokkQ!OIJ+L~8s>U{N^|2O3wDBqWMs$f zW3qZj=A_XB)`Fc=564V$jF~dPMTtA=HoZgE);UO42iJHT^6hM6Ang%&Q9E17j7RJ$R&u~a+V%*1p7-0x=a0bWc_0xL4Y)`#kHSTo zDv6K6@!3|1j-GLm`cphw#!!5;Egsnz4=Maw6%0GjN_8cBsJo1lQ^V!jz_%x!nPck% zvs(Z9z1*60>!;N;e$vf>{(f`pBNalU(BEuV%74pG^21N+mTwnnWn6eE|AfuVjVDwdO^I{XctT*@ zG-`YTPbe8}nu0w}SM&B}ydrRIoE{k}=S)Hi<-^d)RF1@zrib!8*zT=78~Q_(*TMcs z!>A~I{I5pN*usr(`m4_Edhvq{RF`r@dXhQ>Sa`ILW0VT{)U zc{udVo1@^D5N|vkFh}y{$_C~tX0LH|u#<;E7*_|JjR@oFfTw_ss{;-^&31wPA_`=M~=DJyiiwtt7p*P^vPRp#yUE9GLa^Mf+nS+DDg?vz8R zIbvZUvQf(d$b>uyAu}YWA%i4ku5&X}V37X3`&t9<5o@OtL5?mcr`Pn*g-ovo)0}AU`%3QB*>n`)R(05 zLAXGxWhk_;BaVY`FjvbdO0SFaIfB(v`Exi^qfn41VU4NTFP>HIEMq>m zC&lwYVM(Y2{g$d^sIC_43z)|3yyh2nQamTnmoQ3Q8dfi$Hu4LUCs}4gJANPPbcZo0 z^=TY!H|k2Q?_~cMrbL&aY#$7h0*CCR=y+(3R1Hse+(%9kj_ zi7=^g#GV*m0L?WSuA`?#px`se&>JjUj$nAU2%YbFikb(a&5R$S`L@posyYvRNSRAJ zE-J5tya;FipwF9CCFSv`Ya8XFXw?DA@1Q7?lrwR#=SRDWn8cj^eL*AM5A|)G7weyL z)Sgn%YZjC^fszeD>Yy(@jSS1&wv*9J-2VEi;SI{wH%>%u!EQBxtbKPz$Op5Tn^b7Y-S=|TZ-dd@Yt$Ms~%HMqwGs^Zi)%hBX0#P$}m zYWvl!^T$VJrZdbsw>um16T36;`*ZI9625p>YEGi#ew~GYk8UKy{oR*ea zFV%NyX(g+?zrqc>T2$y+96{Ul*p|wzAI4)p<^d! z4IDMzCCy5>i-{HDo|#W8f4bBW_(V?5q^zMe2jYS8%n)}!fBR7P3QJz;?S#w7aCee_ zakx9dIM{(V^!x`3xT|^0c+_~==%{qxjF%0rEmIdqBzx%w8pj%<{$S%+gBPJM@%(51 zw$ab;RsIYnIw{uz_f@8EJV5ys*vV7o**{*H1Xt($Vz1CJ&fp=RJ0JuHYqnmxJ9`al6|B+ z6FMBnFv}e9DP`8`yfSBne^su8v<`mMk3d*RnGP4G)-eBwhxP@j)vSt#-b=;SI-K_Mn9_k{jq%HKj~g)$GptI7o}d8?2+$$CeQ6>?`;*Q9=7 zce3>aUa@h<#h*Z-#^J|Vp1LqVmysE6lljoHKXc3Lg^~7oIjpa}r|v zOcAs4CM?k%<5WW%v=FnnHx>K#)5%h$L0AQ+Q8{>dW<@V@S(pebO>zG*A6AkBiSECj zlSr$VRxj{9hW`HQN$!W@LU~|pSHgd5N`kL9hx?yv;P&Tt1&z6|4#;p=E%*yQ0t!0-k z-8*%YQ`8KuIT$lBe=--wDMepKP&U0tGR?D0VmskQTZ^)&E*{OrI4E` z4KN?-uq9c`!l+m=j5pO8-EqOLK6L7q} zUHJvb`EZZ&9AscFL*OS-neSE3iGoYY9HH_B8tRvYei-rtdtED0o1%do7r*FY+*~NV z3}v%I2iyhmT-L+HyI`oP^6N-zmU2SA4LnTcO^|k?ax>U8t978m{=jK6+M%<*)J8Yu zP5~&b)(EaQeqEUb+NvB2qX(5+pa#d4TOvHK?1tPd35Cta&{~Q3(f$tU+tD3ojg<)< z-O2G8&>F>@+&orZKTWAvhLra@q9sub$vZfFU)Sgp~;J(1z+E&=P`BD9uBsjjH+RH@T7pgpK7ir5@={T0Kc z;D{y+W3ND->$~FCh^HB!L(LSmTOh27kZ-SabH`;)MH}B_3KVHK+U2&Ub|B}gR+Rq; zc}X&A!WYphl6m>Vmag)tvK{8)nam8Ak&F{S4`_5-xg5-GIYo#bjOZ6Mn#s&-Nsw~^ zsCg|3x&49$`kp%aVB(DGPm<2v-EocSq{ecH&Fe3=s$?X)3a(4$>J4m-RNflq%upRV zGaZg51y5#*`ww+@_X+dz!W;A*?jxg7xeX&DV{TK)+I9Al)nAB41%d~C7n!arwi{#} zYSv-5gBQfDJz1M@2)VG!|5`tHyvP2%qI^8S-N7m=cu&3~7KsdHP*2`r&_cpM;>_7dadxPbVead~^ zio)0}Yiw?IPS$`4g9iC0K8r!r`!c){v!9|frY`9o^h8fkT44^Vcd?&xJRK! z8n*+CdwahXa633rVcZVLoZCnSyA0gi^!%WCKf3~f|y1D&^& z|AABPePu4u*r9w0yjQtB!cUb4Av~n~fd_8KSGw~g!jsCgP|_ciQ^34Ou~lw^uPAf= z_@;6v6ygu%?I?6%6qfoNLKRo$mon%XP|jxx<;Y&_oQI(*8ZirrQg`bpJ+iS}Fnhs7$lXf`tBLnO;t5%9@#5pLJQ>KrS zs{9a2-AK7L zk(dt`m{|{GX8aUzd&sM+d&>eshkV_&dPMq`YN*phbr?y*#u>_=yYYa;(V}| z8RMhXt|b~V4Ndow@&JUJl=>f`)U1^94j8Sh%wb3!WxlpWHbp2M#yZ$pB&m9L?NxUio({IcNz<*|?-S3ZrjXO!u*Usv9b z+FSTBZ9$}sR`w!3zO(MEM&c^U946FNZVGPm7kPK(3XpSA5{;PV~ud^J>Bt;XEQ zuQGHsJlG_e&!CZPVu0_6Ah16Rli#S5C#BY4_lt08zQ&yt{|!v=nHtTo2yZJdm8omo zaS0T0B^u}Fxt-ML;bF324V=Tr)a{vU7;`=bqu;@Pm@_lnI6!3iCUW=4GBj~ ze4u>A$VTh@Iut(!yQvf*$1D_%7f43;2$#|?yJHh>W51Kee}VY!8vlN%bU=K3Ike7b zjqiZ&HbLXZ$<&wO#B#pNc&RYI4$;O-t&I2BWc|zTu2KAqR6%-_5M7d}SCE43S(_rH zsDkF;XLcAJBN>QJ=mGoPG{rl`wUE~bjDpX_?Ne3jDcfNs zleIddRzgq%vo;Eu@X-9Ka;~YnqsdsyaQLb$6dyZAlc7IO5?@7bm2f0-C@LAqZJb8; zl)kUxu#J-muewtb_=J9o8kvY%?oh4Y!({8LXzCS+H(yXn3|OH<%uI?paslz#S5W9Q z%HY{^OV{{iD0FM(S5dg0L597d%FQ5uPt`r6Pb^i z0YmXU7#W=GC{^}gz%VkA52w2C!Ntg1)eIIEAllhkl^+-|%d6hz{tC<(O_Pl4cvM+d zF7I(yv@cbcguO7ZF^%Vqc{0>%ulqkd8LC~Uc3>7W)&J&RcZ(vSoDZ^H_$o`j|K|a} z(wP6(2hA+z`Qz^X`F82e>Z%T3yBk~LJsuV-Wlp-s`$j*17oQM*=A=8^?>p($*m zYN5<$5@tvMeJ-2sq;h&)J%Wt1eUy2fAE^8Q?0AC=ow>?f)Z-Q1c>>{c${!;Eof_7X ze&z?td?mpw#)3Q!d_?7oVbmHtUCvnhx{Iu??Yz2mFYwcR_4dEuv*8YWvh9W--7<*${V0#7NWx@ zM{_TjApv&gqY$sD5}#(ir_2HM9_49BKxcu4djN8C0SEJqxSv&i3~7H+<`Y#8l&Rkw zX$vZ|#Y-r6L;nk`O^4DPa8r$-FWOEy7R|+X`)KG%)L@u02R)qopqy{lJ*Ipwc!Ba6 zWM)?MLZ?62c=h1>!JOu7#>6)Phcp3K`k578s$`=e)X9a>(#o8WGH=2|zD#bPhl4X5 zQQa9`7?r?wzp6ei)i1c?qPWz0qKZ~a=L>Kz%3}9fijW~Uj~NbwMj~#9$Xcs2mcPPp?i;o=JsA-uAW3yQhpZik^o-#hN-@dWes9XJC0rTrgp1WL-TKd@JJQ85p^ibc7es#b+X3q8s9 z$<#$}c`7)Mx)e|}lBP8$9V5x>torLVua?FmZ?<%w*&x=ZJB zp8Wn}7wSa4&_M$z6_lxWqz}zh%ztpSVVz0hM-hRR<^)@bJ-!g5M*+~$lPNQ^BTq^ zW~vQ53&nd;lXI-XOA=Er0h=)fxE0LqR(Y(Xwe+Om{pjqLp5*eeNL>rvnJKt6Rp!u| zt2QW)m32_Hw?)dQEj_WBOtMd-LU2Vns?4vtey{uy>UT-`0Qh%h&X|RxFf`j-s<-mQ zd1;OdZYlZ-Cd`csyb#IEoE10~hr_&J1*X2aiGkO_Y@VjQh4wK|;vgRfJ7&&``e=tI zRc91xWtPuDJ_w94cXN~RK`6bYO50J!4}**@azL3MO*)}`496@2dMtD&aaw!2Mo)%_ zkAf+(B4ki&PYo+q7Pa=YPJ0MNZiLi~3PI5_mGjWxoW7$x3f0X~UL;X%JjvdJEa6{N z-%>7taHsMx)cW9GbiP)uhqUxVX_~Ja{i^&mID~fv$T^qA=|=Jh#3w4VST#ai@>&~D zAuCq4xADZq-(L{EjmE!=R$rssSS};F0za!~rg>nB+fN#ugagO7D4E5<2&s|jiEYb; z&HP3OWuuU;qVjKOrCQ4U5%MK(>O6$-US-<8Px+51c_P!3WNna5nVvY)lZV1IlRSyT zIYD_a%<@~#ls_!zGd;-}^$`8Kidv!s{9rbtT~Pf*nT>GdFFM~VD$rOGl&`q#%KY+yC>@tptQY1XO7)yMQSj~?_l$n`|G2_1}vvt610 z|MoPKU-KkI-1T$oLR&mV{Vm`1^t20PWlg~URH{`oYrGt&A6nE`qEJEUvc=QDe{+i` zF)Y89&GcEqZck7Brz8iq;0q87_IN7ACS>8XFy3Z^z(Wq>ZGxXEB%hz~)QCQTPTK^1 z*#p_02#Nk0_x{F`@wF!|>U*R!P9~x($%ZH`8ro)J3L-bU^(sx-5b|}(mvJI*P)?C` zUway6(vi8MqCvPEa^8U%rr<=oqZ|eH(9whZE%coN%HvRq80B+P{Tokf`*>lQ_>HHP z^`UI}#uHbwBMK6RYamm0MRmEbklYx#CMq9*PIcu5VG?rEla%H|p$Dlf6c>1|Xk)sP zsK6BEqsZN-yi9tX^dxtG1j|#*k`<`#Mi%jOtiZoOwSw{wNYYq28&{0p$}^xdSeeLnG<=N;M{PY&{d=F(gsr)T!e!;<)aK80awwB5HZ#^jk z=<9TWoVr_4PrfEb9)|qQM+d+;VDr8gcrtVzQJw2(BcF0pge#OAA0j8*Dl;8FMRJQ)#H0qgR5;A z!FQ6UDzm8{Q}!eABIUl&S*3gn)!pzH{dfP=k=WB1(K6jRm2qbDrt(f z-Jz87dQ_ycGWF{yQ@@!q^*bnM!uI{jt)PQXo{Impr&M{i&f^-vmNj4XM#h6sc;mye z5c2D3Pl}hSCsmdCnD3$@nvHr><*bu~eU_aIISMH=Z7F4@t)e^=2cfre4CM5dm^K^P zn}?EMrd=3;ClHJ%j-$8yFNTcQ2{{Y#w#r$EUCQyOlW{Jg^AZZgX#v_9fCj&yJT_A7 zA3aGKKOvreBjXPsu9$LH6u^9u74i>I;6#=4sX#^L{gVA7hR#o6w|-EB%R&?7n+RJf z4?>^1SNRVVud{LnP9=6+X3rT^Ua(A2P@RFwAL5iUk4T}@0Zoyoa<2Y)I1>L2gbC<2 z&eDh+ly|Oj8HDCJDsk+AZ2B-c5(jI&E z^BAO`K}Nd@%iJ@b8VTi4^Ma^=Cq3}Mp(q)~7e~nF&`bCPE!LDWREAqejlUHm(Pure zRzWF$){|u~j*&;sVp#f!tUHU$I?MjE7(d@a;xyEf#<}Vyoebj-$ZbY*nA1_Crz5(X zMi-RoKYM1{uSUx2KcgNM<?&ZiS2K}YbMGWzpp&>Kj(QrL0x1MAa4fSe%5q1mWoj}jC zR=h-Ag46t{%)aDlXulRCJ1%*;n0l3r3)E{Sj#MJq)T{bs*k-*NX!Ji2-9)25DIonW z8{6|Qdp5^!Ms-^i4_K*)!*DMdRtie5D{$^tN6W)kJV{1vyQ;kcwcUTJy+gIHW8~|g zS|eP<+14p2Kr^zj7Jt>#+E{r@qcJg~k$R2HzlOdyAzW5p^Yla8I@dk7YEpYSW~o`? zu4s!F$k2WO*N6=&_d>o|<>Tzj#_DyoO{u@l`-i z&A<`;f(&ywqNQ(oViRcg0%geVYaIQn8V|D_5O3q?47v%|u%N8J={b_H5ILuog?g=k zxhCX*x#hpAx%(-DxkPlHERBb`PZ_@k2mV2ge*p1bjYlKg`PDQ+-`_mnMSIZ*o}_^3 zuOj^&Zh7XWczfVv2tJ7zMMlxN%_8G+#%&cD2a?-%G7bl~&&g4? zkU=*|tdC%Xj6q;e9xprUI~}5CjmPnWjp6te^jTesNNNKo)!sH<1~hOw*!#xI>jvo&Y-6pWRN3?_c$G8K{LlA!_(Un(0*Q-Db_?j0lT8d##cGM5k#t62FPJ2&T0-~tNphv7Gtquykqm2vG&%B4 zD@0G*A_ZDIRjlE@%Th$zw02^UzE^9fV#u={-~<$wM_M~c_S|gqU~U_t?S0wud26Qv ziuxTGs3&rwas*hmFhnY<%m!|Oy~Oid_Q-qTs~h9 z-1vAbe-G0B*AIJ3sVxx+;_VV%MfNQSOYm(tP}KiUcc-1(Kc<)SVZPVxjQPc>A|v`Z z1*LXxXRAEa$9X-Ua~9^Gh*a6SV`58y9`9IDBYa+Ym~6^*;u1KWyF*34 z!AaYz{C0?Z3p4R~keyUnOJwtd@@gqH$cf9~H;!(qXg-`v7k(`9A|#7Yt^$r#?tn`} zl5%4i0~7Ilir!8|{5K9}X&y|Z%SuF-zY5W_8od$)Sfl(N+Q+O)fod~30oAsgtUamG z%Mt#dTmg0DQgaq;J96Wz1!OJ{;Nm3mbja^0fA5jTgPo*|Cy`w&evF?6-IB_;z)8vn zQRhm^Jz%4T@`JK$u#@b42&!ECPZK#XpRLRlBW8ID<@m!WS1j*NNX(m|`M+A=aG5^DiL+YD zk|9nCF3BGaak8TN!J=8P4TY{!XQ=a?<&pSdPICEskZ(_vgJ#acWR5cLB%_t_9bBk- zYa;q>73Cr{cM2rqlCtkq&X&EVToqwF($MrhV0IbuN`&2&bD=*)c^CLO<&y~4D|4|w z#y+7Y@i&gYz;LIZmDUE*pexdwjEZnGZi%`gb17F>1Rp3H;k;*uk4^~OXUfRI0q!y~ zt#BAV?wjY7v!^7;>O5R==OoAthOZ__tIu|QWaDV3k3G4BM32E$ zd0(PDG6q-WK5}>ruF5YaNrkbvDt{oI$2!TlxK0@BRCFTX=yc?V=aTLFiV^Lg#&iif%(1M^0Wsxi=CeuGoO{NpEor`McW zG4V~&cIFaZ9+xG!&*;R)QEMXSKX4kBe+QygDtZi^wyW||bjkabW2OBzC(gSIs^e5d zZ|p(kOsGy*{t0LG9OXxF%9{&4a^{oS$!~(5$(Jeo<7WTTn+bwmWenxqOhz18I)0 z3-IltI1dC|q#i2h#wj;~(9H5&oUO?oB ziRRd+D#sEXmDkB_sHPo7fz3r3(X6bwDAN*Dd0}R94TwLk zI&>P&D1Qo_>&lT*>OvhZ!BJ)$!ku)r!}VR|UL5pwY+RauHW zW!@?t`iuNgIS;fz&&To&#` z`%FZ0ad{=9F<^H+ryMWa5e>Kb*iKxl3(D=CPHStSwA|%1%;=43EJPN;6?NuTg!G&R z-$J?C`dpc>W!zG(DqD8JZVUWt9p{MklC0nDw2o)5;_G0X)p;0Y>_Uc-5&oz>PNx^w{t8i)LSK!kGYQeiAA>_B%DKUUGRq+O-9WxRg<-Qdby!Ue(%3;-~OD zy#CIgI#n%v;AQG(*lj24KSMKDmV=);UE|xqY#6*2rj3K0C^E7*C>aM(h)1RS0Vl}zEf?v`>M(w4$ag!Wq^vs%i# z!0nYMyWqH($v-H~mreIa#Q9%5==3b?ZHnCfmFF{wa^%8o9vKbGZ7Uglhg$(&JJchp zPS(j8^Ds{*XI4-SpM~4&>V>s7K>g!`N5F^|aKq>Vxve4yVu5TwX617U#2y#rtLqDpCax{{QC~|KB7wU-|iVyXTBNche~< zdu}?d&Budf^vbXpf1_WWYF2!GoNvbOLXT~Oda?ho_x>W&e{)hY=rNmjSs-c*ZlTc^ z5Z@=1ifl&i}5-*7wIh}}^t+9QQ-;X;~(&w!5ztKr+&&SO39?|aMn8UL=; zk+!#;iQ%(wg0h1akud|^adPrDW~iRS2i)(V4?JTQFn79xF7QT(47h_s`!mYlK<3}U zg%AIy{SGEtF#VK^gq^m#W1_YNjlm#3_{V?8dK99)a%yUUZ$^p0j zO8t)cW^Hzdi+$z@E$yF8G+hOn73mn^gUQqK1vJ1WnCy&lRBLx=Y` z>bwYfj&gD2GhBHq4k0cUMrStq^<&Bfp|hB?!%Q3lB{LcT|BS1?84ZB9;+|th17I$M zyRM1(k)0UyQ>Nt$$b2b`yb57OW$N?w8Or$xy@_&f=-;P|cP#YPB+VdCBR)b&A5!M0 z{2x{3P16hspdXIg%||Nd%7w$qX_2ye5L$L0WFgRJ0seu)6jr9a(#o_~ML7YLN>_dW z`u8gHNpWXoJ^<^dyc%|fDL;!NF;STnpJ8*@gp4_YUab++U}&2%XQa#uZ0Jmej#+^X z-T*$WI$R}dz9RrRACsEz2!PAT%E2KR!yq5Cwgw_-21XcQ)}sgSuHasBVsJ>Zm;d1N zAr%R7Fbk^@eID0~$5dV%;UZ-=(Mn}D(R$?*kn=q}n!XPqzobo`2l+AONyz?tWlrJW za=XMkB*gJf#qr`>V@yyDoS@8iq#7#oNeBlM)L~N$SLU}qW-7A-asHD!oQX1vaKJof zYgNt)yrIkrZ1d>x;K4CVbdZ4WNttm6nBVq0tqF$UIPr{N25yA0%4?Cfq;dv2f?2Ku zouSfqXh@v(vrHI@wqA#}HVbkPvmLxZQ*=j)Wy%X7e_5HYL6{jb=<^uuQaKOl=gREL zd`q7t`hRG95Adpru5CDHC#NSNA&`*J6M9QRuhJ2a5_(Ze=p~_dfdB#`(gQA<2uK$Y z6gYH60Rcq;1wjEpQ9%I#l_tu6-*eXS<$u4|$MBVXUCG>g&6+jkOkJ~PHtjyIE8YU8 zM~bQXwK=pww*XT(1T(-DOzH9{z6)l42A%Z1h=5B3;<3njUB%borYL5@Iw`J)?Dtc= zADNk^cnK1*Me%Pol-)6XIW8;uIbt$6Gk&b3hDcW!_!;M1{6=w#Sy0A^9CXgkX*xrm zKH#aXm_Jc-#ZwW@3yK-Gui~XBgprE5p@k(>MI(qzyycog8NnNhc`Ls8#2e^mkilI_ ze@4>#J8)JF@n2Tb0htR@1}#yHnGr%zYBVHJYDZM*@``&R>i&vzplGKkrfvK@#or<9 za>X}*sSu(ZR{O(>+aTar4XPc>`boM9aQ^}x@=68z+s7EQ;n&wCdzXN|-l~LYJ zyL_AGh>v3><^3yxIHDAPVwZgBj;d{0)RUCPz1&%fNnfRyU!XIee1TkAntbpC&)Fw< zt|;yiE}J3SR^4|k-O=2JD=?}Ibo9sF1gi%+5@T7cb&x9VfY(D!GA7oL+(vE;bbM*s zBZme#J`PJqJZ>yqnC@lr`e68!hCvC7U%>EA zsZ?8}6C>;)Suxa+Xx(whsiBU}&(IL?F1>UCW6$nqV|F8m5qvHZm{aK#TS&1xL+7SjxUQa?)^w7ecIvIK1xg-2%fV_N1U@pIo8;AONG&n z#KMs%*K;vo)4lDh#0Z{-U(mwTmw1P~GTQO1t)-kCjlOc8TpR6Zg|`}G@DnwZ^*7<;;tYGPYJ?AB!L0bE!Sn5fl3(MCXK(J+K2EM~rC7 z$zXVAl!A99k~qc3Q(e7RBBjqOrWJcH#pCe1zo?jJc^4?=8LG94xia0Xm|x5MSn(AU z_4kVVf#+UsZKNGhlA$O$rfUu=dAwrwk>wP32fd}@G7!{T@omsYDdv&Qd5V`n=3>Qz zfPGKkGm7s!`eSI32Fxzbi%f~2QQPO-8QL+I=0yK?1y)hY6Nm5;b(K$dN#K#zT zaNdk35oHlj6|^Eoz2?Ikq64ae>BOokFf$izY1>GT@$-L7tg3>`#Ig9H_>@B40*#_I zF@8-xulP06r zIGRhtRgO^K7i%1$HfviC`F5>igtcm%bX*5vS7yluxKM&TSckr8jb9g1dA%dTI-eXd!p?wk>Tsn2Lv@WTVPw@jl>>U?m}PN#@c(TMpmp4GwF9a|5cU74+xRta1apT%IhQ-F=#Et{90b$=U8mC*R?!&|lEq*~>k?npIb=sQ=cEvOue8+OLwyr#I#<|2hW70}z*n%g z^Rz9qWn7$ZSzYH0{lw*o8n#eh!v@YAg<{ju-?1r&yx`y8H%nmP_hooLXF|p*#UJ3K%@`5Z6}evs|RPAYZT{0|GY`@NOV@a6jRV)#i`&wrI_~hR2?z0Q>f7Q zo}fptlLb8iC2dR>fXQ#n6*BO07OAF5E17ucvX)|6q&HE_HGF%;%vle`ry%ID;s!{x zmp!XS5TlrL-a?9LjhyrZPiJGNhXAzEZ}>!jHj24tkorV`bj7vsdyfgw@w-laf<9ky zPM6$CbH-;}gbZGB!G!&V#2Rxw;1?0+b)^?X$=*>s6^VSHI2f7bRtv-K0L_dRz#TDE zRZ%)ez6OeUmfegZ;28}XK2=o01U?5rW|s^e3}DRofcu~XH)&v->`QlI{(|&|gN1Rf zgja6Gvn+IPah#Kjo}d%yQqRm_O73yQY^_ z?W}q%Mqp#vhrq)Tn3Em~nkC}~IAa>4i#AtelJqjxAeF&RoCoE_&5A&rEfvo}QP7}> zbXF!~&H-G;Ar}WYqcdQdZZ0CFKTR>uDQ^;@&4xUY&db>HDCSN9KdDYSPtaFXyb$T4 zj)3&fk+&4ZEPfunAbkR4_g9>V3XJt5v|{jB2%#^jM|l+L9K}2YvQ%*yVD5|s__sa5 ze?akFxcnR(dE${-?g9~0`|_1yUJ>$x;+4Fd^p3u4Kra7Mj1%2K9z@8%)OPSb0piP` zCnzRAkCO#>sw?h;$QmkUjcWY_Kd*r#Kjm`;6v93s3NNFdU@~i1WttlZ0C$<KcpPZfIn5dtFI&)y1)(jFX@l4YI};j_l&mC{ zevN=PGEOT2vQjqh(Ic(^o^Fa6)~sHQxWR+b()!|uQ%rH@$hwMqPU#fY zKEQ*T(L*uEph1e?MwI4zR|v~4a-q_HM1xsnj+txPw*&+svb~B~;71g*HJnx)f&#p( zm>IaCm}ll{P`4bpgDL6ePLu30|5|OUB%52V3^`C6#fLo?6hYn zrqB(FSA%Yj;UHor=%CUm|DW}ms3b2v~kc{M0Q_2%vBK(%p6pOTS)O*w9X{OJYrT| zF^7Rhihq*h!<{k7%wW2b*eQ=u+z@q-Lm@@8bC|Dqp5z>XSub1INhKYC_eI5=)%>iO z&Gxb4=fEF|@}s!rpv5X?F&0tGzNwsIR{f@mAEAhN`v=3Op;!iFqc$8bUqUDkr4TQH z&{>K(WM?YoN_>@KMzBROYv5kRcOmq9#g8O%B=WZducc606ix5SiYcO=Vy3aT;&Vvj zFvU#olqdM-DrS_gD`wdD6;u8t#pJymp)(jp_NRIu6^eTZTlAaaIGd=@>+2 zW^+{bAu{e-Z6t<-+cIv9Gr_89$;vU#GFFJ)cWR7tr42jO!^b+`wuSQq_J>f8khhav z%8qxIv8|NU@y^6pX1f?wcM$m&5><`Zh+IG3xde*4`Weo|I2Kw3=p)Is2jyFp7+fKC znU&#;ww;pIU_&xbXE^(3TtMr{4c#L-SUyD*Gt>1Iv-fVHn5&Y`iuVJLR?Ma|NAWAD z{;w;37r*pcVx*PN4yCgJzaPgcfn;*LKBOKN67d-^=mk*czEJu-xi-O>P`DjfA1jIK zpKvPqI$}=`vSWyu4@sWrOt8J}YctUqWwY&+zLW4eUS>{mzGQn*B40xGS4!=doC$F> z$z4pX8DdLEvfm(v2+VoMzvPUL<(zb<`hNibed?bkn_j{%-&}t83oATXf1hzsK~Y$C z{0ftZA#AFoPj*(bQY_gp+5DR4z@5xrGY^zh>$pf^^Qr4}D$(T}7ZwD5kg}_2BgfUBQ>e2_ccVdEQ2Hr33F)zH%=gv1A-w-o@0DVE z8SP@P)OZ<%#7Z`|pg%$u!E(j#$rN}aiQD1C(Z?R|PeOUL91k&)h)Vp|%g8U+q{T@? zXgEQG)PIqbo@S(Ug2U38HO(1~MkZ^fq2ynb(|%w3D;h_?1b-Zdp>`ig?2n__E6#Y^ zIvM;5nsrS%`HFKk+u(E*;cXc`-MJQD5H0vBn%pkg_bQ@0DK}m<#cQ8|UnoY3&v1?m zv!lB7E9w^+Bb#PGR96X`=}ZWV!LMDQnBQlm)R<|+wwdYtIGmkEMsdGSM3|JB%jXH)RysO@B4}LnOS4DcPl73NVMgZ7Db(-t6n6ll-${BZjO zDeEey#o6C>S!yptU7%e-*Yf@V!)5S7=XAF`j zgE)_$?jy$N!RHdO?p5$|;lN&n*@q}#Sp46dMpfiv?iGpm?JPc#}A3oSD5@yM3Dy$ zotka?L_gCx($Ti~7!3&;5+9=#DeK2vN(Ih5cOypi=W~>}f*+gxZ&Y_at?-j_r;JYq zu>2I_)LzEoXCU+BO+*%h}BOZJX?ei%kzq-M~=v{TN&#m-3KdtjTfOGeytjxo{nFsJLCF33-jubf##~QX*@;!v_jJ+~|?)|;8 zA1-nc_6NK-?vu`c;I)u%(;vmCrT$|$J!HXSXGv`G z9(as7>Bb83!((R=$H`YP&POcB?uxh8FXkrCf+nsA>-ypaon1x3wm_@yLj4_qUt_4G zwsGaLRa>wb9w%0!CquU~5*}TQoue&j!Jn>@ezVtq*!ZcC1nWkcy>83q8)9>{2yybO z23F9cr!aekbz_{ozd0m9RybUv>_dm84(lmz)N(}o5?!uUSv@Yw4HZwfVO@A~s7qQ; z+6_j@Ew`&q7B^&!0irXlYW-Ll1D>&(C9g0845x*oIT z0{os$8i%>!Gic$;D$lMe1zk-I#k7m7r`QSlbBYt-wp3gJ^bU&CP=Z|*Uq;y8ig^{^ z0L6!Zhbz7hJciAh5&wdh*VTizZ95e6tiw^oyu+g`=#olKUG&ZyL;QaRUXt*8x7RfnX zYN`GYO}0w8vi>G}E1&OXPFI@S8HF;if+j!3UhV(w^W$>4uB)8s1AGSxxZy+k6XOC8?0F<2+FN-?F;}jT!k?pfIlZ7g zgcn_=Y4E_cK#L_Ei=-eam<^wwwxKGSwhwQ9zN#9%fji(=Zm9SW#5Gr(3AeT4Q*cui z^J@To6wd{Jy5cQx(Z-F;-Qbz1xD|M&!3fKEa4tGqJ=g~*UfC-2>S&n>+L-vgkGH`MDmr|Zb;5Xd?=&7JJPOBalTcDUGsH+t(27QC#zd-jr!GBOOlV>hmhs*@vb4sTbsxg=W zeHCcG+SonfB^g55+Ys-C8?Km4xfSOIHq%n@Fj8Z`3_KKxsH;5Tkkdl(9%RPYFN242 z`YWB6>Wxx7C^#)BXo|jY!_D0LhX5~w>6p@4I#(2Pd-}HGHHg6M27{l4O{-#NfTdVi z@vBH&ImJRa8w-EjU)Ae%1h-Ql%Pjn^Pmdl@sF6t|CCgCkc>L(#gUnIwgtds8o#Pb*-@h84T zNv`i~jac6U%K{p`2a@_Ou_np<@R(=v`I2;Wmwe0x1n2|#1p5w^b$uL+8Q$EA=z+V+ zWjNO4oKm)ut6A7mv|{$A1@RXqE#AXHo8^^U6Rd4HCBCw21SVLEDkD@Oxdz9YoLj0^ zag{(8YR7`}{Vui$Oy^#yf}VOcRwv!Xb{OO%RWQGDMH+jWTfh)vtLiFhO~_+D6pq76 zE%1tiOrz)?z9R#YF#m*UB zZuu;m#7OIUuF<|K^<76SXQr*wVRRYJC0 z_h#)YJe3>_jCt8B&${;f*J+Th4PC9RI4)gGHyr_;6Pk?arhzBg#yHN27;MIzKz;7w$QuZCig&=6ZVs2l29H7JfQt>tL+*14@Fgs?(bbn%!iX+uD_2UC$vqTDyukxPx@rl5bmMWiu_*G`Bo$ zToLw9pvWF(CQaA>!g(&P{gV(U$LMJPq^UeBg}H$^tQID0U6DJk_i=oTr@grMQ`7(1 zi#Go0gta#XIImw znGs3ZaqcLQF0St+ajH9?+kXvkwfW@qo=YU1uSvP+-T z0X?v`YTm<@!83vpJ{-zQv0`r^|E2}vci@o~(*l9xCH=T3A>((Hv}u4Ky$5U>ATXB& zrU3%a2fZHtcn2w*O70R0pKv0vMsV6R;>=1NTkEXHXJ*6m!OKMDYp4 zb&;46-;x`BFbAuIcNcg_T8%h!C~gng1r%>XI4)($vl#Tsil>5JUomF}T)>eh6Ee-c zFTgh7-b&vIo}r3YLk_HxF$V+Fw;;|_54;8~Qk)-wmn$BC!0d0y|32tO?FT#!^zBMN z0->D4k%tq%gNk>7&Tf@-_CDtouMWYi{Hng3hsSS2_cXf24)xjJsso7mIQtY%RR8j`G3gIK(!v~`K{n=;ivwLn>~8dvmt^iZtVQz-0{t4cA_jrZ6JoVY zKY5~_JHpzTGjmy`BK8w#u3wt7uJ$xAq-5T%RER3pai&KM9qiH*uKzGsDOwYIvV0c}VrVKY%VP@HQ(W>C6BpkjaTT(B zGZwps+hy5u*A5APU@zmFu);OX6UTeJO_zsS@EUS#x;$WCh{SJnm9YxpHiwO__}H4L zD5fui-$c|Y)0Y9iE;Bc}V#0VvZy0{_5RkUX){U-&jB=R9&QQ_=c+XSJ&t;ie16bV{ zw$17en0E%QQXUUx_oib5{TbjbN{U!#g%1?BN4!VSZ<)00LJcxKAq4Rp z+84^yR|apwGLfdAkCbFbIHNHE>vFJi7QyVy1`m69VlH?4DdsID!xf)GK3-LPSgt{I z>h};~x-$fO8%Z^@eBf6h;)1fyM|Qqb90ttE52N8F&A%w-=D_cYc@fDUid#tfX7q$^ zhz?`>M=-YkoQkQ$Ha#KeJP%V+={%=cNpS`6H&z@E{x*tdfj?C-QW7*s@lb@#P)zOo zY%8dliFhFba0|;%z=;iPaC=z_CCNLNsG5#F{wO| zTTDs!kN_$b={*y9r7DfMJfi8Nn4i@hsF+rRBNVeN#wkuf5+^I3Cr96QB_uPNx0J+a z*wIsTLqu~>F?mh|cqH!@RP{|D{jQ`DQX3>I#4R1SK;84HjKgc$<48|82%#`q8p6oe z;BiRXM8&*{~a{vGbTauVWmZZ*PKO#jD9G_&y~*INOPT zlnoc4jGuO6Ud3;t46-Zk0D7!q+ME@z2h}wJxFc6oJ@_db9z3F;*`QZcJRXUtqnIVs zO!4hd*{~IJ+Tt$xbgL`6(P|`+`ayrC8D%e-`Hm|goYwN;NHIxJPws!mRkcw$rF};A0nRwQh4IrEYe|Z$LQrBG)24=42-VZx)*mOQpOHUlE_HmyJ?p?Sww#lGt~lh%ZU;U9MJE zG&aO{VV-hH7VL68YwIo-;KXsat|5kgYGe4cBZkbpPI+cGQZP+YcOzT-T18#;|_4gHbLt6O{2i>Y6LN)JukO+yAo5kVjHBtC$6xZ?ij_q+( z^k$$({#*5Arl2TfKD$YWAU>RD2X>@y5Mv$5Cxm?((!r+@F@*CmJuX`>p5dft_v4ww zx>HS~P82$Fg2$-aa?0k;!TDY*ZMXY2edOvLV(qOY(Vt+!93eeFfnKVKY^O6w?8h+| zoL?EYL%Bv+ldH(5beC3<2A^VX_^67!1{baN`%hgT;d0(hpCM2w`SUZ(BSU2P3B2L6 zl5nh@tt9#+rjehUdv02vg!Zbk^gIa#SDknis?0r!+ViZ`Zsd-UPvC3KNR*2wv0r$k z4ce3q9UYQh(!a{S+LkIYXvz}YOOw&JAa{w9+BZ1AHKT%zKjkX%ANLX~CRMI%_7dkr z`R<%@EwVeQ?X&P@`v2SO>Zdt7mY-A3?DFL~*Jio>l`F1#DDZz$btw zV>~k+KjMY6H8TALJV!BSbxRcoL+Coi{1^`xo#f|K>=VVEq{e0RQP0Vg%dS?~p+9#S zH8E2Dq~q}w{MOYjIJPJF13KfEh|xvyS>c=ey{nyNEew^PexyPvRH|NeHM1W<=My0< zT5;Fys;jGY79RK7SR9(7;)_||0Uqfr)p%s|n{e;M4rS?yt zynxd7Yn>+i1ipGQm(&?(=D~hV(+MziMWU!_iu7xmk`RAyv#u)e^S`dE@YySg>={zb z*48)vXV;P7e^>va($tjCesN{NarTuzlG?b*IhKr zim7C0a9$t27Bwo%SBeITB|N70puPjUbWcr7>z3NRf0teZf?zJtt8Z`L;Yjz^V9B-w zB{wps`zxEIeCW*;I(z`nV)pD&0JmxRzK(Hkwq=fW$4RX?_jteh>qMNpi5ok*c?-D{ zY;&Y`A$KCp4hy-v*pB#47IF`@S!aW!K@oSnbv;P>6>&F+n}_koEF24Cor9mpG+|)O z){cQM;~s{a@_3^u5bd{X2QVs9L&d*f7;LV%4_XqsUBl0Pa86+uwk!q}(}aN!f#2*T z0{;sBG0JlbLo3%BSCG1&6;DKN?keWD5-spCp-bTA zRLnzD9Gpmh8woP|mB7>>vvVW;86?7}sDXQd-kJ0aysSXr9?C?kDYJ3~{U@Y!kkVfS zeWce1gIh7>OI+Tjh(gAbhHRG;&G(=$bl4SY} zLe1_Z@JP^~RXRVRX^xVD&gpM^rEi1VNwE|1%t91AUw~(X(o-RGtl}z=gWHuDS4L0^ zcuZ4|QAnLA4z>HYPne*MsJL$`{V|Gnz2f{3^tNKIu8pb={0T@z;9l3mpdU~kL4rO~ zY)pt)ke-qOkWF7UA~!h|cK}mv#X~?ZsF>e&qR9#a9|XO$VxFEd`>~*N zq~zWzd3dQ#Q^lttvz21%9XsGJgG{eNkolG`@Hte*p33w9xWD3|D51fM(QbpdVa%{R zw`dmcz-)#uDZLdUenoLK@NC5mAajA@4|vtl5`E!ex|NEn;wRXon1+)dDdtacT=6Rq z^toahJbtB^GHxhlf`3)aW^0ZEBcWU@2SIK~VYL5LL>6UUpydIMRV=_<#4(qPkvOyG z3_JlpK^3Jpg`irBLqKn(nBSi5p!j=4+*NTD=;l5w$axd;M`ozUNaW@v#SOtUTk%j7 z-4ex@5CIQ9F+uE*-c@`P_G zz`QojC?|j;kvNat&$AZ4cZ6d05iyECf>54KryM)@i!0`5hFdH4LZ-1z1rNVFHo}hI zp8?Jw0UW4^cOwUz6;DQFM!5hwZv&=YkvuOU@MXobA@rVNc2?$7%HWBFoYDwSe)b)0 z6>mj?o>zR+n*@^ueHo9Eca>vB-Gl#`VDv0tx^kvk$j?_+aCs=vx(ksawk4to3;j4tmX1;colL(y5 z!r{Qoz+4kP(!k7rnCvxu6w!FJR z_;n<%I}%Bj9ync99`l(6&{csot|(&Q{%K&fzgqcfdE_tnXYi=)8Kvz)g3UQR$YDSB zNaf-Bb&+3~cwnxq=DHeFoWtda&YCQ^(ZqK@lSTm~1bh!5Gm|A~ev>Hdqy&2{J z&-*_)d<*YTgV-9ni`w1!Fi$osvRVxxEtBH3c8tP)9VuQ`(}4; zeyC?_7PS(z=fNQ#S8>_6-R%|c0#Bq&ZsqY%(UD(0@eF+l)58sX|G zy(WtCImJH#w^aNMTw{U&{w=`0mCl{10gAaFViY6bISBbPac;vz+zDOVB4wg!!A8a0 z1bk0%SxN8Yjs@H(y!?Pe&S7lv9s* zF!6V%ATC^MD((h4J9g3!AVJ2Y378vEEtK9L{Otn#h`_9zz{6QpccssO9GY#Jgdzh2 zi8H|TBVLB9$1*UzsF+1g%^3r*lY3b)Ek0%{W-(_fo(4IK6eofHhT@T+uU32(_$|c( zEMIhS=dg~3$WL9|Wnx1>F?wlCjref6!`R=FD6qMAi?k-`xU)5~V!6(F6Z27Kq zb%)ww|3Hwyz!ZT51T<+0K6f{Fe^fjf-oxD-U(-B9r;z0A>E@keGkdz5TT3J5Vo&!7 z`+YRKb(w?lWx2Fo?#lM%(dgJR58!n#Ur*q*VP<5jC<*QD&S9%01$(;_?75(KER>lF z>I=QmWpd#@SO&w#${lWc(v2i{#ZWiaboJXF2S?beBT&OkTk8b-WS|;;b=!IHaNtH4 z9hAd=fMu+7>f`>;SC=K1ONQDRUzdlm<;gzoY!Ntb%DUPhi&|%PU%ug^-ETjQiI%U> zi|(Z`PaNxBBzeUX=?fn3-kwzfrpJuv-fLuH_q4t}2jG@$W25EEaDNc?|LY{B`G?*( z-(M5l4)YBiF*j%znB?AQkK2hnq6U}>QUev|zxS6VQI3KB@+SNP{bepxpFn>}#Xvok zF&q4W{<1abf&MZt=z;!nEqDX{Wd-mA`pZwy7DuX_bjY76o#JB1z-Gm~{%f0J9?aRTct6|^6c6DMz2o{4iRN@paS#G? zF3e`dhW}XcMPNJpNdFLt@G3q8H&XEm(2Z#XcxVn)Na=MTg9A6ix)E0$V)iVj5ul}d z&=6v%;*m&ohGHu6MDZ6Wi9jbl3-sMeUkCTFVt&B#bHxkc2Kwj4;I~6A6Y&{vMx?&H z3pcOgD7fsH$&`w~{0)h_AmTcTe+O=(m|ab(;+Np2E3SrI(%zO~SAZwb9kK-H`{@}$ zyt815GDRc6O2zylz0o!ya3usV<`cj?p6OGbi=gjUoQm8WQp}O$wBquRd08y%Wk395`P<$k4gPJB*dV;qzcdcF zyGO<~he#?L1N}M+jNW^LW&8{*`A*7$8SXA|Um@B2K?4T&1Bm@>;WH5nlm9Ylh@}`r z&QO1RFlHvS-rvgWGocD!CI@C>&CO%1%}`ck=KP@*F@&9z+cU9Pe@?2*0`nQ^Gz-jQ z!SI_hb0~lCH}gz*TOZ>NfLZQI$x|TnJEvdHBW77KufgIafRvvGQF#^Dk-^zqF}^{w z-3#q?TEQbQ$c-mP9DF!z1akZVF#_}Xj#yV^UZg3+BjMfL`FwpAxKCKTrgI@jzq~Si zp}U#=Y+l&!$>23!oI6~I@h%4jqC#?j@3j`WD`Q)A^dfh2`vj0fC3P*xmq4~=qHIj9!XhUa`q4ucaHMajY? z7$h5G>#6aA%lKmJ`*^kH=Q88xReGJx#WB&WL?V`AB~nNVF2zb@TPaLc{iEmgSPa(4 zVnN9DkDm5Cd9br-K`>>)XrK#<+Hf;WEKHfjpWJ7 ziG=@k(Zy>W3r77rQwdw#>z-<0meM(4w>fjW+v)pct9yD@!KypQKK#=QrO1pL>NR@7J(J z_!{nZf8mJTf~tYaAH=J96X2RL9XKNM!pjL6`B0V1sxMbA^%Z}L_R~(W8^3=K#c#sx ztC;Hh5sIV0Z&o*u%o_qHE1h2qcvUeC%*3LRM0`xK6Z8{`x5GW7xHfpsE2cj3Tg8ijuPQ#l9{r}i>_IMnQ_P)_hl)7`;0Vw1 z%Z12XiW?&bW?u>P=}0uYJ@Sl2qRS{g1pe}hJ)l=r{1z}17?Obim_!FPRS({2_=4iu z2;5(>ATs8f5llgPmMXpk%#ob*9!NBY8seFtf2DW_=-hlKodfO-#kBO!_*GxtLa{th z{1II4gfKvLFqzYEz-tgV2mVO^2>fx1GvO9c%u&3EVygO$$vF5Q0+&^KPL!50ACJLW zk+Au}w{M<%c#pA&Lg5uub@22=u(76H1gtmk`Kru^Um|`E~ zj4>lRn9AT~f_m_#!KsQ%0?$xf7J-d@I`~iHXIiNAhTvJIn5L4e6z2!#^puH62me;Z zZ$gflhJc<;P9Ah8WUPSK1NG)I)sCPHRtTaa73YGRPw|%!QD1R>h)z*Fl;xqAR*S4o7Nk9-?_qZ<>rk-lKaBZDx|}`?jmKsP z8ipLvLW;8>>c|=RwnTn}SpYs?SmGmhzu2ZoUl`I77^uy+IKWm@Hhl!e^$$+D_z~3i z%fxmB+7yq(9zl%XNTVZWa+2;RPQhF36+}=6(gLPkEr=lrQy6f?dXV^jBq2SVM-Ei) z6>=LQW3QqtHjxyHjN;j=-czK?Q7mD1+okhScbW8g$Vp>PNFcZolHE`7X#|{R-7s@2C5_KgMzSo13-)VgF*(X^6tcz6VO~>8s+_~TW@-+ek;j*Qqhu7w z*7!VTDzqHf{yt_kef(3Qq(NTjaLdV8=iEhLQfK?hT@)wMiu;`wUj;M=75?W-&&&ON z>3Kchp|9NEB{_NfjTPj0nmJF6P)~SP<}Ls0oF`7+3-!ciC9Dr~o?o+iF}YtKi%Cqh z?@pv=_fySZHb!}JNm35aOPMY63P}Zx0AV**V8_GEVr7?1O=J}sx8d~1-Of( zCV3Jws-a%hMit}dCl;WtHB|f?>S=SuaS*}94SBdhX{=pmJ_&U;UGZRq z)q{$A0h?J2=%1p)FtZro8}K)?7~uQvB(-+I3-@Gy*1(*f-BsKhIk2JN7(rf3_9uIy zZT;kQGS1lXqFAbT=`{y7J>$IMSD?_&M;bR9qBc z&HMv$=7XR8G~>zv`Zl8q#!GHQY}5|GoFAA;3h?v5Mg;-98Tb-BLW8!@Dt@fd}n^sY@o)S_rc6YY`7Y#4g3$hc=TP z(0NyYnd|_^A~G}C0X_>JGuZ)dk8ozP13VaApPB3c7X?47Qb-0~)<7s1 z_e$j){f^@4_`!_j3u5Jz!-GE-$uDkcP~H=t5sx3SnbKAvTt~(H!fr3c)P|2%yaK76 zp_rxunTmO6V{w3vqM^=<@$jI@I>nPA^KHciA!kQ0l#68Qfh?Kn2QY03jFuWW9+~(; z1293C6jzhH6+AIvG~Kr&4HR)*YJ(J89Fmfh)E^^5buS!<%+2~+@yHAoP@X{&msYrltgjY6;s>;#ot12P|*|JmN-MQNN)07z)wMjF6-V95;l#jCLpsu5&1_CbG3!G3A;2#6ah@?faC@ zYn6{Grds#BVt%;hFU4cPPemkUCL+$9iYcETyCHowswIwo_`6INcRlrB#O90#m{_ZN zDV;h*PFflGBoZ-E@k2z)V+EwMhv1d?#NsQnU(O< zGyVc3mBbH)hv(brT~qeK8~d=QD|uSQvO=t;5B~UYF-ES&QoFL@>QLE}V5=tMDti)P z?YjxY*v5z~5z;9+7%44Jj4)Ahp|YnHzS@*rMXR2!d9(5EwpOwmX0HwBx7_m@EdPwPUs1D}t zbm-go&_0l257=cAIHRO+brep5G@=tF=^%#FA}$z-B;OIQ%&QLRm*qG(tlzzIr@E(19Dnu2 zWI>uQApLJBJN&EG@T>v*M@v*MbgK%%eM@E1IvJFH91rEzx;zfHBM zyQjIWyj-KxM5^@g;J(BZncl;LyAD(2E4n*Vq(o1+2UBDW-K!~b3@%nmvAqzgtMr9K zYnI);Ky4!K-k`RZ4s-^|dN|hDR`RenMYfX0eJG-pEQZU)Rv&n8Yb{m#g8ZnpZ+c(P zEE~=k#r8)d+1pR%^#@xqx!fPZk|a6JQ_^h9b>v~1rwC#wm=1;}FQH-hzr&p-gGotif@zmoSp6YUc zs3*f3K0NcA{J0TdnCD?uS6aAB$eSpOhI%68?g-Dy%)d%^%{=yeOKCXD(=dZe0JFqG zrKgE0D{+-~6@LKtS;Y;((@ZgSUaia; z3X!p|=&Bxn0ryti4tTg?+C!Ly8Th&BH&yA&;m%WB2pL$WcnaJNifPEPL-8=UrxicX z<=t2MvJ38yisAUpXb>6CTs&5~6Rz2-1)X~8803hhMU!Rfyoss#O;pT;R#TiG?z4)i zv^85xJb*>hlP>CU4jx9c3%VdO8uwD@LGWBsd>eG5sRjK9V56J`z62hQG~{QFBNekl zG+VNu9|bP1bncL5n2r!0#}L5iet@YIHR})H&4|FPIe>crn{@{8FW?!ff_Oe+g5qP~ zF$VqMVX5s^I!pJ6VrJlsA6r3%d|@wod^VW69U6;XVN0kyGtN`O7Q$_$%mM>LWWqR4 zsQotjydV6RAFdjQuHnnfrj>BK5RfOeE{^jQ38Ome1SGTOhWZarT?eU}XVI#ItvXA)ME`?c#+NPAGd!!U`EQFl-`@G2 zcSHO~kOnUD1e+sBg(Y~2XYRlDEdDMbY^kS%QMVXF4{6fK>GsWB>Y441<5>dJD-=Ps z!kBA%1z=Rlifgd`TMb${yum=!e8<(acHnUs+-oa+I%-8j#T9{@D(22dN5!e&@1pn= zc+wPe9XeRC7yRQDXM&D~jb#KPy9Z*XdQ64Ka>XCRU89(b*Nuv~jq{G;9uR7@Fp$#% zb<1dBfWHIJLFJ*r2CFmUT7kr|auHXc^5BfVgu~-vz{7#HhT=6nm?g*Zeig|E`rV8Y#0~rkze~z$ih%?|pE4rTQ5sd&-6@P+=sXb=k_Ym0h zAi!J@9aMS}@JYqo66dY~`8$B0hKa{N z`^5%|ze5223=~RTPD{m181*xxbHUVEG5g>iimA~uDsS*Gk%N@Z?|hFidqeP`nry6k zdV+9y(m%HmdWn#JK)BrL9liN1Lko#C_Y`}7}1VRlUDc*lZcc^-&NIJcjL%7k? zzcR_P57L=y*pCU;DQ`FWB3d`3B_hF}(=V#mDR0See^!JbH zJCAyB$=*kvF1CN21YjnSS3W=LnUq<(bX4Z_j-k@;m}i3DK;+RePkSqF2QqD@0r-Wv zx;4`P;7@Hb;5eoMYw(LFg3bm?{XUA#U=Gt%E0F#w+i--eCUxP%LVIB0w z;J>7F&U(#=4f++(&4>-W3%D@CFrhQ81T2~Kh23;@Vr@t4>B#hkZ2i;|lg#n22T5p7 zoCoz)Z|W9?Dt$TVqm*7s9(;-}w1rgo4BE$K@G%1%1b&GSTozIIaNmm0Jeklf@?tpO z?9V-;ZPt}g@tnr0x`1>zjp_8{Y;p>()|Kp;ohwGmC;PDau64$fWIf8Bxfz6MXFNG< zv3`@SGiVbzu%UMCz|I?Xwc3-0( zJY$|}(*4zsp00Kr?62HKY?W=%((I}y;wcvWiH^jyfxU4zW~Zy3M9F^5(>oj9@#TRL zJ=40R^-b?P%y;FQr&)+;fHLY2e7QO0re~}(j;mwS3iCquh^k;(A#hR5Cr-v#S1`Zm%@ZnqyC}dpX_q%t|z_?w?MyE+UtnuC&ko? z-BL{b|6Rog;XYE#DJ;#UD4X*vm*Tck`yR&OwFnlcq_^OjX*XDJAb1g_2P0T1#r2Rk z)3(9G=?#~-6kQuU&nT`1ne`OY=&7;d1;Cu%k-rW2p<=;;1uyXs`ht4UB(|I4BXIjD zjs@Lp#ezQ-v|&o03qfNPpF^T2DCS1jRK-z{PrG`?wFNx$70C`!Rk5ccp@E)hZYD$X-n0f93=|hrqigHT5tIAA- zj$04!pEdMnz_--rxauHIFp zHoP0LS*#*Sr$Fzj_a!9VY)2wzi%~cqDxLTAnIrFBBh7Cah1hz@3dVL2vDxs)4xLW~ zvGG0$@4}Q?T1i77mD}@wPqLIg7$1Vs-1j623HTGN=9oGp{biG9{_sS{vUFyte?FAV zDh>X!q%$~=iybD3{R^J2)%$lGE=F+kokCu#&&2J*rv*>i<9z4;@LUSX$cD%Q178JV z{mnZO>o3K$2g-XIu@>$!;y_6fYbl)|)*`t|9Oh4zQFev)w*8Mp7!0x3gJksH9j_G15om#S$PJ6LA;q;IebOy-^IM&$Qa>wb# zdDYxf#|3wOZkb1Sd2adEhjFz5&F$3CSQ5IMeGZUW3FH( zq1M%CDG>_NPW-Ys5*mwW`-Xa>{5Qh?!wj@Svbho7`YoI9aH#iDu#;D7T0uCQ`*LvH zzs?Ognf%|-XD!I$cxaSZ^|O12ItC5Fh}vJ~Cb;fPv!6ZTumq}5pD;?oUC{%OZm7h+wvQRD=hIWJY`(^i_&~2WC?yy${^&ieG^HjpAg4y{@}l+J0W86Ux42Xv#Zf*`M~kM$(+nZ%pjF z;2yyc+6he5(6i)Ec!yoV>nbH}_Z5uucCi)aIw{bXn8>s*F$1uQY3N&>8%@=^B>ZB{41b#03NUtUbQcyW*Z)ViTAZ5odx?H{Q$?X(xLi#nkYKY;z0eHW9A4UuZ z6>}4d-3q1BI_!kvW^m6a<`?)bDyFU6&x&6|itZ{7k%=w53CSao*^;dP5VrtcrcnWR z0=mPW9H>b(&uGFo2HxrQ*aIAG3aE+`N0f;KZc(76?8 zw7kGH_%cdf;H3y_l)S)S*^xZFGl^7dLYIoUEnb=;UF^z;4yYLrC4yf}n& zVDj)WT3+Da5!PsVfhQvoM#~F46#PcX3tRzmYH8SE;5SNM&~FFD{MVIM$KYYKyzuy0 z!diJ_GI+MWhlYCu*C=)2O_k&zr6&WAP<#^U8mo9b%6*dJ9^f%LU4(reGL23bn8yH( zP8YZt_>E2%xFC|U(&9OOy!6EjRR_e^5SZEm;^N5cNyP)?cq?y0+sAfvtYD${HwgBO zVxF`y2K=DYG9XRqJrL)3#XLP{tm(i*yQnov&lM~UT6_E1rpngVsQA>2HiC%26s$cl zg7m=tnm5KaKpwP4XPG8>+nBcsZM?HFNjlWVn;82JB=W>%W*O@<%1D`vT7iABZwjEn*7<;TMaQ*X@_JqkWTH8 zj2<$)oj0)t7b%qxE?{3l6L=E_>1wTbF%r<3IJ>E2=7fb`?qx`dy#>~n=nbU>pLqch z64~CH5PJqc$##;|I^*Eq-c8$kTiN=`3UFE19dfumN_3zkcR(WhN}CSeRF7=J#P&vF1B2x_VkDN&Y?(O5ipkhc62kG}%px$j+gRoFC(22zb8Dl( z)wy*Y(lMa(X+s?0$24UQl>TU9E#(=+TBL6f=LkwLA*kXCEGf)#E!r>W7t&+onH{!# zzNi!9QpZ?xrm`cy>kBK#HMBtQ%jR>4tunXZ-y_B3v;}pG=#HV|k z$))}nZ^ov3>q$}?&?i9F!BUca0C-N6l5TLr&X@AWI7;uZ|X$;T4|yh+y8Qeq7R z+i#_$C0w307>I~Mc z77fAtKg3v`oE?Hm#g#hp(-6$uJLcxEV$K&78w&chc(VXV0mc!8)S=#@*50}@eWGyZbrlFOR=T_(7FBZhg;*qrx}ODpJD)|Jj# z-@#5A=^dE)PKWGS`y`W@PqY~2jgfZ>L6_KPl(*(nO(pV;@_PKYz04U6w6T@tHXq@W zlHcQM&p~6nqinHMznGZ>YBmR1Gm`+WZoVlK$Ek55{#fy;W3H(9!ytKPtT(#GM!Y|# zv`^rsDBcaXlj0I^yD1(Icc9`0GIuOGsT#6=EP8{N<@i|d!0<2WiN1)>wIE;Pao&oS z^~fQUGO&D%kOvv2!ZezI3iF4VJx-f|%Fsg=O~88bSER6k9GU=eRWb*!jd^V%R*GXG zt)aQo;{D60Lt|Z1bt3B4Mktj}8TG4m&@R(S`T{*sQ6u!o#DAHRV*3E!4&#(`kgxD0 z?-DPp7c=v}p0!Vhm5?^GyxIOYW~3>dU3TAxv%G_CvV4v=m+!zF^no#%N&OASgLlA4~?Z4=%OD_z@IZr|JUys`d^FHY>ULM6|~ zA)&ronciU`aqP-W-NL9a8uiT7E#R+hGJc^q-dxgUDimlO&})F7BghuudWw$#n+gW{ zE5K&)2wWQS%;FK4Q!ul51bz=Z{WWYo(1$2a0e!S$I6+g);t>H@*A}V=7l3ant_=LP z;!?nS6z>N9RBB|KKFdHGLfqcwH zh+)E}yYk!xPfx`|ZCGM>%f!-3%8XY!gqcY!!%4x#-k5L(x%6K_8Z1VTRiGu|FQ8Vc zNo<15M3BM^(w-!YTy-F*lX`Q%Ww_#Ea&$4~8Y1>3-bCxPO)4(IqOiAg(AVjBwQgIo zd5O0w4h3FYg0UYd4qxgmW5X>|OHpZ~Bz37b(Uu@{@n-#O`F1SzPPeC;Leth?fC<;~ zs6(uIq<%M$cp=7ofzLa{I&PcExz|t6##E#G;#0`CVx{-vV1A`=z#5EPOLNNpHQr_X zDq-if7!2oBbcM*4wccSEGz+iuHn&&L2hFOvCSb-oZ)N*7yt*=zaTCD#b>1A9X5C)r zO|b9HkC~;pAE4BFZ-m3bWZ*AL>a0g81o^*KI2Lr=Ts?EWw`f?g0yy-7w7?0Q z^KdSgZWhY!$(R^$!d_4&R(0jK4(5taN3*F8jP|vbu95D`MPv#qpkZt}pg|@BJ(XV}_jA z<{dZ`J4O*7c)#EU(|gUA3@6kJ4)ql~=)L8LdmlB$^h;1ye1gI@{Sq*mP1%ppFYN#_ zgC1!70*mRRfN7|0`Y7O8kYV~L;4i>$`Y2$wT+>GZuLHdU{-|1J|I}MCw?fhsH-|e~ zF-;LBnSKcY?%`#&dbEYRK=BF$UZR*=9xD{*!E|@6V)jv+6lZ|vUB$d8WtZX$aNko* zld40Chts0M=FJfPK|>ieCf$rs7_}4-|8~X9pko7lD>b@iOojg&621z+)^u zf$xCMK8*ZavtbSiJ1x9$0nD8^dQ<|Y@`$(&gpO4_1@!rfxhJzl@p|O`h~mbepH=)P z!kP&^g1^2+rn{WKgkte`)NyeQ(m^QND1mdZOuTG$g{y=Oey)o9G zL4t!J@mO)yIEfTzlPM>?359PV_9N(E1B;X|i4ps1B$+3mi8sh`2nZu79{n##N98t1 zg-NPU5|U#f-Hp||7m{YIfWSIlN}uw^)SxPAmU=fqus0NQr-P^DDb)vjLh&;2UsgQb zH}RCWw<~NQ`~ySiOtC%0$DltT39~ppUl8j+@B^_91V$U90|9kSfq|efu`b{m6YByl zoj99c<^*D0z|AFQpMhs3arPkJt6zI_hG3vr@eSsrP2@J6_EP&2oE|cl&LFuACu}Un z2M$1`@E0yEx?%m4avAHVY58T+WsE}0^UEo^8}m#2w-|+LUBb#~*tcF(R!RNNJJOm^ zRKEHS43|nvi7RmDmyt17yd`Y!__s`UUg4T4QI1`~B(@dC^1=R((z(6|{hy?#1db0aE-9QTqp#W*o)KORV?X&XB3whiTzIDHN8(OhzQ3hs2LDe~hUm0z} zR3m()C6_n16uRTh(WWMNYAgL!V73ZYwK=Fx%@tE$$_Y8h+C_8F|Zj2Ac7NSGY$ar*@Ztw(_C)c@s5n;OsWR{0{vDKVt6l; zalb-?(}i~!ei-{318Hx_ZZj}Hi}r2$4O2nxEnK{d3W~jjGWX!#_sIBrShy9F-|u1H zuYjcAN3-Z7``}m$!|4woYEY$bic~Zmr3jnj+g<{&6d?GL~*%%FJUAya@w%4HgT}Jm3IHD=UvS zq#kr%K>|oU0GJ2dWB>pF delta 90665 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 diff --git a/variants/libarc32drv_arduino101.a b/variants/libarc32drv_arduino101.a new file mode 100644 index 0000000000000000000000000000000000000000..5edc114bf16b1da00ac9836cf60828362c4743b4 GIT binary patch literal 782484 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�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%0A5TEmJIM=~ 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

$EgfQ#}_}| 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 bM7(8S@;+d*!MKPspAR;{qaJ)C)A#=Xnv{lw literal 0 HcmV?d00001 From ca22565a9c1d9e82a031e60d7246bf28f7153ec2 Mon Sep 17 00:00:00 2001 From: Erik Nyquist Date: Tue, 8 Nov 2016 10:44:59 -0800 Subject: [PATCH 18/22] advertisedServiceUuid: force caller to provide storage Previously this method was returning the address of a function-local variable. All example sketches for Central were attempting to access this address after the method had returned, so would have invoked undefined behaviour. --- .../central/led_control/led_control.ino | 7 ++++--- .../peripheral_explorer.ino | 6 ++++-- libraries/BLE/examples/central/scan/scan.ino | 6 ++++-- .../central/scan_callback/scan_callback.ino | 6 ++++-- .../sensortag_button/sensortag_button.ino | 7 +++++-- libraries/BLE/src/BLEDevice.cpp | 8 +++---- libraries/BLE/src/BLEDevice.h | 4 ++-- .../BLE/src/internal/BLEDeviceManager.cpp | 21 ++++++++----------- libraries/BLE/src/internal/BLEDeviceManager.h | 4 ++-- 9 files changed, 38 insertions(+), 31 deletions(-) diff --git a/libraries/BLE/examples/central/led_control/led_control.ino b/libraries/BLE/examples/central/led_control/led_control.ino index 7d087b5d..b98eef70 100644 --- a/libraries/BLE/examples/central/led_control/led_control.ino +++ b/libraries/BLE/examples/central/led_control/led_control.ino @@ -24,6 +24,7 @@ const int buttonPin = 2; int oldButtonState = LOW; +char uuid_buf[70]; void setup() { Serial.begin(9600); @@ -46,16 +47,16 @@ void loop() { if (peripheral) { // discovered a peripheral, print out address, local name, and advertised service + peripheral.advertisedServiceUuid(uuid_buf); Serial.print("Found "); Serial.print(peripheral.address()); Serial.print(" '"); Serial.print(peripheral.localName()); Serial.print("' "); - Serial.print(peripheral.advertisedServiceUuid()); - Serial.println(); + Serial.println(uuid_buf); // see if peripheral is advertising the LED service - if (peripheral.advertisedServiceUuid() == "19b10000-e8f2-537e-4f6c-d104768a1214") { + if (String(uuid_buf) == String("19b10000-e8f2-537e-4f6c-d104768a1214")) { // stop scanning BLE.stopScanning(); diff --git a/libraries/BLE/examples/central/peripheral_explorer/peripheral_explorer.ino b/libraries/BLE/examples/central/peripheral_explorer/peripheral_explorer.ino index 41f11ea5..16eafb8c 100644 --- a/libraries/BLE/examples/central/peripheral_explorer/peripheral_explorer.ino +++ b/libraries/BLE/examples/central/peripheral_explorer/peripheral_explorer.ino @@ -19,6 +19,8 @@ #include +char uuid_buf[70]; + void setup() { Serial.begin(9600); @@ -37,13 +39,13 @@ void loop() { if (peripheral) { // discovered a peripheral, print out address, local name, and advertised service + peripheral.advertisedServiceUuid(uuid_buf); Serial.print("Found "); Serial.print(peripheral.address()); Serial.print(" '"); Serial.print(peripheral.localName()); Serial.print("' "); - Serial.print(peripheral.advertisedServiceUuid()); - Serial.println(); + Serial.println(uuid_buf); // see if peripheral is a SensorTag if (peripheral.localName() == "SensorTag") { diff --git a/libraries/BLE/examples/central/scan/scan.ino b/libraries/BLE/examples/central/scan/scan.ino index 5bad0456..6b643967 100644 --- a/libraries/BLE/examples/central/scan/scan.ino +++ b/libraries/BLE/examples/central/scan/scan.ino @@ -17,6 +17,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +char uuid_buf[70]; + #include void setup() { @@ -54,8 +56,8 @@ void loop() { if (peripheral.hasAdvertisedServiceUuid()) { Serial.print("Service UUID's: "); for (int i = 0; i < peripheral.advertisedServiceUuidCount(); i++) { - Serial.print(peripheral.advertisedServiceUuid(i)); - Serial.print(" "); + peripheral.advertisedServiceUuid(i, uuid_buf); + Serial.print(String(uuid_buf) + " "); } Serial.println(); } diff --git a/libraries/BLE/examples/central/scan_callback/scan_callback.ino b/libraries/BLE/examples/central/scan_callback/scan_callback.ino index 983d3f40..5dede517 100644 --- a/libraries/BLE/examples/central/scan_callback/scan_callback.ino +++ b/libraries/BLE/examples/central/scan_callback/scan_callback.ino @@ -19,6 +19,8 @@ #include +char uuid_buf[70]; + void setup() { Serial.begin(9600); @@ -58,8 +60,8 @@ void bleCentralDiscoverHandler(BLEDevice peripheral) { if (peripheral.hasAdvertisedServiceUuid()) { Serial.print("Service UUID's: "); for (int i = 0; i < peripheral.advertisedServiceUuidCount(); i++) { - Serial.print(peripheral.advertisedServiceUuid(i)); - Serial.print(" "); + peripheral.advertisedServiceUuid(i, uuid_buf); + Serial.print(String(uuid_buf) + " "); } Serial.println(); } diff --git a/libraries/BLE/examples/central/sensortag_button/sensortag_button.ino b/libraries/BLE/examples/central/sensortag_button/sensortag_button.ino index e72f2c99..10b18125 100644 --- a/libraries/BLE/examples/central/sensortag_button/sensortag_button.ino +++ b/libraries/BLE/examples/central/sensortag_button/sensortag_button.ino @@ -19,6 +19,8 @@ #include +uuid_buf[70]; + void setup() { Serial.begin(9600); @@ -37,13 +39,14 @@ void loop() { if (peripheral) { // discovered a peripheral, print out address, local name, and advertised service + peripheral.advertisedServiceUuid(uuid_buf); + Serial.print("Found "); Serial.print(peripheral.address()); Serial.print(" '"); Serial.print(peripheral.localName()); Serial.print("' "); - Serial.print(peripheral.advertisedServiceUuid()); - Serial.println(); + Serial.println(uuid_buf); // see if peripheral is a SensorTag if (peripheral.localName() == "SensorTag") { diff --git a/libraries/BLE/src/BLEDevice.cpp b/libraries/BLE/src/BLEDevice.cpp index cc837f11..d6711585 100644 --- a/libraries/BLE/src/BLEDevice.cpp +++ b/libraries/BLE/src/BLEDevice.cpp @@ -273,14 +273,14 @@ String BLEDevice::localName() const return BLEDeviceManager::instance()->localName(this); } -String BLEDevice::advertisedServiceUuid() const +void BLEDevice::advertisedServiceUuid(char *buf) const { - return BLEDeviceManager::instance()->advertisedServiceUuid(this); + BLEDeviceManager::instance()->advertisedServiceUuid(this, buf); } -String BLEDevice::advertisedServiceUuid(int index) const +void BLEDevice::advertisedServiceUuid(int index, char *buf) const { - return BLEDeviceManager::instance()->advertisedServiceUuid(this, index); + BLEDeviceManager::instance()->advertisedServiceUuid(this, index, buf); } int BLEDevice::rssi() const diff --git a/libraries/BLE/src/BLEDevice.h b/libraries/BLE/src/BLEDevice.h index 845add70..a96ce433 100644 --- a/libraries/BLE/src/BLEDevice.h +++ b/libraries/BLE/src/BLEDevice.h @@ -436,8 +436,8 @@ class BLEDevice int advertisedServiceUuidCount() const; // number of services the peripheral is advertising String localName() const; // returns the advertised local name as a String - String advertisedServiceUuid() const; // returns the advertised service as a UUID String - String advertisedServiceUuid(int index) const; // returns the nth advertised service as a UUID String + void advertisedServiceUuid(char *buf) const; // returns the advertised service as a UUID String + void advertisedServiceUuid(int index, char *buf) const; // returns the nth advertised service as a UUID String int rssi() const; // returns the RSSI of the peripheral at discovery diff --git a/libraries/BLE/src/internal/BLEDeviceManager.cpp b/libraries/BLE/src/internal/BLEDeviceManager.cpp index a00d11eb..155813b6 100644 --- a/libraries/BLE/src/internal/BLEDeviceManager.cpp +++ b/libraries/BLE/src/internal/BLEDeviceManager.cpp @@ -740,28 +740,26 @@ String BLEDeviceManager::localName(const BLEDevice* device) const return localname_string; } -String BLEDeviceManager::advertisedServiceUuid(const BLEDevice* device) const +void BLEDeviceManager::advertisedServiceUuid(const BLEDevice* device, char *buf) const { - return advertisedServiceUuid(device, 0); + advertisedServiceUuid(device, 0, buf); } -String BLEDeviceManager::advertisedServiceUuid(const BLEDevice* device, int index) const +void BLEDeviceManager::advertisedServiceUuid(const BLEDevice* device, int index, char *buf) const { const uint8_t* adv_data = NULL; uint8_t adv_data_len = 0; uint8_t service_cnt = 0; bt_uuid_128_t service_uuid; - char uuid_string[37]; - memset(uuid_string, 0, sizeof(uuid_string)); if (BLEUtils::isLocalBLE(*device) == true) { // Local device only support advertise 1 service now. if (_has_service_uuid && index == 0) { - BLEUtils::uuidBT2String(&service_uuid.uuid, uuid_string); + BLEUtils::uuidBT2String(&service_uuid.uuid, buf); } - return uuid_string; + return; } getDeviceAdvertiseBuffer(device->bt_le_address(), @@ -770,7 +768,7 @@ String BLEDeviceManager::advertisedServiceUuid(const BLEDevice* device, int inde if (NULL == adv_data) { - return uuid_string; + return; } while (adv_data_len > 1) @@ -781,12 +779,12 @@ String BLEDeviceManager::advertisedServiceUuid(const BLEDevice* device, int inde /* Check for early termination */ if (len == 0) { - return uuid_string; + return; } if ((len + 1 > adv_data_len) || (adv_data_len < 2)) { pr_info(LOG_MODULE_BLE, "AD malformed\n"); - return uuid_string; + return; } if (type == BT_DATA_UUID16_ALL || @@ -808,7 +806,7 @@ String BLEDeviceManager::advertisedServiceUuid(const BLEDevice* device, int inde memcpy(service_uuid.val, &adv_data[2], 16); } - BLEUtils::uuidBT2String(&service_uuid.uuid, uuid_string); + BLEUtils::uuidBT2String(&service_uuid.uuid, buf); break; } @@ -816,7 +814,6 @@ String BLEDeviceManager::advertisedServiceUuid(const BLEDevice* device, int inde adv_data_len -= len + 1; adv_data += len + 1; } - return uuid_string; } int BLEDeviceManager::rssi(const BLEDevice* device) const diff --git a/libraries/BLE/src/internal/BLEDeviceManager.h b/libraries/BLE/src/internal/BLEDeviceManager.h index 84f584c1..cb8fdbd7 100644 --- a/libraries/BLE/src/internal/BLEDeviceManager.h +++ b/libraries/BLE/src/internal/BLEDeviceManager.h @@ -316,8 +316,8 @@ class BLEDeviceManager int advertisedServiceUuidCount(const BLEDevice* device) const; // number of services the peripheral is advertising String localName(const BLEDevice* device) const; // returns the advertised local name as a String - String advertisedServiceUuid(const BLEDevice* device) const; // returns the advertised service as a UUID String - String advertisedServiceUuid(const BLEDevice* device, int index) const; // returns the nth advertised service as a UUID String + void advertisedServiceUuid(const BLEDevice* device, char *buf) const; // returns the advertised service as a UUID String + void advertisedServiceUuid(const BLEDevice* device, int index, char *buf) const; // returns the nth advertised service as a UUID String int rssi(const BLEDevice* device) const; // returns the RSSI of the peripheral at discovery From 6d15bef08dd002aae629d614675aa25d9227e14e Mon Sep 17 00:00:00 2001 From: Erik Nyquist Date: Tue, 8 Nov 2016 16:56:10 -0800 Subject: [PATCH 19/22] BLEDeviceManager.cpp: fix typo in advertisedServicesUuid() Apparently advertisedServicesUuid() has never worked. It should work now. --- libraries/BLE/src/internal/BLEDeviceManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/BLE/src/internal/BLEDeviceManager.cpp b/libraries/BLE/src/internal/BLEDeviceManager.cpp index 155813b6..188c9561 100644 --- a/libraries/BLE/src/internal/BLEDeviceManager.cpp +++ b/libraries/BLE/src/internal/BLEDeviceManager.cpp @@ -757,7 +757,7 @@ void BLEDeviceManager::advertisedServiceUuid(const BLEDevice* device, int index, // Local device only support advertise 1 service now. if (_has_service_uuid && index == 0) { - BLEUtils::uuidBT2String(&service_uuid.uuid, buf); + BLEUtils::uuidBT2String(&_service_uuid.uuid, buf); } return; } From 58fcfaea0cdc9493bbaa3e795354d516c2e20e28 Mon Sep 17 00:00:00 2001 From: "Xie,Qi" Date: Wed, 9 Nov 2016 16:42:27 +0800 Subject: [PATCH 20/22] fix KW issue for class BLEDescriptor, BLECharacteristic and port.c --- libraries/BLE/src/BLECharacteristic.cpp | 68 ++++++++++++------ libraries/BLE/src/BLECharacteristic.h | 6 +- libraries/BLE/src/BLEDescriptor.cpp | 35 +++++++++ libraries/BLE/src/BLEDescriptor.h | 2 + libraries/BLE/src/internal/BLECallbacks.cpp | 3 +- .../BLE/src/internal/BLECharacteristicImp.cpp | 49 +++++++------ .../BLE/src/internal/BLEDescriptorImp.cpp | 55 ++++++++++++-- libraries/BLE/src/internal/BLEDescriptorImp.h | 4 ++ .../BLE/src/internal/BLEDeviceManager.cpp | 56 ++++++++------- libraries/BLE/src/internal/BLEDeviceManager.h | 2 +- .../BLE/src/internal/BLEProfileManager.cpp | 27 ++++--- libraries/BLE/src/internal/LinkList.h | 7 +- .../framework/src/infra/port.c | 45 +++++++++--- .../framework/src/os/panic.c | 2 +- .../src/services/ble_service/ble_service.c | 4 +- variants/libarc32drv_arduino101.a | Bin 782484 -> 782684 bytes 16 files changed, 265 insertions(+), 100 deletions(-) diff --git a/libraries/BLE/src/BLECharacteristic.cpp b/libraries/BLE/src/BLECharacteristic.cpp index d37849be..8c0451d7 100644 --- a/libraries/BLE/src/BLECharacteristic.cpp +++ b/libraries/BLE/src/BLECharacteristic.cpp @@ -10,22 +10,23 @@ BLECharacteristic::BLECharacteristic(): _bledev(), _internal(NULL), _properties(0), - _value_size(0), _value(NULL),_event_handlers(NULL) + _value_size(0), _value(NULL) { memset(_uuid_cstr, 0, sizeof(_uuid_cstr)); + memset(_event_handlers, 0, sizeof(_event_handlers)); } BLECharacteristic::BLECharacteristic(const char* uuid, unsigned char properties, unsigned short valueSize): - _bledev(), _internal(NULL), _properties(properties), _value(NULL), - _event_handlers(NULL) + _bledev(), _internal(NULL), _properties(properties), _value(NULL) { bt_uuid_128 bt_uuid_tmp; _value_size = valueSize > BLE_MAX_ATTR_LONGDATA_LEN ? BLE_MAX_ATTR_LONGDATA_LEN : valueSize; BLEUtils::uuidString2BT(uuid, (bt_uuid_t *)&bt_uuid_tmp); BLEUtils::uuidBT2String((const bt_uuid_t *)&bt_uuid_tmp, _uuid_cstr); _bledev.setAddress(*BLEUtils::bleGetLoalAddress()); + memset(_event_handlers, 0, sizeof(_event_handlers)); } BLECharacteristic::BLECharacteristic(const char* uuid, @@ -39,11 +40,53 @@ BLECharacteristic::BLECharacteristic(const char* uuid, BLECharacteristic::BLECharacteristic(BLECharacteristicImp *characteristicImp, const BLEDevice *bleDev): _bledev(bleDev), _internal(characteristicImp), - _value(NULL),_event_handlers(NULL) + _value(NULL) { BLEUtils::uuidBT2String(characteristicImp->bt_uuid(), _uuid_cstr); _properties = characteristicImp->properties(); _value_size = characteristicImp->valueSize(); + memset(_event_handlers, 0, sizeof(_event_handlers)); +} + +BLECharacteristic::BLECharacteristic(const BLECharacteristic& rhs) +{ + _value = (unsigned char*)malloc(rhs._value_size); // Sid. KW: _value should not make local here + if (_value) { + memcpy(_value, rhs._value, rhs._value_size); + _value_size = rhs._value_size; + } else { + _value_size = 0; + } + memcpy(_uuid_cstr, rhs._uuid_cstr, sizeof(_uuid_cstr)); + _properties = rhs._properties; + memcpy(_event_handlers, rhs._event_handlers, sizeof(_event_handlers)); + _internal = rhs._internal; + _bledev = BLEDevice(&rhs._bledev); +} + +BLECharacteristic& BLECharacteristic::operator= (const BLECharacteristic& rhs) +{ + if (this != &rhs) + { + if (_value) + { + free(_value); + } + _value = (unsigned char*)malloc(rhs._value_size); + if (_value) + { + memcpy(_value, rhs._value, rhs._value_size); + _value_size = rhs._value_size; + } else { + _value_size = 0; + } + memcpy(_uuid_cstr, rhs._uuid_cstr, sizeof(_uuid_cstr)); + _properties = rhs._properties; + memcpy(_event_handlers, rhs._event_handlers, sizeof(_event_handlers)); + _internal = rhs._internal; + _bledev = BLEDevice(&rhs._bledev); + } + return *this; } BLECharacteristic::~BLECharacteristic() @@ -51,13 +94,6 @@ BLECharacteristic::~BLECharacteristic() if (_value) { free(_value); - _value = NULL; - } - - if (_event_handlers != NULL) - { - free(_event_handlers); - _event_handlers = NULL; } } @@ -433,15 +469,7 @@ void BLECharacteristic::setEventHandler(BLECharacteristicEvent event, } else { - if (_event_handlers == NULL) - { - _event_handlers = (BLECharacteristicEventHandler*)malloc(sizeof(BLECharacteristicEventHandler) * BLECharacteristicEventLast); - } - - if (_event_handlers != NULL) - { - _event_handlers[event] = eventHandler; - } + _event_handlers[event] = eventHandler; } } diff --git a/libraries/BLE/src/BLECharacteristic.h b/libraries/BLE/src/BLECharacteristic.h index 0d70b21b..f8a346bc 100644 --- a/libraries/BLE/src/BLECharacteristic.h +++ b/libraries/BLE/src/BLECharacteristic.h @@ -84,6 +84,10 @@ class BLECharacteristic: public BLEAttributeWithValue unsigned char properties, const char* value); + BLECharacteristic(const BLECharacteristic&); + + BLECharacteristic& operator=(const BLECharacteristic&); + virtual ~BLECharacteristic(); /** @@ -518,7 +522,7 @@ class BLECharacteristic: public BLEAttributeWithValue unsigned short _value_size; // The value size unsigned char* _value; // The value. Will delete after create the _internal - BLECharacteristicEventHandler* _event_handlers; + BLECharacteristicEventHandler _event_handlers[BLECharacteristicEventLast]; // Sid. Define the arr as in BLECharacteristicImp.h }; #endif diff --git a/libraries/BLE/src/BLEDescriptor.cpp b/libraries/BLE/src/BLEDescriptor.cpp index 5cdc07b9..47556d32 100644 --- a/libraries/BLE/src/BLEDescriptor.cpp +++ b/libraries/BLE/src/BLEDescriptor.cpp @@ -66,6 +66,41 @@ BLEDescriptor::BLEDescriptor(const char* uuid, BLEDescriptor(uuid, (const unsigned char*)value, strlen(value)) {} +BLEDescriptor::BLEDescriptor(const BLEDescriptor& rhs) +{ + _value = (unsigned char*)malloc(rhs._value_size); // Sid. KW: allocate memory for _value, not local + if (_value) { + memcpy(_value, rhs._value, rhs._value_size); + _value_size = rhs._value_size; + } else { + _value_size = 0; + } + memcpy(_uuid_cstr, rhs._uuid_cstr, sizeof(_uuid_cstr)); + _properties = rhs._properties; + _bledev = BLEDevice(&rhs._bledev); +} + +BLEDescriptor& BLEDescriptor::operator= (const BLEDescriptor& rhs) +{ + if (this != &rhs) + { + if (_value) + { + free(_value); + } + _value = (unsigned char*)malloc(rhs._value_size); + if (_value) + { + memcpy(_value, rhs._value, rhs._value_size); + _value_size = rhs._value_size; + memcpy(_uuid_cstr, rhs._uuid_cstr, sizeof(_uuid_cstr)); + _properties = rhs._properties; + _bledev = BLEDevice(&rhs._bledev); + } + } + return *this; +} + BLEDescriptor::~BLEDescriptor() { if (_value) diff --git a/libraries/BLE/src/BLEDescriptor.h b/libraries/BLE/src/BLEDescriptor.h index 608c3159..faf867c0 100644 --- a/libraries/BLE/src/BLEDescriptor.h +++ b/libraries/BLE/src/BLEDescriptor.h @@ -32,6 +32,8 @@ class BLEDescriptor BLEDescriptor(const char* uuid, const char* value); // create a descriptor the specified uuid and string value BLEDescriptor(BLEDescriptorImp* descriptorImp, const BLEDevice *bleDev); + BLEDescriptor(const BLEDescriptor&); + BLEDescriptor& operator=(const BLEDescriptor&); virtual ~BLEDescriptor(); diff --git a/libraries/BLE/src/internal/BLECallbacks.cpp b/libraries/BLE/src/internal/BLECallbacks.cpp index 80e8ed9e..ca5a5188 100644 --- a/libraries/BLE/src/internal/BLECallbacks.cpp +++ b/libraries/BLE/src/internal/BLECallbacks.cpp @@ -137,7 +137,8 @@ uint8_t profile_read_rsp_process(bt_conn_t *conn, // Get characteristic by handle params->single.handle chrc = BLEProfileManager::instance()->characteristic(bleDevice, params->single.handle); - chrc->setValue((const unsigned char *)data, length); + if (chrc) // KW issue: may be NULL and will be dereferenced + chrc->setValue((const unsigned char *)data, length); pr_debug(LOG_MODULE_BLE, "%s-%d", __FUNCTION__, __LINE__); return BT_GATT_ITER_STOP; } diff --git a/libraries/BLE/src/internal/BLECharacteristicImp.cpp b/libraries/BLE/src/internal/BLECharacteristicImp.cpp index 7183be53..8f3113ce 100644 --- a/libraries/BLE/src/internal/BLECharacteristicImp.cpp +++ b/libraries/BLE/src/internal/BLECharacteristicImp.cpp @@ -44,12 +44,15 @@ BLECharacteristicImp::BLECharacteristicImp(const bt_uuid_t* uuid, { _value_size = BLE_MAX_ATTR_DATA_LEN;// Set as MAX value. TODO: long read/write need to twist _value = (unsigned char*)malloc(_value_size); - if (_value_size > BLE_MAX_ATTR_DATA_LEN) - { - _value_buffer = (unsigned char*)malloc(_value_size); - } - - memset(_value, 0, _value_size); + + // TODO: Enable when max value is not set. + // if (_value_size > BLE_MAX_ATTR_DATA_LEN) + // { + // _value_buffer = (unsigned char*)malloc(_value_size); + // } + + if (_value) + memset(_value, 0, _value_size); memset(&_ccc_cfg, 0, sizeof(_ccc_cfg)); memset(&_ccc_value, 0, sizeof(_ccc_value)); @@ -144,16 +147,8 @@ BLECharacteristicImp::BLECharacteristicImp(BLECharacteristic& characteristic, _sub_params.value |= BT_GATT_CCC_INDICATE; } _gatt_chrc.uuid = (bt_uuid_t*)this->bt_uuid();//&_characteristic_uuid;//this->uuid(); - if (NULL != characteristic._event_handlers) - { - memcpy(_event_handlers, - characteristic._event_handlers, - sizeof(_event_handlers)); - } - else - { - memset(_event_handlers, 0, sizeof(_event_handlers)); - } + + memcpy(_event_handlers, characteristic._event_handlers, sizeof(_event_handlers)); _sub_params.notify = profile_notify_process; @@ -174,12 +169,12 @@ BLECharacteristicImp::~BLECharacteristicImp() releaseDescriptors(); if (_value) { free(_value); - _value = NULL; + _value = (unsigned char *)NULL; } if (_value_buffer) { free(_value_buffer); - _value_buffer = NULL; + _value_buffer = (unsigned char *)NULL; } } @@ -203,10 +198,11 @@ bool BLECharacteristicImp::writeValue(const byte value[], int length) { // Notify for peripheral. status = bt_gatt_notify(NULL, _attr_chrc_value, value, length, NULL); - if (0 == status) - { + // Sid. KW found status is always 0 + // if (!status) + // { retVal = true; - } + // } } //Not schedule write request for central @@ -229,10 +225,11 @@ bool BLECharacteristicImp::writeValue(const byte value[], int length, int offset { // Notify for peripheral. status = bt_gatt_notify(NULL, _attr_chrc_value, value, length, NULL); - if (0 == status) - { + // Sid. KW found status is always 0. + // if (!status) + // { retVal = true; - } + // } } //Not schedule write request for central @@ -612,7 +609,8 @@ void BLECharacteristicImp::setBuffer(const uint8_t value[], uint16_t length, uint16_t offset) { - if (length + offset > _value_size) { + if ((length + offset > _value_size) || + ((unsigned char *)NULL == _value_buffer)) { // Ignore the data return; } @@ -627,6 +625,7 @@ void BLECharacteristicImp::syncupBuffer2Value() void BLECharacteristicImp::discardBuffer() { + if(_value_buffer) memcpy(_value_buffer, _value, _value_size); } diff --git a/libraries/BLE/src/internal/BLEDescriptorImp.cpp b/libraries/BLE/src/internal/BLEDescriptorImp.cpp index df323272..fb7f7e04 100644 --- a/libraries/BLE/src/internal/BLEDescriptorImp.cpp +++ b/libraries/BLE/src/internal/BLEDescriptorImp.cpp @@ -34,7 +34,10 @@ BLEDescriptorImp::BLEDescriptorImp(BLEDevice& bledevice, _value_length = descriptor.valueLength(); _value = (unsigned char*)malloc(_value_length); - memcpy(_value, descriptor.value(), _value_length); + if (_value) + memcpy(_value, descriptor.value(), _value_length); + else + _value_length = 0; } BLEDescriptorImp::BLEDescriptorImp(const bt_uuid_t* uuid, @@ -48,13 +51,57 @@ BLEDescriptorImp::BLEDescriptorImp(const bt_uuid_t* uuid, _value_length = BLE_MAX_ATTR_DATA_LEN; _value = (unsigned char*)malloc(_value_length); - memset(_value, 0, _value_length); + if (_value) + memset(_value, 0, _value_length); + else + _value_length = 0; +} + + +BLEDescriptorImp::BLEDescriptorImp(const BLEDescriptorImp& rhs) : + BLEAttribute(rhs) +{ + _value_length = rhs._value_length; + _value = (unsigned char *)malloc(_value_length); + if (_value) + memcpy(_value, rhs._value, sizeof(_value_length)); + else + _value_length = 0; + + _value_handle = rhs._value_handle; + _properties = rhs._properties; + _descriptor_uuid = rhs._descriptor_uuid; + _bledev = BLEDevice(&rhs._bledev); +} + + +BLEDescriptorImp& BLEDescriptorImp::operator=(const BLEDescriptorImp& that) +{ + if (this != &that) { + + BLEAttribute::operator=(that); + if (_value) + free(_value); + + _value_length = that._value_length; + _value = (unsigned char *)malloc(_value_length); + if (_value) + memcpy(_value, that._value, sizeof(_value_length)); + else + _value_length = 0; + + _value_handle = that._value_handle; + _properties = that._properties; + _descriptor_uuid = that._descriptor_uuid; + _bledev = BLEDevice(&that._bledev); + } + return *this; } BLEDescriptorImp::~BLEDescriptorImp() { - if (_value) { + if (_value != (unsigned char *)NULL) { free(_value); - _value = NULL; + _value = (unsigned char *)NULL; } } diff --git a/libraries/BLE/src/internal/BLEDescriptorImp.h b/libraries/BLE/src/internal/BLEDescriptorImp.h index 3282333f..bfd2635b 100644 --- a/libraries/BLE/src/internal/BLEDescriptorImp.h +++ b/libraries/BLE/src/internal/BLEDescriptorImp.h @@ -40,6 +40,10 @@ class BLEDescriptorImp: public BLEAttribute{ uint16_t handle, BLEDevice& bledevice); + BLEDescriptorImp(const BLEDescriptorImp& rhs); + + BLEDescriptorImp& operator=(const BLEDescriptorImp& that); + virtual ~BLEDescriptorImp(); /** diff --git a/libraries/BLE/src/internal/BLEDeviceManager.cpp b/libraries/BLE/src/internal/BLEDeviceManager.cpp index 188c9561..777ae59e 100644 --- a/libraries/BLE/src/internal/BLEDeviceManager.cpp +++ b/libraries/BLE/src/internal/BLEDeviceManager.cpp @@ -300,13 +300,14 @@ BLEDeviceManager::_advDataInit(void) length = UUID_SIZE_16; type = BT_DATA_UUID16_ALL; } - else if (BT_UUID_TYPE_128 == _service_uuid.uuid.type) + else // Sid. KW, default is BT_UUID_TYPE_128 { data = _service_uuid.val; length = UUID_SIZE_128; type = BT_DATA_UUID128_ALL; } - if (NULL != data) + + // if (data) // Sid. KW, data is always initialized { _adv_data[_adv_data_idx].type = type; _adv_data[_adv_data_idx].data = data; @@ -332,13 +333,13 @@ BLEDeviceManager::_advDataInit(void) length = UUID_SIZE_16; type = BT_DATA_SOLICIT16; } - else if (BT_UUID_TYPE_128 == _service_solicit_uuid.uuid.type) + else // Sid. KW, default is BT_UUID_TYPE_128 { data = _service_solicit_uuid.val; length = UUID_SIZE_128; type = BT_DATA_SOLICIT128; } - if (NULL != data) + // Sid. KW, data is always initialized. if (data) { _adv_data[_adv_data_idx].type = type; _adv_data[_adv_data_idx].data = data; @@ -500,7 +501,8 @@ bool BLEDeviceManager::startScanningWithDuplicates() bool BLEDeviceManager::stopScanning() { int err = bt_le_scan_stop(); - if (0 != err) + + if (err) // Sid. TODO: KW detected bt_le_scan_stop return only 0. { pr_info(LOG_MODULE_BLE, "Stop LE scan failed (err %d)\n", err); return false; @@ -536,7 +538,7 @@ void BLEDeviceManager::setAdvertiseCritical(BLEService& service) length = UUID_SIZE_16; type = BT_DATA_UUID16_ALL; } - else if (BT_UUID_TYPE_128 == _adv_critical_service_uuid.uuid.type) + else // Sid. KW, default is BT_UUID_TYPE_128 { data = _adv_critical_service_uuid.val; length = UUID_SIZE_128; @@ -575,7 +577,7 @@ bool BLEDeviceManager::hasLocalName(const BLEDevice* device) const return false; } - if ((len + 1 > adv_data_len) || (adv_data_len < 2)) { + if ((len + 1) > adv_data_len) { // Sid. KW, can't be (adv_data_len < 2) pr_info(LOG_MODULE_BLE, "AD malformed\n"); return false; } @@ -675,7 +677,7 @@ int BLEDeviceManager::advertisedServiceUuidCount(const BLEDevice* device) const return service_cnt; } - if ((len + 1 > adv_data_len) || (adv_data_len < 2)) { + if ((len + 1) > adv_data_len) { // Sid. KW, can't be (adv_data_len < 2) pr_info(LOG_MODULE_BLE, "AD malformed\n"); return service_cnt; } @@ -706,38 +708,44 @@ String BLEDeviceManager::localName(const BLEDevice* device) const getDeviceAdvertiseBuffer(device->bt_le_address(), adv_data, adv_data_len); - if (NULL == adv_data) - { - return localname_string; + + if (NULL == adv_data) { + String temp(localname_string); + return temp; } - + while (adv_data_len > 1) { uint8_t len = adv_data[0]; uint8_t type = adv_data[1]; /* Check for early termination */ - if (len == 0) - { - return localname_string; + if (len == 0) { + String temp(localname_string); + return temp; } - if ((len + 1 > adv_data_len) || (adv_data_len < 2)) { + if ((len + 1) > adv_data_len) { // Sid. KW, cannot be (adv_data_len < 2) pr_info(LOG_MODULE_BLE, "AD malformed\n"); - return localname_string; + String temp(localname_string); + return temp; } if (type == BT_DATA_NAME_COMPLETE) { - memcpy(localname_string, &adv_data[2], len - 1); - //localname_string[len - 1] = '\0'; + if (len >= BLE_MAX_ADV_SIZE) + len = BLE_MAX_ADV_SIZE-1; + memcpy(localname_string, &adv_data[2], len); + localname_string[len] = '\0'; break; } adv_data_len -= len + 1; adv_data += len + 1; } - return localname_string; + + String temp(localname_string); + return temp; } void BLEDeviceManager::advertisedServiceUuid(const BLEDevice* device, char *buf) const @@ -766,7 +774,7 @@ void BLEDeviceManager::advertisedServiceUuid(const BLEDevice* device, int index, adv_data, adv_data_len); - if (NULL == adv_data) + if ((uint8_t *)NULL == adv_data) { return; } @@ -782,7 +790,7 @@ void BLEDeviceManager::advertisedServiceUuid(const BLEDevice* device, int index, return; } - if ((len + 1 > adv_data_len) || (adv_data_len < 2)) { + if ((len + 1) > adv_data_len) { // Sid. KW, cannot be adv_data_len < 2 pr_info(LOG_MODULE_BLE, "AD malformed\n"); return; } @@ -901,7 +909,7 @@ bool BLEDeviceManager::connectToDevice(BLEDevice &device) } else { - if (NULL == unused) + if (NULL == unused) { unused = temp; // Buffer the ADV data @@ -1175,7 +1183,7 @@ void BLEDeviceManager::handleDeviceFound(const bt_addr_le_t *addr, return; } - if ((len + 1 > data_len) || (data_len < 2)) { + if ((len + 1) > data_len) { // Sid. KW, cannot be (data_len < 2) pr_info(LOG_MODULE_BLE, "AD malformed\n"); return; } diff --git a/libraries/BLE/src/internal/BLEDeviceManager.h b/libraries/BLE/src/internal/BLEDeviceManager.h index cb8fdbd7..06f2c97b 100644 --- a/libraries/BLE/src/internal/BLEDeviceManager.h +++ b/libraries/BLE/src/internal/BLEDeviceManager.h @@ -395,7 +395,7 @@ class BLEDeviceManager // ADV data for peripheral uint8_t _adv_type; - bt_data_t _adv_data[4]; + bt_data_t _adv_data[5]; // KW: fount _advDataInit() can use 5 slots. size_t _adv_data_idx; String _local_name; diff --git a/libraries/BLE/src/internal/BLEProfileManager.cpp b/libraries/BLE/src/internal/BLEProfileManager.cpp index a013f546..d1efb06d 100644 --- a/libraries/BLE/src/internal/BLEProfileManager.cpp +++ b/libraries/BLE/src/internal/BLEProfileManager.cpp @@ -68,9 +68,10 @@ BLEProfileManager::BLEProfileManager (): BLEProfileManager::~BLEProfileManager (void) { - if (this->_attr_base) + if (_attr_base) { - free(this->_attr_base); + free(_attr_base); + _attr_base = (bt_gatt_attr_t *)NULL; } ServiceReadLinkNodePtr node = link_node_get_first(&_read_service_header); while (NULL != node) @@ -248,12 +249,13 @@ int BLEProfileManager::registerProfile(BLEDevice &bledevice) if (NULL == _attr_base) { _attr_base = (bt_gatt_attr_t *)malloc(attr_counter * sizeof(bt_gatt_attr_t)); - memset(_attr_base, 0x00, (attr_counter * sizeof(bt_gatt_attr_t))); - pr_info(LOG_MODULE_BLE, "_attr_base_-%p, size-%d, attr_counter-%d", _attr_base, sizeof(_attr_base), attr_counter); - if (NULL == _attr_base) - { + if (NULL == _attr_base) { err_code = BLE_STATUS_NO_MEMORY; } + else { + memset((void *)_attr_base, 0x00, (attr_counter * sizeof(bt_gatt_attr_t))); + pr_info(LOG_MODULE_BLE, "_attr_base_-%p, size-%d, attr_counter-%d", _attr_base, sizeof(_attr_base), attr_counter); + } } if (BLE_STATUS_SUCCESS != err_code) @@ -563,7 +565,7 @@ bool BLEProfileManager::discoverAttributes(BLEDevice* device) } // Block it _start_discover = true; - while (_start_discover) + while (_start_discover) // Sid. KW warning acknowldged { delay(10); } @@ -759,12 +761,17 @@ void BLEProfileManager::serviceDiscoverComplete(const BLEDevice &bledevice) serviceCurImp = node->value; if (NULL != serviceCurImp) { + if (servicePrevImp) // KW issue: Chk for NULL. servicePrevImp->setEndHandle(serviceCurImp->startHandle() - 1); } - pr_debug(LOG_MODULE_BLE, "Curr: start-%d, end-%d", servicePrevImp->startHandle(), servicePrevImp->endHandle()); - servicePrevImp = serviceCurImp; - pr_debug(LOG_MODULE_BLE, "Curr: start-%d, end-%d", servicePrevImp->startHandle(), servicePrevImp->endHandle()); + if (servicePrevImp) + { + pr_debug(LOG_MODULE_BLE, "Curr: start-%d, end-%d", servicePrevImp->startHandle(), servicePrevImp->endHandle()); + } + servicePrevImp = serviceCurImp; + if (servicePrevImp) // KW issue: Chk for NULL. + pr_debug(LOG_MODULE_BLE, "Curr: start-%d, end-%d", servicePrevImp->startHandle(), servicePrevImp->endHandle()); node = node->next; } return; diff --git a/libraries/BLE/src/internal/LinkList.h b/libraries/BLE/src/internal/LinkList.h index 65c34fe4..04f51bba 100644 --- a/libraries/BLE/src/internal/LinkList.h +++ b/libraries/BLE/src/internal/LinkList.h @@ -9,8 +9,11 @@ template struct LinkNode { template LinkNode* link_node_create(T value) { LinkNode* node = (LinkNode*)malloc(sizeof(LinkNode)); - node->value = value; - node->next = NULL; + + if (node) { + node->value = value; + node->next = NULL; + } return node; } diff --git a/system/libarc32_arduino101/framework/src/infra/port.c b/system/libarc32_arduino101/framework/src/infra/port.c index b719d1d6..d150ccd3 100644 --- a/system/libarc32_arduino101/framework/src/infra/port.c +++ b/system/libarc32_arduino101/framework/src/infra/port.c @@ -129,7 +129,10 @@ static struct port * get_port(uint16_t port_id) void port_set_queue(uint16_t port_id, void * queue) { struct port * p = get_port(port_id); - p->queue = queue; + if (p) + { + p->queue = queue; + } } #ifdef CONFIG_INFRA_IS_MASTER @@ -176,8 +179,11 @@ uint16_t port_alloc(void *queue) void port_set_handler(uint16_t port_id, void (*handler)(struct message*, void*), void *param) { struct port * port = get_port(port_id); - port->handle_message = handler; - port->handle_param = param; + if (port) + { + port->handle_message = handler; + port->handle_param = param; + } } struct message * message_alloc(int size, OS_ERR_TYPE * err) @@ -193,7 +199,7 @@ struct message * message_alloc(int size, OS_ERR_TYPE * err) void port_process_message(struct message * msg) { struct port * p = get_port(msg->dst_port_id); - if (p->handle_message != NULL) { + if (p && p->handle_message != NULL) { p->handle_message(msg, p->handle_param); } } @@ -201,19 +207,32 @@ void port_process_message(struct message * msg) void port_set_cpu_id(uint16_t port_id, uint8_t cpu_id) { struct port * p = get_port(port_id); - p->cpu_id = cpu_id; + if (p) + { + p->cpu_id = cpu_id; + } } void port_set_port_id(uint16_t port_id) { struct port * p = get_port(port_id); - p->id = port_id; + if (p) + { + p->id = port_id; + } } uint8_t port_get_cpu_id(uint16_t port_id) { struct port * p = get_port(port_id); - return p->cpu_id; + if (p) + { + return p->cpu_id; + } + else + { + return 0; + } } #ifdef INFRA_MULTI_CPU_SUPPORT @@ -258,7 +277,7 @@ int port_send_message(struct message * message) pr_info(LOG_MODULE_MAIN, "Sending message %p to port %p(q:%p) ret: %d", message, port, port->queue, err); #endif struct port *src_port = get_port(MESSAGE_SRC(message)); - if (src_port->cpu_id == get_cpu_id()) { + if (src_port && src_port->cpu_id == get_cpu_id()) { /* We bypass the software queue here and process directly * due to lack of background thread on this implementation */ @@ -278,6 +297,10 @@ int port_send_message(struct message * message) void message_free(struct message * msg) { struct port * port = get_port(MESSAGE_SRC(msg)); + if (!port) + { + return; + } pr_debug(LOG_MODULE_MAIN, "free message %p: port %p[%d] this %d id %d", msg, port, port->cpu_id, get_cpu_id(), MESSAGE_SRC(msg)); if (port->cpu_id == get_cpu_id()) { @@ -291,8 +314,12 @@ void message_free(struct message * msg) int port_send_message(struct message * msg) { - struct port * port = get_port(MESSAGE_DST(msg)); OS_ERR_TYPE err; + struct port * port = get_port(MESSAGE_DST(msg)); + if (!port) + { + return E_OS_ERR_NO_MEMORY; + } if (src_port->cpu_id == get_cpu_id()) { /* We bypass the software queue here and process directly * due to lack of background thread on this implementation diff --git a/system/libarc32_arduino101/framework/src/os/panic.c b/system/libarc32_arduino101/framework/src/os/panic.c index 214d5bb5..c77fc501 100644 --- a/system/libarc32_arduino101/framework/src/os/panic.c +++ b/system/libarc32_arduino101/framework/src/os/panic.c @@ -27,6 +27,6 @@ void __attribute__((weak)) _Fault(void) ARC_V2_ECR_CODE(ecr), ARC_V2_ECR_PARAMETER(ecr)); pr_error(0, "Address 0x%x\n", exc_addr); - while (1); + while (1); // Sid. Acknowledge KW warning. } diff --git a/system/libarc32_arduino101/framework/src/services/ble_service/ble_service.c b/system/libarc32_arduino101/framework/src/services/ble_service/ble_service.c index a056a643..07231cb1 100644 --- a/system/libarc32_arduino101/framework/src/services/ble_service/ble_service.c +++ b/system/libarc32_arduino101/framework/src/services/ble_service/ble_service.c @@ -194,7 +194,7 @@ static void handle_ble_disable(struct ble_enable_req *req, struct _ble_service_c resp = (void *)cfw_alloc_rsp_msg(&req->header, MSG_ID_BLE_ENABLE_RSP, sizeof(*resp)); - cfw_send_message(resp); + cfw_send_message(resp); // Sid. KW warning ack. } @@ -227,7 +227,7 @@ static void ble_service_message_handler(struct cfw_message *msg, void *param) MSG_ID_BLE_ENABLE_RSP, sizeof(*resp)); resp->status = -EINPROGRESS; resp->enable = 0; - cfw_send_message(resp); + cfw_send_message(resp); // Sid. KW warning ack. } } break; diff --git a/variants/libarc32drv_arduino101.a b/variants/libarc32drv_arduino101.a index 5edc114bf16b1da00ac9836cf60828362c4743b4..7fe479f88a350102124b372c7564ea5ae4571a31 100644 GIT binary patch delta 7160 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 delta 6943 zcmbW64Rlo1wa3r7GdG!;ldoZtnMr1l3HbyknIT3hpTMygktrk*>>n^~*yZu75Rm#YN0r@t5vU z#7OGb@t@0E4)zM&6-l*OHe^eWsPI~$<*^J^Xr5}>aE)MIVCipGJrdrwoOP)}${#I3 zdHu0o8p^B(h7WnTBR9^nK6`^ITz<@&Jbp-UMSSmAySJ!9UxV#R@{r(;TwY=eRSXHP zNXIX11Ijquk!Rkv9anlmk8f=?sY-}m6f7+r+i9=Q)9R}sH%Y8b$e;Z5M9t!3DJyq% zb)MRt0Y{R=f|UHp)l&@p*{|`;??5$#z@O-lHCj?d-F1udDP49ksH&S*TA zG-e;UfjQSkRl9W*+gVBa&WiYfV--YIS=yMZ4%TCZM7!|7mY3o(hB4<)qyCmDmb7@g z=3JiwGwh;P>stvucG1QECsH>|*i8HsG^7ciyKkC_F)MY;PwR-1KZUJnVmBWPl}_Q~ zGhn_`l<{o%nN!@xH^7%p;V#~AmZGp^t+@LE#y37h(O9zSi*H&(-;Ft|rD~3fQ0fx7 zp{K6X8FTNEEzC;+Gi;1`c&%prma!2e@E-ckB?@mve;=8=X9-nUeDuY8{-#kqXc|L` zjbSehiPuK+&q2ie@fUe5^f^Vsl*qR-bS#GRuqn<+63K~XWGwIQ6)PBzPz#l;r9Ww5 z{8vU;25Bu2bc;Y%FL613c(@L|^P--;hG*6?#@~Wg+vJp9~Xhlj7dro*$z z2}>r%{T|+N3NNfp7jdrlNsI-b84P}Z3B2nT*Fi8{h`=!;)Qz4z9E>-v>6p<4jpexU zq~M4Xr!?gbeL=ZXqftM`sO!n(Ur;(;%SJ}6{^;~Tj3r++n7rTURX(@@a*sLva?okO zaEhfKpk%yGMqCOFh4c-ET)H;o(jBpo*l4a8PiYH=^6)P$#-&~{ZhL0*@qPD4Y}Yoe z@>37|d*jjz$PD7=K@2QpW2n*0jn+}rN;4*dz=}6}F1pe5T+L$BwGH0P5F_-lIBR#p zXBi@AYJgn1lT+177#@$yDj1y#PHqgUkea#sAM`}&GN8^QvTQUW7DhV&Ydj)7R1^&? z<;H`oK%0mEfL+`mTh3FBrFbdhW&ARdwrOQ?^d^*Zc^6{cj~^V)jf)pBK4CCv^m6xL zr-v}FhyNJwn}<*5K@SI~SNI(lQ6Bg2xNda0%Ex~OcX-8!{2YvlHkeomje(y@LnSt0 zN=b-cO2eE1VXyEkY{9l<8WD@OS)QY1+eRX78p+WGS4Pd2^>hc4&yz|Q6l;nFy-o>e ztjQG3V*bmH4*DOB3?DA;(?%6DQ_@YMOiXoXF2)K3xAS3f+<{MBw&<~z037YNru%|A zGg;7Is$(8q$1&o`s`N1q3M$$*ntR~Tqq@Utc)20lCytplw#))MyTzDDFjG`%+s^W0 z=*SXPP!YDK!CDcY2_2hsFD}&hH0a8T6EN_caBaJ)2{^b@^TV>UT)^7LbpaipatHXn zwib7G{8s`L<%qjWqx%_N)sPu zf|6#N#&_WG+X9v?-|Sy>;5E&xH2x|Q86_Gm(9k0sFy{r`4!dr~C48Vg%2H&Ev%FV`xSV!+-BZCr)f;4Z{=S)HSw~ zeI;!-*h|D*z6F+-V3O9z<`Qwg#zz7#6K~@CLvNXIrQd_T_VKZRRakkVE19t;-~u*R zc}b#k0}E}<9^8(Kv8G9G&&Mw;RroP{W+JEDOe$mZ@#86t%qSPL^+Y;N4x1S>XEN3W zZ&Zo^{}8^c6fS-a^eTLjxeAq4IG*9SS>J`P7G^w4_Tp~8lo(^mV0o3eC4Ck)|4vG+ z$RA368jfO;oZq4}7LNvsNW~RYMx4l)6MLT|xe&%o#NNllyouO*6FfW-d*2AJVwJcb zW1M){QT%cE%S16ZbRMPEB;kMta6s+Exa&~aBK32qhh>;R-YNCR(dQM(AL7tnC8iS{ z%WtJlvF{OMmwx0Aq^_g?>39~3cDw@v{zMIF(OCAKbiy$)GaiE!{38r@5u>;muQgNZ z_oALljIq_|e}nYtN1qAOX9xOBls>iSQzLmH_RsMDtI^;U*zk~KAM#qsG#54#j%BELe61G*nO*iTGP0?QF%95g-RW73BP-6!=#oGNFCLl{8w@6Ti)(;5(X-c$7RFa=y#`Ndi7c)xgB|(yIQiV|`9C#eONd8SAUW0>7x>10kk|A3w-=i9#B=*g#v35s*3P#8|$< ze#O5`@xNK|nWFGC#h>;pa{`Tqu}0z$8nloX$Ohs?lE)&`#%2VpQGC`b{HWspv}9Zc zY=`75+CFK!q;g0y4g7tDKae~c^^X)jCAk3gGYX%ROs}~Y zV;sV_d&a(z4JF8bRanCfmj*$Ti1u(|TdBzhbAz-Q0 z@cENLn|QTsAg+;2tDs2!~j7XDg57(8&Urtq!`B~H(|qRh0jP{hI+rk^a?_Qco_9>6#iavE9!K3lFvwFyW~P- zx8x8RUTM@LXGx|(%>F&M!mWYtyXxlPTU%b?x6C-?5B-P9zL8_ z5pADX(uirjVhIo@#kh)C?hkhm)6B=xLTr!m!^Fd4{1~x(``kgC8vD71?Ii;zEtNkI z(_w(+D=o5Sy4b}NPmoB*9hQ@kf_m|Llh%Jde0L}Qdgz6zGsW#ZAD)^iu7~b`o(h(8 zT8gz8?=4+GIb@#GMu4x;WQNpPBAZu0=`4}QeK3!zRq!ZP)vyOEZL1$Xnk91K;1*%# z<(;Q*cR;_}l%nnS!|2%}mmh$(i#8YJCyEs9H9xeXwyoY~hDR>iQejcO&CdPs4mp}( z^Bmy<%N%q(;)jemsCl7s4)$@<5BHE6B$qtxtRG%S6FTMyH(c>sQnX8cur;94M@n^r zNZ|}-HHbV2HVEvh0k0teUP3`nz=g9aI?CF^))f7b0>s=*`^}wr5B1?DT;&`!hGv2oW3aqUFZLY}Ge~t3?s9Z4@ zyDf&gxtM1LZlUJR%sK~jpsIad1-sCh&xXU;s+}9z`Pb=Aury;2{w(x_;msl|@xVmv z3}-6se5IYspY!NTCNp*x<~ey5-P2}FT#E<%)1AvP@sVbs{#w6(o=A~@<)J?-q(=(x j6Q}az&-ptGEv`uZHj!GU3Nw$2E5_eDq8>}mhyn3`%&J)t From 48ed67ed283a558f1adf81e14dbcf7fcf0536e16 Mon Sep 17 00:00:00 2001 From: Sidney Leung Date: Tue, 15 Nov 2016 00:53:28 -0800 Subject: [PATCH 21/22] Added compiled driver binary image. --- variants/arduino_101/libarc32drv_arduino101.a | Bin 782484 -> 786392 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/variants/arduino_101/libarc32drv_arduino101.a b/variants/arduino_101/libarc32drv_arduino101.a index 74a50048d52f2439556a9c08bfcd4129795adff6..c87e0b5268c8d606c9224ea56269417c5b8841c1 100644 GIT binary patch delta 95866 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 Date: Tue, 15 Nov 2016 01:05:41 -0800 Subject: [PATCH 22/22] Corrected KW issue. Mods: - A KW issue that turns out to be a bug in BLEProfile::attribute. It shunted the if check and caused the routine to bail early with nothing found. --- libraries/CurieBLE/src/BLEProfile.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/CurieBLE/src/BLEProfile.cpp b/libraries/CurieBLE/src/BLEProfile.cpp index d333132f..0ffa12ff 100644 --- a/libraries/CurieBLE/src/BLEProfile.cpp +++ b/libraries/CurieBLE/src/BLEProfile.cpp @@ -595,7 +595,7 @@ BLEAttribute *BLEProfile::attribute(const bt_uuid_t* uuid) continue; } chrc_tmp = (BLECharacteristic *)attr_tmp; - if (chrc_tmp->uuid() == uuid); + if (chrc_tmp->uuid() == uuid) { att_found = true; break;