Example program for the Eddystone Beacon service.
Dependencies: BLE_API mbed nRF51822 X_NUCLEO_IDB0XA1
Fork of BLE_EddystoneBeacon by
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.
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.
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.
Diff: ZipBeaconConfigService.h
- Revision:
- 16:a7d07ea94b31
- Parent:
- 15:af8c24f34a9f
- Child:
- 17:0458759c40e4
--- a/ZipBeaconConfigService.h Mon Jul 20 19:31:40 2015 +0000 +++ b/ZipBeaconConfigService.h Wed Jul 22 18:01:20 2015 +0000 @@ -17,8 +17,9 @@ #ifndef SERVICES_ZIPBEACONCONFIGSERVICE_H_ #define SERVICES_ZIPBEACONCONFIGSERVICE_H_ -#include "BLE.h" #include "mbed.h" +#include "ble/BLE.h" + #define UUID_URI_BEACON(FIRST, SECOND) { \ 0xee, 0x0c, FIRST, SECOND, 0x87, 0x86, 0x40, 0xba, \ @@ -37,6 +38,17 @@ static const uint8_t UUID_RESET_CHAR[] = UUID_URI_BEACON(0x20, 0x89); static const uint8_t BEACON_EDDYSTONE[] = {0xAA, 0xFE}; +//Debug is disabled by default +#if 0 +#define DBG(x, ...) printf("[EddyStone: DBG]"x" \t[%s,%d]\r\n", ##__VA_ARGS__,__FILE__,__LINE__); +#define WARN(x, ...) printf("[EddyStone: WARN]"x" \t[%s,%d]\r\n", ##__VA_ARGS__,__FILE__,__LINE__); +#define ERR(x, ...) printf("[EddyStone: ERR]"x" \t[%s,%d]\r\n", ##__VA_ARGS__,__FILE__,__LINE__); +#else +#define DBG(x, ...) //wait_us(10); +#define WARN(x, ...) //wait_us(10); +#define ERR(x, ...) +#endif + /** * @class ZipBeaconConfigService * @brief ZipBeacon Configuration Service. Can be used to set URL, adjust power levels, and set flags. @@ -369,54 +381,115 @@ } /* + * callback function, called every 0.1s, incriments the TimeSinceBoot field in the TLM frame + * @return nothing + */ + void tsbCallback(void) { + TlmTimeSinceBoot++; + } + + /* + * Update advertising data + * @return true on success, false on failure + */ + bool updateAdvPacket(uint8_t serviceData[], unsigned serviceDataLen) { + // Fields from the Service + DBG("Updating Adv Data: %d", serviceDataLen); + + ble.clearAdvertisingPayload(); + ble.accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE); + ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, BEACON_EDDYSTONE, sizeof(BEACON_EDDYSTONE)); + ble.accumulateAdvertisingPayload(GapAdvertisingData::SERVICE_DATA, serviceData, serviceDataLen); + // attach callback to count number of sent packets + + +// printf("\r\n"); +// for(int x = 0; x<serviceDataLen; x++) { +// printf("%2.2x:",serviceData[x]); +// } +// printf("\r\n"); + + ble.gap().startAdvertising(); + return true; + } + + /* * Callback from onRadioNotification(), used to update the PDUCounter and maybe other stuff eventually? * * */ - //void radioNotificationCallback(bool radioActive){ - void radioNotificationCallback(void) { + void radioNotificationCallback(bool radioActive) { + DBG("RadioNotificationCallback : %d, %d",radioActive,frameIndex); // Update Time and PDUCount - TlmPduCount++; - TlmTimeSinceBoot++; - - // Every 1 second switch the frame types - if((TlmPduCount % 10) == 1) { + // True just before an frame is sent, fale just after a frame is sent + if(radioActive) { + // Do Nothing + } else { + static bool switchFlag; + TlmPduCount++; frameIndex = frameIndex % EDDYSTONE_MAX_FRAMETYPE; uint8_t serviceData[SERVICE_DATA_MAX]; unsigned serviceDataLen = 0; - //switch payloads + //hard code in the eddystone UUID serviceData[serviceDataLen++] = BEACON_EDDYSTONE[0]; serviceData[serviceDataLen++] = BEACON_EDDYSTONE[1]; + + // state machine to control which packet is being sent switch(frameIndex) { case 0: - printf("constructing TLM Frame: "); - serviceDataLen += constructTLMFrame(serviceData+serviceDataLen,20); + DBG("\tconstructing TLM Frame: "); + serviceDataLen += constructTLMFrame(serviceData+serviceDataLen,SERVICE_DATA_MAX); + updateAdvPacket(serviceData,serviceDataLen); + frameIndex++; + switchFlag = true; break; case 1: - printf("constructing URL Frame: "); - serviceDataLen += constructURLFrame(serviceData+serviceDataLen,20); + // switch out packets + if(switchFlag) { + DBG("\tconstructing URL Frame: "); + serviceDataLen += constructURLFrame(serviceData+serviceDataLen,SERVICE_DATA_MAX); + updateAdvPacket(serviceData,serviceDataLen); + switchFlag = false; + } else { + if((TlmPduCount % 10) == 0) { // every 10 adv packets switch the frame + switchFlag = true; + frameIndex++; + } + } break; case 2: - printf("constructing UID Frame: "); - serviceDataLen += constructUIDFrame(serviceData+serviceDataLen,20); + // switch out packets + if(switchFlag) { + DBG("\tconstructing UID Frame: "); + serviceDataLen += constructUIDFrame(serviceData+serviceDataLen,SERVICE_DATA_MAX); + updateAdvPacket(serviceData,serviceDataLen); + switchFlag = false; + } else { + if((TlmPduCount % 10) == 0) { // every 10 adv packets switch the frame + switchFlag = true; + frameIndex++; + } + } break; } - ble.accumulateAdvertisingPayload(GapAdvertisingData::SERVICE_DATA, serviceData, serviceDataLen); - for(int x = 0; x<serviceDataLen; x++) { - printf("%2.2x:",serviceData[x]); - } - printf("\r\n"); } - //TODO: add bit to keep timer / update time - frameIndex++; + return; } /* Helper function to switch to the non-connectible normal mode for ZipBeacon. This gets called after a timeout. */ void setupZipBeaconAdvertisements() { - printf("Switching Config -> adv\r\n"); + DBG("Switching Config -> adv"); uint8_t serviceData[SERVICE_DATA_MAX]; unsigned serviceDataLen = 0; + unsigned beaconPeriod = params.beaconPeriod; + unsigned txPowerMode = params.txPowerMode; + unsigned uriDataLength = params.uriDataLength; + + ZipBeaconConfigService::UriData_t &uriData = params.uriData; + ZipBeaconConfigService::PowerLevels_t &advPowerLevels = params.advPowerLevels; + uint8_t flags = params.flags; + // Initialize Frame transition frameIndex = 0; uidRFU = 0; @@ -424,24 +497,12 @@ /* Reinitialize the BLE stack. This will clear away the existing services and advertising state. */ ble.shutdown(); ble.init(); - - // Fields from the Service - unsigned beaconPeriod = params.beaconPeriod; - unsigned txPowerMode = params.txPowerMode; - unsigned uriDataLength = params.uriDataLength; - ZipBeaconConfigService::UriData_t &uriData = params.uriData; - ZipBeaconConfigService::PowerLevels_t &advPowerLevels = params.advPowerLevels; - uint8_t flags = params.flags; - - extern void saveURIBeaconConfigParams(const Params_t *paramsP); /* forward declaration; necessary to avoid a circular dependency. */ - saveURIBeaconConfigParams(¶ms); - - ble.clearAdvertisingPayload(); ble.setTxPower(params.advPowerLevels[params.txPowerMode]); ble.setAdvertisingType(GapAdvertisingParams::ADV_NON_CONNECTABLE_UNDIRECTED); ble.setAdvertisingInterval(beaconPeriod); - ble.accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE); - ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, BEACON_EDDYSTONE, sizeof(BEACON_EDDYSTONE)); + ble.gap().onRadioNotification(this,&ZipBeaconConfigService::radioNotificationCallback); + extern void saveURIBeaconConfigParams(const Params_t *paramsP); /* forward declaration; necessary to avoid a circular dependency. */ + saveURIBeaconConfigParams(¶ms); setTLMFrameData(0x00,22,33,0,0); // Initialize TLM Data, for testing, remove for release updateTlmPduCount(0); @@ -450,22 +511,23 @@ // Construct TLM Frame in initial advertising. serviceData[serviceDataLen++] = BEACON_EDDYSTONE[0]; serviceData[serviceDataLen++] = BEACON_EDDYSTONE[1]; - constructTLMFrame(serviceData+serviceDataLen,20); + serviceDataLen += constructTLMFrame(serviceData+serviceDataLen,SERVICE_DATA_MAX); + + updateAdvPacket(serviceData, serviceDataLen); // serviceData[serviceDataLen++] = FRAME_TYPE_URL | flags; // serviceData[serviceDataLen++] = advPowerLevels[txPowerMode]; // for (unsigned j = 0; j < uriDataLength; j++) { // serviceData[serviceDataLen++] = uriData[j]; // } - // attach callback to count number of sent packets - //ble.gap().onRadioNotification(this,&ZipBeaconConfigService::radioNotificationCallback); - ble.accumulateAdvertisingPayload(GapAdvertisingData::SERVICE_DATA, serviceData, serviceDataLen); - callbackTick.attach(this,&ZipBeaconConfigService::radioNotificationCallback, 0.1); // switch services every 2 seconds + + //callbackTick.attach(this,&ZipBeaconConfigService::radioNotificationCallback, 0.1); // switch services every 2 seconds + timeSinceBootTick.attach(this,&ZipBeaconConfigService::tsbCallback,0.1); // incriment the TimeSinceBoot ticker every 0.1s //callbackTick.attach(stupidWrapperFn,2.0); } private: - // True if the lock bits are non-zero +// True if the lock bits are non-zero bool isLocked() { Lock_t testLock; memset(testLock, 0, sizeof(Lock_t)); @@ -621,7 +683,8 @@ BLEDevice &ble; Params_t ¶ms; Ticker callbackTick; - // Default value that is restored on reset + Ticker timeSinceBootTick; +// Default value that is restored on reset size_t defaultUriDataLength; UriData_t defaultUriData; UIDNamespaceID_t defaultUidNamespaceID; @@ -629,13 +692,13 @@ int8_t defaultUidPower; int8_t defaultUrlPower; uint16_t uidRFU; - // Default value that is restored on reset +// Default value that is restored on reset PowerLevels_t &defaultAdvPowerLevels; uint8_t lockedState; bool initSucceeded; uint8_t resetFlag; - // Private Variables for Telemetry Data +// Private Variables for Telemetry Data uint8_t TlmVersion; volatile uint16_t TlmBatteryVoltage; volatile uint16_t TlmBeaconTemp;