nmealib 0.0.4
NMEA 0183/NMEA 2000 parsing library
Loading...
Searching...
No Matches
nmea0183.cpp
Go to the documentation of this file.
1#include "nmealib/nmea0183.h"
2
5
6#include <sstream>
7#include <iomanip>
8#include <algorithm>
9#include <cctype>
10#include <cstdlib>
11#include <numeric>
12#include <cmath>
13
14namespace nmealib {
15namespace nmea0183 {
16
17Message0183::Message0183(std::string raw,
18 TimePoint ts,
19 char startChar,
20 std::string talker,
21 std::string sentenceType,
22 std::string payload) noexcept
23 : Message(std::move(raw), Type::NMEA0183, ts),
24 startChar_(startChar),
25 talker_(std::move(talker)),
26 sentenceType_(std::move(sentenceType)),
27 payload_(std::move(payload)) {
28 calculatedChecksumStr_ = computeChecksum(payload_);
29}
30
31Message0183::Message0183(std::string raw,
32 TimePoint ts,
33 char startChar,
34 std::string talker,
35 std::string sentenceType,
36 std::string payload,
37 std::string checksumStr) noexcept
38 : Message(std::move(raw), Type::NMEA0183, ts),
39 startChar_(startChar),
40 talker_(std::move(talker)),
41 sentenceType_(std::move(sentenceType)),
42 payload_(std::move(payload)),
43 checksumStr_(std::move(checksumStr)) {
44 calculatedChecksumStr_ = computeChecksum(payload_);
45}
46
47std::unique_ptr<Message0183> Message0183::create(const std::string& raw, TimePoint ts) {
48 std::string context = "Message0183::create";
49#if defined(NMEALIB_NO_EXCEPTIONS)
50 if (!validateFormat(context, raw)) {
51 return nullptr;
52 }
53#else
54 validateFormat(context, raw);
55#endif
56
57 char startChar = raw[0];
58
59 bool hasCRLF = raw.size() >= 2 && raw.substr(raw.size() - 2) == "\r\n";
60
61 // Extract talker and sentence type from the raw sentence.
62 std::string talker = raw.substr(1, 2);
63 std::string sentenceType = raw.substr(3, 3);
64 bool hasChecksum = raw.find('*') != std::string::npos;
65 if(hasChecksum) {
66 size_t asteriskPos = raw.find('*');
67 std::string payload = raw.substr(1, asteriskPos - 1); // Exclude start char and checksum part
68 std::string checksumStr = raw.substr(asteriskPos + 1, 2);
69 return std::unique_ptr<Message0183>(new Message0183(raw, ts, startChar, talker, sentenceType, payload, checksumStr));
70 } else {
71 if (hasCRLF) {
72 std::string payload = raw.substr(1, raw.size() - 3); // Exclude start char and CRLF
73 return std::unique_ptr<Message0183>(new Message0183(raw, ts, startChar, talker, sentenceType, payload));
74 } else {
75 std::string payload = raw.substr(1); // Exclude start char only
76 return std::unique_ptr<Message0183>(new Message0183(raw, ts, startChar, talker, sentenceType, payload));
77 }
78 }
79}
80
81bool Message0183::validateFormat(const std::string& context, const std::string& raw) {
82 // TODO: I have to check that it correspons to the minimum sentence: $XXXXX*ZZ<CR><LF>
83 // and also without checksum: $XXXXX<CR><LF>
84 // and also without CRLF: $XXXXX*ZZ and $XXXXX
85 if(raw.size() > 82) {
86 NMEALIB_RETURN_ERROR_VALUE(TooLongSentenceException(context, "Input string length: " + std::to_string(raw.size())), false);
87 }
88 if(raw.empty() || (raw[0] != '$' && raw[0] != '!')) {
89 NMEALIB_RETURN_ERROR_VALUE(InvalidStartCharacterException(context), false);
90 }
91
92 bool hasChecksum = raw.find('*') != std::string::npos;
93 if (hasChecksum) {
94 // If it has a checksum, find the position of '*' and extract payload and checksum accordingly.
95 size_t asteriskPos = raw.find('*');
96 std::string checksumStr = raw.substr(asteriskPos + 1, 2);
97 if(!isHexByte(checksumStr)) {
98 NMEALIB_RETURN_ERROR_VALUE(NoChecksumException(context, "Invalid checksum format: " + checksumStr), false);
99 }
100 }
101
102 return true;
103}
104
105std::unique_ptr<nmealib::Message> Message0183::clone() const {
106 return std::unique_ptr<Message0183>(new Message0183(*this));
107}
108
109std::string Message0183::getPayload() const noexcept {
110 return payload_;
111}
112
113char Message0183::getStartChar() const noexcept {
114 return startChar_;
115}
116
117std::string Message0183::getTalker() const noexcept {
118 return talker_;
119}
120
121std::string Message0183::getSentenceType() const noexcept {
122 return sentenceType_;
123}
124
125std::string Message0183::getChecksumStr() const {
126 if (checksumStr_.empty()) {
127 NMEALIB_RETURN_ERROR_VALUE(NoChecksumException("Message0183::getChecksumStr", "This sentence does not contain a checksum"), std::string());
128 }
129 return checksumStr_;
130}
131
132std::string Message0183::getCalculatedChecksumStr() const noexcept {
134}
135
136std::string Message0183::getStringContent(bool verbose) const noexcept {
137 std::stringstream ss;
138
139 ss << this->toString(verbose);
140
141 if (verbose) ss << "\tUnimplemented sentence type";
142 else ss << "Unimplemented sentence type";
143
144 return ss.str();
145}
146
147std::string Message0183::toString(bool verbose) const noexcept {
148 std::stringstream ss;
149 std::string validity = "KO";
150 if(validate()) {
151 validity = "OK";
152 }
153
154 if (verbose) {
155 ss << "--------------------------------\n";
156 ss << "Protocol: " << typeToString(type_) << "\n";
157 ss << "Talker: " << getTalker() << "\n";
158 ss << "Sentence Type: " << getSentenceType() << "\n";
159 ss << "Checksum: " << (checksumStr_.empty() ? "None" : validity) << "\n";
160 ss << "Fields:\n";
161 } else {
162 ss << "[" << validity << "] " << typeToString(type_) << " " << getTalker() << " " << getSentenceType() << ": ";
163 }
164
165 return ss.str();
166}
167
168std::string Message0183::serialize() const {
169 return rawData_;
170}
171
172bool Message0183::validate() const noexcept{
173 if (checksumStr_.empty()) {
174 return true;
175 }
176
178}
179
180std::string Message0183::computeChecksum(const std::string& payload) noexcept {
181 uint8_t checksum = std::accumulate(payload.begin(), payload.end(), static_cast<uint8_t>(0),
182 [](uint8_t acc, char c) { return acc ^ static_cast<uint8_t>(c); });
183 std::stringstream ss;
184 ss << std::uppercase << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(checksum);
185 return ss.str();
186}
187
188bool Message0183::isHexByte(const std::string& s) noexcept {
189 if (s.size() != 2) return false;
190 return std::isxdigit(static_cast<unsigned char>(s[0])) &&
191 std::isxdigit(static_cast<unsigned char>(s[1]));
192}
193
194double Message0183::convertNmeaCoordinateToDecimalDegrees(const std::string& nmeaCoordinate) {
195 double converted = 0.0;
196 if (!detail::parseNmeaCoordinate(nmeaCoordinate, converted)) {
197 NMEALIB_RETURN_ERROR_VALUE(NmeaException("Message0183::convertNmeaCoordinateToDecimalDegrees", "Invalid coordinate format", nmeaCoordinate), 0.0);
198 }
199
200 return converted;
201}
202
203// TODO: Re-expose this method to all child classes and restrict the inherited ones like i did in
204bool Message0183::operator==(const Message0183& other) const noexcept {
205 return startChar_ == other.startChar_ &&
206 talker_ == other.talker_ &&
207 sentenceType_ == other.sentenceType_ &&
208 payload_ == other.payload_ &&
209 checksumStr_ == other.checksumStr_ &&
210 calculatedChecksumStr_ == other.calculatedChecksumStr_ &&
211 Message::operator==(other);
212}
213
214} // namespace nmea0183
215} // namespace nmealib
std::string rawData_
Definition message.h:97
std::chrono::system_clock::time_point TimePoint
Definition message.h:30
bool operator==(const Message &other) const noexcept
Compares two Message objects for equality based on their content.
Definition message.cpp:33
Base exception class for all NMEA library errors.
Represents an NMEA 0183 sentence.
Definition nmea0183.h:98
bool validate() const noexcept override
Returns whether the message is valid or not.
Definition nmea0183.cpp:172
std::string serialize() const override
Returns the wire-format representation of the NMEA 0183 sentence, that is, the raw information that w...
Definition nmea0183.cpp:168
std::string getChecksumStr() const
Get the checksum string extracted from the raw sentence.
Definition nmea0183.cpp:125
std::string getSentenceType() const noexcept
Returns the sentence type identifier extracted from the sentence.
Definition nmea0183.cpp:121
virtual std::string getStringContent(bool verbose) const noexcept
Returns a human-readable string representation of the message content.
Definition nmea0183.cpp:136
bool operator==(const Message0183 &other) const noexcept
Compares two Message0183 objects for equality based on their content and timestamp.
Definition nmea0183.cpp:204
std::unique_ptr< nmealib::Message > clone() const override
Creates a polymorphic deep copy of this Message0183.
Definition nmea0183.cpp:105
std::string getTalker() const noexcept
Returns the talker identifier extracted from the sentence.
Definition nmea0183.cpp:117
std::string getPayload() const noexcept
Returns the payload of the NMEA 0183 sentence.
Definition nmea0183.cpp:109
static double convertNmeaCoordinateToDecimalDegrees(const std::string &nmeaCoordinate)
Converts an NMEA coordinate in ddmm.mmmm / dddmm.mmmm format to decimal degrees.
Definition nmea0183.cpp:194
Message0183(const Message0183 &)=default
Copy constructor.
char getStartChar() const noexcept
Returns the start character of the NMEA 0183 sentence.
Definition nmea0183.cpp:113
std::string getCalculatedChecksumStr() const noexcept
Get the calculated checksum string for the sentence payload.
Definition nmea0183.cpp:132
std::string toString(bool verbose) const noexcept
Provides the common string representation for every NMEA 0183 message, which can be used by derived c...
Definition nmea0183.cpp:147
static std::unique_ptr< Message0183 > create(const std::string &raw, TimePoint ts=std::chrono::system_clock::now())
Protected factory method to create a Message0183 from a raw sentence string.
Definition nmea0183.cpp:47
Exception thrown when an operation requires a checksum but none is present in the sentence.
Definition nmea0183.h:44
Exception thrown when an NMEA 0183 sentence exceeds the maximum allowed length of 82 characters.
Definition nmea0183.h:14
#define NMEALIB_RETURN_ERROR_VALUE(exceptionExpr, value)
bool parseNmeaCoordinate(const std::string &text, double &value) noexcept
Definition parse.h:118