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:30:08 2015 +0000
Revision:
6:e90c398b03e0
Parent:
4:4440953bde10
Child:
7:e9800c45e065
adding some missing bits from URIBeaconConfigService about saving params.

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
rgrover1 6:e90c398b03e0 211 extern void saveURIBeaconConfigParams(const Params_t *paramsP); /* forward declaration; necessary to avoid a circular dependency. */
rgrover1 6:e90c398b03e0 212 saveURIBeaconConfigParams(&params);
rgrover1 6:e90c398b03e0 213
screamer 0:c04d932e96c9 214 ble.clearAdvertisingPayload();
screamer 0:c04d932e96c9 215 ble.setTxPower(params.advPowerLevels[params.txPowerMode]);
screamer 0:c04d932e96c9 216 ble.setAdvertisingType(GapAdvertisingParams::ADV_NON_CONNECTABLE_UNDIRECTED);
screamer 0:c04d932e96c9 217 ble.setAdvertisingInterval(beaconPeriod);
screamer 0:c04d932e96c9 218 ble.accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
screamer 0:c04d932e96c9 219 ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, BEACON_UUID, sizeof(BEACON_UUID));
screamer 0:c04d932e96c9 220
screamer 0:c04d932e96c9 221 serviceData[serviceDataLen++] = BEACON_UUID[0];
screamer 0:c04d932e96c9 222 serviceData[serviceDataLen++] = BEACON_UUID[1];
screamer 0:c04d932e96c9 223 serviceData[serviceDataLen++] = FRAME_TYPE_URL | flags;
screamer 0:c04d932e96c9 224 serviceData[serviceDataLen++] = advPowerLevels[txPowerMode];
screamer 0:c04d932e96c9 225 for (unsigned j = 0; j < uriDataLength; j++) {
screamer 0:c04d932e96c9 226 serviceData[serviceDataLen++] = uriData[j];
screamer 0:c04d932e96c9 227 }
screamer 0:c04d932e96c9 228 ble.accumulateAdvertisingPayload(GapAdvertisingData::SERVICE_DATA, serviceData, serviceDataLen);
screamer 0:c04d932e96c9 229 }
screamer 0:c04d932e96c9 230
screamer 0:c04d932e96c9 231 private:
screamer 0:c04d932e96c9 232 // True if the lock bits are non-zero
screamer 0:c04d932e96c9 233 bool isLocked() {
screamer 0:c04d932e96c9 234 Lock_t testLock;
screamer 0:c04d932e96c9 235 memset(testLock, 0, sizeof(Lock_t));
screamer 0:c04d932e96c9 236 return memcmp(params.lock, testLock, sizeof(Lock_t));
screamer 0:c04d932e96c9 237 }
screamer 0:c04d932e96c9 238
screamer 0:c04d932e96c9 239 /*
screamer 0:c04d932e96c9 240 * This callback is invoked when a GATT client attempts to modify any of the
screamer 0:c04d932e96c9 241 * characteristics of this service. Attempts to do so are also applied to
screamer 0:c04d932e96c9 242 * the internal state of this service object.
screamer 0:c04d932e96c9 243 */
screamer 0:c04d932e96c9 244 void onDataWrittenCallback(const GattCharacteristicWriteCBParams *writeParams) {
screamer 0:c04d932e96c9 245 uint16_t handle = writeParams->charHandle;
screamer 0:c04d932e96c9 246
screamer 0:c04d932e96c9 247 if (handle == lockChar.getValueHandle()) {
screamer 0:c04d932e96c9 248 // Validated earlier
screamer 0:c04d932e96c9 249 memcpy(params.lock, writeParams->data, sizeof(Lock_t));
screamer 0:c04d932e96c9 250 // use isLocked() in case bits are being set to all 0's
screamer 0:c04d932e96c9 251 lockedState = isLocked();
screamer 0:c04d932e96c9 252 } else if (handle == unlockChar.getValueHandle()) {
screamer 0:c04d932e96c9 253 // Validated earlier
screamer 0:c04d932e96c9 254 memset(params.lock, 0, sizeof(Lock_t));
screamer 0:c04d932e96c9 255 lockedState = false;
screamer 0:c04d932e96c9 256 } else if (handle == uriDataChar.getValueHandle()) {
screamer 0:c04d932e96c9 257 params.uriDataLength = writeParams->len;
screamer 0:c04d932e96c9 258 memcpy(params.uriData, writeParams->data, params.uriDataLength);
screamer 0:c04d932e96c9 259 } else if (handle == flagsChar.getValueHandle()) {
screamer 0:c04d932e96c9 260 params.flags = *(writeParams->data);
screamer 0:c04d932e96c9 261 } else if (handle == advPowerLevelsChar.getValueHandle()) {
screamer 0:c04d932e96c9 262 memcpy(params.advPowerLevels, writeParams->data, sizeof(PowerLevels_t));
screamer 0:c04d932e96c9 263 } else if (handle == txPowerModeChar.getValueHandle()) {
screamer 0:c04d932e96c9 264 params.txPowerMode = *(writeParams->data);
screamer 0:c04d932e96c9 265 } else if (handle == beaconPeriodChar.getValueHandle()) {
screamer 0:c04d932e96c9 266 params.beaconPeriod = *((uint16_t *)(writeParams->data));
rgrover1 4:4440953bde10 267
rgrover1 4:4440953bde10 268 /* Re-map beaconPeriod to within permissible bounds if necessary. */
rgrover1 4:4440953bde10 269 if (params.beaconPeriod != 0) {
rgrover1 4:4440953bde10 270 bool paramsUpdated = false;
rgrover1 4:4440953bde10 271 if (params.beaconPeriod < ble.getMinAdvertisingInterval()) {
rgrover1 4:4440953bde10 272 params.beaconPeriod = ble.getMinAdvertisingInterval();
rgrover1 4:4440953bde10 273 paramsUpdated = true;
rgrover1 4:4440953bde10 274 } else if (params.beaconPeriod > ble.getMaxAdvertisingInterval()) {
rgrover1 4:4440953bde10 275 params.beaconPeriod = ble.getMaxAdvertisingInterval();
rgrover1 4:4440953bde10 276 paramsUpdated = true;
rgrover1 4:4440953bde10 277 }
rgrover1 4:4440953bde10 278 if (paramsUpdated) {
rgrover1 4:4440953bde10 279 ble.updateCharacteristicValue(beaconPeriodChar.getValueHandle(), reinterpret_cast<uint8_t *>(&params.beaconPeriod), sizeof(uint16_t));
rgrover1 4:4440953bde10 280 }
rgrover1 4:4440953bde10 281 }
screamer 0:c04d932e96c9 282 } else if (handle == resetChar.getValueHandle()) {
screamer 0:c04d932e96c9 283 resetToDefaults();
screamer 0:c04d932e96c9 284 }
screamer 0:c04d932e96c9 285 }
screamer 0:c04d932e96c9 286
screamer 0:c04d932e96c9 287 /*
screamer 0:c04d932e96c9 288 * Reset the default values.
screamer 0:c04d932e96c9 289 */
screamer 0:c04d932e96c9 290 void resetToDefaults(void) {
screamer 0:c04d932e96c9 291 lockedState = false;
screamer 0:c04d932e96c9 292 memset(params.lock, 0, sizeof(Lock_t));
screamer 0:c04d932e96c9 293 memcpy(params.uriData, defaultUriData, URI_DATA_MAX);
screamer 0:c04d932e96c9 294 params.uriDataLength = defaultUriDataLength;
screamer 0:c04d932e96c9 295 params.flags = 0;
screamer 0:c04d932e96c9 296 memcpy(params.advPowerLevels, defaultAdvPowerLevels, sizeof(PowerLevels_t));
screamer 0:c04d932e96c9 297 params.txPowerMode = TX_POWER_MODE_LOW;
screamer 0:c04d932e96c9 298 params.beaconPeriod = 1000;
screamer 0:c04d932e96c9 299 updateCharacteristicValues();
screamer 0:c04d932e96c9 300 }
screamer 0:c04d932e96c9 301
screamer 0:c04d932e96c9 302 /*
screamer 0:c04d932e96c9 303 * Internal helper function used to update the GATT database following any
screamer 0:c04d932e96c9 304 * change to the internal state of the service object.
screamer 0:c04d932e96c9 305 */
screamer 0:c04d932e96c9 306 void updateCharacteristicValues(void) {
screamer 0:c04d932e96c9 307 ble.updateCharacteristicValue(lockedStateChar.getValueHandle(), &lockedState, 1);
screamer 0:c04d932e96c9 308 ble.updateCharacteristicValue(uriDataChar.getValueHandle(), params.uriData, params.uriDataLength);
screamer 0:c04d932e96c9 309 ble.updateCharacteristicValue(flagsChar.getValueHandle(), &params.flags, 1);
screamer 0:c04d932e96c9 310 ble.updateCharacteristicValue(beaconPeriodChar.getValueHandle(),
screamer 0:c04d932e96c9 311 reinterpret_cast<uint8_t *>(&params.beaconPeriod), sizeof(uint16_t));
screamer 0:c04d932e96c9 312 ble.updateCharacteristicValue(txPowerModeChar.getValueHandle(), &params.txPowerMode, 1);
screamer 0:c04d932e96c9 313 ble.updateCharacteristicValue(advPowerLevelsChar.getValueHandle(),
screamer 0:c04d932e96c9 314 reinterpret_cast<uint8_t *>(params.advPowerLevels), sizeof(PowerLevels_t));
screamer 0:c04d932e96c9 315 }
screamer 0:c04d932e96c9 316
screamer 0:c04d932e96c9 317 private:
screamer 0:c04d932e96c9 318 void lockAuthorizationCallback(GattCharacteristicWriteAuthCBParams *authParams) {
screamer 0:c04d932e96c9 319 if (lockedState) {
screamer 0:c04d932e96c9 320 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION;
screamer 0:c04d932e96c9 321 } else if (authParams->len != sizeof(Lock_t)) {
screamer 0:c04d932e96c9 322 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH;
screamer 0:c04d932e96c9 323 } else if (authParams->offset != 0) {
screamer 0:c04d932e96c9 324 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET;
screamer 0:c04d932e96c9 325 } else {
screamer 0:c04d932e96c9 326 authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
screamer 0:c04d932e96c9 327 }
screamer 0:c04d932e96c9 328 }
screamer 0:c04d932e96c9 329
screamer 0:c04d932e96c9 330
screamer 0:c04d932e96c9 331 void unlockAuthorizationCallback(GattCharacteristicWriteAuthCBParams *authParams) {
screamer 0:c04d932e96c9 332 if (!lockedState) {
screamer 0:c04d932e96c9 333 authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
screamer 0:c04d932e96c9 334 } else if (authParams->len != sizeof(Lock_t)) {
screamer 0:c04d932e96c9 335 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH;
screamer 0:c04d932e96c9 336 } else if (authParams->offset != 0) {
screamer 0:c04d932e96c9 337 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET;
screamer 0:c04d932e96c9 338 } else if (memcmp(authParams->data, params.lock, sizeof(Lock_t)) != 0) {
screamer 0:c04d932e96c9 339 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION;
screamer 0:c04d932e96c9 340 } else {
screamer 0:c04d932e96c9 341 authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
screamer 0:c04d932e96c9 342 }
screamer 0:c04d932e96c9 343 }
screamer 0:c04d932e96c9 344
screamer 0:c04d932e96c9 345 void uriDataWriteAuthorizationCallback(GattCharacteristicWriteAuthCBParams *authParams) {
screamer 0:c04d932e96c9 346 if (lockedState) {
screamer 0:c04d932e96c9 347 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION;
screamer 0:c04d932e96c9 348 } else if (authParams->offset != 0) {
screamer 0:c04d932e96c9 349 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET;
screamer 0:c04d932e96c9 350 } else {
screamer 0:c04d932e96c9 351 authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
screamer 0:c04d932e96c9 352 }
screamer 0:c04d932e96c9 353 }
screamer 0:c04d932e96c9 354
screamer 0:c04d932e96c9 355 void powerModeAuthorizationCallback(GattCharacteristicWriteAuthCBParams *authParams) {
screamer 0:c04d932e96c9 356 if (lockedState) {
screamer 0:c04d932e96c9 357 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION;
screamer 0:c04d932e96c9 358 } else if (authParams->len != sizeof(uint8_t)) {
screamer 0:c04d932e96c9 359 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH;
screamer 0:c04d932e96c9 360 } else if (authParams->offset != 0) {
screamer 0:c04d932e96c9 361 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET;
screamer 0:c04d932e96c9 362 } else if (*((uint8_t *)authParams->data) >= NUM_POWER_MODES) {
screamer 0:c04d932e96c9 363 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_WRITE_NOT_PERMITTED;
screamer 0:c04d932e96c9 364 } else {
screamer 0:c04d932e96c9 365 authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
screamer 0:c04d932e96c9 366 }
screamer 0:c04d932e96c9 367 }
screamer 0:c04d932e96c9 368
screamer 0:c04d932e96c9 369 template <typename T>
screamer 0:c04d932e96c9 370 void basicAuthorizationCallback(GattCharacteristicWriteAuthCBParams *authParams) {
screamer 0:c04d932e96c9 371 if (lockedState) {
screamer 0:c04d932e96c9 372 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION;
screamer 0:c04d932e96c9 373 } else if (authParams->len != sizeof(T)) {
screamer 0:c04d932e96c9 374 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH;
screamer 0:c04d932e96c9 375 } else if (authParams->offset != 0) {
screamer 0:c04d932e96c9 376 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET;
screamer 0:c04d932e96c9 377 } else {
screamer 0:c04d932e96c9 378 authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
screamer 0:c04d932e96c9 379 }
screamer 0:c04d932e96c9 380 }
screamer 0:c04d932e96c9 381
screamer 0:c04d932e96c9 382 BLEDevice &ble;
screamer 0:c04d932e96c9 383 Params_t &params;
screamer 0:c04d932e96c9 384 // Default value that is restored on reset
screamer 0:c04d932e96c9 385 size_t defaultUriDataLength;
screamer 0:c04d932e96c9 386 UriData_t defaultUriData;
screamer 0:c04d932e96c9 387 // Default value that is restored on reset
screamer 0:c04d932e96c9 388 PowerLevels_t &defaultAdvPowerLevels;
screamer 0:c04d932e96c9 389 uint8_t lockedState;
screamer 0:c04d932e96c9 390 bool initSucceeded;
screamer 0:c04d932e96c9 391 uint8_t resetFlag;
screamer 0:c04d932e96c9 392
screamer 0:c04d932e96c9 393 ReadOnlyGattCharacteristic<uint8_t> lockedStateChar;
screamer 0:c04d932e96c9 394 WriteOnlyGattCharacteristic<Lock_t> lockChar;
screamer 0:c04d932e96c9 395 GattCharacteristic uriDataChar;
screamer 0:c04d932e96c9 396 WriteOnlyGattCharacteristic<Lock_t> unlockChar;
screamer 0:c04d932e96c9 397 ReadWriteGattCharacteristic<uint8_t> flagsChar;
screamer 0:c04d932e96c9 398 ReadWriteGattCharacteristic<PowerLevels_t> advPowerLevelsChar;
screamer 0:c04d932e96c9 399 ReadWriteGattCharacteristic<uint8_t> txPowerModeChar;
screamer 0:c04d932e96c9 400 ReadWriteGattCharacteristic<uint16_t> beaconPeriodChar;
screamer 0:c04d932e96c9 401 WriteOnlyGattCharacteristic<uint8_t> resetChar;
screamer 0:c04d932e96c9 402
screamer 0:c04d932e96c9 403 public:
screamer 0:c04d932e96c9 404 /*
screamer 0:c04d932e96c9 405 * Encode a human-readable URI into the binary format defined by URIBeacon spec (https://github.com/google/uribeacon/tree/master/specification).
screamer 0:c04d932e96c9 406 */
screamer 0:c04d932e96c9 407 static void encodeURI(const char *uriDataIn, UriData_t uriDataOut, size_t &sizeofURIDataOut) {
screamer 0:c04d932e96c9 408 const char *prefixes[] = {
screamer 0:c04d932e96c9 409 "http://www.",
screamer 0:c04d932e96c9 410 "https://www.",
screamer 0:c04d932e96c9 411 "http://",
screamer 0:c04d932e96c9 412 "https://",
screamer 0:c04d932e96c9 413 };
screamer 0:c04d932e96c9 414 const size_t NUM_PREFIXES = sizeof(prefixes) / sizeof(char *);
screamer 0:c04d932e96c9 415 const char *suffixes[] = {
screamer 0:c04d932e96c9 416 ".com/",
screamer 0:c04d932e96c9 417 ".org/",
screamer 0:c04d932e96c9 418 ".edu/",
screamer 0:c04d932e96c9 419 ".net/",
screamer 0:c04d932e96c9 420 ".info/",
screamer 0:c04d932e96c9 421 ".biz/",
screamer 0:c04d932e96c9 422 ".gov/",
screamer 0:c04d932e96c9 423 ".com",
screamer 0:c04d932e96c9 424 ".org",
screamer 0:c04d932e96c9 425 ".edu",
screamer 0:c04d932e96c9 426 ".net",
screamer 0:c04d932e96c9 427 ".info",
screamer 0:c04d932e96c9 428 ".biz",
screamer 0:c04d932e96c9 429 ".gov"
screamer 0:c04d932e96c9 430 };
screamer 0:c04d932e96c9 431 const size_t NUM_SUFFIXES = sizeof(suffixes) / sizeof(char *);
screamer 0:c04d932e96c9 432
screamer 0:c04d932e96c9 433 sizeofURIDataOut = 0;
screamer 0:c04d932e96c9 434 memset(uriDataOut, 0, sizeof(UriData_t));
screamer 0:c04d932e96c9 435
screamer 0:c04d932e96c9 436 if ((uriDataIn == NULL) || (strlen(uriDataIn) == 0)) {
screamer 0:c04d932e96c9 437 return;
screamer 0:c04d932e96c9 438 }
screamer 0:c04d932e96c9 439
screamer 0:c04d932e96c9 440 /*
screamer 0:c04d932e96c9 441 * handle prefix
screamer 0:c04d932e96c9 442 */
screamer 0:c04d932e96c9 443 for (unsigned i = 0; i < NUM_PREFIXES; i++) {
screamer 0:c04d932e96c9 444 size_t prefixLen = strlen(prefixes[i]);
screamer 0:c04d932e96c9 445 if (strncmp(uriDataIn, prefixes[i], prefixLen) == 0) {
screamer 0:c04d932e96c9 446 uriDataOut[sizeofURIDataOut++] = i;
screamer 0:c04d932e96c9 447 uriDataIn += prefixLen;
screamer 0:c04d932e96c9 448 break;
screamer 0:c04d932e96c9 449 }
screamer 0:c04d932e96c9 450 }
screamer 0:c04d932e96c9 451
screamer 0:c04d932e96c9 452 /*
screamer 0:c04d932e96c9 453 * handle suffixes
screamer 0:c04d932e96c9 454 */
screamer 0:c04d932e96c9 455 while (*uriDataIn && (sizeofURIDataOut < URI_DATA_MAX)) {
screamer 0:c04d932e96c9 456 /* check for suffix match */
screamer 0:c04d932e96c9 457 unsigned i;
screamer 0:c04d932e96c9 458 for (i = 0; i < NUM_SUFFIXES; i++) {
screamer 0:c04d932e96c9 459 size_t suffixLen = strlen(suffixes[i]);
screamer 0:c04d932e96c9 460 if (strncmp(uriDataIn, suffixes[i], suffixLen) == 0) {
screamer 0:c04d932e96c9 461 uriDataOut[sizeofURIDataOut++] = i;
screamer 0:c04d932e96c9 462 uriDataIn += suffixLen;
screamer 0:c04d932e96c9 463 break; /* from the for loop for checking against suffixes */
screamer 0:c04d932e96c9 464 }
screamer 0:c04d932e96c9 465 }
screamer 0:c04d932e96c9 466 /* This is the default case where we've got an ordinary character which doesn't match a suffix. */
screamer 0:c04d932e96c9 467 if (i == NUM_SUFFIXES) {
screamer 0:c04d932e96c9 468 uriDataOut[sizeofURIDataOut++] = *uriDataIn;
screamer 0:c04d932e96c9 469 ++uriDataIn;
screamer 0:c04d932e96c9 470 }
screamer 0:c04d932e96c9 471 }
screamer 0:c04d932e96c9 472 }
screamer 0:c04d932e96c9 473 };
screamer 0:c04d932e96c9 474
screamer 0:c04d932e96c9 475 #endif // SERVICES_ZIPBEACONCONFIGSERVICE_H_