ST
/
mbed-os-example-ble-EddystoneService
BLE EddystoneService example
Embed:
(wiki syntax)
Show/hide line numbers
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