Home > Net >  C Use a class non-static method as a function pointer callback in freeRTOS xTimerCreate
C Use a class non-static method as a function pointer callback in freeRTOS xTimerCreate

Time:02-21

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:

  1. Why on earth the std::bind "approach" works for WiFi.onEvent but not for xTimerCreate? They seem pretty similar to me... WiFi is typedef void (*WiFiEventCb)(system_event_id_t event); while the timer typedef void (*TimerCallbackFunction_t)( TimerHandle_t xTimer );
  2. How can I make this work? Is there a cast or a better approach?
  3. 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:

  1. 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 from std::bind().

  2. 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 using pvTimerGetTimerID(). With a class pointer you can then forward the callback to your C class. See example below.

  3. 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);
  • Related