The Uniform Resource Identifier Beacon (UriBeacon) defines Bluetooth 4.0 Advertisement Data that contain Web URIs. URIBeacon provides a way for Bluetooth Low Energy devices to discover nearby URIs, for example, provides a way for a user to discover a short URL and then download it on their smartphone.

Dependencies:   BLE_API mbed nRF51822

Fork of BLE_PhysicalWeb by Bluetooth Low Energy

This example demonstrates how to set up and initialize a basic URI Beacon. For a more advanced example of using a URI beacon please see the BLE_PhysicalWeb project. The Google Github Page also gives a great description of what UriBeacons are and how to use them.

Basic Details

URI Beacons are a standard way of providing a URI link in a BLE advertising packet. Website URL's are the most popular URI's. The goal of URI Beacons are to act as a bridge between the physical and digital worlds. Since the URI can be any web capable link the possibilities for use are really endless. The UriBeacons can be thought of as a natural extension of and more useful version of QR codes.

Smartphone App Links

iPhone Physical Web app

Android App

Walkthrough of Physical Web application

Size of URI

The UriBeacon has one purpose, to advertise a web link. Because of the nature of BLE these web links have to be small. In order to provide a nice balance of small and useful the UriBeacon specification has abstracted out the representation of the URI prefix('http://www.', 'https://www.' , ...etc) and suffix ('.com','.org','.edu','.gov' ...etc) to a single byte each. Of the 27 bytes available for a usual BLE payload the UriBeacon has 19 bytes available for the URI. Of these 19bytes one byte must be given to the prefix. That leaves 18 bytes to fit the address and the suffix into. If the suffix used is not one of the standard ones in the UriBeacon specification then each letter will take up 1 byte instead of the entire suffix being abbreviated into a single byte.

Here are the acceptable abbreviations currently available

PreFixSuffix
http://www..com/
https://www..org/
http://.edu/
https://.net/
urn:uuid:.info/
.biz/
.gov/
.com
.org
.edu
.net
.info
.biz
.gov

So for example the address "http://www.google.com" would take up 8 bytes. Both "http://www." and ".com" are supported abbreviations so each will be shortened to 1 byte.

Number of BytesData
1"http://www." abbreviated
6"google", 1 byte for each letter
1".com/" abbreviated

An address like "http://www.bit.ly/xyz" would take up 11 bytes. Notice that ".ly" is not a supported suffix, so each letter takes up 1 byte.

Number of BytesData
1"http://www." abbreviated
10"bit.ly/xyz"

Even addresses with the suffix in the middle are abbreviated, ie "http://www.youtube.com/XYZ". Notice here that the '.com' is in the middle of an address, it is still shortened to just 1 byte.

Number of BytesData
1"http://www." abbreviated
7"youtube"
1".com/" abbreviated
4"XYZ"

Using UriBeacons with mbed BLE API

Using the UriBeacon with the mbed API is rather simple. Just like any other BLE project you must first initialize the BLE baselayer by creating a ble object.

Initialize_bl_object

BLEDevice ble;

Then you pass the BLE object to a UriBeacon config service.

Configure_UriBeacon_Service

URIBeaconConfigService *uriBeaconConfig;
uriBeaconConfig = new URIBeaconConfigService(ble, "http://www.mbed.org");

Optionally you can then adjust settings of the UriBeacon such as transmission power levels, grabbing verbose debug information and other handy dandy services.

Optional_UriBeacon_configuration

/* Adjust the TX Power Level */
    const int8_t powerLevels[] = {-20, -4, 0, 10};
    uriBeaconConfig->setTxPowerLevels(powerLevels); // Set TX power levels, Lowest(-20), Low(-4), Medium(0), High(10)
    uriBeaconConfig->setTxPowerMode(URIBeaconConfigService::TX_POWER_MODE_LOW); // Set transmission in Low power mode

/* Adjust Beacon Period*/
setBeaconPeriod(1000); // Set beacon to advertise every 1000ms

Other UriBeacon Services

This is just the bare basics of how URI beacons work. There is also a configuration service that allows URI beacons to be updated, locked, and provides other management feature. That is beyond the scope of this example but details can be found in the Technical Details section below.

Technical Details

For more details on how URI beacons work please see these websites:
UriBeacon Github Project : the github home for all things UriBeacon (maintained by google)
UriBeacon Specification : lots of good technical details
UriBeacon configuration service - This is a service that pairs with the UriBeacon that allows changing the URI's, locking them, and some other cool features. This service is not detailed in this example application.
Android App : smartphone application to view nearby UriBeacons.
iOS App : Sample code for using UriBeacons with iOS.
The PhysicalWeb Project : a project that the UriBeacon is central to.

In case you're really interested here is a diagram that nicely sums up how the 27bytes of advertising data payload are used. https://github.com/google/uribeacon/raw/master/specification/uribeacon-figure.png

Committer:
rgrover1
Date:
Fri Feb 13 13:05:49 2015 +0000
Revision:
13:b82d8db73633
Parent:
11:c77cc2b74101
Child:
14:868a1207022d
Updating to Bill's version of the URIBeacon app

Who changed what in which revision?

UserRevisionLine numberNew contents of line
rgrover1 13:b82d8db73633 1 /*
rgrover1 13:b82d8db73633 2 * Copyright 2014-2015 Google Inc. All rights reserved.
rgrover1 0:790a27ffc99b 3 *
rgrover1 0:790a27ffc99b 4 * Licensed under the Apache License, Version 2.0 (the "License");
rgrover1 0:790a27ffc99b 5 * you may not use this file except in compliance with the License.
rgrover1 0:790a27ffc99b 6 * You may obtain a copy of the License at
rgrover1 0:790a27ffc99b 7 *
rgrover1 13:b82d8db73633 8 * http://www.apache.org/licenses/LICENSE-2.0
rgrover1 0:790a27ffc99b 9 *
rgrover1 0:790a27ffc99b 10 * Unless required by applicable law or agreed to in writing, software
rgrover1 0:790a27ffc99b 11 * distributed under the License is distributed on an "AS IS" BASIS,
rgrover1 0:790a27ffc99b 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
rgrover1 0:790a27ffc99b 13 * See the License for the specific language governing permissions and
rgrover1 0:790a27ffc99b 14 * limitations under the License.
rgrover1 0:790a27ffc99b 15 */
rgrover1 0:790a27ffc99b 16
rgrover1 13:b82d8db73633 17 #include <stdint.h>
rgrover1 13:b82d8db73633 18 #include <stddef.h>
rgrover1 13:b82d8db73633 19 #include <nrf_error.h>
rgrover1 0:790a27ffc99b 20 #include "mbed.h"
rgrover1 0:790a27ffc99b 21 #include "BLEDevice.h"
rgrover1 13:b82d8db73633 22 #include "URIBeaconConfigService.h"
rgrover1 11:c77cc2b74101 23 #include "DFUService.h"
rgrover1 13:b82d8db73633 24 #include "pstorage.h"
rgrover1 11:c77cc2b74101 25 #include "DeviceInformationService.h"
rgrover1 0:790a27ffc99b 26
rgrover1 13:b82d8db73633 27 // Struct to hold persistent data across power cycles
rgrover1 13:b82d8db73633 28 struct PersistentData_t {
rgrover1 13:b82d8db73633 29 uint32_t magic;
rgrover1 13:b82d8db73633 30 URIBeaconConfigService::Params_t params;
rgrover1 13:b82d8db73633 31 uint8_t pad[4];
rgrover1 13:b82d8db73633 32 } __attribute__ ((aligned (4)));
rgrover1 13:b82d8db73633 33
rgrover1 13:b82d8db73633 34 static const int PERSISTENT_DATA_ALIGNED_SIZE = sizeof(PersistentData_t);
rgrover1 13:b82d8db73633 35 // Seconds after power-on that config service is available.
rgrover1 13:b82d8db73633 36 static const int ADVERTISING_TIMEOUT_SECONDS = 60;
rgrover1 13:b82d8db73633 37 // Advertising interval for config service.
rgrover1 13:b82d8db73633 38 static const int ADVERTISING_INTERVAL_MSEC = 1000;
rgrover1 13:b82d8db73633 39 // Maximum size of service data in ADV packets
rgrover1 13:b82d8db73633 40 static const int SERVICE_DATA_MAX = 31;
rgrover1 13:b82d8db73633 41 // Magic that identifies persistent storage
rgrover1 13:b82d8db73633 42 static const uint32_t MAGIC = 0x1BEAC000;
rgrover1 13:b82d8db73633 43 // Values for ADV packets related to firmware levels
rgrover1 13:b82d8db73633 44 static URIBeaconConfigService::PowerLevels_t defaultAdvPowerLevels = {-20, -4, 0, 10};
rgrover1 13:b82d8db73633 45 // Values for setTxPower() indexed by power mode.
rgrover1 13:b82d8db73633 46 static const int8_t firmwarePowerLevels[] = {-20, -4, 0, 10};
rgrover1 13:b82d8db73633 47
rgrover1 0:790a27ffc99b 48 BLEDevice ble;
rgrover1 13:b82d8db73633 49 URIBeaconConfigService *uriBeaconConfig;
rgrover1 13:b82d8db73633 50 pstorage_handle_t pstorageHandle;
rgrover1 13:b82d8db73633 51 PersistentData_t persistentData;
rgrover1 13:b82d8db73633 52
rgrover1 13:b82d8db73633 53 /* LEDs for indication */
rgrover1 13:b82d8db73633 54 DigitalOut connectionStateLed(LED1);
rgrover1 13:b82d8db73633 55 DigitalOut advertisingStateLed(LED2);
rgrover1 13:b82d8db73633 56
rgrover1 13:b82d8db73633 57 /* Dummy callback handler needed by Nordic's pstorage module. */
rgrover1 13:b82d8db73633 58 void pstorageNotificationCallback(pstorage_handle_t *p_handle,
rgrover1 13:b82d8db73633 59 uint8_t op_code,
rgrover1 13:b82d8db73633 60 uint32_t result,
rgrover1 13:b82d8db73633 61 uint8_t * p_data,
rgrover1 13:b82d8db73633 62 uint32_t data_len) {
rgrover1 13:b82d8db73633 63 /* APP_ERROR_CHECK(result); */
rgrover1 13:b82d8db73633 64 }
rgrover1 13:b82d8db73633 65
rgrover1 13:b82d8db73633 66 void pstorageLoad() {
rgrover1 13:b82d8db73633 67 pstorage_init();
rgrover1 13:b82d8db73633 68 pstorage_module_param_t pstorageParams = {
rgrover1 13:b82d8db73633 69 .cb = pstorageNotificationCallback,
rgrover1 13:b82d8db73633 70 .block_size = PERSISTENT_DATA_ALIGNED_SIZE,
rgrover1 13:b82d8db73633 71 .block_count = 1
rgrover1 13:b82d8db73633 72 };
rgrover1 13:b82d8db73633 73 pstorage_register(&pstorageParams, &pstorageHandle);
rgrover1 13:b82d8db73633 74 if (pstorage_load(reinterpret_cast<uint8_t *>(&persistentData),
rgrover1 13:b82d8db73633 75 &pstorageHandle, PERSISTENT_DATA_ALIGNED_SIZE, 0) != NRF_SUCCESS) {
rgrover1 13:b82d8db73633 76 // On failure zero out and let the service reset to defaults
rgrover1 13:b82d8db73633 77 memset(&persistentData, 0, sizeof(PersistentData_t));
rgrover1 13:b82d8db73633 78 }
rgrover1 13:b82d8db73633 79 }
rgrover1 13:b82d8db73633 80
rgrover1 13:b82d8db73633 81
rgrover1 13:b82d8db73633 82 void pstorageSave() {
rgrover1 13:b82d8db73633 83 if (persistentData.magic != MAGIC) {
rgrover1 13:b82d8db73633 84 persistentData.magic = MAGIC;
rgrover1 13:b82d8db73633 85 pstorage_store(&pstorageHandle,
rgrover1 13:b82d8db73633 86 reinterpret_cast<uint8_t *>(&persistentData),
rgrover1 13:b82d8db73633 87 sizeof(PersistentData_t),
rgrover1 13:b82d8db73633 88 0 /* offset */);
rgrover1 13:b82d8db73633 89 } else {
rgrover1 13:b82d8db73633 90 pstorage_update(&pstorageHandle,
rgrover1 13:b82d8db73633 91 reinterpret_cast<uint8_t *>(&persistentData),
rgrover1 13:b82d8db73633 92 sizeof(PersistentData_t),
rgrover1 13:b82d8db73633 93 0 /* offset */);
rgrover1 13:b82d8db73633 94 }
rgrover1 13:b82d8db73633 95 }
rgrover1 13:b82d8db73633 96
rgrover1 13:b82d8db73633 97 void startAdvertisingUriBeaconConfig() {
rgrover1 13:b82d8db73633 98 char DEVICE_NAME[] = "mUriBeacon Config";
rgrover1 13:b82d8db73633 99
rgrover1 13:b82d8db73633 100 ble.clearAdvertisingPayload();
rgrover1 13:b82d8db73633 101
rgrover1 13:b82d8db73633 102 // Stops advertising the UriBeacon Config Service after a delay
rgrover1 13:b82d8db73633 103 ble.setAdvertisingTimeout(ADVERTISING_TIMEOUT_SECONDS);
rgrover1 13:b82d8db73633 104
rgrover1 13:b82d8db73633 105 ble.accumulateAdvertisingPayload(
rgrover1 13:b82d8db73633 106 GapAdvertisingData::BREDR_NOT_SUPPORTED |
rgrover1 13:b82d8db73633 107 GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
rgrover1 13:b82d8db73633 108
rgrover1 13:b82d8db73633 109 // UUID is in different order in the ADV frame (!)
rgrover1 13:b82d8db73633 110 uint8_t reversedServiceUUID[sizeof(UUID_URI_BEACON_SERVICE)];
rgrover1 13:b82d8db73633 111 for (unsigned int i = 0; i < sizeof(UUID_URI_BEACON_SERVICE); i++) {
rgrover1 13:b82d8db73633 112 reversedServiceUUID[i] =
rgrover1 13:b82d8db73633 113 UUID_URI_BEACON_SERVICE[sizeof(UUID_URI_BEACON_SERVICE) - i - 1];
rgrover1 13:b82d8db73633 114 }
rgrover1 13:b82d8db73633 115 ble.accumulateAdvertisingPayload(
rgrover1 13:b82d8db73633 116 GapAdvertisingData::COMPLETE_LIST_128BIT_SERVICE_IDS,
rgrover1 13:b82d8db73633 117 reversedServiceUUID,
rgrover1 13:b82d8db73633 118 sizeof(reversedServiceUUID));
rgrover1 0:790a27ffc99b 119
rgrover1 13:b82d8db73633 120 ble.accumulateAdvertisingPayload(GapAdvertisingData::GENERIC_TAG);
rgrover1 13:b82d8db73633 121 ble.accumulateScanResponse(
rgrover1 13:b82d8db73633 122 GapAdvertisingData::COMPLETE_LOCAL_NAME,
rgrover1 13:b82d8db73633 123 reinterpret_cast<uint8_t *>(&DEVICE_NAME),
rgrover1 13:b82d8db73633 124 sizeof(DEVICE_NAME));
rgrover1 13:b82d8db73633 125 ble.accumulateScanResponse(
rgrover1 13:b82d8db73633 126 GapAdvertisingData::TX_POWER_LEVEL,
rgrover1 13:b82d8db73633 127 reinterpret_cast<uint8_t *>(
rgrover1 13:b82d8db73633 128 &defaultAdvPowerLevels[URIBeaconConfigService::TX_POWER_MODE_LOW]),
rgrover1 13:b82d8db73633 129 sizeof(uint8_t));
rgrover1 13:b82d8db73633 130
rgrover1 13:b82d8db73633 131 ble.setTxPower(
rgrover1 13:b82d8db73633 132 firmwarePowerLevels[URIBeaconConfigService::TX_POWER_MODE_LOW]);
rgrover1 13:b82d8db73633 133
rgrover1 13:b82d8db73633 134 ble.setDeviceName(reinterpret_cast<uint8_t *>(&DEVICE_NAME));
rgrover1 13:b82d8db73633 135 ble.setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
rgrover1 13:b82d8db73633 136 ble.setAdvertisingInterval(
rgrover1 13:b82d8db73633 137 Gap::MSEC_TO_ADVERTISEMENT_DURATION_UNITS(ADVERTISING_INTERVAL_MSEC));
rgrover1 6:31b65d4ea67d 138 ble.startAdvertising();
rgrover1 6:31b65d4ea67d 139 }
rgrover1 0:790a27ffc99b 140
rgrover1 13:b82d8db73633 141 void startAdvertisingUriBeacon() {
rgrover1 13:b82d8db73633 142 uint8_t serviceData[SERVICE_DATA_MAX];
rgrover1 13:b82d8db73633 143 int serviceDataLen = 0;
rgrover1 13:b82d8db73633 144
rgrover1 13:b82d8db73633 145 advertisingStateLed = 1;
rgrover1 13:b82d8db73633 146 connectionStateLed = 1;
rgrover1 13:b82d8db73633 147
rgrover1 13:b82d8db73633 148 ble.shutdown();
rgrover1 13:b82d8db73633 149 ble.init();
rgrover1 13:b82d8db73633 150
rgrover1 13:b82d8db73633 151 // Fields from the Service
rgrover1 13:b82d8db73633 152 int beaconPeriod = persistentData.params.beaconPeriod;
rgrover1 13:b82d8db73633 153 int txPowerMode = persistentData.params.txPowerMode;
rgrover1 13:b82d8db73633 154 int uriDataLength = persistentData.params.uriDataLength;
rgrover1 13:b82d8db73633 155 URIBeaconConfigService::UriData_t &uriData = persistentData.params.uriData;
rgrover1 13:b82d8db73633 156 URIBeaconConfigService::PowerLevels_t &advPowerLevels = persistentData.params.advPowerLevels;
rgrover1 13:b82d8db73633 157 uint8_t flags = persistentData.params.flags;
rgrover1 13:b82d8db73633 158
rgrover1 13:b82d8db73633 159 pstorageSave();
rgrover1 13:b82d8db73633 160
rgrover1 13:b82d8db73633 161 delete uriBeaconConfig;
rgrover1 13:b82d8db73633 162 uriBeaconConfig = NULL;
rgrover1 13:b82d8db73633 163
rgrover1 13:b82d8db73633 164 ble.clearAdvertisingPayload();
rgrover1 13:b82d8db73633 165 ble.setTxPower(firmwarePowerLevels[txPowerMode]);
rgrover1 13:b82d8db73633 166
rgrover1 13:b82d8db73633 167 ble.setAdvertisingType(
rgrover1 13:b82d8db73633 168 GapAdvertisingParams::ADV_NON_CONNECTABLE_UNDIRECTED);
rgrover1 13:b82d8db73633 169
rgrover1 13:b82d8db73633 170 ble.setAdvertisingInterval(
rgrover1 13:b82d8db73633 171 Gap::MSEC_TO_ADVERTISEMENT_DURATION_UNITS(beaconPeriod));
rgrover1 13:b82d8db73633 172
rgrover1 13:b82d8db73633 173 ble.accumulateAdvertisingPayload(
rgrover1 13:b82d8db73633 174 GapAdvertisingData::BREDR_NOT_SUPPORTED |
rgrover1 13:b82d8db73633 175 GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
rgrover1 13:b82d8db73633 176
rgrover1 13:b82d8db73633 177 ble.accumulateAdvertisingPayload(
rgrover1 13:b82d8db73633 178 GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, BEACON_UUID,
rgrover1 13:b82d8db73633 179 sizeof(BEACON_UUID));
rgrover1 13:b82d8db73633 180
rgrover1 13:b82d8db73633 181 serviceData[serviceDataLen++] = BEACON_UUID[0];
rgrover1 13:b82d8db73633 182 serviceData[serviceDataLen++] = BEACON_UUID[1];
rgrover1 13:b82d8db73633 183 serviceData[serviceDataLen++] = flags;
rgrover1 13:b82d8db73633 184 serviceData[serviceDataLen++] = advPowerLevels[txPowerMode];
rgrover1 13:b82d8db73633 185 for (int j=0; j < uriDataLength; j++) {
rgrover1 13:b82d8db73633 186 serviceData[serviceDataLen++] = uriData[j];
rgrover1 13:b82d8db73633 187 }
rgrover1 13:b82d8db73633 188
rgrover1 13:b82d8db73633 189 ble.accumulateAdvertisingPayload(
rgrover1 13:b82d8db73633 190 GapAdvertisingData::SERVICE_DATA,
rgrover1 13:b82d8db73633 191 serviceData, serviceDataLen);
rgrover1 13:b82d8db73633 192
rgrover1 13:b82d8db73633 193 ble.startAdvertising();
rgrover1 13:b82d8db73633 194 }
rgrover1 13:b82d8db73633 195
rgrover1 13:b82d8db73633 196 // After advertising timeout, stop config and switch to UriBeacon
rgrover1 13:b82d8db73633 197 void timeout(void) {
rgrover1 13:b82d8db73633 198 Gap::GapState_t state;
rgrover1 13:b82d8db73633 199 state = ble.getGapState();
rgrover1 13:b82d8db73633 200 if (!state.connected) {
rgrover1 13:b82d8db73633 201 startAdvertisingUriBeacon();
rgrover1 13:b82d8db73633 202 }
rgrover1 13:b82d8db73633 203 }
rgrover1 13:b82d8db73633 204
rgrover1 13:b82d8db73633 205 // When connected to config service, change the LEDs
rgrover1 13:b82d8db73633 206 void connectionCallback(Gap::Handle_t handle,
rgrover1 13:b82d8db73633 207 Gap::addr_type_t peerAddrType,
rgrover1 13:b82d8db73633 208 const Gap::address_t peerAddr,
rgrover1 13:b82d8db73633 209 const Gap::ConnectionParams_t *params) {
rgrover1 13:b82d8db73633 210 advertisingStateLed = 1;
rgrover1 13:b82d8db73633 211 connectionStateLed = 0;
rgrover1 13:b82d8db73633 212 }
rgrover1 13:b82d8db73633 213
rgrover1 13:b82d8db73633 214 // When disconnected from config service, start advertising UriBeacon
rgrover1 13:b82d8db73633 215 void disconnectionCallback(Gap::Handle_t handle,
rgrover1 13:b82d8db73633 216 Gap::DisconnectionReason_t reason) {
rgrover1 13:b82d8db73633 217 advertisingStateLed = 0; // on
rgrover1 13:b82d8db73633 218 connectionStateLed = 1; // off
rgrover1 13:b82d8db73633 219 startAdvertisingUriBeacon();
rgrover1 13:b82d8db73633 220 }
rgrover1 13:b82d8db73633 221
rgrover1 0:790a27ffc99b 222 int main(void)
rgrover1 0:790a27ffc99b 223 {
rgrover1 13:b82d8db73633 224 advertisingStateLed = 0; // on
rgrover1 13:b82d8db73633 225 connectionStateLed = 1; // off
rgrover1 13:b82d8db73633 226
rgrover1 0:790a27ffc99b 227 ble.init();
rgrover1 6:31b65d4ea67d 228 ble.onDisconnection(disconnectionCallback);
rgrover1 13:b82d8db73633 229 ble.onConnection(connectionCallback);
rgrover1 13:b82d8db73633 230 // Advertising timeout
rgrover1 13:b82d8db73633 231 ble.onTimeout(timeout);
rgrover1 0:790a27ffc99b 232
rgrover1 13:b82d8db73633 233 pstorageLoad();
rgrover1 13:b82d8db73633 234 bool resetToDefaults = persistentData.magic != MAGIC;
rgrover1 13:b82d8db73633 235
rgrover1 13:b82d8db73633 236 URIBeaconConfigService::UriData_t defaultUriData;
rgrover1 13:b82d8db73633 237 size_t defaultUriDataLength;
rgrover1 13:b82d8db73633 238 URIBeaconConfigService::encodeURI("http://uribeacon.org", defaultUriData, defaultUriDataLength);
rgrover1 13:b82d8db73633 239
rgrover1 13:b82d8db73633 240 uriBeaconConfig = new URIBeaconConfigService(ble, persistentData.params, resetToDefaults,
rgrover1 13:b82d8db73633 241 defaultUriData, defaultUriDataLength, defaultAdvPowerLevels);
rgrover1 13:b82d8db73633 242 if (!uriBeaconConfig->configuredSuccessfully()) {
rgrover1 6:31b65d4ea67d 243 error("failed to accommodate URI");
rgrover1 6:31b65d4ea67d 244 }
rgrover1 0:790a27ffc99b 245
rgrover1 13:b82d8db73633 246 // Setup auxiliary services to allow over-the-air firmware updates, etc
rgrover1 13:b82d8db73633 247 DFUService dfu(ble);
rgrover1 13:b82d8db73633 248 DeviceInformationService deviceInfo(
rgrover1 13:b82d8db73633 249 ble, "ARM", "UriBeacon", "SN1", "hw-rev1", "fw-rev1", "soft-rev1");
rgrover1 11:c77cc2b74101 250
rgrover1 13:b82d8db73633 251 /* Start out by advertising the configService for a limited time after
rgrover1 13:b82d8db73633 252 * startup; and switch to the normal non-connectible beacon functionality
rgrover1 13:b82d8db73633 253 * afterwards. */
rgrover1 13:b82d8db73633 254 startAdvertisingUriBeaconConfig();
rgrover1 0:790a27ffc99b 255
rgrover1 0:790a27ffc99b 256 while (true) {
rgrover1 0:790a27ffc99b 257 ble.waitForEvent();
rgrover1 0:790a27ffc99b 258 }
rgrover1 0:790a27ffc99b 259 }