From 184d29c35bfe8db868f9ff3f0e3832d7af8da089 Mon Sep 17 00:00:00 2001 From: Vincent Coubard Date: Wed, 25 Nov 2015 11:27:33 +0000 Subject: [PATCH] Various enhancement: Add SafeBool class which allow to easily declare a safe bool operator in c++03. CallChainOfFunctionPointerswithContext: - unify syntax of add - detach function now return true if a function has been detached and false otherwise - Explanations about function call operator - use safe bool idiom - explanations about iterator and why it is mutable FunctionPointerWithContext: - fix call propagation - use safe bool idiom Gap: - add documentation - onRadioNotification does mot call initRadioNotification anymore GattClient: - documentation GattServer: - documentation --- ble/CallChainOfFunctionPointersWithContext.h | 43 ++++--- ble/FunctionPointerWithContext.h | 24 +--- ble/Gap.h | 26 ++++- ble/GattClient.h | 26 ++++- ble/GattServer.h | 16 +++ ble/SafeBool.h | 114 +++++++++++++++++++ source/DiscoveredCharacteristic.cpp | 4 +- 7 files changed, 213 insertions(+), 40 deletions(-) create mode 100644 ble/SafeBool.h diff --git a/ble/CallChainOfFunctionPointersWithContext.h b/ble/CallChainOfFunctionPointersWithContext.h index 761c651..fcda1c5 100644 --- a/ble/CallChainOfFunctionPointersWithContext.h +++ b/ble/CallChainOfFunctionPointersWithContext.h @@ -18,6 +18,7 @@ #include #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 -class CallChainOfFunctionPointersWithContext { +class CallChainOfFunctionPointersWithContext : public SafeBool > { public: typedef FunctionPointerWithContext *pFunctionPointerWithContext_t; @@ -101,8 +102,8 @@ public: * * @param func The FunctionPointerWithContext to add. */ - void add(const FunctionPointerWithContext& func) { - common_add(new FunctionPointerWithContext(func)); + pFunctionPointerWithContext_t add(const FunctionPointerWithContext& func) { + return common_add(new FunctionPointerWithContext(func)); } /** @@ -112,7 +113,7 @@ public: * * @return true if a function pointer has been detached and false otherwise */ - void detach(const FunctionPointerWithContext& toDetach) { + bool detach(const FunctionPointerWithContext& toDetach) { pFunctionPointerWithContext_t current = chainHead; pFunctionPointerWithContext_t previous = NULL; @@ -130,12 +131,14 @@ public: previous->chainAsNext(current->getNext()); } delete current; - return; + return true; } previous = current; current = current->getNext(); } + + return false; } /** Clear the call chain (remove all functions in the chain). @@ -156,9 +159,6 @@ public: } /** 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) { ((const CallChainOfFunctionPointersWithContext*) this)->call(context); @@ -183,16 +183,30 @@ public: /** * @brief same as above but with function call operator + * \code + * + * void first(bool); + * void second(bool); + * + * CallChainOfFunctionPointerWithContext foo; + * + * foo.attach(first); + * foo.attach(second); + * + * // call the callchain like a function + * foo(true); + * + * \endcode */ void operator()(ContextType context) const { call(context); } - typedef void (CallChainOfFunctionPointersWithContext::*bool_type)() const; - void True() const {} - - operator bool_type() const { - return chainHead == NULL ? 0 : &CallChainOfFunctionPointersWithContext::True; + /** + * @brief bool conversion operation + */ + bool toBool() const { + return chainHead != NULL; } private: @@ -209,6 +223,9 @@ private: 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; diff --git a/ble/FunctionPointerWithContext.h b/ble/FunctionPointerWithContext.h index 6e60a44..02b07a6 100644 --- a/ble/FunctionPointerWithContext.h +++ b/ble/FunctionPointerWithContext.h @@ -18,12 +18,13 @@ #define MBED_FUNCTIONPOINTER_WITH_CONTEXT_H #include +#include "SafeBool.h" /** A class for storing and calling a pointer to a static or member void function * that takes a context. */ template -class FunctionPointerWithContext { +class FunctionPointerWithContext : public SafeBool > { public: typedef FunctionPointerWithContext *pFunctionPointerWithContext_t; typedef const FunctionPointerWithContext *cpFunctionPointerWithContext_t; @@ -87,11 +88,6 @@ public: * many FunctionPointers in a chain. */ void call(ContextType context) const { _caller(this, context); - - /* Propagate the call to next in the chain. */ - if (_next) { - _next->call(context); - } } /** @@ -103,7 +99,7 @@ public: /** Same as above, workaround for mbed os FunctionPointer implementation. */ void call(ContextType context) { - _caller(this, context); + ((const FunctionPointerWithContext*) this)->call(context); } typedef void (FunctionPointerWithContext::*bool_type)() const; @@ -111,12 +107,8 @@ public: /** * implementation of safe bool operator */ - operator bool_type() const { - if(_function || _memberFunctionAndPointer._object) { - return &FunctionPointerWithContext::trueValue; - } - - return 0; + bool toBool() const { + return (_function || _memberFunctionAndPointer._object); } /** @@ -164,12 +156,6 @@ private: } } - /** - * @brief True value used in conversion to bool, this function is useless - * beside this usage - */ - void trueValue() const {} - struct MemberFunctionAndPtr { /* * Forward declaration of a class and a member function to this class. diff --git a/ble/Gap.h b/ble/Gap.h index c3b318a..7eefcd9 100644 --- a/ble/Gap.h +++ b/ble/Gap.h @@ -897,35 +897,56 @@ public: /** * 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) { 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);} template 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);} template 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; } @@ -959,15 +980,10 @@ public: */ void onRadioNotification(void (*callback)(bool param)) { radioNotificationCallback.attach(callback); - // why does it start radio notification ? It is not even indicated in the - // doc that it start the listening process - initRadioNotification(); } template void onRadioNotification(T *tptr, void (T::*mptr)(bool)) { radioNotificationCallback.attach(tptr, mptr); - // why does it start radio notification ? - initRadioNotification(); } protected: diff --git a/ble/GattClient.h b/ble/GattClient.h index 343b5e9..a4109d3 100644 --- a/ble/GattClient.h +++ b/ble/GattClient.h @@ -246,24 +246,40 @@ public: /* 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) { 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) { 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; } @@ -292,11 +308,19 @@ public: * 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) { 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; } diff --git a/ble/GattServer.h b/ble/GattServer.h index a71f63b..6a6324b 100644 --- a/ble/GattServer.h +++ b/ble/GattServer.h @@ -274,6 +274,8 @@ public: * * @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(const DataWrittenCallback_t& callback) {dataWrittenCallChain.add(callback);} template @@ -281,6 +283,12 @@ public: 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; } @@ -301,6 +309,8 @@ public: * @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. */ @@ -322,6 +332,12 @@ public: return BLE_ERROR_NONE; } + /** + * @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; } diff --git a/ble/SafeBool.h b/ble/SafeBool.h new file mode 100644 index 0000000..c498508 --- /dev/null +++ b/ble/SafeBool.h @@ -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 + 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 { + * public: + * + * // boolean conversion + * bool toBool() { + * + * } + * }; + * + * class B : public SafeBool { + * public: + * + * // boolean conversion + * bool toBool() const { + * + * } + * }; + * + * A a; + * B b; + * + * // will compile + * if(a) { + * + * } + * + * // compilation error + * if(a == b) { + * + * } + * + * + * \endcode + */ +template +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(this))->toBool() + ? &SafeBool::trueTag : 0; + } +}; + +//Avoid conversion to bool between different classes +template +void operator==(const SafeBool& lhs,const SafeBool& rhs) { + lhs.invalidTag(); +// return false; +} + +//Avoid conversion to bool between different classes +template +void operator!=(const SafeBool& lhs,const SafeBool& rhs) { + lhs.invalidTag(); +// return false; +} + +#endif /* BLE_API_SAFE_BOOL_H_ */ diff --git a/source/DiscoveredCharacteristic.cpp b/source/DiscoveredCharacteristic.cpp index b037c13..91119e0 100644 --- a/source/DiscoveredCharacteristic.cpp +++ b/source/DiscoveredCharacteristic.cpp @@ -33,7 +33,7 @@ DiscoveredCharacteristic::read(uint16_t offset) const struct OneShotReadCallback { static void launch(GattClient* client, Gap::Handle_t connHandle, - GattAttribute::Handle_t handle,const GattClient::ReadCallback_t& cb) { + 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 @@ -107,7 +107,7 @@ DiscoveredCharacteristic::writeWoResponse(uint16_t length, const uint8_t *value) struct OneShotWriteCallback { static void launch(GattClient* client, Gap::Handle_t connHandle, - GattAttribute::Handle_t handle,const GattClient::WriteCallback_t& cb) { + 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