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

528 lines
16 KiB
C++
Executable File

/**
* Convert from/to WLS protocol / handler incoming WLS connection
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2017-01-10
*/
#include "wls_handler.h"
#include "main_handler.h"
#include "settings.h"
#include "wls_sfm_message_converter.h"
#include "web_socket_handler.h"
#include <QTcpSocket>
#include <QDateTime>
#include <web_socket_handler.h>
namespace WirelessLocationSystem
{
static constexpr unsigned char s_startMark = 0x7F;
static constexpr unsigned char s_endMark = 0x8F;
static inline QByteArray escapeData(const QByteArray &bufferData)
{
QByteArray ret;
ret.reserve(bufferData.size() * 6 / 5);
for (const unsigned char c : bufferData)
{
if (c >= 0x20 && c < 0x7f)
ret += c;
else
ret += "\\x" + QByteArray::number(static_cast<unsigned char>(c), 16);
}
return ret;
}
WlsHandler::WlsHandler(QObject *parent)
: QObject(parent)
, m_logger(getLogger("WlsHandler"))
{
if (Settings::wlsMgrsBasePoint().size() <= 15)
WARNING_LOG(m_logger) << "WLS basepoint is not defined precise enough: " << Settings::wlsMgrsBasePoint();
}
WlsHandler::~WlsHandler()
{
delete m_tcpServer;
}
bool WlsHandler::init(WlsParticipantHandler *participantHandler,
WebSocketHandler *wsHandler,
quint16 listenPort)
{
m_participantHandler = participantHandler;
m_wsHandler = wsHandler;
INFO_LOG(m_logger) << "WlsHandler::init";
delete m_tcpServer;
m_tcpServer = new QTcpServer(this);
connect(m_tcpServer, &QTcpServer::newConnection,
this, &WlsHandler::onIncomingConnection);
if (!m_tcpServer->listen(QHostAddress::Any, listenPort))
{
WARNING_LOG(m_logger) << "Error listening on port " << listenPort;
return false;
}
m_wlsZOffset = Settings::wlsZOffset();
return true;
}
bool WlsHandler::sendWlsMessage(const WlsParticipant *p, const SfmMessage_tx &msg)
{
return sendWlsMessage(p->tagId(), msg);
}
bool WlsHandler::sendWlsMessage(const QByteArray &tagId, const SfmMessage_tx &msg)
{
return sendWlsMessage(tagId, WlsTelegramId::PayloadHostToMobile, SfmMessageConverter::generateTxMessage(msg));
}
bool WlsHandler::sendWlsMessage(const QByteArray &tagId, WlsTelegramId telId, const QByteArray &payload)
{
if (!isConnected())
return false;
if (telId != WlsTelegramId::PayloadHostToMobile)
return false;
auto itSocket = m_tcpSocketByTagId.constFind(tagId);
if (itSocket == m_tcpSocketByTagId.constEnd())
{
ERROR_LOG(m_logger) << "Can't find a TCP-socket for tag id: " << tagId;
return false;
}
const unsigned int fragmentParts = ((payload.size() + s_maxLenTransparentData - 1) / s_maxLenTransparentData);
const QDateTime dt = QDateTime::currentDateTimeUtc();
QByteArray data;
// start/end mark, 3*';', telId (can be 2 digits), timestring, \0
data.reserve(2 + 3 + 2 + 23 + tagId.size() + s_maxLenTransparentData + 1);
for (unsigned int i = 0; i < fragmentParts; i++)
{
data.clear();
data.append(s_startMark); // Startkennung
data.append(QByteArray::number(static_cast<int>(telId)));
data.append(';'); // Trennzeichen
data.append(dt.toString(QStringLiteral("yyyy-MM-dd hh:mm:ss.zzz")).toLatin1()); // Aktuelle Zeit
data.append(';'); // Trennzeichen
data.append(tagId); // Tag-ID
data.append(';'); // Trennzeichen
data.append(payload.mid(i * s_maxLenTransparentData, s_maxLenTransparentData));
data.append(s_endMark); // Endekennung
(*itSocket)->write(data);
DEBUG_LOG(m_logger) << "Sending telegram to wls: " << escapeData(data);
}
(*itSocket)->flush();
return true;
}
void WlsHandler::onIncomingConnection()
{
INFO_LOG(m_logger) << "WlsHandler::onIncomingConnection";
QTcpSocket *tcpSocket = m_tcpServer->nextPendingConnection();
if (!tcpSocket)
return;
m_tcpSockets[tcpSocket].clear();
connect(tcpSocket, &QTcpSocket::readyRead,
this, &WlsHandler::onIncomingData);
connect(tcpSocket, &QTcpSocket::disconnected,
this, &WlsHandler::onSocketDisconnected);
// send wls connection state to all web sockets
TrainingAreaBase::TetraIcd::ProxyConnectionState connState;
connState.connectedToSystem = true;
m_wsHandler->sendTelegram(&connState, 0);
}
void WlsHandler::onIncomingData()
{
QTcpSocket *s = qobject_cast<QTcpSocket *>(sender());
if (!s)
return;
auto &tcpBuffer = m_tcpSockets[s];
const QByteArray bufferData = tcpBuffer + s->readAll();
TRACE_LOG(m_logger) << "WlsHandler::onIncomingData bufferData='" << escapeData(bufferData) << "'";
// returns the unparsed part of the buffer
tcpBuffer = parseBuffer(bufferData, s);
}
void WlsHandler::onSocketDisconnected()
{
QTcpSocket *s = qobject_cast<QTcpSocket *>(sender());
if (!s)
return;
INFO_LOG(m_logger) << "Socket disconnected";
m_tcpSockets.remove(s);
// remove tag ID to socket mappings
auto it = m_tcpSocketByTagId.begin();
while (it != m_tcpSocketByTagId.end())
if (it.value() == s)
it = m_tcpSocketByTagId.erase(it);
else
it++;
s->deleteLater();
// send wls connection state to all web sockets
TrainingAreaBase::TetraIcd::ProxyConnectionState connState;
connState.connectedToSystem = !m_tcpSockets.isEmpty();
m_wsHandler->sendTelegram(&connState, 0);
}
QByteArray WlsHandler::parseBuffer(const QByteArray &buffer, QTcpSocket *socket)
{
int iEndPos = 0;
int iCurReadPos = 0;
while (findNextSyncStartByte(buffer, iCurReadPos))
{
const int iStartPos = iCurReadPos;
if (!findNextSyncEndByte(buffer, iCurReadPos))
break;
iEndPos = iCurReadPos;
parseWlsTelegram(buffer.mid(iStartPos + 1, iEndPos - iStartPos - 1), socket);
}
return buffer.mid(iEndPos);
}
void WlsHandler::parseWlsTelegram(const QByteArray &baTele, QTcpSocket *socket)
{
const QList<QByteArray> parts = baTele.split(';');
if (parts.size() < 3)
return;
// insert/update socket for tagId
m_tcpSocketByTagId.insert(parts.at(2), socket);
const WlsTelegramId telegramId = static_cast<WlsTelegramId>(parts.at(0).toInt());
const QDateTime timestamp = QDateTime::fromString(QString::fromUtf8(parts.at(1)), QStringLiteral("yyyy-MM-dd hh:mm:ss.zzz"));
if (telegramId != WlsTelegramId::PlayerPosition)
DEBUG_LOG(m_logger) << "Handling " << telegramId
<< ", data: '" << escapeData(baTele) << "'";
bool bOk = true;
switch (telegramId)
{
case WlsTelegramId::VersionInformation:
bOk = handleVersionsinformation(parts);
break;
case WlsTelegramId::PlayerPosition:
bOk = handlePlayerPosition(parts, timestamp);
break;
case WlsTelegramId::PlayerState:
bOk = handlePlayerState(parts);
break;
case WlsTelegramId::PayloadMobileToHost:
bOk = handleDataPayloadMobileToHost(parts);
break;
case WlsTelegramId::PayloadHostToMobile:
WARNING_LOG(m_logger) << "Protocol error - got a message which should not be sent from Wls (PayloadHostToMobile)";
break;
}
if (!bOk)
WARNING_LOG(m_logger) << "Error handling telegram: '" << baTele << "'";
}
bool WlsHandler::findNextSyncStartByte(const QByteArray &buffer, int &iCurReadPos)
{
// Suchen nach s_startMark
while (iCurReadPos < buffer.size())
{
const unsigned char data = buffer.at(iCurReadPos);
if (data == s_startMark)
return true;
++iCurReadPos;
}
return false;
}
bool WlsHandler::findNextSyncEndByte(const QByteArray &buffer, int &iCurReadPos)
{
// Suchen nach s_endMark
while (iCurReadPos < buffer.size())
{
const unsigned char data = buffer.at(iCurReadPos);
if (data == s_endMark)
return true;
++iCurReadPos;
}
return false;
}
bool WlsHandler::handleVersionsinformation(const QList<QByteArray> &data)
{
if (data.size() < 4)
return false;
DEBUG_LOG(m_logger) << "TX(WLS): Version = " << data.at(3);
return true;
}
bool WlsHandler::handlePlayerPosition(const QList<QByteArray> &data, const QDateTime &timestamp)
{
if (data.size() < 6)
return false;
WlsParticipant *p = participantFromTagId(data.at(2));
WlsParticipant::RelativePosition pos;
int iPos = 3;
if (!parseDoubleCm(data.at(iPos++), pos.x))
return false;
if (!parseDoubleCm(data.at(iPos++), pos.y))
return false;
if (!parseDoubleCm(data.at(iPos++), pos.z))
return false;
if ((data.size() > iPos) && !parseInteger(data.at(iPos++), pos.wlsAreaId))
return false;
if ((data.size() > iPos) && !parseInteger(data.at(iPos++), pos.wlsBuildingId))
return false;
if ((data.size() > iPos) && !parseInteger(data.at(iPos++), pos.wlsRoomNumber))
return false;
if ((data.size() > iPos) && !parseInteger(data.at(iPos++), pos.playerOrientation))
return false;
if ((data.size() > iPos) && !parseInteger(data.at(iPos++), pos.weaponOrientation))
return false;
pos.z += m_wlsZOffset;
p->setPosition(pos, timestamp.toMSecsSinceEpoch());
INFO_LOG(m_logger) << p->getPlayerIdStr() << ": handlePlayerPosition"
<< " x=" << pos.x << ", y=" << pos.y << ", z=" << pos.z
<< ", wlsAreaId=" << pos.wlsAreaId
<< ", wlsBuildingId=" << pos.wlsBuildingId
<< ", wlsRoomNumber=" << pos.wlsRoomNumber
<< ", zone=" << p->wlsZone()
<< ", gpsMode=" << p->gpsMode()
<< ", dataCommunicationPossible: " << p->dataCommunication()
<< ", " << timestamp.toString();
return true;
}
bool WlsHandler::handlePlayerState(const QList<QByteArray> &data)
{
if (data.size() < 6)
return false;
WlsParticipant *p = participantFromTagId(data.at(2));
int timeout;
if (!parseInteger(data.at(3), timeout))
return false;
int tacticalState;
if (!parseInteger(data.at(4), tacticalState))
return false;
int voltage;
if (!parseInteger(data.at(5), voltage))
return false;
p->voltage = voltage / 10.; // 1/10V
INFO_LOG(m_logger) << p->getPlayerIdStr() << ": handlePlayerState"
<< " timeout=" << timeout
<< " ,tacticalState=" << tacticalState
<< " ,voltage=" << p->voltage;
return true;
}
bool WlsHandler::checkPayloadIsHex(const QByteArray &payload) const
{
for (const char c : payload)
{
if ((c < '0' || c > '9') &&
(c < 'a' || c > 'f') &&
(c < 'A' || c > 'F'))
{
WARNING_LOG(m_logger) << "Payload data is not hex coded ...";
return false;
}
}
return true;
}
bool WlsHandler::handleDataPayloadMobileToHost(const QList<QByteArray> &data)
{
if (data.size() < 4)
return false;
const QByteArray &payload = data.at(3);
const QByteArray &tagId = data.at(2);
if (!checkPayloadIsHex(payload))
return false;
QSharedPointer<SfmMessage_rx> msg = SfmMessageConverter::generateRxMessage(payload);
if (!msg)
{
WARNING_LOG(m_logger) << "Can not convert payload to SfmMessage ... ";
return false;
}
WlsParticipant *p = participantFromTagId(tagId, msg->getId() != SfmMessage::MsgId::EquipmentId);
p->setLastSeenWlsTelegramTime(QDateTime::currentMSecsSinceEpoch());
switch (msg->getId())
{
case SfmMessage::MsgId::EquipmentId:
handlePayload(p, static_cast<SfmMsgEquipmentId *>(msg.data()));
break;
case SfmMessage::MsgId::RequestGpsControl:
handlePayload(p, static_cast<SfmMsgRequestGpsControl_rx *>(msg.data()));
break;
case SfmMessage::MsgId::SfmRestartNotification:
handlePayload(p, static_cast<SfmMsgSfmRestart_rx *>(msg.data()));
break;
case SfmMessage::MsgId::Acknowledge:
handlePayload(p, static_cast<Acknowledge_rx *>(msg.data()));
break;
case SfmMessage::MsgId::TetraMessage:
handlePayload(p, static_cast<SfmMsgTetra_rx *>(msg.data()));
break;
case SfmMessage::MsgId::LicenseState:
case SfmMessage::MsgId::TacticalState:
default:
WARNING_LOG(m_logger) << "RX(WLS): MessageID(" << static_cast<int>(msg->getId()) << ")";
break;
}
return true;
}
void WlsHandler::handlePayload(WlsParticipant *participant, const SfmMsgEquipmentId *sfmTele)
{
const int equipmentNumber = sfmTele->getEquipmentNumber();
INFO_LOG(m_logger) << participant->getPlayerIdStr() << ": handlePayload"
<< " - new equipmentNumber = " << equipmentNumber;
if (participant->equipmentNumber() == 0 ||
participant->equipmentNumber() != equipmentNumber)
{
m_participantHandler->setEquipmentNumber(participant, equipmentNumber);
}
}
void WlsHandler::handlePayload(WlsParticipant *participant, const SfmMsgRequestGpsControl_rx *sfmTele)
{
Q_UNUSED(sfmTele);
sendGpsMode(participant);
}
void WlsHandler::handlePayload(WlsParticipant *participant, const SfmMsgSfmRestart_rx *sfmTele)
{
WARNING_LOG(m_logger) << "Restart notification ... " << participant->tagId();
Q_UNUSED(sfmTele)
participant->setWlsZone(-1);
// TODO: noch EXCON informieren?
}
void WlsHandler::handlePayload(WlsParticipant *participant, const Acknowledge_rx *sfmTele)
{
participant->handleWlsAck(sfmTele);
}
void WlsHandler::handlePayload(WlsParticipant *participant, const SfmMsgTetra_rx *sfmTele)
{
Acknowledge_tx msg;
if (!participant->handleSfmRxData(sfmTele))
msg.setFragmentError(participant->nextSfmRxDataFragmentCounter());
msg.setRecvCounter(sfmTele->sendCounter());
sendWlsMessage(participant->tagId(), msg);
}
void WlsHandler::sendGpsMode(WlsParticipant *participant)
{
if (!participant)
return;
SfmMsgGpsControl_tx msg;
msg.setGpsOn(participant->isGpsMode());
INFO_LOG(m_logger) << participant->getPlayerIdStr() << ": sendGpsMode"
<< " GpsMode = " << participant->isGpsMode();
sendWlsMessage(participant->tagId(), msg);
}
bool WlsHandler::parseDoubleCm(const QByteArray &data, double &num) const
{
bool bOk;
num = data.toDouble(&bOk);
if (!bOk)
{
WARNING_LOG(m_logger) << "Error parsing floating point number '" << data << "'";
return false;
}
num /= 100; // cm -> m
return true;
}
bool WlsHandler::parseInteger(const QByteArray &data, int &num) const
{
bool bOk;
num = data.toInt(&bOk);
if (!bOk)
{
WARNING_LOG(m_logger) << "Error parsing number '" << data << "'";
return false;
}
return true;
}
WlsParticipant *WlsHandler::participantFromTagId(const QByteArray &tagId, bool bAskForEqIdIfNew)
{
WlsParticipant *p = m_participantHandler->participantByTagId(tagId);
bool bRequestEquipmentNumber = false;
const qint64 tNow = QDateTime::currentMSecsSinceEpoch();
if (!p)
{
p = m_participantHandler->createNewParticipant(tagId);
bRequestEquipmentNumber = bAskForEqIdIfNew;
}
else
{
// Wenn noch keine EquipmentNumber gekommen ist oder die letzte Anfrage dazu älter als 10 Sekunden ist
if (p->equipmentNumber() <= 0 && p->lastEquipmentNumberRequest < tNow - 10 * 1000)
bRequestEquipmentNumber = true;
}
if (bRequestEquipmentNumber)
{
// TODO: wirklich nur wenn der Teilnehmer noch nicht vorhanden ist?
// was, wenn wir das Antworttelegramm nicht bekommen? Dann wird aktuell nie eine AusstgNr hinzugefügt
// -> Manfred Gronak fragen
SfmMsgRequestEquipmentId_tx getEqId;
INFO_LOG(m_logger) << "Requesting Equipment number for tag " << tagId;
sendWlsMessage(tagId, getEqId);
p->lastEquipmentNumberRequest = tNow;
}
return p;
}
std::ostream &operator<<(std::ostream &stream, WlsHandler::WlsTelegramId ti)
{
switch (ti)
{
case WlsHandler::WlsTelegramId::VersionInformation:
return stream << "WlsTelegramId::VersionInformation";
case WlsHandler::WlsTelegramId::PlayerPosition:
return stream << "WlsTelegramId::PlayerPosition";
case WlsHandler::WlsTelegramId::PlayerState:
return stream << "WlsTelegramId::PlayerState";
case WlsHandler::WlsTelegramId::PayloadMobileToHost:
return stream << "WlsTelegramId::PayloadMobileToHost";
case WlsHandler::WlsTelegramId::PayloadHostToMobile:
return stream << "WlsTelegramId::PayloadHostToMobile";
}
return stream << "Unknown WlsTelegramId (" << std::to_string(int(ti)) << ")";
}
} // namespace WirelessLocationSystem