Pico GPS Teseo I2C
Loading...
Searching...
No Matches
nmea.cpp
Go to the documentation of this file.
1/*
2 * nmea.cpp
3 *
4 * Created on: 29 jul. 2024
5 * Author: jancu
6 */
7
8module;
9
10#include <cassert>
11#include <ranges>
12#include <cmath>
13#include <chrono>
14
15module nmea;
16
17using std::operator""sv;
18constexpr auto delim{","sv};
19
20namespace nmea {
21
22talker_id nmea::talker(const std::string_view& sv) {
24 if (sv.starts_with("$GP")) {
25 id = talker_id::gps;
26 } else if (sv.starts_with("$GL")) {
28 } else if (sv.starts_with("$GA")) {
30 } else if (sv.starts_with("$BD")) {
32 } else if (sv.starts_with("$QZ")) {
33 id = talker_id::qzss;
34 } else if (sv.starts_with("$GN")) {
36 }
37 return id;
38}
39
40talker_id nmea::system(const std::string_view& sv) {
42 if (sv.starts_with("1")) {
43 id = talker_id::gps;
44 } else if (sv.starts_with("2")) {
46 } else if (sv.starts_with("3")) {
48 } else if (sv.starts_with("4")) {
50 } else if (sv.starts_with("5")) {
51 id = talker_id::qzss;
52 }
53 return id;
54}
55
56float nmea::coord(const unsigned int degrees_chars, const std::string_view& sv) {
57 std::string s(sv);
58 // a float (and a double) may not precisely represent the value in the message
59 float coord = std::stof(s.substr(0, degrees_chars), nullptr);
60 coord += std::stof(s.substr(degrees_chars), nullptr) / 60.0;
61 return coord;
62}
63
64direction nmea::dir(const std::string_view& sv) {
65 assert(sv.starts_with('N') ||
66 sv.starts_with('S') ||
67 sv.starts_with('E') ||
68 sv.starts_with('W'));
70 if (sv.starts_with('N')) {
71 d = direction::n;
72 } else if (sv.starts_with('S')) {
73 d = direction::s;
74 } else if (sv.starts_with('E')) {
75 d = direction::e;
76 } else if (sv.starts_with('W')) {
77 d = direction::w;
78 }
79 return d;
80}
81
82void nmea::time(const std::string_view& sv, time_t& t) {
83 std::string s(sv);
84 t = time_t{
85 std::chrono::hours(std::stoi(s.substr(0, 2))) +
86 std::chrono::minutes(std::stoi(s.substr(2, 2))) +
87 std::chrono::seconds(std::stoi(s.substr(4, 2))) +
88 std::chrono::milliseconds(std::stoi(s.substr(7)))
89 };
90}
91
92void nmea::date(const std::string_view& sv, std::chrono::year_month_day& d) {
93 std::string s(sv);
94// NMEA returns two digits, and not all systems know what century we are in.
95// Current solution: use the century defined in source code.
96// alternative could be to convert to stream and use formatter to parse 2 digit year
97// but that's a bit much for our use case
98#define CENTURY (2000)
99 d = std::chrono::year_month_day{
100 std::chrono::year(std::stoi(s.substr(4)) + CENTURY),
101 std::chrono::month(std::stoi(s.substr(2, 2))),
102 std::chrono::day(std::stoi(s.substr(0, 2)))
103 };
104#undef CENTURY
105}
106
107bool nmea::valid(const std::string_view& sv) {
108 assert(sv.starts_with('A') ||
109 sv.starts_with('V'));
110 return sv.starts_with('A');
111}
112
113quality nmea::qual(const std::string_view& sv) {
114 // the typecast from int to enem<unsigned int> below is confirmed
115 // to be safe in this case
116 // https://www.modernescpp.com/index.php/strongly-typed-enums/
117 return static_cast<quality>(std::stoi(std::string(sv)));
118}
119
120// $GPGLL,<Lat>,<N/S>,<Long>,<E/W>,<Timestamp>,<Status>,<mode indicator>*<checksum><cr><lf>
121bool gll::from_data(const std::string& data, gll& gll) {
122 unsigned int field = 0;
123 for (const auto word : std::views::split(data, delim)) {
124 switch (field) {
125 case 0: // talker id
126 gll.source = nmea::talker(std::string_view(word));
127 break;
128 case 1: // latitude
129 gll.lat = nmea::coord(2, std::string_view(word));
130 break;
131 case 2: // latitude direction
132 if (nmea::dir(std::string_view(word)) == direction::s) {
133 gll.lat = gll.lat * -1;
134 }
135 break;
136 case 3: // longitude
137 gll.lon = nmea::coord(3, std::string_view(word));
138 break;
139 case 4: // longitude direction
140 if (nmea::dir(std::string_view(word)) == direction::w) {
141 gll.lon = gll.lon * -1;
142 }
143 break;
144 case 5: // timestamp
145 nmea::time(std::string_view(word), gll.t);
146 break;
147 case 6: // valid
148 gll.valid = nmea::valid(std::string_view(word));
149 break;
150 default: // skip 7
151 break;
152 }
153 field++;
154 }
155 return (field == 8); // everything parsed
156}
157
158// $GPGGA,<Timestamp>,<Lat>,<N/S>,<Long>,<E/W>,<GPSQual>,<Sats>,<HDOP>,<Alt>,<AltVal>,<GeoSep>,
159// <GeoVal>,<DGPSAge>,<DGPSRef>*<checksum><cr><lf>
160bool gga::from_data(const std::string& data, gga& gga) {
161 unsigned int field = 0;
162 for (const auto word : std::views::split(data, delim)) {
163 switch (field) {
164 case 0: // talker id
165 gga.source = nmea::talker(std::string_view(word));
166 break;
167 case 1: // timestamp
168 nmea::time(std::string_view(word), gga.t);
169 break;
170 case 2: // latitude
171 gga.lat = nmea::coord(2, std::string_view(word));
172 break;
173 case 3: // latitude direction
174 if (nmea::dir(std::string_view(word)) == direction::s) {
175 gga.lat = gga.lat * -1;
176 }
177 break;
178 case 4: // longitude
179 gga.lon = nmea::coord(3, std::string_view(word));
180 break;
181 case 5: // longitude direction
182 if (nmea::dir(std::string_view(word)) == direction::w) {
183 gga.lon = gga.lon * -1;
184 }
185 break;
186 case 6: // qual
187 gga.qual = nmea::qual(std::string_view(word));
188 break;
189 case 7: // sats
190 gga.sats = std::stoi(std::string(std::string_view(word)));
191 break;
192 case 9: // altitude (in meters)
193 // a float (and a double) may not precisely represent the value in the message
194 gga.alt = std::stof(std::string(std::string_view(word)), nullptr);
195 break;
196 case 11: // geoid separation (in meters)
197 // a float (and a double) may not precisely represent the value in the message
198 gga.geosep = std::stof(std::string(std::string_view(word)), nullptr);
199 break;
200 default: // skip 8, 10, 12 .. 15
201 break;
202 }
203 field++;
204 }
205 return (field == 15); // everything parsed
206}
207
208// $GNGSA,A,3,15,18,,,,,,,,,,,4.7,3.7,2.9*2D
209// $GNGSA,A,3,73,65,81,,,,,,,,,,4.7,3.7,2.9*2E
210bool gsa::from_data(const std::string& data, gsa& gsa) {
211 unsigned int field = 0;
212 std::string_view v = std::string_view(data).substr(0, data.find('*'));
213 for (const auto word : std::views::split(v, delim)) {
214 switch (field) {
215 case 0: // talker id
216 gsa.source = nmea::talker(std::string_view(word));
217 break;
218 case 3:
219 case 4:
220 case 5:
221 case 6:
222 case 7:
223 case 8:
224 case 9:
225 case 10:
226 case 11:
227 case 12:
228 case 13:
229 case 14:
230 if (std::string_view(word).length() == 0) {
231 gsa.sats[field - 3] = 0;
232 } else {
233 gsa.sats[field - 3] =
234 std::stoi(std::string(std::string_view(word)));
235 }
236 break;
237 case 15:
238 gsa.system_id = nmea::system(std::string_view(word));
239 break;
240 default: // skip 1,2, 16, 17
241 break;
242 }
243 field++;
244 }
245 return (field == 18); // everything parsed
246}
247
248// $GPGSV,3,1,11,13,79,310,,14,53,113,,05,51,214,,30,47,067,*72
249// $GPGSV,3,2,11,15,45,295,24,22,44,145,,20,27,192,,07,16,064,*7A
250// $GPGSV,3,3,11,18,16,298,25,24,08,249,,08,08,029,18,,,,*40
251// $GLGSV,2,1,08,72,79,113,,74,77,084,,75,38,202,,65,37,317,28*68
252// $GLGSV,2,2,08,73,34,040,35,71,28,130,,81,13,333,24,82,08,017,*68
253bool gsv::from_data(const std::string& data, gsv& gsv) {
254 unsigned int field = 0;
255 std::string_view v = std::string_view(data).substr(0, data.find('*'));
256 for (const auto word : std::views::split(v, delim)) {
257 switch (field) {
258 case 0: // talker id
259 gsv.source = nmea::talker(std::string_view(word));
260 break;
261 case 4:
262 case 8:
263 case 12:
264 case 16:
265 if (std::string_view(word).length() == 0) {
266 gsv.sats[(field - 4) / 4 ].prn = 0;
267 } else {
268 gsv.sats[(field - 4) / 4 ].prn =
269 std::stoi(std::string(std::string_view(word)));
270 }
271 break;
272 case 5:
273 case 9:
274 case 13:
275 case 17:
276 if (std::string_view(word).length() == 0) {
277 gsv.sats[(field - 5) / 4 ].elev = 0;
278 } else {
279 gsv.sats[(field - 5) / 4 ].elev =
280 std::stoi(std::string(std::string_view(word)));
281 }
282 break;
283 case 6:
284 case 10:
285 case 14:
286 case 18:
287 if (std::string_view(word).length() == 0) {
288 gsv.sats[(field - 6) / 4 ].azim = 0;
289 } else {
290 gsv.sats[(field - 6) / 4 ].azim =
291 std::stoi(std::string(std::string_view(word)));
292 }
293 break;
294 case 7:
295 case 11:
296 case 15:
297 case 19:
298 if (std::string_view(word).length() == 0) {
299 gsv.sats[(field - 7) / 4 ].snr = 0;
300 } else {
301 gsv.sats[(field - 7) / 4 ].snr =
302 std::stoi(std::string(std::string_view(word)));
303 }
304 break;
305 default: // skip 1, 2, 3
306 break;
307 }
308 field++;
309 }
310 return (field == 20); // everything parsed
311}
312
313// $GPRMC,185427.150,V,5051.83778,N,00422.55809,E,,,240724,,,N*7F
314bool rmc::from_data(const std::string& data, rmc& rmc) {
315 unsigned int field = 0;
316 for (const auto word : std::views::split(data, delim)) {
317 switch (field) {
318 case 0: // talker id
319 rmc.source = nmea::talker(std::string_view(word));
320 break;
321 case 1: // timestamp
322 nmea::time(std::string_view(word), rmc.t);
323 break;
324 case 2: // valid
325 rmc.valid = nmea::valid(std::string_view(word));
326 break;
327 case 3: // latitude
328 rmc.lat = nmea::coord(2, std::string_view(word));
329 break;
330 case 4: // latitude direction
331 if (nmea::dir(std::string_view(word)) == direction::s) {
332 rmc.lat = rmc.lat * -1;
333 }
334 break;
335 case 5: // longitude
336 rmc.lon = nmea::coord(3, std::string_view(word));
337 break;
338 case 6: // longitude direction
339 if (nmea::dir(std::string_view(word)) == direction::w) {
340 rmc.lon = rmc.lon * -1;
341 }
342 break;
343 case 9: // dqtestamp
344 nmea::date(std::string_view(word), rmc.d);
345 break;
346 default: // skip 7, 8, 10, 11, 12
347 break;
348 }
349 field++;
350 }
351 return (field == 13); // everything parsed
352}
353
354} // namespace nmea
float geosep
quality qual
static bool from_data(const std::string &data, gga &gga)
Definition nmea.cpp:160
talker_id source
unsigned int sats
talker_id source
static bool from_data(const std::string &data, gll &gll)
Definition nmea.cpp:121
talker_id source
talker_id system_id
gsa_sat_array sats
static bool from_data(const std::string &data, gsa &gsa)
Definition nmea.cpp:210
static bool from_data(const std::string &data, gsv &gsv)
Definition nmea.cpp:253
talker_id source
gsv_sat_array sats
static quality qual(const std::string_view &sv)
Definition nmea.cpp:113
static bool valid(const std::string_view &sv)
Definition nmea.cpp:107
static talker_id system(const std::string_view &sv)
Definition nmea.cpp:40
static direction dir(const std::string_view &sv)
Definition nmea.cpp:64
static void time(const std::string_view &sv, time_t &t)
Definition nmea.cpp:82
static float coord(const unsigned int degrees_chars, const std::string_view &sv)
Definition nmea.cpp:56
static talker_id talker(const std::string_view &sv)
Definition nmea.cpp:22
static void date(const std::string_view &sv, std::chrono::year_month_day &d)
Definition nmea.cpp:92
std::chrono::year_month_day d
talker_id source
static bool from_data(const std::string &data, rmc &rmc)
Definition nmea.cpp:314
Definition nmea.cpp:20
std::chrono::hh_mm_ss< std::chrono::duration< long long, std::ratio< 1, 1000 > > > time_t
constexpr auto delim
Definition nmea.cpp:18
#define CENTURY