| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtPositioning module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qgeopositioninfosourcefactory_serialnmea.h" |
| #include <QtPositioning/qnmeapositioninfosource.h> |
| #include <QtSerialPort/qserialport.h> |
| #include <QtSerialPort/qserialportinfo.h> |
| #include <QtCore/qloggingcategory.h> |
| #include <QSet> |
| #include "qiopipe_p.h" |
| #include <QSharedPointer> |
| #include "qnmeasatelliteinfosource_p.h" |
| |
| Q_LOGGING_CATEGORY(lcSerial, "qt.positioning.serialnmea") |
| |
| class IODeviceContainer |
| { |
| public: |
| IODeviceContainer() {} |
| IODeviceContainer(IODeviceContainer const&) = delete; |
| void operator=(IODeviceContainer const&) = delete; |
| |
| QSharedPointer<QIOPipe> serial(const QString &portName) |
| { |
| if (m_serialPorts.contains(portName)) { |
| m_serialPorts[portName].refs++; |
| QIOPipe *endPipe = new QIOPipe(m_serialPorts[portName].proxy); |
| m_serialPorts[portName].proxy->addChildPipe(endPipe); |
| return QSharedPointer<QIOPipe>(endPipe); |
| } |
| IODevice device; |
| QSerialPort *port = new QSerialPort(portName); |
| port->setBaudRate(4800); |
| qCDebug(lcSerial) << "Opening serial port" << portName; |
| if (!port->open(QIODevice::ReadOnly)) { |
| qWarning("serialnmea: Failed to open %s", qPrintable(portName)); |
| delete port; |
| return {}; |
| } |
| qCDebug(lcSerial) << "Opened successfully"; |
| device.device = port; |
| device.refs = 1; |
| device.proxy = new QIOPipe(port, QIOPipe::ProxyPipe); |
| m_serialPorts[portName] = device; |
| QIOPipe *endPipe = new QIOPipe(device.proxy); |
| device.proxy->addChildPipe(endPipe); |
| return QSharedPointer<QIOPipe>(endPipe); |
| } |
| |
| void releaseSerial(const QString &portName, QSharedPointer<QIOPipe> &pipe) { |
| if (!m_serialPorts.contains(portName)) |
| return; |
| |
| pipe.clear(); // make sure to release the pipe returned by getSerial, or else, if there are still refs, data will be leaked through it |
| IODevice &device = m_serialPorts[portName]; |
| if (device.refs > 1) { |
| device.refs--; |
| return; |
| } |
| |
| IODevice taken = m_serialPorts.take(portName); |
| taken.device->deleteLater(); |
| } |
| |
| private: |
| |
| struct IODevice { |
| QIODevice *device = nullptr; |
| QIOPipe *proxy = nullptr; // adding client pipes as children of proxy allows to dynamically add clients to one device. |
| unsigned int refs = 1; |
| }; |
| |
| QMap<QString, IODevice> m_serialPorts; |
| }; |
| |
| Q_GLOBAL_STATIC(IODeviceContainer, deviceContainer) |
| |
| |
| class NmeaSource : public QNmeaPositionInfoSource |
| { |
| public: |
| explicit NmeaSource(QObject *parent, const QVariantMap ¶meters); |
| ~NmeaSource() override; |
| bool isValid() const { return !m_port.isNull(); } |
| |
| private: |
| QSharedPointer<QIOPipe> m_port; |
| QString m_portName; |
| }; |
| |
| NmeaSource::NmeaSource(QObject *parent, const QVariantMap ¶meters) |
| : QNmeaPositionInfoSource(RealTimeMode, parent) |
| { |
| QByteArray requestedPort; |
| if (parameters.contains(QStringLiteral("serialnmea.serial_port"))) |
| requestedPort = parameters.value(QStringLiteral("serialnmea.serial_port")).toString().toLatin1(); |
| else |
| requestedPort = qgetenv("QT_NMEA_SERIAL_PORT"); |
| QString portName; |
| if (requestedPort.isEmpty()) { |
| const QList<QSerialPortInfo> ports = QSerialPortInfo::availablePorts(); |
| qCDebug(lcSerial) << "Found" << ports.count() << "serial ports"; |
| if (ports.isEmpty()) { |
| qWarning("serialnmea: No serial ports found"); |
| return; |
| } |
| |
| // Try to find a well-known device. |
| QSet<int> supportedDevices; |
| supportedDevices << 0x67b; // GlobalSat (BU-353S4 and probably others) |
| supportedDevices << 0xe8d; // Qstarz MTK II |
| for (const QSerialPortInfo& port : ports) { |
| if (port.hasVendorIdentifier() && supportedDevices.contains(port.vendorIdentifier())) { |
| portName = port.portName(); |
| break; |
| } |
| } |
| |
| if (portName.isEmpty()) { |
| qWarning("serialnmea: No known GPS device found. Specify the COM port via QT_NMEA_SERIAL_PORT."); |
| return; |
| } |
| m_portName = portName; |
| } else { |
| m_portName = QString::fromUtf8(requestedPort); |
| } |
| |
| m_port = deviceContainer->serial(m_portName); |
| if (!m_port) |
| return; |
| |
| setDevice(m_port.data()); |
| } |
| |
| NmeaSource::~NmeaSource() |
| { |
| deviceContainer->releaseSerial(m_portName, m_port); |
| } |
| |
| |
| |
| class NmeaSatelliteSource : public QNmeaSatelliteInfoSource |
| { |
| public: |
| NmeaSatelliteSource(QObject *parent, const QVariantMap ¶meters) |
| : QNmeaSatelliteInfoSource(parent) |
| { |
| QByteArray requestedPort; |
| if (parameters.contains(QStringLiteral("serialnmea.serial_port"))) |
| requestedPort = parameters.value(QStringLiteral("serialnmea.serial_port")).toString().toLatin1(); |
| else |
| requestedPort = qgetenv("QT_NMEA_SERIAL_PORT"); |
| QString portName; |
| if (requestedPort.isEmpty()) { |
| const QList<QSerialPortInfo> ports = QSerialPortInfo::availablePorts(); |
| qCDebug(lcSerial) << "Found" << ports.count() << "serial ports"; |
| if (ports.isEmpty()) { |
| qWarning("serialnmea: No serial ports found"); |
| return; |
| } |
| |
| // Try to find a well-known device. |
| QSet<int> supportedDevices; |
| supportedDevices << 0x67b; // GlobalSat (BU-353S4 and probably others) |
| supportedDevices << 0xe8d; // Qstarz MTK II |
| foreach (const QSerialPortInfo& port, ports) { |
| if (port.hasVendorIdentifier() && supportedDevices.contains(port.vendorIdentifier())) { |
| portName = port.portName(); |
| break; |
| } |
| } |
| |
| if (portName.isEmpty()) { |
| qWarning("serialnmea: No known GPS device found. Specify the COM port via QT_NMEA_SERIAL_PORT."); |
| return; |
| } |
| m_portName = portName; |
| } else { |
| m_portName = QString::fromUtf8(requestedPort); |
| } |
| |
| m_port = deviceContainer->serial(m_portName); |
| if (!m_port) |
| return; |
| |
| setDevice(m_port.data()); |
| } |
| |
| ~NmeaSatelliteSource() |
| { |
| deviceContainer->releaseSerial(m_portName, m_port); |
| } |
| |
| bool isValid() const { return !m_port.isNull(); } |
| |
| private: |
| QSharedPointer<QIOPipe> m_port; |
| QString m_portName; |
| }; |
| |
| QGeoPositionInfoSource *QGeoPositionInfoSourceFactorySerialNmea::positionInfoSource(QObject *parent) |
| { |
| return positionInfoSourceWithParameters(parent, QVariantMap()); |
| } |
| |
| QGeoSatelliteInfoSource *QGeoPositionInfoSourceFactorySerialNmea::satelliteInfoSource(QObject *parent) |
| { |
| return satelliteInfoSourceWithParameters(parent, QVariantMap()); |
| } |
| |
| QGeoAreaMonitorSource *QGeoPositionInfoSourceFactorySerialNmea::areaMonitor(QObject *parent) |
| { |
| return areaMonitorWithParameters(parent, QVariantMap()); |
| } |
| |
| QGeoPositionInfoSource *QGeoPositionInfoSourceFactorySerialNmea::positionInfoSourceWithParameters(QObject *parent, const QVariantMap ¶meters) |
| { |
| QScopedPointer<NmeaSource> src(new NmeaSource(parent, parameters)); |
| return src->isValid() ? src.take() : nullptr; |
| } |
| |
| QGeoSatelliteInfoSource *QGeoPositionInfoSourceFactorySerialNmea::satelliteInfoSourceWithParameters(QObject *parent, const QVariantMap ¶meters) |
| { |
| QScopedPointer<NmeaSatelliteSource> src(new NmeaSatelliteSource(parent, parameters)); |
| return src->isValid() ? src.take() : nullptr; |
| } |
| |
| QGeoAreaMonitorSource *QGeoPositionInfoSourceFactorySerialNmea::areaMonitorWithParameters(QObject *parent, const QVariantMap ¶meters) |
| { |
| Q_UNUSED(parent); |
| Q_UNUSED(parameters) |
| return nullptr; |
| } |