using System; using Microsoft.SPOT; namespace CPF { public class NMEA_Parser { public delegate void EndOfMessageDelegate(ByteString s); private EndOfMessageDelegate _endOf_GGA_Message; private EndOfMessageDelegate _endOf_RMC_Message; private const uint _MAX_FIELD_SIZE = 15; private const byte _PERIOD = (byte) '.'; private const byte _COMMA = (byte) ','; private const byte _CR = (byte) '\r'; private const byte _LF = (byte) '\n'; private const byte _ASTERISK = (byte) '*'; private const byte _DOLLAR = (byte) '$'; private const byte _UPPER_CASE_A = (byte) 'A'; private const byte _UPPER_CASE_F = (byte) 'F'; private const byte _LOWER_CASE_A = (byte) 'a'; private const byte _LOWER_CASE_F = (byte) 'f'; private const byte _ZERO = (byte) '0'; private const byte _NINE = (byte) '9'; private const byte _MINUS = (byte) '-'; private const byte _NORTH = (byte) 'N'; private const byte _SOUTH = (byte) 'S'; private const byte _EAST = (byte) 'E'; private const byte _WEST = (byte) 'W'; // Value is arbitrary, just larger than max # of fields in NEMA message private const uint _END_OF_LINE = 100; private ByteString _GPRMC; private ByteString _GPGGA; // parsing state variables private delegate void _nmeaArgument(uint argumentNumber); private _nmeaArgument _processArgument; private uint _parity; private bool _isChecksumTerm; private ByteString _term; private ByteString _msg; private uint _curTermNumber; private bool _sentenceHasFix; // statistics private uint _encodedCharCount; private uint _sentencesWithFixCount; private uint _failedChecksumCount; private uint _passedChecksumCount; public NMEA_Parser(EndOfMessageDelegate process_GGA, EndOfMessageDelegate process_RMC) { _term = new ByteString(); _parity = 0; _isChecksumTerm = false; _curTermNumber = 0; _sentenceHasFix = false; _encodedCharCount = 0; _sentencesWithFixCount = 0; _failedChecksumCount = 0; _passedChecksumCount = 0; _GPGGA = new ByteString("GPGGA"); _GPRMC = new ByteString("GPRMC"); _msg = new ByteString(); _endOf_GGA_Message = process_GGA; _endOf_RMC_Message = process_RMC; GPS.Initialize(); } private void Process_GGA(uint argumentNumber) { switch (argumentNumber) { case 1 : // Time GPS.Time.Set(_term); break; case 2 : // Latitude GPS.Location.SetLatitude(_term); break; case 3 : // North / South GPS.Location._rawNewLatData._negative = (_term[0] == _SOUTH); break; case 4 : GPS.Location.SetLongitude(_term); break; case 5 : GPS.Location._rawNewLngData._negative = (_term[0] == _WEST); break; case 6 : // Fix data _sentenceHasFix = (_term[0] > _ZERO); break; case 7 : // Satellites used) GPS.Satellites.Set(_term); break; case 8 : // HDOP GPS.Hdop.Set(_term); break; case 9 : // Altitude GPS.Altitude.Set(_term); break; case _END_OF_LINE : GPS.Time.Commit(); if (_sentenceHasFix) { GPS.Location.Commit(); GPS.Altitude.Commit(); } GPS.Satellites.Commit(); GPS.Hdop.Commit(); _endOf_GGA_Message(_msg); break; default : // WTF? // TO DO: Add Eror handling break; } } private void Process_RMC(uint argumentNumber) { switch (argumentNumber) { case 1 : // Time GPS.Time.Set(_term); break; case 2 : // GPRMC validity _sentenceHasFix = (_term[0] == _UPPER_CASE_A); break; case 3 : // Latitude GPS.Location.SetLatitude(_term); break; case 4 : // North / South GPS.Location._rawNewLatData._negative = (_term[0] == _SOUTH); break; case 5 : // Longitude GPS.Location.SetLongitude(_term); break; case 6 : // East / West GPS.Location._rawNewLngData._negative = (_term[0] == _WEST); break; case 7 : // Speed GPS.Speed.Set(_term); break; case 8 : // Course GPS.Course.Set(_term); break; case 9 : // Date GPS.Date.Set(_term); break; case _END_OF_LINE: GPS.Date.Commit(); GPS.Time.Commit(); if (_sentenceHasFix) { GPS.Location.Commit(); GPS.Speed.Commit(); GPS.Course.Commit(); } _endOf_RMC_Message(_msg); break; default : // WTF? // TO DO: Add Eror handling break; } } private void Unknown_NMEA_Message(uint argumentNumber) { } // process one character received from GPS public bool Parse(byte ch) { bool isValidSentence = false; ++_encodedCharCount; switch (ch) { case _COMMA: case _CR: case _LF: case _ASTERISK: if ((ch != _CR) && (ch != _LF)) // Squash CR-LF _msg.Append(ch); if (ch == _COMMA) _parity ^= ch; if (_term.Available > 0) isValidSentence = EndOfMessage(); ++_curTermNumber; _term.Clear(); _isChecksumTerm = (ch == _ASTERISK) ? true : false; return isValidSentence; case _DOLLAR: // sentence begin _curTermNumber = 0; _parity = 0; _term.Clear(); _msg.Clear(); _msg.Append(ch); _processArgument = Unknown_NMEA_Message; _isChecksumTerm = false; _sentenceHasFix = false; return false; default: // ordinary characters if (_term.Available > 0) { _term.Append(ch); _msg.Append(ch); } if (!_isChecksumTerm) _parity ^= ch; return false; } } private bool EndOfMessage() { // If it's the checksum term, and the checksum checks out, commit if (_isChecksumTerm) { byte checksum = (byte) (16 * Conversions.HexCharacterToInteger(_term[0]) + Conversions.HexCharacterToInteger(_term[1])); if (checksum == _parity) { _passedChecksumCount++; if (_sentenceHasFix) ++_sentencesWithFixCount; _processArgument(_END_OF_LINE); return true; } else ++_failedChecksumCount; return false; } // the first term determines the sentence type if (_curTermNumber == 0) { if (_term == _GPRMC) _processArgument = Process_RMC; else if (_term == _GPGGA) _processArgument = Process_GGA; else _processArgument = Unknown_NMEA_Message; return false; } if (_term.Length > 0) _processArgument(_curTermNumber); return false; } public uint CharsProcessed() { return _encodedCharCount; } public uint SentencesWithFix() { return _sentencesWithFixCount; } public uint FailedChecksum() { return _failedChecksumCount; } public uint PassedChecksum() { return _passedChecksumCount; } } }