Experimental BLE project showing how IO can be made with an App over BLE. Pointer to matching App will be added when ready, initially this works with: - Android App [nRF-Master Control Panel], supports Write,Read,Notify - Android Project [BluetoothLeGatt]

Dependencies:   BLE_API mbed nRF51822

This is an experimental project for BLE (Bluetooth LE == Bluetooth Low Energy == Bluetooth Smart).

  • It supports general IO over BLE with Read/Notify/Write support.
  • It is compatible with FOTA using Android App "nRF Master Control Panel" (20150126)
  • IO supported by:
    • Custom Android App is in the WIKI under: Android-App, developed from Android Sample "BluetoothLeGatt"
    • Android App: nRF-MCP (Master Control Panel)
    • iOS App LightBlue.
    • General HRM, HTM, Battery and similar apps should be able to access the matching services.
  • It includes combinations of code from other projects, alternative code included can be tried by moving comments (, //)
  • 20150126 bleIO r25: It compiles for both "Nordic nRF51822" and "Nordic nRF51822 FOTA" platforms
  • 20150126 The matching bleIO App (in wiki) doesn't support FOTA yet, use Android App "nRF Master Control Panel"

Feedback and ideas greatly appreciated!!!

main.cpp

Committer:
prussell
Date:
2014-12-13
Revision:
3:a98203f84063
Parent:
2:c77c2b06d604
Child:
4:976394791d7a

File content as of revision 3:a98203f84063:

//=========Header (PR)
// blePRv04, Initial: 20141210 Paul Russell (mbed user: prussell = PR)
// This sample includes code from several projects found on http://developer.mbed.org, including but not limited to:
//    - http://developer.mbed.org/teams/Bluetooth-Low-Energy/code/BLE_HeartRate/
//    - https://developer.mbed.org/teams/Bluetooth-Low-Energy/code/BLE_LoopbackUART/
//    - https://developer.mbed.org/users/takafuminaka/code/BLE_HTM_by_InTempSensr/
//    - miscellaneous adopted from more samples...
// Reference:
//    - http://developer.mbed.org/teams/Bluetooth-Low-Energy/code/BLE_API/
//    - Reference: http://developer.mbed.org/teams/Bluetooth-Low-Energy/code/BLE_API/docs/tip/
//    - Reference: http://developer.mbed.org/teams/Bluetooth-Low-Energy/
// Warnings:
//    - As of 20141210 it is necessary to use Android App [nRF-Master Control Panel] to ensure any previous connected 
//      code on mkit is properly Disconnected before trying to connect other Android nRF Apps (nRFToolbox, nRF UART 2.0, etc.).
//      As UART device doesn't offer disconnect you may need to load a 3rf sample, then connect, then discoonect, to clear the link.
// Notes: 
//    - onDataSent() maybe only occuring when confirmed receive by phone, as onDataSent() didn't happen when phone moved out of range.
//    - onDisconnect(Reason:8) occured after ~20Tx with no onDataSent() after phone moved out of range, OnTimeout didn't occur at all.
// ToDo: and ToCheck:
//    - Re-check where voltatile needed
//==========End of PR's Header

//==========Historic Licencing from original imported sample from mbed website [BLE_HeartRate] ==========
//From: http://developer.mbed.org/teams/Bluetooth-Low-Energy/code/BLE_HeartRate/
/* mbed Microcontroller Library
 * Copyright (c) 2006-2013 ARM Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *     http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. */
//==========end of Historic Licencing ==========


//==========Compile Options==========
#define ENABLE_SerialUSB_DEBUG_CONSOLE                  1  //PR: Enable Debug on mbed's USB Serial Debug, Setup: Serial 9600,8,N,1,NoFlowControl (TeraTerm: http://en.sourceforge.jp/projects/ttssh2/releases/)
#define UPDATE_PARAMS_FOR_LONGER_CONNECTION_INTERVAL    0  //PR: Option to slow the connection intervsal possibly saving power (After Connected)

//==========Includes==========
#include "mbed.h"
#include "BLEDevice.h"                  // BLE
#include "nrf_soc.h"                    // nRF Internal Temperature Sensor

//Services
#include "BatteryService.h"
#include "DeviceInformationService.h"
//#include "DFUService"                 //TODO: DFU and FOTA Support
#include "HealthThermometerService.h"   //TODO: Temperature, #include "ble_hts.h"
#include "HeartRateService.h"
//#include "UARTService.h"              //TODO: Add a UART Channel for streaming data like logs?

//==========Debug Console==========
#if ENABLE_SerialUSB_DEBUG_CONSOLE
    Serial  debug_serial(USBTX, USBRX); //PR: DebugSerialOverMbedUSB 9600-8N1N
    #define DEBUG(...) { debug_serial.printf(__VA_ARGS__); }
#else
    #define DEBUG(...)            //Do Nothing
#endif 

//========== This Section is to Create Debug output showing variable prep before main() ==========
bool u8_prep_dummy(void) {
    DEBUG("\n\nBLE: ____Prep Memory____\n");
    return true;
}
const bool bPrep = u8_prep_dummy();

//==========LEDs==========
//LEDs:
//DigitalOut  out_led1(LED1);             //PR: Firmware heartbeat
//DigitalOut  out_led2(LED2);             //PR: Firmware heartbeat
PwmOut      pwm_led1(LED1);             //PR: Firmware Life Indicator 
PwmOut      pwm_led2(LED2);             //TODO: Controlled by App 
float       f_led1level = 0.0;          //Initial Brightness (Typically 0.0~0.5)
float       f_led2level = 0.0;          //Initial Brightness (Typically 0.0~0.5)

//==========BLE==========
BLEDevice   ble;
const static char     pcDeviceName[]    = "blePRv04"; //PR: Why can App nRF-MCP modify this even though flagged as Const, maybe only temporary mod till App restarts?

//UUID List by Advertised 16bit UUID list (PartA)
static const uint16_t uuid16_list[]     = { //Service List (Pre-defined standard 16bit services)
    //*This list seems different from that shown in App:nRF-MCP, 
    //BLE_UUID_GAP  UUID_GENERIC_ACCESS                 //0x1800    //Included by Default, DeviceName, Appearance, PreferredConnectionParam
    //BLE_UUID_GATT UUID_GENERIC ATTRIBUTE              //0x1801    //Included by Default, ServiceChanged, 
    GattService::UUID_HEALTH_THERMOMETER_SERVICE,       //0x1809    //HTM (Might need to be first for nRF App)
    GattService::UUID_BATTERY_SERVICE,                  //0x180F    //BatteryLevel
    GattService::UUID_DEVICE_INFORMATION_SERVICE,       //0x180A    //sManufacturer, sModelNumber, sSerialNumber, sHWver, sFWver, sSWver
    GattService::UUID_HEART_RATE_SERVICE,               //0x180D    //HRM, BodyLocation, ControlPoint
//    GattService::UUID_HEALTH_THERMOMETER_SERVICE,       //0x1809    //HTM
    //x GattService::UUID_DFU,                          //0x1530 - See UARTServiceShortUUID in BLE_API:DFUService.cpp  //
    //x GattService::UARTService,                       //0x0001 - See DFUServiceShortUUID  in BLE_API:UARTService.cpp //
    //GattService::UUID_ALERT_NOTIFICATION_SERVICE,     = 0x1811,
    //GattService::UUID_CURRENT_TIME_SERVICE,           = 0x1805,
    //GattService::UUID_HUMAN_INTERFACE_DEVICE_SERVICE, = 0x1812,
    //GattService::UUID_IMMEDIATE_ALERT_SERVICE,       = 0x1802,
    //GattService::UUID_LINK_LOSS_SERVICE,              = 0x1803,
    //GattService::UUID_PHONE_ALERT_STATUS_SERVICE,     = 0x180E,
    //GattService::UUID_REFERENCE_TIME_UPDATE_SERVICE,  = 0x1806,
    //GattService::UUID_SCAN_PARAMETERS_SERVICE,        = 0x1813,
 };
 
//========== Prep UUID list (before main()) ==========
//UUID List by Advertised 128bit UUID list (PartA, ToDo:)
// Gatt characteristic and service UUIDs - Readable to UUID
const UUID stringToUUID(const char* str) {
    uint8_t array[16] = {0x55,0x55,0x55,0x55, 0x55,0x55,0x55,0x55, 0x55,0x55,0x55,0x55, 0x55,0x55,0x55,0x55};//Prefill 'U' so fully specified even short string
    for(int i = 0; i < strlen(str); i++) { //Don't convert unspecified bytes past end of string
        array[i] = str[i];    
    }
    
    DEBUG("UUID: Prep[%16s] --> [%02x %02x %02x %02x  %02x %02x %02x %02x  %02x %02x %02x %02x  %02x %02x %02x %02x] %c\n", 
        str, array[0], array[1], array[2], array[3], array[4], array[5], array[6], array[7], 
             array[8], array[9], array[10],array[11],array[12],array[13],array[14],array[15], array[15] );       
       
    return UUID(array);//TODO: Check if this array is safe or stack temporary??
}
//const UUID SCORING_GATT_SERVICE =               stringToUUID("nod.score1.serv ");
//const UUID THRESHOLD_GATT_CHARACTERISTIC =      stringToUUID("nod.score1.thres");
//const UUID DIVISOR_GATT_CHARACTERISTIC =        stringToUUID("nod.score1.div  ");
//const UUID INTERVAL_US_GATT_CHARACTERISTIC =    stringToUUID("nod.score1.intus");
// Strings to hex UUID                                         "0123456789ABCDEF"
const UUID UUID_GATT_SERVICE_A                  = stringToUUID("com.test.servA");
const UUID UUID_GATT_CHARACTERISTIC_A1          = stringToUUID("com.test.charA1");
const UUID UUID_GATT_CHARACTERISTIC_A2          = stringToUUID("com.test.charA2");
const UUID UUID_GATT_CHARACTERISTIC_SHORT       = stringToUUID("com.short");

//==========Functions:Timer==========
static volatile bool  b_Ticker1 = false;//Volatile, don't optimize, changes under interrupt control
void CallbackTicker1(void)
{
    static uint32_t    u32_Counter; // Counter for Debug Output

    //pwm_led1 = !pwm_led1; /* Do blinky on LED1 while we're waiting for BLE events */
    f_led1level+=0.1; if (f_led1level>0.5){f_led1level = 0.1;}; pwm_led1=f_led1level;//PR: Ramp Blink
    DEBUG("\nBLEi: Ticker1(%u) ", ++u32_Counter);
    b_Ticker1 = true;   //PR: Flag to handle Ticker1 Event in Main loop so interupts not blocked.
}

//==========Functions:BLE==========
void Callback_BLE_onTimeout(void)
{
    DEBUG("\nBLEi: Callback_BLE_onTimeout()\n" );
    //PR: Haven't seen this, even when phone moved out of range and OnDisconnect(Reason0x08) occurs
  
    //DEBUG("\nBLE:Callback_BLE_onTimeout(), Restarting Advertising\n" );
    //ble.startAdvertising();
}

//void  onDisconnection (Gap::DisconnectionEventCallback_t disconnectionCallback) 
void Callback_BLE_onDisconnect(Gap::Handle_t tHandle, Gap::DisconnectionReason_t eReason)
{
    //PR: onDisconnect(Reason:8) occured after ~20Tx with no onDataSent() after phone moved out of range

    //   REMOTE_USER_TERMINATED_CONNECTION = 0x13 = 19,
    //   LOCAL_HOST_TERMINATED_CONNECTION  = 0x16 = 22,
    //   CONN_INTERVAL_UNACCEPTABLE        = 0x3B = 59,
    DEBUG("\nBLEi: Callback_BLE_Disconnect(Handle:%d, eReason:0x%02x), Restarting Advertising\n",tHandle,eReason );//PR: Occurs properly when click disconnect in App nRFToolbox:HRM

    //DEBUG("Wait10sec...\n");wait(10.0); //PR: Optional to test effect on advertising
    ble.startAdvertising(); // restart advertising
}

//inline void BLEDevice::onConnection(Gap::ConnectionEventCallback_t connectionCallback){ transport->getGap().setOnConnection(connectionCallback);}
void Callback_BLE_onConnect(Gap::Handle_t tHandle, Gap::addr_type_t ePeerAddrType, const Gap::address_t c6PeerAddr, const Gap::ConnectionParams_t *params)
{
    DEBUG("\nBLEi: Callback_BLE_Connect(Handle:%d, eType:%d, Add:%u ...)\n", tHandle, ePeerAddrType, c6PeerAddr);

    #if UPDATE_PARAMS_FOR_LONGER_CONNECTION_INTERVAL
        /* Updating connection parameters can be attempted only after a connection has been
         * established. Please note that the ble-Central is still the final arbiter for
         * the effective parameters; the peripheral can only hope that the request is
         * honored. Please also be mindful of the constraints that might be enforced by
         * the BLE stack on the underlying controller.*/
        #define MIN_CONN_INTERVAL 250  /**< Minimum connection interval (250 ms) */
        #define MAX_CONN_INTERVAL 350  /**< Maximum connection interval (350 ms). */
        #define CONN_SUP_TIMEOUT  6000 /**< Connection supervisory timeout (6 seconds). */
        #define SLAVE_LATENCY     4

        Gap::ConnectionParams_t tGap_conn_params;
        tGap_conn_params.minConnectionInterval        = Gap::MSEC_TO_GAP_DURATION_UNITS(MIN_CONN_INTERVAL);
        tGap_conn_params.maxConnectionInterval        = Gap::MSEC_TO_GAP_DURATION_UNITS(MAX_CONN_INTERVAL);
        tGap_conn_params.connectionSupervisionTimeout = Gap::MSEC_TO_GAP_DURATION_UNITS(CONN_SUP_TIMEOUT);
        tGap_conn_params.slaveLatency                 = SLAVE_LATENCY;
        ble.updateConnectionParams(tHandle, &tGap_conn_params);
    #endif /* #if UPDATE_PARAMS_FOR_LONGER_CONNECTION_INTERVAL */
}

static volatile bool bSent = false; //Volatile, don't optimize, changes under interrupt control
static volatile unsigned uSentBLE;
void Callback_BLE_onDataSent(unsigned uSent){
    uSentBLE=uSent;
    DEBUG("BLEi: SentI(%u)", uSent); //TODO: PR: Why uSent always "1", expected it to match sent bytes length
    bSent = true;
    //PR: onDataSent() maybe only occuring when confirmed receive by phone, as onDataSent() didn't happen when phone moved out of range.
}

void Callback_BLE_onDataWritten(const GattCharacteristicWriteCBParams *pParams)
{
    // Callback_BLE_onDataWritten == This does occur when use nRF-MCP to save New Heart Rate Control Point (Ignored if incorrect length)

    //Warning: *data may not be NULL terminated
    DEBUG("\nBLEi: Callback_BLE_onDataWritten(Handle:%d, eOp:%d, uOffset:%u uLen:%u Data0[0x%02x]=Data[%*s]\n", pParams->charHandle, pParams->op, pParams->offset, pParams->len, (char)(pParams->data[0]), pParams->len, pParams->data);
}

void Callback_BLE_onUpdatesEnabled(Gap::Handle_t tHandle)
{
    DEBUG("\nBLEi: Callback_BLE_onUpdates(Handle:%d)\r\n", tHandle);
}

//==========main==========
int main(void)
{
    f_led1level = 1; pwm_led1 = f_led1level;//Start LED1=OnMax
    f_led2level = 1; pwm_led2 = f_led2level;//Start LED2=OnMax
    DEBUG("\nBLE: ___%s___\n", pcDeviceName); //Restart TeraTerm just before Pressing Reset on mbed
    DEBUG("BLE: Connect App for Data: nRF-MCP, nRF-Toolbox:HRM, etc.\n");

    Ticker ticker1;                             //PR: Timer Object(Structure)
    ticker1.attach(CallbackTicker1, 2.0);       //PR: Timer Handler, Float=PeriodSeconds

    //BLE1: Setup BLE Service (and event actions) //TODO: Check for services declared before main() - Is that OK?
    DEBUG("BLE: Setup BLE\n");
    ble.init();
    ble.onDisconnection(Callback_BLE_onDisconnect);
    ble.onConnection(Callback_BLE_onConnect); //PR: Not required if no actions enabled, enabled now just for debug printf()
    ble.onDataSent(Callback_BLE_onDataSent);
    ble.onDataWritten(Callback_BLE_onDataWritten);
    ble.onTimeout(Callback_BLE_onTimeout);
    ble.onUpdatesEnabled(Callback_BLE_onUpdatesEnabled);

//ble_error_t readCharacteristicValue  ( uint16_t  handle,    uint8_t *const  buffer,    uint16_t *const  lengthP  ) 
//ble_error_t  updateCharacteristicValue (uint16_t handle, const uint8_t *value, uint16_t size, bool localOnly=false) 

//UUID List by Services that are setup (Maybe this is what nRF-MCP discovers?)
    //BLE2: Setup Services
    DEBUG("BLE: Setup Services\n");
    HeartRateService            hrmService(ble, (uint8_t)111, HeartRateService::LOCATION_FINGER);
    HealthThermometerService    htmService(ble, 30.0, HealthThermometerService::LOCATION_EAR); 
    BatteryService              battService(ble, 11);//Declare the service for BLE:BATTERY
    DeviceInformationService    deviceInfo(ble, "Maker", pcDeviceName, "sn1234", "hw00", "fw00", "sw00");
                                //(BLEDevice), pcManufacturer, pcModelNumber, pcSerialNumber, pcHWver, pcFWver, pcSWver
    //BLE3: Setup advertising
    DEBUG("BLE: Setup Advertising\n");
    ble.setAdvertisingInterval(Gap::MSEC_TO_ADVERTISEMENT_DURATION_UNITS(1000)); //PR: Advertise 1sec (1Hz)
    ble.setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED); //PR: TODO
    ble.accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE); //PR: BLE Only, Options(LE_GENERAL_DISCOVERABLE/LE_LIMITED_DISCOVERABLE)

//UUID List by Advertised 16bit UUID list (PartB)
    ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, (uint8_t *)uuid16_list, sizeof(uuid16_list)); //PR: Might need to change for Custom Services/Characteristics

    ble.accumulateAdvertisingPayload(GapAdvertisingData::GENERIC_HEART_RATE_SENSOR);//PR: Add Heart Rate
    ble.accumulateAdvertisingPayload(GapAdvertisingData::GENERIC_THERMOMETER);  //PR: Add Thermometer
    ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)pcDeviceName, sizeof(pcDeviceName));//PR: Product?
    //Thermometer: ble.setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
    //Thermometer: ble.setAdvertisingInterval(160); /* 100ms; in multiples of 0.625ms. */
    ble.startAdvertising();

    DEBUG("BLE: Main Loop\n");
    uint32_t u32_wakeevents=0, u32_wakelast=0; // Counter&Register for tracking Wake Events (to see monitor their Frequency)
    while (true) {
        if (b_Ticker1 && ble.getGapState().connected) { //If Ticker1 and Connected Update Data
            b_Ticker1 = false; // Clear flag for next Ticker1, see CallbackTicker1()
  
            // Read Sensors, and update matching Service Characteristics (only if connected)
            uint8_t update_hrm(void); //prototype
            float   update_htm(void); //prototype
            uint8_t update_batt(void);//prototype
            hrmService.updateHeartRate( update_hrm() );
            htmService.updateTemperature( update_htm() );
            battService.updateBatteryLevel( update_batt() );
            
            DEBUG("BLE: Wakes:%u Delta:%d ", u32_wakeevents, u32_wakeevents-u32_wakelast); //For Evaluating Timing
            u32_wakelast = u32_wakeevents;           
        } else if (b_Ticker1) {
            b_Ticker1 = false; // Clear flag for next Ticker1, see CallbackTicker1()
            DEBUG("BLE: Tick while unconnected ");
        } else if (bSent){
            bSent=false; //clear flag
            //DEBUG("BLE: Sent %ubytes ", uSentBLE);
        } else {
            //DEBUG("BLE: Wait for Event\n\r"); //x Debug output here causes unnecessary wakes resulting in endless awakes.    
            ble.waitForEvent(); //PR: Wait for event - Yield control to BLE Stack and other events (Process ALL pending events before waiting again)
            f_led2level+=0.25; if (f_led2level>0.5){f_led2level = 0.0;}; pwm_led2=f_led2level;//PR: Ramp Blink
            u32_wakeevents++;   //PR: Count events for frequency monitoring (20141207PR: nRF51822 mbed HRM = 50Hz)
        }
    }
}

//==========HRM==========
//Adopted 2014Dec from http://developer.mbed.org/teams/Bluetooth-Low-Energy/code/BLE_HeartRate/
uint8_t update_hrm(void)//(bool bInit)
{
    static uint8_t u8_hrm = 100;
    u8_hrm++;
    if (u8_hrm >= 175) {
        u8_hrm = 100;
        DEBUG("BLE: HRM Rollover175->100 ");
    }
    DEBUG("[HRM:%d]", u8_hrm);   
    return(u8_hrm);
}
//==========HTM:Internal Temperature==========
//Adopted 2014Dec from: https://developer.mbed.org/users/takafuminaka/code/BLE_HTM_by_InTempSensr/
// Service:  https://developer.bluetooth.org/gatt/services/Pages/ServiceViewer.aspx?u=org.bluetooth.service.health_thermometer.xml
// HTM Char: https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.temperature_measurement.xml

//****Although nRF-MCP displays float OK, the HTM Apps don't, its possible IEEE format required like in the original code:BLE_HTM_by_InTempSensr 
float update_htm(void)
{
    //static float fTemperature = 0;//-123.456;
    int32_t i32_temp;
    sd_temp_get(&i32_temp);   //Read the nRF Internal Temperature (Die in 0.25'C steps, Counting From TBD), TODO:Check Scaling
    float fTemperature = (float(i32_temp)/4.0) - 16.0;   // Scale&Shift (0.25'C from -16'C?)
    DEBUG("[HTMi:%d HTMf:%f]", i32_temp, fTemperature);
    return(fTemperature);
}
//==========Battery==========
uint8_t update_batt(void)
{
    static uint8_t u8_BattPercent=33; //Level: 0..100% 
    u8_BattPercent <= 50 ? u8_BattPercent=100 : u8_BattPercent--; // Simulate Battery Decay
    DEBUG("[BATT:%d%%]", u8_BattPercent);
    return(u8_BattPercent);
}
//========== end ==========