Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
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 ¶msIn, 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 ¶ms) 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 }
Generated on Tue Jul 12 2022 19:55:16 by
1.7.2