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-16
Revision:
8:f187ba55aed2
Parent:
7:1097d012b01a
Child:
9:2d11beda333f

File content as of revision 8:f187ba55aed2:

//=========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/
//    - https://developer.mbed.org/users/garimagupta002/notebook/ble-uart-lcd-demo/  (Advertise UUID16+UUID128)
//    - 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:
//    - Handle All return codes for all functions not void, including BLE not BLE_ERROR_NONE, as a minimum output some debug and log event in non-volatile memory for diagnostics.
//    - Re-check where voltatile needed
//    - Evaluate setting: IS_SRVC_CHANGED_CHARACT_PRESENT, see: https://devzone.nordicsemi.com/question/22751/nrftoobox-on-android-not-recognizing-changes-in-application-type-running-on-nordic-pcb/?answer=23097#post-id-23097
//
//==========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 "HealthThermometerService.h"   
#include "HeartRateService.h"
#include "LinkLossService.h"            //TODO: How support this?
//#include "DFUService"                 //TODO: DFU and FOTA Support
//#include "UARTService.h"              //TODO: Add a UART Channel for streaming data like logs?

//==========Debug Console==========
#if ENABLE_SerialUSB_DEBUG_CONSOLE
    //Restart TeraTerm just before Pressing Reset on mbed, Default:9600-8N1(No Flow Control)
    //Using default baud rate to avoid issues with DEBUG in a constructor being at wrong baud rate before main()
    Serial  debug_serial(USBTX, USBRX); //PR: DebugSerialOverMbedUSB
    #define DEBUG(...) { debug_serial.printf(__VA_ARGS__); }
#else
    #define DEBUG(...) //Do Nothing, DEBUG() lines are ignored
#endif 

//========== This Section is to Create Debug output showing variable prep before main() ==========
bool u8_prep_dummy(void) {
    DEBUG("\n\nBLE: ____Prep Memory____\n"); //May comment this out if none of the following Objects/Classes call initiator functions with debug
    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==========
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?
BLEDevice   ble;
    HealthThermometerService    *pServiceHTM; 
    BatteryService              *pServiceBattery;
    HeartRateService            *pServiceHRM;
    DeviceInformationService    *pServiceDeviceInfo;
    LinkLossService             *pServiceLinkLoss;
    //UARTService               *pServiceUART;
        //pServiceUART->getTXCharacteristicHandle();
        //pServiceUART->getRXCharacteristicHandle();
        //ble.updateCharacteristicValue(pServiceUART->getRXCharacteristicHandle(), pData, uLen); // Tx to App


//==========UUID==========
//UUID16 List - Advertising these required by App nRF-Toolbox:HRM&HTM 
// Keep list short so advertizing not too long.
static const uint16_t uuid16_list[]     = { //Service List (Pre-defined standard 16bit services)
    // *Order here doesn't affect order in nRF-MCP Discovery of Services
    //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_HEART_RATE_SERVICE,               //0x180D    //HRM, BodyLocation, ControlPoint
    GattService::UUID_DEVICE_INFORMATION_SERVICE,       //0x180A    //sManufacturer, sModelNumber, sSerialNumber, sHWver, sFWver, sSWver
    GattService::UUID_BATTERY_SERVICE,                  //0x180F    //BatteryLevel
    GattService::UUID_LINK_LOSS_SERVICE,                //0x1803    //LinkLoss

    //x GattService::UUID_DFU,                          //0x1530 - See UARTServiceShortUUID in BLE_API:DFUService.cpp  //
    //x GattService::UARTService,                       //0x0001~0x0003 - 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_PHONE_ALERT_STATUS_SERVICE,     = 0x180E,
    //GattService::UUID_REFERENCE_TIME_UPDATE_SERVICE,  = 0x1806,
    //GattService::UUID_SCAN_PARAMETERS_SERVICE,        = 0x1813,
};

//UUID128 List(Only a single UUID128 can fit in advertising)  
uint8_t puUUID128_Bluetooth_Base_Rev[16] = {0xFB,0x34,0x9B,0x5F,0x80,0, 0,0x80, 0,0x10, 0,0, 0x00,0x00, 0,0};//0000****-0000-1000-8000 00805F9B34FB, Base for UUID16
uint8_t puUUID128_BatteryService[16]     = {0xFB,0x34,0x9B,0x5F,0x80,0, 0,0x80, 0,0x10, 0,0, 0x0F,0x18, 0,0};//0000****-0000-1000-8000 00805F9B34FB, ***=0x180F


/*    //Adopted from sample code in header of BLE_API:UUID.cpp
//    // Create a UUID16 (0x180F)
//    uint8_t shortID[16] = { 0, 0, 0x0F, 0x18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
//    UUID ble_uuid = UUID(shortID);  // Will have: ble_uuid.type  = UUID_TYPE_SHORT, ble_uuid.value = 0x180F
//    // Create a UUID128
//    uint8_t longID[16] = { 0x00,0x11,0x22,0x33, 0x44,0x55,0x66,0x77, 0x88,0x99,0xAA,0xBB, 0xCC,0xDD,0xEE,0xFF };
//    UUID custom_uuid = UUID(longID);// Will have: custom_uuid.type=UUID_TYPE_LONG, custom_uuid.value=0x3322, custom_uuid.base=00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF
*/

/* // The Nordic UART Service
//static const uint8_t uart_base_uuid[] = {0x71, 0x3D, 0, 0, 0x50, 0x3E, 0x4C, 0x75, 0xBA, 0x94, 0x31, 0x48, 0xF1, 0x8D, 0x94, 0x1E};
//static const uint8_t uart_tx_uuid[]   = {0x71, 0x3D, 0, 3, 0x50, 0x3E, 0x4C, 0x75, 0xBA, 0x94, 0x31, 0x48, 0xF1, 0x8D, 0x94, 0x1E};
//static const uint8_t uart_rx_uuid[]   = {0x71, 0x3D, 0, 2, 0x50, 0x3E, 0x4C, 0x75, 0xBA, 0x94, 0x31, 0x48, 0xF1, 0x8D, 0x94, 0x1E};
//static const uint8_t uart_base_uuid_rev[] = {0x1E, 0x94, 0x8D, 0xF1, 0x48, 0x31, 0x94, 0xBA, 0x75, 0x4C, 0x3E, 0x50, 0, 0, 0x3D, 0x71};// = flip uart_base_uuid[]
//const uint16_t UUID16_UART_Serv = 0x0001;
//const uint16_t UUID16_UART_Tx = 0x0002;
//const uint16_t UUID16_UART_Rx = 0x0003;
//const uint8_t  UUID128_UART_Serv[LENGTH_OF_LONG_UUID]={0x6E,0x40,(uint8_t)(UUID16_UART_Serv>>8),(uint8_t)(UUID16_UART_Serv&0xFF),0xB5,0xA3,0xF3,0x93,0xE0,0xA9,0xE5,0x0E,0x24,0xDC,0xCA,0x9E};
//const uint8_t  UUID128_UART_Rev[LENGTH_OF_LONG_UUID]={0x9E,0xCA,0xDC,0x24,0x0E,0xE5,0xA9,0xE0,0x93,0xF3,0xA3,0xB5,(uint8_t)(UUID16_UART_Serv&0xFF),(uint8_t)(UUID16_UART_Serv>>8),0x40,0x6E};//Use with: INCOMPLETE_LIST_128BIT_SERVICE_IDS
*/
 
//UUID Info:
// 20141213 From https://developer.bluetooth.org/community/lists/community%20discussion/flat.aspx?rootfolder=/community/lists/community+discussion/16+bit+uuid+vs.+128+uuid&folderctid=0x01200200e2f0e56e3d53004dba96bdf0c357551f
// 16bit UUID reserved 128bit base = 32 hex digits: 0000****-0000-1000-8000 00805F9B34FB (Careful to use unsigned to avoid negatives and overflows)
// 32bit UUID reserved 128bit base = 32 hex digits: ********-0000-1000-8000 00805F9B34FB (Careful to use unsigned to avoid negatives and overflows)
 /*     All the custom GATT based services and characteristics must use a 128 bit UUID. 
        The Bluetooth_Base_UUID is: 00000000-0000-1000-8000 00805F9B34FB. 
            All the 16-bit Attribute UUIDs defined in the adopted specifications use the above base. 
        Generating a 128 bit UUID for custom profiles: For the 128 bit UUID, please refer to The ITU-T Rec. X.667. 
        You can download a free copy of ITU-T Rec. X.667 from http://www.itu.int/ITU-T/studygroups/com17/oid/X.667-E.pdf. 
        In addition if you go to http://www.itu.int/ITU-T/asn1/uuid.html, you can generate a unique 128-bit UUID. 
        Latency: Refer to Core Spec V4.0, Vol 3, Part F - 3.3.1, which is "Ready by Type Request". 
        If you look at the PDU, you have to send 2 bytes UUID for adopted profiles and 16 bytes UUID for custom profiles. 
        There is additional 14 extra bytes over head for the custom profiles for "Ready By Type Request Method" 
        Note: All attribute types are effectively compared as 128-bit UUIDs, even if a 16-bit UUID is provided in this request or defined for an attribute. (See Section 3.2.1.) 
        A suggestive alternative will be to use a notification method, (see 3.4.7.1), where you don't need the 128 bit UUID or the indication method (see 3.4.7.2) 
 */

//========== UUID128 from readable strings based on URL (processed before main()) ==========
// UUID for released product may need to be properly generated, but for testing this can be convenient
// Adopted 20141214 from Nordic PUCK
uint8_t* puStrToUUID128(const char* pStr) {
    static uint8_t pUUID[LENGTH_OF_LONG_UUID];  //Call only once before using
    for(int i = 0; i < LENGTH_OF_LONG_UUID; i++) { 
        if( i < strlen(pStr) ){ pUUID[LENGTH_OF_LONG_UUID-i-1] = pStr[i];}
        else { pUUID[LENGTH_OF_LONG_UUID-i-1] =' '; } //Pad end with space character (Or your choice of character such as 'u' or 'x' or NULL)
    }  
    // (UUID128 byte order to match that displayed by App nRF-MCP)
    DEBUG("UUID: Prep[%-16s] --> [%02x %02x %02x %02x  %02x %02x %02x %02x  %02x %02x %02x %02x  %02x %02x %02x %02x]\n", 
        pStr, pUUID[15],pUUID[14],pUUID[13],pUUID[12], pUUID[11],pUUID[10],pUUID[ 9],pUUID[ 8], 
              pUUID[ 7],pUUID[ 6],pUUID[ 5],pUUID[ 4], pUUID[ 3],pUUID[ 2],pUUID[ 1],pUUID[ 0] );       
    return pUUID; //Pointer to prepared UUID128
}
// Strings to UUID128, test url: www.none.com: "0123456789ABCDEF"  (16chars=32HexDigits=32nibbles) 
//const char pc_com_none_servA[]  = "com.none.servA";// Provide a short URL you own (in reverse to match Android naming style)
//const char pc_com_none_charA1[] = "com.none.charA1";
//const char pc_com_none_charA2[] = "com.none.charA2";

//==========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: App nRF-MCP doesn't cause onDataSent(), while App nRF-ToolBox:HRM does cause onDataSent()
    //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);

    //Triggered by BluetoothLEGatt sample changing Linkloss setting:
    //Alert=1:  Debug=BLEi: Callback_BLE_onDataWritten(Handle:12, eOp:1, uOffset:0 uLen:1 Data0[0x01]=Data[]
    //Alert=2:  Debug=BLEi: Callback_BLE_onDataWritten(Handle:12, eOp:1, uOffset:0 uLen:1 Data0[0x02]=Data[]

/* From mbed project BLE_LoopbackUART:
    if ((uartServicePtr != NULL) && (params->charHandle == uartServicePtr->getTXCharacteristicHandle())) {
        uint16_t bytesRead = params->len;
        DEBUG("received %u bytes\n\r", bytesRead);
        ble.updateCharacteristicValue(uartServicePtr->getRXCharacteristicHandle(), params->data, bytesRead);
    }
*/
    //if(ble.getGapState().connected){ //Ensure BLE still connected
      //  if(params->charHandle == ble.getTXCharacteristicHandle())
//            htmService.updateTemperature( update_htm() );
//            hrmService.updateHeartRate( update_hrm() );
//            battService.updateBatteryLevel( update_batt() );


 /*   switch(pParams->op){
        case GATTS_CHAR_OP_INVALID:
        GATTS_CHAR_OP_WRITE_REQ  
        GATTS_CHAR_OP_WRITE_CMD
        GATTS_CHAR_OP_SIGN_WRITE_CMD         //< Signed Write Command
        GATTS_CHAR_OP_PREP_WRITE_REQ         //< Prepare Write Request
        GATTS_CHAR_OP_EXEC_WRITE_REQ_CANCEL  //< Execute Write Request: Cancel all prepared writes
        GATTS_CHAR_OP_EXEC_WRITE_REQ_NOW     //< Execute Write Request: Immediately execute all prepared writes
    }
*/



}

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

// Adopted 20141213 from http://developer.mbed.org/teams/Bluetooth-Low-Energy/code/BLE_LinkLoss/file/440ee5e8595f/main.cpp
void Callback_BLE_onLinkLoss(LinkLossService::AlertLevel_t level)
{
    printf("\nBLEi: Link loss alert[%d]\n", level);
}
//==========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);   
    pServiceHRM->updateHeartRate( u8_hrm );// Update Characteristic so sent by BLE
    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?)

    //{//Force to IEEE format to match needs of Apps like nRF-HTM and nRF-Toolbox:HTM
    // PR: Didn't work 20141213, might need 5byte style from BLE_HTM_by_InTempSensr if this is really necessary. OK in nRF-MCP for now.
    //    uint8_t  exponent = 0xFE; //exponent is -2
    //    uint32_t mantissa = (uint32_t)(fTemperature*100);
    //    uint32_t temp_ieee11073 = ((((uint32_t)exponent) << 24) | (mantissa)); //Note: Assumes Mantissa within 24bits
    //    memcpy(((uint8_t*)&fTemperature)+1, (uint8_t*)&temp_ieee11073, 4); //Overwrite with IEEE format float
    //}

    pServiceHTM->updateTemperature( fTemperature );// Update Characteristic so sent by BLE
    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
    pServiceBattery->updateBatteryLevel( u8_BattPercent ); // Update Characteristic so sent by BLE
    DEBUG("[BATT:%d%%]", u8_BattPercent);
    return(u8_BattPercent);
}
//==========main==========
int main(void)
{
    f_led1level = 1; pwm_led1 = f_led1level;//Start LED1=OnMax
    f_led2level = 1; pwm_led2 = f_led2level;//Start LED2=OnMax

    //Restart TeraTerm just before Pressing Reset on mbed, 9600-8N1(No Flow Control)
    DEBUG("\nBLE: ___%s___\n", pcDeviceName); 
    DEBUG("BLE: Connect App for Data: nRF-MCP, nRF-Toolbox:HRM/HTM, Android Sample BluetoothLeGatt, etc.\n");
    DEBUG("BLE: BluetoothLeGatt: App->mbed: LinkLoss->AlertLevel(UpArrow)\n"); 
    DEBUG("BLE: BluetoothLeGatt: App->mbed: BatteryService->BatteryLevel(DownArrow)\n"); 
    DEBUG("BLE: BluetoothLeGatt: mbed->App: BatteryService->BatteryLevel->EnableNotify(ThreeDownArrows), Also App Acks the send=DEBUG('SentI')\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);
    
    DEBUG("BLE: Handles:\n");
    //DEBUG(" Service: BLE: %d\n", ble.getHandle());
    //DEBUG(" Characteristic:BattLevel: %d\n", batteryLevelCharacteristic.getValueAttribute().getHandle());
    //DFUService

    
    

//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
    //BLE2: Setup Services (with their initial values and options)
    DEBUG("BLE: Setup Services\n");
    // *Order here affects order in nRF-MCP Discovery of Services
    DeviceInformationService    ServiceDeviceInfo(ble, "Maker", pcDeviceName, "sn1234", "hw00", "fw00", "sw00");//(BLEDevice), pcManufacturer, pcModelNumber, pcSerialNumber, pcHWver, pcFWver, pcSWver
        pServiceDeviceInfo      = &ServiceDeviceInfo; DEBUG("   Handle Service DeviceInfo:%d\n", ServiceDeviceInfo);
    LinkLossService             ServiceLinkLoss(ble, Callback_BLE_onLinkLoss, LinkLossService::HIGH_ALERT); //New20141213, TBD
        pServiceLinkLoss        = &ServiceLinkLoss;
    BatteryService              ServiceBattery(ble, 10);
        pServiceBattery         = &ServiceBattery;
    HeartRateService            ServiceHRM(ble, (uint8_t)111, HeartRateService::LOCATION_FINGER);
        pServiceHRM             = &ServiceHRM;
    HealthThermometerService    ServiceHTM(ble, 33.3, HealthThermometerService::LOCATION_EAR); 
        pServiceHTM             = &ServiceHTM;
    //UARTService               ServiceUART(ble);
    //  pServiceUART            = &ServiceUART;

    //BLE3: Setup advertising
    // Note the Advertising payload is limited to 31bytes, so careful don't overflow this. Multiple UUID16's or a single UUID128 are possible in advertising.
    // If there isn't enough space, then the accumulatepayload won't add that block to the advertising, it won't add a partial block.
    DEBUG("BLE: Setup Advertising\n");
    ble.clearAdvertisingPayload(); //Prep
    ble.setAdvertisingInterval(Gap::MSEC_TO_ADVERTISEMENT_DURATION_UNITS(1000));    //PR: Advertise 1sec (1Hz)
    ble.setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);       //PR: TODO: To Study
    ble.accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE); //PR: BLE Only (Option:LE_LIMITED_DISCOVERABLE)
    // Does "LE_GENERAL_DISCOVERABLE" affect whether UUID needs to be advertised to discover services?
    //ble.accumulateAdvertisingPayload(GapAdvertisingData::INCOMPLETE_LIST_128BIT_SERVICE_IDS, (const uint8_t *)UUID128_UART_Rev, sizeof(UUID128_UART_Rev)); //Single UUID128
    //ble.accumulateAdvertisingPayload(GapAdvertisingData::INCOMPLETE_LIST_128BIT_SERVICE_IDS, (const uint8_t *)puUUID128_BatteryService, sizeof(puUUID128_BatteryService)); //Single UUID128
    //ble.accumulateAdvertisingPayload(GapAdvertisingData::INCOMPLETE_LIST_128BIT_SERVICE_IDS, puStrToUUID128( pc_com_none_servA), LENGTH_OF_LONG_UUID); //Single UUID128
    //ble.accumulateAdvertisingPayload(GapAdvertisingData::INCOMPLETE_LIST_16BIT_SERVICE_IDS, (uint8_t *)uuid16_list, sizeof(uuid16_list)); // Multiple UUID16
    ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, (uint8_t *)uuid16_list, sizeof(uuid16_list)); // Multiple UUID16
    //ble.accumulateAdvertisingPayload(GapAdvertisingData::GENERIC_HEART_RATE_SENSOR);//PR: Add Appearance::HeartRate (HRM Connects without this)
    //ble.accumulateAdvertisingPayload(GapAdvertisingData::GENERIC_THERMOMETER);      //PR: Add Appearance::Thermometer (HTM Connects without this, though float format still needs change)
    // Add LocalName last so if Advertising too long will easily see as Name won't be available for the device.
    ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)pcDeviceName, sizeof(pcDeviceName)-1);//PR: LocalName (No NULL) 
    ble.startAdvertising();

    // Raw Advertizing max length 31bytes:             0x01020304050607080910111213141516171819202122232425262728293031
    // Example Raw Advertising caught by App nRF-MCP:  0x020106070309180A180D180909626C655052763034
    // = Len02 Type01 Value06                                   (Flags: BREDR_NOT_SUPPORTED, LE_GENERAL_DISCOVERABLE)
    // = Len07 Type03 Values: 0918 0A18 0D18                    (ListUUID16: 1809 180A 180D )
    // = Len09 Type09 Values: 62 6C 65 50 52 76 30 34           (LocalName = "blePRv04")

    // Example Raw Advertising caught by App nRF-MCP:  0x02010611069ECADC240EE5A9E093F3A3B50100406E090209180A180D180F18 (==Max Length)
    // = Len02 Type01 Value06                                   (Flags: BREDR_NOT_SUPPORTED, LE_GENERAL_DISCOVERABLE)
    // = Len11 Type06 Value9ECADC240EE5A9E093F3A3B5_0100_406E   (UUID128: NordicUART=0x0001)
    // = Len09 Type02 Values:0918 0A18 0D18 0F18                (UUID16: 1809 180A 180D 180F)
    // = LocalName field wasn't appended as insufficient space, so Device won't be named when scanning.

    // Example Raw Advertising caught by App nRF-MCP:  0x0201061106FB349B5F80000080001000000F180000070209180A180D18
    // = Len02 Type01 Value06                                   (Flags: BREDR_NOT_SUPPORTED, LE_GENERAL_DISCOVERABLE)
    // = Len11 Type06 ValueFB349B5F8000008000100000_0F18_0000   (UUID128: BluetoothBattery=0x180F)
    // = Len07 Type02 Value 0918 0A18 0D18                      (UUID16: 1809 180A 180D )
    // = LocalName field wasn't appended as insufficient space, so Device won't be named when scanning.

    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)
            // *Order here doesn't affect order in nRF-MCP Discovery of Services
            //TBD: Maybe save power by not Tx unless enabled by App?
            // The Services are discovered if they were setup/started (They don't need update to be discovered)        
            update_htm();
            update_hrm();
            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)
        }
    }
}
//========== end ==========