Command pattern
The command pattern might help you.
You could define one function for each cmdtype, and call the corresponding function when you receive some input via terminal.
This approach was also suggested by @PhilipKendall. The problem is that Arduino-C++ isn't really a complete C++, and std::map<std::string, action_fn> isn't available.
Arduino Strings should be avoided, too, because they use too much heap.
Interactive shell
I wrote a small Arduino library for a similar use-case, and asked a related question on Codereview.
The corresponding functions have either no argument, one string argument or one integer argument (e.g. for booleans or serial numbers).
Here's a modified version, for your use-case:
#include "command_invoker.h" // https://github.com/EricDuminil/arduino_interactive_shell/tree/master/arduino_interactive_shell
/*
- Define your logic in those functions:
*/
bool sendDisabled = true;
void allowSend(int32_t allowed) {
Serial.print("Sending data is now ");
Serial.println(allowed ? "allowed" : "disallowed");
sendDisabled = !allowed;
}
bool cannotSend() {
if (sendDisabled) {
Serial.println("Not sending anything. Sorry. Call SEND 1 first.");
}
return sendDisabled;
}
void valcn() {
if (cannotSend()) return;
Serial.println("Let's call rtnProtocols with VALCN!");
}
void firmv() {
if (cannotSend()) return;
Serial.println("Let's call rtnProtocols with FIRMV!");
}
void rqspc(int32_t serial_number) {
if (cannotSend()) return;
Serial.print("Let's call RQSPC with ");
Serial.print(serial_number);
Serial.println("!");
}
/**
*/
void setup() {
Serial.begin(115200);
// Assign commands here:
command_invoker::defineIntCommand("SEND", allowSend, F(" 0/1 (Allow SEND or not)"));
command_invoker::defineIntCommand("RQSPC", rqspc, F(" 123456 (doc here)"));
command_invoker::defineCommand("VALCN", valcn, F(" (doc here)"));
command_invoker::defineCommand("FIRMV", firmv, F(" (doc here)"));
Serial.println(F("Console is ready!"));
Serial.print(F("> "));
}
/*
- Saves bytes from Serial.read() until enter is pressed, and tries to run the corresponding command.
- http://www.gammon.com.au/serial
*/
void processSerialInput(const byte input_byte) {
static char input_line[MAX_COMMAND_SIZE];
static unsigned int input_pos = 0;
switch (input_byte) {
case '\n': // end of text
Serial.println();
input_line[input_pos] = 0;
command_invoker::execute(input_line);
input_pos = 0;
Serial.print(F("> "));
break;
case '\r': // discard carriage return
break;
case '\b': // backspace
if (input_pos > 0) {
input_pos--;
Serial.print(F("\b \b"));
}
break;
default:
// keep adding if not full ... allow for terminating null byte
if (input_pos < (MAX_COMMAND_SIZE - 1)) {
input_line[input_pos++] = input_byte;
Serial.print((char) input_byte);
}
break;
}
}
/**
- Loop and wait for serial input.
*/
void loop() {
while (Serial.available() > 0) {
processSerialInput(Serial.read());
}
delay(50);
}
The code is a bit long, but most of it is generic (setup/loop/processSerialInput), and it wouldn't get more complex with more functions. You'd simply add functions and callbacks inside setup.
Example
The above code seems to work fine on my ESP8266. As a bonus, you can get a list of every defined command by typing an unknown one (e.g. 'help'), and you can use hex numbers as parameter (e.g. 'RQSPC 0xFF00FF'):
Console is ready!
> help
No argument
'help' not supported. Available commands :
FIRMV (doc here).
RQSPC 123456 (doc here).
SEND 0/1 (Allow SEND or not).
VALCN (doc here).
> FIRMV
No argument
Calling : FIRMV()
Not sending anything. Sorry. Call SEND 1 first.
> SEND 1
Calling : SEND(1)
Sending data is now allowed
> RQSPC 12345
Calling : RQSPC(12345)
Let's call RQSPC with 12345!
> FIRMV
No argument
Calling : FIRMV()
Let's call rtnProtocols with FIRMV!
> VALCN
No argument
Calling : VALCN()
Let's call rtnProtocols with VALCN!
> SEND 0
Calling : SEND(0)
Sending data is now disallowed
> VALCN
No argument
Calling : VALCN()
Not sending anything. Sorry. Call SEND 1 first.