Skip to content
NemoInterface.cpp 41.6 KiB
Newer Older
Valentin Platzgummer's avatar
Valentin Platzgummer committed
#include "NemoInterface.h"

#include "QGCApplication.h"
#include "QGCLoggingCategory.h"
#include "QGCToolbox.h"
#include "SettingsFact.h"
#include "SettingsManager.h"
#include "WimaSettings.h"

#include <shared_mutex>

#include <QJsonArray>
#include <QJsonObject>
Valentin Platzgummer's avatar
Valentin Platzgummer committed
#include <QTimer>
#include <QUrl>
#include "GenericSingelton.h"
#include "geometry/MeasurementArea.h"
Valentin Platzgummer's avatar
Valentin Platzgummer committed
#include "nemo_interface/FutureWatcher.h"
#include "nemo_interface/MeasurementTile.h"
#include "nemo_interface/QNemoHeartbeat.h"
Valentin Platzgummer's avatar
Valentin Platzgummer committed
#include "nemo_interface/TaskDispatcher.h"

Valentin Platzgummer's avatar
Valentin Platzgummer committed
#include "ros_bridge/include/messages/geographic_msgs/geopoint.h"
#include "ros_bridge/include/messages/nemo_msgs/heartbeat.h"
#include "ros_bridge/include/messages/nemo_msgs/progress_array.h"
Valentin Platzgummer's avatar
Valentin Platzgummer committed
#include "ros_bridge/include/messages/nemo_msgs/tile.h"
#include "rosbridge/rosbridge.h"
Valentin Platzgummer's avatar
Valentin Platzgummer committed

QGC_LOGGING_CATEGORY(NemoInterfaceLog, "NemoInterfaceLog")

#define SYNC_INTERVAL 1000        // ms
#define NO_HEARTBEAT_TIMEOUT 5000 // ms
Valentin Platzgummer's avatar
Valentin Platzgummer committed
static constexpr auto maxResponseTime = std::chrono::milliseconds(10000);
static const char *progressTopic = "/nemo/progress";
static const char *heartbeatTopic = "/nemo/heartbeat";

Valentin Platzgummer's avatar
Valentin Platzgummer committed
using hrc = std::chrono::high_resolution_clock;
using ROSBridgePtr = std::shared_ptr<Rosbridge>;
Valentin Platzgummer's avatar
Valentin Platzgummer committed

typedef ros_bridge::messages::nemo_msgs::tile::GenericTile<QGeoCoordinate,
                                                           QList>
    Tile;
typedef std::map<QString, std::shared_ptr<Tile>> TileMap;
typedef std::map<QString, std::shared_ptr<const Tile>> TileMapConst;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
typedef ros_bridge::messages::nemo_msgs::heartbeat::Heartbeat Heartbeat;
typedef nemo_interface::TaskDispatcher Dispatcher;
typedef nemo_interface::FutureWatcher<QVariant, std::shared_future>
    FutureWatcher;
Valentin Platzgummer's avatar
Valentin Platzgummer committed

class NemoInterface::Impl {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  enum class STATE {
    STOPPED,
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    START_BRIDGE,
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    WEBSOCKET_DETECTED,
    TRY_TOPIC_SERVICE_SETUP,
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    USER_SYNC,
    SYS_SYNC,
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    READY,
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    WEBSOCKET_TIMEOUT,
    HEARTBEAT_TIMEOUT
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  };
Valentin Platzgummer's avatar
Valentin Platzgummer committed

Valentin Platzgummer's avatar
Valentin Platzgummer committed
public:
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  Impl(NemoInterface *p);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  ~Impl();
Valentin Platzgummer's avatar
Valentin Platzgummer committed

  void start();
  void stop();

Valentin Platzgummer's avatar
Valentin Platzgummer committed
  // Tile editing.
  // 	Functions that require communication to device.
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  std::shared_future<QVariant> addTiles(const TilePtrArray &tileArray);
  std::shared_future<QVariant> removeTiles(const IDArray &idArray);
  std::shared_future<QVariant> clearTiles();
Valentin Platzgummer's avatar
Valentin Platzgummer committed

  // 	Functions that don't require communication to device.
  TileArray getTiles(const IDArray &idArray) const;
  TileArray getAllTiles() const;
  LogicalArray containsTiles(const IDArray &idArray) const;
  std::size_t size() const;
  bool empty() const;
Valentin Platzgummer's avatar
Valentin Platzgummer committed

  // Progress.
  ProgressArray getProgress() const;
  ProgressArray getProgress(const IDArray &idArray) const;
  NemoInterface::STATUS status() const;
  bool running() const; // thread safe
  bool ready() const;   // thread safe
Valentin Platzgummer's avatar
Valentin Platzgummer committed

  const QString &infoString() const;
  const QString &warningString() const;
Valentin Platzgummer's avatar
Valentin Platzgummer committed

private:
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  void _doTopicServiceSetup();
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  void _doAction();
  void _trySynchronize();
  bool _isSynchronized() const;
  bool _userSync() const;          // thread safe
  bool _sysSync() const;           // thread safe
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  void _onFutureWatcherFinished(); // thread safe
  void _onHeartbeatTimeout();      // thread safe
  void _onRosbridgeStateChanged();
Valentin Platzgummer's avatar
Valentin Platzgummer committed

  // called from dispatcher thread!
  QVariant _callAddTiles(
      std::shared_ptr<QVector<std::shared_ptr<const Tile>>> pTileArray);
  // called from dispatcher thread!
  QVariant _callRemoveTiles(std::shared_ptr<IDArray> pIdArray);
  // called from dispatcher thread!
  QVariant _callClearTiles();
  // called from dispatcher thread!
  QVariant _callGetProgress(std::shared_ptr<IDArray> pIdArray);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  QVariant _callGetAllProgress();
  enum class CALL_NAME {
    ADD_TILES,
    REMOVE_TILES,
    CLEAR_TILES,
    GET_PROGRESS,
    GET_ALL_PROGRESS
  };
  QString _toString(CALL_NAME name);
Valentin Platzgummer's avatar
Valentin Platzgummer committed

  void _addTilesRemote(
      std::shared_ptr<QVector<std::shared_ptr<const Tile>>> pTileArray,
      std::promise<bool> promise);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  void
  _addTilesRemote2(std::shared_ptr<QVector<std::shared_ptr<Tile>>> pTileArray,
                   std::promise<bool> promise);
  void _removeTilesRemote(std::shared_ptr<IDArray> idArray,
                          std::promise<bool> promise);
  void _clearTilesRemote(std::promise<bool> promise);
  void _updateProgress(std::shared_ptr<ProgressArray> pArray,
                       std::promise<bool> promise);
  void _onHeartbeatReceived(const QNemoHeartbeat &hb,
                            std::promise<bool> promise);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  void _setInfoString(const QString &info);
  void _setWarningString(const QString &warning);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  bool _setState(STATE newState); // not thread safe
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  static bool _ready(STATE s);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  static bool _userSync(STATE s);
  static bool _sysSync(STATE s);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  static bool _running(STATE s);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  static NemoInterface::STATUS _status(STATE state);
  static QString _toString(STATE s);
  static QString _toString(NemoInterface::STATUS s);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  std::atomic<STATE> _state;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  std::atomic<CALL_NAME> _lastCall;
  ROSBridgePtr _pRosbridge;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  TileMap _remoteTiles;
  TileMapConst _localTiles;
  NemoInterface *const _parent;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  Dispatcher _dispatcher;
  QString _infoString;
  QString _warningString;
  QTimer _timeoutTimer;
  QNemoHeartbeat _lastHeartbeat;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  FutureWatcher _futureWatcher;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
};

using StatusMap = std::map<NemoInterface::STATUS, QString>;
static StatusMap statusMap{
    std::make_pair<NemoInterface::STATUS, QString>(
        NemoInterface::STATUS::NOT_CONNECTED, "Not Connected"),
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    std::make_pair<NemoInterface::STATUS, QString>(NemoInterface::STATUS::SYNC,
                                                   "Synchronizing"),
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    std::make_pair<NemoInterface::STATUS, QString>(NemoInterface::STATUS::READY,
                                                   "Ready"),
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    std::make_pair<NemoInterface::STATUS, QString>(
        NemoInterface::STATUS::TIMEOUT, "Timeout"),
    std::make_pair<NemoInterface::STATUS, QString>(
        NemoInterface::STATUS::WEBSOCKET_DETECTED, "Websocket Detected")};

NemoInterface::Impl::Impl(NemoInterface *p)
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    : _state(STATE::STOPPED), _parent(p) {
Valentin Platzgummer's avatar
Valentin Platzgummer committed

  // ROS Bridge.
  WimaSettings *wimaSettings =
      qgcApp()->toolbox()->settingsManager()->wimaSettings();
  auto connectionStringFact = wimaSettings->rosbridgeConnectionString();
  auto setConnectionString = [connectionStringFact, this] {
    auto connectionString = connectionStringFact->rawValue().toString();
Valentin Platzgummer's avatar
Valentin Platzgummer committed

Valentin Platzgummer's avatar
Valentin Platzgummer committed
    bool wasRunning = this->running();
    this->stop();
    this->_pRosbridge = std::make_shared<Rosbridge>(
        QUrl(QString("ws://") + connectionString.toLocal8Bit().data()));
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    if (wasRunning) {
      this->start();
    }
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  };
  connect(connectionStringFact, &SettingsFact::rawValueChanged,
          setConnectionString);
  setConnectionString();

Valentin Platzgummer's avatar
Valentin Platzgummer committed
  // Heartbeat timeout.
  connect(&this->_timeoutTimer, &QTimer::timeout,
          std::bind(&Impl::_onHeartbeatTimeout, this));
Valentin Platzgummer's avatar
Valentin Platzgummer committed

  // Connection timer (temporary workaround)
  connect(this->_pRosbridge.get(), &Rosbridge::stateChanged,
          [this] { this->_onRosbridgeStateChanged(); });
Valentin Platzgummer's avatar
Valentin Platzgummer committed

  connect(&this->_futureWatcher, &FutureWatcher::finished,
          [this] { this->_onFutureWatcherFinished(); });
NemoInterface::Impl::~Impl() { this->_pRosbridge->stop(); }
Valentin Platzgummer's avatar
Valentin Platzgummer committed

Valentin Platzgummer's avatar
Valentin Platzgummer committed
void NemoInterface::Impl::start() {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  if (!running()) {
    this->_setState(STATE::START_BRIDGE);
    this->_doAction();
  }
Valentin Platzgummer's avatar
Valentin Platzgummer committed
}

void NemoInterface::Impl::stop() {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  if (running()) {
    this->_setState(STATE::STOPPED);
    this->_doAction();
  }
Valentin Platzgummer's avatar
Valentin Platzgummer committed
}

Valentin Platzgummer's avatar
Valentin Platzgummer committed
std::shared_future<QVariant>
NemoInterface::Impl::addTiles(const TilePtrArray &tileArray) {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  using namespace nemo_interface;

  // qDebug() << "addTiles called";
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  if (tileArray.size() > 0) {

    // copy unknown tiles
    auto pTileArray = std::make_shared<QVector<std::shared_ptr<const Tile>>>();
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    auto pIdArray = std::make_shared<IDArray>();
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    for (const auto *pTile : tileArray) {
      auto id = pTile->id();
      const auto it = this->_localTiles.find(id);
      Q_ASSERT(it == _localTiles.end());
      if (Q_LIKELY(it == _localTiles.end())) {
        auto pTileCopy =
            std::make_shared<const Tile>(pTile->coordinateList(), 0.0, id);
        _localTiles.insert(std::make_pair(id, pTileCopy));
        pTileArray->push_back(pTileCopy);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
        pIdArray->push_back(id);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
      } else {
        qCDebug(NemoInterfaceLog)
            << "addTiles(): tile with id: " << pTile->id() << "already added.";
      }
    }
    if (pTileArray->size() > 0) {
      this->_parent->tilesChanged();
    }
Valentin Platzgummer's avatar
Valentin Platzgummer committed

Valentin Platzgummer's avatar
Valentin Platzgummer committed
    // ready for send?
    if (pTileArray->size() > 0 && (this->ready() || this->_userSync())) {

Valentin Platzgummer's avatar
Valentin Platzgummer committed
      this->_setState(STATE::USER_SYNC);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
      this->_doAction();
Valentin Platzgummer's avatar
Valentin Platzgummer committed

Valentin Platzgummer's avatar
Valentin Platzgummer committed
      // create add tiles command.
      auto pTask = std::make_unique<Task>(
Valentin Platzgummer's avatar
Valentin Platzgummer committed
          std::bind(&Impl::_callAddTiles, this, pTileArray));

Valentin Platzgummer's avatar
Valentin Platzgummer committed
      // dispatch command.
      auto ret = _dispatcher.dispatch(std::move(pTask));
      auto addFuture = ret.share();

      // create get progress cmd.
      pTask = std::make_unique<Task>([this, addFuture, pIdArray] {
        addFuture.wait();
        if (addFuture.get().toBool()) {
          return this->_callGetProgress(pIdArray);
        } else {
          return QVariant(false);
        }
      });

      // dispatch command.
      ret = _dispatcher.dispatch(std::move(pTask));
      auto progressFuture = ret.share();
      _futureWatcher.setFuture(progressFuture);
      return progressFuture;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    }
  }

  std::promise<QVariant> p;
  p.set_value(QVariant(false));
  return p.get_future();
}

std::shared_future<QVariant>
NemoInterface::Impl::removeTiles(const IDArray &idArray) {
  using namespace nemo_interface;

  // qDebug() << "removeTiles called";
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  if (idArray.size() > 0) {

    // copy known ids
    auto pIdArray = std::make_shared<IDArray>();
    for (const auto id : idArray) {
      const auto it = this->_localTiles.find(id);
      Q_ASSERT(it != _localTiles.end());
      if (Q_LIKELY(it != _localTiles.end())) {
        _localTiles.erase(it);
        pIdArray->push_back(id);
      } else {
        qCDebug(NemoInterfaceLog) << "removeTiles(): unknown id: " << id << ".";
Valentin Platzgummer's avatar
Valentin Platzgummer committed
      }
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    }
    if (pIdArray->size() > 0) {
      this->_parent->tilesChanged();
    }
Valentin Platzgummer's avatar
Valentin Platzgummer committed

    // ready for send?
    if (pIdArray->size() > 0 && (this->ready() || this->_userSync())) {

Valentin Platzgummer's avatar
Valentin Platzgummer committed
      this->_setState(STATE::USER_SYNC);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
      this->_doAction();

      // create command.
      auto cmd = std::make_unique<Task>(
          std::bind(&Impl::_callRemoveTiles, this, pIdArray));

      // dispatch command and return.
      auto ret = _dispatcher.dispatch(std::move(cmd));
      auto sfut = ret.share();
      _futureWatcher.setFuture(sfut);
      return sfut;
    }
  }

  std::promise<QVariant> p;
  p.set_value(QVariant(false));
  return p.get_future();
}
Valentin Platzgummer's avatar
Valentin Platzgummer committed

Valentin Platzgummer's avatar
Valentin Platzgummer committed
std::shared_future<QVariant> NemoInterface::Impl::clearTiles() {
  using namespace nemo_interface;
Valentin Platzgummer's avatar
Valentin Platzgummer committed

  // qDebug() << "clearTiles called";
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  // clear local tiles (_localTiles)
  if (!_localTiles.empty()) {
    this->_localTiles.clear();
    this->_parent->tilesChanged();
  }
Valentin Platzgummer's avatar
Valentin Platzgummer committed

  if (this->ready() || this->_userSync()) {
Valentin Platzgummer's avatar
Valentin Platzgummer committed

Valentin Platzgummer's avatar
Valentin Platzgummer committed
    this->_setState(STATE::USER_SYNC);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    this->_doAction();
Valentin Platzgummer's avatar
Valentin Platzgummer committed

Valentin Platzgummer's avatar
Valentin Platzgummer committed
    // create command.
    auto pTask =
        std::make_unique<Task>(std::bind(&Impl::_callClearTiles, this));
Valentin Platzgummer's avatar
Valentin Platzgummer committed

    // dispatch command and return.
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    auto ret = _dispatcher.dispatch(std::move(pTask));
    auto sfut = ret.share();
    _futureWatcher.setFuture(sfut);
    return sfut;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  } else {
    std::promise<QVariant> p;
    p.set_value(QVariant(false));
    return p.get_future();
  }
}
Valentin Platzgummer's avatar
Valentin Platzgummer committed

TileArray NemoInterface::Impl::getTiles(const IDArray &idArray) const {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  TileArray tileArray;

  if (this->ready()) {
    for (const auto &id : idArray) {
      const auto it = _remoteTiles.find(id);
      if (it != _remoteTiles.end()) {
        MeasurementTile copy;
        copy.setId(it->second->id());
        copy.setProgress(it->second->progress());
        copy.setPath(it->second->tile());
        tileArray.append(std::move(copy));
      }
    }
  } else {
    for (const auto &id : idArray) {
      const auto it = _localTiles.find(id);
      if (it != _localTiles.end()) {
        MeasurementTile copy;
        copy.setId(it->second->id());
        copy.setProgress(it->second->progress());
        copy.setPath(it->second->tile());
        tileArray.append(std::move(copy));
      }
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    }
  }

  return tileArray;
}

TileArray NemoInterface::Impl::getAllTiles() const {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  TileArray tileArray;

  if (this->ready()) {
    for (const auto &entry : _remoteTiles) {
      auto pTile = entry.second;
      MeasurementTile copy;
      copy.setId(pTile->id());
      copy.setProgress(pTile->progress());
      copy.setPath(pTile->tile());
      tileArray.append(std::move(copy));
    }

  } else {
    for (const auto &entry : _localTiles) {
      auto pTile = entry.second;
      MeasurementTile copy;
      copy.setId(pTile->id());
      copy.setProgress(pTile->progress());
      copy.setPath(pTile->tile());
      tileArray.append(std::move(copy));
    }
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  }

  return tileArray;
}

LogicalArray NemoInterface::Impl::containsTiles(const IDArray &idArray) const {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  LogicalArray logicalArray;

  for (const auto &id : idArray) {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    const auto &it = _localTiles.find(id);
    logicalArray.append(it != _localTiles.end());
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  }

  return logicalArray;
}

std::size_t NemoInterface::Impl::size() const { return _localTiles.size(); }
Valentin Platzgummer's avatar
Valentin Platzgummer committed

bool NemoInterface::Impl::empty() const { return _localTiles.empty(); }
Valentin Platzgummer's avatar
Valentin Platzgummer committed

ProgressArray NemoInterface::Impl::getProgress() const {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  ProgressArray progressArray;

Valentin Platzgummer's avatar
Valentin Platzgummer committed
  if (this->_isSynchronized()) {
    for (const auto &entry : _remoteTiles) {
      progressArray.append(
          LabeledProgress{entry.second->progress(), entry.second->id()});
    }
  } else {
    for (const auto &entry : _localTiles) {
      progressArray.append(
          LabeledProgress{entry.second->progress(), entry.second->id()});
    }
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  }

  return progressArray;
}

ProgressArray NemoInterface::Impl::getProgress(const IDArray &idArray) const {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  ProgressArray progressArray;

Valentin Platzgummer's avatar
Valentin Platzgummer committed
  if (this->_isSynchronized()) {
    for (const auto &id : idArray) {
      const auto it = _remoteTiles.find(id);
      if (it != _remoteTiles.end()) {
        progressArray.append(
            LabeledProgress{it->second->progress(), it->second->id()});
      }
    }
  } else {
    for (const auto &id : idArray) {
      const auto it = _localTiles.find(id);
      if (it != _localTiles.end()) {
        progressArray.append(
            LabeledProgress{it->second->progress(), it->second->id()});
      }
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    }
  }

  return progressArray;
NemoInterface::STATUS NemoInterface::Impl::status() const {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  return _status(this->_state);
bool NemoInterface::Impl::running() const { return _running(this->_state); }
bool NemoInterface::Impl::ready() const { return _ready(this->_state.load()); }
bool NemoInterface::Impl::_sysSync() const { return _sysSync(this->_state); }
Valentin Platzgummer's avatar
Valentin Platzgummer committed
void NemoInterface::Impl::_onFutureWatcherFinished() {
  if (this->ready() || this->_userSync() || this->_sysSync()) {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    auto lastTransactionSuccessfull = _futureWatcher.result().toBool();
    if (!lastTransactionSuccessfull) {
      qCDebug(NemoInterfaceLog)
          << "last transaction unsuccessfull: " << _toString(_lastCall);
      QTimer::singleShot(5000, [this] { this->_trySynchronize(); });
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    }
Valentin Platzgummer's avatar
Valentin Platzgummer committed
}

void NemoInterface::Impl::_onHeartbeatTimeout() {
  this->_setState(STATE::HEARTBEAT_TIMEOUT);
  this->_doAction();
}

void NemoInterface::Impl::_onRosbridgeStateChanged() {
  auto state = this->_pRosbridge->state();
  if (state == Rosbridge::STATE::CONNECTED) {
    if (this->_state == STATE::START_BRIDGE ||
        this->_state == STATE::WEBSOCKET_TIMEOUT) {
      this->_setState(STATE::WEBSOCKET_DETECTED);
      this->_doAction();
    }
  } else if (state == Rosbridge::STATE::TIMEOUT) {
    if (this->_state == STATE::TRY_TOPIC_SERVICE_SETUP ||
        this->_state == STATE::READY ||
        this->_state == STATE::WEBSOCKET_DETECTED ||
        this->_state == STATE::HEARTBEAT_TIMEOUT) {
      this->_setState(STATE::WEBSOCKET_TIMEOUT);
      this->_doAction();
    }
  }
}

bool NemoInterface::Impl::_userSync() const { return _userSync(this->_state); }
Valentin Platzgummer's avatar
Valentin Platzgummer committed

const QString &NemoInterface::Impl::infoString() const { return _infoString; }
Valentin Platzgummer's avatar
Valentin Platzgummer committed

const QString &NemoInterface::Impl::warningString() const {
  return _warningString;
}
Valentin Platzgummer's avatar
Valentin Platzgummer committed

void NemoInterface::Impl::_updateProgress(std::shared_ptr<ProgressArray> pArray,
                                          std::promise<bool> promise) {
  // qDebug() << "_updateProgress called";

  bool error = false;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  for (auto itLP = pArray->begin(); itLP != pArray->end();) {
Valentin Platzgummer's avatar
Valentin Platzgummer committed

Valentin Platzgummer's avatar
Valentin Platzgummer committed
    auto it = _remoteTiles.find(itLP->id());
Valentin Platzgummer's avatar
Valentin Platzgummer committed

Valentin Platzgummer's avatar
Valentin Platzgummer committed
    if (Q_LIKELY(it != _remoteTiles.end())) {
      it->second->setProgress(itLP->progress());
      ++itLP;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    } else {
      qCDebug(NemoInterfaceLog)
Valentin Platzgummer's avatar
Valentin Platzgummer committed
          << "_updateProgress(): tile with id " << itLP->id() << " not found.";
      itLP = pArray->erase(itLP);
      error = true;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    }
  }

Valentin Platzgummer's avatar
Valentin Platzgummer committed
  if (pArray->size() > 0) {
    emit _parent->progressChanged(*pArray);
  }

  promise.set_value(!error);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
}

void NemoInterface::Impl::_onHeartbeatReceived(const QNemoHeartbeat &hb,
                                               std::promise<bool> promise) {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  _lastHeartbeat = hb;
  this->_timeoutTimer.start(NO_HEARTBEAT_TIMEOUT);
  if (this->_state == STATE::TRY_TOPIC_SERVICE_SETUP) {
    this->_setState(STATE::READY);
    this->_doAction();
  } else if (this->_state == STATE::HEARTBEAT_TIMEOUT) {
    this->_setState(STATE::READY);
    this->_doAction();
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  }
  promise.set_value(true);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
}

void NemoInterface::Impl::_setInfoString(const QString &info) {
  if (_infoString != info) {
    _infoString = info;
    emit this->_parent->infoStringChanged();
  }
}

void NemoInterface::Impl::_setWarningString(const QString &warning) {
  if (_warningString != warning) {
    _warningString = warning;
    emit this->_parent->warningStringChanged();
  }
}

void NemoInterface::Impl::_doTopicServiceSetup() {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  using namespace ros_bridge::messages;
Valentin Platzgummer's avatar
Valentin Platzgummer committed

  // Subscribe nemo progress.
  this->_pRosbridge->subscribeTopic(
      progressTopic, [this](const QJsonObject &o) {
        nemo_msgs::progress_array::ProgressArray progressArray;
        if (nemo_msgs::progress_array::fromJson(o, progressArray)) {

          // correct range errors of progress
          for (auto &lp : progressArray.progress_array()) {
            bool rangeError = false;
            if (lp.progress() < 0) {
              lp.setProgress(0);
              rangeError = true;
            }
            if (lp.progress() > 100) {
              lp.setProgress(100);
              rangeError = true;
            }

            if (rangeError) {
              qCWarning(NemoInterfaceLog) << "/nemo/progress progress out "
                                             "of range, value was set to: "
                                          << lp.progress();
Valentin Platzgummer's avatar
Valentin Platzgummer committed
            }
          }

          auto p = std::make_shared<ProgressArray>();
          *p = std::move(progressArray.progress_array());
          std::promise<bool> promise;
          auto future = promise.get_future();
          bool value = QMetaObject::invokeMethod(
              this->_parent, [this, p, promise = std::move(promise)]() mutable {
                this->_updateProgress(p, std::move(promise));
              });
          Q_ASSERT(value == true);
          future.wait();
Valentin Platzgummer's avatar
Valentin Platzgummer committed
        } else {
          qCWarning(NemoInterfaceLog) << "/nemo/progress not able to "
                                         "create ProgressArray form json: "
Valentin Platzgummer's avatar
Valentin Platzgummer committed
        }
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  // Subscribe heartbeat msg.
  this->_pRosbridge->subscribeTopic(
      heartbeatTopic, [this](const QJsonObject &o) {
        nemo_msgs::heartbeat::Heartbeat heartbeat;
        if (nemo_msgs::heartbeat::fromJson(o, heartbeat)) {
          std::promise<bool> promise;
          auto future = promise.get_future();
          bool value = QMetaObject::invokeMethod(
              this->_parent,
              [this, heartbeat, promise = std::move(promise)]() mutable {
                this->_onHeartbeatReceived(heartbeat, std::move(promise));
              });
          Q_ASSERT(value == true);
          future.wait();
Valentin Platzgummer's avatar
Valentin Platzgummer committed
        } else {
          qCWarning(NemoInterfaceLog) << "/nemo/heartbeat not able to "
                                         "create Heartbeat form json: "
Valentin Platzgummer's avatar
Valentin Platzgummer committed
        }
Valentin Platzgummer's avatar
Valentin Platzgummer committed
void NemoInterface::Impl::_trySynchronize() {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  if ((this->_state == STATE::READY || this->_state == STATE::SYS_SYNC ||
       this->_state == STATE::USER_SYNC) &&
      !_isSynchronized()) {
    if (!_dispatcher.idle()) {
      QTimer::singleShot(5000, [this] { this->_trySynchronize(); });
      return;
    }

    qCWarning(NemoInterfaceLog) << "trying to synchronize";

Valentin Platzgummer's avatar
Valentin Platzgummer committed
    this->_setState(STATE::SYS_SYNC);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    this->_doAction();

    // create clear cmd.
    auto pTask = std::make_unique<nemo_interface::Task>(
        std::bind(&Impl::_callClearTiles, this));

    // dispatch command.
    Q_ASSERT(_dispatcher.pendingTasks() == 0);
    auto ret = _dispatcher.dispatch(std::move(pTask));
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    auto clearFuture = ret.share();
Valentin Platzgummer's avatar
Valentin Platzgummer committed

    // create tile array.
    auto pTileArray = std::make_shared<QVector<std::shared_ptr<const Tile>>>();
    for (auto pair : _localTiles) {
      pTileArray->push_back(pair.second);
    }

    // create addTiles cmd.
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    pTask =
        std::make_unique<nemo_interface::Task>([this, pTileArray, clearFuture] {
          clearFuture.wait();
          if (clearFuture.get().toBool()) {
            return this->_callAddTiles(pTileArray);
          } else {
            return QVariant(false);
          }
        });

    // dispatch command.
    ret = _dispatcher.dispatch(std::move(pTask));
    auto addFuture = ret.share();

    // create GetAllProgress cmd.
    pTask = std::make_unique<nemo_interface::Task>([this, addFuture] {
      addFuture.wait();
      if (addFuture.get().toBool()) {
        return this->_callGetAllProgress();
      } else {
        return QVariant(false);
      }
    });
Valentin Platzgummer's avatar
Valentin Platzgummer committed

    // dispatch command.
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    ret = _dispatcher.dispatch(std::move(pTask));
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    _futureWatcher.setFuture(ret.share());
  }
}

bool NemoInterface::Impl::_isSynchronized() const {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  return _localTiles.size() > 0 && _remoteTiles.size() > 0 &&
         std::equal(
             _localTiles.begin(), _localTiles.end(), _remoteTiles.begin(),
             [](const auto &a, const auto &b) { return a.first == b.first; });
}

Valentin Platzgummer's avatar
Valentin Platzgummer committed
void NemoInterface::Impl::_doAction() {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  static bool resetDone = false;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  switch (this->_state) {
  case STATE::STOPPED:
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    this->_timeoutTimer.stop();
    this->_clearTilesRemote(std::promise<bool>());
    if (this->_pRosbridge->state() != Rosbridge::STATE::STOPPED) {
      this->_pRosbridge->stop();
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    break;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  case STATE::START_BRIDGE:
    this->_pRosbridge->start();
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    break;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  case STATE::WEBSOCKET_DETECTED:
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    resetDone = false;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    this->_setState(STATE::TRY_TOPIC_SERVICE_SETUP);
    this->_doAction();
    break;
  case STATE::TRY_TOPIC_SERVICE_SETUP:
    this->_doTopicServiceSetup();
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    this->_timeoutTimer.start(NO_HEARTBEAT_TIMEOUT);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    break;
  case STATE::READY:
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    _trySynchronize();
    break;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  case STATE::USER_SYNC:
  case STATE::SYS_SYNC:
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    break;
  case STATE::HEARTBEAT_TIMEOUT:
    this->_clearTilesRemote(std::promise<bool>());
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    break;
  case STATE::WEBSOCKET_TIMEOUT:
    if (!resetDone) {
      resetDone = true;
      this->_pRosbridge->stop();
      this->_pRosbridge->start();
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    }
    this->_timeoutTimer.stop();
    this->_clearTilesRemote(std::promise<bool>());
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    break;
  };
Valentin Platzgummer's avatar
Valentin Platzgummer committed
QVariant NemoInterface::Impl::_callAddTiles(
    std::shared_ptr<QVector<std::shared_ptr<const Tile>>> pTileArray) {
Valentin Platzgummer's avatar
Valentin Platzgummer committed

  // qDebug() << "_callAddTiles called";
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  this->_lastCall = CALL_NAME::ADD_TILES;

Valentin Platzgummer's avatar
Valentin Platzgummer committed
  // create json object
  QJsonArray jsonTileArray;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  for (const auto &tile : *pTileArray) {
    using namespace ros_bridge::messages;
    QJsonObject jsonTile;
    if (!nemo_msgs::tile::toJson(*tile, jsonTile)) {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
      qCDebug(NemoInterfaceLog)
          << "addTiles(): not able to create json object: tile id: "
          << tile->id() << " progress: " << tile->progress()
          << " points: " << tile->tile();
    }
    jsonTileArray.append(std::move(jsonTile));
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  } // for
  QJsonObject req;
  req["in_tile_array"] = std::move(jsonTileArray);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  // create response handler.
  auto promise_response = std::make_shared<std::promise<bool>>();
  auto future_response = promise_response->get_future();
  auto responseHandler = [promise_response](const QJsonObject &o) mutable {
    // check if transaction was successfull
    if (o.contains("success") && o["success"].isBool()) {
      promise_response->set_value(o["success"].toBool());
      qCWarning(NemoInterfaceLog)
          << "/nemo/add_tiles no \"success\" key or wrong type: " << o;
      promise_response->set_value(false);
    }
  };
Valentin Platzgummer's avatar
Valentin Platzgummer committed

  // call service.
  this->_pRosbridge->callService("/nemo/add_tiles", responseHandler, req);
Valentin Platzgummer's avatar
Valentin Platzgummer committed

  // wait for response.
  auto tStart = hrc::now();
  bool abort = true;
  do {
    auto status = future_response.wait_for(std::chrono::milliseconds(100));
    if (status == std::future_status::ready) {
      abort = false;
      break;
    }
  } while (hrc::now() - tStart < maxResponseTime ||
           this->_dispatcher.isInterruptionRequested());

  if (abort) {
    qCWarning(NemoInterfaceLog)
        << "addTiles(): Websocket not responding to request.";
    return QVariant(false);
  }

  // transaction error?
  if (!future_response.get()) {
    return QVariant(false);
  }

  // add remote tiles (_remoteTiles)
  std::promise<bool> promise;
  auto future = promise.get_future();
  bool value = QMetaObject::invokeMethod(
      this->_parent /* context */,
      [this, pTileArray, promise = std::move(promise)]() mutable {
        this->_addTilesRemote(pTileArray, std::move(promise));
      });
  Q_ASSERT(value == true);
Valentin Platzgummer's avatar
Valentin Platzgummer committed

  // return success
  return QVariant(future.get());
Valentin Platzgummer's avatar
Valentin Platzgummer committed
}

QVariant
NemoInterface::Impl::_callRemoveTiles(std::shared_ptr<IDArray> pIdArray) {
  // qDebug() << "_callRemoveTiles called";
Valentin Platzgummer's avatar
Valentin Platzgummer committed

  this->_lastCall = CALL_NAME::REMOVE_TILES;

Valentin Platzgummer's avatar
Valentin Platzgummer committed
  // create json object
  QJsonArray jsonIdArray;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  for (const auto id : *pIdArray) {
    using namespace ros_bridge::messages;
    jsonIdArray.append(id);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  } // for
  QJsonObject req;
  req["ids"] = std::move(jsonIdArray);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  // create response handler.
  auto promise_response = std::make_shared<std::promise<bool>>();
  auto future_response = promise_response->get_future();
  auto responseHandler = [promise_response](const QJsonObject &o) mutable {
    // check if transaction was successfull
    QString msg = QJsonDocument(o).toJson(QJsonDocument::JsonFormat::Compact);
    if (o.contains("success") && o["success"].isBool()) {
      promise_response->set_value(o["success"].toBool());
      qCWarning(NemoInterfaceLog)
          << "/nemo/remove_tiles no \"success\" key or wrong type: " << msg;
      promise_response->set_value(false);
    }
  };
Valentin Platzgummer's avatar
Valentin Platzgummer committed

  // call service.
  this->_pRosbridge->callService("/nemo/remove_tiles", responseHandler, req);
Valentin Platzgummer's avatar
Valentin Platzgummer committed

  // wait for response.
  auto tStart = hrc::now();
  bool abort = true;
  do {
    auto status = future_response.wait_for(std::chrono::milliseconds(100));
    if (status == std::future_status::ready) {
      abort = false;
      break;
    }
  } while (hrc::now() - tStart < maxResponseTime ||
           this->_dispatcher.isInterruptionRequested());

  if (abort) {
    qCWarning(NemoInterfaceLog)
        << "remove_tiles(): Websocket not responding to request.";
    return QVariant(false);
  }

  // transaction error?
  if (!future_response.get()) {
    return QVariant(false);
  }

  // remove remote tiles (_remoteTiles)
  std::promise<bool> promise;
  auto future = promise.get_future();
  bool value = QMetaObject::invokeMethod(
      this->_parent /* context */,
      [this, pIdArray, promise = std::move(promise)]() mutable {
        this->_removeTilesRemote(pIdArray, std::move(promise));
      });
  Q_ASSERT(value == true);
Valentin Platzgummer's avatar
Valentin Platzgummer committed

  // return success
  return QVariant(future.get());
Valentin Platzgummer's avatar
Valentin Platzgummer committed
}

QVariant NemoInterface::Impl::_callClearTiles() {
Valentin Platzgummer's avatar
Valentin Platzgummer committed

  // qDebug() << "_callClearTiles called";
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  this->_lastCall = CALL_NAME::CLEAR_TILES;

Valentin Platzgummer's avatar
Valentin Platzgummer committed
  // create response handler.
  auto promise_response = std::make_shared<std::promise<bool>>();
  auto future_response = promise_response->get_future();
  auto responseHandler = [promise_response](const QJsonObject &o) mutable {
    // check if transaction was successfull
    promise_response->set_value(true);
Valentin Platzgummer's avatar
Valentin Platzgummer committed

  // call service.
  this->_pRosbridge->callService("/nemo/clear_tiles", responseHandler,
                                 QJsonObject());
Valentin Platzgummer's avatar
Valentin Platzgummer committed

  // wait for response.
  auto tStart = hrc::now();
  bool abort = true;
  do {
    auto status = future_response.wait_for(std::chrono::milliseconds(100));
    if (status == std::future_status::ready) {
      abort = false;
      break;
    }
  } while (hrc::now() - tStart < maxResponseTime ||
           this->_dispatcher.isInterruptionRequested());

  if (abort) {
    qCWarning(NemoInterfaceLog) << "Websocket not responding to request.";
    return QVariant(false);
  }

  // transaction failed?
  if (!future_response.get()) {
    return QVariant(false);
  }

  // clear remote tiles (_remoteTiles)
  std::promise<bool> promise;
  auto future = promise.get_future();
  bool value = QMetaObject::invokeMethod(
      this->_parent, [this, promise = std::move(promise)]() mutable {
        this->_clearTilesRemote(std::move(promise));
      });
  Q_ASSERT(value == true);
Valentin Platzgummer's avatar
Valentin Platzgummer committed

  // return success
  return QVariant(future.get());
Valentin Platzgummer's avatar
Valentin Platzgummer committed
}

QVariant
NemoInterface::Impl::_callGetProgress(std::shared_ptr<IDArray> pIdArray) {
  // qDebug() << "_callGetProgress called";
Valentin Platzgummer's avatar
Valentin Platzgummer committed

  this->_lastCall = CALL_NAME::GET_PROGRESS;

Valentin Platzgummer's avatar
Valentin Platzgummer committed
  // create json object
  QJsonArray jsonIdArray;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  for (const auto id : *pIdArray) {
    using namespace ros_bridge::messages;
    jsonIdArray.append(id);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  } // for
  QJsonObject req;
  req["ids"] = std::move(jsonIdArray);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
  // create response handler.
  typedef std::shared_ptr<ProgressArray> ResponseType;
  auto promise_response = std::make_shared<std::promise<ResponseType>>();
  auto future_response = promise_response->get_future();
  auto responseHandler = [promise_response](const QJsonObject &o) mutable {
    // check if transaction was successfull
    ros_bridge::messages::nemo_msgs::progress_array::ProgressArray
        progressArrayMsg;
    if (ros_bridge::messages::nemo_msgs::progress_array::fromJson(
            o, progressArrayMsg)) {