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 
54 QT_BEGIN_NAMESPACE
55 
56 #define USE_NMEA_PIMPL 0
57 
58 #if USE_NMEA_PIMPL
59 class QGeoPositionInfoPrivateNmea : public QGeoPositionInfoPrivate
60 {
61 public:
62     virtual ~QGeoPositionInfoPrivateNmea();
63     virtual QGeoPositionInfoPrivate *clone() const;
64 
65     QList<QByteArray> nmeaSentences;
66 };
67 
68 
~QGeoPositionInfoPrivateNmea()69 QGeoPositionInfoPrivateNmea::~QGeoPositionInfoPrivateNmea()
70 {
71 
72 }
73 
clone() const74 QGeoPositionInfoPrivate *QGeoPositionInfoPrivateNmea::clone() const
75 {
76     return new QGeoPositionInfoPrivateNmea(*this);
77 }
78 #else
79 typedef QGeoPositionInfoPrivate QGeoPositionInfoPrivateNmea;
80 #endif
81 
propagateCoordinate(QGeoPositionInfo & dst,const QGeoPositionInfo & src,bool force=true)82 static 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(src.coordinate().latitude())
88             && (!qIsFinite(dst.coordinate().latitude()) || force)) {
89         updated |= (c.latitude() != srcCoordinate.latitude());
90         c.setLatitude(src.coordinate().latitude());
91     }
92     if (qIsFinite(src.coordinate().longitude())
93             && (!qIsFinite(dst.coordinate().longitude()) || force)) {
94         updated |= (c.longitude() != srcCoordinate.longitude());
95         c.setLongitude(src.coordinate().longitude());
96     }
97     if (qIsFinite(src.coordinate().altitude())
98             && (!qIsFinite(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 
propagateDate(QGeoPositionInfo & dst,const QGeoPositionInfo & src)106 static 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 
propagateAttributes(QGeoPositionInfo & dst,const QGeoPositionInfo & src,bool force=true)115 static 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                                                 { 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(a) && (!dst.hasAttribute(a) || force)) {
127             updated |= (dst.attribute(a) != src.attribute(a));
128             dst.setAttribute(a, src.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.
mergePositions(QGeoPositionInfo & dst,const QGeoPositionInfo & src,QByteArray nmeaSentence)137 static 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 
msecsTo(const QDateTime & from,const QDateTime & to)154 static 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 
QNmeaRealTimeReader(QNmeaPositionInfoSourcePrivate * sourcePrivate)165 QNmeaRealTimeReader::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("QT_NMEA_PUSH_DELAY");
174     if (!pushDelay.isEmpty())
175         m_pushDelay = qBound(-1, QString::fromLatin1(pushDelay).toInt(), 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(&m_timer, &QTimer::timeout, [this]() {
183            this->notifyNewUpdate();
184         });
185     }
186 }
187 
readAvailableData()188 void 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(buf, sizeof(buf));
199         const bool oldFix = m_hasFix;
200         bool hasFix;
201         const bool parsed = m_proxy->parsePosInfoFromNmeaData(buf, size, &pos, &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(&m_update, oldFix);
231                         m_lastPushedTS = m_update.timestamp();
232                     }
233                     m_timer.stop();
234                     // next update data
235                     propagateAttributes(pos, m_update, 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(m_update, pos, 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(m_update, pos, 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(pos, 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 
notifyNewUpdate()273 void 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(&m_update, m_hasFix);
281         m_lastPushedTS = m_update.timestamp();
282     }
283     m_timer.stop();
284 }
285 
286 
287 //============================================================
288 
QNmeaSimulatedReader(QNmeaPositionInfoSourcePrivate * sourcePrivate)289 QNmeaSimulatedReader::QNmeaSimulatedReader(QNmeaPositionInfoSourcePrivate *sourcePrivate)
290         : QNmeaReader(sourcePrivate),
291         m_currTimerId(-1),
292         m_hasValidDateTime(false)
293 {
294 }
295 
~QNmeaSimulatedReader()296 QNmeaSimulatedReader::~QNmeaSimulatedReader()
297 {
298     if (m_currTimerId > 0)
299         killTimer(m_currTimerId);
300 }
301 
readAvailableData()302 void 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("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 
processSentence(QGeoPositionInfo & info,QByteArray & m_nextLine,QNmeaPositionInfoSourcePrivate * m_proxy,QQueue<QPendingGeoPositionInfo> & m_pendingUpdates,bool & hasFix)325 static 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(buf, 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(buf, size, &pos, &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(info, pos, QByteArray(buf, size));
397                         // else discard out of order outdated info.
398                     }
399                 } else {
400                     // no timestamp available -- merge into info
401                     mergePositions(info, pos, 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(prevTs, 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 
setFirstDateTime()422 bool 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(pending);
434         return true;
435     }
436     return false;
437 }
438 
simulatePendingUpdate()439 void 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(&pending.info, pending.hasFix);
445     }
446 
447     processNextSentence();
448 }
449 
timerEvent(QTimerEvent * event)450 void QNmeaSimulatedReader::timerEvent(QTimerEvent *event)
451 {
452     killTimer(event->timerId());
453     m_currTimerId = -1;
454     simulatePendingUpdate();
455 }
456 
processNextSentence()457 void 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(pending);
472     m_currTimerId = startTimer(timeToNextUpdate);
473 }
474 
475 
476 //============================================================
477 
478 
QNmeaPositionInfoSourcePrivate(QNmeaPositionInfoSource * parent,QNmeaPositionInfoSource::UpdateMode updateMode)479 QNmeaPositionInfoSourcePrivate::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 
~QNmeaPositionInfoSourcePrivate()498 QNmeaPositionInfoSourcePrivate::~QNmeaPositionInfoSourcePrivate()
499 {
500     delete m_nmeaReader;
501     delete m_updateTimer;
502 }
503 
openSourceDevice()504 bool QNmeaPositionInfoSourcePrivate::openSourceDevice()
505 {
506     if (!m_device) {
507         qWarning("QNmeaPositionInfoSource: no QIODevice data source, call setDevice() first");
508         return false;
509     }
510 
511     if (!m_device->isOpen() && !m_device->open(QIODevice::ReadOnly)) {
512         qWarning("QNmeaPositionInfoSource: cannot open QIODevice data source");
513         return false;
514     }
515 
516     connect(m_device, SIGNAL(aboutToClose()), SLOT(sourceDataClosed()));
517     connect(m_device, SIGNAL(readChannelFinished()), SLOT(sourceDataClosed()));
518     connect(m_device, SIGNAL(destroyed()), SLOT(sourceDataClosed()));
519 
520     return true;
521 }
522 
sourceDataClosed()523 void QNmeaPositionInfoSourcePrivate::sourceDataClosed()
524 {
525     if (m_nmeaReader && m_device && m_device->bytesAvailable())
526         m_nmeaReader->readAvailableData();
527 }
528 
readyRead()529 void QNmeaPositionInfoSourcePrivate::readyRead()
530 {
531     if (m_nmeaReader)
532         m_nmeaReader->readAvailableData();
533 }
534 
initialize()535 bool 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 
prepareSourceDevice()551 void 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(m_device, SIGNAL(readyRead()), SLOT(readyRead()));
561         m_connectedReadyRead = true;
562     }
563 }
564 
parsePosInfoFromNmeaData(const char * data,int size,QGeoPositionInfo * posInfo,bool * hasFix)565 bool QNmeaPositionInfoSourcePrivate::parsePosInfoFromNmeaData(const char *data, int size,
566         QGeoPositionInfo *posInfo, bool *hasFix)
567 {
568     return m_source->parsePosInfoFromNmeaData(data, size, posInfo, hasFix);
569 }
570 
startUpdates()571 void 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(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(m_source->updateInterval(), this);
602     }
603 
604     if (initialized)
605         prepareSourceDevice();
606 }
607 
stopUpdates()608 void 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 
requestUpdate(int msec)617 void 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(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 
updateRequestTimeout()642 void QNmeaPositionInfoSourcePrivate::updateRequestTimeout()
643 {
644     m_requestTimer->stop();
645     emit m_source->updateTimeout();
646 }
647 
notifyNewUpdate(QGeoPositionInfo * update,bool hasFix)648 void 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(QGeoPositionInfo::HorizontalAccuracy))
666         m_horizontalAccuracy = update->attribute(QGeoPositionInfo::HorizontalAccuracy);
667     else if (!qIsNaN(m_horizontalAccuracy))
668         update->setAttribute(QGeoPositionInfo::HorizontalAccuracy, m_horizontalAccuracy);
669 
670     if (update->hasAttribute(QGeoPositionInfo::VerticalAccuracy))
671         m_verticalAccuracy = update->attribute(QGeoPositionInfo::VerticalAccuracy);
672     else if (!qIsNaN(m_verticalAccuracy))
673         update->setAttribute(QGeoPositionInfo::VerticalAccuracy, 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);
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);
691             }
692         }
693         m_lastUpdate = *update; // Set in any case, if update is valid. Used in lastKnownPosition().
694     }
695 }
696 
timerEvent(QTimerEvent *)697 void QNmeaPositionInfoSourcePrivate::timerEvent(QTimerEvent *)
698 {
699     emitPendingUpdate();
700 }
701 
emitPendingUpdate()702 void QNmeaPositionInfoSourcePrivate::emitPendingUpdate()
703 {
704     if (m_pendingUpdate.isValid()) {
705         m_updateTimeoutSent = false;
706         m_noUpdateLastInterval = false;
707         emitUpdated(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 
emitUpdated(const QGeoPositionInfo & update)719 void 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 */
QNmeaPositionInfoSource(UpdateMode updateMode,QObject * parent)775 QNmeaPositionInfoSource::QNmeaPositionInfoSource(UpdateMode updateMode, QObject *parent)
776         : QGeoPositionInfoSource(parent),
777         d(new QNmeaPositionInfoSourcePrivate(this, updateMode))
778 {
779 }
780 
781 /*!
782     Destroys the position source.
783 */
~QNmeaPositionInfoSource()784 QNmeaPositionInfoSource::~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 */
setUserEquivalentRangeError(double uere)805 void 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 */
userEquivalentRangeError() const819 double 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 */
parsePosInfoFromNmeaData(const char * data,int size,QGeoPositionInfo * posInfo,bool * hasFix)838 bool QNmeaPositionInfoSource::parsePosInfoFromNmeaData(const char *data, int size,
839         QGeoPositionInfo *posInfo, bool *hasFix)
840 {
841     return QLocationUtils::getPosInfoFromNmea(data, size, posInfo, d->m_userEquivalentRangeError,
842                                               hasFix);
843 }
844 
845 /*!
846     Returns the update mode.
847 */
updateMode() const848 QNmeaPositionInfoSource::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 */
setDevice(QIODevice * device)865 void 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 */
device() const878 QIODevice *QNmeaPositionInfoSource::device() const
879 {
880     return d->m_device;
881 }
882 
883 /*!
884     \reimp
885 */
setUpdateInterval(int msec)886 void 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 */
startUpdates()901 void QNmeaPositionInfoSource::startUpdates()
902 {
903     d->startUpdates();
904 }
905 
906 /*!
907     \reimp
908 */
stopUpdates()909 void QNmeaPositionInfoSource::stopUpdates()
910 {
911     d->stopUpdates();
912 }
913 
914 /*!
915     \reimp
916 */
requestUpdate(int msec)917 void QNmeaPositionInfoSource::requestUpdate(int msec)
918 {
919     d->requestUpdate(msec == 0 ? 60000 * 5 : msec); // 5min default timeout
920 }
921 
922 /*!
923     \reimp
924 */
lastKnownPosition(bool) const925 QGeoPositionInfo 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 */
supportedPositioningMethods() const934 QGeoPositionInfoSource::PositioningMethods QNmeaPositionInfoSource::supportedPositioningMethods() const
935 {
936     return SatellitePositioningMethods;
937 }
938 
939 /*!
940     \reimp
941 */
minimumUpdateInterval() const942 int QNmeaPositionInfoSource::minimumUpdateInterval() const
943 {
944     return 2; // Some chips are capable of over 100 updates per seconds.
945 }
946 
947 /*!
948     \reimp
949 */
error() const950 QGeoPositionInfoSource::Error QNmeaPositionInfoSource::error() const
951 {
952     return d->m_positionError;
953 }
954 
setError(QGeoPositionInfoSource::Error positionError)955 void QNmeaPositionInfoSource::setError(QGeoPositionInfoSource::Error positionError)
956 {
957     d->m_positionError = positionError;
958     emit QGeoPositionInfoSource::error(positionError);
959 }
960 
961 QT_END_NAMESPACE
962