APMAirframeComponentController.cc 13.3 KB
Newer Older
1 2
/****************************************************************************
 *
Gus Grubba's avatar
Gus Grubba committed
3
 * (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
4 5 6 7 8 9
 *
 * QGroundControl is licensed according to the terms in the file
 * COPYING.md in the root of the source code directory.
 *
 ****************************************************************************/

10 11 12 13
#include "APMAirframeComponentController.h"
#include "QGCMAVLink.h"
#include "MultiVehicleManager.h"
#include "QGCApplication.h"
Don Gagne's avatar
Don Gagne committed
14
#include "QGCFileDownload.h"
15
#include "ParameterManager.h"
16 17
#include "ArduCopterFirmwarePlugin.h"
#include "ArduRoverFirmwarePlugin.h"
18 19 20

#include <QVariant>
#include <QQmlProperty>
Don Gagne's avatar
Don Gagne committed
21
#include <QStandardPaths>
Don Gagne's avatar
Don Gagne committed
22
#include <QDir>
Don Gagne's avatar
Don Gagne committed
23 24
#include <QJsonParseError>
#include <QJsonObject>
25

26
// These should match the ArduCopter FRAME_CLASS parameter enum meta data
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
#define FRAME_CLASS_UNDEFINED       0
#define FRAME_CLASS_QUAD            1
#define FRAME_CLASS_HEX             2
#define FRAME_CLASS_OCTA            3
#define FRAME_CLASS_OCTAQUAD        4
#define FRAME_CLASS_Y6              5
#define FRAME_CLASS_HELI            6
#define FRAME_CLASS_TRI             7
#define FRAME_CLASS_SINGLECOPTER    8
#define FRAME_CLASS_COAXCOPTER      9
#define FRAME_CLASS_BICOPTER        10
#define FRAME_CLASS_HELI_DUAL       11
#define FRAME_CLASS_DODECAHEXA      12
#define FRAME_CLASS_HELIQUAD        13

42
// These should match the ArduCopter FRAME_TYPE parameter enum meta data
43 44 45 46 47 48 49 50 51 52 53 54
#define FRAME_TYPE_PLUS         0
#define FRAME_TYPE_X            1
#define FRAME_TYPE_V            2
#define FRAME_TYPE_H            3
#define FRAME_TYPE_V_TAIL       4
#define FRAME_TYPE_A_TAIL       5
#define FRAME_TYPE_Y6B          10
#define FRAME_TYPE_Y6F          11
#define FRAME_TYPE_BETAFLIGHTX  12
#define FRAME_TYPE_DJIX         13
#define FRAME_TYPE_CLOCKWISEX   14

55 56 57 58 59 60 61 62 63 64 65
// These should match the Rover FRAME_CLASS parameter enum meta data
#define FRAME_CLASS_ROVER       1
#define FRAME_CLASS_BOAT        2
#define FRAME_CLASS_BALANCEBOT  3

// These should match the Rover FRAME_TYPE parameter enum meta data
#define FRAME_TYPE_UNDEFINED    0
#define FRAME_TYPE_OMNI3        1
#define FRAME_TYPE_OMNIX        2
#define FRAME_TYPE_OMNIPLUS     3

66 67 68 69 70 71
typedef struct {
    int         frameClass;
    int         frameType;
    const char* imageResource;
} FrameToImageInfo_t;

72
static const FrameToImageInfo_t s_rgFrameToImageCopter[] = {
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
    { FRAME_CLASS_QUAD,         FRAME_TYPE_X,       "QuadRotorX" },             // Default
    { FRAME_CLASS_QUAD,         FRAME_TYPE_PLUS,    "QuadRotorPlus" },
    { FRAME_CLASS_QUAD,         FRAME_TYPE_V,       "QuadRotorWide" },
    { FRAME_CLASS_QUAD,         FRAME_TYPE_H,       "QuadRotorH" },
    { FRAME_CLASS_QUAD,         FRAME_TYPE_V_TAIL,  "QuadRotorVTail" },
    { FRAME_CLASS_QUAD,         FRAME_TYPE_A_TAIL,  "QuadRotorATail" },

    { FRAME_CLASS_HEX,          FRAME_TYPE_X,       "HexaRotorX" },             // Default
    { FRAME_CLASS_HEX,          FRAME_TYPE_PLUS,    "HexaRotorPlus" },

    { FRAME_CLASS_OCTA,         FRAME_TYPE_X,       "OctoRotorX" },             // Default
    { FRAME_CLASS_OCTA,         FRAME_TYPE_PLUS,    "OctoRotorPlus" },
    { FRAME_CLASS_OCTA,         FRAME_TYPE_V,       "AirframeUnknown" },
    { FRAME_CLASS_OCTA,         FRAME_TYPE_H,       "AirframeUnknown" },

    { FRAME_CLASS_OCTAQUAD,     FRAME_TYPE_X,       "OctoRotorXCoaxial" },      // Default
    { FRAME_CLASS_OCTAQUAD,     FRAME_TYPE_PLUS,    "OctoRotorPlusCoaxial" },
    { FRAME_CLASS_OCTAQUAD,     FRAME_TYPE_V,       "AirframeUnknown" },
    { FRAME_CLASS_OCTAQUAD,     FRAME_TYPE_H,       "AirframeUnknown" },

    { FRAME_CLASS_Y6,           FRAME_TYPE_Y6B,     "Y6B" },                    // Default
    { FRAME_CLASS_Y6,           FRAME_TYPE_Y6F,     "AirframeUnknown" },
    { FRAME_CLASS_Y6,           -1,                 "Y6A" },

    { FRAME_CLASS_DODECAHEXA,   FRAME_TYPE_X,       "AirframeUnknown" },        // Default
    { FRAME_CLASS_DODECAHEXA,   FRAME_TYPE_PLUS,    "AirframeUnknown" },

    { FRAME_CLASS_HELI,         -1,                 "Helicopter" },
    { FRAME_CLASS_TRI,          -1,                 "YPlus" },
102 103
};

104 105 106 107 108
static const FrameToImageInfo_t s_rgFrameToImageRover[] = {
    { FRAME_CLASS_ROVER,    -1, "Rover" },
    { FRAME_CLASS_BOAT,     -1, "Boat" },
};

109 110 111
/// Returns the image resource for the frameClass, frameType pair
///     @param[in,out] frameType Specified frame type, or -1 to match first item in list (frameType found will be returned)
static QString s_findImageResourceCopter(int frameClass, int& frameType)
112
{
113 114
    for (size_t i=0; i<sizeof(s_rgFrameToImageCopter)/sizeof(s_rgFrameToImageCopter[0]); i++) {
        const FrameToImageInfo_t* pFrameToImageInfo = &s_rgFrameToImageCopter[i];
115

116 117 118
        if ((pFrameToImageInfo->frameClass == frameClass && frameType == -1) ||
                (pFrameToImageInfo->frameClass == frameClass && pFrameToImageInfo->frameType == frameType)) {
            frameType = pFrameToImageInfo->frameType;
119 120
            return pFrameToImageInfo->imageResource;
        }
121
    }
122

123
    return QStringLiteral("AirframeUnknown");
124 125
}

126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
static QString s_findImageResourceRover(int frameClass, int frameType)
{
    Q_UNUSED(frameType);

    for (size_t i=0; i<sizeof(s_rgFrameToImageRover)/sizeof(s_rgFrameToImageRover[0]); i++) {
        const FrameToImageInfo_t* pFrameToImageInfo = &s_rgFrameToImageRover[i];

        if (pFrameToImageInfo->frameClass == frameClass) {
            return pFrameToImageInfo->imageResource;
        }
    }

    return QStringLiteral("AirframeUnknown");
}

141
APMAirframeComponentController::APMAirframeComponentController(void)
142
    : _frameClassFact   (getParameterFact(FactSystem::defaultComponentId, QStringLiteral("FRAME_CLASS"), false /* reportMissing */))
143
    , _frameTypeFact    (getParameterFact(FactSystem::defaultComponentId, QStringLiteral("FRAME_TYPE"), false /* reportMissing */))
144
    , _frameClassModel  (new QmlObjectListModel(this))
145
{
146
    _fillFrameClasses();
147 148
}

149
APMAirframeComponentController::~APMAirframeComponentController()
150
{
151

152 153
}

154
void APMAirframeComponentController::_fillFrameClasses()
155
{
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
    FirmwarePlugin* fwPlugin = _vehicle->firmwarePlugin();

    if (qobject_cast<ArduCopterFirmwarePlugin*>(fwPlugin)) {
        QList<int> frameTypeNotSupported;

        frameTypeNotSupported << FRAME_CLASS_HELI
                              << FRAME_CLASS_SINGLECOPTER
                              << FRAME_CLASS_COAXCOPTER
                              << FRAME_CLASS_BICOPTER
                              << FRAME_CLASS_HELI_DUAL
                              << FRAME_CLASS_HELIQUAD;

        for (int i=1; i<_frameClassFact->enumStrings().count(); i++) {
            QString frameClassName =    _frameClassFact->enumStrings()[i];
            int     frameClass =        _frameClassFact->enumValues()[i].toInt();

            if (frameClass == FRAME_CLASS_HELI) {
                // Heli requires it's own firmware variant. You can't switch to Heli from a Copter variant firmware.
                continue;
            }

177
            _frameClassModel->append(new APMFrameClass(frameClassName, true /* copter */, frameClass, _frameTypeFact, _frameClassModel));
178 179 180 181 182
        }
    } else if (qobject_cast<ArduRoverFirmwarePlugin*>(fwPlugin)) {
        for (int i=1; i<_frameClassFact->enumStrings().count(); i++) {
            QString frameClassName =    _frameClassFact->enumStrings()[i];
            int     frameClass =        _frameClassFact->enumValues()[i].toInt();
183
            _frameClassModel->append(new APMFrameClass(frameClassName, false /* copter */, frameClass, _frameTypeFact, _frameClassModel));
184 185 186 187
        }
    }
}

Don Gagne's avatar
Don Gagne committed
188 189 190 191 192 193 194 195
void APMAirframeComponentController::_loadParametersFromDownloadFile(const QString& downloadedParamFile)
{
    QFile parametersFile(downloadedParamFile);
    if (!parametersFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
        qWarning() << "Unable to open downloaded parameter file" << downloadedParamFile << parametersFile.errorString();
        qgcApp()->restoreOverrideCursor();
        return;
    }
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210

    QTextStream reader(&parametersFile);

    while (!reader.atEnd()) {
        QString line = reader.readLine().trimmed();
        if (line.isEmpty() || line.at(0) == QChar('#')) {
            continue;
        }

        QStringList aux = line.split(',');
        if (parameterExists(-1, aux.at(0))) {
            Fact *param = getParameterFact(-1, aux.at(0));
            param->setRawValue(QVariant::fromValue(aux.at(1)));
        }
    }
Don Gagne's avatar
Don Gagne committed
211
    qgcApp()->restoreOverrideCursor();
212
    _vehicle->parameterManager()->refreshAllParameters();
213 214
}

Don Gagne's avatar
Don Gagne committed
215
void APMAirframeComponentController::loadParameters(const QString& paramFile)
216 217
{
    qgcApp()->setOverrideCursor(Qt::WaitCursor);
Don Gagne's avatar
Don Gagne committed
218 219 220 221 222 223 224

    QString paramFileUrl = QStringLiteral("https://api.github.com/repos/ArduPilot/ardupilot/contents/Tools/Frame_params/%1?ref=master");

    QGCFileDownload* downloader = new QGCFileDownload(this);
    connect(downloader, &QGCFileDownload::downloadFinished, this, &APMAirframeComponentController::_githubJsonDownloadFinished);
    connect(downloader, &QGCFileDownload::error, this, &APMAirframeComponentController::_githubJsonDownloadError);
    downloader->download(paramFileUrl.arg(paramFile));
225 226
}

Don Gagne's avatar
Don Gagne committed
227
void APMAirframeComponentController::_githubJsonDownloadFinished(QString remoteFile, QString localFile)
228
{
Don Gagne's avatar
Don Gagne committed
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
    Q_UNUSED(remoteFile);

    QFile jsonFile(localFile);
    if (!jsonFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
        qWarning() << "Unable to open github json file" << localFile << jsonFile.errorString();
        qgcApp()->restoreOverrideCursor();
        return;
    }
    QByteArray bytes = jsonFile.readAll();
    jsonFile.close();

    QJsonParseError jsonParseError;
    QJsonDocument doc = QJsonDocument::fromJson(bytes, &jsonParseError);
    if (jsonParseError.error != QJsonParseError::NoError) {
        qWarning() <<  "Unable to open json document" << localFile << jsonParseError.errorString();
        qgcApp()->restoreOverrideCursor();
        return;
    }
    QJsonObject json = doc.object();

    QGCFileDownload* downloader = new QGCFileDownload(this);
    connect(downloader, &QGCFileDownload::downloadFinished, this, &APMAirframeComponentController::_paramFileDownloadFinished);
    connect(downloader, &QGCFileDownload::error, this, &APMAirframeComponentController::_paramFileDownloadError);
Don Gagne's avatar
Don Gagne committed
252
    downloader->download(json[QLatin1Literal("download_url")].toString());
Don Gagne's avatar
Don Gagne committed
253 254 255 256
}

void APMAirframeComponentController::_githubJsonDownloadError(QString errorMsg)
{
257
    qgcApp()->showAppMessage(tr("Param file github json download failed: %1").arg(errorMsg));
Don Gagne's avatar
Don Gagne committed
258
    qgcApp()->restoreOverrideCursor();
259 260
}

Don Gagne's avatar
Don Gagne committed
261 262 263 264 265 266 267 268 269
void APMAirframeComponentController::_paramFileDownloadFinished(QString remoteFile, QString localFile)
{
    Q_UNUSED(remoteFile);

    _loadParametersFromDownloadFile(localFile);
}

void APMAirframeComponentController::_paramFileDownloadError(QString errorMsg)
{
270
    qgcApp()->showAppMessage(tr("Param file download failed: %1").arg(errorMsg));
Don Gagne's avatar
Don Gagne committed
271 272
    qgcApp()->restoreOverrideCursor();
}
273

274
APMFrameClass::APMFrameClass(const QString& name, bool copter, int frameClass, Fact* frameTypeFact, QObject* parent)
275 276
    : QObject               (parent)
    , _name                 (name)
277
    , _copter               (copter)
278
    , _frameClass           (frameClass)
279 280
    , _defaultFrameType     (-1)
    , _frameTypeSupported   (false)
281 282
    , _frameTypeFact        (frameTypeFact)
{
283 284 285 286
    if (frameTypeFact) {
        connect(frameTypeFact, &Fact::rawValueChanged, this, &APMFrameClass::imageResourceChanged);
        connect(frameTypeFact, &Fact::rawValueChanged, this, &APMFrameClass::frameTypeChanged);
    }
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322

    if (copter) {
        QList<int> rgSupportedFrameTypes;

        for (size_t i=0; i<sizeof(s_rgFrameToImageCopter)/sizeof(s_rgFrameToImageCopter[0]); i++) {
            const FrameToImageInfo_t* pFrameToImageInfo = &s_rgFrameToImageCopter[i];

            if (pFrameToImageInfo->frameClass == frameClass) {
                if (_defaultFrameType == -1) {
                    // Default frame type/icon is the first item found to match frameClass
                    _defaultFrameType = pFrameToImageInfo->frameType;
                    _imageResourceDefault = QStringLiteral("/qmlimages/Airframe/%1").arg(pFrameToImageInfo->imageResource);
                }

                if (pFrameToImageInfo->frameType != -1) {
                    // The list includes the supported frame types for the class
                    rgSupportedFrameTypes.append(pFrameToImageInfo->frameType);
                }
            }
        }
        if (_imageResourceDefault.isEmpty()) {
            _imageResourceDefault = QStringLiteral("/qmlimages/Airframe/AirframeUnknown");
        }

        // Filter the enums
        for (const int frameType: rgSupportedFrameTypes) {
            int index = frameTypeFact->enumValues().indexOf(frameType);
            if (index != -1) {
                _frameTypeEnumValues.append(frameType);
                _frameTypeEnumStrings.append(frameTypeFact->enumStrings()[index]);
            }
        }
    }

    // If the frameClass is not in the list then frame type is not supported
    _frameTypeSupported = _defaultFrameType != -1;
323 324 325 326 327 328 329 330 331
}

APMFrameClass::~APMFrameClass()
{

}

QString APMFrameClass::imageResource(void)
{
332 333 334 335 336 337 338 339 340 341
    QString imageResource;

    int frameType = _frameTypeFact ? _frameTypeFact->rawValue().toInt() : -1;

    if (_copter) {
        imageResource = s_findImageResourceCopter(_frameClass, frameType);
    } else {
        imageResource = s_findImageResourceRover(_frameClass, frameType);
    }
    return QStringLiteral("/qmlimages/Airframe/%1").arg(imageResource);
342
}