Verifying it .toInt is really catching an integer

Hi there,

I hope I was not simply too blind to find it.

So what I am doing is receiving serial commands on my Arduino and try to parse them into the functions my Arduino runs.

So in this part it is about switching chanels in the connected switching matrix.
What works fine:
Sending "setChanel+" switches the chanel up, "setChanel-" switches it down.
But I want to parse also comands like "setChanel5" to switch to chanel 5.
While "setChanelx" should return an error.

So this is my partial code for this (I think I sould not bother you with 900 lines of code:

//Check for Serial Input
  if (Serial.available() > 0)  {                                                                                 
  SerialRead = Serial.readString();
SerialRead.trim();
//  Serial.println("The following derial input had been received:"); //for debugging
  Serial.println(SerialRead);                                      //for debugging                                             
    if(SerialRead.substring(0,9) == "setChanel"){
      if(SerialRead.substring(9,10) == "+")   { 
        if(SerialRead.length() < 11){
          Serial.println("Serial_received_Chanel+");
          ChanelPlus();
        }
        else {
          Serial.println("'" + SerialRead + "' is not a valid setChanel command!");
        }
      }                                                 
      else if(SerialRead.substring(9,10) == "-")   { 
        if(SerialRead.length() < 11){
        Serial.println("Serial_received_Chanel-");
        ChanelMinus();  
        }
        else {
          Serial.println("'" + SerialRead + "' is not a valid setChanel command!");
        }
      }  
      else {
        int ChanelParser = 9999;
        ChanelParser = SerialRead.substring(9).toInt();
        Serial.println(ChanelParser);

 
      }
    }
  }

My situation is that as long as I set a number after "setChanel" I can proceed absolute fine.
But if there is anything that is not a number, ChanelParser is ending up as 0.
I mean I could code a workaround to handle the value 0 as an error state (actually my code knows a chanel 0, so it is not a sexy way), but is there a way to detect if SerialRead.substring(9) contains others as a number?

Thanks and with kind regards,
Felix

String::toInt is clearly documented:

  • It stops at the first non-digit
  • If there's no digit at the start, it returns zero

That's entirely reasonable. Given this is "simplified C++", it's not going to throw an exception, return an error object, or something like that.

Since you are checking the character after setChanel, you might as well do it like

char c = SerialRead.charAt(9);
if (c == '+') {
  //...
} else if (c == '-') {
  //...
} else if (c >= '0' && c <= '9') {
  // There's at least one digit
  int ChanelParser = SerialRead.substring(9).toInt();
  //...
} else {
  // none of the above
}
1 Like

You might get some ideas from reading Robin's updated Serial Input Basics.

If you send the command in a format like setChannel,5,+ you can easily parse the command.

1 Like

I think the verification from kenb works the easiest for me, since I don't know what channel numbers mitht be used in the future.
I assume to be less than 10 but who knows...
I know that I just checking the fist charakter of the possible numbers, but honestly, this is the not about catching all that anyone might fool with the serial interface...so if it is setChanel40bla, it will simply ignore the bla after the number.

//Check for Serial Input
  if (Serial.available() > 0)  {                                                                                 
  SerialRead = Serial.readString();
SerialRead.trim();
//  Serial.println("The following derial input had been received:"); //for debugging
  Serial.println(SerialRead);                                      //for debugging                                             
    if(SerialRead.substring(0,9) == "setChanel"){
      char CanelPreParser = SerialRead.charAt(9);
      if(SerialRead.substring(9,10) == "+")   { 
        if(SerialRead.length() < 11){
          Serial.println("Serial_received_Chanel+");
          ChanelPlus();
        }
        else {
          Serial.println("'" + SerialRead + "' is not a valid setChanel command!");
        }
      }                                                 
      else if(SerialRead.substring(9,10) == "-")   { 
        if(SerialRead.length() < 11){
        Serial.println("Serial_received_Chanel-");
        ChanelMinus();  
        }
        else {
          Serial.println("'" + SerialRead + "' is not a valid setChanel command!");
        }
      }
      else if(CanelPreParser >= '0' && CanelPreParser <= '9')   {
        int ChanelParser = 9999;
        ChanelParser = SerialRead.substring(9).toInt();
        Serial.println(ChanelParser);
        if (ChanelParser >= '0' && ChanelParser <= maxstatelevel)   {
          statelevel = ChanelParser;
          ProcessStateLevel();
        } 
        else {
          Serial.println("There is no chanel " + String(ChanelParser) + " avalable in this config.");
        } 
      }  
      else {
        Serial.println("'" + SerialRead + "' is not a valid setChanel command!");
      }
    }
  }
}

I just now ended up in the issue that even this returns a valid channel number:
Serial.println(ChanelParser);
(Max Statelevel is defined as:
int maxstatelevel = 5;
)
It will always go into else instead of:
if (ChanelParser >= '0' && ChanelParser <= maxstatelevel) {

so setChanel4 will not be catched, even if masstatelevel is 5.

Any idea what goes wrong?

Regards,
Felix

My second "ASCII chart" question of the day. Try this

void setup() {
  Serial.begin(115200);
  char someNumber = 5;
  char someChar = '5';
  int whatIsIt = someNumber;
  Serial.println(whatIsIt);
  whatIsIt = someChar;
  Serial.println(whatIsIt);
}

void loop() {}

I could not try your code now, but I assume you refer to the point that it compares an Int with a Char, right?

But in my code Maxstatelevel is an Int and also ChanelParser is an Int (filled with the toInt function).
ChanelParser could already be compared with an Int a few rows above.

Regards,
Felix

The char '0' has the value 48, which you can look up in any ASCII chart. So regardless of whether ChanelParser is a char or an int, it cannot be both

  • >= 48 and
  • <= 5

try sscanf(), e.g.

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

void loop() {
  if (Serial.available()) {
    char text[100] = { 0 };
    int channel = 0;
    Serial.readBytesUntil('\n', text, 100);
    Serial.print("text input ");
    Serial.println(text);
    if (sscanf(text, "setChanel%d", &channel) == 1) {
      Serial.print("channel = ");
      Serial.println(channel);
    } else
      Serial.println("invalid input");
  }
}

test output

text input setChanel1
channel = 1
text input setChanel10
channel = 10
text input setChanel78
channel = 78
text input setChanel45678
channel = 45678
text input setChanel90
channel = 90
text input setChanelx
invalid input

Now I saw that I missed the '
Sorry for that, now the code works fine^^

Thanks,
Felix

It’s not
because you can’t know if you read a real 0 value or it’s an error. So if 0 is a reasonable expected number then this function should not be used.

My recommendation would be strtol() (or its variations depending on the expected type) or sscanf() but it has a larger memory impact.

I agree.
It would be better to handle if toInt() would not change the value in case of an error to make it easy to generate an own exeption handling.

But this is nothing we can change here.

Regards,
Felix

Just don’t use that function…

You have to have an OS to thrown an exception to.
That is not simplified C++. It is simple system.

Like so many others, I write my own parsers.
Unlike many others, I evaluate incoming data 1 char at a time and don't use String or string.h functions.

'5' - '0' == 5

You don’t need an OS to throw exceptions in C++, it works on bare metal MCUs and is self contained to your code but they are memory- and compute-intensive.

Exception handling relies on stack unwinding, which increases code size and execution time and adds some heavy libraries (libgcc, libstdc++,…)

If there is no OS and an uncaught exception occurs in C++ after propagating through the stack, the program will call std::terminate(), which by default calls std::abort(). In a bare-metal environment, this typically results in an infinite loop, a CPU halt, or a system reset, depending on how std::abort() is implemented in the runtime library. If the microcontroller has a watchdog timer enabled, it may eventually reset the system.

On microcontrollers, exceptions work if the compiler provides support (which ours does), but they consume flash and RAM, so many IDE disable them with -fno-exceptions and use error codes instead where possible.

1 Like

Oh, sorry. If you want to throw exceptions and not risk crashing, you only need to provide some sort of code system to deal with that, and we do have libraries that do that for us.... but don't call that an OS.
Right you are!

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.