/** * 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_def::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().is_set() ? newPosition.position().moutObjectId().get() : 0); m_positionToPublish.roomId(newPosition.position().moutRoomId().is_set() ? 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().is_set()) { // list of new mounted participants std::vector newMountedParticipantIds; // list of already mounted participants std::vector currentMountedParticipantIds; if (m_positionPublished.mountedParticipants().is_set()) 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().is_set())) 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::inner_enum::CombatReady && s.stateTime() > newState.sampleTime()) { return false; } } std::bitset bits(newState.vulnerabilityBits()); bits.set(static_cast(newState.vulnerabilityState().underlying())); QStringList bitsList; for (int i = (int)ProjectDataManagement::VulnerabilityState::inner_enum::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::TemporarySeverance && newState.vulnerabilityState() != ProjectDataManagement::VulnerabilityState::NearMiss && newState.vulnerabilityState() != ProjectDataManagement::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::inner_enum::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::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().underlying() << "\nvuln now: " << (int)mainVulState.underlying() << "\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.underlying()); 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::inner_enum::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::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::VisibleWeaponSystemDamage || newState.vulnerabilityState() == ProjectDataManagement::VulnerabilityState::NearMiss || newState.vulnerabilityState() == ProjectDataManagement::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().underlying(); sanEvaluation = lastState.sanitationEvaluation().underlying(); } if(mainVulState == ProjectDataManagement::VulnerabilityState::CombatReady || newState.originatorType() == ProjectDataManagement::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::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(qualification.underlying()); state.originatorType(ProjectDataManagement::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.underlying())); state.sanitationEvaluation(static_cast(sanitationEvaluation.underlying())); 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::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_def::NoEffect); state.sanitationEvaluation(ParticipantManagement::SanitationEvaluation_def::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::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, g.lastUpdate()); m_lastManipulationReset = m_dynamicData.sampleTime(); for (const auto &m : m_dynamicData.manipulation()) m_lastManipulationReset = std::min(m_lastManipulationReset, 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.underlying()) { 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().is_set()) newEquipmentNumber = m_participantData.equipmentNumber().get(); if (data.equipmentNumber().is_set() && m_participantData.equipmentNumber() != data.equipmentNumber()) { newEquipmentNumber = data.equipmentNumber().get(); hasChanges = true; } // = Reserved for excon if (!data.equipmentNumber().is_set() && 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().is_set() ? 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().is_set()) { if (operationalState.equipmentFunctionId().is_set() && 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().is_set() && 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