Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Fork of BLE_API by
Diff: ble/services/EddystoneService.h
- Revision:
- 830:a6ef72e0697a
- Parent:
- 786:d6d7087d8377
- Child:
- 832:5dac246f8f02
--- a/ble/services/EddystoneService.h Tue Sep 29 09:54:18 2015 +0100 +++ b/ble/services/EddystoneService.h Tue Sep 29 09:54:18 2015 +0100 @@ -19,7 +19,7 @@ #include "ble/BLE.h" #include "mbed.h" - +#include "CircularBuffer.h" static const uint8_t BEACON_EDDYSTONE[] = {0xAA, 0xFE}; //Debug is disabled by default @@ -48,19 +48,21 @@ class EddystoneService { public: - /** - * @brief Transmission Power Modes for UriBeacon - */ + enum FrameTypes { + NONE, + url, + uid, + tlm + }; - static const int ADVERTISING_INTERVAL_MSEC = 1000; // Advertising interval for config service. static const int SERVICE_DATA_MAX = 31; // Maximum size of service data in ADV packets // There are currently 3 subframes defined, URI, UID, and TLM #define EDDYSTONE_MAX_FRAMETYPE 3 void (*frames[EDDYSTONE_MAX_FRAMETYPE])(uint8_t *, uint32_t); - uint8_t frameIndex; static const int URI_DATA_MAX = 18; typedef uint8_t UriData_t[URI_DATA_MAX]; + CircularBuffer<FrameTypes,EDDYSTONE_MAX_FRAMETYPE> overflow; // UID Frame Type subfields static const int UID_NAMESPACEID_SIZE = 10; @@ -84,7 +86,11 @@ * @param RFU 2B of RFU, initialized to 0x0000 and not broadcast, included for future reference. * */ - void setUIDFrameData(int8_t power, UIDNamespaceID_t namespaceID, UIDInstanceID_t instanceID, uint16_t RFU = 0x0000) { + void setUIDFrameData(int8_t power, UIDNamespaceID_t namespaceID, UIDInstanceID_t instanceID, uint32_t uidAdvPeriodIn, uint16_t RFU = 0x0000) { + if(0 == uidAdvPeriodIn){ + uidIsSet = false; + return; + } if(power > 20) { power = 20; } @@ -95,6 +101,8 @@ memcpy(defaultUidNamespaceID, namespaceID, UID_NAMESPACEID_SIZE); memcpy(defaultUidInstanceID, instanceID, UID_INSTANCEID_SIZE); uidRFU = (uint16_t)RFU; // this is probably bad form, but it doesnt really matter yet. + uidAdvPeriod = uidAdvPeriodIn; + uidIsSet = true; // set toggle to advertise UID frames return; } @@ -115,12 +123,18 @@ defaultUidPower = -100; } Data[index++] = defaultUidPower; // 1B Power @ 0meter + DBG("UID NamespaceID = '0x"); for(int x = 0; x < UID_NAMESPACEID_SIZE; x++) { // 10B Namespce ID Data[index++] = defaultUidNamespaceID[x]; + DBG("%x,",defaultUidNamespaceID[x]); } + DBG("'\r\n"); + DBG("UID InstancdID = '0x"); for(int x = 0; x< UID_INSTANCEID_SIZE; x++) { // 6B Instance ID Data[index++] = defaultUidInstanceID[x]; + DBG("%x,",defaultUidInstanceID[x]); } + DBG("'\r\n"); if(0 != uidRFU) { // 2B RFU, include if non-zero, otherwise ignore Data[index++] = (uint8_t)(uidRFU >> 0); Data[index++] = (uint8_t)(uidRFU >> 8); @@ -133,14 +147,21 @@ * Set Eddystone URL Frame information. * @param[in] power TX Power in dB measured at 0 meters from the device. * @param url URL to encode + * @param urlAdvPeriodIn How long to advertise the URL frame (measured in # of adv periods) * @return false on success, true on failure. */ - bool setURLFrameData(int8_t power, const char * url) { + bool setURLFrameData(int8_t power, const char * urlIn, uint32_t urlAdvPeriodIn) { + if(0 == urlAdvPeriodIn){ + urlIsSet = false; + return false; + } defaultUrlPower = power; - encodeURL(url, defaultUriData, defaultUriDataLength); // encode URL to URL Formatting + encodeURL(urlIn, defaultUriData, defaultUriDataLength); // encode URL to URL Formatting if (defaultUriDataLength > URI_DATA_MAX) { return true; // error, URL is too big } + urlAdvPeriod = urlAdvPeriodIn; + urlIsSet = true; return false; } @@ -164,16 +185,23 @@ /* * Set Eddystone TLM Frame information. * @param[in] Version of the TLM beacon data format + * @param[in] advPeriod how often to advertise the TLM frame for (in minutes) * @param batteryVoltage in milivolts * @param beaconTemp in 8.8 floating point notation * */ - void setTLMFrameData(uint8_t version, uint16_t batteryVoltage, uint16_t beaconTemp, uint32_t pduCount = 0, uint32_t timeSinceBoot = 0) { + void setTLMFrameData(uint8_t version = 0, uint32_t advPeriod = 60, uint16_t batteryVoltage = 0, uint16_t beaconTemp = 0, uint32_t pduCount = 0, uint32_t timeSinceBoot = 0) { + if(0 == advPeriod){ + tlmIsSet = false; + return; + } TlmVersion = version; TlmBatteryVoltage = batteryVoltage; TlmBeaconTemp = beaconTemp; TlmPduCount = pduCount; // reset TlmTimeSinceBoot = timeSinceBoot; // reset + TlmAdvPeriod = advPeriod; + tlmIsSet = true; // TLM Data has been enabled return; } @@ -191,14 +219,14 @@ Data[index++] = (uint8_t)(TlmBatteryVoltage>>0); // Battery Voltage[1] Data[index++] = (uint8_t)(TlmBeaconTemp>>8); // Beacon Temp[0] Data[index++] = (uint8_t)(TlmBeaconTemp>>0); // Beacon Temp[1] - Data[index++] = (uint8_t)(TlmPduCount>>24); // PDU Count [0] - Data[index++] = (uint8_t)(TlmPduCount>>16); // PDU Count [1] - Data[index++] = (uint8_t)(TlmPduCount>>8); // PDU Count [2] - Data[index++] = (uint8_t)(TlmPduCount>>0); // PDU Count [3] - Data[index++] = (uint8_t)(TlmTimeSinceBoot>>24); // Time Since Boot [0] - Data[index++] = (uint8_t)(TlmTimeSinceBoot>>16); // Time Since Boot [1] - Data[index++] = (uint8_t)(TlmTimeSinceBoot>>8); // Time Since Boot [2] - Data[index++] = (uint8_t)(TlmTimeSinceBoot>>0); // Time Since Boot [3] + Data[index++] = (uint8_t)(TlmPduCount>>24); // PDU Count [0] + Data[index++] = (uint8_t)(TlmPduCount>>16); // PDU Count [1] + Data[index++] = (uint8_t)(TlmPduCount>>8); // PDU Count [2] + Data[index++] = (uint8_t)(TlmPduCount>>0); // PDU Count [3] + Data[index++] = (uint8_t)(TlmTimeSinceBoot>>24); // Time Since Boot [0] + Data[index++] = (uint8_t)(TlmTimeSinceBoot>>16); // Time Since Boot [1] + Data[index++] = (uint8_t)(TlmTimeSinceBoot>>8); // Time Since Boot [2] + Data[index++] = (uint8_t)(TlmTimeSinceBoot>>0); // Time Since Boot [3] DBG("constructURLFrame: %d, %d",maxSize,index); return index; } @@ -264,10 +292,13 @@ // } // printf("\r\n"); ble.clearAdvertisingPayload(); + ble.setAdvertisingType(GapAdvertisingParams::ADV_NON_CONNECTABLE_UNDIRECTED); + ble.setAdvertisingInterval(100); 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); + return true; } @@ -277,7 +308,7 @@ * This function exists because of time constraints in the radioNotificationCallback, so it is effectively * broken up into two functions. */ - void swapOutFrames(void) { + void swapOutFrames(FrameTypes frameType) { uint8_t serviceData[SERVICE_DATA_MAX]; unsigned serviceDataLen = 0; //hard code in the eddystone UUID @@ -285,37 +316,114 @@ serviceData[serviceDataLen++] = BEACON_EDDYSTONE[1]; // if certain frames are not enabled, then skip them. Worst case TLM is always enabled - switch(frameIndex) { - case 1: + switch(frameType) { + case tlm: + // TLM frame + if(tlmIsSet) { + DBG("Swapping in TLM Frame: version=%x, Batt=%d, Temp = %d, PDUCnt = %d, TimeSinceBoot=%d",TlmVersion, TlmBatteryVoltage, TlmBeaconTemp, TlmPduCount, TlmTimeSinceBoot); + serviceDataLen += constructTLMFrame(serviceData+serviceDataLen,20); + DBG("\t Swapping in TLM Frame: len=%d",serviceDataLen); + updateAdvPacket(serviceData,serviceDataLen); + } + break; + case url: // URL Frame if(urlIsSet) { - INFO("Swapping in URL Frame: Power: %d",defaultUrlPower); + DBG("Swapping in URL Frame: Power: %d",defaultUrlPower); serviceDataLen += constructURLFrame(serviceData+serviceDataLen,20); DBG("\t Swapping in URL Frame: len=%d ",serviceDataLen); updateAdvPacket(serviceData,serviceDataLen); - switchFlag = false; - frameIndex++; - break; + //switchFlag = false; } - case 2: + break; + case uid: // UID Frame if(uidIsSet) { - INFO("Swapping in UID Frame: Power: %d",defaultUidPower); + DBG("Swapping in UID Frame: Power: %d",defaultUidPower); serviceDataLen += constructUIDFrame(serviceData+serviceDataLen,20); DBG("\t Swapping in UID Frame: len=%d",serviceDataLen); updateAdvPacket(serviceData,serviceDataLen); - switchFlag = false; - frameIndex++; - break; + //switchFlag = false; } + break; default: - // TLM frame - INFO("Swapping in TLM Frame: version=%x, Batt=%d, Temp = %d, PDUCnt = %d, TimeSinceBoot=%d",TlmVersion, TlmBatteryVoltage, TlmBeaconTemp, TlmPduCount, TlmTimeSinceBoot); - serviceDataLen += constructTLMFrame(serviceData+serviceDataLen,20); - DBG("\t Swapping in TLM Frame: len=%d",serviceDataLen); - updateAdvPacket(serviceData,serviceDataLen); - frameIndex++; - break; + ERR("You have not initialized a Frame yet, please initialize one before starting a beacon"); + ERR("uidIsSet = %d, urlIsSet = %d, tlmIsSet = %d", uidIsSet, urlIsSet, tlmIsSet); + } + } + + /* + * Callback to swap in URL frame + */ + void urlCallback(void) { + DBG("urlCallback"); + if( false == advLock) { + advLock = true; + DBG("advLock = url") + frameIndex = url; + swapOutFrames(frameIndex); + ble.startAdvertising(); + } else { + // Someone else is broadcasting, toss it into the overflow buffer to retransmit when free + INFO("URI(%d) cannot complete, %d is currently broadcasting",url, frameIndex); + FrameTypes x = url; + overflow.push(x); + } + return; + } + + /* + * Callback to swap in UID frame + */ + void uidCallback(void) { + DBG("uidCallback"); + if( false == advLock) { + advLock = true; + DBG("advLock = uid") + frameIndex = uid; + swapOutFrames(frameIndex); + ble.startAdvertising(); + } else { + // Someone else is broadcasting, toss it into the overflow buffer to retransmit when free + INFO("UID(%d) cannot complete, %d is currently broadcasting", uid,frameIndex); + FrameTypes x = uid; // have to do this to satisfy cont vs volatile keywords... sigh... + overflow.push(x); + } + return; + } + + /* + * Callback to swap in TLM frame + */ + void tlmCallback(void) { + DBG("tlmCallback"); + if( false == advLock) { + // OK to broadcast + advLock = true; + DBG("advLock = tlm") + frameIndex = tlm; + swapOutFrames(frameIndex); + ble.startAdvertising(); + } else { + // Someone else is broadcasting, toss it into the overflow buffer to retransmit when free + INFO("TLM(%d) cannot complete, %d is currently broadcasting",tlm ,frameIndex); + FrameTypes x = tlm; + overflow.push(x); + } + return; + } + + void stopAdvCallback(void) { + if(overflow.empty()) { + // if nothing left to transmit, stop + ble.stopAdvertising(); + advLock = false; // unlock lock + } else { + // transmit other packets at current time index + FrameTypes x = NONE; + overflow.pop(x); + INFO("Re-Transmitting %d",x); + swapOutFrames(x); } } @@ -324,47 +432,15 @@ */ #define EDDYSTONE_SWAPFRAME_DELAYMS 1 void radioNotificationCallback(bool radioActive) { - //DBG("RadioNotificationCallback : %d, %d, %d, %d",radioActive,frameIndex,TlmPduCount,TlmTimeSinceBoot); // Update PDUCount TlmPduCount++; - frameIndex = frameIndex % EDDYSTONE_MAX_FRAMETYPE; - - - // True just before an frame is sent, fale just after a frame is sent + // True just before an frame is sent, false just after a frame is sent if(radioActive) { // Do Nothing } else { - // state machine to control which packet is being sent - switch(frameIndex) { - case 0: // TLM Frame - switchFrame.attach_us(this, &EddystoneService::swapOutFrames, EDDYSTONE_SWAPFRAME_DELAYMS); - switchFlag = true; - break; - case 1: // URL Frame - // switch out packets - if(switchFlag) { - switchFrame.attach_us(this, &EddystoneService::swapOutFrames, EDDYSTONE_SWAPFRAME_DELAYMS); - switchFlag = false; - } else { - if((TlmPduCount % 10) == 0) { // every 10 adv packets switch the frame - switchFlag = true; - } - } - break; - case 2: // UIDFrame - // switch out packets - if(switchFlag ) { - switchFrame.attach_us(this, &EddystoneService::swapOutFrames, EDDYSTONE_SWAPFRAME_DELAYMS); - switchFlag = false; - } else { - if((TlmPduCount % 10) == 0) { // every 10 adv packets switch the frame - switchFlag = true; - } - } - break; - } + // Packet has been sent, disable advertising + stopAdv.attach_us(this, &EddystoneService::stopAdvCallback, 1); } - return; } @@ -373,102 +449,102 @@ * this function should be used in leu of the config service. * * @param bleIn ble object used to broadcast eddystone information - * @oaram beaconPeriodus is how often ble broadcasts are mde, in mili seconds + * @param beaconPeriodus is how often ble broadcasts are mde, in mili seconds * @param txPowerLevel sets the broadcasting power level. - * @param uidNamespaceID 10Byte Namespace UUID - * @param uidInstanceID 6Byte Instance UUID - * @param url shortened URL to broadcast (pass in as a string) - * @param urlLen length of shortened url - * @param tlmVersion version of telemetry data field to use (default to 0x00) * */ EddystoneService(BLEDevice &bleIn, - uint16_t beaconPeriodus = 100, - uint8_t txPowerLevel = 0, - uint8_t * uidNamespaceID = NULL, - uint8_t * uidInstanceID = NULL, - const char * url = NULL, - uint8_t urlLen = 0, - uint8_t tlmVersion = 0) : - ble(bleIn) - { - uint8_t serviceData[SERVICE_DATA_MAX]; - unsigned serviceDataLen = 0; - ERR("This function is not fully implemented yet, dont use it!!"); - // Check optional frames, set their 'isSet' flags appropriately - if((uidNamespaceID != NULL) & (uidInstanceID != NULL)) { - uidIsSet = true; - setUIDFrameData(txPowerLevel,uidNamespaceID, uidInstanceID); - } else { - uidIsSet = false; - } - if(url != NULL) { - urlIsSet = true; - setURLFrameData(txPowerLevel,url); - } else { - uidIsSet = false; + uint16_t beaconPeriodus = 100, + uint8_t txPowerIn = 0) : + ble(bleIn), + advPeriodus(beaconPeriodus), + txPower(txPowerIn), + advLock(false), + frameIndex(NONE) { + + } + + /* + * @breif this function starts eddystone advertising based on configured frames. + */ + void start(void) { + // Initialize Frame transition, start with URL to pass eddystone validator app on first try + if (urlIsSet) { + frameIndex = url; + urlTicker.attach(this, &EddystoneService::urlCallback, urlAdvPeriod); + DBG("attached urlCallback every %d seconds", urlAdvPeriod); } - // Default TLM frame to version 0x00, start all values at zero to be spec compliant. - setTLMFrameData(tlmVersion, 0x00,0x00); - - // Initialize Frame transition - frameIndex = 0; - uidRFU = 0; - switchFlag = true; - - /* Reinitialize the BLE stack. This will clear away the existing services and advertising state. */ - ble.shutdown(); - ble.init(); - ble.setTxPower(txPowerLevel); - ble.setAdvertisingType(GapAdvertisingParams::ADV_NON_CONNECTABLE_UNDIRECTED); - ble.setAdvertisingInterval(beaconPeriodus); + if (uidIsSet) { + frameIndex = uid; + uidTicker.attach(this, &EddystoneService::uidCallback, uidAdvPeriod); + DBG("attached uidCallback every %d seconds", uidAdvPeriod); + } + if(tlmIsSet) { + frameIndex = tlm; + // Make double sure the PDUCount and TimeSinceBoot fields are set to zero at reset + updateTlmPduCount(0); + updateTlmTimeSinceBoot(0); + timeSinceBootTick.attach(this,&EddystoneService::tsbCallback,0.1); // incriment the TimeSinceBoot ticker every 0.1s + tlmTicker.attach(this, &EddystoneService::tlmCallback, TlmAdvPeriod); + DBG("attached tlmCallback every %d seconds", TlmAdvPeriod); + } + if(NONE == frameIndex) { + error("No Frames were Initialized! Please initialize a frame before starting an eddystone beacon."); + } + //uidRFU = 0; - // Make double sure the PDUCount and TimeSinceBoot fields are set to zero at reset - updateTlmPduCount(0); - updateTlmTimeSinceBoot(0); - - // Construct TLM Frame in initial advertising. - serviceData[serviceDataLen++] = BEACON_EDDYSTONE[0]; - serviceData[serviceDataLen++] = BEACON_EDDYSTONE[1]; - serviceDataLen += constructTLMFrame(serviceData+serviceDataLen,SERVICE_DATA_MAX); - - updateAdvPacket(serviceData, serviceDataLen); - ble.gap().startAdvertising(); - ble.gap().onRadioNotification(this,&EddystoneService::radioNotificationCallback); - timeSinceBootTick.attach(this,&EddystoneService::tsbCallback,0.1); // incriment the TimeSinceBoot ticker every 0.1s - + ble.setTxPower(txPower); +// ble.setAdvertisingType(GapAdvertisingParams::ADV_NON_CONNECTABLE_UNDIRECTED); +// ble.setAdvertisingInterval(advPeriodus); +// ble.gap().startAdvertising(); + ble.gap().onRadioNotification(this,&EddystoneService::radioNotificationCallback); } private: - + // Eddystone Variables BLEDevice &ble; - Ticker timeSinceBootTick; - Timeout switchFrame; -// Default value that is restored on reset - size_t defaultUriDataLength; + uint16_t advPeriodus; + uint8_t txPower; + Ticker timeSinceBootTick; // counter that counts time since boot + volatile bool advLock; + volatile FrameTypes frameIndex; + Timeout stopAdv; + + + // URI Frame Variables + uint8_t defaultUriDataLength; UriData_t defaultUriData; + int8_t defaultUrlPower; + bool urlIsSet; // flag that enables / disable URI Frames + uint32_t urlAdvPeriod; // how long the url frame will be advertised for + Ticker urlTicker; + + // UID Frame Variables UIDNamespaceID_t defaultUidNamespaceID; UIDInstanceID_t defaultUidInstanceID; int8_t defaultUidPower; - int8_t defaultUrlPower; uint16_t uidRFU; - bool uidIsSet; - bool urlIsSet; - bool switchFlag; + bool uidIsSet; // flag that enables / disable UID Frames + uint32_t uidAdvPeriod; // how long the uid frame will be advertised for + Ticker uidTicker; -// Private Variables for Telemetry Data + // TLM Frame Variables uint8_t TlmVersion; volatile uint16_t TlmBatteryVoltage; volatile uint16_t TlmBeaconTemp; volatile uint32_t TlmPduCount; volatile uint32_t TlmTimeSinceBoot; + bool tlmIsSet; // flag that enables / disables TLM frames + uint32_t TlmAdvPeriod; // number of minutes between adv frames + Ticker tlmTicker; public: /* * Encode a human-readable URI into the binary format defined by URIBeacon spec (https://github.com/google/uribeacon/tree/master/specification). */ - static void encodeURL(const char *uriDataIn, UriData_t uriDataOut, size_t &sizeofURIDataOut) { + static void encodeURL(const char *uriDataIn, UriData_t uriDataOut, uint8_t &sizeofURIDataOut) { + DBG("Encode URL = %s",uriDataIn); const char *prefixes[] = { "http://www.", "https://www.", @@ -528,6 +604,7 @@ } } /* This is the default case where we've got an ordinary character which doesn't match a suffix. */ + INFO("Encoding URI: No Suffix Found"); if (i == NUM_SUFFIXES) { uriDataOut[sizeofURIDataOut++] = *uriDataIn; ++uriDataIn;