Hi guys,
In working around the Serial class functions on my MKR Zero board (SAMD21 architecture), I came across the following issue.
The Serial.readStringUntil(char terminator) function is documented within the Arduino reference library to have the following return behavior:
Returns the entire string read from the serial buffer, up to the terminator character. If the terminator character can’t be found, or if there is no data before the terminator character, it will return NULL.
However, when testing with the simple code below, I found that the function will never return a NULL response. It either returns a String determined by the given terminator char (with char removed).... or just returns whatever String it has when the serial timeout expires. This persists across different terminators, using the all the line ending options in the serial monitor, etc.
void setup(){
Serial.begin(9600);
}
void loop(){
String input = Serial.readStringUntil('\n');
Serial.println(input);
}
I noted that the function is inherited from the Stream class, so I dug into the Stream.cpp on AVR core github and found the following relevant sections.
int Stream::timedRead()
{
int c;
_startMillis = millis();
do {
c = read();
if (c >= 0) return c;
} while(millis() - _startMillis < _timeout);
return -1; // -1 indicates timeout
}
String Stream::readStringUntil(char terminator)
{
String ret;
int c = timedRead();
while (c >= 0 && c != terminator)
{
ret += (char)c;
c = timedRead();
}
return ret;
}
It seems like the behavior I'm seeing is what's coded, unless I'm missing something. Does anyone know why this is the case? The documented function is to differentiate between messages based on the presence of a terminator, but the actual function is just String or delayed String.
Below is a stand-in function for the functionality I'm looking for that I based on the github code above.
String readStringUntilWithTimeout(Stream& stream, char terminator, unsigned long timeout = Serial.getTimeout()) {
String ret;
unsigned long startTime = millis();
while (millis() - startTime < timeout) {
if (stream.available() > 0) {
char c = stream.read();
if (c == terminator) {
return ret;
} else {
ret += c;
}
}
}
// Timeout occurred, discard the result
ret = "";
return ret;
}
// example loop using this function
void loop() {
if (Serial.available()) {
if (String input = readStringUntilWithTimeout(Serial, '\n'); input.isEmpty()) {
Serial.println("Timed out looking for terminator.");
} else {
Serial.println(input);
}
}
}
Should this be changed in the code, or the documentation? Or am I completely missing what's going on here?
Also I did check the SAMD core libraries on github since I'm on a MKR Zero, but couldn't find a Stream class reference. I'm guessing they just take the AVR code.
Also yes, I'm aware Strings are bad. I don't think that needs to be discussed.