Fail to test a string with indexOf

Hey,

I am trying to check communication through serial between the Portenta H7 and the Cat M1 shield.

I do an AT command which returns (print into its Serial)

AT+CFUN=1,1

OK

^SYSSTART

(I voluntarily copy/pasted with blank spaces).

Firstly I have this code:

unsigned long end = millis() + TIMEOUT;
  this->debug.log("await -> " + delimiter + " || timeout -> " + String(end) + "ms");
  String input = "";
  bool found = false;
  while( true ){
    if( ModemSerial.available() ){
      String line = ModemSerial.readStringUntil('\n');
      input += line;
      // Delimiter found in new line
      if( this->test(line, delimiter) ){
        found = true;
        break;
      }
    }
    // Delimiter found in whole input
    if( this->test(input, delimiter) ){
      found = true;
      break;
    }
    // Timeout reached
    if( millis() > end ) break;
  }
  this->debug.log("await input -> " + input);
  return found ? input : "";

Which works and found the string I am searching ("^SYSSTART").

But I want to then check from another place if the returned String contains "^SYSSTART" to create a bool but the 'response.indexOf("^SYSSTART")' now fails...

Why the .indexOf() fail to find the string despite me seeing it well in the Serial Monitor ?

You will need to post full code (or a representative example). I don't see an indexOf in your code.

line is only known inside the if and nowhere else. Can that be the possible cause of your issue?

Ah yes my bad I hided it in another function to return a bool.

Here it is:

bool ModemCom::test( String input, String delimiter ){
  return input.indexOf(delimiter) != -1;
}

And to answe your other question, no, the fact that line is defined in the if dont cause any issue.
BUT
Checking line works where checking input (where the line got appended to) doesnt...
I dont understand why, I am missing something, its not logical...

And what is the delimiter variable in below?

'\r'? '\n'? Something else?

Please print the received line or input as shown below

    String x = Serial.readStringUntil('\n');

    for (uint8_t cnt = 0; cnt < x.length(); cnt++)
    {
      if (x[cnt] < 0x10)
      {
        Serial.print("0");
      }
      Serial.print(x[cnt], HEX);
      Serial.print(" ");
    }

A full demo sketch

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

void loop()
{
  if (Serial.available())
  {
    String x = Serial.readStringUntil('\n');

    for (uint8_t cnt = 0; cnt < x.length(); cnt++)
    {
      if (x[cnt] < 0x10)
      {
        Serial.print("0");
      }
      Serial.print(x[cnt], HEX);
      Serial.print(" ");
    }
  }
}

Serial monitor settings CR and LF; output when sending hello

16:27:30.166 -> 68 65 6C 6C 6F 0D 

Note the 0D (CR) at the end.

The delimiter (what I search in indexOf()) is `"^SYSSTART".

And here is the output from your chunk of code:
41 54 2B 43 46 55 4E 3D 31 2C 31 0D 0D 4F 4B 0D 00 0D 5E 53 59 53 53 54 41 52 54 0D
which correspond to the shield serial output of:
`AT+CFUN=1,1

OK

^SYSSTART`.

The .indexOf("^SYSSTART") fail.

Maybe these changes will shine some light on your issue.

  // Delimiter found in whole input
  if (this->test(input, delimiter))
  {
    found = true;
    this->debug.log("await input -> delimiter found");
    break;
  }

  // Timeout reached
  if (millis() > end)
  {
    this->debug.log("await input -> timed out");
    break;
  }

I don't use a Portenta and don't have the the board package installed so can't test or compile.

Note:
I suspect a timeout issue but don't know what the TIMEOUT value is (I might have missed it) and I don't have your sender.

Maybe useful to note that, in addition to the bespoke timeout mechanism in the sketch, there is another one in the Stream class's readStringUntil function:

https://docs.arduino.cc/language-reference/en/functions/communication/stream/streamReadStringUntil/

The function terminates if the terminator character is detected or it times out (see setTimeout()).

https://docs.arduino.cc/language-reference/en/functions/communication/stream/streamSetTimeout/

setTimeout() sets the maximum milliseconds to wait for stream data, it defaults to 1000 milliseconds.

No it is not a timeout because I see the word in the monitor. Timeout is equal to 10 000 (10s) and the text is complete in 2s.
So is everything alright in the output I pasted you?
I tried to "read" it and I think it is ok except the "00" I dont know what is is.

The 00 is the string terminator; I suspect that anything after that will not be checked by indexOf.

I did see it but it did not click.

For you to figure out where it comes from; it might be the sender.

Thanks !
Its exactly that, everything before that could be found but not after.
I cannot control the sender, its a built-in software...
So I just cleaned the input string to remove the 00 (string terminator) that way:

String cleaned = "";
for( unsigned int character = 0; character < input.length(); character++ ){
  // Ignore string terminator
  if( input[character] != '\0') cleaned += input[character];
}

And it works perfectly.

Now, I have a many questions:

  • is it normal for a String to be able to contain a string terminator?
    Shouldn't it just be ignored and not inserted during concatenation or any kind of merging?
  • Is it normal for indexOf() to be stopped at first string terminator rather than the whole string?
    Or maybe adding that info into the documentation could prevent cases like this one?

But, seriously, thank you! :slight_smile:

Under the hood String is nothing more than a null-terminated character array. But your concatenation should throw that one null-character away.

On the other hand, your sender might transfer that character in which case you would have two null-characters and the concatenation will only throw the last one away.

You are reading up to '\n'; if the sender sends the '\0' (null-terminator) that will be the first byte of the next received data.

You can test what you actually received with a simple test sketch that just displays received characters in hex.

#define ModemSerial Serial3

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

void loop()
{
  static uint8_t cnt;

  if (ModemSerial.available())
  {
    char ch = ModemSerial.read();
    if (ch < 0x10)
    {
      Serial.print("0");
    }
    Serial.print(ch, HEX);
    Serial.print("");
    cnt++;
    if (cnt == 16)
    {
      Serial.println();
      cnt = 0;
    }
  }
}

Not tested but should work. It will display 16 received characters in hex on a single line.
You will find the '\r' (0x0D) followed by the '\n' (0x0A) followed the '\0' (0x00). If you also see that after the ^SYSSTART or any other messages, you should not read until '\n' but till '\0'.

Note:
I do not know what ModemSerial is and hence have just simulated with a #define.

As said, the String class uses a null-terminated character array under the hood. And as mentioned, '\0' indicates the end of the text in the underlying character array. indexOf() considers the first '\0' that it finds as the end of the text and will not search further. So it is normal.

This is the function that you use

int String::indexOf(const String &s2) const
{
	return indexOf(s2, 0);
}

Which calls

int String::indexOf(const String &s2, unsigned int fromIndex) const
{
	if (fromIndex >= len) return -1;
	const char *found = strstr(buffer + fromIndex, s2.buffer);
	if (found == NULL) return -1;
	return found - buffer;
}

As you can see it uses strstr which is a c-string function (not a String function); read up on c-strings :wink:

You can find the string class (on a Windows system) in C:\Users\yourUsername\AppData\Local\Arduino15\packages\arduino\hardware\mbed_portenta\4.2.1\cores\arduino\api\String.cpp. For other operating systems, check https://support.arduino.cc/hc/en-us/articles/4415103213714-Find-sketches-libraries-board-cores-and-other-files-on-your-computer#boards where to find the board packages and take it from there.