QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
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
239 // use GSA for satellites in use;
240 }
241}
242
243void QgsNmeaConnection::processGstSentence( const char *data, int len )
244{
245 nmeaGPGST result;
246 if ( nmea_parse_GPGST( data, len, &result ) )
247 {
248 //update mLastGPSInformation
249 const double sig_lat = result.sig_lat;
250 const double sig_lon = result.sig_lon;
251 const double sig_alt = result.sig_alt;
252
253 // Horizontal RMS
254 mLastGPSInformation.hacc = sqrt( ( pow( sig_lat, 2 ) + pow( sig_lon, 2 ) ) / 2.0 );
255 // Vertical RMS
256 mLastGPSInformation.vacc = sig_alt;
257 // 3D RMS
258 mLastGPSInformation.hvacc = sqrt( ( pow( sig_lat, 2 ) + pow( sig_lon, 2 ) + pow( sig_alt, 2 ) ) / 3.0 );
259 }
260}
261
262void QgsNmeaConnection::processHdtSentence( const char *data, int len )
263{
264 nmeaGPHDT result;
265 if ( nmea_parse_GPHDT( data, len, &result ) )
266 {
267 mLastGPSInformation.direction = result.heading;
268 }
269}
270
271void QgsNmeaConnection::processHchdgSentence( const char *data, int len )
272{
273 nmeaHCHDG result;
274 if ( nmea_parse_HCHDG( data, len, &result ) )
275 {
276 mLastGPSInformation.direction = result.mag_heading;
277 if ( result.ew_variation == 'E' )
278 mLastGPSInformation.direction += result.mag_variation;
279 else
280 mLastGPSInformation.direction -= result.mag_variation;
281 }
282}
283
284void QgsNmeaConnection::processRmcSentence( const char *data, int len )
285{
286 nmeaGPRMC result;
287 if ( nmea_parse_GPRMC( data, len, &result ) )
288 {
289 double longitude = result.lon;
290 if ( result.ew == 'W' )
291 {
292 longitude = -longitude;
293 }
294 double latitude = result.lat;
295 if ( result.ns == 'S' )
296 {
297 latitude = -latitude;
298 }
299 mLastGPSInformation.longitude = nmea_ndeg2degree( longitude );
300 mLastGPSInformation.latitude = nmea_ndeg2degree( latitude );
301 mLastGPSInformation.speed = KNOTS_TO_KMH * result.speed;
302 if ( !std::isnan( result.direction ) )
303 mLastGPSInformation.direction = result.direction;
304 mLastGPSInformation.status = result.status; // A,V
305
306 const QDate date( result.utc.year + 1900, result.utc.mon + 1, result.utc.day );
307 const QTime time( result.utc.hour, result.utc.min, result.utc.sec, result.utc.msec );
308 if ( date.isValid() && time.isValid() )
309 {
310 mLastGPSInformation.utcTime = time;
311 mLastGPSInformation.utcDateTime.setTimeSpec( Qt::UTC );
312 mLastGPSInformation.utcDateTime.setDate( date );
313 mLastGPSInformation.utcDateTime.setTime( time );
314 QgsDebugMsgLevel( u"utc date/time:"_s, 2 );
315 QgsDebugMsgLevel( mLastGPSInformation.utcDateTime.toString(), 2 );
316 QgsDebugMsgLevel( u"local date/time:"_s, 2 );
317 QgsDebugMsgLevel( mLastGPSInformation.utcDateTime.toLocalTime().toString(), 2 );
318 }
319
320 // convert mode to signal (aka quality) indicator
321 // (see https://gitlab.com/fhuberts/nmealib/-/blob/master/src/info.c#L27)
322 // UM98x Status == D (Differential)
323 if ( result.status == 'A' || result.status == 'D' )
324 {
325 if ( result.mode == 'A' )
326 {
327 mLastGPSInformation.quality = static_cast<int>( Qgis::GpsQualityIndicator::GPS );
329 }
330 else if ( result.mode == 'D' )
331 {
332 mLastGPSInformation.quality = static_cast<int>( Qgis::GpsQualityIndicator::DGPS );
334 }
335 else if ( result.mode == 'P' )
336 {
337 mLastGPSInformation.quality = static_cast<int>( Qgis::GpsQualityIndicator::PPS );
339 }
340 else if ( result.mode == 'R' )
341 {
342 mLastGPSInformation.quality = static_cast<int>( Qgis::GpsQualityIndicator::RTK );
344 }
345 else if ( result.mode == 'F' )
346 {
347 mLastGPSInformation.quality = static_cast<int>( Qgis::GpsQualityIndicator::FloatRTK );
349 }
350 else if ( result.mode == 'E' )
351 {
352 mLastGPSInformation.quality = static_cast<int>( Qgis::GpsQualityIndicator::Estimated );
354 }
355 else if ( result.mode == 'M' )
356 {
357 mLastGPSInformation.quality = static_cast<int>( Qgis::GpsQualityIndicator::Manual );
359 }
360 else if ( result.mode == 'S' )
361 {
364 }
365 else
366 {
367 mLastGPSInformation.quality = static_cast<int>( Qgis::GpsQualityIndicator::Unknown );
369 }
370 }
371 else if ( result.status == 'V' )
372 {
373 mLastGPSInformation.quality = static_cast<int>( Qgis::GpsQualityIndicator::Invalid );
375 }
376 // for other cases: quality and qualityIndicator read by GGA
377 }
378
379 if ( result.navstatus == 'S' )
380 {
382 }
383 else if ( result.navstatus == 'C' )
384 {
386 }
387 else if ( result.navstatus == 'U' )
388 {
390 }
391 else
392 {
394 }
395}
396
397void QgsNmeaConnection::processGsvSentence( const char *data, int len )
398{
399 nmeaGPGSV result;
400 if ( nmea_parse_GPGSV( data, len, &result ) )
401 {
402 // for determining when to graph sat info
403 for ( int i = 0; i < NMEA_SATINPACK; ++i )
404 {
405 const nmeaSATELLITE currentSatellite = result.sat_data[i];
406 QgsSatelliteInfo satelliteInfo;
407 satelliteInfo.azimuth = currentSatellite.azimuth;
408 satelliteInfo.elevation = currentSatellite.elv;
409 satelliteInfo.id = currentSatellite.id;
410 satelliteInfo.inUse = false;
411 for ( int k = 0; k < mLastGPSInformation.satPrn.size(); ++k )
412 {
413 if ( mLastGPSInformation.satPrn.at( k ) == currentSatellite.id )
414 {
415 satelliteInfo.inUse = true;
416 }
417 }
418 satelliteInfo.signal = currentSatellite.sig;
419 satelliteInfo.satType = result.talkerId[1];
420
421 if ( result.talkerId[0] == 'G' )
422 {
423 if ( result.talkerId[1] == 'P' )
424 {
425 satelliteInfo.mConstellation = Qgis::GnssConstellation::Gps;
426 }
427 else if ( result.talkerId[1] == 'L' )
428 {
429 satelliteInfo.mConstellation = Qgis::GnssConstellation::Glonass;
430 }
431 else if ( result.talkerId[1] == 'A' )
432 {
433 satelliteInfo.mConstellation = Qgis::GnssConstellation::Galileo;
434 }
435 else if ( result.talkerId[1] == 'B' )
436 {
437 satelliteInfo.mConstellation = Qgis::GnssConstellation::BeiDou;
438 }
439 else if ( result.talkerId[1] == 'Q' )
440 {
441 satelliteInfo.mConstellation = Qgis::GnssConstellation::Qzss;
442 }
443 }
444
445 if ( satelliteInfo.satType == 'P' && satelliteInfo.id > 32 )
446 {
447 satelliteInfo.mConstellation = Qgis::GnssConstellation::Sbas;
448 satelliteInfo.satType = 'S';
449 satelliteInfo.id = currentSatellite.id + 87;
450 }
451
452 bool idAlreadyPresent = false;
453 if ( mLastGPSInformation.satellitesInView.size() > NMEA_SATINPACK )
454 {
455 for ( int i = 0; i < mLastGPSInformation.satellitesInView.size(); ++i )
456 {
457 QgsSatelliteInfo &existingSatInView = mLastGPSInformation.satellitesInView[i];
458 if ( existingSatInView.id == currentSatellite.id )
459 {
460 idAlreadyPresent = true;
461 // Signal averaging
462 if ( existingSatInView.signal == 0 )
463 {
464 existingSatInView.signal = currentSatellite.sig;
465 }
466 else if ( currentSatellite.sig != 0 )
467 {
468 existingSatInView.signal = ( existingSatInView.signal + currentSatellite.sig ) / 2;
469 }
470 break;
471 }
472 }
473 }
474
475 if ( !idAlreadyPresent && currentSatellite.azimuth > 0 && currentSatellite.elv > 0 )
476 {
477 mLastGPSInformation.satellitesInView.append( satelliteInfo );
478 }
479 }
480 }
481}
482
483void QgsNmeaConnection::processVtgSentence( const char *data, int len )
484{
485 nmeaGPVTG result;
486 if ( nmea_parse_GPVTG( data, len, &result ) )
487 {
488 mLastGPSInformation.speed = result.spk;
489 if ( !std::isnan( result.dir ) )
490 mLastGPSInformation.direction = result.dir;
491 }
492}
493
494void QgsNmeaConnection::processGsaSentence( const char *data, int len )
495{
496 if ( mLastGPSInformation.satInfoComplete )
497 {
498 //clear satellite information when a new series of packs arrives
499 mLastGPSInformation.satPrn.clear();
500 mLastGPSInformation.satellitesInView.clear();
501 mLastGPSInformation.satellitesUsed = 0;
502 mLastGPSInformation.satInfoComplete = false;
503 }
504 nmeaGPGSA result;
505 if ( nmea_parse_GPGSA( data, len, &result ) )
506 {
507 // clear() on GGA
508 mLastGPSInformation.hdop = result.HDOP;
509 mLastGPSInformation.pdop = result.PDOP;
510 mLastGPSInformation.vdop = result.VDOP;
511 mLastGPSInformation.fixMode = result.fix_mode;
512 mLastGPSInformation.fixType = result.fix_type;
513
515 bool mixedConstellation = false;
516 for ( int i = 0; i < NMEA_MAXSAT; i++ )
517 {
518 if ( result.sat_prn[i] > 0 )
519 {
520 mLastGPSInformation.satPrn.append( result.sat_prn[i] );
521 mLastGPSInformation.satellitesUsed += 1;
522
524 if ( ( result.talkerId[0] == 'G' && result.talkerId[1] == 'L' ) || result.sat_prn[i] > 64 )
525 constellation = Qgis::GnssConstellation::Glonass;
526 else if ( result.sat_prn[i] >= 1 && result.sat_prn[i] <= 32 )
527 constellation = Qgis::GnssConstellation::Gps;
528 else if ( result.sat_prn[i] > 32 && result.sat_prn[i] <= 64 )
529 constellation = Qgis::GnssConstellation::Sbas;
530
531 // cppcheck-suppress identicalInnerCondition
532 if ( result.sat_prn[i] > 0 )
533 {
534 if ( mixedConstellation || ( commonConstellation != Qgis::GnssConstellation::Unknown && 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:1983
@ Gps
Global Positioning System (GPS).
Definition qgis.h:1985
@ Glonass
Global Navigation Satellite System (GLONASS).
Definition qgis.h:1986
@ Unknown
Unknown/other system.
Definition qgis.h:1984
@ Qzss
Quasi Zenith Satellite System (QZSS).
Definition qgis.h:1989
GpsQualityIndicator
GPS signal quality indicator.
Definition qgis.h:2001
@ RTK
Real-time-kynematic.
Definition qgis.h:2007
@ DGPS
Differential GPS.
Definition qgis.h:2005
@ Estimated
Estimated.
Definition qgis.h:2009
@ Simulation
Simulation mode.
Definition qgis.h:2011
@ FloatRTK
Float real-time-kynematic.
Definition qgis.h:2008
@ Manual
Manual input mode.
Definition qgis.h:2010
@ NoFix
GPS is not fixed.
Definition qgis.h:1970
@ Fix2D
2D fix
Definition qgis.h:1971
@ Fix3D
3D fix
Definition qgis.h:1972
@ NotValid
Navigation status not valid.
Definition qgis.h:2022
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:63
#define KNOTS_TO_KMH