QGIS API Documentation 3.39.0-Master (47f7b3a4989)
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
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 QgsDebugMsgLevel( QStringLiteral( "Got %1 NMEA bytes" ).arg( numBytes ), 3 );
64 QgsDebugMsgLevel( QStringLiteral( "Current NMEA device status is %1" ).arg( mStatus ), 3 );
65 if ( mStatus != GPSDataReceived )
66 {
67 QgsDebugMsgLevel( QStringLiteral( "Setting device status to DataReceived" ), 3 );
69 }
70
71 //append new data to the remaining results from last parseData() call
72 mStringBuffer.append( mSource->read( numBytes ) );
74 QgsDebugMsgLevel( QStringLiteral( "Processed buffer" ), 3 );
75
76 QgsDebugMsgLevel( QStringLiteral( "New status is %1" ).arg( mStatus ), 3 );
77 if ( mStatus == GPSDataReceived )
78 {
80 }
81 }
82}
83
85{
86 int endSentenceIndex = 0;
87 int dollarIndex;
88
89 while ( ( endSentenceIndex = mStringBuffer.indexOf( QLatin1String( "\r\n" ) ) ) && endSentenceIndex != -1 )
90 {
91 endSentenceIndex = mStringBuffer.indexOf( QLatin1String( "\r\n" ) );
92
93 dollarIndex = mStringBuffer.indexOf( QLatin1Char( '$' ) );
94 if ( endSentenceIndex == -1 )
95 {
96 break;
97 }
98
99 if ( endSentenceIndex >= dollarIndex )
100 {
101 if ( dollarIndex != -1 )
102 {
103 const QString substring = mStringBuffer.mid( dollarIndex, endSentenceIndex );
104 QByteArray ba = substring.toLocal8Bit();
105 if ( substring.startsWith( QLatin1String( "$GPGGA" ) ) || substring.startsWith( QLatin1String( "$GNGGA" ) ) )
106 {
107 QgsDebugMsgLevel( substring, 2 );
109 processGgaSentence( ba.data(), ba.length() );
111 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
112 }
113 else if ( substring.startsWith( QLatin1String( "$GPRMC" ) ) || substring.startsWith( QLatin1String( "$GNRMC" ) ) )
114 {
115 QgsDebugMsgLevel( substring, 2 );
117 processRmcSentence( ba.data(), ba.length() );
119 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
120 }
121 // GPS+SBAS GLONASS GALILEO BEIDOU QZSS;
122 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" ) ) )
123 {
124 QgsDebugMsgLevel( substring, 2 );
126 processGsvSentence( ba.data(), ba.length() );
128 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
129 }
130 else if ( substring.startsWith( QLatin1String( "$GPVTG" ) ) || substring.startsWith( QLatin1String( "$GNVTG" ) ) )
131 {
132 QgsDebugMsgLevel( substring, 2 );
134 processVtgSentence( ba.data(), ba.length() );
136 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
137 }
138 else if ( substring.startsWith( QLatin1String( "$GPGSA" ) ) || substring.startsWith( QLatin1String( "$GNGSA" ) ) || substring.startsWith( QLatin1String( "$GLGSA" ) ) )
139 {
140 QgsDebugMsgLevel( substring, 2 );
141 processGsaSentence( ba.data(), ba.length() );
143 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
144 }
145 else if ( substring.startsWith( QLatin1String( "$GPGST" ) ) || substring.startsWith( QLatin1String( "$GNGST" ) ) )
146 {
147 QgsDebugMsgLevel( substring, 2 );
149 processGstSentence( ba.data(), ba.length() );
151 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
152 }
153 else if ( substring.startsWith( QLatin1String( "$GPHDT" ) ) || substring.startsWith( QLatin1String( "$GNHDT" ) ) )
154 {
155 QgsDebugMsgLevel( substring, 2 );
157 processHdtSentence( ba.data(), ba.length() );
159 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
160 }
161 else if ( substring.startsWith( QLatin1String( "$HCHDG" ) ) )
162 {
163 QgsDebugMsgLevel( substring, 2 );
165 processHchdgSentence( ba.data(), ba.length() );
167 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
168 }
169 else if ( substring.startsWith( QLatin1String( "$HCHDT" ) ) )
170 {
171 QgsDebugMsgLevel( substring, 2 );
173 processHchdtSentence( ba.data(), ba.length() );
175 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
176 }
177 else
178 {
180 QgsDebugMsgLevel( QStringLiteral( "unknown nmea sentence: %1" ).arg( substring ), 2 );
181 }
182 emit nmeaSentenceReceived( substring ); // added to be able to save raw data
183 }
184 }
185 mStringBuffer.remove( 0, endSentenceIndex + 2 );
186 }
187}
188
189void QgsNmeaConnection::processGgaSentence( const char *data, int len )
190{
191 nmeaGPGGA result;
192 if ( nmea_parse_GPGGA( data, len, &result ) )
193 {
194 //update mLastGPSInformation
195 double longitude = result.lon;
196 if ( result.ew == 'W' )
197 {
198 longitude = -longitude;
199 }
200 double latitude = result.lat;
201 if ( result.ns == 'S' )
202 {
203 latitude = -latitude;
204 }
205
206 mLastGPSInformation.longitude = nmea_ndeg2degree( longitude );
207 mLastGPSInformation.latitude = nmea_ndeg2degree( latitude );
208 mLastGPSInformation.elevation = result.elv;
210
211 const QTime time( result.utc.hour, result.utc.min, result.utc.sec, result.utc.msec );
212 if ( time.isValid() )
213 {
215 if ( mLastGPSInformation.utcDateTime.isValid() )
216 {
217 mLastGPSInformation.utcDateTime.setTimeSpec( Qt::UTC );
218 mLastGPSInformation.utcDateTime.setTime( time );
219 }
220 QgsDebugMsgLevel( QStringLiteral( "utc time:" ), 2 );
222 }
223
224 mLastGPSInformation.quality = result.sig;
225 if ( result.sig >= 0 && result.sig <= 8 )
226 {
228 }
229 else
230 {
232 }
233
234 // use GSA for satellites in use;
235 }
236}
237
238void QgsNmeaConnection::processGstSentence( const char *data, int len )
239{
240 nmeaGPGST result;
241 if ( nmea_parse_GPGST( data, len, &result ) )
242 {
243 //update mLastGPSInformation
244 const double sig_lat = result.sig_lat;
245 const double sig_lon = result.sig_lon;
246 const double sig_alt = result.sig_alt;
247
248 // Horizontal RMS
249 mLastGPSInformation.hacc = sqrt( ( pow( sig_lat, 2 ) + pow( sig_lon, 2 ) ) / 2.0 );
250 // Vertical RMS
251 mLastGPSInformation.vacc = sig_alt;
252 // 3D RMS
253 mLastGPSInformation.hvacc = sqrt( ( pow( sig_lat, 2 ) + pow( sig_lon, 2 ) + pow( sig_alt, 2 ) ) / 3.0 );
254 }
255}
256
257void QgsNmeaConnection::processHdtSentence( const char *data, int len )
258{
259 nmeaGPHDT result;
260 if ( nmea_parse_GPHDT( data, len, &result ) )
261 {
262 mLastGPSInformation.direction = result.heading;
263 }
264}
265
266void QgsNmeaConnection::processHchdgSentence( const char *data, int len )
267{
268 nmeaHCHDG result;
269 if ( nmea_parse_HCHDG( data, len, &result ) )
270 {
271 mLastGPSInformation.direction = result.mag_heading;
272 if ( result.ew_variation == 'E' )
273 mLastGPSInformation.direction += result.mag_variation;
274 else
275 mLastGPSInformation.direction -= result.mag_variation;
276 }
277}
278
279void QgsNmeaConnection::processHchdtSentence( const char *data, int len )
280{
281 nmeaHCHDT result;
282 if ( nmea_parse_HCHDT( data, len, &result ) )
283 {
284 mLastGPSInformation.direction = result.direction;
285 }
286}
287
288void QgsNmeaConnection::processRmcSentence( const char *data, int len )
289{
290 nmeaGPRMC result;
291 if ( nmea_parse_GPRMC( data, len, &result ) )
292 {
293 double longitude = result.lon;
294 if ( result.ew == 'W' )
295 {
296 longitude = -longitude;
297 }
298 double latitude = result.lat;
299 if ( result.ns == 'S' )
300 {
301 latitude = -latitude;
302 }
303 mLastGPSInformation.longitude = nmea_ndeg2degree( longitude );
304 mLastGPSInformation.latitude = nmea_ndeg2degree( latitude );
305 mLastGPSInformation.speed = KNOTS_TO_KMH * result.speed;
306 if ( !std::isnan( result.direction ) )
307 mLastGPSInformation.direction = result.direction;
308 mLastGPSInformation.status = result.status; // A,V
309
310 const QDate date( result.utc.year + 1900, result.utc.mon + 1, result.utc.day );
311 const QTime time( result.utc.hour, result.utc.min, result.utc.sec, result.utc.msec );
312 if ( date.isValid() && time.isValid() )
313 {
315 mLastGPSInformation.utcDateTime.setTimeSpec( Qt::UTC );
316 mLastGPSInformation.utcDateTime.setDate( date );
317 mLastGPSInformation.utcDateTime.setTime( time );
318 QgsDebugMsgLevel( QStringLiteral( "utc date/time:" ), 2 );
320 QgsDebugMsgLevel( QStringLiteral( "local date/time:" ), 2 );
321 QgsDebugMsgLevel( mLastGPSInformation.utcDateTime.toLocalTime().toString(), 2 );
322 }
323
324 // convert mode to signal (aka quality) indicator
325 // (see https://gitlab.com/fhuberts/nmealib/-/blob/master/src/info.c#L27)
326 if ( result.status == 'A' )
327 {
328 if ( result.mode == 'A' )
329 {
332 }
333 else if ( result.mode == 'D' )
334 {
337 }
338 else if ( result.mode == 'P' )
339 {
342 }
343 else if ( result.mode == 'R' )
344 {
347 }
348 else if ( result.mode == 'F' )
349 {
352 }
353 else if ( result.mode == 'E' )
354 {
357 }
358 else if ( result.mode == 'M' )
359 {
362 }
363 else if ( result.mode == 'S' )
364 {
367 }
368 else
369 {
372 }
373 }
374 else
375 {
378 }
379 }
380
381 if ( result.navstatus == 'S' )
382 {
384 }
385 else if ( result.navstatus == 'C' )
386 {
388 }
389 else if ( result.navstatus == 'U' )
390 {
392 }
393 else
394 {
396 }
397}
398
399void QgsNmeaConnection::processGsvSentence( const char *data, int len )
400{
401 nmeaGPGSV result;
402 if ( nmea_parse_GPGSV( data, len, &result ) )
403 {
404 // for determining when to graph sat info
405 for ( int i = 0; i < NMEA_SATINPACK; ++i )
406 {
407 const nmeaSATELLITE currentSatellite = result.sat_data[i];
408 QgsSatelliteInfo satelliteInfo;
409 satelliteInfo.azimuth = currentSatellite.azimuth;
410 satelliteInfo.elevation = currentSatellite.elv;
411 satelliteInfo.id = currentSatellite.id;
412 satelliteInfo.inUse = false;
413 for ( int k = 0; k < mLastGPSInformation.satPrn.size(); ++k )
414 {
415 if ( mLastGPSInformation.satPrn.at( k ) == currentSatellite.id )
416 {
417 satelliteInfo.inUse = true;
418 }
419 }
420 satelliteInfo.signal = currentSatellite.sig;
421 satelliteInfo.satType = result.pack_type;
422
423 if ( result.pack_type == 'P' )
424 {
425 satelliteInfo.mConstellation = Qgis::GnssConstellation::Gps;
426 }
427 else if ( result.pack_type == 'L' )
428 {
429 satelliteInfo.mConstellation = Qgis::GnssConstellation::Glonass;
430 }
431 else if ( result.pack_type == 'A' )
432 {
433 satelliteInfo.mConstellation = Qgis::GnssConstellation::Galileo;
434 }
435 else if ( result.pack_type == 'B' )
436 {
437 satelliteInfo.mConstellation = Qgis::GnssConstellation::BeiDou;
438 }
439 else if ( result.pack_type == 'Q' )
440 {
441 satelliteInfo.mConstellation = Qgis::GnssConstellation::Qzss;
442 }
443
444 if ( satelliteInfo.satType == 'P' && satelliteInfo.id > 32 )
445 {
446 satelliteInfo.mConstellation = Qgis::GnssConstellation::Sbas;
447 satelliteInfo.satType = 'S';
448 satelliteInfo.id = currentSatellite.id + 87;
449 }
450
451 bool idAlreadyPresent = false;
452 if ( mLastGPSInformation.satellitesInView.size() > NMEA_SATINPACK )
453 {
454 for ( const QgsSatelliteInfo &existingSatInView : std::as_const( mLastGPSInformation.satellitesInView ) )
455 {
456 if ( existingSatInView.id == currentSatellite.id )
457 {
458 idAlreadyPresent = true;
459 break;
460 }
461 }
462 }
463
464 if ( !idAlreadyPresent && currentSatellite.azimuth > 0 && currentSatellite.elv > 0 )
465 {
466 mLastGPSInformation.satellitesInView.append( satelliteInfo );
467 }
468 }
469
470 }
471}
472
473void QgsNmeaConnection::processVtgSentence( const char *data, int len )
474{
475 nmeaGPVTG result;
476 if ( nmea_parse_GPVTG( data, len, &result ) )
477 {
478 mLastGPSInformation.speed = result.spk;
479 if ( !std::isnan( result.dir ) )
480 mLastGPSInformation.direction = result.dir;
481 }
482}
483
484void QgsNmeaConnection::processGsaSentence( const char *data, int len )
485{
487 {
488 //clear satellite information when a new series of packs arrives
493 }
494 nmeaGPGSA result;
495 if ( nmea_parse_GPGSA( data, len, &result ) )
496 {
497 // clear() on GGA
498 mLastGPSInformation.hdop = result.HDOP;
499 mLastGPSInformation.pdop = result.PDOP;
500 mLastGPSInformation.vdop = result.VDOP;
501 mLastGPSInformation.fixMode = result.fix_mode;
502 mLastGPSInformation.fixType = result.fix_type;
503
505 bool mixedConstellation = false;
506 for ( int i = 0; i < NMEA_MAXSAT; i++ )
507 {
508 if ( result.sat_prn[ i ] > 0 )
509 {
510 mLastGPSInformation.satPrn.append( result.sat_prn[ i ] );
512
514 if ( result.pack_type == 'L' || result.sat_prn[i] > 64 )
515 constellation = Qgis::GnssConstellation::Glonass;
516 else if ( result.sat_prn[i] >= 1 && result.sat_prn[i] <= 32 )
517 constellation = Qgis::GnssConstellation::Gps;
518 else if ( result.sat_prn[i] > 32 && result.sat_prn[i] <= 64 )
519 constellation = Qgis::GnssConstellation::Sbas;
520
521 // cppcheck-suppress identicalInnerCondition
522 if ( result.sat_prn[i] > 0 )
523 {
524 if ( mixedConstellation
525 || ( commonConstellation != Qgis::GnssConstellation::Unknown
526 && commonConstellation != constellation ) )
527 {
528 mixedConstellation = true;
529 }
530 else
531 {
532 commonConstellation = constellation;
533 }
534 }
535 }
536 }
537 if ( mixedConstellation )
538 commonConstellation = Qgis::GnssConstellation::Unknown;
539
540 switch ( result.fix_type )
541 {
542 case 1:
543 mLastGPSInformation.mConstellationFixStatus[ commonConstellation ] = Qgis::GpsFixStatus::NoFix;
544 break;
545
546 case 2:
547 mLastGPSInformation.mConstellationFixStatus[ commonConstellation ] = Qgis::GpsFixStatus::Fix2D;
548 break;
549
550 case 3:
551 mLastGPSInformation.mConstellationFixStatus[ commonConstellation ] = Qgis::GpsFixStatus::Fix3D;
552 break;
553 }
554 }
555}
GnssConstellation
GNSS constellation.
Definition qgis.h:1574
@ 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:1592
@ 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 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