WimaController.cc 13.2 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
  _planDataValid = false;
234

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

239
  // Extract areas.
240
  auto planData = WimaBridge::instance()->planData();
241

242 243 244 245
  // Measurement Area.
  if (planData.measurementArea().coordinateList().size() >= 3) {
    _measurementArea = planData.measurementArea();
    _areas.append(&_measurementArea);
246

247 248 249 250
    // Service Area.
    if (planData.serviceArea().coordinateList().size() >= 3) {
      _serviceArea = planData.serviceArea();
      _areas.append(&_serviceArea);
251

252 253 254
      _WMSettings.setHomePosition(
          QGeoCoordinate(_serviceArea.depot().latitude(),
                         _serviceArea.depot().longitude(), 0));
255

256 257 258 259
      // Joined Area.
      if (planData.joinedArea().coordinateList().size() >= 3) {
        _joinedArea = planData.joinedArea();
        _areas.append(&_joinedArea);
260

261
        _planDataValid = true;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
262

263 264 265 266 267
        // Corridor.
        if (planData.corridor().coordinateList().size() >= 3) {
          _corridor = planData.corridor();
        }
      }
Valentin Platzgummer's avatar
Valentin Platzgummer committed
268
    }
269
  }
270

271 272 273 274 275 276 277 278
  if (_planDataValid) {
    emit visualItemsChanged();
  } else {
    _areas.clear();
    _measurementArea = WimaMeasurementAreaData();
    _serviceArea = WimaServiceAreaData();
    _corridor = WimaCorridorData();
    _joinedArea = WimaJoinedAreaData();
279
  }
280 281 282 283
}

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

286
WimaController *WimaController::thisPointer() { return this; }
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
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() {
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
  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();
          }
342
        }
343 344
      } else {
        _lowBatteryHandlingTriggered = false;
345
      }
346
    }
347
  }
348 349
}

350 351 352 353 354 355 356 357 358 359
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
360 361
}

362
void WimaController::_smartRTLCleanUp(bool flying) {
363
  if (!flying && _missionController) { // vehicle has landed
364
    _switchWaypointManager(_emptyWM);
365 366 367 368
    _missionController->removeAll();
    disconnect(masterController()->managerVehicle(), &Vehicle::flyingChanged,
               this, &WimaController::_smartRTLCleanUp);
  }
369 370
}

371
bool WimaController::_SRTLPrecondition(QString &errorString) {
372
  if (!_planDataValid) {
373 374 375 376
    errorString.append(tr("No WiMA data available. Please define at least a "
                          "measurement and a service area."));
    return false;
  }
377

378
  return _rtlWM.checkPrecondition(errorString);
379 380
}

381 382 383 384
void WimaController::_switchWaypointManager(
    WaypointManager::ManagerBase &manager) {
  if (_currentWM != &manager) {
    _currentWM = &manager;
385
    emit missionItemsChanged();
386
    emit waypointPathChanged();
387

388 389
    qCWarning(WimaControllerLog) << "_switchWaypointManager: statistics update "
                                    "missing.";
390 391 392 393 394 395
  }
}

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

398 399 400 401 402 403 404 405 406 407 408 409
  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();
      }
410 411 412 413 414 415 416 417 418 419 420
    }
  } 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);
  }
}