Search code examples
c++arduinoesp32

Refactoring sample code into class raises no instance of overloaded function


I an new on CPP and try to "refactor" this example code https://github.com/espressif/arduino-esp32/blob/master/libraries/WiFi/examples/WPS/WPS.ino into a class called ApiClient`. I then wanna be able to do something like this:

apiClient = ApiClient(myUrl);
apiClient.sendValue(key, value);

Everything compiles, except the wifi.onEvent(WiFiEvent); function call. When I copy paste the whole example code in my main.cpp file the example is working. When I use my "refactored" approach, the wifi.onEvent(WifiEvent) is complaining.

The exact error message:

no instance of overloaded function "WiFiClass::onEvent" matches the argument list -- argument types are: (void (system_event_id_t event, system_event_info_t info)) -- object type is: WiFiClass

I know that the onEvent function normally takes two arguments, but why is this working in the example code? And how can I solve that?

This is what I have so far:

main.cpp



#include "WiFi.h"
#include <esp_wps.h>
#include <Hythe22.h>
#include <ApiClient.h>

#define DHTPIN 14
// ?accessKey=ist_NJu3tjPIBCYeJd6DGGBxzq14LvungHoK&bucketKey=B37GHBNK5HL3
#define API_URL "https://groker.init.st/api/events";

Hythe22 dht(DHTPIN);
ApiClient apiClient;
uint64_t chipid;
#define ESP_DEVICE_NAME String(chipid)


void setup()
{
  String apiUrl = "https://myApi.Endpoint.com/event";
  Serial.begin(115200);
  delay(100);
  Serial.println();
}

void loop()
{

  String temp = String(dht.temperature);
  Serial.println("TEMP:" + temp);
  apiClient.sendValue("temperature", temp);

  String hum = String(dht.humidity);
  Serial.println("HUM:" + hum);
  apiClient.sendValue("humidity", hum);
}

ApiClient.h

/*
===========================================================
*/

#ifndef WebClient_h
#define WebClient_h

#include <Arduino.h>
#include <WiFi.h>
#include "esp_wps.h"
#include <HTTPClient.h>

class ApiClient
{
  public:
    ApiClient(String apiUrl);
    void sendValue(String key, String value);
    void wpsInitConfig();
    void WiFiEvent(WiFiEvent_t event, system_event_info_t info);
    String wpspin2string(uint8_t a[]);
    String requestUrl;
    String _apiUrl;
    int chipid;

  private:
};

#endif

ApiClient.cpp


/*
===========================================================
*/

#include <Arduino.h>
#include <ApiClient.h>
#include <WiFi.h>
#include <esp_wps.h>
#include <HTTPClient.h>

int chipid;

#define ESP_WPS_MODE WPS_TYPE_PBC
#define ESP_MANUFACTURER "ESPRESSIF"
#define ESP_MODEL_NUMBER "ESP32"
#define ESP_MODEL_NAME "ESPRESSIF IOT"
#define ESP_DEVICE_NAME "ESP STATION"

String _apiUrl;
HTTPClient http;
String requestUrl;
WiFiClass wifi;

static esp_wps_config_t config;

ApiClient::ApiClient(String apiUrl)
{
    Serial.begin(115200);
    delay(10);

    Serial.println();

    wifi.onEvent(WiFiEvent);
    wifi.mode(WIFI_MODE_STA);

    Serial.println("Starting WPS");

    wpsInitConfig();
    esp_wifi_wps_enable(&config);
    esp_wifi_wps_start(0);
}

void sendValue(String key, String value)
{
    HTTPClient http;
    Serial.println("key:" + key);
    Serial.println("value:" + value);
    requestUrl = _apiUrl + "?" + key + "=" + value;
    // Serial.println(apiUrl);
    http.begin(requestUrl);

    int httpCode = http.GET();

    if (httpCode > 0)
    {
        Serial.printf("[HTTP] GET... code: %d\n", httpCode);
        //file found at server --> on unsucessful connection code will be -1
        if (httpCode == HTTP_CODE_OK)
        {
            String payload = http.getString();
            Serial.println(payload);
        }
    }
    else
    {
        Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
    }
    http.end();
}

void ApiClient::wpsInitConfig()
{
    config.crypto_funcs = &g_wifi_default_wps_crypto_funcs;
    config.wps_type = ESP_WPS_MODE;
    strcpy(config.factory_info.manufacturer, ESP_MANUFACTURER);
    strcpy(config.factory_info.model_number, ESP_MODEL_NUMBER);
    strcpy(config.factory_info.model_name, ESP_MODEL_NAME);
    strcpy(config.factory_info.device_name, ESP_DEVICE_NAME);
}

String wpspin2string(uint8_t a[])
{
    char wps_pin[9];
    for (int i = 0; i < 8; i++)
    {
        wps_pin[i] = a[i];
    }
    wps_pin[8] = '\0';
    return (String)wps_pin;
}

void WiFiEvent(WiFiEvent_t event, system_event_info_t info){
  switch(event){
    case SYSTEM_EVENT_STA_START:
      Serial.println("Station Mode Started");
      break;
    case SYSTEM_EVENT_STA_GOT_IP:
      Serial.println("Connected to :" + String(WiFi.SSID()));
      Serial.print("Got IP: ");
      Serial.println(WiFi.localIP());
      break;
    case SYSTEM_EVENT_STA_DISCONNECTED:
      Serial.println("Disconnected from station, attempting reconnection");
      WiFi.reconnect();
      break;
    case SYSTEM_EVENT_STA_WPS_ER_SUCCESS:
      Serial.println("WPS Successfull, stopping WPS and connecting to: " + String(WiFi.SSID()));
      esp_wifi_wps_disable();
      delay(10);
      WiFi.begin();
      break;
    case SYSTEM_EVENT_STA_WPS_ER_FAILED:
      Serial.println("WPS Failed, retrying");
      esp_wifi_wps_disable();
      esp_wifi_wps_enable(&config);
      esp_wifi_wps_start(0);
      break;
    case SYSTEM_EVENT_STA_WPS_ER_TIMEOUT:
      Serial.println("WPS Timedout, retrying");
      esp_wifi_wps_disable();
      esp_wifi_wps_enable(&config);
      esp_wifi_wps_start(0);
      break;
    case SYSTEM_EVENT_STA_WPS_ER_PIN:
      Serial.println("WPS_PIN = " + wpspin2string(info.sta_er_pin.pin_code));
      break;
    default:
      break;
  }
}

Thank you in advance


Solution

  • I had a look onto the example linked in OP's question.

    The relevant part is

    void setup(){
      // contents skipped
      WiFi.onEvent(WiFiEvent);
      // contents skipped
    }
    

    Thereby WiFiEvent is a free function which is defined above:

    void WiFiEvent(WiFiEvent_t event, system_event_info_t info){
      switch(event){
        // some cases to handle various events
        default:
          break;
      }
    }
    

    The OP wants to refactor this event handler into his class ApiClient:

    class ApiClient
    {
      public:
        ApiClient(String apiUrl);
        void sendValue(String key, String value);
        void wpsInitConfig();
        void WiFiEvent(WiFiEvent_t event, system_event_info_t info);
        String wpspin2string(uint8_t a[]);
        String requestUrl;
        String _apiUrl;
        int chipid;
    
      private:
    };
    

    The essential difference is that WiFiEvent() becomes a member function due to this, and OP got the reported error

    no instance of overloaded function "WiFiClass::onEvent" matches the argument list -- argument types are: (void (system_event_id_t event, system_event_info_t info)) -- object type is: WiFiClass
    

    Out of curiosity, I digged a bit in the github project and finally found the declaration of WiFiClass::onEvent() – it's inherited from class WiFiGenericClass:

    class WiFiGenericClass
    {
      public:
        WiFiGenericClass();
    
        wifi_event_id_t onEvent(WiFiEventCb cbEvent, system_event_id_t event = SYSTEM_EVENT_MAX);
        wifi_event_id_t onEvent(WiFiEventFuncCb cbEvent, system_event_id_t event = SYSTEM_EVENT_MAX);
        wifi_event_id_t onEvent(WiFiEventSysCb cbEvent, system_event_id_t event = SYSTEM_EVENT_MAX);
        // a lot more - skipped
    };
    

    Thus, there are actually three declarations of onEvent() with two parameters, thereby the 2nd parameter of each has a default argument. (Hence, the call WiFi.onEvent(WiFiEvent); with only one argument in the example was OK.)

    To puzzle this out completely, I looked for WiFiEventCb, WiFiEventFuncCb, and WiFiEventSysCb and found them in the same header file above of class WiFiGenericClass:

    typedef void (*WiFiEventCb)(system_event_id_t event);
    typedef std::function<void(system_event_id_t event, system_event_info_t info)> WiFiEventFuncCb;
    typedef void (*WiFiEventSysCb)(system_event_t *event);
    

    This is what the three typedefs mean:

    1. WiFiEventCb ... a function pointer to a (free) function which returns void and has one parameter of type system_event_id_t
    2. WiFiEventFuncCb ... a std::function object for anything returning void and having two parameters of types system_event_id_t and system_event_info_t
    3. WiFiEventSysCb ... a function pointer to a (free) function which returns void and has one parameter of type system_event_id_t*.

    Obviously, the example used the 2nd onEvent() as it is the only one accepting functions with two parameters.

    Support of std::function is very nice, because it accepts anything callable with matching signature:

    • free functions,
    • functors,
    • lambdas (which are actually nothing else than one of the former).

    So, to make the ApiClient::WiFiEvent() compatible, there are two options:

    1. declare ApiClient::WiFiEvent() as static member function
    2. bind ApiClient::WiFiEvent() with an instance with which it is called.

    The first option restricts the usability of ApiClient::WiFiEvent() as static member functions are called without an instance of the resp. class. The drawback – there is no instance in the member function available (i.e. explicit or implicit access to this prohibited) which may or may not be acceptable.

    The second option can be achieved easily by using a lambda as adapter. For this, the event handler registration in ApiClient::ApiClient() has to be changed:

    //ERROR: wifi.onEvent(WiFiEvent);
    //Instead:
    wifi.onEvent(
      [this](WiFiEvent_t event, system_event_info_t info) {
        this->WiFiEvent(event, info);
      });
    

    This effectively registers a functor with the accepted signature which captures this of ApiClient so that a valid call of member function with instance can be done. The return type of lambda is implicitly declared to void because there is no return in the body of the lambda.

    Finally, I'd like to mention that capturing in lambdas is something which has to be done carefully. If wifi outlives this (i.e. the instance of ApiClient) then it may call ApiClient::WiFiEvent() without a valid this-pointer.

    To make it "bullet-proof", the destructor of ApiClient could call removeEvent() using the wifi_event_id_t which is returned by onEvent(). (This should be stored in ApiClient for this purpose.)