APMAirframeComponentController.cc 13.1 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

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

    QGCFileDownload* downloader = new QGCFileDownload(this);
222
    connect(downloader, &QGCFileDownload::downloadComplete, this, &APMAirframeComponentController::_githubJsonDownloadComplete);
Don Gagne's avatar
Don Gagne committed
223
    downloader->download(paramFileUrl.arg(paramFile));
224 225
}

226
void APMAirframeComponentController::_githubJsonDownloadComplete(QString /*remoteFile*/, QString localFile, QString errorMsg)
227
{
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
    if (errorMsg.isEmpty()) {
        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();
Don Gagne's avatar
Don Gagne committed
246

247 248
        QGCFileDownload* downloader = new QGCFileDownload(this);
        connect(downloader, &QGCFileDownload::downloadComplete, this, &APMAirframeComponentController::_paramFileDownloadComplete);
249
        downloader->download(json[QLatin1String("download_url")].toString());
250 251
    } else {
        qgcApp()->showAppMessage(tr("Param file github json download failed: %1").arg(errorMsg));
Don Gagne's avatar
Don Gagne committed
252 253 254 255
        qgcApp()->restoreOverrideCursor();
    }
}

256
void APMAirframeComponentController::_paramFileDownloadComplete(QString /*remoteFile*/, QString localFile, QString errorMsg)
Don Gagne's avatar
Don Gagne committed
257
{
258 259 260 261 262 263
    if (errorMsg.isEmpty()) {
        _loadParametersFromDownloadFile(localFile);
    } else {
        qgcApp()->showAppMessage(tr("Param file download failed: %1").arg(errorMsg));
        qgcApp()->restoreOverrideCursor();
    }
Don Gagne's avatar
Don Gagne committed
264
}
265

266
APMFrameClass::APMFrameClass(const QString& name, bool copter, int frameClass, Fact* frameTypeFact, QObject* parent)
267 268
    : QObject               (parent)
    , _name                 (name)
269
    , _copter               (copter)
270
    , _frameClass           (frameClass)
271 272
    , _defaultFrameType     (-1)
    , _frameTypeSupported   (false)
273 274
    , _frameTypeFact        (frameTypeFact)
{
275 276 277 278
    if (frameTypeFact) {
        connect(frameTypeFact, &Fact::rawValueChanged, this, &APMFrameClass::imageResourceChanged);
        connect(frameTypeFact, &Fact::rawValueChanged, this, &APMFrameClass::frameTypeChanged);
    }
279 280 281 282 283 284 285 286 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

    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;
315 316 317 318 319 320 321 322 323
}

APMFrameClass::~APMFrameClass()
{

}

QString APMFrameClass::imageResource(void)
{
324 325 326 327 328 329 330 331 332 333
    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);
334
}