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.
Diff: source/EddystoneService.cpp
- Revision:
- 1:9db4d46bb63f
- Parent:
- 0:4c8f8bf32a99
- Child:
- 2:9ee673e0b86a
--- a/source/EddystoneService.cpp Tue Jul 26 14:40:25 2016 +0100
+++ b/source/EddystoneService.cpp Thu Jul 28 23:14:36 2016 +0100
@@ -1,845 +1,845 @@
-/* mbed Microcontroller Library
- * Copyright (c) 2006-2015 ARM Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "EddystoneService.h"
-
-/* Initialise the EddystoneService using parameters from persistent storage */
-EddystoneService::EddystoneService(BLE &bleIn,
- EddystoneParams_t ¶msIn,
- const PowerLevels_t &radioPowerLevelsIn,
- EventQueue &evQ,
- uint32_t advConfigIntervalIn) :
- ble(bleIn),
- operationMode(EDDYSTONE_MODE_NONE),
- urlFrame(paramsIn.urlData, paramsIn.urlDataLength),
- uidFrame(paramsIn.uidNamespaceID, paramsIn.uidInstanceID),
- tlmFrame(paramsIn.tlmVersion),
- resetFlag(false),
- rawUrlFrame(NULL),
- rawUidFrame(NULL),
- rawTlmFrame(NULL),
- tlmBatteryVoltageCallback(NULL),
- tlmBeaconTemperatureCallback(NULL),
- uidFrameCallbackHandle(),
- urlFrameCallbackHandle(),
- tlmFrameCallbackHandle(),
- radioManagerCallbackHandle(),
- deviceName(DEFAULT_DEVICE_NAME),
- eventQueue(evQ)
-{
- lockState = paramsIn.lockState;
- flags = paramsIn.flags;
- txPowerMode = paramsIn.txPowerMode;
- urlFramePeriod = correctAdvertisementPeriod(paramsIn.urlFramePeriod);
- uidFramePeriod = correctAdvertisementPeriod(paramsIn.uidFramePeriod);
- tlmFramePeriod = correctAdvertisementPeriod(paramsIn.tlmFramePeriod);
-
- memcpy(lock, paramsIn.lock, sizeof(Lock_t));
- memcpy(unlock, paramsIn.unlock, sizeof(Lock_t));
-
- eddystoneConstructorHelper(paramsIn.advPowerLevels, radioPowerLevelsIn, advConfigIntervalIn);
-}
-
-/* When using this constructor we need to call setURLData,
- * setTMLData and setUIDData to initialise values manually
- */
-EddystoneService::EddystoneService(BLE &bleIn,
- const PowerLevels_t &advPowerLevelsIn,
- const PowerLevels_t &radioPowerLevelsIn,
- EventQueue &evQ,
- uint32_t advConfigIntervalIn) :
- ble(bleIn),
- operationMode(EDDYSTONE_MODE_NONE),
- urlFrame(),
- uidFrame(),
- tlmFrame(),
- lockState(false),
- resetFlag(false),
- lock(),
- unlock(),
- flags(0),
- txPowerMode(0),
- urlFramePeriod(DEFAULT_URL_FRAME_PERIOD_MSEC),
- uidFramePeriod(DEFAULT_UID_FRAME_PERIOD_MSEC),
- tlmFramePeriod(DEFAULT_TLM_FRAME_PERIOD_MSEC),
- rawUrlFrame(NULL),
- rawUidFrame(NULL),
- rawTlmFrame(NULL),
- tlmBatteryVoltageCallback(NULL),
- tlmBeaconTemperatureCallback(NULL),
- uidFrameCallbackHandle(),
- urlFrameCallbackHandle(),
- tlmFrameCallbackHandle(),
- radioManagerCallbackHandle(),
- deviceName(DEFAULT_DEVICE_NAME),
- eventQueue(evQ)
-{
- eddystoneConstructorHelper(advPowerLevelsIn, radioPowerLevelsIn, advConfigIntervalIn);
-}
-
-/* Setup callback to update BatteryVoltage in TLM frame */
-void EddystoneService::onTLMBatteryVoltageUpdate(TlmUpdateCallback_t tlmBatteryVoltageCallbackIn)
-{
- tlmBatteryVoltageCallback = tlmBatteryVoltageCallbackIn;
-}
-
-/* Setup callback to update BeaconTemperature in TLM frame */
-void EddystoneService::onTLMBeaconTemperatureUpdate(TlmUpdateCallback_t tlmBeaconTemperatureCallbackIn)
-{
- tlmBeaconTemperatureCallback = tlmBeaconTemperatureCallbackIn;
-}
-
-void EddystoneService::setTLMData(uint8_t tlmVersionIn)
-{
- tlmFrame.setTLMData(tlmVersionIn);
-}
-
-void EddystoneService::setURLData(const char *urlDataIn)
-{
- urlFrame.setURLData(urlDataIn);
-}
-
-void EddystoneService::setUIDData(const UIDNamespaceID_t &uidNamespaceIDIn, const UIDInstanceID_t &uidInstanceIDIn)
-{
- uidFrame.setUIDData(uidNamespaceIDIn, uidInstanceIDIn);
-}
-
-EddystoneService::EddystoneError_t EddystoneService::startConfigService(void)
-{
- if (operationMode == EDDYSTONE_MODE_CONFIG) {
- /* Nothing to do, we are already in config mode */
- return EDDYSTONE_ERROR_NONE;
- } else if (advConfigInterval == 0) {
- /* Nothing to do, the advertisement interval is 0 */
- return EDDYSTONE_ERROR_INVALID_ADVERTISING_INTERVAL;
- }
-
- if (operationMode == EDDYSTONE_MODE_BEACON) {
- ble.shutdown();
- stopBeaconService();
- }
-
- if (!ble.hasInitialized()) {
- operationMode = EDDYSTONE_MODE_CONFIG;
- ble.init(this, &EddystoneService::bleInitComplete);
- /* Set the device name once more */
- ble.gap().setDeviceName(reinterpret_cast<const uint8_t *>(deviceName));
- return EDDYSTONE_ERROR_NONE;
- }
-
- operationMode = EDDYSTONE_MODE_CONFIG;
- setupConfigService();
- return EDDYSTONE_ERROR_NONE;
-}
-
-EddystoneService::EddystoneError_t EddystoneService::startBeaconService(void)
-{
- if (operationMode == EDDYSTONE_MODE_BEACON) {
- /* Nothing to do, we are already in beacon mode */
- return EDDYSTONE_ERROR_NONE;
- } else if (!urlFramePeriod && !uidFramePeriod && !tlmFramePeriod) {
- /* Nothing to do, the period is 0 for all frames */
- return EDDYSTONE_ERROR_INVALID_ADVERTISING_INTERVAL;
- }
-
- if (operationMode == EDDYSTONE_MODE_CONFIG) {
- ble.shutdown();
- /* Free unused memory */
- freeConfigCharacteristics();
- }
-
- if (!ble.hasInitialized()) {
- operationMode = EDDYSTONE_MODE_BEACON;
- ble.init(this, &EddystoneService::bleInitComplete);
- /* Set the device name once more */
- ble.gap().setDeviceName(reinterpret_cast<const uint8_t *>(deviceName));
- return EDDYSTONE_ERROR_NONE;
- }
-
- operationMode = EDDYSTONE_MODE_BEACON;
- setupBeaconService();
-
- return EDDYSTONE_ERROR_NONE;
-}
-
-EddystoneService::EddystoneError_t EddystoneService::stopCurrentService(void)
-{
- switch (operationMode) {
- case EDDYSTONE_MODE_NONE:
- return EDDYSTONE_ERROR_INVALID_STATE;
- case EDDYSTONE_MODE_BEACON:
- ble.shutdown();
- stopBeaconService();
- break;
- case EDDYSTONE_MODE_CONFIG:
- ble.shutdown();
- freeConfigCharacteristics();
- break;
- default:
- /* Some error occurred */
- error("Invalid EddystonService mode");
- break;
- }
- operationMode = EDDYSTONE_MODE_NONE;
- /* Currently on some platforms, the BLE stack handles power management,
- * so we should bring it up again, but not configure it.
- * Once the system sleep without BLE initialised is fixed, remove this
- */
- ble.init(this, &EddystoneService::bleInitComplete);
-
- return EDDYSTONE_ERROR_NONE;
-}
-
-ble_error_t EddystoneService::setCompleteDeviceName(const char *deviceNameIn)
-{
- /* Make sure the device name is safe */
- ble_error_t error = ble.gap().setDeviceName(reinterpret_cast<const uint8_t *>(deviceNameIn));
- if (error == BLE_ERROR_NONE) {
- deviceName = deviceNameIn;
- if (operationMode == EDDYSTONE_MODE_CONFIG) {
- /* Need to update the advertising packets to the new name */
- setupEddystoneConfigScanResponse();
- }
- }
-
- return error;
-}
-
-/* It is not the responsibility of the Eddystone implementation to store
- * the configured parameters in persistent storage since this is
- * platform-specific. So we provide this function that returns the
- * configured values that need to be stored and the main application
- * takes care of storing them.
- */
-void EddystoneService::getEddystoneParams(EddystoneParams_t ¶ms)
-{
- params.lockState = lockState;
- params.flags = flags;
- params.txPowerMode = txPowerMode;
- params.urlFramePeriod = urlFramePeriod;
- params.tlmFramePeriod = tlmFramePeriod;
- params.uidFramePeriod = uidFramePeriod;
- params.tlmVersion = tlmFrame.getTLMVersion();
- params.urlDataLength = urlFrame.getEncodedURLDataLength();
-
- memcpy(params.advPowerLevels, advPowerLevels, sizeof(PowerLevels_t));
- memcpy(params.lock, lock, sizeof(Lock_t));
- memcpy(params.unlock, unlock, sizeof(Lock_t));
- memcpy(params.urlData, urlFrame.getEncodedURLData(), urlFrame.getEncodedURLDataLength());
- memcpy(params.uidNamespaceID, uidFrame.getUIDNamespaceID(), sizeof(UIDNamespaceID_t));
- memcpy(params.uidInstanceID, uidFrame.getUIDInstanceID(), sizeof(UIDInstanceID_t));
-}
-
-/* Helper function used only once during constructing the object to avoid
- * duplicated code.
- */
-void EddystoneService::eddystoneConstructorHelper(const PowerLevels_t &advPowerLevelsIn,
- const PowerLevels_t &radioPowerLevelsIn,
- uint32_t advConfigIntervalIn)
-{
- /* We cannot use correctAdvertisementPeriod() for this check because the function
- * call to get the minimum advertising interval in the BLE API is different for
- * connectable and non-connectable advertising.
- */
- if (advConfigIntervalIn != 0) {
- if (advConfigIntervalIn < ble.gap().getMinAdvertisingInterval()) {
- advConfigInterval = ble.gap().getMinAdvertisingInterval();
- } else if (advConfigIntervalIn > ble.gap().getMaxAdvertisingInterval()) {
- advConfigInterval = ble.gap().getMaxAdvertisingInterval();
- } else {
- advConfigInterval = advConfigIntervalIn;
- }
- }
-
- memcpy(radioPowerLevels, radioPowerLevelsIn, sizeof(PowerLevels_t));
- memcpy(advPowerLevels, advPowerLevelsIn, sizeof(PowerLevels_t));
-
- /* TODO: Note that this timer is started from the time EddystoneService
- * is initialised and NOT from when the device is booted. So app needs
- * to take care that EddystoneService is one of the first things to be
- * started!
- */
- timeSinceBootTimer.start();
-
- /* Set the device name at startup */
- ble.gap().setDeviceName(reinterpret_cast<const uint8_t *>(deviceName));
-}
-
-/* When changing modes, we shutdown and init the BLE instance, so
- * this is needed to complete the initialisation task.
- */
-void EddystoneService::bleInitComplete(BLE::InitializationCompleteCallbackContext* initContext)
-{
- if (initContext->error != BLE_ERROR_NONE) {
- /* Initialisation failed */
- return;
- }
-
- switch (operationMode) {
- case EDDYSTONE_MODE_CONFIG:
- setupConfigService();
- break;
- case EDDYSTONE_MODE_BEACON:
- setupBeaconService();
- break;
- case EDDYSTONE_MODE_NONE:
- /* We don't need to do anything here, but it isn't an error */
- break;
- default:
- /* Some error occurred */
- error("Invalid EddystonService mode");
- break;
- }
-}
-
-void EddystoneService::swapAdvertisedFrame(FrameType frameType)
-{
- switch(frameType) {
- case EDDYSTONE_FRAME_URL:
- updateAdvertisementPacket(rawUrlFrame, urlFrame.getRawFrameSize());
- break;
- case EDDYSTONE_FRAME_UID:
- updateAdvertisementPacket(rawUidFrame, uidFrame.getRawFrameSize());
- break;
- case EDDYSTONE_FRAME_TLM:
- updateRawTLMFrame();
- updateAdvertisementPacket(rawTlmFrame, tlmFrame.getRawFrameSize());
- break;
- default:
- /* Some error occurred */
- error("Frame to swap in does not specify a valid type");
- break;
- }
-}
-
-/* Helper function that calls user-defined functions to update Battery Voltage and Temperature (if available),
- * then updates the raw frame data and finally updates the actual advertised packet. This operation must be
- * done fairly often because the TLM frame TimeSinceBoot must have a 0.1 secs resolution according to the
- * Eddystone specification.
- */
-void EddystoneService::updateRawTLMFrame(void)
-{
- if (tlmBeaconTemperatureCallback != NULL) {
- tlmFrame.updateBeaconTemperature((*tlmBeaconTemperatureCallback)(tlmFrame.getBeaconTemperature()));
- }
- if (tlmBatteryVoltageCallback != NULL) {
- tlmFrame.updateBatteryVoltage((*tlmBatteryVoltageCallback)(tlmFrame.getBatteryVoltage()));
- }
- tlmFrame.updateTimeSinceBoot(timeSinceBootTimer.read_ms());
- tlmFrame.constructTLMFrame(rawTlmFrame);
-}
-
-void EddystoneService::updateAdvertisementPacket(const uint8_t* rawFrame, size_t rawFrameLength)
-{
- ble.gap().clearAdvertisingPayload();
- ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
- ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, EDDYSTONE_UUID, sizeof(EDDYSTONE_UUID));
- ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::SERVICE_DATA, rawFrame, rawFrameLength);
-}
-
-void EddystoneService::setupBeaconService(void)
-{
- /* Initialise arrays to hold constructed raw frames */
- if (urlFramePeriod) {
- rawUrlFrame = new uint8_t[urlFrame.getRawFrameSize()];
- urlFrame.constructURLFrame(rawUrlFrame, advPowerLevels[txPowerMode]);
- }
-
- if (uidFramePeriod) {
- rawUidFrame = new uint8_t[uidFrame.getRawFrameSize()];
- uidFrame.constructUIDFrame(rawUidFrame, advPowerLevels[txPowerMode]);
- }
-
- if (tlmFramePeriod) {
- rawTlmFrame = new uint8_t[tlmFrame.getRawFrameSize()];
- /* Do not initialise because we have to reconstruct every 0.1 secs */
- }
-
- /* Configure advertisements */
- ble.gap().setTxPower(radioPowerLevels[txPowerMode]);
- ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_NON_CONNECTABLE_UNDIRECTED);
- ble.gap().setAdvertisingInterval(ble.gap().getMaxAdvertisingInterval());
-
- /* Make sure the queue is currently empty */
- advFrameQueue.reset();
- /* Setup callbacks to periodically add frames to be advertised to the queue and
- * add initial frame so that we have something to advertise on startup */
- if (uidFramePeriod) {
- advFrameQueue.push(EDDYSTONE_FRAME_UID);
- uidFrameCallbackHandle = eventQueue.post_every(
- Callback<void(FrameType)>(this, &EddystoneService::enqueueFrame),
- EDDYSTONE_FRAME_UID,
- uidFramePeriod
- );
- }
- if (tlmFramePeriod) {
- advFrameQueue.push(EDDYSTONE_FRAME_TLM);
- tlmFrameCallbackHandle = eventQueue.post_every(
- Callback<void(FrameType)>(this, &EddystoneService::enqueueFrame),
- EDDYSTONE_FRAME_TLM,
- tlmFramePeriod
- );
- }
- if (urlFramePeriod) {
- advFrameQueue.push(EDDYSTONE_FRAME_URL);
- tlmFrameCallbackHandle = eventQueue.post_every(
- Callback<void(FrameType)>(this, &EddystoneService::enqueueFrame),
- EDDYSTONE_FRAME_URL,
- urlFramePeriod
- );
- }
-
- /* Start advertising */
- manageRadio();
-}
-
-void EddystoneService::enqueueFrame(FrameType frameType)
-{
- advFrameQueue.push(frameType);
- if (!radioManagerCallbackHandle) {
- /* Advertising stopped and there is not callback posted in the scheduler. Just
- * execute the manager to resume advertising */
- manageRadio();
- }
-}
-
-void EddystoneService::manageRadio(void)
-{
- FrameType frameType;
- uint32_t startTimeManageRadio = timeSinceBootTimer.read_ms();
-
- /* Signal that there is currently no callback posted */
- radioManagerCallbackHandle = 0;
-
- if (advFrameQueue.pop(frameType)) {
- /* We have something to advertise */
- if (ble.gap().getState().advertising) {
- ble.gap().stopAdvertising();
- }
- swapAdvertisedFrame(frameType);
- ble.gap().startAdvertising();
-
- /* Increase the advertised packet count in TLM frame */
- tlmFrame.updatePduCount();
-
- /* Post a callback to itself to stop the advertisement or pop the next
- * frame from the queue. However, take into account the time taken to
- * swap in this frame. */
- radioManagerCallbackHandle = eventQueue.post_in(
- Callback<void()>(this, &EddystoneService::manageRadio),
- ble.gap().getMinNonConnectableAdvertisingInterval() - (timeSinceBootTimer.read_ms() - startTimeManageRadio)
- );
- } else if (ble.gap().getState().advertising) {
- /* Nothing else to advertise, stop advertising and do not schedule any callbacks */
- ble.gap().stopAdvertising();
- }
-}
-
-void EddystoneService::setupConfigService(void)
-{
- lockStateChar = new ReadOnlyGattCharacteristic<bool>(UUID_LOCK_STATE_CHAR, &lockState);
- lockChar = new WriteOnlyArrayGattCharacteristic<uint8_t, sizeof(Lock_t)>(UUID_LOCK_CHAR, lock);
- unlockChar = new WriteOnlyArrayGattCharacteristic<uint8_t, sizeof(Lock_t)>(UUID_UNLOCK_CHAR, unlock);
- 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);
- flagsChar = new ReadWriteGattCharacteristic<uint8_t>(UUID_FLAGS_CHAR, &flags);
- advPowerLevelsChar = new ReadWriteArrayGattCharacteristic<int8_t, sizeof(PowerLevels_t)>(UUID_ADV_POWER_LEVELS_CHAR, advPowerLevels);
- txPowerModeChar = new ReadWriteGattCharacteristic<uint8_t>(UUID_TX_POWER_MODE_CHAR, &txPowerMode);
- beaconPeriodChar = new ReadWriteGattCharacteristic<uint16_t>(UUID_BEACON_PERIOD_CHAR, &urlFramePeriod);
- resetChar = new WriteOnlyGattCharacteristic<bool>(UUID_RESET_CHAR, &resetFlag);
-
- lockChar->setWriteAuthorizationCallback(this, &EddystoneService::lockAuthorizationCallback);
- unlockChar->setWriteAuthorizationCallback(this, &EddystoneService::unlockAuthorizationCallback);
- urlDataChar->setWriteAuthorizationCallback(this, &EddystoneService::urlDataWriteAuthorizationCallback);
- flagsChar->setWriteAuthorizationCallback(this, &EddystoneService::basicAuthorizationCallback<uint8_t>);
- advPowerLevelsChar->setWriteAuthorizationCallback(this, &EddystoneService::basicAuthorizationCallback<PowerLevels_t>);
- txPowerModeChar->setWriteAuthorizationCallback(this, &EddystoneService::powerModeAuthorizationCallback);
- beaconPeriodChar->setWriteAuthorizationCallback(this, &EddystoneService::basicAuthorizationCallback<uint16_t>);
- resetChar->setWriteAuthorizationCallback(this, &EddystoneService::basicAuthorizationCallback<bool>);
-
- charTable[0] = lockStateChar;
- charTable[1] = lockChar;
- charTable[2] = unlockChar;
- charTable[3] = urlDataChar;
- charTable[4] = flagsChar;
- charTable[5] = advPowerLevelsChar;
- charTable[6] = txPowerModeChar;
- charTable[7] = beaconPeriodChar;
- charTable[8] = resetChar;
-
- GattService configService(UUID_URL_BEACON_SERVICE, charTable, sizeof(charTable) / sizeof(GattCharacteristic *));
-
- ble.gattServer().addService(configService);
- ble.gattServer().onDataWritten(this, &EddystoneService::onDataWrittenCallback);
- updateCharacteristicValues();
- setupEddystoneConfigAdvertisements();
-}
-
-void EddystoneService::freeConfigCharacteristics(void)
-{
- delete lockStateChar;
- delete lockChar;
- delete unlockChar;
- delete urlDataChar;
- delete flagsChar;
- delete advPowerLevelsChar;
- delete txPowerModeChar;
- delete beaconPeriodChar;
- delete resetChar;
-}
-
-void EddystoneService::stopBeaconService(void)
-{
- /* Free unused memory */
- if (rawUrlFrame) {
- delete[] rawUrlFrame;
- rawUrlFrame = NULL;
- }
- if (rawUidFrame) {
- delete[] rawUidFrame;
- rawUidFrame = NULL;
- }
- if (rawTlmFrame) {
- delete[] rawTlmFrame;
- rawTlmFrame = NULL;
- }
-
- /* Unschedule callbacks */
- if (urlFrameCallbackHandle) {
- eventQueue.cancel(urlFrameCallbackHandle);
- urlFrameCallbackHandle = 0;
- }
- if (uidFrameCallbackHandle) {
- eventQueue.cancel(uidFrameCallbackHandle);
- uidFrameCallbackHandle = 0;
- }
- if (tlmFrameCallbackHandle) {
- eventQueue.cancel(tlmFrameCallbackHandle);
- tlmFrameCallbackHandle = 0;
- }
- if (radioManagerCallbackHandle) {
- eventQueue.cancel(radioManagerCallbackHandle);
- radioManagerCallbackHandle = 0;
- }
-}
-
-/*
- * Internal helper function used to update the GATT database following any
- * change to the internal state of the service object.
- */
-void EddystoneService::updateCharacteristicValues(void)
-{
- ble.gattServer().write(lockStateChar->getValueHandle(), reinterpret_cast<uint8_t *>(&lockState), sizeof(bool));
- ble.gattServer().write(urlDataChar->getValueHandle(), urlFrame.getEncodedURLData(), urlFrame.getEncodedURLDataLength());
- ble.gattServer().write(flagsChar->getValueHandle(), &flags, sizeof(uint8_t));
- ble.gattServer().write(beaconPeriodChar->getValueHandle(), reinterpret_cast<uint8_t *>(&urlFramePeriod), sizeof(uint16_t));
- ble.gattServer().write(txPowerModeChar->getValueHandle(), &txPowerMode, sizeof(uint8_t));
- ble.gattServer().write(advPowerLevelsChar->getValueHandle(), reinterpret_cast<uint8_t *>(advPowerLevels), sizeof(PowerLevels_t));
- ble.gattServer().write(lockChar->getValueHandle(), lock, sizeof(PowerLevels_t));
- ble.gattServer().write(unlockChar->getValueHandle(), unlock, sizeof(PowerLevels_t));
-}
-
-void EddystoneService::setupEddystoneConfigAdvertisements(void)
-{
- ble.gap().clearAdvertisingPayload();
-
- /* Accumulate the new payload */
- ble.gap().accumulateAdvertisingPayload(
- GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE
- );
- /* UUID is in different order in the ADV frame (!) */
- uint8_t reversedServiceUUID[sizeof(UUID_URL_BEACON_SERVICE)];
- for (size_t i = 0; i < sizeof(UUID_URL_BEACON_SERVICE); i++) {
- reversedServiceUUID[i] = UUID_URL_BEACON_SERVICE[sizeof(UUID_URL_BEACON_SERVICE) - i - 1];
- }
- ble.gap().accumulateAdvertisingPayload(
- GapAdvertisingData::COMPLETE_LIST_128BIT_SERVICE_IDS,
- reversedServiceUUID,
- sizeof(reversedServiceUUID)
- );
- ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::GENERIC_TAG);
- setupEddystoneConfigScanResponse();
-
- ble.gap().setTxPower(radioPowerLevels[txPowerMode]);
- ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
- ble.gap().setAdvertisingInterval(advConfigInterval);
- ble.gap().startAdvertising();
-}
-
-void EddystoneService::setupEddystoneConfigScanResponse(void)
-{
- ble.gap().clearScanResponse();
- ble.gap().accumulateScanResponse(
- GapAdvertisingData::COMPLETE_LOCAL_NAME,
- reinterpret_cast<const uint8_t *>(deviceName),
- strlen(deviceName)
- );
- ble.gap().accumulateScanResponse(
- GapAdvertisingData::TX_POWER_LEVEL,
- reinterpret_cast<uint8_t *>(&advPowerLevels[TX_POWER_MODE_LOW]),
- sizeof(uint8_t)
- );
-}
-
-void EddystoneService::lockAuthorizationCallback(GattWriteAuthCallbackParams *authParams)
-{
- if (lockState) {
- authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION;
- } else if (authParams->len != sizeof(Lock_t)) {
- authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH;
- } else if (authParams->offset != 0) {
- authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET;
- } else {
- authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
- }
-}
-
-void EddystoneService::unlockAuthorizationCallback(GattWriteAuthCallbackParams *authParams)
-{
- if (!lockState && (authParams->len == sizeof(Lock_t))) {
- authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
- } else if (authParams->len != sizeof(Lock_t)) {
- authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH;
- } else if (authParams->offset != 0) {
- authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET;
- } else if (memcmp(authParams->data, lock, sizeof(Lock_t)) != 0) {
- authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION;
- } else {
- authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
- }
-}
-
-void EddystoneService::urlDataWriteAuthorizationCallback(GattWriteAuthCallbackParams *authParams)
-{
- if (lockState) {
- authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION;
- } else if (authParams->offset != 0) {
- authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET;
- } else {
- authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
- }
-}
-
-void EddystoneService::powerModeAuthorizationCallback(GattWriteAuthCallbackParams *authParams)
-{
- if (lockState) {
- authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION;
- } else if (authParams->len != sizeof(uint8_t)) {
- authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH;
- } else if (authParams->offset != 0) {
- authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET;
- } else if (*((uint8_t *)authParams->data) >= NUM_POWER_MODES) {
- authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_WRITE_NOT_PERMITTED;
- } else {
- authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
- }
-}
-
-template <typename T>
-void EddystoneService::basicAuthorizationCallback(GattWriteAuthCallbackParams *authParams)
-{
- if (lockState) {
- authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION;
- } else if (authParams->len != sizeof(T)) {
- authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH;
- } else if (authParams->offset != 0) {
- authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET;
- } else {
- authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
- }
-}
-
-/*
- * This callback is invoked when a GATT client attempts to modify any of the
- * characteristics of this service. Attempts to do so are also applied to
- * the internal state of this service object.
- */
-void EddystoneService::onDataWrittenCallback(const GattWriteCallbackParams *writeParams)
-{
- uint16_t handle = writeParams->handle;
-
- if (handle == lockChar->getValueHandle()) {
- memcpy(lock, writeParams->data, sizeof(Lock_t));
- /* Set the state to be locked by the lock code (note: zeros are a valid lock) */
- lockState = true;
- ble.gattServer().write(lockChar->getValueHandle(), lock, sizeof(PowerLevels_t));
- ble.gattServer().write(lockStateChar->getValueHandle(), reinterpret_cast<uint8_t *>(&lockState), sizeof(bool));
- } else if (handle == unlockChar->getValueHandle()) {
- /* Validated earlier */
- lockState = false;
- ble.gattServer().write(unlockChar->getValueHandle(), unlock, sizeof(PowerLevels_t));
- ble.gattServer().write(lockStateChar->getValueHandle(), reinterpret_cast<uint8_t *>(&lockState), sizeof(bool));
- } else if (handle == urlDataChar->getValueHandle()) {
- urlFrame.setEncodedURLData(writeParams->data, writeParams->len);
- ble.gattServer().write(urlDataChar->getValueHandle(), urlFrame.getEncodedURLData(), urlFrame.getEncodedURLDataLength());
- } else if (handle == flagsChar->getValueHandle()) {
- flags = *(writeParams->data);
- ble.gattServer().write(flagsChar->getValueHandle(), &flags, sizeof(uint8_t));
- } else if (handle == advPowerLevelsChar->getValueHandle()) {
- memcpy(advPowerLevels, writeParams->data, sizeof(PowerLevels_t));
- ble.gattServer().write(advPowerLevelsChar->getValueHandle(), reinterpret_cast<uint8_t *>(advPowerLevels), sizeof(PowerLevels_t));
- } else if (handle == txPowerModeChar->getValueHandle()) {
- txPowerMode = *(writeParams->data);
- ble.gattServer().write(txPowerModeChar->getValueHandle(), &txPowerMode, sizeof(uint8_t));
- } else if (handle == beaconPeriodChar->getValueHandle()) {
- uint16_t tmpBeaconPeriod = correctAdvertisementPeriod(*((uint16_t *)(writeParams->data)));
- if (tmpBeaconPeriod != urlFramePeriod) {
- urlFramePeriod = tmpBeaconPeriod;
- ble.gattServer().write(beaconPeriodChar->getValueHandle(), reinterpret_cast<uint8_t *>(&urlFramePeriod), sizeof(uint16_t));
- }
- } else if (handle == resetChar->getValueHandle() && (*((uint8_t *)writeParams->data) != 0)) {
- /* Reset characteristics to default values */
- flags = 0;
- txPowerMode = TX_POWER_MODE_LOW;
- urlFramePeriod = DEFAULT_URL_FRAME_PERIOD_MSEC;
-
- urlFrame.setURLData(DEFAULT_URL);
- memset(lock, 0, sizeof(Lock_t));
-
- ble.gattServer().write(urlDataChar->getValueHandle(), urlFrame.getEncodedURLData(), urlFrame.getEncodedURLDataLength());
- ble.gattServer().write(flagsChar->getValueHandle(), &flags, sizeof(uint8_t));
- ble.gattServer().write(txPowerModeChar->getValueHandle(), &txPowerMode, sizeof(uint8_t));
- ble.gattServer().write(beaconPeriodChar->getValueHandle(), reinterpret_cast<uint8_t *>(&urlFramePeriod), sizeof(uint16_t));
- ble.gattServer().write(lockChar->getValueHandle(), lock, sizeof(PowerLevels_t));
- }
-}
-
-uint16_t EddystoneService::correctAdvertisementPeriod(uint16_t beaconPeriodIn) const
-{
- /* Re-map beaconPeriod to within permissible bounds if necessary. */
- if (beaconPeriodIn != 0) {
- if (beaconPeriodIn < ble.gap().getMinNonConnectableAdvertisingInterval()) {
- return ble.gap().getMinNonConnectableAdvertisingInterval();
- } else if (beaconPeriodIn > ble.gap().getMaxAdvertisingInterval()) {
- return ble.gap().getMaxAdvertisingInterval();
- }
- }
- return beaconPeriodIn;
-}
-
-void EddystoneService::setURLFrameAdvertisingInterval(uint16_t urlFrameIntervalIn)
-{
- if (urlFrameIntervalIn == urlFramePeriod) {
- /* Do nothing */
- return;
- }
-
- /* Make sure the input period is within bounds */
- urlFramePeriod = correctAdvertisementPeriod(urlFrameIntervalIn);
-
- if (operationMode == EDDYSTONE_MODE_BEACON) {
- if (urlFrameCallbackHandle) {
- eventQueue.cancel(urlFrameCallbackHandle);
- } else {
- /* This frame was just enabled */
- if (!rawUidFrame && urlFramePeriod) {
- /* Allocate memory for this frame and construct it */
- rawUrlFrame = new uint8_t[urlFrame.getRawFrameSize()];
- urlFrame.constructURLFrame(rawUrlFrame, advPowerLevels[txPowerMode]);
- }
- }
-
- if (urlFramePeriod) {
- /* Currently the only way to change the period of a callback
- * is to cancel it and reschedule
- */
- urlFrameCallbackHandle = eventQueue.post_every(
- Callback<void(FrameType)>(this, &EddystoneService::enqueueFrame),
- EDDYSTONE_FRAME_URL,
- urlFramePeriod
- );
- } else {
- urlFrameCallbackHandle = 0;
- }
- } else if (operationMode == EDDYSTONE_MODE_CONFIG) {
- ble.gattServer().write(beaconPeriodChar->getValueHandle(), reinterpret_cast<uint8_t *>(&urlFramePeriod), sizeof(uint16_t));
- }
-}
-
-void EddystoneService::setUIDFrameAdvertisingInterval(uint16_t uidFrameIntervalIn)
-{
- if (uidFrameIntervalIn == uidFramePeriod) {
- /* Do nothing */
- return;
- }
-
- /* Make sure the input period is within bounds */
- uidFramePeriod = correctAdvertisementPeriod(uidFrameIntervalIn);
-
- if (operationMode == EDDYSTONE_MODE_BEACON) {
- if (uidFrameCallbackHandle) {
- /* The advertisement interval changes, update the periodic callback */
- eventQueue.cancel(uidFrameCallbackHandle);
- } else {
- /* This frame was just enabled */
- if (!rawUidFrame && uidFramePeriod) {
- /* Allocate memory for this frame and construct it */
- rawUidFrame = new uint8_t[uidFrame.getRawFrameSize()];
- uidFrame.constructUIDFrame(rawUidFrame, advPowerLevels[txPowerMode]);
- }
- }
-
- if (uidFramePeriod) {
- /* Currently the only way to change the period of a callback
- * is to cancel it and reschedule
- */
- uidFrameCallbackHandle = eventQueue.post_every(
- Callback<void(FrameType)>(this, &EddystoneService::enqueueFrame),
- EDDYSTONE_FRAME_UID,
- uidFramePeriod
- );
- } else {
- uidFrameCallbackHandle = 0;
- }
- }
-}
-
-void EddystoneService::setTLMFrameAdvertisingInterval(uint16_t tlmFrameIntervalIn)
-{
- if (tlmFrameIntervalIn == tlmFramePeriod) {
- /* Do nothing */
- return;
- }
-
- /* Make sure the input period is within bounds */
- tlmFramePeriod = correctAdvertisementPeriod(tlmFrameIntervalIn);
-
- if (operationMode == EDDYSTONE_MODE_BEACON) {
- if (tlmFrameCallbackHandle) {
- /* The advertisement interval changes, update periodic callback */
- eventQueue.cancel(tlmFrameCallbackHandle);
- } else {
- /* This frame was just enabled */
- if (!rawTlmFrame && tlmFramePeriod) {
- /* Allocate memory for this frame and construct it */
- rawTlmFrame = new uint8_t[tlmFrame.getRawFrameSize()];
- /* Do not construct the TLM frame because this changes every 0.1 seconds */
- }
- }
-
- if (tlmFramePeriod) {
- /* Currently the only way to change the period of a callback
- * is to cancel it and reschedule
- */
- tlmFrameCallbackHandle = eventQueue.post_every(
- Callback<void(FrameType)>(this, &EddystoneService::enqueueFrame),
- EDDYSTONE_FRAME_TLM,
- tlmFramePeriod
- );
- } else {
- tlmFrameCallbackHandle = 0;
- }
- }
-}
+/* mbed Microcontroller Library
+ * Copyright (c) 2006-2015 ARM Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "EddystoneService.h"
+
+/* Initialise the EddystoneService using parameters from persistent storage */
+EddystoneService::EddystoneService(BLE &bleIn,
+ EddystoneParams_t ¶msIn,
+ const PowerLevels_t &radioPowerLevelsIn,
+ EventQueue &evQ,
+ uint32_t advConfigIntervalIn) :
+ ble(bleIn),
+ operationMode(EDDYSTONE_MODE_NONE),
+ urlFrame(paramsIn.urlData, paramsIn.urlDataLength),
+ uidFrame(paramsIn.uidNamespaceID, paramsIn.uidInstanceID),
+ tlmFrame(paramsIn.tlmVersion),
+ resetFlag(false),
+ rawUrlFrame(NULL),
+ rawUidFrame(NULL),
+ rawTlmFrame(NULL),
+ tlmBatteryVoltageCallback(NULL),
+ tlmBeaconTemperatureCallback(NULL),
+ uidFrameCallbackHandle(),
+ urlFrameCallbackHandle(),
+ tlmFrameCallbackHandle(),
+ radioManagerCallbackHandle(),
+ deviceName(DEFAULT_DEVICE_NAME),
+ eventQueue(evQ)
+{
+ lockState = paramsIn.lockState;
+ flags = paramsIn.flags;
+ txPowerMode = paramsIn.txPowerMode;
+ urlFramePeriod = correctAdvertisementPeriod(paramsIn.urlFramePeriod);
+ uidFramePeriod = correctAdvertisementPeriod(paramsIn.uidFramePeriod);
+ tlmFramePeriod = correctAdvertisementPeriod(paramsIn.tlmFramePeriod);
+
+ memcpy(lock, paramsIn.lock, sizeof(Lock_t));
+ memcpy(unlock, paramsIn.unlock, sizeof(Lock_t));
+
+ eddystoneConstructorHelper(paramsIn.advPowerLevels, radioPowerLevelsIn, advConfigIntervalIn);
+}
+
+/* When using this constructor we need to call setURLData,
+ * setTMLData and setUIDData to initialise values manually
+ */
+EddystoneService::EddystoneService(BLE &bleIn,
+ const PowerLevels_t &advPowerLevelsIn,
+ const PowerLevels_t &radioPowerLevelsIn,
+ EventQueue &evQ,
+ uint32_t advConfigIntervalIn) :
+ ble(bleIn),
+ operationMode(EDDYSTONE_MODE_NONE),
+ urlFrame(),
+ uidFrame(),
+ tlmFrame(),
+ lockState(false),
+ resetFlag(false),
+ lock(),
+ unlock(),
+ flags(0),
+ txPowerMode(0),
+ urlFramePeriod(DEFAULT_URL_FRAME_PERIOD_MSEC),
+ uidFramePeriod(DEFAULT_UID_FRAME_PERIOD_MSEC),
+ tlmFramePeriod(DEFAULT_TLM_FRAME_PERIOD_MSEC),
+ rawUrlFrame(NULL),
+ rawUidFrame(NULL),
+ rawTlmFrame(NULL),
+ tlmBatteryVoltageCallback(NULL),
+ tlmBeaconTemperatureCallback(NULL),
+ uidFrameCallbackHandle(),
+ urlFrameCallbackHandle(),
+ tlmFrameCallbackHandle(),
+ radioManagerCallbackHandle(),
+ deviceName(DEFAULT_DEVICE_NAME),
+ eventQueue(evQ)
+{
+ eddystoneConstructorHelper(advPowerLevelsIn, radioPowerLevelsIn, advConfigIntervalIn);
+}
+
+/* Setup callback to update BatteryVoltage in TLM frame */
+void EddystoneService::onTLMBatteryVoltageUpdate(TlmUpdateCallback_t tlmBatteryVoltageCallbackIn)
+{
+ tlmBatteryVoltageCallback = tlmBatteryVoltageCallbackIn;
+}
+
+/* Setup callback to update BeaconTemperature in TLM frame */
+void EddystoneService::onTLMBeaconTemperatureUpdate(TlmUpdateCallback_t tlmBeaconTemperatureCallbackIn)
+{
+ tlmBeaconTemperatureCallback = tlmBeaconTemperatureCallbackIn;
+}
+
+void EddystoneService::setTLMData(uint8_t tlmVersionIn)
+{
+ tlmFrame.setTLMData(tlmVersionIn);
+}
+
+void EddystoneService::setURLData(const char *urlDataIn)
+{
+ urlFrame.setURLData(urlDataIn);
+}
+
+void EddystoneService::setUIDData(const UIDNamespaceID_t &uidNamespaceIDIn, const UIDInstanceID_t &uidInstanceIDIn)
+{
+ uidFrame.setUIDData(uidNamespaceIDIn, uidInstanceIDIn);
+}
+
+EddystoneService::EddystoneError_t EddystoneService::startConfigService(void)
+{
+ if (operationMode == EDDYSTONE_MODE_CONFIG) {
+ /* Nothing to do, we are already in config mode */
+ return EDDYSTONE_ERROR_NONE;
+ } else if (advConfigInterval == 0) {
+ /* Nothing to do, the advertisement interval is 0 */
+ return EDDYSTONE_ERROR_INVALID_ADVERTISING_INTERVAL;
+ }
+
+ if (operationMode == EDDYSTONE_MODE_BEACON) {
+ ble.shutdown();
+ stopBeaconService();
+ }
+
+ if (!ble.hasInitialized()) {
+ operationMode = EDDYSTONE_MODE_CONFIG;
+ ble.init(this, &EddystoneService::bleInitComplete);
+ /* Set the device name once more */
+ ble.gap().setDeviceName(reinterpret_cast<const uint8_t *>(deviceName));
+ return EDDYSTONE_ERROR_NONE;
+ }
+
+ operationMode = EDDYSTONE_MODE_CONFIG;
+ setupConfigService();
+ return EDDYSTONE_ERROR_NONE;
+}
+
+EddystoneService::EddystoneError_t EddystoneService::startBeaconService(void)
+{
+ if (operationMode == EDDYSTONE_MODE_BEACON) {
+ /* Nothing to do, we are already in beacon mode */
+ return EDDYSTONE_ERROR_NONE;
+ } else if (!urlFramePeriod && !uidFramePeriod && !tlmFramePeriod) {
+ /* Nothing to do, the period is 0 for all frames */
+ return EDDYSTONE_ERROR_INVALID_ADVERTISING_INTERVAL;
+ }
+
+ if (operationMode == EDDYSTONE_MODE_CONFIG) {
+ ble.shutdown();
+ /* Free unused memory */
+ freeConfigCharacteristics();
+ }
+
+ if (!ble.hasInitialized()) {
+ operationMode = EDDYSTONE_MODE_BEACON;
+ ble.init(this, &EddystoneService::bleInitComplete);
+ /* Set the device name once more */
+ ble.gap().setDeviceName(reinterpret_cast<const uint8_t *>(deviceName));
+ return EDDYSTONE_ERROR_NONE;
+ }
+
+ operationMode = EDDYSTONE_MODE_BEACON;
+ setupBeaconService();
+
+ return EDDYSTONE_ERROR_NONE;
+}
+
+EddystoneService::EddystoneError_t EddystoneService::stopCurrentService(void)
+{
+ switch (operationMode) {
+ case EDDYSTONE_MODE_NONE:
+ return EDDYSTONE_ERROR_INVALID_STATE;
+ case EDDYSTONE_MODE_BEACON:
+ ble.shutdown();
+ stopBeaconService();
+ break;
+ case EDDYSTONE_MODE_CONFIG:
+ ble.shutdown();
+ freeConfigCharacteristics();
+ break;
+ default:
+ /* Some error occurred */
+ error("Invalid EddystonService mode");
+ break;
+ }
+ operationMode = EDDYSTONE_MODE_NONE;
+ /* Currently on some platforms, the BLE stack handles power management,
+ * so we should bring it up again, but not configure it.
+ * Once the system sleep without BLE initialised is fixed, remove this
+ */
+ ble.init(this, &EddystoneService::bleInitComplete);
+
+ return EDDYSTONE_ERROR_NONE;
+}
+
+ble_error_t EddystoneService::setCompleteDeviceName(const char *deviceNameIn)
+{
+ /* Make sure the device name is safe */
+ ble_error_t error = ble.gap().setDeviceName(reinterpret_cast<const uint8_t *>(deviceNameIn));
+ if (error == BLE_ERROR_NONE) {
+ deviceName = deviceNameIn;
+ if (operationMode == EDDYSTONE_MODE_CONFIG) {
+ /* Need to update the advertising packets to the new name */
+ setupEddystoneConfigScanResponse();
+ }
+ }
+
+ return error;
+}
+
+/* It is not the responsibility of the Eddystone implementation to store
+ * the configured parameters in persistent storage since this is
+ * platform-specific. So we provide this function that returns the
+ * configured values that need to be stored and the main application
+ * takes care of storing them.
+ */
+void EddystoneService::getEddystoneParams(EddystoneParams_t ¶ms)
+{
+ params.lockState = lockState;
+ params.flags = flags;
+ params.txPowerMode = txPowerMode;
+ params.urlFramePeriod = urlFramePeriod;
+ params.tlmFramePeriod = tlmFramePeriod;
+ params.uidFramePeriod = uidFramePeriod;
+ params.tlmVersion = tlmFrame.getTLMVersion();
+ params.urlDataLength = urlFrame.getEncodedURLDataLength();
+
+ memcpy(params.advPowerLevels, advPowerLevels, sizeof(PowerLevels_t));
+ memcpy(params.lock, lock, sizeof(Lock_t));
+ memcpy(params.unlock, unlock, sizeof(Lock_t));
+ memcpy(params.urlData, urlFrame.getEncodedURLData(), urlFrame.getEncodedURLDataLength());
+ memcpy(params.uidNamespaceID, uidFrame.getUIDNamespaceID(), sizeof(UIDNamespaceID_t));
+ memcpy(params.uidInstanceID, uidFrame.getUIDInstanceID(), sizeof(UIDInstanceID_t));
+}
+
+/* Helper function used only once during constructing the object to avoid
+ * duplicated code.
+ */
+void EddystoneService::eddystoneConstructorHelper(const PowerLevels_t &advPowerLevelsIn,
+ const PowerLevels_t &radioPowerLevelsIn,
+ uint32_t advConfigIntervalIn)
+{
+ /* We cannot use correctAdvertisementPeriod() for this check because the function
+ * call to get the minimum advertising interval in the BLE API is different for
+ * connectable and non-connectable advertising.
+ */
+ if (advConfigIntervalIn != 0) {
+ if (advConfigIntervalIn < ble.gap().getMinAdvertisingInterval()) {
+ advConfigInterval = ble.gap().getMinAdvertisingInterval();
+ } else if (advConfigIntervalIn > ble.gap().getMaxAdvertisingInterval()) {
+ advConfigInterval = ble.gap().getMaxAdvertisingInterval();
+ } else {
+ advConfigInterval = advConfigIntervalIn;
+ }
+ }
+
+ memcpy(radioPowerLevels, radioPowerLevelsIn, sizeof(PowerLevels_t));
+ memcpy(advPowerLevels, advPowerLevelsIn, sizeof(PowerLevels_t));
+
+ /* TODO: Note that this timer is started from the time EddystoneService
+ * is initialised and NOT from when the device is booted. So app needs
+ * to take care that EddystoneService is one of the first things to be
+ * started!
+ */
+ timeSinceBootTimer.start();
+
+ /* Set the device name at startup */
+ ble.gap().setDeviceName(reinterpret_cast<const uint8_t *>(deviceName));
+}
+
+/* When changing modes, we shutdown and init the BLE instance, so
+ * this is needed to complete the initialisation task.
+ */
+void EddystoneService::bleInitComplete(BLE::InitializationCompleteCallbackContext* initContext)
+{
+ if (initContext->error != BLE_ERROR_NONE) {
+ /* Initialisation failed */
+ return;
+ }
+
+ switch (operationMode) {
+ case EDDYSTONE_MODE_CONFIG:
+ setupConfigService();
+ break;
+ case EDDYSTONE_MODE_BEACON:
+ setupBeaconService();
+ break;
+ case EDDYSTONE_MODE_NONE:
+ /* We don't need to do anything here, but it isn't an error */
+ break;
+ default:
+ /* Some error occurred */
+ error("Invalid EddystonService mode");
+ break;
+ }
+}
+
+void EddystoneService::swapAdvertisedFrame(FrameType frameType)
+{
+ switch(frameType) {
+ case EDDYSTONE_FRAME_URL:
+ updateAdvertisementPacket(rawUrlFrame, urlFrame.getRawFrameSize());
+ break;
+ case EDDYSTONE_FRAME_UID:
+ updateAdvertisementPacket(rawUidFrame, uidFrame.getRawFrameSize());
+ break;
+ case EDDYSTONE_FRAME_TLM:
+ updateRawTLMFrame();
+ updateAdvertisementPacket(rawTlmFrame, tlmFrame.getRawFrameSize());
+ break;
+ default:
+ /* Some error occurred */
+ error("Frame to swap in does not specify a valid type");
+ break;
+ }
+}
+
+/* Helper function that calls user-defined functions to update Battery Voltage and Temperature (if available),
+ * then updates the raw frame data and finally updates the actual advertised packet. This operation must be
+ * done fairly often because the TLM frame TimeSinceBoot must have a 0.1 secs resolution according to the
+ * Eddystone specification.
+ */
+void EddystoneService::updateRawTLMFrame(void)
+{
+ if (tlmBeaconTemperatureCallback != NULL) {
+ tlmFrame.updateBeaconTemperature((*tlmBeaconTemperatureCallback)(tlmFrame.getBeaconTemperature()));
+ }
+ if (tlmBatteryVoltageCallback != NULL) {
+ tlmFrame.updateBatteryVoltage((*tlmBatteryVoltageCallback)(tlmFrame.getBatteryVoltage()));
+ }
+ tlmFrame.updateTimeSinceBoot(timeSinceBootTimer.read_ms());
+ tlmFrame.constructTLMFrame(rawTlmFrame);
+}
+
+void EddystoneService::updateAdvertisementPacket(const uint8_t* rawFrame, size_t rawFrameLength)
+{
+ ble.gap().clearAdvertisingPayload();
+ ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
+ ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, EDDYSTONE_UUID, sizeof(EDDYSTONE_UUID));
+ ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::SERVICE_DATA, rawFrame, rawFrameLength);
+}
+
+void EddystoneService::setupBeaconService(void)
+{
+ /* Initialise arrays to hold constructed raw frames */
+ if (urlFramePeriod) {
+ rawUrlFrame = new uint8_t[urlFrame.getRawFrameSize()];
+ urlFrame.constructURLFrame(rawUrlFrame, advPowerLevels[txPowerMode]);
+ }
+
+ if (uidFramePeriod) {
+ rawUidFrame = new uint8_t[uidFrame.getRawFrameSize()];
+ uidFrame.constructUIDFrame(rawUidFrame, advPowerLevels[txPowerMode]);
+ }
+
+ if (tlmFramePeriod) {
+ rawTlmFrame = new uint8_t[tlmFrame.getRawFrameSize()];
+ /* Do not initialise because we have to reconstruct every 0.1 secs */
+ }
+
+ /* Configure advertisements */
+ ble.gap().setTxPower(radioPowerLevels[txPowerMode]);
+ ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_NON_CONNECTABLE_UNDIRECTED);
+ ble.gap().setAdvertisingInterval(ble.gap().getMaxAdvertisingInterval());
+
+ /* Make sure the queue is currently empty */
+ advFrameQueue.reset();
+ /* Setup callbacks to periodically add frames to be advertised to the queue and
+ * add initial frame so that we have something to advertise on startup */
+ if (uidFramePeriod) {
+ advFrameQueue.push(EDDYSTONE_FRAME_UID);
+ uidFrameCallbackHandle = eventQueue.post_every(
+ uidFramePeriod,
+ Callback<void(FrameType)>(this, &EddystoneService::enqueueFrame),
+ EDDYSTONE_FRAME_UID
+ );
+ }
+ if (tlmFramePeriod) {
+ advFrameQueue.push(EDDYSTONE_FRAME_TLM);
+ tlmFrameCallbackHandle = eventQueue.post_every(
+ tlmFramePeriod,
+ Callback<void(FrameType)>(this, &EddystoneService::enqueueFrame),
+ EDDYSTONE_FRAME_TLM
+ );
+ }
+ if (urlFramePeriod) {
+ advFrameQueue.push(EDDYSTONE_FRAME_URL);
+ tlmFrameCallbackHandle = eventQueue.post_every(
+ urlFramePeriod,
+ Callback<void(FrameType)>(this, &EddystoneService::enqueueFrame),
+ EDDYSTONE_FRAME_URL
+ );
+ }
+
+ /* Start advertising */
+ manageRadio();
+}
+
+void EddystoneService::enqueueFrame(FrameType frameType)
+{
+ advFrameQueue.push(frameType);
+ if (!radioManagerCallbackHandle) {
+ /* Advertising stopped and there is not callback posted in the scheduler. Just
+ * execute the manager to resume advertising */
+ manageRadio();
+ }
+}
+
+void EddystoneService::manageRadio(void)
+{
+ FrameType frameType;
+ uint32_t startTimeManageRadio = timeSinceBootTimer.read_ms();
+
+ /* Signal that there is currently no callback posted */
+ radioManagerCallbackHandle = 0;
+
+ if (advFrameQueue.pop(frameType)) {
+ /* We have something to advertise */
+ if (ble.gap().getState().advertising) {
+ ble.gap().stopAdvertising();
+ }
+ swapAdvertisedFrame(frameType);
+ ble.gap().startAdvertising();
+
+ /* Increase the advertised packet count in TLM frame */
+ tlmFrame.updatePduCount();
+
+ /* Post a callback to itself to stop the advertisement or pop the next
+ * frame from the queue. However, take into account the time taken to
+ * swap in this frame. */
+ radioManagerCallbackHandle = eventQueue.post_in(
+ ble.gap().getMinNonConnectableAdvertisingInterval() - (timeSinceBootTimer.read_ms() - startTimeManageRadio),
+ Callback<void()>(this, &EddystoneService::manageRadio)
+ );
+ } else if (ble.gap().getState().advertising) {
+ /* Nothing else to advertise, stop advertising and do not schedule any callbacks */
+ ble.gap().stopAdvertising();
+ }
+}
+
+void EddystoneService::setupConfigService(void)
+{
+ lockStateChar = new ReadOnlyGattCharacteristic<bool>(UUID_LOCK_STATE_CHAR, &lockState);
+ lockChar = new WriteOnlyArrayGattCharacteristic<uint8_t, sizeof(Lock_t)>(UUID_LOCK_CHAR, lock);
+ unlockChar = new WriteOnlyArrayGattCharacteristic<uint8_t, sizeof(Lock_t)>(UUID_UNLOCK_CHAR, unlock);
+ 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);
+ flagsChar = new ReadWriteGattCharacteristic<uint8_t>(UUID_FLAGS_CHAR, &flags);
+ advPowerLevelsChar = new ReadWriteArrayGattCharacteristic<int8_t, sizeof(PowerLevels_t)>(UUID_ADV_POWER_LEVELS_CHAR, advPowerLevels);
+ txPowerModeChar = new ReadWriteGattCharacteristic<uint8_t>(UUID_TX_POWER_MODE_CHAR, &txPowerMode);
+ beaconPeriodChar = new ReadWriteGattCharacteristic<uint16_t>(UUID_BEACON_PERIOD_CHAR, &urlFramePeriod);
+ resetChar = new WriteOnlyGattCharacteristic<bool>(UUID_RESET_CHAR, &resetFlag);
+
+ lockChar->setWriteAuthorizationCallback(this, &EddystoneService::lockAuthorizationCallback);
+ unlockChar->setWriteAuthorizationCallback(this, &EddystoneService::unlockAuthorizationCallback);
+ urlDataChar->setWriteAuthorizationCallback(this, &EddystoneService::urlDataWriteAuthorizationCallback);
+ flagsChar->setWriteAuthorizationCallback(this, &EddystoneService::basicAuthorizationCallback<uint8_t>);
+ advPowerLevelsChar->setWriteAuthorizationCallback(this, &EddystoneService::basicAuthorizationCallback<PowerLevels_t>);
+ txPowerModeChar->setWriteAuthorizationCallback(this, &EddystoneService::powerModeAuthorizationCallback);
+ beaconPeriodChar->setWriteAuthorizationCallback(this, &EddystoneService::basicAuthorizationCallback<uint16_t>);
+ resetChar->setWriteAuthorizationCallback(this, &EddystoneService::basicAuthorizationCallback<bool>);
+
+ charTable[0] = lockStateChar;
+ charTable[1] = lockChar;
+ charTable[2] = unlockChar;
+ charTable[3] = urlDataChar;
+ charTable[4] = flagsChar;
+ charTable[5] = advPowerLevelsChar;
+ charTable[6] = txPowerModeChar;
+ charTable[7] = beaconPeriodChar;
+ charTable[8] = resetChar;
+
+ GattService configService(UUID_URL_BEACON_SERVICE, charTable, sizeof(charTable) / sizeof(GattCharacteristic *));
+
+ ble.gattServer().addService(configService);
+ ble.gattServer().onDataWritten(this, &EddystoneService::onDataWrittenCallback);
+ updateCharacteristicValues();
+ setupEddystoneConfigAdvertisements();
+}
+
+void EddystoneService::freeConfigCharacteristics(void)
+{
+ delete lockStateChar;
+ delete lockChar;
+ delete unlockChar;
+ delete urlDataChar;
+ delete flagsChar;
+ delete advPowerLevelsChar;
+ delete txPowerModeChar;
+ delete beaconPeriodChar;
+ delete resetChar;
+}
+
+void EddystoneService::stopBeaconService(void)
+{
+ /* Free unused memory */
+ if (rawUrlFrame) {
+ delete[] rawUrlFrame;
+ rawUrlFrame = NULL;
+ }
+ if (rawUidFrame) {
+ delete[] rawUidFrame;
+ rawUidFrame = NULL;
+ }
+ if (rawTlmFrame) {
+ delete[] rawTlmFrame;
+ rawTlmFrame = NULL;
+ }
+
+ /* Unschedule callbacks */
+ if (urlFrameCallbackHandle) {
+ eventQueue.cancel(urlFrameCallbackHandle);
+ urlFrameCallbackHandle = 0;
+ }
+ if (uidFrameCallbackHandle) {
+ eventQueue.cancel(uidFrameCallbackHandle);
+ uidFrameCallbackHandle = 0;
+ }
+ if (tlmFrameCallbackHandle) {
+ eventQueue.cancel(tlmFrameCallbackHandle);
+ tlmFrameCallbackHandle = 0;
+ }
+ if (radioManagerCallbackHandle) {
+ eventQueue.cancel(radioManagerCallbackHandle);
+ radioManagerCallbackHandle = 0;
+ }
+}
+
+/*
+ * Internal helper function used to update the GATT database following any
+ * change to the internal state of the service object.
+ */
+void EddystoneService::updateCharacteristicValues(void)
+{
+ ble.gattServer().write(lockStateChar->getValueHandle(), reinterpret_cast<uint8_t *>(&lockState), sizeof(bool));
+ ble.gattServer().write(urlDataChar->getValueHandle(), urlFrame.getEncodedURLData(), urlFrame.getEncodedURLDataLength());
+ ble.gattServer().write(flagsChar->getValueHandle(), &flags, sizeof(uint8_t));
+ ble.gattServer().write(beaconPeriodChar->getValueHandle(), reinterpret_cast<uint8_t *>(&urlFramePeriod), sizeof(uint16_t));
+ ble.gattServer().write(txPowerModeChar->getValueHandle(), &txPowerMode, sizeof(uint8_t));
+ ble.gattServer().write(advPowerLevelsChar->getValueHandle(), reinterpret_cast<uint8_t *>(advPowerLevels), sizeof(PowerLevels_t));
+ ble.gattServer().write(lockChar->getValueHandle(), lock, sizeof(PowerLevels_t));
+ ble.gattServer().write(unlockChar->getValueHandle(), unlock, sizeof(PowerLevels_t));
+}
+
+void EddystoneService::setupEddystoneConfigAdvertisements(void)
+{
+ ble.gap().clearAdvertisingPayload();
+
+ /* Accumulate the new payload */
+ ble.gap().accumulateAdvertisingPayload(
+ GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE
+ );
+ /* UUID is in different order in the ADV frame (!) */
+ uint8_t reversedServiceUUID[sizeof(UUID_URL_BEACON_SERVICE)];
+ for (size_t i = 0; i < sizeof(UUID_URL_BEACON_SERVICE); i++) {
+ reversedServiceUUID[i] = UUID_URL_BEACON_SERVICE[sizeof(UUID_URL_BEACON_SERVICE) - i - 1];
+ }
+ ble.gap().accumulateAdvertisingPayload(
+ GapAdvertisingData::COMPLETE_LIST_128BIT_SERVICE_IDS,
+ reversedServiceUUID,
+ sizeof(reversedServiceUUID)
+ );
+ ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::GENERIC_TAG);
+ setupEddystoneConfigScanResponse();
+
+ ble.gap().setTxPower(radioPowerLevels[txPowerMode]);
+ ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
+ ble.gap().setAdvertisingInterval(advConfigInterval);
+ ble.gap().startAdvertising();
+}
+
+void EddystoneService::setupEddystoneConfigScanResponse(void)
+{
+ ble.gap().clearScanResponse();
+ ble.gap().accumulateScanResponse(
+ GapAdvertisingData::COMPLETE_LOCAL_NAME,
+ reinterpret_cast<const uint8_t *>(deviceName),
+ strlen(deviceName)
+ );
+ ble.gap().accumulateScanResponse(
+ GapAdvertisingData::TX_POWER_LEVEL,
+ reinterpret_cast<uint8_t *>(&advPowerLevels[TX_POWER_MODE_LOW]),
+ sizeof(uint8_t)
+ );
+}
+
+void EddystoneService::lockAuthorizationCallback(GattWriteAuthCallbackParams *authParams)
+{
+ if (lockState) {
+ authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION;
+ } else if (authParams->len != sizeof(Lock_t)) {
+ authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH;
+ } else if (authParams->offset != 0) {
+ authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET;
+ } else {
+ authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
+ }
+}
+
+void EddystoneService::unlockAuthorizationCallback(GattWriteAuthCallbackParams *authParams)
+{
+ if (!lockState && (authParams->len == sizeof(Lock_t))) {
+ authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
+ } else if (authParams->len != sizeof(Lock_t)) {
+ authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH;
+ } else if (authParams->offset != 0) {
+ authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET;
+ } else if (memcmp(authParams->data, lock, sizeof(Lock_t)) != 0) {
+ authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION;
+ } else {
+ authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
+ }
+}
+
+void EddystoneService::urlDataWriteAuthorizationCallback(GattWriteAuthCallbackParams *authParams)
+{
+ if (lockState) {
+ authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION;
+ } else if (authParams->offset != 0) {
+ authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET;
+ } else {
+ authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
+ }
+}
+
+void EddystoneService::powerModeAuthorizationCallback(GattWriteAuthCallbackParams *authParams)
+{
+ if (lockState) {
+ authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION;
+ } else if (authParams->len != sizeof(uint8_t)) {
+ authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH;
+ } else if (authParams->offset != 0) {
+ authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET;
+ } else if (*((uint8_t *)authParams->data) >= NUM_POWER_MODES) {
+ authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_WRITE_NOT_PERMITTED;
+ } else {
+ authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
+ }
+}
+
+template <typename T>
+void EddystoneService::basicAuthorizationCallback(GattWriteAuthCallbackParams *authParams)
+{
+ if (lockState) {
+ authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION;
+ } else if (authParams->len != sizeof(T)) {
+ authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH;
+ } else if (authParams->offset != 0) {
+ authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET;
+ } else {
+ authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
+ }
+}
+
+/*
+ * This callback is invoked when a GATT client attempts to modify any of the
+ * characteristics of this service. Attempts to do so are also applied to
+ * the internal state of this service object.
+ */
+void EddystoneService::onDataWrittenCallback(const GattWriteCallbackParams *writeParams)
+{
+ uint16_t handle = writeParams->handle;
+
+ if (handle == lockChar->getValueHandle()) {
+ memcpy(lock, writeParams->data, sizeof(Lock_t));
+ /* Set the state to be locked by the lock code (note: zeros are a valid lock) */
+ lockState = true;
+ ble.gattServer().write(lockChar->getValueHandle(), lock, sizeof(PowerLevels_t));
+ ble.gattServer().write(lockStateChar->getValueHandle(), reinterpret_cast<uint8_t *>(&lockState), sizeof(bool));
+ } else if (handle == unlockChar->getValueHandle()) {
+ /* Validated earlier */
+ lockState = false;
+ ble.gattServer().write(unlockChar->getValueHandle(), unlock, sizeof(PowerLevels_t));
+ ble.gattServer().write(lockStateChar->getValueHandle(), reinterpret_cast<uint8_t *>(&lockState), sizeof(bool));
+ } else if (handle == urlDataChar->getValueHandle()) {
+ urlFrame.setEncodedURLData(writeParams->data, writeParams->len);
+ ble.gattServer().write(urlDataChar->getValueHandle(), urlFrame.getEncodedURLData(), urlFrame.getEncodedURLDataLength());
+ } else if (handle == flagsChar->getValueHandle()) {
+ flags = *(writeParams->data);
+ ble.gattServer().write(flagsChar->getValueHandle(), &flags, sizeof(uint8_t));
+ } else if (handle == advPowerLevelsChar->getValueHandle()) {
+ memcpy(advPowerLevels, writeParams->data, sizeof(PowerLevels_t));
+ ble.gattServer().write(advPowerLevelsChar->getValueHandle(), reinterpret_cast<uint8_t *>(advPowerLevels), sizeof(PowerLevels_t));
+ } else if (handle == txPowerModeChar->getValueHandle()) {
+ txPowerMode = *(writeParams->data);
+ ble.gattServer().write(txPowerModeChar->getValueHandle(), &txPowerMode, sizeof(uint8_t));
+ } else if (handle == beaconPeriodChar->getValueHandle()) {
+ uint16_t tmpBeaconPeriod = correctAdvertisementPeriod(*((uint16_t *)(writeParams->data)));
+ if (tmpBeaconPeriod != urlFramePeriod) {
+ urlFramePeriod = tmpBeaconPeriod;
+ ble.gattServer().write(beaconPeriodChar->getValueHandle(), reinterpret_cast<uint8_t *>(&urlFramePeriod), sizeof(uint16_t));
+ }
+ } else if (handle == resetChar->getValueHandle() && (*((uint8_t *)writeParams->data) != 0)) {
+ /* Reset characteristics to default values */
+ flags = 0;
+ txPowerMode = TX_POWER_MODE_LOW;
+ urlFramePeriod = DEFAULT_URL_FRAME_PERIOD_MSEC;
+
+ urlFrame.setURLData(DEFAULT_URL);
+ memset(lock, 0, sizeof(Lock_t));
+
+ ble.gattServer().write(urlDataChar->getValueHandle(), urlFrame.getEncodedURLData(), urlFrame.getEncodedURLDataLength());
+ ble.gattServer().write(flagsChar->getValueHandle(), &flags, sizeof(uint8_t));
+ ble.gattServer().write(txPowerModeChar->getValueHandle(), &txPowerMode, sizeof(uint8_t));
+ ble.gattServer().write(beaconPeriodChar->getValueHandle(), reinterpret_cast<uint8_t *>(&urlFramePeriod), sizeof(uint16_t));
+ ble.gattServer().write(lockChar->getValueHandle(), lock, sizeof(PowerLevels_t));
+ }
+}
+
+uint16_t EddystoneService::correctAdvertisementPeriod(uint16_t beaconPeriodIn) const
+{
+ /* Re-map beaconPeriod to within permissible bounds if necessary. */
+ if (beaconPeriodIn != 0) {
+ if (beaconPeriodIn < ble.gap().getMinNonConnectableAdvertisingInterval()) {
+ return ble.gap().getMinNonConnectableAdvertisingInterval();
+ } else if (beaconPeriodIn > ble.gap().getMaxAdvertisingInterval()) {
+ return ble.gap().getMaxAdvertisingInterval();
+ }
+ }
+ return beaconPeriodIn;
+}
+
+void EddystoneService::setURLFrameAdvertisingInterval(uint16_t urlFrameIntervalIn)
+{
+ if (urlFrameIntervalIn == urlFramePeriod) {
+ /* Do nothing */
+ return;
+ }
+
+ /* Make sure the input period is within bounds */
+ urlFramePeriod = correctAdvertisementPeriod(urlFrameIntervalIn);
+
+ if (operationMode == EDDYSTONE_MODE_BEACON) {
+ if (urlFrameCallbackHandle) {
+ eventQueue.cancel(urlFrameCallbackHandle);
+ } else {
+ /* This frame was just enabled */
+ if (!rawUidFrame && urlFramePeriod) {
+ /* Allocate memory for this frame and construct it */
+ rawUrlFrame = new uint8_t[urlFrame.getRawFrameSize()];
+ urlFrame.constructURLFrame(rawUrlFrame, advPowerLevels[txPowerMode]);
+ }
+ }
+
+ if (urlFramePeriod) {
+ /* Currently the only way to change the period of a callback
+ * is to cancel it and reschedule
+ */
+ urlFrameCallbackHandle = eventQueue.post_every(
+ urlFramePeriod,
+ Callback<void(FrameType)>(this, &EddystoneService::enqueueFrame),
+ EDDYSTONE_FRAME_URL
+ );
+ } else {
+ urlFrameCallbackHandle = 0;
+ }
+ } else if (operationMode == EDDYSTONE_MODE_CONFIG) {
+ ble.gattServer().write(beaconPeriodChar->getValueHandle(), reinterpret_cast<uint8_t *>(&urlFramePeriod), sizeof(uint16_t));
+ }
+}
+
+void EddystoneService::setUIDFrameAdvertisingInterval(uint16_t uidFrameIntervalIn)
+{
+ if (uidFrameIntervalIn == uidFramePeriod) {
+ /* Do nothing */
+ return;
+ }
+
+ /* Make sure the input period is within bounds */
+ uidFramePeriod = correctAdvertisementPeriod(uidFrameIntervalIn);
+
+ if (operationMode == EDDYSTONE_MODE_BEACON) {
+ if (uidFrameCallbackHandle) {
+ /* The advertisement interval changes, update the periodic callback */
+ eventQueue.cancel(uidFrameCallbackHandle);
+ } else {
+ /* This frame was just enabled */
+ if (!rawUidFrame && uidFramePeriod) {
+ /* Allocate memory for this frame and construct it */
+ rawUidFrame = new uint8_t[uidFrame.getRawFrameSize()];
+ uidFrame.constructUIDFrame(rawUidFrame, advPowerLevels[txPowerMode]);
+ }
+ }
+
+ if (uidFramePeriod) {
+ /* Currently the only way to change the period of a callback
+ * is to cancel it and reschedule
+ */
+ uidFrameCallbackHandle = eventQueue.post_every(
+ uidFramePeriod,
+ Callback<void(FrameType)>(this, &EddystoneService::enqueueFrame),
+ EDDYSTONE_FRAME_UID
+ );
+ } else {
+ uidFrameCallbackHandle = 0;
+ }
+ }
+}
+
+void EddystoneService::setTLMFrameAdvertisingInterval(uint16_t tlmFrameIntervalIn)
+{
+ if (tlmFrameIntervalIn == tlmFramePeriod) {
+ /* Do nothing */
+ return;
+ }
+
+ /* Make sure the input period is within bounds */
+ tlmFramePeriod = correctAdvertisementPeriod(tlmFrameIntervalIn);
+
+ if (operationMode == EDDYSTONE_MODE_BEACON) {
+ if (tlmFrameCallbackHandle) {
+ /* The advertisement interval changes, update periodic callback */
+ eventQueue.cancel(tlmFrameCallbackHandle);
+ } else {
+ /* This frame was just enabled */
+ if (!rawTlmFrame && tlmFramePeriod) {
+ /* Allocate memory for this frame and construct it */
+ rawTlmFrame = new uint8_t[tlmFrame.getRawFrameSize()];
+ /* Do not construct the TLM frame because this changes every 0.1 seconds */
+ }
+ }
+
+ if (tlmFramePeriod) {
+ /* Currently the only way to change the period of a callback
+ * is to cancel it and reschedule
+ */
+ tlmFrameCallbackHandle = eventQueue.post_every(
+ tlmFramePeriod,
+ Callback<void(FrameType)>(this, &EddystoneService::enqueueFrame),
+ EDDYSTONE_FRAME_TLM
+ );
+ } else {
+ tlmFrameCallbackHandle = 0;
+ }
+ }
+}