#include "wls_participant.h" #include "wls_sfm_message.h" #include "wls_handler.h" #include "settings.h" #include #include #include 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(NGL::NGLLoggerBootstrap::getNGLRootLogger()) { 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))) { 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) return; m_lastValidPosition = tsNow; if (isDataCommunicationPossible()) emit positionChanged(this); return; } const int newWlsZone = WlsParticipant::calcWlsZone(newPosition.wlsAreaId, newPosition.wlsBuildingId, newPosition.wlsRoomNumber); setWlsZone(newWlsZone); m_lastSeenPosition = tsCurrentPos; m_lastValidPosition = tsCurrentPos; m_relativePosition = newPosition; m_relativePosition.isValid = true; if (isDataCommunicationPossible()) emit positionChanged(this); } void WlsParticipant::checkGpsOnOffTimeoutTimer(qint64 tNow) { 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; 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; } void WlsParticipant::setWlsZone(int newWlsZone) { const auto oldGpsMode = m_gpsMode; const auto oldDataComm = m_dataCommunication; if (newWlsZone >= 0) { 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; m_sfmRxPayloadFragmentNumber = 0; } if (m_sfmRxPayloadFragmentNumber == 0) m_sfmRxPayloadData.clear(); ++m_sfmRxPayloadFragmentNumber; m_sfmRxPayloadData += sfmTele->payload(); m_bSfmPayloadReady = (sfmTele->fragmentCount() == sfmTele->fragmentNumber()); if (m_bSfmPayloadReady) m_sfmRxPayloadFragmentNumber = 0; } else { 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; 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) { 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) { 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; 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 &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) { 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(); else m_currentFragmentedTeleQueue.enqueue(h); } m_wlsQueue.dequeue(); m_wlsTeleTimeoutTimer->stop(); } else { 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) { m_wlsQueue.clear(); return; } WARNING_LOG(m_logger) << getPlayerIdStr() << ": Trying to send a wls telegram but no data communication possible"; 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) { 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)) << ")"; } }