Parsing char from Serial.read()

hello,

i am using the following code to read serial data via xbee:

char incoming;
int val0, val1, val2; 

void setup() {
  Serial.begin(9600);
}

void loop() {
  while (Serial.available() > 0) {
    incoming = Serial.read();
    Serial.print(incoming);
  }
}

==========================
and the serial monitor output something like:

66a37b120c
66a37b120c
66a37b120c

i want to parse the data and extract the numerical value and assign them to variables, something like: val0=66, val1=37, val2=120.
i am quite new to arduino and tried to look up solution but with no luck... can anyone kindly help me with this or point me to the right resource? many thanks!

-wyPreformatted text

Are the numerical values always in the same place and always the same length in the received data ?

hello, thank you for the quick response.
the format stays the same: always number a number b number c, but the lengh of digits for each number varies from 1 to 3 (so x to xxx)..

Separate atoi() or sscanf() perhaps with strtok or a few .parseInt().
The .parseInt() is the easiest (if it works).

What else is there ? Are there a CarriageReturn and/or LineFeed at the end ?
How much time is there between packets of data ?

An example of parsing the string to give you some ideas

int val0;
int val1;
int val2;

void setup()
{
  Serial.begin(115200);
  while (!Serial);
  parse("1a2b3c");
  parse("66a37b120c");
  parse("999a456b1c");
  parse("6a5b1234c");
}

void loop()
{
}

void parse(char * input)
{
  val0 = atoi(strtok(input, "a"));
  val1 = atoi(strtok(NULL, "b"));
  val2 = atoi(strtok(NULL, "c"));
  Serial.println(val0);
  Serial.println(val1);
  Serial.println(val2);
  Serial.println();
}

For this to work the received data needs to be in a zero terminated array of chars. As each character is received put it in the array, increment the array index and save a '\0' to the next position. Once a termination character has been received call the parsing function to extract the data

hi,

i did try the .parseInt() but it seems that there are sudden irregular jumps to high numbers.
i was also sending the same data to SuperCollider, and the code i attached above gives the same result without the high number jump issue. i parsed the data in SuperCollider but don't know how to do it in arduino due to my unfamiliarity with it... as for the time interval between the packets of data, the source xbee is sending out packet every 10 mill-seconds.

You can parse your data without buffering it.

char incoming;
int accu = 0;
int val0, val1, val2;

void setup() {
  Serial.begin(9600);
}

void loop() {
  bool gotSomething = false;
  if (Serial.available()) {
    gotSomething = true;
    incoming = Serial.read();
    switch (incoming) {
      case '0' ... '9':
        accu *= 10;
        accu += incoming - '0';
        break;
      case 'a':
        val0 = accu;
        accu = 0;
        break;
      case 'b':
        val1 = accu;
        accu = 0;
        break;
      case 'c':
        val2 = accu;
        accu = 0;
        break;
    }
  }
  if (gotSomething) {
    Serial.print(F("rxed char '"));
    Serial.write(incoming);
    Serial.print(F("' val0 = "));
    Serial.print(val0);
    Serial.print(F(", val1 = "));
    Serial.print(val1);
    Serial.print(F(", val2 = "));
    Serial.println(val2);
  }
}

You could try this. Based on the sketches in my tutorial Arduino Software Solutions
Note: atoi is very sloppy returns 0 for any error. The code below checks the conversion is valid.
Sample output

readStringUntil_nonBlocking.ino
 got a line of input '66a37b120c'
66 37 120
 got a line of input 'abc'
 Invalid a number !!
Invalid input abc

The sketch

// readStringUntil_nonBlocking.ino
// Reads chars into a String until newline '\n'
// https://www.forward.com.au/pfod/ArduinoProgramming/SoftwareSolutions/index.html
// Pros: Simple. Non-Blocking
// Cons: Does not return until the until_c char is found. i.e. no timeout or length limit

String input;
int a, b, c;

void setup() {
  Serial.begin(9600);
  for (int i = 10; i > 0; i--) {
    Serial.print(' '); Serial.print(i);
    delay(500);
  }
  Serial.println();
  Serial.println(F("readStringUntil_nonBlocking.ino"));
  input.reserve(80); // expected line size
  // flush old input
  Serial.setTimeout(100); // set 100mS timeout
  Serial.readString();
  Serial.setTimeout(1000); // reset default timeout
}

// read Serial until until_c char found, returns true when found else false
// non-blocking, until_c is returned as last char in String, updates input String with chars read
bool readStringUntil(String& input, char until_c) {
  while (Serial.available()) {
    char c = Serial.read();
    input += c;
    if (c == until_c) {
      return true;
    }
  }
  return false;
}

bool parseInput(String& input) {
  // parse it
  int idxA = input.indexOf('a');
  int idxB = input.indexOf('b');
  int idxC = input.indexOf('c');
  if ((idxA < 0) || (idxB < 0) || (idxC < 0)) {
    Serial.println(F(" Invalid data!!"));
    return false;
  }
  //else
  String aNo = input.substring(0, idxA);
  String bNo = input.substring(idxA + 1, idxB);
  String cNo = input.substring(idxB + 1, idxC);
  // convert to ints can check they are valid ints
  a = aNo.toInt();
  if (String(a) != aNo) {
    Serial.println(F(" Invalid a number !!"));
    return false;
  }
  b = bNo.toInt();
  if (String(b) != bNo) {
    Serial.println(F(" Invalid b number!!"));
    return false;
  }
  c = cNo.toInt();
  if (String(c) != cNo) {
    Serial.println(F(" Invalid c number!!"));
    return false;
  }
  return true;
}

void loop() {
  if (readStringUntil(input, 'c')) { // read until find terminating 'c'
    input.trim(); // remove any leading trailng white space \r\n
    Serial.print(F(" got a line of input '")); Serial.print(input); Serial.println("'");
    if (parseInput(input)) {
      Serial.print(a); Serial.print(' '); Serial.print(b); Serial.print(' '); Serial.print(c); Serial.println();
    } else {
      Serial.print(F("Invalid input ")); Serial.print(input); Serial.println();
    }
    input = ""; // clear after processing for next line
  }
}

hi, thank you for the reply. i am not quite sure if i follow your answer... am i write to say that i need to convert the char variable incoming into a char array, and at the end of the char array, add the '\0' ? after that, i can use the parse function in your code to parse the data?

hello, thank you for the reply. the code seemed to work briefly but then it went off, here is the data posted:

rxed char '1' val0 = 416, val1 = 127, val2 = 136
rxed char 'a' val0 = 1, val1 = 127, val2 = 136
rxed char '7' val0 = 1, val1 = 127, val2 = 136
rxed char '3' val0 = 1, val1 = 127, val2 = 136
rxed char '
' val0 = 1, val1 = 127, val2 = 136
rxed char '1' val0 = 1, val1 = 127, val2 = 136
rxed char '5' val0 = 1, val1 = 127, val2 = 136
rxed char 'c' val0 = 1, val1 = 127, val2 = 7315
rxed char '4' val0 = 1, val1 = 127, val2 = 7315
rxed char 'b' val0 = 1, val1 = 4, val2 = 7315
rxed char '
' val0 = 1, val1 = 4, val2 = 7315
rxed char 'a' val0 = 0, val1 = 4, val2 = 7315
rxed char '6' val0 = 0, val1 = 4, val2 = 7315
rxed char 'a' val0 = 6, val1 = 4, val2 = 7315
rxed char '8' val0 = 6, val1 = 4, val2 = 7315
rxed char 'a' val0 = 8, val1 = 4, val2 = 7315
rxed char 'c' val0 = 8, val1 = 4, val2 = 0
rxed char '2' val0 = 8, val1 = 4, val2 = 0

i don't know why this is the case..

also this code is quite similar to how i parse the data in the program SuperCollider, which is attached below, but i don't know how to translate this in arduino, perhaps you can help me with this?

(
~charArray = [];
~readValue = Routine({
	var ascii;
	{
		ascii = ~port.read.asAscii;  //this reads the Serial port data

		case
		{ascii.isDecDigit}{~charArray=~charArray.add(ascii.digit)}
		{ascii==$a}{~val0 = ~charArray.convertDigits; ~charArray = []}
		{ascii==$b}{~val1 = ~charArray.convertDigits; ~charArray = []}
		{ascii==$c}{~val2 = ~charArray.convertDigits; ~charArray = []}
		[~ldr0Val, ~ldr1Val, ~ldr2Val].postln;

	}.loop
}).play
)

If you feed garbage in, you get garbage out.

the last data you entered was "a6a8ac2" which is not the format you specified.

No, it is not.

My code does not buffer anything, your code collects a string and converts it afterwards.

1 Like

Here is a slightly modified version that does not output anything on cr or lf.

char incoming;
int accu = 0;
int val0, val1, val2;

void setup() {
  Serial.begin(115200);
}

void loop() {
  bool gotSomething = false;
  if (Serial.available()) {
    incoming = Serial.read();
    gotSomething = incoming != 10 && incoming != 13;
    switch (incoming) {
      case '0' ... '9':
        accu *= 10;
        accu += incoming - '0';
        break;
      case 'a':
        val0 = accu;
        accu = 0;
        break;
      case 'b':
        val1 = accu;
        accu = 0;
        break;
      case 'c':
        val2 = accu;
        accu = 0;
        break;
    }
  }
  if (gotSomething) {
    Serial.print(F("rxed char '"));
    Serial.write(incoming);
    Serial.print(F("' val0 = "));
    Serial.print(val0);
    Serial.print(F(", val1 = "));
    Serial.print(val1);
    Serial.print(F(", val2 = "));
    Serial.println(val2);
  }
}
rxed char '1' val0 = 0, val1 = 0, val2 = 0
rxed char 'a' val0 = 1, val1 = 0, val2 = 0
rxed char '2' val0 = 1, val1 = 0, val2 = 0
rxed char 'b' val0 = 1, val1 = 2, val2 = 0
rxed char '3' val0 = 1, val1 = 2, val2 = 0
rxed char 'c' val0 = 1, val1 = 2, val2 = 3
rxed char '1' val0 = 1, val1 = 2, val2 = 3
rxed char '2' val0 = 1, val1 = 2, val2 = 3
rxed char '3' val0 = 1, val1 = 2, val2 = 3
rxed char 'a' val0 = 123, val1 = 2, val2 = 3
rxed char '3' val0 = 123, val1 = 2, val2 = 3
rxed char '4' val0 = 123, val1 = 2, val2 = 3
rxed char '5' val0 = 123, val1 = 2, val2 = 3
rxed char 'b' val0 = 123, val1 = 345, val2 = 3
rxed char '1' val0 = 123, val1 = 345, val2 = 3
rxed char '3' val0 = 123, val1 = 345, val2 = 3
rxed char '4' val0 = 123, val1 = 345, val2 = 3
rxed char '5' val0 = 123, val1 = 345, val2 = 3
rxed char '7' val0 = 123, val1 = 345, val2 = 3
rxed char 'c' val0 = 123, val1 = 345, val2 = 13457

I think it just works as specified.

it is supposed to be in the format i specified, which i did get from the serial monitor of the code for the source sensors (and it is also consistent with what i get in the supercollider, which receives the serial data also via xbee)

//reading ldr sensor data and send them via xbee
int ldr0 = A0, ldr0Val = 0; 
int ldr1 = A1, ldr1Val = 0; 
int ldr2 = A2, ldr2Val = 0; 

void setup() {
  Serial.begin(9600);
}

void loop() {
  // Read ldr sensor values from analog pins
  ldr0Val = analogRead(ldr0); ldr1Val = analogRead(ldr1); ldr2Val = analogRead(ldr2);

  // Print the ldr sensor values, seperate by alphabetical marker
  Serial.print(ldr0Val); Serial.print('a');
  Serial.print(ldr1Val); Serial.print('b');
  Serial.print(ldr2Val); Serial.println('c');
}

and the monitor is printing data such as this:

429a123b147c
434a125b150c
435a126b151c
431a124b149c
430a123b147c
434a126b150c
435a126b151c
431a125b149c

but when the data is sent to the 2nd xbee, i tried the code you posted, but it doesn't turn up the way... i don't know if this is a garbage in garbage out issue, but certainly any help is appreciated..

@wyang03 correct. The example parse() function requires the data to be in a C style string

@Whandall
i tried the revised code, but still has the similar problem... here are the sample of data posted:

rxed char 'c' val0 = 354, val1 = 122, val2 = 131
rxed char '3' val0 = 354, val1 = 122, val2 = 131
rxed char '5' val0 = 354, val1 = 122, val2 = 131
rxed char '5' val0 = 354, val1 = 122, val2 = 131
rxed char 'a' val0 = 355, val1 = 122, val2 = 131
rxed char '1' val0 = 355, val1 = 122, val2 = 131
rxed char '2' val0 = 355, val1 = 122, val2 = 131
rxed char 'b' val0 = 355, val1 = 12, val2 = 131
rxed char '1' val0 = 355, val1 = 12, val2 = 131
rxed char '3' val0 = 355, val1 = 12, val2 = 131
rxed char 'c' val0 = 355, val1 = 12, val2 = 13
rxed char '1' val0 = 355, val1 = 12, val2 = 13
rxed char '3' val0 = 355, val1 = 12, val2 = 13
rxed char '3' val0 = 355, val1 = 12, val2 = 13
rxed char '2' val0 = 355, val1 = 12, val2 = 13
rxed char '2' val0 = 355, val1 = 12, val2 = 13
rxed char '2' val0 = 355, val1 = 12, val2 = 13
rxed char '3' val0 = 355, val1 = 12, val2 = 13
rxed char '5' val0 = 355, val1 = 12, val2 = 13
rxed char '1' val0 = 355, val1 = 12, val2 = 13
rxed char '3' val0 = 355, val1 = 12, val2 = 13
rxed char '1' val0 = 355, val1 = 12, val2 = 13
rxed char 'b' val0 = 355, val1 = 11515, val2 = 13
rxed char '5' val0 = 355, val1 = 11515, val2 = 13
rxed char '2' val0 = 355, val1 = 11515, val2 = 13
rxed char 'a' val0 = 52, val1 = 11515, val2 = 13
rxed char 'c' val0 = 52, val1 = 11515, val2 = 0
rxed char '2' val0 = 52, val1 = 11515, val2 = 0
rxed char 'b' val0 = 52, val1 = 2, val2 = 0
rxed char '5' val0 = 52, val1 = 2, val2 = 0
rxed char '3' val0 = 52, val1 = 2, val2 = 0
rxed char 'a' val0 = 53, val1 = 2, val2 = 0
rxed char '3' val0 = 53, val1 = 2, val2 = 0

as you can see it works for a while (until the bold line) and the val1 starts to deviate.. and eventually all three val-s do the same..

As you can see, you fed the sketch garbage:

'c'

'3'
'5'
'5'
'a'

'1'
'2'
'b'

'1'
'3'
'c'

'1'
'3'
'3'
'2'
'2'
'2'
'3'
'5'
'1'
'3'
'1'
'b'

'5'
'2'
'a'

'c'

'2'
'b'

'5'
'3'
'a'

'3'

So you are adding a \r\n at the end of the data
So read line ending in \n and then parse it.

My suggested sketch has a lot more error checking in it
change
if (readStringUntil(input, 'c')) { // read until find terminating 'c'
to
if (readStringUntil(input, '\n')) { // read until find terminating '\n'
to read in a line of data

@drmpf
hi, thank you for the help! i tried the revised code and couldn't get anything post on the serial monitor. here is the code, can you kindly tell me if i am missing something?

String input;
int a, b, c;

void setup() {
  Serial.begin(9600);
  for (int i = 10; i > 0; i--) {
    Serial.print(' '); Serial.print(i);
    delay(500);
  }
  Serial.println();
  Serial.println(F("readStringUntil_nonBlocking.ino"));
  input.reserve(80); // expected line size
  // flush old input
  Serial.setTimeout(100); // set 100mS timeout
  Serial.readString();
  Serial.setTimeout(1000); // reset default timeout
}

// read Serial until until_c char found, returns true when found else false
// non-blocking, until_c is returned as last char in String, updates input String with chars read
bool readStringUntil(String& input, char until_c) {
  while (Serial.available()) {
    char c = Serial.read();
    input += c;
    if (c == until_c) {
      return true;
    }
  }
  return false;
}

bool parseInput(String& input) {
  // parse it
  int idxA = input.indexOf('a');
  int idxB = input.indexOf('b');
  int idxC = input.indexOf('c');
  if ((idxA < 0) || (idxB < 0) || (idxC < 0)) {
    Serial.println(F(" Invalid data!!"));
    return false;
  }
  //else
  String aNo = input.substring(0, idxA);
  String bNo = input.substring(idxA + 1, idxB);
  String cNo = input.substring(idxB + 1, idxC);
  // convert to ints can check they are valid ints
  a = aNo.toInt();
  if (String(a) != aNo) {
    Serial.println(F(" Invalid a number !!"));
    return false;
  }
  b = bNo.toInt();
  if (String(b) != bNo) {
    Serial.println(F(" Invalid b number!!"));
    return false;
  }
  c = cNo.toInt();
  if (String(c) != cNo) {
    Serial.println(F(" Invalid c number!!"));
    return false;
  }
  return true;
}

void loop() {
  if (readStringUntil(input, '\n')) { // read until find terminating 'c'
    input.trim(); // remove any leading trailng white space \r\n
    Serial.print(F(" got a line of input '")); Serial.print(input); Serial.println("'");
    if (parseInput(input)) {
      Serial.print(a); Serial.print(' '); Serial.print(b); Serial.print(' '); Serial.print(c); Serial.println();
    } else {
      Serial.print(F("Invalid input ")); Serial.print(input); Serial.println();
    }
    input = ""; // clear after processing for next line
  }
}

many thanks!!

If you got nothing then that suggests you don't have a new line appended. As noted in the comments at the top
Cons: Does not return until the until_c char is found. i.e. no timeout or length limit
Other sketches on my Arduino Software Solutions that don't have those limitations BUT first

"serial monitor output something like:" in #1 needs to be refined

In your first post you read AND WRITE to Serial.
If Serial is connected to Xbee than are you really writing back to re-transmit the data?
What board are you using?
Is the Xbee connected to D0/D1 pins?

Try connecting the Xbee to another serial. If the board does not have one try SoftwareSerial for Xbee and dump the received output to Serial (USB)

So Skip the parsing for the moment and just dump what you are receiving.

void setup() {
  Serial.begin(115200);  // the debug output, faster as more output written then being received
  another_serial.begin(9600); // the Xbee connection at 9600
}

void loop() {
  if (another_serial.available()) {
    int i;
    i = another_serial.read();
    Serial.print(F(" 0x"));
    if (i<16) { Serial.print('0');}
    Serial.print(i,HEX);  Serial.print(' ');
    Serial.print((char)i);
  }
}

Then input from Xbee of 66a37b120c should print (if there is a newline on the end)

0x36 6 0x36 6 0x61 a 0x33 3 0x37 7 0x62 b 0x31 1 0x32 2 0x30 0 0x63 c 0x0A

Edit- A possible explanation of the garbage output in #16, #17 is that the input is almost continuous and the extra debug output is causing chars to be missed due to RX buffer overflow.
My tutorial on Arduino Serial I/O for the Real World covers that in detail,
BUT the first suggested fix in that tutorial is to increase the baud rate of the Serial debug connection. So try the sketch above with the Xbee on a different serial connection and with Serial at a much higher baudrate