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:
mbedAustin
Date:
Thu Jul 23 18:00:59 2015 +0000
Revision:
22:160766614338
Parent:
21:f4646308f363
[[Fix]] Eddystone functional with onRadioNotificationCallback. The beacon is functionally complete. Everything going forward is just polish.

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 "mbed.h"
mbedAustin 16:a7d07ea94b31 21 #include "ble/BLE.h"
mbedAustin 16:a7d07ea94b31 22
screamer 0:c04d932e96c9 23
screamer 0:c04d932e96c9 24 #define UUID_URI_BEACON(FIRST, SECOND) { \
screamer 0:c04d932e96c9 25 0xee, 0x0c, FIRST, SECOND, 0x87, 0x86, 0x40, 0xba, \
screamer 0:c04d932e96c9 26 0xab, 0x96, 0x99, 0xb9, 0x1a, 0xc9, 0x81, 0xd8, \
screamer 0:c04d932e96c9 27 }
screamer 0:c04d932e96c9 28
screamer 0:c04d932e96c9 29 static const uint8_t UUID_URI_BEACON_SERVICE[] = UUID_URI_BEACON(0x20, 0x80);
screamer 0:c04d932e96c9 30 static const uint8_t UUID_LOCK_STATE_CHAR[] = UUID_URI_BEACON(0x20, 0x81);
screamer 0:c04d932e96c9 31 static const uint8_t UUID_LOCK_CHAR[] = UUID_URI_BEACON(0x20, 0x82);
screamer 0:c04d932e96c9 32 static const uint8_t UUID_UNLOCK_CHAR[] = UUID_URI_BEACON(0x20, 0x83);
screamer 0:c04d932e96c9 33 static const uint8_t UUID_URI_DATA_CHAR[] = UUID_URI_BEACON(0x20, 0x84);
screamer 0:c04d932e96c9 34 static const uint8_t UUID_FLAGS_CHAR[] = UUID_URI_BEACON(0x20, 0x85);
screamer 0:c04d932e96c9 35 static const uint8_t UUID_ADV_POWER_LEVELS_CHAR[] = UUID_URI_BEACON(0x20, 0x86);
screamer 0:c04d932e96c9 36 static const uint8_t UUID_TX_POWER_MODE_CHAR[] = UUID_URI_BEACON(0x20, 0x87);
screamer 0:c04d932e96c9 37 static const uint8_t UUID_BEACON_PERIOD_CHAR[] = UUID_URI_BEACON(0x20, 0x88);
screamer 0:c04d932e96c9 38 static const uint8_t UUID_RESET_CHAR[] = UUID_URI_BEACON(0x20, 0x89);
mbedAustin 14:5a2a104a21a8 39 static const uint8_t BEACON_EDDYSTONE[] = {0xAA, 0xFE};
screamer 0:c04d932e96c9 40
mbedAustin 16:a7d07ea94b31 41 //Debug is disabled by default
mbedAustin 22:160766614338 42 #if 0
mbedAustin 16:a7d07ea94b31 43 #define DBG(x, ...) printf("[EddyStone: DBG]"x" \t[%s,%d]\r\n", ##__VA_ARGS__,__FILE__,__LINE__);
mbedAustin 16:a7d07ea94b31 44 #define WARN(x, ...) printf("[EddyStone: WARN]"x" \t[%s,%d]\r\n", ##__VA_ARGS__,__FILE__,__LINE__);
mbedAustin 16:a7d07ea94b31 45 #define ERR(x, ...) printf("[EddyStone: ERR]"x" \t[%s,%d]\r\n", ##__VA_ARGS__,__FILE__,__LINE__);
mbedAustin 16:a7d07ea94b31 46 #else
mbedAustin 16:a7d07ea94b31 47 #define DBG(x, ...) //wait_us(10);
mbedAustin 16:a7d07ea94b31 48 #define WARN(x, ...) //wait_us(10);
mbedAustin 16:a7d07ea94b31 49 #define ERR(x, ...)
mbedAustin 16:a7d07ea94b31 50 #endif
mbedAustin 16:a7d07ea94b31 51
screamer 0:c04d932e96c9 52 /**
screamer 0:c04d932e96c9 53 * @class ZipBeaconConfigService
screamer 0:c04d932e96c9 54 * @brief ZipBeacon Configuration Service. Can be used to set URL, adjust power levels, and set flags.
screamer 0:c04d932e96c9 55 * See http://uribeacon.org
screamer 0:c04d932e96c9 56 *
screamer 0:c04d932e96c9 57 */
mbedAustin 15:af8c24f34a9f 58 class ZipBeaconConfigService
mbedAustin 15:af8c24f34a9f 59 {
mbedAustin 15:af8c24f34a9f 60 public:
screamer 0:c04d932e96c9 61 /**
screamer 0:c04d932e96c9 62 * @brief Transmission Power Modes for UriBeacon
screamer 0:c04d932e96c9 63 */
screamer 0:c04d932e96c9 64 static const uint8_t TX_POWER_MODE_LOWEST = 0; /*!< Lowest TX power mode */
screamer 0:c04d932e96c9 65 static const uint8_t TX_POWER_MODE_LOW = 1; /*!< Low TX power mode */
screamer 0:c04d932e96c9 66 static const uint8_t TX_POWER_MODE_MEDIUM = 2; /*!< Medium TX power mode */
screamer 0:c04d932e96c9 67 static const uint8_t TX_POWER_MODE_HIGH = 3; /*!< High TX power mode */
screamer 0:c04d932e96c9 68 static const unsigned int NUM_POWER_MODES = 4; /*!< Number of Power Modes defined */
screamer 0:c04d932e96c9 69
screamer 0:c04d932e96c9 70 static const int ADVERTISING_INTERVAL_MSEC = 1000; // Advertising interval for config service.
screamer 0:c04d932e96c9 71 static const int SERVICE_DATA_MAX = 31; // Maximum size of service data in ADV packets
screamer 0:c04d932e96c9 72
screamer 0:c04d932e96c9 73 typedef uint8_t Lock_t[16]; /* 128 bits */
screamer 0:c04d932e96c9 74 typedef int8_t PowerLevels_t[NUM_POWER_MODES];
screamer 0:c04d932e96c9 75
mbedAustin 15:af8c24f34a9f 76 // There are currently 3 subframes defined, URI, UID, and TLM
mbedAustin 15:af8c24f34a9f 77 #define EDDYSTONE_MAX_FRAMETYPE 3
mbedAustin 14:5a2a104a21a8 78 void (*frames[EDDYSTONE_MAX_FRAMETYPE])(uint8_t *, uint32_t);
mbedAustin 14:5a2a104a21a8 79 uint8_t frameIndex;
screamer 0:c04d932e96c9 80 static const int URI_DATA_MAX = 18;
screamer 0:c04d932e96c9 81 typedef uint8_t UriData_t[URI_DATA_MAX];
mbedAustin 15:af8c24f34a9f 82
mbedAustin 11:73ea4ef7f5a4 83 // UID Frame Type subfields
mbedAustin 10:b5d19bcf23cf 84 static const int UID_NAMESPACEID_SIZE = 10;
mbedAustin 11:73ea4ef7f5a4 85 typedef uint8_t UIDNamespaceID_t[UID_NAMESPACEID_SIZE];
mbedAustin 10:b5d19bcf23cf 86 static const int UID_INSTANCEID_SIZE = 6;
mbedAustin 11:73ea4ef7f5a4 87 typedef uint8_t UIDInstanceID_t[UID_INSTANCEID_SIZE];
screamer 0:c04d932e96c9 88
mbedAustin 11:73ea4ef7f5a4 89 // Eddystone Frame Type ID
screamer 0:c04d932e96c9 90 static const uint8_t FRAME_TYPE_UID = 0x00;
screamer 0:c04d932e96c9 91 static const uint8_t FRAME_TYPE_URL = 0x10;
screamer 0:c04d932e96c9 92 static const uint8_t FRAME_TYPE_TLM = 0x20;
mbedAustin 15:af8c24f34a9f 93
mbedAustin 11:73ea4ef7f5a4 94 static const uint8_t FRAME_SIZE_TLM = 14; // TLM frame is a constant 14Bytes
mbedAustin 11:73ea4ef7f5a4 95 static const uint8_t FRAME_SIZE_UID = 20; // includes RFU bytes
screamer 0:c04d932e96c9 96
screamer 0:c04d932e96c9 97 struct Params_t {
screamer 0:c04d932e96c9 98 Lock_t lock;
screamer 0:c04d932e96c9 99 uint8_t uriDataLength;
screamer 0:c04d932e96c9 100 UriData_t uriData;
screamer 0:c04d932e96c9 101 uint8_t flags;
screamer 0:c04d932e96c9 102 PowerLevels_t advPowerLevels; // Current value of AdvertisedPowerLevels
screamer 0:c04d932e96c9 103 uint8_t txPowerMode; // Firmware power levels used with setTxPower()
screamer 0:c04d932e96c9 104 uint16_t beaconPeriod;
mbedAustin 10:b5d19bcf23cf 105 uint8_t tlmVersion; // version of TLM packet
mbedAustin 11:73ea4ef7f5a4 106 UIDNamespaceID_t uidNamespaceID; // UUID type, Namespace ID, 10B
mbedAustin 11:73ea4ef7f5a4 107 UIDInstanceID_t uidInstanceID; // UUID type, Instance ID, 6B
screamer 0:c04d932e96c9 108 };
mbedAustin 15:af8c24f34a9f 109
screamer 0:c04d932e96c9 110 /**
screamer 0:c04d932e96c9 111 * @param[ref] ble
screamer 0:c04d932e96c9 112 * BLEDevice object for the underlying controller.
screamer 0:c04d932e96c9 113 * @param[in/out] paramsIn
screamer 0:c04d932e96c9 114 * Reference to application-visible beacon state, loaded
screamer 0:c04d932e96c9 115 * from persistent storage at startup.
screamer 0:c04d932e96c9 116 * @paramsP[in] resetToDefaultsFlag
screamer 0:c04d932e96c9 117 * Applies to the state of the 'paramsIn' parameter.
screamer 0:c04d932e96c9 118 * If true, it indicates that paramsIn is potentially
screamer 0:c04d932e96c9 119 * un-initialized, and default values should be used
screamer 0:c04d932e96c9 120 * instead. Otherwise, paramsIn overrides the defaults.
screamer 0:c04d932e96c9 121 * @param[in] defaultUriDataIn
screamer 0:c04d932e96c9 122 * Default un-encoded URI; applies only if the resetToDefaultsFlag is true.
screamer 0:c04d932e96c9 123 * @param[in] defaultAdvPowerLevelsIn
screamer 0:c04d932e96c9 124 * Default power-levels array; applies only if the resetToDefaultsFlag is true.
screamer 0:c04d932e96c9 125 */
screamer 0:c04d932e96c9 126 ZipBeaconConfigService(BLEDevice &bleIn,
screamer 0:c04d932e96c9 127 Params_t &paramsIn,
screamer 0:c04d932e96c9 128 bool resetToDefaultsFlag,
screamer 0:c04d932e96c9 129 const char *defaultURIDataIn,
screamer 0:c04d932e96c9 130 PowerLevels_t &defaultAdvPowerLevelsIn) :
screamer 0:c04d932e96c9 131 ble(bleIn),
screamer 0:c04d932e96c9 132 params(paramsIn),
screamer 0:c04d932e96c9 133 defaultUriDataLength(),
screamer 0:c04d932e96c9 134 defaultUriData(),
screamer 0:c04d932e96c9 135 defaultAdvPowerLevels(defaultAdvPowerLevelsIn),
screamer 0:c04d932e96c9 136 initSucceeded(false),
screamer 0:c04d932e96c9 137 resetFlag(),
screamer 0:c04d932e96c9 138 lockedStateChar(UUID_LOCK_STATE_CHAR, &lockedState),
screamer 0:c04d932e96c9 139 lockChar(UUID_LOCK_CHAR, &params.lock),
screamer 0:c04d932e96c9 140 uriDataChar(UUID_URI_DATA_CHAR, params.uriData, 0, URI_DATA_MAX,
screamer 0:c04d932e96c9 141 GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE),
screamer 0:c04d932e96c9 142 unlockChar(UUID_UNLOCK_CHAR, &params.lock),
screamer 0:c04d932e96c9 143 flagsChar(UUID_FLAGS_CHAR, &params.flags),
screamer 0:c04d932e96c9 144 advPowerLevelsChar(UUID_ADV_POWER_LEVELS_CHAR, &params.advPowerLevels),
screamer 0:c04d932e96c9 145 txPowerModeChar(UUID_TX_POWER_MODE_CHAR, &params.txPowerMode),
screamer 0:c04d932e96c9 146 beaconPeriodChar(UUID_BEACON_PERIOD_CHAR, &params.beaconPeriod),
screamer 0:c04d932e96c9 147 resetChar(UUID_RESET_CHAR, &resetFlag) {
screamer 0:c04d932e96c9 148
mbedAustin 10:b5d19bcf23cf 149 encodeURI(defaultURIDataIn, defaultUriData, defaultUriDataLength); // encode URL to URL Formatting
screamer 0:c04d932e96c9 150 if (defaultUriDataLength > URI_DATA_MAX) {
screamer 0:c04d932e96c9 151 return;
screamer 0:c04d932e96c9 152 }
screamer 0:c04d932e96c9 153
screamer 0:c04d932e96c9 154 if (!resetToDefaultsFlag && (params.uriDataLength > URI_DATA_MAX)) {
screamer 0:c04d932e96c9 155 resetToDefaultsFlag = true;
screamer 0:c04d932e96c9 156 }
screamer 0:c04d932e96c9 157 if (resetToDefaultsFlag) {
screamer 0:c04d932e96c9 158 resetToDefaults();
screamer 0:c04d932e96c9 159 } else {
screamer 0:c04d932e96c9 160 updateCharacteristicValues();
screamer 0:c04d932e96c9 161 }
screamer 0:c04d932e96c9 162
screamer 0:c04d932e96c9 163 lockedState = isLocked();
screamer 0:c04d932e96c9 164
screamer 0:c04d932e96c9 165 lockChar.setWriteAuthorizationCallback(this, &ZipBeaconConfigService::lockAuthorizationCallback);
screamer 0:c04d932e96c9 166 unlockChar.setWriteAuthorizationCallback(this, &ZipBeaconConfigService::unlockAuthorizationCallback);
screamer 0:c04d932e96c9 167 uriDataChar.setWriteAuthorizationCallback(this, &ZipBeaconConfigService::uriDataWriteAuthorizationCallback);
screamer 0:c04d932e96c9 168 flagsChar.setWriteAuthorizationCallback(this, &ZipBeaconConfigService::basicAuthorizationCallback<uint8_t>);
screamer 0:c04d932e96c9 169 advPowerLevelsChar.setWriteAuthorizationCallback(this, &ZipBeaconConfigService::basicAuthorizationCallback<PowerLevels_t>);
screamer 0:c04d932e96c9 170 txPowerModeChar.setWriteAuthorizationCallback(this, &ZipBeaconConfigService::powerModeAuthorizationCallback);
screamer 0:c04d932e96c9 171 beaconPeriodChar.setWriteAuthorizationCallback(this, &ZipBeaconConfigService::basicAuthorizationCallback<uint16_t>);
screamer 0:c04d932e96c9 172 resetChar.setWriteAuthorizationCallback(this, &ZipBeaconConfigService::basicAuthorizationCallback<uint8_t>);
screamer 0:c04d932e96c9 173
screamer 0:c04d932e96c9 174 static GattCharacteristic *charTable[] = {
screamer 0:c04d932e96c9 175 &lockedStateChar, &lockChar, &unlockChar, &uriDataChar,
screamer 0:c04d932e96c9 176 &flagsChar, &advPowerLevelsChar, &txPowerModeChar, &beaconPeriodChar, &resetChar
screamer 0:c04d932e96c9 177 };
screamer 0:c04d932e96c9 178
screamer 0:c04d932e96c9 179 GattService configService(UUID_URI_BEACON_SERVICE, charTable, sizeof(charTable) / sizeof(GattCharacteristic *));
screamer 0:c04d932e96c9 180
screamer 0:c04d932e96c9 181 ble.addService(configService);
screamer 0:c04d932e96c9 182 ble.onDataWritten(this, &ZipBeaconConfigService::onDataWrittenCallback);
screamer 0:c04d932e96c9 183
screamer 0:c04d932e96c9 184 setupZipBeaconConfigAdvertisements(); /* Setup advertising for the configService. */
screamer 0:c04d932e96c9 185
screamer 0:c04d932e96c9 186 initSucceeded = true;
screamer 0:c04d932e96c9 187 }
screamer 0:c04d932e96c9 188
screamer 0:c04d932e96c9 189 bool configuredSuccessfully(void) const {
screamer 0:c04d932e96c9 190 return initSucceeded;
screamer 0:c04d932e96c9 191 }
screamer 0:c04d932e96c9 192
screamer 0:c04d932e96c9 193 /* Start out by advertising the configService for a limited time after
screamer 0:c04d932e96c9 194 * startup; and switch to the normal non-connectible beacon functionality
screamer 0:c04d932e96c9 195 * afterwards. */
mbedAustin 15:af8c24f34a9f 196 void setupZipBeaconConfigAdvertisements() {
mbedAustin 10:b5d19bcf23cf 197 const char DEVICE_NAME[] = "eddystone Config";
screamer 0:c04d932e96c9 198
screamer 0:c04d932e96c9 199 ble.clearAdvertisingPayload();
screamer 0:c04d932e96c9 200
screamer 0:c04d932e96c9 201 ble.accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
screamer 0:c04d932e96c9 202
screamer 0:c04d932e96c9 203 // UUID is in different order in the ADV frame (!)
screamer 0:c04d932e96c9 204 uint8_t reversedServiceUUID[sizeof(UUID_URI_BEACON_SERVICE)];
screamer 0:c04d932e96c9 205 for (unsigned int i = 0; i < sizeof(UUID_URI_BEACON_SERVICE); i++) {
screamer 0:c04d932e96c9 206 reversedServiceUUID[i] = UUID_URI_BEACON_SERVICE[sizeof(UUID_URI_BEACON_SERVICE) - i - 1];
screamer 0:c04d932e96c9 207 }
screamer 0:c04d932e96c9 208 ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_128BIT_SERVICE_IDS, reversedServiceUUID, sizeof(reversedServiceUUID));
screamer 0:c04d932e96c9 209 ble.accumulateAdvertisingPayload(GapAdvertisingData::GENERIC_TAG);
screamer 0:c04d932e96c9 210 ble.accumulateScanResponse(GapAdvertisingData::COMPLETE_LOCAL_NAME, reinterpret_cast<const uint8_t *>(&DEVICE_NAME), sizeof(DEVICE_NAME));
screamer 0:c04d932e96c9 211 ble.accumulateScanResponse(
screamer 0:c04d932e96c9 212 GapAdvertisingData::TX_POWER_LEVEL,
screamer 0:c04d932e96c9 213 reinterpret_cast<uint8_t *>(&defaultAdvPowerLevels[ZipBeaconConfigService::TX_POWER_MODE_LOW]),
screamer 0:c04d932e96c9 214 sizeof(uint8_t));
screamer 0:c04d932e96c9 215
screamer 0:c04d932e96c9 216 ble.setTxPower(params.advPowerLevels[params.txPowerMode]);
screamer 0:c04d932e96c9 217 ble.setDeviceName(reinterpret_cast<const uint8_t *>(&DEVICE_NAME));
screamer 0:c04d932e96c9 218 ble.setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
screamer 0:c04d932e96c9 219 ble.setAdvertisingInterval(Gap::MSEC_TO_ADVERTISEMENT_DURATION_UNITS(ADVERTISING_INTERVAL_MSEC));
screamer 0:c04d932e96c9 220 }
mbedAustin 15:af8c24f34a9f 221
mbedAustin 10:b5d19bcf23cf 222 /*
mbedAustin 15:af8c24f34a9f 223 * Set Eddystone UID Frame information.
mbedAustin 10:b5d19bcf23cf 224 * @param[in] power TX Power in dB measured at 0 meters from the device. Range of -100 to +20 dB.
mbedAustin 10:b5d19bcf23cf 225 * @param namespaceID 10B namespace ID
mbedAustin 10:b5d19bcf23cf 226 * @param instanceID 6B instance ID
mbedAustin 15:af8c24f34a9f 227 * @param RFU 2B of RFU, initialized to 0x0000 and not broadcast, included for future reference.
mbedAustin 10:b5d19bcf23cf 228 *
mbedAustin 10:b5d19bcf23cf 229 */
mbedAustin 15:af8c24f34a9f 230 void setUIDFrameData(int8_t power, UIDNamespaceID_t namespaceID, UIDInstanceID_t instanceID, uint16_t RFU = 0x0000) {
mbedAustin 22:160766614338 231 if(power > 20) {
mbedAustin 22:160766614338 232 power = 20;
mbedAustin 22:160766614338 233 }
mbedAustin 22:160766614338 234 if(power < -100) {
mbedAustin 22:160766614338 235 power = -100;
mbedAustin 22:160766614338 236 }
mbedAustin 11:73ea4ef7f5a4 237 defaultUidPower = power;
mbedAustin 11:73ea4ef7f5a4 238 memcpy(defaultUidNamespaceID, namespaceID, UID_NAMESPACEID_SIZE);
mbedAustin 11:73ea4ef7f5a4 239 memcpy(defaultUidInstanceID, instanceID, UID_INSTANCEID_SIZE);
mbedAustin 15:af8c24f34a9f 240 uidRFU = (uint16_t)RFU; // this is probably bad form, but it doesnt really matter yet.
mbedAustin 11:73ea4ef7f5a4 241 return;
mbedAustin 11:73ea4ef7f5a4 242 }
mbedAustin 15:af8c24f34a9f 243
mbedAustin 11:73ea4ef7f5a4 244 /*
mbedAustin 11:73ea4ef7f5a4 245 * Construct UID frame from private variables
mbedAustin 11:73ea4ef7f5a4 246 * @param[in/out] Data pointer to array to store constructed frame in
mbedAustin 11:73ea4ef7f5a4 247 * @param[in] maxSize number of bytes left in array, effectively how much emtpy space is available to write to
mbedAustin 15:af8c24f34a9f 248 * @return number of bytes used. negative number indicates error message.
mbedAustin 11:73ea4ef7f5a4 249 */
mbedAustin 15:af8c24f34a9f 250 int constructUIDFrame(uint8_t * Data, uint8_t maxSize) {
mbedAustin 17:0458759c40e4 251
mbedAustin 11:73ea4ef7f5a4 252 int index = 0;
mbedAustin 11:73ea4ef7f5a4 253 Data[index++] = FRAME_TYPE_UID; // 1B Type
mbedAustin 22:160766614338 254 if(defaultUidPower > 20) {
mbedAustin 22:160766614338 255 defaultUidPower = 20; // enforce range of vaild values.
mbedAustin 22:160766614338 256 }
mbedAustin 22:160766614338 257 if(defaultUidPower < -100) {
mbedAustin 22:160766614338 258 defaultUidPower = -100;
mbedAustin 22:160766614338 259 }
mbedAustin 11:73ea4ef7f5a4 260 Data[index++] = defaultUidPower; // 1B Power @ 0meter
mbedAustin 15:af8c24f34a9f 261 for(int x = 0; x < UID_NAMESPACEID_SIZE; x++) { // 10B Namespce ID
mbedAustin 11:73ea4ef7f5a4 262 Data[index++] = defaultUidNamespaceID[x];
mbedAustin 15:af8c24f34a9f 263 }
mbedAustin 15:af8c24f34a9f 264 for(int x = 0; x< UID_INSTANCEID_SIZE; x++) { // 6B Instance ID
mbedAustin 11:73ea4ef7f5a4 265 Data[index++] = defaultUidInstanceID[x];
mbedAustin 15:af8c24f34a9f 266 }
mbedAustin 19:f7c33fa88ca5 267 if(0 != uidRFU) { // 2B RFU, include if non-zero, otherwise ignore
mbedAustin 21:f4646308f363 268 Data[index++] = (uint8_t)(uidRFU >> 0);
mbedAustin 11:73ea4ef7f5a4 269 Data[index++] = (uint8_t)(uidRFU >> 8);
mbedAustin 11:73ea4ef7f5a4 270 }
mbedAustin 17:0458759c40e4 271 DBG("construcUIDFrame %d, %d",maxSize,index);
mbedAustin 11:73ea4ef7f5a4 272 return index;
mbedAustin 10:b5d19bcf23cf 273 }
mbedAustin 15:af8c24f34a9f 274
mbedAustin 10:b5d19bcf23cf 275 /*
mbedAustin 15:af8c24f34a9f 276 * Set Eddystone URL Frame information.
mbedAustin 10:b5d19bcf23cf 277 * @param[in] power TX Power in dB measured at 0 meters from the device.
mbedAustin 15:af8c24f34a9f 278 * @param url URL to encode
mbedAustin 10:b5d19bcf23cf 279 * @return false on success, true on failure.
mbedAustin 10:b5d19bcf23cf 280 */
mbedAustin 15:af8c24f34a9f 281 bool setURLFrameData(int8_t power, const char * url) {
mbedAustin 11:73ea4ef7f5a4 282 defaultUrlPower = power;
mbedAustin 11:73ea4ef7f5a4 283 encodeURI(url, defaultUriData, defaultUriDataLength); // encode URL to URL Formatting
mbedAustin 10:b5d19bcf23cf 284 if (defaultUriDataLength > URI_DATA_MAX) {
mbedAustin 10:b5d19bcf23cf 285 return true; // error, URL is too big
mbedAustin 10:b5d19bcf23cf 286 }
mbedAustin 11:73ea4ef7f5a4 287 return false;
mbedAustin 11:73ea4ef7f5a4 288 }
mbedAustin 15:af8c24f34a9f 289
mbedAustin 11:73ea4ef7f5a4 290 /*
mbedAustin 11:73ea4ef7f5a4 291 * Construct URL frame from private variables
mbedAustin 11:73ea4ef7f5a4 292 * @param[in/out] Data pointer to array to store constructed frame in
mbedAustin 11:73ea4ef7f5a4 293 * @param[in] maxSize number of bytes left in array, effectively how much emtpy space is available to write to
mbedAustin 15:af8c24f34a9f 294 * @return number of bytes used. negative number indicates error message.
mbedAustin 11:73ea4ef7f5a4 295 */
mbedAustin 15:af8c24f34a9f 296 int constructURLFrame(uint8_t * Data, uint8_t maxSize) {
mbedAustin 11:73ea4ef7f5a4 297 int index = 0;
mbedAustin 11:73ea4ef7f5a4 298 Data[index++] = FRAME_TYPE_URL; // 1B Type
mbedAustin 15:af8c24f34a9f 299 Data[index++] = params.txPowerMode; // 1B TX Power
mbedAustin 15:af8c24f34a9f 300 for(int x = 0; x < defaultUriDataLength; x++) { // 18B of URL Prefix + encoded URL
mbedAustin 11:73ea4ef7f5a4 301 Data[index++] = defaultUriData[x];
mbedAustin 11:73ea4ef7f5a4 302 }
mbedAustin 17:0458759c40e4 303 DBG("constructURLFrame: %d, %d",maxSize,index);
mbedAustin 15:af8c24f34a9f 304 return index;
mbedAustin 10:b5d19bcf23cf 305 }
mbedAustin 15:af8c24f34a9f 306
mbedAustin 10:b5d19bcf23cf 307 /*
mbedAustin 15:af8c24f34a9f 308 * Set Eddystone TLM Frame information.
mbedAustin 10:b5d19bcf23cf 309 * @param[in] Version of the TLM beacon data format
mbedAustin 10:b5d19bcf23cf 310 * @param batteryVoltage in milivolts
mbedAustin 10:b5d19bcf23cf 311 * @param beaconTemp in 8.8 floating point notation
mbedAustin 10:b5d19bcf23cf 312 *
mbedAustin 10:b5d19bcf23cf 313 */
mbedAustin 15:af8c24f34a9f 314 void setTLMFrameData(uint8_t version, uint16_t batteryVoltage, uint16_t beaconTemp, uint32_t pduCount = 0, uint32_t timeSinceBoot = 0) {
mbedAustin 10:b5d19bcf23cf 315 TlmVersion = version;
mbedAustin 10:b5d19bcf23cf 316 TlmBatteryVoltage = batteryVoltage;
mbedAustin 10:b5d19bcf23cf 317 TlmBeaconTemp = beaconTemp;
mbedAustin 10:b5d19bcf23cf 318 TlmPduCount = pduCount; // reset
mbedAustin 10:b5d19bcf23cf 319 TlmTimeSinceBoot = timeSinceBoot; // reset
mbedAustin 15:af8c24f34a9f 320 return;
mbedAustin 10:b5d19bcf23cf 321 }
mbedAustin 15:af8c24f34a9f 322
mbedAustin 11:73ea4ef7f5a4 323 /*
mbedAustin 11:73ea4ef7f5a4 324 * Construct TLM frame from private variables
mbedAustin 11:73ea4ef7f5a4 325 * @param[in/out] Data pointer to array to store constructed frame in
mbedAustin 11:73ea4ef7f5a4 326 * @param[in] maxSize number of bytes left in array, effectively how much emtpy space is available to write to
mbedAustin 15:af8c24f34a9f 327 * @return number of bytes used. negative number indicates error message.
mbedAustin 11:73ea4ef7f5a4 328 */
mbedAustin 15:af8c24f34a9f 329 int constructTLMFrame(uint8_t * Data, uint8_t maxSize) {
mbedAustin 11:73ea4ef7f5a4 330 int index = 0;
mbedAustin 11:73ea4ef7f5a4 331 Data[index++] = FRAME_TYPE_TLM; // Eddystone frame type = Telemetry
mbedAustin 11:73ea4ef7f5a4 332 Data[index++] = TlmVersion; // TLM Version Number
mbedAustin 20:3b133cfafc39 333 Data[index++] = (uint8_t)(TlmBatteryVoltage>>8); // Battery Voltage[0]
mbedAustin 20:3b133cfafc39 334 Data[index++] = (uint8_t)(TlmBatteryVoltage>>0); // Battery Voltage[1]
mbedAustin 20:3b133cfafc39 335 Data[index++] = (uint8_t)(TlmBeaconTemp>>8); // Beacon Temp[0]
mbedAustin 20:3b133cfafc39 336 Data[index++] = (uint8_t)(TlmBeaconTemp>>0); // Beacon Temp[1]
mbedAustin 20:3b133cfafc39 337 Data[index++] = (uint8_t)(TlmPduCount>>24); // PDU Count [0]
mbedAustin 20:3b133cfafc39 338 Data[index++] = (uint8_t)(TlmPduCount>>16); // PDU Count [1]
mbedAustin 20:3b133cfafc39 339 Data[index++] = (uint8_t)(TlmPduCount>>8); // PDU Count [2]
mbedAustin 20:3b133cfafc39 340 Data[index++] = (uint8_t)(TlmPduCount>>0); // PDU Count [3]
mbedAustin 20:3b133cfafc39 341 Data[index++] = (uint8_t)(TlmTimeSinceBoot>>24); // Time Since Boot [0]
mbedAustin 20:3b133cfafc39 342 Data[index++] = (uint8_t)(TlmTimeSinceBoot>>16); // Time Since Boot [1]
mbedAustin 20:3b133cfafc39 343 Data[index++] = (uint8_t)(TlmTimeSinceBoot>>8); // Time Since Boot [2]
mbedAustin 20:3b133cfafc39 344 Data[index++] = (uint8_t)(TlmTimeSinceBoot>>0); // Time Since Boot [3]
mbedAustin 17:0458759c40e4 345 DBG("constructURLFrame: %d, %d",maxSize,index);
mbedAustin 11:73ea4ef7f5a4 346 return index;
mbedAustin 11:73ea4ef7f5a4 347 }
mbedAustin 15:af8c24f34a9f 348
mbedAustin 12:ced5e837c511 349 /*
mbedAustin 12:ced5e837c511 350 * Update the TLM frame battery voltage value
mbedAustin 12:ced5e837c511 351 * @param[in] voltagemv Voltage to update the TLM field battery voltage with (in mV)
mbedAustin 12:ced5e837c511 352 * @return nothing
mbedAustin 12:ced5e837c511 353 */
mbedAustin 15:af8c24f34a9f 354 void updateTlmBatteryVoltage(uint16_t voltagemv) {
mbedAustin 12:ced5e837c511 355 TlmBatteryVoltage = voltagemv;
mbedAustin 15:af8c24f34a9f 356 return;
mbedAustin 12:ced5e837c511 357 }
mbedAustin 15:af8c24f34a9f 358
mbedAustin 12:ced5e837c511 359 /*
mbedAustin 15:af8c24f34a9f 360 * Update the TLM frame beacon temperature
mbedAustin 12:ced5e837c511 361 * @param[in] temp Temperature of beacon (in 8.8fpn)
mbedAustin 12:ced5e837c511 362 * @return nothing
mbedAustin 12:ced5e837c511 363 */
mbedAustin 15:af8c24f34a9f 364 void updateTlmBeaconTemp(uint16_t temp) {
mbedAustin 12:ced5e837c511 365 TlmBeaconTemp = temp;
mbedAustin 15:af8c24f34a9f 366 return;
mbedAustin 12:ced5e837c511 367 }
mbedAustin 15:af8c24f34a9f 368
mbedAustin 12:ced5e837c511 369 /*
mbedAustin 12:ced5e837c511 370 * Update the TLM frame PDU Count field
mbedAustin 12:ced5e837c511 371 * @param[in] pduCount Number of Advertisiting frames sent since powerup
mbedAustin 12:ced5e837c511 372 * @return nothing
mbedAustin 12:ced5e837c511 373 */
mbedAustin 15:af8c24f34a9f 374 void updateTlmPduCount(uint32_t pduCount) {
mbedAustin 12:ced5e837c511 375 TlmPduCount = pduCount;
mbedAustin 15:af8c24f34a9f 376 return;
mbedAustin 12:ced5e837c511 377 }
mbedAustin 15:af8c24f34a9f 378
mbedAustin 12:ced5e837c511 379 /*
mbedAustin 12:ced5e837c511 380 * Update the TLM frame Time since boot in 0.1s incriments
mbedAustin 12:ced5e837c511 381 * @param[in] timeSinceBoot Time since boot in 0.1s incriments
mbedAustin 12:ced5e837c511 382 * @return nothing
mbedAustin 12:ced5e837c511 383 */
mbedAustin 15:af8c24f34a9f 384 void updateTlmTimeSinceBoot(uint32_t timeSinceBoot) {
mbedAustin 12:ced5e837c511 385 TlmTimeSinceBoot = timeSinceBoot;
mbedAustin 15:af8c24f34a9f 386 return;
mbedAustin 12:ced5e837c511 387 }
mbedAustin 15:af8c24f34a9f 388
mbedAustin 14:5a2a104a21a8 389 /*
mbedAustin 16:a7d07ea94b31 390 * callback function, called every 0.1s, incriments the TimeSinceBoot field in the TLM frame
mbedAustin 16:a7d07ea94b31 391 * @return nothing
mbedAustin 16:a7d07ea94b31 392 */
mbedAustin 16:a7d07ea94b31 393 void tsbCallback(void) {
mbedAustin 16:a7d07ea94b31 394 TlmTimeSinceBoot++;
mbedAustin 16:a7d07ea94b31 395 }
mbedAustin 16:a7d07ea94b31 396
mbedAustin 16:a7d07ea94b31 397 /*
mbedAustin 16:a7d07ea94b31 398 * Update advertising data
mbedAustin 16:a7d07ea94b31 399 * @return true on success, false on failure
mbedAustin 16:a7d07ea94b31 400 */
mbedAustin 16:a7d07ea94b31 401 bool updateAdvPacket(uint8_t serviceData[], unsigned serviceDataLen) {
mbedAustin 16:a7d07ea94b31 402 // Fields from the Service
mbedAustin 17:0458759c40e4 403 DBG("Updating AdvFrame: %d", serviceDataLen);
mbedAustin 22:160766614338 404 // printf("\r\n");
mbedAustin 22:160766614338 405 // for(int x = 0; x<serviceDataLen; x++) {
mbedAustin 22:160766614338 406 // printf("%2.2x:",serviceData[x]);
mbedAustin 22:160766614338 407 // }
mbedAustin 22:160766614338 408 // printf("\r\n");
mbedAustin 16:a7d07ea94b31 409 ble.clearAdvertisingPayload();
mbedAustin 16:a7d07ea94b31 410 ble.accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
mbedAustin 16:a7d07ea94b31 411 ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, BEACON_EDDYSTONE, sizeof(BEACON_EDDYSTONE));
mbedAustin 16:a7d07ea94b31 412 ble.accumulateAdvertisingPayload(GapAdvertisingData::SERVICE_DATA, serviceData, serviceDataLen);
mbedAustin 16:a7d07ea94b31 413
mbedAustin 16:a7d07ea94b31 414 return true;
mbedAustin 16:a7d07ea94b31 415 }
mbedAustin 16:a7d07ea94b31 416
mbedAustin 16:a7d07ea94b31 417 /*
mbedAustin 22:160766614338 418 * State machine for switching out frames.
mbedAustin 22:160766614338 419 * This function is called by the radioNotificationCallback when a frame needs to get swapped out.
mbedAustin 22:160766614338 420 * This function exists because of time constraints in the radioNotificationCallback, so it is effectively
mbedAustin 22:160766614338 421 * broken up into two functions.
mbedAustin 14:5a2a104a21a8 422 */
mbedAustin 22:160766614338 423 void swapOutFrames(void) {
mbedAustin 17:0458759c40e4 424 uint8_t serviceData[SERVICE_DATA_MAX];
mbedAustin 17:0458759c40e4 425 unsigned serviceDataLen = 0;
mbedAustin 17:0458759c40e4 426 //hard code in the eddystone UUID
mbedAustin 17:0458759c40e4 427 serviceData[serviceDataLen++] = BEACON_EDDYSTONE[0];
mbedAustin 17:0458759c40e4 428 serviceData[serviceDataLen++] = BEACON_EDDYSTONE[1];
mbedAustin 22:160766614338 429
mbedAustin 22:160766614338 430 switch(frameIndex) {
mbedAustin 22:160766614338 431 case 0:
mbedAustin 22:160766614338 432 // TLM frame
mbedAustin 22:160766614338 433 serviceDataLen += constructTLMFrame(serviceData+serviceDataLen,20);
mbedAustin 22:160766614338 434 DBG("\t Swapping in TLM Frame: len=%d",serviceDataLen);
mbedAustin 22:160766614338 435 updateAdvPacket(serviceData,serviceDataLen);
mbedAustin 22:160766614338 436 frameIndex++;
mbedAustin 22:160766614338 437 break;
mbedAustin 22:160766614338 438 case 1:
mbedAustin 22:160766614338 439 // URL Frame
mbedAustin 22:160766614338 440 serviceDataLen += constructURLFrame(serviceData+serviceDataLen,20);
mbedAustin 22:160766614338 441 DBG("\t Swapping in URL Frame: len=%d ",serviceDataLen);
mbedAustin 22:160766614338 442 updateAdvPacket(serviceData,serviceDataLen);
mbedAustin 22:160766614338 443 switchFlag = false;
mbedAustin 22:160766614338 444 frameIndex++;
mbedAustin 22:160766614338 445 break;
mbedAustin 22:160766614338 446 case 2:
mbedAustin 22:160766614338 447 // UID Frame
mbedAustin 22:160766614338 448 serviceDataLen += constructUIDFrame(serviceData+serviceDataLen,20);
mbedAustin 22:160766614338 449 DBG("\t Swapping in UID Frame: len=%d",serviceDataLen);
mbedAustin 22:160766614338 450 updateAdvPacket(serviceData,serviceDataLen);
mbedAustin 22:160766614338 451 switchFlag = false;
mbedAustin 22:160766614338 452 frameIndex++;
mbedAustin 22:160766614338 453 break;
mbedAustin 22:160766614338 454 }
mbedAustin 22:160766614338 455 }
mbedAustin 22:160766614338 456
mbedAustin 22:160766614338 457 /*
mbedAustin 22:160766614338 458 * Callback from onRadioNotification(), used to update the PDUCounter and maybe other stuff eventually?
mbedAustin 22:160766614338 459 *
mbedAustin 22:160766614338 460 *
mbedAustin 22:160766614338 461 */
mbedAustin 22:160766614338 462 #define EDDYSTONE_SWAPFRAME_DELAYMS 1
mbedAustin 22:160766614338 463 void radioNotificationCallback(bool radioActive) {
mbedAustin 22:160766614338 464 //DBG("RadioNotificationCallback : %d, %d, %d, %d",radioActive,frameIndex,TlmPduCount,TlmTimeSinceBoot);
mbedAustin 22:160766614338 465 // Update PDUCount
mbedAustin 22:160766614338 466 TlmPduCount++;
mbedAustin 22:160766614338 467 frameIndex = frameIndex % EDDYSTONE_MAX_FRAMETYPE;
mbedAustin 22:160766614338 468
mbedAustin 22:160766614338 469
mbedAustin 22:160766614338 470 // True just before an frame is sent, fale just after a frame is sent
mbedAustin 18:91c36071aafb 471 if(radioActive) {
mbedAustin 16:a7d07ea94b31 472 // Do Nothing
mbedAustin 16:a7d07ea94b31 473 } else {
mbedAustin 16:a7d07ea94b31 474 // state machine to control which packet is being sent
mbedAustin 15:af8c24f34a9f 475 switch(frameIndex) {
mbedAustin 14:5a2a104a21a8 476 case 0:
mbedAustin 22:160766614338 477 switchFrame.attach_us(this, &ZipBeaconConfigService::swapOutFrames, EDDYSTONE_SWAPFRAME_DELAYMS);
mbedAustin 16:a7d07ea94b31 478 switchFlag = true;
mbedAustin 14:5a2a104a21a8 479 break;
mbedAustin 14:5a2a104a21a8 480 case 1:
mbedAustin 16:a7d07ea94b31 481 // switch out packets
mbedAustin 16:a7d07ea94b31 482 if(switchFlag) {
mbedAustin 22:160766614338 483 switchFrame.attach_us(this, &ZipBeaconConfigService::swapOutFrames, EDDYSTONE_SWAPFRAME_DELAYMS);
mbedAustin 16:a7d07ea94b31 484 switchFlag = false;
mbedAustin 16:a7d07ea94b31 485 } else {
mbedAustin 16:a7d07ea94b31 486 if((TlmPduCount % 10) == 0) { // every 10 adv packets switch the frame
mbedAustin 16:a7d07ea94b31 487 switchFlag = true;
mbedAustin 16:a7d07ea94b31 488 }
mbedAustin 16:a7d07ea94b31 489 }
mbedAustin 14:5a2a104a21a8 490 break;
mbedAustin 14:5a2a104a21a8 491 case 2:
mbedAustin 16:a7d07ea94b31 492 // switch out packets
mbedAustin 16:a7d07ea94b31 493 if(switchFlag) {
mbedAustin 22:160766614338 494 switchFrame.attach_us(this, &ZipBeaconConfigService::swapOutFrames, EDDYSTONE_SWAPFRAME_DELAYMS);
mbedAustin 16:a7d07ea94b31 495 switchFlag = false;
mbedAustin 16:a7d07ea94b31 496 } else {
mbedAustin 16:a7d07ea94b31 497 if((TlmPduCount % 10) == 0) { // every 10 adv packets switch the frame
mbedAustin 16:a7d07ea94b31 498 switchFlag = true;
mbedAustin 16:a7d07ea94b31 499 }
mbedAustin 16:a7d07ea94b31 500 }
mbedAustin 14:5a2a104a21a8 501 break;
mbedAustin 15:af8c24f34a9f 502 }
mbedAustin 14:5a2a104a21a8 503 }
mbedAustin 16:a7d07ea94b31 504
mbedAustin 15:af8c24f34a9f 505 return;
mbedAustin 14:5a2a104a21a8 506 }
screamer 0:c04d932e96c9 507
screamer 0:c04d932e96c9 508 /* Helper function to switch to the non-connectible normal mode for ZipBeacon. This gets called after a timeout. */
mbedAustin 15:af8c24f34a9f 509 void setupZipBeaconAdvertisements() {
mbedAustin 16:a7d07ea94b31 510 DBG("Switching Config -> adv");
screamer 0:c04d932e96c9 511 uint8_t serviceData[SERVICE_DATA_MAX];
screamer 0:c04d932e96c9 512 unsigned serviceDataLen = 0;
mbedAustin 16:a7d07ea94b31 513 unsigned beaconPeriod = params.beaconPeriod;
mbedAustin 16:a7d07ea94b31 514 unsigned txPowerMode = params.txPowerMode;
mbedAustin 16:a7d07ea94b31 515 unsigned uriDataLength = params.uriDataLength;
mbedAustin 16:a7d07ea94b31 516
mbedAustin 16:a7d07ea94b31 517 ZipBeaconConfigService::UriData_t &uriData = params.uriData;
mbedAustin 16:a7d07ea94b31 518 ZipBeaconConfigService::PowerLevels_t &advPowerLevels = params.advPowerLevels;
mbedAustin 16:a7d07ea94b31 519
mbedAustin 14:5a2a104a21a8 520 // Initialize Frame transition
mbedAustin 17:0458759c40e4 521 frameIndex = 2;
mbedAustin 15:af8c24f34a9f 522 uidRFU = 0;
mbedAustin 17:0458759c40e4 523 switchFlag = true;
mbedAustin 17:0458759c40e4 524
screamer 0:c04d932e96c9 525
screamer 0:c04d932e96c9 526 /* Reinitialize the BLE stack. This will clear away the existing services and advertising state. */
screamer 0:c04d932e96c9 527 ble.shutdown();
screamer 0:c04d932e96c9 528 ble.init();
screamer 0:c04d932e96c9 529 ble.setTxPower(params.advPowerLevels[params.txPowerMode]);
screamer 0:c04d932e96c9 530 ble.setAdvertisingType(GapAdvertisingParams::ADV_NON_CONNECTABLE_UNDIRECTED);
screamer 0:c04d932e96c9 531 ble.setAdvertisingInterval(beaconPeriod);
mbedAustin 16:a7d07ea94b31 532 extern void saveURIBeaconConfigParams(const Params_t *paramsP); /* forward declaration; necessary to avoid a circular dependency. */
mbedAustin 16:a7d07ea94b31 533 saveURIBeaconConfigParams(&params);
mbedAustin 15:af8c24f34a9f 534
mbedAustin 17:0458759c40e4 535 setTLMFrameData(0x00,0x2222,0x3333,0x01,0x02); // Initialize TLM Data, for testing, remove for release
mbedAustin 19:f7c33fa88ca5 536 updateTlmPduCount(0);
mbedAustin 19:f7c33fa88ca5 537 updateTlmTimeSinceBoot(0);
mbedAustin 15:af8c24f34a9f 538
mbedAustin 15:af8c24f34a9f 539 // Construct TLM Frame in initial advertising.
mbedAustin 14:5a2a104a21a8 540 serviceData[serviceDataLen++] = BEACON_EDDYSTONE[0];
mbedAustin 14:5a2a104a21a8 541 serviceData[serviceDataLen++] = BEACON_EDDYSTONE[1];
mbedAustin 17:0458759c40e4 542 serviceDataLen += constructURLFrame(serviceData+serviceDataLen,SERVICE_DATA_MAX);
mbedAustin 16:a7d07ea94b31 543
mbedAustin 16:a7d07ea94b31 544 updateAdvPacket(serviceData, serviceDataLen);
mbedAustin 17:0458759c40e4 545 ble.gap().startAdvertising();
mbedAustin 22:160766614338 546 ble.gap().onRadioNotification(this,&ZipBeaconConfigService::radioNotificationCallback);
mbedAustin 16:a7d07ea94b31 547 timeSinceBootTick.attach(this,&ZipBeaconConfigService::tsbCallback,0.1); // incriment the TimeSinceBoot ticker every 0.1s
screamer 0:c04d932e96c9 548 }
screamer 0:c04d932e96c9 549
mbedAustin 15:af8c24f34a9f 550 private:
mbedAustin 16:a7d07ea94b31 551 // True if the lock bits are non-zero
screamer 0:c04d932e96c9 552 bool isLocked() {
screamer 0:c04d932e96c9 553 Lock_t testLock;
screamer 0:c04d932e96c9 554 memset(testLock, 0, sizeof(Lock_t));
screamer 0:c04d932e96c9 555 return memcmp(params.lock, testLock, sizeof(Lock_t));
screamer 0:c04d932e96c9 556 }
screamer 0:c04d932e96c9 557
screamer 0:c04d932e96c9 558 /*
screamer 0:c04d932e96c9 559 * This callback is invoked when a GATT client attempts to modify any of the
screamer 0:c04d932e96c9 560 * characteristics of this service. Attempts to do so are also applied to
screamer 0:c04d932e96c9 561 * the internal state of this service object.
screamer 0:c04d932e96c9 562 */
rgrover1 7:e9800c45e065 563 void onDataWrittenCallback(const GattWriteCallbackParams *writeParams) {
rgrover1 7:e9800c45e065 564 uint16_t handle = writeParams->handle;
screamer 0:c04d932e96c9 565
screamer 0:c04d932e96c9 566 if (handle == lockChar.getValueHandle()) {
screamer 0:c04d932e96c9 567 // Validated earlier
screamer 0:c04d932e96c9 568 memcpy(params.lock, writeParams->data, sizeof(Lock_t));
screamer 0:c04d932e96c9 569 // use isLocked() in case bits are being set to all 0's
screamer 0:c04d932e96c9 570 lockedState = isLocked();
screamer 0:c04d932e96c9 571 } else if (handle == unlockChar.getValueHandle()) {
screamer 0:c04d932e96c9 572 // Validated earlier
screamer 0:c04d932e96c9 573 memset(params.lock, 0, sizeof(Lock_t));
screamer 0:c04d932e96c9 574 lockedState = false;
screamer 0:c04d932e96c9 575 } else if (handle == uriDataChar.getValueHandle()) {
screamer 0:c04d932e96c9 576 params.uriDataLength = writeParams->len;
screamer 0:c04d932e96c9 577 memcpy(params.uriData, writeParams->data, params.uriDataLength);
screamer 0:c04d932e96c9 578 } else if (handle == flagsChar.getValueHandle()) {
screamer 0:c04d932e96c9 579 params.flags = *(writeParams->data);
screamer 0:c04d932e96c9 580 } else if (handle == advPowerLevelsChar.getValueHandle()) {
screamer 0:c04d932e96c9 581 memcpy(params.advPowerLevels, writeParams->data, sizeof(PowerLevels_t));
screamer 0:c04d932e96c9 582 } else if (handle == txPowerModeChar.getValueHandle()) {
screamer 0:c04d932e96c9 583 params.txPowerMode = *(writeParams->data);
screamer 0:c04d932e96c9 584 } else if (handle == beaconPeriodChar.getValueHandle()) {
screamer 0:c04d932e96c9 585 params.beaconPeriod = *((uint16_t *)(writeParams->data));
rgrover1 4:4440953bde10 586
rgrover1 4:4440953bde10 587 /* Re-map beaconPeriod to within permissible bounds if necessary. */
rgrover1 4:4440953bde10 588 if (params.beaconPeriod != 0) {
rgrover1 4:4440953bde10 589 bool paramsUpdated = false;
rgrover1 4:4440953bde10 590 if (params.beaconPeriod < ble.getMinAdvertisingInterval()) {
rgrover1 4:4440953bde10 591 params.beaconPeriod = ble.getMinAdvertisingInterval();
rgrover1 4:4440953bde10 592 paramsUpdated = true;
rgrover1 4:4440953bde10 593 } else if (params.beaconPeriod > ble.getMaxAdvertisingInterval()) {
rgrover1 4:4440953bde10 594 params.beaconPeriod = ble.getMaxAdvertisingInterval();
rgrover1 4:4440953bde10 595 paramsUpdated = true;
rgrover1 4:4440953bde10 596 }
rgrover1 4:4440953bde10 597 if (paramsUpdated) {
rgrover1 4:4440953bde10 598 ble.updateCharacteristicValue(beaconPeriodChar.getValueHandle(), reinterpret_cast<uint8_t *>(&params.beaconPeriod), sizeof(uint16_t));
rgrover1 4:4440953bde10 599 }
rgrover1 4:4440953bde10 600 }
screamer 0:c04d932e96c9 601 } else if (handle == resetChar.getValueHandle()) {
screamer 0:c04d932e96c9 602 resetToDefaults();
screamer 0:c04d932e96c9 603 }
screamer 0:c04d932e96c9 604 }
screamer 0:c04d932e96c9 605
screamer 0:c04d932e96c9 606 /*
screamer 0:c04d932e96c9 607 * Reset the default values.
screamer 0:c04d932e96c9 608 */
screamer 0:c04d932e96c9 609 void resetToDefaults(void) {
screamer 0:c04d932e96c9 610 lockedState = false;
screamer 0:c04d932e96c9 611 memset(params.lock, 0, sizeof(Lock_t));
screamer 0:c04d932e96c9 612 memcpy(params.uriData, defaultUriData, URI_DATA_MAX);
screamer 0:c04d932e96c9 613 params.uriDataLength = defaultUriDataLength;
screamer 0:c04d932e96c9 614 params.flags = 0;
screamer 0:c04d932e96c9 615 memcpy(params.advPowerLevels, defaultAdvPowerLevels, sizeof(PowerLevels_t));
screamer 0:c04d932e96c9 616 params.txPowerMode = TX_POWER_MODE_LOW;
screamer 0:c04d932e96c9 617 params.beaconPeriod = 1000;
mbedAustin 10:b5d19bcf23cf 618 memcpy(params.uidNamespaceID, defaultUidNamespaceID, UID_NAMESPACEID_SIZE);
mbedAustin 10:b5d19bcf23cf 619 memcpy(params.uidInstanceID, defaultUidInstanceID, UID_INSTANCEID_SIZE);
mbedAustin 12:ced5e837c511 620 params.tlmVersion = 0;
screamer 0:c04d932e96c9 621 updateCharacteristicValues();
screamer 0:c04d932e96c9 622 }
screamer 0:c04d932e96c9 623
screamer 0:c04d932e96c9 624 /*
screamer 0:c04d932e96c9 625 * Internal helper function used to update the GATT database following any
screamer 0:c04d932e96c9 626 * change to the internal state of the service object.
screamer 0:c04d932e96c9 627 */
screamer 0:c04d932e96c9 628 void updateCharacteristicValues(void) {
screamer 0:c04d932e96c9 629 ble.updateCharacteristicValue(lockedStateChar.getValueHandle(), &lockedState, 1);
screamer 0:c04d932e96c9 630 ble.updateCharacteristicValue(uriDataChar.getValueHandle(), params.uriData, params.uriDataLength);
screamer 0:c04d932e96c9 631 ble.updateCharacteristicValue(flagsChar.getValueHandle(), &params.flags, 1);
screamer 0:c04d932e96c9 632 ble.updateCharacteristicValue(beaconPeriodChar.getValueHandle(),
screamer 0:c04d932e96c9 633 reinterpret_cast<uint8_t *>(&params.beaconPeriod), sizeof(uint16_t));
screamer 0:c04d932e96c9 634 ble.updateCharacteristicValue(txPowerModeChar.getValueHandle(), &params.txPowerMode, 1);
screamer 0:c04d932e96c9 635 ble.updateCharacteristicValue(advPowerLevelsChar.getValueHandle(),
screamer 0:c04d932e96c9 636 reinterpret_cast<uint8_t *>(params.advPowerLevels), sizeof(PowerLevels_t));
screamer 0:c04d932e96c9 637 }
screamer 0:c04d932e96c9 638
mbedAustin 15:af8c24f34a9f 639 private:
rgrover1 7:e9800c45e065 640 void lockAuthorizationCallback(GattWriteAuthCallbackParams *authParams) {
screamer 0:c04d932e96c9 641 if (lockedState) {
screamer 0:c04d932e96c9 642 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION;
screamer 0:c04d932e96c9 643 } else if (authParams->len != sizeof(Lock_t)) {
screamer 0:c04d932e96c9 644 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH;
screamer 0:c04d932e96c9 645 } else if (authParams->offset != 0) {
screamer 0:c04d932e96c9 646 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET;
screamer 0:c04d932e96c9 647 } else {
screamer 0:c04d932e96c9 648 authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
screamer 0:c04d932e96c9 649 }
screamer 0:c04d932e96c9 650 }
screamer 0:c04d932e96c9 651
screamer 0:c04d932e96c9 652
rgrover1 7:e9800c45e065 653 void unlockAuthorizationCallback(GattWriteAuthCallbackParams *authParams) {
screamer 0:c04d932e96c9 654 if (!lockedState) {
screamer 0:c04d932e96c9 655 authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
screamer 0:c04d932e96c9 656 } else if (authParams->len != sizeof(Lock_t)) {
screamer 0:c04d932e96c9 657 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH;
screamer 0:c04d932e96c9 658 } else if (authParams->offset != 0) {
screamer 0:c04d932e96c9 659 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET;
screamer 0:c04d932e96c9 660 } else if (memcmp(authParams->data, params.lock, sizeof(Lock_t)) != 0) {
screamer 0:c04d932e96c9 661 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION;
screamer 0:c04d932e96c9 662 } else {
screamer 0:c04d932e96c9 663 authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
screamer 0:c04d932e96c9 664 }
screamer 0:c04d932e96c9 665 }
screamer 0:c04d932e96c9 666
rgrover1 7:e9800c45e065 667 void uriDataWriteAuthorizationCallback(GattWriteAuthCallbackParams *authParams) {
screamer 0:c04d932e96c9 668 if (lockedState) {
screamer 0:c04d932e96c9 669 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION;
screamer 0:c04d932e96c9 670 } else if (authParams->offset != 0) {
screamer 0:c04d932e96c9 671 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET;
screamer 0:c04d932e96c9 672 } else {
screamer 0:c04d932e96c9 673 authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
screamer 0:c04d932e96c9 674 }
screamer 0:c04d932e96c9 675 }
screamer 0:c04d932e96c9 676
rgrover1 7:e9800c45e065 677 void powerModeAuthorizationCallback(GattWriteAuthCallbackParams *authParams) {
screamer 0:c04d932e96c9 678 if (lockedState) {
screamer 0:c04d932e96c9 679 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION;
screamer 0:c04d932e96c9 680 } else if (authParams->len != sizeof(uint8_t)) {
screamer 0:c04d932e96c9 681 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH;
screamer 0:c04d932e96c9 682 } else if (authParams->offset != 0) {
screamer 0:c04d932e96c9 683 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET;
screamer 0:c04d932e96c9 684 } else if (*((uint8_t *)authParams->data) >= NUM_POWER_MODES) {
screamer 0:c04d932e96c9 685 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_WRITE_NOT_PERMITTED;
screamer 0:c04d932e96c9 686 } else {
screamer 0:c04d932e96c9 687 authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
screamer 0:c04d932e96c9 688 }
screamer 0:c04d932e96c9 689 }
screamer 0:c04d932e96c9 690
screamer 0:c04d932e96c9 691 template <typename T>
rgrover1 7:e9800c45e065 692 void basicAuthorizationCallback(GattWriteAuthCallbackParams *authParams) {
screamer 0:c04d932e96c9 693 if (lockedState) {
screamer 0:c04d932e96c9 694 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION;
screamer 0:c04d932e96c9 695 } else if (authParams->len != sizeof(T)) {
screamer 0:c04d932e96c9 696 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH;
screamer 0:c04d932e96c9 697 } else if (authParams->offset != 0) {
screamer 0:c04d932e96c9 698 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET;
screamer 0:c04d932e96c9 699 } else {
screamer 0:c04d932e96c9 700 authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS;
screamer 0:c04d932e96c9 701 }
screamer 0:c04d932e96c9 702 }
screamer 0:c04d932e96c9 703
mbedAustin 10:b5d19bcf23cf 704 BLEDevice &ble;
mbedAustin 10:b5d19bcf23cf 705 Params_t &params;
mbedAustin 22:160766614338 706 Ticker callbackTick;
mbedAustin 22:160766614338 707 Ticker timeSinceBootTick;
mbedAustin 22:160766614338 708 Timeout switchFrame;
mbedAustin 16:a7d07ea94b31 709 // Default value that is restored on reset
mbedAustin 10:b5d19bcf23cf 710 size_t defaultUriDataLength;
mbedAustin 10:b5d19bcf23cf 711 UriData_t defaultUriData;
mbedAustin 10:b5d19bcf23cf 712 UIDNamespaceID_t defaultUidNamespaceID;
mbedAustin 10:b5d19bcf23cf 713 UIDInstanceID_t defaultUidInstanceID;
mbedAustin 11:73ea4ef7f5a4 714 int8_t defaultUidPower;
mbedAustin 11:73ea4ef7f5a4 715 int8_t defaultUrlPower;
mbedAustin 10:b5d19bcf23cf 716 uint16_t uidRFU;
mbedAustin 16:a7d07ea94b31 717 // Default value that is restored on reset
mbedAustin 10:b5d19bcf23cf 718 PowerLevels_t &defaultAdvPowerLevels;
mbedAustin 10:b5d19bcf23cf 719 uint8_t lockedState;
mbedAustin 10:b5d19bcf23cf 720 bool initSucceeded;
mbedAustin 10:b5d19bcf23cf 721 uint8_t resetFlag;
mbedAustin 17:0458759c40e4 722 bool switchFlag;
mbedAustin 15:af8c24f34a9f 723
mbedAustin 16:a7d07ea94b31 724 // Private Variables for Telemetry Data
mbedAustin 13:91c356fa928e 725 uint8_t TlmVersion;
mbedAustin 13:91c356fa928e 726 volatile uint16_t TlmBatteryVoltage;
mbedAustin 13:91c356fa928e 727 volatile uint16_t TlmBeaconTemp;
mbedAustin 13:91c356fa928e 728 volatile uint32_t TlmPduCount;
mbedAustin 13:91c356fa928e 729 volatile uint32_t TlmTimeSinceBoot;
screamer 0:c04d932e96c9 730
screamer 0:c04d932e96c9 731 ReadOnlyGattCharacteristic<uint8_t> lockedStateChar;
screamer 0:c04d932e96c9 732 WriteOnlyGattCharacteristic<Lock_t> lockChar;
screamer 0:c04d932e96c9 733 GattCharacteristic uriDataChar;
screamer 0:c04d932e96c9 734 WriteOnlyGattCharacteristic<Lock_t> unlockChar;
screamer 0:c04d932e96c9 735 ReadWriteGattCharacteristic<uint8_t> flagsChar;
screamer 0:c04d932e96c9 736 ReadWriteGattCharacteristic<PowerLevels_t> advPowerLevelsChar;
screamer 0:c04d932e96c9 737 ReadWriteGattCharacteristic<uint8_t> txPowerModeChar;
screamer 0:c04d932e96c9 738 ReadWriteGattCharacteristic<uint16_t> beaconPeriodChar;
screamer 0:c04d932e96c9 739 WriteOnlyGattCharacteristic<uint8_t> resetChar;
screamer 0:c04d932e96c9 740
mbedAustin 15:af8c24f34a9f 741 public:
screamer 0:c04d932e96c9 742 /*
screamer 0:c04d932e96c9 743 * Encode a human-readable URI into the binary format defined by URIBeacon spec (https://github.com/google/uribeacon/tree/master/specification).
screamer 0:c04d932e96c9 744 */
screamer 0:c04d932e96c9 745 static void encodeURI(const char *uriDataIn, UriData_t uriDataOut, size_t &sizeofURIDataOut) {
screamer 0:c04d932e96c9 746 const char *prefixes[] = {
screamer 0:c04d932e96c9 747 "http://www.",
screamer 0:c04d932e96c9 748 "https://www.",
screamer 0:c04d932e96c9 749 "http://",
screamer 0:c04d932e96c9 750 "https://",
screamer 0:c04d932e96c9 751 };
screamer 0:c04d932e96c9 752 const size_t NUM_PREFIXES = sizeof(prefixes) / sizeof(char *);
screamer 0:c04d932e96c9 753 const char *suffixes[] = {
screamer 0:c04d932e96c9 754 ".com/",
screamer 0:c04d932e96c9 755 ".org/",
screamer 0:c04d932e96c9 756 ".edu/",
screamer 0:c04d932e96c9 757 ".net/",
screamer 0:c04d932e96c9 758 ".info/",
screamer 0:c04d932e96c9 759 ".biz/",
screamer 0:c04d932e96c9 760 ".gov/",
screamer 0:c04d932e96c9 761 ".com",
screamer 0:c04d932e96c9 762 ".org",
screamer 0:c04d932e96c9 763 ".edu",
screamer 0:c04d932e96c9 764 ".net",
screamer 0:c04d932e96c9 765 ".info",
screamer 0:c04d932e96c9 766 ".biz",
screamer 0:c04d932e96c9 767 ".gov"
screamer 0:c04d932e96c9 768 };
screamer 0:c04d932e96c9 769 const size_t NUM_SUFFIXES = sizeof(suffixes) / sizeof(char *);
screamer 0:c04d932e96c9 770
screamer 0:c04d932e96c9 771 sizeofURIDataOut = 0;
screamer 0:c04d932e96c9 772 memset(uriDataOut, 0, sizeof(UriData_t));
screamer 0:c04d932e96c9 773
screamer 0:c04d932e96c9 774 if ((uriDataIn == NULL) || (strlen(uriDataIn) == 0)) {
screamer 0:c04d932e96c9 775 return;
screamer 0:c04d932e96c9 776 }
screamer 0:c04d932e96c9 777
screamer 0:c04d932e96c9 778 /*
screamer 0:c04d932e96c9 779 * handle prefix
screamer 0:c04d932e96c9 780 */
screamer 0:c04d932e96c9 781 for (unsigned i = 0; i < NUM_PREFIXES; i++) {
screamer 0:c04d932e96c9 782 size_t prefixLen = strlen(prefixes[i]);
screamer 0:c04d932e96c9 783 if (strncmp(uriDataIn, prefixes[i], prefixLen) == 0) {
screamer 0:c04d932e96c9 784 uriDataOut[sizeofURIDataOut++] = i;
screamer 0:c04d932e96c9 785 uriDataIn += prefixLen;
screamer 0:c04d932e96c9 786 break;
screamer 0:c04d932e96c9 787 }
screamer 0:c04d932e96c9 788 }
screamer 0:c04d932e96c9 789
screamer 0:c04d932e96c9 790 /*
screamer 0:c04d932e96c9 791 * handle suffixes
screamer 0:c04d932e96c9 792 */
screamer 0:c04d932e96c9 793 while (*uriDataIn && (sizeofURIDataOut < URI_DATA_MAX)) {
screamer 0:c04d932e96c9 794 /* check for suffix match */
screamer 0:c04d932e96c9 795 unsigned i;
screamer 0:c04d932e96c9 796 for (i = 0; i < NUM_SUFFIXES; i++) {
screamer 0:c04d932e96c9 797 size_t suffixLen = strlen(suffixes[i]);
screamer 0:c04d932e96c9 798 if (strncmp(uriDataIn, suffixes[i], suffixLen) == 0) {
screamer 0:c04d932e96c9 799 uriDataOut[sizeofURIDataOut++] = i;
screamer 0:c04d932e96c9 800 uriDataIn += suffixLen;
screamer 0:c04d932e96c9 801 break; /* from the for loop for checking against suffixes */
screamer 0:c04d932e96c9 802 }
screamer 0:c04d932e96c9 803 }
screamer 0:c04d932e96c9 804 /* This is the default case where we've got an ordinary character which doesn't match a suffix. */
screamer 0:c04d932e96c9 805 if (i == NUM_SUFFIXES) {
screamer 0:c04d932e96c9 806 uriDataOut[sizeofURIDataOut++] = *uriDataIn;
screamer 0:c04d932e96c9 807 ++uriDataIn;
screamer 0:c04d932e96c9 808 }
screamer 0:c04d932e96c9 809 }
screamer 0:c04d932e96c9 810 }
screamer 0:c04d932e96c9 811 };
screamer 0:c04d932e96c9 812
screamer 0:c04d932e96c9 813 #endif // SERVICES_ZIPBEACONCONFIGSERVICE_H_