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