QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
MAVLinkConsoleController.cc
Go to the documentation of this file.
2#include "MAVLinkProtocol.h"
5#include "QGCPalette.h"
6#include "Vehicle.h"
7
8#include <QtGui/QGuiApplication>
9#include <QtGui/QClipboard>
10
11QGC_LOGGING_CATEGORY(MAVLinkConsoleControllerLog, "AnalyzeView.MAVLinkConsoleController")
12
14 : QStringListModel(parent)
15 , _palette(new QGCPalette(this))
16{
17 qCDebug(MAVLinkConsoleControllerLog) << this;
18
19 (void) connect(MultiVehicleManager::instance(), &MultiVehicleManager::activeVehicleChanged, this, &MAVLinkConsoleController::_setActiveVehicle);
20
21 _setActiveVehicle(MultiVehicleManager::instance()->activeVehicle());
22}
23
25{
26 if (_vehicle) {
27 QByteArray msg;
28 _sendSerialData(msg, true);
29 }
30
31 qCDebug(MAVLinkConsoleControllerLog) << this;
32}
33
34void MAVLinkConsoleController::sendCommand(const QString &command)
35{
36 QString output = command;
37
38 // there might be multiple commands, add them separately to the history
39 const QStringList lines = output.split('\n');
40 for (const QString &line : lines) {
41 if (!line.isEmpty()) {
42 _history.append(line);
43 }
44 }
45
46 (void) output.append("\n");
47 _sendSerialData(qPrintable(output));
48 _cursorHomePos = -1;
49}
50
51QString MAVLinkConsoleController::handleClipboard(const QString &command_pre)
52{
53 QString clipboardData = command_pre + QGuiApplication::clipboard()->text();
54
55 const int lastLinePos = clipboardData.lastIndexOf('\n');
56 if (lastLinePos != -1) {
57 const QString commands = clipboardData.mid(0, lastLinePos);
58 sendCommand(commands);
59 clipboardData = clipboardData.mid(lastLinePos + 1);
60 }
61
62 return clipboardData;
63}
64
65void MAVLinkConsoleController::_setActiveVehicle(Vehicle *vehicle)
66{
67 for (QMetaObject::Connection &con : _connections) {
68 (void) disconnect(con);
69 }
70 _connections.clear();
71
72 _vehicle = vehicle;
73 if (_vehicle) {
74 _incomingBuffer.clear();
75 // Reset the model
76 setStringList(QStringList());
77 _cursorY = 0;
78 _cursorX = 0;
79 _cursorHomePos = -1;
80 _connections << connect(_vehicle, &Vehicle::mavlinkSerialControl, this, &MAVLinkConsoleController::_receiveData);
81 }
82}
83
84void MAVLinkConsoleController::_receiveData(uint8_t device, uint8_t flags, uint16_t timeout, uint32_t baudrate, const QByteArray &data)
85{
86 Q_UNUSED(flags); Q_UNUSED(timeout); Q_UNUSED(baudrate);
87
88 if (device != SERIAL_CONTROL_DEV_SHELL) {
89 return;
90 }
91
92 // Append incoming data and parse for ANSI codes
93 (void) _incomingBuffer.append(data);
94
95 while (!_incomingBuffer.isEmpty()) {
96 bool newline = false;
97
98 int idx = _incomingBuffer.indexOf('\n');
99 if (idx == -1) {
100 // Read the whole incoming buffer
101 idx = _incomingBuffer.size();
102 } else {
103 newline = true;
104 }
105
106 QByteArray fragment = _incomingBuffer.mid(0, idx);
107 if (!_processANSItext(fragment)) {
108 // ANSI processing failed, need more data
109 return;
110 }
111
112 _writeLine(_cursorY, fragment);
113 if (newline) {
114 _cursorY++;
115 _cursorX = 0;
116 // ensure line exists
117 const int rc = rowCount();
118 if (_cursorY >= rc) {
119 (void) insertRows(rc, 1 + _cursorY - rc);
120 }
121 }
122
123 (void) _incomingBuffer.remove(0, idx + (newline ? 1 : 0));
124 }
125}
126
127void MAVLinkConsoleController::_sendSerialData(const QByteArray &data, bool close)
128{
129 if (!_vehicle) {
130 qCWarning(MAVLinkConsoleControllerLog) << "Internal error";
131 return;
132 }
133
134 SharedLinkInterfacePtr sharedLink = _vehicle->vehicleLinkManager()->primaryLink().lock();
135 if (!sharedLink) {
136 return;
137 }
138
139 // Send maximum sized chunks until the complete buffer is transmitted
140 QByteArray output(data);
141 while (output.size()) {
142 QByteArray chunk(output.left(MAVLINK_MSG_SERIAL_CONTROL_FIELD_DATA_LEN));
143 const int dataSize = chunk.size();
144
145 // Ensure the buffer is large enough, as the MAVLink parser expects MAVLINK_MSG_SERIAL_CONTROL_FIELD_DATA_LEN bytes
146 (void) chunk.append(MAVLINK_MSG_SERIAL_CONTROL_FIELD_DATA_LEN - chunk.size(), '\0');
147
148 const uint8_t flags = close ? 0 : SERIAL_CONTROL_FLAG_EXCLUSIVE | SERIAL_CONTROL_FLAG_RESPOND | SERIAL_CONTROL_FLAG_MULTI;
149
151 (void) mavlink_msg_serial_control_pack_chan(
154 sharedLink->mavlinkChannel(),
155 &msg,
156 SERIAL_CONTROL_DEV_SHELL,
157 flags,
158 0,
159 0,
160 dataSize,
161 reinterpret_cast<uint8_t*>(chunk.data()),
162 _vehicle->id(),
163 _vehicle->defaultComponentId()
164 );
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)
void sendCommand(const QString &command)
QString handleClipboard(const QString &command_pre)
static int getComponentId()
Get the component id of this application.
static MAVLinkProtocol * instance()
int getSystemId() const
Get the system id of this application.
void activeVehicleChanged(Vehicle *activeVehicle)
WeakLinkInterfacePtr primaryLink() const
VehicleLinkManager * vehicleLinkManager()
Definition Vehicle.h:580
int id() const
Definition Vehicle.h:425
bool sendMessageOnLinkThreadSafe(LinkInterface *link, mavlink_message_t message)
Definition Vehicle.cc:1470
int defaultComponentId() const
Definition Vehicle.h:711
void mavlinkSerialControl(uint8_t device, uint8_t flags, uint16_t timeout, uint32_t baudrate, QByteArray data)
bool close(int deviceId)