/// <summary> /// Parses a raw NMEA GSA (gps fix information) sentence. /// This is the prefered sentence to use for positions. /// It is only generated by NMEA 2.0 GPS units. All modern units should be version 2.0 complient. /// </summary> /// <remarks> /// GSA - essential fix data which provide 3D location and accuracy data. /// $GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39 /// Where: /// GSA Satellite status // A Auto selection of 2D or 3D fix(M = manual) // 3 3D fix - values include: 1 = no fix // 2 = 2D fix // 3 = 3D fix // 04,05... PRNs of satellites used for fix(space for 12) // 2.5 PDOP(dilution of precision) // 1.3 Horizontal dilution of precision(HDOP) // 2.1 Vertical dilution of precision(VDOP) // *39 the checksum data, always begins with* /// </remarks> /// <param name="sentence">NMEA GGA Senetence.</param> /// <returns>True if the sentence is successfully parsed.</returns> public bool ParseGSA(string sentence) { bool success = true; // The sentence should be a comma seperated list of values // Some may be empty! // Start by stripping the leading and trailing data. Char[] splitChars = { ',' }; //string[] words = sentence.Substring(1, sentence.IndexOf('*')).Split(splitChars, StringSplitOptions.None); string[] words = sentence.Split(splitChars, StringSplitOptions.None); // save the sentence type mTag = words[0]; // Do we have enough words to parse the fix status? if (words.Length > 2 && words[2].Length != 0) { // Get the fix code mNMEAMode = words[2].Equals("1", StringComparison.OrdinalIgnoreCase) ? NMEAMODE.NO_FIX : (words[2].Equals("2", StringComparison.OrdinalIgnoreCase) ? NMEAMODE.TWO_DIMENSION : NMEAMODE.THREE_DIMENSION); } else { // The fix status is invalid mNMEAMode = NMEAMODE.NO_MODE; success = false; } return(success); }
/// <summary> /// Diseminates NMEA2000 Position, Rapid Update CAN frames. /// </summary> /// <remarks> /// Position, Rapid Update PGN 129025 /// First two bytes of a NMEA2000 frame indicate the /// <field> /// name = "Latitude" /// type = "int" /// offset = "0" /// length = "32" /// signed = "yes" /// units = "deg" /// scaling = "0.0000001" /// </field> /// <field> /// name = "Longitude" /// type = "int" /// offset = "32" /// length = "32" /// signed = "yes" /// units = "deg" /// scaling = "0.0000001" /// </field> /// </remarks> public void ParsePositionRapidUpdate(byte[] data) { if (!BitConverter.IsLittleEndian) { Array.Reverse(data, 0, 4); Array.Reverse(data, 4, 4); } double lat = BitConverter.ToInt32(data, 0); lat *= 0.0000001; double lon = BitConverter.ToInt32(data, 4); lon *= 0.0000001; mPosition.Reset(lat, lon); mNMEAMode = NMEAMODE.TWO_DIMENSION; //Console.WriteLine("Position {0}", mPosition.ToString()); }
/// <summary> /// Parses a GPSD 'o' report. /// Attempts to return a complete time/position/velocity report as a unit. /// Any field for which data is not available being reported as ?. /// If there is no fix, the response is simply "O=?", otherwise a tag and timestamp are always reported. /// </summary> /// <param name="sentence">GPSD 'o' report.</param> /// <exception cref="System.Exception">Thrown if the sentence is not a GPSD report or sentence is not an 'O' report.</exception> public void Parse(string sentence) { // break up the sentence into fields Char[] splitChars = { ' ' }; string[] fields = sentence.Split(splitChars); // validate the sentence if (!fields[0].StartsWith("O=")) { throw new Exception("Invalid sentence, not an 'O' report."); } // make sure there is a complete report if (fields[0] == "O=?") { // need to invalidate all data fields = new string[] { "O=?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?" } } ; if (fields.Length != 15) { throw new Exception("Invalid sentence, invalid field count."); } // looks good to go mTag = fields[0].Substring(2); // check that this is a fresh update double newStamp = (fields[1] == "?")? Double.NaN : Double.Parse(fields[1]); mInvalid = (newStamp == double.NaN); mTimeStamp = new DateTime((long)newStamp); mTimeError = (fields[2] == "?")? Double.NaN : Double.Parse(fields[2]); mPosition.Reset((fields[3] == "?")? Double.NaN : Double.Parse(fields[3]), (fields[4] == "?")? Double.NaN : Double.Parse(fields[4])); mAltitude = (fields[5] == "?")? Double.NaN : Double.Parse(fields[5]); mHorizontalErrorEstimate = (fields[6] == "?")? 0.0 : Double.Parse(fields[6]); mVerticalErrorEstimate = (fields[7] == "?")? Double.NaN : Double.Parse(fields[7]); mCourseOverGround = (fields[8] == "?")? Double.NaN : Double.Parse(fields[8]); mSpeedOverGround = (fields[9] == "?")? Double.NaN : Double.Parse(fields[9]); mClimbSink = (fields[10] == "?")? Double.NaN : Double.Parse(fields[10]); mEstimatedErrorCourseOverGround = (fields[11] == "?")? Double.MaxValue : Double.Parse(fields[11]); mEstimatedErrorSpeedOverGround = (fields[12] == "?")? Double.MaxValue : Double.Parse(fields[12]); mEstimatedErrorClimbSink = (fields[13] == "?")? Double.MaxValue : Double.Parse(fields[13]); mNMEAMode = (NMEAMODE)((fields[14] == "?")? 0 : Int32.Parse(fields[14])); }
/// <summary> /// Parses a raw NMEA RMC (recommended minimum data for gps) sentence. /// This provides the least information for positions but is guarenteed to be generated by all GPS units. /// </summary> /// <remarks> /// $GPRMC,hhmmss.ss,A,llll.ll,a,yyyyy.yy,a,x.x,x.x,ddmmyy,x.x,a*hh /// 1 = UTC of position fix /// 2 = Data status (Navigation receiver warning A = OK, V = warning) /// 3 = Latitude of fix /// 4 = N or S /// 5 = Longitude of fix /// 6 = E or W /// 7 = Speed over ground in knots /// 8 = Track made good in degrees True /// 9 = UT date /// 10 = Magnetic variation degrees (Easterly var. subtracts from true course) /// 11 = E or W /// 12 = Checksum /// </remarks> /// <param name="sentence">NMEA RMC Senetence.</param> /// <returns>True if the sentence is successfully parsed.</returns> public bool ParseRMC(string sentence) { bool success = true; // The sentence should be a comma seperated list of values // Some may be empty! // Start by stripping the leading and trailing data. Char[] splitChars = { ',' }; //string[] words = sentence.Substring(1, sentence.IndexOf('*')).Split(splitChars, StringSplitOptions.None); string[] words = sentence.Split(splitChars, StringSplitOptions.None); // save the sentence type mTag = words[0]; // Do we have enough words to parse the fix status? if (words.Length > 2 && words[2].Length != 0) { // Get the fix flag //if (words[2].Equals("A", StringComparison.OrdinalIgnoreCase)) // mNMEAMode = NMEAMODE.TWO_DIMENSION; //else //{ // mNMEAMode = NMEAMODE.NO_FIX; // return false; //} if (!words[2].Equals("A", StringComparison.OrdinalIgnoreCase)) { mNMEAMode = NMEAMODE.NO_FIX; return(false); } } else { // The fix status is invalid mNMEAMode = NMEAMODE.NO_MODE; success = false; } // Do we have enough words to parse the UTC date/time? if (words.Length > 9) { if (words[1].Length != 0 && words[9].Length != 0) { try { // time part string utcTimeWord = words[1]; int utcHours = int.Parse(utcTimeWord.Substring(0, 2), NMEAGPSClient.NMEACultureInfo); // AA int utcMinutes = int.Parse(utcTimeWord.Substring(2, 2), NMEAGPSClient.NMEACultureInfo); // BB int utcSeconds = int.Parse(utcTimeWord.Substring(4, 2), NMEAGPSClient.NMEACultureInfo); // CC int utcMilliseconds = 0; if (utcTimeWord.Length > 6) { utcMilliseconds = Convert.ToInt32(float.Parse(utcTimeWord.Substring(6)) * 1000, NMEAGPSClient.NMEACultureInfo); // DDDD } // date part string utcDateWord = words[9]; int utcDay = int.Parse(utcDateWord.Substring(0, 2), NMEAGPSClient.NMEACultureInfo); int utcMonth = int.Parse(utcDateWord.Substring(2, 2), NMEAGPSClient.NMEACultureInfo); int utcYear = int.Parse(utcDateWord.Substring(4, 2), NMEAGPSClient.NMEACultureInfo) + 2000; // make the date/time mTimeStamp = new DateTime(utcYear, utcMonth, utcDay, utcHours, utcMinutes, utcSeconds, utcMilliseconds, DateTimeKind.Utc); //CNXLog.InfoFormat("ParseRMC DateTime {0} time word {1} date word {2}", mTimeStamp.ToString(), words[1], words[9]); } catch (Exception e) { // The UTC date/time is invalid CNXLog.ErrorFormat("ParseRMC DateTime error {0} {1}", sentence, e); mTimeStamp = DateTime.MaxValue; success = false; } } else { // The UTC date/time is invalid CNXLog.ErrorFormat("ParseRMC DateTime sentence length error {0} {1}", sentence); mTimeStamp = DateTime.MaxValue; success = false; } } else { // The UTC date/time is invalid CNXLog.ErrorFormat("ParseRMC DateTime sentence length error {0} {1}", sentence); mTimeStamp = DateTime.MaxValue; success = false; } // Do we have enough data to parse the location? if (words.Length > 6 && words[3].Length != 0 && words[4].Length != 0 && words[5].Length != 0 && words[6].Length != 0) { mPosition = ParseNMEAPosition(words[3], words[4], words[5], words[6]); } // Do we have enough info to process speed? if (words.Length > 7 && words[7].Length != 0) { // The speed is the sixth word, expressed in knots // Conversion, 1,852 Knot to meter mSpeedOverGround = ParseVelocity(words[7]); } else { mSpeedOverGround = double.MinValue; } // do we have enough info to process the bearing? if (words.Length > 8 && words[8].Length != 0) { // The bearing is the seventh word if (!double.TryParse(words[8], out mCourseOverGround)) { mCourseOverGround = double.NaN; } } else { mCourseOverGround = double.NaN; } return(success); }