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