I am trying to use marvinroger/async-mqtt-client
that in the provided examples is used together with freertos timers that use a callback that gets invoked whenever the timer expire. The full example is here.
I wanted to create a singleton class to enclose all the connection managing part and just expose the constructor (through a getInstance) and a begin function that other than setting the callbacks, creates the timers for reconnection.
The class looks like (I simplified by removing useless parts):
class MqttManager : public Singleton<MqttManager> {
public:
virtual ~MqttManager() = default;
void begin();
protected:
MqttManager();
void connectToMqtt(TimerHandle_t xTimer);
void WiFiEvent(WiFiEvent_t event);
void onConnect(bool sessionPresent);
std::unique_ptr<AsyncMqttClient> client;
TimerHandle_t mqttReconnectTimer;
TimerHandle_t wifiReconnectTimer;
};
While my issue is when I try to pass the connectToMqtt
callback to the timer.
MqttManager::MqttManager() {
this->client = std::unique_ptr<AsyncMqttClient>(new AsyncMqttClient());
// use bind to use a class non-static method as a callback
// (works fine for mqtt callbacks and wifi callback)
this->client->onConnect(std::bind(&MqttManager::onConnect, this, std::placeholders::_1));
WiFi.onEvent(std::bind(&MqttManager::WiFiEvent, this, std::placeholders::_1));
// Here it fails
mqttReconnectTimer = xTimerCreate("mqttTimer", pdMS_TO_TICKS(2000), pdFALSE, (void*)nullptr, &MqttManager::connectToMqtt, this, std::placeholders::_1);
The error is:
cannot convert 'void (MqttManager::)(TimerHandle_t) {aka void (MqttManager::)(void*)}' to 'TimerCallbackFunction_t {aka void ()(void)}' for argument '5' to 'void* xTimerCreate(const char*, TickType_t, UBaseType_t, void*, TimerCallbackFunction_t)'
Now, from here, having in mind that the problem is around having a pointer to a non-static method that needs somehow to be casted to a free function pointer, three doubts arise:
- Why on earth the
std::bind
"approach" works forWiFi.onEvent
but not forxTimerCreate
? They seem pretty similar to me... WiFi istypedef void (*WiFiEventCb)(system_event_id_t event);
while the timertypedef void (*TimerCallbackFunction_t)( TimerHandle_t xTimer );
- How can I make this work? Is there a cast or a better approach?
- Is this bad practice? My goal here was to enclose mqtt and wifi functions and callbacks in a neat class easily recognizable, organized and maintainable; but I guess that sometimes you just obtain the opposite result without noticing...
CodePudding user response:
std::bind
returns a callable object, not a function pointer. It works with WiFi.onEvent
because there is an overload taking a std::function
:
typedef std::function<void(arduino_event_id_t event, arduino_event_info_t info)> WiFiEventFuncCb;
// ...
wifi_event_id_t onEvent(WiFiEventFuncCb cbEvent, arduino_event_id_t event = ARDUINO_EVENT_MAX);
Solution
Create a static function for the timer callback and simply get the MqttManager
instance as you would from anywhere else.
CodePudding user response:
FreeRTOS code is plain old C. It knows nothing about C , instance methods, function objects, etc. It takes a pointer to a function, period. As Armandas pointed out,
WiFi.onEvent
on the other hand is C , lovingly written by someone to accept output fromstd::bind()
.There is a workaround. When you read the xTimerCreate API docs, there is a sneaky little parameter
pvTimerID
which is effectively user-specified data. You can use this to pass a pointer to your class and later retrieve it from inside the callback function usingpvTimerGetTimerID()
. With a class pointer you can then forward the callback to your C class. See example below.It's good practice to try to hide private class methods and data. Unfortunately this only works well if you're working entirely in C :) If calling into C libraries (like FreeRTOS) I find myself breaking such idealistic principles occasionally.
Here's how I'd do it. I use a lambda (without context) as the actual callback function because it's throwaway wrapper code, and the C libraries happily accept it as a plain old function pointer.
auto onTimer = [](TimerHandle_t hTmr) {
MqttManager* mm = static_cast<MqttManager*>(pvTimerGetTimerID(hTmr)); // Retrieve the pointer to class
assert(mm); // Sanity check
mm->connectToMqtt(hTmr); // Forward to the real callback
}
mqttReconnectTimer = xTimerCreate("mqttTimer", pdMS_TO_TICKS(2000), pdFALSE, static_cast<void*>(this), onTimer);