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:
9:2d11beda333f
Parent:
8:f187ba55aed2
Child:
10:ee3a359f7d3f
--- a/main.cpp	Tue Dec 16 16:44:56 2014 +0000
+++ b/main.cpp	Thu Dec 18 18:53:33 2014 +0000
@@ -1,6 +1,7 @@
 //=========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/
@@ -21,7 +22,8 @@
 //    - Handle All return codes for all functions not void, including BLE not BLE_ERROR_NONE, as a minimum output some debug and log event in non-volatile memory for diagnostics.
 //    - Re-check where voltatile needed
 //    - Evaluate setting: IS_SRVC_CHANGED_CHARACT_PRESENT, see: https://devzone.nordicsemi.com/question/22751/nrftoobox-on-android-not-recognizing-changes-in-application-type-running-on-nordic-pcb/?answer=23097#post-id-23097
-//
+//    - delete all code with "//x " prefix as that was just for notes and doesn't actually work
+//    - change all valid code temporarily commended to "//a " prefix to indicate alternnative is valid code
 //==========End of PR's Header
 
 //==========Historic Licencing from original imported sample from mbed website [BLE_HeartRate] ==========
@@ -46,7 +48,7 @@
 #define UPDATE_PARAMS_FOR_LONGER_CONNECTION_INTERVAL    0  //PR: Option to slow the connection intervsal possibly saving power (After Connected)
 
 //==========Includes==========
-#include "mbed.h"
+#include "mbed.h"                       // mbed
 #include "BLEDevice.h"                  // BLE
 #include "nrf_soc.h"                    // nRF Internal Temperature Sensor
 
@@ -86,8 +88,14 @@
 float       f_led2level = 0.0;          //Initial Brightness (Typically 0.0~0.5)
 
 //==========BLE==========
-const static char     pcDeviceName[]    = "blePRv04"; //PR: Why can App nRF-MCP modify this even though flagged as Const, maybe only temporary mod till App restarts?
+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?
 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;
     HeartRateService            *pServiceHRM;
@@ -98,6 +106,15 @@
         //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 *));*/
 
 //==========UUID==========
 //UUID16 List - Advertising these required by App nRF-Toolbox:HRM&HTM 
@@ -112,30 +129,31 @@
     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 //
-    //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,
+
+    //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
-*/
+//    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};
@@ -198,6 +216,77 @@
     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)  
+
+// 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 
+
+    //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
+
+// 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[])
+
+    //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
+
+    //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)
+{
+    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;
+    }
+    //pServiceHRM->updateHeartRate( u8_hrm );// Update Characteristic so sent by BLE
+}
+
+void update_CustomR4(void)
+{
+    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;
+    }
+}
+
+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;
+    }
+}
+
 //==========Functions:BLE==========
 void Callback_BLE_onTimeout(void)
 {
@@ -258,11 +347,43 @@
 }
 
 void Callback_BLE_onDataWritten(const GattCharacteristicWriteCBParams *pParams)
-{
-    // Callback_BLE_onDataWritten == This does occur when use nRF-MCP to save New Heart Rate Control Point (Ignored if incorrect length)
+{   // 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;
 
     //Warning: *data may not be NULL terminated
-    DEBUG("\nBLEi: Callback_BLE_onDataWritten(Handle:%d, eOp:%d, uOffset:%u uLen:%u Data0[0x%02x]=Data[%*s]\n", pParams->charHandle, pParams->op, pParams->offset, pParams->len, (char)(pParams->data[0]), pParams->len, pParams->data);
+    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:}...  
+
+    // 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); }
+
+    //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
+    } 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[]
@@ -293,19 +414,26 @@
     }
 */
 
-
+    //pServiceLinkLoss->setAlertLevel( AlertLevel_t  newLevel )  
 
 }
 
 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);
+    }
 }
 
 // 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 )  
 }
 //==========HRM==========
 //Adopted 2014Dec from http://developer.mbed.org/teams/Bluetooth-Low-Energy/code/BLE_HeartRate/
@@ -325,8 +453,7 @@
 //Adopted 2014Dec from: https://developer.mbed.org/users/takafuminaka/code/BLE_HTM_by_InTempSensr/
 // Service:  https://developer.bluetooth.org/gatt/services/Pages/ServiceViewer.aspx?u=org.bluetooth.service.health_thermometer.xml
 // HTM Char: https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.temperature_measurement.xml
-
-//****Although nRF-MCP displays float OK, the HTM Apps don't, its possible IEEE format required like in the original code:BLE_HTM_by_InTempSensr 
+//****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;
@@ -355,6 +482,7 @@
     DEBUG("[BATT:%d%%]", u8_BattPercent);
     return(u8_BattPercent);
 }
+
 //==========main==========
 int main(void)
 {
@@ -381,27 +509,33 @@
     ble.onTimeout(Callback_BLE_onTimeout);
     ble.onUpdatesEnabled(Callback_BLE_onUpdatesEnabled);
     
-    DEBUG("BLE: Handles:\n");
-    //DEBUG(" Service: BLE: %d\n", ble.getHandle());
-    //DEBUG(" Characteristic:BattLevel: %d\n", batteryLevelCharacteristic.getValueAttribute().getHandle());
-    //DFUService
-
-    
-    
-
-//ble_error_t readCharacteristicValue  ( uint16_t  handle,    uint8_t *const  buffer,    uint16_t *const  lengthP  ) 
-//ble_error_t  updateCharacteristicValue (uint16_t handle, const uint8_t *value, uint16_t size, bool localOnly=false) 
-
-//UUID List by Services that are setup
     //BLE2: Setup Services (with their initial values and options)
     DEBUG("BLE: Setup Services\n");
     // *Order here affects order in nRF-MCP Discovery of Services
     DeviceInformationService    ServiceDeviceInfo(ble, "Maker", pcDeviceName, "sn1234", "hw00", "fw00", "sw00");//(BLEDevice), pcManufacturer, pcModelNumber, pcSerialNumber, pcHWver, pcFWver, pcSWver
-        pServiceDeviceInfo      = &ServiceDeviceInfo; DEBUG("   Handle Service DeviceInfo:%d\n", ServiceDeviceInfo);
+        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); 
@@ -409,22 +543,33 @@
     //UARTService               ServiceUART(ble);
     //  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]);
+
     //BLE3: Setup advertising
     // Note the Advertising payload is limited to 31bytes, so careful don't overflow this. Multiple UUID16's or a single UUID128 are possible in advertising.
     // If there isn't enough space, then the accumulatepayload won't add that block to the advertising, it won't add a partial block.
-    DEBUG("BLE: Setup Advertising\n");
+    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?
-    //ble.accumulateAdvertisingPayload(GapAdvertisingData::INCOMPLETE_LIST_128BIT_SERVICE_IDS, (const uint8_t *)UUID128_UART_Rev, sizeof(UUID128_UART_Rev)); //Single UUID128
-    //ble.accumulateAdvertisingPayload(GapAdvertisingData::INCOMPLETE_LIST_128BIT_SERVICE_IDS, (const uint8_t *)puUUID128_BatteryService, sizeof(puUUID128_BatteryService)); //Single UUID128
-    //ble.accumulateAdvertisingPayload(GapAdvertisingData::INCOMPLETE_LIST_128BIT_SERVICE_IDS, puStrToUUID128( pc_com_none_servA), LENGTH_OF_LONG_UUID); //Single UUID128
-    //ble.accumulateAdvertisingPayload(GapAdvertisingData::INCOMPLETE_LIST_16BIT_SERVICE_IDS, (uint8_t *)uuid16_list, sizeof(uuid16_list)); // Multiple UUID16
+    //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
-    //ble.accumulateAdvertisingPayload(GapAdvertisingData::GENERIC_HEART_RATE_SENSOR);//PR: Add Appearance::HeartRate (HRM Connects without this)
-    //ble.accumulateAdvertisingPayload(GapAdvertisingData::GENERIC_THERMOMETER);      //PR: Add Appearance::Thermometer (HTM Connects without this, though float format still needs change)
+    //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)
+    
+    
+// main() init:
+//    ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_128BIT_SERVICE_IDS, (const uint8_t *)puUUID128_CustomService1, sizeof(puUUID128_CustomService1));
+    
     // 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();
@@ -461,6 +606,10 @@
             update_hrm();
             update_batt();
 
+            update_CustomW8();
+            update_CustomR4();
+            update_CustomN4();
+
             DEBUG("BLE: Wakes:%u Delta:%d ", u32_wakeevents, u32_wakeevents-u32_wakelast); //For Evaluating Timing
             u32_wakelast = u32_wakeevents;           
         } else if (b_Ticker1) {
@@ -478,3 +627,73 @@
     }
 }
 //========== 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