WimaController.cc 13.7 KB
Newer Older
Valentin Platzgummer's avatar
Valentin Platzgummer committed
1
#include "WimaController.h"
2

Valentin Platzgummer's avatar
Valentin Platzgummer committed
3
#include "utilities.h"
4

5 6 7 8
#include "MissionController.h"
#include "MissionSettingsItem.h"
#include "PlanMasterController.h"
#include "QGCApplication.h"
9
#include "QGCLoggingCategory.h"
10
#include "SettingsManager.h"
11 12
#include "SimpleMissionItem.h"

13 14
#include "WimaBridge.h"
#include "WimaPlanData.h"
15 16
#include "WimaSettings.h"

17
#include "Snake/QNemoHeartbeat.h"
18
#include "Snake/QNemoProgress.h"
19
#include "Snake/SnakeTile.h"
20

21
#include "QVector3D"
22
#include <QScopedPointer>
23

24
#define CLIPPER_SCALE 1000000
25
#include "clipper/clipper.hpp"
26

27 28
#include <memory>

29 30
QGC_LOGGING_CATEGORY(WimaControllerLog, "WimaControllerLog")

Valentin Platzgummer's avatar
Valentin Platzgummer committed
31 32 33 34 35
template <typename T>
constexpr typename std::underlying_type<T>::type integral(T value) {
  return static_cast<typename std::underlying_type<T>::type>(value);
}

36
#define SMART_RTL_MAX_ATTEMPTS 3       // times
37
#define SMART_RTL_ATTEMPT_INTERVAL 200 // ms
38 39 40 41 42 43 44 45
#define EVENT_TIMER_INTERVAL 50        // ms

const char *WimaController::areaItemsName = "AreaItems";
const char *WimaController::missionItemsName = "MissionItems";
const char *WimaController::settingsGroup = "WimaController";
const char *WimaController::enableWimaControllerName = "EnableWimaController";
const char *WimaController::flightSpeedName = "FlightSpeed";
const char *WimaController::altitudeName = "Altitude";
Valentin Platzgummer's avatar
Valentin Platzgummer committed
46

47
WimaController::WimaController(QObject *parent)
48
    : QObject(parent), _joinedArea(), _measurementArea(), _serviceArea(),
49
      _corridor(), _planDataValid(false),
50 51
      _areaInterface(&_measurementArea, &_serviceArea, &_corridor,
                     &_joinedArea),
52
      _WMSettings(), _emptyWM(_WMSettings, _areaInterface),
53
      _rtlWM(_WMSettings, _areaInterface),
54
      _currentWM(&_emptyWM), _WMList{&_emptyWM, &_rtlWM},
55 56 57 58 59 60
      _metaDataMap(FactMetaData::createMapFromJsonFile(
          QStringLiteral(":/json/WimaController.SettingsGroup.json"), this)),
      _enableWimaController(settingsGroup,
                            _metaDataMap[enableWimaControllerName]),
      _flightSpeed(settingsGroup, _metaDataMap[flightSpeedName]),
      _altitude(settingsGroup, _metaDataMap[altitudeName]),
61
      _lowBatteryHandlingTriggered(false),
62 63
      _batteryLevelTicker(EVENT_TIMER_INTERVAL, 1000 /*ms*/) {

Valentin Platzgummer's avatar
Valentin Platzgummer committed
64
  // Set up facts for waypoint manager.
65 66 67 68 69 70 71 72 73
  connect(&_flightSpeed, &Fact::rawValueChanged, this,
          &WimaController::_updateflightSpeed);
  connect(&_altitude, &Fact::rawValueChanged, this,
          &WimaController::_updateAltitude);

  // Periodic.
  connect(&_eventTimer, &QTimer::timeout, this,
          &WimaController::_eventTimerHandler);
  _eventTimer.start(EVENT_TIMER_INTERVAL);
74 75 76 77 78 79

  // PlanData and Progress.
  connect(WimaBridge::instance(), &WimaBridge::planDataChanged, this,
          &WimaController::planDataChangedHandler);
  connect(WimaBridge::instance(), &WimaBridge::progressChanged, this,
          &WimaController::progressChangedHandler);
80 81
}

82
PlanMasterController *WimaController::masterController() {
83
  return _masterController;
84 85 86
}

MissionController *WimaController::missionController() {
87
  return _missionController;
88 89
}

90
QmlObjectListModel *WimaController::visualItems() { return &_areas; }
91 92

QmlObjectListModel *WimaController::missionItems() {
93
  return const_cast<QmlObjectListModel *>(&_currentWM->currentMissionItems());
94 95
}

96
QVariantList WimaController::waypointPath() {
97
  return const_cast<QVariantList &>(_currentWM->currentWaypointsVariant());
98 99
}

100
Fact *WimaController::enableWimaController() { return &_enableWimaController; }
101

102
Fact *WimaController::flightSpeed() { return &_flightSpeed; }
103

104
Fact *WimaController::altitude() { return &_altitude; }
105

106 107 108 109
void WimaController::setMasterController(PlanMasterController *masterC) {
  _masterController = masterC;
  _WMSettings.setMasterController(masterC);
  emit masterControllerChanged();
110 111
}

112 113 114 115
void WimaController::setMissionController(MissionController *missionC) {
  _missionController = missionC;
  _WMSettings.setMissionController(missionC);
  emit missionControllerChanged();
116 117
}

118
void WimaController::requestSmartRTL() {
119
  qCWarning(WimaControllerLog) << "requestSmartRTL() called";
120 121
  QString errorString("Smart RTL requested.");
  if (!_SRTLPrecondition(errorString)) {
122 123 124 125
    qgcApp()->showMessage(errorString);
    return;
  }
  emit smartRTLRequestConfirm();
126 127
}

128
bool WimaController::upload() {
129 130 131 132 133 134 135 136 137 138 139
  auto &items = _currentWM->currentMissionItems();
  if (_masterController && _masterController->managerVehicle() &&
      items.count() > 0) {
    if (!_joinedArea.containsCoordinate(
            _masterController->managerVehicle()->coordinate())) {
      emit forceUploadConfirm();
      return false;
    } else {
      return forceUpload();
    }
  } else {
140 141
    return false;
  }
142 143
}

144
bool WimaController::forceUpload() {
145 146 147
  auto &currentMissionItems = _currentWM->currentMissionItems();
  if (currentMissionItems.count() < 1 || !_missionController ||
      !_masterController) {
148 149 150 151 152
    qCWarning(WimaControllerLog)
        << "forceUpload(): error:"
        << "currentMissionItems.count(): " << currentMissionItems.count()
        << "_missionController: " << _missionController
        << "_masterController: " << _masterController;
153
    return false;
154 155 156 157 158 159 160 161
  } else {
    _missionController->removeAll();
    // Set homeposition of settingsItem.
    QmlObjectListModel *visuals = _missionController->visualItems();
    MissionSettingsItem *settingsItem =
        visuals->value<MissionSettingsItem *>(0);
    if (settingsItem == nullptr) {
      Q_ASSERT(false);
162
      qCWarning(WimaControllerLog) << "updateCurrentMissionItems(): nullptr";
163 164 165
      return false;
    } else {
      settingsItem->setCoordinate(_WMSettings.homePosition());
166

167 168 169 170 171 172 173 174
      // Copy mission items to _missionController and send them.
      for (int i = 1; i < currentMissionItems.count(); i++) {
        auto *item = currentMissionItems.value<const SimpleMissionItem *>(i);
        _missionController->insertSimpleMissionItem(*item, visuals->count());
      }
      _masterController->sendToVehicle();
      return true;
    }
175
  }
176
}
177

178
void WimaController::removeFromVehicle() {
179 180 181 182
  if (_masterController && _missionController) {
    _masterController->removeAllFromVehicle();
    _missionController->removeAll();
  }
183 184
}

185
void WimaController::executeSmartRTL() {
186
  qCWarning(WimaControllerLog) << "executeSmartRTL() called";
187 188 189 190
  if (_masterController && _masterController->managerVehicle()) {
    forceUpload();
    _masterController->managerVehicle()->startMission();
  }
191 192
}

193
void WimaController::initSmartRTL() {
194
  qCWarning(WimaControllerLog) << "initSmartRTL() called";
195 196
  _initSmartRTL();
}
197

198
void WimaController::removeVehicleTrajectoryHistory() {
199 200 201 202 203
  if (_masterController && _masterController->managerVehicle()) {
    _masterController->managerVehicle()
        ->trajectoryPoints()
        ->clearAndDeleteContents();
  }
204 205
}

Valentin Platzgummer's avatar
Valentin Platzgummer committed
206 207
bool WimaController::_calcShortestPath(const QGeoCoordinate &start,
                                       const QGeoCoordinate &destination,
208 209 210 211 212 213 214 215 216
                                       QVector<QGeoCoordinate> &path) {
  using namespace GeoUtilities;
  using namespace PolygonCalculus;
  QPolygonF polygon2D;
  toCartesianList(_joinedArea.coordinateList(), /*origin*/ start, polygon2D);
  QPointF start2D(0, 0);
  QPointF end2D;
  toCartesian(destination, start, end2D);
  QVector<QPointF> path2D;
217

218 219 220
  bool retVal =
      PolygonCalculus::shortestPath(polygon2D, start2D, end2D, path2D);
  toGeoList(path2D, /*origin*/ start, path);
221

222 223
  return retVal;
}
224

225 226
void WimaController::planDataChangedHandler() {

227 228
  // reset visual items
  _areas.clear();
229 230 231 232
  _measurementArea = WimaMeasurementAreaData();
  _serviceArea = WimaServiceAreaData();
  _corridor = WimaCorridorData();
  _joinedArea = WimaJoinedAreaData();
233

234 235 236
  emit visualItemsChanged();
  emit missionItemsChanged();
  emit waypointPathChanged();
237

238
  _planDataValid = false;
239

240
  auto planData = WimaBridge::instance()->planData();
241
  // extract list with WimaAreas
242
  QList<const WimaAreaData *> areaList = planData.areaList();
243

244
  int areaCounter = 0;
245 246
  const int numAreas = 4; // extract only numAreas Areas, if there are more
                          // they are invalid and ignored
247 248
  for (int i = 0; i < areaList.size(); i++) {
    const WimaAreaData *areaData = areaList[i];
249

250 251 252 253 254
    if (areaData->type() ==
        WimaServiceAreaData::typeString) { // is it a service area?
      _serviceArea = *qobject_cast<const WimaServiceAreaData *>(areaData);
      areaCounter++;
      _areas.append(&_serviceArea);
255

256
      continue;
257
    }
258

259 260 261 262 263 264
    if (areaData->type() ==
        WimaMeasurementAreaData::typeString) { // is it a measurement area?
      _measurementArea =
          *qobject_cast<const WimaMeasurementAreaData *>(areaData);
      areaCounter++;
      _areas.append(&_measurementArea);
265

266
      continue;
267
    }
268

269 270 271 272
    if (areaData->type() == WimaCorridorData::typeString) { // is it a corridor?
      _corridor = *qobject_cast<const WimaCorridorData *>(areaData);
      areaCounter++;
      //_visualItems.append(&_corridor); // not needed
273

274
      continue;
275
    }
276

277 278 279 280 281
    if (areaData->type() ==
        WimaJoinedAreaData::typeString) { // is it a corridor?
      _joinedArea = *qobject_cast<const WimaJoinedAreaData *>(areaData);
      areaCounter++;
      _areas.append(&_joinedArea);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
282

283
      continue;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
284 285
    }

286 287 288
    if (areaCounter >= numAreas)
      break;
  }
289

290 291
  if (areaCounter != numAreas) {
    Q_ASSERT(false);
292
    return;
293
  }
294

295
  emit visualItemsChanged();
296

297 298
  _WMSettings.setHomePosition(QGeoCoordinate(
      _serviceArea.depot().latitude(), _serviceArea.depot().longitude(), 0));
299 300

  _planDataValid = true;
301 302 303 304
}

void WimaController::progressChangedHandler() {
  _measurementArea.setProgress(WimaBridge::instance()->progress());
305
}
306

307
WimaController *WimaController::thisPointer() { return this; }
308

309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337
void WimaController::_updateflightSpeed() {
  bool value;
  _WMSettings.setFlightSpeed(_flightSpeed.rawValue().toDouble(&value));
  Q_ASSERT(value);
  (void)value;

  if (!_currentWM->update()) {
    Q_ASSERT(false);
  }

  emit missionItemsChanged();
  emit waypointPathChanged();
}

void WimaController::_updateAltitude() {
  bool value;
  _WMSettings.setAltitude(_altitude.rawValue().toDouble(&value));
  Q_ASSERT(value);
  (void)value;

  if (!_currentWM->update()) {
    Q_ASSERT(false);
  }

  emit missionItemsChanged();
  emit waypointPathChanged();
}

void WimaController::_checkBatteryLevel() {
338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362
  if (_missionController && _masterController &&
      _masterController->managerVehicle()) {
    Vehicle *managerVehicle = masterController()->managerVehicle();
    WimaSettings *wimaSettings =
        qgcApp()->toolbox()->settingsManager()->wimaSettings();
    int threshold = wimaSettings->lowBatteryThreshold()->rawValue().toInt();
    bool enabled = _enableWimaController.rawValue().toBool();
    unsigned int minTime =
        wimaSettings->minimalRemainingMissionTime()->rawValue().toUInt();

    if (enabled) {
      Fact *battery1percentRemaining =
          managerVehicle->battery1FactGroup()->getFact(
              VehicleBatteryFactGroup::_percentRemainingFactName);
      Fact *battery2percentRemaining =
          managerVehicle->battery2FactGroup()->getFact(
              VehicleBatteryFactGroup::_percentRemainingFactName);

      if (battery1percentRemaining->rawValue().toDouble() < threshold &&
          battery2percentRemaining->rawValue().toDouble() < threshold) {
        if (!_lowBatteryHandlingTriggered) {
          _lowBatteryHandlingTriggered = true;
          if (!(_missionController->remainingTime() <= minTime)) {
            requestSmartRTL();
          }
363
        }
364 365
      } else {
        _lowBatteryHandlingTriggered = false;
366
      }
367
    }
368
  }
369 370
}

371 372 373 374 375 376 377 378 379 380
void WimaController::_eventTimerHandler() {
  // Battery level check necessary?
  Fact *enableLowBatteryHandling = qgcApp()
                                       ->toolbox()
                                       ->settingsManager()
                                       ->wimaSettings()
                                       ->enableLowBatteryHandling();
  if (enableLowBatteryHandling->rawValue().toBool() &&
      _batteryLevelTicker.ready())
    _checkBatteryLevel();
Valentin Platzgummer's avatar
Valentin Platzgummer committed
381 382
}

383
void WimaController::_smartRTLCleanUp(bool flying) {
384
  if (!flying && _missionController) { // vehicle has landed
385
    _switchWaypointManager(_emptyWM);
386 387 388 389
    _missionController->removeAll();
    disconnect(masterController()->managerVehicle(), &Vehicle::flyingChanged,
               this, &WimaController::_smartRTLCleanUp);
  }
390 391
}

392
bool WimaController::_SRTLPrecondition(QString &errorString) {
393
  if (!_planDataValid) {
394 395 396 397
    errorString.append(tr("No WiMA data available. Please define at least a "
                          "measurement and a service area."));
    return false;
  }
398

399
  return _rtlWM.checkPrecondition(errorString);
400 401
}

402 403 404 405
void WimaController::_switchWaypointManager(
    WaypointManager::ManagerBase &manager) {
  if (_currentWM != &manager) {
    _currentWM = &manager;
406
    emit missionItemsChanged();
407
    emit waypointPathChanged();
408

409 410
    qCWarning(WimaControllerLog) << "_switchWaypointManager: statistics update "
                                    "missing.";
411 412 413 414 415 416
  }
}

void WimaController::_initSmartRTL() {
  static int attemptCounter = 0;
  attemptCounter++;
417
  QString errorString;
418

419 420 421 422 423 424 425 426 427 428 429 430
  if (_SRTLPrecondition(errorString)) {
    if (_missionController && _masterController &&
        _masterController->managerVehicle()) {
      _masterController->managerVehicle()->pauseVehicle();
      connect(_masterController->managerVehicle(), &Vehicle::flyingChanged,
              this, &WimaController::_smartRTLCleanUp);
      if (_rtlWM.update()) { // Calculate return path.
        _switchWaypointManager(_rtlWM);
        removeFromVehicle();
        attemptCounter = 0;
        emit smartRTLPathConfirm();
      }
431 432 433 434 435 436 437 438 439 440 441
    }
  } else if (attemptCounter > SMART_RTL_MAX_ATTEMPTS) {
    errorString.append(
        tr("Smart RTL: No success after maximum number of attempts."));
    qgcApp()->showMessage(errorString);
    attemptCounter = 0;
  } else {
    _smartRTLTimer.singleShot(SMART_RTL_ATTEMPT_INTERVAL, this,
                              &WimaController::_initSmartRTL);
  }
}