Efficient method to split comma-separated values?

The outptut from my PC to the Arduino via Serial is something like <1023,0,511,1023,0,1032> but about 30 values long.

The following code captures complete packets starting with < and ending with >, and then splits the comma-separated values and stores them in array colours.

I am curious as to how other people do this, is my code ok?, how can it be improved?

//vars for Serial packet recieve
#define SOP '<'
#define EOP '>'

boolean started = false;
boolean ended = false;

char inData[80];
byte index;

//Vars to process packet
int colours[30] = {0};
char * val;
int count = 0;

void loop() 
{
	while(Serial.available() > 0)
	{
		char inChar = Serial.read();
		
		if(inChar == SOP)
		{
			index = 0;
			inData[index] = '\0';
			started = true;
			ended = false;
		}
		else if(inChar == EOP)
		{
			ended = true;
			break;
		}
		else
		{
			if(index < 79)
			{
				inData[index] = inChar;
				index++;
				inData[index] = '\0';
			}
		}
	}
  // We are here either because all pending serial
  // data has been read OR because an end of
  // packet marker arrived. Which is it?
  if(started && ended)
  {
		val = strtok (inData,",");
		
		colours[count] = atoi(val);
		
		while ((val = strtok (NULL, ",")) != NULL)
		{
			colours[++count] = atoi(val);
		}
		
	// Reset for the next packet
	started = false;
	ended = false;
	index = 0;
	inData[index] = '\0';
  }
}

is my code ok?

Yes, it is.

how can it be improved?

You could re-implement strtok(). But, why?

You could re-implement strtok(). But, why?

Because I more or less guessed to do it that way and would like to improve if I can! in what sort of way would you have done it?

but about 30 values long

If you have 30 values within < and > then an 80 byte string is too short to hold them all. Assuming that the maximum integer is 4 characters, the whole string can be 152 characters long.
Your code won't blow up if the received string is too long but it throws away everything after the 79th character including the > character, so it won't process the input.

Pete

in what sort of way would you have done it?

I know a little about the development cycle for code, and the amount of testing that goes into functions delivered in the C library, where strtok() is delivered. Knowing that the code was professionally developed, peer reviewed, and extensively tested, I would not begin to think that I could do a better job, using fewer resources, executing faster. I would, therefore, have used the strtok() function.

Thanks for sharing PaulS ..... it's usefull :slight_smile:

why not make a parser that reads until a ',' and put the just read field in the appropriate index of an integer array?

int fields[30]; // would take 60 bytes iso 152 you need for holding the string

and yes you need an index and maybe a few more..

some small sketch that parses a <1023,0,511,1023,0,1032> alike packet into an array of ints as bytes are received over serial.

//
//    FILE: parsePacket.ino
//  AUTHOR: Rob Tillaart
// VERSION: 0.1.00
// PURPOSE: demo
//    DATE: 2014-dec-29
//     URL:
//
// Released to the public domain
//

int field[30];
uint8_t idx = 0;
bool done = false;

void setup()
{
  Serial.begin(115200);
  Serial.println("Start ");
}

void loop()
{
  if (Serial.available() > 0)
  {
    done = parsePacket(Serial.read());
  }

  if (done) // ready to process a package
  {
    Serial.print("fields:\t");
    Serial.println(idx);
    for (int i = 0; i < idx; i++)
    {
      Serial.print(i);
      Serial.print("\t");
      Serial.println(field[i]);
    }
    Serial.println("done");
  }

  // do other things here
  
}

bool parsePacket(int c)
{
  bool ready = false;
  switch (c)
  {
    case '0'...'9':
      field[idx] = field[idx] * 10 + c - '0';
      break;
    case ',':
      idx++;
      field[idx] = 0;
      break;
    case '<':
      idx = 0;
      field[idx] = 0;
      break;
    case '>':
      idx++;
      ready = true;
      break;
    default:
      ; // skip
  }
  return ready;
}

A method to capture what is sent then parse out the individual data parts.

//zoomkat 11-12-13 String capture and parsing  
//from serial port input (via serial monitor)
//and print result out serial port
//copy test strings and use ctrl/v to paste in
//serial monitor if desired
// * is used as the data string delimiter
// , is used to delimit individual data 

String readString; //main captured String 
String angle; //data String
String fuel;
String speed1;
String altidude;

int ind1; // , locations
int ind2;
int ind3;
int ind4;
 
void setup() {
  Serial.begin(9600);
  Serial.println("serial delimit test 11-12-13"); // so I can keep track of what is loaded
}

void loop() {

  //expect a string like 90,low,15.6,125*
  //or 130,hi,7.2,389*

  if (Serial.available())  {
    char c = Serial.read();  //gets one byte from serial buffer
    if (c == '*') {
      //do stuff
      
      Serial.println();
      Serial.print("captured String is : "); 
      Serial.println(readString); //prints string to serial port out
      
      ind1 = readString.indexOf(',');  //finds location of first ,
      angle = readString.substring(0, ind1);   //captures first data String
      ind2 = readString.indexOf(',', ind1+1 );   //finds location of second ,
      fuel = readString.substring(ind1+1, ind2+1);   //captures second data String
      ind3 = readString.indexOf(',', ind2+1 );
      speed1 = readString.substring(ind2+1, ind3+1);
      ind4 = readString.indexOf(',', ind3+1 );
      altidude = readString.substring(ind3+1); //captures remain part of data after last ,

      Serial.print("angle = ");
      Serial.println(angle); 
      Serial.print("fuel = ");
      Serial.println(fuel);
      Serial.print("speed = ");
      Serial.println(speed1);
      Serial.print("altidude = ");
      Serial.println(altidude);
      Serial.println();
      Serial.println();
      
      readString=""; //clears variable for new input
      angle="";
      fuel="";
      speed1="";
      altidude="";
    }  
    else {     
      readString += c; //makes the string readString
    }
  }
}

hej robtillaart,

i really like your slim solution for this task!
Now i'm trying to do the same thing but with a UDP input. So i combined your code with the UDP example. Unfortunately it seams like switch...case dose not like to work with the char from the packetBuffer. But why? Now i got totally stuck and i need a little hint.

here is the code:

//
//    FILE: parsePacket.ino
//  AUTHOR: Rob Tillaart
// VERSION: 0.1.00
// PURPOSE: demo
//    DATE: 2014-dec-29
//     URL:
//
// Released to the public domain
//

#include <SPI.h>         // needed for Arduino versions later than 0018
#include <Ethernet.h>
#include <EthernetUdp.h>         // UDP library from: bjoern@cs.stanford.edu 12/30/2008

byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
IPAddress ip(192, 168, 0, 222);
unsigned int localPort = 8888;      // local port to listen on

// buffers for receiving and sending data
char packetBuffer[UDP_TX_PACKET_MAX_SIZE];  //buffer to hold incoming packet,
char ReplyBuffer[] = "acknowledged";       // a string to send back

EthernetUDP Udp;           // An EthernetUDP instance to let us send and receive packets over UDP

int field[30];
uint8_t idx = 0;
bool done = false;


void setup()
{
  Ethernet.begin(mac, ip);
  Udp.begin(localPort);
  Serial.begin(9600);
  Serial.println("Start ");
}


void loop()
{
  int packetSize = Udp.parsePacket();
  if (packetSize > 0) 
  {
    Udp.read(packetBuffer, packetSize);            // read the packet into packetBuffer
    Serial.println();
    Serial.print("recieved massage: ");
    Serial.println(packetBuffer);
    done = parsePacket(packetBuffer);
    for(int i=0;i<UDP_TX_PACKET_MAX_SIZE;i++) packetBuffer[i] = 0;              //cleer buffer
  }

  if (done)                                         // ready to process a package
  {
    Serial.print("fields:\t");
    Serial.println(idx);
    for (int i = 0; i < idx; i++)
    {
      Serial.print(i);
      Serial.print("\t");
      Serial.println(field[i]);
    }
    Serial.println("done");
  }

  // do other things here
  
}

bool parsePacket(int c)
{  
  bool ready = false;  
  switch (c)
  {
    case '0'...'9':
      field[idx] = field[idx] * 10 + c - '0';
      break;
    case ',':
      idx++;
      field[idx] = 0;
      break;
    case '<':
      idx = 0;
      field[idx] = 0;
      break;
    case '>':
      idx++;
      ready = true;
      break;
    default:
      ; // skip
  }
  return ready;
}

Take a look at description of Serial.readBytesUntil(character, buffer, length).

Then try this PSEUDOCODE

char MyBuffer[] double array to store packets

int i = 0; starting index to array

while (Serial.available()) while loop when Serial buffer has data
{

read a packet of characters from Serial buffer until character ',' and writes it into MyBuffer , returns count of characters read in packet

Character Count = Serial.readBytesUntil(character, MyBuffer*, length); removes packet read from Serial buffer*
i++; read next packet
}
where
character = ',' parsing character
buffer = MyBuffer l
length > then max length of packet expected
PS ignore capital I , it should be lower case - autocorrect at its best

232, please post your code using code tags.

@taurisco:

bool parsePacket(int c)

So, the function take an int.

    done = parsePacket(packetBuffer);

So, why are passing it an array of chars?

if you know for sure the number of data and the specific format, you can try using sscanf

char str[] = "<1023,0,511,1023,0,1032>"

int first, second, third, fourth, fifth, sixth;


int result = sscanf(str, "<%d,%d,%d,%d,%d,%d>", &first, &second, &third, &fourth, &fifth, &sixth);

//result should be 6 if sscanf successes (because we scan 6 numbers)

Again this code is not robust as you must follow the pattern.

Quote from: 232
__________________

Character Count = Serial.readBytesUntil(character, MyBuffer*, length); removes*

Guess why the rest of your post is in italics. -_-