QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
MAVLinkConsoleController.cc
Go to the documentation of this file.
2
3#include <QtGui/QClipboard>
4#include <QtGui/QGuiApplication>
5
6#include "MAVLinkLib.h"
7#include "MAVLinkProtocol.h"
10#include "QGCPalette.h"
11#include "Vehicle.h"
12#include "VehicleLinkManager.h"
13
14QGC_LOGGING_CATEGORY(MAVLinkConsoleControllerLog, "AnalyzeView.MAVLinkConsoleController")
15
17 : QStringListModel(parent)
18 , _palette(new QGCPalette(this))
19{
20 qCDebug(MAVLinkConsoleControllerLog) << this;
21
23 &MAVLinkConsoleController::_setActiveVehicle);
24
25 _setActiveVehicle(MultiVehicleManager::instance()->activeVehicle());
26}
27
29{
30 if (_vehicle) {
31 QByteArray msg;
32 _sendSerialData(msg, true);
33 }
34
35 qCDebug(MAVLinkConsoleControllerLog) << this;
36}
37
38void MAVLinkConsoleController::sendCommand(const QString& command)
39{
40 QString output = command;
41
42 // there might be multiple commands, add them separately to the history
43 const QStringList lines = output.split('\n');
44 for (const QString& line : lines) {
45 if (!line.isEmpty()) {
46 _history.append(line);
47 }
48 }
49
50 (void) output.append("\n");
51 _sendSerialData(qPrintable(output));
52 _cursorHomePos = -1;
53}
54
55QString MAVLinkConsoleController::handleClipboard(const QString& command_pre)
56{
57 QString clipboardData = command_pre + QGuiApplication::clipboard()->text();
58
59 const int lastLinePos = clipboardData.lastIndexOf('\n');
60 if (lastLinePos != -1) {
61 const QString commands = clipboardData.mid(0, lastLinePos);
62 sendCommand(commands);
63 clipboardData = clipboardData.mid(lastLinePos + 1);
64 }
65
66 return clipboardData;
67}
68
69void MAVLinkConsoleController::_setActiveVehicle(Vehicle* vehicle)
70{
71 for (QMetaObject::Connection& con : _connections) {
72 (void) disconnect(con);
73 }
74 _connections.clear();
75
76 _vehicle = vehicle;
77 if (_vehicle) {
78 _incomingBuffer.clear();
79 // Reset the model
80 setStringList(QStringList());
81 _cursorY = 0;
82 _cursorX = 0;
83 _cursorHomePos = -1;
84 _connections << connect(_vehicle, &Vehicle::mavlinkSerialControl, this,
85 &MAVLinkConsoleController::_receiveData);
86 }
87}
88
89void MAVLinkConsoleController::_receiveData(uint8_t device, uint8_t flags, uint16_t timeout, uint32_t baudrate,
90 const QByteArray& data)
91{
92 Q_UNUSED(flags);
93 Q_UNUSED(timeout);
94 Q_UNUSED(baudrate);
95
96 if (device != SERIAL_CONTROL_DEV_SHELL) {
97 return;
98 }
99
100 // Append incoming data and parse for ANSI codes
101 (void) _incomingBuffer.append(data);
102
103 while (!_incomingBuffer.isEmpty()) {
104 bool newline = false;
105
106 int idx = _incomingBuffer.indexOf('\n');
107 if (idx == -1) {
108 // Read the whole incoming buffer
109 idx = _incomingBuffer.size();
110 } else {
111 newline = true;
112 }
113
114 QByteArray fragment = _incomingBuffer.mid(0, idx);
115 if (!_processANSItext(fragment)) {
116 // ANSI processing failed, need more data
117 return;
118 }
119
120 _writeLine(_cursorY, fragment);
121 if (newline) {
122 _cursorY++;
123 _cursorX = 0;
124 // ensure line exists
125 const int rc = rowCount();
126 if (_cursorY >= rc) {
127 (void) insertRows(rc, 1 + _cursorY - rc);
128 }
129 }
130
131 (void) _incomingBuffer.remove(0, idx + (newline ? 1 : 0));
132 }
133}
134
135void MAVLinkConsoleController::_sendSerialData(const QByteArray& data, bool close)
136{
137 if (!_vehicle) {
138 qCWarning(MAVLinkConsoleControllerLog) << "_sendSerialData called with no active vehicle";
139 return;
140 }
141
142 SharedLinkInterfacePtr sharedLink = _vehicle->vehicleLinkManager()->primaryLink().lock();
143 if (!sharedLink) {
144 return;
145 }
146
147 // Send maximum sized chunks until the complete buffer is transmitted
148 QByteArray output(data);
149 while (output.size()) {
150 QByteArray chunk(output.left(MAVLINK_MSG_SERIAL_CONTROL_FIELD_DATA_LEN));
151 const int dataSize = chunk.size();
152
153 // Ensure the buffer is large enough, as the MAVLink parser expects MAVLINK_MSG_SERIAL_CONTROL_FIELD_DATA_LEN
154 // bytes
155 (void) chunk.append(MAVLINK_MSG_SERIAL_CONTROL_FIELD_DATA_LEN - chunk.size(), '\0');
156
157 const uint8_t flags =
158 close ? 0 : SERIAL_CONTROL_FLAG_EXCLUSIVE | SERIAL_CONTROL_FLAG_RESPOND | SERIAL_CONTROL_FLAG_MULTI;
159
161 (void) mavlink_msg_serial_control_pack_chan(
162 MAVLinkProtocol::instance()->getSystemId(), MAVLinkProtocol::getComponentId(), sharedLink->mavlinkChannel(),
163 &msg, SERIAL_CONTROL_DEV_SHELL, flags, 0, 0, dataSize, reinterpret_cast<uint8_t*>(chunk.data()),
164 _vehicle->id(), _vehicle->defaultComponentId());
165
166 (void) _vehicle->sendMessageOnLinkThreadSafe(sharedLink.get(), msg);
167 (void) output.remove(0, chunk.size());
168 }
169}
170
171bool MAVLinkConsoleController::_processANSItext(QByteArray& line)
172{
173 // Iterate over the incoming buffer to parse off known ANSI control codes
174 for (int i = 0; i < line.size(); i++) {
175 if (line[i] != '\x1B') {
176 continue;
177 }
178
179 // For ANSI codes we expect at least 3 incoming chars
180 if ((i >= (line.size() - 2)) || (line[i + 1] != '[')) {
181 // We can reasonably expect a control code was fragemented
182 // Stop parsing here and wait for it to come in
183 return false;
184 }
185
186 switch (line[i + 2]) {
187 case 'H':
188 if (_cursorHomePos == -1) {
189 // Assign new home position if home is unset
190 _cursorHomePos = _cursorY;
191 } else {
192 // Rewind write cursor position to home
193 _cursorY = _cursorHomePos;
194 _cursorX = 0;
195 }
196 break;
197 case 'K':
198 // Erase the current line to the end
199 if (_cursorY < rowCount()) {
200 const QModelIndex idx = index(_cursorY);
201 QString updated = data(idx, Qt::DisplayRole).toString();
202 const int eraseIdx = _cursorX + i;
203 if (eraseIdx < updated.length()) {
204 (void) setData(idx, updated.remove(eraseIdx, updated.length()));
205 }
206 }
207 break;
208 case '2':
209 // Check for sufficient buffer size
210 if (i >= (line.size() - 3)) {
211 return false;
212 }
213
214 if ((line[i + 3] == 'J') && (_cursorHomePos != -1)) {
215 // Erase everything and rewind to home
216 const bool blocked = blockSignals(true);
217 for (int j = _cursorHomePos; j < rowCount(); j++) {
218 (void) setData(index(j), "");
219 }
220 (void) blockSignals(blocked);
221
222 const QVector<int> roles({Qt::DisplayRole, Qt::EditRole});
223 emit dataChanged(index(_cursorY), index(rowCount()), roles);
224 }
225
226 // Even if we didn't understand this ANSI code, remove the 4th char
227 (void) line.remove(i + 3, 1);
228 break;
229 default:
230 continue;
231 }
232
233 // Remove the parsed ANSI code and decrement the bufferpos
234 (void) line.remove(i, 3);
235 i--;
236 }
237
238 return true;
239}
240
241QString MAVLinkConsoleController::_transformLineForRichText(const QString& line) const
242{
243 QString ret = line.toHtmlEscaped().replace(" ", "&nbsp;").replace("\t", "&nbsp;&nbsp;&nbsp;&nbsp;");
244
245 if (ret.startsWith("WARN", Qt::CaseSensitive)) {
246 (void) ret.replace(0, 4, "<font color=\"" + _palette->colorOrange().name() + "\">WARN</font>");
247 } else if (ret.startsWith("ERROR", Qt::CaseSensitive)) {
248 (void) ret.replace(0, 5, "<font color=\"" + _palette->colorRed().name() + "\">ERROR</font>");
249 }
250
251 return ret;
252}
253
254QString MAVLinkConsoleController::_getText() const
255{
256 QString ret;
257 if (rowCount() > 0) {
258 ret = _transformLineForRichText(data(index(0), Qt::DisplayRole).toString());
259 }
260
261 for (int i = 1; i < rowCount(); ++i) {
262 ret += ("<br>" + _transformLineForRichText(data(index(i), Qt::DisplayRole).toString()));
263 }
264
265 return ret;
266}
267
268void MAVLinkConsoleController::_writeLine(int line, const QByteArray& text)
269{
270 const int rc = rowCount();
271 if (line >= rc) {
272 (void) insertRows(rc, 1 + line - rc);
273 }
274
275 if (rowCount() > kMaxNumLines) {
276 const int count = rowCount() - kMaxNumLines;
277 (void) removeRows(0, count);
278 line -= count;
279 _cursorY -= count;
280 _cursorHomePos -= count;
281 if (_cursorHomePos < 0) {
282 _cursorHomePos = -1;
283 }
284 }
285
286 const QModelIndex idx = index(line);
287 QString updated = data(idx, Qt::DisplayRole).toString();
288 (void) updated.replace(_cursorX, text.size(), text);
289 (void) setData(idx, updated);
290 _cursorX += text.size();
291}
292
293void MAVLinkConsoleController::CommandHistory::append(const QString& command)
294{
295 if (!command.isEmpty()) {
296 // do not append duplicates
297 if ((_history.isEmpty()) || (_history.last() != command)) {
298 if (_history.length() >= CommandHistory::kMaxHistoryLength) {
299 _history.removeFirst();
300 }
301 _history.append(command);
302 }
303 }
304
305 _index = _history.length();
306}
307
308QString MAVLinkConsoleController::CommandHistory::up(const QString& current)
309{
310 if (_index <= 0) {
311 return current;
312 }
313
314 --_index;
315 if (_index < _history.length()) {
316 return _history[_index];
317 }
318
319 return QStringLiteral("");
320}
321
322QString MAVLinkConsoleController::CommandHistory::down(const QString& current)
323{
324 if (_index >= _history.length()) {
325 return current;
326 }
327
328 ++_index;
329 if (_index < _history.length()) {
330 return _history[_index];
331 }
332
333 return QStringLiteral("");
334}
std::shared_ptr< LinkInterface > SharedLinkInterfacePtr
struct __mavlink_message mavlink_message_t
#define QGC_LOGGING_CATEGORY(name, categoryStr)
Q_INVOKABLE void sendCommand(const QString &command)
Q_INVOKABLE QString handleClipboard(const QString &command_pre)
static int getComponentId()
static MAVLinkProtocol * instance()
int getSystemId() const
static MultiVehicleManager * instance()
void activeVehicleChanged(Vehicle *activeVehicle)
QGCPalette is used in QML ui to expose color properties for the QGC palette.
Definition QGCPalette.h:83
WeakLinkInterfacePtr primaryLink() const
VehicleLinkManager * vehicleLinkManager()
Definition Vehicle.h:597
int id() const
Definition Vehicle.h:443
bool sendMessageOnLinkThreadSafe(LinkInterface *link, mavlink_message_t message)
Definition Vehicle.cc:1401
int defaultComponentId() const
Definition Vehicle.h:700
void mavlinkSerialControl(uint8_t device, uint8_t flags, uint16_t timeout, uint32_t baudrate, QByteArray data)
bool close(int deviceId)
QByteArray line(std::string_view command)
command as one CR/LF-terminated line.
GPSCommandSequence::Command command(QByteArrayView body, GPSTextMatcher reply, std::chrono::milliseconds timeout)
body labelled by its text, framed as a sentence, and completed by reply within timeout.