High level Bluetooth Low Energy API and radio abstraction layer
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;