/** * Websocket interface to the training area proxy * $Id$ * $HeadURL$ * $Author$ * Created by Christian Ehrlicher, SLSE, 2017-01-11 */ #include "web_socket_handler.h" #include "ism_handler.h" #include "main_handler.h" #include "settings.h" #include "wls_handler.h" #include "wls_participant_handler.h" #include #include #include #include namespace WirelessLocationSystem { const char *exerciseIdProperty = "exerciseId"; WebSocketHandler::WebSocketHandler(QObject *parent) : QObject(parent) , m_webSocketServer(new QWebSocketServer(QStringLiteral("WLS"), QWebSocketServer::NonSecureMode, this)) , m_basePoint(LtsCoordTrans::UtmCoordinate::fromMgrs(Settings::wlsMgrsBasePoint())) , m_logger(getLogger("WebSocketHandler")) {} WebSocketHandler::~WebSocketHandler() {} bool WebSocketHandler::init(std::shared_ptr wlsHandler, std::shared_ptr participantHandler, std::shared_ptr ismHandler, quint16 listenPort) { if (!m_webSocketServer->listen(QHostAddress::Any, listenPort)) { WARNING_LOG(m_logger) << "Can not listen on port " << listenPort << " - aborting" << m_webSocketServer->errorString(); return false; } connect(m_webSocketServer, &QWebSocketServer::newConnection, this, &WebSocketHandler::onNewConnection); m_wlsHandler = wlsHandler; m_participantHandler = participantHandler; m_ismHandler = ismHandler; return true; } void WebSocketHandler::onNewConnection() { QWebSocket *ws = m_webSocketServer->nextPendingConnection(); if (ws == nullptr) return; DEBUG_LOG(m_logger) << "New websocket connection from " << ws->peerAddress().toString() << ":" << ws->peerPort(); connect(ws, &QWebSocket::textMessageReceived, this, &WebSocketHandler::onTextMessageReceived); connect(ws, &QWebSocket::disconnected, ws, &QWebSocket::deleteLater); connect(this, &WebSocketHandler::signalBroadcastMessage, ws, [this, ws](const QString &msg, int exerciseId) { DEBUG_LOG(m_logger) << "broadcast: exercise property is valid? " << ws->property(exerciseIdProperty).isValid() << " for " << ws->peerPort(); bool exerciseIdAvailable = false; int wsExerciseId = ws->property(exerciseIdProperty).toInt(&exerciseIdAvailable); if (exerciseIdAvailable && wsExerciseId != exerciseId) // NOTE: send for websocket clients that previously didn't set their exercise id beforehand! (e.g. the WLS-Diagnose tool) { DEBUG_LOG(m_logger) << "broadcast: websocket has " << wsExerciseId << ", but " << exerciseId << " was requested"; return; } ws->sendTextMessage(msg); }); // send wls connection state (to all web sockets, should not hurt much) TrainingAreaBase::TetraIcd::ProxyConnectionState connState; connState.connectedToSystem = m_wlsHandler->isConnected(); sendTelegram(&connState, 0); for (const WlsParticipant *participant : qAsConst(m_participantHandler->participants())) sendDataCommunicationState(participant); } void WebSocketHandler::sendTelegramInternal(const TrainingAreaBase::TetraIcd::TrainingAreaTelegram *data, const QString &type, int exerciseId) { QJsonObject obj; obj[QStringLiteral("data")] = data->toJson(); obj[QStringLiteral("type")] = type; obj[QStringLiteral("typeId")] = data->id(); // if (data->id() != TrainingAreaBase::TetraIcd::TelegramId::BESTAETIGUNG) // obj[QStringLiteral("counter")] = m_sendCounter++; const QString jsonStr(QString::fromUtf8(QJsonDocument(obj).toJson(QJsonDocument::Compact))); DEBUG_LOG(m_logger) << "jsonStr: " << jsonStr; emit signalBroadcastMessage(jsonStr, exerciseId); } void WebSocketHandler::sendParticipantConnectionStates(int exerciseId) { TrainingAreaBase::TetraIcd::WlsParticipantsConnectionState partConnState; for (const WlsParticipant *p : m_participantHandler->participants()) { if (p->equipmentNumber() == 0 || p->exerciseId() != exerciseId || p->tagId().isNull()) continue; partConnState.equipmentNumber = p->equipmentNumber(); partConnState.dataCommunicationPossible = p->isDataCommunicationPossible(); sendTelegram(&partConnState, exerciseId); } } bool WebSocketHandler::sendWlsStateReport(int exerciseId) { TrainingAreaBase::TetraIcd::WlsParticipantsStateReport stateReport; stateReport.participantStates.reserve(m_participantHandler->participants().size()); for (const WlsParticipant *p : m_participantHandler->participants()) stateReport.participantStates.push_back(p->wlsParticipantState()); sendTelegram(&stateReport, exerciseId); return true; } void WebSocketHandler::onTextMessageReceived(const QString &baJson) { using namespace TrainingAreaBase::TetraIcd; TRACE_LOG(m_logger) << "WebSocketHandler::onTextMessageReceived: " << baJson; QWebSocket *ws = qobject_cast(sender()); if (ws == nullptr) return; if (baJson.isEmpty()) return; QJsonParseError err; QJsonDocument doc(QJsonDocument::fromJson(baJson.toUtf8(), &err)); if (err.error != QJsonParseError::NoError) { ERROR_LOG(m_logger) << "received invalid json from websocket at " << err.offset << ": " << err.errorString(); return; } const QJsonObject obj(doc.object()); // const QJsonValue sendCounter = obj.value(QStringLiteral("counter")); // if (!sendCounter.isUndefined()) // { // if (!sendCounter.isDouble()) // return false; // recvCounter = sendCounter.toInt(-1); // DEBUG_LOG(m_logger) << "Incoming telegram with Id " << obj.value(QStringLiteral("type")).toString(); // if (recvCounter < 0) // return false; // } const QJsonValue typeId(obj.value(QStringLiteral("typeId"))); if (!typeId.isDouble()) { ERROR_LOG(m_logger) << "Can not read type id: " << baJson; return; } const QJsonObject data(obj.value(QStringLiteral("data")).toObject()); try { switch (typeId.toInt()) { case Constraints::id(): // 3001 (only used by WLS-Diagnose tool) { sendWlsStateReport(-1); // needs to be -1, otherwise the websocket who subscribed to exercise 0 would receive the answer, too break; } case Constraints::id(): // 2008 { int exerciseId = ExerciseStateData::fromJson(data).exerciseId; ws->setProperty(exerciseIdProperty, exerciseId); DEBUG_LOG(m_logger) << "set exercise id " << exerciseId << " for ws with peer port " << ws->peerPort(); sendParticipantConnectionStates(exerciseId); break; } case Constraints::id(): // 2012 m_participantHandler->exerciseEquipmentAssignmentChanged(ExerciseEquipmentAssignmentComplete::fromJson(data).exerciseEquipmentAssignments); break; case Constraints::id(): // 3003 m_participantHandler->handleWlsBinarySfmData(WlsBinarySfmData::fromJson(data)); break; default: WARNING_LOG(m_logger) << "Handler for telegram Id " << typeId.toInt() << " not yet implemented (" << baJson << ")"; return; } } catch (const std::exception &e) { WARNING_LOG(m_logger) << "Could not parse json: " << e.what() << " (" << baJson << ")"; return; } } void WebSocketHandler::sendBinarySfmData(const WlsParticipant *participant) { if (!participant->isSfmRxDataAvailable()) return; if (participant->equipmentNumber() == 0 || participant->tagId().isNull()) return; TrainingAreaBase::TetraIcd::WlsBinarySfmData tele; tele.header.equipmentNumber = participant->equipmentNumber(); tele.header.timeStamp = participant->lastSeenWlsTelegramTime(); LtsUtils::convert(participant->sfmRxData(), tele.payload); sendTelegram(&tele, participant->exerciseId()); } void WebSocketHandler::sendDataCommunicationState(const WlsParticipant *participant) { if (participant->equipmentNumber() == 0 || participant->tagId().isNull()) return; TrainingAreaBase::TetraIcd::WlsParticipantsConnectionState tele; tele.equipmentNumber = participant->equipmentNumber(); tele.dataCommunicationPossible = participant->isDataCommunicationPossible(); sendTelegram(&tele, participant->exerciseId()); } void WebSocketHandler::sendGpsPositionUsage(const WlsParticipant *participant) { if (!participant->isGpsMode()) return; // trigger to notify to not use WlsPositions anymore // see SendToDds::setPosition forceSendPosition(participant, -2, -2); } template void WebSocketHandler::setPosition(T &tele, const WlsParticipant *participant) { tele.header.equipmentNumber = participant->equipmentNumber(); tele.header.timeStamp = participant->positionTimestamp(); const WlsParticipant::RelativePosition &pos = participant->position(); const QPointF lonLat(m_basePoint.geographic(pos.x, pos.y)); tele.geoPosition.lon = lonLat.x(); tele.geoPosition.lat = lonLat.y(); tele.geoPosition.z = pos.z; const int moutObjectId = m_ismHandler->buildingIdFromExternalWlsId(pos.wlsBuildingId); const int moutRoomId = m_ismHandler->roomIdFromRoomNumber(moutObjectId, pos.wlsRoomNumber); //tele.opsPosition.moutAreaId = pos.wlsAreaId; // needed? tele.opsPosition.moutObjectId = moutObjectId; tele.opsPosition.moutRoomId = moutRoomId; } void WebSocketHandler::sendPosition(const WlsParticipant *participant) { INFO_LOG(m_logger) << "sendPosition equipmentNumber=" << participant->equipmentNumber(); if (!participant || !participant->positionIsValid() || participant->equipmentNumber() == 0) { INFO_LOG(m_logger) << "not send - participant is not yet fully initialized"; return; } if (participant->isGpsMode()) { INFO_LOG(m_logger) << "not send - participant is in gps mode"; return; } TrainingAreaBase::TetraIcd::PositionReport posValue; setPosition(posValue, participant); sendTelegram(&posValue, participant->exerciseId()); } void WebSocketHandler::forceSendPosition(const WlsParticipant *participant, int moutObjectId, int moutRoomId) { // Convert Telegram to JSON-Object TrainingAreaBase::TetraIcd::PositionReport posValue; setPosition(posValue, participant); posValue.opsPosition.moutObjectId = moutObjectId; posValue.opsPosition.moutRoomId = moutRoomId; sendTelegram(&posValue, participant->exerciseId()); } } // namespace WirelessLocationSystem