Pinned to some recent date

Committer:
Simon Cooksey
Date:
Thu Nov 17 16:43:53 2016 +0000
Revision:
0:fb7af294d5d9
Initial commit

Who changed what in which revision?

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