jgh
Fork of BLE_API by
Revision 990:53ac0ac3aa39, committed 2015-11-26
- Comitter:
- rgrover1
- Date:
- Thu Nov 26 14:51:22 2015 +0000
- Parent:
- 989:017f77f120e1
- Child:
- 991:c6c50b28ccd2
- Commit message:
- Synchronized with git rev f8ab6daf
Author: Rohit Grover
Merge pull request #119 from LiyouZhou/guard_dfu_files
Guard nordic specific implementation with #ifdef TARGET_NRF51822
Changed in this revision
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.gitignore Thu Nov 26 14:51:22 2015 +0000 @@ -0,0 +1,2 @@ +# Ignore the generated Doxygen output +apidoc/ \ No newline at end of file
--- a/CONTRIBUTING.md Thu Nov 26 14:51:22 2015 +0000 +++ b/CONTRIBUTING.md Thu Nov 26 14:51:22 2015 +0000 @@ -1,7 +1,7 @@ # Hello! -We are an open source project of [ARM mbed](www.mbed.com). Contributions via [pull request](https://github.com/armmbed/yotta/pulls), and [bug reports](https://github.com/armmbed/yotta/issues) are welcome! +We are an open source project of [ARM mbed](https://www.mbed.com). Contributions via [pull request](https://github.com/ARMmbed/ble/pulls), and [bug reports](https://github.com/ARMmbed/ble/issues) are welcome! -Please submit your pull request to the 'develop' branch of this module. Commits to develop will merge into master at the time of the next release. +Please submit your pull request to the `develop` branch of [this module](https://github.com/ARMmbed/ble/tree/develop). Commits to develop will be merge into the master branch at the time of the next release. # Contributor agreement -For your pull request to be accepted, we will need you to agree to our [contributor agreement](http://developer.mbed.org/contributor_agreement/) to give us the necessary rights to use and distribute your contributions. (To click through the agreement create an account on mbed.com and log in.) \ No newline at end of file +For your pull request to be accepted, we will need you to agree to our [contributor agreement](https://developer.mbed.org/contributor_agreement/) to give us the necessary rights to use and distribute your contributions. (To click through the agreement create an account on mbed.com and log in.) \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/DOXYGEN_FRONTPAGE.md Thu Nov 26 14:51:22 2015 +0000 @@ -0,0 +1,31 @@ +# BLE API {#mainpage} + +The BLE module within mbed OS offers a high abstraction level for using +Bluetooth Low Energy on multiple platforms. + +This documentation describes the internal structure of the mbed +[BLE API](https://github.com/armmbed/ble). It was automatically generated from +specially formatted comment blocks in BLE API's source code using Doxygen (see http://www.stack.nl/~dimitri/doxygen/ for more information on Doxygen). + +For getting started with BLE on mbed, check our [introduction +page](https://docs.mbed.com/docs/ble-intros/en/latest/). + +For mbed OS examples using BLE, check [this +repository](https://github.com/armmbed/ble-examples). For mbed-classic +examples, please refer to [code under mbed.org](https://developer.mbed.org/teams/Bluetooth-Low-Energy/code/). + +## Supported Services + +Currently supported reference services include: + +* [Battery](@ref BatteryService) +* [Device Firmware Update (DFU)](@ref DFUService) +* [Device Information](@ref DeviceInformationService) +* [Health Thermometer](@ref HealthThermometerService) +* [Heart Rate](@ref HeartRateService) +* [UART](@ref UARTService) +* [UriBeacon](@ref URIBeaconConfigService) +* [iBeacon](@ref iBeacon) + +The [documentation](https://docs.mbed.com/docs/ble-intros/en/latest/AdvSamples/Overview/) +contains an overview on how to create new, application-specific services. \ No newline at end of file
--- a/README.md Thu Nov 26 14:51:22 2015 +0000 +++ b/README.md Thu Nov 26 14:51:22 2015 +0000 @@ -1,21 +1,25 @@ # mbed Bluetooth Low Energy Stack -This is the github repo for the BLE_API used by developer.mbed.org. Please see [mbed BLE Homepage](http://developer.mbed.org/teams/Bluetooth-Low-Energy/) for all documentation, code examples and general help. +This is the Github repo for the `BLE_API` used by developer.mbed.org. Please see the [mbed BLE Homepage](https://developer.mbed.org/teams/Bluetooth-Low-Energy/) for all documentation, code examples and general help. # Supported Services -Supported GATT services and constantly being added and can be found in the /services folder. -Currently supported services include: -* Battery +Supported GATT services and constantly being added and can be found in the [ble/services/](https://github.com/ARMmbed/ble/tree/master/ble/services) folder. + +Currently supported services include: +* Battery * Device Firmware Update (DFU) -* Device Information +* Device Information +* Eddystone Configuration Service * Health Thermometer * Heart Rate +* Link Loss * UART * UriBeacon * iBeacon -# Getting Started -The mbed BLE API is meant to be used in projects on developer.mbed.org. Please see examples and sample project files there. +# Getting Started +The mbed BLE API is meant to be used in projects on developer.mbed.org. Please see examples and sample project files there. A good starting point are these pages: -* [mbed BLE Homepage](http://developer.mbed.org/teams/Bluetooth-Low-Energy/) for all things BLE -* [mbed BLE Getting Started Guide](http://developer.mbed.org/forum/team-63-Bluetooth-Low-Energy-community/topic/5262/) a wonderful primer on using BLE with mbed -* [mbed BLE API page](http://developer.mbed.org/teams/Bluetooth-Low-Energy/code/BLE_API/docs/tip/) for the API in generated by doxygen \ No newline at end of file +* [mbed BLE Homepage](https://developer.mbed.org/teams/Bluetooth-Low-Energy/) for all things BLE +* [mbed BLE Getting Started Guide](https://developer.mbed.org/forum/team-63-Bluetooth-Low-Energy-community/topic/5262/) a wonderful primer on using BLE with mbed +* [mbed BLE doc](https://docs.mbed.com/docs/ble-intros/en/latest/) for an introduction to mbed BLE +* [mbed BLE API page](https://developer.mbed.org/teams/Bluetooth-Low-Energy/code/BLE_API/docs/tip/) for the API in generated by doxygen \ No newline at end of file
--- a/ble.doxyfile Thu Nov 26 14:51:22 2015 +0000 +++ b/ble.doxyfile Thu Nov 26 14:51:22 2015 +0000 @@ -664,7 +664,7 @@ # directories like "/usr/src/myproject". Separate the files or directories # with spaces. -INPUT = . +INPUT = # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is @@ -682,7 +682,7 @@ # *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py # *.f90 *.f *.for *.vhd *.vhdl -FILE_PATTERNS = *.h *.cpp +FILE_PATTERNS = *.h *.cpp *.md # The RECURSIVE tag can be used to turn specify whether or not subdirectories # should be searched for input files as well. Possible values are YES and NO. @@ -789,7 +789,7 @@ # (index.html). This can be useful if you have a project on for instance GitHub # and want reuse the introduction page also for the doxygen output. -USE_MDFILE_AS_MAINPAGE = +USE_MDFILE_AS_MAINPAGE = DOXYGEN_FRONTPAGE.md #--------------------------------------------------------------------------- # configuration options related to source browsing
--- a/ble/CallChainOfFunctionPointersWithContext.h Thu Nov 26 14:51:22 2015 +0000 +++ b/ble/CallChainOfFunctionPointersWithContext.h Thu Nov 26 14:51:22 2015 +0000 @@ -18,6 +18,7 @@ #include <string.h> #include "FunctionPointerWithContext.h" +#include "SafeBool.h" /** Group one or more functions in an instance of a CallChainOfFunctionPointersWithContext, then call them in @@ -56,7 +57,7 @@ */ template <typename ContextType> -class CallChainOfFunctionPointersWithContext { +class CallChainOfFunctionPointersWithContext : public SafeBool<CallChainOfFunctionPointersWithContext<ContextType> > { public: typedef FunctionPointerWithContext<ContextType> *pFunctionPointerWithContext_t; @@ -97,6 +98,49 @@ return common_add(new FunctionPointerWithContext<ContextType>(tptr, mptr)); } + /** Add a function at the front of the chain. + * + * @param func The FunctionPointerWithContext to add. + */ + pFunctionPointerWithContext_t add(const FunctionPointerWithContext<ContextType>& func) { + return common_add(new FunctionPointerWithContext<ContextType>(func)); + } + + /** + * Detach a function pointer from a callchain + * + * @oaram toDetach FunctionPointerWithContext to detach from this callchain + * + * @return true if a function pointer has been detached and false otherwise + */ + bool detach(const FunctionPointerWithContext<ContextType>& toDetach) { + pFunctionPointerWithContext_t current = chainHead; + pFunctionPointerWithContext_t previous = NULL; + + while (current) { + if(*current == toDetach) { + if(previous == NULL) { + if(currentCalled == current) { + currentCalled = NULL; + } + chainHead = current->getNext(); + } else { + if(currentCalled == current) { + currentCalled = previous; + } + previous->chainAsNext(current->getNext()); + } + delete current; + return true; + } + + previous = current; + current = current->getNext(); + } + + return false; + } + /** Clear the call chain (remove all functions in the chain). */ void clear(void) { @@ -115,16 +159,56 @@ } /** Call all the functions in the chain in sequence - * @Note: The stack frames of all the callbacks within the chained - * FunctionPointers will stack up. Hopefully there won't be too many - * chained FunctionPointers. */ void call(ContextType context) { - if (chainHead) { - chainHead->call(context); + ((const CallChainOfFunctionPointersWithContext*) this)->call(context); + } + + /** + * @brief same as above but const + */ + void call(ContextType context) const { + currentCalled = chainHead; + + while(currentCalled) { + currentCalled->call(context); + // if this was the head and the call removed the head + if(currentCalled == NULL) { + currentCalled = chainHead; + } else { + currentCalled = currentCalled->getNext(); + } } } + /** + * @brief same as above but with function call operator + * \code + * + * void first(bool); + * void second(bool); + * + * CallChainOfFunctionPointerWithContext<bool> foo; + * + * foo.attach(first); + * foo.attach(second); + * + * // call the callchain like a function + * foo(true); + * + * \endcode + */ + void operator()(ContextType context) const { + call(context); + } + + /** + * @brief bool conversion operation + */ + bool toBool() const { + return chainHead != NULL; + } + private: pFunctionPointerWithContext_t common_add(pFunctionPointerWithContext_t pf) { if (chainHead == NULL) { @@ -139,6 +223,11 @@ private: pFunctionPointerWithContext_t chainHead; + // iterator during a function call, this has to be mutable because the call function is const. + // Note: mutable is the correct behaviour here, the iterator never leak outside the object. + // So the object can still be seen as logically const even if it change its internal state + mutable pFunctionPointerWithContext_t currentCalled; + /* Disallow copy constructor and assignment operators. */ private:
--- a/ble/DiscoveredCharacteristic.h Thu Nov 26 14:51:22 2015 +0000 +++ b/ble/DiscoveredCharacteristic.h Thu Nov 26 14:51:22 2015 +0000 @@ -83,6 +83,8 @@ */ ble_error_t read(uint16_t offset = 0) const; + ble_error_t read(uint16_t offset, const GattClient::ReadCallback_t& onRead) const; + /** * Perform a write without response procedure. * @@ -135,6 +137,11 @@ */ ble_error_t write(uint16_t length, const uint8_t *value) const; + /** + * Same as above but register the callback wich will be called once the data has been written + */ + ble_error_t write(uint16_t length, const uint8_t *value, const GattClient::WriteCallback_t& onRead) const; + void setupLongUUID(UUID::LongUUIDBytes_t longUUID) { uuid.setupLong(longUUID); }
--- a/ble/FunctionPointerWithContext.h Thu Nov 26 14:51:22 2015 +0000 +++ b/ble/FunctionPointerWithContext.h Thu Nov 26 14:51:22 2015 +0000 @@ -18,14 +18,16 @@ #define MBED_FUNCTIONPOINTER_WITH_CONTEXT_H #include <string.h> +#include "SafeBool.h" /** A class for storing and calling a pointer to a static or member void function * that takes a context. */ template <typename ContextType> -class FunctionPointerWithContext { +class FunctionPointerWithContext : public SafeBool<FunctionPointerWithContext<ContextType> > { public: typedef FunctionPointerWithContext<ContextType> *pFunctionPointerWithContext_t; + typedef const FunctionPointerWithContext<ContextType> *cpFunctionPointerWithContext_t; typedef void (*pvoidfcontext_t)(ContextType context); /** Create a FunctionPointerWithContext, attaching a static function. @@ -33,7 +35,7 @@ * @param function The void static function to attach (default is none). */ FunctionPointerWithContext(void (*function)(ContextType context) = NULL) : - _function(NULL), _caller(NULL), _next(NULL) { + _memberFunctionAndPointer(), _caller(NULL), _next(NULL) { attach(function); } @@ -48,6 +50,17 @@ attach(object, member); } + FunctionPointerWithContext(const FunctionPointerWithContext& that) : + _memberFunctionAndPointer(that._memberFunctionAndPointer), _caller(that._caller), _next(NULL) { + } + + FunctionPointerWithContext& operator=(const FunctionPointerWithContext& that) { + _memberFunctionAndPointer = that._memberFunctionAndPointer; + _caller = that._caller; + _next = NULL; + return *this; + } + /** Attach a static function. * * @param function The void static function to attach (default is none). @@ -73,13 +86,29 @@ * FunctionPointers their callbacks are invoked as well. * @Note: All chained callbacks stack up, so hopefully there won't be too * many FunctionPointers in a chain. */ - void call(ContextType context) { + void call(ContextType context) const { _caller(this, context); + } + + /** + * @brief Same as above + */ + void operator()(ContextType context) const { + call(context); + } - /* Propagate the call to next in the chain. */ - if (_next) { - _next->call(context); - } + /** Same as above, workaround for mbed os FunctionPointer implementation. */ + void call(ContextType context) { + ((const FunctionPointerWithContext*) this)->call(context); + } + + typedef void (FunctionPointerWithContext::*bool_type)() const; + + /** + * implementation of safe bool operator + */ + bool toBool() const { + return (_function || _memberFunctionAndPointer._object); } /** @@ -101,9 +130,18 @@ return (pvoidfcontext_t)_function; } + friend bool operator==(const FunctionPointerWithContext& lhs, const FunctionPointerWithContext& rhs) { + return rhs._caller == lhs._caller && + memcmp( + &rhs._memberFunctionAndPointer, + &lhs._memberFunctionAndPointer, + sizeof(rhs._memberFunctionAndPointer) + ) == 0; + } + private: template<typename T> - static void membercaller(pFunctionPointerWithContext_t self, ContextType context) { + static void membercaller(cpFunctionPointerWithContext_t self, ContextType context) { if (self->_memberFunctionAndPointer._object) { T *o = static_cast<T *>(self->_memberFunctionAndPointer._object); void (T::*m)(ContextType); @@ -112,7 +150,7 @@ } } - static void functioncaller(pFunctionPointerWithContext_t self, ContextType context) { + static void functioncaller(cpFunctionPointerWithContext_t self, ContextType context) { if (self->_function) { self->_function(context); } @@ -141,10 +179,10 @@ * object this pointer and pointer to member - * _memberFunctionAndPointer._object will be NULL if none attached */ - MemberFunctionAndPtr _memberFunctionAndPointer; + mutable MemberFunctionAndPtr _memberFunctionAndPointer; }; - void (*_caller)(FunctionPointerWithContext*, ContextType); + void (*_caller)(const FunctionPointerWithContext*, ContextType); pFunctionPointerWithContext_t _next; /**< Optional link to make a chain out of functionPointers. This * allows chaining function pointers without requiring @@ -152,4 +190,23 @@ * 'CallChain' as an alternative. */ }; +/** + * @brief Create a new FunctionPointerWithContext which bind an instance and a + * a member function together. + * @details This little helper is a just here to eliminate the need to write the + * FunctionPointerWithContext type each time you want to create one by kicking + * automatic type deduction of function templates. With this function, it is easy + * to write only one entry point for functions which expect a FunctionPointer + * in parameters. + * + * @param object to bound with member function + * @param member The member function called + * @return a new FunctionPointerWithContext + */ +template<typename T, typename ContextType> +FunctionPointerWithContext<ContextType> makeFunctionPointer(T *object, void (T::*member)(ContextType context)) +{ + return FunctionPointerWithContext<ContextType>(object, member); +} + #endif // ifndef MBED_FUNCTIONPOINTER_WITH_CONTEXT_H \ No newline at end of file
--- a/ble/Gap.h Thu Nov 26 14:51:22 2015 +0000 +++ b/ble/Gap.h Thu Nov 26 14:51:22 2015 +0000 @@ -140,10 +140,15 @@ return (durationInMillis * 1000) / UNIT_1_25_MS; } + typedef FunctionPointerWithContext<TimeoutSource_t> TimeoutEventCallback_t; + typedef CallChainOfFunctionPointersWithContext<TimeoutSource_t> TimeoutEventCallbackChain_t; - typedef void (*TimeoutEventCallback_t)(TimeoutSource_t source); - typedef void (*ConnectionEventCallback_t)(const ConnectionCallbackParams_t *params); - typedef void (*DisconnectionEventCallback_t)(const DisconnectionCallbackParams_t *params); + typedef FunctionPointerWithContext<const ConnectionCallbackParams_t *> ConnectionEventCallback_t; + typedef CallChainOfFunctionPointersWithContext<const ConnectionCallbackParams_t *> ConnectionEventCallbackChain_t; + + typedef FunctionPointerWithContext<const DisconnectionCallbackParams_t*> DisconnectionEventCallback_t; + typedef CallChainOfFunctionPointersWithContext<const DisconnectionCallbackParams_t*> DisconnectionEventCallbackChain_t; + typedef FunctionPointerWithContext<bool> RadioNotificationEventCallback_t; /* @@ -892,11 +897,25 @@ /** * Set up a callback for timeout events. Refer to TimeoutSource_t for * possible event types. + * @note It is possible to unregister callbacks using onTimeout().detach(callback) */ - void onTimeout(TimeoutEventCallback_t callback) {timeoutCallback = callback;} + void onTimeout(TimeoutEventCallback_t callback) { + timeoutCallbackChain.add(callback); + } + + /** + * @brief provide access to the callchain of timeout event callbacks + * It is possible to register callbacks using onTimeout().add(callback); + * It is possible to unregister callbacks using onTimeout().detach(callback) + * @return The timeout event callbacks chain + */ + TimeoutEventCallbackChain_t& onTimeout() { + return timeoutCallbackChain; + } /** * Append to a chain of callbacks to be invoked upon GAP connection. + * @note It is possible to unregister callbacks using onConnection().detach(callback) */ void onConnection(ConnectionEventCallback_t callback) {connectionCallChain.add(callback);} @@ -904,7 +923,18 @@ void onConnection(T *tptr, void (T::*mptr)(const ConnectionCallbackParams_t*)) {connectionCallChain.add(tptr, mptr);} /** + * @brief provide access to the callchain of connection event callbacks + * It is possible to register callbacks using onConnection().add(callback); + * It is possible to unregister callbacks using onConnection().detach(callback) + * @return The connection event callbacks chain + */ + ConnectionEventCallbackChain_t& onconnection() { + return connectionCallChain; + } + + /** * Append to a chain of callbacks to be invoked upon GAP disconnection. + * @note It is possible to unregister callbacks using onDisconnection().detach(callback) */ void onDisconnection(DisconnectionEventCallback_t callback) {disconnectionCallChain.add(callback);} @@ -912,6 +942,16 @@ void onDisconnection(T *tptr, void (T::*mptr)(const DisconnectionCallbackParams_t*)) {disconnectionCallChain.add(tptr, mptr);} /** + * @brief provide access to the callchain of disconnection event callbacks + * It is possible to register callbacks using onDisconnection().add(callback); + * It is possible to unregister callbacks using onDisconnection().detach(callback) + * @return The disconnection event callbacks chain + */ + DisconnectionEventCallbackChain_t& onDisconnection() { + return disconnectionCallChain; + } + + /** * Set the application callback for radio-notification events. * * Radio Notification is a feature that enables ACTIVE and INACTIVE @@ -940,12 +980,10 @@ */ void onRadioNotification(void (*callback)(bool param)) { radioNotificationCallback.attach(callback); - initRadioNotification(); } template <typename T> void onRadioNotification(T *tptr, void (T::*mptr)(bool)) { radioNotificationCallback.attach(tptr, mptr); - initRadioNotification(); } protected: @@ -956,7 +994,7 @@ _scanResponse(), state(), scanningActive(false), - timeoutCallback(NULL), + timeoutCallbackChain(), radioNotificationCallback(), onAdvertisementReport(), connectionCallChain(), @@ -1002,8 +1040,8 @@ } void processTimeoutEvent(TimeoutSource_t source) { - if (timeoutCallback) { - timeoutCallback(source); + if (timeoutCallbackChain) { + timeoutCallbackChain(source); } } @@ -1017,11 +1055,11 @@ bool scanningActive; protected: - TimeoutEventCallback_t timeoutCallback; - RadioNotificationEventCallback_t radioNotificationCallback; - AdvertisementReportCallback_t onAdvertisementReport; - CallChainOfFunctionPointersWithContext<const ConnectionCallbackParams_t*> connectionCallChain; - CallChainOfFunctionPointersWithContext<const DisconnectionCallbackParams_t*> disconnectionCallChain; + TimeoutEventCallbackChain_t timeoutCallbackChain; + RadioNotificationEventCallback_t radioNotificationCallback; + AdvertisementReportCallback_t onAdvertisementReport; + ConnectionEventCallbackChain_t connectionCallChain; + DisconnectionEventCallbackChain_t disconnectionCallChain; private: /* Disallow copy and assignment. */
--- a/ble/GattClient.h Thu Nov 26 14:51:22 2015 +0000 +++ b/ble/GattClient.h Thu Nov 26 14:51:22 2015 +0000 @@ -23,18 +23,23 @@ #include "GattCallbackParamTypes.h" +#include "CallChainOfFunctionPointersWithContext.h" + class GattClient { public: - typedef void (*ReadCallback_t)(const GattReadCallbackParams *params); + typedef FunctionPointerWithContext<const GattReadCallbackParams*> ReadCallback_t; + typedef CallChainOfFunctionPointersWithContext<const GattReadCallbackParams*> ReadCallbackChain_t; enum WriteOp_t { GATT_OP_WRITE_REQ = 0x01, /**< Write request. */ GATT_OP_WRITE_CMD = 0x02, /**< Write command. */ }; - typedef void (*WriteCallback_t)(const GattWriteCallbackParams *params); + typedef FunctionPointerWithContext<const GattWriteCallbackParams*> WriteCallback_t; + typedef CallChainOfFunctionPointersWithContext<const GattWriteCallbackParams*> WriteCallbackChain_t; - typedef void (*HVXCallback_t)(const GattHVXCallbackParams *params); + typedef FunctionPointerWithContext<const GattHVXCallbackParams*> HVXCallback_t; + typedef CallChainOfFunctionPointersWithContext<const GattHVXCallbackParams*> HVXCallbackChain_t; /* * The following functions are meant to be overridden in the platform-specific sub-class. @@ -241,18 +246,42 @@ /* Event callback handlers. */ public: /** - * Set up a callback for read response events. + * Set up a callback for read response events. + * It is possible to remove registered callbacks using + * onDataRead().detach(callbackToRemove) */ void onDataRead(ReadCallback_t callback) { - onDataReadCallback = callback; + onDataReadCallbackChain.add(callback); + } + + /** + * @brief provide access to the callchain of read callbacks + * It is possible to register callbacks using onDataRead().add(callback); + * It is possible to unregister callbacks using onDataRead().detach(callback) + * @return The read callbacks chain + */ + ReadCallbackChain_t& onDataRead() { + return onDataReadCallbackChain; } /** * Set up a callback for write response events. + * It is possible to remove registered callbacks using + * onDataWritten().detach(callbackToRemove). * @Note: Write commands (issued using writeWoResponse) don't generate a response. */ void onDataWritten(WriteCallback_t callback) { - onDataWriteCallback = callback; + onDataWriteCallbackChain.add(callback); + } + + /** + * @brief provide access to the callchain of data written callbacks + * It is possible to register callbacks using onDataWritten().add(callback); + * It is possible to unregister callbacks using onDataWritten().detach(callback) + * @return The data written callbacks chain + */ + WriteCallbackChain_t& onDataWritten() { + return onDataWriteCallbackChain; } /** @@ -279,9 +308,21 @@ * Set up a callback for when the GATT client receives an update event * corresponding to a change in the value of a characteristic on the remote * GATT server. + * It is possible to remove registered callbacks using onHVX().detach(callbackToRemove). */ void onHVX(HVXCallback_t callback) { - onHVXCallback = callback; + onHVXCallbackChain.add(callback); + } + + + /** + * @brief provide access to the callchain of HVX callbacks + * It is possible to register callbacks using onHVX().add(callback); + * It is possible to unregister callbacks using onHVX().detach(callback) + * @return The HVX callbacks chain + */ + HVXCallbackChain_t& onHVX() { + return onHVXCallbackChain; } protected: @@ -292,27 +333,23 @@ /* Entry points for the underlying stack to report events back to the user. */ public: void processReadResponse(const GattReadCallbackParams *params) { - if (onDataReadCallback) { - onDataReadCallback(params); - } + onDataReadCallbackChain(params); } void processWriteResponse(const GattWriteCallbackParams *params) { - if (onDataWriteCallback) { - onDataWriteCallback(params); - } + onDataWriteCallbackChain(params); } void processHVXEvent(const GattHVXCallbackParams *params) { - if (onHVXCallback) { - onHVXCallback(params); + if (onHVXCallbackChain) { + onHVXCallbackChain(params); } } protected: - ReadCallback_t onDataReadCallback; - WriteCallback_t onDataWriteCallback; - HVXCallback_t onHVXCallback; + ReadCallbackChain_t onDataReadCallbackChain; + WriteCallbackChain_t onDataWriteCallbackChain; + HVXCallbackChain_t onHVXCallbackChain; private: /* Disallow copy and assignment. */
--- a/ble/GattServer.h Thu Nov 26 14:51:22 2015 +0000 +++ b/ble/GattServer.h Thu Nov 26 14:51:22 2015 +0000 @@ -26,9 +26,18 @@ class GattServer { public: + /* Event callback handlers. */ - typedef void (*EventCallback_t)(GattAttribute::Handle_t attributeHandle); - typedef void (*ServerEventCallback_t)(void); /**< Independent of any particular attribute. */ + typedef FunctionPointerWithContext<unsigned> DataSentCallback_t; + typedef CallChainOfFunctionPointersWithContext<unsigned> DataSentCallbackChain_t; + + typedef FunctionPointerWithContext<const GattWriteCallbackParams*> DataWrittenCallback_t; + typedef CallChainOfFunctionPointersWithContext<const GattWriteCallbackParams*> DataWrittenCallbackChain_t; + + typedef FunctionPointerWithContext<const GattReadCallbackParams*> DataReadCallback_t; + typedef CallChainOfFunctionPointersWithContext<const GattReadCallbackParams *> DataReadCallbackChain_t; + + typedef FunctionPointerWithContext<GattAttribute::Handle_t> EventCallback_t; protected: GattServer() : @@ -238,13 +247,20 @@ * @Note: It is also possible to set up a callback into a member function of * some object. */ - void onDataSent(void (*callback)(unsigned count)) {dataSentCallChain.add(callback);} + void onDataSent(const DataSentCallback_t& callback) {dataSentCallChain.add(callback);} template <typename T> void onDataSent(T *objPtr, void (T::*memberPtr)(unsigned count)) { dataSentCallChain.add(objPtr, memberPtr); } /** + * @brief get the callback chain called when the event DATA_EVENT is triggered. + */ + DataSentCallbackChain_t& onDataSent() { + return dataSentCallChain; + } + + /** * Set up a callback for when an attribute has its value updated by or at the * connected peer. For a peripheral, this callback is triggered when the local * GATT server has an attribute updated by a write command from the peer. @@ -258,14 +274,26 @@ * * @Note: It is also possible to set up a callback into a member function of * some object. + * + * @Note It is possible to unregister a callback using onDataWritten().detach(callback) */ - void onDataWritten(void (*callback)(const GattWriteCallbackParams *eventDataP)) {dataWrittenCallChain.add(callback);} + void onDataWritten(const DataWrittenCallback_t& callback) {dataWrittenCallChain.add(callback);} template <typename T> void onDataWritten(T *objPtr, void (T::*memberPtr)(const GattWriteCallbackParams *context)) { dataWrittenCallChain.add(objPtr, memberPtr); } /** + * @brief provide access to the callchain of data written event callbacks + * It is possible to register callbacks using onDataWritten().add(callback); + * It is possible to unregister callbacks using onDataWritten().detach(callback) + * @return The data written event callbacks chain + */ + DataWrittenCallbackChain_t& onDataWritten() { + return dataWrittenCallChain; + } + + /** * Setup a callback to be invoked on the peripheral when an attribute is * being read by a remote client. * @@ -281,10 +309,12 @@ * @Note: It is also possible to set up a callback into a member function of * some object. * + * @Note It is possible to unregister a callback using onDataRead().detach(callback) + * * @return BLE_ERROR_NOT_IMPLEMENTED if this functionality isn't available; * else BLE_ERROR_NONE. */ - ble_error_t onDataRead(void (*callback)(const GattReadCallbackParams *eventDataP)) { + ble_error_t onDataRead(const DataReadCallback_t& callback) { if (!isOnDataReadAvailable()) { return BLE_ERROR_NOT_IMPLEMENTED; } @@ -303,6 +333,16 @@ } /** + * @brief provide access to the callchain of data read event callbacks + * It is possible to register callbacks using onDataRead().add(callback); + * It is possible to unregister callbacks using onDataRead().detach(callback) + * @return The data read event callbacks chain + */ + DataReadCallbackChain_t& onDataRead() { + return dataReadCallChain; + } + + /** * Set up a callback for when notifications or indications are enabled for a * characteristic on the local GATT server. */ @@ -323,15 +363,11 @@ /* Entry points for the underlying stack to report events back to the user. */ protected: void handleDataWrittenEvent(const GattWriteCallbackParams *params) { - if (dataWrittenCallChain.hasCallbacksAttached()) { - dataWrittenCallChain.call(params); - } + dataWrittenCallChain.call(params); } void handleDataReadEvent(const GattReadCallbackParams *params) { - if (dataReadCallChain.hasCallbacksAttached()) { - dataReadCallChain.call(params); - } + dataReadCallChain.call(params); } void handleEvent(GattServerEvents::gattEvent_e type, GattAttribute::Handle_t attributeHandle) { @@ -357,9 +393,7 @@ } void handleDataSentEvent(unsigned count) { - if (dataSentCallChain.hasCallbacksAttached()) { - dataSentCallChain.call(count); - } + dataSentCallChain.call(count); } protected: @@ -367,12 +401,12 @@ uint8_t characteristicCount; private: - CallChainOfFunctionPointersWithContext<unsigned> dataSentCallChain; - CallChainOfFunctionPointersWithContext<const GattWriteCallbackParams *> dataWrittenCallChain; - CallChainOfFunctionPointersWithContext<const GattReadCallbackParams *> dataReadCallChain; - EventCallback_t updatesEnabledCallback; - EventCallback_t updatesDisabledCallback; - EventCallback_t confirmationReceivedCallback; + DataSentCallbackChain_t dataSentCallChain; + DataWrittenCallbackChain_t dataWrittenCallChain; + DataReadCallbackChain_t dataReadCallChain; + EventCallback_t updatesEnabledCallback; + EventCallback_t updatesDisabledCallback; + EventCallback_t confirmationReceivedCallback; private: /* Disallow copy and assignment. */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ble/SafeBool.h Thu Nov 26 14:51:22 2015 +0000 @@ -0,0 +1,114 @@ +/* mbed Microcontroller Library + * Copyright (c) 2006-2013 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef BLE_API_SAFE_BOOL_H_ +#define BLE_API_SAFE_BOOL_H_ + +//safe bool idiom, see : http://www.artima.com/cppsource/safebool.html + +namespace SafeBool_ { +/** + * @brief Base class for all intances of SafeBool, + * This base class reduce instantiation of trueTag function + */ +class base { + template<typename> + friend class SafeBool; + +protected: + //the bool type is a pointer to method which can be used in boolean context + typedef void (base::*BoolType_t)() const; + + // non implemented call, use to disallow conversion between unrelated types + void invalidTag() const; + + // member function which indicate true value + void trueTag() const {} +}; + + +} + +/** + * @brief template class SafeBool use CRTP to made boolean conversion easy and correct. + * Derived class should implement the function bool toBool() const to make this work. Inheritance + * should be public. + * + * @tparam T Type of the derived class + * + * \code + * + * class A : public SafeBool<A> { + * public: + * + * // boolean conversion + * bool toBool() { + * + * } + * }; + * + * class B : public SafeBool<B> { + * public: + * + * // boolean conversion + * bool toBool() const { + * + * } + * }; + * + * A a; + * B b; + * + * // will compile + * if(a) { + * + * } + * + * // compilation error + * if(a == b) { + * + * } + * + * + * \endcode + */ +template <typename T> +class SafeBool : public SafeBool_::base { +public: + /** + * bool operator implementation, derived class has to provide bool toBool() const function. + */ + operator BoolType_t() const { + return (static_cast<const T*>(this))->toBool() + ? &SafeBool<T>::trueTag : 0; + } +}; + +//Avoid conversion to bool between different classes +template <typename T, typename U> +void operator==(const SafeBool<T>& lhs,const SafeBool<U>& rhs) { + lhs.invalidTag(); +// return false; +} + +//Avoid conversion to bool between different classes +template <typename T,typename U> +void operator!=(const SafeBool<T>& lhs,const SafeBool<U>& rhs) { + lhs.invalidTag(); +// return false; +} + +#endif /* BLE_API_SAFE_BOOL_H_ */ \ No newline at end of file
--- a/ble/ServiceDiscovery.h Thu Nov 26 14:51:22 2015 +0000 +++ b/ble/ServiceDiscovery.h Thu Nov 26 14:51:22 2015 +0000 @@ -38,7 +38,7 @@ * framework. The application can safely make a persistent shallow-copy of * this object to work with the service beyond the callback. */ - typedef void (*ServiceCallback_t)(const DiscoveredService *); + typedef FunctionPointerWithContext<const DiscoveredService *> ServiceCallback_t; /** * Callback type for when a matching characteristic is found during service- @@ -48,12 +48,12 @@ * framework. The application can safely make a persistent shallow-copy of * this object to work with the characteristic beyond the callback. */ - typedef void (*CharacteristicCallback_t)(const DiscoveredCharacteristic *); + typedef FunctionPointerWithContext<const DiscoveredCharacteristic *> CharacteristicCallback_t; /** * Callback type for when serviceDiscovery terminates. */ - typedef void (*TerminationCallback_t)(Gap::Handle_t connectionHandle); + typedef FunctionPointerWithContext<Gap::Handle_t> TerminationCallback_t; public: /**
--- a/ble/blecommon.h Thu Nov 26 14:51:22 2015 +0000 +++ b/ble/blecommon.h Thu Nov 26 14:51:22 2015 +0000 @@ -22,9 +22,7 @@ #endif -/** @defgroup BLE_UUID_VALUES assigned values for BLE UUIDs. - * @{ */ -/* Generic UUIDs, applicable to all services. */ +/*! @brief Assigned values for BLE UUIDs. */ enum { BLE_UUID_UNKNOWN = 0x0000, /**< Reserved UUID. */ BLE_UUID_SERVICE_PRIMARY = 0x2800, /**< Primary Service. */ @@ -50,11 +48,10 @@ BLE_UUID_GAP_CHARACTERISTIC_RECONN_ADDR = 0x2A03, /**< Reconnection Address Characteristic. */ BLE_UUID_GAP_CHARACTERISTIC_PPCP = 0x2A04, /**< Peripheral Preferred Connection Parameters Characteristic. */ }; -/** @} */ -/** @defgroup BLE_APPEARANCES Bluetooth appearance values. +/*! Bluetooth appearance values. * @note Retrieved from http://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.gap.appearance.xml - * @{ */ + */ enum { BLE_APPEARANCE_UNKNOWN = 0, /**< Unknown. */ BLE_APPEARANCE_GENERIC_PHONE = 64, /**< Generic Phone. */ @@ -106,13 +103,9 @@ BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_POD = 5187, /**< Location Pod (Outdoor Sports Activity subtype). */ BLE_APPEARANCE_OUTDOOR_SPORTS_ACT_LOC_AND_NAV_POD = 5188, /**< Location and Navigation Pod (Outdoor Sports Activity subtype). */ }; -/** @} */ + -/**************************************************************************/ -/*! - \brief Error codes for the BLE API -*/ -/**************************************************************************/ +/*! @brief Error codes for the BLE API. */ enum ble_error_t { BLE_ERROR_NONE = 0, /**< No error. */ BLE_ERROR_BUFFER_OVERFLOW = 1, /**< The requested action would cause a buffer overflow and has been aborted. */
--- a/ble/services/BatteryService.h Thu Nov 26 14:51:22 2015 +0000 +++ b/ble/services/BatteryService.h Thu Nov 26 14:51:22 2015 +0000 @@ -21,8 +21,8 @@ /** * @class BatteryService -* @brief BLE Battery Service. This service displays the battery level from 0%->100% represented as a 8bit number.<br> -* Service: https://developer.bluetooth.org/gatt/services/Pages/ServiceViewer.aspx?u=org.bluetooth.service.battery_service.xml <br> +* @brief BLE Battery Service. This service displays the battery level from 0% to 100%, represented as an 8bit number. +* Service: https://developer.bluetooth.org/gatt/services/Pages/ServiceViewer.aspx?u=org.bluetooth.service.battery_service.xml * Battery Level Char: https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.battery_level.xml */ class BatteryService { @@ -45,11 +45,11 @@ } /** - * @brief Update the battery level with a new value. Valid values range from - * 0..100. Anything outside this range will be ignored. + * @brief Update the battery level with a new value. [Valid values lie between 0 and 100]; + * anything outside this range will be ignored. * * @param newLevel - * update to battery level. + * Update to battery level. */ void updateBatteryLevel(uint8_t newLevel) { batteryLevel = newLevel;
--- a/ble/services/DFUService.h Thu Nov 26 14:51:22 2015 +0000 +++ b/ble/services/DFUService.h Thu Nov 26 14:51:22 2015 +0000 @@ -41,20 +41,20 @@ class DFUService { public: /** - * @brief Signature for the handover callback. The application may provide such a - * callback when setting up the DFU service, in which case it will be + * @brief Signature for the handover callback. The application may provide this + * callback when setting up the DFU service. The callback is then * invoked before handing control over to the bootloader. */ typedef void (*ResetPrepare_t)(void); public: /** - * @brief Adds Device Firmware Update service to an existing ble object. + * @brief Adds Device Firmware Update Service to an existing BLE object. * * @param[ref] _ble * BLE object for the underlying controller. * @param[in] _handoverCallback - * Application specific handover callback. + * Application-specific handover callback. */ DFUService(BLE &_ble, ResetPrepare_t _handoverCallback = NULL) : ble(_ble), @@ -63,12 +63,12 @@ GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE_WITHOUT_RESPONSE), controlBytes(), packetBytes() { - static bool serviceAdded = false; /* We should only ever need to add the DFU service once. */ + static bool serviceAdded = false; /* We only add the DFU service once. */ if (serviceAdded) { return; } - /* Set an initial value for control bytes so that the application's DFUService can + /* Set an initial value for control bytes, so that the application's DFU service can * be distinguished from the real DFU service provided by the bootloader. */ controlBytes[0] = 0xFF; controlBytes[1] = 0xFF; @@ -84,7 +84,7 @@ } /** - * @brief get the handle for the value attribute of the control characteristic. + * @brief Get the handle for the value attribute of the control characteristic. */ uint16_t getControlHandle(void) const { return controlPoint.getValueHandle(); @@ -92,7 +92,7 @@ /** * @brief This callback allows the DFU service to receive the initial trigger to - * handover control to the bootloader; but first the application is given a + * hand control over to the bootloader. First, the application is given a * chance to clean up. * * @param[in] params @@ -100,7 +100,7 @@ */ virtual void onDataWritten(const GattWriteCallbackParams *params) { if (params->handle == controlPoint.getValueHandle()) { - /* At present, writing anything will do the trick--this needs to be improved. */ + /* At present, writing anything will do the trick - this needs to be improved. */ if (handoverCallback) { handoverCallback(); } @@ -124,22 +124,22 @@ protected: BLE &ble; - /**< Writing to the control characteristic triggers the handover to dfu- - * bootloader. At present, writing anything will do the trick--this needs + /**< Writing to the control characteristic triggers the handover to DFU + * bootloader. At present, writing anything will do the trick - this needs * to be improved. */ WriteOnlyArrayGattCharacteristic<uint8_t, SIZEOF_CONTROL_BYTES> controlPoint; - /**< The packet characteristic in this service doesn't do anything meaningful, but - * is only a placeholder to mimic the corresponding characteristic in the + /**< The packet characteristic in this service doesn't do anything meaningful; + * it is only a placeholder to mimic the corresponding characteristic in the * actual DFU service implemented by the bootloader. Without this, some - * FOTA clients might get confused as service definitions change after + * FOTA clients might get confused, because service definitions change after * handing control over to the bootloader. */ GattCharacteristic packet; uint8_t controlBytes[SIZEOF_CONTROL_BYTES]; uint8_t packetBytes[SIZEOF_PACKET_BYTES]; - static ResetPrepare_t handoverCallback; /**< application specific handover callback. */ + static ResetPrepare_t handoverCallback; /**< Application-specific handover callback. */ }; #endif /* #ifndef __BLE_DFU_SERVICE_H__*/
--- a/ble/services/DeviceInformationService.h Thu Nov 26 14:51:22 2015 +0000 +++ b/ble/services/DeviceInformationService.h Thu Nov 26 14:51:22 2015 +0000 @@ -21,41 +21,30 @@ /** * @class DeviceInformationService -* @brief BLE Device Information Service <br> -* Service: https://developer.bluetooth.org/gatt/services/Pages/ServiceViewer.aspx?u=org.bluetooth.service.device_information.xml <br> +* @brief BLE Device Information Service +* Service: https://developer.bluetooth.org/gatt/services/Pages/ServiceViewer.aspx?u=org.bluetooth.service.device_information.xml * Manufacturer Name String Char: https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.manufacturer_name_string.xml */ class DeviceInformationService { public: /** - * @brief Device Information Service Constructor. + * @brief Device Information Service Constructor: copies device-specific information + * into the BLE stack. * * @param[ref] _ble * BLE object for the underlying controller. * @param[in] manufacturersName - * This characteristic represents the name of the - * manufacturer of the device. The name is copied into the - * BLE stack during this constructor. + * The name of the manufacturer of the device. * @param[in] modelNumber - * This characteristic represents the model number that is - * assigned by the device vendor. The value is copied into - * the BLE stack during this constructor. + * The model number that is assigned by the device vendor. * @param[in] serialNumber - * This characteristic represents the serial number for a - * particular instance of the device. The value is copied - * into the BLE stack during this constructor. + * The serial number for a particular instance of the device. * @param[in] hardwareRevision - * This characteristic represents the hardware revision for - * the hardware within the device. The value is copied - * into the BLE stack during this constructor. + * The hardware revision for the hardware within the device. * @param[in] firmwareRevision - * This characteristic represents the firmware revision for - * the firmware within the device. The value is copied - * into the BLE stack during this constructor. + * The device's firmware version. * @param[in] softwareRevision - * This characteristic represents the software revision for - * the software within the device. The value is copied - * into the BLE stack during this constructor. + * The device's software version. */ DeviceInformationService(BLE &_ble, const char *manufacturersName = NULL, @@ -67,36 +56,36 @@ ble(_ble), manufacturersNameStringCharacteristic(GattCharacteristic::UUID_MANUFACTURER_NAME_STRING_CHAR, (uint8_t *)manufacturersName, - (manufacturersName != NULL) ? strlen(manufacturersName) : 0, /* minLength */ - (manufacturersName != NULL) ? strlen(manufacturersName) : 0, /* maxLength */ + (manufacturersName != NULL) ? strlen(manufacturersName) : 0, /* Min length */ + (manufacturersName != NULL) ? strlen(manufacturersName) : 0, /* Max length */ GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ), modelNumberStringCharacteristic(GattCharacteristic::UUID_MODEL_NUMBER_STRING_CHAR, (uint8_t *)modelNumber, - (modelNumber != NULL) ? strlen(modelNumber) : 0, /* minLength */ - (modelNumber != NULL) ? strlen(modelNumber) : 0, /* maxLength */ + (modelNumber != NULL) ? strlen(modelNumber) : 0, /* Min length */ + (modelNumber != NULL) ? strlen(modelNumber) : 0, /* Max length */ GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ), serialNumberStringCharacteristic(GattCharacteristic::UUID_SERIAL_NUMBER_STRING_CHAR, (uint8_t *)serialNumber, - (serialNumber != NULL) ? strlen(serialNumber) : 0, /* minLength */ - (serialNumber != NULL) ? strlen(serialNumber) : 0, /* maxLength */ + (serialNumber != NULL) ? strlen(serialNumber) : 0, /* Min length */ + (serialNumber != NULL) ? strlen(serialNumber) : 0, /* Max length */ GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ), hardwareRevisionStringCharacteristic(GattCharacteristic::UUID_HARDWARE_REVISION_STRING_CHAR, (uint8_t *)hardwareRevision, - (hardwareRevision != NULL) ? strlen(hardwareRevision) : 0, /* minLength */ - (hardwareRevision != NULL) ? strlen(hardwareRevision) : 0, /* maxLength */ + (hardwareRevision != NULL) ? strlen(hardwareRevision) : 0, /* Min length */ + (hardwareRevision != NULL) ? strlen(hardwareRevision) : 0, /* Max length */ GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ), firmwareRevisionStringCharacteristic(GattCharacteristic::UUID_FIRMWARE_REVISION_STRING_CHAR, (uint8_t *)firmwareRevision, - (firmwareRevision != NULL) ? strlen(firmwareRevision) : 0, /* minLength */ - (firmwareRevision != NULL) ? strlen(firmwareRevision) : 0, /* maxLength */ + (firmwareRevision != NULL) ? strlen(firmwareRevision) : 0, /* Min length */ + (firmwareRevision != NULL) ? strlen(firmwareRevision) : 0, /* Max length */ GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ), softwareRevisionStringCharacteristic(GattCharacteristic::UUID_SOFTWARE_REVISION_STRING_CHAR, (uint8_t *)softwareRevision, - (softwareRevision != NULL) ? strlen(softwareRevision) : 0, /* minLength */ - (softwareRevision != NULL) ? strlen(softwareRevision) : 0, /* maxLength */ + (softwareRevision != NULL) ? strlen(softwareRevision) : 0, /* Min length */ + (softwareRevision != NULL) ? strlen(softwareRevision) : 0, /* Max length */ GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ) { - static bool serviceAdded = false; /* We should only ever need to add the information service once. */ + static bool serviceAdded = false; /* We only add the information service once. */ if (serviceAdded) { return; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ble/services/EddystoneConfigService.h Thu Nov 26 14:51:22 2015 +0000 @@ -0,0 +1,542 @@ +/* mbed Microcontroller Library + * Copyright (c) 2006-2013 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SERVICES_EDDYSTONE_BEACON_CONFIG_SERVICE_H_ +#define SERVICES_EDDYSTONE_BEACON_CONFIG_SERVICE_H_ + +#warning ble/services/EddystoneConfigService.h is deprecated. Please use the example in 'github.com/ARMmbed/ble-examples/tree/master/BLE_EddystoneService'. + +#include "mbed.h" +#include "ble/BLE.h" +#include "ble/services/EddystoneService.h" + +#define UUID_URI_BEACON(FIRST, SECOND) { \ + 0xee, 0x0c, FIRST, SECOND, 0x87, 0x86, 0x40, 0xba, \ + 0xab, 0x96, 0x99, 0xb9, 0x1a, 0xc9, 0x81, 0xd8, \ +} + +static const uint8_t UUID_URI_BEACON_SERVICE[] = UUID_URI_BEACON(0x20, 0x80); +static const uint8_t UUID_LOCK_STATE_CHAR[] = UUID_URI_BEACON(0x20, 0x81); +static const uint8_t UUID_LOCK_CHAR[] = UUID_URI_BEACON(0x20, 0x82); +static const uint8_t UUID_UNLOCK_CHAR[] = UUID_URI_BEACON(0x20, 0x83); +static const uint8_t UUID_URI_DATA_CHAR[] = UUID_URI_BEACON(0x20, 0x84); +static const uint8_t UUID_FLAGS_CHAR[] = UUID_URI_BEACON(0x20, 0x85); +static const uint8_t UUID_ADV_POWER_LEVELS_CHAR[] = UUID_URI_BEACON(0x20, 0x86); +static const uint8_t UUID_TX_POWER_MODE_CHAR[] = UUID_URI_BEACON(0x20, 0x87); +static const uint8_t UUID_BEACON_PERIOD_CHAR[] = UUID_URI_BEACON(0x20, 0x88); +static const uint8_t UUID_RESET_CHAR[] = UUID_URI_BEACON(0x20, 0x89); +extern const uint8_t BEACON_EDDYSTONE[2]; + +/** +* @class EddystoneConfigService +* @brief Eddystone Configuration Service. Can be used to set URL, adjust power levels, and set flags. +* See https://github.com/google/eddystone +* +*/ +class EddystoneConfigService +{ +public: + /** + * @brief Transmission Power Modes for UriBeacon + */ + enum { + TX_POWER_MODE_LOWEST, + TX_POWER_MODE_LOW, + TX_POWER_MODE_MEDIUM, + TX_POWER_MODE_HIGH, + NUM_POWER_MODES + }; + + static const unsigned ADVERTISING_INTERVAL_MSEC = 1000; // Advertising interval for config service. + static const unsigned SERVICE_DATA_MAX = 31; // Maximum size of service data in ADV packets + + typedef uint8_t Lock_t[16]; /* 128 bits */ + typedef int8_t PowerLevels_t[NUM_POWER_MODES]; + + // There are currently 3 subframes defined, URI, UID, and TLM +#define EDDYSTONE_MAX_FRAMETYPE 3 + static const unsigned URI_DATA_MAX = 18; + typedef uint8_t UriData_t[URI_DATA_MAX]; + + // UID Frame Type subfields + static const size_t UID_NAMESPACEID_SIZE = 10; + typedef uint8_t UIDNamespaceID_t[UID_NAMESPACEID_SIZE]; + static const size_t UID_INSTANCEID_SIZE = 6; + typedef uint8_t UIDInstanceID_t[UID_INSTANCEID_SIZE]; + + // Eddystone Frame Type ID + static const uint8_t FRAME_TYPE_UID = 0x00; + static const uint8_t FRAME_TYPE_URL = 0x10; + static const uint8_t FRAME_TYPE_TLM = 0x20; + + static const uint8_t FRAME_SIZE_TLM = 14; // TLM frame is a constant 14Bytes + static const uint8_t FRAME_SIZE_UID = 20; // includes RFU bytes + + struct Params_t { + // Config Data + bool isConfigured; // Flag for configuration being complete, + // True = configured, false = not configured. Reset at instantiation, used for external callbacks. + uint8_t lockedState; + Lock_t lock; + uint8_t flags; + PowerLevels_t advPowerLevels; // Current value of AdvertisedPowerLevels + uint8_t txPowerMode; // Firmware power levels used with setTxPower() + uint16_t beaconPeriod; + // TLM Frame Data + uint8_t tlmVersion; // version of TLM packet + bool tlmEnabled; + float tlmBeaconPeriod; // how often to broadcat TLM frame, in seconds. + // URI Frame Data + uint8_t uriDataLength; + UriData_t uriData; + bool uriEnabled; + float uriBeaconPeriod; // how often to broadcast URIFrame, in seconds. + // UID Frame Data + UIDNamespaceID_t uidNamespaceID; // UUID type, Namespace ID, 10B + UIDInstanceID_t uidInstanceID; // UUID type, Instance ID, 6B + bool uidEnabled; + float uidBeaconPeriod; // how often to broadcast UID Frame, in seconds. + }; + + /** + * @param[ref] ble + * BLEDevice object for the underlying controller. + * @param[in/out] paramsIn + * Reference to application-visible beacon state, loaded + * from persistent storage at startup. + * @param[in] defaultAdvPowerLevelsIn + * Default power-levels array; applies only if the resetToDefaultsFlag is true. + */ + EddystoneConfigService(BLEDevice &bleIn, + Params_t ¶msIn, + PowerLevels_t &defaultAdvPowerLevelsIn, + PowerLevels_t &radioPowerLevelsIn) : + ble(bleIn), + params(paramsIn), // Initialize URL Data + defaultAdvPowerLevels(defaultAdvPowerLevelsIn), + radioPowerLevels(radioPowerLevelsIn), + initSucceeded(false), + resetFlag(), + defaultUidNamespaceID(), // Initialize UID Data + defaultUidInstanceID(), + defaultUidPower(defaultAdvPowerLevelsIn[params.txPowerMode]), + uidIsSet(false), + defaultUriDataLength(), + defaultUriData(), + defaultUrlPower(defaultAdvPowerLevelsIn[params.txPowerMode]), + urlIsSet(false), + tlmIsSet(false), + lockedStateChar(UUID_LOCK_STATE_CHAR, ¶ms.lockedState), + lockChar(UUID_LOCK_CHAR, ¶ms.lock), + uriDataChar(UUID_URI_DATA_CHAR, params.uriData, 0, URI_DATA_MAX, + GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE), + unlockChar(UUID_UNLOCK_CHAR, ¶ms.lock), + flagsChar(UUID_FLAGS_CHAR, ¶ms.flags), + advPowerLevelsChar(UUID_ADV_POWER_LEVELS_CHAR, ¶ms.advPowerLevels), + txPowerModeChar(UUID_TX_POWER_MODE_CHAR, ¶ms.txPowerMode), + beaconPeriodChar(UUID_BEACON_PERIOD_CHAR, ¶ms.beaconPeriod), + resetChar(UUID_RESET_CHAR, &resetFlag) { + // set eddystone as not configured yet. Used to exit config before timeout if GATT services are written to. + params.isConfigured = false; + + lockChar.setWriteAuthorizationCallback(this, &EddystoneConfigService::lockAuthorizationCallback); + unlockChar.setWriteAuthorizationCallback(this, &EddystoneConfigService::unlockAuthorizationCallback); + uriDataChar.setWriteAuthorizationCallback(this, &EddystoneConfigService::uriDataWriteAuthorizationCallback); + flagsChar.setWriteAuthorizationCallback(this, &EddystoneConfigService::basicAuthorizationCallback<uint8_t>); + advPowerLevelsChar.setWriteAuthorizationCallback(this, &EddystoneConfigService::basicAuthorizationCallback<PowerLevels_t>); + txPowerModeChar.setWriteAuthorizationCallback(this, &EddystoneConfigService::powerModeAuthorizationCallback); + beaconPeriodChar.setWriteAuthorizationCallback(this, &EddystoneConfigService::basicAuthorizationCallback<uint16_t>); + resetChar.setWriteAuthorizationCallback(this, &EddystoneConfigService::basicAuthorizationCallback<uint8_t>); + + static GattCharacteristic *charTable[] = { + &lockedStateChar, &lockChar, &unlockChar, &uriDataChar, + &flagsChar, &advPowerLevelsChar, &txPowerModeChar, &beaconPeriodChar, &resetChar + }; + + GattService configService(UUID_URI_BEACON_SERVICE, charTable, sizeof(charTable) / sizeof(GattCharacteristic *)); + + ble.addService(configService); + ble.onDataWritten(this, &EddystoneConfigService::onDataWrittenCallback); + } + + /** + * @brief Start EddystoneConfig advertising. This function should be called + * after the EddystoneConfig constructor and after all the frames have been added. + * + * @paramsP[in] resetToDefaultsFlag + * Applies to the state of the 'paramsIn' parameter. + * If true, it indicates that paramsIn is potentially + * un-initialized, and default values should be used + * instead. Otherwise, paramsIn overrides the defaults. + */ + void start(bool resetToDefaultsFlag){ + INFO("reset to defaults flag = %d", resetToDefaultsFlag); + if (!resetToDefaultsFlag && (params.uriDataLength > URI_DATA_MAX)) { + INFO("Reset to Defaults triggered"); + resetToDefaultsFlag = true; + } + + if (resetToDefaultsFlag) { + resetToDefaults(); + } else { + updateCharacteristicValues(); + } + + setupEddystoneConfigAdvertisements(); /* Setup advertising for the configService. */ + initSucceeded = true; + } + + /* + * Check if eddystone initialized successfully + */ + bool initSuccessfully(void) const { + return initSucceeded; + } + + /* + * @brief Function to update the default values for the TLM frame. Only applied if Reset Defaults is applied. + * + * @param[in] tlmVersionIn Version of the TLM frame being used + * @param[in] advPeriodInMin how long between TLM frames being advertised, this is measured in minutes. + * + */ + void setDefaultTLMFrameData(uint8_t tlmVersionIn = 0, float advPeriodInSec = 60){ + DBG("Setting Default TLM Data, version = %d, advPeriodInMind= %f", tlmVersionIn, advPeriodInSec); + defaultTlmVersion = tlmVersionIn; + TlmBatteryVoltage = 0; + TlmBeaconTemp = 0x8000; + TlmPduCount = 0; + TlmTimeSinceBoot = 0; + defaultTlmAdvPeriod = advPeriodInSec; + tlmIsSet = true; // flag to add this to eddystone service when config is done + } + + /* + * @brief Function to update the default values for the URI frame. Only applied if Reset Defaults is applied. + * + * @param[in] uriIn url to advertise + * @param[in] advPeriod how long to advertise the url for, measured in number of ADV frames. + * + */ + void setDefaultURIFrameData(const char *uriIn, float advPeriod = 1){ + DBG("Setting Default URI Data"); + // Set URL Frame + EddystoneService::encodeURL(uriIn, defaultUriData, defaultUriDataLength); // encode URL to URL Formatting + if (defaultUriDataLength > URI_DATA_MAX) { + return; + } + INFO("\t URI input = %s : %d", uriIn, defaultUriDataLength); + INFO("\t default URI = %s : %d ", defaultUriData, defaultUriDataLength ); + defaultUriAdvPeriod = advPeriod; + urlIsSet = true; // flag to add this to eddystone service when config is done + } + + /* + * @brief Function to update the default values for the UID frame. Only applied if Reset Defaults is applied. + * + * @param[in] namespaceID 10Byte Namespace ID + * @param[in] instanceID 6Byte Instance ID + * @param[in] advPeriod how long to advertise the URL for, measured in the number of adv frames. + * + */ + void setDefaultUIDFrameData(UIDNamespaceID_t *namespaceID, UIDInstanceID_t *instanceID, float advPeriod = 10){ + //Set UID frame + DBG("Setting default UID Data"); + memcpy(defaultUidNamespaceID, namespaceID, UID_NAMESPACEID_SIZE); + memcpy(defaultUidInstanceID, instanceID, UID_INSTANCEID_SIZE); + defaultUidAdvPeriod = advPeriod; + uidIsSet = true; // flag to add this to eddystone service when config is done + } + + /* Start out by advertising the configService for a limited time after + * startup; and switch to the normal non-connectible beacon functionality + * afterwards. */ + void setupEddystoneConfigAdvertisements() { + const char DEVICE_NAME[] = "eddystone Config"; + + ble.clearAdvertisingPayload(); + + ble.accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE); + + // UUID is in different order in the ADV frame (!) + uint8_t reversedServiceUUID[sizeof(UUID_URI_BEACON_SERVICE)]; + for (unsigned int i = 0; i < sizeof(UUID_URI_BEACON_SERVICE); i++) { + reversedServiceUUID[i] = UUID_URI_BEACON_SERVICE[sizeof(UUID_URI_BEACON_SERVICE) - i - 1]; + } + ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_128BIT_SERVICE_IDS, reversedServiceUUID, sizeof(reversedServiceUUID)); + ble.accumulateAdvertisingPayload(GapAdvertisingData::GENERIC_TAG); + ble.accumulateScanResponse(GapAdvertisingData::COMPLETE_LOCAL_NAME, reinterpret_cast<const uint8_t *>(&DEVICE_NAME), sizeof(DEVICE_NAME)); + ble.accumulateScanResponse( + GapAdvertisingData::TX_POWER_LEVEL, + reinterpret_cast<uint8_t *>(&defaultAdvPowerLevels[EddystoneConfigService::TX_POWER_MODE_LOW]), + sizeof(uint8_t)); + + ble.setTxPower(radioPowerLevels[params.txPowerMode]); + ble.setDeviceName(reinterpret_cast<const uint8_t *>(&DEVICE_NAME)); + ble.setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED); + ble.setAdvertisingInterval(ADVERTISING_INTERVAL_MSEC); + } + + /* + * This function actually impliments the Eddystone Beacon service. It can be called with the help of the wrapper function + * to load saved config params, or it can be called explicitly to reset the eddystone beacon to hardcoded values on each reset. + * + */ + void setupEddystoneAdvertisements() { + DBG("Switching Config -> adv"); + // Save params to storage + extern void saveURIBeaconConfigParams(const Params_t *paramsP); /* forward declaration; necessary to avoid a circular dependency. */ + saveURIBeaconConfigParams(¶ms); + INFO("Saved Params to Memory.") + // Setup Eddystone Service + static EddystoneService eddyServ(ble, params.beaconPeriod, radioPowerLevels[params.txPowerMode]); + // Set configured frames (TLM,UID,URI...etc) + if (params.tlmEnabled) { + eddyServ.setTLMFrameData(params.tlmVersion, params.tlmBeaconPeriod); + } + if (params.uriEnabled) { + eddyServ.setURLFrameEncodedData(params.advPowerLevels[params.txPowerMode], (const char *) params.uriData, params.uriDataLength, params.uriBeaconPeriod); + } + if (params.uidEnabled) { + eddyServ.setUIDFrameData(params.advPowerLevels[params.txPowerMode], + (uint8_t *)params.uidNamespaceID, + (uint8_t *)params.uidInstanceID, + params.uidBeaconPeriod); + } + // Start Advertising the eddystone service. + eddyServ.start(); + } + +private: + /* + * This callback is invoked when a GATT client attempts to modify any of the + * characteristics of this service. Attempts to do so are also applied to + * the internal state of this service object. + */ + void onDataWrittenCallback(const GattWriteCallbackParams *writeParams) { + uint16_t handle = writeParams->handle; + + if (handle == lockChar.getValueHandle()) { + // Validated earlier + memcpy(params.lock, writeParams->data, sizeof(Lock_t)); + // Set the state to be locked by the lock code (note: zeros are a valid lock) + params.lockedState = true; + INFO("Device Locked"); + } else if (handle == unlockChar.getValueHandle()) { + // Validated earlier + params.lockedState = false; + INFO("Device Unlocked"); + } else if (handle == uriDataChar.getValueHandle()) { + params.uriDataLength = writeParams->len; + memset(params.uriData, 0x00, URI_DATA_MAX); // clear URI string + memcpy(params.uriData, writeParams->data, writeParams->len); // set URI string + params.uriEnabled = true; + INFO("URI = %s, URILen = %d", writeParams->data, writeParams->len); + } else if (handle == flagsChar.getValueHandle()) { + params.flags = *(writeParams->data); + INFO("flagsChar = 0x%x", params.flags); + } else if (handle == advPowerLevelsChar.getValueHandle()) { + memcpy(params.advPowerLevels, writeParams->data, sizeof(PowerLevels_t)); + INFO("PowerLevelsChar = %4x", params.advPowerLevels); + } else if (handle == txPowerModeChar.getValueHandle()) { + params.txPowerMode = *(writeParams->data); + INFO("TxPowerModeChar = %d", params.txPowerMode); + } else if (handle == beaconPeriodChar.getValueHandle()) { + params.beaconPeriod = *((uint16_t *)(writeParams->data)); + INFO("BeaconPeriod = %d", params.beaconPeriod); + + /* Re-map beaconPeriod to within permissible bounds if necessary. */ + if (params.beaconPeriod != 0) { + bool paramsUpdated = false; + if (params.beaconPeriod < ble.getMinAdvertisingInterval()) { + params.beaconPeriod = ble.getMinAdvertisingInterval(); + paramsUpdated = true; + } else if (params.beaconPeriod > ble.getMaxAdvertisingInterval()) { + params.beaconPeriod = ble.getMaxAdvertisingInterval(); + paramsUpdated = true; + } + if (paramsUpdated) { + ble.updateCharacteristicValue(beaconPeriodChar.getValueHandle(), reinterpret_cast<uint8_t *>(¶ms.beaconPeriod), sizeof(uint16_t)); + } + } + } else if (handle == resetChar.getValueHandle()) { + INFO("Reset triggered from Config Service, resetting to defaults"); + resetToDefaults(); + } + updateCharacteristicValues(); + params.isConfigured = true; // some configuration data has been passed, on disconnect switch to advertising mode. + } + + /* + * Reset the default values. + */ + void resetToDefaults(void) { + INFO("Resetting to defaults"); + // General + params.lockedState = false; + memset(params.lock, 0, sizeof(Lock_t)); + params.flags = 0x10; + memcpy(params.advPowerLevels, defaultAdvPowerLevels, sizeof(PowerLevels_t)); + params.txPowerMode = TX_POWER_MODE_LOW; + params.beaconPeriod = (uint16_t) defaultUriAdvPeriod * 1000; + + // TLM Frame + params.tlmVersion = defaultTlmVersion; + params.tlmBeaconPeriod = defaultTlmAdvPeriod; + params.tlmEnabled = tlmIsSet; + + // URL Frame + memcpy(params.uriData, defaultUriData, URI_DATA_MAX); + params.uriDataLength = defaultUriDataLength; + params.uriBeaconPeriod = defaultUriAdvPeriod; + params.uriEnabled = urlIsSet; + + // UID Frame + memcpy(params.uidNamespaceID, defaultUidNamespaceID, UID_NAMESPACEID_SIZE); + memcpy(params.uidInstanceID, defaultUidInstanceID, UID_INSTANCEID_SIZE); + params.uidBeaconPeriod = defaultUidAdvPeriod; + params.uidEnabled = uidIsSet; + + updateCharacteristicValues(); + } + + /* + * Internal helper function used to update the GATT database following any + * change to the internal state of the service object. + */ + void updateCharacteristicValues(void) { + ble.updateCharacteristicValue(lockedStateChar.getValueHandle(), ¶ms.lockedState, 1); + ble.updateCharacteristicValue(uriDataChar.getValueHandle(), params.uriData, params.uriDataLength); + ble.updateCharacteristicValue(flagsChar.getValueHandle(), ¶ms.flags, 1); + ble.updateCharacteristicValue(beaconPeriodChar.getValueHandle(), + reinterpret_cast<uint8_t *>(¶ms.beaconPeriod), sizeof(uint16_t)); + ble.updateCharacteristicValue(txPowerModeChar.getValueHandle(), ¶ms.txPowerMode, 1); + ble.updateCharacteristicValue(advPowerLevelsChar.getValueHandle(), + reinterpret_cast<uint8_t *>(params.advPowerLevels), sizeof(PowerLevels_t)); + } + +private: + void lockAuthorizationCallback(GattWriteAuthCallbackParams *authParams) { + if (params.lockedState) { + authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION; + } else if (authParams->len != sizeof(Lock_t)) { + authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH; + } else if (authParams->offset != 0) { + authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET; + } else { + authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS; + } + } + + void unlockAuthorizationCallback(GattWriteAuthCallbackParams *authParams) { + if ((!params.lockedState) && (authParams->len == sizeof(Lock_t))) { + authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS; + } else if (authParams->len != sizeof(Lock_t)) { + authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH; + } else if (authParams->offset != 0) { + authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET; + } else if (memcmp(authParams->data, params.lock, sizeof(Lock_t)) != 0) { + authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION; + } else { + authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS; + } + } + + void uriDataWriteAuthorizationCallback(GattWriteAuthCallbackParams *authParams) { + if (params.lockedState) { + authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION; + } else if (authParams->offset != 0) { + authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET; + } else { + authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS; + } + } + + void powerModeAuthorizationCallback(GattWriteAuthCallbackParams *authParams) { + if (params.lockedState) { + authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION; + } else if (authParams->len != sizeof(uint8_t)) { + authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH; + } else if (authParams->offset != 0) { + authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET; + } else if (*((uint8_t *)authParams->data) >= NUM_POWER_MODES) { + authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_WRITE_NOT_PERMITTED; + } else { + authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS; + } + } + + template <typename T> + void basicAuthorizationCallback(GattWriteAuthCallbackParams *authParams) { + if (params.lockedState) { + authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INSUF_AUTHORIZATION; + } else if (authParams->len != sizeof(T)) { + authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH; + } else if (authParams->offset != 0) { + authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET; + } else { + authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS; + } + } + + BLEDevice &ble; + Params_t ¶ms; + Ticker timeSinceBootTick; + Timeout switchFrame; + // Default value that is restored on reset + PowerLevels_t &defaultAdvPowerLevels; // this goes into the advertising frames (radio power measured at 1m from device) + PowerLevels_t &radioPowerLevels; // this configures the power levels of the radio + uint8_t lockedState; + bool initSucceeded; + uint8_t resetFlag; + bool switchFlag; + + //UID Default value that is restored on reset + UIDNamespaceID_t defaultUidNamespaceID; + UIDInstanceID_t defaultUidInstanceID; + float defaultUidAdvPeriod; + int8_t defaultUidPower; + uint16_t uidRFU; + bool uidIsSet; + + //URI Default value that is restored on reset + uint8_t defaultUriDataLength; + UriData_t defaultUriData; + int8_t defaultUrlPower; + float defaultUriAdvPeriod; + bool urlIsSet; + + //TLM Default value that is restored on reset + uint8_t defaultTlmVersion; + float defaultTlmAdvPeriod; + volatile uint16_t TlmBatteryVoltage; + volatile uint16_t TlmBeaconTemp; + volatile uint32_t TlmPduCount; + volatile uint32_t TlmTimeSinceBoot; + bool tlmIsSet; + + ReadOnlyGattCharacteristic<uint8_t> lockedStateChar; + WriteOnlyGattCharacteristic<Lock_t> lockChar; + GattCharacteristic uriDataChar; + WriteOnlyGattCharacteristic<Lock_t> unlockChar; + ReadWriteGattCharacteristic<uint8_t> flagsChar; + ReadWriteGattCharacteristic<PowerLevels_t> advPowerLevelsChar; + ReadWriteGattCharacteristic<uint8_t> txPowerModeChar; + ReadWriteGattCharacteristic<uint16_t> beaconPeriodChar; + WriteOnlyGattCharacteristic<uint8_t> resetChar; +}; + +#endif // SERVICES_EDDYSTONE_BEACON_CONFIG_SERVICE_H_ \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ble/services/EddystoneService.h Thu Nov 26 14:51:22 2015 +0000 @@ -0,0 +1,653 @@ +/* mbed Microcontroller Library + * Copyright (c) 2006-2015 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SERVICES_EDDYSTONEBEACON_H_ +#define SERVICES_EDDYSTONEBEACON_H_ + +#warning ble/services/EddystoneService.h is deprecated. Please use the example in 'github.com/ARMmbed/ble-examples/tree/master/BLE_EddystoneService'. + +#include "ble/BLE.h" +#include "mbed.h" +#include "CircularBuffer.h" +static const uint8_t BEACON_EDDYSTONE[] = {0xAA, 0xFE}; + +//Debug is disabled by default +#if 0 +#define DBG(MSG, ...) printf("[EddyStone: DBG]" MSG " \t[%s,%d]\r\n", \ + ## __VA_ARGS__, \ + __FILE__, \ + __LINE__); +#define WARN(MSG, ...) printf("[EddyStone: WARN]" MSG " \t[%s,%d]\r\n", \ + ## __VA_ARGS__, \ + __FILE__, \ + __LINE__); +#define ERR(MSG, ...) printf("[EddyStone: ERR]" MSG " \t[%s,%d]\r\n", \ + ## __VA_ARGS__, \ + __FILE__, \ + __LINE__); +#else // if 0 +#define DBG(x, ...) //wait_us(10); +#define WARN(x, ...) //wait_us(10); +#define ERR(x, ...) +#endif // if 0 + +#if 0 +#define INFO(x, ...) printf("[EddyStone: INFO]"x " \t[%s,%d]\r\n", \ + ## __VA_ARGS__, \ + __FILE__, \ + __LINE__); +#else // if 0 +#define INFO(x, ...) +#endif // if 0 + +/** +* @class Eddystone +* @brief Eddystone Configuration Service. Can be used to set URL, adjust power levels, and set flags. +* See https://github.com/google/eddystone +* +*/ +class EddystoneService +{ +public: + enum FrameTypes { + NONE, + url, + uid, + tlm + }; + + 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); + 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; + typedef uint8_t UIDNamespaceID_t[UID_NAMESPACEID_SIZE]; + static const int UID_INSTANCEID_SIZE = 6; + typedef uint8_t UIDInstanceID_t[UID_INSTANCEID_SIZE]; + + // Eddystone Frame Type ID + static const uint8_t FRAME_TYPE_UID = 0x00; + static const uint8_t FRAME_TYPE_URL = 0x10; + static const uint8_t FRAME_TYPE_TLM = 0x20; + + static const uint8_t FRAME_SIZE_TLM = 14; // TLM frame is a constant 14Bytes + static const uint8_t FRAME_SIZE_UID = 20; // includes RFU bytes + + /** + * Set Eddystone UID Frame information. + * + * @param[in] power TX Power in dB measured at 0 meters from the device. Range of -100 to +20 dB. + * @param[in] namespaceID 10B namespace ID + * @param[in] instanceID 6B instance ID + * @param[in] 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, + float uidAdvPeriodIn, + uint16_t RFU = 0x0000) { + if (0.0f == uidAdvPeriodIn) { + uidIsSet = false; + return; + } + if (power > 20) { + power = 20; + } + if (power < -100) { + power = -100; + } + + defaultUidPower = power; + memcpy(defaultUidNamespaceID, namespaceID, UID_NAMESPACEID_SIZE); + memcpy(defaultUidInstanceID, instanceID, UID_INSTANCEID_SIZE); + uidRFU = (uint16_t)RFU; // this is probably bad form, but it doesn't really matter yet. + uidAdvPeriod = uidAdvPeriodIn; + uidIsSet = true; // set toggle to advertise UID frames + } + + /* + * Construct UID frame from private variables + * @param[in/out] Data pointer to array to store constructed frame in + * @param[in] maxSize number of bytes left in array, effectively how much empty space is available to write to + * @return number of bytes used. negative number indicates error message. + */ + unsigned constructUIDFrame(uint8_t *Data, uint8_t maxSize) { + unsigned index = 0; + + Data[index++] = FRAME_TYPE_UID; // 1B Type + + if (defaultUidPower > 20) { + defaultUidPower = 20; // enforce range of vaild values. + } + if (defaultUidPower < -100) { + defaultUidPower = -100; + } + Data[index++] = defaultUidPower; // 1B Power @ 0meter + + DBG("UID NamespaceID = '0x"); + for (size_t x = 0; x < UID_NAMESPACEID_SIZE; x++) { // 10B Namespace ID + Data[index++] = defaultUidNamespaceID[x]; + DBG("%x,", defaultUidNamespaceID[x]); + } + DBG("'\r\n"); + + DBG("UID InstanceID = '0x"); + for (size_t 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); + } + DBG("construcUIDFrame %d, %d", maxSize, index); + return index; + } + + /** + * Set Eddystone URL Frame information. + * @param[in] power TX Power in dB measured at 0 meters from the device. + * @param[in] url URL to encode + * @param[in] 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 *urlIn, float urlAdvPeriodIn) { + if (0.0f == urlAdvPeriodIn) { + urlIsSet = false; + return false; + } + encodeURL(urlIn, defaultUriData, defaultUriDataLength); // encode URL to URL Formatting + if (defaultUriDataLength > URI_DATA_MAX) { + return true; // error, URL is too big + } + defaultUrlPower = power; + urlAdvPeriod = urlAdvPeriodIn; + urlIsSet = true; + return false; + } + + /** + * Set Eddystone URL Frame information. + * @param[in] power TX Power in dB measured at 0 meters from the device. + * @param[in] encodedUrlIn Encoded URL + * @param[in] encodedUrlInLength Length of the encoded URL + * @param[in] urlAdvPeriodIn How long to advertise the URL frame (measured in # of adv periods) + * @return false on success, true on failure. + */ + bool setURLFrameEncodedData(int8_t power, const char *encodedUrlIn, uint8_t encodedUrlInLength, float urlAdvPeriodIn) { + if (0.0f == urlAdvPeriodIn) { + urlIsSet = false; + return false; + } + memcpy(defaultUriData, encodedUrlIn, encodedUrlInLength); + if (defaultUriDataLength > URI_DATA_MAX) { + return true; // error, URL is too big + } + defaultUrlPower = power; + defaultUriDataLength = encodedUrlInLength; + urlAdvPeriod = urlAdvPeriodIn; + urlIsSet = true; + return false; + } + + /* + * Construct URL frame from private variables + * @param[in/out] Data pointer to array to store constructed frame in + * @param[in] maxSize number of bytes left in array, effectively how much emtpy space is available to write to + * @return number of bytes used. negative number indicates error message. + */ + int constructURLFrame(uint8_t *Data, uint8_t maxSize) { + int index = 0; + Data[index++] = FRAME_TYPE_URL; // 1B Type + Data[index++] = defaultUrlPower; // 1B TX Power + for (int x = 0; x < defaultUriDataLength; x++) { // 18B of URL Prefix + encoded URL + Data[index++] = defaultUriData[x]; + } + DBG("constructURLFrame: %d, %d", maxSize, index); + return index; + } + + /* + * 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 = 0, + float advPeriod = 60.0f, + uint16_t batteryVoltage = 0, + uint16_t beaconTemp = 0x8000, + uint32_t pduCount = 0, + uint32_t timeSinceBoot = 0) { + if (0.0f == 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 + } + + /* + * Construct TLM frame from private variables + * @param[in/out] Data pointer to array to store constructed frame in + * @param[in] maxSize number of bytes left in array, effectively how much emtpy space is available to write to + * @return number of bytes used. negative number indicates error message. + */ + int constructTLMFrame(uint8_t *Data, uint8_t maxSize) { + uint32_t now = timeSinceBootTimer.read_ms(); + TlmTimeSinceBoot += (now - lastBootTimerRead) / 100; + lastBootTimerRead = now; + + int index = 0; + Data[index++] = FRAME_TYPE_TLM; // Eddystone frame type = Telemetry + Data[index++] = TlmVersion; // TLM Version Number + Data[index++] = (uint8_t)(TlmBatteryVoltage >> 8); // Battery Voltage[0] + 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] + DBG("constructURLFrame: %d, %d", maxSize, index); + return index; + } + + /* + * Update the TLM frame battery voltage value + * @param[in] voltagemv Voltage to update the TLM field battery voltage with (in mV) + * @return nothing + */ + void updateTlmBatteryVoltage(uint16_t voltagemv) { + TlmBatteryVoltage = voltagemv; + } + + /* + * Update the TLM frame beacon temperature + * @param[in] temp Temperature of beacon (in 8.8fpn) + * @return nothing + */ + void updateTlmBeaconTemp(uint16_t temp) { + TlmBeaconTemp = temp; + } + + /* + * Update the TLM frame PDU Count field + * @param[in] pduCount Number of Advertisiting frames sent since powerup + * @return nothing + */ + void updateTlmPduCount(uint32_t pduCount) { + TlmPduCount = pduCount; + } + + /* + * Update the TLM frame Time since boot in 0.1s incriments + * @param[in] timeSinceBoot Time since boot in 0.1s incriments + * @return nothing + */ + void updateTlmTimeSinceBoot(uint32_t timeSinceBoot) { + TlmTimeSinceBoot = timeSinceBoot; + } + + /* + * Update advertising data + * @return true on success, false on failure + */ + bool updateAdvPacket(uint8_t serviceData[], unsigned serviceDataLen) { + // Fields from the Service + DBG("Updating AdvFrame: %d", serviceDataLen); + + 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; + } + + /* + * State machine for switching out frames. + * This function is called by the radioNotificationCallback when a frame needs to get swapped out. + * This function exists because of time constraints in the radioNotificationCallback, so it is effectively + * broken up into two functions. + */ + void swapOutFrames(FrameTypes frameType) { + uint8_t serviceData[SERVICE_DATA_MAX]; + unsigned serviceDataLen = 0; + //hard code in the eddystone UUID + serviceData[serviceDataLen++] = BEACON_EDDYSTONE[0]; + serviceData[serviceDataLen++] = BEACON_EDDYSTONE[1]; + + // if certain frames are not enabled, then skip them. Worst case TLM is always enabled + 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) { + 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; + } + break; + case uid: + // UID Frame + if (uidIsSet) { + 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; + } + break; + default: + 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); + } + } + + /* + * 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); + } + } + + /* + * 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); + } + } + + 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); + } + } + + /* + * Callback from onRadioNotification(), used to update the PDUCounter and process next state. + */ +#define EDDYSTONE_SWAPFRAME_DELAYMS 1 + void radioNotificationCallback(bool radioActive) { + // Update PDUCount + TlmPduCount++; + // True just before an frame is sent, false just after a frame is sent + if (radioActive) { + // Do Nothing + } else { + // Packet has been sent, disable advertising + stopAdv.attach_us(this, &EddystoneService::stopAdvCallback, 1); + } + } + + /* + * This function explicityly sets the parameters used by the Eddystone beacon. + * this function should be used in leu of the config service. + * + * @param bleIn ble object used to broadcast eddystone information + * @param beaconPeriodus is how often ble broadcasts are mde, in mili seconds + * @param txPowerLevel sets the broadcasting power level. + * + */ + EddystoneService(BLEDevice &bleIn, + 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, (float) advPeriodus / 1000.0f); + DBG("attached urlCallback every %d seconds", urlAdvPeriod); + } + 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); + lastBootTimerRead = 0; + timeSinceBootTimer.start(); + 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; + + ble.setTxPower(txPower); + ble.gap().onRadioNotification(this, &EddystoneService::radioNotificationCallback); + } + +private: + + // Eddystone Variables + BLEDevice &ble; + uint16_t advPeriodus; + uint8_t txPower; + Timer timeSinceBootTimer; + volatile uint32_t lastBootTimerRead; + 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 + float urlAdvPeriod; // how long the url frame will be advertised for + Ticker urlTicker; + + // UID Frame Variables + UIDNamespaceID_t defaultUidNamespaceID; + UIDInstanceID_t defaultUidInstanceID; + int8_t defaultUidPower; + uint16_t uidRFU; + bool uidIsSet; // flag that enables / disable UID Frames + float uidAdvPeriod; // how long the uid frame will be advertised for + Ticker uidTicker; + + // 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 + float 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, uint8_t &sizeofURIDataOut) { + DBG("Encode URL = %s", uriDataIn); + const char *prefixes[] = { + "http://www.", + "https://www.", + "http://", + "https://", + }; + const size_t NUM_PREFIXES = sizeof(prefixes) / sizeof(char *); + const char *suffixes[] = { + ".com/", + ".org/", + ".edu/", + ".net/", + ".info/", + ".biz/", + ".gov/", + ".com", + ".org", + ".edu", + ".net", + ".info", + ".biz", + ".gov" + }; + const size_t NUM_SUFFIXES = sizeof(suffixes) / sizeof(char *); + + sizeofURIDataOut = 0; + memset(uriDataOut, 0, sizeof(UriData_t)); + + if ((uriDataIn == NULL) || (strlen(uriDataIn) == 0)) { + return; + } + + /* + * handle prefix + */ + for (unsigned i = 0; i < NUM_PREFIXES; i++) { + size_t prefixLen = strlen(prefixes[i]); + if (strncmp(uriDataIn, prefixes[i], prefixLen) == 0) { + uriDataOut[sizeofURIDataOut++] = i; + uriDataIn += prefixLen; + break; + } + } + + /* + * handle suffixes + */ + while (*uriDataIn && (sizeofURIDataOut < URI_DATA_MAX)) { + /* check for suffix match */ + unsigned i; + for (i = 0; i < NUM_SUFFIXES; i++) { + size_t suffixLen = strlen(suffixes[i]); + if (strncmp(uriDataIn, suffixes[i], suffixLen) == 0) { + uriDataOut[sizeofURIDataOut++] = i; + uriDataIn += suffixLen; + break; /* from the for loop for checking against suffixes */ + } + } + /* 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; + } + } + } +}; + +#endif // SERVICES_EDDYSTONEBEACON_H_ \ No newline at end of file
--- a/module.json Thu Nov 26 14:51:22 2015 +0000 +++ b/module.json Thu Nov 26 14:51:22 2015 +0000 @@ -1,6 +1,6 @@ { "name": "ble", - "version": "2.0.5", + "version": "2.1.3", "description": "The BLE module offers a high level abstraction for using Bluetooth Low Energy on multiple platforms.", "keywords": [ "Bluetooth", @@ -13,7 +13,7 @@ "url": "https://github.com/ARMmbed/ble.git", "type": "git" }, - "homepage": "http://mbed.org/ble", + "homepage": "https://developer.mbed.org/teams/Bluetooth-Low-Energy/", "licenses": [ { "url": "https://spdx.org/licenses/Apache-2.0",
--- a/source/DiscoveredCharacteristic.cpp Thu Nov 26 14:51:22 2015 +0000 +++ b/source/DiscoveredCharacteristic.cpp Thu Nov 26 14:51:22 2015 +0000 @@ -31,6 +31,52 @@ return gattc->read(connHandle, valueHandle, offset); } +struct OneShotReadCallback { + static void launch(GattClient* client, Gap::Handle_t connHandle, + GattAttribute::Handle_t handle, const GattClient::ReadCallback_t& cb) { + OneShotReadCallback* oneShot = new OneShotReadCallback(client, connHandle, handle, cb); + oneShot->attach(); + // delete will be made when this callback is called + } + +private: + OneShotReadCallback(GattClient* client, Gap::Handle_t connHandle, + GattAttribute::Handle_t handle, const GattClient::ReadCallback_t& cb) : + _client(client), + _connHandle(connHandle), + _handle(handle), + _callback(cb) { } + + void attach() { + _client->onDataRead(makeFunctionPointer(this, &OneShotReadCallback::call)); + } + + void call(const GattReadCallbackParams* params) { + // verifiy that it is the right characteristic on the right connection + if (params->connHandle == _connHandle && params->handle == _handle) { + _callback(params); + _client->onDataRead().detach(makeFunctionPointer(this, &OneShotReadCallback::call)); + delete this; + } + } + + GattClient* _client; + Gap::Handle_t _connHandle; + GattAttribute::Handle_t _handle; + GattClient::ReadCallback_t _callback; +}; + +ble_error_t DiscoveredCharacteristic::read(uint16_t offset, const GattClient::ReadCallback_t& onRead) const { + ble_error_t error = read(offset); + if (error) { + return error; + } + + OneShotReadCallback::launch(gattc, connHandle, valueHandle, onRead); + + return error; +} + ble_error_t DiscoveredCharacteristic::write(uint16_t length, const uint8_t *value) const { @@ -59,6 +105,52 @@ return gattc->write(GattClient::GATT_OP_WRITE_CMD, connHandle, valueHandle, length, value); } +struct OneShotWriteCallback { + static void launch(GattClient* client, Gap::Handle_t connHandle, + GattAttribute::Handle_t handle, const GattClient::WriteCallback_t& cb) { + OneShotWriteCallback* oneShot = new OneShotWriteCallback(client, connHandle, handle, cb); + oneShot->attach(); + // delete will be made when this callback is called + } + +private: + OneShotWriteCallback(GattClient* client, Gap::Handle_t connHandle, + GattAttribute::Handle_t handle, const GattClient::WriteCallback_t& cb) : + _client(client), + _connHandle(connHandle), + _handle(handle), + _callback(cb) { } + + void attach() { + _client->onDataWritten(makeFunctionPointer(this, &OneShotWriteCallback::call)); + } + + void call(const GattWriteCallbackParams* params) { + // verifiy that it is the right characteristic on the right connection + if (params->connHandle == _connHandle && params->handle == _handle) { + _callback(params); + _client->onDataWritten().detach(makeFunctionPointer(this, &OneShotWriteCallback::call)); + delete this; + } + } + + GattClient* _client; + Gap::Handle_t _connHandle; + GattAttribute::Handle_t _handle; + GattClient::WriteCallback_t _callback; +}; + +ble_error_t DiscoveredCharacteristic::write(uint16_t length, const uint8_t *value, const GattClient::WriteCallback_t& onRead) const { + ble_error_t error = write(length, value); + if (error) { + return error; + } + + OneShotWriteCallback::launch(gattc, connHandle, valueHandle, onRead); + + return error; +} + ble_error_t DiscoveredCharacteristic::discoverDescriptors(DescriptorCallback_t callback, const UUID &matchingUUID) const {