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
- Committer:
- bsiever
- Date:
- 2016-11-11
- Revision:
- 3:f593ad98fe21
- Parent:
- 2:b850666f3c8f
- Child:
- 4:6b6018da25f6
File content as of revision 3:f593ad98fe21:
#include <events/mbed_events.h> #include <mbed.h> #include "ble/BLE.h" #include "ble/Gap.h" // See the official heart rate service definitions: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.heart_rate_measurement.xml // This version shows a working heart rate service created with a "C style" (non object-oriented) approach. // It uses 8-bit heart rate values and includes "energy expended" values. // If debug is "true", include code to print debugging messages to the "console" #define DEBUG 1 #if DEBUG #define LOG_PRINTF(...) eventQueue.call(printf, __VA_ARGS__); #else #define LOG_PRINTF(...) ; #endif /*********************************************************************** I. Global Variables: This section declares "global variable" (variables that are used in multiple functions in this file and need to exist the entire time code is running (i.e., the concept of global is about both their scope and their lifetime) ************************************************************************/ // A. UUIDs for the service and its characteristics (they won't change, so declared const) const UUID HRMS_SERV(0x180D); const UUID HRMS_HRM_CHAR(0x2A37); const UUID HRMS_BODYSENSELOC_CHAR(0x2A38); const UUID HRMS_CONTROLPOINT_CHAR(0x2A39); // B. Create variables for any actual data uint8_t hrmHeartRateData[4] = {0x08,0,0,0}; // 1st byte: flags, 2nd byte heart date, 3-4th bytes: energy expended // Flags 0x8 indicates energy expended is included and heart rate is an 8-bit value. 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 uint8_t hrmControlPoint = 0; // C. Create "pointers" (references) to the characteristics // (The actual objects are constructed at run-time) GattCharacteristic hrmRateChar(HRMS_HRM_CHAR, // UUID to use hrmHeartRateData, // Pointer to data to use (arrays ARE pointers) 4, // Number of bytes (current data) 4, // Number of bytes (max) GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY); // Permissions // The versions below use templated classes (a congenience for a few "common" characteristic permissions) ReadOnlyGattCharacteristic<uint8_t> hrmLocationChar(HRMS_BODYSENSELOC_CHAR, // UUID to use &hrmBodySensorLocation); // Data to use WriteOnlyGattCharacteristic<uint8_t> hrmControlPointChar(HRMS_CONTROLPOINT_CHAR, // UUID &hrmControlPoint); // Data to use // D. A way to create new "events" /* Total size of space for eventQueue = event count * event size */ EventQueue eventQueue( 16 * 32); // D. Setup the interrupt pins for any buttons InterruptIn buttons[4] = { InterruptIn(P0_13), InterruptIn(P0_14), InterruptIn(P0_15), InterruptIn(P0_16) }; /*********************************************************************** II. Event "Call backs": This section contains functions that are called to respond to events. (Each of these is "set" as a callback somewhere in the following code too) ************************************************************************/ // A. Callback for things to do when a device disconnects void bleDisconnectionCallback(const Gap::DisconnectionCallbackParams_t *params) { LOG_PRINTF(__func__); // Print the function's name // Start advertising for a new connection BLE::Instance().gap().startAdvertising(); } void onDataWritten(const GattWriteCallbackParams *params) { LOG_PRINTF(__func__); // Print the function's name // See: https://developer.mbed.org/teams/Bluetooth-Low-Energy/code/BLE_API/docs/65474dc93927/GattCallbackParamTypes_8h_source.html for structure // A write of 1 to the "control point" should reset the energy expended if (params->handle == hrmControlPointChar.getValueAttribute().getHandle()) { LOG_PRINTF("Writing to HRM Control Point"); if(params->len == 1 && params->data[0]==1) { LOG_PRINTF("Clearing HRM Control Point"); // If it has the correct length and data, reset hrmHeartRateData[2] = 0; hrmHeartRateData[3] = 0; // Get the BLE object BLE& ble = BLE::Instance(); ble.gattServer().write(hrmRateChar.getValueHandle(), hrmHeartRateData, 4); } } } void createHeartRateService() { LOG_PRINTF(__func__); // Print the function's name // Use the default BLE object: BLE &ble = BLE::Instance(); GattCharacteristic *charTable[] = {&hrmRateChar, &hrmLocationChar, &hrmControlPointChar}; GattService hrmService(HRMS_SERV, charTable, sizeof(charTable) / sizeof(GattCharacteristic *)); ble.addService(hrmService); // Add an call back when data is written. ble.onDataWritten(onDataWritten); } // B. Callback for things to do when the BLE object is ready void bleInitComplete(BLE::InitializationCompleteCallbackContext *params) { LOG_PRINTF(__func__); // Print the function's name // Get the BLE object BLE& ble = BLE::Instance(); // Create the HeartRateService createHeartRateService(); // Setup the "onDisconnection()" callback // Connection info is handeled by GAP (Generic ACCESS Protocol), hence the ble.gap() part ble.gap().onDisconnection(bleDisconnectionCallback); // Setup advertising: Build the "payload" // Add in the mode (discoverable / low energy only) ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE); // Include a list of services in the advertising packet uint16_t uuid16_list[] = {GattService::UUID_HEART_RATE_SERVICE}; ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, (uint8_t *) uuid16_list, sizeof(uuid16_list)); // Include the device name in the advertising packet char DEVICE_NAME[] = "HRM"; ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *) DEVICE_NAME, sizeof(DEVICE_NAME)); // Add the fact that it's "connectable" to the advertising packet (and that its not directed to a specific device) ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED); // Wait 1S between advertising packets. Shorter will lead to faster connections but more power consumption // Longer will usually lead to slower times-to-connect, but lower power consumption ble.gap().setAdvertisingInterval(1000); /* 1000ms */ // Also set the device name in the GAP level ble.setDeviceName((uint8_t*)DEVICE_NAME); // Set the "GAP Name" too. // Start advertising ble.gap().startAdvertising(); } void buttonsPress() { LOG_PRINTF(__func__); // Print the function's name // Get access to the BLE object BLE &ble = BLE::Instance(); // Display the buttons that are pressed // Do NOT use printf() in callbacks. Put all printing on tasks handled by the event queue. // (This uses the debug macro provided to call printf() with arguments) for(int i=0;i<sizeof(buttons)/sizeof(InterruptIn);i++) { if(buttons[i].read()==0) LOG_PRINTF("\t%d down\r\n",i+1); } if(buttons[0].read()==0) { // Increase the heart rate and update hrmHeartRateData[1]++; ble.gattServer().write(hrmRateChar.getValueHandle(), hrmHeartRateData, 4); } else if(buttons[1].read()==0) { hrmHeartRateData[1]--; ble.gattServer().write(hrmRateChar.getValueHandle(), hrmHeartRateData, 4); } if(buttons[2].read()==0) { unsigned energy = hrmHeartRateData[2] | hrmHeartRateData[3]<<8; energy++; hrmHeartRateData[2] = energy & 0xFF; hrmHeartRateData[3] = energy >> 8; ble.gattServer().write(hrmRateChar.getValueHandle(), hrmHeartRateData, 4); } if(buttons[3].read()==0) { unsigned energy = hrmHeartRateData[2] | hrmHeartRateData[3]<<8; energy--; hrmHeartRateData[2] = energy & 0xFF; hrmHeartRateData[3] = energy >> 8; ble.gattServer().write(hrmRateChar.getValueHandle(), hrmHeartRateData, 4); } } // C. Callback to respond to BLE events. This will add a function call to the // Event Queue to process the event. void scheduleBleEventsProcessing(BLE::OnEventsToProcessCallbackContext* context) { BLE &ble = BLE::Instance(); eventQueue.call(Callback<void()>(&ble, &BLE::processEvents)); } /*********************************************************************** III. main() thread This section will run initially and configure/start everything else. ************************************************************************/ int main() { LOG_PRINTF(__func__); // Print the function's name // Configure the buttons. for(int i=0;i<sizeof(buttons)/sizeof(InterruptIn); i++) { // Pull the button voltages "up" to 3v by default buttons[i].mode(PullUp); // Callback for when the button falls to a 0 (when the button is pressed) buttons[i].fall(&buttonsPress); } // Get access to the BLE object BLE &ble = BLE::Instance(); // Set the BLE object to use the event queue to process any BLE events // (So when a BLE event occurs, a function to respond to it will be placed in the event queue) ble.onEventsToProcess(scheduleBleEventsProcessing); // Initialize the BLE object ble.init(bleInitComplete); // Let the "event queue" take over eventQueue.dispatch_forever(); return 0; }