QGIS API Documentation 3.99.0-Master (e9821da5c6b)
Loading...
Searching...
No Matches
qgsnmeaconnection.cpp
Go to the documentation of this file.
1
2/***************************************************************************
3 qgsnmeaconnection.cpp - description
4 ---------------------
5 begin : November 30th, 2009
6 copyright : (C) 2009 by Marco Hugentobler
7 email : marco at hugis dot net
8 ***************************************************************************/
9
10/***************************************************************************
11 * *
12 * This program is free software; you can redistribute it and/or modify *
13 * it under the terms of the GNU General Public License as published by *
14 * the Free Software Foundation; either version 2 of the License, or *
15 * (at your option) any later version. *
16 * *
17 ***************************************************************************/
18
19#include "qgsnmeaconnection.h"
20
21#include "qgslogger.h"
22
23#include <QApplication>
24#include <QIODevice>
25#include <QRegularExpression>
26#include <QString>
27#include <QStringList>
28
29#include "moc_qgsnmeaconnection.cpp"
30
31using namespace Qt::StringLiterals;
32
33//from libnmea
34#include "parse.h"
35#include "gmath.h"
36#include "info.h"
37
38// for sqrt
39#include <math.h>
40
41#define KNOTS_TO_KMH 1.852
42
44 : QgsGpsConnection( device )
45{
46}
47
49{
50 if ( !mSource )
51 {
52 return;
53 }
54
55 //print out the data as a test
56 qint64 numBytes = 0;
57 if ( ! mSource->isSequential() ) //necessary because of a bug in QExtSerialPort //SLM - bytesAvailable() works on Windows, so I reversed the logic (added ! ); this is what QIODevice docs say to do; the orig impl of win_qextserialport had an (unsigned int)-1 return on error - it should be (qint64)-1, which was fixed by ?
58 {
59 numBytes = mSource->size();
60 }
61 else
62 {
63 numBytes = mSource->bytesAvailable();
64 }
65
66 QgsDebugMsgLevel( "numBytes:" + QString::number( numBytes ), 2 );
67
68 if ( numBytes >= 6 )
69 {
70 QgsDebugMsgLevel( u"Got %1 NMEA bytes"_s.arg( numBytes ), 3 );
71 QgsDebugMsgLevel( u"Current NMEA device status is %1"_s.arg( mStatus ), 3 );
72 if ( mStatus != GPSDataReceived )
73 {
74 QgsDebugMsgLevel( u"Setting device status to DataReceived"_s, 3 );
76 }
77
78 //append new data to the remaining results from last parseData() call
79 mStringBuffer.append( mSource->read( numBytes ) );
81 QgsDebugMsgLevel( u"Processed buffer"_s, 3 );
82
83 QgsDebugMsgLevel( u"New status is %1"_s.arg( mStatus ), 3 );
84 if ( mStatus == GPSDataReceived )
85 {
87 }
88 }
89}
90
92{
93 int endSentenceIndex = 0;
94 int dollarIndex;
95
96 while ( ( endSentenceIndex = mStringBuffer.indexOf( "\r\n"_L1 ) ) && endSentenceIndex != -1 )
97 {
98 endSentenceIndex = mStringBuffer.indexOf( "\r\n"_L1 );
99
100 dollarIndex = mStringBuffer.indexOf( '$'_L1 );
101 if ( endSentenceIndex == -1 )
102 {
103 break;
104 }
105
106 if ( endSentenceIndex >= dollarIndex )
107 {
108 if ( dollarIndex != -1 )
109 {
110 const QString substring = mStringBuffer.mid( dollarIndex, endSentenceIndex );
111 QByteArray ba = substring.toLocal8Bit();
112 const thread_local QRegularExpression rxSentence( u"^\\$([A-Z]{2})([A-Z]{3})"_s );
113 const QRegularExpressionMatch sentenceMatch = rxSentence.match( substring );
114 const QString sentenceId = sentenceMatch.captured( 2 );
115 if ( sentenceId == "GGA"_L1 )
116 {
117 QgsDebugMsgLevel( substring, 2 );
118 mLastGPSInformation.satInfoComplete = true;
119 processGgaSentence( ba.data(), ba.length() );
121 QgsDebugMsgLevel( u"*******************GPS data received****************"_s, 2 );
122 }
123 else if ( sentenceId == "RMC"_L1 )
124 {
125 QgsDebugMsgLevel( substring, 2 );
126 mLastGPSInformation.satInfoComplete = true;
127 processRmcSentence( ba.data(), ba.length() );
129 QgsDebugMsgLevel( u"*******************GPS data received****************"_s, 2 );
130 }
131 else if ( sentenceId == "GSV"_L1 )
132 {
133 QgsDebugMsgLevel( substring, 2 );
134 mLastGPSInformation.satInfoComplete = false;
135 processGsvSentence( ba.data(), ba.length() );
137 QgsDebugMsgLevel( u"*******************GPS data received****************"_s, 2 );
138 }
139 else if ( sentenceId == "VTG"_L1 )
140 {
141 QgsDebugMsgLevel( substring, 2 );
142 mLastGPSInformation.satInfoComplete = true;
143 processVtgSentence( ba.data(), ba.length() );
145 QgsDebugMsgLevel( u"*******************GPS data received****************"_s, 2 );
146 }
147 else if ( sentenceId == "GSA"_L1 )
148 {
149 QgsDebugMsgLevel( substring, 2 );
150 processGsaSentence( ba.data(), ba.length() );
152 QgsDebugMsgLevel( u"*******************GPS data received****************"_s, 2 );
153 }
154 else if ( sentenceId == "GST"_L1 )
155 {
156 QgsDebugMsgLevel( substring, 2 );
157 mLastGPSInformation.satInfoComplete = true;
158 processGstSentence( ba.data(), ba.length() );
160 QgsDebugMsgLevel( u"*******************GPS data received****************"_s, 2 );
161 }
162 else if ( sentenceId == "HDT"_L1 )
163 {
164 QgsDebugMsgLevel( substring, 2 );
165 mLastGPSInformation.satInfoComplete = true;
166 processHdtSentence( ba.data(), ba.length() );
168 QgsDebugMsgLevel( u"*******************GPS data received****************"_s, 2 );
169 }
170 else if ( sentenceId == "HDG"_L1 )
171 {
172 QgsDebugMsgLevel( substring, 2 );
173 mLastGPSInformation.satInfoComplete = true;
174 processHchdgSentence( ba.data(), ba.length() );
176 QgsDebugMsgLevel( u"*******************GPS data received****************"_s, 2 );
177 }
178 else
179 {
180 mLastGPSInformation.satInfoComplete = true;
181 QgsDebugMsgLevel( u"unknown nmea sentence: %1"_s.arg( substring ), 2 );
182 }
183 emit nmeaSentenceReceived( substring ); // added to be able to save raw data
184 }
185 else
186 {
187 //other text strings that do not start with '$'
188 mLastGPSInformation.satInfoComplete = true;
189 }
190 }
191 mStringBuffer.remove( 0, endSentenceIndex + 2 );
192 }
193}
194
195void QgsNmeaConnection::processGgaSentence( const char *data, int len )
196{
197 nmeaGPGGA result;
198 if ( nmea_parse_GPGGA( data, len, &result ) )
199 {
200 //update mLastGPSInformation
201 double longitude = result.lon;
202 if ( result.ew == 'W' )
203 {
204 longitude = -longitude;
205 }
206 double latitude = result.lat;
207 if ( result.ns == 'S' )
208 {
209 latitude = -latitude;
210 }
211
212 mLastGPSInformation.longitude = nmea_ndeg2degree( longitude );
213 mLastGPSInformation.latitude = nmea_ndeg2degree( latitude );
214 mLastGPSInformation.elevation = result.elv;
215 mLastGPSInformation.elevation_diff = result.diff;
216
217 const QTime time( result.utc.hour, result.utc.min, result.utc.sec, result.utc.msec );
218 if ( time.isValid() )
219 {
220 mLastGPSInformation.utcTime = time;
221 if ( mLastGPSInformation.utcDateTime.isValid() )
222 {
223 mLastGPSInformation.utcDateTime.setTimeSpec( Qt::UTC );
224 mLastGPSInformation.utcDateTime.setTime( time );
225 }
226 QgsDebugMsgLevel( u"utc time:"_s, 2 );
227 QgsDebugMsgLevel( mLastGPSInformation.utcTime.toString(), 2 );
228 }
229
230 mLastGPSInformation.quality = result.sig;
231 if ( result.sig >= 0 && result.sig <= 8 )
232 {
233 mLastGPSInformation.qualityIndicator = static_cast<Qgis::GpsQualityIndicator>( result.sig );
234 }
235 else
236 {
238 }
239
240 // use GSA for satellites in use;
241 }
242}
243
244void QgsNmeaConnection::processGstSentence( const char *data, int len )
245{
246 nmeaGPGST result;
247 if ( nmea_parse_GPGST( data, len, &result ) )
248 {
249 //update mLastGPSInformation
250 const double sig_lat = result.sig_lat;
251 const double sig_lon = result.sig_lon;
252 const double sig_alt = result.sig_alt;
253
254 // Horizontal RMS
255 mLastGPSInformation.hacc = sqrt( ( pow( sig_lat, 2 ) + pow( sig_lon, 2 ) ) / 2.0 );
256 // Vertical RMS
257 mLastGPSInformation.vacc = sig_alt;
258 // 3D RMS
259 mLastGPSInformation.hvacc = sqrt( ( pow( sig_lat, 2 ) + pow( sig_lon, 2 ) + pow( sig_alt, 2 ) ) / 3.0 );
260 }
261}
262
263void QgsNmeaConnection::processHdtSentence( const char *data, int len )
264{
265 nmeaGPHDT result;
266 if ( nmea_parse_GPHDT( data, len, &result ) )
267 {
268 mLastGPSInformation.direction = result.heading;
269 }
270}
271
272void QgsNmeaConnection::processHchdgSentence( const char *data, int len )
273{
274 nmeaHCHDG result;
275 if ( nmea_parse_HCHDG( data, len, &result ) )
276 {
277 mLastGPSInformation.direction = result.mag_heading;
278 if ( result.ew_variation == 'E' )
279 mLastGPSInformation.direction += result.mag_variation;
280 else
281 mLastGPSInformation.direction -= result.mag_variation;
282 }
283}
284
285void QgsNmeaConnection::processRmcSentence( const char *data, int len )
286{
287 nmeaGPRMC result;
288 if ( nmea_parse_GPRMC( data, len, &result ) )
289 {
290 double longitude = result.lon;
291 if ( result.ew == 'W' )
292 {
293 longitude = -longitude;
294 }
295 double latitude = result.lat;
296 if ( result.ns == 'S' )
297 {
298 latitude = -latitude;
299 }
300 mLastGPSInformation.longitude = nmea_ndeg2degree( longitude );
301 mLastGPSInformation.latitude = nmea_ndeg2degree( latitude );
302 mLastGPSInformation.speed = KNOTS_TO_KMH * result.speed;
303 if ( !std::isnan( result.direction ) )
304 mLastGPSInformation.direction = result.direction;
305 mLastGPSInformation.status = result.status; // A,V
306
307 const QDate date( result.utc.year + 1900, result.utc.mon + 1, result.utc.day );
308 const QTime time( result.utc.hour, result.utc.min, result.utc.sec, result.utc.msec );
309 if ( date.isValid() && time.isValid() )
310 {
311 mLastGPSInformation.utcTime = time;
312 mLastGPSInformation.utcDateTime.setTimeSpec( Qt::UTC );
313 mLastGPSInformation.utcDateTime.setDate( date );
314 mLastGPSInformation.utcDateTime.setTime( time );
315 QgsDebugMsgLevel( u"utc date/time:"_s, 2 );
316 QgsDebugMsgLevel( mLastGPSInformation.utcDateTime.toString(), 2 );
317 QgsDebugMsgLevel( u"local date/time:"_s, 2 );
318 QgsDebugMsgLevel( mLastGPSInformation.utcDateTime.toLocalTime().toString(), 2 );
319 }
320
321 // convert mode to signal (aka quality) indicator
322 // (see https://gitlab.com/fhuberts/nmealib/-/blob/master/src/info.c#L27)
323 // UM98x Status == D (Differential)
324 if ( result.status == 'A' || result.status == 'D' )
325 {
326 if ( result.mode == 'A' )
327 {
328 mLastGPSInformation.quality = static_cast<int>( Qgis::GpsQualityIndicator::GPS );
330 }
331 else if ( result.mode == 'D' )
332 {
333 mLastGPSInformation.quality = static_cast<int>( Qgis::GpsQualityIndicator::DGPS );
335 }
336 else if ( result.mode == 'P' )
337 {
338 mLastGPSInformation.quality = static_cast<int>( Qgis::GpsQualityIndicator::PPS );
340 }
341 else if ( result.mode == 'R' )
342 {
343 mLastGPSInformation.quality = static_cast<int>( Qgis::GpsQualityIndicator::RTK );
345 }
346 else if ( result.mode == 'F' )
347 {
348 mLastGPSInformation.quality = static_cast<int>( Qgis::GpsQualityIndicator::FloatRTK );
350 }
351 else if ( result.mode == 'E' )
352 {
353 mLastGPSInformation.quality = static_cast<int>( Qgis::GpsQualityIndicator::Estimated );
355 }
356 else if ( result.mode == 'M' )
357 {
358 mLastGPSInformation.quality = static_cast<int>( Qgis::GpsQualityIndicator::Manual );
360 }
361 else if ( result.mode == 'S' )
362 {
365 }
366 else
367 {
368 mLastGPSInformation.quality = static_cast<int>( Qgis::GpsQualityIndicator::Unknown );
370 }
371 }
372 else if ( result.status == 'V' )
373 {
374 mLastGPSInformation.quality = static_cast<int>( Qgis::GpsQualityIndicator::Invalid );
376 }
377 // for other cases: quality and qualityIndicator read by GGA
378 }
379
380 if ( result.navstatus == 'S' )
381 {
383 }
384 else if ( result.navstatus == 'C' )
385 {
387 }
388 else if ( result.navstatus == 'U' )
389 {
391 }
392 else
393 {
395 }
396}
397
398void QgsNmeaConnection::processGsvSentence( const char *data, int len )
399{
400 nmeaGPGSV result;
401 if ( nmea_parse_GPGSV( data, len, &result ) )
402 {
403 // for determining when to graph sat info
404 for ( int i = 0; i < NMEA_SATINPACK; ++i )
405 {
406 const nmeaSATELLITE currentSatellite = result.sat_data[i];
407 QgsSatelliteInfo satelliteInfo;
408 satelliteInfo.azimuth = currentSatellite.azimuth;
409 satelliteInfo.elevation = currentSatellite.elv;
410 satelliteInfo.id = currentSatellite.id;
411 satelliteInfo.inUse = false;
412 for ( int k = 0; k < mLastGPSInformation.satPrn.size(); ++k )
413 {
414 if ( mLastGPSInformation.satPrn.at( k ) == currentSatellite.id )
415 {
416 satelliteInfo.inUse = true;
417 }
418 }
419 satelliteInfo.signal = currentSatellite.sig;
420 satelliteInfo.satType = result.talkerId[1];
421
422 if ( result.talkerId[0] == 'G' )
423 {
424 if ( result.talkerId[1] == 'P' )
425 {
426 satelliteInfo.mConstellation = Qgis::GnssConstellation::Gps;
427 }
428 else if ( result.talkerId[1] == 'L' )
429 {
430 satelliteInfo.mConstellation = Qgis::GnssConstellation::Glonass;
431 }
432 else if ( result.talkerId[1] == 'A' )
433 {
434 satelliteInfo.mConstellation = Qgis::GnssConstellation::Galileo;
435 }
436 else if ( result.talkerId[1] == 'B' )
437 {
438 satelliteInfo.mConstellation = Qgis::GnssConstellation::BeiDou;
439 }
440 else if ( result.talkerId[1] == 'Q' )
441 {
442 satelliteInfo.mConstellation = Qgis::GnssConstellation::Qzss;
443 }
444 }
445
446 if ( satelliteInfo.satType == 'P' && satelliteInfo.id > 32 )
447 {
448 satelliteInfo.mConstellation = Qgis::GnssConstellation::Sbas;
449 satelliteInfo.satType = 'S';
450 satelliteInfo.id = currentSatellite.id + 87;
451 }
452
453 bool idAlreadyPresent = false;
454 if ( mLastGPSInformation.satellitesInView.size() > NMEA_SATINPACK )
455 {
456 for ( int i = 0; i < mLastGPSInformation.satellitesInView.size(); ++i )
457 {
458 QgsSatelliteInfo &existingSatInView = mLastGPSInformation.satellitesInView[i];
459 if ( existingSatInView.id == currentSatellite.id )
460 {
461 idAlreadyPresent = true;
462 // Signal averaging
463 if ( existingSatInView.signal == 0 )
464 {
465 existingSatInView.signal = currentSatellite.sig;
466 }
467 else if ( currentSatellite.sig != 0 )
468 {
469 existingSatInView.signal = ( existingSatInView.signal + currentSatellite.sig ) / 2;
470 }
471 break;
472 }
473 }
474 }
475
476 if ( !idAlreadyPresent && currentSatellite.azimuth > 0 && currentSatellite.elv > 0 )
477 {
478 mLastGPSInformation.satellitesInView.append( satelliteInfo );
479 }
480 }
481
482 }
483}
484
485void QgsNmeaConnection::processVtgSentence( const char *data, int len )
486{
487 nmeaGPVTG result;
488 if ( nmea_parse_GPVTG( data, len, &result ) )
489 {
490 mLastGPSInformation.speed = result.spk;
491 if ( !std::isnan( result.dir ) )
492 mLastGPSInformation.direction = result.dir;
493 }
494}
495
496void QgsNmeaConnection::processGsaSentence( const char *data, int len )
497{
498 if ( mLastGPSInformation.satInfoComplete )
499 {
500 //clear satellite information when a new series of packs arrives
501 mLastGPSInformation.satPrn.clear();
502 mLastGPSInformation.satellitesInView.clear();
503 mLastGPSInformation.satellitesUsed = 0;
504 mLastGPSInformation.satInfoComplete = false;
505 }
506 nmeaGPGSA result;
507 if ( nmea_parse_GPGSA( data, len, &result ) )
508 {
509 // clear() on GGA
510 mLastGPSInformation.hdop = result.HDOP;
511 mLastGPSInformation.pdop = result.PDOP;
512 mLastGPSInformation.vdop = result.VDOP;
513 mLastGPSInformation.fixMode = result.fix_mode;
514 mLastGPSInformation.fixType = result.fix_type;
515
517 bool mixedConstellation = false;
518 for ( int i = 0; i < NMEA_MAXSAT; i++ )
519 {
520 if ( result.sat_prn[ i ] > 0 )
521 {
522 mLastGPSInformation.satPrn.append( result.sat_prn[ i ] );
523 mLastGPSInformation.satellitesUsed += 1;
524
526 if ( ( result.talkerId[0] == 'G' && result.talkerId[1] == 'L' ) || result.sat_prn[i] > 64 )
527 constellation = Qgis::GnssConstellation::Glonass;
528 else if ( result.sat_prn[i] >= 1 && result.sat_prn[i] <= 32 )
529 constellation = Qgis::GnssConstellation::Gps;
530 else if ( result.sat_prn[i] > 32 && result.sat_prn[i] <= 64 )
531 constellation = Qgis::GnssConstellation::Sbas;
532
533 // cppcheck-suppress identicalInnerCondition
534 if ( result.sat_prn[i] > 0 )
535 {
536 if ( mixedConstellation
537 || ( commonConstellation != Qgis::GnssConstellation::Unknown
538 && commonConstellation != constellation ) )
539 {
540 mixedConstellation = true;
541 }
542 else
543 {
544 commonConstellation = constellation;
545 }
546 }
547 }
548 }
549 if ( mixedConstellation )
550 commonConstellation = Qgis::GnssConstellation::Unknown;
551
552 switch ( result.fix_type )
553 {
554 case 1:
555 mLastGPSInformation.mConstellationFixStatus[ commonConstellation ] = Qgis::GpsFixStatus::NoFix;
556 break;
557
558 case 2:
559 mLastGPSInformation.mConstellationFixStatus[ commonConstellation ] = Qgis::GpsFixStatus::Fix2D;
560 break;
561
562 case 3:
563 mLastGPSInformation.mConstellationFixStatus[ commonConstellation ] = Qgis::GpsFixStatus::Fix3D;
564 break;
565 }
566 }
567}
GnssConstellation
GNSS constellation.
Definition qgis.h:1962
@ Gps
Global Positioning System (GPS).
Definition qgis.h:1964
@ Glonass
Global Navigation Satellite System (GLONASS).
Definition qgis.h:1965
@ Unknown
Unknown/other system.
Definition qgis.h:1963
@ Qzss
Quasi Zenith Satellite System (QZSS).
Definition qgis.h:1968
GpsQualityIndicator
GPS signal quality indicator.
Definition qgis.h:1980
@ RTK
Real-time-kynematic.
Definition qgis.h:1986
@ DGPS
Differential GPS.
Definition qgis.h:1984
@ Estimated
Estimated.
Definition qgis.h:1988
@ Simulation
Simulation mode.
Definition qgis.h:1990
@ FloatRTK
Float real-time-kynematic.
Definition qgis.h:1987
@ Manual
Manual input mode.
Definition qgis.h:1989
@ NoFix
GPS is not fixed.
Definition qgis.h:1949
@ Fix2D
2D fix
Definition qgis.h:1950
@ Fix3D
3D fix
Definition qgis.h:1951
@ NotValid
Navigation status not valid.
Definition qgis.h:2001
QgsGpsInformation mLastGPSInformation
Last state of the gps related variables (e.g. position, time, ...).
void nmeaSentenceReceived(const QString &substring)
Emitted whenever the GPS device receives a raw NMEA sentence.
std::unique_ptr< QIODevice > mSource
Data source (e.g. serial device, socket, file,...).
QgsGpsConnection(QIODevice *dev)
Constructor.
Status mStatus
Connection status.
void stateChanged(const QgsGpsInformation &info)
Emitted whenever the GPS state is changed.
void processVtgSentence(const char *data, int len)
process VTG sentence
void processRmcSentence(const char *data, int len)
process RMC sentence
void parseData() override
Parse available data source content.
void processHchdgSentence(const char *data, int len)
process HCHDG sentence
void processGgaSentence(const char *data, int len)
process GGA sentence
void processGsvSentence(const char *data, int len)
process GSV sentence
void processGstSentence(const char *data, int len)
process GST sentence
void processHdtSentence(const char *data, int len)
process HDT sentence
void processGsaSentence(const char *data, int len)
process GSA sentence
QString mStringBuffer
Store data from the device before it is processed.
QgsNmeaConnection(QIODevice *device)
Constructs a QgsNmeaConnection with given device.
void processStringBuffer()
Splits mStringBuffer into sentences and calls libnmea.
Encapsulates information relating to a GPS satellite.
double elevation
Elevation of the satellite, in degrees.
bool inUse
true if satellite was used in obtaining the position fix.
int signal
Signal strength (0-99dB), or -1 if not available.
int id
Contains the satellite identifier number.
double azimuth
The azimuth of the satellite to true north, in degrees.
QChar satType
satType value from NMEA message $GxGSV, where x: P = GPS; S = SBAS (GPSid> 32 then SBasid = GPSid + 8...
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63
#define KNOTS_TO_KMH