#include "NemoInterface.h" #include "QGCApplication.h" #include "QGCLoggingCategory.h" #include "QGCToolbox.h" #include "SettingsFact.h" #include "SettingsManager.h" #include "WimaSettings.h" #include #include #include #include #include #include "GenericSingelton.h" #include "geometry/MeasurementArea.h" #include "geometry/geometry.h" #include "nemo_interface/MeasurementTile.h" #include "nemo_interface/QNemoHeartbeat.h" #include "nemo_interface/TaskDispatcher.h" #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" #include "ros_bridge/include/messages/nemo_msgs/tile.h" #include "ros_bridge/include/messages/nemo_msgs/tile_array.h" #include "rosbridge/rosbridge.h" /* * Here some rules: * * not threas safe functions are marked with *NotSafe(...) * * If a function is not safe: * * the call to it must be protected with a Lock. * * not safe functions are not allowed to emit signals directly (danger of * deadlock). * * if a not safe function needs to emit a signal, defere it with * QTimer::singleShot(). * * not safe functions are allowed to call other not safe functions * * it is a bad idea to wait inside a not safe function for a asynchronous * operation (potential deadlock) * * Functions that are not marked with *NotSafe(...) must be thread safe! */ #define INVM(context, fun) \ { \ auto value = QMetaObject::invokeMethod(context, fun); \ Q_ASSERT(value == true); \ Q_UNUSED(value); \ } Q_DECLARE_METATYPE(ProgressArray) QGC_LOGGING_CATEGORY(NemoInterfaceLog, "NemoInterfaceLog") #define NO_HEARTBEAT_TIMEOUT 5000 // ms #define RESTART_INTERVAl 600000 // ms == 10 min #define RESTART_RETRY_INTERVAl 2000 // ms #define SYNC_INTERVAL 10000 // ms #define SYNC_RETRY_INTERVAL 2000 // ms static constexpr auto maxResponseTime = std::chrono::milliseconds(20000); static char const *progressTopic = "/nemo/progress"; static char const *heartbeatTopic = "/nemo/heartbeat"; static char const *addTilesService = "/nemo/add_tiles"; static char const *removeTilesService = "/nemo/remove_tiles"; static char const *clearTilesService = "/nemo/clear_tiles"; static char const *getAllTilesService = "/nemo/get_all_tiles"; // static char const *getTilesService = "/nemo/get_tiles"; // static char const *containsTilesService = "/nemo/contains_tiles"; // static char const *extractTilesService = "/nemo/extract_tiles"; // static char const *sizeService = "/nemo/size"; // static char const *emptyService = "/nemo/empty"; static char const *getProgressService = "/nemo/get_progress"; static char const *getAllProgressService = "/nemo/get_all_progress"; static char const *getVersionService = "/nemo/get_version"; static const std::vector requiredServices{ addTilesService, removeTilesService, clearTilesService, getAllTilesService, getAllProgressService, getProgressService, getVersionService}; static const std::vector requiredTopics{progressTopic, heartbeatTopic}; using hrc = std::chrono::high_resolution_clock; using ROSBridgePtr = std::shared_ptr; typedef ros_bridge::messages::nemo_msgs::tile::GenericTile Tile; typedef std::map> TileMap; typedef std::map> TileMapConst; typedef ros_bridge::messages::nemo_msgs::heartbeat::Heartbeat Heartbeat; typedef nemo_interface::TaskDispatcher Dispatcher; typedef std::unique_lock Lock; class NemoInterface::Impl { enum class STATE { STOPPED, START_BRIDGE, WEBSOCKET_DETECTED, TRY_SETUP, USER_SYNC, SYS_SYNC, SYNC_ERROR, READY, WEBSOCKET_TIMEOUT, HEARTBEAT_TIMEOUT }; public: Impl(NemoInterface *p); ~Impl(); void start(); void stop(); // Tile editing. // Functions that require communication to device. std::shared_future addTiles(const TilePtrArray &tileArray); std::shared_future removeTiles(const IDArray &idArray); std::shared_future clearTiles(); // 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; // Progress. ProgressArray getProgress() const; ProgressArray getProgress(const IDArray &idArray) const; NemoInterface::STATUS status() const; bool running() const; // thread safe bool ready() const; // thread safe const QString &infoString() const; const QString &warningString() const; private: void _doSetup(); void _doActionNotSafe(); void _synchronize(); void _tryRestart(); bool _isSynchronizedNotSafe() const; bool _isSynchronized() const; void _onHeartbeatTimeout(); void _onRosbridgeStateChanged(); bool _updateProgress(const ProgressArray &pArray); void _onHeartbeatReceived(const QNemoHeartbeat &hb); void _setInfoStringNotSafe(const QString &info); void _setWarningStringNotSafe(const QString &warning); void _setInfoString(const QString &info); void _setWarningString(const QString &warning); // state suff bool _userSync() const; bool _sysSync() const; bool _setStateNotSafe(STATE newState); static bool _readyNotSafe(STATE s); static bool _userSyncNotSafe(STATE s); static bool _sysSyncNotSafe(STATE s); static bool _runningNotSafe(STATE s); static NemoInterface::STATUS _status(STATE state); static QString _toString(STATE s); static QString _toString(NemoInterface::STATUS s); // impl functions bool _addTilesImpl( std::shared_ptr>> pTileArray, std::shared_ptr pIdArray); bool _removeTilesImpl(std::shared_ptr pIdArray); bool _clearTilesImpl(); // Worker functions. static bool _callAddTiles(const QVector> &tileArray, Dispatcher &disp, Rosbridge &rb); static bool _callClearTiles(Dispatcher &d, Rosbridge &rb); static ProgressArray _callGetProgress(const IDArray &pIdArray, Dispatcher &d, Rosbridge &rb); static ProgressArray _callGetAllProgress(Dispatcher &d, Rosbridge &rb); static QVector> _callGetAllTiles(Dispatcher &d, Rosbridge &rb); static QString _callGetVersion(Dispatcher &d, Rosbridge &rb); static bool _callRemoveTiles(const IDArray &pIdArray, Dispatcher &d, Rosbridge &rb); // functions to manipulate _remoteTiles bool _addTilesRemote(const QVector> tileArray); bool _addTilesRemote(const QVector> &tileArray); void _removeTilesRemote(const IDArray &idArray); void _clearTilesRemote(); void _clearTilesRemoteNotSafe(); // static and const members static const char *_localVersion; NemoInterface *const _parent; // thread safe members ROSBridgePtr _pRosbridge; Dispatcher _dispatcher; // protected by mutex mutable std::mutex _m; STATE _state; TileMap _remoteTiles; TileMapConst _localTiles; QString _infoString; QString _warningString; // Members belonging to _parent->thread() QTimer _timeoutTimer; QTimer _syncTimer; QTimer _restartTimer; }; const char *NemoInterface::Impl::_localVersion("V_1.0"); using StatusMap = std::map; static StatusMap statusMap{ std::make_pair( NemoInterface::STATUS::NOT_CONNECTED, "Not Connected"), std::make_pair(NemoInterface::STATUS::ERROR, "ERROR"), std::make_pair(NemoInterface::STATUS::SYNC, "Synchronizing"), std::make_pair(NemoInterface::STATUS::READY, "Ready"), std::make_pair( NemoInterface::STATUS::TIMEOUT, "Timeout"), std::make_pair( NemoInterface::STATUS::WEBSOCKET_DETECTED, "Websocket Detected")}; NemoInterface::Impl::Impl(NemoInterface *p) : _parent(p), _state(STATE::STOPPED) { // ROS Bridge. WimaSettings *wimaSettings = qgcApp()->toolbox()->settingsManager()->wimaSettings(); auto connectionStringFact = wimaSettings->rosbridgeConnectionString(); auto setConnectionString = [connectionStringFact, this] { auto connectionString = connectionStringFact->rawValue().toString(); bool wasRunning = this->running(); this->stop(); this->_pRosbridge = std::make_shared( QUrl(QString("ws://") + connectionString.toLocal8Bit().data())); if (wasRunning) { this->start(); } }; connect(connectionStringFact, &SettingsFact::rawValueChanged, setConnectionString); setConnectionString(); // Heartbeat timeout. connect(&this->_timeoutTimer, &QTimer::timeout, std::bind(&Impl::_onHeartbeatTimeout, this)); connect(this->_pRosbridge.get(), &Rosbridge::stateChanged, this->_parent, [this] { this->_onRosbridgeStateChanged(); }); connect(&this->_restartTimer, &QTimer::timeout, this->_parent, [this] { this->_tryRestart(); }); connect(&this->_syncTimer, &QTimer::timeout, this->_parent, [this] { this->_synchronize(); }); static std::once_flag flag; std::call_once(flag, [] { qRegisterMetaType(); }); } NemoInterface::Impl::~Impl() { this->_pRosbridge->stop(); } void NemoInterface::Impl::start() { Lock lk(this->_m); if (!_runningNotSafe(this->_state)) { this->_setStateNotSafe(STATE::START_BRIDGE); this->_doActionNotSafe(); } } void NemoInterface::Impl::stop() { Lock lk(this->_m); if (_runningNotSafe(this->_state)) { this->_setStateNotSafe(STATE::STOPPED); this->_doActionNotSafe(); } } std::shared_future NemoInterface::Impl::addTiles(const TilePtrArray &tileArray) { using namespace nemo_interface; // qDebug() << "addTiles called"; if (tileArray.size() > 0) { // copy unknown tiles auto pTileArray = std::make_shared>>(); auto pIdArray = std::make_shared(); Lock lk(this->_m); 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(pTile->coordinateList(), 0.0, id); _localTiles.insert(std::make_pair(id, pTileCopy)); pTileArray->push_back(pTileCopy); pIdArray->push_back(id); } else { qCDebug(NemoInterfaceLog) << "addTiles(): tile with id: " << pTile->id() << "already added."; } } if (pTileArray->size() > 0) { lk.unlock(); emit this->_parent->tilesChanged(); lk.lock(); } // ready for send? if (pTileArray->size() > 0 && (this->_readyNotSafe(this->_state) || this->_userSyncNotSafe(this->_state))) { this->_setStateNotSafe(STATE::USER_SYNC); this->_doActionNotSafe(); lk.unlock(); // create task. auto pTask = std::make_unique([this, pTileArray, pIdArray] { auto ret = this->_addTilesImpl(pTileArray, pIdArray); if (ret) { Lock lk(this->_m); if (this->_isSynchronizedNotSafe()) { this->_setStateNotSafe(STATE::READY); this->_doActionNotSafe(); } } return ret; }); // dispatch command. auto ret = _dispatcher.dispatch(std::move(pTask)); return ret.share(); } } std::promise p; p.set_value(QVariant(false)); return p.get_future(); } std::shared_future NemoInterface::Impl::removeTiles(const IDArray &idArray) { using namespace nemo_interface; // qDebug() << "removeTiles called"; if (idArray.size() > 0) { // copy known ids auto pIdArray = std::make_shared(); Lock lk(this->_m); 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 << "."; } } if (pIdArray->size() > 0) { lk.unlock(); emit this->_parent->tilesChanged(); lk.lock(); } // ready for send? if (pIdArray->size() > 0 && (this->_readyNotSafe(this->_state) || this->_userSyncNotSafe(this->_state))) { this->_setStateNotSafe(STATE::USER_SYNC); this->_doActionNotSafe(); lk.unlock(); // create command. auto cmd = std::make_unique([this, pIdArray] { auto ret = this->_removeTilesImpl(pIdArray); if (ret) { Lock lk(this->_m); if (this->_isSynchronizedNotSafe()) { this->_setStateNotSafe(STATE::READY); this->_doActionNotSafe(); } } return ret; }); // dispatch command and return. auto ret = _dispatcher.dispatch(std::move(cmd)); auto sfut = ret.share(); return sfut; } } std::promise p; p.set_value(QVariant(false)); return p.get_future(); } std::shared_future NemoInterface::Impl::clearTiles() { using namespace nemo_interface; // qDebug() << "clearTiles called"; // clear local tiles (_localTiles) Lock lk(this->_m); if (!_localTiles.empty()) { this->_localTiles.clear(); lk.unlock(); emit this->_parent->tilesChanged(); lk.lock(); } if (this->_readyNotSafe(this->_state) || this->_readyNotSafe(this->_state)) { this->_setStateNotSafe(STATE::USER_SYNC); this->_doActionNotSafe(); lk.unlock(); // create command. auto pTask = std::make_unique([this] { auto ret = this->_clearTilesImpl(); if (ret) { Lock lk(this->_m); if (this->_isSynchronizedNotSafe()) { this->_setStateNotSafe(STATE::READY); this->_doActionNotSafe(); } } return QVariant(ret); }); // dispatch command and return. auto ret = _dispatcher.dispatch(std::move(pTask)); return ret.share(); } else { lk.unlock(); std::promise p; p.set_value(QVariant(false)); return p.get_future(); } } TileArray NemoInterface::Impl::getTiles(const IDArray &idArray) const { TileArray tileArray; Lock lk(this->_m); 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)); } } } return tileArray; } TileArray NemoInterface::Impl::getAllTiles() const { TileArray tileArray; Lock lk(this->_m); 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)); } } return tileArray; } LogicalArray NemoInterface::Impl::containsTiles(const IDArray &idArray) const { LogicalArray logicalArray; Lock lk(this->_m); for (const auto &id : idArray) { const auto &it = _localTiles.find(id); logicalArray.append(it != _localTiles.end()); } return logicalArray; } std::size_t NemoInterface::Impl::size() const { Lock lk(this->_m); return _localTiles.size(); } bool NemoInterface::Impl::empty() const { Lock lk(this->_m); return _localTiles.empty(); } ProgressArray NemoInterface::Impl::getProgress() const { ProgressArray progressArray; Lock lk(this->_m); if (this->_isSynchronizedNotSafe()) { 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()}); } } return progressArray; } ProgressArray NemoInterface::Impl::getProgress(const IDArray &idArray) const { ProgressArray progressArray; Lock lk(this->_m); if (this->_isSynchronizedNotSafe()) { 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()}); } } } return progressArray; } NemoInterface::STATUS NemoInterface::Impl::status() const { Lock lk(this->_m); return _status(this->_state); } bool NemoInterface::Impl::running() const { Lock lk1(this->_m); return _runningNotSafe(this->_state); } bool NemoInterface::Impl::ready() const { Lock lk1(this->_m); return _readyNotSafe(this->_state); } bool NemoInterface::Impl::_sysSync() const { Lock lk1(this->_m); return _sysSyncNotSafe(this->_state); } void NemoInterface::Impl::_onHeartbeatTimeout() { Lock lk1(this->_m); this->_setStateNotSafe(STATE::HEARTBEAT_TIMEOUT); this->_doActionNotSafe(); } void NemoInterface::Impl::_onRosbridgeStateChanged() { auto rbState = this->_pRosbridge->state(); if (rbState == Rosbridge::STATE::CONNECTED) { Lock lk1(this->_m); if (this->_state == STATE::START_BRIDGE || this->_state == STATE::WEBSOCKET_TIMEOUT) { this->_setStateNotSafe(STATE::WEBSOCKET_DETECTED); this->_doActionNotSafe(); } } else if (rbState == Rosbridge::STATE::TIMEOUT) { Lock lk1(this->_m); if (this->_state == STATE::TRY_SETUP || this->_state == STATE::READY || this->_state == STATE::WEBSOCKET_DETECTED || this->_state == STATE::HEARTBEAT_TIMEOUT) { this->_setStateNotSafe(STATE::WEBSOCKET_TIMEOUT); this->_doActionNotSafe(); } } } bool NemoInterface::Impl::_userSync() const { Lock lk1(this->_m); return _userSyncNotSafe(this->_state); } const QString &NemoInterface::Impl::infoString() const { Lock lk1(this->_m); return _infoString; } const QString &NemoInterface::Impl::warningString() const { Lock lk1(this->_m); return _warningString; } bool NemoInterface::Impl::_updateProgress(const ProgressArray &pArray) { ProgressArray copy; bool error = false; Lock lk1(this->_m); for (auto itLP = pArray.begin(); itLP != pArray.end(); ++itLP) { auto it = _remoteTiles.find(itLP->id()); if (Q_LIKELY(it != _remoteTiles.end())) { it->second->setProgress(itLP->progress()); copy.push_back(*itLP); } else { qCDebug(NemoInterfaceLog) << "_updateProgress(): tile with id " << itLP->id() << " not found."; error = true; } } lk1.unlock(); if (copy.size() > 0) { emit _parent->progressChanged(copy); } return !error; } void NemoInterface::Impl::_onHeartbeatReceived(const QNemoHeartbeat &) { INVM(this->_parent, ([this]() mutable { this->_timeoutTimer.start(NO_HEARTBEAT_TIMEOUT); })) Lock lk(this->_m); if (this->_state == STATE::TRY_SETUP || this->_state == STATE::HEARTBEAT_TIMEOUT) { this->_setStateNotSafe(STATE::READY); this->_doActionNotSafe(); } } void NemoInterface::Impl::_setInfoStringNotSafe(const QString &info) { if (_infoString != info) { _infoString = info; // emit signal later, can't emit while mutex locked! QTimer::singleShot(5, this->_parent, [this] { emit this->_parent->infoStringChanged(); }); } } void NemoInterface::Impl::_setWarningStringNotSafe(const QString &warning) { if (_warningString != warning) { _warningString = warning; // emit signal later, can't emit while mutex locked! QTimer::singleShot(5, this->_parent, [this] { emit this->_parent->warningStringChanged(); }); } } void NemoInterface::Impl::_setInfoString(const QString &info) { Lock lk(this->_m); _setInfoStringNotSafe(info); } void NemoInterface::Impl::_setWarningString(const QString &warning) { Lock lk(this->_m); _setWarningStringNotSafe(warning); } void NemoInterface::Impl::_doSetup() { // create task auto pTask = std::make_unique([this] { auto rb = this->_pRosbridge; auto &disp = this->_dispatcher; // check if required services are available auto cond = [&disp] { return !disp.isInterruptionRequested(); }; for (const auto &cc : requiredServices) { QString s(cc); this->_setInfoString("Waiting for required service " + s); auto available = rb->waitForService(cc, cond); if (!available) { return QVariant(false); } } // check if version is compatible{ this->_setInfoString("Checking version."); auto version = _callGetVersion(disp, *rb); if (version != _localVersion) { this->_setWarningString( "Version checking failed. Local protocol version (" + QString(_localVersion) + ") does not match remote version (" + version + ")."); return QVariant(false); } // check if required topics are available for (const auto &cc : requiredTopics) { QString s(cc); this->_setInfoString("Waiting for required topic " + s); auto available = rb->waitForTopic(cc, cond); if (!available) { return QVariant(false); } } // subscribe topics rb->subscribeTopic(progressTopic, [this](const QJsonObject &o) { ros_bridge::messages::nemo_msgs::progress_array::ProgressArray progressArray; if (ros_bridge::messages::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) << progressTopic << " progress out " "of range, value was set to: " << lp.progress(); } } auto ret = this->_updateProgress(progressArray.progress_array()); if (!ret) this->_setWarningString("Progress update failed."); } else { qCWarning(NemoInterfaceLog) << progressTopic << " not able to " "create ProgressArray form json: " << o; } }); using namespace ros_bridge::messages; rb->subscribeTopic(heartbeatTopic, [this](const QJsonObject &o) { nemo_msgs::heartbeat::Heartbeat heartbeat; if (nemo_msgs::heartbeat::fromJson(o, heartbeat)) { this->_onHeartbeatReceived(heartbeat); } else { qCWarning(NemoInterfaceLog) << heartbeatTopic << " not able to " "create Heartbeat form json: " << o; } }); // now ready INVM(this->_parent, ([this]() mutable { this->_timeoutTimer.start(NO_HEARTBEAT_TIMEOUT); })) _setInfoString(""); _setWarningString(""); return QVariant(true); }); auto f = _dispatcher.dispatch(std::move(pTask)); Q_UNUSED(f); } void NemoInterface::Impl::_synchronize() { Lock lk(this->_m); if (this->_state == STATE::READY || this->_state == STATE::SYNC_ERROR) { _setStateNotSafe(STATE::SYS_SYNC); _doActionNotSafe(); // copy tiles. auto pTileArray = std::make_shared>>(); for (auto it = _localTiles.begin(); it != _localTiles.end(); ++it) { pTileArray->push_back(it->second); } lk.unlock(); // create cmd. auto pTask = std::make_unique([this, pTileArray] { auto rb = this->_pRosbridge; auto &disp = this->_dispatcher; // are _localTiles and _remoteTiles synced? auto remoteTiles = this->_callGetAllTiles(disp, *rb); // create tile map; std::map> tempMap; for (auto &&pTile : remoteTiles) { auto it = tempMap.find(pTile->id()); if (Q_LIKELY(it == tempMap.end())) { tempMap.insert(std::make_pair(pTile->id(), pTile)); } else { qCDebug(NemoInterfaceLog) << "_synchronizeIfNeccessary(): remoteTiles contains " "duplicate id"; } } // compare with pTileArray bool synced = true; for (auto &&pTile : *pTileArray) { auto it = tempMap.find(pTile->id()); if (it == tempMap.end() || it->second->tile() != pTile->tile()) { synced = false; break; } } if (!synced) { auto success = this->_clearTilesImpl(); if (!success) { Lock lk1(this->_m); if (this->_state == STATE::SYS_SYNC) { _setStateNotSafe(STATE::SYNC_ERROR); _doActionNotSafe(); } return QVariant(false); } auto pIdArray = std::make_shared(); for (auto &&pTile : *pTileArray) { pIdArray->push_back(pTile->id()); } success = this->_addTilesImpl(pTileArray, pIdArray); if (!success) { Lock lk2(this->_m); if (this->_state == STATE::SYS_SYNC) { _setStateNotSafe(STATE::SYNC_ERROR); _doActionNotSafe(); } return QVariant(false); } } else { // update progress this->_clearTilesRemote(); auto ret = this->_addTilesRemote(*pTileArray); if (ret) { ProgressArray progress; for (auto &&pTile : remoteTiles) { progress.push_back(LabeledProgress(pTile->progress(), pTile->id())); } ret = _updateProgress(progress); } if (!ret) { Lock lk(this->_m); if (this->_state == STATE::SYS_SYNC) { this->_setStateNotSafe(STATE::SYNC_ERROR); this->_doActionNotSafe(); lk.unlock(); this->_setWarningString("Getting progress failed."); qCDebug(NemoInterfaceLog) << "_addTilesImpl(): _updateProgress failed: unknown id."; } return QVariant(false); } } // check if local versions are still synced (user could habe modified // _localTiles). Lock lk(this->_m); if (_isSynchronizedNotSafe()) { _setStateNotSafe(STATE::READY); _doActionNotSafe(); } else { INVM(this->_parent, [this] { this->_synchronize(); }) } lk.unlock(); return QVariant(true); }); // dispatch command. auto ret = _dispatcher.dispatch(std::move(pTask)); Q_UNUSED(ret); INVM(this->_parent, [this] { this->_syncTimer.start(SYNC_INTERVAL); }) } else { INVM(this->_parent, [this] { this->_syncTimer.start(SYNC_RETRY_INTERVAL); }) } } void NemoInterface::Impl::_tryRestart() { if (this->running()) { if (_dispatcher.idle()) { qDebug() << "_tryRestart: restarting"; this->stop(); this->start(); _restartTimer.start(RESTART_INTERVAl); } else { _restartTimer.start(RESTART_RETRY_INTERVAl); } } } bool NemoInterface::Impl::_isSynchronizedNotSafe() const { return _localTiles.size() == _remoteTiles.size() && std::equal( _localTiles.begin(), _localTiles.end(), _remoteTiles.begin(), [](const auto &a, const auto &b) { return a.first == b.first; }); } bool NemoInterface::Impl::_isSynchronized() const { Lock lk(this->_m); return _isSynchronizedNotSafe(); } void NemoInterface::Impl::_doActionNotSafe() { static bool resetDone = false; switch (this->_state) { case STATE::STOPPED: { INVM(this->_parent, ([this]() mutable { this->_timeoutTimer.stop(); this->_restartTimer.stop(); this->_syncTimer.stop(); })) if (this->_pRosbridge->state() != Rosbridge::STATE::STOPPED) { this->_pRosbridge->stop(); } _dispatcher.stop(); _dispatcher.clear(); _setInfoStringNotSafe(""); _setWarningStringNotSafe(""); } break; case STATE::START_BRIDGE: { INVM(this->_parent, ([this]() mutable { this->_restartTimer.start(RESTART_INTERVAl); })) this->_pRosbridge->start(); _setInfoStringNotSafe(""); _setWarningStringNotSafe(""); } break; case STATE::WEBSOCKET_DETECTED: resetDone = false; this->_setStateNotSafe(STATE::TRY_SETUP); this->_doActionNotSafe(); _setInfoStringNotSafe(""); _setWarningStringNotSafe(""); break; case STATE::TRY_SETUP: this->_doSetup(); _setInfoStringNotSafe(""); _setWarningStringNotSafe(""); break; case STATE::READY: { INVM(this->_parent, ([this]() mutable { this->_syncTimer.start(SYNC_INTERVAL); })) if (!_isSynchronizedNotSafe()) { // can't call this here directly. QTimer::singleShot(100, this->_parent, [this] { this->_synchronize(); }); } _setInfoStringNotSafe(""); _setWarningStringNotSafe(""); } break; case STATE::USER_SYNC: case STATE::SYS_SYNC: case STATE::SYNC_ERROR: _setInfoStringNotSafe(""); _setWarningStringNotSafe(""); break; case STATE::HEARTBEAT_TIMEOUT: { INVM(this->_parent, ([this]() mutable { this->_syncTimer.stop(); })) _setInfoStringNotSafe(""); _setWarningStringNotSafe(""); } break; case STATE::WEBSOCKET_TIMEOUT: { INVM(this->_parent, ([this]() mutable { this->_timeoutTimer.stop(); this->_syncTimer.stop(); })) if (!resetDone) { resetDone = true; this->_pRosbridge->stop(); this->_pRosbridge->start(); } _dispatcher.stop(); _dispatcher.clear(); _setInfoStringNotSafe(""); _setWarningStringNotSafe(""); } break; }; } bool NemoInterface::Impl::_callAddTiles( const QVector> &tileArray, Dispatcher &disp, Rosbridge &rb) { // qDebug() << "_callAddTiles called"; // create json object QJsonArray jsonTileArray; for (auto &&tile : tileArray) { using namespace ros_bridge::messages; QJsonObject jsonTile; if (!nemo_msgs::tile::toJson(*tile, jsonTile)) { qCDebug(NemoInterfaceLog) << "addTiles(): not able to create json object: tile id: " << tile->id() << " progress: " << tile->progress() << " points: " << tile->tile(); } jsonTileArray.append(std::move(jsonTile)); } // for QJsonObject req; req["in_tile_array"] = std::move(jsonTileArray); // create response handler. auto promise_response = std::make_shared>(); 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()); } else { qCWarning(NemoInterfaceLog) << addTilesService << " no \"success\" key or wrong type: " << o; promise_response->set_value(false); } }; // call service. rb.callService(addTilesService, responseHandler, req); // wait for response. auto tStart = hrc::now(); bool abort = true; while (hrc::now() - tStart < maxResponseTime && !disp.isInterruptionRequested()) { auto status = future_response.wait_for(std::chrono::milliseconds(10)); if (status == std::future_status::ready) { abort = false; break; } } if (abort) { qCWarning(NemoInterfaceLog) << "addTiles(): Websocket not responding to request."; return false; } // transaction error? if (!future_response.get()) { return false; } // return success return true; } bool NemoInterface::Impl::_callRemoveTiles(const IDArray &pIdArray, Dispatcher &disp, Rosbridge &rb) { // qDebug() << "_callRemoveTiles called"; // create json object QJsonArray jsonIdArray; for (auto &&id : pIdArray) { using namespace ros_bridge::messages; jsonIdArray.append(id); } // for QJsonObject req; req["ids"] = std::move(jsonIdArray); // create response handler. auto promise_response = std::make_shared>(); 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()); } else { qCWarning(NemoInterfaceLog) << removeTilesService << " no \"success\" key or wrong type: " << msg; promise_response->set_value(false); } }; // call service. rb.callService(removeTilesService, responseHandler, req); // wait for response. auto tStart = hrc::now(); bool abort = true; while (hrc::now() - tStart < maxResponseTime && !disp.isInterruptionRequested()) { auto status = future_response.wait_for(std::chrono::milliseconds(10)); if (status == std::future_status::ready) { abort = false; break; } } if (abort) { qCWarning(NemoInterfaceLog) << "remove_tiles(): Websocket not responding to request."; return false; } // transaction error? if (!future_response.get()) { return false; } return true; } bool NemoInterface::Impl::_callClearTiles(Dispatcher &disp, Rosbridge &rb) { // create response handler. auto promise_response = std::make_shared>(); auto future_response = promise_response->get_future(); auto responseHandler = [promise_response](const QJsonObject &) mutable { // check if transaction was successfull promise_response->set_value(true); }; // call service. rb.callService(clearTilesService, responseHandler, QJsonObject()); // wait for response. auto tStart = hrc::now(); bool abort = true; while (hrc::now() - tStart < maxResponseTime && !disp.isInterruptionRequested()) { auto status = future_response.wait_for(std::chrono::milliseconds(10)); if (status == std::future_status::ready) { abort = false; break; } } if (abort) { qCWarning(NemoInterfaceLog) << "Websocket not responding to request."; return false; } // transaction failed? if (!future_response.get()) { return false; } return true; } ProgressArray NemoInterface::Impl::_callGetProgress(const IDArray &pIdArray, Dispatcher &disp, Rosbridge &rb) { // qDebug() << "_callGetProgress called"; // create json object QJsonArray jsonIdArray; for (auto &&id : pIdArray) { using namespace ros_bridge::messages; jsonIdArray.append(id); } // for QJsonObject req; req["ids"] = std::move(jsonIdArray); // create response handler. typedef std::shared_ptr ResponseType; auto promise_response = std::make_shared>(); 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)) { auto pArray = std::make_shared(); *pArray = std::move(progressArrayMsg.progress_array()); promise_response->set_value(pArray); } else { qCWarning(NemoInterfaceLog) << getProgressService << " error while creating ProgressArray " "from json."; promise_response->set_value(nullptr); } }; // call service. rb.callService(getProgressService, responseHandler, req); // wait for response. auto tStart = hrc::now(); bool abort = true; while (hrc::now() - tStart < maxResponseTime && !disp.isInterruptionRequested()) { auto status = future_response.wait_for(std::chrono::milliseconds(10)); if (status == std::future_status::ready) { abort = false; break; } } if (abort) { qCWarning(NemoInterfaceLog) << "all_remove_tiles(): Websocket not responding to request."; return ProgressArray(); } // return success return *future_response.get(); } ProgressArray NemoInterface::Impl::_callGetAllProgress(Dispatcher &disp, Rosbridge &rb) { // qDebug() << "_callGetAllProgress called"; // create response handler. typedef std::shared_ptr ResponseType; auto promise_response = std::make_shared>(); 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)) { auto pArray = std::make_shared(); *pArray = std::move(progressArrayMsg.progress_array()); promise_response->set_value(pArray); } else { qCWarning(NemoInterfaceLog) << getAllProgressService << " error while creating ProgressArray " "from json. msg: " << o; promise_response->set_value(nullptr); } }; // call service. rb.callService(getAllProgressService, responseHandler, QJsonObject()); // wait for response. auto tStart = hrc::now(); bool abort = true; while (hrc::now() - tStart < maxResponseTime && !disp.isInterruptionRequested()) { auto status = future_response.wait_for(std::chrono::milliseconds(10)); if (status == std::future_status::ready) { abort = false; break; } } if (abort) { qCWarning(NemoInterfaceLog) << "_callGetAllProgress(): Websocket not responding to request."; return ProgressArray(); } // transaction error? return *future_response.get(); } QVector> NemoInterface::Impl::_callGetAllTiles(Dispatcher &disp, Rosbridge &rb) { // qDebug() << "_callGetAllProgress called"; // create response handler. typedef std::shared_ptr>> ResponseType; auto promise_response = std::make_shared>(); auto future_response = promise_response->get_future(); auto responseHandler = [promise_response](const QJsonObject &o) mutable { const char *const tileArrayKey = "tile_array"; // check if transaction was successfull if (o.contains(tileArrayKey) && o[tileArrayKey].isArray()) { auto pArray = std::make_shared>>(); const auto jsonArray = o[tileArrayKey].toArray(); for (int i = 0; i < jsonArray.size(); ++i) { if (jsonArray[i].isObject()) { QJsonObject o = jsonArray[i].toObject(); auto tile = std::make_shared(); if (ros_bridge::messages::nemo_msgs::tile::fromJson(o, *tile)) { pArray->push_back(tile); } else { qCWarning(NemoInterfaceLog) << getAllTilesService << " error while creating tile."; promise_response->set_value(nullptr); } } else { qCWarning(NemoInterfaceLog) << getAllTilesService << " json array does not contain objects."; promise_response->set_value(nullptr); } } // success! promise_response->set_value(pArray); } else { qCWarning(NemoInterfaceLog) << getAllTilesService << " no tile_array key or wrong type."; promise_response->set_value(nullptr); } }; // call service. rb.callService(getAllTilesService, responseHandler, QJsonObject()); // wait for response. auto tStart = hrc::now(); bool abort = true; while (hrc::now() - tStart < maxResponseTime && !disp.isInterruptionRequested()) { auto status = future_response.wait_for(std::chrono::milliseconds(10)); if (status == std::future_status::ready) { abort = false; break; } } if (abort) { qCWarning(NemoInterfaceLog) << "all_remove_tiles(): Websocket not responding to request."; return QVector>(); } return *future_response.get(); } QString NemoInterface::Impl::_callGetVersion(Dispatcher &disp, Rosbridge &rb) { // create response handler. typedef QString ResponseType; auto promise_response = std::make_shared>(); auto future_response = promise_response->get_future(); auto responseHandler = [promise_response](const QJsonObject &o) mutable { const char *const versionKey = "version"; // check if transaction was successfull if (o.contains(versionKey) && o[versionKey].isString()) { const auto version = o[versionKey].toString(); promise_response->set_value(version); } else { qCWarning(NemoInterfaceLog) << getVersionService << " no version key or wrong type."; promise_response->set_value("error!"); } }; // call service. rb.callService(getVersionService, responseHandler, QJsonObject()); // wait for response. auto tStart = hrc::now(); bool abort = true; while (hrc::now() - tStart < maxResponseTime && !disp.isInterruptionRequested()) { auto status = future_response.wait_for(std::chrono::milliseconds(10)); if (status == std::future_status::ready) { abort = false; break; } } if (abort) { qCWarning(NemoInterfaceLog) << "all_remove_tiles(): Websocket not responding to request."; return "unknown_version"; } // transaction error? auto version = future_response.get(); if (version == "error!") { return "unknown_version"; } // return success return version; } bool NemoInterface::Impl::_addTilesRemote( const QVector> tileArray) { // qDebug() << "_addTilesRemote called"; QVector> copy; for (auto &&pTile : tileArray) { copy.push_back(std::make_shared(*pTile)); } return _addTilesRemote(copy); } bool NemoInterface::Impl::_addTilesRemote( const QVector> &tileArray) { bool error = false; bool anyChange = false; Lock lk(this->_m); for (auto &&pTile : tileArray) { auto id = pTile->id(); auto it = _remoteTiles.find(id); if (Q_LIKELY(it == _remoteTiles.end())) { auto ret = _remoteTiles.insert(std::make_pair(id, pTile)); anyChange = true; Q_ASSERT(ret.second == true); Q_UNUSED(ret); } else { if (pTile->tile() != it->second->tile()) { qCWarning(NemoInterfaceLog) << "_addTilesRemote: tiles differ but have the same id."; error = true; } } } lk.unlock(); if (anyChange > 0) { emit this->_parent->tilesChanged(); } return !error; } void NemoInterface::Impl::_removeTilesRemote(const IDArray &idArray) { // qDebug() << "_removeTilesRemote called"; bool anyChange = false; Lock lk(this->_m); for (auto &&id : idArray) { auto it = _remoteTiles.find(id); if (Q_LIKELY(it != _remoteTiles.end())) { _remoteTiles.erase(it); anyChange = true; } else { qCWarning(NemoInterfaceLog) << "_removeTilesRemote: tile with unknown id " << id << "."; } } if (anyChange) { emit this->_parent->tilesChanged(); } } void NemoInterface::Impl::_clearTilesRemote() { Lock lk(this->_m); _clearTilesRemoteNotSafe(); } void NemoInterface::Impl::_clearTilesRemoteNotSafe() { // qDebug() << "_clearTilesRemote called"; if (_remoteTiles.size() > 0) { _remoteTiles.clear(); } } bool NemoInterface::Impl::_setStateNotSafe(STATE newState) { if (newState != this->_state) { auto oldState = this->_state; this->_state = newState; qCDebug(NemoInterfaceLog) << "state: " << _toString(oldState) << " -> " << _toString(newState); auto oldStatus = _status(oldState); auto newStatus = _status(newState); if (oldStatus != newStatus) { // emit signal later QTimer::singleShot(5, this->_parent, [this] { emit this->_parent->statusChanged(); }); } if (_runningNotSafe(oldState) != _runningNotSafe(newState)) { // emit signal later QTimer::singleShot(5, this->_parent, [this] { emit this->_parent->runningChanged(); }); } return true; } else { return false; } } bool NemoInterface::Impl::_readyNotSafe(NemoInterface::Impl::STATE s) { return s == STATE::READY; } bool NemoInterface::Impl::_userSyncNotSafe(NemoInterface::Impl::STATE s) { return s == STATE::USER_SYNC; } bool NemoInterface::Impl::_sysSyncNotSafe(NemoInterface::Impl::STATE s) { return s == STATE::SYS_SYNC; } bool NemoInterface::Impl::_runningNotSafe(NemoInterface::Impl::STATE s) { return s != STATE::STOPPED; } NemoInterface::STATUS NemoInterface::Impl::_status(NemoInterface::Impl::STATE state) { NemoInterface::STATUS status; switch (state) { case STATE::STOPPED: status = NemoInterface::STATUS::NOT_CONNECTED; break; case STATE::START_BRIDGE: status = NemoInterface::STATUS::NOT_CONNECTED; break; case STATE::WEBSOCKET_DETECTED: status = NemoInterface::STATUS::WEBSOCKET_DETECTED; break; case STATE::TRY_SETUP: status = NemoInterface::STATUS::WEBSOCKET_DETECTED; break; case STATE::READY: status = NemoInterface::STATUS::READY; break; case STATE::USER_SYNC: case STATE::SYS_SYNC: status = NemoInterface::STATUS::SYNC; break; case STATE::SYNC_ERROR: status = NemoInterface::STATUS::ERROR; break; case STATE::WEBSOCKET_TIMEOUT: case STATE::HEARTBEAT_TIMEOUT: status = NemoInterface::STATUS::TIMEOUT; break; } return status; } QString NemoInterface::Impl::_toString(NemoInterface::Impl::STATE s) { switch (s) { case STATE::STOPPED: return QString("STOPPED"); case STATE::START_BRIDGE: return QString("START_BRIDGE"); case STATE::WEBSOCKET_DETECTED: return QString("WEBSOCKET_DETECTED"); case STATE::TRY_SETUP: return QString("TRY_SETUP"); case STATE::READY: return QString("READY"); case STATE::USER_SYNC: return QString("SYNC_USER"); case STATE::SYS_SYNC: return QString("SYNC_SYS"); case STATE::SYNC_ERROR: return QString("SYNC_ERROR"); case STATE::WEBSOCKET_TIMEOUT: return QString("WEBSOCKET_TIMEOUT"); case STATE::HEARTBEAT_TIMEOUT: return QString("HEARTBEAT_TIMEOUT"); } return "unknown state!"; } QString NemoInterface::Impl::_toString(NemoInterface::STATUS s) { switch (s) { case NemoInterface::STATUS::NOT_CONNECTED: return QString("NOT_CONNECTED"); case NemoInterface::STATUS::ERROR: return QString("ERROR"); case NemoInterface::STATUS::READY: return QString("READY"); case NemoInterface::STATUS::TIMEOUT: return QString("TIMEOUT"); case NemoInterface::STATUS::WEBSOCKET_DETECTED: return QString("WEBSOCKET_DETECTED"); case NemoInterface::STATUS::SYNC: return QString("SYNC"); } return "unknown state!"; } bool NemoInterface::Impl::_addTilesImpl( std::shared_ptr>> pTileArray, std::shared_ptr pIdArray) { auto rb = this->_pRosbridge; auto &disp = this->_dispatcher; // add tiles bool success = this->_callAddTiles(*pTileArray, disp, *rb); if (!success) { this->_setWarningString( "Adding tiles failed. This might indicate a poor connection."); return false; } auto ret = this->_addTilesRemote(*pTileArray); if (!ret) { Lock lk(this->_m); if (this->_state == STATE::SYS_SYNC || this->_state == STATE::USER_SYNC) { this->_setStateNotSafe(STATE::SYNC_ERROR); this->_doActionNotSafe(); lk.unlock(); this->_setWarningString("Adding tiles failed."); qCDebug(NemoInterfaceLog) << "_addTilesImpl(): _addTilesRemote return " "false: different tiles with same id."; } return false; } // fetch progress auto array = this->_callGetProgress(*pIdArray, disp, *rb); if (array.size() != pIdArray->size()) { Lock lk1(this->_m); if (this->_state == STATE::SYS_SYNC || this->_state == STATE::USER_SYNC) { this->_setStateNotSafe(STATE::SYNC_ERROR); this->_doActionNotSafe(); lk1.unlock(); this->_setWarningString("Getting progress failed. This might " "indicate a poor connection."); } return false; } ret = this->_updateProgress(array); if (!ret) { Lock lk2(this->_m); if (this->_state == STATE::SYS_SYNC || this->_state == STATE::USER_SYNC) { this->_setStateNotSafe(STATE::SYNC_ERROR); this->_doActionNotSafe(); lk2.unlock(); this->_setWarningString("Getting progress failed."); qCDebug(NemoInterfaceLog) << "_addTilesImpl(): _updateProgress failed: unknown id."; } return false; } return true; } bool NemoInterface::Impl::_removeTilesImpl( std::shared_ptr pIdArray) { auto rb = this->_pRosbridge; auto &disp = this->_dispatcher; auto success = this->_callRemoveTiles(*pIdArray, disp, *rb); if (!success) { Lock lk(this->_m); if (this->_state == STATE::SYS_SYNC || this->_state == STATE::USER_SYNC) { this->_setStateNotSafe(STATE::SYNC_ERROR); this->_doActionNotSafe(); lk.unlock(); this->_setWarningString("Removing tiles failed. This might " "indicate a poor connection."); } return false; } this->_removeTilesRemote(*pIdArray); return true; } bool NemoInterface::Impl::_clearTilesImpl() { auto rb = this->_pRosbridge; auto &disp = this->_dispatcher; auto success = this->_callClearTiles(disp, *rb); if (!success) { Lock lk(this->_m); if (this->_state == STATE::SYS_SYNC || this->_state == STATE::USER_SYNC) { this->_setStateNotSafe(STATE::SYNC_ERROR); this->_doActionNotSafe(); lk.unlock(); this->_setWarningString("Clear tiles failed. This might " "indicate a poor connection."); } return false; } this->_clearTilesRemote(); return true; } // =============================================================== // NemoInterface NemoInterface::NemoInterface() : QObject(), pImpl(std::make_unique(this)) {} NemoInterface *NemoInterface::createInstance() { return new NemoInterface(); } NemoInterface *NemoInterface::instance() { return GenericSingelton::instance( NemoInterface::createInstance); } NemoInterface::~NemoInterface() {} void NemoInterface::start() { this->pImpl->start(); } void NemoInterface::stop() { this->pImpl->stop(); } std::shared_future NemoInterface::addTiles(const TileArray &tileArray) { TilePtrArray ptrArray; for (const auto &tile : tileArray) { ptrArray.push_back(const_cast(&tile)); } return this->pImpl->addTiles(ptrArray); } std::shared_future NemoInterface::addTiles(const TilePtrArray &tileArray) { return this->pImpl->addTiles(tileArray); } std::shared_future NemoInterface::removeTiles(const IDArray &idArray) { return this->pImpl->removeTiles(idArray); } std::shared_future NemoInterface::clearTiles() { return this->pImpl->clearTiles(); } TileArray NemoInterface::getTiles(const IDArray &idArray) const { return this->pImpl->getTiles(idArray); } TileArray NemoInterface::getAllTiles() const { return this->pImpl->getAllTiles(); } LogicalArray NemoInterface::containsTiles(const IDArray &idArray) const { return this->pImpl->containsTiles(idArray); } std::size_t NemoInterface::size() const { return this->pImpl->size(); } bool NemoInterface::empty() const { return this->pImpl->empty(); } ProgressArray NemoInterface::getProgress() const { return this->pImpl->getProgress(); } ProgressArray NemoInterface::getProgress(const IDArray &idArray) const { return this->pImpl->getProgress(idArray); } NemoInterface::STATUS NemoInterface::status() const { return this->pImpl->status(); } QString NemoInterface::statusString() const { return statusMap.at(this->pImpl->status()); } QString NemoInterface::infoString() const { return this->pImpl->infoString(); } QString NemoInterface::warningString() const { return this->pImpl->warningString(); } QString NemoInterface::editorQml() { return QStringLiteral("NemoInterface.qml"); } bool NemoInterface::running() const { return this->pImpl->running(); }