My C/C++ skills are a bit rusty, and I've mostly been working in Java for the past few years. Now I just started playing around with Arduino, and made a simple button class. I want to add an event listener, so I did something like this:
class MyButton{
public:
MyButton(byte pin);
bool isPressed();
bool wasToggled();
bool wasPressed();
void eventLoop();
inline void setListener(MyButtonListener* listener) { _listener = listener; }
private:
byte _pin;
boolean _lastToggledState = false;
MyButtonListener* _listener;
};
class MyButtonListener{
public:
virtual void onPressed() = 0;
private:
};
The eventLoop()
method (which is intended to be called from the Arduino loop()
function ), invokes the onPressed()
method in the listener class:
void MyButton::eventLoop(){
if( wasPressed() && _listener ){
_listener->onPressed();
}
}
So far, things are okay. But I can't figure out how to actually assign and use the listener in the main Arduino file. Coming from Java, I'm used to just doing something like
myBtn.setListener( new MyButtonListener(){
void onPressed(){
Serial.println("Pressed");
toggleLed(); // toggleLed() is a method in the main Arduino file
}
});
I got it to work in a very convoluted way, by declaring a new class which takes the toggleLed()
method as an argument (because it can't be accessed from within the new class otherwise):
class BtnListener : public MyButtonListener{
public:
BtnListener(void* toggleFunction) : _toggleFunction(toggleFunction){ };
private:
void (*_toggleFunction)();
void onPressed(){
Serial.println("Pressed");
_toggleFunction();
};
};
myBtn.setListener( new BtnListener(toggleLed) );
Surely there must be a more convenient way of doing something like this in C++? It's doable (but ugly) with one listener - I can't even imagine the horror of having 10 buttons which all need different listener implementations...
Since the Arduino IDE by default doesn't seem to include <functional.h>
, I wasn't able to use the answer using std::function<void()>
. However, after some experimenting I realized there was an easier way, which also has the benefit of being able to model the listener.
The listener class simply contains function pointers to each listener callback function, and a constructor that takes an argument for each callback. Then it's very convenient to just create a new instance of the listener class and pass each callback as a lambda.
class MyButton{
public:
inline void setListener(MyButtonListener* listener) { _listener = listener; }
private:
MyButtonListener* _listener;
}
class MyButtonListener{
public:
MyButtonListener(void* onPressed, void* onToggled) : onPressed(onPressed), onToggled(onToggled) {};
void (*onPressed)();
void (*onToggled)();
};
void MyButton::eventLoop(){
if( _listener ){
if( wasPressed() ){
_listener->onPressed();
}
if( wasToggled() ){
_listener->onToggled();
}
}
}
myBtn.setListener(
new MyButtonListener(
// onPressed
[](){
Serial.println("Pressed");
toggleLed();
},
// onToggled
[](){
Serial.println("Toggled");
}
)
);
Not sure if there are any drawbacks with this solution, but it works, is readable and is fit for use on Arduino.