WimaController.cc 13.3 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 "WimaSettings.h"

15
#include "Snake/QNemoHeartbeat.h"
16
#include "Snake/QNemoProgress.h"
17
#include "Snake/SnakeTile.h"
18

19
#include "QVector3D"
20
#include <QScopedPointer>
21

22
#define CLIPPER_SCALE 1000000
23
#include "clipper/clipper.hpp"
24

25 26
#include <memory>

27 28
QGC_LOGGING_CATEGORY(WimaControllerLog, "WimaControllerLog")

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

34
#define SMART_RTL_MAX_ATTEMPTS 3       // times
35
#define SMART_RTL_ATTEMPT_INTERVAL 200 // ms
36 37 38 39 40 41 42 43
#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
44

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

Valentin Platzgummer's avatar
Valentin Platzgummer committed
62
  // Set up facts for waypoint manager.
63 64 65 66 67 68 69 70 71
  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);
72 73
}

74
PlanMasterController *WimaController::masterController() {
75
  return _masterController;
76 77 78
}

MissionController *WimaController::missionController() {
79
  return _missionController;
80 81
}

82
QmlObjectListModel *WimaController::visualItems() { return &_areas; }
83 84

QmlObjectListModel *WimaController::missionItems() {
85
  return const_cast<QmlObjectListModel *>(&_currentWM->missionItems());
86 87
}

88 89
QVariantList WimaController::waypointPath() {
  return const_cast<QVariantList &>(_currentWM->waypointsVariant());
90 91
}

92
Fact *WimaController::enableWimaController() { return &_enableWimaController; }
93

94
Fact *WimaController::flightSpeed() { return &_flightSpeed; }
95

96
Fact *WimaController::altitude() { return &_altitude; }
97

98 99 100 101
void WimaController::setMasterController(PlanMasterController *masterC) {
  _masterController = masterC;
  _WMSettings.setMasterController(masterC);
  emit masterControllerChanged();
102 103
}

104 105 106 107
void WimaController::setMissionController(MissionController *missionC) {
  _missionController = missionC;
  _WMSettings.setMissionController(missionC);
  emit missionControllerChanged();
108 109
}

110
void WimaController::requestSmartRTL() {
111
  qCWarning(WimaControllerLog) << "requestSmartRTL() called";
112 113
  QString errorString("Smart RTL requested.");
  if (!_SRTLPrecondition(errorString)) {
114 115 116 117
    qgcApp()->showMessage(errorString);
    return;
  }
  emit smartRTLRequestConfirm();
118 119
}

120
bool WimaController::upload() {
121 122 123 124 125 126 127 128 129 130 131
  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 {
132 133
    return false;
  }
134 135
}

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

159 160 161 162 163 164 165 166
      // 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;
    }
167
  }
168
}
169

170
void WimaController::removeFromVehicle() {
171 172 173 174
  if (_masterController && _missionController) {
    _masterController->removeAllFromVehicle();
    _missionController->removeAll();
  }
175 176
}

177
void WimaController::executeSmartRTL() {
178
  qCWarning(WimaControllerLog) << "executeSmartRTL() called";
179 180 181 182
  if (_masterController && _masterController->managerVehicle()) {
    forceUpload();
    _masterController->managerVehicle()->startMission();
  }
183 184
}

185
void WimaController::initSmartRTL() {
186
  qCWarning(WimaControllerLog) << "initSmartRTL() called";
187 188
  _initSmartRTL();
}
189

190
void WimaController::removeVehicleTrajectoryHistory() {
191 192 193 194 195
  if (_masterController && _masterController->managerVehicle()) {
    _masterController->managerVehicle()
        ->trajectoryPoints()
        ->clearAndDeleteContents();
  }
196 197
}

Valentin Platzgummer's avatar
Valentin Platzgummer committed
198 199
bool WimaController::_calcShortestPath(const QGeoCoordinate &start,
                                       const QGeoCoordinate &destination,
200 201 202 203 204 205 206 207 208
                                       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;
209

210 211 212
  bool retVal =
      PolygonCalculus::shortestPath(polygon2D, start2D, end2D, path2D);
  toGeoList(path2D, /*origin*/ start, path);
213

214 215
  return retVal;
}
216

217
bool WimaController::setWimaPlanData(QSharedPointer<WimaPlanData> planData) {
218 219
  // reset visual items
  _areas.clear();
220 221 222 223
  _measurementArea = WimaMeasurementAreaData();
  _serviceArea = WimaServiceAreaData();
  _corridor = WimaCorridorData();
  _joinedArea = WimaJoinedAreaData();
224

225 226 227
  emit visualItemsChanged();
  emit missionItemsChanged();
  emit waypointPathChanged();
228

229
  _planDataValid = false;
230

231
  // extract list with WimaAreas
232
  QList<const WimaAreaData *> areaList = planData->areaList();
233

234
  int areaCounter = 0;
235 236
  const int numAreas = 4; // extract only numAreas Areas, if there are more
                          // they are invalid and ignored
237 238
  for (int i = 0; i < areaList.size(); i++) {
    const WimaAreaData *areaData = areaList[i];
239

240 241 242 243 244
    if (areaData->type() ==
        WimaServiceAreaData::typeString) { // is it a service area?
      _serviceArea = *qobject_cast<const WimaServiceAreaData *>(areaData);
      areaCounter++;
      _areas.append(&_serviceArea);
245

246
      continue;
247
    }
248

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

256
      continue;
257
    }
258

259 260 261 262
    if (areaData->type() == WimaCorridorData::typeString) { // is it a corridor?
      _corridor = *qobject_cast<const WimaCorridorData *>(areaData);
      areaCounter++;
      //_visualItems.append(&_corridor); // not needed
263

264
      continue;
265
    }
266

267 268 269 270 271
    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
272

273
      continue;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
274 275
    }

276 277 278
    if (areaCounter >= numAreas)
      break;
  }
279

280 281 282 283
  if (areaCounter != numAreas) {
    Q_ASSERT(false);
    return false;
  }
284

285
  emit visualItemsChanged();
286

287 288
  _WMSettings.setHomePosition(QGeoCoordinate(
      _serviceArea.depot().latitude(), _serviceArea.depot().longitude(), 0));
289 290

  _planDataValid = true;
291
  return true;
292
}
293

294
WimaController *WimaController::thisPointer() { return this; }
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 323 324
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() {
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
  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();
          }
350
        }
351 352
      } else {
        _lowBatteryHandlingTriggered = false;
353
      }
354
    }
355
  }
356 357
}

358 359 360 361 362 363 364 365 366 367
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
368 369
}

370
void WimaController::_smartRTLCleanUp(bool flying) {
371
  if (!flying && _missionController) { // vehicle has landed
372
    _switchWaypointManager(_emptyWM);
373 374 375 376
    _missionController->removeAll();
    disconnect(masterController()->managerVehicle(), &Vehicle::flyingChanged,
               this, &WimaController::_smartRTLCleanUp);
  }
377 378
}

379
bool WimaController::_SRTLPrecondition(QString &errorString) {
380
  if (!_planDataValid) {
381 382 383 384
    errorString.append(tr("No WiMA data available. Please define at least a "
                          "measurement and a service area."));
    return false;
  }
385

386
  return _rtlWM.checkPrecondition(errorString);
387 388
}

389 390 391 392
void WimaController::_switchWaypointManager(
    WaypointManager::ManagerBase &manager) {
  if (_currentWM != &manager) {
    _currentWM = &manager;
393
    emit missionItemsChanged();
394
    emit waypointPathChanged();
395

396 397
    qCWarning(WimaControllerLog) << "_switchWaypointManager: statistics update "
                                    "missing.";
398 399 400 401 402 403
  }
}

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

406 407 408 409 410 411 412 413 414 415 416 417
  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();
      }
418 419 420 421 422 423 424 425 426 427 428
    }
  } 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);
  }
}