
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
source/main.cpp@4:6b6018da25f6, 2016-11-14 (annotated)
- 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?
User | Revision | Line number | New 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 | } |