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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

    // 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
135 136
}

137 138 139 140 141 142 143 144 145 146 147 148
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
149 150 151 152
void QGCParamWidget::loadParameterInfoCSV(const QString& autopilot, const QString& airframe)
{
    QDir appDir = QDir::current();
    appDir.cd("files/parameter_tooltips");
153
    QString fileName = QString("MAV_AUTOPILOT_%1-MAV_TYPE_%2.txt").arg(autopilot).arg(airframe);
lm's avatar
lm committed
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
    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
171 172
    // there might be more lines, but the first
    // line is assumed to be at least header
lm's avatar
lm committed
173 174
    QString header = in.readLine();

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

lm's avatar
lm committed
181 182 183 184 185 186
    bool charRead = false;
    QString separator = "";
    QList<QChar> sepCandidates;
    sepCandidates << '\t';
    sepCandidates << ',';
    sepCandidates << ';';
187
    //sepCandidates << ' ';
lm's avatar
lm committed
188 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
    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;
        }
    }

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


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

234 235 236 237 238 239 240 241
        //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
242 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
        // 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());
        }
    }
}

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

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

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

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

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

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

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

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

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

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

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

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

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

    // Get component
lm's avatar
lm committed
459 460
    if (!components->contains(component))
    {
461
        QString componentName;
lm's avatar
lm committed
462 463
        switch (component)
        {
464 465 466 467 468 469 470 471 472 473 474 475
        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;
        }

        addComponent(uas, component, componentName);
pixhawk's avatar
pixhawk committed
476
    }
477

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

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

lm's avatar
lm committed
516 517
        if (!found)
        {
518 519 520
            // Insert parameter into map
            QStringList plist;
            plist.append(parameterName);
521
            plist.append(QString::number(value.toDouble()));
522
            // CREATE PARAMETER ITEM
523
            parameterItem = new QTreeWidgetItem(plist);
524
            // CONFIGURE PARAMETER ITEM
525 526

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

lm's avatar
lm committed
547 548
        if (!found)
        {
549 550 551
            // Insert parameter into map
            QStringList plist;
            plist.append(parameterName);
552
            plist.append(QString::number(value.toDouble()));
553
            // CREATE PARAMETER ITEM
554
            parameterItem = new QTreeWidgetItem(plist);
555
            // CONFIGURE PARAMETER ITEM
556

lm's avatar
lm committed
557 558
            components->value(component)->addChild(parameterItem);
            parameterItem->setFlags(parameterItem->flags() | Qt::ItemIsEditable);
pixhawk's avatar
pixhawk committed
559
        }
560
        //tree->expandAll();
561
    }
562
    // Reset background color
563 564
    parameterItem->setBackground(0, QBrush(QColor(0, 0, 0)));
    parameterItem->setBackground(1, Qt::NoBrush);
lm's avatar
lm committed
565 566 567 568
    // Add tooltip
    parameterItem->setToolTip(0, paramToolTips.value(parameterName, ""));
    parameterItem->setToolTip(1, paramToolTips.value(parameterName, ""));

569
    //tree->update();
pixhawk's avatar
pixhawk committed
570
    if (changedValues.contains(component)) changedValues.value(component)->remove(parameterName);
pixhawk's avatar
pixhawk committed
571 572
}

573 574 575 576
/**
 * Send a request to deliver the list of onboard parameters
 * to the MAV.
 */
577 578
void QGCParamWidget::requestParameterList()
{
579
    if (!mav) return;
580 581 582 583 584 585 586
    // 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
587
    // Clear view and request param list
588
    clear();
589
    parameters.clear();
lm's avatar
lm committed
590
    received.clear();
lm's avatar
lm committed
591 592 593
    // Clear transmission state
    transmissionListMode = true;
    transmissionListSizeKnown.clear();
594
    foreach (int key, transmissionMissingPackets.keys()) {
lm's avatar
lm committed
595 596 597
        transmissionMissingPackets.value(key)->clear();
    }
    transmissionActive = true;
lm's avatar
lm committed
598 599 600 601 602 603 604

    // 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
605
    mav->requestParameters();
606 607
}

608
void QGCParamWidget::parameterItemChanged(QTreeWidgetItem* current, int column)
lm's avatar
lm committed
609
{
610
    if (current && column > 0) {
611
        QTreeWidgetItem* parent = current->parent();
612
        while (parent->parent() != NULL) {
613 614 615 616
            parent = parent->parent();
        }
        // Parent is now top-level component
        int key = components->key(parent);
617
        if (!changedValues.contains(key)) {
618
            changedValues.insert(key, new QMap<QString, QVariant>());
619
        }
620
        QMap<QString, QVariant>* map = changedValues.value(key, NULL);
621
        if (map) {
622 623 624
            bool ok;
            QString str = current->data(0, Qt::DisplayRole).toString();
            float value = current->data(1, Qt::DisplayRole).toDouble(&ok);
lm's avatar
lm committed
625
            // Set parameter on changed list to be transmitted to MAV
626 627
            if (ok) {
                if (ok) {
lm's avatar
lm committed
628 629
                    statusLabel->setText(tr("Changed Param %1:%2: %3").arg(key).arg(str).arg(value));
                    //qDebug() << "PARAM CHANGED: COMP:" << key << "KEY:" << str << "VALUE:" << value;
630
                    // Changed values list
631
                    if (map->contains(str)) map->remove(str);
632
                    map->insert(str, value);
633 634

                    // Check if the value was numerically changed
635
                    if (!parameters.value(key)->contains(str) || parameters.value(key)->value(str, 0.0f) != value) {
lm's avatar
lm committed
636 637
                        current->setBackground(0, QBrush(QColor(QGC::colorOrange)));
                        current->setBackground(1, QBrush(QColor(QGC::colorOrange)));
638 639 640 641 642 643 644 645 646 647 648 649
                    }

                    // All parameters list
                    if (parameters.value(key)->contains(str)) parameters.value(key)->remove(str);
                    parameters.value(key)->insert(str, value);
                }
            }
        }
    }
}

void QGCParamWidget::saveParameters()
650
{
651
    if (!mav) return;
652 653
    QString fileName = QFileDialog::getSaveFileName(this, tr("Save File"), "./parameters.txt", tr("Parameter File (*.txt)"));
    QFile file(fileName);
654
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
655 656 657 658 659 660 661 662 663 664
        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
665
    QMap<int, QMap<QString, QVariant>*>::iterator i;
666
    for (i = parameters.begin(); i != parameters.end(); ++i) {
667 668
        // Iterate through the parameters of the component
        int compid = i.key();
669
        QMap<QString, QVariant>* comp = i.value();
670
        {
671 672 673
            QMap<QString, QVariant>::iterator j;
            for (j = comp->begin(); j != comp->end(); ++j)
            {
674
                QString paramValue("%1");
675
                paramValue = paramValue.arg(j.value().toDouble(), 25, 'g', 12);
676
                in << mav->getUASID() << "\t" << compid << "\t" << j.key() << "\t" << paramValue << "\n";
677 678 679 680 681 682 683 684 685
                in.flush();
            }
        }
    }
    file.close();
}

void QGCParamWidget::loadParameters()
{
686
    if (!mav) return;
687 688 689 690 691 692 693 694 695
    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);
696
    while (!in.atEnd()) {
697
        QString line = in.readLine();
698
        if (!line.startsWith("#")) {
699
            QStringList wpParams = line.split("\t");
700
            if (wpParams.size() == 4) {
701
                // Only load parameters for right mav
702
                if (mav->getUASID() == wpParams.at(0).toInt()) {
pixhawk's avatar
pixhawk committed
703

lm's avatar
lm committed
704 705 706
                    bool changed = false;
                    int component = wpParams.at(1).toInt();
                    QString parameterName = wpParams.at(2);
707
                    if (!parameters.contains(component) || parameters.value(component)->value(parameterName, 0.0f) != (float)wpParams.at(3).toDouble()) {
lm's avatar
lm committed
708 709 710 711
                        changed = true;
                    }

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

714
                    if (changed) {
lm's avatar
lm committed
715
                        // Create changed values data structure if necessary
716
                        if (!changedValues.contains(wpParams.at(1).toInt())) {
717
                            changedValues.insert(wpParams.at(1).toInt(), new QMap<QString, QVariant>());
718 719
                        }

lm's avatar
lm committed
720
                        // Add to changed values
721
                        if (changedValues.value(wpParams.at(1).toInt())->contains(wpParams.at(2))) {
lm's avatar
lm committed
722 723 724 725 726
                            changedValues.value(wpParams.at(1).toInt())->remove(wpParams.at(2));
                        }

                        changedValues.value(wpParams.at(1).toInt())->insert(wpParams.at(2), (float)wpParams.at(3).toDouble());

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

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

pixhawk's avatar
pixhawk committed
731

732
                    }
733
                }
lm's avatar
lm committed
734 735
            }
        }
736
    }
737 738
    file.close();

lm's avatar
lm committed
739 740
}

741 742 743 744 745 746 747 748 749
/**
 * 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)
{
750
    if (enabled) {
751
        retransmissionTimer.start(retransmissionTimeout);
752
    } else {
753 754 755 756 757 758
        retransmissionTimer.stop();
    }
}

void QGCParamWidget::retransmissionGuardTick()
{
759
    if (transmissionActive) {
760 761
        qDebug() << __FILE__ << __LINE__ << "RETRANSMISSION GUARD ACTIVE, CHECKING FOR DROPS..";

lm's avatar
lm committed
762 763
        // Check for timeout
        // stop retransmission attempts on timeout
764
        if (QGC::groundTimeMilliseconds() > transmissionTimeout) {
lm's avatar
lm committed
765 766 767 768 769 770 771
            setRetransmissionGuardEnabled(false);
            transmissionActive = false;

            // Empty read retransmission list
            // Empty write retransmission list
            int missingReadCount = 0;
            QList<int> readKeys = transmissionMissingPackets.keys();
772
            foreach (int component, readKeys) {
lm's avatar
lm committed
773 774 775 776 777 778 779
                missingReadCount += transmissionMissingPackets.value(component)->count();
                transmissionMissingPackets.value(component)->clear();
            }

            // Empty write retransmission list
            int missingWriteCount = 0;
            QList<int> writeKeys = transmissionMissingWriteAckPackets.keys();
780
            foreach (int component, writeKeys) {
lm's avatar
lm committed
781 782 783 784 785 786 787 788
                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
789
        QMap<int, QMap<QString, QVariant>*>::iterator i;
790
        for (i = parameters.begin(); i != parameters.end(); ++i) {
791 792
            // Iterate through the parameters of the component
            int component = i.key();
lm's avatar
lm committed
793
            // Request n parameters from this component (at maximum)
794
            QList<int> * paramList = transmissionMissingPackets.value(component, NULL);
795
            if (paramList) {
796
                int count = 0;
797 798
                foreach (int id, *paramList) {
                    if (count < retransmissionBurstRequestSize) {
799 800
                        qDebug() << __FILE__ << __LINE__ << "RETRANSMISSION GUARD REQUESTS RETRANSMISSION OF PARAM #" << id << "FROM COMPONENT #" << component;
                        emit requestParameter(component, id);
lm's avatar
lm committed
801
                        statusLabel->setText(tr("Requested retransmission of #%1").arg(id+1));
802
                        count++;
803
                    } else {
804 805 806 807 808
                        break;
                    }
                }
            }
        }
lm's avatar
lm committed
809 810 811 812 813

        // Re-request at maximum retransmissionBurstRequestSize parameters at once
        // to prevent write-request link flooding
        // Empty write retransmission list
        QList<int> writeKeys = transmissionMissingWriteAckPackets.keys();
814
        foreach (int component, writeKeys) {
lm's avatar
lm committed
815
            int count = 0;
816
            QMap <QString, QVariant>* missingParams = transmissionMissingWriteAckPackets.value(component);
817 818
            foreach (QString key, missingParams->keys()) {
                if (count < retransmissionBurstRequestSize) {
lm's avatar
lm committed
819 820
                    // Re-request write operation
                    emit parameterChanged(component, key, missingParams->value(key));
821
                    statusLabel->setText(tr("Requested rewrite of: %1: %2").arg(key).arg(missingParams->value(key).toDouble()));
lm's avatar
lm committed
822
                    count++;
823
                } else {
lm's avatar
lm committed
824 825 826 827
                    break;
                }
            }
        }
828
    } else {
829 830 831 832 833
        qDebug() << __FILE__ << __LINE__ << "STOPPING RETRANSMISSION GUARD GRACEFULLY";
        setRetransmissionGuardEnabled(false);
    }
}

834

835 836 837 838 839
/**
 * The .. signal is emitted
 */
void QGCParamWidget::requestParameterUpdate(int component, const QString& parameter)
{
LM's avatar
LM committed
840
    // FIXME - IMPLEMENT THIS!
841 842 843
}


844 845 846 847 848
/**
 * @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
 */
849
void QGCParamWidget::setParameter(int component, QString parameterName, QVariant value)
850
{
851 852 853 854 855 856 857 858 859 860
    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;
    }
861
    emit parameterChanged(component, parameterName, value);
lm's avatar
lm committed
862 863
    // Wait for parameter to be written back
    // mark it therefore as missing
864 865 866
    if (!transmissionMissingWriteAckPackets.contains(component))
    {
        transmissionMissingWriteAckPackets.insert(component, new QMap<QString, QVariant>());
lm's avatar
lm committed
867 868 869 870 871 872 873 874
    }

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

    // Set timeouts
    transmissionActive = true;
    quint64 newTransmissionTimeout = QGC::groundTimeMilliseconds() + 5*rewriteTimeout;
875
    if (newTransmissionTimeout > transmissionTimeout) {
lm's avatar
lm committed
876 877 878 879
        transmissionTimeout = newTransmissionTimeout;
    }
    // Enable guard / reset timeouts
    setRetransmissionGuardEnabled(true);
880 881
}

882 883 884
/**
 * Set all parameter in the parameter tree on the MAV
 */
885 886
void QGCParamWidget::setParameters()
{
887
    // Iterate through all components, through all parameters and emit them
888
    int parametersSent = 0;
889
    QMap<int, QMap<QString, QVariant>*>::iterator i;
890
    for (i = changedValues.begin(); i != changedValues.end(); ++i) {
891 892
        // Iterate through the parameters of the component
        int compid = i.key();
893
        QMap<QString, QVariant>* comp = i.value();
894
        {
895
            QMap<QString, QVariant>::iterator j;
896
            for (j = comp->begin(); j != comp->end(); ++j) {
897
                setParameter(compid, j.key(), j.value());
898
                parametersSent++;
899 900 901 902
            }
        }
    }

lm's avatar
lm committed
903
    // Change transmission status if necessary
904
    if (parametersSent == 0) {
905
        statusLabel->setText(tr("No transmission: No changed values."));
906
    } else {
907
        statusLabel->setText(tr("Transmitting %1 parameters.").arg(parametersSent));
lm's avatar
lm committed
908 909 910
        // Set timeouts
        transmissionActive = true;
        quint64 newTransmissionTimeout = QGC::groundTimeMilliseconds() + (parametersSent/retransmissionBurstRequestSize+5)*rewriteTimeout;
911
        if (newTransmissionTimeout > transmissionTimeout) {
lm's avatar
lm committed
912 913 914 915
            transmissionTimeout = newTransmissionTimeout;
        }
        // Enable guard
        setRetransmissionGuardEnabled(true);
916 917
    }

lm's avatar
lm committed
918
    changedValues.clear();
919 920
}

921 922 923 924
/**
 * Write the current onboard parameters from RAM into
 * permanent storage, e.g. EEPROM or harddisk
 */
925 926
void QGCParamWidget::writeParameters()
{
927
    if (!mav) return;
928 929 930 931 932
    mav->writeParametersToStorage();
}

void QGCParamWidget::readParameters()
{
933
    if (!mav) return;
934
    mav->readParametersFromStorage();
935 936
}

937 938 939
/**
 * Clear all data in the parameter widget
 */
pixhawk's avatar
pixhawk committed
940 941 942
void QGCParamWidget::clear()
{
    tree->clear();
lm's avatar
lm committed
943
    components->clear();
pixhawk's avatar
pixhawk committed
944
}