QGCParamWidget.cc 40.4 KB
Newer Older
1 2 3 4
/*=====================================================================

QGroundControl Open Source Ground Control Station

pixhawk's avatar
pixhawk committed
5
(c) 2009, 2010 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

This file is part of the QGROUNDCONTROL project

    QGROUNDCONTROL is free software: you can redistribute it and/or modify
    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.

    QGROUNDCONTROL is distributed in the hope that it will be useful,
    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
    along with QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.

======================================================================*/
/**
 * @file
 *   @brief Implementation of class QGCParamWidget
 *   @author Lorenz Meier <mail@qgroundcontrol.org>
 */

29 30
#include <QGridLayout>
#include <QPushButton>
31 32
#include <QFileDialog>
#include <QFile>
33
#include <QList>
34
#include <QTime>
35
#include <QSettings>
36
#include <QMessageBox>
LM's avatar
LM committed
37
#include <QApplication>
pixhawk's avatar
pixhawk committed
38 39 40 41

#include "QGCParamWidget.h"
#include "UASInterface.h"
#include <QDebug>
42
#include "QGC.h"
pixhawk's avatar
pixhawk committed
43

44 45 46 47
/**
 * @param uas MAV to set the parameters on
 * @param parent Parent widget
 */
pixhawk's avatar
pixhawk committed
48
QGCParamWidget::QGCParamWidget(UASInterface* uas, QWidget *parent) :
49 50
    QGCUASParamManager(uas, parent),
    components(new QMap<int, QTreeWidgetItem*>())
pixhawk's avatar
pixhawk committed
51
{
52 53 54
    // Load settings
    loadSettings();

lm's avatar
lm committed
55 56 57
    // Load default values and tooltips
    loadParameterInfoCSV(uas->getAutopilotTypeName(), uas->getSystemTypeName());

pixhawk's avatar
pixhawk committed
58 59
    // Create tree widget
    tree = new QTreeWidget(this);
lm's avatar
lm committed
60
    statusLabel = new QLabel();
lm's avatar
lm committed
61
    statusLabel->setAutoFillBackground(true);
62
    tree->setColumnWidth(70, 30);
pixhawk's avatar
pixhawk committed
63 64

    // Set tree widget as widget onto this component
65
    QGridLayout* horizontalLayout;
pixhawk's avatar
pixhawk committed
66
    //form->setAutoFillBackground(false);
67
    horizontalLayout = new QGridLayout(this);
68 69
    horizontalLayout->setHorizontalSpacing(6);
    horizontalLayout->setVerticalSpacing(6);
pixhawk's avatar
pixhawk committed
70
    horizontalLayout->setMargin(0);
71 72
    horizontalLayout->setSizeConstraint(QLayout::SetMinimumSize);
    //horizontalLayout->setSizeConstraint( QLayout::SetFixedSize );
pixhawk's avatar
pixhawk committed
73

lm's avatar
lm committed
74
    // Parameter tree
75
    horizontalLayout->addWidget(tree, 0, 0, 1, 3);
lm's avatar
lm committed
76 77

    // Status line
lm's avatar
lm committed
78
    statusLabel->setText(tr("Click refresh to download parameters"));
lm's avatar
lm committed
79 80 81 82
    horizontalLayout->addWidget(statusLabel, 1, 0, 1, 3);


    // BUTTONS
Jessica's avatar
Jessica committed
83
    QPushButton* refreshButton = new QPushButton(tr("Get"));
84 85
    refreshButton->setToolTip(tr("Load parameters currently in non-permanent memory of aircraft."));
    refreshButton->setWhatsThis(tr("Load parameters currently in non-permanent memory of aircraft."));
86
    connect(refreshButton, SIGNAL(clicked()), this, SLOT(requestParameterList()));
lm's avatar
lm committed
87
    horizontalLayout->addWidget(refreshButton, 2, 0);
88

Jessica's avatar
Jessica committed
89
    QPushButton* setButton = new QPushButton(tr("Set"));
90 91
    setButton->setToolTip(tr("Set current parameters in non-permanent onboard memory"));
    setButton->setWhatsThis(tr("Set current parameters in non-permanent onboard memory"));
92
    connect(setButton, SIGNAL(clicked()), this, SLOT(setParameters()));
lm's avatar
lm committed
93
    horizontalLayout->addWidget(setButton, 2, 1);
94

95
    QPushButton* writeButton = new QPushButton(tr("Write (ROM)"));
96 97
    writeButton->setToolTip(tr("Copy current parameters in non-permanent memory of the aircraft to permanent memory. Transmit your parameters first to write these."));
    writeButton->setWhatsThis(tr("Copy current parameters in non-permanent memory of the aircraft to permanent memory. Transmit your parameters first to write these."));
98
    connect(writeButton, SIGNAL(clicked()), this, SLOT(writeParameters()));
lm's avatar
lm committed
99
    horizontalLayout->addWidget(writeButton, 2, 2);
100

101
    QPushButton* loadFileButton = new QPushButton(tr("Load File"));
102 103
    loadFileButton->setToolTip(tr("Load parameters from a file on this computer in the view. To write them to the aircraft, use transmit after loading them."));
    loadFileButton->setWhatsThis(tr("Load parameters from a file on this computer in the view. To write them to the aircraft, use transmit after loading them."));
104
    connect(loadFileButton, SIGNAL(clicked()), this, SLOT(loadParameters()));
lm's avatar
lm committed
105
    horizontalLayout->addWidget(loadFileButton, 3, 0);
106 107

    QPushButton* saveFileButton = new QPushButton(tr("Save File"));
108 109
    saveFileButton->setToolTip(tr("Save parameters in this view to a file on this computer."));
    saveFileButton->setWhatsThis(tr("Save parameters in this view to a file on this computer."));
110
    connect(saveFileButton, SIGNAL(clicked()), this, SLOT(saveParameters()));
lm's avatar
lm committed
111 112 113 114 115 116 117
    horizontalLayout->addWidget(saveFileButton, 3, 1);

    QPushButton* readButton = new QPushButton(tr("Read (ROM)"));
    readButton->setToolTip(tr("Copy parameters from permanent memory to non-permanent current memory of aircraft. DOES NOT update the parameters in this view, click refresh after copying them to get them."));
    readButton->setWhatsThis(tr("Copy parameters from permanent memory to non-permanent current memory of aircraft. DOES NOT update the parameters in this view, click refresh after copying them to get them."));
    connect(readButton, SIGNAL(clicked()), this, SLOT(readParameters()));
    horizontalLayout->addWidget(readButton, 3, 2);
118

119 120 121 122 123 124
    // Set correct vertical scaling
    horizontalLayout->setRowStretch(0, 100);
    horizontalLayout->setRowStretch(1, 10);
    horizontalLayout->setRowStretch(2, 10);
    horizontalLayout->setRowStretch(3, 10);

125
    // Set layout
pixhawk's avatar
pixhawk committed
126 127 128 129 130 131 132 133
    this->setLayout(horizontalLayout);

    // Set header
    QStringList headerItems;
    headerItems.append("Parameter");
    headerItems.append("Value");
    tree->setHeaderLabels(headerItems);
    tree->setColumnCount(2);
134
    tree->setExpandsOnDoubleClick(true);
135 136

    // Connect signals/slots
137
    connect(this, SIGNAL(parameterChanged(int,QString,QVariant)), mav, SLOT(setParameter(int,QString,QVariant)));
138
    connect(tree, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(parameterItemChanged(QTreeWidgetItem*,int)));
lm's avatar
lm committed
139

140
    // New parameters from UAS
141
    connect(uas, SIGNAL(parameterChanged(int,int,int,int,QString,QVariant)), this, SLOT(addParameter(int,int,int,int,QString,QVariant)));
142 143

    // Connect retransmission guard
144
    connect(this, SIGNAL(requestParameter(int,QString)), uas, SLOT(requestParameter(int,QString)));
145 146
    connect(this, SIGNAL(requestParameter(int,int)), uas, SLOT(requestParameter(int,int)));
    connect(&retransmissionTimer, SIGNAL(timeout()), this, SLOT(retransmissionGuardTick()));
147 148

    // Get parameters
149
    if (uas) requestParameterList();
pixhawk's avatar
pixhawk committed
150 151
}

152 153 154 155 156 157 158 159 160 161 162 163
void QGCParamWidget::loadSettings()
{
    QSettings settings;
    settings.beginGroup("QGC_MAVLINK_PROTOCOL");
    bool ok;
    int temp = settings.value("PARAMETER_RETRANSMISSION_TIMEOUT", retransmissionTimeout).toInt(&ok);
    if (ok) retransmissionTimeout = temp;
    temp = settings.value("PARAMETER_REWRITE_TIMEOUT", rewriteTimeout).toInt(&ok);
    if (ok) rewriteTimeout = temp;
    settings.endGroup();
}

lm's avatar
lm committed
164 165
void QGCParamWidget::loadParameterInfoCSV(const QString& autopilot, const QString& airframe)
{
LM's avatar
LM committed
166
    QDir appDir = QApplication::applicationDirPath();
167
    appDir.cd("files");
LM's avatar
LM committed
168 169
    QString fileName = QString("%1/%2/%3/parameter_tooltips/tooltips.txt").arg(appDir.canonicalPath()).arg(autopilot.toLower()).arg(airframe.toLower());
    QFile paramMetaFile(fileName);
lm's avatar
lm committed
170 171 172 173

    // Load CSV data
    if (!paramMetaFile.open(QIODevice::ReadOnly | QIODevice::Text))
    {
174
        //qDebug() << "COULD NOT OPEN PARAM META INFO FILE:" << fileName;
lm's avatar
lm committed
175 176 177 178 179 180 181 182 183 184
        return;
    }

    // Extract header

    // Read in values
    // Find all keys
    QTextStream in(&paramMetaFile);

    // First line is header
185 186
    // there might be more lines, but the first
    // line is assumed to be at least header
lm's avatar
lm committed
187 188
    QString header = in.readLine();

189 190 191 192 193 194
    // Ignore top-level comment lines
    while (header.startsWith('#') || header.startsWith('/') || header.startsWith('='))
    {
        header = in.readLine();
    }

lm's avatar
lm committed
195 196 197 198 199 200
    bool charRead = false;
    QString separator = "";
    QList<QChar> sepCandidates;
    sepCandidates << '\t';
    sepCandidates << ',';
    sepCandidates << ';';
201
    //sepCandidates << ' ';
lm's avatar
lm committed
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
    sepCandidates << '~';
    sepCandidates << '|';

    // Iterate until separator is found
    // or full header is parsed
    for (int i = 0; i < header.length(); i++)
    {
        if (sepCandidates.contains(header.at(i)))
        {
            // Separator found
            if (charRead)
            {
                separator += header[i];
            }
        }
        else
        {
            // Char found
            charRead = true;
            // If the separator is not empty, this char
            // has been read after a separator, so detection
            // is now complete
            if (separator != "") break;
        }
    }

228 229 230 231 232 233 234 235 236
    bool stripFirstSeparator = false;
    bool stripLastSeparator = false;

    // Figure out if the lines start with the separator (e.g. wiki syntax)
    if (header.startsWith(separator)) stripFirstSeparator = true;

    // Figure out if the lines end with the separator (e.g. wiki syntax)
    if (header.endsWith(separator)) stripLastSeparator = true;

lm's avatar
lm committed
237 238
    QString out = separator;
    out.replace("\t", "<tab>");
239
    //qDebug() << " Separator: \"" << out << "\"";
lm's avatar
lm committed
240 241 242 243 244 245 246 247
    //qDebug() << "READING CSV:" << header;


    // Read data
    while (!in.atEnd())
    {
        QString line = in.readLine();

248 249 250 251 252 253 254 255
        //qDebug() << "LINE PRE-STRIP" << line;

        // Strip separtors if necessary
        if (stripFirstSeparator) line.remove(0, separator.length());
        if (stripLastSeparator) line.remove(line.length()-separator.length(), line.length()-1);

        //qDebug() << "LINE POST-STRIP" << line;

lm's avatar
lm committed
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
        // Keep empty parts here - we still have to act on them
        QStringList parts = line.split(separator, QString::KeepEmptyParts);

        // Each line is:
        // variable name, Min, Max, Default, Multiplier, Enabled (0 = no, 1 = yes), Comment


        // Fill in min, max and default values
        if (parts.count() > 1)
        {
            // min
            paramMin.insert(parts.at(0), parts.at(1).toDouble());
        }
        if (parts.count() > 2)
        {
            // max
            paramMax.insert(parts.at(0), parts.at(2).toDouble());
        }
        if (parts.count() > 3)
        {
            // default
            paramDefault.insert(parts.at(0), parts.at(3).toDouble());
        }
        // IGNORING 4 and 5 for now
        if (parts.count() > 6)
        {
            // tooltip
            paramToolTips.insert(parts.at(0), parts.at(6).trimmed());
        }
    }
}

288 289 290 291
/**
 * @return The MAV of this widget. Unless the MAV object has been destroyed, this
 *         pointer is never zero.
 */
pixhawk's avatar
pixhawk committed
292 293 294 295 296 297 298 299 300 301 302
UASInterface* QGCParamWidget::getUAS()
{
    return mav;
}

/**
 *
 * @param uas System which has the component
 * @param component id of the component
 * @param componentName human friendly name of the component
 */
303
void QGCParamWidget::addComponent(int uas, int component, QString componentName)
pixhawk's avatar
pixhawk committed
304
{
305
    Q_UNUSED(uas);
306
    if (components->contains(component)) {
pixhawk's avatar
pixhawk committed
307
        // Update existing
308 309 310
        components->value(component)->setData(0, Qt::DisplayRole, QString("%1 (#%2)").arg(componentName).arg(component));
        //components->value(component)->setData(1, Qt::DisplayRole, QString::number(component));
        components->value(component)->setFirstColumnSpanned(true);
311
    } else {
pixhawk's avatar
pixhawk committed
312
        // Add new
lm's avatar
lm committed
313
        QStringList list(QString("%1 (#%2)").arg(componentName).arg(component));
pixhawk's avatar
pixhawk committed
314
        QTreeWidgetItem* comp = new QTreeWidgetItem(list);
lm's avatar
lm committed
315
        comp->setFirstColumnSpanned(true);
pixhawk's avatar
pixhawk committed
316 317 318
        components->insert(component, comp);
        // Create grouping and update maps
        paramGroups.insert(component, new QMap<QString, QTreeWidgetItem*>());
pixhawk's avatar
pixhawk committed
319 320
        tree->addTopLevelItem(comp);
        tree->update();
321
        // Create map in parameters
322
        if (!parameters.contains(component)) {
323
            parameters.insert(component, new QMap<QString, QVariant>());
324 325
        }
        // Create map in changed parameters
326
        if (!changedValues.contains(component)) {
327
            changedValues.insert(component, new QMap<QString, QVariant>());
328
        }
pixhawk's avatar
pixhawk committed
329 330 331
    }
}

lm's avatar
lm committed
332 333 334 335 336
/**
 * @param uas System which has the component
 * @param component id of the component
 * @param parameterName human friendly name of the parameter
 */
337
void QGCParamWidget::addParameter(int uas, int component, int paramCount, int paramId, QString parameterName, QVariant value)
lm's avatar
lm committed
338 339 340
{
    addParameter(uas, component, parameterName, value);

lm's avatar
lm committed
341
    // Missing packets list has to be instantiated for all components
342
    if (!transmissionMissingPackets.contains(component)) {
lm's avatar
lm committed
343 344 345
        transmissionMissingPackets.insert(component, new QList<int>());
    }

lm's avatar
lm committed
346
    // List mode is different from single parameter transfers
347
    if (transmissionListMode) {
lm's avatar
lm committed
348 349
        // Only accept the list size once on the first packet from
        // each component
lm's avatar
lm committed
350 351
        if (!transmissionListSizeKnown.contains(component))
        {
lm's avatar
lm committed
352 353
            // Mark list size as known
            transmissionListSizeKnown.insert(component, true);
lm's avatar
lm committed
354

355
            // Mark all parameters as missing
lm's avatar
lm committed
356 357 358 359
            for (int i = 0; i < paramCount; ++i)
            {
                if (!transmissionMissingPackets.value(component)->contains(i))
                {
lm's avatar
lm committed
360 361 362
                    transmissionMissingPackets.value(component)->append(i);
                }
            }
363

lm's avatar
lm committed
364 365 366 367
            // There is only one transmission timeout for all components
            // since components do not manage their transmission,
            // the longest timeout is safe for all components.
            quint64 thisTransmissionTimeout = QGC::groundTimeMilliseconds() + ((paramCount/retransmissionBurstRequestSize+5)*retransmissionTimeout);
lm's avatar
lm committed
368 369
            if (thisTransmissionTimeout > transmissionTimeout)
            {
lm's avatar
lm committed
370 371
                transmissionTimeout = thisTransmissionTimeout;
            }
lm's avatar
lm committed
372
        }
lm's avatar
lm committed
373 374 375 376

        // Start retransmission guard
        // or reset timer
        setRetransmissionGuardEnabled(true);
lm's avatar
lm committed
377 378
    }

lm's avatar
lm committed
379
    // Mark this parameter as received in read list
lm's avatar
lm committed
380 381 382 383
    int index = transmissionMissingPackets.value(component)->indexOf(paramId);
    // If the MAV sent the parameter without request, it wont be in missing list
    if (index != -1) transmissionMissingPackets.value(component)->removeAt(index);

lm's avatar
lm committed
384 385
    bool justWritten = false;
    bool writeMismatch = false;
386
    //bool lastWritten = false;
lm's avatar
lm committed
387
    // Mark this parameter as received in write ACK list
388
    QMap<QString, QVariant>* map = transmissionMissingWriteAckPackets.value(component);
lm's avatar
lm committed
389 390
    if (map && map->contains(parameterName))
    {
lm's avatar
lm committed
391
        justWritten = true;
lm's avatar
lm committed
392 393
        if (map->value(parameterName) != value)
        {
lm's avatar
lm committed
394 395 396 397 398
            writeMismatch = true;
        }
        map->remove(parameterName);
    }

399
    int missCount = 0;
lm's avatar
lm committed
400 401
    foreach (int key, transmissionMissingPackets.keys())
    {
402 403 404
        missCount +=  transmissionMissingPackets.value(key)->count();
    }

lm's avatar
lm committed
405
    int missWriteCount = 0;
lm's avatar
lm committed
406 407
    foreach (int key, transmissionMissingWriteAckPackets.keys())
    {
lm's avatar
lm committed
408 409 410
        missWriteCount += transmissionMissingWriteAckPackets.value(key)->count();
    }

lm's avatar
lm committed
411 412
    if (justWritten && !writeMismatch && missWriteCount == 0)
    {
lm's avatar
lm committed
413 414 415 416 417
        // Just wrote one and count went to 0 - this was the last missing write parameter
        statusLabel->setText(tr("SUCCESS: WROTE ALL PARAMETERS"));
        QPalette pal = statusLabel->palette();
        pal.setColor(backgroundRole(), QGC::colorGreen);
        statusLabel->setPalette(pal);
lm's avatar
lm committed
418 419
    } else if (justWritten && !writeMismatch)
    {
420
        statusLabel->setText(tr("SUCCESS: Wrote %2 (#%1/%4): %3").arg(paramId+1).arg(parameterName).arg(value.toDouble()).arg(paramCount));
lm's avatar
lm committed
421 422 423
        QPalette pal = statusLabel->palette();
        pal.setColor(backgroundRole(), QGC::colorGreen);
        statusLabel->setPalette(pal);
lm's avatar
lm committed
424 425
    } else if (justWritten && writeMismatch)
    {
lm's avatar
lm committed
426
        // Mismatch, tell user
lm's avatar
lm committed
427 428 429
        QPalette pal = statusLabel->palette();
        pal.setColor(backgroundRole(), QGC::colorRed);
        statusLabel->setPalette(pal);
430
        statusLabel->setText(tr("FAILURE: Wrote %1: sent %2 != onboard %3").arg(parameterName).arg(map->value(parameterName).toDouble()).arg(value.toDouble()));
lm's avatar
lm committed
431 432 433 434 435
    }
    else
    {
        if (missCount > 0)
        {
lm's avatar
lm committed
436 437 438
            QPalette pal = statusLabel->palette();
            pal.setColor(backgroundRole(), QGC::colorOrange);
            statusLabel->setPalette(pal);
lm's avatar
lm committed
439 440 441
        }
        else
        {
lm's avatar
lm committed
442 443 444 445
            QPalette pal = statusLabel->palette();
            pal.setColor(backgroundRole(), QGC::colorGreen);
            statusLabel->setPalette(pal);
        }
446 447
        QString val = QString("%1").arg(value.toFloat(), 5, 'f', 1, QChar(' '));
        //statusLabel->setText(tr("OK: %1 %2 #%3/%4, %5 miss").arg(parameterName).arg(val).arg(paramId+1).arg(paramCount).arg(missCount));
448 449 450 451 452 453 454 455 456 457 458 459
        if (missCount == 0)
        {
            // Transmission done
            QTime time = QTime::currentTime();
            QString timeString = time.toString();
            statusLabel->setText(tr("All received. (updated at %1)").arg(timeString));
        }
        else
        {
            // Transmission in progress
            statusLabel->setText(tr("OK: %1 %2 (%3/%4)").arg(parameterName).arg(val).arg(paramCount-missCount).arg(paramCount));
        }
lm's avatar
lm committed
460
    }
lm's avatar
lm committed
461 462

    // Check if last parameter was received
lm's avatar
lm committed
463 464
    if (missCount == 0 && missWriteCount == 0)
    {
lm's avatar
lm committed
465 466 467
        this->transmissionActive = false;
        this->transmissionListMode = false;
        transmissionListSizeKnown.clear();
lm's avatar
lm committed
468 469
        foreach (int key, transmissionMissingPackets.keys())
        {
lm's avatar
lm committed
470 471
            transmissionMissingPackets.value(key)->clear();
        }
472 473 474

        // Expand visual tree
        tree->expandItem(tree->topLevelItem(0));
lm's avatar
lm committed
475 476 477
    }
}

478 479 480 481 482
/**
 * @param uas System which has the component
 * @param component id of the component
 * @param parameterName human friendly name of the parameter
 */
483
void QGCParamWidget::addParameter(int uas, int component, QString parameterName, QVariant value)
pixhawk's avatar
pixhawk committed
484
{
485
    //qDebug() << "PARAM WIDGET GOT PARAM:" << value;
486
    Q_UNUSED(uas);
487
    // Reference to item in tree
488
    QTreeWidgetItem* parameterItem = NULL;
pixhawk's avatar
pixhawk committed
489 490

    // Get component
lm's avatar
lm committed
491 492
    if (!components->contains(component))
    {
493 494 495 496 497 498 499 500 501 502 503 504 505
//        QString componentName;
//        switch (component)
//        {
//        case MAV_COMP_ID_CAMERA:
//            componentName = tr("Camera (#%1)").arg(component);
//            break;
//        case MAV_COMP_ID_IMU:
//            componentName = tr("IMU (#%1)").arg(component);
//            break;
//        default:
//            componentName = tr("Component #").arg(component);
//            break;
//        }
506
        QString componentName = tr("Component #%1").arg(component);
507
        addComponent(uas, component, componentName);
pixhawk's avatar
pixhawk committed
508
    }
509

510 511 512 513 514 515 516
    // Replace value in map

    // FIXME
    if (parameters.value(component)->contains(parameterName)) parameters.value(component)->remove(parameterName);
    parameters.value(component)->insert(parameterName, value);


pixhawk's avatar
pixhawk committed
517 518
    QString splitToken = "_";
    // Check if auto-grouping can work
lm's avatar
lm committed
519 520
    if (parameterName.contains(splitToken))
    {
pixhawk's avatar
pixhawk committed
521 522
        QString parent = parameterName.section(splitToken, 0, 0, QString::SectionSkipEmpty);
        QMap<QString, QTreeWidgetItem*>* compParamGroups = paramGroups.value(component);
523 524
        if (!compParamGroups->contains(parent))
        {
pixhawk's avatar
pixhawk committed
525 526 527 528 529 530
            // Insert group item
            QStringList glist;
            glist.append(parent);
            QTreeWidgetItem* item = new QTreeWidgetItem(glist);
            compParamGroups->insert(parent, item);
            components->value(component)->addChild(item);
531
        }
532 533 534 535

        // Append child to group
        bool found = false;
        QTreeWidgetItem* parentItem = compParamGroups->value(parent);
536
        for (int i = 0; i < parentItem->childCount(); i++) {
537 538
            QTreeWidgetItem* child = parentItem->child(i);
            QString key = child->data(0, Qt::DisplayRole).toString();
539 540
            if (key == parameterName)
            {
541 542 543 544 545 546 547
                //qDebug() << "UPDATED CHILD";
                parameterItem = child;
                parameterItem->setData(1, Qt::DisplayRole, value);
                found = true;
            }
        }

lm's avatar
lm committed
548 549
        if (!found)
        {
550 551 552
            // Insert parameter into map
            QStringList plist;
            plist.append(parameterName);
553
            // CREATE PARAMETER ITEM
554
            parameterItem = new QTreeWidgetItem(plist);
555
            // CONFIGURE PARAMETER ITEM
556
            parameterItem->setData(1, Qt::DisplayRole, value);
557 558

            compParamGroups->value(parent)->addChild(parameterItem);
lm's avatar
lm committed
559
            parameterItem->setFlags(parameterItem->flags() | Qt::ItemIsEditable);
560
        }
lm's avatar
lm committed
561 562 563
    }
    else
    {
pixhawk's avatar
pixhawk committed
564 565
        bool found = false;
        QTreeWidgetItem* parent = components->value(component);
lm's avatar
lm committed
566 567
        for (int i = 0; i < parent->childCount(); i++)
        {
pixhawk's avatar
pixhawk committed
568 569
            QTreeWidgetItem* child = parent->child(i);
            QString key = child->data(0, Qt::DisplayRole).toString();
lm's avatar
lm committed
570 571
            if (key == parameterName)
            {
pixhawk's avatar
pixhawk committed
572
                //qDebug() << "UPDATED CHILD";
573 574
                parameterItem = child;
                parameterItem->setData(1, Qt::DisplayRole, value);
pixhawk's avatar
pixhawk committed
575 576 577 578
                found = true;
            }
        }

lm's avatar
lm committed
579 580
        if (!found)
        {
581 582 583
            // Insert parameter into map
            QStringList plist;
            plist.append(parameterName);
584
            // CREATE PARAMETER ITEM
585
            parameterItem = new QTreeWidgetItem(plist);
586
            // CONFIGURE PARAMETER ITEM
587
            parameterItem->setData(1, Qt::DisplayRole, value);
588

lm's avatar
lm committed
589 590
            components->value(component)->addChild(parameterItem);
            parameterItem->setFlags(parameterItem->flags() | Qt::ItemIsEditable);
pixhawk's avatar
pixhawk committed
591
        }
592
        //tree->expandAll();
593
    }
594
    // Reset background color
595
    parameterItem->setBackground(0, Qt::NoBrush);
596
    parameterItem->setBackground(1, Qt::NoBrush);
lm's avatar
lm committed
597
    // Add tooltip
598 599 600 601
    QString tooltipFormat;
    if (paramDefault.contains(parameterName))
    {
        tooltipFormat = tr("Default: %1, %2");
LM's avatar
LM committed
602
        tooltipFormat = tooltipFormat.arg(paramDefault.value(parameterName, 0.0f)).arg(paramToolTips.value(parameterName, ""));
603 604 605 606 607 608 609
    }
    else
    {
        tooltipFormat = paramToolTips.value(parameterName, "");
    }
    parameterItem->setToolTip(0, tooltipFormat);
    parameterItem->setToolTip(1, tooltipFormat);
lm's avatar
lm committed
610

611
    //tree->update();
pixhawk's avatar
pixhawk committed
612
    if (changedValues.contains(component)) changedValues.value(component)->remove(parameterName);
pixhawk's avatar
pixhawk committed
613 614
}

615 616 617 618
/**
 * Send a request to deliver the list of onboard parameters
 * to the MAV.
 */
619 620
void QGCParamWidget::requestParameterList()
{
621
    if (!mav) return;
622 623 624 625 626 627 628
    // FIXME This call does not belong here
    // Once the comm handling is moved to a new
    // Param manager class the settings can be directly
    // loaded from MAVLink protocol
    loadSettings();
    // End of FIXME

lm's avatar
lm committed
629
    // Clear view and request param list
630
    clear();
631
    parameters.clear();
lm's avatar
lm committed
632
    received.clear();
lm's avatar
lm committed
633 634 635
    // Clear transmission state
    transmissionListMode = true;
    transmissionListSizeKnown.clear();
Lorenz Meier's avatar
Lorenz Meier committed
636 637
    foreach (int key, transmissionMissingPackets.keys())
    {
lm's avatar
lm committed
638 639 640
        transmissionMissingPackets.value(key)->clear();
    }
    transmissionActive = true;
lm's avatar
lm committed
641 642 643 644 645

    // Set status text
    statusLabel->setText(tr("Requested param list.. waiting"));

    mav->requestParameters();
646 647
}

648
void QGCParamWidget::parameterItemChanged(QTreeWidgetItem* current, int column)
lm's avatar
lm committed
649
{
650
    if (current && column > 0) {
651
        QTreeWidgetItem* parent = current->parent();
652
        while (parent->parent() != NULL) {
653 654 655 656
            parent = parent->parent();
        }
        // Parent is now top-level component
        int key = components->key(parent);
657
        if (!changedValues.contains(key)) {
658
            changedValues.insert(key, new QMap<QString, QVariant>());
659
        }
660
        QMap<QString, QVariant>* map = changedValues.value(key, NULL);
661
        if (map) {
662
            QString str = current->data(0, Qt::DisplayRole).toString();
663
            QVariant value = current->data(1, Qt::DisplayRole);
lm's avatar
lm committed
664
            // Set parameter on changed list to be transmitted to MAV
665 666 667 668
            QPalette pal = statusLabel->palette();
            pal.setColor(backgroundRole(), QGC::colorOrange);
            statusLabel->setPalette(pal);
            statusLabel->setText(tr("Transmit pend. %1:%2: %3").arg(key).arg(str).arg(value.toFloat(), 5, 'f', 1, QChar(' ')));
669 670 671 672 673 674 675 676 677 678
            //qDebug() << "PARAM CHANGED: COMP:" << key << "KEY:" << str << "VALUE:" << value;
            // Changed values list
            if (map->contains(str)) map->remove(str);
            map->insert(str, value);

            // Check if the value was numerically changed
            if (!parameters.value(key)->contains(str) || parameters.value(key)->value(str, value.toDouble()-1) != value) {
                current->setBackground(0, QBrush(QColor(QGC::colorOrange)));
                current->setBackground(1, QBrush(QColor(QGC::colorOrange)));
            }
679

680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697
            switch (parameters.value(key)->value(str).type())
            {
            case QVariant::Int:
                {
                    QVariant fixedValue(value.toInt());
                    parameters.value(key)->insert(str, fixedValue);
                }
                break;
            case QVariant::UInt:
                {
                    QVariant fixedValue(value.toUInt());
                    parameters.value(key)->insert(str, fixedValue);
                }
                break;
            case QMetaType::Float:
                {
                    QVariant fixedValue(value.toFloat());
                    parameters.value(key)->insert(str, fixedValue);
698
                }
699 700 701 702
                break;
            default:
                qCritical() << "ABORTED PARAM UPDATE, NO VALID QVARIANT TYPE";
                return;
703 704 705 706 707 708
            }
        }
    }
}

void QGCParamWidget::saveParameters()
709
{
710
    if (!mav) return;
711 712
    QString fileName = QFileDialog::getSaveFileName(this, tr("Save File"), "./parameters.txt", tr("Parameter File (*.txt)"));
    QFile file(fileName);
713
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
714 715 716 717 718 719 720 721 722 723
        return;
    }

    QTextStream in(&file);

    in << "# Onboard parameters for system " << mav->getUASName() << "\n";
    in << "#\n";
    in << "# MAV ID  COMPONENT ID  PARAM NAME  VALUE (FLOAT)\n";

    // Iterate through all components, through all parameters and emit them
724
    QMap<int, QMap<QString, QVariant>*>::iterator i;
725
    for (i = parameters.begin(); i != parameters.end(); ++i) {
726 727
        // Iterate through the parameters of the component
        int compid = i.key();
728
        QMap<QString, QVariant>* comp = i.value();
729
        {
730 731 732
            QMap<QString, QVariant>::iterator j;
            for (j = comp->begin(); j != comp->end(); ++j)
            {
733
                QString paramValue("%1");
734 735 736 737 738
                QString paramType("%1");
                switch (j.value().type())
                {
                case QVariant::Int:
                    paramValue = paramValue.arg(j.value().toInt());
739
                    paramType = paramType.arg(MAV_PARAM_TYPE_INT32);
740 741 742
                    break;
                case QVariant::UInt:
                    paramValue = paramValue.arg(j.value().toUInt());
743
                    paramType = paramType.arg(MAV_PARAM_TYPE_UINT32);
744 745 746
                    break;
                case QMetaType::Float:
                    paramValue = paramValue.arg(j.value().toDouble(), 25, 'g', 12);
747
                    paramType = paramType.arg(MAV_PARAM_TYPE_REAL32);
748 749 750 751 752 753
                    break;
                default:
                    qCritical() << "ABORTED PARAM WRITE TO FILE, NO VALID QVARIANT TYPE" << j.value();
                    return;
                }
                in << mav->getUASID() << "\t" << compid << "\t" << j.key() << "\t" << paramValue << "\t" << paramType << "\n";
754 755 756 757 758 759 760 761 762
                in.flush();
            }
        }
    }
    file.close();
}

void QGCParamWidget::loadParameters()
{
763
    if (!mav) return;
764 765 766 767 768 769 770 771 772
    QString fileName = QFileDialog::getOpenFileName(this, tr("Load File"), ".", tr("Parameter file (*.txt)"));
    QFile file(fileName);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
        return;

    // Clear list
    clear();

    QTextStream in(&file);
773
    while (!in.atEnd()) {
774
        QString line = in.readLine();
775
        if (!line.startsWith("#")) {
776
            QStringList wpParams = line.split("\t");
777
            if (wpParams.size() == 5) {
778
                // Only load parameters for right mav
779
                if (mav->getUASID() == wpParams.at(0).toInt()) {
pixhawk's avatar
pixhawk committed
780

lm's avatar
lm committed
781 782 783
                    bool changed = false;
                    int component = wpParams.at(1).toInt();
                    QString parameterName = wpParams.at(2);
784
                    if (!parameters.contains(component) || parameters.value(component)->value(parameterName, wpParams.at(3).toDouble()-3.0f) != (float)wpParams.at(3).toDouble()) {
lm's avatar
lm committed
785 786 787 788
                        changed = true;
                    }

                    // Set parameter value
789
                    addParameter(wpParams.at(0).toInt(), wpParams.at(1).toInt(), wpParams.at(2), wpParams.at(3).toDouble());
lm's avatar
lm committed
790

791
                    if (changed) {
lm's avatar
lm committed
792
                        // Create changed values data structure if necessary
793
                        if (!changedValues.contains(wpParams.at(1).toInt())) {
794
                            changedValues.insert(wpParams.at(1).toInt(), new QMap<QString, QVariant>());
795 796
                        }

lm's avatar
lm committed
797
                        // Add to changed values
798
                        if (changedValues.value(wpParams.at(1).toInt())->contains(wpParams.at(2))) {
lm's avatar
lm committed
799 800 801
                            changedValues.value(wpParams.at(1).toInt())->remove(wpParams.at(2));
                        }

802 803
                        switch (wpParams.at(3).toUInt())
                        {
804
                        case MAV_PARAM_TYPE_REAL32:
805 806
                            changedValues.value(wpParams.at(1).toInt())->insert(wpParams.at(2), wpParams.at(3).toFloat());
                            break;
807
                        case MAV_PARAM_TYPE_UINT32:
808 809
                            changedValues.value(wpParams.at(1).toInt())->insert(wpParams.at(2), wpParams.at(3).toUInt());
                            break;
810
                        case MAV_PARAM_TYPE_INT32:
811 812 813
                            changedValues.value(wpParams.at(1).toInt())->insert(wpParams.at(2), wpParams.at(3).toInt());
                            break;
                        }
lm's avatar
lm committed
814

815
                        //qDebug() << "MARKING COMP" << wpParams.at(1).toInt() << "PARAM" << wpParams.at(2) << "VALUE" << (float)wpParams.at(3).toDouble() << "AS CHANGED";
lm's avatar
lm committed
816 817

                        // Mark in UI
pixhawk's avatar
pixhawk committed
818

pixhawk's avatar
pixhawk committed
819

820
                    }
821
                }
lm's avatar
lm committed
822 823
            }
        }
824
    }
825 826
    file.close();

lm's avatar
lm committed
827 828
}

829 830 831 832 833 834 835 836 837
/**
 * Enabling the retransmission guard enables the parameter widget to track
 * dropped parameters and to re-request them. This works for both individual
 * parameter reads as well for whole list requests.
 *
 * @param enabled True if retransmission checking should be enabled, false else
 */
void QGCParamWidget::setRetransmissionGuardEnabled(bool enabled)
{
838
    if (enabled) {
839
        retransmissionTimer.start(retransmissionTimeout);
840
    } else {
841 842 843 844 845 846
        retransmissionTimer.stop();
    }
}

void QGCParamWidget::retransmissionGuardTick()
{
847
    if (transmissionActive) {
848
        //qDebug() << __FILE__ << __LINE__ << "RETRANSMISSION GUARD ACTIVE, CHECKING FOR DROPS..";
849

lm's avatar
lm committed
850 851
        // Check for timeout
        // stop retransmission attempts on timeout
852
        if (QGC::groundTimeMilliseconds() > transmissionTimeout) {
lm's avatar
lm committed
853 854 855 856 857 858 859
            setRetransmissionGuardEnabled(false);
            transmissionActive = false;

            // Empty read retransmission list
            // Empty write retransmission list
            int missingReadCount = 0;
            QList<int> readKeys = transmissionMissingPackets.keys();
860
            foreach (int component, readKeys) {
lm's avatar
lm committed
861 862 863 864 865 866 867
                missingReadCount += transmissionMissingPackets.value(component)->count();
                transmissionMissingPackets.value(component)->clear();
            }

            // Empty write retransmission list
            int missingWriteCount = 0;
            QList<int> writeKeys = transmissionMissingWriteAckPackets.keys();
868
            foreach (int component, writeKeys) {
lm's avatar
lm committed
869 870 871 872 873 874 875 876
                missingWriteCount += transmissionMissingWriteAckPackets.value(component)->count();
                transmissionMissingWriteAckPackets.value(component)->clear();
            }
            statusLabel->setText(tr("TIMEOUT! MISSING: %1 read, %2 write.").arg(missingReadCount).arg(missingWriteCount));
        }

        // Re-request at maximum retransmissionBurstRequestSize parameters at once
        // to prevent link flooding
877
        QMap<int, QMap<QString, QVariant>*>::iterator i;
878
        for (i = parameters.begin(); i != parameters.end(); ++i) {
879 880
            // Iterate through the parameters of the component
            int component = i.key();
lm's avatar
lm committed
881
            // Request n parameters from this component (at maximum)
882
            QList<int> * paramList = transmissionMissingPackets.value(component, NULL);
883
            if (paramList) {
884
                int count = 0;
885 886
                foreach (int id, *paramList) {
                    if (count < retransmissionBurstRequestSize) {
887
                        //qDebug() << __FILE__ << __LINE__ << "RETRANSMISSION GUARD REQUESTS RETRANSMISSION OF PARAM #" << id << "FROM COMPONENT #" << component;
888
                        emit requestParameter(component, id);
lm's avatar
lm committed
889
                        statusLabel->setText(tr("Requested retransmission of #%1").arg(id+1));
890
                        count++;
891
                    } else {
892 893 894 895 896
                        break;
                    }
                }
            }
        }
lm's avatar
lm committed
897 898 899 900 901

        // Re-request at maximum retransmissionBurstRequestSize parameters at once
        // to prevent write-request link flooding
        // Empty write retransmission list
        QList<int> writeKeys = transmissionMissingWriteAckPackets.keys();
902
        foreach (int component, writeKeys) {
lm's avatar
lm committed
903
            int count = 0;
904
            QMap <QString, QVariant>* missingParams = transmissionMissingWriteAckPackets.value(component);
905 906
            foreach (QString key, missingParams->keys()) {
                if (count < retransmissionBurstRequestSize) {
lm's avatar
lm committed
907
                    // Re-request write operation
908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929
                    QVariant value = missingParams->value(key);
                    switch (parameters.value(component)->value(key).type())
                    {
                    case QVariant::Int:
                        {
                            QVariant fixedValue(value.toInt());
                            emit parameterChanged(component, key, fixedValue);
                        }
                        break;
                    case QVariant::UInt:
                        {
                            QVariant fixedValue(value.toUInt());
                            emit parameterChanged(component, key, fixedValue);
                        }
                        break;
                    case QMetaType::Float:
                        {
                            QVariant fixedValue(value.toFloat());
                            emit parameterChanged(component, key, fixedValue);
                        }
                        break;
                    default:
930
                        //qCritical() << "ABORTED PARAM RETRANSMISSION, NO VALID QVARIANT TYPE";
931 932
                        return;
                    }
933
                    statusLabel->setText(tr("Requested rewrite of: %1: %2").arg(key).arg(missingParams->value(key).toDouble()));
lm's avatar
lm committed
934
                    count++;
935
                } else {
lm's avatar
lm committed
936 937 938 939
                    break;
                }
            }
        }
940
    } else {
941
        //qDebug() << __FILE__ << __LINE__ << "STOPPING RETRANSMISSION GUARD GRACEFULLY";
942 943 944 945
        setRetransmissionGuardEnabled(false);
    }
}

946

947 948 949 950 951
/**
 * The .. signal is emitted
 */
void QGCParamWidget::requestParameterUpdate(int component, const QString& parameter)
{
952
    if (mav) mav->requestParameter(component, parameter);
953 954 955
}


956 957 958 959 960
/**
 * @param component the subsystem which has the parameter
 * @param parameterName name of the parameter, as delivered by the system
 * @param value value of the parameter
 */
961
void QGCParamWidget::setParameter(int component, QString parameterName, QVariant value)
962
{
963 964 965 966 967 968 969 970 971 972
    if (paramMin.contains(parameterName) && value.toDouble() < paramMin.value(parameterName))
    {
        statusLabel->setText(tr("REJ. %1 < min").arg(value.toDouble()));
        return;
    }
    if (paramMax.contains(parameterName) && value.toDouble() > paramMax.value(parameterName))
    {
        statusLabel->setText(tr("REJ. %1 > max").arg(value.toDouble()));
        return;
    }
973 974 975 976 977 978 979

    switch (parameters.value(component)->value(parameterName).type())
    {
    case QVariant::Int:
        {
            QVariant fixedValue(value.toInt());
            emit parameterChanged(component, parameterName, fixedValue);
980
            //qDebug() << "PARAM WIDGET SENT:" << fixedValue;
981 982 983 984 985 986
        }
        break;
    case QVariant::UInt:
        {
            QVariant fixedValue(value.toUInt());
            emit parameterChanged(component, parameterName, fixedValue);
987
            //qDebug() << "PARAM WIDGET SENT:" << fixedValue;
988 989 990 991 992 993
        }
        break;
    case QMetaType::Float:
        {
            QVariant fixedValue(value.toFloat());
            emit parameterChanged(component, parameterName, fixedValue);
994
            //qDebug() << "PARAM WIDGET SENT:" << fixedValue;
995 996 997 998 999 1000 1001
        }
        break;
    default:
        qCritical() << "ABORTED PARAM SEND, NO VALID QVARIANT TYPE";
        return;
    }

lm's avatar
lm committed
1002 1003
    // Wait for parameter to be written back
    // mark it therefore as missing
1004 1005 1006
    if (!transmissionMissingWriteAckPackets.contains(component))
    {
        transmissionMissingWriteAckPackets.insert(component, new QMap<QString, QVariant>());
lm's avatar
lm committed
1007 1008 1009 1010 1011 1012 1013 1014
    }

    // Insert it in missing write ACK list
    transmissionMissingWriteAckPackets.value(component)->insert(parameterName, value);

    // Set timeouts
    transmissionActive = true;
    quint64 newTransmissionTimeout = QGC::groundTimeMilliseconds() + 5*rewriteTimeout;
1015
    if (newTransmissionTimeout > transmissionTimeout) {
lm's avatar
lm committed
1016 1017 1018 1019
        transmissionTimeout = newTransmissionTimeout;
    }
    // Enable guard / reset timeouts
    setRetransmissionGuardEnabled(true);
1020 1021
}

1022 1023 1024
/**
 * Set all parameter in the parameter tree on the MAV
 */
1025 1026
void QGCParamWidget::setParameters()
{
1027
    // Iterate through all components, through all parameters and emit them
1028
    int parametersSent = 0;
1029
    QMap<int, QMap<QString, QVariant>*>::iterator i;
1030
    for (i = changedValues.begin(); i != changedValues.end(); ++i) {
1031 1032
        // Iterate through the parameters of the component
        int compid = i.key();
1033
        QMap<QString, QVariant>* comp = i.value();
1034
        {
1035
            QMap<QString, QVariant>::iterator j;
1036
            for (j = comp->begin(); j != comp->end(); ++j) {
1037
                setParameter(compid, j.key(), j.value());
1038
                parametersSent++;
1039 1040 1041 1042
            }
        }
    }

lm's avatar
lm committed
1043
    // Change transmission status if necessary
1044
    if (parametersSent == 0) {
1045
        statusLabel->setText(tr("No transmission: No changed values."));
1046
    } else {
1047
        statusLabel->setText(tr("Transmitting %1 parameters.").arg(parametersSent));
lm's avatar
lm committed
1048 1049 1050
        // Set timeouts
        transmissionActive = true;
        quint64 newTransmissionTimeout = QGC::groundTimeMilliseconds() + (parametersSent/retransmissionBurstRequestSize+5)*rewriteTimeout;
1051
        if (newTransmissionTimeout > transmissionTimeout) {
lm's avatar
lm committed
1052 1053 1054 1055
            transmissionTimeout = newTransmissionTimeout;
        }
        // Enable guard
        setRetransmissionGuardEnabled(true);
1056
    }
1057 1058
}

1059 1060 1061 1062
/**
 * Write the current onboard parameters from RAM into
 * permanent storage, e.g. EEPROM or harddisk
 */
1063 1064
void QGCParamWidget::writeParameters()
{
1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091
    int changedParamCount = 0;

    QMap<int, QMap<QString, QVariant>*>::iterator i;
    for (i = changedValues.begin(); i != changedValues.end(); ++i)
    {
        // Iterate through the parameters of the component
        QMap<QString, QVariant>* comp = i.value();
        {
            QMap<QString, QVariant>::iterator j;
            for (j = comp->begin(); j != comp->end(); ++j)
            {
                changedParamCount++;
            }
        }
    }

    if (changedParamCount > 0)
    {
        QMessageBox msgBox;
        msgBox.setText(tr("There are locally changed parameters. Please transmit them first (<TRANSMIT>) or update them with the onboard values (<REFRESH>) before storing onboard from RAM to ROM."));
        msgBox.exec();
    }
    else
    {
        if (!mav) return;
        mav->writeParametersToStorage();
    }
1092 1093 1094 1095
}

void QGCParamWidget::readParameters()
{
1096
    if (!mav) return;
1097
    mav->readParametersFromStorage();
1098 1099
}

1100 1101 1102
/**
 * Clear all data in the parameter widget
 */
pixhawk's avatar
pixhawk committed
1103 1104 1105
void QGCParamWidget::clear()
{
    tree->clear();
lm's avatar
lm committed
1106
    components->clear();
pixhawk's avatar
pixhawk committed
1107
}