Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qgeopositioninfosourcefactory_nmea.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5#include <QtPositioning/QNmeaPositionInfoSource>
6#include <QtPositioning/QNmeaSatelliteInfoSource>
7#include <QtNetwork/QTcpSocket>
8#include <QLoggingCategory>
9#include <QSet>
10#include <QUrl>
11#include <QFile>
12#include <QSharedPointer>
13#include "qiopipe_p.h"
14
15#ifdef QT_NMEA_PLUGIN_HAS_SERIALPORT
16# include <QtSerialPort/QSerialPort>
17# include <QtSerialPort/QSerialPortInfo>
18#endif
19
20
21Q_LOGGING_CATEGORY(lcNmea, "qt.positioning.nmea")
22
24
25static const auto sourceParameterName = QStringLiteral("nmea.source");
26static const auto socketScheme = QStringLiteral("socket:");
27static const auto serialScheme = QStringLiteral("serial:");
28
29static const auto baudRateParameterName = QStringLiteral("nmea.baudrate");
30static constexpr auto defaultBaudRate = 4800;
31
32#ifdef QT_NMEA_PLUGIN_HAS_SERIALPORT
33
34// This class is used only for SerialPort devices, because we can't open the
35// same serial port twice.
36// In case of files and sockets it's easier to explicitly create a QIODevice for
37// each new instance of Nmea*InfoSource.
38// Also QFile can't be directly used with QIOPipe, because QFile is not a
39// sequential device.
40// TcpSocket could be used with QIOPipe, but it complicates error handling
41// dramatically, as we would need to somehow forward socket errors through
42// QIOPipes to the clients.
43class IODeviceContainer
44{
45public:
46 IODeviceContainer() {}
47 IODeviceContainer(IODeviceContainer const&) = delete;
48 void operator=(IODeviceContainer const&) = delete;
49
50 QSharedPointer<QIOPipe> serial(const QString &portName, qint32 baudRate)
51 {
52 if (m_serialPorts.contains(portName)) {
53 m_serialPorts[portName].refs++;
54 QIOPipe *endPipe = new QIOPipe(m_serialPorts[portName].proxy);
55 m_serialPorts[portName].proxy->addChildPipe(endPipe);
56 return QSharedPointer<QIOPipe>(endPipe);
57 }
58 IODevice device;
59 QSerialPort *port = new QSerialPort(portName);
60 port->setBaudRate(baudRate);
61 qCDebug(lcNmea) << "Opening serial port" << portName << "with baudrate" << baudRate;
62 if (!port->open(QIODevice::ReadOnly)) {
63 qWarning("nmea: Failed to open %s", qPrintable(portName));
64 delete port;
65 return {};
66 }
67 qCDebug(lcNmea) << "Opened successfully";
68 device.device = port;
69 device.refs = 1;
71 m_serialPorts[portName] = device;
72 QIOPipe *endPipe = new QIOPipe(device.proxy);
73 device.proxy->addChildPipe(endPipe);
74 return QSharedPointer<QIOPipe>(endPipe);
75 }
76
77 void releaseSerial(const QString &portName, QSharedPointer<QIOPipe> &pipe)
78 {
79 if (!m_serialPorts.contains(portName))
80 return;
81
82 pipe.clear(); // make sure to release the pipe returned by getSerial, or else, if there are still refs, data will be leaked through it
83 IODevice &device = m_serialPorts[portName];
84 if (device.refs > 1) {
85 device.refs--;
86 return;
87 }
88
89 IODevice taken = m_serialPorts.take(portName);
90 taken.device->deleteLater();
91 }
92
93private:
94
95 struct IODevice {
96 QIODevice *device = nullptr;
97 QIOPipe *proxy = nullptr; // adding client pipes as children of proxy
98 // allows to dynamically add clients to one device.
99 unsigned int refs = 1;
100 };
101
102 QMap<QString, IODevice> m_serialPorts;
103};
104
105Q_GLOBAL_STATIC(IODeviceContainer, deviceContainer)
106
107#endif // QT_NMEA_PLUGIN_HAS_SERIALPORT
108
110{
111 explicit NmeaParameters(const QVariantMap &parameters);
112
115};
116
118{
120 bool ok = false;
121 const auto br = parameters.value(baudRateParameterName).toInt(&ok);
122 // According to QSerialPort::setBaudRate() documentation, we can pick any
123 // positive number as a baud rate.
124 if (ok && br > 0)
125 baudRate = br;
126}
127
128// We use a string prefix to distinguish between the different data sources.
129// "socket:" means that we use a socket connection
130// "serial:" means that we use a serial port connection
131// "file:///", "qrc:///" and just plain strings mean that we try to use local
132// file.
133// Note: if we do not specify anything, or specify "serial:" without specifying
134// the port name, then we will try to search for a well-known serial port
135// device.
137{
139public:
140 NmeaSource(QObject *parent, const QVariantMap &parameters);
142 ~NmeaSource() override;
143 bool isValid() const
144 {
145 return !m_dataSource.isNull() || !m_fileSource.isNull() || !m_socket.isNull();
146 }
147
148private slots:
149 void onSocketError(QAbstractSocket::SocketError error);
150
151private:
152 void processParameters(const NmeaParameters &parameters);
153 void addSerialDevice(const QString &requestedPort, quint32 baudRate);
154 void setFileName(const QString &fileName);
155 void connectSocket(const QString &source);
156
157 QSharedPointer<QIOPipe> m_dataSource;
158 QScopedPointer<QFile> m_fileSource;
159 QScopedPointer<QTcpSocket> m_socket;
160 QString m_sourceName;
161};
162
163NmeaSource::NmeaSource(QObject *parent, const QVariantMap &parameters)
164 : QNmeaPositionInfoSource(RealTimeMode, parent)
165{
166 processParameters(NmeaParameters(parameters));
167}
168
170 : QNmeaPositionInfoSource(SimulationMode, parent)
171{
172 setFileName(fileName);
173}
174
176{
177#ifdef QT_NMEA_PLUGIN_HAS_SERIALPORT
178 if (deviceContainer.exists())
179 deviceContainer->releaseSerial(m_sourceName, m_dataSource);
180#endif
181}
182
183void NmeaSource::onSocketError(QAbstractSocket::SocketError error)
184{
185 m_socket->close();
186
187 switch (error) {
190 break;
193 break;
196 break;
197 default:
198 qWarning() << "Connection failed! QAbstractSocket::SocketError" << error;
199 // TODO - introduce new type of error. TransportError?
201 break;
202 }
203}
204
205void NmeaSource::processParameters(const NmeaParameters &parameters)
206{
207 if (parameters.source.startsWith(socketScheme)) {
208 // This is a socket
209 connectSocket(parameters.source);
210 } else {
211 // Last chance - this can be serial device.
212 // Note: File is handled in a separate case.
213 addSerialDevice(parameters.source, parameters.baudRate);
214 }
215}
216
217#ifdef QT_NMEA_PLUGIN_HAS_SERIALPORT
218static QString tryFindSerialDevice(const QString &requestedPort)
219{
220 QString portName;
221 if (requestedPort.isEmpty()) {
222 const QList<QSerialPortInfo> ports = QSerialPortInfo::availablePorts();
223 qCDebug(lcNmea) << "Found" << ports.size() << "serial ports";
224 if (ports.isEmpty()) {
225 qWarning("nmea: No serial ports found");
226 return portName;
227 }
228
229 // Try to find a well-known device.
230 QSet<int> supportedDevices;
231 supportedDevices << 0x67b; // GlobalSat (BU-353S4 and probably others)
232 supportedDevices << 0xe8d; // Qstarz MTK II
233 for (const QSerialPortInfo& port : ports) {
234 if (port.hasVendorIdentifier() && supportedDevices.contains(port.vendorIdentifier())) {
235 portName = port.portName();
236 break;
237 }
238 }
239
240 if (portName.isEmpty()) {
241 qWarning("nmea: No known GPS device found.");
242 }
243 } else {
244 portName = requestedPort;
245 if (portName.startsWith(serialScheme))
246 portName.remove(0, 7);
247 }
248 return portName;
249}
250#endif // QT_NMEA_PLUGIN_HAS_SERIALPORT
251
252void NmeaSource::addSerialDevice(const QString &requestedPort, quint32 baudRate)
253{
254#ifdef QT_NMEA_PLUGIN_HAS_SERIALPORT
255 m_sourceName = tryFindSerialDevice(requestedPort);
256 if (m_sourceName.isEmpty())
257 return;
258
259 m_dataSource = deviceContainer->serial(m_sourceName, baudRate);
260 if (!m_dataSource)
261 return;
262
263 setDevice(m_dataSource.data());
264#else
265 Q_UNUSED(baudRate);
266 // As we are not calling setDevice(), the source will be invalid, so
267 // the factory methods will return nullptr.
268 qWarning() << "Plugin was built without serialport support!"
269 << requestedPort << "cannot be used!";
270#endif
271}
272
273void NmeaSource::setFileName(const QString &fileName)
274{
275 m_sourceName = fileName;
276
277 m_fileSource.reset(new QFile(fileName));
278 qCDebug(lcNmea) << "Opening file" << fileName;
279 if (!m_fileSource->open(QIODevice::ReadOnly)) {
280 qWarning("nmea: failed to open file %s", qPrintable(fileName));
281 m_fileSource.reset();
282 }
283
284 if (!m_fileSource)
285 return;
286
287 qCDebug(lcNmea) << "Opened successfully";
288
289 setDevice(m_fileSource.data());
290}
291
292void NmeaSource::connectSocket(const QString &source)
293{
294 const QUrl url(source);
295 const QString host = url.host();
296 const int port = url.port();
297 if (!host.isEmpty() && (port > 0)) {
298 m_socket.reset(new QTcpSocket);
299 // no need to explicitly connect to connected() signal
300 connect(m_socket.get(), &QTcpSocket::errorOccurred, this, &NmeaSource::onSocketError);
301 m_socket->connectToHost(host, port, QTcpSocket::ReadOnly);
302 m_sourceName = source;
303
304 setDevice(m_socket.data());
305 } else {
306 qWarning("nmea: incorrect socket parameters %s:%d", qPrintable(host), port);
307 }
308}
309
311{
313public:
314 NmeaSatelliteSource(QObject *parent, const QVariantMap &parameters);
315 NmeaSatelliteSource(QObject *parent, const QString &fileName, const QVariantMap &parameters);
317
318 bool isValid() const { return !m_port.isNull() || !m_file.isNull() || !m_socket.isNull(); }
319
320private slots:
321 void onSocketError(QAbstractSocket::SocketError error);
322
323private:
324 void processRealtimeParameters(const NmeaParameters &parameters);
325 void parseSimulationSource(const QString &localFileName);
326
327 QSharedPointer<QIOPipe> m_port;
328 QScopedPointer<QFile> m_file;
329 QScopedPointer<QTcpSocket> m_socket;
330 QString m_sourceName;
331};
332
335{
336 processRealtimeParameters(NmeaParameters(parameters));
337}
338
339// We can use a QNmeaSatelliteInfoSource::SimulationUpdateInterval parameter to
340// set the file read frequency in simulation mode. We use setBackendProperty()
341// for it. The value can't be smaller than minimumUpdateInterval().
342// This check is done on the QNmeaSatelliteInfoSource level
344 const QVariantMap &parameters)
346{
347 bool ok = false;
348 const int interval =
350 if (ok)
352 parseSimulationSource(fileName);
353}
354
356{
357#ifdef QT_NMEA_PLUGIN_HAS_SERIALPORT
358 if (deviceContainer.exists())
359 deviceContainer->releaseSerial(m_sourceName, m_port);
360#endif
361}
362
363void NmeaSatelliteSource::onSocketError(QAbstractSocket::SocketError error)
364{
365 m_socket->close();
366
367 switch (error) {
370 break;
373 break;
376 break;
377 default:
378 qWarning() << "Connection failed! QAbstractSocket::SocketError" << error;
379 // TODO - introduce new type of error. TransportError?
381 break;
382 }
383}
384
385void NmeaSatelliteSource::processRealtimeParameters(const NmeaParameters &parameters)
386{
387 const QString source = parameters.source;
388 if (source.startsWith(socketScheme)) {
389 // This is a socket.
390 const QUrl url(source);
391 const QString host = url.host();
392 const int port = url.port();
393 if (!host.isEmpty() && (port > 0)) {
394 m_socket.reset(new QTcpSocket);
395 // no need to explicitly connect to connected() signal
397 this, &NmeaSatelliteSource::onSocketError);
398 m_socket->connectToHost(host, port, QTcpSocket::ReadOnly);
399 m_sourceName = source;
400
401 setDevice(m_socket.data());
402 } else {
403 qWarning("nmea: incorrect socket parameters %s:%d", qPrintable(host), port);
404 }
405 } else {
406#ifdef QT_NMEA_PLUGIN_HAS_SERIALPORT
407 // Last chance - this can be serial device.
408 m_sourceName = tryFindSerialDevice(source);
409 if (m_sourceName.isEmpty())
410 return;
411
412 m_port = deviceContainer->serial(m_sourceName, parameters.baudRate);
413 if (!m_port)
414 return;
415
416 setDevice(m_port.data());
417#else
418 // As we are not calling setDevice(), the source will be invalid, so
419 // the factory methods will return nullptr.
420 qWarning() << "Plugin was built without serialport support!"
421 << source << "cannot be used!";
422#endif // QT_NMEA_PLUGIN_HAS_SERIALPORT
423 }
424}
425
426void NmeaSatelliteSource::parseSimulationSource(const QString &localFileName)
427{
428 // This is a text file.
429 m_sourceName = localFileName;
430
431 qCDebug(lcNmea) << "Opening file" << localFileName;
432 m_file.reset(new QFile(localFileName));
433 if (!m_file->open(QIODevice::ReadOnly)) {
434 qWarning("nmea: failed to open file %s", qPrintable(localFileName));
435 m_file.reset();
436 return;
437 }
438 qCDebug(lcNmea) << "Opened successfully";
439
440 setDevice(m_file.data());
441}
442
450{
451 if (source.isEmpty())
452 return QString();
453
454 QString localFileName = source;
455
456 if (!QFile::exists(localFileName)) {
457 if (localFileName.startsWith(QStringLiteral("qrc:///")))
458 localFileName.remove(0, 7);
459 else if (localFileName.startsWith(QStringLiteral("file:///")))
460 localFileName.remove(0, 7);
461 else if (localFileName.startsWith(QStringLiteral("qrc:/")))
462 localFileName.remove(0, 5);
463
464 if (!QFile::exists(localFileName) && localFileName.startsWith(QLatin1Char('/')))
465 localFileName.remove(0, 1);
466 }
467 if (!QFile::exists(localFileName))
468 localFileName.prepend(QLatin1Char(':'));
469
470 const bool isLocalFile = QFile::exists(localFileName);
471 return isLocalFile ? localFileName : QString();
472}
473
478static QString extractLocalFileName(const QVariantMap &parameters)
479{
480 QString localFileName = parameters.value(sourceParameterName).toString();
481 return checkSourceIsFile(localFileName);
482}
483
485{
486 std::unique_ptr<NmeaSource> src = nullptr;
487
488 const QString localFileName = extractLocalFileName(parameters);
489 if (localFileName.isEmpty())
490 src = std::make_unique<NmeaSource>(parent, parameters); // use RealTimeMode
491 else
492 src = std::make_unique<NmeaSource>(parent, localFileName); // use SimulationMode
493
494 return (src && src->isValid()) ? src.release() : nullptr;
495}
496
498{
499 std::unique_ptr<NmeaSatelliteSource> src = nullptr;
500
501 const QString localFileName = extractLocalFileName(parameters);
502 if (localFileName.isEmpty()) {
503 // use RealTimeMode
504 src = std::make_unique<NmeaSatelliteSource>(parent, parameters);
505 } else {
506 // use SimulationMode
507 src = std::make_unique<NmeaSatelliteSource>(parent, localFileName, parameters);
508 }
509 return (src && src->isValid()) ? src.release() : nullptr;
510}
511
513{
515 Q_UNUSED(parameters);
516 return nullptr;
517}
518
520
521#include "moc_qgeopositioninfosourcefactory_nmea.cpp"
522#include "qgeopositioninfosourcefactory_nmea.moc"
IOBluetoothDevice * device
NmeaSatelliteSource(QObject *parent, const QVariantMap &parameters)
NmeaSource(QObject *parent, const QVariantMap &parameters)
void errorOccurred(QAbstractSocket::SocketError)
void close() override
Closes the I/O device for the socket and calls disconnectFromHost() to close the socket's connection.
SocketError
This enum describes the socket errors that can occur.
virtual void connectToHost(const QString &hostName, quint16 port, OpenMode mode=ReadWrite, NetworkLayerProtocol protocol=AnyIPProtocol)
Attempts to make a connection to hostName on the given port.
\inmodule QtCore
Definition qfile.h:93
QFILE_MAYBE_NODISCARD bool open(OpenMode flags) override
Opens the file using OpenMode mode, returning true if successful; otherwise false.
Definition qfile.cpp:904
bool exists() const
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qfile.cpp:351
\inmodule QtPositioning
QGeoAreaMonitorSource * areaMonitor(QObject *parent, const QVariantMap &parameters) override
Returns a new QGeoAreaMonitorSource associated with this plugin with parent parent,...
Q_PLUGIN_METADATA(IID "org.qt-project.qt.position.sourcefactory/6.0" FILE "plugin.json") public QGeoSatelliteInfoSource * satelliteInfoSource(QObject *parent, const QVariantMap &parameters) override
Returns a new QGeoSatelliteInfoSource associated with this plugin with parent parent,...
virtual QGeoPositionInfoSource * positionInfoSource(QObject *parent, const QVariantMap &parameters)=0
Returns a new QGeoPositionInfoSource associated with this plugin with parent parent,...
\inmodule QtPositioning
\inmodule QtPositioning
\inmodule QtCore \reentrant
Definition qiodevice.h:34
@ ProxyPipe
Definition qiopipe_p.h:35
T value(const Key &key, const T &defaultValue=T()) const
Definition qmap.h:357
\inmodule QtPositioning
Error error() const override
\reimp
void setError(QGeoPositionInfoSource::Error positionError)
void setDevice(QIODevice *source)
Sets the NMEA data source to device.
UpdateMode
Defines the available update modes.
bool setBackendProperty(const QString &name, const QVariant &value) override
\reimp
static QString SimulationUpdateInterval
\variable QNmeaSatelliteInfoSource::SimulationUpdateInterval
void setDevice(QIODevice *source)
Sets the NMEA data source to device.
Error error() const override
\reimp
void setError(QGeoSatelliteInfoSource::Error satelliteError)
\inmodule QtCore
Definition qobject.h:103
QObject * parent() const
Returns a pointer to the parent object.
Definition qobject.h:346
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
T * get() const noexcept
T * data() const noexcept
Returns the value of the pointer referenced by this object.
bool isNull() const noexcept
Returns true if this object refers to \nullptr.
void reset(T *other=nullptr) noexcept(noexcept(Cleanup::cleanup(std::declval< T * >())))
Deletes the existing object it is pointing to (if any), and sets its pointer to other.
bool isNull() const noexcept
Returns true if this object refers to \nullptr.
T * data() const noexcept
Returns the value of the pointer referenced by this object.
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
bool startsWith(const QString &s, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Returns true if the string starts with s; otherwise returns false.
Definition qstring.cpp:5455
bool isEmpty() const noexcept
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:192
The QTcpSocket class provides a TCP socket.
Definition qtcpsocket.h:18
\inmodule QtCore
Definition qurl.h:94
QString host(ComponentFormattingOptions=FullyDecoded) const
Returns the host of the URL if it is defined; otherwise an empty string is returned.
Definition qurl.cpp:2340
int port(int defaultPort=-1) const
Definition qurl.cpp:2383
int toInt(bool *ok=nullptr) const
Returns the variant as an int if the variant has userType() \l QMetaType::Int, \l QMetaType::Bool,...
QString toString() const
Returns the variant as a QString if the variant has a userType() including, but not limited to:
Combined button and popup list for selecting options.
DBusConnection const char DBusError * error
EGLOutputPortEXT port
const EGLAttrib EGLOutputPortEXT * ports
static QT_BEGIN_NAMESPACE const auto sourceParameterName
static QString checkSourceIsFile(const QString &source)
static const auto socketScheme
static QString extractLocalFileName(const QVariantMap &parameters)
static constexpr auto defaultBaudRate
static const auto baudRateParameterName
static const auto serialScheme
#define Q_GLOBAL_STATIC(TYPE, NAME,...)
#define qWarning
Definition qlogging.h:166
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
GLenum src
GLsizei GLsizei GLchar * source
#define qPrintable(string)
Definition qstring.h:1531
#define QStringLiteral(str)
#define Q_OBJECT
#define slots
#define Q_UNUSED(x)
unsigned int quint32
Definition qtypes.h:50
int qint32
Definition qtypes.h:49
QUrl url("example.com")
[constructor-url-reference]
QTcpSocket * socket
[1]
QNetworkProxy proxy
[0]
NmeaParameters(const QVariantMap &parameters)
\inmodule QtCore \reentrant
Definition qchar.h:18