Fork of nRF5-DK-HeartRateDemo by Bill Siever

Committer:
bsiever
Date:
Fri Nov 11 20:33:07 2016 +0000
Revision:
3:f593ad98fe21
Parent:
2:b850666f3c8f
Child:
4:6b6018da25f6
nRF52-DK Heart Rate Service developed with C-based approach to the service (rather than an object for the service).  It still uses objects for the UUIDs and Characteristics.  Initial commit.  This version supports only the 8-bit heart rate and energy

Who changed what in which revision?

UserRevisionLine numberNew contents of line
bsiever 0:3a7b472313d7 1 #include <events/mbed_events.h>
bsiever 0:3a7b472313d7 2 #include <mbed.h>
bsiever 0:3a7b472313d7 3 #include "ble/BLE.h"
bsiever 0:3a7b472313d7 4 #include "ble/Gap.h"
bsiever 0:3a7b472313d7 5
bsiever 3:f593ad98fe21 6 // See the official heart rate service definitions: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.heart_rate_measurement.xml
bsiever 3:f593ad98fe21 7 // This version shows a working heart rate service created with a "C style" (non object-oriented) approach.
bsiever 3:f593ad98fe21 8 // It uses 8-bit heart rate values and includes "energy expended" values.
bsiever 3:f593ad98fe21 9
bsiever 0:3a7b472313d7 10
bsiever 2:b850666f3c8f 11 // If debug is "true", include code to print debugging messages to the "console"
bsiever 0:3a7b472313d7 12 #define DEBUG 1
bsiever 0:3a7b472313d7 13 #if DEBUG
bsiever 2:b850666f3c8f 14 #define LOG_PRINTF(...) eventQueue.call(printf, __VA_ARGS__);
bsiever 0:3a7b472313d7 15 #else
bsiever 2:b850666f3c8f 16 #define LOG_PRINTF(...) ;
bsiever 0:3a7b472313d7 17 #endif
bsiever 0:3a7b472313d7 18
bsiever 0:3a7b472313d7 19 /***********************************************************************
bsiever 0:3a7b472313d7 20 I. Global Variables:
bsiever 0:3a7b472313d7 21 This section declares "global variable" (variables that are used
bsiever 3:f593ad98fe21 22 in multiple functions in this file and need to exist the entire time
bsiever 3:f593ad98fe21 23 code is running (i.e., the concept of global is about both their scope
bsiever 3:f593ad98fe21 24 and their lifetime)
bsiever 0:3a7b472313d7 25 ************************************************************************/
bsiever 3:f593ad98fe21 26 // A. UUIDs for the service and its characteristics (they won't change, so declared const)
bsiever 3:f593ad98fe21 27 const UUID HRMS_SERV(0x180D);
bsiever 3:f593ad98fe21 28 const UUID HRMS_HRM_CHAR(0x2A37);
bsiever 3:f593ad98fe21 29 const UUID HRMS_BODYSENSELOC_CHAR(0x2A38);
bsiever 3:f593ad98fe21 30 const UUID HRMS_CONTROLPOINT_CHAR(0x2A39);
bsiever 3:f593ad98fe21 31
bsiever 3:f593ad98fe21 32 // B. Create variables for any actual data
bsiever 3:f593ad98fe21 33 uint8_t hrmHeartRateData[4] = {0x08,0,0,0}; // 1st byte: flags, 2nd byte heart date, 3-4th bytes: energy expended
bsiever 3:f593ad98fe21 34 // Flags 0x8 indicates energy expended is included and heart rate is an 8-bit value.
bsiever 3:f593ad98fe21 35 uint8_t hrmBodySensorLocation = 0; // 0 is code for "Other". See https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.body_sensor_location.xml
bsiever 3:f593ad98fe21 36 uint8_t hrmControlPoint = 0;
bsiever 3:f593ad98fe21 37
bsiever 0:3a7b472313d7 38
bsiever 3:f593ad98fe21 39 // C. Create "pointers" (references) to the characteristics
bsiever 3:f593ad98fe21 40 // (The actual objects are constructed at run-time)
bsiever 3:f593ad98fe21 41 GattCharacteristic hrmRateChar(HRMS_HRM_CHAR, // UUID to use
bsiever 3:f593ad98fe21 42 hrmHeartRateData, // Pointer to data to use (arrays ARE pointers)
bsiever 3:f593ad98fe21 43 4, // Number of bytes (current data)
bsiever 3:f593ad98fe21 44 4, // Number of bytes (max)
bsiever 3:f593ad98fe21 45 GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY); // Permissions
bsiever 0:3a7b472313d7 46
bsiever 3:f593ad98fe21 47 // The versions below use templated classes (a congenience for a few "common" characteristic permissions)
bsiever 3:f593ad98fe21 48 ReadOnlyGattCharacteristic<uint8_t> hrmLocationChar(HRMS_BODYSENSELOC_CHAR, // UUID to use
bsiever 3:f593ad98fe21 49 &hrmBodySensorLocation); // Data to use
bsiever 3:f593ad98fe21 50
bsiever 3:f593ad98fe21 51 WriteOnlyGattCharacteristic<uint8_t> hrmControlPointChar(HRMS_CONTROLPOINT_CHAR, // UUID
bsiever 3:f593ad98fe21 52 &hrmControlPoint); // Data to use
bsiever 3:f593ad98fe21 53
bsiever 3:f593ad98fe21 54
bsiever 3:f593ad98fe21 55
bsiever 3:f593ad98fe21 56
bsiever 3:f593ad98fe21 57 // D. A way to create new "events"
bsiever 0:3a7b472313d7 58 /* Total size of space for eventQueue = event count * event size */
bsiever 0:3a7b472313d7 59 EventQueue eventQueue( 16 * 32);
bsiever 0:3a7b472313d7 60
bsiever 3:f593ad98fe21 61 // D. Setup the interrupt pins for any buttons
bsiever 0:3a7b472313d7 62 InterruptIn buttons[4] = { InterruptIn(P0_13), InterruptIn(P0_14), InterruptIn(P0_15), InterruptIn(P0_16) };
bsiever 0:3a7b472313d7 63
bsiever 0:3a7b472313d7 64
bsiever 0:3a7b472313d7 65
bsiever 0:3a7b472313d7 66 /***********************************************************************
bsiever 0:3a7b472313d7 67 II. Event "Call backs":
bsiever 0:3a7b472313d7 68 This section contains functions that are called to respond to events.
bsiever 0:3a7b472313d7 69 (Each of these is "set" as a callback somewhere in the following code too)
bsiever 0:3a7b472313d7 70 ************************************************************************/
bsiever 0:3a7b472313d7 71
bsiever 0:3a7b472313d7 72 // A. Callback for things to do when a device disconnects
bsiever 0:3a7b472313d7 73 void bleDisconnectionCallback(const Gap::DisconnectionCallbackParams_t *params) {
bsiever 2:b850666f3c8f 74 LOG_PRINTF(__func__); // Print the function's name
bsiever 0:3a7b472313d7 75
bsiever 0:3a7b472313d7 76 // Start advertising for a new connection
bsiever 0:3a7b472313d7 77 BLE::Instance().gap().startAdvertising();
bsiever 0:3a7b472313d7 78 }
bsiever 0:3a7b472313d7 79
bsiever 0:3a7b472313d7 80
bsiever 3:f593ad98fe21 81
bsiever 3:f593ad98fe21 82 void onDataWritten(const GattWriteCallbackParams *params) {
bsiever 3:f593ad98fe21 83 LOG_PRINTF(__func__); // Print the function's name
bsiever 3:f593ad98fe21 84 // See: https://developer.mbed.org/teams/Bluetooth-Low-Energy/code/BLE_API/docs/65474dc93927/GattCallbackParamTypes_8h_source.html for structure
bsiever 3:f593ad98fe21 85 // A write of 1 to the "control point" should reset the energy expended
bsiever 3:f593ad98fe21 86 if (params->handle == hrmControlPointChar.getValueAttribute().getHandle()) {
bsiever 3:f593ad98fe21 87 LOG_PRINTF("Writing to HRM Control Point");
bsiever 3:f593ad98fe21 88 if(params->len == 1 && params->data[0]==1) {
bsiever 3:f593ad98fe21 89 LOG_PRINTF("Clearing HRM Control Point");
bsiever 3:f593ad98fe21 90 // If it has the correct length and data, reset
bsiever 3:f593ad98fe21 91 hrmHeartRateData[2] = 0;
bsiever 3:f593ad98fe21 92 hrmHeartRateData[3] = 0;
bsiever 3:f593ad98fe21 93 // Get the BLE object
bsiever 3:f593ad98fe21 94 BLE& ble = BLE::Instance();
bsiever 3:f593ad98fe21 95 ble.gattServer().write(hrmRateChar.getValueHandle(), hrmHeartRateData, 4);
bsiever 3:f593ad98fe21 96 }
bsiever 3:f593ad98fe21 97 }
bsiever 3:f593ad98fe21 98 }
bsiever 3:f593ad98fe21 99
bsiever 3:f593ad98fe21 100 void createHeartRateService() {
bsiever 3:f593ad98fe21 101 LOG_PRINTF(__func__); // Print the function's name
bsiever 3:f593ad98fe21 102
bsiever 3:f593ad98fe21 103 // Use the default BLE object:
bsiever 3:f593ad98fe21 104 BLE &ble = BLE::Instance();
bsiever 3:f593ad98fe21 105
bsiever 3:f593ad98fe21 106 GattCharacteristic *charTable[] = {&hrmRateChar, &hrmLocationChar, &hrmControlPointChar};
bsiever 3:f593ad98fe21 107 GattService hrmService(HRMS_SERV, charTable, sizeof(charTable) / sizeof(GattCharacteristic *));
bsiever 3:f593ad98fe21 108
bsiever 3:f593ad98fe21 109 ble.addService(hrmService);
bsiever 3:f593ad98fe21 110 // Add an call back when data is written.
bsiever 3:f593ad98fe21 111 ble.onDataWritten(onDataWritten);
bsiever 3:f593ad98fe21 112 }
bsiever 3:f593ad98fe21 113
bsiever 3:f593ad98fe21 114
bsiever 0:3a7b472313d7 115 // B. Callback for things to do when the BLE object is ready
bsiever 2:b850666f3c8f 116 void bleInitComplete(BLE::InitializationCompleteCallbackContext *params) {
bsiever 2:b850666f3c8f 117 LOG_PRINTF(__func__); // Print the function's name
bsiever 0:3a7b472313d7 118
bsiever 0:3a7b472313d7 119 // Get the BLE object
bsiever 0:3a7b472313d7 120 BLE& ble = BLE::Instance();
bsiever 0:3a7b472313d7 121
bsiever 3:f593ad98fe21 122 // Create the HeartRateService
bsiever 3:f593ad98fe21 123 createHeartRateService();
bsiever 0:3a7b472313d7 124
bsiever 0:3a7b472313d7 125 // Setup the "onDisconnection()" callback
bsiever 0:3a7b472313d7 126 // Connection info is handeled by GAP (Generic ACCESS Protocol), hence the ble.gap() part
bsiever 0:3a7b472313d7 127 ble.gap().onDisconnection(bleDisconnectionCallback);
bsiever 0:3a7b472313d7 128
bsiever 0:3a7b472313d7 129 // Setup advertising: Build the "payload"
bsiever 0:3a7b472313d7 130 // Add in the mode (discoverable / low energy only)
bsiever 0:3a7b472313d7 131 ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
bsiever 0:3a7b472313d7 132 // Include a list of services in the advertising packet
bsiever 0:3a7b472313d7 133 uint16_t uuid16_list[] = {GattService::UUID_HEART_RATE_SERVICE};
bsiever 0:3a7b472313d7 134 ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, (uint8_t *) uuid16_list, sizeof(uuid16_list));
bsiever 0:3a7b472313d7 135
bsiever 0:3a7b472313d7 136 // Include the device name in the advertising packet
bsiever 0:3a7b472313d7 137 char DEVICE_NAME[] = "HRM";
bsiever 0:3a7b472313d7 138 ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *) DEVICE_NAME, sizeof(DEVICE_NAME));
bsiever 0:3a7b472313d7 139 // Add the fact that it's "connectable" to the advertising packet (and that its not directed to a specific device)
bsiever 0:3a7b472313d7 140 ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
bsiever 0:3a7b472313d7 141 // Wait 1S between advertising packets. Shorter will lead to faster connections but more power consumption
bsiever 0:3a7b472313d7 142 // Longer will usually lead to slower times-to-connect, but lower power consumption
bsiever 0:3a7b472313d7 143 ble.gap().setAdvertisingInterval(1000); /* 1000ms */
bsiever 0:3a7b472313d7 144
bsiever 0:3a7b472313d7 145 // Also set the device name in the GAP level
bsiever 0:3a7b472313d7 146 ble.setDeviceName((uint8_t*)DEVICE_NAME); // Set the "GAP Name" too.
bsiever 0:3a7b472313d7 147
bsiever 0:3a7b472313d7 148 // Start advertising
bsiever 0:3a7b472313d7 149 ble.gap().startAdvertising();
bsiever 0:3a7b472313d7 150 }
bsiever 0:3a7b472313d7 151
bsiever 0:3a7b472313d7 152 void buttonsPress() {
bsiever 2:b850666f3c8f 153 LOG_PRINTF(__func__); // Print the function's name
bsiever 3:f593ad98fe21 154
bsiever 3:f593ad98fe21 155 // Get access to the BLE object
bsiever 3:f593ad98fe21 156 BLE &ble = BLE::Instance();
bsiever 3:f593ad98fe21 157
bsiever 0:3a7b472313d7 158 // Display the buttons that are pressed
bsiever 0:3a7b472313d7 159
bsiever 0:3a7b472313d7 160 // Do NOT use printf() in callbacks. Put all printing on tasks handled by the event queue.
bsiever 2:b850666f3c8f 161 // (This uses the debug macro provided to call printf() with arguments)
bsiever 0:3a7b472313d7 162 for(int i=0;i<sizeof(buttons)/sizeof(InterruptIn);i++) {
bsiever 0:3a7b472313d7 163 if(buttons[i].read()==0)
bsiever 2:b850666f3c8f 164 LOG_PRINTF("\t%d down\r\n",i+1);
bsiever 0:3a7b472313d7 165 }
bsiever 0:3a7b472313d7 166 if(buttons[0].read()==0) {
bsiever 3:f593ad98fe21 167 // Increase the heart rate and update
bsiever 3:f593ad98fe21 168 hrmHeartRateData[1]++;
bsiever 3:f593ad98fe21 169 ble.gattServer().write(hrmRateChar.getValueHandle(), hrmHeartRateData, 4);
bsiever 0:3a7b472313d7 170 } else
bsiever 0:3a7b472313d7 171 if(buttons[1].read()==0) {
bsiever 3:f593ad98fe21 172 hrmHeartRateData[1]--;
bsiever 3:f593ad98fe21 173 ble.gattServer().write(hrmRateChar.getValueHandle(), hrmHeartRateData, 4);
bsiever 0:3a7b472313d7 174 }
bsiever 3:f593ad98fe21 175 if(buttons[2].read()==0) {
bsiever 3:f593ad98fe21 176 unsigned energy = hrmHeartRateData[2] | hrmHeartRateData[3]<<8;
bsiever 3:f593ad98fe21 177 energy++;
bsiever 3:f593ad98fe21 178 hrmHeartRateData[2] = energy & 0xFF;
bsiever 3:f593ad98fe21 179 hrmHeartRateData[3] = energy >> 8;
bsiever 3:f593ad98fe21 180 ble.gattServer().write(hrmRateChar.getValueHandle(), hrmHeartRateData, 4);
bsiever 3:f593ad98fe21 181 }
bsiever 3:f593ad98fe21 182 if(buttons[3].read()==0) {
bsiever 3:f593ad98fe21 183 unsigned energy = hrmHeartRateData[2] | hrmHeartRateData[3]<<8;
bsiever 3:f593ad98fe21 184 energy--;
bsiever 3:f593ad98fe21 185 hrmHeartRateData[2] = energy & 0xFF;
bsiever 3:f593ad98fe21 186 hrmHeartRateData[3] = energy >> 8;
bsiever 3:f593ad98fe21 187 ble.gattServer().write(hrmRateChar.getValueHandle(), hrmHeartRateData, 4);
bsiever 3:f593ad98fe21 188 }
bsiever 0:3a7b472313d7 189 }
bsiever 0:3a7b472313d7 190
bsiever 0:3a7b472313d7 191 // C. Callback to respond to BLE events. This will add a function call to the
bsiever 0:3a7b472313d7 192 // Event Queue to process the event.
bsiever 0:3a7b472313d7 193 void scheduleBleEventsProcessing(BLE::OnEventsToProcessCallbackContext* context) {
bsiever 0:3a7b472313d7 194 BLE &ble = BLE::Instance();
bsiever 0:3a7b472313d7 195 eventQueue.call(Callback<void()>(&ble, &BLE::processEvents));
bsiever 0:3a7b472313d7 196 }
bsiever 0:3a7b472313d7 197
bsiever 0:3a7b472313d7 198 /***********************************************************************
bsiever 0:3a7b472313d7 199 III. main() thread
bsiever 0:3a7b472313d7 200 This section will run initially and configure/start everything else.
bsiever 0:3a7b472313d7 201 ************************************************************************/
bsiever 0:3a7b472313d7 202
bsiever 0:3a7b472313d7 203
bsiever 0:3a7b472313d7 204 int main() {
bsiever 2:b850666f3c8f 205 LOG_PRINTF(__func__); // Print the function's name
bsiever 0:3a7b472313d7 206 // Configure the buttons.
bsiever 0:3a7b472313d7 207 for(int i=0;i<sizeof(buttons)/sizeof(InterruptIn); i++) {
bsiever 0:3a7b472313d7 208 // Pull the button voltages "up" to 3v by default
bsiever 0:3a7b472313d7 209 buttons[i].mode(PullUp);
bsiever 0:3a7b472313d7 210 // Callback for when the button falls to a 0 (when the button is pressed)
bsiever 0:3a7b472313d7 211 buttons[i].fall(&buttonsPress);
bsiever 0:3a7b472313d7 212 }
bsiever 0:3a7b472313d7 213
bsiever 0:3a7b472313d7 214 // Get access to the BLE object
bsiever 0:3a7b472313d7 215 BLE &ble = BLE::Instance();
bsiever 0:3a7b472313d7 216 // Set the BLE object to use the event queue to process any BLE events
bsiever 0:3a7b472313d7 217 // (So when a BLE event occurs, a function to respond to it will be placed in the event queue)
bsiever 0:3a7b472313d7 218 ble.onEventsToProcess(scheduleBleEventsProcessing);
bsiever 0:3a7b472313d7 219
bsiever 0:3a7b472313d7 220 // Initialize the BLE object
bsiever 0:3a7b472313d7 221 ble.init(bleInitComplete);
bsiever 0:3a7b472313d7 222
bsiever 0:3a7b472313d7 223 // Let the "event queue" take over
bsiever 0:3a7b472313d7 224 eventQueue.dispatch_forever();
bsiever 0:3a7b472313d7 225
bsiever 0:3a7b472313d7 226 return 0;
bsiever 0:3a7b472313d7 227 }