Serial parsing with Serial Proxy

I am working on a security robot that has a speakjet shield. I have modified the sparkfun speakjet example to look for '/' and parse that as a command instead of text to speak.

It works great with usb and xbee. My problem is when I use serial proxy to make the xbee IP addressable (using tinker proxy build). The first time is works correctly but the send time it will speak the command instead of seeing it as a command.

I had a hard time getting the parsing to work originally. It has been great since but now I am working on my control app/website and I found this bug.

Here is the code I am using to parse incoming command/text from the serial port. Which currently is a Xbee, will be Wifi Shield later on. But it looks like it is a code problem as it works without serial proxy.

I am sure this code can be written better but this is how I got it work at all.

//Function: getMessage(char *)
//Description: Retrieves a string from the Serial port. Doesn't return the string until a carriage return character is detected.
//Inputs: None
//Outputs: char * message - The message received on the serial port.
//Returns: Nothing
//usage: getMessage(english_sentance);
void getMessage(char * message, char * message2)
{
    char cmd[5]="";    //Array to hold the code char during serial read
    char in_char=0;    //Create a character to store the incoming byte from the serial port.
    int i = 0;
    int value = 0;
    char cmd1[1]="";
    
    
    //Wait for a character to come into the serial port (Old way, causes code to stop here)
    //while(Serial.available() <=0);

    //Check for charcter from Xbee/Wifi and if not resume to main loop else process charcaters
    if (Serial.available())
    {
        
    //Copy the incoming character to our variable.
    in_char=Serial.read();
   
    if (in_char == 0x2f)
    {
      while(in_char != 0x0D)
      {
        while(Serial.available() <= 0);
        in_char = Serial.read();
        cmd[i] = in_char;
  
        i++;
      }
      cmd[i]='\0';
      
      cmd1[0] = cmd[0];
            
      for (int x = 0; x <= i; x++)
      {
       cmd[x] = cmd[x+1];
        
      }

      
      value = atoi(cmd);
      
      
      pCmd(cmd1[0], value);    
      
      
      
    }
    else {
    //For SpeakJet Phrases
    //Keep retreiving characters until the 'end of sentance(0x0D)' character is received.
    while(in_char != 0x0D){
        if (in_char == 0x5C)
        {
          for(int i = 0; i <= 2; i++){      //Change to look for digits, would allow shorter codes
          while(Serial.available() <=0);    //Now wait for the next character...
          in_char = Serial.read();     
          cmd[i]=in_char;
          }
     
         int code = atoi(cmd);  //convert code from char array to single int
         *message++=code;  //put code inline with message
         while(Serial.available() <=0);
         in_char = Serial.read(); 
        }
        else {
        *message++=in_char;    //Every time we receive a character we should add the character to the message string.
        *message2++=in_char;
        while(Serial.available() <=0);    //Now wait for the next character...
        in_char = Serial.read();            //and copy it to the variable again.
    }
    }
   *message='\0';    //Strings must end with a Null terminator so we need to add this to our message.
   *message2='\0';

    speak();
    }   
   
    }
    return;
}

So basically it looks at the first char to determine if its a command or not. So serial proxy must being doing something different to cause the If statement not to see it as a /.

Thanks for the help. After this I can work on my robot control app and website.

I am using these parts.

with

Tinker Proxy site that has source
http://code.google.com/p/tinkerit/wiki/TinkerProxy

I am thinking it might better to talk directly to the serial port from asp.net. I saw that it is possible. I still prefer the above but I could plug the Xbee to my VM and have the web server talk directly.

    char cmd1[1]="";

Why a one element array? What's wrong with

char cmd1;
    if (in_char == 0x2f)
      while(in_char != 0x0D)
    while(in_char != 0x0D){
        if (in_char == 0x5C)

Drat. Left me cheat sheet at home. What's a 0x2f? There is no key on my keyboard with that symbol on it. 0x5C? 0x0D? That's a '\n' isn't it?

while(Serial.available() <= 0);
        in_char = Serial.read();
        cmd[i] = in_char;
  
        i++;

Let's hope "i" doesn't get too large.
Like 1.

Let's hope "i" doesn't get too large.
Like 1.

Why would i == 1 cause a problem? cmd is a 5 element array.

0x2f is '/' which is my char to check to see if the rest of the input is a command. Is that isn't there then it is text to speak.

Not sure why I did cmd1[1]. I can change that but I don't think it will make a difference.

cmd is just for the /f4 which is move forward speed 4. pcmd function handles that.

cmd1 separates the command 'f' from the value 4 to be passed. And then I remove the '/' from cmd.

I am sure there is a better way to handle this.

I realize i need to go back and comment rename variables as this was my first attempt and I left it once it worked till now.

Edit:

The speak function also looks for text with <digits> for sounds codes for the speakjet.

Oops!
Read "cmd1" as "cmd".
Apologies.

No problem. I will have to clean that up. I dont' usually do that.

So what do you guys think? Have asp.net talk to serial port or maybe change the char for command? Its really weird. My robot will actually say "slash f" lol. It has to be the way that the serial to ip proxy software handles characters.

Is there another way I can check for command character?

The forward slash may have significance to some software along the way. Is there some other character you could use? Semi-colon maybe? Or question mark?

The reason I asked about the 0x2f value was that it is much easier (for me) to understand code that uses actual ascii characters in the if tests. The compiler doesn't care.

Thanks. I will try another character.

I know what you mean, I had just used the hex because the code was based on an example. So I would just use single quotes

if (in_char == '#')

So I would just use single quotes

Just like that.

So I tired a few different characters and nothings work. The funny thing it works the first time after a reboot.

Okay did some messing around and I think what is happening the speak function isn't ending or somewhere the code is waiting. My robot has a lcd with sensor output and I noticed after i send a command using serial proxy it stops updating. If I send another command it updates once. So something is causing the serial input to be waiting.

On a side note, would the functions run faster if I didn't have it recreate the array's every iteration? This function is called from loop continuously. Or maybe move this code to the loop()? I do have speed problems in reacting to a object not being fast enough.

I think it is hanging up here

//Check for charcter from Xbee/Wifi and if not resume to main loop else process charcaters
    if (Serial.available())
    {

Since it works the first time and then after that is goes back to the loop and then gets stuck somewhere.

I think that it things the serial is available and then since there isn't a '/' it goes to the speak part and waits for text.

There must be something with serial proxy causing the serial to be triggered as available.

Does serial.available mean there is data to receive?

Would my function work without the serial.available and just constantly read the serial port or is there a better way because that would probably slow things down.

Edit: I added this and now it works every other time.

after speak();

     in_char=Serial.read();

Edit:
So I did a work around, not very clean but it works.

After speak();

 speak();
    if (Serial.available())
    {
     in_char=Serial.read();
     }

Which still causes it every other command but if I send a Carriage Return before I send a command it works. With the if statement above it doesn't affect the commands if I am not using serial proxy and talking directly to the serial port.

If I get some time I might peak at the serial proxy source and see if I can figure it out. It didn't affect my first robot but it was much simpler.

I guess I found a bug in serial proxy.

Thanks for all the help it got me thinking looking in the right direction. This will get my by for now.

If anyone has a better work around let me know.

Now I am seeing another problem.

I am not seeing the return data in asp.net, I do when I telnet to serial proxy.

Here is my code in vb.net for the website.

    Protected Sub btnCamera_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnCamera.Click

        clientSocket.Connect(serverIP2, serverPort2)

        Dim serverStream As NetworkStream = clientSocket.GetStream()
        Dim outStream As Byte() = System.Text.Encoding.ASCII.GetBytes(vbCrLf + cmdCamera + vbCrLf)
        Dim inStream(10024) As Byte
        serverStream.Write(outStream, 0, outStream.Length)
        serverStream.Flush()
        serverStream.Read(inStream, 0, CInt(clientSocket.ReceiveBufferSize))
        Dim returndata As String = System.Text.Encoding.ASCII.GetString(inStream)
        lblReturn.Text = returndata
        clientSocket.Close()

    End Sub

It was displaying the data returned but now I get nothing. I doubt the asp.net code would cause the problem. I might need a better solution to my work around. I don't know.

Darn it! I just realized even though it seemed to sort of work the main loop isn't running, the LCD isn't updating until I click a command so back to the drawing board!!! =(

Edit:
I tired this program is this post and the same result. So weird!!

http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1218590225

SerialServer
http://itp.nyu.edu/~dbo3/SerialServer/SerialServer.html

At least now I can see the data in the debug window.

I think it is hanging up here

Nope. Incoming serial data triggers an interrupt. During that interrupt, the incoming character is added to a buffer. All that Serial.available() is doing is looking to see if the head and tail of the ring buffer used are at the same place (no data available) or at different places (some data available - the amount being the difference in the head and tail values).

Thanks. that helps.

For now I did this.

 Dim outStream As Byte() = System.Text.Encoding.ASCII.GetBytes(vbCrLf + cmdCamera + vbCrLf + vbCrLf)

I added another Carriage Return at the end of the command sequence.

It came to me in my sleep. Good thing cause I didn't want to think about it all weekend since I won't be able to work on it.

Now in the mean time to speed up my code. I am thinking of moving this function into the loop function. Would that make a difference?

MobileWill:
Thanks. that helps.

For now I did this.

 Dim outStream As Byte() = System.Text.Encoding.ASCII.GetBytes(vbCrLf + cmdCamera + vbCrLf + vbCrLf)

I added another Carriage Return at the end of the command sequence.

It came to me in my sleep. Good thing cause I didn't want to think about it all weekend since I won't be able to work on it.

Now in the mean time to speed up my code. I am thinking of moving this function into the loop function. Would that make a difference?

Actually, I'd have to recommend the opposite. Your code is a bit too disorganized and redundant. You have a getMessage function that does a lot more than just get a Message. It gets it, and conditionally processes based on the first character. Actually, it doesn't even get the whole message first. You also have two separate blocks of code that both are reading in the serial data, and then processing it. That's just a bad design and prone to errors.

You really should refactor your code. First, modify getMessage so it does just one single thing. Get an entire message. Don't look at what's in the message. Just get the entire string (without code duplication), ideally with both a start and end character, but still workable with just an end character.

After you have your entire message, then you can parse and process it. Check the first character, if it's your 'command' character, send the string to a separate command process function. If it isn't, send it to your speech processing function.

The overhead involved with function calls is effectively non-existent when dealing with serial data. You aren't going to see any sort of performance gain by eliminating a few functions, but you will significantly impact code readability and the likelihood of introducing more bugs into your code.

Thanks for all the help. Here is my rewrite, that works well, very well. Now I can string commands together.

boolean getMessage2()
{
  char in_char = '\0';    //Create a character to store the incoming byte from the serial port.
  inCount = 0;
 
  if (Serial.available() > 0) {
    in_char=Serial.read();
   if (in_char=='#'){
    while(in_char != '!'){
      while(Serial.available() <=0);
       in_char = Serial.read();   
       inString[inCount]=in_char; 
      inCount++;
      //Serial.println(in_char);
    }
        
    inString[inCount+1]='\0';    //Strings must end with a Null terminator so we need to add this to our message.
  
    return true;
   }
  }
  
 return false;
}

void parseCmd()
{
    char cmd[5]="";
    char cmdChar;
    int cmdValue=0;
    int code=0;
   
      if (inString[0] == '/'){
        
        for (int x = 0; x < inCount; x++)
        {
           cmd[x] = inString[x+1];
        
        }
        
        cmdChar = cmd[0];
        
        for (int x = 0; x < inCount-1; x++)
        {
           cmd[x] = cmd[x+1];
        }
        
        cmdValue = atoi(cmd);
        
        pCmd(cmdChar, cmdValue);

      }
      
      else {
      
      int x=0;
      for (int i = 0; i < inCount-1; i++){
        
       speakCmd[x]=inString[i];
        
      if (inString[i] == 0x5C){
      
      cmd[0]=inString[i+1];
      cmd[1]=inString[i+2];
      cmd[2]=inString[i+3]; 
      
      code = atoi(cmd);
      
      speakCmd[x]=code;

      i = i + 3;
      }
      x++;

      }
                inString[inCount-1]='\0';
                speakCmd[inCount-1]=0x0D;
                speakCmd[inCount]='\0';
                speak();
    
      }
}

Just need to comment the code.