QGIS API Documentation 3.30.0-'s-Hertogenbosch (f186b8efe0)
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 const QTime time( result.utc.hour, result.utc.min, result.utc.sec, result.utc.msec );
203 if ( time.isValid() )
204 {
206 if ( mLastGPSInformation.utcDateTime.isValid() )
207 {
208 mLastGPSInformation.utcDateTime.setTimeSpec( Qt::UTC );
209 mLastGPSInformation.utcDateTime.setTime( time );
210 }
211 QgsDebugMsgLevel( QStringLiteral( "utc time:" ), 2 );
213 }
214
215 mLastGPSInformation.quality = result.sig;
216 if ( result.sig >= 0 && result.sig <= 8 )
217 {
219 }
220 else
221 {
223 }
224
225 // use GSA for satellites in use;
226 }
227}
228
229void QgsNmeaConnection::processGstSentence( const char *data, int len )
230{
231 nmeaGPGST result;
232 if ( nmea_parse_GPGST( data, len, &result ) )
233 {
234 //update mLastGPSInformation
235 const double sig_lat = result.sig_lat;
236 const double sig_lon = result.sig_lon;
237 const double sig_alt = result.sig_alt;
238
239 // Horizontal RMS
240 mLastGPSInformation.hacc = sqrt( ( pow( sig_lat, 2 ) + pow( sig_lon, 2 ) ) / 2.0 );
241 // Vertical RMS
242 mLastGPSInformation.vacc = sig_alt;
243 // 3D RMS
244 mLastGPSInformation.hvacc = sqrt( ( pow( sig_lat, 2 ) + pow( sig_lon, 2 ) + pow( sig_alt, 2 ) ) / 3.0 );
245 }
246}
247
248void QgsNmeaConnection::processHdtSentence( const char *data, int len )
249{
250 nmeaGPHDT result;
251 if ( nmea_parse_GPHDT( data, len, &result ) )
252 {
253 mLastGPSInformation.direction = result.heading;
254 }
255}
256
257void QgsNmeaConnection::processHchdgSentence( const char *data, int len )
258{
259 nmeaHCHDG result;
260 if ( nmea_parse_HCHDG( data, len, &result ) )
261 {
262 mLastGPSInformation.direction = result.mag_heading;
263 if ( result.ew_variation == 'E' )
264 mLastGPSInformation.direction += result.mag_variation;
265 else
266 mLastGPSInformation.direction -= result.mag_variation;
267 }
268}
269
270void QgsNmeaConnection::processHchdtSentence( const char *data, int len )
271{
272 nmeaHCHDT result;
273 if ( nmea_parse_HCHDT( data, len, &result ) )
274 {
275 mLastGPSInformation.direction = result.direction;
276 }
277}
278
279void QgsNmeaConnection::processRmcSentence( const char *data, int len )
280{
281 nmeaGPRMC result;
282 if ( nmea_parse_GPRMC( data, len, &result ) )
283 {
284 double longitude = result.lon;
285 if ( result.ew == 'W' )
286 {
287 longitude = -longitude;
288 }
289 double latitude = result.lat;
290 if ( result.ns == 'S' )
291 {
292 latitude = -latitude;
293 }
294 mLastGPSInformation.longitude = nmea_ndeg2degree( longitude );
295 mLastGPSInformation.latitude = nmea_ndeg2degree( latitude );
296 mLastGPSInformation.speed = KNOTS_TO_KMH * result.speed;
297 if ( !std::isnan( result.direction ) )
298 mLastGPSInformation.direction = result.direction;
299 mLastGPSInformation.status = result.status; // A,V
300
301 const QDate date( result.utc.year + 1900, result.utc.mon + 1, result.utc.day );
302 const QTime time( result.utc.hour, result.utc.min, result.utc.sec, result.utc.msec );
303 if ( date.isValid() && time.isValid() )
304 {
306 mLastGPSInformation.utcDateTime.setTimeSpec( Qt::UTC );
307 mLastGPSInformation.utcDateTime.setDate( date );
308 mLastGPSInformation.utcDateTime.setTime( time );
309 QgsDebugMsgLevel( QStringLiteral( "utc date/time:" ), 2 );
311 QgsDebugMsgLevel( QStringLiteral( "local date/time:" ), 2 );
312 QgsDebugMsgLevel( mLastGPSInformation.utcDateTime.toLocalTime().toString(), 2 );
313 }
314
315 // convert mode to signal (aka quality) indicator
316 // (see https://gitlab.com/fhuberts/nmealib/-/blob/master/src/info.c#L27)
317 if ( result.status == 'A' )
318 {
319 if ( result.mode == 'A' )
320 {
323 }
324 else if ( result.mode == 'D' )
325 {
328 }
329 else if ( result.mode == 'P' )
330 {
333 }
334 else if ( result.mode == 'R' )
335 {
338 }
339 else if ( result.mode == 'F' )
340 {
343 }
344 else if ( result.mode == 'E' )
345 {
348 }
349 else if ( result.mode == 'M' )
350 {
353 }
354 else if ( result.mode == 'S' )
355 {
358 }
359 else
360 {
363 }
364 }
365 else
366 {
369 }
370 }
371}
372
373void QgsNmeaConnection::processGsvSentence( const char *data, int len )
374{
375 nmeaGPGSV result;
376 if ( nmea_parse_GPGSV( data, len, &result ) )
377 {
378 // for determining when to graph sat info
379 for ( int i = 0; i < NMEA_SATINPACK; ++i )
380 {
381 const nmeaSATELLITE currentSatellite = result.sat_data[i];
382 QgsSatelliteInfo satelliteInfo;
383 satelliteInfo.azimuth = currentSatellite.azimuth;
384 satelliteInfo.elevation = currentSatellite.elv;
385 satelliteInfo.id = currentSatellite.id;
386 satelliteInfo.inUse = false;
387 for ( int k = 0; k < mLastGPSInformation.satPrn.size(); ++k )
388 {
389 if ( mLastGPSInformation.satPrn.at( k ) == currentSatellite.id )
390 {
391 satelliteInfo.inUse = true;
392 }
393 }
394 satelliteInfo.signal = currentSatellite.sig;
395 satelliteInfo.satType = result.pack_type;
396
397 if ( result.pack_type == 'P' )
398 {
399 satelliteInfo.mConstellation = Qgis::GnssConstellation::Gps;
400 }
401 else if ( result.pack_type == 'L' )
402 {
403 satelliteInfo.mConstellation = Qgis::GnssConstellation::Glonass;
404 }
405 else if ( result.pack_type == 'A' )
406 {
407 satelliteInfo.mConstellation = Qgis::GnssConstellation::Galileo;
408 }
409 else if ( result.pack_type == 'B' )
410 {
411 satelliteInfo.mConstellation = Qgis::GnssConstellation::BeiDou;
412 }
413 else if ( result.pack_type == 'Q' )
414 {
415 satelliteInfo.mConstellation = Qgis::GnssConstellation::Qzss;
416 }
417
418 if ( satelliteInfo.satType == 'P' && satelliteInfo.id > 32 )
419 {
420 satelliteInfo.mConstellation = Qgis::GnssConstellation::Sbas;
421 satelliteInfo.satType = 'S';
422 satelliteInfo.id = currentSatellite.id + 87;
423 }
424
425 bool idAlreadyPresent = false;
426 if ( mLastGPSInformation.satellitesInView.size() > NMEA_SATINPACK )
427 {
428 for ( const QgsSatelliteInfo &existingSatInView : std::as_const( mLastGPSInformation.satellitesInView ) )
429 {
430 if ( existingSatInView.id == currentSatellite.id )
431 {
432 idAlreadyPresent = true;
433 break;
434 }
435 }
436 }
437
438 if ( !idAlreadyPresent && currentSatellite.azimuth > 0 && currentSatellite.elv > 0 )
439 {
440 mLastGPSInformation.satellitesInView.append( satelliteInfo );
441 }
442 }
443
444 }
445}
446
447void QgsNmeaConnection::processVtgSentence( const char *data, int len )
448{
449 nmeaGPVTG result;
450 if ( nmea_parse_GPVTG( data, len, &result ) )
451 {
452 mLastGPSInformation.speed = result.spk;
453 if ( !std::isnan( result.dir ) )
454 mLastGPSInformation.direction = result.dir;
455 }
456}
457
458void QgsNmeaConnection::processGsaSentence( const char *data, int len )
459{
461 {
462 //clear satellite information when a new series of packs arrives
467 }
468 nmeaGPGSA result;
469 if ( nmea_parse_GPGSA( data, len, &result ) )
470 {
471 // clear() on GGA
472 mLastGPSInformation.hdop = result.HDOP;
473 mLastGPSInformation.pdop = result.PDOP;
474 mLastGPSInformation.vdop = result.VDOP;
475 mLastGPSInformation.fixMode = result.fix_mode;
476 mLastGPSInformation.fixType = result.fix_type;
477
479 bool mixedConstellation = false;
480 for ( int i = 0; i < NMEA_MAXSAT; i++ )
481 {
482 if ( result.sat_prn[ i ] > 0 )
483 {
484 mLastGPSInformation.satPrn.append( result.sat_prn[ i ] );
486
488 if ( result.pack_type == 'L' || result.sat_prn[i] > 64 )
489 constellation = Qgis::GnssConstellation::Glonass;
490 else if ( result.sat_prn[i] >= 1 && result.sat_prn[i] <= 32 )
491 constellation = Qgis::GnssConstellation::Gps;
492 else if ( result.sat_prn[i] > 32 && result.sat_prn[i] <= 64 )
493 constellation = Qgis::GnssConstellation::Sbas;
494
495 if ( result.sat_prn[i] > 0 )
496 {
497 if ( mixedConstellation
498 || ( commonConstellation != Qgis::GnssConstellation::Unknown
499 && commonConstellation != constellation ) )
500 {
501 mixedConstellation = true;
502 }
503 else
504 {
505 commonConstellation = constellation;
506 }
507 }
508 }
509 }
510 if ( mixedConstellation )
511 commonConstellation = Qgis::GnssConstellation::Unknown;
512
513 switch ( result.fix_type )
514 {
515 case 1:
516 mLastGPSInformation.mConstellationFixStatus[ commonConstellation ] = Qgis::GpsFixStatus::NoFix;
517 break;
518
519 case 2:
520 mLastGPSInformation.mConstellationFixStatus[ commonConstellation ] = Qgis::GpsFixStatus::Fix2D;
521 break;
522
523 case 3:
524 mLastGPSInformation.mConstellationFixStatus[ commonConstellation ] = Qgis::GpsFixStatus::Fix3D;
525 break;
526 }
527 }
528}
GnssConstellation
GNSS constellation.
Definition: qgis.h:1142
@ 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:1160
@ 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