QGCParamWidget.cc 39.2 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>
pixhawk's avatar
pixhawk committed
36 37 38 39

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

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

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

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

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

lm's avatar
lm committed
70
    // Parameter tree
71
    horizontalLayout->addWidget(tree, 0, 0, 1, 3);
lm's avatar
lm committed
72 73

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


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

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

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

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

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

115
    // Set layout
pixhawk's avatar
pixhawk committed
116 117 118 119 120 121 122 123
    this->setLayout(horizontalLayout);

    // Set header
    QStringList headerItems;
    headerItems.append("Parameter");
    headerItems.append("Value");
    tree->setHeaderLabels(headerItems);
    tree->setColumnCount(2);
124
    tree->setExpandsOnDoubleClick(true);
125 126

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

130
    // New parameters from UAS
131
    connect(uas, SIGNAL(parameterChanged(int,int,int,int,QString,QVariant)), this, SLOT(addParameter(int,int,int,int,QString,QVariant)));
132 133 134 135

    // Connect retransmission guard
    connect(this, SIGNAL(requestParameter(int,int)), uas, SLOT(requestParameter(int,int)));
    connect(&retransmissionTimer, SIGNAL(timeout()), this, SLOT(retransmissionGuardTick()));
pixhawk's avatar
pixhawk committed
136 137
}

138 139 140 141 142 143 144 145 146 147 148 149
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
150 151 152
void QGCParamWidget::loadParameterInfoCSV(const QString& autopilot, const QString& airframe)
{
    QDir appDir = QDir::current();
153 154
    appDir.cd("files");
    QString fileName = QString("/%1/%2/parameter_tooltips/tooltips.txt").arg(autopilot.toLower()).arg(airframe.toLower());
lm's avatar
lm committed
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
    QString filePath = appDir.filePath(fileName);
    QFile paramMetaFile(filePath);

    // Load CSV data
    if (!paramMetaFile.open(QIODevice::ReadOnly | QIODevice::Text))
    {
        qDebug() << "COULD NOT OPEN PARAM META INFO FILE:" << filePath;
        return;
    }

    // Extract header

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

    // First line is header
172 173
    // there might be more lines, but the first
    // line is assumed to be at least header
lm's avatar
lm committed
174 175
    QString header = in.readLine();

176 177 178 179 180 181
    // Ignore top-level comment lines
    while (header.startsWith('#') || header.startsWith('/') || header.startsWith('='))
    {
        header = in.readLine();
    }

lm's avatar
lm committed
182 183 184 185 186 187
    bool charRead = false;
    QString separator = "";
    QList<QChar> sepCandidates;
    sepCandidates << '\t';
    sepCandidates << ',';
    sepCandidates << ';';
188
    //sepCandidates << ' ';
lm's avatar
lm committed
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
    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;
        }
    }

215 216 217 218 219 220 221 222 223
    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
224 225 226 227 228 229 230 231 232 233 234
    QString out = separator;
    out.replace("\t", "<tab>");
    qDebug() << " Separator: \"" << out << "\"";
    //qDebug() << "READING CSV:" << header;


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

235 236 237 238 239 240 241 242
        //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
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274
        // 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());
        }
    }
}

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

lm's avatar
lm committed
319 320 321 322 323
/**
 * @param uas System which has the component
 * @param component id of the component
 * @param parameterName human friendly name of the parameter
 */
324
void QGCParamWidget::addParameter(int uas, int component, int paramCount, int paramId, QString parameterName, QVariant value)
lm's avatar
lm committed
325 326 327
{
    addParameter(uas, component, parameterName, value);

lm's avatar
lm committed
328
    // Missing packets list has to be instantiated for all components
329
    if (!transmissionMissingPackets.contains(component)) {
lm's avatar
lm committed
330 331 332
        transmissionMissingPackets.insert(component, new QList<int>());
    }

lm's avatar
lm committed
333
    // List mode is different from single parameter transfers
334
    if (transmissionListMode) {
lm's avatar
lm committed
335 336
        // Only accept the list size once on the first packet from
        // each component
lm's avatar
lm committed
337 338
        if (!transmissionListSizeKnown.contains(component))
        {
lm's avatar
lm committed
339 340
            // Mark list size as known
            transmissionListSizeKnown.insert(component, true);
lm's avatar
lm committed
341

342
            // Mark all parameters as missing
lm's avatar
lm committed
343 344 345 346
            for (int i = 0; i < paramCount; ++i)
            {
                if (!transmissionMissingPackets.value(component)->contains(i))
                {
lm's avatar
lm committed
347 348 349
                    transmissionMissingPackets.value(component)->append(i);
                }
            }
350

lm's avatar
lm committed
351 352 353 354
            // 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
355 356
            if (thisTransmissionTimeout > transmissionTimeout)
            {
lm's avatar
lm committed
357 358
                transmissionTimeout = thisTransmissionTimeout;
            }
lm's avatar
lm committed
359
        }
lm's avatar
lm committed
360 361 362 363

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

lm's avatar
lm committed
366
    // Mark this parameter as received in read list
lm's avatar
lm committed
367 368 369 370
    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
371 372
    bool justWritten = false;
    bool writeMismatch = false;
373
    //bool lastWritten = false;
lm's avatar
lm committed
374
    // Mark this parameter as received in write ACK list
375
    QMap<QString, QVariant>* map = transmissionMissingWriteAckPackets.value(component);
lm's avatar
lm committed
376 377
    if (map && map->contains(parameterName))
    {
lm's avatar
lm committed
378
        justWritten = true;
lm's avatar
lm committed
379 380
        if (map->value(parameterName) != value)
        {
lm's avatar
lm committed
381 382 383 384 385
            writeMismatch = true;
        }
        map->remove(parameterName);
    }

386
    int missCount = 0;
lm's avatar
lm committed
387 388
    foreach (int key, transmissionMissingPackets.keys())
    {
389 390 391
        missCount +=  transmissionMissingPackets.value(key)->count();
    }

lm's avatar
lm committed
392
    int missWriteCount = 0;
lm's avatar
lm committed
393 394
    foreach (int key, transmissionMissingWriteAckPackets.keys())
    {
lm's avatar
lm committed
395 396 397
        missWriteCount += transmissionMissingWriteAckPackets.value(key)->count();
    }

lm's avatar
lm committed
398 399
    if (justWritten && !writeMismatch && missWriteCount == 0)
    {
lm's avatar
lm committed
400 401 402 403 404
        // 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
405 406
    } else if (justWritten && !writeMismatch)
    {
407
        statusLabel->setText(tr("SUCCESS: Wrote %2 (#%1/%4): %3").arg(paramId+1).arg(parameterName).arg(value.toDouble()).arg(paramCount));
lm's avatar
lm committed
408 409 410
        QPalette pal = statusLabel->palette();
        pal.setColor(backgroundRole(), QGC::colorGreen);
        statusLabel->setPalette(pal);
lm's avatar
lm committed
411 412
    } else if (justWritten && writeMismatch)
    {
lm's avatar
lm committed
413
        // Mismatch, tell user
lm's avatar
lm committed
414 415 416
        QPalette pal = statusLabel->palette();
        pal.setColor(backgroundRole(), QGC::colorRed);
        statusLabel->setPalette(pal);
417
        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
418 419 420 421 422
    }
    else
    {
        if (missCount > 0)
        {
lm's avatar
lm committed
423 424 425
            QPalette pal = statusLabel->palette();
            pal.setColor(backgroundRole(), QGC::colorOrange);
            statusLabel->setPalette(pal);
lm's avatar
lm committed
426 427 428
        }
        else
        {
lm's avatar
lm committed
429 430 431 432
            QPalette pal = statusLabel->palette();
            pal.setColor(backgroundRole(), QGC::colorGreen);
            statusLabel->setPalette(pal);
        }
433
        statusLabel->setText(tr("Got %2 (#%1/%5): %3 (%4 missing)").arg(paramId+1).arg(parameterName).arg(value.toDouble()).arg(missCount).arg(paramCount));
lm's avatar
lm committed
434
    }
lm's avatar
lm committed
435 436

    // Check if last parameter was received
lm's avatar
lm committed
437 438
    if (missCount == 0 && missWriteCount == 0)
    {
lm's avatar
lm committed
439 440 441
        this->transmissionActive = false;
        this->transmissionListMode = false;
        transmissionListSizeKnown.clear();
lm's avatar
lm committed
442 443
        foreach (int key, transmissionMissingPackets.keys())
        {
lm's avatar
lm committed
444 445 446 447 448
            transmissionMissingPackets.value(key)->clear();
        }
    }
}

449 450 451 452 453
/**
 * @param uas System which has the component
 * @param component id of the component
 * @param parameterName human friendly name of the parameter
 */
454
void QGCParamWidget::addParameter(int uas, int component, QString parameterName, QVariant value)
pixhawk's avatar
pixhawk committed
455
{
456
    qDebug() << "PARAM WIDGET GOT PARAM:" << value;
457
    Q_UNUSED(uas);
458
    // Reference to item in tree
459
    QTreeWidgetItem* parameterItem = NULL;
pixhawk's avatar
pixhawk committed
460 461

    // Get component
lm's avatar
lm committed
462 463
    if (!components->contains(component))
    {
464 465 466 467 468 469 470 471 472 473 474 475 476 477
//        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;
//        }
        QString componentName = tr("Component #").arg(component);
478
        addComponent(uas, component, componentName);
pixhawk's avatar
pixhawk committed
479
    }
480

481 482 483 484 485 486 487
    // 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
488 489
    QString splitToken = "_";
    // Check if auto-grouping can work
lm's avatar
lm committed
490 491
    if (parameterName.contains(splitToken))
    {
pixhawk's avatar
pixhawk committed
492 493
        QString parent = parameterName.section(splitToken, 0, 0, QString::SectionSkipEmpty);
        QMap<QString, QTreeWidgetItem*>* compParamGroups = paramGroups.value(component);
494 495
        if (!compParamGroups->contains(parent))
        {
pixhawk's avatar
pixhawk committed
496 497 498 499 500 501
            // Insert group item
            QStringList glist;
            glist.append(parent);
            QTreeWidgetItem* item = new QTreeWidgetItem(glist);
            compParamGroups->insert(parent, item);
            components->value(component)->addChild(item);
502
        }
503 504 505 506

        // Append child to group
        bool found = false;
        QTreeWidgetItem* parentItem = compParamGroups->value(parent);
507
        for (int i = 0; i < parentItem->childCount(); i++) {
508 509
            QTreeWidgetItem* child = parentItem->child(i);
            QString key = child->data(0, Qt::DisplayRole).toString();
510 511
            if (key == parameterName)
            {
512 513 514 515 516 517 518
                //qDebug() << "UPDATED CHILD";
                parameterItem = child;
                parameterItem->setData(1, Qt::DisplayRole, value);
                found = true;
            }
        }

lm's avatar
lm committed
519 520
        if (!found)
        {
521 522 523
            // Insert parameter into map
            QStringList plist;
            plist.append(parameterName);
524
            // CREATE PARAMETER ITEM
525
            parameterItem = new QTreeWidgetItem(plist);
526
            // CONFIGURE PARAMETER ITEM
527
            parameterItem->setData(1, Qt::DisplayRole, value);
528 529

            compParamGroups->value(parent)->addChild(parameterItem);
lm's avatar
lm committed
530
            parameterItem->setFlags(parameterItem->flags() | Qt::ItemIsEditable);
531
        }
lm's avatar
lm committed
532 533 534
    }
    else
    {
pixhawk's avatar
pixhawk committed
535 536
        bool found = false;
        QTreeWidgetItem* parent = components->value(component);
lm's avatar
lm committed
537 538
        for (int i = 0; i < parent->childCount(); i++)
        {
pixhawk's avatar
pixhawk committed
539 540
            QTreeWidgetItem* child = parent->child(i);
            QString key = child->data(0, Qt::DisplayRole).toString();
lm's avatar
lm committed
541 542
            if (key == parameterName)
            {
pixhawk's avatar
pixhawk committed
543
                //qDebug() << "UPDATED CHILD";
544 545
                parameterItem = child;
                parameterItem->setData(1, Qt::DisplayRole, value);
pixhawk's avatar
pixhawk committed
546 547 548 549
                found = true;
            }
        }

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

lm's avatar
lm committed
560 561
            components->value(component)->addChild(parameterItem);
            parameterItem->setFlags(parameterItem->flags() | Qt::ItemIsEditable);
pixhawk's avatar
pixhawk committed
562
        }
563
        //tree->expandAll();
564
    }
565
    // Reset background color
566 567
    parameterItem->setBackground(0, QBrush(QColor(0, 0, 0)));
    parameterItem->setBackground(1, Qt::NoBrush);
lm's avatar
lm committed
568
    // Add tooltip
569 570 571 572 573 574 575 576 577 578 579 580
    QString tooltipFormat;
    if (paramDefault.contains(parameterName))
    {
        tooltipFormat = tr("Default: %1, %2");
        tooltipFormat = tooltipFormat.arg(paramToolTips.value(parameterName, ""), paramDefault.value(parameterName));
    }
    else
    {
        tooltipFormat = paramToolTips.value(parameterName, "");
    }
    parameterItem->setToolTip(0, tooltipFormat);
    parameterItem->setToolTip(1, tooltipFormat);
lm's avatar
lm committed
581

582
    //tree->update();
pixhawk's avatar
pixhawk committed
583
    if (changedValues.contains(component)) changedValues.value(component)->remove(parameterName);
pixhawk's avatar
pixhawk committed
584 585
}

586 587 588 589
/**
 * Send a request to deliver the list of onboard parameters
 * to the MAV.
 */
590 591
void QGCParamWidget::requestParameterList()
{
592
    if (!mav) return;
593 594 595 596 597 598 599
    // 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
600
    // Clear view and request param list
601
    clear();
602
    parameters.clear();
lm's avatar
lm committed
603
    received.clear();
lm's avatar
lm committed
604 605 606
    // Clear transmission state
    transmissionListMode = true;
    transmissionListSizeKnown.clear();
607
    foreach (int key, transmissionMissingPackets.keys()) {
lm's avatar
lm committed
608 609 610
        transmissionMissingPackets.value(key)->clear();
    }
    transmissionActive = true;
lm's avatar
lm committed
611 612 613 614 615 616 617

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

    // Request twice as mean of forward error correction
    mav->requestParameters();
    QGC::SLEEP::msleep(10);
lm's avatar
lm committed
618
    mav->requestParameters();
619 620
}

621
void QGCParamWidget::parameterItemChanged(QTreeWidgetItem* current, int column)
lm's avatar
lm committed
622
{
623
    if (current && column > 0) {
624
        QTreeWidgetItem* parent = current->parent();
625
        while (parent->parent() != NULL) {
626 627 628 629
            parent = parent->parent();
        }
        // Parent is now top-level component
        int key = components->key(parent);
630
        if (!changedValues.contains(key)) {
631
            changedValues.insert(key, new QMap<QString, QVariant>());
632
        }
633
        QMap<QString, QVariant>* map = changedValues.value(key, NULL);
634
        if (map) {
635
            QString str = current->data(0, Qt::DisplayRole).toString();
636 637
            QVariant value = current->data(1, Qt::DisplayRole);
            qDebug() << "CHANGED PARAM:" << value;
lm's avatar
lm committed
638
            // Set parameter on changed list to be transmitted to MAV
639 640 641 642 643 644 645 646 647 648 649
            statusLabel->setText(tr("Changed Param %1:%2: %3").arg(key).arg(str).arg(value.toDouble()));
            //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)));
            }
650

651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668
            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);
669
                }
670 671 672 673
                break;
            default:
                qCritical() << "ABORTED PARAM UPDATE, NO VALID QVARIANT TYPE";
                return;
674 675 676 677 678 679
            }
        }
    }
}

void QGCParamWidget::saveParameters()
680
{
681
    if (!mav) return;
682 683
    QString fileName = QFileDialog::getSaveFileName(this, tr("Save File"), "./parameters.txt", tr("Parameter File (*.txt)"));
    QFile file(fileName);
684
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
685 686 687 688 689 690 691 692 693 694
        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
695
    QMap<int, QMap<QString, QVariant>*>::iterator i;
696
    for (i = parameters.begin(); i != parameters.end(); ++i) {
697 698
        // Iterate through the parameters of the component
        int compid = i.key();
699
        QMap<QString, QVariant>* comp = i.value();
700
        {
701 702 703
            QMap<QString, QVariant>::iterator j;
            for (j = comp->begin(); j != comp->end(); ++j)
            {
704
                QString paramValue("%1");
705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724
                QString paramType("%1");
                switch (j.value().type())
                {
                case QVariant::Int:
                    paramValue = paramValue.arg(j.value().toInt());
                    paramType.arg(MAVLINK_TYPE_INT32_T);
                    break;
                case QVariant::UInt:
                    paramValue = paramValue.arg(j.value().toUInt());
                    paramType.arg(MAVLINK_TYPE_UINT32_T);
                    break;
                case QMetaType::Float:
                    paramValue = paramValue.arg(j.value().toDouble(), 25, 'g', 12);
                    paramType.arg(MAVLINK_TYPE_FLOAT);
                    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";
725 726 727 728 729 730 731 732 733
                in.flush();
            }
        }
    }
    file.close();
}

void QGCParamWidget::loadParameters()
{
734
    if (!mav) return;
735 736 737 738 739 740 741 742 743
    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);
744
    while (!in.atEnd()) {
745
        QString line = in.readLine();
746
        if (!line.startsWith("#")) {
747
            QStringList wpParams = line.split("\t");
748
            if (wpParams.size() == 5) {
749
                // Only load parameters for right mav
750
                if (mav->getUASID() == wpParams.at(0).toInt()) {
pixhawk's avatar
pixhawk committed
751

lm's avatar
lm committed
752 753 754
                    bool changed = false;
                    int component = wpParams.at(1).toInt();
                    QString parameterName = wpParams.at(2);
755
                    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
756 757 758 759
                        changed = true;
                    }

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

762
                    if (changed) {
lm's avatar
lm committed
763
                        // Create changed values data structure if necessary
764
                        if (!changedValues.contains(wpParams.at(1).toInt())) {
765
                            changedValues.insert(wpParams.at(1).toInt(), new QMap<QString, QVariant>());
766 767
                        }

lm's avatar
lm committed
768
                        // Add to changed values
769
                        if (changedValues.value(wpParams.at(1).toInt())->contains(wpParams.at(2))) {
lm's avatar
lm committed
770 771 772
                            changedValues.value(wpParams.at(1).toInt())->remove(wpParams.at(2));
                        }

773 774 775 776 777 778 779 780 781 782 783 784
                        switch (wpParams.at(3).toUInt())
                        {
                        case MAVLINK_TYPE_FLOAT:
                            changedValues.value(wpParams.at(1).toInt())->insert(wpParams.at(2), wpParams.at(3).toFloat());
                            break;
                        case MAVLINK_TYPE_UINT32_T:
                            changedValues.value(wpParams.at(1).toInt())->insert(wpParams.at(2), wpParams.at(3).toUInt());
                            break;
                        case MAVLINK_TYPE_INT32_T:
                            changedValues.value(wpParams.at(1).toInt())->insert(wpParams.at(2), wpParams.at(3).toInt());
                            break;
                        }
lm's avatar
lm committed
785

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

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

pixhawk's avatar
pixhawk committed
790

791
                    }
792
                }
lm's avatar
lm committed
793 794
            }
        }
795
    }
796 797
    file.close();

lm's avatar
lm committed
798 799
}

800 801 802 803 804 805 806 807 808
/**
 * 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)
{
809
    if (enabled) {
810
        retransmissionTimer.start(retransmissionTimeout);
811
    } else {
812 813 814 815 816 817
        retransmissionTimer.stop();
    }
}

void QGCParamWidget::retransmissionGuardTick()
{
818
    if (transmissionActive) {
819 820
        qDebug() << __FILE__ << __LINE__ << "RETRANSMISSION GUARD ACTIVE, CHECKING FOR DROPS..";

lm's avatar
lm committed
821 822
        // Check for timeout
        // stop retransmission attempts on timeout
823
        if (QGC::groundTimeMilliseconds() > transmissionTimeout) {
lm's avatar
lm committed
824 825 826 827 828 829 830
            setRetransmissionGuardEnabled(false);
            transmissionActive = false;

            // Empty read retransmission list
            // Empty write retransmission list
            int missingReadCount = 0;
            QList<int> readKeys = transmissionMissingPackets.keys();
831
            foreach (int component, readKeys) {
lm's avatar
lm committed
832 833 834 835 836 837 838
                missingReadCount += transmissionMissingPackets.value(component)->count();
                transmissionMissingPackets.value(component)->clear();
            }

            // Empty write retransmission list
            int missingWriteCount = 0;
            QList<int> writeKeys = transmissionMissingWriteAckPackets.keys();
839
            foreach (int component, writeKeys) {
lm's avatar
lm committed
840 841 842 843 844 845 846 847
                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
848
        QMap<int, QMap<QString, QVariant>*>::iterator i;
849
        for (i = parameters.begin(); i != parameters.end(); ++i) {
850 851
            // Iterate through the parameters of the component
            int component = i.key();
lm's avatar
lm committed
852
            // Request n parameters from this component (at maximum)
853
            QList<int> * paramList = transmissionMissingPackets.value(component, NULL);
854
            if (paramList) {
855
                int count = 0;
856 857
                foreach (int id, *paramList) {
                    if (count < retransmissionBurstRequestSize) {
858 859
                        qDebug() << __FILE__ << __LINE__ << "RETRANSMISSION GUARD REQUESTS RETRANSMISSION OF PARAM #" << id << "FROM COMPONENT #" << component;
                        emit requestParameter(component, id);
lm's avatar
lm committed
860
                        statusLabel->setText(tr("Requested retransmission of #%1").arg(id+1));
861
                        count++;
862
                    } else {
863 864 865 866 867
                        break;
                    }
                }
            }
        }
lm's avatar
lm committed
868 869 870 871 872

        // Re-request at maximum retransmissionBurstRequestSize parameters at once
        // to prevent write-request link flooding
        // Empty write retransmission list
        QList<int> writeKeys = transmissionMissingWriteAckPackets.keys();
873
        foreach (int component, writeKeys) {
lm's avatar
lm committed
874
            int count = 0;
875
            QMap <QString, QVariant>* missingParams = transmissionMissingWriteAckPackets.value(component);
876 877
            foreach (QString key, missingParams->keys()) {
                if (count < retransmissionBurstRequestSize) {
lm's avatar
lm committed
878
                    // Re-request write operation
879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903
                    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;
                    }
904
                    statusLabel->setText(tr("Requested rewrite of: %1: %2").arg(key).arg(missingParams->value(key).toDouble()));
lm's avatar
lm committed
905
                    count++;
906
                } else {
lm's avatar
lm committed
907 908 909 910
                    break;
                }
            }
        }
911
    } else {
912 913 914 915 916
        qDebug() << __FILE__ << __LINE__ << "STOPPING RETRANSMISSION GUARD GRACEFULLY";
        setRetransmissionGuardEnabled(false);
    }
}

917

918 919 920 921 922
/**
 * The .. signal is emitted
 */
void QGCParamWidget::requestParameterUpdate(int component, const QString& parameter)
{
LM's avatar
LM committed
923
    // FIXME - IMPLEMENT THIS!
924 925 926
}


927 928 929 930 931
/**
 * @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
 */
932
void QGCParamWidget::setParameter(int component, QString parameterName, QVariant value)
933
{
934 935 936 937 938 939 940 941 942 943
    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;
    }
944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972

    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
973 974
    // Wait for parameter to be written back
    // mark it therefore as missing
975 976 977
    if (!transmissionMissingWriteAckPackets.contains(component))
    {
        transmissionMissingWriteAckPackets.insert(component, new QMap<QString, QVariant>());
lm's avatar
lm committed
978 979 980 981 982 983 984 985
    }

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

    // Set timeouts
    transmissionActive = true;
    quint64 newTransmissionTimeout = QGC::groundTimeMilliseconds() + 5*rewriteTimeout;
986
    if (newTransmissionTimeout > transmissionTimeout) {
lm's avatar
lm committed
987 988 989 990
        transmissionTimeout = newTransmissionTimeout;
    }
    // Enable guard / reset timeouts
    setRetransmissionGuardEnabled(true);
991 992
}

993 994 995
/**
 * Set all parameter in the parameter tree on the MAV
 */
996 997
void QGCParamWidget::setParameters()
{
998
    // Iterate through all components, through all parameters and emit them
999
    int parametersSent = 0;
1000
    QMap<int, QMap<QString, QVariant>*>::iterator i;
1001
    for (i = changedValues.begin(); i != changedValues.end(); ++i) {
1002 1003
        // Iterate through the parameters of the component
        int compid = i.key();
1004
        QMap<QString, QVariant>* comp = i.value();
1005
        {
1006
            QMap<QString, QVariant>::iterator j;
1007
            for (j = comp->begin(); j != comp->end(); ++j) {
1008
                setParameter(compid, j.key(), j.value());
1009
                parametersSent++;
1010 1011 1012 1013
            }
        }
    }

lm's avatar
lm committed
1014
    // Change transmission status if necessary
1015
    if (parametersSent == 0) {
1016
        statusLabel->setText(tr("No transmission: No changed values."));
1017
    } else {
1018
        statusLabel->setText(tr("Transmitting %1 parameters.").arg(parametersSent));
lm's avatar
lm committed
1019 1020 1021
        // Set timeouts
        transmissionActive = true;
        quint64 newTransmissionTimeout = QGC::groundTimeMilliseconds() + (parametersSent/retransmissionBurstRequestSize+5)*rewriteTimeout;
1022
        if (newTransmissionTimeout > transmissionTimeout) {
lm's avatar
lm committed
1023 1024 1025 1026
            transmissionTimeout = newTransmissionTimeout;
        }
        // Enable guard
        setRetransmissionGuardEnabled(true);
1027
    }
1028 1029
}

1030 1031 1032 1033
/**
 * Write the current onboard parameters from RAM into
 * permanent storage, e.g. EEPROM or harddisk
 */
1034 1035
void QGCParamWidget::writeParameters()
{
1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062
    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();
    }
1063 1064 1065 1066
}

void QGCParamWidget::readParameters()
{
1067
    if (!mav) return;
1068
    mav->readParametersFromStorage();
1069 1070
}

1071 1072 1073
/**
 * Clear all data in the parameter widget
 */
pixhawk's avatar
pixhawk committed
1074 1075 1076
void QGCParamWidget::clear()
{
    tree->clear();
lm's avatar
lm committed
1077
    components->clear();
pixhawk's avatar
pixhawk committed
1078
}