OpenCPN Partial API docs
Loading...
Searching...
No Matches
comm_decoder.cpp
Go to the documentation of this file.
1/***************************************************************************
2 * Copyright (C) 2022 by David Register *
3 * Copyright (C) 2022 Alec Leamas *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, see <https://www.gnu.org/licenses/>. *
17 **************************************************************************/
18
25#include <wx/wxprec.h>
26#ifndef WX_PRECOMP
27#include <wx/wx.h>
28#endif
29
30#include <wx/log.h>
31#include <wx/math.h>
32#include <wx/string.h>
33
34#include "rapidjson/document.h"
35
36#include "model/comm_decoder.h"
37#include "model/comm_util.h"
38#include "model/comm_vars.h"
39#include "model/geodesic.h"
40#include "model/own_ship.h"
41
42bool CommDecoder::ParsePosition(const LATLONG& Position, double& lat,
43 double& lon) {
44 bool ll_valid = true;
45 double llt = Position.Latitude.Latitude;
46
47 if (!std::isnan(llt)) {
48 int lat_deg_int = (int)(llt / 100);
49 double lat_deg = lat_deg_int;
50 double lat_min = llt - (lat_deg * 100);
51
52 lat = lat_deg + (lat_min / 60.);
53 if (Position.Latitude.Northing == South) lat = -lat;
54 } else
55 ll_valid = false;
56
57 double lln = Position.Longitude.Longitude;
58 if (!std::isnan(lln)) {
59 int lon_deg_int = (int)(lln / 100);
60 double lon_deg = lon_deg_int;
61 double lon_min = lln - (lon_deg * 100);
62
63 lon = lon_deg + (lon_min / 60.);
64 if (Position.Longitude.Easting == West) lon = -lon;
65 } else
66 ll_valid = false;
67
68 return ll_valid;
69}
70
71bool CommDecoder::DecodeRMC(std::string s, NavData& temp_data) {
72 wxString sentence(s.c_str());
73 wxString sentence3 = ProcessNMEA4Tags(sentence);
74 m_NMEA0183 << sentence3;
75
76 if (!m_NMEA0183.PreParse()) return false;
77 if (!m_NMEA0183.Parse()) return false;
78
79 if (m_NMEA0183.Rmc.IsDataValid == NTrue) {
80 double tlat, tlon;
81 if (ParsePosition(m_NMEA0183.Rmc.Position, tlat, tlon)) {
82 temp_data.gLat = tlat;
83 temp_data.gLon = tlon;
84 } else
85 return false;
86
87 // FIXME (dave) if (!g_own_ship_sog_cog_calc )
88 {
89 if (!std::isnan(m_NMEA0183.Rmc.SpeedOverGroundKnots)) {
90 temp_data.gSog = m_NMEA0183.Rmc.SpeedOverGroundKnots;
91 }
92 if (!std::isnan(temp_data.gSog) && (temp_data.gSog > 0.05)) {
93 temp_data.gCog = m_NMEA0183.Rmc.TrackMadeGoodDegreesTrue;
94 } else {
95 temp_data.gCog = NAN;
96 }
97 }
98 // Any device sending VAR=0.0 can be assumed to not really know
99 // what the actual variation is, so in this case we use WMM if
100 // available
101 if ((!std::isnan(m_NMEA0183.Rmc.MagneticVariation)) &&
102 0.0 != m_NMEA0183.Rmc.MagneticVariation) {
103 if (m_NMEA0183.Rmc.MagneticVariationDirection == East)
104 temp_data.gVar = m_NMEA0183.Rmc.MagneticVariation;
105 else if (m_NMEA0183.Rmc.MagneticVariationDirection == West)
106 temp_data.gVar = -m_NMEA0183.Rmc.MagneticVariation;
107
108 g_bVAR_Rx = true;
109 }
110
111 gRmcTime = m_NMEA0183.Rmc.UTCTime;
112 gRmcDate = m_NMEA0183.Rmc.Date;
113 } else
114 return false;
115
116 return true;
117}
118
119bool CommDecoder::DecodeHDM(std::string s, NavData& temp_data) {
120 wxString sentence(s.c_str());
121 wxString sentence3 = ProcessNMEA4Tags(sentence);
122 m_NMEA0183 << sentence3;
123
124 if (!m_NMEA0183.PreParse()) return false;
125 if (!m_NMEA0183.Parse()) return false;
126
127 temp_data.gHdm = m_NMEA0183.Hdm.DegreesMagnetic;
128
129 return true;
130}
131
132bool CommDecoder::DecodeTHS(std::string s, NavData& temp_data) {
133 wxString sentence(s.c_str());
134 wxString sentence3 = ProcessNMEA4Tags(sentence);
135 m_NMEA0183 << sentence3;
136
137 if (!m_NMEA0183.PreParse()) return false;
138 if (!m_NMEA0183.Parse()) return false;
139
140 // Handle only valid data A = Autonomous
141 if (!(m_NMEA0183.Ths.ModeInd == "A")) return false;
142 temp_data.gHdt = m_NMEA0183.Ths.TrueHeading;
143
144 return true;
145}
146
147bool CommDecoder::DecodeHDT(std::string s, NavData& temp_data) {
148 wxString sentence(s.c_str());
149 wxString sentence3 = ProcessNMEA4Tags(sentence);
150 m_NMEA0183 << sentence3;
151
152 if (!m_NMEA0183.PreParse()) return false;
153 if (!m_NMEA0183.Parse()) return false;
154
155 temp_data.gHdt = m_NMEA0183.Hdt.DegreesTrue;
156
157 return true;
158}
159
160bool CommDecoder::DecodeHDG(std::string s, NavData& temp_data) {
161 wxString sentence(s.c_str());
162 wxString sentence3 = ProcessNMEA4Tags(sentence);
163 m_NMEA0183 << sentence3;
164
165 if (!m_NMEA0183.PreParse()) return false;
166 if (!m_NMEA0183.Parse()) return false;
167
168 temp_data.gHdm = m_NMEA0183.Hdg.MagneticSensorHeadingDegrees;
169
170 // Any device sending VAR=0.0 can be assumed to not really know
171 // what the actual variation is, so in this case we use WMM if
172 // available
173 if ((!std::isnan(m_NMEA0183.Hdg.MagneticVariationDegrees)) &&
174 0.0 != m_NMEA0183.Hdg.MagneticVariationDegrees) {
175 if (m_NMEA0183.Hdg.MagneticVariationDirection == East)
176 temp_data.gVar = m_NMEA0183.Hdg.MagneticVariationDegrees;
177 else if (m_NMEA0183.Hdg.MagneticVariationDirection == West)
178 temp_data.gVar = -m_NMEA0183.Hdg.MagneticVariationDegrees;
179
180 g_bVAR_Rx = true;
181 }
182
183 return true;
184}
185
186bool CommDecoder::DecodeHVD(std::string s, NavData& temp_data) {
187 wxString sentence(s.c_str());
188 wxString sentence3 = ProcessNMEA4Tags(sentence);
189 m_NMEA0183 << sentence3;
190
191 if (!m_NMEA0183.PreParse()) return false;
192 if (!m_NMEA0183.Parse()) return false;
193
194 // Any device sending VAR=0.0 can be assumed to not really know
195 // what the actual variation is, so in this case we use WMM if
196 // available
197 if ((!std::isnan(m_NMEA0183.Hvd.MagneticVariationDegrees)) &&
198 0.0 != m_NMEA0183.Hvd.MagneticVariationDegrees) {
199 if (m_NMEA0183.Hvd.MagneticVariationDirection == East)
200 temp_data.gVar = m_NMEA0183.Hvd.MagneticVariationDegrees;
201 else if (m_NMEA0183.Hvd.MagneticVariationDirection == West)
202 temp_data.gVar = -m_NMEA0183.Hvd.MagneticVariationDegrees;
203
204 g_bVAR_Rx = true;
205 }
206
207 return true;
208}
209
210bool CommDecoder::DecodeVTG(std::string s, NavData& temp_data) {
211 wxString sentence(s.c_str());
212 wxString sentence3 = ProcessNMEA4Tags(sentence);
213 m_NMEA0183 << sentence3;
214
215 if (!m_NMEA0183.PreParse()) return false;
216 if (!m_NMEA0183.Parse()) return false;
217
218 // FIXME (dave)if (g_own_ship_sog_cog_calc) return false;
219
220 if (!std::isnan(m_NMEA0183.Vtg.SpeedKnots))
221 temp_data.gSog = m_NMEA0183.Vtg.SpeedKnots;
222
223 if (!std::isnan(m_NMEA0183.Vtg.SpeedKnots) &&
224 !std::isnan(m_NMEA0183.Vtg.TrackDegreesTrue)) {
225 temp_data.gCog = m_NMEA0183.Vtg.TrackDegreesTrue;
226 }
227
228 // If COG-T is not available but COG-M is, then
229 // create COG-T from COG-M and gVar
230 if (!std::isnan(m_NMEA0183.Vtg.SpeedKnots) &&
231 !std::isnan(m_NMEA0183.Vtg.TrackDegreesMagnetic)) {
232 // establish gVar, if not already set
233 if (std::isnan(gVar) && (g_UserVar != 0.0)) gVar = g_UserVar;
234
235 double cogt = m_NMEA0183.Vtg.TrackDegreesMagnetic + gVar;
236 if (!std::isnan(cogt)) {
237 if (cogt < 0)
238 cogt += 360.0;
239 else if (cogt >= 360)
240 cogt -= 360.0;
241 temp_data.gCog = cogt;
242 }
243 }
244 return true;
245}
246
247bool CommDecoder::DecodeGLL(std::string s, NavData& temp_data) {
248 wxString sentence(s.c_str());
249 wxString sentence3 = ProcessNMEA4Tags(sentence);
250 m_NMEA0183 << sentence3;
251
252 if (!m_NMEA0183.PreParse()) return false;
253 if (!m_NMEA0183.Parse()) return false;
254
255 if (m_NMEA0183.Gll.IsDataValid == NTrue) {
256 double tlat, tlon;
257 if (ParsePosition(m_NMEA0183.Gll.Position, tlat, tlon)) {
258 temp_data.gLat = tlat;
259 temp_data.gLon = tlon;
260 } else
261 return false;
262 } else
263 return false;
264
265 return true;
266}
267
268bool CommDecoder::DecodeGSV(std::string s, NavData& temp_data) {
269 wxString sentence(s.c_str());
270 wxString sentence3 = ProcessNMEA4Tags(sentence);
271 m_NMEA0183 << sentence3;
272
273 if (!m_NMEA0183.PreParse()) return false;
274 if (!m_NMEA0183.Parse()) return false;
275
276 if (m_NMEA0183.Gsv.MessageNumber == 1)
277 temp_data.n_satellites = m_NMEA0183.Gsv.SatsInView;
278
279 return true;
280}
281
282bool CommDecoder::DecodeGGA(std::string s, NavData& temp_data) {
283 wxString sentence(s.c_str());
284 wxString sentence3 = ProcessNMEA4Tags(sentence);
285 m_NMEA0183 << sentence3;
286
287 if (!m_NMEA0183.PreParse()) return false;
288 if (!m_NMEA0183.Parse()) return false;
289
290 if (m_NMEA0183.Gga.GPSQuality > 0) {
291 double tlat, tlon;
292 if (ParsePosition(m_NMEA0183.Gga.Position, tlat, tlon)) {
293 temp_data.gLat = tlat;
294 temp_data.gLon = tlon;
295 } else
296 return false;
297
298 temp_data.n_satellites = m_NMEA0183.Gga.NumberOfSatellitesInUse;
299
300 } else
301 return false;
302
303 return true;
304}
305
306//---------------------------------------------------------------------
307// NMEA2000 PGN Decode
308//---------------------------------------------------------------------
309
310bool CommDecoder::DecodePGN129026(std::vector<unsigned char> v,
311 NavData& temp_data) {
312 unsigned char SID;
313 tN2kHeadingReference ref;
314 double COG, SOG;
315
316 if (ParseN2kPGN129026(v, SID, ref, COG, SOG)) {
317 temp_data.gCog = COG;
318 temp_data.gSog = SOG;
319 temp_data.SID = SID;
320 return true;
321 }
322
323 return false;
324}
325
326bool CommDecoder::DecodePGN129029(std::vector<unsigned char> v,
327 NavData& temp_data) {
328 unsigned char SID;
329 uint16_t DaysSince1970;
330 double SecondsSinceMidnight;
331 double Latitude, Longitude, Altitude;
332 tN2kGNSStype GNSStype;
333 tN2kGNSSmethod GNSSmethod;
334 unsigned char nSatellites;
335 double HDOP, PDOP, GeoidalSeparation;
336 unsigned char nReferenceStations;
337 tN2kGNSStype ReferenceStationType;
338 uint16_t ReferenceSationID;
339 double AgeOfCorrection;
340
341 if (ParseN2kPGN129029(v, SID, DaysSince1970, SecondsSinceMidnight, Latitude,
342 Longitude, Altitude, GNSStype, GNSSmethod, nSatellites,
343 HDOP, PDOP, GeoidalSeparation, nReferenceStations,
344 ReferenceStationType, ReferenceSationID,
345 AgeOfCorrection)) {
346 temp_data.gLat = Latitude;
347 temp_data.gLon = Longitude;
348 temp_data.SID = SID;
349
350 // Some devices produce "0" satelites for PGN 129029, even with a vaild fix
351 // One supposes that PGN 129540 should be used instead
352 // Here we decide that if a fix is valid, nSatellites must be > 0 to be
353 // reported in this PGN 129029
354 if ((GNSSmethod == N2kGNSSm_GNSSfix) || (GNSSmethod == N2kGNSSm_DGNSS) ||
355 (GNSSmethod == N2kGNSSm_PreciseGNSS)) {
356 if (nSatellites > 0) temp_data.n_satellites = nSatellites;
357 }
358
359 return true;
360 }
361
362 return false;
363}
364
365bool CommDecoder::DecodePGN127250(std::vector<unsigned char> v,
366 NavData& temp_data) {
367 unsigned char SID;
368 double Heading, Deviation, Variation;
369 tN2kHeadingReference ref;
370
371 if (ParseN2kPGN127250(v, SID, Heading, Deviation, Variation, ref)) {
372 temp_data.gHdt = N2kDoubleNA;
373 temp_data.gHdm = N2kDoubleNA;
374 if (ref == tN2kHeadingReference::N2khr_true)
375 temp_data.gHdt = Heading;
376 else if (ref == tN2kHeadingReference::N2khr_magnetic)
377 temp_data.gHdm = Heading;
378
379 temp_data.gVar = Variation;
380 temp_data.SID = SID;
381 return true;
382 }
383
384 return false;
385}
386
387bool CommDecoder::DecodePGN127258(std::vector<unsigned char> v,
388 NavData& temp_data) {
389 unsigned char SID;
390 tN2kMagneticVariation Source;
391 uint16_t DaysSince1970;
392 double Variation;
393 tN2kHeadingReference ref;
394
395 if (ParseN2kPGN127258(v, SID, Source, DaysSince1970, Variation)) {
396 temp_data.gVar = Variation;
397 temp_data.SID = SID;
398 g_bVAR_Rx = true;
399 return true;
400 }
401
402 return false;
403}
404
405bool CommDecoder::DecodePGN129025(std::vector<unsigned char> v,
406 NavData& temp_data) {
407 double Latitude, Longitude;
408
409 if (ParseN2kPGN129025(v, Latitude, Longitude)) {
410 temp_data.gLat = Latitude;
411 temp_data.gLon = Longitude;
412 return true;
413 }
414
415 return false;
416}
417
418bool CommDecoder::DecodePGN129540(std::vector<unsigned char> v,
419 NavData& temp_data) {
420 unsigned char SID;
421 uint8_t NumberOfSVs;
422 ;
423 tN2kRangeResidualMode Mode;
424
425 if (ParseN2kPGN129540(v, SID, Mode, NumberOfSVs)) {
426 temp_data.n_satellites = NumberOfSVs;
427 temp_data.SID = SID;
428 return true;
429 }
430
431 return false;
432}
433
434bool CommDecoder::DecodeSignalK(std::string s, NavData& temp_data) {
435 rapidjson::Document root;
436
437 root.Parse(s);
438 if (root.HasParseError()) return false;
439
440 if (root.HasMember("updates") && root["updates"].IsArray()) {
441 for (rapidjson::Value::ConstValueIterator itr = root["updates"].Begin();
442 itr != root["updates"].End(); ++itr) {
443 handleUpdate(*itr, temp_data);
444 }
445 }
446
447 return true;
448}
449
450void CommDecoder::handleUpdate(const rapidjson::Value& update,
451 NavData& temp_data) {
452 wxString sfixtime = "";
453
454 if (update.HasMember("timestamp")) {
455 sfixtime = update["timestamp"].GetString();
456 }
457 if (update.HasMember("source") && update["source"].HasMember("src")) {
458 src_string = update["source"]["src"].GetString();
459 }
460
461 if (update.HasMember("values") && update["values"].IsArray()) {
462 for (rapidjson::Value::ConstValueIterator itr = update["values"].Begin();
463 itr != update["values"].End(); ++itr) {
464 updateItem(*itr, sfixtime, temp_data);
465 }
466 }
467}
468
469void CommDecoder::updateItem(const rapidjson::Value& item, wxString& sfixtime,
470 NavData& temp_data) {
471 bool bposValid = false;
472 if (item.HasMember("path") && item.HasMember("value")) {
473 const wxString& update_path = item["path"].GetString();
474
475 if (update_path == "navigation.gnss.methodQuality") {
476 // Record statically the GNSS status for this source in a hashmap
477 if (src_string.size()) {
478 if (item["value"] == "no GPS") { // no GPS GNSS Fix
479 GNSS_quality_map[src_string] = 0;
480 } else {
481 GNSS_quality_map[src_string] = 1;
482 }
483 }
484 }
485
486 if (update_path == "navigation.position" && !item["value"].IsNull()) {
487 bposValid = updateNavigationPosition(item["value"], sfixtime, temp_data);
488
489 // if "gnss.methodQuality" is reported as "no GPS", then invalidate gLat
490 // This will flow upstream, eventually triggering the GPS watchdog
491 if (GNSS_quality_map.find(src_string) != GNSS_quality_map.end()) {
492 if (GNSS_quality_map[src_string] == 0) {
493 temp_data.gLat = NAN;
494 }
495 }
496
497 } else if (update_path == "navigation.speedOverGround" &&
498 /*bposValid &&*/ !item["value"].IsNull()) {
499 updateNavigationSpeedOverGround(item["value"], sfixtime, temp_data);
500
501 // If the tracked "methodQuality" exists for this source,
502 // and state was recorded as "no GPS", set SOG = 0
503 if (GNSS_quality_map.find(src_string) != GNSS_quality_map.end()) {
504 if (GNSS_quality_map[src_string] == 0) {
505 temp_data.gSog = 0;
506 }
507 }
508
509 } else if (update_path == "navigation.courseOverGroundTrue" &&
510 /*bposValid &&*/ !item["value"].IsNull()) {
511 updateNavigationCourseOverGround(item["value"], sfixtime, temp_data);
512 } else if (update_path == "navigation.courseOverGroundMagnetic") {
513 } else if (update_path ==
514 "navigation.gnss.satellites") // From GGA sats in use
515 {
516 updateGnssSatellites(item["value"], sfixtime, temp_data);
517 } else if (update_path ==
518 "navigation.gnss.satellitesInView") // From GSV sats in view
519 {
520 updateGnssSatellites(item["value"], sfixtime, temp_data);
521 } else if (update_path == "navigation.headingTrue") {
522 if (!item["value"].IsNull())
523 updateHeadingTrue(item["value"], sfixtime, temp_data);
524 } else if (update_path == "navigation.headingMagnetic") {
525 if (!item["value"].IsNull())
526 updateHeadingMagnetic(item["value"], sfixtime, temp_data);
527 } else if (update_path == "navigation.magneticVariation") {
528 if (!item["value"].IsNull())
529 updateMagneticVariance(item["value"], sfixtime, temp_data);
530 } else {
531 // wxLogMessage(wxString::Format("** Signal K unhandled update: %s",
532 // update_path));
533 }
534 }
535}
536
537bool CommDecoder::updateNavigationPosition(const rapidjson::Value& value,
538 const wxString& sfixtime,
539 NavData& temp_data) {
540 if ((value.HasMember("latitude") && value["latitude"].IsDouble()) &&
541 (value.HasMember("longitude") && value["longitude"].IsDouble())) {
542 // wxLogMessage(" ***** Position Update");
543 temp_data.gLat = value["latitude"].GetDouble();
544 temp_data.gLon = value["longitude"].GetDouble();
545 return true;
546 } else {
547 return false;
548 }
549}
550
551void CommDecoder::updateNavigationSpeedOverGround(const rapidjson::Value& value,
552 const wxString& sfixtime,
553 NavData& temp_data) {
554 double sog_ms = value.GetDouble();
555 double sog_knot = sog_ms * 1.9438444924406; // m/s to knots
556 // wxLogMessage(wxString::Format(" ***** SOG: %f, %f", sog_ms, sog_knot));
557 temp_data.gSog = sog_knot;
558}
559
560void CommDecoder::updateNavigationCourseOverGround(
561 const rapidjson::Value& value, const wxString& sfixtime,
562 NavData& temp_data) {
563 double cog_rad = value.GetDouble();
564 double cog_deg = GEODESIC_RAD2DEG(cog_rad);
565 // wxLogMessage(wxString::Format(" ***** COG: %f, %f", cog_rad, cog_deg));
566 temp_data.gCog = cog_deg;
567}
568
569void CommDecoder::updateGnssSatellites(const rapidjson::Value& value,
570 const wxString& sfixtime,
571 NavData& temp_data) {
572 if (value.IsInt()) {
573 if (value.GetInt() > 0) {
574 temp_data.n_satellites = value.GetInt();
575 }
576 } else if ((value.HasMember("count") && value["count"].IsInt())) {
577 temp_data.n_satellites = value["count"].GetInt();
578 }
579 // If "gnss.methodQuality" is "no GPS", then clear the satellite count
580 if (GNSS_quality_map.find(src_string) != GNSS_quality_map.end()) {
581 if (GNSS_quality_map[src_string] == 0) {
582 temp_data.n_satellites = 0;
583 }
584 }
585}
586
587void CommDecoder::updateHeadingTrue(const rapidjson::Value& value,
588 const wxString& sfixtime,
589 NavData& temp_data) {
590 temp_data.gHdt = GEODESIC_RAD2DEG(value.GetDouble());
591}
592
593void CommDecoder::updateHeadingMagnetic(const rapidjson::Value& value,
594 const wxString& sfixtime,
595 NavData& temp_data) {
596 temp_data.gHdm = GEODESIC_RAD2DEG(value.GetDouble());
597}
598
599void CommDecoder::updateMagneticVariance(const rapidjson::Value& value,
600 const wxString& sfixtime,
601 NavData& temp_data) {
602 temp_data.gVar = GEODESIC_RAD2DEG(value.GetDouble());
603}
Incoming messages decoding support.
wxString ProcessNMEA4Tags(const wxString &msg)
Strip NMEA V4 tag blocks from NMEA0183 message.
Misc driver utilities.
Variables maintained by comm stack, read-only access for others.
double gVar
Magnetic variation in degrees.
Definition own_ship.cpp:32
Position, course, speed, etc.