QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgsnmeaconnection.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsnmeaconnection.cpp - description
3 ---------------------
4 begin : November 30th, 2009
5 copyright : (C) 2009 by Marco Hugentobler
6 email : marco at hugis dot net
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18#include "qgsnmeaconnection.h"
19#include "qgslogger.h"
20
21#include <QIODevice>
22#include <QApplication>
23#include <QStringList>
24
25
26//from libnmea
27#include "parse.h"
28#include "gmath.h"
29#include "info.h"
30
31// for sqrt
32#include <math.h>
33
34#define KNOTS_TO_KMH 1.852
35
37 : QgsGpsConnection( device )
38{
39}
40
42{
43 if ( !mSource )
44 {
45 return;
46 }
47
48 //print out the data as a test
49 qint64 numBytes = 0;
50 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 ?
51 {
52 numBytes = mSource->size();
53 }
54 else
55 {
56 numBytes = mSource->bytesAvailable();
57 }
58
59 QgsDebugMsgLevel( "numBytes:" + QString::number( numBytes ), 2 );
60
61 if ( numBytes >= 6 )
62 {
63 QgsDebugMsgLevel( QStringLiteral( "Got %1 NMEA bytes" ).arg( numBytes ), 3 );
64 QgsDebugMsgLevel( QStringLiteral( "Current NMEA device status is %1" ).arg( mStatus ), 3 );
65 if ( mStatus != GPSDataReceived )
66 {
67 QgsDebugMsgLevel( QStringLiteral( "Setting device status to DataReceived" ), 3 );
69 }
70
71 //append new data to the remaining results from last parseData() call
72 mStringBuffer.append( mSource->read( numBytes ) );
74 QgsDebugMsgLevel( QStringLiteral( "Processed buffer" ), 3 );
75
76 QgsDebugMsgLevel( QStringLiteral( "New status is %1" ).arg( mStatus ), 3 );
77 if ( mStatus == GPSDataReceived )
78 {
80 }
81 }
82}
83
85{
86 int endSentenceIndex = 0;
87 int dollarIndex;
88
89 while ( ( endSentenceIndex = mStringBuffer.indexOf( QLatin1String( "\r\n" ) ) ) && endSentenceIndex != -1 )
90 {
91 endSentenceIndex = mStringBuffer.indexOf( QLatin1String( "\r\n" ) );
92
93 dollarIndex = mStringBuffer.indexOf( QLatin1Char( '$' ) );
94 if ( endSentenceIndex == -1 )
95 {
96 break;
97 }
98
99 if ( endSentenceIndex >= dollarIndex )
100 {
101 if ( dollarIndex != -1 )
102 {
103 const QString substring = mStringBuffer.mid( dollarIndex, endSentenceIndex );
104 QByteArray ba = substring.toLocal8Bit();
105 if ( substring.startsWith( QLatin1String( "$GPGGA" ) ) || substring.startsWith( QLatin1String( "$GNGGA" ) ) )
106 {
107 QgsDebugMsgLevel( substring, 2 );
109 processGgaSentence( ba.data(), ba.length() );
111 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
112 }
113 else if ( substring.startsWith( QLatin1String( "$GPRMC" ) ) || substring.startsWith( QLatin1String( "$GNRMC" ) ) )
114 {
115 QgsDebugMsgLevel( substring, 2 );
117 processRmcSentence( ba.data(), ba.length() );
119 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
120 }
121 // GPS+SBAS GLONASS GALILEO BEIDOU QZSS;
122 else if ( substring.startsWith( QLatin1String( "$GPGSV" ) ) || substring.startsWith( QLatin1String( "$GNGSV" ) ) || substring.startsWith( QLatin1String( "$GLGSV" ) ) || substring.startsWith( QLatin1String( "$GAGSV" ) ) || substring.startsWith( QLatin1String( "$GBGSV" ) ) || substring.startsWith( QLatin1String( "$GQGSV" ) ) )
123 {
124 QgsDebugMsgLevel( substring, 2 );
126 processGsvSentence( ba.data(), ba.length() );
128 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
129 }
130 else if ( substring.startsWith( QLatin1String( "$GPVTG" ) ) || substring.startsWith( QLatin1String( "$GNVTG" ) ) )
131 {
132 QgsDebugMsgLevel( substring, 2 );
134 processVtgSentence( ba.data(), ba.length() );
136 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
137 }
138 else if ( substring.startsWith( QLatin1String( "$GPGSA" ) ) || substring.startsWith( QLatin1String( "$GNGSA" ) ) || substring.startsWith( QLatin1String( "$GLGSA" ) ) )
139 {
140 QgsDebugMsgLevel( substring, 2 );
141 processGsaSentence( ba.data(), ba.length() );
143 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
144 }
145 else if ( substring.startsWith( QLatin1String( "$GPGST" ) ) || substring.startsWith( QLatin1String( "$GNGST" ) ) )
146 {
147 QgsDebugMsgLevel( substring, 2 );
149 processGstSentence( ba.data(), ba.length() );
151 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
152 }
153 else if ( substring.startsWith( QLatin1String( "$GPHDT" ) ) || substring.startsWith( QLatin1String( "$GNHDT" ) ) )
154 {
155 QgsDebugMsgLevel( substring, 2 );
157 processHdtSentence( ba.data(), ba.length() );
159 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
160 }
161 else if ( substring.startsWith( QLatin1String( "$HCHDG" ) ) )
162 {
163 QgsDebugMsgLevel( substring, 2 );
165 processHchdgSentence( ba.data(), ba.length() );
167 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
168 }
169 else if ( substring.startsWith( QLatin1String( "$HCHDT" ) ) )
170 {
171 QgsDebugMsgLevel( substring, 2 );
173 processHchdtSentence( ba.data(), ba.length() );
175 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
176 }
177 else
178 {
180 QgsDebugMsgLevel( QStringLiteral( "unknown nmea sentence: %1" ).arg( substring ), 2 );
181 }
182 emit nmeaSentenceReceived( substring ); // added to be able to save raw data
183 }
184 }
185 mStringBuffer.remove( 0, endSentenceIndex + 2 );
186 }
187}
188
189void QgsNmeaConnection::processGgaSentence( const char *data, int len )
190{
191 nmeaGPGGA result;
192 if ( nmea_parse_GPGGA( data, len, &result ) )
193 {
194 //update mLastGPSInformation
195 double longitude = result.lon;
196 if ( result.ew == 'W' )
197 {
198 longitude = -longitude;
199 }
200 double latitude = result.lat;
201 if ( result.ns == 'S' )
202 {
203 latitude = -latitude;
204 }
205
206 mLastGPSInformation.longitude = nmea_ndeg2degree( longitude );
207 mLastGPSInformation.latitude = nmea_ndeg2degree( latitude );
208 mLastGPSInformation.elevation = result.elv;
210
211 const QTime time( result.utc.hour, result.utc.min, result.utc.sec, result.utc.msec );
212 if ( time.isValid() )
213 {
215 if ( mLastGPSInformation.utcDateTime.isValid() )
216 {
217 mLastGPSInformation.utcDateTime.setTimeSpec( Qt::UTC );
218 mLastGPSInformation.utcDateTime.setTime( time );
219 }
220 QgsDebugMsgLevel( QStringLiteral( "utc time:" ), 2 );
222 }
223
224 mLastGPSInformation.quality = result.sig;
225 if ( result.sig >= 0 && result.sig <= 8 )
226 {
228 }
229 else
230 {
232 }
233
234 // use GSA for satellites in use;
235 }
236}
237
238void QgsNmeaConnection::processGstSentence( const char *data, int len )
239{
240 nmeaGPGST result;
241 if ( nmea_parse_GPGST( data, len, &result ) )
242 {
243 //update mLastGPSInformation
244 const double sig_lat = result.sig_lat;
245 const double sig_lon = result.sig_lon;
246 const double sig_alt = result.sig_alt;
247
248 // Horizontal RMS
249 mLastGPSInformation.hacc = sqrt( ( pow( sig_lat, 2 ) + pow( sig_lon, 2 ) ) / 2.0 );
250 // Vertical RMS
251 mLastGPSInformation.vacc = sig_alt;
252 // 3D RMS
253 mLastGPSInformation.hvacc = sqrt( ( pow( sig_lat, 2 ) + pow( sig_lon, 2 ) + pow( sig_alt, 2 ) ) / 3.0 );
254 }
255}
256
257void QgsNmeaConnection::processHdtSentence( const char *data, int len )
258{
259 nmeaGPHDT result;
260 if ( nmea_parse_GPHDT( data, len, &result ) )
261 {
262 mLastGPSInformation.direction = result.heading;
263 }
264}
265
266void QgsNmeaConnection::processHchdgSentence( const char *data, int len )
267{
268 nmeaHCHDG result;
269 if ( nmea_parse_HCHDG( data, len, &result ) )
270 {
271 mLastGPSInformation.direction = result.mag_heading;
272 if ( result.ew_variation == 'E' )
273 mLastGPSInformation.direction += result.mag_variation;
274 else
275 mLastGPSInformation.direction -= result.mag_variation;
276 }
277}
278
279void QgsNmeaConnection::processHchdtSentence( const char *data, int len )
280{
281 nmeaHCHDT result;
282 if ( nmea_parse_HCHDT( data, len, &result ) )
283 {
284 mLastGPSInformation.direction = result.direction;
285 }
286}
287
288void QgsNmeaConnection::processRmcSentence( const char *data, int len )
289{
290 nmeaGPRMC result;
291 if ( nmea_parse_GPRMC( data, len, &result ) )
292 {
293 double longitude = result.lon;
294 if ( result.ew == 'W' )
295 {
296 longitude = -longitude;
297 }
298 double latitude = result.lat;
299 if ( result.ns == 'S' )
300 {
301 latitude = -latitude;
302 }
303 mLastGPSInformation.longitude = nmea_ndeg2degree( longitude );
304 mLastGPSInformation.latitude = nmea_ndeg2degree( latitude );
305 mLastGPSInformation.speed = KNOTS_TO_KMH * result.speed;
306 if ( !std::isnan( result.direction ) )
307 mLastGPSInformation.direction = result.direction;
308 mLastGPSInformation.status = result.status; // A,V
309
310 const QDate date( result.utc.year + 1900, result.utc.mon + 1, result.utc.day );
311 const QTime time( result.utc.hour, result.utc.min, result.utc.sec, result.utc.msec );
312 if ( date.isValid() && time.isValid() )
313 {
315 mLastGPSInformation.utcDateTime.setTimeSpec( Qt::UTC );
316 mLastGPSInformation.utcDateTime.setDate( date );
317 mLastGPSInformation.utcDateTime.setTime( time );
318 QgsDebugMsgLevel( QStringLiteral( "utc date/time:" ), 2 );
320 QgsDebugMsgLevel( QStringLiteral( "local date/time:" ), 2 );
321 QgsDebugMsgLevel( mLastGPSInformation.utcDateTime.toLocalTime().toString(), 2 );
322 }
323
324 // convert mode to signal (aka quality) indicator
325 // (see https://gitlab.com/fhuberts/nmealib/-/blob/master/src/info.c#L27)
326 if ( result.status == 'A' )
327 {
328 if ( result.mode == 'A' )
329 {
332 }
333 else if ( result.mode == 'D' )
334 {
337 }
338 else if ( result.mode == 'P' )
339 {
342 }
343 else if ( result.mode == 'R' )
344 {
347 }
348 else if ( result.mode == 'F' )
349 {
352 }
353 else if ( result.mode == 'E' )
354 {
357 }
358 else if ( result.mode == 'M' )
359 {
362 }
363 else if ( result.mode == 'S' )
364 {
367 }
368 else
369 {
372 }
373 }
374 else
375 {
378 }
379 }
380}
381
382void QgsNmeaConnection::processGsvSentence( const char *data, int len )
383{
384 nmeaGPGSV result;
385 if ( nmea_parse_GPGSV( data, len, &result ) )
386 {
387 // for determining when to graph sat info
388 for ( int i = 0; i < NMEA_SATINPACK; ++i )
389 {
390 const nmeaSATELLITE currentSatellite = result.sat_data[i];
391 QgsSatelliteInfo satelliteInfo;
392 satelliteInfo.azimuth = currentSatellite.azimuth;
393 satelliteInfo.elevation = currentSatellite.elv;
394 satelliteInfo.id = currentSatellite.id;
395 satelliteInfo.inUse = false;
396 for ( int k = 0; k < mLastGPSInformation.satPrn.size(); ++k )
397 {
398 if ( mLastGPSInformation.satPrn.at( k ) == currentSatellite.id )
399 {
400 satelliteInfo.inUse = true;
401 }
402 }
403 satelliteInfo.signal = currentSatellite.sig;
404 satelliteInfo.satType = result.pack_type;
405
406 if ( result.pack_type == 'P' )
407 {
408 satelliteInfo.mConstellation = Qgis::GnssConstellation::Gps;
409 }
410 else if ( result.pack_type == 'L' )
411 {
412 satelliteInfo.mConstellation = Qgis::GnssConstellation::Glonass;
413 }
414 else if ( result.pack_type == 'A' )
415 {
416 satelliteInfo.mConstellation = Qgis::GnssConstellation::Galileo;
417 }
418 else if ( result.pack_type == 'B' )
419 {
420 satelliteInfo.mConstellation = Qgis::GnssConstellation::BeiDou;
421 }
422 else if ( result.pack_type == 'Q' )
423 {
424 satelliteInfo.mConstellation = Qgis::GnssConstellation::Qzss;
425 }
426
427 if ( satelliteInfo.satType == 'P' && satelliteInfo.id > 32 )
428 {
429 satelliteInfo.mConstellation = Qgis::GnssConstellation::Sbas;
430 satelliteInfo.satType = 'S';
431 satelliteInfo.id = currentSatellite.id + 87;
432 }
433
434 bool idAlreadyPresent = false;
435 if ( mLastGPSInformation.satellitesInView.size() > NMEA_SATINPACK )
436 {
437 for ( const QgsSatelliteInfo &existingSatInView : std::as_const( mLastGPSInformation.satellitesInView ) )
438 {
439 if ( existingSatInView.id == currentSatellite.id )
440 {
441 idAlreadyPresent = true;
442 break;
443 }
444 }
445 }
446
447 if ( !idAlreadyPresent && currentSatellite.azimuth > 0 && currentSatellite.elv > 0 )
448 {
449 mLastGPSInformation.satellitesInView.append( satelliteInfo );
450 }
451 }
452
453 }
454}
455
456void QgsNmeaConnection::processVtgSentence( const char *data, int len )
457{
458 nmeaGPVTG result;
459 if ( nmea_parse_GPVTG( data, len, &result ) )
460 {
461 mLastGPSInformation.speed = result.spk;
462 if ( !std::isnan( result.dir ) )
463 mLastGPSInformation.direction = result.dir;
464 }
465}
466
467void QgsNmeaConnection::processGsaSentence( const char *data, int len )
468{
470 {
471 //clear satellite information when a new series of packs arrives
476 }
477 nmeaGPGSA result;
478 if ( nmea_parse_GPGSA( data, len, &result ) )
479 {
480 // clear() on GGA
481 mLastGPSInformation.hdop = result.HDOP;
482 mLastGPSInformation.pdop = result.PDOP;
483 mLastGPSInformation.vdop = result.VDOP;
484 mLastGPSInformation.fixMode = result.fix_mode;
485 mLastGPSInformation.fixType = result.fix_type;
486
488 bool mixedConstellation = false;
489 for ( int i = 0; i < NMEA_MAXSAT; i++ )
490 {
491 if ( result.sat_prn[ i ] > 0 )
492 {
493 mLastGPSInformation.satPrn.append( result.sat_prn[ i ] );
495
497 if ( result.pack_type == 'L' || result.sat_prn[i] > 64 )
498 constellation = Qgis::GnssConstellation::Glonass;
499 else if ( result.sat_prn[i] >= 1 && result.sat_prn[i] <= 32 )
500 constellation = Qgis::GnssConstellation::Gps;
501 else if ( result.sat_prn[i] > 32 && result.sat_prn[i] <= 64 )
502 constellation = Qgis::GnssConstellation::Sbas;
503
504 // cppcheck-suppress identicalInnerCondition
505 if ( result.sat_prn[i] > 0 )
506 {
507 if ( mixedConstellation
508 || ( commonConstellation != Qgis::GnssConstellation::Unknown
509 && commonConstellation != constellation ) )
510 {
511 mixedConstellation = true;
512 }
513 else
514 {
515 commonConstellation = constellation;
516 }
517 }
518 }
519 }
520 if ( mixedConstellation )
521 commonConstellation = Qgis::GnssConstellation::Unknown;
522
523 switch ( result.fix_type )
524 {
525 case 1:
526 mLastGPSInformation.mConstellationFixStatus[ commonConstellation ] = Qgis::GpsFixStatus::NoFix;
527 break;
528
529 case 2:
530 mLastGPSInformation.mConstellationFixStatus[ commonConstellation ] = Qgis::GpsFixStatus::Fix2D;
531 break;
532
533 case 3:
534 mLastGPSInformation.mConstellationFixStatus[ commonConstellation ] = Qgis::GpsFixStatus::Fix3D;
535 break;
536 }
537 }
538}
GnssConstellation
GNSS constellation.
Definition: qgis.h:1491
@ Gps
Global Positioning System (GPS)
@ Glonass
Global Navigation Satellite System (GLONASS)
@ Unknown
Unknown/other system.
@ Qzss
Quasi Zenith Satellite System (QZSS)
GpsQualityIndicator
GPS signal quality indicator.
Definition: qgis.h:1509
@ RTK
Real-time-kynematic.
@ DGPS
Differential GPS.
@ Simulation
Simulation mode.
@ FloatRTK
Float real-time-kynematic.
@ Manual
Manual input mode.
@ NoFix
GPS is not fixed.
Abstract base class for connection to a GPS device.
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,...)
Status mStatus
Connection status.
void stateChanged(const QgsGpsInformation &info)
Emitted whenever the GPS state is changed.
double vdop
Vertical dilution of precision.
double direction
The bearing measured in degrees clockwise from true north to the direction of travel.
int fixType
Contains the fix type, where 1 = no fix, 2 = 2d fix, 3 = 3d fix.
QChar status
Status (A = active or V = void)
double speed
Ground speed, in km/h.
QTime utcTime
The time at which this position was reported, in UTC time.
double vacc
Vertical accuracy in meters.
Qgis::GpsQualityIndicator qualityIndicator
Returns the signal quality indicator.
double latitude
Latitude in decimal degrees, using the WGS84 datum.
double longitude
Longitude in decimal degrees, using the WGS84 datum.
QList< QgsSatelliteInfo > satellitesInView
Contains a list of information relating to the current satellites in view.
QChar fixMode
Fix mode (where M = Manual, forced to operate in 2D or 3D or A = Automatic, 3D/2D)
QDateTime utcDateTime
The date and time at which this position was reported, in UTC time.
QList< int > satPrn
IDs of satellites used in the position fix.
double elevation
Altitude (in meters) above or below the mean sea level.
bool satInfoComplete
true if satellite information is complete.
double pdop
Dilution of precision.
int satellitesUsed
Count of satellites used in obtaining the fix.
double elevation_diff
Geoidal separation (in meters).
double hdop
Horizontal dilution of precision.
int quality
GPS quality indicator (0 = Invalid; 1 = Fix; 2 = Differential, 3 = Sensitive, etc....
double hacc
Horizontal accuracy in meters.
void processVtgSentence(const char *data, int len)
process VTG sentence
void processRmcSentence(const char *data, int len)
process RMC sentence
void processHchdtSentence(const char *data, int len)
process HCHDT 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:39
#define KNOTS_TO_KMH