Qt 6.x
The Qt SDK
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;
160 QString m_sourceName;
161};
162
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
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
bool open(OpenMode flags) override
Opens the file using OpenMode mode, returning true if successful; otherwise false.
Definition qfile.cpp:881
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:34
Definition qlist.h:74
Definition qmap.h:186
T value(const Key &key, const T &defaultValue=T()) const
Definition qmap.h:356
\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:90
QObject * parent() const
Returns a pointer to the parent object.
Definition qobject.h:311
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2823
\inmodule QtCore
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.
Definition qset.h:18
bool contains(const T &value) const
Definition qset.h:71
\inmodule QtCore
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.
void clear()
Clears this QSharedPointer object, dropping the reference that it may have had to the pointer.
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
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:5299
bool isEmpty() const
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:1083
QString & remove(qsizetype i, qsizetype len)
Removes n characters from the string, starting at the given position index, and returns a reference t...
Definition qstring.cpp:3435
QString & prepend(QChar c)
Definition qstring.h:411
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:2337
int port(int defaultPort=-1) const
Definition qurl.cpp:2380
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:162
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
GLenum src
GLsizei GLsizei GLchar * source
#define qPrintable(string)
Definition qstring.h:1391
#define QStringLiteral(str)
#define Q_OBJECT
#define slots
#define Q_UNUSED(x)
unsigned int quint32
Definition qtypes.h:45
int qint32
Definition qtypes.h:44
QUrl url("example.com")
[constructor-url-reference]
QTcpSocket * socket
[1]
QNetworkProxy proxy
[0]
NmeaParameters(const QVariantMap &parameters)
\inmodule QtCore \reentrant
Definition qchar.h:17
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent