Example program for the Eddystone Beacon service.

Dependencies:   BLE_API mbed nRF51822 X_NUCLEO_IDB0XA1

Fork of BLE_EddystoneBeacon by URIBeacon

This example demonstrates how to set up and initialize a Eddystone Beacon. For more details on the Eddystone specification please see the Eddystone Github Page.

The Basics

An Eddystone Beacon is a Bluetooth Low Energy beacon, that means it does all of its data transfer in GAP advertising packets. Eddystone beacons, unlike other beacons, broadcast multiple types of data. Currently Eddystone beacons have 3 frame types (UID, URL, TLM) that will get swapped out periodically. This swapping of frame data allows Eddystone beacons to send many different types of data, thus increasing their usefulness over traditional beacons. Note that the UID frame type provides the same 16Bytes of UUID namespace that iBeacons do and the URL frame type provides the same functionality as a URIBeacon.

For more details see the Eddystone Specification.

Smartphone Apps

nRF Master Control Panel - this program recognizes Eddystone beacons and will display the data for all frame types.

iPhone Physical Web app

Android App

Walkthrough of Physical Web application

Technical Details

The Eddystone Specification looks like the following image. Please note that this may change over time and for up to date information the official spec should be referenced. /media/uploads/mbedAustin/scratch-1-.png

The Eddystone Frames get swapped in and out depending on what frames you have enabled. The only required frame type is the TLM frame, all others are optional and you can have any number enabled. To disable the UID or URL frames give their values a 'NULL' in the Eddystone constructor. The Eddystone spec recommends broadcasting 10 frames a second.

Note

  • The current Eddystone mbed example does not allow for combining the eddystone service with other services, this will be changes in a future update.
  • There is an Eddystone Config service that allows for updating beacons in the field. We are working on an example for this, so keep your eyes pealed for a future update.
Committer:
rgrover1
Date:
Fri May 15 08:13:35 2015 +0000
Revision:
4:4440953bde10
Parent:
0:c04d932e96c9
Child:
6:e90c398b03e0
Get the Floor Period test to pass.

Who changed what in which revision?

UserRevisionLine numberNew contents of line
screamer 0:c04d932e96c9 1 /* mbed Microcontroller Library
screamer 0:c04d932e96c9 2 * Copyright (c) 2006-2013 ARM Limited
screamer 0:c04d932e96c9 3 *
screamer 0:c04d932e96c9 4 * Licensed under the Apache License, Version 2.0 (the "License");
screamer 0:c04d932e96c9 5 * you may not use this file except in compliance with the License.
screamer 0:c04d932e96c9 6 * You may obtain a copy of the License at
screamer 0:c04d932e96c9 7 *
screamer 0:c04d932e96c9 8 * http://www.apache.org/licenses/LICENSE-2.0
screamer 0:c04d932e96c9 9 *
screamer 0:c04d932e96c9 10 * Unless required by applicable law or agreed to in writing, software
screamer 0:c04d932e96c9 11 * distributed under the License is distributed on an "AS IS" BASIS,
screamer 0:c04d932e96c9 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
screamer 0:c04d932e96c9 13 * See the License for the specific language governing permissions and
screamer 0:c04d932e96c9 14 * limitations under the License.
screamer 0:c04d932e96c9 15 */
screamer 0:c04d932e96c9 16
screamer 0:c04d932e96c9 17 #ifndef SERVICES_ZIPBEACONCONFIGSERVICE_H_
screamer 0:c04d932e96c9 18 #define SERVICES_ZIPBEACONCONFIGSERVICE_H_
screamer 0:c04d932e96c9 19
screamer 0:c04d932e96c9 20 #include "BLEDevice.h"
screamer 0:c04d932e96c9 21 #include "mbed.h"
screamer 0:c04d932e96c9 22
screamer 0:c04d932e96c9 23 #define UUID_URI_BEACON(FIRST, SECOND) { \
screamer 0:c04d932e96c9 24 0xee, 0x0c, FIRST, SECOND, 0x87, 0x86, 0x40, 0xba, \
screamer 0:c04d932e96c9 25 0xab, 0x96, 0x99, 0xb9, 0x1a, 0xc9, 0x81, 0xd8, \
screamer 0:c04d932e96c9 26 }
screamer 0:c04d932e96c9 27
screamer 0:c04d932e96c9 28 static const uint8_t UUID_URI_BEACON_SERVICE[] = UUID_URI_BEACON(0x20, 0x80);
screamer 0:c04d932e96c9 29 static const uint8_t UUID_LOCK_STATE_CHAR[] = UUID_URI_BEACON(0x20, 0x81);
screamer 0:c04d932e96c9 30 static const uint8_t UUID_LOCK_CHAR[] = UUID_URI_BEACON(0x20, 0x82);
screamer 0:c04d932e96c9 31 static const uint8_t UUID_UNLOCK_CHAR[] = UUID_URI_BEACON(0x20, 0x83);
screamer 0:c04d932e96c9 32 static const uint8_t UUID_URI_DATA_CHAR[] = UUID_URI_BEACON(0x20, 0x84);
screamer 0:c04d932e96c9 33 static const uint8_t UUID_FLAGS_CHAR[] = UUID_URI_BEACON(0x20, 0x85);
screamer 0:c04d932e96c9 34 static const uint8_t UUID_ADV_POWER_LEVELS_CHAR[] = UUID_URI_BEACON(0x20, 0x86);
screamer 0:c04d932e96c9 35 static const uint8_t UUID_TX_POWER_MODE_CHAR[] = UUID_URI_BEACON(0x20, 0x87);
screamer 0:c04d932e96c9 36 static const uint8_t UUID_BEACON_PERIOD_CHAR[] = UUID_URI_BEACON(0x20, 0x88);
screamer 0:c04d932e96c9 37 static const uint8_t UUID_RESET_CHAR[] = UUID_URI_BEACON(0x20, 0x89);
screamer 0:c04d932e96c9 38 static const uint8_t BEACON_UUID[] = {0xAA, 0xFE};
screamer 0:c04d932e96c9 39
screamer 0:c04d932e96c9 40 /**
screamer 0:c04d932e96c9 41 * @class ZipBeaconConfigService
screamer 0:c04d932e96c9 42 * @brief ZipBeacon Configuration Service. Can be used to set URL, adjust power levels, and set flags.
screamer 0:c04d932e96c9 43 * See http://uribeacon.org
screamer 0:c04d932e96c9 44 *
screamer 0:c04d932e96c9 45 */
screamer 0:c04d932e96c9 46 class ZipBeaconConfigService {
screamer 0:c04d932e96c9 47 public:
screamer 0:c04d932e96c9 48 /**
screamer 0:c04d932e96c9 49 * @brief Transmission Power Modes for UriBeacon
screamer 0:c04d932e96c9 50 */
screamer 0:c04d932e96c9 51 static const uint8_t TX_POWER_MODE_LOWEST = 0; /*!< Lowest TX power mode */
screamer 0:c04d932e96c9 52 static const uint8_t TX_POWER_MODE_LOW = 1; /*!< Low TX power mode */
screamer 0:c04d932e96c9 53 static const uint8_t TX_POWER_MODE_MEDIUM = 2; /*!< Medium TX power mode */
screamer 0:c04d932e96c9 54 static const uint8_t TX_POWER_MODE_HIGH = 3; /*!< High TX power mode */
screamer 0:c04d932e96c9 55 static const unsigned int NUM_POWER_MODES = 4; /*!< Number of Power Modes defined */
screamer 0:c04d932e96c9 56
screamer 0:c04d932e96c9 57 static const int ADVERTISING_INTERVAL_MSEC = 1000; // Advertising interval for config service.
screamer 0:c04d932e96c9 58 static const int SERVICE_DATA_MAX = 31; // Maximum size of service data in ADV packets
screamer 0:c04d932e96c9 59
screamer 0:c04d932e96c9 60 typedef uint8_t Lock_t[16]; /* 128 bits */
screamer 0:c04d932e96c9 61 typedef int8_t PowerLevels_t[NUM_POWER_MODES];
screamer 0:c04d932e96c9 62
screamer 0:c04d932e96c9 63 static const int URI_DATA_MAX = 18;
screamer 0:c04d932e96c9 64 typedef uint8_t UriData_t[URI_DATA_MAX];
screamer 0:c04d932e96c9 65
screamer 0:c04d932e96c9 66 static const uint8_t FRAME_TYPE_UID = 0x00;
screamer 0:c04d932e96c9 67 static const uint8_t FRAME_TYPE_URL = 0x10;
screamer 0:c04d932e96c9 68 static const uint8_t FRAME_TYPE_TLM = 0x20;
screamer 0:c04d932e96c9 69
screamer 0:c04d932e96c9 70 struct Params_t {
screamer 0:c04d932e96c9 71 Lock_t lock;
screamer 0:c04d932e96c9 72 uint8_t uriDataLength;
screamer 0:c04d932e96c9 73 UriData_t uriData;
screamer 0:c04d932e96c9 74 uint8_t flags;
screamer 0:c04d932e96c9 75 PowerLevels_t advPowerLevels; // Current value of AdvertisedPowerLevels
screamer 0:c04d932e96c9 76 uint8_t txPowerMode; // Firmware power levels used with setTxPower()
screamer 0:c04d932e96c9 77 uint16_t beaconPeriod;
screamer 0:c04d932e96c9 78 };
screamer 0:c04d932e96c9 79
screamer 0:c04d932e96c9 80 /**
screamer 0:c04d932e96c9 81 * @param[ref] ble
screamer 0:c04d932e96c9 82 * BLEDevice object for the underlying controller.
screamer 0:c04d932e96c9 83 * @param[in/out] paramsIn
screamer 0:c04d932e96c9 84 * Reference to application-visible beacon state, loaded
screamer 0:c04d932e96c9 85 * from persistent storage at startup.
screamer 0:c04d932e96c9 86 * @paramsP[in] resetToDefaultsFlag
screamer 0:c04d932e96c9 87 * Applies to the state of the 'paramsIn' parameter.
screamer 0:c04d932e96c9 88 * If true, it indicates that paramsIn is potentially
screamer 0:c04d932e96c9 89 * un-initialized, and default values should be used
screamer 0:c04d932e96c9 90 * instead. Otherwise, paramsIn overrides the defaults.
screamer 0:c04d932e96c9 91 * @param[in] defaultUriDataIn
screamer 0:c04d932e96c9 92 * Default un-encoded URI; applies only if the resetToDefaultsFlag is true.
screamer 0:c04d932e96c9 93 * @param[in] defaultAdvPowerLevelsIn
screamer 0:c04d932e96c9 94 * Default power-levels array; applies only if the resetToDefaultsFlag is true.
screamer 0:c04d932e96c9 95 */
screamer 0:c04d932e96c9 96 ZipBeaconConfigService(BLEDevice &bleIn,
screamer 0:c04d932e96c9 97 Params_t &paramsIn,
screamer 0:c04d932e96c9 98 bool resetToDefaultsFlag,
screamer 0:c04d932e96c9 99 const char *defaultURIDataIn,
screamer 0:c04d932e96c9 100 PowerLevels_t &defaultAdvPowerLevelsIn) :
screamer 0:c04d932e96c9 101 ble(bleIn),
screamer 0:c04d932e96c9 102 params(paramsIn),
screamer 0:c04d932e96c9 103 defaultUriDataLength(),
screamer 0:c04d932e96c9 104 defaultUriData(),
screamer 0:c04d932e96c9 105 defaultAdvPowerLevels(defaultAdvPowerLevelsIn),
screamer 0:c04d932e96c9 106 initSucceeded(false),
screamer 0:c04d932e96c9 107 resetFlag(),
screamer 0:c04d932e96c9 108 lockedStateChar(UUID_LOCK_STATE_CHAR, &lockedState),
screamer 0:c04d932e96c9 109 lockChar(UUID_LOCK_CHAR, &params.lock),
screamer 0:c04d932e96c9 110 uriDataChar(UUID_URI_DATA_CHAR, params.uriData, 0, URI_DATA_MAX,
screamer 0:c04d932e96c9 111 GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE),
screamer 0:c04d932e96c9 112 unlockChar(UUID_UNLOCK_CHAR, &params.lock),
screamer 0:c04d932e96c9 113 flagsChar(UUID_FLAGS_CHAR, &params.flags),
screamer 0:c04d932e96c9 114 advPowerLevelsChar(UUID_ADV_POWER_LEVELS_CHAR, &params.advPowerLevels),
screamer 0:c04d932e96c9 115 txPowerModeChar(UUID_TX_POWER_MODE_CHAR, &params.txPowerMode),
screamer 0:c04d932e96c9 116 beaconPeriodChar(UUID_BEACON_PERIOD_CHAR, &params.beaconPeriod),
screamer 0:c04d932e96c9 117 resetChar(UUID_RESET_CHAR, &resetFlag) {
screamer 0:c04d932e96c9 118
screamer 0:c04d932e96c9 119 encodeURI(defaultURIDataIn, defaultUriData, defaultUriDataLength);
screamer 0:c04d932e96c9 120 if (defaultUriDataLength > URI_DATA_MAX) {
screamer 0:c04d932e96c9 121 return;
screamer 0:c04d932e96c9 122 }
screamer 0:c04d932e96c9 123
screamer 0:c04d932e96c9 124 if (!resetToDefaultsFlag && (params.uriDataLength > URI_DATA_MAX)) {
screamer 0:c04d932e96c9 125 resetToDefaultsFlag = true;
screamer 0:c04d932e96c9 126 }
screamer 0:c04d932e96c9 127 if (resetToDefaultsFlag) {
screamer 0:c04d932e96c9 128 resetToDefaults();
screamer 0:c04d932e96c9 129 } else {
screamer 0:c04d932e96c9 130 updateCharacteristicValues();
screamer 0:c04d932e96c9 131 }
screamer 0:c04d932e96c9 132
screamer 0:c04d932e96c9 133 lockedState = isLocked();
screamer 0:c04d932e96c9 134
screamer 0:c04d932e96c9 135 lockChar.setWriteAuthorizationCallback(this, &ZipBeaconConfigService::lockAuthorizationCallback);
screamer 0:c04d932e96c9 136 unlockChar.setWriteAuthorizationCallback(this, &ZipBeaconConfigService::unlockAuthorizationCallback);
screamer 0:c04d932e96c9 137 uriDataChar.setWriteAuthorizationCallback(this, &ZipBeaconConfigService::uriDataWriteAuthorizationCallback);
screamer 0:c04d932e96c9 138 flagsChar.setWriteAuthorizationCallback(this, &ZipBeaconConfigService::basicAuthorizationCallback<uint8_t>);
screamer 0:c04d932e96c9 139 advPowerLevelsChar.setWriteAuthorizationCallback(this, &ZipBeaconConfigService::basicAuthorizationCallback<PowerLevels_t>);
screamer 0:c04d932e96c9 140 txPowerModeChar.setWriteAuthorizationCallback(this, &ZipBeaconConfigService::powerModeAuthorizationCallback);
screamer 0:c04d932e96c9 141 beaconPeriodChar.setWriteAuthorizationCallback(this, &ZipBeaconConfigService::basicAuthorizationCallback<uint16_t>);
screamer 0:c04d932e96c9 142 resetChar.setWriteAuthorizationCallback(this, &ZipBeaconConfigService::basicAuthorizationCallback<uint8_t>);
screamer 0:c04d932e96c9 143
screamer 0:c04d932e96c9 144 static GattCharacteristic *charTable[] = {
screamer 0:c04d932e96c9 145 &lockedStateChar, &lockChar, &unlockChar, &uriDataChar,
screamer 0:c04d932e96c9 146 &flagsChar, &advPowerLevelsChar, &txPowerModeChar, &beaconPeriodChar, &resetChar
screamer 0:c04d932e96c9 147 };
screamer 0:c04d932e96c9 148
screamer 0:c04d932e96c9 149 GattService configService(UUID_URI_BEACON_SERVICE, charTable, sizeof(charTable) / sizeof(GattCharacteristic *));
screamer 0:c04d932e96c9 150
screamer 0:c04d932e96c9 151 ble.addService(configService);
screamer 0:c04d932e96c9 152 ble.onDataWritten(this, &ZipBeaconConfigService::onDataWrittenCallback);
screamer 0:c04d932e96c9 153
screamer 0:c04d932e96c9 154 setupZipBeaconConfigAdvertisements(); /* Setup advertising for the configService. */
screamer 0:c04d932e96c9 155
screamer 0:c04d932e96c9 156 initSucceeded = true;
screamer 0:c04d932e96c9 157 }
screamer 0:c04d932e96c9 158
screamer 0:c04d932e96c9 159 bool configuredSuccessfully(void) const {
screamer 0:c04d932e96c9 160 return initSucceeded;
screamer 0:c04d932e96c9 161 }
screamer 0:c04d932e96c9 162
screamer 0:c04d932e96c9 163 /* Start out by advertising the configService for a limited time after
screamer 0:c04d932e96c9 164 * startup; and switch to the normal non-connectible beacon functionality
screamer 0:c04d932e96c9 165 * afterwards. */
screamer 0:c04d932e96c9 166 void setupZipBeaconConfigAdvertisements()
screamer 0:c04d932e96c9 167 {
screamer 0:c04d932e96c9 168 const char DEVICE_NAME[] = "zipBeacon Config";
screamer 0:c04d932e96c9 169
screamer 0:c04d932e96c9 170 ble.clearAdvertisingPayload();
screamer 0:c04d932e96c9 171
screamer 0:c04d932e96c9 172 ble.accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
screamer 0:c04d932e96c9 173
screamer 0:c04d932e96c9 174 // UUID is in different order in the ADV frame (!)
screamer 0:c04d932e96c9 175 uint8_t reversedServiceUUID[sizeof(UUID_URI_BEACON_SERVICE)];
screamer 0:c04d932e96c9 176 for (unsigned int i = 0; i < sizeof(UUID_URI_BEACON_SERVICE); i++) {
screamer 0:c04d932e96c9 177 reversedServiceUUID[i] = UUID_URI_BEACON_SERVICE[sizeof(UUID_URI_BEACON_SERVICE) - i - 1];
screamer 0:c04d932e96c9 178 }
screamer 0:c04d932e96c9 179 ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_128BIT_SERVICE_IDS, reversedServiceUUID, sizeof(reversedServiceUUID));
screamer 0:c04d932e96c9 180 ble.accumulateAdvertisingPayload(GapAdvertisingData::GENERIC_TAG);
screamer 0:c04d932e96c9 181 ble.accumulateScanResponse(GapAdvertisingData::COMPLETE_LOCAL_NAME, reinterpret_cast<const uint8_t *>(&DEVICE_NAME), sizeof(DEVICE_NAME));
screamer 0:c04d932e96c9 182 ble.accumulateScanResponse(
screamer 0:c04d932e96c9 183 GapAdvertisingData::TX_POWER_LEVEL,
screamer 0:c04d932e96c9 184 reinterpret_cast<uint8_t *>(&defaultAdvPowerLevels[ZipBeaconConfigService::TX_POWER_MODE_LOW]),
screamer 0:c04d932e96c9 185 sizeof(uint8_t));
screamer 0:c04d932e96c9 186
screamer 0:c04d932e96c9 187 ble.setTxPower(params.advPowerLevels[params.txPowerMode]);
screamer 0:c04d932e96c9 188 ble.setDeviceName(reinterpret_cast<const uint8_t *>(&DEVICE_NAME));
screamer 0:c04d932e96c9 189 ble.setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
screamer 0:c04d932e96c9 190 ble.setAdvertisingInterval(Gap::MSEC_TO_ADVERTISEMENT_DURATION_UNITS(ADVERTISING_INTERVAL_MSEC));
screamer 0:c04d932e96c9 191 }
screamer 0:c04d932e96c9 192
screamer 0:c04d932e96c9 193 /* Helper function to switch to the non-connectible normal mode for ZipBeacon. This gets called after a timeout. */
screamer 0:c04d932e96c9 194 void setupZipBeaconAdvertisements()
screamer 0:c04d932e96c9 195 {
screamer 0:c04d932e96c9 196 uint8_t serviceData[SERVICE_DATA_MAX];
screamer 0:c04d932e96c9 197 unsigned serviceDataLen = 0;
screamer 0:c04d932e96c9 198
screamer 0:c04d932e96c9 199 /* Reinitialize the BLE stack. This will clear away the existing services and advertising state. */
screamer 0:c04d932e96c9 200 ble.shutdown();
screamer 0:c04d932e96c9 201 ble.init();
screamer 0:c04d932e96c9 202
screamer 0:c04d932e96c9 203 // Fields from the Service
screamer 0:c04d932e96c9 204 unsigned beaconPeriod = params.beaconPeriod;
screamer 0:c04d932e96c9 205 unsigned txPowerMode = params.txPowerMode;
screamer 0:c04d932e96c9 206 unsigned uriDataLength = params.uriDataLength;
screamer 0:c04d932e96c9 207 ZipBeaconConfigService::UriData_t &uriData = params.uriData;
screamer 0:c04d932e96c9 208 ZipBeaconConfigService::PowerLevels_t &advPowerLevels = params.advPowerLevels;
screamer 0:c04d932e96c9 209 uint8_t flags = params.flags;
screamer 0:c04d932e96c9 210
screamer 0:c04d932e96c9 211 ble.clearAdvertisingPayload();
screamer 0:c04d932e96c9 212 ble.setTxPower(params.advPowerLevels[params.txPowerMode]);
screamer 0:c04d932e96c9 213 ble.setAdvertisingType(GapAdvertisingParams::ADV_NON_CONNECTABLE_UNDIRECTED);
screamer 0:c04d932e96c9 214 ble.setAdvertisingInterval(beaconPeriod);
screamer 0:c04d932e96c9 215 ble.accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
screamer 0:c04d932e96c9 216 ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, BEACON_UUID, sizeof(BEACON_UUID));
screamer 0:c04d932e96c9 217
screamer 0:c04d932e96c9 218 serviceData[serviceDataLen++] = BEACON_UUID[0];
screamer 0:c04d932e96c9 219 serviceData[serviceDataLen++] = BEACON_UUID[1];
screamer 0:c04d932e96c9 220 serviceData[serviceDataLen++] = FRAME_TYPE_URL | flags;
screamer 0:c04d932e96c9 221 serviceData[serviceDataLen++] = advPowerLevels[txPowerMode];
screamer 0:c04d932e96c9 222 for (unsigned j = 0; j < uriDataLength; j++) {
screamer 0:c04d932e96c9 223 serviceData[serviceDataLen++] = uriData[j];
screamer 0:c04d932e96c9 224 }
screamer 0:c04d932e96c9 225 ble.accumulateAdvertisingPayload(GapAdvertisingData::SERVICE_DATA, serviceData, serviceDataLen);
screamer 0:c04d932e96c9 226 }
screamer 0:c04d932e96c9 227
screamer 0:c04d932e96c9 228 private:
screamer 0:c04d932e96c9 229 // True if the lock bits are non-zero
screamer 0:c04d932e96c9 230 bool isLocked() {
screamer 0:c04d932e96c9 231 Lock_t testLock;
screamer 0:c04d932e96c9 232 memset(testLock, 0, sizeof(Lock_t));
screamer 0:c04d932e96c9 233 return memcmp(params.lock, testLock, sizeof(Lock_t));
screamer 0:c04d932e96c9 234 }
screamer 0:c04d932e96c9 235
screamer 0:c04d932e96c9 236 /*
screamer 0:c04d932e96c9 237 * This callback is invoked when a GATT client attempts to modify any of the
screamer 0:c04d932e96c9 238 * characteristics of this service. Attempts to do so are also applied to
screamer 0:c04d932e96c9 239 * the internal state of this service object.
screamer 0:c04d932e96c9 240 */
screamer 0:c04d932e96c9 241 void onDataWrittenCallback(const GattCharacteristicWriteCBParams *writeParams) {
screamer 0:c04d932e96c9 242 uint16_t handle = writeParams->charHandle;
screamer 0:c04d932e96c9 243
screamer 0:c04d932e96c9 244 if (handle == lockChar.getValueHandle()) {
screamer 0:c04d932e96c9 245 // Validated earlier
screamer 0:c04d932e96c9 246 memcpy(params.lock, writeParams->data, sizeof(Lock_t));
screamer 0:c04d932e96c9 247 // use isLocked() in case bits are being set to all 0's
screamer 0:c04d932e96c9 248 lockedState = isLocked();
screamer 0:c04d932e96c9 249 } else if (handle == unlockChar.getValueHandle()) {
screamer 0:c04d932e96c9 250 // Validated earlier
screamer 0:c04d932e96c9 251 memset(params.lock, 0, sizeof(Lock_t));
screamer 0:c04d932e96c9 252 lockedState = false;
screamer 0:c04d932e96c9 253 } else if (handle == uriDataChar.getValueHandle()) {
screamer 0:c04d932e96c9 254 params.uriDataLength = writeParams->len;
screamer 0:c04d932e96c9 255 memcpy(params.uriData, writeParams->data, params.uriDataLength);
screamer 0:c04d932e96c9 256 } else if (handle == flagsChar.getValueHandle()) {
screamer 0:c04d932e96c9 257 params.flags = *(writeParams->data);
screamer 0:c04d932e96c9 258 } else if (handle == advPowerLevelsChar.getValueHandle()) {
screamer 0:c04d932e96c9 259 memcpy(params.advPowerLevels, writeParams->data, sizeof(PowerLevels_t));
screamer 0:c04d932e96c9 260 } else if (handle == txPowerModeChar.getValueHandle()) {
screamer 0:c04d932e96c9 261 params.txPowerMode = *(writeParams->data);
screamer 0:c04d932e96c9 262 } else if (handle == beaconPeriodChar.getValueHandle()) {
screamer 0:c04d932e96c9 263 params.beaconPeriod = *((uint16_t *)(writeParams->data));
rgrover1 4:4440953bde10 264
rgrover1 4:4440953bde10 265 /* Re-map beaconPeriod to within permissible bounds if necessary. */
rgrover1 4:4440953bde10 266 if (params.beaconPeriod != 0) {
rgrover1 4:4440953bde10 267 bool paramsUpdated = false;
rgrover1 4:4440953bde10 268 if (params.beaconPeriod < ble.getMinAdvertisingInterval()) {
rgrover1 4:4440953bde10 269 params.beaconPeriod = ble.getMinAdvertisingInterval();
rgrover1 4:4440953bde10 270 paramsUpdated = true;
rgrover1 4:4440953bde10 271 } else if (params.beaconPeriod > ble.getMaxAdvertisingInterval()) {
rgrover1 4:4440953bde10 272 params.beaconPeriod = ble.getMaxAdvertisingInterval();
rgrover1 4:4440953bde10 273 paramsUpdated = true;
rgrover1 4:4440953bde10 274 }
rgrover1 4:4440953bde10 275 if (paramsUpdated) {
rgrover1 4:4440953bde10 276 ble.updateCharacteristicValue(beaconPeriodChar.getValueHandle(), reinterpret_cast<uint8_t *>(&params.beaconPeriod), sizeof(uint16_t));
rgrover1 4:4440953bde10 277 }
rgrover1 4:4440953bde10 278 }
screamer 0:c04d932e96c9 279 } else if (handle == resetChar.getValueHandle()) {
screamer 0:c04d932e96c9 280 resetToDefaults();
screamer 0:c04d932e96c9 281 }
screamer 0:c04d932e96c9 282 }
screamer 0:c04d932e96c9 283
screamer 0:c04d932e96c9 284 /*
screamer 0:c04d932e96c9 285 * Reset the default values.
screamer 0:c04d932e96c9 286 */
screamer 0:c04d932e96c9 287 void resetToDefaults(void) {
screamer 0:c04d932e96c9 288 lockedState = false;
screamer 0:c04d932e96c9 289 memset(params.lock, 0, sizeof(Lock_t));
screamer 0:c04d932e96c9 290 memcpy(params.uriData, defaultUriData, URI_DATA_MAX);
screamer 0:c04d932e96c9 291 params.uriDataLength = defaultUriDataLength;
screamer 0:c04d932e96c9 292 params.flags = 0;
screamer 0:c04d932e96c9 293 memcpy(params.advPowerLevels, defaultAdvPowerLevels, sizeof(PowerLevels_t));
screamer 0:c04d932e96c9 294 params.txPowerMode = TX_POWER_MODE_LOW;
screamer 0:c04d932e96c9 295 params.beaconPeriod = 1000;
screamer 0:c04d932e96c9 296 updateCharacteristicValues();
screamer 0:c04d932e96c9 297 }
screamer 0:c04d932e96c9 298
screamer 0:c04d932e96c9 299 /*
screamer 0:c04d932e96c9 300 * Internal helper function used to update the GATT database following any
screamer 0:c04d932e96c9 301 * change to the internal state of the service object.
screamer 0:c04d932e96c9 302 */
screamer 0:c04d932e96c9 303 void updateCharacteristicValues(void) {
screamer 0:c04d932e96c9 304 ble.updateCharacteristicValue(lockedStateChar.getValueHandle(), &lockedState, 1);
screamer 0:c04d932e96c9 305 ble.updateCharacteristicValue(uriDataChar.getValueHandle(), params.uriData, params.uriDataLength);
screamer 0:c04d932e96c9 306 ble.updateCharacteristicValue(flagsChar.getValueHandle(), &params.flags, 1);
screamer 0:c04d932e96c9 307 ble.updateCharacteristicValue(beaconPeriodChar.getValueHandle(),
screamer 0:c04d932e96c9 308 reinterpret_cast<uint8_t *>(&params.beaconPeriod), sizeof(uint16_t));
screamer 0:c04d932e96c9 309 ble.updateCharacteristicValue(txPowerModeChar.getValueHandle(), &params.txPowerMode, 1);
screamer 0:c04d932e96c9 310 ble.updateCharacteristicValue(advPowerLevelsChar.getValueHandle(),
screamer 0:c04d932e96c9 311 reinterpret_cast<uint8_t *>(params.advPowerLevels), sizeof(PowerLevels_t));
screamer 0:c04d932e96c9 312 }
screamer 0:c04d932e96c9 313
screamer 0:c04d932e96c9 314 private:
screamer 0:c04d932e96c9 315 void lockAuthorizationCallback(GattCharacteristicWriteAuthCBParams *authParams) {
screamer 0:c04d932e96c9 316 if (lockedState) {
screamer 0:c04d932e96c9 317 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION;
screamer 0:c04d932e96c9 318 } else if (authParams->len != sizeof(Lock_t)) {
screamer 0:c04d932e96c9 319 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH;
screamer 0:c04d932e96c9 320 } else if (authParams->offset != 0) {
screamer 0:c04d932e96c9 321 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET;
screamer 0:c04d932e96c9 322 } else {
screamer 0:c04d932e96c9 323 authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
screamer 0:c04d932e96c9 324 }
screamer 0:c04d932e96c9 325 }
screamer 0:c04d932e96c9 326
screamer 0:c04d932e96c9 327
screamer 0:c04d932e96c9 328 void unlockAuthorizationCallback(GattCharacteristicWriteAuthCBParams *authParams) {
screamer 0:c04d932e96c9 329 if (!lockedState) {
screamer 0:c04d932e96c9 330 authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
screamer 0:c04d932e96c9 331 } else if (authParams->len != sizeof(Lock_t)) {
screamer 0:c04d932e96c9 332 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH;
screamer 0:c04d932e96c9 333 } else if (authParams->offset != 0) {
screamer 0:c04d932e96c9 334 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET;
screamer 0:c04d932e96c9 335 } else if (memcmp(authParams->data, params.lock, sizeof(Lock_t)) != 0) {
screamer 0:c04d932e96c9 336 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION;
screamer 0:c04d932e96c9 337 } else {
screamer 0:c04d932e96c9 338 authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
screamer 0:c04d932e96c9 339 }
screamer 0:c04d932e96c9 340 }
screamer 0:c04d932e96c9 341
screamer 0:c04d932e96c9 342 void uriDataWriteAuthorizationCallback(GattCharacteristicWriteAuthCBParams *authParams) {
screamer 0:c04d932e96c9 343 if (lockedState) {
screamer 0:c04d932e96c9 344 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION;
screamer 0:c04d932e96c9 345 } else if (authParams->offset != 0) {
screamer 0:c04d932e96c9 346 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET;
screamer 0:c04d932e96c9 347 } else {
screamer 0:c04d932e96c9 348 authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
screamer 0:c04d932e96c9 349 }
screamer 0:c04d932e96c9 350 }
screamer 0:c04d932e96c9 351
screamer 0:c04d932e96c9 352 void powerModeAuthorizationCallback(GattCharacteristicWriteAuthCBParams *authParams) {
screamer 0:c04d932e96c9 353 if (lockedState) {
screamer 0:c04d932e96c9 354 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION;
screamer 0:c04d932e96c9 355 } else if (authParams->len != sizeof(uint8_t)) {
screamer 0:c04d932e96c9 356 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH;
screamer 0:c04d932e96c9 357 } else if (authParams->offset != 0) {
screamer 0:c04d932e96c9 358 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET;
screamer 0:c04d932e96c9 359 } else if (*((uint8_t *)authParams->data) >= NUM_POWER_MODES) {
screamer 0:c04d932e96c9 360 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_WRITE_NOT_PERMITTED;
screamer 0:c04d932e96c9 361 } else {
screamer 0:c04d932e96c9 362 authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
screamer 0:c04d932e96c9 363 }
screamer 0:c04d932e96c9 364 }
screamer 0:c04d932e96c9 365
screamer 0:c04d932e96c9 366 template <typename T>
screamer 0:c04d932e96c9 367 void basicAuthorizationCallback(GattCharacteristicWriteAuthCBParams *authParams) {
screamer 0:c04d932e96c9 368 if (lockedState) {
screamer 0:c04d932e96c9 369 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION;
screamer 0:c04d932e96c9 370 } else if (authParams->len != sizeof(T)) {
screamer 0:c04d932e96c9 371 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH;
screamer 0:c04d932e96c9 372 } else if (authParams->offset != 0) {
screamer 0:c04d932e96c9 373 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET;
screamer 0:c04d932e96c9 374 } else {
screamer 0:c04d932e96c9 375 authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
screamer 0:c04d932e96c9 376 }
screamer 0:c04d932e96c9 377 }
screamer 0:c04d932e96c9 378
screamer 0:c04d932e96c9 379 BLEDevice &ble;
screamer 0:c04d932e96c9 380 Params_t &params;
screamer 0:c04d932e96c9 381 // Default value that is restored on reset
screamer 0:c04d932e96c9 382 size_t defaultUriDataLength;
screamer 0:c04d932e96c9 383 UriData_t defaultUriData;
screamer 0:c04d932e96c9 384 // Default value that is restored on reset
screamer 0:c04d932e96c9 385 PowerLevels_t &defaultAdvPowerLevels;
screamer 0:c04d932e96c9 386 uint8_t lockedState;
screamer 0:c04d932e96c9 387 bool initSucceeded;
screamer 0:c04d932e96c9 388 uint8_t resetFlag;
screamer 0:c04d932e96c9 389
screamer 0:c04d932e96c9 390 ReadOnlyGattCharacteristic<uint8_t> lockedStateChar;
screamer 0:c04d932e96c9 391 WriteOnlyGattCharacteristic<Lock_t> lockChar;
screamer 0:c04d932e96c9 392 GattCharacteristic uriDataChar;
screamer 0:c04d932e96c9 393 WriteOnlyGattCharacteristic<Lock_t> unlockChar;
screamer 0:c04d932e96c9 394 ReadWriteGattCharacteristic<uint8_t> flagsChar;
screamer 0:c04d932e96c9 395 ReadWriteGattCharacteristic<PowerLevels_t> advPowerLevelsChar;
screamer 0:c04d932e96c9 396 ReadWriteGattCharacteristic<uint8_t> txPowerModeChar;
screamer 0:c04d932e96c9 397 ReadWriteGattCharacteristic<uint16_t> beaconPeriodChar;
screamer 0:c04d932e96c9 398 WriteOnlyGattCharacteristic<uint8_t> resetChar;
screamer 0:c04d932e96c9 399
screamer 0:c04d932e96c9 400 public:
screamer 0:c04d932e96c9 401 /*
screamer 0:c04d932e96c9 402 * Encode a human-readable URI into the binary format defined by URIBeacon spec (https://github.com/google/uribeacon/tree/master/specification).
screamer 0:c04d932e96c9 403 */
screamer 0:c04d932e96c9 404 static void encodeURI(const char *uriDataIn, UriData_t uriDataOut, size_t &sizeofURIDataOut) {
screamer 0:c04d932e96c9 405 const char *prefixes[] = {
screamer 0:c04d932e96c9 406 "http://www.",
screamer 0:c04d932e96c9 407 "https://www.",
screamer 0:c04d932e96c9 408 "http://",
screamer 0:c04d932e96c9 409 "https://",
screamer 0:c04d932e96c9 410 };
screamer 0:c04d932e96c9 411 const size_t NUM_PREFIXES = sizeof(prefixes) / sizeof(char *);
screamer 0:c04d932e96c9 412 const char *suffixes[] = {
screamer 0:c04d932e96c9 413 ".com/",
screamer 0:c04d932e96c9 414 ".org/",
screamer 0:c04d932e96c9 415 ".edu/",
screamer 0:c04d932e96c9 416 ".net/",
screamer 0:c04d932e96c9 417 ".info/",
screamer 0:c04d932e96c9 418 ".biz/",
screamer 0:c04d932e96c9 419 ".gov/",
screamer 0:c04d932e96c9 420 ".com",
screamer 0:c04d932e96c9 421 ".org",
screamer 0:c04d932e96c9 422 ".edu",
screamer 0:c04d932e96c9 423 ".net",
screamer 0:c04d932e96c9 424 ".info",
screamer 0:c04d932e96c9 425 ".biz",
screamer 0:c04d932e96c9 426 ".gov"
screamer 0:c04d932e96c9 427 };
screamer 0:c04d932e96c9 428 const size_t NUM_SUFFIXES = sizeof(suffixes) / sizeof(char *);
screamer 0:c04d932e96c9 429
screamer 0:c04d932e96c9 430 sizeofURIDataOut = 0;
screamer 0:c04d932e96c9 431 memset(uriDataOut, 0, sizeof(UriData_t));
screamer 0:c04d932e96c9 432
screamer 0:c04d932e96c9 433 if ((uriDataIn == NULL) || (strlen(uriDataIn) == 0)) {
screamer 0:c04d932e96c9 434 return;
screamer 0:c04d932e96c9 435 }
screamer 0:c04d932e96c9 436
screamer 0:c04d932e96c9 437 /*
screamer 0:c04d932e96c9 438 * handle prefix
screamer 0:c04d932e96c9 439 */
screamer 0:c04d932e96c9 440 for (unsigned i = 0; i < NUM_PREFIXES; i++) {
screamer 0:c04d932e96c9 441 size_t prefixLen = strlen(prefixes[i]);
screamer 0:c04d932e96c9 442 if (strncmp(uriDataIn, prefixes[i], prefixLen) == 0) {
screamer 0:c04d932e96c9 443 uriDataOut[sizeofURIDataOut++] = i;
screamer 0:c04d932e96c9 444 uriDataIn += prefixLen;
screamer 0:c04d932e96c9 445 break;
screamer 0:c04d932e96c9 446 }
screamer 0:c04d932e96c9 447 }
screamer 0:c04d932e96c9 448
screamer 0:c04d932e96c9 449 /*
screamer 0:c04d932e96c9 450 * handle suffixes
screamer 0:c04d932e96c9 451 */
screamer 0:c04d932e96c9 452 while (*uriDataIn && (sizeofURIDataOut < URI_DATA_MAX)) {
screamer 0:c04d932e96c9 453 /* check for suffix match */
screamer 0:c04d932e96c9 454 unsigned i;
screamer 0:c04d932e96c9 455 for (i = 0; i < NUM_SUFFIXES; i++) {
screamer 0:c04d932e96c9 456 size_t suffixLen = strlen(suffixes[i]);
screamer 0:c04d932e96c9 457 if (strncmp(uriDataIn, suffixes[i], suffixLen) == 0) {
screamer 0:c04d932e96c9 458 uriDataOut[sizeofURIDataOut++] = i;
screamer 0:c04d932e96c9 459 uriDataIn += suffixLen;
screamer 0:c04d932e96c9 460 break; /* from the for loop for checking against suffixes */
screamer 0:c04d932e96c9 461 }
screamer 0:c04d932e96c9 462 }
screamer 0:c04d932e96c9 463 /* This is the default case where we've got an ordinary character which doesn't match a suffix. */
screamer 0:c04d932e96c9 464 if (i == NUM_SUFFIXES) {
screamer 0:c04d932e96c9 465 uriDataOut[sizeofURIDataOut++] = *uriDataIn;
screamer 0:c04d932e96c9 466 ++uriDataIn;
screamer 0:c04d932e96c9 467 }
screamer 0:c04d932e96c9 468 }
screamer 0:c04d932e96c9 469 }
screamer 0:c04d932e96c9 470 };
screamer 0:c04d932e96c9 471
screamer 0:c04d932e96c9 472 #endif // SERVICES_ZIPBEACONCONFIGSERVICE_H_