Files
qt5-legatus-legacy/Lts/Proxies/Buildings/building_process.cpp
T
2026-08-13 18:33:10 +02:00

1216 lines
47 KiB
C++
Executable File

/**
* 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 <QStandardPaths>
#include <QFile>
#include <QTimer>
#include <QTransform>
#include <QWebSocket>
#include <QXmlStreamWriter>
#include <QJsonArray>
#include <QJsonDocument>
#include <ngltools/ngltools_filesystem.h>
//Lts
#include <LtsCoordTrans/lts_coord_trans.h>
#include <LtsUtilsCore/outdated_position.h>
#include <bcu_types.h>
#include <ProjectDataManagement/pdm_to_string.h>
#include <AssetManagement/asm.hpp>
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/<ProjectName>/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<BuildingControlUnit::Effect*> &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<int>(effectPtr->override);
if (effectPtr->effectType == 1) // Relay
{
m_effectOverridesReconnect.insert(effectPtr->id, static_cast<int>(effectPtr->override));
}
}
delete xmlReader;
// End effectConfig.xml
// disconnected in shutdown()
connect(&m_proc, QOverload<int, QProcess::ExitStatus>::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<int> &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: "<<bldId;
if (!m_wsSocket || !m_wsSocket->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<Buildings::FortifyFactor> &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<int, AssetManagement::RelayInfo> &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<int> allRelayIds;
for (auto it = m_relayInfos.keyBegin(); it != m_relayInfos.keyEnd(); ++it)
allRelayIds.insert(*it);
#else
QSet<int> 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<arrEgcus.size(); ++i)
{
QJsonObject objState = arrEgcus[i].toObject();
QStringList keys = objState.keys();
QString relayIdString = keys.at(0);
int relayId = relayIdString.toInt();
allRelayIds.remove(relayId);
int value = objState.value(relayIdString).toInt();
BuildingControlUnit::BcuEffectState bcuEffectState;
bcuEffectState.id = relayId;
bcuEffectState.type = BuildingControlUnit::BET_Relay; // so far only relays are handeled
if (relayId == 99) // Special Treatment for Sound
{
bcuEffectState.id = 1;
bcuEffectState.type = BuildingControlUnit::BET_BackgroundSound;
}
bcuEffectState.effectOverride = static_cast<BuildingControlUnit::BcuEffectOverride>(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<BuildingControlUnit::BcuEffectOverride>(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<Buildings::RelayStateItem> &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 <BuildingControlUnit::BcuRelayState>(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<int, BsmInfo> 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<BuildingControlUnit::Json::Telegrams::BsmState *>(tel));
else if (tel->telegramId() == BuildingControlUnit::Json::Telegrams::HitResult::telegramId())
handleHitResult(static_cast<BuildingControlUnit::Json::Telegrams::HitResult *>(tel));
else if (tel->telegramId() == BuildingControlUnit::Json::Telegrams::RelayState::telegramId())
handleRelayState(static_cast<BuildingControlUnit::Json::Telegrams::RelayState *>(tel));
else if (tel->telegramId() == BuildingControlUnit::Json::Telegrams::EffectOverride::telegramId())
handleEffectOverride(static_cast<BuildingControlUnit::Json::Telegrams::EffectOverride *>(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<int, ProjectDataManagement::BuildingChargeEffect> buildingCharges = m_mainHandler->pdmHandler()->buildingChargeEffects();
const QMap<int, ProjectDataManagement::ChargeType> 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<QAbstractSocket::SocketError>::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="<<m_wsSocket->localPort();
QMapIterator<int, QString> 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