QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
NMEASentence.h
Go to the documentation of this file.
1#pragma once
2
3#include <array>
4#include <charconv>
5#include <cmath>
6#include <cstdint>
7#include <optional>
8#include <span>
9#include <string_view>
10#include <type_traits>
11
12#include <QtCore/QByteArray>
13#include <QtCore/QByteArrayView>
14#include <QtCore/QTime>
15
16#include "GPSReceiverReports.h"
17
18namespace NMEA {
19constexpr int DECIMAL_BASE = 10;
20constexpr int HEX_BASE = 16;
21constexpr int CHECKSUM_DIGITS = 2;
22constexpr double MINUTES_PER_DEGREE = 60.0;
23
24namespace Field {
25constexpr size_t UTC_TIME = 1;
26constexpr size_t GGA_LATITUDE = 2;
27constexpr size_t GGA_LATITUDE_HEMISPHERE = 3;
28constexpr size_t GGA_LONGITUDE = 4;
29constexpr size_t GGA_LONGITUDE_HEMISPHERE = 5;
30constexpr size_t GGA_QUALITY = 6;
31constexpr size_t GGA_SATELLITES_USED = 7;
32constexpr size_t GGA_HDOP = 8;
33constexpr size_t GGA_ALTITUDE = 9;
34constexpr size_t GGA_ALTITUDE_UNITS = 10;
35constexpr size_t GGA_GEOID_SEPARATION = 11;
36constexpr size_t GGA_GEOID_UNITS = 12;
37constexpr size_t GGA_MIN_FIELDS = GGA_GEOID_UNITS + 1;
38constexpr size_t RMC_STATUS = 2;
39constexpr size_t RMC_LATITUDE = 3;
40constexpr size_t RMC_LATITUDE_HEMISPHERE = 4;
41constexpr size_t RMC_LONGITUDE = 5;
42constexpr size_t RMC_LONGITUDE_HEMISPHERE = 6;
43constexpr size_t RMC_SPEED_KNOTS = 7;
44constexpr size_t RMC_COURSE = 8;
45constexpr size_t RMC_DATE = 9;
46constexpr size_t RMC_MIN_FIELDS = RMC_DATE + 1;
47constexpr size_t GLL_LATITUDE = 1;
48constexpr size_t GLL_LATITUDE_HEMISPHERE = 2;
49constexpr size_t GLL_LONGITUDE = 3;
50constexpr size_t GLL_LONGITUDE_HEMISPHERE = 4;
51constexpr size_t GLL_TIME = 5;
52constexpr size_t GLL_STATUS = 6;
53constexpr size_t GLL_MIN_FIELDS = GLL_STATUS + 1;
54constexpr size_t GSA_DIMENSION = 2;
55constexpr size_t GSA_FIRST_SATELLITE = 3;
56constexpr size_t GSA_SATELLITE_SLOTS = 12;
58constexpr size_t GSA_VDOP = GSA_HDOP + 1;
59constexpr size_t GSA_MIN_FIELDS = GSA_VDOP + 1;
60constexpr size_t GSA_SYSTEM_ID = GSA_MIN_FIELDS;
61constexpr size_t VTG_TRUE_COURSE = 1;
62constexpr size_t VTG_SPEED_KNOTS = 5;
63constexpr size_t VTG_SPEED_KMH = 7;
64constexpr size_t VTG_MIN_FIELDS = VTG_SPEED_KMH + 1;
65constexpr size_t ZDA_DAY = 2;
66constexpr size_t ZDA_MONTH = 3;
67constexpr size_t ZDA_YEAR = 4;
68constexpr size_t ZDA_MIN_FIELDS = ZDA_YEAR + 1;
69constexpr size_t GST_LATITUDE_ERROR = 6;
70constexpr size_t GST_LONGITUDE_ERROR = 7;
71constexpr size_t GST_ALTITUDE_ERROR = 8;
72} // namespace Field
73
74namespace GgaQuality {
75constexpr unsigned INVALID = 0;
76constexpr unsigned GPS = 1;
77constexpr unsigned DIFFERENTIAL = 2;
78constexpr unsigned RTK_FIXED = 4;
79constexpr unsigned RTK_FLOAT = 5;
80constexpr unsigned ESTIMATED = 6;
82constexpr unsigned MANUAL = 7;
83constexpr unsigned MAX_VALUE = 8;
84} // namespace GgaQuality
85
86namespace FixDimension {
87constexpr unsigned NO_FIX = 1;
88constexpr unsigned THREE_D = 3;
89} // namespace FixDimension
90
92[[nodiscard]] GPSFixQuality fixQuality(unsigned quality, GPSFixQuality autonomous);
93
94template <class T>
95[[nodiscard]] std::optional<T> number(std::string_view field, int base = DECIMAL_BASE)
96{
97 if (field.empty()) {
98 return std::nullopt;
99 }
100 if (field.front() == '+') {
101 field.remove_prefix(1);
102 if (!field.empty() && (field.front() == '-' || field.front() == '+')) {
103 return std::nullopt;
104 }
105 }
106 if (field.empty()) {
107 return std::nullopt;
108 }
109 T value{};
110 if constexpr (std::is_integral_v<T>) {
111 const auto parsed = std::from_chars(field.data(), field.data() + field.size(), value, base);
112 if (parsed.ec != std::errc{} || parsed.ptr != field.data() + field.size()) {
113 return std::nullopt;
114 }
115 } else {
116 static_assert(std::is_same_v<T, double> || std::is_same_v<T, float>);
117 // Qt parses floating point in the C locale on every platform; libc++ before 20 has no floating-point
118 // from_chars. Qt skips surrounding whitespace, which a field must not carry.
119 constexpr std::string_view WHITESPACE = " \t\n\v\f\r";
120 if (WHITESPACE.find(field.front()) != std::string_view::npos ||
121 WHITESPACE.find(field.back()) != std::string_view::npos) {
122 return std::nullopt;
123 }
124 bool ok = false;
125 if constexpr (std::is_same_v<T, float>) {
126 value = QByteArrayView(field).toFloat(&ok);
127 } else {
128 value = QByteArrayView(field).toDouble(&ok);
129 }
130 if (!ok || !std::isfinite(value)) {
131 return std::nullopt;
132 }
133 }
134 return value;
135}
136
138{
139 static constexpr size_t MAX_FIELDS = 32;
140 static constexpr int PREFIX_LENGTH = 1;
141 static constexpr int TALKER_LENGTH = 2;
142 static constexpr int TYPE_LENGTH = 3;
143 static constexpr int TYPE_OFFSET = PREFIX_LENGTH + TALKER_LENGTH;
144 static constexpr int HEADER_LENGTH = TYPE_OFFSET + TYPE_LENGTH;
145
146 std::array<std::string_view, MAX_FIELDS> fields{};
147 size_t count = 0;
148
149 std::string_view type() const { return fields[0].substr(TYPE_OFFSET); }
150
151 std::string_view talker() const { return fields[0].substr(PREFIX_LENGTH, TALKER_LENGTH); }
152};
153
154[[nodiscard]] unsigned char checksum(std::string_view body);
155
157[[nodiscard]] constexpr bool isAsciiDigit(char ch)
158{
159 return ch >= '0' && ch <= '9';
160}
161
162[[nodiscard]] constexpr bool isAsciiUpper(char ch)
163{
164 return ch >= 'A' && ch <= 'Z';
165}
166
168[[nodiscard]] std::optional<uint32_t> hexNumber(std::string_view digits);
169
171size_t splitFields(std::string_view text, std::span<std::string_view> fields);
172
174struct Frame
175{
176 std::string_view body;
177 std::string_view checksum;
178
179 [[nodiscard]] bool hasValidChecksum() const;
180};
181
184[[nodiscard]] std::optional<Frame> frame(std::string_view text);
185
186[[nodiscard]] std::optional<Sentence> sentence(std::string_view text);
187
189[[nodiscard]] bool isStandardSentence(std::string_view text);
190
192[[nodiscard]] double degreesFromDegreesMinutes(double ddmm);
193
194[[nodiscard]] std::optional<double> coordinate(std::string_view field, std::string_view hemisphere, bool latitude);
195
196struct GGA
197{
198 double latitude = NAN;
199 double longitude = NAN;
200 double altitude = NAN;
201 double geoidSeparation = NAN;
202 double hdop = NAN;
204 std::optional<unsigned> satellitesUsed = std::nullopt;
205};
206
208[[nodiscard]] std::optional<GGA> gga(const Sentence& input);
209
210std::optional<int> utcMilliseconds(std::string_view field);
211
213{
214 int year = 0;
215 unsigned month = 0;
216 unsigned day = 0;
217};
218
219[[nodiscard]] std::optional<UtcDate> rmcDate(std::string_view field);
220
221struct RMC
222{
223 double latitude = NAN;
224 double longitude = NAN;
225 std::optional<int> utcMilliseconds;
227 double courseDegrees = NAN;
228};
229
230[[nodiscard]] std::optional<RMC> rmc(const Sentence& input);
231
232struct GLL
233{
234 double latitude = NAN;
235 double longitude = NAN;
236 std::optional<int> utcMilliseconds;
237};
238
239[[nodiscard]] std::optional<GLL> gll(const Sentence& input);
240
241struct VTG
242{
244 double courseDegrees = NAN;
245};
246
247[[nodiscard]] std::optional<VTG> vtg(const Sentence& input);
248
249struct ZDA
250{
251 std::optional<int> utcMilliseconds;
253};
254
255[[nodiscard]] std::optional<ZDA> zda(const Sentence& input);
256
258{
259 bool valid = false;
260 std::optional<int> utcMilliseconds;
261};
262
265[[nodiscard]] std::optional<NavigationStatus> navigationStatus(const Sentence& input);
266
267struct GST
268{
269 double horizontalAccuracy = NAN;
270 double verticalAccuracy = NAN;
271};
272
273[[nodiscard]] std::optional<GST> gst(const Sentence& input);
274} // namespace NMEA
275
276namespace NMEAUtils {
278[[nodiscard]] QByteArray frame(QByteArrayView body);
279
281[[nodiscard]] QByteArray repairChecksum(const QByteArray& sentence);
282
287QByteArray makeGGA(const NMEA::GGA& fix, const QTime& utc);
288
289} // namespace NMEAUtils
int32_t latitude
Definition UBXPlan.cc:40
QByteArray repairChecksum(const QByteArray &sentence)
Rebuild a valid frame with its checksum and CRLF; short or malformed bodies are only terminated.
QByteArray makeGGA(const NMEA::GGA &fix, const QTime &utc)
QByteArray frame(QByteArrayView body)
body as one "$<body>*hh\r\n" sentence, with uppercase checksum digits.
constexpr size_t VTG_SPEED_KNOTS
constexpr size_t GST_LONGITUDE_ERROR
constexpr size_t GLL_STATUS
constexpr size_t GSA_DIMENSION
constexpr size_t ZDA_DAY
constexpr size_t GSA_SATELLITE_SLOTS
constexpr size_t GGA_QUALITY
constexpr size_t RMC_MIN_FIELDS
constexpr size_t RMC_SPEED_KNOTS
constexpr size_t GLL_LONGITUDE_HEMISPHERE
constexpr size_t GGA_GEOID_UNITS
constexpr size_t GSA_VDOP
constexpr size_t RMC_DATE
constexpr size_t GGA_LATITUDE_HEMISPHERE
constexpr size_t RMC_LONGITUDE
constexpr size_t GSA_MIN_FIELDS
constexpr size_t VTG_MIN_FIELDS
constexpr size_t RMC_LONGITUDE_HEMISPHERE
constexpr size_t GGA_LONGITUDE_HEMISPHERE
constexpr size_t GGA_HDOP
constexpr size_t ZDA_MONTH
constexpr size_t ZDA_MIN_FIELDS
constexpr size_t GGA_ALTITUDE_UNITS
constexpr size_t GSA_SYSTEM_ID
constexpr size_t RMC_COURSE
constexpr size_t GLL_MIN_FIELDS
constexpr size_t GLL_TIME
constexpr size_t GLL_LATITUDE_HEMISPHERE
constexpr size_t GGA_SATELLITES_USED
constexpr size_t VTG_TRUE_COURSE
constexpr size_t GSA_FIRST_SATELLITE
constexpr size_t GST_ALTITUDE_ERROR
constexpr size_t GGA_ALTITUDE
constexpr size_t ZDA_YEAR
constexpr size_t GGA_LONGITUDE
constexpr size_t RMC_STATUS
constexpr size_t RMC_LATITUDE
constexpr size_t GLL_LONGITUDE
constexpr size_t GST_LATITUDE_ERROR
constexpr size_t GSA_HDOP
constexpr size_t VTG_SPEED_KMH
constexpr size_t GGA_MIN_FIELDS
constexpr size_t RMC_LATITUDE_HEMISPHERE
constexpr size_t GLL_LATITUDE
constexpr size_t GGA_LATITUDE
constexpr size_t UTC_TIME
constexpr size_t GGA_GEOID_SEPARATION
constexpr unsigned THREE_D
constexpr unsigned NO_FIX
constexpr unsigned MAX_VALUE
constexpr unsigned RTK_FLOAT
constexpr unsigned DIFFERENTIAL
constexpr unsigned MANUAL
Manual input: the position a base station was fixed at.
constexpr unsigned ESTIMATED
constexpr unsigned RTK_FIXED
constexpr unsigned GPS
constexpr unsigned INVALID
std::optional< ZDA > zda(const Sentence &input)
std::optional< T > number(std::string_view field, int base=DECIMAL_BASE)
std::optional< Sentence > sentence(std::string_view text)
constexpr int HEX_BASE
std::optional< double > coordinate(std::string_view field, std::string_view hemisphere, bool latitude)
std::optional< UtcDate > rmcDate(std::string_view field)
GPSFixQuality fixQuality(unsigned quality, GPSFixQuality autonomous)
Autonomous GGA does not distinguish 2D/3D; the caller supplies its independently resolved quality.
constexpr int DECIMAL_BASE
constexpr bool isAsciiUpper(char ch)
std::optional< int > utcMilliseconds(std::string_view field)
std::optional< RMC > rmc(const Sentence &input)
std::optional< GLL > gll(const Sentence &input)
double degreesFromDegreesMinutes(double ddmm)
Convert a signed ddmm.mmmm (or dddmm.mmmm) value; invalid values return NaN.
std::optional< uint32_t > hexNumber(std::string_view digits)
The value of digits when every one is a hexadecimal digit, with no sign or prefix; nullopt otherwise.
constexpr int CHECKSUM_DIGITS
unsigned char checksum(std::string_view body)
constexpr double MINUTES_PER_DEGREE
std::optional< GGA > gga(const Sentence &input)
Quality zero is a valid fix-loss observation with empty coordinates and optional hemisphere fields.
std::optional< VTG > vtg(const Sentence &input)
constexpr bool isAsciiDigit(char ch)
Locale-independent ASCII character classes for receiver text.
std::optional< NavigationStatus > navigationStatus(const Sentence &input)
std::optional< GST > gst(const Sentence &input)
bool isStandardSentence(std::string_view text)
A checked sentence from a standard talker, not a proprietary $P... sentence.
size_t splitFields(std::string_view text, std::span< std::string_view > fields)
Preserve empty fields, including the final field. Return zero on storage overflow.
std::optional< Frame > frame(std::string_view text)
Views into a single wire frame; the caller owns the backing bytes.
bool hasValidChecksum() const
std::string_view checksum
std::string_view body
double altitude
double hdop
unsigned quality
double geoidSeparation
double latitude
std::optional< unsigned > satellitesUsed
double longitude
double longitude
double latitude
std::optional< int > utcMilliseconds
double horizontalAccuracy
double verticalAccuracy
std::optional< int > utcMilliseconds
double latitude
double courseDegrees
double longitude
double speedMetersPerSecond
std::optional< int > utcMilliseconds
static constexpr size_t MAX_FIELDS
static constexpr int TALKER_LENGTH
static constexpr int TYPE_OFFSET
std::string_view type() const
static constexpr int TYPE_LENGTH
static constexpr int PREFIX_LENGTH
std::array< std::string_view, MAX_FIELDS > fields
std::string_view talker() const
static constexpr int HEADER_LENGTH
double speedMetersPerSecond
double courseDegrees
UtcDate date
std::optional< int > utcMilliseconds