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
qnmeapositioninfosource.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 Jolla Ltd.
2// Copyright (C) 2016 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4// Qt-Security score:significant reason:default
8
9#include <QIODevice>
10#include <QBasicTimer>
11#include <QTimerEvent>
12#include <QTimer>
13#include <array>
14#include <QDebug>
15#include <QtCore/QtNumeric>
16#include <QtCore/QDateTime>
17#include <QtCore/QTimeZone>
18
19#include <algorithm>
20
21QT_BEGIN_NAMESPACE
22
23#define USE_POSITION_NMEA_PIMPL 0
24
26class QGeoPositionInfoPrivateNmea : public QGeoPositionInfoPrivate
27{
28public:
29 virtual ~QGeoPositionInfoPrivateNmea();
30
31 QList<QByteArray> nmeaSentences;
32};
33
34
35QGeoPositionInfoPrivateNmea::~QGeoPositionInfoPrivateNmea()
36{
37
38}
39#else
40typedef QGeoPositionInfoPrivate QGeoPositionInfoPrivateNmea;
41#endif
42
43static bool propagateCoordinate(QGeoPositionInfo &dst, const QGeoPositionInfo &src, bool force = true)
44{
45 bool updated = false;
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());
52 }
53 if (qIsFinite(src.coordinate().longitude())
54 && (!qIsFinite(dst.coordinate().longitude()) || force)) {
55 updated |= (c.longitude() != srcCoordinate.longitude());
56 c.setLongitude(src.coordinate().longitude());
57 }
58 if (qIsFinite(src.coordinate().altitude())
59 && (!qIsFinite(dst.coordinate().altitude()) || force)) {
60 updated |= (c.altitude() != srcCoordinate.altitude());
61 c.setAltitude(src.coordinate().altitude());
62 }
63 dst.setCoordinate(c);
64 return updated;
65}
66
67static bool propagateDate(QGeoPositionInfo &dst, const QGeoPositionInfo &src)
68{
69 if (!dst.timestamp().date().isValid() && src.timestamp().isValid()) { // time was supposed to be set/the same already. Date can be overwritten.
70 dst.setTimestamp(src.timestamp());
71 return true;
72 }
73 return false;
74}
75
76static bool propagateAttributes(QGeoPositionInfo &dst, const QGeoPositionInfo &src, bool force = true)
77{
78 bool updated = false;
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));
90 }
91 }
92
93 return updated;
94}
95
96// returns false if src does not contain any additional or different data than dst,
97// true otherwise.
98static bool mergePositions(QGeoPositionInfo &dst, const QGeoPositionInfo &src, QByteArray nmeaSentence)
99{
100 bool updated = false;
101
102 updated |= propagateCoordinate(dst, src);
103 updated |= propagateDate(dst, src);
104 updated |= propagateAttributes(dst, src);
105
107 QGeoPositionInfoPrivateNmea *dstPimpl = static_cast<QGeoPositionInfoPrivateNmea *>(QGeoPositionInfoPrivate::get(dst));
108 dstPimpl->nmeaSentences.append(nmeaSentence);
109#else
110 Q_UNUSED(nmeaSentence);
111#endif
112 return updated;
113}
114
115static qint64 msecsTo(const QDateTime &from, const QDateTime &to)
116{
117 if (!from.time().isValid() || !to.time().isValid())
118 return 0;
119
120 if (!from.date().isValid() || !to.date().isValid()) // use only time
121 return from.time().msecsTo(to.time());
122
123 return from.msecsTo(to);
124}
125
127 = default;
128
130 : QNmeaReader(sourcePrivate), m_update(*new QGeoPositionInfoPrivateNmea)
131{
132 // An env var controlling the number of milliseconds to use to withold
133 // an update and wait for additional data to combine.
134 // The update will be pushed earlier than this if a newer update will be received.
135 // The update will be withold longer than this amount of time if additional
136 // valid data will keep arriving within this time frame.
137 bool ok = false;
138 int pushDelay = qEnvironmentVariableIntValue("QT_NMEA_PUSH_DELAY", &ok);
139 if (ok)
140 pushDelay = std::clamp(pushDelay, -1, 1000);
141 else
142 pushDelay = 20;
143
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();
149 });
150 }
151 m_pushDelay = pushDelay;
152}
153
154QNmeaRealTimeReader::~QNmeaRealTimeReader()
155 = default;
156
158{
159 while (m_proxy->m_device->canReadLine()) {
160 const QTime infoTime = m_update.timestamp().time(); // if update has been set, time must be valid.
161 const QDate infoDate = m_update.timestamp().date(); // this one might not be valid, as some sentences do not contain it
162
163 QGeoPositionInfoPrivateNmea *pimpl = new QGeoPositionInfoPrivateNmea;
164 QGeoPositionInfo pos(*pimpl);
165
166 char buf[1024];
167 qint64 size = m_proxy->m_device->readLine(buf, sizeof(buf));
168 if (size <= 0)
169 continue;
170
171 const bool oldFix = m_hasFix;
172 bool hasFix;
173 const bool parsed = m_proxy->parsePosInfoFromNmeaData(
174 QByteArrayView{buf, static_cast<qsizetype>(size)}, &pos, &hasFix);
175
176 if (!parsed) {
177 // got garbage, don't stop the timer
178 continue;
179 }
180
181 m_hasFix |= hasFix;
182 m_updateParsed = true;
183
184 // Date may or may not be valid, as some packets do not have date.
185 // If date isn't valid, match is performed on time only.
186 // Hence, make sure that packet blocks are generated with
187 // the sentences containing the full timestamp (e.g., GPRMC) *first* !
188 if (infoTime.isValid()) {
189 if (pos.timestamp().time().isValid()) {
190 const bool newerTime = infoTime < pos.timestamp().time();
191 const bool newerDate = (infoDate.isValid() // if time is valid but one date or both are not,
192 && pos.timestamp().date().isValid()
193 && infoDate < pos.timestamp().date());
194 if (newerTime || newerDate) {
195 // Effectively read data for different update, that is also newer,
196 // so flush retained update, and copy the new pos into m_update
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();
205 }
206 m_timer.stop();
207 // next update data
208 propagateAttributes(pos, m_update, false);
209 m_update = pos;
210 m_hasFix = hasFix;
211 } else {
212 if (infoTime == pos.timestamp().time())
213 // timestamps match -- merge into m_update
214 if (mergePositions(m_update, pos, QByteArray(buf, size))) {
215 // Reset the timer only if new info has been received.
216 // Else the source might be keep repeating outdated info until
217 // new info become available.
218 m_timer.stop();
219 }
220 // else discard out of order outdated info.
221 }
222 } else {
223 // no timestamp available in parsed update-- merge into m_update
224 if (mergePositions(m_update, pos, QByteArray(buf, size)))
225 m_timer.stop();
226 }
227 } else {
228 // there was no info with valid TS. Overwrite with whatever is parsed.
230 pimpl->nmeaSentences.append(QByteArray(buf, size));
231#endif
232 propagateAttributes(pos, m_update);
233 m_update = pos;
234 m_timer.stop();
235 }
236 }
237
238 if (m_updateParsed) {
239 if (m_pushDelay < 0)
241 else
242 m_timer.start();
243 }
244}
245
247{
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();
255 }
256 m_timer.stop();
257}
258
259
260//============================================================
261
262QNmeaSimulatedReader::QNmeaSimulatedReader(QNmeaPositionInfoSourcePrivate *sourcePrivate)
263 : QNmeaReader(sourcePrivate),
264 m_currTimerId(-1),
265 m_hasValidDateTime(false)
266{
267}
268
270{
271 if (m_currTimerId > 0)
272 killTimer(m_currTimerId);
273}
274
276{
277 if (m_currTimerId > 0) // we are already reading
278 return;
279
280 if (!m_hasValidDateTime) { // first update
281 Q_ASSERT(m_proxy->m_device && (m_proxy->m_device->openMode() & QIODevice::ReadOnly));
282
283 if (!setFirstDateTime()) {
284 //m_proxy->notifyReachedEndOfFile();
285 qWarning("QNmeaPositionInfoSource: cannot find NMEA sentence with valid date & time");
286 return;
287 }
288
289 m_hasValidDateTime = true;
290 simulatePendingUpdate();
291
292 } else {
293 // previously read to EOF, but now new data has arrived
294 processNextSentence();
295 }
296}
297
298static int processSentence(QGeoPositionInfo &info,
299 QByteArray &m_nextLine,
301 QQueue<QPendingGeoPositionInfo> &m_pendingUpdates,
302 bool &hasFix)
303{
304 int timeToNextUpdate = -1;
305 QDateTime prevTs;
306 if (!m_pendingUpdates.isEmpty())
307 prevTs = m_pendingUpdates.head().info.timestamp();
308
309 // find the next update with a valid time (as long as the time is valid,
310 // we can calculate when the update should be emitted)
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;
314 QByteArray nextLine;
315 qint64 size = 0;
316 if (!m_nextLine.isEmpty()) {
317 // Read something in the previous call, but TS was later.
318 size = m_nextLine.size();
319 nextLine = m_nextLine;
320 m_nextLine.clear();
321 buf = nextLine.data();
322 } else {
323 size = m_proxy->m_device->readLine(buf, sizeof(static_buf));
324 }
325
326 if (size <= 0)
327 continue;
328
329 const QTime infoTime = info.timestamp().time(); // if info has been set, time must be valid.
330 const QDate infoDate = info.timestamp().date(); // this one might not be valid, as some sentences do not contain it
331
332 /*
333 Packets containing time information are GGA, RMC, ZDA, GLL:
334
335 GGA : GPS fix data - only time
336 GLL : geographic latitude and longitude - only time
337 RMC : recommended minimum FPOS/transit data - date and time
338 ZDA : only timestamp - date and time
339
340 QLocationUtils is currently also capable of parsing VTG and GSA sentences:
341
342 VTG: containing Track made good and ground speed
343 GSA: overall satellite data, w. accuracies (ends up into PositionInfo)
344
345 Since these sentences contain no timestamp, their content will be merged with the content
346 from any prior sentence that had timestamp info, if any is available.
347 */
348
349 QGeoPositionInfoPrivateNmea *pimpl = new QGeoPositionInfoPrivateNmea;
350 QGeoPositionInfo pos(*pimpl);
351 if (m_proxy->parsePosInfoFromNmeaData(
352 QByteArrayView{buf, static_cast<qsizetype>(size)}, &pos, &hasFix)) {
353 // Date may or may not be valid, as some packets do not have date.
354 // If date isn't valid, match is performed on time only.
355 // Hence, make sure that packet blocks are generated with
356 // the sentences containing the full timestamp (e.g., GPRMC) *first* !
357 if (infoTime.isValid()) {
358 if (pos.timestamp().time().isValid()) {
359 const bool newerTime = infoTime < pos.timestamp().time();
360 const bool newerDate = (infoDate.isValid() // if time is valid but one date or both are not,
361 && pos.timestamp().date().isValid()
362 && infoDate < pos.timestamp().date());
363 if (newerTime || newerDate) {
364 // Effectively read data for different update, that is also newer, so copy buf into m_nextLine
365 m_nextLine = QByteArray(buf, size);
366 break;
367 } else {
368 if (infoTime == pos.timestamp().time())
369 // timestamps match -- merge into info
370 mergePositions(info, pos, QByteArray(buf, size));
371 // else discard out of order outdated info.
372 }
373 } else {
374 // no timestamp available -- merge into info
375 mergePositions(info, pos, QByteArray(buf, size));
376 }
377 } else {
378 // there was no info with valid TS. Overwrite with whatever is parsed.
380 pimpl->nmeaSentences.append(QByteArray(buf, size));
381#endif
382 info = pos;
383 }
384
385 if (prevTs.time().isValid()) {
386 timeToNextUpdate = msecsTo(prevTs, info.timestamp());
387 if (timeToNextUpdate < 0) // Somehow parsing expired packets, reset info
388 info = QGeoPositionInfo(*new QGeoPositionInfoPrivateNmea);
389 }
390 }
391 }
392
393 return timeToNextUpdate;
394}
395
396bool QNmeaSimulatedReader::setFirstDateTime()
397{
398 // find the first update with valid date and time
399 QGeoPositionInfo info(*new QGeoPositionInfoPrivateNmea);
400 bool hasFix = false;
401 processSentence(info, m_nextLine, m_proxy, m_pendingUpdates, hasFix);
402
403 if (info.timestamp().time().isValid()) { // NMEA may have sentences with only time and no date. These would generate invalid positions
405 pending.info = info;
406 pending.hasFix = hasFix;
407 m_pendingUpdates.enqueue(pending);
408 return true;
409 }
410 return false;
411}
412
413void QNmeaSimulatedReader::simulatePendingUpdate()
414{
415 if (!m_pendingUpdates.isEmpty()) {
416 // will be dequeued in processNextSentence()
417 QPendingGeoPositionInfo &pending = m_pendingUpdates.head();
418 m_proxy->notifyNewUpdate(&pending.info, pending.hasFix);
419 }
420
421 processNextSentence();
422}
423
424void QNmeaSimulatedReader::timerEvent(QTimerEvent *event)
425{
426 killTimer(event->timerId());
427 m_currTimerId = -1;
428 simulatePendingUpdate();
429}
430
431void QNmeaSimulatedReader::processNextSentence()
432{
433 QGeoPositionInfo info(*new QGeoPositionInfoPrivateNmea);
434 bool hasFix = false;
435
436 int timeToNextUpdate = processSentence(info, m_nextLine, m_proxy, m_pendingUpdates, hasFix);
437 if (timeToNextUpdate < 0)
438 return;
439
440 m_pendingUpdates.dequeue();
441
443 pending.info = info;
444 pending.hasFix = hasFix;
445 m_pendingUpdates.enqueue(pending);
446 m_currTimerId = startTimer(timeToNextUpdate);
447}
448
449
450//============================================================
451
452
453QNmeaPositionInfoSourcePrivate::QNmeaPositionInfoSourcePrivate(QNmeaPositionInfoSource *parent, QNmeaPositionInfoSource::UpdateMode updateMode)
454 : QObject(parent),
455 m_updateMode(updateMode),
456 m_device(0),
457 m_invokedStart(false),
458 m_positionError(QGeoPositionInfoSource::UnknownSourceError),
460 m_source(parent),
461 m_nmeaReader(0),
462 m_updateTimer(0),
463 m_requestTimer(0),
464 m_horizontalAccuracy(qQNaN()),
465 m_verticalAccuracy(qQNaN()),
466 m_noUpdateLastInterval(false),
467 m_updateTimeoutSent(false),
468 m_connectedReadyRead(false)
469{
470}
471
473{
474 delete m_nmeaReader;
475 delete m_updateTimer;
476}
477
478bool QNmeaPositionInfoSourcePrivate::openSourceDevice()
479{
480 if (!m_device) {
481 qWarning("QNmeaPositionInfoSource: no QIODevice data source, call setDevice() first");
482 return false;
483 }
484
485 if (!m_device->isOpen() && !m_device->open(QIODevice::ReadOnly)) {
486 qWarning("QNmeaPositionInfoSource: cannot open QIODevice data source");
487 return false;
488 }
489
490 connect(m_device, SIGNAL(aboutToClose()), SLOT(sourceDataClosed()));
491 connect(m_device, SIGNAL(readChannelFinished()), SLOT(sourceDataClosed()));
492 connect(m_device, SIGNAL(destroyed()), SLOT(sourceDataClosed()));
493
494 return true;
495}
496
497void QNmeaPositionInfoSourcePrivate::sourceDataClosed()
498{
499 if (m_nmeaReader && m_device && m_device->bytesAvailable())
500 m_nmeaReader->readAvailableData();
501}
502
504{
505 if (m_nmeaReader)
506 m_nmeaReader->readAvailableData();
507}
508
509bool QNmeaPositionInfoSourcePrivate::initialize()
510{
511 if (m_nmeaReader)
512 return true;
513
514 if (!openSourceDevice())
515 return false;
516
517 if (m_updateMode == QNmeaPositionInfoSource::RealTimeMode)
518 m_nmeaReader = new QNmeaRealTimeReader(this);
519 else
520 m_nmeaReader = new QNmeaSimulatedReader(this);
521
522 return true;
523}
524
525void QNmeaPositionInfoSourcePrivate::prepareSourceDevice()
526{
527 // some data may already be available
528 if (m_updateMode == QNmeaPositionInfoSource::SimulationMode) {
529 if (m_nmeaReader && m_device->bytesAvailable())
530 m_nmeaReader->readAvailableData();
531 }
532
533 if (!m_connectedReadyRead) {
534 connect(m_device, SIGNAL(readyRead()), SLOT(readyRead()));
535 m_connectedReadyRead = true;
536 }
537}
538
540 QGeoPositionInfo *posInfo, bool *hasFix)
541{
542 return m_source->parsePosInfoFromNmeaData(data, posInfo, hasFix);
543}
544
546{
547 if (m_invokedStart)
548 return;
549
550 m_positionError = QGeoPositionInfoSource::NoError;
551
552 m_invokedStart = true;
553 m_pendingUpdate = QGeoPositionInfo();
554 m_noUpdateLastInterval = false;
555
556 bool initialized = initialize();
557 if (!initialized) {
558 m_source->setError(QGeoPositionInfoSource::AccessError);
559 return;
560 }
561
562 if (m_updateMode == QNmeaPositionInfoSource::RealTimeMode) {
563 // skip over any buffered data - we only want the newest data.
564 // Don't do this in requestUpdate. In that case bufferedData is good to have/use.
565 if (m_device->bytesAvailable()) {
566 if (m_device->isSequential())
567 m_device->readAll();
568 else
569 m_device->seek(m_device->bytesAvailable());
570 }
571 }
572
573 if (m_updateTimer)
574 m_updateTimer->stop();
575
576 if (m_source->updateInterval() > 0) {
577 if (!m_updateTimer)
578 m_updateTimer = new QBasicTimer;
579 m_updateTimer->start(m_source->updateInterval(), this);
580 }
581
582 if (initialized)
583 prepareSourceDevice();
584}
585
587{
588 m_invokedStart = false;
589 if (m_updateTimer)
590 m_updateTimer->stop();
591 m_pendingUpdate = QGeoPositionInfo();
592 m_noUpdateLastInterval = false;
593}
594
596{
597 if (m_requestTimer && m_requestTimer->isActive())
598 return;
599
600 m_positionError = QGeoPositionInfoSource::NoError;
601
602 if (msec <= 0 || msec < m_source->minimumUpdateInterval()) {
603 m_source->setError(QGeoPositionInfoSource::UpdateTimeoutError);
604 return;
605 }
606
607 if (!m_requestTimer) {
608 m_requestTimer = new QTimer(this);
609 connect(m_requestTimer, SIGNAL(timeout()), SLOT(updateRequestTimeout()));
610 }
611
612 bool initialized = initialize();
613 if (!initialized) {
614 m_source->setError(QGeoPositionInfoSource::UpdateTimeoutError);
615 return;
616 }
617
618 m_requestTimer->start(msec);
619 prepareSourceDevice();
620}
621
622void QNmeaPositionInfoSourcePrivate::updateRequestTimeout()
623{
624 m_requestTimer->stop();
625 m_source->setError(QGeoPositionInfoSource::UpdateTimeoutError);
626}
627
628void QNmeaPositionInfoSourcePrivate::notifyNewUpdate(QGeoPositionInfo *update, bool hasFix)
629{
630 // include <QDebug> before uncommenting
631 //qDebug() << "QNmeaPositionInfoSourcePrivate::notifyNewUpdate()" << update->timestamp() << hasFix << m_invokedStart << (m_requestTimer && m_requestTimer->isActive());
632
633 QDate date = update->timestamp().date();
634 if (date.isValid()) {
635 m_currentDate = date;
636 } else {
637 // some sentence have time but no date
638 QTime time = update->timestamp().time();
639 if (time.isValid() && m_currentDate.isValid())
640 update->setTimestamp(QDateTime(m_currentDate, time, QTimeZone::UTC));
641 }
642
643 // Some attributes are sent in separate NMEA sentences. Save and restore the accuracy
644 // measurements.
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);
649
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);
654
655 if (hasFix && update->isValid()) {
656 if (m_requestTimer && m_requestTimer->isActive()) { // User called requestUpdate()
657 m_requestTimer->stop();
658 emitUpdated(*update);
659 } else if (m_invokedStart) { // user called startUpdates()
660 if (m_updateTimer && m_updateTimer->isActive()) { // update interval > 0
661 // for periodic updates, only want the most recent update
662 m_pendingUpdate = *update; // Set what to send in timerEvent()
663 if (m_noUpdateLastInterval) {
664 // if the update was invalid when timerEvent was last called, a valid update
665 // should be sent ASAP
666 emitPendingUpdate();
667 m_noUpdateLastInterval = false;
668 }
669 } else { // update interval <= 0
670 emitUpdated(*update);
671 }
672 }
673 m_lastUpdate = *update; // Set in any case, if update is valid. Used in lastKnownPosition().
674 }
675}
676
678{
679 emitPendingUpdate();
680}
681
682void QNmeaPositionInfoSourcePrivate::emitPendingUpdate()
683{
684 if (m_pendingUpdate.isValid()) {
685 m_updateTimeoutSent = false;
686 m_noUpdateLastInterval = false;
687 emitUpdated(m_pendingUpdate);
688 m_pendingUpdate = QGeoPositionInfo();
689 } else { // invalid update
690 if (m_noUpdateLastInterval && !m_updateTimeoutSent) {
691 m_updateTimeoutSent = true;
692 m_pendingUpdate = QGeoPositionInfo(); // Invalid already, but clear just in case.
693 m_source->setError(QGeoPositionInfoSource::UpdateTimeoutError);
694 }
695 m_noUpdateLastInterval = true;
696 }
697}
698
699void QNmeaPositionInfoSourcePrivate::emitUpdated(const QGeoPositionInfo &update)
700{
701 // check for duplication already done in QNmeaRealTimeReader::notifyNewUpdate
702 // and QNmeaRealTimeReader::readAvailableData
703 m_lastUpdate = update;
704 emit m_source->positionUpdated(update);
705}
706
707//=========================================================
708
709/*!
710 \class QNmeaPositionInfoSource
711 \inmodule QtPositioning
712 \ingroup QtPositioning-positioning
713 \since 5.2
714
715 \brief The QNmeaPositionInfoSource class provides positional information using a NMEA data source.
716
717 NMEA is a commonly used protocol for the specification of one's global
718 position at a certain point in time. The QNmeaPositionInfoSource class reads NMEA
719 data and uses it to provide positional data in the form of
720 QGeoPositionInfo objects.
721
722 A QNmeaPositionInfoSource instance operates in either \l {RealTimeMode} or
723 \l {SimulationMode}. These modes allow NMEA data to be read from either a
724 live source of positional data, or replayed for simulation purposes from
725 previously recorded NMEA data.
726
727 The source of NMEA data is set with setDevice().
728
729 Use startUpdates() to start receiving regular position updates and stopUpdates() to stop these
730 updates. If you only require updates occasionally, you can call requestUpdate() to request a
731 single update.
732
733 In both cases the position information is received via the positionUpdated() signal and the
734 last known position can be accessed with lastKnownPosition().
735
736 QNmeaPositionInfoSource supports reporting the accuracy of the horizontal and vertical position.
737 To enable position accuracy reporting an estimate of the User Equivalent Range Error associated
738 with the NMEA source must be set with setUserEquivalentRangeError().
739*/
740
741
742/*!
743 \enum QNmeaPositionInfoSource::UpdateMode
744 Defines the available update modes.
745
746 \value RealTimeMode Positional data is read and distributed from the data source as it becomes available. Use this mode if you are using a live source of positional data (for example, a GPS hardware device).
747 \value SimulationMode The data and time information in the NMEA source data is used to provide positional updates at the rate at which the data was originally recorded. Use this mode if the data source contains previously recorded NMEA data and you want to replay the data for simulation purposes.
748*/
749
750
751/*!
752 Constructs a QNmeaPositionInfoSource instance with the given \a parent
753 and \a updateMode.
754*/
755QNmeaPositionInfoSource::QNmeaPositionInfoSource(UpdateMode updateMode, QObject *parent)
756 : QGeoPositionInfoSource(parent),
757 d(new QNmeaPositionInfoSourcePrivate(this, updateMode))
758{
759}
760
761/*!
762 Destroys the position source.
763*/
764QNmeaPositionInfoSource::~QNmeaPositionInfoSource()
765{
766 delete d;
767}
768
769/*!
770 Sets the User Equivalent Range Error (UERE) to \a uere. The UERE is used in calculating an
771 estimate of the accuracy of the position information reported by the position info source. The
772 UERE should be set to a value appropriate for the GPS device which generated the NMEA stream.
773
774 The true UERE value is calculated from multiple error sources including errors introduced by
775 the satellites and signal propogation delays through the atmosphere as well as errors
776 introduced by the receiving GPS equipment. For details on GPS accuracy see
777 \l {https://web.archive.org/web/20161212144906if_/http://edu-observatory.org/gps/gps_accuracy.html} {Sam J. Wormley, GPS Errors & Estimating Your Reveiver's Accuracy}.
778
779 A typical value for UERE is approximately 5.1.
780
781 \since 5.3
782
783 \sa userEquivalentRangeError()
784*/
785void QNmeaPositionInfoSource::setUserEquivalentRangeError(double uere)
786{
787 d->m_userEquivalentRangeError = uere;
788}
789
790/*!
791 Returns the current User Equivalent Range Error (UERE). The UERE is used in calculating an
792 estimate of the accuracy of the position information reported by the position info source. The
793 default value is NaN which means no accuracy information will be provided.
794
795 \since 5.3
796
797 \sa setUserEquivalentRangeError()
798*/
799double QNmeaPositionInfoSource::userEquivalentRangeError() const
800{
801 return d->m_userEquivalentRangeError;
802}
803
804/*!
805 Parses an NMEA sentence string into a QGeoPositionInfo.
806
807 The default implementation will parse standard NMEA sentences.
808 This method should be reimplemented in a subclass whenever the need to deal with non-standard
809 NMEA sentences arises.
810
811 The parser reads \a size bytes from \a data and uses that information to setup \a posInfo and
812 \a hasFix. If \a hasFix is set to false then \a posInfo may contain only the time or the date
813 and the time.
814
815 Returns true if the sentence was successfully parsed, otherwise returns false and should not
816 modifiy \a posInfo or \a hasFix.
817*/
818
819#if QT_DEPRECATED_SINCE(7, 0)
820bool QNmeaPositionInfoSource::parsePosInfoFromNmeaData(const char *data, int size,
821 QGeoPositionInfo *posInfo, bool *hasFix)
822{
823#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
824 return QLocationUtils::getPosInfoFromNmea(QByteArrayView{data, size}, posInfo,
825 d->m_userEquivalentRangeError, hasFix);
826#else
827 return parsePosInfoFromNmeaData(QByteArrayView{data, size}, posInfo, hasFix);
828#endif
829}
830#endif // QT_DEPRECATED_SINCE(7, 0)
831
832bool QNmeaPositionInfoSource::parsePosInfoFromNmeaData(QByteArrayView data,
833 QGeoPositionInfo *posInfo, bool *hasFix)
834{
835#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
836 return parsePosInfoFromNmeaData(data.data(), static_cast<int>(data.size()),
837 posInfo, hasFix);
838#else
839 return QLocationUtils::getPosInfoFromNmea(data, posInfo,
840 d->m_userEquivalentRangeError, hasFix);
841#endif
842}
843
844
845/*!
846 Returns the update mode.
847*/
848QNmeaPositionInfoSource::UpdateMode QNmeaPositionInfoSource::updateMode() const
849{
850 return d->m_updateMode;
851}
852
853/*!
854 Sets the NMEA data source to \a device. If the device is not open, it
855 will be opened in \l{QIODeviceBase::}{ReadOnly} mode.
856
857 The source device can only be set once and must be set before calling
858 \l startUpdates() or \l requestUpdate().
859
860 \note The \a device must emit \l {QIODevice::readyRead()} for the
861 source to be notified when data is available for reading.
862 \l QNmeaPositionInfoSource does not assume the ownership of the device,
863 and hence does not deallocate it upon destruction.
864*/
865void QNmeaPositionInfoSource::setDevice(QIODevice *device)
866{
867 if (device != d->m_device) {
868 if (!d->m_device)
869 d->m_device = device;
870 else
871 qWarning("QNmeaPositionInfoSource: source device has already been set");
872 }
873}
874
875/*!
876 Returns the NMEA data source.
877*/
878QIODevice *QNmeaPositionInfoSource::device() const
879{
880 return d->m_device;
881}
882
883/*!
884 \reimp
885*/
886void QNmeaPositionInfoSource::setUpdateInterval(int msec)
887{
888 int interval = msec;
889 if (interval != 0)
890 interval = qMax(msec, minimumUpdateInterval());
891 QGeoPositionInfoSource::setUpdateInterval(interval);
892 if (d->m_invokedStart) {
893 d->stopUpdates();
894 d->startUpdates();
895 }
896}
897
898/*!
899 \reimp
900*/
901void QNmeaPositionInfoSource::startUpdates()
902{
903 d->startUpdates();
904}
905
906/*!
907 \reimp
908*/
909void QNmeaPositionInfoSource::stopUpdates()
910{
911 d->stopUpdates();
912}
913
914/*!
915 \reimp
916*/
917void QNmeaPositionInfoSource::requestUpdate(int msec)
918{
919 d->requestUpdate(msec == 0 ? 60000 * 5 : msec); // 5min default timeout
920}
921
922/*!
923 \reimp
924*/
925QGeoPositionInfo QNmeaPositionInfoSource::lastKnownPosition(bool) const
926{
927 // the bool value does not matter since we only use satellite positioning
928 return d->m_lastUpdate;
929}
930
931/*!
932 \reimp
933*/
934QGeoPositionInfoSource::PositioningMethods QNmeaPositionInfoSource::supportedPositioningMethods() const
935{
936 return SatellitePositioningMethods;
937}
938
939/*!
940 \reimp
941*/
942int QNmeaPositionInfoSource::minimumUpdateInterval() const
943{
944 return 2; // Some chips are capable of over 100 updates per seconds.
945}
946
947/*!
948 \reimp
949*/
950QGeoPositionInfoSource::Error QNmeaPositionInfoSource::error() const
951{
952 return d->m_positionError;
953}
954
955void QNmeaPositionInfoSource::setError(QGeoPositionInfoSource::Error positionError)
956{
957 d->m_positionError = positionError;
958 if (d->m_positionError != QGeoPositionInfoSource::NoError)
959 emit QGeoPositionInfoSource::errorOccurred(positionError);
960}
961
962QT_END_NAMESPACE
963
964#include "moc_qnmeapositioninfosource_p.cpp"
965#include "moc_qnmeapositioninfosource.cpp"
void notifyNewUpdate(QGeoPositionInfo *update, bool fixStatus)
bool parsePosInfoFromNmeaData(QByteArrayView data, QGeoPositionInfo *posInfo, bool *hasFix)
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
virtual ~QNmeaReader()
QNmeaRealTimeReader(QNmeaPositionInfoSourcePrivate *sourcePrivate)
void timerEvent(QTimerEvent *event) override
This event handler can be reimplemented in a subclass to receive timer events for the object.
#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)