OneButton Library in wrapper Class

Hi there, hope everyone’s doing well!
This is my first post here, so a tiny bit of context: I’m a .NET developer, mainly C#, having fun on weekends with some hardware!

I’m using a D1 mini and ended up trying to use a OneButton object (from the OneButton awesome Library) as a private member of a custom class, and got into trouble, I believe this is my lack of knowledge of the language itself.

I’m struggling with the attachClick method and its relationships to callbackFunction or static methods alongside modifying a private member of my custom class.

For troubleshooting purposes I’ve created a cutdown project that shows problems.

So considering the following class header:

#include <OneButton.h>

#ifndef Thing_h
#define Thing_h

class Thing
{
public:
  Thing(int btnPin);
  void setup();
  void tick();

private:
  OneButton _btn;
  int _btnPin;
  bool _state;
};

#endif

it’s implementation:

#include "Arduino.h"
#include "Thing.h"

// Constructor
Thing::Thing(int btnPin)
{
    _btnPin = btnPin;
    _state = false;
}

// Public Methods
void Thing::setup()
{
    _btn = OneButton(_btnPin, true, true);
    _btn.attachClick([]() {
        _state = !_state;
    });
}

void Thing::tick()
{
    _btn.tick();
}

and the arduino sketch for good measure:

#include "Thing.h"

int btnPin = D2;

Thing thing(btnPin);

void setup()
{
  thing.setup();
}

void loop()
{
  thing.tick();
}

This will throw the following error on compilation:

Thing.cpp:15:9: error: 'this' was not captured for this lambda function
  _state = !_state;

modifying to the following instead:

_btn.attachClick([this]() {
   _state = !_state;
});

will throw:

sketch\Thing.cpp: In member function 'void Thing::setup()':

Thing.cpp:16:6: error: no matching function for call to 'OneButton::attachClick(Thing::setup()::__lambda0)'
    });

After that I thought I would try another approach as it seems you can pass a static method as an argument to attachClick and so tried the following:

class header:

#include <OneButton.h>

#ifndef Thing_h
#define Thing_h

class Thing
{
public:
  Thing(int btnPin);
  void setup();
  void tick();

private:
  OneButton _btn;
  int _btnPin;
  bool _state;
  static void process();
};

#endif

and implementation:

#include "Arduino.h"
#include "Thing.h"

// Constructor
Thing::Thing(int btnPin)
{
    _btnPin = btnPin;
    _state = false;
}

// Private
void Thing::process()
{
    _state = !_state;
}

// Public Methods
void Thing::setup()
{
    _btn = OneButton(_btnPin, true, true);
    _btn.attachClick(process);
}

void Thing::tick()
{
    _btn.tick();
}

this will throw the following

Thing.h:16:8: error: invalid use of member 'Thing::_state' in static member function
   bool _state;
Thing.cpp:13:5: error: from this location
     _state = !_state;

And so I’m running out of ideas as to what I might be doing wrong.
Am I not following specific programming patterns or trying to do something that is totally possible but taking the wrong approach?

To anyone trying to help out, thank you in advance!

First, I am far, far from your level of expertise. Everything I know of C++ comes from three years of Arduino projects: Halloween and Christmas displays, Escape Room games, and of course IOT.

Plus I read these forums a lot.

Using classes is exceedingly rare here, but if you need multiple instances of an object, then make a library. As I understand, it's essentially the same.

Sorry that I have nothing to add here, but I am interested in the solution you choose and the forum doesn't have a method to "watch" a thread without posting to it.

Hi Steve, thanks for the reply!

I think “expertise” is a bit of a stretch, I’ve started playing with arduino stuff for real something like last month :slight_smile:

I’ll check-out the differences between classes and libraries, thanks for the heads-up, is that something you got a hint for from the error messages or just a common practice?

tguillard:
Hi Steve, thanks for the reply!

I think “expertise” is a bit of a stretch, I’ve started playing with arduino stuff for real something like last month :slight_smile:

I’ll check-out the differences between classes and libraries, thanks for the heads-up, is that something you got a hint for from the error messages or just a common practice?

I followed this morse code library tutorial just to learn how to write a library. Defining a class looks just like a library construction, but I’ve never used a class. (Or namespace).

If you don’t need multiple instances of the object, just keep it simple and write the code as a function. I don’t think there is any advantage to using a class or library for code that is only used once.

I don’t know if it’s common practice but it’s the way I have always coded.

tguillard:
I'll check-out the differences between classes and libraries, thanks for the heads-up

Ignore that advice, it has nothing to do with your issue. Arduino "libraries" are no different than regular C++ code. They just happen to be stored in particular directories that allow them to be included in any sketch.

The problem is that class instance functions have a different signature than regular functions. OneButton's attachClick() function is looking for a pointer to the latter. You can read about it here: Standard C++. Even though you used a Lambda, I guess it's still seen as an instance function.

Static functions have the correct signature and you can use them. However, as you discovered, a static function can't directly access instance data. It needs a pointer to the particular instance whose data you want to access.

Fortunately, the OneButton class has an overload of the attachClick() function that will take a function pointer and a pointer to parameters to send to the callback when it's invoked. Take a look at it in OneButton.h and OneButton.cpp.

gfvalvo:
Fortunately, the OneButton class has an overload of the attachClick() function that will take a function pointer and a pointer to parameters to send to the callback when it's invoked. Take a look at it in OneButton.h and OneButton.cpp.

Try something like this. Compiles but untested:

#include "OneButton.h"

class Thing
{
  public:
    Thing(uint8_t btnPin);
    void tick();

  private:
    OneButton _btn;
    bool _state;
    static void callBack(void *ptr);
};

Thing::Thing(uint8_t btnPin) : _btn(btnPin, true, true), _state(false) {
  _btn.attachClick(callBack, this);
}

void Thing::callBack(void *ptr) {
  Thing *thingPtr = (Thing *)ptr;
  thingPtr->_state = !thingPtr->_state;
}

void Thing::tick() {
  _btn.tick();
}


Thing thing(3);

void setup() {
}

void loop() {
  thing.tick();
}

gfvalvo, thank you very much for the reply!

I was afraid someone would mention pointers :slight_smile:

I will brush up on the topic and re-open my books, I’ve not done that in a while now, so probably a good idea.

gfvalvo, you were perfectly right!
This compiles just fine and works great on breadboard as well, I just have to adapt to my real thing now (and still brush up on pointers).

Thank you very much for the lift, much appreciated!

tguillard:
gfvalvo, you were perfectly right!
This compiles just fine and works great on breadboard as well, I just have to adapt to my real thing now (and still brush up on pointers).

Thank you very much for the lift, much appreciated!

Pointers are my Achilles Heel.

Glad you got a solution.

Glad it worked for you. BTW, if the code to process the clicks gets more involved, then the '->' notation required to access instance variables from the static function may become burdensome. In that case, it would be easier to call an instance function from the static function:

#include "OneButton.h"

class Thing
{
  public:
    Thing(uint8_t btnPin);
    void tick();

  private:
    OneButton _btn;
    bool _state;
    void processClick();
    
    static void callBack(void *ptr);
};

Thing::Thing(uint8_t btnPin) : _btn(btnPin, true, true), _state(false) {
  _btn.attachClick(callBack, this);
}

void Thing::callBack(void *ptr) {
  Thing *thingPtr = (Thing *)ptr;
  thingPtr->processClick();
}

void Thing::tick() {
  _btn.tick();
}

void Thing::processClick() {
  _state = !_state;
}


Thing thing(3);

void setup() {
}

void loop() {
  thing.tick();
}

It would definitely be worthwhile for you to review pointers. IMO, you can't do any serious programming in the language without them. C++ does have references that can do a lot of things that pointers can (and some things pointers can't). But there are times when nothing but a good old-fashioned C pointer will do the job.