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