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:
12:8bac5f5d3a3e
Parent:
11:7d02fe5ebea5
Child:
13:1c67c03bbf53
--- a/main.cpp	Sun Dec 21 16:26:46 2014 +0000
+++ b/main.cpp	Sun Dec 21 20:44:37 2014 +0000
@@ -83,25 +83,23 @@
 const bool bPrep = u8_prep_dummy();
 
 //========== IO Hardware: Buttons, LEDs, PWM ==========
-// Inputs:
+// Inputs: (Simultaneous DigitalIn and InteruptIn is OK)
 DigitalIn       bB1in(BUTTON1); //if(bB1in){}
 DigitalIn       bB2in(BUTTON2); //if(bB2in){}
 InterruptIn     B1int(BUTTON1); //B1int.rise(&onB1rise);  
 InterruptIn     B2int(BUTTON2); //B1int.fall(&onB1fall);
 
 // Outputs:
-//DigitalOut      bL1out(LED1);   // Direct LED1 drive
-//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)
+//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
 
-// onButton Callbacks for InterruptIn
-// *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 uB1rise; void onB1rise(void){ uB1rise++; };// Flag Event, Counter helps detect missed events since last cleared
-volatile uint8_t uB1fall; void onB1fall(void){ uB1fall++; };// Flag Event, Counter helps detect missed events since last cleared
-volatile uint8_t uB2rise; void onB2rise(void){ uB2rise++; };// Flag Event, Counter helps detect missed events since last cleared
-volatile uint8_t uB2fall; void onB2fall(void){ uB2fall++; };// Flag Event, Counter helps detect missed events since last cleared
+//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
 
 //==========BLE==========
 const static char pcDeviceName[] = "bleIOv04_pr"; //PR: Why can App nRF-MCP modify this even though flagged as Const, maybe only temporary mod till App restarts?
@@ -198,27 +196,98 @@
 */
 
     // 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 
+    //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, 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  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, 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
+    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
+    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
 
-//a Option: Allow device to modify its settings and inform host (Some devices may be able to self-modify host written settings)
+//========== 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
+    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[0], sIOw8.s.px[0]);
+    //DEBUG(" pIOw8: Handle:%d Len:%d [%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(" 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]);
+    uLen=sizeof(puBuf8); ble.readCharacteristicValue(IOw8.getValueHandle(), puBuf8, &uLen);  // Real Characteristic's Actual Value
+    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] );
+ 
+    // IOr4 Read
+    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(" pIOr4: Handle:%d Len:%d [%02X %02X %02X %02X]\n", IOr4.getValueHandle(), sizeof(pIOr4), pIOr4[0], pIOr4[1], pIOr4[2], pIOr4[3]);
+    //DEBUG(" sIOr4: Handle:%d Len:%d [%02X %02X %02X %02X][%.4s]\n", IOr4.getValueHandle(), sizeof(sIOr4), sIOr4.pu[0], sIOr4.pu[1], sIOr4.pu[2], sIOr4.pu[3], sIOr4.pc );
+    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]);
+    // Read Actual IOr4 Characteristic's data back from Service (i.e. last written to service):
+    uLen=sizeof(puBuf8); ble.readCharacteristicValue(IOr4.getValueHandle(), puBuf8, &uLen);  // Real Characteristic's Actual Value
+    DEBUG(" cIOr4: Handle:%d Len:%d [%02X %02X %02X %02X]\n", IOr4.getValueHandle(), uLen, puBuf8[0], puBuf8[1], puBuf8[2], puBuf8[3] );
+
+    // IOn4 Notify
+    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("\n pIOn4: Handle:%d Len:%d [%02X %02X %02X %02X]\n", IOn4.getValueHandle(), sizeof(pIOn4), pIOn4[0], pIOn4[1], pIOn4[2], pIOn4[3]);
+    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]);
+    uLen=sizeof(puBuf8); ble.readCharacteristicValue(IOn4.getValueHandle(), puBuf8, &uLen);  // Real Characteristic's Actual Value
+    DEBUG(" cIOn4: Handle:%d Len:%d [%02X %02X %02X %02X]\n", IOn4.getValueHandle(), uLen, puBuf8[0], puBuf8[1], puBuf8[2], puBuf8[3] );
+}
+
+//==== 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;
@@ -228,34 +297,72 @@
     //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();
 }*/
 
-void vUpdate_IOr4(void) // Handle device updates r4 characteristics (Without Notify, Host must poll for these)
-{   // For out project this would be: Temperature, Version Info, etc.
-    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));
+//==== 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?)
 }
 
-void vUpdate_IOn4(void) // Handle device updates n4 characteristics (With notify)
-{   // For our Device this would be: Dispenser count changes, Button Activity, Empty warning, Timeouts, etc.
-    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;
-    }
-    ble.updateCharacteristicValue(IOn4.getValueHandle(), pIOn4, sizeof(pIOn4));
-}
+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));// Notify
+    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(IOr4.getValueHandle(), sIOr4.pu, sizeof(sIOr4));
+    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(IOr4.getValueHandle(), sIOr4.pu, sizeof(sIOr4));
+    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));// Notify
+    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("\nBLEi: Callback_BLE_onTimeout()\n" ); 
+    DEBUG("\n\n\n\n**** BLEi: Callback_BLE_onTimeout() ****\n\n\n\n" ); 
   
     //DEBUG("\nBLE:Callback_BLE_onTimeout(), Restarting Advertising\n" );
     //ble.startAdvertising();
@@ -296,14 +403,13 @@
     //x #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)", uSentBLE); //TODO: PR: Why uSent always "1", expected it to match sent bytes length
-    bSent = true;
-    //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.
+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)
@@ -336,9 +442,16 @@
     } else {
         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]);
+            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: Direct LED Control or Output Control, Or Configuration (Includes flags for: Factory Reset, Enter Test Mode, Enter Demo Mode)
+
         //} else if (tHandle == IOr4.getValueHandle()) { // Readonly, shouldn't occur
         //    ble.readCharacteristicValue(tHandle, pIOr4, &uLen);
         //    DEBUG("  IOr4[%d]:%02X %02X %02X %02X\n", uLen, pIOr4[0], pIOr4[1], pIOr4[2], pIOr4[3]);
@@ -358,19 +471,19 @@
     //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[])
 
-    //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]);
+    //vShowIO(); //Show IO Characteristic Status
 }
 
 void Callback_BLE_onUpdatesEnabled(Gap::Handle_t 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
+    if        (tHandle == IOn4.getValueHandle())    { DEBUG("\nBLEi: 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); // Notify not enabled on this Characteristic
   //} else if (tHandle == IOw8.getValueHandle())    { DEBUG("  onUpdates(Handle:%d) Enabled - IOw8\n", tHandle); // Notify not enabled on this Characteristic
-    } else                                          { DEBUG("  onUpdates(Handle:%d) Enabled - Characteristic?\n", tHandle);
+    } else                                          { DEBUG("\nBLEi: 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
@@ -403,7 +516,7 @@
 {
     //static float fTemperature = 0;//-123.456;
     int32_t i32_temp;
-    sd_temp_get(&i32_temp);   //Read the nRF Internal Temperature (Die in 0.25'C steps, Counting From TBD), TODO:Check Scaling
+    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
@@ -434,36 +547,39 @@
 {
     static uint32_t u32_Counter; // Counter for checking Timing
     DEBUG("\nBLEi: Ticker(%04u) ", ++u32_Counter);
-    fL1level+=0.1; if (fL1level>0.5){fL1level = 0.1;}; fL1pwm=fL1level;//PR: Ramp Blink
+    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
     uTicker1++; // Flag event, using counter so can detect missed events
 }
 
 //==========main==========
 int main(void)
-{
+{    
+    vShowIO();
+
     fL1level = 1; fL1pwm = fL1level;//Start LED1=OnMax
     fL2level = 1; fL2pwm = fL2level;//Start LED2=OnMax
 
     //Restart TeraTerm just before Pressing Reset on mbed, 9600-8N1(No Flow Control)
     DEBUG("\nBLE: ___%s___\n", pcDeviceName); 
-    DEBUG(" Built:[ %s %s ]  Compiler:[ %s ]\n", __DATE__, __TIME__, __VERSION__);
+    DEBUG(" Built UTC:[ %s %s ]  Compiler:[ %s ]\n", __DATE__, __TIME__, __VERSION__);
     DEBUG("BLE: Connect App for Data: nRF-MCP, nRF-Toolbox:HRM/HTM, Android Sample BluetoothLeGatt, etc.\n");
     DEBUG("BLE: BluetoothLeGatt: App->mbed: LinkLoss->AlertLevel(UpArrow)\n"); 
     DEBUG("BLE: BluetoothLeGatt: App->mbed: BatteryService->BatteryLevel(DownArrow)\n"); 
     DEBUG("BLE: BluetoothLeGatt: mbed->App: BatteryService->BatteryLevel->EnableNotify(ThreeDownArrows), Also App Acks the send=DEBUG('SentI')\n"); 
 
     Ticker ticker1;                             //PR: Timer Object(Structure)
-    ticker1.attach(CallbackTicker1, 2.0);       //PR: Timer Handler, Float=PeriodSeconds
+    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("BLE: Setup BLE\n");
     ble.init();
-    ble.onDisconnection(Callback_BLE_onDisconnect);
-    ble.onConnection(Callback_BLE_onConnect); //PR: Not required if no actions enabled, enabled now just for debug printf()
-    ble.onDataSent(Callback_BLE_onDataSent);
-    ble.onDataWritten(Callback_BLE_onDataWritten);
-    ble.onTimeout(Callback_BLE_onTimeout);
-    ble.onUpdatesEnabled(Callback_BLE_onUpdatesEnabled);
+    ble.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.onDataRead(Callback_BLE_onDataRead) //TODO: Need a callback for when reading data so can ensure characteristic is fully up to data before it is passed back (For now update periodically)
     
     //BLE2: Setup Services (with their initial values and options)
     DEBUG("BLE: Setup Services\n");
@@ -481,12 +597,10 @@
     //UARTService               ServiceUART(ble);
     //  pServiceUART            = &ServiceUART;
 
-    //Start CustomIO Service:
+    DEBUG("BLE: Setup Custom IO Service\n");
     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
+    vShowIO();
+    //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.
@@ -519,12 +633,12 @@
     // = 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
+    // Setup InterruptIn for Buttons (coded for nRF51822-mkit polarities on these buttons)
     DEBUG("BLE: Enable Button Interrupts\n");
-    B1int.fall(&onB1fall);// Button1 Press
-    B1int.rise(&onB1rise);// Button1 Release
-    B2int.fall(&onB2fall);// Button2 Press
-    B2int.rise(&onB2rise);// Button2 Release
+    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");
     uint32_t u32_wakeevents=0, u32_wakelast=0; // Counter&Register for tracking Wake Events (to see monitor their Frequency)
@@ -541,23 +655,23 @@
             update_batt();
 
             //a vUpdate_IOw8();
-            vUpdate_IOr4();
-            vUpdate_IOn4();
+            //a vUpdate_IOr4();
+            //a vUpdate_IOn4();
 
-            DEBUG("  BLE:Wakes:%u,Delta:%u ", u32_wakeevents, u32_wakeevents-u32_wakelast); //For Evaluating Timing
+            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 (bSent){
-            bSent=false; //clear flag
-            //DEBUG("BLE: Sent %ubytes ", uSentBLE);
+            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
-            fL2level+=0.25; if (fL2level>0.5){fL2level = 0.0;}; fL2pwm=fL2level;//PR: Ramp Blink
+            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
+         }
     }
 }
 //========== end of main.cpp ==========