UPDATE: Shortly after posting this, I switched to a different method using a custom data serialization protocol, which you can learn more about here.
One of the coolest things about C++ is how flexible the language is. If you’re faced with a problem that has no apparent solution, chances are you’re just not approaching it from the right angle.
I was recently working on a program designed to run on desktop PCs and on Arduino boards. One component of this program stores function calls as variables, then executes them at some later time. These function calls, their parameters, and their timings aren’t known in advance and can change over the course of the program’s runtime. To make matters worse, most of these were non-static member functions, meaning they had to operate on an existing object. To make this component work, I needed some way of storing an iterable array of diverse, dynamic, and unpredictable calls that worked just as well on a microcontroller as they did on a Core i7.
After a bit of research I stumbled upon the <functional> header in the C++ Standard Library. This looked perfect! Using std::bind, you can specify a function (including member functions) and its parameters, store it in a variable, then run the function by calling the variable itself as a function. For instance, the following code creates a new object called myobject, stores a call to myobject.myMethod() in a variable, then runs the method from the variable.
MyClass myobject; function<void()> call = std::bind(&MyClass::myMethod, &myobject); call();
Note how the second parameter to std::bind is the object that the method is called on. Any additional parameters are passed to myMethod as that method’s parameters.
For use on a PC, this worked great! However, running the Standard Library on an Arduino is not exactly recommended. Instead, I did some brainstorming and came up with an alternate (and hopefully more cross-compatible) solution.
The Action Wrapper Approach
I first approached this issue by going over my requirements. To make this work, I needed a system that could:
- Access multiple classes
- Make both static and member function calls
- Pass a complete set of parameters to the function
- Execute the function at a specific time
What I ended up with was sort of a wrapper to each function call where the call and its parameters is defined as a class, and you instantiate the class as an object before passing it to back to the program. Let me explain…
I created an abstract class (we’ll call it Action) that stores two things: the time that the Action will execute, and an empty method definition (called run()) for the code that will run when the Action executes. Inheriting this class are specific Action classes that map to different functions in the program. For example, there might be a MyClassMyMethodAction class that runs MyMethod on a MyClass object:
MyClass myobject; int time = 10000; // Run the Action after 10 seconds MyClassMyMethodAction action = MyClassMyMethodAction(&myobject, 10000); /* ... Update the program's runtime ... */ if (runtime >= action.getTime()) { action.run(); }
The MyClassMyMethodAction class stores the reference to myobject and the execution time in action. MyClassMyAction::run() implements Action::run() by calling MyClass::myMethod on myobject. The biggest drawback to this is that for each function you want to be able to call, you have to create a new class derived from Action. You also have to store each parameter in each Action instance, which could quickly chew up memory. However, it works fine for me since it limits the amount of damage I can do by passing the wrong parameters or calling the wrong method at the wrong time: I can only work within the confines of the functions already defined, yet I can always create a new definition if necessary. Another benefit is that because these classes all inherit from the same base class, I can line up each call in a single array by execution time and simply iterate over the array, checking periodically to see whether to run the next one.
int currentActionIndex = 0; Action *actions[] = { new MyClassMyMethodAction(&myobject, time1), new MyClassAnotherMethodAction(&myobject, time2, param1), new MyClassThirdMethodAction(&myobject, time3, param2, param3), new RegularMethodAction(time4, param4) } /* ... Update the program's runtime ... */ if (runtime >= actions[currentActionIndex]->getTime()) { actions[currentActionIndex]->run(); currentActionIndex += 1; }
It may be a cumbersome workaround, but it works! You can see a sample Arduino sketch that leverages the workaround by clicking here. In this sample, Actions are the same as Transitions, and the Show class manages the timing and execution of Transitions. This sketch rotates a strip of 8 LEDs through a series of animations every 5 seconds.
Got any questions or comments? Feel free to leave them below!
Hello, facing the same kind off problem, i’m interested by your solution but the sample code link leads to a 404 error. Would it be possible for you to post this sample code again ? Thank you.
Hey Vincent, I updated the link to the code sample so it should work now. Thanks for pointing that out!