Reputation: 16164
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
Upvotes: 3
Views: 1561
Reputation: 20141
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 typedef
s mean:
WiFiEventCb
... a function pointer to a (free) function which returns void
and has one parameter of type system_event_id_t
WiFiEventFuncCb
... a std::function
object for anything returning void
and having two parameters of types system_event_id_t
and system_event_info_t
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:
So, to make the ApiClient::WiFiEvent()
compatible, there are two options:
ApiClient::WiFiEvent()
as static member functionApiClient::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.)
Upvotes: 6