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

QGroundControl Open Source Ground Control Station

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

This file is part of the QGROUNDCONTROL project

    QGROUNDCONTROL is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    QGROUNDCONTROL is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.

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

29 30
#include <QGridLayout>
#include <QPushButton>
31 32
#include <QFileDialog>
#include <QFile>
33
#include <QList>
34
#include <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(0, 150);
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 67
    horizontalLayout = new QGridLayout(this);
    horizontalLayout->setSpacing(6);
pixhawk's avatar
pixhawk committed
68 69 70
    horizontalLayout->setMargin(0);
    horizontalLayout->setSizeConstraint(QLayout::SetMinimumSize);

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

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


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

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

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

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

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

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

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

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

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

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

140 141 142 143 144 145 146 147 148 149 150 151
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
152 153
void QGCParamWidget::loadParameterInfoCSV(const QString& autopilot, const QString& airframe)
{
LM's avatar
LM committed
154
    QDir appDir = QApplication::applicationDirPath();
155
    appDir.cd("files");
LM's avatar
LM committed
156 157
    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
158 159 160 161

    // Load CSV data
    if (!paramMetaFile.open(QIODevice::ReadOnly | QIODevice::Text))
    {
LM's avatar
LM committed
162
        qDebug() << "COULD NOT OPEN PARAM META INFO FILE:" << fileName;
lm's avatar
lm committed
163 164 165 166 167 168 169 170 171 172
        return;
    }

    // Extract header

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    // Get component
lm's avatar
lm committed
463 464
    if (!components->contains(component))
    {
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;
//        }
478
        QString componentName = tr("Component #%1").arg(component);
479
        addComponent(uas, component, componentName);
pixhawk's avatar
pixhawk committed
480
    }
481

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

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

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

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

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

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

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

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

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

    // Request twice as mean of forward error correction
    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
                QString paramType("%1");
                switch (j.value().type())
                {
                case QVariant::Int:
                    paramValue = paramValue.arg(j.value().toInt());
pixhawk's avatar
pixhawk committed
710
                    paramType = paramType.arg(MAVLINK_TYPE_INT32_T);
711 712 713
                    break;
                case QVariant::UInt:
                    paramValue = paramValue.arg(j.value().toUInt());
pixhawk's avatar
pixhawk committed
714
                    paramType = paramType.arg(MAVLINK_TYPE_UINT32_T);
715 716 717
                    break;
                case QMetaType::Float:
                    paramValue = paramValue.arg(j.value().toDouble(), 25, 'g', 12);
pixhawk's avatar
pixhawk committed
718
                    paramType = paramType.arg(MAVLINK_TYPE_FLOAT);
719 720 721 722 723 724
                    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)
{
923
    if (mav) mav->requestParameter(component, parameter);
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
}