Files
qt5-legatus-legacy/Lts/Services/ParticipantManagement/ParticipantHandling/participant.cpp
T
2026-08-13 18:33:10 +02:00

1142 lines
40 KiB
C++
Executable File

/**
* 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 <ParticipantManagement/web_socket_handler.h>
#include <QSet>
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<int32_t> newMountedParticipantIds;
// list of already mounted participants
std::vector<int32_t> 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<int32_t>(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<sizeof(newState.vulnerabilityBits()) * 8> bits(newState.vulnerabilityBits());
bits.set(static_cast<int>(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<ProjectDataManagement::VulnerabilityState_def::type>(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("<empty>") : 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<int>(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<ParticipantManagement::StateInformation> 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<SanitationEvaluation::inner_enum>(sanEvaluationVal);
sanResult = static_cast<SanitationResult::inner_enum>(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<GearInformation> oldGearData;
std::swap(gearVec, oldGearData);
gearVec.reserve(gearAssignment.gears().size());
QSet<int> 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<int> &materialStock, int abgSn, qint64 sampleTime, Qualification qualif, qint64 eqNo)
{
// outdated?
if (m_lastSanMatUsage > sampleTime)
return false;
m_mutex.lock();
m_lastSanMatUsage = sampleTime;
std::vector<SanitationMaterial> 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<ParticipantManagement::SanitationResult::inner_enum>(sanitationResult.underlying()));
state.sanitationEvaluation(static_cast<ParticipantManagement::SanitationEvaluation::inner_enum>(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<int32_t> 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<ManipulationInformation> 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<Interface::Dts> Participant::dts() const
{
return m_participantHandler->dts();
}
} // namespace ParticipantHandling
} // namespace ParticipantManagement