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 298 299 300 301 302 303 304
        // Add new
        QStringList list;
        list.append(componentName);
        list.append(QString::number(component));
        QTreeWidgetItem* comp = new QTreeWidgetItem(list);
        components->insert(component, comp);
        // Create grouping and update maps
        paramGroups.insert(component, new QMap<QString, QTreeWidgetItem*>());
pixhawk's avatar
pixhawk committed
305 306
        tree->addTopLevelItem(comp);
        tree->update();
307
        // Create map in parameters
308
        if (!parameters.contains(component)) {
309
            parameters.insert(component, new QMap<QString, QVariant>());
310 311
        }
        // Create map in changed parameters
312
        if (!changedValues.contains(component)) {
313
            changedValues.insert(component, new QMap<QString, QVariant>());
314
        }
pixhawk's avatar
pixhawk committed
315 316 317
    }
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

lm's avatar
lm committed
721
                        // Add to changed values
722
                        if (changedValues.value(wpParams.at(1).toInt())->contains(wpParams.at(2))) {
lm's avatar
lm committed
723 724 725 726 727
                            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());

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

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

pixhawk's avatar
pixhawk committed
732

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

lm's avatar
lm committed
740 741
}

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

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

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

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

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

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

835

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


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

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

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

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

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

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

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

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

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