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 @@
+
+
+
+
\ 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
+ }
}
}
}