BLE EddystoneService example

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 &radioPowerLevelsIn,
00023                                    EventQueue          &evQ,
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     rawUrlFrame(NULL),
00032     rawUidFrame(NULL),
00033     rawTlmFrame(NULL),
00034     tlmBatteryVoltageCallback(NULL),
00035     tlmBeaconTemperatureCallback(NULL),
00036     uidFrameCallbackHandle(),
00037     urlFrameCallbackHandle(),
00038     tlmFrameCallbackHandle(),
00039     radioManagerCallbackHandle(),
00040     deviceName(DEFAULT_DEVICE_NAME),
00041     eventQueue(evQ)
00042 {
00043     lockState      = paramsIn.lockState;
00044     flags          = paramsIn.flags;
00045     txPowerMode    = paramsIn.txPowerMode;
00046     urlFramePeriod = correctAdvertisementPeriod(paramsIn.urlFramePeriod);
00047     uidFramePeriod = correctAdvertisementPeriod(paramsIn.uidFramePeriod);
00048     tlmFramePeriod = correctAdvertisementPeriod(paramsIn.tlmFramePeriod);
00049 
00050     memcpy(lock,   paramsIn.lock,   sizeof(Lock_t));
00051     memcpy(unlock, paramsIn.unlock, sizeof(Lock_t));
00052 
00053     eddystoneConstructorHelper(paramsIn.advPowerLevels, radioPowerLevelsIn, advConfigIntervalIn);
00054 }
00055 
00056 /* When using this constructor we need to call setURLData,
00057  * setTMLData and setUIDData to initialise values manually
00058  */
00059 EddystoneService::EddystoneService(BLE                 &bleIn,
00060                                    const PowerLevels_t &advPowerLevelsIn,
00061                                    const PowerLevels_t &radioPowerLevelsIn,
00062                                    EventQueue          &evQ,
00063                                    uint32_t            advConfigIntervalIn) :
00064     ble(bleIn),
00065     operationMode(EDDYSTONE_MODE_NONE),
00066     urlFrame(),
00067     uidFrame(),
00068     tlmFrame(),
00069     lockState(false),
00070     resetFlag(false),
00071     lock(),
00072     unlock(),
00073     flags(0),
00074     txPowerMode(0),
00075     urlFramePeriod(DEFAULT_URL_FRAME_PERIOD_MSEC),
00076     uidFramePeriod(DEFAULT_UID_FRAME_PERIOD_MSEC),
00077     tlmFramePeriod(DEFAULT_TLM_FRAME_PERIOD_MSEC),
00078     rawUrlFrame(NULL),
00079     rawUidFrame(NULL),
00080     rawTlmFrame(NULL),
00081     tlmBatteryVoltageCallback(NULL),
00082     tlmBeaconTemperatureCallback(NULL),
00083     uidFrameCallbackHandle(),
00084     urlFrameCallbackHandle(),
00085     tlmFrameCallbackHandle(),
00086     radioManagerCallbackHandle(),
00087     deviceName(DEFAULT_DEVICE_NAME),
00088     eventQueue(evQ)
00089 {
00090     eddystoneConstructorHelper(advPowerLevelsIn, radioPowerLevelsIn, advConfigIntervalIn);
00091 }
00092 
00093 /* Setup callback to update BatteryVoltage in TLM frame */
00094 void EddystoneService::onTLMBatteryVoltageUpdate(TlmUpdateCallback_t tlmBatteryVoltageCallbackIn)
00095 {
00096     tlmBatteryVoltageCallback = tlmBatteryVoltageCallbackIn;
00097 }
00098 
00099 /* Setup callback to update BeaconTemperature in TLM frame */
00100 void EddystoneService::onTLMBeaconTemperatureUpdate(TlmUpdateCallback_t tlmBeaconTemperatureCallbackIn)
00101 {
00102     tlmBeaconTemperatureCallback = tlmBeaconTemperatureCallbackIn;
00103 }
00104 
00105 void EddystoneService::setTLMData(uint8_t tlmVersionIn)
00106 {
00107    tlmFrame.setTLMData(tlmVersionIn);
00108 }
00109 
00110 void EddystoneService::setURLData(const char *urlDataIn)
00111 {
00112     urlFrame.setURLData(urlDataIn);
00113 }
00114 
00115 void EddystoneService::setUIDData(const UIDNamespaceID_t &uidNamespaceIDIn, const UIDInstanceID_t &uidInstanceIDIn)
00116 {
00117     uidFrame.setUIDData(uidNamespaceIDIn, uidInstanceIDIn);
00118 }
00119 
00120 EddystoneService::EddystoneError_t EddystoneService::startConfigService(void)
00121 {
00122     if (operationMode == EDDYSTONE_MODE_CONFIG) {
00123         /* Nothing to do, we are already in config mode */
00124         return EDDYSTONE_ERROR_NONE;
00125     } else if (advConfigInterval == 0) {
00126         /* Nothing to do, the advertisement interval is 0 */
00127         return EDDYSTONE_ERROR_INVALID_ADVERTISING_INTERVAL;
00128     }
00129 
00130     if (operationMode == EDDYSTONE_MODE_BEACON) {
00131         ble.shutdown();
00132         stopBeaconService();
00133     }
00134 
00135     if (!ble.hasInitialized()) {
00136         operationMode = EDDYSTONE_MODE_CONFIG;
00137         ble.init(this, &EddystoneService::bleInitComplete);
00138         /* Set the device name once more */
00139         ble.gap().setDeviceName(reinterpret_cast<const uint8_t *>(deviceName));
00140         return EDDYSTONE_ERROR_NONE;
00141     }
00142 
00143     operationMode = EDDYSTONE_MODE_CONFIG;
00144     setupConfigService();
00145     return EDDYSTONE_ERROR_NONE;
00146 }
00147 
00148 EddystoneService::EddystoneError_t EddystoneService::startBeaconService(void)
00149 {
00150     if (operationMode == EDDYSTONE_MODE_BEACON) {
00151         /* Nothing to do, we are already in beacon mode */
00152         return EDDYSTONE_ERROR_NONE;
00153     } else if (!urlFramePeriod && !uidFramePeriod && !tlmFramePeriod) {
00154         /* Nothing to do, the period is 0 for all frames */
00155         return EDDYSTONE_ERROR_INVALID_ADVERTISING_INTERVAL;
00156     }
00157 
00158     if (operationMode == EDDYSTONE_MODE_CONFIG) {
00159         ble.shutdown();
00160         /* Free unused memory */
00161         freeConfigCharacteristics();
00162     }
00163 
00164     if (!ble.hasInitialized()) {
00165         operationMode = EDDYSTONE_MODE_BEACON;
00166         ble.init(this, &EddystoneService::bleInitComplete);
00167         /* Set the device name once more */
00168         ble.gap().setDeviceName(reinterpret_cast<const uint8_t *>(deviceName));
00169         return EDDYSTONE_ERROR_NONE;
00170     }
00171 
00172     operationMode = EDDYSTONE_MODE_BEACON;
00173     setupBeaconService();
00174 
00175     return EDDYSTONE_ERROR_NONE;
00176 }
00177 
00178 EddystoneService::EddystoneError_t EddystoneService::stopCurrentService(void)
00179 {
00180     switch (operationMode) {
00181     case EDDYSTONE_MODE_NONE:
00182         return EDDYSTONE_ERROR_INVALID_STATE;
00183     case EDDYSTONE_MODE_BEACON:
00184         ble.shutdown();
00185         stopBeaconService();
00186         break;
00187     case EDDYSTONE_MODE_CONFIG:
00188         ble.shutdown();
00189         freeConfigCharacteristics();
00190         break;
00191     default:
00192         /* Some error occurred */
00193         error("Invalid EddystonService mode");
00194         break;
00195     }
00196     operationMode = EDDYSTONE_MODE_NONE;
00197     /* Currently on some platforms, the BLE stack handles power management,
00198      * so we should bring it up again, but not configure it.
00199      * Once the system sleep without BLE initialised is fixed, remove this
00200      */
00201     ble.init(this, &EddystoneService::bleInitComplete);
00202 
00203     return EDDYSTONE_ERROR_NONE;
00204 }
00205 
00206 ble_error_t EddystoneService::setCompleteDeviceName(const char *deviceNameIn)
00207 {
00208     /* Make sure the device name is safe */
00209     ble_error_t error = ble.gap().setDeviceName(reinterpret_cast<const uint8_t *>(deviceNameIn));
00210     if (error == BLE_ERROR_NONE) {
00211         deviceName = deviceNameIn;
00212         if (operationMode == EDDYSTONE_MODE_CONFIG) {
00213             /* Need to update the advertising packets to the new name */
00214             setupEddystoneConfigScanResponse();
00215         }
00216     }
00217 
00218     return error;
00219 }
00220 
00221 /* It is not the responsibility of the Eddystone implementation to store
00222  * the configured parameters in persistent storage since this is
00223  * platform-specific. So we provide this function that returns the
00224  * configured values that need to be stored and the main application
00225  * takes care of storing them.
00226  */
00227 void EddystoneService::getEddystoneParams(EddystoneParams_t &params)
00228 {
00229     params.lockState      = lockState;
00230     params.flags          = flags;
00231     params.txPowerMode    = txPowerMode;
00232     params.urlFramePeriod = urlFramePeriod;
00233     params.tlmFramePeriod = tlmFramePeriod;
00234     params.uidFramePeriod = uidFramePeriod;
00235     params.tlmVersion     = tlmFrame.getTLMVersion();
00236     params.urlDataLength  = urlFrame.getEncodedURLDataLength();
00237 
00238     memcpy(params.advPowerLevels, advPowerLevels,               sizeof(PowerLevels_t));
00239     memcpy(params.lock,           lock,                         sizeof(Lock_t));
00240     memcpy(params.unlock,         unlock,                       sizeof(Lock_t));
00241     memcpy(params.urlData,        urlFrame.getEncodedURLData(), urlFrame.getEncodedURLDataLength());
00242     memcpy(params.uidNamespaceID, uidFrame.getUIDNamespaceID(), sizeof(UIDNamespaceID_t));
00243     memcpy(params.uidInstanceID,  uidFrame.getUIDInstanceID(),  sizeof(UIDInstanceID_t));
00244 }
00245 
00246 /* Helper function used only once during constructing the object to avoid
00247  * duplicated code.
00248  */
00249 void EddystoneService::eddystoneConstructorHelper(const PowerLevels_t &advPowerLevelsIn,
00250                                                   const PowerLevels_t &radioPowerLevelsIn,
00251                                                   uint32_t            advConfigIntervalIn)
00252 {
00253     /* We cannot use correctAdvertisementPeriod() for this check because the function
00254      * call to get the minimum advertising interval in the BLE API is different for
00255      * connectable and non-connectable advertising.
00256      */
00257     if (advConfigIntervalIn != 0) {
00258         if (advConfigIntervalIn < ble.gap().getMinAdvertisingInterval()) {
00259             advConfigInterval = ble.gap().getMinAdvertisingInterval();
00260         } else if (advConfigIntervalIn > ble.gap().getMaxAdvertisingInterval()) {
00261             advConfigInterval = ble.gap().getMaxAdvertisingInterval();
00262         } else {
00263             advConfigInterval = advConfigIntervalIn;
00264         }
00265     }
00266 
00267     memcpy(radioPowerLevels, radioPowerLevelsIn, sizeof(PowerLevels_t));
00268     memcpy(advPowerLevels,   advPowerLevelsIn,   sizeof(PowerLevels_t));
00269 
00270     /* TODO: Note that this timer is started from the time EddystoneService
00271      * is initialised and NOT from when the device is booted. So app needs
00272      * to take care that EddystoneService is one of the first things to be
00273      * started!
00274      */
00275     timeSinceBootTimer.start();
00276 
00277     /* Set the device name at startup */
00278     ble.gap().setDeviceName(reinterpret_cast<const uint8_t *>(deviceName));
00279 }
00280 
00281 /* When changing modes, we shutdown and init the BLE instance, so
00282  * this is needed to complete the initialisation task.
00283  */
00284 void EddystoneService::bleInitComplete(BLE::InitializationCompleteCallbackContext* initContext)
00285 {
00286     if (initContext->error != BLE_ERROR_NONE) {
00287         /* Initialisation failed */
00288         return;
00289     }
00290 
00291     switch (operationMode) {
00292     case EDDYSTONE_MODE_CONFIG:
00293         setupConfigService();
00294         break;
00295     case EDDYSTONE_MODE_BEACON:
00296         setupBeaconService();
00297         break;
00298     case EDDYSTONE_MODE_NONE:
00299         /* We don't need to do anything here, but it isn't an error */
00300         break;
00301     default:
00302         /* Some error occurred */
00303         error("Invalid EddystonService mode");
00304         break;
00305     }
00306 }
00307 
00308 void EddystoneService::swapAdvertisedFrame(FrameType frameType)
00309 {
00310     switch(frameType) {
00311     case EDDYSTONE_FRAME_URL:
00312         updateAdvertisementPacket(rawUrlFrame, urlFrame.getRawFrameSize());
00313         break;
00314     case EDDYSTONE_FRAME_UID:
00315         updateAdvertisementPacket(rawUidFrame, uidFrame.getRawFrameSize());
00316         break;
00317     case EDDYSTONE_FRAME_TLM:
00318         updateRawTLMFrame();
00319         updateAdvertisementPacket(rawTlmFrame, tlmFrame.getRawFrameSize());
00320         break;
00321     default:
00322         /* Some error occurred */
00323         error("Frame to swap in does not specify a valid type");
00324         break;
00325     }
00326 }
00327 
00328 /* Helper function that calls user-defined functions to update Battery Voltage and Temperature (if available),
00329  * then updates the raw frame data and finally updates the actual advertised packet. This operation must be
00330  * done fairly often because the TLM frame TimeSinceBoot must have a 0.1 secs resolution according to the
00331  * Eddystone specification.
00332  */
00333 void EddystoneService::updateRawTLMFrame(void)
00334 {
00335     if (tlmBeaconTemperatureCallback != NULL) {
00336         tlmFrame.updateBeaconTemperature((*tlmBeaconTemperatureCallback)(tlmFrame.getBeaconTemperature()));
00337     }
00338     if (tlmBatteryVoltageCallback != NULL) {
00339         tlmFrame.updateBatteryVoltage((*tlmBatteryVoltageCallback)(tlmFrame.getBatteryVoltage()));
00340     }
00341     tlmFrame.updateTimeSinceBoot(timeSinceBootTimer.read_ms());
00342     tlmFrame.constructTLMFrame(rawTlmFrame);
00343 }
00344 
00345 void EddystoneService::updateAdvertisementPacket(const uint8_t* rawFrame, size_t rawFrameLength)
00346 {
00347     ble.gap().clearAdvertisingPayload();
00348     ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
00349     ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, EDDYSTONE_UUID, sizeof(EDDYSTONE_UUID));
00350     ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::SERVICE_DATA, rawFrame, rawFrameLength);
00351 }
00352 
00353 void EddystoneService::setupBeaconService(void)
00354 {
00355     /* Initialise arrays to hold constructed raw frames */
00356     if (urlFramePeriod) {
00357         rawUrlFrame = new uint8_t[urlFrame.getRawFrameSize()];
00358         urlFrame.constructURLFrame(rawUrlFrame, advPowerLevels[txPowerMode]);
00359     }
00360 
00361     if (uidFramePeriod) {
00362         rawUidFrame = new uint8_t[uidFrame.getRawFrameSize()];
00363         uidFrame.constructUIDFrame(rawUidFrame, advPowerLevels[txPowerMode]);
00364     }
00365 
00366     if (tlmFramePeriod) {
00367         rawTlmFrame = new uint8_t[tlmFrame.getRawFrameSize()];
00368         /* Do not initialise because we have to reconstruct every 0.1 secs */
00369     }
00370 
00371     /* Configure advertisements */
00372     ble.gap().setTxPower(radioPowerLevels[txPowerMode]);
00373     ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_NON_CONNECTABLE_UNDIRECTED);
00374     ble.gap().setAdvertisingInterval(ble.gap().getMaxAdvertisingInterval());
00375 
00376     /* Make sure the queue is currently empty */
00377     advFrameQueue.reset();
00378     /* Setup callbacks to periodically add frames to be advertised to the queue and
00379      * add initial frame so that we have something to advertise on startup */
00380     if (uidFramePeriod) {
00381         advFrameQueue.push(EDDYSTONE_FRAME_UID);
00382         uidFrameCallbackHandle = eventQueue.call_every(
00383             uidFramePeriod,
00384             Callback<void(FrameType)>(this, &EddystoneService::enqueueFrame),
00385             EDDYSTONE_FRAME_UID
00386         );
00387     }
00388     if (tlmFramePeriod) {
00389         advFrameQueue.push(EDDYSTONE_FRAME_TLM);
00390         tlmFrameCallbackHandle = eventQueue.call_every(
00391             tlmFramePeriod,
00392             Callback<void(FrameType)>(this, &EddystoneService::enqueueFrame),
00393             EDDYSTONE_FRAME_TLM
00394         );
00395     }
00396     if (urlFramePeriod) {
00397         advFrameQueue.push(EDDYSTONE_FRAME_URL);
00398         tlmFrameCallbackHandle = eventQueue.call_every(
00399             urlFramePeriod,
00400             Callback<void(FrameType)>(this, &EddystoneService::enqueueFrame),
00401             EDDYSTONE_FRAME_URL
00402         );
00403     }
00404 
00405     /* Start advertising */
00406     manageRadio();
00407 }
00408 
00409 void EddystoneService::enqueueFrame(FrameType frameType)
00410 {
00411     advFrameQueue.push(frameType);
00412     if (!radioManagerCallbackHandle) {
00413         /* Advertising stopped and there is not callback posted in the scheduler. Just
00414          * execute the manager to resume advertising */
00415         manageRadio();
00416     }
00417 }
00418 
00419 void EddystoneService::manageRadio(void)
00420 {
00421     FrameType frameType;
00422     uint32_t  startTimeManageRadio = timeSinceBootTimer.read_ms();
00423 
00424     /* Signal that there is currently no callback posted */
00425     radioManagerCallbackHandle = 0;
00426 
00427     if (advFrameQueue.pop(frameType)) {
00428         /* We have something to advertise */
00429         if (ble.gap().getState().advertising) {
00430             ble.gap().stopAdvertising();
00431         }
00432         swapAdvertisedFrame(frameType);
00433         ble.gap().startAdvertising();
00434 
00435         /* Increase the advertised packet count in TLM frame */
00436         tlmFrame.updatePduCount();
00437 
00438         /* Post a callback to itself to stop the advertisement or pop the next
00439          * frame from the queue. However, take into account the time taken to
00440          * swap in this frame. */
00441         radioManagerCallbackHandle = eventQueue.call_in(
00442             ble.gap().getMinNonConnectableAdvertisingInterval() - (timeSinceBootTimer.read_ms() - startTimeManageRadio),
00443             Callback<void()>(this, &EddystoneService::manageRadio)
00444         );
00445     } else if (ble.gap().getState().advertising) {
00446         /* Nothing else to advertise, stop advertising and do not schedule any callbacks */
00447         ble.gap().stopAdvertising();
00448     }
00449 }
00450 
00451 void EddystoneService::setupConfigService(void)
00452 {
00453     lockStateChar      = new ReadOnlyGattCharacteristic<bool>(UUID_LOCK_STATE_CHAR, &lockState);
00454     lockChar           = new WriteOnlyArrayGattCharacteristic<uint8_t, sizeof(Lock_t)>(UUID_LOCK_CHAR, lock);
00455     unlockChar         = new WriteOnlyArrayGattCharacteristic<uint8_t, sizeof(Lock_t)>(UUID_UNLOCK_CHAR, unlock);
00456     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);
00457     flagsChar          = new ReadWriteGattCharacteristic<uint8_t>(UUID_FLAGS_CHAR, &flags);
00458     advPowerLevelsChar = new ReadWriteArrayGattCharacteristic<int8_t, sizeof(PowerLevels_t)>(UUID_ADV_POWER_LEVELS_CHAR, advPowerLevels);
00459     txPowerModeChar    = new ReadWriteGattCharacteristic<uint8_t>(UUID_TX_POWER_MODE_CHAR, &txPowerMode);
00460     beaconPeriodChar   = new ReadWriteGattCharacteristic<uint16_t>(UUID_BEACON_PERIOD_CHAR, &urlFramePeriod);
00461     resetChar          = new WriteOnlyGattCharacteristic<bool>(UUID_RESET_CHAR, &resetFlag);
00462 
00463     lockChar->setWriteAuthorizationCallback(this, &EddystoneService::lockAuthorizationCallback);
00464     unlockChar->setWriteAuthorizationCallback(this, &EddystoneService::unlockAuthorizationCallback);
00465     urlDataChar->setWriteAuthorizationCallback(this, &EddystoneService::urlDataWriteAuthorizationCallback);
00466     flagsChar->setWriteAuthorizationCallback(this, &EddystoneService::basicAuthorizationCallback<uint8_t>);
00467     advPowerLevelsChar->setWriteAuthorizationCallback(this, &EddystoneService::basicAuthorizationCallback<PowerLevels_t>);
00468     txPowerModeChar->setWriteAuthorizationCallback(this, &EddystoneService::powerModeAuthorizationCallback);
00469     beaconPeriodChar->setWriteAuthorizationCallback(this, &EddystoneService::basicAuthorizationCallback<uint16_t>);
00470     resetChar->setWriteAuthorizationCallback(this, &EddystoneService::basicAuthorizationCallback<bool>);
00471 
00472     charTable[0] = lockStateChar;
00473     charTable[1] = lockChar;
00474     charTable[2] = unlockChar;
00475     charTable[3] = urlDataChar;
00476     charTable[4] = flagsChar;
00477     charTable[5] = advPowerLevelsChar;
00478     charTable[6] = txPowerModeChar;
00479     charTable[7] = beaconPeriodChar;
00480     charTable[8] = resetChar;
00481 
00482     GattService configService(UUID_URL_BEACON_SERVICE, charTable, sizeof(charTable) / sizeof(GattCharacteristic *));
00483 
00484     ble.gattServer().addService(configService);
00485     ble.gattServer().onDataWritten(this, &EddystoneService::onDataWrittenCallback);
00486     updateCharacteristicValues();
00487     setupEddystoneConfigAdvertisements();
00488 }
00489 
00490 void EddystoneService::freeConfigCharacteristics(void)
00491 {
00492     delete lockStateChar;
00493     delete lockChar;
00494     delete unlockChar;
00495     delete urlDataChar;
00496     delete flagsChar;
00497     delete advPowerLevelsChar;
00498     delete txPowerModeChar;
00499     delete beaconPeriodChar;
00500     delete resetChar;
00501 }
00502 
00503 void EddystoneService::stopBeaconService(void)
00504 {
00505     /* Free unused memory */
00506     if (rawUrlFrame) {
00507         delete[] rawUrlFrame;
00508         rawUrlFrame = NULL;
00509     }
00510     if (rawUidFrame) {
00511         delete[] rawUidFrame;
00512         rawUidFrame = NULL;
00513     }
00514     if (rawTlmFrame) {
00515         delete[] rawTlmFrame;
00516         rawTlmFrame = NULL;
00517     }
00518 
00519     /* Unschedule callbacks */
00520     if (urlFrameCallbackHandle) {
00521         eventQueue.cancel(urlFrameCallbackHandle);
00522         urlFrameCallbackHandle = 0;
00523     }
00524     if (uidFrameCallbackHandle) {
00525         eventQueue.cancel(uidFrameCallbackHandle);
00526         uidFrameCallbackHandle = 0;
00527     }
00528     if (tlmFrameCallbackHandle) {
00529         eventQueue.cancel(tlmFrameCallbackHandle);
00530         tlmFrameCallbackHandle = 0;
00531     }
00532     if (radioManagerCallbackHandle) {
00533         eventQueue.cancel(radioManagerCallbackHandle);
00534         radioManagerCallbackHandle = 0;
00535     }
00536 }
00537 
00538 /*
00539  * Internal helper function used to update the GATT database following any
00540  * change to the internal state of the service object.
00541  */
00542 void EddystoneService::updateCharacteristicValues(void)
00543 {
00544     ble.gattServer().write(lockStateChar->getValueHandle(), reinterpret_cast<uint8_t *>(&lockState), sizeof(bool));
00545     ble.gattServer().write(urlDataChar->getValueHandle(), urlFrame.getEncodedURLData(), urlFrame.getEncodedURLDataLength());
00546     ble.gattServer().write(flagsChar->getValueHandle(), &flags, sizeof(uint8_t));
00547     ble.gattServer().write(beaconPeriodChar->getValueHandle(), reinterpret_cast<uint8_t *>(&urlFramePeriod), sizeof(uint16_t));
00548     ble.gattServer().write(txPowerModeChar->getValueHandle(), &txPowerMode, sizeof(uint8_t));
00549     ble.gattServer().write(advPowerLevelsChar->getValueHandle(), reinterpret_cast<uint8_t *>(advPowerLevels), sizeof(PowerLevels_t));
00550     ble.gattServer().write(lockChar->getValueHandle(), lock, sizeof(PowerLevels_t));
00551     ble.gattServer().write(unlockChar->getValueHandle(), unlock, sizeof(PowerLevels_t));
00552 }
00553 
00554 void EddystoneService::setupEddystoneConfigAdvertisements(void)
00555 {
00556     ble.gap().clearAdvertisingPayload();
00557 
00558     /* Accumulate the new payload */
00559     ble.gap().accumulateAdvertisingPayload(
00560         GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE
00561     );
00562     /* UUID is in different order in the ADV frame (!) */
00563     uint8_t reversedServiceUUID[sizeof(UUID_URL_BEACON_SERVICE)];
00564     for (size_t i = 0; i < sizeof(UUID_URL_BEACON_SERVICE); i++) {
00565         reversedServiceUUID[i] = UUID_URL_BEACON_SERVICE[sizeof(UUID_URL_BEACON_SERVICE) - i - 1];
00566     }
00567     ble.gap().accumulateAdvertisingPayload(
00568         GapAdvertisingData::COMPLETE_LIST_128BIT_SERVICE_IDS,
00569         reversedServiceUUID,
00570         sizeof(reversedServiceUUID)
00571     );
00572     ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::GENERIC_TAG);
00573     setupEddystoneConfigScanResponse();
00574 
00575     ble.gap().setTxPower(radioPowerLevels[txPowerMode]);
00576     ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
00577     ble.gap().setAdvertisingInterval(advConfigInterval);
00578     ble.gap().startAdvertising();
00579 }
00580 
00581 void EddystoneService::setupEddystoneConfigScanResponse(void)
00582 {
00583     ble.gap().clearScanResponse();
00584     ble.gap().accumulateScanResponse(
00585         GapAdvertisingData::COMPLETE_LOCAL_NAME,
00586         reinterpret_cast<const uint8_t *>(deviceName),
00587         strlen(deviceName)
00588     );
00589     ble.gap().accumulateScanResponse(
00590         GapAdvertisingData::TX_POWER_LEVEL,
00591         reinterpret_cast<uint8_t *>(&advPowerLevels[TX_POWER_MODE_LOW]),
00592         sizeof(uint8_t)
00593     );
00594 }
00595 
00596 void EddystoneService::lockAuthorizationCallback(GattWriteAuthCallbackParams *authParams)
00597 {
00598     if (lockState) {
00599         authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION;
00600     } else if (authParams->len != sizeof(Lock_t)) {
00601         authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH;
00602     } else if (authParams->offset != 0) {
00603         authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET;
00604     } else {
00605         authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
00606     }
00607 }
00608 
00609 void EddystoneService::unlockAuthorizationCallback(GattWriteAuthCallbackParams *authParams)
00610 {
00611     if (!lockState && (authParams->len == sizeof(Lock_t))) {
00612         authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
00613     } else if (authParams->len != sizeof(Lock_t)) {
00614         authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH;
00615     } else if (authParams->offset != 0) {
00616         authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET;
00617     } else if (memcmp(authParams->data, lock, sizeof(Lock_t)) != 0) {
00618         authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION;
00619     } else {
00620         authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
00621     }
00622 }
00623 
00624 void EddystoneService::urlDataWriteAuthorizationCallback(GattWriteAuthCallbackParams *authParams)
00625 {
00626     if (lockState) {
00627         authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION;
00628     } else if (authParams->offset != 0) {
00629         authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET;
00630     } else {
00631         authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
00632     }
00633 }
00634 
00635 void EddystoneService::powerModeAuthorizationCallback(GattWriteAuthCallbackParams *authParams)
00636 {
00637     if (lockState) {
00638         authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION;
00639     } else if (authParams->len != sizeof(uint8_t)) {
00640         authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH;
00641     } else if (authParams->offset != 0) {
00642         authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET;
00643     } else if (*((uint8_t *)authParams->data) >= NUM_POWER_MODES) {
00644         authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_WRITE_NOT_PERMITTED;
00645     } else {
00646         authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
00647     }
00648 }
00649 
00650 template <typename T>
00651 void EddystoneService::basicAuthorizationCallback(GattWriteAuthCallbackParams *authParams)
00652 {
00653     if (lockState) {
00654         authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION;
00655     } else if (authParams->len != sizeof(T)) {
00656         authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH;
00657     } else if (authParams->offset != 0) {
00658         authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET;
00659     } else {
00660         authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
00661     }
00662 }
00663 
00664 /*
00665  * This callback is invoked when a GATT client attempts to modify any of the
00666  * characteristics of this service. Attempts to do so are also applied to
00667  * the internal state of this service object.
00668  */
00669 void EddystoneService::onDataWrittenCallback(const GattWriteCallbackParams *writeParams)
00670 {
00671     uint16_t handle = writeParams->handle;
00672 
00673     if (handle == lockChar->getValueHandle()) {
00674         memcpy(lock, writeParams->data, sizeof(Lock_t));
00675         /* Set the state to be locked by the lock code (note: zeros are a valid lock) */
00676         lockState = true;
00677         ble.gattServer().write(lockChar->getValueHandle(), lock, sizeof(PowerLevels_t));
00678         ble.gattServer().write(lockStateChar->getValueHandle(), reinterpret_cast<uint8_t *>(&lockState), sizeof(bool));
00679     } else if (handle == unlockChar->getValueHandle()) {
00680         /* Validated earlier */
00681         lockState = false;
00682         ble.gattServer().write(unlockChar->getValueHandle(), unlock, sizeof(PowerLevels_t));
00683         ble.gattServer().write(lockStateChar->getValueHandle(), reinterpret_cast<uint8_t *>(&lockState), sizeof(bool));
00684     } else if (handle == urlDataChar->getValueHandle()) {
00685         urlFrame.setEncodedURLData(writeParams->data, writeParams->len);
00686         ble.gattServer().write(urlDataChar->getValueHandle(), urlFrame.getEncodedURLData(), urlFrame.getEncodedURLDataLength());
00687     } else if (handle == flagsChar->getValueHandle()) {
00688         flags = *(writeParams->data);
00689         ble.gattServer().write(flagsChar->getValueHandle(), &flags, sizeof(uint8_t));
00690     } else if (handle == advPowerLevelsChar->getValueHandle()) {
00691         memcpy(advPowerLevels, writeParams->data, sizeof(PowerLevels_t));
00692         ble.gattServer().write(advPowerLevelsChar->getValueHandle(), reinterpret_cast<uint8_t *>(advPowerLevels), sizeof(PowerLevels_t));
00693     } else if (handle == txPowerModeChar->getValueHandle()) {
00694         txPowerMode = *(writeParams->data);
00695         ble.gattServer().write(txPowerModeChar->getValueHandle(), &txPowerMode, sizeof(uint8_t));
00696     } else if (handle == beaconPeriodChar->getValueHandle()) {
00697         uint16_t tmpBeaconPeriod = correctAdvertisementPeriod(*((uint16_t *)(writeParams->data)));
00698         if (tmpBeaconPeriod != urlFramePeriod) {
00699             urlFramePeriod = tmpBeaconPeriod;
00700             ble.gattServer().write(beaconPeriodChar->getValueHandle(), reinterpret_cast<uint8_t *>(&urlFramePeriod), sizeof(uint16_t));
00701         }
00702     } else if (handle == resetChar->getValueHandle() && (*((uint8_t *)writeParams->data) != 0)) {
00703         /* Reset characteristics to default values */
00704         flags          = 0;
00705         txPowerMode    = TX_POWER_MODE_LOW;
00706         urlFramePeriod = DEFAULT_URL_FRAME_PERIOD_MSEC;
00707 
00708         urlFrame.setURLData(DEFAULT_URL);
00709         memset(lock, 0, sizeof(Lock_t));
00710 
00711         ble.gattServer().write(urlDataChar->getValueHandle(), urlFrame.getEncodedURLData(), urlFrame.getEncodedURLDataLength());
00712         ble.gattServer().write(flagsChar->getValueHandle(), &flags, sizeof(uint8_t));
00713         ble.gattServer().write(txPowerModeChar->getValueHandle(), &txPowerMode, sizeof(uint8_t));
00714         ble.gattServer().write(beaconPeriodChar->getValueHandle(), reinterpret_cast<uint8_t *>(&urlFramePeriod), sizeof(uint16_t));
00715         ble.gattServer().write(lockChar->getValueHandle(), lock, sizeof(PowerLevels_t));
00716     }
00717 }
00718 
00719 uint16_t EddystoneService::correctAdvertisementPeriod(uint16_t beaconPeriodIn) const
00720 {
00721     /* Re-map beaconPeriod to within permissible bounds if necessary. */
00722     if (beaconPeriodIn != 0) {
00723         if (beaconPeriodIn < ble.gap().getMinNonConnectableAdvertisingInterval()) {
00724             return ble.gap().getMinNonConnectableAdvertisingInterval();
00725         } else if (beaconPeriodIn > ble.gap().getMaxAdvertisingInterval()) {
00726             return ble.gap().getMaxAdvertisingInterval();
00727         }
00728     }
00729     return beaconPeriodIn;
00730 }
00731 
00732 void EddystoneService::setURLFrameAdvertisingInterval(uint16_t urlFrameIntervalIn)
00733 {
00734     if (urlFrameIntervalIn == urlFramePeriod) {
00735         /* Do nothing */
00736         return;
00737     }
00738 
00739     /* Make sure the input period is within bounds */
00740     urlFramePeriod = correctAdvertisementPeriod(urlFrameIntervalIn);
00741 
00742     if (operationMode == EDDYSTONE_MODE_BEACON) {
00743         if (urlFrameCallbackHandle) {
00744             eventQueue.cancel(urlFrameCallbackHandle);
00745         } else {
00746             /* This frame was just enabled */
00747             if (!rawUidFrame && urlFramePeriod) {
00748                 /* Allocate memory for this frame and construct it */
00749                 rawUrlFrame = new uint8_t[urlFrame.getRawFrameSize()];
00750                 urlFrame.constructURLFrame(rawUrlFrame, advPowerLevels[txPowerMode]);
00751             }
00752         }
00753 
00754         if (urlFramePeriod) {
00755             /* Currently the only way to change the period of a callback
00756              * is to cancel it and reschedule
00757              */
00758             urlFrameCallbackHandle = eventQueue.call_every(
00759                 urlFramePeriod,
00760                 Callback<void(FrameType)>(this, &EddystoneService::enqueueFrame),
00761                 EDDYSTONE_FRAME_URL
00762             );
00763         } else {
00764             urlFrameCallbackHandle = 0;
00765         }
00766     } else if (operationMode == EDDYSTONE_MODE_CONFIG) {
00767         ble.gattServer().write(beaconPeriodChar->getValueHandle(), reinterpret_cast<uint8_t *>(&urlFramePeriod), sizeof(uint16_t));
00768     }
00769 }
00770 
00771 void EddystoneService::setUIDFrameAdvertisingInterval(uint16_t uidFrameIntervalIn)
00772 {
00773     if (uidFrameIntervalIn == uidFramePeriod) {
00774         /* Do nothing */
00775         return;
00776     }
00777 
00778     /* Make sure the input period is within bounds */
00779     uidFramePeriod = correctAdvertisementPeriod(uidFrameIntervalIn);
00780 
00781     if (operationMode == EDDYSTONE_MODE_BEACON) {
00782         if (uidFrameCallbackHandle) {
00783             /* The advertisement interval changes, update the periodic callback */
00784             eventQueue.cancel(uidFrameCallbackHandle);
00785         } else {
00786             /* This frame was just enabled */
00787             if (!rawUidFrame && uidFramePeriod) {
00788                 /* Allocate memory for this frame and construct it */
00789                 rawUidFrame = new uint8_t[uidFrame.getRawFrameSize()];
00790                 uidFrame.constructUIDFrame(rawUidFrame, advPowerLevels[txPowerMode]);
00791             }
00792         }
00793 
00794         if (uidFramePeriod) {
00795             /* Currently the only way to change the period of a callback
00796              * is to cancel it and reschedule
00797              */
00798             uidFrameCallbackHandle = eventQueue.call_every(
00799                 uidFramePeriod,
00800                 Callback<void(FrameType)>(this, &EddystoneService::enqueueFrame),
00801                 EDDYSTONE_FRAME_UID
00802             );
00803         } else {
00804             uidFrameCallbackHandle = 0;
00805         }
00806     }
00807 }
00808 
00809 void EddystoneService::setTLMFrameAdvertisingInterval(uint16_t tlmFrameIntervalIn)
00810 {
00811     if (tlmFrameIntervalIn == tlmFramePeriod) {
00812         /* Do nothing */
00813         return;
00814     }
00815 
00816     /* Make sure the input period is within bounds */
00817     tlmFramePeriod = correctAdvertisementPeriod(tlmFrameIntervalIn);
00818 
00819     if (operationMode == EDDYSTONE_MODE_BEACON) {
00820         if (tlmFrameCallbackHandle) {
00821             /* The advertisement interval changes, update periodic callback */
00822             eventQueue.cancel(tlmFrameCallbackHandle);
00823         } else {
00824             /* This frame was just enabled */
00825             if (!rawTlmFrame && tlmFramePeriod) {
00826                 /* Allocate memory for this frame and construct it */
00827                 rawTlmFrame = new uint8_t[tlmFrame.getRawFrameSize()];
00828                 /* Do not construct the TLM frame because this changes every 0.1 seconds */
00829             }
00830         }
00831 
00832         if (tlmFramePeriod) {
00833             /* Currently the only way to change the period of a callback
00834              * is to cancel it and reschedule
00835              */
00836             tlmFrameCallbackHandle = eventQueue.call_every(
00837                 tlmFramePeriod,
00838                 Callback<void(FrameType)>(this, &EddystoneService::enqueueFrame),
00839                 EDDYSTONE_FRAME_TLM
00840             );
00841         } else {
00842             tlmFrameCallbackHandle = 0;
00843         }
00844     }
00845 }