Files
T
berrux-dev 598fc2560e Complete Qt6 migration: full clean build green across groups G1-G12 incl. runtime plugin set
new_docker_build.sh gains Group 12 (212 GUI/Data plugins); fixes LtsUtilsGuiDds visibility (LTO-stripped exports), LtsProjectionWorkstation unity name shadow, 6 unity-off dirs (dup file-scope statics, ddsftc INTERFACE sources) and reconfigures BUILD_FILTERMANAGEMENT/BUILD_PLUGINS_SERVICEMONITOR=ON
2026-10-20 09:36:57 +02:00

1142 lines
39 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 <QtCore/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::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<int32_t> newMountedParticipantIds;
// list of already mounted participants
std::vector<int32_t> 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<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::VulnerabilityState_CombatReady &&
s.stateTime > newState.sampleTime)
{
return false;
}
}
std::bitset<sizeof(newState.vulnerabilityBits) * 8> bits(newState.vulnerabilityBits);
bits.set(static_cast<int>(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<ProjectDataManagement::VulnerabilityState>(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("<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);
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<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::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>(sanEvaluationVal);
sanResult = static_cast<SanitationResult>(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<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 = static_cast<int>(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<ParticipantManagement::SanitationResult>(sanitationResult);
state.sanitationEvaluation = static_cast<ParticipantManagement::SanitationEvaluation>(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<qint64>(g.lastUpdate));
m_lastManipulationReset = m_dynamicData.sampleTime;
for (const auto &m : m_dynamicData.manipulation)
m_lastManipulationReset = std::min(m_lastManipulationReset, static_cast<qint64>(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<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.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<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