QGIS API Documentation 3.41.0-Master (fda2aa46e9a)
Loading...
Searching...
No Matches
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 "moc_qgsnmeaconnection.cpp"
20#include "qgslogger.h"
21
22#include <QIODevice>
23#include <QApplication>
24#include <QStringList>
25#include <QRegularExpression>
26
27
28//from libnmea
29#include "parse.h"
30#include "gmath.h"
31#include "info.h"
32
33// for sqrt
34#include <math.h>
35
36#define KNOTS_TO_KMH 1.852
37
39 : QgsGpsConnection( device )
40{
41}
42
44{
45 if ( !mSource )
46 {
47 return;
48 }
49
50 //print out the data as a test
51 qint64 numBytes = 0;
52 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 ?
53 {
54 numBytes = mSource->size();
55 }
56 else
57 {
58 numBytes = mSource->bytesAvailable();
59 }
60
61 QgsDebugMsgLevel( "numBytes:" + QString::number( numBytes ), 2 );
62
63 if ( numBytes >= 6 )
64 {
65 QgsDebugMsgLevel( QStringLiteral( "Got %1 NMEA bytes" ).arg( numBytes ), 3 );
66 QgsDebugMsgLevel( QStringLiteral( "Current NMEA device status is %1" ).arg( mStatus ), 3 );
67 if ( mStatus != GPSDataReceived )
68 {
69 QgsDebugMsgLevel( QStringLiteral( "Setting device status to DataReceived" ), 3 );
71 }
72
73 //append new data to the remaining results from last parseData() call
74 mStringBuffer.append( mSource->read( numBytes ) );
76 QgsDebugMsgLevel( QStringLiteral( "Processed buffer" ), 3 );
77
78 QgsDebugMsgLevel( QStringLiteral( "New status is %1" ).arg( mStatus ), 3 );
79 if ( mStatus == GPSDataReceived )
80 {
82 }
83 }
84}
85
87{
88 int endSentenceIndex = 0;
89 int dollarIndex;
90
91 while ( ( endSentenceIndex = mStringBuffer.indexOf( QLatin1String( "\r\n" ) ) ) && endSentenceIndex != -1 )
92 {
93 endSentenceIndex = mStringBuffer.indexOf( QLatin1String( "\r\n" ) );
94
95 dollarIndex = mStringBuffer.indexOf( QLatin1Char( '$' ) );
96 if ( endSentenceIndex == -1 )
97 {
98 break;
99 }
100
101 if ( endSentenceIndex >= dollarIndex )
102 {
103 if ( dollarIndex != -1 )
104 {
105 const QString substring = mStringBuffer.mid( dollarIndex, endSentenceIndex );
106 QByteArray ba = substring.toLocal8Bit();
107 const thread_local QRegularExpression rxSentence( QStringLiteral( "^\\$([A-Z]{2})([A-Z]{3})" ) );
108 const QRegularExpressionMatch sentenceMatch = rxSentence.match( substring );
109 const QString sentenceId = sentenceMatch.captured( 2 );
110 if ( sentenceId == QLatin1String( "GGA" ) )
111 {
112 QgsDebugMsgLevel( substring, 2 );
114 processGgaSentence( ba.data(), ba.length() );
116 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
117 }
118 else if ( sentenceId == QLatin1String( "RMC" ) )
119 {
120 QgsDebugMsgLevel( substring, 2 );
122 processRmcSentence( ba.data(), ba.length() );
124 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
125 }
126 else if ( sentenceId == QLatin1String( "GSV" ) )
127 {
128 QgsDebugMsgLevel( substring, 2 );
130 processGsvSentence( ba.data(), ba.length() );
132 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
133 }
134 else if ( sentenceId == QLatin1String( "VTG" ) )
135 {
136 QgsDebugMsgLevel( substring, 2 );
138 processVtgSentence( ba.data(), ba.length() );
140 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
141 }
142 else if ( sentenceId == QLatin1String( "GSA" ) )
143 {
144 QgsDebugMsgLevel( substring, 2 );
145 processGsaSentence( ba.data(), ba.length() );
147 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
148 }
149 else if ( sentenceId == QLatin1String( "GST" ) )
150 {
151 QgsDebugMsgLevel( substring, 2 );
153 processGstSentence( ba.data(), ba.length() );
155 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
156 }
157 else if ( sentenceId == QLatin1String( "HDT" ) )
158 {
159 QgsDebugMsgLevel( substring, 2 );
161 processHdtSentence( ba.data(), ba.length() );
163 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
164 }
165 else if ( sentenceId == QLatin1String( "HDG" ) )
166 {
167 QgsDebugMsgLevel( substring, 2 );
169 processHchdgSentence( ba.data(), ba.length() );
171 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
172 }
173 else
174 {
176 QgsDebugMsgLevel( QStringLiteral( "unknown nmea sentence: %1" ).arg( substring ), 2 );
177 }
178 emit nmeaSentenceReceived( substring ); // added to be able to save raw data
179 }
180 }
181 mStringBuffer.remove( 0, endSentenceIndex + 2 );
182 }
183}
184
185void QgsNmeaConnection::processGgaSentence( const char *data, int len )
186{
187 nmeaGPGGA result;
188 if ( nmea_parse_GPGGA( data, len, &result ) )
189 {
190 //update mLastGPSInformation
191 double longitude = result.lon;
192 if ( result.ew == 'W' )
193 {
194 longitude = -longitude;
195 }
196 double latitude = result.lat;
197 if ( result.ns == 'S' )
198 {
199 latitude = -latitude;
200 }
201
202 mLastGPSInformation.longitude = nmea_ndeg2degree( longitude );
203 mLastGPSInformation.latitude = nmea_ndeg2degree( latitude );
204 mLastGPSInformation.elevation = result.elv;
206
207 const QTime time( result.utc.hour, result.utc.min, result.utc.sec, result.utc.msec );
208 if ( time.isValid() )
209 {
211 if ( mLastGPSInformation.utcDateTime.isValid() )
212 {
213 mLastGPSInformation.utcDateTime.setTimeSpec( Qt::UTC );
214 mLastGPSInformation.utcDateTime.setTime( time );
215 }
216 QgsDebugMsgLevel( QStringLiteral( "utc time:" ), 2 );
218 }
219
220 mLastGPSInformation.quality = result.sig;
221 if ( result.sig >= 0 && result.sig <= 8 )
222 {
224 }
225 else
226 {
228 }
229
230 // use GSA for satellites in use;
231 }
232}
233
234void QgsNmeaConnection::processGstSentence( const char *data, int len )
235{
236 nmeaGPGST result;
237 if ( nmea_parse_GPGST( data, len, &result ) )
238 {
239 //update mLastGPSInformation
240 const double sig_lat = result.sig_lat;
241 const double sig_lon = result.sig_lon;
242 const double sig_alt = result.sig_alt;
243
244 // Horizontal RMS
245 mLastGPSInformation.hacc = sqrt( ( pow( sig_lat, 2 ) + pow( sig_lon, 2 ) ) / 2.0 );
246 // Vertical RMS
247 mLastGPSInformation.vacc = sig_alt;
248 // 3D RMS
249 mLastGPSInformation.hvacc = sqrt( ( pow( sig_lat, 2 ) + pow( sig_lon, 2 ) + pow( sig_alt, 2 ) ) / 3.0 );
250 }
251}
252
253void QgsNmeaConnection::processHdtSentence( const char *data, int len )
254{
255 nmeaGPHDT result;
256 if ( nmea_parse_GPHDT( data, len, &result ) )
257 {
258 mLastGPSInformation.direction = result.heading;
259 }
260}
261
262void QgsNmeaConnection::processHchdgSentence( const char *data, int len )
263{
264 nmeaHCHDG result;
265 if ( nmea_parse_HCHDG( data, len, &result ) )
266 {
267 mLastGPSInformation.direction = result.mag_heading;
268 if ( result.ew_variation == 'E' )
269 mLastGPSInformation.direction += result.mag_variation;
270 else
271 mLastGPSInformation.direction -= result.mag_variation;
272 }
273}
274
275void QgsNmeaConnection::processRmcSentence( const char *data, int len )
276{
277 nmeaGPRMC result;
278 if ( nmea_parse_GPRMC( data, len, &result ) )
279 {
280 double longitude = result.lon;
281 if ( result.ew == 'W' )
282 {
283 longitude = -longitude;
284 }
285 double latitude = result.lat;
286 if ( result.ns == 'S' )
287 {
288 latitude = -latitude;
289 }
290 mLastGPSInformation.longitude = nmea_ndeg2degree( longitude );
291 mLastGPSInformation.latitude = nmea_ndeg2degree( latitude );
292 mLastGPSInformation.speed = KNOTS_TO_KMH * result.speed;
293 if ( !std::isnan( result.direction ) )
294 mLastGPSInformation.direction = result.direction;
295 mLastGPSInformation.status = result.status; // A,V
296
297 const QDate date( result.utc.year + 1900, result.utc.mon + 1, result.utc.day );
298 const QTime time( result.utc.hour, result.utc.min, result.utc.sec, result.utc.msec );
299 if ( date.isValid() && time.isValid() )
300 {
302 mLastGPSInformation.utcDateTime.setTimeSpec( Qt::UTC );
303 mLastGPSInformation.utcDateTime.setDate( date );
304 mLastGPSInformation.utcDateTime.setTime( time );
305 QgsDebugMsgLevel( QStringLiteral( "utc date/time:" ), 2 );
307 QgsDebugMsgLevel( QStringLiteral( "local date/time:" ), 2 );
308 QgsDebugMsgLevel( mLastGPSInformation.utcDateTime.toLocalTime().toString(), 2 );
309 }
310
311 // convert mode to signal (aka quality) indicator
312 // (see https://gitlab.com/fhuberts/nmealib/-/blob/master/src/info.c#L27)
313 if ( result.status == 'A' )
314 {
315 if ( result.mode == 'A' )
316 {
319 }
320 else if ( result.mode == 'D' )
321 {
324 }
325 else if ( result.mode == 'P' )
326 {
329 }
330 else if ( result.mode == 'R' )
331 {
334 }
335 else if ( result.mode == 'F' )
336 {
339 }
340 else if ( result.mode == 'E' )
341 {
344 }
345 else if ( result.mode == 'M' )
346 {
349 }
350 else if ( result.mode == 'S' )
351 {
354 }
355 else
356 {
359 }
360 }
361 else
362 {
365 }
366 }
367
368 if ( result.navstatus == 'S' )
369 {
371 }
372 else if ( result.navstatus == 'C' )
373 {
375 }
376 else if ( result.navstatus == 'U' )
377 {
379 }
380 else
381 {
383 }
384}
385
386void QgsNmeaConnection::processGsvSentence( const char *data, int len )
387{
388 nmeaGPGSV result;
389 if ( nmea_parse_GPGSV( data, len, &result ) )
390 {
391 // for determining when to graph sat info
392 for ( int i = 0; i < NMEA_SATINPACK; ++i )
393 {
394 const nmeaSATELLITE currentSatellite = result.sat_data[i];
395 QgsSatelliteInfo satelliteInfo;
396 satelliteInfo.azimuth = currentSatellite.azimuth;
397 satelliteInfo.elevation = currentSatellite.elv;
398 satelliteInfo.id = currentSatellite.id;
399 satelliteInfo.inUse = false;
400 for ( int k = 0; k < mLastGPSInformation.satPrn.size(); ++k )
401 {
402 if ( mLastGPSInformation.satPrn.at( k ) == currentSatellite.id )
403 {
404 satelliteInfo.inUse = true;
405 }
406 }
407 satelliteInfo.signal = currentSatellite.sig;
408 satelliteInfo.satType = result.talkerId[1];
409
410 if ( result.talkerId[0] == 'G' )
411 {
412 if ( result.talkerId[1] == 'P' )
413 {
414 satelliteInfo.mConstellation = Qgis::GnssConstellation::Gps;
415 }
416 else if ( result.talkerId[1] == 'L' )
417 {
418 satelliteInfo.mConstellation = Qgis::GnssConstellation::Glonass;
419 }
420 else if ( result.talkerId[1] == 'A' )
421 {
422 satelliteInfo.mConstellation = Qgis::GnssConstellation::Galileo;
423 }
424 else if ( result.talkerId[1] == 'B' )
425 {
426 satelliteInfo.mConstellation = Qgis::GnssConstellation::BeiDou;
427 }
428 else if ( result.talkerId[1] == 'Q' )
429 {
430 satelliteInfo.mConstellation = Qgis::GnssConstellation::Qzss;
431 }
432 }
433
434 if ( satelliteInfo.satType == 'P' && satelliteInfo.id > 32 )
435 {
436 satelliteInfo.mConstellation = Qgis::GnssConstellation::Sbas;
437 satelliteInfo.satType = 'S';
438 satelliteInfo.id = currentSatellite.id + 87;
439 }
440
441 bool idAlreadyPresent = false;
442 if ( mLastGPSInformation.satellitesInView.size() > NMEA_SATINPACK )
443 {
444 for ( const QgsSatelliteInfo &existingSatInView : std::as_const( mLastGPSInformation.satellitesInView ) )
445 {
446 if ( existingSatInView.id == currentSatellite.id )
447 {
448 idAlreadyPresent = true;
449 break;
450 }
451 }
452 }
453
454 if ( !idAlreadyPresent && currentSatellite.azimuth > 0 && currentSatellite.elv > 0 )
455 {
456 mLastGPSInformation.satellitesInView.append( satelliteInfo );
457 }
458 }
459
460 }
461}
462
463void QgsNmeaConnection::processVtgSentence( const char *data, int len )
464{
465 nmeaGPVTG result;
466 if ( nmea_parse_GPVTG( data, len, &result ) )
467 {
468 mLastGPSInformation.speed = result.spk;
469 if ( !std::isnan( result.dir ) )
470 mLastGPSInformation.direction = result.dir;
471 }
472}
473
474void QgsNmeaConnection::processGsaSentence( const char *data, int len )
475{
477 {
478 //clear satellite information when a new series of packs arrives
483 }
484 nmeaGPGSA result;
485 if ( nmea_parse_GPGSA( data, len, &result ) )
486 {
487 // clear() on GGA
488 mLastGPSInformation.hdop = result.HDOP;
489 mLastGPSInformation.pdop = result.PDOP;
490 mLastGPSInformation.vdop = result.VDOP;
491 mLastGPSInformation.fixMode = result.fix_mode;
492 mLastGPSInformation.fixType = result.fix_type;
493
495 bool mixedConstellation = false;
496 for ( int i = 0; i < NMEA_MAXSAT; i++ )
497 {
498 if ( result.sat_prn[ i ] > 0 )
499 {
500 mLastGPSInformation.satPrn.append( result.sat_prn[ i ] );
502
504 if ( ( result.talkerId[0] == 'G' && result.talkerId[1] == 'L' ) || result.sat_prn[i] > 64 )
505 constellation = Qgis::GnssConstellation::Glonass;
506 else if ( result.sat_prn[i] >= 1 && result.sat_prn[i] <= 32 )
507 constellation = Qgis::GnssConstellation::Gps;
508 else if ( result.sat_prn[i] > 32 && result.sat_prn[i] <= 64 )
509 constellation = Qgis::GnssConstellation::Sbas;
510
511 // cppcheck-suppress identicalInnerCondition
512 if ( result.sat_prn[i] > 0 )
513 {
514 if ( mixedConstellation
515 || ( commonConstellation != Qgis::GnssConstellation::Unknown
516 && commonConstellation != constellation ) )
517 {
518 mixedConstellation = true;
519 }
520 else
521 {
522 commonConstellation = constellation;
523 }
524 }
525 }
526 }
527 if ( mixedConstellation )
528 commonConstellation = Qgis::GnssConstellation::Unknown;
529
530 switch ( result.fix_type )
531 {
532 case 1:
533 mLastGPSInformation.mConstellationFixStatus[ commonConstellation ] = Qgis::GpsFixStatus::NoFix;
534 break;
535
536 case 2:
537 mLastGPSInformation.mConstellationFixStatus[ commonConstellation ] = Qgis::GpsFixStatus::Fix2D;
538 break;
539
540 case 3:
541 mLastGPSInformation.mConstellationFixStatus[ commonConstellation ] = Qgis::GpsFixStatus::Fix3D;
542 break;
543 }
544 }
545}
GnssConstellation
GNSS constellation.
Definition qgis.h:1751
@ 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:1769
@ RTK
Real-time-kynematic.
@ DGPS
Differential GPS.
@ Simulation
Simulation mode.
@ FloatRTK
Float real-time-kynematic.
@ Manual
Manual input mode.
@ NoFix
GPS is not fixed.
@ NotValid
Navigation status not valid.
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.
void setNavigationStatus(Qgis::GpsNavigationStatus status)
Sets the navigation status.
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 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