Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qnmeasatelliteinfosource.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
6#include "private/qnmeasatelliteinfosource_p.h"
7#include <QtPositioning/private/qgeosatelliteinfo_p.h>
8#include <QtPositioning/private/qgeosatelliteinfosource_p.h>
9#include <QtPositioning/private/qlocationutils_p.h>
10
11#include <QIODevice>
12#include <QBasicTimer>
13#include <QTimerEvent>
14#include <QTimer>
15#include <array>
16#include <QDebug>
17#include <QtCore/QtNumeric>
18
19QT_BEGIN_NAMESPACE
20
21#if USE_SATELLITE_NMEA_PIMPL
22class QGeoSatelliteInfoPrivateNmea : public QGeoSatelliteInfoPrivate
23{
24public:
25 QGeoSatelliteInfoPrivateNmea(const QGeoSatelliteInfoPrivate &other);
26 QGeoSatelliteInfoPrivateNmea(const QGeoSatelliteInfoPrivateNmea &other);
27 virtual ~QGeoSatelliteInfoPrivateNmea();
28
29 QList<QByteArray> nmeaSentences;
30};
31
32QGeoSatelliteInfoPrivateNmea::QGeoSatelliteInfoPrivateNmea(const QGeoSatelliteInfoPrivate &other)
33 : QGeoSatelliteInfoPrivate(other)
34{
35}
36
37QGeoSatelliteInfoPrivateNmea::QGeoSatelliteInfoPrivateNmea(const QGeoSatelliteInfoPrivateNmea &other)
38 : QGeoSatelliteInfoPrivate(other)
39{
40 nmeaSentences = other.nmeaSentences;
41}
42
43QGeoSatelliteInfoPrivateNmea::~QGeoSatelliteInfoPrivateNmea() {}
44#else
45typedef QGeoSatelliteInfoPrivate QGeoSatelliteInfoPrivateNmea;
46#endif
47
48QNmeaSatelliteInfoSourcePrivate::QNmeaSatelliteInfoSourcePrivate(QNmeaSatelliteInfoSource *parent, QNmeaSatelliteInfoSource::UpdateMode updateMode)
49 : m_source(parent),
50 m_updateMode(updateMode)
51{
52}
53
54void QNmeaSatelliteInfoSourcePrivate::notifyNewUpdate()
55{
56 if (m_pendingUpdate.isValid() && m_pendingUpdate.isFresh()) {
57 if (m_requestTimer && m_requestTimer->isActive()) { // User called requestUpdate()
58 m_requestTimer->stop();
59 emitUpdated(m_pendingUpdate, true);
60 } else if (m_invokedStart) { // user called startUpdates()
61 if (m_updateTimer && m_updateTimer->isActive()) { // update interval > 0
62 // for periodic updates, only want the most recent update
63 if (m_noUpdateLastInterval) {
64 // if the update was invalid when timerEvent was last called, a valid update
65 // should be sent ASAP
66 emitPendingUpdate(); // m_noUpdateLastInterval handled in there.
67 }
68 } else { // update interval <= 0, send anything new ASAP
69 m_noUpdateLastInterval = !emitUpdated(m_pendingUpdate, false);
70 }
71 }
72 }
73}
74
75void QNmeaSatelliteInfoSourcePrivate::processNmeaData(QNmeaSatelliteInfoUpdate &updateInfo)
76{
77 char buf[1024];
78 qint64 size = m_device->readLine(buf, sizeof(buf));
79 if (size <= 0)
80 return;
81
82 QList<int> satInUse;
83 const auto satSystemType = m_source->parseSatellitesInUseFromNmea(QByteArrayView{buf,static_cast<qsizetype>(size)},
84 satInUse);
85 if (satSystemType != QGeoSatelliteInfo::Undefined) {
86 const bool res = updateInfo.setSatellitesInUse(satSystemType, satInUse);
87#if USE_SATELLITE_NMEA_PIMPL
88 if (res) {
89 updateInfo.gsa = QByteArray(buf, size);
90 auto &info = updateInfo.m_satellites[satSystemType];
91 if (!info.satellitesInUse.isEmpty()) {
92 for (auto &s : info.satellitesInUse) {
93 static_cast<QGeoSatelliteInfoPrivateNmea *>(QGeoSatelliteInfoPrivate::get(s))
94 ->nmeaSentences.append(updateInfo.gsa);
95 }
96 for (auto &s : info.satellitesInView) {
97 static_cast<QGeoSatelliteInfoPrivateNmea *>(QGeoSatelliteInfoPrivate::get(s))
98 ->nmeaSentences.append(updateInfo.gsa);
99 }
100 }
101 }
102#else
103 Q_UNUSED(res)
104#endif // USE_SATELLITE_NMEA_PIMPL
105 } else {
106 // Here we have the assumption that multiple parts of GSV sentence
107 // come one after another. At least this is how it should be.
108 auto systemType = QGeoSatelliteInfo::Undefined;
109 const auto parserStatus = m_source->parseSatelliteInfoFromNmea(
110 QByteArrayView{buf,static_cast<qsizetype>(size)}, updateInfo.m_satellitesInViewParsed, systemType);
111 if (parserStatus == QNmeaSatelliteInfoSource::PartiallyParsed) {
112 updateInfo.m_satellites[systemType].updatingGSV = true;
113#if USE_SATELLITE_NMEA_PIMPL
114 updateInfo.gsv.append(QByteArray(buf, size));
115#endif
116 } else if (parserStatus == QNmeaSatelliteInfoSource::FullyParsed) {
117#if USE_SATELLITE_NMEA_PIMPL
118 updateInfo.gsv.append(QByteArray(buf, size));
119 for (int i = 0; i < updateInfo.m_satellitesInViewParsed.size(); i++) {
120 const QGeoSatelliteInfo &s = updateInfo.m_satellitesInViewParsed.at(i);
121 QGeoSatelliteInfoPrivateNmea *pimpl =
122 new QGeoSatelliteInfoPrivateNmea(*QGeoSatelliteInfoPrivate::get(s));
123 pimpl->nmeaSentences.append(updateInfo.gsa);
124 pimpl->nmeaSentences.append(updateInfo.gsv);
125 updateInfo.m_satellitesInViewParsed.replace(i, QGeoSatelliteInfo(*pimpl));
126 }
127 updateInfo.gsv.clear();
128#endif
129 updateInfo.setSatellitesInView(systemType, updateInfo.m_satellitesInViewParsed);
130 }
131 }
132}
133
134QNmeaSatelliteInfoSourcePrivate::~QNmeaSatelliteInfoSourcePrivate()
135{
136 delete m_updateTimer;
137}
138
139void QNmeaSatelliteInfoSourcePrivate::startUpdates()
140{
141 if (m_invokedStart)
142 return;
143
144 m_satelliteError = QGeoSatelliteInfoSource::NoError;
145
146 m_invokedStart = true;
147 m_pendingUpdate.clear();
148 m_noUpdateLastInterval = false;
149
150 bool initialized = initialize();
151 if (!initialized)
152 return;
153
154 if (m_updateMode == QNmeaSatelliteInfoSource::UpdateMode::RealTimeMode) {
155 // skip over any buffered data - we only want the newest data.
156 // Don't do this in requestUpdate. In that case bufferedData is good to have/use.
157 if (m_device->bytesAvailable()) {
158 if (m_device->isSequential())
159 m_device->readAll();
160 else
161 m_device->seek(m_device->bytesAvailable());
162 }
163 }
164
165 if (m_updateTimer)
166 m_updateTimer->stop();
167
168 if (m_source->updateInterval() > 0) {
169 if (!m_updateTimer)
170 m_updateTimer = new QBasicTimer;
171 m_updateTimer->start(m_source->updateInterval(), this);
172 }
173
174 if (initialized)
175 prepareSourceDevice();
176}
177
178void QNmeaSatelliteInfoSourcePrivate::stopUpdates()
179{
180 m_invokedStart = false;
181 if (m_updateTimer)
182 m_updateTimer->stop();
183 m_pendingUpdate.clear();
184 m_noUpdateLastInterval = false;
185}
186
187void QNmeaSatelliteInfoSourcePrivate::requestUpdate(int msec)
188{
189 if (m_requestTimer && m_requestTimer->isActive())
190 return;
191
192 m_satelliteError = QGeoSatelliteInfoSource::NoError;
193
194 if (msec <= 0 || msec < m_source->minimumUpdateInterval()) {
195 m_source->setError(QGeoSatelliteInfoSource::UpdateTimeoutError);
196 return;
197 }
198
199 if (!m_requestTimer) {
200 m_requestTimer = new QTimer(this);
201 connect(m_requestTimer, SIGNAL(timeout()), SLOT(updateRequestTimeout()));
202 }
203
204 bool initialized = initialize();
205 if (!initialized) {
206 m_source->setError(QGeoSatelliteInfoSource::UpdateTimeoutError);
207 return;
208 }
209
210 m_requestTimer->start(msec);
211 prepareSourceDevice();
212}
213
214void QNmeaSatelliteInfoSourcePrivate::readyRead()
215{
216 if (m_nmeaReader)
217 m_nmeaReader->readAvailableData();
218}
219
220void QNmeaSatelliteInfoSourcePrivate::emitPendingUpdate()
221{
222 if (m_pendingUpdate.isValid() && m_pendingUpdate.isFresh()) {
223 m_updateTimeoutSent = false;
224 m_noUpdateLastInterval = false;
225 if (!emitUpdated(m_pendingUpdate, false))
226 m_noUpdateLastInterval = true;
227 // m_pendingUpdate.clear(); // Do not clear, it will be incrementally updated
228 } else { // invalid or not fresh update
229 if (m_noUpdateLastInterval && !m_updateTimeoutSent) {
230 m_updateTimeoutSent = true;
231 m_source->setError(QGeoSatelliteInfoSource::UpdateTimeoutError);
232 }
233 m_noUpdateLastInterval = true;
234 }
235}
236
237void QNmeaSatelliteInfoSourcePrivate::sourceDataClosed()
238{
239 if (m_nmeaReader && m_device && m_device->bytesAvailable())
240 m_nmeaReader->readAvailableData();
241}
242
243void QNmeaSatelliteInfoSourcePrivate::updateRequestTimeout()
244{
245 m_requestTimer->stop();
246 m_source->setError(QGeoSatelliteInfoSource::UpdateTimeoutError);
247}
248
249bool QNmeaSatelliteInfoSourcePrivate::openSourceDevice()
250{
251 if (!m_device) {
252 qWarning("QNmeaSatelliteInfoSource: no QIODevice data source, call setDevice() first");
253 return false;
254 }
255
256 if (!m_device->isOpen() && !m_device->open(QIODevice::ReadOnly)) {
257 qWarning("QNmeaSatelliteInfoSource: cannot open QIODevice data source");
258 return false;
259 }
260
261 connect(m_device, SIGNAL(aboutToClose()), SLOT(sourceDataClosed()));
262 connect(m_device, SIGNAL(readChannelFinished()), SLOT(sourceDataClosed()));
263 connect(m_device, SIGNAL(destroyed()), SLOT(sourceDataClosed()));
264
265 return true;
266}
267
268bool QNmeaSatelliteInfoSourcePrivate::initialize()
269{
270 if (m_nmeaReader)
271 return true;
272
273 if (!openSourceDevice())
274 return false;
275
276 if (m_updateMode == QNmeaSatelliteInfoSource::UpdateMode::RealTimeMode)
277 m_nmeaReader.reset(new QNmeaSatelliteRealTimeReader(this));
278 else
279 m_nmeaReader.reset(new QNmeaSatelliteSimulationReader(this));
280
281 return true;
282}
283
284void QNmeaSatelliteInfoSourcePrivate::prepareSourceDevice()
285{
286 // some data may already be available
287 if (m_updateMode == QNmeaSatelliteInfoSource::UpdateMode::SimulationMode) {
288 if (m_nmeaReader && m_device->bytesAvailable())
289 m_nmeaReader->readAvailableData();
290 }
291
292 if (!m_connectedReadyRead) {
293 connect(m_device, SIGNAL(readyRead()), SLOT(readyRead()));
294 m_connectedReadyRead = true;
295 }
296}
297
298bool QNmeaSatelliteInfoSourcePrivate::emitUpdated(QNmeaSatelliteInfoUpdate &update,
299 bool fromRequestUpdate)
300{
301 bool emitted = false;
302 if (!update.isFresh())
303 return emitted;
304
305 update.consume();
306 bool inUseUpdated = false;
307 bool inViewUpdated = false;
308 if (!fromRequestUpdate) {
309 // we need to send update if information from at least one satellite
310 // systems has changed
311 for (auto it = update.m_satellites.cbegin(); it != update.m_satellites.cend(); ++it) {
312 inUseUpdated |=
313 it->satellitesInUse != m_lastUpdate.m_satellites[it.key()].satellitesInUse;
314 inViewUpdated |=
315 it->satellitesInView != m_lastUpdate.m_satellites[it.key()].satellitesInView;
316 }
317 } else {
318 // if we come here from requestUpdate(), we need to emit, even if the data
319 // didn't really change
320 inUseUpdated = true;
321 inViewUpdated = true;
322 }
323
324 m_lastUpdate = update;
325 if (update.m_validInUse && inUseUpdated) {
326 emit m_source->satellitesInUseUpdated(update.allSatellitesInUse());
327 emitted = true;
328 }
329 if (update.m_validInView && inViewUpdated) {
330 emit m_source->satellitesInViewUpdated(update.allSatellitesInView());
331 emitted = true;
332 }
333 return emitted;
334}
335
336void QNmeaSatelliteInfoSourcePrivate::timerEvent(QTimerEvent * /*event*/)
337{
338 emitPendingUpdate();
339}
340
341/*!
342 \class QNmeaSatelliteInfoSource
343 \inmodule QtPositioning
344 \ingroup QtPositioning-positioning
345 \since 6.2
346
347 \brief The \l QNmeaSatelliteInfoSource class provides satellite information
348 using an NMEA data source.
349
350 NMEA is a commonly used protocol for the specification of one's global
351 position at a certain point in time. The \l QNmeaSatelliteInfoSource class
352 reads NMEA data and uses it to provide information about satellites in view
353 and satellites in use in form of lists of \l QGeoSatelliteInfo objects.
354
355 A \l QNmeaSatelliteInfoSource instance operates in either \l {RealTimeMode}
356 or \l {SimulationMode}. These modes allow NMEA data to be read from either a
357 live source of data, or replayed for simulation purposes from previously
358 recorded NMEA data.
359
360 The source of NMEA data is set via \l setDevice().
361
362 Use \l startUpdates() to start receiving regular satellite information
363 updates and \l stopUpdates() to stop these updates. If you only require
364 updates occasionally, you can call \l requestUpdate() to request a single
365 update of both satellites in view and satellites in use.
366
367 The information about satellites in view is received via the
368 \l satellitesInViewUpdated() signal.
369
370 The information about satellites in use is received via the
371 \l satellitesInUseUpdated() signal.
372*/
373
374/*!
375 \enum QNmeaSatelliteInfoSource::UpdateMode
376 Defines the available update modes.
377
378 \value RealTimeMode Satellite information is read and distributed from the
379 data source as it becomes available. Use this mode if you are using
380 a live source of NMEA data (for example a GPS hardware device).
381 \value SimulationMode Satellite information is read and distributed from the
382 data source at the given rate. The rate is determined by the
383 \l {QNmeaSatelliteInfoSource::}{SimulationUpdateInterval} parameter.
384 Use this mode if the data source contains previously recorded NMEA
385 data and you want to replay the data for simulation purposes.
386*/
387
388/*!
389 \variable QNmeaSatelliteInfoSource::SimulationUpdateInterval
390 \brief The backend property name for data read rate in the
391 \l SimulationMode. The value for this property is the integer number
392 representing the amount of milliseconds between the subsequent reads.
393 Use this parameter in the \l {QNmeaSatelliteInfoSource::}
394 {setBackendProperty()} and \l {QNmeaSatelliteInfoSource::}{backendProperty()}
395 methods.
396
397 \note This property is different from the interval that can be set via
398 \l {setUpdateInterval()}. The value set via \l {setUpdateInterval()}
399 denotes an interval for the user notification, while this parameter
400 specifies the internal frequency of reading the data from source file. It
401 means that one can have multiple (or none) reads during the
402 \l {updateInterval()} period.
403*/
404QString QNmeaSatelliteInfoSource::SimulationUpdateInterval =
405 QStringLiteral("nmea.satellite_info_simulation_interval");
406
407/*!
408 Constructs a \l QNmeaSatelliteInfoSource instance with the given \a parent
409 and \a mode.
410*/
411QNmeaSatelliteInfoSource::QNmeaSatelliteInfoSource(UpdateMode mode, QObject *parent)
412 : QGeoSatelliteInfoSource(parent),
413 d(new QNmeaSatelliteInfoSourcePrivate(this, mode))
414{
415}
416
417/*!
418 Destroys the satellite information source.
419*/
420QNmeaSatelliteInfoSource::~QNmeaSatelliteInfoSource()
421{
422 delete d;
423}
424
425/*!
426 Returns the update mode.
427*/
428QNmeaSatelliteInfoSource::UpdateMode QNmeaSatelliteInfoSource::updateMode() const
429{
430 return d->m_updateMode;
431}
432
433/*!
434 Sets the NMEA data source to \a device. If the device is not open, it
435 will be opened in \l{QIODeviceBase::}{ReadOnly} mode.
436
437 The source device can only be set once and must be set before calling
438 \l startUpdates() or \l requestUpdate().
439
440 \note The \a device must emit \l {QIODevice::readyRead()} for the
441 source to be notified when data is available for reading.
442 \l QNmeaSatelliteInfoSource does not assume the ownership of the device,
443 and hence does not deallocate it upon destruction.
444*/
445void QNmeaSatelliteInfoSource::setDevice(QIODevice *device)
446{
447 if (device != d->m_device) {
448 if (!d->m_device)
449 d->m_device = device;
450 else
451 qWarning("QNmeaSatelliteInfoSource: source device has already been set");
452 }
453}
454
455/*!
456 Returns the NMEA data source.
457*/
458QIODevice *QNmeaSatelliteInfoSource::device() const
459{
460 return d->m_device;
461}
462
463/*!
464 \reimp
465*/
466void QNmeaSatelliteInfoSource::setUpdateInterval(int msec)
467{
468 int interval = msec;
469 if (interval != 0)
470 interval = qMax(msec, minimumUpdateInterval());
471 QGeoSatelliteInfoSource::setUpdateInterval(interval);
472 if (d->m_invokedStart) {
473 d->stopUpdates();
474 d->startUpdates();
475 }
476}
477
478/*!
479 \reimp
480*/
481int QNmeaSatelliteInfoSource::minimumUpdateInterval() const
482{
483 return 2; // Some chips are capable of over 100 updates per seconds.
484}
485
486/*!
487 \reimp
488*/
489QGeoSatelliteInfoSource::Error QNmeaSatelliteInfoSource::error() const
490{
491 return d->m_satelliteError;
492}
493
494/*!
495 \reimp
496*/
497bool QNmeaSatelliteInfoSource::setBackendProperty(const QString &name, const QVariant &value)
498{
499 if (name == SimulationUpdateInterval && d->m_updateMode == UpdateMode::SimulationMode) {
500 bool ok = false;
501 const int interval = value.toInt(&ok);
502 if (ok) {
503 auto *reader = dynamic_cast<QNmeaSatelliteSimulationReader *>(d->m_nmeaReader.get());
504 if (reader) {
505 reader->setUpdateInterval(interval);
506 } else {
507 // d->m_nmeaReader will use it in constructor
508 d->m_simulationUpdateInterval = interval;
509 }
510 return true;
511 }
512 }
513 return false;
514}
515
516/*!
517 \reimp
518*/
519QVariant QNmeaSatelliteInfoSource::backendProperty(const QString &name) const
520{
521 if (name == SimulationUpdateInterval && d->m_updateMode == UpdateMode::SimulationMode) {
522 auto *reader = dynamic_cast<QNmeaSatelliteSimulationReader *>(d->m_nmeaReader.get());
523 if (reader)
524 return reader->updateInterval();
525 else
526 return d->m_simulationUpdateInterval;
527 }
528 return QVariant();
529}
530
531/*!
532 \reimp
533*/
534void QNmeaSatelliteInfoSource::startUpdates()
535{
536 d->startUpdates();
537}
538
539/*!
540 \reimp
541*/
542void QNmeaSatelliteInfoSource::stopUpdates()
543{
544 d->stopUpdates();
545}
546
547/*!
548 \reimp
549*/
550void QNmeaSatelliteInfoSource::requestUpdate(int msec)
551{
552 d->requestUpdate(msec == 0 ? 60000 * 5 : msec); // 5min default timeout
553}
554
555/*!
556 Parses an NMEA sentence string to extract the IDs of satelites in use.
557
558 The default implementation will parse standard NMEA $GPGSA sentences.
559 This method should be reimplemented in a subclass whenever the need to deal
560 with non-standard NMEA sentences arises.
561
562 The parser reads \a size bytes from \a data and uses that information to
563 fill \a pnrsInUse list.
564
565 Returns system type if the sentence was successfully parsed, otherwise
566 returns \l QGeoSatelliteInfo::Undefined and should not modifiy \a pnrsInUse.
567*/
568
569#if QT_DEPRECATED_SINCE(7, 0)
570QGeoSatelliteInfo::SatelliteSystem
571QNmeaSatelliteInfoSource::parseSatellitesInUseFromNmea(const char *data, int size,
572 QList<int> &pnrsInUse)
573{
574#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
575 return QLocationUtils::getSatInUseFromNmea(QByteArrayView{data, size}, pnrsInUse);
576#else
577 return parseSatellitesInUseFromNmea(QByteArrayView{data, size}, pnrsInUse);
578#endif
579}
580#endif // QT_DEPRECATED_SINCE(7, 0)
581
582QGeoSatelliteInfo::SatelliteSystem
583QNmeaSatelliteInfoSource::parseSatellitesInUseFromNmea(QByteArrayView data,
584 QList<int> &pnrsInUse)
585{
586#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
587 return parseSatellitesInUseFromNmea(data.data(), static_cast<int>(data.size()), pnrsInUse);
588#else
589 return QLocationUtils::getSatInUseFromNmea(data, pnrsInUse);
590#endif
591}
592
593/*!
594 \enum QNmeaSatelliteInfoSource::SatelliteInfoParseStatus
595 Defines the parse status of satellite information. The satellite information
596 can be split into multiple sentences, and we need to parse all of them.
597 \value NotParsed The data does not contain information about satellites.
598 \value PartiallyParsed A valid satellite information is received and parsed,
599 but it's not complete, so we need to wait for another NMEA sentence.
600 \value FullyParsed Satellite information was fully collected and parsed.
601*/
602
603/*!
604 Parses an NMEA sentence string to extract the information about satellites
605 in view.
606
607 The default implementation will parse standard NMEA $GPGSV sentences.
608 This method should be reimplemented in a subclass whenever the need to deal
609 with non-standard NMEA sentences arises.
610
611 The parser reads \a size bytes from \a data and uses that information to
612 fill \a infos list.
613
614 Returns \l SatelliteInfoParseStatus with parse result.
615 Modifies \a infos list in case \l {QNmeaSatelliteInfoSource::}
616 {PartiallyParsed} or \l {QNmeaSatelliteInfoSource::}{FullyParsed} is
617 returned.
618 Also sets the \a system to correct satellite system type. This is required
619 to determine the system type in case there are no satellites in view.
620*/
621
622#if QT_DEPRECATED_SINCE(7, 0)
623QNmeaSatelliteInfoSource::SatelliteInfoParseStatus
624QNmeaSatelliteInfoSource::parseSatelliteInfoFromNmea(const char *data, int size,
625 QList<QGeoSatelliteInfo> &infos,
626 QGeoSatelliteInfo::SatelliteSystem &system)
627{
628#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
629 return static_cast<SatelliteInfoParseStatus>(
630 QLocationUtils::getSatInfoFromNmea(QByteArrayView{data, size}, infos, system));
631#else
632 return parseSatelliteInfoFromNmea(QByteArrayView{data, size}, infos, system);
633#endif
634}
635#endif // QT_DEPRECATED_SINCE(7, 0)
636
637QNmeaSatelliteInfoSource::SatelliteInfoParseStatus
638QNmeaSatelliteInfoSource::parseSatelliteInfoFromNmea(QByteArrayView data,
639 QList<QGeoSatelliteInfo> &infos,
640 QGeoSatelliteInfo::SatelliteSystem &system)
641{
642#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
643 return parseSatelliteInfoFromNmea(data.data(), static_cast<int>(data.size()), infos, system);
644#else
645 return static_cast<SatelliteInfoParseStatus>(
646 QLocationUtils::getSatInfoFromNmea(data, infos, system));
647#endif
648}
649
650void QNmeaSatelliteInfoSource::setError(QGeoSatelliteInfoSource::Error satelliteError)
651{
652 d->m_satelliteError = satelliteError;
653 if (d->m_satelliteError != QGeoSatelliteInfoSource::NoError)
654 emit QGeoSatelliteInfoSource::errorOccurred(satelliteError);
655}
656
657QList<QGeoSatelliteInfo> QNmeaSatelliteInfoUpdate::allSatellitesInUse() const
658{
659 QList<QGeoSatelliteInfo> result;
660 for (const auto &s : m_satellites)
661 result.append(s.satellitesInUse);
662 return result;
663}
664
665QList<QGeoSatelliteInfo> QNmeaSatelliteInfoUpdate::allSatellitesInView() const
666{
667 QList<QGeoSatelliteInfo> result;
668 for (const auto &s : m_satellites)
669 result.append(s.satellitesInView);
670 return result;
671}
672
673void QNmeaSatelliteInfoUpdate::setSatellitesInView(QGeoSatelliteInfo::SatelliteSystem system,
674 const QList<QGeoSatelliteInfo> &inView)
675{
676 auto &info = m_satellites[system];
677 info.updatingGSV = false;
678
679 info.satellitesInView = inView;
680 info.validInView = true;
681
682 if (!info.satellitesInUseReceived) {
683 // Normally a GSA message should come after a GSV message. If this flag
684 // is not set, we have received 2 consecutive GSV messages for this
685 // system without a GSA in between.
686 // This means that we could actually receive a $GNGSA empty message for
687 // this specific type. As it had no ids and GN talker id, we could not
688 // determine system type. This most probably means that we have no
689 // satellites in use for this system type.
690 // Clear satellites in use, if any.
691 info.satellitesInUse.clear();
692 info.inUseIds.clear();
693 info.validInUse = true;
694 }
695 info.satellitesInUseReceived = false;
696
697 if (info.satellitesInView.isEmpty()) {
698 // If we received an empty list of satellites in view, then the list of
699 // satellites in use will also be empty, so we would not be able to
700 // match it with correct system type in case of $GNGSA message. Clear
701 // the list in advance.
702 info.satellitesInUse.clear();
703 info.inUseIds.clear();
704 info.validInUse = true;
705 } else if (!info.inUseIds.isEmpty()) {
706 // We have some satellites in use cached. Check if we have received the
707 // proper GSV for them.
708 info.satellitesInUse.clear();
709 info.validInUse = false;
710 bool corrupt = false;
711 const auto inUseIds = std::exchange(info.inUseIds, {});
712 for (const auto id : inUseIds) {
713 bool found = false;
714 for (const auto &s : std::as_const(info.satellitesInView)) {
715 if (s.satelliteIdentifier() == id) {
716 info.satellitesInUse.append(s);
717 found = true;
718 break;
719 }
720 }
721 if (!found) {
722 // The previoulsy received GSA is incorrect or not related to
723 // this GSV
724 info.satellitesInUse.clear();
725 corrupt = true;
726 break;
727 }
728 }
729 info.validInUse = !corrupt;
730 }
731
732 m_validInUse = calculateValidInUse();
733 m_validInView = calculateValidInView();
734 m_fresh = true;
735}
736
737bool QNmeaSatelliteInfoUpdate::setSatellitesInUse(QGeoSatelliteInfo::SatelliteSystem system,
738 const QList<int> &inUse)
739{
740 if (system == QGeoSatelliteInfo::Undefined || system == QGeoSatelliteInfo::Multiple)
741 return false; // No way to determine satellite system
742
743 SatelliteInfo &info = m_satellites[system];
744 info.satellitesInUse.clear();
745
746 info.satellitesInUseReceived = true;
747 info.inUseIds = inUse;
748
749 if (info.updatingGSV) {
750 info.validInView = false;
751 m_validInView = false;
752 return false;
753 }
754
755 for (const auto id : inUse) {
756 bool found = false;
757 for (const auto &s : std::as_const(info.satellitesInView)) {
758 if (s.satelliteIdentifier() == id) {
759 info.satellitesInUse.append(s);
760 found = true;
761 break;
762 }
763 }
764 if (!found) {
765 // satellites in use are not in view -> related GSV is not yet received
766 info.satellitesInView.clear();
767 info.validInView = false;
768 m_validInView = false;
769 return false;
770 }
771 }
772
773 info.inUseIds.clear(); // make sure we remove all obsolete cache
774
775 info.validInUse = true;
776 m_fresh = true;
777 m_validInUse = calculateValidInUse();
778
779 return true;
780}
781
782void QNmeaSatelliteInfoUpdate::consume()
783{
784 m_fresh = false;
785}
786
787bool QNmeaSatelliteInfoUpdate::isFresh() const
788{
789 return m_fresh;
790}
791
792void QNmeaSatelliteInfoUpdate::clear()
793{
794 m_satellites.clear();
795 m_satellitesInViewParsed.clear();
796 m_validInView = false;
797 m_validInUse = false;
798 m_fresh = false;
799#if USE_SATELLITE_NMEA_PIMPL
800 gsa.clear();
801 gsv.clear();
802#endif
803}
804
805bool QNmeaSatelliteInfoUpdate::isValid() const
806{
807 // GSV without GSA is valid. GSA with outdated but still matching GSV also valid.
808 return m_validInView || m_validInUse;
809}
810
811bool QNmeaSatelliteInfoUpdate::calculateValidInUse() const
812{
813 for (const auto &s : m_satellites) {
814 if (!s.validInUse)
815 return false;
816 }
817 return true;
818}
819
820bool QNmeaSatelliteInfoUpdate::calculateValidInView() const
821{
822 for (const auto &s : m_satellites) {
823 if (!s.validInView)
824 return false;
825 }
826 return true;
827}
828
829QNmeaSatelliteReader::QNmeaSatelliteReader(QNmeaSatelliteInfoSourcePrivate *sourcePrivate)
830 : m_proxy(sourcePrivate)
831{
832}
833
834QNmeaSatelliteReader::~QNmeaSatelliteReader()
835{
836}
837
838QNmeaSatelliteRealTimeReader::QNmeaSatelliteRealTimeReader(QNmeaSatelliteInfoSourcePrivate *sourcePrivate)
839 : QNmeaSatelliteReader(sourcePrivate)
840{
841}
842
843void QNmeaSatelliteRealTimeReader::readAvailableData()
844{
845 while (m_proxy->m_device->canReadLine())
846 m_proxy->processNmeaData(m_proxy->m_pendingUpdate);
847 m_proxy->notifyNewUpdate();
848}
849
850QNmeaSatelliteSimulationReader::QNmeaSatelliteSimulationReader(QNmeaSatelliteInfoSourcePrivate *sourcePrivate)
851 : QNmeaSatelliteReader(sourcePrivate)
852{
853 m_timer.reset(new QTimer);
854 QObject::connect(m_timer.get(), &QTimer::timeout, m_timer.get(), [this]() {
855 readAvailableData();
856 });
857 m_updateInterval =
858 qMax(m_proxy->m_simulationUpdateInterval, m_proxy->m_source->minimumUpdateInterval());
859}
860
861void QNmeaSatelliteSimulationReader::readAvailableData()
862{
863 if (!m_timer->isActive()) {
864 // At the very first start we just start a timer to simulate a short
865 // delay for overlapping requestUpdate() calls.
866 // See TestQGeoSatelliteInfoSource::requestUpdate_overlappingCalls and
867 // TestQGeoSatelliteInfoSource::requestUpdate_overlappingCallsWithTimeout
868 m_timer->start(m_updateInterval);
869 } else {
870 // Here we try to get both satellites in view and satellites in use.
871 // We behave like that because according to the QGeoSatelliteInfoSource
872 // tests each call to requestUpdate() should return both satellites in
873 // view and satellites in use. Same is expected on each interval for
874 // startUpdates().
875 // However user-provided NMEA logs might not contain some of the
876 // messages, so we will try not to get stuck here infinitely.
877 int numSatInUseMsgs = 0;
878 int numSatInViewMsgs = 0;
879 while (!m_proxy->m_device->atEnd() && (!numSatInUseMsgs || !numSatInViewMsgs)) {
880 m_proxy->processNmeaData(m_proxy->m_pendingUpdate);
881 if (m_proxy->m_pendingUpdate.m_validInUse)
882 numSatInUseMsgs++;
883 if (m_proxy->m_pendingUpdate.m_validInView)
884 numSatInViewMsgs++;
885 // if we got the second message for one of them, but still didn't
886 // receive any for the other - break.
887 // We use 2 in the comparison, because, as soon as the m_validIn*
888 // flag is set, it will stay true until we receive invalid message.
889 if (numSatInUseMsgs > 2 || numSatInViewMsgs > 2) {
890 const QString msgType = (numSatInUseMsgs > numSatInViewMsgs)
891 ? QStringLiteral("GSA")
892 : QStringLiteral("GSV");
893 qWarning() << "nmea simulation reader: possibly incorrect message order. Got too "
894 "many consecutive"
895 << msgType << "messages";
896 break;
897 }
898 }
899 m_proxy->notifyNewUpdate();
900 }
901}
902
903void QNmeaSatelliteSimulationReader::setUpdateInterval(int msec)
904{
905 // restart the timer with new interval
906 m_updateInterval = qMax(msec, m_proxy->m_source->minimumUpdateInterval());
907 if (m_timer->isActive())
908 m_timer->start(m_updateInterval);
909}
910
911int QNmeaSatelliteSimulationReader::updateInterval() const
912{
913 return m_updateInterval;
914}
915
916QT_END_NAMESPACE
917
918#include "moc_qnmeasatelliteinfosource_p.cpp"
919#include "moc_qnmeasatelliteinfosource.cpp"