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.
Dependencies: BLE_API LIS3DH mbed nRF51822 BMC050 nRF51_LowPwr nRF51_Vdd
Fork of BLE_EddystoneBeacon_Service by
Revision 34:f6d4a699a1ea, committed 2015-11-26
- Comitter:
- andresag
- Date:
- Thu Nov 26 16:36:04 2015 +0000
- Parent:
- 33:862e6e0831ea
- Child:
- 35:c64495aac4d1
- Commit message:
- Update example to use latest version of BLE_API and nRF51822 library. Furthermore, the example now uses a new Eddystone implementation rather than the deprecated implementation under ble/services.
Changed in this revision
--- a/BLE_API.lib Thu Oct 01 18:30:37 2015 +0000 +++ b/BLE_API.lib Thu Nov 26 16:36:04 2015 +0000 @@ -1,1 +1,1 @@ -http://mbed.org/teams/Bluetooth-Low-Energy/code/BLE_API/#d494ad3e87bd +http://mbed.org/teams/Bluetooth-Low-Energy/code/BLE_API/#c6c50b28ccd2
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/EddystoneService.cpp Thu Nov 26 16:36:04 2015 +0000
@@ -0,0 +1,570 @@
+/* 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 &advPowerLevelsIn,
+ const PowerLevels_t &radioPowerLevelsIn,
+ uint32_t advConfigIntervalIn) :
+ ble(bleIn),
+ operationMode(EDDYSTONE_MODE_NONE),
+ urlFrame(paramsIn.urlData, paramsIn.urlDataLength),
+ uidFrame(paramsIn.uidNamespaceID, paramsIn.uidInstanceID),
+ tlmFrame(paramsIn.tlmVersion),
+ resetFlag(false),
+ tlmBatteryVoltageCallback(NULL),
+ tlmBeaconTemperatureCallback(NULL)
+{
+ lockState = paramsIn.lockState;
+ flags = paramsIn.flags;
+ txPowerMode = paramsIn.txPowerMode;
+ beaconPeriod = correctAdvertisementPeriod(paramsIn.beaconPeriod);
+
+ memcpy(lock, paramsIn.lock, sizeof(Lock_t));
+ memcpy(unlock, paramsIn.unlock, sizeof(Lock_t));
+
+ eddystoneConstructorHelper(advPowerLevelsIn, 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,
+ uint32_t advConfigIntervalIn) :
+ ble(bleIn),
+ operationMode(EDDYSTONE_MODE_NONE),
+ urlFrame(),
+ uidFrame(),
+ tlmFrame(),
+ lockState(false),
+ resetFlag(false),
+ lock(),
+ unlock(),
+ flags(0),
+ txPowerMode(0),
+ beaconPeriod(DEFAULT_BEACON_PERIOD_MSEC),
+ tlmBatteryVoltageCallback(NULL),
+ tlmBeaconTemperatureCallback(NULL)
+{
+ 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();
+ /* Free unused memory */
+ freeBeaconFrames();
+ operationMode = EDDYSTONE_MODE_CONFIG;
+ ble.init(this, &EddystoneService::bleInitComplete);
+ return EDDYSTONE_ERROR_NONE;
+ }
+
+ operationMode = EDDYSTONE_MODE_CONFIG;
+ setupConfigService();
+ return EDDYSTONE_ERROR_NONE;
+}
+
+EddystoneService::EddystoneError_t EddystoneService::startBeaconService(uint16_t consecUrlFramesIn, uint16_t consecUidFramesIn, uint16_t consecTlmFramesIn)
+{
+ if (operationMode == EDDYSTONE_MODE_BEACON) {
+ /* Nothing to do, we are already in beacon mode */
+ return EDDYSTONE_ERROR_NONE;
+ } else if (!consecUrlFramesIn && !consecUidFramesIn && !consecTlmFramesIn) {
+ /* Nothing to do, the user wants 0 consecutive frames of everything */
+ return EDDYSTONE_ERROR_INVALID_CONSEC_FRAMES;
+ } else if (!beaconPeriod) {
+ /* Nothing to do, the period is 0 for all frames */
+ return EDDYSTONE_ERROR_INVALID_BEACON_PERIOD;
+ }
+
+ /* Setup tracking of the current advertised frame. Note that this will
+ * cause URL or UID frames to be advertised first!
+ */
+ currentAdvertisedFrame = EDDYSTONE_FRAME_TLM;
+ consecFrames[EDDYSTONE_FRAME_URL] = consecUrlFramesIn;
+ consecFrames[EDDYSTONE_FRAME_UID] = consecUidFramesIn;
+ consecFrames[EDDYSTONE_FRAME_TLM] = consecTlmFramesIn;
+
+ memset(currentConsecFrames, 0, sizeof(uint16_t) * NUM_EDDYSTONE_FRAMES);
+
+ if (operationMode == EDDYSTONE_MODE_CONFIG) {
+ ble.shutdown();
+ /* Free unused memory */
+ freeConfigCharacteristics();
+ operationMode = EDDYSTONE_MODE_BEACON;
+ ble.init(this, &EddystoneService::bleInitComplete);
+ return EDDYSTONE_ERROR_NONE;
+ }
+
+ operationMode = EDDYSTONE_MODE_BEACON;
+ setupBeaconService();
+ return EDDYSTONE_ERROR_NONE;
+}
+
+/* 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 *params)
+{
+ params->lockState = lockState;
+ params->flags = flags;
+ params->txPowerMode = txPowerMode;
+ params->beaconPeriod = beaconPeriod;
+ 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)
+{
+ advConfigInterval = (advConfigIntervalIn > 0) ? correctAdvertisementPeriod(advConfigIntervalIn) : 0;
+
+ 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();
+}
+
+/* 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;
+ default:
+ /* Some error occurred */
+ break;
+ }
+}
+
+void EddystoneService::swapAdvertisedFrame(void)
+{
+ /* This essentially works out which is the next frame to be swapped in
+ * and updated the advertised packets. It will eventually terminate
+ * and in the worst case the frame swapped in is the current advertised
+ * frame.
+ */
+ while (true) {
+ currentAdvertisedFrame = (currentAdvertisedFrame + 1) % NUM_EDDYSTONE_FRAMES;
+
+ if (currentAdvertisedFrame == EDDYSTONE_FRAME_URL && consecFrames[EDDYSTONE_FRAME_URL] > 0) {
+ updateAdvertisementPacket(rawUrlFrame, urlFrame.getRawFrameSize());
+ return;
+ } else if (currentAdvertisedFrame == EDDYSTONE_FRAME_UID && consecFrames[EDDYSTONE_FRAME_UID] > 0) {
+ updateAdvertisementPacket(rawUidFrame, uidFrame.getRawFrameSize());
+ return;
+ } else if (currentAdvertisedFrame == EDDYSTONE_FRAME_TLM && consecFrames[EDDYSTONE_FRAME_UID] > 0) {
+ updateRawTLMFrame();
+ updateAdvertisementPacket(rawTlmFrame, tlmFrame.getRawFrameSize());
+ return;
+ }
+ }
+}
+
+/* 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 (consecFrames[EDDYSTONE_FRAME_URL] > 0) {
+ rawUrlFrame = new uint8_t[urlFrame.getRawFrameSize()];
+ urlFrame.constructURLFrame(rawUrlFrame, advPowerLevels[txPowerMode]);
+ }
+
+ if (consecFrames[EDDYSTONE_FRAME_UID] > 0) {
+ rawUidFrame = new uint8_t[uidFrame.getRawFrameSize()];
+ uidFrame.constructUIDFrame(rawUidFrame, advPowerLevels[txPowerMode]);
+ }
+
+ if (consecFrames[EDDYSTONE_FRAME_TLM] > 0) {
+ 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(beaconPeriod);
+ ble.gap().onRadioNotification(this, &EddystoneService::radioNotificationCallback);
+ ble.gap().initRadioNotification();
+
+ /* Set advertisement packet payload */
+ swapAdvertisedFrame();
+
+ /* Start advertising */
+ ble.gap().startAdvertising();
+}
+
+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, &beaconPeriod);
+ 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::freeBeaconFrames(void)
+{
+ delete[] rawUrlFrame;
+ delete[] rawUidFrame;
+ delete[] rawTlmFrame;
+}
+
+void EddystoneService::radioNotificationCallback(bool radioActive)
+{
+ if (radioActive) {
+ /* Do nothing */
+ return;
+ }
+
+ tlmFrame.updatePduCount();
+ currentConsecFrames[currentAdvertisedFrame]++;
+
+ if (consecFrames[currentAdvertisedFrame] > currentConsecFrames[currentAdvertisedFrame]) {
+ if (currentAdvertisedFrame == EDDYSTONE_FRAME_TLM) {
+ /* Update the TLM frame otherwise we will not meet the 0.1 secs resolution of
+ * the Eddystone specification.
+ */
+ updateRawTLMFrame();
+ updateAdvertisementPacket(rawTlmFrame, tlmFrame.getRawFrameSize());
+ }
+ /* Keep advertising the same frame */
+ return;
+ }
+
+ currentConsecFrames[currentAdvertisedFrame] = 0;
+
+#ifdef YOTTA_CFG_MBED_OS
+ minar::Scheduler::postCallback(this, &EddystoneService::swapAdvertisedFrame);
+#else
+ swapAdvertisedFrameTimeout.attach_us(this, &EddystoneService::swapAdvertisedFrame, 1);
+#endif
+}
+
+/*
+ * 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 *>(&beaconPeriod), 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();
+ 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);
+ ble.gap().accumulateScanResponse(GapAdvertisingData::COMPLETE_LOCAL_NAME, reinterpret_cast<const uint8_t *>(&DEVICE_NAME), sizeof(DEVICE_NAME));
+ ble.gap().accumulateScanResponse(
+ GapAdvertisingData::TX_POWER_LEVEL,
+ reinterpret_cast<uint8_t *>(&advPowerLevels[TX_POWER_MODE_LOW]),
+ sizeof(uint8_t));
+
+ ble.gap().setTxPower(radioPowerLevels[txPowerMode]);
+ ble.gap().setDeviceName(reinterpret_cast<const uint8_t *>(&DEVICE_NAME));
+ ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
+ ble.gap().setAdvertisingInterval(advConfigInterval);
+ ble.gap().startAdvertising();
+}
+
+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 != beaconPeriod) {
+ beaconPeriod = tmpBeaconPeriod;
+ ble.gattServer().write(beaconPeriodChar->getValueHandle(), reinterpret_cast<uint8_t *>(&beaconPeriod), 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;
+ beaconPeriod = DEFAULT_BEACON_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 *>(&beaconPeriod), 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().getMinAdvertisingInterval()) {
+ return ble.gap().getMinAdvertisingInterval();
+ } else if (beaconPeriodIn > ble.gap().getMaxAdvertisingInterval()) {
+ return ble.gap().getMaxAdvertisingInterval();
+ }
+ }
+ return beaconPeriodIn;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/EddystoneService.h Thu Nov 26 16:36:04 2015 +0000
@@ -0,0 +1,238 @@
+/* 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.
+ */
+
+#ifndef __EDDYSTONESERVICE_H__
+#define __EDDYSTONESERVICE_H__
+
+#include "ble/BLE.h"
+#include "EddystoneTypes.h"
+#include "URLFrame.h"
+#include "UIDFrame.h"
+#include "TLMFrame.h"
+#include <string.h>
+#ifdef YOTTA_CFG_MBED_OS
+ #include "mbed-drivers/mbed.h"
+#else
+ #include "mbed.h"
+#endif
+
+class EddystoneService
+{
+public:
+ static const uint16_t TOTAL_CHARACTERISTICS = 9;
+
+ static const uint32_t DEFAULT_CONFIG_PERIOD_MSEC = 1000;
+ static const uint16_t DEFAULT_BEACON_PERIOD_MSEC = 1000;
+
+ /* Operation modes of the EddystoneService:
+ * NONE: EddystoneService has been initialised but no memory has been
+ * dynamically allocated. Additionally, no services are running
+ * nothing is being advertised.
+ * CONFIG: EddystoneService has been initialised, the configuration
+ * service started and memory has been allocated for BLE
+ * characteristics. Memory consumption peaks during CONFIG
+ * mode.
+ * BEACON: Eddystone service is running as a beacon advertising URL,
+ * UID and/or TLM frames depending on how it is configured.
+ * Note: The main app can change the mode of EddystoneService at any point
+ * of time by calling startConfigService() or startBeaconService().
+ * Resources from the previous mode will be freed. It is currently NOT
+ * possible to force EddystoneService back into MODE_NONE.
+ */
+ enum OperationModes {
+ EDDYSTONE_MODE_NONE,
+ EDDYSTONE_MODE_CONFIG,
+ EDDYSTONE_MODE_BEACON
+ };
+
+ struct EddystoneParams_t {
+ bool lockState;
+ Lock_t lock;
+ Lock_t unlock;
+ uint8_t flags;
+ PowerLevels_t advPowerLevels;
+ uint8_t txPowerMode;
+ uint16_t beaconPeriod;
+ uint8_t tlmVersion;
+ uint8_t urlDataLength;
+ UrlData_t urlData;
+ UIDNamespaceID_t uidNamespaceID;
+ UIDInstanceID_t uidInstanceID;
+ };
+
+ enum EddystoneError_t {
+ EDDYSTONE_ERROR_NONE,
+ EDDYSTONE_ERROR_INVALID_BEACON_PERIOD,
+ EDDYSTONE_ERROR_INVALID_CONSEC_FRAMES,
+ EDDYSTONE_ERROR_INVALID_ADVERTISING_INTERVAL
+ };
+
+ enum FrameType {
+ EDDYSTONE_FRAME_URL,
+ EDDYSTONE_FRAME_UID,
+ EDDYSTONE_FRAME_TLM,
+ NUM_EDDYSTONE_FRAMES
+ };
+
+ /* Initialise the EddystoneService using parameters from persistent storage */
+ EddystoneService(BLE &bleIn,
+ EddystoneParams_t ¶msIn,
+ const PowerLevels_t &advPowerLevelsIn,
+ const PowerLevels_t &radioPowerLevelsIn,
+ uint32_t advConfigIntervalIn = DEFAULT_CONFIG_PERIOD_MSEC);
+
+ /* When using this constructor we need to call setURLData,
+ * setTMLData and setUIDData to initialise values manually
+ */
+ EddystoneService(BLE &bleIn,
+ const PowerLevels_t &advPowerLevelsIn,
+ const PowerLevels_t &radioPowerLevelsIn,
+ uint32_t advConfigIntervalIn = DEFAULT_CONFIG_PERIOD_MSEC);
+
+ /* Setup callback to update BatteryVoltage in TLM frame */
+ void onTLMBatteryVoltageUpdate(TlmUpdateCallback_t tlmBatteryVoltageCallbackIn);
+
+ /* Setup callback to update BeaconTemperature in TLM frame */
+ void onTLMBeaconTemperatureUpdate(TlmUpdateCallback_t tlmBeaconTemperatureCallbackIn);
+
+ void setTLMData(uint8_t tlmVersionIn = 0);
+
+ void setURLData(const char *urlDataIn);
+
+ void setUIDData(const UIDNamespaceID_t *uidNamespaceIDIn, const UIDInstanceID_t *uidInstanceIDIn);
+
+ EddystoneError_t startConfigService(void);
+
+ EddystoneError_t startBeaconService(uint16_t consecUrlFramesIn = 2, uint16_t consecUidFramesIn = 2, uint16_t consecTlmFramesIn = 2);
+
+ /* 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 getEddystoneParams(EddystoneParams_t *params);
+
+private:
+ /* Helper function used only once during constructing the object to avoid
+ * duplicated code.
+ */
+ void eddystoneConstructorHelper(const PowerLevels_t &advPowerLevelsIn,
+ const PowerLevels_t &radioPowerLevelsIn,
+ uint32_t advConfigIntervalIn);
+
+ /* When changing modes, we shutdown and init the BLE instance, so
+ * this is needed to complete the initialisation task.
+ */
+ void bleInitComplete(BLE::InitializationCompleteCallbackContext* initContext);
+
+ void swapAdvertisedFrame(void);
+
+ void updateAdvertisementPacket(const uint8_t* rawFrame, size_t rawFrameLength);
+
+ /* 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 updateRawTLMFrame(void);
+
+ void setupBeaconService(void);
+
+ void setupConfigService(void);
+
+ void freeConfigCharacteristics(void);
+
+ void freeBeaconFrames(void);
+
+ void radioNotificationCallback(bool radioActive);
+
+ /*
+ * Internal helper function used to update the GATT database following any
+ * change to the internal state of the service object.
+ */
+ void updateCharacteristicValues(void);
+
+ void setupEddystoneConfigAdvertisements(void);
+
+ void lockAuthorizationCallback(GattWriteAuthCallbackParams *authParams);
+
+ void unlockAuthorizationCallback(GattWriteAuthCallbackParams *authParams);
+
+ void urlDataWriteAuthorizationCallback(GattWriteAuthCallbackParams *authParams);
+
+ void powerModeAuthorizationCallback(GattWriteAuthCallbackParams *authParams);
+
+ template <typename T>
+ void basicAuthorizationCallback(GattWriteAuthCallbackParams *authParams);
+
+ /*
+ * 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 onDataWrittenCallback(const GattWriteCallbackParams *writeParams);
+
+ uint16_t correctAdvertisementPeriod(uint16_t beaconPeriodIn) const;
+
+ BLE &ble;
+ uint32_t advConfigInterval;
+ uint8_t operationMode;
+
+ URLFrame urlFrame;
+ UIDFrame uidFrame;
+ TLMFrame tlmFrame;
+
+ PowerLevels_t radioPowerLevels;
+ PowerLevels_t advPowerLevels;
+ bool lockState;
+ bool resetFlag;
+ Lock_t lock;
+ Lock_t unlock;
+ uint8_t flags;
+ uint8_t txPowerMode;
+ uint16_t beaconPeriod;
+
+ ReadOnlyGattCharacteristic<bool> *lockStateChar;
+ WriteOnlyArrayGattCharacteristic<uint8_t, sizeof(Lock_t)> *lockChar;
+ WriteOnlyArrayGattCharacteristic<uint8_t, sizeof(Lock_t)> *unlockChar;
+ GattCharacteristic *urlDataChar;
+ ReadWriteGattCharacteristic<uint8_t> *flagsChar;
+ ReadWriteArrayGattCharacteristic<int8_t, sizeof(PowerLevels_t)> *advPowerLevelsChar;
+ ReadWriteGattCharacteristic<uint8_t> *txPowerModeChar;
+ ReadWriteGattCharacteristic<uint16_t> *beaconPeriodChar;
+ WriteOnlyGattCharacteristic<bool> *resetChar;
+
+ uint8_t *rawUrlFrame;
+ uint8_t *rawUidFrame;
+ uint8_t *rawTlmFrame;
+
+ uint16_t consecFrames[NUM_EDDYSTONE_FRAMES];
+ uint16_t currentConsecFrames[NUM_EDDYSTONE_FRAMES];
+ uint8_t currentAdvertisedFrame;
+
+ TlmUpdateCallback_t tlmBatteryVoltageCallback;
+ TlmUpdateCallback_t tlmBeaconTemperatureCallback;
+
+ Timer timeSinceBootTimer;
+#ifndef YOTTA_CFG_MBED_OS
+ Timeout swapAdvertisedFrameTimeout;
+#endif
+
+ GattCharacteristic *charTable[TOTAL_CHARACTERISTICS];
+};
+
+#endif /* __EDDYSTONESERVICE_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/EddystoneTypes.h Thu Nov 26 16:36:04 2015 +0000
@@ -0,0 +1,72 @@
+/* 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.
+ */
+
+#ifndef __EDDYSTONETYPES_H__
+#define __EDDYSTONETYPES_H__
+
+#include <stdint.h>
+#include <stddef.h>
+
+#define UUID_URL_BEACON(FIRST, SECOND) { \
+ 0xee, 0x0c, FIRST, SECOND, 0x87, 0x86, 0x40, 0xba, \
+ 0xab, 0x96, 0x99, 0xb9, 0x1a, 0xc9, 0x81, 0xd8, \
+}
+
+const uint8_t EDDYSTONE_UUID[] = {0xAA, 0xFE};
+
+const uint8_t UUID_URL_BEACON_SERVICE[] = UUID_URL_BEACON(0x20, 0x80);
+const uint8_t UUID_LOCK_STATE_CHAR[] = UUID_URL_BEACON(0x20, 0x81);
+const uint8_t UUID_LOCK_CHAR[] = UUID_URL_BEACON(0x20, 0x82);
+const uint8_t UUID_UNLOCK_CHAR[] = UUID_URL_BEACON(0x20, 0x83);
+const uint8_t UUID_URL_DATA_CHAR[] = UUID_URL_BEACON(0x20, 0x84);
+const uint8_t UUID_FLAGS_CHAR[] = UUID_URL_BEACON(0x20, 0x85);
+const uint8_t UUID_ADV_POWER_LEVELS_CHAR[] = UUID_URL_BEACON(0x20, 0x86);
+const uint8_t UUID_TX_POWER_MODE_CHAR[] = UUID_URL_BEACON(0x20, 0x87);
+const uint8_t UUID_BEACON_PERIOD_CHAR[] = UUID_URL_BEACON(0x20, 0x88);
+const uint8_t UUID_RESET_CHAR[] = UUID_URL_BEACON(0x20, 0x89);
+
+const char DEVICE_NAME[] = "EDDYSTONE CONFIG";
+
+const char DEFAULT_URL[] = "http://www.mbed.com/";
+
+enum PowerModes {
+ TX_POWER_MODE_LOWEST,
+ TX_POWER_MODE_LOW,
+ TX_POWER_MODE_MEDIUM,
+ TX_POWER_MODE_HIGH,
+ NUM_POWER_MODES
+};
+
+/* 128 bits of lock */
+typedef uint8_t Lock_t[16];
+typedef int8_t PowerLevels_t[NUM_POWER_MODES];
+
+const uint16_t URL_DATA_MAX = 18;
+typedef uint8_t UrlData_t[URL_DATA_MAX];
+
+/* UID Frame Type subfields */
+const size_t UID_NAMESPACEID_SIZE = 10;
+typedef uint8_t UIDNamespaceID_t[UID_NAMESPACEID_SIZE];
+const size_t UID_INSTANCEID_SIZE = 6;
+typedef uint8_t UIDInstanceID_t[UID_INSTANCEID_SIZE];
+
+/* Callbacks for updating BateryVoltage and Temperature */
+typedef uint16_t (*TlmUpdateCallback_t) (uint16_t);
+
+/* Size of Eddystone UUID needed for all frames */
+const uint16_t EDDYSTONE_UUID_SIZE = sizeof(EDDYSTONE_UUID);
+
+#endif /* __EDDYSTONETYPES_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/TLMFrame.cpp Thu Nov 26 16:36:04 2015 +0000
@@ -0,0 +1,105 @@
+/* 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 "TLMFrame.h"
+
+TLMFrame::TLMFrame(uint8_t tlmVersionIn,
+ uint16_t tlmBatteryVoltageIn,
+ uint16_t tlmBeaconTemperatureIn,
+ uint32_t tlmPduCountIn,
+ uint32_t tlmTimeSinceBootIn) :
+ tlmVersion(tlmVersionIn),
+ lastTimeSinceBootRead(0),
+ tlmBatteryVoltage(tlmBatteryVoltageIn),
+ tlmBeaconTemperature(tlmBeaconTemperatureIn),
+ tlmPduCount(tlmPduCountIn),
+ tlmTimeSinceBoot(tlmTimeSinceBootIn)
+{
+}
+
+void TLMFrame::setTLMData(uint8_t tlmVersionIn)
+{
+ /* According to the Eddystone spec BatteryVoltage is 0 and
+ * BeaconTemperature is 0x8000 if not supported
+ */
+ tlmVersion = tlmVersionIn;
+ tlmBatteryVoltage = 0;
+ tlmBeaconTemperature = 0x8000;
+ tlmPduCount = 0;
+ tlmTimeSinceBoot = 0;
+}
+
+void TLMFrame::constructTLMFrame(uint8_t *rawFrame)
+{
+ size_t index = 0;
+ rawFrame[index++] = EDDYSTONE_UUID[0]; // 16-bit Eddystone UUID
+ rawFrame[index++] = EDDYSTONE_UUID[1];
+ rawFrame[index++] = FRAME_TYPE_TLM; // Eddystone frame type = Telemetry
+ rawFrame[index++] = tlmVersion; // TLM Version Number
+ rawFrame[index++] = (uint8_t)(tlmBatteryVoltage >> 8); // Battery Voltage[0]
+ rawFrame[index++] = (uint8_t)(tlmBatteryVoltage >> 0); // Battery Voltage[1]
+ rawFrame[index++] = (uint8_t)(tlmBeaconTemperature >> 8); // Beacon Temp[0]
+ rawFrame[index++] = (uint8_t)(tlmBeaconTemperature >> 0); // Beacon Temp[1]
+ rawFrame[index++] = (uint8_t)(tlmPduCount >> 24); // PDU Count [0]
+ rawFrame[index++] = (uint8_t)(tlmPduCount >> 16); // PDU Count [1]
+ rawFrame[index++] = (uint8_t)(tlmPduCount >> 8); // PDU Count [2]
+ rawFrame[index++] = (uint8_t)(tlmPduCount >> 0); // PDU Count [3]
+ rawFrame[index++] = (uint8_t)(tlmTimeSinceBoot >> 24); // Time Since Boot [0]
+ rawFrame[index++] = (uint8_t)(tlmTimeSinceBoot >> 16); // Time Since Boot [1]
+ rawFrame[index++] = (uint8_t)(tlmTimeSinceBoot >> 8); // Time Since Boot [2]
+ rawFrame[index++] = (uint8_t)(tlmTimeSinceBoot >> 0); // Time Since Boot [3]
+}
+
+size_t TLMFrame::getRawFrameSize(void) const
+{
+ return FRAME_SIZE_TLM + EDDYSTONE_UUID_SIZE;
+}
+
+void TLMFrame::updateTimeSinceBoot(uint32_t nowInMillis)
+{
+ tlmTimeSinceBoot += (nowInMillis - lastTimeSinceBootRead) / 100;
+ lastTimeSinceBootRead = nowInMillis;
+}
+
+void TLMFrame::updateBatteryVoltage(uint16_t tlmBatteryVoltageIn)
+{
+ tlmBatteryVoltage = tlmBatteryVoltageIn;
+}
+
+void TLMFrame::updateBeaconTemperature(uint16_t tlmBeaconTemperatureIn)
+{
+ tlmBeaconTemperature = tlmBeaconTemperatureIn;
+}
+
+void TLMFrame::updatePduCount(void)
+{
+ tlmPduCount++;
+}
+
+uint16_t TLMFrame::getBatteryVoltage(void) const
+{
+ return tlmBatteryVoltage;
+}
+
+uint16_t TLMFrame::getBeaconTemperature(void) const
+{
+ return tlmBeaconTemperature;
+}
+
+uint8_t TLMFrame::getTLMVersion(void) const
+{
+ return tlmVersion;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/TLMFrame.h Thu Nov 26 16:36:04 2015 +0000
@@ -0,0 +1,63 @@
+/* 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.
+ */
+
+#ifndef __TLMFRAME_H__
+#define __TLMFRAME_H__
+
+#include "EddystoneTypes.h"
+
+class TLMFrame
+{
+public:
+ TLMFrame(uint8_t tlmVersionIn = 0,
+ uint16_t tlmBatteryVoltageIn = 0,
+ uint16_t tlmBeaconTemperatureIn = 0x8000,
+ uint32_t tlmPduCountIn = 0,
+ uint32_t tlmTimeSinceBootIn = 0);
+
+ void setTLMData(uint8_t tlmVersionIn = 0);
+
+ void constructTLMFrame(uint8_t *rawFrame);
+
+ size_t getRawFrameSize(void) const;
+
+ void updateTimeSinceBoot(uint32_t nowInMillis);
+
+ void updateBatteryVoltage(uint16_t tlmBatteryVoltageIn);
+
+ void updateBeaconTemperature(uint16_t tlmBeaconTemperatureIn);
+
+ void updatePduCount(void);
+
+ uint16_t getBatteryVoltage(void) const;
+
+ uint16_t getBeaconTemperature(void) const;
+
+ uint8_t getTLMVersion(void) const;
+
+private:
+ static const uint8_t FRAME_TYPE_TLM = 0x20;
+ static const uint8_t FRAME_SIZE_TLM = 14;
+
+ uint8_t tlmVersion;
+ uint32_t lastTimeSinceBootRead;
+ uint16_t tlmBatteryVoltage;
+ uint16_t tlmBeaconTemperature;
+ uint32_t tlmPduCount;
+ uint32_t tlmTimeSinceBoot;
+};
+
+#endif /* __TLMFRAME_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/UIDFrame.cpp Thu Nov 26 16:36:04 2015 +0000
@@ -0,0 +1,67 @@
+/* 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 "UIDFrame.h"
+
+UIDFrame::UIDFrame(void)
+{
+ memset(uidNamespaceID, 0, sizeof(UIDNamespaceID_t));
+ memset(uidInstanceID, 0, sizeof(UIDInstanceID_t));
+}
+
+UIDFrame::UIDFrame(const UIDNamespaceID_t uidNamespaceIDIn, const UIDInstanceID_t uidInstanceIDIn)
+{
+ memcpy(uidNamespaceID, uidNamespaceIDIn, sizeof(UIDNamespaceID_t));
+ memcpy(uidInstanceID, uidInstanceIDIn, sizeof(UIDInstanceID_t));
+}
+
+void UIDFrame::setUIDData(const UIDNamespaceID_t *uidNamespaceIDIn, const UIDInstanceID_t *uidInstanceIDIn)
+{
+ memcpy(uidNamespaceID, uidNamespaceIDIn, sizeof(UIDNamespaceID_t));
+ memcpy(uidInstanceID, uidInstanceIDIn, sizeof(UIDInstanceID_t));
+}
+
+void UIDFrame::constructUIDFrame(uint8_t *rawFrame, int8_t advPowerLevel)
+{
+ size_t index = 0;
+
+ rawFrame[index++] = EDDYSTONE_UUID[0]; // 16-bit Eddystone UUID
+ rawFrame[index++] = EDDYSTONE_UUID[1];
+ rawFrame[index++] = FRAME_TYPE_UID; // 1B Type
+ rawFrame[index++] = advPowerLevel; // 1B Power @ 0meter
+
+ memcpy(rawFrame + index, uidNamespaceID, sizeof(UIDNamespaceID_t)); // 10B Namespace ID
+ index += sizeof(UIDNamespaceID_t);
+ memcpy(rawFrame + index, uidInstanceID, sizeof(UIDInstanceID_t)); // 6B Instance ID
+ index += sizeof(UIDInstanceID_t);
+
+ memset(rawFrame + index, 0, 2 * sizeof(uint8_t)); // 2B RFU, which are unused
+}
+
+size_t UIDFrame::getRawFrameSize(void) const
+{
+ return FRAME_SIZE_UID + EDDYSTONE_UUID_SIZE;
+}
+
+uint8_t* UIDFrame::getUIDNamespaceID(void)
+{
+ return uidNamespaceID;
+}
+
+uint8_t* UIDFrame::getUIDInstanceID(void)
+{
+ return uidInstanceID;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/UIDFrame.h Thu Nov 26 16:36:04 2015 +0000
@@ -0,0 +1,48 @@
+/* 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.
+ */
+
+#ifndef __UIDFRAME_H__
+#define __UIDFRAME_H__
+
+#include <string.h>
+#include "EddystoneTypes.h"
+
+class UIDFrame
+{
+public:
+ UIDFrame(void);
+
+ UIDFrame(const UIDNamespaceID_t uidNamespaceIDIn, const UIDInstanceID_t uidInstanceIDIn);
+
+ void setUIDData(const UIDNamespaceID_t *uidNamespaceIDIn, const UIDInstanceID_t *uidInstanceIDIn);
+
+ void constructUIDFrame(uint8_t *rawFrame, int8_t advPowerLevel);
+
+ size_t getRawFrameSize(void) const;
+
+ uint8_t* getUIDNamespaceID(void);
+
+ uint8_t* getUIDInstanceID(void);
+
+private:
+ static const uint8_t FRAME_TYPE_UID = 0x00;
+ static const uint8_t FRAME_SIZE_UID = 20;
+
+ UIDNamespaceID_t uidNamespaceID;
+ UIDInstanceID_t uidInstanceID;
+};
+
+#endif /* __UIDFRAME_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/URLFrame.cpp Thu Nov 26 16:36:04 2015 +0000
@@ -0,0 +1,141 @@
+/* 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 "URLFrame.h"
+
+URLFrame::URLFrame(void)
+{
+ urlDataLength = 0;
+ memset(urlData, 0, sizeof(UrlData_t));
+}
+
+URLFrame::URLFrame(const char *urlDataIn)
+{
+ encodeURL(urlDataIn);
+}
+
+URLFrame::URLFrame(UrlData_t urlDataIn, uint8_t urlDataLength)
+{
+ if (urlDataLength > URL_DATA_MAX) {
+ memcpy(urlData, urlDataIn, URL_DATA_MAX);
+ } else {
+ memcpy(urlData, urlDataIn, urlDataLength);
+ }
+}
+
+void URLFrame::constructURLFrame(uint8_t* rawFrame, int8_t advPowerLevel)
+{
+ size_t index = 0;
+ rawFrame[index++] = EDDYSTONE_UUID[0]; // 16-bit Eddystone UUID
+ rawFrame[index++] = EDDYSTONE_UUID[1];
+ rawFrame[index++] = FRAME_TYPE_URL; // 1B Type
+ rawFrame[index++] = advPowerLevel; // 1B Power @ 0meter
+ memcpy(rawFrame + index, urlData, urlDataLength); // Encoded URL
+}
+
+size_t URLFrame::getRawFrameSize(void) const
+{
+ return urlDataLength + FRAME_MIN_SIZE_URL + EDDYSTONE_UUID_SIZE;
+}
+
+uint8_t* URLFrame::getEncodedURLData(void)
+{
+ return urlData;
+}
+
+uint8_t URLFrame::getEncodedURLDataLength(void) const
+{
+ return urlDataLength;
+}
+
+void URLFrame::setURLData(const char *urlDataIn)
+{
+ encodeURL(urlDataIn);
+}
+
+void URLFrame::setEncodedURLData(const uint8_t* urlEncodedDataIn, const uint8_t urlEncodedDataLengthIn)
+{
+ urlDataLength = urlEncodedDataLengthIn;
+ memcpy(urlData, urlEncodedDataIn, urlEncodedDataLengthIn);
+}
+
+void URLFrame::encodeURL(const char *urlDataIn)
+{
+ const char *prefixes[] = {
+ "http://www.",
+ "https://www.",
+ "http://",
+ "https://",
+ };
+ const size_t NUM_PREFIXES = sizeof(prefixes) / sizeof(char *);
+ const char *suffixes[] = {
+ ".com/",
+ ".org/",
+ ".edu/",
+ ".net/",
+ ".info/",
+ ".biz/",
+ ".gov/",
+ ".com",
+ ".org",
+ ".edu",
+ ".net",
+ ".info",
+ ".biz",
+ ".gov"
+ };
+ const size_t NUM_SUFFIXES = sizeof(suffixes) / sizeof(char *);
+
+ urlDataLength = 0;
+ memset(urlData, 0, sizeof(UrlData_t));
+
+ if ((urlDataIn == NULL) || (strlen(urlDataIn) == 0)) {
+ return;
+ }
+
+ /*
+ * handle prefix
+ */
+ for (size_t i = 0; i < NUM_PREFIXES; i++) {
+ size_t prefixLen = strlen(prefixes[i]);
+ if (strncmp(urlDataIn, prefixes[i], prefixLen) == 0) {
+ urlData[urlDataLength++] = i;
+ urlDataIn += prefixLen;
+ break;
+ }
+ }
+
+ /*
+ * handle suffixes
+ */
+ while (*urlDataIn && (urlDataLength < URL_DATA_MAX)) {
+ /* check for suffix match */
+ size_t i;
+ for (i = 0; i < NUM_SUFFIXES; i++) {
+ size_t suffixLen = strlen(suffixes[i]);
+ if (strncmp(urlDataIn, suffixes[i], suffixLen) == 0) {
+ urlData[urlDataLength++] = i;
+ urlDataIn += suffixLen;
+ break; /* from the for loop for checking against suffixes */
+ }
+ }
+ /* This is the default case where we've got an ordinary character which doesn't match a suffix. */
+ if (i == NUM_SUFFIXES) {
+ urlData[urlDataLength++] = *urlDataIn;
+ ++urlDataIn;
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/URLFrame.h Thu Nov 26 16:36:04 2015 +0000
@@ -0,0 +1,56 @@
+/* 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.
+ */
+
+#ifndef __URLFRAME_H__
+#define __URLFRAME_H__
+
+#include "EddystoneTypes.h"
+#include <string.h>
+
+class URLFrame
+{
+public:
+ URLFrame(void);
+
+ URLFrame(const char *urlDataIn);
+
+ URLFrame(UrlData_t urlDataIn, uint8_t urlDataLength);
+
+ void constructURLFrame(uint8_t* rawFrame, int8_t advPowerLevel);
+
+ size_t getRawFrameSize(void) const;
+
+ uint8_t* getEncodedURLData(void);
+
+ uint8_t getEncodedURLDataLength(void) const;
+
+ void setURLData(const char *urlDataIn);
+
+ void setEncodedURLData(const uint8_t* urlEncodedDataIn, const uint8_t urlEncodedDataLengthIn);
+
+private:
+ void encodeURL(const char *urlDataIn);
+
+ static const uint8_t FRAME_TYPE_URL = 0x10;
+ /* Even if the URL is 0 bytes we still need to include the type and txPower i.e. 2 bytes */
+ static const uint8_t FRAME_MIN_SIZE_URL = 2;
+
+ uint8_t urlDataLength;
+ UrlData_t urlData;
+
+};
+
+#endif /* __URLFRAME_H__ */
--- a/main.cpp Thu Oct 01 18:30:37 2015 +0000
+++ b/main.cpp Thu Nov 26 16:36:04 2015 +0000
@@ -15,59 +15,80 @@
*/
#include "mbed.h"
-#include "BLE.h"
-#include "ble/services/EddystoneService.h"
+#include "ble/BLE.h"
+#include "EddystoneService.h"
+
+EddystoneService *eddyServicePtr;
-BLE ble;
-uint8_t UIDnamespace[] = {0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0xAA}; // 10Bytes for Namespace UUID
-uint8_t UIDinstance[] = {0xbb,0xcc,0xdd,0xee,0xff,0x00}; // 6Bytes for Instance UUID
-char Url[] = "http://www.mbed.org";
-int8_t radioTxPower = 20;
-int8_t advTxPower = -20;
-uint16_t beaconPeriodus = 1000;
-uint8_t tlmVersion = 0x00;
-
+void onBleInitError(BLE::InitializationCompleteCallbackContext* initContext)
+{
+ /* Initialization error handling goes here... */
+ (void) initContext;
+}
-//Callbacks for temperature / battery updates
-Ticker tlmBattery;
-Ticker tlmTemperature;
-int battery = 0;
-int temp = 0;
+/*
+ * Update TLM frame battery voltage value.
+ */
+uint16_t tlmBatteryVoltageCallback(uint16_t prevBatteryVoltage)
+{
+ prevBatteryVoltage++;
+ return prevBatteryVoltage;
+}
-// Setup Eddystone Service
-EddystoneService eddyBeacon(ble,beaconPeriodus,radioTxPower);
-
-
-// Function to update beacon battery voltage
-void tlmBatteryCallback(void){
- eddyBeacon.updateTlmBatteryVoltage(battery++);
+/*
+ * Update TLM frame beacon temperature value.
+ */
+uint16_t tlmBeaconTemperatureCallback(uint16_t prevBeaconTemperature)
+{
+ prevBeaconTemperature++;
+ return prevBeaconTemperature;
}
-// Function to update Beacon Temperature
-void tlmTemperatureCallback(void){
- eddyBeacon.updateTlmBeaconTemp(temp++);
+void bleInitComplete(BLE::InitializationCompleteCallbackContext* initContext)
+{
+ BLE &ble = initContext->ble;
+ ble_error_t error = initContext->error;
+
+ if (error != BLE_ERROR_NONE) {
+ onBleInitError(initContext);
+ return;
+ }
+
+ /* Set UID and TLM frame data */
+ const UIDNamespaceID_t uidNamespaceID = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99};
+ const UIDInstanceID_t uidInstanceID = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF};
+ uint8_t tlmVersion = 0x00;
+
+ /* Initialize a EddystoneBeaconConfig service providing config params, default URI, and power levels. */
+ static const PowerLevels_t defaultAdvPowerLevels = {-47, -33, -21, -13}; // Values for ADV packets related to firmware levels, calibrated based on measured values at 1m
+ static const PowerLevels_t radioPowerLevels = {-30, -16, -4, 4}; // Values for radio power levels, provided by manufacturer.
+
+ /* Set everything to defaults */
+ eddyServicePtr = new EddystoneService(ble, defaultAdvPowerLevels, radioPowerLevels);
+
+ /* Set default URL, UID and TLM frame data if not initialized through the config service */
+ eddyServicePtr->setURLData("http://mbed.org");
+ eddyServicePtr->setUIDData(&uidNamespaceID, &uidInstanceID);
+ eddyServicePtr->setTLMData(tlmVersion);
+
+ /* Set battery voltage and temperature callbacks */
+ eddyServicePtr->onTLMBatteryVoltageUpdate(tlmBatteryVoltageCallback);
+ eddyServicePtr->onTLMBeaconTemperatureUpdate(tlmBeaconTemperatureCallback);
+
+ /* Start Eddystone in config mode */
+ eddyServicePtr->startBeaconService(5, 5, 5);
}
int main(void)
{
- printf("Starting Example\r\n"); // To enable low power mode disable all printf's
- ble.init();
-
- // Set Eddystone Frame Data (TLM,UID,URI...etc)
- eddyBeacon.setTLMFrameData(tlmVersion,5.0);
- eddyBeacon.setURLFrameData(advTxPower, Url, 2.0);
- eddyBeacon.setUIDFrameData(advTxPower, UIDnamespace, UIDinstance, 3.0);
+ BLE& ble = BLE::Instance(BLE::DEFAULT_INSTANCE);
+ ble.init(bleInitComplete);
+
+ /* SpinWait for initialization to complete. This is necessary because the
+ * BLE object is used in the main loop below. */
+ while (ble.hasInitialized() == false) { /* spin loop */ }
- // Attach functions to modify TLM data
- tlmTemperature.attach(tlmTemperatureCallback,2.0f);
- tlmBattery.attach(tlmBatteryCallback, 1.0f);
-
- // Start Advertising the eddystone service.
- eddyBeacon.start();
- ble.gap().startAdvertising();
-
- printf("Running...\r\n");
while (true) {
- ble.waitForEvent();
+ ble.waitForEvent(); /* this will return upon any system event (such as an interrupt or a ticker wakeup) */
}
}
--- a/mbed.bld Thu Oct 01 18:30:37 2015 +0000 +++ b/mbed.bld Thu Nov 26 16:36:04 2015 +0000 @@ -1,1 +1,1 @@ -http://mbed.org/users/mbed_official/code/mbed/builds/4f6c30876dfa \ No newline at end of file +http://mbed.org/users/mbed_official/code/mbed/builds/9296ab0bfc11 \ No newline at end of file
--- a/nRF51822.lib Thu Oct 01 18:30:37 2015 +0000 +++ b/nRF51822.lib Thu Nov 26 16:36:04 2015 +0000 @@ -1,1 +1,1 @@ -http://mbed.org/teams/Nordic-Semiconductor/code/nRF51822/#088f5738bf18 +http://mbed.org/teams/Nordic-Semiconductor/code/nRF51822/#1762975cb3cb
