Example program for the Eddystone Beacon service.

Dependencies:   BLE_API mbed nRF51822 X_NUCLEO_IDB0XA1

Fork of BLE_EddystoneBeacon by URIBeacon

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers EddystoneService.cpp Source File

EddystoneService.cpp

00001 /* mbed Microcontroller Library
00002  * Copyright (c) 2006-2015 ARM Limited
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 #include "EddystoneService.h"
00018 
00019 /* Initialise the EddystoneService using parameters from persistent storage */
00020 EddystoneService::EddystoneService(BLE                 &bleIn,
00021                                    EddystoneParams_t   &paramsIn,
00022                                    const PowerLevels_t &advPowerLevelsIn,
00023                                    const PowerLevels_t &radioPowerLevelsIn,
00024                                    uint32_t            advConfigIntervalIn) :
00025     ble(bleIn),
00026     operationMode(EDDYSTONE_MODE_NONE),
00027     urlFrame(paramsIn.urlData, paramsIn.urlDataLength),
00028     uidFrame(paramsIn.uidNamespaceID, paramsIn.uidInstanceID),
00029     tlmFrame(paramsIn.tlmVersion),
00030     resetFlag(false),
00031     tlmBatteryVoltageCallback(NULL),
00032     tlmBeaconTemperatureCallback(NULL)
00033 {
00034     lockState         = paramsIn.lockState;
00035     flags             = paramsIn.flags;
00036     txPowerMode       = paramsIn.txPowerMode;
00037     beaconPeriod      = correctAdvertisementPeriod(paramsIn.beaconPeriod);
00038 
00039     memcpy(lock,             paramsIn.lock,      sizeof(Lock_t));
00040     memcpy(unlock,           paramsIn.unlock,    sizeof(Lock_t));
00041 
00042     eddystoneConstructorHelper(advPowerLevelsIn, radioPowerLevelsIn, advConfigIntervalIn);
00043 }
00044 
00045 /* When using this constructor we need to call setURLData,
00046  * setTMLData and setUIDData to initialise values manually
00047  */
00048 EddystoneService::EddystoneService(BLE                 &bleIn,
00049                                    const PowerLevels_t &advPowerLevelsIn,
00050                                    const PowerLevels_t &radioPowerLevelsIn,
00051                                    uint32_t            advConfigIntervalIn) :
00052     ble(bleIn),
00053     operationMode(EDDYSTONE_MODE_NONE),
00054     urlFrame(),
00055     uidFrame(),
00056     tlmFrame(),
00057     lockState(false),
00058     resetFlag(false),
00059     lock(),
00060     unlock(),
00061     flags(0),
00062     txPowerMode(0),
00063     beaconPeriod(DEFAULT_BEACON_PERIOD_MSEC),
00064     tlmBatteryVoltageCallback(NULL),
00065     tlmBeaconTemperatureCallback(NULL)
00066 {
00067     eddystoneConstructorHelper(advPowerLevelsIn, radioPowerLevelsIn, advConfigIntervalIn);
00068 }
00069 
00070 /* Setup callback to update BatteryVoltage in TLM frame */
00071 void EddystoneService::onTLMBatteryVoltageUpdate(TlmUpdateCallback_t tlmBatteryVoltageCallbackIn)
00072 {
00073     tlmBatteryVoltageCallback = tlmBatteryVoltageCallbackIn;
00074 }
00075 
00076 /* Setup callback to update BeaconTemperature in TLM frame */
00077 void EddystoneService::onTLMBeaconTemperatureUpdate(TlmUpdateCallback_t tlmBeaconTemperatureCallbackIn)
00078 {
00079     tlmBeaconTemperatureCallback = tlmBeaconTemperatureCallbackIn;
00080 }
00081 
00082 void EddystoneService::setTLMData(uint8_t tlmVersionIn)
00083 {
00084    tlmFrame.setTLMData(tlmVersionIn);
00085 }
00086 
00087 void EddystoneService::setURLData(const char *urlDataIn)
00088 {
00089     urlFrame.setURLData(urlDataIn);
00090 }
00091 
00092 void EddystoneService::setUIDData(const UIDNamespaceID_t *uidNamespaceIDIn, const UIDInstanceID_t *uidInstanceIDIn)
00093 {
00094     uidFrame.setUIDData(uidNamespaceIDIn, uidInstanceIDIn);
00095 }
00096 
00097 EddystoneService::EddystoneError_t EddystoneService::startConfigService(void)
00098 {
00099     if (operationMode == EDDYSTONE_MODE_CONFIG) {
00100         /* Nothing to do, we are already in config mode */
00101         return EDDYSTONE_ERROR_NONE;
00102     } else if (advConfigInterval == 0) {
00103         /* Nothing to do, the advertisement interval is 0 */
00104         return EDDYSTONE_ERROR_INVALID_ADVERTISING_INTERVAL;
00105     }
00106 
00107     if (operationMode == EDDYSTONE_MODE_BEACON) {
00108         ble.shutdown();
00109         /* Free unused memory */
00110         freeBeaconFrames();
00111         operationMode = EDDYSTONE_MODE_CONFIG;
00112         ble.init(this, &EddystoneService::bleInitComplete);
00113         return EDDYSTONE_ERROR_NONE;
00114     }
00115 
00116     operationMode = EDDYSTONE_MODE_CONFIG;
00117     setupConfigService();
00118     return EDDYSTONE_ERROR_NONE;
00119 }
00120 
00121 EddystoneService::EddystoneError_t EddystoneService::startBeaconService(uint16_t consecUrlFramesIn, uint16_t consecUidFramesIn, uint16_t consecTlmFramesIn)
00122 {
00123     if (operationMode == EDDYSTONE_MODE_BEACON) {
00124         /* Nothing to do, we are already in beacon mode */
00125         return EDDYSTONE_ERROR_NONE;
00126     } else if (!consecUrlFramesIn && !consecUidFramesIn && !consecTlmFramesIn) {
00127         /* Nothing to do, the user wants 0 consecutive frames of everything */
00128         return EDDYSTONE_ERROR_INVALID_CONSEC_FRAMES;
00129     } else if (!beaconPeriod) {
00130         /* Nothing to do, the period is 0 for all frames */
00131         return EDDYSTONE_ERROR_INVALID_BEACON_PERIOD;
00132     }
00133 
00134     /* Setup tracking of the current advertised frame. Note that this will
00135      * cause URL or UID frames to be advertised first!
00136      */
00137     currentAdvertisedFrame            = EDDYSTONE_FRAME_TLM;
00138     consecFrames[EDDYSTONE_FRAME_URL] = consecUrlFramesIn;
00139     consecFrames[EDDYSTONE_FRAME_UID] = consecUidFramesIn;
00140     consecFrames[EDDYSTONE_FRAME_TLM] = consecTlmFramesIn;
00141 
00142     memset(currentConsecFrames, 0, sizeof(uint16_t) * NUM_EDDYSTONE_FRAMES);
00143 
00144     if (operationMode == EDDYSTONE_MODE_CONFIG) {
00145         ble.shutdown();
00146         /* Free unused memory */
00147         freeConfigCharacteristics();
00148         operationMode = EDDYSTONE_MODE_BEACON;
00149         ble.init(this, &EddystoneService::bleInitComplete);
00150         return EDDYSTONE_ERROR_NONE;
00151     }
00152 
00153     operationMode = EDDYSTONE_MODE_BEACON;
00154     setupBeaconService();
00155     return EDDYSTONE_ERROR_NONE;
00156 }
00157 
00158 /* It is not the responsibility of the Eddystone implementation to store
00159  * the configured parameters in persistent storage since this is
00160  * platform-specific. So we provide this function that returns the
00161  * configured values that need to be stored and the main application
00162  * takes care of storing them.
00163  */
00164 void EddystoneService::getEddystoneParams(EddystoneParams_t *params)
00165 {
00166     params->lockState     = lockState;
00167     params->flags         = flags;
00168     params->txPowerMode   = txPowerMode;
00169     params->beaconPeriod  = beaconPeriod;
00170     params->tlmVersion    = tlmFrame.getTLMVersion();
00171     params->urlDataLength = urlFrame.getEncodedURLDataLength();
00172 
00173     memcpy(params->advPowerLevels, advPowerLevels,               sizeof(PowerLevels_t));
00174     memcpy(params->lock,           lock,                         sizeof(Lock_t));
00175     memcpy(params->unlock,         unlock,                       sizeof(Lock_t));
00176     memcpy(params->urlData,        urlFrame.getEncodedURLData(), urlFrame.getEncodedURLDataLength());
00177     memcpy(params->uidNamespaceID, uidFrame.getUIDNamespaceID(), sizeof(UIDNamespaceID_t));
00178     memcpy(params->uidInstanceID,  uidFrame.getUIDInstanceID(),  sizeof(UIDInstanceID_t));
00179 }
00180 
00181 /* Helper function used only once during constructing the object to avoid
00182  * duplicated code.
00183  */
00184 void EddystoneService::eddystoneConstructorHelper(const PowerLevels_t &advPowerLevelsIn,
00185                                                   const PowerLevels_t &radioPowerLevelsIn,
00186                                                   uint32_t            advConfigIntervalIn)
00187 {
00188     advConfigInterval = (advConfigIntervalIn > 0) ? correctAdvertisementPeriod(advConfigIntervalIn) : 0;
00189 
00190     memcpy(radioPowerLevels, radioPowerLevelsIn, sizeof(PowerLevels_t));
00191     memcpy(advPowerLevels,   advPowerLevelsIn,   sizeof(PowerLevels_t));
00192 
00193     /* TODO: Note that this timer is started from the time EddystoneService
00194      * is initialised and NOT from when the device is booted. So app needs
00195      * to take care that EddystoneService is one of the first things to be
00196      * started!
00197      */
00198     timeSinceBootTimer.start();
00199 }
00200 
00201 /* When changing modes, we shutdown and init the BLE instance, so
00202  * this is needed to complete the initialisation task.
00203  */
00204 void EddystoneService::bleInitComplete(BLE::InitializationCompleteCallbackContext* initContext)
00205 {
00206     if (initContext->error != BLE_ERROR_NONE) {
00207         /* Initialisation failed */
00208         return;
00209     }
00210 
00211     switch (operationMode) {
00212     case EDDYSTONE_MODE_CONFIG:
00213         setupConfigService();
00214         break;
00215     case EDDYSTONE_MODE_BEACON:
00216         setupBeaconService();
00217         break;
00218     default:
00219         /* Some error occurred */
00220         break;
00221     }
00222 }
00223 
00224 void EddystoneService::swapAdvertisedFrame(void)
00225 {
00226     /* This essentially works out which is the next frame to be swapped in
00227      * and updated the advertised packets. It will eventually terminate
00228      * and in the worst case the frame swapped in is the current advertised
00229      * frame.
00230      */
00231     while (true) {
00232         currentAdvertisedFrame = (currentAdvertisedFrame + 1) % NUM_EDDYSTONE_FRAMES;
00233 
00234         if (currentAdvertisedFrame == EDDYSTONE_FRAME_URL && consecFrames[EDDYSTONE_FRAME_URL] > 0) {
00235             updateAdvertisementPacket(rawUrlFrame, urlFrame.getRawFrameSize());
00236             return;
00237         } else if (currentAdvertisedFrame == EDDYSTONE_FRAME_UID && consecFrames[EDDYSTONE_FRAME_UID] > 0) {
00238             updateAdvertisementPacket(rawUidFrame, uidFrame.getRawFrameSize());
00239             return;
00240         } else if (currentAdvertisedFrame == EDDYSTONE_FRAME_TLM && consecFrames[EDDYSTONE_FRAME_UID] > 0) {
00241             updateRawTLMFrame();
00242             updateAdvertisementPacket(rawTlmFrame, tlmFrame.getRawFrameSize());
00243             return;
00244         }
00245     }
00246 }
00247 
00248 /* Helper function that calls user-defined functions to update Battery Voltage and Temperature (if available),
00249  * then updates the raw frame data and finally updates the actual advertised packet. This operation must be
00250  * done fairly often because the TLM frame TimeSinceBoot must have a 0.1 secs resolution according to the
00251  * Eddystone specification.
00252  */
00253 void EddystoneService::updateRawTLMFrame(void)
00254 {
00255     if (tlmBeaconTemperatureCallback != NULL) {
00256         tlmFrame.updateBeaconTemperature((*tlmBeaconTemperatureCallback)(tlmFrame.getBeaconTemperature()));
00257     }
00258     if (tlmBatteryVoltageCallback != NULL) {
00259         tlmFrame.updateBatteryVoltage((*tlmBatteryVoltageCallback)(tlmFrame.getBatteryVoltage()));
00260     }
00261     tlmFrame.updateTimeSinceBoot(timeSinceBootTimer.read_ms());
00262     tlmFrame.constructTLMFrame(rawTlmFrame);
00263 }
00264 
00265 void EddystoneService::updateAdvertisementPacket(const uint8_t* rawFrame, size_t rawFrameLength)
00266 {
00267     ble.gap().clearAdvertisingPayload();
00268     ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
00269     ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, EDDYSTONE_UUID, sizeof(EDDYSTONE_UUID));
00270     ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::SERVICE_DATA, rawFrame, rawFrameLength);
00271 }
00272 
00273 void EddystoneService::setupBeaconService(void)
00274 {
00275     /* Initialise arrays to hold constructed raw frames */
00276     if (consecFrames[EDDYSTONE_FRAME_URL] > 0) {
00277         rawUrlFrame = new uint8_t[urlFrame.getRawFrameSize()];
00278         urlFrame.constructURLFrame(rawUrlFrame, advPowerLevels[txPowerMode]);
00279     }
00280 
00281     if (consecFrames[EDDYSTONE_FRAME_UID] > 0) {
00282         rawUidFrame = new uint8_t[uidFrame.getRawFrameSize()];
00283         uidFrame.constructUIDFrame(rawUidFrame, advPowerLevels[txPowerMode]);
00284     }
00285 
00286     if (consecFrames[EDDYSTONE_FRAME_TLM] > 0) {
00287         rawTlmFrame = new uint8_t[tlmFrame.getRawFrameSize()];
00288         /* Do not initialise because we have to reconstruct every 0.1 secs */
00289     }
00290 
00291     /* Configure advertisements */
00292     ble.gap().setTxPower(radioPowerLevels[txPowerMode]);
00293     ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_NON_CONNECTABLE_UNDIRECTED);
00294     ble.gap().setAdvertisingInterval(beaconPeriod);
00295     ble.gap().onRadioNotification(this, &EddystoneService::radioNotificationCallback);
00296     ble.gap().initRadioNotification();
00297 
00298     /* Set advertisement packet payload */
00299     swapAdvertisedFrame();
00300 
00301     /* Start advertising */
00302     ble.gap().startAdvertising();
00303 }
00304 
00305 void EddystoneService::setupConfigService(void)
00306 {
00307     lockStateChar      = new ReadOnlyGattCharacteristic<bool>(UUID_LOCK_STATE_CHAR, &lockState);
00308     lockChar           = new WriteOnlyArrayGattCharacteristic<uint8_t, sizeof(Lock_t)>(UUID_LOCK_CHAR, lock);
00309     unlockChar         = new WriteOnlyArrayGattCharacteristic<uint8_t, sizeof(Lock_t)>(UUID_UNLOCK_CHAR, unlock);
00310     urlDataChar        = new GattCharacteristic(UUID_URL_DATA_CHAR, urlFrame.getEncodedURLData(), 0, URL_DATA_MAX, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE);
00311     flagsChar          = new ReadWriteGattCharacteristic<uint8_t>(UUID_FLAGS_CHAR, &flags);
00312     advPowerLevelsChar = new ReadWriteArrayGattCharacteristic<int8_t, sizeof(PowerLevels_t)>(UUID_ADV_POWER_LEVELS_CHAR, advPowerLevels);
00313     txPowerModeChar    = new ReadWriteGattCharacteristic<uint8_t>(UUID_TX_POWER_MODE_CHAR, &txPowerMode);
00314     beaconPeriodChar   = new ReadWriteGattCharacteristic<uint16_t>(UUID_BEACON_PERIOD_CHAR, &beaconPeriod);
00315     resetChar          = new WriteOnlyGattCharacteristic<bool>(UUID_RESET_CHAR, &resetFlag);
00316 
00317     lockChar->setWriteAuthorizationCallback(this, &EddystoneService::lockAuthorizationCallback);
00318     unlockChar->setWriteAuthorizationCallback(this, &EddystoneService::unlockAuthorizationCallback);
00319     urlDataChar->setWriteAuthorizationCallback(this, &EddystoneService::urlDataWriteAuthorizationCallback);
00320     flagsChar->setWriteAuthorizationCallback(this, &EddystoneService::basicAuthorizationCallback<uint8_t>);
00321     advPowerLevelsChar->setWriteAuthorizationCallback(this, &EddystoneService::basicAuthorizationCallback<PowerLevels_t>);
00322     txPowerModeChar->setWriteAuthorizationCallback(this, &EddystoneService::powerModeAuthorizationCallback);
00323     beaconPeriodChar->setWriteAuthorizationCallback(this, &EddystoneService::basicAuthorizationCallback<uint16_t>);
00324     resetChar->setWriteAuthorizationCallback(this, &EddystoneService::basicAuthorizationCallback<bool>);
00325 
00326     charTable[0] = lockStateChar;
00327     charTable[1] = lockChar;
00328     charTable[2] = unlockChar;
00329     charTable[3] = urlDataChar;
00330     charTable[4] = flagsChar;
00331     charTable[5] = advPowerLevelsChar;
00332     charTable[6] = txPowerModeChar;
00333     charTable[7] = beaconPeriodChar;
00334     charTable[8] = resetChar;
00335 
00336     GattService configService(UUID_URL_BEACON_SERVICE, charTable, sizeof(charTable) / sizeof(GattCharacteristic *));
00337 
00338     ble.gattServer().addService(configService);
00339     ble.gattServer().onDataWritten(this, &EddystoneService::onDataWrittenCallback);
00340     updateCharacteristicValues();
00341     setupEddystoneConfigAdvertisements();
00342 }
00343 
00344 void EddystoneService::freeConfigCharacteristics(void)
00345 {
00346     delete lockStateChar;
00347     delete lockChar;
00348     delete unlockChar;
00349     delete urlDataChar;
00350     delete flagsChar;
00351     delete advPowerLevelsChar;
00352     delete txPowerModeChar;
00353     delete beaconPeriodChar;
00354     delete resetChar;
00355 }
00356 
00357 void EddystoneService::freeBeaconFrames(void)
00358 {
00359     delete[] rawUrlFrame;
00360     delete[] rawUidFrame;
00361     delete[] rawTlmFrame;
00362 }
00363 
00364 void EddystoneService::radioNotificationCallback(bool radioActive)
00365 {
00366     if (radioActive) {
00367         /* Do nothing */
00368         return;
00369     }
00370 
00371     tlmFrame.updatePduCount();
00372     currentConsecFrames[currentAdvertisedFrame]++;
00373 
00374     if (consecFrames[currentAdvertisedFrame] > currentConsecFrames[currentAdvertisedFrame]) {
00375         if (currentAdvertisedFrame == EDDYSTONE_FRAME_TLM) {
00376             /* Update the TLM frame otherwise we will not meet the 0.1 secs resolution of
00377              * the Eddystone specification.
00378              */
00379             updateRawTLMFrame();
00380             updateAdvertisementPacket(rawTlmFrame, tlmFrame.getRawFrameSize());
00381         }
00382         /* Keep advertising the same frame */
00383         return;
00384     }
00385 
00386     currentConsecFrames[currentAdvertisedFrame] = 0;
00387 
00388 #ifdef YOTTA_CFG_MBED_OS
00389     minar::Scheduler::postCallback(this, &EddystoneService::swapAdvertisedFrame);
00390 #else
00391     swapAdvertisedFrameTimeout.attach_us(this, &EddystoneService::swapAdvertisedFrame, 1);
00392 #endif
00393 }
00394 
00395 /*
00396  * Internal helper function used to update the GATT database following any
00397  * change to the internal state of the service object.
00398  */
00399 void EddystoneService::updateCharacteristicValues(void)
00400 {
00401     ble.gattServer().write(lockStateChar->getValueHandle(), reinterpret_cast<uint8_t *>(&lockState), sizeof(bool));
00402     ble.gattServer().write(urlDataChar->getValueHandle(), urlFrame.getEncodedURLData(), urlFrame.getEncodedURLDataLength());
00403     ble.gattServer().write(flagsChar->getValueHandle(), &flags, sizeof(uint8_t));
00404     ble.gattServer().write(beaconPeriodChar->getValueHandle(), reinterpret_cast<uint8_t *>(&beaconPeriod), sizeof(uint16_t));
00405     ble.gattServer().write(txPowerModeChar->getValueHandle(), &txPowerMode, sizeof(uint8_t));
00406     ble.gattServer().write(advPowerLevelsChar->getValueHandle(), reinterpret_cast<uint8_t *>(advPowerLevels), sizeof(PowerLevels_t));
00407     ble.gattServer().write(lockChar->getValueHandle(), lock, sizeof(PowerLevels_t));
00408     ble.gattServer().write(unlockChar->getValueHandle(), unlock, sizeof(PowerLevels_t));
00409 }
00410 
00411 void EddystoneService::setupEddystoneConfigAdvertisements(void)
00412 {
00413     ble.gap().clearAdvertisingPayload();
00414     ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
00415 
00416     /* UUID is in different order in the ADV frame (!) */
00417     uint8_t reversedServiceUUID[sizeof(UUID_URL_BEACON_SERVICE)];
00418     for (size_t i = 0; i < sizeof(UUID_URL_BEACON_SERVICE); i++) {
00419         reversedServiceUUID[i] = UUID_URL_BEACON_SERVICE[sizeof(UUID_URL_BEACON_SERVICE) - i - 1];
00420     }
00421     ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_128BIT_SERVICE_IDS, reversedServiceUUID, sizeof(reversedServiceUUID));
00422     ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::GENERIC_TAG);
00423     ble.gap().accumulateScanResponse(GapAdvertisingData::COMPLETE_LOCAL_NAME, reinterpret_cast<const uint8_t *>(&DEVICE_NAME), sizeof(DEVICE_NAME));
00424     ble.gap().accumulateScanResponse(
00425         GapAdvertisingData::TX_POWER_LEVEL,
00426         reinterpret_cast<uint8_t *>(&advPowerLevels[TX_POWER_MODE_LOW]),
00427         sizeof(uint8_t));
00428 
00429     ble.gap().setTxPower(radioPowerLevels[txPowerMode]);
00430     ble.gap().setDeviceName(reinterpret_cast<const uint8_t *>(&DEVICE_NAME));
00431     ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
00432     ble.gap().setAdvertisingInterval(advConfigInterval);
00433     ble.gap().startAdvertising();
00434 }
00435 
00436 void EddystoneService::lockAuthorizationCallback(GattWriteAuthCallbackParams *authParams)
00437 {
00438     if (lockState) {
00439         authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION;
00440     } else if (authParams->len != sizeof(Lock_t)) {
00441         authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH;
00442     } else if (authParams->offset != 0) {
00443         authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET;
00444     } else {
00445         authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
00446     }
00447 }
00448 
00449 void EddystoneService::unlockAuthorizationCallback(GattWriteAuthCallbackParams *authParams)
00450 {
00451     if (!lockState && (authParams->len == sizeof(Lock_t))) {
00452         authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
00453     } else if (authParams->len != sizeof(Lock_t)) {
00454         authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH;
00455     } else if (authParams->offset != 0) {
00456         authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET;
00457     } else if (memcmp(authParams->data, lock, sizeof(Lock_t)) != 0) {
00458         authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION;
00459     } else {
00460         authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
00461     }
00462 }
00463 
00464 void EddystoneService::urlDataWriteAuthorizationCallback(GattWriteAuthCallbackParams *authParams)
00465 {
00466     if (lockState) {
00467         authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION;
00468     } else if (authParams->offset != 0) {
00469         authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET;
00470     } else {
00471         authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
00472     }
00473 }
00474 
00475 void EddystoneService::powerModeAuthorizationCallback(GattWriteAuthCallbackParams *authParams)
00476 {
00477     if (lockState) {
00478         authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION;
00479     } else if (authParams->len != sizeof(uint8_t)) {
00480         authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH;
00481     } else if (authParams->offset != 0) {
00482         authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET;
00483     } else if (*((uint8_t *)authParams->data) >= NUM_POWER_MODES) {
00484         authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_WRITE_NOT_PERMITTED;
00485     } else {
00486         authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
00487     }
00488 }
00489 
00490 template <typename T>
00491 void EddystoneService::basicAuthorizationCallback(GattWriteAuthCallbackParams *authParams)
00492 {
00493     if (lockState) {
00494         authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION;
00495     } else if (authParams->len != sizeof(T)) {
00496         authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH;
00497     } else if (authParams->offset != 0) {
00498         authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET;
00499     } else {
00500         authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
00501     }
00502 }
00503 
00504 /*
00505  * This callback is invoked when a GATT client attempts to modify any of the
00506  * characteristics of this service. Attempts to do so are also applied to
00507  * the internal state of this service object.
00508  */
00509 void EddystoneService::onDataWrittenCallback(const GattWriteCallbackParams *writeParams)
00510 {
00511     uint16_t handle = writeParams->handle;
00512 
00513     if (handle == lockChar->getValueHandle()) {
00514         memcpy(lock, writeParams->data, sizeof(Lock_t));
00515         /* Set the state to be locked by the lock code (note: zeros are a valid lock) */
00516         lockState = true;
00517         ble.gattServer().write(lockChar->getValueHandle(), lock, sizeof(PowerLevels_t));
00518         ble.gattServer().write(lockStateChar->getValueHandle(), reinterpret_cast<uint8_t *>(&lockState), sizeof(bool));
00519     } else if (handle == unlockChar->getValueHandle()) {
00520         /* Validated earlier */
00521         lockState = false;
00522         ble.gattServer().write(unlockChar->getValueHandle(), unlock, sizeof(PowerLevels_t));
00523         ble.gattServer().write(lockStateChar->getValueHandle(), reinterpret_cast<uint8_t *>(&lockState), sizeof(bool));
00524     } else if (handle == urlDataChar->getValueHandle()) {
00525         urlFrame.setEncodedURLData(writeParams->data, writeParams->len);
00526         ble.gattServer().write(urlDataChar->getValueHandle(), urlFrame.getEncodedURLData(), urlFrame.getEncodedURLDataLength());
00527     } else if (handle == flagsChar->getValueHandle()) {
00528         flags = *(writeParams->data);
00529         ble.gattServer().write(flagsChar->getValueHandle(), &flags, sizeof(uint8_t));
00530     } else if (handle == advPowerLevelsChar->getValueHandle()) {
00531         memcpy(advPowerLevels, writeParams->data, sizeof(PowerLevels_t));
00532         ble.gattServer().write(advPowerLevelsChar->getValueHandle(), reinterpret_cast<uint8_t *>(advPowerLevels), sizeof(PowerLevels_t));
00533     } else if (handle == txPowerModeChar->getValueHandle()) {
00534         txPowerMode = *(writeParams->data);
00535         ble.gattServer().write(txPowerModeChar->getValueHandle(), &txPowerMode, sizeof(uint8_t));
00536     } else if (handle == beaconPeriodChar->getValueHandle()) {
00537         uint16_t tmpBeaconPeriod = correctAdvertisementPeriod(*((uint16_t *)(writeParams->data)));
00538         if (tmpBeaconPeriod != beaconPeriod) {
00539             beaconPeriod = tmpBeaconPeriod;
00540             ble.gattServer().write(beaconPeriodChar->getValueHandle(), reinterpret_cast<uint8_t *>(&beaconPeriod), sizeof(uint16_t));
00541         }
00542     } else if (handle == resetChar->getValueHandle() && (*((uint8_t *)writeParams->data) != 0)) {
00543         /* Reset characteristics to default values */
00544         flags        = 0;
00545         txPowerMode  = TX_POWER_MODE_LOW;
00546         beaconPeriod = DEFAULT_BEACON_PERIOD_MSEC;
00547 
00548         urlFrame.setURLData(DEFAULT_URL);
00549         memset(lock, 0, sizeof(Lock_t));
00550 
00551         ble.gattServer().write(urlDataChar->getValueHandle(), urlFrame.getEncodedURLData(), urlFrame.getEncodedURLDataLength());
00552         ble.gattServer().write(flagsChar->getValueHandle(), &flags, sizeof(uint8_t));
00553         ble.gattServer().write(txPowerModeChar->getValueHandle(), &txPowerMode, sizeof(uint8_t));
00554         ble.gattServer().write(beaconPeriodChar->getValueHandle(), reinterpret_cast<uint8_t *>(&beaconPeriod), sizeof(uint16_t));
00555         ble.gattServer().write(lockChar->getValueHandle(), lock, sizeof(PowerLevels_t));
00556     }
00557 }
00558 
00559 uint16_t EddystoneService::correctAdvertisementPeriod(uint16_t beaconPeriodIn) const
00560 {
00561     /* Re-map beaconPeriod to within permissible bounds if necessary. */
00562     if (beaconPeriodIn != 0) {
00563         if (beaconPeriodIn < ble.gap().getMinAdvertisingInterval()) {
00564             return ble.gap().getMinAdvertisingInterval();
00565         } else if (beaconPeriodIn > ble.gap().getMaxAdvertisingInterval()) {
00566             return ble.gap().getMaxAdvertisingInterval();
00567         }
00568     }
00569     return beaconPeriodIn;
00570 }