QGCParamWidget.cc 40.1 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 <QSettings>
35
#include <QMessageBox>
LM's avatar
LM committed
36
#include <QApplication>
pixhawk's avatar
pixhawk committed
37 38 39 40

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

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

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

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

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

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

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


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

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

94
    QPushButton* writeButton = new QPushButton(tr("Write (ROM)"));
95 96
    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."));
97
    connect(writeButton, SIGNAL(clicked()), this, SLOT(writeParameters()));
lm's avatar
lm committed
98
    horizontalLayout->addWidget(writeButton, 2, 2);
99

100
    QPushButton* loadFileButton = new QPushButton(tr("Load File"));
101 102
    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."));
103
    connect(loadFileButton, SIGNAL(clicked()), this, SLOT(loadParameters()));
lm's avatar
lm committed
104
    horizontalLayout->addWidget(loadFileButton, 3, 0);
105 106

    QPushButton* saveFileButton = new QPushButton(tr("Save File"));
107 108
    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."));
109
    connect(saveFileButton, SIGNAL(clicked()), this, SLOT(saveParameters()));
lm's avatar
lm committed
110 111 112 113 114 115 116
    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);
117

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

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

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

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

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

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

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

151 152 153 154 155 156 157 158 159 160 161 162
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
163 164
void QGCParamWidget::loadParameterInfoCSV(const QString& autopilot, const QString& airframe)
{
LM's avatar
LM committed
165
    QDir appDir = QApplication::applicationDirPath();
166
    appDir.cd("files");
LM's avatar
LM committed
167 168
    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
169 170 171 172

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

    // Extract header

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

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

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

lm's avatar
lm committed
194 195 196 197 198 199
    bool charRead = false;
    QString separator = "";
    QList<QChar> sepCandidates;
    sepCandidates << '\t';
    sepCandidates << ',';
    sepCandidates << ';';
200
    //sepCandidates << ' ';
lm's avatar
lm committed
201 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
    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;
        }
    }

227 228 229 230 231 232 233 234 235
    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
236 237 238 239 240 241 242 243 244 245 246
    QString out = separator;
    out.replace("\t", "<tab>");
    qDebug() << " Separator: \"" << out << "\"";
    //qDebug() << "READING CSV:" << header;


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

247 248 249 250 251 252 253 254
        //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
255 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
        // 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());
        }
    }
}

287 288 289 290
/**
 * @return The MAV of this widget. Unless the MAV object has been destroyed, this
 *         pointer is never zero.
 */
pixhawk's avatar
pixhawk committed
291 292 293 294 295 296 297 298 299 300 301
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
 */
302
void QGCParamWidget::addComponent(int uas, int component, QString componentName)
pixhawk's avatar
pixhawk committed
303
{
304
    Q_UNUSED(uas);
305
    if (components->contains(component)) {
pixhawk's avatar
pixhawk committed
306
        // Update existing
307 308 309
        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);
310
    } else {
pixhawk's avatar
pixhawk committed
311
        // Add new
lm's avatar
lm committed
312
        QStringList list(QString("%1 (#%2)").arg(componentName).arg(component));
pixhawk's avatar
pixhawk committed
313
        QTreeWidgetItem* comp = new QTreeWidgetItem(list);
lm's avatar
lm committed
314
        comp->setFirstColumnSpanned(true);
pixhawk's avatar
pixhawk committed
315 316 317
        components->insert(component, comp);
        // Create grouping and update maps
        paramGroups.insert(component, new QMap<QString, QTreeWidgetItem*>());
pixhawk's avatar
pixhawk committed
318 319
        tree->addTopLevelItem(comp);
        tree->update();
320
        // Create map in parameters
321
        if (!parameters.contains(component)) {
322
            parameters.insert(component, new QMap<QString, QVariant>());
323 324
        }
        // Create map in changed parameters
325
        if (!changedValues.contains(component)) {
326
            changedValues.insert(component, new QMap<QString, QVariant>());
327
        }
pixhawk's avatar
pixhawk committed
328 329 330
    }
}

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

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

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

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

lm's avatar
lm committed
363 364 365 366
            // 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
367 368
            if (thisTransmissionTimeout > transmissionTimeout)
            {
lm's avatar
lm committed
369 370
                transmissionTimeout = thisTransmissionTimeout;
            }
lm's avatar
lm committed
371
        }
lm's avatar
lm committed
372 373 374 375

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

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

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

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

lm's avatar
lm committed
410 411
    if (justWritten && !writeMismatch && missWriteCount == 0)
    {
lm's avatar
lm committed
412 413 414 415 416
        // 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
417 418
    } else if (justWritten && !writeMismatch)
    {
419
        statusLabel->setText(tr("SUCCESS: Wrote %2 (#%1/%4): %3").arg(paramId+1).arg(parameterName).arg(value.toDouble()).arg(paramCount));
lm's avatar
lm committed
420 421 422
        QPalette pal = statusLabel->palette();
        pal.setColor(backgroundRole(), QGC::colorGreen);
        statusLabel->setPalette(pal);
lm's avatar
lm committed
423 424
    } else if (justWritten && writeMismatch)
    {
lm's avatar
lm committed
425
        // Mismatch, tell user
lm's avatar
lm committed
426 427 428
        QPalette pal = statusLabel->palette();
        pal.setColor(backgroundRole(), QGC::colorRed);
        statusLabel->setPalette(pal);
429
        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
430 431 432 433 434
    }
    else
    {
        if (missCount > 0)
        {
lm's avatar
lm committed
435 436 437
            QPalette pal = statusLabel->palette();
            pal.setColor(backgroundRole(), QGC::colorOrange);
            statusLabel->setPalette(pal);
lm's avatar
lm committed
438 439 440
        }
        else
        {
lm's avatar
lm committed
441 442 443 444
            QPalette pal = statusLabel->palette();
            pal.setColor(backgroundRole(), QGC::colorGreen);
            statusLabel->setPalette(pal);
        }
445 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));
        statusLabel->setText(tr("OK: %1 %2 (%3/%4)").arg(parameterName).arg(val).arg(paramCount-missCount).arg(paramCount));
lm's avatar
lm committed
448
    }
lm's avatar
lm committed
449 450

    // Check if last parameter was received
lm's avatar
lm committed
451 452
    if (missCount == 0 && missWriteCount == 0)
    {
lm's avatar
lm committed
453 454 455
        this->transmissionActive = false;
        this->transmissionListMode = false;
        transmissionListSizeKnown.clear();
lm's avatar
lm committed
456 457
        foreach (int key, transmissionMissingPackets.keys())
        {
lm's avatar
lm committed
458 459
            transmissionMissingPackets.value(key)->clear();
        }
460 461 462

        // Expand visual tree
        tree->expandItem(tree->topLevelItem(0));
lm's avatar
lm committed
463 464 465
    }
}

466 467 468 469 470
/**
 * @param uas System which has the component
 * @param component id of the component
 * @param parameterName human friendly name of the parameter
 */
471
void QGCParamWidget::addParameter(int uas, int component, QString parameterName, QVariant value)
pixhawk's avatar
pixhawk committed
472
{
473
    qDebug() << "PARAM WIDGET GOT PARAM:" << value;
474
    Q_UNUSED(uas);
475
    // Reference to item in tree
476
    QTreeWidgetItem* parameterItem = NULL;
pixhawk's avatar
pixhawk committed
477 478

    // Get component
lm's avatar
lm committed
479 480
    if (!components->contains(component))
    {
481 482 483 484 485 486 487 488 489 490 491 492 493
//        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;
//        }
494
        QString componentName = tr("Component #%1").arg(component);
495
        addComponent(uas, component, componentName);
pixhawk's avatar
pixhawk committed
496
    }
497

498 499 500 501 502 503 504
    // 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
505 506
    QString splitToken = "_";
    // Check if auto-grouping can work
lm's avatar
lm committed
507 508
    if (parameterName.contains(splitToken))
    {
pixhawk's avatar
pixhawk committed
509 510
        QString parent = parameterName.section(splitToken, 0, 0, QString::SectionSkipEmpty);
        QMap<QString, QTreeWidgetItem*>* compParamGroups = paramGroups.value(component);
511 512
        if (!compParamGroups->contains(parent))
        {
pixhawk's avatar
pixhawk committed
513 514 515 516 517 518
            // Insert group item
            QStringList glist;
            glist.append(parent);
            QTreeWidgetItem* item = new QTreeWidgetItem(glist);
            compParamGroups->insert(parent, item);
            components->value(component)->addChild(item);
519
        }
520 521 522 523

        // Append child to group
        bool found = false;
        QTreeWidgetItem* parentItem = compParamGroups->value(parent);
524
        for (int i = 0; i < parentItem->childCount(); i++) {
525 526
            QTreeWidgetItem* child = parentItem->child(i);
            QString key = child->data(0, Qt::DisplayRole).toString();
527 528
            if (key == parameterName)
            {
529 530 531 532 533 534 535
                //qDebug() << "UPDATED CHILD";
                parameterItem = child;
                parameterItem->setData(1, Qt::DisplayRole, value);
                found = true;
            }
        }

lm's avatar
lm committed
536 537
        if (!found)
        {
538 539 540
            // Insert parameter into map
            QStringList plist;
            plist.append(parameterName);
541
            // CREATE PARAMETER ITEM
542
            parameterItem = new QTreeWidgetItem(plist);
543
            // CONFIGURE PARAMETER ITEM
544
            parameterItem->setData(1, Qt::DisplayRole, value);
545 546

            compParamGroups->value(parent)->addChild(parameterItem);
lm's avatar
lm committed
547
            parameterItem->setFlags(parameterItem->flags() | Qt::ItemIsEditable);
548
        }
lm's avatar
lm committed
549 550 551
    }
    else
    {
pixhawk's avatar
pixhawk committed
552 553
        bool found = false;
        QTreeWidgetItem* parent = components->value(component);
lm's avatar
lm committed
554 555
        for (int i = 0; i < parent->childCount(); i++)
        {
pixhawk's avatar
pixhawk committed
556 557
            QTreeWidgetItem* child = parent->child(i);
            QString key = child->data(0, Qt::DisplayRole).toString();
lm's avatar
lm committed
558 559
            if (key == parameterName)
            {
pixhawk's avatar
pixhawk committed
560
                //qDebug() << "UPDATED CHILD";
561 562
                parameterItem = child;
                parameterItem->setData(1, Qt::DisplayRole, value);
pixhawk's avatar
pixhawk committed
563 564 565 566
                found = true;
            }
        }

lm's avatar
lm committed
567 568
        if (!found)
        {
569 570 571
            // Insert parameter into map
            QStringList plist;
            plist.append(parameterName);
572
            // CREATE PARAMETER ITEM
573
            parameterItem = new QTreeWidgetItem(plist);
574
            // CONFIGURE PARAMETER ITEM
575
            parameterItem->setData(1, Qt::DisplayRole, value);
576

lm's avatar
lm committed
577 578
            components->value(component)->addChild(parameterItem);
            parameterItem->setFlags(parameterItem->flags() | Qt::ItemIsEditable);
pixhawk's avatar
pixhawk committed
579
        }
580
        //tree->expandAll();
581
    }
582
    // Reset background color
583
    parameterItem->setBackground(0, Qt::NoBrush);
584
    parameterItem->setBackground(1, Qt::NoBrush);
lm's avatar
lm committed
585
    // Add tooltip
586 587 588 589
    QString tooltipFormat;
    if (paramDefault.contains(parameterName))
    {
        tooltipFormat = tr("Default: %1, %2");
LM's avatar
LM committed
590
        tooltipFormat = tooltipFormat.arg(paramDefault.value(parameterName, 0.0f)).arg(paramToolTips.value(parameterName, ""));
591 592 593 594 595 596 597
    }
    else
    {
        tooltipFormat = paramToolTips.value(parameterName, "");
    }
    parameterItem->setToolTip(0, tooltipFormat);
    parameterItem->setToolTip(1, tooltipFormat);
lm's avatar
lm committed
598

599
    //tree->update();
pixhawk's avatar
pixhawk committed
600
    if (changedValues.contains(component)) changedValues.value(component)->remove(parameterName);
pixhawk's avatar
pixhawk committed
601 602
}

603 604 605 606
/**
 * Send a request to deliver the list of onboard parameters
 * to the MAV.
 */
607 608
void QGCParamWidget::requestParameterList()
{
609
    if (!mav) return;
610 611 612 613 614 615 616
    // 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
617
    // Clear view and request param list
618
    clear();
619
    parameters.clear();
lm's avatar
lm committed
620
    received.clear();
lm's avatar
lm committed
621 622 623
    // Clear transmission state
    transmissionListMode = true;
    transmissionListSizeKnown.clear();
Lorenz Meier's avatar
Lorenz Meier committed
624 625
    foreach (int key, transmissionMissingPackets.keys())
    {
lm's avatar
lm committed
626 627 628
        transmissionMissingPackets.value(key)->clear();
    }
    transmissionActive = true;
lm's avatar
lm committed
629 630 631 632 633

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

    mav->requestParameters();
634 635
}

636
void QGCParamWidget::parameterItemChanged(QTreeWidgetItem* current, int column)
lm's avatar
lm committed
637
{
638
    if (current && column > 0) {
639
        QTreeWidgetItem* parent = current->parent();
640
        while (parent->parent() != NULL) {
641 642 643 644
            parent = parent->parent();
        }
        // Parent is now top-level component
        int key = components->key(parent);
645
        if (!changedValues.contains(key)) {
646
            changedValues.insert(key, new QMap<QString, QVariant>());
647
        }
648
        QMap<QString, QVariant>* map = changedValues.value(key, NULL);
649
        if (map) {
650
            QString str = current->data(0, Qt::DisplayRole).toString();
651
            QVariant value = current->data(1, Qt::DisplayRole);
lm's avatar
lm committed
652
            // Set parameter on changed list to be transmitted to MAV
653 654 655 656
            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(' ')));
657 658 659 660 661 662 663 664 665 666
            //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)));
            }
667

668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685
            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);
686
                }
687 688 689 690
                break;
            default:
                qCritical() << "ABORTED PARAM UPDATE, NO VALID QVARIANT TYPE";
                return;
691 692 693 694 695 696
            }
        }
    }
}

void QGCParamWidget::saveParameters()
697
{
698
    if (!mav) return;
699 700
    QString fileName = QFileDialog::getSaveFileName(this, tr("Save File"), "./parameters.txt", tr("Parameter File (*.txt)"));
    QFile file(fileName);
701
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
702 703 704 705 706 707 708 709 710 711
        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
712
    QMap<int, QMap<QString, QVariant>*>::iterator i;
713
    for (i = parameters.begin(); i != parameters.end(); ++i) {
714 715
        // Iterate through the parameters of the component
        int compid = i.key();
716
        QMap<QString, QVariant>* comp = i.value();
717
        {
718 719 720
            QMap<QString, QVariant>::iterator j;
            for (j = comp->begin(); j != comp->end(); ++j)
            {
721
                QString paramValue("%1");
722 723 724 725 726
                QString paramType("%1");
                switch (j.value().type())
                {
                case QVariant::Int:
                    paramValue = paramValue.arg(j.value().toInt());
727
                    paramType = paramType.arg(MAV_PARAM_TYPE_INT32);
728 729 730
                    break;
                case QVariant::UInt:
                    paramValue = paramValue.arg(j.value().toUInt());
731
                    paramType = paramType.arg(MAV_PARAM_TYPE_UINT32);
732 733 734
                    break;
                case QMetaType::Float:
                    paramValue = paramValue.arg(j.value().toDouble(), 25, 'g', 12);
735
                    paramType = paramType.arg(MAV_PARAM_TYPE_REAL32);
736 737 738 739 740 741
                    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";
742 743 744 745 746 747 748 749 750
                in.flush();
            }
        }
    }
    file.close();
}

void QGCParamWidget::loadParameters()
{
751
    if (!mav) return;
752 753 754 755 756 757 758 759 760
    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);
761
    while (!in.atEnd()) {
762
        QString line = in.readLine();
763
        if (!line.startsWith("#")) {
764
            QStringList wpParams = line.split("\t");
765
            if (wpParams.size() == 5) {
766
                // Only load parameters for right mav
767
                if (mav->getUASID() == wpParams.at(0).toInt()) {
pixhawk's avatar
pixhawk committed
768

lm's avatar
lm committed
769 770 771
                    bool changed = false;
                    int component = wpParams.at(1).toInt();
                    QString parameterName = wpParams.at(2);
772
                    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
773 774 775 776
                        changed = true;
                    }

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

779
                    if (changed) {
lm's avatar
lm committed
780
                        // Create changed values data structure if necessary
781
                        if (!changedValues.contains(wpParams.at(1).toInt())) {
782
                            changedValues.insert(wpParams.at(1).toInt(), new QMap<QString, QVariant>());
783 784
                        }

lm's avatar
lm committed
785
                        // Add to changed values
786
                        if (changedValues.value(wpParams.at(1).toInt())->contains(wpParams.at(2))) {
lm's avatar
lm committed
787 788 789
                            changedValues.value(wpParams.at(1).toInt())->remove(wpParams.at(2));
                        }

790 791
                        switch (wpParams.at(3).toUInt())
                        {
792
                        case MAV_PARAM_TYPE_REAL32:
793 794
                            changedValues.value(wpParams.at(1).toInt())->insert(wpParams.at(2), wpParams.at(3).toFloat());
                            break;
795
                        case MAV_PARAM_TYPE_UINT32:
796 797
                            changedValues.value(wpParams.at(1).toInt())->insert(wpParams.at(2), wpParams.at(3).toUInt());
                            break;
798
                        case MAV_PARAM_TYPE_INT32:
799 800 801
                            changedValues.value(wpParams.at(1).toInt())->insert(wpParams.at(2), wpParams.at(3).toInt());
                            break;
                        }
lm's avatar
lm committed
802

803
                        qDebug() << "MARKING COMP" << wpParams.at(1).toInt() << "PARAM" << wpParams.at(2) << "VALUE" << (float)wpParams.at(3).toDouble() << "AS CHANGED";
lm's avatar
lm committed
804 805

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

pixhawk's avatar
pixhawk committed
807

808
                    }
809
                }
lm's avatar
lm committed
810 811
            }
        }
812
    }
813 814
    file.close();

lm's avatar
lm committed
815 816
}

817 818 819 820 821 822 823 824 825
/**
 * 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)
{
826
    if (enabled) {
827
        retransmissionTimer.start(retransmissionTimeout);
828
    } else {
829 830 831 832 833 834
        retransmissionTimer.stop();
    }
}

void QGCParamWidget::retransmissionGuardTick()
{
835
    if (transmissionActive) {
836 837
        qDebug() << __FILE__ << __LINE__ << "RETRANSMISSION GUARD ACTIVE, CHECKING FOR DROPS..";

lm's avatar
lm committed
838 839
        // Check for timeout
        // stop retransmission attempts on timeout
840
        if (QGC::groundTimeMilliseconds() > transmissionTimeout) {
lm's avatar
lm committed
841 842 843 844 845 846 847
            setRetransmissionGuardEnabled(false);
            transmissionActive = false;

            // Empty read retransmission list
            // Empty write retransmission list
            int missingReadCount = 0;
            QList<int> readKeys = transmissionMissingPackets.keys();
848
            foreach (int component, readKeys) {
lm's avatar
lm committed
849 850 851 852 853 854 855
                missingReadCount += transmissionMissingPackets.value(component)->count();
                transmissionMissingPackets.value(component)->clear();
            }

            // Empty write retransmission list
            int missingWriteCount = 0;
            QList<int> writeKeys = transmissionMissingWriteAckPackets.keys();
856
            foreach (int component, writeKeys) {
lm's avatar
lm committed
857 858 859 860 861 862 863 864
                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
865
        QMap<int, QMap<QString, QVariant>*>::iterator i;
866
        for (i = parameters.begin(); i != parameters.end(); ++i) {
867 868
            // Iterate through the parameters of the component
            int component = i.key();
lm's avatar
lm committed
869
            // Request n parameters from this component (at maximum)
870
            QList<int> * paramList = transmissionMissingPackets.value(component, NULL);
871
            if (paramList) {
872
                int count = 0;
873 874
                foreach (int id, *paramList) {
                    if (count < retransmissionBurstRequestSize) {
875 876
                        qDebug() << __FILE__ << __LINE__ << "RETRANSMISSION GUARD REQUESTS RETRANSMISSION OF PARAM #" << id << "FROM COMPONENT #" << component;
                        emit requestParameter(component, id);
lm's avatar
lm committed
877
                        statusLabel->setText(tr("Requested retransmission of #%1").arg(id+1));
878
                        count++;
879
                    } else {
880 881 882 883 884
                        break;
                    }
                }
            }
        }
lm's avatar
lm committed
885 886 887 888 889

        // Re-request at maximum retransmissionBurstRequestSize parameters at once
        // to prevent write-request link flooding
        // Empty write retransmission list
        QList<int> writeKeys = transmissionMissingWriteAckPackets.keys();
890
        foreach (int component, writeKeys) {
lm's avatar
lm committed
891
            int count = 0;
892
            QMap <QString, QVariant>* missingParams = transmissionMissingWriteAckPackets.value(component);
893 894
            foreach (QString key, missingParams->keys()) {
                if (count < retransmissionBurstRequestSize) {
lm's avatar
lm committed
895
                    // Re-request write operation
896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920
                    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:
                        qCritical() << "ABORTED PARAM RETRANSMISSION, NO VALID QVARIANT TYPE";
                        return;
                    }
921
                    statusLabel->setText(tr("Requested rewrite of: %1: %2").arg(key).arg(missingParams->value(key).toDouble()));
lm's avatar
lm committed
922
                    count++;
923
                } else {
lm's avatar
lm committed
924 925 926 927
                    break;
                }
            }
        }
928
    } else {
929 930 931 932 933
        qDebug() << __FILE__ << __LINE__ << "STOPPING RETRANSMISSION GUARD GRACEFULLY";
        setRetransmissionGuardEnabled(false);
    }
}

934

935 936 937 938 939
/**
 * The .. signal is emitted
 */
void QGCParamWidget::requestParameterUpdate(int component, const QString& parameter)
{
940
    if (mav) mav->requestParameter(component, parameter);
941 942 943
}


944 945 946 947 948
/**
 * @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
 */
949
void QGCParamWidget::setParameter(int component, QString parameterName, QVariant value)
950
{
951 952 953 954 955 956 957 958 959 960
    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;
    }
961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989

    switch (parameters.value(component)->value(parameterName).type())
    {
    case QVariant::Int:
        {
            QVariant fixedValue(value.toInt());
            emit parameterChanged(component, parameterName, fixedValue);
            qDebug() << "PARAM WIDGET SENT:" << fixedValue;
        }
        break;
    case QVariant::UInt:
        {
            QVariant fixedValue(value.toUInt());
            emit parameterChanged(component, parameterName, fixedValue);
            qDebug() << "PARAM WIDGET SENT:" << fixedValue;
        }
        break;
    case QMetaType::Float:
        {
            QVariant fixedValue(value.toFloat());
            emit parameterChanged(component, parameterName, fixedValue);
            qDebug() << "PARAM WIDGET SENT:" << fixedValue;
        }
        break;
    default:
        qCritical() << "ABORTED PARAM SEND, NO VALID QVARIANT TYPE";
        return;
    }

lm's avatar
lm committed
990 991
    // Wait for parameter to be written back
    // mark it therefore as missing
992 993 994
    if (!transmissionMissingWriteAckPackets.contains(component))
    {
        transmissionMissingWriteAckPackets.insert(component, new QMap<QString, QVariant>());
lm's avatar
lm committed
995 996 997 998 999 1000 1001 1002
    }

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

    // Set timeouts
    transmissionActive = true;
    quint64 newTransmissionTimeout = QGC::groundTimeMilliseconds() + 5*rewriteTimeout;
1003
    if (newTransmissionTimeout > transmissionTimeout) {
lm's avatar
lm committed
1004 1005 1006 1007
        transmissionTimeout = newTransmissionTimeout;
    }
    // Enable guard / reset timeouts
    setRetransmissionGuardEnabled(true);
1008 1009
}

1010 1011 1012
/**
 * Set all parameter in the parameter tree on the MAV
 */
1013 1014
void QGCParamWidget::setParameters()
{
1015
    // Iterate through all components, through all parameters and emit them
1016
    int parametersSent = 0;
1017
    QMap<int, QMap<QString, QVariant>*>::iterator i;
1018
    for (i = changedValues.begin(); i != changedValues.end(); ++i) {
1019 1020
        // Iterate through the parameters of the component
        int compid = i.key();
1021
        QMap<QString, QVariant>* comp = i.value();
1022
        {
1023
            QMap<QString, QVariant>::iterator j;
1024
            for (j = comp->begin(); j != comp->end(); ++j) {
1025
                setParameter(compid, j.key(), j.value());
1026
                parametersSent++;
1027 1028 1029 1030
            }
        }
    }

lm's avatar
lm committed
1031
    // Change transmission status if necessary
1032
    if (parametersSent == 0) {
1033
        statusLabel->setText(tr("No transmission: No changed values."));
1034
    } else {
1035
        statusLabel->setText(tr("Transmitting %1 parameters.").arg(parametersSent));
lm's avatar
lm committed
1036 1037 1038
        // Set timeouts
        transmissionActive = true;
        quint64 newTransmissionTimeout = QGC::groundTimeMilliseconds() + (parametersSent/retransmissionBurstRequestSize+5)*rewriteTimeout;
1039
        if (newTransmissionTimeout > transmissionTimeout) {
lm's avatar
lm committed
1040 1041 1042 1043
            transmissionTimeout = newTransmissionTimeout;
        }
        // Enable guard
        setRetransmissionGuardEnabled(true);
1044
    }
1045 1046
}

1047 1048 1049 1050
/**
 * Write the current onboard parameters from RAM into
 * permanent storage, e.g. EEPROM or harddisk
 */
1051 1052
void QGCParamWidget::writeParameters()
{
1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079
    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();
    }
1080 1081 1082 1083
}

void QGCParamWidget::readParameters()
{
1084
    if (!mav) return;
1085
    mav->readParametersFromStorage();
1086 1087
}

1088 1089 1090
/**
 * Clear all data in the parameter widget
 */
pixhawk's avatar
pixhawk committed
1091 1092 1093
void QGCParamWidget::clear()
{
    tree->clear();
lm's avatar
lm committed
1094
    components->clear();
pixhawk's avatar
pixhawk committed
1095
}