I've got a little problem which would be a no brainer in my native language (Tcl/Tk) because Tcl is almost typeless and can execute any string as a command... but in strongly typed languages like C and CPP it seems much harder.
I have a lot of events coming at me from a semi-smart user control panel -- button presses, sliders, all kinds of stuff. When the event arrives I know its name, and thinking ahead I chose to name them so they could occupy unique positions in a 2D array (100 = page 1 - widget 0, 203 = page 2 - widget 3, and so on).
So far so good. Now, I could write an enormous (50+ options?) switch statement, where "if widgetcode == 103 do this, if widgetcode ==104 do that," and so on, tediously. It would be kind of ugly and long.
So I was wondering if there's some more elegant way to build, I dunno, an index of functions? an index of pointers to functions? this gets me into more indirection than I've ever attempted in Arduino cpp, so please forgive ignorance, I'm not even sure what search terms to use to google this topic.
Would it be possible somehow in Arduino cpp to implement "execute the command found in commands_array(1,4)" instead of "if widgetcode == 104 then foo"?
If the widget returned a value, could I even pass that received value on to the command that was invoked via the array? so if widgetcode is 310 (page 3, number 10) and that's a slider, then the value sent by the slider event can be passed to whatever command handles widget 310?
I realise this is a hopelessly naive question. if someone will tell me what this would properly be called in C-land, then I can google for that search term and learn more.
Tazling:
So far so good. Now, I could write an enormous (50+ options?) switch statement, where "if widgetcode == 103 do this, if widgetcode ==104 do that," and so on, tediously. It would be kind of ugly and long.
is not a "switch function". While you're Googling, also search for "c switch statement".
Tazling:
Sorry, switch statement, you are quite right. Edited subject line.
What I meant was that a bunch of "if (x == y) {" statements is NOT a switch statement. That's a different control structure that is cleaner in many applications. See: switch...case - Arduino Reference
Sounds like a security hole that should have been closed 20 years ago.
User-interface code usually ends up looking like spaghetti unless you have a big framework like a web browser supporting your simple code. Don't sweat it if it gets messy.
nah, there are not 99 buttons per page! that would indeed be terrifying. I just wanted a nice clean way of getting page and widget in one ID string which could also be used as an array index. The worst page has about 20 buttons on it. The page element of the name is just a sanity check to make sure that the touch screen is really on the page that the MCU thinks it's on.
The reason for so many buttons is that it's a USB game controller and the game has lots of commands and options. The first-generation controller used physical switches, and it took two Adafruit Trellises plus a pot and several more standalone switches and I never did manage to get enough buttons... ran out of gpio despite using 2 Leonardos...
Anyway, yes I have been using a big switch statement to interpret the events from e.g. the 32 Trellis buttons. But I thought I could get a little more elegant with the next version.
I've done some reading today on function pointers. The only disappointment is that apparently an array of function pointers can only hold pointers to functions with identical (a) return types and (b) arguments. That complicates life a bit but is not a deal breaker.
My project is undergoing a pretty major revamp for version 2. Along with the touchscreen, I'm stepping up to a Due instead of Leonardos (can get by with just one MCU) and introducing a bunch of new features like RTC and possibly even FRAM for saving user preferences and history.
Tazling:
The only disappointment is that apparently an array of function pointers can only hold pointers to functions with identical (a) return types and (b) arguments.
How could it be otherwise? If you want to use an index to reference and call a function from an array, then all the functions have to have the same signature (term you were looking for -- a function's parameter types and return type). If the functions could have different signatures, then you'd have to have a bunch of conditional statements (if / else or switch) in order to determine how many parameters to put in your function call ---- i.e. the situation you were trying to avoid at the start of this thread.
Tazling:
I have a lot of events coming at me from a semi-smart user control panel
Having read quickly through all the Replies it seems to e that you need to up the user control panel from semi-smart to smart to make life easier for the Arduino.
You have told us nothing about what the Arduino will be doing in response to all these messages? Maybe it's just lighting up one of 3 LEDs?
I'm not sure if it would work in your setup, but you might think about arrays of objects instead of arrays of functions, then you could use method calls appropriate to the object type.
class Button {
private:
bool on = false;
public:
void toggle() {
this->on = !this->on;
}
bool isOn() {
return (this->on);
}
};
class Slider {
private:
int val = 0;
public:
int getVal() {
return (this->val);
}
void setVal(int v) {
this->val = v;
}
};
Button buttons[4];
Slider sliders[4];
void setup() {
int i;
Serial.begin(9600);
Serial.println("Buttons:");
// print button values, toggle, print
for (i = 0; i < 4; i++ ) Serial.println(buttons[i].isOn());
for (i = 0; i < 4; i++ ) buttons[i].toggle();
for (i = 0; i < 4; i++ ) Serial.println(buttons[i].isOn());
Serial.println("Sliders:");
// print slider values, set value and print
for (i = 0; i < 4; i++ ) Serial.println(sliders[i].getVal());
for (i = 0; i < 4; i++ ) sliders[i].setVal(i + 2);
for (i = 0; i < 4; i++ ) Serial.println(sliders[i].getVal());
}
void loop() {
}
Or, perhaps, have a family of different classes all derived from the same base class. Then, you could have an array of base class pointers. The polymorphism feature of C++ will then allow derived class methods to be called from base class pointers.
Tazling:
So far so good. Now, I could write an enormous (50+ options?) switch statement, where "if widgetcode == 103 do this, if widgetcode ==104 do that," and so on, tediously. It would be kind of ugly and long.
But that's exactly what the switch statement is for. Its designed so the compiler can have a
chance of optimizing a multi-way dispatch.
Keep large switch statements neat by using one line only per case, calling a named function to
handle each case. If you need to use fall-throughs, comment that the fall-through is deliberate.