Library that exposes BLE characteristics as ordinary variables.

Dependents:   SimpleBLE-Example-mbed-os-5 SimpleBLE-Example ObCP_ENSMM_Test SimpleBLE-ObCP_ENSMM_V2019_Test_BLE

Looking for example code? See SimpleBLE-Example.

Looking for the blog post about this library? See A new Bluetooth library: SimpleBLE.

SimpleBLE is a library which allows you to easily add Bluetooth Low Energy support to any application, without having to change the way you write code. It's goal is to expose any value or sensor over BLE in a single line of code. Let's say you're reading a sensor value every second from pin A0:

uint16_t lightValue;

AnalogIn light(A0);
void read() {
  lightValue = light.read_u16();
  printf("Value is now %d\n", lightValue);
}

Ticker t;
t.attach(&read, 1.0f);

Now we can expose this sensor to any BLE client using SimpleBLE:

SimpleChar<uint16_t> lightValue = ble.readOnly_u16(0x8000, 0x8001);

AnalogIn light(A0);
void read() {
  lightValue = light.read_u16();
  printf("Value is now %d\n", lightValue);
}

Ticker t;
t.attach(&read, 1.0f);

SimpleBLE will automatically gather services for you, bootstrap the BLE runtime, react to write callbacks and update the underlying BLE characteristic whenever the variable gets changed.

Setting up the library

First import this library to the application where you want to use it. Then load SimpleBLE like this:

#include "mbed.h"
#include "SimpleBLE.h"

SimpleBLE ble("DEVICE_NAME"); // second argument is the advertisement interval (default: 1000 ms.)

// variables here!

int main(int, char**) {
    ble.start();
    while (1) {
        ble.waitForEvent();
    }
}

You'll also need to import the BLE library for your platform (e.g. nrf51822 for nRF51-DK and micro:bit). If you grab the example program, all should be set.

After you build and flash this application the device will now show up as 'DEVICE_NAME' in any BLE scanner like nRF Master Control Panel.

Reading and writing values

Every variable that you declare after creating the `SimpleBLE` object, and calling `start()` will automatically be exposed over BLE. We have three different types of objects:

  • readOnly - Variable can only be read over BLE.
  • readWrite - Variable can be read and written over BLE.
  • writeOnly - Variable can be written over BLE, but not read.

Creating a new variable is done by calling the respective function on the simpleBLE object, postfixed by the type you want the variable to have:

SimpleBLE ble("DEVICE_NAME");

SimpleChar<uint8_t> myVar = ble.readOnly_u8(0x8200, 0x8201); // creates uint8_t

The postfix has to be either: u8 (uint8_t), u16 (uint16_t), u32 (uint32_t), i8 (int8_t), i16 (int16_t), i32 (int32_t), bool (bool) or float (float).

The functions take four (non-write) or five (write) arguments:

  • serviceUUID - Which service should this variable fall under. Either use a uint16_t (shorthand) or you can use a string (full UUID).
  • characterUUID - Which character this variable should have. Also, use uint16_t or a string.
  • observable - Whether a BLE client can subscribe to updates on this characteristic (default: true).
  • defaultValue - Default value of the characteristic (default: 0 or 0.0f, depending on your type).
  • callback - Function to be called whenever the value of your characteristic was updated over BLE (only available for variables that support writing).

For example, this is how we make a non-observable readWrite characteristic, with a default value of 100, and a callback function:

SimpleBLE ble("DEVICE_NAME");

void updated(uint32_t newValue) {
  printf("My value was updated, and is now %d\n", newValue);
}

SimpleChar<uint32_t> myVar = ble.readWrite_u32(0x9341, 0x9342, false, 100, &updated);

Just like normal variables

SimpleBLE variables behave just like normal variables, you can read and write straight from them. For example, this will compile just fine:

SimpleChar<uint8_t> heartrate = ble.readOnly_u8(0x180d, 0x2a37, true, 100);

void updateHr() {
  heartrate = heartrate + 1;
  if (heartrate > 180) {
    heartrate = 100;
  }
}

Ticker t;
t.attach(updateHr, 1.0f);

What to use it for?

I wrote this library because I do quite a lot of workshops and hackathons, and I have seen that BLE_API is too complicated for people who do not do embedded development or C++ on a daily basis. Exposing a new sensor over Bluetooth should be a single line of code. So if you're running any workshops, give this lib a go and let me know your experiences.

Files at this revision

API Documentation at this revision

Comitter:
Jan Jongboom
Date:
Mon Feb 19 13:29:45 2018 +0800
Parent:
7:9573379273a6
Commit message:
Revert back to 15329a3de04c, mbed os 5 fork now lives in separate repository

Changed in this revision

.hgignore Show diff for this revision Revisions of this file
BLE_API.lib Show annotated file Show diff for this revision Revisions of this file
SimpleBLE.h Show annotated file Show diff for this revision Revisions of this file
mbed-events.lib Show diff for this revision Revisions of this file
--- a/.hgignore	Mon Oct 10 12:45:06 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-mbed-events/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/BLE_API.lib	Mon Feb 19 13:29:45 2018 +0800
@@ -0,0 +1,1 @@
+http://mbed.org/teams/Bluetooth-Low-Energy/code/BLE_API/#66159681aa21
--- a/SimpleBLE.h	Mon Oct 10 12:45:06 2016 +0200
+++ b/SimpleBLE.h	Mon Feb 19 13:29:45 2018 +0800
@@ -13,13 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
+ 
 #include <string>
 #include <sstream>
 #include <vector>
 #include <map>
-#include <mbed-events/events.h>
-#include <mbed.h>
 #include "ble/BLE.h"
 
 #define def_fn(T, postfix) \
@@ -77,7 +75,7 @@
 class SimpleCharBase {
 public:
     virtual void update(T newValue) = 0;
-    virtual T* getValue(void) = 0;
+    virtual T* getValue(void) = 0;  
 };
 
 /**
@@ -87,16 +85,16 @@
 template <class T, template <typename T2> class U>
 class SimpleCharInternal : public Updatable, public SimpleCharBase<T> {
 public:
-    SimpleCharInternal(BLE* aBle,
-               const UUID &uuid,
-               GattCharacteristic::Properties_t aGattChar,
+    SimpleCharInternal(BLE* aBle, 
+               const UUID &uuid, 
+               GattCharacteristic::Properties_t aGattChar, 
                T aDefaultValue,
                void(*aCallback)(T) = NULL) :
         ble(aBle), value(new T(aDefaultValue)), callback(aCallback)
     {
         state = new U<T>(uuid, value, aGattChar);
     }
-
+    
     ~SimpleCharInternal() {
         if (state) {
             free(state);
@@ -105,12 +103,12 @@
             free(value);
         }
     }
-
+    
     virtual void update(T newValue) {
         *value = newValue;
         ble->gattServer().write(state->getValueHandle(), (uint8_t *)value, sizeof(T));
     }
-
+    
     U<T>* getChar(void) {
         return state;
     }
@@ -118,7 +116,7 @@
     virtual T* getValue(void) {
         return value;
     }
-
+    
     virtual void onDataWritten(const uint8_t* data, size_t len) {
         *value = ((T*)data)[0];
         if (callback) {
@@ -155,7 +153,7 @@
     operator T() const {
         return *(base->getValue());
     };
-
+    
 private:
     SimpleCharBase<T>* base;
 };
@@ -163,28 +161,38 @@
 
 class SimpleBLE {
 public:
-    SimpleBLE(const char* aName, uint16_t aInterval = 1000, bool aLogging = true)
-        : name(aName), interval(aInterval), logging(aLogging)
+    SimpleBLE(const char* aName, uint16_t aInterval = 1000, bool aLogging = true) 
+        : name(aName), interval(aInterval), logging(aLogging) 
     {
         ble = &BLE::Instance();
     }
     ~SimpleBLE() {}
-
-    void start(EventQueue* aEventQueue) {
-        eventQueue = aEventQueue;
-
-        ble->onEventsToProcess(BLE::OnEventsToProcessCallback_t(this, &SimpleBLE::scheduleBleEventsProcessing));
+    
+    void start() {
         ble->init(this, &SimpleBLE::bleInitComplete);
+    
+        /* SpinWait for initialization to complete. This is necessary because the
+         * BLE object is used in the main loop below. */
+        while (ble->hasInitialized()  == false) { /* spin loop */ }
     }
-
+    
+    // Start up the BLE service and just run with it!
+    void waitForEvent() {
+        ble->waitForEvent();
+    }    
+    
+    void onDisconnection(Gap::DisconnectionEventCallback_t callback) {
+        ble->gap().onDisconnection(callback);
+    }
+    
     void onConnection(Gap::ConnectionEventCallback_t callback) {
         ble->gap().onConnection(callback);
     }
-
+    
     BLE* getBle(void) {
         return ble;
     }
-
+    
     def_fn(uint8_t, u8)
     def_fn(uint16_t, u16)
     def_fn(uint32_t, u32)
@@ -193,41 +201,41 @@
     def_fn(int32_t, i32)
     def_fn(bool, bool)
     def_fn(float, float)
-
+    
 private:
     void bleInitComplete(BLE::InitializationCompleteCallbackContext *params)
     {
         if (logging) printf("bleInitComplete\r\n");
-
+        
         BLE&        ble   = params->ble;
         ble_error_t error = params->error;
-
+    
         if (error != BLE_ERROR_NONE) {
             if (logging) printf("BLE Init error %d\r\n", error);
             return;
         }
-
+    
         /* Ensure that it is the default instance of BLE */
         if(ble.getInstanceID() != BLE::DEFAULT_INSTANCE) {
             return;
         }
 
         ble.gattServer().onDataWritten(this, &SimpleBLE::onDataWrittenCallback);
-
+        
         // let's add some services yo (why is there no 'auto' in mbed?)
         uint16_t uuid16_list[uint16_services.size()];
         size_t uuid16_counter = 0;
-        {
+        {   
             typedef std::map<uint16_t, vector<GattCharacteristic*>* >::iterator it_type;
             for(it_type it = uint16_services.begin(); it != uint16_services.end(); it++) {
                 if (logging) printf("Creating service 0x%x\n", it->first);
-                uuid16_list[uuid16_counter++] = it->first;
-
+                uuid16_list[uuid16_counter++] = it->first;            
+    
                 GattCharacteristic* charTable[it->second->size()];
                 for (size_t git = 0; git < it->second->size(); git++) {
                     charTable[git] = it->second->at(git);
                 }
-
+    
                 GattService service(it->first, charTable, it->second->size());
                 ble.gattServer().addService(service);
             }
@@ -240,20 +248,18 @@
             typedef std::map<string, vector<GattCharacteristic*>* >::iterator it_type;
             for(it_type it = uint128_services.begin(); it != uint128_services.end(); it++) {
                 if (logging) printf("Creating service %s\n", it->first.c_str());
-                uuid128_list[uuid128_counter++] = it->first.c_str();
-
+                uuid128_list[uuid128_counter++] = it->first.c_str();            
+    
                 GattCharacteristic* charTable[it->second->size()];
                 for (size_t git = 0; git < it->second->size(); git++) {
                     charTable[git] = it->second->at(git);
                 }
-
+    
                 GattService service(UUID(it->first.c_str()), charTable, it->second->size());
                 ble.gattServer().addService(service);
             }
         }
 
-        ble.gap().onDisconnection(Gap::DisconnectionEventCallback_t(this, &SimpleBLE::disconnectionCallback));
-
         ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
         ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, (uint8_t *)uuid16_list, uuid16_counter);
         ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_128BIT_SERVICE_IDS, (uint8_t *)uuid128_list, uuid128_counter);
@@ -261,15 +267,10 @@
         ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
         ble.gap().setAdvertisingInterval(interval);
         ble.gap().startAdvertising();
-
+        
         if (logging) printf("Started advertising\r\n");
     }
-
-    void disconnectionCallback(const Gap::DisconnectionCallbackParams_t *params)
-    {
-        BLE::Instance().gap().startAdvertising(); // restart advertising
-    }
-
+    
     void onDataWrittenCallback(const GattWriteCallbackParams *params) {
         // see if we know for which char this message is...
         typedef std::map<GattCharacteristic*, Updatable* >::iterator it_type;
@@ -279,7 +280,7 @@
             }
         }
     }
-
+    
     void addToServices(uint16_t uuid, GattCharacteristic* c) {
         if (uint16_services.count(uuid) == 0) {
             uint16_services[uuid] = new vector<GattCharacteristic*>();
@@ -287,7 +288,7 @@
 
         uint16_services[uuid]->push_back(c);
     }
-
+    
     void addToServices(const char* aUuid, GattCharacteristic* c) {
         string uuid(aUuid);
         if (uint128_services.count(uuid) == 0) {
@@ -296,63 +297,58 @@
 
         uint128_services[uuid]->push_back(c);
     }
-
-    void scheduleBleEventsProcessing(BLE::OnEventsToProcessCallbackContext* context) {
-        BLE &ble = BLE::Instance();
-        eventQueue->post(Callback<void()>(&ble, &BLE::processEvents));
-    }
-
+    
     // === START READONLY ===
-
+    
     template <typename T>
-    SimpleChar<T> readOnly(uint16_t serviceUuid,
-                                     const UUID& charUuid,
+    SimpleChar<T> readOnly(uint16_t serviceUuid, 
+                                     const UUID& charUuid, 
                                      bool enableNotify = true,
                                      T defaultValue = T()) {
-        GattCharacteristic::Properties_t gattChar = enableNotify ?
+        GattCharacteristic::Properties_t gattChar = enableNotify ? 
             GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY :
             GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ;
 
-        SimpleCharInternal<T, ReadOnlyGattCharacteristic>* c =
+        SimpleCharInternal<T, ReadOnlyGattCharacteristic>* c = 
             new SimpleCharInternal<T, ReadOnlyGattCharacteristic>(ble, charUuid, gattChar, defaultValue);
-
+        
         addToServices(serviceUuid, c->getChar());
-
+        
         return *(new SimpleChar<T>(c));
     }
 
     template <typename T>
-    SimpleChar<T> readOnly(const char* serviceUuid,
-                                     const UUID& charUuid,
+    SimpleChar<T> readOnly(const char* serviceUuid, 
+                                     const UUID& charUuid, 
                                      bool enableNotify = true,
                                      T defaultValue = T()) {
-        GattCharacteristic::Properties_t gattChar = enableNotify ?
+        GattCharacteristic::Properties_t gattChar = enableNotify ? 
             GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY :
             GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ;
 
-        SimpleCharInternal<T, ReadOnlyGattCharacteristic>* c =
+        SimpleCharInternal<T, ReadOnlyGattCharacteristic>* c = 
             new SimpleCharInternal<T, ReadOnlyGattCharacteristic>(ble, charUuid, gattChar, defaultValue);
-
+        
         addToServices(serviceUuid, c->getChar());
 
         return *(new SimpleChar<T>(c));
     }
 
     // === END READONLY ===
-
+    
     // === START READWRITE ===
 
     template <typename T>
-    SimpleChar<T> readWrite(uint16_t serviceUuid,
-                                      const UUID& charUuid,
+    SimpleChar<T> readWrite(uint16_t serviceUuid, 
+                                      const UUID& charUuid, 
                                       bool enableNotify = true,
                                       T defaultValue = T(),
                                       void(*callback)(T) = NULL) {
-        GattCharacteristic::Properties_t gattChar = enableNotify ?
+        GattCharacteristic::Properties_t gattChar = enableNotify ? 
             GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY :
             GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ;
 
-        SimpleCharInternal<T, ReadWriteGattCharacteristic>* c =
+        SimpleCharInternal<T, ReadWriteGattCharacteristic>* c = 
             new SimpleCharInternal<T, ReadWriteGattCharacteristic>(ble, charUuid, gattChar, defaultValue, callback);
 
         addToServices(serviceUuid, c->getChar());
@@ -361,52 +357,52 @@
 
         return *(new SimpleChar<T>(c));
     }
-
-
+    
+    
 
     template <typename T>
-    SimpleChar<T> readWrite(const char* serviceUuid,
-                                      const UUID& charUuid,
+    SimpleChar<T> readWrite(const char* serviceUuid, 
+                                      const UUID& charUuid, 
                                       bool enableNotify = true,
                                       T defaultValue = T(),
                                       void(*callback)(T) = NULL) {
-        GattCharacteristic::Properties_t gattChar = enableNotify ?
+        GattCharacteristic::Properties_t gattChar = enableNotify ? 
             GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY :
             GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ;
 
-        SimpleCharInternal<T, ReadWriteGattCharacteristic>* c =
+        SimpleCharInternal<T, ReadWriteGattCharacteristic>* c = 
             new SimpleCharInternal<T, ReadWriteGattCharacteristic>(ble, charUuid, gattChar, defaultValue, callback);
 
         addToServices(serviceUuid, c->getChar());
 
         writeCallbacks[c->getChar()] = c;
-
+        
         return *(new SimpleChar<T>(c));
     }
-
+    
     template <typename T>
-    SimpleChar<T> readWrite(uint16_t serviceUuid,
-                                      const UUID& charUuid,
+    SimpleChar<T> readWrite(uint16_t serviceUuid, 
+                                      const UUID& charUuid, 
                                       void(*callback)(T) = NULL) {
         return readWrite(serviceUuid, charUuid, true, T(), callback);
     }
 
     template <typename T>
-    SimpleChar<T> readWrite(const char* serviceUuid,
-                                      const UUID& charUuid,
+    SimpleChar<T> readWrite(const char* serviceUuid, 
+                                      const UUID& charUuid, 
                                       void(*callback)(T) = NULL) {
         return readWrite(serviceUuid, charUuid, true, T(), callback);
     }
-
+    
     // === END READWRITE ===
-
+    
     // === START WRITEONLY ===
 
     template <typename T>
-    SimpleChar<T> writeOnly(uint16_t serviceUuid,
+    SimpleChar<T> writeOnly(uint16_t serviceUuid, 
                                       const UUID& charUuid,
                                       void(*callback)(T) = NULL) {
-        SimpleCharInternal<T, WriteOnlyGattCharacteristic>* c =
+        SimpleCharInternal<T, WriteOnlyGattCharacteristic>* c = 
             new SimpleCharInternal<T, WriteOnlyGattCharacteristic>(ble, charUuid, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NONE, T(), callback);
 
         addToServices(serviceUuid, c->getChar());
@@ -417,20 +413,20 @@
     }
 
     template <typename T>
-    SimpleChar<T> writeOnly(const char* serviceUuid,
+    SimpleChar<T> writeOnly(const char* serviceUuid, 
                                       const UUID& charUuid,
                                       void(*callback)(T) = NULL) {
 
-        SimpleCharInternal<T, WriteOnlyGattCharacteristic>* c =
+        SimpleCharInternal<T, WriteOnlyGattCharacteristic>* c = 
             new SimpleCharInternal<T, WriteOnlyGattCharacteristic>(ble, charUuid, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NONE, T(), callback);
 
         addToServices(serviceUuid, c->getChar());
 
         writeCallbacks[c->getChar()] = c;
-
+        
         return *(new SimpleChar<T>(c));
     }
-
+    
     // === END WRITEONLY ===
 
     BLE* ble;
@@ -440,6 +436,5 @@
     map<uint16_t, vector<GattCharacteristic*>* > uint16_services;
     map<string, vector<GattCharacteristic*>* > uint128_services;
     map<GattCharacteristic*, Updatable*> writeCallbacks;
-    EventQueue* eventQueue;
 };
 
--- a/mbed-events.lib	Mon Oct 10 12:45:06 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-https://github.com/ARMmbed/mbed-events.git#6be60bf880c11a0beafcc2064bf467f8d897529a