/* Stream.cpp - adds parsing methods to Stream class Copyright (c) 2008 David A. Mellis. All right reserved. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Created July 2011 parsing functions based on TextFinder library by Michael Margolis */ #include "Arduino.h" #include "Stream.h" // if/branch optimization #define UNLIKELY(x) (__builtin_expect (!!(x), 0)) #define LIKELY(x) (__builtin_expect (!!(x), 1)) #define PARSE_TIMEOUT 1000 // default number of milli-seconds to wait #define NO_SKIP_CHAR 1 // a magic char not found in a valid ASCII numeric field // private method to read stream with timeout int Stream::timedRead() { int c; _startMillis = millis(); do { c = read(); if (c >= 0) return c; wait_for_interrupt(); // XMega enhancement } while(millis() - _startMillis < _timeout); return -1; // -1 indicates timeout } // private method to peek stream with timeout int Stream::timedPeek() { int c; _startMillis = millis(); do { c = peek(); if (c >= 0) return c; wait_for_interrupt(); // XMega enhancement } while(millis() - _startMillis < _timeout); return -1; // -1 indicates timeout } // returns peek of the next digit in the stream or -1 if timeout // discards non-numeric characters int Stream::peekNextDigit() { int c; while (1) { c = timedPeek(); // refactored this for (possibly) better efficiency AND readability - seems to be the same size/footprint // if (c < 0) // return c; // timeout // // if (c == '-') // return c; // // if (c >= '0' && c <= '9') // return c; if(UNLIKELY(c < 0) || // refactored. < 0 is a timeout UNLIKELY(c == '-') || // negative (TODO: test for leading char only, or lead on exponent) LIKELY(c >= '0' && c <= '9')) // numeric digit; TODO test for exponent on float? { return c; } read(); // discard non-numeric } } // Public Methods ////////////////////////////////////////////////////////////// void Stream::setTimeout(unsigned long timeout) // sets the maximum number of milliseconds to wait { _timeout = timeout; } // find returns true if the target string is found bool Stream::find(char *target) { return findUntil(target, NULL/*""*/); } // reads data from the stream until the target string of given length is found // returns true if target string is found, false if timed out bool Stream::find(char *target, size_t length) { return findUntil(target, length, NULL, 0); } // as find but search ends if the terminator string is found bool Stream::findUntil(char *target, char *terminator) { return findUntil(target, strlen(target), terminator, strlen(terminator)); } // reads data from the stream until the target string of the given length is found // search terminated if the terminator string is found // returns true if target string is found, false if terminated or timed out bool Stream::findUntil(char *target, size_t targetLen, char *terminator, size_t termLen) { size_t index = 0; // maximum target string length is 64k bytes! size_t termIndex = 0; int c; if(!target || *target == 0) // update, allow NULL pointer { return true; // return true if target is a null string } while( (c = timedRead()) > 0) { if(c != target[index]) { index = 0; // reset index if any char does not match } if( c == target[index]) { //////Serial.print("found "); Serial.write(c); Serial.print("index now"); Serial.println(index+1); if(++index >= targetLen) // return true if all chars in the target match { return true; } } // allow for NULL 'terminator' if(terminator && termLen > 0 && UNLIKELY(c == terminator[termIndex])) { if(++termIndex >= termLen) { return false; // return false if terminate string found before target string } } else { termIndex = 0; } } return false; } // returns the first valid (long) integer value from the current position. // initial characters that are not digits (or the minus sign) are skipped // function is terminated by the first character that is not a digit. long Stream::parseInt() { return parseInt(NO_SKIP_CHAR); // terminate on first non-digit character (or timeout) } // as above but a given skipChar is ignored // this allows format characters (typically commas) in values to be ignored long Stream::parseInt(char skipChar) { boolean isNegative = false; long value = 0; int c; c = peekNextDigit(); // ignore non numeric leading characters if(c < 0) return 0; // zero returned if timeout do { if(c == skipChar) ; // ignore this charactor else if(c == '-') isNegative = true; else if(c >= '0' && c <= '9') // is c a digit? value = value * 10 + c - '0'; read(); // consume the character we got with peek c = timedPeek(); } while( (c >= '0' && c <= '9') || c == skipChar ) ; if(isNegative) value = -value; return value; } // as parseInt but returns a floating point value float Stream::parseFloat() { return parseFloat(NO_SKIP_CHAR); } // as above but the given skipChar is ignored // this allows format characters (typically commas) in values to be ignored float Stream::parseFloat(char skipChar) { boolean isNegative = false; boolean isFraction = false; long value = 0; char c; float fraction = 1.0; c = peekNextDigit(); // ignore non numeric leading characters if(c < 0) { return 0; // zero returned if timeout } do { if(c == skipChar) { // ignore } else if(c == '-') { isNegative = true; } else if (c == '.') { isFraction = true; } else if(c >= '0' && c <= '9') // is c a digit? { value = value * 10 + c - '0'; if(isFraction) { fraction *= 0.1; } } read(); // consume the character we got with peek c = timedPeek(); } while( (c >= '0' && c <= '9') || c == '.' || c == skipChar ) ; if(isNegative) value = -value; if(isFraction) return value * fraction; else return value; } // read characters from stream into buffer // terminates if length characters have been read, or timeout (see setTimeout) // returns the number of characters placed in the buffer // the buffer is NOT null terminated. // size_t Stream::readBytes(char *buffer, size_t length) { size_t count = 0; while (count < length) { int c = timedRead(); if (c < 0) break; *buffer++ = (char)c; count++; } return count; } // as readBytes with terminator character // terminates if length characters have been read, timeout, or if the terminator character detected // returns the number of characters placed in the buffer (0 means no valid data found) size_t Stream::readBytesUntil(char terminator, char *buffer, size_t length) { if (length < 1) return 0; size_t index = 0; while (index < length) { int c = timedRead(); if (c < 0 || c == terminator) break; *buffer++ = (char)c; index++; } return index; // return number of characters, not including null terminator } String Stream::readString() { String ret; int c = timedRead(); while (c >= 0) { ret += (char)c; c = timedRead(); } return ret; } String Stream::readStringUntil(char terminator) { String ret; int c = timedRead(); while (c >= 0 && c != terminator) { ret += (char)c; c = timedRead(); } return ret; }