1/****************************************************************************
2**
3** Copyright (C) 2016 Jolla Ltd.
4** Contact: Aaron McCarthy <aaron.mccarthy@jollamobile.com>
5** Copyright (C) 2016 The Qt Company Ltd.
6** Contact: https://www.qt.io/licensing/
7**
8** This file is part of the QtPositioning module of the Qt Toolkit.
9**
10** $QT_BEGIN_LICENSE:LGPL$
11** Commercial License Usage
12** Licensees holding valid commercial Qt licenses may use this file in
13** accordance with the commercial license agreement provided with the
14** Software or, alternatively, in accordance with the terms contained in
15** a written agreement between you and The Qt Company. For licensing terms
16** and conditions see https://www.qt.io/terms-conditions. For further
17** information use the contact form at https://www.qt.io/contact-us.
18**
19** GNU Lesser General Public License Usage
20** Alternatively, this file may be used under the terms of the GNU Lesser
21** General Public License version 3 as published by the Free Software
22** Foundation and appearing in the file LICENSE.LGPL3 included in the
23** packaging of this file. Please review the following information to
24** ensure the GNU Lesser General Public License version 3 requirements
25** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
26**
27** GNU General Public License Usage
28** Alternatively, this file may be used under the terms of the GNU
29** General Public License version 2.0 or (at your option) the GNU General
30** Public license version 3 or any later version approved by the KDE Free
31** Qt Foundation. The licenses are as published by the Free Software
32** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
33** included in the packaging of this file. Please review the following
34** information to ensure the GNU General Public License requirements will
35** be met: https://www.gnu.org/licenses/gpl-2.0.html and
36** https://www.gnu.org/licenses/gpl-3.0.html.
37**
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41#include "qnmeapositioninfosource_p.h"
42#include "qgeopositioninfo_p.h"
43#include "qlocationutils_p.h"
44
45#include <QIODevice>
46#include <QBasicTimer>
47#include <QTimerEvent>
48#include <QTimer>
49#include <array>
50#include <QDebug>
51#include <QtCore/QtNumeric>
52
53
54QT_BEGIN_NAMESPACE
55
56#define USE_NMEA_PIMPL 0
57
58#if USE_NMEA_PIMPL
59class QGeoPositionInfoPrivateNmea : public QGeoPositionInfoPrivate
60{
61public:
62 virtual ~QGeoPositionInfoPrivateNmea();
63 virtual QGeoPositionInfoPrivate *clone() const;
64
65 QList<QByteArray> nmeaSentences;
66};
67
68
69QGeoPositionInfoPrivateNmea::~QGeoPositionInfoPrivateNmea()
70{
71
72}
73
74QGeoPositionInfoPrivate *QGeoPositionInfoPrivateNmea::clone() const
75{
76 return new QGeoPositionInfoPrivateNmea(*this);
77}
78#else
79typedef QGeoPositionInfoPrivate QGeoPositionInfoPrivateNmea;
80#endif
81
82static bool propagateCoordinate(QGeoPositionInfo &dst, const QGeoPositionInfo &src, bool force = true)
83{
84 bool updated = false;
85 QGeoCoordinate c = dst.coordinate();
86 const QGeoCoordinate & srcCoordinate = src.coordinate();
87 if (qIsFinite(d: src.coordinate().latitude())
88 && (!qIsFinite(d: dst.coordinate().latitude()) || force)) {
89 updated |= (c.latitude() != srcCoordinate.latitude());
90 c.setLatitude(src.coordinate().latitude());
91 }
92 if (qIsFinite(d: src.coordinate().longitude())
93 && (!qIsFinite(d: dst.coordinate().longitude()) || force)) {
94 updated |= (c.longitude() != srcCoordinate.longitude());
95 c.setLongitude(src.coordinate().longitude());
96 }
97 if (qIsFinite(d: src.coordinate().altitude())
98 && (!qIsFinite(d: dst.coordinate().altitude()) || force)) {
99 updated |= (c.altitude() != srcCoordinate.altitude());
100 c.setAltitude(src.coordinate().altitude());
101 }
102 dst.setCoordinate(c);
103 return updated;
104}
105
106static bool propagateDate(QGeoPositionInfo &dst, const QGeoPositionInfo &src)
107{
108 if (!dst.timestamp().date().isValid() && src.timestamp().isValid()) { // time was supposed to be set/the same already. Date can be overwritten.
109 dst.setTimestamp(src.timestamp());
110 return true;
111 }
112 return false;
113}
114
115static bool propagateAttributes(QGeoPositionInfo &dst, const QGeoPositionInfo &src, bool force = true)
116{
117 bool updated = false;
118 static Q_DECL_CONSTEXPR std::array<QGeoPositionInfo::Attribute, 6> attrs {
119 ._M_elems: { QGeoPositionInfo::GroundSpeed
120 ,QGeoPositionInfo::HorizontalAccuracy
121 ,QGeoPositionInfo::VerticalAccuracy
122 ,QGeoPositionInfo::Direction
123 ,QGeoPositionInfo::VerticalSpeed
124 ,QGeoPositionInfo::MagneticVariation} };
125 for (const auto a: attrs) {
126 if (src.hasAttribute(attribute: a) && (!dst.hasAttribute(attribute: a) || force)) {
127 updated |= (dst.attribute(attribute: a) != src.attribute(attribute: a));
128 dst.setAttribute(attribute: a, value: src.attribute(attribute: a));
129 }
130 }
131
132 return updated;
133}
134
135// returns false if src does not contain any additional or different data than dst,
136// true otherwise.
137static bool mergePositions(QGeoPositionInfo &dst, const QGeoPositionInfo &src, QByteArray nmeaSentence)
138{
139 bool updated = false;
140
141 updated |= propagateCoordinate(dst, src);
142 updated |= propagateDate(dst, src);
143 updated |= propagateAttributes(dst, src);
144
145#if USE_NMEA_PIMPL
146 QGeoPositionInfoPrivateNmea *dstPimpl = static_cast<QGeoPositionInfoPrivateNmea *>(QGeoPositionInfoPrivate::get(dst));
147 dstPimpl->nmeaSentences.append(nmeaSentence);
148#else
149 Q_UNUSED(nmeaSentence);
150#endif
151 return updated;
152}
153
154static qint64 msecsTo(const QDateTime &from, const QDateTime &to)
155{
156 if (!from.time().isValid() || !to.time().isValid())
157 return 0;
158
159 if (!from.date().isValid() || !to.date().isValid()) // use only time
160 return from.time().msecsTo(to.time());
161
162 return from.msecsTo(to);
163}
164
165QNmeaRealTimeReader::QNmeaRealTimeReader(QNmeaPositionInfoSourcePrivate *sourcePrivate)
166 : QNmeaReader(sourcePrivate), m_update(*new QGeoPositionInfoPrivateNmea)
167{
168 // An env var controlling the number of milliseconds to use to withold
169 // an update and wait for additional data to combine.
170 // The update will be pushed earlier than this if a newer update will be received.
171 // The update will be withold longer than this amount of time if additional
172 // valid data will keep arriving within this time frame.
173 QByteArray pushDelay = qgetenv(varName: "QT_NMEA_PUSH_DELAY");
174 if (!pushDelay.isEmpty())
175 m_pushDelay = qBound(min: -1, val: QString::fromLatin1(str: pushDelay).toInt(), max: 1000);
176 else
177 m_pushDelay = 20;
178
179 if (m_pushDelay >= 0) {
180 m_timer.setSingleShot(true);
181 m_timer.setInterval(m_pushDelay);
182 m_timer.connect(sender: &m_timer, signal: &QTimer::timeout, slot: [this]() {
183 this->notifyNewUpdate();
184 });
185 }
186}
187
188void QNmeaRealTimeReader::readAvailableData()
189{
190 while (m_proxy->m_device->canReadLine()) {
191 const QTime infoTime = m_update.timestamp().time(); // if update has been set, time must be valid.
192 const QDate infoDate = m_update.timestamp().date(); // this one might not be valid, as some sentences do not contain it
193
194 QGeoPositionInfoPrivateNmea *pimpl = new QGeoPositionInfoPrivateNmea;
195 QGeoPositionInfo pos(*pimpl);
196
197 char buf[1024];
198 qint64 size = m_proxy->m_device->readLine(data: buf, maxlen: sizeof(buf));
199 const bool oldFix = m_hasFix;
200 bool hasFix;
201 const bool parsed = m_proxy->parsePosInfoFromNmeaData(data: buf, size, posInfo: &pos, hasFix: &hasFix);
202
203 if (!parsed) {
204 // got garbage, don't stop the timer
205 continue;
206 }
207
208 m_hasFix |= hasFix;
209 m_updateParsed = true;
210
211 // Date may or may not be valid, as some packets do not have date.
212 // If date isn't valid, match is performed on time only.
213 // Hence, make sure that packet blocks are generated with
214 // the sentences containing the full timestamp (e.g., GPRMC) *first* !
215 if (infoTime.isValid()) {
216 if (pos.timestamp().time().isValid()) {
217 const bool newerTime = infoTime < pos.timestamp().time();
218 const bool newerDate = (infoDate.isValid() // if time is valid but one date or both are not,
219 && pos.timestamp().date().isValid()
220 && infoDate < pos.timestamp().date());
221 if (newerTime || newerDate) {
222 // Effectively read data for different update, that is also newer,
223 // so flush retained update, and copy the new pos into m_update
224 const QDate updateDate = m_update.timestamp().date();
225 const QDate lastPushedDate = m_lastPushedTS.date();
226 const bool newerTimestampSinceLastPushed = m_update.timestamp() > m_lastPushedTS;
227 const bool invalidDate = !(updateDate.isValid() && lastPushedDate.isValid());
228 const bool newerTimeSinceLastPushed = m_update.timestamp().time() > m_lastPushedTS.time();
229 if ( newerTimestampSinceLastPushed || (invalidDate && newerTimeSinceLastPushed)) {
230 m_proxy->notifyNewUpdate(update: &m_update, fixStatus: oldFix);
231 m_lastPushedTS = m_update.timestamp();
232 }
233 m_timer.stop();
234 // next update data
235 propagateAttributes(dst&: pos, src: m_update, force: false);
236 m_update = pos;
237 m_hasFix = hasFix;
238 } else {
239 if (infoTime == pos.timestamp().time())
240 // timestamps match -- merge into m_update
241 if (mergePositions(dst&: m_update, src: pos, nmeaSentence: QByteArray(buf, size))) {
242 // Reset the timer only if new info has been received.
243 // Else the source might be keep repeating outdated info until
244 // new info become available.
245 m_timer.stop();
246 }
247 // else discard out of order outdated info.
248 }
249 } else {
250 // no timestamp available in parsed update-- merge into m_update
251 if (mergePositions(dst&: m_update, src: pos, nmeaSentence: QByteArray(buf, size)))
252 m_timer.stop();
253 }
254 } else {
255 // there was no info with valid TS. Overwrite with whatever is parsed.
256#if USE_NMEA_PIMPL
257 pimpl->nmeaSentences.append(QByteArray(buf, size));
258#endif
259 propagateAttributes(dst&: pos, src: m_update);
260 m_update = pos;
261 m_timer.stop();
262 }
263 }
264
265 if (m_updateParsed) {
266 if (m_pushDelay < 0)
267 notifyNewUpdate();
268 else
269 m_timer.start();
270 }
271}
272
273void QNmeaRealTimeReader::notifyNewUpdate()
274{
275 const bool newerTime = m_update.timestamp().time() > m_lastPushedTS.time();
276 const bool newerDate = (m_update.timestamp().date().isValid()
277 && m_lastPushedTS.date().isValid()
278 && m_update.timestamp().date() > m_lastPushedTS.date());
279 if (newerTime || newerDate) {
280 m_proxy->notifyNewUpdate(update: &m_update, fixStatus: m_hasFix);
281 m_lastPushedTS = m_update.timestamp();
282 }
283 m_timer.stop();
284}
285
286
287//============================================================
288
289QNmeaSimulatedReader::QNmeaSimulatedReader(QNmeaPositionInfoSourcePrivate *sourcePrivate)
290 : QNmeaReader(sourcePrivate),
291 m_currTimerId(-1),
292 m_hasValidDateTime(false)
293{
294}
295
296QNmeaSimulatedReader::~QNmeaSimulatedReader()
297{
298 if (m_currTimerId > 0)
299 killTimer(id: m_currTimerId);
300}
301
302void QNmeaSimulatedReader::readAvailableData()
303{
304 if (m_currTimerId > 0) // we are already reading
305 return;
306
307 if (!m_hasValidDateTime) { // first update
308 Q_ASSERT(m_proxy->m_device && (m_proxy->m_device->openMode() & QIODevice::ReadOnly));
309
310 if (!setFirstDateTime()) {
311 //m_proxy->notifyReachedEndOfFile();
312 qWarning(msg: "QNmeaPositionInfoSource: cannot find NMEA sentence with valid date & time");
313 return;
314 }
315
316 m_hasValidDateTime = true;
317 simulatePendingUpdate();
318
319 } else {
320 // previously read to EOF, but now new data has arrived
321 processNextSentence();
322 }
323}
324
325static int processSentence(QGeoPositionInfo &info,
326 QByteArray &m_nextLine,
327 QNmeaPositionInfoSourcePrivate *m_proxy,
328 QQueue<QPendingGeoPositionInfo> &m_pendingUpdates,
329 bool &hasFix)
330{
331 int timeToNextUpdate = -1;
332 QDateTime prevTs;
333 if (m_pendingUpdates.size() > 0)
334 prevTs = m_pendingUpdates.head().info.timestamp();
335
336 // find the next update with a valid time (as long as the time is valid,
337 // we can calculate when the update should be emitted)
338 while (m_nextLine.size() || (m_proxy->m_device && m_proxy->m_device->bytesAvailable() > 0)) {
339 char static_buf[1024];
340 char *buf = static_buf;
341 QByteArray nextLine;
342 qint64 size = 0;
343 if (m_nextLine.size()) {
344 // Read something in the previous call, but TS was later.
345 size = m_nextLine.size();
346 nextLine = m_nextLine;
347 m_nextLine.clear();
348 buf = nextLine.data();
349 } else {
350 size = m_proxy->m_device->readLine(data: buf, maxlen: sizeof(static_buf));
351 }
352
353 if (size <= 0)
354 continue;
355
356 const QTime infoTime = info.timestamp().time(); // if info has been set, time must be valid.
357 const QDate infoDate = info.timestamp().date(); // this one might not be valid, as some sentences do not contain it
358
359 /*
360 Packets containing time information are GGA, RMC, ZDA, GLL:
361
362 GGA : GPS fix data - only time
363 GLL : geographic latitude and longitude - only time
364 RMC : recommended minimum FPOS/transit data - date and time
365 ZDA : only timestamp - date and time
366
367 QLocationUtils is currently also capable of parsing VTG and GSA sentences:
368
369 VTG: containing Track made good and ground speed
370 GSA: overall satellite data, w. accuracies (ends up into PositionInfo)
371
372 Since these sentences contain no timestamp, their content will be merged with the content
373 from any prior sentence that had timestamp info, if any is available.
374 */
375
376 QGeoPositionInfoPrivateNmea *pimpl = new QGeoPositionInfoPrivateNmea;
377 QGeoPositionInfo pos(*pimpl);
378 if (m_proxy->parsePosInfoFromNmeaData(data: buf, size, posInfo: &pos, hasFix: &hasFix)) {
379 // Date may or may not be valid, as some packets do not have date.
380 // If date isn't valid, match is performed on time only.
381 // Hence, make sure that packet blocks are generated with
382 // the sentences containing the full timestamp (e.g., GPRMC) *first* !
383 if (infoTime.isValid()) {
384 if (pos.timestamp().time().isValid()) {
385 const bool newerTime = infoTime < pos.timestamp().time();
386 const bool newerDate = (infoDate.isValid() // if time is valid but one date or both are not,
387 && pos.timestamp().date().isValid()
388 && infoDate < pos.timestamp().date());
389 if (newerTime || newerDate) {
390 // Effectively read data for different update, that is also newer, so copy buf into m_nextLine
391 m_nextLine = QByteArray(buf, size);
392 break;
393 } else {
394 if (infoTime == pos.timestamp().time())
395 // timestamps match -- merge into info
396 mergePositions(dst&: info, src: pos, nmeaSentence: QByteArray(buf, size));
397 // else discard out of order outdated info.
398 }
399 } else {
400 // no timestamp available -- merge into info
401 mergePositions(dst&: info, src: pos, nmeaSentence: QByteArray(buf, size));
402 }
403 } else {
404 // there was no info with valid TS. Overwrite with whatever is parsed.
405#if USE_NMEA_PIMPL
406 pimpl->nmeaSentences.append(QByteArray(buf, size));
407#endif
408 info = pos;
409 }
410
411 if (prevTs.time().isValid()) {
412 timeToNextUpdate = msecsTo(from: prevTs, to: info.timestamp());
413 if (timeToNextUpdate < 0) // Somehow parsing expired packets, reset info
414 info = QGeoPositionInfo(*new QGeoPositionInfoPrivateNmea);
415 }
416 }
417 }
418
419 return timeToNextUpdate;
420}
421
422bool QNmeaSimulatedReader::setFirstDateTime()
423{
424 // find the first update with valid date and time
425 QGeoPositionInfo info(*new QGeoPositionInfoPrivateNmea);
426 bool hasFix = false;
427 processSentence(info, m_nextLine, m_proxy, m_pendingUpdates, hasFix);
428
429 if (info.timestamp().time().isValid()) { // NMEA may have sentences with only time and no date. These would generate invalid positions
430 QPendingGeoPositionInfo pending;
431 pending.info = info;
432 pending.hasFix = hasFix;
433 m_pendingUpdates.enqueue(t: pending);
434 return true;
435 }
436 return false;
437}
438
439void QNmeaSimulatedReader::simulatePendingUpdate()
440{
441 if (m_pendingUpdates.size() > 0) {
442 // will be dequeued in processNextSentence()
443 QPendingGeoPositionInfo &pending = m_pendingUpdates.head();
444 m_proxy->notifyNewUpdate(update: &pending.info, fixStatus: pending.hasFix);
445 }
446
447 processNextSentence();
448}
449
450void QNmeaSimulatedReader::timerEvent(QTimerEvent *event)
451{
452 killTimer(id: event->timerId());
453 m_currTimerId = -1;
454 simulatePendingUpdate();
455}
456
457void QNmeaSimulatedReader::processNextSentence()
458{
459 QGeoPositionInfo info(*new QGeoPositionInfoPrivateNmea);
460 bool hasFix = false;
461
462 int timeToNextUpdate = processSentence(info, m_nextLine, m_proxy, m_pendingUpdates, hasFix);
463 if (timeToNextUpdate < 0)
464 return;
465
466 m_pendingUpdates.dequeue();
467
468 QPendingGeoPositionInfo pending;
469 pending.info = info;
470 pending.hasFix = hasFix;
471 m_pendingUpdates.enqueue(t: pending);
472 m_currTimerId = startTimer(interval: timeToNextUpdate);
473}
474
475
476//============================================================
477
478
479QNmeaPositionInfoSourcePrivate::QNmeaPositionInfoSourcePrivate(QNmeaPositionInfoSource *parent, QNmeaPositionInfoSource::UpdateMode updateMode)
480 : QObject(parent),
481 m_updateMode(updateMode),
482 m_device(0),
483 m_invokedStart(false),
484 m_positionError(QGeoPositionInfoSource::UnknownSourceError),
485 m_userEquivalentRangeError(qQNaN()),
486 m_source(parent),
487 m_nmeaReader(0),
488 m_updateTimer(0),
489 m_requestTimer(0),
490 m_horizontalAccuracy(qQNaN()),
491 m_verticalAccuracy(qQNaN()),
492 m_noUpdateLastInterval(false),
493 m_updateTimeoutSent(false),
494 m_connectedReadyRead(false)
495{
496}
497
498QNmeaPositionInfoSourcePrivate::~QNmeaPositionInfoSourcePrivate()
499{
500 delete m_nmeaReader;
501 delete m_updateTimer;
502}
503
504bool QNmeaPositionInfoSourcePrivate::openSourceDevice()
505{
506 if (!m_device) {
507 qWarning(msg: "QNmeaPositionInfoSource: no QIODevice data source, call setDevice() first");
508 return false;
509 }
510
511 if (!m_device->isOpen() && !m_device->open(mode: QIODevice::ReadOnly)) {
512 qWarning(msg: "QNmeaPositionInfoSource: cannot open QIODevice data source");
513 return false;
514 }
515
516 connect(asender: m_device, SIGNAL(aboutToClose()), SLOT(sourceDataClosed()));
517 connect(asender: m_device, SIGNAL(readChannelFinished()), SLOT(sourceDataClosed()));
518 connect(asender: m_device, SIGNAL(destroyed()), SLOT(sourceDataClosed()));
519
520 return true;
521}
522
523void QNmeaPositionInfoSourcePrivate::sourceDataClosed()
524{
525 if (m_nmeaReader && m_device && m_device->bytesAvailable())
526 m_nmeaReader->readAvailableData();
527}
528
529void QNmeaPositionInfoSourcePrivate::readyRead()
530{
531 if (m_nmeaReader)
532 m_nmeaReader->readAvailableData();
533}
534
535bool QNmeaPositionInfoSourcePrivate::initialize()
536{
537 if (m_nmeaReader)
538 return true;
539
540 if (!openSourceDevice())
541 return false;
542
543 if (m_updateMode == QNmeaPositionInfoSource::RealTimeMode)
544 m_nmeaReader = new QNmeaRealTimeReader(this);
545 else
546 m_nmeaReader = new QNmeaSimulatedReader(this);
547
548 return true;
549}
550
551void QNmeaPositionInfoSourcePrivate::prepareSourceDevice()
552{
553 // some data may already be available
554 if (m_updateMode == QNmeaPositionInfoSource::SimulationMode) {
555 if (m_nmeaReader && m_device->bytesAvailable())
556 m_nmeaReader->readAvailableData();
557 }
558
559 if (!m_connectedReadyRead) {
560 connect(asender: m_device, SIGNAL(readyRead()), SLOT(readyRead()));
561 m_connectedReadyRead = true;
562 }
563}
564
565bool QNmeaPositionInfoSourcePrivate::parsePosInfoFromNmeaData(const char *data, int size,
566 QGeoPositionInfo *posInfo, bool *hasFix)
567{
568 return m_source->parsePosInfoFromNmeaData(data, size, posInfo, hasFix);
569}
570
571void QNmeaPositionInfoSourcePrivate::startUpdates()
572{
573 if (m_invokedStart)
574 return;
575
576 m_invokedStart = true;
577 m_pendingUpdate = QGeoPositionInfo();
578 m_noUpdateLastInterval = false;
579
580 bool initialized = initialize();
581 if (!initialized)
582 return;
583
584 if (m_updateMode == QNmeaPositionInfoSource::RealTimeMode) {
585 // skip over any buffered data - we only want the newest data.
586 // Don't do this in requestUpdate. In that case bufferedData is good to have/use.
587 if (m_device->bytesAvailable()) {
588 if (m_device->isSequential())
589 m_device->readAll();
590 else
591 m_device->seek(pos: m_device->bytesAvailable());
592 }
593 }
594
595 if (m_updateTimer)
596 m_updateTimer->stop();
597
598 if (m_source->updateInterval() > 0) {
599 if (!m_updateTimer)
600 m_updateTimer = new QBasicTimer;
601 m_updateTimer->start(msec: m_source->updateInterval(), obj: this);
602 }
603
604 if (initialized)
605 prepareSourceDevice();
606}
607
608void QNmeaPositionInfoSourcePrivate::stopUpdates()
609{
610 m_invokedStart = false;
611 if (m_updateTimer)
612 m_updateTimer->stop();
613 m_pendingUpdate = QGeoPositionInfo();
614 m_noUpdateLastInterval = false;
615}
616
617void QNmeaPositionInfoSourcePrivate::requestUpdate(int msec)
618{
619 if (m_requestTimer && m_requestTimer->isActive())
620 return;
621
622 if (msec <= 0 || msec < m_source->minimumUpdateInterval()) {
623 emit m_source->updateTimeout();
624 return;
625 }
626
627 if (!m_requestTimer) {
628 m_requestTimer = new QTimer(this);
629 connect(asender: m_requestTimer, SIGNAL(timeout()), SLOT(updateRequestTimeout()));
630 }
631
632 bool initialized = initialize();
633 if (!initialized) {
634 emit m_source->updateTimeout();
635 return;
636 }
637
638 m_requestTimer->start(msec);
639 prepareSourceDevice();
640}
641
642void QNmeaPositionInfoSourcePrivate::updateRequestTimeout()
643{
644 m_requestTimer->stop();
645 emit m_source->updateTimeout();
646}
647
648void QNmeaPositionInfoSourcePrivate::notifyNewUpdate(QGeoPositionInfo *update, bool hasFix)
649{
650 // include <QDebug> before uncommenting
651 //qDebug() << "QNmeaPositionInfoSourcePrivate::notifyNewUpdate()" << update->timestamp() << hasFix << m_invokedStart << (m_requestTimer && m_requestTimer->isActive());
652
653 QDate date = update->timestamp().date();
654 if (date.isValid()) {
655 m_currentDate = date;
656 } else {
657 // some sentence have time but no date
658 QTime time = update->timestamp().time();
659 if (time.isValid() && m_currentDate.isValid())
660 update->setTimestamp(QDateTime(m_currentDate, time, Qt::UTC));
661 }
662
663 // Some attributes are sent in separate NMEA sentences. Save and restore the accuracy
664 // measurements.
665 if (update->hasAttribute(attribute: QGeoPositionInfo::HorizontalAccuracy))
666 m_horizontalAccuracy = update->attribute(attribute: QGeoPositionInfo::HorizontalAccuracy);
667 else if (!qIsNaN(d: m_horizontalAccuracy))
668 update->setAttribute(attribute: QGeoPositionInfo::HorizontalAccuracy, value: m_horizontalAccuracy);
669
670 if (update->hasAttribute(attribute: QGeoPositionInfo::VerticalAccuracy))
671 m_verticalAccuracy = update->attribute(attribute: QGeoPositionInfo::VerticalAccuracy);
672 else if (!qIsNaN(d: m_verticalAccuracy))
673 update->setAttribute(attribute: QGeoPositionInfo::VerticalAccuracy, value: m_verticalAccuracy);
674
675 if (hasFix && update->isValid()) {
676 if (m_requestTimer && m_requestTimer->isActive()) { // User called requestUpdate()
677 m_requestTimer->stop();
678 emitUpdated(update: *update);
679 } else if (m_invokedStart) { // user called startUpdates()
680 if (m_updateTimer && m_updateTimer->isActive()) { // update interval > 0
681 // for periodic updates, only want the most recent update
682 m_pendingUpdate = *update; // Set what to send in timerEvent()
683 if (m_noUpdateLastInterval) {
684 // if the update was invalid when timerEvent was last called, a valid update
685 // should be sent ASAP
686 emitPendingUpdate();
687 m_noUpdateLastInterval = false;
688 }
689 } else { // update interval <= 0
690 emitUpdated(update: *update);
691 }
692 }
693 m_lastUpdate = *update; // Set in any case, if update is valid. Used in lastKnownPosition().
694 }
695}
696
697void QNmeaPositionInfoSourcePrivate::timerEvent(QTimerEvent *)
698{
699 emitPendingUpdate();
700}
701
702void QNmeaPositionInfoSourcePrivate::emitPendingUpdate()
703{
704 if (m_pendingUpdate.isValid()) {
705 m_updateTimeoutSent = false;
706 m_noUpdateLastInterval = false;
707 emitUpdated(update: m_pendingUpdate);
708 m_pendingUpdate = QGeoPositionInfo();
709 } else { // invalid update
710 if (m_noUpdateLastInterval && !m_updateTimeoutSent) {
711 m_updateTimeoutSent = true;
712 m_pendingUpdate = QGeoPositionInfo(); // Invalid already, but clear just in case.
713 emit m_source->updateTimeout();
714 }
715 m_noUpdateLastInterval = true;
716 }
717}
718
719void QNmeaPositionInfoSourcePrivate::emitUpdated(const QGeoPositionInfo &update)
720{
721 // check for duplication already done in QNmeaRealTimeReader::notifyNewUpdate
722 // and QNmeaRealTimeReader::readAvailableData
723 m_lastUpdate = update;
724 emit m_source->positionUpdated(update);
725}
726
727//=========================================================
728
729/*!
730 \class QNmeaPositionInfoSource
731 \inmodule QtPositioning
732 \ingroup QtPositioning-positioning
733 \since 5.2
734
735 \brief The QNmeaPositionInfoSource class provides positional information using a NMEA data source.
736
737 NMEA is a commonly used protocol for the specification of one's global
738 position at a certain point in time. The QNmeaPositionInfoSource class reads NMEA
739 data and uses it to provide positional data in the form of
740 QGeoPositionInfo objects.
741
742 A QNmeaPositionInfoSource instance operates in either \l {RealTimeMode} or
743 \l {SimulationMode}. These modes allow NMEA data to be read from either a
744 live source of positional data, or replayed for simulation purposes from
745 previously recorded NMEA data.
746
747 The source of NMEA data is set with setDevice().
748
749 Use startUpdates() to start receiving regular position updates and stopUpdates() to stop these
750 updates. If you only require updates occasionally, you can call requestUpdate() to request a
751 single update.
752
753 In both cases the position information is received via the positionUpdated() signal and the
754 last known position can be accessed with lastKnownPosition().
755
756 QNmeaPositionInfoSource supports reporting the accuracy of the horizontal and vertical position.
757 To enable position accuracy reporting an estimate of the User Equivalent Range Error associated
758 with the NMEA source must be set with setUserEquivalentRangeError().
759*/
760
761
762/*!
763 \enum QNmeaPositionInfoSource::UpdateMode
764 Defines the available update modes.
765
766 \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).
767 \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.
768*/
769
770
771/*!
772 Constructs a QNmeaPositionInfoSource instance with the given \a parent
773 and \a updateMode.
774*/
775QNmeaPositionInfoSource::QNmeaPositionInfoSource(UpdateMode updateMode, QObject *parent)
776 : QGeoPositionInfoSource(parent),
777 d(new QNmeaPositionInfoSourcePrivate(this, updateMode))
778{
779}
780
781/*!
782 Destroys the position source.
783*/
784QNmeaPositionInfoSource::~QNmeaPositionInfoSource()
785{
786 delete d;
787}
788
789/*!
790 Sets the User Equivalent Range Error (UERE) to \a uere. The UERE is used in calculating an
791 estimate of the accuracy of the position information reported by the position info source. The
792 UERE should be set to a value appropriate for the GPS device which generated the NMEA stream.
793
794 The true UERE value is calculated from multiple error sources including errors introduced by
795 the satellites and signal propogation delays through the atmosphere as well as errors
796 introduced by the receiving GPS equipment. For details on GPS accuracy see
797 \l {http://edu-observatory.org/gps/gps_accuracy.html}.
798
799 A typical value for UERE is approximately 5.1.
800
801 \since 5.3
802
803 \sa userEquivalentRangeError()
804*/
805void QNmeaPositionInfoSource::setUserEquivalentRangeError(double uere)
806{
807 d->m_userEquivalentRangeError = uere;
808}
809
810/*!
811 Returns the current User Equivalent Range Error (UERE). The UERE is used in calculating an
812 estimate of the accuracy of the position information reported by the position info source. The
813 default value is NaN which means no accuracy information will be provided.
814
815 \since 5.3
816
817 \sa setUserEquivalentRangeError()
818*/
819double QNmeaPositionInfoSource::userEquivalentRangeError() const
820{
821 return d->m_userEquivalentRangeError;
822}
823
824/*!
825 Parses an NMEA sentence string into a QGeoPositionInfo.
826
827 The default implementation will parse standard NMEA sentences.
828 This method should be reimplemented in a subclass whenever the need to deal with non-standard
829 NMEA sentences arises.
830
831 The parser reads \a size bytes from \a data and uses that information to setup \a posInfo and
832 \a hasFix. If \a hasFix is set to false then \a posInfo may contain only the time or the date
833 and the time.
834
835 Returns true if the sentence was succsesfully parsed, otherwise returns false and should not
836 modifiy \a posInfo or \a hasFix.
837*/
838bool QNmeaPositionInfoSource::parsePosInfoFromNmeaData(const char *data, int size,
839 QGeoPositionInfo *posInfo, bool *hasFix)
840{
841 return QLocationUtils::getPosInfoFromNmea(data, size, info: posInfo, uere: d->m_userEquivalentRangeError,
842 hasFix);
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 QIODevice::ReadOnly mode.
856
857 The source device can only be set once and must be set before calling
858 startUpdates() or requestUpdate().
859
860 \b {Note:} The \a device must emit QIODevice::readyRead() for the
861 source to be notified when data is available for reading.
862 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(msg: "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(a: msec, b: 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: 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 emit QGeoPositionInfoSource::error(positionError);
959}
960
961QT_END_NAMESPACE
962

source code of qtlocation/src/positioning/qnmeapositioninfosource.cpp