A library for easier setup and prototyping of new pucks, by collecting everything that is common for all pucks in one place.

Dependencies:   BLE_API nRF51822

Fork of Puck by Nordic Pucks

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers Puck.h Source File

Puck.h

00001 #ifndef __PUCK_HPP__
00002 #define __PUCK_HPP__
00003  
00004 #include "BLEDevice.h"
00005 #include <vector>
00006 #include "Log.h"
00007 
00008 enum PuckState {
00009     CONNECTING,
00010     CONNECTED,
00011     ADVERTISING,
00012     DISCONNECTED
00013 };
00014 
00015 const UUID stringToUUID(const char* str);
00016 
00017 typedef void (*CharacteristicWriteCallback)(uint8_t* value);
00018  
00019  typedef struct {
00020      const UUID* uuid;
00021      std::vector<CharacteristicWriteCallback>* callbacks;
00022 } CharacteristicWriteCallbacks;
00023   
00024 /**
00025  *  @brief A library for easier setup and prototyping of IoT devices (pucks), by collecting everything that is common for all pucks in one place.
00026  *
00027  */ 
00028 class Puck {
00029     private:
00030         Puck() {}
00031         Puck(const Puck&);
00032         Puck& operator=(const Puck&);
00033         
00034         BLEDevice ble;        
00035         uint8_t beaconPayload[25];
00036         PuckState state;
00037         std::vector<GattService*> services;
00038         std::vector<GattCharacteristic*> characteristics;
00039         std::vector<CharacteristicWriteCallbacks*> writeCallbacks;
00040         std::vector<CharacteristicWriteCallback> pendingCallbackStack;
00041         std::vector<uint8_t*> pendingCallbackParameterStack;
00042         
00043         GattCharacteristic **previousCharacteristics;
00044         
00045     public:
00046         static Puck &getPuck();
00047         
00048         BLEDevice &getBle() { return ble; }
00049         PuckState getState() { return state; }
00050         void setState(PuckState state);        
00051         void init(uint16_t minor);
00052         void startAdvertising();
00053         void stopAdvertising();
00054         void disconnect();
00055         bool drive();
00056         int countFreeMemory();
00057         void onDataWritten(uint16_t handle);
00058         void addCharacteristic(const UUID serviceUuid, const UUID characteristicUuid, int bytes, int properties = 0xA);
00059 
00060         void onCharacteristicWrite(const UUID* uuid, CharacteristicWriteCallback callback);
00061         void updateCharacteristicValue(const UUID uuid, uint8_t* value, int length);
00062 
00063         uint8_t* getCharacteristicValue(const UUID uuid);
00064 };
00065 
00066 /**
00067  *  @brief Returns singleton instance of puck object.
00068  *
00069  *  @return singleton instance of puck object.
00070  */
00071 Puck &Puck::getPuck() {
00072     static Puck _puckSingletonInstance;
00073     return _puckSingletonInstance;
00074 }
00075 
00076 
00077 void onDisconnection(void) {
00078     LOG_INFO("Disconnected.\n");
00079     Puck::getPuck().setState(DISCONNECTED);
00080 }
00081 
00082 void onConnection(void) {
00083     LOG_INFO("Connected.\n");
00084     Puck::getPuck().setState(CONNECTED);
00085 }
00086 
00087 void onDataWrittenCallback(uint16_t handle) {
00088     Puck::getPuck().onDataWritten(handle);
00089 }
00090 
00091 bool isEqualUUID(const UUID* uuidA, const UUID uuidB) {
00092     const uint8_t* uuidABase = uuidA->getBaseUUID();
00093     const uint8_t* uuidBBase = uuidB.getBaseUUID();
00094     
00095     for(int i = 0; i < 16; i++) {
00096         if(uuidABase[i] != uuidBBase[i]) {
00097             return false;
00098         }
00099     }
00100     if(uuidA->getShortUUID() != uuidB.getShortUUID()) {
00101         return false;
00102     }
00103     return true;
00104 }
00105 
00106 /**
00107  * @brief Returns UUID representation of a 16-character string.
00108  *
00109  */
00110 const UUID stringToUUID(const char* str) {
00111     uint8_t array[16];
00112     for(int i = 0; i < 16; i++) {
00113         array[i] = str[i];    
00114     }
00115     return UUID(array);
00116 }
00117 
00118 void Puck::disconnect() {
00119     ble.disconnect();    
00120 }
00121 
00122 /**
00123  *  @brief Approximates malloc-able heap space. Do not use in production code, as it may crash.
00124  *
00125  */
00126 int Puck::countFreeMemory() {
00127     int blocksize = 256;
00128     int amount = 0;
00129     while (blocksize > 0) {
00130         amount += blocksize;
00131         LOG_VERBOSE("Trying to malloc %i bytes... ", amount);
00132         char *p = (char *) malloc(amount);
00133         if (p == NULL) {
00134             LOG_VERBOSE("FAIL!\n", amount);
00135             amount -= blocksize;
00136             blocksize /= 2;
00137         } else {
00138             free(p);
00139             LOG_VERBOSE("OK!\n", amount);
00140         }
00141     }
00142     LOG_DEBUG("Free memory: %i bytes.\n", amount);
00143     return amount;
00144 }
00145 
00146 void Puck::setState(PuckState state) {
00147     LOG_DEBUG("Changed state to %i\n", state);
00148     this->state = state;    
00149 }
00150 
00151 /**
00152  *  @brief  Call after finishing configuring puck (adding services, characteristics, callbacks).
00153             Starts advertising over bluetooth le. 
00154  *
00155  *  @parameter  minor
00156  *              Minor number to use for iBeacon identifier.
00157  *
00158  */
00159 void Puck::init(uint16_t minor) {
00160         /*
00161      * The Beacon payload (encapsulated within the MSD advertising data structure)
00162      * has the following composition:
00163      * 128-Bit UUID = E2 0A 39 F4 73 F5 4B C4 A1 2F 17 D1 AD 07 A9 61
00164      * Major/Minor  = 1337 / XXXX
00165      * Tx Power     = C8
00166      */
00167     uint8_t beaconPayloadTemplate[] = {
00168         0x00, 0x00, // Company identifier code (0x004C == Apple)
00169         0x02,       // ID
00170         0x15,       // length of the remaining payload
00171         0xE2, 0x0A, 0x39, 0xF4, 0x73, 0xF5, 0x4B, 0xC4, // UUID
00172         0xA1, 0x2F, 0x17, 0xD1, 0xAD, 0x07, 0xA9, 0x61,
00173         0x13, 0x37, // the major value to differenciate a location (Our app requires 1337 as major number)
00174         0x00, 0x00, // the minor value to differenciate a location (Change this to differentiate location pucks)
00175         0xC8        // 2's complement of the Tx power (-56dB)
00176     };
00177     beaconPayloadTemplate[22] = minor >> 8;
00178     beaconPayloadTemplate[23] = minor & 255;
00179     
00180     for (int i=0; i < 25; i++) {
00181         beaconPayload[i] = beaconPayloadTemplate[i];
00182     }
00183     
00184     ble.init();
00185     LOG_DEBUG("Inited BLEDevice.\n");
00186     setState(DISCONNECTED);
00187 
00188     ble.accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED);
00189     LOG_DEBUG("Accumulate advertising payload: BREDR_NOT_SUPPORTED.\n");
00190     
00191     ble.accumulateAdvertisingPayload(GapAdvertisingData::MANUFACTURER_SPECIFIC_DATA, beaconPayload, sizeof(beaconPayload));
00192     LOG_DEBUG("Accumulate advertising payload: beacon data.\n");
00193     
00194     ble.setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
00195     LOG_DEBUG("Setting advertising type: ADV_CONNECTABLE_UNDIRECTED.\n");
00196     
00197     int hundredMillisecondsInAdvertisingIntervalFormat = 160;
00198     ble.setAdvertisingInterval(hundredMillisecondsInAdvertisingIntervalFormat); 
00199     LOG_DEBUG("Set advertising interval: 160 (100 ms).\n");
00200     
00201     ble.onDisconnection(onDisconnection);
00202     ble.onConnection(onConnection);
00203     ble.onDataWritten(onDataWrittenCallback);
00204     LOG_DEBUG("Hooked up internal event handlers.\n");
00205     
00206     for(int i = 0; i < services.size(); i++) {
00207         ble.addService(*services[i]);
00208         LOG_DEBUG("Added service %x to BLEDevice\n", services[i]);
00209     }
00210     
00211     LOG_INFO("Inited puck as 0x%X.\n", minor);
00212 }
00213 
00214 void Puck::startAdvertising() {
00215     ble.startAdvertising();
00216     LOG_INFO("Starting to advertise.\n");
00217     setState(ADVERTISING);
00218 }
00219 
00220 void Puck::stopAdvertising() {
00221     if(state == ADVERTISING) {
00222         ble.stopAdvertising();
00223         LOG_INFO("Stopped advertising.\n");
00224         setState(DISCONNECTED);
00225     } else {
00226         LOG_WARN("Tried to stop advertising, but advertising is already stopped!\n");    
00227     }
00228 }
00229 
00230 /** 
00231  *  @brief  Extends the given gatt service with the given gatt characteristic.
00232  *          If the service doesn't exist, it is created.
00233  *
00234  *  @param  serviceUuid
00235             UUID of the gatt service to be extended.
00236  *
00237  *  @param  characteristicUuid
00238  *          UUID to use for this characteristic.
00239  *
00240  *  @param  bytes
00241  *          Length in bytes of this characteristic's value.
00242  *
00243  *  @param  properties
00244  *          8-bit bit field containing the characteristic's properties. See @ref ble_gatt_char_properties_t.
00245  *
00246  *  @return Void.
00247  */
00248 void Puck::addCharacteristic(const UUID serviceUuid, const UUID characteristicUuid, int bytes, int properties) {
00249     MBED_ASSERT(bytes <= 20);
00250     uint16_t size = sizeof(uint8_t) * bytes;
00251     uint8_t* value = (uint8_t*) malloc(size);
00252     if(value == NULL) {
00253         LOG_ERROR("Unable to malloc value for characteristic. Possibly out of memory!\n");    
00254     }
00255     
00256     GattCharacteristic* characteristic = new GattCharacteristic(characteristicUuid, value, size, size, properties);
00257     characteristics.push_back(characteristic);
00258     
00259     
00260     GattService* service = NULL;
00261     
00262     int removeIndex = -1;
00263     for(int i = 0; i < services.size(); i++) {
00264         if(isEqualUUID(&services[i]->getUUID(), serviceUuid)) {
00265             service = services[i];
00266             removeIndex = i;
00267             break;
00268         }
00269     }
00270     GattCharacteristic** characteristics = NULL;
00271     int characteristicsLength = 0;
00272     if(service != NULL) {
00273         characteristicsLength = service->getCharacteristicCount() + 1;
00274         characteristics = (GattCharacteristic**) malloc(sizeof(GattCharacteristic*) * characteristicsLength);
00275         if(characteristics == NULL) {
00276             LOG_ERROR("Unable to malloc array of characteristics for service creation. Possibly out of memory!\n");    
00277         }
00278         for(int i = 0; i < characteristicsLength; i++) {
00279             characteristics[i] = service->getCharacteristic(i);    
00280         }
00281         services.erase(services.begin() + removeIndex);
00282         delete service;
00283         free(previousCharacteristics);
00284     } else {
00285         characteristicsLength = 1;
00286         characteristics = (GattCharacteristic**) malloc(sizeof(GattCharacteristic*) * characteristicsLength);
00287         if(characteristics == NULL) {
00288             LOG_ERROR("Unable to malloc array of characteristics for service creation. Possibly out of memory!\n");    
00289         }
00290     }
00291     
00292     characteristics[characteristicsLength - 1] = characteristic;
00293     previousCharacteristics = characteristics;
00294     service = new GattService(serviceUuid, characteristics, characteristicsLength);
00295     services.push_back(service);
00296     LOG_DEBUG("Added characteristic.\n");
00297 }
00298 
00299 /** 
00300  *  @brief  Update the value of the given gatt characteristic.
00301  *
00302  *  @param  uuid
00303             UUID of the gatt characteristic to be updated.
00304  *
00305  *  @param  value
00306  *          New value of the characteristic.
00307  *
00308  *  @param  length
00309  *          Length in bytes of the characteristic's value.
00310  *
00311  *  @return Void.
00312  */
00313 void Puck::updateCharacteristicValue(const UUID uuid, uint8_t* value, int length) {
00314     GattCharacteristic* characteristic = NULL;
00315     for( int i = 0; i < characteristics.size(); i++) {
00316         if(isEqualUUID(&characteristics[i]->getUUID(), uuid)) {
00317             characteristic = characteristics[i];
00318             break;    
00319         }    
00320     }
00321     if(characteristic != NULL) {
00322         ble.updateCharacteristicValue(characteristic->getHandle(), value, length);
00323         LOG_VERBOSE("Updated characteristic value.\n");
00324     } else {
00325         LOG_WARN("Tried to update an unkown characteristic!\n");    
00326     }
00327 }
00328 
00329 /** 
00330  *  @brief Pass control to the bluetooth stack, executing pending callbacks afterwards. Should be used inside a while condition loop.
00331  *
00332  *  Example:
00333  *  @code
00334  *  while (puck->drive()) {
00335  *      // Do stuff
00336  *  }
00337  *  @endcode
00338  *
00339  * @return true.
00340  *
00341  */
00342 bool Puck::drive() {
00343     ble.waitForEvent();
00344     if(state == DISCONNECTED) {
00345         startAdvertising();    
00346     }
00347     while(pendingCallbackStack.size() > 0) {
00348         pendingCallbackStack.back()(pendingCallbackParameterStack.back());
00349         pendingCallbackStack.pop_back();
00350         pendingCallbackParameterStack.pop_back();
00351     }
00352     return true;
00353 }
00354 
00355 /** 
00356  *  @brief Register callback to be triggered on characteristic write.
00357  *
00358  *  @parameter  uuid
00359  *              UUID of the gatt characteristic to bind callback to.
00360  *
00361  *  @parameter  callback
00362  *              CharacteristicWriteCallback to be executed on characteristic write.
00363  *
00364  *  @return Void.
00365  *
00366  */
00367 void Puck::onCharacteristicWrite(const UUID* uuid, CharacteristicWriteCallback callback) {
00368     CharacteristicWriteCallbacks* cb = NULL;
00369     for(int i = 0; i< writeCallbacks.size(); i++) {
00370         if(isEqualUUID(writeCallbacks[i]->uuid, *uuid)) {
00371             cb = writeCallbacks[i];    
00372             break;
00373         }
00374     }
00375     if(cb == NULL) {
00376         cb = (CharacteristicWriteCallbacks*) malloc(sizeof(CharacteristicWriteCallbacks));
00377         if(cb == NULL) {
00378             LOG_ERROR("Could not malloc CharacteristicWriteCallbacks container. Possibly out of memory!\n");    
00379         }
00380         cb->uuid = uuid;
00381         cb->callbacks = new std::vector<CharacteristicWriteCallback>();
00382         writeCallbacks.push_back(cb);
00383     }
00384     cb->callbacks->push_back(callback);
00385     LOG_VERBOSE("Bound characteristic write callback (uuid: %x, callback: %x)\n", uuid, callback);
00386 }
00387 
00388 /**
00389  *  @brief Returns current value of provided gatt characteristic.
00390  *
00391  */
00392 uint8_t* Puck::getCharacteristicValue(const UUID uuid) {
00393     LOG_VERBOSE("Reading characteristic value for UUID %x\n", uuid);
00394     for(int i = 0; i < characteristics.size(); i++) {
00395         GattCharacteristic* characteristic = characteristics[i];
00396         if(isEqualUUID(&characteristic->getUUID(), uuid)) {
00397             return characteristic->getValuePtr();
00398         }
00399     }
00400     LOG_WARN("Tried to read an unknown characteristic!");
00401     return NULL;
00402 }
00403 
00404 /**
00405  * @brief For internal use only. Exposed to hack around mbed framework limitation.
00406  *
00407  */
00408 void Puck::onDataWritten(uint16_t handle) {
00409     for (int i = 0; i < characteristics.size(); i++) {
00410         GattCharacteristic* characteristic = characteristics[i];
00411         if (characteristic->getHandle() == handle) {
00412             uint16_t maxLength = characteristic->getMaxLength();
00413             ble.readCharacteristicValue(handle, characteristic->getValuePtr(), &maxLength);
00414             for(int j = 0; j < writeCallbacks.size(); j++) {    
00415                 CharacteristicWriteCallbacks* characteristicWriteCallbacks = writeCallbacks[j];
00416                 if(isEqualUUID(characteristicWriteCallbacks->uuid, characteristic->getUUID())) {
00417                     for(int k = 0; k < characteristicWriteCallbacks->callbacks->size(); k++) {
00418                         pendingCallbackStack.push_back(characteristicWriteCallbacks->callbacks->at(k));
00419                         pendingCallbackParameterStack.push_back(characteristic->getValuePtr());
00420                     }
00421                     return;
00422                 }
00423             }
00424         }
00425     }
00426 }
00427 
00428 
00429 
00430  #endif // __PUCK_HPP__