Fork of nRF5-DK-HeartRateDemo by Bill Siever

source/main.cpp

Committer:
bsiever
Date:
2016-11-14
Revision:
4:6b6018da25f6
Parent:
3:f593ad98fe21

File content as of revision 4:6b6018da25f6:

#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_FN_START() eventQueue.call(printf, "%s\r\n", __func__);
#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/initialize 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 characteristics
// The first example uses the generic "GattCharacteristic" object constructor. 
// See: https://developer.mbed.org/teams/mbed/code/ble-api/docs/d19a554823af/classGattCharacteristic.html
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 conveniencce for a few "common" characteristic permissions)
// See: https://developer.mbed.org/teams/Bluetooth-Low-Energy/code/BLE_API/docs/tip/ and search for "GattCharacteristic" to see others.
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);

// E. Setup the interrupt pins for any buttons
InterruptIn buttons[4] = { InterruptIn(P0_13), InterruptIn(P0_14), InterruptIn(P0_15), InterruptIn(P0_16) };

// F. Setup the LEDs (in case needed)
DigitalOut led1(LED1, 1);
DigitalOut led2(LED2, 1);
DigitalOut led3(LED3, 1);
DigitalOut led4(LED4, 1);



/***********************************************************************
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_FN_START();  // Print the function's name
    
    // Start advertising for a new connection
    BLE::Instance().gap().startAdvertising();
}



void onDataWritten(const GattWriteCallbackParams *params) {
    LOG_FN_START();  // 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\r\n");  
        if(params->len == 1 && params->data[0]==1) {
            LOG_PRINTF("Clearing HRM Control Point\r\n");  
            // 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_FN_START();  // 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_FN_START();  // 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_FN_START();  // 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;
}