/** * @RDE_HEADER@ * Data Plugin for The Timeline - DDS => GUI * $Id$ * $HeadURL$ * $Author$ * * Created by Mike Wille, SLSE, 2017-03-27 */ #include "tln_data_plugin.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace TimeLineManagement { std::shared_ptr DataPlugin::createDataInterface(LtsSession::LtsSessionDataDds *session) { return std::make_shared(session); } DataInterfaceImpl::DataInterfaceImpl(LtsSession::LtsSessionDataDds *session) : DataInterface() , m_foreign(std::make_shared(session)) , m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("Plugins.Timeline.Data")) {} ForeignInterfaceImpl::ForeignInterfaceImpl(LtsSession::LtsSessionDataDds *session) : m_session(session) , m_webSocketScm(nullptr) , m_webSocketEem(nullptr) , m_liveHistoryBufferTimer(new QTimer(this)) , m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("Plugins.Timeline.Data")) { connect(m_liveHistoryBufferTimer, &QTimer::timeout, this, &ForeignInterfaceImpl::onClearHistoryBuffer); m_liveHistoryBufferTimer->setInterval(5000); m_liveHistoryBufferTimer->start(); // open ws socket to exercise event management QTimer::singleShot(0, this, &ForeignInterfaceImpl::connectToScm); } ForeignInterfaceImpl::~ForeignInterfaceImpl() { m_pendingRequests.clear(); m_liveHistoryBuffer.clear(); cleanupSocket(m_webSocketScm); cleanupSocket(m_webSocketEem); } static ExerciseEventManagement::Event db2dds(const ExerciseEventManagement::Database::EemEvent &evDb) { ExerciseEventManagement::Event ev; ev.id(evDb.id & 0xffffffff); ev.sampleTime(evDb.sampleTime); ev.eventTypeId(evDb.eventTypeId); ev.participantId(evDb.participantId); ev.partyId(evDb.partyId); ev.equipmentNumber(evDb.equipmentNumber); ev.properties(evDb.properties.toUtf8().data()); // Add Originator to Hit Building if ( ev.eventTypeId() == ProjectDataManagement::EventType_def::HitBuilding) { QJsonObject properties = QJsonDocument::fromJson(QString::fromStdString(ev.properties()).toUtf8()).object(); if ( properties.contains(QStringLiteral("AesName"))) { properties.insert(QStringLiteral("Originator"), properties.value(QStringLiteral("AesName")).toString()); } else if ( properties.contains(QStringLiteral("OriginatorEquipmentNumber"))) { QJsonParseError parseError; QJsonDocument doc = QJsonDocument::fromJson(QByteArray::fromStdString(ev.properties()), &parseError); long originatorEquipmentNumber = properties.value(QStringLiteral("OriginatorEquipmentNumber")).toInt(); QString originatorEquipmentNumberStr = QString::number(originatorEquipmentNumber); properties.insert(QStringLiteral("Originator"),originatorEquipmentNumberStr); } QJsonDocument doc(properties); //QString strJson(QStringLiteral( doc.toJson(QJsonDocument::Compact))); std::string propertiesStd = QJsonDocument(properties).toJson(QJsonDocument::Compact).toStdString(); ev.properties(propertiesStd); } ev.position(Core::GeoPosition(evDb.position.x(), evDb.position.y(), evDb.position.z())); return ev; } TimelineDataProvider::TimelineDataProvider(int requestId, const QUrl &url, qint64 from, qint64 to, const QVector &eventTypeIds, const QJsonObject &filter) : m_webSocketEem(new QWebSocket) , m_requestId(requestId) , m_bFinshed(false) , m_requestMin(from) , m_requestMax(to) , m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("Plugins.Timeline.Data")) { connect(m_webSocketEem, &QWebSocket::disconnected, this, &TimelineDataProvider::disconnectedFromEem); connect(m_webSocketEem, &QWebSocket::textMessageReceived, this, &TimelineDataProvider::textMessageFromEem); if (from > to) std::swap(from, to); QUrl realUrl(url); realUrl.setPath(QStringLiteral("/request")); QUrlQuery query; query.addQueryItem(QStringLiteral("from"), QString::number(from)); query.addQueryItem(QStringLiteral("to"), QString::number(to)); query.addQueryItem(QStringLiteral("requestId"), QString::number(requestId)); if (!eventTypeIds.isEmpty()) { QStringList sl; sl.reserve(eventTypeIds.size()); for (int id : eventTypeIds) sl += QString::number(id); query.addQueryItem(QStringLiteral("eventTypeId"), sl.join(QLatin1Char(','))); } if (!filter.isEmpty()) { const QJsonDocument doc(filter); query.addQueryItem(QStringLiteral("filter"), QString::fromUtf8(doc.toJson(QJsonDocument::Compact))); } realUrl.setQuery(query); m_webSocketEem->open(realUrl); DEBUG_LOG(m_logger) << "requesting data from EEM: " << realUrl.toString(); } TimelineDataProvider::~TimelineDataProvider() { m_webSocketEem->disconnect(this); m_webSocketEem->close(); m_webSocketEem->deleteLater(); } void TimelineDataProvider::disconnectedFromEem() { m_webSocketEem->close(); if (m_bFinshed) return; m_bFinshed = true; emit finished(); } void TimelineDataProvider::textMessageFromEem(const QString &message) { if (message.isEmpty() || message == QStringLiteral("{}")) { DEBUG_LOG(m_logger) << "textMessageFromEem - finished"; disconnectedFromEem(); return; } const QJsonDocument doc(QJsonDocument::fromJson(message.toUtf8())); const QJsonArray arr = doc.array(); DEBUG_LOG(m_logger) << "textMessageFromEem - #events: " << arr.size(); m_eventData.reserve(m_eventData.size() + arr.size()); using namespace ExerciseEventManagement::Database; std::transform(arr.begin(), arr.end(), std::back_inserter(m_eventData), [](const QJsonValue &val) { return db2dds(fromJson(val.toObject())); }); } TimelineDataProviderInterface *ForeignInterfaceImpl::requestData(qint64 from, qint64 to, const QVector &eventTypeIds, const QJsonObject &filter) { if (!m_webSocketEem) { WARNING_LOG(m_logger) << "No connection to EEM - can not request data!"; return nullptr; } static int s_requestId = 0; ++s_requestId; int curRequestId = s_requestId; TimelineDataProvider *tldp = new TimelineDataProvider(curRequestId, eemUrl(), from, to, eventTypeIds, filter); connect(tldp, &TimelineDataProvider::finished, this, &ForeignInterfaceImpl::requestFinished); connect(tldp, &TimelineDataProvider::destroyed, this, [this, curRequestId]() { m_pendingRequests.remove(curRequestId);}); m_pendingRequests[curRequestId] = tldp; DEBUG_LOG(m_logger) << "add to m_pendingRequests: " << curRequestId << ", " << tldp; return tldp; } QVector ForeignInterfaceImpl::liveHistory() const { QVector ret; for (const QVector &vec : m_liveHistoryBuffer) ret += vec; return ret; } void ForeignInterfaceImpl::connectToScm() { cleanupSocket(m_webSocketScm); m_webSocketScm = new QWebSocket; connect(m_webSocketScm, &QWebSocket::connected, this, &ForeignInterfaceImpl::connectedToScm); connect(m_webSocketScm, &QWebSocket::textMessageReceived, this, &ForeignInterfaceImpl::textMessageFromScm); const QUrl scmUrl(QStringLiteral("ws://%1:%2") .arg(NGL::Connections::hostName(NGL::Connections::eConnection::SCM_WEBSOCKET)) .arg(NGL::Connections::port(NGL::Connections::eConnection::SCM_WEBSOCKET))); DEBUG_LOG(m_logger) << "Trying to connect to SCM at " << scmUrl.toString(); m_webSocketScm->open(scmUrl); } void ForeignInterfaceImpl::connectedToScm() { DEBUG_LOG(m_logger) << "Connected to SCM..."; m_webSocketScm->sendTextMessage(QStringLiteral("{\"type\": \"servicePortsInformationRequest\",\"uuid\": \"7777\"}")); } void ForeignInterfaceImpl::textMessageFromScm(const QString &message) { DEBUG_LOG(m_logger) << "textMessageFromScm from scm: " << message; const QJsonDocument doc = QJsonDocument::fromJson(message.toUtf8()); const QJsonArray exercises = doc.object().value(QStringLiteral("data")).toArray(); for (const QJsonValue &valEx : exercises) { const QJsonObject &exercise = valEx.toObject(); const int exerciseId = exercise.value(QStringLiteral("exerciseId")).toInt(); DEBUG_LOG(m_logger) << "textMessageFromScm from scm - exerciseId " << exerciseId; if (exerciseId != m_session->exerciseId()) continue; const QJsonArray services = exercise.value(QStringLiteral("services")).toArray(); for (const QJsonValue &valSvc : services) { const QJsonObject &service = valSvc.toObject(); const QString svcName = service.value(QStringLiteral("nameAbbrevation")).toString().toUpper(); DEBUG_LOG(m_logger) << "textMessageFromScm from scm - svcName " << svcName; if (svcName != QStringLiteral("EEM")) continue; const QJsonArray ports = service.value(QStringLiteral("ports")).toArray(); for (const QJsonValue &valPort : ports) { const QJsonObject &port = valPort.toObject(); int typeId = port.value(QStringLiteral("typeId")).toInt(); if (typeId == Core::PortType::WebSocketServer) { m_eemPort = port.value(QStringLiteral("number")).toInt(); connectToEem(); cleanupSocket(m_webSocketScm); m_webSocketScm = nullptr; return; } } } } } void ForeignInterfaceImpl::connectToEem() { cleanupSocket(m_webSocketEem); m_webSocketEem = new QWebSocket; connect(m_webSocketEem, &QWebSocket::connected, this, &ForeignInterfaceImpl::connectedToEem); connect(m_webSocketEem, &QWebSocket::disconnected, this, &ForeignInterfaceImpl::disconnectedFromEem); connect(m_webSocketEem, &QWebSocket::textMessageReceived, this, &ForeignInterfaceImpl::textMessageFromEem); QUrl url(eemUrl()); url.setPath(QStringLiteral("/live")); DEBUG_LOG(m_logger) << "Trying to connect to EEM at " << url.toString(); m_webSocketEem->open(url); } void ForeignInterfaceImpl::connectedToEem() { DEBUG_LOG(m_logger) << "Connected to EEM..."; } void ForeignInterfaceImpl::disconnectedFromEem() { WARNING_LOG(m_logger) << "EEM connection closed - try to reconnect"; QTimer::singleShot(500, this, &ForeignInterfaceImpl::connectToScm); } void ForeignInterfaceImpl::textMessageFromEem(const QString &message) { TRACE_LOG(m_logger) << "textMessageFromEem: " << message; const QJsonObject obj(QJsonDocument::fromJson(message.toUtf8()).object()); using namespace ExerciseEventManagement::Database; ExerciseEventManagement::Event ev(db2dds(fromJson(obj))); emit liveEvent(ev); m_liveHistoryBuffer[ev.sampleTime() / 1000].push_back(std::move(ev)); } void ForeignInterfaceImpl::requestFinished() { TimelineDataProvider *tldp = qobject_cast(sender()); if (!tldp) { WARNING_LOG(m_logger) << "requestFinished without valid sender"; return; } auto it = m_pendingRequests.find(tldp->requestId()); if (it == m_pendingRequests.end()) { WARNING_LOG(m_logger) << "requestFinished but request not found in pending requests: " << tldp->requestId() << ", " << tldp; return; } DEBUG_LOG(m_logger) << "emit requestedDataAvailable for " << tldp->requestId() << ", #events: " << tldp->eventData().size() << ", " << tldp; emit requestedDataAvailable(it.value()); it.value()->deleteLater(); m_pendingRequests.erase(it); } void ForeignInterfaceImpl::cleanupSocket(QWebSocket *s) { if (!s) return; s->disconnect(this); s->close(); s->deleteLater(); } const QUrl ForeignInterfaceImpl::eemUrl() const { return QUrl(QStringLiteral("ws://%1:%2") .arg(NGL::Connections::hostName(NGL::Connections::eConnection::SCM_WEBSOCKET)) .arg(m_eemPort)); } void ForeignInterfaceImpl::onClearHistoryBuffer() { qint64 toRemove = time(nullptr) - 10; auto it = m_liveHistoryBuffer.begin(); while (it != m_liveHistoryBuffer.end() && it.key() < toRemove) it = m_liveHistoryBuffer.erase(it); } } // namespace TimeLineManagement