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
lm's avatar
lm committed
80
    QPushButton* refreshButton = new QPushButton(tr("Refresh"));
lm's avatar
lm committed
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."));
lm's avatar
lm committed
83
    connect(refreshButton, SIGNAL(clicked()), this, SLOT(requestParameterList()));
lm's avatar
lm committed
84
    horizontalLayout->addWidget(refreshButton, 2, 0);
85

lm's avatar
lm committed
86
    QPushButton* setButton = new QPushButton(tr("Transmit"));
lm's avatar
lm committed
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

lm's avatar
lm committed
92
    QPushButton* writeButton = new QPushButton(tr("Write (ROM)"));
lm's avatar
lm committed
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);
lm's avatar
lm committed
97

98
    QPushButton* loadFileButton = new QPushButton(tr("Load File"));
lm's avatar
lm committed
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"));
lm's avatar
lm committed
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);
lm's avatar
lm committed
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);
lm's avatar
lm committed
525
            // CREATE PARAMETER ITEM
526
            parameterItem = new QTreeWidgetItem(plist);
lm's avatar
lm committed
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);
lm's avatar
lm committed
556
            // CREATE PARAMETER ITEM
557
            parameterItem = new QTreeWidgetItem(plist);
lm's avatar
lm committed
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()
{
LM's avatar
LM committed
593
    qDebug() << "LOADING PARAM LIST";
594
    if (!mav) return;
595
596
597
598
599
600
601
    // 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
602
    // Clear view and request param list
603
    clear();
604
    parameters.clear();
lm's avatar
lm committed
605
    received.clear();
lm's avatar
lm committed
606
607
608
    // Clear transmission state
    transmissionListMode = true;
    transmissionListSizeKnown.clear();
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)
            {
lm's avatar
lm committed
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();
    }
lm's avatar
lm committed
1063
1064
1065
1066
}

void QGCParamWidget::readParameters()
{
1067
    if (!mav) return;
lm's avatar
lm committed
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
}