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

470 lines
14 KiB
C++
Executable File

/**
* @RDE_HEADER@
* Handle incoming/outgoing data from/to all BSMs of one building
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2017-01-18
*/
#include "bsm_socket_handler.h"
#include "bsm_telegrams_base.h"
#include "bsm_telegrams_bit_timeout_request.h"
#include "bsm_telegrams_chopped_signal_flash.h"
#include "bsm_telegrams_configuration_request.h"
#include "bsm_telegrams_direct_fire.h"
#include "bsm_telegrams_observer_controller.h"
#include "bsm_telegrams_sw_version_report.h"
#include "bsm_telegrams_sw_version_request.h"
#include "bsm_telegrams_technical_status_request.h"
#include <QDateTime>
#include <QHostInfo>
namespace BuildingControlUnit
{
namespace BSM
{
static const qint64 s_opticalTimeSlot = 100;
SocketHandler::SocketHandler(QObject *parent)
: QObject(parent)
, m_logger(BuildingControlUnit::getLogger("BSM.SocketHandler"))
{
connect(&m_cleanupQueueTimer, &QTimer::timeout,
this, &SocketHandler::onCleanupQueue);
m_cleanupQueueTimer.setSingleShot(false);
m_cleanupQueueTimer.setInterval(10000);
m_cleanupQueueTimer.setTimerType(Qt::VeryCoarseTimer);
m_cleanupQueueTimer.start();
connect(&m_bsmStateTimer, &QTimer::timeout, this, &SocketHandler::onBsmStateTimerTimeOut);
m_bsmStateTimer.setSingleShot(true);
m_bsmStateTimer.setInterval(500);
m_bsmStateTimer.setTimerType(Qt::VeryCoarseTimer);
}
bool SocketHandler::init(const BuildingBsmConfig &bsmConfig)
{
// remove old sockets (should never happen)
qDeleteAll(m_sockets);
m_sockets.clear();
m_socketData.clear();
for (const Bsm &c : bsmConfig.bsmMap)
{
Socket *s = new Socket(c, this);//THIS
connect(s, &Socket::dataAvailable,
this, &SocketHandler::onDataAvailable);
connect(s, &Socket::disconnected,
this, &SocketHandler::onSocketDisconnected);
connect(s, &Socket::connected,
this, &SocketHandler::onSocketConnected);
m_sockets[c.id] = s;
}
return true;
}
bool SocketHandler::triggerFlashlight(int bsmId, const QString &hostname, int numberOfFlashes, int pauseBetweenFlashesSec)
{
Socket *s = hostname.isEmpty() ? getSocketForBsm(bsmId) : getSocketForBsm(hostname);
if (!s)
return false;
DEBUG_LOG(m_logger) << "triggerFlashlight BSM Socket with socketId " << bsmId;
Telegrams::PanChoppedSignalFlashlight flash;
flash.m_noOfFlashes = qBound(0, numberOfFlashes, 255);
flash.m_pauseDuration = qBound(1, pauseBetweenFlashesSec, 10);
flash.m_flashDuration = 1;
return s->sendData(flash.toBytestream());
}
bool SocketHandler::triggerFlashlightPulse(int bsmId, const QString &hostname,
int numberOfFlashes,
int pauseDuration,
int choppedConfiguration,
int flashDuration)
{
Socket *s = hostname.isEmpty() ? getSocketForBsm(bsmId) : getSocketForBsm(hostname);
if (!s)
return false;
DEBUG_LOG(m_logger) << "triggerFlashlightPulse BSM Socket with socketId " << bsmId
<< " hostname=" << hostname
<< " numberOfFlashes=" << numberOfFlashes
<< " pauseDuration=" << pauseDuration
<< " choppedConfiguration=" << choppedConfiguration
<< " flashDuration=" << flashDuration;
Telegrams::PanChoppedSignalFlashlight flash;
flash.m_noOfFlashes = qBound(0, numberOfFlashes, 255);
flash.m_pauseDuration = qBound(1, pauseDuration, 10);
flash.m_choppedFlashConfig = qBound(2, choppedConfiguration, 10);
flash.m_flashDuration = flashDuration;
return s->sendData(flash.toBytestream());
}
bool SocketHandler::triggerFlashlights(int numberOfFlashes,
int pauseDuration,
int choppedConfiguration,
int flashDuration)
{
int cntSocketsActive = 0;
Telegrams::PanChoppedSignalFlashlight flash;
flash.m_noOfFlashes = qBound(0, numberOfFlashes, 255);
flash.m_pauseDuration = qBound(1, pauseDuration, 10);
flash.m_choppedFlashConfig = qBound(2, choppedConfiguration, 10);
flash.m_flashDuration = flashDuration;
const QByteArray data = flash.toBytestream();
for (Socket *s : qAsConst(m_sockets))
{
if (s->isConnected())
{
if (s->sendData(data))
++cntSocketsActive;
}
else
WARNING_LOG(m_logger) << "BSM " << s->bsmConfig().id
<< " is not connected - can not activate flash light";
}
if (cntSocketsActive == 0)
{
WARNING_LOG(m_logger) << "No active bsm - can not trigger flash light for all bsm";
return false;
}
return true;
}
bool SocketHandler::getBsmConfiguration(int bsmId)
{
Socket *s = getSocketForBsm(bsmId);
if (!s)
return false;
Telegrams::PanConfigurationRequest req;
return s->sendData(req.toBytestream());
}
bool SocketHandler::getBsmBitTimeout(int bsmId)
{
Socket *s = getSocketForBsm(bsmId);
if (!s)
return false;
Telegrams::PanBitTimeoutRequest req;
return s->sendData(req.toBytestream());
}
bool SocketHandler::getBsmTechnicalStatus(int bsmId)
{
Socket *s = getSocketForBsm(bsmId);
if (!s)
return false;
Telegrams::PanTechnicalStatusRequest req;
return s->sendData(req.toBytestream());
}
QVector<BsmState> SocketHandler::bsmStates() const
{
QVector<BsmState> ret;
ret.reserve(m_sockets.size());
for (const Socket *s : m_sockets)
{
BsmState bs(s->bsmConfig());
bs.bConnected = s->isConnected();
bs.versionInfo = s->versionInfo();
ret += bs;
}
return ret;
}
void SocketHandler::onBsmStateTimerTimeOut()
{
emit bsmStateChanged(bsmStates());
}
void SocketHandler::onDataAvailable(int id, const QByteArray &data)
{
m_socketData[id] = parseBuffer(id, m_socketData[id] + data);
}
void SocketHandler::onSocketConnected(int id)
{
requestVersion(id); // hopefully 500ms are enough for a version request
DEBUG_LOG(m_logger) << "BSM Socket with id " << id << " connected";
m_bsmStateTimer.start();
}
void SocketHandler::onSocketDisconnected(int id)
{
DEBUG_LOG(m_logger) << "BSM Socket with id " << id << " disconnected";
m_socketData[id].clear();
m_bsmStateTimer.start();
}
void SocketHandler::onCleanupQueue()
{
if (m_lastShots.isEmpty())
return;
QVector<OpticalShot> activeShots;
qint64 tNow = QDateTime::currentMSecsSinceEpoch();
for (const OpticalShot &os : qAsConst(m_lastShots))
{
if (os.timeStampMSec < tNow - s_opticalTimeSlot)
continue;
activeShots += os;
}
m_lastShots = activeShots;
}
Socket *SocketHandler::getSocketForBsm(int bsmId)
{
Socket *s = m_sockets.value(bsmId);
if (!s)
{
WARNING_LOG(m_logger) << "Got invalid bsm id (" << bsmId << ") to trigger flashlight";
return nullptr;
}
if (!s->isConnected())
{
WARNING_LOG(m_logger) << "Bsm with id " << bsmId
<< " is not connected - can not activate flash light";
return nullptr;
}
return s;
}
Socket *SocketHandler::getSocketForBsm(const QString &hostname)
{
const QHostInfo hi = QHostInfo::fromName(hostname);
const auto addresses = hi.addresses();
if (addresses.isEmpty())
return nullptr;
for (const QHostAddress &ha : addresses)
{
if (ha.isLoopback())
continue;
for (Socket *s : qAsConst(m_sockets))
{
if (s->peerAddress() == ha)
return s;
}
}
return nullptr;
}
QByteArray SocketHandler::parseBuffer(int id, const QByteArray &buffer)
{
int iEndPos = 0;
int iCurReadPos = 0;
while (findNextSyncStartByte(buffer, iCurReadPos))
{
const int iStartPos = iCurReadPos;
if (!findNextSyncEndByte(buffer, iCurReadPos))
break;
iEndPos = iCurReadPos;
parseBsmTelegram(id, buffer.mid(iStartPos + 1, iEndPos - iStartPos - 1));
}
return buffer.mid(iEndPos);
}
void SocketHandler::parseBsmTelegram(int id, const QByteArray &baTele)
{
Telegrams::Telegram *tele = Telegrams::Telegram::telegramFromBytestream(baTele);
if (!tele)
return;
if (tele->telId() != Telegrams::PanTelegramId::ALIVE)
DEBUG_LOG(m_logger) << "Got tele from bsm with id " << id << ": " << tele->print();
switch (tele->telId())
{
case Telegrams::PanTelegramId::ALIVE:
{
Socket *s = m_sockets.value(id);
if (s)
s->gotAlive();
break;
}
case Telegrams::PanTelegramId::REQ_BIT_TIMEOUT:
case Telegrams::PanTelegramId::BIT_TIMEOUT_REPORT:
case Telegrams::PanTelegramId::REQ_SW_VERSION:
case Telegrams::PanTelegramId::CONFIGURATION_SETUP:
case Telegrams::PanTelegramId::REQ_CONFIGURATION:
case Telegrams::PanTelegramId::REQ_TECHNICAL_STATUS:
case Telegrams::PanTelegramId::TECHNICAL_STATUS:
case Telegrams::PanTelegramId::SIGNAL_FLASHLIGHT:
case Telegrams::PanTelegramId::CHOPPED_SIGNAL_FLASH:
break;
case Telegrams::PanTelegramId::CONFIGURATION_REPORT:
// request version
//requestVersion(id);
break;
case Telegrams::PanTelegramId::SW_VERSION_REPORT:
{
Telegrams::PanSwVersionReport *rep = static_cast<Telegrams::PanSwVersionReport *>(tele);
Socket *s = m_sockets.value(id);
if (s)
s->setVersionInformation(rep->m_mainVersion,
rep->m_subVersion,
rep->m_revision,
QString::fromLatin1(rep->m_manufacturer));
break;
}
case Telegrams::PanTelegramId::ER_DIRECT_FIRE:
handleDirectFire(id, static_cast<Telegrams::PanDirectFire *>(tele));
break;
case Telegrams::PanTelegramId::ER_OBSERVER_CONTR:
handleObserverController(id, static_cast<Telegrams::PanObserverController *>(tele));
break;
}
delete tele;
}
bool SocketHandler::requestVersion(int id)
{
DEBUG_LOG(m_logger) << "Requesting version for BSM " << id;
Socket *s = m_sockets.value(id);
if (!s || !s->isConnected())
return false;
Telegrams::PanSwVersionRequest tele;
return s->sendData(tele.toBytestream());
}
void SocketHandler::handleDirectFire(int id, const Telegrams::PanDirectFire *tele)
{
if (!tele)
return;
if (tele->m_ammunitionId == 0)
{
WARNING_LOG(m_logger) << "Got direct fire without valid ammunition - ignoring: " << tele->print();
return;
}
OpticalShot osNew;
osNew.id = id;
osNew.ammunitionId = tele->m_ammunitionId;
osNew.originatorId = tele->m_originatorId;
osNew.deposition1 = tele->m_deposition1;
osNew.deposition2 = tele->m_deposition2;
osNew.timeStampMSec = QDateTime::currentMSecsSinceEpoch();
m_lastShots += osNew;
QTimer::singleShot(500, this, &SocketHandler::handleDirectFireSummray);
}
void SocketHandler::handleDirectFireSummray()
{
DEBUG_LOG(m_logger) << "handleDirectFireSummray m_lastShots.size=" << m_lastShots.size();
OpticalShot osNew;
if ( m_lastShots.size() == 0)
return;
int distance = 3000; // Unrealistisch hohe Distanz
DEBUG_LOG(m_logger) << "distance=" << distance;
for (const OpticalShot &os : qAsConst(m_lastShots))
{
int act_distance = os.deposition1 * os.deposition1 + os.deposition2 * os.deposition2;
DEBUG_LOG(m_logger) << "act_distance=" << act_distance << " BSM-ID=" << os.id
<< " ammunitionId=" << os.ammunitionId;
if ( act_distance < distance)
{
distance = act_distance;
DEBUG_LOG(m_logger) << "distance=" << distance << " BSM-ID=" << os.id;
osNew = os;
}
}
emit opticalShot(QDateTime::currentMSecsSinceEpoch(),
osNew.id,
osNew.originatorId,
osNew.ammunitionId,
osNew.deposition1,
osNew.deposition2);
m_lastShots.clear();
}
void SocketHandler::handleObserverController(int id, const Telegrams::PanObserverController *tele)
{
DamageState damageState = DamageState::Unknown;
if (tele->m_codeNo == 2) // Zerstoerung
damageState = DamageState::Destroyed;
else if (tele->m_codeNo == 4 || tele->m_codeNo == 18 || tele->m_codeNo == 28) // Aktivierung, Reaktivierung
damageState = DamageState::NoDamage;
if (damageState == DamageState::Unknown)
return;
if (m_timerOC.isValid())
{
if (m_timerOC.elapsed() < 500) // < 500ms -> ignore event
return;
}
m_timerOC.start();
emit opticalShotOC(QDateTime::currentMSecsSinceEpoch(),
id,
tele->m_originatorId,
tele->m_ammunitionId,
damageState);
}
bool SocketHandler::findNextSyncStartByte(const QByteArray &buffer, int &iCurReadPos)
{
// Suchen nach s_stx
while (iCurReadPos < buffer.size())
{
const char data = buffer.at(iCurReadPos);
if (data == Telegrams::Telegram::s_stx)
return true;
++iCurReadPos;
}
return false;
}
bool SocketHandler::findNextSyncEndByte(const QByteArray &buffer, int &iCurReadPos)
{
// Suchen nach s_endMark
while (iCurReadPos < buffer.size())
{
const char data = buffer.at(iCurReadPos);
if (data == Telegrams::Telegram::s_etx)
return true;
++iCurReadPos;
}
return false;
}
static inline bool isSameAmmuniton(int a1, int a2)
{
if (a1 == a2 - 1)
return true;
if (a1 == a2)
return true;
if (a1 == a2 + 1)
return true;
return false;
}
static inline bool isSameOriginator(int o1, int o2)
{
if (o1 == o2)
return true;
// o1 == 0 || o2 == 0 is filtered out before
if (o1 == 0)
return true;
if (o2 == 0)
return true;
return false;
}
bool SocketHandler::isSameOpticalShot(const OpticalShot &o1, const OpticalShot &o2)
{
// mehr als 100ms auseinander -> nicht der gleiche Treffer
if (o1.timeStampMSec < o2.timeStampMSec - s_opticalTimeSlot)
return false;
return (isSameAmmuniton(o1.ammunitionId, o2.ammunitionId) &&
isSameOriginator(o1.originatorId, o2.originatorId));
}
} // namespace BSM
} // namespace BuildingControlUnit