DebugConsole.cc 29.1 KB
Newer Older
pixhawk's avatar
pixhawk committed
1 2
/*=====================================================================

lm's avatar
lm committed
3
QGroundControl Open Source Ground Control Station
pixhawk's avatar
pixhawk committed
4

lm's avatar
lm committed
5
(c) 2009, 2010 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
pixhawk's avatar
pixhawk committed
6

lm's avatar
lm committed
7
This file is part of the QGROUNDCONTROL project
pixhawk's avatar
pixhawk committed
8

lm's avatar
lm committed
9
    QGROUNDCONTROL is free software: you can redistribute it and/or modify
pixhawk's avatar
pixhawk committed
10 11 12 13
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

lm's avatar
lm committed
14
    QGROUNDCONTROL is distributed in the hope that it will be useful,
pixhawk's avatar
pixhawk committed
15 16 17 18 19
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
lm's avatar
lm committed
20
    along with QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.
pixhawk's avatar
pixhawk committed
21 22 23 24 25

======================================================================*/

/**
 * @file
lm's avatar
lm committed
26
 *   @brief Implementation of DebugConsole
pixhawk's avatar
pixhawk committed
27 28 29 30 31
 *
 *   @author Lorenz Meier <mavteam@student.ethz.ch>
 *
 */
#include <QPainter>
lm's avatar
lm committed
32
#include <QSettings>
Lorenz Meier's avatar
Lorenz Meier committed
33
#include <QScrollBar>
pixhawk's avatar
pixhawk committed
34 35 36 37

#include "DebugConsole.h"
#include "ui_DebugConsole.h"
#include "LinkManager.h"
38
#include "UASManager.h"
39
#include "protocol.h"
40
#include "QGC.h"
pixhawk's avatar
pixhawk committed
41 42 43 44

#include <QDebug>

DebugConsole::DebugConsole(QWidget *parent) :
45 46 47 48 49 50 51 52
    QWidget(parent),
    currLink(NULL),
    holdOn(false),
    convertToAscii(true),
    filterMAVLINK(false),
    autoHold(true),
    bytesToIgnore(0),
    lastByte(-1),
53 54
    escReceived(false),
    escIndex(0),
55 56 57
    sentBytes(),
    holdBuffer(),
    lineBuffer(""),
58
    lastLineBuffer(0),
59 60 61 62 63 64
    lineBufferTimer(),
    snapShotTimer(),
    snapShotInterval(500),
    snapShotBytes(0),
    dataRate(0.0f),
    lowpassDataRate(0.0f),
65
    dataRateThreshold(0.4),
66 67
    commandIndex(0),
    m_ui(new Ui::DebugConsole)
pixhawk's avatar
pixhawk committed
68 69 70
{
    // Setup basic user interface
    m_ui->setupUi(this);
pixhawk's avatar
pixhawk committed
71 72
    // Hide sent text field - it is only useful after send has been hit
    m_ui->sentText->setVisible(false);
73
    // Hide auto-send checkbox
74
    //m_ui->specialCheckBox->setVisible(false);
pixhawk's avatar
pixhawk committed
75
    // Make text area not editable
lm's avatar
lm committed
76
    m_ui->receiveText->setReadOnly(false);
pixhawk's avatar
pixhawk committed
77 78 79 80 81
    // Limit to 500 lines
    m_ui->receiveText->setMaximumBlockCount(500);
    // Allow to wrap everywhere
    m_ui->receiveText->setWordWrapMode(QTextOption::WrapAnywhere);

82
    // Load settings for the DebugConsole
83 84
    loadSettings();

pixhawk's avatar
pixhawk committed
85 86 87 88
    // Enable traffic measurements
    connect(&snapShotTimer, SIGNAL(timeout()), this, SLOT(updateTrafficMeasurements()));
    snapShotTimer.setInterval(snapShotInterval);
    snapShotTimer.start();
89 90
    // Update measurements the first time
    updateTrafficMeasurements();
pixhawk's avatar
pixhawk committed
91

92 93 94 95 96 97
    // First connect management slots, then make sure to add all existing objects
    // Connect to link manager to get notified about new links
    connect(LinkManager::instance(), SIGNAL(newLink(LinkInterface*)), this, SLOT(addLink(LinkInterface*)));
    // Connect to UAS manager to get notified about new UAS
    connect(UASManager::instance(), SIGNAL(UASCreated(UASInterface*)), this, SLOT(uasCreated(UASInterface*)));

pixhawk's avatar
pixhawk committed
98 99
    // Get a list of all existing links
    links = QList<LinkInterface*>();
100
    foreach (LinkInterface* link, LinkManager::instance()->getLinks()) {
pixhawk's avatar
pixhawk committed
101 102 103
        addLink(link);
    }

104 105 106 107 108
    // Get a list of all existing UAS
    foreach (UASInterface* uas, UASManager::instance()->getUASList()) {
        uasCreated(uas);
    }

pixhawk's avatar
pixhawk committed
109 110 111 112 113 114 115 116 117 118
    // Connect link combo box
    connect(m_ui->linkComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(linkSelected(int)));
    // Connect send button
    connect(m_ui->transmitButton, SIGNAL(clicked()), this, SLOT(sendBytes()));
    // Connect HEX conversion and MAVLINK filter checkboxes
    connect(m_ui->mavlinkCheckBox, SIGNAL(clicked(bool)), this, SLOT(MAVLINKfilterEnabled(bool)));
    connect(m_ui->hexCheckBox, SIGNAL(clicked(bool)), this, SLOT(hexModeEnabled(bool)));
    connect(m_ui->holdCheckBox, SIGNAL(clicked(bool)), this, SLOT(setAutoHold(bool)));
    // Connect hold button
    connect(m_ui->holdButton, SIGNAL(toggled(bool)), this, SLOT(hold(bool)));
119 120
    // Connect connect button
    connect(m_ui->connectButton, SIGNAL(clicked()), this, SLOT(handleConnectButton()));
121
    // Connect the special chars combo box
122
    connect(m_ui->addSymbolButton, SIGNAL(clicked()), this, SLOT(appendSpecialSymbol()));
123 124
    // Connect Checkbox
    connect(m_ui->specialComboBox, SIGNAL(highlighted(QString)), this, SLOT(specialSymbolSelected(QString)));
125 126
    // Allow to send via return
    connect(m_ui->sendText, SIGNAL(returnPressed()), this, SLOT(sendBytes()));
pixhawk's avatar
pixhawk committed
127 128
}

129 130 131 132 133 134
void DebugConsole::hideEvent(QHideEvent* event)
{
    Q_UNUSED(event);
    storeSettings();
}

pixhawk's avatar
pixhawk committed
135 136
DebugConsole::~DebugConsole()
{
lm's avatar
lm committed
137
    storeSettings();
pixhawk's avatar
pixhawk committed
138 139 140
    delete m_ui;
}

lm's avatar
lm committed
141 142 143 144 145 146 147 148 149
void DebugConsole::loadSettings()
{
    // Load defaults from settings
    QSettings settings;
    settings.sync();
    settings.beginGroup("QGC_DEBUG_CONSOLE");
    m_ui->specialComboBox->setCurrentIndex(settings.value("SPECIAL_SYMBOL", m_ui->specialComboBox->currentIndex()).toInt());
    m_ui->specialCheckBox->setChecked(settings.value("SPECIAL_SYMBOL_CHECKBOX_STATE", m_ui->specialCheckBox->isChecked()).toBool());
    hexModeEnabled(settings.value("HEX_MODE_ENABLED", m_ui->hexCheckBox->isChecked()).toBool());
150 151
    MAVLINKfilterEnabled(settings.value("MAVLINK_FILTER_ENABLED", filterMAVLINK).toBool());
    setAutoHold(settings.value("AUTO_HOLD_ENABLED", autoHold).toBool());
lm's avatar
lm committed
152 153 154 155 156 157 158 159 160 161 162
    settings.endGroup();
}

void DebugConsole::storeSettings()
{
    // Store settings
    QSettings settings;
    settings.beginGroup("QGC_DEBUG_CONSOLE");
    settings.setValue("SPECIAL_SYMBOL", m_ui->specialComboBox->currentIndex());
    settings.setValue("SPECIAL_SYMBOL_CHECKBOX_STATE", m_ui->specialCheckBox->isChecked());
    settings.setValue("HEX_MODE_ENABLED", m_ui->hexCheckBox->isChecked());
163 164
    settings.setValue("MAVLINK_FILTER_ENABLED", filterMAVLINK);
    settings.setValue("AUTO_HOLD_ENABLED", autoHold);
lm's avatar
lm committed
165 166 167 168
    settings.endGroup();
    settings.sync();
}

169 170 171 172 173 174
void DebugConsole::uasCreated(UASInterface* uas)
{
    connect(uas, SIGNAL(textMessageReceived(int,int,int,QString)),
            this, SLOT(receiveTextMessage(int,int,int,QString)), Qt::UniqueConnection);
}

pixhawk's avatar
pixhawk committed
175 176 177
/**
 * Add a link to the debug console output
 */
pixhawk's avatar
pixhawk committed
178 179 180 181
void DebugConsole::addLink(LinkInterface* link)
{
    // Add link to link list
    links.insert(link->getId(), link);
pixhawk's avatar
pixhawk committed
182

pixhawk's avatar
pixhawk committed
183 184 185
    m_ui->linkComboBox->insertItem(link->getId(), link->getName());
    // Set new item as current
    m_ui->linkComboBox->setCurrentIndex(qMax(0, links.size() - 1));
186
    linkSelected(m_ui->linkComboBox->currentIndex());
pixhawk's avatar
pixhawk committed
187 188

    // Register for name changes
189 190
    connect(link, SIGNAL(nameChanged(QString)), this, SLOT(updateLinkName(QString)), Qt::UniqueConnection);
    connect(link, SIGNAL(deleteLink(LinkInterface* const)), this, SLOT(removeLink(LinkInterface* const)), Qt::UniqueConnection);
191 192
}

193
void DebugConsole::removeLink(LinkInterface* const linkInterface)
194 195
{
    // Add link to link list
196
    if (links.contains(linkInterface)) {
197 198 199 200 201 202
        int linkIndex = links.indexOf(linkInterface);

        links.removeAt(linkIndex);

        m_ui->linkComboBox->removeItem(linkIndex);
    }
203
    if (linkInterface == currLink) currLink = NULL;
pixhawk's avatar
pixhawk committed
204
}
205 206
void DebugConsole::linkStatusUpdate(const QString& name,const QString& text)
{
207
    Q_UNUSED(name);
208 209 210 211
    m_ui->receiveText->appendPlainText(text);
    // Ensure text area scrolls correctly
    m_ui->receiveText->ensureCursorVisible();
}
pixhawk's avatar
pixhawk committed
212 213 214 215

void DebugConsole::linkSelected(int linkId)
{
    // Disconnect
216
    if (currLink) {
pixhawk's avatar
pixhawk committed
217
        disconnect(currLink, SIGNAL(bytesReceived(LinkInterface*,QByteArray)), this, SLOT(receiveBytes(LinkInterface*, QByteArray)));
218
        disconnect(currLink, SIGNAL(connected(bool)), this, SLOT(setConnectionState(bool)));
219
        disconnect(currLink,SIGNAL(communicationUpdate(QString,QString)),this,SLOT(linkStatusUpdate(QString,QString)));
pixhawk's avatar
pixhawk committed
220 221 222 223 224 225 226
    }
    // Clear data
    m_ui->receiveText->clear();

    // Connect new link
    currLink = links[linkId];
    connect(currLink, SIGNAL(bytesReceived(LinkInterface*,QByteArray)), this, SLOT(receiveBytes(LinkInterface*, QByteArray)));
227
    connect(currLink, SIGNAL(connected(bool)), this, SLOT(setConnectionState(bool)));
228
    connect(currLink,SIGNAL(communicationUpdate(QString,QString)),this,SLOT(linkStatusUpdate(QString,QString)));
229
    setConnectionState(currLink->isConnected());
pixhawk's avatar
pixhawk committed
230 231 232 233 234 235 236
}

/**
 * @param name new name for this link - the link is determined to the sender to this slot by QObject::sender()
 */
void DebugConsole::updateLinkName(QString name)
{
237
	// Set name if signal came from a link
pixhawk's avatar
pixhawk committed
238
    LinkInterface* link = qobject_cast<LinkInterface*>(sender());
239 240 241 242 243
	if((link != NULL) && (links.contains(link)))
	{
		const qint16 &linkIndex(links.indexOf(link));
		m_ui->linkComboBox->setItemText(linkIndex,name);
	}
pixhawk's avatar
pixhawk committed
244 245 246 247 248
}

void DebugConsole::setAutoHold(bool hold)
{
    // Disable current hold if hold had been enabled
249
    if (autoHold && holdOn && !hold) {
pixhawk's avatar
pixhawk committed
250 251 252
        this->hold(false);
        m_ui->holdButton->setChecked(false);
    }
253
    // Set auto hold checkbox
254
    if (m_ui->holdCheckBox->isChecked() != hold) {
255 256
        m_ui->holdCheckBox->setChecked(hold);
    }
257 258 259 260 261 262 263 264 265 266 267

    if (!hold)
    {
        // Warn user about not activated hold
        m_ui->receiveText->appendHtml(QString("<font color=\"%1\">%2</font>\n").arg(QColor(Qt::red).name(), tr("WARNING: You have NOT enabled auto-hold (stops updating the console if huge amounts of serial data arrive). Updating the console consumes significant CPU load, so if you receive more than about 5 KB/s of serial data, make sure to enable auto-hold if not using the console.")));
    }
    else
    {
        m_ui->receiveText->clear();
    }

pixhawk's avatar
pixhawk committed
268 269 270 271
    // Set new state
    autoHold = hold;
}

272 273 274 275
/**
 * Prints the message in the UAS color
 */
void DebugConsole::receiveTextMessage(int id, int component, int severity, QString text)
276
{
277
    Q_UNUSED(severity);
278 279 280 281 282 283 284 285 286 287 288 289 290
    if (isVisible())
    {
        QString name = UASManager::instance()->getUASForId(id)->getUASName();
        QString comp;
        // Get a human readable name if possible
        switch (component) {
            // TODO: To be completed
        case MAV_COMP_ID_IMU:
            comp = tr("IMU");
            break;
        case MAV_COMP_ID_MAPPER:
            comp = tr("MAPPER");
            break;
lm's avatar
lm committed
291 292
        case MAV_COMP_ID_MISSIONPLANNER:
            comp = tr("MISSION");
293 294 295 296 297 298 299 300
            break;
        case MAV_COMP_ID_SYSTEM_CONTROL:
            comp = tr("SYS-CONTROL");
            break;
        default:
            comp = QString::number(component);
            break;
        }
pixhawk's avatar
pixhawk committed
301

Lorenz Meier's avatar
Lorenz Meier committed
302 303 304 305
        //turn off updates while we're appending content to avoid breaking the autoscroll behavior
        m_ui->receiveText->setUpdatesEnabled(false);
        QScrollBar *scroller = m_ui->receiveText->verticalScrollBar();

306
        m_ui->receiveText->appendHtml(QString("<font color=\"%1\">(%2:%3) %4</font>\n").arg(UASManager::instance()->getUASForId(id)->getColor().name(), name, comp, text));
Lorenz Meier's avatar
Lorenz Meier committed
307

308
        // Ensure text area scrolls correctly
Lorenz Meier's avatar
Lorenz Meier committed
309 310
        scroller->setValue(scroller->maximum());
        m_ui->receiveText->setUpdatesEnabled(true);
311
    }
312 313
}

pixhawk's avatar
pixhawk committed
314 315
void DebugConsole::updateTrafficMeasurements()
{
316 317 318
    // Low-pass the calculated data rate with a very low frequency digital FIR filter.
    dataRate = (float)snapShotBytes / (float)snapShotInterval;
    lowpassDataRate = 0.9f * lowpassDataRate + 0.1f * dataRate;
pixhawk's avatar
pixhawk committed
319 320
    snapShotBytes = 0;

321 322 323
    // Check if the hold rate limit has been exceeded, and if so, stop displaying the incoming data stream.
    // We use the real data rate here because we want this to kick in immediately.
    if ((dataRate > dataRateThreshold) && autoHold) {
pixhawk's avatar
pixhawk committed
324 325 326 327
        m_ui->holdButton->setChecked(true);
        hold(true);
    }

328
    // Update the rate label.
329
    m_ui->downSpeedLabel->setText(tr("%L1 kB/s").arg(lowpassDataRate, 4, 'f', 1, '0'));
pixhawk's avatar
pixhawk committed
330 331 332 333
}

void DebugConsole::paintEvent(QPaintEvent *event)
{
334
    Q_UNUSED(event);
pixhawk's avatar
pixhawk committed
335 336 337 338 339
}

void DebugConsole::receiveBytes(LinkInterface* link, QByteArray bytes)
{
    snapShotBytes += bytes.size();
lm's avatar
lm committed
340 341 342
    int len = bytes.size();
    int lastSpace = 0;
    if ((this->bytesToIgnore > 260) || (this->bytesToIgnore < -2)) this->bytesToIgnore = 0;
343
    // Only add data from current link
lm's avatar
lm committed
344 345
    if (link == currLink && !holdOn)
    {
pixhawk's avatar
pixhawk committed
346
        // Parse all bytes
lm's avatar
lm committed
347 348
        for (int j = 0; j < len; j++)
        {
pixhawk's avatar
pixhawk committed
349
            unsigned char byte = bytes.at(j);
lm's avatar
lm committed
350
            // Filter MAVLink (http://qgroundcontrol.org/mavlink/) messages out of the stream.
lm's avatar
lm committed
351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367
            if (filterMAVLINK)
            {
                if (this->bytesToIgnore > 0)
                {
                    if ( (j + this->bytesToIgnore) < len )
                        j += this->bytesToIgnore - 1, this->bytesToIgnore = 1;
                    else
                        this->bytesToIgnore -= (len - j - 1), j = len - 1;
                } else
                if (this->bytesToIgnore == -2)
                {   // Payload plus header - but we got STX already
                    this->bytesToIgnore = static_cast<unsigned int>(byte) + MAVLINK_NUM_NON_PAYLOAD_BYTES - 1;
                    if ( (j + this->bytesToIgnore) < len )
                        j += this->bytesToIgnore - 1, this->bytesToIgnore = 1;
                    else
                        this->bytesToIgnore -= (len - j - 1), j = len - 1;
                } else
pixhawk's avatar
pixhawk committed
368
                // Filtering is done by setting an ignore counter based on the MAVLINK packet length
lm's avatar
lm committed
369 370 371 372 373 374 375 376 377
                if (static_cast<unsigned char>(byte) == MAVLINK_STX)
                {
                    this->bytesToIgnore = -1;
                } else
                    this->bytesToIgnore = 0;
            } else this->bytesToIgnore = 0;

            if ( (this->bytesToIgnore <= 0) && (this->bytesToIgnore != -1) )
            {
pixhawk's avatar
pixhawk committed
378 379
                QString str;
                // Convert to ASCII for readability
lm's avatar
lm committed
380 381
                if (convertToAscii)
                {
382 383
                    if (escReceived)
                    {
384
                        if (escIndex < static_cast<int>(sizeof(escBytes)))
385 386 387 388 389 390 391 392 393
                        {
                            escBytes[escIndex] = byte;
                            if (/*escIndex == 1 && */escBytes[escIndex] == 0x48)
                            {
                                // Handle sequence
                                // for this one, clear all text
                                m_ui->receiveText->clear();
                                escReceived = false;
                            }
394
                            else if (escBytes[escIndex] == 0x4b)
395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416
                            {
                                // Handle sequence
                                // for this one, do nothing
                                escReceived = false;
                            }
                            else if (byte == 0x5b)
                            {
                                // Do nothing, this is still a valid escape sequence
                            }
                            else
                            {
                                escReceived = false;
                            }
                         }
                        else
                        {
                            // Obviously something went wrong, reset
                            escReceived = false;
                            escIndex = 0;
                        }
                    }
                    else if ((byte <= 32) || (byte > 126))
lm's avatar
lm committed
417 418 419 420
                    {
                        switch (byte)
                        {
                            case (unsigned char)'\n':   // Accept line feed
421
                                if (lastByte != '\r')   // Do not break line again for LF+CR
lm's avatar
lm committed
422
                                    str.append(byte);   // only break line for single LF or CR bytes
423
                            break;
lm's avatar
lm committed
424 425 426
                            case (unsigned char)' ':    // space of any type means don't add another on hex output
                            case (unsigned char)'\t':   // Accept tab
                            case (unsigned char)'\r':   // Catch and carriage return
427 428
                                if (lastByte != '\n')   // Do not break line again for CR+LF
                                str.append(byte);       // only break line for single LF or CR bytes
lm's avatar
lm committed
429
                                lastSpace = 1;
pixhawk's avatar
pixhawk committed
430
                            break;
431 432 433 434 435 436 437 438 439
                            /* VT100 emulation (partially */
                            case 0x1b:                  // ESC received
                                escReceived = true;
                                escIndex = 0;
                                //qDebug() << "GOT ESC";
                                break;
                            case 0x08:                  // BS (backspace) received
                                // Do nothing for now
                                break;
lm's avatar
lm committed
440 441 442 443 444 445 446
                            default:                    // Append replacement character (box) if char is not ASCII
                                QString str2;
                                if ( lastSpace == 1)
                                    str2.sprintf("0x%02x ", byte);
                                else str2.sprintf(" 0x%02x ", byte);
                                str.append(str2);
                                lastSpace = 1;
447
                                escReceived = false;
pixhawk's avatar
pixhawk committed
448
                            break;
lm's avatar
lm committed
449
                        }
pixhawk's avatar
pixhawk committed
450
                    }
lm's avatar
lm committed
451 452
                    else
                    {
453 454 455
                        // Ignore carriage return, because that
                        // is auto-added with '\n'
                        if (byte != '\r') str.append(byte);           // Append original character
lm's avatar
lm committed
456 457 458 459 460
                        lastSpace = 0;
                    }
                }
                else
                {
pixhawk's avatar
pixhawk committed
461 462 463 464 465
                    QString str2;
                    str2.sprintf("%02x ", byte);
                    str.append(str2);
                }
                lineBuffer.append(str);
466
                lastByte = byte;
lm's avatar
lm committed
467 468 469 470
            }
            else
            {
                if (filterMAVLINK) this->bytesToIgnore--;
pixhawk's avatar
pixhawk committed
471 472 473
            }

        }
474
        // Plot every 200 ms if windows is visible
475
        if (lineBuffer.length() > 0 && (QGC::groundTimeMilliseconds() - lastLineBuffer) > 200) {
476 477
            if (isVisible())
            {
478 479 480
                m_ui->receiveText->appendPlainText(lineBuffer);
                lineBuffer.clear();
                lastLineBuffer = QGC::groundTimeMilliseconds();
481 482 483
                // Ensure text area scrolls correctly
                m_ui->receiveText->ensureCursorVisible();
            }
484 485 486 487
            if (lineBuffer.size() > 8192)
            {
                lineBuffer.remove(0, 4096);
            }
488
        }
lm's avatar
lm committed
489 490 491
    }
    else if (link == currLink && holdOn)
    {
pixhawk's avatar
pixhawk committed
492
        holdBuffer.append(bytes);
lm's avatar
lm committed
493 494
        if (holdBuffer.size() > 8192)
            holdBuffer.remove(0, 4096); // drop old stuff
pixhawk's avatar
pixhawk committed
495 496 497
    }
}

498 499 500
QByteArray DebugConsole::symbolNameToBytes(const QString& text)
{
    QByteArray b;
501
    if (text.contains("CR+LF")) {
502
        b.append(static_cast<char>(0x0D));
503
        b.append(static_cast<char>(0x0A));
504
    } else if (text.contains("LF")) {
505
        b.append(static_cast<char>(0x0A));
506
    } else if (text.contains("FF")) {
507
        b.append(static_cast<char>(0x0C));
508
    } else if (text.contains("CR")) {
509
        b.append(static_cast<char>(0x0D));
510
    } else if (text.contains("TAB")) {
511
        b.append(static_cast<char>(0x09));
512
    } else if (text.contains("NUL")) {
513
        b.append(static_cast<char>(0x00));
514
    } else if (text.contains("ESC")) {
515
        b.append(static_cast<char>(0x1B));
516
    } else if (text.contains("~")) {
517
        b.append(static_cast<char>(0x7E));
518
    } else if (text.contains("<Space>")) {
519 520 521 522 523
        b.append(static_cast<char>(0x20));
    }
    return b;
}

524 525 526
QString DebugConsole::bytesToSymbolNames(const QByteArray& b)
{
    QString text;
527
    if (b.size() > 1 && b.contains(0x0D) && b.contains(0x0A)) {
528
        text = "<CR+LF>";
529
    } else if (b.contains(0x0A)) {
530
        text = "<LF>";
531
    } else if (b.contains(0x0C)) {
532
        text = "<FF>";
533
    } else if (b.contains(0x0D)) {
534
        text = "<CR>";
535
    } else if (b.contains(0x09)) {
536
        text = "<TAB>";
537
    } else if (b.contains((char)0x00)) {
538
        text = "<NUL>";
539
    } else if (b.contains(0x1B)) {
540
        text = "<ESC>";
541
    } else if (b.contains(0x7E)) {
542
        text = "<~>";
543
    } else if (b.contains(0x20)) {
544
        text = "<Space>";
545
    } else {
546 547 548 549 550
        text.append(b);
    }
    return text;
}

551 552 553 554 555
void DebugConsole::specialSymbolSelected(const QString& text)
{
    Q_UNUSED(text);
}

556 557 558 559 560
void DebugConsole::appendSpecialSymbol(const QString& text)
{
    QString line = m_ui->sendText->text();
    QByteArray symbols = symbolNameToBytes(text);
    // The text is appended to the enter field
561
    if (convertToAscii) {
562
        line.append(symbols);
563
    } else {
564

565
        for (int i = 0; i < symbols.size(); i++) {
566 567 568 569 570 571 572
            QString add(" 0x%1");
            line.append(add.arg(static_cast<char>(symbols.at(i)), 2, 16, QChar('0')));
        }
    }
    m_ui->sendText->setText(line);
}

573 574 575 576 577
void DebugConsole::appendSpecialSymbol()
{
    appendSpecialSymbol(m_ui->specialComboBox->currentText());
}

pixhawk's avatar
pixhawk committed
578 579
void DebugConsole::sendBytes()
{
lm's avatar
lm committed
580 581 582 583 584 585
    // FIXME This store settings should be removed
    // once all threading issues have been resolved
    // since its called in the destructor, which
    // is absolutely sufficient
    storeSettings();

lm's avatar
lm committed
586 587 588 589 590 591
    // Store command history
    commandHistory.append(m_ui->sendText->text());
    // Since text was just sent, we're at position commandHistory.length()
    // which is the current text
    commandIndex = commandHistory.length();

592
    if (!m_ui->sentText->isVisible()) {
pixhawk's avatar
pixhawk committed
593 594 595
        m_ui->sentText->setVisible(true);
    }

596
    if (!currLink->isConnected()) {
597 598 599 600
        m_ui->sentText->setText(tr("Nothing sent. The link %1 is unconnected. Please connect first.").arg(currLink->getName()));
        return;
    }

601 602 603
    QString transmitUnconverted = m_ui->sendText->text();
    QByteArray specialSymbol;

604
    // Append special symbol if checkbox is checked
605
    if (m_ui->specialCheckBox->isChecked()) {
606 607 608 609
        // Get auto-add special symbols
        specialSymbol = symbolNameToBytes(m_ui->specialComboBox->currentText());

        // Convert them if needed
610
        if (!convertToAscii) {
611
            QString specialSymbolConverted;
612
            for (int i = 0; i < specialSymbol.length(); i++) {
613 614 615 616 617 618
                QString add(" 0x%1");
                specialSymbolConverted.append(add.arg(static_cast<char>(specialSymbol.at(i)), 2, 16, QChar('0')));
            }
            specialSymbol.clear();
            specialSymbol.append(specialSymbolConverted);
        }
619 620
    }

pixhawk's avatar
pixhawk committed
621 622 623
    QByteArray transmit;
    QString feedback;
    bool ok = true;
624
    if (convertToAscii) {
pixhawk's avatar
pixhawk committed
625
        // ASCII text is not converted
626 627 628 629 630 631 632
        transmit = transmitUnconverted.toLatin1();
        // Auto-add special symbol handling
        transmit.append(specialSymbol);

        QString translated;

        // Replace every occurence of a special symbol with its text name
633
        for (int i = 0; i < transmit.size(); ++i) {
634 635 636 637 638 639
            QByteArray specialChar;
            specialChar.append(transmit.at(i));
            translated.append(bytesToSymbolNames(specialChar));
        }

        feedback.append(translated);
640
    } else {
pixhawk's avatar
pixhawk committed
641
        // HEX symbols are converted to bytes
642 643
        QString str = transmitUnconverted.toLatin1();
        str.append(specialSymbol);
pixhawk's avatar
pixhawk committed
644 645
        str.remove(' ');
        str.remove("0x");
646
        str = str.simplified();
pixhawk's avatar
pixhawk committed
647
        int bufferIndex = 0;
648 649
        if ((str.size() % 2) == 0) {
            for (int i = 0; i < str.size(); i=i+2) {
pixhawk's avatar
pixhawk committed
650 651 652 653 654 655 656
                bool okByte;
                QString strBuf = QString(str.at(i));
                strBuf.append(str.at(i+1));
                unsigned char hex = strBuf.toInt(&okByte, 16);
                ok = (ok && okByte);
                transmit[bufferIndex++] = hex;

657
                if (okByte) {
pixhawk's avatar
pixhawk committed
658 659 660 661
                    // Feedback
                    feedback.append(str.at(i).toUpper());
                    feedback.append(str.at(i+1).toUpper());
                    feedback.append(" ");
662
                } else {
pixhawk's avatar
pixhawk committed
663 664 665
                    feedback = tr("HEX format error near \"") + strBuf + "\"";
                }
            }
666
        } else {
pixhawk's avatar
pixhawk committed
667 668 669 670 671 672
            ok = false;
            feedback = tr("HEX values have to be in pairs, e.g. AA or AA 05");
        }
    }

    // Transmit ASCII or HEX formatted text, only if more than one symbol
673
    if (ok && m_ui->sendText->text().toLatin1().size() > 0) {
pixhawk's avatar
pixhawk committed
674
        // Transmit only if conversion succeeded
675 676
        currLink->writeBytes(transmit, transmit.size());
        m_ui->sentText->setText(tr("Sent: ") + feedback);
677
    } else if (m_ui->sendText->text().toLatin1().size() > 0) {
pixhawk's avatar
pixhawk committed
678 679 680 681 682 683 684 685 686 687 688 689 690 691
        // Conversion failed, display error message
        m_ui->sentText->setText(tr("Not sent: ") + feedback);
    }

    // Select text to easy follow-up input from user
    m_ui->sendText->selectAll();
    m_ui->sendText->setFocus(Qt::OtherFocusReason);
}

/**
 * @param mode true to convert all in and output to/from HEX, false to send and receive ASCII values
 */
void DebugConsole::hexModeEnabled(bool mode)
{
692
    if (convertToAscii == mode) {
lm's avatar
lm committed
693
        convertToAscii = !mode;
694
        if (m_ui->hexCheckBox->isChecked() != mode) {
lm's avatar
lm committed
695 696 697 698 699 700 701
            m_ui->hexCheckBox->setChecked(mode);
        }
        m_ui->receiveText->clear();
        m_ui->sendText->clear();
        m_ui->sentText->clear();
        commandHistory.clear();
    }
pixhawk's avatar
pixhawk committed
702 703 704 705 706 707 708
}

/**
 * @param filter true to ignore all MAVLINK raw data in output, false, to display all incoming data
 */
void DebugConsole::MAVLINKfilterEnabled(bool filter)
{
709
    if (filterMAVLINK != filter) {
lm's avatar
lm committed
710
        filterMAVLINK = filter;
lm's avatar
lm committed
711
        this->bytesToIgnore = 0;
712
        if (m_ui->mavlinkCheckBox->isChecked() != filter) {
lm's avatar
lm committed
713 714 715
            m_ui->mavlinkCheckBox->setChecked(filter);
        }
    }
pixhawk's avatar
pixhawk committed
716 717 718 719 720 721
}
/**
 * @param hold Freeze the input and thus any scrolling
 */
void DebugConsole::hold(bool hold)
{
722 723 724 725 726 727 728 729
    if (holdOn != hold) {
        // Check if we need to append bytes from the hold buffer
        if (this->holdOn && !hold) {
            // TODO No conversion is done to the bytes in the hold buffer
            m_ui->receiveText->appendPlainText(QString(holdBuffer));
            holdBuffer.clear();
            lowpassDataRate = 0.0f;
        }
pixhawk's avatar
pixhawk committed
730

731
        this->holdOn = hold;
732

733 734 735 736 737 738 739 740 741
        // Change text interaction mode
        if (hold) {
            m_ui->receiveText->setTextInteractionFlags(Qt::TextSelectableByKeyboard | Qt::TextSelectableByMouse | Qt::LinksAccessibleByKeyboard | Qt::LinksAccessibleByMouse);
        } else {
            m_ui->receiveText->setTextInteractionFlags(Qt::NoTextInteraction);
        }
        if (m_ui->holdCheckBox->isChecked() != hold) {
            m_ui->holdCheckBox->setChecked(hold);
        }
lm's avatar
lm committed
742 743
    }
}
pixhawk's avatar
pixhawk committed
744

745 746 747 748 749
/**
 * Sets the connection state the widget shows to this state
 */
void DebugConsole::setConnectionState(bool connected)
{
750
    if(connected) {
751
        m_ui->connectButton->setText(tr("Disconn."));
752
        m_ui->receiveText->appendHtml(QString("<font color=\"%1\">%2</font>\n").arg(QGC::colorGreen.name(), tr("Link %1 is connected.").arg(currLink->getName())));
753
    } else {
754
        m_ui->connectButton->setText(tr("Connect"));
755
        m_ui->receiveText->appendHtml(QString("<font color=\"%1\">%2</font>\n").arg(QGC::colorOrange.name(), tr("Link %1 is unconnected.").arg(currLink->getName())));
756 757 758 759 760 761
    }
}

/** @brief Handle the connect button */
void DebugConsole::handleConnectButton()
{
762 763
    if (currLink) {
        if (currLink->isConnected()) {
764
            currLink->disconnect();
765
        } else {
766
            currLink->connect();
767 768 769 770
        }
    }
}

771 772
void DebugConsole::keyPressEvent(QKeyEvent * event)
{
773
    if (event->key() == Qt::Key_Up) {
774
        cycleCommandHistory(true);
775
    } else if (event->key() == Qt::Key_Down) {
776
        cycleCommandHistory(false);
777
    } else {
778 779 780 781 782 783
        QWidget::keyPressEvent(event);
    }
}

void DebugConsole::cycleCommandHistory(bool up)
{
lm's avatar
lm committed
784
    // Only cycle if there is a history
785
    if (commandHistory.length() > 0) {
lm's avatar
lm committed
786
        // Store current command if we're not in history yet
787
        if (commandIndex == commandHistory.length() && up) {
lm's avatar
lm committed
788 789
            currCommand = m_ui->sendText->text();
        }
790

791
        if (up) {
lm's avatar
lm committed
792 793
            // UP
            commandIndex--;
794
            if (commandIndex >= 0) {
lm's avatar
lm committed
795 796 797 798
                m_ui->sendText->setText(commandHistory.at(commandIndex));
            }

            // If the index
799
        } else {
lm's avatar
lm committed
800 801
            // DOWN
            commandIndex++;
802
            if (commandIndex < commandHistory.length()) {
lm's avatar
lm committed
803 804 805
                m_ui->sendText->setText(commandHistory.at(commandIndex));
            }
            // If the index is at history length, load the last current command
806

lm's avatar
lm committed
807 808 809
        }

        // Restore current command if we went out of history
810
        if (commandIndex == commandHistory.length()) {
lm's avatar
lm committed
811
            m_ui->sendText->setText(currCommand);
812 813
        }

lm's avatar
lm committed
814
        // If we are too far down or too far up, wrap around to current command
815
        if (commandIndex < 0 || commandIndex > commandHistory.length()) {
lm's avatar
lm committed
816 817 818
            commandIndex = commandHistory.length();
            m_ui->sendText->setText(currCommand);
        }
819

lm's avatar
lm committed
820 821 822
        // Bound the index
        if (commandIndex < 0) commandIndex = 0;
        if (commandIndex > commandHistory.length()) commandIndex = commandHistory.length();
823 824 825
    }
}

pixhawk's avatar
pixhawk committed
826 827 828 829 830 831 832 833 834 835 836
void DebugConsole::changeEvent(QEvent *e)
{
    QWidget::changeEvent(e);
    switch (e->type()) {
    case QEvent::LanguageChange:
        m_ui->retranslateUi(this);
        break;
    default:
        break;
    }
}