NemoInterface.cpp 15.5 KB
Newer Older
Valentin Platzgummer's avatar
Valentin Platzgummer committed
1
#include "NemoInterface.h"
2
#include "SnakeTilesLocal.h"
Valentin Platzgummer's avatar
Valentin Platzgummer committed
3 4

#include "QGCApplication.h"
5
#include "QGCLoggingCategory.h"
Valentin Platzgummer's avatar
Valentin Platzgummer committed
6 7 8 9 10 11 12 13 14 15 16
#include "QGCToolbox.h"
#include "SettingsFact.h"
#include "SettingsManager.h"
#include "WimaSettings.h"

#include <shared_mutex>

#include <QTimer>

#include "QNemoHeartbeat.h"
#include "QNemoProgress.h"
17 18 19
#include "Wima/Geometry/WimaMeasurementArea.h"
#include "Wima/Snake/SnakeTile.h"
#include "Wima/Snake/snake.h"
Valentin Platzgummer's avatar
Valentin Platzgummer committed
20 21 22 23 24 25 26 27 28 29

#include "ros_bridge/include/messages/geographic_msgs/geopoint.h"
#include "ros_bridge/include/messages/jsk_recognition_msgs/polygon_array.h"
#include "ros_bridge/include/messages/nemo_msgs/heartbeat.h"
#include "ros_bridge/include/messages/nemo_msgs/progress.h"
#include "ros_bridge/include/ros_bridge.h"
#include "ros_bridge/rapidjson/include/rapidjson/document.h"
#include "ros_bridge/rapidjson/include/rapidjson/ostreamwrapper.h"
#include "ros_bridge/rapidjson/include/rapidjson/writer.h"

30 31
QGC_LOGGING_CATEGORY(NemoInterfaceLog, "NemoInterfaceLog")

Valentin Platzgummer's avatar
Valentin Platzgummer committed
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
#define EVENT_TIMER_INTERVAL 100 // ms
auto static timeoutInterval = std::chrono::milliseconds(3000);

using ROSBridgePtr = std::unique_ptr<ros_bridge::ROSBridge>;
using JsonDocUPtr = ros_bridge::com_private::JsonDocUPtr;
using UniqueLock = std::unique_lock<std::shared_timed_mutex>;
using SharedLock = std::shared_lock<std::shared_timed_mutex>;
using JsonDocUPtr = ros_bridge::com_private::JsonDocUPtr;

class NemoInterface::Impl {
  using TimePoint = std::chrono::time_point<std::chrono::high_resolution_clock>;

public:
  Impl(NemoInterface *p);

  void start();
  void stop();
49 50
  void setTileData(const TileData &tileData);
  bool hasTileData(const TileData &tileData) const;
51 52
  void setAutoPublish(bool ap);
  void setHoldProgress(bool hp);
53

54 55 56
  void publishTileData();

  NemoInterface::STATUS status();
Valentin Platzgummer's avatar
Valentin Platzgummer committed
57
  QVector<int> progress();
58
  bool running();
Valentin Platzgummer's avatar
Valentin Platzgummer committed
59 60

private:
61
  void doTopicServiceSetup();
Valentin Platzgummer's avatar
Valentin Platzgummer committed
62
  void loop();
63 64
  static STATUS heartbeatToStatus(
      const ros_bridge::messages::nemo_msgs::heartbeat::Heartbeat &hb);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
65 66 67 68 69 70 71 72 73 74
  //!
  //! \brief Publishes tilesENU
  //! \pre this->tilesENUMutex must be locked
  //!
  void publishTilesENU();
  //!
  //! \brief Publishes ENUOrigin
  //! \pre this->ENUOriginMutex must be locked
  //!
  void publishENUOrigin();
75
  bool setStatus(NemoInterface::STATUS s);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
76 77 78 79 80 81

  // Data.
  SnakeTilesLocal tilesENU;
  mutable std::shared_timed_mutex tilesENUMutex;
  QGeoCoordinate ENUOrigin;
  mutable std::shared_timed_mutex ENUOriginMutex;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
82
  QNemoProgress qProgress;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
83 84
  mutable std::shared_timed_mutex progressMutex;
  TimePoint nextTimeout;
85 86
  mutable std::shared_timed_mutex timeoutMutex;
  std::atomic<NemoInterface::STATUS> status_;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
87

88 89 90
  // Not protected data.
  TileData tileData;

Valentin Platzgummer's avatar
Valentin Platzgummer committed
91
  // Internals
92
  std::atomic_bool running_;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
93 94 95 96 97 98
  std::atomic_bool topicServiceSetupDone;
  ROSBridgePtr pRosBridge;
  QTimer loopTimer;
  NemoInterface *parent;
};

99
using StatusMap = std::map<NemoInterface::STATUS, QString>;
100
static StatusMap statusMap{
101 102 103 104 105 106 107 108 109 110 111
    std::make_pair<NemoInterface::STATUS, QString>(
        NemoInterface::STATUS::NOT_CONNECTED, "Not Connected"),
    std::make_pair<NemoInterface::STATUS, QString>(
        NemoInterface::STATUS::HEARTBEAT_DETECTED, "Heartbeat Detected"),
    std::make_pair<NemoInterface::STATUS, QString>(
        NemoInterface::STATUS::TIMEOUT, "Timeout"),
    std::make_pair<NemoInterface::STATUS, QString>(
        NemoInterface::STATUS::INVALID_HEARTBEAT, "Error"),
    std::make_pair<NemoInterface::STATUS, QString>(
        NemoInterface::STATUS::WEBSOCKET_DETECTED, "Websocket Detected")};

Valentin Platzgummer's avatar
Valentin Platzgummer committed
112
NemoInterface::Impl::Impl(NemoInterface *p)
113
    : nextTimeout(TimePoint::max()), status_(STATUS::NOT_CONNECTED),
114
      running_(false), topicServiceSetupDone(false), parent(p) {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
115 116 117 118 119 120 121 122 123 124

  // ROS Bridge.
  WimaSettings *wimaSettings =
      qgcApp()->toolbox()->settingsManager()->wimaSettings();
  auto connectionStringFact = wimaSettings->rosbridgeConnectionString();
  auto setConnectionString = [connectionStringFact, this] {
    auto connectionString = connectionStringFact->rawValue().toString();
    if (ros_bridge::isValidConnectionString(
            connectionString.toLocal8Bit().data())) {
    } else {
125 126 127 128
      qgcApp()->warningMessageBoxOnMainThread(
          "Nemo Interface",
          "Websocket connection string possibly invalid: " + connectionString +
              ". Trying to connect anyways.");
Valentin Platzgummer's avatar
Valentin Platzgummer committed
129
    }
130 131
    this->pRosBridge.reset(
        new ros_bridge::ROSBridge(connectionString.toLocal8Bit().data()));
Valentin Platzgummer's avatar
Valentin Platzgummer committed
132 133 134 135 136 137
  };
  connect(connectionStringFact, &SettingsFact::rawValueChanged,
          setConnectionString);
  setConnectionString();

  // Periodic.
Valentin Platzgummer's avatar
Valentin Platzgummer committed
138
  connect(&this->loopTimer, &QTimer::timeout, [this] { this->loop(); });
Valentin Platzgummer's avatar
Valentin Platzgummer committed
139 140 141
  this->loopTimer.start(EVENT_TIMER_INTERVAL);
}

142 143 144 145
void NemoInterface::Impl::start() {
  this->running_ = true;
  emit this->parent->runningChanged();
}
Valentin Platzgummer's avatar
Valentin Platzgummer committed
146

147 148 149 150
void NemoInterface::Impl::stop() {
  this->running_ = false;
  emit this->parent->runningChanged();
}
Valentin Platzgummer's avatar
Valentin Platzgummer committed
151

152 153 154 155 156 157 158
void NemoInterface::Impl::setTileData(const TileData &tileData) {
  this->tileData = tileData;
  if (tileData.tiles.count() > 0) {
    std::lock(this->ENUOriginMutex, this->tilesENUMutex);
    UniqueLock lk1(this->ENUOriginMutex, std::adopt_lock);
    UniqueLock lk2(this->tilesENUMutex, std::adopt_lock);

159
    const auto *obj = tileData.tiles[0];
160 161 162
    const auto *tile = qobject_cast<const SnakeTile *>(obj);
    if (tile != nullptr) {
      if (tile->coordinateList().size() > 0) {
163
        if (tile->coordinateList().first().isValid()) {
164 165 166 167 168 169 170 171 172 173 174
          this->ENUOrigin = tile->coordinateList().first();
          const auto &origin = this->ENUOrigin;
          this->tilesENU.polygons().clear();
          for (int i = 0; i < tileData.tiles.count(); ++i) {
            obj = tileData.tiles[i];
            tile = qobject_cast<const SnakeTile *>(obj);
            if (tile != nullptr) {
              SnakeTileLocal tileENU;
              snake::areaToEnu(origin, tile->coordinateList(), tileENU.path());
              this->tilesENU.polygons().push_back(std::move(tileENU));
            } else {
175
              qCDebug(NemoInterfaceLog) << "Impl::setTileData(): nullptr.";
176 177
              break;
            }
178
          }
179
        } else {
180
          qCDebug(NemoInterfaceLog) << "Impl::setTileData(): Origin invalid.";
181
        }
182 183
      } else {
        qCDebug(NemoInterfaceLog) << "Impl::setTileData(): tile empty.";
184
      }
185
    }
186 187 188 189 190 191 192 193
  } else {
    this->tileData.clear();

    std::lock(this->ENUOriginMutex, this->tilesENUMutex);
    UniqueLock lk1(this->ENUOriginMutex, std::adopt_lock);
    UniqueLock lk2(this->tilesENUMutex, std::adopt_lock);
    this->ENUOrigin = QGeoCoordinate(0, 0, 0);
    this->tilesENU = SnakeTilesLocal();
Valentin Platzgummer's avatar
Valentin Platzgummer committed
194 195 196
  }
}

197
bool NemoInterface::Impl::hasTileData(const TileData &tileData) const {
198
  return this->tileData == tileData;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
199 200
}

201 202 203 204 205 206 207 208 209 210 211 212
void NemoInterface::Impl::publishTileData() {
  std::lock(this->ENUOriginMutex, this->tilesENUMutex);
  UniqueLock lk1(this->ENUOriginMutex, std::adopt_lock);
  UniqueLock lk2(this->tilesENUMutex, std::adopt_lock);

  if (this->tilesENU.polygons().size() > 0 && this->running_ &&
      this->topicServiceSetupDone) {
    this->publishENUOrigin();
    this->publishTilesENU();
  }
}

213
NemoInterface::STATUS NemoInterface::Impl::status() { return status_.load(); }
Valentin Platzgummer's avatar
Valentin Platzgummer committed
214 215 216

QVector<int> NemoInterface::Impl::progress() {
  SharedLock lk(this->progressMutex);
217
  return this->qProgress.progress();
Valentin Platzgummer's avatar
Valentin Platzgummer committed
218 219
}

220 221
bool NemoInterface::Impl::running() { return this->running_.load(); }

222
void NemoInterface::Impl::doTopicServiceSetup() {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
223 224
  using namespace ros_bridge::messages;

225
  // snake tiles.
Valentin Platzgummer's avatar
Valentin Platzgummer committed
226 227 228 229 230 231 232
  {
    SharedLock lk(this->tilesENUMutex);
    this->pRosBridge->advertiseTopic(
        "/snake/tiles",
        jsk_recognition_msgs::polygon_array::messageType().c_str());
  }

233
  // snake origin.
Valentin Platzgummer's avatar
Valentin Platzgummer committed
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250
  {
    SharedLock lk(this->ENUOriginMutex);
    this->pRosBridge->advertiseTopic(
        "/snake/origin", geographic_msgs::geo_point::messageType().c_str());
  }

  // Subscribe nemo progress.
  this->pRosBridge->subscribe(
      "/nemo/progress",
      /* callback */ [this](JsonDocUPtr pDoc) {
        std::lock(this->progressMutex, this->tilesENUMutex,
                  this->ENUOriginMutex);
        UniqueLock lk1(this->progressMutex, std::adopt_lock);
        UniqueLock lk2(this->tilesENUMutex, std::adopt_lock);
        UniqueLock lk3(this->ENUOriginMutex, std::adopt_lock);

        int requiredSize = this->tilesENU.polygons().size();
Valentin Platzgummer's avatar
Valentin Platzgummer committed
251
        auto &progressMsg = this->qProgress;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
252 253 254 255
        if (!nemo_msgs::progress::fromJson(*pDoc, progressMsg) ||
            progressMsg.progress().size() !=
                requiredSize) { // Some error occured.
          progressMsg.progress().clear();
256 257
          qgcApp()->informationMessageBoxOnMainThread(
              "Nemo Interface", "Invalid progress message received.");
Valentin Platzgummer's avatar
Valentin Platzgummer committed
258
        }
259
        emit this->parent->progressChanged();
260

Valentin Platzgummer's avatar
Valentin Platzgummer committed
261 262 263 264 265 266 267 268 269
        lk1.unlock();
        lk2.unlock();
        lk3.unlock();
      });

  // Subscribe /nemo/heartbeat.
  this->pRosBridge->subscribe(
      "/nemo/heartbeat",
      /* callback */ [this](JsonDocUPtr pDoc) {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
270
        //        auto start = std::chrono::high_resolution_clock::now();
271
        nemo_msgs::heartbeat::Heartbeat heartbeatMsg;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
272
        if (!nemo_msgs::heartbeat::fromJson(*pDoc, heartbeatMsg)) {
273
          this->setStatus(STATUS::INVALID_HEARTBEAT);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
274
        } else {
275
          this->setStatus(heartbeatToStatus(heartbeatMsg));
276
        }
277 278
        if (this->status_ == STATUS::INVALID_HEARTBEAT) {
          UniqueLock lk(this->timeoutMutex);
279
          this->nextTimeout = TimePoint::max();
280 281
        } else if (this->status_ == STATUS::HEARTBEAT_DETECTED) {
          UniqueLock lk(this->timeoutMutex);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
282 283 284 285
          this->nextTimeout =
              std::chrono::high_resolution_clock::now() + timeoutInterval;
        }

Valentin Platzgummer's avatar
Valentin Platzgummer committed
286 287 288 289 290 291
        //        auto delta =
        //        std::chrono::duration_cast<std::chrono::milliseconds>(
        //            std::chrono::high_resolution_clock::now() - start);
        //        std::cout << "/nemo/heartbeat callback time: " <<
        //        delta.count() << " ms"
        //                  << std::endl;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
292 293 294 295 296 297 298
      });

  // Advertise /snake/get_origin.
  this->pRosBridge->advertiseService(
      "/snake/get_origin", "snake_msgs/GetOrigin",
      [this](JsonDocUPtr) -> JsonDocUPtr {
        using namespace ros_bridge::messages;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
299
        SharedLock lk(this->ENUOriginMutex);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
300 301 302 303 304 305

        JsonDocUPtr pDoc(
            std::make_unique<rapidjson::Document>(rapidjson::kObjectType));
        auto &origin = this->ENUOrigin;
        rapidjson::Value jOrigin(rapidjson::kObjectType);
        lk.unlock();
306 307 308
        if (geographic_msgs::geo_point::toJson(origin, jOrigin,
                                               pDoc->GetAllocator())) {
          lk.unlock();
309
          pDoc->AddMember("origin", jOrigin, pDoc->GetAllocator());
310 311 312 313 314 315
        } else {
          lk.unlock();
          qCWarning(NemoInterfaceLog)
              << "/snake/get_origin service: could not create json document.";
        }

Valentin Platzgummer's avatar
Valentin Platzgummer committed
316 317 318 319 320 321 322
        return pDoc;
      });

  // Advertise /snake/get_tiles.
  this->pRosBridge->advertiseService(
      "/snake/get_tiles", "snake_msgs/GetTiles",
      [this](JsonDocUPtr) -> JsonDocUPtr {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
323
        SharedLock lk(this->tilesENUMutex);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
324 325 326 327

        JsonDocUPtr pDoc(
            std::make_unique<rapidjson::Document>(rapidjson::kObjectType));
        rapidjson::Value jSnakeTiles(rapidjson::kObjectType);
328 329 330 331

        if (jsk_recognition_msgs::polygon_array::toJson(
                this->tilesENU, jSnakeTiles, pDoc->GetAllocator())) {
          lk.unlock();
332
          pDoc->AddMember("tiles", jSnakeTiles, pDoc->GetAllocator());
333 334 335 336 337 338
        } else {
          lk.unlock();
          qCWarning(NemoInterfaceLog)
              << "/snake/get_tiles service: could not create json document.";
        }

Valentin Platzgummer's avatar
Valentin Platzgummer committed
339 340 341 342 343 344
        return pDoc;
      });
}

void NemoInterface::Impl::loop() {
  // Check ROS Bridge status and do setup if necessary.
345
  if (this->running_) {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
346 347
    if (!this->pRosBridge->isRunning()) {
      this->pRosBridge->start();
348
      this->loop();
Valentin Platzgummer's avatar
Valentin Platzgummer committed
349 350
    } else if (this->pRosBridge->isRunning() && this->pRosBridge->connected() &&
               !this->topicServiceSetupDone) {
351
      this->doTopicServiceSetup();
352
      this->topicServiceSetupDone = true;
353 354

      this->setStatus(STATUS::WEBSOCKET_DETECTED);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
355 356 357 358 359
    } else if (this->pRosBridge->isRunning() &&
               !this->pRosBridge->connected() && this->topicServiceSetupDone) {
      this->pRosBridge->reset();
      this->pRosBridge->start();
      this->topicServiceSetupDone = false;
360

361
      this->setStatus(STATUS::TIMEOUT);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
362 363 364 365 366 367 368
    }
  } else if (this->pRosBridge->isRunning()) {
    this->pRosBridge->reset();
    this->topicServiceSetupDone = false;
  }

  // Check if heartbeat timeout occured.
369
  if (this->running_ && this->topicServiceSetupDone) {
370
    UniqueLock lk(this->timeoutMutex);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
371 372
    if (this->nextTimeout != TimePoint::max() &&
        this->nextTimeout < std::chrono::high_resolution_clock::now()) {
373
      lk.unlock();
374
      if (this->pRosBridge->isRunning() && this->pRosBridge->connected()) {
375
        this->setStatus(STATUS::WEBSOCKET_DETECTED);
376
      } else {
377
        this->setStatus(STATUS::TIMEOUT);
378
      }
Valentin Platzgummer's avatar
Valentin Platzgummer committed
379 380 381 382
    }
  }
}

383 384 385 386 387 388 389 390
NemoInterface::STATUS NemoInterface::Impl::heartbeatToStatus(
    const ros_bridge::messages::nemo_msgs::heartbeat::Heartbeat &hb) {
  if (STATUS(hb.status()) == STATUS::HEARTBEAT_DETECTED)
    return STATUS::HEARTBEAT_DETECTED;
  else
    return STATUS::INVALID_HEARTBEAT;
}

Valentin Platzgummer's avatar
Valentin Platzgummer committed
391
void NemoInterface::Impl::publishTilesENU() {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
392
  using namespace ros_bridge::messages;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
393 394
  JsonDocUPtr jSnakeTiles(
      std::make_unique<rapidjson::Document>(rapidjson::kObjectType));
395 396
  if (jsk_recognition_msgs::polygon_array::toJson(
          this->tilesENU, *jSnakeTiles, jSnakeTiles->GetAllocator())) {
397
    this->pRosBridge->publish(std::move(jSnakeTiles), "/snake/tiles");
398 399 400 401
  } else {
    qCWarning(NemoInterfaceLog)
        << "Impl::publishTilesENU: could not create json document.";
  }
Valentin Platzgummer's avatar
Valentin Platzgummer committed
402 403 404
}

void NemoInterface::Impl::publishENUOrigin() {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
405
  using namespace ros_bridge::messages;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
406 407
  JsonDocUPtr jOrigin(
      std::make_unique<rapidjson::Document>(rapidjson::kObjectType));
408 409
  if (geographic_msgs::geo_point::toJson(this->ENUOrigin, *jOrigin,
                                         jOrigin->GetAllocator())) {
410
    this->pRosBridge->publish(std::move(jOrigin), "/snake/origin");
411 412 413 414
  } else {
    qCWarning(NemoInterfaceLog)
        << "Impl::publishENUOrigin: could not create json document.";
  }
Valentin Platzgummer's avatar
Valentin Platzgummer committed
415 416
}

417 418 419 420 421 422 423 424 425 426
bool NemoInterface::Impl::setStatus(NemoInterface::STATUS s) {
  if (s != this->status_) {
    this->status_ = s;
    emit this->parent->statusChanged();
    return true;
  } else {
    return false;
  }
}

Valentin Platzgummer's avatar
Valentin Platzgummer committed
427 428 429 430 431 432 433
// ===============================================================
// NemoInterface
NemoInterface::NemoInterface(QObject *parent)
    : QObject(parent), pImpl(std::make_unique<NemoInterface::Impl>(this)) {}

NemoInterface::~NemoInterface() {}

Valentin Platzgummer's avatar
Valentin Platzgummer committed
434 435 436 437
void NemoInterface::start() { this->pImpl->start(); }

void NemoInterface::stop() { this->pImpl->stop(); }

438 439 440
void NemoInterface::publishTileData() { this->pImpl->publishTileData(); }

void NemoInterface::requestProgress() {
441
  qCWarning(NemoInterfaceLog) << "requestProgress(): dummy.";
442 443 444
}

void NemoInterface::setTileData(const TileData &tileData) {
445
  this->pImpl->setTileData(tileData);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
446 447
}

448 449
bool NemoInterface::hasTileData(const TileData &tileData) const {
  return this->pImpl->hasTileData(tileData);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
450 451
}

452 453 454
int NemoInterface::status() const { return integral(this->pImpl->status()); }

NemoInterface::STATUS NemoInterface::statusEnum() const {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
455 456 457
  return this->pImpl->status();
}

458 459 460 461
QString NemoInterface::statusString() const {
  return statusMap.at(this->pImpl->status());
}

Valentin Platzgummer's avatar
Valentin Platzgummer committed
462
QVector<int> NemoInterface::progress() const { return this->pImpl->progress(); }
463 464 465 466 467 468

QString NemoInterface::editorQml() {
  return QStringLiteral("NemoInterface.qml");
}

bool NemoInterface::running() { return this->pImpl->running(); }