Skip to content
Snippets Groups Projects
QGCParamWidget.cc 39.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • /*=====================================================================
    
    QGroundControl Open Source Ground Control Station
    
    
    pixhawk's avatar
    pixhawk committed
    (c) 2009, 2010 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
    
    
    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>
     */
    
    
    #include <QGridLayout>
    #include <QPushButton>
    
    #include <QFileDialog>
    #include <QFile>
    
    #include <QSettings>
    
    #include <QMessageBox>
    
    LM's avatar
    LM committed
    #include <QApplication>
    
    pixhawk's avatar
    pixhawk committed
    
    #include "QGCParamWidget.h"
    #include "UASInterface.h"
    #include <QDebug>
    
    #include "QGC.h"
    
    pixhawk's avatar
    pixhawk committed
    
    
    /**
     * @param uas MAV to set the parameters on
     * @param parent Parent widget
     */
    
    pixhawk's avatar
    pixhawk committed
    QGCParamWidget::QGCParamWidget(UASInterface* uas, QWidget *parent) :
    
        QGCUASParamManager(uas, parent),
        components(new QMap<int, QTreeWidgetItem*>())
    
    pixhawk's avatar
    pixhawk committed
    {
    
        // Load settings
        loadSettings();
    
    
    lm's avatar
    lm committed
        // Load default values and tooltips
        loadParameterInfoCSV(uas->getAutopilotTypeName(), uas->getSystemTypeName());
    
    
    pixhawk's avatar
    pixhawk committed
        // Create tree widget
        tree = new QTreeWidget(this);
    
    lm's avatar
    lm committed
        statusLabel = new QLabel();
    
    lm's avatar
    lm committed
        statusLabel->setAutoFillBackground(true);
    
    pixhawk's avatar
    pixhawk committed
    
        // Set tree widget as widget onto this component
    
    pixhawk's avatar
    pixhawk committed
        //form->setAutoFillBackground(false);
    
        horizontalLayout = new QGridLayout(this);
        horizontalLayout->setSpacing(6);
    
    pixhawk's avatar
    pixhawk committed
        horizontalLayout->setMargin(0);
        horizontalLayout->setSizeConstraint(QLayout::SetMinimumSize);
    
    
    lm's avatar
    lm committed
        // Parameter tree
    
        horizontalLayout->addWidget(tree, 0, 0, 1, 3);
    
    lm's avatar
    lm committed
    
        // Status line
    
    lm's avatar
    lm committed
        statusLabel->setText(tr("Click refresh to download parameters"));
    
    lm's avatar
    lm committed
        horizontalLayout->addWidget(statusLabel, 1, 0, 1, 3);
    
    
        // BUTTONS
    
        QPushButton* refreshButton = new QPushButton(tr("Refresh"));
    
    lm's avatar
    lm committed
        refreshButton->setToolTip(tr("Load parameters currently in non-permanent memory of aircraft."));
        refreshButton->setWhatsThis(tr("Load parameters currently in non-permanent memory of aircraft."));
    
        connect(refreshButton, SIGNAL(clicked()), this, SLOT(requestParameterList()));
    
    lm's avatar
    lm committed
        horizontalLayout->addWidget(refreshButton, 2, 0);
    
        QPushButton* setButton = new QPushButton(tr("Transmit"));
    
    lm's avatar
    lm committed
        setButton->setToolTip(tr("Set current parameters in non-permanent onboard memory"));
        setButton->setWhatsThis(tr("Set current parameters in non-permanent onboard memory"));
    
        connect(setButton, SIGNAL(clicked()), this, SLOT(setParameters()));
    
    lm's avatar
    lm committed
        horizontalLayout->addWidget(setButton, 2, 1);
    
        QPushButton* writeButton = new QPushButton(tr("Write (ROM)"));
    
    lm's avatar
    lm committed
        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."));
    
        connect(writeButton, SIGNAL(clicked()), this, SLOT(writeParameters()));
    
    lm's avatar
    lm committed
        horizontalLayout->addWidget(writeButton, 2, 2);
    
        QPushButton* loadFileButton = new QPushButton(tr("Load File"));
    
    lm's avatar
    lm committed
        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."));
    
        connect(loadFileButton, SIGNAL(clicked()), this, SLOT(loadParameters()));
    
    lm's avatar
    lm committed
        horizontalLayout->addWidget(loadFileButton, 3, 0);
    
    
        QPushButton* saveFileButton = new QPushButton(tr("Save File"));
    
    lm's avatar
    lm committed
        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."));
    
        connect(saveFileButton, SIGNAL(clicked()), this, SLOT(saveParameters()));
    
    lm's avatar
    lm committed
        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);
    
    pixhawk's avatar
    pixhawk committed
        this->setLayout(horizontalLayout);
    
        // Set header
        QStringList headerItems;
        headerItems.append("Parameter");
        headerItems.append("Value");
        tree->setHeaderLabels(headerItems);
        tree->setColumnCount(2);
    
        tree->setExpandsOnDoubleClick(true);
    
        connect(this, SIGNAL(parameterChanged(int,QString,QVariant)), mav, SLOT(setParameter(int,QString,QVariant)));
    
        connect(tree, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(parameterItemChanged(QTreeWidgetItem*,int)));
    
    lm's avatar
    lm committed
    
    
        connect(uas, SIGNAL(parameterChanged(int,int,int,int,QString,QVariant)), this, SLOT(addParameter(int,int,int,int,QString,QVariant)));
    
    
        // Connect retransmission guard
    
        connect(this, SIGNAL(requestParameter(int,QString)), uas, SLOT(requestParameter(int,QString)));
    
        connect(this, SIGNAL(requestParameter(int,int)), uas, SLOT(requestParameter(int,int)));
        connect(&retransmissionTimer, SIGNAL(timeout()), this, SLOT(retransmissionGuardTick()));
    
    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
    void QGCParamWidget::loadParameterInfoCSV(const QString& autopilot, const QString& airframe)
    {
    
    LM's avatar
    LM committed
        QDir appDir = QApplication::applicationDirPath();
    
        appDir.cd("files");
    
    LM's avatar
    LM committed
        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
    
        // Load CSV data
        if (!paramMetaFile.open(QIODevice::ReadOnly | QIODevice::Text))
        {
    
    LM's avatar
    LM committed
            qDebug() << "COULD NOT OPEN PARAM META INFO FILE:" << fileName;
    
    lm's avatar
    lm committed
            return;
        }
    
        // Extract header
    
        // Read in values
        // Find all keys
        QTextStream in(&paramMetaFile);
    
        // First line is header
    
        // there might be more lines, but the first
        // line is assumed to be at least header
    
    lm's avatar
    lm committed
        QString header = in.readLine();
    
    
        // Ignore top-level comment lines
        while (header.startsWith('#') || header.startsWith('/') || header.startsWith('='))
        {
            header = in.readLine();
        }
    
    
    lm's avatar
    lm committed
        bool charRead = false;
        QString separator = "";
        QList<QChar> sepCandidates;
        sepCandidates << '\t';
        sepCandidates << ',';
        sepCandidates << ';';
    
        //sepCandidates << ' ';
    
    lm's avatar
    lm committed
        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;
            }
        }
    
    
        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
        QString out = separator;
        out.replace("\t", "<tab>");
        qDebug() << " Separator: \"" << out << "\"";
        //qDebug() << "READING CSV:" << header;
    
    
        // Read data
        while (!in.atEnd())
        {
            QString line = in.readLine();
    
    
            //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
            // 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());
            }
        }
    }
    
    
    /**
     * @return The MAV of this widget. Unless the MAV object has been destroyed, this
     *         pointer is never zero.
     */
    
    pixhawk's avatar
    pixhawk committed
    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
     */
    
    void QGCParamWidget::addComponent(int uas, int component, QString componentName)
    
    pixhawk's avatar
    pixhawk committed
    {
    
        if (components->contains(component)) {
    
    pixhawk's avatar
    pixhawk committed
            // Update existing
    
            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);
    
    pixhawk's avatar
    pixhawk committed
            // Add new
    
    lm's avatar
    lm committed
            QStringList list(QString("%1 (#%2)").arg(componentName).arg(component));
    
    pixhawk's avatar
    pixhawk committed
            QTreeWidgetItem* comp = new QTreeWidgetItem(list);
    
    lm's avatar
    lm committed
            comp->setFirstColumnSpanned(true);
    
    pixhawk's avatar
    pixhawk committed
            components->insert(component, comp);
            // Create grouping and update maps
            paramGroups.insert(component, new QMap<QString, QTreeWidgetItem*>());
    
    pixhawk's avatar
    pixhawk committed
            tree->addTopLevelItem(comp);
            tree->update();
    
            // Create map in parameters
    
            if (!parameters.contains(component)) {
    
                parameters.insert(component, new QMap<QString, QVariant>());
    
            }
            // Create map in changed parameters
    
            if (!changedValues.contains(component)) {
    
                changedValues.insert(component, new QMap<QString, QVariant>());
    
    lm's avatar
    lm committed
    /**
     * @param uas System which has the component
     * @param component id of the component
     * @param parameterName human friendly name of the parameter
     */
    
    void QGCParamWidget::addParameter(int uas, int component, int paramCount, int paramId, QString parameterName, QVariant value)
    
    lm's avatar
    lm committed
    {
        addParameter(uas, component, parameterName, value);
    
    
    lm's avatar
    lm committed
        // Missing packets list has to be instantiated for all components
    
        if (!transmissionMissingPackets.contains(component)) {
    
    lm's avatar
    lm committed
            transmissionMissingPackets.insert(component, new QList<int>());
        }
    
    
    lm's avatar
    lm committed
        // List mode is different from single parameter transfers
    
        if (transmissionListMode) {
    
    lm's avatar
    lm committed
            // Only accept the list size once on the first packet from
            // each component
    
    lm's avatar
    lm committed
            if (!transmissionListSizeKnown.contains(component))
            {
    
    lm's avatar
    lm committed
                // Mark list size as known
                transmissionListSizeKnown.insert(component, true);
    
    lm's avatar
    lm committed
    
    
                // Mark all parameters as missing
    
    lm's avatar
    lm committed
                for (int i = 0; i < paramCount; ++i)
                {
                    if (!transmissionMissingPackets.value(component)->contains(i))
                    {
    
    lm's avatar
    lm committed
                        transmissionMissingPackets.value(component)->append(i);
                    }
                }
    
    lm's avatar
    lm committed
                // 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
                if (thisTransmissionTimeout > transmissionTimeout)
                {
    
    lm's avatar
    lm committed
                    transmissionTimeout = thisTransmissionTimeout;
                }
    
    lm's avatar
    lm committed
            }
    
    lm's avatar
    lm committed
    
            // Start retransmission guard
            // or reset timer
            setRetransmissionGuardEnabled(true);
    
    lm's avatar
    lm committed
        }
    
    
    lm's avatar
    lm committed
        // Mark this parameter as received in read list
    
    lm's avatar
    lm committed
        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
        bool justWritten = false;
        bool writeMismatch = false;
    
        //bool lastWritten = false;
    
    lm's avatar
    lm committed
        // Mark this parameter as received in write ACK list
    
        QMap<QString, QVariant>* map = transmissionMissingWriteAckPackets.value(component);
    
    lm's avatar
    lm committed
        if (map && map->contains(parameterName))
        {
    
    lm's avatar
    lm committed
            justWritten = true;
    
    lm's avatar
    lm committed
            if (map->value(parameterName) != value)
            {
    
    lm's avatar
    lm committed
                writeMismatch = true;
            }
            map->remove(parameterName);
        }
    
    
    lm's avatar
    lm committed
        foreach (int key, transmissionMissingPackets.keys())
        {
    
            missCount +=  transmissionMissingPackets.value(key)->count();
        }
    
    
    lm's avatar
    lm committed
        int missWriteCount = 0;
    
    lm's avatar
    lm committed
        foreach (int key, transmissionMissingWriteAckPackets.keys())
        {
    
    lm's avatar
    lm committed
            missWriteCount += transmissionMissingWriteAckPackets.value(key)->count();
        }
    
    
    lm's avatar
    lm committed
        if (justWritten && !writeMismatch && missWriteCount == 0)
        {
    
    lm's avatar
    lm committed
            // 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
        } else if (justWritten && !writeMismatch)
        {
    
            statusLabel->setText(tr("SUCCESS: Wrote %2 (#%1/%4): %3").arg(paramId+1).arg(parameterName).arg(value.toDouble()).arg(paramCount));
    
    lm's avatar
    lm committed
            QPalette pal = statusLabel->palette();
            pal.setColor(backgroundRole(), QGC::colorGreen);
            statusLabel->setPalette(pal);
    
    lm's avatar
    lm committed
        } else if (justWritten && writeMismatch)
        {
    
    lm's avatar
    lm committed
            // Mismatch, tell user
    
    lm's avatar
    lm committed
            QPalette pal = statusLabel->palette();
            pal.setColor(backgroundRole(), QGC::colorRed);
            statusLabel->setPalette(pal);
    
            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
        }
        else
        {
            if (missCount > 0)
            {
    
    lm's avatar
    lm committed
                QPalette pal = statusLabel->palette();
                pal.setColor(backgroundRole(), QGC::colorOrange);
                statusLabel->setPalette(pal);
    
    lm's avatar
    lm committed
            }
            else
            {
    
    lm's avatar
    lm committed
                QPalette pal = statusLabel->palette();
                pal.setColor(backgroundRole(), QGC::colorGreen);
                statusLabel->setPalette(pal);
            }
    
            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
        }
    
    lm's avatar
    lm committed
    
        // Check if last parameter was received
    
    lm's avatar
    lm committed
        if (missCount == 0 && missWriteCount == 0)
        {
    
    lm's avatar
    lm committed
            this->transmissionActive = false;
            this->transmissionListMode = false;
            transmissionListSizeKnown.clear();
    
    lm's avatar
    lm committed
            foreach (int key, transmissionMissingPackets.keys())
            {
    
    lm's avatar
    lm committed
                transmissionMissingPackets.value(key)->clear();
            }
        }
    }
    
    
    /**
     * @param uas System which has the component
     * @param component id of the component
     * @param parameterName human friendly name of the parameter
     */
    
    void QGCParamWidget::addParameter(int uas, int component, QString parameterName, QVariant value)
    
    pixhawk's avatar
    pixhawk committed
    {
    
        qDebug() << "PARAM WIDGET GOT PARAM:" << value;
    
        // Reference to item in tree
    
        QTreeWidgetItem* parameterItem = NULL;
    
    pixhawk's avatar
    pixhawk committed
    
        // Get component
    
    lm's avatar
    lm committed
        if (!components->contains(component))
        {
    
    //        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;
    //        }
    
            QString componentName = tr("Component #%1").arg(component);
    
            addComponent(uas, component, componentName);
    
    pixhawk's avatar
    pixhawk committed
        }
    
        // 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
        QString splitToken = "_";
        // Check if auto-grouping can work
    
    lm's avatar
    lm committed
        if (parameterName.contains(splitToken))
        {
    
    pixhawk's avatar
    pixhawk committed
            QString parent = parameterName.section(splitToken, 0, 0, QString::SectionSkipEmpty);
            QMap<QString, QTreeWidgetItem*>* compParamGroups = paramGroups.value(component);
    
            if (!compParamGroups->contains(parent))
            {
    
    pixhawk's avatar
    pixhawk committed
                // Insert group item
                QStringList glist;
                glist.append(parent);
                QTreeWidgetItem* item = new QTreeWidgetItem(glist);
                compParamGroups->insert(parent, item);
                components->value(component)->addChild(item);
    
    
            // Append child to group
            bool found = false;
            QTreeWidgetItem* parentItem = compParamGroups->value(parent);
    
            for (int i = 0; i < parentItem->childCount(); i++) {
    
                QTreeWidgetItem* child = parentItem->child(i);
                QString key = child->data(0, Qt::DisplayRole).toString();
    
                if (key == parameterName)
                {
    
                    //qDebug() << "UPDATED CHILD";
                    parameterItem = child;
                    parameterItem->setData(1, Qt::DisplayRole, value);
                    found = true;
                }
            }
    
    
    lm's avatar
    lm committed
            if (!found)
            {
    
                // Insert parameter into map
                QStringList plist;
                plist.append(parameterName);
    
                // CREATE PARAMETER ITEM
    
                parameterItem = new QTreeWidgetItem(plist);
    
                // CONFIGURE PARAMETER ITEM
    
                parameterItem->setData(1, Qt::DisplayRole, value);
    
    
                compParamGroups->value(parent)->addChild(parameterItem);
    
    lm's avatar
    lm committed
                parameterItem->setFlags(parameterItem->flags() | Qt::ItemIsEditable);
    
    lm's avatar
    lm committed
        }
        else
        {
    
    pixhawk's avatar
    pixhawk committed
            bool found = false;
            QTreeWidgetItem* parent = components->value(component);
    
    lm's avatar
    lm committed
            for (int i = 0; i < parent->childCount(); i++)
            {
    
    pixhawk's avatar
    pixhawk committed
                QTreeWidgetItem* child = parent->child(i);
                QString key = child->data(0, Qt::DisplayRole).toString();
    
    lm's avatar
    lm committed
                if (key == parameterName)
                {
    
    pixhawk's avatar
    pixhawk committed
                    //qDebug() << "UPDATED CHILD";
    
                    parameterItem = child;
                    parameterItem->setData(1, Qt::DisplayRole, value);
    
    pixhawk's avatar
    pixhawk committed
                    found = true;
                }
            }
    
    
    lm's avatar
    lm committed
            if (!found)
            {
    
                // Insert parameter into map
                QStringList plist;
                plist.append(parameterName);
    
                // CREATE PARAMETER ITEM
    
                parameterItem = new QTreeWidgetItem(plist);
    
                // CONFIGURE PARAMETER ITEM
    
                parameterItem->setData(1, Qt::DisplayRole, value);
    
    lm's avatar
    lm committed
                components->value(component)->addChild(parameterItem);
                parameterItem->setFlags(parameterItem->flags() | Qt::ItemIsEditable);
    
    pixhawk's avatar
    pixhawk committed
            }
    
            //tree->expandAll();
    
        // Reset background color
    
        parameterItem->setBackground(0, QBrush(QColor(0, 0, 0)));
        parameterItem->setBackground(1, Qt::NoBrush);
    
    lm's avatar
    lm committed
        // Add tooltip
    
        QString tooltipFormat;
        if (paramDefault.contains(parameterName))
        {
            tooltipFormat = tr("Default: %1, %2");
    
    LM's avatar
    LM committed
            tooltipFormat = tooltipFormat.arg(paramDefault.value(parameterName, 0.0f)).arg(paramToolTips.value(parameterName, ""));
    
        }
        else
        {
            tooltipFormat = paramToolTips.value(parameterName, "");
        }
        parameterItem->setToolTip(0, tooltipFormat);
        parameterItem->setToolTip(1, tooltipFormat);
    
    lm's avatar
    lm committed
    
    
    pixhawk's avatar
    pixhawk committed
        if (changedValues.contains(component)) changedValues.value(component)->remove(parameterName);
    
    /**
     * Send a request to deliver the list of onboard parameters
     * to the MAV.
     */
    
    void QGCParamWidget::requestParameterList()
    {
    
    LM's avatar
    LM committed
        qDebug() << "LOADING PARAM LIST";
    
        if (!mav) return;
    
        // 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
        // Clear view and request param list
    
    lm's avatar
    lm committed
        received.clear();
    
    lm's avatar
    lm committed
        // Clear transmission state
        transmissionListMode = true;
        transmissionListSizeKnown.clear();
    
        foreach (int key, transmissionMissingPackets.keys()) {
    
    lm's avatar
    lm committed
            transmissionMissingPackets.value(key)->clear();
        }
        transmissionActive = true;
    
    lm's avatar
    lm committed
    
        // Set status text
        statusLabel->setText(tr("Requested param list.. waiting"));
    
        // Request twice as mean of forward error correction
        mav->requestParameters();
    
    void QGCParamWidget::parameterItemChanged(QTreeWidgetItem* current, int column)
    
    lm's avatar
    lm committed
    {
    
        if (current && column > 0) {
    
            QTreeWidgetItem* parent = current->parent();
    
            while (parent->parent() != NULL) {
    
                parent = parent->parent();
            }
            // Parent is now top-level component
            int key = components->key(parent);
    
            if (!changedValues.contains(key)) {
    
                changedValues.insert(key, new QMap<QString, QVariant>());
    
            QMap<QString, QVariant>* map = changedValues.value(key, NULL);
    
                QString str = current->data(0, Qt::DisplayRole).toString();
    
                QVariant value = current->data(1, Qt::DisplayRole);
                qDebug() << "CHANGED PARAM:" << value;
    
    lm's avatar
    lm committed
                // Set parameter on changed list to be transmitted to MAV
    
                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)));
                }
    
                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);
    
                    break;
                default:
                    qCritical() << "ABORTED PARAM UPDATE, NO VALID QVARIANT TYPE";
                    return;
    
                }
            }
        }
    }
    
    void QGCParamWidget::saveParameters()
    
        if (!mav) return;
    
        QString fileName = QFileDialog::getSaveFileName(this, tr("Save File"), "./parameters.txt", tr("Parameter File (*.txt)"));
        QFile file(fileName);
    
        if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
    
            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
    
        QMap<int, QMap<QString, QVariant>*>::iterator i;
    
        for (i = parameters.begin(); i != parameters.end(); ++i) {
    
            // Iterate through the parameters of the component
            int compid = i.key();
    
            QMap<QString, QVariant>* comp = i.value();
    
                QMap<QString, QVariant>::iterator j;
                for (j = comp->begin(); j != comp->end(); ++j)
                {
    
                    QString paramValue("%1");
    
                    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";
    
                    in.flush();
                }
            }
        }
        file.close();
    }
    
    void QGCParamWidget::loadParameters()
    {
    
        if (!mav) return;
    
        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);
    
        while (!in.atEnd()) {
    
            QString line = in.readLine();
    
            if (!line.startsWith("#")) {
    
                QStringList wpParams = line.split("\t");
    
                if (wpParams.size() == 5) {
    
                    // Only load parameters for right mav
    
                    if (mav->getUASID() == wpParams.at(0).toInt()) {
    
    pixhawk's avatar
    pixhawk committed
    
    
    lm's avatar
    lm committed
                        bool changed = false;
                        int component = wpParams.at(1).toInt();
                        QString parameterName = wpParams.at(2);
    
                        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
                            changed = true;
                        }
    
                        // Set parameter value
    
                        addParameter(wpParams.at(0).toInt(), wpParams.at(1).toInt(), wpParams.at(2), wpParams.at(3).toDouble());
    
    lm's avatar
    lm committed
    
    
                        if (changed) {
    
    lm's avatar
    lm committed
                            // Create changed values data structure if necessary
    
                            if (!changedValues.contains(wpParams.at(1).toInt())) {
    
                                changedValues.insert(wpParams.at(1).toInt(), new QMap<QString, QVariant>());
    
    lm's avatar
    lm committed
                            // Add to changed values
    
                            if (changedValues.value(wpParams.at(1).toInt())->contains(wpParams.at(2))) {
    
    lm's avatar
    lm committed
                                changedValues.value(wpParams.at(1).toInt())->remove(wpParams.at(2));
                            }
    
    
                            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
    
    
                            qDebug() << "MARKING COMP" << wpParams.at(1).toInt() << "PARAM" << wpParams.at(2) << "VALUE" << (float)wpParams.at(3).toDouble() << "AS CHANGED";
    
    lm's avatar
    lm committed
    
                            // Mark in UI
    
    pixhawk's avatar
    pixhawk committed
    
    
    pixhawk's avatar
    pixhawk committed
    
    
    lm's avatar
    lm committed
                }
            }
    
    lm's avatar
    lm committed
    }
    
    
    /**
     * 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)
    {
    
        if (enabled) {
    
            retransmissionTimer.start(retransmissionTimeout);
    
            retransmissionTimer.stop();
        }
    }
    
    void QGCParamWidget::retransmissionGuardTick()
    {
    
        if (transmissionActive) {
    
            qDebug() << __FILE__ << __LINE__ << "RETRANSMISSION GUARD ACTIVE, CHECKING FOR DROPS..";
    
    
    lm's avatar
    lm committed
            // Check for timeout
            // stop retransmission attempts on timeout
    
            if (QGC::groundTimeMilliseconds() > transmissionTimeout) {
    
    lm's avatar
    lm committed
                setRetransmissionGuardEnabled(false);
                transmissionActive = false;
    
                // Empty read retransmission list
                // Empty write retransmission list
                int missingReadCount = 0;
                QList<int> readKeys = transmissionMissingPackets.keys();
    
                foreach (int component, readKeys) {
    
    lm's avatar
    lm committed
                    missingReadCount += transmissionMissingPackets.value(component)->count();
                    transmissionMissingPackets.value(component)->clear();
                }
    
                // Empty write retransmission list
                int missingWriteCount = 0;
                QList<int> writeKeys = transmissionMissingWriteAckPackets.keys();
    
                foreach (int component, writeKeys) {
    
    lm's avatar
    lm committed
                    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
    
            QMap<int, QMap<QString, QVariant>*>::iterator i;
    
            for (i = parameters.begin(); i != parameters.end(); ++i) {
    
                // Iterate through the parameters of the component
                int component = i.key();
    
    lm's avatar
    lm committed
                // Request n parameters from this component (at maximum)
    
                QList<int> * paramList = transmissionMissingPackets.value(component, NULL);
    
                if (paramList) {
    
                    foreach (int id, *paramList) {
                        if (count < retransmissionBurstRequestSize) {
    
                            qDebug() << __FILE__ << __LINE__ << "RETRANSMISSION GUARD REQUESTS RETRANSMISSION OF PARAM #" << id << "FROM COMPONENT #" << component;
                            emit requestParameter(component, id);
    
    lm's avatar
    lm committed
                            statusLabel->setText(tr("Requested retransmission of #%1").arg(id+1));
    
    lm's avatar
    lm committed
    
            // Re-request at maximum retransmissionBurstRequestSize parameters at once
            // to prevent write-request link flooding
            // Empty write retransmission list
            QList<int> writeKeys = transmissionMissingWriteAckPackets.keys();
    
            foreach (int component, writeKeys) {
    
    lm's avatar
    lm committed
                int count = 0;
    
                QMap <QString, QVariant>* missingParams = transmissionMissingWriteAckPackets.value(component);
    
                foreach (QString key, missingParams->keys()) {
                    if (count < retransmissionBurstRequestSize) {
    
    lm's avatar
    lm committed
                        // Re-request write operation
    
                        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;
                        }
    
                        statusLabel->setText(tr("Requested rewrite of: %1: %2").arg(key).arg(missingParams->value(key).toDouble()));
    
    lm's avatar
    lm committed
                        count++;
    
    lm's avatar
    lm committed
                        break;
                    }
                }
            }
    
            qDebug() << __FILE__ << __LINE__ << "STOPPING RETRANSMISSION GUARD GRACEFULLY";
            setRetransmissionGuardEnabled(false);
        }
    }
    
    
    /**
     * The .. signal is emitted
     */
    void QGCParamWidget::requestParameterUpdate(int component, const QString& parameter)
    {
    
        if (mav) mav->requestParameter(component, parameter);
    
    /**
     * @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
     */
    
    void QGCParamWidget::setParameter(int component, QString parameterName, QVariant value)
    
        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;
        }
    
    
        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
        // Wait for parameter to be written back
        // mark it therefore as missing
    
        if (!transmissionMissingWriteAckPackets.contains(component))
        {
            transmissionMissingWriteAckPackets.insert(component, new QMap<QString, QVariant>());
    
    lm's avatar
    lm committed
        }
    
        // Insert it in missing write ACK list
        transmissionMissingWriteAckPackets.value(component)->insert(parameterName, value);
    
        // Set timeouts
        transmissionActive = true;
        quint64 newTransmissionTimeout = QGC::groundTimeMilliseconds() + 5*rewriteTimeout;
    
        if (newTransmissionTimeout > transmissionTimeout) {
    
    lm's avatar
    lm committed
            transmissionTimeout = newTransmissionTimeout;
        }
        // Enable guard / reset timeouts
        setRetransmissionGuardEnabled(true);
    
    /**
     * Set all parameter in the parameter tree on the MAV
     */
    
        // Iterate through all components, through all parameters and emit them
    
        QMap<int, QMap<QString, QVariant>*>::iterator i;