SimpleMissionItemTest.cc 17.6 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
#include "SimpleMissionItemTest.h"
11
#include "QGCApplication.h"
12
#include "QGroundControlQmlGlobal.h"
13
#include "SettingsManager.h"
14
#include "PlanMasterController.h"
15

16
static const ItemInfo_t _rgItemInfo[] = {
17
    { MAV_CMD_NAV_WAYPOINT,     MAV_FRAME_GLOBAL_RELATIVE_ALT },
18
    { MAV_CMD_NAV_LOITER_UNLIM, MAV_FRAME_GLOBAL },
19
    { MAV_CMD_NAV_LOITER_TURNS, MAV_FRAME_GLOBAL_RELATIVE_ALT },
20
    { MAV_CMD_NAV_LOITER_TIME,  MAV_FRAME_GLOBAL },
21
    { MAV_CMD_NAV_LAND,         MAV_FRAME_GLOBAL_RELATIVE_ALT },
22
    { MAV_CMD_NAV_TAKEOFF,      MAV_FRAME_GLOBAL },
23 24 25
    { MAV_CMD_DO_JUMP,          MAV_FRAME_MISSION },
};

26 27 28 29 30 31 32 33
static const FactValue_t _rgFactValuesWaypoint[] = {
    { "Hold",   QGCMAVLink::VehicleClassMultiRotor, false,  1 },
    { "Yaw",    QGCMAVLink::VehicleClassMultiRotor, true,   4 },
};

static const FactValue_t _rgFactValuesLoiterUnlim[] = {
    { "Radius", QGCMAVLink::VehicleClassFixedWing,  false,  3 },
    { "Yaw",    QGCMAVLink::VehicleClassMultiRotor, true,   4 },
34 35
};

36 37 38
static const FactValue_t _rgFactValuesLoiterTurns[] = {
    { "Turns",  QGCMAVLink::VehicleClassFixedWing,  false, 1 },
    { "Radius", QGCMAVLink::VehicleClassFixedWing,  false, 3 },
39 40
};

41 42 43
static const FactValue_t _rgFactValuesLoiterTime[] = {
    { "Loiter Time",    QGCMAVLink::VehicleClassGeneric,    false, 1 },
    { "Radius",         QGCMAVLink::VehicleClassFixedWing,  false, 3 },
44 45
};

46 47
static const FactValue_t _rgFactValuesLand[] = {
    { "Yaw", QGCMAVLink::VehicleClassMultiRotor, true, 4 },
48 49
};

50 51 52
static const FactValue_t _rgFactValuesTakeoff[] = {
    { "Pitch",  QGCMAVLink::VehicleClassFixedWing,  false,  1 },
    { "Yaw",    QGCMAVLink::VehicleClassMultiRotor, true,   4 },
53 54
};

55 56 57
static const FactValue_t _rgFactValuesDoJump[] = {
    { "Item #", QGCMAVLink::VehicleClassGeneric, false, 1 },
    { "Repeat", QGCMAVLink::VehicleClassGeneric, false, 2 },
58 59
};

60 61 62 63 64 65 66 67
const ItemExpected_t _rgItemExpected[] = {
    { sizeof(_rgFactValuesWaypoint)/sizeof(_rgFactValuesWaypoint[0]),       _rgFactValuesWaypoint,      70.1234567, QGroundControlQmlGlobal::AltitudeModeRelative },
    { sizeof(_rgFactValuesLoiterUnlim)/sizeof(_rgFactValuesLoiterUnlim[0]), _rgFactValuesLoiterUnlim,   70.1234567, QGroundControlQmlGlobal::AltitudeModeAbsolute },
    { sizeof(_rgFactValuesLoiterTurns)/sizeof(_rgFactValuesLoiterTurns[0]), _rgFactValuesLoiterTurns,   70.1234567, QGroundControlQmlGlobal::AltitudeModeRelative },
    { sizeof(_rgFactValuesLoiterTime)/sizeof(_rgFactValuesLoiterTime[0]),   _rgFactValuesLoiterTime,    70.1234567, QGroundControlQmlGlobal::AltitudeModeAbsolute },
    { sizeof(_rgFactValuesLand)/sizeof(_rgFactValuesLand[0]),               _rgFactValuesLand,          70.1234567, QGroundControlQmlGlobal::AltitudeModeRelative },
    { sizeof(_rgFactValuesTakeoff)/sizeof(_rgFactValuesTakeoff[0]),         _rgFactValuesTakeoff,       70.1234567, QGroundControlQmlGlobal::AltitudeModeAbsolute },
    { sizeof(_rgFactValuesDoJump)/sizeof(_rgFactValuesDoJump[0]),           _rgFactValuesDoJump,        qQNaN(),    QGroundControlQmlGlobal::AltitudeModeRelative },
68 69 70
};

SimpleMissionItemTest::SimpleMissionItemTest(void)
71
    : _simpleItem(nullptr)
72 73 74 75
{
    
}

Don Gagne's avatar
Don Gagne committed
76 77
void SimpleMissionItemTest::init(void)
{
78 79
    VisualMissionItemTest::init();

80
    rgSimpleItemSignals[commandChangedIndex] =                          SIGNAL(commandChanged(int));
81
    rgSimpleItemSignals[altitudeModeChangedIndex] =                     SIGNAL(altitudeModeChanged());
82 83 84
    rgSimpleItemSignals[friendlyEditAllowedChangedIndex] =              SIGNAL(friendlyEditAllowedChanged(bool));
    rgSimpleItemSignals[headingDegreesChangedIndex] =                   SIGNAL(headingDegreesChanged(double));
    rgSimpleItemSignals[rawEditChangedIndex] =                          SIGNAL(rawEditChanged(bool));
85 86
    rgSimpleItemSignals[cameraSectionChangedIndex] =                    SIGNAL(cameraSectionChanged(QObject*));
    rgSimpleItemSignals[speedSectionChangedIndex] =                     SIGNAL(speedSectionChanged(QObject*));
87 88 89 90 91 92 93 94 95 96 97 98 99

    MissionItem missionItem(1,              // sequence number
                            MAV_CMD_NAV_WAYPOINT,
                            MAV_FRAME_GLOBAL_RELATIVE_ALT,
                            10.1234567,     // param 1-7
                            20.1234567,
                            30.1234567,
                            40.1234567,
                            50.1234567,
                            60.1234567,
                            70.1234567,
                            true,           // autoContinue
                            false);         // isCurrentItem
100
    _simpleItem = new SimpleMissionItem(_masterController, false /* flyView */, missionItem, this);
101 102 103 104

    // It's important top check that the right signals are emitted at the right time since that drives ui change.
    // It's also important to check that things are not being over-signalled when they should not be, since that can lead
    // to incorrect ui or perf impact of uneeded signals propogating ui change.
Don Gagne's avatar
Don Gagne committed
105

106 107
    _spySimpleItem = new MultiSignalSpy();
    QCOMPARE(_spySimpleItem->init(_simpleItem, rgSimpleItemSignals, cSimpleItemSignals), true);
108
    VisualMissionItemTest::_createSpy(_simpleItem, &_spyVisualItem);
Don Gagne's avatar
Don Gagne committed
109 110 111 112
}

void SimpleMissionItemTest::cleanup(void)
{
113 114
    delete _simpleItem;
    VisualMissionItemTest::cleanup();
Don Gagne's avatar
Don Gagne committed
115 116
}

117
bool SimpleMissionItemTest::_classMatch(QGCMAVLink::VehicleClass_t vehicleClass, QGCMAVLink::VehicleClass_t testClass)
118
{
119 120 121 122 123 124 125 126 127 128
    return vehicleClass == QGCMAVLink::VehicleClassGeneric || vehicleClass == testClass;
}

void SimpleMissionItemTest::_testEditorFactsWorker(QGCMAVLink::VehicleClass_t vehicleClass, QGCMAVLink::VehicleClass_t vtolMode, const ItemExpected_t* rgExpected)
{
    qDebug() << "vehicleClass:vtolMode" << QGCMAVLink::vehicleClassToString(vehicleClass) << QGCMAVLink::vehicleClassToString(vtolMode);

    PlanMasterController planController(MAV_AUTOPILOT_PX4, QGCMAVLink::vehicleClassToMavType(vehicleClass));

    QGCMAVLink::VehicleClass_t commandVehicleClass = vtolMode == QGCMAVLink::VehicleClassGeneric ? vehicleClass : vtolMode;
129

130
    for (size_t i=0; i<sizeof(_rgItemInfo)/sizeof(_rgItemInfo[0]); i++) {
131 132 133
        const ItemInfo_t*       info        = &_rgItemInfo[i];
        const ItemExpected_t*   expected    = &rgExpected[i];

Don Gagne's avatar
Don Gagne committed
134
        qDebug() << "Command" << info->command;
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152

        // Determine how many fact values we should get back
        int cExpectedTextFieldFacts = 0;
        int cExpectedNaNFieldFacts  = 0;
        for (size_t j=0; j<expected->cFactValues; j++) {
            const FactValue_t* factValue = &expected->rgFactValues[j];

            if (!_classMatch(factValue->vehicleClass, commandVehicleClass)) {
                continue;
            }
            if (factValue->nanValue) {
                cExpectedNaNFieldFacts++;

            } else {
                cExpectedTextFieldFacts++;
            }
        }

153 154 155 156 157 158 159 160 161 162 163 164
        MissionItem missionItem(1,              // sequence number
                                info->command,
                                info->frame,
                                10.1234567,     // param 1-7
                                20.1234567,
                                30.1234567,
                                40.1234567,
                                50.1234567,
                                60.1234567,
                                70.1234567,
                                true,           // autoContinue
                                false);         // isCurrentItem
165 166 167 168 169 170 171 172
        SimpleMissionItem simpleMissionItem(&planController, false /* flyView */, missionItem, nullptr);

        MissionController::MissionFlightStatus_t missionFlightStatus;
        missionFlightStatus.vtolMode        = vtolMode;
        missionFlightStatus.vehicleSpeed    = 10;
        missionFlightStatus.gimbalYaw       = qQNaN();
        missionFlightStatus.gimbalPitch     = qQNaN();
        simpleMissionItem.setMissionFlightStatus(missionFlightStatus);
173 174 175

        // Validate that the fact values are correctly returned

176
        int foundTextFieldCount = 0;
177 178
        for (int i=0; i<simpleMissionItem.textFieldFacts()->count(); i++) {
            Fact* fact = qobject_cast<Fact*>(simpleMissionItem.textFieldFacts()->get(i));
179

180 181 182
            bool found = false;
            for (size_t j=0; j<expected->cFactValues; j++) {
                const FactValue_t* factValue = &expected->rgFactValues[j];
183 184 185 186 187

                if (!_classMatch(factValue->vehicleClass, commandVehicleClass)) {
                    continue;
                }

188
                if (factValue->name == fact->name()) {
189 190
                    QCOMPARE(fact->rawValue().toDouble(), (factValue->paramIndex * 10.0) + 0.1234567);
                    foundTextFieldCount ++;
191 192 193 194
                    found = true;
                    break;
                }
            }
195

196
            qDebug() << "textFieldFact" << fact->name();
197 198
            QVERIFY(found);
        }
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
        QCOMPARE(foundTextFieldCount, cExpectedTextFieldFacts);

        int foundNaNFieldCount = 0;
        for (int i=0; i<simpleMissionItem.nanFacts()->count(); i++) {
            Fact* fact = qobject_cast<Fact*>(simpleMissionItem.nanFacts()->get(i));

            bool found = false;
            for (size_t j=0; j<expected->cFactValues; j++) {
                const FactValue_t* factValue = &expected->rgFactValues[j];

                if (!_classMatch(factValue->vehicleClass, commandVehicleClass)) {
                    continue;
                }

                if (factValue->name == fact->name()) {
                    QCOMPARE(fact->rawValue().toDouble(), (factValue->paramIndex * 10.0) + 0.1234567);
                    foundNaNFieldCount ++;
                    found = true;
                    break;
                }
            }

            qDebug() << "nanFieldFact" << fact->name();
            QVERIFY(found);
        }
        QCOMPARE(foundNaNFieldCount, cExpectedNaNFieldFacts);
225

226 227 228 229
        if (!qIsNaN(expected->altValue)) {
            QCOMPARE(simpleMissionItem.altitudeMode(), expected->altMode);
            QCOMPARE(simpleMissionItem.altitude()->rawValue().toDouble(), expected->altValue);
        }
230 231 232
    }
}

233 234 235 236 237 238 239 240
void SimpleMissionItemTest::_testEditorFacts(void)
{
    _testEditorFactsWorker(QGCMAVLink::VehicleClassMultiRotor,  QGCMAVLink::VehicleClassGeneric,    _rgItemExpected);
    _testEditorFactsWorker(QGCMAVLink::VehicleClassFixedWing,   QGCMAVLink::VehicleClassGeneric,    _rgItemExpected);
    _testEditorFactsWorker(QGCMAVLink::VehicleClassVTOL,        QGCMAVLink::VehicleClassMultiRotor, _rgItemExpected);
    _testEditorFactsWorker(QGCMAVLink::VehicleClassVTOL,        QGCMAVLink::VehicleClassFixedWing,  _rgItemExpected);
}

241 242
void SimpleMissionItemTest::_testDefaultValues(void)
{
243
    SimpleMissionItem item(_masterController, false /* flyView */, false /* forLoad */, nullptr);
244 245 246

    item.missionItem().setCommand(MAV_CMD_NAV_WAYPOINT);
    item.missionItem().setFrame(MAV_FRAME_GLOBAL_RELATIVE_ALT);
247
    QCOMPARE(item.missionItem().param7(), qgcApp()->toolbox()->settingsManager()->appSettings()->defaultMissionItemAltitude()->rawValue().toDouble());
248 249 250 251
}

void SimpleMissionItemTest::_testSignals(void)
{
252
    MissionItem& missionItem = _simpleItem->missionItem();
253

254 255 256 257
    // Check that changing to the same coordinate does not signal
    _simpleItem->setCoordinate(QGeoCoordinate(missionItem.param5(), missionItem.param6(), missionItem.param7()));
    QVERIFY(_spyVisualItem->checkNoSignals());
    QVERIFY(_spySimpleItem->checkNoSignals());
258 259 260 261 262 263

    // Check coordinateChanged signalling. Calling setCoordinate should trigger:
    //      coordinateChanged
    //      dirtyChanged

    // Check that actually changing coordinate signals correctly
264 265 266 267 268 269
    _simpleItem->setCoordinate(QGeoCoordinate(missionItem.param5() + 1, missionItem.param6(), missionItem.param7()));
    QVERIFY(_spyVisualItem->checkOnlySignalByMask(coordinateChangedMask | dirtyChangedMask));
    _spyVisualItem->clearAllSignals();
    _simpleItem->setCoordinate(QGeoCoordinate(missionItem.param5(), missionItem.param6() + 1, missionItem.param7()));
    QVERIFY(_spyVisualItem->checkOnlySignalByMask(coordinateChangedMask | dirtyChangedMask));
    _spyVisualItem->clearAllSignals();
270 271

    // Altitude in coordinate is not used in setCoordinate
272
    _simpleItem->setCoordinate(QGeoCoordinate(missionItem.param5(), missionItem.param6(), missionItem.param7() + 1));
273 274
    QVERIFY(_spyVisualItem->checkNoSignals());
    QVERIFY(_spySimpleItem->checkNoSignals());
275
    _spyVisualItem->clearAllSignals();
276 277 278

    // Check dirtyChanged signalling

279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
    missionItem.setParam1(missionItem.param1());
    QVERIFY(_spyVisualItem->checkNoSignals());
    missionItem.setParam2(missionItem.param2());
    QVERIFY(_spyVisualItem->checkNoSignals());
    missionItem.setParam3(missionItem.param3());
    QVERIFY(_spyVisualItem->checkNoSignals());
    missionItem.setParam4(missionItem.param4());
    QVERIFY(_spyVisualItem->checkNoSignals());

    missionItem.setParam1(missionItem.param1() + 1);
    QVERIFY(_spyVisualItem->checkOnlySignalByMask(dirtyChangedMask));
    _spyVisualItem->clearAllSignals();
    missionItem.setParam1(missionItem.param2() + 1);
    QVERIFY(_spyVisualItem->checkOnlySignalByMask(dirtyChangedMask));
    _spyVisualItem->clearAllSignals();
    missionItem.setParam1(missionItem.param3() + 1);
    QVERIFY(_spyVisualItem->checkOnlySignalByMask(dirtyChangedMask));
    _spyVisualItem->clearAllSignals();
    missionItem.setParam1(missionItem.param4() + 1);
    QVERIFY(_spyVisualItem->checkOnlySignalByMask(dirtyChangedMask));
    _spyVisualItem->clearAllSignals();
300

301
    _simpleItem->setAltitudeMode(_simpleItem->altitudeMode() == QGroundControlQmlGlobal::AltitudeModeRelative ? QGroundControlQmlGlobal::AltitudeModeAbsolute : QGroundControlQmlGlobal::AltitudeModeRelative);
302
    QVERIFY(_spySimpleItem->checkOnlySignalByMask(dirtyChangedMask | friendlyEditAllowedChangedMask | altitudeModeChangedMask));
303 304 305 306 307 308 309 310 311
    _spySimpleItem->clearAllSignals();
    _spyVisualItem->clearAllSignals();

    // Check commandChanged signalling. Call setCommand should trigger:
    //      commandChanged
    //      commandNameChanged
    //      dirtyChanged
    //      coordinateChanged - since altitude will be set back to default

312
    _simpleItem->setCommand(MAV_CMD_NAV_WAYPOINT);
313 314 315
    QVERIFY(_spyVisualItem->checkNoSignals());
    QVERIFY(_spySimpleItem->checkNoSignals());

316
    _simpleItem->setCommand(MAV_CMD_NAV_LOITER_TIME);
317
    QVERIFY(_spySimpleItem->checkSignalsByMask(commandChangedMask));
318
    QVERIFY(_spyVisualItem->checkSignalsByMask(commandNameChangedMask | dirtyChangedMask));
319 320
}

321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352
void SimpleMissionItemTest::_testCameraSectionDirty(void)
{
    CameraSection* cameraSection = _simpleItem->cameraSection();

    QVERIFY(!cameraSection->dirty());
    QVERIFY(!_simpleItem->dirty());

    // Dirtying the camera section should also dirty the item
    cameraSection->setDirty(true);
    QVERIFY(_simpleItem->dirty());

    // Clearing the dirty bit from the item should also clear the dirty bit on the camera section
    _simpleItem->setDirty(false);
    QVERIFY(!cameraSection->dirty());
}

void SimpleMissionItemTest::_testSpeedSectionDirty(void)
{
    SpeedSection* speedSection = _simpleItem->speedSection();

    QVERIFY(!speedSection->dirty());
    QVERIFY(!_simpleItem->dirty());

    // Dirtying the speed section should also dirty the item
    speedSection->setDirty(true);
    QVERIFY(_simpleItem->dirty());

    // Clearing the dirty bit from the item should also clear the dirty bit on the camera section
    _simpleItem->setDirty(false);
    QVERIFY(!speedSection->dirty());
}

353
void SimpleMissionItemTest::_testCameraSection(void)
354
{
355 356 357 358 359 360 361 362 363 364 365 366 367 368
    // No gimbal yaw to start with
    QVERIFY(qIsNaN(_simpleItem->specifiedGimbalYaw()));
    QVERIFY(qIsNaN(_simpleItem->missionGimbalYaw()));
    QCOMPARE(_simpleItem->dirty(), false);

    double gimbalYaw = 10.1234;
    _simpleItem->cameraSection()->setSpecifyGimbal(true);
    _simpleItem->cameraSection()->gimbalYaw()->setRawValue(gimbalYaw);
    QCOMPARE(_simpleItem->specifiedGimbalYaw(), gimbalYaw);
    QVERIFY(qIsNaN(_simpleItem->missionGimbalYaw()));
    QCOMPARE(_spyVisualItem->checkSignalsByMask(specifiedGimbalYawChangedMask), true);
    QCOMPARE(_simpleItem->dirty(), true);
}

369

370 371 372 373 374 375 376 377 378 379 380 381
void SimpleMissionItemTest::_testSpeedSection(void)
{
    // No flight speed
    QVERIFY(qIsNaN(_simpleItem->specifiedFlightSpeed()));
    QCOMPARE(_simpleItem->dirty(), false);

    double flightSpeed = 10.1234;
    _simpleItem->speedSection()->setSpecifyFlightSpeed(true);
    _simpleItem->speedSection()->flightSpeed()->setRawValue(flightSpeed);
    QCOMPARE(_simpleItem->specifiedFlightSpeed(), flightSpeed);
    QCOMPARE(_spyVisualItem->checkSignalsByMask(specifiedFlightSpeedChangedMask), true);
    QCOMPARE(_simpleItem->dirty(), true);
382
}
383 384 385 386 387

void SimpleMissionItemTest::_testAltitudePropogation(void)
{
    // Make sure that changes to altitude propogate to param 7 of the mission item

388
    _simpleItem->setAltitudeMode(QGroundControlQmlGlobal::AltitudeModeRelative);
389 390 391 392
    _simpleItem->altitude()->setRawValue(_simpleItem->altitude()->rawValue().toDouble() + 1);
    QCOMPARE(_simpleItem->altitude()->rawValue().toDouble(), _simpleItem->missionItem().param7());
    QCOMPARE(_simpleItem->missionItem().frame(), MAV_FRAME_GLOBAL_RELATIVE_ALT);

393
    _simpleItem->setAltitudeMode(QGroundControlQmlGlobal::AltitudeModeAbsolute);
394 395 396 397
    _simpleItem->altitude()->setRawValue(_simpleItem->altitude()->rawValue().toDouble() + 1);
    QCOMPARE(_simpleItem->altitude()->rawValue().toDouble(), _simpleItem->missionItem().param7());
    QCOMPARE(_simpleItem->missionItem().frame(), MAV_FRAME_GLOBAL);
}