1 /****************************************************************************
2 **
3 ** Copyright (C) 2019 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtPositioning module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qnmeasatelliteinfosource_p.h"
41 #include <QtPositioning/private/qgeosatelliteinfo_p.h>
42 #include <QtPositioning/private/qgeosatelliteinfosource_p.h>
43 #include <QtPositioning/private/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 1
57 
58 #if USE_NMEA_PIMPL
59 class QGeoSatelliteInfoPrivateNmea : public QGeoSatelliteInfoPrivate
60 {
61 public:
62     QGeoSatelliteInfoPrivateNmea(const QGeoSatelliteInfoPrivate &other);
63     QGeoSatelliteInfoPrivateNmea(const QGeoSatelliteInfoPrivateNmea &other);
64     virtual ~QGeoSatelliteInfoPrivateNmea();
65     virtual QGeoSatelliteInfoPrivate *clone() const;
66 
67     QList<QByteArray> nmeaSentences;
68 };
69 
QGeoSatelliteInfoPrivateNmea(const QGeoSatelliteInfoPrivate & other)70 QGeoSatelliteInfoPrivateNmea::QGeoSatelliteInfoPrivateNmea(const QGeoSatelliteInfoPrivate &other)
71 :   QGeoSatelliteInfoPrivate(other)
72 {
73 }
74 
QGeoSatelliteInfoPrivateNmea(const QGeoSatelliteInfoPrivateNmea & other)75 QGeoSatelliteInfoPrivateNmea::QGeoSatelliteInfoPrivateNmea(const QGeoSatelliteInfoPrivateNmea &other)
76 :   QGeoSatelliteInfoPrivate(other)
77 {
78     nmeaSentences = other.nmeaSentences;
79 }
80 
~QGeoSatelliteInfoPrivateNmea()81 QGeoSatelliteInfoPrivateNmea::~QGeoSatelliteInfoPrivateNmea() {}
82 
clone() const83 QGeoSatelliteInfoPrivate *QGeoSatelliteInfoPrivateNmea::clone() const
84 {
85     return new QGeoSatelliteInfoPrivateNmea(*this);
86 }
87 #else
88 typedef QGeoSatelliteInfoPrivate QGeoSatelliteInfoPrivateNmea;
89 #endif
90 
91 class QNmeaSatelliteInfoSourcePrivate : public QObject, public QGeoSatelliteInfoSourcePrivate
92 {
93     Q_OBJECT
94 public:
95     QNmeaSatelliteInfoSourcePrivate(QNmeaSatelliteInfoSource *parent);
96     ~QNmeaSatelliteInfoSourcePrivate();
97 
98     void startUpdates();
99     void stopUpdates();
100     void requestUpdate(int msec);
101     void notifyNewUpdate();
102 
103 public slots:
104     void readyRead();
105     void emitPendingUpdate();
106     void sourceDataClosed();
107     void updateRequestTimeout();
108 
109 
110 public:
111     QGeoSatelliteInfoSource *m_source = nullptr;
112     QGeoSatelliteInfoSource::Error m_satelliteError = QGeoSatelliteInfoSource::NoError;
113     QPointer<QIODevice> m_device;
114     struct Update {
115         QList<QGeoSatelliteInfo> m_satellitesInView;
116         QList<QGeoSatelliteInfo> m_satellitesInUse;
117         QList<int> m_inUse; // temp buffer for GSA received before GSV
118         bool m_validInView = false;
119         bool m_validInUse = false;
120         bool m_fresh = false;
121         bool m_updatingGsv = false;
122 #if USE_NMEA_PIMPL
123         QByteArray gsa;
124         QList<QByteArray> gsv;
125 #endif
setSatellitesInViewQNmeaSatelliteInfoSourcePrivate::Update126         void setSatellitesInView(const QList<QGeoSatelliteInfo> &inView)
127         {
128             m_updatingGsv = false;
129             m_satellitesInView = inView;
130             m_validInView = m_fresh = true;
131             if (m_inUse.size()) {
132                 m_satellitesInUse.clear();
133                 m_validInUse = false;
134                 bool corrupt = false;
135                 for (const auto i: m_inUse) {
136                     bool found = false;
137                     for (const auto &s: m_satellitesInView) {
138                         if (s.satelliteIdentifier() == i) {
139                             m_satellitesInUse.append(s);
140                             found = true;
141                         }
142                     }
143                     if (!found) { // received a GSA before a GSV, but it was incorrect or something. Unrelated to this GSV at least.
144                         m_satellitesInUse.clear();
145                         corrupt = true;
146                         break;
147                     }
148                 }
149                 m_validInUse = !corrupt;
150                 m_inUse.clear();
151             }
152         }
153 
154 //        void setSatellitesInUse(const QList<QGeoSatelliteInfo> &inUse)
155 //        {
156 //            m_satellitesInUse = inUse;
157 //            m_validInUse = true;
158 //            m_inUse.clear();
159 //        }
160 
setSatellitesInUseQNmeaSatelliteInfoSourcePrivate::Update161         bool setSatellitesInUse(const QList<int> &inUse)
162         {
163             m_satellitesInUse.clear();
164             m_validInUse = false;
165             m_inUse = inUse;
166             if (m_updatingGsv) {
167                 m_satellitesInUse.clear();
168                 m_validInView =  false;
169                 return false;
170             }
171             for (const auto i: inUse) {
172                 bool found = false;
173                 for (const auto &s: m_satellitesInView) {
174                     if (s.satelliteIdentifier() == i) {
175                         m_satellitesInUse.append(s);
176                         found = true;
177                     }
178                 }
179                 if (!found) {                       // if satellites in use aren't in view, the related GSV is still to be received.
180                     m_inUse = inUse;        // So clear outdated data, buffer the info, and set it later.
181                     m_satellitesInUse.clear();
182                     m_satellitesInView.clear();
183                     m_validInView =  false;
184                     return false;
185                 }
186             }
187             m_validInUse = m_fresh = true;
188             return true;
189         }
190 
consumeQNmeaSatelliteInfoSourcePrivate::Update191         void consume()
192         {
193             m_fresh = false;
194         }
195 
isFreshQNmeaSatelliteInfoSourcePrivate::Update196         bool isFresh()
197         {
198             return m_fresh;
199         }
200 
inUseQNmeaSatelliteInfoSourcePrivate::Update201         QSet<int> inUse() const
202         {
203             QSet<int> res;
204             for (const auto &s: m_satellitesInUse)
205                 res.insert(s.satelliteIdentifier());
206             return res;
207         }
208 
clearQNmeaSatelliteInfoSourcePrivate::Update209         void clear()
210         {
211             m_satellitesInView.clear();
212             m_satellitesInUse.clear();
213             m_validInView = m_validInUse = false;
214         }
215 
isValidQNmeaSatelliteInfoSourcePrivate::Update216         bool isValid()
217         {
218             return m_validInView || m_validInUse; // GSV without GSA is valid. GSA with outdated but still matching GSV also valid.
219         }
220     } m_pendingUpdate, m_lastUpdate;
221     bool m_fresh = false;
222     bool m_invokedStart = false;
223     bool m_noUpdateLastInterval = false;
224     bool m_updateTimeoutSent = false;
225     bool m_connectedReadyRead = false;
226     int m_pushDelay = 20;
227     QBasicTimer *m_updateTimer = nullptr; // the timer used in startUpdates()
228     QTimer *m_requestTimer = nullptr; // the timer used in requestUpdate()
229 
230 protected:
231     void readAvailableData();
232     bool openSourceDevice();
233     bool initialize();
234     void prepareSourceDevice();
235     bool emitUpdated(Update &update);
236     void timerEvent(QTimerEvent *event) override;
237 };
238 
QNmeaSatelliteInfoSourcePrivate(QNmeaSatelliteInfoSource * parent)239 QNmeaSatelliteInfoSourcePrivate::QNmeaSatelliteInfoSourcePrivate(QNmeaSatelliteInfoSource *parent)
240 :   m_source(parent)
241 {
242 }
243 
notifyNewUpdate()244 void QNmeaSatelliteInfoSourcePrivate::notifyNewUpdate()
245 {
246     if (m_pendingUpdate.isValid() && m_pendingUpdate.isFresh()) {
247         if (m_requestTimer && m_requestTimer->isActive()) { // User called requestUpdate()
248             m_requestTimer->stop();
249             emitUpdated(m_pendingUpdate);
250         } else if (m_invokedStart) { // user called startUpdates()
251             if (m_updateTimer && m_updateTimer->isActive()) { // update interval > 0
252                 // for periodic updates, only want the most recent update
253                 if (m_noUpdateLastInterval) {
254                     // if the update was invalid when timerEvent was last called, a valid update
255                     // should be sent ASAP
256                     emitPendingUpdate(); // m_noUpdateLastInterval handled in there.
257                 }
258             } else { // update interval <= 0, send anything new ASAP
259                 m_noUpdateLastInterval = !emitUpdated(m_pendingUpdate);
260             }
261         }
262     }
263 }
264 
~QNmeaSatelliteInfoSourcePrivate()265 QNmeaSatelliteInfoSourcePrivate::~QNmeaSatelliteInfoSourcePrivate()
266 {
267     delete m_updateTimer;
268 }
269 
startUpdates()270 void QNmeaSatelliteInfoSourcePrivate::startUpdates()
271 {
272     if (m_invokedStart)
273         return;
274 
275     m_invokedStart = true;
276     m_pendingUpdate.clear();
277     m_noUpdateLastInterval = false;
278 
279     bool initialized = initialize();
280     if (!initialized)
281         return;
282 
283     // Do not support simulation just yet
284 //    if (m_updateMode == QNmeaPositionInfoSource::RealTimeMode)
285     {
286         // skip over any buffered data - we only want the newest data.
287         // Don't do this in requestUpdate. In that case bufferedData is good to have/use.
288         if (m_device->bytesAvailable()) {
289             if (m_device->isSequential())
290                 m_device->readAll();
291             else
292                 m_device->seek(m_device->bytesAvailable());
293         }
294     }
295 
296     if (m_updateTimer)
297         m_updateTimer->stop();
298 
299     if (m_source->updateInterval() > 0) {
300         if (!m_updateTimer)
301             m_updateTimer = new QBasicTimer;
302         m_updateTimer->start(m_source->updateInterval(), this);
303     }
304 
305     if (initialized)
306         prepareSourceDevice();
307 }
308 
stopUpdates()309 void QNmeaSatelliteInfoSourcePrivate::stopUpdates()
310 {
311     m_invokedStart = false;
312     if (m_updateTimer)
313         m_updateTimer->stop();
314     m_pendingUpdate.clear();
315     m_noUpdateLastInterval = false;
316 }
317 
requestUpdate(int msec)318 void QNmeaSatelliteInfoSourcePrivate::requestUpdate(int msec)
319 {
320     if (m_requestTimer && m_requestTimer->isActive())
321         return;
322 
323     if (msec <= 0 || msec < m_source->minimumUpdateInterval()) {
324         emit m_source->requestTimeout();
325         return;
326     }
327 
328     if (!m_requestTimer) {
329         m_requestTimer = new QTimer(this);
330         connect(m_requestTimer, SIGNAL(timeout()), SLOT(updateRequestTimeout()));
331     }
332 
333     bool initialized = initialize();
334     if (!initialized) {
335         emit m_source->requestTimeout();
336         return;
337     }
338 
339     m_requestTimer->start(msec);
340     prepareSourceDevice();
341 }
342 
readyRead()343 void QNmeaSatelliteInfoSourcePrivate::readyRead()
344 {
345     readAvailableData();
346 }
347 
emitPendingUpdate()348 void QNmeaSatelliteInfoSourcePrivate::emitPendingUpdate()
349 {
350     if (m_pendingUpdate.isValid() && m_pendingUpdate.isFresh()) {
351         m_updateTimeoutSent = false;
352         m_noUpdateLastInterval = false;
353         if (!emitUpdated(m_pendingUpdate))
354             m_noUpdateLastInterval = true;
355 //        m_pendingUpdate.clear(); // Do not clear, it will be incrementally updated
356     } else { // invalid or not fresh update
357         if (m_noUpdateLastInterval && !m_updateTimeoutSent) {
358             m_updateTimeoutSent = true;
359             emit m_source->requestTimeout();
360         }
361         m_noUpdateLastInterval = true;
362     }
363 }
364 
sourceDataClosed()365 void QNmeaSatelliteInfoSourcePrivate::sourceDataClosed()
366 {
367     if (m_device && m_device->bytesAvailable())
368         readAvailableData();
369 }
370 
updateRequestTimeout()371 void QNmeaSatelliteInfoSourcePrivate::updateRequestTimeout()
372 {
373     m_requestTimer->stop();
374     emit m_source->requestTimeout();
375 }
376 
readAvailableData()377 void QNmeaSatelliteInfoSourcePrivate::readAvailableData()
378 {
379     while (m_device->canReadLine()) {
380         char buf[1024];
381         qint64 size = m_device->readLine(buf, sizeof(buf));
382         QList<int> satInUse;
383         const bool satInUseParsed = QLocationUtils::getSatInUseFromNmea(buf, size, satInUse);
384         if (satInUseParsed) {
385             m_pendingUpdate.setSatellitesInUse(satInUse);
386 #if USE_NMEA_PIMPL
387             m_pendingUpdate.gsa = QByteArray(buf, size);
388             if (m_pendingUpdate.m_satellitesInUse.size()) {
389                 for (auto &s: m_pendingUpdate.m_satellitesInUse)
390                     static_cast<QGeoSatelliteInfoPrivateNmea *>(QGeoSatelliteInfoPrivate::get(s))->nmeaSentences.append(m_pendingUpdate.gsa);
391                 for (auto &s: m_pendingUpdate.m_satellitesInView)
392                     static_cast<QGeoSatelliteInfoPrivateNmea *>(QGeoSatelliteInfoPrivate::get(s))->nmeaSentences.append(m_pendingUpdate.gsa);
393             }
394 #endif
395         } else {
396             const QLocationUtils::GSVParseStatus parserStatus = QLocationUtils::getSatInfoFromNmea(buf, size, m_pendingUpdate.m_satellitesInView);
397             if (parserStatus == QLocationUtils::GSVPartiallyParsed) {
398                 m_pendingUpdate.m_updatingGsv = true;
399 #if USE_NMEA_PIMPL
400                 m_pendingUpdate.gsv.append(QByteArray(buf, size));
401 #endif
402             } else if (parserStatus == QLocationUtils::GSVFullyParsed) {
403 #if USE_NMEA_PIMPL
404                 m_pendingUpdate.gsv.append(QByteArray(buf, size));
405                 for (int i = 0; i < m_pendingUpdate.m_satellitesInView.size(); i++) {
406                     const QGeoSatelliteInfo &s = m_pendingUpdate.m_satellitesInView.at(i);
407                     QGeoSatelliteInfoPrivateNmea *pimpl = new QGeoSatelliteInfoPrivateNmea(*QGeoSatelliteInfoPrivate::get(s));
408                     pimpl->nmeaSentences.append(m_pendingUpdate.gsa);
409                     pimpl->nmeaSentences.append(m_pendingUpdate.gsv);
410                     m_pendingUpdate.m_satellitesInView.replace(i, QGeoSatelliteInfo(*pimpl));
411                 }
412                 m_pendingUpdate.gsv.clear();
413 #endif
414                 m_pendingUpdate.setSatellitesInView(m_pendingUpdate.m_satellitesInView);
415             }
416         }
417     }
418     notifyNewUpdate();
419 }
420 
openSourceDevice()421 bool QNmeaSatelliteInfoSourcePrivate::openSourceDevice()
422 {
423     if (!m_device) {
424         qWarning("QNmeaSatelliteInfoSource: no QIODevice data source, call setDevice() first");
425         return false;
426     }
427 
428     if (!m_device->isOpen() && !m_device->open(QIODevice::ReadOnly)) {
429         qWarning("QNmeaSatelliteInfoSource: cannot open QIODevice data source");
430         return false;
431     }
432 
433     connect(m_device, SIGNAL(aboutToClose()), SLOT(sourceDataClosed()));
434     connect(m_device, SIGNAL(readChannelFinished()), SLOT(sourceDataClosed()));
435     connect(m_device, SIGNAL(destroyed()), SLOT(sourceDataClosed()));
436 
437     return true;
438 }
439 
initialize()440 bool QNmeaSatelliteInfoSourcePrivate::initialize()
441 {
442     if (!openSourceDevice())
443         return false;
444 
445     return true;
446 }
447 
prepareSourceDevice()448 void QNmeaSatelliteInfoSourcePrivate::prepareSourceDevice()
449 {
450     if (!m_connectedReadyRead) {
451         connect(m_device, SIGNAL(readyRead()), SLOT(readyRead()));
452         m_connectedReadyRead = true;
453     }
454 }
455 
emitUpdated(QNmeaSatelliteInfoSourcePrivate::Update & update)456 bool QNmeaSatelliteInfoSourcePrivate::emitUpdated(QNmeaSatelliteInfoSourcePrivate::Update &update)
457 {
458     bool emitted = false;
459     if (!update.isFresh())
460         return emitted;
461 
462     update.consume();
463     const bool inUseUpdated = update.m_satellitesInUse != m_lastUpdate.m_satellitesInUse;
464     const bool inViewUpdated = update.m_satellitesInView != m_lastUpdate.m_satellitesInView;
465 
466 
467     m_lastUpdate = update;
468     if (update.m_validInUse && inUseUpdated) {
469         emit m_source->satellitesInUseUpdated(update.m_satellitesInUse);
470         emitted = true;
471     }
472     if (update.m_validInView && inViewUpdated) {
473         emit m_source->satellitesInViewUpdated(update.m_satellitesInView);
474         emitted = true;
475     }
476     return emitted;
477 }
478 
timerEvent(QTimerEvent *)479 void QNmeaSatelliteInfoSourcePrivate::timerEvent(QTimerEvent * /*event*/)
480 {
481     emitPendingUpdate();
482 }
483 
484 
485 // currently supports only realtime
QNmeaSatelliteInfoSource(QObject * parent)486 QNmeaSatelliteInfoSource::QNmeaSatelliteInfoSource(QObject *parent)
487 :   QGeoSatelliteInfoSource(*new QNmeaSatelliteInfoSourcePrivate(this), parent)
488 {
489     d = static_cast<QNmeaSatelliteInfoSourcePrivate *>(QGeoSatelliteInfoSourcePrivate::get(*this));
490 }
491 
~QNmeaSatelliteInfoSource()492 QNmeaSatelliteInfoSource::~QNmeaSatelliteInfoSource()
493 {
494     // d deleted in superclass destructor
495 }
496 
setDevice(QIODevice * device)497 void QNmeaSatelliteInfoSource::setDevice(QIODevice *device)
498 {
499     if (device != d->m_device) {
500         if (!d->m_device)
501             d->m_device = device;
502         else
503             qWarning("QNmeaPositionInfoSource: source device has already been set");
504     }
505 }
506 
device() const507 QIODevice *QNmeaSatelliteInfoSource::device() const
508 {
509     return d->m_device;
510 }
511 
setUpdateInterval(int msec)512 void QNmeaSatelliteInfoSource::setUpdateInterval(int msec)
513 {
514     int interval = msec;
515     if (interval != 0)
516         interval = qMax(msec, minimumUpdateInterval());
517     QGeoSatelliteInfoSource::setUpdateInterval(interval);
518     if (d->m_invokedStart) {
519         d->stopUpdates();
520         d->startUpdates();
521     }
522 }
523 
minimumUpdateInterval() const524 int QNmeaSatelliteInfoSource::minimumUpdateInterval() const
525 {
526     return 2; // Some chips are capable of over 100 updates per seconds.
527 }
528 
error() const529 QGeoSatelliteInfoSource::Error QNmeaSatelliteInfoSource::error() const
530 {
531     return d->m_satelliteError;
532 }
533 
startUpdates()534 void QNmeaSatelliteInfoSource::startUpdates()
535 {
536     d->startUpdates();
537 }
538 
stopUpdates()539 void QNmeaSatelliteInfoSource::stopUpdates()
540 {
541     d->stopUpdates();
542 }
543 
requestUpdate(int msec)544 void QNmeaSatelliteInfoSource::requestUpdate(int msec)
545 {
546     d->requestUpdate(msec == 0 ? 60000 * 5 : msec); // 5min default timeout
547 }
548 
setError(QGeoSatelliteInfoSource::Error satelliteError)549 void QNmeaSatelliteInfoSource::setError(QGeoSatelliteInfoSource::Error satelliteError)
550 {
551     d->m_satelliteError = satelliteError;
552     emit QGeoSatelliteInfoSource::error(satelliteError);
553 }
554 
555 
556 //QT_END_NAMESPACE
557 
558 #include "qnmeasatelliteinfosource.moc"
559