465 lines
15 KiB
C++
Executable File
465 lines
15 KiB
C++
Executable File
/**
|
|
* A single Participant
|
|
* $Id$
|
|
* $HeadURL$
|
|
* $Author$
|
|
* Created by Christian Ehrlicher, SLSE, 2017-01-10
|
|
*/
|
|
|
|
#include "wls_participant.h"
|
|
#include "wls_sfm_message.h"
|
|
#include "wls_handler.h"
|
|
#include "settings.h"
|
|
#include <QDateTime>
|
|
#include <LtsUtilsCore/dds_vector.h>
|
|
#include <LtsUtilsCore/sfm_bytemessage_converter.h>
|
|
|
|
static constexpr qint64 s_dataCommunicationAliveTimeout = 5 * 1000;
|
|
static constexpr int s_maxWlsRetryCount = 3;
|
|
|
|
namespace WirelessLocationSystem
|
|
{
|
|
WlsParticipant::WlsParticipant(QObject *parent)
|
|
: QObject(parent)
|
|
, m_wlsTeleTimeoutTimer(new QTimer(this))
|
|
, m_logger(getLogger("WlsParticipant"))
|
|
{
|
|
m_wlsTeleTimeoutTimer->setSingleShot(true);
|
|
m_wlsTeleTimeoutTimer->setTimerType(Qt::VeryCoarseTimer);
|
|
m_wlsTeleTimeoutTimer->setInterval(s_dataCommunicationAliveTimeout);
|
|
connect(m_wlsTeleTimeoutTimer, &QTimer::timeout, this, &WlsParticipant::onWlsTeleTimeout);
|
|
}
|
|
|
|
QByteArray WlsParticipant::getPlayerIdStr() const
|
|
{
|
|
QByteArray ret;
|
|
if (m_equipmentNumber > 0)
|
|
ret = QByteArray::number(m_equipmentNumber);
|
|
else
|
|
ret = "unknown";
|
|
if (m_tagId.isEmpty())
|
|
ret += "(unknown tag id)";
|
|
else
|
|
ret += "(" + m_tagId + ")";
|
|
return ret;
|
|
}
|
|
|
|
void WlsParticipant::setEquipmentNumber(int newEquipmentNumber)
|
|
{
|
|
if (m_equipmentNumber == newEquipmentNumber)
|
|
return;
|
|
m_equipmentNumber = newEquipmentNumber;
|
|
|
|
emit dataCommunicationStateChanged(this);
|
|
emit positionChanged(this);
|
|
}
|
|
|
|
void WlsParticipant::setPosition(const RelativePosition &newPosition,
|
|
qint64 tsCurrentPos)
|
|
{
|
|
if (tsCurrentPos == m_lastSeenPosition ||
|
|
(((tsCurrentPos - m_lastSeenPosition) <= Settings::deadPosTimeout()) && comparePos(m_relativePosition, newPosition)))// same timestamp or same pos for 5 sec
|
|
{
|
|
DEBUG_LOG(m_logger) << "Check for pos Update";
|
|
DEBUG_LOG(m_logger) << "Is Outdated= " <<((tsCurrentPos - m_lastSeenPosition) <= Settings::deadPosTimeout());
|
|
|
|
if (((tsCurrentPos - m_lastSeenPosition) <= Settings::deadPosTimeout()) && comparePos(m_relativePosition, newPosition))
|
|
{
|
|
DEBUG_LOG(m_logger) << "Same Position or not older than 5000msecs " << tsCurrentPos - m_lastSeenPosition;
|
|
return;
|
|
}
|
|
const qint64 tsNow = QDateTime::currentMSecsSinceEpoch();
|
|
if (tsCurrentPos == m_lastSeenPosition && m_lastValidPosition + 9 * 60 * 1000 > tsNow) // younger than 9 min -> ignore //should never be true as agilion always send with new timestamp
|
|
return; // position did not change
|
|
|
|
// did not move in the last 9min, send a new position report to excon
|
|
m_lastValidPosition = tsNow;
|
|
if (isDataCommunicationPossible())
|
|
emit positionChanged(this);
|
|
|
|
return;
|
|
}
|
|
|
|
const int newWlsZone = WlsParticipant::calcWlsZone(newPosition.wlsAreaId,
|
|
newPosition.wlsBuildingId,
|
|
newPosition.wlsRoomNumber);
|
|
setWlsZone(newWlsZone);
|
|
|
|
// to create a new position report on restart / after 9 min timeout
|
|
m_lastSeenPosition = tsCurrentPos;
|
|
m_lastValidPosition = tsCurrentPos;
|
|
|
|
m_relativePosition = newPosition;
|
|
m_relativePosition.isValid = true;
|
|
|
|
if (isDataCommunicationPossible())
|
|
emit positionChanged(this);
|
|
}
|
|
|
|
void WlsParticipant::checkGpsOnOffTimeoutTimer(qint64 tNow)
|
|
{
|
|
// full blown tetra mode -> nothing to do
|
|
if (m_gpsMode == GpsMode::On ||
|
|
m_dataCommunication == DataCommunication::Off)
|
|
return;
|
|
if (m_lastSeenDataCommunication > tNow - s_maxWlsRetryCount * s_dataCommunicationAliveTimeout)
|
|
return;
|
|
|
|
DEBUG_LOG(m_logger) << getPlayerIdStr() << ": lastSeenDataCommunication: "
|
|
<< m_lastSeenDataCommunication << ", now: "
|
|
<< (tNow - s_maxWlsRetryCount * s_dataCommunicationAliveTimeout)
|
|
<< " (" << tNow << "), "
|
|
<< m_dataCommunication << ", "
|
|
<< m_gpsMode;
|
|
|
|
// no alive from participant -> switch back to
|
|
if (m_dataCommunication == DataCommunication::On)
|
|
{
|
|
m_dataCommunication = DataCommunication::Off;
|
|
emit dataCommunicationStateChanged(this);
|
|
}
|
|
if (m_gpsMode == GpsMode::Off)
|
|
{
|
|
m_gpsMode = GpsMode::On;
|
|
emit gpsOnOffChanged(this);
|
|
}
|
|
m_wlsZone = 0;
|
|
}
|
|
|
|
void WlsParticipant::setLastSeenWlsTelegramTime(qint64 ts)
|
|
{
|
|
m_lastSeenDataCommunication = ts;
|
|
// current only allowed when wlsZone > 3
|
|
// m_relativePosition.dataCommunication = WlsParticipant::DataCommunication::On;
|
|
}
|
|
|
|
void WlsParticipant::setWlsZone(int newWlsZone)
|
|
{
|
|
const auto oldGpsMode = m_gpsMode;
|
|
const auto oldDataComm = m_dataCommunication;
|
|
if (newWlsZone >= 0) // < 0 == restart
|
|
{
|
|
if (WlsParticipant::mustSendGpsOff(m_wlsZone, newWlsZone))
|
|
m_gpsMode = GpsMode::Off;
|
|
else if (WlsParticipant::mustSendGpsOn(m_wlsZone, newWlsZone))
|
|
m_gpsMode = GpsMode::On;
|
|
|
|
m_dataCommunication = (newWlsZone > 3)
|
|
? DataCommunication::On
|
|
: DataCommunication::Off;
|
|
INFO_LOG(m_logger) << getPlayerIdStr() << ": wlsZone old: " << m_wlsZone << ", new: " << newWlsZone
|
|
<< ", new gps mode: " << m_gpsMode
|
|
<< ", new data comm mode: " << m_dataCommunication
|
|
<< ", " << this;
|
|
}
|
|
else
|
|
{
|
|
INFO_LOG(m_logger) << getPlayerIdStr() << ": Reset zone/data comunication status - sfm was restarted";
|
|
m_gpsMode = GpsMode::On;
|
|
m_dataCommunication = DataCommunication::Unknown;
|
|
m_lastSeenPosition = -1;
|
|
}
|
|
m_wlsZone = newWlsZone;
|
|
|
|
if (oldDataComm != m_dataCommunication)
|
|
{
|
|
if (m_dataCommunication == DataCommunication::On)
|
|
m_lastSeenDataCommunication = QDateTime::currentMSecsSinceEpoch();
|
|
emit dataCommunicationStateChanged(this);
|
|
}
|
|
if (oldGpsMode != m_gpsMode)
|
|
emit gpsOnOffChanged(this);
|
|
}
|
|
|
|
bool WlsParticipant::handleSfmRxData(const SfmMsgTetra_rx *sfmTele)
|
|
{
|
|
if (!isRecvCounterCorrect(sfmTele->sendCounter()))
|
|
{
|
|
WARNING_LOG(m_logger) << "Got data telegram with counter " << int(sfmTele->sendCounter())
|
|
<< " last one was " << int(m_lastRecvCounter) << " - ignoring";
|
|
return false;
|
|
}
|
|
|
|
m_bSfmPayloadReady = false;
|
|
if (sfmTele->isFragmented())
|
|
{
|
|
if (m_sfmRxPayloadFragmentNumber + 1 != sfmTele->fragmentNumber())
|
|
{
|
|
WARNING_LOG(m_logger) << getPlayerIdStr() << ": Missing a fragment (waiting for "
|
|
<< (m_sfmRxPayloadFragmentNumber + 1)
|
|
<< ", got " << sfmTele->fragmentNumber() << ")";
|
|
if (sfmTele->fragmentNumber() != 1)
|
|
return false;
|
|
// assume the last fragmented telegram did not came through, start over
|
|
m_sfmRxPayloadFragmentNumber = 0;
|
|
}
|
|
if (m_sfmRxPayloadFragmentNumber == 0) // first fragment - clear buffer
|
|
m_sfmRxPayloadData.clear();
|
|
++m_sfmRxPayloadFragmentNumber;
|
|
m_sfmRxPayloadData += sfmTele->payload();
|
|
m_bSfmPayloadReady = (sfmTele->fragmentCount() == sfmTele->fragmentNumber());
|
|
|
|
if (m_bSfmPayloadReady) // last fragment - reset fragment counter
|
|
m_sfmRxPayloadFragmentNumber = 0;
|
|
}
|
|
else
|
|
{
|
|
// not fragmented
|
|
if (m_sfmRxPayloadFragmentNumber != 0)
|
|
WARNING_LOG(m_logger) << getPlayerIdStr()
|
|
<< ": Got unfragmented telegram but currently waiting for next fragment of a fragmented telegram";
|
|
m_sfmRxPayloadFragmentNumber = 0; // assume the last fragmenation did not came through
|
|
m_sfmRxPayloadData = sfmTele->payload();
|
|
m_bSfmPayloadReady = true;
|
|
}
|
|
|
|
if (m_bSfmPayloadReady)
|
|
emit wlsBinarySfmDataComplete(this);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool WlsParticipant::calcChunkCountAndLen(const QByteArray &data, int &chunkCount, int &chunkLen)
|
|
{
|
|
// hex as ascii -> / 2
|
|
// STX
|
|
// msgid (2bytes, current msgIds are all fine - no substitution needed),
|
|
// msg length is in message, we assume it must be subsituted (using 0x02 for checks)
|
|
// fragment byte is correctly calculated
|
|
// checksum byte (2 bytes worst case)
|
|
// ETX
|
|
constexpr int maxBytesPerChunk = (WlsHandler::s_maxLenTransparentData / 2) - (1 + 2 + 2 + 1);
|
|
|
|
chunkLen = 0;
|
|
chunkCount = 1;
|
|
bool bChunkCountIsOk = true;
|
|
QByteArray chunk;
|
|
for (int i = chunkCount; i < 16; ++i) // max. 15 chunks
|
|
{
|
|
bChunkCountIsOk = true;
|
|
chunkLen = (data.size() + i - 1) / i;
|
|
|
|
for (int j = 0; j < i; ++j)
|
|
{
|
|
const char fragByte = SfmMsgTetra_tx::fragmentByte(i, j + 1);
|
|
chunk.clear();
|
|
chunk += 0x02; // a send counter which needs to be substituted
|
|
chunk += fragByte;
|
|
chunk += data.mid(j * chunkLen, chunkLen);
|
|
|
|
const int payloadLen = LtsUtils::lenMaskedData(chunk);
|
|
if (payloadLen > maxBytesPerChunk)
|
|
{
|
|
bChunkCountIsOk = false;
|
|
break;
|
|
}
|
|
}
|
|
if (bChunkCountIsOk)
|
|
{
|
|
chunkCount = i;
|
|
break;
|
|
}
|
|
}
|
|
return bChunkCountIsOk;
|
|
}
|
|
|
|
void WlsParticipant::sendBinarySfmData(const std::vector<unsigned char> &data)
|
|
{
|
|
int chunkLen = 0;
|
|
int chunkCount = 0;
|
|
|
|
const QByteArray ba = LtsUtils::toQByteArray(data);
|
|
if (!calcChunkCountAndLen(ba, chunkCount, chunkLen))
|
|
{
|
|
WARNING_LOG(m_logger) << getPlayerIdStr()
|
|
<< ": Internal error calculating chunk size for payload size " << data.size();
|
|
return;
|
|
}
|
|
|
|
WlsPayloadData payload;
|
|
payload.fragmentCount = chunkCount;
|
|
payload.fragmentNumber = 0;
|
|
int iPos = 0;
|
|
while (iPos < ba.size())
|
|
{
|
|
++payload.fragmentNumber;
|
|
payload.payload = ba.mid(iPos, chunkLen);
|
|
m_wlsQueue.enqueue(payload);
|
|
iPos += chunkLen;
|
|
}
|
|
|
|
QTimer::singleShot(0, this, &WlsParticipant::checkSendTele);
|
|
}
|
|
|
|
void WlsParticipant::handleWlsAck(const Acknowledge_rx *ack)
|
|
{
|
|
// the ack recv counter must match our send counter
|
|
if (ack->recvCounter() != m_nextSendCounter)
|
|
{
|
|
WARNING_LOG(m_logger) << "Got ack for telegram with counter " << int(ack->recvCounter())
|
|
<< " but expected " << int(m_nextSendCounter) << " - ignoring";
|
|
return;
|
|
}
|
|
if (ack->isOk())
|
|
{
|
|
if (m_wlsQueue.isEmpty())
|
|
{
|
|
ERROR_LOG(m_logger) << "got an ack but nothing was sent - something went terribly wrong - resetting send/recv counter";
|
|
m_nextSendCounter = 0;
|
|
m_lastRecvCounter = 0;
|
|
return;
|
|
}
|
|
const WlsPayloadData &h = m_wlsQueue.head();
|
|
if (h.fragmentCount > 1)
|
|
{
|
|
if (h.fragmentCount == h.fragmentNumber)
|
|
m_currentFragmentedTeleQueue.clear(); // sent complete fragmented telegram - can throw away the segments
|
|
else
|
|
m_currentFragmentedTeleQueue.enqueue(h); // save for possible resend
|
|
}
|
|
m_wlsQueue.dequeue();
|
|
m_wlsTeleTimeoutTimer->stop();
|
|
}
|
|
else
|
|
{
|
|
// if we sent a fragmented telegram then we've to start over from the beginning
|
|
while (!m_currentFragmentedTeleQueue.isEmpty())
|
|
m_wlsQueue.push_front(m_currentFragmentedTeleQueue.dequeue());
|
|
}
|
|
checkSendTele();
|
|
}
|
|
|
|
void WlsParticipant::checkSendTele()
|
|
{
|
|
if (m_wlsTeleTimeoutTimer->isActive())
|
|
return;
|
|
sendNextWlsTelegram(false);
|
|
}
|
|
|
|
void WlsParticipant::sendNextWlsTelegram(bool bIsResend)
|
|
{
|
|
if (m_wlsQueue.isEmpty())
|
|
return;
|
|
if (m_dataCommunication == WlsParticipant::DataCommunication::Off)
|
|
{
|
|
if (bIsResend)
|
|
{
|
|
// when we try to resend due to timeout but now we know that there is no
|
|
// data communication anymore -> throw away
|
|
m_wlsQueue.clear();
|
|
return;
|
|
}
|
|
WARNING_LOG(m_logger) << getPlayerIdStr()
|
|
<< ": Trying to send a wls telegram but no data communication possible";
|
|
// should not happen so leave the assertion to find out the reasons for it
|
|
Q_ASSERT(false);
|
|
m_wlsQueue.clear();
|
|
return;
|
|
}
|
|
const auto &tele = m_wlsQueue.head();
|
|
SfmMsgTetra_tx tx(getNextSendCounter(bIsResend),
|
|
tele.fragmentCount,
|
|
tele.fragmentNumber,
|
|
tele.payload);
|
|
emit dataForWls(this, tx);
|
|
if (!bIsResend)
|
|
m_wlsTeleTimeoutCounter = 0;
|
|
m_wlsTeleTimeoutTimer->start();
|
|
}
|
|
|
|
void WlsParticipant::onWlsTeleTimeout()
|
|
{
|
|
++m_wlsTeleTimeoutCounter;
|
|
DEBUG_LOG(m_logger) << getPlayerIdStr()
|
|
<< ": Resend WLS connection to participant " << m_equipmentNumber
|
|
<< "(" << m_tagId << ")"
|
|
<< " m_wlsTeleTimeoutCounter =" << m_wlsTeleTimeoutCounter
|
|
<< " s_maxWlsRetryCount=" << s_maxWlsRetryCount;
|
|
if (m_wlsTeleTimeoutCounter == s_maxWlsRetryCount)
|
|
{
|
|
WARNING_LOG(m_logger) << getPlayerIdStr()
|
|
<< ": Lost WLS connection to participant " << m_equipmentNumber
|
|
<< "(" << m_tagId << ")";
|
|
m_lastSeenDataCommunication = -1;
|
|
m_wlsTeleTimeoutCounter = 0;
|
|
m_nextSendCounter = 0;
|
|
m_lastRecvCounter = 0;
|
|
checkGpsOnOffTimeoutTimer(QDateTime::currentMSecsSinceEpoch());
|
|
return;
|
|
}
|
|
sendNextWlsTelegram(true);
|
|
}
|
|
|
|
uint8_t WlsParticipant::getNextSendCounter(bool bIsResend)
|
|
{
|
|
if (bIsResend)
|
|
return m_nextSendCounter;
|
|
++m_nextSendCounter;
|
|
if (m_nextSendCounter == 0)
|
|
++m_nextSendCounter;
|
|
return m_nextSendCounter;
|
|
}
|
|
|
|
bool WlsParticipant::isRecvCounterCorrect(uint8_t recvCounter)
|
|
{
|
|
// same as in tetra - the counter may not be monotonically ascending ...
|
|
bool bRet = ((recvCounter == 0 && m_lastRecvCounter == 0) ||
|
|
recvCounter != m_lastRecvCounter);
|
|
m_lastRecvCounter = recvCounter;
|
|
return bRet;
|
|
}
|
|
|
|
TrainingAreaBase::TetraIcd::WlsParticipantState WlsParticipant::wlsParticipantState() const
|
|
{
|
|
TrainingAreaBase::TetraIcd::WlsParticipantState ps;
|
|
ps.equipmentNumber = m_equipmentNumber;
|
|
ps.inBlockedArea = false;
|
|
ps.gpsOn = isGpsMode();
|
|
ps.lastSeen = m_lastSeenDataCommunication;
|
|
ps.tacticalState = -1;
|
|
ps.tagId = QString::fromLatin1(tagId());
|
|
ps.voltage = voltage;
|
|
|
|
ps.relativePosition.x = m_relativePosition.x;
|
|
ps.relativePosition.y = m_relativePosition.y;
|
|
ps.relativePosition.z = m_relativePosition.z;
|
|
|
|
ps.relativePosition.wlsAreaId = m_relativePosition.wlsAreaId;
|
|
ps.relativePosition.wlsZone = m_wlsZone;
|
|
ps.relativePosition.wlsBuildingId = m_relativePosition.wlsBuildingId;
|
|
ps.relativePosition.wlsRoomNumber = m_relativePosition.wlsRoomNumber;
|
|
ps.relativePosition.playerOrientation = m_relativePosition.playerOrientation;
|
|
ps.relativePosition.weaponOrientation = m_relativePosition.weaponOrientation;
|
|
ps.relativePosition.isValid = m_relativePosition.isValid;
|
|
|
|
return ps;
|
|
}
|
|
|
|
std::ostream &operator<<(std::ostream &stream, WlsParticipant::GpsMode gm)
|
|
{
|
|
switch (gm)
|
|
{
|
|
case WlsParticipant::GpsMode::On:
|
|
return stream << "GpsMode::On";
|
|
case WlsParticipant::GpsMode::Off:
|
|
return stream << "GpsMode::Off";
|
|
}
|
|
return stream << "Unknown GpsMode (" << std::to_string(int(gm)) << ")";
|
|
}
|
|
|
|
std::ostream &operator<<(std::ostream &stream, WlsParticipant::DataCommunication dc)
|
|
{
|
|
switch (dc)
|
|
{
|
|
case WlsParticipant::DataCommunication::Unknown:
|
|
return stream << "DataCommunication::Unknown";
|
|
case WlsParticipant::DataCommunication::On:
|
|
return stream << "DataCommunication::On";
|
|
case WlsParticipant::DataCommunication::Off:
|
|
return stream << "DataCommunication::Off";
|
|
}
|
|
return stream << "Unknown DataCommunication (" << std::to_string(int(dc)) << ")";
|
|
}
|
|
|
|
} // namespace WirelessLocationSystem
|