QGIS API Documentation 3.29.0-Master (8c80f25a4f)
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 if ( mStatus != GPSDataReceived )
64 {
66 }
67
68 //append new data to the remaining results from last parseData() call
69 mStringBuffer.append( mSource->read( numBytes ) );
72 }
73}
74
76{
77 int endSentenceIndex = 0;
78 int dollarIndex;
79
80 while ( ( endSentenceIndex = mStringBuffer.indexOf( QLatin1String( "\r\n" ) ) ) && endSentenceIndex != -1 )
81 {
82 endSentenceIndex = mStringBuffer.indexOf( QLatin1String( "\r\n" ) );
83
84 dollarIndex = mStringBuffer.indexOf( QLatin1Char( '$' ) );
85 if ( endSentenceIndex == -1 )
86 {
87 break;
88 }
89
90 if ( endSentenceIndex >= dollarIndex )
91 {
92 if ( dollarIndex != -1 )
93 {
94 const QString substring = mStringBuffer.mid( dollarIndex, endSentenceIndex );
95 QByteArray ba = substring.toLocal8Bit();
96 if ( substring.startsWith( QLatin1String( "$GPGGA" ) ) || substring.startsWith( QLatin1String( "$GNGGA" ) ) )
97 {
98 QgsDebugMsgLevel( substring, 2 );
100 processGgaSentence( ba.data(), ba.length() );
102 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
103 }
104 else if ( substring.startsWith( QLatin1String( "$GPRMC" ) ) || substring.startsWith( QLatin1String( "$GNRMC" ) ) )
105 {
106 QgsDebugMsgLevel( substring, 2 );
108 processRmcSentence( ba.data(), ba.length() );
110 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
111 }
112 // GPS+SBAS GLONASS GALILEO BEIDOU QZSS;
113 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" ) ) )
114 {
115 QgsDebugMsgLevel( substring, 2 );
117 processGsvSentence( ba.data(), ba.length() );
119 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
120 }
121 else if ( substring.startsWith( QLatin1String( "$GPVTG" ) ) || substring.startsWith( QLatin1String( "$GNVTG" ) ) )
122 {
123 QgsDebugMsgLevel( substring, 2 );
125 processVtgSentence( ba.data(), ba.length() );
127 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
128 }
129 else if ( substring.startsWith( QLatin1String( "$GPGSA" ) ) || substring.startsWith( QLatin1String( "$GNGSA" ) ) || substring.startsWith( QLatin1String( "$GLGSA" ) ) )
130 {
131 QgsDebugMsgLevel( substring, 2 );
132 processGsaSentence( ba.data(), ba.length() );
134 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
135 }
136 else if ( substring.startsWith( QLatin1String( "$GPGST" ) ) || substring.startsWith( QLatin1String( "$GNGST" ) ) )
137 {
138 QgsDebugMsgLevel( substring, 2 );
140 processGstSentence( ba.data(), ba.length() );
142 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
143 }
144 else if ( substring.startsWith( QLatin1String( "$GPHDT" ) ) || substring.startsWith( QLatin1String( "$GNHDT" ) ) )
145 {
146 QgsDebugMsgLevel( substring, 2 );
148 processHdtSentence( ba.data(), ba.length() );
150 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
151 }
152 else if ( substring.startsWith( QLatin1String( "$HCHDG" ) ) )
153 {
154 QgsDebugMsgLevel( substring, 2 );
156 processHchdgSentence( ba.data(), ba.length() );
158 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
159 }
160 else if ( substring.startsWith( QLatin1String( "$HCHDT" ) ) )
161 {
162 QgsDebugMsgLevel( substring, 2 );
164 processHchdtSentence( ba.data(), ba.length() );
166 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
167 }
168 else
169 {
171 QgsDebugMsgLevel( QStringLiteral( "unknown nmea sentence: %1" ).arg( substring ), 2 );
172 }
173 emit nmeaSentenceReceived( substring ); // added to be able to save raw data
174 }
175 }
176 mStringBuffer.remove( 0, endSentenceIndex + 2 );
177 }
178}
179
180void QgsNmeaConnection::processGgaSentence( const char *data, int len )
181{
182 nmeaGPGGA result;
183 if ( nmea_parse_GPGGA( data, len, &result ) )
184 {
185 //update mLastGPSInformation
186 double longitude = result.lon;
187 if ( result.ew == 'W' )
188 {
189 longitude = -longitude;
190 }
191 double latitude = result.lat;
192 if ( result.ns == 'S' )
193 {
194 latitude = -latitude;
195 }
196
197 mLastGPSInformation.longitude = nmea_ndeg2degree( longitude );
198 mLastGPSInformation.latitude = nmea_ndeg2degree( latitude );
199 mLastGPSInformation.elevation = result.elv;
201
202 mLastGPSInformation.quality = result.sig;
203 if ( result.sig >= 0 && result.sig <= 8 )
204 {
206 }
207 else
208 {
210 }
211
212 // use GSA for satellites in use;
213 }
214}
215
216void QgsNmeaConnection::processGstSentence( const char *data, int len )
217{
218 nmeaGPGST result;
219 if ( nmea_parse_GPGST( data, len, &result ) )
220 {
221 //update mLastGPSInformation
222 const double sig_lat = result.sig_lat;
223 const double sig_lon = result.sig_lon;
224 const double sig_alt = result.sig_alt;
225
226 // Horizontal RMS
227 mLastGPSInformation.hacc = sqrt( ( pow( sig_lat, 2 ) + pow( sig_lon, 2 ) ) / 2.0 );
228 // Vertical RMS
229 mLastGPSInformation.vacc = sig_alt;
230 // 3D RMS
231 mLastGPSInformation.hvacc = sqrt( ( pow( sig_lat, 2 ) + pow( sig_lon, 2 ) + pow( sig_alt, 2 ) ) / 3.0 );
232 }
233}
234
235void QgsNmeaConnection::processHdtSentence( const char *data, int len )
236{
237 nmeaGPHDT result;
238 if ( nmea_parse_GPHDT( data, len, &result ) )
239 {
240 mLastGPSInformation.direction = result.heading;
241 }
242}
243
244void QgsNmeaConnection::processHchdgSentence( const char *data, int len )
245{
246 nmeaHCHDG result;
247 if ( nmea_parse_HCHDG( data, len, &result ) )
248 {
249 mLastGPSInformation.direction = result.mag_heading;
250 if ( result.ew_variation == 'E' )
251 mLastGPSInformation.direction += result.mag_variation;
252 else
253 mLastGPSInformation.direction -= result.mag_variation;
254 }
255}
256
257void QgsNmeaConnection::processHchdtSentence( const char *data, int len )
258{
259 nmeaHCHDT result;
260 if ( nmea_parse_HCHDT( data, len, &result ) )
261 {
262 mLastGPSInformation.direction = result.direction;
263 }
264}
265
266void QgsNmeaConnection::processRmcSentence( const char *data, int len )
267{
268 nmeaGPRMC result;
269 if ( nmea_parse_GPRMC( data, len, &result ) )
270 {
271 double longitude = result.lon;
272 if ( result.ew == 'W' )
273 {
274 longitude = -longitude;
275 }
276 double latitude = result.lat;
277 if ( result.ns == 'S' )
278 {
279 latitude = -latitude;
280 }
281 mLastGPSInformation.longitude = nmea_ndeg2degree( longitude );
282 mLastGPSInformation.latitude = nmea_ndeg2degree( latitude );
283 mLastGPSInformation.speed = KNOTS_TO_KMH * result.speed;
284 if ( !std::isnan( result.direction ) )
285 mLastGPSInformation.direction = result.direction;
286 mLastGPSInformation.status = result.status; // A,V
287
288 //date and time
289 const QDate date( result.utc.year + 1900, result.utc.mon + 1, result.utc.day );
290 const QTime time( result.utc.hour, result.utc.min, result.utc.sec, result.utc.msec ); // added msec part
291 if ( date.isValid() && time.isValid() )
292 {
293 mLastGPSInformation.utcDateTime.setTimeSpec( Qt::UTC );
294 mLastGPSInformation.utcDateTime.setDate( date );
295 mLastGPSInformation.utcDateTime.setTime( time );
296 QgsDebugMsgLevel( QStringLiteral( "utc time:" ), 2 );
298 QgsDebugMsgLevel( QStringLiteral( "local time:" ), 2 );
299 QgsDebugMsgLevel( mLastGPSInformation.utcDateTime.toLocalTime().toString(), 2 );
300 }
301
302 // convert mode to signal (aka quality) indicator
303 // (see https://gitlab.com/fhuberts/nmealib/-/blob/master/src/info.c#L27)
304 if ( result.status == 'A' )
305 {
306 if ( result.mode == 'A' )
307 {
310 }
311 else if ( result.mode == 'D' )
312 {
315 }
316 else if ( result.mode == 'P' )
317 {
320 }
321 else if ( result.mode == 'R' )
322 {
325 }
326 else if ( result.mode == 'F' )
327 {
330 }
331 else if ( result.mode == 'E' )
332 {
335 }
336 else if ( result.mode == 'M' )
337 {
340 }
341 else if ( result.mode == 'S' )
342 {
345 }
346 else
347 {
350 }
351 }
352 else
353 {
356 }
357 }
358}
359
360void QgsNmeaConnection::processGsvSentence( const char *data, int len )
361{
362 nmeaGPGSV result;
363 if ( nmea_parse_GPGSV( data, len, &result ) )
364 {
365 // for determining when to graph sat info
366 for ( int i = 0; i < NMEA_SATINPACK; ++i )
367 {
368 const nmeaSATELLITE currentSatellite = result.sat_data[i];
369 QgsSatelliteInfo satelliteInfo;
370 satelliteInfo.azimuth = currentSatellite.azimuth;
371 satelliteInfo.elevation = currentSatellite.elv;
372 satelliteInfo.id = currentSatellite.id;
373 satelliteInfo.inUse = false;
374 for ( int k = 0; k < mLastGPSInformation.satPrn.size(); ++k )
375 {
376 if ( mLastGPSInformation.satPrn.at( k ) == currentSatellite.id )
377 {
378 satelliteInfo.inUse = true;
379 }
380 }
381 satelliteInfo.signal = currentSatellite.sig;
382 satelliteInfo.satType = result.pack_type;
383
384 if ( result.pack_type == 'P' )
385 {
386 satelliteInfo.mConstellation = Qgis::GnssConstellation::Gps;
387 }
388 else if ( result.pack_type == 'L' )
389 {
390 satelliteInfo.mConstellation = Qgis::GnssConstellation::Glonass;
391 }
392 else if ( result.pack_type == 'A' )
393 {
394 satelliteInfo.mConstellation = Qgis::GnssConstellation::Galileo;
395 }
396 else if ( result.pack_type == 'B' )
397 {
398 satelliteInfo.mConstellation = Qgis::GnssConstellation::BeiDou;
399 }
400 else if ( result.pack_type == 'Q' )
401 {
402 satelliteInfo.mConstellation = Qgis::GnssConstellation::Qzss;
403 }
404
405 if ( satelliteInfo.satType == 'P' && satelliteInfo.id > 32 )
406 {
407 satelliteInfo.mConstellation = Qgis::GnssConstellation::Sbas;
408 satelliteInfo.satType = 'S';
409 satelliteInfo.id = currentSatellite.id + 87;
410 }
411
412 bool idAlreadyPresent = false;
413 if ( mLastGPSInformation.satellitesInView.size() > NMEA_SATINPACK )
414 {
415 for ( const QgsSatelliteInfo &existingSatInView : std::as_const( mLastGPSInformation.satellitesInView ) )
416 {
417 if ( existingSatInView.id == currentSatellite.id )
418 {
419 idAlreadyPresent = true;
420 break;
421 }
422 }
423 }
424
425 if ( currentSatellite.sig > 0 )
426 {
427 satelliteInfo.inUse = 1; // check where used ???? (+=1)
428 }
429 if ( !idAlreadyPresent && currentSatellite.azimuth > 0 && currentSatellite.elv > 0 )
430 {
431 mLastGPSInformation.satellitesInView.append( satelliteInfo );
432 }
433 }
434
435 }
436}
437
438void QgsNmeaConnection::processVtgSentence( const char *data, int len )
439{
440 nmeaGPVTG result;
441 if ( nmea_parse_GPVTG( data, len, &result ) )
442 {
443 mLastGPSInformation.speed = result.spk;
444 }
445}
446
447void QgsNmeaConnection::processGsaSentence( const char *data, int len )
448{
450 {
451 //clear satellite information when a new series of packs arrives
456 }
457 nmeaGPGSA result;
458 if ( nmea_parse_GPGSA( data, len, &result ) )
459 {
460 // clear() on GGA
461 mLastGPSInformation.hdop = result.HDOP;
462 mLastGPSInformation.pdop = result.PDOP;
463 mLastGPSInformation.vdop = result.VDOP;
464 mLastGPSInformation.fixMode = result.fix_mode;
465 mLastGPSInformation.fixType = result.fix_type;
466
468 bool mixedConstellation = false;
469 for ( int i = 0; i < NMEA_MAXSAT; i++ )
470 {
471 if ( result.sat_prn[ i ] > 0 )
472 {
473 mLastGPSInformation.satPrn.append( result.sat_prn[ i ] );
475
477 if ( result.pack_type == 'L' || result.sat_prn[i] > 64 )
478 constellation = Qgis::GnssConstellation::Glonass;
479 else if ( result.sat_prn[i] >= 1 && result.sat_prn[i] <= 32 )
480 constellation = Qgis::GnssConstellation::Gps;
481 else if ( result.sat_prn[i] > 32 && result.sat_prn[i] <= 64 )
482 constellation = Qgis::GnssConstellation::Sbas;
483
484 if ( result.sat_prn[i] > 0 )
485 {
486 if ( mixedConstellation
487 || ( commonConstellation != Qgis::GnssConstellation::Unknown
488 && commonConstellation != constellation ) )
489 {
490 mixedConstellation = true;
491 }
492 else
493 {
494 commonConstellation = constellation;
495 }
496 }
497 }
498 }
499 if ( mixedConstellation )
500 commonConstellation = Qgis::GnssConstellation::Unknown;
501
502 switch ( result.fix_type )
503 {
504 case 1:
505 mLastGPSInformation.mConstellationFixStatus[ commonConstellation ] = Qgis::GpsFixStatus::NoFix;
506 break;
507
508 case 2:
509 mLastGPSInformation.mConstellationFixStatus[ commonConstellation ] = Qgis::GpsFixStatus::Fix2D;
510 break;
511
512 case 3:
513 mLastGPSInformation.mConstellationFixStatus[ commonConstellation ] = Qgis::GpsFixStatus::Fix3D;
514 break;
515 }
516 }
517}
GnssConstellation
GNSS constellation.
Definition: qgis.h:972
@ 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:990
@ 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.
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