14#include <QtCore/QtNumeric>
15#include <QtCore/QDateTime>
16#include <QtCore/QTimeZone>
22#define USE_POSITION_NMEA_PIMPL 0
25class QGeoPositionInfoPrivateNmea :
public QGeoPositionInfoPrivate
28 virtual ~QGeoPositionInfoPrivateNmea();
30 QList<QByteArray> nmeaSentences;
34QGeoPositionInfoPrivateNmea::~QGeoPositionInfoPrivateNmea()
39typedef QGeoPositionInfoPrivate QGeoPositionInfoPrivateNmea;
45 QGeoCoordinate c = dst.coordinate();
46 const QGeoCoordinate & srcCoordinate = src.coordinate();
47 if (qIsFinite(src.coordinate().latitude())
48 && (!qIsFinite(dst.coordinate().latitude()) || force)) {
49 updated |= (c.latitude() != srcCoordinate.latitude());
50 c.setLatitude(src.coordinate().latitude());
52 if (qIsFinite(src.coordinate().longitude())
53 && (!qIsFinite(dst.coordinate().longitude()) || force)) {
54 updated |= (c.longitude() != srcCoordinate.longitude());
55 c.setLongitude(src.coordinate().longitude());
57 if (qIsFinite(src.coordinate().altitude())
58 && (!qIsFinite(dst.coordinate().altitude()) || force)) {
59 updated |= (c.altitude() != srcCoordinate.altitude());
60 c.setAltitude(src.coordinate().altitude());
66static bool propagateDate(QGeoPositionInfo &dst,
const QGeoPositionInfo &src)
68 if (!dst.timestamp().date().isValid() && src.timestamp().isValid()) {
69 dst.setTimestamp(src.timestamp());
78 static Q_DECL_CONSTEXPR std::array<QGeoPositionInfo::Attribute, 6> attrs {
79 { QGeoPositionInfo::GroundSpeed
80 ,QGeoPositionInfo::HorizontalAccuracy
81 ,QGeoPositionInfo::VerticalAccuracy
82 ,QGeoPositionInfo::Direction
83 ,QGeoPositionInfo::VerticalSpeed
84 ,QGeoPositionInfo::MagneticVariation} };
85 for (
const auto a: attrs) {
86 if (src.hasAttribute(a) && (!dst.hasAttribute(a) || force)) {
87 updated |= (dst.attribute(a) != src.attribute(a));
88 dst.setAttribute(a, src.attribute(a));
106 QGeoPositionInfoPrivateNmea *dstPimpl =
static_cast<QGeoPositionInfoPrivateNmea *>(QGeoPositionInfoPrivate::get(dst));
107 dstPimpl->nmeaSentences.append(nmeaSentence);
109 Q_UNUSED(nmeaSentence);
116 if (!from.time().isValid() || !to.time().isValid())
119 if (!from.date().isValid() || !to.date().isValid())
120 return from.time().msecsTo(to.time());
122 return from.msecsTo(to);
129 : QNmeaReader(sourcePrivate), m_update(*
new QGeoPositionInfoPrivateNmea)
137 int pushDelay = qEnvironmentVariableIntValue(
"QT_NMEA_PUSH_DELAY", &ok);
139 pushDelay = std::clamp(pushDelay, -1, 1000);
143 if (pushDelay >= 0) {
144 m_timer.setSingleShot(
true);
145 m_timer.setInterval(pushDelay);
146 m_timer.connect(&m_timer, &QTimer::timeout, &m_timer, [
this]() {
147 this->notifyNewUpdate();
150 m_pushDelay = pushDelay;
153QNmeaRealTimeReader::~QNmeaRealTimeReader()
156void QNmeaRealTimeReader::readAvailableData()
158 while (m_proxy->m_device->canReadLine()) {
159 const QTime infoTime = m_update.timestamp().time();
160 const QDate infoDate = m_update.timestamp().date();
162 QGeoPositionInfoPrivateNmea *pimpl =
new QGeoPositionInfoPrivateNmea;
163 QGeoPositionInfo pos(*pimpl);
166 qint64 size = m_proxy->m_device->readLine(buf,
sizeof(buf));
170 const bool oldFix = m_hasFix;
172 const bool parsed = m_proxy->parsePosInfoFromNmeaData(
173 QByteArrayView{buf,
static_cast<qsizetype>(size)}, &pos, &hasFix);
181 m_updateParsed =
true;
187 if (infoTime.isValid()) {
188 if (pos.timestamp().time().isValid()) {
189 const bool newerTime = infoTime < pos.timestamp().time();
190 const bool newerDate = (infoDate.isValid()
191 && pos.timestamp().date().isValid()
192 && infoDate < pos.timestamp().date());
193 if (newerTime || newerDate) {
196 const QDate updateDate = m_update.timestamp().date();
197 const QDate lastPushedDate = m_lastPushedTS.date();
198 const bool newerTimestampSinceLastPushed = m_update.timestamp() > m_lastPushedTS;
199 const bool invalidDate = !(updateDate.isValid() && lastPushedDate.isValid());
200 const bool newerTimeSinceLastPushed = m_update.timestamp().time() > m_lastPushedTS.time();
201 if ( newerTimestampSinceLastPushed || (invalidDate && newerTimeSinceLastPushed)) {
202 m_proxy->notifyNewUpdate(&m_update, oldFix);
203 m_lastPushedTS = m_update.timestamp();
207 propagateAttributes(pos, m_update,
false);
211 if (infoTime == pos.timestamp().time())
213 if (mergePositions(m_update, pos, QByteArray(buf, size))) {
223 if (mergePositions(m_update, pos, QByteArray(buf, size)))
229 pimpl->nmeaSentences.append(QByteArray(buf, size));
231 propagateAttributes(pos, m_update);
237 if (m_updateParsed) {
245void QNmeaRealTimeReader::notifyNewUpdate()
247 const bool newerTime = m_update.timestamp().time() > m_lastPushedTS.time();
248 const bool newerDate = (m_update.timestamp().date().isValid()
249 && m_lastPushedTS.date().isValid()
250 && m_update.timestamp().date() > m_lastPushedTS.date());
251 if (newerTime || newerDate) {
252 m_proxy->notifyNewUpdate(&m_update, m_hasFix);
253 m_lastPushedTS = m_update.timestamp();
261QNmeaSimulatedReader::QNmeaSimulatedReader(QNmeaPositionInfoSourcePrivate *sourcePrivate)
262 : QNmeaReader(sourcePrivate),
264 m_hasValidDateTime(
false)
268QNmeaSimulatedReader::~QNmeaSimulatedReader()
270 if (m_currTimerId > 0)
271 killTimer(m_currTimerId);
274void QNmeaSimulatedReader::readAvailableData()
276 if (m_currTimerId > 0)
279 if (!m_hasValidDateTime) {
280 Q_ASSERT(m_proxy->m_device && (m_proxy->m_device->openMode() & QIODevice::ReadOnly));
282 if (!setFirstDateTime()) {
284 qWarning(
"QNmeaPositionInfoSource: cannot find NMEA sentence with valid date & time");
288 m_hasValidDateTime =
true;
289 simulatePendingUpdate();
293 processNextSentence();
297static int processSentence(QGeoPositionInfo &info,
298 QByteArray &m_nextLine,
299 QNmeaPositionInfoSourcePrivate *m_proxy,
300 QQueue<QPendingGeoPositionInfo> &m_pendingUpdates,
303 int timeToNextUpdate = -1;
305 if (!m_pendingUpdates.isEmpty())
306 prevTs = m_pendingUpdates.head().info.timestamp();
310 while (!m_nextLine.isEmpty() || (m_proxy->m_device && m_proxy->m_device->bytesAvailable() > 0)) {
311 char static_buf[1024];
312 char *buf = static_buf;
315 if (!m_nextLine.isEmpty()) {
317 size = m_nextLine.size();
318 nextLine = m_nextLine;
320 buf = nextLine.data();
322 size = m_proxy->m_device->readLine(buf,
sizeof(static_buf));
328 const QTime infoTime = info.timestamp().time();
329 const QDate infoDate = info.timestamp().date();
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
348 QGeoPositionInfoPrivateNmea *pimpl =
new QGeoPositionInfoPrivateNmea;
349 QGeoPositionInfo pos(*pimpl);
350 if (m_proxy->parsePosInfoFromNmeaData(
351 QByteArrayView{buf,
static_cast<qsizetype>(size)}, &pos, &hasFix)) {
356 if (infoTime.isValid()) {
357 if (pos.timestamp().time().isValid()) {
358 const bool newerTime = infoTime < pos.timestamp().time();
359 const bool newerDate = (infoDate.isValid()
360 && pos.timestamp().date().isValid()
361 && infoDate < pos.timestamp().date());
362 if (newerTime || newerDate) {
364 m_nextLine = QByteArray(buf, size);
367 if (infoTime == pos.timestamp().time())
369 mergePositions(info, pos, QByteArray(buf, size));
374 mergePositions(info, pos, QByteArray(buf, size));
379 pimpl->nmeaSentences.append(QByteArray(buf, size));
384 if (prevTs.time().isValid()) {
385 timeToNextUpdate = msecsTo(prevTs, info.timestamp());
386 if (timeToNextUpdate < 0)
387 info = QGeoPositionInfo(*
new QGeoPositionInfoPrivateNmea);
392 return timeToNextUpdate;
395bool QNmeaSimulatedReader::setFirstDateTime()
398 QGeoPositionInfo info(*
new QGeoPositionInfoPrivateNmea);
400 processSentence(info, m_nextLine, m_proxy, m_pendingUpdates, hasFix);
402 if (info.timestamp().time().isValid()) {
403 QPendingGeoPositionInfo pending;
405 pending.hasFix = hasFix;
406 m_pendingUpdates.enqueue(pending);
412void QNmeaSimulatedReader::simulatePendingUpdate()
414 if (!m_pendingUpdates.isEmpty()) {
416 QPendingGeoPositionInfo &pending = m_pendingUpdates.head();
417 m_proxy->notifyNewUpdate(&pending.info, pending.hasFix);
420 processNextSentence();
423void QNmeaSimulatedReader::timerEvent(QTimerEvent *event)
425 killTimer(event->timerId());
427 simulatePendingUpdate();
430void QNmeaSimulatedReader::processNextSentence()
432 QGeoPositionInfo info(*
new QGeoPositionInfoPrivateNmea);
435 int timeToNextUpdate = processSentence(info, m_nextLine, m_proxy, m_pendingUpdates, hasFix);
436 if (timeToNextUpdate < 0)
439 m_pendingUpdates.dequeue();
441 QPendingGeoPositionInfo pending;
443 pending.hasFix = hasFix;
444 m_pendingUpdates.enqueue(pending);
445 m_currTimerId = startTimer(timeToNextUpdate);
452QNmeaPositionInfoSourcePrivate::QNmeaPositionInfoSourcePrivate(QNmeaPositionInfoSource *parent, QNmeaPositionInfoSource::UpdateMode updateMode)
454 m_updateMode(updateMode),
456 m_invokedStart(
false),
457 m_positionError(QGeoPositionInfoSource::UnknownSourceError),
458 m_userEquivalentRangeError(qQNaN()),
463 m_horizontalAccuracy(qQNaN()),
464 m_verticalAccuracy(qQNaN()),
465 m_noUpdateLastInterval(
false),
466 m_updateTimeoutSent(
false),
467 m_connectedReadyRead(
false)
471QNmeaPositionInfoSourcePrivate::~QNmeaPositionInfoSourcePrivate()
474 delete m_updateTimer;
477bool QNmeaPositionInfoSourcePrivate::openSourceDevice()
480 qWarning(
"QNmeaPositionInfoSource: no QIODevice data source, call setDevice() first");
484 if (!m_device->isOpen() && !m_device->open(QIODevice::ReadOnly)) {
485 qWarning(
"QNmeaPositionInfoSource: cannot open QIODevice data source");
489 connect(m_device, SIGNAL(aboutToClose()), SLOT(sourceDataClosed()));
490 connect(m_device, SIGNAL(readChannelFinished()), SLOT(sourceDataClosed()));
491 connect(m_device, SIGNAL(destroyed()), SLOT(sourceDataClosed()));
496void QNmeaPositionInfoSourcePrivate::sourceDataClosed()
498 if (m_nmeaReader && m_device && m_device->bytesAvailable())
499 m_nmeaReader->readAvailableData();
502void QNmeaPositionInfoSourcePrivate::readyRead()
505 m_nmeaReader->readAvailableData();
508bool QNmeaPositionInfoSourcePrivate::initialize()
513 if (!openSourceDevice())
516 if (m_updateMode == QNmeaPositionInfoSource::RealTimeMode)
517 m_nmeaReader =
new QNmeaRealTimeReader(
this);
519 m_nmeaReader =
new QNmeaSimulatedReader(
this);
524void QNmeaPositionInfoSourcePrivate::prepareSourceDevice()
527 if (m_updateMode == QNmeaPositionInfoSource::SimulationMode) {
528 if (m_nmeaReader && m_device->bytesAvailable())
529 m_nmeaReader->readAvailableData();
532 if (!m_connectedReadyRead) {
533 connect(m_device, SIGNAL(readyRead()), SLOT(readyRead()));
534 m_connectedReadyRead =
true;
538bool QNmeaPositionInfoSourcePrivate::parsePosInfoFromNmeaData(QByteArrayView data,
539 QGeoPositionInfo *posInfo,
bool *hasFix)
541 return m_source->parsePosInfoFromNmeaData(data, posInfo, hasFix);
544void QNmeaPositionInfoSourcePrivate::startUpdates()
549 m_positionError = QGeoPositionInfoSource::NoError;
551 m_invokedStart =
true;
552 m_pendingUpdate = QGeoPositionInfo();
553 m_noUpdateLastInterval =
false;
555 bool initialized = initialize();
557 m_source->setError(QGeoPositionInfoSource::AccessError);
561 if (m_updateMode == QNmeaPositionInfoSource::RealTimeMode) {
564 if (m_device->bytesAvailable()) {
565 if (m_device->isSequential())
568 m_device->seek(m_device->bytesAvailable());
573 m_updateTimer->stop();
575 if (m_source->updateInterval() > 0) {
577 m_updateTimer =
new QBasicTimer;
578 m_updateTimer->start(m_source->updateInterval(),
this);
582 prepareSourceDevice();
585void QNmeaPositionInfoSourcePrivate::stopUpdates()
587 m_invokedStart =
false;
589 m_updateTimer->stop();
590 m_pendingUpdate = QGeoPositionInfo();
591 m_noUpdateLastInterval =
false;
594void QNmeaPositionInfoSourcePrivate::requestUpdate(
int msec)
596 if (m_requestTimer && m_requestTimer->isActive())
599 m_positionError = QGeoPositionInfoSource::NoError;
601 if (msec <= 0 || msec < m_source->minimumUpdateInterval()) {
602 m_source->setError(QGeoPositionInfoSource::UpdateTimeoutError);
606 if (!m_requestTimer) {
607 m_requestTimer =
new QTimer(
this);
608 connect(m_requestTimer, SIGNAL(timeout()), SLOT(updateRequestTimeout()));
611 bool initialized = initialize();
613 m_source->setError(QGeoPositionInfoSource::UpdateTimeoutError);
617 m_requestTimer->start(msec);
618 prepareSourceDevice();
621void QNmeaPositionInfoSourcePrivate::updateRequestTimeout()
623 m_requestTimer->stop();
624 m_source->setError(QGeoPositionInfoSource::UpdateTimeoutError);
627void QNmeaPositionInfoSourcePrivate::notifyNewUpdate(QGeoPositionInfo *update,
bool hasFix)
632 QDate date = update->timestamp().date();
633 if (date.isValid()) {
634 m_currentDate = date;
637 QTime time = update->timestamp().time();
638 if (time.isValid() && m_currentDate.isValid())
639 update->setTimestamp(QDateTime(m_currentDate, time, QTimeZone::UTC));
644 if (update->hasAttribute(QGeoPositionInfo::HorizontalAccuracy))
645 m_horizontalAccuracy = update->attribute(QGeoPositionInfo::HorizontalAccuracy);
646 else if (!qIsNaN(m_horizontalAccuracy))
647 update->setAttribute(QGeoPositionInfo::HorizontalAccuracy, m_horizontalAccuracy);
649 if (update->hasAttribute(QGeoPositionInfo::VerticalAccuracy))
650 m_verticalAccuracy = update->attribute(QGeoPositionInfo::VerticalAccuracy);
651 else if (!qIsNaN(m_verticalAccuracy))
652 update->setAttribute(QGeoPositionInfo::VerticalAccuracy, m_verticalAccuracy);
654 if (hasFix && update->isValid()) {
655 if (m_requestTimer && m_requestTimer->isActive()) {
656 m_requestTimer->stop();
657 emitUpdated(*update);
658 }
else if (m_invokedStart) {
659 if (m_updateTimer && m_updateTimer->isActive()) {
661 m_pendingUpdate = *update;
662 if (m_noUpdateLastInterval) {
666 m_noUpdateLastInterval =
false;
669 emitUpdated(*update);
672 m_lastUpdate = *update;
676void QNmeaPositionInfoSourcePrivate::timerEvent(QTimerEvent *)
681void QNmeaPositionInfoSourcePrivate::emitPendingUpdate()
683 if (m_pendingUpdate.isValid()) {
684 m_updateTimeoutSent =
false;
685 m_noUpdateLastInterval =
false;
686 emitUpdated(m_pendingUpdate);
687 m_pendingUpdate = QGeoPositionInfo();
689 if (m_noUpdateLastInterval && !m_updateTimeoutSent) {
690 m_updateTimeoutSent =
true;
691 m_pendingUpdate = QGeoPositionInfo();
692 m_source->setError(QGeoPositionInfoSource::UpdateTimeoutError);
694 m_noUpdateLastInterval =
true;
698void QNmeaPositionInfoSourcePrivate::emitUpdated(
const QGeoPositionInfo &update)
702 m_lastUpdate = update;
703 emit m_source->positionUpdated(update);
709
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
742
743
744
745
746
747
751
752
753
754QNmeaPositionInfoSource::QNmeaPositionInfoSource(UpdateMode updateMode, QObject *parent)
755 : QGeoPositionInfoSource(parent),
756 d(
new QNmeaPositionInfoSourcePrivate(
this, updateMode))
761
762
763QNmeaPositionInfoSource::~QNmeaPositionInfoSource()
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784void QNmeaPositionInfoSource::setUserEquivalentRangeError(
double uere)
786 d->m_userEquivalentRangeError = uere;
790
791
792
793
794
795
796
797
798double QNmeaPositionInfoSource::userEquivalentRangeError()
const
800 return d->m_userEquivalentRangeError;
804
805
806
807
808
809
810
811
812
813
814
815
816
818#if QT_DEPRECATED_SINCE(7
, 0
)
819bool QNmeaPositionInfoSource::parsePosInfoFromNmeaData(
const char *data,
int size,
820 QGeoPositionInfo *posInfo,
bool *hasFix)
822#if QT_VERSION < QT_VERSION_CHECK(7
, 0
, 0
)
823 return QLocationUtils::getPosInfoFromNmea(QByteArrayView{data, size}, posInfo,
824 d->m_userEquivalentRangeError, hasFix);
826 return parsePosInfoFromNmeaData(QByteArrayView{data, size}, posInfo, hasFix);
831bool QNmeaPositionInfoSource::parsePosInfoFromNmeaData(QByteArrayView data,
832 QGeoPositionInfo *posInfo,
bool *hasFix)
834#if QT_VERSION < QT_VERSION_CHECK(7
, 0
, 0
)
835 return parsePosInfoFromNmeaData(data.data(),
static_cast<
int>(data.size()),
838 return QLocationUtils::getPosInfoFromNmea(data, posInfo,
839 d->m_userEquivalentRangeError, hasFix);
845
846
847QNmeaPositionInfoSource::UpdateMode QNmeaPositionInfoSource::updateMode()
const
849 return d->m_updateMode;
853
854
855
856
857
858
859
860
861
862
863
864void QNmeaPositionInfoSource::setDevice(QIODevice *device)
866 if (device != d->m_device) {
868 d->m_device = device;
870 qWarning(
"QNmeaPositionInfoSource: source device has already been set");
875
876
877QIODevice *QNmeaPositionInfoSource::device()
const
883
884
885void QNmeaPositionInfoSource::setUpdateInterval(
int msec)
889 interval = qMax(msec, minimumUpdateInterval());
890 QGeoPositionInfoSource::setUpdateInterval(interval);
891 if (d->m_invokedStart) {
898
899
900void QNmeaPositionInfoSource::startUpdates()
906
907
908void QNmeaPositionInfoSource::stopUpdates()
914
915
916void QNmeaPositionInfoSource::requestUpdate(
int msec)
918 d->requestUpdate(msec == 0 ? 60000 * 5 : msec);
922
923
924QGeoPositionInfo QNmeaPositionInfoSource::lastKnownPosition(
bool)
const
927 return d->m_lastUpdate;
931
932
933QGeoPositionInfoSource::PositioningMethods QNmeaPositionInfoSource::supportedPositioningMethods()
const
935 return SatellitePositioningMethods;
939
940
941int QNmeaPositionInfoSource::minimumUpdateInterval()
const
947
948
949QGeoPositionInfoSource::Error QNmeaPositionInfoSource::error()
const
951 return d->m_positionError;
954void QNmeaPositionInfoSource::setError(QGeoPositionInfoSource::Error positionError)
956 d->m_positionError = positionError;
957 if (d->m_positionError != QGeoPositionInfoSource::NoError)
958 emit QGeoPositionInfoSource::errorOccurred(positionError);
963#include "moc_qnmeapositioninfosource_p.cpp"
964#include "moc_qnmeapositioninfosource.cpp"
QNmeaRealTimeReader(QNmeaPositionInfoSourcePrivate *sourcePrivate)
#define USE_POSITION_NMEA_PIMPL
static bool propagateCoordinate(QGeoPositionInfo &dst, const QGeoPositionInfo &src, bool force=true)
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)