/** * A single building instance * $Id$ * $HeadURL$ * $Author$ * Created by Christian Ehrlicher, SLSE, 2017-02-06 */ #include "building_process.h" #include "main_handler.h" #include "BuildingControlUnit/ControlEffects/xmlreader.h" // Qt #include #include #include #include #include #include #include #include #include //Lts #include #include #include #include #include namespace Buildings { // some helper QStringList idsToStringList(const std::vector< int32_t >& ids) { QStringList sl; sl.reserve(ids.size()); for (const auto &id : qAsConst(ids)) sl.append(QString::number(id)); return sl; } // // class BuildingProcess // BuildingProcess::BuildingProcess(MainHandler *mainHandler, const QString &procName, const BuildingInfo &info, const bool initBuilding, QObject *parent) : QObject(parent) , m_mainHandler(mainHandler) , m_buildingInfo(info) , m_wsSocket(nullptr) , m_wsReconnectTimer(new QTimer(this)) , m_hitLineInfoTimer(new QTimer(this)) , m_hitLineId(info.mseId << 16) , m_initBuilding(initBuilding) , m_logger(getLogger("BuildingProcess")) { m_proc.setProgram(procName); m_proc.setReadChannel(QProcess::StandardOutput); connect(m_wsReconnectTimer, &QTimer::timeout, this, &BuildingProcess::openWebSocket); connect(m_hitLineInfoTimer, &QTimer::timeout, this, &BuildingProcess::onHitLineInfoTimeout); m_hitLineInfoTimer->setSingleShot(false); m_hitLineInfoTimer->start(15000); } BuildingProcess::~BuildingProcess() { shutdown(); } void BuildingProcess::setBuildingInfo(const BuildingInfo &info) { m_buildingInfo = info; shutdown(); start(); } void BuildingProcess::onStructureElementUpdateEffectOverrideReply(const Core::FunctionReplyObject &replyObject) { DEBUG_LOG(m_logger) << "onSendCesHitReply for id " << replyObject.id() << " success= " << replyObject.error(); } bool BuildingProcess::start() { DEBUG_LOG(m_logger) << "BuildingProcess::start mseId = " << m_buildingInfo.mseId << " m_historyState.size() = " << m_historyState.stateHistory().size() << " m_initBuilding=" << m_initBuilding; if (m_buildingInfo.mseId <= 0) { WARNING_LOG(m_logger) << "No valid building information set - can not start building process"; return false; } if (m_proc.state() != QProcess::NotRunning) { WARNING_LOG(m_logger) << "Process for building " << m_buildingInfo.mseId << " is already running!"; return false; } // create config files const QString tmpPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + QLatin1Char('/'); const QString optPath = QStringLiteral("/opt/BuildingModels/StaticMeshes/"); const QString vulnerabilityModelFilename = optPath + QStringLiteral("%1.flt.VWM_Building.xml").arg(m_buildingInfo.name); // QByteArray out; // try // { // // may throw ... :/ // out = qUncompress(m_buildingInfo.vulnerabilityModelData); // } // catch (...) // {} // if (!writeFile(vulnerabilityModelFilename, out)) // { // WARNING_LOG(m_logger) << "(!writeFile(vulnerabilityModelFilename, out)"; // return false; // } const QByteArray bldCfgData = generateBuildingConfigData(); // Lts/Projects//resources/effect_config.xml const QString effectConfigFilename = NGL::findFile(NGL::FileType::ConfigFile, QStringLiteral("effect_config.xml")); WARNING_LOG(m_logger) << "(effectConfigFilename) = " << effectConfigFilename; //const QString effectConfigFilename = LtsUtils:: QStringLiteral("/tmp/effect_config.xml"); const QString bldDataFilename = tmpPath + QStringLiteral("bldg_%1_building_config.xml").arg(m_buildingInfo.mseId); if (!writeFile(bldDataFilename, bldCfgData)) { WARNING_LOG(m_logger) << "(!writeFile(bldDataFilename, bldCfgData)"; return false; } const QByteArray chargesData = generateChargesData(); const QString chargesDataFilename = tmpPath + QStringLiteral("bldg_%1_charges.xml").arg(m_buildingInfo.mseId); if (!writeFile(chargesDataFilename, chargesData)) { WARNING_LOG(m_logger) << "(!writeFile(chargesDataFilename, chargesData)"; return false; } // Read effectConfig.xml for overrides after reconnect EGCU [GUEZE-2011] BuildingControlUnit::XmlReader *xmlReader = new BuildingControlUnit::XmlReader(); xmlReader->init(effectConfigFilename); const QVector &effectsPtrRef = xmlReader->contents().effects; for (auto effectPtr : effectsPtrRef) { if (!effectPtr) continue; DEBUG_LOG(m_logger) << "effect->id=" << effectPtr->id << " effect->category=" << effectPtr->category << " effect->effectType=" << effectPtr->effectType << " effect->override=" << static_cast(effectPtr->override); if (effectPtr->effectType == 1) // Relay { m_effectOverridesReconnect.insert(effectPtr->id, static_cast(effectPtr->override)); } } delete xmlReader; // End effectConfig.xml // disconnected in shutdown() connect(&m_proc, QOverload::of(&QProcess::finished), this, &BuildingProcess::onProcessFinished, Qt::UniqueConnection); connect(&m_proc, &QProcess::readyRead, this, &BuildingProcess::onReadyRead, Qt::UniqueConnection); // start process m_proc.setArguments( { QStringLiteral("--charges"), chargesDataFilename, QStringLiteral("--buildingConfig"), bldDataFilename, QStringLiteral("--effectConfig"), effectConfigFilename, QStringLiteral("--damage"), vulnerabilityModelFilename, QStringLiteral("--buildingId"), QString::number(m_buildingInfo.mseId), QStringLiteral("--percentDamageInit"), QString::number(m_initBuilding) }); m_proc.start(); return m_proc.waitForStarted(); } void BuildingProcess::shutdown() { m_proc.disconnect(this); if (m_proc.state() == QProcess::Running) { m_proc.terminate(); m_proc.waitForFinished(5000); if (m_proc.state() == QProcess::Running) m_proc.kill(); } closeWebSocket(); m_wsUrl.clear(); } void BuildingProcess::setHistoryState(const Buildings::HistoryState &historyState) { DEBUG_LOG(m_logger) << "BuildingProcess::setHistoryState size =" << historyState.stateHistory().size(); ERROR_LOG(m_logger) << "[BuildingProcess::setHistoryState]>>> history =" << historyState; m_historyState = historyState; m_historyState.sampleTime(QDateTime::currentMSecsSinceEpoch()); m_mainHandler->ddsHandler()->dts()->publish(m_historyState); } bool BuildingProcess::setVulnerabilityState(ProjectDataManagement::VulnerabilityState state, const std::vector &roomIds) { DEBUG_LOG(m_logger) << "BuildingProcess::setVulnerabilityState:" << state; if (!m_wsSocket || !m_wsSocket->isValid()) return false; BuildingControlUnit::Json::Telegrams::SetDamageState tele; tele.damageState = (state == ProjectDataManagement::VulnerabilityState::CatastrophicKill ? BuildingControlUnit::DamageState::Destroyed : BuildingControlUnit::DamageState::NoDamage); tele.roomIds.reserve(roomIds.size()); for (int id : roomIds) tele.roomIds += m_buildingInfo.roomId2SubId.value(id); tele.originatorId = 0; // exercise control DEBUG_LOG(m_logger) << "setVulnerabilityState websocket send: " << QString::fromUtf8(tele.toJson()); m_wsSocket->sendTextMessage(QString::fromUtf8(tele.toJson())); return true; } bool BuildingProcess::setContaminationState(const int &bldId, const ContaminationState &contaminationState) { DEBUG_LOG(m_logger) << "BuildingProcess::setContaminationState bldId: "<isValid()) return false; BuildingControlUnit::Json::Telegrams::SetContaminationState tele; BuildingControlUnit::ContaminationState contState; contState.aesId = contaminationState.aesId(); if ( contaminationState.vulnerabilityState() == ProjectDataManagement::VulnerabilityState::CatastrophicKill ) contState.damagePercentNew = 100; else contState.damagePercentNew = 0; contState.rooms = contaminationState.rooms();//cs.rooms(); tele.contaminationStates.push_back(contState); DEBUG_LOG(m_logger) << "SetContaminationState websocket send: " << QString::fromUtf8(tele.toJson()); m_wsSocket->sendTextMessage(QString::fromUtf8(tele.toJson())); return true; } bool BuildingProcess::resetContaminationState() { DEBUG_LOG(m_logger) << "BuildingProcess::resetContaminationState" ; if (!m_wsSocket || !m_wsSocket->isValid()) return false; BuildingControlUnit::Json::Telegrams::ResetContaminationState tele; DEBUG_LOG(m_logger) << "ResetContaminationState websocket send: " << QString::fromUtf8(tele.toJson()); m_wsSocket->sendTextMessage(QString::fromUtf8(tele.toJson())); return true; } bool BuildingProcess::setFortifyFactors(const std::vector &fortifyFactors) { if (!m_wsSocket || !m_wsSocket->isValid()) return false; BuildingControlUnit::Json::Telegrams::SetFortifyFactor tele; for (const auto &ff : fortifyFactors) { BuildingControlUnit::Json::Telegrams::SetFortifyFactor::FortifyInfo info; info.roomId = m_buildingInfo.roomId2SubId.value(ff.roomId()); info.fortifyFactor = ff.fortifyFactor(); tele.fortifyFactors.push_back(info); } DEBUG_LOG(m_logger) << "setFortifyFactors websocket send: " << QString::fromUtf8(tele.toJson()); m_wsSocket->sendTextMessage(QString::fromUtf8(tele.toJson())); return true; } void BuildingProcess::newRelayInfos(const QMap &map) { m_relayInfos = map; } bool BuildingProcess::setEffectStates( const int assetId, const QByteArray &properties) { BuildingControlUnit::Json::Telegrams::SetEffectStates tele; if (!m_wsSocket || !m_wsSocket->isValid()) return false; DEBUG_LOG(m_logger) << "setEffectStates" << " assetId=" << assetId << " properties=" << properties; if (assetId > 0) { for (const EgcuInfo &egcu : qAsConst(m_buildingInfo.egcus)) { DEBUG_LOG(m_logger) << "EGCU: " << egcu.assetName << " assetId=" << egcu.assetId << " egcu.assetId=" << egcu.assetId << " hostname=" << egcu.hostname; if ( assetId == egcu.assetId) { DEBUG_LOG(m_logger) << "segment=" << egcu.hostname; tele.segment = egcu.hostname; } } if (tele.segment.isEmpty()) { WARNING_LOG(m_logger) << "Asset with id " << assetId << " not found in this building"; return false; } if (tele.segment == QString()) { ERROR_LOG(m_logger) << "Asset with id=" << assetId << " has no Hostname!!!!"; return false; } } QJsonParseError parserError; const QJsonDocument doc(QJsonDocument::fromJson(properties, &parserError)); if (parserError.error != QJsonParseError::NoError) return false; #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) QSet allRelayIds; for (auto it = m_relayInfos.keyBegin(); it != m_relayInfos.keyEnd(); ++it) allRelayIds.insert(*it); #else QSet allRelayIds(m_relayInfos.keyBegin(), m_relayInfos.keyEnd()); #endif QJsonObject docObj = QJsonObject(doc.object()); QJsonObject overridesObj = docObj.value(QStringLiteral("Overrides")).toObject(); QJsonArray arrEgcus = overridesObj.value(QStringLiteral("relays")).toArray(); for (int i=0; i(value); tele.effectStates.append(bcuEffectState); } for (int relayId : qAsConst(allRelayIds)) { BuildingControlUnit::BcuEffectState bcuEffectState; bcuEffectState.id = relayId; bcuEffectState.type = BuildingControlUnit::BET_Relay; // so far only relays are handeled bcuEffectState.effectOverride = BuildingControlUnit::BcuEffectOverride::BEO_Auto; if (relayId == 99) // Special Treatment for Sound { bcuEffectState.id = 1; bcuEffectState.type = BuildingControlUnit::BET_BackgroundSound; } tele.effectStates.append(bcuEffectState); } { BuildingControlUnit::BcuEffectState bcuEffectState; bcuEffectState.id = 1; // Id aus effect_config.xml bcuEffectState.type = BuildingControlUnit::BET_BsmFlash; int flashVal = -1; if (overridesObj.contains(QStringLiteral("flash"))) flashVal = overridesObj.value(QStringLiteral("flash")).toInt(); bcuEffectState.effectOverride = static_cast(flashVal); tele.effectStates.append(bcuEffectState); } QString valueStr = QString::fromUtf8(tele.toJson()); DEBUG_LOG(m_logger) << "setEffectStates websocket send: " << valueStr; qint64 sendBytesCount = m_wsSocket->sendTextMessage(valueStr); DEBUG_LOG(m_logger) << "sendBytesCount= " << sendBytesCount; m_effectStates.insert(assetId, valueStr); return true; } bool BuildingProcess::relayState(const std::vector &relayStates) { if (!m_wsSocket || !m_wsSocket->isValid()) return false; BuildingControlUnit::Json::Telegrams::RelayState tele; for (const auto &ff : relayStates) { BuildingControlUnit::Json::Telegrams::RelayState::RelayStateInfo info; info.id = ff.id(); info.state = ff.state(); tele.relayStates.push_back(info); } DEBUG_LOG(m_logger) << "relayState websocket send: " << QString::fromUtf8(tele.toJson()); m_wsSocket->sendTextMessage(QString::fromUtf8(tele.toJson())); return true; } bool BuildingProcess::sendCesHitOnBuilding(const qint64 effectTimeMSec, const int chargeTypeId, const int destructiveFactor, const int penetrationFactor, const int aesId, const Core::GeoPosition &impactPosition, const QString &properties) { if (!m_wsSocket || !m_wsSocket->isValid()) return false; // currently we only support single/area fire, therefore the tas charge type is enough const LtsCoordTrans::UtmCoordinate coord(LtsCoordTrans::UtmCoordinate::fromGeographic(m_buildingInfo.absPosition.lon(), m_buildingInfo.absPosition.lat())); const LtsCoordTrans::UtmCoordinate impact(LtsCoordTrans::UtmCoordinate::fromGeographic(impactPosition.lon(), impactPosition.lat(), coord.zone)); Core::RelativePosition relPos(impact.x - coord.x, impact.y - coord.y, impactPosition.z() - m_buildingInfo.absPosition.z()); double xRot, yRot; QTransform m; // - -> rotate back to 0° m.rotate(-m_buildingInfo.angle); m.map(relPos.offsetX(), relPos.offsetY(), &xRot, &yRot); relPos.offsetX(xRot); relPos.offsetY(yRot); BuildingControlUnit::Json::Telegrams::CentralEffectSimulationHit tele; tele.effectTimeMSec = effectTimeMSec; tele.chargeTypeId = chargeTypeId; tele.destructiveFactor = destructiveFactor; tele.penetrationFactor = penetrationFactor; tele.aesId = aesId; tele.relPosition.x = relPos.offsetX(); tele.relPosition.y = relPos.offsetY(); tele.relPosition.z = relPos.offsetZ(); tele.properties = properties; DEBUG_LOG(m_logger) << "sendCesHitOnBuilding websocket send: " << QString::fromUtf8(tele.toJson()); m_wsSocket->sendTextMessage(QString::fromUtf8(tele.toJson())); return true; } bool BuildingProcess::sendCesHit3DonBuilding(const qint64 effectTimeMSec, const int chargeTypeId, const int destructiveFactor, const int penetrationFactor, const int aesId, const Core::GeoPosition &impactPosition, const Core::GeoPosition &detonationLocation, double detonationDistance, const QString &properties) { if (!m_wsSocket || !m_wsSocket->isValid()) return false; // currently we only support single/area fire, therefore the tas charge type is enough const LtsCoordTrans::UtmCoordinate &coord(LtsCoordTrans::UtmCoordinate::fromGeographic(m_buildingInfo.absPosition.lon(), m_buildingInfo.absPosition.lat())); Core::RelativePosition relImpactPos; { const LtsCoordTrans::UtmCoordinate &impactUtm(LtsCoordTrans::UtmCoordinate::fromGeographic(impactPosition.lon(), impactPosition.lat(), coord.zone)); relImpactPos = Core::RelativePosition(impactUtm.x - coord.x, impactUtm.y - coord.y, impactPosition.z() - m_buildingInfo.absPosition.z()); } Core::RelativePosition relDetoLoc; { const LtsCoordTrans::UtmCoordinate &detonationLocationUtm(LtsCoordTrans::UtmCoordinate::fromGeographic(detonationLocation.lon(), detonationLocation.lat(), coord.zone)); relDetoLoc = Core::RelativePosition(detonationLocationUtm.x - coord.x, detonationLocationUtm.y - coord.y, detonationLocation.z() - m_buildingInfo.absPosition.z()); } double xRot, yRot; QTransform m; // - -> rotate back to 0° m.rotate(-m_buildingInfo.angle); m.map(relImpactPos.offsetX(), relImpactPos.offsetY(), &xRot, &yRot); relImpactPos.offsetX(xRot); relImpactPos.offsetY(yRot); m.map(relDetoLoc.offsetX(), relDetoLoc.offsetY(), &xRot, &yRot); relDetoLoc.offsetX(xRot); relDetoLoc.offsetY(yRot); BuildingControlUnit::Json::Telegrams::CentralEffectSimulationHit3D tele; tele.effectTimeMSec = effectTimeMSec; tele.chargeTypeId = chargeTypeId; tele.destructiveFactor = destructiveFactor; tele.penetrationFactor = penetrationFactor; tele.aesId = aesId; tele.impactPosition.x = relImpactPos.offsetX(); tele.impactPosition.y = relImpactPos.offsetY(); tele.impactPosition.z = relImpactPos.offsetZ(); tele.detonationLocation.x = relDetoLoc.offsetX(); tele.detonationLocation.y = relDetoLoc.offsetY(); tele.detonationLocation.z = relDetoLoc.offsetZ(); tele.detonationDistance = detonationDistance; tele.properties = properties; DEBUG_LOG(m_logger) << "sendCesHit3DonBuilding websocket send: " << QString::fromUtf8(tele.toJson()); m_wsSocket->sendTextMessage(QString::fromUtf8(tele.toJson())); return true; } bool BuildingProcess::switchRelay(const SwitchRelayRequest &data) { BuildingControlUnit::Json::Telegrams::SwitchRelay tele; if (!m_wsSocket || !m_wsSocket->isValid()) return false; DEBUG_LOG(m_logger) << "switchRelay" << " buildingId=" << data.buildingId() << " assetId=" << data.assetId() << data.state(); for (const EgcuInfo &egcu : qAsConst(m_buildingInfo.egcus)) { DEBUG_LOG(m_logger) << "EGCU: " << egcu.assetName << " assetId=" << egcu.assetId << " hostname=" << egcu.hostname; DEBUG_LOG(m_logger) << "data.assetId()=" << data.assetId() << "egcu.assetId=" << egcu.assetId; if ( data.assetId() != egcu.assetId) continue; tele.segment = egcu.hostname; tele.switchRelayValues.id = data.number(); tele.switchRelayValues.bcuRelayState = static_cast (data.state().underlying()); if ( data.state() == StateSetRelay_def::Pulse) { tele.switchRelayValues.count = data.pulseAttributes().get().count(); tele.switchRelayValues.delay = data.pulseAttributes().get().delay(); tele.switchRelayValues.pulse = data.pulseAttributes().get().pulse(); } DEBUG_LOG(m_logger) << "switchRelay websocket send: " << QString::fromUtf8(tele.toJson()); m_wsSocket->sendTextMessage(QString::fromUtf8(tele.toJson())); return true; } return false; } bool BuildingProcess::triggerFlashlight(const TriggerBsmFlashRequest data) { BuildingControlUnit::Json::Telegrams::BsmTriggerFlashlight tele; if (!m_wsSocket || !m_wsSocket->isValid()) return false; DEBUG_LOG(m_logger) << "triggerFlashlight" << " BuildingId=" << data.buildingId() << " assetId=" << data.assetId() << " m_buildingInfo.bsms.size()" << m_buildingInfo.bsms.size(); QMapIterator i(m_buildingInfo.bsms); while (i.hasNext()) { i.next(); DEBUG_LOG(m_logger) << "key=" << i.key() << " assetId=" << i.value().assetId << " bsmId=" << i.value().bsmId << " hostname=" << i.value().hostname << " port=" << i.value().port; } if ( m_buildingInfo.bsms.contains(data.assetId())) { BsmInfo bsmInfo = m_buildingInfo.bsms.value(data.assetId()); DEBUG_LOG(m_logger) << " BuildingId=" << data.buildingId() << " assetId=" << bsmInfo.assetId << " hostname=" << bsmInfo.hostname; tele.hostname = bsmInfo.hostname; tele.numberOfFlashes = 1; tele.pauseBetweenFlashesSec = 0; tele.sensorId = 0; // Hostname reicht DEBUG_LOG(m_logger) << "triggerFlashlight websocket send: " << QString::fromUtf8(tele.toJson()); m_wsSocket->sendTextMessage(QString::fromUtf8(tele.toJson())); return true; } return false; } void BuildingProcess::onReadyRead() { const QString data = QString::fromLocal8Bit(m_proc.readAll()); const QString strToSearch(QStringLiteral("web socket url:")); if (data.contains(strToSearch)) { m_wsUrl = data.split(strToSearch).at(1).split(QLatin1Char('\n')).at(0).trimmed(); INFO_LOG(m_logger) << "Using web socket url '" << m_wsUrl << "' for building " << m_buildingInfo.mseId; openWebSocket(); } } void BuildingProcess::onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) { // normal exit if (exitCode == 0 && exitStatus == QProcess::NormalExit) return; const char *what = exitStatus == QProcess::NormalExit ? "stopped" : "crashed"; WARNING_LOG(m_logger) << "building control process for building " << m_buildingInfo.mseId << " " << what << " with exit code " << exitCode << " - restarting in 1 second"; if (exitCode != 0) { WARNING_LOG(m_logger) << "stdout: " << m_proc.readAllStandardOutput(); WARNING_LOG(m_logger) << "stderr: " << m_proc.readAllStandardError(); } closeWebSocket(); QTimer::singleShot(1000, this, &BuildingProcess::start); } void BuildingProcess::onTextMessageReceived(const QString &message) { TRACE_LOG(m_logger) << "Got message from building control process: " << message; BuildingControlUnit::Json::Telegrams::BaseTelegram *tel = BuildingControlUnit::Json::Telegrams::BaseTelegram::telegramFromJson(message.toUtf8()); if (!tel) { WARNING_LOG(m_logger) << "Can not convert '" << message << "' to a valid telegram!"; return; } if (tel->telegramId() == BuildingControlUnit::Json::Telegrams::BsmState::telegramId()) handleBsmState(static_cast(tel)); else if (tel->telegramId() == BuildingControlUnit::Json::Telegrams::HitResult::telegramId()) handleHitResult(static_cast(tel)); else if (tel->telegramId() == BuildingControlUnit::Json::Telegrams::RelayState::telegramId()) handleRelayState(static_cast(tel)); else if (tel->telegramId() == BuildingControlUnit::Json::Telegrams::EffectOverride::telegramId()) handleEffectOverride(static_cast(tel)); else WARNING_LOG(m_logger) << "Unhandled incoming telegram with id '" << tel->telegramId() << "'"; delete tel; } void BuildingProcess::handleBsmState(BuildingControlUnit::Json::Telegrams::BsmState *tel) { BuildingStateTechnical state; state.id(m_buildingInfo.mseId); state.assetId(m_buildingInfo.assetId); state.structureElementId(m_buildingInfo.structureElementId); state.sampleTime(QDateTime::currentMSecsSinceEpoch()); state.bsmStates().resize(tel->bsmStates.size()); BsmState s; for (int i = 0; i < tel->bsmStates.size(); ++i) { const BuildingControlUnit::Json::Telegrams::BsmState::BsmStateInfo &info = tel->bsmStates.at(i); s.id(info.id); s.connected(info.connected); s.hostport(info.port); s.hostname(info.hostname.toStdString()); s.versionInfo(info.versionInfo.toStdString()); state.bsmStates()[i] = s; m_bsmStates.insert(info.id, s); } m_mainHandler->ddsHandler()->dts()->publish(state); } void BuildingProcess::historyState(const BuildingState &state) { if (state.vulnerabilityState() == ProjectDataManagement::VulnerabilityState::CombatReady && state.damagePercent() == 0 && state.originatorParticipantId() == 0 && state.contaminationStates().size() == 0) // Criterium for init from Excon { DEBUG_LOG(m_logger) << "setVulnerabilityState->removeHistoryStateByKey:" << state.id(); m_historyState.stateHistory().resize(0); } HistoryStateItem historyStateItem; historyStateItem.aesId(state.aesId()); historyStateItem.chargeTypeId(state.chargeTypeId()); historyStateItem.damagePercent(state.damagePercent()); historyStateItem.damagePercentOld(state.damagePercentOld()); historyStateItem.originatorParticipantId(state.originatorParticipantId()); historyStateItem.originatorType() = state.originatorType(); historyStateItem.time(state.sampleTime()); historyStateItem.vulnerabilityState() = state.vulnerabilityState(); historyStateItem.contaminationStates() = state.contaminationStates(); m_historyState.stateHistory().push_back(std::move(historyStateItem)); DEBUG_LOG(m_logger) << "BuildingProcess::historyState contaminationStates:" << state; m_historyState.mseId(state.id()); DEBUG_LOG(m_logger) << "setVulnerabilityState->removeHistoryStateByKey:" << m_historyState.mseId(); m_historyState.sampleTime(QDateTime::currentMSecsSinceEpoch()); m_mainHandler->ddsHandler()->dts()->publish(m_historyState); } void BuildingProcess::handleHitResult(BuildingControlUnit::Json::Telegrams::HitResult *tel) { const BuildingControlUnit::Json::Telegrams::HitResult::HitResultInfo &data = tel->hitResult; BuildingState state; DEBUG_LOG(m_logger) << LOGFUNCTION << " data.hitOnRoomId=" << data.hitOnRoomId; state.id(m_buildingInfo.mseId); int assetId = m_mainHandler->ddsHandler()->dtcIsm()->getAssetIdFromMountingPointId(m_buildingInfo.mseId, data.sensorId); DEBUG_LOG(m_logger) << "handleHitResult assetId=" << assetId; state.assetId(assetId); //state.structureElementId(m_buildingInfo.structureElementId); InfrastructureManagement::StructureElement se; m_mainHandler->ddsHandler()->dtcIsm()->getStructureElementInMntPointAssetByAssetId(assetId, se); DEBUG_LOG(m_logger) << "handleHitResult structureElementId=" << se.id(); state.structureElementId(se.id()); state.sampleTime(QDateTime::currentMSecsSinceEpoch()); if (data.originatorType == BuildingControlUnit::OriginatorType::DirectHit) { // direct hit -> originator = equipmentNumber state.originatorParticipantId(m_mainHandler->ppmHandler()->equipmentNumberToParticipantId(data.originatorId)); state.chargeTypeId(data.chargeTypeId); state.originatorType(ProjectDataManagement::StateChangeOriginator_def::Participant); } else if (data.originatorType == BuildingControlUnit::OriginatorType::CesHit) { // indirect hit -> originator = aesId state.aesId(data.originatorId); // no need to convert because we sent our chargeTypeId within the hit request state.chargeTypeId(data.chargeTypeId); state.originatorType(ProjectDataManagement::StateChangeOriginator_def::AreaEffectSimulation); } else if (data.originatorType == BuildingControlUnit::OriginatorType::OC)// OC { state.originatorParticipantId(0); state.chargeTypeId(data.chargeTypeId); state.originatorType(ProjectDataManagement::StateChangeOriginator_def::ObserverController); } else // ECC { state.originatorParticipantId(0); state.chargeTypeId(data.chargeTypeId); state.originatorType(ProjectDataManagement::StateChangeOriginator_def::ExerciseControl); } state.damagePercent(data.damagePercentNew); state.damagePercentOld(data.damagePercentOld); state.vulnerabilityState(data.damageState == BuildingControlUnit::DamageState::Destroyed ? ProjectDataManagement::VulnerabilityState::CatastrophicKill : ProjectDataManagement::VulnerabilityState::CombatReady); state.hitOnRoomId(data.hitOnRoomId); state.roomStates().reserve(data.rooms.size()); RoomState s; for (const auto &info : data.rooms) { s.id(m_buildingInfo.subId2RoomId.value(info.roomId)); s.mountingPointId(info.roomId); s.damagePercent(info.damagePercent); s.remainingEffectPercent(info.remainingEffectPercent); s.fortifyFactor(info.fortifyFactor); state.roomStates().push_back(s); } ContaminationState cs; state.contaminationStates().reserve(data.contaminations.count()); for (const auto &csi : data.contaminations) { cs.aesId(csi.aesId); cs.rooms().assign(csi.rooms.begin(), csi.rooms.end()); state.contaminationStates().push_back(cs); } if (m_historyState.mseId() != 0) historyState(state); else { // initial - don't publish state m_historyState.mseId(m_buildingInfo.mseId); } m_mainHandler->ddsHandler()->dts()->publish(state); if (data.originatorType == BuildingControlUnit::OriginatorType::DirectHit) sendHitLine(state); } // [GUEZE-2011] // Is run when an EGCU is reconnected to the Building void BuildingProcess::handleReconnectEgcu(const QString &segment) { // Aus der Konfiguration effect_config.xml die Overrides vom Segment nehmen // Function Topic ISM::LTS_StructureElementUpdateEffectOverrideRequest schicken DEBUG_LOG(m_logger) << "handleReconnectEgcu segment =" << segment; for (const EgcuInfo &egcu : qAsConst(m_buildingInfo.egcus)) { if (QString::compare(egcu.hostname, segment, Qt::CaseInsensitive) == 0) { const auto keys = m_effectOverridesReconnect.keys(); for (const int &relayId : keys) { DEBUG_LOG(m_logger) << "RelayId=" << relayId << " Override=" << m_effectOverridesReconnect.value(relayId); if (m_effectOverridesReconnect.value(relayId) >= 0) { // int relayId = 2; // fog (is loaded from effect_config.xml) InfrastructureManagement::EffectOverride override = InfrastructureManagement::EffectOverride_def::PermanentOff; int structureElementIdMp = m_mainHandler->ddsHandler()->dtcIsm()->getStructueElementIdFromAssetId(egcu.assetId); DEBUG_LOG(m_logger) << "EGCU: " << egcu.assetName << " assetId=" << egcu.assetId << " hostname='" << egcu.hostname << "' buildingName=" << egcu.buildingName << " mountPointId=" << egcu.mountPointId << " structureElementIdMp=" << structureElementIdMp << " relayId=" << relayId; InfrastructureManagement::StructureElementUpdateEffectOverrideRequest request; request.structureElementId(structureElementIdMp); request.effectId(relayId); request.effectOverride(override); DEBUG_LOG(m_logger) << "Vor structureElementUpdateEffectOverrideRequest"; int result = m_mainHandler->ismHandler()->ftcIsm()->structureElementUpdateEffectOverrideRequest(request, this); DEBUG_LOG(m_logger) << "Nach structureElementUpdateEffectOverrideRequest result=" << result; } } break; } } } void BuildingProcess::handleRelayState(BuildingControlUnit::Json::Telegrams::RelayState *tel) { RelayState state; DEBUG_LOG(m_logger) << "handleRelayState EGCU:" << tel->segment << " m_buildingInfo.egcus.size()=" << m_buildingInfo.egcus.size() << " stateTime=" << tel->stateTime << " offlineTime=" << tel->offlineTime; // Check, if recconnect EGCU if ( tel->offlineTime > 1000) { handleReconnectEgcu(tel->segment); } for (const EgcuInfo &egcu : qAsConst(m_buildingInfo.egcus)) { if (QString::compare(egcu.hostname,tel->segment, Qt::CaseInsensitive) == 0) { DEBUG_LOG(m_logger) << "EGCU: " << egcu.assetName << " assetId=" << egcu.assetId << " hostname='" << egcu.hostname << "' buildingName=" << egcu.buildingName; state.id(egcu.assetId); state.stateTime(tel->stateTime); state.offlineTime(tel->offlineTime); break; } } RelayStateItem item; for (const auto &info : qAsConst(tel->relayStates)) { item.id(info.id); item.state(info.state); state.relayStates().push_back(item); } // TODO nur geaenderte Meldungen auf den dds-Bus schicken. m_mainHandler->ddsHandler()->dts()->publish(state); } void BuildingProcess::handleEffectOverride(BuildingControlUnit::Json::Telegrams::EffectOverride *tel) { DEBUG_LOG(m_logger) << "BuildingProcess::handleEffectOverride" << " tel->overrides.size=" << tel->overrides.size(); InfrastructureManagement::StructureElementUpdateEffectOverrideRequest request; for ( int i = 0; i < m_buildingInfo.egcus.size(); i++) { DEBUG_LOG(m_logger) << "BuildingProcess::handleEffectOverride i=" << i; if ( m_buildingInfo.egcus[i].hostname.toLower() == tel->segment) { int structureElementIdMp = m_mainHandler->ddsHandler()->dtcIsm()->getStructueElementIdFromAssetId( m_buildingInfo.egcus.at(i).assetId); DEBUG_LOG(m_logger) << "BuildingProcess::handleEffectOverride i=" << i << " mountPointId=" << m_buildingInfo.egcus.at(i).mountPointId << " assetId=" << m_buildingInfo.egcus.at(i).assetId << " structureElementId=" << m_buildingInfo.structureElementId << " structureElementIdMp=" << structureElementIdMp; request.structureElementId(structureElementIdMp); } for ( int j = 0; j < tel->overrides.size(); j++ ) { //request.structureElementId(tel->segment); request.effectId(tel->overrides.at(j).id); switch (tel->overrides.at(j).effectOverride) { case -1: { request.effectOverride(InfrastructureManagement::EffectOverride_def::Automatic); break; } case 0: { request.effectOverride(InfrastructureManagement::EffectOverride_def::PermanentOff); break; } case 1: { request.effectOverride(InfrastructureManagement::EffectOverride_def::PermanentOn); break; } } DEBUG_LOG(m_logger) << "Send Override Request" << " structureElementId=" << request.structureElementId() << " effectId=" << request.effectId() << " effectOverride=" << request.effectOverride(); if ( request.structureElementId() > 0) m_mainHandler->ddsHandler()->ftcIsm()->structureElementUpdateEffectOverrideRequest(request, this); else ERROR_LOG(m_logger) << "request.structureElementId() =" << request.structureElementId() << " NOT VALID!!!"; } } } bool BuildingProcess::isValidPos(const Core::GeoPosition &pos) { if (qFuzzyIsNull(pos.lat()) && qFuzzyIsNull(pos.lon())) return false; return !std::isnan(pos.lat()) && !std::isnan(pos.lon()); } void BuildingProcess::sendHitLine(const BuildingState &state) { if (state.originatorParticipantId() <= 0) return; const ParticipantManagement::ParticipantPosition participantPosition = m_mainHandler->ppmHandler()->participantPosition(state.originatorParticipantId()); if (!isValidPos(participantPosition.pos())) return; BuildingHitLineInfo buildingHitLineInfo; InfrastructureManagement::MainStructureElement mainStructureElement = m_mainHandler->ismHandler()->mainStructureElement(state.id()); // Direct Fire buildingHitLineInfo.id(m_hitLineId); buildingHitLineInfo.buildingId(m_buildingInfo.mseId); buildingHitLineInfo.buildingPos(mainStructureElement.geoPosition()); if (state.hitOnRoomId() > 0) { for (const auto &se : mainStructureElement.elements()) { if (se.structureElementTypeId() == InfrastructureManagement::StructureElementType::Room && se.mountingPointId() == state.hitOnRoomId()) { buildingHitLineInfo.buildingPos(se.geoPosition()); break; } } } buildingHitLineInfo.chargeTypeId(state.chargeTypeId()); buildingHitLineInfo.originatorParticipantId(state.originatorParticipantId()); buildingHitLineInfo.originatorPos(participantPosition.pos()); buildingHitLineInfo.originatorPartyId(participantPosition.partyId()); buildingHitLineInfo.sampleTime(state.sampleTime()); buildingHitLineInfo.hitOnRoomId(state.hitOnRoomId()); buildingHitLineInfo.contaminationStates(state.contaminationStates()); m_mainHandler->ddsHandler()->dts()->publish(buildingHitLineInfo); m_hitLineInfoMap.insert(buildingHitLineInfo.id(), buildingHitLineInfo); ++m_hitLineId; } void BuildingProcess::onHitLineInfoTimeout() { const qint64 tNow = QDateTime::currentMSecsSinceEpoch(); auto it = m_hitLineInfoMap.begin(); while (it != m_hitLineInfoMap.end()) { if (!LtsUtils::isPositionTimeOutdated(tNow, it.value().sampleTime())) break; m_mainHandler->ddsHandler()->dts()->removeBuildingHitLineInfoByKey(it.value().id()); it = m_hitLineInfoMap.erase(it); } } bool BuildingProcess::writeFile(const QString &fn, const QByteArray &data) { QFile f(fn); if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) { WARNING_LOG(m_logger) << "Can not open file '" << fn << "' - error: " << f.errorString(); return false; } if (f.write(data) != data.size()) { WARNING_LOG(m_logger) << "Can not write data to file '" << fn << "' - error: " << f.errorString(); return false; } return true; } static void outputBsmData(QXmlStreamWriter &sw, const BsmInfo &bsm) { sw.writeStartElement(QStringLiteral("BSM")); { sw.writeAttribute(QStringLiteral("id"), QString::number(bsm.bsmId)); sw.writeAttribute(QStringLiteral("hostname"), bsm.hostname); sw.writeAttribute(QStringLiteral("port"), QString::number(bsm.port)); sw.writeStartElement(QStringLiteral("Position")); { sw.writeAttribute(QStringLiteral("x"), QString::number(bsm.relPosition.offsetX(), 'f', 10)); sw.writeAttribute(QStringLiteral("y"), QString::number(bsm.relPosition.offsetY(), 'f', 10)); sw.writeAttribute(QStringLiteral("z"), QString::number(bsm.relPosition.offsetZ(), 'f', 10)); } sw.writeEndElement(); sw.writeStartElement(QStringLiteral("Direction")); { sw.writeAttribute(QStringLiteral("x"), QString::number(bsm.orientation.heave(), 'f', 10)); sw.writeAttribute(QStringLiteral("y"), QString::number(bsm.orientation.pitch(), 'f', 10)); sw.writeAttribute(QStringLiteral("z"), QString::number(bsm.orientation.roll(), 'f', 10)); } sw.writeEndElement(); } sw.writeEndElement(); } static void outputEgcuData(QXmlStreamWriter &sw, const EgcuInfo &egcu) { sw.writeStartElement(QStringLiteral("EGCU")); { sw.writeAttribute(QStringLiteral("hostname"), egcu.hostname); sw.writeStartElement(QStringLiteral("rooms")); for (int roomId : egcu.roomIds) { sw.writeStartElement(QStringLiteral("room")); sw.writeAttribute(QStringLiteral("id"), QString::number(roomId)); sw.writeEndElement(); } sw.writeEndElement(); } sw.writeEndElement(); } QByteArray BuildingProcess::generateBuildingConfigData() const { QByteArray ret; QXmlStreamWriter sw(&ret); sw.setAutoFormatting(true); sw.setAutoFormattingIndent(2); sw.setCodec("UTF-8"); sw.writeStartDocument(QStringLiteral("1.0"), true); sw.writeStartElement(QStringLiteral("BuildingConfiguration")); { sw.writeTextElement(QStringLiteral("BuildingName"), m_buildingInfo.name); sw.writeTextElement(QStringLiteral("ModelId"), QString::number(m_buildingInfo.modelId)); for (const BsmInfo &bsm : m_buildingInfo.bsms) outputBsmData(sw, bsm); for (const EgcuInfo &egcu : m_buildingInfo.egcus) outputEgcuData(sw, egcu); } sw.writeEndElement(); sw.writeEndDocument(); return ret; } QByteArray BuildingProcess::generateChargesData() const { const QMap buildingCharges = m_mainHandler->pdmHandler()->buildingChargeEffects(); const QMap chargeTypes = m_mainHandler->pdmHandler()->chargeTypes(); QByteArray ret; QString properties = QStringLiteral("TODO fill properties"); QXmlStreamWriter sw(&ret); sw.setAutoFormatting(true); sw.setAutoFormattingIndent(2); sw.setCodec("UTF-8"); sw.writeStartDocument(QStringLiteral("1.0"), true); sw.writeStartElement(QStringLiteral("Charges")); { for (const ProjectDataManagement::BuildingChargeEffect &charge : buildingCharges) { auto it = chargeTypes.find(charge.chargeTypeId()); if (it == chargeTypes.end()) { WARNING_LOG(m_logger) << "Could not find charge type with id " << charge.chargeTypeId() << " - skipping"; continue; } // NOTE: igniterTypeId/IgniterFunction = 0 as long as BuildingChargeEffect does not contain values ProjectDataManagement::ChargeTypeProperty chargeTypeProperty = m_mainHandler->ddsHandler()->dtcPdm()->getChargeTypeSpecialProperty(charge.chargeTypeId(),0,0); sw.writeStartElement(QStringLiteral("Charge")); sw.writeTextElement(QStringLiteral("Id"), QString::number(charge.id())); sw.writeTextElement(QStringLiteral("ChargeTypeId"), QString::number(charge.chargeTypeId())); sw.writeTextElement(QStringLiteral("DestructiveFactor"), QString::number(charge.destructiveFactor())); sw.writeTextElement(QStringLiteral("PenetrationEffect"), QString::number(charge.penetrationFactor())); sw.writeTextElement(QStringLiteral("properties"), properties); sw.writeTextElement(QStringLiteral("EffectSound"), QString::number(charge.hitSound())); sw.writeTextElement(QStringLiteral("DifcueLight"), QString::number(charge.difcueLight())); sw.writeTextElement(QStringLiteral("DifcuePyro"), QString::number(charge.difcuePyro())); if (chargeTypeProperty == ProjectDataManagement::ChargeTypeProperty_def::None) if (charge.destructiveFactor() > 0) sw.writeTextElement(QStringLiteral("showHitLine"), QString::number(1)); else sw.writeTextElement(QStringLiteral("showHitLine"), QString::number(0)); else sw.writeTextElement(QStringLiteral("showHitLine"), QString::number(1)); sw.writeEndElement(); } } sw.writeEndElement(); sw.writeEndDocument(); return ret; } void BuildingProcess::openWebSocket() { closeWebSocket(); m_wsSocket = new QWebSocket; connect(m_wsSocket, QOverload::of(&QWebSocket::error), this, &BuildingProcess::onWebSocketError); connect(m_wsSocket, &QWebSocket::connected, this, &BuildingProcess::onWebSocketConnected); connect(m_wsSocket, &QWebSocket::disconnected, this, &BuildingProcess::onWebSocketDisconnected); connect(m_wsSocket, &QWebSocket::textMessageReceived, this, &BuildingProcess::onTextMessageReceived); WARNING_LOG(m_logger) << "openWebSocket:" << m_wsUrl.toLocal8Bit() << " (buildingId: " << m_buildingInfo.mseId << ") - socket: " << m_wsSocket; m_wsSocket->open(QUrl::fromEncoded(m_wsUrl.toLocal8Bit())); } void BuildingProcess::closeWebSocket() { if (m_wsSocket) { WARNING_LOG(m_logger) << "closeWebSocket (buildingId : " << m_buildingInfo.mseId << ") - socket: " << m_wsSocket << " localPort=" << m_wsSocket->localPort() << ", error: " << ", " << m_wsSocket->errorString(); m_wsSocket->disconnect(this); m_wsSocket->close(); m_wsSocket->deleteLater(); m_wsSocket = nullptr; } m_wsReconnectTimer->stop(); } void BuildingProcess::onWebSocketError(QAbstractSocket::SocketError error) { if (!m_wsSocket) return; WARNING_LOG(m_logger) << "Error connecting to " << m_wsSocket->peerAddress().toString() << ": " << error << " (buildingId: " << m_buildingInfo.mseId << ") - socket: " << m_wsSocket; closeWebSocket(); m_wsReconnectTimer->start(1000); } void BuildingProcess::onWebSocketConnected() { INFO_LOG(m_logger) << "connected to building control process " << m_buildingInfo.mseId << " via websockets localPort="<localPort(); QMapIterator i(m_effectStates); while (i.hasNext()) { i.next(); DEBUG_LOG(m_logger) << "setEffectStates websocket send: " << i.value(); qint64 sendBytesCount = m_wsSocket->sendTextMessage(i.value()); DEBUG_LOG(m_logger) << "sendBytesCount= " << sendBytesCount; } m_wsReconnectTimer->stop(); } void BuildingProcess::onWebSocketDisconnected() { WARNING_LOG(m_logger) << "Websocket to " << m_wsUrl << " closed - trying to reconnect in 1 sec" << " (buildingId : " << m_buildingInfo.mseId << ") - socket: " << m_wsSocket << " localPort=" << m_wsSocket->localPort(); closeWebSocket(); m_wsReconnectTimer->start(1000); } } // namespace Buildings