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("!");
}
/**
* Setup
*/
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.