Integers and Strings in the same serial message

The project I'm working on requires both integers and strings to be received via serial communications. I'm making a PC fan and LED controller with an OLED display and a PC software interface, which will allow the user to, among other things, set the names of the connected fans.

My problem is sending a string along with integers to the Arduino. I have seen numerous examples on how to parse integers from a serial message (which I am currently using and is working well), and tutorials on how to read strings from serial, but none on how to combine the both. Ideally I would like to be able to send the Arduino a serial message like this hypothetical one: 3,255,"Fan 1" The 3 selecting Fan 3, the 255 setting the fan's PWM amount to max and the "Fan 1" string setting the fans' name to be displayed on the screen.

Is it possible to send a mixture of integers and strings (or just 1 string) to the Arduino?

Here's the code I'm working with currently, which allows setting either LED or Fan Control variables. It works great for setting integer variables, but I have no idea on how to add string parsing into it. (A Serial.parseString(); would be great haha)

while (Serial.available() > 0 ) //Incoming serial commands
  {
    //Set each integer in the serial message to its own variable
    int b0 = Serial.parseInt(); //Addressed module (-1=Send Values, 0=LED, 1=Fan Control) //Send a -1 for Fan vars to leave them unchanged
    int b1 = Serial.parseInt(); //[LED]Red Value 1    [FAN]Target fan
    int b2 = Serial.parseInt(); //[LED]Green Value 1  [FAN]Assigned sensor
    int b3 = Serial.parseInt(); //[LED]Blue Value 1   [FAN]PWM Amount (Power)
    int b4 = Serial.parseInt(); //[LED]Pattern        [FAN]Fan mode (0=Auto, 1=Manual)
    int b5 = Serial.parseInt(); //[LED]Pattern Mode   [FAN]Sensor polling rate
    int b6 = Serial.parseInt(); //[LED]Pattern Speed  [FAN]UI Fan Select
    int b7 = Serial.parseInt(); //[LED]Red Value 2    [FAN] No use currently
    int b8 = Serial.parseInt(); //[LED]Green Value 2  [FAN] No use currently
    int b9 = Serial.parseInt(); //[LED]Blue Value 2   [FAN] No use currently

    if (Serial.read() == '\n') //If there's a newline character (signals end of msg), set the variables
    {
      if (b0 == -1) //If the command was to send variables
      {
        Serial.println("Send variables!");
      }
      else if (b0 == 0) //If this command was for the LED section (defined by first int in serial msg)
      {
        led_redVal = b1;
        led_greenVal = b2;
        led_blueVal = b3;
        led_pattern = b4;
        led_patMode = b5;
        led_speedMod = b6;
      }
      else if (b0 == 1) //If this command was for the Fan section (defined by first int in serial msg)
      {
        if (b2!=-1) {fan_sens[b1] = b2;}
        if (b3!=-1) {fan_power[b1] = b3;}
        if (b4!=-1)
        {
          if (b4 == 0) {fan_mode = 'A';} 
          else if (b4 == 1) {fan_mode = 'M';}
        }
        if (b5!=-1) {pollrate = b5;}
        if (b6!=-1) {ui_fanselect = b6;}
      }
    }
  }

I think the readCSV() function in this demo illustrates what you want.

...R

3,255,"Fan 1"

Why bother? Just send:
3,255,'F',1

steinie44:

3,255,"Fan 1"

Why bother? Just send:
3,255,'F',1

Why even bother with single quotes?

How many fans are there and does it control others things too?

Why not just send [3,255,0] // 0 = fan 1, 1 = fan 2 ... N = fan N+1
With this you can just have an array of possible messages to display. Or you could keep it as [3,255,"fan 1"], in which you don't need to have an array of messages, and just print what you got in.

Strtok() or sscanf() can split this up with no problem, Or you can be clever and make your own function.
There are plenty of example sketches that show how to split this up [3,255,0] easily without Strtok or sscanf.

Is it possible to send a mixture of integers and strings (or just 1 string) to the Arduino?

Sure. The data is sent as one string of characters. Decide on a parsing scheme that is easy for you to use, then send your data in that format. Below is simple parsing test code.

//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
    }
  }
}

As has been mentioned above I don't see why you can't just send three bytes. The sending of stuff like "Fan 1" is a common beginner thing to do because they want the data to be human-readable, but computers don't care and it just makes life harder.


Rob

a very simple way is to simply use a starting Unique Identifier and ending delimiter for example, transmit your serial data like this:

F255x

Where the device is F, its value is 255 and the trailing delimiter is x. This will allow you to have 52 devices to control just using the alphabet's capital and small letters. You could also use any of the other non-numeric characters (e.g. #,$) further expanding your options.

using this method, you can even stack the values in a transmission for example:

F255G0H100x

and process like this example:

int myF,myG,myH;

void setup()
{
  Serial.begin(115200);
}
void loop()
{
  if (Serial.available())
  {
    char myChar = Serial.read();
    if (myChar == 'F')
    { 
      myF = Serial.parseInt();
      Serial.println(myF);
    }
    else if (myChar == 'G')
    {
      myG = Serial.parseInt();
      Serial.println(myG);
    }
    else if (myChar == 'H')
    {
      myH = Serial.parseInt();
      Serial.println(myH);
    }
  }
}
2 Likes

Servo test code that uses a command structure that starts with the numeric value, followed by the device identifier, followed by the packet delimiter.

//zoomkat 11-22-12 simple delimited ',' string parse 
//from serial port input (via serial monitor)
//and print result out serial port
//multi servos added 
// Powering a servo from the arduino usually *DOES NOT WORK*.

String readString;
#include <Servo.h> 
Servo myservoa, myservob, myservoc, myservod;  // create servo object to control a servo 

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

  //myservoa.writeMicroseconds(1500); //set initial servo position if desired

  myservoa.attach(6);  //the pin for the servoa control
  myservob.attach(7);  //the pin for the servob control
  myservoc.attach(8);  //the pin for the servoc control
  myservod.attach(9);  //the pin for the servod control 
  Serial.println("multi-servo-delimit-test-dual-input-11-22-12"); // so I can keep track of what is loaded
}

void loop() {

  //expect single strings like 700a, or 1500c, or 2000d,
  //or like 30c, or 90a, or 180d,
  //or combined like 30c,180b,70a,120d,

  if (Serial.available())  {
    char c = Serial.read();  //gets one byte from serial buffer
    if (c == ',') {
      if (readString.length() >1) {
        Serial.println(readString); //prints string to serial port out

        int n = readString.toInt();  //convert readString into a number

        // auto select appropriate value, copied from someone elses code.
        if(n >= 500)
        {
          Serial.print("writing Microseconds: ");
          Serial.println(n);
          if(readString.indexOf('a') >0) myservoa.writeMicroseconds(n);
          if(readString.indexOf('b') >0) myservob.writeMicroseconds(n);
          if(readString.indexOf('c') >0) myservoc.writeMicroseconds(n);
          if(readString.indexOf('d') >0) myservod.writeMicroseconds(n);
        }
        else
        {   
          Serial.print("writing Angle: ");
          Serial.println(n);
          if(readString.indexOf('a') >0) myservoa.write(n);
          if(readString.indexOf('b') >0) myservob.write(n);
          if(readString.indexOf('c') >0) myservoc.write(n);
          if(readString.indexOf('d') >0) myservod.write(n);
        }
         readString=""; //clears variable for new input
      }
    }  
    else {     
      readString += c; //makes the string readString
    }
  }
}

Code:

3,255,"Fan 1"

Why bother? Just send:
3,255,'F',1

Why even bother with single quotes?

I mean 'F' is for Ascii F = 70 So it would be 3,255,70,1 or 3, 255, 70, 49

while (Serial.available() > 0 ) //Incoming serial commands
  {
    //Set each integer in the serial message to its own variable
    int b0 = Serial.parseInt(); //Addressed module (-1=Send Values, 0=LED, 1=Fan Control) //Send a -1 for Fan vars to leave them unchanged
    int b1 = Serial.parseInt(); //[LED]Red Value 1    [FAN]Target fan
    int b2 = Serial.parseInt(); //[LED]Green Value 1  [FAN]Assigned sensor
    int b3 = Serial.parseInt(); //[LED]Blue Value 1   [FAN]PWM Amount (Power)
    int b4 = Serial.parseInt(); //[LED]Pattern        [FAN]Fan mode (0=Auto, 1=Manual)
    int b5 = Serial.parseInt(); //[LED]Pattern Mode   [FAN]Sensor polling rate
    int b6 = Serial.parseInt(); //[LED]Pattern Speed  [FAN]UI Fan Select
    int b7 = Serial.parseInt(); //[LED]Red Value 2    [FAN] No use currently
    int b8 = Serial.parseInt(); //[LED]Green Value 2  [FAN] No use currently
    int b9 = Serial.parseInt(); //[LED]Blue Value 2   [FAN] No use currently

    if (Serial.read() == '\n') //If there's a newline character (signals end of msg), set the variables

You seem to have assumed that the whole packet is going to have arrived before you start reading. That is fatally flawed assumption.

That readCSV() function seems to be exactly what I'm looking for! I'll give it a go and report back!

To the people saying "why not just send a number or 'f'", the string is a user-defined piece of text to be displayed on the OLED screen. "Fan 1" was just an example, it could be set to "Motherboard Fan" or "Front Bay Fan" or "foobar", it allows the user to set the name of each fan for easy identification on the screen and is not a constant. It isn't used as a way to identify a fan in the serial message, it's what is being printed to the screen.

You seem to have assumed that the whole packet is going to have arrived before you start reading. That is fatally flawed assumption.

The code works fine as it is, the " if (Serial.read() == '\n')" makes sure that the entire serial message has arrived before deciding what to do with it. This code is taken from the built-in serial communication tutorial.

the " if (Serial.read() == '\n')" makes sure that the entire serial message has arrived before deciding what to do with it.

And, if it hasn't? You don't continue where you left off reading. You start over.

This code is taken from the built-in serial communication tutorial.

It's still crap.

PaulS:
And, if it hasn't? You don't continue where you left off reading. You start over.

The serial messages are being sent from a Windows program, not a user manually entering it into a console, so it will always be fully formed if that's what you mean? Are you saying there's a better way to do this? If so, how?

"Fan 1" was just an example, it could be set to "Motherboard Fan" or "Front Bay Fan" or "foobar",

OK, that's different.


Rob

liljohn360:
The serial messages are being sent from a Windows program, not a user manually entering it into a console, so it will always be fully formed if that's what you mean? Are you saying there's a better way to do this? If so, how?

liljohn360:
The serial messages are being sent from a Windows program, not a user manually entering it into a console, so it will always be fully formed if that's what you mean?

Whether it's Windows, Mac, Linux or a whole lot of monkeys typing on the keyboard, they have to obey the laws of physics.

One of those is that at 9600 baud (and you don't say what your baud rate is) each character takes 1/960 of a second (1.04 mS). Your hypothetical message (3,255,"Fan 1") therefore must take 13 * 1.04 mS (13.5 mS) at least, if not longer if the monkeys get tired. So if you read in a very fast loop "just hoping" it will all arrive, you will probably miss most of those.

liljohn360:

PaulS:
And, if it hasn't? You don't continue where you left off reading. You start over.

The serial messages are being sent from a Windows program, not a user manually entering it into a console, so it will always be fully formed if that's what you mean? Are you saying there's a better way to do this? If so, how?

Did you try the example I gave you?