Commit f7aac909 authored by Nate Weibley's avatar Nate Weibley

Switch over to model based storage for the mavlink console

parent 190d80ef
...@@ -12,7 +12,8 @@ ...@@ -12,7 +12,8 @@
#include "UAS.h" #include "UAS.h"
MavlinkConsoleController::MavlinkConsoleController() MavlinkConsoleController::MavlinkConsoleController()
: _cursor_home_pos{-1}, : QStringListModel(),
_cursor_home_pos{-1},
_cursor{0}, _cursor{0},
_vehicle{nullptr} _vehicle{nullptr}
{ {
...@@ -35,7 +36,7 @@ MavlinkConsoleController::sendCommand(QString command) ...@@ -35,7 +36,7 @@ MavlinkConsoleController::sendCommand(QString command)
command.append("\n"); command.append("\n");
_sendSerialData(qPrintable(command)); _sendSerialData(qPrintable(command));
_cursor_home_pos = -1; _cursor_home_pos = -1;
_cursor = _console_text.length(); _cursor = rowCount();
} }
void void
...@@ -49,9 +50,10 @@ MavlinkConsoleController::_setActiveVehicle(Vehicle* vehicle) ...@@ -49,9 +50,10 @@ MavlinkConsoleController::_setActiveVehicle(Vehicle* vehicle)
if (_vehicle) { if (_vehicle) {
_incoming_buffer.clear(); _incoming_buffer.clear();
_console_text.clear(); // Reset the model
emit cursorChanged(0); setStringList(QStringList());
emit textChanged(_console_text); _cursor = 0;
_cursor_home_pos = -1;
_uas_connections << connect(_vehicle, &Vehicle::mavlinkSerialControl, this, &MavlinkConsoleController::_receiveData); _uas_connections << connect(_vehicle, &Vehicle::mavlinkSerialControl, this, &MavlinkConsoleController::_receiveData);
} }
} }
...@@ -64,17 +66,27 @@ MavlinkConsoleController::_receiveData(uint8_t device, uint8_t, uint16_t, uint32 ...@@ -64,17 +66,27 @@ MavlinkConsoleController::_receiveData(uint8_t device, uint8_t, uint16_t, uint32
// Append incoming data and parse for ANSI codes // Append incoming data and parse for ANSI codes
_incoming_buffer.append(data); _incoming_buffer.append(data);
auto old_size = _console_text.size(); while(!_incoming_buffer.isEmpty()) {
_processANSItext(); bool newline = false;
auto new_size = _console_text.size(); int idx = _incoming_buffer.indexOf('\n');
if (idx == -1) {
// Read the whole incoming buffer
idx = _incoming_buffer.size();
} else {
newline = true;
}
// Update QML and cursor QByteArray fragment = _incoming_buffer.mid(0, idx);
if (old_size > new_size) { if (_processANSItext(fragment)) {
// Rewind back so we don't get a warning to stderr writeLine(_cursor, fragment);
emit cursorChanged(new_size); if (newline)
_cursor++;
_incoming_buffer.remove(0, idx + (newline ? 1 : 0));
} else {
// ANSI processing failed, need more data
return;
}
} }
emit textChanged(_console_text);
emit cursorChanged(new_size);
} }
void void
...@@ -109,18 +121,16 @@ MavlinkConsoleController::_sendSerialData(QByteArray data, bool close) ...@@ -109,18 +121,16 @@ MavlinkConsoleController::_sendSerialData(QByteArray data, bool close)
} }
} }
void bool
MavlinkConsoleController::_processANSItext() MavlinkConsoleController::_processANSItext(QByteArray &line)
{ {
int i; // Position into the parsed buffer
// Iterate over the incoming buffer to parse off known ANSI control codes // Iterate over the incoming buffer to parse off known ANSI control codes
for (i = 0; i < _incoming_buffer.size(); i++) { for (int i = 0; i < line.size(); i++) {
if (_incoming_buffer.at(i) == '\x1B') { if (line.at(i) == '\x1B') {
// For ANSI codes we expect at most 4 incoming chars // For ANSI codes we expect at least 3 incoming chars
if (i < _incoming_buffer.size() - 3 && _incoming_buffer.at(i+1) == '[') { if (i < line.size() - 2 && line.at(i+1) == '[') {
// Parse ANSI code // Parse ANSI code
switch(_incoming_buffer.at(i+2)) { switch(line.at(i+2)) {
default: default:
continue; continue;
case 'H': case 'H':
...@@ -134,40 +144,51 @@ MavlinkConsoleController::_processANSItext() ...@@ -134,40 +144,51 @@ MavlinkConsoleController::_processANSItext()
break; break;
case 'K': case 'K':
// Erase the current line to the end // Erase the current line to the end
{ if (_cursor < rowCount()) {
auto next_lf = _console_text.indexOf('\n', _cursor); setData(index(_cursor), "");
if (next_lf > 0) }
_console_text.remove(_cursor, next_lf + 1 - _cursor);
}
break; break;
case '2': case '2':
// Erase everything and rewind to home // Check for sufficient buffer size
if (_incoming_buffer.at(i+3) == 'J' && _cursor_home_pos != -1) { if ( i >= line.size() - 3)
// Keep newlines so textedit doesn't scroll annoyingly return false;
int newlines = _console_text.mid(_cursor_home_pos).count('\n');
_console_text.remove(_cursor_home_pos, _console_text.size()); if (line.at(i+3) == 'J' && _cursor_home_pos != -1) {
_console_text.append(QByteArray(newlines, '\n')); // Erase everything and rewind to home
_cursor = _cursor_home_pos; bool blocked = blockSignals(true);
for (int j = _cursor_home_pos; j < rowCount(); j++)
setData(index(j), "");
blockSignals(blocked);
QVector<int> roles;
roles.reserve(2);
roles.append(Qt::DisplayRole);
roles.append(Qt::EditRole);
emit dataChanged(index(_cursor), index(rowCount()), roles);
} }
// Even if we didn't understand this ANSI code, remove the 4th char // Even if we didn't understand this ANSI code, remove the 4th char
_incoming_buffer.remove(i+3,1); line.remove(i+3,1);
break; break;
} }
// Remove the parsed ANSI code and decrement the bufferpos // Remove the parsed ANSI code and decrement the bufferpos
_incoming_buffer.remove(i, 3); line.remove(i, 3);
i--; i--;
} else { } else {
// We can reasonably expect a control code was fragemented // We can reasonably expect a control code was fragemented
// Stop parsing here and wait for it to come in // Stop parsing here and wait for it to come in
break; return false;
} }
} }
} }
return true;
}
// Insert the new data and increment the write cursor void
_console_text.insert(_cursor, _incoming_buffer.left(i)); MavlinkConsoleController::writeLine(int line, const QByteArray &text)
_cursor += i; {
auto rc = rowCount();
// Remove written data from the incoming buffer if (line >= rc) {
_incoming_buffer.remove(0, i); insertRows(rc, 1 + line - rc);
}
auto idx = index(line);
setData(idx, data(idx, Qt::DisplayRole).toString() + text);
} }
...@@ -14,51 +14,39 @@ ...@@ -14,51 +14,39 @@
#include "FactMetaData.h" #include "FactMetaData.h"
#include <QObject> #include <QObject>
#include <QString> #include <QString>
#include <QThread>
#include <QFileInfoList>
#include <QElapsedTimer>
#include <QDebug>
#include <QGeoCoordinate>
#include <QMetaObject> #include <QMetaObject>
#include <QStringListModel>
// Fordward decls // Fordward decls
class Vehicle; class Vehicle;
/// Controller for MavlinkConsole.qml. /// Controller for MavlinkConsole.qml.
class MavlinkConsoleController : public QObject class MavlinkConsoleController : public QStringListModel
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(int cursor READ cursor NOTIFY cursorChanged)
Q_PROPERTY(QString text READ text NOTIFY textChanged)
public: public:
MavlinkConsoleController(); MavlinkConsoleController();
~MavlinkConsoleController(); ~MavlinkConsoleController();
int cursor() const { return _console_text.size(); }
QString text() const { return _console_text; }
public slots: public slots:
void sendCommand(QString command); void sendCommand(QString command);
signals: signals:
void cursorChanged(int); void cursorChanged(int);
void textChanged(QString text);
private slots: private slots:
void _setActiveVehicle (Vehicle* vehicle); void _setActiveVehicle (Vehicle* vehicle);
void _receiveData(uint8_t device, uint8_t flags, uint16_t timeout, uint32_t baudrate, QByteArray data); void _receiveData(uint8_t device, uint8_t flags, uint16_t timeout, uint32_t baudrate, QByteArray data);
private: private:
void _processANSItext(); bool _processANSItext(QByteArray &line);
void _sendSerialData(QByteArray, bool close = false); void _sendSerialData(QByteArray, bool close = false);
void writeLine(int line, const QByteArray &text);
int _cursor_home_pos; int _cursor_home_pos;
int _cursor; int _cursor;
QByteArray _incoming_buffer; QByteArray _incoming_buffer;
QString _console_text;
Vehicle* _vehicle; Vehicle* _vehicle;
QList<QMetaObject::Connection> _uas_connections; QList<QMetaObject::Connection> _uas_connections;
......
...@@ -21,10 +21,12 @@ import QGroundControl.ScreenTools 1.0 ...@@ -21,10 +21,12 @@ import QGroundControl.ScreenTools 1.0
import QGroundControl.Controllers 1.0 import QGroundControl.Controllers 1.0
AnalyzePage { AnalyzePage {
id: mavlinkConsolePage id: mavlinkConsolePage
pageComponent: pageComponent pageComponent: pageComponent
pageName: qsTr("Mavlink Console") pageName: qsTr("Mavlink Console")
pageDescription: qsTr("Mavlink Console provides a connection to the vehicle's system shell.") pageDescription: qsTr("Mavlink Console provides a connection to the vehicle's system shell.")
property bool loaded: false
Component { Component {
id: pageComponent id: pageComponent
...@@ -34,28 +36,79 @@ AnalyzePage { ...@@ -34,28 +36,79 @@ AnalyzePage {
height: availableHeight height: availableHeight
width: availableWidth width: availableWidth
TextArea { Connections {
id: consoleEditor target: conController
onDataChanged: {
// Keep the view in sync if the button is checked
if (loaded) {
if (followTail.checked) {
listview.positionViewAtEnd();
}
}
}
}
Component {
id: delegateItem
Rectangle {
color: qgcPal.windowShade
height: Math.round(ScreenTools.defaultFontPixelHeight * 0.5 + field.height)
width: listview.width
QGCLabel {
id: field
text: display
width: parent.width
wrapMode: Text.NoWrap
font.family: ScreenTools.fixedFontFamily
anchors.verticalCenter: parent.verticalCenter
}
}
}
QGCListView {
Component.onCompleted: {
loaded = true
}
Layout.fillHeight: true Layout.fillHeight: true
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
font.family: ScreenTools.fixedFontFamily clip: true
font.pointSize: ScreenTools.defaultFontPointSize id: listview
readOnly: true model: conController
delegate: delegateItem
cursorPosition: conController.cursor // Unsync the view if the user interacts
text: conController.text onMovementStarted: {
followTail.checked = false
}
} }
QGCTextField { RowLayout {
id: command anchors.left: parent.left
anchors.left: parent.left anchors.right: parent.right
anchors.right: parent.right QGCTextField {
placeholderText: "Enter Commands here..." id: command
Layout.fillWidth: true
placeholderText: "Enter Commands here..."
onAccepted: {
conController.sendCommand(text)
text = ""
}
}
QGCButton {
id: followTail
text: qsTr("Show Latest")
checkable: true
checked: true
onAccepted: { onCheckedChanged: {
conController.sendCommand(text) if (checked && loaded) {
text = "" listview.positionViewAtEnd();
}
}
} }
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment