I have been experimenting with different ways to process commands through Serial inputs. I am looking to have the functionality to both input commands to execute programming, as well as change program settings, such as by changing the value of various variables.
First, I should mention that I am still fairly new to Arduino, especially working with Serial inputs, so this may be a perplexing approach to those that have more experience. What I have now involves reading and storing Serial inputs into a character array one character at a time, with an index variable keeping track of the current "write" position in the array. If the next character read is '\n', then rather than storing the character, the current contents of the array are compared against a series of preset commands, and various actions are executed. Then, the array is "emptied" with memset() and index is reset to 0.
While this does certainly work, the programming structure seems to be a bit messy and cumbersome for just a few simple commands. I imagine there must be a far simpler and more elegant approach, so I was hoping to see what everyone's thoughts are. How do you structure Serial input commands? What is the simplest and best organized way to structure this?
Also, here's an example of what I have been trying:
//commands
#define COMMAND1 "command1"
#define COMMAND2 "command2"
#define COMMAND3 "command3"
#define COMMAND4 "command4"
char input[100],input_buffer;
int setting1=0,setting2=0,input_mode=0,index=0;
void setup() {
Serial.begin(9600);
}
void loop() {
if (Serial.available()){
input_buffer=Serial.read();
if (input_buffer!='\n'){
input[index]=input_buffer;
index++;
}
else {
switch (input_mode){
case 0:
if (strcmp(input,COMMAND1)==0){
//do something
Serial.println(COMMAND1);
}
else if (strcmp(input,COMMAND2)==0){
//do something else
Serial.println(COMMAND2);
}
else if (strcmp(input,COMMAND3)==0){
input_mode=1;
//next input changes setting1
Serial.println(COMMAND3);
}
else if (strcmp(input,COMMAND4)==0){
input_mode=2;
//next input changes setting2
Serial.println(COMMAND4);
}
break;
case 1:
//change setting1
setting1=atoi(input);
Serial.println(setting1);
input_mode=0;
break;
case 2:
//change setting2
setting2=atoi(input);
Serial.println(setting2);
input_mode=0;
break;
}
memset(input, '\0', sizeof input);
index=0;
}
}
}
You're protocol does not have a way of detecting data corruption or dropped packets. Also, it would be more efficient to send a "command id" as a byte inside the actual packet. If you do this, you can get around having to use strcmp().
Lastly, if you want any inspiration code to reference when making your own serial protocol, feel free to check out the source code for this library or this one.
You can try playing with this simple example to see if it does what you want:
const byte pinLED = LED_BUILTIN;
#define BUFF_SIZE 128 //# message buffer size
#define MSG_TIMEOUT 1000 //mS timeout from last character received
//create a buffer to hold the received message
byte
RxBuffer[BUFF_SIZE];
//used to timeout a message that is cut off or too slow
unsigned long
lastCharacterTime;
//this creates a type that is a pointer to a command function, used in the structure below
typedef void (*CmdFunc)();
//command structure
typedef struct
{
char *szCommand; //command string e.g. "ON", "OFF"; use caps for alpha characters
CmdFunc CmdFunction; //pointer to the function that will execute if this command matches
}structCmdStruct;
//for this example, there are two commands; on and off
#define NUM_CMDS 2
//prototypes of command functions
//need these prototypes before the following CmdTable variable or the compiler
//will complain
void CommandON( void );
void CommandOFF( void );
structCmdStruct CmdTable[NUM_CMDS] =
{
{
.szCommand = "ON", //capitalized command for turning the LED on
.CmdFunction = &CommandON //if "ON" is received, run CommandON
},
{
.szCommand = "OFF", //off
.CmdFunction = &CommandOFF //off command function
}
};
void setup( void )
{
//usual start-up drivel; 115200 baud and an LED pin to show functions work
Serial.begin(115200);
pinMode( pinLED, OUTPUT );
digitalWrite( pinLED, LOW );
}//setup
void loop( void )
{
//can be called as part of a loop doing lots of other things
ReceiveMessage();
}//loop
void ReceiveMessage( void )
{
byte
ch;
static byte
idxMsg = 0;
//if we've received at least one character, check for a timeout
if( idxMsg > 0 )
{
//if MSG_TIMEOUT mS has passed since the last character, reset the
//buffer index
//(i.e. ignore whatever message this might have been...)
if( (millis() - lastCharacterTime) > MSG_TIMEOUT )
{
idxMsg = 0;
Serial.println( "Message timeout..." );
}//if
}//if
//if there's a character waiting...
if( Serial.available() > 0 )
{
//update the last char timer
lastCharacterTime = millis();
//read the character
ch = Serial.read();
//if it's the newline (example delimter for typical serial monitor messages)
//this is the end of the message...
if( ch == '\n' )
{
//insert a NULL to "null-terminate" the message
RxBuffer[idxMsg] = '\0';
//and go process it
ProcessMsg();
//when we come back, reset the buffer index ready for the next msg
idxMsg = 0;
}//if
else
{
//still receiving the msg
//convert alphabetical characters to uppercase (don't have to do this but be sure
//to watch case when typing in the serial monitor!) and store in the buffer
RxBuffer[idxMsg] = toupper(ch);
//bump the buffer pointer but not beyond the limits of the buffer
idxMsg++;
if( idxMsg == BUFF_SIZE )
idxMsg--;
}//else
}//if
}//ReceiveMessage
void ProcessMsg( void )
{
//got a message...walk through the command table to try to find a match
for( int i=0; i<NUM_CMDS; i++ )
{
//if the received message matches the NULL-terminated command string, we
//have a match...
if( strcmp( RxBuffer, CmdTable[i].szCommand ) == 0 )
{
//...so execute that command's function
CmdTable[i].CmdFunction();
//and leave
return;
}//if
}//for
//if we make it here we didn't find a match in the command table
//so just send an "Unrecognized command" response and what the offending message was
Serial.print( "Unrecognized command: " ); Serial.println( (char *)RxBuffer );
}//ProcessMsg
// Command Functions
//these functions execute the commands. They are pointed to in the command table
void CommandON( void )
{
//turn on the LED and send a message
Serial.println( "LED on" );
digitalWrite( pinLED, HIGH );
}//CommandON
void CommandOFF( void )
{
//turn off the LED and send a message
Serial.println( "LED off" );
digitalWrite( pinLED, LOW );
}//CommandOFF