Efficient means to parse several integers from string

My application involves unidirectional transfer of a string from one xBee (connected to a PC) to another (connected to Arduino Uno).

The message sent from the PC is a string of the format "KP123 KI123 KD123" where 123 can be any integer between 0-100. At the receiving end, I have the following code to process the string.

#include "SoftwareSerial.h"

// use software serial for xBee communication
SoftwareSerial xBeeSerial(4,5);  // use pin 4(RX) and 5(TX) for software serial

// PID control variables
long kp = 10;
long ki = 1;
long kd = 1;
String pid_string;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600); // Serial output to console
  xBeeSerial.begin(9600);
}

void loop() {
  // Loop to update PID values
  if (xBeeSerial.available() > 0){
    pid_string = xBeeSerial.readStringUntil('Z');
    pid_loop_counter = 0;
    kp = (pid_string.substring(pid_string.indexOf('P')+1,pid_string.indexOf('P')+3)).toInt();
    ki = (pid_string.substring(pid_string.indexOf('I')+1,pid_string.indexOf('I')+3)).toInt();
    kd = (pid_string.substring(pid_string.indexOf('D')+1,pid_string.indexOf('D')+3)).toInt();

    // debugging messages
    Serial.print(pid_string);Serial.print("   ");
    Serial.print(kp);Serial.print("   ");
    Serial.print(ki);Serial.print("   ");
    Serial.println(kd);
  }

The code works and I am able to update the values for kp, ki and kd but I feel like this is a "brute force" way of doing things. There are two questions that I have in mind:

  1. I would like to know if there's a more efficient and elegant way of processing the String variable?

  2. Is there a way to read the message based on events rather than polling the buffer as I have done above? I would like to be able to process the data only when I message has been received.

Appreciate if someone can guide me along. Thanks in advance.

You may want to see if stoi will convert the integers and ignore the alphabetic characters.

The most efficient way is probably to process each character as they arrive. Good example here: http://www.gammon.com.au/forum/?id=11425&reply=1#reply1.

There are other, less efficient but simpler, ways: strtok + atoi, sscanf...

The String class will get you in trouble in the Arduino environment as memory fragmentation can cause mysterious crashes when RAM is at a premium.

Calling indexOf() to locate each character twice is silly. You aren't expecting the character to move between calls are you?

@guix Thanks for sharing. That was an excellent post. I think I will go along with this solution.

@aarg I think I have read something along that line about the String variable but I have never really understood why it can be so bad... how does usage of String lead to fragmentation?

@PaulS I agree that it's silly. I didn't bother to change it as I think that at best that would serve as an incremental improvement to my existing code. I am actually looking for a different idea from what I was doing.

To parse a string, we implement a parser. This example is simple enough to do with an LALR(0) parser.

start in state 'waiting for K'.

read a character. If we can't read a character, then we are done

if we are in state "waiting for K", then
   if the character is 'K', then move to state 'waiting for variable name'
   if the character is space, then do noting
  otherwise, we have a bad string

if we are in state 'waiting for variable name', then
   if the character is 'P', 'I', or 'D', then:
      record the fact that this is the variable. Easy way to do this is to set a pointer or reference to int
      zero the selected variable
      move to state 'reading the number'
  otherwise, we have a bad string

If we are in state 'reading the number', then
  if the character is a space, then
    move to state "waiting for K"
  if the character is a digit, then
    multiply the selected variable by 10, 
    add the value of the digit  (ch-'0') to the selected variable
  otherwise, we have a bad string

Now "read the character" might mean several things - waiting for a keypress, waiting for serial, advancing an index along a string.

Note that this code will incorrectly accept "KP K". It will set P to zero and treat no number as zero, and it will go "meh" when the input runs out after K. Depends on how much error checking you want to do.

ckong80: how does usage of String lead to fragmentation?

Here is the typical sad story. Here are some gory details. And if you're still not sure, read this.

Cheers, /dev

You should have formulated your question with complete details of how the sender formats its strings:

Will there always be two blank spaces between each segment of information? Will the segments always be sent in the same order, without missing any segment or switching their orders? Things like this you can probably control, right? Don't make it difficult for yourself to interpret when you receive it, ok?

Here is my solution but to each his own way:

http://goo.gl/BDXvhG

What you have to do is to send a '\n' at the end of each communication. The receiver will buffer anything until it sees the '\n', toss it and hand over all received to the sscanf function for conversion.

I agree with others that condemned String class. I might use it if it is only one String and I run reserve() on it first. Your way of using it is beyond control :D

https://www.arduino.cc/en/Reference/StringReserve

aarg: The String class will get you in trouble in the Arduino environment as memory fragmentation can cause mysterious crashes when RAM is at a premium.

Not any more surely, I thought they fixed the bugs... I've hammered it for hours. But perhaps there are still more bugs?

Mark,

I think the bugs may have been fixed but the way that people use String class to manipulate strings without regard to how little memory arduino has, is non-fixable. The way OP is using it may fragment the memory and you can't even use reserve() to alleviate the situation.

@ckong80, have a look at the parse example in Serial Input Basics

…R

You are right. I could have articulated my string format clearer. Do pardon me for that as I have little experience posting on forums. Will do better next time.

Thanks for sharing your idea. It's really quite elegant and short too. I have already implemented the solution proposed by guix and it worked like a charm, but I will archive your solution too as I know it will come in handy for my future projects.

cheers ck

liudr: You should have formulated your question with complete details of how the sender formats its strings:

Will there always be two blank spaces between each segment of information? Will the segments always be sent in the same order, without missing any segment or switching their orders? Things like this you can probably control, right? Don't make it difficult for yourself to interpret when you receive it, ok?

Here is my solution but to each his own way:

http://goo.gl/BDXvhG

What you have to do is to send a '\n' at the end of each communication. The receiver will buffer anything until it sees the '\n', toss it and hand over all received to the sscanf function for conversion.

I agree with others that condemned String class. I might use it if it is only one String and I run reserve() on it first. Your way of using it is beyond control :D

https://www.arduino.cc/en/Reference/StringReserve

Wow. That's just brilliant! This is a must read for absolute beginners such as myself. I have printed a pdf copy for future reference.

Thanks for the excellent post.

Robin2: @ckong80, have a look at the parse example in Serial Input Basics

...R

ckong80: Wow. That's just brilliant! This is a must read for absolute beginners such as myself.

Thank you for your kind words. There is a lot of useful stuff for beginners in the Useful Links sticky.

...R