Need help, read serial string with variable

I would like to send to arduino with serial monitor strings with variables. Something like "var 255" and arduino should write integer 255 to variable "var". Numbers and text will be seperated by space.

That's cool, you dont need our permission.

Do you have a question for the forum?

My code doesn't work, it wants a space at end of command ("var 1400 ") and it ignores the "var" part. I have no idea how to fix this.

String inputString = "";         // a String to hold incoming data
int Step;
int var;

void setup() {
  // initialize serial:
  Serial.begin(9600);
  // reserve 200 bytes for the inputString:
  inputString.reserve(200);
}

void loop() {  
}

void serialEvent() {
  while (Serial.available())
  {
    char inChar = (char)Serial.read();

    if (inChar != 32) inputString += inChar;
    
    if ((inChar == 32)||(inChar == 13)) // check string
    {
      if(inputString == "var") Step = 1;
      if((inputString == "1400")&&(Step = 1))
      {
        var = 1400;
        Serial.println("OK");
      }
      inputString = "";
    }

    if (inChar == 13) inputString = "", Step = 0;
  }
}

Test_code.ino (735 Bytes)

Sigh..

POST YOU CODE IN CODE TAGS LIKE THIS

String inputString = "";         // a String to hold incoming data
int Step;
int var;

void setup() {
  // initialize serial:
  Serial.begin(9600);
  // reserve 200 bytes for the inputString:
  inputString.reserve(200);
}

void loop() {  
}

void serialEvent() {
  while (Serial.available())
  {
    char inChar = (char)Serial.read();

    if (inChar != 32) inputString += inChar;
    
    if ((inChar == 32)||(inChar == 13)) // Presledek
    {
      if(inputString == "var") Step = 1;
      if((inputString == "1400")&&(Step = 1))
      {
        var = 1400;
        Serial.println("OK");
      }
      inputString = "";
    }

    if (inChar == 13) inputString = "", Step = 0; // Će konec
  }
}

Oh, I already did it for you.

-jim lee

This conditional is incorrect:

      if((inputString == "1400")&&(Step = 1))

I think you meant:

      if((inputString == "1400")&&(Step == 1))

For this:

    if (inChar == 13) inputString = "", Step = 0;

You should have this:

    if (inChar == 13) 
    {
       inputString = "";
       Step = 0;
    }

My guess is you are not receiving a CR (13) and therefore a space (32) is required to trigger your conditional. You need to look at how your serial monitor is configured.

Doesn't look like your doing very much here..

void loop() {  
}

-jim lee

This code does what you want. It links variable names to variables via functions that take the new value.
The input line can be terminated by Arduino IDE settings, "Carriage return" or "Newline" or "Both NL and CR" or "No line ending" in which case the line is processed after 1sec (non-blocking delay)
The check on the int value is very robust. Only valid ints are accepted.
The SafeStringReader has auto echo turned on so it displays the input as typed.

Sample output

 Enter inputs like  variable integer,  e.g. var1 255
 Valid variables are:- var1 var2 var3
abc
'abc' does not have a valid variable name.
var1
 Variable var1 value '' is  not an integer
var2 5a
 Variable var2 value ' 5a' is  not an integer
var3   244
 Setting var3 to 244
// ReadVariableValues
// https://forum.arduino.cc/index.php?topic=731855.0
//
//
// download and install the SafeString library from
// www.forward.com.au/pfod/ArduinoProgramming/SafeString/index.html
#include "SafeStringReader.h"

// create an sfReader instance of SafeStringReader class
// that will handle line upto 50 chars long
// delimited by CarrageReturn or NewLine or just input timeout 1sec if no line ending
const unsigned int MAX_CMD_LENGTH = 50;
createSafeStringReader(sfReader, MAX_CMD_LENGTH, "\r\n");

int var1;
int var2;
int var3;
// etc

void var1Cmd(int value) {
  var1 = value;
}
void var2Cmd(int value) {
  var2 = value;
}
void var3Cmd(int value) {
  var3 = value;
}

typedef struct {
  const char* cmd;
  void (*cmdFn)(int); // a method taking a SafeString& arg and returning void
} COMMANDS_STRUCT;

COMMANDS_STRUCT commands[] = {
  {"var1", var1Cmd},
  {"var2", var2Cmd},
  {"var3", var3Cmd}
};

const size_t NO_OF_CMDS = sizeof commands / sizeof commands[0];

void setup() {
  Serial.begin(9600);
  for (int i = 10; i > 0; i--) { // pause a little to give you time to open the Arduino monitor
    Serial.print(i); Serial.print(' '); delay(500);
  }
  Serial.println();
  SafeString::setOutput(Serial); // enable error messages and SafeString.debug() output to be sent to Serial

  Serial.println(F(" Enter inputs like  variable integer,  e.g. var1 255"));
  Serial.print(F(" Valid variables are:-"));
  for (size_t i = 0; i < NO_OF_CMDS; i++) {
    Serial.print(" "); Serial.print(commands[i].cmd);
  }
  Serial.println();
  sfReader.connect(Serial); // where SafeStringReader will read from
  sfReader.echoOn(); // echo back all input, by default echo is off
  sfReader.setTimeout(1000); // 1000mS = 1sec timeout
}

void processCmd(SafeString & input) {
  input.trim();
  if (input.isEmpty()) {
    return; // nothing to do
  }
  // get var int separated by space
  cSF(sfVarName, MAX_CMD_LENGTH); // large enough to handle entire input if no space found
  int idx = input.indexOf(' '); // look for space
  input.substring(sfVarName, 0, idx);
  // Check token against valid commands
  for (size_t i = 0; i < NO_OF_CMDS; i++) {
    if (sfVarName == commands[i].cmd) { // found one
      // pickup the integer
      cSF(sfValue, MAX_CMD_LENGTH);
      input.substring(sfValue, idx);
      int value = 0;
      if (sfValue.toInt(value)) {
        Serial.print(F(" Setting ")); Serial.print(sfVarName); Serial.print(F(" to ")); Serial.println(value);
        commands[i].cmdFn(value); // call the cmd fn
      } else {
        Serial.print(F(" Variable ")); Serial.print(sfVarName); Serial.print(F(" value ")); Serial.print(sfValue); Serial.println(F(" is  not an integer"));
      }
      return; // found cmd
    }
  }
  // else no cmd found have finished processing this token, clear it so it is not output
  Serial.print("'"); Serial.print(input);
  Serial.println(F("' does not have a valid variable name."));
}

void loop() {
  if (sfReader.read()) {
    processCmd(sfReader); //sfReader holds the input line
  } // else no line yet

}
1 Like

drmpf:

According to ASCII they are named CR (13, Carriage Return) and LF (10, Line Feed).

True but Arduino IDE options are Newline (not LineFeed) :frowning:
But just to clarify let me edit the post.

I tried this, but it wont accept "M0 12", only "M0 12 " whit a space at the end.
This get trigered for both "M0 12" and M0 12 " and oututs "Reset"

if (inChar == 13) inputString = "", Serial.println("Reset"), Step = 0; // If end

But this part ignores the char 13 for some reason, Serial monitor set to carriage return

if ((inChar == 32)||(inChar == 13)) // Space
    {
      if(inputString == "M0") Step = 1;
      if((inputString == "12")&&(Step == 1)) Serial.println("OK");
      inputString = "";
    }
String inputString = "";         // a String to hold incoming data
int Step;

void setup() {
  // initialize serial:
  Serial.begin(9600);
  // reserve 200 bytes for the inputString:
  inputString.reserve(200);
}

void loop() {
}

void serialEvent() {
  while (Serial.available())
  {
    char inChar = (char)Serial.read();

    if (inChar != 32) inputString += inChar;
    
    if ((inChar == 32)||(inChar == 13)) // Space
    {
      if(inputString == "M0") Step = 1;
      if((inputString == "12")&&(Step == 1)) Serial.println("OK");
      inputString = "";
    }

    if (inChar == 13) inputString = "", Serial.println("Reset"), Step = 0; // If end
  }
}

Try this version, input line needs to be terminated with '\n'

String inputString = "";         // a String to hold incoming data
int var;

void setup() {
  Serial.begin(9600);
  for (int i = 10; i > 0; i--) { // pause a little to give you time to open the Arduino monitor
    Serial.print(i); Serial.print(' '); delay(500);
  }
  Serial.println();  // reserve 200 bytes for the inputString:
  inputString.reserve(200);
}

bool readLine() {
  while (Serial.available()) {
    char inChar = Serial.read();
    if (inChar == '\n') {
      return true;
    }
    // else
    inputString += inChar;
  }
  return false;
}

void loop() {

  if (readLine()) {
    // got whole line terminated by \n
    Serial.println(inputString);
    inputString.trim(); // added in later edit.
    int idx = inputString.indexOf(' '); // look for space
    String varName = inputString.substring(0, idx);
    if (varName == "var") {
      // got varname
      String valueStr = inputString.substring(idx);
      valueStr.trim();
      int newValue = valueStr.toInt();
      if (valueStr == String(newValue)) {  // ok
        var = newValue;
        Serial.print(F("Set var to ")); Serial.println(newValue);
      } else {
        Serial.print(F(" Variable ")); Serial.print(varName);
        Serial.print(F(" value '")); Serial.print(valueStr); Serial.println(F("' is  not an integer"));
      }
    } else {
      Serial.print("'"); Serial.print(varName);
      Serial.println(F("' not valid variable name."));
    }
    inputString = "";
  }
}

Is this really needed?

 for (int i = 10; i > 0; i--) { // pause a little to give you time to open the Arduino monitor

Serial.print(i); Serial.print(' '); delay(500);
 }

when opening the Arduino Serial monitor you reboot the board (for many arduino boards)

Also checking for -1 returned by indexOf() would be good programming practice (generating an UB issue otherwise later on when you try to extract the substring if I remember well)

Is this really needed?

Not for Uno/Mega2560. Boards restart when the download finishes but some other boards do not restart (again) when you open the Monitor and some even don't like the Monitor being open when programming and sometimes my computer does not like it either :frowning: So I just add this to all my sketches to let me know I have not missed any startup messages.
So no not needed, just habit of mine as I often cross compile on different boards to check results/errors.

checking for -1 returned by indexOf() would be good programming practice

Yes I used to think so until I had a close look at the substring and indexOf methods

The index args for substring and indexOf() are unsigned int, so -1 => a very large number and the code for substring and indexOf is very relaxed, and does not complain if the index is out of range but instead converts it to an appropriate number, usually length(), so
substring(0,-1) just returns the whole String, while substring(-1) returns an empty String.
Just what is needed in this case.
Since Arduino Strings do not have means of flagging errors, the methods try hard to give you a usable result even for out of bounds arguments.

But you do have to check in while ( ) loops when you step over the last index to start the next search, because
-1 + 1 == 0 and loop will just go back to searching from the front of the String and the loop will not stop :frowning:

I had to change a lot of code in V4 of SafeString to mimic this type of behaviour. In V3 of SafeString it would have refused to use a (size_t)(-1) argument, now, in V4, SafeString just complains (if you are listening) and treats it the same way as Arduino Strings. There is a new test sketch in the SafeStrings examples that compares Arduino Strings and SafeStrings results for these cases.

OK got it. we usually see more often while (!Serial);for some boards than an active 5s wait

good point on the unsigned int, indeed will work then.

I got the code working 99%.
Code reads variable name and value form serial and writes the value to matching variable. Now im trying to read only string "Mem" that will write back to serial monitor all values stored in variables (M0 and M1), but the If state wont trigger for some reason.
Can some one help me?

void OnlyString(String str)
{
  if (str == "Mem") // <--- This if state wont triger when I send "Mem" over serial with carriage return
  {
    Serial.print("M0 = ");
    Serial.println(M0);
    Serial.print("M1 = ");
    Serial.println(M1);
  }
  Serial.println(str); // <--- Outputs "Mem" to serial just fine
}

But this part works ok

void UsefulData(String var, uint16_t num)
{
  Serial.println(var);
  Serial.println(num);

  if (var == "M0") M0 = num, Serial.println(M0); // <--- Detects "M0" in string perfectly
  if (var == "M1") M1 = num, Serial.println(M1);

  Serial.println("OK");
}

Full code:

String inputString = "", serialWord = "";
int serialStep;
int M0, M1;

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

void loop() {  
}

void serialEvent() {
  while (Serial.available())
  {
    char inChar = (char)Serial.read();
    if (inChar != 32) inputString += inChar;    
    if (inChar == 32) serialWord = inputString, inputString = "", serialStep = 1;
    if ((inChar == 13)&&(serialStep == 1)) UsefulData(serialWord, inputString.toInt()), inputString = "", serialWord = "", serialStep = 0;
    if ((inChar == 13)&&(serialStep == 0)) OnlyString(inputString), inputString = "", serialStep = 0;
  }
}

void UsefulData(String var, uint16_t num)
{
  Serial.println(var);
  Serial.println(num);

  if (var == "M0") M0 = num, Serial.println(M0);
  if (var == "M1") M1 = num, Serial.println(M1);

  Serial.println("OK");
}

void OnlyString(String str)
{
  if (str == "Mem")
  {
    Serial.print("M0 = ");
    Serial.println(M0);
    Serial.print("M1 = ");
    Serial.println(M1);
  }
  Serial.println(str);
}

Just a serving suggestion.

    ....
    ....
    char inChar = Serial.read();
    switch(inChar) {
        case 32  :
            serialWord = inputString;
            inputString = "";
            serialStep = 1;
        break;
        case  13 :
            if (serialStep == 1) {
                UsefulData(serialWord, inputString.toInt());
                serialWord = "";
            } else if (serialStep == 0) {
                OnlyString(inputString);
            }
            inputString = "";
            serialStep = 0;
        break;
        default   : inputString += inChar; break;
    }

Using a switch statement may help make the logic easier to follow.

Or not.. Just a user preference kind of thing.

-jim lee

This is the first of 3 posts on your project.

String usage.
Check out my tutorial on Taming Arduino Strings

For your code, it is so simple that there are not really any String problems.

But Taming Arduino Strings advises adding the reserve(20) back in for the two Strings
and changing the methods to
void UsefulData(const String& var, uint16_t num)
void OnlyString(String& str) // str updated by trim();
to avoid un-necessary String creations and data copying.

Your use of serialEvent() is un-common. Not a problem, just not often used.

This is what happens under the hood in Arduino

int main(void) {  // reset starts running the microprocessor from here
  // . . 
	setup();
	for (;;) {  // loop here for ever running loop() and serialEventRun()
		loop();
	   // call serialEventRun(); which expands to
           if (Serial.available()) serialEvent();
	}        
	return 0;
}

So using serialEvent() is the same as

    if (Serial.available()) {
       // do stuff
    }

The difference is that serialEvent() is only ever called once each loop().

Normally this is not a problem, but for larger more complicated sketches the loop() may be slow
due to processing/Serial prints and you can miss Serial data. Then you need to stop using serialEvent()
and poll the Serial more often during the loop() code

See my tutorials on
Arduino Serial I/O for the Real World and Multi-tasking in Arduino for what to do in that case

Again not a problem for the code you have at the moment.

See your slightly modified code below, with added comments and the fix.

Interesting use of the , to string statements together, the result is the last one, which you throw away.
I replaced them with ;
More code comments and Debug prints help a lot.
Your code 'works' but is very fragile. If you enter "Mem " with a trailing space it is ignored due to your state machine. trim() does not help in this case. Suggest you have another look at either of my two previous code solutions which are more robust.

String inputString = "", serialWord = "";
int serialStep;
int M0, M1;

void setup() {
  Serial.begin(9600);
  for (int i = 10; i > 0; i--) { // pause a little to give you time to open the Arduino monitor
    Serial.print(i); Serial.print(' '); delay(500);
  }
  Serial.println();
  inputString.reserve(20); // reserve 20 bytes for the inputString:
  serialWord.reserve(20); // reserve 20 bytes for the serialWord:
  Serial.println(F("setup() complete"));
}

void loop() {
}

void UsefulData(const String& var, uint16_t num)
{
  Serial.println(var);
  Serial.println(num);

  if (var == "M0") M0 = num, Serial.println(M0);
  if (var == "M1") M1 = num, Serial.println(M1);

  Serial.println("OK");
}

void OnlyString(String& str) // str updated by trim();
{
  Serial.print("'"); Serial.print(str); Serial.println("'");
  str.trim(); // remove leading trailing white space NL CR etc
  if (str == "Mem") // << this now works
  {
    Serial.print("M0 = ");
    Serial.println(M0);
    Serial.print("M1 = ");
    Serial.println(M1);
  }
}

// run the state machine with two states serialStep == 0 or 1
// starting state serialStep == 0  => serialStep == 1 when have read first word delimited by ' '
void serialEvent() {
  while (Serial.available())
  {
    char inChar = (char)Serial.read();
    if (inChar != ' ') {       // note this adds '\n' to the inputString at endOfLine
      inputString += inChar;
    } else { // inChar == 32
      serialWord = inputString;
      inputString = "";
      serialStep = 1;
    }

    if (inChar == '\n') { // == 13 // found end of line
      // what state are we in
      if (serialStep == 1) {
        UsefulData(serialWord, inputString.toInt()); // toInt is not very good see my previous posts for better code
        // start again
        inputString = "";
        serialWord = "";
        serialStep = 0;
      } else { // (serialStep == 0) i.e. got newline without a trailing ' '
        OnlyString(inputString);
        inputString = "";
        serialStep = 0;
      }
    }
  }
}