diff --git a/ChangeLog.md b/ChangeLog.md index d26fcf89766caa1803a412acf3c9554d43a1e919..b138488b07911c19171f478fee40434d6f5a1e6e 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -18,6 +18,7 @@ Note: This file only contains high level features or important fixes. * Bumped settings version (now 8). This will cause all settings to be reset to defaults. * Orbit visuals support changing rotation direction * Added support for the Taisync 2.4GHz ViUlinx digital HD wireless link. +* Added UDP Port option for NMEA GPS Device. ## 3.4 diff --git a/qgcresources.qrc b/qgcresources.qrc index e87e4a066b91395b533b3d9914ee2a1b0f73f449..3a3f6767ed5a67b46a2f735c84e5b921047d4036 100644 --- a/qgcresources.qrc +++ b/qgcresources.qrc @@ -210,6 +210,7 @@ resources/QGCLogoBlack.svg resources/QGCLogoFull.svg resources/QGCLogoWhite.svg + resources/QGCLogoArrow.svg resources/QGroundControlConnect.svg resources/rtl.svg resources/SplashScreen.png diff --git a/qgroundcontrol.pro b/qgroundcontrol.pro index 05b6447f69eb40a1a7dd2d63068ba470483fb72a..a113889635ee9678a48ecb96026627699d23aaf8 100644 --- a/qgroundcontrol.pro +++ b/qgroundcontrol.pro @@ -620,6 +620,7 @@ HEADERS += \ src/comm/QGCMAVLink.h \ src/comm/TCPLink.h \ src/comm/UDPLink.h \ + src/comm/UdpIODevice.h \ src/uas/UAS.h \ src/uas/UASInterface.h \ src/uas/UASMessageHandler.h \ @@ -820,6 +821,7 @@ SOURCES += \ src/comm/QGCMAVLink.cc \ src/comm/TCPLink.cc \ src/comm/UDPLink.cc \ + src/comm/UdpIODevice.cc \ src/main.cc \ src/uas/UAS.cc \ src/uas/UASMessageHandler.cc \ diff --git a/resources/QGCLogoArrow.svg b/resources/QGCLogoArrow.svg new file mode 100644 index 0000000000000000000000000000000000000000..89cc75ff697a298e6f8ba28d60bd462982425cf5 --- /dev/null +++ b/resources/QGCLogoArrow.svg @@ -0,0 +1,129 @@ + + + +image/svg+xml + + + + + + + + + + + + \ No newline at end of file diff --git a/src/FlightMap/FlightMap.qml b/src/FlightMap/FlightMap.qml index 635acab5fe063308633564e922362e8288b412ee..568b20f33bf724ca2b3896e2a1d0ce4d2b513ad0 100644 --- a/src/FlightMap/FlightMap.qml +++ b/src/FlightMap/FlightMap.qml @@ -35,6 +35,7 @@ Map { property string mapName: 'defaultMap' property bool isSatelliteMap: activeMapType.name.indexOf("Satellite") > -1 || activeMapType.name.indexOf("Hybrid") > -1 property var gcsPosition: QGroundControl.qgcPositionManger.gcsPosition + property var gcsHeading: QGroundControl.qgcPositionManger.gcsHeading property bool userPanned: false ///< true: the user has manually panned the map property bool allowGCSLocationCenter: false ///< true: map will center/zoom to gcs location one time property bool allowVehicleLocationCenter: false ///< true: map will center/zoom to vehicle location one time @@ -134,12 +135,18 @@ Map { coordinate: gcsPosition sourceItem: Image { - source: "/res/QGCLogoFull" + id: mapItemImage + source: isNaN(gcsHeading) ? "/res/QGCLogoFull" : "/res/QGCLogoArrow" mipmap: true antialiasing: true fillMode: Image.PreserveAspectFit - height: ScreenTools.defaultFontPixelHeight * 1.75 + height: ScreenTools.defaultFontPixelHeight * (isNaN(gcsHeading) ? 1.75 : 2.5 ) sourceSize.height: height + transform: Rotation { + origin.x: mapItemImage.width / 2 + origin.y: mapItemImage.height / 2 + angle: isNaN(gcsHeading) ? 0 : gcsHeading + } } } } // Map diff --git a/src/PositionManager/PositionManager.cpp b/src/PositionManager/PositionManager.cpp index 09f485142162f1ca634be70045d1fec08b4edda7..5de73d0f6e715a455e19de612c91dfbc486469e6 100644 --- a/src/PositionManager/PositionManager.cpp +++ b/src/PositionManager/PositionManager.cpp @@ -14,6 +14,7 @@ QGCPositionManager::QGCPositionManager(QGCApplication* app, QGCToolbox* toolbox) : QGCTool (app, toolbox) , _updateInterval (0) + , _gcsHeading (NAN) , _currentSource (NULL) , _defaultSource (NULL) , _nmeaSource (NULL) @@ -49,8 +50,19 @@ void QGCPositionManager::setToolbox(QGCToolbox *toolbox) void QGCPositionManager::setNmeaSourceDevice(QIODevice* device) { + // stop and release _nmeaSource if (_nmeaSource) { + _nmeaSource->stopUpdates(); + disconnect(_nmeaSource); + + // if _currentSource is pointing there, point to null + if (_currentSource == _nmeaSource){ + _currentSource = nullptr; + } + delete _nmeaSource; + _nmeaSource = nullptr; + } _nmeaSource = new QNmeaPositionInfoSource(QNmeaPositionInfoSource::RealTimeMode, this); _nmeaSource->setDevice(device); @@ -60,6 +72,7 @@ void QGCPositionManager::setNmeaSourceDevice(QIODevice* device) void QGCPositionManager::_positionUpdated(const QGeoPositionInfo &update) { QGeoCoordinate newGCSPosition = QGeoCoordinate(); + qreal newGCSHeading = update.attribute(QGeoPositionInfo::Direction); if (update.isValid()) { // Note that gcsPosition filters out possible crap values @@ -71,6 +84,10 @@ void QGCPositionManager::_positionUpdated(const QGeoPositionInfo &update) _gcsPosition = newGCSPosition; emit gcsPositionChanged(_gcsPosition); } + if (newGCSHeading != _gcsHeading) { + _gcsHeading = newGCSHeading; + emit gcsHeadingChanged(_gcsHeading); + } emit positionInfoUpdated(update); } diff --git a/src/PositionManager/PositionManager.h b/src/PositionManager/PositionManager.h index 692ae2324e2b71ada38798ca4aaeb0d9717a9778..30c39f363a208bde826bab11895813d48035e8ef 100644 --- a/src/PositionManager/PositionManager.h +++ b/src/PositionManager/PositionManager.h @@ -25,7 +25,8 @@ public: QGCPositionManager(QGCApplication* app, QGCToolbox* toolbox); ~QGCPositionManager(); - Q_PROPERTY(QGeoCoordinate gcsPosition READ gcsPosition NOTIFY gcsPositionChanged) + Q_PROPERTY(QGeoCoordinate gcsPosition READ gcsPosition NOTIFY gcsPositionChanged) + Q_PROPERTY(qreal gcsHeading READ gcsHeading NOTIFY gcsHeadingChanged) enum QGCPositionSource { Simulated, @@ -36,6 +37,8 @@ public: QGeoCoordinate gcsPosition(void) { return _gcsPosition; } + qreal gcsHeading() { return _gcsHeading; } + void setPositionSource(QGCPositionSource source); int updateInterval() const; @@ -50,11 +53,13 @@ private slots: signals: void gcsPositionChanged(QGeoCoordinate gcsPosition); + void gcsHeadingChanged(qreal gcsHeading); void positionInfoUpdated(QGeoPositionInfo update); private: int _updateInterval; QGeoCoordinate _gcsPosition; + qreal _gcsHeading; QGeoPositionInfoSource* _currentSource; QGeoPositionInfoSource* _defaultSource; diff --git a/src/Settings/AutoConnect.SettingsGroup.json b/src/Settings/AutoConnect.SettingsGroup.json index ce5c7ca154454be9dd39e9f8bdc2e3a8d450f260..da283b5c16e3a84c71d7289b9528cb80094bdc08 100644 --- a/src/Settings/AutoConnect.SettingsGroup.json +++ b/src/Settings/AutoConnect.SettingsGroup.json @@ -72,5 +72,11 @@ "shortDescription": "UDP target host port for autoconnect", "type": "uint32", "defaultValue": 14550 +}, +{ + "name": "nmeaUdpPort", + "shortDescription": "Udp port to receive NMEA streams", + "type": "uint32", + "defaultValue": 14401 } ] diff --git a/src/Settings/AutoConnectSettings.cc b/src/Settings/AutoConnectSettings.cc index 23c576acacf4bbd9b1b18db9917144ac10ad66f1..c539a1aed19c2282c7303b9cfb43bb637a101220 100644 --- a/src/Settings/AutoConnectSettings.cc +++ b/src/Settings/AutoConnectSettings.cc @@ -23,6 +23,7 @@ DECLARE_SETTINGSFACT(AutoConnectSettings, autoConnectUDP) DECLARE_SETTINGSFACT(AutoConnectSettings, udpListenPort) DECLARE_SETTINGSFACT(AutoConnectSettings, udpTargetHostIP) DECLARE_SETTINGSFACT(AutoConnectSettings, udpTargetHostPort) +DECLARE_SETTINGSFACT(AutoConnectSettings, nmeaUdpPort) DECLARE_SETTINGSFACT_NO_FUNC(AutoConnectSettings, autoConnectPixhawk) { diff --git a/src/Settings/AutoConnectSettings.h b/src/Settings/AutoConnectSettings.h index 9f96d38ff0fb10db1ac78f5e1370c0832a623f5a..65716520daacfad255baad1e57c09af3c38704fd 100644 --- a/src/Settings/AutoConnectSettings.h +++ b/src/Settings/AutoConnectSettings.h @@ -31,5 +31,5 @@ public: DEFINE_SETTINGFACT(udpListenPort) DEFINE_SETTINGFACT(udpTargetHostIP) DEFINE_SETTINGFACT(udpTargetHostPort) - + DEFINE_SETTINGFACT(nmeaUdpPort) }; diff --git a/src/comm/CMakeLists.txt b/src/comm/CMakeLists.txt index 315774cc9e3c6f45acb7a6019ddf5d0a336707ed..4a5fa752f30a0f68dd47e66d6cf98c94ba46d60c 100644 --- a/src/comm/CMakeLists.txt +++ b/src/comm/CMakeLists.txt @@ -24,6 +24,7 @@ add_library(comm SerialLink.cc TCPLink.cc UDPLink.cc + UdpIODevice.cc ${EXTRA_SRC} diff --git a/src/comm/LinkManager.cc b/src/comm/LinkManager.cc index 036441a4b5514a54a54ed14ae5e766aaa1e7719c..6f511c822cf1bb73dcdfccf2755417f8a6258deb 100644 --- a/src/comm/LinkManager.cc +++ b/src/comm/LinkManager.cc @@ -479,6 +479,27 @@ void LinkManager::_updateAutoConnectLinks(void) createConnectedLink(config); emit linkConfigurationsChanged(); } +#ifndef __mobile__ + // check to see if nmea gps is configured for UDP input, if so, set it up to connect + if (_autoConnectSettings->autoConnectNmeaPort()->cookedValueString() == "UDP Port") { + if (_nmeaSocket.localPort() != _autoConnectSettings->nmeaUdpPort()->rawValue().toUInt() + || _nmeaSocket.state() != UdpIODevice::BoundState) { + qCDebug(LinkManagerLog) << "Changing port for UDP NMEA stream"; + _nmeaSocket.close(); + _nmeaSocket.bind(QHostAddress::AnyIPv4, _autoConnectSettings->nmeaUdpPort()->rawValue().toUInt()); + _toolbox->qgcPositionManager()->setNmeaSourceDevice(&_nmeaSocket); + } + //close serial port + if (_nmeaPort) { + _nmeaPort->close(); + delete _nmeaPort; + _nmeaPort = nullptr; + _nmeaDeviceName = ""; + } + } else { + _nmeaSocket.close(); + } +#endif #ifndef NO_SERIAL_LINK QStringList currentPorts; @@ -513,6 +534,7 @@ void LinkManager::_updateAutoConnectLinks(void) #ifndef NO_SERIAL_LINK #ifndef __mobile__ + // check to see if nmea gps is configured for current Serial port, if so, set it up to connect if (portInfo.systemLocation().trimmed() == _autoConnectSettings->autoConnectNmeaPort()->cookedValueString()) { if (portInfo.systemLocation().trimmed() != _nmeaDeviceName) { _nmeaDeviceName = portInfo.systemLocation().trimmed(); diff --git a/src/comm/LinkManager.h b/src/comm/LinkManager.h index f09360142d62835a478973acdb0c28a1b9fba5f0..8322e1c6f593296ac3e64155226efa060ada8706 100644 --- a/src/comm/LinkManager.h +++ b/src/comm/LinkManager.h @@ -25,6 +25,7 @@ #include "MAVLinkProtocol.h" #if !defined(__mobile__) #include "LogReplayLink.h" +#include "UdpIODevice.h" #endif #include "QmlObjectListModel.h" @@ -241,6 +242,7 @@ private: QString _nmeaDeviceName; QSerialPort* _nmeaPort; uint32_t _nmeaBaud; + UdpIODevice _nmeaSocket; #endif #endif }; diff --git a/src/comm/UdpIODevice.cc b/src/comm/UdpIODevice.cc new file mode 100644 index 0000000000000000000000000000000000000000..7f85dd6017167350a369791e4f2082772e0789ec --- /dev/null +++ b/src/comm/UdpIODevice.cc @@ -0,0 +1,45 @@ +/**************************************************************************** + * + * (c) 2009-2018 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#include "UdpIODevice.h" +#include + +UdpIODevice::UdpIODevice(QObject *parent) : QUdpSocket(parent) +{ + // this might cause data to be available only after a second readyRead() signal + connect(this, &QUdpSocket::readyRead, this, &UdpIODevice::_readAvailableData); +} + +bool UdpIODevice::canReadLine() const +{ + return _buffer.indexOf('\n') > -1; +} + +qint64 UdpIODevice::readLineData(char *data, qint64 maxSize) +{ + int length = _buffer.indexOf('\n') + 1; // add 1 to include the '\n' + if (length == 0) { + return 0; + } + length = std::min(length, static_cast(maxSize)); + // copy lines to output + std::copy(_buffer.data(), _buffer.data() + length, data); + // trim buffer to remove consumed line + _buffer = _buffer.right(_buffer.size() - length); + // return number of bytes read + return length; +} + +void UdpIODevice::_readAvailableData() { + while (hasPendingDatagrams()) { + int previousSize = _buffer.size(); + _buffer.resize(static_cast(_buffer.size() + pendingDatagramSize())); + readDatagram((_buffer.data() + previousSize), pendingDatagramSize()); + } +} diff --git a/src/comm/UdpIODevice.h b/src/comm/UdpIODevice.h new file mode 100644 index 0000000000000000000000000000000000000000..2ccd8a8afb4a0e374e001ef82db06b1211043b2b --- /dev/null +++ b/src/comm/UdpIODevice.h @@ -0,0 +1,35 @@ +/**************************************************************************** + * + * (c) 2009-2018 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ +#pragma once + +#include + +/** + * @brief QUdpSocket implementation of canReadLine() readLineData() in server mode. + * The UdpIODevice class works almost exactly as a QUdpSocket, but + * also implements canReadLine() and readLineData() while in the bound state. + * Regular QUdpSocket only allows to use these QIODevice interfaces when using + * connectToHost(), which means it is working as a client instead of server. + * + **/ + +class UdpIODevice: public QUdpSocket +{ + Q_OBJECT +public: + UdpIODevice(QObject *parent = nullptr); + bool canReadLine() const; + qint64 readLineData(char *data, qint64 maxSize); + +private slots: + void _readAvailableData(); + +private: + QByteArray _buffer; +}; diff --git a/src/ui/preferences/GeneralSettings.qml b/src/ui/preferences/GeneralSettings.qml index a358e29338399b803557f81f4e57dd848b08e036..783ad7293b888ea1bace5489dd2333eecc9fb39d 100644 --- a/src/ui/preferences/GeneralSettings.qml +++ b/src/ui/preferences/GeneralSettings.qml @@ -52,6 +52,9 @@ QGCView { property bool _isTCP: _isGst && _videoSource === QGroundControl.settingsManager.videoSettings.tcpVideoSource property bool _isMPEGTS: _isGst && _videoSource === QGroundControl.settingsManager.videoSettings.mpegtsVideoSource + property string gpsDisabled: "Disabled" + property string gpsUdpPort: "UDP Port" + readonly property real _internalWidthRatio: 0.8 QGCPalette { id: qgcPal } @@ -504,7 +507,6 @@ QGCView { Layout.preferredWidth: _comboFieldWidth model: ListModel { - ListElement { text: "disabled" } } onActivated: { @@ -513,18 +515,26 @@ QGCView { } } Component.onCompleted: { + model.append({text: gpsDisabled}) + model.append({text: gpsUdpPort}) + for (var i in QGroundControl.linkManager.serialPorts) { nmeaPortCombo.model.append({text:QGroundControl.linkManager.serialPorts[i]}) } var index = nmeaPortCombo.find(QGroundControl.settingsManager.autoConnectSettings.autoConnectNmeaPort.valueString); nmeaPortCombo.currentIndex = index; + if (QGroundControl.linkManager.serialPorts.length === 0) { + nmeaPortCombo.model.append({text: "Serial "}) + } } } QGCLabel { + visible: nmeaPortCombo.currentText !== gpsUdpPort && nmeaPortCombo.currentText !== gpsDisabled text: qsTr("NMEA GPS Baudrate") } QGCComboBox { + visible: nmeaPortCombo.currentText !== gpsUdpPort && nmeaPortCombo.currentText !== gpsDisabled id: nmeaBaudCombo Layout.preferredWidth: _comboFieldWidth model: [4800, 9600, 19200, 38400, 57600, 115200] @@ -539,6 +549,16 @@ QGCView { nmeaBaudCombo.currentIndex = index; } } + + QGCLabel { + text: qsTr("NMEA stream UDP port") + visible: nmeaPortCombo.currentText === gpsUdpPort + } + FactTextField { + visible: nmeaPortCombo.currentText === gpsUdpPort + Layout.preferredWidth: _valueFieldWidth + fact: QGroundControl.settingsManager.autoConnectSettings.nmeaUdpPort + } } } }