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!!!

Revision:
10:ee3a359f7d3f
Parent:
9:2d11beda333f
Child:
11:7d02fe5ebea5
--- a/main.cpp	Thu Dec 18 18:53:33 2014 +0000
+++ b/main.cpp	Fri Dec 19 22:37:29 2014 +0000
@@ -6,6 +6,9 @@
 //    - 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
+//    - 
 //    - miscellaneous adopted from more samples...
 // Reference:
 //    - http://developer.mbed.org/teams/Bluetooth-Low-Energy/code/BLE_API/
@@ -16,7 +19,7 @@
 //      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.
+//    - 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.
@@ -64,7 +67,7 @@
 //==========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()
+    //Using default baud rate to avoid issues with DEBUG in a constructor being at default baud rate before main()
     Serial  debug_serial(USBTX, USBRX); //PR: DebugSerialOverMbedUSB
     #define DEBUG(...) { debug_serial.printf(__VA_ARGS__); }
 #else
@@ -79,98 +82,29 @@
 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)
+float       f_led1level = 0.1;          //Initial Brightness (Typically 0.0~0.5)
+float       f_led2level = 0.1;          //Initial Brightness (Typically 0.0~0.5)
 
 //==========BLE==========
-const static char     pcDeviceName[]    = "bleCustomIO"; //PR: Why can App nRF-MCP modify this even though flagged as Const, maybe only temporary mod till App restarts?
+const static char     pcDeviceName[]    = "bleIO"; //PR: Why can App nRF-MCP modify this even though flagged as Const, maybe only temporary mod till App restarts?
 BLEDevice   ble;
-//Services&Characteristics
-    //GattCharacteristic* characteristic = new GattCharacteristic(characteristicUuid, value, size, size, properties);  characteristics.push_back(characteristic);//(from: Puck.h)
-    //characteristic->getHandle()
-    //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) 
-
-    HealthThermometerService    *pServiceHTM; 
-    BatteryService              *pServiceBattery;
+//Pointers to services for accesses outside main()
+    HealthThermometerService    *pServiceHTM;       //tempChar.getValueAttribute().getHandle() 
+    BatteryService              *pServiceBattery;   //battLevel.getValueAttribute().getHandle()
     HeartRateService            *pServiceHRM;
     DeviceInformationService    *pServiceDeviceInfo;
     LinkLossService             *pServiceLinkLoss;
-    //UARTService               *pServiceUART;
-        //pServiceUART->getTXCharacteristicHandle();
-        //pServiceUART->getRXCharacteristicHandle();
-        //ble.updateCharacteristicValue(pServiceUART->getRXCharacteristicHandle(), pData, uLen); // Tx to App
-
-//x /* From: https://developer.mbed.org/users/todotani/code/BLE_Health_Thermometer2-010/wiki/BLE_Health_Thermometer2-010
-//x ble.updateCharacteristicValue(tempChar.getValueAttribute().getHandle(), thermTempPayload, sizeof(thermTempPayload));
-//x ble.updateCharacteristicValue(battLevel.getValueAttribute().getHandle(), (uint8_t *)&batt, sizeof(batt));*/
-
-//x /* From: nRF51822_SimpleControls
-//x GattCharacteristic  txCharacteristic (uart_tx_uuid, txPayload, 1, TXRX_BUF_LEN, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE_WITHOUT_RESPONSE);                                     
-//x GattCharacteristic  rxCharacteristic (uart_rx_uuid, rxPayload, 1, TXRX_BUF_LEN, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY);
-//x GattCharacteristic *uartChars[] = {&txCharacteristic, &rxCharacteristic};
-//x GattService         uartService(uart_base_uuid, uartChars, sizeof(uartChars) / sizeof(GattCharacteristic *));*/
+    //x DFUService                *pServiceDFU;
+    //x UARTService               *pServiceUART;      //FromApp: pServiceUART->getTXCharacteristicHandle(); //ToApp: ble.updateCharacteristicValue(pServiceUART->getRXCharacteristicHandle(), pData, uLen);
+    //x bleIOService              *pServiceIO;
 
 //==========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
-
-    //TODO:
-    //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
-    //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. 
@@ -181,126 +115,141 @@
         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) 
- */
+        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
 
-//========== 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";
+//a //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 //
+    //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
+};*/
 
-//==========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.
-}
-
-//==========Custom: a service for IO (GPIO,PWM,etc.)==========
-// TODO: Change from dummy UUID16 to proper UUID128
-//UUID128 List(Only a single UUID128 can fit in advertising)  
+//========== 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]
-    //                                        {0x1*,0xc5,0xd5,0xa5,0x02,0x00,0x84,0xb0,0xe4,0x11,0xd1,0x86,0xc0,0x81,0x32,0x4d};// Proper Flipped order
-    uint8_t puUUID128_CustomServiceIO[16]   = {0x10,0xc5,0xd5,0xa5,0x02,0x00,0x84,0xb0,0xe4,0x11,0xd1,0x86,0xc0,0x81,0x32,0x4d};// Service
-    uint8_t puUUID128_CustomW8[16]          = {0x11,0xc5,0xd5,0xa5,0x02,0x00,0x84,0xb0,0xe4,0x11,0xd1,0x86,0xc0,0x81,0x32,0x4d};// Value:Write[8byte]  to mbed from phone
-    uint8_t puUUID128_CustomR4[16]          = {0x12,0xc5,0xd5,0xa5,0x02,0x00,0x84,0xb0,0xe4,0x11,0xd1,0x86,0xc0,0x81,0x32,0x4d};// Value:Read[4byte]   from mbed to phone
-    uint8_t puUUID128_CustomN4[16]          = {0x13,0xc5,0xd5,0xa5,0x02,0x00,0x84,0xb0,0xe4,0x11,0xd1,0x86,0xc0,0x81,0x32,0x4d};// Value:Notify[4byte] from mbed to phone 
+    // 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 (=FlippedOrder)
+    // 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
+
 
-    //uint8_t puUUID128_CustomX_Config32[16] = {0x1X,0xc5,0xd5,0xa5,0x02,0x00,0x84,0xb0,0xe4,0x11,0xd1,0x86,0xc0,0x81,0x32,0x4d};// Proper Flipped order
-    //uint8_t puUUID128_CustomX_OutDig1[16]  = {0x1X,0xc5,0xd5,0xa5,0x02,0x00,0x84,0xb0,0xe4,0x11,0xd1,0x86,0xc0,0x81,0x32,0x4d};// Proper Flipped order
-    //uint8_t puUUID128_CustomX_OutPWM100[16]= {0x1X,0xc5,0xd5,0xa5,0x02,0x00,0x84,0xb0,0xe4,0x11,0xd1,0x86,0xc0,0x81,0x32,0x4d};// Proper Flipped order
-    //uint8_t puUUID128_CustomX_OutPWM256[16]= {0x1X,0xc5,0xd5,0xa5,0x02,0x00,0x84,0xb0,0xe4,0x11,0xd1,0x86,0xc0,0x81,0x32,0x4d};// Proper Flipped order
-    //uint8_t puUUID128_CustomX_InDig1[16]   = {0x1X,0xc5,0xd5,0xa5,0x02,0x00,0x84,0xb0,0xe4,0x11,0xd1,0x86,0xc0,0x81,0x32,0x4d};// Proper Flipped order
-    //uint8_t puUUID128_CustomX_InADC12[16]  = {0x1X,0xc5,0xd5,0xa5,0x02,0x00,0x84,0xb0,0xe4,0x11,0xd1,0x86,0xc0,0x81,0x32,0x4d};// Proper Flipped order
+/* 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");
 
-// Characteristic Value Storage:
-    uint8_t pCustomW8[8] = {0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17};   //Value Storage for Write Characteristic (Doesn't have to be byte array[])
-    uint8_t pCustomR4[4] = {0x20,0x21,0x22,0x23};                       //Value Storage for Read Characteristic  (Doesn't have to be byte array[])
-    uint8_t pCustomN4[4] = {0x30,0x31,0x32,0x33};                       //Value Storage for Notify Characteristic(Doesn't have to be byte array[])
+        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):
+    uint8_t pIOw8[8] = {0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17};   //Value Storage for Characteristic Write8 
+    uint8_t pIOr4[4] = {0x20,0x21,0x22,0x23};                       //Value Storage for Characteristic Read4 
+    uint8_t pIOn4[4] = {0x30,0x31,0x32,0x33};                       //Value Storage for Characteristic Notify4 
 
     //x GattCharacteristic::GattAttribute *descriptors[]; //How Set Characteristic Descriptors? Maybe in the App is easier?
 
-    //GattCharacteristic      xx(*UUID, *Payload=*Value, LenInit, LenMax, Properties, *Descriptors, NumDescriptors)
-    GattCharacteristic  CustomW8(puUUID128_CustomW8, pCustomW8, sizeof(pCustomW8), sizeof(pCustomW8), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE_WITHOUT_RESPONSE);
-    GattCharacteristic  CustomR4(puUUID128_CustomR4, pCustomR4, sizeof(pCustomR4), sizeof(pCustomR4), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ);
-    GattCharacteristic  CustomN4(puUUID128_CustomN4, pCustomN4, sizeof(pCustomN4), sizeof(pCustomN4), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ);
-    GattCharacteristic *pCustomCharacteristics[] = {&CustomW8, &CustomR4, &CustomN4};//Pointers to Characteristics
+    //IO Characteristics: //GattCharacteristics xx(*UUID, *Payload=*Value, LenInit, LenMax, Properties)
+    //GattCharacteristic  IOw8(puUUID128_IOw8, pIOw8, sizeof(pIOw8), sizeof(pIOw8), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE_WITHOUT_RESPONSE);
+    GattCharacteristic  IOw8(puUUID128_IOw8, pIOw8, sizeof(pIOw8), sizeof(pIOw8), 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, pIOr4, sizeof(pIOr4), sizeof(pIOr4), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ); // Host can manually request
+    //GattCharacteristic  IOn4(puUUID128_IOn4, pIOn4, sizeof(pIOn4), sizeof(pIOn4), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY);
+    GattCharacteristic  IOn4(puUUID128_IOn4, pIOn4, sizeof(pIOn4), sizeof(pIOn4), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY); // Host is notified of changes
+    GattCharacteristic *pCustomCharacteristics[] = {&IOw8, &IOr4, &IOn4};//Table of Pointers to Characteristics
+
+    //IO Service: //GattService::GattService(*UUID, *GattCharacteristics, (numCharacteristics) ) :
+    GattService ServiceIO(puUUID128_IOService, pCustomCharacteristics, (sizeof(pCustomCharacteristics)/sizeof(pCustomCharacteristics[0])));
+    GattService *pServiceIO= &ServiceIO;  // Pointer to Service
 
-    //GattService:
-    //GattService::GattService(*UUID, *GattCharacteristics, numCharacteristics) :
-    GattService ServiceCustomIO(puUUID128_CustomServiceIO, pCustomCharacteristics, (sizeof(pCustomCharacteristics)/sizeof(pCustomCharacteristics[0])));
-    GattService *pServiceCustomIO= &ServiceCustomIO;  // Pointer to Service
-
-void update_CustomW8(void)
+//a Option: Allow device to modify its settings and inform host (Typically the device wouldn't change a host only setting)
+/*void vUpdate_IOw8(void)  // Handle in device changes to w8 characteristic 
 {
-    static uint8_t u8_w8; //Level: 0..2 
-    switch(u8_w8++){
-        case 0:     memcpy(pCustomW8, "w0w0w0wa", sizeof(pCustomW8)); DEBUG(" UpdateW8a"); break;
-        case 1:     memcpy(pCustomW8, "w1w1w1wb", sizeof(pCustomW8)); DEBUG(" UpdateW8b"); break;
-        default:    memcpy(pCustomW8, "w2w2w2wc", sizeof(pCustomW8)); DEBUG(" UpdateW8c"); u8_w8=0; break;
+    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
+}*/
+
+void vUpdate_IOr4(void) // Handle in device changes to r4 characteristic
+{
+    static uint8_t ur4; //Level: 0..2 
+    switch(++ur4){
+        case 2:         memcpy(pIOr4, "r2r2", sizeof(pIOr4)); DEBUG(" r4c"); break;
+        case 1:         memcpy(pIOr4, "r1r1", sizeof(pIOr4)); DEBUG(" r4b"); break;
+        default: ur4=0; memcpy(pIOr4, "r0r0", sizeof(pIOr4)); DEBUG(" r4a"); break;
+    }
+    ble.updateCharacteristicValue(IOr4.getValueHandle(), pIOr4, sizeof(pIOr4));
 }
 
-void update_CustomR4(void)
+void vUpdate_IOn4(void) // Handle in device changes to n4 characteristic
 {
-    static uint8_t u8_r4; //Level: 0..2 
-    switch(u8_r4++){
-        case 0:     memcpy(pCustomR4, "r0r0", sizeof(pCustomR4)); DEBUG(" UpdateR4a"); break;
-        case 1:     memcpy(pCustomR4, "r1r1", sizeof(pCustomR4)); DEBUG(" UpdateR4b"); break;
-        default:    memcpy(pCustomR4, "r2r2", sizeof(pCustomR4)); DEBUG(" UpdateR4c"); u8_r4=0; break;
+    static uint8_t un4; //Level: 0..2 
+    switch(++un4){
+        case 2:         memcpy(pIOn4, "n2n2", sizeof(pIOr4)); DEBUG(" n4c"); break;
+        case 1:         memcpy(pIOn4, "n1n1", sizeof(pIOr4)); DEBUG(" n4b"); break;
+        default: un4=0; memcpy(pIOn4, "n0n0", sizeof(pIOr4)); DEBUG(" n4a"); break;
     }
-}
-
-void update_CustomN4(void)
-{
-    static uint8_t u8_n4; //Level: 0..2 
-    switch(u8_n4++){
-        case 0:     memcpy(pCustomN4, "n0n0", sizeof(pCustomR4)); DEBUG(" UpdateN4a"); break;
-        case 1:     memcpy(pCustomN4, "n1n1", sizeof(pCustomR4)); DEBUG(" UpdateN4b"); break;
-        default:    memcpy(pCustomN4, "n2n2", sizeof(pCustomR4)); DEBUG(" UpdateN4c"); u8_n4=0; break;
-    }
+    ble.updateCharacteristicValue(IOn4.getValueHandle(), pIOn4, sizeof(pIOn4));
 }
 
 //==========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
+{   //PR: Haven't seen this, even when phone moved out of range and events occur like OnDisconnect(Reason0x08) or LinkLoss
+    DEBUG("\nBLEi: Callback_BLE_onTimeout()\n" ); 
   
     //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
+{   //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,
@@ -311,12 +260,11 @@
     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
+    #if UPDATE_PARAMS_FOR_LONGER_CONNECTION_INTERVAL //PR: Adopted optional code never tested
         /* 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
@@ -333,17 +281,17 @@
         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 */
+    #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
+    DEBUG("BLEi: SentI(%u)", uSentBLE); //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.
+    //FYI: App nRF-MCP doesn't cause onDataSent(), while App nRF-ToolBox:HRM does cause onDataSent()
+    //TBD: 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)
@@ -353,104 +301,89 @@
     //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:}...  
+    /* //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?
+    }*/
 
-    // These are equivalent ways of accessing characteristic handles:
-    // if (pParams->charHandle == CustomW8.getValueHandle()) { DEBUG("\n\nOK tHandle %d\n\n", pParams->charHandle); }
-    // if (pParams->charHandle == ServiceCustomIO.getCharacteristic(0)->getValueHandle()) { DEBUG("\n\nOK tHandle %d\n\n", pParams->charHandle); }
-    // if (CustomW8.getValueHandle() == ServiceCustomIO.getCharacteristic(0)->getValueHandle()) { DEBUG("\n\nOK tHandle equivalent %d\n\n", pParams->charHandle); }
+    //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:
-    uint16_t uLen; 
-    //x if (tHandle == pServiceLinkLoss->getCharacteristic(0)->getValueHandle()) {
-    //x    LinkLossService::AlertLevel_t tLLnewAlertLevel;
-    //x    ble.readCharacteristicValue(tHandle, tLLnewAlertLevel, &uLen);
-    //x    DEBUG("  LinkLoss[%d]:AlertLevel[%d]\n", uLen, tLLnewAlertLevel); //TODO: Confirm that actually writing to the LinkLoss service is properly handled in the service.
-    //x    //TODO: Update Outputs
-    //X } else 
-    if (tHandle == CustomW8.getValueHandle()) {
-        ble.readCharacteristicValue(tHandle, pCustomW8, &uLen);
-        //x memcpy( pCustomW8b, pCustomW8, sizeof(pCustomW8b));  //TODO: Working copy??
-        DEBUG("  CustomW8[%d]:%02X %02X %02X %02X %02X %02X %02X %02X\n", uLen, pCustomW8[0], pCustomW8[1], pCustomW8[2], pCustomW8[3],pCustomW8[4], pCustomW8[5], pCustomW8[6], pCustomW8[7]);
-        //TODO: Update Outputs
-    } else if (tHandle == CustomR4.getValueHandle()) {
-        ble.readCharacteristicValue(tHandle, pCustomR4, &uLen);
-        DEBUG("  CustomR4[%d]:%02X %02X %02X %02X\n", uLen, pCustomR4[0], pCustomR4[1], pCustomR4[2], pCustomR4[3]);
-        //TODO: Update Outputs
-    } else if (tHandle == CustomN4.getValueHandle()) {
-        ble.readCharacteristicValue(tHandle, pCustomN4, &uLen);
-        DEBUG("  CustomN4[%d]:%02X %02X %02X %02X\n", uLen, pCustomN4[0], pCustomN4[1], pCustomN4[2], pCustomN4[3]);
-        //TODO: Update Outputs
+    if(!ble.getGapState().connected){ //Ensure BLE still connected
+        DEBUG("BLEi: Callback_BLE_onDataWritten() while disconnected!!");
     } else {
-        DEBUG("\n  Unknown Onwrite(tHandle:%d)\n\n", tHandle);
+        uint16_t uLen; 
+        if (tHandle == IOw8.getValueHandle()) {     // This occurs from Write by App nRF-MCP since has Write Property set
+            ble.readCharacteristicValue(tHandle, pIOw8, &uLen);
+            DEBUG("  IOw8[%d]:%02X %02X %02X %02X %02X %02X %02X %02X\n", uLen, pIOw8[0], pIOw8[1], pIOw8[2], pIOw8[3],pIOw8[4], pIOw8[5], pIOw8[6], pIOw8[7]);
+            //TODO: Update Outputs
+        //} else if (tHandle == IOr4.getValueHandle()) {
+        //    ble.readCharacteristicValue(tHandle, pIOr4, &uLen);
+        //    DEBUG("  IOr4[%d]:%02X %02X %02X %02X\n", uLen, pIOr4[0], pIOr4[1], pIOr4[2], pIOr4[3]);
+        //    //TODO: Update Outputs
+        //} else if (tHandle == IOn4.getValueHandle()) {
+        //    ble.readCharacteristicValue(tHandle, pIOn4, &uLen);
+        //    DEBUG("  IOn4[%d]:%02X %02X %02X %02X\n", uLen, pIOn4[0], pIOn4[1], pIOn4[2], pIOn4[3]);
+        //    //TODO: Update Outputs
+        } else {
+            DEBUG("\n  Unknown onWrite(tHandle:%d)\n\n", tHandle);
+        }
     }
 
-    //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[]
+    //LinkLoss:
+    //x pServiceLinkLoss->setAlertLevel( AlertLevel_t  newLevel )  
+    //x Triggered by 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[])
 
-/* 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
-    }
-*/
-
-    //pServiceLinkLoss->setAlertLevel( AlertLevel_t  newLevel )  
-
+    //Characteristic Status:
+    DEBUG("  IOw8: Handle:%d Len:%d Initial:[%02X %02X %02X %02X %02X %02X %02X %02X]\n", IOw8.getValueHandle(), sizeof(pIOw8), pIOw8[0], pIOw8[1], pIOw8[2], pIOw8[3],pIOw8[4], pIOw8[5], pIOw8[6], pIOw8[7]);
+    DEBUG("  IOr4: Handle:%d Len:%d Initial:[%02X %02X %02X %02X]\n", IOr4.getValueHandle(), sizeof(pIOr4), pIOr4[0], pIOr4[1], pIOr4[2], pIOr4[3]);
+    DEBUG("  IOn4: Handle:%d Len:%d Initial:[%02X %02X %02X %02X]\n", IOn4.getValueHandle(), sizeof(pIOn4), pIOn4[0], pIOn4[1], pIOn4[2], pIOn4[3]);
 }
 
 void Callback_BLE_onUpdatesEnabled(Gap::Handle_t tHandle)
-{   //Tiggered when click the UpdateContinuousIcon in nRF-MCP(ThreeDownArrows)
-    DEBUG("\nBLEi: Callback_BLE_onUpdates(Handle:%d)\r\n", tHandle);
-    if        (tHandle == CustomW8.getValueHandle())    { DEBUG("  onUpdates Enabled - CustomW8\n");
-    } else if (tHandle == CustomR4.getValueHandle())    { DEBUG("  onUpdates Enabled - CustomR4\n");
-    } else if (tHandle == CustomN4.getValueHandle())    { DEBUG("  onUpdates Enabled - CustomN4\n");
-    } else                                              { DEBUG("  onUpdates Enabled - Characteristic Handle[%d]\n", tHandle);
+{   //Triggered when click the UpdateContinuousIcon in nRF-MCP(ThreeDownArrows)
+    if        (tHandle == IOn4.getValueHandle())    { DEBUG("  onUpdates(Handle:%d) Enabled - IOn4\n", tHandle); //This Occurs when selected by App nRF-MCP as has Notify property set
+  //} else if (tHandle == IOr4.getValueHandle())    { DEBUG("  onUpdates(Handle:%d) Enabled - IOr4\n", tHandle);
+  //} else if (tHandle == IOw8.getValueHandle())    { DEBUG("  onUpdates(Handle:%d) Enabled - IOw8\n", tHandle);
+    } else                                          { DEBUG("  onUpdates(Handle:%d) Enabled - Characteristic?\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);
-    
-    //pServiceLinkLoss->setAlertLevel( AlertLevel_t  newLevel )  
+    printf("\nBLEi: Link Loss Alert, Level:%d\n", level);
+    //TODO: Handle Link Loss, maybe lower power mode and/or advertising mode
 }
-//==========HRM==========
+//==========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;
-    u8_hrm++;
-    if (u8_hrm >= 175) {
+    if (++u8_hrm >= 175) {
         u8_hrm = 100;
-        DEBUG("BLE: HRM Rollover175->100 ");
-    }
-    DEBUG("[HRM:%d]", u8_hrm);   
+        DEBUG(" HRM>100");
+    } else {
+        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/
+//==========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 
@@ -470,19 +403,28 @@
     //}
 
     pServiceHTM->updateTemperature( fTemperature );// Update Characteristic so sent by BLE
-    DEBUG("[HTMi:%d HTMf:%f]", i32_temp, fTemperature);
+    DEBUG(" HTM%03d==%2.2f", i32_temp, fTemperature);
     return(fTemperature);
 }
-//==========Battery==========
+//==========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:%d%%]", u8_BattPercent);
+    DEBUG(" Batt%03d%%", u8_BattPercent);
     return(u8_BattPercent);
 }
 
+//==========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
+    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.
+}
 //==========main==========
 int main(void)
 {
@@ -516,26 +458,8 @@
         pServiceDeviceInfo      = &ServiceDeviceInfo; //DEBUG("   Handle Service DeviceInfo:%d\n", ServiceDeviceInfo.XXXgetHandle());
     LinkLossService             ServiceLinkLoss(ble, Callback_BLE_onLinkLoss, LinkLossService::HIGH_ALERT); //New20141213, TBD
         pServiceLinkLoss        = &ServiceLinkLoss;
-        //x //How to get characteristics of service, so gan get characteristic's handles used in callbacks?
-        //x //        DEBUG(" DeviceInfo CharacteristicHandle: LinkLoss: AlertLevel:%d\n", ServiceLinkLoss.alertLevelChar.getValueAttribute().getHandle());
-        //x //ble.updateCharacteristicValue(tempChar.getValueAttribute().getHandle(), thermTempPayload, sizeof(thermTempPayload));
-        //x //ble.updateCharacteristicValue(battLevel.getValueAttribute().getHandle(), (uint8_t *)&batt, sizeof(batt));
-
     BatteryService              ServiceBattery(ble, 10);
         pServiceBattery         = &ServiceBattery;
-        //DEBUG(" Service:Batt Characteristic:BatteryLevel: %d\n", ServiceBattery->charTable[0].getValueAttribute().getHandle()); //.batteryLevelCharacteristic
-        //x uint32_t *h = ServiceBattery.charTable[0].getValueAttribute().getHandle(); //.batteryLevelCharacteristic
-        //x uint32_t *h = ServiceBattery.batteryLevelCharacteristic.getValueAttribute().getHandle(); //.batteryLevelCharacteristic
-        //x DEBUG(" Service:Batt Characteristic:BatteryLevel: %d\n", ServiceBattery->batteryLevelCharacteristic.getValueAttribute().getHandle()); //.batteryLevelCharacteristic
-        //x DEBUG(" Service:Batt Characteristic:BatteryLevel: %d\n", ServiceBattery->batteryLevelCharacteristic.getValueAttribute().getHandle()); //.batteryLevelCharacteristic
-        //x //GattCharacteristic *charTable[] = {&batteryLevelCharacteristic};
-        
-        //x //uartServicePtr->txCharacteristic.getValueAttribute().getHandle()  // From: GattCharacteristic  txCharacteristic;
-        //x //        DEBUG(" DeviceInfo CharacteristicHandle: BatteryLevel:%d\n", ServiceBattery.batteryLevelCharacteristic.getValueAttribute().getHandle());
-        //x //        batteryLevelCharacteristic(GattCharacteristic::UUID_BATTERY_LEVEL_CHAR, &batteryLevel, sizeof(batteryLevel), sizeof(batteryLevel), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY) {
-        //x //ble.updateCharacteristicValue(tempChar.getValueAttribute().getHandle(), thermTempPayload, sizeof(thermTempPayload));
-        //x //ble.updateCharacteristicValue(battLevel.getValueAttribute().getHandle(), (uint8_t *)&batt, sizeof(batt));
-
     HeartRateService            ServiceHRM(ble, (uint8_t)111, HeartRateService::LOCATION_FINGER);
         pServiceHRM             = &ServiceHRM;
     HealthThermometerService    ServiceHTM(ble, 33.3, HealthThermometerService::LOCATION_EAR); 
@@ -544,48 +468,37 @@
     //  pServiceUART            = &ServiceUART;
 
     //Start CustomIO Service:
-    ble.addService(ServiceCustomIO); //Pointer: pServiceCustomIO
-        DEBUG(" CustomIO: Field Lengths:  [%d] [%d] [%d]\n", sizeof(pCustomW8), sizeof(pCustomR4), sizeof(pCustomN4));
-        DEBUG(" CustomIO: Initial Values: [%02x] [%02x] [%02x]\n", pCustomW8[0], pCustomR4[0], pCustomN4[0]);
+    ble.addService(ServiceIO); //Pointer: pServiceIO
+        DEBUG("  IOw8: Handle:%d Len:%d Initial:[%02X %02X %02X %02X %02X %02X %02X %02X]\n", IOw8.getValueHandle(), sizeof(pIOw8), pIOw8[0], pIOw8[1], pIOw8[2], pIOw8[3],pIOw8[4], pIOw8[5], pIOw8[6], pIOw8[7]);
+        DEBUG("  IOr4: Handle:%d Len:%d Initial:[%02X %02X %02X %02X]\n", IOr4.getValueHandle(), sizeof(pIOr4), pIOr4[0], pIOr4[1], pIOr4[2], pIOr4[3]);
+        DEBUG("  IOn4: Handle:%d Len:%d Initial:[%02X %02X %02X %02X]\n", IOn4.getValueHandle(), sizeof(pIOn4), pIOn4[0], pIOn4[1], pIOn4[2], pIOn4[3]);
+        //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 accumulatepayload won't add that block to the advertising, it won't add a partial block.
+    // 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?
-    //x ble.accumulateAdvertisingPayload(GapAdvertisingData::INCOMPLETE_LIST_128BIT_SERVICE_IDS, (const uint8_t *)UUID128_UART_Rev, sizeof(UUID128_UART_Rev)); //Single UUID128
-    //a 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, (const uint8_t *)puUUID128_CustomServiceIO, sizeof(puUUID128_CustomServiceIO)); //Single UUID128
-    //a ble.accumulateAdvertisingPayload(GapAdvertisingData::INCOMPLETE_LIST_128BIT_SERVICE_IDS, puStrToUUID128( pc_com_none_servA), LENGTH_OF_LONG_UUID); //Single UUID128
-    //a 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
-    //a ble.accumulateAdvertisingPayload(GapAdvertisingData::GENERIC_HEART_RATE_SENSOR);//PR: Add Appearance::HeartRate (HRM Connects without this)
-    //a ble.accumulateAdvertisingPayload(GapAdvertisingData::GENERIC_THERMOMETER);      //PR: Add Appearance::Thermometer (HTM Connects without this, though float format still needs change)
+    //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_IOService, sizeof(puUUID128_IOService)); //Single UUID128 for primary Service
     
-    
-// main() init:
-//    ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_128BIT_SERVICE_IDS, (const uint8_t *)puUUID128_CustomService1, sizeof(puUUID128_CustomService1));
+    //? 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();
 
-    // Raw Advertizing max length 31bytes:             0x01020304050607080910111213141516171819202122232425262728293031
+    //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:  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)
@@ -606,11 +519,11 @@
             update_hrm();
             update_batt();
 
-            update_CustomW8();
-            update_CustomR4();
-            update_CustomN4();
+            //a vUpdate_IOw8();
+            vUpdate_IOr4();
+            vUpdate_IOn4();
 
-            DEBUG("BLE: Wakes:%u Delta:%d ", u32_wakeevents, u32_wakeevents-u32_wakelast); //For Evaluating Timing
+            DEBUG("  BLE:Wakes:%u,Delta:%u ", 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()
@@ -626,74 +539,4 @@
         }
     }
 }
-//========== end ==========
-#ifdef NOT_DEFINED //From puck.h - Adding Characteristic
-void Puck::addCharacteristic(const UUID serviceUuid, const UUID characteristicUuid, int bytes, int properties) {
-    MBED_ASSERT(bytes <= 20);
-    uint16_t size = sizeof(uint8_t) * bytes;
-    uint8_t* value = (uint8_t*) malloc(size);
-    if(value == NULL) {
-        LOG_ERROR("Unable to malloc value for characteristic. Possibly out of memory!\n");    
-    }
-    
-    GattCharacteristic* characteristic = new GattCharacteristic(characteristicUuid, value, size, size, properties);
-    characteristics.push_back(characteristic);
-    
-    
-    GattService* service = NULL;
-    
-    int removeIndex = -1;
-    for(int i = 0; i < services.size(); i++) {
-        if(isEqualUUID(&services[i]->getUUID(), serviceUuid)) {
-            service = services[i];
-            removeIndex = i;
-            break;
-        }
-    }
-    GattCharacteristic** characteristics = NULL;
-    int characteristicsLength = 0;
-    if(service != NULL) {
-        characteristicsLength = service->getCharacteristicCount() + 1;
-        characteristics = (GattCharacteristic**) malloc(sizeof(GattCharacteristic*) * characteristicsLength);
-        if(characteristics == NULL) {
-            LOG_ERROR("Unable to malloc array of characteristics for service creation. Possibly out of memory!\n");    
-        }
-        for(int i = 0; i < characteristicsLength; i++) {
-            characteristics[i] = service->getCharacteristic(i);    
-        }
-        services.erase(services.begin() + removeIndex);
-        delete service;
-        free(previousCharacteristics);
-    } else {
-        characteristicsLength = 1;
-        characteristics = (GattCharacteristic**) malloc(sizeof(GattCharacteristic*) * characteristicsLength);
-        if(characteristics == NULL) {
-            LOG_ERROR("Unable to malloc array of characteristics for service creation. Possibly out of memory!\n");    
-        }
-    }
-    
-    characteristics[characteristicsLength - 1] = characteristic;
-    previousCharacteristics = characteristics;
-    service = new GattService(serviceUuid, characteristics, characteristicsLength);
-    services.push_back(service);
-    LOG_DEBUG("Added characteristic.\n");
-}
-
-
-void Puck::updateCharacteristicValue(const UUID uuid, uint8_t* value, int length) {
-    GattCharacteristic* characteristic = NULL;
-    for( int i = 0; i < characteristics.size(); i++) {
-        if(isEqualUUID(&characteristics[i]->getUUID(), uuid)) {
-            characteristic = characteristics[i];
-            break;    
-        }    
-    }
-    if(characteristic != NULL) {
-        ble.updateCharacteristicValue(characteristic->getHandle(), value, length);
-        LOG_VERBOSE("Updated characteristic value.\n");
-    } else {
-        LOG_WARN("Tried to update an unkown characteristic!\n");    
-    }
-}
-#endif
-//=================================================
\ No newline at end of file
+//========== end of main.cpp ==========