Callbacks in C++ for an Arduino project - c++

I am working on a Particle project and coming from JS so I'm challenged by callbacks in C++. I am trying to refactor my Firebase code into a reusable class and for this I need callbacks:
void setup() {
firebase = new Firebase;
Serial.begin(9600);
firebase->subscribe();
firebase->setCallback(readCallback);
}
void readCallback(JsonObject& root)
{
r = root["r"];
g = root["g"];
b = root["b"];
Serial.printlnf("Yes! r=%d g=%d b=%d d=%d", r, g, b);
}
Firebase.h:
#ifndef Firebase_h
#define Firebase_h
#include <SparkJson.h>
class Firebase {
public:
Firebase();
void
subscribe(),
setCallback(void (*readCallback)(JsonObject& root));
private:
static void getDataHandler(const char *topic, const char *data);
void (*_readCallback)(JsonObject& root);
};
#endif
Firebase.m:
#include "Particle.h"
// This #include statement was automatically added by the Particle IDE.
#include <SparkJson.h>
#include "ArduinoJson.h"
#include "Firebase.h"
Firebase::Firebase(void) {
Serial.printlnf("Firebase instance created");
}
void Firebase::getDataHandler(const char *topic, const char *data) {
Serial.printlnf("getDataHandler invoked");
StaticJsonBuffer<256> jsonBuffer;
char *mutableCopy = strdup(data);
JsonObject& root = jsonBuffer.parseObject(mutableCopy);
free(mutableCopy);
Serial.printlnf("data received: %s", data);
// _readCallback(root);
}
void Firebase::subscribe() {
Serial.printlnf("Firebase subscribe");
Particle.subscribe("hook-response/test3rdata", getDataHandler, MY_DEVICES);
}
void Firebase::setCallback(void (*readCallback)(JsonObject& root))
{
Serial.printlnf("set callback");
_readCallback = readCallback;
}
When getDataHandler is static everything seems to work but naturally I am having trouble accessing the callback and I get:
invalid use of member 'Firebase::_readCallback' in static member
function
When it's not static I get for this line:
Particle.subscribe("hook-response/test3rdata", getDataHandler, MY_DEVICES);
the following error:
invalid use of non-static member function
When I try to bind it as advised here:
Particle.subscribe("hook-response/test3rdata", std::bind(&Firebase::getDataHandler,this), MY_DEVICES);
I get a mistype as Particle.subscribe does not expect a binded method:
no matching function for call to 'CloudClass::subscribe(const char
[25], std::_Bind_helper::type, Spark_Subscription_Scope_TypeDef)'
Is there a way around it?

You are getting this error because std::bind returns a function object that adheres to the signature void() and not void(char const*, char const*). The reason being, that you didn't specify any placeholders for those arguments. So a quick fix would be:
std::bind(&Firebase::getDataHandler, this, std::placeholders::_1, std::placeholders::_2)
Now the bind helper expects two parameters which it will forward to the bound member function.
Having said all that, there is no reason to use std::bind if a lambda will suffice. Lambdas are in fact superior in most regards. In C++14 there's virtually no reason to use std::bind at all. And even in C++11, your use case can be dealt with by a simple lambda:
Particle.subscribe("hook-response/test3rdata", [this](char const* a, char const* b) { getDataHandler(a, b); }, MY_DEVICES);

Related

C++ - How to bind a callback to a class method without being static?

I have my class:
class Foo
{
public:
(...)
private:
void mycallback(void* buff, wifi_promiscuous_pkt_type_t type);
void registerMyCallback();
};
The mycallback is the callback.
I want to use a method esp_wifi_set_promiscuous_rx_cb to register the mycallback so that when a WiFi packet is detected, this callback method will be executed.
The esp_wifi_set_promiscuous_rx_cb signature is:
esp_err_t esp_wifi_set_promiscuous_rx_cb(wifi_promiscuous_cb_t cb);
Where the wifi_promiscuous_cb_t definition is:
typedef void (* wifi_promiscuous_cb_t)(void *buf, wifi_promiscuous_pkt_type_t type);
I want to use the mycallback method inside my class, therefore I simply can't use like this:
void Foo::registerMyCallback()
{
esp_wifi_set_promiscuous_rx_cb(&mycallback);
}
I know that I could use something similar if I would just make my method as static.
Is there anyway that I bind mycallback to esp_wifi_set_promiscuous_rx_cb without making the callback static?
I have tried the following:
esp_wifi_set_promiscuous_rx_cb(std::bind(&Foo::mycallback, this, std::placeholders::_1, std::placeholders::_2));
But I am still having the following error:
cannot convert 'std::_Bind_helper<false, void (Foo::Foo::*)(void*, wifi_promiscuous_pkt_type_t),
Foo::Foo*, const std::_Placeholder<1>&, const std::_Placeholder<2>&>::type
to
'wifi_promiscuous_cb_t {aka void (*)(void*, wifi_promiscuous_pkt_type_t)}' for argument '1'
Th library you are using is C package.
Thus the only guaranteed way pass a valid function is to pass a C function with C linkage. This function can then call the method on your object.
If you want the callback method to be non static you need to store a pointer (ore reference) to the callback object somewhere that your callback function can find it. (in most C callback functions you can provide a void* object that is passed to your callback, but this interface does not seem to allow this so you will have to save the value yourself).
Foo* myCBObject = nullptr;
extern "C" void myCB(void *buf, wifi_promiscuous_pkt_type_t type)
{
try
{
myCBObject->mycallback(buff, type);
}
catch(...) {} // Don't allow exceptions to cross C linkage
}
...
// Your code.
void Foo::registerMyCallback()
{
myCBObject = this;
esp_wifi_set_promiscuous_rx_cb(myCB);
}
Note: You should NOT be registering static member functions with a C library. If this works it is only by chance. There is no guarantee that a static function has the same calling convention of a C function (they usually do but that is not guaranteed).
After some research, I hope I found the solution. The trick is to bind member function first and then obtain the function pointer from the std::function. Notice the usage of my_wifi_promiscuous_cb_t and std::function::target<>().
#include <iostream>
#include <functional>
using namespace std::placeholders;
// using fake definitions
extern "C"
{
enum wifi_promiscuous_pkt_type_t {};
typedef int32_t esp_err_t;
typedef void (*wifi_promiscuous_cb_t)(void* buf, wifi_promiscuous_pkt_type_t type);
typedef void my_wifi_promiscuous_cb_t(void* buf, wifi_promiscuous_pkt_type_t type);
esp_err_t esp_wifi_set_promiscuous_rx_cb(wifi_promiscuous_cb_t cb)
{
return 0;
}
}
class Class
{
public:
void mycallback(void* buff, wifi_promiscuous_pkt_type_t type) {}
void registerMyCallback() {
std::function<void(void*, wifi_promiscuous_pkt_type_t)> fun2 = std::bind(&Class::mycallback, this, _1, _2);
esp_wifi_set_promiscuous_rx_cb(fun2.target<my_wifi_promiscuous_cb_t>());
}
};
int main()
{
Class c;
c.registerMyCallback();
}

Put Coap Works in another class

I've used coap server like this :
coapServer coap;
coap.server(callback_light, "light");
coap.start();
And the callback method :
void callback_light(coapPacket *packet, IPAddress ip, int port,int obs) {
///Some Work...
}
and it works perfectly.
I've made a class called COAPService and the header file :
#include <coap_server.h>
class COAPService
{
private:
coapServer coap;
int WiFiTimeOut = 5000;
void getListOfWiFi(coapPacket *packet, IPAddress ip, int port, int obs);//id = 0 GET
public:
COAPService();
void COAPLoop();
};
and the cpp file :
#include "COAPService.h"
#include <coap_server.h>
#include <ESP8266WiFi.h>
COAPService::COAPService()
{
coap.server(static_cast<COAPService*>(this)->getListOfWiFi, "wifilist");
coap.start(5683);
}
void COAPService::getListOfWiFi(coapPacket *packet, IPAddress ip, int port, int obs) //id = 0 GET
{
///Some Work
}
My problem is in the constructor.
when i call static_cast(this)->getListOfWiFi for a callBack method it returns :
COAPService.cpp:7:75: error: no matching function for call to 'coapServer::server(<unresolved overloaded function type>, const char [9])'
coap.server(static_cast<COAPService*>(this)->getListOfWiFi, "wifilist");
Why this error appears ?
I assume the problem is that you are passing a member function pointer to coapServer in order to be called at some point in the future.
If this is right, I assume that the function pointer you pass as argument to coap.server() must be a non-member function as you didn't pass the object pointer any time.
Try to transform getListOfWiFi in a static one (include the keyword static in the function signature):
static void getListOfWiFi(coapPacket *packet, IPAddress ip, int port, int obs);
Or just declare this function outside of any class/struct.
And in the COAPService ctor, just pass its address:
COAPService::COAPService()
{
// the static_cast you made here doesn't make much sense.
coap.server(getListOfWiFi, "wifilist");
coap.start(5683);
}

How to use a C++ member function as an interrupt handler in Arduino? [duplicate]

This question already has answers here:
Use class member functions as callbacks?
(6 answers)
Closed 3 years ago.
Arduino's attachInterrupt requires a callback function of type void(*)(), but I'd like to pass it a member function instead. I can't use a C++ member function here because of its implicit this argument.
Background
I know it's possible to use C++ member functions as callbacks. For example, FreeRTOS' xTaskCreate(...) takes a callback function of type void(*)(*).
isocpp.org has a nice FAQ on the use of member functions as callbacks.
In this related question user thiton writes:
Most sane callback libraries allow you to pass this void* argument to the functions as a way to have user-defined data in it
Perhaps the Arduino library is not "sane?" or perhaps this is design decision made to simplify the Arduino API?
it's there... inside attachInterrupt
I'm programming for an ESP32. In the arduino-esp32 implementation of attachInterrupt, there's a function called __attachInterruptFunctionalArg(...) that seems to do exactly what I want, but since it's not part of the Arduino API, I'm hesitant to include it in a project that's for public consumption because it may break.
Example program
// An attempt to summarize https://github.com/pierremolinaro/acan2517/issues/4
#include <stdio.h>
#include <stdint.h>
#include <functional>
#define IRAM_ATTR __attribute__((section(".iram1")))
// from `esp32-hal-gpio.c`
typedef void (*voidFuncPtrArg)(void*);
extern void __attachInterruptFunctionalArg(uint8_t pin, voidFuncPtrArg userFunc, void * arg, int intr_type, bool functional);
// from Arduino `FunctionalInterrupt.cpp`
void attachInterrupt(uint8_t pin, std::function<void(void)> intRoutine, int mode);
void IRAM_ATTR interruptFunctional(void* arg);
// from Arduino `FunctionalInterrupt.h`
struct InterruptArgStructure {
std::function<void(void)> interruptFunction;
};
// from ACAN2517
class ACAN2517
{
public: ACAN2517 (const int interrupt_pin);
public: void begin (void (* inInterruptServiceRoutine) (void));
public: void begin_functional (void (* inInterruptServiceRoutine) (void *), void *);
public: void isr(void);
private: const int interrupt_pin;
};
ACAN2517::ACAN2517 (const int interrupt_pin):
interrupt_pin(interrupt_pin)
{};
#define FALLING 0
// This won't work with a member function
void ACAN2517::begin (void (* inInterruptServiceRoutine) (void)) {
attachInterrupt(interrupt_pin, inInterruptServiceRoutine, FALLING);
}
// This will, but is prone to breakage when the Arduino internals change
void ACAN2517::begin_functional (void (* inInterruptServiceRoutine) (void *), void *arg)
{
__attachInterruptFunctionalArg(interrupt_pin, inInterruptServiceRoutine, arg, FALLING, true);
}
void ACAN2517::isr(void)
{
printf("fhtagn");
}
//===
// User code begin
//===
#define N_DRIVERS 3
ACAN2517 g_driver(23); // Initializing a driver instance statically
ACAN2517 *drivers[N_DRIVERS];
void call_ACAN_isr(void *arg)
{
ACAN2517 *driver = (ACAN2517 *)arg;
driver->isr();
}
int main()
{
g_driver.begin( []{g_driver.isr();} ); // No problem
for (int i = 0; i < N_DRIVERS; i++)
{
drivers[i] = &ACAN2517(i);
drivers[i]->begin( []{drivers[i]->isr();} );
// ERROR
// static void lambda []void ()->void::_FUN()
// an enclosing-function local variable cannot be referenced in a lambda body unless it is in the capture list
}
for (int i = 0; i < N_DRIVERS; i++)
{
drivers[i] = &ACAN2517(i);
drivers[i]->begin( [i]{drivers[i]->isr();} );
// ERROR
// no suitable conversion function from "lambda []void ()->void" to "void (*)()" exists
}
for (int i = 0; i < N_DRIVERS; i++)
{
drivers[i] = &ACAN2517(i);
ACAN2517 *driver = drivers[i];
drivers[i]->begin_functional( [driver]{driver->isr();}, driver);
// Not sure how to get this to work in a lambda...
}
for (int i = 0; i < N_DRIVERS; i++)
{
drivers[i] = &ACAN2517(i);
ACAN2517 *driver = drivers[i];
drivers[i]->begin_functional( call_ACAN_isr, driver);
// OK
}
}
//===
// User code end
//===
// from esp32-hal-gpio.c
extern void __attachInterruptFunctionalArg(uint8_t pin, voidFuncPtrArg userFunc, void * arg, int intr_type, bool functional)
{
// ...
}
// from Arduino `FunctionalInterrupt.cpp`
void attachInterrupt(uint8_t pin, std::function<void(void)> intRoutine, int mode)
{
// use the local interrupt routine which takes the ArgStructure as argument
__attachInterruptFunctionalArg (pin, (voidFuncPtrArg)interruptFunctional, new InterruptArgStructure{intRoutine}, mode, true);
}
void IRAM_ATTR interruptFunctional(void* arg)
{
InterruptArgStructure* localArg = (InterruptArgStructure*)arg;
if (localArg->interruptFunction)
{
localArg->interruptFunction();
}
}
I can't use a C++ member function here because of its implicit this argument.
Yes, that is exactly the problem and this can't be solved without extra code, if your API did not provide something what lets you store some additional data like the this pointer.
What you simply can do is:
Write your own wrapper and register the callback to the original handler. But that creates another indirection which increases the latency.
The other way is not as simple, but a bit less slow:
Write your own interrupt handler and callback registration. As you have the original sources of the arduino libs, you simply can replace the stuff around the attachInterrupt function.
Sorry, but there is no magic way to generate a data store for this without any additional software.

Arduino: Use Timer in c++ class

I am trying to use a timer to repeatedly change the PWM Output over time to have a smooth transition when the brightness changes. I keep getting this error when trying to compile the code:
/Users/jt/Documents/Arduino/libraries/SingleColorLight/SingleColorLight.cpp: In constructor 'CSingleColorLight::CSingleColorLight(int)':
/Users/jt/Documents/Arduino/libraries/SingleColorLight/SingleColorLight.cpp:13:58: error: cannot convert 'CSingleColorLight::DimmerCallback' from type 'void (CSingleColorLight::)(void*)' to type 'void ()(void)'
ets_timer_setfn(&Dimmer, this->DimmerCallback, NULL);
Here is my code:
class CSingleColorLight {
private:
int pin;
int intensitySetPoint;
int intensityActual;
int percentageBuffer;
ETSTimer Dimmer;
int dimmerCount;
public:
CSingleColorLight(int _pin);
bool setIntensity(int _intensity);
int getIntensity();
bool getStatus(void);
bool setStatus(bool _status);
void DimmerCallback(void*);
};
and in the cpp file:
void CSingleColorLight::DimmerCallback(void*) {
if(dimmerCount>0){
dimmerCount--;
intensityActual++;
} else if(dimmerCount<0){
dimmerCount++;
intensityActual--;
} else {
ets_timer_disarm(&Dimmer);
}
analogWrite(pin, percentageToTime[intensityActual]);
return;
}
It asks for a pointer, right? Any idea how to fix this?
Thanks a lot!
If you want DimmerCallback to take a void* argument, then you need to name it, like
void CSingleColorLight::DimmerCallback(void* x)
but you are not using the void* in the code. It looks like you should just get rid of it, so it would be
void CSingleColorLight::DimmerCallback()
int the cpp and
void DimmerCallback();
in the header.
A void* argument is a pointer that can point to any data type, it is not the same as void which is just no argument.

error: cannot convert 'void (CApp::*)()' to 'void (*)()' for argument '1' to 'void Mix_HookMusicFinished(void (*)())'

I'm trying to create a C++ application using SDL and SDL_Mixer for audio, and am trying to follow this tutorial. However, using SDL_Mixer's Mix_HookMusicFinished() isn't working, giving the error: argument of type 'void (CApp::)()' does not match 'void (*)()'
I've researched this error, and it seems the problem is that cleanMusic is a member function of CApp. I can't tell how to solve the problem, however, since most problems similar to this one are centered around pthread_create(). My cleanMusic() function needs to be able to access music_ which is a private variable of CApp. How can I resolve the error?
Here is the code for CApp.h, CApp::handleKeyEvents(), and CApp::cleanMusic(). Let me know if you need to see something else.
CApp.h
#ifndef CAPP_H
#define CAPP_H
#include <SDL.h>
#include <SDL_mixer.h>
#include <gl\gl.h>
#include <gl\glu.h>
class CApp {
private:
bool isRunning_;
private:
void cleanMusic();
private:
SDL_Surface *surfDisplay_;
Mix_Music *music_;
bool isRotating_;
GLfloat rQuad_;
public:
CApp();
int run();
public:
bool initialize();
void handleEvents(SDL_Event *event);
void loopData();
void render();
void clean();
public:
void handleKeyEvents(SDL_KeyboardEvent *key);
};
#endif // CAPP_H
CApp::handleKeyEvents()
#include "CApp.h"
void CApp::handleKeyEvents(SDL_KeyboardEvent *key) {
switch(key->keysym.sym) {
case SDLK_m:
if (key->state == SDL_PRESSED) {
if(music_ == NULL) {
music_ = Mix_LoadMUS("resources\\audio\\boop.wav");
Mix_PlayMusic(music_, 0);
Mix_HookMusicFinished(cleanMusic);
isRotating_ = true;
} else {
Mix_HaltMusic();
cleanMusic();
isRotating_ = false;
}
}
break;
default:
break;
}
}
CApp::cleanMusic()
#include "CApp.h"
void CApp::cleanMusic() {
Mix_FreeMusic(music_);
music_ = NULL;
}
Two changes. cleanMusic needs to be static.
static void cleanMusic();
Second, you register the hook with:
Mix_HookMusicFinished(&CApp::cleanMusic);
Since your method is now static, music_ needs to be static as well.
static Mix_Music *music_;
This means that there will only be one instance of this variable shared between all instantiations of CApp. Since I haven't seen all of your code, I can't tell if this is an issue.
void cleanMusic(); is what is known as a member function. A member function is very different from a normal function. The reason your compiler complains is because Mix_HookMusicFinished expects a normal function pointer of type void (*)(), but you are trying to pass a member function pointer of type void (CApp::*)(). These types are incompatible.
The simplest solution is just to make cleanMusic a normal function and Mix_Music *music; a global:
Mix_Music *music;
void cleanMusic() {
Mix_FreeMusic(music);
music = NULL;
}
Another way is to make them both static members:
static void cleanMusic();
static Mix_Music *music_;