Parsing serial commands / programs (string stuff)

Hi,

I’m trying to write some code that polls a serial port (from an Electric Imp) listening for commands or ‘programs’ to execute (i.e. different patterns to run on my xmas lights.)

My command format is simple: up to 19 characters followed by a newline (\n).

I’ve cobbled together the following by looking at other people’s examples, but I think I’m munging strings somewhere, because my debug output is starting to look pretty funky.

#include <SoftwareSerial.h>

SoftwareSerial impSerial(8, 9); // RX on 8, TX on 9

const char programs[][10]={
    "RANDOM",
    "XMAS",
    "USA",
    "HWEEN"
};

char incoming[20]; // array to build the string in
char inChar=-1; // current character we just read
byte idx=0; // Where to stick inChar in incoming
byte cmd_valid = 0; // Did we understand the last?

void setup()  
{
 // Open the hardware serial port
  Serial.begin(19200);
  
  // set the data rate for the SoftwareSerial port
  impSerial.begin(19200);
}

void loop() // run over and over
{  
  // Send data from the software serial
  if (impSerial.available())
  {
    if(idx < 19)
    {
       inChar = impSerial.read();
 
       if (inChar != '\n')
       {
         incoming[idx] = inChar;
          idx++;
         incoming[idx] = '\0'; // null terminate
       
         impSerial.print(F("Added the character '"));
         impSerial.print(inChar);
         impSerial.println(F("' to the buffer."));
         impSerial.print(F("idx is now at position: "));
         impSerial.println(idx);
        }
    }
    
    if (idx >= 19 || inChar == '\n') // ran out of space or they typed a newline
    {
      // see if we recognize a command or a program name
      if (strcmp(incoming, "PON") == 0) // power on command
      {
        impSerial.println(F("Power on command"));
        cmd_valid = 1;
      }
      else if (strcmp(incoming, "POF") == 0) // power off command
     {
        impSerial.println(F("Power off command"));
        cmd_valid = 1;
     }
     else
     {
       // walk the array of program names and see if any match our string
       for (size_t i = 0; i < sizeof(programs); i++)
       {
         if (strcmp(incoming, programs[i]) == 0)
         {
           impSerial.print("Executing program '");
           impSerial.print(incoming);
           impSerial.println("'."); 
           cmd_valid = 1;
         }
       }
       
       if (cmd_valid != 1)
       {
          impSerial.println("Command or program name not recognized."); 
       }
     }
     
     // erase the string
       for (int j=0;j<19;j++) {
            incoming[j]=0;
        }
        idx=0;
        cmd_valid = 0;  
    }  
  }


//    Serial.write(impSerial.read());  // to the hardware serial

  // Send data from the hardware serial
  if (Serial.available())
    impSerial.write(Serial.read());  // to the software serial
}

(Once I got stuff working, I wanted to break more of it out into functions, but right now I’m just trying to make it recognize commands.)

My debug statements started to look like this:

Wed Jul 31 2013 23:55:21 GMT-0500 (CDT): Added the character 'H' to the buffer.
Wed Jul 31 2013 23:55:21 GMT-0500 (CDT): idx is now at position: 7
Wed Jul 31 2013 23:56:22 GMT-0500 (CDT): Added the character 'W' to theat' t.dioaot:
ddhoeue
ot:1Aehcrt f.iiopo3Added the character 'U' to the buffer.
Wed Jul 31 2013 23:56:22 GMT-0500 (CDT): idx is now at position: 14
Wed Jul 31 2013 23:56:24 GMT-0500 (CDT): Added the character 'S' to th ti otpio:8

I get the feeling that as usual, I’m trying to run before I can walk. Instead of hooking the Arduino and Electric Imp together, I’m thinking it would be better if I debugged locally using a USB-FTDI serial cable, until I have the string parsing code working… less places for the data to get lost, messed up, etc.

Still, if anyone sees any obvious errors with my code, or can suggest a better/cleaner way to do what I’m trying to do, I would appreciate hearing about it!

Thank you!

That looks about right and I don’t see anything specifically wrong with it, but the algorithm for deciding whether a complete message has been received looks a bit funny. Rather than try to figure out whether it would do what you need, it’s easier just to post a fragment that does work:

void handleSerial()
{
    const int BUFF_SIZE = 32; // make it big enough to hold your longest command
    static char buffer[BUFF_SIZE+1]; // +1 allows space for the null terminator
    static int length = 0; // number of characters currently in the buffer

    if(Serial.available())
    {
        char c = Serial.read();
        if((c == '\r') || (c == '\n'))
        {
            // end-of-line received
            if(length > 0)
            {
                handleReceivedMessage(buffer);
            }
            length = 0;
        }
        else
        {
            if(length < BUFF_SIZE)
            {
                buffer[length++] = c; // append the received character to the array
                buffer[length] = 0; // append the null terminator
            }
            else
            {
                // buffer full - discard the received character
            }
        }
    }
}

void handleReceivedMessage(char *msg)
{
    ...
}

The way I’d do the parsing in handleReceivedMessage() is to write a function which loops through your command array looking for a matching string and returns the index of the matching string - in effect, a command number. Then use a switch statement to execute the numbered command. This could incorporate the PON/POF commands too.

In the scientific calculator program, I blocked the verbs to 3 characters to facilitate parsing easily. Of course, you nearly have the variable-length stuff worked out, but here is my code anyway. It is extensible and reasonably fast:
http://forum.arduino.cc/index.php?PHPSESSID=pc7drrp7759jas0667mln7tsc3&topic=147550.0

Ray

Thank you both for your helpful advice and code examples! Between the two, I am seeing a lot of useful ways that I could be doing this more elegantly. I'll try applying some of these lessons learned to my code and hopefully it will get me over the hump!