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:
2015-01-26
Revision:
26:942faa4201d5
Parent:
25:1c6c2895f729

File content as of revision 26:942faa4201d5:

//=========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/users/jksoft/code/BLE_RCBController/   (Setting up a custom Service/Characteristic)
//    - 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)
//    - http://developer.mbed.org/teams/UCL-IoT/code/BLE_LED_Controller/             (With matching Android App, uses BLE-UART)
//    - https://developer.mbed.org/users/todotani/code/BLE_Health_Thermometer2-010/wiki/BLE_Health_Thermometer2-010 //Temperature handles
//    - https://developer.mbed.org/users/rosterloh84/code/Buddi_Blueband (Disconnect Reasons)
//    - miscellaneous adopted from more samples...
// Reference:
//    - nRF51822:
//          - MKit Pins: http://developer.mbed.org/users/mbed_official/code/mbed-src/file/c80ac197fa6a/targets/hal/TARGET_NORDIC/TARGET_MCU_NRF51822/TARGET_NRF51822_MKIT/PinNames.h
//          - Platform:  http://developer.mbed.org/platforms/Nordic-nRF51822/
//          - MCU:       http://developer.mbed.org/users/mbed_official/code/mbed-src/file/d2c15dda23c1/targets/cmsis/TARGET_NORDIC/TARGET_MCU_NRF51822
//    - 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 non-uart sample, then use MCP to connect and 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, and no onDataSent() with App nRF-MCP.
//    - 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
//    - ToCheck(Maybe in App): device.CreateBond(), device.SetPairingConfirmation(), gatt.refresh(to clear ble cache when changing services in devlopment)
//    - 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
//    - invalid or untested reference code commented with "//x ", feel free to delete or experiment
//    - valid alternate code commented with "//a " prefix to indicate alternnative is valid code
//==========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_uSerial  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/)
//x #define ENABLE_BLE_SLOW 0   //PR: Option to slow the connection interval possibly saving power (After Connected) -- Imported feature never tested

//==========Includes==========
#include "mbed.h"                       // mbed
#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_uSerial
    //Restart TeraTerm just before Pressing Reset on mbed, Default Serual=9600-8N1(No Flow Control)
    //Using default baud rate to avoid issues with DEBUG in a constructor being at default baud rate before main()
    //TeraTerm: http://en.sourceforge.jp/projects/ttssh2/releases/
    Serial  debug_userial(USBTX, USBRX); //PR: DebugSerialOverMbedUSB
    #define DEBUG(...) { debug_userial.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();

//========== IO Hardware: Buttons, LEDs, PWM ==========
// Inputs: (Simultaneous DigitalIn and InteruptIn is OK)
DigitalIn       bB1in(BUTTON1); //nRF51822p0.16==pin22 //if(bB1in){}
DigitalIn       bB2in(BUTTON2); //nRF51822p0.17==pin25 //if(bB2in){}
InterruptIn     B1int(BUTTON1); //nRF51822p0.16==pin22 //B1int.rise(&onB1rise);  
InterruptIn     B2int(BUTTON2); //nRF51822p0.17==pin25 //B1int.fall(&onB1fall);

// Outputs:
//a DigitalOut      bL1out(LED1);   // Direct LED1 drive
//a DigitalOut    bL2out(LED2);   // Direct LED2 drive
PwmOut fL1pwm(LED1); float fL1level = 0.1;  // PWM LED1, brightness=float(0.0~1.0)
PwmOut fL2pwm(LED2); float fL2level = 0.1;  // PWM LED2, brightness=float(0.0~1.0)
bool bAppControl = false;                   //Flag: Host has control of LEDs through BLE

//a volatile uint8_t uB1rise; void onB1rise(void){ uB1rise++; };// Flag Event, Counter helps detect missed events since last cleared
//a volatile uint8_t uB1fall; void onB1fall(void){ uB1fall++; };// Flag Event, Counter helps detect missed events since last cleared
//a volatile uint8_t uB2rise; void onB2rise(void){ uB2rise++; };// Flag Event, Counter helps detect missed events since last cleared
//a volatile uint8_t uB2fall; void onB2fall(void){ uB2fall++; };// Flag Event, Counter helps detect missed events since last cleared

//========== ADC ==========
// ADC/Analog: 20150106PR
//    - Options:
//      - AnalogIn, Default: http://developer.mbed.org/handbook/AnalogIn
//      - ADC Lib, http://developer.mbed.org/users/simonb/code/ADC_test/
//      - Fast, but burns power: http://developer.mbed.org/users/Sissors/code/FastAnalogIn/
// Initially using the mbed standard: AnalogIn  (Later may change when power or timing require it)
//      - adcX.read() returns a float, scaled so 0.0~1.0 == ADC range 0~Vcc (0.3 == 30% of Vcc)
//      - adcX.read_u16() returns uint16, raw (0x0000~0xFFFF)
//nRF51822 mkit Analog Pins are only p1~p6 (P0_1~P0_6):
//AnalogIn adc0(p26);// nRF51822p0.26==pin45==AIN0==XL2   (Reserve for low power 32KHz Crystal)
//AnalogIn adc1(p27);// nRF51822p0.27==pin46==AIN1==XL1   (Reserve for low power 32KHz Crystal)
//AnalogXX AREF0     // nRF51822p0.00==pin04      ==AREF0 (Reserve for Analog Reference)
AnalogIn   adcA(p1); // nRF51822p0.01==pin05==AIN2
AnalogIn   adcB(p2); // nRF51822p0.02==pin06==AIN3
//AnalogIn   adcC(p3); // nRF51822p0.03==pin07==AIN4
//AnalogIn   adcD(p4); // nRF51822p0.04==pin08==AIN5
//AnalogIn   adcE(p5); // nRF51822p0.05==pin09==AIN6 
//AnalogIn   adcF(p6); // nRF51822p0.06==pin10==AIN7==AREF1 (Option: Reserve for Analog Reference)

void vShowADC(void)
{   //TODO: Check what the actual ADC register settings are to know how the nRF51822 is actually configured
    DEBUG(" ShowADC:");
    DEBUG(" p1=AIN2 0x%04X %f,", adcA.read_u16(), adcA.read() );
    DEBUG(" p2=AIN3 0x%04X %f,", adcB.read_u16(), adcB.read() );
    //DEBUG(" p3=AIN4 0x%04X %f,", adcC.read_u16(), adcC.read() );
    //DEBUG(" p4=AIN5 0x%04X %f,", adcD.read_u16(), adcD.read() );
    //DEBUG(" p5=AIN6 0x%04X %f,", adcE.read_u16(), adcE.read() );
    //DEBUG(" p6=AIN7 0x%04X %f,", adcF.read_u16(), adcF.read() );
    DEBUG(" \n");  
}

//==========BLE==========
//const static char pcDeviceName[] = "bleIOv04_pr"; //Too Long for advertising with a UUID128
const static char pcDeviceName[] = "bleIOr25"; //Advertised device name (Max length depends on available space with other Advertising data)
BLEDevice   ble;
//Pointers to services for accesses outside main()
    HeartRateService            *pServiceHRM;
    HealthThermometerService    *pServiceHTM;
    BatteryService              *pServiceBattery;
    DeviceInformationService    *pServiceDeviceInfo;
    LinkLossService             *pServiceLinkLoss;
    //x DFUService                *pServiceDFU;
    //x UARTService               *pServiceUART;      //FromApp: pServiceUART->getTXCharacteristicHandle(); //ToApp: ble.updateCharacteristicValue(pServiceUART->getRXCharacteristicHandle(), pData, uLen);
    //x bleIOService              *pServiceIO;

//==========UUID==========
//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
 /*     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)  */
// 16bit UUID uses: 128bit base(32 hex digits): 0000****-0000-1000-8000 00805F9B34FB (Careful to use unsigned to avoid negatives and overflows)
// 32bit UUID uses: 128bit base(32 hex digits): ********-0000-1000-8000 00805F9B34FB (Careful to use unsigned to avoid negatives and overflows)

//UUID128 List(Only a single UUID128 can fit in advertising)  
//a 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, where ****==UUID16
//a 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, where ****==0x180F

//a //UUID16 List - Advertising these required by App nRF-Toolbox:HRM&HTM // Keep list short so advertizing not over 31bytes.
/*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 //
    //Possibly useful:
    //x GattService::UUID_ALERT_NOTIFICATION_SERVICE,     //0x1811
    //x GattService::UUID_CURRENT_TIME_SERVICE,           //0x1805
    //x GattService::UUID_HUMAN_INTERFACE_DEVICE_SERVICE, //0x1812
    //x GattService::UUID_IMMEDIATE_ALERT_SERVICE,        //0x1802
    //x GattService::UUID_PHONE_ALERT_STATUS_SERVICE,     //0x180E
    //x GattService::UUID_REFERENCE_TIME_UPDATE_SERVICE,  //0x1806
    //x GattService::UUID_SCAN_PARAMETERS_SERVICE,        //0x1813
};*/

//========== bleIO - A Custom Service for Handling IO with an App (GPIO,PWM,etc.)==========
//UUID128 List - Note only the single Service UUID128 can fit in the advertising  

// Custom UUID128 base for this service and its characteristics (from: http://www.itu.int/ITU-T/asn1/uuid.html)
    // **This is a temporary UUID for testing by anyone, should be replaced before proceeding to real product.**
    // Can check that UUID is correctly entered by viewing with Android App: nRF-Master Control panel
    // UUID128 can be generated using: http://www.itu.int/ITU-T/asn1/uuid.html
    // 20141218PR: [4d3281c0-86d1-11e4-b084-0002a5d5c51b] == OID[2.25.102612802202735078710424021169255859483]
    // Base UUID128 (1 digit modified):{0x4d,0x32,0x81,0xc0,0x86,0xd1,0x11,0xe4,0xb0,0x84,0x00,0x02,0xa5,0xd5,0xc5,0x1*};// NormalOrder
    uint8_t puUUID128_IOService[16]  = {0x4d,0x32,0x81,0xc0,0x86,0xd1,0x11,0xe4,0xb0,0x84,0x00,0x02,0xa5,0xd5,0xc5,0x10};// Service UUID
    uint8_t puUUID128_IOAdvertise[16]= {0x10,0xc5,0xd5,0xa5,0x02,0x00,0x84,0xb0,0xe4,0x11,0xd1,0x86,0xc0,0x81,0x32,0x4d};// Advertising Service UUID (=ReversedOrder)
    // Characteristic UUID: Initially using generic blocks of IO data that may be manually dividied up into Buttons, LEDs, PWM, ADC, etc.
    uint8_t puUUID128_IOw8[16]       = {0x4d,0x32,0x81,0xc0,0x86,0xd1,0x11,0xe4,0xb0,0x84,0x00,0x02,0xa5,0xd5,0xc5,0x11};// Write[8byte]  to mbed from phone
    uint8_t puUUID128_IOr4[16]       = {0x4d,0x32,0x81,0xc0,0x86,0xd1,0x11,0xe4,0xb0,0x84,0x00,0x02,0xa5,0xd5,0xc5,0x12};// Read[4byte]   from mbed to phone
    uint8_t puUUID128_IOn4[16]       = {0x4d,0x32,0x81,0xc0,0x86,0xd1,0x11,0xe4,0xb0,0x84,0x00,0x02,0xa5,0xd5,0xc5,0x13};// Notify[4byte] from mbed to phone 
    // Alternately can have a characterostic for each discrete item, such as:
    //a uint8_t puUUID128_bleConfig32[16] = {0x1X,0xc5,0xd5,0xa5,0x02,0x00,0x84,0xb0,0xe4,0x11,0xd1,0x86,0xc0,0x81,0x32,0x4d};// Configuration 32bits
    //a uint8_t puUUID128_bleOut1[16]     = {0x1X,0xc5,0xd5,0xa5,0x02,0x00,0x84,0xb0,0xe4,0x11,0xd1,0x86,0xc0,0x81,0x32,0x4d};// Output 1bit
    //a uint8_t puUUID128_bleOutPWM100[16]= {0x1X,0xc5,0xd5,0xa5,0x02,0x00,0x84,0xb0,0xe4,0x11,0xd1,0x86,0xc0,0x81,0x32,0x4d};// Output PWM (Range 0~100%)
    //a uint8_t puUUID128_bleOutPWM256[16]= {0x1X,0xc5,0xd5,0xa5,0x02,0x00,0x84,0xb0,0xe4,0x11,0xd1,0x86,0xc0,0x81,0x32,0x4d};// Output PWM (256 levels, 0~255)
    //a uint8_t puUUID128_bleIn1[16]      = {0x1X,0xc5,0xd5,0xa5,0x02,0x00,0x84,0xb0,0xe4,0x11,0xd1,0x86,0xc0,0x81,0x32,0x4d};// Input 1bit
    //a uint8_t puUUID128_bleInADC12[16]  = {0x1X,0xc5,0xd5,0xa5,0x02,0x00,0x84,0xb0,0xe4,0x11,0xd1,0x86,0xc0,0x81,0x32,0x4d};// Input ADC 12bit

/* Android:
        //bleIO Service and Characteristics
        attributes.put("4d3281c0-86d1-11e4-b084-0002a5d5c51b", "bleIO base");
        attributes.put("4d3281c0-86d1-11e4-b084-0002a5d5c510", "bleIO Service");
        attributes.put("4d3281c0-86d1-11e4-b084-0002a5d5c511", "bleIO w8");
        attributes.put("4d3281c0-86d1-11e4-b084-0002a5d5c512", "bleIO r4");
        attributes.put("4d3281c0-86d1-11e4-b084-0002a5d5c513", "bleIO n4");

        attributes.put("1bc5d5a5-0200-84b0-e411-d186c081324d", "bleIO rev base");
        attributes.put("10c5d5a5-0200-84b0-e411-d186c081324d", "bleIO rev Service");
        attributes.put("11c5d5a5-0200-84b0-e411-d186c081324d", "bleIO rev w8");
        attributes.put("12c5d5a5-0200-84b0-e411-d186c081324d", "bleIO rev r4");
        attributes.put("13c5d5a5-0200-84b0-e411-d186c081324d", "bleIO rev n4");
*/

    // Characteristic Value Storage (with Initial values, should match actual data type transferred, doesn't have to be byte array):
    //a uint8_t pIOw8[8] = {0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17};   //Value Storage for Characteristic Write8 
    //a uint8_t pIOr4[4] = {0x20,0x21,0x22,0x23};                       //Value Storage for Characteristic Read4 
    //a uint8_t pIOn4[4] = {0x30,0x31,0x32,0x33};                       //Value Storage for Characteristic Notify4 

    union { // Option: split long setup from short updates to minimize BLE power and traffic
        struct {// First item in union defines the initializers
            uint8_t     L1pwm100;   // LED1 Brightness (0%~100%)
            uint8_t     L2pwm255;   // LED2 Brightness (0~255)
            //float       xxxx;     //Option: If both smartphone and device use same type of float, else put converter in smartphone code (phone has larger battery and memory)
            uint16_t    px[3];      // Spare 3*16bit (Test: incremented by Ticker1
        } s;
        uint8_t pu[];               //Direct Byte access
        char    pc[];               //Character Access (No Terminating Null)
    } sIOw8 = {100,255,0xA00A, 0xB000, 0xC000}; // Structure/union for Characteristic IOw8
    // sIOw8.s.L1pwm100, sIOw8.s.L2pwm255, sIOw8.s.px, sIOw8.pu 

    union { // Longer maybe OK since only read upon command
        struct {// First item in union defines the initializers
            uint8_t bB1p:1,bB2p:1;  // Button status, 1/true=Pressed, bit1==Button1, bit2==Button2 
            uint8_t uB1p:6;         // Count Button1 Presses (6bit)
            uint8_t uB1r;           // Count Button1 Releases
            uint8_t uB2p;           // Count Button2 Presses
            uint8_t uB2r;           // Count Button2 Releases
            //                      // Option: ADC or other inputs or device status
        } s;
        uint8_t pu[];               //Direct Byte access
        char    pc[];               //Character Access (No Terminating Null)
    } sIOr4 = { true, false, 0x3F, 255, 255, 255}; // Structure/union for Characteristic IOr4
    // sIOr4.s.bB1p, sIOr4.s.bB1p, sIOr4.s.uB1p, sIOr4.s.bB1r, sIOr4.s.uB2p, sIOr4.s.uB2r, sIOr4.pu

    union { // Option: Shorten content to reduce BLE power and traffic
        struct {// First item in union defines the initializers
            uint8_t bB1p:1,bB2p:1;  // Button status, 1/true=Pressed, bit1==Button1, bit2==Button2 
            uint8_t uB1p:6;         // Count Button2 Presses (6bit) *Notify*
            uint8_t uB2r;           // Count Button2 Releases       *Notify*
            int16_t iTempC100;      // Temperature in hundreths of 'C (i.e. float = iTempC10/100)
            //                      // Option: ADC or other inputs or device status
        } s;
        uint8_t pu[];               //Direct Byte access
        char    pc[];               //Character Access (No Terminating Null)
    } sIOn4 = { false, true, 0x3F, 255, -2345/*=-23.45'C*/}; // Structure/union for Characteristic IOn4
    // sIOn4.s.bB1p, sIOn4.s.bB2p, sIOn4.s.uB1p, sIOn4.s.uB2r, sIOn4.s.iTempC100, sIOn4.pu

    //x GattCharacteristic::GattAttribute *descriptors[]; //How Set Characteristic Descriptors? Maybe in the App is easier?

    //IO Characteristics: //GattCharacteristics xx(*UUID, *Payload=*Value, LenInit, LenMax, Properties)
    //GattCharacteristic  IOw8(puUUID128_IOw8, sIOw8.pu, sizeof(sIOw8), sizeof(sIOw8), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE_WITHOUT_RESPONSE);
    GattCharacteristic  IOw8(puUUID128_IOw8, sIOw8.pu, sizeof(sIOw8), sizeof(sIOw8), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE_WITHOUT_RESPONSE);// Allow Host to read back last setting
    GattCharacteristic  IOr4(puUUID128_IOr4, sIOr4.pu, sizeof(sIOr4), sizeof(sIOr4), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ);  // Host can manually request
    GattCharacteristic  IOn4(puUUID128_IOn4, sIOn4.pu, sizeof(sIOn4), sizeof(sIOn4), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY);// Host is Notified of changes (Same as HRM), *Works but first read not until a channge
    //GattCharacteristic  IOn4(puUUID128_IOn4, sIOn4.pu, sizeof(sIOn4), sizeof(sIOn4), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY); // Host is notified of changes (Same as HTM & BattLevel), Buggy until can queue BLE Operations
    GattCharacteristic *pCustomCharacteristics[] = {&IOw8, &IOr4, &IOn4};//Table of Pointers to GattCharacteristics

    //IO Service: //GattService::GattService(*UUID, *GattCharacteristics, (numCharacteristics) ) :
    GattService ServiceIO(puUUID128_IOService, pCustomCharacteristics, (sizeof(pCustomCharacteristics)/sizeof(pCustomCharacteristics[0])));
    GattService *pServiceIO= &ServiceIO;  // Pointer to Service

//========== Functions for IO Characteristics ========
void vShowIO(void)
{   //TODO: For Read Characteristics get data actually written to Characteristic, versus the memory expected to be in characteristic.
    uint8_t puBuf8[8];
    uint16_t uLen;

    DEBUG(" ShowIO:\n");

    // IOw8 Write (Displaying structure and buffer(c) data for comparison)
    uLen=sizeof(puBuf8); ble.readCharacteristicValue(IOw8.getValueHandle(), puBuf8, &uLen);  // Real Characteristic's Actual Value
    //DEBUG(" sIOw8: Handle:%d Len:%d L1:%d L2:%d %04X %04X %04X\n", IOw8.getValueHandle(), uLen, sIOw8.s.L1pwm100, sIOw8.s.L2pwm255, sIOw8.s.px[0], sIOw8.s.px[1], sIOw8.s.px[2]);
    //DEBUG(" sIOw8: Handle:%d Len:%d [%02X %02X %02X %02X  %02X %02X %02X %02X]\n", IOw8.getValueHandle(), sizeof(sIOw8), sIOw8.pu[0],sIOw8.pu[1],sIOw8.pu[2],sIOw8.pu[3],sIOw8.pu[4],sIOw8.pu[5],sIOw8.pu[6],sIOw8.pu[7]);
    //DEBUG(" cIOw8: Handle:%d Len:%d [%02X %02X %02X %02X  %02X %02X %02X %02X]\n", IOw8.getValueHandle(), uLen, puBuf8[0], puBuf8[1], puBuf8[2], puBuf8[3], puBuf8[4], puBuf8[5], puBuf8[6], puBuf8[7] );
    DEBUG(" sIOw8: Handle:%d Len:%d L1:%d L2:%d %04X %04X %04X", IOw8.getValueHandle(), uLen, sIOw8.s.L1pwm100, sIOw8.s.L2pwm255, sIOw8.s.px[0], sIOw8.s.px[1], sIOw8.s.px[2]);
    DEBUG(" sIOw8:[%02X %02X %02X %02X  %02X %02X %02X %02X]",   IOw8.getValueHandle(), sizeof(sIOw8), sIOw8.pu[0],sIOw8.pu[1],sIOw8.pu[2],sIOw8.pu[3],sIOw8.pu[4],sIOw8.pu[5],sIOw8.pu[6],sIOw8.pu[7]);
    DEBUG(" cIOw8:[%02X %02X %02X %02X  %02X %02X %02X %02X]\n", IOw8.getValueHandle(), uLen, puBuf8[0], puBuf8[1], puBuf8[2], puBuf8[3], puBuf8[4], puBuf8[5], puBuf8[6], puBuf8[7] );
 
    // IOr4 Read  (Displaying structure and buffer(c) data for comparison)
    uLen=sizeof(puBuf8); ble.readCharacteristicValue(IOr4.getValueHandle(), puBuf8, &uLen);  // Real Characteristic's Actual Value
    //DEBUG("\n sIOr4: Handle:%d Len:%d B1:%d B2:%d B1p:%d B1r:%d B2p:%d B2r:%d\n", IOr4.getValueHandle(), uLen, sIOr4.s.bB1p, sIOr4.s.bB2p, sIOr4.s.uB1p, sIOr4.s.uB1r, sIOr4.s.uB2p, sIOr4.s.uB2r);
    //DEBUG(" sIOr4: Handle:%d Len:%d [%02X %02X %02X %02X]\n", IOr4.getValueHandle(), sizeof(sIOr4), sIOr4.pu[0], sIOr4.pu[1], sIOr4.pu[2], sIOr4.pu[3]);
    //DEBUG(" cIOr4: Handle:%d Len:%d [%02X %02X %02X %02X]\n", IOr4.getValueHandle(), uLen, puBuf8[0], puBuf8[1], puBuf8[2], puBuf8[3] );
    DEBUG(" sIOr4: Handle:%d Len:%d B1:%d B2:%d B1p:%d B1r:%d B2p:%d B2r:%d", IOr4.getValueHandle(), uLen, sIOr4.s.bB1p, sIOr4.s.bB2p, sIOr4.s.uB1p, sIOr4.s.uB1r, sIOr4.s.uB2p, sIOr4.s.uB2r);
    DEBUG(" sIOr4:[%02X %02X %02X %02X]",   IOr4.getValueHandle(), sizeof(sIOr4), sIOr4.pu[0], sIOr4.pu[1], sIOr4.pu[2], sIOr4.pu[3]);
    DEBUG(" cIOr4:[%02X %02X %02X %02X]\n", IOr4.getValueHandle(), uLen, puBuf8[0], puBuf8[1], puBuf8[2], puBuf8[3] );

    // IOn4 Notify  (Displaying structure and buffer(c) data for comparison)
    uLen=sizeof(puBuf8); ble.readCharacteristicValue(IOn4.getValueHandle(), puBuf8, &uLen);  // Real Characteristic's Actual Value
    //DEBUG("\n sIOn4: Handle:%d Len:%d B1:%d B2:%d B1p:%d B2r:%d %.2f\n", IOn4.getValueHandle(), uLen, sIOn4.s.bB1p, sIOn4.s.bB2p, sIOn4.s.uB1p, sIOn4.s.uB2r, ((float)sIOn4.s.iTempC100/100.0));
    //DEBUG(" sIOn4: Handle:%d Len:%d [%02X %02X %02X %02X]\n", IOn4.getValueHandle(), sizeof(sIOn4), sIOn4.pu[0], sIOn4.pu[1], sIOn4.pu[2], sIOn4.pu[3]);
    //DEBUG(" cIOn4: Handle:%d Len:%d [%02X %02X %02X %02X]\n", IOn4.getValueHandle(), uLen, puBuf8[0], puBuf8[1], puBuf8[2], puBuf8[3] );
    DEBUG(" sIOn4: Handle:%d Len:%d B1:%d B2:%d B1p:%d B2r:%d %.2f", IOn4.getValueHandle(), uLen, sIOn4.s.bB1p, sIOn4.s.bB2p, sIOn4.s.uB1p, sIOn4.s.uB2r, ((float)sIOn4.s.iTempC100/100.0));
    DEBUG(" sIOn4:[%02X %02X %02X %02X]",   IOn4.getValueHandle(), sizeof(sIOn4), sIOn4.pu[0], sIOn4.pu[1], sIOn4.pu[2], sIOn4.pu[3]);
    DEBUG(" cIOn4:[%02X %02X %02X %02X]\n", IOn4.getValueHandle(), uLen, puBuf8[0], puBuf8[1], puBuf8[2], puBuf8[3] );

    //DEBUG("\n Handles for Standard Services' Characteristics:\n");
    //DEBUG("  HRM: Rate:%d  Location:%d  Control:%d", pServiceHRM->getCharacteristic(0)->getValueHandle(),pServiceHRM->getCharacteristic(1)->getValueHandle(),pServiceHRM->getCharacteristic(2)->getValueHandle());
    //DEBUG("  HTM: Temp:%d  Type:%d",    pServiceHTM->getCharacteristic(0)->getValueHandle(),pServiceHTM->getCharacteristic(1)->getValueHandle());
    //DEBUG("  Batt: Level:%d",           pServiceBattery->getCharacteristic(0)->getHandle());
    //DEBUG("  DeviceInfo: %d~%d",        pServiceDeviceInfo->getCharacteristic(0)->getHandle(), pServiceDeviceInfo->getCharacteristic(5)->getHandle());
    //DEBUG("  LinkLoss: AlertLevel:%d",  pServiceLinkLoss->getCharacteristic(0)->getHandle());
    //DEBUG("  DFU:%d",           pServiceDFU.getValueAttribute().getHandle());
    //DEBUG("  UART:%d\n",        pServiceUART.getValueAttribute().getHandle());

    vShowADC();
}

//==== Soft Updates to Characteristics for initial tests: (Most replaced by real buttons/sensors/timers )
/*void vUpdate_IOw8(void)  // Handle in device changes to w8 characteristic 
{//a Option: Allow device to modify its settings and inform host (Some devices may be able to self-modify host written settings)
    static uint8_t uw8; //Level: 0..2 
    switch(++uw8){
        case 2:         memcpy(pIOw8, "w2w2w2wc", sizeof(pIOw8)); DEBUG(" w8c"); break;
        case 1:         memcpy(pIOw8, "w1w1w1wb", sizeof(pIOw8)); DEBUG(" w8b"); break;
        default: uw8=0; memcpy(pIOw8, "w0w0w0wa", sizeof(pIOw8)); DEBUG(" w8a"); break;
    }
    //ble.updateCharacteristicValue(uint16_t handle, const uint8_t *value, uint16_t size, bool localOnly)
    ble.updateCharacteristicValue(IOw8.getValueHandle(), pIOw8, sizeof(pIOw8));
    //pServiceHRM->updateHeartRate( u8_hrm );// Update Characteristic so sent by BLE
    vShowIO();
}*/

//==== Hard Updates to Characteristics: [onButton()]   Notify: B1press or B2release
// *When direct driving hardware consider adjusting drive within the onCallback to reduce response time
//TODO: Check need of volatile for changes in interrupts affecting variables in non-interrupt code?

volatile uint8_t uB1press;  // Events since last handled in main()
volatile uint8_t uB1release;// Events since last handled in main()
volatile uint8_t uB2press;  // Events since last handled in main()
volatile uint8_t uB2release;// Events since last handled in main()

void vFillIO(void)  // Fill the structures for the Characteristics, but leave any Notify to calling function
{   //Structures are filled completely so available for BLE read at any time
    // sIOr4.s.bB1p, sIOr4.s.bB1p, sIOr4.s.uB1p, sIOr4.s.bB1r, sIOr4.s.uB2p, sIOr4.s.uB2r, sIOr4.pu   
    sIOr4.s.bB1p = !bB1in;  //Button1 Pressed (nRF51822 mkit: Pressed=Lo)
    sIOr4.s.bB2p = !bB2in;  //Button2 Pressed (nRF51822 mkit: Pressed=Lo)
    sIOr4.s.uB1p = uB1press;// Button1 Presses (Truncates to size)
    sIOr4.s.uB1r = uB1release;
    sIOr4.s.uB2p = uB2press;
    sIOr4.s.uB2r = uB2release;
       
    // sIOn4.s.bB1p, sIOn4.s.bB2p, sIOn4.s.uB1p, sIOn4.s.uB2r, sIOn4.s.iTempC100, sIOn4.pu
    sIOn4.s.bB1p = !bB1in;  //Button1 Pressed (nRF51822 mkit: Pressed=Lo)
    sIOn4.s.bB2p = !bB2in;  //Button2 Pressed (nRF51822 mkit: Pressed=Lo)
    sIOn4.s.uB1p = uB1press;//Button1 Presses (Truncates to size)
    sIOn4.s.uB2r = uB2release;

    int32_t i32_C_nRF; sd_temp_get(&i32_C_nRF); //Read the nRF Internal Temperature (Die in 0.25'C steps, offset +16'C)
    sIOn4.s.iTempC100 = (i32_C_nRF*25)-(1600);  //Save temperature in hundreths (0.01'C steps relative to 0'C)(.25'C * 25=.01'C steps)
    //float fTemperature = (float(i32_temp)/4.0) - 16.0;   // Scale&Shift (0.25'C from -16'C?)

    ble.updateCharacteristicValue(IOr4.getValueHandle(), sIOr4.pu, sizeof(sIOr4)); // Update r4 data so ready for read (Doesn't Trigger Notify)
}

//PR: Notify: Doing Notify on both Press and Release since n4 contains status of multiple inputs so need both bits to be correct. If Notify Characteristic is single button then notify could be on just press or just release.
void onB1press(void)        // B1 Press == *Notify*
{   // Update Characteristics that include this button
    uB1press++;             // Flag/Count Events (allows detect any missed processing)
    vFillIO();              // Prepare IO Characteristic data
    ble.updateCharacteristicValue(IOn4.getValueHandle(), sIOn4.pu, sizeof(sIOn4));// Triggers Notify (Reading n4 at other times may have old data)
    DEBUG("\n B1p%d", uB1press); vShowIO();
};
void onB1release(void)      // B1 Release
{   // Update Characteristics that include this button
    uB1release++;           // Flag/Count Events (allows detect any missed processing)
    vFillIO();              // Prepare IO Characteristic data
    ble.updateCharacteristicValue(IOn4.getValueHandle(), sIOn4.pu, sizeof(sIOn4));// Triggers Notify (Reading n4 at other times may have old data)
    DEBUG("\n B1r%d", uB1release); vShowIO();
};
void onB2press(void)        // B2 Press
{   // Update Characteristics that include this button
    uB2press++;             // Flag/Count Events (allows detect any missed processing)
    vFillIO();              // Prepare IO Characteristic data
    ble.updateCharacteristicValue(IOn4.getValueHandle(), sIOn4.pu, sizeof(sIOn4));// Triggers Notify (Reading n4 at other times may have old data)
    DEBUG("\n B2p%d", uB2press); vShowIO();
};
void onB2release(void)      // B2 Release
{   // Update Characteristics that include this button
    uB2release++;           // Flag/Count Events (allows detect any missed processing)
    vFillIO();              // Prepare IO Characteristic data
    ble.updateCharacteristicValue(IOn4.getValueHandle(), sIOn4.pu, sizeof(sIOn4));// Triggers Notify (Reading n4 at other times may have old data)
    DEBUG("\n B2r%d", uB2release); vShowIO();
};

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

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=", tHandle, eReason );//PR: Occurs properly when click disconnect in App nRFToolbox:HRM
    switch(eReason) {
        case Gap::REMOTE_USER_TERMINATED_CONNECTION: DEBUG("REMOTE_USER_TERMINATED_CONNECTION"); break;
        case Gap::CONN_INTERVAL_UNACCEPTABLE:        DEBUG("CONN_INTERVAL_UNACCEPTABLE"); break;
        case Gap::LOCAL_HOST_TERMINATED_CONNECTION:  DEBUG("LOCAL_HOST_TERMINATED_CONNECTION"); break;
        default:                                     DEBUG("UNKNOWN"); break;
    }    
    DEBUG("), 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

    //TODO: LED Mode to Indicate Advertising until something else changes LED Mode?
}


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);

    //x #if ENABLE_BLE_SLOW //UPDATE_PARAMS_FOR_LONGER_CONNECTION_INTERVAL //PR: Adopted optional code never tested
    //x    // Updating connection parameters can be attempted only after a connection has been
    //x    // established. Please note that the ble-Central is still the final arbiter for
    //x    // the effective parameters; the peripheral can only hope that the request is
    //x    // honored. Please also be mindful of the constraints that might be enforced by
    //x    // the BLE stack on the underlying controller.
    //x    #define MIN_CONN_INTERVAL 250  // Minimum connection interval (250 ms)
    //x    #define MAX_CONN_INTERVAL 350  // Maximum connection interval (350 ms)
    //x    #define CONN_SUP_TIMEOUT  6000 // Connection supervisory timeout (6 seconds)
    //x    #define SLAVE_LATENCY     4
    //x    Gap::ConnectionParams_t tGap_conn_params;
    //x    tGap_conn_params.minConnectionInterval        = Gap::MSEC_TO_GAP_DURATION_UNITS(MIN_CONN_INTERVAL);
    //x    tGap_conn_params.maxConnectionInterval        = Gap::MSEC_TO_GAP_DURATION_UNITS(MAX_CONN_INTERVAL);
    //x    tGap_conn_params.connectionSupervisionTimeout = Gap::MSEC_TO_GAP_DURATION_UNITS(CONN_SUP_TIMEOUT);
    //x    tGap_conn_params.slaveLatency                 = SLAVE_LATENCY;
    //x    ble.updateConnectionParams(tHandle, &tGap_conn_params);
    //x #endif // #if UPDATE_PARAMS_FOR_LONGER_CONNECTION_INTERVAL
}

#ifdef NOT_DEFINED //Not available in BLE_API r277 20150126
void Callback_BLE_onDataRead(const GattCharacteristicReadAuthCBParams *pParams) //NotAvailableOnlineYet20150114 
{
    GattAttribute::Handle_t tHandle=pParams->charHandle;
    
    //Handle Changes to Service Characteristics:
    if(!ble.getGapState().connected){ //Ensure BLE still connected
        DEBUG("\nBLEi: Callback_BLE_onDataRead() while disconnected!!");
    } else if (tHandle == IOr4.getValueHandle()) {     // IOr4 Characteristic?
        DEBUG("\nBLEi: Callback_BLE_onDataRead() while disconnected!!");
        vFillIO(); // Update the data for this Characteristic
        vShowIO();          
    //} else if (tHandle == IOw8.getValueHandle()) { // Writeonly, Continue with no changes, App can read back whatever was last written
    //} else if (tHandle == IOr4.getValueHandle()) { // Notifyonly, shouldn't occur, is automatically sent when Notify changes, anything needed should be in IOr4
    } else {
        DEBUG("\nBLEi: Callback_BLE_onDataRead(Unknown tHandle:%d)\n\n", tHandle);
    }  
}
#endif

static volatile bool bNotifySent = false; //Volatile, don't optimize, changes under interrupt control
static volatile unsigned uNotifyBLE;
void Callback_BLE_onNotifySent(unsigned uNotify)    //TODO: Consider renaming to onNotifySent(uNotified)
{   // Appears to get called when a Characteristic flagged for Notify is Updated/Sent (not for a characteristic being Read by the app).
    uNotifyBLE=uNotify;
    DEBUG(" Senti(%u)", uNotifyBLE); //TODO: PR: Why uSent always "1", expected it to match sent bytes length
    bNotifySent = true;
}

void Callback_BLE_onDataWritten(const GattCharacteristicWriteCBParams *pParams)
{   // This occurs when use nRF-MCP to save a new Characteristic Value (SingleUpASrrowIcon) such as New Heart Rate Control Point (unsent if incorrect length)
    GattAttribute::Handle_t tHandle=pParams->charHandle;
    uint16_t uLen; 

    //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", tHandle, pParams->op, pParams->offset, pParams->len, (char)(pParams->data[0]), pParams->len, pParams->data);

    /* //TODO: Add check of op type with tHandle check: switch(pParams->op){ case: GATTS_CHAR_OP_WRITE_REQ:}...  
        switch(pParams->op){
        case GATTS_CHAR_OP_INVALID:
        case GATTS_CHAR_OP_WRITE_REQ  
        case GATTS_CHAR_OP_WRITE_CMD
        case GATTS_CHAR_OP_SIGN_WRITE_CMD         //< Signed Write Command
        case GATTS_CHAR_OP_PREP_WRITE_REQ         //< Prepare Write Request
        case GATTS_CHAR_OP_EXEC_WRITE_REQ_CANCEL  //< Execute Write Request: Cancel all prepared writes
        case GATTS_CHAR_OP_EXEC_WRITE_REQ_NOW     //< Execute Write Request: Immediately execute all prepared writes
        default: Error?
    }*/

    //a These are equivalent ways of accessing characteristic handles:
    //a if (pParams->charHandle == IOw8.getValueHandle()) { DEBUG("\n\nOK tHandle %d\n\n", pParams->charHandle); }
    //a if (pParams->charHandle == ServiceIO.getCharacteristic(0)->getValueHandle()) { DEBUG("\n\nOK tHandle %d\n\n", pParams->charHandle); }
    //a if (IOw8.getValueHandle() == ServiceIO.getCharacteristic(0)->getValueHandle()) { DEBUG("\n\nOK tHandle equivalent %d\n\n", pParams->charHandle); }

    //Handle Changes to Service Characteristics:
    if(!ble.getGapState().connected){ //Ensure BLE still connected
        DEBUG("BLEi: Callback_BLE_onDataWritten() while disconnected!!");
    } else if (tHandle == IOw8.getValueHandle()) {     // This occurs from Write by App nRF-MCP since has Write Property set
        ble.readCharacteristicValue(tHandle, sIOw8.pu, &uLen); //Update Characteristic with new information (Option: Verify length or a checksum first)
        vShowIO();
        bAppControl = true; //Host has taken control of LEDs
        fL1level = (((float)sIOw8.s.L1pwm100)/100.0); if(fL1level>1.0){fL1level=1.0;}; fL1pwm=fL1level; //Set LED1 Level
        fL2level = (((float)sIOw8.s.L2pwm255)/255.0); if(fL2level>1.0){fL2level=1.0;}; fL2pwm=fL2level; //Set LED1 Level
        DEBUG("  L1:%d==%f  L2:%d==%f\n", sIOw8.s.L1pwm100, fL1level, sIOw8.s.L2pwm255, fL2level);
        //TODO: Update Outputs and settings (Includes flags for: Factory Reset, Enter Test Mode, Enter Demo Mode)
    
    //} else if (tHandle == LinkLossHandle) {        // TODO: Handle LinkLoss Changes
        //x pServiceLinkLoss->setAlertLevel( AlertLevel_t  newLevel )  
        //x Can be Triggered by MCP or BluetoothLEGatt sample changing Linkloss setting:
        //x Alert=1:  Debug=BLEi: Callback_BLE_onDataWritten(Handle:12, eOp:1, uOffset:0 uLen:1 Data0[0x01]=Data[])
        //x Alert=2:  Debug=BLEi: Callback_BLE_onDataWritten(Handle:12, eOp:1, uOffset:0 uLen:1 Data0[0x02]=Data[])
    //} else if (tHandle == IOr4.getValueHandle()) { // Readonly, shouldn't occur
    //} else if (tHandle == IOn4.getValueHandle()) { // Notifyonly, shouldn't occur
    } else {
        DEBUG("\n  Unknown onWrite(tHandle:%d)\n\n", tHandle);
        //This appears to happen at the beginning of a FOTA DFU Update, 20150126PR
    }
}

void Callback_BLE_onUpdatesEnabled(Gap::Handle_t tHandle) // Notifications
{   //Triggered when click the UpdateContinuousIcon in nRF-MCP(ThreeDownArrows)
    if        (tHandle == IOn4.getValueHandle())    { DEBUG("\nBLEi: onUpdates(Handle:%d) Enabled - IOn4 == Use Buttons to Trigger Update\n", tHandle); //This Occurs when selected by App nRF-MCP as has Notify property set
    } else if (tHandle == IOr4.getValueHandle())    { DEBUG("\nBLEi: onUpdates(Handle:%d) Enabled - IOr4\n", tHandle); // Notify not enabled on this Characteristic
    } else if (tHandle == IOw8.getValueHandle())    { DEBUG("\nBLEi: onUpdates(Handle:%d) Enabled - IOw8\n", tHandle); // Notify not enabled on this Characteristic
    } else                                          { DEBUG("\nBLEi: onUpdates(Handle:%d) Enabled - Characteristic?\n", tHandle);
    }
    //TODO: process Enable for Each Notify Characteriistic of each Service
}

void Callback_BLE_onUpdatesDisabled(Gap::Handle_t tHandle) // Notifications
{   //Triggered when click the UpdateContinuousIcon in nRF-MCP(ThreeDownArrows)
    if        (tHandle == IOn4.getValueHandle())    { DEBUG("\nBLEi: onUpdates(Handle:%d) Disabled - IOn4\n", tHandle); //This Occurs when selected by App nRF-MCP as has Notify property set
    } else if (tHandle == IOr4.getValueHandle())    { DEBUG("\nBLEi: onUpdates(Handle:%d) Disabled - IOr4\n", tHandle); // Notify not enabled on this Characteristic
    } else if (tHandle == IOw8.getValueHandle())    { DEBUG("\nBLEi: onUpdates(Handle:%d) Disabled - IOw8\n", tHandle); // Notify not enabled on this Characteristic
    } else                                          { DEBUG("\nBLEi: onUpdates(Handle:%d) Disabled - Characteristic?\n", tHandle);
    }
    //TODO: process Disable for Each Notify Characteriistic of each Service
}
void Callback_BLE_onConfirmRx(Gap::Handle_t tHandle) // Confirmations??
{
    DEBUG("\nBLEi: ConfirmationRx(Handle:%d)\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, Level:%d\n", level);
    //TODO: Handle Link Loss, maybe lower power mode and/or advertising mode
}
//==========BLE: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;
    if (++u8_hrm >= 175) {
        u8_hrm = 100;
        DEBUG(" HRM>100");
    } else {
        DEBUG(" HRM:%d", u8_hrm); 
    }  
    pServiceHRM->updateHeartRate( u8_hrm );// Update Characteristic so sent by BLE
    return(u8_hrm);
}
//==========BLE:HTM(Using nRF Internal Temperature)==========
// *If not using nRF51822 IC then change this to a simple counter like BLE:Battery section
// 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, it is likely IEEE format required like in the original project: 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, Offset: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(" HTM%03d==%2.2f", i32_temp, fTemperature);
    return(fTemperature);
}
//==========BLE: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%03d%%", u8_BattPercent);
    return(u8_BattPercent);
}

//==========Functions:Timer==========
uint32_t u32_wakeevents=0, u32_wakelast=0; // Counter&Register for tracking Wake Events (to see monitor their Frequency)
static volatile uint8_t uTicker1;//Volatile, don't optimize, changes under interrupt control
void CallbackTicker1(void)
{
    static uint32_t u32_Counter; // Counter for checking Timing
    DEBUG("\nBLEi: Ticker(%04u) WakeEvents(%d)", ++u32_Counter, u32_wakeevents);
    //if (!bAppControl) { fL1level+=0.1; if (fL1level>0.5){fL1level = 0.1;}; fL1pwm=fL1level;}//If host not controlling LED then ramp blink to show life
    { fL1level+=0.1; if (fL1level>0.5){fL1level = 0.1;}; fL1pwm=fL1level;}//Allow Ticker to regain control after a host change to LEDs
    uTicker1++; // Flag event, using counter so can detect missed events
}

//==========main==========
int main(void)
{    
    fL1level = 1.0; fL1pwm = fL1level;//Start LED1=OnMax
    fL2level = 0.2; fL2pwm = fL2level;//Start LED2=Dim

    //Restart TeraTerm just before Pressing Reset on mbed, 9600-8N1(No Flow Control)
    DEBUG("\nBLE: ___%s___\n", pcDeviceName); 

    //ARM Predefines: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0491c/BABJFEFG.html
    //  *Some are numbers not strings so printf(%s) not always appropriate
    DEBUG(" ARM Compiler Predefines:\n");
    DEBUG("   Built UTC:[ %s %s ]\n", __DATE__, __TIME__);
    DEBUG("   __arm__[%d]", __arm__);
    DEBUG("   __ARMCC_VERSION [%d]=[P:major V:minor bbbb:build]\n", __ARMCC_VERSION);
    DEBUG("   __VERSION__(gnu Compiler)[" __VERSION__ "]\n");
    //DEBUG("   __STDC_VERSION__ [%d]\n", __STDC_VERSION__);
    //DEBUG("   __BIG_ENDIAN[%d] ", __BIG_ENDIAN);
    DEBUG("   __OPTIMISE_LEVEL[%d]", __OPTIMISE_LEVEL);
    //DEBUG("   __OPTIMISE_SPACE[%d]", __OPTIMISE_SPACE);
    DEBUG("   __OPTIMISE_TIME[%d]", __OPTIMISE_TIME);
    DEBUG("\n   __MODULE__[" __MODULE__ "] __FILE__[" __FILE__ "] __BASE_FILE__[" __BASE_FILE__ "]\n");
    DEBUG("   __FUNCTION__[%s] __PRETTY_FUNCTION__[%s]\n", __FUNCTION__, __PRETTY_FUNCTION__)
  
    //vShowIO(); Show Raw initialized values before any BLE

    Ticker ticker1;                             //PR: Timer Object(Structure)
    ticker1.attach(CallbackTicker1, 5.0);       //PR: Timer Handler, Float=PeriodSeconds (Note BLE default wakes about 50Hz/20msec)

    //BLE1: Setup BLE Service (and event actions) //TODO: Check for services declared before main() - Is that OK?
    DEBUG("\nBLE: Connect App for Data: nRF-MCP, nRF-Toolbox:HRM/HTM, Android Sample BluetoothLeGatt, etc.\n");
    DEBUG(" nRF-MCP: App->mbed: LinkLoss->AlertLevel(UpArrow)\n"); 
    DEBUG(" nRF-MCP: App->mbed: BatteryService->BatteryLevel(DownArrow)\n"); 
    DEBUG(" nRF-MCP: mbed->App: BatteryService->BatteryLevel->EnableNotify(ThreeDownArrows), Also App Acks the send=DEBUG('SentI')\n"); 

    DEBUG("BLE: Setup BLE\n");
    ble.init();
    ble.onDisconnection(Callback_BLE_onDisconnect);     //PR: Host disconnects, restart advertising, clear any unnecessary functions to save power
    ble.onConnection(Callback_BLE_onConnect);           //PR: Host connects (Not required if no actions enabled, enabled now just for debug)
    ble.onDataSent(Callback_BLE_onNotifySent);          //PR: Occurs when a Notify Characteristic has been updated and sent over BLE
    ble.onDataWritten(Callback_BLE_onDataWritten);      //PR: Occurs when an update to a Characteristic has been received over BLE
    ble.onTimeout(Callback_BLE_onTimeout);              //PR: ??? Hasn't occured, TODO: Monitor and find out what causes this
    ble.onUpdatesEnabled(Callback_BLE_onUpdatesEnabled);//PR: Occurs when host enables notify on a Characteristic that has Notify property set
    ble.onUpdatesDisabled(Callback_BLE_onUpdatesDisabled);    //TODO:
    ble.onConfirmationReceived(Callback_BLE_onConfirmRx);     //TODO:
    //NotAvailableOnlineYet20150126 ble.setReadAuthorizationCallback(Callback_BLE_onDataRead);//TODO: Test new callback "setReadAuthorizationCallback()", then can read sensors live instead of polling them continuously.   
    
    //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.XXXgetHandle());
    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;

    DEBUG("BLE: Setup Custom IO Service\n");
    ble.addService(ServiceIO); //Pointer: pServiceIO
    vShowIO(); // Show Initialized values with bleIO initialized (includes tHandle)
    //TODO: Ensure outputs in correct Initial state

    //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 accumulate payload won't add that block to the advertising, it won't add a partial block, and if name last then device shows as unnamed in App.
    DEBUG("BLE: Setup Advertising - Apps like HRM/HTM/UART require matching UUID\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?
    //a ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, (uint8_t *)uuid16_list, sizeof(uuid16_list)); // Multiple UUID16 for Standard Services
    ble.accumulateAdvertisingPayload(GapAdvertisingData::INCOMPLETE_LIST_128BIT_SERVICE_IDS, (const uint8_t *)puUUID128_IOAdvertise, sizeof(puUUID128_IOAdvertise)); //Single UUID128 for primary Service
 
    //? PR: I'm not sure what these lines do, they were inherited from an example:
    //? ble.accumulateAdvertisingPayload(GapAdvertisingData::GENERIC_HEART_RATE_SENSOR);
    //? ble.accumulateAdvertisingPayload(GapAdvertisingData::GENERIC_THERMOMETER);         
    
    // 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();

    //Example Advertising (Max 31 bytes):              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:  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.

    // Setup InterruptIn for Buttons (coded for nRF51822-mkit polarities on these buttons)
    DEBUG("BLE: Enable Button Interrupts\n");
    B1int.fall(&onB1press);     // Button1 Press/fall
    B1int.rise(&onB1release);   // Button1 Release/rise
    B2int.fall(&onB2press);     // Button2 Press/fall
    B2int.rise(&onB2release);   // Button2 Release/rise

    DEBUG("BLE: Main Loop\n");
    while (true) {
        if (uTicker1 && ble.getGapState().connected) { //If Ticker1 and Connected then Update Appropriate Data
            uTicker1 = 0; // Clear flag for next Ticker1, see CallbackTicker1(), TODO: log if missed any ticks
  
            // 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();

            //a vUpdate_IOw8();
            //a vUpdate_IOr4();
            //a vUpdate_IOn4();

            DEBUG(" BLE:Wakes:%04u,Delta:%03u ", u32_wakeevents, u32_wakeevents-u32_wakelast); //For Evaluating Timing
            u32_wakelast = u32_wakeevents;           
        } else if (uTicker1) {
            uTicker1 = 0; // Clear flag for next Ticker1, see CallbackTicker1(), TODO: log if missed any ticks
            DEBUG(" BLE: Tick while unconnected ");
        } else if (bNotifySent){
            bNotifySent=false; //clear flag Notify Characteristic Transmitted
            DEBUG(" BLE: Notify(%u) ", uNotifyBLE);
        } else {
            //DEBUG("BLE: Wait for Event\n\r"); //x Debug output here causes endless wakes from sleep()
            ble.waitForEvent(); //PR: Like sleep() with ble handling. TODO: Handle Error return 
            u32_wakeevents++;   //PR: Count events for frequency monitoring (20141207PR: nRF51822 mbed HRM = 50Hz)
            //if (!bAppControl) { fL2level+=0.25; if (fL2level>0.5){fL2level = 0.0;}; fL2pwm=fL2level;} //If host not controling LED then Ramp Blink to show life
            //{ fL2level+=0.25; if (fL2level>0.5){fL2level = 0.0;}; fL2pwm=fL2level;} //Allow Ticker to regain control after a host change to LEDs

            //** Possibly PWM is causing extra wakes?

         }
    }
}
//========== end of main.cpp ==========