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

Dependencies:   BLE_API nRF51822

Dependents:   ir-puck display-puck ir-puck2 BLE_ScoringDevice ... more

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers Puck.h Source File

Puck.h

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