QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
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 if ( mStatus != GPSDataReceived )
64 {
66 }
67
68 //append new data to the remaining results from last parseData() call
69 mStringBuffer.append( mSource->read( numBytes ) );
72 }
73}
74
76{
77 int endSentenceIndex = 0;
78 int dollarIndex;
79
80 while ( ( endSentenceIndex = mStringBuffer.indexOf( QLatin1String( "\r\n" ) ) ) && endSentenceIndex != -1 )
81 {
82 endSentenceIndex = mStringBuffer.indexOf( QLatin1String( "\r\n" ) );
83
84 dollarIndex = mStringBuffer.indexOf( QLatin1Char( '$' ) );
85 if ( endSentenceIndex == -1 )
86 {
87 break;
88 }
89
90
91 if ( endSentenceIndex >= dollarIndex )
92 {
93 if ( dollarIndex != -1 )
94 {
95 const QString substring = mStringBuffer.mid( dollarIndex, endSentenceIndex );
96 QByteArray ba = substring.toLocal8Bit();
97 if ( substring.startsWith( QLatin1String( "$GPGGA" ) ) || substring.startsWith( QLatin1String( "$GNGGA" ) ) )
98 {
99 QgsDebugMsgLevel( substring, 2 );
100 processGgaSentence( ba.data(), ba.length() );
102 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
103 }
104 else if ( substring.startsWith( QLatin1String( "$GPRMC" ) ) || substring.startsWith( QLatin1String( "$GNRMC" ) ) )
105 {
106 QgsDebugMsgLevel( substring, 2 );
107 processRmcSentence( ba.data(), ba.length() );
109 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
110 }
111 // GPS+SBAS GLONASS GALILEO BEIDOU QZSS;
112 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" ) ) )
113 {
114 QgsDebugMsgLevel( substring, 2 );
115 processGsvSentence( ba.data(), ba.length() );
117 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
118 }
119 else if ( substring.startsWith( QLatin1String( "$GPVTG" ) ) || substring.startsWith( QLatin1String( "$GNVTG" ) ) )
120 {
121 QgsDebugMsgLevel( substring, 2 );
122 processVtgSentence( ba.data(), ba.length() );
124 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
125 }
126 else if ( substring.startsWith( QLatin1String( "$GPGSA" ) ) || substring.startsWith( QLatin1String( "$GNGSA" ) ) )
127 {
128 QgsDebugMsgLevel( substring, 2 );
129 processGsaSentence( ba.data(), ba.length() );
131 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
132 }
133 else if ( substring.startsWith( QLatin1String( "$GPGST" ) ) || substring.startsWith( QLatin1String( "$GNGST" ) ) )
134 {
135 QgsDebugMsgLevel( substring, 2 );
136 processGstSentence( ba.data(), ba.length() );
138 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
139 }
140 else if ( substring.startsWith( QLatin1String( "$GPHDT" ) ) || substring.startsWith( QLatin1String( "$GNHDT" ) ) )
141 {
142 QgsDebugMsgLevel( substring, 2 );
143 processHdtSentence( ba.data(), ba.length() );
145 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
146 }
147 else if ( substring.startsWith( QLatin1String( "$HCHDG" ) ) )
148 {
149 QgsDebugMsgLevel( substring, 2 );
150 processHchdgSentence( ba.data(), ba.length() );
152 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
153 }
154 else if ( substring.startsWith( QLatin1String( "$HCHDT" ) ) )
155 {
156 QgsDebugMsgLevel( substring, 2 );
157 processHchdtSentence( ba.data(), ba.length() );
159 QgsDebugMsgLevel( QStringLiteral( "*******************GPS data received****************" ), 2 );
160 }
161 else
162 {
163 QgsDebugMsgLevel( QStringLiteral( "unknown nmea sentence: %1" ).arg( substring ), 2 );
164 }
165 emit nmeaSentenceReceived( substring ); // added to be able to save raw data
166 }
167 }
168 mStringBuffer.remove( 0, endSentenceIndex + 2 );
169 }
170}
171
172void QgsNmeaConnection::processGgaSentence( const char *data, int len )
173{
174 //GSA
176 //GSV
179 nmeaGPGGA result;
180 if ( nmea_parse_GPGGA( data, len, &result ) )
181 {
182 //update mLastGPSInformation
183 double longitude = result.lon;
184 if ( result.ew == 'W' )
185 {
186 longitude = -longitude;
187 }
188 double latitude = result.lat;
189 if ( result.ns == 'S' )
190 {
191 latitude = -latitude;
192 }
193
194 mLastGPSInformation.longitude = nmea_ndeg2degree( longitude );
195 mLastGPSInformation.latitude = nmea_ndeg2degree( latitude );
196 mLastGPSInformation.elevation = result.elv;
198
199 mLastGPSInformation.quality = result.sig;
200 if ( result.sig >= 0 && result.sig <= 8 )
201 {
203 }
204 else
205 {
207 }
208
209 // use GSA for satellites in use;
210 }
211}
212
213void QgsNmeaConnection::processGstSentence( const char *data, int len )
214{
215 nmeaGPGST result;
216 if ( nmea_parse_GPGST( data, len, &result ) )
217 {
218 //update mLastGPSInformation
219 const double sig_lat = result.sig_lat;
220 const double sig_lon = result.sig_lon;
221 const double sig_alt = result.sig_alt;
222
223 // Horizontal RMS
224 mLastGPSInformation.hacc = sqrt( ( pow( sig_lat, 2 ) + pow( sig_lon, 2 ) ) / 2.0 );
225 // Vertical RMS
226 mLastGPSInformation.vacc = sig_alt;
227 // 3D RMS
228 mLastGPSInformation.hvacc = sqrt( ( pow( sig_lat, 2 ) + pow( sig_lon, 2 ) + pow( sig_alt, 2 ) ) / 3.0 );
229 }
230}
231
232void QgsNmeaConnection::processHdtSentence( const char *data, int len )
233{
234 nmeaGPHDT result;
235 if ( nmea_parse_GPHDT( data, len, &result ) )
236 {
237 mLastGPSInformation.direction = result.heading;
238 }
239}
240
241void QgsNmeaConnection::processHchdgSentence( const char *data, int len )
242{
243 nmeaHCHDG result;
244 if ( nmea_parse_HCHDG( data, len, &result ) )
245 {
246 mLastGPSInformation.direction = result.mag_heading;
247 if ( result.ew_variation == 'E' )
248 mLastGPSInformation.direction += result.mag_variation;
249 else
250 mLastGPSInformation.direction -= result.mag_variation;
251 }
252}
253
254void QgsNmeaConnection::processHchdtSentence( const char *data, int len )
255{
256 nmeaHCHDT result;
257 if ( nmea_parse_HCHDT( data, len, &result ) )
258 {
259 mLastGPSInformation.direction = result.direction;
260 }
261}
262
263void QgsNmeaConnection::processRmcSentence( const char *data, int len )
264{
265 nmeaGPRMC result;
266 if ( nmea_parse_GPRMC( data, len, &result ) )
267 {
268 double longitude = result.lon;
269 if ( result.ew == 'W' )
270 {
271 longitude = -longitude;
272 }
273 double latitude = result.lat;
274 if ( result.ns == 'S' )
275 {
276 latitude = -latitude;
277 }
278 mLastGPSInformation.longitude = nmea_ndeg2degree( longitude );
279 mLastGPSInformation.latitude = nmea_ndeg2degree( latitude );
280 mLastGPSInformation.speed = KNOTS_TO_KMH * result.speed;
281 if ( !std::isnan( result.direction ) )
282 mLastGPSInformation.direction = result.direction;
283 mLastGPSInformation.status = result.status; // A,V
284
285 //date and time
286 const QDate date( result.utc.year + 1900, result.utc.mon + 1, result.utc.day );
287 const QTime time( result.utc.hour, result.utc.min, result.utc.sec, result.utc.msec ); // added msec part
288 if ( date.isValid() && time.isValid() )
289 {
290 mLastGPSInformation.utcDateTime.setTimeSpec( Qt::UTC );
291 mLastGPSInformation.utcDateTime.setDate( date );
292 mLastGPSInformation.utcDateTime.setTime( time );
293 QgsDebugMsgLevel( QStringLiteral( "utc time:" ), 2 );
295 QgsDebugMsgLevel( QStringLiteral( "local time:" ), 2 );
296 QgsDebugMsgLevel( mLastGPSInformation.utcDateTime.toLocalTime().toString(), 2 );
297 }
298
299 // convert mode to signal (aka quality) indicator
300 // (see https://gitlab.com/fhuberts/nmealib/-/blob/master/src/info.c#L27)
301 if ( result.status == 'A' )
302 {
303 if ( result.mode == 'A' )
304 {
307 }
308 else if ( result.mode == 'D' )
309 {
312 }
313 else if ( result.mode == 'P' )
314 {
317 }
318 else if ( result.mode == 'R' )
319 {
322 }
323 else if ( result.mode == 'F' )
324 {
327 }
328 else if ( result.mode == 'E' )
329 {
332 }
333 else if ( result.mode == 'M' )
334 {
337 }
338 else if ( result.mode == 'S' )
339 {
342 }
343 else
344 {
347 }
348 }
349 else
350 {
353 }
354 }
355}
356
357void QgsNmeaConnection::processGsvSentence( const char *data, int len )
358{
359 nmeaGPGSV result;
360 if ( nmea_parse_GPGSV( data, len, &result ) )
361 {
362 //clear satellite information when a new series of packs arrives
363 // clear() on GGA
364
365 // for determining when to graph sat info
366 mLastGPSInformation.satInfoComplete = ( result.pack_index == result.pack_count );
367
368 for ( int i = 0; i < NMEA_SATINPACK; ++i )
369 {
370 const nmeaSATELLITE currentSatellite = result.sat_data[i];
371 QgsSatelliteInfo satelliteInfo;
372 satelliteInfo.azimuth = currentSatellite.azimuth;
373 satelliteInfo.elevation = currentSatellite.elv;
374 satelliteInfo.id = currentSatellite.id;
375 satelliteInfo.inUse = false;
376 for ( int k = 0; k < mLastGPSInformation.satPrn.size(); ++k )
377 {
378 if ( mLastGPSInformation.satPrn.at( k ) == currentSatellite.id )
379 {
380 satelliteInfo.inUse = true;
381 }
382 }
383 satelliteInfo.signal = currentSatellite.sig;
384 satelliteInfo.satType = result.pack_type;
385 if ( satelliteInfo.satType == 'P' && satelliteInfo.id > 32 )
386 {
387 satelliteInfo.satType = 'S';
388 satelliteInfo.id = currentSatellite.id + 87;
389 }
390
391 bool idAlreadyPresent = false;
392 if ( mLastGPSInformation.satellitesInView.size() > NMEA_SATINPACK )
393 {
394 for ( const QgsSatelliteInfo &existingSatInView : std::as_const( mLastGPSInformation.satellitesInView ) )
395 {
396 if ( existingSatInView.id == currentSatellite.id )
397 {
398 idAlreadyPresent = true;
399 break;
400 }
401 }
402 }
403
404 if ( currentSatellite.sig > 0 )
405 {
406 satelliteInfo.inUse = 1; // check where used ???? (+=1)
407 }
408 if ( !idAlreadyPresent && currentSatellite.azimuth > 0 && currentSatellite.elv > 0 )
409 {
410 mLastGPSInformation.satellitesInView.append( satelliteInfo );
411 }
412 }
413
414 }
415}
416
417void QgsNmeaConnection::processVtgSentence( const char *data, int len )
418{
419 nmeaGPVTG result;
420 if ( nmea_parse_GPVTG( data, len, &result ) )
421 {
422 mLastGPSInformation.speed = result.spk;
423 }
424}
425
426void QgsNmeaConnection::processGsaSentence( const char *data, int len )
427{
428 nmeaGPGSA result;
429 if ( nmea_parse_GPGSA( data, len, &result ) )
430 {
431 // clear() on GGA
432 mLastGPSInformation.hdop = result.HDOP;
433 mLastGPSInformation.pdop = result.PDOP;
434 mLastGPSInformation.vdop = result.VDOP;
435 mLastGPSInformation.fixMode = result.fix_mode;
436 mLastGPSInformation.fixType = result.fix_type;
437 for ( int i = 0; i < NMEA_MAXSAT; i++ )
438 {
439 if ( result.sat_prn[ i ] > 0 )
440 {
441 mLastGPSInformation.satPrn.append( result.sat_prn[ i ] );
443 }
444 }
445 }
446}
GpsQualityIndicator
GPS signal quality indicator.
Definition: qgis.h:887
@ RTK
Real-time-kynematic.
@ DGPS
Differential GPS.
@ Simulation
Simulation mode.
@ FloatRTK
Float real-time-kynematic.
@ Manual
Manual input mode.
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)
std::unique_ptr< QIODevice > mSource
Data source (e.g. serial device, socket, file,...)
Status mStatus
Connection status.
void stateChanged(const QgsGpsInformation &info)
double vdop
Vertical dilution of precision.
double direction
The bearing measured in degrees clockwise from true north to the direction of travel.
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.
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 (Diff.
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