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

QGroundControl Open Source Ground Control Station

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

This file is part of the QGROUNDCONTROL project

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

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

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

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

29 30
#include <QGridLayout>
#include <QPushButton>
31 32
#include <QFileDialog>
#include <QFile>
33
#include <QList>
34
#include <QSettings>
35
#include <QMessageBox>
pixhawk's avatar
pixhawk committed
36 37 38 39

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

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

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

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

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

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

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


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

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

91
    QPushButton* writeButton = new QPushButton(tr("Write (ROM)"));
92 93
    writeButton->setToolTip(tr("Copy current parameters in non-permanent memory of the aircraft to permanent memory. Transmit your parameters first to write these."));
    writeButton->setWhatsThis(tr("Copy current parameters in non-permanent memory of the aircraft to permanent memory. Transmit your parameters first to write these."));
94
    connect(writeButton, SIGNAL(clicked()), this, SLOT(writeParameters()));
lm's avatar
lm committed
95
    horizontalLayout->addWidget(writeButton, 2, 2);
96

97
    QPushButton* loadFileButton = new QPushButton(tr("Load File"));
98 99
    loadFileButton->setToolTip(tr("Load parameters from a file on this computer in the view. To write them to the aircraft, use transmit after loading them."));
    loadFileButton->setWhatsThis(tr("Load parameters from a file on this computer in the view. To write them to the aircraft, use transmit after loading them."));
100
    connect(loadFileButton, SIGNAL(clicked()), this, SLOT(loadParameters()));
lm's avatar
lm committed
101
    horizontalLayout->addWidget(loadFileButton, 3, 0);
102 103

    QPushButton* saveFileButton = new QPushButton(tr("Save File"));
104 105
    saveFileButton->setToolTip(tr("Save parameters in this view to a file on this computer."));
    saveFileButton->setWhatsThis(tr("Save parameters in this view to a file on this computer."));
106
    connect(saveFileButton, SIGNAL(clicked()), this, SLOT(saveParameters()));
lm's avatar
lm committed
107 108 109 110 111 112 113
    horizontalLayout->addWidget(saveFileButton, 3, 1);

    QPushButton* readButton = new QPushButton(tr("Read (ROM)"));
    readButton->setToolTip(tr("Copy parameters from permanent memory to non-permanent current memory of aircraft. DOES NOT update the parameters in this view, click refresh after copying them to get them."));
    readButton->setWhatsThis(tr("Copy parameters from permanent memory to non-permanent current memory of aircraft. DOES NOT update the parameters in this view, click refresh after copying them to get them."));
    connect(readButton, SIGNAL(clicked()), this, SLOT(readParameters()));
    horizontalLayout->addWidget(readButton, 3, 2);
114

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

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

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

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

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

139 140 141 142 143 144 145 146 147 148 149 150
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
151 152 153
void QGCParamWidget::loadParameterInfoCSV(const QString& autopilot, const QString& airframe)
{
    QDir appDir = QDir::current();
154 155
    appDir.cd("files");
    QString fileName = QString("/%1/%2/parameter_tooltips/tooltips.txt").arg(autopilot.toLower()).arg(airframe.toLower());
lm's avatar
lm committed
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
    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
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 574 575 576 577 578 579 580 581
    QString tooltipFormat;
    if (paramDefault.contains(parameterName))
    {
        tooltipFormat = tr("Default: %1, %2");
        tooltipFormat = tooltipFormat.arg(paramToolTips.value(parameterName, ""), paramDefault.value(parameterName));
    }
    else
    {
        tooltipFormat = paramToolTips.value(parameterName, "");
    }
    parameterItem->setToolTip(0, tooltipFormat);
    parameterItem->setToolTip(1, tooltipFormat);
lm's avatar
lm committed
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();
608
    foreach (int key, transmissionMissingPackets.keys()) {
lm's avatar
lm committed
609 610 611
        transmissionMissingPackets.value(key)->clear();
    }
    transmissionActive = true;
lm's avatar
lm committed
612 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();
    QGC::SLEEP::msleep(10);
lm's avatar
lm committed
619
    mav->requestParameters();
620 621
}

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

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

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

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

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

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

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

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

774 775 776 777 778 779 780 781 782 783 784 785
                        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
786

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

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

pixhawk's avatar
pixhawk committed
791

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

lm's avatar
lm committed
799 800
}

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

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

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

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

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

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

918

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


928 929 930 931 932
/**
 * @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
 */
933
void QGCParamWidget::setParameter(int component, QString parameterName, QVariant value)
934
{
935 936 937 938 939 940 941 942 943 944
    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;
    }
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 973

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

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

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

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

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

1031 1032 1033 1034
/**
 * Write the current onboard parameters from RAM into
 * permanent storage, e.g. EEPROM or harddisk
 */
1035 1036
void QGCParamWidget::writeParameters()
{
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 1063
    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();
    }
1064 1065 1066 1067
}

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

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