diff --git a/CHANGELOG.md b/CHANGELOG.md index dccf4f75..011bb5c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [3.4.3] +- Added option to export NMEA sentences to a unix socket or a TCP connection. See `--nmea-export-*` command-line arguments. This requires the use of the NMEA receiver. +- Fixed a bug where in cases of TCP connection failure, the addrinfo struct was not freed. + ## [3.4.2] - Fixed a bug where GAD messages had TF009 set to 0. It will now be set to time specified in the STEC/Gridded IE. - Fixed a bug where parsing bitfields in UBX-NAV-PVT would not be incorrect. diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b411bb6..0ec81659 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,7 +28,7 @@ find_package(OpenSSL REQUIRED) endif (USE_OPENSSL) add_definitions(-D_POSIX_C_SOURCE=200809L) -add_definitions(-DCLIENT_VERSION="3.4.2") +add_definitions(-DCLIENT_VERSION="3.4.3") if(${ASN_DEBUG}) add_definitions(-DASN_EMIT_DEBUG=1) diff --git a/README.md b/README.md index 18854034..07d68c5e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SUPL 3GPP LPP client -![version](https://img.shields.io/badge/version-3.4.2-green) +![version](https://img.shields.io/badge/version-3.4.3-green) ![license](https://img.shields.io/badge/license-MXM-blue) This project is a set of libraries, examples and tools to facilitate the development of 3GPP LPP clients. diff --git a/examples/lpp/README.md b/examples/lpp/README.md index 384183e7..c8732456 100644 --- a/examples/lpp/README.md +++ b/examples/lpp/README.md @@ -14,7 +14,7 @@ There are a few required arguments: ``` ./example-lpp COMMAND {OPTIONS} - 3GPP LPP Example (3.4.0) - This sample code is a simple client that asks for + 3GPP LPP Example (3.4.3) - This sample code is a simple client that asks for assistance data from a location server. It can handle OSR, SSR, and AGNSS requests. The assistance data can converted to RTCM or SPARTN before being sent to a GNSS receiver or other interface. The client also supports to 3GPP @@ -96,6 +96,11 @@ There are a few required arguments: Parity Bits One of: none, odd, even Default: none + --nmea-export-un=[unix socket] Export NMEA to unix socket + --nmea-export-tcp=[ip] Export NMEA to TCP + --nmea-export-tcp-port=[port] Export NMEA to TCP Port + Other Receiver Options: + --print-receiver-messages, --prm Print Receiver Messages Output: File: --file=[file_path] Path diff --git a/examples/lpp/options.cpp b/examples/lpp/options.cpp index d81da4b1..cf395c5f 100644 --- a/examples/lpp/options.cpp +++ b/examples/lpp/options.cpp @@ -190,6 +190,22 @@ args::ValueFlag nmea_serial_parity_bits{nmea_receiver_group, "Parity Bits", {"nmea-serial-parity"}, args::Options::Single}; +// export nmea to unix socket +args::ValueFlag nmea_export_un{nmea_receiver_group, + "unix socket", + "Export NMEA to unix socket", + {"nmea-export-un"}, + args::Options::Single}; +args::ValueFlag nmea_export_tcp{nmea_receiver_group, + "ip", + "Export NMEA to TCP", + {"nmea-export-tcp"}, + args::Options::Single}; +args::ValueFlag nmea_export_tcp_port{nmea_receiver_group, + "port", + "Export NMEA to TCP Port", + {"nmea-export-tcp-port"}, + args::Options::Single}; // // Output @@ -691,10 +707,27 @@ static NmeaOptions nmea_parse_options() { } } + std::vector> nmea_export_interfaces; + if (nmea_export_un) { + auto interface = interface::Interface::unix_socket_stream(nmea_export_un.Get(), true); + nmea_export_interfaces.emplace_back(interface); + } + + if (nmea_export_tcp) { + if (!nmea_export_tcp_port) { + throw args::RequiredError("nmea-export-tcp-port"); + } + + auto interface = + interface::Interface::tcp(nmea_export_tcp.Get(), nmea_export_tcp_port.Get(), true); + nmea_export_interfaces.emplace_back(interface); + } + auto interface = interface::Interface::serial(nmea_serial_device.Get(), baud_rate, data_bits, stop_bits, parity_bit); auto print_messages = print_receiver_options_parse(); - return NmeaOptions{std::unique_ptr(interface), print_messages}; + return NmeaOptions{std::unique_ptr(interface), print_messages, + std::move(nmea_export_interfaces)}; } else { return NmeaOptions{}; } diff --git a/examples/lpp/options.hpp b/examples/lpp/options.hpp index 52ccce5e..4e2d0a61 100644 --- a/examples/lpp/options.hpp +++ b/examples/lpp/options.hpp @@ -73,6 +73,8 @@ struct NmeaOptions { std::unique_ptr interface; /// Whether to print messages. bool print_messages; + /// Export messages to other interfaces. + std::vector> export_interfaces; }; /// Location information options. diff --git a/examples/lpp/osr_example.cpp b/examples/lpp/osr_example.cpp index f2a0dfc9..74541f20 100644 --- a/examples/lpp/osr_example.cpp +++ b/examples/lpp/osr_example.cpp @@ -131,8 +131,17 @@ void execute(Options options, osr_example::Format format, osr_example::MsmType m nmea_options.interface->open(); nmea_options.interface->print_info(); + if (!nmea_options.export_interfaces.empty()) { + printf("[nmea-export]\n"); + for (auto& interface : nmea_options.export_interfaces) { + interface->open(); + interface->print_info(); + } + } + gNmeaReceiver = std::unique_ptr( - new NReceiver(std::move(nmea_options.interface), nmea_options.print_messages)); + new NReceiver(std::move(nmea_options.interface), nmea_options.print_messages, + std::move(nmea_options.export_interfaces))); gNmeaReceiver->start(); } diff --git a/examples/lpp/ssr_example.cpp b/examples/lpp/ssr_example.cpp index 999e2f77..bb116370 100644 --- a/examples/lpp/ssr_example.cpp +++ b/examples/lpp/ssr_example.cpp @@ -102,8 +102,17 @@ void execute(Options options, ssr_example::Format format, int ura_override, nmea_options.interface->open(); nmea_options.interface->print_info(); + if (!nmea_options.export_interfaces.empty()) { + printf("[nmea-export]\n"); + for (auto& interface : nmea_options.export_interfaces) { + interface->open(); + interface->print_info(); + } + } + gNmeaReceiver = std::unique_ptr( - new NReceiver(std::move(nmea_options.interface), nmea_options.print_messages)); + new NReceiver(std::move(nmea_options.interface), nmea_options.print_messages, + std::move(nmea_options.export_interfaces))); gNmeaReceiver->start(); } diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 44eb02a0..c78b0663 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -10,6 +10,7 @@ add_library(dependency_interface STATIC "reconnectable_socket.cpp" "tcp.cpp" "udp.cpp" + "unix_socket.cpp" ) add_library(dependency::interface ALIAS dependency_interface) diff --git a/interface/file.cpp b/interface/file.cpp index 31b66eb9..30341f3e 100644 --- a/interface/file.cpp +++ b/interface/file.cpp @@ -27,7 +27,7 @@ void FileInterface::open() { } if (fd < 0) { - throw std::runtime_error("Failed to open file"); + throw std::runtime_error("Failed to open file: " + mFilePath); } mFileDescriptor = FileDescriptor(fd); diff --git a/interface/include/interface/interface.hpp b/interface/include/interface/interface.hpp index b5339011..66c1a91a 100644 --- a/interface/include/interface/interface.hpp +++ b/interface/include/interface/interface.hpp @@ -110,6 +110,11 @@ class Interface { /// Create a stdin interface. static Interface* stdin(); + + /// Create a unix socket interface. The socket type is SOCK_STREAM. + /// @param socket_path The path to the unix socket. + /// @param reconnect Whether to reconnect if the connection is lost. + static Interface* unix_socket_stream(std::string socket_path, bool reconnect); }; } // namespace interface diff --git a/interface/reconnectable_socket.cpp b/interface/reconnectable_socket.cpp index 418c348a..cbec3b2e 100644 --- a/interface/reconnectable_socket.cpp +++ b/interface/reconnectable_socket.cpp @@ -96,6 +96,7 @@ void ReconnectableSocket::print_info() IF_NOEXCEPT { switch (mAddress.family()) { case AF_INET: printf(" family: AF_INET\n"); break; case AF_INET6: printf(" family: AF_INET6\n"); break; + case AF_UNIX: printf(" family: AF_UNIX\n"); break; default: printf(" family: AF_??? (%d)\n", mAddress.family()); break; } switch (mAddress.type()) { @@ -106,6 +107,7 @@ void ReconnectableSocket::print_info() IF_NOEXCEPT { switch (mAddress.protocol()) { case IPPROTO_TCP: printf(" protocol: IPPROTO_TCP\n"); break; case IPPROTO_UDP: printf(" protocol: IPPROTO_UDP\n"); break; + case IPPROTO_IP: printf(" protocol: IPPROTO_IP\n"); break; default: printf(" protocol: IPPROTO_??? (%d)\n", mAddress.protocol()); break; } printf(" address: %s\n", mAddress.to_string().c_str()); diff --git a/interface/socket.hpp b/interface/socket.hpp index 2bee817e..f375e071 100644 --- a/interface/socket.hpp +++ b/interface/socket.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "types.hpp" namespace interface { @@ -18,6 +19,7 @@ class NetworkAddress { switch (addr->ai_family) { case AF_INET: break; case AF_INET6: break; + case AF_UNIX: break; default: throw std::runtime_error("Unsupported network address"); } NetworkAddress rtrn{}; @@ -31,12 +33,25 @@ class NetworkAddress { return rtrn; } + static NetworkAddress unix_socket_stream(const std::string& path) { + if (path.size() >= sizeof(sockaddr_un::sun_path)) throw std::runtime_error("Path too long"); + + NetworkAddress rtrn{}; + rtrn.mFamily = AF_UNIX; + rtrn.mType = SOCK_STREAM; + rtrn.mProtocol = 0; + rtrn.mAddr.un.sun_family = AF_UNIX; + strncpy(rtrn.mAddr.un.sun_path, path.c_str(), sizeof(rtrn.mAddr.un.sun_path)); + return rtrn; + } + IF_NODISCARD struct sockaddr* ptr() IF_NOEXCEPT { return &mAddr.base; } IF_NODISCARD std::size_t length() const IF_NOEXCEPT { switch (mFamily) { case AF_INET: return sizeof(mAddr.in4); case AF_INET6: return sizeof(mAddr.in6); + case AF_UNIX: return sizeof(mAddr.un); default: return 0; /* fail safe in later call */ } } @@ -54,6 +69,7 @@ class NetworkAddress { case AF_INET6: inet_ntop(mFamily, &mAddr.in6.sin6_addr, buffer, INET6_ADDRSTRLEN); return std::string(buffer) + ":" + std::to_string(ntohs(mAddr.in6.sin6_port)); + case AF_UNIX: return std::string(mAddr.un.sun_path); default: return "Unsupported network address"; } } @@ -67,6 +83,7 @@ class NetworkAddress { sockaddr base; sockaddr_in in4; sockaddr_in6 in6; + sockaddr_un un; } mAddr; }; diff --git a/interface/tcp.cpp b/interface/tcp.cpp index a67837a6..b7955e4d 100644 --- a/interface/tcp.cpp +++ b/interface/tcp.cpp @@ -39,10 +39,12 @@ void TcpInterface::open() { auto address = NetworkAddress::from_addrinfo(rp); mSocket = ReconnectableSocket::connect(address, mReconnect); if (mSocket.is_open()) { + freeaddrinfo(result); return; } } + freeaddrinfo(result); throw std::runtime_error("Failed to connect to host"); } diff --git a/interface/unix_socket.cpp b/interface/unix_socket.cpp new file mode 100644 index 00000000..aabc9496 --- /dev/null +++ b/interface/unix_socket.cpp @@ -0,0 +1,79 @@ +#include "unix_socket.hpp" +#include +#include +#include +#include +#include +#include + +namespace interface { + +UnixSocketInterface::UnixSocketInterface(std::string path, bool reconnect) IF_NOEXCEPT + : mPath(std::move(path)), + mReconnect(reconnect) {} + +UnixSocketInterface::~UnixSocketInterface() IF_NOEXCEPT { + close(); +} + +void UnixSocketInterface::open() { + if (mSocket.is_open()) { + return; + } + + auto address = NetworkAddress::unix_socket_stream(mPath); + mSocket = ReconnectableSocket::connect(address, mReconnect); + if (!mSocket.is_open()) { + throw std::runtime_error("Failed to connect to unix socket: '" + mPath + "'"); + } +} + +void UnixSocketInterface::close() { + mSocket.close(); +} + +size_t UnixSocketInterface::read(void* data, const size_t size) { + return mSocket.read(data, size); +} + +size_t UnixSocketInterface::write(const void* data, const size_t size) { + return mSocket.write(data, size); +} + +bool UnixSocketInterface::can_read() IF_NOEXCEPT { + return mSocket.can_read(); +} + +bool UnixSocketInterface::can_write() IF_NOEXCEPT { + return mSocket.can_write(); +} + +void UnixSocketInterface::wait_for_read() IF_NOEXCEPT { + mSocket.wait_for_read(); +} + +void UnixSocketInterface::wait_for_write() IF_NOEXCEPT { + mSocket.wait_for_write(); +} + +bool UnixSocketInterface::is_open() IF_NOEXCEPT { + return mSocket.is_open(); +} + +void UnixSocketInterface::print_info() IF_NOEXCEPT { + printf("[interface]\n"); + printf(" type: unix-socket (stream)\n"); + printf(" path: %s\n", mPath.c_str()); + printf(" reconnect: %s\n", mReconnect ? "true" : "false"); + mSocket.print_info(); +} + +// +// +// + +Interface* Interface::unix_socket_stream(std::string path, bool reconnect) { + return new UnixSocketInterface(std::move(path), reconnect); +} + +} // namespace interface diff --git a/interface/unix_socket.hpp b/interface/unix_socket.hpp new file mode 100644 index 00000000..4f014b83 --- /dev/null +++ b/interface/unix_socket.hpp @@ -0,0 +1,35 @@ +#pragma once +#include +#include +#include "interface.hpp" +#include "reconnectable_socket.hpp" + +namespace interface { + +class UnixSocketInterface final : public Interface { +public: + explicit UnixSocketInterface(std::string path, bool reconnect) IF_NOEXCEPT; + ~UnixSocketInterface() IF_NOEXCEPT override; + + void open() override; + void close() override; + + size_t read(void* data, size_t length) override; + size_t write(const void* data, size_t length) override; + + IF_NODISCARD bool can_read() IF_NOEXCEPT override; + IF_NODISCARD bool can_write() IF_NOEXCEPT override; + + void wait_for_read() IF_NOEXCEPT override; + void wait_for_write() IF_NOEXCEPT override; + + IF_NODISCARD bool is_open() IF_NOEXCEPT override; + void print_info() IF_NOEXCEPT override; + +private: + std::string mPath; + bool mReconnect; + ReconnectableSocket mSocket; +}; + +} // namespace interface diff --git a/receiver/nmea/gga.cpp b/receiver/nmea/gga.cpp index 9eef1d23..f5e04923 100644 --- a/receiver/nmea/gga.cpp +++ b/receiver/nmea/gga.cpp @@ -123,12 +123,13 @@ static bool parse_altitude(const std::string& altitude, const std::string& units } } -GgaMessage::GgaMessage(std::string prefix) NMEA_NOEXCEPT : Message{prefix}, - mTimeOfDay{TAI_Time::now()}, - mLatitude{0.0}, - mLongitude{0.0}, - mFixQuality{GgaFixQuality::Invalid}, - mSatellitesInView{0} {} +GgaMessage::GgaMessage(std::string prefix, std::string payload, std::string checksum) NMEA_NOEXCEPT + : Message{prefix, payload, checksum}, + mTimeOfDay{TAI_Time::now()}, + mLatitude{0.0}, + mLongitude{0.0}, + mFixQuality{GgaFixQuality::Invalid}, + mSatellitesInView{0} {} void GgaMessage::print() const NMEA_NOEXCEPT { printf("[%5s]\n", prefix().c_str()); @@ -151,7 +152,8 @@ void GgaMessage::print() const NMEA_NOEXCEPT { printf(" altitude: %.2f\n", altitude()); } -std::unique_ptr GgaMessage::parse(std::string prefix, const std::string& payload) { +std::unique_ptr GgaMessage::parse(std::string prefix, const std::string& payload, + std::string checksum) { // split payload by ',' auto tokens = split(payload, ','); @@ -161,7 +163,7 @@ std::unique_ptr GgaMessage::parse(std::string prefix, const std::string } // parse - auto message = new GgaMessage(prefix); + auto message = new GgaMessage(prefix, payload, checksum); auto success = true; success &= parse_utc(tokens[0], message->mTimeOfDay); success &= parse_latitude(tokens[1], tokens[2], message->mLatitude); diff --git a/receiver/nmea/gst.cpp b/receiver/nmea/gst.cpp index bd056317..f9b1ac25 100644 --- a/receiver/nmea/gst.cpp +++ b/receiver/nmea/gst.cpp @@ -26,14 +26,15 @@ static bool parse_double_opt(const std::string& token, double& value) { } } -GstMessage::GstMessage(std::string prefix) NMEA_NOEXCEPT : Message{prefix}, - mRmsValue{0.0}, - mSemiMajorError{0.0}, - mSemiMinorError{0.0}, - mOrientationOfSemiMajorError{0.0}, - mLatitudeError{0.0}, - mLongitudeError{0.0}, - mAltitudeError{0.0} {} +GstMessage::GstMessage(std::string prefix, std::string payload, std::string checksum) NMEA_NOEXCEPT + : Message{prefix, payload, checksum}, + mRmsValue{0.0}, + mSemiMajorError{0.0}, + mSemiMinorError{0.0}, + mOrientationOfSemiMajorError{0.0}, + mLatitudeError{0.0}, + mLongitudeError{0.0}, + mAltitudeError{0.0} {} void GstMessage::print() const NMEA_NOEXCEPT { printf("[%5s]\n", prefix().c_str()); printf(" rms value: %f\n", mRmsValue); @@ -45,7 +46,8 @@ void GstMessage::print() const NMEA_NOEXCEPT { printf(" altitude error: %f\n", mAltitudeError); } -std::unique_ptr GstMessage::parse(std::string prefix, const std::string& payload) { +std::unique_ptr GstMessage::parse(std::string prefix, const std::string& payload, + std::string checksum) { // split payload by ',' auto tokens = split(payload, ','); @@ -58,7 +60,7 @@ std::unique_ptr GstMessage::parse(std::string prefix, const std::string } // parse - auto message = new GstMessage(prefix); + auto message = new GstMessage(prefix, payload, checksum); auto success = true; success &= parse_double_opt(tokens[1], message->mRmsValue); success &= parse_double_opt(tokens[2], message->mSemiMajorError); diff --git a/receiver/nmea/include/receiver/nmea/gga.hpp b/receiver/nmea/include/receiver/nmea/gga.hpp index cca01af0..a5d41327 100644 --- a/receiver/nmea/include/receiver/nmea/gga.hpp +++ b/receiver/nmea/include/receiver/nmea/gga.hpp @@ -54,11 +54,12 @@ class GgaMessage final : public Message { /// Get the altitude in meters. NMEA_NODISCARD double altitude() const NMEA_NOEXCEPT { return mMsl + mGeoidSeparation; } - NMEA_NODISCARD static std::unique_ptr parse(std::string prefix, - const std::string& payload); + NMEA_NODISCARD static std::unique_ptr + parse(std::string prefix, const std::string& payload, std::string checksum); private: - NMEA_EXPLICIT GgaMessage(std::string prefix) NMEA_NOEXCEPT; + NMEA_EXPLICIT GgaMessage(std::string prefix, std::string payload, + std::string checksum) NMEA_NOEXCEPT; TAI_Time mTimeOfDay; double mLatitude; diff --git a/receiver/nmea/include/receiver/nmea/gst.hpp b/receiver/nmea/include/receiver/nmea/gst.hpp index 77ef3a8c..2ff24ad5 100644 --- a/receiver/nmea/include/receiver/nmea/gst.hpp +++ b/receiver/nmea/include/receiver/nmea/gst.hpp @@ -34,11 +34,12 @@ class GstMessage final : public Message { /// Get the vertical position error. NMEA_NODISCARD double vertical_position_error() const NMEA_NOEXCEPT { return mAltitudeError; } - NMEA_NODISCARD static std::unique_ptr parse(std::string prefix, - const std::string& payload); + NMEA_NODISCARD static std::unique_ptr + parse(std::string prefix, const std::string& payload, std::string checksum); private: - NMEA_EXPLICIT GstMessage(std::string prefix) NMEA_NOEXCEPT; + NMEA_EXPLICIT GstMessage(std::string prefix, std::string payload, + std::string checksum) NMEA_NOEXCEPT; double mRmsValue; double mSemiMajorError; diff --git a/receiver/nmea/include/receiver/nmea/message.hpp b/receiver/nmea/include/receiver/nmea/message.hpp index e84af483..05aba3a0 100644 --- a/receiver/nmea/include/receiver/nmea/message.hpp +++ b/receiver/nmea/include/receiver/nmea/message.hpp @@ -9,10 +9,12 @@ namespace nmea { /// Base class for all messages. class Message { public: - NMEA_EXPLICIT Message(std::string prefix) NMEA_NOEXCEPT; + NMEA_EXPLICIT Message(std::string prefix, std::string payload, + std::string checksum) NMEA_NOEXCEPT; virtual ~Message() = default; - Message(const Message& other) : mPrefix(other.mPrefix) {} + Message(const Message& other) + : mPrefix(other.mPrefix), mPayload(other.mPayload), mChecksum(other.mChecksum) {} Message(Message&&) = delete; Message& operator=(const Message&) = delete; Message& operator=(Message&&) = delete; @@ -20,52 +22,51 @@ class Message { /// Get the message prefix, e.g. "$GPGGA". NMEA_NODISCARD const std::string& prefix() const NMEA_NOEXCEPT { return mPrefix; } + /// Get the message payload. + NMEA_NODISCARD const std::string& payload() const NMEA_NOEXCEPT { return mPayload; } + + /// Get the reconstructed sentence. + NMEA_NODISCARD std::string sentence() const NMEA_NOEXCEPT { + return mPrefix + "," + mPayload + "*" + mChecksum + "\r\n"; + } + /// Print the message to stdout. virtual void print() const NMEA_NOEXCEPT = 0; private: std::string mPrefix; + std::string mPayload; + std::string mChecksum; }; /// Unsupported or unknown message. class UnsupportedMessage final : public Message { public: - NMEA_EXPLICIT UnsupportedMessage(std::string prefix, std::string payload) NMEA_NOEXCEPT; + NMEA_EXPLICIT UnsupportedMessage(std::string prefix, std::string payload, + std::string checksum) NMEA_NOEXCEPT; ~UnsupportedMessage() override = default; - UnsupportedMessage(const UnsupportedMessage& other) - : Message(other), mPayload(other.mPayload) {} + UnsupportedMessage(const UnsupportedMessage& other) : Message(other) {} UnsupportedMessage(UnsupportedMessage&&) = delete; UnsupportedMessage& operator=(const UnsupportedMessage&) = delete; UnsupportedMessage& operator=(UnsupportedMessage&&) = delete; void print() const NMEA_NOEXCEPT override; - - /// Get the message payload. - NMEA_NODISCARD const std::string& payload() const NMEA_NOEXCEPT { return mPayload; } - -private: - std::string mPayload; }; /// Error message. This is used to indicate that the message could not be parsed. class ErrorMessage final : public Message { public: - NMEA_EXPLICIT ErrorMessage(std::string prefix, std::string payload) NMEA_NOEXCEPT; + NMEA_EXPLICIT ErrorMessage(std::string prefix, std::string payload, + std::string checksum) NMEA_NOEXCEPT; ~ErrorMessage() override = default; - ErrorMessage(const ErrorMessage& other) : Message(other), mPayload(other.mPayload) {} + ErrorMessage(const ErrorMessage& other) : Message(other) {} ErrorMessage(ErrorMessage&&) = delete; ErrorMessage& operator=(const ErrorMessage&) = delete; ErrorMessage& operator=(ErrorMessage&&) = delete; void print() const NMEA_NOEXCEPT override; - - /// Get the message payload. - NMEA_NODISCARD const std::string& payload() const NMEA_NOEXCEPT { return mPayload; } - -private: - std::string mPayload; }; } // namespace nmea diff --git a/receiver/nmea/include/receiver/nmea/threaded_receiver.hpp b/receiver/nmea/include/receiver/nmea/threaded_receiver.hpp index 109a9090..3d601d62 100644 --- a/receiver/nmea/include/receiver/nmea/threaded_receiver.hpp +++ b/receiver/nmea/include/receiver/nmea/threaded_receiver.hpp @@ -16,8 +16,9 @@ namespace nmea { class ThreadedReceiver { public: /// The receiver will be created on the thread, thus this will _not_ block. - NMEA_EXPLICIT ThreadedReceiver(std::unique_ptr interface, - bool print_messages) NMEA_NOEXCEPT; + NMEA_EXPLICIT ThreadedReceiver( + std::unique_ptr interface, bool print_messages, + std::vector> export_interfaces) NMEA_NOEXCEPT; ~ThreadedReceiver() NMEA_NOEXCEPT; /// Start the receiver thread. @@ -48,12 +49,13 @@ class ThreadedReceiver { void run(); private: - std::unique_ptr mInterface; - std::unique_ptr mReceiver; - std::unique_ptr mThread; - std::atomic mRunning; - std::mutex mMutex; - bool mPrintMessages; + std::unique_ptr mInterface; + std::unique_ptr mReceiver; + std::unique_ptr mThread; + std::atomic mRunning; + std::mutex mMutex; + bool mPrintMessages; + std::vector> mExportInterfaces; std::unique_ptr mGga; std::unique_ptr mVtg; diff --git a/receiver/nmea/include/receiver/nmea/vtg.hpp b/receiver/nmea/include/receiver/nmea/vtg.hpp index fb749876..8b7d6e5a 100644 --- a/receiver/nmea/include/receiver/nmea/vtg.hpp +++ b/receiver/nmea/include/receiver/nmea/vtg.hpp @@ -39,11 +39,12 @@ class VtgMessage final : public Message { return mSpeedOverGroundKmh / 3.6; } - NMEA_NODISCARD static std::unique_ptr parse(std::string prefix, - const std::string& payload); + NMEA_NODISCARD static std::unique_ptr + parse(std::string prefix, const std::string& payload, std::string checksum); private: - NMEA_EXPLICIT VtgMessage(std::string prefix) NMEA_NOEXCEPT; + NMEA_EXPLICIT VtgMessage(std::string prefix, std::string payload, + std::string checksum) NMEA_NOEXCEPT; double mTrueCourseOverGround; double mMagneticCourseOverGround; diff --git a/receiver/nmea/message.cpp b/receiver/nmea/message.cpp index 3a129c40..77adee98 100644 --- a/receiver/nmea/message.cpp +++ b/receiver/nmea/message.cpp @@ -5,15 +5,18 @@ namespace receiver { namespace nmea { -Message::Message(std::string prefix) NMEA_NOEXCEPT : mPrefix(prefix) {} +Message::Message(std::string prefix, std::string payload, std::string checksum) NMEA_NOEXCEPT + : mPrefix(prefix), + mPayload(payload), + mChecksum(checksum) {} // // // -UnsupportedMessage::UnsupportedMessage(std::string prefix, std::string payload) NMEA_NOEXCEPT - : Message(prefix), - mPayload(payload) {} +UnsupportedMessage::UnsupportedMessage(std::string prefix, std::string payload, + std::string checksum) NMEA_NOEXCEPT + : Message(prefix, payload, checksum) {} void UnsupportedMessage::print() const NMEA_NOEXCEPT { printf("[%5s] UNSUPPORTED %s\n", prefix().c_str(), payload().c_str()); @@ -23,9 +26,9 @@ void UnsupportedMessage::print() const NMEA_NOEXCEPT { // // -ErrorMessage::ErrorMessage(std::string prefix, std::string payload) NMEA_NOEXCEPT - : Message(prefix), - mPayload(payload) {} +ErrorMessage::ErrorMessage(std::string prefix, std::string payload, + std::string checksum) NMEA_NOEXCEPT + : Message(prefix, payload, checksum) {} void ErrorMessage::print() const NMEA_NOEXCEPT { printf("[%5s] ERROR %s\n", prefix().c_str(), payload().c_str()); diff --git a/receiver/nmea/parser.cpp b/receiver/nmea/parser.cpp index 8c5da147..74735013 100644 --- a/receiver/nmea/parser.cpp +++ b/receiver/nmea/parser.cpp @@ -111,33 +111,38 @@ std::unique_ptr Parser::try_parse() NMEA_NOEXCEPT { return nullptr; } - auto data_length = data_end - data_start; - auto data_payload = payload.substr(data_start, data_length); + auto data_length = data_end - data_start; + auto data_payload = payload.substr(data_start, data_length); + auto data_checksum = payload.substr(data_end + 1, data_end + 3); // parse message if (prefix == "GPGGA" || prefix == "GLGGA" || prefix == "GAGGA" || prefix == "GNGGA") { - auto message = GgaMessage::parse(prefix, data_payload); + auto message = GgaMessage::parse(prefix, data_payload, data_checksum); if (message) { return message; } else { - return std::unique_ptr(new ErrorMessage(prefix, data_payload)); + return std::unique_ptr( + new ErrorMessage(prefix, data_payload, data_checksum)); } } else if (prefix == "GPVTG" || prefix == "GLVTG" || prefix == "GAVTG" || prefix == "GNVTG") { - auto message = VtgMessage::parse(prefix, data_payload); + auto message = VtgMessage::parse(prefix, data_payload, data_checksum); if (message) { return message; } else { - return std::unique_ptr(new ErrorMessage(prefix, data_payload)); + return std::unique_ptr( + new ErrorMessage(prefix, data_payload, data_checksum)); } } else if (prefix == "GPGST" || prefix == "GLGST" || prefix == "GAGST" || prefix == "GNGST") { - auto message = GstMessage::parse(prefix, data_payload); + auto message = GstMessage::parse(prefix, data_payload, data_checksum); if (message) { return message; } else { - return std::unique_ptr(new ErrorMessage(prefix, data_payload)); + return std::unique_ptr( + new ErrorMessage(prefix, data_payload, data_checksum)); } } else { - return std::unique_ptr(new UnsupportedMessage(prefix, data_payload)); + return std::unique_ptr( + new UnsupportedMessage(prefix, data_payload, data_checksum)); } } diff --git a/receiver/nmea/threaded_receiver.cpp b/receiver/nmea/threaded_receiver.cpp index cdb9ae5e..32ee41b7 100644 --- a/receiver/nmea/threaded_receiver.cpp +++ b/receiver/nmea/threaded_receiver.cpp @@ -13,11 +13,13 @@ namespace receiver { namespace nmea { -ThreadedReceiver::ThreadedReceiver(std::unique_ptr interface, - bool print_messages) NMEA_NOEXCEPT +ThreadedReceiver::ThreadedReceiver( + std::unique_ptr interface, bool print_messages, + std::vector> export_interfaces) NMEA_NOEXCEPT : mInterface(std::move(interface)), mRunning(false), mPrintMessages(print_messages), + mExportInterfaces(std::move(export_interfaces)), mGga(nullptr), mVtg(nullptr), mGst(nullptr) { @@ -84,6 +86,15 @@ void ThreadedReceiver::run() { mGst = std::unique_ptr( static_cast(message.release())); } + + if (!mExportInterfaces.empty()) { + auto message_data = message->sentence(); + for (auto& interface : mExportInterfaces) { + if (interface->can_write()) { + interface->write(message_data.data(), message_data.size()); + } + } + } } else { break; } @@ -127,7 +138,7 @@ std::unique_ptr ThreadedReceiver::vtg() NMEA_NOEXCEPT { RNT_DEBUG("[rnt] lock (vtg)\n"); std::lock_guard lock(mMutex); - if(!mVtg) return nullptr; + if (!mVtg) return nullptr; auto vtg = std::unique_ptr(new VtgMessage{*mVtg.get()}); RNT_DEBUG("[rnt] unlock (vtg)\n"); return vtg; @@ -138,7 +149,7 @@ std::unique_ptr ThreadedReceiver::gst() NMEA_NOEXCEPT { RNT_DEBUG("[rnt] lock (gst)\n"); std::lock_guard lock(mMutex); - if(!mGst) return nullptr; + if (!mGst) return nullptr; auto gst = std::unique_ptr(new GstMessage{*mGst.get()}); RNT_DEBUG("[rnt] unlock (gst)\n"); return gst; diff --git a/receiver/nmea/vtg.cpp b/receiver/nmea/vtg.cpp index f1b17b5f..9bd4fe47 100644 --- a/receiver/nmea/vtg.cpp +++ b/receiver/nmea/vtg.cpp @@ -41,12 +41,13 @@ static bool parse_mode_indicator(const std::string& token, ModeIndicator& mode_i } } -VtgMessage::VtgMessage(std::string prefix) NMEA_NOEXCEPT : Message{prefix}, - mTrueCourseOverGround{0.0}, - mMagneticCourseOverGround{0.0}, - mSpeedOverGroundKnots{0.0}, - mSpeedOverGroundKmh{0.0}, - mModeIndicator{ModeIndicator::Unknown} {} +VtgMessage::VtgMessage(std::string prefix, std::string payload, std::string checksum) NMEA_NOEXCEPT + : Message{prefix, payload, checksum}, + mTrueCourseOverGround{0.0}, + mMagneticCourseOverGround{0.0}, + mSpeedOverGroundKnots{0.0}, + mSpeedOverGroundKmh{0.0}, + mModeIndicator{ModeIndicator::Unknown} {} void VtgMessage::print() const NMEA_NOEXCEPT { printf("[%5s]\n", prefix().c_str()); @@ -63,7 +64,8 @@ void VtgMessage::print() const NMEA_NOEXCEPT { } } -std::unique_ptr VtgMessage::parse(std::string prefix, const std::string& payload) { +std::unique_ptr VtgMessage::parse(std::string prefix, const std::string& payload, + std::string checksum) { // split payload by ',' auto tokens = split(payload, ','); @@ -76,7 +78,7 @@ std::unique_ptr VtgMessage::parse(std::string prefix, const std::string } // parse - auto message = new VtgMessage(prefix); + auto message = new VtgMessage(prefix, payload, checksum); auto success = true; success &= parse_double_opt(tokens[0], message->mTrueCourseOverGround); success &= parse_double_opt(tokens[2], message->mMagneticCourseOverGround);