15#include <QtCore/QtNumeric>
16#include <QtCore/QDateTime>
17#include <QtCore/QTimeZone>
23#define USE_POSITION_NMEA_PIMPL 0
26class QGeoPositionInfoPrivateNmea :
public QGeoPositionInfoPrivate
29 virtual ~QGeoPositionInfoPrivateNmea();
31 QList<QByteArray> nmeaSentences;
35QGeoPositionInfoPrivateNmea::~QGeoPositionInfoPrivateNmea()
40typedef QGeoPositionInfoPrivate QGeoPositionInfoPrivateNmea;
46 QGeoCoordinate c = dst.coordinate();
47 const QGeoCoordinate & srcCoordinate = src.coordinate();
48 if (qIsFinite(src.coordinate().latitude())
49 && (!qIsFinite(dst.coordinate().latitude()) || force)) {
50 updated |= (c.latitude() != srcCoordinate.latitude());
51 c.setLatitude(src.coordinate().latitude());
53 if (qIsFinite(src.coordinate().longitude())
54 && (!qIsFinite(dst.coordinate().longitude()) || force)) {
55 updated |= (c.longitude() != srcCoordinate.longitude());
56 c.setLongitude(src.coordinate().longitude());
58 if (qIsFinite(src.coordinate().altitude())
59 && (!qIsFinite(dst.coordinate().altitude()) || force)) {
60 updated |= (c.altitude() != srcCoordinate.altitude());
61 c.setAltitude(src.coordinate().altitude());
67static bool propagateDate(QGeoPositionInfo &dst,
const QGeoPositionInfo &src)
69 if (!dst.timestamp().date().isValid() && src.timestamp().isValid()) {
70 dst.setTimestamp(src.timestamp());
79 static constexpr std::array<QGeoPositionInfo::Attribute, 6> attrs {
80 { QGeoPositionInfo::GroundSpeed
81 ,QGeoPositionInfo::HorizontalAccuracy
82 ,QGeoPositionInfo::VerticalAccuracy
83 ,QGeoPositionInfo::Direction
84 ,QGeoPositionInfo::VerticalSpeed
85 ,QGeoPositionInfo::MagneticVariation} };
86 for (
const auto a: attrs) {
87 if (src.hasAttribute(a) && (!dst.hasAttribute(a) || force)) {
88 updated |= (dst.attribute(a) != src.attribute(a));
89 dst.setAttribute(a, src.attribute(a));
100 bool updated =
false;
107 QGeoPositionInfoPrivateNmea *dstPimpl =
static_cast<QGeoPositionInfoPrivateNmea *>(QGeoPositionInfoPrivate::get(dst));
108 dstPimpl->nmeaSentences.append(nmeaSentence);
110 Q_UNUSED(nmeaSentence);
117 if (!from.time().isValid() || !to.time().isValid())
120 if (!from.date().isValid() || !to.date().isValid())
121 return from.time().msecsTo(to.time());
123 return from.msecsTo(to);
130 : QNmeaReader(sourcePrivate), m_update(*
new QGeoPositionInfoPrivateNmea)
138 int pushDelay = qEnvironmentVariableIntValue(
"QT_NMEA_PUSH_DELAY", &ok);
140 pushDelay = std::clamp(pushDelay, -1, 1000);
144 if (pushDelay >= 0) {
145 m_timer.setSingleShot(
true);
146 m_timer.setInterval(pushDelay);
147 m_timer.connect(&m_timer, &QTimer::timeout, &m_timer, [
this]() {
148 this->notifyNewUpdate();
151 m_pushDelay = pushDelay;
154QNmeaRealTimeReader::~QNmeaRealTimeReader()
159 while (m_proxy->m_device->canReadLine()) {
160 const QTime infoTime = m_update.timestamp().time();
161 const QDate infoDate = m_update.timestamp().date();
163 QGeoPositionInfoPrivateNmea *pimpl =
new QGeoPositionInfoPrivateNmea;
164 QGeoPositionInfo pos(*pimpl);
167 qint64 size = m_proxy->m_device->readLine(buf,
sizeof(buf));
173 const bool parsed = m_proxy->parsePosInfoFromNmeaData(
174 QByteArrayView{buf,
static_cast<qsizetype>(size)}, &pos, &hasFix);
188 if (infoTime.isValid()) {
189 if (pos.timestamp().time().isValid()) {
190 const bool newerTime = infoTime < pos.timestamp().time();
191 const bool newerDate = (infoDate.isValid()
192 && pos.timestamp().date().isValid()
193 && infoDate < pos.timestamp().date());
194 if (newerTime || newerDate) {
197 const QDate updateDate = m_update.timestamp().date();
198 const QDate lastPushedDate = m_lastPushedTS.date();
199 const bool newerTimestampSinceLastPushed = m_update.timestamp() > m_lastPushedTS;
200 const bool invalidDate = !(updateDate.isValid() && lastPushedDate.isValid());
201 const bool newerTimeSinceLastPushed = m_update.timestamp().time() > m_lastPushedTS.time();
202 if ( newerTimestampSinceLastPushed || (invalidDate && newerTimeSinceLastPushed)) {
203 m_proxy->notifyNewUpdate(&m_update, oldFix);
204 m_lastPushedTS = m_update.timestamp();
208 propagateAttributes(pos, m_update,
false);
212 if (infoTime == pos.timestamp().time())
214 if (mergePositions(m_update, pos,
QByteArray(buf, size))) {
224 if (mergePositions(m_update, pos, QByteArray(buf, size)))
230 pimpl->nmeaSentences.append(QByteArray(buf, size));
232 propagateAttributes(pos, m_update);
248 const bool newerTime = m_update.timestamp().time() > m_lastPushedTS.time();
249 const bool newerDate = (m_update.timestamp().date().isValid()
250 && m_lastPushedTS.date().isValid()
251 && m_update.timestamp().date() > m_lastPushedTS.date());
252 if (newerTime || newerDate) {
253 m_proxy->notifyNewUpdate(&m_update, m_hasFix);
254 m_lastPushedTS = m_update.timestamp();
265 m_hasValidDateTime(
false)
271 if (m_currTimerId > 0)
272 killTimer(m_currTimerId);
277 if (m_currTimerId > 0)
280 if (!m_hasValidDateTime) {
281 Q_ASSERT(m_proxy->m_device && (m_proxy->m_device->openMode() & QIODevice::ReadOnly));
283 if (!setFirstDateTime()) {
285 qWarning(
"QNmeaPositionInfoSource: cannot find NMEA sentence with valid date & time");
289 m_hasValidDateTime =
true;
290 simulatePendingUpdate();
294 processNextSentence();
301 QQueue<QPendingGeoPositionInfo> &m_pendingUpdates,
304 int timeToNextUpdate = -1;
306 if (!m_pendingUpdates.isEmpty())
307 prevTs = m_pendingUpdates.head().info.timestamp();
311 while (!m_nextLine.isEmpty() || (m_proxy->m_device && m_proxy->m_device->bytesAvailable() > 0)) {
312 char static_buf[1024];
313 char *buf = static_buf;
316 if (!m_nextLine.isEmpty()) {
318 size = m_nextLine.size();
319 nextLine = m_nextLine;
321 buf = nextLine.data();
323 size = m_proxy->m_device->readLine(buf,
sizeof(static_buf));
329 const QTime infoTime = info.timestamp().time();
330 const QDate infoDate = info.timestamp().date();
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
349 QGeoPositionInfoPrivateNmea *pimpl =
new QGeoPositionInfoPrivateNmea;
350 QGeoPositionInfo pos(*pimpl);
351 if (m_proxy->parsePosInfoFromNmeaData(
352 QByteArrayView{buf,
static_cast<qsizetype>(size)}, &pos, &hasFix)) {
357 if (infoTime.isValid()) {
358 if (pos.timestamp().time().isValid()) {
359 const bool newerTime = infoTime < pos.timestamp().time();
360 const bool newerDate = (infoDate.isValid()
361 && pos.timestamp().date().isValid()
362 && infoDate < pos.timestamp().date());
363 if (newerTime || newerDate) {
368 if (infoTime == pos.timestamp().time())
370 mergePositions(info, pos,
QByteArray(buf, size));
375 mergePositions(info, pos,
QByteArray(buf, size));
380 pimpl->nmeaSentences.append(QByteArray(buf, size));
385 if (prevTs.time().isValid()) {
386 timeToNextUpdate = msecsTo(prevTs, info.timestamp());
387 if (timeToNextUpdate < 0)
388 info = QGeoPositionInfo(*
new QGeoPositionInfoPrivateNmea);
393 return timeToNextUpdate;
399 QGeoPositionInfo info(*
new QGeoPositionInfoPrivateNmea);
401 processSentence(info, m_nextLine, m_proxy, m_pendingUpdates, hasFix);
403 if (info.timestamp().time().isValid()) {
407 m_pendingUpdates.enqueue(pending);
415 if (!m_pendingUpdates.isEmpty()) {
418 m_proxy->notifyNewUpdate(&pending.info, pending.hasFix);
421 processNextSentence();
426 killTimer(event->timerId());
428 simulatePendingUpdate();
433 QGeoPositionInfo info(*
new QGeoPositionInfoPrivateNmea);
436 int timeToNextUpdate = processSentence(info, m_nextLine, m_proxy, m_pendingUpdates, hasFix);
437 if (timeToNextUpdate < 0)
440 m_pendingUpdates.dequeue();
445 m_pendingUpdates.enqueue(pending);
446 m_currTimerId = startTimer(timeToNextUpdate);
455 m_updateMode(updateMode),
458 m_positionError(QGeoPositionInfoSource::UnknownSourceError),
464 m_horizontalAccuracy(qQNaN()),
465 m_verticalAccuracy(qQNaN()),
466 m_noUpdateLastInterval(
false),
467 m_updateTimeoutSent(
false),
468 m_connectedReadyRead(
false)
475 delete m_updateTimer;
481 qWarning(
"QNmeaPositionInfoSource: no QIODevice data source, call setDevice() first");
485 if (!m_device->isOpen() && !m_device->open(QIODevice::ReadOnly)) {
486 qWarning(
"QNmeaPositionInfoSource: cannot open QIODevice data source");
490 connect(m_device, SIGNAL(aboutToClose()), SLOT(sourceDataClosed()));
491 connect(m_device, SIGNAL(readChannelFinished()), SLOT(sourceDataClosed()));
492 connect(m_device, SIGNAL(destroyed()), SLOT(sourceDataClosed()));
499 if (m_nmeaReader && m_device && m_device->bytesAvailable())
514 if (!openSourceDevice())
517 if (m_updateMode == QNmeaPositionInfoSource::RealTimeMode)
520 m_nmeaReader =
new QNmeaSimulatedReader(
this);
528 if (m_updateMode == QNmeaPositionInfoSource::SimulationMode) {
529 if (m_nmeaReader && m_device->bytesAvailable())
533 if (!m_connectedReadyRead) {
534 connect(m_device, SIGNAL(readyRead()), SLOT(readyRead()));
535 m_connectedReadyRead =
true;
540 QGeoPositionInfo *posInfo,
bool *hasFix)
542 return m_source->parsePosInfoFromNmeaData(data, posInfo, hasFix);
550 m_positionError = QGeoPositionInfoSource::NoError;
553 m_pendingUpdate = QGeoPositionInfo();
554 m_noUpdateLastInterval =
false;
556 bool initialized = initialize();
558 m_source->setError(QGeoPositionInfoSource::AccessError);
562 if (m_updateMode == QNmeaPositionInfoSource::RealTimeMode) {
565 if (m_device->bytesAvailable()) {
566 if (m_device->isSequential())
569 m_device->seek(m_device->bytesAvailable());
574 m_updateTimer->stop();
576 if (m_source->updateInterval() > 0) {
578 m_updateTimer =
new QBasicTimer;
579 m_updateTimer->start(m_source->updateInterval(),
this);
583 prepareSourceDevice();
590 m_updateTimer->stop();
591 m_pendingUpdate = QGeoPositionInfo();
592 m_noUpdateLastInterval =
false;
597 if (m_requestTimer && m_requestTimer->isActive())
600 m_positionError = QGeoPositionInfoSource::NoError;
602 if (msec <= 0 || msec < m_source->minimumUpdateInterval()) {
603 m_source->setError(QGeoPositionInfoSource::UpdateTimeoutError);
607 if (!m_requestTimer) {
608 m_requestTimer =
new QTimer(
this);
609 connect(m_requestTimer, SIGNAL(timeout()), SLOT(updateRequestTimeout()));
612 bool initialized = initialize();
614 m_source->setError(QGeoPositionInfoSource::UpdateTimeoutError);
618 m_requestTimer->start(msec);
619 prepareSourceDevice();
624 m_requestTimer->stop();
625 m_source->setError(QGeoPositionInfoSource::UpdateTimeoutError);
633 QDate date = update->timestamp().date();
634 if (date.isValid()) {
635 m_currentDate = date;
638 QTime time = update->timestamp().time();
639 if (time.isValid() && m_currentDate.isValid())
640 update->setTimestamp(QDateTime(m_currentDate, time, QTimeZone::UTC));
645 if (update->hasAttribute(QGeoPositionInfo::HorizontalAccuracy))
646 m_horizontalAccuracy = update->attribute(QGeoPositionInfo::HorizontalAccuracy);
647 else if (!qIsNaN(m_horizontalAccuracy))
648 update->setAttribute(QGeoPositionInfo::HorizontalAccuracy, m_horizontalAccuracy);
650 if (update->hasAttribute(QGeoPositionInfo::VerticalAccuracy))
651 m_verticalAccuracy = update->attribute(QGeoPositionInfo::VerticalAccuracy);
652 else if (!qIsNaN(m_verticalAccuracy))
653 update->setAttribute(QGeoPositionInfo::VerticalAccuracy, m_verticalAccuracy);
655 if (hasFix && update->isValid()) {
656 if (m_requestTimer && m_requestTimer->isActive()) {
657 m_requestTimer->stop();
658 emitUpdated(*update);
660 if (m_updateTimer && m_updateTimer->isActive()) {
662 m_pendingUpdate = *update;
663 if (m_noUpdateLastInterval) {
667 m_noUpdateLastInterval =
false;
670 emitUpdated(*update);
673 m_lastUpdate = *update;
684 if (m_pendingUpdate.isValid()) {
685 m_updateTimeoutSent =
false;
686 m_noUpdateLastInterval =
false;
687 emitUpdated(m_pendingUpdate);
688 m_pendingUpdate = QGeoPositionInfo();
690 if (m_noUpdateLastInterval && !m_updateTimeoutSent) {
691 m_updateTimeoutSent =
true;
692 m_pendingUpdate = QGeoPositionInfo();
693 m_source->setError(QGeoPositionInfoSource::UpdateTimeoutError);
695 m_noUpdateLastInterval =
true;
703 m_lastUpdate = update;
704 emit m_source->positionUpdated(update);
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
743
744
745
746
747
748
752
753
754
755QNmeaPositionInfoSource::QNmeaPositionInfoSource(UpdateMode updateMode, QObject *parent)
756 : QGeoPositionInfoSource(parent),
757 d(
new QNmeaPositionInfoSourcePrivate(
this, updateMode))
762
763
764QNmeaPositionInfoSource::~QNmeaPositionInfoSource()
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785void QNmeaPositionInfoSource::setUserEquivalentRangeError(
double uere)
787 d->m_userEquivalentRangeError = uere;
791
792
793
794
795
796
797
798
799double QNmeaPositionInfoSource::userEquivalentRangeError()
const
801 return d->m_userEquivalentRangeError;
805
806
807
808
809
810
811
812
813
814
815
816
817
819#if QT_DEPRECATED_SINCE(7
, 0
)
820bool QNmeaPositionInfoSource::parsePosInfoFromNmeaData(
const char *data,
int size,
821 QGeoPositionInfo *posInfo,
bool *hasFix)
823#if QT_VERSION < QT_VERSION_CHECK(7
, 0
, 0
)
824 return QLocationUtils::getPosInfoFromNmea(QByteArrayView{data, size}, posInfo,
825 d->m_userEquivalentRangeError, hasFix);
827 return parsePosInfoFromNmeaData(QByteArrayView{data, size}, posInfo, hasFix);
832bool QNmeaPositionInfoSource::parsePosInfoFromNmeaData(QByteArrayView data,
833 QGeoPositionInfo *posInfo,
bool *hasFix)
835#if QT_VERSION < QT_VERSION_CHECK(7
, 0
, 0
)
836 return parsePosInfoFromNmeaData(data.data(),
static_cast<
int>(data.size()),
839 return QLocationUtils::getPosInfoFromNmea(data, posInfo,
840 d->m_userEquivalentRangeError, hasFix);
846
847
848QNmeaPositionInfoSource::UpdateMode QNmeaPositionInfoSource::updateMode()
const
850 return d->m_updateMode;
854
855
856
857
858
859
860
861
862
863
864
865void QNmeaPositionInfoSource::setDevice(QIODevice *device)
867 if (device != d->m_device) {
869 d->m_device = device;
871 qWarning(
"QNmeaPositionInfoSource: source device has already been set");
876
877
878QIODevice *QNmeaPositionInfoSource::device()
const
884
885
886void QNmeaPositionInfoSource::setUpdateInterval(
int msec)
890 interval = qMax(msec, minimumUpdateInterval());
891 QGeoPositionInfoSource::setUpdateInterval(interval);
892 if (d->m_invokedStart) {
899
900
901void QNmeaPositionInfoSource::startUpdates()
907
908
909void QNmeaPositionInfoSource::stopUpdates()
915
916
917void QNmeaPositionInfoSource::requestUpdate(
int msec)
919 d->requestUpdate(msec == 0 ? 60000 * 5 : msec);
923
924
925QGeoPositionInfo QNmeaPositionInfoSource::lastKnownPosition(
bool)
const
928 return d->m_lastUpdate;
932
933
934QGeoPositionInfoSource::PositioningMethods QNmeaPositionInfoSource::supportedPositioningMethods()
const
936 return SatellitePositioningMethods;
940
941
942int QNmeaPositionInfoSource::minimumUpdateInterval()
const
948
949
950QGeoPositionInfoSource::Error QNmeaPositionInfoSource::error()
const
952 return d->m_positionError;
955void QNmeaPositionInfoSource::setError(QGeoPositionInfoSource::Error positionError)
957 d->m_positionError = positionError;
958 if (d->m_positionError != QGeoPositionInfoSource::NoError)
959 emit QGeoPositionInfoSource::errorOccurred(positionError);
964#include "moc_qnmeapositioninfosource_p.cpp"
965#include "moc_qnmeapositioninfosource.cpp"
void notifyNewUpdate(QGeoPositionInfo *update, bool fixStatus)
~QNmeaPositionInfoSourcePrivate()
bool parsePosInfoFromNmeaData(QByteArrayView data, QGeoPositionInfo *posInfo, bool *hasFix)
void requestUpdate(int msec)
double m_userEquivalentRangeError
void timerEvent(QTimerEvent *event) override
This event handler can be reimplemented in a subclass to receive timer events for the object.
QNmeaReader(QNmeaPositionInfoSourcePrivate *sourcePrivate)
virtual void readAvailableData()=0
void readAvailableData() override
QNmeaRealTimeReader(QNmeaPositionInfoSourcePrivate *sourcePrivate)
void timerEvent(QTimerEvent *event) override
This event handler can be reimplemented in a subclass to receive timer events for the object.
void readAvailableData() override
#define USE_POSITION_NMEA_PIMPL
static bool propagateCoordinate(QGeoPositionInfo &dst, const QGeoPositionInfo &src, bool force=true)
static int processSentence(QGeoPositionInfo &info, QByteArray &m_nextLine, QNmeaPositionInfoSourcePrivate *m_proxy, QQueue< QPendingGeoPositionInfo > &m_pendingUpdates, bool &hasFix)
static bool propagateAttributes(QGeoPositionInfo &dst, const QGeoPositionInfo &src, bool force=true)
static qint64 msecsTo(const QDateTime &from, const QDateTime &to)
static bool propagateDate(QGeoPositionInfo &dst, const QGeoPositionInfo &src)
static bool mergePositions(QGeoPositionInfo &dst, const QGeoPositionInfo &src, QByteArray nmeaSentence)