/* * NMEA parser library for Arduino * * Simple and compact NMEA parser designed for Arduino * * Author : Glinnes Hulden * * This work is distributed under license CC0. * Check https://creativecommons.org/publicdomain/zero/1.0/deed.en * * No Copyright * * The person who associated a work with this deed has dedicated the work * to the public domain by waiving all of his or her rights to the work * worldwide under copyright law, including all related and neighboring rights, * to the extent allowed by law. * * You can copy, modify, distribute and perform the work, even for commercial * purposes, all without asking permission. See Other Information below. * * Other Information * * In no way are the patent or trademark rights of any person affected by CC0, * nor are the rights that other persons may have in the work or in how the * work is used, such as publicity or privacy rights. * Unless expressly stated otherwise, the person who associated a work with * this deed makes no warranties about the work, and disclaims liability for * all uses of the work, to the fullest extent permitted by applicable law. * When using or citing the work, you should not imply endorsement by the * author or the affirmer. */ #ifndef __NMEAParser_h__ #define __NMEAParser_h__ #ifdef __amd64__ /* To use on my development platform */ #include #include #include #include #include #include #else #include #endif namespace NMEA { /* * Error codes */ typedef enum { NO_ERROR, UNEXPECTED_CHAR, BUFFER_FULL, TYPE_TOO_LONG, CRC_ERROR, INTERNAL_ERROR } ErrorCode; } /* * The library consists of a single template: NMEAParser. */ template class NMEAParser { private: typedef void (*NMEAErrorHandler)(void); typedef void (*NMEAHandler)(void); typedef struct { char mToken[6]; NMEAHandler mHandler; } NMEAHandlerEntry; typedef enum { INIT, SENT, ARG, CRCH, CRCL, CRLFCR, CRLFLF } State; public: /* * maximum sentence size is 82 including the starting '$' and the * at the end. Since '$', the '*', the 2 characters CRC and the * are not bufferized, 82 - 6 + 1 = 77 chars are enough. * is enough. */ static const uint8_t kSentenceMaxSize = 77; private: /* * buffer to store the NMEA sentence excluding starting '$', the ',' * separator, the '*', the CRC and the . The tail of the buffer * is used to store the index of the arguments. */ char mBuffer[kSentenceMaxSize]; /* * Current index to store a char of the sentence */ uint8_t mIndex; /* * Current index to store the index of an argument */ uint8_t mArgIndex; /* * A handler to notify a malformed sentence */ NMEAErrorHandler mErrorHandler; /* * A handler for well formed but unrecognized sentences */ NMEAHandler mDefaultHandler; /* * An array of NMEA handler : pointers to functions to call when a sentence * is recognized */ NMEAHandlerEntry mHandlers[S]; /* * The current number of mHandlers */ uint8_t mHandlerCount; /* * Parsing automaton variable */ State mState; /* * mError */ NMEA::ErrorCode mError; /* * True if CRC is handled, false otherwise. Defaults to true */ bool mHandleCRC; /* * Variables used to computed and parse the CRC */ uint8_t mComputedCRC; uint8_t mGotCRC; /* * NMEAParserStringify is used internally to temporarely replace a char * in the buffer by a '\0' so that libc string functions may be used. * Instantiating a NMEAParserStringify object in a pair of {} defines * a section in which the 'stringification' is done : the constructor * does that according to the arguments and se destructor restore the buffer. */ class NMEAParserStringify { uint8_t mPos; char mTmp; NMEAParser *mParent; public: NMEAParserStringify(NMEAParser *inParent, uint8_t inPos) : mPos(inPos), mParent(inParent) { mTmp = mParent->mBuffer[mPos]; mParent->mBuffer[mPos] = '\0'; } ~NMEAParserStringify() { mParent->mBuffer[mPos] = mTmp; } }; /* * Call the error handler if defined */ void callErrorHandler(void) { if (mErrorHandler != NULL) { mErrorHandler(); } } /* * Reset the parser */ void reset() { mState = INIT; mIndex = 0; mArgIndex = kSentenceMaxSize; mError = NMEA::NO_ERROR; } /* * Called when the parser encounter a char that should not be there */ void unexpectedChar() { mError = NMEA::UNEXPECTED_CHAR; callErrorHandler(); reset(); } /* * Called when the buffer is full because of a malformed sentence */ void bufferFull() { mError = NMEA::BUFFER_FULL; callErrorHandler(); reset(); } /* * Called when the type of the sentence is longer than 5 characters */ void typeTooLong() { mError = NMEA::TYPE_TOO_LONG; callErrorHandler(); reset(); } /* * Called when the CRC is wrong */ void crcError() { mError = NMEA::CRC_ERROR; callErrorHandler(); reset(); } /* * Called when the state of the parser is not ok */ void internalError() { mError = NMEA::INTERNAL_ERROR; callErrorHandler(); reset(); } /* * retuns true is there is at least one byte left in the buffer */ bool spaceAvail() { return (mIndex < mArgIndex); } /* * convert a one hex digit char into an int. Used for the CRC */ static int8_t hexToNum(const char inChar) { if (isdigit(inChar)) return inChar - '0'; else if (isupper(inChar) && inChar <= 'F') return inChar - 'A' + 10; else if (islower(inChar) && inChar <= 'f') return inChar - 'a' + 10; else return -1; } static bool strnwcmp(const char *s1, const char *s2, uint8_t len) { while (len-- > 0) { if (*s1 != *s2 && *s1 != '-' && *s2 != '-') return false; s1++; s2++; } return true; } /* * return the slot number for a handler. -1 if not found */ int8_t getHandler(const char *inToken) { /* Look for the token */ for (uint8_t i = 0; i < mHandlerCount; i++) { if (strnwcmp(mHandlers[i].mToken, inToken, 5)) { return i; } } return -1; } /* * When all the sentence has been parsed, process it by calling the handler */ void processSentence() { /* Look for the token */ uint8_t endPos = startArgPos(0); int8_t slot; { NMEAParserStringify stfy(this, endPos); slot = getHandler(mBuffer); } if (slot != -1) { mHandlers[slot].mHandler(); } else { if (mDefaultHandler != NULL) { mDefaultHandler(); } } } /* * Return true if inArgNum corresponds to an actual argument */ bool validArgNum(uint8_t inArgNum) { return inArgNum < (kSentenceMaxSize - mArgIndex); } /* * Return the start index of the inArgNum th argument */ uint8_t startArgPos(uint8_t inArgNum) { return mBuffer[kSentenceMaxSize - 1 - inArgNum]; } /* * Return the end index of the inArgNum th argument */ uint8_t endArgPos(uint8_t inArgNum) { return mBuffer[kSentenceMaxSize - 2 - inArgNum]; } public: /* * Constructor initialize the parser. */ NMEAParser() : mErrorHandler(NULL), mDefaultHandler(NULL), mHandlerCount(0), mError(NMEA::NO_ERROR), mHandleCRC(true), mComputedCRC(0), mGotCRC(0) { reset(); } /* * Add a sentence handler */ void addHandler(const char *inToken, NMEAHandler inHandler) { if (mHandlerCount < S) { if (getHandler(inToken) == -1) { strncpy(mHandlers[mHandlerCount].mToken, inToken, 5); mHandlers[mHandlerCount].mToken[5] = '\0'; mHandlers[mHandlerCount].mHandler = inHandler; mHandlerCount++; } } } #ifdef __AVR__ /* * Add a sentence handler. Version with a token stored in flash. */ void addHandler(const __FlashStringHelper *ifsh, NMEAHandler inHandler) { char buf[6]; PGM_P p = reinterpret_cast(ifsh); for (uint8_t i = 0; i < 6; i++) { char c = pgm_read_byte(p++); buf[i] = c; if (c == '\0') break; } addHandler(buf, inHandler); } #endif /* * Set the error handler which is called when a sentence is malformed */ void setErrorHandler(NMEAErrorHandler inHandler) { mErrorHandler = inHandler; } /* * Set the default handler which is called when a sentence is well formed * but has no handler associated to */ void setDefaultHandler(NMEAHandler inHandler) { mDefaultHandler = inHandler; } /* * Give a character to the parser */ void operator<<(char inChar) { int8_t tmp; switch (mState) { /* Waiting for the starting $ character */ case INIT: mError = NMEA::NO_ERROR; if (inChar == '$') { mComputedCRC = 0; mState = SENT; } else unexpectedChar(); break; case SENT: if (isalnum(inChar)) { if (spaceAvail()) { if (mIndex < 5) { mBuffer[mIndex++] = inChar; mComputedCRC ^= inChar; } else { typeTooLong(); } } else bufferFull(); } else { switch(inChar) { case ',' : mComputedCRC ^= inChar; mBuffer[--mArgIndex] = mIndex; mState = ARG; break; case '*' : mGotCRC = 0; mBuffer[--mArgIndex] = mIndex; mState = CRCH; break; default : unexpectedChar(); break; } } break; case ARG: if (spaceAvail()) { switch(inChar) { case ',' : mComputedCRC ^= inChar; mBuffer[--mArgIndex] = mIndex; break; case '*' : mGotCRC = 0; mBuffer[--mArgIndex] = mIndex; mState = CRCH; break; default : mComputedCRC ^= inChar; mBuffer[mIndex++] = inChar; break; } } else bufferFull(); break; case CRCH: tmp = hexToNum(inChar); if (tmp != -1) { mGotCRC |= (uint8_t)tmp << 4; mState = CRCL; } else unexpectedChar(); break; case CRCL: tmp = hexToNum(inChar); if (tmp != -1) { mGotCRC |= (uint8_t)tmp; mState = CRLFCR; } else unexpectedChar(); break; case CRLFCR: if (inChar == '\r') { mState = CRLFLF; } else unexpectedChar(); break; case CRLFLF: if (inChar == '\n') { if (mHandleCRC && (mGotCRC != mComputedCRC)) { crcError(); } else { processSentence(); } reset(); } else unexpectedChar(); break; default: internalError(); break; } } /* * Returns the number of arguments discovered in a well formed sentence. */ uint8_t argCount() { return kSentenceMaxSize - mArgIndex - 1; } /* * Returns one of the arguments. Different versions according to data type. */ bool getArg(uint8_t num, char &arg) { if (validArgNum(num)) { uint8_t startPos = startArgPos(num); uint8_t endPos = endArgPos(num); arg = mBuffer[startPos]; return (endPos - startPos) == 1; } else return false; } bool getArg(uint8_t num, char *arg) { if (validArgNum(num)) { uint8_t startPos = startArgPos(num); uint8_t endPos = endArgPos(num); { NMEAParserStringify stfy(this, endPos); strcpy(arg, &mBuffer[startPos]); } return true; } else return false; } #ifdef ARDUINO bool getArg(uint8_t num, String &arg) { if (validArgNum(num)) { uint8_t startPos = startArgPos(num); uint8_t endPos = endArgPos(num); { NMEAParserStringify stfy(this, endPos); arg = &mBuffer[startPos]; } return true; } else return false; } #endif bool getArg(uint8_t num, int &arg) { if (validArgNum(num)) { uint8_t startPos = startArgPos(num); uint8_t endPos = endArgPos(num); { NMEAParserStringify stfy(this, endPos); arg = atoi(&mBuffer[startPos]); } return true; } else return false; } bool getArg(uint8_t num, float &arg) { if (validArgNum(num)) { uint8_t startPos = startArgPos(num); uint8_t endPos = endArgPos(num); { NMEAParserStringify stfy(this, endPos); arg = atof(&mBuffer[startPos]); } return true; } else return false; } /* * Returns the type of sentence. */ bool getType(char *arg) { if (mIndex > 0) { uint8_t endPos = startArgPos(0); { NMEAParserStringify stfy(this, endPos); strncpy(arg, mBuffer, 5); arg[5] = '\0'; } return true; } else return false; } #ifdef ARDUINO bool getType(String &arg) { if (mIndex > 0) { uint8_t endPos = startArgPos(0); { NMEAParserStringify stfy(this, endPos); arg = mBuffer; } return true; } else return false; } #endif bool getType(uint8_t inIndex, char &outTypeChar) { if (mIndex > 0) { uint8_t endPos = startArgPos(0); if (inIndex < endPos) { outTypeChar = mBuffer[inIndex]; return true; } else return false; } else return false; } NMEA::ErrorCode error() { return mError; } void setHandleCRC(bool inHandleCRC) { mHandleCRC = inHandleCRC; } #ifdef __amd64__ void printBuffer() { { NMEAParserStringify stfy(this, startArgPos(0)); printf("%s\n", mBuffer); } for (uint8_t i = 0; i < argCount(); i++) { uint8_t startPos = startArgPos(i); uint8_t endPos = endArgPos(i); { NMEAParserStringify stfy(this, endPos); printf("%s\n", &mBuffer[startPos]); } } } #endif }; #endif