This is an example Heart Rate Service created using a C-like approach. Rather than creating the service as a class and then using an instance of it, the service is entirely done with simple functions and variables.

Fork of nRF5-DK-HeartRateDemo by Bill Siever

Committer:
bsiever
Date:
Mon Nov 14 20:05:44 2016 +0000
Revision:
4:6b6018da25f6
Parent:
3:f593ad98fe21
Initial Release;

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