From 2ccef693262a7b335e777f9b8923a9403785dbe1 Mon Sep 17 00:00:00 2001 From: lianggao Date: Fri, 29 Jul 2016 22:19:36 +0800 Subject: [PATCH 01/14] Add BLE central function, debug interface and refactor peripheral -Add debug interface on Serial1 -Update BLE stack and need update BLE's FW -Reconstruct the BLE peripheral base on V3 -Implement the BLE Central Role base on V3 -Implement some sketches for new BLE library -Add central read/write example -Add set advertising parameter interface -Add API to allow set up advertising after setup -Add interface to set the device name File description Porting from V3 system/libarc32_arduino101/common/atomic.h system/libarc32_arduino101/common/misc/byteorder.h system/libarc32_arduino101/drivers/atomic_native.c system/libarc32_arduino101/drivers/bluetooth/att.h system/libarc32_arduino101/drivers/bluetooth/bluetooth.h system/libarc32_arduino101/drivers/bluetooth/conn.h system/libarc32_arduino101/drivers/bluetooth/conn_internal.h system/libarc32_arduino101/drivers/bluetooth/gatt.h system/libarc32_arduino101/drivers/bluetooth/hci.h system/libarc32_arduino101/drivers/bluetooth/uuid.h system/libarc32_arduino101/drivers/rpc/rpc.h system/libarc32_arduino101/drivers/rpc/rpc_deserialize.c system/libarc32_arduino101/drivers/rpc/rpc_functions_to_ble_core.h system/libarc32_arduino101/drivers/rpc/rpc_functions_to_quark.h system/libarc32_arduino101/drivers/rpc/rpc_serialize.c system/libarc32_arduino101/framework/include/util/misc.h system/libarc32_arduino101/framework/src/os/panic.c system/libarc32_arduino101/framework/src/services/ble/conn.c system/libarc32_arduino101/framework/src/services/ble/conn_internal.h system/libarc32_arduino101/framework/src/services/ble/dtm_tcmd.c system/libarc32_arduino101/framework/src/services/ble/gap.c system/libarc32_arduino101/framework/src/services/ble/gatt.c system/libarc32_arduino101/framework/src/services/ble/hci_core.h system/libarc32_arduino101/framework/src/services/ble/l2cap.c system/libarc32_arduino101/framework/src/services/ble/l2cap_internal.h system/libarc32_arduino101/framework/src/services/ble/smp.h system/libarc32_arduino101/framework/src/services/ble/smp_null.c system/libarc32_arduino101/framework/src/services/ble/uuid.c system/libarc32_arduino101/framework/src/services/ble_service/ble_service.c system/libarc32_arduino101/framework/src/services/ble_service/ble_service_api.c system/libarc32_arduino101/framework/src/services/ble_service/ble_service_int.h system/libarc32_arduino101/framework/src/services/ble_service/ble_service_internal.h system/libarc32_arduino101/framework/src/services/ble_service/ble_service_utils.c system/libarc32_arduino101/framework/src/services/ble_service/gap_internal.h system/libarc32_arduino101/framework/src/services/ble_service/gatt_internal.h system/libarc32_arduino101/framework/src/services/ble_service/nble_driver.c --- README.md | 18 + .../arduino/printk.cpp | 28 +- .../BatteryMonitor/BatteryMonitor.ino | 2 +- .../examples/CallbackLED/CallbackLED.ino | 6 +- .../examples/IMUBleCentral/IMUBleCentral.ino | 143 ++ .../IMUBleNotification/IMUBleNotification.ino | 120 ++ libraries/CurieBLE/examples/LED/LED.ino | 2 +- .../examples/LEDCentral/LEDCentral.ino | 157 ++ .../CurieBLE/examples/MIDIBLE/MIDIBLE.ino | 6 +- .../CurieBLE/examples/Scanning/Scanning.ino | 129 ++ libraries/CurieBLE/src/BLEAttribute.cpp | 59 +- libraries/CurieBLE/src/BLEAttribute.h | 39 +- libraries/CurieBLE/src/BLECentral.cpp | 119 +- libraries/CurieBLE/src/BLECentral.h | 153 +- libraries/CurieBLE/src/BLECentralHelper.cpp | 103 ++ libraries/CurieBLE/src/BLECentralHelper.h | 72 + libraries/CurieBLE/src/BLECentralRole.cpp | 292 +++ libraries/CurieBLE/src/BLECentralRole.h | 276 +++ libraries/CurieBLE/src/BLECharacteristic.cpp | 341 ++-- libraries/CurieBLE/src/BLECharacteristic.h | 164 +- libraries/CurieBLE/src/BLECommon.h | 42 +- libraries/CurieBLE/src/BLEDescriptor.cpp | 20 - libraries/CurieBLE/src/BLEDescriptor.h | 1 - libraries/CurieBLE/src/BLEHelper.cpp | 135 ++ libraries/CurieBLE/src/BLEHelper.h | 79 + libraries/CurieBLE/src/BLEPeripheral.cpp | 479 ++--- libraries/CurieBLE/src/BLEPeripheral.h | 139 +- .../CurieBLE/src/BLEPeripheralHelper.cpp | 117 ++ libraries/CurieBLE/src/BLEPeripheralHelper.h | 84 + libraries/CurieBLE/src/BLEPeripheralRole.cpp | 278 +++ libraries/CurieBLE/src/BLEPeripheralRole.h | 262 +++ libraries/CurieBLE/src/BLEProfile.cpp | 535 ++++++ libraries/CurieBLE/src/BLEProfile.h | 189 ++ libraries/CurieBLE/src/BLERoleBase.cpp | 86 + libraries/CurieBLE/src/BLERoleBase.h | 133 ++ libraries/CurieBLE/src/BLEService.cpp | 30 +- libraries/CurieBLE/src/BLEService.h | 14 +- libraries/CurieBLE/src/BLEUuid.cpp | 54 - libraries/CurieBLE/src/BLEUuid.h | 36 - libraries/CurieBLE/src/CurieBLE.h | 2 + libraries/CurieBLE/src/internal/ble_client.c | 832 +-------- libraries/CurieBLE/src/internal/ble_client.h | 63 +- platform.txt | 4 +- system/libarc32_arduino101/Makefile | 21 +- system/libarc32_arduino101/common/atomic.h | 156 ++ .../common/misc/byteorder.h | 44 + system/libarc32_arduino101/common/misc/util.h | 5 + .../drivers/atomic_native.c | 370 ++++ .../drivers/bluetooth/att.h | 55 + .../drivers/bluetooth/bluetooth.h | 353 ++++ .../drivers/bluetooth/conn.h | 399 +++++ .../drivers/bluetooth/conn_internal.h | 125 ++ .../drivers/bluetooth/gatt.h | 1045 +++++++++++ .../drivers/bluetooth/hci.h | 683 ++++++++ .../drivers/bluetooth/uuid.h | 463 +++++ .../drivers/ipc_uart_ns16550.c | 423 +++-- .../drivers/ipc_uart_ns16550.h | 70 +- system/libarc32_arduino101/drivers/rpc/rpc.h | 152 ++ .../drivers/rpc/rpc_deserialize.c | 457 +++++ .../drivers/rpc/rpc_functions_to_ble_core.h | 106 ++ .../drivers/rpc/rpc_functions_to_quark.h | 124 ++ .../drivers/rpc/rpc_serialize.c | 323 ++++ .../framework/include/cfw/cfw.h | 2 +- .../framework/include/cfw/cfw_client.h | 16 - .../framework/include/cfw_platform.h | 1 - .../framework/include/infra/ipc_uart.h | 19 - .../framework/include/infra/log.h | 10 +- .../framework/include/log_modules | 1 + .../framework/include/panic_api.h | 2 +- .../include/services/ble/ble_service.h | 210 ++- .../services/ble/ble_service_gap_api.h | 1036 ----------- .../include/services/ble/ble_service_gatt.h | 325 ---- .../services/ble/ble_service_gattc_api.h | 372 ---- .../services/ble/ble_service_gatts_api.h | 559 ------ .../framework/include/services/services_ids.h | 1 + .../framework/include/util/misc.h | 110 ++ .../framework/src/cfw/service_api.c | 48 - .../framework/src/cfw_platform.c | 21 +- .../framework/src/infra/log.c | 8 +- .../framework/src/infra/log_impl_printk.c | 5 +- .../framework/src/nordic_interface.c | 192 -- .../libarc32_arduino101/framework/src/os/os.c | 3 + .../framework/src/os/panic.c | 16 + .../framework/src/services/ble/ble_protocol.h | 233 --- .../src/services/ble/ble_service_core_int.h | 207 --- .../src/services/ble/ble_service_gap_api.c | 384 ---- .../src/services/ble/ble_service_gatt_int.h | 102 -- .../src/services/ble/ble_service_gatts_api.c | 287 --- .../framework/src/services/ble/conn.c | 927 ++++++++++ .../src/services/ble/conn_internal.h | 125 ++ .../framework/src/services/ble/dtm_tcmd.c | 12 + .../framework/src/services/ble/gap.c | 912 ++++++++++ .../framework/src/services/ble/gatt.c | 1561 +++++++++++++++++ .../framework/src/services/ble/hci_core.h | 70 + .../framework/src/services/ble/l2cap.c | 62 + .../src/services/ble/l2cap_internal.h | 27 + .../framework/src/services/ble/smp.h | 100 ++ .../framework/src/services/ble/smp_null.c | 118 ++ .../framework/src/services/ble/uuid.c | 135 ++ .../services/ble_service/ble_protocol.h} | 35 +- .../src/services/ble_service/ble_service.c | 297 ++++ .../services/ble_service/ble_service_api.c | 66 + .../services/ble_service/ble_service_int.h | 67 + .../ble_service/ble_service_internal.h | 79 + .../{ble => ble_service}/ble_service_utils.c | 93 +- .../{ble => ble_service}/ble_service_utils.h | 72 +- .../src/services/ble_service/gap_internal.h | 629 +++++++ .../src/services/ble_service/gatt_internal.h | 308 ++++ .../src/services/ble_service/nble_driver.c | 354 ++++ .../src/services/ble_service/nble_driver.h | 55 + variants/arduino_101/variant.cpp | 10 + 111 files changed, 15785 insertions(+), 5716 deletions(-) rename system/libarc32_arduino101/framework/src/nordic_interface.h => cores/arduino/printk.cpp (79%) create mode 100644 libraries/CurieBLE/examples/IMUBleCentral/IMUBleCentral.ino create mode 100644 libraries/CurieBLE/examples/IMUBleNotification/IMUBleNotification.ino create mode 100644 libraries/CurieBLE/examples/LEDCentral/LEDCentral.ino create mode 100644 libraries/CurieBLE/examples/Scanning/Scanning.ino create mode 100644 libraries/CurieBLE/src/BLECentralHelper.cpp create mode 100644 libraries/CurieBLE/src/BLECentralHelper.h create mode 100644 libraries/CurieBLE/src/BLECentralRole.cpp create mode 100644 libraries/CurieBLE/src/BLECentralRole.h create mode 100644 libraries/CurieBLE/src/BLEHelper.cpp create mode 100644 libraries/CurieBLE/src/BLEHelper.h create mode 100644 libraries/CurieBLE/src/BLEPeripheralHelper.cpp create mode 100644 libraries/CurieBLE/src/BLEPeripheralHelper.h create mode 100644 libraries/CurieBLE/src/BLEPeripheralRole.cpp create mode 100644 libraries/CurieBLE/src/BLEPeripheralRole.h create mode 100644 libraries/CurieBLE/src/BLEProfile.cpp create mode 100644 libraries/CurieBLE/src/BLEProfile.h create mode 100644 libraries/CurieBLE/src/BLERoleBase.cpp create mode 100644 libraries/CurieBLE/src/BLERoleBase.h delete mode 100644 libraries/CurieBLE/src/BLEUuid.cpp delete mode 100644 libraries/CurieBLE/src/BLEUuid.h create mode 100644 system/libarc32_arduino101/common/atomic.h create mode 100644 system/libarc32_arduino101/common/misc/byteorder.h create mode 100644 system/libarc32_arduino101/drivers/atomic_native.c create mode 100644 system/libarc32_arduino101/drivers/bluetooth/att.h create mode 100644 system/libarc32_arduino101/drivers/bluetooth/bluetooth.h create mode 100644 system/libarc32_arduino101/drivers/bluetooth/conn.h create mode 100644 system/libarc32_arduino101/drivers/bluetooth/conn_internal.h create mode 100644 system/libarc32_arduino101/drivers/bluetooth/gatt.h create mode 100644 system/libarc32_arduino101/drivers/bluetooth/hci.h create mode 100644 system/libarc32_arduino101/drivers/bluetooth/uuid.h create mode 100644 system/libarc32_arduino101/drivers/rpc/rpc.h create mode 100644 system/libarc32_arduino101/drivers/rpc/rpc_deserialize.c create mode 100644 system/libarc32_arduino101/drivers/rpc/rpc_functions_to_ble_core.h create mode 100644 system/libarc32_arduino101/drivers/rpc/rpc_functions_to_quark.h create mode 100644 system/libarc32_arduino101/drivers/rpc/rpc_serialize.c delete mode 100644 system/libarc32_arduino101/framework/include/services/ble/ble_service_gap_api.h delete mode 100644 system/libarc32_arduino101/framework/include/services/ble/ble_service_gatt.h delete mode 100644 system/libarc32_arduino101/framework/include/services/ble/ble_service_gattc_api.h delete mode 100644 system/libarc32_arduino101/framework/include/services/ble/ble_service_gatts_api.h create mode 100644 system/libarc32_arduino101/framework/include/util/misc.h delete mode 100644 system/libarc32_arduino101/framework/src/nordic_interface.c create mode 100644 system/libarc32_arduino101/framework/src/os/panic.c delete mode 100644 system/libarc32_arduino101/framework/src/services/ble/ble_protocol.h delete mode 100644 system/libarc32_arduino101/framework/src/services/ble/ble_service_core_int.h delete mode 100644 system/libarc32_arduino101/framework/src/services/ble/ble_service_gap_api.c delete mode 100644 system/libarc32_arduino101/framework/src/services/ble/ble_service_gatt_int.h delete mode 100644 system/libarc32_arduino101/framework/src/services/ble/ble_service_gatts_api.c create mode 100644 system/libarc32_arduino101/framework/src/services/ble/conn.c create mode 100644 system/libarc32_arduino101/framework/src/services/ble/conn_internal.h create mode 100644 system/libarc32_arduino101/framework/src/services/ble/dtm_tcmd.c create mode 100644 system/libarc32_arduino101/framework/src/services/ble/gap.c create mode 100644 system/libarc32_arduino101/framework/src/services/ble/gatt.c create mode 100644 system/libarc32_arduino101/framework/src/services/ble/hci_core.h create mode 100644 system/libarc32_arduino101/framework/src/services/ble/l2cap.c create mode 100644 system/libarc32_arduino101/framework/src/services/ble/l2cap_internal.h create mode 100644 system/libarc32_arduino101/framework/src/services/ble/smp.h create mode 100644 system/libarc32_arduino101/framework/src/services/ble/smp_null.c create mode 100644 system/libarc32_arduino101/framework/src/services/ble/uuid.c rename system/libarc32_arduino101/framework/{include/services/ble/ble_service_msg.h => src/services/ble_service/ble_protocol.h} (70%) create mode 100644 system/libarc32_arduino101/framework/src/services/ble_service/ble_service.c create mode 100644 system/libarc32_arduino101/framework/src/services/ble_service/ble_service_api.c create mode 100644 system/libarc32_arduino101/framework/src/services/ble_service/ble_service_int.h create mode 100644 system/libarc32_arduino101/framework/src/services/ble_service/ble_service_internal.h rename system/libarc32_arduino101/framework/src/services/{ble => ble_service}/ble_service_utils.c (50%) rename system/libarc32_arduino101/framework/src/services/{ble => ble_service}/ble_service_utils.h (66%) create mode 100644 system/libarc32_arduino101/framework/src/services/ble_service/gap_internal.h create mode 100644 system/libarc32_arduino101/framework/src/services/ble_service/gatt_internal.h create mode 100644 system/libarc32_arduino101/framework/src/services/ble_service/nble_driver.c create mode 100644 system/libarc32_arduino101/framework/src/services/ble_service/nble_driver.h diff --git a/README.md b/README.md index a710c0f3..b437314b 100644 --- a/README.md +++ b/README.md @@ -53,3 +53,21 @@ them to the [support forum](https://forum.arduino.cc/index.php?board=103). > "How do I use this library?" > "I can't get this example sketch to work. What am I doing wrong?" + +# Enable debug interface on Serail1 + +* Default disable the debug interface. + +If you want to enable debug trace on Serial1 to debug corelib, follow these instructions. + +1. Shut down the IDE +2. Go to Arduino15 directory + * Windows: `C:\Users\\AppData\Roaming\Arduino15` + * OS X: `~/Library/Arduino15` + * Linux: `~/.arduino15` +3. Modify the platform.txt + * Find `compiler.c.flags` and add `-DCONFIGURE_DEBUG_CORELIB_ENABLED` at the end of this line + * Find `compiler.cpp.flags` and add `-DCONFIGURE_DEBUG_CORELIB_ENABLED` at the end of this line +4. Initial Serial1 in your sketch + * Add `Serial1.begin(115200);` in your `setup()` +5. Adjust the output level at log_init function in log.c diff --git a/system/libarc32_arduino101/framework/src/nordic_interface.h b/cores/arduino/printk.cpp similarity index 79% rename from system/libarc32_arduino101/framework/src/nordic_interface.h rename to cores/arduino/printk.cpp index 1b187b16..babfc0ed 100644 --- a/system/libarc32_arduino101/framework/src/nordic_interface.h +++ b/cores/arduino/printk.cpp @@ -28,13 +28,25 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#ifndef NORDIC_INTERFACE_H -#define NORDIC_INTERFACE_H -#include "infra/ipc_uart.h" +#include +#include +#include "UARTClass.h" -void uart_ipc_message_cback(uint8_t cpu_id, int channel, int len, void * p_data); -int send_message_ipc_uart(struct message * message); -void free_message_ipc_uart(struct message * message); -int nordic_interface_init(T_QUEUE queue); +extern "C" void printk(const char *fmt, va_list args); +extern UARTClass Serial1; +#define PRINTK_BUFSIZ 256 + +void printk(const char *fmt, va_list args) +{ +#ifdef CONFIGURE_DEBUG_CORELIB_ENABLED + int len = 0; + + char tmp[PRINTK_BUFSIZ]; + + len = vsnprintf(tmp, PRINTK_BUFSIZ, fmt, args); + + tmp[len] = '\0'; + Serial1.println(tmp); +#endif +} -#endif // NORDIC_INTERFACE_H diff --git a/libraries/CurieBLE/examples/BatteryMonitor/BatteryMonitor.ino b/libraries/CurieBLE/examples/BatteryMonitor/BatteryMonitor.ino index 688b147f..7386087f 100644 --- a/libraries/CurieBLE/examples/BatteryMonitor/BatteryMonitor.ino +++ b/libraries/CurieBLE/examples/BatteryMonitor/BatteryMonitor.ino @@ -45,7 +45,7 @@ void setup() { void loop() { // listen for BLE peripherals to connect: - BLECentral central = blePeripheral.central(); + BLECentralHelper central = blePeripheral.central(); // if a central is connected to peripheral: if (central) { diff --git a/libraries/CurieBLE/examples/CallbackLED/CallbackLED.ino b/libraries/CurieBLE/examples/CallbackLED/CallbackLED.ino index f8788731..26946cbf 100644 --- a/libraries/CurieBLE/examples/CallbackLED/CallbackLED.ino +++ b/libraries/CurieBLE/examples/CallbackLED/CallbackLED.ino @@ -45,19 +45,19 @@ void loop() { blePeripheral.poll(); } -void blePeripheralConnectHandler(BLECentral& central) { +void blePeripheralConnectHandler(BLEHelper& central) { // central connected event handler Serial.print("Connected event, central: "); Serial.println(central.address()); } -void blePeripheralDisconnectHandler(BLECentral& central) { +void blePeripheralDisconnectHandler(BLEHelper& central) { // central disconnected event handler Serial.print("Disconnected event, central: "); Serial.println(central.address()); } -void switchCharacteristicWritten(BLECentral& central, BLECharacteristic& characteristic) { +void switchCharacteristicWritten(BLEHelper& central, BLECharacteristic& characteristic) { // central wrote new value to characteristic, update LED Serial.print("Characteristic event, written: "); diff --git a/libraries/CurieBLE/examples/IMUBleCentral/IMUBleCentral.ino b/libraries/CurieBLE/examples/IMUBleCentral/IMUBleCentral.ino new file mode 100644 index 00000000..0523ed55 --- /dev/null +++ b/libraries/CurieBLE/examples/IMUBleCentral/IMUBleCentral.ino @@ -0,0 +1,143 @@ +/* + 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 + +/* + This sketch example partially implements the standard Bluetooth Low-Energy Battery service. + For more information: https://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx +*/ + +#define MAX_IMU_RECORD 1 + +struct bt_le_conn_param conn_param = {0x18, 0x28, 0, 400}; +typedef struct { + int index; + unsigned int slot[3]; +} imuFrameType; + +imuFrameType imuBuf[MAX_IMU_RECORD]; +BLECentral bleCentral; // BLE Central Device (the board you're programming) + +BLEService bleImuService("F7580001-153E-D4F6-F26D-43D8D98EEB13"); +BLECharacteristic bleImuChar("F7580003-153E-D4F6-F26D-43D8D98EEB13", // standard 128-bit characteristic UUID + BLERead | BLENotify, sizeof(imuBuf)); // remote clients will be able to + // get notifications if this characteristic changes + +void ble_connected(BLEHelper &role) +{ + BLEPeripheralHelper&peripheral = *(BLEPeripheralHelper*)(&role); + Serial.println("Connected"); + + // Start discovery the profiles in peripheral device + peripheral.discover(); +} + +void bleImuCharacteristicWritten(BLEHelper& central, BLECharacteristic& characteristic) +{ + // Peripheral wrote new value to characteristic by Notification/Indication + const unsigned char *cvalue = characteristic.value(); + const imuFrameType *value = (const imuFrameType *)cvalue; + Serial.print("\r\nCharacteristic event, written: "); + Serial.print(value->index); + Serial.print("\t"); + Serial.print(value->slot[0]); + Serial.print("\t"); + Serial.print(value->slot[1]); + Serial.print("\t"); + Serial.println(value->slot[2]); +} + +bool adv_found(uint8_t type, + const uint8_t *data, + uint8_t data_len, + void *user_data) +{ + bt_addr_le_t *addr = (bt_addr_le_t *)user_data; + int i; + + Serial.print("[AD]:"); + Serial.print(type); + Serial.print(" data_len "); + Serial.println(data_len); + + switch (type) + { + case BT_DATA_UUID128_SOME: + case BT_DATA_UUID128_ALL: + { + if (data_len % MAX_UUID_SIZE != 0) + { + Serial.println("AD malformed"); + return true; + } + struct bt_uuid * serviceuuid = bleImuService.uuid(); + for (i = 0; i < data_len; i += MAX_UUID_SIZE) + { + if (memcmp (((struct bt_uuid_128*)serviceuuid)->val, &data[i], MAX_UUID_SIZE) != 0) + { + continue; + } + + // Accept the advertisement + if (!bleCentral.stopScan()) + { + Serial.println("Stop LE scan failed"); + continue; + } + Serial.println("Connecting"); + // Connect to peripheral + bleCentral.connect(addr, &conn_param); + return false; + } + } + } + + return true; +} + +void setup() { + Serial.begin(115200); // initialize serial communication + pinMode(13, OUTPUT); // initialize the LED on pin 13 to indicate when a central is connected + + bleImuChar.setEventHandler(BLEWritten, bleImuCharacteristicWritten); + + /* Set a local name for the BLE device + This name will appear in advertising packets + and can be used by remote devices to identify this BLE device + The name can be changed but maybe be truncated based on space + left in advertisement packet */ + bleCentral.addAttribute(bleImuService); // Add the BLE IMU service + bleCentral.addAttribute(bleImuChar); // Add the BLE IMU characteristic + + /* Setup callback */ + bleCentral.setAdvertiseHandler(adv_found); + bleCentral.setEventHandler(BLEConnected, ble_connected); + + /* Now activate the BLE device. It will start continuously transmitting BLE + advertising packets and will be visible to remote BLE central devices + until it receives a new connection */ + bleCentral.begin(); +} + + +void loop() +{ + delay(2000); +} + diff --git a/libraries/CurieBLE/examples/IMUBleNotification/IMUBleNotification.ino b/libraries/CurieBLE/examples/IMUBleNotification/IMUBleNotification.ino new file mode 100644 index 00000000..4a179e2a --- /dev/null +++ b/libraries/CurieBLE/examples/IMUBleNotification/IMUBleNotification.ino @@ -0,0 +1,120 @@ +/* + 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 + +/* + This sketch example partially implements the standard Bluetooth Low-Energy Battery service. + For more information: https://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx +*/ + +#define MAX_IMU_RECORD 1 + +typedef struct { + int index; + unsigned int slot[3]; +} imuFrameType; + +imuFrameType imuBuf[MAX_IMU_RECORD]; + +unsigned seqNum = 0; + +BLEPeripheral blePeripheral; // BLE Peripheral Device (the board you're programming) +BLEService bleImuService("F7580001-153E-D4F6-F26D-43D8D98EEB13"); // Tx IMU data Characteristic +BLECharacteristic bleImuChar("F7580003-153E-D4F6-F26D-43D8D98EEB13", // standard 128-bit characteristic UUID + BLERead | BLENotify, sizeof(imuBuf)); // remote clients will be able to + // get notifications if this characteristic changes +void setup() { + + Serial.begin(9600); // initialize serial communication + pinMode(13, OUTPUT); // initialize the LED on pin 13 to indicate when a central is connected + + + /* Set a local name for the BLE device + This name will appear in advertising packets + and can be used by remote devices to identify this BLE device + The name can be changed but maybe be truncated based on space left in advertisement packet */ + blePeripheral.setLocalName("Imu"); + blePeripheral.setAdvertisedServiceUuid(bleImuService.uuid()); // add the service UUID + blePeripheral.addAttribute(bleImuService); // Add the BLE Battery service + blePeripheral.addAttribute(bleImuChar); // add the battery level characteristic + + /* Now activate the BLE device. It will start continuously transmitting BLE + advertising packets and will be visible to remote BLE central devices + until it receives a new connection */ + blePeripheral.begin(); + + CurieIMU.begin(); +} + +void recordImuData(int index) { + /* Read IMU data. + */ + int ax, ay, az; + int gx, gy, gz; + + imuBuf[index].index = seqNum++; + CurieIMU.readMotionSensor(ax, ay, az, gx, gy, gz); + + imuBuf[index].slot[0] = (unsigned int)((ax << 16) | (ay & 0x0FFFF)); + imuBuf[index].slot[1] = (unsigned int)((az << 16) | (gx & 0x0FFFF)); + imuBuf[index].slot[2] = (unsigned int)((gy << 16) | (gz & 0x0FFFF)); + +} + + +void loop() { + // listen for BLE peripherals to connect: + BLECentralHelper central = blePeripheral.central(); + + // if a central is connected to peripheral: + if (central) + { + Serial.print("Connected to central: "); + // print the central's MAC address: + Serial.println(central.address()); + + Serial.print("IMU buffer size: "); + Serial.println(sizeof(imuBuf)); + + // turn on the LED to indicate the connection: + digitalWrite(13, HIGH); + + long currentMillis, sentTime; + + // Send IMU data as long as the central is still connected + currentMillis = sentTime = millis(); + while (central.connected()) + { + // Take IMU data every 100 msec + if ((millis() - sentTime) >= 100) + { + recordImuData(0); + sentTime = millis(); + bleImuChar.setValue((unsigned char *)&(imuBuf[0]), sizeof(imuBuf)); + } + } // while + + // when the central disconnects, turn off the LED: + digitalWrite(13, LOW); + Serial.print("Disconnected from central: "); + Serial.println(central.address()); + } +} + diff --git a/libraries/CurieBLE/examples/LED/LED.ino b/libraries/CurieBLE/examples/LED/LED.ino index a55501dc..263c7d5b 100644 --- a/libraries/CurieBLE/examples/LED/LED.ino +++ b/libraries/CurieBLE/examples/LED/LED.ino @@ -38,7 +38,7 @@ void setup() { void loop() { // listen for BLE peripherals to connect: - BLECentral central = blePeripheral.central(); + BLECentralHelper central = blePeripheral.central(); // if a central is connected to peripheral: if (central) { diff --git a/libraries/CurieBLE/examples/LEDCentral/LEDCentral.ino b/libraries/CurieBLE/examples/LEDCentral/LEDCentral.ino new file mode 100644 index 00000000..04cd0971 --- /dev/null +++ b/libraries/CurieBLE/examples/LEDCentral/LEDCentral.ino @@ -0,0 +1,157 @@ +/* + 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 +*/ + +// This example can work with CallbackLED to show the profile read/write operation in central +#include + +struct bt_le_conn_param conn_param = {0x18, 0x28, 0, 400}; + +const int ledPin = 13; // set ledPin to use on-board LED +BLECentral bleCentral; // create central instance +BLEPeripheralHelper *blePeripheral1 = NULL; + +BLEService ledService("19B10000-E8F2-537E-4F6C-D104768A1214"); // create service +BLECharCharacteristic switchChar("19B10001-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite);// create switch characteristic and allow remote device to read and write + +void bleCentralConnectHandler(BLEHelper& peripheral) +{ + // peripheral connected event handler + blePeripheral1 = (BLEPeripheralHelper *)(&peripheral); + Serial.print("Connected event, peripheral: "); + Serial.println(peripheral.address()); + // Start discovery the profiles in peripheral device + blePeripheral1->discover(); +} + +void bleCentralDisconnectHandler(BLEHelper& peripheral) +{ + // peripheral disconnected event handler + blePeripheral1 = NULL; + Serial.print("Disconnected event, peripheral: "); + Serial.println(peripheral.address()); + bleCentral.startScan(); +} + +void switchCharacteristicWritten(BLEHelper& peripheral, BLECharacteristic& characteristic) +{ + // Read response/Notification wrote new value to characteristic, update LED + Serial.print("Characteristic event, written: "); + + if (switchChar.value()) + { + Serial.println("LED on"); + digitalWrite(ledPin, HIGH); + } + else + { + Serial.println("LED off"); + digitalWrite(ledPin, LOW); + } +} + +bool adv_found(uint8_t type, + const uint8_t *data, + uint8_t data_len, + void *user_data) +{ + bt_addr_le_t *addr = (bt_addr_le_t *)user_data; + int i; + + Serial.print("[AD]:"); + Serial.print(type); + Serial.print(" data_len "); + Serial.println(data_len); + + switch (type) + { + case BT_DATA_UUID128_SOME: + case BT_DATA_UUID128_ALL: + { + if (data_len % MAX_UUID_SIZE != 0) + { + Serial.println("AD malformed"); + return true; + } + struct bt_uuid * serviceuuid = ledService.uuid(); + for (i = 0; i < data_len; i += MAX_UUID_SIZE) + { + if (memcmp (((struct bt_uuid_128*)serviceuuid)->val, &data[i], MAX_UUID_SIZE) != 0) + { + continue; + } + + // Accept the advertisement + if (!bleCentral.stopScan()) + { + Serial.println("Stop LE scan failed"); + continue; + } + Serial.println("Connecting"); + // Connect to peripheral + bleCentral.connect(addr, &conn_param); + return false; + } + } + } + + return true; +} + +void setup() { + Serial.begin(9600); + pinMode(ledPin, OUTPUT); // use the LED on pin 13 as an output + + // add service and characteristic + bleCentral.addAttribute(ledService); + bleCentral.addAttribute(switchChar); + + // assign event handlers for connected, disconnected to central + bleCentral.setEventHandler(BLEConnected, bleCentralConnectHandler); + bleCentral.setEventHandler(BLEDisconnected, bleCentralDisconnectHandler); + + // advertise the service + bleCentral.setAdvertiseHandler(adv_found); + + // assign event handlers for characteristic + switchChar.setEventHandler(BLEWritten, switchCharacteristicWritten); + + bleCentral.begin(); + Serial.println(("Bluetooth device active, waiting for connections...")); +} + +void loop() +{ + static unsigned int counter = 0; + static char ledstate = 0; + delay(2000); + if (blePeripheral1) + { + counter++; + + if (counter % 3) + { + switchChar.read(*blePeripheral1); + } + else + { + ledstate = !ledstate; + switchChar.write(*blePeripheral1, (unsigned char *)(&ledstate), sizeof (ledstate)); + } + } +} diff --git a/libraries/CurieBLE/examples/MIDIBLE/MIDIBLE.ino b/libraries/CurieBLE/examples/MIDIBLE/MIDIBLE.ino index 2dbe9833..4892f017 100644 --- a/libraries/CurieBLE/examples/MIDIBLE/MIDIBLE.ino +++ b/libraries/CurieBLE/examples/MIDIBLE/MIDIBLE.ino @@ -140,19 +140,19 @@ void loop() { } -void midiDeviceConnectHandler(BLECentral& central) { +void midiDeviceConnectHandler(BLEHelper& central) { // central connected event handler Serial.print("Connected event, central: "); Serial.println(central.address()); } -void midiDeviceDisconnectHandler(BLECentral& central) { +void midiDeviceDisconnectHandler(BLEHelper& central) { // central disconnected event handler Serial.print("Disconnected event, central: "); Serial.println(central.address()); } -void midiCharacteristicWritten(BLECentral& central, BLECharacteristic& characteristic) { +void midiCharacteristicWritten(BLEHelper& central, BLECharacteristic& characteristic) { // central wrote new value to characteristic, update LED Serial.print("Characteristic event, written: "); } diff --git a/libraries/CurieBLE/examples/Scanning/Scanning.ino b/libraries/CurieBLE/examples/Scanning/Scanning.ino new file mode 100644 index 00000000..d2d7d0e2 --- /dev/null +++ b/libraries/CurieBLE/examples/Scanning/Scanning.ino @@ -0,0 +1,129 @@ +/* + 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 + +#define BLE_SCANING_DEVICE_MAX_CNT 5 + +typedef struct{ + char macaddr[32]; // BLE MAC address. + char loacalname[22]; // Device's name +}ble_device_info_t; + +ble_device_info_t device_list[BLE_SCANING_DEVICE_MAX_CNT]; +uint8_t list_index = 0; + +BLECentral bleCentral; // BLE Central Device (the board you're programming) + +bool adv_list_add(ble_device_info_t &device) +{ + if (list_index >= BLE_SCANING_DEVICE_MAX_CNT) + { + return false; + } + for (int i = 0; i < list_index; i++) + { + if (0 == memcmp(device.macaddr, device_list[i].macaddr, sizeof (device.macaddr))) + { + // Found and update the item + return false; + } + } + // Add the device + memcpy(&device_list[list_index], &device, sizeof (ble_device_info_t)); + list_index++; + return true; +} + + +bool adv_list_update(ble_device_info_t &device) +{ + for (int i = 0; i < list_index; i++) + { + if (0 == memcmp(device.macaddr, device_list[i].macaddr, sizeof (device.macaddr))) + { + // Found and update the item + memcpy(device_list[i].loacalname, device.loacalname, sizeof(device.loacalname)); + return true; + } + } + return false; +} + +void adv_list_clear() +{ + list_index = 0; + memset(device_list, 0x00, sizeof(device_list)); +} + +// Process the Advertisement data +bool adv_found(uint8_t type, + const uint8_t *data, + uint8_t data_len, + void *user_data) +{ + bt_addr_le_t *addr = (bt_addr_le_t *)user_data; + ble_device_info_t device; + bt_addr_le_to_str (addr, device.macaddr, sizeof (device.macaddr)); + memcpy(device.loacalname, " -NA-", sizeof(" -NA-")); + + switch (type) { + case BT_DATA_NAME_SHORTENED: + case BT_DATA_NAME_COMPLETE: + memcpy(device.loacalname, data, data_len); + device.loacalname[data_len] = '\0'; + adv_list_update(device); + break; + } + adv_list_add(device); + return true; +} + +void setup() { + Serial.begin(115200); // initialize serial communication + + /* Setup callback */ + bleCentral.setAdvertiseHandler(adv_found); + + /* Now activate the BLE device. + It will start continuously scanning BLE advertising + */ + bleCentral.begin(); + Serial.println("Bluetooth device active, start scanning..."); +} + +void loop() { + // Output the scanned device per 3s + delay(3000); + Serial.print("\r\n\r\n\t\t\tScaning result\r\n \tMAC\t\t\t\tLocal Name\r\n"); + Serial.print("-------------------------------------------------------------\r\n"); + + for (int i = 0; i < list_index; i++) + { + + Serial.print(device_list[i].macaddr); + Serial.print(" | "); + Serial.println(device_list[i].loacalname); + } + if (list_index == 0) + { + Serial.print("No device found\r\n"); + } + Serial.print("-------------------------------------------------------------\r\n"); + adv_list_clear(); +} + diff --git a/libraries/CurieBLE/src/BLEAttribute.cpp b/libraries/CurieBLE/src/BLEAttribute.cpp index d6f46591..478c2e00 100644 --- a/libraries/CurieBLE/src/BLEAttribute.cpp +++ b/libraries/CurieBLE/src/BLEAttribute.cpp @@ -19,23 +19,60 @@ #include "BLEAttribute.h" -#include "BLEUuid.h" - unsigned char BLEAttribute::_numAttributes = 0; BLEAttribute::BLEAttribute(const char* uuid, enum BLEAttributeType type) : - _uuid(uuid), + _uuid_cstr(uuid), _type(type), _handle(0) { + char temp[] = {0, 0, 0}; + int strLength = strlen(uuid); + int length = 0; + _numAttributes++; + + memset (&_uuid, 0x00, sizeof(_uuid)); + + 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.val[length] = strtoul(temp, NULL, 16); + + length++; + } + + if (length == 2) + { + uint16_t temp = (_uuid.val[1] << 8)| _uuid.val[0]; + _uuid.uuid.type = BT_UUID_TYPE_16; + ((struct bt_uuid_16*)(&_uuid.uuid))->val = temp; + } + else + { + _uuid.uuid.type = BT_UUID_TYPE_128; + } } const char* BLEAttribute::uuid() const { - return _uuid; + return _uuid_cstr; +} + +struct bt_uuid *BLEAttribute::uuid(void) +{ + return (struct bt_uuid *)&_uuid; } + enum BLEAttributeType BLEAttribute::type() const { return this->_type; @@ -52,14 +89,14 @@ BLEAttribute::setHandle(uint16_t handle) { } -bt_uuid -BLEAttribute::btUuid() const { - BLEUuid bleUuid = BLEUuid(uuid()); - - return bleUuid.uuid(); -} - unsigned char BLEAttribute::numAttributes() { return _numAttributes; } + +bool BLEAttribute::discovering() +{ + return _discoverying; +} + + diff --git a/libraries/CurieBLE/src/BLEAttribute.h b/libraries/CurieBLE/src/BLEAttribute.h index 8aaca067..71a19fca 100644 --- a/libraries/CurieBLE/src/BLEAttribute.h +++ b/libraries/CurieBLE/src/BLEAttribute.h @@ -28,7 +28,10 @@ enum BLEAttributeType { BLETypeDescriptor = 0x2900 }; +// Class declare +class BLEProfile; class BLEPeripheral; +class BLEPeripheralHelper; class BLEAttribute { public: @@ -39,9 +42,20 @@ class BLEAttribute { * @return const char* string representation of the Attribute */ const char* uuid(void) const; + struct bt_uuid *uuid(void); protected: - friend BLEPeripheral; + //friend BLEPeripheral; + friend BLEProfile; + + friend ssize_t profile_write_process(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + const void *buf, uint16_t len, + uint16_t offset); + friend ssize_t profile_read_process(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + void *buf, uint16_t len, + uint16_t offset); BLEAttribute(const char* uuid, enum BLEAttributeType type); @@ -51,13 +65,32 @@ class BLEAttribute { void setHandle(uint16_t handle); static unsigned char numAttributes(void); - + // The below APIs are for central device to discover the + virtual void discover(struct bt_gatt_discover_params *params) = 0; + virtual void discover(const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) = 0; + /** + * @brief Get attribute's discover state + * + * @param none + * + * @return bool true - In discovering state + * false- Not discovering + * + * @note none + */ + bool discovering(); + + bool _discoverying; private: static unsigned char _numAttributes; - const char* _uuid; + const char* _uuid_cstr; + struct bt_uuid_128 _uuid; + enum BLEAttributeType _type; uint16_t _handle; + }; #endif // _BLE_ATTRIBUTE_H_INCLUDED diff --git a/libraries/CurieBLE/src/BLECentral.cpp b/libraries/CurieBLE/src/BLECentral.cpp index 73d0326d..6b4afcf5 100644 --- a/libraries/CurieBLE/src/BLECentral.cpp +++ b/libraries/CurieBLE/src/BLECentral.cpp @@ -17,87 +17,84 @@ * */ -#include "BLECentral.h" - -#include "BLEPeripheral.h" +#include "BLECentralRole.h" +#include "BLECentral.h" -BLECentral::BLECentral(BLEPeripheral* peripheral) : - _peripheral(peripheral) +bool BLECentral::startScan() { - clearAddress(); + return BLECentralRole::instance()->startScan(); } -BLECentral::operator bool() const { - ble_addr_t zero; - - memset(&zero, 0, sizeof(zero)); - - return (memcmp(&_address, &zero, sizeof(_address)) != 0); +bool BLECentral::startScan(const struct bt_le_scan_param &scan_param) +{ + return BLECentralRole::instance()->startScan(scan_param); } -bool -BLECentral::operator==(const BLECentral& rhs) const { - return (memcmp(&_address, &rhs._address, sizeof(_address)) == 0); +bool BLECentral::stopScan() +{ + return BLECentralRole::instance()->stopScan(); } -bool -BLECentral::operator!=(const BLECentral& rhs) const { - return !(*this == rhs); +bool BLECentral::connect(const bt_addr_le_t *addr, const struct bt_le_conn_param *param) +{ + return BLECentralRole::instance()->connect(addr, param); } -bool -BLECentral::connected() { - poll(); - - return (*this && *this == _peripheral->central()); +void BLECentral::discover(BLEPeripheralHelper &peripheral) +{ + peripheral.discover(); } -const char* -BLECentral::address() const { - static char address[18]; - - String addressStr = ""; - - for (int i = 5; i >= 0; i--) { - unsigned char a = _address.addr[i]; - - if (a < 0x10) { - addressStr += "0"; - } - - addressStr += String(a, 16); - - if (i > 0) { - addressStr += ":"; - } - } +void BLECentral::setEventHandler(BLERoleEvent event, BLERoleEventHandler callback) +{ + BLECentralRole::instance()->setEventHandler(event, callback); +} - strcpy(address, addressStr.c_str()); +void BLECentral::setAdvertiseHandler(ble_advertise_handle_cb_t advcb) +{ + BLECentralRole::instance()->setAdvertiseHandler(advcb); +} - return address; +void BLECentral::setScanParam(const struct bt_le_scan_param &scan_param) +{ + BLECentralRole::instance()->setScanParam(scan_param); } -void -BLECentral::poll() { - _peripheral->poll(); +void BLECentral::addAttribute(BLEAttribute& attribute) +{ + BLECentralRole::instance()->addAttribute(attribute); } -bool -BLECentral::disconnect() { - if (connected()) { - return _peripheral->disconnect(); +bool BLECentral::begin(void) +{ + bool retval = BLECentralRole::instance()->begin(); + if (!retval) + { + pr_error(LOG_MODULE_BLE,"%s: Intit failed", __FUNCTION__); + return false; } - - return false; + + // Start scan + const struct bt_le_scan_param *scan_param = BLECentralRole::instance()->getScanParam(); + struct bt_le_scan_param zero_param; + memset(&zero_param, 0x00, sizeof (zero_param)); + if (0 == memcmp(&zero_param, scan_param, sizeof (zero_param))) + { + // Not set the scan parameter. + // Use the default scan parameter to scan + zero_param.type = BT_HCI_LE_SCAN_ACTIVE; + zero_param.filter_dup = BT_HCI_LE_SCAN_FILTER_DUP_ENABLE; + zero_param.interval = BT_GAP_SCAN_FAST_INTERVAL;//BT_GAP_SCAN_SLOW_INTERVAL_1;// + zero_param.window = BT_GAP_SCAN_FAST_WINDOW; //BT_GAP_SCAN_SLOW_WINDOW_1;// + retval = BLECentralRole::instance()->startScan(zero_param); + } + else + { + retval = BLECentralRole::instance()->startScan(); + } + return retval; } -void -BLECentral::setAddress(ble_addr_t address) { - _address = address; -} -void -BLECentral::clearAddress() { - memset(&_address, 0x00, sizeof(_address)); -} + diff --git a/libraries/CurieBLE/src/BLECentral.h b/libraries/CurieBLE/src/BLECentral.h index c51250d9..8327f5d6 100644 --- a/libraries/CurieBLE/src/BLECentral.h +++ b/libraries/CurieBLE/src/BLECentral.h @@ -21,50 +21,119 @@ #define _BLE_CENTRAL_H_INCLUDED #include "BLECommon.h" +#include "BLERoleBase.h" -class BLEPeripheral; +class BLEAttribute; -class BLECentral { - friend class BLEPeripheral; - - public: - /** - * Is the Central connected - * - * @return boolean_t true if the central is connected, otherwise false - */ - bool connected(void); - - /** - * Get the address of the Central in string form - * - * @return const char* address of the Central in string form - */ - const char* address(void) const; - - /** - * Disconnect the central if it is connected - * - */ - bool disconnect(void); - - /** - * Poll the central for events - */ - void poll(void); - - operator bool(void) const; - bool operator==(const BLECentral& rhs) const; - bool operator!=(const BLECentral& rhs) const; - - protected: - BLECentral(BLEPeripheral* peripheral); - void setAddress(ble_addr_t address); - void clearAddress(); - - private: - BLEPeripheral* _peripheral; - ble_addr_t _address; +class BLECentral{ +public: + /** + * @brief Start scan + * + * @param none + * + * @return bool Indicate the success or error + * + * @note none + */ + bool startScan(); + + /** + * @brief Start scan with scan parameter + * + * @param none + * + * @return bool Indicate the success or error + * + * @note none + */ + bool startScan(const struct bt_le_scan_param &scan_param); + + /** + * @brief Stop scan + * + * @param none + * + * @return bool Indicate the success or error + * + * @note none + */ + bool stopScan(); + + /** + * @brief Schedule a connect request to peripheral to establish a connection + * + * @param addr The MAC address of peripheral device that want to establish connection + * + * @param param The connetion parameters + * + * @return bool Indicate the success or error + * + * @note none + */ + bool connect(const bt_addr_le_t *addr, const struct bt_le_conn_param *param); + + /** + * @brief Discover the peripheral device profile + * + * @param peripheral The Peripheral that need to discover the profile + * + * @return none + * + * @note none + */ + void discover(BLEPeripheralHelper &peripheral); + + /** + * @brief Set the scan parameter + * + * @param scan_param The scan parameter want to be set + * + * @return none + * + * @note none + */ + void setScanParam(const struct bt_le_scan_param &scan_param); + + /** + * @brief Add an attribute to the BLE Central Device + * + * @param attribute Attribute to add to Central + * + * @return none + * + * @note The attribute will used for discover the peripheral handler + */ + void addAttribute(BLEAttribute& attribute); + + /** + * Provide a function to be called when events related to this Device are raised + * + * @param event Event type for callback + * @param callback Pointer to callback function to invoke when an event occurs. + */ + void setEventHandler(BLERoleEvent event, BLERoleEventHandler callback); + + /** + * @brief Provide a function to be called when scanned the advertisement + * + * @param advcb Pointer to callback function to invoke when advertisement received + * + * @return none + * + * @note none + */ + void setAdvertiseHandler(ble_advertise_handle_cb_t advcb); + + /** + * Setup attributes and start scan + * + * @return bool indicating success or error + */ + bool begin(void); +protected: +private: + }; #endif diff --git a/libraries/CurieBLE/src/BLECentralHelper.cpp b/libraries/CurieBLE/src/BLECentralHelper.cpp new file mode 100644 index 00000000..99e92de9 --- /dev/null +++ b/libraries/CurieBLE/src/BLECentralHelper.cpp @@ -0,0 +1,103 @@ +/* + * 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 "BLECentralHelper.h" + +#include "BLEPeripheralRole.h" + + +BLECentralHelper::BLECentralHelper(BLEPeripheralRole* peripheral) : + _peripheral(peripheral) +{ + clearAddress(); +} + +BLECentralHelper::operator bool() const { + bt_addr_le_t zero; + + memset(&zero, 0, sizeof(zero)); + + return (memcmp(&_address, &zero, sizeof(_address)) != 0); +} + +bool +BLECentralHelper::operator==(const BLECentralHelper& rhs) const { + return (memcmp(&_address, &rhs._address, sizeof(_address)) == 0); +} + +bool +BLECentralHelper::operator!=(const BLECentralHelper& rhs) const { + return !(*this == rhs); +} + +bool +BLECentralHelper::connected() { + poll(); + + return (*this && *this == _peripheral->central()); +} + +const char* +BLECentralHelper::address() const { + static char address[18]; + + String addressStr = ""; + + for (int i = 5; i >= 0; i--) { + unsigned char a = _address.val[i]; + + if (a < 0x10) { + addressStr += "0"; + } + + addressStr += String(a, 16); + + if (i > 0) { + addressStr += ":"; + } + } + + strcpy(address, addressStr.c_str()); + + return address; +} + +void +BLECentralHelper::poll() { + _peripheral->poll(); +} + +bool +BLECentralHelper::disconnect() { + if (connected()) { + return _peripheral->disconnect(); + } + + return false; +} + +void +BLECentralHelper::setAddress(bt_addr_le_t address) { + _address = address; +} + +void +BLECentralHelper::clearAddress() { + memset(&_address, 0x00, sizeof(_address)); +} diff --git a/libraries/CurieBLE/src/BLECentralHelper.h b/libraries/CurieBLE/src/BLECentralHelper.h new file mode 100644 index 00000000..465d9cab --- /dev/null +++ b/libraries/CurieBLE/src/BLECentralHelper.h @@ -0,0 +1,72 @@ +/* + * 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_CENTRAL_HELPER_H_INCLUDED +#define _BLE_CENTRAL_HELPER_H_INCLUDED + +#include "BLECommon.h" +#include "BLEHelper.h" + +class BLEPeripheralRole; + +class BLECentralHelper: public BLEHelper{ + friend class BLEPeripheralRole; + friend class BLECentralRole; + + public: + /** + * Is the Central connected + * + * @return boolean_t true if the central is connected, otherwise false + */ + bool connected(void); + + /** + * Get the address of the Central in string form + * + * @return const char* address of the Central in string form + */ + const char* address(void) const; + + /** + * Disconnect the central if it is connected + * + */ + bool disconnect(void); + + /** + * Poll the central for events + */ + void poll(void); + + operator bool(void) const; + bool operator==(const BLECentralHelper& rhs) const; + bool operator!=(const BLECentralHelper& rhs) const; + + protected: + BLECentralHelper(BLEPeripheralRole* peripheral); + void setAddress(bt_addr_le_t address); + void clearAddress(); + + private: + BLEPeripheralRole* _peripheral; + bt_addr_le_t _address; +}; + +#endif diff --git a/libraries/CurieBLE/src/BLECentralRole.cpp b/libraries/CurieBLE/src/BLECentralRole.cpp new file mode 100644 index 00000000..1595e24d --- /dev/null +++ b/libraries/CurieBLE/src/BLECentralRole.cpp @@ -0,0 +1,292 @@ +/* + * 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 "BLECentralRole.h" + + +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); + + BLECentralRole::instance()->handleDeviceFound(addr, rssi, type, + ad, len); +} + + +BLECentralRole* BLECentralRole::_ble_central_ins = NULL; + +BLECentralRole *BLECentralRole::instance() +{ + if (NULL == _ble_central_ins) + { + _ble_central_ins = new BLECentralRole(); + } + return _ble_central_ins; +} + +BLECentralRole::BLECentralRole(): + _central(NULL), _adv_event_handle(NULL) +{ + memset(_peripherial, 0, sizeof (_peripherial)); + for (int i = 0; i < BLE_MAX_CONN_CFG; i++) + { + _peripherial[i] = new BLEPeripheralHelper(this); + } + memset (&_scan_param, 0x00, sizeof (_scan_param)); + _central.setAddress(_local_bda); +} + + +BLECentralRole::~BLECentralRole() +{ + for (int i = 0; i < BLE_MAX_CONN_CFG; i++) + { + delete (_peripherial[i]); + //_peripherial[i] = NULL; + } +} + +const BLECentralHelper *BLECentralRole::central(void) const +{ + return &_central; +} + +bool BLECentralRole::connect(const bt_addr_le_t *addr, const struct bt_le_conn_param *param) +{ + BLEPeripheralHelper* temp = NULL; + BLEPeripheralHelper* unused = NULL; + bool link_existed = false; + bool retval = false; + + // Find free peripheral Items + for (int i = 0; i < BLE_MAX_CONN_CFG; i++) + { + temp = _peripherial[i]; + if (true == *temp) + { + if (*temp == *addr) + { + // 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; + } + } + } + + if (!link_existed) + { + // Send connect request + struct bt_conn* conn = bt_conn_create_le(addr, param); + if (NULL != conn) + { + unused->setAddress(*addr); + retval = true; + bt_conn_unref(conn); + } + } + return retval; +} + +bool BLECentralRole::startScan() +{ + 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 BLECentralRole::startScan(const struct bt_le_scan_param &scan_param) +{ + setScanParam(scan_param); + return startScan(); +} + +void BLECentralRole::setScanParam(const struct bt_le_scan_param &scan_param) +{ + memcpy(&_scan_param, &scan_param, sizeof (_scan_param)); +} + +const struct bt_le_scan_param* BLECentralRole::getScanParam() +{ + return &_scan_param; +} + + +bool BLECentralRole::stopScan() +{ + 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; +} + +BLEPeripheralHelper* BLECentralRole::peripheral(struct bt_conn *conn) +{ + BLEPeripheralHelper* temp = NULL; + const bt_addr_le_t *addr = bt_conn_get_dst(conn); + // Find free peripheral Items + for (int i = 0; i < BLE_MAX_CONN_CFG; i++) + { + temp = _peripherial[i]; + if (*temp == *addr) + { + return temp; + } + } + return NULL; +} + + +void BLECentralRole::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; + + if (_adv_event_handle == NULL) + { + return; + } + + /* 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", __FUNCTION__); + + 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 (!_adv_event_handle(data[1], &data[2], len - 1, (void *)addr)) + { + return; + } + + data_len -= len + 1; + data += len + 1; + } + pr_debug(LOG_MODULE_BLE, "%s: done", __FUNCTION__); + } +} + +void BLECentralRole::handleConnectEvent(struct bt_conn *conn, uint8_t err) +{ + if (_event_handlers[BLEConnected]) + { + BLEPeripheralHelper *temp = peripheral(conn); + _event_handlers[BLEConnected](*temp); + } +} + +void BLECentralRole::handleDisconnectEvent(struct bt_conn *conn, uint8_t reason) +{ + if (_event_handlers[BLEDisconnected]) + { + BLEPeripheralHelper *temp = peripheral(conn); + _event_handlers[BLEDisconnected](*temp); + temp->linkLost(); + } +} + +void BLECentralRole::handleParamUpdated(struct bt_conn *conn, + uint16_t interval, + uint16_t latency, + uint16_t timeout) +{ + if (_event_handlers[BLEUpdateParam]) + { + // Fix me Add parameter proc + BLEPeripheralHelper *temp = peripheral(conn); + _event_handlers[BLEUpdateParam](*temp); + } +} + +void BLECentralRole::setEventHandler(BLERoleEvent event, BLERoleEventHandler callback) +{ + + if (event < sizeof(_event_handlers)) + { + _event_handlers[event] = callback; + } +} + +void BLECentralRole::setAdvertiseHandler(ble_advertise_handle_cb_t advcb) +{ + _adv_event_handle = advcb; +} + +void BLECentralRole::addAttribute(BLEAttribute& attribute) +{ + for (int i = 0; i < BLE_MAX_CONN_CFG; i++) + { + _peripherial[i]->addAttribute(attribute); + } +} + +bool BLECentralRole::begin() +{ + BleStatus status; + status = _init(); + if (status != BLE_STATUS_SUCCESS) + { + return false; + } + return true; +} + +bool BLECentralRole::disconnect() +{ + return true; +} + + diff --git a/libraries/CurieBLE/src/BLECentralRole.h b/libraries/CurieBLE/src/BLECentralRole.h new file mode 100644 index 00000000..d3757fae --- /dev/null +++ b/libraries/CurieBLE/src/BLECentralRole.h @@ -0,0 +1,276 @@ +/* + * 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_CENTRALROLE_H_INCLUDED +#define _BLE_CENTRALROLE_H_INCLUDED +#include "BLECommon.h" +#include "BLEPeripheralHelper.h" +#include "BLECentralHelper.h" +#include "BLERoleBase.h" + +class BLECentralRole: public BLERoleBase { +public: + /** + * @brief Start scan + * + * @param none + * + * @return bool Indicate the success or error + * + * @note none + */ + bool startScan(); + + /** + * @brief Start scan with scan parameter + * + * @param none + * + * @return bool Indicate the success or error + * + * @note none + */ + bool startScan(const struct bt_le_scan_param &scan_param); + + /** + * @brief Stop scan + * + * @param none + * + * @return bool Indicate the success or error + * + * @note none + */ + bool stopScan(); + + /** + * @brief Schedule a connect request to peripheral to establish a connection + * + * @param addr The MAC address of peripheral device that want to establish connection + * + * @param param The connetion parameters + * + * @return bool Indicate the success or error + * + * @note none + */ + bool connect(const bt_addr_le_t *addr, const struct bt_le_conn_param *param); + + /** + * @brief Set the scan parameter + * + * @param scan_param The scan parameter want to be set + * + * @return none + * + * @note none + */ + void setScanParam(const struct bt_le_scan_param &scan_param); + + /** + * @brief Get the scan parameter + * + * @param none + * + * @return const struct bt_le_scan_param* The scan parameter that current used + * + * @note none + */ + const struct bt_le_scan_param* getScanParam(); + + /** + * @brief Discover the peripheral device profile + * + * @param peripheral The Peripheral that need to discover the profile + * + * @return none + * + * @note none + */ + void discover(BLEPeripheralHelper &peripheral); + + /** + * @brief Add an attribute to the BLE Central Device + * + * @param attribute Attribute to add to Central + * + * @return none + * + * @note The attribute will used for discover the peripheral handler + */ + void addAttribute(BLEAttribute& attribute); + + /** + * Provide a function to be called when events related to this Device are raised + * + * @param event Event type for callback + * @param callback Pointer to callback function to invoke when an event occurs. + */ + void setEventHandler(BLERoleEvent event, BLERoleEventHandler callback); + + /** + * @brief Provide a function to be called when scanned the advertisement + * + * @param advcb Pointer to callback function to invoke when advertisement received + * + * @return none + * + * @note none + */ + void setAdvertiseHandler(ble_advertise_handle_cb_t advcb); + + /** + * @brief Get BLE peripheral helper by conntion + * + * @param conn The connection object + * + * @return BLEPeripheralHelper* The BLE peripheral helper + * + * @note none + */ + BLEPeripheralHelper* peripheral(struct bt_conn *conn); + + /** + * @brief Get BLE central helper that for APP use + * + * @param none + * + * @return const BLECentralHelper * The BLE central helper + * + * @note none + */ + const BLECentralHelper *central(void) const; + + /** + * Setup attributes and start advertising + * + * @return bool indicating success or error + */ + bool begin(); + + /** + * @brief Disconnect the central connected if there is one connected + * + * @param none + * + * @return bool Indicating success or error + * + * @note none + */ + bool disconnect(); + + /** + * @brief Get BLE Central instance. + * + * @param none + * + * @return BLECentralRole* The BLE Central instance + * + * @note Singleton. Only have one object to communicate with + * stack and manage the device + */ + static BLECentralRole *instance(); + +protected: + friend void ble_central_device_found(const bt_addr_le_t *addr, + int8_t rssi, + uint8_t type, + const uint8_t *ad, + uint8_t len); + + /** + * @brief Handle the connected event + * + * @param conn The object that established the connection + * + * @param err The code of the process + * + * @return none + * + * @note none + */ + void handleConnectEvent(struct bt_conn *conn, uint8_t err); + + /** + * @brief Handle the disconnected event + * + * @param conn The object that lost the connection + * + * @param reason The link lost reason + * + * @return none + * + * @note none + */ + void handleDisconnectEvent(struct bt_conn *conn, uint8_t reason); + + /** + * @brief Handle the conntion update request + * + * @param conn The connection object that need to process the update request + * + * @param interval The connection interval + * + * @param latency The connection latency + * + * @param timeout The connection timeout + * + * @return none + * + * @note none + */ + void handleParamUpdated(struct bt_conn *conn, + uint16_t interval, + uint16_t latency, + uint16_t timeout); + /** + * @brief Handle the advertisement + * + * @param addr The device's MAC address that send out ADV + * + * @param rssi The antenna's RSSI + * + * @param type The advertise type + * + * @param ad The advertisement RAW data + * + * @param len The RAW data's length + * + * @return none + * + * @note none + */ + void handleDeviceFound(const bt_addr_le_t *addr, + int8_t rssi, + uint8_t type, + const uint8_t *ad, + uint8_t len); +private: + BLECentralRole(); + ~BLECentralRole(); + BLEPeripheralHelper* _peripherial[BLE_MAX_CONN_CFG]; + BLECentralHelper _central; + struct bt_le_scan_param _scan_param; + + static BLECentralRole* _ble_central_ins; + ble_advertise_handle_cb_t _adv_event_handle; +}; + +#endif + diff --git a/libraries/CurieBLE/src/BLECharacteristic.cpp b/libraries/CurieBLE/src/BLECharacteristic.cpp index a6c46c07..5c810a5f 100644 --- a/libraries/CurieBLE/src/BLECharacteristic.cpp +++ b/libraries/CurieBLE/src/BLECharacteristic.cpp @@ -18,28 +18,75 @@ */ #include "BLECharacteristic.h" +#include "BLEPeripheralHelper.h" #include "internal/ble_client.h" -#define BLE_CCCD_NOTIFY_EN_MASK 0x1 -#define BLE_CCCD_INDICATE_EN_MASK 0x2 +uint8_t profile_notify_process (struct bt_conn *conn, + struct bt_gatt_subscribe_params *params, + const void *data, uint16_t length); +uint8_t profile_read_rsp_process(struct bt_conn *conn, int err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length); + +unsigned char BLECharacteristic::_numNotifyAttributes = 0; + +struct bt_uuid_16 BLECharacteristic::_gatt_chrc_uuid = {BT_UUID_TYPE_16, BT_UUID_GATT_CHRC_VAL}; +struct bt_uuid_16 BLECharacteristic::_gatt_ccc_uuid = {BT_UUID_TYPE_16, BT_UUID_GATT_CCC_VAL}; BLECharacteristic::BLECharacteristic(const char* uuid, const unsigned char properties, const unsigned short maxLength) : BLEAttribute(uuid, BLETypeCharacteristic), - _properties(properties), _value_length(0), _written(false), - _cccd_value(0), - _value_handle(0), - _cccd_handle(0), _user_description(NULL), - _presentation_format(NULL) + _presentation_format(NULL), + _attr_chrc_declaration(NULL), + _attr_chrc_value(NULL), + _attr_cccd(NULL) { _value_size = maxLength > BLE_MAX_ATTR_DATA_LEN ? BLE_MAX_ATTR_DATA_LEN : maxLength; _value = (unsigned char*)malloc(_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)); + + _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 = this->uuid(); memset(_event_handlers, 0, sizeof(_event_handlers)); + + _numNotifyAttributes++; + if (properties & (BT_GATT_CHRC_NOTIFY | BT_GATT_CHRC_INDICATE)) + { + _numNotifyAttributes++; + } + _sub_params.notify = profile_notify_process; } BLECharacteristic::BLECharacteristic(const char* uuid, @@ -61,44 +108,40 @@ BLECharacteristic::~BLECharacteristic() unsigned char BLECharacteristic::properties() const { - return _properties; + return _gatt_chrc.properties; } bool BLECharacteristic::setValue(const unsigned char value[], uint16_t length) { - BleStatus status; + int status; _setValue(value, length); - if (_value_handle) { - status = ble_client_gatts_set_attribute_value(_value_handle, _value_length, _value, 0); - if (BLE_STATUS_SUCCESS != status) { + if (_attr_chrc_value) + { + // TODO: Notify for peripheral. + // Write request for central. + status = bt_gatt_notify(NULL, _attr_chrc_value, value, length, NULL); + if (0 != status) + { return false; } - - if (subscribed()) { - boolean_t indication = (_cccd_value & BLE_CCCD_INDICATE_EN_MASK); - - status = ble_client_gatts_send_notif_ind(_value_handle, _value_length, _value, 0, indication); - if (BLE_STATUS_SUCCESS != status) { - return false; - } - } } - return true; } void -BLECharacteristic::setValue(BLECentral& central, const unsigned char* value, unsigned short length) +BLECharacteristic::setValue(BLEHelper& blehelper, const unsigned char* value, unsigned short length) { + //BLEHelper *bledevice = ¢ral; _setValue(value, length); _written = true; + _reading = false; if (_event_handlers[BLEWritten]) { - _event_handlers[BLEWritten](central, *this); + _event_handlers[BLEWritten](blehelper, *this); } } @@ -139,7 +182,7 @@ BLECharacteristic::written() bool BLECharacteristic::subscribed() { - return (_cccd_value & (BLE_CCCD_NOTIFY_EN_MASK | BLE_CCCD_INDICATE_EN_MASK)); + return (_gatt_chrc.properties & (BT_GATT_CHRC_NOTIFY | BT_GATT_CHRC_INDICATE)); } void @@ -152,114 +195,212 @@ BLECharacteristic::setEventHandler(BLECharacteristicEvent event, BLECharacterist interrupts(); } -bool -BLECharacteristic::add(uint16_t serviceHandle) +uint16_t +BLECharacteristic::valueHandle() { - bt_uuid uuid = btUuid(); + uint16_t handle = 0; + if (NULL != _attr_chrc_value) + { + handle = _attr_chrc_value->handle; + } + + return handle; +} - struct ble_gatts_characteristic char_data; - struct ble_gatts_char_handles handles; - struct ble_gatt_char_user_desc user_desc; - struct ble_gatt_pf_desc pf_desc; +uint16_t +BLECharacteristic::cccdHandle() +{ + uint16_t handle = 0; + if (NULL != _attr_cccd) + { + handle = _attr_cccd->handle; + } + return handle; +} - memset(&char_data, 0, sizeof(char_data)); +void +BLECharacteristic::setUserDescription(BLEDescriptor *descriptor) +{ + _user_description = descriptor; +} - char_data.p_uuid = &uuid; - char_data.props.props = _properties; +void +BLECharacteristic::setPresentationFormat(BLEDescriptor *descriptor) +{ + _presentation_format = descriptor; +} - if (_properties & (BLERead | BLENotify | BLEIndicate)) { - char_data.perms.rd = GAP_SEC_MODE_1 | GAP_SEC_LEVEL_1; - } else { - char_data.perms.rd = GAP_SEC_NO_PERMISSION; +void +BLECharacteristic::_setValue(const uint8_t value[], uint16_t length) +{ + if (length > _value_size) { + length = _value_size; } - if (_properties & (BLEWriteWithoutResponse | BLEWrite)) { - char_data.perms.wr = GAP_SEC_MODE_1 | GAP_SEC_LEVEL_1; - } else { - char_data.perms.wr = GAP_SEC_NO_PERMISSION; - } + memcpy(_value, value, length); + _value_length = length; +} + +unsigned char +BLECharacteristic::numNotifyAttributes(void) { + return _numNotifyAttributes; +} - char_data.init_len = _value_length; - char_data.max_len = _value_size; - char_data.p_value = _value; +struct _bt_gatt_ccc* BLECharacteristic::getCccCfg(void) +{ + return &_ccc_value; +} - if (_user_description) { - user_desc.buffer = (uint8_t*)_user_description->value(); - user_desc.len = _user_description->valueLength(); +struct bt_gatt_chrc* BLECharacteristic::getCharacteristicAttValue(void) +{ + return &_gatt_chrc; +} - char_data.p_user_desc = &user_desc; +uint8_t BLECharacteristic::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; +} - if (_presentation_format) { - const uint8_t* pfValue = _presentation_format->value(); +struct bt_uuid* BLECharacteristic::getCharacteristicAttributeUuid(void) +{ + return (struct bt_uuid*) &_gatt_chrc_uuid; +} +struct bt_uuid* BLECharacteristic::getClientCharacteristicConfigUuid(void) +{ + return (struct bt_uuid*) &_gatt_ccc_uuid; +} - pf_desc.format = pfValue[0]; - pf_desc.exp = pfValue[1]; - pf_desc.unit = (pfValue[3] << 8) | pfValue[2]; - pf_desc.name_spc = pfValue[4]; - pf_desc.descr = (pfValue[6] << 8) | pfValue[5]; - char_data.p_char_pf_desc = &pf_desc; - } - - BleStatus status = ble_client_gatts_add_characteristic(serviceHandle, &char_data, &handles); - if (BLE_STATUS_SUCCESS == status) { - _value_handle = handles.value_handle; - _cccd_handle = handles.cccd_handle; - } +void BLECharacteristic::addCharacteristicDeclaration(struct bt_gatt_attr *gatt_attr) +{ + _attr_chrc_declaration = gatt_attr; +} - return (BLE_STATUS_SUCCESS == status); +void BLECharacteristic::addCharacteristicValue(struct bt_gatt_attr *gatt_attr) +{ + _attr_chrc_value = gatt_attr; } -uint16_t -BLECharacteristic::valueHandle() +void BLECharacteristic::addCharacteristicConfigDescriptor(struct bt_gatt_attr *gatt_attr) { - return _value_handle; + _attr_cccd = gatt_attr; } -uint16_t -BLECharacteristic::cccdHandle() +void BLECharacteristic::discover(struct bt_gatt_discover_params *params) { - return _cccd_handle; + params->type = BT_GATT_DISCOVER_CHARACTERISTIC; + params->uuid = this->uuid(); + // Start discovering + _discoverying = true; + // Re-Init the read/write parameter + _reading = false; } -void -BLECharacteristic::setCccdValue(BLECentral& central, uint16_t value) + +void BLECharacteristic::discover(const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) { - if (_cccd_value != value) { - _cccd_value = value; - - if (subscribed()) { - if (_event_handlers[BLESubscribed]) { - _event_handlers[BLESubscribed](central, *this); - } - } else { - if (_event_handlers[BLEUnsubscribed]) { - _event_handlers[BLEUnsubscribed](central, *this); - } + 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; + } } -void -BLECharacteristic::setUserDescription(BLEDescriptor *descriptor) +struct bt_gatt_subscribe_params *BLECharacteristic::getSubscribeParams() { - _user_description = descriptor; + return &_sub_params; } -void -BLECharacteristic::setPresentationFormat(BLEDescriptor *descriptor) +bool BLECharacteristic::read(BLEPeripheralHelper &peripheral) { - _presentation_format = descriptor; + int retval = 0; + struct bt_conn* conn = NULL; + if (_reading) + { + // Already in reading state + return false; + } + + _read_params.func = profile_read_rsp_process; + _read_params.handle_count = 1; + _read_params.single.handle = peripheral.valueHandle(this); + _read_params.single.offset = 0; + + if (0 == _read_params.single.handle) + { + // Discover not complete + return false; + } + + conn = bt_conn_lookup_addr_le(peripheral.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; } -void -BLECharacteristic::_setValue(const uint8_t value[], uint16_t length) +bool BLECharacteristic::write(BLEPeripheralHelper &peripheral, + const unsigned char value[], + uint16_t length) { - if (length > _value_size) { - length = _value_size; + int retval = 0; + struct bt_conn* conn = NULL; + + conn = bt_conn_lookup_addr_le(peripheral.bt_le_address()); + if (NULL == conn) + { + return false; } - - memcpy(_value, value, length); - _value_length = length; + + // Send read request + retval = bt_gatt_write_without_response(conn, + peripheral.valueHandle(this), + value, length, false); + bt_conn_unref(conn); + return (0 == retval); } + + diff --git a/libraries/CurieBLE/src/BLECharacteristic.h b/libraries/CurieBLE/src/BLECharacteristic.h index a5afaa36..a867e296 100644 --- a/libraries/CurieBLE/src/BLECharacteristic.h +++ b/libraries/CurieBLE/src/BLECharacteristic.h @@ -20,8 +20,10 @@ #ifndef _BLE_CHARACTERISTIC_H_INCLUDED #define _BLE_CHARACTERISTIC_H_INCLUDED +#include "BLECommon.h" + #include "BLEAttribute.h" -#include "BLECentral.h" +#include "BLECentralHelper.h" #include "BLEDescriptor.h" /** @@ -38,9 +40,10 @@ enum BLECharacteristicEvent { /* Forward declaration needed for callback function prototype below */ class BLECharacteristic; class BLEPeripheral; +class BLEHelper; /** Function prototype for BLE Characteristic event callback */ -typedef void (*BLECharacteristicEventHandler)(BLECentral ¢ral, BLECharacteristic &characteristic); +typedef void (*BLECharacteristicEventHandler)(BLEHelper &bleHelper, BLECharacteristic &characteristic); /** * BLE Characteristic Property types @@ -94,6 +97,18 @@ class BLECharacteristic : public BLEAttribute { */ bool setValue(const unsigned char value[], unsigned short length); + /** + * Set the current value of the Characteristic + * + * @param central The central device that update the value. + * @param value New value to set, as a byte array. Data is stored in internal copy. + * @param 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 + */ + void setValue(BLEHelper& blehelper, const uint8_t value[], uint16_t length); + /** * Get the property mask of the Characteristic * @@ -146,38 +161,161 @@ class BLECharacteristic : public BLEAttribute { */ void setEventHandler(BLECharacteristicEvent event, BLECharacteristicEventHandler callback); -protected: - bool add(uint16_t serviceHandle); + /** + * @brief Get Notify Attribute counter that created + * + * @param none + * + * @return unsigned char The totla number of the notify attributes + * + * @note none + */ + static unsigned char numNotifyAttributes(void); + + /** + * @brief Schedule the read request to read the characteristic in peripheral + * + * @param peripheral The peripheral device that want to read. + * + * @return bool Indicate the success or error + * + * @note Only for central device + */ + bool read(BLEPeripheralHelper &peripheral); + + /** + * @brief Schedule the write request to update the characteristic in peripheral + * + * @param peripheral The peripheral device that want to be updated + * @param value New value to set, as a byte array. Data is stored in internal copy. + * @param 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(BLEPeripheralHelper &peripheral, + const unsigned char value[], + uint16_t length); +protected: + friend class BLEProfile; + + void addCharacteristicDeclaration(struct bt_gatt_attr *gatt_attr); + void addCharacteristicValue(struct bt_gatt_attr *gatt_attr); + void addCharacteristicConfigDescriptor(struct bt_gatt_attr *gatt_attr); + + /** + * @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 setValue(BLECentral& central, const uint8_t value[], uint16_t length); - void setCccdValue(BLECentral& central, uint16_t value); - + void setUserDescription(BLEDescriptor *descriptor); void setPresentationFormat(BLEDescriptor *descriptor); - - friend class BLEPeripheral; + + struct _bt_gatt_ccc* getCccCfg(void); + struct bt_gatt_chrc* getCharacteristicAttValue(void); + static struct bt_uuid* getCharacteristicAttributeUuid(void); + static struct bt_uuid* 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 attr The discover response + * + * @param params The discover parameter that need to fill + * + * @return none + * + * @note Only for central + */ + void discover(const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params); + + /** + * @brief For central to discover the peripherial profile + * + * @param params The discover parameter that need to fill + * + * @return none + * + * @note Only for central + */ + void discover(struct bt_gatt_discover_params *params); + + /** + * @brief Get the subscribe parameter + * + * @param none + * + * @return struct bt_gatt_subscribe_params * the subscribe parameter + * + * @note Only for central + */ + struct bt_gatt_subscribe_params* getSubscribeParams(); private: void _setValue(const uint8_t value[], uint16_t length); private: - unsigned char _properties; + + static unsigned char _numNotifyAttributes; + static struct bt_uuid_16 _gatt_chrc_uuid; + static struct bt_uuid_16 _gatt_ccc_uuid; + unsigned short _value_size; unsigned short _value_length; unsigned char* _value; bool _written; - uint16_t _cccd_value; uint16_t _value_handle; - uint16_t _cccd_handle; + struct bt_gatt_ccc_cfg _ccc_cfg; + struct _bt_gatt_ccc _ccc_value; + struct bt_gatt_chrc _gatt_chrc; BLEDescriptor* _user_description; BLEDescriptor* _presentation_format; + struct bt_gatt_attr *_attr_chrc_declaration; + struct bt_gatt_attr *_attr_chrc_value; + struct bt_gatt_attr *_attr_cccd; + + // For central device to subscribe the Notification/Indication + struct bt_gatt_subscribe_params _sub_params; + + bool _reading; + struct bt_gatt_read_params _read_params; BLECharacteristicEventHandler _event_handlers[BLECharacteristicEventLast]; }; diff --git a/libraries/CurieBLE/src/BLECommon.h b/libraries/CurieBLE/src/BLECommon.h index 20bf0e23..00c31342 100644 --- a/libraries/CurieBLE/src/BLECommon.h +++ b/libraries/CurieBLE/src/BLECommon.h @@ -22,9 +22,21 @@ #include "Arduino.h" -#include "../src/services/ble/ble_protocol.h" -#include "services/ble/ble_service_gatt.h" -#include "services/ble/ble_service_gatts_api.h" +#include "../src/services/ble_service/ble_protocol.h" + + +#include "infra/log.h" + + +#include +#include +#include +#include + +#define BLE_ADDR_LEN 6 + +#define MAX_UUID_SIZE 16 + /* Theoretically we should be able to support attribute lengths up to 512 bytes * but this involves splitting it across multiple packets. For simplicity, @@ -40,6 +52,30 @@ /* Invalid BLE Address type */ #define BLE_DEVICE_ADDR_INVALID 0xFF +/** 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_GAP_BASE = 0x100, /**< GAP specific error base */ + BLE_STATUS_GATT_BASE = 0x200, /**< GATT specific Error base */ +}; + +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 *data, + uint8_t data_len, void *user_data); + #endif // _BLE_COMMON_H_INCLUDED diff --git a/libraries/CurieBLE/src/BLEDescriptor.cpp b/libraries/CurieBLE/src/BLEDescriptor.cpp index 8e50120b..d611d471 100644 --- a/libraries/CurieBLE/src/BLEDescriptor.cpp +++ b/libraries/CurieBLE/src/BLEDescriptor.cpp @@ -63,23 +63,3 @@ BLEDescriptor::operator[] (int offset) const return _value[offset]; } -bool -BLEDescriptor::add(uint16_t serviceHandle) -{ - bt_uuid uuid = btUuid(); - struct ble_gatts_descriptor desc; - uint16_t handle = 0; - - memset(&desc, 0, sizeof(desc)); - - desc.p_uuid = &uuid; - - desc.p_value = _value; - desc.length = _value_length; - - // this class only supports read-only descriptors - desc.perms.rd = GAP_SEC_MODE_1 | GAP_SEC_LEVEL_1; - desc.perms.wr = GAP_SEC_NO_PERMISSION; - - return (ble_client_gatts_add_descriptor(serviceHandle, &desc, &handle) == BLE_STATUS_SUCCESS); -} diff --git a/libraries/CurieBLE/src/BLEDescriptor.h b/libraries/CurieBLE/src/BLEDescriptor.h index 08e53f13..498035a0 100644 --- a/libraries/CurieBLE/src/BLEDescriptor.h +++ b/libraries/CurieBLE/src/BLEDescriptor.h @@ -65,7 +65,6 @@ class BLEDescriptor : public BLEAttribute { unsigned char operator[] (int offset) const; protected: - bool add(uint16_t serviceHandle); friend BLEPeripheral; diff --git a/libraries/CurieBLE/src/BLEHelper.cpp b/libraries/CurieBLE/src/BLEHelper.cpp new file mode 100644 index 00000000..b756f5d4 --- /dev/null +++ b/libraries/CurieBLE/src/BLEHelper.cpp @@ -0,0 +1,135 @@ + +#include "BLECentralHelper.h" + +#include "BLEPeripheral.h" + + +BLEHelper::BLEHelper() +{ + clearAddress(); + memset(&_conn_params, 0x00, sizeof(_conn_params)); + _conn_params.interval_max = BT_GAP_INIT_CONN_INT_MAX; + _conn_params.interval_min = BT_GAP_INIT_CONN_INT_MIN; + _conn_params.latency = 0; + _conn_params.timeout = 400; +} + +BLEHelper::~BLEHelper() +{ + #if 0 + if (NULL != _conn) + { + bt_conn_unref(_conn); + } + #endif +} + +#if 0 +void BLEHelper::setConn(struct bt_conn *conn) +{ + if (conn == _conn) + { + return; + } + + if (NULL != _conn) + { + bt_conn_unref(_conn); + } + _conn = conn; +} +#endif + +BLEHelper::operator bool() const +{ + bt_addr_le_t zero; + + memset(&zero, 0, sizeof(zero)); + + return (memcmp(&_address, &zero, sizeof(_address)) != 0); +} + +bool BLEHelper::operator==(const BLEHelper& rhs) const +{ + return (memcmp(&_address, &rhs._address, sizeof(_address)) == 0); +} + +bool +BLEHelper::operator==(const bt_addr_le_t& address) const { + return (memcmp(&_address, &address, sizeof(_address)) == 0); +} + +bool +BLEHelper::operator!=(const BLEHelper& rhs) const { + return !(*this == rhs); +} + +const char* +BLEHelper::address() const { + static char address[18]; + + String addressStr = ""; + + for (int i = 5; i >= 0; i--) { + unsigned char a = _address.val[i]; + + if (a < 0x10) { + addressStr += "0"; + } + + addressStr += String(a, 16); + + if (i > 0) { + addressStr += ":"; + } + } + + strcpy(address, addressStr.c_str()); + + return address; +} +/* +const bt_addr_t *BLEHelper::address(void) const +{ + return (bt_addr_t *)_address.val; +} +*/ + +const bt_addr_le_t *BLEHelper::bt_le_address(void) const +{ + return &_address; +} + +void +BLEHelper::poll() { + delay(1); +} + +void +BLEHelper::setAddress(bt_addr_le_t address) { + _address = address; +} + +void +BLEHelper::clearAddress() { + memset(&_address, 0x00, sizeof(_address)); +} + +const struct bt_le_conn_param *BLEHelper::getConnParams() +{ + return &_conn_params; +} + +void BLEHelper::setConnParames(uint16_t intervalmin, + uint16_t intervalmax, + uint16_t latency, + uint16_t timeout) +{ + _conn_params.interval_max = intervalmin; + _conn_params.interval_min = intervalmax; + _conn_params.latency = latency; + _conn_params.timeout = timeout; + +} + + diff --git a/libraries/CurieBLE/src/BLEHelper.h b/libraries/CurieBLE/src/BLEHelper.h new file mode 100644 index 00000000..f844eea6 --- /dev/null +++ b/libraries/CurieBLE/src/BLEHelper.h @@ -0,0 +1,79 @@ +/* + * 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_HELPER_H_ +#define _BLE_HELPER_H_ + + +class BLEHelper { + public: + /** + * Is the Central connected + * + * @return boolean_t true if the central is connected, otherwise false + */ + virtual bool connected(void) = 0; + + /** + * Get the address of the Central in string form + * + * @return const char* address of the Central in string form + */ + const char* address(void) const; + + const bt_addr_le_t *bt_le_address(void) const; + /** + * Disconnect the central if it is connected + * + */ + virtual bool disconnect(void) = 0; + + /** + * Poll the central for events + */ + void poll(void); + + operator bool(void) const; + bool operator==(const BLEHelper& rhs) const; + bool operator==(const bt_addr_le_t& rhs) const; + bool operator!=(const BLEHelper& rhs) const; + void operator=(const BLEHelper& rhs); + void setConn(struct bt_conn *conn); + + const struct bt_le_conn_param *getConnParams(); + void setConnParames(uint16_t intervalmin, + uint16_t intervalmax, + uint16_t latency, + uint16_t timeout); + + protected: + void setAddress(bt_addr_le_t address); + void clearAddress(); + BLEHelper(); + virtual ~BLEHelper(); + + private: + bt_addr_le_t _address; + //struct bt_conn *_conn; + struct bt_le_conn_param _conn_params; +}; + +#endif + + diff --git a/libraries/CurieBLE/src/BLEPeripheral.cpp b/libraries/CurieBLE/src/BLEPeripheral.cpp index f1218bcd..ca40137b 100644 --- a/libraries/CurieBLE/src/BLEPeripheral.cpp +++ b/libraries/CurieBLE/src/BLEPeripheral.cpp @@ -18,112 +18,42 @@ */ #include "BLEPeripheral.h" +#include "BLEPeripheralRole.h" -#include "BLECharacteristic.h" -#include "BLEDescriptor.h" -#include "BLEService.h" -#include "BLEUuid.h" - - -#define BLE_DISCONNECT_REASON_LOCAL_TERMINATION 0x16 - -void -blePeripheralGapEventHandler(ble_client_gap_event_t event, struct ble_gap_event *event_data, void *param) -{ - BLEPeripheral* p = (BLEPeripheral*)param; - - p->handleGapEvent(event, event_data); -} - -void -blePeripheralGattsEventHandler(ble_client_gatts_event_t event, struct ble_gatts_evt_msg *event_data, void *param) -{ - BLEPeripheral* p = (BLEPeripheral*)param; - - p->handleGattsEvent(event, event_data); -} +//#include "BLECharacteristic.h" BLEPeripheral::BLEPeripheral(void) : - _state(BLE_PERIPH_STATE_NOT_READY), - _advertise_service_uuid(NULL), _local_name(NULL), - _service_data_uuid(NULL), - _service_data(NULL), - _service_data_length(0), _appearance(0), - _min_conn_interval(DEFAULT_MIN_CONN_INTERVAL), - _max_conn_interval(DEFAULT_MAX_CONN_INTERVAL), - _central(this), - _attributes(NULL), - _num_attributes(0), - _last_added_characteritic(NULL) + _adv_data_idx(0) { - memset(_event_handlers, 0x00, sizeof(_event_handlers)); - - ble_client_get_factory_config(&_local_bda, _device_name); + memset(_adv_data, 0x00, sizeof(_adv_data)); + + // Default Advertising parameter + setAdvertisingParam(BT_LE_ADV_IND , + 0xA0, + 0xF0); } BLEPeripheral::~BLEPeripheral(void) { - if (this->_attributes) { - free(this->_attributes); - } } bool BLEPeripheral::begin() { - BleStatus status; - - status = _init(); - if (status != BLE_STATUS_SUCCESS) { + bool ret = false; + + pr_info(LOG_MODULE_BLE, "%s: %d", __FUNCTION__, 1); + + ret = BLEPeripheralRole::instance()->begin(); + if (!ret) + { return false; } - - /* Populate advertising data - */ - _advDataInit(); - - status = ble_client_gap_wr_adv_data(_adv_data, _adv_data_len); - if (BLE_STATUS_SUCCESS != status) { - return false; - } - - uint16_t lastServiceHandle = 0; - - for (int i = 0; i < _num_attributes; i++) { - BLEAttribute* attribute = _attributes[i]; - BLEAttributeType type = attribute->type(); - bool addResult = false; - - if (BLETypeService == type) { - BLEService* service = (BLEService*)attribute; - - addResult = service->add(); - - lastServiceHandle = service->handle(); - } else if (BLETypeCharacteristic == type) { - BLECharacteristic* characteristic = (BLECharacteristic*)attribute; - - addResult = characteristic->add(lastServiceHandle); - } else if (BLETypeDescriptor == type) { - BLEDescriptor *descriptor = (BLEDescriptor*)attribute; - - if (strcmp(descriptor->uuid(), "2901") == 0 || - strcmp(descriptor->uuid(), "2902") == 0 || - strcmp(descriptor->uuid(), "2903") == 0 || - strcmp(descriptor->uuid(), "2904") == 0) { - continue; // skip - } - - addResult = descriptor->add(lastServiceHandle); - } - - if (!addResult) { - return false; - } - } - - return (_startAdvertising() == BLE_STATUS_SUCCESS); + + pr_info(LOG_MODULE_BLE, "%s: %d", __FUNCTION__, 2); + + return (startAdvertising() == BLE_STATUS_SUCCESS); } void @@ -136,23 +66,11 @@ BLEPeripheral::poll() void BLEPeripheral::end() { - _stop(); -} - -uint8_t -BLEPeripheral::getAdvertisingLength() -{ - return _adv_data_len; -} - -uint8_t* -BLEPeripheral::getAdvertising() -{ - return _adv_data; + BLEPeripheralRole::instance()->stop(); } void -BLEPeripheral::setAdvertisedServiceUuid(const char* advertisedServiceUuid) +BLEPeripheral::setAdvertisedServiceUuid(const struct bt_uuid* advertisedServiceUuid) { _advertise_service_uuid = advertisedServiceUuid; } @@ -164,23 +82,28 @@ BLEPeripheral::setLocalName(const char* localName) } void -BLEPeripheral::setAdvertisedServiceData(const char* serviceDataUuid, uint8_t* serviceData, uint8_t serviceDataLength) +BLEPeripheral::setAdvertisedServiceData(const struct bt_uuid* serviceDataUuid, + uint8_t* serviceData, + uint8_t serviceDataLength) { _service_data_uuid = serviceDataUuid; _service_data = serviceData; _service_data_length = serviceDataLength; } +void BLEPeripheral::setAdvertisingParam(uint8_t type, + uint16_t interval_min, + uint16_t interval_max) +{ + BLEPeripheralRole::instance()->setAdvertisingParam(type, + interval_min, + interval_max); +} + void BLEPeripheral::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); - } + BLEPeripheralRole::instance()->setDeviceName(deviceName); } void @@ -192,286 +115,160 @@ BLEPeripheral::setAppearance(const uint16_t appearance) void BLEPeripheral::setConnectionInterval(const unsigned short minConnInterval, const unsigned short maxConnInterval) { - _min_conn_interval = minConnInterval; - _max_conn_interval = maxConnInterval; - - if (_min_conn_interval < MIN_CONN_INTERVAL) { - _min_conn_interval = MIN_CONN_INTERVAL; - } else if (_min_conn_interval > MAX_CONN_INTERVAL) { - _min_conn_interval = MAX_CONN_INTERVAL; - } - - if (_max_conn_interval < _min_conn_interval) { - _max_conn_interval = _min_conn_interval; - } else if (_max_conn_interval > MAX_CONN_INTERVAL) { - _max_conn_interval = MAX_CONN_INTERVAL; - } + BLEPeripheralRole::instance()->setConnectionInterval(minConnInterval, + maxConnInterval); } void -BLEPeripheral::setEventHandler(BLEPeripheralEvent event, BLEPeripheralEventHandler callback) +BLEPeripheral::setEventHandler(BLERoleEvent event, BLERoleEventHandler callback) { - if (event < sizeof(_event_handlers)) { - _event_handlers[event] = callback; - } -} - -void -BLEPeripheral::addAttribute(BLEAttribute& attribute) -{ - if (_attributes == NULL) { - _attributes = (BLEAttribute**)malloc(BLEAttribute::numAttributes() * sizeof(BLEAttribute*)); - } - - _attributes[_num_attributes] = &attribute; - _num_attributes++; - - BLEAttributeType type = attribute.type(); - - if (BLETypeCharacteristic == type) { - _last_added_characteritic = (BLECharacteristic*)&attribute; - } else if (BLETypeDescriptor == type) { - if (_last_added_characteritic) { - BLEDescriptor* descriptor = (BLEDescriptor*)&attribute; - - if (strcmp("2901", descriptor->uuid()) == 0) { - _last_added_characteritic->setUserDescription(descriptor); - } else if (strcmp("2904", descriptor->uuid()) == 0) { - _last_added_characteritic->setPresentationFormat(descriptor); - } - } - } + BLEPeripheralRole::instance()->setEventHandler(event, callback); } bool BLEPeripheral::disconnect() { - BleStatus status; - - if (BLE_PERIPH_STATE_CONNECTED == _state) { - status = ble_client_gap_disconnect(BLE_DISCONNECT_REASON_LOCAL_TERMINATION); - } else { - status = BLE_STATUS_WRONG_STATE; - } - - return (status == BLE_STATUS_SUCCESS); + return BLEPeripheralRole::instance()->disconnect(); } -BLECentral +BLECentralHelper BLEPeripheral::central() { - poll(); - - return _central; + return BLEPeripheralRole::instance()->central(); } bool BLEPeripheral::connected() { - poll(); - - return _central; + return BLEPeripheralRole::instance()->connected(); } -BleStatus -BLEPeripheral::_init() +void BLEPeripheral::addAttribute(BLEAttribute& attribute) { - BleStatus status; - int8_t txPower = 127; - - if (BLE_PERIPH_STATE_NOT_READY != _state) - return BLE_STATUS_WRONG_STATE; - - status = ble_client_init(blePeripheralGapEventHandler, this, - blePeripheralGattsEventHandler, this); - if (BLE_STATUS_SUCCESS != status) { - return status; - } - - status = ble_client_gap_set_enable_config(_device_name, &_local_bda, _appearance, txPower, _min_conn_interval, _max_conn_interval); - if (BLE_STATUS_SUCCESS != status) { - return status; - } - - _state = BLE_PERIPH_STATE_READY; - return BLE_STATUS_SUCCESS; + BLEPeripheralRole::instance()->addAttribute(attribute); } -void + +BleStatus BLEPeripheral::_advDataInit(void) { - uint8_t *adv_tmp = _adv_data; - - memset(_adv_data, 0, sizeof(_adv_data)); - + uint8_t lengthTotal = 2; // Flags data length + _adv_data_idx = 0; + /* Add flags */ - *adv_tmp++ = 2; - *adv_tmp++ = BLE_ADV_TYPE_FLAGS; - *adv_tmp++ = BLE_SVC_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE; - _adv_data_len = 3; - - if (_advertise_service_uuid) { - BLEUuid bleUuid = BLEUuid(_advertise_service_uuid); - struct bt_uuid uuid = bleUuid.uuid(); - - if (BT_UUID16 == uuid.type) { - uint8_t *adv_tmp = &_adv_data[_adv_data_len]; - *adv_tmp++ = (1 + sizeof(uint16_t)); /* Segment data length */ - *adv_tmp++ = BLE_ADV_TYPE_COMP_16_UUID; /* Needed for Eddystone */ - UINT16_TO_LESTREAM(adv_tmp, uuid.uuid16); - _adv_data_len += (2 + sizeof(uint16_t)); - } else if (BT_UUID128 == uuid.type) { - uint8_t *adv_tmp = &_adv_data[_adv_data_len]; - *adv_tmp++ = (1 + MAX_UUID_SIZE); /* Segment data length */ - *adv_tmp++ = BLE_ADV_TYPE_INC_128_UUID; - memcpy(adv_tmp, uuid.uuid128, MAX_UUID_SIZE); - _adv_data_len += (2 + MAX_UUID_SIZE); + _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 (_advertise_service_uuid) + { + uint8_t type; + uint8_t length; + uint8_t *data = NULL; + + pr_info(LOG_MODULE_BLE, "ADV Type-%d", _advertise_service_uuid->type); + if (BT_UUID_TYPE_16 == _advertise_service_uuid->type) + { + //UINT16_TO_LESTREAM(adv_tmp, uuid.uuid16); + data = (uint8_t *)&(((struct bt_uuid_16 *)_advertise_service_uuid)->val); + length = sizeof(uint16_t); + type = BT_DATA_UUID16_ALL; + } + else if (BT_UUID_TYPE_128 == _advertise_service_uuid->type) + { + data = ((struct bt_uuid_128 *)_advertise_service_uuid)->val; + length = MAX_UUID_SIZE; + 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) { + if (_local_name) + { /* Add device name (truncated if too long) */ - uint8_t calculated_len; - - adv_tmp = &_adv_data[_adv_data_len]; - if (_adv_data_len + strlen(_local_name) + 2 <= BLE_MAX_ADV_SIZE) { - *adv_tmp++ = strlen(_local_name) + 1; - *adv_tmp++ = BLE_ADV_TYPE_COMP_LOCAL_NAME; - calculated_len = strlen(_local_name); - } else { - *adv_tmp++ = BLE_MAX_ADV_SIZE - _adv_data_len - 1; - *adv_tmp++ = BLE_ADV_TYPE_SHORT_LOCAL_NAME; - calculated_len = BLE_MAX_ADV_SIZE - _adv_data_len - 2; - } - - memcpy(adv_tmp, _local_name, calculated_len); - _adv_data_len += calculated_len + 2; + _adv_data[_adv_data_idx].type = BT_DATA_NAME_COMPLETE; + _adv_data[_adv_data_idx].data = (const uint8_t*)_local_name; + _adv_data[_adv_data_idx].data_len = strlen(_local_name); + _adv_data_idx++; + + lengthTotal += strlen(_local_name); + pr_info(LOG_MODULE_BLE, "Local Name -%s", _local_name); + pr_info(LOG_MODULE_BLE, "Local Name Len -%d", strlen(_local_name)); } - if (_service_data) { + if (_service_data) + { /* Add Service Data (if it will fit) */ - BLEUuid bleUuid = BLEUuid(_service_data_uuid); - struct bt_uuid uuid = bleUuid.uuid(); - /* A 128-bit Service Data UUID won't fit in an Advertising packet */ - if (BT_UUID16 != uuid.type) { - return; /* We support service data only for 16-bit service UUID */ + 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 = 1 + sizeof(uint16_t) + _service_data_length; - if (_adv_data_len + 1 + block_len > BLE_MAX_ADV_SIZE) { - return; // Service data block is too large. + 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++; - adv_tmp = &_adv_data[_adv_data_len]; + uint8_t *adv_tmp = _service_data_buf; - *adv_tmp++ = block_len; - _adv_data_len++; - - *adv_tmp++ = BLE_ADV_TYPE_SERVICE_DATA_16_UUID; - UINT16_TO_LESTREAM(adv_tmp, uuid.uuid16); + UINT16_TO_LESTREAM(adv_tmp, (((struct bt_uuid_16 *)_service_data_uuid)->val)); memcpy(adv_tmp, _service_data, _service_data_length); - - _adv_data_len += block_len; + + lengthTotal += block_len; + pr_info(LOG_MODULE_BLE, "SVC Len -%d", block_len); + } + 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; } -} - -BleStatus -BLEPeripheral::_startAdvertising() -{ - BleStatus status; - - if (_state != BLE_PERIPH_STATE_READY) - return BLE_STATUS_WRONG_STATE; - - status = ble_client_gap_start_advertise(0); // 0 = no timeout - if (BLE_STATUS_SUCCESS != status) - return status; - - _state = BLE_PERIPH_STATE_ADVERTISING; return BLE_STATUS_SUCCESS; } BleStatus -BLEPeripheral::_stop(void) +BLEPeripheral::startAdvertising() { - BleStatus status; - - if (BLE_PERIPH_STATE_ADVERTISING == _state) - status = ble_client_gap_stop_advertise(); - else - status = disconnect(); - + BleStatus status = BLE_STATUS_SUCCESS; + status = _advDataInit(); if (BLE_STATUS_SUCCESS != status) + { return status; - - _state = BLE_PERIPH_STATE_READY; - return BLE_STATUS_SUCCESS; -} - -void -BLEPeripheral::handleGapEvent(ble_client_gap_event_t event, struct ble_gap_event *event_data) -{ - if (BLE_CLIENT_GAP_EVENT_CONNECTED == event) { - _state = BLE_PERIPH_STATE_CONNECTED; - _central.setAddress(event_data->connected.peer_bda); - - if (_event_handlers[BLEConnected]) { - _event_handlers[BLEConnected](_central); - } - } else if (BLE_CLIENT_GAP_EVENT_DISCONNECTED == event) { - - for (int i = 0; i < _num_attributes; i++) { - BLEAttribute* attribute = _attributes[i]; - - if (attribute->type() == BLETypeCharacteristic) { - BLECharacteristic* characteristic = (BLECharacteristic*)attribute; - - characteristic->setCccdValue(_central, 0x0000); // reset CCCD - } - } - - if (_event_handlers[BLEDisconnected]) - _event_handlers[BLEDisconnected](_central); - - _state = BLE_PERIPH_STATE_READY; - _central.clearAddress(); - - _startAdvertising(); - } else if (BLE_CLIENT_GAP_EVENT_CONN_TIMEOUT == event) { - _state = BLE_PERIPH_STATE_READY; - - _startAdvertising(); } + status = BLEPeripheralRole::instance()->startAdvertising(_adv_data, + _adv_data_idx, + NULL, + 0); + return status; } -void -BLEPeripheral::handleGattsEvent(ble_client_gatts_event_t event, struct ble_gatts_evt_msg *event_data) +BleStatus +BLEPeripheral::stopAdvertising() { - if (BLE_CLIENT_GATTS_EVENT_WRITE == event) { - uint16_t handle = event_data->wr.attr_handle; - - for (int i = 0; i < _num_attributes; i++) { - BLEAttribute* attribute = _attributes[i]; - - if (attribute->type() != BLETypeCharacteristic) { - continue; - } - - BLECharacteristic* characteristic = (BLECharacteristic*)attribute; - - if (characteristic->valueHandle() == handle) { - characteristic->setValue(_central, event_data->wr.data, event_data->wr.len); - break; - } else if (characteristic->cccdHandle() == handle) { - uint16_t cccdValue = 0; - - memcpy(&cccdValue, event_data->wr.data, event_data->wr.len); - - characteristic->setCccdValue(_central, cccdValue); - break; - } - } - } + BleStatus status = BLE_STATUS_SUCCESS; + + status = BLEPeripheralRole::instance()->stopAdvertising(); + return status; } + diff --git a/libraries/CurieBLE/src/BLEPeripheral.h b/libraries/CurieBLE/src/BLEPeripheral.h index 054af330..67b6f07b 100644 --- a/libraries/CurieBLE/src/BLEPeripheral.h +++ b/libraries/CurieBLE/src/BLEPeripheral.h @@ -22,28 +22,17 @@ #include "internal/ble_client.h" -#include "BLEAttribute.h" -#include "BLECentral.h" -#include "BLECharacteristic.h" #include "BLECommon.h" - -/** - * BLE Peripheral Events - */ -enum BLEPeripheralEvent { - BLEConnected = 0, - BLEDisconnected = 1, - - BLEPeripheralEventLast = 2 -}; +#include "BLERoleBase.h" +#include "BLEPeripheralHelper.h" /** Function prototype for BLE Peripheral Device event callback */ -typedef void (*BLEPeripheralEventHandler)(BLECentral ¢ral); +typedef void (*BLEPeripheralEventHandler)(BLECentralHelper ¢ral); /** * BLE Peripheral */ -class BLEPeripheral { +class BLEPeripheral{ public: /** * Default Constructor for BLE Peripheral Device @@ -55,23 +44,6 @@ class BLEPeripheral { */ virtual ~BLEPeripheral(void); - /** - * Return the number of bytes in the advertising block. - * Useful for debugging advertising problems. - * - * @note Call only after calling begin(). - */ - uint8_t getAdvertisingLength(); - - /** - * Returns a pointer to the advertising block - * of length getAdvertisingLength(). - * Useful for debugging advertising problems. - * - * @note Call only after calling begin(). - */ - uint8_t* getAdvertising(); - /** * Set the service UUID that the BLE Peripheral Device advertises * @@ -80,7 +52,7 @@ class BLEPeripheral { * * @note This method must be called before the begin method */ - void setAdvertisedServiceUuid(const char* advertisedServiceUuid); + void setAdvertisedServiceUuid(const struct bt_uuid* advertisedServiceUuid); /** * Set the local name that the BLE Peripheral Device advertises @@ -113,8 +85,23 @@ class BLEPeripheral { * the service data will silently not be copied * into the advertising block. */ - void setAdvertisedServiceData(const char* serviceDataUuid, uint8_t* serviceData, uint8_t serviceDataLength); - + void setAdvertisedServiceData(const struct bt_uuid* serviceDataUuid, + uint8_t* serviceData, + uint8_t serviceDataLength); + /** + * Set the ADV parameters about the ADV-Type and interval + * + * @param type Advertising types + * + * @param interval_min Minimum Advertising Interval (N * 0.625) + * + * @param interval_max Maximum Advertising Interval (N * 0.625) + * + * @note none + */ + void setAdvertisingParam(uint8_t type, + uint16_t interval_min, + uint16_t interval_max); /** * Set the device name for the BLE Peripheral Device * @@ -168,7 +155,7 @@ class BLEPeripheral { * @param event Event type for callback * @param callback Pointer to callback function to invoke when an event occurs. */ - void setEventHandler(BLEPeripheralEvent event, BLEPeripheralEventHandler callback); + void setEventHandler(BLERoleEvent event, BLERoleEventHandler callback); /** * Setup attributes and start advertising @@ -199,7 +186,7 @@ class BLEPeripheral { * * @return BleStatus indicating success or error */ - BLECentral central(void); + BLECentralHelper central(void); /** * Is a central connected? @@ -207,52 +194,58 @@ class BLEPeripheral { * @return boolean_t true if central connected, otherwise false */ bool connected(void); - + + /** + * @brief Init the ADV data and start send advertisement + * + * @param none + * + * @return BleStatus 0 - Success. Others - error code + * + * @note none + */ + BleStatus startAdvertising(void); + + /** + * @brief Stop send advertisement + * + * @param none + * + * @return BleStatus 0 - Success. Others - error code + * + * @note none + */ + BleStatus stopAdvertising(void); + protected: - friend void blePeripheralGapEventHandler(ble_client_gap_event_t event, struct ble_gap_event *event_data, void *param); - friend void blePeripheralGattsEventHandler(ble_client_gatts_event_t event, struct ble_gatts_evt_msg *event_data, void *param); - - void handleGapEvent(ble_client_gap_event_t event, struct ble_gap_event *event_data); - void handleGattsEvent(ble_client_gatts_event_t event, struct ble_gatts_evt_msg *event_data); + void handleConnectEvent(struct bt_conn *conn, uint8_t err); + void handleDisconnectEvent(struct bt_conn *conn, uint8_t reason); + void handleParamUpdated(struct bt_conn *conn, + uint16_t interval, + uint16_t latency, + uint16_t timeout); private: - BleStatus _init(void); - BleStatus _startAdvertising(void); + BleStatus _stop(void); - void _advDataInit(void); + BleStatus _advDataInit(void); private: - - enum BLEPeripheralState { - BLE_PERIPH_STATE_NOT_READY = 0, - BLE_PERIPH_STATE_READY, - BLE_PERIPH_STATE_ADVERTISING, - BLE_PERIPH_STATE_CONNECTED, - }; - - BLEPeripheralState _state; - - const char* _advertise_service_uuid; const char* _local_name; - const char* _service_data_uuid; + + const struct bt_uuid* _service_data_uuid; uint8_t* _service_data; - uint8_t _service_data_length; - char _device_name[BLE_MAX_DEVICE_NAME+1]; + uint8_t _service_data_length; + uint8_t _service_data_buf[BLE_MAX_ADV_SIZE]; + uint16_t _appearance; - uint16_t _min_conn_interval; - uint16_t _max_conn_interval; - uint8_t _adv_data[BLE_MAX_ADV_SIZE]; - uint8_t _adv_data_len; - ble_addr_t _local_bda; - BLECentral _central; - - BLEPeripheralEventHandler _event_handlers[BLEPeripheralEventLast]; - - BLEAttribute** _attributes; - uint16_t _num_attributes; - - BLECharacteristic* _last_added_characteritic; + + const struct bt_uuid* _advertise_service_uuid; + + uint8_t _adv_type; + struct bt_data _adv_data[4]; + size_t _adv_data_idx; }; #endif // _BLE_DEVICE_H_INCLUDED diff --git a/libraries/CurieBLE/src/BLEPeripheralHelper.cpp b/libraries/CurieBLE/src/BLEPeripheralHelper.cpp new file mode 100644 index 00000000..65d54c3d --- /dev/null +++ b/libraries/CurieBLE/src/BLEPeripheralHelper.cpp @@ -0,0 +1,117 @@ +/* + * 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 "BLEPeripheralHelper.h" + +BLEAttribute *BLEPeripheralHelper::attribute(uint16_t handle) +{ + return _profile.attribute(handle); +} + +BLEAttribute *BLEPeripheralHelper::attribute(struct bt_gatt_subscribe_params *params) +{ + return _profile.attribute(params); +} + +void BLEPeripheralHelper::discover(const struct bt_gatt_attr *attr) +{ + // Not allow to call the discover + if (NULL == _central) + { + return; + } + _profile.discover(attr); +} + +void BLEPeripheralHelper::discover() +{ + if (NULL == _central) + { + return; + } + _profile.discover(); +} + +BLEPeripheralHelper::BLEPeripheralHelper(BLECentralRole* central): + _profile(this), + _central(central) +{ + ; +} +BLEPeripheralHelper::~BLEPeripheralHelper() +{ + +} + +bool BLEPeripheralHelper::disconnect(void) +{ + int err = 0; + struct bt_conn* conn = bt_conn_lookup_addr_le(this->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); +} + +bool BLEPeripheralHelper::connected(void) +{ + struct bt_conn* conn = bt_conn_lookup_addr_le(this->bt_le_address()); + if (NULL == conn) + { + return false; + } + bt_conn_unref(conn); + return true; +} + +void BLEPeripheralHelper::linkLost(void) +{ + clearAddress(); + if (NULL != _central) + { + // Only central role need to do + _profile.clearHandles(); + } +} + +void BLEPeripheralHelper::addAttribute(BLEAttribute& attribute) +{ + _profile.addAttribute(attribute); +} + +int BLEPeripheralHelper::registerProfile() +{ + return _profile.registerProfile(); +} + +uint16_t BLEPeripheralHelper::valueHandle(BLEAttribute *attr) +{ + return _profile.valueHandle(attr); +} + +uint16_t BLEPeripheralHelper::cccdHandle(BLEAttribute *attr) +{ + return _profile.cccdHandle(attr); +} + + diff --git a/libraries/CurieBLE/src/BLEPeripheralHelper.h b/libraries/CurieBLE/src/BLEPeripheralHelper.h new file mode 100644 index 00000000..2d29a210 --- /dev/null +++ b/libraries/CurieBLE/src/BLEPeripheralHelper.h @@ -0,0 +1,84 @@ +/* + * 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_PERIPHERAL_HELPER_H_ +#define _BLE_PERIPHERAL_HELPER_H_ + +#include "BLECommon.h" +#include "BLEHelper.h" +#include "BLEProfile.h" + +class BLEAttribute; +class BLECentralRole; + +class BLEPeripheralHelper : public BLEHelper { + friend class BLECentralRole; + friend class BLEPeripheralRole; + public: + /** + * Is the Central connected + * + * @return boolean_t true if the central is connected, otherwise false + */ + bool connected(void); + + /** + * Disconnect the central if it is connected + * + */ + bool disconnect(void); + + /** + * Add an attribute to the BLE Peripheral helper + * + * @param attribute Attribute to add to Peripheral + * + * @note This method must be called before the begin method + */ + void addAttribute(BLEAttribute& attribute); + + BLEAttribute *attribute(struct bt_gatt_subscribe_params *params); + BLEAttribute *attribute(uint16_t handle); + + /** + * For central to discover the profile + */ + void discover(); + void discover(const struct bt_gatt_attr *attr); + + // For peripheral to register the tree + int registerProfile(); + void linkLost(void); + + // Get value handle. + // 0 is invalid + uint16_t valueHandle(BLEAttribute *attr); + uint16_t cccdHandle(BLEAttribute *attr); + + protected: + BLEPeripheralHelper(BLECentralRole* central); + ~BLEPeripheralHelper(); + + private: + BLEProfile _profile; + BLECentralRole* _central; +}; + +#endif + diff --git a/libraries/CurieBLE/src/BLEPeripheralRole.cpp b/libraries/CurieBLE/src/BLEPeripheralRole.cpp new file mode 100644 index 00000000..dc13b566 --- /dev/null +++ b/libraries/CurieBLE/src/BLEPeripheralRole.cpp @@ -0,0 +1,278 @@ +/* + * 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 "BLEPeripheralRole.h" + +#include "BLECharacteristic.h" +#include "BLEDescriptor.h" +#include "BLEService.h" + +BLEPeripheralRole* BLEPeripheralRole::_ins = NULL; + +BLEPeripheralRole* BLEPeripheralRole::instance() +{ + if (NULL == _ins) + { + _ins = new BLEPeripheralRole(); + } + return _ins; +} + +BLEPeripheralRole::BLEPeripheralRole(void) : + _state(BLE_PERIPH_STATE_NOT_READY), + _min_conn_interval(DEFAULT_MIN_CONN_INTERVAL), + _max_conn_interval(DEFAULT_MAX_CONN_INTERVAL), + _peripheral(NULL), + _central(this) +{ + memset(_event_handlers, 0x00, sizeof(_event_handlers)); + _peripheral.setAddress(_local_bda); +} + +BLEPeripheralRole::~BLEPeripheralRole(void) +{ +} + +bool BLEPeripheralRole::begin() +{ + BleStatus status; + + if (BLE_PERIPH_STATE_NOT_READY != _state) + return BLE_STATUS_WRONG_STATE; + + status = _init(); + if (status != BLE_STATUS_SUCCESS) { + return false; + } + _state = BLE_PERIPH_STATE_READY; + + // Set device name + setDeviceName(); + // Register profile + _peripheral.registerProfile(); + delay(2); // Temp solution for send data fast will makes ADV data set failed + return true; +} + +void +BLEPeripheralRole::poll() +{ + // no-op for now + delay(1); +} + +void +BLEPeripheralRole::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 +BLEPeripheralRole::setDeviceName() +{ + int len = strlen(_device_name); + bt_le_set_device_name(_device_name, len); +} + +void +BLEPeripheralRole::setConnectionInterval(const unsigned short minConnInterval, const unsigned short maxConnInterval) +{ + _min_conn_interval = minConnInterval; + _max_conn_interval = maxConnInterval; + + if (_min_conn_interval < MIN_CONN_INTERVAL) { + _min_conn_interval = MIN_CONN_INTERVAL; + } else if (_min_conn_interval > MAX_CONN_INTERVAL) { + _min_conn_interval = MAX_CONN_INTERVAL; + } + + if (_max_conn_interval < _min_conn_interval) { + _max_conn_interval = _min_conn_interval; + } else if (_max_conn_interval > MAX_CONN_INTERVAL) { + _max_conn_interval = MAX_CONN_INTERVAL; + } +} + +void +BLEPeripheralRole::setEventHandler(BLERoleEvent event, BLERoleEventHandler callback) +{ + if (event < sizeof(_event_handlers)) { + _event_handlers[event] = callback; + } +} + +bool +BLEPeripheralRole::disconnect() +{ + BleStatus status = BLE_STATUS_WRONG_STATE; + + if (BLE_PERIPH_STATE_CONNECTED == _state) + { + struct bt_conn *central_conn = bt_conn_lookup_addr_le(_central.bt_le_address()); + if (NULL != central_conn) + { + status = bt_conn_disconnect (central_conn, + BT_HCI_ERR_REMOTE_USER_TERM_CONN); + bt_conn_unref(central_conn); + } + } + return (status == BLE_STATUS_SUCCESS); +} + +BLECentralHelper +BLEPeripheralRole::central() +{ + poll(); + + return _central; +} + +bool +BLEPeripheralRole::connected() +{ + poll(); + + return _central; +} + +void BLEPeripheralRole::addAttribute(BLEAttribute& attribute) +{ + _peripheral.addAttribute(attribute); +} + +BleStatus +BLEPeripheralRole::stopAdvertising() +{ + int err_code = 0; + BleStatus 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; +} + +BleStatus +BLEPeripheralRole::startAdvertising(const struct bt_data *ad, + size_t ad_len, + const struct bt_data *sd, + size_t sd_len) +{ + int ret; + + pr_info(LOG_MODULE_BLE, "%s-ad_len%d", __FUNCTION__, ad_len); + if (_state != BLE_PERIPH_STATE_READY) + return BLE_STATUS_WRONG_STATE; + + ret = bt_le_adv_start(&_adv_param, ad, ad_len, sd, sd_len); + if (0 != ret) + { + pr_error(LOG_MODULE_APP, "[ADV] Start failed. Error: %d", ret); + return BLE_STATUS_WRONG_STATE; + } + _state = BLE_PERIPH_STATE_ADVERTISING; + return BLE_STATUS_SUCCESS; +} + +void BLEPeripheralRole::setAdvertisingParam(uint8_t type, + uint16_t interval_min, + uint16_t interval_max) +{ + _adv_param.addr_type = _local_bda.type; + _adv_param.type = type; + _adv_param.interval_min = interval_min; + _adv_param.interval_max = interval_max; +} + +BleStatus +BLEPeripheralRole::stop(void) +{ + int err_code; + BleStatus status; + + if (BLE_PERIPH_STATE_ADVERTISING == _state) + { + err_code = bt_le_adv_stop(); + status = errorno_to_ble_status(err_code); + } + else + status = disconnect(); + + if (BLE_STATUS_SUCCESS != status) + return status; + + _state = BLE_PERIPH_STATE_READY; + return BLE_STATUS_SUCCESS; +} + +void BLEPeripheralRole::handleConnectEvent(struct bt_conn *conn, uint8_t err) +{ + // Update the central address + const bt_addr_le_t *central_addr = bt_conn_get_dst(conn); + _central.setAddress(*central_addr); + + pr_info(LOG_MODULE_BLE, "Connected: %d", err); + // Call the CB + if (_event_handlers[BLEConnected]) + _event_handlers[BLEConnected](_central); +} + + +void BLEPeripheralRole::handleDisconnectEvent(struct bt_conn *conn, uint8_t reason) +{ + struct bt_conn *central_conn = bt_conn_lookup_addr_le(_central.bt_le_address()); + if (conn == central_conn) + { + pr_info(LOG_MODULE_BLE, "Peripheral Disconnect reason: %d", reason); + if (_event_handlers[BLEDisconnected]) + _event_handlers[BLEDisconnected](_central); + } + _central.clearAddress(); + if (NULL != central_conn) + { + bt_conn_unref(central_conn); + } +} + +void BLEPeripheralRole::handleParamUpdated(struct bt_conn *conn, + uint16_t interval, + uint16_t latency, + uint16_t timeout) +{ + pr_info(LOG_MODULE_BLE, "Parameter updated\r\n\tConn: %p\r\n\tinterval: %d\r\n\tlatency: %d\r\n\ttimeout: %d", + conn, interval, latency, timeout); + if (_event_handlers[BLEUpdateParam]) + _event_handlers[BLEUpdateParam](_central); +} + + diff --git a/libraries/CurieBLE/src/BLEPeripheralRole.h b/libraries/CurieBLE/src/BLEPeripheralRole.h new file mode 100644 index 00000000..0fcbd40e --- /dev/null +++ b/libraries/CurieBLE/src/BLEPeripheralRole.h @@ -0,0 +1,262 @@ +/* + * 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_PERIPHERALROLE_H_INCLUDED +#define _BLE_PERIPHERALROLE_H_INCLUDED + +#include "internal/ble_client.h" + +#include "BLECommon.h" +#include "BLERoleBase.h" +#include "BLEPeripheralHelper.h" + +/** + * BLE Peripheral Role + */ +class BLEPeripheralRole: public BLERoleBase{ +public: + /** + * Default Constructor for BLE Peripheral Device + */ + BLEPeripheralRole(void); + + /** + * Destructor for BLE Peripheral Device + */ + virtual ~BLEPeripheralRole(void); + + /** + * Set the device name for the BLE Peripheral Device + * + * If device name is not set, a default name will be used instead + * + * @param deviceName 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 + */ + void setDeviceName(const char *deviceName); + + /** + * Set the min and max connection interval BLE Peripheral Device + * + * @param minConnInterval Minimum connection interval (1.25 ms units), minimum 0x0006 (7.5ms) + * @param maxConnInterval Maximum connection interval (1.25 ms units), maximum 0x095f (2998.75ms) + * + * @note This method must be called before the begin method + */ + void setConnectionInterval(const unsigned short minConnInterval, const unsigned short maxConnInterval); + + /** + * Add an attribute to the BLE Peripheral Device + * + * @param attribute Attribute to add to Peripheral + * + * @return BleStatus indicating success or error + * + * @note This method must be called before the begin method + */ + void addAttribute(BLEAttribute& attribute); + + /** + * Provide a function to be called when events related to this Device are raised + * + * @param event Event type for callback + * @param callback Pointer to callback function to invoke when an event occurs. + */ + void setEventHandler(BLERoleEvent event, BLERoleEventHandler callback); + + /** + * Setup attributes and start advertising + * + * @return bool indicating success or error + */ + bool begin(void); + + /** + * Poll the peripheral for events + */ + void poll(void); + + /** + * Stop advertising and disconnect a central if connected + */ + BleStatus stop(void); + + /** + * Disconnect the central connected if there is one connected + * + * @return bool indicating success or error + */ + bool disconnect(void); + + /** + * Setup attributes and start advertising + * + * @return BleStatus indicating success or error + */ + BLECentralHelper central(void); + + /** + * Is a central connected? + * + * @return boolean_t true if central connected, otherwise false + */ + bool connected(void); + + /** + * @brief Start peripheral advertising + * + * @param ad The ADV data array + * + * @param ad_len The ADV data array length + * + * @param sd The Scan response data array + * + * @param sd_len The Scan response data array length + * + * @return BleStatus + * + * @note none + */ + BleStatus startAdvertising(const struct bt_data *ad, + size_t ad_len, + const struct bt_data *sd, + size_t sd_len); + + /** + * @brief Stop send advertisement + * + * @param none + * + * @return none + * + * @note none + */ + BleStatus stopAdvertising(); + + /** + * @brief Set advertising parameter + * + * @param type Advertising type + * + * @param interval_min Minimum Advertising Interval (N * 0.625) + * + * @param interval_max Maximum Advertising Interval (N * 0.625) + * + * @return none + * + * @note none + */ + void setAdvertisingParam(uint8_t type, + uint16_t interval_min, + uint16_t interval_max); + + /** + * @brief Get BLE Peripheral instance. + * + * @param none + * + * @return BLEPeripheralRole* The BLE perpheral instance + * + * @note Singleton. Only have one object to communicate with + * stack and manage the device + */ + static BLEPeripheralRole* instance(); + +protected: + /** + * @brief Handle the connected event + * + * @param conn The object that established the connection + * + * @param err The code of the process + * + * @return none + * + * @note none + */ + void handleConnectEvent(struct bt_conn *conn, uint8_t err); + + /** + * @brief Handle the disconnected event + * + * @param conn The object that lost the connection + * + * @param reason The link lost reason + * + * @return none + * + * @note none + */ + void handleDisconnectEvent(struct bt_conn *conn, uint8_t reason); + + /** + * @brief Handle the conntion update request + * + * @param conn The connection object that need to process the update request + * + * @param interval The connection interval + * + * @param latency The connection latency + * + * @param timeout The connection timeout + * + * @return none + * + * @note none + */ + void handleParamUpdated(struct bt_conn *conn, + uint16_t interval, + uint16_t latency, + uint16_t timeout); + +private: + + /** + * Set the device name to Nordic BLE's profile + * + * @param none + * + * @note none + */ + void setDeviceName(); + + enum BLEPeripheralState { + BLE_PERIPH_STATE_NOT_READY = 0, + BLE_PERIPH_STATE_READY, + BLE_PERIPH_STATE_ADVERTISING, + BLE_PERIPH_STATE_CONNECTED, + }; + + BLEPeripheralState _state; + + uint16_t _min_conn_interval; + uint16_t _max_conn_interval; + + struct bt_le_adv_param _adv_param; + + BLEPeripheralHelper _peripheral; + BLECentralHelper _central; + + BLERoleEventHandler _event_handlers[BLERoleEventLast]; + static BLEPeripheralRole *_ins; +}; + +#endif // _BLE_DEVICE_H_INCLUDED diff --git a/libraries/CurieBLE/src/BLEProfile.cpp b/libraries/CurieBLE/src/BLEProfile.cpp new file mode 100644 index 00000000..186bf4fa --- /dev/null +++ b/libraries/CurieBLE/src/BLEProfile.cpp @@ -0,0 +1,535 @@ +/* + * 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 "BLEProfile.h" +#include "BLEPeripheral.h" +#include "BLECentralRole.h" +#include "BLEPeripheralRole.h" + +// Only for peripheral +ssize_t profile_read_process(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + void *buf, uint16_t len, + uint16_t offset) +{ + const unsigned char *pvalue; + BLEAttribute *bleattr = (BLEAttribute *)attr->user_data; + BLECharacteristic* blecharacteritic; + BLEAttributeType type = bleattr->type(); + if (BLETypeCharacteristic != type) + { + return 0; + } + blecharacteritic = (BLECharacteristic*)bleattr; + pvalue = blecharacteritic->value(); + return bt_gatt_attr_read(conn, attr, buf, len, offset, pvalue, + blecharacteritic->valueLength()); +} + +// Only for peripheral +ssize_t profile_write_process(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + const void *buf, uint16_t len, + uint16_t offset) +{ + pr_info(LOG_MODULE_BLE, "%s1", __FUNCTION__); + BLEAttribute *bleattr = (BLEAttribute *)attr->user_data; + BLECharacteristic* blecharacteritic; + BLEAttributeType type = bleattr->type(); + BLECentralHelper central = BLEPeripheralRole::instance()->central(); + if ((BLETypeCharacteristic != type) || 0 != offset) + { + return 0; + } + + blecharacteritic = (BLECharacteristic*)bleattr; + blecharacteritic->setValue(*((BLEHelper *)¢ral), (const uint8_t *) buf, len); + + return len; +} + + +// Only for central +uint8_t profile_notify_process (struct bt_conn *conn, + struct bt_gatt_subscribe_params *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 + BLECharacteristic *chrc = (BLECharacteristic *)notifyatt; + + //assert(notifyatt->type() == BLETypeCharacteristic); + pr_debug(LOG_MODULE_APP, "%s1", __FUNCTION__); + chrc->setValue(*((BLEHelper *)peripheral),(const unsigned char *)data, length); + return BT_GATT_ITER_CONTINUE; +} + +// Only for central +uint8_t profile_discover_process(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + BLEPeripheralHelper* peripheral = BLECentralRole::instance()->peripheral(conn);// Find peripheral by bt_conn + peripheral->discover(attr); + return BT_GATT_ITER_STOP; +} + +// Only for central +uint8_t profile_read_rsp_process(struct bt_conn *conn, int err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + if (NULL == data) + { + return BT_GATT_ITER_STOP; + } + BLEPeripheralHelper* peripheral = BLECentralRole::instance()->peripheral(conn);// Find peripheral by bt_conn + BLEAttribute* readatt = peripheral->attribute(params->single.handle); + BLECharacteristic *chrc = (BLECharacteristic *)readatt; + + //assert(readatt->type() == BLETypeCharacteristic); + chrc->setValue(*((BLEHelper *)peripheral), (const unsigned char *)data, length); + return BT_GATT_ITER_STOP; +} + +BLEProfile::BLEProfile (BLEPeripheralHelper *peripheral): + _attr_base(NULL), + _attr_index(0), + _attributes(NULL), + _num_attributes(0), + _sub_param(NULL), + _sub_param_idx(0) +{ + _peripheral = peripheral; + memset(&_discover_params, 0, sizeof(_discover_params)); + _discover_params.end_handle = 0xFFFF; + _discover_params.start_handle = 0x0001; + _discover_params.func = profile_discover_process; +} + +BLEProfile::~BLEProfile (void) +{ + if (this->_attributes) { + free(this->_attributes); + } + if (this->_attr_base) + { + free(this->_attr_base); + } + if (this->_sub_param) + { + free(this->_sub_param); + } +} + +void BLEProfile::addAttribute (BLEAttribute& attribute) +{ + struct bt_gatt_attr *start; + if (NULL == _attributes) + { + _attributes = (BLEAttribute**)malloc(BLEAttribute::numAttributes() * sizeof(BLEAttribute*)); + memset(_attributes, 0x00, BLEAttribute::numAttributes() * sizeof(BLEAttribute*)); + } + if (NULL == _attr_base) + { + _attr_base = (struct bt_gatt_attr *)malloc((BLEAttribute::numAttributes() + BLECharacteristic::numNotifyAttributes()) * sizeof(struct bt_gatt_attr)); + memset(_attr_base, 0x00, ((BLEAttribute::numAttributes() + BLECharacteristic::numNotifyAttributes()) * sizeof(struct bt_gatt_attr))); + pr_info(LOG_MODULE_BLE, "_attr_base_-%p, size-%d", _attr_base, sizeof(_attr_base)); + } + if (NULL == _sub_param) + { + _sub_param = (struct bt_gatt_subscribe_params *)malloc((BLECharacteristic::numNotifyAttributes()) * sizeof(struct bt_gatt_subscribe_params)); + memset(_sub_param, 0x00, ((BLECharacteristic::numNotifyAttributes()) * sizeof(struct bt_gatt_subscribe_params))); + } + + _attributes[_num_attributes] = &attribute; + _num_attributes++; + start = _attr_base + _attr_index; + pr_info(LOG_MODULE_BLE, "_attr_base_-%p", _attr_base); + + BLEAttributeType type = attribute.type(); + pr_info(LOG_MODULE_BLE, "%s: idx-%d, %p, %d", __FUNCTION__,_num_attributes, &attribute ,attribute.uuid()->type); + + + if (BLETypeCharacteristic == type) + { + BLECharacteristic* characteritic = (BLECharacteristic*) &attribute; + + // Characteristic + memset(start, 0, sizeof(struct bt_gatt_attr)); + start->uuid = BLECharacteristic::getCharacteristicAttributeUuid(); + start->perm = BT_GATT_PERM_READ; + start->read = bt_gatt_attr_read_chrc; + start->user_data = characteritic->getCharacteristicAttValue(); + characteritic->addCharacteristicDeclaration(start); + pr_info(LOG_MODULE_BLE, "chrc-%p, uuid type-%d", start, start->uuid->type); + start++; + _attr_index++; + + // Descriptor + memset(start, 0, sizeof(struct bt_gatt_attr)); + start->uuid = characteritic->uuid(); + start->perm = characteritic->getPermission(); + start->read = profile_read_process; + start->write = profile_write_process; + start->user_data = (void*)&attribute; + characteritic->addCharacteristicValue(start); + pr_info(LOG_MODULE_BLE, "desc-%p, uuid: 0x%x", start, ((struct bt_uuid_16*) start->uuid)->val); + + start++; + _attr_index++; + // CCCD + if (characteritic->subscribed()) + { + pr_info(LOG_MODULE_BLE, "cccd-%p", start); + // Descriptor + memset(start, 0, sizeof(struct bt_gatt_attr)); + start->uuid = characteritic->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 = characteritic->getCccCfg(); + characteritic->addCharacteristicConfigDescriptor(start); + + start++; + _attr_index++; + } + } + else if (BLETypeService == type) + { + pr_info(LOG_MODULE_BLE, "service-%p", start); + start->uuid = BLEService::getPrimayUuid(); + start->perm = BT_GATT_PERM_READ; + start->read = bt_gatt_attr_read_service; + start->user_data = attribute.uuid(); + start++; + _attr_index++; + } + +} + +int BLEProfile::registerProfile() +{ + int ret = 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 + + ret = bt_gatt_register(_attr_base, + _attr_index); + pr_info(LOG_MODULE_APP, "%s: ret, %d", __FUNCTION__, ret); + + return ret; +} + +void BLEProfile::discover(const struct bt_gatt_attr *attr) +{ + BLEAttribute* attribute = NULL; + int err; + int i; + bool send_discover = false; + + for (i = 0; i < _num_attributes; i++) + { + attribute = _attributes[i]; + if (attribute->discovering()) + { + if (NULL != attr) + { + // Discover success + switch (_discover_params.type) + { + case BT_GATT_DISCOVER_CHARACTERISTIC: + { + struct bt_gatt_attr *attr_dec = declarationAttr(attribute); + attr_dec++; + attr_dec->handle = attr->handle + 1; + break; + } + case BT_GATT_DISCOVER_DESCRIPTOR: + { + BLECharacteristic *chrc = (BLECharacteristic *)attribute; + struct bt_gatt_attr *attr_dec = declarationAttr(attribute); + struct bt_gatt_attr *attr_chrc = attr_dec + 1; + struct bt_gatt_attr *attr_cccd = attr_dec + 2; + struct bt_gatt_subscribe_params *sub_param_tmp = chrc->getSubscribeParams(); + struct bt_gatt_subscribe_params *sub_param = _sub_param + _sub_param_idx; + struct bt_conn *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(struct bt_gatt_subscribe_params)); + 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); + } + break; + } + case BT_GATT_DISCOVER_PRIMARY: + default: + { + // Do nothing + break; + } + } + } + attribute->discover(attr, &_discover_params); + break; + } + } + + // Send discover + if (attribute->discovering()) + { + send_discover = true; + } + else + { + // Current attribute complete discovery + // Find next attribute to discover + i++; + if (i < _num_attributes) + { + attribute = _attributes[i]; + attribute->discover(&_discover_params); + send_discover = true; + } + } + + if (send_discover) + { + struct bt_conn *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; + } + 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; + } + } +} + + +void BLEProfile::discover() +{ + int err; + BLEService *serviceattr = (BLEService *)_attributes[0]; + struct bt_conn *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; + } +} + +BLEAttribute *BLEProfile::attribute(struct bt_gatt_subscribe_params *params) +{ + return attribute(params->value_handle); +} + +BLEAttribute *BLEProfile::attribute(const struct bt_uuid* uuid) +{ + int i; + BLEAttribute *attr_tmp = NULL; + BLECharacteristic *chrc_tmp = NULL; + bool att_found = false; + + for (i = 0; i < _num_attributes; i++) + { + attr_tmp = _attributes[i]; + if ((NULL == attr_tmp) || (attr_tmp->type() != BLETypeCharacteristic)) + { + continue; + } + chrc_tmp = (BLECharacteristic *)attr_tmp; + if (chrc_tmp->uuid() == uuid); + { + att_found = true; + break; + } + } + + if (false == att_found) + { + pr_debug(LOG_MODULE_APP, "Attributes not found"); + // Didn't found the characteristic + chrc_tmp = NULL; + } + return chrc_tmp; +} + + +BLEAttribute *BLEProfile::attribute(uint16_t handle) +{ + int i; + struct bt_gatt_attr *attr_gatt = NULL; + for (i = 0; i < _attr_index; i++) + { + attr_gatt = _attr_base + i; + if (handle == attr_gatt->handle) + { + break; + } + } + + if (i < _attr_index && i > 1) + { + // Found the GATT ATTR + // Serach the attribute + // Characteristic Declaration + // Characteristic Descriptor + // CCCD + attr_gatt--; + if (attr_gatt->uuid == BLECharacteristic::getCharacteristicAttributeUuid()) + { + attr_gatt++; + } + else + { + attr_gatt--; + if (attr_gatt->uuid == BLECharacteristic::getCharacteristicAttributeUuid()) + { + attr_gatt++; + } + else + { + attr_gatt = NULL; + } + } + } + else + { + attr_gatt = NULL; + } + + if (NULL != attr_gatt) + { + return attribute(attr_gatt->uuid); + } + return NULL; +} + + +void BLEProfile::clearHandles(void) +{ + int i; + struct bt_gatt_attr *attr = NULL; + // Didn't need to unsubscribe + // The stack will unsubscribe the notify when disconnected. + // The sub_param has some pointer. So can't call memset. Just reset the index. + _sub_param_idx = 0; + + for (i = 0; i < _attr_index; i++) + { + // Clear the handle + attr = _attr_base + i; + attr->handle = 0; + } +} + +struct bt_gatt_attr* BLEProfile::declarationAttr(BLEAttribute *attr) +{ + int i; + struct bt_gatt_attr *attr_gatt = NULL; + + for (i = 0; i < _attr_index; i++) + { + // Clear the handle + attr_gatt = _attr_base + i; + if (attr->uuid() == attr_gatt->uuid) + { + attr_gatt--; + return attr_gatt; + } + } + return NULL; +} + +uint16_t BLEProfile::valueHandle(BLEAttribute *attr) +{ + uint16_t handle = 0; + struct bt_gatt_attr *attr_gatt = declarationAttr(attr); + attr_gatt++; + if (attr_gatt->uuid == attr->uuid()) + { + handle = attr_gatt->handle; + } + return handle; +} + +uint16_t BLEProfile::cccdHandle(BLEAttribute *attr) +{ + uint16_t handle = 0; + struct bt_gatt_attr *attr_gatt = declarationAttr(attr); + attr_gatt+= 2; + if (attr_gatt->uuid == BLECharacteristic::getClientCharacteristicConfigUuid()) + { + handle = attr_gatt->handle; + } + return handle; +} + + + diff --git a/libraries/CurieBLE/src/BLEProfile.h b/libraries/CurieBLE/src/BLEProfile.h new file mode 100644 index 00000000..33b06c75 --- /dev/null +++ b/libraries/CurieBLE/src/BLEProfile.h @@ -0,0 +1,189 @@ +/* + * 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_H__ +#define __BLE_PROFILE_H__ + +#include "BLECommon.h" +#include "BLEAttribute.h" +#include "BLECentralHelper.h" +#include "BLECharacteristic.h" +#include "BLEService.h" + +class BLEProfile{ +public: + BLEProfile(BLEPeripheralHelper *peripheral); + ~BLEProfile (void); + + /** + * @brief Add an attribute to the BLE Peripheral Device + * + * @param attribute Attribute to add to Peripheral + * + * @return BleStatus indicating success or error + * + * @note This method must be called before the begin method + */ + void addAttribute(BLEAttribute& attribute); + + /** + * @brief Register the profile to Nordic BLE stack + * + * @param none + * + * @return int std C errno + * + * @note none + */ + int registerProfile(); + + /** + * @brief Get BLEAttribute by subscribe parameter + * + * @param struct bt_gatt_subscribe_params * Subscribe parameter + * + * @return BLEAttribute * NULL - Not found + * Not NULL - The BLEAttribute object + * + * @note none + */ + BLEAttribute *attribute(struct bt_gatt_subscribe_params *params); + + /** + * @brief Get BLEAttribute by characteristic handle + * + * @param uint16_t The characteristic handle + * + * @return BLEAttribute * NULL - Not found + * Not NULL - The BLEAttribute object + * + * @note none + */ + BLEAttribute *attribute(uint16_t handle); + + /** + * @brief Process the discover response and + * discover the BLE peripheral profile + * + * @param const struct bt_gatt_attr * The gatt attribute response + * + * @return none + * + * @note This function only for the central device. + */ + void discover(const struct bt_gatt_attr *attr); + + /** + * @brief Discover the BLE peripheral profile + * + * @param none + * + * @return none + * + * @note This function only for the central device. + * + * @note The central deivce didn't know the connected BLE's profile. + * Need send discover request to search the attribute in the BLE peripheral + */ + void discover(); + + /** + * @brief Clear the handle in central mode + * + * @param none + * + * @return none + * + * @note The peripheral can't call this. + * Because the central need discover the handles. + * Peripheral device only get the handle when register the profile. + */ + void clearHandles(void); + + /** + * @brief Get the characteristic value handle + * + * @param none + * + * @return uint16_t The value handle + * 0 is invalid handle + * + * @note none + */ + uint16_t valueHandle(BLEAttribute *attr); + + /** + * @brief Get characteristic configuration descriptor value handle + * + * @param none + * + * @return uint16_t The value handle + * 0 is invalid handle + * + * @note none + */ + uint16_t cccdHandle(BLEAttribute *attr); +protected: + friend ssize_t profile_write_process(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + const void *buf, uint16_t len, + uint16_t offset); +private: + /** + * @brief Get BLEAttribute by UUID + * + * @param const struct bt_uuid* The UUID + * + * @return BLEAttribute * NULL - Not found + * Not NULL - The BLEAttribute object + * + * @note Use the pointer value instead the UUID value. + * Because the uuid pointer in bt_gatt_attr is got from BLEAttribute + * So set this as private. + */ + BLEAttribute *attribute(const struct bt_uuid* uuid); + + /** + * @brief Get bt_gatt_attr by BLEAttribute class + * + * @param BLEAttribute * The BLEAttribute object + * + * @return struct bt_gatt_attr* NULL - Not found + * Not NULL - The bt_gatt_attr in the stack + * + * @note none + */ + struct bt_gatt_attr* declarationAttr(BLEAttribute *attr); + +private: + BLEPeripheralHelper *_peripheral; + struct bt_gatt_attr *_attr_base; + int _attr_index; + + BLEAttribute** _attributes; + uint16_t _num_attributes; + + struct bt_gatt_subscribe_params *_sub_param; + int _sub_param_idx; + + struct bt_gatt_discover_params _discover_params; +}; + +#endif + diff --git a/libraries/CurieBLE/src/BLERoleBase.cpp b/libraries/CurieBLE/src/BLERoleBase.cpp new file mode 100644 index 00000000..6ab9def5 --- /dev/null +++ b/libraries/CurieBLE/src/BLERoleBase.cpp @@ -0,0 +1,86 @@ +/* + * 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 "internal/ble_client.h" + +#include "BLERoleBase.h" + +void bleConnectEventHandler(struct bt_conn *conn, + uint8_t err, + void *param) +{ + BLERoleBase* p = (BLERoleBase*)param; + + p->handleConnectEvent(conn, err); +} + + +void bleDisconnectEventHandler(struct bt_conn *conn, + uint8_t reason, + void *param) +{ + BLERoleBase* p = (BLERoleBase*)param; + + pr_info(LOG_MODULE_BLE, "Connect lost. Reason: %d", reason); + + p->handleDisconnectEvent(conn, reason); +} + +void bleParamUpdatedEventHandler(struct bt_conn *conn, + uint16_t interval, + uint16_t latency, + uint16_t timeout, + void *param) +{ + BLERoleBase* p = (BLERoleBase*)param; + + p->handleParamUpdated(conn, interval, latency, timeout); +} + +uint8_t BLERoleBase::m_init_cnt = 0; + +void BLERoleBase::setTxPower (int8_t tx_power) +{ + ble_gap_set_tx_power(tx_power); +} + + +BleStatus +BLERoleBase::_init() +{ + // Curie may support multi-role at same time in future. + // Make sure the BLE only init once. + if (this->m_init_cnt == 0) + { + ble_client_init(bleConnectEventHandler, this, + bleDisconnectEventHandler, this, + bleParamUpdatedEventHandler, this); + } + this->m_init_cnt++; + + return BLE_STATUS_SUCCESS; +} + +BLERoleBase::BLERoleBase(): m_connected(false) +{ + memset (_event_handlers, 0x00, sizeof (_event_handlers)); + ble_client_get_factory_config(&_local_bda, _device_name); +} + + diff --git a/libraries/CurieBLE/src/BLERoleBase.h b/libraries/CurieBLE/src/BLERoleBase.h new file mode 100644 index 00000000..4db80c48 --- /dev/null +++ b/libraries/CurieBLE/src/BLERoleBase.h @@ -0,0 +1,133 @@ +/* + * 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 __BLEROLEBASE_H__ +#define __BLEROLEBASE_H__ + +#include "BLECommon.h" + +/** + * BLE Events + */ +enum BLERoleEvent { + BLEConnected = 0, + BLEDisconnected = 1, + BLEUpdateParam, + + BLERoleEventLast +}; + +class BLEHelper; + +typedef void (*BLERoleEventHandler)(BLEHelper &role); + + +class BLERoleBase{ +public: + virtual bool begin()=0; + virtual bool disconnect()=0; + BLERoleBase(); + + /** + * Is connected? + * + * @return boolean_t true if established connection, otherwise false + */ + bool connected (void) {return m_connected;} + + /** + * Set TX output power + * + * @param tx_power The antenna TX power + * + * @return boolean_t true if established connection, otherwise false + */ + void setTxPower (int8_t tx_power); +protected: + virtual BleStatus _init(void); + + friend void bleConnectEventHandler(struct bt_conn *conn, + uint8_t err, + void *param); + friend void bleDisconnectEventHandler(struct bt_conn *conn, + uint8_t reason, + void *param); + friend void bleParamUpdatedEventHandler(struct bt_conn *conn, + uint16_t interval, + uint16_t latency, + uint16_t timeout, + void *param); + + /** + * @brief Handle the connected event + * + * @param conn The object that established the connection + * + * @param err The code of the process + * + * @return none + * + * @note virtual function. Just define the interface and the children need to implement + */ + virtual void handleConnectEvent(struct bt_conn *conn, uint8_t err) = 0; + + /** + * @brief Handle the disconnected event + * + * @param conn The object that lost the connection + * + * @param reason The link lost reason + * + * @return none + * + * @note virtual function. Just define the interface and the children need to implement + */ + virtual void handleDisconnectEvent(struct bt_conn *conn, uint8_t reason) = 0; + + /** + * @brief Handle the conntion update request + * + * @param conn The connection object that need to process the update request + * + * @param interval The connection interval + * + * @param latency The connection latency + * + * @param timeout The connection timeout + * + * @return none + * + * @note virtual function. Just define the interface and the children need to implement + */ + virtual void handleParamUpdated (struct bt_conn *conn, + uint16_t interval, + uint16_t latency, + uint16_t timeout) = 0; + + char _device_name[BLE_MAX_DEVICE_NAME+1]; + bt_addr_le_t _local_bda; + BLERoleEventHandler _event_handlers[BLERoleEventLast]; + +private: + bool m_connected; + static uint8_t m_init_cnt; // Reserved for support multi-role at same time +}; + +#endif + diff --git a/libraries/CurieBLE/src/BLEService.cpp b/libraries/CurieBLE/src/BLEService.cpp index f7569e3b..2e25f161 100644 --- a/libraries/CurieBLE/src/BLEService.cpp +++ b/libraries/CurieBLE/src/BLEService.cpp @@ -20,21 +20,33 @@ #include "internal/ble_client.h" #include "BLEService.h" +struct bt_uuid_16 BLEService::_gatt_primary_uuid = {BT_UUID_TYPE_16, BT_UUID_GATT_PRIMARY_VAL}; +struct bt_uuid *BLEService::getPrimayUuid(void) +{ + return (struct bt_uuid *)&_gatt_primary_uuid; +} BLEService::BLEService(const char* uuid) : BLEAttribute(uuid, BLETypeService) { } -bool -BLEService::add() { - bt_uuid uuid = btUuid(); - uint16_t handle = 0; - BleStatus status = ble_client_gatts_add_service(&uuid, BLE_GATT_SVC_PRIMARY, &handle); - if (BLE_STATUS_SUCCESS == status) { - setHandle(handle); - } +void BLEService::discover(struct bt_gatt_discover_params *params) +{ + params->type = BT_GATT_DISCOVER_PRIMARY; + + params->uuid = this->uuid(); + // Start discovering + _discoverying = true; +} - return (BLE_STATUS_SUCCESS == status); +void BLEService::discover(const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + params->start_handle = attr->handle + 1; + + // Complete the discover + _discoverying = false; } + diff --git a/libraries/CurieBLE/src/BLEService.h b/libraries/CurieBLE/src/BLEService.h index 17311f72..2d4c4999 100644 --- a/libraries/CurieBLE/src/BLEService.h +++ b/libraries/CurieBLE/src/BLEService.h @@ -22,6 +22,10 @@ #include "BLEAttribute.h" #include "BLECommon.h" +#include "BLEProfile.h" + +class BLEPeripheral; +class BLEProfile; /** * BLE GATT Service @@ -37,8 +41,14 @@ class BLEService : public BLEAttribute { protected: friend BLEPeripheral; - - bool add(void); + friend BLEProfile; + void discover(const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params); + void discover(struct bt_gatt_discover_params *params); + + static struct bt_uuid *getPrimayUuid(void); +private: + static bt_uuid_16 _gatt_primary_uuid; }; #endif // _BLE_SERVICE_H_INCLUDED diff --git a/libraries/CurieBLE/src/BLEUuid.cpp b/libraries/CurieBLE/src/BLEUuid.cpp deleted file mode 100644 index f0764383..00000000 --- a/libraries/CurieBLE/src/BLEUuid.cpp +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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 "BLEUuid.h" - -BLEUuid::BLEUuid(const char * str) -{ - char temp[] = {0, 0, 0}; - int strLength = strlen(str); - int length = 0; - - memset(&_uuid, 0x00, sizeof(_uuid)); - - for (int i = strLength - 1; i >= 0 && length < MAX_UUID_SIZE; i -= 2) { - if (str[i] == '-') { - i++; - continue; - } - - temp[0] = str[i - 1]; - temp[1] = str[i]; - - _uuid.uuid128[length] = strtoul(temp, NULL, 16); - - length++; - } - - if (length == 2) { - _uuid.type = BT_UUID16; - } else { - _uuid.type = BT_UUID128; - } -} - -bt_uuid BLEUuid::uuid() const -{ - return _uuid; -} diff --git a/libraries/CurieBLE/src/BLEUuid.h b/libraries/CurieBLE/src/BLEUuid.h deleted file mode 100644 index 39b8aff5..00000000 --- a/libraries/CurieBLE/src/BLEUuid.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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_UUID_H_INCLUDED -#define _BLE_UUID_H_INCLUDED - -#include "BLECommon.h" - -class BLEUuid -{ -public: - BLEUuid(const char * str); - - bt_uuid uuid(void) const; - -private: - struct bt_uuid _uuid; -}; - -#endif // _BLE_UUID_H_INCLUDED diff --git a/libraries/CurieBLE/src/CurieBLE.h b/libraries/CurieBLE/src/CurieBLE.h index fb51dd58..bc1be251 100644 --- a/libraries/CurieBLE/src/CurieBLE.h +++ b/libraries/CurieBLE/src/CurieBLE.h @@ -23,3 +23,5 @@ #include "BLEService.h" #include "BLEPeripheral.h" #include "BLETypedCharacteristics.h" + +#include "BLECentral.h" diff --git a/libraries/CurieBLE/src/internal/ble_client.c b/libraries/CurieBLE/src/internal/ble_client.c index a4f4cbc6..5731a75f 100644 --- a/libraries/CurieBLE/src/internal/ble_client.c +++ b/libraries/CurieBLE/src/internal/ble_client.c @@ -27,6 +27,8 @@ * 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" @@ -48,327 +50,71 @@ #include "ble_client.h" #include "platform.h" -/* Advertising parameters */ -#define BLE_GAP_ADV_TYPE_ADV_IND 0x00 /**< Connectable undirected. */ -#define BLE_GAP_ADV_FP_ANY 0x00 /**< Allow scan requests and connect requests from any device. */ -/** options see \ref BLE_ADV_OPTIONS */ -/* options: BLE_NO_ADV_OPT */ -#define APP_ULTRA_FAST_ADV_INTERVAL 32 -#define APP_ULTRA_FAST_ADV_TIMEOUT_IN_SECONDS 180 -/* options: BLE_SLOW_ADV */ -#define APP_DISC_ADV_INTERVAL 160 -#define APP_DISC_ADV_TIMEOUT_IN_SECONDS 180 -/* options: BLE_NON_DISC_ADV */ -#define APP_NON_DISC_ADV_FAST_INTERVAL 160 -#define APP_NON_DISC_ADV_FAST_TIMEOUT_IN_SECONDS 30 -/* options: BLE_SLOW_ADV | BLE_NON_DISC_ADV */ -#define APP_NON_DISC_ADV_SLOW_INTERVAL 2056 -#define APP_NON_DISC_ADV_SLOW_TIMEOUT_IN_SECONDS 0 +#include "infra/log.h" -struct cfw_msg_rsp_sync { - volatile unsigned response; - volatile ble_status_t status; - void *param; -}; - -#define TIMEOUT_TICKS_1SEC 32768 /* ~1 second in RTC timer ticks */ -#define TIMEOUT_TICKS_1MS 32 /* ~1 millisecond in RTC timer ticks */ -#define wait_for_condition(cond, status) \ -do { \ - unsigned timeout = get_uptime_32k() + TIMEOUT_TICKS_1SEC; \ - status = BLE_STATUS_SUCCESS; \ - while (!(cond)) { \ - if (get_uptime_32k() > timeout) { \ - status = BLE_STATUS_TIMEOUT; \ - break; \ - } \ - } \ -} while(0) - -static cfw_handle_t client_handle; -static svc_client_handle_t *service_handle; -static uint16_t conn_handle; -static bool connected; - -static ble_client_gap_event_cb_t ble_client_gap_event_cb; -static void *ble_client_gap_event_param; - -static ble_client_gatts_event_cb_t ble_client_gatts_event_cb; -static void *ble_client_gatts_event_param; - -volatile struct cfw_msg_rsp_sync sync; - - -static void handle_msg_id_cfw_svc_avail_evt(cfw_svc_available_evt_msg_t *evt, void *param) -{ - if (evt->service_id == BLE_CORE_SERVICE_ID) { - sync.status = BLE_STATUS_SUCCESS; - sync.response = 1; - } -} - -static void handle_msg_id_cfw_open_svc(cfw_open_conn_rsp_msg_t *rsp, void *param) -{ - service_handle = (svc_client_handle_t *)(rsp->client_handle); - - sync.status = BLE_STATUS_SUCCESS; - sync.response = 1; -} - -static void handle_msg_id_ble_gap_wr_conf_rsp(struct ble_rsp *rsp, void *param) -{ - sync.status = rsp->status; - sync.response = 1; -} - -static void handle_msg_id_ble_gap_rd_bda_rsp(ble_bda_rd_rsp_t *rsp, void *param) -{ - ble_addr_t *p_bda = (ble_addr_t *)sync.param; - - if (p_bda && BLE_STATUS_SUCCESS == rsp->status) - memcpy(p_bda, &rsp->bd, sizeof(*p_bda)); - - sync.status = rsp->status; - sync.response = 1; -} - -static void handle_msg_id_ble_gap_sm_config_rsp(struct ble_rsp *rsp, void *param) -{ - sync.status = rsp->status; - sync.response = 1; -} - -static void handle_msg_id_ble_gap_wr_adv_data_rsp(struct ble_rsp *rsp, void *param) -{ - sync.status = rsp->status; - sync.response = 1; -} - -static void handle_msg_id_ble_gap_enable_adv_rsp(struct ble_rsp *rsp, void *param) -{ - /* No waiting for this response, so nothing to do here */ -} - -static void handle_msg_id_ble_gap_disable_adv_rsp(struct ble_rsp *rsp, void *param) -{ - /* No waiting for this response, so nothing to do here */ -} - -static void handle_msg_id_gatts_add_service_rsp(struct ble_gatts_add_svc_rsp *rsp, void *param) -{ - uint16_t *p_svc_handle = (uint16_t *)sync.param; - - if (p_svc_handle && BLE_STATUS_SUCCESS == rsp->status) - *p_svc_handle = rsp->svc_handle; - - sync.status = rsp->status; - sync.response = 1; -} - -static void handle_msg_id_gatts_add_characteristic_rsp(struct ble_gatts_add_char_rsp *rsp, void *param) -{ - struct ble_gatts_char_handles *p_handles = (struct ble_gatts_char_handles *)sync.param; - - if (p_handles && BLE_STATUS_SUCCESS == rsp->status) - memcpy(p_handles, &rsp->char_h, sizeof(*p_handles)); - - sync.status = rsp->status; - sync.response = 1; -} - -static void handle_msg_id_gatts_add_desc_rsp(struct ble_gatts_add_desc_rsp *rsp, void *param) -{ - uint16_t *p_handle = (uint16_t *)sync.param; - - if (p_handle && BLE_STATUS_SUCCESS == rsp->status) - *p_handle = rsp->handle; - - sync.status = rsp->status; - sync.response = 1; -} - -static void handle_msg_id_ble_gatts_set_attribute_value_rsp(struct ble_gatts_set_attr_rsp_msg *rsp, void *param) -{ - sync.status = rsp->status; - sync.response = 1; -} - -static void handle_msg_id_ble_gap_connect_evt_msg(struct ble_gap_event *evt, void *param) -{ - conn_handle = evt->conn_handle; - connected = true; - - if (ble_client_gap_event_cb) - ble_client_gap_event_cb(BLE_CLIENT_GAP_EVENT_CONNECTED, evt, ble_client_gap_event_param); -} - -static void handle_msg_id_ble_gap_disconnect_evt_msg(struct ble_gap_event *evt, void *param) -{ - connected = false; - - if (ble_client_gap_event_cb) - ble_client_gap_event_cb(BLE_CLIENT_GAP_EVENT_DISCONNECTED, evt, ble_client_gap_event_param); -} +// APP callback +static ble_client_connect_event_cb_t ble_client_connect_event_cb = NULL; +static void *ble_client_connect_event_param; -static void handle_msg_id_ble_gap_timeout_evt_msg(struct ble_gap_event *evt, void *param) -{ - connected = false; +static ble_client_disconnect_event_cb_t ble_client_disconnect_event_cb = NULL; +static void *ble_client_disconnect_event_param; - if (!ble_client_gap_event_cb) - return; +static ble_client_update_param_event_cb_t ble_client_update_param_event_cb = NULL; +static void *ble_client_update_param_event_param; - switch (evt->timeout.reason) { - case BLE_SVC_GAP_TO_ADV: - ble_client_gap_event_cb(BLE_CLIENT_GAP_EVENT_ADV_TIMEOUT, evt, ble_client_gap_event_param); - break; - case BLE_SVC_GAP_TO_CONN: - ble_client_gap_event_cb(BLE_CLIENT_GAP_EVENT_CONN_TIMEOUT, evt, ble_client_gap_event_param); - break; - }; -} -static void handle_msg_id_ble_gap_rssi_evt_msg(struct ble_gap_event *evt, void *param) -{ - if (ble_client_gap_event_cb) - ble_client_gap_event_cb(BLE_CLIENT_GAP_EVENT_RSSI, evt, ble_client_gap_event_param); -} +#define NIBBLE_TO_CHAR(n) \ + ((n) >= 0xA ? ('A' + (n) - 0xA) : ('0' + (n))) -static void handle_msg_id_ble_gatts_write_evt_msg(struct ble_gatts_evt_msg *evt, void *param) -{ - if (ble_client_gatts_event_cb) - ble_client_gatts_event_cb(BLE_CLIENT_GATTS_EVENT_WRITE, evt, ble_client_gatts_event_param); -} +#define BYTE_TO_STR(s, byte) \ + do { \ + *s++ = NIBBLE_TO_CHAR(byte >> 4); \ + *s++ = NIBBLE_TO_CHAR(byte & 0xF); \ + }while(0) -static void handle_msg_id_ble_gatts_send_notif_ind_rsp(ble_gatts_rsp_t *rsp, void *param) -{ - sync.status = rsp->status; - sync.response = 1; -} -static void handle_msg_id_ble_gap_disconnect_rsp(struct ble_rsp *rsp, void *param) -{ - sync.status = rsp->status; - sync.response = 1; -} +#ifdef __cplusplus +extern "C" { +#endif -static void handle_msg_id_ble_gap_set_rssi_report_rsp(struct ble_rsp *rsp, void *param) +static void on_connected(struct bt_conn *conn, uint8_t err) { - sync.status = rsp->status; - sync.response = 1; + if (ble_client_connect_event_cb) + { + ble_client_connect_event_cb(conn, err, ble_client_connect_event_param); + } } -static void handle_msg_id_ble_gap_dtm_init_rsp(struct ble_generic_msg *rsp, void *param) +static void on_disconnected(struct bt_conn *conn, uint8_t reason) { - sync.status = rsp->status; - sync.response = 1; + if (ble_client_disconnect_event_cb) + { + ble_client_disconnect_event_cb(conn, reason, ble_client_disconnect_event_param); + } } -static void ble_core_client_handle_message(struct cfw_message *msg, void *param) +static void on_le_param_updated(struct bt_conn *conn, uint16_t interval, + uint16_t latency, uint16_t timeout) { - switch (CFW_MESSAGE_ID(msg)) { - - case MSG_ID_CFW_SVC_AVAIL_EVT: - handle_msg_id_cfw_svc_avail_evt((cfw_svc_available_evt_msg_t *)msg, param); - break; - - case MSG_ID_CFW_OPEN_SERVICE: - handle_msg_id_cfw_open_svc((cfw_open_conn_rsp_msg_t *)msg, param); - break; - - case MSG_ID_BLE_GAP_WR_CONF_RSP: - handle_msg_id_ble_gap_wr_conf_rsp((struct ble_rsp *)msg, param); - break; - - case MSG_ID_BLE_GAP_RD_BDA_RSP: - handle_msg_id_ble_gap_rd_bda_rsp((ble_bda_rd_rsp_t *)msg, param); - break; - - case MSG_ID_BLE_GAP_SM_CONFIG_RSP: - handle_msg_id_ble_gap_sm_config_rsp((struct ble_rsp *)msg, param); - break; - - case MSG_ID_BLE_GAP_WR_ADV_DATA_RSP: - handle_msg_id_ble_gap_wr_adv_data_rsp((struct ble_rsp *)msg, param); - break; - - case MSG_ID_BLE_GAP_ENABLE_ADV_RSP: - handle_msg_id_ble_gap_enable_adv_rsp((struct ble_rsp *)msg, param); - break; - - case MSG_ID_BLE_GAP_DISABLE_ADV_RSP: - handle_msg_id_ble_gap_disable_adv_rsp((struct ble_rsp *)msg, param); - break; - - case MSG_ID_BLE_GATTS_ADD_SERVICE_RSP: - handle_msg_id_gatts_add_service_rsp((struct ble_gatts_add_svc_rsp *)msg, param); - break; - - case MSG_ID_BLE_GATTS_ADD_CHARACTERISTIC_RSP: - handle_msg_id_gatts_add_characteristic_rsp((struct ble_gatts_add_char_rsp *)msg, param); - break; - - case MSG_ID_BLE_GATTS_ADD_DESCRIPTOR_RSP: - handle_msg_id_gatts_add_desc_rsp((struct ble_gatts_add_desc_rsp *)msg, param); - break; - - case MSG_ID_BLE_GATTS_SET_ATTRIBUTE_VALUE_RSP: - handle_msg_id_ble_gatts_set_attribute_value_rsp((struct ble_gatts_set_attr_rsp_msg *)msg, param); - break; - - case MSG_ID_BLE_GATTS_SEND_NOTIF_RSP: - case MSG_ID_BLE_GATTS_SEND_IND_RSP: - handle_msg_id_ble_gatts_send_notif_ind_rsp((ble_gatts_rsp_t *)msg, param); - break; - - case MSG_ID_BLE_GAP_CONNECT_EVT: - handle_msg_id_ble_gap_connect_evt_msg((struct ble_gap_event *)msg, param); - break; - - case MSG_ID_BLE_GAP_DISCONNECT_EVT: - handle_msg_id_ble_gap_disconnect_evt_msg((struct ble_gap_event *)msg, param); - break; - - case MSG_ID_BLE_GAP_TO_EVT: - handle_msg_id_ble_gap_timeout_evt_msg((struct ble_gap_event *)msg, param); - break; - - case MSG_ID_BLE_GAP_RSSI_EVT: - handle_msg_id_ble_gap_rssi_evt_msg((struct ble_gap_event *)msg, param); - break; - - case MSG_ID_BLE_GATTS_WRITE_EVT: - handle_msg_id_ble_gatts_write_evt_msg((struct ble_gatts_evt_msg *)msg, param); - break; - - case MSG_ID_BLE_GAP_DISCONNECT_RSP: - handle_msg_id_ble_gap_disconnect_rsp((struct ble_rsp *)msg, param); - break; - - case MSG_ID_BLE_GAP_SET_RSSI_REPORT_RSP: - handle_msg_id_ble_gap_set_rssi_report_rsp((struct ble_rsp *)msg, param); - break; - - case MSG_ID_BLE_GAP_DTM_INIT_RSP: - handle_msg_id_ble_gap_dtm_init_rsp((struct ble_generic_msg *)msg, param); - break; + if (ble_client_update_param_event_cb) + { + ble_client_update_param_event_cb (conn, + interval, + latency, + timeout, + ble_client_update_param_event_param); } - cfw_msg_free(msg); } -#ifdef __cplusplus -extern "C" { -#endif +static struct bt_conn_cb conn_callbacks = { + .connected = on_connected, + .disconnected = on_disconnected, + .le_param_updated = on_le_param_updated +}; -#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) -void ble_client_get_factory_config(ble_addr_t *bda, char *name) +void ble_client_get_factory_config(bt_addr_le_t *bda, char *name) { struct curie_oem_data *p_oem = NULL; unsigned i; @@ -382,7 +128,7 @@ void ble_client_get_factory_config(ble_addr_t *bda, char *name) 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->addr[i] = p_oem->bt_address[BLE_ADDR_LEN - 1 - i]; + bda->val[i] = p_oem->bt_address[BLE_ADDR_LEN - 1 - i]; } } } @@ -419,9 +165,9 @@ void ble_client_get_factory_config(ble_addr_t *bda, char *name) if (bda && bda->type != BLE_DEVICE_ADDR_INVALID) { *suffix++ = '-'; - 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 + 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 @@ -433,453 +179,57 @@ void ble_client_get_factory_config(ble_addr_t *bda, char *name) } } -BleStatus ble_client_init(ble_client_gap_event_cb_t gap_event_cb, void *gap_event_param, - ble_client_gatts_event_cb_t gatts_event_cb, void *gatts_event_param) -{ - BleStatus status; - uint32_t delay_until; - - cfw_platform_nordic_init(); - - client_handle = cfw_init(cfw_get_service_queue(), - ble_core_client_handle_message, - NULL); - - sync.response = 0; - if (cfw_register_svc_available(client_handle, - BLE_CORE_SERVICE_ID, - NULL)) - return BLE_STATUS_ERROR; - - /* Wait for response messages */ - wait_for_condition(sync.response, status); - if (status != BLE_STATUS_SUCCESS) - return status; - - /* We need to wait for ~1 ms before continuing */ - delay_until = get_uptime_32k() + TIMEOUT_TICKS_1MS; - while (get_uptime_32k() < delay_until); - - sync.response = 0; - cfw_open_service(client_handle, - BLE_CORE_SERVICE_ID, - NULL); - - /* Wait for response messages */ - wait_for_condition(sync.response, status); - if (status != BLE_STATUS_SUCCESS) - return status; - - ble_client_gap_event_cb = gap_event_cb; - ble_client_gap_event_param = gap_event_param; - - ble_client_gatts_event_cb = gatts_event_cb; - ble_client_gatts_event_param = gatts_event_param; - - return sync.status; -} - -BleStatus ble_client_gap_set_enable_config(const char *name, - const ble_addr_t *bda, - const uint16_t appearance, - const int8_t tx_power, - const uint16_t min_conn_interval, - const uint16_t max_conn_interval) -{ - struct ble_wr_config config; - BleStatus status; - - config.p_bda = (bda && bda->type != BLE_DEVICE_ADDR_INVALID) ? (ble_addr_t *)bda : NULL; - config.p_name = (uint8_t *)name; - config.appearance = appearance; - config.tx_power = tx_power; - config.peripheral_conn_params.interval_min = min_conn_interval; - config.peripheral_conn_params.interval_max = max_conn_interval; - config.peripheral_conn_params.slave_latency = SLAVE_LATENCY; - config.peripheral_conn_params.link_sup_to = CONN_SUP_TIMEOUT; - config.central_conn_params.interval_min = min_conn_interval; - config.central_conn_params.interval_max = max_conn_interval; - config.central_conn_params.slave_latency = SLAVE_LATENCY; - config.central_conn_params.link_sup_to = CONN_SUP_TIMEOUT; - - sync.response = 0; - if (ble_gap_set_enable_config(service_handle, &config, NULL)) - return BLE_STATUS_ERROR; - /* Wait for response message */ - wait_for_condition(sync.response, status); - if (status != BLE_STATUS_SUCCESS) - return status; - if (sync.status) - return sync.status; - - struct ble_gap_sm_config_params sm_params = { - .options = BLE_GAP_BONDING, - .io_caps = BLE_GAP_IO_NO_INPUT_NO_OUTPUT, - .key_size = 16, - }; - sync.response = 0; - if (ble_gap_sm_config(service_handle, &sm_params, NULL)) - return BLE_STATUS_ERROR; - /* Wait for response message */ - wait_for_condition(sync.response, status); - if (status != BLE_STATUS_SUCCESS) - return status; - - return sync.status; -} - -BleStatus ble_client_gap_get_bda(ble_addr_t *p_bda) -{ - BleStatus status; - - sync.response = 0; - sync.param = (void *)p_bda; - if (ble_gap_read_bda(service_handle, NULL)) - return BLE_STATUS_ERROR; - /* Wait for response message */ - wait_for_condition(sync.response, status); - if (status != BLE_STATUS_SUCCESS) - return status; - - return sync.status; -} - -BleStatus ble_client_gap_wr_adv_data(uint8_t *adv_data, const uint8_t adv_data_len) -{ - BleStatus status; - - struct ble_gap_adv_rsp_data adv_rsp_data = { - .p_data = adv_data, - .len = adv_data_len, - }; - - /* write advertisement data */ - sync.response = 0; - if (ble_gap_wr_adv_data(service_handle, &adv_rsp_data, NULL, NULL)) - return BLE_STATUS_ERROR; - - /* Wait for response messages */ - wait_for_condition(sync.response, status); - if (status != BLE_STATUS_SUCCESS) - return status; - - return sync.status; -} - -BleStatus ble_client_gap_start_advertise(uint16_t timeout) -{ - /* Hard-coding these advertising parameters for now - * Could be changed to support advanced features such as: - * - slow advertising - * - directed advertising - * - whitelist filtering - * - etc. - */ - ble_gap_adv_param_t adv_params = { - .timeout = timeout, - .interval_min = APP_ULTRA_FAST_ADV_INTERVAL, - .interval_max = APP_ULTRA_FAST_ADV_INTERVAL, - .type = BLE_GAP_ADV_TYPE_ADV_IND, - .filter_policy = BLE_GAP_ADV_FP_ANY, - .p_peer_bda = NULL, - .options = BLE_GAP_OPT_ADV_DEFAULT, - }; - - /* For this message, we don't wait for the response, just fire - * and forget. This allows us to invoke it within the - * disconnect event handler to restart the connection - */ - return ble_gap_start_advertise(service_handle, &adv_params, NULL); -} - -BleStatus ble_client_gap_stop_advertise(void) -{ - /* For this message, we don't wait for the response, just fire - * and forget. - */ - return ble_gap_stop_advertise(service_handle, NULL); -} - -BleStatus ble_client_gatts_add_service(const struct bt_uuid *uuid, - const uint8_t type, - uint16_t *svc_handle) -{ - BleStatus status; - - sync.response = 0; - sync.param = (void *)svc_handle; - if (ble_gatts_add_service(service_handle, uuid, type, NULL, NULL)) - return BLE_STATUS_ERROR; - - /* Wait for response messages */ - wait_for_condition(sync.response, status); - if (status != BLE_STATUS_SUCCESS) - return status; - - return sync.status; -} - -BleStatus ble_client_gatts_include_service(const uint16_t primary_svc_handle, - const uint16_t included_svc_handle) -{ - BleStatus status; - - sync.response = 0; - if (ble_gatts_add_included_svc(service_handle, - primary_svc_handle, - included_svc_handle, - NULL)) - return BLE_STATUS_ERROR; - - /* Wait for response messages */ - wait_for_condition(sync.response, status); - if (status != BLE_STATUS_SUCCESS) - return status; - - return sync.status; -} - -BleStatus ble_client_gatts_add_characteristic(const uint16_t svc_handle, - struct ble_gatts_characteristic *char_data, - struct ble_gatts_char_handles *handles) -{ - BleStatus status; - - sync.response = 0; - sync.param = (void *)handles; - - if (ble_gatts_add_characteristic(service_handle, svc_handle, char_data, - NULL, NULL)) - return BLE_STATUS_ERROR; - - /* Wait for response messages */ - wait_for_condition(sync.response, status); - if (status != BLE_STATUS_SUCCESS) - return status; - - return sync.status; -} - -BleStatus ble_client_gatts_add_descriptor(const uint16_t svc_handle, - struct ble_gatts_descriptor *desc, - uint16_t *handle) -{ - BleStatus status; - - sync.response = 0; - sync.param = (void *)handle; - - if (ble_gatts_add_descriptor(service_handle, desc, NULL, NULL)) - return BLE_STATUS_ERROR; - - /* Wait for response messages */ - wait_for_condition(sync.response, status); - if (status != BLE_STATUS_SUCCESS) - return status; - - return sync.status; -} - -BleStatus ble_client_gatts_set_attribute_value(const uint16_t value_handle, - const uint16_t len, const uint8_t * p_value, - const uint16_t offset) -{ - BleStatus status; - - sync.response = 0; - if (ble_gatts_set_attribute_value(service_handle, value_handle, - len, p_value, offset, NULL)) - return BLE_STATUS_ERROR; - - /* Wait for response messages */ - wait_for_condition(sync.response, status); - if (status != BLE_STATUS_SUCCESS) - return status; - - return sync.status; -} - -BleStatus ble_client_gatts_send_notif_ind(const uint16_t value_handle, - const uint16_t len, uint8_t * p_value, - const uint16_t offset, - const bool indication) -{ - BleStatus status; - - ble_gatts_ind_params_t ind_params = { - .val_handle = value_handle, - .len = len, - .p_data = p_value, - .offset = offset, - }; - - sync.response = 0; - if (indication) - status = ble_gatts_send_ind(service_handle, conn_handle, &ind_params, NULL, NULL); - else - status = ble_gatts_send_notif(service_handle, conn_handle, &ind_params, NULL, NULL); - - if (status) - return BLE_STATUS_ERROR; - - /* Wait for response messages */ - wait_for_condition(sync.response, status); - if (status != BLE_STATUS_SUCCESS) - return status; - - return sync.status; -} - -BleStatus ble_client_gap_disconnect(const uint8_t reason) -{ - BleStatus status; - - if (!connected) - return BLE_STATUS_WRONG_STATE; - - sync.response = 0; - if (ble_gap_disconnect(service_handle, conn_handle, reason, NULL)) - return BLE_STATUS_ERROR; - - /* Wait for response messages */ - wait_for_condition(sync.response, status); - if (status != BLE_STATUS_SUCCESS) - return status; - - return sync.status; -} - -BleStatus ble_client_gap_set_rssi_report(boolean_t enable) -{ - BleStatus status; - struct rssi_report_params params; - - if (!connected) - return BLE_STATUS_WRONG_STATE; - - params.conn_hdl = conn_handle; - params.op = enable ? BLE_GAP_RSSI_ENABLE_REPORT : BLE_GAP_RSSI_DISABLE_REPORT; - /* TODO - pick sensible defaults for these and/or allow user to specify */ - params.delta_dBm = 5; - params.min_count = 3; - - sync.response = 0; - if (ble_gap_set_rssi_report(service_handle, ¶ms, NULL)) - return BLE_STATUS_ERROR; - - /* Wait for response messages */ - wait_for_condition(sync.response, status); - if (status != BLE_STATUS_SUCCESS) - return status; - - return sync.status; -} - -BleStatus ble_client_dtm_init(void) -{ - BleStatus status; - - /* Ensure that the ble_client_init() has been called already */ - if (!service_handle) - return BLE_STATUS_WRONG_STATE; - - /* Instruct the Nordic to enter Direct Test Mode */ - sync.response = 0; - ble_gap_dtm_init_req(service_handle, NULL); - - /* Wait for response messages */ - wait_for_condition(sync.response, status); - if (status != BLE_STATUS_SUCCESS) - return status; - - /* DTM is active. Detach UART IPC driver to allow direct access */ - if (BLE_STATUS_SUCCESS == sync.status) - uart_ipc_disable(IPC_UART); - - return sync.status; -} - -static int uart_raw_ble_core_tx_rx(uint8_t * send_data, uint8_t send_no, - uint8_t * rcv_data, uint8_t rcv_no) -{ - int i; - uint8_t rx_byte; - int res; - /* send command */ - for (i = 0; i < send_no; i++) - uart_poll_out(IPC_UART, send_data[i]); - /* answer */ - i = 0; - do { - res = uart_poll_in(IPC_UART, &rx_byte); - if (res == 0) { - rcv_data[i++] = rx_byte; - } - } while (i < rcv_no); - return i; -} - -BleStatus ble_client_dtm_cmd(const struct ble_test_cmd *test_cmd, - struct ble_dtm_test_result *test_result) -{ - BleStatus status; - - uint8_t send_data[7]; - uint8_t rcv_data[9] = {}; - int send_no; - int rcv_no; - - send_data[0] = DTM_HCI_CMD; - send_data[1] = test_cmd->mode; - send_data[2] = DTM_HCI_OPCODE2; - - switch (test_cmd->mode) { - case BLE_TEST_START_DTM_RX: - send_data[3] = 1; /* length */ - send_data[4] = test_cmd->rx.freq; - send_no = 5; - rcv_no = 7; +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; +} + +BleStatus errorno_to_ble_status(int err) +{ + BleStatus 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 BLE_TEST_START_DTM_TX: - send_data[3] = 3; /* length */ - send_data[4] = test_cmd->tx.freq; - send_data[5] = test_cmd->tx.len; - send_data[6] = test_cmd->tx.pattern; - send_no = 7; - rcv_no = 7; + case EBUSY: + err_code = BLE_STATUS_TIMEOUT; break; - case BLE_TEST_SET_TXPOWER: - send_data[3] = 1; /* length */ - send_data[4] = test_cmd->tx_pwr.dbm; - send_no = 5; - rcv_no = 7; + case EFBIG: + case ENOTSUP: + err_code = BLE_STATUS_NOT_SUPPORTED; break; - case BLE_TEST_END_DTM: - send_data[3] = 0; /* length */ - send_no = 4; - rcv_no = 9; + case EPERM: + case EACCES: + err_code = BLE_STATUS_NOT_ALLOWED; break; + case ENOMEM: // No memeory default: - return BLE_STATUS_NOT_SUPPORTED; - } - - uart_raw_ble_core_tx_rx(send_data, send_no, rcv_data, rcv_no); - - status = rcv_data[DTM_HCI_STATUS_IDX]; - - test_result->mode = test_cmd->mode; - - uint8_t *p; - switch (test_cmd->mode) { - case BLE_TEST_END_DTM: - p = &rcv_data[DTM_HCI_LE_END_IDX]; - LESTREAM_TO_UINT16(p, test_result->nb); + err_code = BLE_STATUS_ERROR; break; } - - return status; + return err_code; } + #ifdef __cplusplus } #endif diff --git a/libraries/CurieBLE/src/internal/ble_client.h b/libraries/CurieBLE/src/internal/ble_client.h index 3fe47bd4..b7495867 100644 --- a/libraries/CurieBLE/src/internal/ble_client.h +++ b/libraries/CurieBLE/src/internal/ble_client.h @@ -87,63 +87,26 @@ enum { uuid.type = BT_UUID128; \ } while(0) -typedef enum { - BLE_CLIENT_GAP_EVENT_CONNECTED = 0, - BLE_CLIENT_GAP_EVENT_DISCONNECTED, - BLE_CLIENT_GAP_EVENT_ADV_TIMEOUT, - BLE_CLIENT_GAP_EVENT_CONN_TIMEOUT, - BLE_CLIENT_GAP_EVENT_RSSI, -} ble_client_gap_event_t; -typedef enum { - BLE_CLIENT_GATTS_EVENT_WRITE = 0, -} ble_client_gatts_event_t; +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); -typedef void (*ble_client_gap_event_cb_t)(ble_client_gap_event_t event, struct ble_gap_event *event_data, void *param); -typedef void (*ble_client_gatts_event_cb_t)(ble_client_gatts_event_t event, struct ble_gatts_evt_msg *event_data, void *param); #ifdef __cplusplus extern "C" { #endif -void ble_client_get_factory_config(ble_addr_t *bda, char *name); -BleStatus ble_client_init(ble_client_gap_event_cb_t gap_event_cb, - void *gap_event_param, - ble_client_gatts_event_cb_t gatts_event_cb, - void *gatts_event_param); -BleStatus ble_client_gap_set_enable_config(const char *name, - const ble_addr_t *bda, - const uint16_t appearance, - const int8_t tx_power, - const uint16_t min_conn_interval, - const uint16_t max_conn_interval); -BleStatus ble_client_gap_get_bda(ble_addr_t *p_bda); -BleStatus ble_client_gap_wr_adv_data(uint8_t *adv_data, - const uint8_t adv_data_len); -BleStatus ble_client_gap_start_advertise(uint16_t timeout); -BleStatus ble_client_gap_stop_advertise(void); -BleStatus ble_client_gatts_add_service(const struct bt_uuid *uuid, const uint8_t type, uint16_t *svc_handle); -BleStatus ble_client_gatts_include_service(const uint16_t primary_svc_handle, uint16_t included_svc_handle); -BleStatus ble_client_gatts_add_characteristic(const uint16_t svc_handle, - struct ble_gatts_characteristic *char_data, - struct ble_gatts_char_handles *handles); -BleStatus ble_client_gatts_add_descriptor(const uint16_t svc_handle, - struct ble_gatts_descriptor *desc, - uint16_t *handle); -BleStatus ble_client_gatts_set_attribute_value(const uint16_t value_handle, - const uint16_t len, const uint8_t *value, - const uint16_t offset); -BleStatus ble_client_gatts_send_notif_ind(const uint16_t value_handle, - const uint16_t len, uint8_t * p_value, - const uint16_t offset, - const bool indication); -BleStatus ble_client_gap_disconnect(const uint8_t reason); -BleStatus ble_client_gap_set_rssi_report(boolean_t enable); - -/* Direct Test Mode (DTM) API - for internal use only */ -BleStatus ble_client_dtm_init(void); -BleStatus ble_client_dtm_cmd(const struct ble_test_cmd *test_cmd, - struct ble_dtm_test_result *test_result); +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); +BleStatus errorno_to_ble_status(int err); #ifdef __cplusplus } diff --git a/platform.txt b/platform.txt index e5493cb2..ecac9280 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__ +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.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 +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.ar.cmd=arc-elf32-ar compiler.ar.flags=rcs compiler.objcopy.cmd=arc-elf32-objcopy diff --git a/system/libarc32_arduino101/Makefile b/system/libarc32_arduino101/Makefile index 9602e3ad..2e947bc9 100644 --- a/system/libarc32_arduino101/Makefile +++ b/system/libarc32_arduino101/Makefile @@ -3,10 +3,12 @@ ASM_SRC+=$(wildcard $(PWD)/drivers/*.S) ASM_SRC+=$(wildcard $(PWD)/common/*.S) C_SRC+=$(wildcard $(PWD)/bootcode/*.c) C_SRC+=$(wildcard $(PWD)/drivers/*.c) +C_SRC+=$(wildcard $(PWD)/drivers/rpc/*.c) C_SRC+=$(wildcard $(PWD)/common/*.c) C_SRC+=$(wildcard $(PWD)/framework/src/*.c) C_SRC+=$(wildcard $(PWD)/framework/src/services/*.c) C_SRC+=$(wildcard $(PWD)/framework/src/services/ble/*.c) +C_SRC+=$(wildcard $(PWD)/framework/src/services/ble_service/*.c) C_SRC+=$(wildcard $(PWD)/framework/src/cfw/*.c) C_SRC+=$(wildcard $(PWD)/framework/src/infra/*.c) C_SRC+=$(wildcard $(PWD)/framework/src/util/*.c) @@ -25,8 +27,21 @@ HWFLAGS=-mARCv2EM -mav2em -mlittle-endian CFGFLAGS=-DCONFIG_SOC_GPIO_32 -DCONFIG_SOC_GPIO_AON -DINFRA_MULTI_CPU_SUPPORT -DCFW_MULTI_CPU_SUPPORT -DCONFIG_HAS_SHARED_MEM -DCONFIG_INFRA_IS_MASTER OPTFLAGS=-g -Os -Wall -Werror +CFGFLAGS+=-DCONFIG_SOC_QUARK_SE +#CFGFLAGS+=-DTRACK_ALLOCS +#CFGFLAGS+=-DIPC_UART_DBG_RX +#CFGFLAGS+=-DIPC_UART_DBG_TX +CFGFLAGS+=-DBT_GATT_DEBUG +CFGFLAGS+=-DCONFIG_RPC_IN +CFGFLAGS+=-DCONFIG_IPC_UART_NS16550 CFGFLAGS+=-DCONFIG_IPC_UART_BAUDRATE=1000000 -INCLUDES=-I. -Icommon -Idrivers -Ibootcode -Iframework/include -Iframework/src/services/ble +CFGFLAGS+=-DCONFIG_BLUETOOTH_MAX_CONN=2 +CFGFLAGS+=-DCONFIG_BT_GATT_BLE_MAX_SERVICES=10 +CFGFLAGS+=-DCONFIG_BLUETOOTH_GATT_CLIENT +CFGFLAGS+=-DCONFIG_BLUETOOTH_CENTRAL -DCONFIG_BLUETOOTH_PERIPHERAL +INCLUDES=-I. -Icommon -Idrivers -Ibootcode -Iframework/include -Iframework/include/services/ble -Iframework/src/services/ble_service +#-Iframework/src/services/ble -Iframework/include/services/ble +INCLUDES+= -Idrivers/rpc -Iframework/src EXTRA_CFLAGS=-D__CPU_ARC__ -DCLOCK_SPEED=32 -std=c99 -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 CFLAGS=$(HWFLAGS) $(OPTFLAGS) $(EXTRA_CFLAGS) $(CFGFLAGS) $(INCLUDES) @@ -39,7 +54,7 @@ lib: $(TARGET_LIB) $(TARGET_LIB): $(C_OBJ) $(ASM_OBJ) @echo "Link $@" - $(AR) rcs $@ $^ + @$(AR) rcs $@ $^ %.o: %.S @echo "Assembling $<" @@ -47,7 +62,7 @@ $(TARGET_LIB): $(C_OBJ) $(ASM_OBJ) %.o: %.c @echo "Compiling $<" - $(CC) -c $(CFLAGS) $< -o $@ + @$(CC) -c $(CFLAGS) $< -o $@ lib_install: lib @if test "$(LIB_INSTALL_PATH)" = "" ; then \ diff --git a/system/libarc32_arduino101/common/atomic.h b/system/libarc32_arduino101/common/atomic.h new file mode 100644 index 00000000..d30cfbe4 --- /dev/null +++ b/system/libarc32_arduino101/common/atomic.h @@ -0,0 +1,156 @@ +/* atomic operations */ + +/* + * Copyright (c) 1997-2015, Wind River Systems, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __ATOMIC_H__ +#define __ATOMIC_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int atomic_t; +typedef atomic_t atomic_val_t; + +extern atomic_val_t atomic_add(atomic_t *target, atomic_val_t value); +extern atomic_val_t atomic_and(atomic_t *target, atomic_val_t value); +extern atomic_val_t atomic_dec(atomic_t *target); +extern atomic_val_t atomic_inc(atomic_t *target); +extern atomic_val_t atomic_nand(atomic_t *target, atomic_val_t value); +extern atomic_val_t atomic_or(atomic_t *target, atomic_val_t value); +extern atomic_val_t atomic_sub(atomic_t *target, atomic_val_t value); +extern atomic_val_t atomic_xor(atomic_t *target, atomic_val_t value); +extern atomic_val_t atomic_clear(atomic_t *target); +extern atomic_val_t atomic_get(const atomic_t *target); +extern atomic_val_t atomic_set(atomic_t *target, atomic_val_t value); +extern int atomic_cas(atomic_t *target, + atomic_val_t oldValue, atomic_val_t newValue); + + +#define ATOMIC_INIT(i) {(i)} + +#define ATOMIC_BITS (sizeof(atomic_val_t) * 8) +#define ATOMIC_MASK(bit) (1 << ((bit) & (ATOMIC_BITS - 1))) +#define ATOMIC_ELEM(addr, bit) ((addr) + ((bit) / ATOMIC_BITS)) + +/** @brief Test whether a bit is set + * + * Test whether bit number bit is set or not. + * + * Also works for an array of multiple atomic_t variables, in which + * case the bit number may go beyond the number of bits in a single + * atomic_t variable. + * + * @param addr base address to start counting from + * @param bit bit number counted from the base address + * + * @return 1 if the bit was set, 0 if it wasn't + */ +static inline int atomic_test_bit(const atomic_t *addr, int bit) +{ + atomic_val_t val = atomic_get(ATOMIC_ELEM(addr, bit)); + + return (1 & (val >> (bit & (ATOMIC_BITS - 1)))); +} + +/** @brief Clear a bit and return its old value + * + * Atomically clear a bit and return its old value. + * + * Also works for an array of multiple atomic_t variables, in which + * case the bit number may go beyond the number of bits in a single + * atomic_t variable. + * + * @param addr base address to start counting from + * @param bit bit number counted from the base address + * + * @return 1 if the bit was set, 0 if it wasn't + */ +static inline int atomic_test_and_clear_bit(atomic_t *addr, int bit) +{ + atomic_val_t mask = ATOMIC_MASK(bit); + atomic_val_t old; + + old = atomic_and(ATOMIC_ELEM(addr, bit), ~mask); + + return (old & mask) != 0; +} + +/** @brief Set a bit and return its old value + * + * Atomically set a bit and return its old value. + * + * Also works for an array of multiple atomic_t variables, in which + * case the bit number may go beyond the number of bits in a single + * atomic_t variable. + * + * @param addr base address to start counting from + * @param bit bit number counted from the base address + * + * @return 1 if the bit was set, 0 if it wasn't + */ +static inline int atomic_test_and_set_bit(atomic_t *addr, int bit) +{ + atomic_val_t mask = ATOMIC_MASK(bit); + atomic_val_t old; + + old = atomic_or(ATOMIC_ELEM(addr, bit), mask); + + return (old & mask) != 0; +} + +/** @brief Clear a bit + * + * Atomically clear a bit. + * + * Also works for an array of multiple atomic_t variables, in which + * case the bit number may go beyond the number of bits in a single + * atomic_t variable. + * + * @param addr base address to start counting from + * @param bit bit number counted from the base address + */ +static inline void atomic_clear_bit(atomic_t *addr, int bit) +{ + atomic_val_t mask = ATOMIC_MASK(bit); + + atomic_and(ATOMIC_ELEM(addr, bit), ~mask); +} + +/** @brief Set a bit + * + * Atomically set a bit. + * + * Also works for an array of multiple atomic_t variables, in which + * case the bit number may go beyond the number of bits in a single + * atomic_t variable. + * + * @param addr base address to start counting from + * @param bit bit number counted from the base address + */ +static inline void atomic_set_bit(atomic_t *addr, int bit) +{ + atomic_val_t mask = ATOMIC_MASK(bit); + + atomic_or(ATOMIC_ELEM(addr, bit), mask); +} + +#ifdef __cplusplus +} +#endif + +#endif /* __ATOMIC_H__ */ diff --git a/system/libarc32_arduino101/common/misc/byteorder.h b/system/libarc32_arduino101/common/misc/byteorder.h new file mode 100644 index 00000000..67cecbc3 --- /dev/null +++ b/system/libarc32_arduino101/common/misc/byteorder.h @@ -0,0 +1,44 @@ +/* byteorder.h - Byte order helpers */ + +/* + * Copyright (c) 2015, Intel Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define bswap_16(x) ((uint16_t) ((((x) >> 8) & 0xff) | (((x) & 0xff) << 8))) + +#define bswap_32(x) ((uint32_t) ((((x) >> 24) & 0xff) | (((x) >> 8) & 0xff00) \ + | (((x) & 0xff00) << 8) | (((x) & 0xff) << 24))) + +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define sys_le16_to_cpu(val) (val) +#define sys_cpu_to_le16(val) (val) +#define sys_be16_to_cpu(val) bswap_16(val) +#define sys_cpu_to_be16(val) bswap_16(val) +#define sys_le32_to_cpu(val) (val) +#define sys_cpu_to_le32(val) (val) +#define sys_be32_to_cpu(val) bswap_32(val) +#define sys_cpu_to_be32(val) bswap_32(val) +#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#define sys_le16_to_cpu(val) bswap_16(val) +#define sys_cpu_to_le16(val) bswap_16(val) +#define sys_be16_to_cpu(val) (val) +#define sys_cpu_to_be16(val) (val) +#define sys_le32_to_cpu(val) bswap_32(val) +#define sys_cpu_to_le32(val) bswap_32(val) +#define sys_be32_to_cpu(val) (val) +#define sys_cpu_to_be32(val) (val) +#else +#error "Unknown byte order" +#endif diff --git a/system/libarc32_arduino101/common/misc/util.h b/system/libarc32_arduino101/common/misc/util.h index 626c2b69..ab45bbb1 100644 --- a/system/libarc32_arduino101/common/misc/util.h +++ b/system/libarc32_arduino101/common/misc/util.h @@ -44,6 +44,11 @@ extern "C" { #ifndef _ASMLANGUAGE +#define ARRAY_SIZE(array) ((unsigned long)(sizeof(array) / sizeof((array)[0]))) +#define CONTAINER_OF(ptr, type, field) \ + ((type *)(((char *)(ptr)) - offsetof(type, field))) + + /* round "x" up/down to next multiple of "align" (which must be a power of 2) */ #define ROUND_UP(x, align) \ (((unsigned long)(x) + ((unsigned long)align - 1)) & \ diff --git a/system/libarc32_arduino101/drivers/atomic_native.c b/system/libarc32_arduino101/drivers/atomic_native.c new file mode 100644 index 00000000..41bed7d4 --- /dev/null +++ b/system/libarc32_arduino101/drivers/atomic_native.c @@ -0,0 +1,370 @@ +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2011-2014 Wind River Systems, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file Atomic ops for ARC EM + * + * This module provides the atomic operators for ARC EM family processors + * which do not support native atomic operations. + * + * The atomic operations are guaranteed to be atomic with respect + * to interrupt service routines, and to operations performed by peer + * processors. + * + * (originally from x86's atomic.c) + */ + +#include +//#include +//#include +#include +#define irq_lock() interrupt_lock() +#define irq_unlock(key) interrupt_unlock(key) + +#if defined(__clang__) +#define FUNC_NO_FP +#else +#define FUNC_NO_FP __attribute__((optimize("-fomit-frame-pointer"))) +#endif +/** + * + * @brief Atomic compare-and-set primitive + * + * This routine provides the compare-and-set operator. If the original value at + * equals , then is stored at and the + * function returns 1. + * + * If the original value at does not equal , then the store + * is not done and the function returns 0. + * + * The reading of the original value at , the comparison, + * and the write of the new value (if it occurs) all happen atomically with + * respect to both interrupts and accesses of other processors to . + * + * @param target address to be tested + * @param old_value value to compare against + * @param new_value value to compare against + * @return Returns 1 if is written, 0 otherwise. + */ +FUNC_NO_FP int atomic_cas(atomic_t *target, atomic_val_t old_value, + atomic_val_t new_value) +{ + unsigned int key; + int ret = 0; + + key = irq_lock(); + + if (*target == old_value) { + *target = new_value; + ret = 1; + } + + irq_unlock(key); + + return ret; +} + +/** + * + * @brief Atomic addition primitive + * + * This routine provides the atomic addition operator. The is + * atomically added to the value at , placing the result at , + * and the old value from is returned. + * + * @param target memory location to add to + * @param value the value to add + * + * @return The previous value from + */ +FUNC_NO_FP atomic_val_t atomic_add(atomic_t *target, atomic_val_t value) +{ + unsigned int key; + atomic_val_t ret; + + key = irq_lock(); + + ret = *target; + *target += value; + + irq_unlock(key); + + return ret; +} + +/** + * + * @brief Atomic subtraction primitive + * + * This routine provides the atomic subtraction operator. The is + * atomically subtracted from the value at , placing the result at + * , and the old value from is returned. + * + * @param target the memory location to subtract from + * @param value the value to subtract + * + * @return The previous value from + */ +FUNC_NO_FP atomic_val_t atomic_sub(atomic_t *target, atomic_val_t value) +{ + unsigned int key; + atomic_val_t ret; + + key = irq_lock(); + + ret = *target; + *target -= value; + + irq_unlock(key); + + return ret; +} + +/** + * + * @brief Atomic increment primitive + * + * @param target memory location to increment + * + * This routine provides the atomic increment operator. The value at + * is atomically incremented by 1, and the old value from is returned. + * + * @return The value from before the increment + */ +FUNC_NO_FP atomic_val_t atomic_inc(atomic_t *target) +{ + unsigned int key; + atomic_val_t ret; + + key = irq_lock(); + + ret = *target; + (*target)++; + + irq_unlock(key); + + return ret; +} + +/** + * + * @brief Atomic decrement primitive + * + * @param target memory location to decrement + * + * This routine provides the atomic decrement operator. The value at + * is atomically decremented by 1, and the old value from is returned. + * + * @return The value from prior to the decrement + */ +FUNC_NO_FP atomic_val_t atomic_dec(atomic_t *target) +{ + unsigned int key; + atomic_val_t ret; + + key = irq_lock(); + + ret = *target; + (*target)--; + + irq_unlock(key); + + return ret; +} + +/** + * + * @brief Atomic get primitive + * + * @param target memory location to read from + * + * This routine provides the atomic get primitive to atomically read + * a value from . It simply does an ordinary load. Note that + * is expected to be aligned to a 4-byte boundary. + * + * @return The value read from + */ +FUNC_NO_FP atomic_val_t atomic_get(const atomic_t *target) +{ + return *target; +} + +/** + * + * @brief Atomic get-and-set primitive + * + * This routine provides the atomic set operator. The is atomically + * written at and the previous value at is returned. + * + * @param target the memory location to write to + * @param value the value to write + * + * @return The previous value from + */ +FUNC_NO_FP atomic_val_t atomic_set(atomic_t *target, atomic_val_t value) +{ + unsigned int key; + atomic_val_t ret; + + key = irq_lock(); + + ret = *target; + *target = value; + + irq_unlock(key); + + return ret; +} + +/** + * + * @brief Atomic clear primitive + * + * This routine provides the atomic clear operator. The value of 0 is atomically + * written at and the previous value at is returned. (Hence, + * atomic_clear(pAtomicVar) is equivalent to atomic_set(pAtomicVar, 0).) + * + * @param target the memory location to write + * + * @return The previous value from + */ +FUNC_NO_FP atomic_val_t atomic_clear(atomic_t *target) +{ + unsigned int key; + atomic_val_t ret; + + key = irq_lock(); + + ret = *target; + *target = 0; + + irq_unlock(key); + + return ret; +} + +/** + * + * @brief Atomic bitwise inclusive OR primitive + * + * This routine provides the atomic bitwise inclusive OR operator. The + * is atomically bitwise OR'ed with the value at , placing the result + * at , and the previous value at is returned. + * + * @param target the memory location to be modified + * @param value the value to OR + * + * @return The previous value from + */ +FUNC_NO_FP atomic_val_t atomic_or(atomic_t *target, atomic_val_t value) +{ + unsigned int key; + atomic_val_t ret; + + key = irq_lock(); + + ret = *target; + *target |= value; + + irq_unlock(key); + + return ret; +} + +/** + * + * @brief Atomic bitwise exclusive OR (XOR) primitive + * + * This routine provides the atomic bitwise exclusive OR operator. The + * is atomically bitwise XOR'ed with the value at , placing the result + * at , and the previous value at is returned. + * + * @param target the memory location to be modified + * @param value the value to XOR + * + * @return The previous value from + */ +FUNC_NO_FP atomic_val_t atomic_xor(atomic_t *target, atomic_val_t value) +{ + unsigned int key; + atomic_val_t ret; + + key = irq_lock(); + + ret = *target; + *target ^= value; + + irq_unlock(key); + + return ret; +} + +/** + * + * @brief Atomic bitwise AND primitive + * + * This routine provides the atomic bitwise AND operator. The is + * atomically bitwise AND'ed with the value at , placing the result + * at , and the previous value at is returned. + * + * @param target the memory location to be modified + * @param value the value to AND + * + * @return The previous value from + */ +FUNC_NO_FP atomic_val_t atomic_and(atomic_t *target, atomic_val_t value) +{ + unsigned int key; + atomic_val_t ret; + + key = irq_lock(); + + ret = *target; + *target &= value; + + irq_unlock(key); + + return ret; +} + +/** + * + * @brief Atomic bitwise NAND primitive + * + * This routine provides the atomic bitwise NAND operator. The is + * atomically bitwise NAND'ed with the value at , placing the result + * at , and the previous value at is returned. + * + * @param target the memory location to be modified + * @param value the value to NAND + * + * @return The previous value from + */ +FUNC_NO_FP atomic_val_t atomic_nand(atomic_t *target, atomic_val_t value) +{ + unsigned int key; + atomic_val_t ret; + + key = irq_lock(); + + ret = *target; + *target = ~(*target & value); + + irq_unlock(key); + + return ret; +} diff --git a/system/libarc32_arduino101/drivers/bluetooth/att.h b/system/libarc32_arduino101/drivers/bluetooth/att.h new file mode 100644 index 00000000..5752a079 --- /dev/null +++ b/system/libarc32_arduino101/drivers/bluetooth/att.h @@ -0,0 +1,55 @@ +/** @file + * @brief Attribute Protocol handling. + */ + +/* + * Copyright (c) 2016 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __BT_ATT_H +#define __BT_ATT_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Error codes for Error response PDU */ +#define BT_ATT_ERR_INVALID_HANDLE 0x01 +#define BT_ATT_ERR_READ_NOT_PERMITTED 0x02 +#define BT_ATT_ERR_WRITE_NOT_PERMITTED 0x03 +#define BT_ATT_ERR_INVALID_PDU 0x04 +#define BT_ATT_ERR_AUTHENTICATION 0x05 +#define BT_ATT_ERR_NOT_SUPPORTED 0x06 +#define BT_ATT_ERR_INVALID_OFFSET 0x07 +#define BT_ATT_ERR_AUTHORIZATION 0x08 +#define BT_ATT_ERR_PREPARE_QUEUE_FULL 0x09 +#define BT_ATT_ERR_ATTRIBUTE_NOT_FOUND 0x0a +#define BT_ATT_ERR_ATTRIBUTE_NOT_LONG 0x0b +#define BT_ATT_ERR_ENCRYPTION_KEY_SIZE 0x0c +#define BT_ATT_ERR_INVALID_ATTRIBUTE_LEN 0x0d +#define BT_ATT_ERR_UNLIKELY 0x0e +#define BT_ATT_ERR_INSUFFICIENT_ENCRYPTION 0x0f +#define BT_ATT_ERR_UNSUPPORTED_GROUP_TYPE 0x10 +#define BT_ATT_ERR_INSUFFICIENT_RESOURCES 0x11 + +/* Common Profile Error Codes (from CSS) */ +#define BT_ATT_ERR_CCC_IMPROPER_CONF 0xfd +#define BT_ATT_ERR_PROCEDURE_IN_PROGRESS 0xfe +#define BT_ATT_ERR_OUT_OF_RANGE 0xff + +#ifdef __cplusplus +} +#endif + +#endif /* __BT_ATT_H */ diff --git a/system/libarc32_arduino101/drivers/bluetooth/bluetooth.h b/system/libarc32_arduino101/drivers/bluetooth/bluetooth.h new file mode 100644 index 00000000..e87cf2ab --- /dev/null +++ b/system/libarc32_arduino101/drivers/bluetooth/bluetooth.h @@ -0,0 +1,353 @@ +/** @file + * @brief Bluetooth subsystem core APIs. + */ + +/* + * Copyright (c) 2015 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __BT_BLUETOOTH_H +#define __BT_BLUETOOTH_H + +#include +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Callback for notifying that Bluetooth has been enabled. + * + * @param err zero on success or (negative) error code otherwise. + */ +typedef void (*bt_ready_cb_t)(int err); + +/** @brief Enable Bluetooth + * + * Enable Bluetooth. Must be the called before any calls that + * require communication with the local Bluetooth hardware. + * + * @param cb Callback to notify completion or NULL to perform the + * enabling synchronously. + * + * @return Zero on success or (negative) error code otherwise. + */ +int bt_enable(bt_ready_cb_t cb); + +/* Advertising API */ + +/** Description of different data types that can be encoded into + * advertising data. Used to form arrays that are passed to the + * bt_le_adv_start() function. + */ +struct bt_data { + uint8_t type; + uint8_t data_len; + const uint8_t *data; +}; + +/** @brief Helper to declare elements of bt_data arrays + * + * This macro is mainly for creating an array of struct bt_data + * elements which is then passed to bt_le_adv_start(). + * + * @param _type Type of advertising data field + * @param _data Pointer to the data field payload + * @param _data_len Number of bytes behind the _data pointer + */ +#define BT_DATA(_type, _data, _data_len) \ + { \ + .type = (_type), \ + .data_len = (_data_len), \ + .data = (_data), \ + } + +/** @brief Helper to declare elements of bt_data arrays + * + * This macro is mainly for creating an array of struct bt_data + * elements which is then passed to bt_le_adv_start(). + * + * @param _type Type of advertising data field + * @param _bytes Variable number of single-byte parameters + */ +#define BT_DATA_BYTES(_type, _bytes...) \ + BT_DATA(_type, ((uint8_t []) { _bytes }), \ + sizeof((uint8_t []) { _bytes })) + +/** Local advertising address type */ +enum { + /** Use local identity address for advertising. Unless a static + * random address has been configured this will be the public + * address. + */ + BT_LE_ADV_ADDR_IDENTITY, + + /** Use local Non-resolvable Private Address (NRPA) for advertising */ + BT_LE_ADV_ADDR_NRPA, +}; + +/** LE Advertising Parameters. */ +struct bt_le_adv_param { + /** Advertising type */ + uint8_t type; + + /** Which type of own address to use for advertising */ + uint8_t addr_type; + + /** Minimum Advertising Interval (N * 0.625) */ + uint16_t interval_min; + + /** Maximum Advertising Interval (N * 0.625) */ + uint16_t interval_max; +}; + +/** Helper to declare advertising parameters inline + * + * @param _type Advertising Type + * @param _addr_type Local address type to use for advertising + * @param _int_min Minimum advertising interval + * @param _int_max Maximum advertising interval + */ +#define BT_LE_ADV_PARAM(_type, _addr_type, _int_min, _int_max) \ + (&(struct bt_le_adv_param) { \ + .type = (_type), \ + .addr_type = (_addr_type), \ + .interval_min = (_int_min), \ + .interval_max = (_int_max), \ + }) + +#define BT_LE_ADV(t) BT_LE_ADV_PARAM(t, BT_LE_ADV_ADDR_IDENTITY, \ + BT_GAP_ADV_FAST_INT_MIN_2, \ + BT_GAP_ADV_FAST_INT_MAX_2) + +/** @brief Start advertising + * + * Set advertisement data, scan response data, advertisement parameters + * and start advertising. + * + * @param param Advertising parameters. + * @param ad Data to be used in advertisement packets. + * @param ad_len Number of elements in ad + * @param sd Data to be used in scan response packets. + * @param sd_len Number of elements in sd + * + * @return Zero on success or (negative) error code otherwise. + */ +int bt_le_adv_start(const struct bt_le_adv_param *param, + const struct bt_data *ad, size_t ad_len, + const struct bt_data *sd, size_t sd_len); + +/** @brief Stop advertising + * + * Stops ongoing advertising. + * + * @return Zero on success or (negative) error code otherwise. + */ +int bt_le_adv_stop(void); + +/** @brief Define a type allowing user to implement a function that can + * be used to get back active LE scan results. + * + * A function of this type will be called back when user application + * triggers active LE scan. The caller will populate all needed + * parameters based on data coming from scan result. + * Such function can be set by user when LE active scan API is used. + * + * @param addr Advertiser LE address and type. + * @param rssi Strength of advertiser signal. + * @param adv_type Type of advertising response from advertiser. + * @param adv_data Address of buffer containig advertiser data. + * @param len Length of advertiser data contained in buffer. + */ +typedef void bt_le_scan_cb_t(const bt_addr_le_t *addr, int8_t rssi, + uint8_t adv_type, const uint8_t *adv_data, + uint8_t len); + +/** LE scan parameters */ +struct bt_le_scan_param { + /** Scan type (BT_HCI_LE_SCAN_ACTIVE or BT_HCI_LE_SCAN_PASSIVE) */ + uint8_t type; + + /** Duplicate filtering (BT_HCI_LE_SCAN_FILTER_DUP_ENABLE or + * BT_HCI_LE_SCAN_FILTER_DUP_DISABLE) + */ + uint8_t filter_dup; + + /** Scan interval (N * 0.625 ms) */ + uint16_t interval; + + /** Scan window (N * 0.625 ms) */ + uint16_t window; +}; + +/** Helper to declare scan parameters inline + * + * @param _type Scan Type (BT_HCI_LE_SCAN_ACTIVE/BT_HCI_LE_SCAN_PASSIVE) + * @param _filter Filter Duplicates + * @param _interval Scan Interval (N * 0.625 ms) + * @param _window Scan Window (N * 0.625 ms) + */ +#define BT_LE_SCAN_PARAM(_type, _filter, _interval, _window) \ + (&(struct bt_le_scan_param) { \ + .type = (_type), \ + .filter_dup = (_filter), \ + .interval = (_interval), \ + .window = (_window), \ + }) + +/** Helper macro to enable active scanning to discover new devices. */ +#define BT_LE_SCAN_ACTIVE BT_LE_SCAN_PARAM(BT_HCI_LE_SCAN_ACTIVE, \ + BT_HCI_LE_SCAN_FILTER_DUP_ENABLE, \ + BT_GAP_SCAN_FAST_INTERVAL, \ + BT_GAP_SCAN_FAST_WINDOW) + +/** Helper macro to enable passive scanning to discover new devices. + * + * This macro should be used if information required for device identification + * (eg UUID) are known to be placed in Advertising Data. + */ +#define BT_LE_SCAN_PASSIVE BT_LE_SCAN_PARAM(BT_HCI_LE_SCAN_PASSIVE, \ + BT_HCI_LE_SCAN_FILTER_DUP_ENABLE, \ + BT_GAP_SCAN_FAST_INTERVAL, \ + BT_GAP_SCAN_FAST_WINDOW) + +/** @brief Start (LE) scanning + * + * Start LE scanning with and provide results through the specified + * callback. + * + * @param param Scan parameters. + * @param cb Callback to notify scan results. + * + * @return Zero on success or error code otherwise, positive in case + * of protocol error or negative (POSIX) in case of stack internal error + */ +int bt_le_scan_start(const struct bt_le_scan_param *param, bt_le_scan_cb_t cb); + +/** @brief Stop (LE) scanning. + * + * Stops ongoing LE scanning. + * + * @return Zero on success or error code otherwise, positive in case + * of protocol error or negative (POSIX) in case of stack internal error + */ +int bt_le_scan_stop(void); + +/** @def BT_ADDR_STR_LEN + * + * @brief Recommended length of user string buffer for Bluetooth address + * + * @details The recommended length guarantee the output of address + * conversion will not lose valuable information about address being + * processed. + */ +#define BT_ADDR_STR_LEN 18 + +/** @def BT_ADDR_LE_STR_LEN + * + * @brief Recommended length of user string buffer for Bluetooth LE address + * + * @details The recommended length guarantee the output of address + * conversion will not lose valuable information about address being + * processed. + */ +#define BT_ADDR_LE_STR_LEN 27 + +/** @brief Converts binary Bluetooth address to string. + * + * @param addr Address of buffer containing binary Bluetooth address. + * @param str Address of user buffer with enough room to store formatted + * string containing binary address. + * @param len Length of data to be copied to user string buffer. Refer to + * BT_ADDR_STR_LEN about recommended value. + * + * @return Number of successfully formatted bytes from binary address. + */ +static inline int bt_addr_to_str(const bt_addr_t *addr, char *str, size_t len) +{ + return snprintf(str, len, "%2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X", + addr->val[5], addr->val[4], addr->val[3], + addr->val[2], addr->val[1], addr->val[0]); +} + +/** @brief Converts binary LE Bluetooth address to string. + * + * @param addr Address of buffer containing binary LE Bluetooth address. + * @param str Address of user buffer with enough room to store + * formatted string containing binary LE address. + * @param len Length of data to be copied to user string buffer. Refer to + * BT_ADDR_LE_STR_LEN about recommended value. + * + * @return Number of successfully formatted bytes from binary address. + */ +static inline int bt_addr_le_to_str(const bt_addr_le_t *addr, char *str, + size_t len) +{ + char type[7]; + + switch (addr->type) { + case BT_ADDR_LE_PUBLIC: + strcpy(type, "public"); + break; + case BT_ADDR_LE_RANDOM: + strcpy(type, "random"); + break; + default: + sprintf(type, "0x%02x", addr->type); + break; + } + + return snprintf(str, len, "%2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X (%s)", + addr->val[5], addr->val[4], addr->val[3], + addr->val[2], addr->val[1], addr->val[0], type); +} + +#if defined(CONFIG_BLUETOOTH_BREDR) +/** @brief Enable/disable set controller in discoverable state. + * + * Allows make local controller to listen on INQUIRY SCAN channel and responds + * to devices making general inquiry. To enable this state it's mandatory + * to first be in connectable state. + * + * @param enable Value allowing/disallowing controller to become discoverable. + * + * @return Negative if fail set to requested state or requested state has been + * already set. Zero if done successfully. + */ +int bt_br_set_discoverable(bool enable); + +/** @brief Enable/disable set controller in connectable state. + * + * Allows make local controller to be connectable. It means the controller + * start listen to devices requests on PAGE SCAN channel. If disabled also + * resets discoverability if was set. + * + * @param enable Value allowing/disallowing controller to be connectable. + * + * @return Negative if fail set to requested state or requested state has been + * already set. Zero if done successfully. + */ +int bt_br_set_connectable(bool enable); +#endif + +void bt_le_set_device_name(char *device_name, int len); + +#ifdef __cplusplus +} +#endif + +#endif /* __BT_BLUETOOTH_H */ diff --git a/system/libarc32_arduino101/drivers/bluetooth/conn.h b/system/libarc32_arduino101/drivers/bluetooth/conn.h new file mode 100644 index 00000000..1b710d6f --- /dev/null +++ b/system/libarc32_arduino101/drivers/bluetooth/conn.h @@ -0,0 +1,399 @@ +/** @file + * @brief Bluetooth connection handling + */ + +/* + * Copyright (c) 2015 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __BT_CONN_H +#define __BT_CONN_H + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(CONFIG_BLUETOOTH_CENTRAL) || defined(CONFIG_BLUETOOTH_PERIPHERAL) +#include + +#include +#include + +/** Opaque type representing a connection to a remote device */ +struct bt_conn; + +/** Connection parameters for LE connections */ +struct bt_le_conn_param { + uint16_t interval_min; + uint16_t interval_max; + uint16_t latency; + uint16_t timeout; +}; + +/** Helper to declare connection parameters inline + * + * @param int_min Minimum Connection Interval (N * 1.25 ms) + * @param int_max Maximum Connection Interval (N * 1.25 ms) + * @param lat Connection Latency + * @param timeout Supervision Timeout (N * 10 ms) + */ +#define BT_LE_CONN_PARAM(int_min, int_max, lat, to) \ + (&(struct bt_le_conn_param) { \ + .interval_min = (int_min), \ + .interval_max = (int_max), \ + .latency = (lat), \ + .timeout = (to), \ + }) + +/** Default LE connection parameters: + * Connection Interval: 30-50 ms + * Latency: 0 + * Timeout: 4 s + */ +#define BT_LE_CONN_PARAM_DEFAULT BT_LE_CONN_PARAM(BT_GAP_INIT_CONN_INT_MIN, \ + BT_GAP_INIT_CONN_INT_MAX, \ + 0, 400) + +/** @brief Increment a connection's reference count. + * + * Increment the reference count of a connection object. + * + * @param conn Connection object. + * + * @return Connection object with incremented reference count. + */ +struct bt_conn *bt_conn_ref(struct bt_conn *conn); + +/** @brief Decrement a connection's reference count. + * + * Decrement the reference count of a connection object. + * + * @param conn Connection object. + */ +void bt_conn_unref(struct bt_conn *conn); + +/** @brief Look up an existing connection by address. + * + * Look up an existing connection based on the remote address. + * + * @param peer Remote address. + * + * @return Connection object or NULL if not found. The caller gets a + * new reference to the connection object which must be released with + * bt_conn_unref() once done using the object. + */ +struct bt_conn *bt_conn_lookup_addr_le(const bt_addr_le_t *peer); + +/** @brief Get destination (peer) address of a connection. + * + * @param conn Connection object. + * + * @return Destination address. + */ +const bt_addr_le_t *bt_conn_get_dst(const struct bt_conn *conn); + +/** Connection Type */ +enum { + BT_CONN_TYPE_LE, /** LE Connection Type */ +#if defined(CONFIG_BLUETOOTH_BREDR) + BT_CONN_TYPE_BR, /** BR/EDR Connection Type */ +#endif +}; + +/** LE Connection Info Structure */ +struct bt_conn_le_info { + const bt_addr_le_t *src; /** Source Address */ + const bt_addr_le_t *dst; /** Destination Address */ + uint16_t interval; /** Connection interval */ + uint16_t latency; /** Connection slave latency */ + uint16_t timeout; /** Connection supervision timeout */ +}; + +#if defined(CONFIG_BLUETOOTH_BREDR) +/** BR/EDR Connection Info Structure */ +struct bt_conn_br_info { + const bt_addr_t *dst; /** Destination BR/EDR address */ +}; +#endif + +/** Connection role (master or slave) */ +enum { + BT_CONN_ROLE_MASTER, + BT_CONN_ROLE_SLAVE, +}; + +/** Connection Info Structure */ +struct bt_conn_info { + /** Connection Type */ + uint8_t type; + + /** Connection Role */ + uint8_t role; + + union { + /** LE Connection specific Info */ + struct bt_conn_le_info le; +#if defined(CONFIG_BLUETOOTH_BREDR) + struct bt_conn_br_info br; +#endif + }; +}; + +/** @brief Get connection info + * + * @param conn Connection object. + * @param info Connection info object. + * + * @return Zero on success or (negative) error code on failure. + */ +int bt_conn_get_info(const struct bt_conn *conn, struct bt_conn_info *info); + +/** @brief Update the connection parameters. + * + * @param conn Connection object. + * @param param Updated connection parameters. + * + * @return Zero on success or (negative) error code on failure. + */ +int bt_conn_le_param_update(struct bt_conn *conn, + const struct bt_le_conn_param *param); + +/** @brief Disconnect from a remote device or cancel pending connection. + * + * Disconnect an active connection with the specified reason code or cancel + * pending outgoing connection. + * + * @param conn Connection to disconnect. + * @param reason Reason code for the disconnection. + * + * @return Zero on success or (negative) error code on failure. + */ +int bt_conn_disconnect(struct bt_conn *conn, uint8_t reason); + +#if defined(CONFIG_BLUETOOTH_CENTRAL) +/** @brief Initiate an LE connection to a remote device. + * + * Allows initiate new LE link to remote peer using its address. + * Returns a new reference that the the caller is responsible for managing. + * + * @param peer Remote address. + * @param param Initial connection parameters. + * + * @return Valid connection object on success or NULL otherwise. + */ +struct bt_conn *bt_conn_create_le(const bt_addr_le_t *peer, + const struct bt_le_conn_param *param); + +/** @brief Automatically connect to remote device if it's in range. + * + * This function enables/disables automatic connection initiation. + * Everytime the device looses the connection with peer, this connection + * will be re-established if connectable advertisement from peer is received. + * + * @param addr Remote Bluetooth address. + * @param param If non-NULL, auto connect is enabled with the given + * parameters. If NULL, auto connect is disabled. + * + * @return Zero on success or error code otherwise. + */ +int bt_le_set_auto_conn(bt_addr_le_t *addr, + const struct bt_le_conn_param *param); +#endif /* CONFIG_BLUETOOTH_CENTRAL */ + +#if defined(CONFIG_BLUETOOTH_PERIPHERAL) +/** @brief Initiate directed advertising to a remote device + * + * Allows initiating a new LE connection to remote peer with the remote + * acting in central role and the local device in peripheral role. + * + * The advertising type must be either BT_LE_ADV_DIRECT_IND or + * BT_LE_ADV_DIRECT_IND_LOW_DUTY. + * + * In case of high duty cycle this will result in a callback with + * connected() with a new connection or with an error. + * + * The advertising may be cancelled with bt_conn_disconnect(). + * + * Returns a new reference that the the caller is responsible for managing. + * + * @param peer Remote address. + * @param param Directed advertising parameters. + * + * @return Valid connection object on success or NULL otherwise. + */ +struct bt_conn *bt_conn_create_slave_le(const bt_addr_le_t *peer, + const struct bt_le_adv_param *param); +#endif /* CONFIG_BLUETOOTH_PERIPHERAL */ + +/** Security level. */ +typedef enum __packed { + BT_SECURITY_LOW, /** No encryption and no authentication. */ + BT_SECURITY_MEDIUM, /** encryption and no authentication (no MITM). */ + BT_SECURITY_HIGH, /** encryption and authentication (MITM). */ + BT_SECURITY_FIPS, /** Authenticated LE Secure Connections and + * encryption. + */ +} bt_security_t; + +#if defined(CONFIG_BLUETOOTH_SMP) +/** @brief Set security level for a connection. + * + * This function enable security (encryption) for a connection. If device is + * already paired with sufficiently strong key encryption will be enabled. If + * link is already encrypted with sufficiently strong key this function does + * nothing. + * + * If device is not paired pairing will be initiated. If device is paired and + * keys are too weak but input output capabilities allow for strong enough keys + * pairing will be initiated. + * + * This function may return error if required level of security is not possible + * to achieve due to local or remote device limitation (eg input output + * capabilities). + * + * @param conn Connection object. + * @param sec Requested security level. + * + * @return 0 on success or negative error + */ +int bt_conn_security(struct bt_conn *conn, bt_security_t sec); + +/** @brief Get encryption key size. + * + * This function gets encryption key size. + * If there is no security (encryption) enabled 0 will be returned. + * + * @param conn Existing connection object. + * + * @return Encryption key size. + */ +uint8_t bt_conn_enc_key_size(struct bt_conn *conn); + +/** @brief Clear device information (bonding, keys). + * + * Clears all a bonding information (keys, etc). A bonded connection is + * disconnected. + * BT_ADDR_LE_ANY removes the of all bonded devices + * + * @param addr identity address of a bonded device + * + * @return 0 in success, error code otherwise + * + */ +int bt_conn_remove_info(const bt_addr_le_t *addr); +#endif /* CONFIG_BLUETOOTH_SMP */ + +/** Connection callback structure */ +struct bt_conn_cb { + void (*connected)(struct bt_conn *conn, uint8_t err); + void (*disconnected)(struct bt_conn *conn, uint8_t reason); + void (*le_param_updated)(struct bt_conn *conn, uint16_t interval, + uint16_t latency, uint16_t timeout); +#if defined(CONFIG_BLUETOOTH_SMP) + void (*identity_resolved)(struct bt_conn *conn, + const bt_addr_le_t *rpa, + const bt_addr_le_t *identity); + void (*security_changed)(struct bt_conn *conn, bt_security_t level); +#endif + struct bt_conn_cb *_next; +}; + +/** @brief Register connection callbacks. + * + * Register callbacks to monitor the state of connections. + * + * @param cb Callback struct. + */ +void bt_conn_cb_register(struct bt_conn_cb *cb); + +#endif /* CONFIG_BLUETOOTH_CENTRAL || CONFIG_BLUETOOTH_PERIPHERAL */ + +#if defined(CONFIG_BLUETOOTH_SMP) || defined(CONFIG_BLUETOOTH_BREDR) +/** Authenticated pairing callback structure */ +struct bt_conn_auth_cb { + void (*passkey_display)(struct bt_conn *conn, unsigned int passkey); + void (*passkey_entry)(struct bt_conn *conn); + void (*passkey_confirm)(struct bt_conn *conn, unsigned int passkey); + void (*cancel)(struct bt_conn *conn); +#if defined(CONFIG_BLUETOOTH_BREDR) + void (*pincode_entry)(struct bt_conn *conn, bool highsec); +#endif +}; + +/** @brief Register authentication callbacks. + * + * Register callbacks to handle authenticated pairing. Passing NULL unregisters + * previous callbacks structure. + * + * @param cb Callback struct. + * + * @return Zero on success or negative error code otherwise + */ +int bt_conn_auth_cb_register(const struct bt_conn_auth_cb *cb); + +/** @brief Reply with entered passkey. + * + * This function should be called only after passkey_entry callback from + * bt_conn_auth_cb structure was called. + * + * @param conn Connection object. + * @param passkey Entered passkey. + * + * @return Zero on success or negative error code otherwise + */ +int bt_conn_auth_passkey_entry(struct bt_conn *conn, unsigned int passkey); + +/** @brief Cancel ongoing authenticated pairing. + * + * This function allows to cancel ongoing authenticated pairing. + * + * @param conn Connection object. + * + * @return Zero on success or negative error code otherwise + */ +int bt_conn_auth_cancel(struct bt_conn *conn); + +/** @brief Reply if passkey was confirmed by user. + * + * This function should be called only after passkey_confirm callback from + * bt_conn_auth_cb structure was called. If passkey is confirmed to match + * then match should be true. Otherwise match should be false. + * + * @param conn Connection object. + * @param match True if passkey was confirmed to match, false otherwise. + * + * @return Zero on success or negative error code otherwise + */ +int bt_conn_auth_passkey_confirm(struct bt_conn *conn, bool match); + +#if defined(CONFIG_BLUETOOTH_BREDR) +/** @brief Reply with entered PIN code. + * + * This function should be called only after PIN code callback from + * bt_conn_auth_cb structure was called. It's for legacy 2.0 devices. + * + * @param conn Connection object. + * @param pin Entered PIN code. + * + * @return Zero on success or negative error code otherwise + */ +int bt_conn_auth_pincode_entry(struct bt_conn *conn, const char *pin); +#endif /* CONFIG_BLUETOOTH_BREDR */ +#endif /* CONFIG_BLUETOOTH_SMP || CONFIG_BLUETOOTH_BREDR */ + +#ifdef __cplusplus +} +#endif + +#endif /* __BT_CONN_H */ diff --git a/system/libarc32_arduino101/drivers/bluetooth/conn_internal.h b/system/libarc32_arduino101/drivers/bluetooth/conn_internal.h new file mode 100644 index 00000000..04c08839 --- /dev/null +++ b/system/libarc32_arduino101/drivers/bluetooth/conn_internal.h @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2016 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +typedef enum { + BT_CONN_DISCONNECTED, + BT_CONN_CONNECT_SCAN, + BT_CONN_CONNECT, + BT_CONN_CONNECTED, + BT_CONN_DISCONNECT, +} bt_conn_state_t; + +/* bt_conn flags: the flags defined here represent connection parameters */ +enum { + BT_CONN_AUTO_CONNECT, +}; + +struct bt_conn_le { + bt_addr_le_t dst; + +#if 0 + bt_addr_le_t init_addr; + bt_addr_le_t resp_addr; +#endif + uint16_t interval; + uint16_t interval_min; + uint16_t interval_max; + + uint16_t latency; + uint16_t timeout; +#if 0 + uint8_t features[8]; +#endif +}; + +#if defined(CONFIG_BLUETOOTH_BREDR) +struct bt_conn_br { + bt_addr_t dst; +}; +#endif + +struct bt_conn { + uint16_t handle; + uint8_t type; + uint8_t role; + +#if defined(CONFIG_BLUETOOTH_SMP) + uint8_t encrypt; + bt_security_t sec_level; + bt_security_t required_sec_level; +#endif /* CONFIG_BLUETOOTH_SMP */ + + atomic_t ref; + + /* Connection error or reason for disconnect */ + uint8_t err; + + bt_conn_state_t state; + union { + struct bt_conn_le le; +#if defined(CONFIG_BLUETOOTH_BREDR) + struct bt_conn_br br; +#endif + }; +}; + +/* Add a new LE connection */ +struct bt_conn *bt_conn_add_le(const bt_addr_le_t *peer); + +#if defined(CONFIG_BLUETOOTH_BREDR) +/* Add a new BR/EDR connection */ +struct bt_conn *bt_conn_add_br(const bt_addr_t *peer); + +/* Look up an existing connection by BT address */ +struct bt_conn *bt_conn_lookup_addr_br(const bt_addr_t *peer); +#endif + +/* Look up an existing connection */ +struct bt_conn *bt_conn_lookup_handle(uint16_t handle); + +/* Look up a connection state. For BT_ADDR_LE_ANY, returns the first connection + * with the specific state + */ +struct bt_conn *bt_conn_lookup_state_le(const bt_addr_le_t *peer, + const bt_conn_state_t state); + +/* Set connection object in certain state and perform action related to state */ +void bt_conn_set_state(struct bt_conn *conn, bt_conn_state_t state); + +void bt_conn_set_param_le(struct bt_conn *conn, + const struct bt_le_conn_param *param); + +int bt_conn_update_param_le(struct bt_conn *conn, + const struct bt_le_conn_param *param); + +int bt_conn_le_conn_update(struct bt_conn *conn, + const struct bt_le_conn_param *param); + +void notify_le_param_updated(struct bt_conn *conn); + +#if defined(CONFIG_BLUETOOTH_SMP) +/* rand and ediv should be in BT order */ +int bt_conn_le_start_encryption(struct bt_conn *conn, uint64_t rand, + uint16_t ediv, const uint8_t *ltk, size_t len); + +/* Notify higher layers that RPA was resolved */ +void bt_conn_identity_resolved(struct bt_conn *conn); + +/* Notify higher layers that connection security changed */ +void bt_conn_security_changed(struct bt_conn *conn); +#endif /* CONFIG_BLUETOOTH_SMP */ +/* Initialize connection management */ +int bt_conn_init(void); diff --git a/system/libarc32_arduino101/drivers/bluetooth/gatt.h b/system/libarc32_arduino101/drivers/bluetooth/gatt.h new file mode 100644 index 00000000..e9eac613 --- /dev/null +++ b/system/libarc32_arduino101/drivers/bluetooth/gatt.h @@ -0,0 +1,1045 @@ +/** @file + * @brief Generic Attribute Profile handling. + */ + +/* + * Copyright (c) 2015 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __BT_GATT_H +#define __BT_GATT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(CONFIG_BLUETOOTH_CENTRAL) || defined(CONFIG_BLUETOOTH_PERIPHERAL) +#include +#include +#include +#include +#include +#include + +/* GATT attribute permission bit field values */ + +/** @def BT_GATT_PERM_READ + * @brief Attribute read permission. + */ +#define BT_GATT_PERM_READ 0x01 +/** @def BT_GATT_PERM_WRITE + * @brief Attribute write permission. + */ +#define BT_GATT_PERM_WRITE 0x02 +/** @def BT_GATT_PERM_READ_ENCRYPT + * @brief Attribute read permission with encryption. + * + * If set, requires encryption for read access. + */ +#define BT_GATT_PERM_READ_ENCRYPT 0x04 +/** @def BT_GATT_PERM_WRITE_ENCRYPT + * @brief Attribute write permission with encryption. + * + * If set, requires encryption for write access. + */ +#define BT_GATT_PERM_WRITE_ENCRYPT 0x08 +/** @def BT_GATT_PERM_READ_AUTHEN + * @brief Attribute read permission with authentication. + * + * If set, requires encryption using authenticated link-key for read access. + */ +#define BT_GATT_PERM_READ_AUTHEN 0x10 +/** @def BT_GATT_PERM_WRITE_AUTHEN + * @brief Attribute write permission with authentication. + * + * If set, requires encryption using authenticated link-key for write access. + */ +#define BT_GATT_PERM_WRITE_AUTHEN 0x20 +/** @def BT_GATT_PERM_READ_AUTHOR + * @brief Attribute read permission with authorization. + * + * If set, requires authorization for read access. + */ +#define BT_GATT_PERM_READ_AUTHOR 0x40 +/** @def BT_GATT_PERM_WRITE_AUTHOR + * @brief Attribute write permission with authorization. + * + * If set, requires authorization for write access. + */ +#define BT_GATT_PERM_WRITE_AUTHOR 0x80 + +/* GATT attribute flush flags */ +/** @def BT_GATT_FLUSH_DISCARD + * @brief Attribute flush discard flag. + */ +#define BT_GATT_FLUSH_DISCARD 0x00 +/** @def BT_GATT_FLUSH_DISCARD + * @brief Attribute flush synchronize flag. + */ +#define BT_GATT_FLUSH_SYNC 0x01 + +/** @def BT_GATT_ERR + * @brief Construct error return value for attribute read, write and + * flush callbacks. + * + * @param _att_err ATT error code + * + * @return Appropriate error code for the attribute callbacks. + * + */ +#define BT_GATT_ERR(_att_err) (-(_att_err)) + +/** @brief GATT Attribute structure. */ +struct bt_gatt_attr { + /** Attribute UUID */ + const struct bt_uuid *uuid; + + /** Attribute read callback + * + * @param conn The connection that is requesting to read + * @param attr The attribute that's being read + * @param buf Buffer to place the read result in + * @param len Length of data to read + * @param offset Offset to start reading from + * + * @return Number fo bytes read, or in case of an error + * BT_GATT_ERR() with a specific ATT error code. + */ + ssize_t (*read)(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + void *buf, uint16_t len, + uint16_t offset); + + /** Attribute write callback + * + * @param conn The connection that is requesting to write + * @param attr The attribute that's being read + * @param buf Buffer with the data to write + * @param len Number of bytes in the buffer + * @param offset Offset to start writing from + * + * @return Number of bytes written, or in case of an error + * BT_GATT_ERR() with a specific ATT error code. + */ + ssize_t (*write)(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + const void *buf, uint16_t len, + uint16_t offset); + + /** Attribute flush callback + * + * If this callback is provided (non-NULL) every write + * operation will be followed by a call to it. The expectation + * is for the attribute implementation to only commit the write + * result once this is called. + * + * @param conn The connection that is requesting to write + * @param attr The attribute that's being read + * @param flags Flags (BT_GATT_FLUSH_*) + * + * @return Number of bytes flushed, or in case of an error + * BT_GATT_ERR() with a specific ATT error code. + */ + ssize_t (*flush)(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + uint8_t flags); + + /** Attribute user data */ + void *user_data; + /** Attribute handle */ + uint16_t handle; + /** Attribute permissions */ + uint8_t perm; +#if defined(CONFIG_BLUETOOTH_GATT_DYNAMIC_DB) + struct bt_gatt_attr *_next; +#endif /* CONFIG_BLUETOOTH_GATT_DYNAMIC_DB */ +}; + +/** @brief Service Attribute Value. */ +struct bt_gatt_service { + /** Service UUID. */ + const struct bt_uuid *uuid; + /** Service end handle. */ + uint16_t end_handle; +}; + +/** @brief Include Attribute Value. */ +struct bt_gatt_include { + /** Service UUID. */ + const struct bt_uuid *uuid; + /** Service start handle. */ + uint16_t start_handle; + /** Service end handle. */ + uint16_t end_handle; +}; + +/* Characteristic Properties Bit field values */ + +/** @def BT_GATT_CHRC_BROADCAST + * @brief Characteristic broadcast property. + * + * If set, permits broadcasts of the Characteristic Value using Server + * Characteristic Configuration Descriptor. + */ +#define BT_GATT_CHRC_BROADCAST 0x01 +/** @def BT_GATT_CHRC_READ + * @brief Characteristic read property. + * + * If set, permits reads of the Characteristic Value. + */ +#define BT_GATT_CHRC_READ 0x02 +/** @def BT_GATT_CHRC_WRITE_WITHOUT_RESP + * @brief Characteristic write without response property. + * + * If set, permits write of the Characteristic Value without response. + */ +#define BT_GATT_CHRC_WRITE_WITHOUT_RESP 0x04 +/** @def BT_GATT_CHRC_WRITE + * @brief Characteristic write with response property. + * + * If set, permits write of the Characteristic Value with response. + */ +#define BT_GATT_CHRC_WRITE 0x08 +/** @def BT_GATT_CHRC_NOTIFY + * @brief Characteristic notify property. + * + * If set, permits notifications of a Characteristic Value without + * acknowledgment. + */ +#define BT_GATT_CHRC_NOTIFY 0x10 +/** @def BT_GATT_CHRC_INDICATE + * @brief Characteristic indicate property. + * + * If set, permits indications of a Characteristic Value with acknowledgment. + */ +#define BT_GATT_CHRC_INDICATE 0x20 +/** @def BT_GATT_CHRC_AUTH + * @brief Characteristic Authenticated Signed Writes property. + * + * If set, permits signed writes to the Characteristic Value. + */ +#define BT_GATT_CHRC_AUTH 0x40 +/** @def BT_GATT_CHRC_EXT_PROP + * @brief Characteristic Extended Properties property. + * + * If set, additional characteristic properties are defined in the + * Characteristic Extended Properties Descriptor. + */ +#define BT_GATT_CHRC_EXT_PROP 0x80 + +/** @brief Characteristic Attribute Value. */ +struct bt_gatt_chrc { + /** Characteristic UUID. */ + const struct bt_uuid *uuid; + /** Characteristic properties. */ + uint8_t properties; +}; + +/* Characteristic Extended Properties Bit field values */ +#define BT_GATT_CEP_RELIABLE_WRITE 0x0001 +#define BT_GATT_CEP_WRITABLE_AUX 0x0002 + +/** @brief Characteristic Extended Properties Attribute Value. */ +struct bt_gatt_cep { + /** Characteristic Extended properties */ + uint16_t properties; +}; + +/* Client Characteristic Configuration Values */ + +/** @def BT_GATT_CCC_NOTIFY + * @brief Client Characteristic Configuration Notification. + * + * If set, changes to Characteristic Value shall be notified. + */ +#define BT_GATT_CCC_NOTIFY 0x0001 +/** @def BT_GATT_CCC_INDICATE + * @brief Client Characteristic Configuration Indication. + * + * If set, changes to Characteristic Value shall be indicated. + */ +#define BT_GATT_CCC_INDICATE 0x0002 + +/* Client Characteristic Configuration Attribute Value */ +struct bt_gatt_ccc { + /** Client Characteristic Configuration flags */ + uint16_t flags; +}; + +/** @brief GATT Characteristic Presentation Format Attribute Value. */ +struct bt_gatt_cpf { + /** Format of the value of the characteristic */ + uint8_t format; + /** Exponent field to determine how the value of this characteristic is further formatted */ + int8_t exponent; + /** Unit of the characteristic */ + uint16_t unit; + /** Name space of the description */ + uint8_t name_space; + /** Description of the characteristic as defined in a higher layer profile */ + uint16_t description; +} __packed; + +/* Server API */ + +/** @brief Register attribute database. + * + * Register GATT attribute database table. Applications can make use of + * macros such as BT_GATT_PRIMARY_SERVICE, BT_GATT_CHARACTERISTIC, + * BT_GATT_DESCRIPTOR, etc. + * + * @param attrs Database table containing the available attributes. + * @param count Size of the database table. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_gatt_register(struct bt_gatt_attr *attrs, size_t count); + +enum { + BT_GATT_ITER_STOP = 0, + BT_GATT_ITER_CONTINUE, +}; + +/** @brief Attribute iterator callback. + * + * @param attr Attribute found. + * @param user_data Data given. + * + * @return BT_GATT_ITER_CONTINUE if should continue to the next attribute + * or BT_GATT_ITER_STOP to stop. + */ +typedef uint8_t (*bt_gatt_attr_func_t)(const struct bt_gatt_attr *attr, + void *user_data); + +/** @brief Attribute iterator. + * + * Iterate attributes in the given range. + * + * @param start_handle Start handle. + * @param end_handle End handle. + * @param func Callback function. + * @param user_data Data to pass to the callback. + */ +void bt_gatt_foreach_attr(uint16_t start_handle, uint16_t end_handle, + bt_gatt_attr_func_t func, void *user_data); + +/** @brief Iterate to the next attribute + * + * Iterate to the next attribute following a given attribute. + * + * @param attr Current Attribute. + * + * @return The next attribute or NULL if it cannot be found. + */ +struct bt_gatt_attr *bt_gatt_attr_next(const struct bt_gatt_attr *attr); + +/** @brief Generic Read Attribute value helper. + * + * Read attribute value storing the result into buffer. + * + * @param conn Connection object. + * @param attr Attribute to read. + * @param buf Buffer to store the value. + * @param buf_len Buffer length. + * @param offset Start offset. + * @param value Attribute value. + * @param value_len Length of the attribute value. + * + * @return int number of bytes read in case of success or negative values in + * case of error. + */ +ssize_t bt_gatt_attr_read(struct bt_conn *conn, const struct bt_gatt_attr *attr, + void *buf, uint16_t buf_len, uint16_t offset, + const void *value, uint16_t value_len); + +/** @brief Read Service Attribute helper. + * + * Read service attribute value storing the result into buffer after + * enconding it. + * NOTE: Only use this with attributes which user_data is a bt_uuid. + * + * @param conn Connection object. + * @param attr Attribute to read. + * @param buf Buffer to store the value read. + * @param len Buffer length. + * @param offset Start offset. + * + * @return int number of bytes read in case of success or negative values in + * case of error. + */ +ssize_t bt_gatt_attr_read_service(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + void *buf, uint16_t len, uint16_t offset); + +/** @def BT_GATT_SERVICE + * @brief Generic Service Declaration Macro. + * + * Helper macro to declare a service attribute. + * + * @param _uuid Service attribute type. + * @param _data Service attribute value. + */ +#define BT_GATT_SERVICE(_uuid, _service) \ +{ \ + .uuid = _uuid, \ + .perm = BT_GATT_PERM_READ, \ + .read = bt_gatt_attr_read_service, \ + .user_data = _service, \ +} + +/** @def BT_GATT_PRIMARY_SERVICE + * @brief Primary Service Declaration Macro. + * + * Helper macro to declare a primary service attribute. + * + * @param _service Service attribute value. + */ +#define BT_GATT_PRIMARY_SERVICE(_service) \ +{ \ + .uuid = BT_UUID_GATT_PRIMARY, \ + .perm = BT_GATT_PERM_READ, \ + .read = bt_gatt_attr_read_service, \ + .user_data = _service, \ +} + +/** @def BT_GATT_SECONDARY_SERVICE + * @brief Secondary Service Declaration Macro. + * + * Helper macro to declare a secondary service attribute. + * + * @param _service Service attribute value. + */ +#define BT_GATT_SECONDARY_SERVICE(_service) \ +{ \ + .uuid = BT_UUID_GATT_SECONDARY, \ + .perm = BT_GATT_PERM_READ, \ + .read = bt_gatt_attr_read_service, \ + .user_data = _service, \ +} + +/** @brief Read Include Attribute helper. + * + * Read include service attribute value storing the result into buffer after + * enconding it. + * NOTE: Only use this with attributes which user_data is a bt_gatt_include. + * + * @param conn Connection object. + * @param attr Attribute to read. + * @param buf Buffer to store the value read. + * @param len Buffer length. + * @param offset Start offset. + * + * @return int number of bytes read in case of success or negative values in + * case of error. + */ +ssize_t bt_gatt_attr_read_included(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + void *buf, uint16_t len, uint16_t offset); + +/** @def BT_GATT_INCLUDE_SERVICE + * @brief Include Service Declaration Macro. + * + * Helper macro to declare a include service attribute. + * + * @param _service Service attribute value. + */ +#define BT_GATT_INCLUDE_SERVICE(_service) \ +{ \ + .uuid = BT_UUID_GATT_INCLUDE, \ + .perm = BT_GATT_PERM_READ, \ + .read = bt_gatt_attr_read_included, \ + .user_data = _service, \ +} + +/** @brief Read Characteristic Attribute helper. + * + * Read characteristic attribute value storing the result into buffer after + * enconding it. + * NOTE: Only use this with attributes which user_data is a bt_gatt_chrc. + * + * @param conn Connection object. + * @param attr Attribute to read. + * @param buf Buffer to store the value read. + * @param len Buffer length. + * @param offset Start offset. + * + * @return number of bytes read in case of success or negative values in + * case of error. + */ +ssize_t bt_gatt_attr_read_chrc(struct bt_conn *conn, + const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset); + +/** @def BT_GATT_CHARACTERISTIC + * @brief Characteristic Declaration Macro. + * + * Helper macro to declare a characteristic attribute. + * + * @param _uuid Characteristic attribute uuid. + * @param _props Characteristic attribute properties. + */ +#define BT_GATT_CHARACTERISTIC(_uuid, _props) \ +{ \ + .uuid = BT_UUID_GATT_CHRC, \ + .perm = BT_GATT_PERM_READ, \ + .read = bt_gatt_attr_read_chrc, \ + .user_data = (&(struct bt_gatt_chrc) { .uuid = _uuid, \ + .properties = _props, }),\ +} + +/** @brief GATT CCC configuration entry. */ +struct bt_gatt_ccc_cfg { + /** Config peer address. */ + bt_addr_le_t peer; + /** Config peer value. */ + uint16_t value; + /** Config valid flag. */ + uint8_t valid; +}; + +/* Internal representation of CCC value */ +struct _bt_gatt_ccc { + struct bt_gatt_ccc_cfg *cfg; + size_t cfg_len; + uint16_t value; + void (*cfg_changed)(uint16_t value); +}; + +/** @brief Read Client Characteristic Configuration Attribute helper. + * + * Read CCC attribute value storing the result into buffer after + * enconding it. + * NOTE: Only use this with attributes which user_data is a _bt_gatt_ccc. + * + * @param conn Connection object. + * @param attr Attribute to read. + * @param buf Buffer to store the value read. + * @param len Buffer length. + * @param offset Start offset. + * + * @return number of bytes read in case of success or negative values in + * case of error. + */ +ssize_t bt_gatt_attr_read_ccc(struct bt_conn *conn, + const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset); + +/** @brief Write Client Characteristic Configuration Attribute helper. + * + * Write value in the buffer into CCC attribute. + * NOTE: Only use this with attributes which user_data is a _bt_gatt_ccc. + * + * @param conn Connection object. + * @param attr Attribute to read. + * @param buf Buffer to store the value read. + * @param len Buffer length. + * @param offset Start offset. + * + * @return number of bytes written in case of success or negative values in + * case of error. + */ +ssize_t bt_gatt_attr_write_ccc(struct bt_conn *conn, + const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset); + +/** @def BT_GATT_CCC + * @brief Client Characteristic Configuration Declaration Macro. + * + * Helper macro to declare a CCC attribute. + * + * @param _cfg Initial configuration. + * @param _cfg_changed Configuration changed callback. + */ +#define BT_GATT_CCC(_cfg, _cfg_changed) \ +{ \ + .uuid = BT_UUID_GATT_CCC, \ + .perm = BT_GATT_PERM_READ | BT_GATT_PERM_WRITE, \ + .read = bt_gatt_attr_read_ccc, \ + .write = bt_gatt_attr_write_ccc, \ + .user_data = (&(struct _bt_gatt_ccc) { .cfg = _cfg, \ + .cfg_len = ARRAY_SIZE(_cfg), \ + .cfg_changed = _cfg_changed, }),\ +} + +/** @brief Read Characteristic Extended Properties Attribute helper + * + * Read CEP attribute value storing the result into buffer after + * encoding it. + * NOTE: Only use this with attributes which user_data is a bt_gatt_cep. + * + * @param conn Connection object + * @param attr Attribute to read + * @param buf Buffer to store the value read + * @param len Buffer length + * @param offset Start offset + * + * @return number of bytes read in case of success or negative values in + * case of error. + */ +ssize_t bt_gatt_attr_read_cep(struct bt_conn *conn, + const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset); + +/** @def BT_GATT_CEP + * @brief Characteristic Extended Properties Declaration Macro. + * + * Helper macro to declare a CEP attribute. + * + * @param _value Descriptor attribute value. + */ +#define BT_GATT_CEP(_value) \ +{ \ + .uuid = BT_UUID_GATT_CEP, \ + .perm = BT_GATT_PERM_READ, \ + .read = bt_gatt_attr_read_cep, \ + .user_data = _value, \ +} + +/** @brief Read Characteristic User Description Descriptor Attribute helper + * + * Read CUD attribute value storing the result into buffer after + * encoding it. + * NOTE: Only use this with attributes which user_data is a NULL-terminated C string. + * + * @param conn Connection object + * @param attr Attribute to read + * @param buf Buffer to store the value read + * @param len Buffer length + * @param offset Start offset + * + * @return number of bytes read in case of success or negative values in + * case of error. + */ +ssize_t bt_gatt_attr_read_cud(struct bt_conn *conn, + const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset); + +/** @def BT_GATT_CUD + * @brief Characteristic User Format Descriptor Declaration Macro. + * + * Helper macro to declare a CUD attribute. + * + * @param _value User description NULL-terminated C string. + * @param _perm Descriptor attribute access permissions. + */ +#define BT_GATT_CUD(_value, _perm) \ +{ \ + .uuid = BT_UUID_GATT_CUD, \ + .perm = _perm, \ + .read = bt_gatt_attr_read_cud, \ + .user_data = _value, \ +} + +/** @brief Read Characteristic Presentation format Descriptor Attribute helper + * + * Read CPF attribute value storing the result into buffer after + * encoding it. + * NOTE: Only use this with attributes which user_data is a bt_gatt_pf. + * + * @param conn Connection object + * @param attr Attribute to read + * @param buf Buffer to store the value read + * @param len Buffer length + * @param offset Start offset + * + * @return number of bytes read in case of success or negative values in + * case of error. + */ +ssize_t bt_gatt_attr_read_cpf(struct bt_conn *conn, + const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset); + +/** @def BT_GATT_CPF + * @brief Characteristic Presentation Format Descriptor Declaration Macro. + * + * Helper macro to declare a CPF attribute. + * + * @param _value Descriptor attribute value. + */ +#define BT_GATT_CPF(_value) \ +{ \ + .uuid = BT_UUID_GATT_CPF, \ + .perm = BT_GATT_PERM_READ, \ + .read = bt_gatt_attr_read_cpf, \ + .user_data = _value, \ +} + +/** @def BT_GATT_DESCRIPTOR + * @brief Descriptor Declaration Macro. + * + * Helper macro to declare a descriptor attribute. + * + * @param _uuid Descriptor attribute uuid. + * @param _perm Descriptor attribute access permissions. + * @param _read Descriptor attribute read callback. + * @param _write Descriptor attribute write callback. + * @param _value Descriptor attribute value. + */ +#define BT_GATT_DESCRIPTOR(_uuid, _perm, _read, _write, _value) \ +{ \ + .uuid = _uuid, \ + .perm = _perm, \ + .read = _read, \ + .write = _write, \ + .user_data = _value, \ +} + +/** @def BT_GATT_LONG_DESCRIPTOR + * @brief Descriptor Declaration Macro. + * + * Helper macro to declare a descriptor attribute. + * + * @param _uuid Descriptor attribute uuid. + * @param _perm Descriptor attribute access permissions. + * @param _read Descriptor attribute read callback. + * @param _write Descriptor attribute write callback. + * @param _flush Descriptor attribute flush callback. + * @param _value Descriptor attribute value. + */ +#define BT_GATT_LONG_DESCRIPTOR(_uuid, _perm, _read, _write, _flush, _value) \ +{ \ + .uuid = _uuid, \ + .perm = _perm, \ + .read = _read, \ + .write = _write, \ + .flush = _flush, \ + .user_data = _value, \ +} + +/** @brief Notify sent callback + * + * This means that the complete attribute has been sent. This does not mean it + * has been received however (use indicate for this). + * This shall be used to flow control the callee to avoid flooding the ble + * controller. + * + * @param conn Connection object. + * @param attr Attribute object. + * @param err 0 if none + */ +typedef void (*bt_gatt_notify_sent_func_t)(struct bt_conn *conn, struct bt_gatt_attr *attr, + uint8_t err); + +/** @brief Notify attribute value change. + * + * Send notification of attribute value change, if connection is NULL notify + * all peer that have notification enabled via CCC otherwise do a direct + * notification only the given connection. + * + * @param conn Connection object. + * @param attr Attribute object. + * @param value Attribute value. + * @param len Attribute value length. + * @param cb callback function called when send is complete (or NULL) + */ +int bt_gatt_notify(struct bt_conn *conn, const struct bt_gatt_attr *attr, + const void *data, uint16_t len, + bt_gatt_notify_sent_func_t cb); + +/** @brief Indication complete result callback. + * + * @param conn Connection object. + * @param attr Attribute object. + * @param err: 0 success, error in the other case + */ +typedef void (*bt_gatt_indicate_func_t)(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + int err); + +/** @brief GATT Indicate Value parameters */ +struct bt_gatt_indicate_params { + /** Indicate Attribute object*/ + const struct bt_gatt_attr *attr; + /** Indicate Value callback */ + bt_gatt_indicate_func_t func; + /** Indicate Value data*/ + const void *data; + /** Indicate Value length*/ + uint16_t len; +}; + +/** @brief Indicate attribute value change. + * + * Send an indication of attribute value change. + * Note: This function should only be called if CCC is declared with + * BT_GATT_CCC otherwise it cannot find a valid peer configuration. + * + * Note: This procedure is asynchronous therefore the parameters need to + * remains valid while it is active. + * + * @param conn Connection object. + * @param params Indicate parameters. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_gatt_indicate(struct bt_conn *conn, + struct bt_gatt_indicate_params *params); + +#if defined(CONFIG_BLUETOOTH_GATT_CLIENT) +/* Client API */ + +/** @brief Response callback function + * + * @param conn Connection object. + * @param err Error code. + */ +typedef void (*bt_gatt_rsp_func_t)(struct bt_conn *conn, uint8_t err); + +/** @brief Exchange MTU + * + * This client procedure can be used to set the MTU to the maximum possible + * size the buffers can hold. + * NOTE: Shall only be used once per connection. + * + * @param conn Connection object. + * @param func Exchange MTU Response callback function. + */ +int bt_gatt_exchange_mtu(struct bt_conn *conn, bt_gatt_rsp_func_t func); + +struct bt_gatt_discover_params; + +/** @brief Discover attribute callback function. + * + * @param conn Connection object. + * @param attr Attribute found. + * @param params Discovery parameters given. + * + * If discovery procedure has completed this callback will be called with + * attr set to NULL. This will not happen if procedure was stopped by returning + * BT_GATT_ITER_STOP. + * + * @return BT_GATT_ITER_CONTINUE if should continue attribute discovery + * or BT_GATT_ITER_STOP to stop discovery procedure. + */ +typedef uint8_t (*bt_gatt_discover_func_t)(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params); + +enum { + BT_GATT_DISCOVER_PRIMARY, + BT_GATT_DISCOVER_SECONDARY, + BT_GATT_DISCOVER_INCLUDE, + BT_GATT_DISCOVER_CHARACTERISTIC, + BT_GATT_DISCOVER_DESCRIPTOR, +}; + +/** @brief GATT Discover Attributes parameters */ +struct bt_gatt_discover_params { + /** Discover UUID type */ + struct bt_uuid *uuid; + /** Discover attribute callback */ + bt_gatt_discover_func_t func; + /** Discover start handle */ + uint16_t start_handle; + /** Discover end handle */ + uint16_t end_handle; + /** Discover type */ + uint8_t type; +}; + +/** @brief GATT Discover function + * + * This procedure is used by a client to discover attributes on a server. + * + * Primary Service Discovery: Procedure allows to discover specific Primary + * Service based on UUID. + * Include Service Discovery: Procedure allows to discover all Include Services + * within specified range. + * Characteristic Discovery: Procedure allows to discover all characteristics + * within specified handle range as well as + * discover characteristics with specified UUID. + * Descriptors Discovery: Procedure allows to discover all characteristic + * descriptors within specified range. + * + * For each attribute found the callback is called which can then decide + * whether to continue discovering or stop. + * + * Note: This procedure is asynchronous therefore the parameters need to + * remains valid while it is active. + * + * @param conn Connection object. + * @param params Discover parameters. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_gatt_discover(struct bt_conn *conn, + struct bt_gatt_discover_params *params); + +struct bt_gatt_read_params; + +/** @brief Read callback function + * + * @param conn Connection object. + * @param err Error code. + * @param params Read parameters used. + * @param data Attribute value data. NULL means read has completed. + * @param length Attribute value length. + */ +typedef uint8_t (*bt_gatt_read_func_t)(struct bt_conn *conn, int err, + struct bt_gatt_read_params *params, + const void *data, uint16_t length); + +/** @brief GATT Read parameters */ +struct bt_gatt_read_params { + /** Read attribute callback */ + bt_gatt_read_func_t func; + /** Handles count. + * If equals to 1 single.handle and single.offset are used. + * If >1 Read Multiple Characteristic Values is performed and handles + * are used. + */ + size_t handle_count; + union { + struct { + /** Attribute handle */ + uint16_t handle; + /** Attribute data offset */ + uint16_t offset; + } single; + /** Handles to read in Read Multiple Characteristic Values */ + uint16_t *handles; + }; +}; + +/** @brief Read Attribute Value by handle + * + * This procedure read the attribute value and return it to the callback. + * + * Note: This procedure is asynchronous therefore the parameters need to + * remains valid while it is active. + * + * @param conn Connection object. + * @param params Read parameters. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_gatt_read(struct bt_conn *conn, struct bt_gatt_read_params *params); + +/** @brief Write Response callback function + * + * @param conn Connection object. + * @param err Error code. + * @param data Data pointer in the write request. + */ +typedef void (*bt_gatt_write_rsp_func_t)(struct bt_conn *conn, uint8_t err, const void *data); + +/** @brief Write Attribute Value by handle + * + * This procedure write the attribute value and return the result in the + * callback. + * + * @param conn Connection object. + * @param handle Attribute handle. + * @param offset Attribute data offset. + * @param data Data to be written. + * @param length Data length. + * @param func Callback function. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_gatt_write(struct bt_conn *conn, uint16_t handle, uint16_t offset, + const void *data, uint16_t length, bt_gatt_write_rsp_func_t func); + +/** @brief Write Attribute Value by handle without response + * + * This procedure write the attribute value without requiring an + * acknowledgement that the write was successfully performed + * + * @param conn Connection object. + * @param handle Attribute handle. + * @param data Data to be written. + * @param length Data length. + * @param sign Whether to sign data + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_gatt_write_without_response(struct bt_conn *conn, uint16_t handle, + const void *data, uint16_t length, + bool sign); + +struct bt_gatt_subscribe_params; + +/** @brief Notification callback function + * + * @param conn Connection object. + * @param params Subscription parameters. + * @param data Attribute value data. If NULL then subscription was removed. + * @param length Attribute value length. + */ +typedef uint8_t (*bt_gatt_notify_func_t)(struct bt_conn *conn, + struct bt_gatt_subscribe_params *params, + const void *data, uint16_t length); + +/** @brief GATT Subscribe parameters */ +struct bt_gatt_subscribe_params { + bt_addr_le_t _peer; + /** Notification value callback */ + bt_gatt_notify_func_t notify; + /** Subscribe value handle */ + uint16_t value_handle; + /** Subscribe CCC handle */ + uint16_t ccc_handle; + /** Subscribe value */ + uint16_t value; + struct bt_gatt_subscribe_params *_next; +}; + +/** @brief Subscribe Attribute Value Notification + * + * This procedure subscribe to value notification using the Client + * Characteristic Configuration handle. + * If notification received subscribe value callback is called to return + * notified value. One may then decide whether to unsubscribe directly from + * this callback. Notification callback with NULL data will not be called if + * subscription was removed by this method. + * + * Note: This procedure is asynchronous therefore the parameters need to + * remains valid while it is active. + * + * @param conn Connection object. + * @param params Subscribe parameters. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_gatt_subscribe(struct bt_conn *conn, + struct bt_gatt_subscribe_params *params); + +/** @brief Unsubscribe Attribute Value Notification + * + * This procedure unsubscribe to value notification using the Client + * Characteristic Configuration handle. Notification callback with NULL data + * will not be called if subscription was removed by this call. + * + * @param conn Connection object. + * @param params Subscribe parameters. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_gatt_unsubscribe(struct bt_conn *conn, + struct bt_gatt_subscribe_params *params); + +/** @brief Cancel GATT pending request + * + * @param conn Connection object. + */ +void bt_gatt_cancel(struct bt_conn *conn); + +#endif /* CONFIG_BLUETOOTH_GATT_CLIENT */ +#endif /* CONFIG_BLUETOOTH_CENTRAL || CONFIG_BLUETOOTH_PERIPHERAL */ + +#ifdef __cplusplus +} +#endif + +#endif /* __BT_GATT_H */ diff --git a/system/libarc32_arduino101/drivers/bluetooth/hci.h b/system/libarc32_arduino101/drivers/bluetooth/hci.h new file mode 100644 index 00000000..f7ff52c1 --- /dev/null +++ b/system/libarc32_arduino101/drivers/bluetooth/hci.h @@ -0,0 +1,683 @@ +/* hci.h - Bluetooth Host Control Interface definitions */ + +/* + * Copyright (c) 2015 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __BT_HCI_H +#define __BT_HCI_H + +//#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define BT_ADDR_LE_PUBLIC 0x00 +#define BT_ADDR_LE_RANDOM 0x01 + +typedef struct { + uint8_t val[6]; +} bt_addr_t; + +typedef struct { + uint8_t type; + uint8_t val[6]; +} bt_addr_le_t; + +#define BT_ADDR_ANY (&(bt_addr_t) {{0, 0, 0, 0, 0, 0} }) +#define BT_ADDR_LE_ANY (&(bt_addr_le_t) { 0, {0, 0, 0, 0, 0, 0} }) + +static inline int bt_addr_cmp(const bt_addr_t *a, const bt_addr_t *b) +{ + return memcmp(a, b, sizeof(*a)); +} + +static inline int bt_addr_le_cmp(const bt_addr_le_t *a, const bt_addr_le_t *b) +{ + return memcmp(a, b, sizeof(*a)); +} + +static inline void bt_addr_copy(bt_addr_t *dst, const bt_addr_t *src) +{ + memcpy(dst, src, sizeof(*dst)); +} + +static inline void bt_addr_le_copy(bt_addr_le_t *dst, const bt_addr_le_t *src) +{ + memcpy(dst, src, sizeof(*dst)); +} + +/* HCI Error Codes */ +#define BT_HCI_ERR_UNKNOWN_CONN_ID 0x02 +#define BT_HCI_ERR_AUTHENTICATION_FAIL 0x05 +#define BT_HCI_ERR_INSUFFICIENT_RESOURCES 0x0d +#define BT_HCI_ERR_REMOTE_USER_TERM_CONN 0x13 +#define BT_HCI_ERR_PAIRING_NOT_ALLOWED 0x18 +#define BT_HCI_ERR_UNSUPP_REMOTE_FEATURE 0x1a +#define BT_HCI_ERR_INVALID_LL_PARAMS 0x1e +#define BT_HCI_ERR_UNSPECIFIED 0x1f +#define BT_HCI_ERR_PAIRING_NOT_SUPPORTED 0x29 +#define BT_HCI_ERR_UNACCEPT_CONN_PARAMS 0x3b +#define BT_HCI_ERR_DIRECTED_ADV_TIMEOUT 0x3c + +/* EIR/AD data type definitions */ +#define BT_DATA_FLAGS 0x01 /* AD flags */ +#define BT_DATA_UUID16_SOME 0x02 /* 16-bit UUID, more available */ +#define BT_DATA_UUID16_ALL 0x03 /* 16-bit UUID, all listed */ +#define BT_DATA_UUID32_SOME 0x04 /* 32-bit UUID, more available */ +#define BT_DATA_UUID32_ALL 0x05 /* 32-bit UUID, all listed */ +#define BT_DATA_UUID128_SOME 0x06 /* 128-bit UUID, more available */ +#define BT_DATA_UUID128_ALL 0x07 /* 128-bit UUID, all listed */ +#define BT_DATA_NAME_SHORTENED 0x08 /* Shortened name */ +#define BT_DATA_NAME_COMPLETE 0x09 /* Complete name */ +#define BT_DATA_TX_POWER 0x0a /* Tx Power */ +#define BT_DATA_SOLICIT16 0x14 /* Solicit UUIDs, 16-bit */ +#define BT_DATA_SOLICIT128 0x15 /* Solicit UUIDs, 128-bit */ +#define BT_DATA_SVC_DATA16 0x16 /* Service data, 16-bit UUID */ +#define BT_DATA_GAP_APPEARANCE 0x19 /* GAP appearance */ +#define BT_DATA_SOLICIT32 0x1f /* Solicit UUIDs, 32-bit */ +#define BT_DATA_SVC_DATA32 0x20 /* Service data, 32-bit UUID */ +#define BT_DATA_SVC_DATA128 0x21 /* Service data, 128-bit UUID */ +#define BT_DATA_MANUFACTURER_DATA 0xff /* Manufacturer Specific Data */ + +#define BT_LE_AD_LIMITED 0x01 /* Limited Discoverable */ +#define BT_LE_AD_GENERAL 0x02 /* General Discoverable */ +#define BT_LE_AD_NO_BREDR 0x04 /* BR/EDR not supported */ + +struct bt_hci_evt_hdr { + uint8_t evt; + uint8_t len; +} __packed; + +#define BT_ACL_START_NO_FLUSH 0x00 +#define BT_ACL_CONT 0x01 +#define BT_ACL_START 0x02 + +#define bt_acl_handle(h) ((h) & 0x0fff) +#define bt_acl_flags(h) ((h) >> 12) +#define bt_acl_handle_pack(h, f) ((h) | ((f) << 12)) + +struct bt_hci_acl_hdr { + uint16_t handle; + uint16_t len; +} __packed; + +struct bt_hci_cmd_hdr { + uint16_t opcode; + uint8_t param_len; +} __packed; + +/* LMP features */ +#define BT_LMP_NO_BREDR 0x20 +#define BT_LMP_LE 0x40 + +/* LE features */ +#define BT_HCI_LE_ENCRYPTION 0x01 +#define BT_HCI_LE_CONN_PARAM_REQ_PROC 0x02 +#define BT_HCI_LE_SLAVE_FEATURES 0x08 + +/* Bonding/authentication types */ +#define BT_HCI_NO_BONDING 0x00 +#define BT_HCI_NO_BONDING_MITM 0x01 +#define BT_HCI_DEDICATED_BONDING 0x02 +#define BT_HCI_DEDICATED_BONDING_MITM 0x03 +#define BT_HCI_GENERAL_BONDING 0x04 +#define BT_HCI_GENERAL_BONDING_MITM 0x05 + +/* I/O capabilities */ +#define BT_IO_DISPLAY_ONLY 0x00 +#define BT_IO_DISPLAY_YESNO 0x01 +#define BT_IO_KEYBOARD_ONLY 0x02 +#define BT_IO_NO_INPUT_OUTPUT 0x03 + +/* Defined GAP timers */ +#define BT_GAP_SCAN_FAST_INTERVAL 0x0060 /* 60 ms */ +#define BT_GAP_SCAN_FAST_WINDOW 0x0030 /* 30 ms */ +#define BT_GAP_SCAN_SLOW_INTERVAL_1 0x0800 /* 1.28 s */ +#define BT_GAP_SCAN_SLOW_WINDOW_1 0x0012 /* 11.25 ms */ +#define BT_GAP_SCAN_SLOW_INTERVAL_2 0x1000 /* 2.56 s */ +#define BT_GAP_SCAN_SLOW_WINDOW_2 0x0012 /* 11.25 ms */ +#define BT_GAP_ADV_FAST_INT_MIN_1 0x0030 /* 30 ms */ +#define BT_GAP_ADV_FAST_INT_MAX_1 0x0060 /* 60 ms */ +#define BT_GAP_ADV_FAST_INT_MIN_2 0x00a0 /* 100 ms */ +#define BT_GAP_ADV_FAST_INT_MAX_2 0x00f0 /* 150 ms */ +#define BT_GAP_ADV_SLOW_INT_MIN 0x0640 /* 1 s */ +#define BT_GAP_ADV_SLOW_INT_MAX 0x0780 /* 1.2 s */ +#define BT_GAP_INIT_CONN_INT_MIN 0x0018 /* 30 ms */ +#define BT_GAP_INIT_CONN_INT_MAX 0x0028 /* 50 ms */ + +/* HCI BR/EDR link types */ +#define BT_HCI_SCO 0x00 +#define BT_HCI_ACL 0x01 +#define BT_HCI_ESCO 0x02 + +/* OpCode Group Fields */ +#define BT_OGF_LINK_CTRL 0x01 +#define BT_OGF_BASEBAND 0x03 +#define BT_OGF_INFO 0x04 +#define BT_OGF_LE 0x08 + +/* Construct OpCode from OGF and OCF */ +#define BT_OP(ogf, ocf) ((ocf) | ((ogf) << 10)) + +#define BT_HCI_OP_DISCONNECT BT_OP(BT_OGF_LINK_CTRL, 0x0006) +struct bt_hci_cp_disconnect { + uint16_t handle; + uint8_t reason; +} __packed; + +#define BT_HCI_OP_ACCEPT_CONN_REQ BT_OP(BT_OGF_LINK_CTRL, 0x0009) +struct bt_hci_cp_accept_conn_req { + bt_addr_t bdaddr; + uint8_t role; +} __packed; + +#define BT_HCI_OP_REJECT_CONN_REQ BT_OP(BT_OGF_LINK_CTRL, 0x000a) +struct bt_hci_cp_reject_conn_req { + bt_addr_t bdaddr; + uint8_t reason; +} __packed; + +#define BT_HCI_OP_LINK_KEY_REPLY BT_OP(BT_OGF_LINK_CTRL, 0x000b) +struct bt_hci_cp_link_key_reply { + bt_addr_t bdaddr; + uint8_t link_key[16]; +} __packed; + +#define BT_HCI_OP_LINK_KEY_NEG_REPLY BT_OP(BT_OGF_LINK_CTRL, 0x000c) +struct bt_hci_cp_link_key_neg_reply { + bt_addr_t bdaddr; +} __packed; + +#define BT_HCI_OP_PIN_CODE_REPLY BT_OP(BT_OGF_LINK_CTRL, 0x000d) +struct bt_hci_cp_pin_code_reply { + bt_addr_t bdaddr; + uint8_t pin_len; + uint8_t pin_code[16]; +} __packed; +struct bt_hci_rp_pin_code_reply { + uint8_t status; + bt_addr_t bdaddr; +} __packed; + +#define BT_HCI_OP_PIN_CODE_NEG_REPLY BT_OP(BT_OGF_LINK_CTRL, 0x000e) +struct bt_hci_cp_pin_code_neg_reply { + bt_addr_t bdaddr; +} __packed; +struct bt_hci_rp_pin_code_neg_reply { + uint8_t status; + bt_addr_t bdaddr; +} __packed; + +#define BT_HCI_OP_IO_CAPABILITY_REPLY BT_OP(BT_OGF_LINK_CTRL, 0x002b) +struct bt_hci_cp_io_capability_reply { + bt_addr_t bdaddr; + uint8_t capability; + uint8_t oob_data; + uint8_t authentication; +} __packed; + +#define BT_HCI_OP_IO_CAPABILITY_NEG_REPLY BT_OP(BT_OGF_LINK_CTRL, 0x0034) +struct bt_hci_cp_io_capability_neg_reply { + bt_addr_t bdaddr; + uint8_t reason; +} __packed; + +#define BT_HCI_OP_SET_EVENT_MASK BT_OP(BT_OGF_BASEBAND, 0x0001) +struct bt_hci_cp_set_event_mask { + uint8_t events[8]; +} __packed; + +#define BT_HCI_OP_RESET BT_OP(BT_OGF_BASEBAND, 0x0003) + +#define BT_HCI_OP_WRITE_SCAN_ENABLE BT_OP(BT_OGF_BASEBAND, 0x001a) +#define BT_BREDR_SCAN_DISABLED 0x00 +#define BT_BREDR_SCAN_INQUIRY 0x01 +#define BT_BREDR_SCAN_PAGE 0x02 + +#define BT_HCI_CTL_TO_HOST_FLOW_ENABLE 0x01 +#define BT_HCI_OP_SET_CTL_TO_HOST_FLOW BT_OP(BT_OGF_BASEBAND, 0x0031) + +#define BT_HCI_OP_HOST_BUFFER_SIZE BT_OP(BT_OGF_BASEBAND, 0x0033) +struct bt_hci_cp_host_buffer_size { + uint16_t acl_mtu; + uint8_t sco_mtu; + uint16_t acl_pkts; + uint16_t sco_pkts; +} __packed; + +struct bt_hci_handle_count { + uint16_t handle; + uint16_t count; +} __packed; + +#define BT_HCI_OP_HOST_NUM_COMPLETED_PACKETS BT_OP(BT_OGF_BASEBAND, 0x0035) +struct bt_hci_cp_host_num_completed_packets { + uint8_t num_handles; + struct bt_hci_handle_count h[0]; +} __packed; + +#define BT_HCI_OP_WRITE_SSP_MODE BT_OP(BT_OGF_BASEBAND, 0x0056) +struct bt_hci_cp_write_ssp_mode { + uint8_t mode; +} __packed; + +#define BT_HCI_OP_LE_WRITE_LE_HOST_SUPP BT_OP(BT_OGF_BASEBAND, 0x006d) +struct bt_hci_cp_write_le_host_supp { + uint8_t le; + uint8_t simul; +} __packed; + +#define BT_HCI_OP_READ_LOCAL_VERSION_INFO BT_OP(BT_OGF_INFO, 0x0001) +struct bt_hci_rp_read_local_version_info { + uint8_t status; + uint8_t hci_version; + uint16_t hci_revision; + uint8_t lmp_version; + uint16_t manufacturer; + uint16_t lmp_subversion; +} __packed; + +#define BT_HCI_OP_READ_SUPPORTED_COMMANDS BT_OP(BT_OGF_INFO, 0x0002) +struct bt_hci_rp_read_supported_commands { + uint8_t status; + uint8_t commands[36]; +} __packed; + +#define BT_HCI_OP_READ_LOCAL_FEATURES BT_OP(BT_OGF_INFO, 0x0003) +struct bt_hci_rp_read_local_features { + uint8_t status; + uint8_t features[8]; +} __packed; + +#define BT_HCI_OP_READ_BUFFER_SIZE BT_OP(BT_OGF_INFO, 0x0005) +struct bt_hci_rp_read_buffer_size { + uint8_t status; + uint16_t acl_max_len; + uint8_t sco_max_len; + uint16_t acl_max_num; + uint16_t sco_max_num; +} __packed; + +#define BT_HCI_OP_READ_BD_ADDR BT_OP(BT_OGF_INFO, 0x0009) +struct bt_hci_rp_read_bd_addr { + uint8_t status; + bt_addr_t bdaddr; +} __packed; + +#define BT_HCI_OP_LE_SET_EVENT_MASK BT_OP(BT_OGF_LE, 0x0001) +struct bt_hci_cp_le_set_event_mask { + uint8_t events[8]; +} __packed; +struct bt_hci_rp_le_set_event_mask { + uint8_t status; +} __packed; + +#define BT_HCI_OP_LE_READ_BUFFER_SIZE BT_OP(BT_OGF_LE, 0x0002) +struct bt_hci_rp_le_read_buffer_size { + uint8_t status; + uint16_t le_max_len; + uint8_t le_max_num; +} __packed; + +#define BT_HCI_OP_LE_READ_LOCAL_FEATURES BT_OP(BT_OGF_LE, 0x0003) +struct bt_hci_rp_le_read_local_features { + uint8_t status; + uint8_t features[8]; +} __packed; + +#define BT_HCI_OP_LE_SET_RANDOM_ADDRESS BT_OP(BT_OGF_LE, 0x0005) + +/* Advertising types */ +#define BT_LE_ADV_IND 0x00 +#define BT_LE_ADV_DIRECT_IND 0x01 +#define BT_LE_ADV_SCAN_IND 0x02 +#define BT_LE_ADV_NONCONN_IND 0x03 +#define BT_LE_ADV_DIRECT_IND_LOW_DUTY 0x04 +/* Needed in advertising reports when getting info about */ +#define BT_LE_ADV_SCAN_RSP 0x04 + +#define BT_HCI_OP_LE_SET_ADV_PARAMETERS BT_OP(BT_OGF_LE, 0x0006) +struct bt_hci_cp_le_set_adv_parameters { + uint16_t min_interval; + uint16_t max_interval; + uint8_t type; + uint8_t own_addr_type; + bt_addr_le_t direct_addr; + uint8_t channel_map; + uint8_t filter_policy; +} __packed; + +#define BT_HCI_OP_LE_SET_ADV_DATA BT_OP(BT_OGF_LE, 0x0008) +struct bt_hci_cp_le_set_adv_data { + uint8_t len; + uint8_t data[31]; +} __packed; + +#define BT_HCI_OP_LE_SET_SCAN_RSP_DATA BT_OP(BT_OGF_LE, 0x0009) +struct bt_hci_cp_le_set_scan_rsp_data { + uint8_t len; + uint8_t data[31]; +} __packed; + +#define BT_HCI_LE_ADV_DISABLE 0x00 +#define BT_HCI_LE_ADV_ENABLE 0x01 + +#define BT_HCI_OP_LE_SET_ADV_ENABLE BT_OP(BT_OGF_LE, 0x000a) +struct bt_hci_cp_le_set_adv_enable { + uint8_t enable; +} __packed; + +/* Scan types */ +#define BT_HCI_OP_LE_SET_SCAN_PARAMS BT_OP(BT_OGF_LE, 0x000b) +#define BT_HCI_LE_SCAN_PASSIVE 0x00 +#define BT_HCI_LE_SCAN_ACTIVE 0x01 + +struct bt_hci_cp_le_set_scan_params { + uint8_t scan_type; + uint16_t interval; + uint16_t window; + uint8_t addr_type; + uint8_t filter_policy; +} __packed; + +#define BT_HCI_OP_LE_SET_SCAN_ENABLE BT_OP(BT_OGF_LE, 0x000c) + +#define BT_HCI_LE_SCAN_DISABLE 0x00 +#define BT_HCI_LE_SCAN_ENABLE 0x01 + +#define BT_HCI_LE_SCAN_FILTER_DUP_DISABLE 0x00 +#define BT_HCI_LE_SCAN_FILTER_DUP_ENABLE 0x01 + +struct bt_hci_cp_le_set_scan_enable { + uint8_t enable; + uint8_t filter_dup; +} __packed; + +#define BT_HCI_OP_LE_CREATE_CONN BT_OP(BT_OGF_LE, 0x000d) +struct bt_hci_cp_le_create_conn { + uint16_t scan_interval; + uint16_t scan_window; + uint8_t filter_policy; + bt_addr_le_t peer_addr; + uint8_t own_addr_type; + uint16_t conn_interval_min; + uint16_t conn_interval_max; + uint16_t conn_latency; + uint16_t supervision_timeout; + uint16_t min_ce_len; + uint16_t max_ce_len; +} __packed; + +#define BT_HCI_OP_LE_CREATE_CONN_CANCEL BT_OP(BT_OGF_LE, 0x000e) + +#define BT_HCI_OP_LE_CONN_UPDATE BT_OP(BT_OGF_LE, 0x0013) +struct hci_cp_le_conn_update { + uint16_t handle; + uint16_t conn_interval_min; + uint16_t conn_interval_max; + uint16_t conn_latency; + uint16_t supervision_timeout; + uint16_t min_ce_len; + uint16_t max_ce_len; +} __packed; + +#define BT_HCI_OP_LE_READ_REMOTE_FEATURES BT_OP(BT_OGF_LE, 0x0016) +struct bt_hci_cp_le_read_remote_features { + uint16_t handle; +} __packed; + +#define BT_HCI_OP_LE_ENCRYPT BT_OP(BT_OGF_LE, 0x0017) +struct bt_hci_cp_le_encrypt { + uint8_t key[16]; + uint8_t plaintext[16]; +} __packed; +struct bt_hci_rp_le_encrypt { + uint8_t status; + uint8_t enc_data[16]; +} __packed; + +#define BT_HCI_OP_LE_RAND BT_OP(BT_OGF_LE, 0x0018) +struct bt_hci_rp_le_rand { + uint8_t status; + uint8_t rand[8]; +} __packed; + +#define BT_HCI_OP_LE_START_ENCRYPTION BT_OP(BT_OGF_LE, 0x0019) +struct bt_hci_cp_le_start_encryption { + uint16_t handle; + uint64_t rand; + uint16_t ediv; + uint8_t ltk[16]; +} __packed; + +#define BT_HCI_OP_LE_LTK_REQ_REPLY BT_OP(BT_OGF_LE, 0x001a) +struct bt_hci_cp_le_ltk_req_reply { + uint16_t handle; + uint8_t ltk[16]; +} __packed; + +#define BT_HCI_OP_LE_LTK_REQ_NEG_REPLY BT_OP(BT_OGF_LE, 0x001b) +struct bt_hci_cp_le_ltk_req_neg_reply { + uint16_t handle; +} __packed; + +#define BT_HCI_OP_LE_CONN_PARAM_REQ_REPLY BT_OP(BT_OGF_LE, 0x0020) +struct bt_hci_cp_le_conn_param_req_reply { + uint16_t handle; + uint16_t interval_min; + uint16_t interval_max; + uint16_t latency; + uint16_t timeout; + uint16_t min_ce_len; + uint16_t max_ce_len; +} __packed; + +#define BT_HCI_OP_LE_CONN_PARAM_REQ_NEG_REPLY BT_OP(BT_OGF_LE, 0x0021) +struct bt_hci_cp_le_conn_param_req_neg_reply { + uint16_t handle; + uint8_t reason; +} __packed; + +#define BT_HCI_OP_LE_P256_PUBLIC_KEY BT_OP(BT_OGF_LE, 0x0025) + +#define BT_HCI_OP_LE_GENERATE_DHKEY BT_OP(BT_OGF_LE, 0x0026) +struct bt_hci_cp_le_generate_dhkey { + uint8_t key[64]; +} __packed; + +/* Event definitions */ + +#define BT_HCI_EVT_CONN_COMPLETE 0x03 +struct bt_hci_evt_conn_complete { + uint8_t status; + uint16_t handle; + bt_addr_t bdaddr; + uint8_t link_type; + uint8_t encr_enabled; +} __packed; + +#define BT_HCI_EVT_CONN_REQUEST 0x04 +struct bt_hci_evt_conn_request { + bt_addr_t bdaddr; + uint8_t dev_class[3]; + uint8_t link_type; +} __packed; + +#define BT_HCI_EVT_DISCONN_COMPLETE 0x05 +struct bt_hci_evt_disconn_complete { + uint8_t status; + uint16_t handle; + uint8_t reason; +} __packed; + +#define BT_HCI_EVT_ENCRYPT_CHANGE 0x08 +struct bt_hci_evt_encrypt_change { + uint8_t status; + uint16_t handle; + uint8_t encrypt; +} __packed; + +#define BT_HCI_EVT_CMD_COMPLETE 0x0e +struct hci_evt_cmd_complete { + uint8_t ncmd; + uint16_t opcode; +} __packed; + +#define BT_HCI_EVT_CMD_STATUS 0x0f +struct bt_hci_evt_cmd_status { + uint8_t status; + uint8_t ncmd; + uint16_t opcode; +} __packed; + +#define BT_HCI_EVT_NUM_COMPLETED_PACKETS 0x13 +struct bt_hci_evt_num_completed_packets { + uint8_t num_handles; + struct bt_hci_handle_count h[0]; +} __packed; + +#define BT_HCI_EVT_PIN_CODE_REQ 0x16 +struct bt_hci_evt_pin_code_req { + bt_addr_t bdaddr; +} __packed; + +#define BT_HCI_EVT_LINK_KEY_REQ 0x17 +struct bt_hci_evt_link_key_req { + bt_addr_t bdaddr; +} __packed; + +/* Link Key types */ +#define BT_LK_COMBINATION 0x00 +#define BT_LK_LOCAL_UNIT 0x01 +#define BT_LK_REMOTE_UNIT 0x02 +#define BT_LK_DEBUG_COMBINATION 0x03 +#define BT_LK_UNAUTH_COMBINATION_P192 0x04 +#define BT_LK_AUTH_COMBINATION_P192 0x05 +#define BT_LK_CHANGED_COMBINATION 0x06 +#define BT_LK_UNAUTH_COMBINATION_P256 0x07 +#define BT_LK_AUTH_COMBINATION_P256 0x08 + +#define BT_HCI_EVT_LINK_KEY_NOTIFY 0x18 +struct bt_hci_ev_link_key_notify { + bt_addr_t bdaddr; + uint8_t link_key[16]; + uint8_t key_type; +} __packed; + +#define BT_HCI_EVT_ENCRYPT_KEY_REFRESH_COMPLETE 0x30 +struct bt_hci_evt_encrypt_key_refresh_complete { + uint8_t status; + uint16_t handle; +} __packed; + +#define BT_HCI_EVT_IO_CAPA_REQ 0x31 +struct bt_hci_evt_io_capa_req { + bt_addr_t bdaddr; +} __packed; + +#define BT_HCI_EVT_IO_CAPA_RESP 0x32 +struct bt_hci_evt_io_capa_resp { + bt_addr_t bdaddr; + uint8_t capability; + uint8_t oob_data; + uint8_t authentication; +} __packed; + +#define BT_HCI_EVT_SSP_COMPLETE 0x36 +struct bt_hci_evt_ssp_complete { + uint8_t status; + bt_addr_t bdaddr; +} __packed; + +#define BT_HCI_EVT_LE_META_EVENT 0x3e +struct bt_hci_evt_le_meta_event { + uint8_t subevent; +} __packed; + +#define BT_HCI_ROLE_MASTER 0x00 +#define BT_HCI_ROLE_SLAVE 0x01 + +#define BT_HCI_EVT_LE_CONN_COMPLETE 0x01 +struct bt_hci_evt_le_conn_complete { + uint8_t status; + uint16_t handle; + uint8_t role; + bt_addr_le_t peer_addr; + uint16_t interval; + uint16_t latency; + uint16_t supv_timeout; + uint8_t clock_accuracy; +} __packed; + +#define BT_HCI_EVT_LE_ADVERTISING_REPORT 0x02 +struct bt_hci_ev_le_advertising_info { + uint8_t evt_type; + bt_addr_le_t addr; + uint8_t length; + uint8_t data[0]; +} __packed; + +#define BT_HCI_EVT_LE_CONN_UPDATE_COMPLETE 0x03 +struct bt_hci_evt_le_conn_update_complete { + uint8_t status; + uint16_t handle; + uint16_t interval; + uint16_t latency; + uint16_t supv_timeout; +} __packed; + +#define BT_HCI_EV_LE_REMOTE_FEAT_COMPLETE 0x04 +struct bt_hci_ev_le_remote_feat_complete { + uint8_t status; + uint16_t handle; + uint8_t features[8]; +} __packed; + +#define BT_HCI_EVT_LE_LTK_REQUEST 0x05 +struct bt_hci_evt_le_ltk_request { + uint16_t handle; + uint64_t rand; + uint16_t ediv; +} __packed; + +#define BT_HCI_EVT_LE_CONN_PARAM_REQ 0x06 +struct bt_hci_evt_le_conn_param_req { + uint16_t handle; + uint16_t interval_min; + uint16_t interval_max; + uint16_t latency; + uint16_t timeout; +} __packed; + +#define BT_HCI_EVT_LE_P256_PUBLIC_KEY_COMPLETE 0x08 +struct bt_hci_evt_le_p256_public_key_complete { + uint8_t status; + uint8_t key[64]; +} __packed; + +#define BT_HCI_EVT_LE_GENERATE_DHKEY_COMPLETE 0x09 +struct bt_hci_evt_le_generate_dhkey_complete { + uint8_t status; + uint8_t dhkey[32]; +} __packed; + +#ifdef __cplusplus +} +#endif + +#endif /* __BT_HCI_H */ diff --git a/system/libarc32_arduino101/drivers/bluetooth/uuid.h b/system/libarc32_arduino101/drivers/bluetooth/uuid.h new file mode 100644 index 00000000..a54108ba --- /dev/null +++ b/system/libarc32_arduino101/drivers/bluetooth/uuid.h @@ -0,0 +1,463 @@ +/** @file + * @brief Bluetooth UUID handling + */ + +/* + * Copyright (c) 2015 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __BT_UUID_H +#define __BT_UUID_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Bluetooth UUID types */ +enum { + BT_UUID_TYPE_16, + BT_UUID_TYPE_128, +}; + +/** @brief This is a 'tentative' type and should be used as a pointer only */ +struct bt_uuid { + uint8_t type; +}; + +struct bt_uuid_16 { + struct bt_uuid uuid; + uint16_t val; +}; + +struct bt_uuid_128 { + struct bt_uuid uuid; + uint8_t val[16]; +}; + +#define BT_UUID_INIT_16(value) \ +{ \ + .uuid.type = BT_UUID_TYPE_16, \ + .val = (value), \ +} + +#define BT_UUID_INIT_128(value...) \ +{ \ + .uuid.type = BT_UUID_TYPE_128, \ + .val = { value }, \ +} + +#define BT_UUID_DECLARE_16(value) \ + ((struct bt_uuid *) (&(struct bt_uuid_16) BT_UUID_INIT_16(value))) +#define BT_UUID_DECLARE_128(value...) \ + ((struct bt_uuid *) (&(struct bt_uuid_128) BT_UUID_INIT_128(value))) + +#define BT_UUID_16(__u) CONTAINER_OF(__u, struct bt_uuid_16, uuid) +#define BT_UUID_128(__u) CONTAINER_OF(__u, struct bt_uuid_128, uuid) + +/** @def BT_UUID_GAP + * @brief Generic Access + */ +#define BT_UUID_GAP BT_UUID_DECLARE_16(0x1800) +#define BT_UUID_GAP_VAL 0x1800 +/** @def BT_UUID_GATT + * @brief Generic Attribute + */ +#define BT_UUID_GATT BT_UUID_DECLARE_16(0x1801) +#define BT_UUID_GATT_VAL 0x1801 +/** @def BT_UUID_CTS + * @brief Current Time Service + */ +#define BT_UUID_CTS BT_UUID_DECLARE_16(0x1805) +#define BT_UUID_CTS_VAL 0x1805 +/** @def BT_UUID_DIS + * @brief Device Information Service + */ +#define BT_UUID_DIS BT_UUID_DECLARE_16(0x180a) +#define BT_UUID_DIS_VAL 0x180a +/** @def BT_UUID_HRS + * @brief Heart Rate Service + */ +#define BT_UUID_HRS BT_UUID_DECLARE_16(0x180d) +#define BT_UUID_HRS_VAL 0x180d +/** @def BT_UUID_BAS + * @brief Battery Service + */ +#define BT_UUID_BAS BT_UUID_DECLARE_16(0x180f) +#define BT_UUID_BAS_VAL 0x180f +/** @def BT_UUID_RSCS + * @brief Running Speed and Cadence Service + */ +#define BT_UUID_RSCS BT_UUID_DECLARE_16(0x1814) +#define BT_UUID_RSCS_VAL 0x1814 +/** @def BT_UUID_CSC + * @brief Cycling Speed and Cadence Service + */ +#define BT_UUID_CSC BT_UUID_DECLARE_16(0x1816) +#define BT_UUID_CSC_VAL 0x1816 +/** @def BT_UUID_ESS + * @brief Environmental Sensing Service + */ +#define BT_UUID_ESS BT_UUID_DECLARE_16(0x181a) +#define BT_UUID_ESS_VAL 0x181a +/** @def BT_UUID_IPSS + * @brief IP Support Service + */ +#define BT_UUID_IPSS BT_UUID_DECLARE_16(0x1820) +#define BT_UUID_IPSS_VAL 0x1820 +/** @def BT_UUID_LNS + * @brief Location and Navigation Support Service + */ +#define BT_UUID_LNS BT_UUID_DECLARE_16(0x1819) +#define BT_UUID_LNS_VAL 0x1819 +/** @def BT_UUID_GATT_PRIMARY + * @brief GATT Primary Service + */ +#define BT_UUID_GATT_PRIMARY BT_UUID_DECLARE_16(0x2800) +#define BT_UUID_GATT_PRIMARY_VAL 0x2800 +/** @def BT_UUID_GATT_SECONDARY + * @brief GATT Secondary Service + */ +#define BT_UUID_GATT_SECONDARY BT_UUID_DECLARE_16(0x2801) +#define BT_UUID_GATT_SECONDARY_VAL 0x2801 +/** @def BT_UUID_GATT_INCLUDE + * @brief GATT Include Service + */ +#define BT_UUID_GATT_INCLUDE BT_UUID_DECLARE_16(0x2802) +#define BT_UUID_GATT_INCLUDE_VAL 0x2802 +/** @def BT_UUID_GATT_CHRC + * @brief GATT Characteristic + */ +#define BT_UUID_GATT_CHRC BT_UUID_DECLARE_16(0x2803) +#define BT_UUID_GATT_CHRC_VAL 0x2803 +/** @def BT_UUID_GATT_CEP + * @brief GATT Characteristic Extended Properties + */ +#define BT_UUID_GATT_CEP BT_UUID_DECLARE_16(0x2900) +#define BT_UUID_GATT_CEP_VAL 0x2900 +/** @def BT_UUID_GATT_CUD + * @brief GATT Characteristic User Description + */ +#define BT_UUID_GATT_CUD BT_UUID_DECLARE_16(0x2901) +#define BT_UUID_GATT_CUD_VAL 0x2901 +/** @def BT_UUID_GATT_CCC + * @brief GATT Client Characteristic Configuration + */ +#define BT_UUID_GATT_CCC BT_UUID_DECLARE_16(0x2902) +#define BT_UUID_GATT_CCC_VAL 0x2902 +/** @def BT_UUID_GATT_SCC + * @brief GATT Server Characteristic Configuration + */ +#define BT_UUID_GATT_SCC BT_UUID_DECLARE_16(0x2903) +#define BT_UUID_GATT_SCC_VAL 0x2903 +/** @def BT_UUID_GATT_CPF + * @brief GATT Characteristic Presentation Format + */ +#define BT_UUID_GATT_CPF BT_UUID_DECLARE_16(0x2904) +#define BT_UUID_GATT_CPF_VAL 0x2904 +/** @def BT_UUID_VALID_RANGE + * @brief Valid Range Descriptor + */ +#define BT_UUID_VALID_RANGE BT_UUID_DECLARE_16(0x2906) +#define BT_UUID_VALID_RANGE_VAL 0x2906 +/** @def BT_UUID_ES_CONFIGURATION + * @brief Environmental Sensing Configuration Descriptor + */ +#define BT_UUID_ES_CONFIGURATION BT_UUID_DECLARE_16(0x290b) +#define BT_UUID_ES_CONFIGURATION_VAL 0x290b +/** @def BT_UUID_ES_MEASUREMENT + * @brief Environmental Sensing Measurement Descriptor + */ +#define BT_UUID_ES_MEASUREMENT BT_UUID_DECLARE_16(0x290c) +#define BT_UUID_ES_MEASUREMENT_VAL 0x290c +/** @def BT_UUID_ES_TRIGGER_SETTING + * @brief Environmental Sensing Trigger Setting Descriptor + */ +#define BT_UUID_ES_TRIGGER_SETTING BT_UUID_DECLARE_16(0x290d) +#define BT_UUID_ES_TRIGGER_SETTING_VAL 0x290d +/** @def BT_UUID_GAP_DEVICE_NAME + * @brief GAP Characteristic Device Name + */ +#define BT_UUID_GAP_DEVICE_NAME BT_UUID_DECLARE_16(0x2a00) +#define BT_UUID_GAP_DEVICE_NAME_VAL 0x2a00 +/** @def BT_UUID_GAP_APPEARANCE + * @brief GAP Characteristic Appearance + */ +#define BT_UUID_GAP_APPEARANCE BT_UUID_DECLARE_16(0x2a01) +#define BT_UUID_GAP_APPEARANCE_VAL 0x2a01 +/** @def BT_UUID_GAP_PPCP + * @brief GAP Characteristic Peripheral Preferred Connection Parameters + */ +#define BT_UUID_GAP_PPCP BT_UUID_DECLARE_16(0x2a04) +#define BT_UUID_GAP_PPCP_VAL 0x2a04 +/** @def BT_UUID_BAS_BATTERY_LEVEL + * @brief BAS Characteristic Battery Level + */ +#define BT_UUID_BAS_BATTERY_LEVEL BT_UUID_DECLARE_16(0x2a19) +#define BT_UUID_BAS_BATTERY_LEVEL_VAL 0x2a19 +/** @def BT_UUID_DIS_SYSTEM_ID + * @brief DIS Characteristic System ID + */ +#define BT_UUID_DIS_SYSTEM_ID BT_UUID_DECLARE_16(0x2a23) +#define BT_UUID_DIS_SYSTEM_ID_VAL 0x2a23 +/** @def BT_UUID_DIS_MODEL_NUMBER + * @brief DIS Characteristic Model Number String + */ +#define BT_UUID_DIS_MODEL_NUMBER BT_UUID_DECLARE_16(0x2a24) +#define BT_UUID_DIS_MODEL_NUMBER_VAL 0x2a24 +/** @def BT_UUID_DIS_SERIAL_NUMBER + * @brief DIS Characteristic Serial Number String + */ +#define BT_UUID_DIS_SERIAL_NUMBER BT_UUID_DECLARE_16(0x2a25) +#define BT_UUID_DIS_SERIAL_NUMBER_VAL 0x2a25 +/** @def BT_UUID_DIS_FIRMWARE_REVISION + * @brief DIS Characteristic Firmware Revision String + */ +#define BT_UUID_DIS_FIRMWARE_REVISION BT_UUID_DECLARE_16(0x2a26) +#define BT_UUID_DIS_FIRMWARE_REVISION_VAL 0x2a26 +/** @def BT_UUID_DIS_HARDWARE_REVISION + * @brief DIS Characteristic Hardware Revision String + */ +#define BT_UUID_DIS_HARDWARE_REVISION BT_UUID_DECLARE_16(0x2a27) +#define BT_UUID_DIS_HARDWARE_REVISION_VAL 0x2a27 +/** @def BT_UUID_DIS_SOFTWARE_REVISION + * @brief DIS Characteristic Software Revision String + */ +#define BT_UUID_DIS_SOFTWARE_REVISION BT_UUID_DECLARE_16(0x2a28) +#define BT_UUID_DIS_SOFTWARE_REVISION_VAL 0x2a28 +/** @def BT_UUID_DIS_MANUFACTURER_NAME + * @brief DIS Characteristic Manufacturer Name String + */ +#define BT_UUID_DIS_MANUFACTURER_NAME BT_UUID_DECLARE_16(0x2a29) +#define BT_UUID_DIS_MANUFACTURER_NAME_VAL 0x2a29 +/** @def BT_UUID_DIS_PNP_ID + * @brief DIS Characteristic PnP ID + */ +#define BT_UUID_DIS_PNP_ID BT_UUID_DECLARE_16(0x2a50) +#define BT_UUID_DIS_PNP_ID_VAL 0x2a50 +/** @def BT_UUID_RSC_MEASUREMENT + * @brief RSC Characteristic measurement ID + */ +#define BT_UUID_RSC_MEASUREMENT BT_UUID_DECLARE_16(0x2a53) +#define BT_UUID_RSC_MEASUREMENT_VAL 0x2a53 +/** @def BT_UUID_RSC_FEATURE + * @brief RSC Characteristic feature ID + */ +#define BT_UUID_RSC_FEATURE BT_UUID_DECLARE_16(0x2a54) +#define BT_UUID_RSC_FEATURE_VAL 0x2a54 +/** @def BT_UUID_CTS_CURRENT_TIME + * @brief CTS Characteristic Current Time + */ +#define BT_UUID_CTS_CURRENT_TIME BT_UUID_DECLARE_16(0x2a2b) +#define BT_UUID_CTS_CURRENT_TIME_VAL 0x2a2b +/** @def BT_UUID_MAGN_DECLINATION + * @brief Magnetic Declination Characteristic + */ +#define BT_UUID_MAGN_DECLINATION BT_UUID_DECLARE_16(0x2a2c) +#define BT_UUID_MAGN_DECLINATION_VAL 0x2a2c +/** @def BT_UUID_HRS_MEASUREMENT + * @brief HRS Characteristic Measurement Interval + */ +#define BT_UUID_HRS_MEASUREMENT BT_UUID_DECLARE_16(0x2a37) +#define BT_UUID_HRS_MEASUREMENT_VAL 0x2a37 +/** @def BT_UUID_HRS_BODY_SENSOR + * @brief HRS Characteristic Body Sensor Location + */ +#define BT_UUID_HRS_BODY_SENSOR BT_UUID_DECLARE_16(0x2a38) +#define BT_UUID_HRS_BODY_SENSOR_VAL 0x2a38 +/** @def BT_UUID_HRS_CONTROL_POINT + * @brief HRS Characteristic Control Point + */ +#define BT_UUID_HRS_CONTROL_POINT BT_UUID_DECLARE_16(0x2a39) +#define BT_UUID_HRS_CONTROL_POINT_VAL 0x2a39 +/** @def BT_UUID_CSC_MEASUREMENT + * @brief CSC Measurement Characteristic + */ +#define BT_UUID_CSC_MEASUREMENT BT_UUID_DECLARE_16(0x2a5b) +#define BT_UUID_CSC_MEASUREMENT_VAL 0x2a5b +/** @def BT_UUID_CSC_FEATURE + * @brief CSC Feature Characteristic + */ +#define BT_UUID_CSC_FEATURE BT_UUID_DECLARE_16(0x2a5c) +#define BT_UUID_CSC_FEATURE_VAL 0x2a5c +/** @def BT_UUID_SENSOR_LOCATION + * @brief Sensor Location Characteristic + */ +#define BT_UUID_SENSOR_LOCATION BT_UUID_DECLARE_16(0x2a5d) +#define BT_UUID_SENSOR_LOCATION_VAL 0x2a5d +/** @def BT_UUID_SC_CONTROL_POINT + * @brief SC Control Point Characteristic + */ +#define BT_UUID_SC_CONTROL_POINT BT_UUID_DECLARE_16(0x2a55) +#define BT_UUID_SC_CONTROL_POINT_VAl 0x2a55 +/** @def BT_UUID_LNS_CONTROL_POINT + * @brief LNS Control Point Characteristic + */ +#define BT_UUID_LNS_CONTROL_POINT BT_UUID_DECLARE_16(0x2a6B) +#define BT_UUID_LNS_CONTROL_POINT_VAL 0x2a6B +/** @def BT_UUID_LNS_LOCATION_SPEED + * @brief LNS Characteristic Location and Speed + */ +#define BT_UUID_LNS_LOCATION_SPEED BT_UUID_DECLARE_16(0x2a67) +#define BT_UUID_LNS_LOCATION_SPEED_VAL 0x2a67 +/** @def BT_UUID_LNS_FEATURE + * @brief LNS Characteristic Feature + */ +#define BT_UUID_LNS_FEATURE BT_UUID_DECLARE_16(0x2a6a) +#define BT_UUID_LNS_FEATURE_VAL 0x2a6a +/** @def BT_UUID_ELEVATION + * @brief Elevation Characteristic + */ +#define BT_UUID_ELEVATION BT_UUID_DECLARE_16(0x2a6c) +#define BT_UUID_ELEVATION_VAL 0x2a6c +/** @def BT_UUID_PRESSURE + * @brief Pressure Characteristic + */ +#define BT_UUID_PRESSURE BT_UUID_DECLARE_16(0x2a6d) +#define BT_UUID_PRESSURE_VAL 0x2a6d +/** @def BT_UUID_TEMPERATURE + * @brief Temperature Characteristic + */ +#define BT_UUID_TEMPERATURE BT_UUID_DECLARE_16(0x2a6e) +#define BT_UUID_TEMPERATURE_VAL 0x2a6e +/** @def BT_UUID_HUMIDITY + * @brief Humidity Characteristic + */ +#define BT_UUID_HUMIDITY BT_UUID_DECLARE_16(0x2a6f) +#define BT_UUID_HUMIDITY_VAL 0x2a6f +/** @def BT_UUID_TRUE_WIND_SPEED + * @brief True Wind Speed Characteristic + */ +#define BT_UUID_TRUE_WIND_SPEED BT_UUID_DECLARE_16(0x2a70) +#define BT_UUID_TRUE_WIND_SPEED_VAL 0x2a70 +/** @def BT_UUID_TRUE_WIND_DIR + * @brief True Wind Direction Characteristic + */ +#define BT_UUID_TRUE_WIND_DIR BT_UUID_DECLARE_16(0x2a71) +#define BT_UUID_TRUE_WIND_DIR_VAL 0x2a71 +/** @def BT_UUID_APPARENT_WIND_SPEED + * @brief Apparent Wind Speed Characteristic + */ +#define BT_UUID_APPARENT_WIND_SPEED BT_UUID_DECLARE_16(0x2a72) +#define BT_UUID_APPARENT_WIND_SPEED_VAL 0x2a72 +/** @def BT_UUID_APPARENT_WIND_DIR + * @brief Apparent Wind Direction Characteristic + */ +#define BT_UUID_APPARENT_WIND_DIR BT_UUID_DECLARE_16(0x2a73) +#define BT_UUID_APPARENT_WIND_DIR_VAL 0x2a73 +/** @def BT_UUID_GUST_FACTOR + * @brief Gust Factor Characteristic + */ +#define BT_UUID_GUST_FACTOR BT_UUID_DECLARE_16(0x2a74) +#define BT_UUID_GUST_FACTOR_VAL 0x2a74 +/** @def BT_UUID_POLLEN_CONCENTRATION + * @brief Pollen Concentration Characteristic + */ +#define BT_UUID_POLLEN_CONCENTRATION BT_UUID_DECLARE_16(0x2a75) +#define BT_UUID_POLLEN_CONCENTRATION_VAL 0x2a75 +/** @def BT_UUID_UV_INDEX + * @brief UV Index Characteristic + */ +#define BT_UUID_UV_INDEX BT_UUID_DECLARE_16(0x2a76) +#define BT_UUID_UV_INDEX_VAL 0x2a76 +/** @def BT_UUID_IRRADIANCE + * @brief Irradiance Characteristic + */ +#define BT_UUID_IRRADIANCE BT_UUID_DECLARE_16(0x2a77) +#define BT_UUID_IRRADIANCE_VAL 0x2a77 +/** @def BT_UUID_RAINFALL + * @brief Rainfall Characteristic + */ +#define BT_UUID_RAINFALL BT_UUID_DECLARE_16(0x2a78) +#define BT_UUID_RAINFALL_VAL 0x2a78 +/** @def BT_UUID_WIND_CHILL + * @brief Wind Chill Characteristic + */ +#define BT_UUID_WIND_CHILL BT_UUID_DECLARE_16(0x2a79) +#define BT_UUID_WIND_CHILL_VAL 0x2a79 +/** @def BT_UUID_HEAT_INDEX + * @brief Heat Index Characteristic + */ +#define BT_UUID_HEAT_INDEX BT_UUID_DECLARE_16(0x2a7a) +#define BT_UUID_HEAT_INDEX_VAL 0x2a7a +/** @def BT_UUID_DEW_POINT + * @brief Dew Point Characteristic + */ +#define BT_UUID_DEW_POINT BT_UUID_DECLARE_16(0x2a7b) +#define BT_UUID_DEW_POINT_VAL 0x2a7b +/** @def BT_UUID_DESC_VALUE_CHANGED + * @brief Descriptor Value Changed Characteristic + */ +#define BT_UUID_DESC_VALUE_CHANGED BT_UUID_DECLARE_16(0x2a7d) +#define BT_UUID_DESC_VALUE_CHANGED_VAL 0x2a7d +/** @def BT_UUID_MAGN_FLUX_DENSITY_2D + * @brief Magnetic Flux Density - 2D Characteristic + */ +#define BT_UUID_MAGN_FLUX_DENSITY_2D BT_UUID_DECLARE_16(0x2aa0) +#define BT_UUID_MAGN_FLUX_DENSITY_2D_VAL 0x2aa0 +/** @def BT_UUID_MAGN_FLUX_DENSITY_3D + * @brief Magnetic Flux Density - 3D Characteristic + */ +#define BT_UUID_MAGN_FLUX_DENSITY_3D BT_UUID_DECLARE_16(0x2aa1) +#define BT_UUID_MAGN_FLUX_DENSITY_3D_VAL 0x2aa1 +/** @def BT_UUID_BAR_PRESSURE_TREND + * @brief Barometric Pressure Trend Characteristic + */ +#define BT_UUID_BAR_PRESSURE_TREND BT_UUID_DECLARE_16(0x2aa3) +#define BT_UUID_BAR_PRESSURE_TREND_VAL 0x2aa3 + +/** @brief Compare Bluetooth UUIDs. + * + * Compares 2 Bluetooth UUIDs, if the types are different both UUIDs are + * first converted to 128 bits format before comparing. + * + * @param u1 First Bluetooth UUID to compare + * @param u2 Second Bluetooth UUID to compare + * + * @return negative value if @a u1 < @a u2, 0 if @a u1 == @a u2, else positive + */ +int bt_uuid_cmp(const struct bt_uuid *u1, const struct bt_uuid *u2); + +#if defined(CONFIG_BLUETOOTH_DEBUG) +/** @brief Convert Bluetooth UUID to string. + * + * Converts Bluetooth UUID to string. UUID has to be in 16 bits or 128 bits + * format. + * + * @param uuid Bluetooth UUID + * @param str pointer where to put converted string + * @param len length of str + * + * @return N/A + */ +void bt_uuid_to_str(const struct bt_uuid *uuid, char *str, size_t len); + +/** @brief Convert Bluetooth UUID to string in place. + * + * Converts Bluetooth UUID to string in place. UUID has to be in 16 bits or + * 128 bits format. + * + * @param uuid Bluetooth UUID + * + * @return String representation of the UUID given + */ +const char *bt_uuid_str(const struct bt_uuid *uuid); +#endif /* CONFIG_BLUETOOTH_DEBUG */ + +#ifdef __cplusplus +} +#endif + +#endif /* __BT_UUID_H */ diff --git a/system/libarc32_arduino101/drivers/ipc_uart_ns16550.c b/system/libarc32_arduino101/drivers/ipc_uart_ns16550.c index a497b6cf..b2f6c08f 100644 --- a/system/libarc32_arduino101/drivers/ipc_uart_ns16550.c +++ b/system/libarc32_arduino101/drivers/ipc_uart_ns16550.c @@ -49,14 +49,38 @@ #define IPC_UART_HDR_REQUEST_LEN (IPC_HEADER_LEN+sizeof(uint32_t)) /* ipc header + request len */ -struct ipc_uart_channels channels[IPC_UART_MAX_CHANNEL] = { {0, 0, NULL},}; -static uint16_t send_counter = 0; -static uint16_t received_counter = 0; -static uint8_t * ipc_uart_tx = NULL; -static uint8_t * ipc_uart_rx = NULL; -static uint8_t ipc_uart_tx_state = 0; +enum { + STATUS_TX_IDLE = 0, + STATUS_TX_BUSY, + STATUS_TX_DONE, +}; + +enum { + STATUS_RX_IDLE = 0, + STATUS_RX_HDR, + STATUS_RX_DATA +}; + +struct ipc_uart { + uint8_t *tx_data; + uint8_t *rx_ptr; + struct ipc_uart_channels channels[IPC_UART_MAX_CHANNEL]; + struct ipc_uart_header tx_hdr; + struct ipc_uart_header rx_hdr; + uint16_t send_counter; + uint16_t rx_size; + uint8_t tx_state; + uint8_t rx_state; + uint8_t uart_enabled; + /* protect against multiple wakelock and wake assert calls */ + uint8_t tx_wakelock_acquired; + /* TODO: remove once IRQ will take a parameter */ + //struct td_device *device; + void (*tx_cb)(bool wake_state, void *); /*!< Callback to be called to set wake state when TX is starting or ending */ + void *tx_cb_param; /*!< tx_cb function parameter */ +}; -static void * ble_cfw_channel; +static struct ipc_uart ipc = {}; static const struct uart_init_info uart_dev_info[] = { { @@ -79,187 +103,246 @@ static const struct uart_init_info uart_dev_info[] = { }, }; -void uart_ipc_close_channel(int channel_id) +void ipc_uart_close_channel(int channel_id) { - channels[channel_id].state = IPC_CHANNEL_STATE_CLOSED; - channels[channel_id].cb = NULL; - channels[channel_id].index = channel_id; + ipc.channels[channel_id].state = IPC_CHANNEL_STATE_CLOSED; + ipc.channels[channel_id].cb = NULL; + ipc.channels[channel_id].index = channel_id; + + ipc.uart_enabled = 0; + ipc.tx_wakelock_acquired = 0; } -void uart_ipc_disable(int num) +void ipc_uart_ns16550_disable(int num) { int i; for (i = 0; i < IPC_UART_MAX_CHANNEL; i++) - uart_ipc_close_channel(i); + ipc_uart_close_channel(i); + if (ipc.tx_cb) + ipc.tx_cb(0, ipc.tx_cb_param); + UART_IRQ_TX_DISABLE(num); UART_IRQ_RX_DISABLE(num); } -void uart_ipc_init(int num) +void ipc_uart_init(int num) { int i; (void)num; - uint8_t c; for (i = 0; i < IPC_UART_MAX_CHANNEL; i++) - uart_ipc_close_channel(i); + ipc_uart_close_channel(i); - pr_info(LOG_MODULE_IPC, "uart_ipc_init(nr: %d), baudrate %d, options:" - "0x%x, irq: %d",IPC_UART, + pr_info(LOG_MODULE_IPC, "%s(nr: %d), baudrate %d, options:" + "0x%x, irq: %d",__FUNCTION__, IPC_UART, uart_dev_info[IPC_UART].baud_rate, uart_dev_info[IPC_UART].options, uart_dev_info[IPC_UART].irq); uart_init(IPC_UART, &uart_dev_info[IPC_UART]); - /* Drain RX FIFOs (no need to disable IRQ at this stage) */ - while (uart_poll_in(IPC_UART, &c) != -1); - uart_int_connect(IPC_UART, uart_ipc_isr, NULL, NULL); - - UART_IRQ_RX_ENABLE(IPC_UART); + + ipc.uart_enabled = 0; + ipc.tx_wakelock_acquired = 0; + + /* Initialize the reception pointer */ + ipc.rx_size = sizeof(ipc.rx_hdr); + ipc.rx_ptr = (uint8_t *)&ipc.rx_hdr; + ipc.rx_state = STATUS_RX_IDLE; } -void uart_ipc_set_channel(void * ipc_channel) +static void ipc_uart_push_frame(uint16_t len, uint8_t *p_data) { - ble_cfw_channel = ipc_channel; -} - -void * uart_ipc_get_channel(void) -{ - return ble_cfw_channel; -} - -void uart_ipc_push_frame(void) { - void * frame; - OS_ERR_TYPE error = E_OS_OK; - - if (NULL == ipc_uart_rx) - return; - int len = IPC_FRAME_GET_LEN(ipc_uart_rx); - int channel = IPC_FRAME_GET_CHANNEL(ipc_uart_rx); - uint8_t cpu_id = IPC_FRAME_GET_SRC(ipc_uart_rx); - - pr_debug(LOG_MODULE_IPC, "%s: received frame: len %d, channel %d, src " - "%d", __func__, len, channel, cpu_id); - - if (channels[channel].cb != NULL) { - frame = balloc(len, &error); - if (error != E_OS_OK) { - pr_error(LOG_MODULE_IPC, "NO MEM: error: %d size: %d", - error, len); - } else { - memcpy(frame, &ipc_uart_rx[IPC_HEADER_LEN], len); - - channels[channel].cb(cpu_id, channel, len, frame); - } + //pr_debug(LOG_MODULE_IPC, "push_frame: received:frame len: %d, 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]); + + if ((ipc.rx_hdr.channel < IPC_UART_MAX_CHANNEL) && + (ipc.channels[ipc.rx_hdr.channel].cb != NULL)) { + ipc.channels[ipc.rx_hdr.channel].cb(ipc.rx_hdr.channel, + IPC_MSG_TYPE_MESSAGE, + len, + p_data); + } else { + bfree(p_data); + pr_error(LOG_MODULE_IPC, "uart_ipc: bad channel %d", + ipc.rx_hdr.channel); } - if (ipc_uart_rx) - bfree(ipc_uart_rx); - ipc_uart_rx = NULL; } -void uart_ipc_isr() +void ipc_uart_isr() { - uint8_t *p_rx; + /* TODO: remove once IRQ supports parameter */ uint8_t *p_tx; - while (UART_IRQ_HW_UPDATE(IPC_UART) && UART_IRQ_IS_PENDING(IPC_UART)) { - if (UART_IRQ_ERR_DETECTED(IPC_UART)) { + while (UART_IRQ_HW_UPDATE(IPC_UART) && + UART_IRQ_IS_PENDING(IPC_UART)) { + if (UART_IRQ_ERR_DETECTED(IPC_UART)) + { uint8_t c; - if (UART_BREAK_CHECK(IPC_UART)){ - panic(); + if (UART_BREAK_CHECK(IPC_UART)) { + panic(-1); } UART_POLL_IN(IPC_UART, &c); - } else if (UART_IRQ_RX_READY(IPC_UART)) { - int received; - if (received_counter < 2) { - if (NULL == ipc_uart_rx) - ipc_uart_rx = - balloc(IPC_UART_MAX_PAYLOAD, NULL); - p_rx = ipc_uart_rx; - received = UART_FIFO_READ(IPC_UART, - &p_rx[received_counter], - 1); - received_counter += received; - } else { - p_rx = ipc_uart_rx; - received = UART_FIFO_READ(IPC_UART, - &p_rx[received_counter], - IPC_FRAME_GET_LEN(p_rx) + - IPC_HEADER_LEN - - received_counter); - received_counter += received; - if (received_counter == IPC_FRAME_GET_LEN(p_rx) - + IPC_HEADER_LEN) { + } + if (UART_IRQ_RX_READY(IPC_UART)) { + int rx_cnt; + + while ((rx_cnt = + UART_FIFO_READ(IPC_UART, + ipc.rx_ptr, + ipc.rx_size)) != 0) + { + if ((ipc.uart_enabled) && + (ipc.rx_state == STATUS_RX_IDLE)) { + /* acquire wakelock until frame is fully received */ + //pm_wakelock_acquire(&info->rx_wl); + ipc.rx_state = STATUS_RX_HDR; + } + + /* Until UART has enabled at least one channel, data should be discarded */ + if (ipc.uart_enabled) { + ipc.rx_size -= rx_cnt; + ipc.rx_ptr += rx_cnt; + } + + if (ipc.rx_size == 0) { + if (ipc.rx_state == STATUS_RX_HDR) { + pr_error(0, "%s-%d", __FUNCTION__, ipc.rx_hdr.len); + ipc.rx_ptr = balloc( + ipc.rx_hdr.len, NULL); + + //pr_debug( + // LOG_MODULE_IPC, + // "ipc_uart_isr: rx_ptr is %p", + // ipc.rx_ptr); + ipc.rx_size = ipc.rx_hdr.len; + ipc.rx_state = STATUS_RX_DATA; + } else { #ifdef IPC_UART_DBG_RX - for(int i = 0; i < received_counter; i++) { - pr_debug(LOG_MODULE_IPC, "%s: %d byte is %d", __func__, i, p_rx[i]); - } + uint8_t *p_rx = ipc.rx_ptr - + ipc.rx_hdr.len; + for (int i = 0; + i < ipc.rx_hdr.len; + i++) { + pr_debug( + LOG_MODULE_IPC, + "ipc_uart_isr: %d byte is %d", + i, p_rx[i]); + } #endif - received_counter = 0; - uart_ipc_push_frame(); + + ipc_uart_push_frame( + ipc.rx_hdr.len, + ipc.rx_ptr - + ipc.rx_hdr.len); + ipc.rx_size = sizeof(ipc.rx_hdr); + ipc.rx_ptr = + (uint8_t *)&ipc.rx_hdr; + ipc.rx_state = STATUS_RX_IDLE; + } } } - } else if (UART_IRQ_TX_READY(IPC_UART)) { - int transmitted; - if (ipc_uart_tx_state == STATUS_TX_IDLE) { + } + if (UART_IRQ_TX_READY(IPC_UART)) { + int tx_len; + + if (ipc.tx_state == STATUS_TX_DONE) { uint8_t lsr = UART_LINE_STATUS(IPC_UART); + ipc.tx_state = STATUS_TX_IDLE; UART_IRQ_TX_DISABLE(IPC_UART); - - pr_debug(LOG_MODULE_IPC, "ipc_isr_tx: disable TXint, LSR: 0x%2x\n", - lsr); + /* wait for FIFO AND THR being empty! */ while ((lsr & BOTH_EMPTY) != BOTH_EMPTY) { lsr = UART_LINE_STATUS(IPC_UART); } + + /* No more TX activity, send event and release wakelock */ + if (ipc.tx_cb) { + ipc.tx_cb(0, ipc.tx_cb_param); + } + //pm_wakelock_release(&info->tx_wl); + ipc.tx_wakelock_acquired = 0; return; } - if(NULL == ipc_uart_tx){ - pr_warning(LOG_MODULE_IPC, "%s: Bad Tx data",__func__); + if (NULL == ipc.tx_data) { + pr_warning(LOG_MODULE_IPC, + "ipc_uart_isr: Bad Tx data"); return; } - p_tx = ipc_uart_tx; - transmitted = UART_FIFO_FILL(IPC_UART, &p_tx[send_counter], - IPC_FRAME_GET_LEN(p_tx) + - IPC_HEADER_LEN - send_counter); - send_counter += transmitted; - if (send_counter == IPC_FRAME_GET_LEN(p_tx) + - IPC_HEADER_LEN) { - send_counter = 0; -#ifdef IPC_UART_DBG_TX - pr_debug(LOG_MODULE_IPC, "%s: sent IPC FRAME " - "len %d", __func__, - IPC_FRAME_GET_LEN(p_tx)); - for (int i = 0; i < send_counter; i++) { - pr_debug(LOG_MODULE_IPC, "%s: %d sent " - "byte is %d", - __func__, i, p_tx[i]); + + if (!ipc.tx_wakelock_acquired) { + ipc.tx_wakelock_acquired = 1; + /* Starting TX activity, send wake assert event and acquire wakelock */ + if (ipc.tx_cb) { + ipc.tx_cb(1, ipc.tx_cb_param); } + //pm_wakelock_acquire(&info->tx_wl); + } + if (ipc.send_counter < sizeof(ipc.tx_hdr)) { + p_tx = (uint8_t *)&ipc.tx_hdr + + ipc.send_counter; + tx_len = sizeof(ipc.tx_hdr) - ipc.send_counter; + } else { + p_tx = ipc.tx_data + + (ipc.send_counter - sizeof(ipc.tx_hdr)); + tx_len = ipc.tx_hdr.len - + (ipc.send_counter - sizeof(ipc.tx_hdr)); + } + ipc.send_counter += UART_FIFO_FILL(IPC_UART, + p_tx, + tx_len); + + if (ipc.send_counter == + (ipc.tx_hdr.len + sizeof(ipc.tx_hdr))) { + ipc.send_counter = 0; +#ifdef IPC_UART_DBG_TX + pr_debug( + LOG_MODULE_IPC, + "ipc_uart_isr: sent IPC FRAME " + "len %d", ipc.tx_hdr.len); #endif - bfree(ipc_uart_tx); - ipc_uart_tx = NULL; - ipc_uart_tx_state = STATUS_TX_IDLE; + p_tx = ipc.tx_data; + ipc.tx_data = NULL; + ipc.tx_state = STATUS_TX_DONE; + + /* free sent message and pull send next frame one in the queue */ + if (ipc.channels[ipc.tx_hdr.channel].cb) + { + ipc.channels[ipc.tx_hdr.channel].cb( + ipc.tx_hdr.channel, + IPC_MSG_TYPE_FREE, + ipc.tx_hdr.len, + p_tx); + } + else + { + bfree(p_tx); + } + #ifdef IPC_UART_DBG_TX - uint8_t lsr = UART_LINE_STATUS(IPC_UART); - pr_info(LOG_MODULE_IPC, "ipc_isr_tx: tx_idle LSR: 0x%2x\n", - lsr); + uint8_t lsr = UART_LINE_STATUS(IPC_UART);//(info->uart_num); + pr_debug(LOG_MODULE_IPC, + "ipc_isr_tx: tx_idle LSR: 0x%2x\n", + lsr); #endif } - } else { - pr_warning(LOG_MODULE_IPC, "%s: Unknown ISR src", - __func__); } + } } -void *uart_ipc_channel_open(int channel_id, - void (*cb) (uint8_t, int, int, void *)) +void *ipc_uart_channel_open(int channel_id, + int (*cb)(int, int, int, void *)) { struct ipc_uart_channels *chan; + uint8_t c; - if (channel_id > IPC_UART_MAX_CHANNEL - 1) + if (channel_id > (IPC_UART_MAX_CHANNEL - 1)) return NULL; - chan = &channels[channel_id]; + chan = &ipc.channels[channel_id]; if (chan->state != IPC_CHANNEL_STATE_CLOSED) return NULL; @@ -267,74 +350,48 @@ void *uart_ipc_channel_open(int channel_id, chan->state = IPC_CHANNEL_STATE_OPEN; chan->cb = cb; + ipc.uart_enabled = 1; + ipc.tx_wakelock_acquired = 0; + + pr_debug(LOG_MODULE_IPC, "%s: open chan success", __FUNCTION__); + + /* Drain RX FIFOs (no need to disable IRQ at this stage) */ + while (uart_poll_in(IPC_UART, &c) != -1); + uart_int_connect(IPC_UART, ipc_uart_isr, NULL, NULL); + + UART_IRQ_RX_ENABLE(IPC_UART); + return chan; } -int uart_ipc_send_message(void *handle, int len, void *p_data) +int ipc_uart_ns16550_send_pdu(void *handle, int len, void *p_data) { - struct ipc_uart_channels *chan = (struct ipc_uart_channels *) handle; - - int flags = interrupt_lock(); - if (ipc_uart_tx_state == STATUS_TX_BUSY) { - interrupt_unlock(flags); - return IPC_UART_ERROR_WRONG_STATE; - } - ipc_uart_tx_state = STATUS_TX_BUSY; - interrupt_unlock(flags); - - uint8_t *p_tx = ipc_uart_tx = balloc(len + IPC_UART_HDR_REQUEST_LEN, - NULL); + struct ipc_uart_channels *chan = (struct ipc_uart_channels *)handle; - /* Adding size of the message request field*/ - int size = len + sizeof(uint32_t); + pr_debug(LOG_MODULE_IPC, "%s: %d", __FUNCTION__, ipc.tx_state); - /* length = cfw_message size + message request field*/ - IPC_FRAME_SET_LEN(p_tx, size); - IPC_FRAME_SET_CHANNEL(p_tx, chan->index); - IPC_FRAME_SET_SRC(p_tx, get_cpu_id()); - IPC_FRAME_SET_REQUEST(p_tx, IPC_MSG_TYPE_MESSAGE); - - /* IPC_HEADER + request_ID + cfw_message */ - /* copy cfw_message within IPC frame*/ - memcpy(IPC_FRAME_DATA(p_tx), p_data, len); + if (ipc.tx_state == STATUS_TX_BUSY) { + return IPC_UART_TX_BUSY; + } + + /* It is eventually possible to be in DONE state (sending last bytes of previous message), + * so we move immediately to BUSY and configure the next frame */ + ipc.tx_state = STATUS_TX_BUSY; - pr_debug(LOG_MODULE_IPC, "%s: tx: channel %d, len %d, request 0x%x", - __func__, p_tx[2], len, p_tx[4]); + ipc.tx_hdr.len = len; + ipc.tx_hdr.channel = chan->index; + ipc.tx_hdr.src_cpu_id = 0; + ipc.tx_data = p_data; + /* Enable the interrupt (ready will expire if it was disabled) */ UART_IRQ_TX_ENABLE(IPC_UART); return IPC_UART_ERROR_OK; } -int uart_ipc_send_sync_resp(int channel, int request_id, int param1, int param2, - void * ptr) +void ipc_uart_ns16550_set_tx_cb(void (*cb)(bool, void *), void *param) { - if (ipc_uart_tx_state == STATUS_TX_BUSY) - return IPC_UART_ERROR_WRONG_STATE; - ipc_uart_tx_state = STATUS_TX_BUSY; - - uint8_t *p_tx = ipc_uart_tx = balloc(IPC_UART_HDR_REQUEST_LEN + 12, - NULL); - - IPC_FRAME_SET_LEN(p_tx, 16); - IPC_FRAME_SET_CHANNEL(p_tx, channel); - IPC_FRAME_SET_SRC(ipc_uart_tx, get_cpu_id()); - - IPC_FRAME_SET_REQUEST(p_tx, request_id); - SYNC_FRAME_SET_PARAM1(p_tx, param1); - SYNC_FRAME_SET_PARAM2(p_tx, param2); - SYNC_FRAME_SET_PTR(p_tx, ptr); - -#ifdef IPC_UART_DBG_SYNC_RESP - for (int i = 0; i < 20; i++) { - pr_debug(LOG_MODULE_IPC, "%s: IPC sync resp %d byte : %d", - __func__,i, p_tx[i]); - } - pr_debug(LOG_MODULE_IPC, "%s: tx: channel %d, request %xh", __func__, - p_tx[2], p_tx[4]); -#endif - - UART_IRQ_TX_ENABLE(IPC_UART); - - return IPC_UART_ERROR_OK; + ipc.tx_cb = cb; + ipc.tx_cb_param = param; } + diff --git a/system/libarc32_arduino101/drivers/ipc_uart_ns16550.h b/system/libarc32_arduino101/drivers/ipc_uart_ns16550.h index 7fc04670..d71a61b9 100644 --- a/system/libarc32_arduino101/drivers/ipc_uart_ns16550.h +++ b/system/libarc32_arduino101/drivers/ipc_uart_ns16550.h @@ -33,19 +33,71 @@ #define IPC_UART 0 -enum { - STATUS_TX_IDLE = 0, - STATUS_TX_BUSY, -}; -enum { +/** IPC UART return codes */ +enum IPC_UART_RESULT_CODES { IPC_UART_ERROR_OK = 0, - IPC_UART_ERROR_WRONG_STATE, IPC_UART_ERROR_DATA_TO_BIG, + IPC_UART_TX_BUSY /**< A transmission is already ongoing, message is NOT sent */ +}; + + +/** + * Channel list + */ +enum ipc_channels { + RPC_CHANNEL=0, /**< RPC channel */ + IPC_UART_MAX_CHANNEL = 4 +}; + +/** + * Channel state + */ +enum ipc_channel_state { + IPC_CHANNEL_STATE_CLOSED = 0, + IPC_CHANNEL_STATE_OPEN +}; + +/** + * Definitions valid for NONE sync IPC UART headers + * |len|channel|cpu_id|request|payload| + * + * len = len(request)+len(payload) + */ + +/** + * @note this structure must be self-aligned and self-packed + */ +struct ipc_uart_header { + uint16_t len; /**< Length of IPC message, (request + payload) */ + uint8_t channel; /**< Channel number of IPC message. */ + uint8_t src_cpu_id; /**< CPU id of IPC sender. */ }; -void uart_ipc_isr(); -void uart_ipc_push_frame(void); -void uart_ipc_close_channel(int channel_id); +/** + * IPC channel description + */ +struct ipc_uart_channels { + uint16_t index; /**< Channel number */ + uint16_t state; /**< @ref ipc_channel_state */ + int (*cb)(int chan, int request, int len, void *data); + /**< Callback of the channel. + * @param chan Channel index used + * @param request Request id (defined in ipc_requests.h) + * @param len Payload size + * @param data Pointer to data + */ +}; + +void ipc_uart_init(int num); +void ipc_uart_isr(); +//static void ipc_uart_push_frame(uint16_t len, uint8_t *p_data); +void ipc_uart_ns16550_disable(int num); +void ipc_uart_close_channel(int channel_id); +void ipc_uart_ns16550_set_tx_cb(void (*cb)(bool, void *), void *param); +int ipc_uart_ns16550_send_pdu(void *handle, int len, void *p_data); +void *ipc_uart_channel_open(int channel_id, + int (*cb)(int, int, int, void *)); + #endif /* _IPC_UART_NS16550_H_ */ diff --git a/system/libarc32_arduino101/drivers/rpc/rpc.h b/system/libarc32_arduino101/drivers/rpc/rpc.h new file mode 100644 index 00000000..5d45d463 --- /dev/null +++ b/system/libarc32_arduino101/drivers/rpc/rpc.h @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2016 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RPC_H_ +#define RPC_H_ + +#include + +/** Identifiers of the signature supported by the RPC */ +enum { + SIG_TYPE_NONE = 1, + SIG_TYPE_S, + SIG_TYPE_P, + SIG_TYPE_S_B, + SIG_TYPE_B_B_P, + SIG_TYPE_S_P, + SIG_TYPE_S_B_P, + SIG_TYPE_S_B_B_P +}; + +/** + * RPC memory allocation function, must be implemented by the user of the RPC. + * + * This function is called by the RPC mechanism to allocate a buffer for transmission + * of a serialized function. The function should not fail. + * + * @param length Length of the buffer to allocate + * + * @return Pointer to the allocated buffer, the allocation shall not fail, error must + * be handled internally + */ +uint8_t * rpc_alloc_cb(uint16_t length); + +/** + * RPC transmission function, must be implemented by the user of the RPC. + * + * @param p_buf Pointer to the buffer allocated for transmission by @ref rpc_alloc_cb + * @param length Length of the buffer to transmit + */ +void rpc_transmit_cb(uint8_t * p_buf, uint16_t length); + +/** + * RPC serialization function to serialize a function that does not require any parameter. + * + * @param fn_index Index of the function + */ +void rpc_serialize_none(uint8_t fn_index); + +/** + * RPC serialization function to serialize a function that expects a structure as parameter. + * + * @param fn_index Index of the function + * @param struct_data Pointer to the structure to serialize + * @param struct_length Length of the structure to serialize + */ +void rpc_serialize_s(uint8_t fn_index, const void * struct_data, uint8_t struct_length); + +/** + * RPC serialization function to serialize a function that expects a structure as parameter. + * + * @param fn_index Index of the function + * @param struct_data Pointer to the structure to serialize + * @param struct_length Length of the structure to serialize + * @param p_priv Pointer to serialize + */ +void rpc_serialize_s_p(uint8_t fn_index, const void * struct_data, uint8_t struct_length, void * p_priv); + +/** + * RPC serialization function to serialize a function that expects a pointer as parameter. + * + * @param fn_index Index of the function + * @param p_priv Pointer to serialize + */ +void rpc_serialize_p(uint8_t fn_index, void * p_priv); + +/** + * RPC serialization function to serialize a function that expects a structure + * and a buffer as parameters. + * + * @param fn_index Index of the function + * @param struct_data Pointer to the structure to serialize + * @param struct_length Length of the structure to serialize + * @param vbuf Pointer to the buffer to serialize + * @param vbuf_length Length of the buffer to serialize + */ +void rpc_serialize_s_b(uint8_t fn_index, const void * struct_data, uint8_t struct_length, const void * vbuf, uint16_t vbuf_length); + +/** + * RPC serialization function to serialize a function that expects a structure + * and a buffer as parameters. + * + * @param fn_index Index of the function + * @param vbuf1 Pointer to the buffer1 to serialize + * @param vbuf1_length Length of the buffer1 to serialize + * @param vbuf2 Pointer to the buffer2 to serialize + * @param vbuf2_length Length of the buffer2 to serialize + * @param p_priv Pointer to serialize + */ +void rpc_serialize_b_b_p(uint8_t fn_index, const void * vbuf1, uint16_t vbuf1_length, + const void * vbuf2, uint16_t vbuf2_length, void * p_priv); + +/** + * RPC serialization function to serialize a function that expects a structure + * and a buffer as parameters. + * + * @param fn_index Index of the function + * @param struct_data Pointer to the structure to serialize + * @param struct_length Length of the structure to serialize + * @param vbuf Pointer to the buffer to serialize + * @param vbuf_length Length of the buffer to serialize + * @param p_priv Pointer to serialize + */ +void rpc_serialize_s_b_p(uint8_t fn_index, const void * struct_data, uint8_t struct_length, + const void * vbuf, uint16_t vbuf_length, void * p_priv); + +/** + * RPC serialization function to serialize a function that expects a structure + * and a buffer as parameters. + * + * @param fn_index Index of the function + * @param struct_data Pointer to the structure to serialize + * @param struct_length Length of the structure to serialize + * @param vbuf1 Pointer to the buffer1 to serialize + * @param vbuf1_length Length of the buffer1 to serialize + * @param vbuf2 Pointer to the buffer2 to serialize + * @param vbuf2_length2 Length of the buffer2 to serialize + * @param p_priv Pointer to serialize + */ +void rpc_serialize_s_b_b_p(uint8_t fn_index, const void * struct_data, uint8_t struct_length, + const void * vbuf1, uint16_t vbuf1_length, const void * vbuf2, uint16_t vbuf2_length, void * p_priv); + +/** RPC deserialization function, shall be invoked when a buffer is received over the transport interface. + * + * @param p_buf Pointer to the received buffer + * @param length Length of the received buffer + */ +void rpc_deserialize(const uint8_t * p_buf, uint16_t length); + +#endif /* RPC_H_*/ diff --git a/system/libarc32_arduino101/drivers/rpc/rpc_deserialize.c b/system/libarc32_arduino101/drivers/rpc/rpc_deserialize.c new file mode 100644 index 00000000..e3f568d0 --- /dev/null +++ b/system/libarc32_arduino101/drivers/rpc/rpc_deserialize.c @@ -0,0 +1,457 @@ +/* + * Copyright (c) 2016 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "rpc.h" + +extern void panic(int err); + +/* Include the functions offered */ +#if defined(CONFIG_QUARK_SE_BLE_CORE) +#include "rpc_functions_to_ble_core.h" +#elif defined(CONFIG_SOC_QUARK_SE) +#include "rpc_functions_to_quark.h" +#elif defined(LINUX_HOST_RUNTIME) +// for the host compilation (to simulate connection to BLE controller) +#include "rpc_functions_to_ble_core.h" +#else +#error "File is compiled but should not" +#endif + +/* Build the list of prototypes and check that list are made only of matching signatures */ +#define FN_SIG_NONE(__fn) void __fn(void); +LIST_FN_SIG_NONE +#undef FN_SIG_NONE + +#define FN_SIG_S(__fn, __s) void __fn(__s p_s); +LIST_FN_SIG_S +#undef FN_SIG_S + +#define FN_SIG_P(__fn, __type) void __fn(__type p_priv); +LIST_FN_SIG_P +#undef FN_SIG_P + +#define FN_SIG_S_B(__fn, __s, __type, __length) void __fn(__s p_s, __type p_buf, __length length); +LIST_FN_SIG_S_B +#undef FN_SIG_S_B + +#define FN_SIG_B_B_P(__fn, __type1, __length1, __type2, __length2, __type3) \ + void __fn(__type1 p_buf1, __length1 length1, __type2 p_buf2, __length2 length2, __type3 p_priv); +LIST_FN_SIG_B_B_P +#undef FN_SIG_B_B_P + +#define FN_SIG_S_P(__fn, __s, __type) void __fn(__s p_s, __type p_priv); +LIST_FN_SIG_S_P +#undef FN_SIG_S_P + +#define FN_SIG_S_B_P(__fn, __s, __type, __length, __type_ptr) \ + void __fn(__s p_s, __type p_buf, __length length, __type_ptr p_priv); +LIST_FN_SIG_S_B_P +#undef FN_SIG_S_B_P + +#define FN_SIG_S_B_B_P(__fn, __s, __type1, __length1, __type2, __length2, __type_ptr) \ + void __fn(__s p_s, __type1 p_buf1, __length1 length1, __type2 p_buf2, __length2 length2, __type_ptr p_priv); +LIST_FN_SIG_S_B_B_P +#undef FN_SIG_S_B_B_P + + + +/* 1 - define the size check arrays */ +#define FN_SIG_NONE(__fn) + +#define FN_SIG_S(__fn, __s) sizeof(*((__s)0)), + +#define FN_SIG_P(__fn, __type) + +#define FN_SIG_S_B(__fn, __s, __type, __length) sizeof(*((__s)0)), + +#define FN_SIG_B_B_P(__fn, __type1, __length1, __type2, __length2, __type3) sizeof(*((__s)0)), + +#define FN_SIG_S_P(__fn, __s, __type) sizeof(*((__s)0)), + +#define FN_SIG_S_B_P(__fn, __s, __type, __length, __type_ptr) sizeof(*((__s)0)), + +#define FN_SIG_S_B_B_P(__fn, __s, __type1, __length1, __type2, __length2, __type3) sizeof(*((__s)0)), + +static uint8_t m_size_s[] = { LIST_FN_SIG_S }; +static uint8_t m_size_s_b[] = { LIST_FN_SIG_S_B }; +static uint8_t m_size_s_p[] = { LIST_FN_SIG_S_P }; +static uint8_t m_size_s_b_p[] = { LIST_FN_SIG_S_B_P }; +static uint8_t m_size_s_b_b_p[] = { LIST_FN_SIG_S_B_B_P }; + +#undef FN_SIG_NONE +#undef FN_SIG_S +#undef FN_SIG_P +#undef FN_SIG_S_B +#undef FN_SIG_B_B_P +#undef FN_SIG_S_P +#undef FN_SIG_S_B_P +#undef FN_SIG_S_B_B_P + +/* 2- build the enumerations list */ +#define FN_SIG_NONE(__fn) fn_index_##__fn, +#define FN_SIG_S(__fn, __s) FN_SIG_NONE(__fn) +#define FN_SIG_P(__fn, __type) FN_SIG_NONE(__fn) +#define FN_SIG_S_B(__fn, __s, __type, __length) FN_SIG_NONE(__fn) +#define FN_SIG_B_B_P(__fn, __type1, __length1, __type2, __length2, __type3) FN_SIG_NONE(__fn) +#define FN_SIG_S_P(__fn, __s, __type) FN_SIG_NONE(__fn) +#define FN_SIG_S_B_P(__fn, __s, __type, __length, __type_ptr) FN_SIG_NONE(__fn) +#define FN_SIG_S_B_B_P(__fn, __s, __type1, __length1, __type2, __length2, __type3) FN_SIG_NONE(__fn) + +/* Build the list of function indexes in the deserialization array */ +enum { LIST_FN_SIG_NONE fn_none_index_max }; +enum { LIST_FN_SIG_S fn_s_index_max }; +enum { LIST_FN_SIG_P fn_p_index_max }; +enum { LIST_FN_SIG_S_B fn_s_b_index_max }; +enum { LIST_FN_SIG_B_B_P fn_b_b_p_index_max }; +enum { LIST_FN_SIG_S_P fn_s_p_index_max }; +enum { LIST_FN_SIG_S_B_P fn_s_b_p_index_max }; +enum { LIST_FN_SIG_S_B_B_P fn_s_b_b_p_index_max }; + +#undef FN_SIG_NONE +#undef FN_SIG_S +#undef FN_SIG_P +#undef FN_SIG_S_B +#undef FN_SIG_B_B_P +#undef FN_SIG_S_P +#undef FN_SIG_S_B_P +#undef FN_SIG_S_B_B_P + +/* 3- build the array */ +#define FN_SIG_NONE(__fn) [fn_index_##__fn] = (void*)__fn, +#define FN_SIG_S(__fn, __s) FN_SIG_NONE(__fn) +#define FN_SIG_P(__fn, __type) FN_SIG_NONE(__fn) +#define FN_SIG_S_B(__fn, __s, __type, __length) FN_SIG_NONE(__fn) +#define FN_SIG_B_B_P(__fn, __type1, __length1, __type2, __length2, __type3) FN_SIG_NONE(__fn) +#define FN_SIG_S_P(__fn, __s, __type) FN_SIG_NONE(__fn) +#define FN_SIG_S_B_P(__fn, __s, __type, __length, __type_ptr) FN_SIG_NONE(__fn) +#define FN_SIG_S_B_B_P(__fn, __s, __type1, __length1, __type2, __length2, __type3) FN_SIG_NONE(__fn) + +static void (*m_fct_none[])(void) = { LIST_FN_SIG_NONE }; +static void (*m_fct_s[])(void * structure) = { LIST_FN_SIG_S }; +static void (*m_fct_p[])(void * pointer) = { LIST_FN_SIG_P }; +static void (*m_fct_s_b[])(void * structure, void * buffer, uint8_t length) = { LIST_FN_SIG_S_B }; +static void (*m_fct_b_b_p[])(void * buffer1, uint8_t length1, void * buffer2, uint8_t length2, void * pointer) = { LIST_FN_SIG_B_B_P }; +static void (*m_fct_s_p[])(void * structure, void * pointer) = { LIST_FN_SIG_S_P }; +static void (*m_fct_s_b_p[])(void * structure, void * buffer, uint8_t length, void * pointer) = { LIST_FN_SIG_S_B_P }; +static void (*m_fct_s_b_b_p[])(void * structure, void * buffer1, uint8_t length1, void * buffer2, uint8_t length2, void * pointer) = { LIST_FN_SIG_S_B_B_P }; + +static const uint8_t * deserialize_struct(const uint8_t *p, const uint8_t **pp_struct, uint8_t *p_struct_length) { + uint8_t struct_length; + + struct_length = *p++; + *pp_struct = p; + *p_struct_length = struct_length; + + return p + struct_length; +} + +static const uint8_t * deserialize_buf(const uint8_t *p, const uint8_t **pp_buf, uint16_t *p_buflen) { + uint8_t b; + uint16_t buflen; + + /* Get the current byte */ + b = *p++; + buflen = b & 0x7F; + if (b & 0x80) { + /* Get the current byte */ + b = *p++; + buflen += (uint16_t)b << 7; + } + + /* Return the values */ + *pp_buf = p; + *p_buflen = buflen; + p += buflen; + return p; +} + +static void deserialize_none(uint8_t fn_index, const uint8_t * p_buf, uint16_t length) { + (void)p_buf; + if (length != 0) + panic(-1); + m_fct_none[fn_index](); +} + +static void deserialize_s(uint8_t fn_index, const uint8_t * p_buf, uint16_t length) { + const uint8_t *p_struct_data; + uint8_t struct_length; + const uint8_t *p; + + p = deserialize_struct(p_buf, &p_struct_data, &struct_length); + + if ((length != (p - p_buf)) || + (struct_length != m_size_s[fn_index])) + panic(-1); + + { + /* Always align structures on word boundary */ + uintptr_t struct_data[(struct_length + (sizeof(uintptr_t) - 1))/(sizeof(uintptr_t))]; + + memcpy(struct_data, p_struct_data, struct_length); + + m_fct_s[fn_index](struct_data); + } +} + +static void deserialize_p(uint8_t fn_index, const uint8_t * p_buf, uint16_t length) { + uintptr_t p_priv; + + if (length != 4) + panic(-1); + + /* little endian conversion */ + p_priv = p_buf[0] | (p_buf[1] << 8) | (p_buf[2] << 16) | (p_buf[3] << 24); + + m_fct_p[fn_index]((void *)p_priv); +} + +static void deserialize_s_b(uint8_t fn_index, const uint8_t * p_buf, uint16_t length) { + const uint8_t *p_struct_data; + uint8_t struct_length; + const uint8_t *p_vbuf; + uint16_t vbuf_length; + const uint8_t *p; + + p = deserialize_struct(p_buf, &p_struct_data, &struct_length); + p = deserialize_buf(p, &p_vbuf, &vbuf_length); + + if ((length != (p - p_buf)) || + (struct_length != m_size_s_b[fn_index])) + panic(-1); + + { + /* Always align structures on word boundary */ + uintptr_t struct_data[(struct_length + (sizeof(uintptr_t) - 1))/(sizeof(uintptr_t))]; + uintptr_t vbuf[(vbuf_length + (sizeof(uintptr_t) - 1))/(sizeof(uintptr_t))]; + void * buf = NULL; + + memcpy(struct_data, p_struct_data, struct_length); + + if (vbuf_length) { + memcpy(vbuf, p_vbuf, vbuf_length); + buf = vbuf; + } + + m_fct_s_b[fn_index](struct_data, buf, vbuf_length); + } +} + +static void deserialize_b_b_p(uint8_t fn_index, const uint8_t * p_buf, uint16_t length) { + const uint8_t *p_vbuf1; + uint16_t vbuf1_length; + const uint8_t *p_vbuf2; + uint16_t vbuf2_length; + uintptr_t p_priv; + const uint8_t *p; + + p = deserialize_buf(p_buf, &p_vbuf1, &vbuf1_length); + p = deserialize_buf(p, &p_vbuf2, &vbuf2_length); + p += 4; + + if (length != (p - p_buf)) + panic(-1); + + { + /* Always align structures on word boundary */ + uintptr_t vbuf1[(vbuf1_length + (sizeof(uintptr_t) - 1))/(sizeof(uintptr_t))]; + uintptr_t vbuf2[(vbuf2_length + (sizeof(uintptr_t) - 1))/(sizeof(uintptr_t))]; + void * buf1 = NULL; + void * buf2 = NULL; + + if (vbuf1_length) { + memcpy(vbuf1, p_vbuf1, vbuf1_length); + buf1 = vbuf1; + } + + if (vbuf2_length) { + memcpy(vbuf2, p_vbuf2, vbuf2_length); + buf2 = vbuf2; + } + p = p_vbuf2 + vbuf2_length; + + /* little endian conversion */ + p_priv = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); + + m_fct_b_b_p[fn_index](buf1, vbuf1_length, buf2, vbuf2_length, (void *)p_priv); + } +} + +static void deserialize_s_p(uint8_t fn_index, const uint8_t * p_buf, uint16_t length) +{ + const uint8_t *p_struct_data; + uint8_t struct_length; + uintptr_t p_priv; + const uint8_t *p; + + p = deserialize_struct(p_buf, &p_struct_data, &struct_length); + p += 4; + + if ((length != (p - p_buf)) || + (struct_length != m_size_s_p[fn_index])) + panic(-1); + + { + /* Always align structures on word boundary */ + uintptr_t struct_data[(struct_length + (sizeof(uintptr_t) - 1))/(sizeof(uintptr_t))]; + + memcpy(struct_data, p_struct_data, struct_length); + p = p_struct_data + struct_length; + + /* little endian conversion */ + p_priv = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); + + m_fct_s_p[fn_index](struct_data, (void *)p_priv); + } +} + +static void deserialize_s_b_p(uint8_t fn_index, const uint8_t * p_buf, uint16_t length) +{ + const uint8_t *p_struct_data; + uint8_t struct_length; + const uint8_t *p_vbuf; + uint16_t vbuf_length; + uintptr_t p_priv; + const uint8_t *p; + + p = deserialize_struct(p_buf, &p_struct_data, &struct_length); + p = deserialize_buf(p, &p_vbuf, &vbuf_length); + p += 4; + + if ((length != (p - p_buf)) || + (struct_length != m_size_s_b_p[fn_index])) + panic(-1); + + { + /* Always align structures on word boundary */ + uintptr_t struct_data[(struct_length + (sizeof(uintptr_t) - 1))/(sizeof(uintptr_t))]; + uintptr_t vbuf[(vbuf_length + (sizeof(uintptr_t) - 1))/(sizeof(uintptr_t))]; + void * buf = NULL; + + memcpy(struct_data, p_struct_data, struct_length); + + if (vbuf_length) { + memcpy(vbuf, p_vbuf, vbuf_length); + buf = vbuf; + } + p = p_vbuf + vbuf_length; + + /* little endian conversion */ + p_priv = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); + + m_fct_s_b_p[fn_index](struct_data, buf, vbuf_length, (void *)p_priv); + } +} + +static void deserialize_s_b_b_p(uint8_t fn_index, const uint8_t * p_buf, uint16_t length) { + const uint8_t *p_struct_data; + uint8_t struct_length; + const uint8_t *p_vbuf1; + uint16_t vbuf1_length; + const uint8_t *p_vbuf2; + uint16_t vbuf2_length; + uintptr_t p_priv; + const uint8_t *p; + + p = deserialize_struct(p_buf, &p_struct_data, &struct_length); + p = deserialize_buf(p, &p_vbuf1, &vbuf1_length); + p = deserialize_buf(p, &p_vbuf2, &vbuf2_length); + p += 4; + if ((length != (p - p_buf)) || + (struct_length != m_size_s_b_b_p[fn_index])) + panic(-1); + + { + /* Always align structures on word boundary */ + uintptr_t struct_data[(struct_length + (sizeof(uintptr_t) - 1))/(sizeof(uintptr_t))]; + uintptr_t vbuf1[(vbuf1_length + (sizeof(uintptr_t) - 1))/(sizeof(uintptr_t))]; + uintptr_t vbuf2[(vbuf2_length + (sizeof(uintptr_t) - 1))/(sizeof(uintptr_t))]; + void * buf1 = NULL; + void * buf2 = NULL; + + memcpy(struct_data, p_struct_data, struct_length); + + if (vbuf1_length) { + memcpy(vbuf1, p_vbuf1, vbuf1_length); + buf1 = vbuf1; + } + if (vbuf2_length) { + memcpy(vbuf2, p_vbuf2, vbuf2_length); + buf2 = vbuf2; + } + + p = p_vbuf2 + vbuf2_length; + + /* little endian conversion */ + p_priv = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); + + m_fct_s_b_b_p[fn_index](struct_data, buf1, vbuf1_length, buf2, vbuf2_length, (void *)p_priv); + } +} + +void rpc_deserialize(const uint8_t * p_buf, uint16_t length) { + + uint8_t fn_index; + uint8_t sig_type; + + if (NULL != p_buf) { + sig_type = p_buf[0]; + fn_index = p_buf[1]; + + p_buf += 2; + length -= 2; + + switch(sig_type) { + case SIG_TYPE_NONE: + if (sizeof(m_fct_none)) + deserialize_none(fn_index, p_buf, length); + break; + case SIG_TYPE_S: + if (sizeof(m_fct_s)) + deserialize_s(fn_index, p_buf, length); + break; + case SIG_TYPE_P: + if (sizeof(m_fct_p)) + deserialize_p(fn_index, p_buf, length); + break; + case SIG_TYPE_S_B: + if (sizeof(m_fct_s_b)) + deserialize_s_b(fn_index, p_buf, length); + break; + case SIG_TYPE_B_B_P: + if (sizeof(m_fct_b_b_p)) + deserialize_b_b_p(fn_index, p_buf, length); + break; + case SIG_TYPE_S_P: + if (sizeof(m_fct_s_p)) + deserialize_s_p(fn_index, p_buf, length); + break; + case SIG_TYPE_S_B_P: + if (sizeof(m_fct_s_b_p)) + deserialize_s_b_p(fn_index, p_buf, length); + break; + case SIG_TYPE_S_B_B_P: + if (sizeof(m_fct_s_b_b_p)) + deserialize_s_b_b_p(fn_index, p_buf, length); + break; + default: + panic(-1); + break; + } + } +} diff --git a/system/libarc32_arduino101/drivers/rpc/rpc_functions_to_ble_core.h b/system/libarc32_arduino101/drivers/rpc/rpc_functions_to_ble_core.h new file mode 100644 index 00000000..5f742208 --- /dev/null +++ b/system/libarc32_arduino101/drivers/rpc/rpc_functions_to_ble_core.h @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2016 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RPC_FUNCTIONS_TO_BLE_CORE_H_ +#define RPC_FUNCTIONS_TO_BLE_CORE_H_ + +#include "gap_internal.h" +#include "gatt_internal.h" + +/* declare the list of functions sorted by signature */ +#define LIST_FN_SIG_NONE \ + FN_SIG_NONE(nble_gap_start_adv_req) \ + FN_SIG_NONE(nble_gap_stop_scan_req) + +#define LIST_FN_SIG_S \ + FN_SIG_S(nble_gap_set_adv_data_req, \ + struct nble_gap_ad_data_params *) \ + FN_SIG_S(nble_gap_set_adv_params_req, \ + struct nble_gap_adv_params *) \ + FN_SIG_S(nble_gap_start_scan_req, \ + const struct nble_gap_scan_params *) \ + FN_SIG_S(nble_gap_sm_config_req, \ + const struct nble_gap_sm_config_params *) \ + FN_SIG_S(nble_gap_sm_passkey_reply_req, \ + const struct nble_gap_sm_key_reply_req_params *) \ + FN_SIG_S(nble_gap_sm_bond_info_req, \ + const struct nble_gap_sm_bond_info_param *) \ + FN_SIG_S(nble_gap_sm_security_req, \ + const struct nble_gap_sm_security_params *) \ + FN_SIG_S(nble_gap_sm_clear_bonds_req, \ + const struct nble_gap_sm_clear_bond_req_params *) \ + FN_SIG_S(nble_set_bda_req, const struct nble_set_bda_params *) \ + FN_SIG_S(nble_gap_conn_update_req, \ + const struct nble_gap_connect_update_params *) \ + FN_SIG_S(nble_gattc_discover_req, \ + const struct nble_discover_params *) \ + FN_SIG_S(nble_gatts_wr_reply_req, \ + const struct nble_gatts_wr_reply_params *) \ + FN_SIG_S(nble_uas_rssi_calibrate_req, \ + const struct nble_uas_rssi_calibrate *) \ + FN_SIG_S(nble_gap_service_write_req, \ + const struct nble_gap_service_write_params *) \ + FN_SIG_S(nble_gap_disconnect_req, \ + const struct nble_gap_disconnect_req_params *) \ + FN_SIG_S(nble_gattc_read_req, \ + const struct ble_gattc_read_params *) \ + FN_SIG_S(nble_gap_tx_power_req, \ + const struct nble_gap_tx_power_params *) \ + FN_SIG_S(nble_get_version_req, \ + const struct nble_gap_get_version_param *) + +#define LIST_FN_SIG_P \ + FN_SIG_P(nble_gap_dtm_init_req, void *) \ + FN_SIG_P(nble_gap_read_bda_req, void *) \ + FN_SIG_P(nble_gap_stop_adv_req, void *) \ + FN_SIG_P(nble_gap_cancel_connect_req, void *) + +#define LIST_FN_SIG_S_B \ + FN_SIG_S_B(nble_gatt_register_req, \ + const struct nble_gatt_register_req *, \ + uint8_t *, uint16_t) \ + FN_SIG_S_B(nble_gatt_send_notif_req, \ + const struct nble_gatt_send_notif_params *, \ + const uint8_t *, uint16_t) \ + FN_SIG_S_B(nble_gatt_send_ind_req, \ + const struct nble_gatt_send_ind_params *, \ + const uint8_t *, uint8_t) \ + FN_SIG_S_B(nble_gatts_rd_reply_req, \ + const struct nble_gatts_rd_reply_params *, \ + uint8_t *, uint16_t) \ + FN_SIG_S_B(nble_gattc_write_req, \ + const struct ble_gattc_write_params *, \ + const uint8_t *, uint8_t) \ + FN_SIG_S_B(nble_gattc_read_multiple_req, \ + const struct ble_gattc_read_multiple_params *, \ + const uint16_t *, uint16_t) + +#define LIST_FN_SIG_B_B_P + +#define LIST_FN_SIG_S_P \ + FN_SIG_S_P(nble_gap_connect_req, \ + const struct nble_gap_connect_req_params *, void *) \ + FN_SIG_S_P(nble_gap_set_rssi_report_req, \ + const struct nble_rssi_report_params *, void *) \ + FN_SIG_S_P(nble_gap_dbg_req, \ + const struct nble_debug_params *, \ + void *) + +#define LIST_FN_SIG_S_B_P + +#define LIST_FN_SIG_S_B_B_P + +#endif /* RPC_FUNCTIONS_TO_BLE_CORE_H_ */ diff --git a/system/libarc32_arduino101/drivers/rpc/rpc_functions_to_quark.h b/system/libarc32_arduino101/drivers/rpc/rpc_functions_to_quark.h new file mode 100644 index 00000000..b08005e6 --- /dev/null +++ b/system/libarc32_arduino101/drivers/rpc/rpc_functions_to_quark.h @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2016 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RPC_FUNCTIONS_TO_QUARK_H_ +#define RPC_FUNCTIONS_TO_QUARK_H_ + +#include "gap_internal.h" +#include "gatt_internal.h" +#include "gap_internal.h" + +/* declare the list of functions sorted by signature */ +#define LIST_FN_SIG_NONE \ + FN_SIG_NONE(on_nble_up) + +#define LIST_FN_SIG_S \ + FN_SIG_S(on_nble_get_version_rsp, \ + const struct nble_version_response *) \ + FN_SIG_S(on_nble_gap_connect_evt, \ + const struct nble_gap_connect_evt *) \ + FN_SIG_S(on_nble_gap_disconnect_evt, \ + const struct nble_gap_disconnect_evt *) \ + FN_SIG_S(on_nble_gap_conn_update_evt, \ + const struct nble_gap_conn_update_evt *) \ + FN_SIG_S(on_nble_gap_sm_status_evt, \ + const struct nble_gap_sm_status_evt *) \ + FN_SIG_S(on_nble_gap_sm_passkey_display_evt, \ + const struct nble_gap_sm_passkey_disp_evt *) \ + FN_SIG_S(on_nble_gap_sm_passkey_req_evt, \ + const struct nble_gap_sm_passkey_req_evt *) \ + FN_SIG_S(on_nble_gap_rssi_evt, \ + const struct nble_gap_rssi_evt *) \ + FN_SIG_S(on_nble_common_rsp, \ + const struct nble_response *) \ + FN_SIG_S(on_nble_gap_connect_rsp, \ + const struct nble_response *) \ + FN_SIG_S(on_nble_gap_cancel_connect_rsp, \ + const struct nble_response *) \ + FN_SIG_S(on_nble_gap_read_bda_rsp, \ + const struct nble_service_read_bda_response *) \ + FN_SIG_S(on_nble_gap_sm_config_rsp, \ + struct nble_gap_sm_config_rsp *) \ + FN_SIG_S(on_nble_gap_sm_common_rsp, \ + const struct nble_gap_sm_response *) \ + FN_SIG_S(on_nble_set_bda_rsp, \ + const struct nble_set_bda_rsp *) \ + FN_SIG_S(on_nble_gap_set_rssi_report_rsp, \ + const struct nble_response *) \ + FN_SIG_S(on_nble_gap_dbg_rsp, \ + const struct nble_debug_resp *) \ + FN_SIG_S(on_nble_gap_dir_adv_timeout_evt, \ + const struct nble_gap_dir_adv_timeout_evt *) \ + FN_SIG_S(on_nble_gatts_send_notif_rsp, \ + const struct nble_gatt_notif_rsp *) \ + FN_SIG_S(on_nble_gatts_send_ind_rsp, \ + const struct nble_gatt_ind_rsp *) \ + FN_SIG_S(on_nble_gap_start_advertise_rsp, \ + const struct nble_response *) \ + FN_SIG_S(on_nble_gap_scan_start_stop_rsp, \ + const struct nble_response *) \ + FN_SIG_S(on_nble_gatts_read_evt, \ + const struct nble_gatt_rd_evt *) \ + FN_SIG_S(on_nble_gatts_write_exec_evt, \ + const struct nble_gatt_wr_exec_evt *) \ + FN_SIG_S(on_nble_uas_bucket_change, \ + const struct nble_uas_bucket_change *) \ + FN_SIG_S(on_nble_gattc_write_rsp, \ + const struct ble_gattc_write_rsp *) \ + FN_SIG_S(on_nble_gap_tx_power_rsp, \ + const struct nble_response *) + +#define LIST_FN_SIG_P \ + FN_SIG_P(on_nble_gap_dtm_init_rsp, void *) + +#define LIST_FN_SIG_S_B \ + FN_SIG_S_B(nble_log, const struct nble_log_s *, char *, \ + uint8_t) \ + FN_SIG_S_B(on_nble_gattc_value_evt, \ + const struct ble_gattc_value_evt *, \ + uint8_t *, uint8_t) \ + FN_SIG_S_B(on_nble_gatts_write_evt, \ + const struct nble_gatt_wr_evt *, \ + const uint8_t *, uint8_t) \ + FN_SIG_S_B(on_nble_gatt_register_rsp, \ + const struct nble_gatt_register_rsp *, \ + const struct nble_gatt_attr_handles *, \ + uint8_t) \ + FN_SIG_S_B(on_nble_gattc_discover_rsp, \ + const struct nble_gattc_discover_rsp *, \ + const uint8_t *, uint8_t) \ + FN_SIG_S_B(on_nble_gap_adv_report_evt, \ + const struct nble_gap_adv_report_evt *, \ + const uint8_t *, uint8_t) \ + FN_SIG_S_B(on_nble_gap_sm_bond_info_rsp, \ + const struct nble_gap_sm_bond_info_rsp*, \ + const bt_addr_le_t *, uint16_t) \ + FN_SIG_S_B(on_nble_gattc_read_rsp, \ + const struct ble_gattc_read_rsp *, \ + uint8_t *, uint8_t) \ + FN_SIG_S_B(on_nble_gattc_read_multiple_rsp, \ + const struct ble_gattc_read_rsp *, \ + uint8_t *, uint8_t) + +#define LIST_FN_SIG_B_B_P + +#define LIST_FN_SIG_S_P + +#define LIST_FN_SIG_S_B_P + +#define LIST_FN_SIG_S_B_B_P + +#endif /* RPC_FUNCTIONS_TO_QUARK_H_ */ diff --git a/system/libarc32_arduino101/drivers/rpc/rpc_serialize.c b/system/libarc32_arduino101/drivers/rpc/rpc_serialize.c new file mode 100644 index 00000000..e2144ee3 --- /dev/null +++ b/system/libarc32_arduino101/drivers/rpc/rpc_serialize.c @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2016 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "rpc.h" + +/* Include the functions called */ +#if defined(CONFIG_QUARK_SE_BLE_CORE) +#include "rpc_functions_to_quark.h" +#elif defined(CONFIG_SOC_QUARK_SE) +#include "rpc_functions_to_ble_core.h" +#elif defined(LINUX_HOST_RUNTIME) +// for the host compilation (to simulate connection to BLE controller) +#include "rpc_functions_to_ble_core.h" +#else +#error "File is compiled but should not" +#endif + +/* Build the functions exposed */ +/* Define the functions identifiers per signature */ +#define FN_SIG_NONE(__fn) fn_index_##__fn, +#define FN_SIG_S(__fn, __s) FN_SIG_NONE(__fn) +#define FN_SIG_P(__fn, __type) FN_SIG_NONE(__fn) +#define FN_SIG_S_B(__fn, __s, __type, __length) FN_SIG_NONE(__fn) +#define FN_SIG_B_B_P(__fn, __type1, __length1, __type2, __length2,__type3) FN_SIG_NONE(__fn) +#define FN_SIG_S_P(__fn, __s, __type) FN_SIG_NONE(__fn) +#define FN_SIG_S_B_P(__fn, __s, __type, __length, __type_ptr) FN_SIG_NONE(__fn) +#define FN_SIG_S_B_B_P(__fn, __s, __type1, __length1, __type2, __length2,__type3) FN_SIG_NONE(__fn) + +/* Build the list of function indexes -> this should match the array at deserialization */ +enum { LIST_FN_SIG_NONE fn_none_index_max }; +enum { LIST_FN_SIG_S fn_s_index_max }; +enum { LIST_FN_SIG_P fn_p_index_max }; +enum { LIST_FN_SIG_S_B fn_s_b_index_max }; +enum { LIST_FN_SIG_B_B_P fn_b_b_p_index_max }; +enum { LIST_FN_SIG_S_P fn_s_p_index_max }; +enum { LIST_FN_SIG_S_B_P fn_s_b_p_index_max }; +enum { LIST_FN_SIG_S_B_B_P fn_s_b_b_p_index_max }; + +/* Implement the functions using serialization API */ +#undef FN_SIG_NONE +#undef FN_SIG_S +#undef FN_SIG_P +#undef FN_SIG_S_B +#undef FN_SIG_B_B_P +#undef FN_SIG_S_P +#undef FN_SIG_S_B_P +#undef FN_SIG_S_B_B_P + +#define FN_SIG_NONE(__fn) \ + void __fn(void) { \ + rpc_serialize_none(fn_index_##__fn); \ + } \ + +#define FN_SIG_S(__fn, __s) \ + void __fn(__s p_s) { \ + rpc_serialize_s(fn_index_##__fn, p_s, sizeof(*p_s)); \ + } \ + +#define FN_SIG_P(__fn, __type) \ + void __fn(__type p_priv) { \ + rpc_serialize_p(fn_index_##__fn, p_priv); \ + } \ + +#define FN_SIG_S_B(__fn, __s, __type, __length) \ + void __fn(__s p_s, __type p_buf, __length length) { \ + rpc_serialize_s_b(fn_index_##__fn, p_s, sizeof(*p_s), p_buf, length); \ + } \ + +#define FN_SIG_B_B_P(__fn, __type1, __length1, __type2, __length2, __type3) \ + void __fn(__type1 p_buf1, __length1 length1, __type2 p_buf2, __length2 length2, __type3 p_priv) { \ + rpc_serialize_b_b_p(fn_index_##__fn, p_buf1, length1, p_buf2, length2, p_priv); \ + } \ + +#define FN_SIG_S_P(__fn, __s, __type) \ + void __fn(__s p_s, __type p_priv) { \ + rpc_serialize_s_p(fn_index_##__fn, p_s, sizeof(*p_s), p_priv); \ + } \ + +#define FN_SIG_S_B_P(__fn, __s, __type, __length, __type_ptr) \ + void __fn(__s p_s, __type p_buf, __length length, __type_ptr p_priv) { \ + rpc_serialize_s_b_p(fn_index_##__fn, p_s, sizeof(*p_s), p_buf, length, p_priv); \ + } + +#define FN_SIG_S_B_B_P(__fn, __s, __type1, __length1, __type2, __length2, __type3) \ + void __fn(__s p_s, __type1 p_buf1, __length1 length1, __type2 p_buf2, __length2 length2, __type3 p_priv) { \ + rpc_serialize_s_b_b_p(fn_index_##__fn, p_s, sizeof(*p_s), p_buf1, length1, p_buf2, length2, p_priv); \ + } \ + + +/* Build the functions */ +LIST_FN_SIG_NONE +LIST_FN_SIG_S +LIST_FN_SIG_P +LIST_FN_SIG_S_B +LIST_FN_SIG_B_B_P +LIST_FN_SIG_S_P +LIST_FN_SIG_S_B_P +LIST_FN_SIG_S_B_B_P + +#define SIG_TYPE_SIZE 1 +#define FN_INDEX_SIZE 1 +#define POINTER_SIZE 4 + +static void _send(uint8_t *buf, uint16_t length) { + rpc_transmit_cb(buf, length); +} + +static uint16_t encoded_structlen(uint8_t structlen) { + return 1 + structlen; +} + +static uint8_t *serialize_struct(uint8_t *p, const uint8_t *struct_data, uint8_t struct_length) { + *p++ = struct_length; + memcpy(p, struct_data, struct_length); + p += struct_length; + return p; +} + +static uint16_t encoded_buflen(const uint8_t *buf, uint16_t buflen) { + if (NULL == buf) + return 1; + else { + if (buflen < (1 << 7)) { + return 1 + buflen; + } + else + return 2 + buflen; + } +} + +static uint8_t *serialize_buf(uint8_t *p, const uint8_t *buf, uint16_t buflen) { + uint16_t varint; + + if (NULL == buf) + buflen = 0; + + varint = buflen; + + *p = varint & 0x7F; + if (varint >= (1 << 7)) { + *p |= 0x80; + p++; + *p = varint >> 7; + } + p++; + memcpy(p, buf, buflen); + p += buflen; + return p; +} + +static uint8_t *serialize_p(uint8_t *p, uintptr_t priv) { + *p++ = priv; + *p++ = (priv >> 8); + *p++ = (priv >> 16); + *p++ = (priv >> 24); + return p; +} + +void rpc_serialize_none(uint8_t fn_index) { + uint16_t length; + uint8_t * buf; + uint8_t * p; + + length = SIG_TYPE_SIZE + FN_INDEX_SIZE; + + p = buf = rpc_alloc_cb(length); + + *p++ = SIG_TYPE_NONE; + *p = fn_index; + + _send(buf, length); +} + +void rpc_serialize_s(uint8_t fn_index, const void * struct_data, uint8_t struct_length) { + uint16_t length; + uint8_t * buf; + uint8_t * p; + + length = SIG_TYPE_SIZE + FN_INDEX_SIZE + encoded_structlen(struct_length); + + p = buf = rpc_alloc_cb(length); + + *p++ = SIG_TYPE_S; + *p++ = fn_index; + p = serialize_struct(p, struct_data, struct_length); + + _send(buf, length); +} + + +void rpc_serialize_p(uint8_t fn_index, void * p_priv) { + uint16_t length; + uint8_t * buf; + uint8_t * p; + uintptr_t priv = (uintptr_t) p_priv; + + length = SIG_TYPE_SIZE + FN_INDEX_SIZE + POINTER_SIZE; + + p = buf = rpc_alloc_cb(length); + + *p++ = SIG_TYPE_P; + *p++ = fn_index; + p = serialize_p(p, priv); + + _send(buf, length); +} + +void rpc_serialize_s_b(uint8_t fn_index, const void * struct_data, uint8_t struct_length, const void * vbuf, uint16_t vbuf_length) { + uint16_t length; + uint8_t * buf; + uint8_t * p; + + length = SIG_TYPE_SIZE + FN_INDEX_SIZE + encoded_structlen(struct_length) + + encoded_buflen(vbuf, vbuf_length); + + p = buf = rpc_alloc_cb(length); + + *p++ = SIG_TYPE_S_B; + *p++ = fn_index; + p = serialize_struct(p, struct_data, struct_length); + p = serialize_buf(p, vbuf, vbuf_length); + + _send(buf, length); +} + +void rpc_serialize_b_b_p(uint8_t fn_index, const void * vbuf1, uint16_t vbuf1_length, const void * vbuf2, uint16_t vbuf2_length, void * p_priv) { + uint16_t length; + uint8_t * buf; + uint8_t * p; + uintptr_t priv = (uintptr_t) p_priv; + + length = SIG_TYPE_SIZE + FN_INDEX_SIZE + encoded_buflen(vbuf1, vbuf1_length) + + encoded_buflen(vbuf2, vbuf2_length) + POINTER_SIZE; + + p = buf = rpc_alloc_cb(length); + + *p++ = SIG_TYPE_B_B_P; + *p++ = fn_index; + p = serialize_buf(p, vbuf1, vbuf1_length); + p = serialize_buf(p, vbuf2, vbuf2_length); + p = serialize_p(p, priv); + + _send(buf, length); +} + +void rpc_serialize_s_p(uint8_t fn_index, const void * struct_data, uint8_t struct_length, void * p_priv) { + uint16_t length; + uint8_t * buf; + uint8_t * p; + uintptr_t priv = (uintptr_t) p_priv; + + length = SIG_TYPE_SIZE + FN_INDEX_SIZE + encoded_structlen(struct_length) + + POINTER_SIZE; + + p = buf = rpc_alloc_cb(length); + + *p++ = SIG_TYPE_S_P; + *p++ = fn_index; + p = serialize_struct(p, struct_data, struct_length); + p = serialize_p(p, priv); + + _send(buf, length); +} + +void rpc_serialize_s_b_p(uint8_t fn_index, const void * struct_data, uint8_t struct_length, + const void * vbuf, uint16_t vbuf_length, void * p_priv) { + uint16_t length; + uint8_t * buf; + uint8_t * p; + uintptr_t priv = (uintptr_t) p_priv; + + length = SIG_TYPE_SIZE + FN_INDEX_SIZE + encoded_structlen(struct_length) + + encoded_buflen(vbuf, vbuf_length) + POINTER_SIZE; + + p = buf = rpc_alloc_cb(length); + + *p++ = SIG_TYPE_S_B_P; + *p++ = fn_index; + p = serialize_struct(p, struct_data, struct_length); + p = serialize_buf(p, vbuf, vbuf_length); + p = serialize_p(p, priv); + + _send(buf, length); +} + +void rpc_serialize_s_b_b_p(uint8_t fn_index, const void * struct_data, uint8_t struct_length, + const void * vbuf1, uint16_t vbuf1_length, const void * vbuf2, uint16_t vbuf2_length, void * p_priv) { + + uint16_t length; + uint8_t * buf; + uint8_t * p; + uintptr_t priv = (uintptr_t) p_priv; + + length = SIG_TYPE_SIZE + FN_INDEX_SIZE + encoded_structlen(struct_length) + + encoded_buflen(vbuf1, vbuf1_length) + + encoded_buflen(vbuf2, vbuf2_length) + POINTER_SIZE; + + p = buf = rpc_alloc_cb(length); + + *p++ = SIG_TYPE_S_B_B_P; + *p++ = fn_index; + p = serialize_struct(p, struct_data, struct_length); + p = serialize_buf(p, vbuf1, vbuf1_length); + p = serialize_buf(p, vbuf2, vbuf2_length); + p = serialize_p(p, priv); + + _send(buf, length); +} diff --git a/system/libarc32_arduino101/framework/include/cfw/cfw.h b/system/libarc32_arduino101/framework/include/cfw/cfw.h index 9c9f040f..311dcf7b 100644 --- a/system/libarc32_arduino101/framework/include/cfw/cfw.h +++ b/system/libarc32_arduino101/framework/include/cfw/cfw.h @@ -113,7 +113,7 @@ typedef struct svc_client_handle_ { * Passed in the conn field of struct cfw_message for request messages */ void * server_handle; -} svc_client_handle_t; +} svc_client_handle_t, cfw_service_conn_t; struct cfw_message * cfw_alloc_message(int size, OS_ERR_TYPE * err); diff --git a/system/libarc32_arduino101/framework/include/cfw/cfw_client.h b/system/libarc32_arduino101/framework/include/cfw/cfw_client.h index 2a1ae2ba..2154208f 100644 --- a/system/libarc32_arduino101/framework/include/cfw/cfw_client.h +++ b/system/libarc32_arduino101/framework/include/cfw/cfw_client.h @@ -40,22 +40,6 @@ * @{ */ -/** - * Create a handle to the component framework. - * This handle is to be used for all other requests - * to the component framework - * - * Implementation is different in the master and the slave contexts. - * The master context will be pseudo-synchronous, while the slave - * implementation will actually pass a message to the master context - * in order to register a new client. - * - * \param queue pointer to service queue - * \param cb the callback that will be called for each message reception - * \param param the param passed along with the message to the callback - */ -cfw_handle_t cfw_init(void * queue, handle_msg_cb_t cb, void * param); - /** * Allocate a request message for a service. diff --git a/system/libarc32_arduino101/framework/include/cfw_platform.h b/system/libarc32_arduino101/framework/include/cfw_platform.h index 4e361961..aa5cb29e 100644 --- a/system/libarc32_arduino101/framework/include/cfw_platform.h +++ b/system/libarc32_arduino101/framework/include/cfw_platform.h @@ -39,7 +39,6 @@ extern "C" { #endif void cfw_platform_init(void); -void cfw_platform_nordic_init(void); T_QUEUE cfw_get_service_queue(void); #ifdef __cplusplus diff --git a/system/libarc32_arduino101/framework/include/infra/ipc_uart.h b/system/libarc32_arduino101/framework/include/infra/ipc_uart.h index 5f5cbe76..6c8eab4f 100644 --- a/system/libarc32_arduino101/framework/include/infra/ipc_uart.h +++ b/system/libarc32_arduino101/framework/include/infra/ipc_uart.h @@ -78,25 +78,6 @@ /* optional sync frame payload */ #define SYNC_FRAME_DATA(_frame_) ((unsigned char *)&(_frame_)[20]) -#define IPC_CHANNEL_STATE_CLOSED 0 -#define IPC_CHANNEL_STATE_OPEN 1 - -#define IPC_UART_MAX_CHANNEL 4 - -struct ipc_uart_channels { - uint16_t index; - uint16_t state; - void (*cb) (uint8_t cpu_id, int chan, int len, void * data); -}; - -void * uart_ipc_channel_open(int channel, void(*cb)(uint8_t cpu_id, int chan, int len, void * data)); -int uart_ipc_send_message(void * handle, int len, void *p_data); -void uart_ipc_set_channel(void * ipc_channel); -void * uart_ipc_get_channel(void); -int uart_ipc_send_sync_resp(int channel, int request_id, int param1, int param2, void * ptr); -void uart_ipc_init(int num); -void uart_ipc_disable(int num); - /** @} */ #endif /* _IPC_UART_H_ */ diff --git a/system/libarc32_arduino101/framework/include/infra/log.h b/system/libarc32_arduino101/framework/include/infra/log.h index 528b0d99..07848310 100644 --- a/system/libarc32_arduino101/framework/include/infra/log.h +++ b/system/libarc32_arduino101/framework/include/infra/log.h @@ -36,6 +36,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + /** * @defgroup infra_log Log * @ingroup infra @@ -260,7 +264,11 @@ void log_resume(); * @param module the ID of the log module related to this message * @param format the printf-like string format */ -#define pr_debug(module, format,...) pr_debug_ ## module(format, ##__VA_ARGS__) +#define pr_debug(module, format,...) log_printk(LOG_LEVEL_DEBUG, module, format, ##__VA_ARGS__) + +#ifdef __cplusplus +} +#endif /** @} */ diff --git a/system/libarc32_arduino101/framework/include/log_modules b/system/libarc32_arduino101/framework/include/log_modules index c7246635..401bbae9 100644 --- a/system/libarc32_arduino101/framework/include/log_modules +++ b/system/libarc32_arduino101/framework/include/log_modules @@ -25,3 +25,4 @@ DEFINE_LOGGER_MODULE(LOG_MODULE_DRV, "DRV", 0) DEFINE_LOGGER_MODULE(LOG_MODULE_CUNIT, "CUNIT", 0) DEFINE_LOGGER_MODULE(LOG_MODULE_CFW, "CFW", 0) DEFINE_LOGGER_MODULE(LOG_MODULE_GPIO_SVC, "GPIO_SVC", 0) +DEFINE_LOGGER_MODULE(LOG_MODULE_APP, "APP", 0) diff --git a/system/libarc32_arduino101/framework/include/panic_api.h b/system/libarc32_arduino101/framework/include/panic_api.h index 70aea89c..5a579280 100644 --- a/system/libarc32_arduino101/framework/include/panic_api.h +++ b/system/libarc32_arduino101/framework/include/panic_api.h @@ -29,5 +29,5 @@ */ // TODO - replace with a proper implementation of panic() -#define panic(x) _do_fault(); +//#define panic(x) _do_fault(); #define force_panic() panic(-1) diff --git a/system/libarc32_arduino101/framework/include/services/ble/ble_service.h b/system/libarc32_arduino101/framework/include/services/ble/ble_service.h index fa14941b..8917e61c 100644 --- a/system/libarc32_arduino101/framework/include/services/ble/ble_service.h +++ b/system/libarc32_arduino101/framework/include/services/ble/ble_service.h @@ -33,29 +33,205 @@ #include +/* For MSG_ID_BLE_SERVICE_BASE */ +#include "services/services_ids.h" + +/* For bt_uuid */ +#include "bluetooth/gatt.h" + +#include "bluetooth/bluetooth.h" + +#include "cfw/cfw.h" + +/* Forward declarations */ +struct _ble_service_cb; +struct bt_conn; + /** - * @addtogroup ble_service + * @cond + * @defgroup ble_service BLE Service + * + * Bluetooth Low Energy (BLE) application level service. + * + * This service provides BLE service, abstracting most of the complexity of the underlying BLE services/profiles. + * + * @ingroup services + * * @{ + */ + +/* + * CFW Message ID base definitions for BLE services. + */ + +/* BLE Service Message ID definitions. */ +#define MSG_ID_BLE_SERVICE_RSP (MSG_ID_BLE_SERVICE_BASE + 0x40) +#define MSG_ID_BLE_SERVICE_EVT (MSG_ID_BLE_SERVICE_BASE + 0x80) + +/** BLE High level Message IDs used for request, response, events. */ +enum BLE_MSG_ID { + MSG_ID_BLE_ENABLE_RSP = MSG_ID_BLE_SERVICE_RSP, /**< Message ID for enable response, of type @ref ble_enable_rsp */ + MSG_ID_BLE_INIT_SVC_RSP, /**< Message ID for init service response, of type @ref ble_init_svc_rsp */ + + /* BLE direct test mode command */ + MSG_ID_BLE_DBG_RSP, /**< Message ID for DTM command response, of type @ref ble_dbg_req_rsp */ + + MSG_ID_BLE_SERVICE_RSP_LAST, + + /* events */ + MSG_ID_BLE_ADV_TO_EVT = MSG_ID_BLE_SERVICE_EVT, /**< Message ID for struct @ref ble_adv_to_evt */ + MSG_ID_BLE_SERVICE_EVT_LAST +}; + +/** Macro to convert milliseconds to a specific unit */ +#define MSEC_TO_1_25_MS_UNITS(TIME) (((TIME) * 1000) / 1250) +#define MSEC_TO_10_MS_UNITS(TIME) ((TIME) / 10) + +#define BLE_GAP_SEC_RAND_LEN 8 /**< Random Security number length (64 bits) */ +#define BLE_GAP_SEC_MAX_KEY_LEN 16 /**< Maximum security key len (LTK, CSRK) */ + +/** + * Advertisement options. + */ +enum BLE_GAP_ADV_OPTIONS { + BLE_GAP_OPT_ADV_DEFAULT = 0, /**< no specific option */ + BLE_GAP_OPT_ADV_WHITE_LISTED = 0x02 /**< use white list and only report whitelisted devices */ +}; + +/** + * LE security modes. * + * see BT spec PART C, 10.2 + * + * - Security mode 1 + * - Level 1: No security at all (service may use data signing) + * - Level 2: Unauthenticated (no MITM protection pairing with encryption + * - Level 3: Authenticated (MITM protection) pairing with encryption + * - Level 4: Authenticated (MITM protection) LE Secure Connection wi + * + * - Security mode 2 (data signing) + * - Level 1: Unauthenticated pairing with data signing + * - Level 2: Authenticated (MITM protection) with data signing */ +enum BLE_GAP_SEC_MODES { + GAP_SEC_NO_PERMISSION = 0, /**< No access permitted. */ + GAP_SEC_LEVEL_1, + GAP_SEC_LEVEL_2, + GAP_SEC_LEVEL_3, + GAP_SEC_LEVEL_4, + GAP_SEC_MODE_1 = 0x10, + GAP_SEC_MODE_2 = 0x20 /**< only used for data signing, level 1 or 2 */ +}; -/** 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_GAP_BASE = 0x100, /**< GAP specific error base */ - BLE_STATUS_GATT_BASE = 0x200, /**< GATT specific Error base */ +/** + * Security manager passkey type. + */ +enum BLE_GAP_SM_PASSKEY_TYPE { + BLE_GAP_SM_PK_NONE = 0, /**< No key (may be used to reject). */ + BLE_GAP_SM_PK_PASSKEY, /**< Security data is a 6-digit passkey. */ + BLE_GAP_SM_PK_OOB, /**< Security data is 16 bytes of OOB data */ }; -typedef uint16_t ble_status_t; /**< Response and event BLE service status type @ref BLE_STATUS */ +/** + * Connection Parameter update request event. + */ +struct ble_gap_conn_param_update_req_evt { + struct bt_le_conn_param param; +}; + +/* - BLE_SERVICE_GAP_API.H */ + + +/** Generic BLE status response message. */ +struct ble_rsp { + struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ + int status; /**< Response status */ +}; + +/** Generic BLE response with connection reference and status. */ +struct ble_conn_rsp { + struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ + struct bt_conn *conn; /**< Connection reference */ + int status; /**< Status */ +}; + +/** BLE Enable configuration options. */ +struct ble_enable_config { + bt_addr_le_t * p_bda; /**< Optional BT device address. If NULL, internal unique static random will be used */ + struct bt_le_conn_param central_conn_params; /**< Central supported range */ +}; + +/** Parameters of MSG_ID_BLE_ENABLE_RSP. */ +struct ble_enable_rsp { + struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ + int status; /**< Response status */ + uint8_t enable; /**< Enable state: 0:Disabled, 1:Enabled */ + bt_addr_le_t bd_addr; +}; + +/** + * Attribute handle range definition. + */ +struct ble_gatt_handle_range { + uint16_t start_handle; + uint16_t end_handle; +}; + +/** Parameters of the current connection. */ +struct ble_connection_values { + uint16_t interval; /**< Connection interval (unit 1.25 ms) */ + uint16_t latency; /**< Connection latency (unit interval) */ + uint16_t supervision_to; /**< Connection supervision timeout (unit 10ms)*/ +}; + +/** Parameters for @ref MSG_ID_BLE_INIT_SVC_RSP. */ +struct ble_init_svc_rsp { + struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ + int status; +}; + +/** Authentication data. */ +struct ble_auth_data { + union { + uint8_t passkey[6]; /**< 6 digit key (000000 - 999999) */ + uint8_t obb_data[16]; /**< 16 byte of OBB data */ + }; + uint8_t type; /**< @ref BLE_GAP_SM_PASSKEY_TYPE */ +}; + +/** Parameters for @ref MSG_ID_BLE_ADV_TO_EVT. */ +struct ble_adv_to_evt { + struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ +}; + +/** + * BLE debug rsp message. + */ + +/* + * BLE debug req message. + */ +struct ble_dbg_req_rsp { + struct cfw_message header; + uint32_t u0; + uint32_t u1; +}; + +/** Enable/Disable BLE stack. To be called before any BLE service related call. + * + * @param p_service_conn client service connection (cfw service connection) + * @param enable 1: enable BLE stack 0: disable BLE stack + * @param p_config configuration parameters when enabling BLE. shall be null in case of BLE disable. @ref ble_enable_config + * @param p_priv pointer to private structure returned in a response + * + * @return @ref OS_ERR_TYPE + * @note Expected notification: + * - Message with @ref MSG_ID_BLE_ENABLE_RSP and type @ref ble_enable_rsp. + */ +int ble_service_enable(cfw_service_conn_t * p_service_conn, uint8_t enable, + const struct ble_enable_config * p_config, + void *p_priv); + +/** @endcond */ /** @}*/ #endif diff --git a/system/libarc32_arduino101/framework/include/services/ble/ble_service_gap_api.h b/system/libarc32_arduino101/framework/include/services/ble/ble_service_gap_api.h deleted file mode 100644 index 6fd62cec..00000000 --- a/system/libarc32_arduino101/framework/include/services/ble/ble_service_gap_api.h +++ /dev/null @@ -1,1036 +0,0 @@ -/* - * 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_SERVICE_GAP_H__ -#define __BLE_SERVICE_GAP_H__ - -#include "cfw/cfw.h" -#include "cfw/cfw_client.h" -#include "infra/version.h" -#include "ble_service_msg.h" -#include "ble_service.h" - -/** - * @defgroup ble_core_service BLE Core Service GAP/GATT APIs - * - * BLE Core service GAP/GATT APIs used by BLE service. - * - * @ingroup ble_service - * @{ - */ - -/** Macro to convert milliseconds to a specific unit */ -#define MSEC_TO_0_625_MS_UNITS(TIME) (((TIME) * 1000) / 625) -#define MSEC_TO_1_25_MS_UNITS(TIME) (((TIME) * 1000) / 1250) -#define MSEC_TO_10_MS_UNITS(TIME) ((TIME) / 10) - -/** - * BLE GAP Status return codes. - */ -enum BLE_SVC_GAP_STATUS_CODES { - BLE_SVC_GAP_STATUS_SUCCESS = BLE_STATUS_SUCCESS, /**< GAP success */ - BLE_SVC_GAP_STATUS_ERROR = BLE_STATUS_GATT_BASE, /**< Generic GAP error */ - BLE_SVC_GAP_STATUS_INVALID_UUID_LIST, /**< Invalid UUID list provided (e.g. advertisement) */ - /* TODO: add more status codes */ -}; - -/** - * BLE GAP addr types. - * - * BLE GAP supported address types - */ -enum BLE_ADDR_TYPES { - BLE_ADDR_PUBLIC = 0, /**< BD address assigned by IEEE */ - BLE_ADDR_PRIVATE_RANDOM_STATIC, /**< Random address */ - BLE_ADDR_RRIVATE_RANDOM_PRIVATE_RESOLVABLE, /**< Resolvable Private Random address */ - BLE_ADDR_PRIVATE_RANDOM_PRIVATE_NONRESOLVABLE /**< Non-resolvable Private Random address */ -}; - -/** - * BT/BLE address Length. - */ -#define BLE_ADDR_LEN 6 - -#define BLE_GAP_SEC_RAND_LEN 8 /**< Random Security number length (64 bits) */ -#define BLE_GAP_SEC_MAX_KEY_LEN 16 /**< Maximum security key len (LTK, CSRK) */ - -#define BLE_SVC_GAP_HANDLE_INVALID 0xffff /**< Invalid GAP connection handle */ - -/** - * Device GAP name characteristic write permission. - * - * If the characteristic shall be writable, use a combination of the values - * defined in @ref BLE_GAP_SEC_MODES - */ -#define BLE_DEVICE_NAME_WRITE_PERM GAP_SEC_NO_PERMISSION - -typedef struct { - uint8_t type; /**< BLE Address type @ref BLE_ADDR_TYPES */ - uint8_t addr[BLE_ADDR_LEN]; - /**< BD address, little endian format */ -} ble_addr_t; - -/** - * GAP device roles. - */ -enum BLE_ROLES { - BLE_ROLE_INVALID = 0, - BLE_ROLE_PERIPHERAL = 0x01, - BLE_ROLE_CENTRAL = 0x02 -}; - -typedef uint8_t ble_role_t; - -/** - * BLE core (GAP, GATT) Message IDs used for request, response, events and indications. - */ -enum BLE_GAP_MSG_ID { - MSG_ID_BLE_GAP_WR_CONF_REQ = MSG_ID_BLE_GAP_BASE, - MSG_ID_BLE_GAP_RD_BDA_REQ, - MSG_ID_BLE_GAP_WR_ADV_DATA_REQ, - MSG_ID_BLE_GAP_WR_WHITE_LIST_REQ, - MSG_ID_BLE_GAP_CLR_WHITE_LIST_REQ, - MSG_ID_BLE_GAP_ENABLE_ADV_REQ, - MSG_ID_BLE_GAP_DISABLE_ADV_REQ, - MSG_ID_BLE_GAP_CONN_UPDATE_REQ, - MSG_ID_BLE_GAP_DISCONNECT_REQ, - MSG_ID_BLE_GAP_SERVICE_WRITE_REQ, - MSG_ID_BLE_GAP_SERVICE_READ_REQ, - MSG_ID_BLE_GAP_SM_CONFIG_REQ, - MSG_ID_BLE_GAP_SM_PAIRING_REQ, - MSG_ID_BLE_GAP_SM_PASSKEY_REQ, - MSG_ID_BLE_GAP_SET_RSSI_REPORT_REQ, - MSG_ID_BLE_GAP_SCAN_START_REQ, - MSG_ID_BLE_GAP_SCAN_STOP_REQ, - MSG_ID_BLE_GAP_CONNECT_REQ, - MSG_ID_BLE_GAP_CONNECT_CANCEL_REQ, - MSG_ID_BLE_GAP_SET_OPTIONS_REQ, - MSG_ID_BLE_GAP_GENERIC_CMD_REQ, - MSG_ID_BLE_GAP_GET_VERSION_REQ, - MSG_ID_BLE_GAP_DTM_INIT_REQ, - MSG_ID_BLE_CTRL_LOG_REQ, - MSG_ID_BLE_GAP_REQ_LAST, - - /** BLE GAP Response Messages IDs. */ - MSG_ID_BLE_GAP_WR_CONF_RSP = MSG_ID_BLE_GAP_RSP, - /**< Write controller config: own Bluetooth Device Address, tx power */ - MSG_ID_BLE_GAP_RD_BDA_RSP, /**< Read own Bluetooth Device Address */ - MSG_ID_BLE_GAP_WR_ADV_DATA_RSP, /**< Write Advertising Data and Scan response data */ - MSG_ID_BLE_GAP_WR_WHITE_LIST_RSP, /**< Write white list to controller */ - MSG_ID_BLE_GAP_CLR_WHITE_LIST_RSP, /**< Clear current white list */ - MSG_ID_BLE_GAP_ENABLE_ADV_RSP, /**< Enable Advertising */ - MSG_ID_BLE_GAP_DISABLE_ADV_RSP, /**< Disable Advertising */ - MSG_ID_BLE_GAP_CONN_UPDATE_RSP, /**< Update Connection */ - MSG_ID_BLE_GAP_DISCONNECT_RSP, /**< Disconnect */ - MSG_ID_BLE_GAP_SERVICE_WRITE_RSP, /**< Write GAP Service specific like device name, appearance and PPCPparameters */ - MSG_ID_BLE_GAP_SERVICE_READ_RSP, /**< Read GAP Service specific like device name, appearance and PPCPparameters */ - MSG_ID_BLE_GAP_SM_CONFIG_RSP, /**< Response to @ref ble_gap_sm_config */ - MSG_ID_BLE_GAP_SM_PAIRING_RSP, /**< Response to @ref ble_gap_sm_pairing_req */ - MSG_ID_BLE_GAP_SM_PASSKEY_RSP, /**< Response to @ref ble_gap_sm_passkey_reply */ - MSG_ID_BLE_GAP_SET_RSSI_REPORT_RSP, /**< Enable/Disable reporting of changes in RSSI */ - MSG_ID_BLE_GAP_SCAN_START_RSP, /**< Start Scanning */ - MSG_ID_BLE_GAP_SCAN_STOP_RSP, /**< Stop Scanning */ - MSG_ID_BLE_GAP_CONNECT_RSP, /**< Start Connection procedure */ - MSG_ID_BLE_GAP_CONNECT_CANCEL_RSP, /**< Cancel ongoing connection procedure */ - MSG_ID_BLE_GAP_SET_OPTIONS_RSP, /**< Set gap options (e.g. co-ex, master/central role) */ - MSG_ID_BLE_GAP_GENERIC_CMD_RSP, /**< Generic non connection related requests */ - MSG_ID_BLE_GAP_GET_VERSION_RSP, - MSG_ID_BLE_GAP_DTM_INIT_RSP, - MSG_ID_BLE_CTRL_LOG_RSP, /**< BLE controller logging message */ - MSG_ID_BLE_GAP_RSP_LAST, - - /** GAP related events. */ - MSG_ID_BLE_GAP_CONNECT_EVT = MSG_ID_BLE_GAP_EVT, /**< Connection established */ - MSG_ID_BLE_GAP_DISCONNECT_EVT, /**< Disconnect from peer */ - MSG_ID_BLE_GAP_CONN_UPDATE_EVT, /**< Connection Parameters update event (in central, they have been updated, in peripheral, also includes the status of the request) */ - MSG_ID_BLE_GAP_SM_PAIRING_STATUS_EVT, /**< Pairing request status event */ - MSG_ID_BLE_GAP_SM_PASSKEY_REQ_EVT, /**< Pairing passkey request (6 digits or 16 byte OOB data) */ - MSG_ID_BLE_GAP_TO_EVT, /**< GAP Timeout event */ - MSG_ID_BLE_GAP_ADV_DATA_EVT, /**< Advertising raw data event (central role) */ - MSG_ID_BLE_GAP_RSSI_EVT, /**< Signal strength change event */ - MSG_ID_BLE_GAP_GENERIC_CMD_EVT, /**< Generic command request event */ - MSG_ID_BLE_CTRL_LOG_EVT, /**< BLE Controller Logging Events */ - MSG_ID_BLE_GAP_EVT_LAST, -}; - -/** - * Generic BLE Status Response. - * Short status response for commands not returning any additional data - */ -struct ble_rsp { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - ble_status_t status; /**< Response status @ref BLE_STATUS */ -}; - -/** - * Connection requested parameters. - */ -struct ble_gap_connection_params { - uint16_t interval_min; /**< minimal connection interval: range 0x0006 to 0x0c80 (unit 1.25ms) */ - uint16_t interval_max; /**< maximum connection interval: range 0x0006 to 0x0c80 must be bigger then min! */ - uint16_t slave_latency; /**< maximum connection slave latency: 0x0000 to 0x01f3 */ - uint16_t link_sup_to; /**< link supervision timeout: 0x000a to 0x0c80 (unit 10ms) */ -}; - -/** - * Connection values. - */ -struct ble_gap_connection_values { - uint16_t interval; /**< Connection interval (unit 1.25 ms) */ - uint16_t latency; /**< Connection latency (unit interval) */ - uint16_t supervision_to; /**< Connection supervision timeout (unit 10ms)*/ -}; - -/** - * Initial GAP configuration - */ -struct ble_wr_config { - ble_addr_t *p_bda; - uint8_t *p_name; /**< GAP Device name, NULL terminated! */ - uint16_t appearance; /**< see BLE spec */ - int8_t tx_power; - struct ble_gap_connection_params peripheral_conn_params; /**< Peripheral preferred */ - struct ble_gap_connection_params central_conn_params; /**< Central supported range */ -}; - -/** Read BD address response. */ -typedef struct { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - ble_status_t status; /**< Response status @ref BLE_STATUS */ - ble_addr_t bd; /**< if status ok @ref ble_addr_t */ -} ble_bda_rd_rsp_t; - -struct ble_gap_adv_rsp_data { - uint8_t *p_data; /**< max 31 bytes! */ - uint8_t len; -}; - -/** - * Advertising types, see BT spec vol 6, Part B, chapter 2.3. - */ -enum GAP_ADV_TYPES { - ADV_IND = 0x00, /**< Connectable undirected advertising */ - ADV_DIRECT_IND = 0x01, /**< Connectable high duty cycle advertising */ - ADV_NONCONN_IND = 0x02, /**< Non connectable undirected advertising */ - ADV_SCAN_IND = 0x06, /**< Scannable undirected advertising */ - ADV_SCAN_RSP = 0x81, /**< Scan response, only a return value in @ref ble_gap_adv_data_evt_t */ - ADV_RESERVED /* keep last */ -}; - -typedef struct { - uint8_t irk[BLE_GAP_SEC_MAX_KEY_LEN]; - /**< Identity Resolving Key (IRK) */ -} ble_gap_irk_info_t; - -struct ble_gap_whitelist_info { - ble_addr_t **pp_bd; /**< list of bd addresses */ - ble_gap_irk_info_t **pp_key; /**< list of irk keys (for address resolution offload) */ - uint8_t bd_count; /**< number of bd addresses */ - uint8_t key_count; /**< number of keys */ -}; - -/** - * Advertisement options. - */ -enum BLE_GAP_ADV_OPTIONS { - BLE_GAP_OPT_ADV_DEFAULT = 0, /**< no specific option */ - BLE_GAP_OPT_ADV_WHITE_LISTED = 0x02 /**< use white list and only report whitelisted devices */ -}; - -/** - * Advertisement parameters. - */ -typedef struct { - uint16_t timeout; - uint16_t interval_min; /**< min interval 0xffff: use default 0x0800 */ - uint16_t interval_max; /**< max interval 0xffff: use default 0x0800 */ - uint8_t type; /**< advertisement types @ref GAP_ADV_TYPES */ - uint8_t filter_policy; /**< filter policy to apply with white list */ - ble_addr_t *p_peer_bda; /**< bd address of peer device in case of directed advertisement */ - uint8_t options; /**< options see @ref BLE_GAP_ADV_OPTIONS (to be ORed) */ -} ble_gap_adv_param_t; - -/** - * Generic BLE Status. Response - * Short status response for commands not returning any additional data - */ -typedef struct { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - ble_status_t status; /**< Response status @ref BLE_STATUS */ - uint32_t wl_handle; /**< reference handle. to be used for clearing it later */ -} ble_gap_wr_white_list_rsp_t; - - -/** - * Appearance read response message. - */ -typedef struct { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - ble_status_t status; /**< Response status @ref BLE_STATUS */ - uint16_t uuid; /**< value of GAP appearance characteristic */ -} ble_rd_appearance_rsp_t; - -/** - * LE security modes. - * - * see BT spec PART C, 10.2 - * - * - Security mode 1 - * - Level 1: No security at all (service may use data signing) - * - Level 2: Unauthenticated (no MITM protection pairing with encryption - * - Level 3: Authenticated (MITM protection) pairing with encryption - * - Level 4: Authenticated (MITM protection) LE Secure Connection wi - * - * - Security mode 2 (data signing) - * - Level 1: Unauthenticated pairing with data signing - * - Level 2: Authenticated (MITM protection) with data signing - */ -enum BLE_GAP_SEC_MODES { - GAP_SEC_NO_PERMISSION = 0, /**< No access permitted. */ - GAP_SEC_LEVEL_1, - GAP_SEC_LEVEL_2, - GAP_SEC_LEVEL_3, - GAP_SEC_LEVEL_4, - GAP_SEC_MODE_1 = 0x10, - GAP_SEC_MODE_2 = 0x20 /**< only used for data signing, level 1 or 2 */ -}; - -struct ble_gap_svc_local_name { - uint8_t sec_mode; /**< security mode for writing device name, @ref BLE_GAP_SEC_MODES */ - uint8_t authorization; /**< 0: no authorization, 1: authorization required */ - uint8_t len; /**< device name length (0-248) */ - const uint8_t *p_name; /**< name to to write */ -}; - -enum BLE_GAP_SVC_ATTR_TYPE { - GAP_SVC_ATTR_NAME = 0, /**< Device Name, UUID 0x2a00 */ - GAP_SVC_ATTR_APPEARANCE, /**< Appearance, UUID 0x2a01 */ - GAP_SVC_ATTR_PPCP = 4, /**< Peripheral Preferred Connection Parameters (PPCP), UUID 0x2a04 */ - GAP_SVC_ATTR_CAR = 0xa6, /**< Central Address Resolution (CAR), UUID 0x2aa6, BT 4.2 */ -}; - -struct ble_gap_service_write_params { - uint16_t attr_type; /**< GAP Characteristics attribute type @ref BLE_GAP_SVC_ATTR_TYPE */ - union { - struct ble_gap_svc_local_name name; - uint16_t appearance; /**< Appearance UUID */ - struct ble_gap_connection_params conn_params; - /**< Preferred Peripheral Connection Parameters */ - uint8_t car; /**< Central Address Resolution support 0: no, 1: yes */ - }; -}; - -struct ble_gap_service_read_rsp { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - ble_status_t status; /**< status of read operation @ref BLE_STATUS, in case failure union shall be empty */ - uint16_t attr_type; /**< type of attribute returned (valid even in failure case! */ - union { - struct ble_gap_svc_local_name name; - uint16_t appearance; /**< Appearance UUID */ - struct ble_gap_connection_params conn_params; /**< Preferred Peripheral Connection Parameters */ - uint8_t car; /** Central Address Resolution support 0: no, 1: yes */ - }; -}; - -/** - * GAP security manager options for bonding/authentication procedures, see Vol 3: Part H, 3.5. - */ -enum BLE_GAP_SM_OPTIONS { - BLE_GAP_BONDING = 0x01, /**< SMP supports bonding */ - BLE_GAP_MITM = 0x04, /**< SMP requires Man In The Middle protection */ - BLE_GAP_OOB = 0x08 /**< SMP supports Out Of Band data */ -}; - -/** - * IO capabilities, see Vol 3: PART H, 3.5. - */ -enum BLE_GAP_IO_CAPABILITIES { - BLE_GAP_IO_DISPLAY_ONLY = 0, - BLE_GAP_IO_DISPLAY_YESNO = 1, - BLE_GAP_IO_KEYBOARD_ONLY = 2, - BLE_GAP_IO_NO_INPUT_NO_OUTPUT = 3, - BLE_GAP_IO_KEYBOARD_DISPLAY = 4 -}; - -/** - * Security manager configuration parameters. - * - * options and io_caps will define there will be a passkey request or not. - * It is assumed that io_caps and options are compatible. - */ -struct ble_gap_sm_config_params { - uint8_t options; /**< Security options (@ref BLE_GAP_SM_OPTIONS) */ - uint8_t io_caps; /**< I/O Capabilities to allow passkey exchange (@ref BLE_GAP_IO_CAPABILITIES) */ - uint8_t key_size; /**< Maximum encryption key size (7-16) */ -}; - -/** - * Security manager pairing parameters. - */ -struct ble_gap_sm_pairing_params { - uint8_t auth_level; /**< authentication level see @ref BLE_GAP_SM_OPTIONS */ -}; - -/** - * Security manager passkey type. - */ -enum BLE_GAP_SM_PASSKEY_TYPE { - BLE_GAP_SM_PASSKEY = 0, /**< Security data is a passkey. */ - BLE_GAP_SM_OBB, /**< Security data is 16 bytes of OOB data */ -}; -/** - * Security reply to incoming security request. - */ -struct ble_gap_sm_passkey { - uint8_t type; /**< Security data type in this reply @ref BLE_GAP_SM_PASSKEY_TYPE */ - union { - uint8_t passkey[6]; /**< 6 digits (string) */ - uint8_t oob[16]; /**< 16 bytes of OOB security data */ - }; -}; - -/** - * RSSI operation definition. - */ -enum BLE_GAP_RSSI_OPS { - BLE_GAP_RSSI_DISABLE_REPORT = 0, - BLE_GAP_RSSI_ENABLE_REPORT -}; - -enum BLE_GAP_SCAN_OPTIONS { - BLE_GAP_SCAN_DEFAULT = 0, /**< no specific option */ - BLE_GAP_SCAN_ACTIVE = 0x01, /**< do an active scan (request scan response */ - BLE_GAP_SCAN_WHITE_LISTED = 0x02 /**< Use white list and only report whitelisted devices */ -}; - -enum BLE_GAP_SET_OPTIONS { - BLE_GAP_SET_CH_MAP = 0, /**< Set channel map */ -}; - -typedef struct { - uint16_t conn_handle; /**< connection on which to change channel map */ - uint8_t map[5]; /**< 37 bits are used of the 40 bits (LSB) */ -} ble_gap_channel_map_t; - -/** - * GAP option data structure. - */ -typedef union { - ble_gap_channel_map_t ch_map; /**< BLE channel map to set see BT spec */ -} ble_gap_option_t; - -/** - * Scan parameters. - * - * @note Check BT core spec for high low duty cycle interval & window size! - */ -typedef struct { - uint16_t timeout; /**< scan timeout in s, 0 never */ - uint16_t interval; /**< interval: 0x4 - 0x4000. (unit: 0.625ms), use default: 0xffff (0x0010) */ - uint16_t window; /**< Window: 0x4 - 0x4000. (unit: 0.625ms), use default 0xffff (= 0x0010) */ - uint8_t options; /**< scan options, ORed options from @ref BLE_GAP_SCAN_OPTIONS */ -} ble_gap_scan_param_t; - -/** - * Connect event @ref MSG_ID_BLE_GAP_CONNECT_EVT. - */ -struct ble_gap_connect_evt { - struct ble_gap_connection_values conn_values; /**< Connection values */ - uint8_t role; /**< role in this connection @ref */ - ble_addr_t peer_bda; /**< address of peer device */ -}; - -/** - * Disconnect event @ref MSG_ID_BLE_GAP_DISCONNECT_EVT. - */ -struct ble_gap_disconnected_evt { - uint8_t hci_reason; /**< HCI disconnect reason */ -}; - -/** - * Updated connection event @ref MSG_ID_BLE_GAP_CONN_UPDATE_EVT. - */ -struct ble_gap_conn_update_evt { - struct ble_gap_connection_values conn_values; -}; - -/** - * Security manager pairing status event @ref MSG_ID_BLE_GAP_SM_PAIRING_STATUS_EVT. - */ -struct ble_gap_sm_pairing_status_evt { - uint16_t conn_handle; - uint16_t status; -}; - -/** - * Security manager passkey request event @ref MSG_ID_BLE_GAP_SM_PASSKEY_REQ_EVT. - */ -struct ble_gap_sm_passkey_req_evt { - uint8_t dummy; -}; - -/** - * GAP/SMP security result status code. - * see Vol 3: Part H, chapter 3.5.5. - */ -enum BLE_GAP_SEC_RESULT_STATUS { - BLE_GAP_SEC_STATUS_SUCCESS = 0, - /**< bonding/pairing completed successfully */ - BLE_GAP_SEC_STATUS_PASSKEY_ENTRY_FAILED,/**< passkey entry failed */ - BLE_GAP_SEC_STATUS_OOB_NOT_AVAILABLE, /**< Out of Band data is not available */ - BLE_GAP_SEC_STATUS_AUTH_REQUIREMENTS, /**< Authentication requirements not met due to IO cap */ - BLE_GAP_SEC_STATUS_CONFIRM_VALUE, /**< Confirm value does not match calculated value */ - BLE_GAP_SEC_STATUS_PAIRING_NOT_SUPPORTED, - /**< Pairing not supported by the device */ - BLE_GAP_SEC_STATUS_ENC_KEY_SIZE, /**< Encryption key size insufficient */ - BLE_GAP_SEC_STATUS_SMP_CMD_UNSUPPORTED, /**< Unsupported SMP command on this device */ - BLE_GAP_SEC_STATUS_UNSPECIFIED, /**< Failure due to unspecified reason */ - BLE_GAP_SEC_STATUS_REPEATED_ATTEMPTS, /**< Pairing/authent disallowed due to too little time elapsed since last attempt */ - BLE_GAP_SEC_STATUS_INVALID_PARAMS, /**< Invalid parameters due to length or parameters */ - /* 4.2 spec only ? */ - BLE_GAP_SEC_STATUS_DHKEY_CHECK_FAILED, /**< Remote device indicates that DHKey does not match local calculated key */ - BLE_GAP_SEC_STATUS_NUMERIC_COMP_FAILED, /**< values in numeric key comparison protocol do not match */ - BLE_GAP_SEC_STATUS_BREDR_PAIRING_INPROGRESS,/**< Failure due to BR/EDR pairing request */ - BLE_GAP_SEC_STATUS_CROSS_TSPRT_KEY_GEN_DIS, - /**< BR/EDR link key generation can not be use for LE keys handling */ -}; - -enum BLE_SVC_GAP_TIMEOUT_REASON { - BLE_SVC_GAP_TO_ADV, /**< Advertisement Stopped. */ - BLE_SVC_GAP_TO_SEC_REQ, /**< Security Request took too long. */ - BLE_SVC_GAP_TO_SCAN, /**< Scanning stopped. */ - BLE_SVC_GAP_TO_CONN, /**< Connection Link timeout. */ -}; - -/** - * GAP timeout event (e.g. protocol error) MSG_ID_BLE_GAP_TO_EVT. - */ -struct ble_gap_timout_evt { - int reason; /**< reason for timeout @ref BLE_SVC_GAP_TIMEOUT_REASON */ -}; - -/** - * Advertisement data structure (central role) @ref MSG_ID_BLE_GAP_ADV_DATA_EVT. - */ -struct ble_gap_adv_data_evt { - ble_addr_t remote_bda; /**< address of remote device */ - int8_t rssi; /**< signal strength compared to 0 dBm */ - uint8_t type; /**< type of advertisement data or scan response @ref GAP_ADV_TYPES */ - uint8_t len; /**< length of advertisement data or scap response data */ - uint8_t data[]; /**< Advertisement or scan response data */ -}; - -/** - * Connection Parameter update request event @ref MSG_ID_BLE_GAP_CONN_PARAM_UPDATE_REQ_EVT. - * - * @note reply with @ref ble_gap_conn_update_params - */ -struct ble_gap_conn_param_update_req_evt { - struct ble_gap_connection_params param; -}; - -/** - * RSSI signal strength event @ref MSG_ID_BLE_GAP_RSSI_EVT. - */ -struct ble_gap_rssi_evt { - int8_t rssi_lvl; /**< RSSI level (compared to 0 dBm) */ -}; - -/** - * RSSI report parameters @ref MSG_ID_BLE_GAP_SET_RSSI_REPORT_REQ. - */ -struct rssi_report_params { - uint16_t conn_hdl; /**< Connection handle */ - uint8_t op; /**< RSSI operation @ref BLE_GAP_RSSI_OPS */ - uint8_t delta_dBm; /**< minimum RSSI dBm change to report a new RSSI value */ - uint8_t min_count; /**< number of delta_dBm changes before sending a new RSSI report */ -}; - -/** Test Mode opcodes. */ -enum TEST_OPCODE { - BLE_TEST_INIT_DTM = 0x01, /**< Put BLE controller in HCI UART DTM test mode */ - BLE_TEST_START_DTM_RX = 0x1d, /**< LE receiver test HCI opcode */ - BLE_TEST_START_DTM_TX = 0x1e, /**< LE transmitter test HCI opcode */ - BLE_TEST_END_DTM = 0x1f, /**< End LE DTM TEST */ - /* vendor specific commands start at 0x80 */ - BLE_TEST_SET_TXPOWER = 0x80, /**< Set Tx power. To be called before start of tx test */ - BLE_TEST_START_TX_CARRIER, /**< Start Tx Carrier Test */ -}; - -/** - * Rx direct test mode data structure. - */ -struct ble_dtm_rx_test { - uint8_t freq; /**< rf channel 0x00 - 0x27, resulting F = 2402 MHz + [freq * 2 MHz] */ -}; - -/** - * Tx direct test mode data structure - */ -struct ble_dtm_tx_test { - uint8_t freq; /**< rf channel 0x00 - 0x27 where resulting F = 2402 + [freq * 2 MHz] */ - uint8_t len; /**< length of test data payload for each packet */ - uint8_t pattern; /**< packet payload pattern type, 0x00 - 0x02 mandatory */ -}; - -/** - * Tx power settings data structure. - */ -struct ble_set_txpower { - int8_t dbm; /**< Tx power level to set (e.g. -30: -30 dBm). Depends on BLE Controller */ -}; - -/** - * RX test result data. - */ -struct ble_dtm_test_result { - uint16_t mode; - uint16_t nb; -}; - -/** - * Direct Test mode command params - */ -struct ble_test_cmd { - uint8_t mode; /**< test mode to execute @ref TEST_OPCODE */ - union { - struct ble_dtm_rx_test rx; - struct ble_dtm_tx_test tx; - struct ble_set_txpower tx_pwr; /**< Tx power to use for Tx tests. */ - }; -}; - -/** - * BLE GAP event structure. - */ -struct ble_gap_event { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - uint16_t conn_handle; /**< connection handle */ - union { - struct ble_gap_connect_evt connected; /**< connected event parameters */ - struct ble_gap_disconnected_evt disconnected; /**< disconnected reason */ - struct ble_gap_conn_update_evt conn_updated; /**< connection updated */ - struct ble_gap_sm_pairing_status_evt sm_pairing_status; /**< Security Manager pairing status */ - struct ble_gap_sm_passkey_req_evt sm_passkey_req; /**< Security Manager passkey request */ - /**< connection related security update */ - struct ble_gap_timout_evt timeout; /**< gap timeout occurred */ - struct ble_gap_adv_data_evt adv_data; /**< advertisement data */ - struct ble_gap_conn_param_update_req_evt conn_param_req; - /**< update request from remote for connection parameters */ - struct ble_gap_rssi_evt rssi; /**< new rssi level if rssi reporting is enabled */ - }; -}; - -/** Generic request op codes. - * This allows to access some non connection related commands like DTM. - */ -enum BLE_GAP_GEN_OPS { - DUMMY_VALUE = 0, /**< Not used now. */ -}; - -/** - * Generic command parameters. - * - * @note Independent of connection! - */ -struct ble_gap_gen_cmd_params { - uint8_t op_code; /**< @ref BLE_GAP_GEN_OPS */ -}; - -struct ble_version_msg { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - ble_status_t status; - struct version_header version; /**< Nordic version header */ -}; - -struct ble_dtm_init_msg { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - ble_status_t status; -}; - -struct ble_dtm_result_msg { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - ble_status_t status; - struct ble_dtm_test_result result; /**< Result data of DTM RX test */ -}; - -/** - * Generic request message response or event. - */ -struct ble_generic_msg { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - ble_status_t status; - uint8_t op_code; /**< Opcode to which this message is applicable @ref BLE_GAP_GEN_OPS */ -}; - -/** - * Set Enable configuration parameters (BD address, etc). - * - * This shall put the controller stack into a usable (enabled) state. - * Hence this should be called first! - * - * @param p_svc_handle service handle - * @param p_config BLE write configuration - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE - * @return MSG_ID_BLE_GAP_WR_CONF_RSP @ref ble_rsp, TODO: return maybe more info? - */ -int ble_gap_set_enable_config(svc_client_handle_t * p_svc_handle, - const struct ble_wr_config * p_config, void *p_priv); - -/** - * Read BD address from Controller. - * - * - * @param p_svc_handle service handle - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE - * @return MSG: @ref MSG_ID_BLE_GAP_RD_BDA_RSP @ref ble_bda_rd_rsp_t - */ -int ble_gap_read_bda(svc_client_handle_t * p_svc_handle, void *p_priv); - -/** - * Write Advertisement data to BLE controller. - * - * Store advertisement data in BLE controller. It needs to be done BEFORE starting advertisement - * - * @param p_svc_handle service handle - * @param p_adv_data adv data to store in BLE controller - * @param p_scan_data scan response data to store in controller, can be NULL - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE, - * @return MSG: MSG_ID_BLE_GAP_WR_ADV_DATA_RSP @ref ble_rsp - */ -int ble_gap_wr_adv_data(svc_client_handle_t * p_svc_handle, - const struct ble_gap_adv_rsp_data * p_adv_data, - const struct ble_gap_adv_rsp_data * p_scan_data, - void *p_priv); - -/** - * Write white list to the BLE controller. - * - * Store white in BLE controller. It needs to be done BEFORE starting advertisement or - * start scanning - * - * @param p_svc_handle service handle - * @param p_white_list white list to store in the controller - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE, - * @return MSG: MSG_ID_BLE_GAP_WR_WHITE_LIST @ref ble_gap_wr_white_list_rsp_t - */ -int ble_gap_wr_white_list(svc_client_handle_t * p_svc_handle, - const struct ble_gap_whitelist_info * p_white_list, - void *p_priv); - -/** - * Clear previously stored white list. - * - * @param p_svc_handle service handle - * @param wl_handle handle to the white list previously stored - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE - * @return MSG: MSG_ID_BLE_GAP_CLR_WHITE_LIST @ref ble_rsp - */ -int ble_gap_clr_white_list(svc_client_handle_t * p_svc_handle, - uint32_t wl_handle, void *p_priv); - -/** - * Start advertising. - * - * @param p_svc_handle service handle - * @param p_adv_param advertisement - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE - * @return MSG: MSG_ID_BLE_GAP_ENABLE_ADV @ref ble_rsp - */ -int ble_gap_start_advertise(svc_client_handle_t * p_svc_handle, - const ble_gap_adv_param_t * p_adv_param, - void *p_priv); - -/** - * Stop advertising. - * - * @param p_svc_handle service handle - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE - * @return MSG: MSG_ID_BLE_GAP_DISABLE_ADV @ref ble_rsp - */ -int ble_gap_stop_advertise(svc_client_handle_t * p_svc_handle, void *p_priv); - -/** - * Update connection. - * - * This function's behavior depends on the role of the connection: - * - in peripheral mode, it sends an L2CAP signaling connection parameter - * update request based the values in @ref p_conn_param - * and the action can be taken by the central at link layer - * - in central mode, it will send a link layer command to change the - * connection values based on the values in @ref p_conn_param where the - * connection interval is interval_min - * - * When the connection is updated, the event @ref MSG_ID_BLE_GAP_CONN_UPDATE_EVT will - * be received. - * - * @param conn_handle Connection handle - * @param p_conn_param Connection parameters - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE - * @return MSG: @ref MSG_ID_BLE_GAP_CONN_UPDATE_RSP @ref ble_rsp - */ -int ble_gap_conn_update(svc_client_handle_t * p_svc_handle, - uint16_t conn_handle, - const struct ble_gap_connection_params * p_conn_param, - void *p_priv); - -/** - * Disconnect connection (peripheral or central role). - * - * @param p_svc_handle service handle - * @param conn_hhdl connection to terminate - * @param reason HCI reason for connection termination, most often 0x16 (connection terminated by local host) - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE - * @return MSG: MSG_ID_BLE_GAP_DISCONNECT @ref ble_rsp, MSG_ID_BLE_GAP_DISCONNECT_EVT @ref ble_gap_disconnected_evt_t - */ -int ble_gap_disconnect(svc_client_handle_t * p_svc_handle, - uint16_t conn_hhdl, uint8_t reason, - void *p_priv); -/** - * Write GAP Service Attribute Characteristics. - * - * @param p_svc_handle service handle - * @param p_params data of characteristic to write - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE - * @return MSG: MSG_ID_BLE_GAP_SERVICE_WRITE_RSP @ref ble_rsp - */ -int ble_gap_service_write(svc_client_handle_t * p_svc_handle, - const struct ble_gap_service_write_params * p_params, - void *p_priv); - -/** - * Read GAP Service Characteristics. - * - * @param p_svc_handle service handle - * @param type type of GAP service data characteristic to read @ref BLE_GAP_SVC_ATTR_TYPE - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE, - * @return MSG: MSG_ID_BLE_GAP_SERVICE_READ_RSP @ref ble_gap_service_read_rsp - */ -int ble_gap_service_read(svc_client_handle_t * p_svc_handle, - uint16_t type, void * p_priv); - -/** - * Function for configuring the security manager. - * - * @param h Service client - * @param p_params local authentication/bonding parameters - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE - * - * @note Upon completion of the procedure, the client will receive - * a message @ref MSG_ID_BLE_GAP_SM_CONFIG_RSP - */ -int ble_gap_sm_config(const svc_client_handle_t * h, - const struct ble_gap_sm_config_params * p_params, - void *p_priv); - -/** - * Initiate the bonding procedure (central). - * - * @param h Service client - * @param conn_handle connection on which bonding procedure is executed - * @param p_params local authentication/bonding parameters - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE - * - * @note Upon completion of the procedure, the client receives - * @ref MSG_ID_BLE_GAP_SM_PAIRING_RSP - */ -int ble_gap_sm_pairing_req(const svc_client_handle_t * h, - uint16_t conn_handle, - const struct ble_gap_sm_pairing_params * p_params, - void *p_priv); - -/** - * Reply to an incoming passkey request event (@ref MSG_ID_BLE_GAP_SM_PASSKEY_REQ_EVT). - * - * @param p_svc_handle service handle - * @param conn_handle connection on which bonding is going on - * @param p_params bonding security reply - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE - * - * @note Upon completion of the procedure, the client receives - * @ref MSG_ID_BLE_GAP_SM_PASSKEY_RSP - */ -int ble_gap_sm_passkey_reply(svc_client_handle_t * p_svc_handle, - uint16_t conn_handle, - const struct ble_gap_sm_passkey * p_params, - void *p_priv); - -/** - * Enable disable the reporting of the RSSI value. - * - * @param p_svc_handle service handle - * @param conf RSSI report parameters @ref MSG_ID_BLE_GAP_SET_RSSI_REPORT_REQ - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE, - * @return MSG: MSG_ID_BLE_GAP_SET_RSSI_REPORT_RSP @ref ble_rsp - */ -int ble_gap_set_rssi_report(svc_client_handle_t * p_svc_handle, - const struct rssi_report_params *params, - void *p_priv); - -/** - * Start scanning for BLE devices doing advertisement. - * - * @param p_svc_handle service handle - * @param p_scan_params scan parameters to use @ref ble_gap_scan_param_t - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE, - * @return MSG: MSG_ID_BLE_GAP_SCAN_START_RSP @ref ble_rsp - */ -int ble_gap_start_scan(svc_client_handle_t * p_svc_handle, - const ble_gap_scan_param_t * p_scan_params, - void *p_priv); - -/** - * Stop scanning. - * - * @param p_svc_handle service handle - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE, - * @return MSG: MSG_ID_BLE_GAP_STOP_START_RSP @ref ble_rsp - */ -int ble_gap_stop_scan(svc_client_handle_t * p_svc_handle, void *p_priv); - -/** - * Connect to a Remote Device. - * - * @param p_svc_handle service handle - * @param p_bd bd to connect to. shall be null if BLE_GAP_SCAN_WHITE_LISTED option is set in @ref ble_gap_scan_param_t - * @param p_scan_params scan parameters - * @param p_conn_params connection parameters - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE, - * @return MSG: MSG_ID_BLE_GAP_CONNECT_RSP @ref ble_rsp, - * @return MSG: MSG_ID_BLE_GAP_CONNECT_EVT @ref ble_gap_connect_evt_t - */ -int ble_gap_connect(svc_client_handle_t * p_svc_handle, const ble_addr_t * p_bd, - const ble_gap_scan_param_t * p_scan_params, - const struct ble_gap_connection_params * p_conn_params, - void *p_priv); - -/** - * Cancel an ongoing connection attempt. - * - * @param p_svc_handle service handle - * @param p_bd bd address of device for which the connection shall be canceled - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE, - * @return MSG: MSG_ID_BLE_GAP_CONNECT @ref ble_rsp - */ -int ble_gap_cancel_connect(svc_client_handle_t * p_svc_handle, - const ble_addr_t * p_bd, void *p_priv); - -/** - * Set a gap option (channel map etc) on a connection. - * - * @param p_svc_handle service handle - * @param op option to set @ref BLE_GAP_SET_OPTIONS - * @param p_opt bd address of device for which the connection shall be canceled ble_gap_option_t - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE, - * @return MSG: MSG_ID_BLE_GAP_SET_OPTIONS @ref ble_rsp - */ -int ble_gap_set_option(svc_client_handle_t * p_svc_handle, uint8_t op, - const ble_gap_option_t * p_opt, void *p_priv); - -/** - * Set a gap option (channel map etc) on a connection. - * - * @param p_svc_handle service handle - * @param p_params bd address of device for which the connection shall be canceled ble_gap_option_t - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE, - * @return MSG: MSG_ID_BLE_GAP_GENERIC_CMD_RSP @ref ble_rsp or @ref ble_generic_msg - */ -int ble_gap_generic_cmd_req(svc_client_handle_t * p_svc_handle, - const struct ble_gap_gen_cmd_params *p_params, - void *p_priv); - -/** - * Get nordic version. - * - * @param p_svc_handle service handle - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE, - * @return MSG: MSG_ID_BLE_GAP_GET_VERSION_RSP @ref ble_rsp or @ref ble_generic_msg - */ -int ble_gap_get_version_req(svc_client_handle_t * p_svc_handle, - void *p_priv); - -/** - * Init dtm mode. - * - * @param p_svc_handle service handle - * @param p_params bd address of device for which the connection shall be canceled ble_gap_option_t - * - * @return @ref OS_ERR_TYPE, - * @return MSG: MSG_ID_BLE_GAP_DTM_INIT_RSP @ref ble_rsp or @ref ble_generic_msg - */ -int ble_gap_dtm_init_req(svc_client_handle_t * p_svc_handle, - void *p_priv); -/** @} */ - -#endif /* __BLE_SVC_API_H__ */ diff --git a/system/libarc32_arduino101/framework/include/services/ble/ble_service_gatt.h b/system/libarc32_arduino101/framework/include/services/ble/ble_service_gatt.h deleted file mode 100644 index 23c65b73..00000000 --- a/system/libarc32_arduino101/framework/include/services/ble/ble_service_gatt.h +++ /dev/null @@ -1,325 +0,0 @@ -/* - * 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_SERVICE_GATT_H__ -#define __BLE_SERVICE_GATT_H__ - -#include "ble_service.h" -#include "ble_service_gap_api.h" - -/** GATT common definitions. - * - * @ingroup ble_core_service - * - * @addtogroup ble_core_service_gatt BLE core service common GATT definitions - * @{ - */ - -/** - * GATT Success code and error codes. - */ -enum BLE_SVC_GATT_STATUS_CODES { - BLE_SVC_GATT_STATUS_SUCCESS = BLE_STATUS_SUCCESS, /**< GATT success @ref BLE_STATUS_SUCCESS */ - BLE_SVC_GATT_STATUS_ENCRYPTED_MITM = BLE_SVC_GATT_STATUS_SUCCESS, - BLE_SVC_GATT_STATUS_INVALID_HANDLE = BLE_STATUS_GATT_BASE + 0x01,/**< 0x01 see BT Spec Vol 3: Part F (ATT), chapter 3.4.1.1 */ - BLE_SVC_GATT_STATUS_READ_NOT_PERMIT, - BLE_SVC_GATT_STATUS_WRITE_NOT_PERMIT, - BLE_SVC_GATT_STATUS_INVALID_PDU, - BLE_SVC_GATT_STATUS_INSUF_AUTHENTICATION, - BLE_SVC_GATT_STATUS_REQ_NOT_SUPPORTED, - BLE_SVC_GATT_STATUS_INVALID_OFFSET, - BLE_SVC_GATT_STATUS_INSUF_AUTHORIZATION, - BLE_SVC_GATT_STATUS_PREPARE_Q_FULL, - BLE_SVC_GATT_STATUS_NOT_FOUND, - BLE_SVC_GATT_STATUS_NOT_LONG, - BLE_SVC_GATT_STATUS_INSUF_KEY_SIZE, - BLE_SVC_GATT_STATUS_INVALID_ATTR_LEN, - BLE_SVC_GATT_STATUS_ERR_UNLIKELY, - BLE_SVC_GATT_STATUS_INSUF_ENCRYPTION, - BLE_SVC_GATT_STATUS_UNSUPPORT_GRP_TYPE, - BLE_SVC_GATT_STATUS_INSUF_RESOURCE, - - /**< TODO: maybe be not needed, to be covered by generic GAP status */ - BLE_SVC_GATT_STATUS_NO_RESOURCES = BLE_STATUS_GATT_BASE | 0x80, - BLE_SVC_GATT_STATUS_INTERNAL_ERROR, - BLE_SVC_GATT_STATUS_WRONG_STATE, - BLE_SVC_GATT_STATUS_DB_FULL, - BLE_SVC_GATT_STATUS_BUSY, - BLE_SVC_GATT_STATUS_ERROR, - BLE_SVC_GATT_STATUS_CMD_STARTED, - BLE_SVC_GATT_STATUS_ILLEGAL_PARAMETER, - BLE_SVC_GATT_STATUS_PENDING, - BLE_SVC_GATT_STATUS_AUTH_FAIL, - BLE_SVC_GATT_STATUS_MORE, - BLE_SVC_GATT_STATUS_INVALID_CFG, - BLE_SVC_GATT_STATUS_SERVICE_STARTED, - BLE_SVC_GATT_STATUS_ENCRYPTED_NO_MITM, - BLE_SVC_GATT_STATUS_NOT_ENCRYPTED, - BLE_SVC_GATT_STATUS_CONGESTED, -}; - -/** - * GATT Server Message ID definitions. - */ -enum BLE_GATTS_MSG_ID { - /**< GATT Server Requests */ - MSG_ID_BLE_GATTS_ADD_SERVICE_REQ = MSG_ID_BLE_GAP_REQ_LAST, - MSG_ID_BLE_GATTS_ADD_INCL_SVC_REQ, - MSG_ID_BLE_GATTS_ADD_CHARACTERISTIC_REQ, - MSG_ID_BLE_GATTS_ADD_DESCRIPTOR_REQ, - MSG_ID_BLE_GATTS_START_SERVICE_REQ, - MSG_ID_BLE_GATTS_REMOVE_SERVICE_REQ, - MSG_ID_BLE_GATTS_INDICATE_SERVICE_CHANGE_REQ, - MSG_ID_BLE_GATTS_SET_ATTRIBUTE_VALUE_REQ, - MSG_ID_BLE_GATTS_GET_ATTRIBUTE_VALUE_REQ, - MSG_ID_BLE_GATTS_SEND_NOTIF_REQ, - MSG_ID_BLE_GATTS_SEND_IND_REQ, - MSG_ID_BLE_GATTS_SEND_RW_AUTHORIZATION_REQ, - MSG_ID_BLE_GATTS_WR_CONN_ATTRIBUTES_REQ, - MSG_ID_BLE_GATTS_RD_CONN_ATTRIBUTES_REQ /* 37 */ , - MSG_ID_BLE_GATTS_REQ_LAST, - - /**< GATT Server Requests */ - MSG_ID_BLE_GATTS_ADD_SERVICE_RSP = MSG_ID_BLE_GAP_RSP_LAST, - /**< create new service */ - MSG_ID_BLE_GATTS_ADD_INCL_SVC_RSP, - MSG_ID_BLE_GATTS_ADD_CHARACTERISTIC_RSP, - MSG_ID_BLE_GATTS_ADD_DESCRIPTOR_RSP, - MSG_ID_BLE_GATTS_START_SERVICE_RSP, /**< enable created service */ - MSG_ID_BLE_GATTS_REMOVE_SERVICE_RSP, /**< stop and remove service */ - MSG_ID_BLE_GATTS_INDICATE_SERVICE_CHANGE_RSP, /**< indicate a service change */ - MSG_ID_BLE_GATTS_SET_ATTRIBUTE_VALUE_RSP, - MSG_ID_BLE_GATTS_GET_ATTRIBUTE_VALUE_RSP, - MSG_ID_BLE_GATTS_SEND_NOTIF_RSP, /**< send notification */ - MSG_ID_BLE_GATTS_SEND_IND_RSP, /**< send indication */ - MSG_ID_BLE_GATTS_SEND_RW_AUTHORIZATION_RSP, /**< authorize a R/W request from remote */ - MSG_ID_BLE_GATTS_WR_CONN_ATTRIBUTES_RSP, /**< write connection related attributes (previously bonded!) */ - MSG_ID_BLE_GATTS_RD_CONN_ATTRIBUTES_RSP /* 37 */ , /**< read connection related attributes (only for bonded connections!*/ - MSG_ID_BLE_GATTS_RSP_LAST, - - /**< GATT Server Events */ - MSG_ID_BLE_GATTS_WRITE_EVT = MSG_ID_BLE_GAP_EVT_LAST, /**< remote client write happened */ - MSG_ID_BLE_GATTS_RW_AUTHORIZATION_REQ_EVT, /**< remote client R/W authorization request */ - MSG_ID_BLE_GATTS_CONN_ATTRIB_MISSING_EVT, /**< connection related attributes have not been set, access pending */ - MSG_ID_BLE_GATTS_INDICATION_CONF_EVT, /**< indication confirmation event */ - MSG_ID_BLE_GATTS_SVC_CHG_CONF_EVT, /**< confirmation of service change indication (no params) */ - MSG_ID_BLE_GATTS_TO_EVT, /**< GATTS timeout indication */ - MSG_ID_BLE_GATTS_EVT_LAST -}; - -/** - * GATT Client Message ID definitions. - */ -enum BLE_GATTC_MSG_ID { - /**< GATT Client Requests, responses, events and indications */ - MSG_ID_BLE_GATTC_DISCOVER_PRIMARY_SERVICE_REQ = - MSG_ID_BLE_GATTS_REQ_LAST, - MSG_ID_BLE_GATTC_DISCOVER_INCLUDED_SERVICES_REQ, - MSG_ID_BLE_GATTC_DISCOVER_CHAR_REQ, - MSG_ID_BLE_GATTC_DISCOVER_DESCRIPTOR_REQ, - MSG_ID_BLE_GATTC_RD_CHARS_REQ, - MSG_ID_BLE_GATTC_WR_OP_REQ, - MSG_ID_BLE_GATTC_SEND_HANDLE_VALUE_REQ /* 44 */ , - - /** GATT Client Requests, responses, events and indications */ - MSG_ID_BLE_GATTC_DISCOVER_PRIMARY_SERVICE_RSP = MSG_ID_BLE_GATTS_RSP_LAST, /**< discover primary service */ - MSG_ID_BLE_GATTC_DISCOVER_INCLUDED_SERVICES_RSP,/**< find included service procedure */ - MSG_ID_BLE_GATTC_DISCOVER_CHAR_RSP, /**< discover characteristics of a service */ - MSG_ID_BLE_GATTC_DISCOVER_DESCRIPTOR_RSP, /**< discover descriptor of a characteristic */ - MSG_ID_BLE_GATTC_RD_CHARS_RSP, /**< read characteristic or long characteristics */ - MSG_ID_BLE_GATTC_WR_OP_RSP, /**< different types of write operations */ - MSG_ID_BLE_GATTC_SEND_HANDLE_VALUE_RSP /* 44 */ , /**< send attribute handle to server */ - - /** GATT Client Events */ - MSG_ID_BLE_GATTC_DISC_PRIM_SVC_EVT = MSG_ID_BLE_GATTS_EVT_LAST, /**< primary service discovery response */ - MSG_ID_BLE_GATTC_DISC_INCL_SVC_EVT, /**< include service discovery response */ - MSG_ID_BLE_GATTC_DISC_CHAR_EVT, /**< characteristic discovery response */ - MSG_ID_BLE_GATTC_DISC_DESCR_EVT, /**< descriptor discovery response */ - MSG_ID_BLE_GATTC_RD_EVT, /**< data read response */ - MSG_ID_BLE_GATTC_WR_EVT, /**< data write response */ - MSG_ID_BLE_GATTC_HDL_NOTIF_EVT, /**< handle indication/notification event */ - MSG_ID_BLE_GATTC_TO_EVT, /**< GATT Client timeout event */ - MSG_ID_BLE_GATTC_LAST -}; - -/** - * Maximum UUID size - 16 bytes, and structure to hold any type of UUID. - */ -#define MAX_UUID_SIZE 16 - -#define BLE_GATT_INVALID_HANDLE 0x0000 /**< reserved invalid attribute handle */ -#define BLE_GATT_MAX_HANDLE 0xffff /**< maximum handle in a BLE server */ -#define BLE_GATT_START_HANDLE_DISCOVER 0x0001 /**< Value of start handle during discovery. */ - -/** BT uuid types defined as length. */ -enum BT_UUID_TYPES { - BT_UUID16 = 2, /**< 16 bit UUID type */ - BT_UUID32 = 4, /**< 32 bit UUID type */ - BT_UUID128 = 16 /**< 128 bit UUID type */ -}; - -/** - * Generic uuid structure specific to BT/BLE. - */ -struct bt_uuid { - uint8_t type; /**< UUID type (encoded as length of the union element) @ref BT_UUID_TYPES */ - union { - uint16_t uuid16; - uint32_t uuid32; - uint8_t uuid128[MAX_UUID_SIZE]; - }; -}; - -/** - * UUID and Handle combination for services and characteristics - * - * Make sure this is 32 bit aligned! - */ -struct bt_uuid_handle_tuple { - void *p_priv; /**< Service private reference handle. */ - uint16_t handle; /** Service or characteristic handle. */ -}; - -/** - * GATT service types, primary versus secondary/included one. - */ -enum BLE_GATT_SVC_TYPES { - BLE_GATT_SVC_PRIMARY = 0, /**< primary service */ - BLE_GATT_SVC_INCLUDED /**< include service (must be referenced by a primary) */ -}; - -/** - * Characteristic properties. - */ -enum BLE_GATT_CHAR_PROPS { - BLE_GATT_CHAR_PROP_BIT_NONE = 0, - BLE_GATT_CHAR_PROP_BIT_BROADCAST = 0x01, - BLE_GATT_CHAR_PROP_BIT_READ = 0x02, - BLE_GATT_CHAR_PROP_BIT_WRITE_NR = 0x04, - BLE_GATT_CHAR_PROP_BIT_WRITE = 0x08, - BLE_GATT_CHAR_PROP_BIT_NOTIFY = 0x10, - BLE_GATT_CHAR_PROP_BIT_INDICATE = 0x20, - BLE_GATT_CHAR_PROP_BIT_AUTH = 0x40, - BLE_GATT_CHAR_PROP_BIT_EXTEND = 0x80/**< if set the extend property @ref BLE_GATT_CHAR_EXT_PROPS is present! */ -}; - -/** - * Extended characteristic properties. - */ -enum BLE_GATT_CHAR_EXT_PROPS { - BLE_GATT_CHAR_EXT_PROP_BIT_NONE = 0, - BLE_GATT_CHAR_EXT_PROP_RELIABLE_WR = 0x0001, /**< Reliable write procedure is supported */ - BLE_GATT_CHAR_EXT_PROP_WR_AUX = 0x0002, /**< User Descriptor Writes are permitted */ -}; - -struct ble_gatt_char_properties { - uint8_t props; /**< properties, @ref BLE_GATT_CHAR_PROPS */ - uint16_t ext_props; /**< extended properties, @ref BLE_GATT_CHAR_EXT_PROPS, valid if BLE_GATT_CHAR_PROP_BIT_EXTEND set */ -}; - -/** - * Format of the value of a characteristic, enumeration type. - */ -enum BLE_GATT_FORMATS { - BLE_GATT_FORMAT_RES, /* rfu */ - BLE_GATT_FORMAT_BOOL, /* 0x01 boolean */ - BLE_GATT_FORMAT_2BITS, /* 0x02 2 bit */ - BLE_GATT_FORMAT_NIBBLE, /* 0x03 nibble */ - BLE_GATT_FORMAT_UINT8, /* 0x04 uint8 */ - BLE_GATT_FORMAT_UINT12, /* 0x05 uint12 */ - BLE_GATT_FORMAT_UINT16, /* 0x06 uint16 */ - BLE_GATT_FORMAT_UINT24, /* 0x07 uint24 */ - BLE_GATT_FORMAT_UINT32, /* 0x08 uint32 */ - BLE_GATT_FORMAT_UINT48, /* 0x09 uint48 */ - BLE_GATT_FORMAT_UINT64, /* 0x0a uint64 */ - BLE_GATT_FORMAT_UINT128,/* 0x0B uint128 */ - BLE_GATT_FORMAT_SINT8, /* 0x0C signed 8 bit integer */ - BLE_GATT_FORMAT_SINT12, /* 0x0D signed 12 bit integer */ - BLE_GATT_FORMAT_SINT16, /* 0x0E signed 16 bit integer */ - BLE_GATT_FORMAT_SINT24, /* 0x0F signed 24 bit integer */ - BLE_GATT_FORMAT_SINT32, /* 0x10 signed 32 bit integer */ - BLE_GATT_FORMAT_SINT48, /* 0x11 signed 48 bit integer */ - BLE_GATT_FORMAT_SINT64, /* 0x12 signed 64 bit integer */ - BLE_GATT_FORMAT_SINT128,/* 0x13 signed 128 bit integer */ - BLE_GATT_FORMAT_FLOAT32,/* 0x14 float 32 */ - BLE_GATT_FORMAT_FLOAT64,/* 0x15 float 64 */ - BLE_GATT_FORMAT_SFLOAT, /* 0x16 IEEE-11073 16 bit SFLOAT */ - BLE_GATT_FORMAT_FLOAT, /* 0x17 IEEE-11073 32 bit SFLOAT */ - BLE_GATT_FORMAT_DUINT16,/* 0x18 IEEE-20601 format */ - BLE_GATT_FORMAT_UTF8S, /* 0x19 UTF-8 string */ - BLE_GATT_FORMAT_UTF16S, /* 0x1a UTF-16 string */ - BLE_GATT_FORMAT_STRUCT, /* 0x1b Opaque structure */ - BLE_GATT_FORMAT_MAX /* 0x1c or above reserved */ -}; - -/** - * GATT characteristic user description. - */ -struct ble_gatt_char_user_desc { - uint8_t *buffer; /**< Pointer to a UTF-8 string. */ - uint8_t len; /**< The size in bytes of the user description. */ -}; - -/** - * GATT characteristic presentation format description. - */ -struct ble_gatt_pf_desc { - uint16_t unit; /**< as UUIUD defined by SIG */ - uint16_t descr; /**< as UUID as defined by SIG */ - uint8_t format; /**< @ref BLE_GATT_FORMATS */ - int8_t exp; /**< see Unit from Bluetooth Assigned Numbers, https://developer.bluetooth.org/gatt/units/Pages/default.aspx */ - uint8_t name_spc; /**< name space of the description */ -} ; - -/** - * GATT indication types. - */ -enum BLE_GATT_IND_TYPES { - BLE_GATT_IND_TYPE_NONE = 0, - BLE_GATT_IND_TYPE_NOTIFICATION, - BLE_GATT_IND_TYPES_INDICATION, -}; - -/** - * GATT Write operation types - * - * (BT spec Vol 3, Part G, chapter. 4.9) - * @note long char write, Prepare & Exe request are handled internally to the controller stack - */ -enum BLE_GATT_WR_OP_TYPES { - BLE_GATT_WR_OP_NOP = 0, /**< normally not used except to cancel BLE_GATT_WR_OP_REQ long char write procedure */ - BLE_GATT_WR_OP_CMD, /**< Write Command, (no response) */ - BLE_GATT_WR_OP_REQ, /**< Write Request, Write response is received , if length is longer then ATT MTU, Prepare write procedure */ - BLE_GATT_WR_OP_SIGNED_CMD, /**< Signed Write Command */ -}; - -/** @} */ - -#endif diff --git a/system/libarc32_arduino101/framework/include/services/ble/ble_service_gattc_api.h b/system/libarc32_arduino101/framework/include/services/ble/ble_service_gattc_api.h deleted file mode 100644 index d7b42df9..00000000 --- a/system/libarc32_arduino101/framework/include/services/ble/ble_service_gattc_api.h +++ /dev/null @@ -1,372 +0,0 @@ -/* - * 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_SERVICE_GATTC_H__ -#define __BLE_SERVICE_GATTC_H__ - -#include "ble_service.h" -#include "ble_service_gap_api.h" -#include "ble_service_gatt.h" - -/** - * @defgroup ble_core_service_gattc BLE Core Service GATTC - * @ingroup ble_core_service - * - * BLE Core Service GATTC APIs used to implement GATT Clients. - * - * This is typically only used to add new client services to BLE service. - * - * It provides the following services: - * - Discover remote \b primary services or a specific service - * - Discover remote characteristics - * - Discover remote descriptors - * - read/write remote characteristics - * - Getting notified on characteristic changes - * - * @{ - */ - -/** - * Generic GATTC response message. - */ -struct ble_gattc_rsp { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - ble_status_t status; - uint16_t conn_handle; /**< GAP connection handle */ -}; - -/** - * Generic GATTC error event. - */ -struct ble_gattc_err_rsp_evt { - uint16_t err_handle; /**< handle of char attribute causing the failure */ -}; - -/** - * Handle range for a service operation. - */ -struct ble_gattc_handle_range { - uint16_t start_handle; - uint16_t end_handle; -}; - -typedef struct { - struct ble_gattc_handle_range handle_range; /**< range of characteristic handles within a service */ - struct bt_uuid uuid; /**< service uuid */ -} ble_gattc_svc_t; - -/** - * Primary Service discovery Indication message @ref MSG_ID_BLE_GATTC_DISC_PRIM_SVC_EVT. - */ -typedef struct { - uint16_t svc_count; /**< number of service included into this indication */ - ble_gattc_svc_t service_found[];/**< array on fouTnd services */ -} ble_gattc_primary_svc_disc_evt_t; - -/** - * Included service. - */ -typedef struct { - uint16_t incl_handle; /**< handle of included service */ - ble_gattc_svc_t svc; /**< included service */ -} ble_gattc_incl_svc_t; - -/** - * Discovered included services @ref MSG_ID_BLE_GATTC_DISC_INCL_SVC_EVT. - */ -typedef struct { - uint16_t incl_count; /**< Number of included services */ - ble_gattc_incl_svc_t included[]; /**< Array on found services */ -} ble_gattc_incl_svc_disc_evt_t; - -typedef struct { - struct ble_gatt_char_properties char_properties; /**< characteristic properties */ - uint16_t decl_handle; /**< Characteristic declaration handle */ - uint16_t value_handle; /**< Char's value handle */ - struct bt_uuid uuid; /**< Characteristic's UUID */ -} ble_gattc_characteristic_t; - -/** - * Discovered characteristics indication @ref MSG_ID_BLE_GATTC_DISC_CHAR_EVT. - */ -typedef struct { - uint16_t char_count; /**< number of characteristics in this message */ - ble_gattc_characteristic_t chars[]; /**< characteristics data as per char_count */ -} ble_gattc_char_disc_evt_t; - -/** - * GATTC descriptor. - */ -typedef struct { - uint16_t handle; /**< descriptor handle */ - struct bt_uuid uuid; /**< uuid of the descriptor */ -} ble_gattc_descriptor_t; - -/** - * Descriptor discover indication. - */ -typedef struct { - uint16_t desc_count; /**< number of descriptors in this message */ - ble_gattc_descriptor_t descs[]; /**< found descriptors */ -} ble_gattc_desc_disc_evt_t; - -enum BLE_GATTC_RD_CHAR_TYPES { - BLE_GATTC_RD_CHAR_BY_UUID = 0, /**< Read characteristic by UUID */ - BLE_GATTC_RD_CHAR, /**< Read (Long) characteristic or (Long) descriptor. Maybe called multiple times in case of long */ - BLE_GATTC_RD_CHAR_MULTIPLE /**< Read multiple characteristic attributes */ -}; - -/** - * Characteristic read by using UUID. - */ -typedef struct { - struct ble_gattc_handle_range handle_range; /**< characteristic or descriptor handle range */ - struct bt_uuid *p_uuid; /**< uuid of characteristic to read */ -} ble_gattc_rd_char_by_uuid_t; - -/** - * Characteristic or descriptor read. - * - * Maybe used for long too. - */ -typedef struct { - uint16_t handle; /**< attribute handle for reading */ - uint16_t offset; /**< offset into attribute data to read */ -} ble_gattc_rd_char_t; - -/** - * Read multiple characteristics values. - */ -typedef struct { - uint16_t handle_count; /**< number of handles in this structure */ - uint16_t handle[]; /**< handles of attributes to read from */ -} ble_gattc_rd_multi_char_t; - -typedef struct { - union { - ble_gattc_rd_char_by_uuid_t char_by_uuid; - ble_gattc_rd_char_t char_desc; /**< (Long) characteristic or descriptor to read */ - ble_gattc_rd_multi_char_t multi_char; - /**< read multiple characteristics */ - }; -} ble_gattc_rd_characteristic_t; - -typedef struct { - uint16_t char_handle; /**< handle of characteristic */ - uint16_t len; /**< if len is bigger then ATT MTU size, the controller fragment buffer itself */ - uint8_t *p_value; /**< characteristic value to write */ - uint8_t wr_type; /**< type of write operation @ref BLE_GATT_WR_OP_TYPES */ -} ble_gattc_wr_characteristic_t; - -/** - * Read characteristic response indication (@ref MSG_ID_BLE_GATTC_RD_EVT). - */ -typedef struct { - uint16_t handle; /**< handle of characteristic attribute read */ - uint16_t offset; /**< offset of data returned */ - uint16_t len; /**< length of data returned */ - uint8_t data[]; /**< characteristic attribute data */ -} ble_gattc_rd_char_evt_t; - -/** - * Characteristic write response indication @ref MSG_ID_BLE_GATTC_WR_EVT. - */ -typedef struct { - uint16_t char_handle; - uint16_t len; -} ble_gattc_wr_char_evt_t; - -/** - * Handle value indication or notification indication/event (@ref MSG_ID_BLE_GATTC_HDL_NOTIF_EVT). - */ -typedef struct { - uint16_t handle; /**< handle of characteristic being notified/indicated */ - uint16_t len; /**< length of value included into this indication */ - uint8_t type; /**< notification versus indication, @ref BLE_GATT_IND_TYPES */ - uint8_t data[]; /**< value data received */ -} ble_gattc_value_evt_t; - -/** - * GATT timeout reason. - */ -typedef struct { - uint16_t reason; /**< GATT timeout reason */ -} ble_gattc_to_evt_t; - -/** - * GATTC indication or response message structure applicable to most indications/events/responses. - */ -struct ble_gattc_evt_msg { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - ble_status_t status; - uint16_t conn_handle; - union { - struct ble_gattc_err_rsp_evt err_rsp; /**< returned only if status != BLE_GATT_STATUS_SUCCESS */ - ble_gattc_primary_svc_disc_evt_t prim_svc_disc; - /**< primary service discovery indication event */ - ble_gattc_incl_svc_disc_evt_t incl_svc_disc; /**< included services discovered */ - ble_gattc_char_disc_evt_t char_disc; /**< characteristic discover event/indication */ - ble_gattc_desc_disc_evt_t desc_disc; /**< discovered descriptors indication/event */ - ble_gattc_rd_char_evt_t char_rd; /**< read characteristic indication/event */ - ble_gattc_wr_char_evt_t char_wr; /**< characteristic write indication event */ - ble_gattc_value_evt_t val_ind; /**< value indication or notification */ - ble_gattc_to_evt_t timeout_ind; /**< gattc timeout protocol error */ - }; /**< in case for responses, union is not used! */ -}; - -/** - * Discover primary service. - * - * @param p_svc_handle service handle - * @param conn_handle connection to use - * @param p_svc_uuid points to service UUID. if NULL, all services found are returned - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE - * @return MSG: MSG_ID_BLE_GATTC_DISCOVER_PRIMARY_SERVICE_RSP @ref ble_gattc_rsp - * @return EVT: MSG_ID_BLE_GATTC_DISC_PRIM_SVC_EVT @ref ble_gattc_primary_svc_disc_evt_t - */ -int ble_gattc_discover_primary_service(svc_client_handle_t * p_svc_handle, - uint16_t conn_handle, - const struct bt_uuid * p_svc_uuid, - void *p_priv); - -/** - * Discover included services on a previously discovered primary service. - * - * @param p_svc_handle service handle - * @param conn_handle connection to use - * @param p_handle_range handle range previously returned by @ref ble_gattc_primary_svc_disc_evt_t - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE - * @return MSG: MSG_ID_BLE_GATTC_DISCOVER_INCLUDED_SERVICES_RSP @ref ble_gattc_rsp - */ -int ble_gattc_discover_included_service(svc_client_handle_t * p_svc_handle, - uint16_t conn_handle, - const struct ble_gattc_handle_range * - p_handle_range, void *p_priv); - -/** - * Discover characteristics on a service. - * - * May be called several times if not all characteristics have been discovered. - * In this case a new handle range needs to be provided. - * - * @param p_svc_handle service handle - * @param conn_handle connection to use - * @param p_handle_range handle range - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE - * @return MSG: MSG_ID_BLE_GATTC_DISCOVER_CHAR_RSP @ref ble_gattc_rsp - * @ref MSG_ID_BLE_GATTC_DISC_CHAR_EVT @ref ble_gattc_char_disc_evt_t - */ -int ble_gattc_discover_characteristic(svc_client_handle_t * p_svc_handle, - uint16_t conn_handle, - const struct ble_gattc_handle_range * p_handle_range, - void *p_priv); - -/** - * Discover characteristics on a service. - * - * May be called several times if not all characteristics have been discovered. - * In this case a new handle range needs to be provided. - * - * @param p_svc_handle service handle - * @param conn_handle connection to use - * @param p_handle_range handle range - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE - * @return MSG: MSG_ID_BLE_GATTC_DISCOVER_DESCRIPTOR_RSP @ref ble_gattc_rsp - * @ref MSG_ID_BLE_GATTC_DISC_DESCR_EVT @ref ble_gattc_desc_disc_evt_t - */ -int ble_gattc_discover_descriptor(svc_client_handle_t * p_svc_handle, - uint16_t conn_handle, - const struct ble_gattc_handle_range * - p_handle_range, void *p_priv); - -/** - * Read characteristic on remote server. - * - * @param p_svc_handle service handle - * @param conn_handle connection to use - * @param type type of read to execute @ref BLE_GATTC_RD_CHAR_TYPES - * @param p_rd_char_param read type specific characteristic read parameter - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE - * @return MSG: MSG_ID_BLE_GATTC_RD_CHARS_RSP @ref ble_gattc_rsp - * @return EVT: MSG_ID_BLE_GATTC_RD_EVT @ref ble_gattc_rd_char_evt_t - */ -int ble_gattc_read_characteristic(svc_client_handle_t * p_svc_handle, - uint16_t conn_handle, - uint8_t type, - const ble_gattc_rd_characteristic_t * p_rd_char_param, - void *p_priv); - -/** - * Write characteristic on server. - * - * @param p_svc_handle service handle - * @param conn_handle connection to use - * @param p_wr_char_param write characteristic on remote service - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE, - * @return MSG: MSG_ID_BLE_GATTC_WR_OP_RSP @ref ble_gattc_rsp - */ -int ble_gattc_write_char_op(svc_client_handle_t * p_svc_handle, - uint16_t conn_handle, - const ble_gattc_wr_characteristic_t * - p_wr_char_param, - void *p_priv); - - -/** - * Write characteristic on server. - * - * @param p_svc_handle service handle - * @param conn_handle connection to use - * @param val_handle handle to confirm and received by Handle Value Indication (@ref MSG_ID_BLE_GATTC_HDL_NOTIF_EVT) - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE, - * @return MSG: MSG_ID_BLE_GATTC_SEND_HANDLE_VALUE_RSP @ref ble_gattc_rsp - */ -int ble_gattc_send_confirm_handle_value(svc_client_handle_t * p_svc_handle, - uint16_t conn_handle, - uint16_t val_handle, - void *p_priv); - -/** @} */ - -#endif diff --git a/system/libarc32_arduino101/framework/include/services/ble/ble_service_gatts_api.h b/system/libarc32_arduino101/framework/include/services/ble/ble_service_gatts_api.h deleted file mode 100644 index 76bc3149..00000000 --- a/system/libarc32_arduino101/framework/include/services/ble/ble_service_gatts_api.h +++ /dev/null @@ -1,559 +0,0 @@ -/* - * 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_SERVICE_GATTS_H__ -#define __BLE_SERVICE_GATTS_H__ - -#include "ble_service.h" -#include "ble_service_gap_api.h" -#include "ble_service_gatt.h" - -/** @defgroup ble_core_service_gatts BLE Core Service GATTS - * @ingroup ble_core_service - * - * BLE Core GATTS Service APIs to implement GATT Servers. - * - * This API should only be used by BLE service to implement additional BLE profiles/services. - * - * Those the GATT server APIs provide the following services: - * - Create an new (server) BLE service - * - Add characteristics to the service - * - Write local server characteristics - * - Receive data when updated by client - * - * @note If a service is based on a 128 bit UUID (vendor service), all the characteristic - * need to use the same 128 bit UUID base and only vary octets 12-13 of base UUID. - * - * @{ - */ - -/** - * BLE GATTS max attribute length. - * @note BLE controller dependent - */ -#define BLE_SVC_GATTS_FIX_ATTR_LEN_MAX 510 /**< Maximum length for fixed length Attribute Values. */ -#define BLE_SVC_GATTS_VAR_ATTR_LEN_MAX 512 /**< Maximum length for variable length Attribute Values. */ - -/* forward declaration for callback handlers */ -struct _ble_service_cb; -struct ble_gatts_add_svc_rsp; -struct ble_gatts_add_char_rsp; -struct ble_gatts_add_desc_rsp; -struct ble_gatts_notif_ind_rsp_msg; - -/** - * Generic GATTS response message. - */ -typedef struct { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - ble_status_t status; - uint16_t conn_handle; /**< GAP connection handle */ -} ble_gatts_rsp_t; - -/** - * Add Service callback handler. - */ -typedef int (* ble_gatts_add_svc_cback_t)(struct ble_gatts_add_svc_rsp * rsp, - struct _ble_service_cb * p_cb); - -/** - * Add service response message. - */ -struct ble_gatts_add_svc_rsp { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - ble_status_t status; /**< status of service creation */ - ble_gatts_add_svc_cback_t cback; /**< Callback function to execute on reception of this message */ - uint16_t svc_handle; /**< Handle of newly allocated service (only valid in case of success. */ -}; - -/** - * Include service response. - */ -typedef struct { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - ble_status_t status; /**< status of service creation */ - uint16_t svc_handle; /**< updated handle of included service (only valid in case of success */ -} ble_gatts_incl_svc_rsp_t; - -/** - * ATT attribute permission - */ -struct ble_gatts_permissions { - uint8_t rd; /**< Read permissions, @ref BLE_GAP_SEC_MODES */ - uint8_t wr; /**< Write permissions @ref BLE_GAP_SEC_MODES */ -}; - -/** - * GATT characteristic. - */ -struct ble_gatts_characteristic { - struct bt_uuid * p_uuid; /**< Pointer to the characteristic UUID. */ - struct ble_gatts_permissions perms; /**< Characteristic value attribute permissions */ - struct ble_gatt_char_properties props; /**< Characteristic Properties. @ref ble_gatt_char_properties */ - uint16_t max_len; /**< Maximum characteristic value length in bytes, see @ref BLE_SVC_GATTS_FIX_ATTR_LEN_MAX or @ref BLE_SVC_GATTS_VAR_ATTR_LEN_MAX. */ - uint16_t init_len; /**< Initial characteristic value length in bytes. */ - uint8_t * p_value; /**< Pointer to the characteristic initialization value */ - // optional descriptors - struct ble_gatt_char_user_desc * p_user_desc; /**< Optional user description of the characteristic, NULL if not required */ - struct ble_gatt_pf_desc *p_char_pf_desc; /**< Pointer to a presentation format structure or NULL if the descriptor is not required. */ -}; - -/** - * GATT generic descriptor. - */ -struct ble_gatts_descriptor { - struct bt_uuid * p_uuid; /**< Pointer to the descriptor UUID. */ - uint8_t * p_value; /**< Value of the descriptor */ - uint16_t length; /**< Length of the descriptor value */ - struct ble_gatts_permissions perms; /**< Descriptor attribute permissions */ -}; - -struct ble_gatts_char_handles { - uint16_t value_handle; /**< Handle to the characteristic value. */ - uint16_t cccd_handle; /**< Handle to the Client Characteristic Configuration Descriptor, or BLE_GATT_HANDLE_INVALID if not present. */ - uint16_t sccd_handle; /**< Handle to the Server Characteristic Configuration Descriptor, or BLE_GATT_HANDLE_INVALID if not present. */ -}; - -/** - * Add Service callback handler. - */ -typedef int (* ble_gatts_add_char_cback_t)(struct ble_gatts_add_char_rsp * rsp, - struct _ble_service_cb * p_cb); - -/** - * Add characteristic response message. - */ -struct ble_gatts_add_char_rsp { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - ble_status_t status; /**< Status of the operation. */ - ble_gatts_add_char_cback_t cback; /**< Callback function to call on reception of this message */ - struct ble_gatts_char_handles char_h; /**< Handles of the created characteristic */ -}; - -/** - * Add Service callback handler. - */ -typedef int (* ble_gatts_add_desc_cback_t)(struct ble_gatts_add_desc_rsp * rsp, - struct _ble_service_cb * p_cb); - -/** - * Add descriptor response message. - */ -struct ble_gatts_add_desc_rsp { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - ble_status_t status; /**< Status of the operation. */ - ble_gatts_add_desc_cback_t cback; /**< Callback function to call on reception of this message */ - uint16_t handle; /**< Handle of the created descriptor */ -}; - -/** - * Set attribute response message. - */ -struct ble_gatts_set_attr_rsp_msg { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - ble_status_t status; - uint16_t value_handle; -}; - -/** - * Notification/Indication callback. - */ -typedef int (* ble_gatts_notif_ind_cback_t)(struct ble_gatts_notif_ind_rsp_msg * rsp, - struct _ble_service_cb * p_cb); - -/** - * Notification/Indication response message. - */ -struct ble_gatts_notif_ind_rsp_msg { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - ble_status_t status; - uint16_t conn_handle; - ble_gatts_notif_ind_cback_t cback; /**< Callback function to call on reception of this message */ - uint16_t handle; /**< Characteristic value handle */ -}; - -/** - * Shortened attribute type definitions. - * See BT Spec r Vol 3, PART G, chapter 3 - */ -enum BLE_SVC_GATTS_ATTR_TYPES { - BLE_SVC_GATTS_ATTR_TYPE_NONE = 0, - BLE_SVC_GATTS_ATTR_TYPE_PRIMARY_SVC_DECL, - /**< primary service attribute declaration (chpt 3.1) */ - BLE_SVC_GATTS_ATTR_TYPE_SECONDARY_SVC_DECL, - /**< secondary service attribute declaration (chpt 3.1) */ - BLE_SVC_GATTS_ATTR_TYPE_INCLUDE_DECL, /**< include attribute declaration (3.2) */ - BLE_SVC_GATTS_ATTR_TYPE_CHAR_DECL, /**< characteristic declaration (3.3.1) */ - BLE_SVC_GATTS_ATTR_TYPE_CHAR_VALUE_DECL,/**< Characteristic value declaration */ - BLE_SVC_GATTS_ATTR_TYPE_DESC_DECL, /**< descriptor declaration */ -}; - -/** - * GATT server write ops. - */ -enum BLE_GATTS_WR_OPS { - BLE_GATTS_OP_NONE = 0, - BLE_GATTS_OP_WR, /**< 3.4.5.1 Write Request (Attribute), expects write response */ - BLE_GATTS_OP_WR_CMD, /**< 3.4.5.3 Write Command (Attribute) NO response sent */ - BLE_GATTS_OP_WR_CMD_SIGNED, /**< 3.4.5.4 Write Command Signed (Attribute), NO response sent */ - BLE_GATTS_OP_WR_PREP_REQ, /**< 3.4.6.1 Write Prepare Request, expects a prepare write request response */ - BLE_GATTS_OP_WR_EXE_REQ_CANCEL, /**< 3.4.6.3 Cancel Executed Write Request, cancel and clear queue (flags = 0) */ - BLE_GATTS_OP_WR_EXE_REQ_IMM /**< 3.4.6.3 Immediately Execute Write Request */ -}; - -/** - * Write authorization context data structure. - */ -typedef struct { - uint16_t attr_handle; /**< handle of attribute to write */ - uint16_t offset; - uint16_t len; - uint8_t op; /**< @ref BLE_GATTS_WR_OPS */ - uint8_t data[]; -} ble_gatt_wr_evt_t; - -/** - * Read authorization context data structure. - */ -typedef struct { - uint16_t attr_handle; /**< handle of attribute to been read */ - uint16_t offset; -} ble_gatt_rd_evt_t; - -/** - * Connection attribute data is missing @ref MSG_ID_BLE_GATTS_CONN_ATTRIB_MISSING_EVT. - */ -typedef struct { - uint16_t miss_type; /**< missing connection attribute type */ -} ble_gatts_conn_attrib_missing_evt_t; - -/** - * Handle Value Confirmation, @ref MSG_ID_BLE_GATTS_INDICATION_CONF_EVT in response an handle value indication. - */ -typedef struct { - uint16_t handle; /**< attribute handle of indication value sent */ -} ble_gatts_handle_value_conf_evt_t; - -/** - * Read attribute value rsp message. - */ -typedef struct { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - ble_status_t status; - uint16_t conn_handle; /**< GAP connection handle */ - uint16_t val_handle; /**< handle of attribute value */ - uint16_t len; /**< length of value returned */ - uint16_t offset; /**< offset in the value. the same as in the rd request! */ - uint8_t value[]; /**< value data of length \ref len */ -} ble_gatts_rd_attrib_val_rsp_t; - -/** - * Indication or notification. - */ -typedef struct { - uint16_t val_handle; /**< handle of attribute value */ - uint16_t len; /**< len of attribute data value to indicate */ - uint8_t *p_data; /**< data to indicate (maybe NULL if currently stored shall be used) */ - uint16_t offset; /**< optional offset into attribute value data */ -} ble_gatts_ind_params_t; - -/** - * Connection related attribute data parameters (stack specific). - */ -typedef struct { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - ble_status_t status; /**< result for request, if not success, the data afterwards may not be valid */ - uint16_t conn_handle; /**< connection handle of the retrieved connection attribute data */ - uint16_t len; /**< length of the following connection attribute data */ - uint8_t conn_attr_data[]; -} ble_gatts_rd_conn_attr_rsp_t; - -/** - * GATTS timeout @ref MSG_ID_BLE_GATTS_TO_EVT. - */ -typedef struct { - uint16_t reason; /**< reason for timeout */ -} ble_gatts_timeout_evt_t; - -/** - * BLE GATTS Indication Data structure. - */ -struct ble_gatts_evt_msg { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - ble_status_t status; /**< result for request, if not success, the data afterwards may not be valid */ - uint16_t conn_handle; - union { - ble_gatt_wr_evt_t wr; /**< write indication */ - ble_gatts_conn_attrib_missing_evt_t conn_attr; - /**< connection related attribute missing */ - ble_gatts_handle_value_conf_evt_t handle_val_conf; - /**< value confirmation (confirmation of an indication) */ - ble_gatts_timeout_evt_t timeout; /**< GATTS timeout occurred */ - }; -}; - -/** - * Create an new service, primary or include. - * - * @param p_svc_handle service handle - * @param p_uuid UUID of new service - * @param type primary versus included @ref BLE_GATT_SVC_TYPES - * @param cback Callback function to be called on reception of add service response - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE - * @return MSG: MSG_ID_BLE_GATTS_ADD_SERVICE_RSP @ref ble_gatts_add_svc_rsp - */ -int ble_gatts_add_service(svc_client_handle_t * p_svc_handle, - const struct bt_uuid * p_uuid, - uint8_t type, - ble_gatts_add_svc_cback_t cback, - void *p_priv); - -/** - * Include a (secondary) service into a primary service. - * - * @param p_svc_handle service handle - * @param svc_handle service to which to add the included service - * @param svc_handle_to_include the previously created includable service - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE - * @return MSG: MSG_ID_BLE_GATTS_ADD_INCL_SVC @ref ble_gatts_incl_svc_rsp_t - */ -int ble_gatts_add_included_svc(svc_client_handle_t * p_svc_handle, - uint16_t svc_handle, - uint16_t svc_handle_to_include, - void *p_priv); - -/** - * Add a characteristic to service. - * - * @note this may called with the same UUID. the returned handle will be different in this case to - * distinguish multiple instances of the same char - * - * @param p_svc_handle service handle - * @param svc_handle service to which to add the characteristic - * @param p_char meta data for characteristic - * @param cback Callback function called on reception of response message - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE - * @return MSG: MSG_ID_BLE_GATTS_ADD_CHARACTERISTIC @ref ble_gatts_add_char_rsp - */ -int ble_gatts_add_characteristic(svc_client_handle_t * p_svc_handle, - uint16_t svc_handle, - const struct ble_gatts_characteristic * p_char, - ble_gatts_add_char_cback_t cback, - void *p_priv); - -/** - * Add a descriptor to the last added characteristic. - * - * @note The descriptor is automatically added to the latest - * added characteristic - * - * @param p_svc_handle service handle - * @param p_desc description of the descriptor - * @param cback Callback function called on reception of response message - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE - * @return MSG: MSG_ID_BLE_GATTS_ADD_DESCRIPTOR @ref ble_gatts_add_desc_rsp - */ -int ble_gatts_add_descriptor(svc_client_handle_t * p_svc_handle, - const struct ble_gatts_descriptor * p_desc, - ble_gatts_add_desc_cback_t cback, - void * p_priv); - -/** - * Start BLE Service setup before. - * - * @param p_svc_handle service handle - * @param svc_handle service to start - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE - * @return MSG: MSG_ID_BLE_GATTS_START_SERVICE @ref ble_rsp - */ -int ble_gatts_start_service(svc_client_handle_t * p_svc_handle, - uint16_t svc_handle, - void *p_priv); - -/** - * Stop and remove service. - * - * @note Not yet supported - * - * @param p_svc_handle service handle - * @param svc_handle handle of characteristic to which to add the descriptor - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE - * @return MSG: MSG_ID_BLE_GATTS_REMOVE_SERVICE_RSP @ref ble_rsp - */ -int ble_gatts_remove_service(svc_client_handle_t * p_svc_handle, - uint16_t svc_handle, - void *p_priv); - -/** - * Send a service change indication. - * - * @note Not yet supported - * - * @param p_svc_handle service handle - * @param conn_handle handle of the connection affected by the service layout change - * @param start_handle start handle of changed attribute handle range - * @param end_handle end handle of changed attribute handle range - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE - * @return MSG: MSG_ID_BLE_GATTS_INDICATE_SERVICE_CHANGE @ref ble_gatts_rsp_t - */ -int ble_gatts_send_svc_changed(svc_client_handle_t * p_svc_handle, - uint16_t conn_handle, - uint16_t start_handle, - uint16_t end_handle, - void *p_priv); - -/** - * Set an attribute value. - * - * @param p_svc_handle service handle - * @param value_handle handle of value to change - * @param len length of attribute value to write - * @param p_value attribute value data to write - * @param offset optional offset from which on to write the attribute value data - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE - * @return MSG: MSG_ID_BLE_GATTS_WR_ATTRIBUTE_VALUE ble_gatts_wr_attr_rsp_msg - */ -int ble_gatts_set_attribute_value(svc_client_handle_t * p_svc_handle, - uint16_t value_handle, - uint16_t len, - const uint8_t * p_value, - uint16_t offset, - void *p_priv); - -/** - * Get an attribute value. - * - * @param p_svc_handle service handle - * @param value_handle handle of the attribute value to retrieve - * @param len length of the attribute value to get - * @param offset optional offset from which on to get the attribute value - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE - * @return MSG: MSG_ID_BLE_GATTS_RD_ATTRIBUTE_VALUE @ref ble_gatts_rd_attrib_val_rsp_t - */ -int ble_gatts_get_attribute_value(svc_client_handle_t * p_svc_handle, - uint16_t value_handle, - uint16_t len, - uint16_t offset, - void *p_priv); - -/** - * Send notification. - * - * @param p_svc_handle service handle - * @param conn_handle handle of the connection affected by the service layout change - * @param p_ind_params length of attribute value to write - * @param cback callback function to be called on reception of reception of notif response - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE - * @return MSG: MSG_ID_BLE_GATTS_SEND_NOTIF_RSP @ref ble_gatts_notif_ind_rsp_msg - */ -int ble_gatts_send_notif(svc_client_handle_t * p_svc_handle, - uint16_t conn_handle, - const ble_gatts_ind_params_t * p_params, - ble_gatts_notif_ind_cback_t cback, - void *p_priv); - -/** - * Send indication. - * - * @param p_svc_handle service handle - * @param conn_handle handle of the connection affected by the service layout change - * @param p_ind_params length of attribute value to write - * @param cback callback function to be called on reception of reception of ind response - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE - * @return MSG: MSG_ID_BLE_GATTS_SEND_NOTIF_RSP @ref ble_gatts_notif_ind_rsp_msg - */ -int ble_gatts_send_ind(svc_client_handle_t * p_svc_handle, - uint16_t conn_handle, - const ble_gatts_ind_params_t * p_params, - ble_gatts_notif_ind_cback_t cback, - void *p_priv); - -/** - * Write stack specific data of a previously bonded connection. - * - * @note this data is typically stored in NVRAM in relation ship to the bonded device! - * (e.g. CCD) - * - * @param p_svc_handle service handle - * @param conn_handle handle of the connection - * @param p_data data blob specific to stack to write - * @param len length of above byte stream (little endian) - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE - * @return MSG: MSG_ID_BLE_GATTS_WR_CONN_ATTRIBUTES @ref ble_gatts_rsp_t - */ -int ble_gatts_write_conn_attributes(svc_client_handle_t * p_svc_handle, - uint16_t conn_handle, - const uint8_t * p_data, - uint16_t len, - void *p_priv); - -/** - * Read stack specific data of the bonded connection. - * - * @note this data is typically stored in NVRAM in relation ship to the bonded device! - * - * @param p_svc_handle service handle - * @param conn_handle handle of the connection - * @param p_priv pointer to private data - * - * @return @ref OS_ERR_TYPE - * @return MSG: MSG_ID_BLE_GATTS_RD_CONN_ATTRIBUTES @ref ble_gatts_rd_conn_attr_rsp_t - */ -int ble_gatts_read_conn_attributes(svc_client_handle_t * p_svc_handle, - uint16_t conn_handle, - void *p_priv); - -/** @} */ - -#endif diff --git a/system/libarc32_arduino101/framework/include/services/services_ids.h b/system/libarc32_arduino101/framework/include/services/services_ids.h index ccf2260c..c153a468 100644 --- a/system/libarc32_arduino101/framework/include/services/services_ids.h +++ b/system/libarc32_arduino101/framework/include/services/services_ids.h @@ -57,6 +57,7 @@ enum { CFW_LAST_SERVICE_ID = 17 }; +#define MSG_ID_BLE_SERVICE_BASE (BLE_SERVICE_ID << 10) #define BLE_SERVICE_MSG_BASE (BLE_SERVICE_ID << 10) #define BLE_SERVICE_GAP_MSG_BASE (BLE_CORE_SERVICE_ID << 10) #define MSG_ID_GPIO_BASE (SOC_GPIO_SERVICE_ID << 10) diff --git a/system/libarc32_arduino101/framework/include/util/misc.h b/system/libarc32_arduino101/framework/include/util/misc.h new file mode 100644 index 00000000..ea2cdff4 --- /dev/null +++ b/system/libarc32_arduino101/framework/include/util/misc.h @@ -0,0 +1,110 @@ +/* + * 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 __MISC_H__ +#define __MISC_H__ + +/* required for offsetof */ +#include + +//#include "util/compiler.h" + +/** Generate a build time error if a condition is met + * + * @param condition The condition to be tested + * + * @note This function should be used inside a code block + */ +#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2 * !!(condition)])) + +/** Generate a build time error if a condition is met or return 0 + * + * @param condition The condition to be tested + * + * @return 0 + */ +#define BUILD_BUG_ON_ZERO(e) (sizeof(char[1 - 2 * !!(e)]) - 1) + +/** + * Similar to BUILD_BUG_ON_ZERO + */ +#define STATIC_ASSERT(EXPR) typedef char __unused static_assert_failed[(EXPR) ? \ + 1 : -1] + +/** Generate a build time error if the parameter is not an array otherwise return 0 + * + * @param A The array to be checked. + * + * @return 0 + */ +#define __must_be_array(A) \ + BUILD_BUG_ON_ZERO (HAVE_SAME_TYPE(A, &A[0])) + +/** + * Check if the integer provided is a power of two. + * + * @param A Number to be tested. + * + * @return true if value is power of two else false. + */ +#define IS_POWER_OF_TWO(A) ((A) != 0 && (((A)-1) & (A)) == 0) + +/** + * Given a pointer on a member, return the pointer on the containing struct + * + * @param ptr Pointer to the member + * @param type Containing struct + * @param member Member name + * + * @return Casted pointer to the struct + * + * @note Use if possible CONTAINER_OF() defined in Zephyr include + */ +#define container_of(ptr, type, member) ({ \ + (type *)((char *)(ptr) - \ + offsetof(type, member)); }) + +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +/** + * Concatenate 4 ASCII characters into an integer + * + * @param key_1 first ASCII char + * @param key_2 second ASCII char + * @param key_3 third ASCII char + * @param key_4 fourth ASCII char + * + * @return the integer representation of the four-digit string + */ +#define GEN_KEY(key_1, key_2, key_3, key_4) \ + (key_1 << 24) | (key_2 << 16) | (key_3 << 8) | key_4 + +#endif /* __MISC_H__ */ diff --git a/system/libarc32_arduino101/framework/src/cfw/service_api.c b/system/libarc32_arduino101/framework/src/cfw/service_api.c index 936fa3f9..28b5bfe5 100644 --- a/system/libarc32_arduino101/framework/src/cfw/service_api.c +++ b/system/libarc32_arduino101/framework/src/cfw/service_api.c @@ -122,54 +122,6 @@ struct cfw_message * cfw_alloc_internal_msg(int msg_id, int size, void * priv) { return evt; } -void default_msg_handler(struct cfw_message * msg, void *data) { - pr_error(LOG_MODULE_CFW, "Bug: %s should not be called data: %p", __func__, data); - cfw_dump_message(msg); -} - -void client_handle_message(struct cfw_message * msg, void *param) { - _cfw_handle_t * h = (_cfw_handle_t*)param; - switch(CFW_MESSAGE_ID(msg)) { - case MSG_ID_CFW_OPEN_SERVICE: - { - cfw_open_conn_rsp_msg_t * cnf = (cfw_open_conn_rsp_msg_t *) msg; - /** Make client handle point to server handle */ - ((svc_client_handle_t*)cnf->client_handle)->server_handle = cnf->svc_server_handle; - /** Initialize service port. */ - ((svc_client_handle_t*)cnf->client_handle)->port = cnf->port; -#ifndef CONFIG_INFRA_IS_MASTER - /* Set local port and cpu id */ - if (get_cpu_id() != cnf->cpu_id) { - port_set_port_id(cnf->port); - port_set_cpu_id(cnf->port, cnf->cpu_id); - } -#endif - break; - } - case MSG_ID_CFW_CLOSE_SERVICE: - { - /* Free client-side conn */ - bfree(msg->conn); - break; - } - default: - //Nothing to do - break; - } - h->handle_msg(msg, h->data); -} - -cfw_handle_t cfw_init(void * queue, handle_msg_cb_t cb, void *cb_data) { - _cfw_handle_t * handle = (_cfw_handle_t*)balloc(sizeof(*handle), NULL); - handle->handle_msg = cb; - handle->data = cb_data; - - handle->client_port_id = port_alloc(queue); - - cfw_port_set_handler(handle->client_port_id, client_handle_message, handle); - - return (cfw_handle_t) handle; -} int _cfw_send_message(struct cfw_message * message) { diff --git a/system/libarc32_arduino101/framework/src/cfw_platform.c b/system/libarc32_arduino101/framework/src/cfw_platform.c index b2da0288..5daa0951 100644 --- a/system/libarc32_arduino101/framework/src/cfw_platform.c +++ b/system/libarc32_arduino101/framework/src/cfw_platform.c @@ -42,10 +42,11 @@ #include "cfw/cfw_messages.h" -#include "nordic_interface.h" +#include "nble_driver.h" /* FIXME: Service manager API */ extern void _cfw_init(void *); +extern void ble_cfw_service_init(int service_id, T_QUEUE queue); extern void *services; @@ -81,21 +82,6 @@ static void free_message_ipc(struct message *msg) { extern "C" { #endif - -void cfw_platform_nordic_init(void) -{ - /* Setup UART0 for BLE communication, HW flow control required */ - SET_PIN_MODE(18, QRK_PMUX_SEL_MODEA); /* UART0_RXD */ - SET_PIN_MODE(19, QRK_PMUX_SEL_MODEA); /* UART0_TXD */ - SET_PIN_MODE(40, QRK_PMUX_SEL_MODEB); /* UART0_CTS_B */ - SET_PIN_MODE(41, QRK_PMUX_SEL_MODEB); /* UART0_RTS_B */ - - /* Reset the nordic to force sync - Warning: not working everytime */ - nordic_interface_init(service_mgr_queue); - uart_ipc_init(0); - uart_ipc_set_channel(uart_ipc_channel_open(SYNC_CHANNEL, uart_ipc_message_cback)); -} - static void cfw_platform_mbx_int_enable(void) { /* Set up mailbox interrupt handler */ @@ -116,8 +102,6 @@ void cfw_platform_init(void) set_cpu_id(CPU_ID_ARC); set_cpu_message_sender(ipc_remote_cpu, send_message_ipc); set_cpu_free_handler(ipc_remote_cpu, free_message_ipc); - set_cpu_message_sender(CPU_ID_BLE, send_message_ipc_uart); - set_cpu_free_handler(CPU_ID_BLE, free_message_ipc_uart); service_mgr_queue = queue_create(IPC_QUEUE_DEPTH, NULL); @@ -131,6 +115,7 @@ void cfw_platform_init(void) shared_data->services, shared_data->service_mgr_port_id); #else _cfw_init(service_mgr_queue); + ble_cfw_service_init(BLE_SERVICE_ID, service_mgr_queue); /* Initialized shared structure. */ shared_data->ports = port_get_port_table(); diff --git a/system/libarc32_arduino101/framework/src/infra/log.c b/system/libarc32_arduino101/framework/src/infra/log.c index c0d0ab7a..7e21decf 100644 --- a/system/libarc32_arduino101/framework/src/infra/log.c +++ b/system/libarc32_arduino101/framework/src/infra/log.c @@ -62,10 +62,12 @@ void log_init() uint8_t i; for (i = 0; i < LOG_MODULE_NUM; i++) { - global_infos.modules_filter[i].status = 1; - global_infos.modules_filter[i].log_level = LOG_LEVEL_INFO; + global_infos.modules_filter[i].status = true; + global_infos.modules_filter[i].log_level = LOG_LEVEL_DEBUG; } - global_infos.log_level_limit = LOG_LEVEL_INFO; + global_infos.log_level_limit = LOG_LEVEL_DEBUG; + + global_infos.modules_filter[LOG_MODULE_MAIN].log_level = LOG_LEVEL_INFO; log_impl_init(); } diff --git a/system/libarc32_arduino101/framework/src/infra/log_impl_printk.c b/system/libarc32_arduino101/framework/src/infra/log_impl_printk.c index ed8f1e99..958b7521 100644 --- a/system/libarc32_arduino101/framework/src/infra/log_impl_printk.c +++ b/system/libarc32_arduino101/framework/src/infra/log_impl_printk.c @@ -32,13 +32,14 @@ #include "log_impl.h" #include "infra/log_backend.h" -extern int printk(const char * format, ...); +//extern int printk(const char * format, ...); +extern void printk(const char *fmt, va_list args); uint32_t log_write_msg(uint8_t level, uint8_t module, const char *format, va_list args) { // TODO - implement printk -// printk(format, args); + printk(format, args); return 0; } diff --git a/system/libarc32_arduino101/framework/src/nordic_interface.c b/system/libarc32_arduino101/framework/src/nordic_interface.c deleted file mode 100644 index daee0f12..00000000 --- a/system/libarc32_arduino101/framework/src/nordic_interface.c +++ /dev/null @@ -1,192 +0,0 @@ -/* - * 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 "os/os.h" -#include "cfw/cfw.h" -#include "cfw/cfw_debug.h" -#include "cfw/cfw_internal.h" -#include "cfw/cfw_messages.h" -#include "infra/ipc_uart.h" -#include "infra/ipc_requests.h" -#include "infra/log.h" -#include "infra/message.h" -#include "infra/time.h" -#include "drivers/soc_gpio.h" -#include "platform.h" - -#include "portable.h" - -/* Macro definition for reset pin */ -#define BLE_SW_CLK_PIN 27 -#define BLE_SWDIO_PIN 6 -#define RESET_PIN BLE_SWDIO_PIN -#define ATP_BLE_INT 5 - -static T_QUEUE service_mgr_queue; - -/* When we set val to 1 it will wake up the Nordic, setting it to 0 - * will set it back to sleep. */ -static int nordic_wake_assert(bool val) -{ - uint8_t ret = soc_gpio_write(SOC_GPIO_32_ID, ATP_BLE_INT, val); - if (ret != DRV_RC_OK) - pr_error(LOG_MODULE_IPC, "Error setting ATP_BLE_INT %d", val); - return ret; -} - -void nordic_wake_deassert(void* ignored) -{ - nordic_wake_assert(0); -} - -int send_message_ipc_uart(struct cfw_message * message) { - int ret = uart_ipc_send_message(uart_ipc_get_channel(), - CFW_MESSAGE_LEN(message), message); - message_free((struct message *)message); - return ret; -} - -void free_message_ipc_uart(void * ptr) { - bfree(ptr); -} - -/** - * IPC CFW message format is the following (little endian): - * ------------------------------------- - * | len: 2 bytes | chan 1 byte: sender cpu id: 1 byte | - * ------------------------------------- - * | REQUEST_ID | payload | - * ------------------------------------- - * - * For TYPE_MESSAGE request, the payload is the message copy. - * For TYPE_FREE is not valid (this ipc is not shared mem based) - */ -void uart_ipc_message_cback(uint8_t cpu_id, int channel, int len, void * p_data) -{ - struct cfw_message * msg; - unsigned int request = *(uint32_t*)p_data; - - switch (request) { - case IPC_MSG_TYPE_MESSAGE: - { - OS_ERR_TYPE error = E_OS_OK; - int size = len - sizeof(uint32_t); - msg = (struct cfw_message *)message_alloc(size, &error); - if (error != E_OS_OK) - pr_error(LOG_MODULE_IPC, "NO MEM: error: %d size: %d", error, size); - else { - memcpy(msg, (uint8_t *)p_data + sizeof(uint32_t), size); - handle_ipc_sync_request(cpu_id, request, 0, 0, msg); - } - break; - } - case IPC_REQUEST_ALLOC_PORT: - { - unsigned int ret; - unsigned int error; - uint16_t port_id = port_alloc(NULL); - port_set_cpu_id(port_id, cpu_id); - ret = port_id; - pr_info(LOG_MODULE_IPC, "%s return port_id %d", __func__, ret); - error = uart_ipc_send_sync_resp(channel, request, ret, 0, NULL); - if (error) - pr_error(LOG_MODULE_IPC, "%s returned error from ipc uart sync resp %d", __func__, error); - break; - } - default: - { - unsigned int error; - int32_t * p = ((int32_t *) p_data) + 1; - int32_t param0 = *p++; - int32_t param1 = *p++; - void * ptr = (void *) *p; - pr_info(LOG_MODULE_IPC, "%s request %xh, param1 %d, param2 %d", __func__, request, param0, param1); - handle_ipc_sync_request(cpu_id, request, param0, param1, ptr); - error = uart_ipc_send_sync_resp(channel, request, 0, 0, NULL); - if (error) - pr_error(LOG_MODULE_IPC, "%s returned error from ipc uart sync resp %d", __func__, error); - break; - } - } - bfree(p_data); - - /* Dequeue and process any new messages received */ - while(queue_process_message(service_mgr_queue) != 0); -} - -/* Nordic reset is achieved by asserting low the SWDIO pin. - * However, the Nordic chip can be in SWD debug mode, and NRF_POWER->RESET = 0 due to, - * other constraints: therefore, this reset might not work everytime, especially after - * flashing or debugging. - */ -static int nordic_reset(void) -{ - /* RESET_PIN depends on the board and the local configuration: check top of file */ - uint32_t delay_until; - gpio_cfg_data_t pin_cfg = { .gpio_type = GPIO_OUTPUT }; - - soc_gpio_set_config(SOC_GPIO_32_ID, RESET_PIN, &pin_cfg); - - /* Reset hold time is 0.2us (normal) or 100us (SWD debug) */ - soc_gpio_write(SOC_GPIO_32_ID, RESET_PIN, 0); - /* Wait for ~1ms */ - delay_until = get_uptime_32k() + 32768; - while (get_uptime_32k() < delay_until); - /* De-assert the reset */ - soc_gpio_write(SOC_GPIO_32_ID, RESET_PIN, 1); - - /* Set back GPIO to input to avoid interfering with external debugger */ - pin_cfg.gpio_type = GPIO_INPUT; - soc_gpio_set_config(SOC_GPIO_32_ID, RESET_PIN, &pin_cfg); - - return 0; -} - -int nordic_interface_init(T_QUEUE queue) -{ - uint8_t ret; - gpio_cfg_data_t config; - - service_mgr_queue = queue; - - config.gpio_type = GPIO_OUTPUT; - ret = soc_gpio_set_config(SOC_GPIO_32_ID, ATP_BLE_INT, &config); - if (ret != DRV_RC_OK) - return -1; - ret = nordic_wake_assert(1); - if (ret != DRV_RC_OK) - return -1; - ret = nordic_reset(); - if (ret != DRV_RC_OK) - return -1; - - return 0; -} diff --git a/system/libarc32_arduino101/framework/src/os/os.c b/system/libarc32_arduino101/framework/src/os/os.c index 63aa31f6..ed84eb8b 100644 --- a/system/libarc32_arduino101/framework/src/os/os.c +++ b/system/libarc32_arduino101/framework/src/os/os.c @@ -34,6 +34,7 @@ /************************* MEMORY *************************/ #ifdef TRACK_ALLOCS +#include "infra/log.h" int alloc_count = 0; #endif @@ -45,6 +46,7 @@ void * cfw_alloc(int size, OS_ERR_TYPE * err) { (*(int*) ptr) = size; #ifdef TRACK_ALLOCS alloc_count++; + pr_info(0, "alloc_count - %d", alloc_count); #endif interrupt_unlock(flags); return ptr; @@ -56,6 +58,7 @@ void cfw_free(void * ptr, OS_ERR_TYPE * err) { int flags = interrupt_lock(); #ifdef TRACK_ALLOCS alloc_count--; + pr_info(0, "alloc_countf - %d", alloc_count); #endif free(ptr); interrupt_unlock(flags); diff --git a/system/libarc32_arduino101/framework/src/os/panic.c b/system/libarc32_arduino101/framework/src/os/panic.c new file mode 100644 index 00000000..9fe88ef9 --- /dev/null +++ b/system/libarc32_arduino101/framework/src/os/panic.c @@ -0,0 +1,16 @@ + +#include "os/os.h" + +extern void _do_fault(); +void panic(int x) +{ + _do_fault(); +} + + +void __assert_fail() +{ + panic(-10); +} + + diff --git a/system/libarc32_arduino101/framework/src/services/ble/ble_protocol.h b/system/libarc32_arduino101/framework/src/services/ble/ble_protocol.h deleted file mode 100644 index 460e7458..00000000 --- a/system/libarc32_arduino101/framework/src/services/ble/ble_protocol.h +++ /dev/null @@ -1,233 +0,0 @@ -/* - * 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_PROTOCOL_H__ -#define __BLE_PROTOCOL_H__ - -/** - * @defgroup ble_protocol BLE protocol definitions - * - * BT Spec definitions. - * @ingroup ble_service - * @{ - * - * Bluetooth SIG defined macros and enum extracted from Bluetooth Spec 4.2 - */ -#define BLE_MAX_DEVICE_NAME 20 /**< Max BLE device name length, spec size: 248 */ -#define BLE_MAX_ADV_SIZE 31 - -/** Advertising Data Type. */ -#define BLE_ADV_TYPE_FLAGS 0x01 /* Flags */ -#define BLE_ADV_TYPE_INC_16_UUID 0x02 /* Incomplete List of 16-bit Service Class UUIDs */ -#define BLE_ADV_TYPE_COMP_16_UUID 0x03 /* Complete List of 16-bit Service Class UUIDs */ -#define BLE_ADV_TYPE_INC_32_UUID 0x04 /* Incomplete List of 32-bit Service Class UUIDs */ -#define BLE_ADV_TYPE_COMP_32_UUID 0x05 /* Complete List of 32-bit Service Class UUIDs */ -#define BLE_ADV_TYPE_INC_128_UUID 0x06 /* Incomplete List of 128-bit Service Class UUIDs */ -#define BLE_ADV_TYPE_COMP_128_UUID 0x07 /* Complete List of 128-bit Service Class UUIDs */ -#define BLE_ADV_TYPE_SHORT_LOCAL_NAME 0x08 /* Shortened Local Name */ -#define BLE_ADV_TYPE_COMP_LOCAL_NAME 0x09 /* Complete Local Name */ -#define BLE_ADV_TYPE_TX_POWER 0x0A /* Tx Power Level */ -#define BLE_ADV_TYPE_CLASS 0x0D /* Class of Device */ -#define BLE_ADV_TYPE_PAIRING_C 0x0E /* Simple Pairing Hash C */ -#define BLE_ADV_TYPE_PAIRING_C_192 0x0E /* Simple Pairing Hash C-192 */ -#define BLE_ADV_TYPE_PAIRING_R_192 0x0F /* Simple Pairing Randomizer R-192 */ -#define BLE_ADV_TYPE_DEVICE_ID 0x10 /* Device ID */ -#define BLE_ADV_TYPE_TK_VALUE 0x10 /* Security Manager TK Value */ -#define BLE_ADV_TYPE_OOB_FLAGS 0x11 /* Security Manager Out of Band Flags */ -#define BLE_ADV_TYPE_CONN_INTERVAL_RANGE 0x12 /* Slave Connection Interval Range */ -#define BLE_ADV_TYPE_SERVICE_SOLICITATION_16_UUID 0x14 /* List of 16-bit Service Solicitation UUIDs */ -#define BLE_ADV_TYPE_SERVICE_SOLICITATION_32_UUID 0x1F /* List of 32-bit Service Solicitation UUIDs */ -#define BLE_ADV_TYPE_SERVICE_SOLICITATION_128_UUID 0x15 /* List of 128-bit Service Solicitation UUIDs */ -#define BLE_ADV_TYPE_SERVICE_DATA 0x16 /* Service Data */ -#define BLE_ADV_TYPE_SERVICE_DATA_16_UUID 0x16 /* Service Data - 16-bit UUID */ -#define BLE_ADV_TYPE_SERVICE_DATA_32_UUID 0x20 /* Service Data - 32-bit UUID */ -#define BLE_ADV_TYPE_SERVICE_DATA_128_UUID 0x21 /* Service Data - 128-bit UUID */ -#define BLE_ADV_TYPE_SEC_CONF_VALUE 0x22 /* LE Secure Connections Confirmation Value */ -#define BLE_ADV_TYPE_SEC_RANDOM_VALUE 0x23 /* LE Secure Connections Random Value */ -#define BLE_ADV_TYPE_PUB_TARGET_ADDR 0x17 /* Public Target Address */ -#define BLE_ADV_TYPE_APPEARANCE 0x19 /* Appearance */ -#define BLE_ADV_TYPE_ADV_INTERVAL 0x1A /* Advertising Interval */ -#define BLE_ADV_TYPE_DEVICE_ADDR 0x1B /* LE Bluetooth Device Address */ -#define BLE_ADV_TYPE_ROLE 0x1C /* LE Role */ -#define BLE_ADV_TYPE_PAIRING_C_256 0x1D /* Simple Pairing Hash C-256 */ -#define BLE_ADV_TYPE_PAIRING_R_256 0x1E /* Simple Pairing Randomizer R-256 */ -#define BLE_ADV_TYPE_3D 0x3D /* 3D Information Data */ -#define BLE_ADV_TYPE_MANUFACTURER 0xFF /* Manufacturer Specific Data */ - -/** BLE Service UUID Definitions. */ -#define BLE_SVC_UUID_IMMEDIATE_ALERT_SERVICE 0x1802 /**< Immediate Alert service UUID. */ -#define BLE_SVC_UUID_LINK_LOSS_SERVICE 0x1803 /**< Link Loss service UUID. */ -#define BLE_SVC_UUID_TX_POWER_SERVICE 0x1804 /**< TX Power service UUID. */ -#define BLE_SVC_UUID_CURRENT_TIME_SERVICE 0x1805 /**< Current Time service UUID. */ -#define BLE_SVC_UUID_REFERENCE_TIME_UPDATE_SERVICE 0x1806 /**< Reference Time Update service UUID. */ -#define BLE_SVC_UUID_NEXT_DST_CHANGE_SERVICE 0x1807 /**< Next Dst Change service UUID. */ -#define BLE_SVC_UUID_GLUCOSE_SERVICE 0x1808 /**< Glucose service UUID. */ -#define BLE_SVC_UUID_HEALTH_THERMOMETER_SERVICE 0x1809 /**< Health Thermometer service UUID. */ -#define BLE_SVC_UUID_DEVICE_INFORMATION_SERVICE 0x180A /**< Device Information service UUID. */ -#define BLE_SVC_UUID_HEART_RATE_SERVICE 0x180D /**< Heart Rate service UUID. */ -#define BLE_SVC_UUID_PHONE_ALERT_STATUS_SERVICE 0x180E /**< Phone Alert Status service UUID. */ -#define BLE_SVC_UUID_BATTERY_SERVICE 0x180F /**< Battery service UUID. */ -#define BLE_SVC_UUID_BLOOD_PRESSURE_SERVICE 0x1810 /**< Blood Pressure service UUID. */ -#define BLE_SVC_UUID_ALERT_NOTIFICATION_SERVICE 0x1811 /**< Alert Notification service UUID. */ -#define BLE_SVC_UUID_HUMAN_INTERFACE_DEVICE_SERVICE 0x1812 /**< Human Interface Device service UUID. */ -#define BLE_SVC_UUID_SCAN_PARAMETERS_SERVICE 0x1813 /**< Scan Parameters service UUID. */ -#define BLE_SVC_UUID_RUNNING_SPEED_AND_CADENCE 0x1814 /**< Running Speed and Cadence service UUID. */ -#define BLE_SVC_UUID_CYCLING_SPEED_AND_CADENCE 0x1816 /**< Cycling Speed and Cadence service UUID. */ - -#define BLE_SVC_GAP_ADV_FLAG_LE_LIMITED_DISC_MODE (0x01) /**< LE Limited Discoverable Mode. */ -#define BLE_SVC_GAP_ADV_FLAG_LE_GENERAL_DISC_MODE (0x02) /**< LE General Discoverable Mode. */ -#define BLE_SVC_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED (0x04) /**< BR/EDR not supported. */ -#define BLE_SVC_GAP_ADV_FLAG_LE_BR_EDR_CONTROLLER (0x08) /**< Simultaneous LE and BR/EDR, Controller. */ -#define BLE_SVC_GAP_ADV_FLAG_LE_BR_EDR_HOST (0x10) /**< Simultaneous LE and BR/EDR, Host. */ -#define BLE_SVC_GAP_ADV_FLAGS_LE_ONLY_LIMITED_DISC_MODE (BLE_SVC_GAP_ADV_FLAG_LE_LIMITED_DISC_MODE | BLE_SVC_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED) /**< LE Limited Discoverable Mode, BR/EDR not supported. */ -#define BLE_SVC_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE (BLE_SVC_GAP_ADV_FLAG_LE_GENERAL_DISC_MODE | BLE_SVC_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED) /**< LE General Discoverable Mode, BR/EDR not supported. */ - - -/** - * Characteristics UUID definitions. - */ - -/* GAP */ -#define BLE_GAP_DEVICE_NAME 0x2A00 -#define BLE_GAP_APPEARANCE 0x2A01 -#define BLE_GAP_PER_PRIVACY_FLAG 0x2A02 -#define BLE_GAP_RECONN_ADDR 0x2A03 -#define BLE_GAP_PREF_CONN_PARAM 0x2A04 - -/* DIS */ -#define BLE_DIS_MANUFACTURER_NAME 0x2A29 -#define BLE_DIS_MODEL_NB 0x2A24 -#define BLE_DIS_SERIAL_NB 0x2A25 -#define BLE_DIS_FW_REV 0x2A26 -#define BLE_DIS_HW_REV 0x2A27 -#define BLE_DIS_SW_REV 0x2A28 -#define BLE_DIS_SYS_ID 0x2A23 -#define BLE_DIS_CERTIF_DATA_LIST 0x2A2A -#define BLE_DIS_PNP_ID 0x2A50 - -/* BATTERY */ -#define BLE_BAT_BAT_LEVEL 0x2A19 - -/* HR */ -#define BLE_HEART_RATE_MEASUREMENT 0x2A37 -#define BLE_HEART_RATE_SENSOR_LOCATION 0x2A38 -#define BLE_HEART_RATE_CONTROL_POINT 0x2A39 - -/* RSC */ -#define BLE_RSC_MEASUREMENT 0x2A53 -#define BLE_RSC_SENSOR_LOCATION 0x2A5D -#define BLE_RSC_SUPPORTED_FEATURE 0x2A54 -#define BLE_RSC_CONTROL_POINT 0x2A55 - -/*CSC */ -#define BLE_CSC_MEASUREMENT 0x2A5B -#define BLE_CSC_SENSOR_LOCATION BLE_RSC_SENSOR_LOCATION -#define BLE_CSC_SUPPORTED_FEATURE 0x2A5C -#define BLE_CSC_CONTROL_POINT 0x2A55 - -/* CP */ -#define BLE_CP_MEASUREMENT 0x2A63 -#define BLE_CP_SENSOR_LOCATION BLE_RSC_SENSOR_LOCATION -#define BLE_CP_SUPPORTED_FEATURE 0x2A65 -#define BLE_CP_POWER_VECTOR 0x2A64 -#define BLE_CP_CONTROL_POINT 0x2A66 - -/* HCI status (error) codes as per BT spec */ -#define HCI_REMOTE_USER_TERMINATED_CONNECTION 0x13 -#define HCI_REMOTE_DEV_TERMINATION_DUE_TO_LOW_RESOURCES 0x14 -#define HCI_REMOTE_DEV_TERMINATION_DUE_TO_POWER_OFF 0x15 -#define HCI_LOCAL_HOST_TERMINATED_CONNECTION 0x16 - -/** BLE GAP Appearance Characteristic definitions. - * - * See http://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.gap.appearance.xml - */ -#define BLE_GAP_APPEARANCE_TYPE_UNKNOWN 0 -#define BLE_GAP_APPEARANCE_TYPE_GENERIC_PHONE 64 -#define BLE_GAP_APPEARANCE_TYPE_GENERIC_COMPUTER 128 -#define BLE_GAP_APPEARANCE_TYPE_GENERIC_WATCH 192 -#define BLE_GAP_APPEARANCE_TYPE_WATCH_SPORTS_WATCH 193 -#define BLE_GAP_APPEARANCE_TYPE_GENERIC_CLOCK 256 -#define BLE_GAP_APPEARANCE_TYPE_GENERIC_DISPLAY 320 -#define BLE_GAP_APPEARANCE_TYPE_GENERIC_REMOTE_CONTROL 384 -#define BLE_GAP_APPEARANCE_TYPE_GENERIC_EYE_GLASSES 448 -#define BLE_GAP_APPEARANCE_TYPE_GENERIC_TAG 512 -#define BLE_GAP_APPEARANCE_TYPE_GENERIC_KEYRING 576 -#define BLE_GAP_APPEARANCE_TYPE_GENERIC_MEDIA_PLAYER 640 -#define BLE_GAP_APPEARANCE_TYPE_GENERIC_BARCODE_SCANNER 704 -#define BLE_GAP_APPEARANCE_TYPE_GENERIC_THERMOMETER 768 -#define BLE_GAP_APPEARANCE_TYPE_THERMOMETER_EAR 769 -#define BLE_GAP_APPEARANCE_TYPE_GENERIC_HEART_RATE_SENSOR 832 -#define BLE_GAP_APPEARANCE_TYPE_HEART_RATE_SENSOR_HEART_RATE_BELT 833 -#define BLE_GAP_APPEARANCE_TYPE_GENERIC_BLOOD_PRESSURE 896 -#define BLE_GAP_APPEARANCE_TYPE_BLOOD_PRESSURE_ARM 897 -#define BLE_GAP_APPEARANCE_TYPE_BLOOD_PRESSURE_WRIST 898 -#define BLE_GAP_APPEARANCE_TYPE_GENERIC_HID 960 -#define BLE_GAP_APPEARANCE_TYPE_HID_KEYBOARD 961 -#define BLE_GAP_APPEARANCE_TYPE_HID_MOUSE 962 -#define BLE_GAP_APPEARANCE_TYPE_HID_JOYSTICK 963 -#define BLE_GAP_APPEARANCE_TYPE_HID_GAMEPAD 964 -#define BLE_GAP_APPEARANCE_TYPE_HID_DIGITIZERSUBTYPE 965 -#define BLE_GAP_APPEARANCE_TYPE_HID_CARD_READER 966 -#define BLE_GAP_APPEARANCE_TYPE_HID_DIGITAL_PEN 967 -#define BLE_GAP_APPEARANCE_TYPE_HID_BARCODE 968 -#define BLE_GAP_APPEARANCE_TYPE_GENERIC_GLUCOSE_METER 1024 -#define BLE_GAP_APPEARANCE_TYPE_GENERIC_RUNNING_WALKING_SENSOR 1088 -#define BLE_GAP_APPEARANCE_TYPE_RUNNING_WALKING_SENSOR_IN_SHOE 1089 -#define BLE_GAP_APPEARANCE_TYPE_RUNNING_WALKING_SENSOR_ON_SHOE 1090 -#define BLE_GAP_APPEARANCE_TYPE_RUNNING_WALKING_SENSOR_ON_HIP 1091 -#define BLE_GAP_APPEARANCE_TYPE_GENERIC_CYCLING 1152 -#define BLE_GAP_APPEARANCE_TYPE_CYCLING_CYCLING_COMPUTER 1153 -#define BLE_GAP_APPEARANCE_TYPE_CYCLING_SPEED_SENSOR 1154 -#define BLE_GAP_APPEARANCE_TYPE_CYCLING_CADENCE_SENSOR 1155 -#define BLE_GAP_APPEARANCE_TYPE_CYCLING_POWER_SENSOR 1156 -#define BLE_GAP_APPEARANCE_TYPE_CYCLING_SPEED_CADENCE_SENSOR 1157 -#define BLE_GAP_APPEARANCE_TYPE_GENERIC_PULSE_OXIMETER 3136 -#define BLE_GAP_APPEARANCE_TYPE_PULSE_OXIMETER_FINGERTIP 3137 -#define BLE_GAP_APPEARANCE_TYPE_PULSE_OXIMETER_WRIST_WORN 3138 -#define BLE_GAP_APPEARANCE_TYPE_GENERIC_WEIGHT_SCALE 3200 -#define BLE_GAP_APPEARANCE_TYPE_GENERIC_OUTDOOR_SPORTS_ACT 5184 -#define BLE_GAP_APPEARANCE_TYPE_OUTDOOR_SPORTS_ACT_LOC_DISP 5185 -#define BLE_GAP_APPEARANCE_TYPE_OUTDOOR_SPORTS_ACT_LOC_AND_NAV_DISP 5186 -#define BLE_GAP_APPEARANCE_TYPE_OUTDOOR_SPORTS_ACT_LOC_POD 5187 -#define BLE_GAP_APPEARANCE_TYPE_OUTDOOR_SPORTS_ACT_LOC_AND_NAV_POD 5188 - -/** - * DTM commands, opcodes, indexes. - */ -#define DTM_HCI_CMD 0x01 -#define DTM_HCI_OPCODE2 0x20 - -#define DTM_HCI_STATUS_IDX 6 -#define DTM_HCI_LE_END_IDX (DTM_HCI_STATUS_IDX + 1) - -/** @} */ - -#endif diff --git a/system/libarc32_arduino101/framework/src/services/ble/ble_service_core_int.h b/system/libarc32_arduino101/framework/src/services/ble/ble_service_core_int.h deleted file mode 100644 index d2b9311d..00000000 --- a/system/libarc32_arduino101/framework/src/services/ble/ble_service_core_int.h +++ /dev/null @@ -1,207 +0,0 @@ -/* - * 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_SERVICE_CORE_INT_H__ -#define __BLE_SERVICE_CORE_INT_H__ - -#include -#include "services/ble/ble_service_gap_api.h" -#include "services/ble/ble_service_gattc_api.h" - -/* Forward declarations */ -struct _ble_service_cb; -struct ble_enable_req_msg; - -/** - * BLE common structures. - */ - -struct ble_svc_string { - uint8_t *p_string; /**< String utf8 encoded */ - uint8_t len; /**< length of string */ -}; - -struct ble_svc_sec_mode { - uint8_t rd_perm; - uint8_t wr_perm; -}; - -struct ble_svc_cccd_sec_mode { - uint8_t cccd_wr_perm; - uint8_t rd_perm; /**< Read permissions. */ - uint8_t wr_perm; /**< Write permissions. */ -}; - -struct ble_svc_report_reference { - uint8_t report_id; /**< Non-zero value if these is more than one instance of the same Report Type */ - uint8_t report_type; /**< Type of Report characteristic */ -}; - -struct ble_gap_write_config_req_msg { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - uint16_t appearance; - uint8_t bda_len; - uint8_t name_len; - int8_t tx_power; - struct ble_gap_connection_params peripheral_conn_params; - struct ble_gap_connection_params central_conn_params; - uint8_t data[]; -}; - -struct ble_gap_wr_adv_data_req_msg { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - uint8_t adv_len; - uint8_t scan_rsp_len; - uint8_t data[]; - /* adv_data, - * scan_rsp_dat */ -}; - -struct ble_gap_start_advertise_req_msg { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - uint16_t timeout; - uint16_t interval_min; /**< min interval 0xffff: use default 0x0800 */ - uint16_t interval_max; /**< max interval 0xffff: use default 0x0800 */ - uint8_t type; /**< advertisement types @ref GAP_ADV_TYPES */ - uint8_t filter_policy; /**< filter policy to apply with white list */ - uint8_t options; /**< options see @ref BLE_GAP_ADV_OPTIONS (to be ORed) */ - uint8_t bda_len; /**< non 0 if peer_bda is present */ - uint8_t peer_bda[]; /**< format ble_addr_t */ -}; - -struct ble_gap_conn_update_req_msg { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - uint16_t conn_handle; - struct ble_gap_connection_params conn_params; -}; - -struct ble_gap_svc_local_name_req { - uint8_t sec_mode; /**< security mode for writing device name, @ref BLE_GAP_SEC_MODES (GAP_SEC_NO_PERMISSION: write forbidden) */ - uint8_t authorization; /**< 0: no authorization, 1: authorization required */ - uint8_t len; /**< device name length (0-248) */ - uint8_t name_array[]; /**< name to to write */ -}; - -struct ble_gap_service_write_req_msg { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - uint16_t attr_type; /**< GAP Characteristics attribute type @ref BLE_GAP_SVC_ATTR_TYPE */ - union { - struct ble_gap_svc_local_name_req name; - uint16_t appearance; /**< Appearance UUID */ - struct ble_gap_connection_params conn_params; - /**< Preferred Peripheral Connection Parameters */ - uint8_t car; /**< Central Address Resolution support 0: no, 1: yes */ - }; -}; - -struct ble_gap_disconnect_req_msg { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - uint16_t conn_handle; /**< Connection handle*/ - uint8_t reason; /**< Reason of the disconnect*/ -}; - -struct ble_gap_sm_config_req_msg { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - struct ble_gap_sm_config_params params; -}; - -struct ble_gap_sm_pairing_req_msg { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - struct ble_gap_sm_pairing_params params; - uint16_t conn_handle; -}; - -struct ble_dtm_test_req_msg { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - struct ble_test_cmd params; -}; - -struct ble_gap_set_rssi_report_req_msg { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - struct rssi_report_params params; /**< RSSI report config @ref rssi_report_cfg */ -}; - -struct ble_gattc_discover_primary_service_req_msg { - struct cfw_message header; - uint16_t conn_handle; /**< Connection handle */ - uint8_t data[]; /**< Variable length data of the request (UUID) */ -}; - -struct ble_gattc_discover_included_service_req_msg { - struct cfw_message header; - uint16_t conn_handle; - struct ble_gattc_handle_range handle_range; -}; - -struct ble_gattc_discover_characteristic_req_msg { - struct cfw_message header; - uint16_t conn_handle; - struct ble_gattc_handle_range handle_range; -}; - -struct ble_gattc_discover_descriptor_req_msg { - struct cfw_message header; - uint16_t conn_handle; - struct ble_gattc_handle_range handle_range; -}; - -struct ble_gattc_read_characteristic_req_msg { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - uint16_t conn_handle; /**< Connection handle*/ - uint16_t char_handle; /**< handle of the attribute to be read */ - uint16_t offset; /**< offset into the attribute value to be read */ -}; - -struct _ble_gattc_wr_characteristic { - uint16_t char_handle; /**< handle of characteristic */ - uint16_t len; /**< if len is bigger then ATT MTU size, the controller fragment buffer itself */ - uint8_t wr_type; /**< type of write operation @ref BLE_GATT_WR_OP_TYPES */ - uint8_t value[]; /**< characteristic value to write */ -}; - -struct ble_gattc_write_char_op_req_msg { - struct cfw_message header; - uint16_t conn_handle; - struct _ble_gattc_wr_characteristic wr_char_param; -}; - -/** - * Generic BLE controller commands. - */ -struct ble_generic_cmd_req_msg { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ -}; - - -void ble_core_resume_enable(struct ble_enable_req_msg *req, struct _ble_service_cb * p_cb, uint8_t * p_name); - -void ble_core_delete_conn_params_timer(void); - -#endif diff --git a/system/libarc32_arduino101/framework/src/services/ble/ble_service_gap_api.c b/system/libarc32_arduino101/framework/src/services/ble/ble_service_gap_api.c deleted file mode 100644 index 6b772864..00000000 --- a/system/libarc32_arduino101/framework/src/services/ble/ble_service_gap_api.c +++ /dev/null @@ -1,384 +0,0 @@ -/* - * 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 "services/ble/ble_service_gap_api.h" -#include "ble_protocol.h" -#include "ble_service_core_int.h" -#include "infra/log.h" - -int ble_gap_set_enable_config(svc_client_handle_t * p_svc_handle, - const struct ble_wr_config * p_config, - void * p_priv) -{ - struct ble_gap_write_config_req_msg *msg; - int total_len = sizeof(*msg); - int str_len = 0; - - if (p_config->p_bda) - total_len += sizeof(ble_addr_t); - if (p_config->p_name) { - str_len = strlen((char *)p_config->p_name); - if (str_len > BLE_MAX_DEVICE_NAME) - return -2; - total_len += str_len; - } - msg = (struct ble_gap_write_config_req_msg *) - cfw_alloc_message_for_service(p_svc_handle, - MSG_ID_BLE_GAP_WR_CONF_REQ, - total_len, - p_priv); - msg->name_len = str_len; - msg->appearance = p_config->appearance; - msg->tx_power = p_config->tx_power; - msg->central_conn_params = p_config->central_conn_params; - msg->peripheral_conn_params = p_config->peripheral_conn_params; - if (p_config->p_bda) { - msg->bda_len = sizeof(ble_addr_t); - memcpy(msg->data, p_config->p_bda, msg->bda_len); - } else - msg->bda_len = 0; - if (p_config->p_name) - strcpy((char *)&msg->data[msg->bda_len], (char *)p_config->p_name); - - return cfw_send_message(msg); -} - -int ble_gap_read_bda(svc_client_handle_t * p_svc_handle, void * p_priv) -{ - struct cfw_message *msg = cfw_alloc_message_for_service(p_svc_handle, - MSG_ID_BLE_GAP_RD_BDA_REQ, - sizeof(*msg), - p_priv); - return cfw_send_message(msg); -} - -int ble_gap_wr_adv_data(svc_client_handle_t * p_svc_handle, - const struct ble_gap_adv_rsp_data * p_adv_data, - const struct ble_gap_adv_rsp_data * p_scan_data, - void * p_priv) -{ - int data_len = sizeof(struct ble_gap_wr_adv_data_req_msg); - struct ble_gap_wr_adv_data_req_msg *msg; - - if (NULL != p_adv_data) - data_len += p_adv_data->len; - if (NULL != p_scan_data) - data_len += p_scan_data->len; - - msg = (struct ble_gap_wr_adv_data_req_msg *)cfw_alloc_message_for_service( - p_svc_handle, MSG_ID_BLE_GAP_WR_ADV_DATA_REQ, - data_len, p_priv); - - if (NULL != p_adv_data) { - int i; - msg->adv_len = p_adv_data->len; - for (i = 0; i < msg->adv_len; i++) - msg->data[i] = p_adv_data->p_data[i]; - } else - msg->adv_len = 0; - - if (NULL != p_scan_data) { - int i; - msg->scan_rsp_len = p_scan_data->len; - for (i = 0; i < msg->adv_len; i++) - msg->data[msg->adv_len + i] = p_scan_data->p_data[i]; - } else - msg->scan_rsp_len = 0; - - return cfw_send_message(msg); -} - - -int ble_gap_wr_white_list(svc_client_handle_t * p_svc_handle, - const struct ble_gap_whitelist_info * p_white_list, - void * p_priv) -{ - struct cfw_message *msg = cfw_alloc_message_for_service(p_svc_handle, - MSG_ID_BLE_GAP_WR_WHITE_LIST_REQ, - sizeof(*msg), - p_priv); - return cfw_send_message(msg); -} - -int ble_gap_clr_white_list(svc_client_handle_t * p_svc_handle, - uint32_t wl_hdl, void *p_priv) -{ - struct cfw_message *msg = cfw_alloc_message_for_service(p_svc_handle, - MSG_ID_BLE_GAP_CLR_WHITE_LIST_REQ, - sizeof(*msg), - p_priv); - return cfw_send_message(msg); -} - -int ble_gap_start_advertise(svc_client_handle_t * p_svc_handle, - const ble_gap_adv_param_t * p_adv_param, - void *p_priv) -{ - struct ble_gap_start_advertise_req_msg *msg; - int data_len = sizeof(struct ble_gap_start_advertise_req_msg); - - if ((NULL != p_adv_param) && (NULL != p_adv_param->p_peer_bda)) { - data_len += sizeof(ble_addr_t); - } - msg = (struct ble_gap_start_advertise_req_msg *) - cfw_alloc_message_for_service(p_svc_handle, - MSG_ID_BLE_GAP_ENABLE_ADV_REQ, - data_len, - p_priv); - if (NULL != p_adv_param) { - uint8_t i; - msg->filter_policy = p_adv_param->filter_policy; - msg->interval_max = p_adv_param->interval_max; - msg->interval_min = p_adv_param->interval_min; - msg->options = p_adv_param->options; - msg->timeout = p_adv_param->timeout; - msg->type = p_adv_param->type; - if (NULL != (p_adv_param->p_peer_bda)) - { - msg->peer_bda[0] = p_adv_param->p_peer_bda->type; - for (i = 1; i <= BLE_ADDR_LEN; i++) - { - msg->peer_bda[i] = p_adv_param->p_peer_bda->addr[i-1]; - } - msg->bda_len = i; - } else - msg->bda_len = 0; - } - return cfw_send_message(msg); -} - -int ble_gap_stop_advertise(svc_client_handle_t * p_svc_handle, void *p_priv) -{ - struct cfw_message *msg = cfw_alloc_message_for_service(p_svc_handle, - MSG_ID_BLE_GAP_DISABLE_ADV_REQ, - sizeof(*msg), - p_priv); - return cfw_send_message(msg); -} - -int ble_gap_conn_update(svc_client_handle_t * p_svc_handle, - uint16_t conn_handle, - const struct ble_gap_connection_params * p_conn_param, - void *p_priv) -{ - CFW_ALLOC_FOR_SVC(struct ble_gap_conn_update_req_msg, msg, p_svc_handle, - MSG_ID_BLE_GAP_CONN_UPDATE_REQ, 0, p_priv); - - msg->conn_handle = conn_handle; - msg->conn_params = *p_conn_param; - - return cfw_send_message(msg); -} - -int ble_gap_disconnect(svc_client_handle_t * p_svc_handle, - uint16_t conn_hdl, uint8_t reason, - void *p_priv) -{ - struct ble_gap_disconnect_req_msg *msg; - - msg = (struct ble_gap_disconnect_req_msg*)cfw_alloc_message_for_service(p_svc_handle, - MSG_ID_BLE_GAP_DISCONNECT_REQ, - sizeof(*msg), - p_priv); - - msg->reason = reason; - msg->conn_handle = conn_hdl; - - return cfw_send_message(msg); -} - -int ble_gap_service_write(svc_client_handle_t * p_svc_handle, - const struct ble_gap_service_write_params * p_params, - void *p_priv) -{ - struct ble_gap_service_write_req_msg *msg; - int total_len = sizeof(*msg); - if ((p_params->attr_type == GAP_SVC_ATTR_NAME) && (p_params->name.len)) { - total_len += p_params->name.len; - } - msg = (struct ble_gap_service_write_req_msg *) - cfw_alloc_message_for_service(p_svc_handle, - MSG_ID_BLE_GAP_SERVICE_WRITE_REQ, - total_len, - p_priv); - msg->attr_type = p_params->attr_type; - - switch (p_params->attr_type) { - case GAP_SVC_ATTR_NAME: - msg->name.authorization = p_params->name.authorization; - msg->name.len = p_params->name.len; - msg->name.sec_mode = p_params->name.sec_mode; - if (msg->name.len) - strcpy((char *)&msg->name.name_array[0], (char *)p_params->name.p_name); - break; - case GAP_SVC_ATTR_APPEARANCE: - msg->appearance = p_params->appearance; - break; - case GAP_SVC_ATTR_PPCP: - msg->conn_params = p_params->conn_params; - break; - case GAP_SVC_ATTR_CAR: - msg->car = p_params->car; - break; - default: - pr_warning(LOG_MODULE_BLE, "ble_gap_srv_wr: Attr " - "not supported : 0x%x", p_params->attr_type); - } - - return cfw_send_message(msg); -} - -int ble_gap_service_read(svc_client_handle_t * p_svc_handle, - uint16_t type, void *p_priv) -{ - struct cfw_message *msg = cfw_alloc_message_for_service(p_svc_handle, - MSG_ID_BLE_GAP_SERVICE_READ_REQ, - sizeof(*msg), - p_priv); - return cfw_send_message(msg); -} - -int ble_gap_sm_config(const svc_client_handle_t * h, - const struct ble_gap_sm_config_params * p_params, - void *p_priv) -{ - CFW_ALLOC_FOR_SVC(struct ble_gap_sm_config_req_msg, msg, h, MSG_ID_BLE_GAP_SM_CONFIG_REQ, 0, p_priv); - - msg->params = *p_params; - - return cfw_send_message(msg); -} - -int ble_gap_sm_pairing_req(const svc_client_handle_t * h, - uint16_t conn_handle, - const struct ble_gap_sm_pairing_params * p_params, - void *p_priv) -{ - CFW_ALLOC_FOR_SVC(struct ble_gap_sm_pairing_req_msg, msg, h, MSG_ID_BLE_GAP_SM_PAIRING_REQ, 0, p_priv); - - msg->params = *p_params; - msg->conn_handle = conn_handle; - - return cfw_send_message(msg); -} - -int ble_gap_set_rssi_report(svc_client_handle_t * p_svc_handle, - const struct rssi_report_params *params, - void *p_priv) -{ - CFW_ALLOC_FOR_SVC(struct ble_gap_set_rssi_report_req_msg, msg, p_svc_handle, MSG_ID_BLE_GAP_SET_RSSI_REPORT_REQ, 0, p_priv); - msg->params = *params; - return cfw_send_message(msg); -} - -int ble_gap_start_scan(svc_client_handle_t * p_svc_handle, - const ble_gap_scan_param_t * p_scan_params, void * p_priv) -{ - struct cfw_message *msg = cfw_alloc_message_for_service(p_svc_handle, - MSG_ID_BLE_GAP_SCAN_START_REQ, - sizeof(*msg), - p_priv); - return cfw_send_message(msg); -} - -int ble_gap_stop_scan(svc_client_handle_t * p_svc_handle, void *p_priv) -{ - struct cfw_message *msg = cfw_alloc_message_for_service(p_svc_handle, - MSG_ID_BLE_GAP_SCAN_STOP_REQ, - sizeof(*msg), - p_priv); - return cfw_send_message(msg); -} - -int ble_gap_connect(svc_client_handle_t * p_svc_handle, const ble_addr_t * p_bd, - const ble_gap_scan_param_t * p_scan_params, - const struct ble_gap_connection_params * p_conn_params, - void *p_priv) -{ - struct cfw_message *msg = cfw_alloc_message_for_service(p_svc_handle, - MSG_ID_BLE_GAP_CONNECT_REQ, - sizeof(*msg), - p_priv); - return cfw_send_message(msg); -} - -int ble_gap_cancel_connect(svc_client_handle_t * p_svc_handle, - const ble_addr_t * p_bd, void *p_priv) -{ - struct cfw_message *msg = cfw_alloc_message_for_service(p_svc_handle, - MSG_ID_BLE_GAP_CONNECT_CANCEL_REQ, - sizeof(*msg), - p_priv); - return cfw_send_message(msg); -} - -int ble_gap_set_option(svc_client_handle_t * p_svc_handle, uint8_t op, - const ble_gap_option_t * p_opt, void *p_priv) -{ - struct cfw_message *msg = cfw_alloc_message_for_service(p_svc_handle, - MSG_ID_BLE_GAP_SET_OPTIONS_REQ, - sizeof(*msg), - p_priv); - return cfw_send_message(msg); -} - -int ble_gap_generic_cmd_req(svc_client_handle_t * p_svc_handle, - const struct ble_gap_gen_cmd_params *p_params, - void *p_priv) -{ - struct ble_generic_cmd_req_msg *msg = (struct ble_generic_cmd_req_msg *) - cfw_alloc_message_for_service(p_svc_handle, - MSG_ID_BLE_GAP_GENERIC_CMD_REQ, - sizeof(*msg), - p_priv); - - return cfw_send_message(msg); -} - -int ble_gap_get_version_req(svc_client_handle_t * p_svc_handle, void * p_priv) -{ - struct cfw_message *msg = (struct cfw_message *) - cfw_alloc_message_for_service(p_svc_handle, - MSG_ID_BLE_GAP_GET_VERSION_REQ, - sizeof(*msg), - p_priv); - return cfw_send_message(msg); -} - -int ble_gap_dtm_init_req(svc_client_handle_t * p_svc_handle, void * p_priv) -{ - struct cfw_message *msg = (struct cfw_message *) - cfw_alloc_message_for_service(p_svc_handle, - MSG_ID_BLE_GAP_DTM_INIT_REQ, - sizeof(*msg), - p_priv); - return cfw_send_message(msg); -} diff --git a/system/libarc32_arduino101/framework/src/services/ble/ble_service_gatt_int.h b/system/libarc32_arduino101/framework/src/services/ble/ble_service_gatt_int.h deleted file mode 100644 index 3f42b09f..00000000 --- a/system/libarc32_arduino101/framework/src/services/ble/ble_service_gatt_int.h +++ /dev/null @@ -1,102 +0,0 @@ -/* - * 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_SERVICE_GATT_INT_H__ -#define __BLE_SERVICE_GATT_INT_H__ - -#include "services/ble/ble_service_gatt.h" -#include "services/ble/ble_service_gatts_api.h" - -/** - * BLE GATT related shared internal structures between master and BLE controller. - */ -struct ble_gatts_add_service_req_msg { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - ble_gatts_add_svc_cback_t cback; /**< Callback function to be returnedn in response message */ - uint8_t type; /**< Primary or secondary \ref BLE_GATT_SVC_TYPES. */ - uint8_t data[]; /**< Variable length data of the request (UUID) */ -}; - -/** - * Component framework message to add a characteristic to a service - */ -struct ble_gatts_add_char_req_msg { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - ble_gatts_add_char_cback_t cback; /**< Callback function, to be returned to sender! */ - uint16_t svc_handle; /**< Handle of the service to which the characteristic should be added */ - struct ble_gatts_permissions perms; - struct ble_gatt_char_properties props; - uint16_t max_len; /**< Maximum length of the characteristic value */ - uint16_t init_len; /**< Length of the initialization value */ - uint8_t ud_len; /**< Length of the characteristic User Description */ - uint8_t pf_len; - - uint8_t data[]; /**< Variable length data of the request (UUID, init value, user descriptor) */ -}; - -/** - * Component framework message to add a descriptor to a characteristic - */ -struct ble_gatts_add_desc_req_msg { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - ble_gatts_add_desc_cback_t cback; /**< Callback function, to be returned to sender! */ - uint16_t length; - struct ble_gatts_permissions perms; /**< Read/Write permissions for descriptor */ - uint8_t data[]; /**< Variable length data of the request (descriptor value) */ -}; - -struct ble_gatts_start_service_msg { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - uint16_t svc_handle; -}; - -struct ble_gatts_set_attribute_value_msg { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - uint16_t value_handle; /* mandatory */ - uint16_t len; - uint16_t offset; /* by default 0 */ - uint8_t data[]; /* value to update */ -}; - -struct ble_gatts_notif_ind_params { - uint16_t conn_handle; - uint16_t val_handle; - uint16_t len; /* may be 0, in this case already stored data is sent */ - uint16_t offset; - uint8_t data[]; -}; - -struct ble_gatts_send_notif_ind_msg { - struct cfw_message header; /**< Component framework message header (@ref cfw), MUST be first element of structure */ - ble_gatts_notif_ind_cback_t cback; /**< Callback function to be returned in response message */ - struct ble_gatts_notif_ind_params params; /* parameters for notification/indication */ -}; - -#endif diff --git a/system/libarc32_arduino101/framework/src/services/ble/ble_service_gatts_api.c b/system/libarc32_arduino101/framework/src/services/ble/ble_service_gatts_api.c deleted file mode 100644 index 17345bde..00000000 --- a/system/libarc32_arduino101/framework/src/services/ble/ble_service_gatts_api.c +++ /dev/null @@ -1,287 +0,0 @@ -/* - * 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 "services/ble/ble_service_gatt.h" -#include "services/ble/ble_service_gatts_api.h" -#include "ble_service_core_int.h" -#include "ble_service_gatt_int.h" -#include "ble_service_utils.h" -#include - -int ble_gatts_add_service(svc_client_handle_t * p_svc_handle, - const struct bt_uuid * p_uuid, - uint8_t type, - ble_gatts_add_svc_cback_t cback, - void *p_priv) -{ - struct ble_gatts_add_service_req_msg *msg = - (struct ble_gatts_add_service_req_msg *) - cfw_alloc_message_for_service(p_svc_handle, - MSG_ID_BLE_GATTS_ADD_SERVICE_REQ, - sizeof(*msg) + ble_sizeof_bt_uuid(p_uuid), - p_priv); - msg->cback = cback; - msg->type = type; - uint8_t *p = (uint8_t *)&msg->data; - ble_encode_bt_uuid(p_uuid, p); - return cfw_send_message(msg); -} - -int ble_gatts_add_included_svc(svc_client_handle_t * p_svc_handle, - uint16_t svc_handle, - uint16_t svc_handle_to_include, - void *p_priv) -{ - struct cfw_message *msg = cfw_alloc_message_for_service(p_svc_handle, - MSG_ID_BLE_GATTS_ADD_INCL_SVC_REQ, - sizeof(*msg), - p_priv); - return cfw_send_message(msg); -} - -int ble_gatts_add_characteristic(svc_client_handle_t * p_svc_handle, - uint16_t svc_handle, - const struct ble_gatts_characteristic * p_char, - ble_gatts_add_char_cback_t cback, - void *p_priv) -{ - struct ble_gatts_add_char_req_msg *msg; - int data_len = sizeof(struct ble_gatts_add_char_req_msg); - uint8_t * p; - - // Sanity check - if (!p_char || !p_char->p_uuid) - return E_OS_ERR; - - // compute the variable length part of the message - data_len += ble_sizeof_bt_uuid(p_char->p_uuid); - data_len += p_char->init_len; - if (p_char->p_user_desc) - data_len += p_char->p_user_desc->len; - if (p_char->p_char_pf_desc) - data_len += sizeof(struct ble_gatt_pf_desc); - - msg = (struct ble_gatts_add_char_req_msg *) - cfw_alloc_message_for_service(p_svc_handle, - MSG_ID_BLE_GATTS_ADD_CHARACTERISTIC_REQ, - data_len, p_priv); - msg->cback = cback; - p = msg->data; - p = ble_encode_bt_uuid(p_char->p_uuid, p); - msg->svc_handle = svc_handle; - msg->perms = p_char->perms; - msg->props = p_char->props; - msg->max_len = p_char->max_len; - msg->init_len = p_char->init_len; - memcpy(p, p_char->p_value, p_char->init_len); - p += p_char->init_len; - if (p_char->p_user_desc) { - msg->ud_len = p_char->p_user_desc->len; - memcpy(p, p_char->p_user_desc->buffer, p_char->p_user_desc->len); - p += p_char->p_user_desc->len; - } - if (p_char->p_char_pf_desc) { - msg->pf_len = sizeof(struct ble_gatt_pf_desc); - memcpy(p, p_char->p_char_pf_desc, msg->pf_len); - p += msg->pf_len; - } - return cfw_send_message(msg); -} - -int ble_gatts_add_descriptor(svc_client_handle_t * p_svc_handle, - const struct ble_gatts_descriptor * p_desc, - ble_gatts_add_desc_cback_t cback, - void * p_priv) -{ - struct ble_gatts_add_desc_req_msg *req; - uint8_t * p; - int data_len = sizeof(struct ble_gatts_add_desc_req_msg); - - /* Sanity check */ - if (!p_desc || !p_desc->length || !p_desc->p_value || !p_desc->p_uuid) - return E_OS_ERR; - - // compute the variable length part of the message - data_len += ble_sizeof_bt_uuid(p_desc->p_uuid); - data_len += p_desc->length; - - req = (struct ble_gatts_add_desc_req_msg *) - cfw_alloc_message_for_service(p_svc_handle, - MSG_ID_BLE_GATTS_ADD_DESCRIPTOR_REQ, - data_len, p_priv); - req->cback = cback; - p = req->data; - p = ble_encode_bt_uuid(p_desc->p_uuid, p); - req->length = p_desc->length; - req->perms = p_desc->perms; - memcpy(p, p_desc->p_value, p_desc->length); - - return cfw_send_message(req); -} - - -int ble_gatts_start_service(svc_client_handle_t * p_svc_handle, - uint16_t svc_handle, - void *p_priv) -{ - struct cfw_message *msg = cfw_alloc_message_for_service(p_svc_handle, - MSG_ID_BLE_GATTS_START_SERVICE_REQ, - sizeof(*msg), - p_priv); - return cfw_send_message(msg); -} - -int ble_gatts_remove_service(svc_client_handle_t * p_svc_handle, - uint16_t svc_handle, - void *p_priv) -{ - struct cfw_message *msg = cfw_alloc_message_for_service(p_svc_handle, - MSG_ID_BLE_GATTS_REMOVE_SERVICE_REQ, - sizeof(*msg), - p_priv); - return cfw_send_message(msg); -} - -int ble_gatts_send_svc_changed(svc_client_handle_t * p_svc_handle, - uint16_t conn_handle, - uint16_t start_handle, - uint16_t end_handle, - void *p_priv) -{ - struct cfw_message *msg = cfw_alloc_message_for_service(p_svc_handle, - MSG_ID_BLE_GATTS_INDICATE_SERVICE_CHANGE_REQ, - sizeof(*msg), - p_priv); - return cfw_send_message(msg); -} - -int ble_gatts_set_attribute_value(svc_client_handle_t * p_svc_handle, - uint16_t value_handle, - uint16_t len, - const uint8_t * p_value, - uint16_t offset, - void *p_priv) -{ - int i; - struct ble_gatts_set_attribute_value_msg *msg = - (struct ble_gatts_set_attribute_value_msg *) - cfw_alloc_message_for_service(p_svc_handle, - MSG_ID_BLE_GATTS_SET_ATTRIBUTE_VALUE_REQ, - sizeof(*msg)+len, - p_priv); - msg->value_handle = value_handle; - msg->len = len; - msg->offset = offset; - - for (i = 0; i < len; i++) - msg->data[i] = p_value[i]; - return cfw_send_message(msg); -} - -int ble_gatts_get_attribute_value(svc_client_handle_t * p_svc_handle, - uint16_t value_handle, - uint16_t len, - uint16_t offset, - void *p_priv) -{ - struct cfw_message *msg = cfw_alloc_message_for_service(p_svc_handle, - MSG_ID_BLE_GATTS_GET_ATTRIBUTE_VALUE_REQ, sizeof(*msg), - p_priv); - - return cfw_send_message(msg); -} - -int ble_gatts_send_notif(svc_client_handle_t * p_svc_handle, - uint16_t conn_handle, - const ble_gatts_ind_params_t * p_params, - ble_gatts_notif_ind_cback_t cback, - void *p_priv) -{ - struct ble_gatts_send_notif_ind_msg *msg = - (struct ble_gatts_send_notif_ind_msg *) - cfw_alloc_message_for_service(p_svc_handle, - MSG_ID_BLE_GATTS_SEND_NOTIF_REQ, - sizeof(*msg) + p_params->len, - p_priv); - - msg->cback = cback; - msg->params.conn_handle = conn_handle; - msg->params.val_handle = p_params->val_handle; - msg->params.len = p_params->len; - msg->params.offset = p_params->offset; - memcpy(msg->params.data, p_params->p_data, msg->params.len); - return cfw_send_message(msg); -} - -int ble_gatts_send_ind(svc_client_handle_t * p_svc_handle, - uint16_t conn_handle, - const ble_gatts_ind_params_t * p_params, - ble_gatts_notif_ind_cback_t cback, - void *p_priv) -{ - struct ble_gatts_send_notif_ind_msg *msg = - (struct ble_gatts_send_notif_ind_msg *) - cfw_alloc_message_for_service(p_svc_handle, - MSG_ID_BLE_GATTS_SEND_IND_REQ, - sizeof(*msg) + p_params->len, - p_priv); - - msg->cback = cback; - msg->params.conn_handle = conn_handle; - msg->params.val_handle = p_params->val_handle; - msg->params.len = p_params->len; - msg->params.offset = p_params->offset; - memcpy(msg->params.data, p_params->p_data, msg->params.len); - return cfw_send_message(msg); -} - -int ble_gatts_write_conn_attributes(svc_client_handle_t * p_svc_handle, - uint16_t conn_handle, - const uint8_t * p_data, - uint16_t len, - void *p_priv) -{ - struct cfw_message *msg = cfw_alloc_message_for_service(p_svc_handle, - MSG_ID_BLE_GATTS_WR_CONN_ATTRIBUTES_REQ, - sizeof(*msg), - p_priv); - return cfw_send_message(msg); -} - -int ble_gatts_read_conn_attributes(svc_client_handle_t * p_svc_handle, - uint16_t conn_handle, - void *p_priv) -{ - struct cfw_message *msg = cfw_alloc_message_for_service(p_svc_handle, - MSG_ID_BLE_GATTS_RD_CONN_ATTRIBUTES_REQ, - sizeof(*msg), - p_priv); - return cfw_send_message(msg); -} diff --git a/system/libarc32_arduino101/framework/src/services/ble/conn.c b/system/libarc32_arduino101/framework/src/services/ble/conn.c new file mode 100644 index 00000000..c0aa9014 --- /dev/null +++ b/system/libarc32_arduino101/framework/src/services/ble/conn.c @@ -0,0 +1,927 @@ +/* + * Copyright (c) 2016 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include +#include +#include +#include + +#include "hci_core.h" +#include "conn_internal.h" +#include "gap_internal.h" +#include "l2cap_internal.h" +#include "smp.h" + +/* #define BT_GATT_DEBUG 1 */ + +extern void on_nble_curie_log(char *fmt, ...); +extern void __assert_fail(void); +#ifdef BT_GATT_DEBUG +#define BT_DBG(fmt, ...) on_nble_curie_log(fmt, ##__VA_ARGS__) +#define BT_ERR(fmt, ...) on_nble_curie_log(fmt, ##__VA_ARGS__) +#define BT_WARN(fmt, ...) on_nble_curie_log(fmt, ##__VA_ARGS__) +#define BT_INFO(fmt, ...) on_nble_curie_log(fmt, ##__VA_ARGS__) +#define BT_ASSERT(cond) ((cond) ? (void)0 : __assert_fail()) +#else +#define BT_DBG(fmt, ...) do {} while (0) +#define BT_ERR(fmt, ...) on_nble_curie_log(fmt, ##__VA_ARGS__) +#define BT_WARN(fmt, ...) on_nble_curie_log(fmt, ##__VA_ARGS__) +#define BT_INFO(fmt, ...) on_nble_curie_log(fmt, ##__VA_ARGS__) +#define BT_ASSERT(cond) ((cond) ? (void)0 : __assert_fail()) +#endif + +#if defined(CONFIG_BLUETOOTH_SMP) || defined(CONFIG_BLUETOOTH_BREDR) +const struct bt_conn_auth_cb *bt_auth; +#endif /* CONFIG_BLUETOOTH_SMP || CONFIG_BLUETOOTH_BREDR */ + +static struct bt_conn conns[CONFIG_BLUETOOTH_MAX_CONN]; +static struct bt_conn_cb *callback_list; + +static void notify_connected(struct bt_conn *conn) +{ + struct bt_conn_cb *cb; + + for (cb = callback_list; cb; cb = cb->_next) { + if (cb->connected) { + cb->connected(conn, conn->err); + } + } +} + +static void notify_disconnected(struct bt_conn *conn) +{ + struct bt_conn_cb *cb; + + for (cb = callback_list; cb; cb = cb->_next) { + if (cb->disconnected) { + cb->disconnected(conn, conn->err); + } + } +} + +void notify_le_param_updated(struct bt_conn *conn) +{ + struct bt_conn_cb *cb; + + for (cb = callback_list; cb; cb = cb->_next) { + if (cb->le_param_updated) { + cb->le_param_updated(conn, conn->le.interval, + conn->le.latency, + conn->le.timeout); + } + } +} + +#if defined(CONFIG_BLUETOOTH_SMP) + +void bt_conn_security_changed(struct bt_conn *conn) +{ + struct bt_conn_cb *cb; + + for (cb = callback_list; cb; cb = cb->_next) { + if (cb->security_changed) { + cb->security_changed(conn, conn->sec_level); + } + } +} + +static int start_security(struct bt_conn *conn) +{ + switch (conn->role) { +#if defined(CONFIG_BLUETOOTH_CENTRAL) + case BT_HCI_ROLE_MASTER: + { +#ifdef NOT_APPLICABLE_NBLE + if (!conn->keys) { + conn->keys = bt_keys_find(BT_KEYS_LTK_P256, + &conn->le.dst); + if (!conn->keys) { + conn->keys = bt_keys_find(BT_KEYS_LTK, + &conn->le.dst); + } + } + + if (!conn->keys || + !(conn->keys->keys & (BT_KEYS_LTK | BT_KEYS_LTK_P256))) { + return bt_smp_send_pairing_req(conn); + } + + if (conn->required_sec_level > BT_SECURITY_MEDIUM && + !atomic_test_bit(&conn->keys->flags, + BT_KEYS_AUTHENTICATED)) { + return bt_smp_send_pairing_req(conn); + } + + if (conn->required_sec_level > BT_SECURITY_HIGH && + !atomic_test_bit(&conn->keys->flags, + BT_KEYS_AUTHENTICATED) && + !(conn->keys->keys & BT_KEYS_LTK_P256)) { + return bt_smp_send_pairing_req(conn); + } + + /* LE SC LTK and legacy master LTK are stored in same place */ + return bt_conn_le_start_encryption(conn, conn->keys->ltk.rand, + conn->keys->ltk.ediv, + conn->keys->ltk.val, + conn->keys->enc_size); +#endif + return bt_smp_send_pairing_req(conn); + } +#endif /* CONFIG_BLUETOOTH_CENTRAL */ +#if defined(CONFIG_BLUETOOTH_PERIPHERAL) + case BT_HCI_ROLE_SLAVE: + return bt_smp_send_security_req(conn); +#endif /* CONFIG_BLUETOOTH_PERIPHERAL */ + default: + return -EINVAL; + } +} + +int bt_conn_security(struct bt_conn *conn, bt_security_t sec) +{ + int err; + + if (conn->state != BT_CONN_CONNECTED) { + return -ENOTCONN; + } + +#if defined(CONFIG_BLUETOOTH_SMP_SC_ONLY) + if (sec < BT_SECURITY_FIPS) { + return -EOPNOTSUPP; + } +#endif/* CONFIG_BLUETOOTH_SMP_SC_ONLY */ + + /* nothing to do */ + if (conn->sec_level >= sec || conn->required_sec_level >= sec) { + return 0; + } + + conn->required_sec_level = sec; + + err = start_security(conn); + + /* reset required security level in case of error */ + if (err) { + conn->required_sec_level = conn->sec_level; + } + return err; +} +#endif /* CONFIG_BLUETOOTH_SMP */ + +void bt_conn_cb_register(struct bt_conn_cb *cb) +{ + cb->_next = callback_list; + callback_list = cb; +} + +static struct bt_conn *conn_new(void) +{ + struct bt_conn *conn = NULL; + int i; + + for (i = 0; i < ARRAY_SIZE(conns); i++) { + if (!atomic_get(&conns[i].ref)) { + conn = &conns[i]; + break; + } + } + + if (!conn) { + return NULL; + } + + memset(conn, 0, sizeof(*conn)); + + atomic_set(&conn->ref, 1); + + return conn; +} + +struct bt_conn *bt_conn_add_le(const bt_addr_le_t *peer) +{ + struct bt_conn *conn = conn_new(); + + if (!conn) { + return NULL; + } + + bt_addr_le_copy(&conn->le.dst, peer); +#if defined(CONFIG_BLUETOOTH_SMP) + conn->sec_level = BT_SECURITY_LOW; + conn->required_sec_level = BT_SECURITY_LOW; +#endif /* CONFIG_BLUETOOTH_SMP */ + conn->type = BT_CONN_TYPE_LE; + conn->le.interval_min = BT_GAP_INIT_CONN_INT_MIN; + conn->le.interval_max = BT_GAP_INIT_CONN_INT_MAX; + + return conn; +} + +#if defined(CONFIG_BLUETOOTH_BREDR) +struct bt_conn *bt_conn_lookup_addr_br(const bt_addr_t *peer) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(conns); i++) { + if (!atomic_get(&conns[i].ref)) { + continue; + } + + if (conns[i].type != BT_CONN_TYPE_BR) { + continue; + } + + if (!bt_addr_cmp(peer, &conns[i].br.dst)) { + return bt_conn_ref(&conns[i]); + } + } + + return NULL; +} + +struct bt_conn *bt_conn_add_br(const bt_addr_t *peer) +{ + struct bt_conn *conn = conn_new(); + + if (!conn) { + return NULL; + } + + bt_addr_copy(&conn->br.dst, peer); + conn->type = BT_CONN_TYPE_BR; + + return conn; +} +#endif + +void bt_conn_set_state(struct bt_conn *conn, bt_conn_state_t state) +{ + bt_conn_state_t old_state; + + BT_DBG("conn state %d -> %d, err: %d", conn->state, state, conn->err); + + if (conn->state == state) { + BT_DBG("no transition"); + return; + } + + old_state = conn->state; + conn->state = state; + + /* Actions needed for exiting the old state */ + switch (old_state) { + case BT_CONN_DISCONNECTED: + /* Take a reference for the first state transition after + * bt_conn_add_le() and keep it until reaching DISCONNECTED + * again. + */ + bt_conn_ref(conn); + break; + case BT_CONN_CONNECT: +#if 0 + if (conn->timeout) { + fiber_delayed_start_cancel(conn->timeout); + conn->timeout = NULL; + + /* Drop the reference taken by timeout fiber */ + bt_conn_unref(conn); + } +#endif + break; + default: + break; + } + + /* Actions needed for entering the new state */ + switch (conn->state) { + case BT_CONN_CONNECTED: + bt_l2cap_connected(conn); + notify_connected(conn); + break; + case BT_CONN_DISCONNECTED: + /* Notify disconnection and queue a dummy buffer to wake + * up and stop the tx fiber for states where it was + * running. + */ + if (old_state == BT_CONN_CONNECTED || + old_state == BT_CONN_DISCONNECT) { + bt_l2cap_disconnected(conn); + notify_disconnected(conn); + } else if (old_state == BT_CONN_CONNECT) { + /* conn->err will be set in this case */ + notify_connected(conn); + } + + /* Release the reference we took for the very first + * state transition. + */ + bt_conn_unref(conn); + + break; + case BT_CONN_CONNECT_SCAN: + break; + case BT_CONN_CONNECT: + break; + case BT_CONN_DISCONNECT: + break; + default: + + break; + } +} + +struct bt_conn *bt_conn_lookup_handle(uint16_t handle) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(conns); i++) { + if (!atomic_get(&conns[i].ref)) { + continue; + } + /* We only care about connections with a valid handle */ + if (conns[i].state != BT_CONN_CONNECTED && + conns[i].state != BT_CONN_DISCONNECT) { + continue; + } + + if (conns[i].handle == handle) { + return bt_conn_ref(&conns[i]); + } + } + + return NULL; +} + +struct bt_conn *bt_conn_lookup_addr_le(const bt_addr_le_t *peer) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(conns); i++) { + if (!atomic_get(&conns[i].ref)) { + continue; + } + + if (conns[i].type != BT_CONN_TYPE_LE) { + continue; + } + + if (!bt_addr_le_cmp(peer, &conns[i].le.dst)) { + return bt_conn_ref(&conns[i]); + } + } + + return NULL; +} + +struct bt_conn *bt_conn_lookup_state_le(const bt_addr_le_t *peer, + const bt_conn_state_t state) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(conns); i++) { + if (!atomic_get(&conns[i].ref)) { + continue; + } + + if (conns[i].type != BT_CONN_TYPE_LE) { + continue; + } + + if (bt_addr_le_cmp(peer, BT_ADDR_LE_ANY) && + bt_addr_le_cmp(peer, &conns[i].le.dst)) { + continue; + } + + if (conns[i].state == state) { + return bt_conn_ref(&conns[i]); + } + } + + return NULL; +} + +struct bt_conn *bt_conn_ref(struct bt_conn *conn) +{ + atomic_inc(&conn->ref); + + return conn; +} + +void bt_conn_unref(struct bt_conn *conn) +{ + atomic_dec(&conn->ref); +} + +const bt_addr_le_t *bt_conn_get_dst(const struct bt_conn *conn) +{ + return &conn->le.dst; +} + +int bt_conn_get_info(const struct bt_conn *conn, struct bt_conn_info *info) +{ + info->type = conn->type; + info->role = conn->role; + + switch (conn->type) { + case BT_CONN_TYPE_LE: + if (conn->role == BT_HCI_ROLE_MASTER) { +#if 0 + info->le.src = &conn->le.init_addr; + info->le.dst = &conn->le.resp_addr; +#else + info->le.dst = &conn->le.dst; +#endif + } else { +#if 0 + info->le.src = &conn->le.resp_addr; + info->le.dst = &conn->le.init_addr; +#else + info->le.src = &conn->le.dst; +#endif + } + info->le.interval = conn->le.interval; + info->le.latency = conn->le.latency; + info->le.timeout = conn->le.timeout; + + return 0; +#if defined(CONFIG_BLUETOOTH_BREDR) + case BT_CONN_TYPE_BR: + info->br.dst = &conn->br.dst; + return 0; +#endif + } + + return -EINVAL; +} + +static int bt_hci_disconnect(struct bt_conn *conn, uint8_t reason) +{ + struct nble_gap_disconnect_req_params ble_gap_disconnect; + + ble_gap_disconnect.conn_handle = conn->handle; + ble_gap_disconnect.reason = reason; + nble_gap_disconnect_req(&ble_gap_disconnect); + + bt_conn_set_state(conn, BT_CONN_DISCONNECT); + return 0; +} + +static int bt_hci_connect_le_cancel(struct bt_conn *conn) +{ + nble_gap_cancel_connect_req(conn); + return 0; +} + +void on_nble_gap_cancel_connect_rsp(const struct nble_response *params) +{ + struct bt_conn *conn = params->user_data; + + conn->err = BT_HCI_ERR_INSUFFICIENT_RESOURCES; + bt_conn_set_state(conn, BT_CONN_DISCONNECTED); +} + +int bt_conn_disconnect(struct bt_conn *conn, uint8_t reason) +{ +#if defined(CONFIG_BLUETOOTH_CENTRAL) + /* Disconnection is initiated by us, so auto connection shall + * be disabled. Otherwise the passive scan would be enabled + * and we could send LE Create Connection as soon as the remote + * starts advertising. + */ + if (conn->type == BT_CONN_TYPE_LE) { + bt_le_set_auto_conn(&conn->le.dst, NULL); + } +#endif + + switch (conn->state) { + case BT_CONN_CONNECT_SCAN: + conn->err = BT_HCI_ERR_INSUFFICIENT_RESOURCES; + bt_conn_set_state(conn, BT_CONN_DISCONNECTED); + /* scan update not yet implemented */ + return 0; + case BT_CONN_CONNECT: + return bt_hci_connect_le_cancel(conn); + case BT_CONN_CONNECTED: + return bt_hci_disconnect(conn, reason); + case BT_CONN_DISCONNECT: + return 0; + case BT_CONN_DISCONNECTED: + default: + return -ENOTCONN; + } +} + +static bool valid_adv_params(const struct nble_gap_adv_params *params) +{ + if (params->type == BT_LE_ADV_DIRECT_IND) { + /* If high duty, ensure interval is 0 */ + if (params->interval_max != 0) + return false; + + if (params->timeout != 0) + return false; + } else if (params->type == BT_LE_ADV_DIRECT_IND_LOW_DUTY) { + if (params->interval_min < 0x20) + return false; + } else { + return false; + } + + if (params->interval_min > params->interval_max) + return false; + + if (params->interval_max > 0x4000) + return false; + + return true; +} + +struct bt_conn *bt_conn_create_slave_le(const bt_addr_le_t *peer, + const struct bt_le_adv_param *param) +{ + struct bt_conn *conn; + /* Timeout is handled by application timer */ + /* forced to none currently (no whitelist support) */ + struct nble_gap_adv_params params = { + .interval_max = param->interval_max, + .interval_min = param->interval_min, + .type = param->type, + .timeout = 0, + .filter_policy = 0 + }; + + bt_addr_le_copy(¶ms.peer_bda, peer); + + if (!valid_adv_params(¶ms)) { + return NULL; + } + + if (param->type == BT_LE_ADV_DIRECT_IND_LOW_DUTY) { + params.type = BT_LE_ADV_DIRECT_IND; + } + + if (atomic_test_bit(bt_dev.flags, BT_DEV_ADVERTISING)) { + return NULL; + } + + conn = bt_conn_add_le(¶ms.peer_bda); + + if (!conn) { + return NULL; + } + + bt_conn_set_state(conn, BT_CONN_CONNECT); + + nble_gap_set_adv_params_req(¶ms); + nble_gap_start_adv_req(); + + return conn; +} + +#if defined(CONFIG_BLUETOOTH_CENTRAL) +static int hci_le_create_conn(struct bt_conn *conn) +{ + struct nble_gap_connect_req_params conn_params; + + conn_params.bda = conn->le.dst; + conn_params.conn_params.interval_min = conn->le.interval_min; + conn_params.conn_params.interval_max = conn->le.interval_max; + conn_params.conn_params.slave_latency = conn->le.latency; + conn_params.conn_params.link_sup_to = conn->le.timeout; + + conn_params.scan_params.interval = sys_cpu_to_le16(BT_GAP_SCAN_FAST_INTERVAL); + conn_params.scan_params.window = conn_params.scan_params.interval; + conn_params.scan_params.selective = 0; + conn_params.scan_params.active = 1; + conn_params.scan_params.timeout = 0; + + nble_gap_connect_req(&conn_params, conn); + + return 0; +} + +void on_nble_gap_connect_rsp(const struct nble_response *params) +{ + struct bt_conn *conn = params->user_data; + + /* If the connection request was not issued successfully */ + if (params->status) { + conn->err = BT_HCI_ERR_UNACCEPT_CONN_PARAMS; + bt_conn_set_state(conn, BT_CONN_DISCONNECTED); + } +} + +struct bt_conn *bt_conn_create_le(const bt_addr_le_t *peer, + const struct bt_le_conn_param *param) +{ + struct bt_conn *conn; + + if (!bt_le_conn_params_valid(param->interval_min, param->interval_max, + param->latency, param->timeout)) { + return NULL; + } + + /* if (atomic_test_bit(bt_dev.flags, BT_DEV_EXPLICIT_SCAN)) */ + /* return NULL; */ + + conn = bt_conn_lookup_addr_le(peer); + if (conn) { + switch (conn->state) { + case BT_CONN_CONNECT_SCAN: + bt_conn_set_param_le(conn, param); + return conn; + case BT_CONN_CONNECT: + case BT_CONN_CONNECTED: + return conn; + default: + bt_conn_unref(conn); + return NULL; + } + } + + conn = bt_conn_add_le(peer); + if (!conn) { + return NULL; + } +#if 0 + bt_conn_set_state(conn, BT_CONN_CONNECT_SCAN); + + bt_le_scan_update(true); +#endif + + bt_addr_le_copy(&conn->le.dst, peer); + + bt_conn_set_param_le(conn, param); + + /* for the time being, the implementation bypassed the scan procedure */ + if (hci_le_create_conn(conn)) { + goto done; + } + + bt_conn_set_state(conn, BT_CONN_CONNECT); + +done: + return conn; +} +#else + +void on_nble_gap_connect_rsp(const struct nble_response *params) +{ +} + +#endif /* CONFIG_BLUETOOTH_CENTRAL */ + +int bt_conn_le_param_update(struct bt_conn *conn, const struct bt_le_conn_param *param) +{ + return bt_conn_update_param_le(conn, param); +} + +#if defined(CONFIG_BLUETOOTH_SMP) || defined(CONFIG_BLUETOOTH_BREDR) +uint8_t bt_conn_enc_key_size(struct bt_conn *conn) +{ + return 0; +} + +int bt_conn_auth_cb_register(const struct bt_conn_auth_cb *cb) +{ + if (!cb) { + bt_auth = NULL; + return 0; + } + + /* cancel callback should always be provided */ + if (!cb->cancel) { + return -EINVAL; + } + + if (bt_auth) { + return -EALREADY; + } + + bt_auth = cb; + return 0; +} + +#if defined(CONFIG_BLUETOOTH_BREDR) +static int pin_code_neg_reply(const bt_addr_t *bdaddr) +{ + struct bt_hci_cp_pin_code_neg_reply *cp; + struct net_buf *buf; + + BT_DBG(""); + + buf = bt_hci_cmd_create(BT_HCI_OP_PIN_CODE_NEG_REPLY, sizeof(*cp)); + if (!buf) { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + bt_addr_copy(&cp->bdaddr, bdaddr); + + return bt_hci_cmd_send_sync(BT_HCI_OP_PIN_CODE_NEG_REPLY, buf, NULL); +} + +static int pin_code_reply(struct bt_conn *conn, const char *pin, uint8_t len) +{ + struct bt_hci_cp_pin_code_reply *cp; + struct net_buf *buf; + + BT_DBG(""); + + buf = bt_hci_cmd_create(BT_HCI_OP_PIN_CODE_REPLY, sizeof(*cp)); + if (!buf) { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + + bt_addr_copy(&cp->bdaddr, &conn->br.dst); + cp->pin_len = len; + strncpy(cp->pin_code, pin, sizeof(cp->pin_code)); + + return bt_hci_cmd_send_sync(BT_HCI_OP_PIN_CODE_REPLY, buf, NULL); +} + +int bt_conn_auth_pincode_entry(struct bt_conn *conn, const char *pin) +{ + size_t len; + + if (!bt_auth) { + return -EINVAL; + } + + if (conn->type != BT_CONN_TYPE_BR) { + return -EINVAL; + } + + len = strlen(pin); + if (len > 16) { + return -EINVAL; + } + + if (conn->required_sec_level == BT_SECURITY_HIGH && len < 16) { + BT_WARN("PIN code for %s is not 16 bytes wide", + bt_addr_str(&conn->br.dst)); + return -EPERM; + } + + return pin_code_reply(conn, pin, len); +} + +void bt_conn_pin_code_req(struct bt_conn *conn) +{ + if (bt_auth && bt_auth->pincode_entry) { + bool secure = false; + + if (conn->required_sec_level == BT_SECURITY_HIGH) { + secure = true; + } + + bt_auth->pincode_entry(conn, secure); + } else { + pin_code_neg_reply(&conn->br.dst); + } + +} +#endif /* CONFIG_BLUETOOTH_BREDR */ + +int bt_conn_auth_passkey_entry(struct bt_conn *conn, unsigned int passkey) +{ + if (!bt_auth) { + return -EINVAL; + } +#if defined(CONFIG_BLUETOOTH_SMP) + if (conn->type == BT_CONN_TYPE_LE) { + return bt_smp_auth_passkey_entry(conn, passkey); + } +#endif /* CONFIG_BLUETOOTH_SMP */ + + return -EINVAL; +} + +int bt_conn_auth_passkey_confirm(struct bt_conn *conn, bool match) +{ + if (!bt_auth) { + return -EINVAL; + }; +#if defined(CONFIG_BLUETOOTH_SMP) + if (conn->type == BT_CONN_TYPE_LE) { + return bt_smp_auth_passkey_confirm(conn, match); + } +#endif /* CONFIG_BLUETOOTH_SMP */ + + return -EINVAL; +} + +int bt_conn_auth_cancel(struct bt_conn *conn) +{ + if (!bt_auth) { + return -EINVAL; + } +#if defined(CONFIG_BLUETOOTH_SMP) + if (conn->type == BT_CONN_TYPE_LE) { + return bt_smp_auth_cancel(conn); + } +#endif /* CONFIG_BLUETOOTH_SMP */ +#if defined(CONFIG_BLUETOOTH_BREDR) + if (conn->type == BT_CONN_TYPE_BR) { + return pin_code_neg_reply(&conn->br.dst); + } +#endif /* CONFIG_BLUETOOTH_BREDR */ + + return -EINVAL; +} + +int bt_conn_remove_info(const bt_addr_le_t *addr) +{ + struct bt_conn *conn; + + /* TODO: implement address specific removal */ + if (bt_addr_le_cmp(addr, BT_ADDR_LE_ANY)) + return -EINVAL; + + do { + conn = bt_conn_lookup_state_le(addr, BT_CONN_CONNECTED); + if (conn) { + bt_conn_unref(conn); + bt_conn_disconnect(conn, + BT_HCI_ERR_REMOTE_USER_TERM_CONN); + } + } while(conn); + + return bt_smp_remove_info(addr); +} +#endif /* CONFIG_BLUETOOTH_SMP || CONFIG_BLUETOOTH_BREDR */ + +int bt_conn_init(void) +{ + int err; +#if NOT_USED_FOR_NOW + + net_buf_pool_init(frag_pool); + net_buf_pool_init(dummy_pool); + + bt_att_init(); +#endif + + err = bt_smp_init(); + if (err) { + return err; + } + +#if NOT_USED_FOR_NOW + bt_l2cap_init(); + + background_scan_init(); +#endif + return 0; +} + +int bt_conn_le_conn_update(struct bt_conn *conn, + const struct bt_le_conn_param *param) +{ + struct nble_gap_connect_update_params ble_gap_connect_update; +#if 0 + struct hci_cp_le_conn_update *conn_update; + struct net_buf *buf; + + buf = bt_hci_cmd_create(BT_HCI_OP_LE_CONN_UPDATE, + sizeof(*conn_update)); + if (!buf) { + return -ENOBUFS; + } + + conn_update = net_buf_add(buf, sizeof(*conn_update)); + memset(conn_update, 0, sizeof(*conn_update)); + conn_update->handle = sys_cpu_to_le16(conn->handle); + conn_update->conn_interval_min = sys_cpu_to_le16(param->interval_min); + conn_update->conn_interval_max = sys_cpu_to_le16(param->interval_max); + conn_update->conn_latency = sys_cpu_to_le16(param->latency); + conn_update->supervision_timeout = sys_cpu_to_le16(param->timeout); +#endif + ble_gap_connect_update.conn_handle = conn->handle; + ble_gap_connect_update.params.interval_min = param->interval_min; + ble_gap_connect_update.params.interval_max = param->interval_max; + ble_gap_connect_update.params.slave_latency = param->latency; + ble_gap_connect_update.params.link_sup_to = param->timeout; + + nble_gap_conn_update_req(&ble_gap_connect_update); + + return 0; +} diff --git a/system/libarc32_arduino101/framework/src/services/ble/conn_internal.h b/system/libarc32_arduino101/framework/src/services/ble/conn_internal.h new file mode 100644 index 00000000..04c08839 --- /dev/null +++ b/system/libarc32_arduino101/framework/src/services/ble/conn_internal.h @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2016 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +typedef enum { + BT_CONN_DISCONNECTED, + BT_CONN_CONNECT_SCAN, + BT_CONN_CONNECT, + BT_CONN_CONNECTED, + BT_CONN_DISCONNECT, +} bt_conn_state_t; + +/* bt_conn flags: the flags defined here represent connection parameters */ +enum { + BT_CONN_AUTO_CONNECT, +}; + +struct bt_conn_le { + bt_addr_le_t dst; + +#if 0 + bt_addr_le_t init_addr; + bt_addr_le_t resp_addr; +#endif + uint16_t interval; + uint16_t interval_min; + uint16_t interval_max; + + uint16_t latency; + uint16_t timeout; +#if 0 + uint8_t features[8]; +#endif +}; + +#if defined(CONFIG_BLUETOOTH_BREDR) +struct bt_conn_br { + bt_addr_t dst; +}; +#endif + +struct bt_conn { + uint16_t handle; + uint8_t type; + uint8_t role; + +#if defined(CONFIG_BLUETOOTH_SMP) + uint8_t encrypt; + bt_security_t sec_level; + bt_security_t required_sec_level; +#endif /* CONFIG_BLUETOOTH_SMP */ + + atomic_t ref; + + /* Connection error or reason for disconnect */ + uint8_t err; + + bt_conn_state_t state; + union { + struct bt_conn_le le; +#if defined(CONFIG_BLUETOOTH_BREDR) + struct bt_conn_br br; +#endif + }; +}; + +/* Add a new LE connection */ +struct bt_conn *bt_conn_add_le(const bt_addr_le_t *peer); + +#if defined(CONFIG_BLUETOOTH_BREDR) +/* Add a new BR/EDR connection */ +struct bt_conn *bt_conn_add_br(const bt_addr_t *peer); + +/* Look up an existing connection by BT address */ +struct bt_conn *bt_conn_lookup_addr_br(const bt_addr_t *peer); +#endif + +/* Look up an existing connection */ +struct bt_conn *bt_conn_lookup_handle(uint16_t handle); + +/* Look up a connection state. For BT_ADDR_LE_ANY, returns the first connection + * with the specific state + */ +struct bt_conn *bt_conn_lookup_state_le(const bt_addr_le_t *peer, + const bt_conn_state_t state); + +/* Set connection object in certain state and perform action related to state */ +void bt_conn_set_state(struct bt_conn *conn, bt_conn_state_t state); + +void bt_conn_set_param_le(struct bt_conn *conn, + const struct bt_le_conn_param *param); + +int bt_conn_update_param_le(struct bt_conn *conn, + const struct bt_le_conn_param *param); + +int bt_conn_le_conn_update(struct bt_conn *conn, + const struct bt_le_conn_param *param); + +void notify_le_param_updated(struct bt_conn *conn); + +#if defined(CONFIG_BLUETOOTH_SMP) +/* rand and ediv should be in BT order */ +int bt_conn_le_start_encryption(struct bt_conn *conn, uint64_t rand, + uint16_t ediv, const uint8_t *ltk, size_t len); + +/* Notify higher layers that RPA was resolved */ +void bt_conn_identity_resolved(struct bt_conn *conn); + +/* Notify higher layers that connection security changed */ +void bt_conn_security_changed(struct bt_conn *conn); +#endif /* CONFIG_BLUETOOTH_SMP */ +/* Initialize connection management */ +int bt_conn_init(void); diff --git a/system/libarc32_arduino101/framework/src/services/ble/dtm_tcmd.c b/system/libarc32_arduino101/framework/src/services/ble/dtm_tcmd.c new file mode 100644 index 00000000..4a0c5b0c --- /dev/null +++ b/system/libarc32_arduino101/framework/src/services/ble/dtm_tcmd.c @@ -0,0 +1,12 @@ + +#include +#include "uart.h" +#include "ipc_uart_ns16550.h" + +void on_nble_gap_dtm_init_rsp(void *user_data) +{ +#ifdef CONFIG_UART_NS16550 + uart_ipc_disable(); +#endif +} + diff --git a/system/libarc32_arduino101/framework/src/services/ble/gap.c b/system/libarc32_arduino101/framework/src/services/ble/gap.c new file mode 100644 index 00000000..4cf71d47 --- /dev/null +++ b/system/libarc32_arduino101/framework/src/services/ble/gap.c @@ -0,0 +1,912 @@ +/* + * Copyright (c) 2016 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include +#include +#include "gap_internal.h" +#include "conn_internal.h" + +#include "hci_core.h" + +#if defined(CONFIG_BLUETOOTH_SMP) +#include "smp.h" +#endif /* CONFIG_BLUETOOTH_SMP */ + +/* #define BT_GATT_DEBUG 1 */ + +extern void on_nble_curie_log(char *fmt, ...); +extern void __assert_fail(void); +#ifdef BT_GATT_DEBUG +#define BT_DBG(fmt, ...) on_nble_curie_log(fmt, ##__VA_ARGS__) +#define BT_ERR(fmt, ...) on_nble_curie_log(fmt, ##__VA_ARGS__) +#define BT_WARN(fmt, ...) on_nble_curie_log(fmt, ##__VA_ARGS__) +#define BT_INFO(fmt, ...) on_nble_curie_log(fmt, ##__VA_ARGS__) +#define BT_ASSERT(cond) ((cond) ? (void)0 : __assert_fail()) +#else +#define BT_DBG(fmt, ...) do {} while (0) +#define BT_ERR(fmt, ...) on_nble_curie_log(fmt, ##__VA_ARGS__) +#define BT_WARN(fmt, ...) on_nble_curie_log(fmt, ##__VA_ARGS__) +#define BT_INFO(fmt, ...) on_nble_curie_log(fmt, ##__VA_ARGS__) +#define BT_ASSERT(cond) ((cond) ? (void)0 : __assert_fail()) +#endif + +static bt_ready_cb_t bt_ready_cb; +static bt_le_scan_cb_t *scan_dev_found_cb; +static rssi_report_t rssi_report_cb; + +struct bt_dev bt_dev; + +static int set_advertise_enable(void) +{ +#if 0 + struct net_buf *buf; + int err; +#endif + if (atomic_test_bit(bt_dev.flags, BT_DEV_ADVERTISING)) { + return 0; + } +#if 0 + buf = bt_hci_cmd_create(BT_HCI_OP_LE_SET_ADV_ENABLE, 1); + if (!buf) { + return -ENOBUFS; + } + + net_buf_add_u8(buf, BT_HCI_LE_ADV_ENABLE); + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_ADV_ENABLE, buf, NULL); + if (err) { + return err; + } +#endif + nble_gap_start_adv_req(); + + return 0; +} + +static int set_advertise_disable(void) +{ +#if 0 + struct net_buf *buf; + int err; +#endif + if (!atomic_test_bit(bt_dev.flags, BT_DEV_ADVERTISING)) { + return 0; + } +#if 0 + buf = bt_hci_cmd_create(BT_HCI_OP_LE_SET_ADV_ENABLE, 1); + if (!buf) { + return -ENOBUFS; + } + + net_buf_add_u8(buf, BT_HCI_LE_ADV_DISABLE); + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_ADV_ENABLE, buf, NULL); + if (err) { + return err; + } +#endif + nble_gap_stop_adv_req(NULL); + + atomic_clear_bit(bt_dev.flags, BT_DEV_ADVERTISING); + return 0; +} + +void ble_gap_get_bonding_info(ble_bond_info_cb_t func, void *user_data, + bool include_bonded_addrs) +{ + struct nble_gap_sm_bond_info_param params; + + params.cb = func; + params.user_data = user_data; + params.include_bonded_addrs = include_bonded_addrs; + + nble_gap_sm_bond_info_req(¶ms); +} + +void on_nble_gap_start_advertise_rsp(const struct nble_response *params) +{ + if (params->status == 0) + atomic_set_bit(bt_dev.flags, BT_DEV_ADVERTISING); + else + BT_WARN("start advertise failed with %d", params->status); +} + +void on_nble_gap_disconnect_evt(const struct nble_gap_disconnect_evt *evt) +{ + struct bt_conn *conn; + +#if 0 + /* Nordic has no disconnection error */ + if (evt->status) { + return; + } +#endif + + conn = bt_conn_lookup_handle(evt->conn_handle); + if (!conn) { + BT_DBG("Unable to look up conn with handle %u", + evt->conn_handle); + return; + } +#if 0 + /* Check stacks usage (no-ops if not enabled) */ + stack_analyze("rx stack", rx_fiber_stack, sizeof(rx_fiber_stack)); + stack_analyze("cmd rx stack", rx_prio_fiber_stack, + sizeof(rx_prio_fiber_stack)); + stack_analyze("cmd tx stack", cmd_tx_fiber_stack, + sizeof(cmd_tx_fiber_stack)); + stack_analyze("conn tx stack", conn->stack, sizeof(conn->stack)); + +#endif + + conn->err = evt->hci_reason; + bt_conn_set_state(conn, BT_CONN_DISCONNECTED); + conn->handle = 0; + +#if 0 + /* Only LE supported */ + if (conn->type != BT_CONN_TYPE_LE) { + bt_conn_unref(conn); + return; + } + /* TODO enabled when autoconn is supported */ + if (atomic_test_bit(conn->flags, BT_CONN_AUTO_CONNECT)) { + bt_conn_set_state(conn, BT_CONN_CONNECT_SCAN); + bt_le_scan_update(false); + } +#endif + + bt_conn_unref(conn); + if (atomic_test_bit(bt_dev.flags, BT_DEV_KEEP_ADVERTISING)) { + set_advertise_enable(); + } +} + +void on_nble_gap_connect_evt(const struct nble_gap_connect_evt *evt) +{ + struct bt_conn *conn; + + /* Make lookup to check if there's a connection object in CONNECT state + * associated with passed peer LE address. + */ + conn = bt_conn_lookup_state_le(&evt->peer_bda, BT_CONN_CONNECT); + +#if 0 + /* Nordic has no connection error */ + if (evt->status) { + if (!conn) { + return; + } + + conn->err = BT_HCI_ERR_UNACCEPT_CONN_PARAMS; + bt_conn_set_state(conn, BT_CONN_DISCONNECTED); + + /* Drop the reference got by lookup call in CONNECT state. + * We are now in DISCONNECTED state since no successful LE + * link been made. + */ + bt_conn_unref(conn); + + return; + } +#endif + /* + * clear advertising even if we are not able to add connection object + * to keep host in sync with controller state + */ + if (evt->role_slave == BT_CONN_ROLE_SLAVE) { + atomic_clear_bit(bt_dev.flags, BT_DEV_ADVERTISING); + } + + if (!conn) { + conn = bt_conn_add_le(&evt->peer_bda); + } + + if (!conn) { + BT_DBG("Unable to add new conn for handle %u", + evt->conn_handle); + return; + } + + conn->handle = evt->conn_handle; + bt_addr_le_copy(&conn->le.dst, &evt->peer_bda); + conn->le.interval = evt->conn_values.interval; + conn->le.latency = evt->conn_values.latency; + conn->le.timeout = evt->conn_values.supervision_to; + conn->role = evt->role_slave; + +#if 0 + src.type = BT_ADDR_LE_PUBLIC; + memcpy(src.val, bt_dev.bdaddr.val, sizeof(bt_dev.bdaddr.val)); + + /* use connection address (instead of identity address) as initiator + * or responder address + */ + if (conn->role == BT_HCI_ROLE_MASTER) { + bt_addr_le_copy(&conn->le.init_addr, &src); + bt_addr_le_copy(&conn->le.resp_addr, &evt->peer_addr); + } else { + bt_addr_le_copy(&conn->le.init_addr, &evt->peer_addr); + bt_addr_le_copy(&conn->le.resp_addr, &src); + } +#endif + bt_conn_set_state(conn, BT_CONN_CONNECTED); + + /* Note: Connection update removed because Windows interop and BT spec recommendations */ + + bt_conn_unref(conn); +#if 0 + bt_le_scan_update(false); +#endif + +} + +void on_nble_gap_adv_report_evt(const struct nble_gap_adv_report_evt *evt, + const uint8_t *buf, uint8_t len) +{ +#if TODO_IMPLEMENT_CONNECTION + uint8_t num_reports = buf->data[0]; + struct bt_hci_ev_le_advertising_info *info; + + BT_DBG("Adv number of reports %u", num_reports); + + info = net_buf_pull(buf, sizeof(num_reports)); + + while (num_reports--) { + int8_t rssi = info->data[info->length]; + const bt_addr_le_t *addr; + + BT_DBG("%s event %u, len %u, rssi %d dBm", + bt_addr_le_str(&info->addr), + info->evt_type, info->length, rssi); + + addr = find_id_addr(&info->addr); +#endif + + BT_DBG("nble gap: event:%u, len %u", evt->adv_type, len); + + if (scan_dev_found_cb) { + scan_dev_found_cb(&evt->addr, evt->rssi, evt->adv_type, + buf, len); + } +#if TODO_IMPLEMENT_CONNECTION +#if defined(CONFIG_BLUETOOTH_CONN) + check_pending_conn(addr, &info->addr, info->evt_type); +#endif /* CONFIG_BLUETOOTH_CONN */ + /* Get next report iteration by moving pointer to right offset + * in buf according to spec 4.2, Vol 2, Part E, 7.7.65.2. + */ + info = net_buf_pull(buf, sizeof(*info) + info->length + + sizeof(rssi)); + } +#endif +} + +void on_nble_gap_conn_update_evt(const struct nble_gap_conn_update_evt *evt) +{ + struct bt_conn *conn; + uint16_t handle, interval; + + handle = evt->conn_handle; + interval = evt->conn_values.interval; + +/* BT_DBG("status %u, handle %u", evt->status, handle); */ + + conn = bt_conn_lookup_handle(handle); + if (!conn) { +/* BT_ERR("Unable to lookup conn for handle %u", handle); */ + return; + } + +/* if (!evt->status) { */ + conn->le.interval = interval; + conn->le.latency = evt->conn_values.latency; + conn->le.timeout = evt->conn_values.supervision_to; + notify_le_param_updated(conn); +/* } */ + + + bt_conn_unref(conn); +} + +void bt_conn_set_param_le(struct bt_conn *conn, + const struct bt_le_conn_param *param) +{ + conn->le.interval_min = param->interval_min; + conn->le.interval_max = param->interval_max; + conn->le.latency = param->latency; + conn->le.timeout = param->timeout; +} +int bt_conn_update_param_le(struct bt_conn *conn, + const struct bt_le_conn_param *param) +{ +#if 0 + BT_DBG("conn %p features 0x%x params (%d-%d %d %d)", conn, + conn->le.features[0], param->interval_min, param->interval_max, + param->latency, param->timeout); +#endif + /* Check if there's a need to update conn params */ + if (conn->le.interval >= param->interval_min && + conn->le.interval <= param->interval_max) { + return -EALREADY; + } +#if 0 + if ((conn->role == BT_HCI_ROLE_SLAVE) && + !(bt_dev.le.features[0] & BT_HCI_LE_CONN_PARAM_REQ_PROC)) { + return bt_l2cap_update_conn_param(conn, param); + } + + if ((conn->le.features[0] & BT_HCI_LE_CONN_PARAM_REQ_PROC) && + (bt_dev.le.features[0] & BT_HCI_LE_CONN_PARAM_REQ_PROC)) { +#endif + return bt_conn_le_conn_update(conn, param); +#if 0 + } + return -EBUSY; +#endif +} + +void on_nble_gap_scan_start_stop_rsp(const struct nble_response *rsp) +{ + if (rsp->status) + BT_INFO("scan start/stop failed: %d", rsp->status); + /* TODO: clear scanning bit atomic_clear_bit(bt_dev.flags, BT_DEV_SCANNING) */ +} + +static int bt_hci_stop_scanning(void) +{ +#ifdef NOT_USED_FOR_NOW + struct net_buf *buf, *rsp; + struct bt_hci_cp_le_set_scan_enable *scan_enable; + int err; + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_SCANNING)) { + return -EALREADY; + } + + buf = bt_hci_cmd_create(BT_HCI_OP_LE_SET_SCAN_ENABLE, + sizeof(*scan_enable)); + if (!buf) { + return -ENOBUFS; + } + + scan_enable = net_buf_add(buf, sizeof(*scan_enable)); + memset(scan_enable, 0, sizeof(*scan_enable)); + scan_enable->filter_dup = BT_HCI_LE_SCAN_FILTER_DUP_DISABLE; + scan_enable->enable = BT_HCI_LE_SCAN_DISABLE; + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_SCAN_ENABLE, buf, &rsp); + if (err) { + return err; + } + + /* Update scan state in case of success (0) status */ + err = rsp->data[0]; + if (!err) { + atomic_clear_bit(bt_dev.flags, BT_DEV_SCANNING); + } + + net_buf_unref(rsp); + + return err; +#endif + + nble_gap_stop_scan_req(); + + return 0; +} + +#if defined(CONFIG_BLUETOOTH_CENTRAL) +int bt_le_set_auto_conn(bt_addr_le_t *addr, + const struct bt_le_conn_param *param) +{ + return -EINVAL; +} +#endif /* CONFIG_BLUETOOTH_CENTRAL */ + + +static int start_le_scan(uint8_t scan_type, uint16_t interval, uint16_t window, + uint8_t filter_dup) +{ + struct nble_gap_scan_params params = { + .interval = interval, + .window = window, + .scan_type = scan_type, + }; + +#ifdef NOT_USED_FOR_NOW + struct net_buf *buf, *rsp; + struct bt_hci_cp_le_set_scan_params *set_param; + struct bt_hci_cp_le_set_scan_enable *scan_enable; + int err; + + buf = bt_hci_cmd_create(BT_HCI_OP_LE_SET_SCAN_PARAMS, + sizeof(*set_param)); + if (!buf) { + return -ENOBUFS; + } + + + set_param = net_buf_add(buf, sizeof(*set_param)); + memset(set_param, 0, sizeof(*set_param)); + set_param->scan_type = scan_type; + + /* for the rest parameters apply default values according to + * spec 4.2, vol2, part E, 7.8.10 + */ + set_param->interval = sys_cpu_to_le16(interval); + set_param->window = sys_cpu_to_le16(window); + set_param->filter_policy = 0x00; + + if (scan_type == BT_HCI_LE_SCAN_ACTIVE) { + err = le_set_nrpa(); + if (err) { + net_buf_unref(buf); + return err; + } + + set_param->addr_type = BT_ADDR_LE_RANDOM; + } else { + set_param->addr_type = BT_ADDR_LE_PUBLIC; + } + + bt_hci_cmd_send(BT_HCI_OP_LE_SET_SCAN_PARAMS, buf); + buf = bt_hci_cmd_create(BT_HCI_OP_LE_SET_SCAN_ENABLE, + sizeof(*scan_enable)); + if (!buf) { + return -ENOBUFS; + } + + scan_enable = net_buf_add(buf, sizeof(*scan_enable)); + memset(scan_enable, 0, sizeof(*scan_enable)); + scan_enable->filter_dup = filter_dup; + scan_enable->enable = BT_HCI_LE_SCAN_ENABLE; + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_SCAN_ENABLE, buf, &rsp); + if (err) { + return err; + } + /* Update scan state in case of success (0) status */ + err = rsp->data[0]; + if (!err) { + atomic_set_bit(bt_dev.flags, BT_DEV_SCANNING); + } + + net_buf_unref(rsp); +#endif + + nble_gap_start_scan_req(¶ms); + + return 0; +} + +#if NOT_USED_FOR_NOW +/* Used to determine whether to start scan and which scan type should be used */ +int bt_le_scan_update(bool fast_scan) +{ +#if defined(CONFIG_BLUETOOTH_CENTRAL) + uint16_t interval, window; + struct bt_conn *conn; +#endif /* CONFIG_BLUETOOTH_CENTRAL */ + + if (atomic_test_bit(bt_dev.flags, BT_DEV_EXPLICIT_SCAN)) { + return 0; + } + + if (atomic_test_bit(bt_dev.flags, BT_DEV_SCANNING)) { + int err; + + err = bt_hci_stop_scanning(); + if (err) { + return err; + } + } + +#if defined(CONFIG_BLUETOOTH_CENTRAL) + conn = bt_conn_lookup_state_le(NULL, BT_CONN_CONNECT_SCAN); + if (!conn) { + return 0; + } + + bt_conn_unref(conn); + + if (fast_scan) { + interval = BT_GAP_SCAN_FAST_INTERVAL; + window = BT_GAP_SCAN_FAST_WINDOW; + } else { + interval = BT_GAP_SCAN_SLOW_INTERVAL_1; + window = BT_GAP_SCAN_SLOW_WINDOW_1; + } + + return start_le_scan(BT_HCI_LE_SCAN_PASSIVE, interval, window, 0x01); +#else + return 0; +#endif /* CONFIG_BLUETOOTH_CENTRAL */ +} +#endif + +static int bt_init(void) +{ +#if NOT_USED_FOR_NOW + struct bt_driver *drv = bt_dev.drv; +#endif + int err = 0; + +#if NOT_USED_FOR_NOW + err = drv->open(); + if (err) { + BT_ERR("HCI driver open failed (%d)", err); + return err; + } + + err = hci_init(); +#endif + + if (!err) { + err = bt_conn_init(); + } + + scan_dev_found_cb = NULL; + if (!err) { + atomic_set_bit(bt_dev.flags, BT_DEV_READY); +#if 0 + bt_le_scan_update(false); +#endif + } + + return err; +} + +void on_nble_up(void) +{ + BT_DBG("%s", __FUNCTION__); + if (bt_ready_cb) + bt_ready_cb(bt_init()); +} + +extern void on_nble_curie_init(void); + +int bt_enable(bt_ready_cb_t cb) +{ + bt_ready_cb = cb; + + on_nble_curie_init(); + + if (!cb) { + return bt_init(); + } + + return 0; +} + + +static bool valid_adv_param(const struct bt_le_adv_param *param) +{ + switch (param->type) { + case BT_LE_ADV_IND: + case BT_LE_ADV_SCAN_IND: + case BT_LE_ADV_NONCONN_IND: + break; + default: + return false; + } + +#if 0 + /* checks done in Nordic */ + switch (param->addr_type) { + case BT_LE_ADV_ADDR_IDENTITY: + case BT_LE_ADV_ADDR_NRPA: + break; + default: + return false; + } + + if (param->interval_min > param->interval_max || + param->interval_min < 0x0020 || param->interval_max > 0x4000) { + return false; + } +#endif + + return true; +} + +static int set_ad(struct bt_eir_data *p_ad_data, + const struct bt_data *ad, size_t ad_len) +{ + int i; + + for (i = 0; i < ad_len; i++) { + /* Check if ad fit in the remaining buffer */ + if (p_ad_data->len + ad[i].data_len + 2 > 31) { + return -EINVAL; + } + + p_ad_data->data[p_ad_data->len++] = ad[i].data_len + 1; + p_ad_data->data[p_ad_data->len++] = ad[i].type; + + memcpy(&p_ad_data->data[p_ad_data->len], ad[i].data, + ad[i].data_len); + p_ad_data->len += ad[i].data_len; + } + + return 0; +} + +int bt_le_adv_start(const struct bt_le_adv_param *param, + const struct bt_data *ad, size_t ad_len, + const struct bt_data *sd, size_t sd_len) +{ + int err; + struct nble_gap_adv_params set_param = {0}; + struct nble_gap_ad_data_params data; + + if (!valid_adv_param(param)) { + return -EINVAL; + } + + memset(&data, 0, sizeof(data)); + + if (atomic_test_bit(bt_dev.flags, BT_DEV_KEEP_ADVERTISING)) { + return -EALREADY; + } + + err = set_advertise_disable(); + if (err) { + return err; + } + err = set_ad(&data.ad, ad, ad_len); + if (err) { + return err; + } + + /* + * Don't bother with scan response if the advertising type isn't + * a scannable one. + */ + if (param->type == BT_LE_ADV_IND || param->type == BT_LE_ADV_SCAN_IND) { + err = set_ad(&data.sd, sd, sd_len); + if (err) { + return err; + } + } + nble_gap_set_adv_data_req(&data); + + /* Timeout is handled by application timer */ + set_param.timeout = 0; + /* forced to none currently (no whitelist support) */ + set_param.filter_policy = 0; + set_param.interval_max = param->interval_max; + set_param.interval_min = param->interval_min; + set_param.type = param->type; + nble_gap_set_adv_params_req(&set_param); + +#if 0 + if (param->addr_type == BT_LE_ADV_ADDR_NRPA) { + err = le_set_nrpa(); + if (err) { + net_buf_unref(buf); + return err; + } + + set_param->own_addr_type = BT_ADDR_LE_RANDOM; + } else { + set_param->own_addr_type = BT_ADDR_LE_PUBLIC; + } + + bt_hci_cmd_send(BT_HCI_OP_LE_SET_ADV_PARAMETERS, buf); +#endif + + err = set_advertise_enable(); + if (err) { + return err; + } + + atomic_set_bit(bt_dev.flags, BT_DEV_KEEP_ADVERTISING); + + return 0; +} + +void on_nble_gap_dir_adv_timeout_evt(const struct nble_gap_dir_adv_timeout_evt *p_evt) +{ + struct bt_conn *conn = bt_conn_lookup_state_le(BT_ADDR_LE_ANY, BT_CONN_CONNECT); + + if (conn) { + atomic_clear_bit(bt_dev.flags, BT_DEV_ADVERTISING); + conn->err = p_evt->error; + bt_conn_set_state(conn, BT_CONN_DISCONNECTED); + bt_conn_unref(conn); + } +} + +int bt_le_adv_stop(void) +{ + int err; + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_KEEP_ADVERTISING)) { + return -EALREADY; + } + + err = set_advertise_disable(); + if (err) { + return err; + } + atomic_clear_bit(bt_dev.flags, BT_DEV_KEEP_ADVERTISING); + + return 0; +} + +static bool valid_le_scan_param(const struct bt_le_scan_param *param) +{ + if (param->type != BT_HCI_LE_SCAN_PASSIVE && + param->type != BT_HCI_LE_SCAN_ACTIVE) { + return false; + } + + if (/* param->filter_dup != BT_HCI_LE_SCAN_FILTER_DUP_DISABLE */ + /* && nble always filters duplicates */ + param->filter_dup != BT_HCI_LE_SCAN_FILTER_DUP_ENABLE) { + return false; + } + + if (param->interval < 0x0004 || param->interval > 0x4000) { + return false; + } + + if (param->window < 0x0004 || param->window > 0x4000) { + return false; + } + + if (param->window > param->interval) { + return false; + } + + return true; +} + +int bt_le_scan_start(const struct bt_le_scan_param *param, bt_le_scan_cb_t cb) +{ + + int err; + + /* Check that the parameters have valid values */ + if (!valid_le_scan_param(param)) { + return -EINVAL; + } +#if NOT_USED_FOR_NOW + /* Return if active scan is already enabled */ + if (atomic_test_and_set_bit(bt_dev.flags, BT_DEV_EXPLICIT_SCAN)) { + return -EALREADY; + } + + if (atomic_test_bit(bt_dev.flags, BT_DEV_SCANNING)) { + err = bt_hci_stop_scanning(); + if (err) { + atomic_clear_bit(bt_dev.flags, BT_DEV_EXPLICIT_SCAN); + return err; + } + } +#endif + + err = start_le_scan(param->type, param->interval, param->window, + param->filter_dup); + + + if (err) { +#if NOT_USED_FOR_NOW + atomic_clear_bit(bt_dev.flags, BT_DEV_EXPLICIT_SCAN); +#endif + return err; + } + + scan_dev_found_cb = cb; + + return 0; +} + +int bt_le_scan_stop(void) +{ +#if NOT_USED_FOR_NOW + /* Return if active scanning is already disabled */ + if (!atomic_test_and_clear_bit(bt_dev.flags, BT_DEV_EXPLICIT_SCAN)) { + return -EALREADY; + } +#endif + scan_dev_found_cb = NULL; + +#if NOT_USED_FOR_NOW + return bt_le_scan_update(false); +#else + return bt_hci_stop_scanning(); +#endif +} + +/* Temporary RSSI patch for UAS: RPC need definition if UAS not compiled */ +__attribute__((weak)) +void on_nble_uas_bucket_change(const struct nble_uas_bucket_change *p_params) +{ +} + +void ble_gap_set_rssi_report(struct nble_rssi_report_params *params, + struct bt_conn *conn, + rssi_report_resp_t resp_cb, rssi_report_t evt_cb) +{ + rssi_report_cb = evt_cb; + + params->conn_handle = conn->handle; + + nble_gap_set_rssi_report_req(params, resp_cb); +} + +void on_nble_gap_set_rssi_report_rsp(const struct nble_response *params) +{ + rssi_report_resp_t resp_cb = params->user_data; + + if (resp_cb) + resp_cb(params->status); +} + +void on_nble_gap_rssi_evt(const struct nble_gap_rssi_evt *event) +{ + if (rssi_report_cb) + rssi_report_cb(event->rssi_data); +} + +void ble_gap_set_tx_power(int8_t tx_power) +{ + struct nble_gap_tx_power_params params = { + .tx_power = tx_power, + }; + nble_gap_tx_power_req(¶ms); +} + +void on_nble_gap_tx_power_rsp(const struct nble_response *params) +{ +} + +void ble_gap_get_version(ble_get_version_cb_t func) +{ + struct nble_gap_get_version_param params; + + params.cb = func; + + nble_get_version_req(¶ms); +} + +void on_nble_get_version_rsp(const struct nble_version_response *par) +{ + struct nble_gap_get_version_param param = par->params; + ble_get_version_cb_t cb = param.cb; + + if (cb) { + cb(&par->ver); + } +} + +void bt_le_set_device_name(char *device_name, int len) +{ + struct nble_gap_service_write_params gap_service_params; + if (len > 20) + len = 20; + memset(&gap_service_params, 0, sizeof(gap_service_params)); + gap_service_params.attr_type = NBLE_GAP_SVC_ATTR_NAME; + gap_service_params.name.len = len; + gap_service_params.name.sec_mode = 0x11;// GAP_SEC_LEVEL_1 | GAP_SEC_MODE_1; + memcpy(gap_service_params.name.name_array, device_name, len); + nble_gap_service_write_req(&gap_service_params); +} + diff --git a/system/libarc32_arduino101/framework/src/services/ble/gatt.c b/system/libarc32_arduino101/framework/src/services/ble/gatt.c new file mode 100644 index 00000000..d3ef4c09 --- /dev/null +++ b/system/libarc32_arduino101/framework/src/services/ble/gatt.c @@ -0,0 +1,1561 @@ +/* + * Copyright (c) 2016 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#include + +#include +#include "gatt_internal.h" +#include "hci_core.h" +#include "conn_internal.h" + +/* #define BT_GATT_DEBUG 1 */ + +extern void on_nble_curie_log(char *fmt, ...); +extern void __assert_fail(void); +#ifdef BT_GATT_DEBUG +#define BT_DBG(fmt, ...) on_nble_curie_log(fmt, ##__VA_ARGS__) +#define BT_ERR(fmt, ...) on_nble_curie_log(fmt, ##__VA_ARGS__) +#define BT_WARN(fmt, ...) on_nble_curie_log(fmt, ##__VA_ARGS__) +#define BT_INFO(fmt, ...) on_nble_curie_log(fmt, ##__VA_ARGS__) +#define BT_ASSERT(cond) ((cond) ? (void)0 : __assert_fail()) +#else +#define BT_DBG(fmt, ...) do {} while (0) +#define BT_ERR(fmt, ...) on_nble_curie_log(fmt, ##__VA_ARGS__) +#define BT_WARN(fmt, ...) on_nble_curie_log(fmt, ##__VA_ARGS__) +#define BT_INFO(fmt, ...) on_nble_curie_log(fmt, ##__VA_ARGS__) +#define BT_ASSERT(cond) ((cond) ? (void)0 : __assert_fail()) +#endif + +#define N_BLE_BUF_SIZE 512 + +struct ble_gatt_service { + struct bt_gatt_attr *attrs; /* Pointer to the array of attributes */ + uint16_t attr_count; /* Number of attributes in the array */ +}; + +struct ble_gatts_flush_all { + struct bt_conn *conn; + int status; + uint8_t flag; +}; + +static struct ble_gatt_service db[CONFIG_BT_GATT_BLE_MAX_SERVICES]; + +static uint8_t db_cnt; + +#if defined(CONFIG_BLUETOOTH_GATT_CLIENT) +static struct bt_gatt_subscribe_params *subscriptions; +#endif + +/** + * Copy a UUID in a buffer using the smallest memory length + * @param buf Pointer to the memory where the UUID shall be copied + * @param uuid Pointer to the UUID to copy + * @return The length required to store the UUID in the memory + */ +static uint8_t bt_gatt_uuid_memcpy(uint8_t *buf, + const struct bt_uuid *uuid) +{ + uint8_t *ptr = buf; + + /* Store the type of the UUID */ + *ptr = uuid->type; + ptr++; + + /* Store the UUID data */ + if (uuid->type == BT_UUID_TYPE_16) { + uint16_t le16; + + le16 = sys_cpu_to_le16(BT_UUID_16(uuid)->val); + memcpy(ptr, &le16, sizeof(le16)); + ptr += sizeof(le16); + } else { + memcpy(ptr, BT_UUID_128(uuid)->val, + sizeof(BT_UUID_128(uuid)->val)); + ptr += sizeof(BT_UUID_128(uuid)->val); + } + return ptr - buf; +} + +/* These attributes need the value to be read */ +static struct bt_uuid *whitelist[] = { + BT_UUID_GATT_PRIMARY, + BT_UUID_GATT_SECONDARY, + BT_UUID_GATT_INCLUDE, + BT_UUID_GATT_CHRC, + BT_UUID_GATT_CEP, + BT_UUID_GATT_CUD, + BT_UUID_GATT_CPF, + BT_UUID_GAP_DEVICE_NAME, + BT_UUID_GAP_APPEARANCE, + BT_UUID_GAP_PPCP +}; + +static int attr_read(struct bt_gatt_attr *attr, uint8_t *data, size_t len) +{ + uint8_t i; + int data_size; + + if (!data || len < 0) { + return -ENOMEM; + } + + data_size = bt_gatt_uuid_memcpy(data, attr->uuid); + + for (i = 0; i < ARRAY_SIZE(whitelist); i++) { + if (!bt_uuid_cmp(attr->uuid, whitelist[i])) { + int read; + + read = attr->read(NULL, attr, data + data_size, len, + 0); + if (read < 0) { + return read; + } + + data_size += read; + break; + } + } + + return data_size; +} + +int bt_gatt_register(struct bt_gatt_attr *attrs, size_t count) +{ + size_t attr_table_size, i; + struct nble_gatt_register_req param; + /* TODO: Replace the following with net_buf */ + uint8_t attr_table[N_BLE_BUF_SIZE]; + + if (!attrs || !count) { + return -EINVAL; + } + BT_ASSERT(db_cnt < ARRAY_SIZE(db)); + + db[db_cnt].attrs = attrs; + db[db_cnt].attr_count = count; + db_cnt++; + param.attr_base = attrs; + param.attr_count = count; + + attr_table_size = 0; + + for (i = 0; i < count; i++) { + struct bt_gatt_attr *attr = &attrs[i]; + struct ble_gatt_attr *att; + int data_size; + + if (attr_table_size + sizeof(*att) > sizeof(attr_table)) { + return -ENOMEM; + } + + att = (void *)&attr_table[attr_table_size]; + att->perm = attr->perm; + + /* Read attribute data */ + data_size = attr_read(attr, att->data, + sizeof(attr_table) - + (attr_table_size + sizeof(*att))); + if (data_size < 0) { + return data_size; + } + att->data_size = data_size; + + BT_DBG("table size = %u attr data_size = %u", attr_table_size, + att->data_size); + + /* Compute the new element size and align it on upper 4 bytes + * boundary. + */ + attr_table_size += (sizeof(*att) + att->data_size + 3) & ~3; + } + + nble_gatt_register_req(¶m, attr_table, attr_table_size); + return 0; +} + +void on_nble_gatt_register_rsp(const struct nble_gatt_register_rsp *rsp, + const struct nble_gatt_attr_handles *handles, uint8_t len) +{ + + if (rsp->status != 0) { + BT_ERR("failure registering table: %d - %u - %p", rsp->status, + rsp->attr_count, rsp->attr_base); + } +#ifdef BT_GATT_DEBUG + BT_DBG("register rsp : s=%d - b=%p - c=%u", rsp->status, + rsp->attr_base, rsp->attr_count); + { + int i; + + for (i = 0; i < rsp->attr_count; i++) { + if (handles[i].handle != 0) { + BT_DBG("gatt: i %d, h %d, type %d, u16 0x%x", + i, handles[i].handle, + rsp->attr_base[i].uuid->type, + BT_UUID_16(rsp->attr_base[i].uuid)->val); + } + } + } +#endif +} + +ssize_t bt_gatt_attr_read(struct bt_conn *conn, const struct bt_gatt_attr *attr, + void *buf, uint16_t buf_len, uint16_t offset, + const void *value, uint16_t value_len) +{ + uint16_t len; + + if (offset > value_len) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + len = min(buf_len, value_len - offset); + + BT_DBG("handle 0x%04x offset %u length %u", attr->handle, offset, + len); + + memcpy(buf, value + offset, len); + + return len; +} + +ssize_t bt_gatt_attr_read_service(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + void *buf, uint16_t len, uint16_t offset) +{ + struct bt_uuid *uuid = attr->user_data; + + if (uuid->type == BT_UUID_TYPE_16) { + uint16_t uuid16 = sys_cpu_to_le16(BT_UUID_16(uuid)->val); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, + &uuid16, 2); + } + + return bt_gatt_attr_read(conn, attr, buf, len, offset, + BT_UUID_128(uuid)->val, 16); +} + +struct gatt_incl { + uint16_t start_handle; + uint16_t end_handle; + uint16_t uuid16; +} __packed; + +ssize_t bt_gatt_attr_read_included(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + void *buf, uint16_t len, uint16_t offset) +{ + struct bt_gatt_include *incl = attr->user_data; + struct gatt_incl pdu; + uint8_t value_len; + + pdu.start_handle = sys_cpu_to_le16(incl->start_handle); + pdu.end_handle = sys_cpu_to_le16(incl->end_handle); + value_len = sizeof(pdu.start_handle) + sizeof(pdu.end_handle); + + /* + * Core 4.2, Vol 3, Part G, 3.2, + * The Service UUID shall only be present when the UUID is a 16-bit + * Bluetooth UUID. + */ + if (incl->uuid->type == BT_UUID_TYPE_16) { + pdu.uuid16 = sys_cpu_to_le16(BT_UUID_16(incl->uuid)->val); + value_len += sizeof(pdu.uuid16); + } + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &pdu, value_len); +} + +struct gatt_chrc { + uint8_t properties; + uint16_t value_handle; + union { + uint16_t uuid16; + uint8_t uuid[16]; + }; +} __packed; + +ssize_t bt_gatt_attr_read_chrc(struct bt_conn *conn, + const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + struct bt_gatt_chrc *chrc = attr->user_data; + struct gatt_chrc pdu; + uint8_t value_len; + + pdu.properties = chrc->properties; + /* BLUETOOTH SPECIFICATION Version 4.2 [Vol 3, Part G] page 534: + * 3.3.2 Characteristic Value Declaration + * The Characteristic Value declaration contains the value of the + * characteristic. It is the first Attribute after the characteristic + * declaration. All characteristic definitions shall have a + * Characteristic Value declaration. + */ +#if 0 + next = bt_gatt_attr_next(attr); + if (!next) { + BT_WARN("No value for characteristic at 0x%04x", attr->handle); + pdu.value_handle = 0x0000; + } else { + pdu.value_handle = sys_cpu_to_le16(next->handle); + } +#else + pdu.value_handle = 0x0000; +#endif + value_len = sizeof(pdu.properties) + sizeof(pdu.value_handle); + + if (chrc->uuid->type == BT_UUID_TYPE_16) { + pdu.uuid16 = sys_cpu_to_le16(BT_UUID_16(chrc->uuid)->val); + value_len += 2; + } else { + memcpy(pdu.uuid, BT_UUID_128(chrc->uuid)->val, 16); + value_len += 16; + } + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &pdu, value_len); +} + +void bt_gatt_foreach_attr(uint16_t start_handle, uint16_t end_handle, + bt_gatt_attr_func_t func, void *user_data) +{ + + const struct bt_gatt_attr *attr; + + BT_ASSERT(start_handle == 1 && end_handle == 0xFFFF); + + for (attr = db[0].attrs; attr; attr = bt_gatt_attr_next(attr)) { +#if 0 + /* Check if attribute handle is within range */ + if (attr->handle < start_handle || attr->handle > end_handle) { + continue; + } +#endif + + if (func(attr, user_data) == BT_GATT_ITER_STOP) { + break; + } + } +} + +struct bt_gatt_attr *bt_gatt_attr_next(const struct bt_gatt_attr *attr) +{ + struct ble_gatt_service *svc, *svc_last; + + svc_last = &db[db_cnt]; + + for (svc = db; svc < svc_last; svc++) { + if (attr >= svc->attrs && attr < &svc->attrs[svc->attr_count]) { + int index = attr - &svc->attrs[0]; + + if (index < (svc->attr_count - 1)) { + return (struct bt_gatt_attr *)&attr[1]; + } else if ((svc + 1) < svc_last) { + return (svc + 1)->attrs; + } else { + return NULL; + } + } + } + /* Normally, we should not reach here */ + return NULL; +} + +ssize_t bt_gatt_attr_read_ccc(struct bt_conn *conn, + const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + return BT_GATT_ERR(BT_ATT_ERR_NOT_SUPPORTED); + +#if 0 + struct _bt_gatt_ccc *ccc = attr->user_data; + uint16_t value; + size_t i; + + for (i = 0; i < ccc->cfg_len; i++) { + if (bt_addr_le_cmp(&ccc->cfg[i].peer, &conn->le.dst)) { + continue; + } + + value = sys_cpu_to_le16(ccc->cfg[i].value); + break; + } + + /* Default to disable if there is no cfg for the peer */ + if (i == ccc->cfg_len) { + value = 0x0000; + } + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &value, + sizeof(value)); +#endif +} + +static void gatt_ccc_changed(struct _bt_gatt_ccc *ccc) +{ + int i; + uint16_t value = 0x0000; + + for (i = 0; i < ccc->cfg_len; i++) { + if (ccc->cfg[i].value > value) { + value = ccc->cfg[i].value; + } + } + + BT_DBG("ccc %p value 0x%04x", ccc, value); + + if (value != ccc->value) { + ccc->value = value; + if (ccc->cfg_changed) + ccc->cfg_changed(value); + } +} + +#if defined(CONFIG_BLUETOOTH_GATT_CLIENT) +static bool is_bonded(const bt_addr_le_t *addr) +{ +#if defined(CONFIG_BLUETOOTH_SMP) + struct bt_conn *conn = bt_conn_lookup_addr_le(addr); + + /* + * this is a temporary workaround. if encrypt is set, we know we are + * paired. nble does not yet report if device is bonded. + */ + if (conn) { + uint8_t encrypt = conn->encrypt; + + bt_conn_unref(conn); + + return encrypt; + } + return false; +#else + return false; +#endif /* defined(CONFIG_BLUETOOTH_SMP) */ +} +#endif + +ssize_t bt_gatt_attr_write_ccc(struct bt_conn *conn, + const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset) +{ + struct _bt_gatt_ccc *ccc = attr->user_data; + const uint16_t *data = buf; + size_t i; + + //BT_DBG("%s", __FUNCTION__); + + if (offset > sizeof(*data)) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + //BT_DBG("%s1", __FUNCTION__); + + if (offset + len > sizeof(*data)) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + //BT_DBG("%s2", __FUNCTION__); + + for (i = 0; i < ccc->cfg_len; i++) { + /* Check for existing configuration */ + if (!bt_addr_le_cmp(&ccc->cfg[i].peer, &conn->le.dst)) { + break; + } + } + + //BT_DBG("%s3", __FUNCTION__); + + if (i == ccc->cfg_len) { + for (i = 0; i < ccc->cfg_len; i++) { + /* Check for unused configuration */ + if (!ccc->cfg[i].valid) { + bt_addr_le_copy(&ccc->cfg[i].peer, &conn->le.dst); +#if NOT_USED_FOR_THE_TIME_BEING + /* Only set valid if bonded */ + ccc->cfg[i].valid = is_bonded(&conn->le.dst); +#endif + break; + } + } + + if ((i == ccc->cfg_len) && (ccc->cfg_len)) { + BT_WARN("No space to store CCC cfg"); + return -ENOMEM; + } + } + + //BT_DBG("%s len-%p", __FUNCTION__, ccc); + + ccc->cfg[i].value = sys_le16_to_cpu(*data); + //BT_DBG("%s 5len-%d", __FUNCTION__, len); + + //BT_DBG("handle 0x%04x value %u", attr->handle, *data); + + /* Update cfg if don't match */ + if (ccc->value != *data) { + gatt_ccc_changed(ccc); + } + + return len; +} + +ssize_t bt_gatt_attr_read_cep(struct bt_conn *conn, + const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + struct bt_gatt_cep *value = attr->user_data; + uint16_t props = sys_cpu_to_le16(value->properties); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &props, + sizeof(props)); +} + +ssize_t bt_gatt_attr_read_cud(struct bt_conn *conn, + const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + char *value = attr->user_data; + + return bt_gatt_attr_read(conn, attr, buf, len, offset, value, strlen(value)); +} + +ssize_t bt_gatt_attr_read_cpf(struct bt_conn *conn, + const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + struct bt_gatt_cpf *value = attr->user_data; + + return bt_gatt_attr_read(conn, attr, buf, len, offset, value, + sizeof(*value)); +} + +struct notify_data { + uint16_t state; + uint16_t type; + const struct bt_gatt_attr *attr; + const void *data; + uint16_t len; + bt_gatt_notify_sent_func_t notify_cb; + struct bt_gatt_indicate_params *params; +}; + +static int att_notify(struct bt_conn *conn, const struct bt_gatt_attr *attr, + const void *data, size_t len, + bt_gatt_notify_sent_func_t cb) +{ + struct nble_gatt_send_notif_params notif; + + notif.conn_handle = conn->handle; + notif.params.attr = attr; + notif.params.offset = 0; + notif.cback = cb; + + nble_gatt_send_notif_req(¬if, data, len); + + return 0; +} + +void on_nble_gatts_send_notif_rsp(const struct nble_gatt_notif_rsp *rsp) +{ + struct bt_conn *conn; + + conn = bt_conn_lookup_handle(rsp->conn_handle); + + if (conn) { + if (rsp->cback) { + rsp->cback(conn, rsp->attr, rsp->status); + } + bt_conn_unref(conn); + } +} + +void on_nble_gatts_send_ind_rsp(const struct nble_gatt_ind_rsp *rsp) +{ + struct bt_conn *conn; + + conn = bt_conn_lookup_handle(rsp->conn_handle); + + if (conn) { + if (rsp->cback) { + rsp->cback(conn, rsp->attr, rsp->status); + } + bt_conn_unref(conn); + } +} + +static int att_indicate(struct bt_conn *conn, + struct bt_gatt_indicate_params *params) +{ + struct nble_gatt_send_ind_params ind; + + ind.conn_handle = conn->handle; + ind.cback = params->func; + ind.params.attr = params->attr; + ind.params.offset = 0; + + nble_gatt_send_ind_req(&ind, params->data, params->len); + + return 0; +} + +static uint8_t notify_cb(const struct bt_gatt_attr *attr, void *user_data) +{ + struct notify_data *data = user_data; + struct _bt_gatt_ccc *ccc; + size_t i; + + /* Check if the attribute was reached */ + if (data->state == 0) { + if (attr == data->attr) + data->state = 1; + return BT_GATT_ITER_CONTINUE; + } + + if (bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CCC)) { + /* Stop if we reach the next characteristic */ + if (!bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CHRC)) { + return BT_GATT_ITER_STOP; + } + return BT_GATT_ITER_CONTINUE; + } + + /* Check attribute user_data must be of type struct _bt_gatt_ccc */ + if (attr->write != bt_gatt_attr_write_ccc) { + return BT_GATT_ITER_CONTINUE; + } + + ccc = attr->user_data; + + /* Notify all peers configured */ + for (i = 0; i < ccc->cfg_len; i++) { + struct bt_conn *conn; + int err; + + if (ccc->value != data->type) { + continue; + } + + conn = bt_conn_lookup_addr_le(&ccc->cfg[i].peer); + if (!conn || conn->state != BT_CONN_CONNECTED) {//Bug here + continue; + } + + if (data->type == BT_GATT_CCC_INDICATE) { + err = att_indicate(conn, data->params); + + } else { + err = att_notify(conn, data->attr, data->data, + data->len, data->notify_cb); + } + + bt_conn_unref(conn); + + if (err < 0) { + return BT_GATT_ITER_STOP; + } + } + + return BT_GATT_ITER_CONTINUE; +} + +int bt_gatt_notify(struct bt_conn *conn, const struct bt_gatt_attr *attr, + const void *data, uint16_t len, + bt_gatt_notify_sent_func_t cb) +{ + struct notify_data nfy; + + if (!attr) { + return -EINVAL; + } + + if (conn) { + return att_notify(conn, attr, data, len, cb); + } + + nfy.state = 0; + nfy.attr = attr; + nfy.type = BT_GATT_CCC_NOTIFY; + nfy.data = data; + nfy.len = len; + nfy.notify_cb = cb; + + bt_gatt_foreach_attr(1, 0xffff, notify_cb, &nfy); + + return 0; +} + +int bt_gatt_indicate(struct bt_conn *conn, + struct bt_gatt_indicate_params *params) +{ + struct notify_data nfy; + + if (!params || !params->attr) { + return -EINVAL; + } + + if (conn) { + return att_indicate(conn, params); + } + + nfy.state = 0; + nfy.type = BT_GATT_CCC_INDICATE; + nfy.params = params; + + bt_gatt_foreach_attr(1, 0xffff, notify_cb, &nfy); + + return 0; +} + +static uint8_t connected_cb(const struct bt_gatt_attr *attr, void *user_data) +{ + struct bt_conn *conn = user_data; + struct _bt_gatt_ccc *ccc; + size_t i; + + /* Check attribute user_data must be of type struct _bt_gatt_ccc */ + if (attr->write != bt_gatt_attr_write_ccc) { + return BT_GATT_ITER_CONTINUE; + } + + ccc = attr->user_data; + + /* If already enabled skip */ + if (ccc->value) { + return BT_GATT_ITER_CONTINUE; + } + + for (i = 0; i < ccc->cfg_len; i++) { + /* Ignore configuration for different peer */ + if (bt_addr_le_cmp(&conn->le.dst, &ccc->cfg[i].peer)) { + continue; + } + + if (ccc->cfg[i].value) { + gatt_ccc_changed(ccc); + return BT_GATT_ITER_CONTINUE; + } + } + + return BT_GATT_ITER_CONTINUE; +} + +static uint8_t disconnected_cb(const struct bt_gatt_attr *attr, void *user_data) +{ + struct bt_conn *conn = user_data; + struct _bt_gatt_ccc *ccc; + size_t i; + + /* Check attribute user_data must be of type struct _bt_gatt_ccc */ + if (attr->write != bt_gatt_attr_write_ccc) { + return BT_GATT_ITER_CONTINUE; + } + + ccc = attr->user_data; + + /* If already disabled skip */ + if (!ccc->value) { + return BT_GATT_ITER_CONTINUE; + } + + for (i = 0; i < ccc->cfg_len; i++) { + /* Ignore configurations with disabled value */ + if (!ccc->cfg[i].value) { + continue; + } + + if (bt_addr_le_cmp(&conn->le.dst, &ccc->cfg[i].peer)) { + struct bt_conn *tmp; + + /* Skip if there is another peer connected */ + tmp = bt_conn_lookup_addr_le(&ccc->cfg[i].peer); + if (tmp) { + if (tmp->state == BT_CONN_CONNECTED) { + bt_conn_unref(tmp); + return BT_GATT_ITER_CONTINUE; + } + + bt_conn_unref(tmp); + } + } + } + + /* Reset value while disconnected */ + memset(&ccc->value, 0, sizeof(ccc->value)); + + if (ccc->cfg_changed) { + ccc->cfg_changed(ccc->value); + } + + BT_DBG("ccc %p reseted", ccc); + + return BT_GATT_ITER_CONTINUE; +} + +void on_nble_gatts_write_evt(const struct nble_gatt_wr_evt *evt, + const uint8_t *buf, uint8_t buflen) +{ + const struct bt_gatt_attr *attr = evt->attr; + struct bt_conn *conn = bt_conn_lookup_handle(evt->conn_handle); + struct nble_gatts_wr_reply_params reply_data; + + //BT_DBG("write_evt %p", attr); + + /* Check for write support and flush support in case of prepare */ + if (!attr->write || + ((evt->flag & NBLE_GATT_WR_FLAG_PREP) && !attr->flush)) { + reply_data.status = BT_GATT_ERR(BT_ATT_ERR_WRITE_NOT_PERMITTED); + + goto reply; + } + //BT_DBG("%s", __FUNCTION__); + + reply_data.status = attr->write(conn, attr, buf, buflen, evt->offset); + if (reply_data.status < 0) { + + //BT_DBG("%s1-1", __FUNCTION__); + + goto reply; + } + + //BT_DBG("%s1", __FUNCTION__); + + /* Return an error if not all data has been written */ + if (reply_data.status != buflen) { + reply_data.status = BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); + + goto reply; + } + + //BT_DBG("%s2", __FUNCTION__); + + if (attr->flush && !(evt->flag & NBLE_GATT_WR_FLAG_PREP)) + reply_data.status = attr->flush(conn, attr, BT_GATT_FLUSH_SYNC); + + //BT_DBG("%s3", __FUNCTION__); + +reply: + //BT_DBG("%s4", __FUNCTION__); + if (evt->flag & NBLE_GATT_WR_FLAG_REPLY) { + reply_data.conn_handle = evt->conn_handle; + + nble_gatts_wr_reply_req(&reply_data); + } + + if (conn) + bt_conn_unref(conn); +} + +static uint8_t flush_all(const struct bt_gatt_attr *attr, void *user_data) +{ + struct ble_gatts_flush_all *flush_data = user_data; + + if (attr->flush) { + int status = attr->flush(flush_data->conn, attr, flush_data->flag); + + if (status < 0 && flush_data->status == 0) + flush_data->status = status; + } + + return BT_GATT_ITER_CONTINUE; +} + +void on_nble_gatts_write_exec_evt(const struct nble_gatt_wr_exec_evt *evt) +{ + BT_DBG("write_exec_evt"); + + struct ble_gatts_flush_all flush_data = { + .conn = bt_conn_lookup_handle(evt->conn_handle), + .flag = evt->flag, + .status = 0, + }; + + bt_gatt_foreach_attr(0x0001, 0xFFFF, flush_all, &flush_data); + + struct nble_gatts_wr_reply_params reply_data = { + .conn_handle = evt->conn_handle, + .status = flush_data.status, + }; + nble_gatts_wr_reply_req(&reply_data); + + bt_conn_unref(flush_data.conn); +} + +void on_nble_gatts_read_evt(const struct nble_gatt_rd_evt *evt) +{ + struct nble_gatts_rd_reply_params reply_data; + const struct bt_gatt_attr *attr; + /* The length of the value sent back in the response is unknown because + * of NRF API limitation, so we use the max possible one: ATT_MTU-1 */ + uint8_t data[BLE_GATT_MTU_SIZE - 1] = { 0 }; + int len; + + attr = evt->attr; + + BT_DBG("read_evt %p", attr); + + memset(&reply_data, 0, sizeof(reply_data)); + + if (attr->read) { + struct bt_conn *conn = bt_conn_lookup_handle(evt->conn_handle); + + len = attr->read(conn, attr, data, sizeof(data), evt->offset); + + if (conn) + bt_conn_unref(conn); + + } else { + len = BT_GATT_ERR(BT_ATT_ERR_READ_NOT_PERMITTED); + } + + /* status >= 0 is considered as success by nble */ + reply_data.status = len; + + if (len < 0) { + len = 0; + } + + reply_data.conn_handle = evt->conn_handle; + + /* offset is needed by nble even in error case */ + reply_data.offset = evt->offset; + + nble_gatts_rd_reply_req(&reply_data, data, len); +} + +#if defined(CONFIG_BLUETOOTH_GATT_CLIENT) +void on_nble_gattc_value_evt(const struct ble_gattc_value_evt *evt, + uint8_t *data, uint8_t length) +{ + struct bt_gatt_subscribe_params *params; + struct bt_conn *conn; + + conn = bt_conn_lookup_handle(evt->conn_handle); + + //BT_DBG("FUNC %s", __FUNCTION__); + + if (conn) { + for (params = subscriptions; params; params = params->_next) { + if (evt->handle != params->value_handle) { + continue; + } + + if (params->notify(conn, params, data, length) == + BT_GATT_ITER_STOP) { + bt_gatt_unsubscribe(conn, params); + } + } + bt_conn_unref(conn); + } +} + +static void gatt_subscription_remove(struct bt_conn *conn, + struct bt_gatt_subscribe_params *prev, + struct bt_gatt_subscribe_params *params) +{ + /* Remove subscription from the list*/ + if (!prev) { + subscriptions = params->_next; + } else { + prev->_next = params->_next; + } + + params->notify(conn, params, NULL, 0); +} + +static void remove_subscribtions(struct bt_conn *conn) +{ + struct bt_gatt_subscribe_params *params, *prev; + + /* Lookup existing subscriptions */ + for (params = subscriptions, prev = NULL; params; + prev = params, params = params->_next) { + if (bt_addr_le_cmp(¶ms->_peer, &conn->le.dst)) { + continue; + } + + /* Remove subscription */ + gatt_subscription_remove(conn, prev, params); + } +} + +int bt_gatt_exchange_mtu(struct bt_conn *conn, bt_gatt_rsp_func_t func) +{ + return -EINVAL; +} + +void on_nble_gattc_discover_rsp(const struct nble_gattc_discover_rsp *rsp, + const uint8_t *data, uint8_t data_len) +{ + size_t i; + uint8_t attr_count; + uint16_t last_handle; + int status = BT_GATT_ITER_STOP; + struct bt_gatt_discover_params *params; + struct bt_gatt_service svc_value; + struct bt_gatt_include inc_value; + struct bt_conn *conn = bt_conn_lookup_handle(rsp->conn_handle); + + BT_ASSERT(conn); + + params = rsp->user_data; + + BT_DBG("disc_rsp: s=%d", rsp->status); + + if (rsp->status) + goto complete; + + if (rsp->type == BT_GATT_DISCOVER_PRIMARY) { + attr_count = (data_len / sizeof(struct nble_gattc_primary)); + } else if (rsp->type == BT_GATT_DISCOVER_INCLUDE) { + attr_count = (data_len / sizeof(struct nble_gattc_included)); + } else if (rsp->type == BT_GATT_DISCOVER_CHARACTERISTIC) { + attr_count = (data_len / sizeof(struct nble_gattc_characteristic)); + } else if (rsp->type == BT_GATT_DISCOVER_DESCRIPTOR) { + attr_count = (data_len / sizeof(struct nble_gattc_descriptor)); + } else + goto complete; + BT_DBG("disc_rsp: c=%d", attr_count); + last_handle = params->end_handle; + for (i = 0; i < attr_count; i++) { + struct bt_gatt_attr *attr = NULL; + + if (rsp->type == BT_GATT_DISCOVER_PRIMARY) { + const struct nble_gattc_primary *gattr = + (void *)&data[i * sizeof(*gattr)]; + if ((gattr->range.start_handle < params->start_handle) && + (gattr->range.end_handle > params->end_handle)) { + /* + * Only the attributes with attribute handles + * between and including the Starting + * Handle and the Ending Handle is returned + */ + goto complete; + } + svc_value.end_handle = gattr->range.end_handle; + svc_value.uuid = params->uuid; + attr = (&(struct bt_gatt_attr)BT_GATT_PRIMARY_SERVICE(&svc_value)); + attr->handle = gattr->handle; + last_handle = svc_value.end_handle; + } else if (rsp->type == BT_GATT_DISCOVER_INCLUDE) { + const struct nble_gattc_included *gattr = + (void *)&data[i * sizeof(*gattr)]; + + inc_value.start_handle = gattr->range.start_handle; + inc_value.end_handle = gattr->range.end_handle; + /* + * 4.5.1 If the service UUID is a 16-bit Bluetooth UUID + * it is also returned in the response. + */ + switch (gattr->uuid.uuid.type) { + case BT_UUID_TYPE_16: + inc_value.uuid = &gattr->uuid.uuid; + break; + case BT_UUID_TYPE_128: + /* Data is not available at this point */ + break; + } + attr = (&(struct bt_gatt_attr) + BT_GATT_INCLUDE_SERVICE(&inc_value)); + attr->handle = gattr->handle; + last_handle = gattr->handle; + } else if (rsp->type == BT_GATT_DISCOVER_CHARACTERISTIC) { + const struct nble_gattc_characteristic *gattr = + (void *)&data[i * sizeof(*gattr)]; + attr = (&(struct bt_gatt_attr) + BT_GATT_CHARACTERISTIC(&gattr->uuid.uuid, + gattr->prop)); + attr->handle = gattr->handle; + last_handle = gattr->handle; + /* Skip if UUID is set but doesn't match */ + if (params->uuid && bt_uuid_cmp(&gattr->uuid.uuid, params->uuid)) { + continue; + } + } else if (rsp->type == BT_GATT_DISCOVER_DESCRIPTOR) { + const struct nble_gattc_descriptor *gattr = + (void *)&data[i * sizeof(*gattr)]; + + attr = (&(struct bt_gatt_attr) + BT_GATT_DESCRIPTOR(&gattr->uuid.uuid, 0, NULL, NULL, NULL)); + attr->handle = gattr->handle; + last_handle = gattr->handle; + } else { + /* Error case */ + goto complete; + } + status = params->func(conn, attr, params); + if (status == BT_GATT_ITER_STOP) { + /* Not required to call complete */ + goto done; + } + } + if (last_handle < UINT16_MAX) { + last_handle++; + } + BT_DBG("disc_rsp: l=%d", last_handle); + params->start_handle = last_handle; + if (params->start_handle < params->end_handle) { + if (!bt_gatt_discover(conn, params)) + goto not_done; + } + +complete: + /* Indicate that there are no more attributes found */ + params->func(conn, NULL, params); + +done: + BT_DBG("disc_rsp: done"); + +not_done: + bt_conn_unref(conn); +} + +int bt_gatt_discover(struct bt_conn *conn, + struct bt_gatt_discover_params *params) +{ + struct nble_discover_params discover_params; + + if (!conn || !params || !params->func || !params->start_handle || + !params->end_handle || params->start_handle > params->end_handle) { + return -EINVAL; + } + + if (conn->state != BT_CONN_CONNECTED) { + return -EINVAL; + } + + BT_DBG("disc: %d", params->start_handle); + + memset(&discover_params, 0, sizeof(discover_params)); + + switch (params->type) { + case BT_GATT_DISCOVER_PRIMARY: + case BT_GATT_DISCOVER_CHARACTERISTIC: + if (params->uuid) { + /* Always copy a full 128 bit UUID */ + discover_params.uuid = *BT_UUID_128(params->uuid); + discover_params.flags = DISCOVER_FLAGS_UUID_PRESENT; + } + break; + + case BT_GATT_DISCOVER_INCLUDE: + case BT_GATT_DISCOVER_DESCRIPTOR: + break; + default: + return -EINVAL; + } + + discover_params.conn_handle = conn->handle; + discover_params.type = params->type; + discover_params.handle_range.start_handle = params->start_handle; + discover_params.handle_range.end_handle = params->end_handle; + + discover_params.user_data = params; + + nble_gattc_discover_req(&discover_params); + + return 0; +} + +void on_nble_gattc_read_multiple_rsp(const struct ble_gattc_read_rsp *rsp, + uint8_t *pdu, uint8_t length) +{ + struct bt_gatt_read_params *params; + struct bt_conn *conn = bt_conn_lookup_handle(rsp->conn_handle); + + BT_ASSERT(conn); + + params = rsp->user_data; + + BT_DBG("err 0x%02x", rsp->status); + + if (rsp->status) { + params->func(conn, rsp->status, params, NULL, 0); + bt_conn_unref(conn); + return; + } + + params->func(conn, 0, params, pdu, length); + + /* mark read as complete since read multiple is single response */ + params->func(conn, 0, params, NULL, 0); + + bt_conn_unref(conn); +} + +void on_nble_gattc_read_rsp(const struct ble_gattc_read_rsp *rsp, + uint8_t *pdu, uint8_t length) +{ + struct bt_gatt_read_params *params; + struct bt_conn *conn = bt_conn_lookup_handle(rsp->conn_handle); + + BT_ASSERT(conn); + + params = rsp->user_data; + + if (rsp->status) { + params->func(conn, rsp->status, params, NULL, 0); + bt_conn_unref(conn); + return; + } + + if (params->func(conn, 0, params, pdu, length) == BT_GATT_ITER_STOP) { + bt_conn_unref(conn); + return; + } + + /* + * Core Spec 4.2, Vol. 3, Part G, 4.8.1 + * If the Characteristic Value is greater than (ATT_MTU – 1) octets + * in length, the Read Long Characteristic Value procedure may be used + * if the rest of the Characteristic Value is required. + */ + if (length < BLE_GATT_MTU_SIZE - 1) { + params->func(conn, 0, params, NULL, 0); + bt_conn_unref(conn); + return; + } + + params->single.offset += length; + + /* Continue reading the attribute */ + if (bt_gatt_read(conn, params)) { + params->func(conn, BT_ATT_ERR_UNLIKELY, params, NULL, 0); + } + bt_conn_unref(conn); +} + +int bt_gatt_read(struct bt_conn *conn, struct bt_gatt_read_params *params) +{ + + struct ble_gattc_read_params single_req; + struct ble_gattc_read_multiple_params mutiple_req; + + if (!conn || conn->state != BT_CONN_CONNECTED || !params || + params->handle_count == 0 || !params->func) { + return -EINVAL; + } + + single_req.conn_handle = conn->handle; + + if (1 == params->handle_count) { + single_req.handle = params->single.handle; + single_req.offset = params->single.offset; + single_req.user_data = params; + + nble_gattc_read_req(&single_req); + } else { + mutiple_req.conn_handle = conn->handle; + mutiple_req.user_data = params; + + nble_gattc_read_multiple_req(&mutiple_req, params->handles, 2 * params->handle_count); + } + return 0; +} + +static void on_write_no_rsp_complete(struct bt_conn *conn, uint8_t err, + const void *data) +{ +} + +static void on_write_complete(struct bt_conn *conn, uint8_t err, + const struct bt_gatt_write_params *wr_params) +{ + bt_gatt_write_rsp_func_t func = wr_params->user_data[0]; + const void *data = wr_params->user_data[1]; + + BT_ASSERT(func); + func(conn, err, data); +} + +static int _bt_gatt_write(struct bt_conn *conn, uint16_t handle, bool with_resp, + uint16_t offset, const void *data, uint16_t length, + struct bt_gatt_write_params *wr_params) +{ + struct ble_gattc_write_params req; + + req.conn_handle = conn->handle; + req.handle = handle; + req.offset = offset; + req.with_resp = with_resp; + req.wr_params = *wr_params; + + nble_gattc_write_req(&req, data, length); + + return 0; +} + +void on_nble_gattc_write_rsp(const struct ble_gattc_write_rsp *rsp) +{ + + struct bt_conn *conn = bt_conn_lookup_handle(rsp->conn_handle); + + BT_ASSERT(conn); + + if (rsp->wr_params.func) { + rsp->wr_params.func(conn, rsp->status, &rsp->wr_params); + } + + bt_conn_unref(conn); +} + + +int bt_gatt_write_without_response(struct bt_conn *conn, uint16_t handle, + const void *data, uint16_t length, bool sign) +{ + return bt_gatt_write(conn, handle, 0, data, length, + on_write_no_rsp_complete); +} + +int bt_gatt_write(struct bt_conn *conn, uint16_t handle, uint16_t offset, + const void *data, uint16_t length, + bt_gatt_write_rsp_func_t func) +{ + struct bt_gatt_write_params wr_params; + + if (!conn || conn->state != BT_CONN_CONNECTED || !handle || !func) { + return -EINVAL; + } + + wr_params.func = on_write_complete; + wr_params.user_data[0] = func; + wr_params.user_data[1] = (void *)data; + + return _bt_gatt_write(conn, handle, + (func == on_write_no_rsp_complete) + ? false : true, + offset, data, length, &wr_params); +} + +static void gatt_subscription_add(struct bt_conn *conn, + struct bt_gatt_subscribe_params *params) +{ + bt_addr_le_copy(¶ms->_peer, &conn->le.dst); + + /* Prepend subscription */ + params->_next = subscriptions; + subscriptions = params; +} + +static void att_write_ccc_rsp(struct bt_conn *conn, uint8_t err, + const struct bt_gatt_write_params *wr_params) +{ + struct bt_gatt_subscribe_params *params = wr_params->user_data[0]; + + /* if write to CCC failed we remove subscription and notify app */ + if (err) { + struct bt_gatt_subscribe_params *cur, *prev; + + for (cur = subscriptions, prev = NULL; cur; + prev = cur, cur = cur->_next) { + + if (cur == params) { + gatt_subscription_remove(conn, prev, params); + break; + } + } + } +} + +static int gatt_write_ccc(struct bt_conn *conn, uint16_t handle, uint16_t value, + bt_att_func_t func, + struct bt_gatt_subscribe_params *params) +{ + struct bt_gatt_write_params wr_params; + + wr_params.func = func; + wr_params.user_data[0] = params; + + BT_DBG("handle 0x%04x value 0x%04x", handle, value); + + return _bt_gatt_write(conn, handle, true, 0, &value, sizeof(value), + &wr_params); +} + +int bt_gatt_subscribe(struct bt_conn *conn, + struct bt_gatt_subscribe_params *params) +{ + struct bt_gatt_subscribe_params *tmp; + bool has_subscription = false; + + if (!conn || conn->state != BT_CONN_CONNECTED) { + return -ENOTCONN; + } + + if (!params || !params->notify || + !params->value || !params->ccc_handle) { + return -EINVAL; + } + + /* Lookup existing subscriptions */ + for (tmp = subscriptions; tmp; tmp = tmp->_next) { + /* Fail if entry already exists */ + if (tmp == params) { + return -EALREADY; + } + + /* Check if another subscription exists */ + if (!bt_addr_le_cmp(&tmp->_peer, &conn->le.dst) && + tmp->value_handle == params->value_handle && + tmp->value >= params->value) { + has_subscription = true; + } + } + + /* Skip write if already subscribed */ + if (!has_subscription) { + int err; + + err = gatt_write_ccc(conn, params->ccc_handle, params->value, + att_write_ccc_rsp, params); + if (err) { + return err; + } + } + + /* + * Add subscription before write complete as some implementation were + * reported to send notification before reply to CCC write. + */ + gatt_subscription_add(conn, params); + + return 0; +} + +int bt_gatt_unsubscribe(struct bt_conn *conn, + struct bt_gatt_subscribe_params *params) +{ + struct bt_gatt_subscribe_params *tmp; + bool has_subscription = false, found = false; + + if (!conn || conn->state != BT_CONN_CONNECTED) { + return -ENOTCONN; + } + + if (!params) { + return -EINVAL; + } + + /* Check head */ + if (subscriptions == params) { + subscriptions = params->_next; + found = true; + } + + /* Lookup existing subscriptions */ + for (tmp = subscriptions; tmp; tmp = tmp->_next) { + /* Remove subscription */ + if (tmp->_next == params) { + tmp->_next = params->_next; + found = true; + } + + /* Check if there still remains any other subscription */ + if (!bt_addr_le_cmp(&tmp->_peer, &conn->le.dst) && + tmp->value_handle == params->value_handle) { + has_subscription = true; + } + } + + if (!found) { + return -EINVAL; + } + + if (has_subscription) { + return 0; + } + + params->value = 0; + + return gatt_write_ccc(conn, params->ccc_handle, params->value, NULL, + NULL); +} + +static void add_subscriptions(struct bt_conn *conn) +{ + struct bt_gatt_subscribe_params *params, *prev; + + /* Lookup existing subscriptions */ + for (params = subscriptions, prev = NULL; params; + prev = params, params = params->_next) { + if (bt_addr_le_cmp(¶ms->_peer, &conn->le.dst)) { + continue; + } + + /* Force write to CCC to workaround devices that don't track + * it properly. + */ + gatt_write_ccc(conn, params->ccc_handle, params->value, + att_write_ccc_rsp, params); + } +} +#else + +void on_nble_gattc_discover_rsp(const struct nble_gattc_discover_rsp *rsp, + const uint8_t *data, uint8_t data_len) +{ + +} +void on_nble_gattc_write_rsp(const struct ble_gattc_write_rsp *rsp) +{ +} + +void on_nble_gattc_value_evt(const struct ble_gattc_value_evt *evt, + uint8_t *buf, uint8_t buflen) +{ +} + +void on_nble_gattc_read_rsp(const struct ble_gattc_read_rsp *rsp, + uint8_t *data, uint8_t data_len) +{ +} + +void on_nble_gattc_read_multiple_rsp(const struct ble_gattc_read_rsp *rsp, + uint8_t *data, uint8_t data_len) +{ +} + +#endif + +void bt_gatt_connected(struct bt_conn *conn) +{ + BT_DBG("conn %p", conn); + bt_gatt_foreach_attr(0x0001, 0xffff, connected_cb, conn); +#if defined(CONFIG_BLUETOOTH_GATT_CLIENT) + add_subscriptions(conn); +#endif /* CONFIG_BLUETOOTH_GATT_CLIENT */ +} + +void bt_gatt_disconnected(struct bt_conn *conn) +{ + BT_DBG("conn %p", conn); + bt_gatt_foreach_attr(0x0001, 0xffff, disconnected_cb, conn); + +#if defined(CONFIG_BLUETOOTH_GATT_CLIENT) + /* If bonded don't remove subscriptions */ + if (is_bonded(&conn->le.dst)) { + return; + } + + remove_subscribtions(conn); +#endif /* CONFIG_BLUETOOTH_GATT_CLIENT */ +} diff --git a/system/libarc32_arduino101/framework/src/services/ble/hci_core.h b/system/libarc32_arduino101/framework/src/services/ble/hci_core.h new file mode 100644 index 00000000..dc7374b0 --- /dev/null +++ b/system/libarc32_arduino101/framework/src/services/ble/hci_core.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2016 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* State tracking for the local Bluetooth controller */ +struct bt_dev { + atomic_t flags[1]; +}; +extern struct bt_dev bt_dev; + +#if defined(CONFIG_BLUETOOTH_SMP) || defined(CONFIG_BLUETOOTH_BREDR) +extern const struct bt_conn_auth_cb *bt_auth; +#endif /* CONFIG_BLUETOOTH_SMP || CONFIG_BLUETOOTH_BREDR */ + +static inline bool bt_addr_le_is_rpa(const bt_addr_le_t *addr) +{ + if (addr->type != BT_ADDR_LE_RANDOM) + return false; + + if ((addr->val[5] & 0xc0) == 0x40) + return true; + + return false; +} + +static inline bool bt_addr_le_is_identity(const bt_addr_le_t *addr) +{ + if (addr->type == BT_ADDR_LE_PUBLIC) + return true; + + /* Check for Random Static address type */ + if ((addr->val[5] & 0xc0) == 0xc0) + return true; + + return false; +} + +static inline bool bt_le_conn_params_valid(uint16_t min, uint16_t max, + uint16_t latency, uint16_t timeout) +{ + if (min > max || min < 6 || max > 3200) { + return false; + } + + /* Limits according to BT Core spec 4.2 [Vol 2, Part E, 7.8.12] */ + if (timeout < 10 || timeout > 3200) { + return false; + } + + /* Limits according to BT Core spec 4.2 [Vol 6, Part B, 4.5.1] */ + if (latency > 499 || ((latency + 1) * max) > (timeout * 4)) { + return false; + } + + return true; +} + + diff --git a/system/libarc32_arduino101/framework/src/services/ble/l2cap.c b/system/libarc32_arduino101/framework/src/services/ble/l2cap.c new file mode 100644 index 00000000..488cfd0e --- /dev/null +++ b/system/libarc32_arduino101/framework/src/services/ble/l2cap.c @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2016 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include + +#include "conn_internal.h" +#include "gatt_internal.h" +#include "smp.h" + +/* #define BT_GATT_DEBUG 1 */ + +extern void on_nble_curie_log(char *fmt, ...); +extern void __assert_fail(void); +#ifdef BT_GATT_DEBUG +#define BT_DBG(fmt, ...) on_nble_curie_log(fmt, ##__VA_ARGS__) +#define BT_ERR(fmt, ...) on_nble_curie_log(fmt, ##__VA_ARGS__) +#define BT_WARN(fmt, ...) on_nble_curie_log(fmt, ##__VA_ARGS__) +#define BT_INFO(fmt, ...) on_nble_curie_log(fmt, ##__VA_ARGS__) +#define BT_ASSERT(cond) ((cond) ? (void)0 : __assert_fail()) +#else +#define BT_DBG(fmt, ...) do {} while (0) +#define BT_ERR(fmt, ...) on_nble_curie_log(fmt, ##__VA_ARGS__) +#define BT_WARN(fmt, ...) on_nble_curie_log(fmt, ##__VA_ARGS__) +#define BT_INFO(fmt, ...) on_nble_curie_log(fmt, ##__VA_ARGS__) +#define BT_ASSERT(cond) ((cond) ? (void)0 : __assert_fail()) +#endif + +void bt_l2cap_connected(struct bt_conn *conn) +{ + /* TODO Add support for fixed channels on BR/EDR transport */ + if (conn->type != BT_CONN_TYPE_LE) { + return; + } +#if defined(CONFIG_BLUETOOTH_SMP) + bt_smp_connected(conn); +#endif + bt_gatt_connected(conn); +} + +void bt_l2cap_disconnected(struct bt_conn *conn) +{ +#if defined(CONFIG_BLUETOOTH_SMP) + bt_smp_disconnected(conn); +#endif + bt_gatt_disconnected(conn); +} diff --git a/system/libarc32_arduino101/framework/src/services/ble/l2cap_internal.h b/system/libarc32_arduino101/framework/src/services/ble/l2cap_internal.h new file mode 100644 index 00000000..2f593fa5 --- /dev/null +++ b/system/libarc32_arduino101/framework/src/services/ble/l2cap_internal.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2016 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef L2CAP_INTERNAL_H_ +#define L2CAP_INTERNAL_H_ + +/* Notify L2CAP channels of a new connection */ +void bt_l2cap_connected(struct bt_conn *conn); + +/* Notify L2CAP channels of a disconnect event */ +void bt_l2cap_disconnected(struct bt_conn *conn); + + +#endif /* L2CAP_INTERNAL_H_ */ diff --git a/system/libarc32_arduino101/framework/src/services/ble/smp.h b/system/libarc32_arduino101/framework/src/services/ble/smp.h new file mode 100644 index 00000000..4f52c002 --- /dev/null +++ b/system/libarc32_arduino101/framework/src/services/ble/smp.h @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2016 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define BT_SMP_ERR_PASSKEY_ENTRY_FAILED 0x01 +#define BT_SMP_ERR_OOB_NOT_AVAIL 0x02 +#define BT_SMP_ERR_AUTH_REQUIREMENTS 0x03 +#define BT_SMP_ERR_CONFIRM_FAILED 0x04 +#define BT_SMP_ERR_PAIRING_NOTSUPP 0x05 +#define BT_SMP_ERR_ENC_KEY_SIZE 0x06 +#define BT_SMP_ERR_CMD_NOTSUPP 0x07 +#define BT_SMP_ERR_UNSPECIFIED 0x08 +#define BT_SMP_ERR_REPEATED_ATTEMPTS 0x09 +#define BT_SMP_ERR_INVALID_PARAMS 0x0a +#define BT_SMP_ERR_DHKEY_CHECK_FAILED 0x0b +#define BT_SMP_ERR_NUMERIC_COMP_FAILED 0x0c +#define BT_SMP_ERR_BREDR_PAIRING_IN_PROGRESS 0x0d +#define BT_SMP_ERR_CROSS_TRANSP_NOT_ALLOWED 0x0e + +#define BT_SMP_IO_DISPLAY_ONLY 0x00 +#define BT_SMP_IO_DISPLAY_YESNO 0x01 +#define BT_SMP_IO_KEYBOARD_ONLY 0x02 +#define BT_SMP_IO_NO_INPUT_OUTPUT 0x03 +#define BT_SMP_IO_KEYBOARD_DISPLAY 0x04 + +#define BT_SMP_OOB_NOT_PRESENT 0x00 +#define BT_SMP_OOB_PRESENT 0x01 + +#define BT_SMP_MIN_ENC_KEY_SIZE 7 +#define BT_SMP_MAX_ENC_KEY_SIZE 16 + +#define BT_SMP_DIST_ENC_KEY 0x01 +#define BT_SMP_DIST_ID_KEY 0x02 +#define BT_SMP_DIST_SIGN 0x04 +#define BT_SMP_DIST_LINK_KEY 0x08 + +#define BT_SMP_DIST_MASK 0x0f + +#define BT_SMP_AUTH_NONE 0x00 +#define BT_SMP_AUTH_BONDING 0x01 +#define BT_SMP_AUTH_MITM 0x04 +#define BT_SMP_AUTH_SC 0x08 + +#if 0 +bool bt_smp_irk_matches(const uint8_t irk[16], const bt_addr_t *addr); +#endif +int bt_smp_send_pairing_req(struct bt_conn *conn); +int bt_smp_send_security_req(struct bt_conn *conn); +#if 0 +void bt_smp_update_keys(struct bt_conn *conn); +bool bt_smp_get_tk(struct bt_conn *conn, uint8_t *tk); + +void bt_smp_dhkey_ready(const uint8_t *dhkey); +void bt_smp_pkey_ready(void); +#endif + +int bt_smp_init(void); + +int bt_smp_auth_passkey_entry(struct bt_conn *conn, unsigned int passkey); +int bt_smp_auth_passkey_confirm(struct bt_conn *conn, bool match); +int bt_smp_auth_cancel(struct bt_conn *conn); + +int bt_smp_remove_info(const bt_addr_le_t *addr); + +#ifdef CONFIG_BLUETOOTH_SMP +void bt_smp_connected(struct bt_conn *conn); +void bt_smp_disconnected(struct bt_conn *conn); +#endif + +#if 0 +/** brief Verify signed message + * + * @param conn Bluetooth connection + * @param buf received packet buffer with message and signature + * + * @return 0 in success, error code otherwise + */ +int bt_smp_sign_verify(struct bt_conn *conn, struct net_buf *buf); + +/** brief Sign message + * + * @param conn Bluetooth connection + * @param buf message buffer + * + * @return 0 in success, error code otherwise + */ +int bt_smp_sign(struct bt_conn *conn, struct net_buf *buf); +#endif diff --git a/system/libarc32_arduino101/framework/src/services/ble/smp_null.c b/system/libarc32_arduino101/framework/src/services/ble/smp_null.c new file mode 100644 index 00000000..cdc3a4d5 --- /dev/null +++ b/system/libarc32_arduino101/framework/src/services/ble/smp_null.c @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2016 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include "smp.h" + +/* nble internal APIs */ +#include "gap_internal.h" + +/* #define BT_GATT_DEBUG 1 */ + +extern void on_nble_curie_log(char *fmt, ...); +extern void __assert_fail(void); +#ifdef BT_GATT_DEBUG +#define BT_DBG(fmt, ...) on_nble_curie_log(fmt, ##__VA_ARGS__) +#define BT_ERR(fmt, ...) on_nble_curie_log(fmt, ##__VA_ARGS__) +#define BT_WARN(fmt, ...) on_nble_curie_log(fmt, ##__VA_ARGS__) +#define BT_INFO(fmt, ...) on_nble_curie_log(fmt, ##__VA_ARGS__) +#define BT_ASSERT(cond) ((cond) ? (void)0 : __assert_fail()) +#else +#define BT_DBG(fmt, ...) do {} while (0) +#define BT_ERR(fmt, ...) on_nble_curie_log(fmt, ##__VA_ARGS__) +#define BT_WARN(fmt, ...) on_nble_curie_log(fmt, ##__VA_ARGS__) +#define BT_INFO(fmt, ...) on_nble_curie_log(fmt, ##__VA_ARGS__) +#define BT_ASSERT(cond) ((cond) ? (void)0 : __assert_fail()) +#endif + +/** + * Compile time IO capabilities and OOB support settings for nble. + * + * This compile options must match the application registered callbacks + * (bt_conn_auth_cb_register) + */ +#define NBLE_SMP_IO_CAPS BT_SMP_IO_NO_INPUT_OUTPUT +#define NBLE_SMP_AUTH_OPTIONS 0 +#define NBLE_SMP_OBB_PRESENT BT_SMP_OOB_NOT_PRESENT + +#if NOT_USED_FOR_NOW +int bt_smp_sign_verify(struct bt_conn *conn, struct net_buf *buf) +{ + return -ENOTSUP; +} + +int bt_smp_sign(struct bt_conn *conn, struct net_buf *buf) +{ + return -ENOTSUP; +} +#endif + +void on_nble_gap_sm_bond_info_rsp(const struct nble_gap_sm_bond_info_rsp *rsp, + const bt_addr_le_t *peer_addr, uint16_t len) +{ + /* stub */ +} + +void on_nble_gap_sm_passkey_req_evt(const struct nble_gap_sm_passkey_req_evt * p_evt) +{ + /* stub */ +} + +void on_nble_gap_sm_passkey_display_evt( + const struct nble_gap_sm_passkey_disp_evt *p_evt) +{ + /* stub */ +} + +void on_nble_gap_sm_status_evt(const struct nble_gap_sm_status_evt *evt) +{ + /* stub */ + BT_INFO("nble_gap_sm_status_evt: %d, gap_status: %d", + evt->evt_type, evt->status); +} + +int bt_smp_init(void) +{ + struct nble_gap_sm_config_params params = { + .options = NBLE_SMP_AUTH_OPTIONS, + .io_caps = NBLE_SMP_IO_CAPS, + .key_size = BT_SMP_MAX_ENC_KEY_SIZE, + .oob_present = NBLE_SMP_OBB_PRESENT, + }; + + nble_gap_sm_config_req(¶ms); + + return 0; +} + +void on_nble_gap_sm_config_rsp(struct nble_gap_sm_config_rsp *p_params) +{ + if (p_params->status) { + BT_ERR("sm_config failed: %d", p_params->status); + } +} + +void on_nble_gap_sm_common_rsp(const struct nble_gap_sm_response *rsp) +{ + if (rsp->status) { + BT_WARN("gap sm request failed: %d", rsp->status); + } +} diff --git a/system/libarc32_arduino101/framework/src/services/ble/uuid.c b/system/libarc32_arduino101/framework/src/services/ble/uuid.c new file mode 100644 index 00000000..dbacfd94 --- /dev/null +++ b/system/libarc32_arduino101/framework/src/services/ble/uuid.c @@ -0,0 +1,135 @@ +/* uuid.c - Bluetooth UUID handling */ + +/* + * Copyright (c) 2015 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#include +//#include +#include +#include +#include +#include +#include + +#include + +#if defined(CONFIG_BLUETOOTH_DEBUG) +#include +#endif /* CONFIG_BLUETOOTH_DEBUG */ + +#define UUID_16_BASE_OFFSET 12 + +/* TODO: Decide whether to continue using BLE format or switch to RFC 4122 */ + +/* Base UUID : 0000[0000]-0000-1000-8000-00805F9B34FB -> + * { 0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, + * 0x00, 0x10, 0x00, 0x00, [0x00, 0x00], 0x00, 0x00 } + * 0x2800 : 0000[2800]-0000-1000-8000-00805F9B34FB -> + * { 0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, + * 0x00, 0x10, 0x00, 0x00, [0x00, 0x28], 0x00, 0x00 } + * little endian 0x2800 : [00 28] -> no swapping required + * big endian 0x2800 : [28 00] -> swapping required + */ +static const struct bt_uuid_128 uuid128_base = { + .uuid.type = BT_UUID_TYPE_128, + .val = { 0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, +}; + +static inline void u16_to_uuid128(void *dst, uint16_t u16) +{ +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + u16 = bswap_16(u16); +#endif + memcpy(dst, &u16, 2); +} + +static void uuid_to_uuid128(const struct bt_uuid *src, struct bt_uuid_128 *dst) +{ + switch (src->type) { + case BT_UUID_TYPE_16: + *dst = uuid128_base; + u16_to_uuid128(&dst->val[UUID_16_BASE_OFFSET], + BT_UUID_16(src)->val); + return; + case BT_UUID_TYPE_128: + memcpy(dst, src, sizeof(*dst)); + return; + } +} + +static int uuid128_cmp(const struct bt_uuid *u1, const struct bt_uuid *u2) +{ + struct bt_uuid_128 uuid1, uuid2; + + uuid_to_uuid128(u1, &uuid1); + uuid_to_uuid128(u2, &uuid2); + + return memcmp(uuid1.val, uuid2.val, 16); +} + +int bt_uuid_cmp(const struct bt_uuid *u1, const struct bt_uuid *u2) +{ + /* Convert to 128 bit if types don't match */ + if (u1->type != u2->type) + return uuid128_cmp(u1, u2); + + switch (u1->type) { + case BT_UUID_TYPE_16: + return (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); + } + + return -EINVAL; +} + +#if defined(CONFIG_BLUETOOTH_DEBUG) +void bt_uuid_to_str(const struct bt_uuid *uuid, char *str, size_t len) +{ + uint32_t tmp1, tmp5; + uint16_t tmp0, tmp2, tmp3, tmp4; + + switch (uuid->type) { + case BT_UUID_TYPE_16: + snprintf(str, len, "%.4x", BT_UUID_16(uuid)->val); + break; + case BT_UUID_TYPE_128: + memcpy(&tmp0, &BT_UUID_128(uuid)->val[0], sizeof(tmp0)); + memcpy(&tmp1, &BT_UUID_128(uuid)->val[2], sizeof(tmp1)); + memcpy(&tmp2, &BT_UUID_128(uuid)->val[6], sizeof(tmp2)); + memcpy(&tmp3, &BT_UUID_128(uuid)->val[8], sizeof(tmp3)); + memcpy(&tmp4, &BT_UUID_128(uuid)->val[10], sizeof(tmp4)); + memcpy(&tmp5, &BT_UUID_128(uuid)->val[12], sizeof(tmp5)); + + snprintf(str, len, "%.8x-%.4x-%.4x-%.4x-%.8x%.4x", + tmp5, tmp4, tmp3, tmp2, tmp1, tmp0); + break; + default: + memset(str, 0, len); + return; + } +} + +const char *bt_uuid_str(const struct bt_uuid *uuid) +{ + static char str[37]; + + bt_uuid_to_str(uuid, str, sizeof(str)); + + return str; +} +#endif /* CONFIG_BLUETOOTH_DEBUG */ diff --git a/system/libarc32_arduino101/framework/include/services/ble/ble_service_msg.h b/system/libarc32_arduino101/framework/src/services/ble_service/ble_protocol.h similarity index 70% rename from system/libarc32_arduino101/framework/include/services/ble/ble_service_msg.h rename to system/libarc32_arduino101/framework/src/services/ble_service/ble_protocol.h index 6893731a..51109f9d 100644 --- a/system/libarc32_arduino101/framework/include/services/ble/ble_service_msg.h +++ b/system/libarc32_arduino101/framework/src/services/ble_service/ble_protocol.h @@ -28,23 +28,30 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#ifndef __BLE_SERVICE_MSG_H__ -#define __BLE_SERVICE_MSG_H__ +#ifndef __BLE_PROTOCOL_H__ +#define __BLE_PROTOCOL_H__ -#include "services/services_ids.h" - -/* - * CFW Message ID base definitions for BLE services. +/** + * @defgroup ble_protocol BLE protocol definitions + * + * BT Spec definitions. + * @ingroup ble_service + * @{ + * + * Bluetooth SIG defined macros and enum extracted from Bluetooth Spec 4.2 */ +#define BLE_MAX_DEVICE_NAME 20 /**< Max BLE device name length 20 + NULL, spec size: 248 */ +#define BLE_MAX_ADV_SIZE 31 + +#define BLE_GATT_MTU_SIZE 23 /**< Default MTU size */ + +/** Manufacturer IDs */ +#define INTEL_MANUFACTURER 0x0002 -/* BLE Service Message ID definitions. */ -#define MSG_ID_BLE_BASE BLE_SERVICE_MSG_BASE -#define MSG_ID_BLE_RSP (BLE_SERVICE_MSG_BASE + 0x40) -#define MSG_ID_BLE_EVT (BLE_SERVICE_MSG_BASE + 0x80) +/* HCI status (error) codes as per BT spec */ +#define HCI_REMOTE_DEV_TERMINATION_DUE_TO_POWER_OFF 0x15 +#define HCI_LOCAL_HOST_TERMINATED_CONNECTION 0x16 -/* BLE Core Service Message ID base (GAP/GATT) */ -#define MSG_ID_BLE_GAP_BASE BLE_SERVICE_GAP_MSG_BASE -#define MSG_ID_BLE_GAP_RSP (BLE_SERVICE_GAP_MSG_BASE + 0x40) -#define MSG_ID_BLE_GAP_EVT (BLE_SERVICE_GAP_MSG_BASE + 0x80) +/** @} */ #endif 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 new file mode 100644 index 00000000..405c984a --- /dev/null +++ b/system/libarc32_arduino101/framework/src/services/ble_service/ble_service.c @@ -0,0 +1,297 @@ +/* + * 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 "ble_service.h" +#include "os/os.h" + +//#include "util/assert.h" +#include +//#include +#include "cfw/cfw_service.h" +#include "ble_protocol.h" +#include "ble_service_int.h" +#include "ble_service_internal.h" +#include "infra/log.h" + +#include +#include +#include +#include +#include "gap_internal.h" + +#include "nble_driver.h" + +#include "rpc.h" + +#include "util/misc.h" + +struct _ble_service_cb _ble_cb = { 0 }; +volatile uint8_t ble_inited = false; + + +static void ble_client_connected(conn_handle_t *instance); +static void ble_client_disconnected(conn_handle_t *instance); + +#if defined(CONFIG_BLUETOOTH_SMP) +static const struct bt_conn_auth_cb auth_callbacks; +#endif + +static service_t ble_service = { + .service_id = BLE_SERVICE_ID, + .client_connected = ble_client_connected, + .client_disconnected = ble_client_disconnected, +}; + +static void ble_is_not_enabled_rsp(struct cfw_message *msg, int status) +{ + struct ble_enable_rsp *resp = + (struct ble_enable_rsp *)cfw_alloc_rsp_msg(msg, + /* translate msg from req to rsp */ + (CFW_MESSAGE_ID(msg) ^ MSG_ID_BLE_SERVICE_BASE) + | MSG_ID_BLE_SERVICE_RSP, + sizeof(*resp)); + resp->status = status; + cfw_send_message(resp); +} + +#ifdef CONFIG_TCMD_BLE_DEBUG +static void handle_msg_id_ble_dbg(struct cfw_message *msg) +{ + struct nble_debug_params params; + struct ble_dbg_req_rsp *resp = (void *) + cfw_alloc_rsp_msg(msg, MSG_ID_BLE_DBG_RSP, sizeof(*resp)); + struct ble_dbg_req_rsp *req = (struct ble_dbg_req_rsp *) msg; + + params.u0 = req->u0; + params.u1 = req->u1; + + nble_gap_dbg_req(¶ms, resp); +} +#endif /* CONFIG_TCMD_BLE_DEBUG */ + +void on_nble_gap_dbg_rsp(const struct nble_debug_resp *params) +{ +#ifdef CONFIG_TCMD_BLE_DEBUG + struct ble_dbg_req_rsp *resp = params->user_data; + if (!resp) + return; + resp->u0 = params->u0; + resp->u1 = params->u1; + cfw_send_message(resp); +#endif /* CONFIG_TCMD_BLE_DEBUG */ +} + + +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); + bfree(rpc->p_data); + message_free(msg); +} + +static void ble_set_bda_cb(int status, void *user_data) +{ + struct ble_enable_req *req = user_data; + + if (!req) + return; + + struct ble_enable_rsp *resp = (void *)cfw_alloc_rsp_msg(&req->header, + MSG_ID_BLE_ENABLE_RSP, sizeof(*resp)); + resp->status = status; + + if (status == 0) { + resp->enable = 1; + + nble_gap_read_bda_req(resp); + } else { + /* error case */ + resp->enable = 0; + cfw_send_message(resp); + } + bfree(req); +} + + +void on_nble_gap_read_bda_rsp(const struct nble_service_read_bda_response *params) +{ + struct cfw_message *rsp = params->user_data; + + if (rsp) { + struct ble_enable_rsp *r = container_of(rsp, struct ble_enable_rsp, header); + r->bd_addr = params->bd; + cfw_send_message(rsp); + } +} + +static void handle_ble_enable(struct ble_enable_req *req, + struct _ble_service_cb *p_cb) +{ + pr_info(LOG_MODULE_BLE, "ble_enable: state %d", p_cb->ble_state); + + p_cb->ble_state = BLE_ST_ENABLED; + + if (req->bda_present) { + struct nble_set_bda_params params; + + params.cb = ble_set_bda_cb; + params.user_data = req; + params.bda = req->bda; + + nble_set_bda_req(¶ms); + } else { + ble_set_bda_cb(0, req); + } +} + +void on_nble_set_bda_rsp(const struct nble_set_bda_rsp *params) +{ + if (params->cb) { + params->cb(params->status, params->user_data); + } +} + +static void handle_ble_disable(struct ble_enable_req *req, struct _ble_service_cb *p_cb) +{ + struct ble_enable_rsp *resp; + + pr_debug(LOG_MODULE_BLE, "ble_disable"); + p_cb->ble_state = BLE_ST_DISABLED; + + bt_le_adv_stop(); + + resp = (void *)cfw_alloc_rsp_msg(&req->header, + MSG_ID_BLE_ENABLE_RSP, + sizeof(*resp)); + cfw_send_message(resp); + +} + +static void ble_service_message_handler(struct cfw_message *msg, void *param) +{ + bool free_msg = true; + struct _ble_service_cb *p_cb = param; + uint16_t msg_id = CFW_MESSAGE_ID(msg); + + if (p_cb->ble_state < BLE_ST_ENABLED && + msg_id != MSG_ID_BLE_ENABLE_REQ) { + ble_is_not_enabled_rsp(msg, -ENODEV); + goto out; + } + + switch (msg_id) { + case MSG_ID_BLE_ENABLE_REQ: { + struct ble_enable_req *req = + container_of(msg, struct ble_enable_req, header); + if (p_cb->ble_state) { + if (req->enable) { + handle_ble_enable(req, p_cb); + free_msg = false; + } else + handle_ble_disable(req, p_cb); + } else { + pr_debug(LOG_MODULE_BLE, "ble_hdl_msg: core service not opened!"); + /* core service is not yet up */ + struct ble_enable_rsp *resp = (void *)cfw_alloc_rsp_msg(msg, + MSG_ID_BLE_ENABLE_RSP, sizeof(*resp)); + resp->status = -EINPROGRESS; + resp->enable = 0; + cfw_send_message(resp); + } + } + break; +#ifdef CONFIG_TCMD_BLE_DEBUG + case MSG_ID_BLE_DBG_REQ: + handle_msg_id_ble_dbg(msg); + break; +#endif + default: + pr_warning(LOG_MODULE_BLE, "unsupported %d", msg_id); + break; + } +out: + if (free_msg) + cfw_msg_free(msg); +} + +static void ble_client_connected(conn_handle_t *instance) +{ + if (_ble_cb.ble_state == BLE_ST_NOT_READY) + pr_warning(LOG_MODULE_BLE, "BLE_CORE service is not registered"); +} + +static void ble_client_disconnected(conn_handle_t *instance) +{ +} + +void ble_bt_rdy(int err) +{ + _ble_cb.ble_state = BLE_ST_DISABLED; + ble_inited = true; + + /* register BLE service */ + if (cfw_register_service(_ble_cb.queue, &ble_service, + ble_service_message_handler, &_ble_cb) == -1) { + panic(0xb1eb1e); + } +} + +void ble_cfw_service_init(int service_id, T_QUEUE queue) +{ + _ble_cb.queue = queue; + _ble_cb.ble_state = BLE_ST_NOT_READY; + +#ifdef CONFIG_IPC_UART_NS16550 + nble_driver_configure(queue, handle_msg_id_ble_rpc_callin); +#endif + + ble_inited = false; + + bt_enable(ble_bt_rdy); + do{} + while (ble_inited == false); +} + +void nble_log(const struct nble_log_s *param, char *buf, uint8_t buflen) +{ + pr_info(LOG_MODULE_BLE, buf, param->param0, param->param1, param->param2, param->param3); +} + +void on_nble_common_rsp(const struct nble_response *params) +{ + struct ble_rsp *resp = params->user_data; + + if (!resp) + return; + resp->status = params->status; + cfw_send_message(resp); +} diff --git a/system/libarc32_arduino101/framework/src/services/ble_service/ble_service_api.c b/system/libarc32_arduino101/framework/src/services/ble_service/ble_service_api.c new file mode 100644 index 00000000..e70b15c8 --- /dev/null +++ b/system/libarc32_arduino101/framework/src/services/ble_service/ble_service_api.c @@ -0,0 +1,66 @@ +/* + * 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 "ble_service.h" + +#include +#include +//#include +//#include "util/assert.h" + +#include "ble_service_int.h" +#include "ble_service_internal.h" +#include "cfw/cfw_service.h" +#include "cfw/cfw_client.h" + +#include +#include + +#include "ble_protocol.h" +#include "ble_service_utils.h" + +int ble_service_enable(cfw_service_conn_t * p_service_conn, uint8_t enable, + const struct ble_enable_config * p_config, + void *p_priv) +{ + struct ble_enable_req * msg = + (void *) cfw_alloc_message_for_service(p_service_conn, + MSG_ID_BLE_ENABLE_REQ, + sizeof(*msg), p_priv); + msg->central_conn_params = p_config->central_conn_params; + msg->enable = enable; + + if (p_config->p_bda) { + msg->bda_present = 1; + msg->bda = *p_config->p_bda; + } + + return cfw_send_message(msg); +} diff --git a/system/libarc32_arduino101/framework/src/services/ble_service/ble_service_int.h b/system/libarc32_arduino101/framework/src/services/ble_service/ble_service_int.h new file mode 100644 index 00000000..857270c7 --- /dev/null +++ b/system/libarc32_arduino101/framework/src/services/ble_service/ble_service_int.h @@ -0,0 +1,67 @@ +/* + * 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_SERVICE_INT_H_ +#define BLE_SERVICE_INT_H_ + +#include + +/* For cfw_service_conn_t, cfw_client_t, T_QUEUE */ +#include "cfw/cfw.h" + +/* Forward declarations */ +struct ble_init_svc_req; +struct ble_init_svc_rsp; +struct bt_conn_cb; +struct bt_gatt_attr; + +enum BLE_STATE { + BLE_ST_NOT_READY = 0, + BLE_ST_DISABLED, + BLE_ST_ENABLED, + BLE_ST_DTM +}; + +struct _ble_service_cb { + T_QUEUE queue; /* Queue for the messages */ + uint8_t ble_state; +}; + +extern struct _ble_service_cb _ble_cb; + +/** Send advertisement timeout event to application */ +void ble_gap_advertisement_timeout(void); + +#ifdef CONFIG_BLE_CORE_TEST +void test_ble_service_init(void); +T_QUEUE get_service_queue(void); +#endif + +#endif /* BLE_SERVICE_INT_H_ */ diff --git a/system/libarc32_arduino101/framework/src/services/ble_service/ble_service_internal.h b/system/libarc32_arduino101/framework/src/services/ble_service/ble_service_internal.h new file mode 100644 index 00000000..013043a5 --- /dev/null +++ b/system/libarc32_arduino101/framework/src/services/ble_service/ble_service_internal.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2016, 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_SERVICE_INTERNAL_H__ +#define __BLE_SERVICE_INTERNAL_H__ + +// For MSG_ID_BLE_SERVICE_BASE +#include "services/services_ids.h" + +/* BLE High level Message IDs used for request, response, events. */ +enum BLE_MSG_ID_REQ { + MSG_ID_BLE_ENABLE_REQ = MSG_ID_BLE_SERVICE_BASE, /* Message ID for enable request */ + MSG_ID_BLE_SET_NAME_REQ, /* Message ID for set name request */ + MSG_ID_BLE_START_SCAN_REQ, /* Message ID for start scan request */ + MSG_ID_BLE_STOP_SCAN_REQ, /* Message ID for stop scan request */ + MSG_ID_BLE_INIT_SVC_REQ, /* Message ID for init service request */ + + /* BLE debug command */ + MSG_ID_BLE_DBG_REQ, /* Message ID for BLE debug command request */ + + MSG_ID_BLE_REQ_LAST, +}; + +/* Parameters of MSG_ID_BLE_ENABLE_REQ. */ +struct ble_enable_req { + struct cfw_message header; /* Component framework message header, MUST be first element of structure */ + struct bt_le_conn_param central_conn_params; + uint8_t enable; + uint8_t bda_present; + bt_addr_le_t bda; +}; + +struct ble_disconnect_req { + struct cfw_message header; /* Component framework message header, MUST be first element of structure */ + struct bt_conn *conn; /* Connection reference */ + uint8_t reason; /* Reason of the disconnect*/ +}; + +struct ble_connect_req { + struct cfw_message header; /* Component framework message header, MUST be first element of structure */ + struct bt_le_conn_param conn_params; + bt_addr_le_t bd_addr; +}; + +struct ble_init_svc_req { + struct cfw_message header; /* Component framework message header, MUST be first element of structure */ + int (*init_svc)(struct ble_init_svc_req *msg, struct _ble_service_cb *p_cb); + void (*init_svc_complete)(struct ble_init_svc_req *req); + uint8_t status; +}; + +#endif /* __BLE_SERVICE_INTERNAL_H__ */ diff --git a/system/libarc32_arduino101/framework/src/services/ble/ble_service_utils.c b/system/libarc32_arduino101/framework/src/services/ble_service/ble_service_utils.c similarity index 50% rename from system/libarc32_arduino101/framework/src/services/ble/ble_service_utils.c rename to system/libarc32_arduino101/framework/src/services/ble_service/ble_service_utils.c index d40c1b72..0275e79b 100644 --- a/system/libarc32_arduino101/framework/src/services/ble/ble_service_utils.c +++ b/system/libarc32_arduino101/framework/src/services/ble_service/ble_service_utils.c @@ -31,77 +31,48 @@ #include #include "os/os.h" #include "ble_protocol.h" -#include "services/ble/ble_service_gap_api.h" +#include "ble_service.h" +#include "ble_service_int.h" #include "ble_service_utils.h" -uint8_t ble_sizeof_bt_uuid(const struct bt_uuid * p_uuid) +void uint8_to_ascii(uint8_t in, uint8_t *p) { - return p_uuid->type + 1; + uint8_t hi = (in & 0xF0) >> 4; + uint8_t lo = in & 0x0F; + + if (hi < 0x0A) + *p = '0' + hi; + else + *p = 'A' + (hi - 0x0A); + + p++; + + if (lo < 10) + *p = '0' + lo; + else + *p = 'A' + (lo - 0x0A); } -uint8_t * ble_encode_bt_uuid(const struct bt_uuid * p_uuid, uint8_t * p_data) +void uint8buf_to_ascii(uint8_t * dst, const uint8_t * src, int len) { - // start with the type (length) - UINT8_TO_LESTREAM(p_data, p_uuid->type); - switch (p_uuid->type) { - case BT_UUID16: - UINT16_TO_LESTREAM(p_data, p_uuid->uuid16); - break; - case BT_UUID32: - UINT32_TO_LESTREAM(p_data, p_uuid->uuid32); - break; - case BT_UUID128: - memcpy(p_data, p_uuid->uuid128, MAX_UUID_SIZE); - p_data += MAX_UUID_SIZE; - break; + int i; + for (i = 0; i < len; ++i) { + uint8_to_ascii(src[i], dst); + dst += 2; } - return p_data; } -uint8_t ble_cpy_bt_uuid(uint8_t * p_uuid_dst, - const struct bt_uuid * p_uuid_src, - uint8_t type) +size_t adv_data_len(const struct bt_data *ad, size_t count) { - uint8_t *p = p_uuid_dst; - uint8_t res_type = (p_uuid_src->type >= type) ? type : 0; + int i; + size_t data_len = 0; - switch (res_type) { - case BT_UUID16: - switch (p_uuid_src->type) { - case BT_UUID16: - UINT16_TO_LESTREAM(p, p_uuid_src->uuid16); - break; - case BT_UUID32: - UINT16_TO_LESTREAM(p, p_uuid_src->uuid16); - break; - case BT_UUID128: { - uint16_t uuid16; - p = (uint8_t *)&p_uuid_src->uuid128[BLE_BASE_UUID_OCTET_OFFSET]; - LESTREAM_TO_UINT16(p, uuid16); - p = p_uuid_dst; - UINT16_TO_LESTREAM(p, p_uuid_src->uuid16); - break; - } - } - break; - case BT_UUID32: - switch (p_uuid_src->type) { - case BT_UUID32: - UINT32_TO_LESTREAM(p, p_uuid_src->uuid32); - break; - case BT_UUID128: { - uint32_t uuid32; - p = (uint8_t *)&p_uuid_src->uuid128[BLE_BASE_UUID_OCTET_OFFSET]; - LESTREAM_TO_UINT32(p, uuid32); - p = p_uuid_dst; - UINT32_TO_LESTREAM(p, uuid32); - break; - } - } - break; - case BT_UUID128: - memcpy(p_uuid_dst, p_uuid_src->uuid128, MAX_UUID_SIZE); - break; + if (ad == NULL) + return 0; + + for (i = 0; i < count ; i++) { + data_len += ad[i].data_len; } - return res_type; + + return data_len; } diff --git a/system/libarc32_arduino101/framework/src/services/ble/ble_service_utils.h b/system/libarc32_arduino101/framework/src/services/ble_service/ble_service_utils.h similarity index 66% rename from system/libarc32_arduino101/framework/src/services/ble/ble_service_utils.h rename to system/libarc32_arduino101/framework/src/services/ble_service/ble_service_utils.h index a51f8dcd..8d5d9378 100644 --- a/system/libarc32_arduino101/framework/src/services/ble/ble_service_utils.h +++ b/system/libarc32_arduino101/framework/src/services/ble_service/ble_service_utils.h @@ -28,20 +28,19 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#ifndef __BLE_SERVICE_UTILS_H__ -#define __BLE_SERVICE_UTILS_H__ +#ifndef BLE_SERVICE_UTILS_H_ +#define BLE_SERVICE_UTILS_H_ -#include "services/ble/ble_service_gap_api.h" -#include "services/ble/ble_service_gatt.h" -//#include "ble_service_int.h" +#include -#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) + __must_be_array(a) +// For HAVE_SAME_TYPE +#include "compiler.h" #define IS_BIG_ENDIAN (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) #define UINT8_TO_LESTREAM(p, i) \ do { *(p)++ = (uint8_t)(i); } \ - while (sizeof(char[1 - 2 * !(__builtin_types_compatible_p(__typeof__(p), uint8_t *))]) - 1) + while (sizeof(char[1 - 2 * !(HAVE_SAME_TYPE(p, uint8_t *))]) - 1) #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) @@ -59,8 +58,8 @@ #define LESTREAM_TO_UINT8(p, i) \ do { i = *p; p++; } \ - while (sizeof(char[1 - 2 * !(__builtin_types_compatible_p(__typeof__(p), const uint8_t *) || \ - __builtin_types_compatible_p(__typeof__(p), uint8_t *))]) - 1) + while (sizeof(char[1 - 2 * !(HAVE_SAME_TYPE(p, const uint8_t *) || \ + HAVE_SAME_TYPE(p, uint8_t *))]) - 1) #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) @@ -90,53 +89,38 @@ #define BESTREAM_TO_INT32(p, i) \ do {uint32_t __i; BESTREAM_TO_UINT32(p, __i); i = (int32_t)__i; } while (0) -#define between(a, b, c) (((a) >= (b)) || ((a) <= (c))) - -#define strict_between(a, b, c) (((a) > (b)) || ((a) < (c))) - /** * BLE helper functions. */ -#define BLE_BASE_UUID_OCTET_OFFSET 12 -/** - * Compute the size required to encode a UUID in a buffer +/**@brief Represent an unsigned 8 bit value in ASCII hexadecimal + * + * @param[in] in The value to represent + * @param[inout] p Pointer to the buffer to fill * - * @param p_uuid Pointer to the UUID + * @note The representation requires 2 bytes */ -uint8_t ble_sizeof_bt_uuid(const struct bt_uuid * p_uuid); +void uint8_to_ascii(uint8_t in, uint8_t * p); -/** - * Encode a UUID in a buffer +/** Converts buffer from hex to ascii. * - * @param p_uuid Pointer to the UUID to encode - * @param p_data Pointer to the buffer to store the encoded UUID + * @param dst buffer converted + * @param src buffer which is converted + * @param len length of src (dst shall be large enough) * - * @return The pointer to the buffer after the encoded UUID + * @return None */ -uint8_t * ble_encode_bt_uuid(const struct bt_uuid * p_uuid, uint8_t * p_data); +void uint8buf_to_ascii(uint8_t * dst, const uint8_t * src, int len); -/** Copy BT/BLE address and eventually reduce size. - * - * Copies a uuid from src to dst. type may modify the resulting uuid type. only - * going from bigger to small type is supported. typically 128 bits to 16 bits - * If it is different from src type, a transformation is applied: - * IN: BT_UUID128: dd97c415-fed9-4766-b18f-ba690d24a06a (stored in little endian) - * OUT: BT_UUID16: c415 - * OUT: BT_UUID32: dd97c415 - * or - * IN: BT_UUID32: dd97c415 - * OUT: BT_UUID16: c415 - * - * @param p_uuid_src source uuid - * @param[out] p_uuid_dst: destination to copy uuid to (excluding type). little endian - * @param type output type +struct bt_data; +/** + * Get advertisement data length. * - * \return size/type of copied uuid: BT_UUID16, BT_UUID32, BT_UUID128 or 0 for failure! + * @param ad advertisement data + * @param count array length + * @return length of advertisement data * */ -uint8_t ble_cpy_bt_uuid(uint8_t * p_uuid_dst, - const struct bt_uuid * p_uuid_src, - uint8_t type); +size_t adv_data_len(const struct bt_data *ad, size_t count); -#endif +#endif /* BLE_SERVICE_UTILS_H_ */ diff --git a/system/libarc32_arduino101/framework/src/services/ble_service/gap_internal.h b/system/libarc32_arduino101/framework/src/services/ble_service/gap_internal.h new file mode 100644 index 00000000..333ad36e --- /dev/null +++ b/system/libarc32_arduino101/framework/src/services/ble_service/gap_internal.h @@ -0,0 +1,629 @@ +/* + * Copyright (c) 2016 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GAP_INTERNAL_H_ +#define GAP_INTERNAL_H_ + +#include +/* For bt_addr_le_t */ +#include +/* For bt_security_t */ +#include + +/* Directed advertisement timeout error after 1.28s */ +#define ERR_DIRECTED_ADVERTISING_TIMEOUT 0x3C + +enum NBLE_GAP_SM_PASSKEY_TYPE { + NBLE_GAP_SM_REJECT = 0, + NBLE_GAP_SM_PK_PASSKEY, + NBLE_GAP_SM_PK_OOB, +}; + +enum NBLE_GAP_SM_EVT { + NBLE_GAP_SM_EVT_START_PAIRING, + NBLE_GAP_SM_EVT_BONDING_COMPLETE, + NBLE_GAP_SM_EVT_LINK_ENCRYPTED, + NBLE_GAP_SM_EVT_LINK_SECURITY_CHANGE, +}; + +enum NBLE_GAP_RSSI_OPS { + NBLE_GAP_RSSI_DISABLE_REPORT = 0, + NBLE_GAP_RSSI_ENABLE_REPORT +}; + +enum NBLE_TEST_OPCODES { + NBLE_TEST_INIT_DTM = 0x01, + NBLE_TEST_START_DTM_RX = 0x1d, + NBLE_TEST_START_DTM_TX = 0x1e, + NBLE_TEST_END_DTM = 0x1f, + /* vendor specific commands start at 0x80 */ + /* Set Tx power. To be called before start of tx test */ + NBLE_TEST_SET_TXPOWER = 0x80, + NBLE_TEST_START_TX_CARRIER, +}; + +/* DTM commands, opcodes, indexes. */ +#define H4_CMD 0x01 +#define HCI_OGF_LE_CMD 0x20 + +#define DTM_HCI_STATUS_IDX 6 +#define DTM_HCI_LE_END_IDX (DTM_HCI_STATUS_IDX + 1) + + +struct nble_response { + int status; + void *user_data; +}; + +struct nble_gap_device_name { + /* Security mode for writing device name, @ref BLE_GAP_SEC_MODES */ + uint8_t sec_mode; + /* 0: no authorization, 1: authorization required */ + uint8_t authorization; + /* Device name length (0-248) */ + uint8_t len; + uint8_t name_array[20]; +}; + +struct nble_gap_connection_values { + /* Connection interval (unit 1.25 ms) */ + uint16_t interval; + /* Connection latency (unit interval) */ + uint16_t latency; + /* Connection supervision timeout (unit 10ms)*/ + uint16_t supervision_to; +}; + + +enum NBLE_GAP_SVC_ATTR_TYPE { + /* Device Name, UUID 0x2a00 */ + NBLE_GAP_SVC_ATTR_NAME = 0, + /* Appearance, UUID 0x2a01 */ + NBLE_GAP_SVC_ATTR_APPEARANCE, + /* Peripheral Preferred Connection Parameters (PPCP), UUID 0x2a04 */ + NBLE_GAP_SVC_ATTR_PPCP = 4, + /* Central Address Resolution (CAR), UUID 0x2aa6, BT 4.2 */ + NBLE_GAP_SVC_ATTR_CAR = 0xa6, +}; + +struct nble_gap_connection_params { + /* minimal connection interval: range 0x0006 to 0x0c80 (unit 1.25ms) */ + uint16_t interval_min; + /* maximum connection interval: range 0x0006 to 0x0c80 must be bigger then min! */ + uint16_t interval_max; + /* maximum connection slave latency: 0x0000 to 0x01f3 */ + uint16_t slave_latency; + /* link supervision timeout: 0x000a to 0x0c80 (unit 10ms) */ + uint16_t link_sup_to; +}; + +struct nble_gap_scan_parameters { + /* If 1, perform active scanning (scan requests). */ + uint8_t active; + /* If 1, ignore unknown devices (non whitelisted). */ + uint8_t selective; + /* Scan interval between 0x0004 and 0x4000 in 0.625ms units (2.5ms to 10.24s). */ + uint16_t interval; + /* Scan window between 0x0004 and 0x4000 in 0.625ms units (2.5ms to 10.24s). */ + uint16_t window; + /* Scan timeout between 0x0001 and 0xFFFF in seconds, 0x0000 disables timeout. */ + uint16_t timeout; +}; + +struct nble_gap_service_write_params { + /* GAP Characteristics attribute type @ref BLE_GAP_SVC_ATTR_TYPE */ + uint16_t attr_type; + union { + struct nble_gap_device_name name; + /* Appearance UUID */ + uint16_t appearance; + /* Preferred Peripheral Connection Parameters */ + struct nble_gap_connection_params conn_params; + /* Central Address Resolution support 0: no, 1: yes */ + uint8_t car; + }; +}; + +struct nble_service_read_bda_response { + int status; + /* If @ref status ok */ + bt_addr_le_t bd; + void *user_data; +}; + +struct nble_service_write_response { + int status; + /* GAP Characteristics attribute type @ref BLE_GAP_SVC_ATTR_TYPE */ + uint16_t attr_type; + void *user_data; +}; + +struct nble_gap_service_read_params { + /* Type of GAP data characteristic to read @ref BLE_GAP_SVC_ATTR_TYPE */ + uint16_t attr_type; +}; + +struct nble_debug_params { + uint32_t u0; + uint32_t u1; +}; + +struct nble_debug_resp { + int status; + uint32_t u0; + uint32_t u1; + void *user_data; +}; + +typedef void (*nble_set_bda_cb_t)(int status, void *user_data); + +struct nble_set_bda_params { + bt_addr_le_t bda; + nble_set_bda_cb_t cb; + void *user_data; +}; + +struct nble_set_bda_rsp { + nble_set_bda_cb_t cb; + void *user_data; + int status; +}; + +struct bt_eir_data { + uint8_t len; + uint8_t data[31]; +}; + +struct nble_gap_adv_params { + uint16_t timeout; + /* min interval 0xffff: use default 0x0800 */ + uint16_t interval_min; + /* max interval 0xffff: use default 0x0800 */ + uint16_t interval_max; + /* advertisement types @ref GAP_ADV_TYPES */ + uint8_t type; + /* filter policy to apply with white list */ + uint8_t filter_policy; + /* bd address of peer device in case of directed advertisement */ + bt_addr_le_t peer_bda; +}; + +struct nble_gap_ad_data_params { + /* Advertisement data, maybe 0 (length) */ + struct bt_eir_data ad; + /* Scan response data, maybe 0 (length) */ + struct bt_eir_data sd; +}; + +struct nble_log_s { + uint8_t param0; + uint8_t param1; + uint8_t param2; + uint8_t param3; +}; + +/* bt_dev flags: the flags defined here represent BT controller state */ +enum { + BT_DEV_READY, + + BT_DEV_ADVERTISING, + BT_DEV_KEEP_ADVERTISING, + BT_DEV_SCANNING, + BT_DEV_EXPLICIT_SCAN, + +#if defined(CONFIG_BLUETOOTH_BREDR) + BT_DEV_ISCAN, + BT_DEV_PSCAN, +#endif /* CONFIG_BLUETOOTH_BREDR */ +}; + +void nble_log(const struct nble_log_s *param, char *buf, uint8_t buflen); + +void on_nble_up(void); + +void nble_gap_service_write_req(const struct nble_gap_service_write_params *params); + +void on_nble_gap_read_bda_rsp(const struct nble_service_read_bda_response *params); + +void nble_gap_dbg_req(const struct nble_debug_params *params, void *user_data); + +void on_nble_gap_dbg_rsp(const struct nble_debug_resp *params); + +void on_nble_set_bda_rsp(const struct nble_set_bda_rsp *params); + +void nble_set_bda_req(const struct nble_set_bda_params *params); + +void nble_gap_set_adv_data_req(struct nble_gap_ad_data_params *ad_data_params); + +void nble_gap_set_adv_params_req(struct nble_gap_adv_params *adv_params); + +void nble_gap_start_adv_req(void); + +void on_nble_gap_start_advertise_rsp(const struct nble_response *params); + +void nble_gap_stop_adv_req(void *user_data); + +void nble_gap_read_bda_req(void *priv); + +struct nble_gap_irk_info { + /* Identity Resolving Key (IRK) */ + uint8_t irk[16]; +}; + +void on_nble_common_rsp(const struct nble_response *params); + +struct nble_gap_connect_update_params { + uint16_t conn_handle; + struct nble_gap_connection_params params; +}; + +void nble_gap_conn_update_req(const struct nble_gap_connect_update_params *params); + +void on_nble_gap_conn_update_rsp(const struct nble_response *params); + +struct nble_gap_connect_req_params { + bt_addr_le_t bda; + struct nble_gap_connection_params conn_params; + struct nble_gap_scan_parameters scan_params; +}; + +struct nble_gap_disconnect_req_params { + uint16_t conn_handle; + uint8_t reason; +}; + +void nble_gap_disconnect_req(const struct nble_gap_disconnect_req_params *params); + +struct nble_gap_sm_config_params { + /* Security options (@ref BLE_GAP_SM_OPTIONS) */ + uint8_t options; + /* I/O Capabilities to allow passkey exchange (@ref BLE_GAP_IO_CAPABILITIES) */ + uint8_t io_caps; + /* Maximum encryption key size (7-16) */ + uint8_t key_size; + uint8_t oob_present; +}; + +void nble_gap_sm_config_req(const struct nble_gap_sm_config_params *params); + +struct nble_gap_sm_config_rsp { + void *user_data; + int status; + bool sm_bond_dev_avail; +}; + +void on_nble_gap_sm_config_rsp(struct nble_gap_sm_config_rsp *params); + + +struct nble_gap_sm_pairing_params { + /* authentication level see @ref BLE_GAP_SM_OPTIONS */ + uint8_t auth_level; +}; + +struct nble_gap_sm_security_params { + struct bt_conn *conn; + uint16_t conn_handle; + /* Local authentication/bonding parameters */ + struct nble_gap_sm_pairing_params params; +}; + +void nble_gap_sm_security_req(const struct nble_gap_sm_security_params * + params); + +struct nble_gap_sm_passkey { + uint8_t type; + union { + uint32_t passkey; + uint8_t oob[16]; + uint8_t reason; + }; +}; + +struct nble_gap_sm_key_reply_req_params { + struct bt_conn *conn; + uint16_t conn_handle; + struct nble_gap_sm_passkey params; +}; + +void nble_gap_sm_passkey_reply_req(const struct nble_gap_sm_key_reply_req_params + *params); + +struct nble_gap_sm_clear_bond_req_params { + bt_addr_le_t addr; +}; + +void nble_gap_sm_clear_bonds_req(const struct nble_gap_sm_clear_bond_req_params + *params); + +struct nble_gap_sm_response { + int status; + struct bt_conn *conn; +}; + +void on_nble_gap_sm_common_rsp(const struct nble_gap_sm_response *rsp); + +/** + * Callback for rssi event. + */ +typedef void (*rssi_report_t)(const int8_t *rssi_data); + +/** + * Callback for rssi report response. + */ +typedef void (*rssi_report_resp_t)(int status); + +struct nble_rssi_report_params { + uint16_t conn_handle; + /* RSSI operation @ref NBLE_GAP_RSSI_OPS */ + uint8_t op; + /* Channel for RSSI enabling */ + uint8_t channel; + /* minimum RSSI dBm change to report a new RSSI value */ + uint8_t delta_dBm; + /* number of delta_dBm changes before sending a new RSSI report */ + uint8_t min_count; +}; + +void ble_gap_set_rssi_report(struct nble_rssi_report_params *params, + struct bt_conn *conn, + rssi_report_resp_t resp_cb, rssi_report_t evt_cb); + +void nble_gap_set_rssi_report_req(const struct nble_rssi_report_params *params, + void *user_data); + +void on_nble_gap_set_rssi_report_rsp(const struct nble_response *params); + +struct nble_gap_scan_params { + uint16_t interval; + uint16_t window; + uint8_t scan_type; + uint8_t use_whitelist; +}; + +void nble_gap_start_scan_req(const struct nble_gap_scan_params *params); + +void nble_gap_stop_scan_req(void); + +void on_nble_gap_scan_start_stop_rsp(const struct nble_response *rsp); + +void nble_gap_connect_req(const struct nble_gap_connect_req_params *params, + void *user_data); + +void on_nble_gap_connect_rsp(const struct nble_response *params); + +void nble_gap_cancel_connect_req(void *priv); + +void on_nble_gap_cancel_connect_rsp(const struct nble_response *params); + +enum BLE_GAP_SET_OPTIONS { + BLE_GAP_SET_CH_MAP = 0, +}; + +struct nble_gap_channel_map { + /* connection on which to change channel map */ + uint16_t conn_handle; + /* 37 bits are used of the 40 bits (LSB) */ + uint8_t map[5]; +}; + + +struct nble_gap_set_option_params { + /* Option to set @ref BLE_GAP_SET_OPTIONS */ + uint8_t op; + union { + struct nble_gap_channel_map ch_map; + }; +}; + +/* + * Generic request op codes. + * This allows to access some non connection related commands like DTM. + */ +enum BLE_GAP_GEN_OPS { + /* Not used now. */ + DUMMY_VALUE = 0, +}; + +struct nble_gap_gen_cmd_params { + /* @ref BLE_GAP_GEN_OPS */ + uint8_t op_code; +}; + +/* Temporary patch: RSSI processing for UAS */ +struct nble_uas_rssi_calibrate { + float distance; +}; +void nble_uas_rssi_calibrate_req(const struct nble_uas_rssi_calibrate *p_struct); + +/* Temporary patch: RSSI processing for UAS */ +struct nble_uas_bucket_change { + uint8_t distance; +}; +void on_nble_uas_bucket_change(const struct nble_uas_bucket_change *p_params); + +struct nble_version { + uint8_t version; + uint8_t major; + uint8_t minor; + uint8_t patch; + char version_string[20]; + uint8_t hash[4]; +}; + +typedef void (*ble_get_version_cb_t)(const struct nble_version *ver); + +struct nble_gap_get_version_param { + ble_get_version_cb_t cb; +}; + +struct nble_version_response { + struct nble_gap_get_version_param params; + struct nble_version ver; +}; + +void nble_get_version_req(const struct nble_gap_get_version_param *params); + +void on_nble_get_version_rsp(const struct nble_version_response *params); + +void nble_gap_dtm_init_req(void *user_data); + +void on_nble_gap_dtm_init_rsp(void *user_data); + +struct nble_gap_tx_power_params { + int8_t tx_power; +}; + +void nble_gap_tx_power_req(const struct nble_gap_tx_power_params *params); + +void on_nble_gap_tx_power_rsp(const struct nble_response *params); + +struct nble_gap_connect_evt { + uint16_t conn_handle; + struct nble_gap_connection_values conn_values; + /* 0 if connected as master, otherwise as slave */ + uint8_t role_slave; + /* Address of peer device */ + bt_addr_le_t peer_bda; +}; + +void on_nble_gap_connect_evt(const struct nble_gap_connect_evt *evt); + +struct nble_gap_disconnect_evt { + uint16_t conn_handle; + uint8_t hci_reason; +}; + +void on_nble_gap_disconnect_evt(const struct nble_gap_disconnect_evt *evt); + +struct nble_gap_conn_update_evt { + uint16_t conn_handle; + struct nble_gap_connection_values conn_values; +}; + +void on_nble_gap_conn_update_evt(const struct nble_gap_conn_update_evt *evt); + +struct nble_gap_adv_report_evt { + bt_addr_le_t addr; + int8_t rssi; + uint8_t adv_type; +}; + +void on_nble_gap_adv_report_evt(const struct nble_gap_adv_report_evt *evt, + const uint8_t *buf, uint8_t len); + +struct nble_gap_dir_adv_timeout_evt { + uint16_t conn_handle; + uint16_t error; +}; + +void on_nble_gap_dir_adv_timeout_evt(const struct nble_gap_dir_adv_timeout_evt *p_evt); + +#define BLE_GAP_RSSI_EVT_SIZE 32 +struct nble_gap_rssi_evt { + uint16_t conn_handle; + int8_t rssi_data[BLE_GAP_RSSI_EVT_SIZE]; +}; + +void on_nble_gap_rssi_evt(const struct nble_gap_rssi_evt *evt); + +struct nble_gap_timout_evt { + uint16_t conn_handle; + /* reason for timeout @ref BLE_SVC_GAP_TIMEOUT_REASON */ + int reason; +}; + +struct nble_gap_sm_passkey_req_evt { + uint16_t conn_handle; + uint8_t key_type; +}; + +void on_nble_gap_sm_passkey_req_evt(const struct nble_gap_sm_passkey_req_evt *evt); + +struct nble_gap_sm_passkey_disp_evt { + uint16_t conn_handle; + uint32_t passkey; +}; + +void on_nble_gap_sm_passkey_display_evt(const struct nble_gap_sm_passkey_disp_evt *evt); + +struct nble_link_sec { + bt_security_t sec_level; + uint8_t enc_size; +}; + +struct nble_gap_sm_status_evt { + uint16_t conn_handle; + uint8_t evt_type; + int status; + struct nble_link_sec enc_link_sec; +}; + +void on_nble_gap_sm_status_evt(const struct nble_gap_sm_status_evt *evt); + +void on_nble_set_bda_rsp(const struct nble_set_bda_rsp *params); + +enum BLE_INFO_REQ_TYPES { + BLE_INFO_BONDING = 1, /* Get bonding database related information */ + BLE_INFO_LAST /* Keep last */ +}; + +struct ble_gap_bonded_dev_info +{ + uint8_t addr_count; /* Count of le_addr in array. */ + uint8_t irk_count; /* IRK count */ + bt_addr_le_t le_addr[]; /* Bonded device address */ +}; + +struct ble_get_info_rsp { + int status; /* Response status */ + uint8_t info_type; /* Requested information type */ + struct ble_gap_bonded_dev_info info_params; +}; + +struct nble_gap_sm_bond_info; +typedef void (*ble_bond_info_cb_t)(const struct nble_gap_sm_bond_info *info, + const bt_addr_le_t *addr, uint16_t len, + void *user_data); + +struct nble_gap_sm_bond_info_param { + ble_bond_info_cb_t cb; + void *user_data; + bool include_bonded_addrs; +}; + +void nble_gap_sm_bond_info_req(const struct nble_gap_sm_bond_info_param *params); + +struct nble_gap_sm_bond_info { + int err; + uint8_t addr_count; + uint8_t irk_count; +}; + +struct nble_gap_sm_bond_info_rsp { + ble_bond_info_cb_t cb; + void *user_data; + struct nble_gap_sm_bond_info info; +}; + +void on_nble_gap_sm_bond_info_rsp(const struct nble_gap_sm_bond_info_rsp *rsp, + const bt_addr_le_t *peer_addr, uint16_t len); + +void ble_gap_get_bonding_info(ble_bond_info_cb_t func, void *user_data, + bool include_bonded_addrs); + +void ble_gap_get_version(ble_get_version_cb_t func); + +#endif /* GAP_INTERNAL_H_ */ diff --git a/system/libarc32_arduino101/framework/src/services/ble_service/gatt_internal.h b/system/libarc32_arduino101/framework/src/services/ble_service/gatt_internal.h new file mode 100644 index 00000000..25ac4da8 --- /dev/null +++ b/system/libarc32_arduino101/framework/src/services/ble_service/gatt_internal.h @@ -0,0 +1,308 @@ +/* + * Copyright (c) 2016 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GATT_INTERNAL_H_ +#define GATT_INTERNAL_H_ + +#include +#include + +/* Forward declarations */ +struct nble_response; + +#define BLE_GATT_MTU_SIZE 23 + +/* + * Internal APIs used between host and BLE controller + * Typically they are required if gatt.h APIs can not be mapped 1:1 onto controller API + */ + +enum NBLE_GATT_IND_TYPES { + NBLE_GATT_IND_TYPE_NONE = 0, + NBLE_GATT_IND_TYPE_NOTIFICATION, + NBLE_GATT_IND_TYPE_INDICATION, +}; + +struct nble_gatt_register_req { + /* Base address of the attribute table in the Quark mem space */ + struct bt_gatt_attr *attr_base; + /* Number of of attributes in this service */ + uint8_t attr_count; +}; + +struct nble_gatt_register_rsp { + int status; + struct bt_gatt_attr *attr_base; + /* Number of attributes successfully added */ + uint8_t attr_count; +}; + +enum nble_gatt_wr_flag { + NBLE_GATT_WR_FLAG_REPLY = 1, + NBLE_GATT_WR_FLAG_PREP = 2, +}; + +struct nble_gatt_wr_evt { + struct bt_gatt_attr *attr; + uint16_t conn_handle; + uint16_t offset; + uint8_t flag; /* Cf. enum nble_gatt_wr_flag */ +}; + +struct nble_gatt_wr_exec_evt { + uint16_t conn_handle; + uint8_t flag; +}; + +struct nble_gatt_rd_evt { + struct bt_gatt_attr *attr; + uint16_t conn_handle; + uint16_t offset; +}; + +struct nble_gatts_rd_reply_params { + uint16_t conn_handle; + uint16_t offset; + int32_t status; +}; + +struct nble_gatts_wr_reply_params { + uint16_t conn_handle; + int32_t status; +}; + +struct nble_gatt_notif_ind_params { + const struct bt_gatt_attr *attr; + uint16_t offset; +}; + +struct nble_gatt_send_notif_params { + /* Function to be invoked when buffer is freed */ + bt_gatt_notify_sent_func_t cback; + uint16_t conn_handle; + struct nble_gatt_notif_ind_params params; +}; + +struct nble_gatt_notif_rsp { + bt_gatt_notify_sent_func_t cback; + int status; + uint16_t conn_handle; + struct bt_gatt_attr *attr; +}; + +struct nble_gatt_send_ind_params { + /* Function to be invoked when buffer is freed */ + bt_gatt_indicate_func_t cback; + uint16_t conn_handle; + struct nble_gatt_notif_ind_params params; +}; + +struct nble_gatt_ind_rsp { + bt_gatt_indicate_func_t cback; + int status; + uint16_t conn_handle; + struct bt_gatt_attr *attr; +}; + +struct nble_gatt_handle_range { + uint16_t start_handle; + uint16_t end_handle; +}; + +struct nble_gattc_primary { + uint16_t handle; + struct nble_gatt_handle_range range; + struct bt_uuid_128 uuid; +}; + +struct nble_gattc_included { + uint16_t handle; + struct nble_gatt_handle_range range; + struct bt_uuid_128 uuid; +}; + +struct nble_gattc_characteristic { + uint16_t handle; + uint8_t prop; + uint16_t value_handle; + struct bt_uuid_128 uuid; +}; + +struct nble_gattc_descriptor { + uint16_t handle; + struct bt_uuid_128 uuid; +}; + +struct nble_gattc_discover_rsp { + int32_t status; + void *user_data; + uint16_t conn_handle; + uint8_t type; +}; + +struct nble_gatts_attribute_rsp { + int32_t status; + struct bt_gatt_attr *attr; + void *user_data; +}; + +void nble_gatts_rd_reply_req(const struct nble_gatts_rd_reply_params *, uint8_t *, uint16_t); + +void nble_gatts_wr_reply_req(const struct nble_gatts_wr_reply_params *p_params); + +void nble_gatt_register_req(const struct nble_gatt_register_req *p_param, + uint8_t *p_buf, + uint16_t len); + +struct nble_gatt_attr_handles { + uint16_t handle; +}; + +void on_nble_gatt_register_rsp(const struct nble_gatt_register_rsp *p_param, + const struct nble_gatt_attr_handles *p_attrs, uint8_t len); + +void on_nble_gatts_write_evt(const struct nble_gatt_wr_evt *p_evt, + const uint8_t *p_buf, uint8_t buflen); + +void on_nble_gatts_write_exec_evt(const struct nble_gatt_wr_exec_evt *evt); + +void on_nble_gatts_read_evt(const struct nble_gatt_rd_evt *p_evt); + +void nble_gatt_send_notif_req(const struct nble_gatt_send_notif_params *p_params, + const uint8_t *p_value, uint16_t length); + +void nble_gatt_send_ind_req(const struct nble_gatt_send_ind_params *p_params, + const uint8_t *p_value, uint8_t length); + +void on_nble_gatts_send_notif_rsp(const struct nble_gatt_notif_rsp *rsp); + +void on_nble_gatts_send_ind_rsp(const struct nble_gatt_ind_rsp *rsp); + +#define DISCOVER_FLAGS_UUID_PRESENT 1 + +struct nble_discover_params { + void *user_data; + struct bt_uuid_128 uuid; + struct nble_gatt_handle_range handle_range; + uint16_t conn_handle; + uint8_t type; + uint8_t flags; +}; + +void nble_gattc_discover_req(const struct nble_discover_params *req); + +void on_nble_gattc_discover_rsp(const struct nble_gattc_discover_rsp *rsp, + const uint8_t *data, uint8_t data_len); + +/* + * GATT Attribute stream structure. + * + * This structure is a "compressed" copy of @ref bt_gatt_attr. + * UUID pointer and user_data pointer are used as offset into buffer itself. + * The offset is from the beginning of the buffer. therefore a value of 0 + * means that UUID or user_data is not present. + */ +struct ble_gatt_attr { + /* Attribute permissions */ + uint16_t perm; + /* Attribute variable data size */ + uint16_t data_size; + /* Attribute variable data: always starts with the UUID and data follows */ + uint8_t data[]; +}; + +struct ble_gattc_read_params { + void *user_data; + uint16_t conn_handle; + uint16_t handle; + uint16_t offset; +}; + +struct ble_gattc_read_multiple_params { + void *user_data; + uint16_t conn_handle; +}; + +struct ble_gattc_read_rsp { + int status; + void *user_data; + uint16_t conn_handle; + uint16_t handle; + uint16_t offset; +}; + +/* forward declaration */ +struct bt_gatt_write_params; + +typedef void (*bt_att_func_t)(struct bt_conn *conn, uint8_t err, + const struct bt_gatt_write_params *wr_params); + +struct bt_gatt_write_params { + /* Function invoked upon write response */ + bt_att_func_t func; + /* User specific data */ + void *user_data[2]; +}; + +struct ble_gattc_write_params { + uint16_t conn_handle; + uint16_t handle; + uint16_t offset; + /* different than 0 if response required */ + uint8_t with_resp; + struct bt_gatt_write_params wr_params; +}; + +struct ble_gattc_write_rsp { + uint16_t conn_handle; + int status; + uint16_t handle; + uint16_t len; + struct bt_gatt_write_params wr_params; +}; + +void nble_gattc_read_req(const struct ble_gattc_read_params *params); + +void on_nble_gattc_read_rsp(const struct ble_gattc_read_rsp *rsp, + uint8_t *data, uint8_t data_len); + +void nble_gattc_read_multiple_req( + const struct ble_gattc_read_multiple_params *params, + const uint16_t *data, uint16_t data_len); + +void on_nble_gattc_read_multiple_rsp(const struct ble_gattc_read_rsp *rsp, + uint8_t *data, uint8_t data_len); + +void nble_gattc_write_req(const struct ble_gattc_write_params *params, + const uint8_t *buf, uint8_t buflen); + +void on_nble_gattc_write_rsp(const struct ble_gattc_write_rsp *rsp); + +void bt_gatt_connected(struct bt_conn *conn); +void bt_gatt_disconnected(struct bt_conn *conn); + + +struct ble_gattc_value_evt { + uint16_t conn_handle; + int status; + uint16_t handle; + uint8_t type; +}; + +void on_nble_gattc_value_evt(const struct ble_gattc_value_evt *evt, + uint8_t *buf, uint8_t buflen); + +#endif /* GATT_INTERNAL_H_ */ diff --git a/system/libarc32_arduino101/framework/src/services/ble_service/nble_driver.c b/system/libarc32_arduino101/framework/src/services/ble_service/nble_driver.c new file mode 100644 index 00000000..4a128f13 --- /dev/null +++ b/system/libarc32_arduino101/framework/src/services/ble_service/nble_driver.c @@ -0,0 +1,354 @@ +/* + * 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 "nble_driver.h" + +#include "portable.h" + +/* ipc/rpc uart interface */ +#include "os/os.h" +#include "infra/ipc_requests.h" +#include "infra/port.h" +#include "infra/log.h" +#include "drivers/soc_gpio.h" +#include "drivers/ipc_uart_ns16550.h" + +#include "platform.h" + +#include "rpc.h" + +#include "util/misc.h" + +#include "infra/time.h" + +#include "infra/ipc_uart.h" + + + +/* + * Macro definition for reset pin + * Curie and other board - Everything should be working out of the box + */ +#define BLE_SW_CLK_PIN 27 +#define BLE_SWDIO_PIN 6 +#define RESET_PIN BLE_SWDIO_PIN +#define QRK_BLE_INT 5 + +static uint16_t rpc_port_id; +static list_head_t m_rpc_tx_q; + +extern void on_nble_curie_log(char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + log_vprintk(LOG_LEVEL_INFO, LOG_MODULE_BLE, fmt, args); + va_end(args); +} + +/* + * When we set val to 1 it will wake up the remote (BLE Core), setting it to 0 + * will allow remote to sleep. + */ +static int nble_wake_assert(bool val) +{ + val = true; + uint8_t ret = soc_gpio_write(SOC_GPIO_32, QRK_BLE_INT, val); + + //pr_debug(LOG_MODULE_BLE, "BLE wake up: %d", val); + if (ret != DRV_RC_OK) { + pr_debug(LOG_MODULE_BLE, "Error setting QRK_BLE_INT %d", ret); + } + return ret; +} + +/** + * Function registered to be invoked when the IPC starts or ends + * a transmission session. + * + * This sets the wake state of ble core to either keep it awake at start of a tx + * or to allow sleep at the end of a TX. + * + * @param wake_state false if remote may sleep (end of transmission). + * true, wake remote to start a transmission session. + * @param ignored not used + */ +static void nble_ipc_tx_wake_cb(bool wake_state, void *ignored) +{ + nble_wake_assert(wake_state); +} + +static int nble_interface_init(void) +{ + DRIVER_API_RC ret = DRV_RC_OK; + /* setup IPC UART reference */ + ipc_uart_ns16550_set_tx_cb(nble_ipc_tx_wake_cb, NULL); + + /* Configure GPIO as output and high by default */ + gpio_cfg_data_t config; + config.gpio_type = GPIO_OUTPUT; + ret = soc_gpio_set_config(SOC_GPIO_32, QRK_BLE_INT, &config); + if (ret != DRV_RC_OK) + return -1; + + list_init(&m_rpc_tx_q); + + ret = nble_wake_assert(1); + return ret; +} + +void uart_ipc_disable(void) +{ + ipc_uart_ns16550_disable(SYNC_CHANNEL); +} + +static void *m_rpc_channel; + +struct rpc_tx_elt { + list_t l; + uint16_t length; + uint8_t data[0]; +}; + +/** + * Try to send a element of the RPC waiting list + * + * @param l Pointer to the list element to try to send on the UART + * @return + */ +static int uart_rpc_try_tx(list_t *l) +{ + struct rpc_tx_elt *p_elt; + + /* Retrieve the RPC TX element from the list pointer */ + p_elt = container_of(l, struct rpc_tx_elt, l); + + /* Try to send the element payload */ + return ipc_uart_ns16550_send_pdu(m_rpc_channel, + p_elt->length, + p_elt->data); +} + +/** + * Try to send an RPC TX queue element during the free operation + * of the previous message. This is invoked under interrupt context + * and therefore does not require protection. It is also expected + * that the tx operation can not fail. + */ +static void uart_rpc_try_tx_on_free(void) +{ + list_t *l; + int ret; + + /* Get next element in tx q */ + l = list_get(&m_rpc_tx_q); + if (l) { + ret = uart_rpc_try_tx(l); + + /* It is not possible to fail when called on free event */ + assert(ret == IPC_UART_ERROR_OK); + } +} + +/** + * Try to send an RPC TX queue element after enqueuing an element to the + * RPC TX queue. At this point the state of the UART driver is not known + * and there could be a transmission in progress, so the procedure is to + * protect from interruption, pick (not get) the first element and only + * get it out of the queue if the tx operation was successful. + */ +static void uart_rpc_try_tx_on_add(void) +{ + list_t *l; + int ret; + + int flags = interrupt_lock(); + + /* Pick next element in tx q */ + l = m_rpc_tx_q.head; + if (l) { + ret = uart_rpc_try_tx(l); + + /* If it was sent correctly, remove it from the queue */ + if (ret == IPC_UART_ERROR_OK) { + l = list_get(&m_rpc_tx_q); + } + } + + interrupt_unlock(flags); +} + +/** + * Function handling the events from the UART IPC (irq). + * + * @param channel Channel on which the event applies + * @param request IPC_MSG_TYPE_MESSAGE when a new message was received, + * IPC_MSG_TYPE_FREE when the previous message was sent and can be freed. + * @param len Length of the data + * @param p_data Pointer to the data + * @return 0 + */ +static int uart_ipc_rpc_cback(int channel, int request, int len, void *p_data) +{ + switch (request) { + case IPC_MSG_TYPE_MESSAGE: { +#ifdef CONFIG_RPC_IN + /* if BLE service is available, handle it in BLE service context */ + struct ble_rpc_callin *rpc = (void *) message_alloc( + sizeof(*rpc), NULL); +if (NULL == rpc) +{ + panic(-1); +} + MESSAGE_ID(&rpc->msg) = 0; + MESSAGE_LEN(&rpc->msg) = sizeof(*rpc); + MESSAGE_SRC(&rpc->msg) = rpc_port_id; + MESSAGE_DST(&rpc->msg) = rpc_port_id; + MESSAGE_TYPE(&rpc->msg) = TYPE_INT; + rpc->p_data = p_data; + rpc->len = len; + if (port_send_message(&rpc->msg) != E_OS_OK) + panic(-1); +#endif + } + break; + case IPC_MSG_TYPE_FREE: + { + /* Free the message */ + struct rpc_tx_elt *p_elt; + + p_elt = container_of(p_data, struct rpc_tx_elt, data); + bfree(p_elt); + + //pr_debug(LOG_MODULE_IPC, "%s:%p", __FUNCTION__, p_elt); + + /* Try to send another message immediately */ + uart_rpc_try_tx_on_free(); + break; + } + default: + /* Free the message */ + bfree(p_data); + pr_error(LOG_MODULE_BLE, "Unsupported RPC request"); + break; + } + return 0; +} + +uint8_t *rpc_alloc_cb(uint16_t length) +{ + struct rpc_tx_elt *p_elt; + + //pr_info(0, "%s", __FUNCTION__); + + p_elt = balloc(length + offsetof(struct rpc_tx_elt, data), NULL); + //pr_debug(LOG_MODULE_IPC, "%s:%p", __FUNCTION__, p_elt); + assert(p_elt != NULL); + + /* Save the length of the buffer */ + p_elt->length = length; + + return p_elt->data; +} + +/* called under NON-interrupt context */ +void rpc_transmit_cb(uint8_t *p_buf, uint16_t length) +{ + struct rpc_tx_elt *p_elt; + + p_elt = container_of(p_buf, struct rpc_tx_elt, data); + + list_add(&m_rpc_tx_q, &p_elt->l); + + uart_rpc_try_tx_on_add(); +} + +/* nble reset is achieved by asserting low the SWDIO pin. + * However, the BLE Core chip can be in SWD debug mode, and NRF_POWER->RESET = 0 due to, + * other constraints: therefore, this reset might not work everytime, especially after + * flashing or debugging. + */ +void nble_driver_init(void) +{ + uint32_t delay_until; + + nble_interface_init(); + /* Setup UART0 for BLE communication, HW flow control required */ + SET_PIN_MODE(18, QRK_PMUX_SEL_MODEA); /* UART0_RXD */ + SET_PIN_MODE(19, QRK_PMUX_SEL_MODEA); /* UART0_TXD */ + SET_PIN_MODE(40, QRK_PMUX_SEL_MODEB); /* UART0_CTS_B */ + SET_PIN_MODE(41, QRK_PMUX_SEL_MODEB); /* UART0_RTS_B */ + + ipc_uart_init(0); + + //while (1) + //{} + /* RESET_PIN depends on the board and the local configuration: check top of file */ + gpio_cfg_data_t pin_cfg = { .gpio_type = GPIO_OUTPUT }; + + soc_gpio_set_config(SOC_GPIO_32, RESET_PIN, &pin_cfg); + //soc_gpio_set_config(SOC_GPIO_32_ID, BLE_SW_CLK_PIN, &pin_cfg); + /* Reset hold time is 0.2us (normal) or 100us (SWD debug) */ + soc_gpio_write(SOC_GPIO_32, RESET_PIN, 0); + /* Wait for ~1ms */ + delay_until = get_uptime_32k() + 32768; + + /* Open the UART channel for RPC while Nordic is in reset */ + m_rpc_channel = ipc_uart_channel_open(RPC_CHANNEL, uart_ipc_rpc_cback); + + while (get_uptime_32k() < delay_until); + + /* De-assert the reset */ + soc_gpio_write(SOC_GPIO_32, RESET_PIN, 1); + + + /* Set back GPIO to input to avoid interfering with external debugger */ + pin_cfg.gpio_type = GPIO_INPUT; + soc_gpio_set_config(SOC_GPIO_32, RESET_PIN, &pin_cfg); +} + + +void nble_driver_configure(T_QUEUE queue, void (*handler)(struct message*, void*)) +{ + rpc_port_id = port_alloc(queue); + assert(rpc_port_id != 0); + port_set_handler(rpc_port_id, handler, NULL); + pr_debug(LOG_MODULE_BLE, "%s: done port: %d", __func__, rpc_port_id); +} + + +void on_nble_curie_init(void) +{ + nble_driver_init(); +} + + diff --git a/system/libarc32_arduino101/framework/src/services/ble_service/nble_driver.h b/system/libarc32_arduino101/framework/src/services/ble_service/nble_driver.h new file mode 100644 index 00000000..7ea15b44 --- /dev/null +++ b/system/libarc32_arduino101/framework/src/services/ble_service/nble_driver.h @@ -0,0 +1,55 @@ +/* + * 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 NBLE_DRIVER_H_ +#define NBLE_DRIVER_H_ + +#include "os/os.h" +#include "infra/message.h" + +struct ble_rpc_callin { + struct message msg; /**< Message header, MUST be first element of structure */ + uint8_t *p_data; /**< RPC buffer, must be freed after deserializing */ + uint16_t len; /**< length of above buffer */ +}; + +/** + * This resets and initializes the uart/ipc mechanism of nble. + * + * This this will trigger the call to @ref on_nble_up indicating that rpc + * mechanism is up and running. + */ +void nble_driver_init(void); + +void nble_driver_configure(T_QUEUE queue, void (*handler)(struct message*, void*)); + +void uart_ipc_disable(void); + +#endif /* NBLE_DRIVER_H_ */ diff --git a/variants/arduino_101/variant.cpp b/variants/arduino_101/variant.cpp index 0fe85c2d..72e75aae 100644 --- a/variants/arduino_101/variant.cpp +++ b/variants/arduino_101/variant.cpp @@ -22,6 +22,11 @@ #include "cfw_platform.h" #include "platform.h" +// Add for debug corelib +#ifdef CONFIGURE_DEBUG_CORELIB_ENABLED +#include +#endif + /* * Arduino 101 @@ -230,6 +235,11 @@ void initVariant( void ) variantAdcInit(); cfw_platform_init(); + + // Add for debug corelib + #ifdef CONFIGURE_DEBUG_CORELIB_ENABLED + log_init(); + #endif } #ifdef __cplusplus From acfec8dc47d36eaff9598197a47d9a4717ad63b1 Mon Sep 17 00:00:00 2001 From: lianggao Date: Fri, 9 Sep 2016 08:25:31 +0800 Subject: [PATCH 02/14] Jira Tickets and Bug fix 1. Jira 541 Peripheral Start fails on X Number of Attributes -Change the interface and add return value 2. Fix BLE Peripheral is not advertising -Add a delay after register the profile. 3. Modify some comments based on code review 4. Jira 544 BLE peripheral disconnect and end functions -The state not aligned and make disconnect failed. Update when connect and disconnect event received 5. Delete the duplicated code that base class has implemented 6. Fix Jira 665 BLECentral Preview -- compile issue when instantiating BLE descriptor -Fix the build error. Note: i . The current code only support one descriptor on characteristic. ii . The central discover logic need more twist. iii. Now is so long and can't process CCCD and another characteristic. iv . The library will support one other descriptor except CCCD. 7. Improve the discover logic and support another descriptor i. Improve the discover logic ii. Support another the descriptor and CCCD at same time. 8. Modify the comments and delete the unused API 9. Fix Jira 670 A compile error occurs with two BLE methods with the same name i. Add method uuid_cstr in BLEAttribute class. ii. Remove the duplicate file. 10. Fix Jira 672 BLE documentation in code needs to specify units i. Change the scan and connection interval's unit to ms. ii. Unify the advertising interval unit as millisecond iii. Delete some unused code. 11. Fix Jria 671 Support update connection interval in central/peripheral i. Central can update the connection interval. ii. Unify the interval unit to ms at user API. 12. Adjust the example comments and functions i. Fix Jira 675 - BLE callbacks should use passed arguments instead of global variables ii. Adjust the code struture to align with Arduino's requirement 13. Fix Jira 680 LED and LED Central sketches need some work -Add a delay to make sure profile register process success. -The UART send too fast may makes the profile registration failed. 14. Fix Jira 673 BLE Api should pass arguments that are typedef 15. Fix Jira 671 Support update connection interval in central/peripheral i. Add peripheral update the connection interval feature. ii. Update the library. 16. Fix Jira 687 Edit the keyword.txt of CurieBLE to reflect Library changes 17. Make example function ready. But need to resolve Jira 675 18. Fix Jira 676 Review Edit Update BLE User Manual with China Flex 19. Fix Jira 685 BLE Peripheral sketches shall state which associated Central sketch to use 20. Fix Jira 588 - BLE Corrupted Long Write i. Modify the BLECharacteristic to implement the Long write feature. 21. Fix Jira 683 Typecasting and Pointers should be avoided in sketch i. Add write in Characteristic template ii. Add method in BLE Role class to avoid typecaste iii. Modify the advertise address parameter and related example sketches 22. Fix Jira 704 Type Characteristic should be documented 23. Fix Jira 684 Documentation about the central/peripheral --- .../BatteryMonitor/BatteryMonitor.ino | 9 +- .../CurieBLE/examples/ButtonLED/ButtonLED.ino | 15 +- .../examples/CallbackLED/CallbackLED.ino | 21 +- .../examples/IMUBleCentral/IMUBleCentral.ino | 145 +++--- .../IMUBleNotification/IMUBleNotification.ino | 93 ++-- libraries/CurieBLE/examples/LED/LED.ino | 12 +- .../examples/LEDCentral/LEDCentral.ino | 161 ++++--- .../CurieBLE/examples/MIDIBLE/MIDIBLE.ino | 37 +- .../CurieBLE/examples/Scanning/Scanning.ino | 141 +++--- libraries/CurieBLE/keywords.txt | 33 +- libraries/CurieBLE/src/BLEAttribute.cpp | 32 +- libraries/CurieBLE/src/BLEAttribute.h | 74 +++- libraries/CurieBLE/src/BLECentral.cpp | 42 +- libraries/CurieBLE/src/BLECentral.h | 58 ++- libraries/CurieBLE/src/BLECentralHelper.cpp | 52 --- libraries/CurieBLE/src/BLECentralHelper.h | 14 - libraries/CurieBLE/src/BLECentralRole.cpp | 44 +- libraries/CurieBLE/src/BLECentralRole.h | 72 +-- libraries/CurieBLE/src/BLECharacteristic.cpp | 79 +++- libraries/CurieBLE/src/BLECharacteristic.h | 101 +++-- libraries/CurieBLE/src/BLECommon.h | 71 ++- libraries/CurieBLE/src/BLEDescriptor.cpp | 33 +- libraries/CurieBLE/src/BLEDescriptor.h | 36 +- libraries/CurieBLE/src/BLEHelper.cpp | 87 ++-- libraries/CurieBLE/src/BLEHelper.h | 128 +++++- libraries/CurieBLE/src/BLEPeripheral.cpp | 56 ++- libraries/CurieBLE/src/BLEPeripheral.h | 134 +++--- .../CurieBLE/src/BLEPeripheralHelper.cpp | 16 +- libraries/CurieBLE/src/BLEPeripheralHelper.h | 98 ++++- libraries/CurieBLE/src/BLEPeripheralRole.cpp | 66 ++- libraries/CurieBLE/src/BLEPeripheralRole.h | 102 +++-- libraries/CurieBLE/src/BLEProfile.cpp | 413 +++++++++++++----- libraries/CurieBLE/src/BLEProfile.h | 67 ++- libraries/CurieBLE/src/BLERoleBase.cpp | 24 +- libraries/CurieBLE/src/BLERoleBase.h | 34 +- libraries/CurieBLE/src/BLEService.cpp | 12 +- libraries/CurieBLE/src/BLEService.h | 10 +- .../CurieBLE/src/BLETypedCharacteristic.h | 96 ++++ .../CurieBLE/src/BLETypedCharacteristics.h | 143 ++++++ libraries/CurieBLE/src/internal/ble_client.c | 76 ++-- libraries/CurieBLE/src/internal/ble_client.h | 4 +- .../drivers/bluetooth/conn_internal.h | 125 ------ 42 files changed, 2019 insertions(+), 1047 deletions(-) delete mode 100644 system/libarc32_arduino101/drivers/bluetooth/conn_internal.h diff --git a/libraries/CurieBLE/examples/BatteryMonitor/BatteryMonitor.ino b/libraries/CurieBLE/examples/BatteryMonitor/BatteryMonitor.ino index 7386087f..0facfe09 100644 --- a/libraries/CurieBLE/examples/BatteryMonitor/BatteryMonitor.ino +++ b/libraries/CurieBLE/examples/BatteryMonitor/BatteryMonitor.ino @@ -6,7 +6,10 @@ #include /* - This sketch example partially implements the standard Bluetooth Low-Energy Battery service. + This sketch can work with UpdateConnectionInterval. + You can also use an android or IOS app that supports notifications + This sketch example partially implements the standard Bluetooth Low-Energy Battery service + and connection interval paramater update. For more information: https://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx */ @@ -15,9 +18,9 @@ BLEPeripheral blePeripheral; // BLE Peripheral Device (the board you're pr BLEService batteryService("180F"); // BLE Battery Service // BLE Battery Level Characteristic" -BLEUnsignedCharCharacteristic batteryLevelChar("2A19", // standard 16-bit characteristic UUID +BLEUnsignedCharCharacteristic batteryLevelChar("2A19", // standard 16-bit characteristic UUID defined in the URL above BLERead | BLENotify); // remote clients will be able to -// get notifications if this characteristic changes + // get notifications if this characteristic changes int oldBatteryLevel = 0; // last battery level reading from analog input long previousMillis = 0; // last time the battery level was checked, in ms diff --git a/libraries/CurieBLE/examples/ButtonLED/ButtonLED.ino b/libraries/CurieBLE/examples/ButtonLED/ButtonLED.ino index b4cd873a..9cb38a2c 100644 --- a/libraries/CurieBLE/examples/ButtonLED/ButtonLED.ino +++ b/libraries/CurieBLE/examples/ButtonLED/ButtonLED.ino @@ -3,19 +3,28 @@ * See the bottom of this file for the license terms. */ + /* This examples needs a button connected similarly as described here + https://www.arduino.cc/en/Tutorial/Button + The only difference is that instead of connecting to pin 2 connect to pin 4 + After the sketch starts connect to a BLE app on a phone and set notification to the Characteristic and you should see it update + whenever the button is pressed. This sketch is not written to pair with any of the central examples. + */ + #include const int ledPin = 13; // set ledPin to on-board LED const int buttonPin = 4; // set buttonPin to digital pin 4 BLEPeripheral blePeripheral; // create peripheral instance -BLEService ledService("19B10010-E8F2-537E-4F6C-D104768A1214"); // create service +BLEService ledService("19B10010-E8F2-537E-4F6C-D104768A1214"); // create service with a 128-bit UUID (32 characters exclusive of dashes). + // Long UUID denote custom user created UUID // create switch characteristic and allow remote device to read and write BLECharCharacteristic ledCharacteristic("19B10011-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite); // create button characteristic and allow remote device to get notifications BLECharCharacteristic buttonCharacteristic("19B10012-E8F2-537E-4F6C-D104768A1214", BLERead | BLENotify); // allows remote device to get notifications +// Note use of Typed Characteristics. These previous 2 characeristics are of the type char void setup() { Serial.begin(9600); @@ -32,6 +41,7 @@ void setup() { blePeripheral.addAttribute(ledCharacteristic); blePeripheral.addAttribute(buttonCharacteristic); + // set initial values for led and button characteristic ledCharacteristic.setValue(0); buttonCharacteristic.setValue(0); @@ -59,10 +69,13 @@ void loop() { if (ledCharacteristic.written() || buttonChanged) { // update LED, either central has written to characteristic or button state has changed + // if you are using a phone or a BLE central device that is aware of this characteristic, writing a value of 0x40 for example + // Will be interpreted as written if (ledCharacteristic.value()) { Serial.println("LED on"); digitalWrite(ledPin, HIGH); } else { + // If central writes a 0 value then it is interpreted as no value and turns off the LED Serial.println("LED off"); digitalWrite(ledPin, LOW); } diff --git a/libraries/CurieBLE/examples/CallbackLED/CallbackLED.ino b/libraries/CurieBLE/examples/CallbackLED/CallbackLED.ino index 26946cbf..3f516ca5 100644 --- a/libraries/CurieBLE/examples/CallbackLED/CallbackLED.ino +++ b/libraries/CurieBLE/examples/CallbackLED/CallbackLED.ino @@ -2,13 +2,24 @@ * Copyright (c) 2016 Intel Corporation. All rights reserved. * See the bottom of this file for the license terms. */ + + // This example can work with LEDCentral + // You should see the LED blink on and off + // This example demonstrates the use of Callback or event Handlers responding to events + // BLECoonected, BLEDisconnected and BLEWritten are events. + // To test interactively use a Phone app like nrf Controller (Android) or Light Blue (iOS) + // Connect to BLE device named LEDCB and explore characteristic with UUID 19B10001-E8F2-537E-4F6C-D104768A1214 + // Writing a byte value such as 0x40 should turn on the LED + // Writng a byte value of 0x00 should turn off the LED #include const int ledPin = 13; // set ledPin to use on-board LED BLEPeripheral blePeripheral; // create peripheral instance +BLECentralHelper *bleCentral1 = NULL; // peer central device -BLEService ledService("19B10000-E8F2-537E-4F6C-D104768A1214"); // create service +BLEService ledService("19B10000-E8F2-537E-4F6C-D104768A1214"); // create service with a 128-bit UUID (32 characters exclusive of dashes). + // Long UUID denote custom user created UUID // create switch characteristic and allow remote device to read and write BLECharCharacteristic switchChar("19B10001-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite); @@ -45,10 +56,14 @@ void loop() { blePeripheral.poll(); } +// The function parameter (BLEHelper& central) is for peripheral devices +// This enable us to have access to the central's data like its bluetooth address + void blePeripheralConnectHandler(BLEHelper& central) { // central connected event handler + bleCentral1 = blePeripheral.getPeerCentralBLE(central); Serial.print("Connected event, central: "); - Serial.println(central.address()); + Serial.println(bleCentral1->address()); } void blePeripheralDisconnectHandler(BLEHelper& central) { @@ -57,6 +72,8 @@ void blePeripheralDisconnectHandler(BLEHelper& central) { Serial.println(central.address()); } +// In addtion to the BLECentral& central parameter, we also have to have to BLECharacteristic& characteristic parameter + void switchCharacteristicWritten(BLEHelper& central, BLECharacteristic& characteristic) { // central wrote new value to characteristic, update LED Serial.print("Characteristic event, written: "); diff --git a/libraries/CurieBLE/examples/IMUBleCentral/IMUBleCentral.ino b/libraries/CurieBLE/examples/IMUBleCentral/IMUBleCentral.ino index 0523ed55..e14753db 100644 --- a/libraries/CurieBLE/examples/IMUBleCentral/IMUBleCentral.ino +++ b/libraries/CurieBLE/examples/IMUBleCentral/IMUBleCentral.ino @@ -1,31 +1,28 @@ /* - 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 -*/ + * Copyright (c) 2016 Intel Corporation. All rights reserved. + * See the bottom of this file for the license terms. + */ #include /* - This sketch example partially implements the standard Bluetooth Low-Energy Battery service. - For more information: https://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx + This sketch example works with IMUBleNotification.ino + IMUBleNotification.ino will send notification to this sketch. + This sketch will receive the notifications and out put the received data in the serial monitor + It also illustrates using a non typed characteristic + Set the baud rate to 115200 on the serial monitor to accomodate the speed of constant data updates from IMU subsystem + */ #define MAX_IMU_RECORD 1 -struct bt_le_conn_param conn_param = {0x18, 0x28, 0, 400}; +ble_conn_param_t conn_param = {30.0, // minimum interval in ms 7.5 - 4000 + 50.0, // maximum interval in ms 7.5 - + 0, // latency + 4000 // timeout in ms 100 - 32000ms + }; +// define a structure that will serve as buffer for holding IMU data + typedef struct { int index; unsigned int slot[3]; @@ -38,19 +35,64 @@ BLEService bleImuService("F7580001-153E-D4F6-F26D-43D8D98EEB13"); BLECharacteristic bleImuChar("F7580003-153E-D4F6-F26D-43D8D98EEB13", // standard 128-bit characteristic UUID BLERead | BLENotify, sizeof(imuBuf)); // remote clients will be able to // get notifications if this characteristic changes + // We have a third parameter which is the size of imyBuffer. This is because it is a non-typed characteristic + // If we are only writing to this characteristic we can set this buffer to 512 bytes + // But because of the limitation of the Nordic FW, please do not set this to more than 128 if you intend to read it. + // MAX_IMU_RECORD value is 1 so we are safe +// function prototype for function that determines if the advertising data is found +bool adv_found(uint8_t type, + const uint8_t *dataPtr, + uint8_t data_len, + const bt_addr_le_t *addrPtr); + +void setup() +{ + // This is set to higher baud rate because accelerometer data changes very quickly + Serial.begin(115200); // initialize serial communication + pinMode(13, OUTPUT); // initialize the LED on pin 13 to indicate when a central is connected + // set the event handeler function for the bleImuChar characteristic + bleImuChar.setEventHandler(BLEWritten, bleImuCharacteristicWritten); + + bleCentral.addAttribute(bleImuService); // Add the BLE IMU service + bleCentral.addAttribute(bleImuChar); // Add the BLE IMU characteristic + + // Setup callback whenever a Peripheral advertising data is found) + bleCentral.setAdvertiseHandler(adv_found); + bleCentral.setEventHandler(BLEConnected, ble_connected); + + /* Now activate the BLE device. It will start continuously transmitting BLE + advertising packets and will be visible to remote BLE central devices + until it receives a new connection */ + bleCentral.begin(); +} + + +void loop() +{ + // we put a 2 second delay + // Even though this looks empty, since we setup 2 callbacks by setting the advertising handler adv_found + // and event handler for BLEConnected, we basically are lsitening for advertising data and connected events. + + delay(2000); +} void ble_connected(BLEHelper &role) { - BLEPeripheralHelper&peripheral = *(BLEPeripheralHelper*)(&role); + // since we are a central device we create a BLEPeripheralHelper peripheral + BLEPeripheralHelper *peripheral = bleCentral.getPeerPeripheralBLE(role); Serial.println("Connected"); // Start discovery the profiles in peripheral device - peripheral.discover(); + peripheral->discover(); } -void bleImuCharacteristicWritten(BLEHelper& central, BLECharacteristic& characteristic) +void bleImuCharacteristicWritten(BLEHelper& peripheral, BLECharacteristic& characteristic) { // Peripheral wrote new value to characteristic by Notification/Indication + // We have to use pointers because we are NOT using a type characteristic + // In other examples our charcteristics are typed so we not have to use pointers and can access the value directly + // The parent non typde characteristic class, the value method gives a pointer to the characteristic value + const unsigned char *cvalue = characteristic.value(); const imuFrameType *value = (const imuFrameType *)cvalue; Serial.print("\r\nCharacteristic event, written: "); @@ -64,32 +106,32 @@ void bleImuCharacteristicWritten(BLEHelper& central, BLECharacteristic& characte } bool adv_found(uint8_t type, - const uint8_t *data, + const uint8_t *dataPtr, uint8_t data_len, - void *user_data) + const bt_addr_le_t *addrPtr) { - bt_addr_le_t *addr = (bt_addr_le_t *)user_data; int i; Serial.print("[AD]:"); Serial.print(type); Serial.print(" data_len "); Serial.println(data_len); - + // 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. switch (type) { case BT_DATA_UUID128_SOME: case BT_DATA_UUID128_ALL: { - if (data_len % MAX_UUID_SIZE != 0) + if (data_len % UUID_SIZE_128 != 0) { Serial.println("AD malformed"); return true; } - struct bt_uuid * serviceuuid = bleImuService.uuid(); - for (i = 0; i < data_len; i += MAX_UUID_SIZE) + for (i = 0; i < data_len; i += UUID_SIZE_128) { - if (memcmp (((struct bt_uuid_128*)serviceuuid)->val, &data[i], MAX_UUID_SIZE) != 0) + if (bleImuService.uuidCompare(dataPtr + i, UUID_SIZE_128) == false) { continue; } @@ -102,7 +144,7 @@ bool adv_found(uint8_t type, } Serial.println("Connecting"); // Connect to peripheral - bleCentral.connect(addr, &conn_param); + bleCentral.connect(addrPtr, &conn_param); return false; } } @@ -111,33 +153,20 @@ bool adv_found(uint8_t type, return true; } -void setup() { - Serial.begin(115200); // initialize serial communication - pinMode(13, OUTPUT); // initialize the LED on pin 13 to indicate when a central is connected - - bleImuChar.setEventHandler(BLEWritten, bleImuCharacteristicWritten); - - /* Set a local name for the BLE device - This name will appear in advertising packets - and can be used by remote devices to identify this BLE device - The name can be changed but maybe be truncated based on space - left in advertisement packet */ - bleCentral.addAttribute(bleImuService); // Add the BLE IMU service - bleCentral.addAttribute(bleImuChar); // Add the BLE IMU characteristic - - /* Setup callback */ - bleCentral.setAdvertiseHandler(adv_found); - bleCentral.setEventHandler(BLEConnected, ble_connected); - - /* Now activate the BLE device. It will start continuously transmitting BLE - advertising packets and will be visible to remote BLE central devices - until it receives a new connection */ - bleCentral.begin(); -} +/* + 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. -void loop() -{ - delay(2000); -} + 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 +*/ diff --git a/libraries/CurieBLE/examples/IMUBleNotification/IMUBleNotification.ino b/libraries/CurieBLE/examples/IMUBleNotification/IMUBleNotification.ino index 4a179e2a..81ca3dec 100644 --- a/libraries/CurieBLE/examples/IMUBleNotification/IMUBleNotification.ino +++ b/libraries/CurieBLE/examples/IMUBleNotification/IMUBleNotification.ino @@ -1,27 +1,15 @@ /* - 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 -*/ + * Copyright (c) 2016 Intel Corporation. All rights reserved. + * See the bottom of this file for the license terms. + */ #include #include /* - This sketch example partially implements the standard Bluetooth Low-Energy Battery service. - For more information: https://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx + This sketch example works with IMUBleCentral.ino + This sketch will read IMU data from sensor and send notification to IMUBleCentral.ino + IMUBleCentral.ino will receive the Notifications and output the received data. */ #define MAX_IMU_RECORD 1 @@ -31,6 +19,7 @@ typedef struct { unsigned int slot[3]; } imuFrameType; +// Buffer to hold IMU data imuFrameType imuBuf[MAX_IMU_RECORD]; unsigned seqNum = 0; @@ -40,8 +29,8 @@ BLEService bleImuService("F7580001-153E-D4F6-F26D-43D8D98EEB13"); // Tx IMU data BLECharacteristic bleImuChar("F7580003-153E-D4F6-F26D-43D8D98EEB13", // standard 128-bit characteristic UUID BLERead | BLENotify, sizeof(imuBuf)); // remote clients will be able to // get notifications if this characteristic changes -void setup() { - +void setup() +{ Serial.begin(9600); // initialize serial communication pinMode(13, OUTPUT); // initialize the LED on pin 13 to indicate when a central is connected @@ -52,35 +41,21 @@ void setup() { The name can be changed but maybe be truncated based on space left in advertisement packet */ blePeripheral.setLocalName("Imu"); blePeripheral.setAdvertisedServiceUuid(bleImuService.uuid()); // add the service UUID - blePeripheral.addAttribute(bleImuService); // Add the BLE Battery service - blePeripheral.addAttribute(bleImuChar); // add the battery level characteristic + blePeripheral.addAttribute(bleImuService); // Add the Imu service + blePeripheral.addAttribute(bleImuChar); // add the Imu characteristic /* Now activate the BLE device. It will start continuously transmitting BLE advertising packets and will be visible to remote BLE central devices until it receives a new connection */ blePeripheral.begin(); - + // Start the IMU CurieIMU.begin(); } -void recordImuData(int index) { - /* Read IMU data. - */ - int ax, ay, az; - int gx, gy, gz; - - imuBuf[index].index = seqNum++; - CurieIMU.readMotionSensor(ax, ay, az, gx, gy, gz); - - imuBuf[index].slot[0] = (unsigned int)((ax << 16) | (ay & 0x0FFFF)); - imuBuf[index].slot[1] = (unsigned int)((az << 16) | (gx & 0x0FFFF)); - imuBuf[index].slot[2] = (unsigned int)((gy << 16) | (gz & 0x0FFFF)); - -} - - -void loop() { +void loop() +{ // listen for BLE peripherals to connect: + // Since we are a peripheral we need a central object to connect to BLECentralHelper central = blePeripheral.central(); // if a central is connected to peripheral: @@ -109,7 +84,7 @@ void loop() { sentTime = millis(); bleImuChar.setValue((unsigned char *)&(imuBuf[0]), sizeof(imuBuf)); } - } // while + } // end of while loop // when the central disconnects, turn off the LED: digitalWrite(13, LOW); @@ -118,3 +93,39 @@ void loop() { } } +// This function records the IMU data that we send to the central +void recordImuData(int index) +{ + /* Read IMU data. + */ + int ax, ay, az; + int gx, gy, gz; + + imuBuf[index].index = seqNum++; + CurieIMU.readMotionSensor(ax, ay, az, gx, gy, gz); + + // Encode the data into the buffer + imuBuf[index].slot[0] = (unsigned int)((ax << 16) | (ay & 0x0FFFF)); + imuBuf[index].slot[1] = (unsigned int)((az << 16) | (gx & 0x0FFFF)); + imuBuf[index].slot[2] = (unsigned int)((gy << 16) | (gz & 0x0FFFF)); + +} + + +/* + 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 +*/ diff --git a/libraries/CurieBLE/examples/LED/LED.ino b/libraries/CurieBLE/examples/LED/LED.ino index 263c7d5b..2c2c261d 100644 --- a/libraries/CurieBLE/examples/LED/LED.ino +++ b/libraries/CurieBLE/examples/LED/LED.ino @@ -2,7 +2,17 @@ * Copyright (c) 2016 Intel Corporation. All rights reserved. * See the bottom of this file for the license terms. */ - + + // This example can work with LEDCentral + // + // This example is similar to CallbackLED example in functionality + // It does not use callbacks. In the loop it interogates the connection state with central + // Checks if the characteristic is wriiten and turns the LED on or off accordingly + // To test interactively, use a phone app like nrf Controller (Android) or Light Blue (iOS) + // Connect to BLE device named LEDCB and explore characteristic with UUID 19B10001-E8F2-537E-4F6C-D104768A1214 + // Writing a byte value such as 0x40 should turn on the LED + // Writng a byte value of 0x00 should turn off the LED + #include BLEPeripheral blePeripheral; // BLE Peripheral Device (the board you're programming) diff --git a/libraries/CurieBLE/examples/LEDCentral/LEDCentral.ino b/libraries/CurieBLE/examples/LEDCentral/LEDCentral.ino index 04cd0971..b9b533b3 100644 --- a/libraries/CurieBLE/examples/LEDCentral/LEDCentral.ino +++ b/libraries/CurieBLE/examples/LEDCentral/LEDCentral.ino @@ -1,40 +1,88 @@ /* - 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 -*/ + * Copyright (c) 2016 Intel Corporation. All rights reserved. + * See the bottom of this file for the license terms. + */ -// This example can work with CallbackLED to show the profile read/write operation in central #include -struct bt_le_conn_param conn_param = {0x18, 0x28, 0, 400}; +/* + This example can work with CallbackLED and LED + to show how a central device can do charcteristic read and write operations. + A third party serial terminal is recommended to see outputs from central and peripheral device +*/ + +// set up connection params + +ble_conn_param_t conn_param = {30.0, // minimum interval in ms 7.5 - 4000 + 50.0, // maximum interval in ms 7.5 - + 0, // latency + 4000 // timeout in ms 100 - 32000ms + }; const int ledPin = 13; // set ledPin to use on-board LED BLECentral bleCentral; // create central instance -BLEPeripheralHelper *blePeripheral1 = NULL; +BLEPeripheralHelper *blePeripheral1 = NULL; // peer peripheral device -BLEService ledService("19B10000-E8F2-537E-4F6C-D104768A1214"); // create service +BLEService ledService("19B10000-E8F2-537E-4F6C-D104768A1214"); // create service with a 128-bit UUID (32 characters exclusive of dashes). + // Long UUID denote custom user created UUID BLECharCharacteristic switchChar("19B10001-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite);// create switch characteristic and allow remote device to read and write +// function prototype for function that determines if the advertising data is found +bool adv_found(uint8_t type, + const uint8_t *dataPtr, + uint8_t data_len, + const bt_addr_le_t *addrPtr); + +void setup() +{ + Serial.begin(9600); + pinMode(ledPin, OUTPUT); // use the LED on pin 13 as an output + + // add service and characteristic + bleCentral.addAttribute(ledService); + bleCentral.addAttribute(switchChar); + + // assign event handlers for connected, disconnected to central + bleCentral.setEventHandler(BLEConnected, bleCentralConnectHandler); + bleCentral.setEventHandler(BLEDisconnected, bleCentralDisconnectHandler); + + // advertise the service + bleCentral.setAdvertiseHandler(adv_found); + + // assign event handlers for characteristic + switchChar.setEventHandler(BLEWritten, switchCharacteristicWritten); + + bleCentral.begin(); + Serial.println(("Bluetooth device active, waiting for connections...")); +} + +void loop() +{ + static unsigned int counter = 0; + static char ledstate = 0; + delay(2000); + + if (blePeripheral1) + { + counter++; + if (counter % 3) + { + switchChar.read(*blePeripheral1); + } + else + { + ledstate = !ledstate; + switchChar.write(*blePeripheral1, ledstate); + } + } +} + void bleCentralConnectHandler(BLEHelper& peripheral) { // peripheral connected event handler - blePeripheral1 = (BLEPeripheralHelper *)(&peripheral); + blePeripheral1 = bleCentral.getPeerPeripheralBLE(peripheral); Serial.print("Connected event, peripheral: "); - Serial.println(peripheral.address()); + Serial.println(blePeripheral1->address()); // Start discovery the profiles in peripheral device blePeripheral1->discover(); } @@ -66,11 +114,10 @@ void switchCharacteristicWritten(BLEHelper& peripheral, BLECharacteristic& chara } bool adv_found(uint8_t type, - const uint8_t *data, + const uint8_t *dataPtr, uint8_t data_len, - void *user_data) + const bt_addr_le_t *addrPtr) { - bt_addr_le_t *addr = (bt_addr_le_t *)user_data; int i; Serial.print("[AD]:"); @@ -83,15 +130,14 @@ bool adv_found(uint8_t type, case BT_DATA_UUID128_SOME: case BT_DATA_UUID128_ALL: { - if (data_len % MAX_UUID_SIZE != 0) + if (data_len % UUID_SIZE_128 != 0) { Serial.println("AD malformed"); return true; } - struct bt_uuid * serviceuuid = ledService.uuid(); - for (i = 0; i < data_len; i += MAX_UUID_SIZE) + for (i = 0; i < data_len; i += UUID_SIZE_128) { - if (memcmp (((struct bt_uuid_128*)serviceuuid)->val, &data[i], MAX_UUID_SIZE) != 0) + if (ledService.uuidCompare(dataPtr + i, UUID_SIZE_128) == false) { continue; } @@ -104,7 +150,7 @@ bool adv_found(uint8_t type, } Serial.println("Connecting"); // Connect to peripheral - bleCentral.connect(addr, &conn_param); + bleCentral.connect(addrPtr, &conn_param); return false; } } @@ -113,45 +159,20 @@ bool adv_found(uint8_t type, return true; } -void setup() { - Serial.begin(9600); - pinMode(ledPin, OUTPUT); // use the LED on pin 13 as an output - - // add service and characteristic - bleCentral.addAttribute(ledService); - bleCentral.addAttribute(switchChar); - - // assign event handlers for connected, disconnected to central - bleCentral.setEventHandler(BLEConnected, bleCentralConnectHandler); - bleCentral.setEventHandler(BLEDisconnected, bleCentralDisconnectHandler); +/* + Copyright (c) 2016 Intel Corporation. All rights reserved. - // advertise the service - bleCentral.setAdvertiseHandler(adv_found); + 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. - // assign event handlers for characteristic - switchChar.setEventHandler(BLEWritten, switchCharacteristicWritten); + 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. - bleCentral.begin(); - Serial.println(("Bluetooth device active, waiting for connections...")); -} - -void loop() -{ - static unsigned int counter = 0; - static char ledstate = 0; - delay(2000); - if (blePeripheral1) - { - counter++; - - if (counter % 3) - { - switchChar.read(*blePeripheral1); - } - else - { - ledstate = !ledstate; - switchChar.write(*blePeripheral1, (unsigned char *)(&ledstate), sizeof (ledstate)); - } - } -} + 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 +*/ diff --git a/libraries/CurieBLE/examples/MIDIBLE/MIDIBLE.ino b/libraries/CurieBLE/examples/MIDIBLE/MIDIBLE.ino index 4892f017..7d78df27 100644 --- a/libraries/CurieBLE/examples/MIDIBLE/MIDIBLE.ino +++ b/libraries/CurieBLE/examples/MIDIBLE/MIDIBLE.ino @@ -1,7 +1,8 @@ /* Written by Oren Levy (auxren.com; @auxren) while competing on America's Greatest Makers with help from Intel. MIDI over BLE info from: https://developer.apple.com/bluetooth/Apple-Bluetooth-Low-Energy-MIDI-Specification.pdf - + This sketch is not written to pair with any of the central examples. + This sketch plays a random MIDI note (between 0 and 127) every 400ms. For a 'smarter' sketch, check out my Airpeggiator example. The Airpeggiator uses the Curie's IMU to allow you to play @@ -25,6 +26,9 @@ Towards the bottom of advanced, you will see 'Bluetooth MIDI devices'. You should see your Arduino 101 advertising in the list. Connect to your device and it should be available to all other iOS MIDI apps you have. + + If you do not have iOS, you can still use a BLE app on Android and just subscribe + to the midiChar charcteristic and see the updates. To send data, you use the following line: char.setValue(d, n); where char is the BLE characteristic (in our case, midiCha), d is the data, and n is the @@ -98,6 +102,21 @@ void setup() { Serial.println(("Bluetooth device active, waiting for connections...")); } +void loop() { + + /*Simple randome note player to test MIDI output + Plays random note every 400ms + */ + int note = random(0, 127); + //readMIDI(); + noteOn(0, note, 127); //loads up midiData buffer + midiChar.setValue(midiData, 5);//midiData); //posts 5 bytes + delay(200); + noteOff(0, note); + midiChar.setValue(midiData, 5);//midiData); //posts 5 bytes + delay(200); +} + void BLESetup() { // set the local name peripheral advertises @@ -124,22 +143,6 @@ void BLESetup() midiDevice.begin(); } -void loop() { - - /*Simple randome note player to test MIDI output - Plays random note every 400ms - */ - int note = random(0, 127); - //readMIDI(); - noteOn(0, note, 127); //loads up midiData buffer - midiChar.setValue(midiData, 5);//midiData); //posts 5 bytes - delay(200); - noteOff(0, note); - midiChar.setValue(midiData, 5);//midiData); //posts 5 bytes - delay(200); -} - - void midiDeviceConnectHandler(BLEHelper& central) { // central connected event handler Serial.print("Connected event, central: "); diff --git a/libraries/CurieBLE/examples/Scanning/Scanning.ino b/libraries/CurieBLE/examples/Scanning/Scanning.ino index d2d7d0e2..97ea222d 100644 --- a/libraries/CurieBLE/examples/Scanning/Scanning.ino +++ b/libraries/CurieBLE/examples/Scanning/Scanning.ino @@ -1,37 +1,76 @@ /* - Copyright (c) 2016 Intel Corporation. All rights reserved. + * Copyright (c) 2016 Intel Corporation. All rights reserved. + * See the bottom of this file for the license terms. + */ - 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. +#include - 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 +/* + This sketch try to show the scan function + The sketch will list the device's MAC address and device name to the console + The list will refresh every 3s + This sketch is meaningful if one or more BLE peripheral devices (any of the peripheral examples will do) + are present. */ -#include -#define BLE_SCANING_DEVICE_MAX_CNT 5 + +const int bleScanMaxCnt = 5; typedef struct{ char macaddr[32]; // BLE MAC address. char loacalname[22]; // Device's name }ble_device_info_t; -ble_device_info_t device_list[BLE_SCANING_DEVICE_MAX_CNT]; +ble_device_info_t device_list[bleScanMaxCnt]; uint8_t list_index = 0; BLECentral bleCentral; // BLE Central Device (the board you're programming) +bool adv_found(uint8_t type, + const uint8_t *dataPtr, + uint8_t data_len, + const bt_addr_le_t *addrPtr); + +void setup() +{ + Serial.begin(115200); // initialize serial communication + + /* Setup callback */ + bleCentral.setAdvertiseHandler(adv_found); + + /* Now activate the BLE device. + It will start continuously scanning BLE advertising + */ + bleCentral.begin(); + Serial.println("Bluetooth device active, start scanning..."); +} + +void loop() +{ + // Output the scanned device per 3s + delay(3000); + Serial.print("\r\n\r\n\t\t\tScaning result\r\n \tMAC\t\t\t\tLocal Name\r\n"); + Serial.print("-------------------------------------------------------------\r\n"); + + for (int i = 0; i < list_index; i++) + { + + Serial.print(device_list[i].macaddr); + Serial.print(" | "); + Serial.println(device_list[i].loacalname); + } + if (list_index == 0) + { + Serial.print("No device found\r\n"); + } + Serial.print("-------------------------------------------------------------\r\n"); + adv_list_clear(); +} + +// Add the scanned BLE device into the global variables. bool adv_list_add(ble_device_info_t &device) { - if (list_index >= BLE_SCANING_DEVICE_MAX_CNT) + if (list_index >= bleScanMaxCnt) { return false; } @@ -72,58 +111,40 @@ void adv_list_clear() // Process the Advertisement data bool adv_found(uint8_t type, - const uint8_t *data, + const uint8_t *dataPtr, uint8_t data_len, - void *user_data) + const bt_addr_le_t *addrPtr) { - bt_addr_le_t *addr = (bt_addr_le_t *)user_data; ble_device_info_t device; - bt_addr_le_to_str (addr, device.macaddr, sizeof (device.macaddr)); + bt_addr_le_to_str (addrPtr, device.macaddr, sizeof (device.macaddr)); memcpy(device.loacalname, " -NA-", sizeof(" -NA-")); - + // Please see https://www.bluetooth.org/en-us/specification/assigned-numbers/generic-access-profile switch (type) { - case BT_DATA_NAME_SHORTENED: - case BT_DATA_NAME_COMPLETE: - memcpy(device.loacalname, data, data_len); - device.loacalname[data_len] = '\0'; - adv_list_update(device); - break; + case BT_DATA_NAME_SHORTENED: + case BT_DATA_NAME_COMPLETE: + memcpy(device.loacalname, dataPtr, data_len); + device.loacalname[data_len] = '\0'; + adv_list_update(device); + break; } adv_list_add(device); return true; } -void setup() { - Serial.begin(115200); // initialize serial communication - - /* Setup callback */ - bleCentral.setAdvertiseHandler(adv_found); - - /* Now activate the BLE device. - It will start continuously scanning BLE advertising - */ - bleCentral.begin(); - Serial.println("Bluetooth device active, start scanning..."); -} +/* + Copyright (c) 2016 Intel Corporation. All rights reserved. -void loop() { - // Output the scanned device per 3s - delay(3000); - Serial.print("\r\n\r\n\t\t\tScaning result\r\n \tMAC\t\t\t\tLocal Name\r\n"); - Serial.print("-------------------------------------------------------------\r\n"); - - for (int i = 0; i < list_index; i++) - { - - Serial.print(device_list[i].macaddr); - Serial.print(" | "); - Serial.println(device_list[i].loacalname); - } - if (list_index == 0) - { - Serial.print("No device found\r\n"); - } - Serial.print("-------------------------------------------------------------\r\n"); - adv_list_clear(); -} + 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 +*/ diff --git a/libraries/CurieBLE/keywords.txt b/libraries/CurieBLE/keywords.txt index 85eccea2..bb0f37ea 100644 --- a/libraries/CurieBLE/keywords.txt +++ b/libraries/CurieBLE/keywords.txt @@ -13,7 +13,9 @@ BLEDescriptor KEYWORD1 BLEPeripheral KEYWORD1 BLEService KEYWORD1 BLETypedCharacteristic KEYWORD1 -BLEUuid KEYWORD1 +BLEHelper KEYWORD1 +BLECentralHelper KEYWORD1 +BLEPeripheralHelper KEYWORD1 BLECharCharacteristic KEYWORD1 BLEUnsignedCharCharacteristic KEYWORD1 @@ -31,13 +33,18 @@ BLEDoubleCharacteristic KEYWORD1 ####################################### uuid KEYWORD2 -type KEYWORD2 numAttributes KEYWORD2 connected KEYWORD2 address KEYWORD2 poll KEYWORD2 disconnect KEYWORD2 +discover KEYWORD2 +startScan KEYWORD2 +stopScan KEYWORD2 + +getConnParams KEYWORD2 +setConnectionInterval KEYWORD2 properties KEYWORD2 valueSize KEYWORD2 @@ -49,11 +56,14 @@ written KEYWORD2 subscribed KEYWORD2 begin KEYWORD2 -getAdvertisingLength KEYWORD2 -getAdvertising KEYWORD2 + +stopAdvertising KEYWORD2 +setConnectable KEYWORD2 +startAdvertising KEYWORD2 setAdvertisedServiceUuid KEYWORD2 setAdvertisedServiceData KEYWORD2 setLocalName KEYWORD2 +setDeviceName KEYWORD2 setAppearance KEYWORD2 setConnectionInterval KEYWORD2 addAttribute KEYWORD2 @@ -64,10 +74,9 @@ valueLE KEYWORD2 setValueBE KEYWORD2 valueBE KEYWORD2 -str KEYWORD2 -data KEYWORD2 -length KEYWORD2 - +getPeerPeripheralBLE KEYWORD2 +getPeerCentralBLE KEYWORD2 +uuidCompare KEYWORD2 ####################################### # Constants (LITERAL1) @@ -77,7 +86,6 @@ BLETypeService LITERAL1 BLETypeCharacteristic LITERAL1 BLETypeDescriptor LITERAL1 -BLEBroadcast LITERAL1 BLERead LITERAL1 BLEWriteWithoutResponse LITERAL1 BLEWrite LITERAL1 @@ -86,7 +94,14 @@ BLEIndicate LITERAL1 BLEConnected LITERAL1 BLEDisconnected LITERAL1 +BLEUpdateParam LITERAL1 BLEWritten LITERAL1 BLESubscribed LITERAL1 BLEUnsubscribed LITERAL1 + +ble_conn_param_t LITERAL1 +bt_uuid_t LITERAL1 +bt_uuid_16_t LITERAL1 +bt_uuid_128_t LITERAL1 +bt_addr_le_t LITERAL1 diff --git a/libraries/CurieBLE/src/BLEAttribute.cpp b/libraries/CurieBLE/src/BLEAttribute.cpp index 478c2e00..dc49158f 100644 --- a/libraries/CurieBLE/src/BLEAttribute.cpp +++ b/libraries/CurieBLE/src/BLEAttribute.cpp @@ -21,7 +21,7 @@ unsigned char BLEAttribute::_numAttributes = 0; -BLEAttribute::BLEAttribute(const char* uuid, enum BLEAttributeType type) : +BLEAttribute::BLEAttribute(const char* uuid, BLEAttributeType type) : _uuid_cstr(uuid), _type(type), _handle(0) @@ -54,7 +54,7 @@ BLEAttribute::BLEAttribute(const char* uuid, enum BLEAttributeType type) : { uint16_t temp = (_uuid.val[1] << 8)| _uuid.val[0]; _uuid.uuid.type = BT_UUID_TYPE_16; - ((struct bt_uuid_16*)(&_uuid.uuid))->val = temp; + ((bt_uuid_16_t*)(&_uuid.uuid))->val = temp; } else { @@ -67,13 +67,18 @@ BLEAttribute::uuid() const { return _uuid_cstr; } -struct bt_uuid *BLEAttribute::uuid(void) +const char* +BLEAttribute::uuid_cstr() const { + return _uuid_cstr; +} + +bt_uuid_t *BLEAttribute::uuid(void) { - return (struct bt_uuid *)&_uuid; + return (bt_uuid_t *)&_uuid; } -enum BLEAttributeType +BLEAttributeType BLEAttribute::type() const { return this->_type; } @@ -99,4 +104,19 @@ bool BLEAttribute::discovering() return _discoverying; } - +bool BLEAttribute::uuidCompare(const uint8_t *data, uint8_t uuidsize) +{ + bt_uuid_t * serviceuuid = this->uuid(); + + bool status = true; + if(serviceuuid->type == BT_UUID_TYPE_16 && uuidsize == UUID_SIZE_16) + { + status = memcmp (&((bt_uuid_16_t*)serviceuuid)->val, data, UUID_SIZE_16); + } + else if(serviceuuid->type == BT_UUID_TYPE_128 && uuidsize == UUID_SIZE_128) + { + status = memcmp (((bt_uuid_128_t*)serviceuuid)->val, data, UUID_SIZE_128); + } + + return !status; +} diff --git a/libraries/CurieBLE/src/BLEAttribute.h b/libraries/CurieBLE/src/BLEAttribute.h index 71a19fca..1de33613 100644 --- a/libraries/CurieBLE/src/BLEAttribute.h +++ b/libraries/CurieBLE/src/BLEAttribute.h @@ -22,11 +22,12 @@ #include "BLECommon.h" -enum BLEAttributeType { - BLETypeService = 0x2800, - BLETypeCharacteristic = 0x2803, - BLETypeDescriptor = 0x2900 -}; +/// BLE attribute tyep enum +typedef enum { + BLETypeService = 0x2800, ///< the service type + BLETypeCharacteristic = 0x2803, ///< the characteristic type + BLETypeDescriptor = 0x2900 ///< the descriptor type +}BLEAttributeType; // Class declare class BLEProfile; @@ -42,33 +43,72 @@ class BLEAttribute { * @return const char* string representation of the Attribute */ const char* uuid(void) const; - struct bt_uuid *uuid(void); + + /** + * Get the string representation of the Attribute + * + * @return const char* string representation of the Attribute + */ + const char* uuid_cstr(void) const; + + /** + * @brief Get the UUID raw data + * + * @param none + * + * @return bt_uuid_t* The pointer of UUID + * + * @note none + */ + bt_uuid_t *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 uuidCompare(const uint8_t *data, uint8_t uuidsize); protected: //friend BLEPeripheral; friend BLEProfile; - friend ssize_t profile_write_process(struct bt_conn *conn, - const struct bt_gatt_attr *attr, + 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); - friend ssize_t profile_read_process(struct bt_conn *conn, - const struct bt_gatt_attr *attr, + friend ssize_t profile_read_process(bt_conn_t *conn, + const bt_gatt_attr_t *attr, void *buf, uint16_t len, uint16_t offset); - BLEAttribute(const char* uuid, enum BLEAttributeType type); + 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); + friend int profile_longflush_process(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + uint8_t flags); + + BLEAttribute(const char* uuid, BLEAttributeType type); BLEAttributeType type(void) const; - bt_uuid btUuid(void) const; uint16_t handle(void); void setHandle(uint16_t handle); static unsigned char numAttributes(void); // The below APIs are for central device to discover the - virtual void discover(struct bt_gatt_discover_params *params) = 0; - virtual void discover(const struct bt_gatt_attr *attr, - struct bt_gatt_discover_params *params) = 0; + virtual void discover(bt_gatt_discover_params_t *params) = 0; + virtual void discover(const bt_gatt_attr_t *attr, + bt_gatt_discover_params_t *params) = 0; + /** * @brief Get attribute's discover state * @@ -86,9 +126,9 @@ class BLEAttribute { static unsigned char _numAttributes; const char* _uuid_cstr; - struct bt_uuid_128 _uuid; + bt_uuid_128_t _uuid; - enum BLEAttributeType _type; + BLEAttributeType _type; uint16_t _handle; }; diff --git a/libraries/CurieBLE/src/BLECentral.cpp b/libraries/CurieBLE/src/BLECentral.cpp index 6b4afcf5..728b222c 100644 --- a/libraries/CurieBLE/src/BLECentral.cpp +++ b/libraries/CurieBLE/src/BLECentral.cpp @@ -20,15 +20,17 @@ #include "BLECentralRole.h" #include "BLECentral.h" +#include "internal/ble_client.h" bool BLECentral::startScan() { return BLECentralRole::instance()->startScan(); } -bool BLECentral::startScan(const struct bt_le_scan_param &scan_param) +bool BLECentral::startScan(float interval, float window) { - return BLECentralRole::instance()->startScan(scan_param); + setScanParam(interval, window); + return BLECentralRole::instance()->startScan(); } bool BLECentral::stopScan() @@ -36,9 +38,22 @@ bool BLECentral::stopScan() return BLECentralRole::instance()->stopScan(); } -bool BLECentral::connect(const bt_addr_le_t *addr, const struct bt_le_conn_param *param) +bool BLECentral::connect(const bt_addr_le_t *addr, const ble_conn_param_t *param) { - return BLECentralRole::instance()->connect(addr, param); + bt_le_conn_param_t conn_param; + + conn_param.latency = param->latency; + conn_param.interval_max = (uint16_t)MSEC_TO_UNITS(param->interval_max, UNIT_1_25_MS); + conn_param.interval_min = (uint16_t)MSEC_TO_UNITS(param->interval_min, UNIT_1_25_MS); + conn_param.timeout = MSEC_TO_UNITS(param->timeout, UNIT_10_MS); + + pr_debug(LOG_MODULE_BLE,"Latency-%d\r\nInterval min-%d, max-%d\r\ntimeout:%d", + conn_param.latency, + conn_param.interval_min, + conn_param.interval_max, + conn_param.timeout); + + return BLECentralRole::instance()->connect(addr, &conn_param); } void BLECentral::discover(BLEPeripheralHelper &peripheral) @@ -56,14 +71,19 @@ void BLECentral::setAdvertiseHandler(ble_advertise_handle_cb_t advcb) BLECentralRole::instance()->setAdvertiseHandler(advcb); } -void BLECentral::setScanParam(const struct bt_le_scan_param &scan_param) +void BLECentral::setScanParam(float interval, float window) { + bt_le_scan_param_t scan_param; + scan_param.type = BT_HCI_LE_SCAN_ACTIVE; + scan_param.filter_dup = BT_HCI_LE_SCAN_FILTER_DUP_ENABLE; + scan_param.interval = (uint16_t)MSEC_TO_UNITS(interval, UNIT_0_625_MS);; + scan_param.window = (uint16_t)MSEC_TO_UNITS(window, UNIT_0_625_MS);; BLECentralRole::instance()->setScanParam(scan_param); } -void BLECentral::addAttribute(BLEAttribute& attribute) +BleStatus BLECentral::addAttribute(BLEAttribute& attribute) { - BLECentralRole::instance()->addAttribute(attribute); + return BLECentralRole::instance()->addAttribute(attribute); } bool BLECentral::begin(void) @@ -76,8 +96,8 @@ bool BLECentral::begin(void) } // Start scan - const struct bt_le_scan_param *scan_param = BLECentralRole::instance()->getScanParam(); - struct bt_le_scan_param zero_param; + const bt_le_scan_param_t *scan_param = BLECentralRole::instance()->getScanParam(); + bt_le_scan_param_t zero_param; memset(&zero_param, 0x00, sizeof (zero_param)); if (0 == memcmp(&zero_param, scan_param, sizeof (zero_param))) { @@ -96,5 +116,9 @@ bool BLECentral::begin(void) return retval; } +BLEPeripheralHelper *BLECentral::getPeerPeripheralBLE(BLEHelper& peripheral) +{ + return (BLEPeripheralHelper *)(&peripheral); +} diff --git a/libraries/CurieBLE/src/BLECentral.h b/libraries/CurieBLE/src/BLECentral.h index 8327f5d6..09379b2b 100644 --- a/libraries/CurieBLE/src/BLECentral.h +++ b/libraries/CurieBLE/src/BLECentral.h @@ -25,6 +25,11 @@ class BLEAttribute; +/** + * @brief A class defining the BLE central function + * + * This class abstract the BLE central. + */ class BLECentral{ public: /** @@ -41,13 +46,15 @@ class BLECentral{ /** * @brief Start scan with scan parameter * - * @param none + * @param[in] interval The scan interval in ms + * + * @param[in] window The scan window in ms * * @return bool Indicate the success or error * * @note none */ - bool startScan(const struct bt_le_scan_param &scan_param); + bool startScan(float interval, float window); /** * @brief Stop scan @@ -63,20 +70,20 @@ class BLECentral{ /** * @brief Schedule a connect request to peripheral to establish a connection * - * @param addr The MAC address of peripheral device that want to establish connection + * @param[in] addr The MAC address of peripheral device that want to establish connection * - * @param param The connetion parameters + * @param[in] param The connetion parameters * * @return bool Indicate the success or error * * @note none */ - bool connect(const bt_addr_le_t *addr, const struct bt_le_conn_param *param); + bool connect(const bt_addr_le_t *addr, const ble_conn_param_t *param); /** * @brief Discover the peripheral device profile * - * @param peripheral The Peripheral that need to discover the profile + * @param[in] peripheral The Peripheral that need to discover the profile * * @return none * @@ -87,37 +94,47 @@ class BLECentral{ /** * @brief Set the scan parameter * - * @param scan_param The scan parameter want to be set + * @param[in] interval The scan interval in ms + * + * @param[in] window The scan window in ms * * @return none * - * @note none + * @note 1. The scale of the interval and window are 2.5 - 10240ms + * 2. The scan interval and window are like below. + * The device can see the ADV packet in the window. + * window + * ---- ---- + * | | | | + * --- ------- ---- + * |interval| */ - void setScanParam(const struct bt_le_scan_param &scan_param); + void setScanParam(float interval, float window); /** * @brief Add an attribute to the BLE Central Device * - * @param attribute Attribute to add to Central + * @param[in] attribute Attribute to add to Central * - * @return none + * @return BleStatus indicating success or error * * @note The attribute will used for discover the peripheral handler + * Only need check return value at first call. Memory only alloc at first call */ - void addAttribute(BLEAttribute& attribute); + BleStatus addAttribute(BLEAttribute& attribute); /** * Provide a function to be called when events related to this Device are raised * - * @param event Event type for callback - * @param callback Pointer to callback function to invoke when an event occurs. + * @param[in] event Event type for callback + * @param[in] callback Pointer to callback function to invoke when an event occurs. */ void setEventHandler(BLERoleEvent event, BLERoleEventHandler callback); /** * @brief Provide a function to be called when scanned the advertisement * - * @param advcb Pointer to callback function to invoke when advertisement received + * @param[in] advcb Pointer to callback function to invoke when advertisement received * * @return none * @@ -126,11 +143,20 @@ class BLECentral{ void setAdvertiseHandler(ble_advertise_handle_cb_t advcb); /** - * Setup attributes and start scan + * @brief Setup attributes and start scan * * @return bool indicating success or error */ bool begin(void); + + /** + * @brief Get peer peripheral device + * + *@param peripheral peer peripheral device of the central board + * + * @return pointer of peer peripheral device + */ + BLEPeripheralHelper *getPeerPeripheralBLE(BLEHelper& peripheral); protected: private: diff --git a/libraries/CurieBLE/src/BLECentralHelper.cpp b/libraries/CurieBLE/src/BLECentralHelper.cpp index 99e92de9..e48426aa 100644 --- a/libraries/CurieBLE/src/BLECentralHelper.cpp +++ b/libraries/CurieBLE/src/BLECentralHelper.cpp @@ -28,24 +28,6 @@ BLECentralHelper::BLECentralHelper(BLEPeripheralRole* peripheral) : clearAddress(); } -BLECentralHelper::operator bool() const { - bt_addr_le_t zero; - - memset(&zero, 0, sizeof(zero)); - - return (memcmp(&_address, &zero, sizeof(_address)) != 0); -} - -bool -BLECentralHelper::operator==(const BLECentralHelper& rhs) const { - return (memcmp(&_address, &rhs._address, sizeof(_address)) == 0); -} - -bool -BLECentralHelper::operator!=(const BLECentralHelper& rhs) const { - return !(*this == rhs); -} - bool BLECentralHelper::connected() { poll(); @@ -53,31 +35,6 @@ BLECentralHelper::connected() { return (*this && *this == _peripheral->central()); } -const char* -BLECentralHelper::address() const { - static char address[18]; - - String addressStr = ""; - - for (int i = 5; i >= 0; i--) { - unsigned char a = _address.val[i]; - - if (a < 0x10) { - addressStr += "0"; - } - - addressStr += String(a, 16); - - if (i > 0) { - addressStr += ":"; - } - } - - strcpy(address, addressStr.c_str()); - - return address; -} - void BLECentralHelper::poll() { _peripheral->poll(); @@ -92,12 +49,3 @@ BLECentralHelper::disconnect() { return false; } -void -BLECentralHelper::setAddress(bt_addr_le_t address) { - _address = address; -} - -void -BLECentralHelper::clearAddress() { - memset(&_address, 0x00, sizeof(_address)); -} diff --git a/libraries/CurieBLE/src/BLECentralHelper.h b/libraries/CurieBLE/src/BLECentralHelper.h index 465d9cab..4a563d97 100644 --- a/libraries/CurieBLE/src/BLECentralHelper.h +++ b/libraries/CurieBLE/src/BLECentralHelper.h @@ -36,13 +36,6 @@ class BLECentralHelper: public BLEHelper{ * @return boolean_t true if the central is connected, otherwise false */ bool connected(void); - - /** - * Get the address of the Central in string form - * - * @return const char* address of the Central in string form - */ - const char* address(void) const; /** * Disconnect the central if it is connected @@ -55,18 +48,11 @@ class BLECentralHelper: public BLEHelper{ */ void poll(void); - operator bool(void) const; - bool operator==(const BLECentralHelper& rhs) const; - bool operator!=(const BLECentralHelper& rhs) const; - protected: BLECentralHelper(BLEPeripheralRole* peripheral); - void setAddress(bt_addr_le_t address); - void clearAddress(); private: BLEPeripheralRole* _peripheral; - bt_addr_le_t _address; }; #endif diff --git a/libraries/CurieBLE/src/BLECentralRole.cpp b/libraries/CurieBLE/src/BLECentralRole.cpp index 1595e24d..011a4031 100644 --- a/libraries/CurieBLE/src/BLECentralRole.cpp +++ b/libraries/CurieBLE/src/BLECentralRole.cpp @@ -75,7 +75,7 @@ const BLECentralHelper *BLECentralRole::central(void) const return &_central; } -bool BLECentralRole::connect(const bt_addr_le_t *addr, const struct bt_le_conn_param *param) +bool BLECentralRole::connect(const bt_addr_le_t *addr, const bt_le_conn_param_t *param) { BLEPeripheralHelper* temp = NULL; BLEPeripheralHelper* unused = NULL; @@ -108,7 +108,7 @@ bool BLECentralRole::connect(const bt_addr_le_t *addr, const struct bt_le_conn_p if (!link_existed) { // Send connect request - struct bt_conn* conn = bt_conn_create_le(addr, param); + bt_conn_t* conn = bt_conn_create_le(addr, param); if (NULL != conn) { unused->setAddress(*addr); @@ -121,27 +121,27 @@ bool BLECentralRole::connect(const bt_addr_le_t *addr, const struct bt_le_conn_p bool BLECentralRole::startScan() { - int err = bt_le_scan_start(&_scan_param, ble_central_device_found); - if (err) + 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; - } + pr_info(LOG_MODULE_BLE, "Scanning failed to start (err %d)\n", err); + return false; + } return true; } -bool BLECentralRole::startScan(const struct bt_le_scan_param &scan_param) +bool BLECentralRole::startScan(const bt_le_scan_param_t &scan_param) { setScanParam(scan_param); return startScan(); } -void BLECentralRole::setScanParam(const struct bt_le_scan_param &scan_param) +void BLECentralRole::setScanParam(const bt_le_scan_param_t &scan_param) { memcpy(&_scan_param, &scan_param, sizeof (_scan_param)); } -const struct bt_le_scan_param* BLECentralRole::getScanParam() +const bt_le_scan_param_t* BLECentralRole::getScanParam() { return &_scan_param; } @@ -153,12 +153,12 @@ bool BLECentralRole::stopScan() if (err) { pr_info(LOG_MODULE_BLE, "Stop LE scan failed (err %d)\n", err); - return false; + return false; } return true; } -BLEPeripheralHelper* BLECentralRole::peripheral(struct bt_conn *conn) +BLEPeripheralHelper* BLECentralRole::peripheral(bt_conn_t *conn) { BLEPeripheralHelper* temp = NULL; const bt_addr_le_t *addr = bt_conn_get_dst(conn); @@ -207,7 +207,7 @@ void BLECentralRole::handleDeviceFound(const bt_addr_le_t *addr, return; } - if (!_adv_event_handle(data[1], &data[2], len - 1, (void *)addr)) + if (!_adv_event_handle(data[1], &data[2], len - 1, addr)) { return; } @@ -219,7 +219,7 @@ void BLECentralRole::handleDeviceFound(const bt_addr_le_t *addr, } } -void BLECentralRole::handleConnectEvent(struct bt_conn *conn, uint8_t err) +void BLECentralRole::handleConnectEvent(bt_conn_t *conn, uint8_t err) { if (_event_handlers[BLEConnected]) { @@ -228,7 +228,7 @@ void BLECentralRole::handleConnectEvent(struct bt_conn *conn, uint8_t err) } } -void BLECentralRole::handleDisconnectEvent(struct bt_conn *conn, uint8_t reason) +void BLECentralRole::handleDisconnectEvent(bt_conn_t *conn, uint8_t reason) { if (_event_handlers[BLEDisconnected]) { @@ -238,15 +238,15 @@ void BLECentralRole::handleDisconnectEvent(struct bt_conn *conn, uint8_t reason) } } -void BLECentralRole::handleParamUpdated(struct bt_conn *conn, +void BLECentralRole::handleParamUpdated(bt_conn_t *conn, uint16_t interval, uint16_t latency, uint16_t timeout) { if (_event_handlers[BLEUpdateParam]) { - // Fix me Add parameter proc BLEPeripheralHelper *temp = peripheral(conn); + temp->setConnectionParameters(interval, interval, latency, timeout); _event_handlers[BLEUpdateParam](*temp); } } @@ -265,12 +265,18 @@ void BLECentralRole::setAdvertiseHandler(ble_advertise_handle_cb_t advcb) _adv_event_handle = advcb; } -void BLECentralRole::addAttribute(BLEAttribute& attribute) +BleStatus BLECentralRole::addAttribute(BLEAttribute& attribute) { + BleStatus err = BLE_STATUS_SUCCESS; for (int i = 0; i < BLE_MAX_CONN_CFG; i++) { - _peripherial[i]->addAttribute(attribute); + err = _peripherial[i]->addAttribute(attribute); + if (err != BLE_STATUS_SUCCESS) + { + break; + } } + return err; } bool BLECentralRole::begin() diff --git a/libraries/CurieBLE/src/BLECentralRole.h b/libraries/CurieBLE/src/BLECentralRole.h index d3757fae..0ea6c008 100644 --- a/libraries/CurieBLE/src/BLECentralRole.h +++ b/libraries/CurieBLE/src/BLECentralRole.h @@ -46,7 +46,7 @@ class BLECentralRole: public BLERoleBase { * * @note none */ - bool startScan(const struct bt_le_scan_param &scan_param); + bool startScan(const bt_le_scan_param_t &scan_param); /** * @brief Stop scan @@ -62,37 +62,37 @@ class BLECentralRole: public BLERoleBase { /** * @brief Schedule a connect request to peripheral to establish a connection * - * @param addr The MAC address of peripheral device that want to establish connection + * @param[in] addr The MAC address of peripheral device that want to establish connection * - * @param param The connetion parameters + * @param[in] param The connetion parameters * * @return bool Indicate the success or error * * @note none */ - bool connect(const bt_addr_le_t *addr, const struct bt_le_conn_param *param); + bool connect(const bt_addr_le_t *addr, const bt_le_conn_param_t *param); /** * @brief Set the scan parameter * - * @param scan_param The scan parameter want to be set + * @param[in] scan_param The scan parameter want to be set * * @return none * * @note none */ - void setScanParam(const struct bt_le_scan_param &scan_param); + void setScanParam(const bt_le_scan_param_t &scan_param); /** * @brief Get the scan parameter * * @param none * - * @return const struct bt_le_scan_param* The scan parameter that current used + * @return const bt_le_scan_param_t* The scan parameter that current used * * @note none */ - const struct bt_le_scan_param* getScanParam(); + const bt_le_scan_param_t* getScanParam(); /** * @brief Discover the peripheral device profile @@ -108,26 +108,26 @@ class BLECentralRole: public BLERoleBase { /** * @brief Add an attribute to the BLE Central Device * - * @param attribute Attribute to add to Central + * @param[in] attribute Attribute to add to Central * - * @return none + * @return BleStatus indicating success or error * * @note The attribute will used for discover the peripheral handler */ - void addAttribute(BLEAttribute& attribute); + BleStatus addAttribute(BLEAttribute& attribute); /** - * Provide a function to be called when events related to this Device are raised + * @brief Provide a function to be called when events related to this Device are raised * - * @param event Event type for callback - * @param callback Pointer to callback function to invoke when an event occurs. + * @param[in] event Event type for callback + * @param[in] callback Pointer to callback function to invoke when an event occurs. */ void setEventHandler(BLERoleEvent event, BLERoleEventHandler callback); /** * @brief Provide a function to be called when scanned the advertisement * - * @param advcb Pointer to callback function to invoke when advertisement received + * @param[in] advcb Pointer to callback function to invoke when advertisement received * * @return none * @@ -138,13 +138,13 @@ class BLECentralRole: public BLERoleBase { /** * @brief Get BLE peripheral helper by conntion * - * @param conn The connection object + * @param[in] conn The connection object * * @return BLEPeripheralHelper* The BLE peripheral helper * * @note none */ - BLEPeripheralHelper* peripheral(struct bt_conn *conn); + BLEPeripheralHelper* peripheral(bt_conn_t *conn); /** * @brief Get BLE central helper that for APP use @@ -197,60 +197,60 @@ class BLECentralRole: public BLERoleBase { /** * @brief Handle the connected event * - * @param conn The object that established the connection + * @param[in] conn The object that established the connection * - * @param err The code of the process + * @param[in] err The code of the process * * @return none * * @note none */ - void handleConnectEvent(struct bt_conn *conn, uint8_t err); + void handleConnectEvent(bt_conn_t *conn, uint8_t err); /** * @brief Handle the disconnected event * - * @param conn The object that lost the connection + * @param[in] conn The object that lost the connection * - * @param reason The link lost reason + * @param[in] reason The link lost reason * * @return none * * @note none */ - void handleDisconnectEvent(struct bt_conn *conn, uint8_t reason); + void handleDisconnectEvent(bt_conn_t *conn, uint8_t reason); /** * @brief Handle the conntion update request * - * @param conn The connection object that need to process the update request + * @param[in] conn The connection object that need to process the update request * - * @param interval The connection interval + * @param[in] interval The connection interval (N*1.25)ms * - * @param latency The connection latency + * @param[in] latency The connection latency * - * @param timeout The connection timeout + * @param[in] timeout The connection timeout (N*10)ms * * @return none * * @note none */ - void handleParamUpdated(struct bt_conn *conn, + void handleParamUpdated(bt_conn_t *conn, uint16_t interval, - uint16_t latency, - uint16_t timeout); + uint16_t latency, + uint16_t timeout); /** * @brief Handle the advertisement * - * @param addr The device's MAC address that send out ADV + * @param[in] addr The device's MAC address that send out ADV * - * @param rssi The antenna's RSSI + * @param[in] rssi The antenna's RSSI * - * @param type The advertise type + * @param[in] type The advertise type * - * @param ad The advertisement RAW data + * @param[in] ad The advertisement RAW data * - * @param len The RAW data's length + * @param[in] len The RAW data's length * * @return none * @@ -266,7 +266,7 @@ class BLECentralRole: public BLERoleBase { ~BLECentralRole(); BLEPeripheralHelper* _peripherial[BLE_MAX_CONN_CFG]; BLECentralHelper _central; - struct bt_le_scan_param _scan_param; + bt_le_scan_param_t _scan_param; static BLECentralRole* _ble_central_ins; ble_advertise_handle_cb_t _adv_event_handle; diff --git a/libraries/CurieBLE/src/BLECharacteristic.cpp b/libraries/CurieBLE/src/BLECharacteristic.cpp index 5c810a5f..3e46e0a1 100644 --- a/libraries/CurieBLE/src/BLECharacteristic.cpp +++ b/libraries/CurieBLE/src/BLECharacteristic.cpp @@ -21,24 +21,25 @@ #include "BLEPeripheralHelper.h" #include "internal/ble_client.h" -uint8_t profile_notify_process (struct bt_conn *conn, - struct bt_gatt_subscribe_params *params, - const void *data, uint16_t length); -uint8_t profile_read_rsp_process(struct bt_conn *conn, int err, - struct bt_gatt_read_params *params, +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); unsigned char BLECharacteristic::_numNotifyAttributes = 0; -struct bt_uuid_16 BLECharacteristic::_gatt_chrc_uuid = {BT_UUID_TYPE_16, BT_UUID_GATT_CHRC_VAL}; -struct bt_uuid_16 BLECharacteristic::_gatt_ccc_uuid = {BT_UUID_TYPE_16, BT_UUID_GATT_CCC_VAL}; +bt_uuid_16_t BLECharacteristic::_gatt_chrc_uuid = {BT_UUID_TYPE_16, BT_UUID_GATT_CHRC_VAL}; +bt_uuid_16_t BLECharacteristic::_gatt_ccc_uuid = {BT_UUID_TYPE_16, BT_UUID_GATT_CCC_VAL}; BLECharacteristic::BLECharacteristic(const char* uuid, const unsigned char properties, const unsigned short maxLength) : BLEAttribute(uuid, BLETypeCharacteristic), _value_length(0), + _value_buffer(NULL), _written(false), _user_description(NULL), _presentation_format(NULL), @@ -46,8 +47,12 @@ BLECharacteristic::BLECharacteristic(const char* uuid, _attr_chrc_value(NULL), _attr_cccd(NULL) { - _value_size = maxLength > BLE_MAX_ATTR_DATA_LEN ? BLE_MAX_ATTR_DATA_LEN : maxLength; + _value_size = maxLength > BLE_MAX_ATTR_LONGDATA_LEN ? BLE_MAX_ATTR_LONGDATA_LEN : maxLength; _value = (unsigned char*)malloc(_value_size); + if (_value_size > BLE_MAX_ATTR_DATA_LEN) + { + _value_buffer = (unsigned char*)malloc(_value_size); + } memset(&_ccc_cfg, 0, sizeof(_ccc_cfg)); memset(&_ccc_value, 0, sizeof(_ccc_value)); @@ -246,12 +251,12 @@ BLECharacteristic::numNotifyAttributes(void) { return _numNotifyAttributes; } -struct _bt_gatt_ccc* BLECharacteristic::getCccCfg(void) +_bt_gatt_ccc_t* BLECharacteristic::getCccCfg(void) { return &_ccc_value; } -struct bt_gatt_chrc* BLECharacteristic::getCharacteristicAttValue(void) +bt_gatt_chrc_t* BLECharacteristic::getCharacteristicAttValue(void) { return &_gatt_chrc; } @@ -270,32 +275,32 @@ uint8_t BLECharacteristic::getPermission(void) return perm; } -struct bt_uuid* BLECharacteristic::getCharacteristicAttributeUuid(void) +bt_uuid_t* BLECharacteristic::getCharacteristicAttributeUuid(void) { - return (struct bt_uuid*) &_gatt_chrc_uuid; + return (bt_uuid_t*) &_gatt_chrc_uuid; } -struct bt_uuid* BLECharacteristic::getClientCharacteristicConfigUuid(void) +bt_uuid_t* BLECharacteristic::getClientCharacteristicConfigUuid(void) { - return (struct bt_uuid*) &_gatt_ccc_uuid; + return (bt_uuid_t*) &_gatt_ccc_uuid; } -void BLECharacteristic::addCharacteristicDeclaration(struct bt_gatt_attr *gatt_attr) +void BLECharacteristic::addCharacteristicDeclaration(bt_gatt_attr_t *gatt_attr) { _attr_chrc_declaration = gatt_attr; } -void BLECharacteristic::addCharacteristicValue(struct bt_gatt_attr *gatt_attr) +void BLECharacteristic::addCharacteristicValue(bt_gatt_attr_t *gatt_attr) { _attr_chrc_value = gatt_attr; } -void BLECharacteristic::addCharacteristicConfigDescriptor(struct bt_gatt_attr *gatt_attr) +void BLECharacteristic::addCharacteristicConfigDescriptor(bt_gatt_attr_t *gatt_attr) { _attr_cccd = gatt_attr; } -void BLECharacteristic::discover(struct bt_gatt_discover_params *params) +void BLECharacteristic::discover(bt_gatt_discover_params_t *params) { params->type = BT_GATT_DISCOVER_CHARACTERISTIC; params->uuid = this->uuid(); @@ -306,8 +311,8 @@ void BLECharacteristic::discover(struct bt_gatt_discover_params *params) } -void BLECharacteristic::discover(const struct bt_gatt_attr *attr, - struct bt_gatt_discover_params *params) +void BLECharacteristic::discover(const bt_gatt_attr_t *attr, + bt_gatt_discover_params_t *params) { if (!attr) { @@ -340,7 +345,7 @@ void BLECharacteristic::discover(const struct bt_gatt_attr *attr, } } -struct bt_gatt_subscribe_params *BLECharacteristic::getSubscribeParams() +bt_gatt_subscribe_params_t *BLECharacteristic::getSubscribeParams() { return &_sub_params; } @@ -348,7 +353,7 @@ struct bt_gatt_subscribe_params *BLECharacteristic::getSubscribeParams() bool BLECharacteristic::read(BLEPeripheralHelper &peripheral) { int retval = 0; - struct bt_conn* conn = NULL; + bt_conn_t* conn = NULL; if (_reading) { // Already in reading state @@ -387,7 +392,7 @@ bool BLECharacteristic::write(BLEPeripheralHelper &peripheral, uint16_t length) { int retval = 0; - struct bt_conn* conn = NULL; + bt_conn_t* conn = NULL; conn = bt_conn_lookup_addr_le(peripheral.bt_le_address()); if (NULL == conn) @@ -403,4 +408,32 @@ bool BLECharacteristic::write(BLEPeripheralHelper &peripheral, return (0 == retval); } +void BLECharacteristic::setBuffer(BLEHelper& blehelper, + 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 BLECharacteristic::syncupBuffer2Value(BLEHelper& blehelper) +{ + setValue(blehelper, _value_buffer, _value_size); +} + +void BLECharacteristic::discardBuffer() +{ + memcpy(_value_buffer, _value, _value_size); +} + +bool BLECharacteristic::longCharacteristic() +{ + return (_value_size > BLE_MAX_ATTR_DATA_LEN); +} + diff --git a/libraries/CurieBLE/src/BLECharacteristic.h b/libraries/CurieBLE/src/BLECharacteristic.h index a867e296..58274f67 100644 --- a/libraries/CurieBLE/src/BLECharacteristic.h +++ b/libraries/CurieBLE/src/BLECharacteristic.h @@ -65,9 +65,9 @@ class BLECharacteristic : public BLEAttribute { /** * Constructor for BLE Characteristic * - * @param uuid 16-bit or 128-bit UUID (in string form) defined by BLE standard - * @param properties Characteristic property mask - * @param maxLength Maximum data length required for characteristic value (<= BLE_MAX_ATTR_DATA_LEN) + * @param[in] uuid 16-bit or 128-bit UUID (in string form) defined by BLE standard + * @param[in] properties Characteristic property mask + * @param[in] maxLength Maximum data length required for characteristic value (<= BLE_MAX_ATTR_DATA_LEN) */ BLECharacteristic(const char* uuid, const unsigned char properties, @@ -76,9 +76,9 @@ class BLECharacteristic : public BLEAttribute { /** * Constructor for BLE Characteristic * - * @param uuid 16-bit or 128-bit UUID (in string form) defined by BLE standard - * @param properties Characteristic property mask - * @param value String value for characteristic (string length (<= BLE_MAX_ATTR_DATA_LEN)) + * @param[in] uuid 16-bit or 128-bit UUID (in string form) defined by BLE standard + * @param[in] properties Characteristic property mask + * @param[in] value String value for characteristic (string length (<= BLE_MAX_ATTR_DATA_LEN)) */ BLECharacteristic(const char* uuid, const unsigned char properties, @@ -89,8 +89,8 @@ class BLECharacteristic : public BLEAttribute { /** * Set the current value of the Characteristic * - * @param value New value to set, as a byte array. Data is stored in internal copy. - * @param length Length, in bytes, of valid data in the array to write. + * @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 @@ -100,9 +100,9 @@ class BLECharacteristic : public BLEAttribute { /** * Set the current value of the Characteristic * - * @param central The central device that update the value. - * @param value New value to set, as a byte array. Data is stored in internal copy. - * @param length Length, in bytes, of valid data in the array to write. + * @param[in] central The central device that update the value. + * @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 @@ -156,8 +156,8 @@ class BLECharacteristic : public BLEAttribute { /** * Provide a function to be called when events related to this Characteristic are raised * - * @param event Event type to set event handler for - * @param callback Pointer to callback function to invoke when the event occurs. + * @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); @@ -175,7 +175,7 @@ class BLECharacteristic : public BLEAttribute { /** * @brief Schedule the read request to read the characteristic in peripheral * - * @param peripheral The peripheral device that want to read. + * @param[in] peripheral The peripheral device that want to read. * * @return bool Indicate the success or error * @@ -186,9 +186,9 @@ class BLECharacteristic : public BLEAttribute { /** * @brief Schedule the write request to update the characteristic in peripheral * - * @param peripheral The peripheral device that want to be updated - * @param value New value to set, as a byte array. Data is stored in internal copy. - * @param length Length, in bytes, of valid data in the array to write. + * @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 @@ -201,10 +201,26 @@ class BLECharacteristic : public BLEAttribute { protected: friend class BLEProfile; + 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); - void addCharacteristicDeclaration(struct bt_gatt_attr *gatt_attr); - void addCharacteristicValue(struct bt_gatt_attr *gatt_attr); - void addCharacteristicConfigDescriptor(struct bt_gatt_attr *gatt_attr); + void addCharacteristicDeclaration(bt_gatt_attr_t *gatt_attr); + void addCharacteristicValue(bt_gatt_attr_t *gatt_attr); + void addCharacteristicConfigDescriptor(bt_gatt_attr_t *gatt_attr); + + bool longCharacteristic(); + + void setBuffer(BLEHelper& blehelper, + const uint8_t value[], + uint16_t length, + uint16_t offset); + void discardBuffer(); + void syncupBuffer2Value(BLEHelper& blehelper); /** * @brief Get the characteristic value handle @@ -233,10 +249,10 @@ class BLECharacteristic : public BLEAttribute { void setUserDescription(BLEDescriptor *descriptor); void setPresentationFormat(BLEDescriptor *descriptor); - struct _bt_gatt_ccc* getCccCfg(void); - struct bt_gatt_chrc* getCharacteristicAttValue(void); - static struct bt_uuid* getCharacteristicAttributeUuid(void); - static struct bt_uuid* getClientCharacteristicConfigUuid(void); + _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 @@ -252,38 +268,38 @@ class BLECharacteristic : public BLEAttribute { /** * @brief For central to discover the peripherial profile * - * @param attr The discover response + * @param[in] attr The discover response * - * @param params The discover parameter that need to fill + * @param[in] params The discover parameter that need to fill * * @return none * * @note Only for central */ - void discover(const struct bt_gatt_attr *attr, - struct bt_gatt_discover_params *params); + void discover(const bt_gatt_attr_t *attr, + bt_gatt_discover_params_t *params); /** * @brief For central to discover the peripherial profile * - * @param params The discover parameter that need to fill + * @param[in] params The discover parameter that need to fill * * @return none * * @note Only for central */ - void discover(struct bt_gatt_discover_params *params); + void discover(bt_gatt_discover_params_t *params); /** * @brief Get the subscribe parameter * * @param none * - * @return struct bt_gatt_subscribe_params * the subscribe parameter + * @return bt_gatt_subscribe_params_t * the subscribe parameter * * @note Only for central */ - struct bt_gatt_subscribe_params* getSubscribeParams(); + bt_gatt_subscribe_params_t* getSubscribeParams(); private: void _setValue(const uint8_t value[], uint16_t length); @@ -291,31 +307,32 @@ class BLECharacteristic : public BLEAttribute { private: static unsigned char _numNotifyAttributes; - static struct bt_uuid_16 _gatt_chrc_uuid; - static struct bt_uuid_16 _gatt_ccc_uuid; + static bt_uuid_16_t _gatt_chrc_uuid; + static bt_uuid_16_t _gatt_ccc_uuid; unsigned short _value_size; unsigned short _value_length; unsigned char* _value; + unsigned char* _value_buffer; bool _written; uint16_t _value_handle; - struct bt_gatt_ccc_cfg _ccc_cfg; - struct _bt_gatt_ccc _ccc_value; - struct bt_gatt_chrc _gatt_chrc; + bt_gatt_ccc_cfg_t _ccc_cfg; + _bt_gatt_ccc_t _ccc_value; + bt_gatt_chrc_t _gatt_chrc; BLEDescriptor* _user_description; BLEDescriptor* _presentation_format; - struct bt_gatt_attr *_attr_chrc_declaration; - struct bt_gatt_attr *_attr_chrc_value; - struct bt_gatt_attr *_attr_cccd; + bt_gatt_attr_t *_attr_chrc_declaration; + bt_gatt_attr_t *_attr_chrc_value; + bt_gatt_attr_t *_attr_cccd; // For central device to subscribe the Notification/Indication - struct bt_gatt_subscribe_params _sub_params; + bt_gatt_subscribe_params_t _sub_params; bool _reading; - struct bt_gatt_read_params _read_params; + bt_gatt_read_params_t _read_params; BLECharacteristicEventHandler _event_handlers[BLECharacteristicEventLast]; }; diff --git a/libraries/CurieBLE/src/BLECommon.h b/libraries/CurieBLE/src/BLECommon.h index 00c31342..9c26bad3 100644 --- a/libraries/CurieBLE/src/BLECommon.h +++ b/libraries/CurieBLE/src/BLECommon.h @@ -35,14 +35,16 @@ #define BLE_ADDR_LEN 6 -#define MAX_UUID_SIZE 16 - +#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_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 @@ -54,19 +56,20 @@ /** 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_GAP_BASE = 0x100, /**< GAP specific error base */ - BLE_STATUS_GATT_BASE = 0x200, /**< GATT specific Error base */ + 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 */ }; typedef uint16_t ble_status_t; /**< Response and event BLE service status type @ref BLE_STATUS */ @@ -75,7 +78,37 @@ typedef ble_status_t BleStatus; #define BLE_MAX_CONN_CFG 2 -typedef bool (*ble_advertise_handle_cb_t)(uint8_t type, const uint8_t *data, - uint8_t data_len, void *user_data); - +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 + +/// 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/CurieBLE/src/BLEDescriptor.cpp b/libraries/CurieBLE/src/BLEDescriptor.cpp index d611d471..889cd58d 100644 --- a/libraries/CurieBLE/src/BLEDescriptor.cpp +++ b/libraries/CurieBLE/src/BLEDescriptor.cpp @@ -46,7 +46,7 @@ BLEDescriptor::BLEDescriptor(const char* uuid, const char* value) : } const unsigned char* -BLEDescriptor::BLEDescriptor::value() const +BLEDescriptor::value() const { return _value; } @@ -63,3 +63,34 @@ BLEDescriptor::operator[] (int offset) const return _value[offset]; } +void BLEDescriptor::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 + 1; + // Complete the discover + _discoverying = false; + } + +} + + +void BLEDescriptor::discover(bt_gatt_discover_params_t *params) +{ + params->type = BT_GATT_DISCOVER_DESCRIPTOR; + params->uuid = this->uuid(); + // Start discovering + _discoverying = true; +} + + diff --git a/libraries/CurieBLE/src/BLEDescriptor.h b/libraries/CurieBLE/src/BLEDescriptor.h index 498035a0..95f75ed5 100644 --- a/libraries/CurieBLE/src/BLEDescriptor.h +++ b/libraries/CurieBLE/src/BLEDescriptor.h @@ -30,9 +30,9 @@ class BLEDescriptor : public BLEAttribute { /** * Constructor for BLE Descriptor * - * @param uuid 16-bit UUID (in string form) defined by BLE standard - * @param value Value of descriptor, as a byte array. Data is stored in internal copy. - * @param valueLength Data length required for descriptor value (<= BLE_MAX_ATTR_DATA_LEN) + * @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) */ BLEDescriptor(const char* uuid, const unsigned char value[], unsigned short valueLength); @@ -41,8 +41,8 @@ class BLEDescriptor : public BLEAttribute { /** * Constructor for BLE Descriptor * - * @param uuid 16-bit UUID (in string form) defined by BLE standard - * @param value String value of descriptor. Data is stored in internal copy. + * @param[in] uuid 16-bit UUID (in string form) defined by BLE standard + * @param[in] value String value of descriptor. Data is stored in internal copy. * (String length <= BLE_MAX_ATTR_DATA_LEN) */ BLEDescriptor(const char* uuid, const char* value); @@ -62,6 +62,32 @@ class BLEDescriptor : public BLEAttribute { unsigned short valueLength(void) const; + /** + * @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); + + unsigned char operator[] (int offset) const; protected: diff --git a/libraries/CurieBLE/src/BLEHelper.cpp b/libraries/CurieBLE/src/BLEHelper.cpp index b756f5d4..e3e1caa6 100644 --- a/libraries/CurieBLE/src/BLEHelper.cpp +++ b/libraries/CurieBLE/src/BLEHelper.cpp @@ -16,29 +16,7 @@ BLEHelper::BLEHelper() BLEHelper::~BLEHelper() { - #if 0 - if (NULL != _conn) - { - bt_conn_unref(_conn); - } - #endif -} - -#if 0 -void BLEHelper::setConn(struct bt_conn *conn) -{ - if (conn == _conn) - { - return; - } - - if (NULL != _conn) - { - bt_conn_unref(_conn); - } - _conn = conn; } -#endif BLEHelper::operator bool() const { @@ -106,8 +84,8 @@ BLEHelper::poll() { } void -BLEHelper::setAddress(bt_addr_le_t address) { - _address = address; +BLEHelper::setAddress(const bt_addr_le_t &address) { + memcpy(&_address, &address, sizeof(bt_addr_le_t)); } void @@ -115,21 +93,68 @@ BLEHelper::clearAddress() { memset(&_address, 0x00, sizeof(_address)); } -const struct bt_le_conn_param *BLEHelper::getConnParams() +void BLEHelper::getConnParams(ble_conn_param_t &user_conn_params) { - return &_conn_params; + user_conn_params.interval_min = UNITS_TO_MSEC(_conn_params.interval_min, UNIT_1_25_MS); + user_conn_params.interval_max = UNITS_TO_MSEC(_conn_params.interval_max, UNIT_1_25_MS); + user_conn_params.timeout = UNITS_TO_MSEC(_conn_params.timeout, UNIT_10_MS); + user_conn_params.latency = _conn_params.latency; } -void BLEHelper::setConnParames(uint16_t intervalmin, - uint16_t intervalmax, - uint16_t latency, - uint16_t timeout) +void BLEHelper::setConnectionParameters(uint16_t intervalmin, + uint16_t intervalmax, + uint16_t latency, + uint16_t timeout) { _conn_params.interval_max = intervalmin; _conn_params.interval_min = intervalmax; _conn_params.latency = latency; _conn_params.timeout = timeout; - +} + +void BLEHelper::updateConnectionInterval(uint16_t intervalmin, + uint16_t intervalmax, + uint16_t latency, + uint16_t timeout) +{ + setConnectionParameters(intervalmin, intervalmax, latency, timeout); + updateConnectionInterval(); +} + +void BLEHelper::updateConnectionInterval() +{ + bt_conn_t* conn = bt_conn_lookup_addr_le(&_address); + int ret = 0; + if (NULL != conn) + { + ret = bt_conn_le_param_update(conn, &_conn_params); + pr_debug(LOG_MODULE_BLE, "%s-ret:%d",__FUNCTION__, ret); + bt_conn_unref(conn); + } +} + +void BLEHelper::setConnectionInterval(float minInterval, + float maxInterval) +{ + uint16_t minVal = (uint16_t)MSEC_TO_UNITS(minInterval, UNIT_1_25_MS); + uint16_t maxVal = (uint16_t)MSEC_TO_UNITS(maxInterval, UNIT_1_25_MS); + _conn_params.interval_min = minVal; + _conn_params.interval_max = maxVal; + updateConnectionInterval(); +} + +void BLEHelper::setConnectionInterval(float minInterval, + float maxInterval, + uint16_t latency, + uint16_t timeout) +{ + uint16_t minVal = (uint16_t)MSEC_TO_UNITS(minInterval, UNIT_1_25_MS); + uint16_t maxVal = (uint16_t)MSEC_TO_UNITS(maxInterval, UNIT_1_25_MS); + uint16_t timeoutVal = MSEC_TO_UNITS(timeout, UNIT_10_MS); + _conn_params.interval_min = minVal; + _conn_params.interval_max = maxVal; + _conn_params.timeout = timeoutVal; + updateConnectionInterval(); } diff --git a/libraries/CurieBLE/src/BLEHelper.h b/libraries/CurieBLE/src/BLEHelper.h index f844eea6..f0c6bc21 100644 --- a/libraries/CurieBLE/src/BLEHelper.h +++ b/libraries/CurieBLE/src/BLEHelper.h @@ -31,12 +31,21 @@ class BLEHelper { virtual bool connected(void) = 0; /** - * Get the address of the Central in string form + * Get the address of the BLE in string format * - * @return const char* address of the Central in string form + * @return const char* address of the BLE in string format */ const char* address(void) const; + /** + * @brief Get the address of the BLE in raw format + * + * @param none + * + * @return const bt_addr_le_t * address of the BLE in raw format + * + * @note none + */ const bt_addr_le_t *bt_le_address(void) const; /** * Disconnect the central if it is connected @@ -53,25 +62,118 @@ class BLEHelper { bool operator==(const BLEHelper& rhs) const; bool operator==(const bt_addr_le_t& rhs) const; bool operator!=(const BLEHelper& rhs) const; - void operator=(const BLEHelper& rhs); - void setConn(struct bt_conn *conn); - const struct bt_le_conn_param *getConnParams(); - void setConnParames(uint16_t intervalmin, - uint16_t intervalmax, - uint16_t latency, - uint16_t timeout); + /** + * @brief Get the connection paramter + * + * @param[out] user_conn_params connection paramter + * Minimum Connection Interval (ms) + * Maximum Connection Interval (ms) + * Connection Latency + * Supervision Timeout (ms) + * + * @return none + * + * @note none + */ + void getConnParams(ble_conn_param_t &user_conn_params); + + /** + * @brief Set the connection paramter and send connection + * update request + * + * @param[in] intervalmin Minimum Connection Interval (ms) + * + * @param[in] intervalmax Maximum Connection Interval (ms) + * + * @return none + * + * @note none + */ + void setConnectionInterval(float minInterval, + float maxInterval); + + /** + * @brief Set the connection paramter and send connection + * update request + * + * @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 minInterval, + float maxInterval, + uint16_t latency, + uint16_t timeout); + + /** + * @brief Just set the connection parameter. + * Not send out connection update request. + * + * @param[in] intervalmin Minimum Connection Interval (N * 1.25 ms) + * + * @param[in] intervalmax Maximum Connection Interval (N * 1.25 ms) + * + * @param[in] latency Connection Latency + * + * @param[in] timeout Supervision Timeout (N * 10 ms) + * + * @return none + * + * @note The user should care the unit + */ + void setConnectionParameters(uint16_t intervalmin, + uint16_t intervalmax, + uint16_t latency, + uint16_t timeout); + + /** + * @brief Schedule the link connection update request + * + * @param[in] intervalmin Minimum Connection Interval (N * 1.25 ms) + * + * @param[in] intervalmax Maximum Connection Interval (N * 1.25 ms) + * + * @param[in] latency Connection Latency + * + * @param[in] timeout Supervision Timeout (N * 10 ms) + * + * @return none + * + * @note The user should care the unit + */ + void updateConnectionInterval(uint16_t intervalmin, + uint16_t intervalmax, + uint16_t latency, + uint16_t timeout); + + /** + * @brief Schedule the link connection update request + * + * @return none + * + * @note The connection update request will not send if + * parameter doesn't changed + */ + void updateConnectionInterval(); protected: - void setAddress(bt_addr_le_t address); + void setAddress(const bt_addr_le_t &address); void clearAddress(); BLEHelper(); virtual ~BLEHelper(); private: - bt_addr_le_t _address; - //struct bt_conn *_conn; - struct bt_le_conn_param _conn_params; + bt_addr_le_t _address; /// BT low energy address + bt_le_conn_param_t _conn_params; /// Connection parameter }; #endif diff --git a/libraries/CurieBLE/src/BLEPeripheral.cpp b/libraries/CurieBLE/src/BLEPeripheral.cpp index ca40137b..efbb77f6 100644 --- a/libraries/CurieBLE/src/BLEPeripheral.cpp +++ b/libraries/CurieBLE/src/BLEPeripheral.cpp @@ -30,9 +30,7 @@ BLEPeripheral::BLEPeripheral(void) : memset(_adv_data, 0x00, sizeof(_adv_data)); // Default Advertising parameter - setAdvertisingParam(BT_LE_ADV_IND , - 0xA0, - 0xF0); + setConnectable(true); } BLEPeripheral::~BLEPeripheral(void) @@ -70,7 +68,7 @@ BLEPeripheral::end() } void -BLEPeripheral::setAdvertisedServiceUuid(const struct bt_uuid* advertisedServiceUuid) +BLEPeripheral::setAdvertisedServiceUuid(const bt_uuid_t* advertisedServiceUuid) { _advertise_service_uuid = advertisedServiceUuid; } @@ -82,7 +80,7 @@ BLEPeripheral::setLocalName(const char* localName) } void -BLEPeripheral::setAdvertisedServiceData(const struct bt_uuid* serviceDataUuid, +BLEPeripheral::setAdvertisedServiceData(const bt_uuid_t* serviceDataUuid, uint8_t* serviceData, uint8_t serviceDataLength) { @@ -91,13 +89,30 @@ BLEPeripheral::setAdvertisedServiceData(const struct bt_uuid* serviceDataUuid, _service_data_length = serviceDataLength; } -void BLEPeripheral::setAdvertisingParam(uint8_t type, - uint16_t interval_min, - uint16_t interval_max) +void +BLEPeripheral::setAdvertisingInterval(float interval_min, + float interval_max) { - BLEPeripheralRole::instance()->setAdvertisingParam(type, - interval_min, - interval_max); + uint16_t max = (uint16_t) MSEC_TO_UNITS(interval_max, UNIT_0_625_MS); + uint16_t min = (uint16_t) MSEC_TO_UNITS(interval_min, UNIT_0_625_MS); + BLEPeripheralRole::instance()->setAdvertisingInterval(min, max); +} + +void +BLEPeripheral::setAdvertisingInterval(float advertisingInterval) +{ + setAdvertisingInterval(advertisingInterval, advertisingInterval); +} + +void +BLEPeripheral::setConnectable(bool connectable) +{ + uint8_t type = BT_LE_ADV_IND; + if (connectable == false) + { + type = BT_LE_ADV_NONCONN_IND; + } + BLEPeripheralRole::instance()->setAdvertisingType(type); } void @@ -143,9 +158,10 @@ BLEPeripheral::connected() return BLEPeripheralRole::instance()->connected(); } -void BLEPeripheral::addAttribute(BLEAttribute& attribute) +BleStatus +BLEPeripheral::addAttribute(BLEAttribute& attribute) { - BLEPeripheralRole::instance()->addAttribute(attribute); + return BLEPeripheralRole::instance()->addAttribute(attribute); } @@ -172,14 +188,14 @@ BLEPeripheral::_advDataInit(void) if (BT_UUID_TYPE_16 == _advertise_service_uuid->type) { //UINT16_TO_LESTREAM(adv_tmp, uuid.uuid16); - data = (uint8_t *)&(((struct bt_uuid_16 *)_advertise_service_uuid)->val); - length = sizeof(uint16_t); + data = (uint8_t *)&(((bt_uuid_16_t *)_advertise_service_uuid)->val); + length = UUID_SIZE_16; type = BT_DATA_UUID16_ALL; } else if (BT_UUID_TYPE_128 == _advertise_service_uuid->type) { - data = ((struct bt_uuid_128 *)_advertise_service_uuid)->val; - length = MAX_UUID_SIZE; + data = ((bt_uuid_128_t *)_advertise_service_uuid)->val; + length = UUID_SIZE_128; type = BT_DATA_UUID128_ALL; } if (NULL != data) @@ -232,7 +248,7 @@ BLEPeripheral::_advDataInit(void) uint8_t *adv_tmp = _service_data_buf; - UINT16_TO_LESTREAM(adv_tmp, (((struct bt_uuid_16 *)_service_data_uuid)->val)); + UINT16_TO_LESTREAM(adv_tmp, (((bt_uuid_16_t *)_service_data_uuid)->val)); memcpy(adv_tmp, _service_data, _service_data_length); lengthTotal += block_len; @@ -272,3 +288,7 @@ BLEPeripheral::stopAdvertising() return status; } +BLECentralHelper *BLEPeripheral::getPeerCentralBLE(BLEHelper& central) +{ + return (BLECentralHelper *)(¢ral); +} diff --git a/libraries/CurieBLE/src/BLEPeripheral.h b/libraries/CurieBLE/src/BLEPeripheral.h index 67b6f07b..33b8bef7 100644 --- a/libraries/CurieBLE/src/BLEPeripheral.h +++ b/libraries/CurieBLE/src/BLEPeripheral.h @@ -47,17 +47,17 @@ class BLEPeripheral{ /** * Set the service UUID that the BLE Peripheral Device advertises * - * @param advertisedServiceUuid 16-bit or 128-bit UUID to advertis + * @param[in] advertisedServiceUuid 16-bit or 128-bit UUID to advertis * (in string form) * * @note This method must be called before the begin method */ - void setAdvertisedServiceUuid(const struct bt_uuid* advertisedServiceUuid); + void setAdvertisedServiceUuid(const bt_uuid_t* advertisedServiceUuid); /** * Set the local name that the BLE Peripheral Device advertises * - * @param localName local name to advertise + * @param[in] localName local name to advertise * * @note This method must be called before the begin method */ @@ -66,14 +66,14 @@ class BLEPeripheral{ /** * Set the Service Data that the BLE Peripheral Device advertises * - * @param serviceDataUuid 16-bit Service UUID for this Service Data + * @param[in] serviceDataUuid 16-bit Service UUID for this Service Data * (in string form). Must match the UUID parameter * of setAdvertisedServiceUuid(). To fit into BLE_MAX_ADV_SIZE, * the UUID must be a 16-bit UUID. * - * @param serviceData binary array of Service Data. + * @param[in] serviceData binary array of Service Data. * - * @param serviceDataLength length (bytes) of serviceData[] + * @param[in] serviceDataLength length (bytes) of serviceData[] * * @note the entire advertising packet must be no more than * BLE_MAX_ADV_SIZE bytes, which is currently 31. @@ -85,29 +85,53 @@ class BLEPeripheral{ * the service data will silently not be copied * into the advertising block. */ - void setAdvertisedServiceData(const struct bt_uuid* serviceDataUuid, + void setAdvertisedServiceData(const bt_uuid_t* serviceDataUuid, uint8_t* serviceData, uint8_t serviceDataLength); + + /** + * @brief Set advertising interval + * + * @param[in] advertisingInterval Advertising Interval (N * 0.625) + * + * @return none + * + * @note none + */ + void setAdvertisingInterval(float advertisingInterval); + /** - * Set the ADV parameters about the ADV-Type and interval + * @brief Set advertising interval + * + * @param[in] interval_min Minimum Advertising Interval (millisecond) * - * @param type Advertising types + * @param[in] interval_max Maximum Advertising Interval (millisecond) * - * @param interval_min Minimum Advertising Interval (N * 0.625) + * @return none * - * @param interval_max Maximum Advertising Interval (N * 0.625) + * @note none + */ + void setAdvertisingInterval(float interval_min, + float interval_max); + + /** + * @brief Set advertising type as connectable/non-connectable + * + * @param[in] connectable true - The device connectable + * false - The device non-connectable * - * @note none + * @return none + * + * @note none */ - void setAdvertisingParam(uint8_t type, - uint16_t interval_min, - uint16_t interval_max); + void setConnectable(bool connectable); + /** * Set the device name for the BLE Peripheral Device * * If device name is not set, a default name will be used instead * - * @param device User-defined name string for this device. Truncated if + * @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 @@ -120,7 +144,7 @@ class BLEPeripheral{ * See https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.gap.appearance.xml * for available options. * - * @param appearance Appearance category identifier as defined by BLE Standard + * @param[in] appearance Appearance category identifier as defined by BLE Standard * * @return BleStatus indicating success or error * @@ -131,8 +155,8 @@ class BLEPeripheral{ /** * Set the min and max connection interval BLE Peripheral Device * - * @param minConnInterval Minimum connection interval (1.25 ms units), minimum 0x0006 (7.5ms) - * @param maxConnInterval Maximum connection interval (1.25 ms units), maximum 0x095f (2998.75ms) + * @param[in] minConnInterval Minimum connection interval (1.25 ms units), minimum 0x0006 (7.5ms) + * @param[in] maxConnInterval Maximum connection interval (1.25 ms units), maximum 0x095f (2998.75ms) * * @note This method must be called before the begin method */ @@ -141,19 +165,20 @@ class BLEPeripheral{ /** * Add an attribute to the BLE Peripheral Device * - * @param attribute Attribute to add to Peripheral + * @param[in] attribute Attribute to add to Peripheral * * @return BleStatus indicating success or error * * @note This method must be called before the begin method + * Only need check return value at first call. Memory only alloc at first call */ - void addAttribute(BLEAttribute& attribute); + BleStatus addAttribute(BLEAttribute& attribute); /** * Provide a function to be called when events related to this Device are raised * - * @param event Event type for callback - * @param callback Pointer to callback function to invoke when an event occurs. + * @param[in] event Event type for callback + * @param[in] callback Pointer to callback function to invoke when an event occurs. */ void setEventHandler(BLERoleEvent event, BLERoleEventHandler callback); @@ -195,35 +220,38 @@ class BLEPeripheral{ */ bool connected(void); - /** - * @brief Init the ADV data and start send advertisement - * - * @param none - * - * @return BleStatus 0 - Success. Others - error code - * - * @note none - */ + /** + * @brief Init the ADV data and start send advertisement + * + * @param none + * + * @return BleStatus 0 - Success. Others - error code + * + * @note none + */ BleStatus startAdvertising(void); - - /** - * @brief Stop send advertisement - * - * @param none - * - * @return BleStatus 0 - Success. Others - error code - * - * @note none - */ - BleStatus stopAdvertising(void); - + + /** + * @brief Stop send advertisement + * + * @param none + * + * @return BleStatus 0 - Success. Others - error code + * + * @note none + */ + BleStatus stopAdvertising(void); + + /** + * Get peer central device + * + *@param central peer central device of the peripheral board + * + * @return pointer of peer central device + */ + BLECentralHelper *getPeerCentralBLE(BLEHelper& central); + protected: - void handleConnectEvent(struct bt_conn *conn, uint8_t err); - void handleDisconnectEvent(struct bt_conn *conn, uint8_t reason); - void handleParamUpdated(struct bt_conn *conn, - uint16_t interval, - uint16_t latency, - uint16_t timeout); private: @@ -234,17 +262,17 @@ class BLEPeripheral{ private: const char* _local_name; - const struct bt_uuid* _service_data_uuid; + const bt_uuid_t* _service_data_uuid; uint8_t* _service_data; uint8_t _service_data_length; uint8_t _service_data_buf[BLE_MAX_ADV_SIZE]; uint16_t _appearance; - const struct bt_uuid* _advertise_service_uuid; + const bt_uuid_t* _advertise_service_uuid; uint8_t _adv_type; - struct bt_data _adv_data[4]; + bt_data_t _adv_data[4]; size_t _adv_data_idx; }; diff --git a/libraries/CurieBLE/src/BLEPeripheralHelper.cpp b/libraries/CurieBLE/src/BLEPeripheralHelper.cpp index 65d54c3d..2a13c03c 100644 --- a/libraries/CurieBLE/src/BLEPeripheralHelper.cpp +++ b/libraries/CurieBLE/src/BLEPeripheralHelper.cpp @@ -24,19 +24,19 @@ BLEAttribute *BLEPeripheralHelper::attribute(uint16_t handle) return _profile.attribute(handle); } -BLEAttribute *BLEPeripheralHelper::attribute(struct bt_gatt_subscribe_params *params) +BLEAttribute *BLEPeripheralHelper::attribute(bt_gatt_subscribe_params_t *params) { return _profile.attribute(params); } -void BLEPeripheralHelper::discover(const struct bt_gatt_attr *attr) +uint8_t BLEPeripheralHelper::discover(const bt_gatt_attr_t *attr) { // Not allow to call the discover if (NULL == _central) { - return; + return BT_GATT_ITER_STOP; } - _profile.discover(attr); + return _profile.discover(attr); } void BLEPeripheralHelper::discover() @@ -62,7 +62,7 @@ BLEPeripheralHelper::~BLEPeripheralHelper() bool BLEPeripheralHelper::disconnect(void) { int err = 0; - struct bt_conn* conn = bt_conn_lookup_addr_le(this->bt_le_address()); + bt_conn_t* conn = bt_conn_lookup_addr_le(this->bt_le_address()); if (NULL == conn) { return false; @@ -75,7 +75,7 @@ bool BLEPeripheralHelper::disconnect(void) bool BLEPeripheralHelper::connected(void) { - struct bt_conn* conn = bt_conn_lookup_addr_le(this->bt_le_address()); + bt_conn_t* conn = bt_conn_lookup_addr_le(this->bt_le_address()); if (NULL == conn) { return false; @@ -94,9 +94,9 @@ void BLEPeripheralHelper::linkLost(void) } } -void BLEPeripheralHelper::addAttribute(BLEAttribute& attribute) +BleStatus BLEPeripheralHelper::addAttribute(BLEAttribute& attribute) { - _profile.addAttribute(attribute); + return _profile.addAttribute(attribute); } int BLEPeripheralHelper::registerProfile() diff --git a/libraries/CurieBLE/src/BLEPeripheralHelper.h b/libraries/CurieBLE/src/BLEPeripheralHelper.h index 2d29a210..bdb7c719 100644 --- a/libraries/CurieBLE/src/BLEPeripheralHelper.h +++ b/libraries/CurieBLE/src/BLEPeripheralHelper.h @@ -47,28 +47,110 @@ class BLEPeripheralHelper : public BLEHelper { /** * Add an attribute to the BLE Peripheral helper * - * @param attribute Attribute to add to Peripheral + * @param[in] attribute Attribute to add to Peripheral + * + * @return BleStatus indicating success or error * * @note This method must be called before the begin method */ - void addAttribute(BLEAttribute& attribute); + BleStatus addAttribute(BLEAttribute& attribute); - BLEAttribute *attribute(struct bt_gatt_subscribe_params *params); + /** + * @brief Get BLEAttribute by subscribe parameter + * + * @param[in] params Subscribe parameter + * + * @return BLEAttribute * NULL - Not found + * Not NULL - The BLEAttribute object + * + * @note none + */ + BLEAttribute *attribute(bt_gatt_subscribe_params_t *params); + + /** + * @brief Get BLEAttribute by characteristic handle + * + * @param[in] handle The characteristic handle + * + * @return BLEAttribute * NULL - Not found + * Not NULL - The BLEAttribute object + * + * @note none + */ BLEAttribute *attribute(uint16_t handle); /** - * For central to discover the profile + * @brief Discover the BLE peripheral profile for central + * + * @param none + * + * @return none + * + * @note This function only for the central device. + * + * @note The central deivce didn't know the connected BLE's profile. + * Need send discover request to search the attribute in the BLE peripheral */ void discover(); - void discover(const struct bt_gatt_attr *attr); + + /** + * @brief Process the discover response and + * discover the BLE peripheral profile + * + * @param[in] const bt_gatt_attr_t * The gatt attribute response + * + * @return uint8_t BT_GATT_ITER_STOP Stop discover the profile + * BT_GATT_ITER_CONTINUE Continue to send the discover request + * + * @note This function only for the central device. + */ + uint8_t discover(const bt_gatt_attr_t *attr); - // For peripheral to register the tree + /** + * @brief For peripheral to register the profile tree + * + * @param none + * + * @return int 0 - success + * other - error code + * + * @note none + */ int registerProfile(); + + /** + * @brief Process the link lost event + * + * @param none + * + * @return none + * + * @note none + */ void linkLost(void); - // Get value handle. - // 0 is invalid + /** + * @brief Get the characteristic value handle + * + * @param[in] attr Attribute object + * + * @return uint16_t The value hander of attribute + * 0 is invalid + * + * @note Only for central mode + */ uint16_t valueHandle(BLEAttribute *attr); + + /** + * @brief Get characteristic configuration descriptor value handle + * + * @param[in] attr Attribute object + * + * @return uint16_t The value hander of attribute + * 0 is invalid + * + * @note Only for central mode + */ uint16_t cccdHandle(BLEAttribute *attr); protected: diff --git a/libraries/CurieBLE/src/BLEPeripheralRole.cpp b/libraries/CurieBLE/src/BLEPeripheralRole.cpp index dc13b566..ff5baad6 100644 --- a/libraries/CurieBLE/src/BLEPeripheralRole.cpp +++ b/libraries/CurieBLE/src/BLEPeripheralRole.cpp @@ -43,6 +43,11 @@ BLEPeripheralRole::BLEPeripheralRole(void) : { memset(_event_handlers, 0x00, sizeof(_event_handlers)); _peripheral.setAddress(_local_bda); + + _adv_param.type = BT_LE_ADV_IND; + _adv_param.addr_type = _local_bda.type; + _adv_param.interval_min = 0xA0; + _adv_param.interval_max = 0xF0; } BLEPeripheralRole::~BLEPeripheralRole(void) @@ -64,9 +69,10 @@ bool BLEPeripheralRole::begin() // Set device name setDeviceName(); + delay(4); // Register profile _peripheral.registerProfile(); - delay(2); // Temp solution for send data fast will makes ADV data set failed + delay(8); // Temp solution for send data fast will makes ADV data set failed return true; } @@ -128,14 +134,16 @@ bool BLEPeripheralRole::disconnect() { BleStatus status = BLE_STATUS_WRONG_STATE; + int err; if (BLE_PERIPH_STATE_CONNECTED == _state) { - struct bt_conn *central_conn = bt_conn_lookup_addr_le(_central.bt_le_address()); + bt_conn_t *central_conn = bt_conn_lookup_addr_le(_central.bt_le_address()); if (NULL != central_conn) { - status = bt_conn_disconnect (central_conn, - BT_HCI_ERR_REMOTE_USER_TERM_CONN); + err = bt_conn_disconnect (central_conn, + BT_HCI_ERR_REMOTE_USER_TERM_CONN); + status = errorno_to_ble_status(err); bt_conn_unref(central_conn); } } @@ -158,9 +166,10 @@ BLEPeripheralRole::connected() return _central; } -void BLEPeripheralRole::addAttribute(BLEAttribute& attribute) +BleStatus +BLEPeripheralRole::addAttribute(BLEAttribute& attribute) { - _peripheral.addAttribute(attribute); + return _peripheral.addAttribute(attribute); } BleStatus @@ -183,9 +192,9 @@ BLEPeripheralRole::stopAdvertising() } BleStatus -BLEPeripheralRole::startAdvertising(const struct bt_data *ad, +BLEPeripheralRole::startAdvertising(const bt_data_t *ad, size_t ad_len, - const struct bt_data *sd, + const bt_data_t *sd, size_t sd_len) { int ret; @@ -204,16 +213,24 @@ BLEPeripheralRole::startAdvertising(const struct bt_data *ad, return BLE_STATUS_SUCCESS; } -void BLEPeripheralRole::setAdvertisingParam(uint8_t type, - uint16_t interval_min, - uint16_t interval_max) +void BLEPeripheralRole::setAdvertisingInterval(uint16_t advertisingInterval) +{ + setAdvertisingInterval(advertisingInterval, advertisingInterval); +} + +void BLEPeripheralRole::setAdvertisingInterval(uint16_t interval_min, + uint16_t interval_max) { - _adv_param.addr_type = _local_bda.type; - _adv_param.type = type; _adv_param.interval_min = interval_min; _adv_param.interval_max = interval_max; } +void +BLEPeripheralRole::setAdvertisingType(uint8_t type) +{ + _adv_param.type = type; +} + BleStatus BLEPeripheralRole::stop(void) { @@ -235,7 +252,7 @@ BLEPeripheralRole::stop(void) return BLE_STATUS_SUCCESS; } -void BLEPeripheralRole::handleConnectEvent(struct bt_conn *conn, uint8_t err) +void BLEPeripheralRole::handleConnectEvent(bt_conn_t *conn, uint8_t err) { // Update the central address const bt_addr_le_t *central_addr = bt_conn_get_dst(conn); @@ -245,12 +262,15 @@ void BLEPeripheralRole::handleConnectEvent(struct bt_conn *conn, uint8_t err) // Call the CB if (_event_handlers[BLEConnected]) _event_handlers[BLEConnected](_central); + + if (BLE_PERIPH_STATE_ADVERTISING == _state) + _state = BLE_PERIPH_STATE_CONNECTED; } -void BLEPeripheralRole::handleDisconnectEvent(struct bt_conn *conn, uint8_t reason) +void BLEPeripheralRole::handleDisconnectEvent(bt_conn_t *conn, uint8_t reason) { - struct bt_conn *central_conn = bt_conn_lookup_addr_le(_central.bt_le_address()); + bt_conn_t *central_conn = bt_conn_lookup_addr_le(_central.bt_le_address()); if (conn == central_conn) { pr_info(LOG_MODULE_BLE, "Peripheral Disconnect reason: %d", reason); @@ -262,17 +282,23 @@ void BLEPeripheralRole::handleDisconnectEvent(struct bt_conn *conn, uint8_t reas { bt_conn_unref(central_conn); } + + if (BLE_PERIPH_STATE_CONNECTED == _state) + _state = BLE_PERIPH_STATE_ADVERTISING; } -void BLEPeripheralRole::handleParamUpdated(struct bt_conn *conn, - uint16_t interval, - uint16_t latency, - uint16_t timeout) +void BLEPeripheralRole::handleParamUpdated(bt_conn_t *conn, + uint16_t interval, + uint16_t latency, + uint16_t timeout) { pr_info(LOG_MODULE_BLE, "Parameter updated\r\n\tConn: %p\r\n\tinterval: %d\r\n\tlatency: %d\r\n\ttimeout: %d", conn, interval, latency, timeout); if (_event_handlers[BLEUpdateParam]) + { + _central.setConnectionParameters(interval, interval, latency, timeout); _event_handlers[BLEUpdateParam](_central); + } } diff --git a/libraries/CurieBLE/src/BLEPeripheralRole.h b/libraries/CurieBLE/src/BLEPeripheralRole.h index 0fcbd40e..87c311b5 100644 --- a/libraries/CurieBLE/src/BLEPeripheralRole.h +++ b/libraries/CurieBLE/src/BLEPeripheralRole.h @@ -46,7 +46,7 @@ class BLEPeripheralRole: public BLERoleBase{ * * If device name is not set, a default name will be used instead * - * @param deviceName User-defined name string for this device. Truncated if + * @param[in] deviceName 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 @@ -56,8 +56,8 @@ class BLEPeripheralRole: public BLERoleBase{ /** * Set the min and max connection interval BLE Peripheral Device * - * @param minConnInterval Minimum connection interval (1.25 ms units), minimum 0x0006 (7.5ms) - * @param maxConnInterval Maximum connection interval (1.25 ms units), maximum 0x095f (2998.75ms) + * @param[in] minConnInterval Minimum connection interval (1.25 ms units), minimum 0x0006 (7.5ms) + * @param[in] maxConnInterval Maximum connection interval (1.25 ms units), maximum 0x0C80 (4000ms) * * @note This method must be called before the begin method */ @@ -66,19 +66,19 @@ class BLEPeripheralRole: public BLERoleBase{ /** * Add an attribute to the BLE Peripheral Device * - * @param attribute Attribute to add to Peripheral + * @param[in] attribute Attribute to add to Peripheral * * @return BleStatus indicating success or error * * @note This method must be called before the begin method */ - void addAttribute(BLEAttribute& attribute); + BleStatus addAttribute(BLEAttribute& attribute); /** * Provide a function to be called when events related to this Device are raised * - * @param event Event type for callback - * @param callback Pointer to callback function to invoke when an event occurs. + * @param[in] event Event type for callback + * @param[in] callback Pointer to callback function to invoke when an event occurs. */ void setEventHandler(BLERoleEvent event, BLERoleEventHandler callback); @@ -123,50 +123,70 @@ class BLEPeripheralRole: public BLERoleBase{ /** * @brief Start peripheral advertising * - * @param ad The ADV data array + * @param[in] ad The ADV data array * - * @param ad_len The ADV data array length + * @param[in] ad_len The ADV data array length * - * @param sd The Scan response data array + * @param[in] sd The Scan response data array * - * @param sd_len The Scan response data array length + * @param[in] sd_len The Scan response data array length * * @return BleStatus * * @note none */ - BleStatus startAdvertising(const struct bt_data *ad, + BleStatus startAdvertising(const bt_data_t *ad, size_t ad_len, - const struct bt_data *sd, + const bt_data_t *sd, size_t sd_len); - - /** - * @brief Stop send advertisement - * - * @param none - * - * @return none - * - * @note none - */ + + /** + * @brief Stop send advertisement + * + * @param none + * + * @return none + * + * @note none + */ BleStatus stopAdvertising(); - + + /** + * @brief Set advertising parameter + * + * @param[in] advertisingInterval Advertising Interval (N * 0.625) + * + * @return none + * + * @note none + */ + void setAdvertisingInterval(uint16_t advertisingInterval); + /** * @brief Set advertising parameter * - * @param type Advertising type + * @param[in] interval_min Minimum Advertising Interval (N * 0.625) + * + * @param[in] interval_max Maximum Advertising Interval (N * 0.625) * - * @param interval_min Minimum Advertising Interval (N * 0.625) + * @return none + * + * @note none + */ + void setAdvertisingInterval(uint16_t interval_min, + uint16_t interval_max); + + /** + * @brief Set advertising type * - * @param interval_max Maximum Advertising Interval (N * 0.625) + * @param[in] type Advertising type + * BT_LE_ADV_IND, BT_LE_ADV_NONCONN_IND * * @return none * * @note none */ - void setAdvertisingParam(uint8_t type, - uint16_t interval_min, - uint16_t interval_max); + void setAdvertisingType(uint8_t type); /** * @brief Get BLE Peripheral instance. @@ -184,45 +204,45 @@ class BLEPeripheralRole: public BLERoleBase{ /** * @brief Handle the connected event * - * @param conn The object that established the connection + * @param[in] conn The object that established the connection * - * @param err The code of the process + * @param[in] err The code of the process * * @return none * * @note none */ - void handleConnectEvent(struct bt_conn *conn, uint8_t err); + void handleConnectEvent(bt_conn_t *conn, uint8_t err); /** * @brief Handle the disconnected event * - * @param conn The object that lost the connection + * @param[in] conn The object that lost the connection * - * @param reason The link lost reason + * @param[in] reason The link lost reason * * @return none * * @note none */ - void handleDisconnectEvent(struct bt_conn *conn, uint8_t reason); + void handleDisconnectEvent(bt_conn_t *conn, uint8_t reason); /** * @brief Handle the conntion update request * - * @param conn The connection object that need to process the update request + * @param[in] conn The connection object that need to process the update request * - * @param interval The connection interval + * @param[in] interval The connection interval (N*1.25)ms * - * @param latency The connection latency + * @param[in] latency The connection latency * - * @param timeout The connection timeout + * @param[in] timeout The connection timeout (N*10)ms * * @return none * * @note none */ - void handleParamUpdated(struct bt_conn *conn, + void handleParamUpdated(bt_conn_t *conn, uint16_t interval, uint16_t latency, uint16_t timeout); diff --git a/libraries/CurieBLE/src/BLEProfile.cpp b/libraries/CurieBLE/src/BLEProfile.cpp index 186bf4fa..10f0c91e 100644 --- a/libraries/CurieBLE/src/BLEProfile.cpp +++ b/libraries/CurieBLE/src/BLEProfile.cpp @@ -24,27 +24,54 @@ #include "BLEPeripheralRole.h" // Only for peripheral -ssize_t profile_read_process(struct bt_conn *conn, - const struct bt_gatt_attr *attr, +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) + { + BLECharacteristic* blecharacteritic; + blecharacteritic = (BLECharacteristic*)bleattr; + pvalue = blecharacteritic->value(); + return bt_gatt_attr_read(conn, attr, buf, len, offset, pvalue, + blecharacteritic->valueLength()); + } + else if (BLETypeDescriptor == type) + { + BLEDescriptor *bledescriptor = (BLEDescriptor *)bleattr; + pvalue = bledescriptor->value(); + return bt_gatt_attr_read(conn, attr, buf, len, offset, pvalue, bledescriptor->valueLength()); + } + return 0; +} + +// Only for peripheral +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; BLECharacteristic* blecharacteritic; BLEAttributeType type = bleattr->type(); - if (BLETypeCharacteristic != type) + BLECentralHelper central = BLEPeripheralRole::instance()->central(); + if ((BLETypeCharacteristic != type) || 0 != offset) { return 0; } + blecharacteritic = (BLECharacteristic*)bleattr; - pvalue = blecharacteritic->value(); - return bt_gatt_attr_read(conn, attr, buf, len, offset, pvalue, - blecharacteritic->valueLength()); + blecharacteritic->setValue(*((BLEHelper *)¢ral), (const uint8_t *) buf, len); + + return len; } -// Only for peripheral -ssize_t profile_write_process(struct bt_conn *conn, +ssize_t profile_longwrite_process(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, uint16_t len, uint16_t offset) @@ -54,21 +81,50 @@ ssize_t profile_write_process(struct bt_conn *conn, BLECharacteristic* blecharacteritic; BLEAttributeType type = bleattr->type(); BLECentralHelper central = BLEPeripheralRole::instance()->central(); - if ((BLETypeCharacteristic != type) || 0 != offset) + if (BLETypeCharacteristic != type) { return 0; } blecharacteritic = (BLECharacteristic*)bleattr; - blecharacteritic->setValue(*((BLEHelper *)¢ral), (const uint8_t *) buf, len); + blecharacteritic->setBuffer(*((BLEHelper *)¢ral), (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) +{ + BLEAttribute *bleattr = (BLEAttribute *)attr->user_data; + BLECharacteristic* blecharacteritic; + BLEAttributeType type = bleattr->type(); + BLECentralHelper central = BLEPeripheralRole::instance()->central(); + if (BLETypeCharacteristic != type) + { + return 0; + } + + blecharacteritic = (BLECharacteristic*)bleattr; + + 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(*((BLEHelper *)¢ral)); + return 0; + } + + return -EINVAL; +} + // Only for central -uint8_t profile_notify_process (struct bt_conn *conn, - struct bt_gatt_subscribe_params *params, +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 @@ -82,18 +138,17 @@ uint8_t profile_notify_process (struct bt_conn *conn, } // Only for central -uint8_t profile_discover_process(struct bt_conn *conn, - const struct bt_gatt_attr *attr, - struct bt_gatt_discover_params *params) +uint8_t profile_discover_process(bt_conn_t *conn, + const bt_gatt_attr_t *attr, + bt_gatt_discover_params_t *params) { BLEPeripheralHelper* peripheral = BLECentralRole::instance()->peripheral(conn);// Find peripheral by bt_conn - peripheral->discover(attr); - return BT_GATT_ITER_STOP; + return peripheral->discover(attr); } // Only for central -uint8_t profile_read_rsp_process(struct bt_conn *conn, int err, - struct bt_gatt_read_params *params, +uint8_t profile_read_rsp_process(bt_conn_t *conn, int err, + bt_gatt_read_params_t *params, const void *data, uint16_t length) { @@ -140,24 +195,56 @@ BLEProfile::~BLEProfile (void) } } -void BLEProfile::addAttribute (BLEAttribute& attribute) +BleStatus +BLEProfile::addAttribute (BLEAttribute& attribute) { - struct bt_gatt_attr *start; + bt_gatt_attr_t *start; + BleStatus err_code = BLE_STATUS_SUCCESS; + if (NULL == _attributes) { _attributes = (BLEAttribute**)malloc(BLEAttribute::numAttributes() * sizeof(BLEAttribute*)); memset(_attributes, 0x00, BLEAttribute::numAttributes() * sizeof(BLEAttribute*)); + if (NULL == _attributes) + { + err_code = BLE_STATUS_NO_MEMORY; + } } if (NULL == _attr_base) { - _attr_base = (struct bt_gatt_attr *)malloc((BLEAttribute::numAttributes() + BLECharacteristic::numNotifyAttributes()) * sizeof(struct bt_gatt_attr)); - memset(_attr_base, 0x00, ((BLEAttribute::numAttributes() + BLECharacteristic::numNotifyAttributes()) * sizeof(struct bt_gatt_attr))); + _attr_base = (bt_gatt_attr_t *)malloc((BLEAttribute::numAttributes() + BLECharacteristic::numNotifyAttributes()) * sizeof(bt_gatt_attr_t)); + memset(_attr_base, 0x00, ((BLEAttribute::numAttributes() + BLECharacteristic::numNotifyAttributes()) * sizeof(bt_gatt_attr_t))); pr_info(LOG_MODULE_BLE, "_attr_base_-%p, size-%d", _attr_base, sizeof(_attr_base)); + if (NULL == _attr_base) + { + err_code = BLE_STATUS_NO_MEMORY; + } } if (NULL == _sub_param) { - _sub_param = (struct bt_gatt_subscribe_params *)malloc((BLECharacteristic::numNotifyAttributes()) * sizeof(struct bt_gatt_subscribe_params)); - memset(_sub_param, 0x00, ((BLECharacteristic::numNotifyAttributes()) * sizeof(struct bt_gatt_subscribe_params))); + _sub_param = (bt_gatt_subscribe_params_t *)malloc((BLECharacteristic::numNotifyAttributes()) * sizeof(bt_gatt_subscribe_params_t)); + memset(_sub_param, 0x00, ((BLECharacteristic::numNotifyAttributes()) * sizeof(bt_gatt_subscribe_params_t))); + if (NULL == _sub_param) + { + err_code = BLE_STATUS_NO_MEMORY; + } + } + + if (BLE_STATUS_SUCCESS != err_code) + { + if (NULL != _attributes) + { + free(_attributes); + } + if (NULL != _attr_base) + { + free(_attr_base); + } + if (NULL != _sub_param) + { + free(_sub_param); + } + return err_code; } _attributes[_num_attributes] = &attribute; @@ -174,34 +261,46 @@ void BLEProfile::addAttribute (BLEAttribute& attribute) BLECharacteristic* characteritic = (BLECharacteristic*) &attribute; // Characteristic - memset(start, 0, sizeof(struct bt_gatt_attr)); + memset(start, 0, sizeof(bt_gatt_attr_t)); start->uuid = BLECharacteristic::getCharacteristicAttributeUuid(); start->perm = BT_GATT_PERM_READ; start->read = bt_gatt_attr_read_chrc; start->user_data = characteritic->getCharacteristicAttValue(); characteritic->addCharacteristicDeclaration(start); - pr_info(LOG_MODULE_BLE, "chrc-%p, uuid type-%d", start, start->uuid->type); + + pr_info(LOG_MODULE_BLE, "chrc-%p, uuid type-%d", start, start->uuid->type); + start++; _attr_index++; // Descriptor - memset(start, 0, sizeof(struct bt_gatt_attr)); + memset(start, 0, sizeof(bt_gatt_attr_t)); start->uuid = characteritic->uuid(); start->perm = characteritic->getPermission(); - start->read = profile_read_process; - start->write = profile_write_process; start->user_data = (void*)&attribute; characteritic->addCharacteristicValue(start); - pr_info(LOG_MODULE_BLE, "desc-%p, uuid: 0x%x", start, ((struct bt_uuid_16*) start->uuid)->val); + start->read = profile_read_process; + + if (characteritic->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; + } + pr_info(LOG_MODULE_BLE, "desc-%p, uuid: 0x%x", start, ((bt_uuid_16_t*) start->uuid)->val); start++; _attr_index++; // CCCD if (characteritic->subscribed()) { - pr_info(LOG_MODULE_BLE, "cccd-%p", start); // Descriptor - memset(start, 0, sizeof(struct bt_gatt_attr)); + memset(start, 0, sizeof(bt_gatt_attr_t)); start->uuid = characteritic->getClientCharacteristicConfigUuid(); start->perm = BT_GATT_PERM_READ | BT_GATT_PERM_WRITE; start->read = bt_gatt_attr_read_ccc; @@ -209,27 +308,42 @@ void BLEProfile::addAttribute (BLEAttribute& attribute) start->user_data = characteritic->getCccCfg(); characteritic->addCharacteristicConfigDescriptor(start); + pr_info(LOG_MODULE_BLE, "cccd-%p", start); + start++; _attr_index++; } } else if (BLETypeService == type) { - pr_info(LOG_MODULE_BLE, "service-%p", start); start->uuid = BLEService::getPrimayUuid(); start->perm = BT_GATT_PERM_READ; start->read = bt_gatt_attr_read_service; start->user_data = attribute.uuid(); + + pr_debug(LOG_MODULE_BLE, "service-%p", start); start++; _attr_index++; } - + else if (BLETypeDescriptor == type) + { + start->uuid = attribute.uuid(); + start->perm = BT_GATT_PERM_READ; + start->read = profile_read_process; + start->user_data = (void*)&attribute; + + pr_debug(LOG_MODULE_BLE, "Descriptor-%p", start); + start++; + _attr_index++; + } + return err_code; } int BLEProfile::registerProfile() { int ret = 0; +#if 0 // Start debug int i; @@ -244,117 +358,194 @@ int BLEProfile::registerProfile() delay(1000); // End for debug +#endif ret = bt_gatt_register(_attr_base, _attr_index); - pr_info(LOG_MODULE_APP, "%s: ret, %d", __FUNCTION__, ret); + pr_debug(LOG_MODULE_APP, "%s: ret, %d", __FUNCTION__, ret); return ret; } -void BLEProfile::discover(const struct bt_gatt_attr *attr) +void BLEProfile::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 BLEProfile::descriptorDiscoverRsp(const bt_gatt_attr_t *attr, BLEAttribute* bleattr) { - BLEAttribute* attribute = NULL; 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 BLEProfile::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++) { - attribute = _attributes[i]; - if (attribute->discovering()) + // Find the discovering attribute + attribute_tmp = _attributes[i]; + if (attribute_tmp->discovering()) { - if (NULL != attr) + if (NULL == attr) + { + attribute_tmp->discover(attr, &_discover_params); + break; + } + // Discover success + switch (_discover_params.type) { - // Discover success - switch (_discover_params.type) + case BT_GATT_DISCOVER_CHARACTERISTIC: { - case BT_GATT_DISCOVER_CHARACTERISTIC: - { - struct bt_gatt_attr *attr_dec = declarationAttr(attribute); - attr_dec++; - attr_dec->handle = attr->handle + 1; - break; - } - case BT_GATT_DISCOVER_DESCRIPTOR: - { - BLECharacteristic *chrc = (BLECharacteristic *)attribute; - struct bt_gatt_attr *attr_dec = declarationAttr(attribute); - struct bt_gatt_attr *attr_chrc = attr_dec + 1; - struct bt_gatt_attr *attr_cccd = attr_dec + 2; - struct bt_gatt_subscribe_params *sub_param_tmp = chrc->getSubscribeParams(); - struct bt_gatt_subscribe_params *sub_param = _sub_param + _sub_param_idx; - struct bt_conn *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(struct bt_gatt_subscribe_params)); - 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); - } - break; - } - case BT_GATT_DISCOVER_PRIMARY: - default: - { - // Do nothing - break; - } + 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; } } - attribute->discover(attr, &_discover_params); break; } } - // Send discover - if (attribute->discovering()) - { - send_discover = true; - } - else + // Find next attribute to discover + if (attribute_tmp->discovering() == false) { // Current attribute complete discovery - // Find next attribute to discover i++; - if (i < _num_attributes) + while (i < _num_attributes) { - attribute = _attributes[i]; - attribute->discover(&_discover_params); - send_discover = true; + 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; + } - if (send_discover) + // Send the discover request if necessary + if (send_discover && attribute_tmp->discovering()) { - struct bt_conn *conn = bt_conn_lookup_addr_le(_peripheral->bt_le_address()); + 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; + 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; + return ret; } } + return ret; } @@ -362,7 +553,7 @@ void BLEProfile::discover() { int err; BLEService *serviceattr = (BLEService *)_attributes[0]; - struct bt_conn *conn = bt_conn_lookup_addr_le(_peripheral->bt_le_address()); + bt_conn_t *conn = bt_conn_lookup_addr_le(_peripheral->bt_le_address()); if (NULL == conn) { @@ -384,12 +575,12 @@ void BLEProfile::discover() } } -BLEAttribute *BLEProfile::attribute(struct bt_gatt_subscribe_params *params) +BLEAttribute *BLEProfile::attribute(bt_gatt_subscribe_params_t *params) { return attribute(params->value_handle); } -BLEAttribute *BLEProfile::attribute(const struct bt_uuid* uuid) +BLEAttribute *BLEProfile::attribute(const bt_uuid_t* uuid) { int i; BLEAttribute *attr_tmp = NULL; @@ -424,7 +615,7 @@ BLEAttribute *BLEProfile::attribute(const struct bt_uuid* uuid) BLEAttribute *BLEProfile::attribute(uint16_t handle) { int i; - struct bt_gatt_attr *attr_gatt = NULL; + bt_gatt_attr_t *attr_gatt = NULL; for (i = 0; i < _attr_index; i++) { attr_gatt = _attr_base + i; @@ -475,7 +666,7 @@ BLEAttribute *BLEProfile::attribute(uint16_t handle) void BLEProfile::clearHandles(void) { int i; - struct bt_gatt_attr *attr = NULL; + bt_gatt_attr_t *attr = NULL; // Didn't need to unsubscribe // The stack will unsubscribe the notify when disconnected. // The sub_param has some pointer. So can't call memset. Just reset the index. @@ -489,10 +680,10 @@ void BLEProfile::clearHandles(void) } } -struct bt_gatt_attr* BLEProfile::declarationAttr(BLEAttribute *attr) +bt_gatt_attr_t* BLEProfile::declarationAttr(BLEAttribute *attr) { int i; - struct bt_gatt_attr *attr_gatt = NULL; + bt_gatt_attr_t *attr_gatt = NULL; for (i = 0; i < _attr_index; i++) { @@ -510,7 +701,7 @@ struct bt_gatt_attr* BLEProfile::declarationAttr(BLEAttribute *attr) uint16_t BLEProfile::valueHandle(BLEAttribute *attr) { uint16_t handle = 0; - struct bt_gatt_attr *attr_gatt = declarationAttr(attr); + bt_gatt_attr_t *attr_gatt = declarationAttr(attr); attr_gatt++; if (attr_gatt->uuid == attr->uuid()) { @@ -522,7 +713,7 @@ uint16_t BLEProfile::valueHandle(BLEAttribute *attr) uint16_t BLEProfile::cccdHandle(BLEAttribute *attr) { uint16_t handle = 0; - struct bt_gatt_attr *attr_gatt = declarationAttr(attr); + bt_gatt_attr_t *attr_gatt = declarationAttr(attr); attr_gatt+= 2; if (attr_gatt->uuid == BLECharacteristic::getClientCharacteristicConfigUuid()) { diff --git a/libraries/CurieBLE/src/BLEProfile.h b/libraries/CurieBLE/src/BLEProfile.h index 33b06c75..300d54e8 100644 --- a/libraries/CurieBLE/src/BLEProfile.h +++ b/libraries/CurieBLE/src/BLEProfile.h @@ -34,13 +34,13 @@ class BLEProfile{ /** * @brief Add an attribute to the BLE Peripheral Device * - * @param attribute Attribute to add to Peripheral + * @param[in] attribute Attribute to add to Peripheral * * @return BleStatus indicating success or error * * @note This method must be called before the begin method */ - void addAttribute(BLEAttribute& attribute); + BleStatus addAttribute(BLEAttribute& attribute); /** * @brief Register the profile to Nordic BLE stack @@ -56,19 +56,19 @@ class BLEProfile{ /** * @brief Get BLEAttribute by subscribe parameter * - * @param struct bt_gatt_subscribe_params * Subscribe parameter + * @param[in] params Subscribe parameter * * @return BLEAttribute * NULL - Not found * Not NULL - The BLEAttribute object * * @note none */ - BLEAttribute *attribute(struct bt_gatt_subscribe_params *params); + BLEAttribute *attribute(bt_gatt_subscribe_params_t *params); /** * @brief Get BLEAttribute by characteristic handle * - * @param uint16_t The characteristic handle + * @param[in] handle The characteristic handle * * @return BLEAttribute * NULL - Not found * Not NULL - The BLEAttribute object @@ -81,13 +81,14 @@ class BLEProfile{ * @brief Process the discover response and * discover the BLE peripheral profile * - * @param const struct bt_gatt_attr * The gatt attribute response + * @param[in] const bt_gatt_attr_t * The gatt attribute response * - * @return none + * @return uint8_t BT_GATT_ITER_STOP Stop discover the profile + * BT_GATT_ITER_CONTINUE Continue to send the discover request * * @note This function only for the central device. */ - void discover(const struct bt_gatt_attr *attr); + uint8_t discover(const bt_gatt_attr_t *attr); /** * @brief Discover the BLE peripheral profile @@ -119,7 +120,7 @@ class BLEProfile{ /** * @brief Get the characteristic value handle * - * @param none + * @param[in] attr Attribute object * * @return uint16_t The value handle * 0 is invalid handle @@ -131,7 +132,7 @@ class BLEProfile{ /** * @brief Get characteristic configuration descriptor value handle * - * @param none + * @param[in] attr Attribute object * * @return uint16_t The value handle * 0 is invalid handle @@ -140,15 +141,15 @@ class BLEProfile{ */ uint16_t cccdHandle(BLEAttribute *attr); protected: - friend ssize_t profile_write_process(struct bt_conn *conn, - const struct bt_gatt_attr *attr, + 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: /** * @brief Get BLEAttribute by UUID * - * @param const struct bt_uuid* The UUID + * @param[in] const bt_uuid_t* The UUID * * @return BLEAttribute * NULL - Not found * Not NULL - The BLEAttribute object @@ -157,32 +158,58 @@ class BLEProfile{ * Because the uuid pointer in bt_gatt_attr is got from BLEAttribute * So set this as private. */ - BLEAttribute *attribute(const struct bt_uuid* uuid); + BLEAttribute *attribute(const bt_uuid_t* uuid); /** * @brief Get bt_gatt_attr by BLEAttribute class * - * @param BLEAttribute * The BLEAttribute object + * @param[in] BLEAttribute * The BLEAttribute object * - * @return struct bt_gatt_attr* NULL - Not found + * @return bt_gatt_attr_t* NULL - Not found * Not NULL - The bt_gatt_attr in the stack * * @note none */ - struct bt_gatt_attr* declarationAttr(BLEAttribute *attr); + bt_gatt_attr_t* declarationAttr(BLEAttribute *attr); + + /** + * @brief Process the descriptor discover response + * + * @param[in] const bt_gatt_attr_t * The discover response + * + * @param[in] BLEAttribute * The BLEAttribute object in discovering + * + * @return none + * + * @note none + */ + void descriptorDiscoverRsp(const bt_gatt_attr_t *attr, BLEAttribute* bleattr); + + /** + * @brief Process the characteristic discover response + * + * @param[in] const bt_gatt_attr_t * The discover response + * + * @param[in] BLEAttribute * The BLEAttribute object in discovering + * + * @return none + * + * @note none + */ + void characteristicDiscoverRsp(const bt_gatt_attr_t *attr, BLEAttribute* bleattr); private: BLEPeripheralHelper *_peripheral; - struct bt_gatt_attr *_attr_base; + bt_gatt_attr_t *_attr_base; int _attr_index; BLEAttribute** _attributes; uint16_t _num_attributes; - struct bt_gatt_subscribe_params *_sub_param; + bt_gatt_subscribe_params_t *_sub_param; int _sub_param_idx; - struct bt_gatt_discover_params _discover_params; + bt_gatt_discover_params_t _discover_params; }; #endif diff --git a/libraries/CurieBLE/src/BLERoleBase.cpp b/libraries/CurieBLE/src/BLERoleBase.cpp index 6ab9def5..0285b857 100644 --- a/libraries/CurieBLE/src/BLERoleBase.cpp +++ b/libraries/CurieBLE/src/BLERoleBase.cpp @@ -21,7 +21,7 @@ #include "BLERoleBase.h" -void bleConnectEventHandler(struct bt_conn *conn, +void bleConnectEventHandler(bt_conn_t *conn, uint8_t err, void *param) { @@ -31,7 +31,7 @@ void bleConnectEventHandler(struct bt_conn *conn, } -void bleDisconnectEventHandler(struct bt_conn *conn, +void bleDisconnectEventHandler(bt_conn_t *conn, uint8_t reason, void *param) { @@ -42,7 +42,7 @@ void bleDisconnectEventHandler(struct bt_conn *conn, p->handleDisconnectEvent(conn, reason); } -void bleParamUpdatedEventHandler(struct bt_conn *conn, +void bleParamUpdatedEventHandler(bt_conn_t *conn, uint16_t interval, uint16_t latency, uint16_t timeout, @@ -64,15 +64,15 @@ void BLERoleBase::setTxPower (int8_t tx_power) BleStatus BLERoleBase::_init() { - // Curie may support multi-role at same time in future. - // Make sure the BLE only init once. - if (this->m_init_cnt == 0) - { - ble_client_init(bleConnectEventHandler, this, - bleDisconnectEventHandler, this, - bleParamUpdatedEventHandler, this); - } - this->m_init_cnt++; + // Curie may support multi-role at same time in future. + // Make sure the BLE only init once. + if (this->m_init_cnt == 0) + { + ble_client_init (bleConnectEventHandler, this, + bleDisconnectEventHandler, this, + bleParamUpdatedEventHandler, this); + } + this->m_init_cnt++; return BLE_STATUS_SUCCESS; } diff --git a/libraries/CurieBLE/src/BLERoleBase.h b/libraries/CurieBLE/src/BLERoleBase.h index 4db80c48..b8fc5169 100644 --- a/libraries/CurieBLE/src/BLERoleBase.h +++ b/libraries/CurieBLE/src/BLERoleBase.h @@ -54,7 +54,7 @@ class BLERoleBase{ /** * Set TX output power * - * @param tx_power The antenna TX power + * @param[in] tx_power The antenna TX power * * @return boolean_t true if established connection, otherwise false */ @@ -62,13 +62,13 @@ class BLERoleBase{ protected: virtual BleStatus _init(void); - friend void bleConnectEventHandler(struct bt_conn *conn, + friend void bleConnectEventHandler(bt_conn_t *conn, uint8_t err, void *param); - friend void bleDisconnectEventHandler(struct bt_conn *conn, + friend void bleDisconnectEventHandler(bt_conn_t *conn, uint8_t reason, void *param); - friend void bleParamUpdatedEventHandler(struct bt_conn *conn, + friend void bleParamUpdatedEventHandler(bt_conn_t *conn, uint16_t interval, uint16_t latency, uint16_t timeout, @@ -77,48 +77,48 @@ class BLERoleBase{ /** * @brief Handle the connected event * - * @param conn The object that established the connection + * @param[in] conn The object that established the connection * - * @param err The code of the process + * @param[in] err The code of the process * * @return none * * @note virtual function. Just define the interface and the children need to implement */ - virtual void handleConnectEvent(struct bt_conn *conn, uint8_t err) = 0; + virtual void handleConnectEvent(bt_conn_t *conn, uint8_t err) = 0; /** * @brief Handle the disconnected event * - * @param conn The object that lost the connection + * @param[in] conn The object that lost the connection * - * @param reason The link lost reason + * @param[in] reason The link lost reason * * @return none * * @note virtual function. Just define the interface and the children need to implement */ - virtual void handleDisconnectEvent(struct bt_conn *conn, uint8_t reason) = 0; + virtual void handleDisconnectEvent(bt_conn_t *conn, uint8_t reason) = 0; /** * @brief Handle the conntion update request * - * @param conn The connection object that need to process the update request + * @param[in] conn The connection object that need to process the update request * - * @param interval The connection interval + * @param[in] interval The connection interval (N*1.25)ms * - * @param latency The connection latency + * @param[in] latency The connection latency * - * @param timeout The connection timeout + * @param[in] timeout The connection timeout (N*10)ms * * @return none * * @note virtual function. Just define the interface and the children need to implement */ - virtual void handleParamUpdated (struct bt_conn *conn, + virtual void handleParamUpdated (bt_conn_t *conn, uint16_t interval, - uint16_t latency, - uint16_t timeout) = 0; + uint16_t latency, + uint16_t timeout) = 0; char _device_name[BLE_MAX_DEVICE_NAME+1]; bt_addr_le_t _local_bda; diff --git a/libraries/CurieBLE/src/BLEService.cpp b/libraries/CurieBLE/src/BLEService.cpp index 2e25f161..906dc17d 100644 --- a/libraries/CurieBLE/src/BLEService.cpp +++ b/libraries/CurieBLE/src/BLEService.cpp @@ -20,10 +20,10 @@ #include "internal/ble_client.h" #include "BLEService.h" -struct bt_uuid_16 BLEService::_gatt_primary_uuid = {BT_UUID_TYPE_16, BT_UUID_GATT_PRIMARY_VAL}; -struct bt_uuid *BLEService::getPrimayUuid(void) +bt_uuid_16_t BLEService::_gatt_primary_uuid = {BT_UUID_TYPE_16, BT_UUID_GATT_PRIMARY_VAL}; +bt_uuid_t *BLEService::getPrimayUuid(void) { - return (struct bt_uuid *)&_gatt_primary_uuid; + return (bt_uuid_t *)&_gatt_primary_uuid; } BLEService::BLEService(const char* uuid) : @@ -32,7 +32,7 @@ BLEService::BLEService(const char* uuid) : } -void BLEService::discover(struct bt_gatt_discover_params *params) +void BLEService::discover(bt_gatt_discover_params_t *params) { params->type = BT_GATT_DISCOVER_PRIMARY; @@ -41,8 +41,8 @@ void BLEService::discover(struct bt_gatt_discover_params *params) _discoverying = true; } -void BLEService::discover(const struct bt_gatt_attr *attr, - struct bt_gatt_discover_params *params) +void BLEService::discover(const bt_gatt_attr_t *attr, + bt_gatt_discover_params_t *params) { params->start_handle = attr->handle + 1; diff --git a/libraries/CurieBLE/src/BLEService.h b/libraries/CurieBLE/src/BLEService.h index 2d4c4999..24f468ef 100644 --- a/libraries/CurieBLE/src/BLEService.h +++ b/libraries/CurieBLE/src/BLEService.h @@ -35,18 +35,18 @@ class BLEService : public BLEAttribute { /** * Constructor for BLE Service * - * @param uuid 16-bit or 128-bit UUID (in string form) defined by BLE standard + * @param[in] uuid 16-bit or 128-bit UUID (in string form) defined by BLE standard */ BLEService(const char* uuid); protected: friend BLEPeripheral; friend BLEProfile; - void discover(const struct bt_gatt_attr *attr, - struct bt_gatt_discover_params *params); - void discover(struct bt_gatt_discover_params *params); + void discover(const bt_gatt_attr_t *attr, + bt_gatt_discover_params_t *params); + void discover(bt_gatt_discover_params_t *params); - static struct bt_uuid *getPrimayUuid(void); + static bt_uuid_t *getPrimayUuid(void); private: static bt_uuid_16 _gatt_primary_uuid; }; diff --git a/libraries/CurieBLE/src/BLETypedCharacteristic.h b/libraries/CurieBLE/src/BLETypedCharacteristic.h index 77828aae..65842d0f 100644 --- a/libraries/CurieBLE/src/BLETypedCharacteristic.h +++ b/libraries/CurieBLE/src/BLETypedCharacteristic.h @@ -27,18 +27,110 @@ 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); + + /** + * @brief Set the peer peripheral device's characteristic value + * + * @param[in] Peripheral The peer peripheral device that want to set the characteristic value + * + * @param[in] value New value to set + * + * @return bool true - set value success, + * false - on error + * + * @note none + */ + bool write(BLEPeripheralHelper &Peripheral, T value); private: + /** + * @brief Swap the bytes + * + * @param value The typed value + * + * @return T The swapped value + * + * @note none + */ T byteSwap(T value); }; @@ -79,6 +171,10 @@ template T BLETypedCharacteristic::valueBE() { return byteSwap(value()); } +template bool BLETypedCharacteristic::write(BLEPeripheralHelper &Peripheral, T value){ + return BLECharacteristic::write(Peripheral, (unsigned char *)(&value), sizeof(T)); +} + template T BLETypedCharacteristic::byteSwap(T value) { T result; unsigned char* src = (unsigned char*)&value; diff --git a/libraries/CurieBLE/src/BLETypedCharacteristics.h b/libraries/CurieBLE/src/BLETypedCharacteristics.h index fb76cfce..1ecd2a6c 100644 --- a/libraries/CurieBLE/src/BLETypedCharacteristics.h +++ b/libraries/CurieBLE/src/BLETypedCharacteristics.h @@ -24,56 +24,199 @@ 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); }; diff --git a/libraries/CurieBLE/src/internal/ble_client.c b/libraries/CurieBLE/src/internal/ble_client.c index 5731a75f..78afa966 100644 --- a/libraries/CurieBLE/src/internal/ble_client.c +++ b/libraries/CurieBLE/src/internal/ble_client.c @@ -27,7 +27,7 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ - + #include #include @@ -77,7 +77,7 @@ static void *ble_client_update_param_event_param; extern "C" { #endif -static void on_connected(struct bt_conn *conn, uint8_t err) +static void on_connected(bt_conn_t *conn, uint8_t err) { if (ble_client_connect_event_cb) { @@ -85,7 +85,7 @@ static void on_connected(struct bt_conn *conn, uint8_t err) } } -static void on_disconnected(struct bt_conn *conn, uint8_t reason) +static void on_disconnected(bt_conn_t *conn, uint8_t reason) { if (ble_client_disconnect_event_cb) { @@ -93,8 +93,8 @@ static void on_disconnected(struct bt_conn *conn, uint8_t reason) } } -static void on_le_param_updated(struct bt_conn *conn, uint16_t interval, - uint16_t latency, uint16_t timeout) +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) { @@ -107,9 +107,9 @@ static void on_le_param_updated(struct bt_conn *conn, uint16_t interval, } static struct bt_conn_cb conn_callbacks = { - .connected = on_connected, - .disconnected = on_disconnected, - .le_param_updated = on_le_param_updated + .connected = on_connected, + .disconnected = on_disconnected, + .le_param_updated = on_le_param_updated }; @@ -167,12 +167,12 @@ void ble_client_get_factory_config(bt_addr_le_t *bda, char *name) *suffix++ = '-'; 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 + *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 + /* 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 */ } @@ -200,33 +200,35 @@ void ble_client_init(ble_client_connect_event_cb_t connect_cb, void* connect_par BleStatus errorno_to_ble_status(int err) { - BleStatus 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 - default: - err_code = BLE_STATUS_ERROR; - break; - } - return err_code; + BleStatus 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; } diff --git a/libraries/CurieBLE/src/internal/ble_client.h b/libraries/CurieBLE/src/internal/ble_client.h index b7495867..30a33a98 100644 --- a/libraries/CurieBLE/src/internal/ble_client.h +++ b/libraries/CurieBLE/src/internal/ble_client.h @@ -40,12 +40,13 @@ enum { }; #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 0x095f +#define MAX_CONN_INTERVAL 0x0C80 #define SLAVE_LATENCY 0 #define CONN_SUP_TIMEOUT MSEC_TO_UNITS(6000, UNIT_10_MS) @@ -108,6 +109,7 @@ void ble_client_get_factory_config(bt_addr_le_t *bda, char *name); void ble_gap_set_tx_power(int8_t tx_power); BleStatus errorno_to_ble_status(int err); + #ifdef __cplusplus } #endif diff --git a/system/libarc32_arduino101/drivers/bluetooth/conn_internal.h b/system/libarc32_arduino101/drivers/bluetooth/conn_internal.h deleted file mode 100644 index 04c08839..00000000 --- a/system/libarc32_arduino101/drivers/bluetooth/conn_internal.h +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (c) 2016 Intel Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -typedef enum { - BT_CONN_DISCONNECTED, - BT_CONN_CONNECT_SCAN, - BT_CONN_CONNECT, - BT_CONN_CONNECTED, - BT_CONN_DISCONNECT, -} bt_conn_state_t; - -/* bt_conn flags: the flags defined here represent connection parameters */ -enum { - BT_CONN_AUTO_CONNECT, -}; - -struct bt_conn_le { - bt_addr_le_t dst; - -#if 0 - bt_addr_le_t init_addr; - bt_addr_le_t resp_addr; -#endif - uint16_t interval; - uint16_t interval_min; - uint16_t interval_max; - - uint16_t latency; - uint16_t timeout; -#if 0 - uint8_t features[8]; -#endif -}; - -#if defined(CONFIG_BLUETOOTH_BREDR) -struct bt_conn_br { - bt_addr_t dst; -}; -#endif - -struct bt_conn { - uint16_t handle; - uint8_t type; - uint8_t role; - -#if defined(CONFIG_BLUETOOTH_SMP) - uint8_t encrypt; - bt_security_t sec_level; - bt_security_t required_sec_level; -#endif /* CONFIG_BLUETOOTH_SMP */ - - atomic_t ref; - - /* Connection error or reason for disconnect */ - uint8_t err; - - bt_conn_state_t state; - union { - struct bt_conn_le le; -#if defined(CONFIG_BLUETOOTH_BREDR) - struct bt_conn_br br; -#endif - }; -}; - -/* Add a new LE connection */ -struct bt_conn *bt_conn_add_le(const bt_addr_le_t *peer); - -#if defined(CONFIG_BLUETOOTH_BREDR) -/* Add a new BR/EDR connection */ -struct bt_conn *bt_conn_add_br(const bt_addr_t *peer); - -/* Look up an existing connection by BT address */ -struct bt_conn *bt_conn_lookup_addr_br(const bt_addr_t *peer); -#endif - -/* Look up an existing connection */ -struct bt_conn *bt_conn_lookup_handle(uint16_t handle); - -/* Look up a connection state. For BT_ADDR_LE_ANY, returns the first connection - * with the specific state - */ -struct bt_conn *bt_conn_lookup_state_le(const bt_addr_le_t *peer, - const bt_conn_state_t state); - -/* Set connection object in certain state and perform action related to state */ -void bt_conn_set_state(struct bt_conn *conn, bt_conn_state_t state); - -void bt_conn_set_param_le(struct bt_conn *conn, - const struct bt_le_conn_param *param); - -int bt_conn_update_param_le(struct bt_conn *conn, - const struct bt_le_conn_param *param); - -int bt_conn_le_conn_update(struct bt_conn *conn, - const struct bt_le_conn_param *param); - -void notify_le_param_updated(struct bt_conn *conn); - -#if defined(CONFIG_BLUETOOTH_SMP) -/* rand and ediv should be in BT order */ -int bt_conn_le_start_encryption(struct bt_conn *conn, uint64_t rand, - uint16_t ediv, const uint8_t *ltk, size_t len); - -/* Notify higher layers that RPA was resolved */ -void bt_conn_identity_resolved(struct bt_conn *conn); - -/* Notify higher layers that connection security changed */ -void bt_conn_security_changed(struct bt_conn *conn); -#endif /* CONFIG_BLUETOOTH_SMP */ -/* Initialize connection management */ -int bt_conn_init(void); From 727e42ad1e1ea823dad68d27a41f7d9b08d34431 Mon Sep 17 00:00:00 2001 From: lianggao Date: Fri, 9 Sep 2016 08:28:50 +0800 Subject: [PATCH 03/14] Create BLE Example sketches 1. Fix Jira 664 demonstrates changing the ADV data 2. Fix Jira 671 Support update connection interval in central/peripheral --- .../BatteryAdvChange/BatteryAdvChange.ino | 113 ++++++++++ .../BatteryMonitor/BatteryMonitor.ino | 33 +++ .../UpdateConnectionInterval.ino | 205 ++++++++++++++++++ 3 files changed, 351 insertions(+) create mode 100644 libraries/CurieBLE/examples/BatteryAdvChange/BatteryAdvChange.ino create mode 100644 libraries/CurieBLE/examples/UpdateConnectionInterval/UpdateConnectionInterval.ino diff --git a/libraries/CurieBLE/examples/BatteryAdvChange/BatteryAdvChange.ino b/libraries/CurieBLE/examples/BatteryAdvChange/BatteryAdvChange.ino new file mode 100644 index 00000000..44a8055a --- /dev/null +++ b/libraries/CurieBLE/examples/BatteryAdvChange/BatteryAdvChange.ino @@ -0,0 +1,113 @@ +/* Please see code cpyright at the bottom of this example code */ +/* + This sketch illustrates how to change the advertising data so that it is visible but not + connectable. Then after 10 seconds it changes to being connectable + This sketch example partially implements the standard Bluetooth Low-Energy Battery service. + + This sketch is not paired with a specific central example sketch, + but to see how it works you need to use a BLE APP on your phone or central device + and try connecting when it is either a connectable or not connectable state + as displayed in the serial monitor. +*/ + +#include + +BLEPeripheral blePeripheral; // BLE Peripheral Device (the board you're programming) +BLEService batteryService("180F"); // BLE Battery Service +int count = 0; +// BLE Battery Level Characteristic" +BLEUnsignedCharCharacteristic batteryLevelChar("2A19", // standard 16-bit characteristic UUID + BLERead | BLENotify); // remote clients will be able to +// get notifications if this characteristic changes + +void setup() { + Serial.begin(9600); // initialize serial communication + pinMode(13, OUTPUT); // initialize the LED on pin 13 to indicate when a central is connected + while (!Serial) { + //wait for Serial to connect + } + /* Set a local name for the BLE device + This name will appear in advertising packets + and can be used by remote devices to identify this BLE device + The name can be changed but maybe be truncated based on space left in advertisement packet */ + blePeripheral.setLocalName("BatteryAdvChangeSketch"); + blePeripheral.setAdvertisedServiceUuid(batteryService.uuid()); // add the service UUID + blePeripheral.addAttribute(batteryService); // Add the BLE Battery service + blePeripheral.addAttribute(batteryLevelChar); // add the battery level characteristic + + /* Now activate the BLE device. It will start continuously transmitting BLE + advertising packets and will be visible to remote BLE central devices + until it receives a new connection */ + + blePeripheral.begin(); + Serial.println("Bluetooth device active, waiting for connections..."); + Serial.println("Starts in Connectable mode"); +} + +void loop() { + // listen for BLE peripherals to connect: + BLECentralHelper central = blePeripheral.central(); + // wait + Serial.print(". "); + if (count == 10) { + Serial.print("\nReached count "); + Serial.println(count); + + } + delay (1000); + count++; + // Switch from Connectable to Non Connectable and vice versa + if (count > 10 ) { + static bool change_discover = false; + Serial.println("Stop Adv and pausing for 10 seconds. Device should be invisible"); + // Some central devices (phones included) may cache previous scan inofrmation + // restart your central and it should not see this peripheral once stopAdvertising() is called + blePeripheral.stopAdvertising(); + delay(10000); + + if (change_discover) + { + + // Using the function setConnectable we specify that it now NOT connectable + // The loop is for 10 seconds. Your central device may timeout later than that + // and may eventually connect when we set it back to connectable mode below + blePeripheral.setConnectable(false); + Serial.println("In Non Connectable mode"); + + } + else + { + + //using the function setConnectable we specify that it now connectable + blePeripheral.setConnectable(true); + Serial.println("In Connectable mode"); + } + Serial.println("Start Adv"); + blePeripheral.startAdvertising(); + if (change_discover) { + Serial.println("Adding 5 second delay in Non Connect Mode"); + delay(5000); + } + change_discover = !change_discover; + count = 0; + } +} + +/* + 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 +*/ + diff --git a/libraries/CurieBLE/examples/BatteryMonitor/BatteryMonitor.ino b/libraries/CurieBLE/examples/BatteryMonitor/BatteryMonitor.ino index 0facfe09..96969888 100644 --- a/libraries/CurieBLE/examples/BatteryMonitor/BatteryMonitor.ino +++ b/libraries/CurieBLE/examples/BatteryMonitor/BatteryMonitor.ino @@ -66,6 +66,14 @@ void loop() { if (currentMillis - previousMillis >= 200) { previousMillis = currentMillis; updateBatteryLevel(); + + static unsigned short count = 0; + count++; + // update the connection interval + if(count%5 == 0){ + delay(1000); + updateIntervalParams(central); + } } } // when the central disconnects, turn off the LED: @@ -90,6 +98,31 @@ void updateBatteryLevel() { } } +void updateIntervalParams(BLECentralHelper ¢ral) { + // read and update the connection interval that peer central device + static unsigned short interval = 0x60; + ble_conn_param_t m_conn_param; + // Get connection interval that peer central device wanted + central.getConnParams(m_conn_param); + Serial.print("min interval = " ); + Serial.println(m_conn_param.interval_min ); + Serial.print("max interval = " ); + Serial.println(m_conn_param.interval_max ); + Serial.print("latency = " ); + Serial.println(m_conn_param.latency ); + Serial.print("timeout = " ); + Serial.println(m_conn_param.timeout ); + + //Update connection interval + Serial.println("set Connection Interval"); + central.setConnectionInterval(interval,interval); + + interval++; + if(interval<0x06) + interval = 0x06; + if(interval>0x100) + interval = 0x06; +} /* Copyright (c) 2016 Intel Corporation. All rights reserved. diff --git a/libraries/CurieBLE/examples/UpdateConnectionInterval/UpdateConnectionInterval.ino b/libraries/CurieBLE/examples/UpdateConnectionInterval/UpdateConnectionInterval.ino new file mode 100644 index 00000000..a25c2195 --- /dev/null +++ b/libraries/CurieBLE/examples/UpdateConnectionInterval/UpdateConnectionInterval.ino @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2016 Intel Corporation. All rights reserved. + * See the bottom of this file for the license terms. + */ + +#include + +/* + This example can work with BatteryMonitor + to show how to control and response the connection intelval request. +*/ + +// set up connection params + +ble_conn_param_t conn_param = {30.0, // minimum interval in ms 7.5 - 4000 + 50.0, // maximum interval in ms 7.5 - + 0, // latency + 4000 // timeout in ms 100 - 32000ms + }; + +const int ledPin = 13; // set ledPin to use on-board LED +BLECentral bleCentral; // create central instance +BLEPeripheralHelper *blePeripheral1 = NULL; // // peer peripheral device + +BLEService batteryService("180F"); // create service with a 16-bit UUID +BLECharCharacteristic batteryLevelChar("2A19", BLERead | BLENotify);// create switch characteristic +//and allow remote device to read and notify + +bool adv_found(uint8_t type, + const uint8_t *dataPtr, + uint8_t data_len, + const bt_addr_le_t *addrPtr); + +void setup() +{ + Serial.begin(9600); + + // add service and characteristic + bleCentral.addAttribute(batteryService); + bleCentral.addAttribute(batteryLevelChar); + + // assign event handlers for connected, disconnected to central + bleCentral.setEventHandler(BLEConnected, bleCentralConnectHandler); + bleCentral.setEventHandler(BLEDisconnected, bleCentralDisconnectHandler); + bleCentral.setEventHandler(BLEUpdateParam, bleCentralUpdateParam); + + // advertise the service + bleCentral.setAdvertiseHandler(adv_found); + + // assign event handlers for characteristic + batteryLevelChar.setEventHandler(BLEWritten, switchCharacteristicWritten); + + bleCentral.begin(); + Serial.println(("Bluetooth device active, waiting for connections...")); +} + +void loop() +{ + static unsigned int counter = 0; + static char ledstate = 0; + delay(2000); + if (blePeripheral1) + { + counter++; + + if (counter % 3) + { + batteryLevelChar.read(*blePeripheral1); + } + else + { + ledstate = !ledstate; + batteryLevelChar.write(*blePeripheral1, ledstate); + } + } + +} + +void bleCentralConnectHandler(BLEHelper& peripheral) +{ + // peripheral connected event handler + blePeripheral1 = bleCentral.getPeerPeripheralBLE(peripheral); + Serial.print("Connected event, peripheral: "); + Serial.println(peripheral.address()); + // Start discovery the profiles in peripheral device + blePeripheral1->discover(); +} + +void bleCentralDisconnectHandler(BLEHelper& peripheral) +{ + // peripheral disconnected event handler + blePeripheral1 = NULL; + Serial.print("Disconnected event, peripheral: "); + Serial.println(peripheral.address()); + bleCentral.startScan(); +} + +void bleCentralUpdateParam(BLEHelper& peripheral) +{ + // peripheral update the connection interval event handler + Serial.print("UpdateParam event, peripheral: "); + blePeripheral1 = bleCentral.getPeerPeripheralBLE(peripheral);; + Serial.println(peripheral.address()); + + // Get connection interval that peer peripheral device wanted + ble_conn_param_t m_conn_param; + blePeripheral1->getConnParams(m_conn_param); + Serial.print("min interval = " ); + Serial.println(m_conn_param.interval_min ); + Serial.print("max interval = " ); + Serial.println(m_conn_param.interval_max ); + Serial.print("latency = " ); + Serial.println(m_conn_param.latency ); + Serial.print("timeout = " ); + Serial.println(m_conn_param.timeout ); + + //Update the connection interval + blePeripheral1->setConnectionInterval(m_conn_param.interval_min,m_conn_param.interval_max); +} + +void switchCharacteristicWritten(BLEHelper& peripheral, BLECharacteristic& characteristic) +{ + // Read response/Notification wrote new value to characteristic, update LED + Serial.print("Characteristic event, notify: "); + + int battery = batteryLevelChar.value(); + if (battery) + { + Serial.print("Battery Level % is now: "); // print it + Serial.println(battery); + delay(100); + + Serial.println("LED on"); + digitalWrite(ledPin, HIGH); + } + else + { + Serial.println("LED off"); + digitalWrite(ledPin, LOW); + } +} + +bool adv_found(uint8_t type, + const uint8_t *dataPtr, + uint8_t data_len, + const bt_addr_le_t *addrPtr) +{ + int i; + + Serial.print("[AD]:"); + Serial.print(type); + Serial.print(" data_len "); + Serial.println(data_len); + + switch (type) + { + case BT_DATA_UUID16_SOME: + case BT_DATA_UUID16_ALL: + { + if (data_len % UUID_SIZE_16 != 0) + { + Serial.println("AD malformed"); + return true; + } + for (i = 0; i < data_len; i += UUID_SIZE_16) + { + if (batteryService.uuidCompare(dataPtr + i, UUID_SIZE_16) == false) + { + continue; + } + + // Accept the advertisement + if (!bleCentral.stopScan()) + { + Serial.println("Stop LE scan failed"); + continue; + } + Serial.println("Connecting"); + // Connect to peripheral + bleCentral.connect(addrPtr, &conn_param); + return false; + } + } + } + + return true; +} + +/* + 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 +*/ From 1beffde0deecb4d2715260d795173bc38febb040 Mon Sep 17 00:00:00 2001 From: Calvin Sangbin Park Date: Fri, 9 Sep 2016 10:55:17 -0700 Subject: [PATCH 04/14] Rebuilt libarc32drv --- variants/arduino_101/libarc32drv_arduino101.a | Bin 496120 -> 738704 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 8b179a8385f95cc3be74936955f88b4eb12a9572..559d5ea84843b8b0876c5d1130f364d6468e415e 100644 GIT binary patch literal 738704 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 z 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 z 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
@~)kq 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

X7pZ$pK9;&q&UU3>8n_wl-OT3CVMSl3`A`^7g@b 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 literal 496120 zcmeFa3t(MUl|H`Dy|-!Fq;1lcK4@ug(ll*po^2_mv?b|-wvfJCXc4(hle7)Z<2E-b zDN0+gDo7Dj6h}HdbabjvMMVW<#v;fl;$xI?&~cm!K2TA?Cprqh?^}EAbN0PS8khn7 z&wpko=bmq`z4qE`zaM9xeM%NJb~Wu@GQ2vOZEEM8UAy3%d1uX;pG?+>lmDAcp0(ht zIo8Ma;l>2G#y@Aw?3#hEl}C)(ba!+$=Nq&4yD$Ix(g|Z;!z-w%F)#aiU%~|cmFrh0 zOz;Y>SZb{4bGVyn(%u4cTGI^`p{t$e+{n0viF)-d_C|RlW^B} zx0-~z{@800{aiz;kD6C>9Xer#yhc~vs3RtC>XZFn+n+Fb`=5RJ*GJczJpX#A(B!=e z*U-`5HF6~z%+SHE)hEo*|LXPTd(BXLrP>=?J4|awcS~1SPr5tR)Y;L|($sBIt214j z8+Wv|cy7(D8Rr~%XzA$7l|xB)wRU#3cJEJRTDm=PJsqvxspgh_txYYdR$#NQr7P3g z*^%1a(%9V6Wm0Gf_svOlx3;%*)qtZrwY#ySxeYE#>V$`#ZJk$S9HF_Z6(lPRVWpbd zI-B;Sb~Sdln4ZS2?o?~KiL^{hstKYxTH1`>Gm$$YDe8wFM77A4>56zI43!je>|(&V z#B@()cWP%>W4o`3WlD9XTRME19nGosmQ1E`m&coF0X4I~qbb$Zl1VE!(@cU*ceb@5 z)}E!aN1b-I?(9tMY;9|^j$-XX`l^Voy{Ycrl;)w0f+b}qgQd~m{>WojUymU5@d#Ru zM-;U_L6X+Ts}NhdQ65`V;kMR}mQ<#@vAajJYzJiTL3+0|?Xj9!0@KUd`v`5#q>$jv ztsT3fsPv?pqe6m$=8Czb2ok8NvpwAgMXZmu&dzjJ1ccxM+LdZ@);jExR%% z)7d1{<`zia*}5wv-HEp080naGZqqZoI0HFjinq8>Q0nQkc5huYYN z;-6}3&7k0Sx3q`cJG)w1gd9?&i0?|bcBWcUyahC(9@4@fgpj4WP_)#998w#i=m`&L z=}zeEMN}Mwm?g8ZLj%$Z7|Oi@ZHG+)@hMgj8Yw3wI`lwW4nuomW)GVX+sZ{AdpdGl zTIXb#WoQ=DhFJ2El4BiPBH2o~`p3Lr4S0>X6BAca%F0E~* zsj;JmLX#|ZF&-h&zH^4 zz5#W$v|}9Bj|O(gvJdgYIA<3+aGNhwjup4DySuA(M-K|qzQ#6Gh{%=kAJ|pIc64^P z?)1C}u@zl@Bt;5{WJ4q@|MH; znYJaDVJe+$NAKDJG)Y;aLXR!`S~|KjB0kH9X=-bQi;k{cdm?<&OEb5u@-|k`4TCUW z{V)h$VGQ9zm9=a}&Y;M`v*wbdy*jGlH*c1i(ji*Rq zcChV4vubmoeOH(CA1P$3lpxMXtds2;n|2tlHfeup`)rQ3vUnXi)2}GQ@*oKwW*q7v z{TP=jurtm=i@3!aMn@zViJtRjr5g13oDpck>fL58g*k^X4Q-l*xh5h#M%%9nm4jJH zKi}C;I^nYE?xj|amQDc;moqP&zM%g@Du|E)iirC4QqJsZ>)e5ofF2F4(tg`3QKqOt zkP|*t&qcdr3$l11t#44sy6c^!Bf8CwCcA?#%Po5Ar8I@%0M@TR4ghSU6Q4)@o;QT1r`aZ!N{@A z5)haLE$!*<{mR4^VrOd?YFpUw3KFGJoiIH#JNy`6(ZI&#ibf1&Eo3xYTW={@mB*5r zpd03Bdyy%fZR!!aamL(^E*=wT%U~^8E9o?QSFlk@L~& z+=cP%W%53@+Ib6$c$qW~B3v89QFCLuwJ%u)S$!RP zx?9^a^U&NH8u0+%}XZd zEtszn+CKc?iiY!pATZ7Z=3x`rjJau;F+<|U%oB#0W`a4*JbxmVpEMtM=ueZs_~~1h zRiq!TqvOL%`Ae7Bl}f44rJHdh)1)0wpH_ZjhG_hVLt{>#erW1TFa7DI7q**2+j~E_ z_jk|7@EpNiCwmLQ_YzFp1aF*LIN9KVL})sI5;6RYOxzwM@(7Ai3Rj*VzD7|OW0e!y#>&RZbPk`z2(a-o*2S@A(rsn5{V&+ec;WD zS)bPJiR~Z>49;LOh|u87;8VcET1`-KApoAbKs$k^K=2&?GM>TF8mL`(Gwm=laRukR zfZq|$I`R%8m{A|bPjHzrMYLTDZw0uDgAGBA8BNmP!z$n_4m{z&zX6yr*Wu?}ps~ns zm|!Zy9&-iY29H{LJ}j1DS_I1@I3t2H3CB@bd?KSm6~+^c-^QN<_)%w}J{md;66Ya_ z$3dMik*tYwxeZx-I&Hz}6k>wnVEV{01@Vzv#uo=g>f#^2ySM~*OP zlz>@JgX(>aUDcPjbnI#E$W&j^*|jH=#;Qbh6MD0@)*YG3#_l#Oa5nGqNnOo7tsR}U zHMP~5{h0E!SA!GJ0_M)a6M@iaM`vd@XJw{(cV~MGIk9H9r=_b}IO(N&S5p&hl`U;M z=gz5~SGAyOZne&d2k;efLtdrh#p-W6=Iut?>Lx}k)7^|IN!4zq6Q+AuQUFFRGvVOLAFKvLspAUcYhKzBwz_0JXJtcek}va(%wB1NM&2$_$sF05PUp95U`oR<6q= zE0GI}o6b2WS-BI8T`gFLYw4=o*@M)2lW*klh?_XQgv0wF9GLTM8r>`eub?p-&blu1H`&kNlD! z<`n|R`h`iQmTg|QG1aho)8ySwatw?h~=cAF|$fUH8C zO%)Y^|__=9V2joal3Uz3&ncthr|a^ zP)fq&GEWs72dth9)HL=-$B;(GFfwQXw&SyilWiwW2`*wZD`E!Xn*mpjM+W1o1rO!< z;mVK>G#Wd`GN!`Aq(y<#Yu!e}`*1fg1ABEXL(9?adh2plcL) z)P-)8w;7i&?-|&C>=7JX2f8osjga>&9H}4E!O7bW?EC7E0W{aa(<&##bNe8$FYg7&V?Hsy#p*`;r*YAH4KA90 z#=!et>Ex`PZoD4SyW@K^GHZ!tkzdD0*{&?p;bHJSCLH#Md^Eg!!IeR{$?|B|Egj`@slh27VL?tX~0`zcAgtA$of+|DRMnQ=Rr z@a!_7@a!^i@a&9nWa5%Tz8%9z&%`k~km(9&EZ3u+-Xnc*cq-fbnkH%+-YY$a)$QXoCSBXrNLL{Bl~>8J0M-rtttVnzrCRm@Cc1%P2&Fo^^h4#|r(3(J5f zjV0BXGjUClNt~^JlUO-n*jE&@BEYbYO`;y8wJ1Fx5(Eu|0#Bj4;%n{O?AUnXULL;5mxl3Ve>@ zOMvSX*W$iX@q@Utd{NFvaNnZ12K-Hm#{)AiLfpUS;hzz+;eq)BF7lV+UI@9wN`U*>&GHu zgxd+7H!3aze!b$Az`GUy9`}r5X6pgP`QW)u@y~ESrg%5#UscR_{EOl{LI1AeU*S$e zotN^ixB+)&E$PH;eTXl`okhptsfxGbUa5Eo?#xT_Fpe??khK+CuSs{tVHYm>xtihV zVTIErw87TT&Yn|aH_j#pUqAc7$n5pAsZtA_X8-@W+qJ;fWSt3~v$%T3=gev4Ce1h| z@2D>SoH=^icTpcbbtGnvfWkpXOOV%Z9XxHwILSv{l^u*ph<%b}DYF=mg!Ni_& z(@n0Ez4E7VVzcD0QL?RdhHiF6GI495E0VtZltJ9ddn*4k2XRjh;yzgTYqR{brj3|1 z9YYh=C;Uq7)+#O(`FVBLQzeK*F!2y%4&mB;FzIR$pEoo?n)DvAS=#~QAHdJBJuUl9 zY&Pw7XcC(f|15BU(B}x9*t=tYv zc91KN4;thJSwXIW2pI(gM>*WKX}r#*v5a_v^|TgQyn%RynYfE2csQLu^W6HsjCg`6 zl*FedFfVsSdJI;Z3ktZr%;n!9Bf_=j1?VE(`m|fG_SU9XkLho9I(O~5U`QwrrM^nn zu4hcNi`BO*orpT^uN{YUDl;_jX+d}|_z_f};&R|QLY zVtBtN~-1k+CLTv^S287t;5W>n;BN?do(LL zy1Vf5gnM8EHnX#>5$^^>OY>QdDQq5W-*5NeyH)&1w%fiOd8R;?KRn)O>4g^jwMB?7 z{k_hk+oHqOet)4q3dyB0c0^$4{Fhtox9j{-B6zF+k#a7#`*rl{ntf^z`O~Q9l3l9i z(zDVa*RjvDE&r*)|Cq-T9Q>g32v;R#Wa@QGbfYQ3MQjI1cAe=7I;e5|;0)L~z;}H4 zPJr?`YX$7YoSD#&&-I|RJ9$fieZH}O%nOh= z%Vjo*&g~DtPBwX8fV}U5m-1xI2zTbkaCmTjU^OL(_&$%|;2Al5w;&Vlq6amOZvucH z?h_a#GrkO$OR6p&l{xa3qR{&3=j6@Ik@v{y#%x!4V?BBGIr82Kd3A7i#>ta)Jx9d7 z3>DC&GmfIV>mYKJ?BZJlocpZi2o{lVQ(3$@ zc^*liPk%L@)v&D7ux!S%QkL=c7AWB!w;RLAE+#Iqa5lOlkv$W)o5Mm%TtW%0;CG>0 zT3l+1?=Dm5kPw#=9a`a^B{DK`NnzjaPqU~W3bSY8SiyB1%%9PS@0se(w!RoCGEePn zGe;x-tJOYJ?86M(O`0UG(TIN=EihJILHKNkv!Bt7f7m%=4CRe|LUQ;LT=yPPvO4yN+^0*&JQ{jV!W8)g77Cf04ztWo(&E5W=UP3D)H$UytsH#;F*^>J z&A1$WD>3ReO*bw_?BVjk`iO> z^AMIQ;`gC4q1u^*K6_#tl0bc>DZ@pc&!aNWR(vzCJi7t?e&BPIz7M!gaV_pE6>q?u z`A#|K;l4%jIpA+nTn@ZjF`wgHtC;=P-zjD^A5_eRtGyC?-$0hoz1yKjZJx=O)ES79;f? z=|+51>1%OkUM46IhHkF>PauZQG)Y`e=b6M>#%hSQoGc*LysjhGJOT`5N_wJx(Fy8? z@@Z#SOi$5)jxfx7hRbvieKcI!sRMOM5~~hnibJtf2lQIfnSb}pY~vn-m}VnW4r%K`27fml895X-ZaIM3o`#6vCKQUSmD7GFv^ zlPylo22axB9i*38-12(RXIi|Q^a?YS;+UiP{K2*ytLIP-xOoYAjEk(4hIlDQ*>=r; z_+faD7XBxV%-x9YZ!=FsyV8ebKLg0aGXULU&SwBa-LiMSTlVILbY-y1-owjfr?*rZ zv}rI2`E@_P28fxT^S93)>;KXYXjz)zj%>Fl8h6lM`(4Dcc^XBi68p35E@@ZMWH+_5 zG$HehNjG*k@qILGUuCIlZ@e6DC5H=TvUJ+Hb0^oPgePx!=F;~fpOm>KwGHznN@?i>U7#_@5?Kx~^*%g3>UGmftw1c&L? zjO!AQ;NT+A{RX)KW0oUujpR=x|f=p_s9c+K-&tt7Ci}&q`V$O|~O6rai~7+yf}{b>J;X2rPAm z%9UN{@qQh2rmkXQN_i5X_}`(J^TWSY{88|HSTW1SrxdgPJ){^nBlCOcTn)e9R{GE3_lJt7 z;r_g0&d2_&_;cVN20o_4Q@G2zF7O|LWlj%V3?9DHMjqdpouvss6;SJuw z<_Gg?=e+E9cNt?^YoL8_N%FFij*Rxf&4ksNI)DVy%D&4h zjQO|XX*no-h1bkw8sd&$pzOAac8qG7v@ho!UTGd;^DZK{9o!E*GYOW*-v&Mr9W1|) zAxK5y>$ONcT)|PPyg{0TIwAZHzr$F7X_B~tp2zSz`~ZFq+O=D5MRa$8i%oz%bWgA5m*v^@FvJ})})|p}=GdjVtIEIsE6E779gnX)ZVh5Uv zlFDx+V@?H?fb5y-DasV_T}EO*_=qBGF&WNu2c~v`2$lYzo-5=9cdji zKLAtT>C|iW%Bp>BE;GQpr-Qw*>PDJ5FO|BtQ8Cih2^EqB7JJg2eJK^CPtq#>JrgWh zb75t5EHFX1pXNCeG{iUWY-m^$pMAl(vYDl+vj;=%aJVjG?$>Mxt;xbyb)$D>oBEm0 z^)r+EUpajXb2oe@5>fP_$G~T&hM5XK15M5PoUiIrm!uzM`6g;rpY-u(Sg&}?M>yva?Oww-PuUN%r;*+e_kre4 zCCVoq?J4+P?p&-^<@wV}&bCxasihU*OZE_D{%9iNPGJ^D<9TLU}8<^0Bl zdlcB0_Z;kJdjtn>Q9rne*$R1=!jbX{a5;I$fSn&Q&OoC-(IYtc*&Mz)MBE?CIlfPW z&&7%I?!|z-40ndh`KJr_YrxJApxF(1{mo@Li1Ot<33)DEI3{xPo&xse9fCZ*ALWdL z{|_XYL^bt+UdsY4|;ce1sE#6oH_4m@S#Tyjf1>< z!I*hahI0w(=;F(J?wt29%KhF@T6S7^xE%|J zl(-!Qdv@knWcu!5I4$&%1RLL1JyY3P28vOG&sBRJ?xRt|*Q%X2rt@|JHMD1+&Vg+Q z1NI4h8vK5GhNusAyAmUc_R0v7;kx%bhZBn;wm!M{I~S5~EPh~kbI-3>Zrn4jc^-WM zv1XUFYZMH_vd=c}!?+hKraW0Ipd-u~xZECrGGg$rMWLOocnipn+IFQsgF4WmmU2pkmz2^@<+?&pU{RkgCr=A(QWh zz8`iMcUdcdGK}Mwl}`L!Vom2CE4>c)-xEX5OOPMOm0%*l$n$^DZv*X2(t+>6UD8&I zJ;gbFBwy&L#?e_WiIcdqe{p(BzJR|DwC73Jc;zuHmDAL=2L~7O^c(y5aj><8x$_ph z?DrbKjxhYSh4DzSxjIOhKXsOM0fG^e zqIUV!nd`@2-dsOFR(lJ=mjsN>xe!qCT;haI{7Xrv*1^&5;FtMgf>J^nwTfq0hjq87 zO;F~nW_UuD1^!F3@>4$`^e4FCtelf=Jf?M%XTGB29b`r8a5qsF?fK)qoznkXGlA?? z5PmXj2;XaQA_B81g6kb*lA*rAuvl9|+Cb)yaEKYE>;IGqHqOBf>zvH0J-fWYg42f3 zq(`&ta9Uj&jGa}tFX!nw6Q)A%)?GJ6^}?@~-L zIQF7EriYVvE3oqeXnG*8h(L{V<5<;~*X_x}kS~g1y7Bsg-W}iPk@+uYKFWglRVPek&`OzJFlIM10mN~* zVJa^AQBMwWp;Bmeqr7mQ;0)#TXMFmrKOfaWvIhLh_uMllBJ;}UqhrDuEbBJspV^qWB2uJlLYw?py$z%oYz&!2&1jt2bi zz}G9!S&;Ky#UBCvHe!wMaiw2_`)7!C4)vhYaWju9y#)7vQM?p)8YhSKhnRzO6v+@v z*#({XLjR6`x?i8B@@zQ?YiWe6>AnI@tCSpJSMqS$~sQZ zEKDwKY))?OP0Hzyv~TIy)6scF2hNq;n9OuF;T&tLx7GO0hx+P0{M+_a>$ddpvO}Xr znYyKS@4cYBZn%xx!}U-?o{<-(t&FA9>TCXxfAx)==@zD~dxNA$n@yGU8oVRVp#l_{;WrgWKO)~e~`OvvP`~3BHUO!IH(=K_d zxjsJTCNu3P`TwwVT2MRWDx3Cy8WX>#sJ`H5E=G4QmpG(HJ!i(*oV{}?DP`SPzcp6( zQ2m`tx4*G;nbV`7*Wjgz!nD)X#$xPu1L}=3K%al7=Yjt`ttpu<=>6wk*-#34zpQ9@ z(%dm3q*5x=%yRuu&i%$F)|=M;3h~^_R59a5NPJfmyxUy&^FvRPvm(?3TPu{y#-T91 zt&toJdl4&fS+LzqNr;5cJfFOw{)WPI{sYsm+~2ain|wR2bbNI;EZAOhLs75e&cA=f z{<7|K^Xm_R!iEp|jFY`Y!_VZ`nBm_x;|_`3cRfRwQIgJu>BG~533rV+G?lHOjn$~D ze)qhK#SK4q>HqLDD--o)?_%VEjqNMfuqJ)sZ(S~#DTmp_il$}?Pb`9&@_D(R{k$#7 zez=v-+k7n2d}P@>BY$qd+HT8VtSA<|<)O4ztc7Ertte)`_^2w&g zi2VI#=K0m-FPZA+3iqzBGgD%cN~Bas9jGfeqnkVOSJclovF*o$<5+4hHdE?U^Q4I# zZ%fCI?>fHoc+2s^y@}(o`iG=s)=f8~ZHgAAsfE?r>hht7ik3PJ7>lucZ(a6Sve8`l zt5qu-_NMD@s-JnjnR1P2Vzn6iWd4f6bnJ|4#s?|6ZN}DD z1>=Sjjh=CQ80f*+`kAJz{P;-Ff^op5vyYEBUOzJ^ub*Ygs*jI8UO!84=_ALBk3Vy~ z@Qm2lxyOsnSUT3%eS)NT)b`&F{uW>&T!0^ySL_snDtS3YdqX!Gt+s%{Xjo{{QWrWo1Bqx3WN3b_6+9k>nD9Z z`hUCkhdt!T2{+_f{U+Z!l-sCe6OiwR~uOp z_iR1-mFes8U|&CzZy3rIUo$d!YrX`>4NR9~yu&2IFB?uHKLoyE*x0*6`Ek3QDIUYv z0yaA|i0dZ_gj990u%Z1ytrtpdD=9GqBHYAak_z+l!<|RC- zc?r%m@;Uqxafv5x0vv`5tBMzKfsQ$2hFp9ZZx(MQjukZRFhjPDWgeMA-bOTlJmR|W zhgoj&5|lAAkylj@7LLO7HxL|?-?;oH$hPQ5gR%)Tj`oMili#88n=ik^DkVLJp368Z zUX%uu(rktbl&zcl$32G+=n}-y# zf0-0)3Th?=8#(T0Xd8ls3{r(1gU>ICPu-&EsqnFxKqA+g#3L5MDMUR;>Ge-M|=p!H2l zEp6vOzz9eK1HG0}I+7@MwNj8su>-*CV~5}}oh>c{FTVv~jLB!*W&j%d$qn{>!O>M{ z`E4ZbB&4y{_d6^5J98P_YzEJw^-S~*mhCYY^)pFlT0MG6G>BcCJ|EmM<;8-H@6TPF6|@=r)k-Q+YN8ZE$4W_z;zGb>ov@ zxhV7{DzGpz0bN0~61bS~Eil^1-t8hA=I5Ov1mDDAs^=fF+J6z!?-z%StS4b>LHgri zsG{q7KUT?|X~McAXUbeU@ujJWhbj7Ey}Ffqa`!TaZt} z!TS6Y;n*nkc0=qCGd6ZrzRkz&V6+)+vx@3VR-I<@nT9rX&kEznQDkuResI~;i(Fo* zy*7=HN(A%6+_9;-C`{cjf_ES=7r|h86hA44p8mEGJ?k=PXe+cO%+Dp9&HVE@7UUAn zVafG5&Sep?gy|gG@i<3Gr9he^6D+O+#WLhi1)TmEJZy1XaeIJ#FmFZ3bvE41&~@yO zWw2>1VI%96B`hFEUh8!H9wU)D*&i9gsD~B*=sb)dGWieVH!txe{N__rkur4Eb#Qtn zbL^g~*aXpEwg_gJwHZ$TM8t{8A z;n1~~9=-N3Df$?g|F%P1U%VcPM#6gso0}n!q;5~dQ2X*gzJ0TLSoQ{t;n{C!7i1sW zT6kJ8Lq5PY!oH9&G7&@vypEcQEg5|O0WUDvy%{qmx}#k20)yMGff4ltL~0D0w(!$q zc#*;RNXlWcr-46o&H4mb)-I6TQ&l5hF$}gQMnTYey8l|nB=5D1$yu*uoZs5er2s9lEGZbO2x`3bEspL>NsSze9Wyn z^f{!D&l>fqthJi{YMtY~Av1*f%ymcQ&U0UiJB#nkFwpr?!q^x8&klnvn1K($(fb0Q zV0j`kzyv} zhAc?(?T+m3C_oPK?&Xgo_e%=1t{*Nh%_G;)!>L{SFA3gOv6xA7I3~s;543|U3^wu; zl7g%c!Q@Kxl5!y7vcf=rUb>QZW0;#SH`Gmz(cSw^^pGoV4kY>sBXp0X^h5IEzr!)Q zyO*M`EdD7cVW9YjisYo#rhekF*$MON4drg`hc|3an&An<4w$ff*fBIHON4IMQ3yHp zr*U@~rohe#G0jYSGe`8!VfA0j>_z>InSX|6fBF53;j4cCwQpMvbh1lddS}M;ArA0* zBKF4Xhcx|9W6CGBv%aY;J14Q%EqwnFg(GX@B+@nfcqXv>D6{=KCO(mD0cG05VH`us zeYIKkVs>Uy{IFpLJebJy%gXk{<{3Vr?^Loq1q3XZ%JUoERy3w0QeflXFhTXyk~OnS zr#%= zf~EY3RA__cgeQJRIGm2rQ2?w zc41{nWy#d@#5F!*YrN-zc)0nWgPfDb4#YyRFqDwt}H>!w@jN-QXMZ?r2ZP#txT<1w|q;(iqw|XsZAFx zr4?uU=;_+lAI`<@?oHtv zTFE+nn08W&Qef8#jZ@Vh80qy7z>| zIE8m*sT;n{iV`eGkazW_+{u%&+W}in@nLp(#TFl&7dD==!f$$Z_hb+Od{doYhi?YN z`QTy9`4y9J11SRK;W^^i+_KN^@)cXUv#kwQKMKn?USw9UUzXZZzi~68fscN!yD*B> z=GALftlP2~Y0X2!y{NC>v?{f1-NqH({?@EL#G5wPZ{D&gwRvl5_40-lQH+t+F2+`S zq<>EudpTM}dc%r~Ry3rRuUNWerKFoCnIG~+tCw!H@^-dj4=r;}4g^A!U0(d}Ji5ut zolTphhPd=V*-T+e&AN@-Ub`c_+^gDs+Tn`>y;EFZtH*Ygh!CSgz)3&UFr>|OBvDlYnYsQYYmQ>TuD_rfAdc^vp zMURy&o$AUd{9c^aZds$Dq?=N^xMf_MAPEN_mu=hwQcKewqYcI8ZR=O~X|R6Pw$!Fg zUPVF$_T5*mU%gJUGpC+jym8&yl_|z|h1t>A#?93%0=5cWT)%Pc>a{DQ_@jMEHMj7S z#ApCj#f(Xl$dBZDg$KbzZB}-~m#+5}Te>OYL%n7rWoxl(r#G)|kgAE#R`)O+y+AfC zOEDaevSrg!-?}txHIS^*EN1q1G@-g@(q;!gx{O@u?tRV5v#-jsRav(4FZQjB1)JX5 z`B9n4ZBdukZ>~>mUYAT)Xb#wKhgs9fj+Z!(Pu*@_eTwKVFeZ zrTOjej$MB0FI}^`c7BbKYG=z>C>;8B`K;^KuUKolEUPSJqWjf0!IoY7e!hPuK|im; zAQ_t5aB`dbP&PmAjc;UoYO_U1$L$zwybLk;eP#7En+E6lPjYgccM(lXtX z*{xr%4%1g^LVHUl)3~c8;?%rJbs~xWl5dDahbDvyW)~Lk#DtK;wdPe_o!rC8&wJNm zy{$i1DNX%3_(Q$^-2HaAKX)%_`*Y@asy}*GI`_wrl|2J8L@k+1gH*~EqpI2kHFFWP zO&LkDa0n;oOXT)eLiAb^ZH2SRXvDJ#XmNzlr}7CkYY-|Fjd%7Z&~0t8k+hx2ASVd^ z^Drl!@I6+(3LYz=J0vgYwmaCpX+AXwJBWkqQtTrrUzW z_Wh7KN~aqi!f?&Tm$w=AI$X{;8EZjsm~P7V!Pk6Up0@wf?=v7kBQWV{muhv_yM>qhiX zd3c@^%KHqkFYoULkoO7ogPWL{o;*CY3FZAOurH6_PPs#T*dM3UH(+&`F7qJI#g|*G z=tg~ihs&3@70V1?Btean@jUDf)9De&#)KCJN1+IQgCN{d6tE*-xHAMFWEW$!*n^}5nLKg0iVNU zzPsLjLx=fRii>V62YmO(^KK4dxvhg0O^tJ_$&q(-DV_#G0Ogh8a`NhPAU7sUwFXZ*~lP+(`(XZxWV|pQn z@+v)fx2U`t&}e>?MBQftu7>gD_5K{bqBEnVeX=?}wq2e&W-a*cf?sFIN5i{EHj~dn zJx3Xw3p%_=d|B_QiZ8DM`vr!31Sbz{S;nWo`tKpS-q8jbml_uZ&u{;@J}wDzU^g6v z3_K^NRBg=HKt?U0Gve!29FY0#ALbj4;aX@KpP>(tJ42=JC$quQ@2ryNDDIQ`^$wcJpS)5I5x^CWzaOnjtMNH9EAy zoi#Etal1P&B*g9BFwb6Nu2maj!uLkq?S>tNj4( zqfy>|ndAP)9J_sE!4fI2m<0od{n=<};GeBUca9UBp?#^^S*}N;{PG(&G}b=Cw5k0z z&_<)Y9?J30ai24kcT0|)=j=E`caH0vp`GJ0XK44wRJ1>ti^7CwCXJ}uaV{go{r*rIw)jkt`N25NyPwhv4JpjeQ(d)lQ{8xSQG2#qx7EcYyRMl^wP9%}}$#O}k#;WF7|pD0+l>J1$3mA8--Y2WUhe1_(16 zSMhlQWdu-MXR)Mty~Rv|;-wZ-#Zk*FCdsAG1;ooOeJgI0rdYh4Saog#c5&S2(GL(S z|64pd^)Ft5ADH*zay;+%@TY+t&k2wIbq_z~;U57Pvk<{Ni>sI!4)aG`liZL9{Z{cx zA)7p6EqnZysiT(zPtqY^4KebI@t#jS!TMcDel26?lRnkbx01ff;vF8n%fp;oJNZXE zI{WZq&ENNWJa-VQ{>O-+56j3$iB+FZ06Tp?4_vg`>M#;FR}N#q#acG#&BNu;VRbhc_r~0QgMb3E*GwZrGsSALG`Z!j|+>{%!NhKE-cm&7uH9J zRsMazMRbH=+ma{|DCg6-b0H|x(H9WsSstdBqqD5Kvq5h5=uQC+B8jF5bW}8i{EFWj!D< z6Z1?YvaDqR^EE(^<=~g^z`^gAL6^J(<_k@%m!SOtK=7Y|$JaH*>i1;78O_+jvWN%2L{O}@hk{%Y_)uk^FPKLWBShjnGT;x5o<6T=_PLdOG{{FV^; ze~tTEV#JkZ7jc=z-HOkKb-(hky*#4$bl}?+CxJhqxE%NkiYtJ>t(aZL&xjH3ZP4v` z;(QQchC?TY`v&0gijM*_pU6`Le6C_PacdQG*wCoB6u4V)9Wc+1w(s1dk5hUB=$|9j zvD{-yXCL*n;tt>!iFGVD4CzN5x2!YwD^k&f0#AT=xp!X=9_y(o(Rl;{F{e#G}qe{OV^gEUQ54hi@bbeF!9;M#` z`S%ke+~tt@O~sc2|CAVc!ePT7mCke+i7?22CGb*W#GN{arBk1S#1kMK<}h)&#caPA*7tC~g&2Oh!Iyo5gbVyhrT+ly+56GMNlZLrOve2#tIulQ!*2NXXD{1wGN1^!pXRQ0EdS;CUk z9@g&k!^(rM zlAgc?#53&~Y2%ch1bvdyS@z16J{R=aO1}?fe7@4p2mM^7vrMichR$qrwtBcl`6)lG z^iuE~R{BoR->&p|pub<~Y&(xBeJSVE8l6d_h z9WnG-2tDsq%rgHG#T;`zKn(qV1pFwm&TGD`^e0jN{*_qgbl+9_HxSk{#5!;Kxzan3 z4u2qKgAenf(%%F6@B+|vUNDLn;ckPTlZe?+!_+9f7U{EySlj=Ll)eUXb`a0D{JWIS z_NrI$<8Zo8@yCEatoWzU`D=>#U9z7j-h?;~L)%Y%E<`$?u6PQ;Qscr5PAJiJmd z<*oDZCdEnIU+3YJ;&R+~d3cXvhRd-9`*E6sxQJ1V%v(JCHpK^UKdKlvbBAKK3CBEq zw_>Kjy&k?#F^lU74?m)q?Z)FC{<>m@dy*LWaRKf>P<#>YKlAV}6<>z??>ziR#qGEU zC=--JJVf!8!0f+CuK}K*n0Z&>;TpwEH?B*NpIGKKz|1R-Ih-7sr-1%W&^hKHoo(RN ziphVShi_0!evTE`e^IwP6mvfJ_rzM??@~I`p5u7(KMTzINxT$ynS%g-2$*99(%Fyy zi(=}_u>$FNxId$~2=`wqPU0@}4)Bx$v(F~~%?K+`@om67$CLC2f%y$W;x7WTk92(~ z=Ly84L7(kmnWun06Lj{Yj(>$>M!CVmGJgSo5_IlUAwMw>Cn7EdZu0Oh#gxzf*y(VU zV)~W&3kxmGO{4=eTs~)Z_*TV7K$kg-@_$t63qk*shwoQ>4d^F4{D@-8VPEdX1K&|x z5Bm2#{6odpfc~t9pHoaZG4x@skIqv}{be4caVb_h`PpZapY06KBO+$F91{><0(`b& zhRZR5qhFwyZOjG_Z}Irq_mjUAJeMn$^jFNfaFt?)D|0CA({CajnC0Xa58tYoWsH5f z8v}e)F=c+r!}lxx1n4I`%u|)dq=Nu0&R!lj|JS3dy-gnE>p}lS#+i_mMnpL)qnvtnH}&(oU+*Zy=BA`R~N4+aHKkpE@nW2-dhKuJ|0R zm_g6@onJeDo^TF+#%~hOhlU_5raMz*C1%|23{9S#dFRz@uA6y&)pcWbo8+5B=Q}>| zaGJa$aSCwVv65r439&@=jL#)bz9oNLanbkdd9Gz?-P=l*mGiWr z$)e81SCVJz_J{8~aYn4D`t1czRvfQ*`^ozsOFlKc==j@`F9tYY@P-6V%B(K`T!LqN z+Ve5pDWpceE3^69QNx!Q=x}Xey6(fV=L)Z|r;(oQ&E~sxS@r(9WPC{7^)C&-?ds$) z$~$>5S$DFx@$$`S#<5}VW2KKl((tlFY3H{geJoiQ!f8Iora`i8}mNB^c{<35J$B*uZ zOO}m_+!-eqj~HB+Cl3Wq_4g-1$)3u{NBZeBvISiae@dnc=;8@9&>)IQE9+ z{rRnd-@_&Gp>MjsMsp=M&GY2M+(1upKH1CqbG6i;&-MgucqIB}mgPk9(5M$-^1nL-2&Z;o zkpmabVb0EHrp4i;(BGD{mHVPW+>?X2OZuE5 zeWiR9L5S#Q)1Tu9XC(i_GAV)zZ-`5dy**|XFS<1$g;l;S?w8HM;x<^`_2+&8#sbs= zzpSiAeh7TyK1CkLKqF?){f*fnNQ_5na&IAjs<_+`3uFuui(y&CIm~OL0~b+o%R1~= zd>17aJ_Q#MpE&nQa#_5n5zvlr5{rrREq%!<;KHFimNs!7F;B1rXB{!u;DPIjM;Qqq zKX_^?q9zW((-dUHbMON)*#Lqc5)ZLFKO)Yv8(?Fzro$5Fm} z_OaFCIGX7|!-NV(2N#3qw2ev|w-VscK@&2us0U+$D}AQ%WXfR~8~p7lSVW!E0#1_0 zGpN%<0)*s9nGifZ7|WD^)8B!lNSvkzGaQS==dFkjGnoeBO)x8{Cj-}TA_YXpilEVX zonUpG$b6>}_o`t0Tf}}kC%2Ol{5;O-SHo56d9WaGIb04J&a{NHEbMwNti>icC!kxf zAzQIU0iU0rCW?`5VwT!^)Jk%pGtw$5Nk|bKaA!_uU9idWhEOOib|c%vQa{@!A(+z796lK;X%}rrQrB0S`-yHC%h!;{; z7Tp|7WtAF*1ES}0r3Y&=dD9ZNhuLLAyv+;o<}k$D!Vt6Lc4xq6nFED*=O7{4)W0i~ zorF%o(a(jUMlPBhd|+p%{K3&$^f5L$?h6HEC&z<996SRl_uwGP+2r7K-i7#qFvLg0 z5VMoxOF_>mLj2MoA=>2lW+*#}kb|RN4MUAwG&xRs%GKq&S`F!w|ERUQDnQKMHzg}BtiXQtE8TFs78 zF`OiB!X{H1@`_l$l58un5qoH&#%ZC%G6|u<(XWNETo}eODPxYsu~p!p$u1m=V|+v! zMJij3y8hHCiW)0hI4%govQ6Y4LoFf~&DV=V)^ey496b+1G@C7vOLo3q(l=i(ml$6l z0`3h3Tpo$$>-fQ**r#OthhDa^YnKVyVxLvJ4X{15&tS5giGkopz%Vn|fW!MY*mre! z=&U(&Qi7q(w$FNk17S+qq8Y=i!%935YLFd;Lw%!gqeQ_b*EOMl8$$upB-7p@K?NJK z%i%j>o8e=iEPK}=S!S!o?OynNE^8xjdrtUwYWUej=FZp?rwIQ8gM?r46q0l_f~@3E z;ASayO#Li%;ku+AMjlHt2dbyTUM;yP>kY9|p}==>Fn#2hg7`@9Y~ScO-?)FwFE%0+ z6f7x-qum;keR^+R_UXMtvrq5Mk51Wj=kj{rO~$Mwa!<-Bycj&W!)r0o(8FB|R%2K{ z!arR#>rmQ}ksqGxIx0HXH8ufL3eO5xY}(6^d0L@4cA(*;T7abLtZXH| z#H?d=OR366oXWekV4EC&%hP^&sIf{ciw^3YewJaXmTMBL=o#+lVIF=v(>t(uR&=iK znf5T}+4hX*3U_vJr8_&gDpW5#Mz`9Y-CUFH!g8v{I}?@XT&};}IkPX|T zS+aKe)n?xGgXWCV-%sCbem{NE@549#u$AGUnOE*}(4NiXWO;!P5pR9e%RZ_1+|b>N zmlHOM@r%Z;g(0tFDDW8^(cN==_jd^DddKVTF=M|`L%SCh=iHBu9;yHAh@14-Q1Kj{ zB8Qj9)sG&hOoBm`^BCeL(-!L4PeA?0E-)A)SOWnELjmOOd$g^e_nuErLX;wo=zZDU zkz%p~)!G7csUy3)7pcFr(s_7SK5yD7BY{JSouc7=P>n~zQ&bV6qMTEnA_ph>uPFJi z81!qGT>lp-zj)^qzy8X7*u?f<6|-|H67XtEj{1%q$&o`;Wi~mOhwaJvb^RTkK1j5N zpi5m4`Rk`E$&PqY*l^~k7dbec1}%YuGGqsxKTyz-Lmpz2H!ZTq&ECk8qj2QVkHX$b z<^2~2FQLe=A=E7r7WOw}=-b73-31x?J2HHbyvt6W*RDuLS(}rDkwX;gSEO?!U_i-F zK(x%v^dS*L;!_D4PMB%jy}?>#8z!NwW_b4`i7>B}&Q%*4ncL&ONNgCx>)pw7dT<_2 zAHCGqpO#ve9SrSZ=m zOvWEI$*BN~)=h%9{}6Bg8B}GREdD?&7;|y__JdXNr?3z6wu6h}&zR(t(n;mm#2JJq zxjzvL#$L3j6zdg1{7na|5IG-WTy3OY$c#{__I@ z;_nKQODf|(K9G$6EmZM6zSzIVC)MMzfC7T}lLu`0YA>nF67tSC;52#doun2 z?hoO90($y!x?MOQ0OzAYvLt>lz*llu?v4l57nfw8Q2&5{btPe7fo$C4HC z;{e~%JWl4tKXTP=@h72!pRb=4pSQv1Q?cZ<_ho4DDXF}0>Ne~HkNmwTq5az%tKuJw=S1N~EUH`_e+*8a zjwf+X#_z-ZVcb6#&raFfh4TS$J_;f6PXc^RS<=gRRIWXMBRm#ctkKaQ_8?rIMVx89 zvPBN}%{p7(`tIkX6#qDp=9acboU-o@^5D57;rY8gJFG)nXD1HYZ_PB~Aex932adQi zKcGW+ygd&q$vWL6I##!}wQE@;&fRwHmFev1_Tp*J5pm%_=6ZZdpsllGS5kibLzhEB zV{>ztccijY$d8T_SiP^YtNQYmjy_7^#rY9Yprt?}QO0_$C-tDk$(o?pxbl=(By010U z*=2a>u-VaxM8yH{OZVdlW@|wK5u2o5D_gUCGLInPerth_blp zoO6TRK?w0ogJM$naZAyEAY97%5K&mb??N{;+1k< zZ)LhuqGSCvx9o(lbY~h?o~e#wu;qwt>k3=bp2~DrCr^#<>>`f_TbThxgQx1D-E{XZ zXY#U%v2v`uJg^8nw5%{?yf_^Z?HGbg2FuqchhB za6Oge@L;O`{CeHVK+dO~)Z=oP_6f_Zf6B{KH_E#VmoM)( zu=gi#oBF{`OqnMy9~7tG6~Ml{$*?q7FY@bj>QX<>sRr`!e9=%p>^cbLy#v^nw|fA2 zN7N5)Vpe(b7*D6)UBJG)dtph65BuZv`w*-S(`CCCU+yAx^1cM@%X?w~d5@?c+{Cnb z@`^xl@}2?q<^5&=c~7e!5r&^$Pu^&CW4!r3t}kyaCgVxaoeARzrVzvNuqSUU>`p(P zRpiT?H-Nk;Ir47uTh3!w?w{qk?>B%eg-7y{lJq~ z=E)lkeZU%~AI}#`!tM;yg+|`#vrvAY^W<^C)1}{;Ir4T7Adg$noNNmGlP8burIWWh zN8YuN*I#?OJV##s@~Hhg)05YnBkvx_YoG#ZoKCstk0&9o20Uy}D{wje4&})E6Xf-m zzw(ZwhQZr<$Yc4SyeeEy-q9R+eE&8HcW1~)Bky}#D8H9_@~EeicVCXY1p~0?c z3f%3A(~>g!V}wDXPk`x&~P1{e09zfh>lO*C3j4?cP|GY{4!n6Qa+{= z&&*;ub>_X;cDfc@cARl;^4_^4;(qV>_Ve?UcQ!7DOL>3Jk@q7s%5MVA87Hp@Iy+3a zR_rWV%MMkIb1ML!ixZuW;;|y@{ZL(KhL9+2evHZ(_fAA@3GrV40Q@}M!)f}b+$Zx&%^f$Bv|57 z!Ywa<zTpN zkN0~3>dzKFGI434eLGv&$i$^3_w8)qA`|bE{%naN6PK36xAWwK$i$__`*zmY$i&01 z30PWOYK-r`G_nB2&9bZH4~@LNU~h`rEUul&r}(I{WHW!a0&C)A!4_tEB&EcdZ;b|*>J|2R1#hxR0{(I{Vk zrUk~jpB|p4Z|##!xmLg=ct(S3k=mIqqakID+SwP6#_1lHshv478mZ-8O-hQr+`K{E zt^AqhMz!1YIn&&(b{qc+Ia?E6#V|cTukIw!{>>cwkJWDVsWLC9-G*0VhH0B)?I@4Y z60EEfq*6UtZOgRI#Tnc^_zW|Vtl!D;>@Vk>ZsB(8UXdkSF^pEY#4&neVS~w9%koKX zO~a>oXPR@FC0eQqnfhB;4;Q6EE^j3&i`FO1tTiWL^%t6i;IA%u1X)rNdmo|R;*l_T zD@NAtEfYmc+wREwh@va{*@vgki;i&jmy|@Xzm60UoPN@;70+f#v+aO8JDaU2G5fY9 zxQexzyoh)feqeUsa`c;liz-O9ew-ZocbIcQhglbRK3@&)edy?dNca8vm}5q#n@{)g ztHtBX^sz5LPxZk~-aB!+VaOKh$v76*lf!NCQ}9 zMXYqTv-Y)Qd%mNiw~F%hB&9Mqbc-5tpMsPOO8BXFU3|#D(~Q z$w!;y_<07WJ5y$+N0+n4m`E_IJbDAMysH5HJ$f548$_6EJ^B%14(4EvdGx!9M_c+A zJ^JIs*tt&gj7NW#mRjU#<9g@XuEaFLM4LdH4eMa;5XzcdHd2 z$DQp7c|HsJ>lJ?!GIuB*3*4!A1@zpf_|M?KMsXhQZ&& zYdriuruZA6e@ijvbWbb(9`3(X%=ykx_@Unz?qd}H3*3{6li=aFfjj~5D#gEq{!B;G zXX4H^Fyh}ZULKue2GThPdzWICBUx+GInFVqe}{1)E`N+C7|<#PRitb z`J0NL1O9jAIS)KfDg8$XOUgfFZUp^5mFG6#;c#PECxFKgLm!MP%rp<5t#~=;7ZR7+ zvkK*WI`FRrJ+1UY$Udn24C`G==lSaISNi3ke^%){Z}1CB?*jdMN`F1*QWqfqji58# z8SWcFmpL@(2SBeN9r!Tt`HJ5Hyi@VpfUhG)TJ1sl+)Ru$0h$jeooW72V#w!s?vqL< z&li-RP1(1Vhv%g~t@LrA|61t@=+1_oVfEr(K%5U6%;`#JSks9SRt5OaRQiv=vxpe{ zlzEY2R{5REzXtqr{x#&^4IK`Wu5CN#-VB%W|3>-S!7pcIgWdqz-O95G_s=NL+rje% z#Sa31l^8Nv2A)#-zk&X&((6F~lhV0H$$H4R#E}MgElPA?8&gWG{Y$OVshgag4gON_ z*DL)P=<5_yw@Zk@vkW}D74HS!OAMLR?afNR3G^G4z5w*wmCkeXK2D6dv(4anz^-5W zC#6%LZz%s0;Qu$}VP5|SaTx&2FO}!F;Q52{FprAzz%xZy%rs(z^$S?fB-Xz29Hld? z<-~{=&%|4!JPfN*>FYq>qjbu@hFImlo4DMV_kiaPVqMevn9?c#0b3+7_+0!P3^Y zzVbcKK5L)5?poPG8__hc<)QyY0H|8~+v{$66pzZCRu z6U#c``$DJu7m1|wjGwwyomv90m&(ROr-gJ~8ARhn#DLhh=RPI>!`kW-~A4?;;lYj}Sxt zW1xSZSk^W_5<2A{Cx-lCiI_iyo{F*h9J~sIMxWCCi6Z|Q};GsT!guW8=Y+{t# z5xV6O^QJ}^D|D8t>}W&I!{EO}cqnHcG3Y-Ay;k^7fPWb=D~PZ`=#+Dl@F$@klznZK zy97Ls2tQ>$K^#Jvz&{lJLEwK?c(|1MfLPk?pTaW_Jp5sk<#I0VFPL-4NMhmVyIR(l zbKxvvS>r7fI`v#ejCyT`%#DKg0k;T0^=uXT4?%xG=v)?lk66ljg&1Y=S^aBbSvS5f zbe8p3VwA;~=>HP>R?ri%HrMOPZp4s7euo(RncyERJiJXV6#4|v!@|$sUM3OC`cv7+ z7CG~TpEll?3#P4tCc(7%zLgmKn;`#oq4!67-$M+UtmZcb=K_CU_`eN3UnG|G?Qx+~ z|96O?e;9H;5FWPYCqn0#@?%bAxfetKj>NJa?kRN22@ylim%)Fr@KDZp;*e$409Odl z)8M&GcsMNUiKUM=5u>aY&~GM|HTHc%XIXoRQPye5d0Kc_)=z}q4gK~@p;LYvvB>X+ z`J3|dK<_2^Fk}uO7M>wOpN;w|yWZd*iZR2W_lC%#Fy*7EJj^MWJUejB6pV|NBbd`d zu7NqmI@6f6%}W3nu?z!MLdR zfRw)y_?Xa%UlGhT;c)}MA-EaWKMJnF_0NJ??!SnoZMjaR{2I_`A6nOi-%V&9u30q? z+gqjxcwR8@F@vA$Z1VHj#NS1UOMtn~CLRaOy``pe&8_K4f+v87&wkR0y9%Zp zuDSJ^e1KrK!%zbk3Z4Qw*Wa3-zxNTd?$ZrCLonOnasyWjrW~%_wVYK3kFu8yfy~=v z@N-R1ezt?!uK`~K{1u@S?-tBDa;>lVpEP(58~8bcpKE=+7dS2$L&bW_!0!m=5aqgG z^V5nMG5Ki)QIBC|7hCAcE;cap_BD98?$`SQWeYpSm7{E73s$zUfti>40nN{S5i!eE z_OHo+aJ50_{+2vUx}I2gxF68_g>8btX5DGvdj&h7b3dT>3l9rsxsMw7pkNlky@BRe z`$s$k$^T2zMF+K)6#DOtydN1n^v$LB4oQM32i83loj6@E#fA+2K?cuoVkuYcH}NoJ z-msCk+~DD!LGLN%31<7Ky(bn?#C1YaD2K{NlOj3JQ;o<&4?=RjE%yQo`@E-&-i`uKE10ei|!9$yry4*Bk@K8=S!OWX! z@Ni$D_Z=ezvrWbtxJa-AI`9M^9A!6!TpBjZ!~x|5lh{-8T8u(Gq3U& zB|O}R=zYp}1hZV_I|}<3;zL5`yvO~C=BHm8V)DF6EHZy@(A6FpJk0wygNOSTy|3vc zm~zq$++FY((7A8X{J93tC}Jr$Y|u*tGjFBA!@UjV^Zf}dmb(SWxWrJeLrQ4s#9qRf6#tQa&&> zPlG|fUNH6CWMJi63oqJv8{Q^#;=2S>pIyXKuJWzLfsODO>A+m8D?eL;4-1`ne`w&B z1y2E;dp5nlds{HKFeeTCfx*vxo#yvp?LbWa4hHTlnDV*b)BNgPf$*Pi@T>O*pp&2b zI=$~J5sV_OsRphP%==5dLy&!<^2-HGe&v@-+G>rFcfFBUy-yGx?gRBc@vDL8bm zlo4ZJ%20_zKlkSlbJK}XgF`=0Yl-FAS5GX@qefzRmlYxAAV+Ayq37h=h&kaP?7*St zmR-a>a6;IRL*L5>h%w$64&u<`?JzM16~YTRbUQQ7I$~ME*Y*kraZMIXRe3*?hw8CN z)|dPgM@)W4F!@7*$zMQ>x|4r|VDgU^On%PG3-U6iogq!Q`(Yh9cx& zD46`Ug2`VenEZ{zG9GRayaU$=v5bw)f;qRe5VJ85w&5Tq{WfA5Gj|E5zLcfMPpjZ5 zxb7mBv9wn($L)UN6r2zq!lBo$2Z&{iJu2A2^&l}Ti*N`BG36X4ma+GoVA5Y8W`iIc z!$Iue`UbL zG$fe%@Hb0+U*rm=`~qT5{stB9qqZz7ideS}#0RQZ&Wete&F>AMW>#iYNKZzt)asiaH4sNVslFVr4W+Mhn3 zb(_-$wQlEA#L~810>ILqgNdaLONiNk2uq2jts0gss9t#8yu2^$Z)Q{h{-BZJ^Zm`# zUWnh?pxX#l>LC0UKir~!i*U=)qWS~7{54JCtQ4P>*Iduxl$!1LCuTQW;oWXJzD|}D zt6!wP{=jHI=*9I1dc@Jg{z%Kw3H1l?#cy6pgBqCqtcLmnbNm)IwGnu`2!|5Vby{ku z#7+#Q_#?Zmu1q~zMCID$xWA?)(j3mlTcW(9u7;tMw8YTSV9lPImbux@fuk#!H!&UK zTsU?e+VtJ>`l9_U=xzGzSPWk!E$EpOjjZ?1;;tu4B5Ci3np59P>ln5|O`YBgHFbEe zr1_txIwyy%KqS=k$*Gk3sDF_Ak67J(sQXEu4`Vg#Y)@<(P=q?-9kY^W<7}x+ftUtm-p}ymKQ9tFHF$BA=@r|M4|7h^EcJ`8$34$ z;I##wRBp8P)HK_-9zEFU;G#ynW^Uf>yq#+8i8Rl@)j59j>FclEvJPXc`GKc--lxt@ z>bwhQj)an)mXloCsqy`f!UMZxu#QK<)_L~^gT*BSU(XEZpO=#`a9h6BBgguN^gg{%2BCmnRIoH{S<(e*G;gGP(!T=4~zBx?=V2@Jr5wgkWhW43Rw_1-5+LHRV8x z6Ztmt)a-kP&SBrCeL0PReSup$o=oX`xLaD6gq7sU3P0R1vCV&Lhm-y- zcO3NZ`%9O=Y0~{$HamYv_3yK~Zw{7(p0mQaCBB4Ez}g)8M`}u6e5`lNktatjNyxPA z&6%J2v-jDzJbN&sHm7l-6Y=}^4Zh{sCkvP4H2U}LlXCj>ozVSs@5q5R`0sI!v)*iN z-@embeFxg$$LGeA&}2DVXjfb2Ek5TqRqG*ZW;(BAzmzjCnE26%9SGogL{ zeZjV<_Q^dgXIk>;NOI|OTSuRIczH>im30U7#u_Qzml;g=XS$=zYIa8*<)nUk=8Uz+ zX%5`_aQ$-!GY;l7aumH?gdPfT9Qkkj*}*NOZ3_R&V;K?9n{WJ?qST@79Lv}9S!Z1d?~&c%5$f(g(Rk|cC-7C|-j(?SH~Oc9tQAKm zP|I9+&pI3kr2Boh{IT1B37C%%_Bi_~Y2N<)0UP}(^@(kv%X znJvksL)Ppp=AGWxFTM8p{ zv!Zje&#ucXw%itsG`Aj#>B z@cf7^Kah^OM9m?Q<|A=sm}#63hV9B9INo;ftWzzV$2P&t+%K%2jZTXdLkauiDR>@s z_k%Zxl+^5EEBWoz>>|mJXRS)>(M}qkw<@imoix(sW^M}{#Vmj)VMaoll~=6vD$Oai z68n^-ZC~EBzlH0a?kOmHmw!=X3sxsv*0JcXTIvW1C}j?~&w* z`?tEX7!RHZhC6-u?v~x*bnhwhUg_UYQH%W#1d$idLGQW8e0;V>4ver~E@f-v*0=Nx z2e%%?a$He&7AL>;$dLyp7HpyPWI1Eaoe>#$=4(g6V_8llfH{R8z7lYye=ld(dw)HO zZo-#-$klV}%afwG$JU4Ib-o|fpPkcv*6;8~ln(U=cK9FdJ>e0`nUj5Z#J=T!!Lv8e zzoo0&8Yxnv6O9q94>#lBJ9xf(zYWJ-ICkJ@#Zd?qhT#~FV+4*1aE!z;3dd+1pBMNZ zUhN@1k5Q#3W53!1d)F|2S1QB4a*kDl-C{j{W!+)5TD!0#JAhr*VeFiaVHb0nI~F_H z4%$6z#}3(LcDY??&#`OlTD#tEv~RF?*?aB%_5u5#eb|1%e#L&x{@DHmCdre1LAW<` ze1m+szDi$}Z;r3VSL>_yHTojH&A$DU+E{^rgo1E#jV#*o!Ie9Rhpl96oA%H6U6jN!m4Ovd--liUdK#A7(i|N6|Z(JVwAxi|g zOYjyST#&hAz$}ONMKq6M&DfrxcB)-^>`as#?t2=rs_;{s)$6FA^z=#e<< zF=Xi2uwsFgIC~PHWVn<#+08JAEI}7vHWMrv_#l_%s4~NeSJq?1xXD))Az5$@v6MNF z*yr-hC-%E|0kQj*BXQv(U@3ADF~6uoJ+5X^Qv8y2C~A;PzlJ#1#n%!SxVVn>8{y*X zh)274De)L9g{+;4Q44CWYEhoJf`$h=E?Hhq+-FH?tXP zT)c^Rp^LXH1rI+6MDyH8dW)6H3;#wT$WxLY=Bca|xrLm%0houxI|Z7+ptMU*`sIHh zq2tV=8CKG-*w0qS65?MI`#P4FS6WGL61yFi^cHcV(jw_?Vz={>-XZSj(tk&s(sAY_ z$oT_tmyRVff!`wzsz0c(oTM_l0aHMF18n&vO}1GRD}5m&Q&c37URPIdB~4Y4}TM1bc#y_oe+9N>;D6Kw!gkud9PMoDv`D z>Q$m$YT$_qqgUBf>W!jr@D@n(zYg}Eu8%;*)$2N--@-2iG9hQIxyYBKU?^JKrInusvO6Dwrp5ZRSH2Z>sL1!)5q zrCI~AxWRJFWtvK=%XB85sQ@=KlkGn$#mWd<$6#_BtXkkhrHNgAK6$cupl90b4UDRw zI>7#jU1eW`Y(q$O<2}(9r99+#7OTu0{dBYIWh zM6h`b9wncg4Ef|{P&o>yHUleUSNUv8FBh81%!GXYiqDW=Bfwp_TGj5GTXwBw_qNr@ z?s}K7Aq6thMq69q5TAmBFE^Fh{1i_j32|z zeT&7Bd65(zuGYTTgq^c6!p>QT`RxCb2E@sP7^0pRcQz=e9@zRbu!B4*N0Rn}`m*SZIow z3HcL+5?|-XedFEsaW#C}(@@PBt!weip4)3r$7%Szb~Jp(Hy!+{P`0_&Fs6}@_-eu9 z7OFaglegC)t`)oP99vy}|tk_`T6< zm$^e)hOS-4l=aBbDL^@H>2G+YbB|%SF30}Pof_WoriQF~%l?g5cGglv-}0h^u`;kv zsHk1z-FY<-!&_uS(A9y{E8{FajKi}3=@raM*sVcu3|@d|#V0s5tE_V;UVIWg=isIQwE-C1CI(MCD9i)Nafo zi%R=i@j&2C0siq}>SmC}A}Y(b5;7-!FccJ5kRsT#$mZCIA_#D=~^A^C)TugNuV?|g zQztmuQclbg2f>dQU4kcr+t2n#@OyErNP4&qZytLF6EBQ^|}TpXWH=?o@!B_VR!~3 z(#3TkhP7kVTbuUg)>D|3i?&|TbXP8H_-JQqSi?qBW1N<0Cuk5sk8J&-5*h6u%{94e zOg=|aP>&dQaG9|#&9b7^p*(s-lL|2!a{Zd!J)tLky5;D?VhS4IIzVzqa^_9!<*T3NMKZXR5<#S}ACxqE|7d9LTVd|s$yf$3n5Jzp4g#x_iAFtDfY z5_|xw`yVq_Yli8(DjwNx9knp*_Xbg8PhC?tP9M0&FXr=AC@$e?Kr6MiO z>hS3ejTsrC^ZI8yIhhSsudiuVTYdGTVuo9=b5UpNY(DjeIc^N49oTA=rg_d;k#ij(Ns@= zL+|$86(w5D%D=D9n`|{%ciHs#t10?wh>DWFutr8Lyv{V*18XZm~c3?5UoL5_+r zhGBa!%Gh4NL{q(y88dX-qek->!_b2|H66}3@3Ker!cZ#-r8roSM7@!9ux_+cLNO#` zy7XCJYSdCptZBv#bV2TYvZx@Z{dr7EV)8&FbXHd3Yio`hp75ryvps~s74R9(;c4hcJq1} zjH%A(q^OSGgUe^F4{EZ$njW-@h>M4F&_>Juq;G&!BczxT?J+u0JL5|FH0LykziMlTq{keivcz zta074rTKYz`SX`8Te)BvepJe@U3T3fI;N?z^J<{_|7F?3`%lfEoS&Z^8ogf>2vEv8?SNc8O%}Ve4hLBUz0VC zA`+d%g2Dp-syu(%lo&zJ2JE%rg4_ZAee0dE{Y6LrO7J;K$zQ`s7Fxs^RO;^tmum~h z`1?CC0yeW|EB)6y$cuB9ze8T`2!DrW|MhwP4paQskI66h-v!rUAJ}>M>(3FeY2w{3TD{v1)U9{;s*1{fqmR_bXU^XWgu6p~_j)Q2yAWit?;} zS!G$nS5NT&1wP3lzPxJ(SB&!Cv2msUPnJ{e@96kfJ6i_&*Es9^Yx2tP%A6MCnCpW8 z-l_W!uUm||{V>&C|ATKi}?~Mue+S%_RY>hW2>f8&6aod@#DTw_v8bR_^-8;_)ja(f1cxCGG^25cTAm{U#^d>6Q|`D zP73GegR^bJ6mV`%K-uQldpd!7pIS@3?@XYQr`OVv-fana_^=9}7|DSDL4ti-cJ08d zso8x|eROQ5FVRSTBf-A1_XG}`a^3fx^DxX!k7xf(uhpJ^&pO{K zo`&#L$kX#VPm{slaS9szPEzjXiFbu>QjKc3B@HX8=|{*~Fu!`?vZagQ-Rf$qWXA03 z8O7Db71OI`RFs#O&ZwR|y?WNn(smN2S5#FO6&IJzoH=fORcUqQjEbt#;;PaT3%}6A4-x!QErvHPtF*deW_86>OL;Ss*{He^9+9f+ z>Eq-ap0Sh{FqO4*#*FG&)2B|axO6%zLt~z-%Yv14)vGM{I$H@pNGK4#yz1esXcfF_ zEv3_+x+M)ORn0x!q4J8A)hk!bTiSrqt^X?>M@3s39R&YTS1(<((1B;A6|OI-1*_1y z{J;WEn3k+w6m3}5ovY{7x!uLQUf)cwsGe3jtzyPy^wx#vR>QS=%vo3SFC$!+R5J81 z4oF&1Ti}im<+-dD?y&06BdU-XUuNj<`O5DX9idwbp!k{feqoI<2zmvi6*(X>nGAy1n$x!ravy z&um!L9_kKI4GCkqE0I{I8b)l%M%z)mrfIp=6|+lcOf0XsRCU#Aky16eV!Cu(VLKhC+FzBb?)dp|x~Ihymz5Szt#*4!jRozlj`y+d zc+c6P>K@JwI{S>$q7q|f`LFVA7wsE0@0Fv8IFGqYCwBm&dY-Syj~) z6L~wRRE{%vLaV-d#j;iPin16kT=yuy1y{FPnC-AoN~>AB=*__Vvvhh@MO9I``*bLY&i87b zR^{sv9UWXU+NAE@9WW2`MlC&_dCm=^x2HQl<9WbIz3Q^cQft1dr<%>{yasPr3GbBN zMD5NYdfezS<5Cwdi+0_FSu-!QimS@eCb|*f2XYYylVSk;2L8t zQ}dTrh+h(v%$SVZu)3&nGMZZ{Jj=pRsaB6IFkonK8dighZAd?yUjAiE7caTm7)yN0 zaWrulskv8kD%0OOvTzX$PpgcaDP!in|0TZn*m?Ti$)k_HJW|}dL)Xagk=FhQh+|k# z+uqc47E^l& zqo$oiHKbfR_l9}K<6XH}RGp|E&cd{4{;I31|2J}6*Bfs)_!1;h-g2nl{f##sOk>Ez zLCl|+8P3Hw5NZ4Y;Pdzj!4qHZ$Drwe&A1A=IBQJ8qm$eZ`&d5dJn*Z&RgA-w$G`J6 zb<{Fy;`nO7r{5wW$t&w}1CxFXyRLC{t@v{)E-^;+JJpApf`gY&V71i$tl1I}=LtX(Q)DJ)J zd-B?VO?g$2r{A(6$*b=l4NQ71rgJJ6T0bb+GLAnlFMrNG606vWffr;|E;?VMn;^fh`Z5Zh~XnFjkPGhDG zg1jEMQeHP4THY0L@-{=>YoO_%Wn3C3FARBgV4^(yQtip(-=s4;#HcVQIr=t}1TAk( zoVXo_g0)de(e03kx$E0cKr-`%9QAM>2JvUyU5c+?l|OaGxBM9AAwI}CU1s3 z-M^WJJpB2gYCkP+AwD*{&5(y=75WIDPU81(w4oAwRfxDt>>g~4(10_CU&1W?`p99rH*aq_mIQ+pYTXnC|zs4)w80FApDeXN5{ zD~l_)4{pkjp&!c~j6;`uWt_b82gSCZmZxkW>OyeMSZrB`aizQ=IJ7+4LDYFbww{8# z7YHQiw7NL`UW2?KXp}b;hnBZFPF^#Vr~Nk_w7iXR^4_h*vkOd=$3N87@@^A(9P131 z=b=54UCcA(@z@#1w|QjrR}7PnbDKd+FB<%6HD|`NqYv?dUS_7sqt;?^ucRm&bJU?=*??7szz< z??QG+eC_u|s&D?8z>{_fOGX>Us!E6IFWX6(Air1o2}F1{{+8?d^C)hh~zSFc=# z%UN;dUFxh&toxQylV$%JBYD^b`hAVO<%lU~*ab0i^)^=v)O%YUkK52j$>uIrQ{%U; zF$EX8?y@v*bPpS&eBsErW*HfyuG-v2cmC&e)O81jLvKD55PpoK_W%Xex+9GnJ_N3X zG!Mt5#uI?Gf34}jL9E9acpqw>7T};vAy@{ zCG&DofKZ1chKCP#g!6IedVLo-4bNeQ=WwKP!-sGP_lTx*&7Q_g2y0Q8rtbjOa$q6B zN|UKB3piNd@^{CDbzg|9Di>JSMa%4iv{aGLIf4A-=|wE<&(GI2<~*bE3IpF`;HM1y z3j?1fhJ1z=)Jx0ZJdl>4$S?~Kts{%q^_l=29O?3?HXrTcbn-~MWf4pJlQpRR5P;7~ z&}IBkzSeZ9qbp0)>8WzgpkbFqZLXO`x_jaZfo4;l0W#903_P&dv03b9@; zpf5C?e|@d(fhk*MqUUJR+1Lo&7ij)EVs2y+S`GRxVpbI46@$+4tZliq8T8Y{>>Pv; zI!wzSL@bLgwH6nBt{|O_hp^4y=Q>y08%so*w)L1ojO9lb%2E4x;4T<9YR?S(iva$5 zh&-9@J`@)#kcATh?+fP5fo$Gp#9aDuj1g0>v4Xjenjn~e)K($*Rg4eSkKq3ac;*Rx zj~{<5DVX+oxxS;!D$v&oeh}9d!G)mTE%;_!cM7J>?%jeH1AkpGZR|cQxHIbbh~PWH z^Q7Q>==O}@dC={7!B{|9FAFB^XM$TH^Q2(5_n!p64SZTK{|@t?g5QUHZZ23a{{4EQ zV75Kq%aG1#kZT>{i=bx@!HYoeC-^YvA;J8sxAO%rg?wdW4|&fAeWcLOM_F7CQRYtc zMVa7xA+t*GAAoBF-{{A@E%-3#>Rkr%j)$Dvg-+YnUlIHXcpem7h`Mv$W?8iV%XJa) z9pF)89{4)wc0%Ykfc}AC+G74#@Q0uWAd7iF#FcwP;)%F&FHig^bWryN@Oscj2>l1R zh6V2dPq|=D7wX;t|1bQQzl8n^(3=Hc54y6)51#R$?Grlf5x*e#Ey(=2U}UxaAh-nd zzX)E5>lwlQQD5a-3^FeP&v__=`nN%5w%`XrS3a`IgLb%B=+t4Z;D;e+i{Np%D*N== zXfyC15jyQD|48t=kg0r&fhPo>PlX-&w1q&4Elc={rfiQ@@#4oOdigy ztmB=KLkk%CImNyt<|9DaO^2R4po6lT4*okJW18e;J1irXEpRh2_@4m(JwopXIrj^l ze|o;(pg$(`PeK2_;DONjnD98rdqU{EH-Bf)KN9+7p#Mwo^}zUgNa@38IG;VNBmE*M z-(SG&_X?qN{9G=W_xsg?A4i)s2)3cyM!{3S|0TgAQP#bJ9nkL={5@PbMp-WXZ~a*C zPjLMuu{_^T3Z44=g&6I&3o`x4Lrh~5{RHzFF_0K}XP{m~h%*5YE*75i!BZl5EbwH5 zN7)63Og^&~2+te9b%NV~R|(ER-8TrH4t%5Ft-#6_IOLoFzE9{K&~AGK4+Q?E;LCv@ z6}$%cNn+^#ICTD*;Fo~^Aow}7vyCxMp5wqYYC+7oGfOZmky+!3v)yO@6k=HyT_$w4 z=R#uW`7@NeMCddevrhQAPWh67cL;xH@O(|^yhk1rx&!+6h0ZnOPlY}ObiQMz4y6A{ z@C4vbh387}By@Cj{uDS!ENeF9*9&!_&gYX3o$G-Mg#Q%kJ4P^hW(wx}i>rwFa7Vb# z;Atd=9IhYM3!Qb}F8m*W|0@Rmj^H-XpC?A%?}7g%gZ{eEUjhBMLjM7DQ2xH4Kks8Y za?)$dRAQ-DFJj2N6=S@=(5X+p@Ut(*8+fK*HqjCTuOo)cC()`S6)2+Dy8VbD^8@6~72FZyRQcuteK_zm;pg&r zp5XI=R}e!_`sZ6u%mpvPPQk-~ze5Z;Ga%=2p>uEajNmZvi^Q4ky7)Ije+qQvj|(#E zfo;s|dfrYV2Ayl3G-BxRHrlx_aSn(G`b z?-slUb$n9rqsaRc!Iy!DK|gcR9SXmXE1KNH+!~SRZ@8kW3U|Sk>XM#?Yp#I{1XJx% z2Ikh0{Nyb*@Fc-waGh!3O9hwUx>PXRbA{j*T-O+Qz2IAL-E3g;vD|OqdaHr&6#N{n zUpDYp1;2pnJ_A1}_!zEy_eq(=j|u)gt}ha!y%~OlgV@3KH3J_P%yIZz12aGQ**+f{ z_>^FdNv>VU69iVj&x-smLU%w{dk4@dpX(dVpCg!bu4(i-XSCoEppQ3jvEV77mm9cJ za24ot4Sbd0D?ndlVC9z#^XxXzxlUra)PcXF5>x&b!IXKEVDfWK!+VGIReNe+&JTNq zPP|`m88Fv0dYz*TQen;09d!OCN;)y;MB;_O%14>tSB1V9bUxe2L;QwdjycrOTZn znC-yz7U^tf{=P@d`%CS21lJ0EBIsO`X?~gmBQ5}4?TG|$6*|Ysb_3rom~yx_;(CB# zuVD7wHx2x4!Q2ZzZs4Z`zYh8l13xdAzY}o1rR8&eB%TfVZyERdz)uLi3v})eSuXLjf}aI`(ZFg?3Le&%>snpk6M{+S8kcm|{j^}d)B2Zz z&j_Y3y`+H4Puxi`-^0L=ucuFU!R&von<@Vl$X9mvfjju`lP!5k+)5?lrRs^Hnc++$FV4|3iW zd=&VPg3kvJP3(}T3HWbJyO<~+gu33)z7URWeixF6^lf*s%Quzz@q8 z3bWi{f;lga6iofqI|1-h|01DNpEAMJr(7`SFz$;ezawz9V1A#mSnwF&YXp}7bMHic zj;r;8`JBJo!1oD$7xeuGR__gvmt*e{p%W{=ZJ=|UJR@}Kc~mgR3HMHVZ}qxhuDO0= z;NJ=kgU)ZW$WN@^GXQgb`@7JI|0S67D)(CC=Nz6Ucm^={TAHrjMS#ZvJy+<&1%j#1 zD8XUiiv_bR?#(D?H1KqThx;+oCxAZB;OD+e@5{7rH$Ky8e3W!z8Ym^c2zaZ3Zx%cb zbnd(KzHFCZu4%t+;BN}%@5S6}k-rT*M+9@-!MzsgTu;3wnD1PFZQ!>AUj+Jxg2TY4 z1am#b_ji;-{E6Thz<#XbNGDDbJR7*1;46T83g-J-$G{=MOF!*cI{EXmoAMHF(s!9ni`DroqoWq~2HZoh&hKJ?rlV z{+Hk}pmU!|o)lo3G9~6(tDAv)3eE%FF>pvQ<#3P6yj)L?5}XA*&cG7{4+fokQSKjE z)@;GUfUh#}e8FX)UuR%i31Qy(pks(x+P4hvZSG|lpo-cr5Ie+JpEC@?q2FN^5MvL? zFdB#6+lGm;R%Bq=oDUf)aqwPdn1e&#zcs`dKMb`v^mwQzmcDBwrcwwI9D1M5IOWga zRq$+FQAI_+4OjLJdG5e9DEPa$Vro!y&ZQJhp2NU>1asY@>WjA809r2Tz!6*v1h?SI zbuj5S<2qXKw{T^@lKvd7WyI39T+hy2Z-f}_z_1wyG1tkgr|vuILCki(jhGFI za2F0@&Z|3!rB7Q0bN<{#%$p8jFAm~Cxb7#GzJ5qB@81K&J#a#J6bCWe^&m0Ymf;W% z;-BKmIZ?Os{lwC?YVRiPsrF*hhP~0py4{8nOIs=bQPMtlk}hp>HQGYA!~MikceU=7 z`tmm(UB^kpQZKdkle(z2oaoOlNv^J6vP?U5DjYGYaP);EE*Li4aS96yh7TV#3<0>H zVC0Aqjx%i7$O}d~44?1Q=@2HJ1n4<}4TY8-jjs$(9iHo0iL}Rg{}DLsaBIAU9ue9i zLnAwjf464nE^Et=>i@DUu)SxOgkW0gEnP!f+A<^lPL03C0_^$ae?5gCd*D}LXe8y0 z#LS=gKiq|;j`ulK@A_sSpKUqfgD7M9&f@{$59SDtE8o1#Y@h&>@Ymd z!Ec5?9MPUB3b2GZ$!w%yl{^?kqmKiY2K$TjoR(vYJz^2<|kMQ&S^Pxp8Yt?)AfWC2VIaWhvhe z3EQ)`q|_f>PQMS~rT}s)zYn<5??ZTZ?(z{2v#wEp7bi9*9XI_WC713jer$REM%KOu zUIxz1>D=AbDYX66a@Iazcd2RBBiK5LVjrLx)U2z+vSecu1*3CnFymc|+8|`4XKrm-(AR{ptx!(DD zz-h8G_c~j)zCXPwuy*?H&TUo8r#CGEa<*0X*jBQ=Yw|uTv~uB=iA~+s1(t{J|NALtOBjAd zicsc+t<D>WTuNKSc#(%#|^#8B2Cv`yRJFp z8&$XbC77S+7&;8kJYnCp10w2es=jfZ0edI#diP8H}%caZ?e~;ex z;YdoovnN{i&f>Aj-?Q@9L^*VsKT~BEt6ph}bW5oSjzHca>vEUBl$mKc-|)AgO*XPu z%(6Phl~vUo-mT{d@^Lnz*3t5;$aN8V+8G*CgLCofR=7^pIyMg$9tWIUjt2a87jUoY z+$nM(vDl5jX3$eWGJTa47+K~32k;z%uM+qg=}17I5!|XMpC4Su#rwzzf)=3sE`gn} zDWN#};H<;vrC$TaXnqRn+?&r{9a;baEEqmPR58h4vrS(gu1kvGRs-rj-ESxs_i=W4$O7cgLY=yR56bd&$e@A zn(18od{%nsbJELV($DUj0Ws-km(O7pE&uHNeBQ^WPr&++l5pk(i`lK6;0q++vsBCP zE}Z-}yT$VR)Y8@OuB`mYDyBsF^VoiV8;>cnsnwR7FapmaPfT(&AuzN83GgR!(R;Wm z@$T6z3PwzrPo34^vi|eVKa>K2$=e`E!GzEmKzJNMjg#SaWN;?H*(D3}P=1RAu3?it zi)&=A%XRV9aaEwmk?itZ$|6%-Jcr`>BR2A0b{gjl(Qoc|k)Y(aCRA_qAzaVZHHi~< zBenBnxXG}Ke+rls;Jg1WeCeMmjb%>^W4WsewkJ~w!BaTu+{)Nf)d+NX%72K=ZpG|r z-K_i^%j9{ zFZkBItLyec(KDE654CwW*n9=9b`c=3CAu$MwA!+V+n0cT-m5sN?SnniRyFJC#zwg@ zF#yjFclVEWYax{F&8Yo#;Xy~h2={|Fi?*-oqr`thiMOxh-}+hh!yc!48DZJONM_CK z*4dEPpHb@Rz?TK4S-!EbZ1r4lnbyTK6}mqcTlUASBf~W~Tt6LIjM^Wwt_<1pm%!J- z$82wgkkap;tUW^xPY#Z5n1I~6Cn#+P%oh%Jj-6nCit9i?)mej9sKmkS5KRNm`8TV$ zVx!`&ypHY2>u66$d7mw>qn2k4VFxp`;ILa~U@+%?T}XrYv&m2@8Mgt&Z@?_WQ zZsh~rZVe>6p8_Buwp(j;x4NBPOGbu}vZ;TKmVz+_G1qd0+q2veu;Q!{kdY*o;q6<= zI4ouW+{!O^;_Ed$rvGo1?)TbgktcA+*#dV|AoZLAQJ7`#h!^;{C$RNwfvvkfS756M zjIG+&__ET|B+nh%DBl_uQ@!W^3AS8!gdq&H!b&i^(?LZ$(?NxOCDOeI-TCcCPQ$W@~YVC^PzYdPHy!VD|H9Q++2(8_^P{Y5v^ox zJC&@p?|{_!oUwCqZCoXndX)@8E4%eCUL}`$mAsHOv6re6?MvJ$t@NrSt+mDzRHg)# z^*8f!d3ZfmXaTFVu@#wM>l!xbEf3Qh{J8QoF6-4j1RiQnmo-|mTb#cj8lruDqj z6j$qsyU!D+9@4F&Q?$1CdE#7c@AI^E_1ozQR8JMR4_n*mjaJ)%(9iCT*0|o-<*CJ5 z+pRzG)Y=uN)~-0U_Oz$g{q3lAzx~ALs&&7omiwF-$;Bn#aN>9W(>U>iJGH>X+b-YTqCnueJ$GOl@`tQW zoyK+YV`Gk2z{&@Na>S5uZqFT)@hUyp^}5l)^Rb~ke9&hHy+}X;ooaN_J~>jfPmazg zBH3lYGPs*f#|5eE_Mmn#5p~#*2G<21Z+bM{QAKqXcMeL~|E%{8D29T2h;IZqdcbdy z{ODDC>WKH9(L2^9L?6+N9wMN8->4k=rRW#+kPzcnqJNCP49D=7kr~aw>c`~DlA`I> z0m9*nN;e9lp=cX1niKUgF~IaO!P*Yg{x$}=el!M~4ESBnbu=;LEdLw1+W*E-*YAaL z!@$NA=urlYlA7tGx2O5>xpLq$CEnOXEsi&bb(_)C?(5ukwo&Wmxq-mr$?H zhB5GSRey$7z%VNwVG5=1j zwm~f{`4ITB#|?Aj8jQPMBk>lw@&yIMe)ppOVx5qCLK2B7R=2b z3S;aKZJ^!v<2G%;e|y7Iz^^I1Z^InmHx>SdG9v#=JI;vw!#+FXqKd&e$obFpw0M7$ zk5WEezY@0M5xrSO|6@HZ&~NeOja!}7@1;WjvXwCkpS0{7dk+{o?DG7f!;AfEr?6_3 zJeBj5J7PQ}-oKF|err+szKzuJH&$LPBKK{il7CbQtsBSq*HDW;tLP3LJ#FQUA5u6i zb86<;Y2mTsrst0fPeayIs!n&aPNutrW43)`=FnkFE+~hq0ctPP& ze}$7Z7GeD6t1pkqeVfldZxs9r_*3%y*G_?J2%5CdS7Dg{8YR!)?S)}VT$R6mOiW8W z=d%m?We+X99NL{w?YT!a;P2K?aoh8L6@6E==U07s*?q?q_)Mez$0@)mgoON|*FeY% z>m4-5lPcE_mB6PJKBgo*;fw3eKe^>+=TGgQ6(jc_K6_jkqa}N5X8+6l4UWG{tUrqF zex-PJ!PNe-3fV03H&vX*7%jMjrIREN$-oBnaeM&gb zHe)lXa&iTX{a1>Ka^6EQ8$54*9Sn7sPN z4dJjLJRe5W7c8=7Ruoq&*|e{&Zq(}fWw5fpU@^=TDgPl+kt)HnL`*UD%j)K>Sh8}B z5<>~q#ZbkyqOP=nb@PQvpkdG2GZsF1dR290MR^gt55Sr_KG~YLRDHGOSr!({)ShK? zB{D`mSK}p17cZ->Un!9W7$5hVWV%cAd>1GY^Oh~GUN9dXR>Y+8>gpv6wHfU=Gu+yK z^VtT%wb|?#v)h_&YP8kjy1{GkB3NXPwdP+`Tm{3@)n$|6@3g#hc4@h#ExcFEf)9u4 z(vnG~QTzSTF(TtYfAm&8d>CuT)>Rg|moBTud&>q3PHJFGdnt@jd+peOfv~zEs_XUW z%>O!L+i@n)rLB}{dDD?Wv{5{h>8Pu#^a9wCnm=#BwbhH4M$MC}>Y&D618~$u3u9(4 zsbLYuw%hL>4ZnWE56-d$dI+>*Xj>jxG4^(6Y{U)uHY- z)iKfldc;Ky_4C%MYp8Bmv((c`TW42wRpY&3<%(4cR{r-`MrWyc3#1F8GlNV@RWJrW ztFkKIWV)y-E+wMFReTz#ZjNcjB@M=ORX($N=4I21)ubj374N3x|4IiS-uy9qSaei- zld^j&NDo=6f8f=EPYsSKH475VU$(M#(ZZ|Y9|Xq!7pkd3Pnmk)#yM{AZWi7^?iB3a zMVw}&uXI+!WrXycaTm1XmqsbArXKfEAp-|Z0Y%2R7xkBHo@nzJ+EU9gZ3D*bZ9WT3}P& zLx|VuOv1+~KchG09XI6RCt6S5y}+it9*DGnrh}Go2M8K7?On(V;mZ25-b~~5yEsgF zyAT;ef&?w&VZ=3NQu0{$8&b;S2eVq8)2=Y&rAr#?dmewVW zAwwR&mecyZ7$>h5^2|4jTHbSU@?J3HIfgv`eLwzt^BV1w6(FvImiJzqyw@O)d>k)X zIJAEJHai({Jx?BkJoc9kT3*j~_#&DKSItM;71U1$%BulQ2hFGc4oeep4UEIzw;?0- zQZ9yF0 zE;I^1^V31|T@_dEv8fo(_|%8x4#AO?#I zX7H;!H%jrlpFw*BznXEqDC)~6V>I|N(5IMpUPs|Qh{@l9rNTNKL(W%{O+f<9>wqdrsI5BtbvpX@K8d&BnvVW` zP~yBW&(m@7V-n}DV(IAr6B6H#YdUiLL*l$Je`gustAW$;FC=lUNzySMMoWCQig&jr zNt`{Aj`480#LHB?mqmL}44l)`F&=J^IBs9W`&oBMoX^B`%W>`Eb90K9JKlE3+rX+_ ztFbHcINZID7nS`{tg-g?ZPt4mVFmQ=*OTOJIX#N(bE9vM&S}@(%VO+*V`%24_gv=Z zy(;6ysPURGe#`60b@#ckQFoW>(bSGMCKksKw|Dhe&)TrM>%ug-4LN>#%M`)!63oE~ zf%^q*m)^v@r!_ybY0R;cCbuT{Q`)vU=XG6Hmcbt;eK1Z4w8x>#HF-uNP4nyp4$4Gh z>O2GV-k2imaGgtjK5!9C-8jQ(Ju?iwXHwu5Rj?UAyyx4%DSZ|2nZ}I zpuT5y*JtiuX}N1>O)n=81tZkr&~(;E+Zkvv=-Y_dAqe|%X#R(YJCQ2(uBF@$5D%m% zvegIetoa>cS(9kd{g6q_6M|Y>fu{s(4~{+Zp8`LhXT(3lwL~!O6HFD%I#mk38JN!t^1lwAg@Ril zbBW*_;MIcPg>LHw{|ES+1jO;?Kd6Z|(^YX$!v z<*pOVX`xAQ7}plTw7a}hupjN9_K1*4`#t-GPCG-72)+>b8NubazAShq=(Hb4efFZP zcLZ}RC_99p(=OV-giafEYRrRvFWRB2&&|ts3;hLCzOpe09@^y^C3KcmEcgoG3c)P* za=|S3YQZd*dl~A+a_^f`1Ji9vA!_@O)43HNYnX_kwO82<9{7 zW5LsSMR_cjbyc?efJZ@QAEDER8GRdS-9`v@(EgJI^RI?x2tFTq7YKeFc{d9F8SuS= z`$7k09}i^>f}H1tz65f9X5hC4FT(XCG5Vk3??S&0W#K1x&o4tJF=T!l>A6DB1phFh zvtFZ#WjM|E z09H2GK_@;Bb*3ERA%gJ=(;6q3>lDsM5xdGkI>-37ze~ni%!!i@x3> zcrviEU531if$tLfDqv-^9sExKE1T`op3ex+OQ1hTjItI(hgXS(=Y&E3vw@Y(G4MBo zU)dQ0ZUxSQemZXvgWy0=qz`Sc-)g8A&uBnH0&{ybvv z^Zp%Y(5D-CzTjN&uM*5Lzkyit-eSf!N8BAk5VD0%e4*eFco_8B0L`v2 zn#Z%jPR!ezJbWjZDYy@=IfD7zNKHR`ulabDM zX?#v=InxAl{HQ%Ic!=i+{bt~511}IvIee~bIm+)6^0Gf8LMPrVI1KzH1K%o`{d|vs zzbv=_SG5O$OyYe)XSrNMu-p>leOhoCFxL>I4*>qLVCLmBnDoP-zb!ZqS3ZMD=X3eA zLFe;TzZ2kJqZ6|Wx)_+|KuE{!Z1MLcjr$7T4?3U6TF!96Y+KsrC7pPj;I9Ig8hEl` z%29h6@N;hAy~@1ffYt9Uz{7x72z?~*T7!rCeDco$ozH$^wvV!f4$N}-T-VS2y@Ka~ zu56*>W+eW$(CdJoG_cwSfoBEi&k3FQ1;MLtzI>e-i=orREX&<6^p{=*HP3k|xmkuG&9GkCZz(d!oF-wKl) zhK)5(=)~MRk*5lnYZqebxlZtOV6GMPTH+SLTw2_1;QIu#eYl>`{M>61v#+^#()cNZ zpX&_G|B7I?`RfM$wZYG|2>CgNPYLGO_}IYwO`klKp!W^{OWP`&Ob%|cLye*jRaMHjZ2xi-Iy+VGr z1C3S@vmKNzagm9?byjq?!+C<)4qUtFwGIA$S7Ek8p@AD5GJ+bJfY=+}zB)v&^n6%yCxznKEE0}fps)6?irk>v-7Wv8tnXH`-lMc+X zerRCjn+)_3pmY7C*II82c0fOA;12|opKC15Pg7o6zOo;#+uxw8y)(*UQa0(rGgx>S z9V(c09AV(mf>~|}vE&7`w69FoO|Pk#U%xYPh_SC=;B!K+e+r1P#$XtYgU>I9Fb>Y` z47>;Qd|pY+MH#{z9D07JA;vw(P>Vy~FZIN-4sRqzUo%8-=yqeAI&chQI4jICoGh4G zs1|uvFt4hPXmj$9ARUy@nX;Muvqrh_Au5mRQ=nPH+pZ^~6*RVI>Y?u1{3ENnhMR zI`Dp6BgAYpgv~gJ**=t|`)HeB>U$e86+^fS2k~rNcMwb8wF+jt?ILDJAne6KT!t&l z)_wYr-~qTEAkM%E;ZYpKxwsxAMmsYc!a+O&*BsPMxAPUm$>hL(jab@K`O1+te1UXn zx6XW~qkR}!iKTt?+JN-)_^i=mRh~Pf-%Bj@JwPmVRL?jWtM8F6b?F)a7XA7A-;!Ys z+H|qw*)AS&4%@|}Mvj6hV}#GQU3~gN*DeR!hIi>j_)=r8nsVC9uA=QVLaia#+_o|YO)NsHVMTdl}_bOq(t zG;MCSy8G&hx2`MAE)LY}-dQ}SCU<$b#{^5;4AmT|jhVG`GwsC7<~dESZO{(y&2_BA zUh`>z)LE0%?B>umY_0CLl1$#@(jN1|!HBi9xcA9kIk({JoN_BNz@J8IugB+MDpZ^* zR-AS&rL6hd1xO$wHeHqaCJ6 zJJ`|=MZpKwID1idgMU}^=1qrhI;zTdTCBWHDoW4En*7G1#)*w;bD?bbYF%lIIdwg>?>@R_P{eJsz#a3&^{|?Lc<|Gcx`i^YV}JBSe@5B{Y&>6QorFbQt9wwj@t{)6iDZX^P1bBD!#4zWvFFBVx^?09>|0T9^inutW!~XZ)9-YwymHm%ho9cM)*4b|m9BAiP1uefz|wl07YO>Y z@610_VZC5=^*g@wZB9xMt~#>)cKMnKS+lIdB_%mOA7~BjmQ4*4a(?dE>CKLndAln; zt;u=m;GD*UoNkWQJDlU+?zXp`w#f-MZM*ULgIm`&HqIT;rF$v*xyP?qhuuY#6g-i# z+}FqFxOSXFZ{WVe7)Who3%Hhwud{-G*CS$d4Qn1g;nq8FI%|K_Uht{IR`(RuMwI)2 zYQtbrQAPy)v~2zEBb^$bI9-fbr^ZJ<8Z@a=_0;T8q(`U5Z=Pn3l;ueuzOAUci;^d4 zS)Gp8)#t9*+Q%2%+z0nc*g8--`o=!b23v5n;gcO8_kRxUJL7Y3bxs(HnatLQP{-}x{#`#C}Pjn6j zOS=?z{nDTGxQaNwgim_o^>5oJUF`#`!Gvt9@TrrZbWK0-b~vS1IOJ0;{QPn&^VRH( zUIFK=Q#$V~r>MgxU9AJ5UAkpwXGW4KBa-Q}cX)S*qBw!TEudgF!iY-9#5@teUr1J)$3eEnA^IajNZYpu{?02-<1q2 z-+p!Hz|wdB0_*t`8*#U9Oj+K3e}`3n|1P=E+I?gKa7O(cI}@$X9#95(mpXZa) zEFYegiS6`wNA!64^4t}r=<||3&C=(S(641Uu0Rj=*xMC7UWM3|IA-BUZA(kO&$$xZ zvv721`(5&dnJLS!1n(>yEz;xP-`jg{vh;PIy(#tm_qzSu7dWDNxyOfBb}8+8-k;1K z?S4|VgV$DW3;XSq<>9ukd847~1jmBg_kni${x!4j6Jq;*R%SSbQcK=`Ij-mZ_R-j$ z_t{?0f02IWGwU4vT7T|-4urP# zS62CPrB(jO{gKGgV(UPPN;&g~GiO>3MRrA+F+YLh6Byf$?1?nrb||vPwdhYucz3wz zwx`0oolUns>D>EtjEOG#?m(OD)z!R@q53=>H4<&KBrlp?`f2L?(AT zuP5G+*vTC_V$GY1bx$(hjGw`a-4rW1*rR8!-oYML5M#whGu3?8T}egk4&8h7>Jtbg zq;&3rJZ){r7v#r7XU?S99n$*r?bkm8cdL__qy!%Z5A$&;ZQ5e*gSZYG0Mz$c%C7np z#5(sj(+im111H1oI%zt53cyJh>9FO_fzyRH%(-*v61>H?GoUs}oqL;kM#ki!c3qNT zYhC3@+38o}X#d^JGdCuWDu;R80+ELxKF{Lz^QZz@4)@mac@i<|qxB(2mqFV*y5;2V z?BtbWw$U(_qgbQ%)lJ>`y-!r8s%D?9*RRx|G62r1tk{6E-x6Tm8qbA9~#&PhT@0w!#-b4~~WLUy8}vLqy74O<8bh@9*s z5|WSv1XK(tii-O#se;>8t8Hzq)mAN5tCp(Q?ya?!s;ydUOIxdLZ`J?v%)Ik`b50WR z_TKjXdhdVW^uW<|WSETVK;u#vZ63=u}$U2A^xuE8Wgcb1}7XT+=&vJYxm68l~ z5lg{sAr2(L0*4d7^(-hAm(YG`#5YHk56)?#Xj=W1MkAF$4OLIYnPJe`X8PQr%Y z0pqbw8b5_2|By=o;Ah4d$M{)@jA#5Tr^CKKkaX_J;i2>79CQ#Olp;%`m zp8FcQu3}Bx$;1SoPpoS(v71;o#l&62x}+0#6YDZbynr}f%ejy^LF0>w2WY&PSeIMk zCBy?Y{Zis2jV~ik)_5PWuCc_+i3e%=70gB|dSHi>cs1$6H2oUl;Tm5{JVN8^$Ujo! z8%gJPF3{vA;&B?^OgvuWTZkuUyq`FvaSt)Si$UDa5T|Q=EAd2)4-#i+d?)cFjqf7P z)c9`V$r|5Fth;C8eZ*5W{jTvPn_Pp+;bEaX^ z=|oIA{J7F_tCe>=OoG?!Apy_I9Oqs`q(TaW9 zq!}hN^-p(~C%{5y;>XBch(z}g|8gvHdv<$r4n@oPhLNLnf6D0Ir&#sC)V$(9o- z+`U@yXDIvt|2C4^la`1k#Wam`Kc7iB`3j*;q3;!VpmpFpv?uzRi{ELQn~#U3a3A|D zwnlCN1GD(YeS@S>f=*RNb!U zgQgmIjfu$GeZh<9HGV0{bT1Q}YTTZActN0xCq1Tk(!Ct$;_>}qYRf7}cUQXksJ|dA zd!hz$Spju%S#B!si(YbEQM*0QA~{`p59!i7fwJ5$tF%t!+X|9;&Z{@s(T zGxUqf9R#T3f3F@^H1r?+kdzF##0R_C!B!RmnNy}A)RkK}ZjtqrIy zrW?T-0d|?_M$j0z9n7EH2$}-?@~)3YkgvbfaokosEBBkznQEH1K0h+Xq^nrp05~1gA>*cDwg2R^iK071PLIwaK(UA|)J+lrNDM<&F7$SZUX>sOcDAE_rBsUyc}{*Vx{Nz_BRE@xDNM zX>uQVD}b0#tVTD?RizZxM&z%bJBnQCmR0_Veo9zAo9I#5nM70emF}wJS0}GQa+%sf z{$v{1r*c!YZ02IBh6O|E$+E4^8+-qv&osk`$;)HwZ5cnuvT%=-a{FQxiP)i{Dn@MM z(^fy*jd+c%et218g_3Croo;!t&MHdMg&&`Wu0`YgtO>AUz_g6|$1{&7nd1g#=;H=p zlAi_ao8m{bsIoj&#GxkVj0zyJnVG8n&hoN`X0dRnHs3#66G zO=71NXW5hP+fusg*;jU4XPqByrGfEIr&AEV&3IkM3-L)Odk3SxbyZw(xk1xphVA7ZmKo~H3r;|8C4rsW}MOa3sO>ug;1x;0*=!i_#(o#95g`2(=eduUjrHgPM)jKquPu-H1YJEQBlkbk z7F_lDu!{L9YA#}u_!Ko)ZS(p@{#~b^0K*>DJ-nvkhk7M!WwUQ+;>4pW3jKPw8kOO& zj>52@3AK-Cuv&W1*?GQK_7>fOMuMf^F-!TFMN0<1Jo`xEU!_i&k(E{ndX*g=&}R;h z(F#l)R(;fz;0;5D!>Vr!n$Ui#-m2pMT7i-9PpEoZ$$(Sfm1w{zv~a$K`{pq!55{Db z_VI@wfBpQ?NseoG9BWe;J3z+xvbg^{a-u9`}dOqiWr06Y7^ zBU)vQRJWDtX{;K8{9E7 zGyUon5i-Mi2@psn3^a<0)F5D2FID0`joH1`<-lVY;=Zs6wM9LKlPQYma zphlev@FmxsIKN=kob>6_CY~IX(jnJfH?}b2wt@reV}l-W-C*O);OIrc=G@s$;r!Y8 zO<`v2?A+kMQ1I-T!GSaJ5)|LANCCWoQxDR!3m?nQ+&W=GaNwfgw#LO1!m}n8=QIa* z<<=Y*5;^79HR@@D5}Pg$-h{jxt~)U4!pw@r6%%J&5Uj`zo-h+z0Q{`n9&qR6M5X-2pgTIatqW%l z{Po;MoG5TzkmpGJ@m!u2adj{^Cmh_iI;zND2*#)yQYy?$xS^t`K&d)uY);V(=sFR) zE}1g6g1XH?{+Se$xE2Ff1HW;hzkK>HC-#>@iel?ET zzHkn;{YqRP+Wt1qEe&p8gkveDj!z$-mp?8qFC5$&!XXWF9+-G?Fewz=I#ad3nb(3A z+%j{`L|!{WYi4hl6An*Whp&%Zx2~(B1*a!C>@gc^x*A}1ys^!JDSTxb-l=P@k0=(H zSZ}Ls(017CY9sY+tqqZyjt!oj>t#!;DwkDMlvmmP?BP7tVN*@JGT%kNwn(KceJcy= z{5(KDDfew!k&$ANsRq0dlrbf;t+Ama(q7ZiWgScqWue^&;v0;b`uYx~fe5N?X{+1l zXo;$n>Kj_RYLvzpliE?VTcAp7!#1rCW5I4dj+*G|sA=s)$_~xwJO3w*y8A~SsOoCo z(ArQRs%xszRm0lq>}qSr9~cwcgd*)gwRg6)Z0TxlYjtYsx|+A@rlAU2wGWs~_ZqZ5 z$m4976C=BwT__?nHLnD`_UfPXL91@uUlFNkK`}>KqzyNBs+Yt1e$F`^EYg2$6+XX?7-m-k(Rc$b{PlQz`Qi&)vJu)`5M%ppf z(fGTAlrW$gL9yo^c=^+Ho42*5Bk!z+){V`roq1=sb!_Zx$4H!4*VfU{(p=k_Q`6N_ z)5)V1EK)~3T60@LenFmIp5=kFrlSs46l=U7N0oAus%NQl4(*-(u{|scZzPwIKr)Xb z9&k=ZWJ&oFzt5w)>sF;s@!`u?8SFX8d(8{Ssi5jvlo6c#vdM4QdhlW9IYHEI;U68F{2~K&Kw{X7KnK@ix`m|2|5q0}GU&U`%#=fKQRz)4J!v?^DQWKR@*y~0d z-a6YOo7(Cd9Mfr3M^vq#17+u@VGd5xEz}&Xq8L>a-S4}aI#BxFFv}4MRo3M+)--c$ zHG_?w*Qk~1Z%P4AlwTN%q}q^fi^{KNJk^Et*y1%drGf7BI5om#TeW+Cx|G5|%WFWJ zQ^SX|sbN!H`*tg@YF0J^II@&g+Gl0p6~F2UeD!a-S92?l)&Oh0(6BbB5n2xjysK$%z$qnM z6v&q99e3o|)xh!(^`E&Xx2wapXZ2p68n#*ny{9q4)|ac z)toGv)yQ%cQv{83Q#+Qa^=Q0kk~J;4`8ctq&68l(+9Kp%>(q}ic;dt7LHGzlIrUAA zU|!@|krNh-m&?la$2S?FctNZ`4#u@VK7PUwn{GE~#_4aU^_MUPGwjZV`hnOCVAA=+ z2Mo@9{A_s-!EYX%0V+OT=USNl??WErQy$B}$g2ak<-GyFR~W)a65nAlj&VmI?<#mG zkL6(G?EsFF2M>9SGcvY-U@*hl7wIp>$cyhqJbBjv+w#WZF^>1pQTT9iuCy@ydm%3a zSIT3)jeeg8w&iVuye80$GdkS|g24>CAM#kvl!p%nJb5nx+wzV;ULpzNjErx?Z!m*i zfxK#5DK8a2hEdO7<7dmGeMh@(85zHnIJhV`bFp8)LqIX|)OUFBn|41I0p@`zBN};l zs^VeN7eL79w6~2BWz-;8v#InSn_@qBQGOKk3X^U zT=YXGgNt(6;A#EE=bIOk?$hhg zu8YUJ&Xk@r)`t6-owBk#NzdG#B$?F`DBj-Qctg~-d7_>)l>?+z3X zE|kaL%`tq#PLi+Vgw^n+!EgEygDSyyJK`E=^yAecJ8`AhF9@Yzs={km9B-(KEw5@m z_^gOV9@wIsP5t%ASx1qef1-nCq})gN8&E3;A>hI@ zMOY_veEAnpD*@$Ie}d={bH&S-f&tY-?C`y^nt*4V zaQ>v@8fFMA)V&eKKi2s}6#sZ9LB<5W^@F+5yv8y?Csbe2P!#O1og&?_x|cFQy-0>|~g;VC8-{Y@G2e(g^D z(sJF&?zVU?B3;@E$K+Ht9607TwMaqLE=^i2b0ep}@?kVnKE=dRJ`0E?FIB)s z=I4l|e7;I7(GtF&)Bx&uvo4GsybP3;Ntf-`*uhiwnI&Cp+V*VXv3(l;bBTvS7~K69{R_m*DBSZFUEMPe z*Yp$&5JpZa@d!;Xw&-QV$7_1EMXx7j;lUlS=ywv2(sZ`@xUj}4#F?-Q2wixeL;N@J zQWfG;!OUpbNTbFaMvI$-KX4p_$WwyGqwWuZZ$n%TE2Oug53qR=zlkfWoS55|77Ct+ zI^$SJ`e^WAqV>|h2n|7Drbm)Jh;ENIWGlK62zC-W;#JyMW z0;KhM!P|hpEVvlguL)j*>yv`Fp#MKB_)=V75Ij6V?2tpB=fU$6p??eXp9^jP{g;C8 zL|PmtssD%2hihEoROlbXkGK(6WqTa>G2~G_e*-QDUA05tMhgYUlRO7l&3m-2>d@lJ$_s0 zA3}#83Fdh?uM4IzgWm~$A3Xmdn0;E=_l6wW=p71K%=;M#FA#hO=<19u&|d_tO6c5Q zf3{$rF`{g1gXho4%S}Q*8FEyc2Yo!~Ul4j0^7WYDb->D2Hh2an;JLccss9nd)Ki@+ z1Rm9*AoTb5B=8&W?mWu^K6aHf_Z++ zd4i#)^KHRw`^u&?oGtWkfv#*vgHHRZcWeXbg zo1oi$Lcao-?UlMcgM7Rw`0J>LmjzG9^;d$g2G1V^uZIrGwlm^#SWZG3l(QdKb(RqD zM#xVW`m4C+3jPaZoFbTIGf(h^;8(Vq5w`|-z0hf&;Vi);K;LRG(%o(1LJr#mb@*ul z40;IW&h)zl)6Vx71;2?rJ|Xzq$jf2DkAUZgg5QOl*9E_ZbUzkM-IbkWq}2lXvwy!(DoZpYJw!o{E{%)j34S z7oQCzLU*I*5Mt0dULH@3H2~+OY~jg5xfK%28ep;T^Sp)S!ozY~YtfqpcY}v>8{@8p z%nOJ!@%#hza;0GQk^O=%Lw*ht%X;Y{!N`X52yq4g+}ADq95M927IIz?{w(nPOfc8) zM+MJgWTZ{`Tn7yyhCZ~*JC>LeJKQv(GhduXDgQypFB8nO8deG(7=SF{=lEGG^uL0> zffzEmR${+pT3kcjOgw}s>9R0&@g<472>LuK{AZ#}`2N7GkzN&^jo|sMVA>x3qwv%0 z)JH;p3Uu0_q5OkLYcMhLl8AOl8!p5g+h~);=y0mg*MeRwblRC&EOefGwnp$R!0UyF z<*`ZdY~U_ptn0Ye+b)=E(%pjR0$(pU0X+8#X8%&>93d{(j9(KvZ8pCs_zK`36C+=| z-*}x^)`}kzBVVsTw?Ro7e+WE;82l`saYAQ(r4u7=D)gBzJdc20Ni6Hk8lkg2vwzSbQ_@KZ0&U z1pgWMcw+FgPSb@x3i+K(jJT}R3gO{7A*U0|n!i!#ET2xntW$Ni5qMaq*9t%JzX@iY zK5pR`B<}Gj&mR-Zy~9yr=zI#~(^xU_rNHsT;5h~4QGyo$XA#S|moN0?psTZu!2dLG zh0qTJ^Bo)W{v7Z&;wb=dyM=xb`dmTGN`bpp=wCt}ZzGGuhpXQq8!@H4>Q7tA%q z?*#u8^r3@wy7wXa6v5bQ;Vclm6zSFq=JKFbHr%*9Af;SOBU z1U$^5A8KZonjEc^?>U%>Tk!E9IW3jQ&!?+Jb%*FOtpx?Dq;bw#pZ457{t3y%M*yE?;a0(G zL1()p54w*&`w4goFxL?5gJ_x#*CoVUQ{5_fHSq0%Yk|2YFl&Y{3ud~HS@@9P*Fb;P z!s>nyYXi=G>g*?Ax?czn+wxl${*BgVKcmmHf+^=k3x8KI*Hk~T@XrKa1Nxg5en;?4 zp#QYf@pGhMDPO}Z(9nXWqX0zAaW3!VI2gOYy+=vjift|$=9wta%&L}1R-I`4dxo%u6bgnp6U=hpI+*fb z0l&Iu2WI`=Ep%da{|@>v@INSY;x7vx4g7?K)jdA=neMlQ&U9Z8%yeHN=HLl;L@?*c zH!b{*;8f7L4mSKQ=2Bwv(<-gODT2w*^|9es&jf^js>KhZFN%l!>a5`r0C1(G1GCL9 zvhY&D-v@n_h1Uq)hkSAEYu3hXf;n%iX9$oh!A>f`-s_sGr>R4!ixkKf?jFi(*z#^ozMFy zhqzYo)4*q0SUsNs57&NMg-*Of@XNrwZ=sysz_g!9d=>ERg0BI-Tks9Q4_Np?!8d`< zXYq_n{J7xTfxjjAF<|u!2Ru&!qiCJD5dOfi+|510NMgjH3*l$h%vgeW>(YE;Src+z zG51!*#F*dd7T{;*_vOSGZ|PR!XU4s1VlHgpn($+Lqie^{)af>2Dh1b#AM$`8>U`%JM6q|J9aMa&3V;Y26ol*?&UQ+{3DJ?QnUiHDr*_T`<2(_eJTHYa6t zx8D;w10QwD@0r$}^7f$a0Y}pY{=~`ZPI!B8cW66L8ZmJ* zRour1=Hs-m>h6Tp6t{R{TrexcaVy#bIKwPyQulGrA;-<{dcOT|cyO?I;(R9qS{Fm> z;(*b5QKr*A8CuUjFym-__eI@f->hgk;QZ8z_TvX{9di1CBvU>C*gbIO41PFmac1{5 zp*=skt2lfpm=(gz2DCdcP1QxQl>y$sB;{2h%SV=Ll${$F2o8|3% z(|slpClZCW-*Jfk#8=P})cIvRzsA=(V{2Bp*hzVN_`q8oYU%7rIx-EVi`I*_AA9~+ zUvVeAed@sRF5@Qbx%Mq*&wwLYVT>J*UV?H%+uU=>Ta$+KTq`Fr>>Li21pO3JV^Yv3 zEXC@UHgLTvzd&NhIaut+#_Pfu{ZgQQe`lPmM;O;__8BH);Wh3QfKLj4CMukQqR%sV znBf>ZV$MKeiOLx$42+JW(u|J7vo4gD{hfhQ4Jk3{95c@BPZ@{rwDF(ZY%GrYLtwjr zKJy}|C;cFw_u|b)AYNVc$Q;y1Yba5H+_emMSrA%ypn&sBkPj*irD!K9B9U*zBp|ru zM!@(uei)H(C;kl3B7$dsUeUjdzln$NXJ9A(=&!ZAf;;%!Lk<7Ia}-_UbC-cdPYc2G zh~qVVXAN+I#@*yk)OZ(hipIN%Q#Ixpg3d6FFC-qR@g62i8=1(;MZ_VEFJ@X98t)~Z zr153U&=igL5oc?BIdQ(mS5SVD##a*4lq=+4MLbjEYbdi=<7kQos9y)eGx2F&j=&o^D9eYD9cBYNFNX&#S&!a@>QI#^J>6c^cA2t4tlFG&-FVRhIOQ;U8vZtTvsm z)~Alky9UV{9{_0?hofb@XtYu?Sk^{{F8?dtX^5)J|4K&H<$v`g${xo*?h*d2#?S59 z?6(OG&wI;}sO zv_fQLmR^NOkedpBa0E@ z@s@pH6wrj;j5p53YC2@(GEL9f<_0ZWe-<>X(VQy{*)o_KbUJ(+ntu3l18E6z`EAD6 zuPsF@z~^1j2lPo^5{4A5Y^5Pv%KD^rKJ-!vI-C2-iWi>12c)iZsE?>V$T$S^a#m6Gn}&(dN0t6VqW6*B02uB}assyTNv|Y*_G1^9&+A36 z01aamoD6_KrkFTE(Q$gAVbih zjSU@n%2E$a_T+7-tD`Tcp`~$pVO~-0jNIvY&8>AUTVT<|@o%w}QOC}%dTav8K@^x@ zMA72jyDwvmPvI_bZ1JS!KZ8ZU3F#BUxTOfd{2(ug78jTo9GDwr-!I!8Hh%V+aCmlp z!R+*C8;w7A+~MO2N_N1mVsLnFbur?*!G`;T<8y=ci)LL|k+&o#JRw{%VR3r?)QuB! zCgla|L&343f++b1U3c+>nc>+bv)1M>nGjyHVeN!)`kXl$uRW1Qq(rBj)uCezKWiD%F++)3UL>cXG2FM(bkf(xl`ZJ4l7?sm`CtX zFSU8eq@hjcK%y{2n~zfoG{A@nmG-Uc{i7^b<<-N+XhTP5p4|gH(}T^O9n`X;VY9Ps zQ*)OtkA;P$EMSL$rd%9d$-4Gc9kW(yRpwbCDp&ugJeZpFl<`cpX**b!K|a5%Jsvh8 z&pH(I6jfQiO)bNx?Ano!)va4$lq|N}EwKFM*M4I&?9j1cII1Bk9ipQApR}52`m1hV zewT%5wyuR`UU!bF4U3D7uU@rGUEta@6@{v8fO-s~m%TaC-sLqCiNr2O*$$g0TB`J6 zY3&#{NLTemA)=&e#{VNGsr=z6w+lu%b9;{vmi;Aa>^HiY0FJiIn>Wz*5pH_YCo;uW zTpx1!sXWbD|F30%N#88G78x5z}A{ZHb`f4zF_d(u0xQ6jd!;fKH0NC;}@D$3F z^JwrIdBz5liQffze>OruG_WN`-t&-Gh43(bWAQWc&I2Fg=Y^jmhwp;?Fx+xM{fOD4 z1XU?}Uc|SbKv6yl-Eu+w_}HTa^^;l4&lcvppgv>PBLvkhd(R8`6ZF}BAn{qi5WEXH zNrL%IUL$fbl zVQEAxgX4ssHDh?t^gTJlfaB2j>C||Gbm_Q0F!UM3B8Pq7&{q?4@`LNb&(OCKCy}bh z9z(w$e*Lmaf6Hj-M@Z-356AtthR*L7&36u*@8j42;K<7F7EZ-?ev`@8N_q$b z0*l6c^AILxM&a`CBMCqw7Gg6{zRa=~vx{#}Ca#PvzRPviQ6U{=#l1kc03_DjJ_aivbw zhx6=*g8v50`I&S!iQ$5|r#UQm5cJ6r%q#cnlK%q8S923E-3p<<4Sc5H`@z#J_*T%j z24sV1FU_^WcA1 z@H^oDi(u|`j)$LdzYClunCppgf}g^5s^GIhKSA)1AhX0^(3c42-sm-gx&CMn%s#PA zF!$H8O)y=q0S*Xe9o;XO^{Ku~m%jEx(%F#V)VJk`dp7Vdh0YgAzay4Dh{tP+zXtSV z!FL0zZ+pN)e)G-w)4=LGAE8etKjaU?mHo%`-!nxH^Ij`FjleBJcaayhCmL}n?;N3L zgRZ_a2Yn~_FD703{sG}x3!b}(!IOaN=Y`Js{2^lS7b5OAg{KJk+r%=Ry&?QhfS-FQ zjhsIU{nwy#A20bsh&vEbq{x*+%Mw zpW|zX@XrLD?S^tlzfkCFLBB-k)Tf6S`YgfqcH%)G!hMAp^4VS<6CRe&GeZ9|=VvgTzE5vlU_z@4mRgH~; z8J~2fRbt_C!OVMwh1EC-e(F#|y0qH{!RffRSh!s<^R>gmY)_0!z4lo662WXI97o7Q ze4}9M%l<(6FkJ5w%rbmXFw^?7V3xtt7Jg1J(`A2PAIZn{r-J9<`nrYR6wE&UTfv;K z{~-7fuIxXQ69SGGOqnSbR&yVCSoX&YotXDTjLUS{--w%l^9An%=KMrD@u`CE2cB=? z#exrk&OT}SDeo~2-YxiH(Al>sb0qNfg6{f6-i!m^5R6+#JujfY6TcvImeuzy{A0mv4;&*XX9VzX1v4)Dycq{* z-;|j2L<^?~UI)6ej{zRyBP!L2QbS=?Vl3NbqRT9gU&ICnE6us zr+^tZ0YCGNZYnYQJ00uW^!E@k+9Vy z$7saVU+ExevClKzXK+>epdHe2oT3g7;~FAHd!XZ3KukGGPthlvbl_@SmCndJ9mg6| zK5r09-qrd_bTfUE{Ayh!I@}>`kMzf^w)Yyb$j2?qhW6&RNHdQp!S}Vt{I+&R(ex8T zbjSX-7AFixeOp_A7WMl>YeO({m>6>Ic+v@7iJK<4i~lzExrg_cKHHRqFF-SzveJv4 zZ|<0ezXSO#W$FX%aaWES6rY+lcx7(?%`yIV`iE`aUz)f5aOhpP8h3Qm?WuM|cFOkVcN`9V&Ru}d zcJe0A3F4dH&a8|>=oE7ij&BHd#ARIQOamn^b55|nd-%i>r=`96VBX~O9XzDjoHY?2 zaUl=53&=d*NohCV@7|dB8I%FP17+%KLxU3bmp-+HDXN%zl8b&3yWZ!UTu=G)Kn z2mJWRw%;FO>^q;NW~s%hWX@;F>~>i)gEODnN%dv^QUP$1!{4oXW) zjE^h6G;Z^nn&YEMcb3BoV${*X4wa8(E1LgmhGH|zn7ic(1hAfeL7=T>NhjyrlOux8=I0d zoL#Zh;!rT~&iS#_Iu(1*c6|Bl==Pam+Cp&jQPyT`3mFXR*M-`Ec+?yIqbZ%iqbcpI ztyI;jO`B!C%v3GbO?YZEOVZg@+B8kRN~bKeB(%X-zcvkjDXVl>N_+M0y@_;2mCm6)aNuh{O|*(dl$+T4~}w6l_oz)S$0)% zXjdRBT&3c(7=rfpIzAfCJ*Tiq(sK0}B>TwJ22dh#fK7&nYKwSJ(zMfD_QAr9{&IE*ltFZc$xK89PFt|?bj^Pq1 zvp4GS(gS^m&$~>AFFw?F_);$62Qr%@<5yxOpg&!YucGRLS5ZN|3XDHZslkru#GlS2 zQ#4*p@u?cG;S<1NqTkwY!!sZ*VLHw2_g5APCrv|WGCu`zSFQt$i=*{`6fT(uiFWR3 zhv8ExxNB~p0vfOF2GrWQXAsA0`nnZhPt^EK@@q}q^%SEubtB{-hSaFBsynxuX$t%m zU|bvt7_;$Ue=3)j!Nee*&u(nk?gXaMuhy_mpfDH$Jx$RI6@3T;-7_Isk7I66Cpgrz zdQepk5HbuOheuo$0lwkt6YFZ1&y(E92u5}5JYMxUz;QVks8rpan;?;EWrvB6a+kZf z8RRnq$DPl=9O~Sj$77i=8jWVKdk*^q9UH9M!>1e=Oc}u1q8p17y5Q|r9^6AH{m!F$ zbi6fJ?`*0Na{@i?US=%B_mgxSAKbmOaS0o5`rlO8L>8l=xqCDCWJ|c1Z7pnHZ(;i` zJEpLGB1|&*p2;K!ZnuY~<#ZpDkkRY z98cg!y#;eImx&^>u_76^Ln+(tFF+iyD0%q zLdi&%$v^J3-Mqk!L*w*4H1X|TXHu9<2KRdA!#z`%U{6#DrX!>UJxQ0~H+yIJo2Mc4 zlQaBH$#8Uo`oXn!#3{N#QJs)!R0wz6-*ET$RNb)P_iDjKX0=i;_Eze}cWl|QjaA8g zF}7N{(^?DsZf}9#or%zXbEyP=*RED}LX0{binD+mH`z5s&W`W)ybF1H6iIeXtqxQD zGEI#ejHJ@z8f+SU?g%x6WJmSN5$+HAP%Oukpz66JT+vKa8Skl5O|d=S#el9=8Skl* zt456RZZ|mSO!z_Vb+oD(o~o)^IKJ+JFs*L3Qa2|`-E23u40R?)Af}F$+eapI-LFDw zY|Q9P=Eh{Q$jf9VWV=0lkEb(H^Evzd z1RMqR@C|S#v3OuJFnU)^QuMBvWC)NwELb<_%{7BK z&kpwYz$_f9H_RO8?{~q3>xb)5yu9@#4Uy3J$)og67TIz#6ikDmV*En27iO496fd^M zC=v8RhY_-&MnaD_DMsy$8L4-tTt9bI0b1NBYvapkdq2xU*@}Z5E!@U}x7TKK5%=Vb z^>wFroS#v8$Lozcj5k44N?r(0$lgE0(6HIjldgC4OtcyB5QJNRFq1)JCk>l}FbVnM zn9yX)i~LV9JB46xM(-WU^0I`2jmgq9iHy=hgS1kPr)oJi0k`FnVJX@?t6Gq7zUwX= z%2)+qXW`JiWW0^?5>B2xYNjf}>3Y*uQFI8~wm55(Nc83v!w|jK=1fC2S8GFyPg-tZ z-y*HODr8u3n_)LsOQ{LYN!TWX|HX?l()pUBdm0S2jTpLsW|^_r`=q%2rS4@Sg915-PpS_Zwj3Mc3jAL$1lZQ98 zwdUpK=GC^fb*V$L|GtfAT9EpZ&U!7S_M~C|2|nB3(%z-Fq(yB}L)P_nq3!Jr2*zyy zV*{ZyqTg^S64rC7=7T>Axarxmmy9Wy-86?A&Jsciv($B_`a3JT=z^#nV=p@H>>PX& z<9d6@p6x~X563MW8=91pzcw!i+u`m#KNlO>e(K}`-+%sdz&}*@j`LRo|6JjF&hG+# z*~tw@$$rmuhn=!)>LkSc($1OSc*&)duk5@vxSc}3qr4CAWaQ`F+@`G=V-FVu+qwb) zOTnvMmwW0$lXD8s$jg~t8r-p(d0Wmu75tN3RKQCM|D29Lfdpngkf3(+O&|;xRb_0R zh?L*m`Bfx!lS=k?JDHd3+}vvr@Ve6BKIo8Fv19^%IpK+NfMM~8V-I%g|NGCWkZ$oq)d_CaKp2e!1`1qFSknc#0 z{0lUF{&MWCTe!5Uys~oD@+z;{=|j27Do=}4mPSgKEv>3tR#8!23C_r>mE}0kJ~m+K zvZ_c)X=(Y&l?&%qcnqgkF03k#EU#QvRbEz#f!f9ah+s>ej?8aFuqsBLQYR=cRq{eN=%mtPs&Sl4Oo z(QD>Gd3b<{wiKyav%Fl_o8pp}_2yVM_2Qn}yKv0gmbxxoh3(iE3WHe&KYy_``STRwrmwzend5E<|fiy@069#MZ+#Fk1Sitwy$S` z$d=ZYwz`cbU*(l0E73!o!oWCh;e0i!wYRm@bToHuw{|!S^O9v{yWEfEy5dl@ps!A&KI9yh4a!{c5>3U5{`j&~Ya7j5btoov) zrSS4)6(tyv*Z4bU{We|Ts2s|0tzMMh5Sw4Q9|6tny>N z8OH0Q_}TJag2&v2Bgm8SkcCPAKIE}XS$=$9W90n^*p}CYhO-Y`vb5okEX|}K{L+CcozhN88*d|H$=ji-((20{J}TY6#iVQG@jMM%zyA2{ z75$Kea^!miG~w+|Z1f|%qGh@n_?dM1O}vp! z_9syJ?F8aX*e?MX$GEr3wS8{N|*`Mh3qLQ{U&4L0tEI$GH%g`tfO>k;i>sjAPp7V0_5_1P5?&4C8M~4Bzt; zKiwYXqzt^$>7(8#(;_=><_b`uf&Jgwxt}ed**bwFCZV2B6wHZl| z(AS=4FAyN8MgpG_bkNu=7gR08Cj`Cc*_sxNnt>>fE#7y*s2PCaIZme^tV-{K82^pp z@4$5k>gOKub6qe5_45_+^TIm)R*e5;@pC*Kg8Jk9Y8?4LG#*^(Pm>1BC-*}@35&m4 z`G+`p;^$a11ob&r{FTZ-!dWJM=6ndhVl($JdIJT_cE#QbtJS-;Qa23d#>V&SyBTk_ zF9B-tE?BMXBY4~s=_r^U>1xB}1Sfi{_z7yOxDo2z2N{Cg{P>Shtb1j7s3I&ThILaE zo$`q>DW4dfa&u=*ZHs@us&A2uQ0sAnAw@k%@E>5HoH=Z!aqxm;9gdN1)kwekm`s0G zNt+yWN%$GeHHP75G=tXyr*Q&+tHv*FDv=&L%~|Xmiwr-&mp*D>;a z_yM$yR9OVsX^j?sT+|3*WZFDjs~I{+Ewg`#`HiChxDbB&b6>p{H1_e;I!}EQCwq{j z92SsY=oMDnCX3!e%mRbki=W}&N1Q^c?5h_!_rq`Y4!uPz^xqJ3fekknV*Y{STuz?f24rt4@GRh2LJy&Q%LK0gUM%>epnmhmxIAlYjnHq$wOTOu z+;L1N&uids6`TTmuHa;3WS8Jef%gf11aUcDGA_^7xm_^K^6UMjpmRUn*M$Bg@RNdH z1pl*w@x_SV=MVlffWIen7Tr$-pNQ+v1z!f;eku5LT;CD=SI~bin0u=~5PT=};kZjZ zxpzD0YTSdgk_1;EUuq8V)=;&Tnf{{V2V;MXCC_l=Z8r}mlxzX6`5LjN&vmEaxV zSu2?5opC+GxcsKNOYoPF?he7%u$%?I4c)F2oB{eRg4aXNLBYp^{*Yjv4gXcaJe&JT z!9N52dBHpfS?vpj4&Opv-Vpk=;5jO|4ES$?*MeW|1qDCbg*xjFcr@aMkq74GT3m|+ zPeZz=2>uvyiUpsKxN6U5y5n39e3sBVa8-LaK|cpFcMJU%@TfC9Kz|hY7NNfb`F9Jx z0sP920C=d+6GC5z>vsh2g#NDx=7%@$3Z4b}K=`TuS-6G;^WJC*F?6S6{~-_0LYhmg zzOuyqgy1lE>V${qF|`W47=6bYKoaHw(`O#J!DJ#=Xx8CXYIg z1ah7M?K#ppkifByDgQKFe?bi0R)T(1a2N2$g0BbWz7)ot0-Q#SxbGo6WYP01Tq<}T z_*V(80$xunan-qQkV*Qv7SC0J8$rKA`0D~VyNGxYh;WYyo%*Xi|Iq(Cp#Ok$;6DJr zEc~2q)VXVt?)w%!9(6^T!0ncf3W?KBCXq)CH!NWlR3^DjKAxE9F1v%^&4_owSEc~)ywyWO~ zga0_-zYxp%fm@wOXSzd)kuGiDOccBTc#8163Vo&vy&3dU!IuIrAco8$$X_M=)UCmy zZ?^Dm!E?cXnZ>`~qJP%HUl+{&`#s^O%^GzMx~zHL6*}|sXJX`qW6_5~r~V^w^G^Dk zppPX+-hT%6!Evt>^TPf$l6Zus=Lr1} zyeAXOdV99ed1hjj;KzaK%$x!>6ov}QVj1bgY(vCn;TjgqdY>ei?JdW`1%k<6EEuZl zbs*%d1Lk~S<_oTqh&g1fw{WfC4qUqgk zu<)INTX21fSjv!Ny_pMsAUK5UD;7Q?_!eCMAedG5zTj(cRqHy8|HLlF1=lws&eUomz;3<&5 z(ZX$l-vE89h1Hr+%~s#E%K) zc>auq4+~~~zhhzbyjAq%yk+!!M=;x`I#U@u#2*NKCorFxl7BZaz7bRSLg0ZG9whiG z&_@Yo`71kCh)aDqN1Al`43d~>oor#=FOklBnlcM75Znzq*X2f^Re~=CUG1erT&7zs zbf(MsnEdm=(72WHuR=^Z=R$KY{DEL}XXhge;z z_K0AX+f&3MllQ3PVY$5^I0HBVzc@}ha4Z)yx0261j|ved;SU_gTr&^m6Jw5`V;f>$ zpex4D^aZwQ)83X7Q!%*J_?hyoCT4+)Gde&`Zyrx1<{vob-`w{pokcf_pq`{D{mBT& z>`~_qTtmd7GxJY;3$7|(qJK8&z?^qf9w`v62tVd)DXuENX!CSz2h3wNuEoTX$1=fe z+o~MU9_T9YW8N!qT}~`{uM*5U<+Ey24iRF>`(|Rv?*+t?M`gQ8@->8Gj^yPzV$uJv z#GR%ll+J(hxg`=&!eEZ-FTb@67>8mds{LHH_9vu2=MSJ3?LxF!k zI68DWxVkWD7YBx`^CwY0qntp>sO~`OsA9L_;K)(k?!^Z~qYk+lqa1hB<`oC0Am-#( zD-XVOP+K6%7`Hq1zyW8^{!(> zf+6vt1*ywIPo_-^qg{twoW&{pp5ccWspr(WP3m;Mb{meT&}?yU4f4sA*uin3I7EE!HNWxGxz z>>AmU4)l^lyHbCKy(?-|@{FU}DT;cJ?#U}NpK)QSD1=p9;uB7CX7{ecp&FcXn(j;* z7r@E0Pjn}|m$Eqnc;9B&9SU~u0dFua)P3BaKmOP)&M6PXPl|VT6;GSw1e0N(>2T?cmfc>_egrotA!k>a-+ml9;A$>{8I57iyz+y;00;7`(H?NG%WHc zIsK%Wfpn(e7s4+aKdvF^Fb(}v9j8d!o|7zhlnz`T@p-P(k8P&^jp6w!=lajB7#ibP zA%o*?b9F(aT&_(<$=SSI9Yxz-dKwh9rs${4${6mIDOZ@$@fYpd?`MvhK1M46uu&U=^GJRLy8VHEsDDRD&X!~IX%lcEtR-k|EWq5A8pzyFO! zqz*zpZ9<)h+<%&M)E$Kre=<#kAl~eCCSH_g!Zo)#PpAxEnkfTIOEjNg7{9{ikTWk2qW7 z%Zc+fzJl_LG`^B}hQ?PB&(!#uZs1~#uO%+g_&Vab8edObs__lPWg6euiL@4Id=qhn z#y1l$*Z3CVDvkFOuh#fh>ab4Z1H{!DA0)2V_;%tZjqf0C(fF=z;C56buexUMz5${I z)}i#`;z&?cn&IC38Yn8Ndke(~-cPKn)a@bGRp)+&SXZ5^)YMhy-bT8vI#;QztIky_ z>#B2=$|0R_g?s&gc$3(T>?xA)9nH{ZidNUXlgjbQy5m0Na?o+}5h+y&Y||3@xDH+F zg+A`8P<7)e7R&gCaEN8GOx=9KV%cuchglZO)XM<3o{5aW&+Rz~1|nw~%Si|GHd=xY z^$}d{2lJ(%ime)zj?J5SiTzO@!R!3sai}*Hdx@Vmr!t8>vyb4_elTAWs@UuO*z^40 z9Z|u@Gr)b$b@_U5BsXrtL#)gDIaQ*gnsEbm*u8NG@ z-%Gl~RqLgKWlByPM(PZD5n=>QQYCb}Yb0f=@R6z}CbQksvH09E?rOxJV*FFrtH3Na ze?xO8xzn~P|3s49EKdQpEw|^Wr$APW0@*PN?uy1&p5)0G;uh!ZiM4g~#$UExK$~!b?>K-7|AYa+m3r4w$R_%az|v(7#e6v|KIn2tlU7mcl?m6GZqL0*CeNiYWm4)! z=eg9=T9-+0zsWNmh~bkH)KF{+=ew(de{@&oyH##}zPpltDnY2scUR!D z0;=M&+$g!gQ!=EKT#9K$C%Pa;$ptY=E{IXGxBpWqsa0F4RjZ`7EBGf$$;(|XB#UkN`{YijY%uZ4i~%xVNn?PFX3}70Oc@}f@vcgD)$yy7*Qh*AFb2V( z!Ya(^VPh#Q-M1|^(KF1|+g4bHUuKj)$=8}*W0g#0VzO;Gj5fih7$av>wUM(dn?Zfk zMayQ{mPk}w4!oA#w_MAM&^&F!jq&m=18xPHECpX^jFL?^M#+jq3QGx6PVh49*(f_P zn#5)=?!Rtys4o*#HmwhfQPIJxjV=lJkR!3X&qMBe2YInf>}a7Y40#YuDJ53?7i^hf z{MW8;Q%Nbf14-F z@H4FLJ?JHaZH=v;06*qR0~vGWZN}%SFO8HhI>2YBGBDs2Sn)iv8@2gUF}uzx6Nyq+ zS<(6lFzl{#nt_aYnitbh?S^30TB5VXIXR;p%YMT+kO@bVEVXPgCWdA8?Gs7I=mCF! zHTtP~wPmS%%=(EbyDG0Je7T=qb=NuFL}S8!D>c+8D}m~^l_BKy+ojL@KcAuga^V%p zF@^}c#aIo`>OfYB^uuh*BRbd{4s3GYtX?<24&myCL7o_seT2mMd#}8Ir+Zpo)Ez0I zdgy@b7ln>VRk@H{xw8IOb9;jmJm?NR6SIbC^EP3J}jxTT@h(bks`p}v8nuBMKL&L*d(z8=@k=GG07O$QqO2Q4wRBsJXSX zYaxsSA^;~!7btGpIqPiSsMP)cE$u~F+G9C4sM0c)=}f6MZiv)l=xmU}M#1CIZDqqs z)gg`Xv5;9CsyTq6Yny3Tu&<~5IszMA)cM=Wo|jZH1=Q90Hu#VVy!gw;#kL1o(%zMM zI+ChZM@r_>u47$C!v<*t8zXgH9WAu<$fO)?(lOFekJhtUX^JMTugn4w6 z>%$HpWvRl3&BG>`uL?!=1Sx*C6`ToFu8yo)T2@|Z^ec!`&}38h%jg~bgw9du9b$-iBxl*ZH;urOczNTIy>Vtht#y}dtxUsVZh6N)WMofB^i9%CXY&%{xk*GJ-*KTQSNwp)p?xsKR>u#Vd7+6HSO|wP_rl3{P1GCfA)>5zMM2bC0 zOGU+ay-JN0oeiz^x|@1!zpkkUb<`PY;VjW8SILrTX<{N#_ZoIJ3Gc%yB2!;dvJ$3f zRjXfFzI5fnrSl{CQN99IxYBo|qVvrh$4NuvM;lEfW1U#~RN_j?$|@t&!SqIxSzUKB zo_H-Os(bhiN)1VBrsKfp*O7E%ql&S{i2Ti6wQ`M-TF^(A+5#cd3(>DR5TRGKcQyI! zetWjk5@Vu~xv+GoSoO@y?xgBRIykzWBi#d8l^Gw?eOC?4ZL;JsXsP~QR;i3QlC-j_ zq-xbl*ycqsF_J7E^c3FJQPbMlh_!}9FjgN?(wM~a{no=up)`Nx!?vPU7BZcv+K~}n zTg2AAgR$nJL8yslhK&6jhINZrUS6&j4biouTC1uV-GS7Cr@a9dPt|;=nxUfH>{ zT9{^JJIht|Xw9mMl|-}Z5#E0>lbGt81>VA&k0pv?9^|Ood(gu85#zCma{cj5MkwAX zACr%7pJUTC&y|<}UXUus7n25)aCDB|YcoUW48yBR^{~>GR{>gI^8P>ez6HLj;@W%f zw|06 z`Yu$ozHYVFTBX)nYj3r!R$JRzYwP#_uUWItoSj3}R`2)S-~Hy7v-f}2tXZ>W&Fq=k zlRYz>Ky&3~R>bAe23Fo#z^=Sz_>;-osBv%;7p;iPqaCcgoxrZVGvQAr?`n;Mo4Dx} zae4R%Adts>SzLLX4@@TSZjFPRxY~-iJlf6b_Y$xx?-uxz$@_`M!A;zninu&30kZP` z4(!U?2Y)ho?`a&|#BHvK%gY1B${PYKfN14$ZLllvU4-o- zL7kOR9+J1a!t*Wz&vg9y<7eg7h2-(P`h5tu&dOUHl2=h_>M!MS-qy-%56N4O2XC$w zrhN2rEvS`E{~|{o?PKL}9Wwrc=X@vR@foqs%HRjC7Bg(QBX21DR^BZkd9Op>omQCg z(aWz>U3txrR}4Dydk}tB-uFZDb|QfKTW4i_FC_0m$YVXGyy5s+c|Q-yBx(M*~8?$=l_~8>xKMHw*Q{Z72D0<24kZ&gwS;d=MG5 zr{t3_ibH%u!Dr(z=oX~A0(aVFG=4T8hlbMKjz*7qSZ8G{)O0PKeA7X*&hqj6MT;4h zGsp8bLt&;n4nLdjNg;Xrkm!vxggWMtZh1)F_A1E2Iz!4k1V1Z}XVTg@jJIY0_SFH+ zIvaLoi0{b3X5W)A-)W(A_u=0{J!o*5E243ndmEMR8Y>KUo35-Kcj(hkz+O)9 zQy#zlvhsF?jz)7X2Iy3nx==iZ|e#Alu@ zJswgi)HfZgPmMGD9C^fDDxOG=Q|L^%`-C!nmbc0TYq(P&pmQ(M(-Xp-2Tj6rr4J*E zkk^0$o`O+e}{7fb8K6-Cc z_8f87d#}=HJP;_=eE1vCdhboj7BcQqJ&t1I?qAufG#;Dj%h#R(DOH}>z>9D`adDSw zKEewKsrUx(mx0gJ`xK`Fvvh`r{8K~z8ujykyxfcQgitug8`e?Y*&+Ys>feX30@Rx8 z)jwDKgT1@ef0X!#dOY-;?hgD4(9{1^{iIR;8|oh{;ln-luXIuT3UK9~B6P>&SHSYq zS53P$>OXn=cHhdB?SS*7{Ktknfn(=MdecI0*MVR=MHQcWb^losu-VxR=j~F$oL5Ta z?LGfwCeLkfT(v~Ht4Lx#I6j#9^owbgWNyuT-{#md;kq(tGjCSGyR8h541< ziy!?IT`zv6_u|(_z8AmJXF=AmC?#U-f}hoy?d7l{Qqj-hnaF474#(#?f3m?xIP`fA zz1E>Gcjzk}`Y8_mOTc;B^sooA>9V&-hG}O@-wvG5!2;ZE_*psZ3G%da8|q+J{sPeL zo)3$NDHyI6KeO&g_v@fh;U2)x?s2h)m<0>Rw#e>H@f0z}#&q&tHp*bOFP1))Sm{*V z?s3uJ(3cal(BOLTv-}&0X&AUG@w4=6h*h6E9r``Qny=>^`U}L`UB2tk-y`mi3!J>e zTK!Q*c2AWB#7bX8tb3iwJD}3rNT=c9E_C>}6Uzb>j4vJfEyUWLzU0tfAwJm1`M{xb zP{#e@=nf**bfd%=yV9|&Big0XWRXL#Cm&>R>_?2x++`^hG1ovb85BxxkMp zJ_Gk3D83SRsWagJEA-i~^apW&Me#$>`M(sm0{>3&Wib996u*YJQm-KAmq_bFr8gii z1+WF})(kxdD?ST)4p)2{=*5c1LjEC&2O+M^i6HJu$UjW!qd-4OF`pl{MU;OV=*K9& z1Ncnwcb4MykjH&& z$$uw!WdAYXo58bE>D-@^@2}+9h_qz?G2qvs0^cu5|2pz`wc_uB=LW^c!alN}7>CE2lb~lRe&nBlw3tug%V3jXieG_D z*((h6e&Cs+^qI)Zk&3?x{)LLU2bk;w2LAcLvezy!?b)F`v?2F&W?Hn{1&aTOw60Lh zJvhIrn0r=zOYv85|Bm7Ur2DYqyI{ko6?4zdpDNx5J%6ouHSF+5#fRYjq2dhaz_y0^ zj0gW9#cxCAD8<}UP4?Oa&$F<@Or@U(`Vz&{aX(J+lgRHWivNmqHz*ze`CAo3ymyJ> z+n~dj6i)=cNip|K}=_SgLt zlV>nOrA58rwjKh z71PdlD1Hw5K0r)nxF;Q+9}|x;^!-Zz2ki5j(u)xE2gP%Meb|Nk#{j1jL;qV)SM!v< z9(2}4(*FuN+hX!R0s1&%=s=T}5RU_Z<1;Thzz%a2Gu;}8r_SLyju>({hFz(67Vuid z9K-e~<{q4~_b>Q4hTW$06~LD&<``D?0R~SOFxyt@{|5BGgILE!dzDVxK1qyx6@g#& z0S2A}{Bz}b7j@$e<@qA$zgIf<#(SSw$5ee_Yo* zzD10DEkJ(ncQD5s)c^0`|B=!!2hXpS&a)5Rbm)B9qfPch=QPFVro+C(IyN4mbe7Q} z#E{RvqD<+u+YF`81^q~+({4v8y#@58N~ax;SNb;4V@jtTPEk7hfevEmFc|uOQ+bX7 z{Vrl1pYKyTb$gl^y1fjZ=akMe`nl3S1pPIovy6VPbneCeuF|R7he|&dID&yZ?L*!A z5ko%P{{F6Ce%(w_nSO2zL0-=KIH;@+(M zv%vptrL%v(N9mWs&JQd7a?oE?I>!sYA|44P;oes|m(z3G5_^8fFkV=2m}8Kb;xj<+RQxRLyiGCNfU6Y`2A$5f`ScRE;SSXT+)pv*@ro4V zX8y+m{u#g}N+&K?O#V5Fag%*-DgR2~V;uVNig)6^(!r|~Qx5wKtLG-g&)~kr!RIUH zSYo?_FH`&i?o5Mnh<7P|1^2Hz_*;s{AYINmlb`s0#Z!QvP>h>-hthmKuXN^dzvA7v z|H8q)Qp|kGds7qyI{2BE*iW&Pg@gMkma-t$erL2|mSKs5%N6efoqdnxXa7e`{yO3! z066x4mVTmQR70=T;gLP45tsCC<#`DBbO)cUnCV{Z#AQEcb&&Td$oVFC*q_<{PWGA} zYW|OLkJ3?X&0f=>&jn_mXZyHk6|=8-(ZN4cO#a^x7XiSrKePRv%rl^$qWo0!8Ke_u zC_W09eV*;(WFKkpP^P?>DIVwW^Z%VTE%s-&znia^c3a}$I>mQ@&VJ7Fb1x3!&x79K zU~b(*`c0sp<=`!fZwCEB2g~~%_$goZjs~W?UU?}08xFozG39^9!SddRxRn1rr4v7@ znDW{0TK!*AO!>cb@UIn9{yPr-lVZyJz`^)ug~+6Q_TN_iK*bfHAMD^F#S1_m@8F4w zsn1l!)M2_}rps}Fl`rp=$nVLZ*D0O&7$@!u2XkJ7aVdv$F&4|aC+O6l;|VMOe8tp< z|1Tw-_%g-h=Qx9O4CB3<6xRXEnJ=Id-=TE2qxU-aLB-VhNe9cjD&npL{bi*Szp9uz za9qoDX`eS0GhZCXkiH0*b0U_9;~3)0LGSO-}&nYfIx}SIO7Zg)ZeiLG4?ov!W<-J(*#WOI- zLpcvRc&`)pF$eQ(Sn^X2$3}MS^r~VGSKe^&n~I~LbIfG~DyAHH ze^)sdIB_p=@a0ZiIZp@t%;T3GIULW~@!q!;!(`rl4(2>2dHB3=oM-v@4ou9k%ugN6 zEwwB^$AZLc!{vP+I0yIxr4w^9+Vb}$)_#X$M;mv9;^|-?>);8BD?p#@;Ax8Ifi80b zNSC-$>1+oUIGE>?F|7vB8yvh`G4swbtJP<%Vvaq!9n5p8$ise+=VK8wt*weFpJQAr z=PJc$3cRm4_>P`Xjr4yXw4I$ZsUYS9 z2)6(~I|our%m)N+IevERv4WTt6>bfFc0ABYOy%J=;%D26&BR(iFC>N?tfPFEJL?%S z)5%o40(S`my%|{Y3)}|GHl6%e;T|PM9ipRYh!JG+PGN8*_}O_Xv4h%YD(S%F7n?9a zxLNqoZlqTbtKH@*CVc@hm4aJ@A2I2*#A;8LF)`_4Ln;n;B7U?p=_`l{;FwQhj#1YT z>ln37@jl!;iPio+im8{_9AhfFGx1}&uPR>q^B@ z+^-?lvfHVc&q6J9wf1i!R-5+_tDP?(hCS)7Ay#{Six_sJdw^K&_B65D>J?(O&nk{> z)Fv`EQ#;&4y5{{gV$JU~_PLtJ8e+}YiNumv)qWD;`# zf7X!9lRd1_X60d35d8k@e|U!yf4JwBv)|eCaOLLRQ~OkGVZ3=GQ*tV!L0FKESrem)#rP72QnVAK|+kK36XH%+Be2xYDFUY*O(-pk52U zZFF0-q3zV|oh6;Zzh17Vy0H#`yBvC#iCysmJ`4@=#e6tl)kL0F7KKIUTT#~UOtY(OIgnleA=Sd|r!s6sBbZ?j9v&+G*3otouSXnMNM8!*ABn_XQcqtL_rSN|ftc}KJS_BP? zL=>b&P6toAiZh(0B_J6%X+{92wgYBHQp?`J9kW9AdXC_;<$9J}&y?#Kay?zHo8-#> z)nOERj**=@>jwBFnbbLJ0i|V%L`nuD-<-1XA`=Pmyhc96RX8K1gu&Vv|(wr zJ3vms;d91&w0JS5G~PP#@?TF9GcWCgyAZQ)o0oR-aHh}~YYEdD$;2r+B-lipZg2~6 zW?wG2N?T2wW$3NrC3R^xGyU`uevu_1>1Fb97H68g^huSx`NlJa9+Yj~R^0yz{`_Wu z4^^x5Y12>@jfm-lif0fT#nNX{LZ;z4f;b1(VP+&_2c=iv6^NL>0KqDvW-XwKSV*iQ z7BfFNMqK??;9P@GVAqmoa3krQl0k39(oaZkcQeyu;5qFCU31U-KN zlc6)>_*!bjE%=pg(RcH-F{t(3E zn*q!6ahf%(!#u?8B#;Up-YL7Z;rGl_kJX9-WS2$;;;I+`$J z9_tqCfK!)ADwGO6MtGL6YK%1zGQY*gh?SvGbQLLm#xM#ih9Qn)w5&!}3lVFgW}Qfx ztQaOOK1QY=$~u{aZjzL^*-;jBA)o5P9$XAt_DGhtPz@pDFqUgrb5Vb55G(U;mY6)R zCRXO-T%cecU?Mzgd=nbacr3|6d8$N@%pZOyU?l?UgSZv2bPb#2KW7 z`Ub8d#xG}KoBSODqL+CtO~Ha6Yrg*OE3NQ9+c6VuZ)&lZoqa@hY9`vZet<}Wx0*r} z_yBJ#DpSTlvqz%PG9!L=Hg|yJmWzGHA>^RGNHlSy%6nLK9V=>?Eu~=ZY>MS%{b@sPgWR4w@ut_GSJZv)DLXvxC`uV=y zGt=4Bad2EfO<{tFA@RK{6L!u_U>at(ilki*GBGrjgG<$Z+?f+Cld~_S*)-Ee#RFUF z9XUicq{LQ{jvDw^OX8lABjetKAam3hj|Em7rye?u{0 zl7`Z@r0@48;apejF12tCuVv?@xI1e77~SN8^4Uc*XCG9&`n1)vSIS7JLokl=d zvNX21dhyb_z3nFy4u3p#D9F>hrR<$** zb}bVK>y=e8#JV1vm0%B*gczUIQxz}oB}*G(m5Z>m2(~4$4VkU#E9H@LwrvR(!0`FP zb~D%xO&+HnY-5#dkEt~suxm%BK9TZ7m`Ag{qo;+X5R?XC94G< zwV*}`H#TI$!4v5EOd?i7jCHntS#2#gW<#xA*Rs*XM~#%)=*_RHnh_mWSy$E3-r3mH zQwFqQa=_jH;28jE_E zR4F$+@wmZ>yG2betGc_Wgxeq|%xx1L@&Q}L1M!FzLxFCOW?E3$mZf*YYT`Bj58IrD z^AH^8^39WOs<{PMhS3$_N33%%2jm+I9*(d+iVt&3z0#FgE70leOF)T(N#8uuOlMLb z^6dmZv9hw?bhYvuh6nEAK$U=QS386XGvufR;#I`T0u z*4a4kf?zSj-bK2X;m&mP@w4e>Bfcx|ffT$(a-vL~4NC`b<(*fIwJ~_%C;}f|h68zn zz-Qx71lMD%g-q*g*dZZ4ey_RQGU0CdMgzF%wqah!=7X=hHXk!W^1c9htOwRvd54DN zeE@k4U@F3o_20_lH}5tMW&8~CrW2^MVKo4*ezgNOP#=eOE23%|(X3!bN-^LJMF^qNu=+;?2)(Knp7`768TNtR$ zLf##gx&8rh z4^V0_(PPN}{vHh@W3Y~XyT<})^xqu{|8B@1 z-ecj-Q20O8&%UF8^}`*6l{R6j32%&cRBK)(L+_UEV6OKli=jzpB|`7n$=`TfWfI<# zKVqCN1H)j{Zq;vEiJ`U^kP`;;0o9!;NJu^yvJ0?p)Od%p&?ci}e@jky=MKM_%VbzD zBO7YMyLbGpA=zY!+8sN1|C2eL_)J3ZN*ioRtaA(D=w<-TdX}ei$u4G{$(R56nSHy4 z$$x}BFG{-n!_4#M0$V!!k$m}Iq4}06U%!)K|2J#|E^y27%a0Q2_gF;+OIjF1&@svU zVu8}`57Y6&7}7@@dMR$hFg~DT8=B8RxEc5js-2Rv*Xspfko%U=h7{&*qF z_f$47D@aG*P1lTHKKeBO4%28T`?Ml`L>4*fi0?GkQr=(~wA9-w;#Kb!7r#Ap)e(($w3O;JPy z|GD9RPGu|yOvm1Vbbfy=eX({IEJFKj6-`W@zo9Q?wu!k<)HKC!;XYe2W^T>;eem$R zDwYNL|A;p0IK_QIU!j<5uU9Mn5cf{StH6J@;`?F8a}{%~!zGIO4e}QiS3suN2Wc%u z#_v#i5%4{V2O{o6iaGY%r+5$KKcV;{=<|%?+adFL#bd$$lHw-Fd{r^q?%yc>2ju)o z@vYE}gDK{H4secQo_osvi1hcs!Z9uJ4d9uknEwo~R?M+jgJK`LH7h<3Jm)KZ2=vb@ z{yp$F6lXwx_BE7$8`64MG1t$^cXXg%3EE3a=XdDuE8Y+ObjV`db8s(E%(V!^6%T-% zLlkp8`)tMgAV=2DBkp?8mMNX_8WmHocEy{4H!0>=_I$;MK<4KaZ-72uR=geQ%GwIZ z=RcEVO$6{aVgFajgF7ATYlJTvbkK=(j@iZ%gZ~Omw;!f(#hYc{Keqk zK&;Q{`HBaF=Mu%mz}G1M;o#@`cKiI^s(2B2?pDm_<8j3&1HYu0W265fhMvW^zomFS z^8PNdKKmK)6K@9pK*igE#}ccaQAOshI2jA6NV= z?k_9O1kdjk^If(N(xd*@fj(3*Kes7Y+zkCI6_0?-Rf-P-mhZq3Hw!fRmK&J$`f=r9 zU4BFHp}4=VcmdMNM)@#Zwr6yvwSib=mQ5~?x^B|c!zN8XeCnZ-r$(cbCY4W_a@b@z;PUcmQ%ReC=u{J9 zMbad#z-JKM(JmbE)3q+D|ANvu0UiHbl6cWo zT^;RFyhcPhM33@6n~2%n>GETk+T9Z!y>U%Rw6hWaqo0I8g_D)fd`Ik6nJZ}2NKGAh^GUv&?g8X|cGTAwL_8V-*C;gR;@9ZJh*Z1x@*GO4Yef{2k zxt)3a&RLvW-oLzJ{@YEJIh`L2$=p0=WUf!%y%p3r_6mmCd%gb z*3{#=3Osb3)Qgn&UpHlNklN1u9{ko*o8~+-biujyTkcze^6a-U_4S<7CYxNJ+Hd3E zUN7k!yr0tNjPjM_8BkkLudFhmy8220Sa%NE70^H?u>D`AxaR%u7Gv7l@hL=B1x3^H{lR>34&z3=x$R!YbSh=^&!|`nm?!KRlEwbW3zO~>$t>lYfEdJF zeZeVmO_gheY_mAP`R8mBnkLtDxn{^UQ?7mFnkCn4x#q}~d-gzKx%QJQcly9sQ?3Pa zEtG42xek!)K)DW*>tMMKG3C-{;%$iBC(>sIUl1hZfZ3AJPpq$|Q}ZV5Aypx(xJQ!A z8qa;TdMBw|6IUR#Pb6#db^nZO+BU;Ab8m=Cr5wRMSRke97sNSBXcmgmo8wWBNG~R6gd=1S`3TeDJ;Lk`rcpUa_WXHPPxedx&ROU@OuTt)59o%7 zp7Y7)pUgK2FCX2Tz3oAQqIzUbIt?}DbO$PA)ddOaoC0VJG;P~*V)qrVuD(A zCK4B7*jOhr7CSb7C%3sXN}Xt}rCzIi9ve6RMC;X1n?$}W{W#fXNG3r27z>6@I@+U< zgt6|yq|L!^9bPhq)8jX@!$`UKOcSxirXnU-4b7*56D0-?W*jfP5^@1TmPkk)Xbi`c z58o=hTP3&kemU&M%hCQMMmz^WlKN2q6Hwtz4LQ{+YLf3t^memBr;{|FhvfSuP1X(} z{T8GjtAgi9$yfzhsnU>4q2C}1*4ye8u4j{t+CCm(@KR%=yUCVcLFG3?1yP>mb`Y#- zv_0Euo8RgA=PU)C&V+5g!T8j>#d`Clf^1!@p^^*g`k?4~Ov)B$u`Ffl$I)Bj(%f%o zKF>w0dXZ2k5{{NUi0y@!c`SgI3Hg*VQpU}?M289)1GDayOBAW}@`N11)^5ZZxUgbH zn*j!T8LY=?5oWUpKhM94HOc1!wvpoh;y&CA{1Tg3U43+`t#G?Q@^|h<%BF(f8udYe zGZp1_`*4_AyPG0XF0LK53<=O3gP(u@<%nKvy`x(+Y)mIW%EfEM2uDJDcCxIV3s^l- z;yFrdWj!A!osaeY{~g%IMs}?9|+0xyuIS`+4ZoQglsDE@po}dU=jn`;qCc z(&^)_(#f)`bg~my={U=B?1G$JuD_`%jIYwcS_iv)hCKZaxJU+z?<^zN+2ms-imvXV zx4b2WO*53eqWHxWk5!5q23;)Gk9Rat#ud7$fndMF65spPT39Ey+;%0&y$HXWZ1J;R#&gDB(bu|y` znmA{{&Bs`gbCQi_@BgjQV@Xo=y?`1IJ=#KM>FBRopYH_$zPG|sKRJ($>C68G#g~eI zJviyqSr!Vf`{)46?tArd7#RDLP~*`t&`fl`Y*!_;6t6o7bI1;~!AVFN_?Ji4haV0N zvK8k|l4d|kBFU*ezI=;jCCNS2>&^_)fG5ZCoE7)jIKJ#!Wqoq@y~YGTJigb|n-u7X zw#5zo7*9kHHN`6r^s_R2FDS#gpt1AC3#43mu|vl^rA*d4T6%GOIiTAm*MINAowifF zBYFM*i`KZVNf)kprDf(g^dw8+4zSLSU$L9@by$Hn(XOuR^#MOVVM6&~iCc5aDy%Yt z<^QGSZcr7m@mkr|vD&O%lapq9Hg$4gTLy+$ovj@}82@*7G{xdRySxNp82a*SD9#WY z5q7;BzO=_bP(Z^lkze4*hdS#T+dx5z{QrpI?`dt<)pr=QNHtD9++RPlXyL@7vJEMH zJU>_zx7YKJn2342IOP$~Uw`=#Dd`UBDbJscPb}g*FJa??;U}K9ux$41HLI_gz3A5U zv#%~ID|>wX`q|feSW9>0`h@tm`hMGjd1DurmCa6VEL}6B{K(^1AHTS0L|N*f;mo(HZU9_mW4*y4sEvv8g7QqC^Evbqvoi`8Z%A!bFsb@aTiZAa`0d>`t zb7Qr2OB<@IWSxjvg~_FYF|3$t!4cA8>6#_A%No47)pM5O=z1tl;aYFaG4D>;60d$~ zRZOByne5fqqF8Ec#CZA*A8WO&IMfgQ%&I0vnzyL3egR8JwF{j!9^`F_ENhbEv|%V7 zAkAW*v$$r`G)c&*JdhMDs|i*Z(u^w1t!TAKylQD(wXYC(0?LY#PAv&d2sPchf*R@YV5SI26rT%GDsV-qRI)Xb@qwUo%V zIUGFs*Ny)x7j8Kv&&B}FM%_}ZgqmA@ObzNSY8I$cjML{#qSsxc=sb{i-6peW!8(h-j%9unxPe zrEwj~)KMe8>@Xmj6`O1e*_lXdZ(1-c*TlbDxSVNyCtLp56l~|g6_b2C9JAEcJ31~| zwmANQE92@%uAaMC&5gX^I5e`(^8xv;a4{{dZvUOh$HZG!S=TTrR=b!6*Cnu8uMbxUfN%y&vqp7qaa`Dfzl{j%^ghJk|9%rR%}E$!$^Tx-m!P4F4R z+fJi?1Z&g(Q86ww~Rc-byhrTR+owib>eK*|ys47p2C3 zR`zl=u1e^?tL7c+*`{6#+Ud7sacxyqb$xveGwPUu?H()eq-m(Z%`I(>n_}zNai0TQ zNu_f}w}IYyl9a}x>bWcl9Y=71Cc64nt*bq|n6*2;CcOjSps@DhhXd$VcC265)FQ^B zG)j@W3{&DcURK)>e=LK=pL3Tk;|DA<2+({TTUXP77Ch)ra4`uB}9~vlN~FT%#9Ofd;h^vOV)Hcz8i@umC@h zXIHYf(HaST9oostxiVUEl$nIt-`*aZ7OVs|=ZHmoBPA$zY|svkcwTg+Mdb z1=HNGT5ZN}v?xU&!DUMt_w+=7Zmr;?S+#6QO@oz-FQ3pPi3-wPNYCUQ zJGX%~CnyGYeCna`(Tx0et`Y9w`w>?YPb!`2E*aKl*By{L3wYhoz#K(DeybuSoIS1k^QF(Ueg?zW*XC3Ein0AHUt$we-Z)a}+ zgE^g<4krB;2rkBb6n^-Rjqq{e|785!{BkXqodrTrAn!N_ll~0kF+Szt)jyDj|9B?I zn+|zyp6#5|CXIuexR)TW9EkGxk23O6K4-FA{n`$o-eGM%*b+z9q(; z`TYlelqXXgxGzZ6|4jnZSsDGoX2CFEH<^YaKRk82)n9Ew;4d0dNPoejGpB(E5mU_PiH z{~c@P-Kg^TjL|KF@|W_#P)B+E-5%n5ZS==mt2}ZH)&)_3ItzJs)b?(~e<`CVw_?z( z{qlhM%!3@beNgmQRtTsL40s4!`lqp0*%h9Oma@MRXwwvFIpZ9u((<`}<|6J=%~xCj zAr&nL`vW=@Cv?vbPBdpZ`R4!n3>KGaK5(*L*hd!u1mOii7{N!cRc5Mj_wTP&j>d%n zrJ`3vbq*ZMm0v%Syz;>N)IqCAzm-%WlPX~&8 z9_(6Ui8}%WxF~-4yug*>msccE#=m*&MBvzd+cm=EvFn150A{*T+>at%8#jKJmOl;` zxMuw9+Tk{0`NjnOH!$PQ!u?EQ`Igi3wvtEnzl?YSE^xc>BYy+#HxVoU_mqAf?vFY2 z9{^i9FA=LgzadtcACO;VrrX zFuSf%g;6@!h!BDgFk^ zSl(X{_aap06-sYI+|`O#KxU_6?rndz;=iC{zC`f^=>J8psOi zqxliV6Ttt1;zPmzvf`(4e@*dn@cc&c5b%g?q4Sp^=g&%KePz9+9nyhWZ;8)=o?J^w z{1(D<6}LjpK*bk>zew@%kjeKM^1p}sbj1%N?n1>=P{zk7UWq$@y96u9tZ=ubK zUxWP16yE?j|DiY!_XibsL(d;8{uX#%RlE$CdQj#XV6K-Teii&96z>Bbt(a#N)25;W z@*-;k!T&wb4paIX=rd38uMu~VVxILX=h=b(InZf$%DD=8Iax8!bZ%Ds3*b`}{~fqf zG3&LgJw)8;sC#EBeHiM=R>geYHd;pu9w)QxC*-6q__`c z-lF&s=x~SP<011N#Sh^*;@WlQB?ow);u_q!zX0hoP&ss$RDHle%Pu+ zG2go;D}EL-<@`IOwFNdmLg{}6eXioyfaNSa@O%i{a{mSDb~)s?D*ggw@<0L7Y2)>Z zFG0FzDxLuOmnvQfTYW+Ce}I0i;@iQ0z2cFOd6(h^px>_;?B4ejFN3Xqtaumrf2#Na z;8ztdfKC3Sm@Aq7rZ@^c@qvQq%=hBHinl=K5XH-(=V--;z~++_4~Gu36|aYGHHvRT z++~U%zZKv*Ig2mowkNGZVJ;p%eAt%c!iW2R zCZCm?6#o%+zJ(b2(%r9gOu=}M5F@Q4fuB&GJmCGx^8zs2aXTLPqtb5x{qMwxy8^f$ zbSDqh9;}#pmM9(rEN8$$H?~`HEaSL^^CncOfz8 zf5u(TaYJ0r3tp}CameR&#GwBO^xcYQ!OnLn&tsr}SLv64{sYA?0JE*9{GYNeDgC#g zzpC`FLBHP-_k$3)cZi|eNZ@}c&q3g4n@c$i=l*@vhtDtf<{|%7@E@v}W0R$dR|0n^ zZU&aK-4S;R;$BQTWK#YYh(TvR_9dnBeCZpN9s~Uw#Gsu5IS(lA0)Afcg}`qTYhM0L zjB@3eG#z$i+^c{~6yF9cXSJ&iRis0QvyfJ;;%kAI5rgMfuvJXy>~orx{yWguDg9@l zpQZS1U_Q@G_c^5XHDcB6He%>@9_SA#&#S<{QOxmCA?ga_a-4p!VvaW^DW=`zTy^NS z5PWi$I`aYO`i;Bv(*7q*YK{j5@qY{`2r>BLKvPLr-6hVFFD z_z`p7e652!6mzbNZJ&L&+N!uO?iV?Dhhh}Bw0ErMbT{BfO#Q#<;M)}QewTyqRr~|o z_c(Z;V$OxJ-L&$5qL{XN*};7F$iw{o&cS>RNpHpdFAn~jV(OTJG{{5D_iJM6$!FDK zzF!mX0iE-37R&xlpfeudwMi$Y-o(7KttY0dP&@^AzJukx20YZiLFvTH6;uD1gE@Cc znbebeeOfH|q5S>YS1i*E3yl z8|Zx%Q@4D@EF<=Rluwz(ikV0DZKShrlJ_-W)(v?_17;sot30H$ZzDg`YEpbEu)LRn zPQ1?HXa7Wg*3B)7-vgF+G0=&(E1i0>4Y|~oqZnT-i^4waOmv+NavivyAGXwjO~NC?-+3uJo0X(xIpP_&xR1QA%Z*Dp%*FM z1v>jG${7QEnBqf#<$Vft){Qwz=Xiko&5{2S;KhnB17;sa`YFJT4m}-yyFVUPvF$V4 zV%wHRiTmIJHx57B29^`U4s_G;qkQYEoCxVigzY(+;6P@MVx}qSs{H<>1GBvq9kKx6 z4#LllIitjyZn0uMFQS|3P(nKJF5IPlGNW)(52*+o-{Eb(q>gD?U(_-s&tt?YvrKm# zimD6tDLORyK1EZ@Cmo7yi{L(bpQ2~#@IQW(3(J!^sq!jthUkhd+_B}xz2oK;ZCQJU z*=48vXV>k0V)UyA=T%fx<-QTEAjb#T-6eDW`Te>FaDSbHTTt;p*p?>>jh@h?_^ApZ`YmqN9p8pE?rs66Ah!%F6R+teDx z#Ug|-A7)=4mcjLVxKESO)09Da5SN zo0;9|we22~-QXRTUG5jR&(7Y0f#pqJWXI6kM!cQ2W7uu!I}W-Hb?df4ua7*R`KcIF zF)07-$N(?D@7;ah&XhfuhQ8A0?VM8|Mj@d7fv5kAwC&!C#99)!uc-%FN$l&|XWTr) zm#y~MTbWtCsGIT!ze4%fRU}%9^4<95XPNTJ(fH9zZrwdSN$m~Cv+3qayy$*OS9XLt zaJtskEYE&P^5xd6yaR?ac7A`ekJaWX=FvX;uKa?38o#htXxQ%kdvPSz`vD&jKCo9f z`=|r$eYFo^5%djaPbqdNx&3#fNza%&_}X*{;^D;XldOvZ#N)7Mlyi(JrPn#eJA3mc z-3RNF$p0j~?Axu|1`yUe*_q1!s{D!s`Ru53^L9icVxa!lrUkZibz(mm(kZEZ+&EuJ z9*4!8>+UV~ujb>3)A=O%_oo)`AXNaf9&k#>FXNWTz&iQ@` zw?2X=5@J*mc^FrTA1NiwBo*g;a}#J8C#TikN(%O5Gv1@ci#^$lw@$n%7}82iob$a2 zF|&Ej_p20@jin}Ojbt)I@U$l4bWtL$g*Y?YobyebW$3MM0_PZsgVPV=OLum=mp+|J z8U54Eo@~ONK4&e++>?z-OJ34tV-llLdMjU;4d+@WqPV>h&?uGOaT{1N4No_v<*1yV zdl8V6k#X`!G~W`Q5xYK+k+Fhg-|I`#WWd~JfHV^~W2Fy@%$G(oy7&*IJVx9+PHtPw zZL7JRYi{S6+xh0U&D<_9w+qefB6Itkxm|2-+s$ox9836C+7BoxosYAtZQyt&Fx%sJI~zCH@9u(c7eHF zXl@so+vmocx|f;pFpmH?<-RXocQPI!PBHXHiA_Dq_&#yEiF{DT^A`k>e?q8&Uf2Pw z5nf~jQ}HtPUkz-YfQ+BAzT}t$ULt1oW(`Y=u&znX&dm6)dx8XhO`&-1umf1e>tu;| zIe8g>`A<-@Tf5eJ8SfEd1i~=>S1z|1pr2ON_bEt(=45;@26MP3<-Zdu{znP0YW<<; zkdH9L_vz^a#h67X3AVHdQz*#G$o0PruVL)xGq38&6Hkf>$rq3Lge9Xuz+?>crz0IsDtMV0)jt30ZP?}sn|kJp#~&a@S|A?PphluZp!hq8 zn$8>zq_9ZFBL84?>{6-B)fa5$b8hmzL75rLeC`O(Sqv}Z7@uFX7<{Z`%i!fc3voyj zR$gWtZ*&rEb294vjficuZ7?Pp$|QA{wA76A{96Nt^WE%hGubgwFEH6LHt;ic_&gij z1YKexc?FpnclrEhnIYe85*yIo*y&|_M*>nz_4oihQavJS-g`tEvihJu9PO*g(nCJ^ zl;gX8CIXPcqy8EIlfw5+3dZx8c+}pHi$_cR3DGZIB|YgM0s&G@&jlFPOuLG$(lZWE z*%t_(ks{S;KnJv+nZgoN*|WrxYEqpo9<`;Padpbi0|D1akXHNh%&JAG|5EKU=dvGB zT+LJl8~Qw^GSuMt#OB0|%msXKx!W6r)v%n08+;V;7=st~AmLJN88Vk#6ePCPNvxJa zHL;_K`Kb|v)-m!>6S+9^ST;N+V7U|dI7idtiB;1Rh*i52DO9yPiFkyOb24$%VCmnA z3|_&6N7{s&rhsY!nw^BD7gw!UG0UoHYGy0r&JnU7QEMv*LyS8-;=aLOcy##7x5WZ8-0!`sMKiY<=kf1 zBzJ`q;YwD1l{z@{(Jur{-xnrtM5IsY10eQlnNoz3YFa6>&1%FP>OW_b&@{QG%QZu; znR4wT*DSea%QZ)?edU@f*M4%%lWV?Q3*=fT*Zy)HAlHF%9VFMmavkDDVGqwAUCA5V zKK%ThYw*5SM6W-Zkj{9=tV{5Y<#3H|r0j1wev^idBF#U<3N_vd)?3WbuMIK(aP;m2 z_y?>sMt1NV;~$OPQ~Qs>&Uu@a;bkx!48dUVrz1S&YzaPVjQqpWWALnm;2g$2Yq$wc z!3)GP4L(c)N@bCSSA>AE)MO?AIv);H(;_`!P3O2HEyC6+N(TQZt2w>?&cA@C$OI%> z=cq(;EaKRiZZt2<=bNHPJvJFVnz>mZx%HVH*>^OqPLwhHdYdk1D*T<_?Q7GmPfWLg zA#_HoWr^NntX8HNmRq5w%#M3PMUd{($WUJ@5SVl9!Qi7me8YIJa^2f7w7lI@^ zDg6`7`UvRhSBOE`$VVB;-)KeC>+d`klmA5~ATeW`7(!=4Pq#TSs-M}*zKg*=#;1Mj z;`U8VV&AmLNnlNX9ePD6-#<4|qc;ML&P~+lynDzFXEZu5QKN0WH2QfmjUq`jic+Y* z!>X~3zMU7~4aOw2BTb6_i?IKUGLnCXl}xX{lWmYu=#E5n??}||&R%9>%V!GCc`(usXr}vZ zx)qY{qd~g&C#L(rtq7+xT0Ka{xc8w%t@iv-LjzlUInb(}ty5;P7$zf7OBmVyJRpp= zc|uOm{tQic44wiIo|ug%60`AyEj7dZWMVe{ zlVa$CY&3yjjLU5JizFABK`!F0PFjRLc$AU-{Z=>5==nR3DzLikPt@(F452g1yp*WS z%gZ#>)Wn}#p@D9{6W!3}_vvpGX0wbHK4QbO4>mNKc!bc@#Aiil;D~EjFdd>HHmAlPF(Yb(S-;Ke1nK?K z{a)4jXQEZ8J<3XAd813;Ko?Fw`a3Tx#D_R*Jio6`L}yaY^{-9v_wy4q$@g#4@W9Ti zL2z0|QwOpGO@f-dP5A2kDX1jaZ&+&b-~=mP1&(ynSzbrT5m;J;<2jRw!9gZC@9OWI zhxZ3&VsI!ELqg^p>XVbsWO7(2QwRG;Yj}{USs?VP*f~L_+={&jtmzyFrbRfCG^&mc zR5kWa#mWnl_-H?|?8o>Fiz_!aq}+HvSHlD4_6N$bFG|Ub%43%sXcw6Lufka8PdlK= z`#^R&$E0Zyj&P0grGfHM=;iO+6)0aCQohV*7@f&nSxEUwy({ijh~QKgGf?Rl=hcSBBEA^cfa6 z$()dhs(jm37~SWFbe-2**T(~08{%V+{3N=jMP3AJIwzEj+KWSKpEAgrY;j2KB_Xw! zgw(3-t=0+%>s68kNz^jB92@Au{|@*&(=gv)OmJ+VO9@{4{bO}v!v-(+$r`ul@{nrB z_g3w1fojq(nMzieM76X?He{!B`on117-$(qivG@>ftHPdmPYH9KEvpYzbT|vi~n^E z4<6jhLFi?%{z=uk9<1q{7D`L~D?7vIdg_W|e zH5ciz%E(CcH*ye^3Th@DoeDbkawA-}9p~7<;bM9wnXKVGfP9fFC2$l-zk*3}gitEI z^CbAZKCPTID&;WBT&VD5RHT1-Z*Q}ycZR#EcV^=D-F>h%x9*C~t+}(}-uU+2*@@eC z=M?(c+1%ordt3Lln_1`D&8+)H{M6hyL!R9?JYPkp)f6(N0+o_RDTS&b!Z5KHXeePg z_FXms*LVX1@e(>{kWofFgX2kHo-7^@Q!9Nvlk706iMcP0CV4nmqhZf-%!Fw)@r-bH zK##h+WY-jOckhb9SI-&33=}19F3#P%osG*!IU9hZaVd`PAU@jeTuvcloGs1A8Xn!A zd|Z4p@$rEcY224PK?|%VlEQ?kxF`4o)k3|Ptha__sBZ4ff}|j`?RTl!D7>uq#^2>* zP#Gr$iEA9xPi?0V3YV}k^9msykeawjvhM}#@n}cK;mH<;x7`i`EQ7PV_9xy&yEZQ5 zq&N(ymV{&O9Povbx(lJJv}g$1JzQk#vLdK$Aq{#iKhGtGCodi&?6ZR1(t1 zkRKTSK=SM2avftPqf$bAxASKXwo#L0Jh3za8`>Bj5$p*3 zNV9zmTP0*VnN@;HVD8UKT{xyuq339tWQoW@iFX{@W>{sS_yTp_XFqTJfRqS8P;L^7+Lrv zMgJ*g;a{`&Cs8)RoXPAR7|uE5z_HzDMYTCksQ=EhWWya*KC&1;M)mvn=J;0>aE$e- z>hiy~rYAgV$yzR_EC2XK`{|kUQ!V9xt;Qu}JDG`s2mD`=|B)FaL$YQiL9JxXc!O1k z5*q$l4`&(wM>PeXo#8&)@(hg@k`?X0r9epjr(R({iTr)2S(s~t!+$%~kwzDltLO0_ z%ydz?#_!ug{tER^)%#aM;YX-{n%=EH8sZE4hkr!42LF$|od&a~cw8*{f9YVKGTq0n zy9_IW4sc{nTkFcQ)lE%h@uPF5mQF97Qf3d&S=H6p-f~(;*IG*{ThHCudy`w&ftG~M z1ATgPe+}_9KS;v%0@y+& z{!6m8Cq{jXw6;Xrdkzh?lqZ6)_BJ**cLDNP0NfVW&&RItF&=3!zp^$qzj{e^T}@T2 zYVq7y^)U^?C40y>V#%N$J`&?eN-^2dzT4~R=xJ<=@mt=Iq`K;(V>sBS!Rw5zOgQI4 zPiX0EY3V{pV>n)YbwjKU$3n#Fs%0Dgy82r0wAOXa9j8H3JY~s>tZ8h*`6;4I@7&Zc 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%0DOpGM&w&^ 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 JJ-ehb{{>&+Bu)ST From 99b4c9d7e9e06a12dd1f63b6aac2e3d31c350db4 Mon Sep 17 00:00:00 2001 From: Brian Baltz Date: Mon, 29 Aug 2016 14:01:05 -0700 Subject: [PATCH 05/14] Update expected BLE version for V3 Signed-off-by: Brian Baltz --- platform.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/platform.txt b/platform.txt index ecac9280..e0ff63ce 100644 --- a/platform.txt +++ b/platform.txt @@ -5,7 +5,7 @@ # https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5---3rd-party-Hardware-specification name=Intel Curie (32-bit) Boards -version=1.0.7 +version=2.0.0 # Arduino 101 compile variables # ---------------------- @@ -93,8 +93,8 @@ recipe.size.regex=^(?:text|ctors|rodata|datas)\s+([0-9]+).* # BLE firmware check # ------------------- -tools.arduino101load.ble.fw.string="ATP1BLE000-1541C5635" -tools.arduino101load.ble.fw.position=141312 +tools.arduino101load.ble.fw.string="ATP1BLE00R-1631C4439" +tools.arduino101load.ble.fw.position=169984 # Arc Uploader/Programmers tools # ------------------- From 5203f8236ac6ab9673baaad387f533f6f1ca2484 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 12 Sep 2016 10:33:51 -0600 Subject: [PATCH 06/14] JIRA-685, Peripheral sketches shall state associated Central sketch. --- .../BatteryAdvChange/BatteryAdvChange.ino | 21 +++++++++++-------- .../BatteryMonitor/BatteryMonitor.ino | 11 +++++----- .../CurieBLE/examples/ButtonLED/ButtonLED.ino | 14 +++++++------ .../examples/CallbackLED/CallbackLED.ino | 19 ++++++++++------- .../examples/IMUBleCentral/IMUBleCentral.ino | 12 +++++------ .../IMUBleNotification/IMUBleNotification.ino | 7 ++++--- libraries/CurieBLE/examples/LED/LED.ino | 20 ++++++++++-------- .../examples/LEDCentral/LEDCentral.ino | 7 ++++--- .../CurieBLE/examples/Scanning/Scanning.ino | 11 +++++----- .../UpdateConnectionInterval.ino | 5 +++-- 10 files changed, 71 insertions(+), 56 deletions(-) diff --git a/libraries/CurieBLE/examples/BatteryAdvChange/BatteryAdvChange.ino b/libraries/CurieBLE/examples/BatteryAdvChange/BatteryAdvChange.ino index 44a8055a..8b977a39 100644 --- a/libraries/CurieBLE/examples/BatteryAdvChange/BatteryAdvChange.ino +++ b/libraries/CurieBLE/examples/BatteryAdvChange/BatteryAdvChange.ino @@ -1,13 +1,16 @@ -/* Please see code cpyright at the bottom of this example code */ +/* Please see code copyright at the bottom of this example code */ + /* - This sketch illustrates how to change the advertising data so that it is visible but not - connectable. Then after 10 seconds it changes to being connectable - This sketch example partially implements the standard Bluetooth Low-Energy Battery service. - - This sketch is not paired with a specific central example sketch, - but to see how it works you need to use a BLE APP on your phone or central device - and try connecting when it is either a connectable or not connectable state - as displayed in the serial monitor. + This example can work with phone BLE app. + + This sketch illustrates how to change the advertising data so that it is visible but not + connectable. Then after 10 seconds it changes to being connectable. + This sketch example partially implements the standard Bluetooth Low-Energy Battery service. + + This sketch is not paired with a specific central example sketch, + but to see how it works you need to use a BLE APP on your phone or central device + and try connecting when it is either a connectable or not connectable state + as displayed in the serial monitor. */ #include diff --git a/libraries/CurieBLE/examples/BatteryMonitor/BatteryMonitor.ino b/libraries/CurieBLE/examples/BatteryMonitor/BatteryMonitor.ino index 96969888..e3a297cf 100644 --- a/libraries/CurieBLE/examples/BatteryMonitor/BatteryMonitor.ino +++ b/libraries/CurieBLE/examples/BatteryMonitor/BatteryMonitor.ino @@ -6,11 +6,12 @@ #include /* - This sketch can work with UpdateConnectionInterval. - You can also use an android or IOS app that supports notifications - This sketch example partially implements the standard Bluetooth Low-Energy Battery service - and connection interval paramater update. - For more information: https://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx + This sketch can work with UpdateConnectionInterval. + + You can also use an android or IOS app that supports notifications. + This sketch example partially implements the standard Bluetooth Low-Energy Battery service + and connection interval paramater update. + For more information: https://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx */ /* */ diff --git a/libraries/CurieBLE/examples/ButtonLED/ButtonLED.ino b/libraries/CurieBLE/examples/ButtonLED/ButtonLED.ino index 9cb38a2c..7a46c164 100644 --- a/libraries/CurieBLE/examples/ButtonLED/ButtonLED.ino +++ b/libraries/CurieBLE/examples/ButtonLED/ButtonLED.ino @@ -3,12 +3,14 @@ * See the bottom of this file for the license terms. */ - /* This examples needs a button connected similarly as described here - https://www.arduino.cc/en/Tutorial/Button - The only difference is that instead of connecting to pin 2 connect to pin 4 - After the sketch starts connect to a BLE app on a phone and set notification to the Characteristic and you should see it update - whenever the button is pressed. This sketch is not written to pair with any of the central examples. - */ +/* + This example can work with phone BLE app. + + This examples needs a button connected similarly as described here https://www.arduino.cc/en/Tutorial/Button + The only difference is that instead of connecting to pin 2, it connects to pin 4 + After the sketch starts connect to a BLE app on a phone and set notification to the Characteristic and you should see it update + whenever the button is pressed. This sketch is not written to pair with any of the central examples. +*/ #include diff --git a/libraries/CurieBLE/examples/CallbackLED/CallbackLED.ino b/libraries/CurieBLE/examples/CallbackLED/CallbackLED.ino index 3f516ca5..2a661189 100644 --- a/libraries/CurieBLE/examples/CallbackLED/CallbackLED.ino +++ b/libraries/CurieBLE/examples/CallbackLED/CallbackLED.ino @@ -3,14 +3,17 @@ * See the bottom of this file for the license terms. */ - // This example can work with LEDCentral - // You should see the LED blink on and off - // This example demonstrates the use of Callback or event Handlers responding to events - // BLECoonected, BLEDisconnected and BLEWritten are events. - // To test interactively use a Phone app like nrf Controller (Android) or Light Blue (iOS) - // Connect to BLE device named LEDCB and explore characteristic with UUID 19B10001-E8F2-537E-4F6C-D104768A1214 - // Writing a byte value such as 0x40 should turn on the LED - // Writng a byte value of 0x00 should turn off the LED + /* + This example can work with LEDCentral. + + You should see the LED blink on and off. + This example demonstrates the use of Callback or event Handlers responding to events. + BLEConnected, BLEDisconnected and BLEWritten are events. + To test interactively, use a Phone app like nrf Controller (Android) or Light Blue (iOS). + Connect to BLE device named LEDCB and explore characteristic with UUID 19B10001-E8F2-537E-4F6C-D104768A1214. + Writing a byte value such as 0x40 should turn on the LED. + Writing a byte value of 0x00 should turn off the LED. + */ #include diff --git a/libraries/CurieBLE/examples/IMUBleCentral/IMUBleCentral.ino b/libraries/CurieBLE/examples/IMUBleCentral/IMUBleCentral.ino index e14753db..a5359509 100644 --- a/libraries/CurieBLE/examples/IMUBleCentral/IMUBleCentral.ino +++ b/libraries/CurieBLE/examples/IMUBleCentral/IMUBleCentral.ino @@ -6,12 +6,12 @@ #include /* - This sketch example works with IMUBleNotification.ino - IMUBleNotification.ino will send notification to this sketch. - This sketch will receive the notifications and out put the received data in the serial monitor - It also illustrates using a non typed characteristic - Set the baud rate to 115200 on the serial monitor to accomodate the speed of constant data updates from IMU subsystem - + This sketch example works with IMUBleNotification.ino + + IMUBleNotification.ino will send notification to this central sketch. + This sketch will receive the notifications and output the received data in the serial monitor. + It also illustrates using a non-typed characteristic. + Set the baud rate to 115200 on the serial monitor to accomodate the speed of constant data updates from IMU subsystem. */ #define MAX_IMU_RECORD 1 diff --git a/libraries/CurieBLE/examples/IMUBleNotification/IMUBleNotification.ino b/libraries/CurieBLE/examples/IMUBleNotification/IMUBleNotification.ino index 81ca3dec..ee57d295 100644 --- a/libraries/CurieBLE/examples/IMUBleNotification/IMUBleNotification.ino +++ b/libraries/CurieBLE/examples/IMUBleNotification/IMUBleNotification.ino @@ -7,9 +7,10 @@ #include /* - This sketch example works with IMUBleCentral.ino - This sketch will read IMU data from sensor and send notification to IMUBleCentral.ino - IMUBleCentral.ino will receive the Notifications and output the received data. + This sketch example works with IMUBleCentral.ino. + + This sketch will read IMU data from sensor and send notifications to IMUBleCentral.ino. + IMUBleCentral.ino will receive the notifications and output the received data. */ #define MAX_IMU_RECORD 1 diff --git a/libraries/CurieBLE/examples/LED/LED.ino b/libraries/CurieBLE/examples/LED/LED.ino index 2c2c261d..34c26a8c 100644 --- a/libraries/CurieBLE/examples/LED/LED.ino +++ b/libraries/CurieBLE/examples/LED/LED.ino @@ -3,15 +3,17 @@ * See the bottom of this file for the license terms. */ - // This example can work with LEDCentral - // - // This example is similar to CallbackLED example in functionality - // It does not use callbacks. In the loop it interogates the connection state with central - // Checks if the characteristic is wriiten and turns the LED on or off accordingly - // To test interactively, use a phone app like nrf Controller (Android) or Light Blue (iOS) - // Connect to BLE device named LEDCB and explore characteristic with UUID 19B10001-E8F2-537E-4F6C-D104768A1214 - // Writing a byte value such as 0x40 should turn on the LED - // Writng a byte value of 0x00 should turn off the LED + /* + This example can work with LEDCentral + + This example is similar to CallbackLED example in functionality. + It does not use callbacks. In the loop it interogates the connection state with central. + Checks if the characteristic is written and turns the LED on or off accordingly. + To test interactively, use a phone app like nrf Controller (Android) or Light Blue (iOS). + Connect to BLE device named LEDCB and explore characteristic with UUID 19B10001-E8F2-537E-4F6C-D104768A1214. + Writing a byte value such as 0x40 should turn on the LED. + Writing a byte value of 0x00 should turn off the LED. + */ #include diff --git a/libraries/CurieBLE/examples/LEDCentral/LEDCentral.ino b/libraries/CurieBLE/examples/LEDCentral/LEDCentral.ino index b9b533b3..e26e9451 100644 --- a/libraries/CurieBLE/examples/LEDCentral/LEDCentral.ino +++ b/libraries/CurieBLE/examples/LEDCentral/LEDCentral.ino @@ -6,9 +6,10 @@ #include /* - This example can work with CallbackLED and LED - to show how a central device can do charcteristic read and write operations. - A third party serial terminal is recommended to see outputs from central and peripheral device + This example can work with CallbackLED and LED sketches. + + To show how a central device can do charcteristic read and write operations. + A third party serial terminal is recommended to see outputs from central and peripheral device. */ // set up connection params diff --git a/libraries/CurieBLE/examples/Scanning/Scanning.ino b/libraries/CurieBLE/examples/Scanning/Scanning.ino index 97ea222d..966bd3b4 100644 --- a/libraries/CurieBLE/examples/Scanning/Scanning.ino +++ b/libraries/CurieBLE/examples/Scanning/Scanning.ino @@ -6,11 +6,12 @@ #include /* - This sketch try to show the scan function - The sketch will list the device's MAC address and device name to the console - The list will refresh every 3s - This sketch is meaningful if one or more BLE peripheral devices (any of the peripheral examples will do) - are present. + This sketch is meaningful if one or more BLE peripheral devices (any of the peripheral examples will do) + are present. + + This sketch try to show the scan function. + The sketch will list the device's MAC address and device name to the console. + The list will refresh every 3s. */ diff --git a/libraries/CurieBLE/examples/UpdateConnectionInterval/UpdateConnectionInterval.ino b/libraries/CurieBLE/examples/UpdateConnectionInterval/UpdateConnectionInterval.ino index a25c2195..852b8e3b 100644 --- a/libraries/CurieBLE/examples/UpdateConnectionInterval/UpdateConnectionInterval.ino +++ b/libraries/CurieBLE/examples/UpdateConnectionInterval/UpdateConnectionInterval.ino @@ -6,8 +6,9 @@ #include /* - This example can work with BatteryMonitor - to show how to control and response the connection intelval request. + This example can work with BatteryMonitor. + + Show how to control and response the connection interval request. */ // set up connection params From e76e9532ca82bdd06556af9d629c1cf00c98ab89 Mon Sep 17 00:00:00 2001 From: Erik Nyquist Date: Wed, 7 Sep 2016 16:02:31 -0700 Subject: [PATCH 07/14] stdlib_noniso.cpp: fix dtostrf() handling of integral portion Checking only for > 10 here (rather than >= 10) means that numbers with a leading '10' in the integral portion generate incorrect strings. --- cores/arduino/stdlib_noniso.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/arduino/stdlib_noniso.cpp b/cores/arduino/stdlib_noniso.cpp index 68c0ba39..7b8a107c 100644 --- a/cores/arduino/stdlib_noniso.cpp +++ b/cores/arduino/stdlib_noniso.cpp @@ -221,7 +221,7 @@ char *dtostrf(double number, signed char width, unsigned char prec, char *s) // generate chars for each digit of the integral part i = before; - while (integer > 10) { + while (integer >= 10) { digit = integer % 10; out[(i--) - 1] = ASCII_ZERO + digit; integer /= 10; From 1f0a5be6932e577bbff4b13127c4896912d10ed0 Mon Sep 17 00:00:00 2001 From: Biagio Montaruli Date: Wed, 7 Sep 2016 13:05:01 +0200 Subject: [PATCH 08/14] Fix for USB virtual serial port in sketches of CurieI2S library Since Arduino/Genuino 101 uses USB native port, wait for the Serial port to open before executing the next lines of code to not lose serial data already sent to the Serial monitor Signed-off-by: Biagio Montaruli --- .../examples/I2SDMA_RXCallBack/I2SDMA_RXCallBack.ino | 6 +++--- .../examples/I2SDMA_TXCallBack/I2SDMA_TXCallBack.ino | 4 ++-- .../CurieI2S/examples/I2S_RxCallback/I2S_RxCallback.ino | 4 ++-- .../CurieI2S/examples/I2S_TxCallback/I2S_TxCallback.ino | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/libraries/CurieI2S/examples/I2SDMA_RXCallBack/I2SDMA_RXCallBack.ino b/libraries/CurieI2S/examples/I2SDMA_RXCallBack/I2SDMA_RXCallBack.ino index a41f25f3..ed099fa1 100644 --- a/libraries/CurieI2S/examples/I2SDMA_RXCallBack/I2SDMA_RXCallBack.ino +++ b/libraries/CurieI2S/examples/I2SDMA_RXCallBack/I2SDMA_RXCallBack.ino @@ -37,8 +37,8 @@ uint32_t loop_count = 0; // record the higher 16 bits of received data uint32_t shift_count = 0; // the position of first non-zero void setup() { - Serial.begin(115200); - while(!Serial); + Serial.begin(115200); // initialize Serial communication + while(!Serial) ; // wait for serial port to connect. Serial.println("CurieI2SDMA Rx Callback"); CurieI2SDMA.iniRX(); @@ -119,4 +119,4 @@ void loop() License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110- 1301 USA -*/ \ No newline at end of file +*/ diff --git a/libraries/CurieI2S/examples/I2SDMA_TXCallBack/I2SDMA_TXCallBack.ino b/libraries/CurieI2S/examples/I2SDMA_TXCallBack/I2SDMA_TXCallBack.ino index d39bea36..9ce55501 100644 --- a/libraries/CurieI2S/examples/I2SDMA_TXCallBack/I2SDMA_TXCallBack.ino +++ b/libraries/CurieI2S/examples/I2SDMA_TXCallBack/I2SDMA_TXCallBack.ino @@ -15,8 +15,8 @@ uint32_t dataBuff[BUFF_SIZE]; uint32_t loop_count = 0; void setup() { - Serial.begin(115200); - while(!Serial); + Serial.begin(115200); // initialize Serial communication + while(!Serial) ; // wait for serial port to connect. Serial.println("CurieI2SDMA Tx Callback"); CurieI2SDMA.iniTX(); diff --git a/libraries/CurieI2S/examples/I2S_RxCallback/I2S_RxCallback.ino b/libraries/CurieI2S/examples/I2S_RxCallback/I2S_RxCallback.ino index 101d3774..9550ad1f 100644 --- a/libraries/CurieI2S/examples/I2S_RxCallback/I2S_RxCallback.ino +++ b/libraries/CurieI2S/examples/I2S_RxCallback/I2S_RxCallback.ino @@ -23,8 +23,8 @@ volatile int count = 0; void setup() { // put your setup code here, to run once: - Serial.begin(115200); - while(!Serial); + Serial.begin(115200); // initialize Serial communication + while(!Serial) ; // wait for serial port to connect. Serial.println("CurieI2S Rx Callback Example"); CurieI2S.begin(I2S_44K, I2S_32bit); CurieI2S.setI2SMode(PHILIPS_MODE); diff --git a/libraries/CurieI2S/examples/I2S_TxCallback/I2S_TxCallback.ino b/libraries/CurieI2S/examples/I2S_TxCallback/I2S_TxCallback.ino index cb3ff7cf..76189db2 100644 --- a/libraries/CurieI2S/examples/I2S_TxCallback/I2S_TxCallback.ino +++ b/libraries/CurieI2S/examples/I2S_TxCallback/I2S_TxCallback.ino @@ -6,8 +6,8 @@ void setup() { - Serial.begin(115200); - while(!Serial); + Serial.begin(115200); // initialize Serial communication + while(!Serial) ; // wait for serial port to connect. Serial.println("CurieI2S Tx Callback"); CurieI2S.begin(I2S_44K, I2S_32bit); CurieI2S.setI2SMode(PHILIPS_MODE); From f15a9e7c217d25bb039c6196e93b94ee9c4de403 Mon Sep 17 00:00:00 2001 From: Biagio Montaruli Date: Wed, 7 Sep 2016 13:01:32 +0200 Subject: [PATCH 09/14] Fix for USB virtual serial port in sketches of CurieIMU library Since Arduino/Genuino 101 uses USB native port, wait for the Serial port to open before executing the sketch to not lose serial data already sent to the Serial monitor Signed-off-by: Biagio Montaruli --- libraries/CurieIMU/examples/FreeFallDetect/FreeFallDetect.ino | 4 ++-- libraries/CurieIMU/examples/MotionDetect/MotionDetect.ino | 4 ++-- libraries/CurieIMU/examples/ShockDetect/ShockDetect.ino | 4 ++-- libraries/CurieIMU/examples/StepCount/StepCount.ino | 3 ++- libraries/CurieIMU/examples/TapDetect/TapDetect.ino | 4 ++-- .../CurieIMU/examples/TapDoubleDetect/TapDoubleDetect.ino | 4 ++-- .../CurieIMU/examples/ZeroMotionDetect/ZeroMotionDetect.ino | 4 ++-- 7 files changed, 14 insertions(+), 13 deletions(-) diff --git a/libraries/CurieIMU/examples/FreeFallDetect/FreeFallDetect.ino b/libraries/CurieIMU/examples/FreeFallDetect/FreeFallDetect.ino index 36786412..ca88e457 100644 --- a/libraries/CurieIMU/examples/FreeFallDetect/FreeFallDetect.ino +++ b/libraries/CurieIMU/examples/FreeFallDetect/FreeFallDetect.ino @@ -16,8 +16,8 @@ unsigned long interruptsTime = 0; // get the time when free fall event is det void setup() { - Serial.begin(9600); - while(!Serial); // wait for the serial port to open + Serial.begin(9600); // initialize Serial communication + while(!Serial) ; // wait for serial port to connect. /* Initialise the IMU */ CurieIMU.begin(); diff --git a/libraries/CurieIMU/examples/MotionDetect/MotionDetect.ino b/libraries/CurieIMU/examples/MotionDetect/MotionDetect.ino index 2eb8c9ac..400ad461 100644 --- a/libraries/CurieIMU/examples/MotionDetect/MotionDetect.ino +++ b/libraries/CurieIMU/examples/MotionDetect/MotionDetect.ino @@ -15,8 +15,8 @@ unsigned long loopTime = 0; // get the time since program started unsigned long interruptsTime = 0; // get the time when motion event is detected void setup() { - Serial.begin(9600); - while(!Serial); // wait for the serial port to open + Serial.begin(9600); // initialize Serial communication + while(!Serial) ; // wait for serial port to connect. /* Initialise the IMU */ CurieIMU.begin(); diff --git a/libraries/CurieIMU/examples/ShockDetect/ShockDetect.ino b/libraries/CurieIMU/examples/ShockDetect/ShockDetect.ino index 3d3bb656..467299a2 100644 --- a/libraries/CurieIMU/examples/ShockDetect/ShockDetect.ino +++ b/libraries/CurieIMU/examples/ShockDetect/ShockDetect.ino @@ -13,8 +13,8 @@ boolean blinkState = false; // state of the LED void setup() { - Serial.begin(9600); - + Serial.begin(9600); // initialize Serial communication + while(!Serial) ; // wait for serial port to connect.. /* Initialise the IMU */ CurieIMU.begin(); CurieIMU.attachInterrupt(eventCallback); diff --git a/libraries/CurieIMU/examples/StepCount/StepCount.ino b/libraries/CurieIMU/examples/StepCount/StepCount.ino index 386121b0..aafc9483 100644 --- a/libraries/CurieIMU/examples/StepCount/StepCount.ino +++ b/libraries/CurieIMU/examples/StepCount/StepCount.ino @@ -25,7 +25,8 @@ long lastStepCount = 0; // step count on previous polling check boolean blinkState = false; // state of the LED void setup() { - Serial.begin(9600); + Serial.begin(9600); // initialize Serial communication + while(!Serial) ; // wait for serial port to connect. // pinMode(13, OUTPUT); // intialize the sensor: CurieIMU.begin(); diff --git a/libraries/CurieIMU/examples/TapDetect/TapDetect.ino b/libraries/CurieIMU/examples/TapDetect/TapDetect.ino index d0489a9a..a6c6ede3 100644 --- a/libraries/CurieIMU/examples/TapDetect/TapDetect.ino +++ b/libraries/CurieIMU/examples/TapDetect/TapDetect.ino @@ -11,8 +11,8 @@ #include "CurieIMU.h" void setup() { - Serial.begin(9600); - + Serial.begin(9600); // initialize Serial communication + while(!Serial) ; // wait for serial port to connect. // Initialise the IMU CurieIMU.begin(); CurieIMU.attachInterrupt(eventCallback); diff --git a/libraries/CurieIMU/examples/TapDoubleDetect/TapDoubleDetect.ino b/libraries/CurieIMU/examples/TapDoubleDetect/TapDoubleDetect.ino index d8c63f4b..12148e84 100644 --- a/libraries/CurieIMU/examples/TapDoubleDetect/TapDoubleDetect.ino +++ b/libraries/CurieIMU/examples/TapDoubleDetect/TapDoubleDetect.ino @@ -11,8 +11,8 @@ #include "CurieIMU.h" void setup() { - Serial.begin(9600); - + Serial.begin(9600); // initialize Serial communication + while(!Serial) ; // wait for serial port to connect. // Initialise the IMU CurieIMU.begin(); CurieIMU.attachInterrupt(eventCallback); diff --git a/libraries/CurieIMU/examples/ZeroMotionDetect/ZeroMotionDetect.ino b/libraries/CurieIMU/examples/ZeroMotionDetect/ZeroMotionDetect.ino index 56c9b79e..56fc3a9d 100644 --- a/libraries/CurieIMU/examples/ZeroMotionDetect/ZeroMotionDetect.ino +++ b/libraries/CurieIMU/examples/ZeroMotionDetect/ZeroMotionDetect.ino @@ -11,8 +11,8 @@ boolean ledState = false; // state of the LED void setup() { - Serial.begin(9600); - while(!Serial); // wait for the serial port to open + Serial.begin(9600); // initialize Serial communication + while(!Serial) ; // wait for serial port to connect. /* Initialise the IMU */ CurieIMU.begin(); From d06255706e5409ef994d325a260cc06870b4e813 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Tue, 6 Sep 2016 14:06:34 -0400 Subject: [PATCH 10/14] Make String::move of an invalidated String result in an invalidated String --- cores/arduino/WString.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/arduino/WString.cpp b/cores/arduino/WString.cpp index 73a96f9f..ce4e4f85 100644 --- a/cores/arduino/WString.cpp +++ b/cores/arduino/WString.cpp @@ -227,7 +227,7 @@ String & String::copy(const __FlashStringHelper *pstr, unsigned int length) void String::move(String &rhs) { if (buffer) { - if (capacity >= rhs.len) { + if (rhs && capacity >= rhs.len) { strcpy(buffer, rhs.buffer); len = rhs.len; rhs.len = 0; From e7f97b0da28e608c414c146fd8c610d93f582219 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Thu, 15 Sep 2016 15:22:35 -0400 Subject: [PATCH 11/14] WString: add `toDouble` (#293) Port of https://github.com/arduino/Arduino/pull/5362 --- cores/arduino/WString.cpp | 9 +++++++-- cores/arduino/WString.h | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/cores/arduino/WString.cpp b/cores/arduino/WString.cpp index ce4e4f85..d577e002 100644 --- a/cores/arduino/WString.cpp +++ b/cores/arduino/WString.cpp @@ -810,6 +810,11 @@ long String::toInt(void) const float String::toFloat(void) const { - if (buffer) return float(atof(buffer)); - return 0; + return (float)toDouble(); +} + +double String::toDouble(void) const +{ + if (buffer) return atof(buffer); + return 0; } diff --git a/cores/arduino/WString.h b/cores/arduino/WString.h index 75a3e143..2ff259f8 100644 --- a/cores/arduino/WString.h +++ b/cores/arduino/WString.h @@ -199,6 +199,7 @@ class String // parsing/conversion long toInt(void) const; float toFloat(void) const; + double toDouble(void) const; char * getCSpec(int base, bool issigned, bool islong); protected: From 290bf591f1e8282db6429f742cecbf889214a096 Mon Sep 17 00:00:00 2001 From: Biagio Montaruli Date: Sat, 17 Sep 2016 13:07:27 +0200 Subject: [PATCH 12/14] Update some classes of CurieBLE library Improve documentation and code formatting of BLEAttribute, BLECharacteristic and BLEProfile classes Signed-off-by: Biagio Montaruli --- libraries/CurieBLE/src/BLEAttribute.h | 4 ++-- libraries/CurieBLE/src/BLECharacteristic.h | 6 ++---- libraries/CurieBLE/src/BLEProfile.h | 4 ++-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/libraries/CurieBLE/src/BLEAttribute.h b/libraries/CurieBLE/src/BLEAttribute.h index 1de33613..fbb5f786 100644 --- a/libraries/CurieBLE/src/BLEAttribute.h +++ b/libraries/CurieBLE/src/BLEAttribute.h @@ -70,7 +70,7 @@ class BLEAttribute { * @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 + * false- UUID is not the same with data * * @note none */ @@ -104,7 +104,7 @@ class BLEAttribute { void setHandle(uint16_t handle); static unsigned char numAttributes(void); - // The below APIs are for central device to discover the + // The below APIs are for central device to discover peripheral devices virtual void discover(bt_gatt_discover_params_t *params) = 0; virtual void discover(const bt_gatt_attr_t *attr, bt_gatt_discover_params_t *params) = 0; diff --git a/libraries/CurieBLE/src/BLECharacteristic.h b/libraries/CurieBLE/src/BLECharacteristic.h index 58274f67..200c5761 100644 --- a/libraries/CurieBLE/src/BLECharacteristic.h +++ b/libraries/CurieBLE/src/BLECharacteristic.h @@ -166,7 +166,7 @@ class BLECharacteristic : public BLEAttribute { * * @param none * - * @return unsigned char The totla number of the notify attributes + * @return unsigned char The total number of the notify attributes * * @note none */ @@ -303,8 +303,6 @@ class BLECharacteristic : public BLEAttribute { private: void _setValue(const uint8_t value[], uint16_t length); - -private: static unsigned char _numNotifyAttributes; static bt_uuid_16_t _gatt_chrc_uuid; @@ -329,7 +327,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/BLEProfile.h b/libraries/CurieBLE/src/BLEProfile.h index 300d54e8..48ed223b 100644 --- a/libraries/CurieBLE/src/BLEProfile.h +++ b/libraries/CurieBLE/src/BLEProfile.h @@ -166,7 +166,7 @@ class BLEProfile{ * @param[in] BLEAttribute * The BLEAttribute object * * @return bt_gatt_attr_t* NULL - Not found - * Not NULL - The bt_gatt_attr in the stack + * Not NULL - The bt_gatt_attr in the stack * * @note none */ @@ -206,7 +206,7 @@ class BLEProfile{ BLEAttribute** _attributes; uint16_t _num_attributes; - bt_gatt_subscribe_params_t *_sub_param; + bt_gatt_subscribe_params_t *_sub_param; int _sub_param_idx; bt_gatt_discover_params_t _discover_params; From 82981cbc106f906f62d92bd2c8860eb6e6bff970 Mon Sep 17 00:00:00 2001 From: Biagio Montaruli Date: Sat, 17 Sep 2016 13:11:13 +0200 Subject: [PATCH 13/14] Improve keyword.txt file of CurieBLE library Add CurieBLE and new names of methods defined in some classes of CurieBLE library Signed-off-by: Biagio Montaruli --- libraries/CurieBLE/keywords.txt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/libraries/CurieBLE/keywords.txt b/libraries/CurieBLE/keywords.txt index bb0f37ea..47e74fd6 100644 --- a/libraries/CurieBLE/keywords.txt +++ b/libraries/CurieBLE/keywords.txt @@ -6,6 +6,7 @@ # Datatypes (KEYWORD1) ####################################### +CurieBLE KEYWORD1 BLEAttribute KEYWORD1 BLECentral KEYWORD1 BLECharacteristic KEYWORD1 @@ -16,6 +17,9 @@ BLETypedCharacteristic KEYWORD1 BLEHelper KEYWORD1 BLECentralHelper KEYWORD1 BLEPeripheralHelper KEYWORD1 +BLERoleBase KEYWORD1 +BLECentralRole KEYWORD1 +BLEPeripheralRole KEYWORD1 BLECharCharacteristic KEYWORD1 BLEUnsignedCharCharacteristic KEYWORD1 @@ -38,11 +42,16 @@ numAttributes KEYWORD2 connected KEYWORD2 address KEYWORD2 poll KEYWORD2 +connect KEYWORD2 disconnect KEYWORD2 discover KEYWORD2 startScan KEYWORD2 stopScan KEYWORD2 +setScanParam KEYWORD2 +getScanParam KEYWORD2 +setAdvertiseHandler KEYWORD2 + getConnParams KEYWORD2 setConnectionInterval KEYWORD2 @@ -54,8 +63,10 @@ setValue KEYWORD2 setEventHandler KEYWORD2 written KEYWORD2 subscribed KEYWORD2 +numNotifyAttributes KEYWORD2 begin KEYWORD2 +end KEYWORD2 stopAdvertising KEYWORD2 setConnectable KEYWORD2 @@ -66,13 +77,19 @@ setLocalName KEYWORD2 setDeviceName KEYWORD2 setAppearance KEYWORD2 setConnectionInterval KEYWORD2 +setConnectionParameters KEYWORD2 addAttribute KEYWORD2 +attribute KEYWORD2 central KEYWORD2 +peripheral KEYWORD2 setValueLE KEYWORD2 valueLE KEYWORD2 setValueBE KEYWORD2 valueBE KEYWORD2 +valueHandle KEYWORD2 +registerProfile KEYWORD2 +cccdHandle KEYWORD2 getPeerPeripheralBLE KEYWORD2 getPeerCentralBLE KEYWORD2 From 0bbf5a6c151171f09b7b5b427cb0ccef941b9454 Mon Sep 17 00:00:00 2001 From: Biagio Montaruli Date: Sun, 18 Sep 2016 09:52:51 +0200 Subject: [PATCH 14/14] Update sketches of CurieBLE library - Formatting sketches of CurieBLE library with the Auto Format tool of Arduino IDE - Improve documentation of some sketches - Improve the use of USB virtual serial port : Arduino/Genuino 101 uses USB native port as Serial port, moreover the sketches of CurieBLE library use Serial communication and they send data to the Serial Monitor in the 'void setup()' function, so adding 'while(!Serial)' waits for the Serial port to connect in order to not loose data already sent to the Serial Monitor. Added also documentation to inform the user to open the Serial Monitor to continue executing the sketch because 'while(!Serial)' also waits for the user to open the Serial Monitor Signed-off-by: Biagio Montaruli --- .../BatteryAdvChange/BatteryAdvChange.ino | 232 ++++++++-------- .../BatteryMonitor/BatteryMonitor.ino | 34 +-- .../CurieBLE/examples/ButtonLED/ButtonLED.ino | 22 +- .../examples/CallbackLED/CallbackLED.ino | 22 +- .../examples/IMUBleCentral/IMUBleCentral.ino | 196 +++++++------- .../IMUBleNotification/IMUBleNotification.ino | 153 +++++------ libraries/CurieBLE/examples/LED/LED.ino | 20 +- .../examples/LEDCentral/LEDCentral.ino | 200 +++++++------- .../CurieBLE/examples/MIDIBLE/MIDIBLE.ino | 10 +- .../CurieBLE/examples/Scanning/Scanning.ino | 157 +++++------ .../UpdateConnectionInterval.ino | 250 +++++++++--------- 11 files changed, 670 insertions(+), 626 deletions(-) diff --git a/libraries/CurieBLE/examples/BatteryAdvChange/BatteryAdvChange.ino b/libraries/CurieBLE/examples/BatteryAdvChange/BatteryAdvChange.ino index 8b977a39..0f15afc5 100644 --- a/libraries/CurieBLE/examples/BatteryAdvChange/BatteryAdvChange.ino +++ b/libraries/CurieBLE/examples/BatteryAdvChange/BatteryAdvChange.ino @@ -1,116 +1,116 @@ -/* Please see code copyright at the bottom of this example code */ - -/* - This example can work with phone BLE app. - - This sketch illustrates how to change the advertising data so that it is visible but not - connectable. Then after 10 seconds it changes to being connectable. - This sketch example partially implements the standard Bluetooth Low-Energy Battery service. - - This sketch is not paired with a specific central example sketch, - but to see how it works you need to use a BLE APP on your phone or central device - and try connecting when it is either a connectable or not connectable state - as displayed in the serial monitor. -*/ - -#include - -BLEPeripheral blePeripheral; // BLE Peripheral Device (the board you're programming) -BLEService batteryService("180F"); // BLE Battery Service -int count = 0; -// BLE Battery Level Characteristic" -BLEUnsignedCharCharacteristic batteryLevelChar("2A19", // standard 16-bit characteristic UUID - BLERead | BLENotify); // remote clients will be able to -// get notifications if this characteristic changes - -void setup() { - Serial.begin(9600); // initialize serial communication - pinMode(13, OUTPUT); // initialize the LED on pin 13 to indicate when a central is connected - while (!Serial) { - //wait for Serial to connect - } - /* Set a local name for the BLE device - This name will appear in advertising packets - and can be used by remote devices to identify this BLE device - The name can be changed but maybe be truncated based on space left in advertisement packet */ - blePeripheral.setLocalName("BatteryAdvChangeSketch"); - blePeripheral.setAdvertisedServiceUuid(batteryService.uuid()); // add the service UUID - blePeripheral.addAttribute(batteryService); // Add the BLE Battery service - blePeripheral.addAttribute(batteryLevelChar); // add the battery level characteristic - - /* Now activate the BLE device. It will start continuously transmitting BLE - advertising packets and will be visible to remote BLE central devices - until it receives a new connection */ - - blePeripheral.begin(); - Serial.println("Bluetooth device active, waiting for connections..."); - Serial.println("Starts in Connectable mode"); -} - -void loop() { - // listen for BLE peripherals to connect: - BLECentralHelper central = blePeripheral.central(); - // wait - Serial.print(". "); - if (count == 10) { - Serial.print("\nReached count "); - Serial.println(count); - - } - delay (1000); - count++; - // Switch from Connectable to Non Connectable and vice versa - if (count > 10 ) { - static bool change_discover = false; - Serial.println("Stop Adv and pausing for 10 seconds. Device should be invisible"); - // Some central devices (phones included) may cache previous scan inofrmation - // restart your central and it should not see this peripheral once stopAdvertising() is called - blePeripheral.stopAdvertising(); - delay(10000); - - if (change_discover) - { - - // Using the function setConnectable we specify that it now NOT connectable - // The loop is for 10 seconds. Your central device may timeout later than that - // and may eventually connect when we set it back to connectable mode below - blePeripheral.setConnectable(false); - Serial.println("In Non Connectable mode"); - - } - else - { - - //using the function setConnectable we specify that it now connectable - blePeripheral.setConnectable(true); - Serial.println("In Connectable mode"); - } - Serial.println("Start Adv"); - blePeripheral.startAdvertising(); - if (change_discover) { - Serial.println("Adding 5 second delay in Non Connect Mode"); - delay(5000); - } - change_discover = !change_discover; - count = 0; - } -} - -/* - 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 -*/ - +/* Please see code copyright at the bottom of this example code */ + +/* + This example can work with phone BLE app. + + This sketch illustrates how to change the advertising data so that it is visible but not + connectable. Then after 10 seconds it changes to being connectable. + This sketch example partially implements the standard Bluetooth Low-Energy Battery service. + + This sketch is not paired with a specific central example sketch, + but to see how it works you need to use a BLE APP on your phone or central device + and try connecting when it is either a connectable or not connectable state + as displayed in the serial monitor. +*/ + +#include + +BLEPeripheral blePeripheral; // BLE Peripheral Device (the board you're programming) +BLEService batteryService("180F"); // BLE Battery Service +int count = 0; +// BLE Battery Level Characteristic" +BLEUnsignedCharCharacteristic batteryLevelChar("2A19", // standard 16-bit characteristic UUID + BLERead | BLENotify); // remote clients will be able to +// get notifications if this characteristic changes + +void setup() { + Serial.begin(9600); // initialize serial communication + pinMode(13, OUTPUT); // initialize the LED on pin 13 to indicate when a central is connected + while (!Serial) { + ; //wait for Serial to connect + } + /* Set a local name for the BLE device + This name will appear in advertising packets + and can be used by remote devices to identify this BLE device + The name can be changed but maybe be truncated based on space left in advertisement packet */ + blePeripheral.setLocalName("BatteryAdvChangeSketch"); + blePeripheral.setAdvertisedServiceUuid(batteryService.uuid()); // add the service UUID + blePeripheral.addAttribute(batteryService); // Add the BLE Battery service + blePeripheral.addAttribute(batteryLevelChar); // add the battery level characteristic + + /* Now activate the BLE device. It will start continuously transmitting BLE + advertising packets and will be visible to remote BLE central devices + until it receives a new connection */ + + blePeripheral.begin(); + Serial.println("Bluetooth device active, waiting for connections..."); + Serial.println("Starts in Connectable mode"); +} + +void loop() { + // listen for BLE peripherals to connect: + BLECentralHelper central = blePeripheral.central(); + // wait + Serial.print(". "); + if (count == 10) { + Serial.print("\nReached count "); + Serial.println(count); + + } + delay (1000); + count++; + // Switch from Connectable to Non Connectable and vice versa + if (count > 10 ) { + static bool change_discover = false; + Serial.println("Stop Adv and pausing for 10 seconds. Device should be invisible"); + // Some central devices (phones included) may cache previous scan inofrmation + // restart your central and it should not see this peripheral once stopAdvertising() is called + blePeripheral.stopAdvertising(); + delay(10000); + + if (change_discover) + { + + // Using the function setConnectable we specify that it now NOT connectable + // The loop is for 10 seconds. Your central device may timeout later than that + // and may eventually connect when we set it back to connectable mode below + blePeripheral.setConnectable(false); + Serial.println("In Non Connectable mode"); + + } + else + { + + //using the function setConnectable we specify that it now connectable + blePeripheral.setConnectable(true); + Serial.println("In Connectable mode"); + } + Serial.println("Start Adv"); + blePeripheral.startAdvertising(); + if (change_discover) { + Serial.println("Adding 5 second delay in Non Connect Mode"); + delay(5000); + } + change_discover = !change_discover; + count = 0; + } +} + +/* + 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 +*/ + diff --git a/libraries/CurieBLE/examples/BatteryMonitor/BatteryMonitor.ino b/libraries/CurieBLE/examples/BatteryMonitor/BatteryMonitor.ino index e3a297cf..4da03e8a 100644 --- a/libraries/CurieBLE/examples/BatteryMonitor/BatteryMonitor.ino +++ b/libraries/CurieBLE/examples/BatteryMonitor/BatteryMonitor.ino @@ -1,7 +1,7 @@ /* - * Copyright (c) 2016 Intel Corporation. All rights reserved. - * See the bottom of this file for the license terms. - */ + Copyright (c) 2016 Intel Corporation. All rights reserved. + See the bottom of this file for the license terms. +*/ #include @@ -21,14 +21,18 @@ BLEService batteryService("180F"); // BLE Battery Service // BLE Battery Level Characteristic" BLEUnsignedCharCharacteristic batteryLevelChar("2A19", // standard 16-bit characteristic UUID defined in the URL above BLERead | BLENotify); // remote clients will be able to - // get notifications if this characteristic changes +// get notifications if this characteristic changes int oldBatteryLevel = 0; // last battery level reading from analog input long previousMillis = 0; // last time the battery level was checked, in ms void setup() { Serial.begin(9600); // initialize serial communication - pinMode(13, OUTPUT); // initialize the LED on pin 13 to indicate when a central is connected + // wait for the Serial port to connect. Open the Serial Monitor to continue executing the sketch + while (!Serial) { + ; + } + pinMode(LED_BUILTIN, OUTPUT); // initialize the LED on pin 13 to indicate when a central is connected /* Set a local name for the BLE device This name will appear in advertising packets @@ -57,7 +61,7 @@ void loop() { // print the central's MAC address: Serial.println(central.address()); // turn on the LED to indicate the connection: - digitalWrite(13, HIGH); + digitalWrite(LED_BUILTIN, HIGH); // check the battery level every 200ms // as long as the central is still connected: @@ -68,17 +72,17 @@ void loop() { previousMillis = currentMillis; updateBatteryLevel(); - static unsigned short count = 0; + static unsigned short count = 0; count++; // update the connection interval - if(count%5 == 0){ + if (count % 5 == 0) { delay(1000); - updateIntervalParams(central); + updateIntervalParams(central); } } } // when the central disconnects, turn off the LED: - digitalWrite(13, LOW); + digitalWrite(LED_BUILTIN, LOW); Serial.print("Disconnected from central: "); Serial.println(central.address()); } @@ -113,16 +117,16 @@ void updateIntervalParams(BLECentralHelper ¢ral) { Serial.println(m_conn_param.latency ); Serial.print("timeout = " ); Serial.println(m_conn_param.timeout ); - + //Update connection interval Serial.println("set Connection Interval"); - central.setConnectionInterval(interval,interval); + central.setConnectionInterval(interval, interval); interval++; - if(interval<0x06) + if (interval < 0x06) + interval = 0x06; + if (interval > 0x100) interval = 0x06; - if(interval>0x100) - interval = 0x06; } /* Copyright (c) 2016 Intel Corporation. All rights reserved. diff --git a/libraries/CurieBLE/examples/ButtonLED/ButtonLED.ino b/libraries/CurieBLE/examples/ButtonLED/ButtonLED.ino index 7a46c164..9c74039f 100644 --- a/libraries/CurieBLE/examples/ButtonLED/ButtonLED.ino +++ b/libraries/CurieBLE/examples/ButtonLED/ButtonLED.ino @@ -1,7 +1,7 @@ /* - * Copyright (c) 2016 Intel Corporation. All rights reserved. - * See the bottom of this file for the license terms. - */ + Copyright (c) 2016 Intel Corporation. All rights reserved. + See the bottom of this file for the license terms. +*/ /* This example can work with phone BLE app. @@ -11,7 +11,7 @@ After the sketch starts connect to a BLE app on a phone and set notification to the Characteristic and you should see it update whenever the button is pressed. This sketch is not written to pair with any of the central examples. */ - + #include const int ledPin = 13; // set ledPin to on-board LED @@ -19,7 +19,7 @@ const int buttonPin = 4; // set buttonPin to digital pin 4 BLEPeripheral blePeripheral; // create peripheral instance BLEService ledService("19B10010-E8F2-537E-4F6C-D104768A1214"); // create service with a 128-bit UUID (32 characters exclusive of dashes). - // Long UUID denote custom user created UUID +// Long UUID denote custom user created UUID // create switch characteristic and allow remote device to read and write @@ -30,6 +30,10 @@ BLECharCharacteristic buttonCharacteristic("19B10012-E8F2-537E-4F6C-D104768A1214 void setup() { Serial.begin(9600); + // wait for the Serial port to connect. Open the Serial Monitor to continue executing the sketch + while (!Serial) { + ; + } pinMode(ledPin, OUTPUT); // use the LED on pin 13 as an output pinMode(buttonPin, INPUT); // use button pin 4 as an input @@ -70,14 +74,14 @@ void loop() { } if (ledCharacteristic.written() || buttonChanged) { - // update LED, either central has written to characteristic or button state has changed - // if you are using a phone or a BLE central device that is aware of this characteristic, writing a value of 0x40 for example - // Will be interpreted as written + // update LED, either central has written to characteristic or button state has changed. + // If you are using a phone or a BLE central device that is aware of this characteristic, + // writing a value of 0x40 for example will be interpreted as written if (ledCharacteristic.value()) { Serial.println("LED on"); digitalWrite(ledPin, HIGH); } else { - // If central writes a 0 value then it is interpreted as no value and turns off the LED + // If central writes a 0 value (0x00) then it is interpreted as no value and turns off the LED Serial.println("LED off"); digitalWrite(ledPin, LOW); } diff --git a/libraries/CurieBLE/examples/CallbackLED/CallbackLED.ino b/libraries/CurieBLE/examples/CallbackLED/CallbackLED.ino index 2a661189..ba8556ec 100644 --- a/libraries/CurieBLE/examples/CallbackLED/CallbackLED.ino +++ b/libraries/CurieBLE/examples/CallbackLED/CallbackLED.ino @@ -1,9 +1,9 @@ /* - * Copyright (c) 2016 Intel Corporation. All rights reserved. - * See the bottom of this file for the license terms. - */ - - /* + Copyright (c) 2016 Intel Corporation. All rights reserved. + See the bottom of this file for the license terms. +*/ + +/* This example can work with LEDCentral. You should see the LED blink on and off. @@ -13,22 +13,26 @@ Connect to BLE device named LEDCB and explore characteristic with UUID 19B10001-E8F2-537E-4F6C-D104768A1214. Writing a byte value such as 0x40 should turn on the LED. Writing a byte value of 0x00 should turn off the LED. - */ +*/ #include const int ledPin = 13; // set ledPin to use on-board LED BLEPeripheral blePeripheral; // create peripheral instance -BLECentralHelper *bleCentral1 = NULL; // peer central device +BLECentralHelper *bleCentral1 = NULL; // peer central device BLEService ledService("19B10000-E8F2-537E-4F6C-D104768A1214"); // create service with a 128-bit UUID (32 characters exclusive of dashes). - // Long UUID denote custom user created UUID +// Long UUID denote custom user created UUID // create switch characteristic and allow remote device to read and write BLECharCharacteristic switchChar("19B10001-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite); void setup() { Serial.begin(9600); + // wait for the Serial port to connect. Open the Serial Monitor to continue executing the sketch + while (!Serial) { + ; + } pinMode(ledPin, OUTPUT); // use the LED on pin 13 as an output // set the local name peripheral advertises @@ -46,7 +50,7 @@ void setup() { // assign event handlers for characteristic switchChar.setEventHandler(BLEWritten, switchCharacteristicWritten); -// set an initial value for the characteristic + // set an initial value for the characteristic switchChar.setValue(0); // advertise the service diff --git a/libraries/CurieBLE/examples/IMUBleCentral/IMUBleCentral.ino b/libraries/CurieBLE/examples/IMUBleCentral/IMUBleCentral.ino index a5359509..54de854d 100644 --- a/libraries/CurieBLE/examples/IMUBleCentral/IMUBleCentral.ino +++ b/libraries/CurieBLE/examples/IMUBleCentral/IMUBleCentral.ino @@ -1,7 +1,7 @@ /* - * Copyright (c) 2016 Intel Corporation. All rights reserved. - * See the bottom of this file for the license terms. - */ + Copyright (c) 2016 Intel Corporation. All rights reserved. + See the bottom of this file for the license terms. +*/ #include @@ -18,14 +18,14 @@ ble_conn_param_t conn_param = {30.0, // minimum interval in ms 7.5 - 4000 50.0, // maximum interval in ms 7.5 - - 0, // latency + 0, // latency 4000 // timeout in ms 100 - 32000ms - }; + }; // define a structure that will serve as buffer for holding IMU data typedef struct { - int index; - unsigned int slot[3]; + int index; + unsigned int slot[3]; } imuFrameType; imuFrameType imuBuf[MAX_IMU_RECORD]; @@ -34,11 +34,11 @@ BLECentral bleCentral; // BLE Central Device (the board you're programming) BLEService bleImuService("F7580001-153E-D4F6-F26D-43D8D98EEB13"); BLECharacteristic bleImuChar("F7580003-153E-D4F6-F26D-43D8D98EEB13", // standard 128-bit characteristic UUID BLERead | BLENotify, sizeof(imuBuf)); // remote clients will be able to - // get notifications if this characteristic changes - // We have a third parameter which is the size of imyBuffer. This is because it is a non-typed characteristic - // If we are only writing to this characteristic we can set this buffer to 512 bytes - // But because of the limitation of the Nordic FW, please do not set this to more than 128 if you intend to read it. - // MAX_IMU_RECORD value is 1 so we are safe +// get notifications if this characteristic changes +// We have a third parameter which is the size of imyBuffer. This is because it is a non-typed characteristic +// If we are only writing to this characteristic we can set this buffer to 512 bytes +// But because of the limitation of the Nordic FW, please do not set this to more than 128 if you intend to read it. +// MAX_IMU_RECORD value is 1 so we are safe // function prototype for function that determines if the advertising data is found bool adv_found(uint8_t type, const uint8_t *dataPtr, @@ -47,110 +47,116 @@ bool adv_found(uint8_t type, void setup() { - // This is set to higher baud rate because accelerometer data changes very quickly - Serial.begin(115200); // initialize serial communication - pinMode(13, OUTPUT); // initialize the LED on pin 13 to indicate when a central is connected - // set the event handeler function for the bleImuChar characteristic - bleImuChar.setEventHandler(BLEWritten, bleImuCharacteristicWritten); - - bleCentral.addAttribute(bleImuService); // Add the BLE IMU service - bleCentral.addAttribute(bleImuChar); // Add the BLE IMU characteristic - - // Setup callback whenever a Peripheral advertising data is found) - bleCentral.setAdvertiseHandler(adv_found); - bleCentral.setEventHandler(BLEConnected, ble_connected); - - /* Now activate the BLE device. It will start continuously transmitting BLE - advertising packets and will be visible to remote BLE central devices - until it receives a new connection */ - bleCentral.begin(); + // This is set to higher baud rate because accelerometer data changes very quickly + Serial.begin(115200); // initialize serial communication + // wait for the Serial port to connect. Open the Serial Monitor to continue executing the sketch + while (!Serial) { + ; + } + pinMode(LED_BUILTIN, OUTPUT); // initialize the LED on pin 13 to indicate when a central is connected + // set the event handeler function for the bleImuChar characteristic + bleImuChar.setEventHandler(BLEWritten, bleImuCharacteristicWritten); + + bleCentral.addAttribute(bleImuService); // Add the BLE IMU service + bleCentral.addAttribute(bleImuChar); // Add the BLE IMU characteristic + + // Setup callback whenever a Peripheral advertising data is found) + bleCentral.setAdvertiseHandler(adv_found); + bleCentral.setEventHandler(BLEConnected, ble_connected); + + /* Now activate the BLE device. It will start continuously transmitting BLE + advertising packets and will be visible to remote BLE central devices + until it receives a new connection */ + bleCentral.begin(); } void loop() { - // we put a 2 second delay - // Even though this looks empty, since we setup 2 callbacks by setting the advertising handler adv_found - // and event handler for BLEConnected, we basically are lsitening for advertising data and connected events. - - delay(2000); + // we put a 2 second delay + // Even though this looks empty, since we setup 2 callbacks by setting the advertising handler adv_found + // and event handler for BLEConnected, we basically are lsitening for advertising data and connected events. + + delay(2000); } void ble_connected(BLEHelper &role) { - // since we are a central device we create a BLEPeripheralHelper peripheral - BLEPeripheralHelper *peripheral = bleCentral.getPeerPeripheralBLE(role); - Serial.println("Connected"); - - // Start discovery the profiles in peripheral device - peripheral->discover(); + // since we are a central device we create a BLEPeripheralHelper peripheral + BLEPeripheralHelper *peripheral = bleCentral.getPeerPeripheralBLE(role); + Serial.println("Connected"); + // turn the on-board led on to indicate that we (the central device) are connected to a peripheral device + digitalWrite(LED_BUILTIN, HIGH); + + // Start discovery the profiles in peripheral device + peripheral->discover(); } void bleImuCharacteristicWritten(BLEHelper& peripheral, BLECharacteristic& characteristic) { - // Peripheral wrote new value to characteristic by Notification/Indication - // We have to use pointers because we are NOT using a type characteristic - // In other examples our charcteristics are typed so we not have to use pointers and can access the value directly - // The parent non typde characteristic class, the value method gives a pointer to the characteristic value - - const unsigned char *cvalue = characteristic.value(); - const imuFrameType *value = (const imuFrameType *)cvalue; - Serial.print("\r\nCharacteristic event, written: "); - Serial.print(value->index); - Serial.print("\t"); - Serial.print(value->slot[0]); - Serial.print("\t"); - Serial.print(value->slot[1]); - Serial.print("\t"); - Serial.println(value->slot[2]); + // Peripheral wrote new value to characteristic by Notification/Indication + // We have to use pointers because we are NOT using a type characteristic + // In other examples our charcteristics are typed so we not have to use pointers and can access the value directly + // The parent non typde characteristic class, the value method gives a pointer to the characteristic value + + const unsigned char *cvalue = characteristic.value(); + const imuFrameType *value = (const imuFrameType *)cvalue; + Serial.print("\r\nCharacteristic event, written: "); + Serial.print(value->index); + Serial.print("\t"); + Serial.print(value->slot[0]); + Serial.print("\t"); + Serial.print(value->slot[1]); + Serial.print("\t"); + Serial.println(value->slot[2]); } -bool adv_found(uint8_t type, - const uint8_t *dataPtr, +bool adv_found(uint8_t type, + const uint8_t *dataPtr, uint8_t data_len, const bt_addr_le_t *addrPtr) { - int i; - - Serial.print("[AD]:"); - Serial.print(type); - Serial.print(" data_len "); - Serial.println(data_len); - // 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. - switch (type) - { - case BT_DATA_UUID128_SOME: - case BT_DATA_UUID128_ALL: + int i; + + Serial.print("[AD]:"); + Serial.print(type); + Serial.print(" data_len "); + Serial.println(data_len); + // 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. + 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 (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"); - // Connect to peripheral - bleCentral.connect(addrPtr, &conn_param); - return false; - } + 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"); + // Connect to peripheral + bleCentral.connect(addrPtr, &conn_param); + return false; } - } + } + } - return true; + return true; } /* diff --git a/libraries/CurieBLE/examples/IMUBleNotification/IMUBleNotification.ino b/libraries/CurieBLE/examples/IMUBleNotification/IMUBleNotification.ino index ee57d295..8d99d5c8 100644 --- a/libraries/CurieBLE/examples/IMUBleNotification/IMUBleNotification.ino +++ b/libraries/CurieBLE/examples/IMUBleNotification/IMUBleNotification.ino @@ -1,7 +1,7 @@ /* - * Copyright (c) 2016 Intel Corporation. All rights reserved. - * See the bottom of this file for the license terms. - */ + Copyright (c) 2016 Intel Corporation. All rights reserved. + See the bottom of this file for the license terms. +*/ #include #include @@ -14,101 +14,106 @@ */ #define MAX_IMU_RECORD 1 +#define IMU_BUF_INDEX 0 typedef struct { - int index; - unsigned int slot[3]; + int index; + unsigned int slot[3]; } imuFrameType; // Buffer to hold IMU data imuFrameType imuBuf[MAX_IMU_RECORD]; -unsigned seqNum = 0; +unsigned int seqNum = 0; BLEPeripheral blePeripheral; // BLE Peripheral Device (the board you're programming) BLEService bleImuService("F7580001-153E-D4F6-F26D-43D8D98EEB13"); // Tx IMU data Characteristic BLECharacteristic bleImuChar("F7580003-153E-D4F6-F26D-43D8D98EEB13", // standard 128-bit characteristic UUID BLERead | BLENotify, sizeof(imuBuf)); // remote clients will be able to - // get notifications if this characteristic changes +// get notifications if this characteristic changes void setup() { - Serial.begin(9600); // initialize serial communication - pinMode(13, OUTPUT); // initialize the LED on pin 13 to indicate when a central is connected - - - /* Set a local name for the BLE device - This name will appear in advertising packets - and can be used by remote devices to identify this BLE device - The name can be changed but maybe be truncated based on space left in advertisement packet */ - blePeripheral.setLocalName("Imu"); - blePeripheral.setAdvertisedServiceUuid(bleImuService.uuid()); // add the service UUID - blePeripheral.addAttribute(bleImuService); // Add the Imu service - blePeripheral.addAttribute(bleImuChar); // add the Imu characteristic - - /* Now activate the BLE device. It will start continuously transmitting BLE - advertising packets and will be visible to remote BLE central devices - until it receives a new connection */ - blePeripheral.begin(); - // Start the IMU - CurieIMU.begin(); + Serial.begin(9600); // initialize serial communication + // wait for the Serial port to connect. Open the Serial Monitor to continue executing the sketch + while (!Serial) { + ; + } + pinMode(LED_BUILTIN, OUTPUT); // initialize the LED on pin 13 to indicate when a central is connected + + + /* Set a local name for the BLE device + This name will appear in advertising packets + and can be used by remote devices to identify this BLE device + The name can be changed but maybe be truncated based on space left in advertisement packet */ + blePeripheral.setLocalName("Imu"); + blePeripheral.setAdvertisedServiceUuid(bleImuService.uuid()); // add the service UUID + blePeripheral.addAttribute(bleImuService); // Add the Imu service + blePeripheral.addAttribute(bleImuChar); // add the Imu characteristic + + /* Now activate the BLE device. It will start continuously transmitting BLE + advertising packets and will be visible to remote BLE central devices + until it receives a new connection */ + blePeripheral.begin(); + // Start the IMU + CurieIMU.begin(); } void loop() { - // listen for BLE peripherals to connect: - // Since we are a peripheral we need a central object to connect to - BLECentralHelper central = blePeripheral.central(); + // listen for BLE peripherals to connect: + // Since we are a peripheral we need a central object to connect to + BLECentralHelper central = blePeripheral.central(); + + // if a central is connected to peripheral: + if (central) + { + Serial.print("Connected to central: "); + // print the central's MAC address: + Serial.println(central.address()); + + Serial.print("IMU buffer size: "); + Serial.println(sizeof(imuBuf)); + + // turn on the LED to indicate the connection: + digitalWrite(LED_BUILTIN, HIGH); + + long currentMillis, sentTime; - // if a central is connected to peripheral: - if (central) + // Send IMU data as long as the central is still connected + currentMillis = sentTime = millis(); + while (central.connected()) { - Serial.print("Connected to central: "); - // print the central's MAC address: - Serial.println(central.address()); - - Serial.print("IMU buffer size: "); - Serial.println(sizeof(imuBuf)); - - // turn on the LED to indicate the connection: - digitalWrite(13, HIGH); - - long currentMillis, sentTime; - - // Send IMU data as long as the central is still connected - currentMillis = sentTime = millis(); - while (central.connected()) - { - // Take IMU data every 100 msec - if ((millis() - sentTime) >= 100) - { - recordImuData(0); - sentTime = millis(); - bleImuChar.setValue((unsigned char *)&(imuBuf[0]), sizeof(imuBuf)); - } - } // end of while loop - - // when the central disconnects, turn off the LED: - digitalWrite(13, LOW); - Serial.print("Disconnected from central: "); - Serial.println(central.address()); - } + // Take IMU data every 100 msec + if ((millis() - sentTime) >= 100) + { + recordImuData(IMU_BUF_INDEX); + sentTime = millis(); + bleImuChar.setValue((unsigned char *) & (imuBuf[IMU_BUF_INDEX]), sizeof(imuBuf)); + } + } // end of while loop + + // when the central disconnects, turn off the LED: + digitalWrite(LED_BUILTIN, LOW); + Serial.print("Disconnected from central: "); + Serial.println(central.address()); + } } -// This function records the IMU data that we send to the central +// This function records the IMU data that we send to the central void recordImuData(int index) { - /* Read IMU data. - */ - int ax, ay, az; - int gx, gy, gz; - - imuBuf[index].index = seqNum++; - CurieIMU.readMotionSensor(ax, ay, az, gx, gy, gz); - - // Encode the data into the buffer - imuBuf[index].slot[0] = (unsigned int)((ax << 16) | (ay & 0x0FFFF)); - imuBuf[index].slot[1] = (unsigned int)((az << 16) | (gx & 0x0FFFF)); - imuBuf[index].slot[2] = (unsigned int)((gy << 16) | (gz & 0x0FFFF)); + /* Read IMU data. + */ + int ax, ay, az; + int gx, gy, gz; + + imuBuf[index].index = seqNum++; + CurieIMU.readMotionSensor(ax, ay, az, gx, gy, gz); + + // Encode the data into the buffer + imuBuf[index].slot[0] = (unsigned int)((ax << 16) | (ay & 0x0FFFF)); + imuBuf[index].slot[1] = (unsigned int)((az << 16) | (gx & 0x0FFFF)); + imuBuf[index].slot[2] = (unsigned int)((gy << 16) | (gz & 0x0FFFF)); } diff --git a/libraries/CurieBLE/examples/LED/LED.ino b/libraries/CurieBLE/examples/LED/LED.ino index 34c26a8c..9b9474ad 100644 --- a/libraries/CurieBLE/examples/LED/LED.ino +++ b/libraries/CurieBLE/examples/LED/LED.ino @@ -1,20 +1,20 @@ /* - * Copyright (c) 2016 Intel Corporation. All rights reserved. - * See the bottom of this file for the license terms. - */ - - /* + Copyright (c) 2016 Intel Corporation. All rights reserved. + See the bottom of this file for the license terms. +*/ + +/* This example can work with LEDCentral This example is similar to CallbackLED example in functionality. It does not use callbacks. In the loop it interogates the connection state with central. Checks if the characteristic is written and turns the LED on or off accordingly. To test interactively, use a phone app like nrf Controller (Android) or Light Blue (iOS). - Connect to BLE device named LEDCB and explore characteristic with UUID 19B10001-E8F2-537E-4F6C-D104768A1214. + Connect to BLE device named LED and explore characteristic with UUID 19B10001-E8F2-537E-4F6C-D104768A1214. Writing a byte value such as 0x40 should turn on the LED. Writing a byte value of 0x00 should turn off the LED. - */ - +*/ + #include BLEPeripheral blePeripheral; // BLE Peripheral Device (the board you're programming) @@ -27,6 +27,10 @@ const int ledPin = 13; // pin to use for the LED void setup() { Serial.begin(9600); + // wait for the Serial port to connect. Open the Serial Monitor to continue executing the sketch + while (!Serial) { + ; + } // set LED pin to output mode pinMode(ledPin, OUTPUT); diff --git a/libraries/CurieBLE/examples/LEDCentral/LEDCentral.ino b/libraries/CurieBLE/examples/LEDCentral/LEDCentral.ino index e26e9451..6875e53b 100644 --- a/libraries/CurieBLE/examples/LEDCentral/LEDCentral.ino +++ b/libraries/CurieBLE/examples/LEDCentral/LEDCentral.ino @@ -1,11 +1,11 @@ /* - * Copyright (c) 2016 Intel Corporation. All rights reserved. - * See the bottom of this file for the license terms. - */ + Copyright (c) 2016 Intel Corporation. All rights reserved. + See the bottom of this file for the license terms. +*/ #include -/* +/* This example can work with CallbackLED and LED sketches. To show how a central device can do charcteristic read and write operations. @@ -16,16 +16,16 @@ ble_conn_param_t conn_param = {30.0, // minimum interval in ms 7.5 - 4000 50.0, // maximum interval in ms 7.5 - - 0, // latency + 0, // latency 4000 // timeout in ms 100 - 32000ms - }; + }; const int ledPin = 13; // set ledPin to use on-board LED BLECentral bleCentral; // create central instance BLEPeripheralHelper *blePeripheral1 = NULL; // peer peripheral device BLEService ledService("19B10000-E8F2-537E-4F6C-D104768A1214"); // create service with a 128-bit UUID (32 characters exclusive of dashes). - // Long UUID denote custom user created UUID +// Long UUID denote custom user created UUID BLECharCharacteristic switchChar("19B10001-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite);// create switch characteristic and allow remote device to read and write // function prototype for function that determines if the advertising data is found @@ -36,128 +36,132 @@ bool adv_found(uint8_t type, void setup() { - Serial.begin(9600); - pinMode(ledPin, OUTPUT); // use the LED on pin 13 as an output + Serial.begin(9600); + // wait for the Serial port to connect. Open the Serial Monitor to continue executing the sketch + while (!Serial) { + ; + } + pinMode(ledPin, OUTPUT); // use the LED on pin 13 as an output - // add service and characteristic - bleCentral.addAttribute(ledService); - bleCentral.addAttribute(switchChar); + // add service and characteristic + bleCentral.addAttribute(ledService); + bleCentral.addAttribute(switchChar); - // assign event handlers for connected, disconnected to central - bleCentral.setEventHandler(BLEConnected, bleCentralConnectHandler); - bleCentral.setEventHandler(BLEDisconnected, bleCentralDisconnectHandler); + // assign event handlers for connected, disconnected to central + bleCentral.setEventHandler(BLEConnected, bleCentralConnectHandler); + bleCentral.setEventHandler(BLEDisconnected, bleCentralDisconnectHandler); - // advertise the service - bleCentral.setAdvertiseHandler(adv_found); + // advertise the service + bleCentral.setAdvertiseHandler(adv_found); - // assign event handlers for characteristic - switchChar.setEventHandler(BLEWritten, switchCharacteristicWritten); + // assign event handlers for characteristic + switchChar.setEventHandler(BLEWritten, switchCharacteristicWritten); - bleCentral.begin(); - Serial.println(("Bluetooth device active, waiting for connections...")); + bleCentral.begin(); + Serial.println("Bluetooth device active, waiting for connections..."); } void loop() { - static unsigned int counter = 0; - static char ledstate = 0; - delay(2000); - - if (blePeripheral1) + static unsigned int counter = 0; + static char ledstate = 0; + delay(2000); + + if (blePeripheral1) + { + counter++; + if (counter % 3) { - counter++; - if (counter % 3) - { - switchChar.read(*blePeripheral1); - } - else - { - ledstate = !ledstate; - switchChar.write(*blePeripheral1, ledstate); - } + switchChar.read(*blePeripheral1); + } + else + { + ledstate = !ledstate; + switchChar.write(*blePeripheral1, ledstate); } + } } void bleCentralConnectHandler(BLEHelper& peripheral) { - // peripheral connected event handler - blePeripheral1 = bleCentral.getPeerPeripheralBLE(peripheral); - Serial.print("Connected event, peripheral: "); - Serial.println(blePeripheral1->address()); - // Start discovery the profiles in peripheral device - blePeripheral1->discover(); + // peripheral connected event handler + blePeripheral1 = bleCentral.getPeerPeripheralBLE(peripheral); + Serial.print("Connected event, peripheral: "); + Serial.println(blePeripheral1->address()); + // Start discovery the profiles in peripheral device + blePeripheral1->discover(); } void bleCentralDisconnectHandler(BLEHelper& peripheral) { - // peripheral disconnected event handler - blePeripheral1 = NULL; - Serial.print("Disconnected event, peripheral: "); - Serial.println(peripheral.address()); - bleCentral.startScan(); + // peripheral disconnected event handler + blePeripheral1 = NULL; + Serial.print("Disconnected event, peripheral: "); + Serial.println(peripheral.address()); + bleCentral.startScan(); } void switchCharacteristicWritten(BLEHelper& peripheral, BLECharacteristic& characteristic) { - // Read response/Notification wrote new value to characteristic, update LED - Serial.print("Characteristic event, written: "); - - if (switchChar.value()) - { - Serial.println("LED on"); - digitalWrite(ledPin, HIGH); - } - else - { - Serial.println("LED off"); - digitalWrite(ledPin, LOW); - } + // Read response/Notification wrote new value to characteristic, update LED + Serial.print("Characteristic event, written: "); + + if (switchChar.value()) + { + Serial.println("LED on"); + digitalWrite(ledPin, HIGH); + } + else + { + Serial.println("LED off"); + digitalWrite(ledPin, LOW); + } } -bool adv_found(uint8_t type, - const uint8_t *dataPtr, +bool adv_found(uint8_t type, + const uint8_t *dataPtr, uint8_t data_len, const bt_addr_le_t *addrPtr) { - int i; - - Serial.print("[AD]:"); - Serial.print(type); - Serial.print(" data_len "); - Serial.println(data_len); - - switch (type) - { - case BT_DATA_UUID128_SOME: - case BT_DATA_UUID128_ALL: + int i; + + Serial.print("[AD]:"); + Serial.print(type); + Serial.print(" data_len "); + Serial.println(data_len); + + switch (type) + { + case BT_DATA_UUID128_SOME: + case BT_DATA_UUID128_ALL: + { + if (data_len % UUID_SIZE_128 != 0) { - if (data_len % UUID_SIZE_128 != 0) - { - Serial.println("AD malformed"); - return true; - } - for (i = 0; i < data_len; i += UUID_SIZE_128) - { - if (ledService.uuidCompare(dataPtr + i, UUID_SIZE_128) == false) - { - continue; - } - - // Accept the advertisement - if (!bleCentral.stopScan()) - { - Serial.println("Stop LE scan failed"); - continue; - } - Serial.println("Connecting"); - // Connect to peripheral - bleCentral.connect(addrPtr, &conn_param); - return false; - } + Serial.println("AD malformed"); + return true; } - } + for (i = 0; i < data_len; i += UUID_SIZE_128) + { + if (ledService.uuidCompare(dataPtr + i, UUID_SIZE_128) == false) + { + continue; + } + + // Accept the advertisement + if (!bleCentral.stopScan()) + { + Serial.println("Stop LE scan failed"); + continue; + } + Serial.println("Connecting"); + // Connect to peripheral + bleCentral.connect(addrPtr, &conn_param); + return false; + } + } + } - return true; + return true; } /* diff --git a/libraries/CurieBLE/examples/MIDIBLE/MIDIBLE.ino b/libraries/CurieBLE/examples/MIDIBLE/MIDIBLE.ino index 7d78df27..49164ce5 100644 --- a/libraries/CurieBLE/examples/MIDIBLE/MIDIBLE.ino +++ b/libraries/CurieBLE/examples/MIDIBLE/MIDIBLE.ino @@ -2,7 +2,7 @@ America's Greatest Makers with help from Intel. MIDI over BLE info from: https://developer.apple.com/bluetooth/Apple-Bluetooth-Low-Energy-MIDI-Specification.pdf This sketch is not written to pair with any of the central examples. - + This sketch plays a random MIDI note (between 0 and 127) every 400ms. For a 'smarter' sketch, check out my Airpeggiator example. The Airpeggiator uses the Curie's IMU to allow you to play @@ -26,8 +26,8 @@ Towards the bottom of advanced, you will see 'Bluetooth MIDI devices'. You should see your Arduino 101 advertising in the list. Connect to your device and it should be available to all other iOS MIDI apps you have. - - If you do not have iOS, you can still use a BLE app on Android and just subscribe + + If you do not have iOS, you can still use a BLE app on Android and just subscribe to the midiChar charcteristic and see the updates. To send data, you use the following line: char.setValue(d, n); where char is @@ -97,6 +97,10 @@ BLECharacteristic midiChar("7772E5DB-3868-4112-A1A9-F2669D106BF3", BLEWrite | BL void setup() { Serial.begin(9600); + // wait for the Serial port to connect. Open the Serial Monitor to continue executing the sketch + while (!Serial) { + ; + } BLESetup(); Serial.println(("Bluetooth device active, waiting for connections...")); diff --git a/libraries/CurieBLE/examples/Scanning/Scanning.ino b/libraries/CurieBLE/examples/Scanning/Scanning.ino index 966bd3b4..3211f502 100644 --- a/libraries/CurieBLE/examples/Scanning/Scanning.ino +++ b/libraries/CurieBLE/examples/Scanning/Scanning.ino @@ -1,13 +1,14 @@ /* - * Copyright (c) 2016 Intel Corporation. All rights reserved. - * See the bottom of this file for the license terms. - */ + Copyright (c) 2016 Intel Corporation. All rights reserved. + See the bottom of this file for the license terms. +*/ #include /* This sketch is meaningful if one or more BLE peripheral devices (any of the peripheral examples will do) - are present. + are present because this sketch allows to use your Arduino/Genuino 101 as a Central device that scans + for peripheral devices. This sketch try to show the scan function. The sketch will list the device's MAC address and device name to the console. @@ -17,10 +18,10 @@ const int bleScanMaxCnt = 5; -typedef struct{ - char macaddr[32]; // BLE MAC address. - char loacalname[22]; // Device's name -}ble_device_info_t; +typedef struct { + char macaddr[32]; // BLE MAC address. + char localname[22]; // Device's name +} ble_device_info_t; ble_device_info_t device_list[bleScanMaxCnt]; uint8_t list_index = 0; @@ -34,102 +35,106 @@ bool adv_found(uint8_t type, void setup() { - Serial.begin(115200); // initialize serial communication - - /* Setup callback */ - bleCentral.setAdvertiseHandler(adv_found); - - /* Now activate the BLE device. - It will start continuously scanning BLE advertising - */ - bleCentral.begin(); - Serial.println("Bluetooth device active, start scanning..."); + Serial.begin(115200); // initialize serial communication + // wait for the Serial port to connect. Open the Serial Monitor to continue executing the sketch + while (!Serial) { + ; + } + + /* Setup callback */ + bleCentral.setAdvertiseHandler(adv_found); + + /* Now activate the BLE device. + It will start continuously scanning BLE advertising + */ + bleCentral.begin(); + Serial.println("Bluetooth device active, start scanning..."); } void loop() { - // Output the scanned device per 3s - delay(3000); - Serial.print("\r\n\r\n\t\t\tScaning result\r\n \tMAC\t\t\t\tLocal Name\r\n"); - Serial.print("-------------------------------------------------------------\r\n"); - - for (int i = 0; i < list_index; i++) - { - - Serial.print(device_list[i].macaddr); - Serial.print(" | "); - Serial.println(device_list[i].loacalname); - } - if (list_index == 0) - { - Serial.print("No device found\r\n"); - } - Serial.print("-------------------------------------------------------------\r\n"); - adv_list_clear(); + // Output the scanned device per 3s + delay(3000); + Serial.print("\r\n\r\n\t\t\tScaning result\r\n \tMAC\t\t\t\tLocal Name\r\n"); + Serial.print("-------------------------------------------------------------\r\n"); + + for (int i = 0; i < list_index; i++) + { + + Serial.print(device_list[i].macaddr); + Serial.print(" | "); + Serial.println(device_list[i].localname); + } + if (list_index == 0) + { + Serial.print("No device found\r\n"); + } + Serial.print("-------------------------------------------------------------\r\n"); + adv_list_clear(); } // Add the scanned BLE device into the global variables. bool adv_list_add(ble_device_info_t &device) { - if (list_index >= bleScanMaxCnt) - { - return false; - } - for (int i = 0; i < list_index; i++) + if (list_index >= bleScanMaxCnt) + { + return false; + } + for (int i = 0; i < list_index; i++) + { + if (0 == memcmp(device.macaddr, device_list[i].macaddr, sizeof (device.macaddr))) { - if (0 == memcmp(device.macaddr, device_list[i].macaddr, sizeof (device.macaddr))) - { - // Found and update the item - return false; - } + // Found and update the item + return false; } - // Add the device - memcpy(&device_list[list_index], &device, sizeof (ble_device_info_t)); - list_index++; - return true; + } + // Add the device + memcpy(&device_list[list_index], &device, sizeof (ble_device_info_t)); + list_index++; + return true; } bool adv_list_update(ble_device_info_t &device) { - for (int i = 0; i < list_index; i++) + for (int i = 0; i < list_index; i++) + { + if (0 == memcmp(device.macaddr, device_list[i].macaddr, sizeof (device.macaddr))) { - if (0 == memcmp(device.macaddr, device_list[i].macaddr, sizeof (device.macaddr))) - { - // Found and update the item - memcpy(device_list[i].loacalname, device.loacalname, sizeof(device.loacalname)); - return true; - } + // Found and update the item + memcpy(device_list[i].localname, device.localname, sizeof(device.localname)); + return true; } - return false; + } + return false; } void adv_list_clear() { - list_index = 0; - memset(device_list, 0x00, sizeof(device_list)); + list_index = 0; + memset(device_list, 0x00, sizeof(device_list)); } // Process the Advertisement data -bool adv_found(uint8_t type, - const uint8_t *dataPtr, +bool adv_found(uint8_t type, + const uint8_t *dataPtr, uint8_t data_len, const bt_addr_le_t *addrPtr) { - ble_device_info_t device; - bt_addr_le_to_str (addrPtr, device.macaddr, sizeof (device.macaddr)); - memcpy(device.loacalname, " -NA-", sizeof(" -NA-")); - // Please see https://www.bluetooth.org/en-us/specification/assigned-numbers/generic-access-profile - switch (type) { - case BT_DATA_NAME_SHORTENED: - case BT_DATA_NAME_COMPLETE: - memcpy(device.loacalname, dataPtr, data_len); - device.loacalname[data_len] = '\0'; - adv_list_update(device); - break; - } - adv_list_add(device); - return true; + ble_device_info_t device; + bt_addr_le_to_str (addrPtr, device.macaddr, sizeof (device.macaddr)); + memcpy(device.localname, " -NA-", sizeof(" -NA-")); + // Please see https://www.bluetooth.org/en-us/specification/assigned-numbers/generic-access-profile + switch (type) { + case BT_DATA_NAME_SHORTENED: + case BT_DATA_NAME_COMPLETE: + memcpy(device.localname, dataPtr, data_len); + device.localname[data_len] = '\0'; + adv_list_update(device); + break; + } + adv_list_add(device); + return true; } /* diff --git a/libraries/CurieBLE/examples/UpdateConnectionInterval/UpdateConnectionInterval.ino b/libraries/CurieBLE/examples/UpdateConnectionInterval/UpdateConnectionInterval.ino index 852b8e3b..923ddeea 100644 --- a/libraries/CurieBLE/examples/UpdateConnectionInterval/UpdateConnectionInterval.ino +++ b/libraries/CurieBLE/examples/UpdateConnectionInterval/UpdateConnectionInterval.ino @@ -1,11 +1,11 @@ /* - * Copyright (c) 2016 Intel Corporation. All rights reserved. - * See the bottom of this file for the license terms. - */ + Copyright (c) 2016 Intel Corporation. All rights reserved. + See the bottom of this file for the license terms. +*/ #include -/* +/* This example can work with BatteryMonitor. Show how to control and response the connection interval request. @@ -15,16 +15,16 @@ ble_conn_param_t conn_param = {30.0, // minimum interval in ms 7.5 - 4000 50.0, // maximum interval in ms 7.5 - - 0, // latency + 0, // latency 4000 // timeout in ms 100 - 32000ms - }; + }; const int ledPin = 13; // set ledPin to use on-board LED BLECentral bleCentral; // create central instance -BLEPeripheralHelper *blePeripheral1 = NULL; // // peer peripheral device +BLEPeripheralHelper *blePeripheral1 = NULL; // peer peripheral device BLEService batteryService("180F"); // create service with a 16-bit UUID -BLECharCharacteristic batteryLevelChar("2A19", BLERead | BLENotify);// create switch characteristic +BLECharCharacteristic batteryLevelChar("2A19", BLERead | BLENotify); // create switch characteristic //and allow remote device to read and notify bool adv_found(uint8_t type, @@ -34,157 +34,161 @@ bool adv_found(uint8_t type, void setup() { - Serial.begin(9600); + Serial.begin(9600); + // wait for the Serial port to connect. Open the Serial Monitor to continue executing the sketch + while (!Serial) { + ; + } - // add service and characteristic - bleCentral.addAttribute(batteryService); - bleCentral.addAttribute(batteryLevelChar); + // add service and characteristic + bleCentral.addAttribute(batteryService); + bleCentral.addAttribute(batteryLevelChar); - // assign event handlers for connected, disconnected to central - bleCentral.setEventHandler(BLEConnected, bleCentralConnectHandler); - bleCentral.setEventHandler(BLEDisconnected, bleCentralDisconnectHandler); - bleCentral.setEventHandler(BLEUpdateParam, bleCentralUpdateParam); + // assign event handlers for connected, disconnected to central + bleCentral.setEventHandler(BLEConnected, bleCentralConnectHandler); + bleCentral.setEventHandler(BLEDisconnected, bleCentralDisconnectHandler); + bleCentral.setEventHandler(BLEUpdateParam, bleCentralUpdateParam); - // advertise the service - bleCentral.setAdvertiseHandler(adv_found); + // advertise the service + bleCentral.setAdvertiseHandler(adv_found); - // assign event handlers for characteristic - batteryLevelChar.setEventHandler(BLEWritten, switchCharacteristicWritten); + // assign event handlers for characteristic + batteryLevelChar.setEventHandler(BLEWritten, switchCharacteristicWritten); - bleCentral.begin(); - Serial.println(("Bluetooth device active, waiting for connections...")); + bleCentral.begin(); + Serial.println("Bluetooth device active, waiting for connections..."); } void loop() { - static unsigned int counter = 0; - static char ledstate = 0; - delay(2000); - if (blePeripheral1) + static unsigned int counter = 0; + static char ledstate = 0; + delay(2000); + if (blePeripheral1) + { + counter++; + + if (counter % 3) { - counter++; - - if (counter % 3) - { - batteryLevelChar.read(*blePeripheral1); - } - else - { - ledstate = !ledstate; - batteryLevelChar.write(*blePeripheral1, ledstate); - } + batteryLevelChar.read(*blePeripheral1); + } + else + { + ledstate = !ledstate; + batteryLevelChar.write(*blePeripheral1, ledstate); } + } } void bleCentralConnectHandler(BLEHelper& peripheral) { - // peripheral connected event handler - blePeripheral1 = bleCentral.getPeerPeripheralBLE(peripheral); - Serial.print("Connected event, peripheral: "); - Serial.println(peripheral.address()); - // Start discovery the profiles in peripheral device - blePeripheral1->discover(); + // peripheral connected event handler + blePeripheral1 = bleCentral.getPeerPeripheralBLE(peripheral); + Serial.print("Connected event, peripheral: "); + Serial.println(peripheral.address()); + // Start discovery the profiles in peripheral device + blePeripheral1->discover(); } void bleCentralDisconnectHandler(BLEHelper& peripheral) { - // peripheral disconnected event handler - blePeripheral1 = NULL; - Serial.print("Disconnected event, peripheral: "); - Serial.println(peripheral.address()); - bleCentral.startScan(); + // peripheral disconnected event handler + blePeripheral1 = NULL; + Serial.print("Disconnected event, peripheral: "); + Serial.println(peripheral.address()); + bleCentral.startScan(); } void bleCentralUpdateParam(BLEHelper& peripheral) { - // peripheral update the connection interval event handler - Serial.print("UpdateParam event, peripheral: "); - blePeripheral1 = bleCentral.getPeerPeripheralBLE(peripheral);; - Serial.println(peripheral.address()); - - // Get connection interval that peer peripheral device wanted - ble_conn_param_t m_conn_param; - blePeripheral1->getConnParams(m_conn_param); - Serial.print("min interval = " ); - Serial.println(m_conn_param.interval_min ); - Serial.print("max interval = " ); - Serial.println(m_conn_param.interval_max ); - Serial.print("latency = " ); - Serial.println(m_conn_param.latency ); - Serial.print("timeout = " ); - Serial.println(m_conn_param.timeout ); - - //Update the connection interval - blePeripheral1->setConnectionInterval(m_conn_param.interval_min,m_conn_param.interval_max); + // peripheral update the connection interval event handler + Serial.print("UpdateParam event, peripheral: "); + blePeripheral1 = bleCentral.getPeerPeripheralBLE(peripheral);; + Serial.println(peripheral.address()); + + // Get connection interval that peer peripheral device wanted + ble_conn_param_t m_conn_param; + blePeripheral1->getConnParams(m_conn_param); + Serial.print("min interval = " ); + Serial.println(m_conn_param.interval_min ); + Serial.print("max interval = " ); + Serial.println(m_conn_param.interval_max ); + Serial.print("latency = " ); + Serial.println(m_conn_param.latency ); + Serial.print("timeout = " ); + Serial.println(m_conn_param.timeout ); + + //Update the connection interval + blePeripheral1->setConnectionInterval(m_conn_param.interval_min, m_conn_param.interval_max); } void switchCharacteristicWritten(BLEHelper& peripheral, BLECharacteristic& characteristic) { - // Read response/Notification wrote new value to characteristic, update LED - Serial.print("Characteristic event, notify: "); - - int battery = batteryLevelChar.value(); - if (battery) - { - Serial.print("Battery Level % is now: "); // print it - Serial.println(battery); - delay(100); - - Serial.println("LED on"); - digitalWrite(ledPin, HIGH); - } - else - { - Serial.println("LED off"); - digitalWrite(ledPin, LOW); - } + // Read response/Notification wrote new value to characteristic, update LED + Serial.print("Characteristic event, notify: "); + + int battery = batteryLevelChar.value(); + if (battery) + { + Serial.print("Battery Level % is now: "); // print it + Serial.println(battery); + delay(100); + + Serial.println("LED on"); + digitalWrite(ledPin, HIGH); + } + else + { + Serial.println("LED off"); + digitalWrite(ledPin, LOW); + } } -bool adv_found(uint8_t type, - const uint8_t *dataPtr, +bool adv_found(uint8_t type, + const uint8_t *dataPtr, uint8_t data_len, const bt_addr_le_t *addrPtr) { - int i; - - Serial.print("[AD]:"); - Serial.print(type); - Serial.print(" data_len "); - Serial.println(data_len); - - switch (type) - { - case BT_DATA_UUID16_SOME: - case BT_DATA_UUID16_ALL: + int i; + + Serial.print("[AD]:"); + Serial.print(type); + Serial.print(" data_len "); + Serial.println(data_len); + + switch (type) + { + case BT_DATA_UUID16_SOME: + case BT_DATA_UUID16_ALL: + { + if (data_len % UUID_SIZE_16 != 0) { - if (data_len % UUID_SIZE_16 != 0) - { - Serial.println("AD malformed"); - return true; - } - for (i = 0; i < data_len; i += UUID_SIZE_16) - { - if (batteryService.uuidCompare(dataPtr + i, UUID_SIZE_16) == false) - { - continue; - } - - // Accept the advertisement - if (!bleCentral.stopScan()) - { - Serial.println("Stop LE scan failed"); - continue; - } - Serial.println("Connecting"); - // Connect to peripheral - bleCentral.connect(addrPtr, &conn_param); - return false; - } + Serial.println("AD malformed"); + return true; } - } + for (i = 0; i < data_len; i += UUID_SIZE_16) + { + if (batteryService.uuidCompare(dataPtr + i, UUID_SIZE_16) == false) + { + continue; + } + + // Accept the advertisement + if (!bleCentral.stopScan()) + { + Serial.println("Stop LE scan failed"); + continue; + } + Serial.println("Connecting"); + // Connect to peripheral + bleCentral.connect(addrPtr, &conn_param); + return false; + } + } + } - return true; + return true; } /*