Problem validating if a string is numeric

Hi everybody!

I need your help.

I’m parsing a String with values separated by a “;” character.
Those values represent hours and minutes in the format hh:mm (ie: “03:25;05:39;13:22”).

As the values are typed by a user I need to validate that they are only numbers and that they are between 0 and 23 for the hours and between 0 and 59 for the minutes.

I’m using .toInt() to convert the hours and the minutes but if I receive a non numeric value, .toInt() returns a 0 that in my case is in the range of valid numbers and is processed as 0 hour or 0 minute when it should be discarded.

Try typing the following values into the Serial Monitor to force the validation

00:10;aa:15;02:bb;25:33;04:75;13:30

I need only the first and last values to be inserted into the array and the other values (not valid non numeric or bigger than 23 or 59 items) to be discarded. But the code converts “aa” and “bb” to 0 breaking my validation.

Here is my sketch:

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
}

void loop() {
  // put your main code here, to run repeatedly:

  if (Serial.available() > 0) {
    ParseInputSerial(Serial.readString());
  }
}

void ParseInputSerial(String inputTimes) {
  int posInitSeparator = 0;
  String arrTimes[6];
  int posEndSeparator;

  String hm;
  int h;
  int m;

  for (int i = 0; i < 6; i ++) {
    posEndSeparator = inputTimes.indexOf(";", posInitSeparator);
    hm = inputTimes.substring(posInitSeparator, posEndSeparator);

    h = hm.substring(0, hm.indexOf(":")).toInt();
    m = hm.substring(hm.indexOf(":") + 1).toInt();

    if (h >= 0 && h <= 23 && m >= 0 && m <= 59) {
      arrTimes[i] = hm;
    } else {
      arrTimes[i] = "";
    }

    posInitSeparator = posEndSeparator + 1;
    Serial.println(arrTimes[i]);
  }
}

I’m stuck.
Any help will be very appreciated!
Thanks in advance.

Why are you using String? A NULL terminated char array is easier to iterate through, using isdigit(), isalnum(), etc. to determine the validity of the data in the array.

You might check each character and see if it is in the range of a numeric character. You would have to exclude the delimiters

This looks like a job for strtok() !

group__avr__string.html

As for validating it, you probably want to check each character with isDigit()

group__ctype.html

Using this

char buf[100];
etk::Rope(buf, 100);
int hours, minutes;

if(Serial.available() >= 6)
{
    rope.clear();
    while(Serial.available())
    {
        char c = Serial.read();
        rope.append(c);
        if(c == ';')
        {
            if(isDigit(rope[0]) && isDigit(rope[1])
                hours = rope.atoi(); //converts chars to int, will stop when it hits ':'
            if(isDigit(rope[3] && isDigit(rope[4])
                minutes = rope.atoi(3); //skips first three chars 'hh:' to extract minutes
        }
    }
}

Faster and smaller than String or strtok.

Thanks a lot people!

I’ve been trying your suggestions and came to this new sketch that works fine.
I’m sure that it could be optimized in several aspects (newbie mistakes…) but it validates just right all I needed to.

Here is the code (feel free to give me your advice):

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

String inputTime;
int counter = 0;
int arrItemPosition = 0;
String arrTimes[3];
int h;
int m;

void loop() {
  if (Serial.available() > 0) {
    char c = Serial.read();
    if (arrItemPosition < sizeof(arrTimes)) {
      if (c == ';' || c == '\n') {
        if (counter == 5) {
          if (inputTime.indexOf(":") == 2) {

            h = inputTime.substring(0, inputTime.indexOf(":")).toInt();
            m = inputTime.substring(inputTime.indexOf(":") + 1).toInt();

            if (h >= 0 && h <= 23 && m >= 0 && m <= 59) {
              arrTimes[arrItemPosition] = inputTime;
              Serial.println(arrTimes[arrItemPosition]);
              arrItemPosition += 1;
            }
          }
        }
        counter = 0;
        inputTime = "";
      } else {
        if (isDigit(c) || c == ':') {
          inputTime += c;
          counter += 1;
        }
      }
    }
  }
}

Thanks again!

Just a note:

these would incorrectly pass your current validation:

01:::;01::1;01:1:

arduinodlb:
Just a note:

these would incorrectly pass your current validation:

01:::;01::1;01:1:

arduinodlb: you are right! thanks.

I have to validate that the ":" character is at 3rd position (counter = 2) that is the only place where it is valid.
Something like this:

} else {
    if (isDigit(c) || (counter == 2 && c == ':')) {
        inputTime += c;
        counter += 1;
    }
}

I´ll fix it.
Thanks again!

As Paul suggested, the String class tends to bloat the code size. The final code you posted compiled to 4436 bytes for me. The version below, which does not use the String class compiles to 2520 bytes. Note that my code doesn’t function the same in that I flag a bogus field with a -1, you could use that to adjust the input as you see fit.

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

char inputTime[30];
char c;
int counter;
int arrItemPosition = 0;
int arrTimes[3];
int h;
int m;

void loop() {

  if (Serial.available() > 0) {
    inputTime[counter] = Serial.read(); // Test data: 00:10;aa:15;02:bb;25:33;04:75;13:30

    if (inputTime[counter] == ';' || inputTime[counter] == '\n') {  // End of time or input
      inputTime[counter++] = NULL;                                  // Make a string
      if (inputTime[2] == ':' && strlen(inputTime) == 5) {          // Field length = 5
        inputTime[2] = NULL;                                        // Make substrings
        inputTime[5] = NULL;
        if (isdigit(inputTime[0]) != 0 && isdigit(inputTime[1]) != 0) { // Digits?
          arrTimes[0] = atoi(inputTime);                            // Yes...
          if (arrTimes[0] < 0 || arrTimes[0] > 23)
            arrTimes[0] = -1;
        } else {
          arrTimes[0] = -1;                                         // Nope
        }
        if (isdigit(inputTime[3]) != 0 && isdigit(inputTime[4]) != 0) { // Same for 2nd field
          arrTimes[1] = atoi(&inputTime[3]);
          if (arrTimes[1] < 0 || arrTimes[1] > 59)
            arrTimes[1] = -1;
         } else {
          arrTimes[1] = -1;
        }
                                       
        Serial.print(arrTimes[0]);      // show fields.     -1 means invalid
        Serial.print("  ");
        Serial.println(arrTimes[1]);
        counter = 0;
      }
    } else {
      counter++;
    }
  }
}

As Paul suggested, the String class tends to bloat the code size. The final code you posted compiled to 4436 bytes for me. The version below, which does not use the String class compiles to 2520 bytes.

So is String use the problem, or is your comment just distracting noise as to fixing the the numeric identification problem?

zoomkat:
So is String use the problem, or is your comment just distracting noise as to fixing the the numeric identification problem?

The problem has been fixed. The comments now are just general comments.

@zoomcat: I think using String is always a problem. As to my post, its primary purpose was to show how the String class bloats the code size versus using a char array. Second, the code also shows another way of handling a non-digit entry on the input stream, even though an alternative already exists. As to my comment being "just distracting noise", I wonder if the OP finds my post or yours to be the more useful.

econjack:
@zoomcat: I think using String is always a problem.

Then your thinking will always be a problem in a technical setting where you are not actually addressing the problem being discussed. It is what it is.