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