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