diff --git a/README.md b/README.md index 9837e49..11cefb3 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ void onRmcUpdate(nmea::RmcData const rmc) Serial.println(); } /* ... */ -ArduinoNmeaParser parser(onRmcUpdate); +ArduinoNmeaParser parser(onRmcUpdate, nullptr); /* ... */ void setup() { Serial.begin(9600); diff --git a/examples/NMEA-Basic/NMEA-Basic.ino b/examples/NMEA-Basic/NMEA-Basic.ino index d69a7e0..326c945 100644 --- a/examples/NMEA-Basic/NMEA-Basic.ino +++ b/examples/NMEA-Basic/NMEA-Basic.ino @@ -25,12 +25,13 @@ **************************************************************************************/ void onRmcUpdate(nmea::RmcData const); +void onGgaUpdate(nmea::GgaData const); /************************************************************************************** * GLOBAL VARIABLES **************************************************************************************/ -ArduinoNmeaParser parser(onRmcUpdate); +ArduinoNmeaParser parser(onRmcUpdate, onGgaUpdate); /************************************************************************************** * SETUP/LOOP @@ -55,6 +56,8 @@ void loop() void onRmcUpdate(nmea::RmcData const rmc) { + Serial.print("RMC "); + if (rmc.source == nmea::RmcSource::GPS) Serial.print("GPS"); else if (rmc.source == nmea::RmcSource::GLONASS) Serial.print("GLONASS"); else if (rmc.source == nmea::RmcSource::Galileo) Serial.print("Galileo"); @@ -84,3 +87,41 @@ void onRmcUpdate(nmea::RmcData const rmc) Serial.println(); } + +void onGgaUpdate(nmea::GgaData const gga) +{ + Serial.print("GGA "); + + if (gga.source == nmea::GgaSource::GPS) Serial.print("GPS"); + else if (gga.source == nmea::GgaSource::GLONASS) Serial.print("GLONASS"); + else if (gga.source == nmea::GgaSource::Galileo) Serial.print("Galileo"); + else if (gga.source == nmea::GgaSource::GNSS) Serial.print("GNSS"); + + Serial.print(" "); + Serial.print(gga.time_utc.hour); + Serial.print(":"); + Serial.print(gga.time_utc.minute); + Serial.print(":"); + Serial.print(gga.time_utc.second); + Serial.print("."); + Serial.print(gga.time_utc.microsecond); + + if (gga.fix_quality != nmea::FixQuality::Invalid) + { + Serial.print(" : LON "); + Serial.print(gga.longitude); + Serial.print(" ° | LAT "); + Serial.print(gga.latitude); + Serial.print(" ° | Num Sat. "); + Serial.print(gga.num_satellites); + Serial.print(" | HDOP = "); + Serial.print(gga.hdop); + Serial.print(" m | Altitude "); + Serial.print(gga.altitude); + Serial.print(" m | Geoidal Separation "); + Serial.print(gga.geoidal_separation); + Serial.print(" m"); + } + + Serial.println(); +} diff --git a/extras/test/CMakeLists.txt b/extras/test/CMakeLists.txt index db73115..5346f70 100644 --- a/extras/test/CMakeLists.txt +++ b/extras/test/CMakeLists.txt @@ -22,16 +22,23 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) set(TEST_TARGET testNmeaParser) set(TEST_SRCS + src/ArduinoNmeaParser/test_OnGgaUpdateFunc.cpp + src/ArduinoNmeaParser/test_OnRmcUpdateFunc.cpp src/test_ArduinoNmeaParser.cpp src/test_checksum.cpp + src/test_GxGGA.cpp src/test_GxRMC.cpp src/test_Types.cpp src/test_main.cpp + src/test_gga.cpp src/test_rmc.cpp ../../src/nmea/util/checksum.cpp + ../../src/nmea/util/common.cpp + ../../src/nmea/util/gga.cpp ../../src/nmea/util/rmc.cpp ../../src/nmea/util/timegm.c + ../../src/nmea/GxGGA.cpp ../../src/nmea/GxRMC.cpp ../../src/nmea/Types.cpp ../../src/ArduinoNmeaParser.cpp diff --git a/extras/test/src/ArduinoNmeaParser/test_OnGgaUpdateFunc.cpp b/extras/test/src/ArduinoNmeaParser/test_OnGgaUpdateFunc.cpp new file mode 100644 index 0000000..814fe98 --- /dev/null +++ b/extras/test/src/ArduinoNmeaParser/test_OnGgaUpdateFunc.cpp @@ -0,0 +1,76 @@ +/** + * This software is distributed under the terms of the MIT License. + * Copyright (c) 2020 LXRobotics. + * Author: Alexander Entinger + * Contributors: https://github.com/107-systems/107-Arduino-NMEA-Parser/graphs/contributors. + */ + +/************************************************************************************** + * INCLUDE + **************************************************************************************/ + +#include + +#include +#include + +#include + +#include + +/************************************************************************************** + * GLOBAL VARIABLES + **************************************************************************************/ + +static bool on_gga_update_called = false; + +/************************************************************************************** + * FUNCTION DEFINITION + **************************************************************************************/ + +static void encode(ArduinoNmeaParser & parser, std::string const & nmea) +{ + std::for_each(std::begin(nmea), + std::end(nmea), + [&parser](char const c) + { + parser.encode(c); + }); +} + +void onGgaUpdate(nmea::GgaData const data) +{ + on_gga_update_called = true; + + REQUIRE(data.source == nmea::GgaSource::GPS); + + REQUIRE(data.time_utc.hour == 11); + REQUIRE(data.time_utc.minute == 19); + REQUIRE(data.time_utc.second == 8); + REQUIRE(data.time_utc.microsecond == 952); + + REQUIRE(data.latitude == Approx(48.633433)); + REQUIRE(data.longitude == Approx(13.026492)); + + REQUIRE(data.fix_quality == nmea::FixQuality::GPS_Fix); + + REQUIRE(data.num_satellites == 5); + REQUIRE(data.hdop == Approx(2.4)); + REQUIRE(data.altitude == Approx(454.7)); + REQUIRE(data.geoidal_separation == Approx(46.6)); + + REQUIRE(data.dgps_age == Approx(0.0)); + REQUIRE(strncmp(data.dgps_id, "0000", 4) == 0); +} + +/************************************************************************************** + * TEST CODE + **************************************************************************************/ + +TEST_CASE("Testing execution of OnGgaUpdateFunc when a full GxGGA message has been received", "[OnGgaUpdateFunc-01]") +{ + ArduinoNmeaParser parser(nullptr, onGgaUpdate); + std::string const GPGGA = "$GPGGA,111908.952,4838.0060,N,01301.5895,E,1,05,2.4,454.7,M,46.6,M,0.0,0000*7A\r\n"; + encode(parser, GPGGA); + REQUIRE(on_gga_update_called == true); +} diff --git a/extras/test/src/ArduinoNmeaParser/test_OnRmcUpdateFunc.cpp b/extras/test/src/ArduinoNmeaParser/test_OnRmcUpdateFunc.cpp new file mode 100644 index 0000000..ffb38d7 --- /dev/null +++ b/extras/test/src/ArduinoNmeaParser/test_OnRmcUpdateFunc.cpp @@ -0,0 +1,77 @@ +/** + * This software is distributed under the terms of the MIT License. + * Copyright (c) 2020 LXRobotics. + * Author: Alexander Entinger + * Contributors: https://github.com/107-systems/107-Arduino-NMEA-Parser/graphs/contributors. + */ + +/************************************************************************************** + * INCLUDE + **************************************************************************************/ + +#include + +#include +#include + +#include + +#include + +/************************************************************************************** + * GLOBAL VARIABLES + **************************************************************************************/ + +static bool on_rmc_update_called = false; + +/************************************************************************************** + * FUNCTION DEFINITION + **************************************************************************************/ + +static void encode(ArduinoNmeaParser & parser, std::string const & nmea) +{ + std::for_each(std::begin(nmea), + std::end(nmea), + [&parser](char const c) + { + parser.encode(c); + }); +} + +void onRmcUpdate(nmea::RmcData const data) +{ + on_rmc_update_called = true; + + REQUIRE(data.source == nmea::RmcSource::GPS); + + REQUIRE(data.time_utc.hour == 5); + REQUIRE(data.time_utc.minute == 28); + REQUIRE(data.time_utc.second == 56); + REQUIRE(data.time_utc.microsecond == 105); + + REQUIRE(data.is_valid == true); + + REQUIRE(data.latitude == Approx(52.514567)); + REQUIRE(data.longitude == Approx(13.350933)); + + REQUIRE(data.speed == Approx(44.088f)); + REQUIRE(data.course == Approx(206.4f)); + + REQUIRE(data.date.day == 8); + REQUIRE(data.date.month == 7); + REQUIRE(data.date.year == 2020); + + REQUIRE(data.magnetic_variation == Approx(0.0)); +} + +/************************************************************************************** + * TEST CODE + **************************************************************************************/ + +TEST_CASE("Testing execution of OnRmcUpdateFunc when a full GxRMC message has been received", "[OnRmcUpdateFunc-01]") +{ + ArduinoNmeaParser parser(onRmcUpdate, nullptr); + std::string const GPRMC = ("$GPRMC,052856.105,A,5230.874,N,01321.056,E,085.7,206.4,080720,000.0,W*78\r\n"); + encode(parser, GPRMC); + REQUIRE(on_rmc_update_called == true); +} diff --git a/extras/test/src/test_ArduinoNmeaParser.cpp b/extras/test/src/test_ArduinoNmeaParser.cpp index 5fa8481..2a8fa27 100644 --- a/extras/test/src/test_ArduinoNmeaParser.cpp +++ b/extras/test/src/test_ArduinoNmeaParser.cpp @@ -21,7 +21,7 @@ * FUNCTION DEFINITION **************************************************************************************/ -void encode(ArduinoNmeaParser & parser, std::string const & nmea) +static void encode(ArduinoNmeaParser & parser, std::string const & nmea) { std::for_each(std::begin(nmea), std::end(nmea), @@ -37,7 +37,7 @@ void encode(ArduinoNmeaParser & parser, std::string const & nmea) TEST_CASE("No NMEA message received", "[Parser-01]") { - ArduinoNmeaParser parser(nullptr); + ArduinoNmeaParser parser(nullptr, nullptr); REQUIRE(parser.error() == ArduinoNmeaParser::Error::None); REQUIRE(parser.rmc().is_valid == false); @@ -58,7 +58,7 @@ TEST_CASE("No NMEA message received", "[Parser-01]") TEST_CASE("RMC message after startup, no satellites", "[Parser-02]") { - ArduinoNmeaParser parser(nullptr); + ArduinoNmeaParser parser(nullptr, nullptr); std::string const GPRMC = ("$GPRMC,,V,,,,,,,,,,N*53\r\n"); @@ -83,7 +83,7 @@ TEST_CASE("RMC message after startup, no satellites", "[Parser-02]") TEST_CASE("RMC message after startup, time fix available", "[Parser-03]") { - ArduinoNmeaParser parser(nullptr); + ArduinoNmeaParser parser(nullptr, nullptr); std::string const GPRMC = ("$GPRMC,141928.00,V,,,,,,,,,,N*7A\r\n"); @@ -108,7 +108,7 @@ TEST_CASE("RMC message after startup, time fix available", "[Parser-03]") TEST_CASE("Decoding starts mid-message", "[Parser-04]") { - ArduinoNmeaParser parser(nullptr); + ArduinoNmeaParser parser(nullptr, nullptr); std::string const GPRMC = "077.0,023.5,080720,000.0,W*79\r\n$GPRMC,052852.105,A,5230.868,N,01320.958,E,077.0,023.5,080720,000.0,W*79\r\n"; @@ -128,7 +128,7 @@ TEST_CASE("Decoding starts mid-message", "[Parser-04]") TEST_CASE("NMEA message with data corruption (checksum mismatch) received", "[Parser-05]") { - ArduinoNmeaParser parser(nullptr); + ArduinoNmeaParser parser(nullptr, nullptr); std::string const GPRMC = "$GPXXX,052852.105,A,5230.868,N,01320.958,E,077.0,023.5,080720,000.0,W*79\r\n"; @@ -139,7 +139,7 @@ TEST_CASE("NMEA message with data corruption (checksum mismatch) received", "[Pa TEST_CASE("Multiple NMEA messages received", "[Parser-06]") { - ArduinoNmeaParser parser(nullptr); + ArduinoNmeaParser parser(nullptr, nullptr); std::vector const GPRMC = { diff --git a/extras/test/src/test_GxGGA.cpp b/extras/test/src/test_GxGGA.cpp new file mode 100644 index 0000000..b29517e --- /dev/null +++ b/extras/test/src/test_GxGGA.cpp @@ -0,0 +1,108 @@ +/** + * This software is distributed under the terms of the MIT License. + * Copyright (c) 2020 LXRobotics. + * Author: Alexander Entinger + * Contributors: https://github.com/107-systems/107-Arduino-NMEA-Parser/graphs/contributors. + */ + +/************************************************************************************** + * INCLUDE + **************************************************************************************/ + +#include + +#include + +#include + +#include + +/************************************************************************************** + * GLOBAL VARIABLES + **************************************************************************************/ + +static nmea::GgaData data; + +/************************************************************************************** + * TEST CODE + **************************************************************************************/ + +SCENARIO("Extracting satellite system from valid GxGGA message", "[GxGGA-01]") +{ + WHEN("GPS") + { + std::string const GPGGA = "$GPGGA,111908.952,4838.0060,N,01301.5895,E,1,05,2.4,454.7,M,46.6,M,0.0,0000*7A\r\n"; + nmea::GxGGA::parse(GPGGA.c_str(), data); + REQUIRE(data.source == nmea::GgaSource::GPS); + } +} + +TEST_CASE("Extracting position time from valid GxGGA message", "[GxGGA-02]") +{ + std::string const GPGGA = "$GPGGA,111908.952,4838.0060,N,01301.5895,E,1,05,2.4,454.7,M,46.6,M,0.0,0000*7A\r\n"; + nmea::GxGGA::parse(GPGGA.c_str(), data); + REQUIRE(data.time_utc.hour == 11); + REQUIRE(data.time_utc.minute == 19); + REQUIRE(data.time_utc.second == 8); + REQUIRE(data.time_utc.microsecond == 952); +} + +SCENARIO("Extracting latitude/longiture from valid GPGGA message", "[GxGGA-03]") +{ + std::string const GPGGA = "$GPGGA,111908.952,4838.0060,N,01301.5895,E,1,05,2.4,454.7,M,46.6,M,0.0,0000*7A\r\n"; + nmea::GxGGA::parse(GPGGA.c_str(), data); + REQUIRE(data.latitude == Approx(48.633433)); + REQUIRE(data.longitude == Approx(13.026492)); +} + +TEST_CASE("Extracting fix quality angle from valid GxGGA message", "[GxGGA-04]") +{ + WHEN("GPS Fix") + { + std::string const GPGGA = "$GPGGA,111908.952,4838.0060,N,01301.5895,E,1,05,2.4,454.7,M,46.6,M,0.0,0000*7A\r\n"; + nmea::GxGGA::parse(GPGGA.c_str(), data); + REQUIRE(data.fix_quality == nmea::FixQuality::GPS_Fix); + } +} + +SCENARIO("Extracting number of satellites from valid GPGGA message", "[GxGGA-05]") +{ + std::string const GPGGA = "$GPGGA,111908.952,4838.0060,N,01301.5895,E,1,05,2.4,454.7,M,46.6,M,0.0,0000*7A\r\n"; + nmea::GxGGA::parse(GPGGA.c_str(), data); + REQUIRE(data.num_satellites == 5); +} + +SCENARIO("Extracting horizontal dilution of precision from valid GPGGA message", "[GxGGA-06]") +{ + std::string const GPGGA = "$GPGGA,111908.952,4838.0060,N,01301.5895,E,1,05,2.4,454.7,M,46.6,M,0.0,0000*7A\r\n"; + nmea::GxGGA::parse(GPGGA.c_str(), data); + REQUIRE(data.hdop == Approx(2.4)); +} + +SCENARIO("Extracting altitude from valid GPGGA message", "[GxGGA-07]") +{ + std::string const GPGGA = "$GPGGA,111908.952,4838.0060,N,01301.5895,E,1,05,2.4,454.7,M,46.6,M,0.0,0000*7A\r\n"; + nmea::GxGGA::parse(GPGGA.c_str(), data); + REQUIRE(data.altitude == Approx(454.7)); +} + +SCENARIO("Extracting geoidal separation from valid GPGGA message", "[GxGGA-08]") +{ + std::string const GPGGA = "$GPGGA,111908.952,4838.0060,N,01301.5895,E,1,05,2.4,454.7,M,46.6,M,0.0,0000*7A\r\n"; + nmea::GxGGA::parse(GPGGA.c_str(), data); + REQUIRE(data.geoidal_separation == Approx(46.6)); +} + +SCENARIO("Extracting time since last DGPS update from valid GPGGA message", "[GxGGA-09]") +{ + std::string const GPGGA = "$GPGGA,111908.952,4838.0060,N,01301.5895,E,1,05,2.4,454.7,M,46.6,M,0.0,0000*7A\r\n"; + nmea::GxGGA::parse(GPGGA.c_str(), data); + REQUIRE(data.dgps_age == Approx(0.0)); +} + +SCENARIO("Extracting DGPS station id from valid GPGGA message", "[GxGGA-10]") +{ + std::string const GPGGA = "$GPGGA,111908.952,4838.0060,N,01301.5895,E,1,05,2.4,454.7,M,46.6,M,0.0,0000*7A\r\n"; + nmea::GxGGA::parse(GPGGA.c_str(), data); + REQUIRE(strncmp(data.dgps_id, "0000", 4) == 0); +} diff --git a/extras/test/src/test_gga.cpp b/extras/test/src/test_gga.cpp new file mode 100644 index 0000000..f5c1042 --- /dev/null +++ b/extras/test/src/test_gga.cpp @@ -0,0 +1,63 @@ +/** + * This software is distributed under the terms of the MIT License. + * Copyright (c) 2020 LXRobotics. + * Author: Alexander Entinger + * Contributors: https://github.com/107-systems/107-Arduino-NMEA-Parser/graphs/contributors. + */ + +/************************************************************************************** + * INCLUDE + **************************************************************************************/ + +#include + +#include + +/************************************************************************************** + * TEST CODE + **************************************************************************************/ + +TEST_CASE ("Testing 'rmc_isGPGGA(...)' with valid and invalid GPGGA NMEA messages", "[rmc_isGPGGA-01]") +{ + WHEN ("a valid GPGGA NMEA message") + REQUIRE (nmea::util::rmc_isGPGGA("$GPGGA") == true); + WHEN ("a invalid GPGGA NMEA message") + REQUIRE (nmea::util::rmc_isGPGGA("$GAGGA") == false); +} + +TEST_CASE ("Testing 'rmc_isGLGGA(...)' with valid and invalid GLGGA NMEA messages", "[rmc_isGLGGA-01]") +{ + WHEN ("a valid GLGGA NMEA message") + REQUIRE (nmea::util::rmc_isGLGGA("$GLGGA") == true); + WHEN ("a invalid GLGGA NMEA message") + REQUIRE (nmea::util::rmc_isGLGGA("$GAGGA") == false); +} + +TEST_CASE ("Testing 'rmc_isGAGGA(...)' with valid and invalid GAGGA NMEA messages", "[rmc_isGAGGA-01]") +{ + WHEN ("a valid GAGGA NMEA message") + REQUIRE (nmea::util::rmc_isGAGGA("$GAGGA") == true); + WHEN ("a invalid GAGGA NMEA message") + REQUIRE (nmea::util::rmc_isGAGGA("$GLGGA") == false); +} + +TEST_CASE ("Testing 'rmc_isGNGGA(...)' with valid and invalid GNGGA NMEA messages", "[rmc_isGNGGA-01]") +{ + WHEN ("a valid GNGGA NMEA message") + REQUIRE (nmea::util::rmc_isGNGGA("$GNGGA") == true); + WHEN ("a invalid GNGGA NMEA message") + REQUIRE (nmea::util::rmc_isGNGGA("$GAGGA") == false); +} + +TEST_CASE ("Testing 'rmc_isGxGGA(...)' with valid and invalid G(P|L|A|N)GGA NMEA messages", "[rmc_isGxGGA-01]") +{ + WHEN ("a valid G(P|L|A|N)GGA NMEA message") + { + REQUIRE (nmea::util::rmc_isGxGGA("$GPGGA") == true); + REQUIRE (nmea::util::rmc_isGxGGA("$GLGGA") == true); + REQUIRE (nmea::util::rmc_isGxGGA("$GAGGA") == true); + REQUIRE (nmea::util::rmc_isGxGGA("$GNGGA") == true); + } + WHEN ("a invalid G(P|L|A|N)GGA NMEA message") + REQUIRE (nmea::util::rmc_isGxGGA("$GIGGA") == false); +} diff --git a/keywords.txt b/keywords.txt index 0c07520..3eb301c 100644 --- a/keywords.txt +++ b/keywords.txt @@ -12,8 +12,11 @@ ArduinoNmeaParser KEYWORD1 Time KEYWORD1 Date KEYWORD1 RmcData KEYWORD1 +GgaData KEYWORD1 # enum class RmcSource KEYWORD1 +GgaSource KEYWORD1 +FixQuality KEYWORD1 Error KEYWORD1 # namespace nmea KEYWORD1 @@ -36,9 +39,13 @@ toPosixTimestamp KEYWORD2 # enum class Error None LITERAL1 Checksum LITERAL1 -# enum class RmcSource +# enum class RmcSource/GgaSource Unknown LITERAL1 GPS LITERAL1 Galileo LITERAL1 GLONASS LITERAL1 GNSS LITERAL1 +# enum class FixQuality +Invalid LITERAL1 +GPS_Fix LITERAL1 +DGPS_Fix LITERAL1 diff --git a/src/ArduinoNmeaParser.cpp b/src/ArduinoNmeaParser.cpp index bf87b46..3bc8436 100644 --- a/src/ArduinoNmeaParser.cpp +++ b/src/ArduinoNmeaParser.cpp @@ -14,19 +14,24 @@ #include #include "nmea/GxRMC.h" +#include "nmea/GxGGA.h" #include "nmea/util/rmc.h" +#include "nmea/util/gga.h" #include "nmea/util/checksum.h" /************************************************************************************** * CTOR/DTOR **************************************************************************************/ -ArduinoNmeaParser::ArduinoNmeaParser(OnRmcUpdateFunc on_rmc_update) +ArduinoNmeaParser::ArduinoNmeaParser(OnRmcUpdateFunc on_rmc_update, + OnGgaUpdateFunc on_gga_update) : _error{Error::None} , _parser_state{ParserState::Synching} , _parser_buf{{0}, 0} , _rmc{nmea::INVALID_RMC} +, _gga{nmea::INVALID_GGA} , _on_rmc_update{on_rmc_update} +, _on_gga_update{on_gga_update} { } @@ -72,7 +77,8 @@ void ArduinoNmeaParser::encode(char const c) } /* Parse the various NMEA messages. */ - if (nmea::util::rmc_isGxRMC(_parser_buf.buf)) parseGxRMC(); + if (nmea::util::rmc_isGxRMC(_parser_buf.buf)) parseGxRMC(); + else if (nmea::util::rmc_isGxGGA(_parser_buf.buf)) parseGxGGA(); /* The NMEA message has been fully processed and all * values updates so its time to flush the parser @@ -122,5 +128,13 @@ void ArduinoNmeaParser::parseGxRMC() nmea::GxRMC::parse(_parser_buf.buf, _rmc); if (_on_rmc_update) - _on_rmc_update(_rmc); + _on_rmc_update(_rmc); +} + +void ArduinoNmeaParser::parseGxGGA() +{ + nmea::GxGGA::parse(_parser_buf.buf, _gga); + + if (_on_gga_update) + _on_gga_update(_gga); } diff --git a/src/ArduinoNmeaParser.h b/src/ArduinoNmeaParser.h index d52575d..aa920b5 100644 --- a/src/ArduinoNmeaParser.h +++ b/src/ArduinoNmeaParser.h @@ -26,6 +26,7 @@ **************************************************************************************/ typedef std::function OnRmcUpdateFunc; +typedef std::function OnGgaUpdateFunc; /************************************************************************************** * CLASS DECLARATION @@ -36,13 +37,15 @@ class ArduinoNmeaParser public: - ArduinoNmeaParser(OnRmcUpdateFunc on_rmc_update); + ArduinoNmeaParser(OnRmcUpdateFunc on_rmc_update, + OnGgaUpdateFunc on_gga_update); void encode(char const c); inline const nmea::RmcData rmc() const { return _rmc; } + inline const nmea::GgaData gga() const { return _gga; } enum class Error { None, Checksum }; @@ -70,7 +73,9 @@ class ArduinoNmeaParser ParserState _parser_state; ParserBuffer _parser_buf; nmea::RmcData _rmc; + nmea::GgaData _gga; OnRmcUpdateFunc _on_rmc_update; + OnGgaUpdateFunc _on_gga_update; bool isParseBufferFull(); void addToParserBuffer(char const c); @@ -78,7 +83,7 @@ class ArduinoNmeaParser bool isCompleteNmeaMessageInParserBuffer(); void terminateParserBuffer(); void parseGxRMC(); - + void parseGxGGA(); }; #endif /* ARDUINO_MTK3333_NMEA_PARSER_H_ */ diff --git a/src/nmea/GxGGA.cpp b/src/nmea/GxGGA.cpp new file mode 100644 index 0000000..1bd2491 --- /dev/null +++ b/src/nmea/GxGGA.cpp @@ -0,0 +1,233 @@ +/** + * This software is distributed under the terms of the MIT License. + * Copyright (c) 2020 LXRobotics. + * Author: Alexander Entinger + * Contributors: https://github.com/107-systems/107-Arduino-NMEA-Parser/graphs/contributors. + */ + +/************************************************************************************** + * INCLUDE + **************************************************************************************/ + +#include "GxGGA.h" + +#include + +#include "util/gga.h" +#include "util/common.h" +#include "util/checksum.h" + +/************************************************************************************** + * NAMESPACE + **************************************************************************************/ + +namespace nmea +{ + +/************************************************************************************** + * PUBLIC MEMBER FUNCTIONS + **************************************************************************************/ + +void GxGGA::parse(char const * gxgga, GgaData & data) +{ + ParserState state = ParserState::MessadeId; + + /* Replace the '*' sign denoting the start of the checksum + * with a ',' in order to be able to tokenize all elements + * including the one before the checksum. + */ + *strchr((char *)gxgga, '*') = ','; + + for (char * token = strsep((char **)&gxgga, ","); + token != nullptr; + token = strsep((char **)&gxgga, ",")) + { + ParserState next_state = state; + + switch(state) + { + case ParserState::MessadeId: next_state = handle_MessadeId (token, data.source); break; + case ParserState::UTCPositionFix: next_state = handle_UTCPositionFix (token, data.time_utc); break; + case ParserState::LatitudeVal: next_state = handle_LatitudeVal (token, data.latitude); break; + case ParserState::LatitudeNS: next_state = handle_LatitudeNS (token, data.latitude); break; + case ParserState::LongitudeVal: next_state = handle_LongitudeVal (token, data.longitude); break; + case ParserState::LongitudeEW: next_state = handle_LongitudeEW (token, data.longitude); break; + case ParserState::FixQuality: next_state = handle_FixQuality (token, data.fix_quality); break; + case ParserState::NumberSatellites: next_state = handle_NumberSatellites (token, data.num_satellites); break; + case ParserState::HorizontalDilutionOfPrecision: next_state = handle_HorizontalDilutionOfPrecision(token, data.hdop); break; + case ParserState::Altitude: next_state = handle_Altitude (token, data.altitude); break; + case ParserState::AltitudeUnit: next_state = handle_AltitudeUnit (token, data.altitude); break; + case ParserState::GeoidalSeparation: next_state = handle_GeoidalSeparation (token, data.geoidal_separation); break; + case ParserState::GeoidalSeparationUnit: next_state = handle_GeoidalSeparationUnit (token, data.geoidal_separation); break; + case ParserState::DGPSAge: next_state = handle_DGPSAge (token, data.dgps_age); break; + case ParserState::DGPSId: next_state = handle_DGPSId (token, data.dgps_id); break; + case ParserState::Checksum: next_state = handle_Checksum (token); break; + case ParserState::Done: break; + }; + + state = next_state; + } +} + +/************************************************************************************** + * PRIVATE MEMBER FUNCTIONS + **************************************************************************************/ + +GxGGA::ParserState GxGGA::handle_MessadeId(char const * token, GgaSource & source) +{ + if (util::rmc_isGPGGA(token)) + source = GgaSource::GPS; + else if (util::rmc_isGLGGA(token)) + source = GgaSource::GLONASS; + else if (util::rmc_isGAGGA(token)) + source = GgaSource::Galileo; + else if (util::rmc_isGNGGA(token)) + source = GgaSource::GNSS; + + return ParserState::UTCPositionFix; +} + +GxGGA::ParserState GxGGA::handle_UTCPositionFix(char const * token, Time & time_utc) +{ + if (strlen(token)) + util::parseTime(token, time_utc); + else + time_utc = INVALID_TIME; + + return ParserState::LatitudeVal; +} + +GxGGA::ParserState GxGGA::handle_LatitudeVal(char const * token, float & latitude) +{ + if (strlen(token)) + latitude = util::parseLatitude(token); + else + latitude = NAN; + + return ParserState::LatitudeNS; +} + +GxGGA::ParserState GxGGA::handle_LatitudeNS(char const * token, float & latitude) +{ + if (strlen(token) > 0 && !strncmp(token, "S", 1)) + latitude *= (-1.0f); + + return ParserState::LongitudeVal; +} + +GxGGA::ParserState GxGGA::handle_LongitudeVal(char const * token, float & longitude) +{ + if (strlen(token)) + longitude = util::parseLongitude(token); + else + longitude = NAN; + + return ParserState::LongitudeEW; +} + +GxGGA::ParserState GxGGA::handle_LongitudeEW(char const * token, float & longitude) +{ + if (strlen(token) > 0 && !strncmp(token, "W", 1)) + longitude *= (-1.0f); + + return ParserState::FixQuality; +} + +GxGGA::ParserState GxGGA::handle_FixQuality(char const * token, FixQuality & fix_quality) +{ + if (strlen(token) > 0 && !strncmp(token, "1", 1)) + fix_quality = FixQuality::GPS_Fix; + else if (strlen(token) > 0 && !strncmp(token, "2", 1)) + fix_quality = FixQuality::DGPS_Fix; + else + fix_quality = FixQuality::Invalid; + + return ParserState::NumberSatellites; +} + +GxGGA::ParserState GxGGA::handle_NumberSatellites(char const * token, int & num_satellites) +{ + if (strlen(token)) + num_satellites = atoi(token); + else + num_satellites = -1; + + return ParserState::HorizontalDilutionOfPrecision; +} + +GxGGA::ParserState GxGGA::handle_HorizontalDilutionOfPrecision(char const * token, float & hdop) +{ + if (strlen(token)) + hdop = atof(token); + else + hdop = NAN; + + return ParserState::Altitude; +} + +GxGGA::ParserState GxGGA::handle_Altitude(char const * token, float & altitude) +{ + if (strlen(token)) + altitude = atof(token); + else + altitude = NAN; + + return ParserState::AltitudeUnit; +} + +GxGGA::ParserState GxGGA::handle_AltitudeUnit(char const * token, float & altitude) +{ + if (!strlen(token)) + altitude = NAN; + + return ParserState::GeoidalSeparation; +} + +GxGGA::ParserState GxGGA::handle_GeoidalSeparation(char const * token, float & geoidal_separation) +{ + if (strlen(token)) + geoidal_separation = atof(token); + else + geoidal_separation = NAN; + + return ParserState::GeoidalSeparationUnit; +} + +GxGGA::ParserState GxGGA::handle_GeoidalSeparationUnit(char const * token, float & geoidal_separation) +{ + if (!strlen(token)) + geoidal_separation = NAN; + + return ParserState::DGPSAge; +} + +GxGGA::ParserState GxGGA::handle_DGPSAge(char const * token, int & dgps_age) +{ + if (strlen(token)) + dgps_age = atoi(token); + else + dgps_age = -1; + + return ParserState::DGPSId; +} + +GxGGA::ParserState GxGGA::handle_DGPSId(char const * token, char * dgps_id) +{ + if (strlen(token)) + memcpy(dgps_id, token, sizeof(GgaData::dgps_id)); + else + memset(dgps_id, 0, sizeof(GgaData::dgps_id)); + + return ParserState::Checksum; +} + +GxGGA::ParserState GxGGA::handle_Checksum(char const * /* token */) +{ + return ParserState::Done; +} + +/************************************************************************************** + * NAMESPACE + **************************************************************************************/ + +} /* nmea */ diff --git a/src/nmea/GxGGA.h b/src/nmea/GxGGA.h new file mode 100644 index 0000000..98d4e08 --- /dev/null +++ b/src/nmea/GxGGA.h @@ -0,0 +1,87 @@ +/** + * This software is distributed under the terms of the MIT License. + * Copyright (c) 2020 LXRobotics. + * Author: Alexander Entinger + * Contributors: https://github.com/107-systems/107-Arduino-NMEA-Parser/graphs/contributors. + */ + +#ifndef ARDUINO_NMEA_GXGGA_H_ +#define ARDUINO_NMEA_GXGGA_H_ + +/************************************************************************************** + * INCLUDE + **************************************************************************************/ + +#include + +#include "Types.h" + +/************************************************************************************** + * NAMESPACE + **************************************************************************************/ + +namespace nmea +{ + +/************************************************************************************** + * CLASS DECLARATION + **************************************************************************************/ + +class GxGGA +{ + +public: + + static void parse(char const * gxgga, GgaData & data); + +private: + + GxGGA() { } + GxGGA(GxGGA const &) { } + + enum class ParserState : int + { + MessadeId, + UTCPositionFix, + LatitudeVal, + LatitudeNS, + LongitudeVal, + LongitudeEW, + FixQuality, + NumberSatellites, + HorizontalDilutionOfPrecision, + Altitude, + AltitudeUnit, + GeoidalSeparation, + GeoidalSeparationUnit, + DGPSAge, + DGPSId, + Checksum, + Done + }; + + static ParserState handle_MessadeId (char const * token, GgaSource & source); + static ParserState handle_UTCPositionFix (char const * token, Time & time_utc); + static ParserState handle_LatitudeVal (char const * token, float & latitude); + static ParserState handle_LatitudeNS (char const * token, float & latitude); + static ParserState handle_LongitudeVal (char const * token, float & longitude); + static ParserState handle_LongitudeEW (char const * token, float & longitude); + static ParserState handle_FixQuality (char const * token, FixQuality & fix_quality); + static ParserState handle_NumberSatellites (char const * token, int & num_satellites); + static ParserState handle_HorizontalDilutionOfPrecision(char const * token, float & hdop); + static ParserState handle_Altitude (char const * token, float & altitude); + static ParserState handle_AltitudeUnit (char const * token, float & altitude); + static ParserState handle_GeoidalSeparation (char const * token, float & geoidal_separation); + static ParserState handle_GeoidalSeparationUnit (char const * token, float & geoidal_separation); + static ParserState handle_DGPSAge (char const * token, int & dgps_age); + static ParserState handle_DGPSId (char const * token, char * dgps_id); + static ParserState handle_Checksum (char const * token); +}; + +/************************************************************************************** + * NAMESPACE + **************************************************************************************/ + +} /* nmea */ + +#endif /* ARDUINO_NMEA_GXGGA_H_ */ diff --git a/src/nmea/GxRMC.cpp b/src/nmea/GxRMC.cpp index ee720fb..ab4fc8d 100644 --- a/src/nmea/GxRMC.cpp +++ b/src/nmea/GxRMC.cpp @@ -15,6 +15,7 @@ #include #include "util/rmc.h" +#include "util/common.h" #include "util/checksum.h" /************************************************************************************** @@ -96,14 +97,9 @@ GxRMC::ParserState GxRMC::handle_MessadeId(char const * token, RmcSource & sourc GxRMC::ParserState GxRMC::handle_UTCPositionFix(char const * token, Time & time_utc) { if (strlen(token)) - util::rmc_parseTime(token, time_utc); + util::parseTime(token, time_utc); else - { - time_utc.hour = -1; - time_utc.minute = -1; - time_utc.second = -1; - time_utc.microsecond = -1; - } + time_utc = INVALID_TIME; return ParserState::Status; } @@ -121,7 +117,7 @@ GxRMC::ParserState GxRMC::handle_Status(char const * token, bool & is_valid) GxRMC::ParserState GxRMC::handle_LatitudeVal(char const * token, float & latitude) { if (strlen(token)) - latitude = util::rmc_parseLatitude(token); + latitude = util::parseLatitude(token); else latitude = NAN; @@ -139,7 +135,7 @@ GxRMC::ParserState GxRMC::handle_LatitudeNS(char const * token, float & latitude GxRMC::ParserState GxRMC::handle_LongitudeVal(char const * token, float & longitude) { if (strlen(token)) - longitude = util::rmc_parseLongitude(token); + longitude = util::parseLongitude(token); else longitude = NAN; @@ -179,11 +175,7 @@ GxRMC::ParserState GxRMC::handle_Date(char const * token, Date & date) if (strlen(token)) util::rmc_parseDate(token, date); else - { - date.day = -1; - date.month = -1; - date.year = -1; - } + date = INVALID_DATE; return ParserState::MagneticVariation; } diff --git a/src/nmea/Types.h b/src/nmea/Types.h index db01c1f..e700e33 100644 --- a/src/nmea/Types.h +++ b/src/nmea/Types.h @@ -48,24 +48,59 @@ enum class RmcSource typedef struct { - bool is_valid; RmcSource source; + Time time_utc; + bool is_valid; float latitude; float longitude; float speed; float course; float magnetic_variation; - Time time_utc; Date date; } RmcData; +enum class FixQuality +{ + Invalid, GPS_Fix, DGPS_Fix +}; + +typedef RmcSource GgaSource; + +typedef struct +{ + GgaSource source; + Time time_utc; + float latitude; + float longitude; + FixQuality fix_quality; + int num_satellites; + /* HDOP = Horizontal dilution of position */ + float hdop; + /* Antenna altitude above/below mean sea level (Geoid). */ + float altitude; + /* Geoidal separation N + * where + * h = N + H + * and + * h = height above ellipsoid + * H = elevation, orthometric height + * N = geoidal separation (some books call this the geoidal height) + */ + float geoidal_separation; + /* Age in seconds since last update from differential reference station. */ + int dgps_age; + /* DGPS station id - 4 bytes. */ + char dgps_id[4]; +} GgaData; + /************************************************************************************** * CONST **************************************************************************************/ Time const INVALID_TIME = {-1, -1, -1, -1}; Date const INVALID_DATE = {-1, -1, -1}; -RmcData const INVALID_RMC = {false, RmcSource::Unknown, NAN, NAN, NAN, NAN, NAN, INVALID_TIME, INVALID_DATE}; +RmcData const INVALID_RMC = {RmcSource::Unknown, INVALID_TIME, false, NAN, NAN, NAN, NAN, NAN, INVALID_DATE}; +GgaData const INVALID_GGA = {GgaSource::Unknown, INVALID_TIME, NAN, NAN, FixQuality::Invalid, -1, NAN, NAN, NAN, -1, {0}}; /************************************************************************************** * FUNCTION DECLARATION diff --git a/src/nmea/util/common.cpp b/src/nmea/util/common.cpp new file mode 100644 index 0000000..dd11fce --- /dev/null +++ b/src/nmea/util/common.cpp @@ -0,0 +1,74 @@ +/** + * This software is distributed under the terms of the MIT License. + * Copyright (c) 2020 LXRobotics. + * Author: Alexander Entinger + * Contributors: https://github.com/107-systems/107-Arduino-NMEA-Parser/graphs/contributors. + */ + +/************************************************************************************** + * INCLUDES + **************************************************************************************/ + +#include "common.h" + +#include +#include + +/************************************************************************************** + * NAMESPACE + **************************************************************************************/ + +namespace nmea +{ + +namespace util +{ + +/************************************************************************************** + * FUNCTION DEFINITION + **************************************************************************************/ + +void parseTime(char const * token, Time & time_utc) +{ + char const hour_str [] = {token[0], token[1], '\0'}; + char const minute_str [] = {token[2], token[3], '\0'}; + char const second_str [] = {token[4], token[5], '\0'}; + char const microsecond_str[] = {token[7], token[8], token[9],'\0'}; + + time_utc.hour = atoi(hour_str); + time_utc.minute = atoi(minute_str); + time_utc.second = atoi(second_str); + time_utc.microsecond = atoi(microsecond_str); +} + +float parseLatitude(char const * token) +{ + char const deg_str[] = {token[0], token[1], '\0'}; + char min_str[10] = {0}; + strncpy(min_str, token + 2, sizeof(min_str)); + + float latitude = atoi(deg_str); + latitude += atof(min_str) / 60.0f; + + return latitude; +} + +float parseLongitude(char const * token) +{ + char const deg_str[] = {token[0], token[1], token[2], '\0'}; + char min_str[10] = {0}; + strncpy(min_str, token + 3, sizeof(min_str)); + + float longitude = atoi(deg_str); + longitude += atof(min_str) / 60.0f; + + return longitude; +} + +/************************************************************************************** + * NAMESPACE + **************************************************************************************/ + +} /* util */ + +} /* nmea */ diff --git a/src/nmea/util/common.h b/src/nmea/util/common.h new file mode 100644 index 0000000..b7ac1dc --- /dev/null +++ b/src/nmea/util/common.h @@ -0,0 +1,43 @@ +/** + * This software is distributed under the terms of the MIT License. + * Copyright (c) 2020 LXRobotics. + * Author: Alexander Entinger + * Contributors: https://github.com/107-systems/107-Arduino-NMEA-Parser/graphs/contributors. + */ + +#ifndef ARDUINO_NMEA_UTIL_COMMON_H_ +#define ARDUINO_NMEA_UTIL_COMMON_H_ + +/************************************************************************************** + * INCLUDES + **************************************************************************************/ + +#include "../Types.h" + +/************************************************************************************** + * NAMESPACE + **************************************************************************************/ + +namespace nmea +{ + +namespace util +{ + +/************************************************************************************** + * FUNCTION DECLARATION + **************************************************************************************/ + +void parseTime (char const * token, Time & time_utc); +float parseLatitude (char const * token); +float parseLongitude(char const * token); + +/************************************************************************************** + * NAMESPACE + **************************************************************************************/ + +} /* util */ + +} /* nmea */ + +#endif /* ARDUINO_NMEA_UTIL_COMMON_H_ */ diff --git a/src/nmea/util/gga.cpp b/src/nmea/util/gga.cpp new file mode 100644 index 0000000..d2f85ba --- /dev/null +++ b/src/nmea/util/gga.cpp @@ -0,0 +1,62 @@ +/** + * This software is distributed under the terms of the MIT License. + * Copyright (c) 2020 LXRobotics. + * Author: Alexander Entinger + * Contributors: https://github.com/107-systems/107-Arduino-NMEA-Parser/graphs/contributors. + */ + +/************************************************************************************** + * INCLUDES + **************************************************************************************/ + +#include "gga.h" + +#include +#include + +/************************************************************************************** + * NAMESPACE + **************************************************************************************/ + +namespace nmea +{ + +namespace util +{ + +/************************************************************************************** + * FUNCTION DEFINITION + **************************************************************************************/ + +bool rmc_isGPGGA(char const * nmea) +{ + return (strncmp(nmea, "$GPGGA", 6) == 0); +} + +bool rmc_isGLGGA(char const * nmea) +{ + return (strncmp(nmea, "$GLGGA", 6) == 0); +} + +bool rmc_isGAGGA(char const * nmea) +{ + return (strncmp(nmea, "$GAGGA", 6) == 0); +} + +bool rmc_isGNGGA(char const * nmea) +{ + return (strncmp(nmea, "$GNGGA", 6) == 0); +} + +bool rmc_isGxGGA(char const * nmea) +{ + return (rmc_isGPGGA(nmea) || rmc_isGLGGA(nmea) || rmc_isGAGGA(nmea) || rmc_isGNGGA(nmea)); +} + +/************************************************************************************** + * NAMESPACE + **************************************************************************************/ + +} /* util */ + +} /* nmea */ diff --git a/src/nmea/util/gga.h b/src/nmea/util/gga.h new file mode 100644 index 0000000..903078e --- /dev/null +++ b/src/nmea/util/gga.h @@ -0,0 +1,45 @@ +/** + * This software is distributed under the terms of the MIT License. + * Copyright (c) 2020 LXRobotics. + * Author: Alexander Entinger + * Contributors: https://github.com/107-systems/107-Arduino-NMEA-Parser/graphs/contributors. + */ + +#ifndef ARDUINO_NMEA_UTIL_GGA_H_ +#define ARDUINO_NMEA_UTIL_GGA_H_ + +/************************************************************************************** + * INCLUDES + **************************************************************************************/ + +#include "../Types.h" + +/************************************************************************************** + * NAMESPACE + **************************************************************************************/ + +namespace nmea +{ + +namespace util +{ + +/************************************************************************************** + * FUNCTION DECLARATION + **************************************************************************************/ + +bool rmc_isGPGGA(char const * nmea); +bool rmc_isGLGGA(char const * nmea); +bool rmc_isGAGGA(char const * nmea); +bool rmc_isGNGGA(char const * nmea); +bool rmc_isGxGGA(char const * nmea); + +/************************************************************************************** + * NAMESPACE + **************************************************************************************/ + +} /* util */ + +} /* nmea */ + +#endif /* ARDUINO_NMEA_UTIL_GGA_H_ */ diff --git a/src/nmea/util/rmc.cpp b/src/nmea/util/rmc.cpp index 74ac0b9..9f9fe03 100644 --- a/src/nmea/util/rmc.cpp +++ b/src/nmea/util/rmc.cpp @@ -53,43 +53,6 @@ bool rmc_isGxRMC(char const * nmea) return (rmc_isGPRMC(nmea) || rmc_isGLRMC(nmea) || rmc_isGARMC(nmea) || rmc_isGNRMC(nmea)); } -void rmc_parseTime(char const * token, Time & time_utc) -{ - char const hour_str [] = {token[0], token[1], '\0'}; - char const minute_str [] = {token[2], token[3], '\0'}; - char const second_str [] = {token[4], token[5], '\0'}; - char const microsecond_str[] = {token[7], token[8], token[9],'\0'}; - - time_utc.hour = atoi(hour_str); - time_utc.minute = atoi(minute_str); - time_utc.second = atoi(second_str); - time_utc.microsecond = atoi(microsecond_str); -} - -float rmc_parseLatitude(char const * token) -{ - char const deg_str[] = {token[0], token[1], '\0'}; - char min_str[10] = {0}; - strncpy(min_str, token + 2, sizeof(min_str)); - - float latitude = atoi(deg_str); - latitude += atof(min_str) / 60.0f; - - return latitude; -} - -float rmc_parseLongitude(char const * token) -{ - char const deg_str[] = {token[0], token[1], token[2], '\0'}; - char min_str[10] = {0}; - strncpy(min_str, token + 3, sizeof(min_str)); - - float longitude = atoi(deg_str); - longitude += atof(min_str) / 60.0f; - - return longitude; -} - void rmc_parseDate(char const * token, Date & date) { char const day_str [] = {token[0], token[1], '\0'}; diff --git a/src/nmea/util/rmc.h b/src/nmea/util/rmc.h index ea3c535..86a65e7 100644 --- a/src/nmea/util/rmc.h +++ b/src/nmea/util/rmc.h @@ -33,9 +33,6 @@ bool rmc_isGLRMC (char const * nmea); bool rmc_isGARMC (char const * nmea); bool rmc_isGNRMC (char const * nmea); bool rmc_isGxRMC (char const * nmea); -void rmc_parseTime (char const * token, Time & time_utc); -float rmc_parseLatitude (char const * token); -float rmc_parseLongitude(char const * token); void rmc_parseDate (char const * token, Date & date); /**************************************************************************************