Revision control

++ // Copyright (C) 2019 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++ #include "qnmeasatelliteinfosource.h"
++ #include "private/qnmeasatelliteinfosource_p.h"
++ #include <QtPositioning/private/qgeosatelliteinfo_p.h>
++ #include <QtPositioning/private/qgeosatelliteinfosource_p.h>
++ #include <QtPositioning/private/qlocationutils_p.h>
++
++ #include <QIODevice>
++ #include <QBasicTimer>
++ #include <QTimerEvent>
++ #include <QTimer>
++ #include <array>
++ #include <QDebug>
++ #include <QtCore/QtNumeric>
++
++ QT_BEGIN_NAMESPACE
++
++ #if USE_SATELLITE_NMEA_PIMPL
++ class QGeoSatelliteInfoPrivateNmea : public QGeoSatelliteInfoPrivate
++ {
++ public:
++ QGeoSatelliteInfoPrivateNmea(const QGeoSatelliteInfoPrivate &other);
++ QGeoSatelliteInfoPrivateNmea(const QGeoSatelliteInfoPrivateNmea &other);
++ virtual ~QGeoSatelliteInfoPrivateNmea();
++
++ QList<QByteArray> nmeaSentences;
++ };
++
++ QGeoSatelliteInfoPrivateNmea::QGeoSatelliteInfoPrivateNmea(const QGeoSatelliteInfoPrivate &other)
++ : QGeoSatelliteInfoPrivate(other)
++ {
++ }
++
++ QGeoSatelliteInfoPrivateNmea::QGeoSatelliteInfoPrivateNmea(const QGeoSatelliteInfoPrivateNmea &other)
++ : QGeoSatelliteInfoPrivate(other)
++ {
++ nmeaSentences = other.nmeaSentences;
++ }
++
++ QGeoSatelliteInfoPrivateNmea::~QGeoSatelliteInfoPrivateNmea() {}
++ #else
++ typedef QGeoSatelliteInfoPrivate QGeoSatelliteInfoPrivateNmea;
++ #endif
++
++ QNmeaSatelliteInfoSourcePrivate::QNmeaSatelliteInfoSourcePrivate(QNmeaSatelliteInfoSource *parent, QNmeaSatelliteInfoSource::UpdateMode updateMode)
++ : m_source(parent),
++ m_updateMode(updateMode)
++ {
++ }
++
++ void QNmeaSatelliteInfoSourcePrivate::notifyNewUpdate()
++ {
++ if (m_pendingUpdate.isValid() && m_pendingUpdate.isFresh()) {
++ if (m_requestTimer && m_requestTimer->isActive()) { // User called requestUpdate()
++ m_requestTimer->stop();
++ emitUpdated(m_pendingUpdate, true);
++ } else if (m_invokedStart) { // user called startUpdates()
++ if (m_updateTimer && m_updateTimer->isActive()) { // update interval > 0
++ // for periodic updates, only want the most recent update
++ if (m_noUpdateLastInterval) {
++ // if the update was invalid when timerEvent was last called, a valid update
++ // should be sent ASAP
++ emitPendingUpdate(); // m_noUpdateLastInterval handled in there.
++ }
++ } else { // update interval <= 0, send anything new ASAP
++ m_noUpdateLastInterval = !emitUpdated(m_pendingUpdate, false);
++ }
++ }
++ }
++ }
++
++ void QNmeaSatelliteInfoSourcePrivate::processNmeaData(QNmeaSatelliteInfoUpdate &updateInfo)
++ {
++ char buf[1024];
++ qint64 size = m_device->readLine(buf, sizeof(buf));
++ if (size <= 0)
++ return;
++
++ QList<int> satInUse;
++ const auto satSystemType = m_source->parseSatellitesInUseFromNmea(QByteArrayView{buf,static_cast<qsizetype>(size)},
++ satInUse);
++ if (satSystemType != QGeoSatelliteInfo::Undefined) {
++ const bool res = updateInfo.setSatellitesInUse(satSystemType, satInUse);
++ #if USE_SATELLITE_NMEA_PIMPL
++ if (res) {
++ updateInfo.gsa = QByteArray(buf, size);
++ auto &info = updateInfo.m_satellites[satSystemType];
++ if (!info.satellitesInUse.isEmpty()) {
++ for (auto &s : info.satellitesInUse) {
++ static_cast<QGeoSatelliteInfoPrivateNmea *>(QGeoSatelliteInfoPrivate::get(s))
++ ->nmeaSentences.append(updateInfo.gsa);
++ }
++ for (auto &s : info.satellitesInView) {
++ static_cast<QGeoSatelliteInfoPrivateNmea *>(QGeoSatelliteInfoPrivate::get(s))
++ ->nmeaSentences.append(updateInfo.gsa);
++ }
++ }
++ }
++ #else
++ Q_UNUSED(res)
++ #endif // USE_SATELLITE_NMEA_PIMPL
++ } else {
++ // Here we have the assumption that multiple parts of GSV sentence
++ // come one after another. At least this is how it should be.
++ auto systemType = QGeoSatelliteInfo::Undefined;
++ const auto parserStatus = m_source->parseSatelliteInfoFromNmea(
++ QByteArrayView{buf,static_cast<qsizetype>(size)}, updateInfo.m_satellitesInViewParsed, systemType);
++ if (parserStatus == QNmeaSatelliteInfoSource::PartiallyParsed) {
++ updateInfo.m_satellites[systemType].updatingGSV = true;
++ #if USE_SATELLITE_NMEA_PIMPL
++ updateInfo.gsv.append(QByteArray(buf, size));
++ #endif
++ } else if (parserStatus == QNmeaSatelliteInfoSource::FullyParsed) {
++ #if USE_SATELLITE_NMEA_PIMPL
++ updateInfo.gsv.append(QByteArray(buf, size));
++ for (int i = 0; i < updateInfo.m_satellitesInViewParsed.size(); i++) {
++ const QGeoSatelliteInfo &s = updateInfo.m_satellitesInViewParsed.at(i);
++ QGeoSatelliteInfoPrivateNmea *pimpl =
++ new QGeoSatelliteInfoPrivateNmea(*QGeoSatelliteInfoPrivate::get(s));
++ pimpl->nmeaSentences.append(updateInfo.gsa);
++ pimpl->nmeaSentences.append(updateInfo.gsv);
++ updateInfo.m_satellitesInViewParsed.replace(i, QGeoSatelliteInfo(*pimpl));
++ }
++ updateInfo.gsv.clear();
++ #endif
++ updateInfo.setSatellitesInView(systemType, updateInfo.m_satellitesInViewParsed);
++ }
++ }
++ }
++
++ QNmeaSatelliteInfoSourcePrivate::~QNmeaSatelliteInfoSourcePrivate()
++ {
++ delete m_updateTimer;
++ }
++
++ void QNmeaSatelliteInfoSourcePrivate::startUpdates()
++ {
++ if (m_invokedStart)
++ return;
++
++ m_satelliteError = QGeoSatelliteInfoSource::NoError;
++
++ m_invokedStart = true;
++ m_pendingUpdate.clear();
++ m_noUpdateLastInterval = false;
++
++ bool initialized = initialize();
++ if (!initialized)
++ return;
++
++ if (m_updateMode == QNmeaSatelliteInfoSource::UpdateMode::RealTimeMode) {
++ // skip over any buffered data - we only want the newest data.
++ // Don't do this in requestUpdate. In that case bufferedData is good to have/use.
++ if (m_device->bytesAvailable()) {
++ if (m_device->isSequential())
++ m_device->readAll();
++ else
++ m_device->seek(m_device->bytesAvailable());
++ }
++ }
++
++ if (m_updateTimer)
++ m_updateTimer->stop();
++
++ if (m_source->updateInterval() > 0) {
++ if (!m_updateTimer)
++ m_updateTimer = new QBasicTimer;
++ m_updateTimer->start(m_source->updateInterval(), this);
++ }
++
++ if (initialized)
++ prepareSourceDevice();
++ }
++
++ void QNmeaSatelliteInfoSourcePrivate::stopUpdates()
++ {
++ m_invokedStart = false;
++ if (m_updateTimer)
++ m_updateTimer->stop();
++ m_pendingUpdate.clear();
++ m_noUpdateLastInterval = false;
++ }
++
++ void QNmeaSatelliteInfoSourcePrivate::requestUpdate(int msec)
++ {
++ if (m_requestTimer && m_requestTimer->isActive())
++ return;
++
++ m_satelliteError = QGeoSatelliteInfoSource::NoError;
++
++ if (msec <= 0 || msec < m_source->minimumUpdateInterval()) {
++ m_source->setError(QGeoSatelliteInfoSource::UpdateTimeoutError);
++ return;
++ }
++
++ if (!m_requestTimer) {
++ m_requestTimer = new QTimer(this);
++ connect(m_requestTimer, SIGNAL(timeout()), SLOT(updateRequestTimeout()));
++ }
++
++ bool initialized = initialize();
++ if (!initialized) {
++ m_source->setError(QGeoSatelliteInfoSource::UpdateTimeoutError);
++ return;
++ }
++
++ m_requestTimer->start(msec);
++ prepareSourceDevice();
++ }
++
++ void QNmeaSatelliteInfoSourcePrivate::readyRead()
++ {
++ if (m_nmeaReader)
++ m_nmeaReader->readAvailableData();
++ }
++
++ void QNmeaSatelliteInfoSourcePrivate::emitPendingUpdate()
++ {
++ if (m_pendingUpdate.isValid() && m_pendingUpdate.isFresh()) {
++ m_updateTimeoutSent = false;
++ m_noUpdateLastInterval = false;
++ if (!emitUpdated(m_pendingUpdate, false))
++ m_noUpdateLastInterval = true;
++ // m_pendingUpdate.clear(); // Do not clear, it will be incrementally updated
++ } else { // invalid or not fresh update
++ if (m_noUpdateLastInterval && !m_updateTimeoutSent) {
++ m_updateTimeoutSent = true;
++ m_source->setError(QGeoSatelliteInfoSource::UpdateTimeoutError);
++ }
++ m_noUpdateLastInterval = true;
++ }
++ }
++
++ void QNmeaSatelliteInfoSourcePrivate::sourceDataClosed()
++ {
++ if (m_nmeaReader && m_device && m_device->bytesAvailable())
++ m_nmeaReader->readAvailableData();
++ }
++
++ void QNmeaSatelliteInfoSourcePrivate::updateRequestTimeout()
++ {
++ m_requestTimer->stop();
++ m_source->setError(QGeoSatelliteInfoSource::UpdateTimeoutError);
++ }
++
++ bool QNmeaSatelliteInfoSourcePrivate::openSourceDevice()
++ {
++ if (!m_device) {
++ qWarning("QNmeaSatelliteInfoSource: no QIODevice data source, call setDevice() first");
++ return false;
++ }
++
++ if (!m_device->isOpen() && !m_device->open(QIODevice::ReadOnly)) {
++ qWarning("QNmeaSatelliteInfoSource: cannot open QIODevice data source");
++ return false;
++ }
++
++ connect(m_device, SIGNAL(aboutToClose()), SLOT(sourceDataClosed()));
++ connect(m_device, SIGNAL(readChannelFinished()), SLOT(sourceDataClosed()));
++ connect(m_device, SIGNAL(destroyed()), SLOT(sourceDataClosed()));
++
++ return true;
++ }
++
++ bool QNmeaSatelliteInfoSourcePrivate::initialize()
++ {
++ if (m_nmeaReader)
++ return true;
++
++ if (!openSourceDevice())
++ return false;
++
++ if (m_updateMode == QNmeaSatelliteInfoSource::UpdateMode::RealTimeMode)
++ m_nmeaReader.reset(new QNmeaSatelliteRealTimeReader(this));
++ else
++ m_nmeaReader.reset(new QNmeaSatelliteSimulationReader(this));
++
++ return true;
++ }
++
++ void QNmeaSatelliteInfoSourcePrivate::prepareSourceDevice()
++ {
++ // some data may already be available
++ if (m_updateMode == QNmeaSatelliteInfoSource::UpdateMode::SimulationMode) {
++ if (m_nmeaReader && m_device->bytesAvailable())
++ m_nmeaReader->readAvailableData();
++ }
++
++ if (!m_connectedReadyRead) {
++ connect(m_device, SIGNAL(readyRead()), SLOT(readyRead()));
++ m_connectedReadyRead = true;
++ }
++ }
++
++ bool QNmeaSatelliteInfoSourcePrivate::emitUpdated(QNmeaSatelliteInfoUpdate &update,
++ bool fromRequestUpdate)
++ {
++ bool emitted = false;
++ if (!update.isFresh())
++ return emitted;
++
++ update.consume();
++ bool inUseUpdated = false;
++ bool inViewUpdated = false;
++ if (!fromRequestUpdate) {
++ // we need to send update if information from at least one satellite
++ // systems has changed
++ for (auto it = update.m_satellites.cbegin(); it != update.m_satellites.cend(); ++it) {
++ inUseUpdated |=
++ it->satellitesInUse != m_lastUpdate.m_satellites[it.key()].satellitesInUse;
++ inViewUpdated |=
++ it->satellitesInView != m_lastUpdate.m_satellites[it.key()].satellitesInView;
++ }
++ } else {
++ // if we come here from requestUpdate(), we need to emit, even if the data
++ // didn't really change
++ inUseUpdated = true;
++ inViewUpdated = true;
++ }
++
++ m_lastUpdate = update;
++ if (update.m_validInUse && inUseUpdated) {
++ emit m_source->satellitesInUseUpdated(update.allSatellitesInUse());
++ emitted = true;
++ }
++ if (update.m_validInView && inViewUpdated) {
++ emit m_source->satellitesInViewUpdated(update.allSatellitesInView());
++ emitted = true;
++ }
++ return emitted;
++ }
++
++ void QNmeaSatelliteInfoSourcePrivate::timerEvent(QTimerEvent * /*event*/)
++ {
++ emitPendingUpdate();
++ }
++
++ /*!
++ \class QNmeaSatelliteInfoSource
++ \inmodule QtPositioning
++ \ingroup QtPositioning-positioning
++ \since 6.2
++
++ \brief The \l QNmeaSatelliteInfoSource class provides satellite information
++ using an NMEA data source.
++
++ NMEA is a commonly used protocol for the specification of one's global
++ position at a certain point in time. The \l QNmeaSatelliteInfoSource class
++ reads NMEA data and uses it to provide information about satellites in view
++ and satellites in use in form of lists of \l QGeoSatelliteInfo objects.
++
++ A \l QNmeaSatelliteInfoSource instance operates in either \l {RealTimeMode}
++ or \l {SimulationMode}. These modes allow NMEA data to be read from either a
++ live source of data, or replayed for simulation purposes from previously
++ recorded NMEA data.
++
++ The source of NMEA data is set via \l setDevice().
++
++ Use \l startUpdates() to start receiving regular satellite information
++ updates and \l stopUpdates() to stop these updates. If you only require
++ updates occasionally, you can call \l requestUpdate() to request a single
++ update of both satellites in view and satellites in use.
++
++ The information about satellites in view is received via the
++ \l satellitesInViewUpdated() signal.
++
++ The information about satellites in use is received via the
++ \l satellitesInUseUpdated() signal.
++ */
++
++ /*!
++ \enum QNmeaSatelliteInfoSource::UpdateMode
++ Defines the available update modes.
++
++ \value RealTimeMode Satellite information is read and distributed from the
++ data source as it becomes available. Use this mode if you are using
++ a live source of NMEA data (for example a GPS hardware device).
++ \value SimulationMode Satellite information is read and distributed from the
++ data source at the given rate. The rate is determined by the
++ \l {QNmeaSatelliteInfoSource::}{SimulationUpdateInterval} parameter.
++ Use this mode if the data source contains previously recorded NMEA
++ data and you want to replay the data for simulation purposes.
++ */
++
++ /*!
++ \variable QNmeaSatelliteInfoSource::SimulationUpdateInterval
++ \brief The backend property name for data read rate in the
++ \l SimulationMode. The value for this property is the integer number
++ representing the amount of milliseconds between the subsequent reads.
++ Use this parameter in the \l {QNmeaSatelliteInfoSource::}
++ {setBackendProperty()} and \l {QNmeaSatelliteInfoSource::}{backendProperty()}
++ methods.
++
++ \note This property is different from the interval that can be set via
++ \l {setUpdateInterval()}. The value set via \l {setUpdateInterval()}
++ denotes an interval for the user notification, while this parameter
++ specifies the internal frequency of reading the data from source file. It
++ means that one can have multiple (or none) reads during the
++ \l {updateInterval()} period.
++ */
++ QString QNmeaSatelliteInfoSource::SimulationUpdateInterval =
++ QStringLiteral("nmea.satellite_info_simulation_interval");
++
++ /*!
++ Constructs a \l QNmeaSatelliteInfoSource instance with the given \a parent
++ and \a mode.
++ */
++ QNmeaSatelliteInfoSource::QNmeaSatelliteInfoSource(UpdateMode mode, QObject *parent)
++ : QGeoSatelliteInfoSource(parent),
++ d(new QNmeaSatelliteInfoSourcePrivate(this, mode))
++ {
++ }
++
++ /*!
++ Destroys the satellite information source.
++ */
++ QNmeaSatelliteInfoSource::~QNmeaSatelliteInfoSource()
++ {
++ delete d;
++ }
++
++ /*!
++ Returns the update mode.
++ */
++ QNmeaSatelliteInfoSource::UpdateMode QNmeaSatelliteInfoSource::updateMode() const
++ {
++ return d->m_updateMode;
++ }
++
++ /*!
++ Sets the NMEA data source to \a device. If the device is not open, it
++ will be opened in \l{QIODeviceBase::}{ReadOnly} mode.
++
++ The source device can only be set once and must be set before calling
++ \l startUpdates() or \l requestUpdate().
++
++ \note The \a device must emit \l {QIODevice::readyRead()} for the
++ source to be notified when data is available for reading.
++ \l QNmeaSatelliteInfoSource does not assume the ownership of the device,
++ and hence does not deallocate it upon destruction.
++ */
++ void QNmeaSatelliteInfoSource::setDevice(QIODevice *device)
++ {
++ if (device != d->m_device) {
++ if (!d->m_device)
++ d->m_device = device;
++ else
++ qWarning("QNmeaSatelliteInfoSource: source device has already been set");
++ }
++ }
++
++ /*!
++ Returns the NMEA data source.
++ */
++ QIODevice *QNmeaSatelliteInfoSource::device() const
++ {
++ return d->m_device;
++ }
++
++ /*!
++ \reimp
++ */
++ void QNmeaSatelliteInfoSource::setUpdateInterval(int msec)
++ {
++ int interval = msec;
++ if (interval != 0)
++ interval = qMax(msec, minimumUpdateInterval());
++ QGeoSatelliteInfoSource::setUpdateInterval(interval);
++ if (d->m_invokedStart) {
++ d->stopUpdates();
++ d->startUpdates();
++ }
++ }
++
++ /*!
++ \reimp
++ */
++ int QNmeaSatelliteInfoSource::minimumUpdateInterval() const
++ {
++ return 2; // Some chips are capable of over 100 updates per seconds.
++ }
++
++ /*!
++ \reimp
++ */
++ QGeoSatelliteInfoSource::Error QNmeaSatelliteInfoSource::error() const
++ {
++ return d->m_satelliteError;
++ }
++
++ /*!
++ \reimp
++ */
++ bool QNmeaSatelliteInfoSource::setBackendProperty(const QString &name, const QVariant &value)
++ {
++ if (name == SimulationUpdateInterval && d->m_updateMode == UpdateMode::SimulationMode) {
++ bool ok = false;
++ const int interval = value.toInt(&ok);
++ if (ok) {
++ auto *reader = dynamic_cast<QNmeaSatelliteSimulationReader *>(d->m_nmeaReader.get());
++ if (reader) {
++ reader->setUpdateInterval(interval);
++ } else {
++ // d->m_nmeaReader will use it in constructor
++ d->m_simulationUpdateInterval = interval;
++ }
++ return true;
++ }
++ }
++ return false;
++ }
++
++ /*!
++ \reimp
++ */
++ QVariant QNmeaSatelliteInfoSource::backendProperty(const QString &name) const
++ {
++ if (name == SimulationUpdateInterval && d->m_updateMode == UpdateMode::SimulationMode) {
++ auto *reader = dynamic_cast<QNmeaSatelliteSimulationReader *>(d->m_nmeaReader.get());
++ if (reader)
++ return reader->updateInterval();
++ else
++ return d->m_simulationUpdateInterval;
++ }
++ return QVariant();
++ }
++
++ /*!
++ \reimp
++ */
++ void QNmeaSatelliteInfoSource::startUpdates()
++ {
++ d->startUpdates();
++ }
++
++ /*!
++ \reimp
++ */
++ void QNmeaSatelliteInfoSource::stopUpdates()
++ {
++ d->stopUpdates();
++ }
++
++ /*!
++ \reimp
++ */
++ void QNmeaSatelliteInfoSource::requestUpdate(int msec)
++ {
++ d->requestUpdate(msec == 0 ? 60000 * 5 : msec); // 5min default timeout
++ }
++
++ /*!
++ Parses an NMEA sentence string to extract the IDs of satelites in use.
++
++ The default implementation will parse standard NMEA $GPGSA sentences.
++ This method should be reimplemented in a subclass whenever the need to deal
++ with non-standard NMEA sentences arises.
++
++ The parser reads \a size bytes from \a data and uses that information to
++ fill \a pnrsInUse list.
++
++ Returns system type if the sentence was successfully parsed, otherwise
++ returns \l QGeoSatelliteInfo::Undefined and should not modifiy \a pnrsInUse.
++ */
++
++ #if QT_DEPRECATED_SINCE(7, 0)
++ QGeoSatelliteInfo::SatelliteSystem
++ QNmeaSatelliteInfoSource::parseSatellitesInUseFromNmea(const char *data, int size,
++ QList<int> &pnrsInUse)
++ {
++ #if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
++ return QLocationUtils::getSatInUseFromNmea(QByteArrayView{data, size}, pnrsInUse);
++ #else
++ return parseSatellitesInUseFromNmea(QByteArrayView{data, size}, pnrsInUse);
++ #endif
++ }
++ #endif // QT_DEPRECATED_SINCE(7, 0)
++
++ QGeoSatelliteInfo::SatelliteSystem
++ QNmeaSatelliteInfoSource::parseSatellitesInUseFromNmea(QByteArrayView data,
++ QList<int> &pnrsInUse)
++ {
++ #if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
++ return parseSatellitesInUseFromNmea(data.data(), static_cast<int>(data.size()), pnrsInUse);
++ #else
++ return QLocationUtils::getSatInUseFromNmea(data, pnrsInUse);
++ #endif
++ }
++
++ /*!
++ \enum QNmeaSatelliteInfoSource::SatelliteInfoParseStatus
++ Defines the parse status of satellite information. The satellite information
++ can be split into multiple sentences, and we need to parse all of them.
++ \value NotParsed The data does not contain information about satellites.
++ \value PartiallyParsed A valid satellite information is received and parsed,
++ but it's not complete, so we need to wait for another NMEA sentence.
++ \value FullyParsed Satellite information was fully collected and parsed.
++ */
++
++ /*!
++ Parses an NMEA sentence string to extract the information about satellites
++ in view.
++
++ The default implementation will parse standard NMEA $GPGSV sentences.
++ This method should be reimplemented in a subclass whenever the need to deal
++ with non-standard NMEA sentences arises.
++
++ The parser reads \a size bytes from \a data and uses that information to
++ fill \a infos list.
++
++ Returns \l SatelliteInfoParseStatus with parse result.
++ Modifies \a infos list in case \l {QNmeaSatelliteInfoSource::}
++ {PartiallyParsed} or \l {QNmeaSatelliteInfoSource::}{FullyParsed} is
++ returned.
++ Also sets the \a system to correct satellite system type. This is required
++ to determine the system type in case there are no satellites in view.
++ */
++
++ #if QT_DEPRECATED_SINCE(7, 0)
++ QNmeaSatelliteInfoSource::SatelliteInfoParseStatus
++ QNmeaSatelliteInfoSource::parseSatelliteInfoFromNmea(const char *data, int size,
++ QList<QGeoSatelliteInfo> &infos,
++ QGeoSatelliteInfo::SatelliteSystem &system)
++ {
++ #if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
++ return static_cast<SatelliteInfoParseStatus>(
++ QLocationUtils::getSatInfoFromNmea(QByteArrayView{data, size}, infos, system));
++ #else
++ return parseSatelliteInfoFromNmea(QByteArrayView{data, size}, infos, system);
++ #endif
++ }
++ #endif // QT_DEPRECATED_SINCE(7, 0)
++
++ QNmeaSatelliteInfoSource::SatelliteInfoParseStatus
++ QNmeaSatelliteInfoSource::parseSatelliteInfoFromNmea(QByteArrayView data,
++ QList<QGeoSatelliteInfo> &infos,
++ QGeoSatelliteInfo::SatelliteSystem &system)
++ {
++ #if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
++ return parseSatelliteInfoFromNmea(data.data(), static_cast<int>(data.size()), infos, system);
++ #else
++ return static_cast<SatelliteInfoParseStatus>(
++ QLocationUtils::getSatInfoFromNmea(data, infos, system));
++ #endif
++ }
++
++ void QNmeaSatelliteInfoSource::setError(QGeoSatelliteInfoSource::Error satelliteError)
++ {
++ d->m_satelliteError = satelliteError;
++ if (d->m_satelliteError != QGeoSatelliteInfoSource::NoError)
++ emit QGeoSatelliteInfoSource::errorOccurred(satelliteError);
++ }
++
++ QList<QGeoSatelliteInfo> QNmeaSatelliteInfoUpdate::allSatellitesInUse() const
++ {
++ QList<QGeoSatelliteInfo> result;
++ for (const auto &s : m_satellites)
++ result.append(s.satellitesInUse);
++ return result;
++ }
++
++ QList<QGeoSatelliteInfo> QNmeaSatelliteInfoUpdate::allSatellitesInView() const
++ {
++ QList<QGeoSatelliteInfo> result;
++ for (const auto &s : m_satellites)
++ result.append(s.satellitesInView);
++ return result;
++ }
++
++ void QNmeaSatelliteInfoUpdate::setSatellitesInView(QGeoSatelliteInfo::SatelliteSystem system,
++ const QList<QGeoSatelliteInfo> &inView)
++ {
++ auto &info = m_satellites[system];
++ info.updatingGSV = false;
++
++ info.satellitesInView = inView;
++ info.validInView = true;
++
++ if (!info.satellitesInUseReceived) {
++ // Normally a GSA message should come after a GSV message. If this flag
++ // is not set, we have received 2 consecutive GSV messages for this
++ // system without a GSA in between.
++ // This means that we could actually receive a $GNGSA empty message for
++ // this specific type. As it had no ids and GN talker id, we could not
++ // determine system type. This most probably means that we have no
++ // satellites in use for this system type.
++ // Clear satellites in use, if any.
++ info.satellitesInUse.clear();
++ info.inUseIds.clear();
++ info.validInUse = true;
++ }
++ info.satellitesInUseReceived = false;
++
++ if (info.satellitesInView.isEmpty()) {
++ // If we received an empty list of satellites in view, then the list of
++ // satellites in use will also be empty, so we would not be able to
++ // match it with correct system type in case of $GNGSA message. Clear
++ // the list in advance.
++ info.satellitesInUse.clear();
++ info.inUseIds.clear();
++ info.validInUse = true;
++ } else if (!info.inUseIds.isEmpty()) {
++ // We have some satellites in use cached. Check if we have received the
++ // proper GSV for them.
++ info.satellitesInUse.clear();
++ info.validInUse = false;
++ bool corrupt = false;
++ const auto inUseIds = std::exchange(info.inUseIds, {});
++ for (const auto id : inUseIds) {
++ bool found = false;
++ for (const auto &s : std::as_const(info.satellitesInView)) {
++ if (s.satelliteIdentifier() == id) {
++ info.satellitesInUse.append(s);
++ found = true;
++ break;
++ }
++ }
++ if (!found) {
++ // The previoulsy received GSA is incorrect or not related to
++ // this GSV
++ info.satellitesInUse.clear();
++ corrupt = true;
++ break;
++ }
++ }
++ info.validInUse = !corrupt;
++ }
++
++ m_validInUse = calculateValidInUse();
++ m_validInView = calculateValidInView();
++ m_fresh = true;
++ }
++
++ bool QNmeaSatelliteInfoUpdate::setSatellitesInUse(QGeoSatelliteInfo::SatelliteSystem system,
++ const QList<int> &inUse)
++ {
++ if (system == QGeoSatelliteInfo::Undefined || system == QGeoSatelliteInfo::Multiple)
++ return false; // No way to determine satellite system
++
++ SatelliteInfo &info = m_satellites[system];
++ info.satellitesInUse.clear();
++
++ info.satellitesInUseReceived = true;
++ info.inUseIds = inUse;
++
++ if (info.updatingGSV) {
++ info.validInView = false;
++ m_validInView = false;
++ return false;
++ }
++
++ for (const auto id : inUse) {
++ bool found = false;
++ for (const auto &s : std::as_const(info.satellitesInView)) {
++ if (s.satelliteIdentifier() == id) {
++ info.satellitesInUse.append(s);
++ found = true;
++ break;
++ }
++ }
++ if (!found) {
++ // satellites in use are not in view -> related GSV is not yet received
++ info.satellitesInView.clear();
++ info.validInView = false;
++ m_validInView = false;
++ return false;
++ }
++ }
++
++ info.inUseIds.clear(); // make sure we remove all obsolete cache
++
++ info.validInUse = true;
++ m_fresh = true;
++ m_validInUse = calculateValidInUse();
++
++ return true;
++ }
++
++ void QNmeaSatelliteInfoUpdate::consume()
++ {
++ m_fresh = false;
++ }
++
++ bool QNmeaSatelliteInfoUpdate::isFresh() const
++ {
++ return m_fresh;
++ }
++
++ void QNmeaSatelliteInfoUpdate::clear()
++ {
++ m_satellites.clear();
++ m_satellitesInViewParsed.clear();
++ m_validInView = false;
++ m_validInUse = false;
++ m_fresh = false;
++ #if USE_SATELLITE_NMEA_PIMPL
++ gsa.clear();
++ gsv.clear();
++ #endif
++ }
++
++ bool QNmeaSatelliteInfoUpdate::isValid() const
++ {
++ // GSV without GSA is valid. GSA with outdated but still matching GSV also valid.
++ return m_validInView || m_validInUse;
++ }
++
++ bool QNmeaSatelliteInfoUpdate::calculateValidInUse() const
++ {
++ for (const auto &s : m_satellites) {
++ if (!s.validInUse)
++ return false;
++ }
++ return true;
++ }
++
++ bool QNmeaSatelliteInfoUpdate::calculateValidInView() const
++ {
++ for (const auto &s : m_satellites) {
++ if (!s.validInView)
++ return false;
++ }
++ return true;
++ }
++
++ QNmeaSatelliteReader::QNmeaSatelliteReader(QNmeaSatelliteInfoSourcePrivate *sourcePrivate)
++ : m_proxy(sourcePrivate)
++ {
++ }
++
++ QNmeaSatelliteReader::~QNmeaSatelliteReader()
++ {
++ }
++
++ QNmeaSatelliteRealTimeReader::QNmeaSatelliteRealTimeReader(QNmeaSatelliteInfoSourcePrivate *sourcePrivate)
++ : QNmeaSatelliteReader(sourcePrivate)
++ {
++ }
++
++ void QNmeaSatelliteRealTimeReader::readAvailableData()
++ {
++ while (m_proxy->m_device->canReadLine())
++ m_proxy->processNmeaData(m_proxy->m_pendingUpdate);
++ m_proxy->notifyNewUpdate();
++ }
++
++ QNmeaSatelliteSimulationReader::QNmeaSatelliteSimulationReader(QNmeaSatelliteInfoSourcePrivate *sourcePrivate)
++ : QNmeaSatelliteReader(sourcePrivate)
++ {
++ m_timer.reset(new QTimer);
++ QObject::connect(m_timer.get(), &QTimer::timeout, m_timer.get(), [this]() {
++ readAvailableData();
++ });
++ m_updateInterval =
++ qMax(m_proxy->m_simulationUpdateInterval, m_proxy->m_source->minimumUpdateInterval());
++ }
++
++ void QNmeaSatelliteSimulationReader::readAvailableData()
++ {
++ if (!m_timer->isActive()) {
++ // At the very first start we just start a timer to simulate a short
++ // delay for overlapping requestUpdate() calls.
++ // See TestQGeoSatelliteInfoSource::requestUpdate_overlappingCalls and
++ // TestQGeoSatelliteInfoSource::requestUpdate_overlappingCallsWithTimeout
++ m_timer->start(m_updateInterval);
++ } else {
++ // Here we try to get both satellites in view and satellites in use.
++ // We behave like that because according to the QGeoSatelliteInfoSource
++ // tests each call to requestUpdate() should return both satellites in
++ // view and satellites in use. Same is expected on each interval for
++ // startUpdates().
++ // However user-provided NMEA logs might not contain some of the
++ // messages, so we will try not to get stuck here infinitely.
++ int numSatInUseMsgs = 0;
++ int numSatInViewMsgs = 0;
++ while (!m_proxy->m_device->atEnd() && (!numSatInUseMsgs || !numSatInViewMsgs)) {
++ m_proxy->processNmeaData(m_proxy->m_pendingUpdate);
++ if (m_proxy->m_pendingUpdate.m_validInUse)
++ numSatInUseMsgs++;
++ if (m_proxy->m_pendingUpdate.m_validInView)
++ numSatInViewMsgs++;
++ // if we got the second message for one of them, but still didn't
++ // receive any for the other - break.
++ // We use 2 in the comparison, because, as soon as the m_validIn*
++ // flag is set, it will stay true until we receive invalid message.
++ if (numSatInUseMsgs > 2 || numSatInViewMsgs > 2) {
++ const QString msgType = (numSatInUseMsgs > numSatInViewMsgs)
++ ? QStringLiteral("GSA")
++ : QStringLiteral("GSV");
++ qWarning() << "nmea simulation reader: possibly incorrect message order. Got too "
++ "many consecutive"
++ << msgType << "messages";
++ break;
++ }
++ }
++ m_proxy->notifyNewUpdate();
++ }
++ }
++
++ void QNmeaSatelliteSimulationReader::setUpdateInterval(int msec)
++ {
++ // restart the timer with new interval
++ m_updateInterval = qMax(msec, m_proxy->m_source->minimumUpdateInterval());
++ if (m_timer->isActive())
++ m_timer->start(m_updateInterval);
++ }
++
++ int QNmeaSatelliteSimulationReader::updateInterval() const
++ {
++ return m_updateInterval;
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qnmeasatelliteinfosource_p.cpp"
++ #include "moc_qnmeasatelliteinfosource.cpp"