/** * The Participant class is the base class for either Buildings or ExerciseParticipants. * $Id$ * $HeadURL$ * $Author$ * Created by Hannes Baeuerle, SLSE, 2016-05-23 */ #include "participant.h" #include "participant_handler.h" #include "unit_position_calculator.h" #include #include namespace ParticipantManagement { namespace ParticipantHandling { // // Participant // Participant::Participant(ParticipantHandler *participantHandler, UnitPositionCalculator *unitPosCalculator, const Database::PpmParticipant &data, const char *loggerName) : m_participantHandler(participantHandler) , m_unitPosCalculator(unitPosCalculator) , m_lastGearUsage(0) , m_lastManipulationReset(0) , m_lastNbcState(0) , m_lastSpecialSensorData(0) , m_participantData(toDdsParticipant(data)) , m_bPublishPosition(!participantHandler->isUnitType(data.typeId)) , m_logger(getLogger(loggerName)) { m_participantData.sampleTime = 0; m_dynamicData.sampleTime = 0; m_dynamicData.participantId = m_participantData.id; m_participantHandler->setEquipmentNumberForParticipant(0, equipmentNumber(), id()); m_unitPosCalculator->onSuperiorChanged(id(), superiorId()); } Participant::~Participant() { m_unitPosCalculator->onParticipantDeleted(id()); } void Participant::removeFromDds() { auto d = dts(); #ifdef LTS_DEBUG // module test creates participants in ex0 if (d->isParticipantGearEventEnabled()) #endif { d->removeParticipantGearEventByKey(id()); d->removeDynamicParticipantDataByKey(id()); d->removeParticipantPositionByKey(id()); d->removeParticipantByKey(id()); } } void Participant::markAsDeleted() { m_participantData.isDeleted = true; m_participantData.superiorId = 0; m_participantData.inferiorIds.clear(); m_participantData.hierarchyNumber = 0; m_unitPosCalculator->onParticipantDeleted(id()); } bool Participant::isValidPosition(qint64 sampleTime, const Core::GeoPosition &pos, Core::PositionReportingSource repSource) const { if (sampleTime < m_positionToPublish.sampleTime) return false; // an operator can set an invalid position (so the participant is not displayed on the map) if (repSource == Core::PositionReportingSource::SetByOperator) return true; if (std::isnan(pos.lon) || std::isnan(pos.lat)) return false; return true; } bool Participant::setPosition(const TrainingAreaBase::EquipmentPosition &newPosition) { if (!isValidPosition(newPosition.sampleTime, newPosition.position.position, newPosition.position.reportingSource)) return false; m_unitPosCalculator->onPositionChanged(id(), newPosition.sampleTime, newPosition.position.position); m_mutex.lock(); m_positionToPublish.reportingSource = newPosition.position.reportingSource; m_positionToPublish.pos = newPosition.position.position; m_positionToPublish.buildingId = newPosition.position.moutObjectId.has_value() ? newPosition.position.moutObjectId.get() : 0; m_positionToPublish.roomId = newPosition.position.moutRoomId.has_value() ? newPosition.position.moutRoomId.get() : 0; m_positionToPublish.sampleTime = newPosition.sampleTime; m_positionToPublish.mountedOn.reset(); m_mutex.unlock(); // When a participant can have mounted participants, then there is always mountedParticipants set, can be empty if (newPosition.mountedParticipants.has_value()) { // list of new mounted participants std::vector newMountedParticipantIds; // list of already mounted participants std::vector currentMountedParticipantIds; if (m_positionPublished.mountedParticipants.has_value()) currentMountedParticipantIds = m_positionPublished.mountedParticipants.get(); // set new position for all mounted participants for (const auto mountedEqId : newPosition.mountedParticipants.get()) { Participant *mountedParticipant = particpantHandler()->participantByEquipmentNumber(mountedEqId); if (!mountedParticipant) { WARNING_LOG(m_logger) << "Mounted Participant with equipNo " << mountedEqId << " not found - ignoring"; continue; } if (!mountedParticipant->setPosition(newPosition, id())) continue; mountedParticipant->publishPosition(); newMountedParticipantIds.push_back(mountedParticipant->id()); INFO_LOG(m_logger) << "Mounting Participant with id: " << mountedParticipant->id() << " to id: " << id(); } // unmount current mounted participants that are not in the list anymore for (const auto curMountedParticipantId : currentMountedParticipantIds) { size_t idx2 = 0; while (idx2 < newMountedParticipantIds.size()) { if (curMountedParticipantId == newMountedParticipantIds.at(idx2)) break; ++idx2; } // participant not in newMountedParticipantIds -> unmount if (idx2 == newMountedParticipantIds.size()) unmountParticipantById(newPosition, curMountedParticipantId); } m_mutex.lock(); if (newMountedParticipantIds.size() == 0) m_positionToPublish.mountedParticipants.reset(); else m_positionToPublish.mountedParticipants = newMountedParticipantIds; m_mutex.unlock(); } return true; } bool Participant::setPosition(const ParticipantPosition &newPosition, bool bForce) { if (!bForce && !isValidPosition(newPosition.sampleTime, newPosition.pos, newPosition.reportingSource)) { WARNING_LOG(m_logger) << "position for participant " << m_participantData.id << " is outdated: " << newPosition << " , cur pos sample time: " << m_positionPublished.sampleTime; return false; } m_unitPosCalculator->onPositionChanged(id(), newPosition.sampleTime, newPosition.pos); m_mutex.lock(); m_positionToPublish = newPosition; m_mutex.unlock(); return true; } bool Participant::setCalculatedPosition(const Core::GeoPosition &pos, int64_t positionTime) { const bool bNewer = m_positionToPublish.sampleTime < positionTime; m_positionToPublish.pos = pos; m_positionToPublish.sampleTime = positionTime; return bNewer; } bool Participant::setPosition(const TrainingAreaBase::EquipmentPosition &newPosition, int mountedToId) { if (mountedToId == id()) { WARNING_LOG(m_logger) << "Trying to mount to ourself: " << id(); return false; } TrainingAreaBase::EquipmentPosition mountedPosition = newPosition; mountedPosition.mountedParticipants.reset(); // set position if (new mounted position) or (unmounting and participant is still mounted) if ((mountedToId > 0) || (mountedToId <= 0 && m_positionPublished.mountedOn.has_value())) setPosition(mountedPosition); m_positionToPublish.reportingSource = newPosition.position.reportingSource; if (mountedToId > 0) m_positionToPublish.mountedOn = dds::core::optional(mountedToId); return true; } void Participant::unmountParticipantById(const TrainingAreaBase::EquipmentPosition &newPosition, int32_t id) { // no need for a lock as we double_lock ourself when calling unmountParticipantsById out of setPosition // the real lock is done in unmountedParticipant->setPosition Participant *unmountedParticipant = particpantHandler()->participantById(id); unmountedParticipant->setPosition(newPosition, 0); unmountedParticipant->publishPosition(); INFO_LOG(m_logger) << "Unmounting Participant with ID: " << unmountedParticipant->id() << "from ID: " << this->id(); } bool Participant::setState(const TrainingAreaBase::EquipmentState &newState, int originatorId) { auto &stateHistory = m_dynamicData.stateHistory; // search for CombatReady state, ignore newState if it is older for (const auto &s : stateHistory) { if (s.vulnerabilityState == ProjectDataManagement::VulnerabilityState::VulnerabilityState_CombatReady && s.stateTime > newState.sampleTime) { return false; } } std::bitset bits(newState.vulnerabilityBits); bits.set(static_cast(newState.vulnerabilityState)); QStringList bitsList; for (int i = (int)ProjectDataManagement::VulnerabilityState::VulnerabilityState_CatastrophicKill; i > 0; --i) { if (bits.test(i)) bitsList += QString::number(i); } bool vulnIgnored = true; // find current main state ProjectDataManagement::VulnerabilityState mainVulState = newState.vulnerabilityState; if (newState.vulnerabilityState != ProjectDataManagement::VulnerabilityState::VulnerabilityState_TemporarySeverance && newState.vulnerabilityState != ProjectDataManagement::VulnerabilityState::VulnerabilityState_NearMiss && newState.vulnerabilityState != ProjectDataManagement::VulnerabilityState::VulnerabilityState_HitWithNoEffect) { vulnIgnored = false; // only change main vuln state to highest from vulnerabilityBits if a bit smaller than CK (VBG8) is set: /* NOTE: 'i > 0' is intended. This ignores vuln CR (VBG0). That's because CR is a special case where * the history is resetted, etc. Fortunately the TNE never sends a BC (VBG12) if the last vuln is CR, * but sends a CR instead. */ for (int i = (int)ProjectDataManagement::VulnerabilityState::VulnerabilityState_CatastrophicKill; i > 0; --i) { if (bits.test(i)) { mainVulState = static_cast(i); //WARNING_LOG(m_logger) << "mainVulState was manipulated into: " << i; break; } } } // if mainVulnState *still* is BC (VBG12), something went wrong on the sender side (TNE?) if (mainVulState == ProjectDataManagement::VulnerabilityState::VulnerabilityState_VulnerabilityBitsChanged) WARNING_LOG(m_logger) << "Sending a BC (VBG12) if the last vuln is CR (VBG0) is not allowed! Something went wrong on the sender side (TNE?)"; DEBUG_LOG(m_logger) << "setState for eqid: " << newState.equipmentNumber << "\nvuln was: " << (int)newState.vulnerabilityState << "\nvuln now: " << (int)mainVulState << "\nbits (DEC): " << newState.vulnerabilityBits << "\nbits: " << (bitsList.isEmpty() ? QStringLiteral("") : bitsList.join(QStringLiteral(","))) << "\nvuln ignored? " << vulnIgnored ; // some old system or ddsview/LogView sent us an equipmentState with no vulnerability bits -> set at least the current one auto vulnerabilityBits = newState.vulnerabilityBits; if (vulnerabilityBits == 0) { const int idx = static_cast(mainVulState); vulnerabilityBits = (1 << idx); WARNING_LOG(m_logger) << "Missing vulnerabilityBits! bailing by supplementing by adding this vuln state to the bits"; } // check if it is a (younger) CombatReady state - remove older if (mainVulState == ProjectDataManagement::VulnerabilityState::VulnerabilityState_CombatReady) { std::vector currentStates; for (const auto &s : stateHistory) { if (s.stateTime > newState.sampleTime) currentStates.push_back(s); } m_mutex.lock(); std::swap(stateHistory, currentStates); m_mutex.unlock(); } auto sanResult = SanitationResult::Unhandled; auto sanEvaluation = SanitationEvaluation::NotApplicable; #ifdef GUEZ_MODE if(newState.originatorType == ProjectDataManagement::StateChangeOriginator::StateChangeOriginator_MedicalTimeout) { /*! * \brief originatorId * \details For Sanitation Telegram the Split is done in PrtCnv * First 2 Bit represent SanitationEvaluation(Versorgungsbewertung) * The other 6 Bit represent SanitationResult(Versorgungsauswirkung) * For more precise info: ICD52 */ // Only watch lower byte! quint8 originatorId = newState.originatorId; // If Player Unit has wrong Software, it sends its own equipment number. If so, just take the following values: if (newState.originatorId > 255) { WARNING_LOG(m_logger) << "Participant with EqNo " << m_participantData.equipmentNumber << " sent wrong Enum Values (Sanitation Eval / ~ Result): " << newState.originatorId << ". Defaulting to Eval = Okay, Result = Unhandled."; originatorId = 1; // Evaluation = Okay, Result = Unhandled. } quint8 sanEvaluationVal = ((originatorId & 0xc0) >> 6); quint8 sanResultVal = (originatorId & 0x3f); if (sanEvaluationVal > 2) // Todo: Check against valid enum values in a better way! { WARNING_LOG(m_logger) << "Cannot cast upper 2 Bits to SanEvaluation! Defaulting to Okay"; sanEvaluationVal = 0; } if (sanResultVal > 4) // Todo: Check against valid enum values in a better way! { WARNING_LOG(m_logger) << "Cannot cast lower 6 Bits to SanResult! Defaulting to Unhandled"; sanResultVal = 1; } sanEvaluation = static_cast(sanEvaluationVal); sanResult = static_cast(sanResultVal); } #endif //new state Temp Vis Damage if(newState.vulnerabilityState == ProjectDataManagement::VulnerabilityState::VulnerabilityState_VisibleWeaponSystemDamage || newState.vulnerabilityState == ProjectDataManagement::VulnerabilityState::VulnerabilityState_NearMiss || newState.vulnerabilityState == ProjectDataManagement::VulnerabilityState::VulnerabilityState_HitWithNoEffect) { //Get last state in history and use that result const ParticipantManagement::StateInformation lastState = ParticipantManagement::youngestStateInformation(stateHistory, false, false); //Don't Skip Sanitation! sanResult = lastState.sanitationResult; sanEvaluation = lastState.sanitationEvaluation; } if(mainVulState == ProjectDataManagement::VulnerabilityState::VulnerabilityState_CombatReady || newState.originatorType == ProjectDataManagement::StateChangeOriginator::StateChangeOriginator_FunctionalArea) sanResult = SanitationResult::NoEffect; // [GUEZE-3313] Kontaminierter Teilnehmer trotz Dekontamination weiterhin nicht Vitalität von 100% // ICD 52 Kapitel 3.3.2.6.72 VBA / Vitalitaet / Anzeigeindex int32_t vitality = newState.vitality; if(mainVulState == ProjectDataManagement::VulnerabilityState::VulnerabilityState_CombatReady) vitality = 100; ParticipantManagement::StateInformation state(mainVulState, vulnerabilityBits, newState.vulnerabilityKindId, originatorId, newState.originatorType, newState.originatorChargeTypeId, newState.sampleTime, vitality, 0, sanResult, sanEvaluation); for (const ParticipantManagement::StateInformation &hist : stateHistory) { if (state == hist) return false; } m_mutex.lock(); stateHistory.push_back(std::move(state)); m_mutex.unlock(); if (stateHistory.size() == 1) m_lastSanMatUsage = 0; return true; } bool Participant::setSpecialSensorData(const TrainingAreaBase::SpecialSensorData &data) { if (m_lastSpecialSensorData > data.sampleTime) return false; m_mutex.lock(); m_lastSpecialSensorData = data.sampleTime; m_dynamicData.specialSensorDataState = data.operationalState; m_mutex.unlock(); return true; } bool Participant::setGear(const TrainingAreaBase::EquipmentGearAssignment &gearAssignment) { DEBUG_LOG(m_logger) << "Gear Assignment"; // outdated? if (m_lastGearUsage > gearAssignment.sampleTime) return false; m_lastGearUsage = gearAssignment.sampleTime; // remember the old gears and readd the charges if the gear is still there in the new gear assignment auto &gearVec = m_dynamicData.gear; std::vector oldGearData; std::swap(gearVec, oldGearData); gearVec.reserve(gearAssignment.gears.size()); QSet newGearIds; newGearIds.reserve(gearAssignment.gears.size()); for (const TrainingAreaBase::Gear &gear : gearAssignment.gears) { if (gear.gearId == 0) { WARNING_LOG(m_logger) << "Got a gear assignment but gear id is 0 - old Saab AGDUS? - ignoring"; continue; } newGearIds += gear.gearId; gearVec.emplace_back(gear.gearId, gear.gearTypeId, -1, -1, gearAssignment.sampleTime, gear.chargeCounting); } auto it = m_dynamicData.designations.begin(); while (it != m_dynamicData.designations.end()) { if (newGearIds.contains(it->gearId)) ++it; else { DEBUG_LOG(m_logger) << "Gear Assignment: Removing Designation with gearId: " << it->gearId; it = m_dynamicData.designations.erase(it); } } // readd old charges if gear is still there for (GearInformation &gi : oldGearData) { if (!newGearIds.contains(gi.gearId)) continue; // to not be ignored gi.lastUpdate = gearAssignment.sampleTime; setGearCharge(gi); } return true; } bool Participant::setSanitationMaterial(int materialCount, const std::vector &materialStock, int abgSn, qint64 sampleTime, Qualification qualif, qint64 eqNo) { // outdated? if (m_lastSanMatUsage > sampleTime) return false; m_mutex.lock(); m_lastSanMatUsage = sampleTime; std::vector newStock; newStock.reserve(materialCount); for (int i = 0 ; i < materialCount; i++) { newStock.emplace_back(i + 1, materialStock.at(i)); } //as we don't change the amount during an exercise we can override directly m_dynamicData.sanitationMaterialStock = newStock; m_dynamicData.sanitationUniqueId = abgSn; m_dynamicData.qualification = qualif; m_dynamicData.sanitationOriginatorEquipNo = eqNo; m_mutex.unlock(); return true; } bool Participant::setSanitation(qint64 sanitationProceduresTaken, TrainingAreaBase::SanitationResult sanitationResult, TrainingAreaBase::SanitationEvaluation sanitationEvaluation, int originatorId, TrainingAreaBase::Qualification qualification, qint64 sampleTime) { auto &stateHistory = m_dynamicData.stateHistory; ParticipantManagement::StateInformation lastState; // search for CombatReady state, ignore newState if it is older for (const auto &s : stateHistory) { if (s.stateTime > lastState.stateTime) lastState = s; } if (sanitationResult == TrainingAreaBase::SanitationResult::NoEffect && sanitationProceduresTaken == 0) { return true; } ParticipantManagement::StateInformation state; state.originatorId = originatorId; state.originatorChargeTypeId = static_cast(qualification); state.originatorType = ProjectDataManagement::StateChangeOriginator::StateChangeOriginator_Sanitation; state.vitality = lastState.vitality; state.vulnerabilityBits = lastState.vulnerabilityBits; state.vulnerabilityKindId = lastState.vulnerabilityKindId; state.vulnerabilityState = lastState.vulnerabilityState; state.sanitationProceduresTaken = sanitationProceduresTaken; state.sanitationResult = static_cast(sanitationResult); state.sanitationEvaluation = static_cast(sanitationEvaluation); state.stateTime = sampleTime; m_mutex.lock(); stateHistory.push_back(std::move(state)); m_mutex.unlock(); return true; } bool Participant::setOperatorAction(int action, int originatorId, qint64 sampleTime) { auto &stateHistory = m_dynamicData.stateHistory; ParticipantManagement::StateInformation lastState; // search for CombatReady state, ignore newState if it is older for (const auto &s : stateHistory) { if (s.stateTime > lastState.stateTime) lastState = s; } ParticipantManagement::StateInformation state; state.originatorId = originatorId; state.originatorChargeTypeId = action; state.originatorType = ProjectDataManagement::StateChangeOriginator::StateChangeOriginator_ObserverController; state.vitality = lastState.vitality; state.vulnerabilityBits = lastState.vulnerabilityBits; state.vulnerabilityKindId = lastState.vulnerabilityKindId; state.vulnerabilityState = lastState.vulnerabilityState; // 08: Wound paused // 35: Wound Continued state.sanitationResult = ParticipantManagement::SanitationResult::NoEffect; state.sanitationEvaluation = ParticipantManagement::SanitationEvaluation::NotApplicable; state.stateTime = sampleTime; m_mutex.lock(); stateHistory.push_back(std::move(state)); m_mutex.unlock(); return true; } bool Participant::setEquipmentEvent(int event, qint64 sampleTime) { auto &stateHistory = m_dynamicData.stateHistory; ParticipantManagement::StateInformation lastState; // search for CombatReady state, ignore newState if it is older for (const auto &s : stateHistory) { if (s.stateTime > lastState.stateTime) lastState = s; } ParticipantManagement::StateInformation state; state.originatorId = lastState.originatorId; state.originatorChargeTypeId = event; state.originatorType = ProjectDataManagement::StateChangeOriginator::StateChangeOriginator_ObserverController; state.vitality = lastState.vitality; state.sanitationResult = lastState.sanitationResult; state.vulnerabilityBits = lastState.vulnerabilityBits; state.vulnerabilityKindId = lastState.vulnerabilityKindId; state.vulnerabilityState = lastState.vulnerabilityState; state.stateTime = sampleTime; m_mutex.lock(); stateHistory.push_back(std::move(state)); m_mutex.unlock(); return true; } bool Participant::setGearCharge(const TrainingAreaBase::GearCharge &charge, qint64 sampleTime) { // older than last complete reset (2s jitter allowed) if (m_lastGearUsage - 2 * 1000 > sampleTime) return false; GearInformation gearInfo; gearInfo.gearId = charge.gearId; gearInfo.gearTypeId = charge.gearTypeId; gearInfo.chargeTypeId = charge.chargeTypeId; gearInfo.remain = charge.remain; gearInfo.lastUpdate = sampleTime; //set chargeCounting if needed bool count = false; for (const auto gi : m_dynamicData.gear) { if (gi.gearTypeId == gearInfo.gearTypeId && gi.chargeCounting) count = true; } gearInfo.chargeCounting = count; return setGearCharge(gearInfo); } bool Participant::setGearCharge(const GearInformation &gearInfo) { if (gearInfo.remain < 0) return true; // check if charge.chargeType is already known int index = indexByGearAndChargeId(gearInfo.gearId, gearInfo.chargeTypeId); int index_wanne=0; // Hack for old systems which neither send gearTasCode/gearType nor gearSerialNumber/gearId // 1 = Wanne, 2 = Turm, see TAB::send_to_dds.cpp if (index < 0 && gearInfo.gearId == 0) // no location info in EquipmentGearEvent { index = indexByGearAndChargeId(2, gearInfo.chargeTypeId); // EquipmentGearEvent can only come from turret index_wanne = indexByGearAndChargeId(1, gearInfo.chargeTypeId); } else if (index < 0 && gearInfo.gearId == 2) index = indexByGearAndChargeId(0, gearInfo.chargeTypeId); if (index < 0) // not known but maybe a gear assignment (no charge info available yet) index = indexByGearAndChargeId(gearInfo.gearId); bool bRet = false; if (index < 0) { m_mutex.lock(); m_dynamicData.gear.push_back(gearInfo); // new gear/charge combo - append m_mutex.unlock(); bRet = true; } else { // already known, check for new data and update m_mutex.lock(); auto &gi = m_dynamicData.gear[index]; if (gi.lastUpdate <= gearInfo.lastUpdate) { const bool chargeCounting = gi.chargeCounting; const bool bAdjustTurretInfo = (gearInfo.gearId == 0 && gi.gearId == 2); gi = gearInfo; if (bAdjustTurretInfo) { int substr = m_dynamicData.gear[index_wanne].remain; INFO_LOG(m_logger) << "Adjust Turret - substracting index wanne " << substr; if (gi.remain >= substr) gi.remain = gi.remain - m_dynamicData.gear[index_wanne].remain; INFO_LOG(m_logger) << "Turret remain " << gi.remain; gi.gearId = 2; } if (gearInfo.chargeCounting) gi.chargeCounting = gearInfo.chargeCounting; else gi.chargeCounting = chargeCounting; bRet = true; } m_mutex.unlock(); } return bRet; } void Participant::setDynamicData(const DynamicParticipantData &data) { m_mutex.lock(); m_dynamicData = data; m_lastGearUsage = m_dynamicData.sampleTime; for (const auto &g : m_dynamicData.gear) m_lastGearUsage = std::min(m_lastGearUsage, static_cast(g.lastUpdate)); m_lastManipulationReset = m_dynamicData.sampleTime; for (const auto &m : m_dynamicData.manipulation) m_lastManipulationReset = std::min(m_lastManipulationReset, static_cast(m.manipulationTime)); m_lastNbcState = m_lastManipulationReset; m_lastSpecialSensorData = m_dynamicData.sampleTime; m_dynamicData.sampleTime = 0; // for publishing, see publishDynamicData m_mutex.unlock(); } void Participant::setMode(const ParticipantMode &data) { m_mutex.lock(); m_participantMode = data; m_participantMode.sampleTime = 0; // for publishing, see publishDynamicData m_mutex.unlock(); } bool Participant::isNewAssignStateValid(const ParticipantAssignState &newAssignState, int32_t equipmentNumber) const { const auto oldAssignState = m_participantData.assignState; if (oldAssignState == newAssignState && m_participantData.equipmentNumber == equipmentNumber) return true; switch (newAssignState) { case ParticipantAssignState::NotAssigned: if (equipmentNumber) { WARNING_LOG(m_logger) << "Not assigned, but equipmentnumber is set"; return false; } break; case ParticipantAssignState::AssignedWms: if (oldAssignState != ParticipantAssignState::NotAssigned || equipmentNumber == 0) { // equipment number changed -> allow if (oldAssignState == ParticipantAssignState::AssignedWms && equipmentNumber != 0) return true; WARNING_LOG(m_logger) << "Now assigned from WMS, but no equipmentnumber or old assign state != NotAssigned: " << equipmentNumber << ", " << newAssignState << ", " << oldAssignState; return false; } break; case ParticipantAssignState::HandoutWms: if ((oldAssignState != ParticipantAssignState::NotAssigned && #ifdef GUEZ_MODE oldAssignState != ParticipantAssignState::HandoutExcon && #endif oldAssignState != ParticipantAssignState::AssignedWms) || equipmentNumber == 0) { // equipment number changed -> allow if (oldAssignState == ParticipantAssignState::HandoutWms && equipmentNumber != 0) return true; WARNING_LOG(m_logger) << "Now assigned from WMS (handout), but no equipmentnumber or old assign state != NotAssigned/AssignedWms: " << equipmentNumber << ", " << newAssignState << ", " << oldAssignState; return false; } break; case ParticipantAssignState::HandoutExcon: if ((oldAssignState != ParticipantAssignState::NotAssigned && oldAssignState != ParticipantAssignState::AssignedWms) || equipmentNumber == 0) { // equipment number changed -> allow if ((oldAssignState == ParticipantAssignState::HandoutExcon || oldAssignState == ParticipantAssignState::ExconWmsOverruled) && equipmentNumber != 0) return true; // Assign state HandoutExcon + equipmentNumber = 0 -> reserved participant for EXCON in WH (WH must not touch it) if (equipmentNumber == 0) return true; WARNING_LOG(m_logger) << "Now assigned from EXCON, but no equipmentnumber or old assign state != NotAssigned: " << equipmentNumber << ", " << newAssignState << ", " << oldAssignState; return false; } break; case ParticipantAssignState::ExconWmsOverruled: // special case: warehouse doesn't like the equipment number set (ICD23, State C2) if (oldAssignState == ParticipantAssignState::HandoutExcon && equipmentNumber == m_participantData.equipmentNumber) return true; if (oldAssignState != ParticipantAssignState::AssignedWms && oldAssignState != ParticipantAssignState::ExconWmsOverruled && oldAssignState != ParticipantAssignState::HandoutWms && equipmentNumber != 0) { WARNING_LOG(m_logger) << "Now assigned from ExconWmsOverruled, old assign state != AssignedWms or != HandoutWms or equipment number is set: " << equipmentNumber << ", " << newAssignState << ", " << oldAssignState; return false; } break; } return true; } bool Participant::hasToBeChanged(const ParticipantManagement::Participant &data) { const ParticipantBase &baseData = data; bool bRet = hasToBeChanged(baseData); if (hasNewSuperiorId(data.superiorId)) bRet = true; if (hasNewInferiorStructure(data.inferiorIds)) bRet = true; if (data.identification != m_participantData.identification) { bRet = true; m_participantData.identification = data.identification; } return bRet; } bool Participant::hasToBeChanged(const ParticipantManagement::ParticipantBase &data) { DEBUG_LOG(m_logger) << "hasToBeChanged: " << data; ParticipantManagement::ParticipantBase &curData = m_participantData; bool hasChanges = (curData != data); auto assignState = m_participantData.assignState; if (assignState != data.assignState) { assignState = data.assignState; hasChanges = true; } int newEquipmentNumber = 0; // don't know why this is needed though if (m_participantData.equipmentNumber.has_value()) newEquipmentNumber = m_participantData.equipmentNumber.get(); if (data.equipmentNumber.has_value() && m_participantData.equipmentNumber != data.equipmentNumber) { newEquipmentNumber = data.equipmentNumber.get(); hasChanges = true; } // = Reserved for excon if (!data.equipmentNumber.has_value() && assignState == ParticipantManagement::ParticipantAssignState::HandoutExcon) { newEquipmentNumber = 0; hasChanges = true; } // wenn Assignment auf 'NotAssigned' oder 'ExconWmsOverruled' geändert wurde, // muss die EquipmentNumber zurückgesetzt werden, egal was in data.equipmentNumber() steht if (assignState == ParticipantManagement::ParticipantAssignState::NotAssigned || assignState == ParticipantManagement::ParticipantAssignState::ExconWmsOverruled) { if (m_participantData.equipmentNumber != 0) { newEquipmentNumber = 0; hasChanges = true; } } if (hasChanges) { if (curData.typeId != data.typeId) m_bPublishPosition = !m_participantHandler->isUnitType(data.typeId); const int oldEquipNo = curData.equipmentNumber.has_value() ? curData.equipmentNumber.get() : 0; curData = data; if (!updateEquipmentExerciseMapping(oldEquipNo, newEquipmentNumber)) { curData.equipmentNumber.reset(); curData.assignState = ParticipantAssignState::NotAssigned; } } return hasChanges; } bool Participant::hasNewSuperiorId(int32_t superiorId) { bool result = m_participantData.superiorId != superiorId; m_participantData.superiorId = superiorId; if (result) m_unitPosCalculator->onSuperiorChanged(id(), superiorId); return result; } bool Participant::hasNewInferiorStructure(std::vector inferiorIds) { // no need to sort m_inferiorIds - they're sorted since they are always filled with this function std::sort(inferiorIds.begin(), inferiorIds.end()); if (m_participantData.inferiorIds != inferiorIds) { m_participantData.inferiorIds = inferiorIds; m_unitPosCalculator->onInferiorChanged(id(), m_participantData.inferiorIds); return true; } return false; } bool Participant::setOperationalState(const TrainingAreaBase::EquipmentOperationalState &operationalState) { m_mutex.lock(); bool bRet = false; if (operationalState.isManipulation.has_value()) { if (operationalState.equipmentFunctionId.has_value() && operationalState.isManipulation.get() && operationalState.sampleTime > m_lastManipulationReset) // add if there is a new manipulation which is not outdated { removeManipulation(operationalState.equipmentFunctionId.get()); // precautionary m_dynamicData.manipulation.emplace_back(operationalState.equipmentFunctionId.get(), operationalState.sampleTime); bRet = true; } else if (!operationalState.isManipulation.get()) // if manipulation is set to false, clean manipulations { if (m_lastManipulationReset < operationalState.sampleTime) { m_lastManipulationReset = operationalState.sampleTime; cleanManipulations(); bRet = true; } } } if (operationalState.nbcProtectionActive.has_value() && m_lastNbcState < operationalState.sampleTime) { m_dynamicData.hasNbcProtectionActive = operationalState.nbcProtectionActive.get(); m_dynamicData.hasNbcProtectionConfigured = operationalState.nbcProtectionActive.get(); m_lastNbcState = operationalState.sampleTime; bRet = true; } m_mutex.unlock(); return bRet; } RegisterParticipantRequest Participant::toRegisterParticipantRequest() const { return RegisterParticipantRequest(id(), equipmentNumber(), assignState(), m_participantData.identification, m_participantData.info); } bool Participant::publishPosition() { if (!m_bPublishPosition) return true; m_mutex.lock(); m_positionToPublish.participantId = id(); m_positionToPublish.participantTypeId = typeId(); m_positionToPublish.partyId = partyId(); m_mutex.unlock(); if (m_positionToPublish == m_positionPublished) return true; m_mutex.lock(); m_positionPublished = m_positionToPublish; m_participantHandler->m_webSocketHandler->sendPosition(m_positionToPublish, equipmentNumber()); auto copy = m_positionPublished; m_mutex.unlock(); return dts()->publish(copy); } bool Participant::publishDynamicData() { m_mutex.lock(); m_dynamicDataPublished.sampleTime = 0; // for comparision m_mutex.unlock(); if (m_dynamicData == m_dynamicDataPublished) { if ( m_dynamicData.designations.size() == m_dynamicDataPublished.designations.size() ) { int i = 0; bool equal = true; for (LaserDesignation &laserDesignation : m_dynamicData.designations) { if (laserDesignation != m_dynamicDataPublished.designations.at(i)) { equal = false; break; } i++; } if (equal) return true; } } m_mutex.lock(); m_dynamicDataPublished = m_dynamicData; m_dynamicDataPublished.sampleTime = QDateTime::currentMSecsSinceEpoch(); auto copy = m_dynamicDataPublished; m_mutex.unlock(); return dts()->publish(copy); } bool Participant::publishMode() { m_mutex.lock(); m_participantMode.sampleTime = 0; // for comparision m_mutex.unlock(); if (m_participantMode == m_participantModePublished) return true; m_mutex.lock(); m_participantModePublished = m_participantMode; m_participantModePublished.sampleTime = QDateTime::currentMSecsSinceEpoch(); auto copy = m_participantModePublished; m_mutex.unlock(); return dts()->publish(copy); } bool Participant::publishParticipant() { m_mutex.lock(); m_participantPublished.sampleTime = 0; // for comparision m_mutex.unlock(); if (m_participantData == m_participantPublished) return true; m_mutex.lock(); m_participantPublished = m_participantData; // no swap! m_participantPublished.sampleTime = QDateTime::currentMSecsSinceEpoch(); auto copy = m_participantPublished; m_mutex.unlock(); return dts()->publish(copy); } ParticipantManagement::Participant Participant::toDdsParticipant(const Database::PpmParticipant &data) { ParticipantManagement::Participant ret; ret.id = data.id; ret.partyId = data.partyId; ret.hierarchyNumber = data.hierarchyNumber; if (data.equipmentNumber) ret.equipmentNumber = data.equipmentNumber; ret.typeId = data.typeId; ret.externalId = data.externalId; ret.symbol = data.symbol.toStdString(); ret.name = data.name.toStdString(); ret.designator = data.designator.toStdString(); ret.tacticalIdentifier = data.tacticalIdentifier.toStdString(); ret.info = data.info.toStdString(); ret.assignState = data.assignState; ret.isSimulated = data.isSimulated; ret.isDeleted = data.isDeleted; //ret.sampleTime(); ret.superiorId = data.superiorId; //ret.inferiorIds(); ret.identification = data.identification.toStdString(); ret.properties = data.properties.toStdString(); return ret; } Database::PpmParticipant Participant::toDbParticipant() const { Database::PpmParticipant ret; ret.id = m_participantData.id; ret.assignState = m_participantData.assignState; ret.partyId = m_participantData.partyId; ret.typeId = m_participantData.typeId; ret.externalId = m_participantData.externalId; ret.designator = QString::fromStdString(m_participantData.designator); ret.equipmentNumber = equipmentNumber(); ret.hierarchyNumber = m_participantData.hierarchyNumber; ret.identification = QString::fromStdString(m_participantData.identification); ret.info = QString::fromStdString(m_participantData.info); ret.isDeleted = m_participantData.isDeleted; ret.isSimulated = m_participantData.isSimulated; ret.name = QString::fromStdString(m_participantData.name); ret.superiorId = m_participantData.superiorId; ret.symbol = QString::fromStdString(m_participantData.symbol); ret.tacticalIdentifier = QString::fromStdString(m_participantData.tacticalIdentifier); ret.properties = QString::fromStdString(m_participantData.properties); return ret; } void Participant::removeManipulation(int32_t equipmentFunctionId) { size_t i = 0; auto &manipulation = m_dynamicData.manipulation; for (; i < manipulation.size(); ++i) { if (manipulation.at(i).equipmentFunctionId == equipmentFunctionId) break; } if (i >= manipulation.size()) // not found return; ++i; for (; i < manipulation.size(); ++i) manipulation[i - 1] = manipulation.at(i); manipulation.resize(manipulation.size() - 1); } void Participant::cleanManipulations() { std::vector currentManipulations; currentManipulations.reserve(m_dynamicData.manipulation.size()); for (const ManipulationInformation &info : m_dynamicData.manipulation) { if (info.manipulationTime >= m_lastManipulationReset) currentManipulations.push_back(info); } m_dynamicData.manipulation = currentManipulations; } bool Participant::gearHasCharge(int32_t gearId) const { for (const GearInformation &gearInfo : m_dynamicData.gear) { if (gearInfo.gearId == gearId) return gearInfo.chargeTypeId != -1; // -1 indicates, that there was no charge associated } return false; } int32_t Participant::indexByGearAndChargeId(int32_t gearId, int32_t chargeTypeId) const { const auto &gears = m_dynamicData.gear; for (size_t i = 0; i < gears.size(); ++i) { const auto &gear = gears.at(i); if (gear.gearId == gearId) { // search for gear with empty charge if (chargeTypeId == -1 && gear.chargeTypeId == -1) return i; // search for gear with specific charge if (gear.chargeTypeId == chargeTypeId) return i; } } return -1; } qint64 Participant::currentTimeMSec() const { return m_participantHandler->currentTimeMSec(); } std::shared_ptr Participant::dts() const { return m_participantHandler->dts(); } } // namespace ParticipantHandling } // namespace ParticipantManagement