diff --git a/src/AutoPilotPlugins/Common/ESP8266Component.qml b/src/AutoPilotPlugins/Common/ESP8266Component.qml index 93d402ca9baca81436657bd778d3c3f259e4df57..2a0e87a90dc52ec26a23feb42127e89e1854739f 100644 --- a/src/AutoPilotPlugins/Common/ESP8266Component.qml +++ b/src/AutoPilotPlugins/Common/ESP8266Component.qml @@ -24,6 +24,7 @@ import QtQuick 2.5 import QtQuick.Controls 1.2 import QtQuick.Dialogs 1.2 +import QtQuick.Layouts 1.2 import QGroundControl 1.0 import QGroundControl.FactSystem 1.0 @@ -42,12 +43,70 @@ QGCView { property int _firstColumn: ScreenTools.defaultFontPixelWidth * 20 property int _secondColumn: ScreenTools.defaultFontPixelWidth * 12 readonly property string dialogTitle: "controller WiFi Bridge" + property int stStatus: XMLHttpRequest.UNSENT + property int stErrorCount: 0 + property bool stEnabled: false + property bool stResetCounters: false ESP8266ComponentController { id: controller factPanel: panel } + Timer { + id: timer + } + + function thisThingHasNoNumberLocaleSupport(n) { + return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",").replace(",,", ",") + } + + function updateStatus() { + timer.stop() + var req = new XMLHttpRequest; + var url = "http://192.168.4.1/status.json" + if(stResetCounters) { + url = url + "?r=1" + stResetCounters = false + } + req.open("GET", url); + req.onreadystatechange = function() { + stStatus = req.readyState; + if (stStatus === XMLHttpRequest.DONE) { + var objectArray = JSON.parse(req.responseText); + if (objectArray.errors !== undefined) { + console.log("Error fetching WiFi Bridge Status: " + objectArray.errors[0].message) + stErrorCount = stErrorCount + 1 + if(stErrorCount < 2 && stEnabled) + timer.start() + } else { + if(stEnabled) { + //-- This should work but it doesn't + // var n = 34523453.345 + // n.toLocaleString() + // "34,523,453.345" + vpackets.text = thisThingHasNoNumberLocaleSupport(objectArray["vpackets"]) + vsent.text = thisThingHasNoNumberLocaleSupport(objectArray["vsent"]) + vlost.text = thisThingHasNoNumberLocaleSupport(objectArray["vlost"]) + gpackets.text = thisThingHasNoNumberLocaleSupport(objectArray["gpackets"]) + gsent.text = thisThingHasNoNumberLocaleSupport(objectArray["gsent"]) + glost.text = thisThingHasNoNumberLocaleSupport(objectArray["glost"]) + stErrorCount = 0 + wifiStatus.visible = true + timer.start() + } + } + } + } + req.send() + } + + Component.onCompleted: { + timer.interval = 1000 + timer.repeat = true + timer.triggered.connect(updateStatus) + } + property Fact wifiChannel: controller.getParameterFact(controller.componentID, "WIFI_CHANNEL") property Fact hostPort: controller.getParameterFact(controller.componentID, "WIFI_UDP_HPORT") property Fact clientPort: controller.getParameterFact(controller.componentID, "WIFI_UDP_CPORT") @@ -76,97 +135,228 @@ QGCView { Rectangle { width: parent.width - height: wifiCol.height + (ScreenTools.defaultFontPixelHeight * 2) + height: wifiStatus.visible ? Math.max(wifiCol.height, wifiStatus.height) + (ScreenTools.defaultFontPixelHeight * 2) : wifiCol.height + (ScreenTools.defaultFontPixelHeight * 2) color: palette.windowShade - Column { - id: wifiCol - anchors.margins: ScreenTools.defaultFontPixelHeight / 2 + Row { anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - spacing: ScreenTools.defaultFontPixelHeight / 2 - Row { - spacing: ScreenTools.defaultFontPixelWidth - QGCLabel { - text: "WiFi Channel" - width: _firstColumn - anchors.baseline: channelField.baseline + spacing: ScreenTools.defaultFontPixelWidth + Rectangle { + height: parent.height + width: 1 + color: palette.window + } + Column { + id: wifiCol + anchors.verticalCenter: parent.verticalCenter + spacing: ScreenTools.defaultFontPixelHeight / 2 + Row { + spacing: ScreenTools.defaultFontPixelWidth + QGCLabel { + text: "WiFi Channel" + width: _firstColumn + anchors.baseline: channelField.baseline + } + QGCComboBox { + id: channelField + width: _secondColumn + model: controller.wifiChannels + currentIndex: wifiChannel ? wifiChannel.value - 1 : 0 + onActivated: { + wifiChannel.value = index + 1 + } + } } - QGCComboBox { - id: channelField - width: _secondColumn - model: controller.wifiChannels - currentIndex: wifiChannel ? wifiChannel.value - 1 : 0 - onActivated: { - wifiChannel.value = index + 1 + Row { + spacing: ScreenTools.defaultFontPixelWidth + QGCLabel { + text: "WiFi SSID" + width: _firstColumn + anchors.baseline: ssidField.baseline + } + QGCTextField { + id: ssidField + width: _secondColumn + text: controller.wifiSSID + maximumLength: 16 + onEditingFinished: { + controller.wifiSSID = text + } } } - } - Row { - spacing: ScreenTools.defaultFontPixelWidth - QGCLabel { - text: "WiFi SSID" - width: _firstColumn - anchors.baseline: ssidField.baseline + Row { + spacing: ScreenTools.defaultFontPixelWidth + QGCLabel { + text: "WiFi Password" + width: _firstColumn + anchors.baseline: passwordField.baseline + } + QGCTextField { + id: passwordField + width: _secondColumn + text: controller.wifiPassword + maximumLength: 16 + onEditingFinished: { + controller.wifiPassword = text + } } - QGCTextField { - id: ssidField - width: _secondColumn - text: controller.wifiSSID - maximumLength: 16 - onEditingFinished: { - controller.wifiSSID = text + } + Row { + spacing: ScreenTools.defaultFontPixelWidth + QGCLabel { + text: "UART Baud Rate" + width: _firstColumn + anchors.baseline: baudField.baseline + } + QGCComboBox { + id: baudField + width: _secondColumn + model: controller.baudRates + currentIndex: controller.baudIndex + onActivated: { + controller.baudIndex = index + } } } + Row { + spacing: ScreenTools.defaultFontPixelWidth + QGCLabel { + text: "QGC UDP Port" + width: _firstColumn + anchors.baseline: qgcportField.baseline + } + QGCTextField { + id: qgcportField + width: _secondColumn + text: hostPort ? hostPort.valueString : "" + validator: IntValidator {bottom: 1024; top: 65535;} + inputMethodHints: Qt.ImhDigitsOnly + onEditingFinished: { + hostPort.value = text + } + } + } + } + Rectangle { + height: parent.height + width: 1 + color: palette.text + visible: wifiStatus.visible } - Row { - spacing: ScreenTools.defaultFontPixelWidth + Column { + id: wifiStatus + anchors.margins: ScreenTools.defaultFontPixelHeight / 2 + spacing: ScreenTools.defaultFontPixelHeight / 2 + visible: false QGCLabel { - text: "WiFi Password" - width: _firstColumn - anchors.baseline: passwordField.baseline + text: "Bridge/Vehicle Link" + font.weight: Font.DemiBold + } + Row { + spacing: ScreenTools.defaultFontPixelWidth + QGCLabel { + text: "Messages Received" + width: _firstColumn } - QGCTextField { - id: passwordField - width: _secondColumn - text: controller.wifiPassword - maximumLength: 16 - onEditingFinished: { - controller.wifiPassword = text + QGCLabel { + id: vpackets } } - } - Row { - spacing: ScreenTools.defaultFontPixelWidth + Row { + spacing: ScreenTools.defaultFontPixelWidth + QGCLabel { + text: "Messages Lost" + width: _firstColumn + } + QGCLabel { + id: vlost + } + } + Row { + spacing: ScreenTools.defaultFontPixelWidth + QGCLabel { + text: "Messages Sent" + width: _firstColumn + } + QGCLabel { + id: vsent + } + } + Rectangle { + height: 1 + width: parent.width + color: palette.text + } QGCLabel { - text: "UART Baud Rate" - width: _firstColumn - anchors.baseline: baudField.baseline + text: "Bridge/QGC Link" + font.weight: Font.DemiBold } - QGCComboBox { - id: baudField - width: _secondColumn - model: controller.baudRates - currentIndex: controller.baudIndex - onActivated: { - controller.baudIndex = index + Row { + spacing: ScreenTools.defaultFontPixelWidth + QGCLabel { + text: "Messages Received" + width: _firstColumn + } + QGCLabel { + id: gpackets } } - } - Row { - spacing: ScreenTools.defaultFontPixelWidth + Row { + spacing: ScreenTools.defaultFontPixelWidth + QGCLabel { + text: "Messages Lost" + width: _firstColumn + } + QGCLabel { + id: glost + } + } + Row { + spacing: ScreenTools.defaultFontPixelWidth + QGCLabel { + text: "Messages Sent" + width: _firstColumn + } + QGCLabel { + id: gsent + } + } + Rectangle { + height: 1 + width: parent.width + color: palette.text + } QGCLabel { - text: "QGC UDP Port" - width: _firstColumn - anchors.baseline: qgcportField.baseline + text: "QGC/Bridge Link" + font.weight: Font.DemiBold + } + Row { + spacing: ScreenTools.defaultFontPixelWidth + QGCLabel { + text: "Messages Received" + width: _firstColumn + } + QGCLabel { + text: controller.vehicle ? thisThingHasNoNumberLocaleSupport(controller.vehicle.messagesReceived) : 0 + } + } + Row { + spacing: ScreenTools.defaultFontPixelWidth + QGCLabel { + text: "Messages Lost" + width: _firstColumn + } + QGCLabel { + text: controller.vehicle ? thisThingHasNoNumberLocaleSupport(controller.vehicle.messagesLost) : 0 + } } - QGCTextField { - id: qgcportField - width: _secondColumn - text: hostPort ? hostPort.valueString : "" - validator: IntValidator {bottom: 1024; top: 65535;} - inputMethodHints: Qt.ImhDigitsOnly - onEditingFinished: { - hostPort.value = text + Row { + spacing: ScreenTools.defaultFontPixelWidth + QGCLabel { + text: "Messages Sent" + width: _firstColumn + } + QGCLabel { + text: controller.vehicle ? thisThingHasNoNumberLocaleSupport(controller.vehicle.messagesSent) : 0 } } } @@ -204,6 +394,31 @@ QGCView { } } } + QGCButton { + text: stEnabled ? "Hide Status" : "Show Status" + width: ScreenTools.defaultFontPixelWidth * 16 + onClicked: { + stEnabled = !stEnabled + if(stEnabled) + updateStatus() + else { + wifiStatus.visible = false + timer.stop() + } + } + } + QGCButton { + text: "Reset Counters" + visible: stEnabled + enabled: stEnabled + width: ScreenTools.defaultFontPixelWidth * 16 + onClicked: { + stResetCounters = true; + updateStatus() + if(controller.vehicle) + controller.vehicle.resetCounters() + } + } } } } diff --git a/src/AutoPilotPlugins/Common/ESP8266ComponentController.h b/src/AutoPilotPlugins/Common/ESP8266ComponentController.h index 5a5f5ab2127fc14cbb01bb7a4557d030530436e2..72dff94c4fe6c92efc73d804707160a77522e4c4 100644 --- a/src/AutoPilotPlugins/Common/ESP8266ComponentController.h +++ b/src/AutoPilotPlugins/Common/ESP8266ComponentController.h @@ -58,9 +58,10 @@ public: Q_PROPERTY(QStringList baudRates READ baudRates CONSTANT) Q_PROPERTY(int baudIndex READ baudIndex WRITE setBaudIndex NOTIFY baudIndexChanged) Q_PROPERTY(bool busy READ busy NOTIFY busyChanged) + Q_PROPERTY(Vehicle* vehicle READ vehicle CONSTANT) Q_INVOKABLE void restoreDefaults(); - Q_INVOKABLE void reboot(); + Q_INVOKABLE void reboot (); int componentID () { return MAV_COMP_ID_UDP_BRIDGE; } QString version (); @@ -70,6 +71,7 @@ public: QStringList baudRates () { return _baudRates; } int baudIndex (); bool busy () { return _waitType != WAIT_FOR_NOTHING; } + Vehicle* vehicle () { return _vehicle; } void setWifiSSID (QString id); void setWifiPassword (QString pwd); diff --git a/src/Vehicle/Vehicle.cc b/src/Vehicle/Vehicle.cc index 046f532cd73fe69a5b02d3d8e02b9e656fafd680..20554b30e11ca5a4a815db1540483d29503baebd 100644 --- a/src/Vehicle/Vehicle.cc +++ b/src/Vehicle/Vehicle.cc @@ -111,6 +111,12 @@ Vehicle::Vehicle(LinkInterface* link, , _joystickManager(joystickManager) , _flowImageIndex(0) , _allLinksInactiveSent(false) + , _messagesReceived(0) + , _messagesSent(0) + , _messagesLost(0) + , _messageSeq(0) + , _compID(0) + , _heardFrom(false) { _addLink(link); @@ -215,6 +221,16 @@ Vehicle::~Vehicle() } +void +Vehicle::resetCounters() +{ + _messagesReceived = 0; + _messagesSent = 0; + _messagesLost = 0; + _messageSeq = 0; + _heardFrom = false; +} + void Vehicle::_mavlinkMessageReceived(LinkInterface* link, mavlink_message_t message) { if (message.sysid != _id && message.sysid != 0) { @@ -225,6 +241,32 @@ void Vehicle::_mavlinkMessageReceived(LinkInterface* link, mavlink_message_t mes _addLink(link); } + //-- Check link status + _messagesReceived++; + emit messagesReceivedChanged(); + if(!_heardFrom) { + if(message.msgid == MAVLINK_MSG_ID_HEARTBEAT) { + _heardFrom = true; + _compID = message.compid; + _messageSeq = message.seq + 1; + } + } else { + if(_compID == message.compid) { + uint16_t seq_received = (uint16_t)message.seq; + uint16_t packet_lost_count = 0; + //-- Account for overflow during packet loss + if(seq_received < _messageSeq) { + packet_lost_count = (seq_received + 255) - _messageSeq; + } else { + packet_lost_count = seq_received - _messageSeq; + } + _messageSeq = message.seq + 1; + _messagesLost += packet_lost_count; + if(packet_lost_count) + emit messagesLostChanged(); + } + } + // Give the plugin a change to adjust the message contents _firmwarePlugin->adjustMavlinkMessage(this, &message); @@ -471,6 +513,8 @@ void Vehicle::_sendMessageOnLink(LinkInterface* link, mavlink_message_t message) int len = mavlink_msg_to_send_buffer(buffer, &message); link->writeBytes((const char*)buffer, len); + _messagesSent++; + emit messagesSentChanged(); } void Vehicle::_sendMessage(mavlink_message_t message) @@ -1269,8 +1313,8 @@ void Vehicle::_connectionLostTimeout(void) { if (_connectionLostEnabled && !_connectionLost) { _connectionLost = true; + _heardFrom = false; emit connectionLostChanged(true); - _say(QString("connection lost to vehicle %1").arg(id()), GAudioOutput::AUDIO_SEVERITY_NOTICE); } } @@ -1278,11 +1322,9 @@ void Vehicle::_connectionLostTimeout(void) void Vehicle::_connectionActive(void) { _connectionLostTimer.start(); - if (_connectionLost) { _connectionLost = false; emit connectionLostChanged(false); - _say(QString("connection regained to vehicle %1").arg(id()), GAudioOutput::AUDIO_SEVERITY_NOTICE); } } diff --git a/src/Vehicle/Vehicle.h b/src/Vehicle/Vehicle.h index 08e3df9c2003e2768ccdb130ffcf8220a514ce5e..e04570e4178821bd3fb6acbe4b77e4886886a62a 100644 --- a/src/Vehicle/Vehicle.h +++ b/src/Vehicle/Vehicle.h @@ -119,6 +119,12 @@ public: Q_PROPERTY(bool genericFirmware READ genericFirmware CONSTANT) Q_PROPERTY(bool connectionLost READ connectionLost NOTIFY connectionLostChanged) Q_PROPERTY(bool connectionLostEnabled READ connectionLostEnabled WRITE setConnectionLostEnabled NOTIFY connectionLostEnabledChanged) + Q_PROPERTY(uint messagesReceived READ messagesReceived NOTIFY messagesReceivedChanged) + Q_PROPERTY(uint messagesSent READ messagesSent NOTIFY messagesSentChanged) + Q_PROPERTY(uint messagesLost READ messagesLost NOTIFY messagesLostChanged) + + /// Resets link status counters + Q_INVOKABLE void resetCounters (); /// Returns the number of buttons which are reserved for firmware use in the MANUAL_CONTROL mavlink /// message. For example PX4 Flight Stack reserves the first 8 buttons to simulate rc switches. @@ -275,6 +281,9 @@ public: bool genericFirmware () { return !px4Firmware() && !apmFirmware(); } bool connectionLost () const { return _connectionLost; } bool connectionLostEnabled() const { return _connectionLostEnabled; } + uint messagesReceived () { return _messagesReceived; } + uint messagesSent () { return _messagesSent; } + uint messagesLost () { return _messagesLost; } void setConnectionLostEnabled(bool connectionLostEnabled); @@ -305,6 +314,10 @@ signals: void connectionLostChanged(bool connectionLost); void connectionLostEnabledChanged(bool connectionLostEnabled); + void messagesReceivedChanged (); + void messagesSentChanged (); + void messagesLostChanged (); + /// Used internally to move sendMessage call to main thread void _sendMessageOnThread(mavlink_message_t message); void _sendMessageOnLinkOnThread(LinkInterface* link, mavlink_message_t message); @@ -506,6 +519,13 @@ private: bool _allLinksInactiveSent; ///< true: allLinkInactive signal already sent one time + uint _messagesReceived; + uint _messagesSent; + uint _messagesLost; + uint8_t _messageSeq; + uint8_t _compID; + bool _heardFrom; + // Settings keys static const char* _settingsGroup; static const char* _joystickModeSettingsKey;