// SPDX-FileCopyrightText: Copyright (C) 2019 swift Project Community / Contributors // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1 #include "simulatorflightgear.h" #include #include #include #include #include #include #include #include "dbus/dbus.h" #include "fgswiftbusserviceproxy.h" #include "fgswiftbustrafficproxy.h" #include "qcompilerdetection.h" #include "config/buildconfig.h" #include "core/aircraftmatcher.h" #include "misc/aviation/aircraftengine.h" #include "misc/aviation/aircraftenginelist.h" #include "misc/aviation/aircrafticaocode.h" #include "misc/aviation/aircraftparts.h" #include "misc/aviation/aircraftsituation.h" #include "misc/aviation/airlineicaocode.h" #include "misc/aviation/altitude.h" #include "misc/aviation/callsign.h" #include "misc/aviation/comsystem.h" #include "misc/aviation/heading.h" #include "misc/aviation/livery.h" #include "misc/aviation/transponder.h" #include "misc/dbusserver.h" #include "misc/geo/coordinategeodetic.h" #include "misc/geo/latitude.h" #include "misc/geo/longitude.h" #include "misc/iterator.h" #include "misc/logmessage.h" #include "misc/mixin/mixincompare.h" #include "misc/network/textmessage.h" #include "misc/pq/angle.h" #include "misc/pq/frequency.h" #include "misc/pq/length.h" #include "misc/pq/pressure.h" #include "misc/pq/speed.h" #include "misc/pq/temperature.h" #include "misc/simulation/aircraftmodel.h" #include "misc/simulation/simulatedaircraft.h" #include "misc/simulation/simulatedaircraftlist.h" #include "misc/verify.h" #include "misc/weather/cloudlayer.h" #include "misc/weather/cloudlayerlist.h" #include "misc/weather/windlayer.h" #include "misc/weather/windlayerlist.h" using namespace swift::config; using namespace swift::misc; using namespace swift::misc::aviation; using namespace swift::misc::network; using namespace swift::misc::physical_quantities; using namespace swift::misc::simulation; using namespace swift::misc::geo; using namespace swift::misc::simulation; using namespace swift::misc::weather; using namespace swift::core; namespace { inline const QString &fgswiftbusServiceName() { static const QString name("org.swift-project.fgswiftbus"); return name; } } // namespace namespace swift::simplugin::flightgear { int FGSWIFTBUS_API_VERSION = -1; QList incompatibleVersions = { 1, 2 }; CSimulatorFlightgear::CSimulatorFlightgear(const CSimulatorPluginInfo &info, IOwnAircraftProvider *ownAircraftProvider, IRemoteAircraftProvider *remoteAircraftProvider, IClientProvider *clientProvider, QObject *parent) : CSimulatorPluginCommon(info, ownAircraftProvider, remoteAircraftProvider, clientProvider, parent) { m_watcher = new QDBusServiceWatcher(this); m_watcher->setWatchMode(QDBusServiceWatcher::WatchForUnregistration); m_watcher->addWatchedService(fgswiftbusServiceName()); m_watcher->setObjectName("QDBusServiceWatcher"); connect(m_watcher, &QDBusServiceWatcher::serviceUnregistered, this, &CSimulatorFlightgear::onDBusServiceUnregistered, Qt::QueuedConnection); m_fastTimer.setObjectName(this->objectName().append(":m_fastTimer")); m_slowTimer.setObjectName(this->objectName().append(":m_slowTimer")); m_pendingAddedTimer.setObjectName(this->objectName().append(":m_pendingAddedTimer")); connect(&m_fastTimer, &QTimer::timeout, this, &CSimulatorFlightgear::fastTimerTimeout); connect(&m_slowTimer, &QTimer::timeout, this, &CSimulatorFlightgear::slowTimerTimeout); connect(&m_pendingAddedTimer, &QTimer::timeout, this, &CSimulatorFlightgear::addNextPendingAircraft); m_fastTimer.start(100); m_slowTimer.start(1000); m_pendingAddedTimer.start(5000); this->setDefaultModel( { "FG c172p", CAircraftModel::TypeModelMatchingDefaultModel, "C172", CAircraftIcaoCode("C172", "L1P") }); this->resetFlightgearData(); } CSimulatorFlightgear::~CSimulatorFlightgear() { this->unload(); } void CSimulatorFlightgear::unload() { if (!this->isConnected()) { return; } // will call disconnect from CSimulatorPluginCommon::unload(); delete m_watcher; m_watcher = nullptr; } QString CSimulatorFlightgear::getStatisticsSimulatorSpecific() const { return QStringLiteral("Add-time: %1ms/%2ms").arg(m_statsAddCurrentTimeMs).arg(m_statsAddMaxTimeMs); } void CSimulatorFlightgear::resetAircraftStatistics() { m_statsAddMaxTimeMs = -1; m_statsAddCurrentTimeMs = -1; } CStatusMessageList CSimulatorFlightgear::getInterpolationMessages(const CCallsign &callsign) const { if (callsign.isEmpty() || !m_flightgearAircraftObjects.contains(callsign)) { return {}; } const CInterpolationAndRenderingSetupPerCallsign setup = this->getInterpolationSetupConsolidated(callsign, false); return m_flightgearAircraftObjects[callsign].getInterpolationMessages(setup.getInterpolatorMode()); } bool CSimulatorFlightgear::testSendSituationAndParts(const CCallsign &callsign, const CAircraftSituation &situation, const CAircraftParts &parts) { if (this->isShuttingDownOrDisconnected()) { return false; } if (!m_trafficProxy) { return false; } if (!m_flightgearAircraftObjects.contains(callsign)) { return false; } int u = 0; if (!situation.isNull()) { PlanesPositions planesPositions; planesPositions.push_back(situation); m_trafficProxy->setPlanesPositions(planesPositions); u++; } if (parts.isNull() && flightgear::FGSWIFTBUS_API_VERSION >= 2) { PlanesSurfaces surfaces; surfaces.push_back(callsign, parts); m_trafficProxy->setPlanesSurfaces(surfaces); u++; } return u > 0; } void CSimulatorFlightgear::clearAllRemoteAircraftData() { m_aircraftAddedFailed.clear(); CSimulatorPluginCommon::clearAllRemoteAircraftData(); } // convert flightgear squawk mode to swift squawk mode CTransponder::TransponderMode xpdrMode(int transponderMode, bool ident) { if (ident) { return CTransponder::StateIdent; } if (transponderMode == 0 || transponderMode == 1 || transponderMode == 2) { return CTransponder::StateStandby; } return CTransponder::ModeC; } // convert swift squawk mode to flightgear squawk mode int xpdrMode(CTransponder::TransponderMode mode) { return mode == CTransponder::StateStandby ? 1 : 4; } void CSimulatorFlightgear::fastTimerTimeout() { if (!this->isShuttingDownOrDisconnected()) { m_serviceProxy->getOwnAircraftSituationData(&m_flightgearData); m_serviceProxy->getOwnAircraftVelocityData(&m_flightgearData); m_serviceProxy->isPausedAsync(&m_simulatorPaused); m_serviceProxy->getCom1ActiveKhzAsync(&m_flightgearData.com1ActiveKhz); m_serviceProxy->getCom1StandbyKhzAsync(&m_flightgearData.com1StandbyKhz); m_serviceProxy->getCom2ActiveKhzAsync(&m_flightgearData.com2ActiveKhz); m_serviceProxy->getCom2StandbyKhzAsync(&m_flightgearData.com2StandbyKhz); m_serviceProxy->getTransponderCodeAsync(&m_flightgearData.xpdrCode); m_serviceProxy->getTransponderModeAsync(&m_flightgearData.xpdrMode); m_serviceProxy->getTransponderIdentAsync(&m_flightgearData.xpdrIdent); m_serviceProxy->getAllWheelsOnGroundAsync(&m_flightgearData.onGroundAll); m_serviceProxy->getGroundElevationAsync(&m_flightgearData.groundElevation); m_serviceProxy->getCom1VolumeAsync(&m_flightgearData.volumeCom1); m_serviceProxy->getCom2VolumeAsync(&m_flightgearData.volumeCom2); CAircraftSituation situation; situation.setPosition({ m_flightgearData.latitudeDeg, m_flightgearData.longitudeDeg, 0 }); situation.setAltitude({ m_flightgearData.altitudeFt, CAltitude::MeanSeaLevel, CLengthUnit::ft() }); situation.setPressureAltitude({ m_flightgearData.pressureAltitudeFt, CAltitude::MeanSeaLevel, CAltitude::PressureAltitude, CLengthUnit::ft() }); situation.setHeading({ m_flightgearData.trueHeadingDeg, CHeading::True, CAngleUnit::deg() }); situation.setPitch({ m_flightgearData.pitchDeg, CAngleUnit::deg() }); situation.setBank({ m_flightgearData.rollDeg, CAngleUnit::deg() }); situation.setGroundSpeed({ m_flightgearData.groundspeedKts, CSpeedUnit::kts() }); situation.setGroundElevation( CAltitude(m_flightgearData.groundElevation, CAltitude::MeanSeaLevel, CLengthUnit::m()), CAircraftSituation::FromProvider); if (!m_simulatorPaused) { situation.setVelocity({ m_flightgearData.velocityXMs, m_flightgearData.velocityYMs, m_flightgearData.velocityZMs, CSpeedUnit::m_s(), m_flightgearData.pitchRateRadPerSec, m_flightgearData.rollRateRadPerSec, m_flightgearData.yawRateRadPerSec, CAngleUnit::rad(), CTimeUnit::s() }); } else { situation.setVelocity( { 0.0, 0.0, 0.0, CSpeedUnit::m_s(), 0.0, 0.0, 0.0, CAngleUnit::rad(), CTimeUnit::s() }); } // Updates // Do not update ICAO codes, as this overrides reverse lookups // updateOwnIcaoCodes(m_flightgearData.aircraftIcaoCode, CAirlineIcaoCode()); this->updateOwnSituationAndGroundElevation(situation); // defaults CSimulatedAircraft myAircraft(getOwnAircraft()); CComSystem com1(myAircraft.getCom1System()); // set defaults CComSystem com2(myAircraft.getCom2System()); CTransponder transponder(myAircraft.getTransponder()); // updates com1.setFrequencyActive(CFrequency(m_flightgearData.com1ActiveKhz, CFrequencyUnit::kHz())); com1.setFrequencyStandby(CFrequency(m_flightgearData.com1StandbyKhz, CFrequencyUnit::kHz())); com1.setVolumeReceive(qRound(m_flightgearData.volumeCom1 * 100)); const bool changedCom1 = myAircraft.getCom1System() != com1; com2.setFrequencyActive(CFrequency(m_flightgearData.com2ActiveKhz, CFrequencyUnit::kHz())); com2.setFrequencyStandby(CFrequency(m_flightgearData.com2StandbyKhz, CFrequencyUnit::kHz())); com2.setVolumeReceive(qRound(m_flightgearData.volumeCom2 * 100)); const bool changedCom2 = myAircraft.getCom2System() != com2; transponder = CTransponder::getStandardTransponder( m_flightgearData.xpdrCode, xpdrMode(m_flightgearData.xpdrMode, m_flightgearData.xpdrIdent)); const bool changedXpr = (myAircraft.getTransponder() != transponder); if (changedCom1 || changedCom2 || changedXpr) { this->updateCockpit(com1, com2, transponder, identifier()); } } } void CSimulatorFlightgear::slowTimerTimeout() { if (!this->isShuttingDownOrDisconnected()) { m_serviceProxy->getAircraftModelPathAsync( &m_flightgearData.aircraftModelPath); // this is NOT the model string m_serviceProxy->getAircraftIcaoCodeAsync(&m_flightgearData.aircraftIcaoCode); m_serviceProxy->getBeaconLightsOnAsync(&m_flightgearData.beaconLightsOn); m_serviceProxy->getLandingLightsOnAsync(&m_flightgearData.landingLightsOn); m_serviceProxy->getNavLightsOnAsync(&m_flightgearData.navLightsOn); m_serviceProxy->getStrobeLightsOnAsync(&m_flightgearData.strobeLightsOn); m_serviceProxy->getTaxiLightsOnAsync(&m_flightgearData.taxiLightsOn); m_serviceProxy->getFlapsDeployRatioAsync(&m_flightgearData.flapsReployRatio); m_serviceProxy->getGearDeployRatioAsync(&m_flightgearData.gearReployRatio); m_serviceProxy->getEngineN1PercentageAsync(&m_flightgearData.enginesN1Percentage); m_serviceProxy->getSpeedBrakeRatioAsync(&m_flightgearData.speedBrakeRatio); CAircraftEngineList engines; for (int engineNumber = 0; engineNumber < m_flightgearData.enginesN1Percentage.size(); ++engineNumber) { // Engine number start counting at 1 // We consider the engine running when N1 is bigger than 5 % CAircraftEngine engine { engineNumber + 1, m_flightgearData.enginesN1Percentage.at(engineNumber) > 5.0 }; engines.push_back(engine); } CAircraftParts parts { { m_flightgearData.strobeLightsOn, m_flightgearData.landingLightsOn, m_flightgearData.taxiLightsOn, m_flightgearData.beaconLightsOn, m_flightgearData.navLightsOn, false }, m_flightgearData.gearReployRatio > 0, static_cast(m_flightgearData.flapsReployRatio * 100), m_flightgearData.speedBrakeRatio > 0.5, engines, m_flightgearData.onGroundAll }; this->updateOwnParts(parts); this->requestRemoteAircraftDataFromFlightgear(); CCallsignSet invalid; for (CFlightgearMPAircraft &flightgearAircraft : m_flightgearAircraftObjects) { // Update remote aircraft to have the latest transponder modes, codes etc. const CCallsign cs = flightgearAircraft.getCallsign(); const CSimulatedAircraft simulatedAircraft = this->getAircraftInRangeForCallsign(cs); if (!simulatedAircraft.hasCallsign()) { if (!cs.isEmpty()) { invalid.insert(cs); } continue; } flightgearAircraft.setSimulatedAircraft(simulatedAircraft); } int i = 0; for (const CCallsign &cs : invalid) { this->triggerRemoveAircraft(cs, ++i * 100); } } } bool CSimulatorFlightgear::isConnected() const { return m_serviceProxy && m_trafficProxy; } bool CSimulatorFlightgear::connectTo() { if (isConnected()) { return true; } QString dbusAddress = m_fgswiftbusServerSetting.getThreadLocal().getDBusServerAddress(); if (CDBusServer::isSessionOrSystemAddress(dbusAddress)) { m_dBusConnection = QDBusConnection::sessionBus(); m_dbusMode = Session; } else if (CDBusServer::isQtDBusAddress(dbusAddress)) { m_dBusConnection = QDBusConnection::connectToPeer(dbusAddress, "fgswiftbus"); if (!m_dBusConnection.isConnected()) { return false; } m_dbusMode = P2P; } m_serviceProxy = new CFGSwiftBusServiceProxy(m_dBusConnection, this); m_trafficProxy = new CFGSwiftBusTrafficProxy(m_dBusConnection, this); const bool s = m_dBusConnection.connect(QString(), DBUS_PATH_LOCAL, DBUS_INTERFACE_LOCAL, "Disconnected", this, SLOT(onDBusServiceUnregistered())); Q_ASSERT(s); if (!m_serviceProxy->isValid() || !m_trafficProxy->isValid()) { this->disconnectFrom(); return false; } emitOwnAircraftModelChanged(m_serviceProxy->getAircraftModelPath(), m_serviceProxy->getAircraftModelFilename(), m_serviceProxy->getAircraftLivery(), m_serviceProxy->getAircraftIcaoCode(), m_serviceProxy->getAircraftModelString(), m_serviceProxy->getAircraftName(), m_serviceProxy->getAircraftDescription()); setSimulatorDetails("Flightgear", {}, ""); connect(m_serviceProxy, &CFGSwiftBusServiceProxy::aircraftModelChanged, this, &CSimulatorFlightgear::emitOwnAircraftModelChanged); connect(m_trafficProxy, &CFGSwiftBusTrafficProxy::simFrame, this, &CSimulatorFlightgear::updateRemoteAircraft); connect(m_trafficProxy, &CFGSwiftBusTrafficProxy::remoteAircraftAdded, this, &CSimulatorFlightgear::onRemoteAircraftAdded); connect(m_trafficProxy, &CFGSwiftBusTrafficProxy::remoteAircraftAddingFailed, this, &CSimulatorFlightgear::onRemoteAircraftAddingFailed); if (m_watcher) { m_watcher->setConnection(m_dBusConnection); } m_trafficProxy->removeAllPlanes(); this->emitSimulatorCombinedStatus(); this->initSimulatorInternals(); return true; } bool CSimulatorFlightgear::disconnectFrom() { if (!this->isConnected()) { return true; } // avoid emit if already disconnected this->disconnectFromDBus(); if (m_watcher) { m_watcher->setConnection(m_dBusConnection); } delete m_serviceProxy; delete m_trafficProxy; m_serviceProxy = nullptr; m_trafficProxy = nullptr; this->emitSimulatorCombinedStatus(); return true; } void CSimulatorFlightgear::onDBusServiceUnregistered() { if (!m_serviceProxy) { return; } CLogMessage(this).info(u"FG DBus service unregistered"); if (m_dbusMode == P2P) { m_dBusConnection.disconnectFromPeer(m_dBusConnection.name()); } m_dBusConnection = QDBusConnection { "default" }; if (m_watcher) { m_watcher->setConnection(m_dBusConnection); } delete m_serviceProxy; delete m_trafficProxy; m_serviceProxy = nullptr; m_trafficProxy = nullptr; this->emitSimulatorCombinedStatus(); } void CSimulatorFlightgear::emitOwnAircraftModelChanged(const QString &path, const QString &filename, const QString &livery, const QString &icao, const QString &modelString, const QString &name, const QString &description) { CAircraftModel model(modelString, CAircraftModel::TypeOwnSimulatorModel, CSimulatorInfo::XPLANE, name, description, icao); if (!livery.isEmpty()) { model.setModelString(model.getModelString()); } model.setFileName(path + "/" + filename); this->reverseLookupAndUpdateOwnAircraftModel(model); } void CSimulatorFlightgear::displayStatusMessage(const CStatusMessage &message) const { // No assert here as status message may come because of network problems if (this->isShuttingDownOrDisconnected()) { return; } // avoid infinite recursion in case this function is called due to a message caused by this very function static bool isInFunction = false; if (isInFunction) { return; } isInFunction = true; m_serviceProxy->addTextMessage("swift: " + message.getMessage()); isInFunction = false; } void CSimulatorFlightgear::displayTextMessage(const network::CTextMessage &message) const { if (this->isShuttingDownOrDisconnected()) { return; } m_serviceProxy->addTextMessage(message.getSenderCallsign().toQString() + ": " + message.getMessage()); } bool CSimulatorFlightgear::isPhysicallyRenderedAircraft(const CCallsign &callsign) const { return m_flightgearAircraftObjects.contains(callsign); } bool CSimulatorFlightgear::updateOwnSimulatorCockpit(const simulation::CSimulatedAircraft &aircraft, const CIdentifier &originator) { if (originator == this->identifier()) { return false; } if (this->isShuttingDownOrDisconnected()) { return false; } auto com1 = CComSystem::getCom1System({ m_flightgearData.com1ActiveKhz, CFrequencyUnit::kHz() }, { m_flightgearData.com1StandbyKhz, CFrequencyUnit::kHz() }); auto com2 = CComSystem::getCom2System({ m_flightgearData.com2ActiveKhz, CFrequencyUnit::kHz() }, { m_flightgearData.com2StandbyKhz, CFrequencyUnit::kHz() }); auto xpdr = CTransponder::getStandardTransponder( m_flightgearData.xpdrCode, xpdrMode(m_flightgearData.xpdrMode, m_flightgearData.xpdrIdent)); if (aircraft.hasChangedCockpitData(com1, com2, xpdr)) { m_flightgearData.com1ActiveKhz = aircraft.getCom1System().getFrequencyActive().valueInteger(CFrequencyUnit::kHz()); m_flightgearData.com1StandbyKhz = aircraft.getCom1System().getFrequencyStandby().valueInteger(CFrequencyUnit::kHz()); m_flightgearData.com2ActiveKhz = aircraft.getCom2System().getFrequencyActive().valueInteger(CFrequencyUnit::kHz()); m_flightgearData.com2StandbyKhz = aircraft.getCom2System().getFrequencyStandby().valueInteger(CFrequencyUnit::kHz()); m_flightgearData.xpdrCode = aircraft.getTransponderCode(); m_flightgearData.xpdrMode = xpdrMode(aircraft.getTransponderMode()); m_serviceProxy->setCom1ActiveKhz(m_flightgearData.com1ActiveKhz); m_serviceProxy->setCom1StandbyKhz(m_flightgearData.com1StandbyKhz); m_serviceProxy->setCom2ActiveKhz(m_flightgearData.com2ActiveKhz); m_serviceProxy->setCom2StandbyKhz(m_flightgearData.com2StandbyKhz); m_serviceProxy->setTransponderCode(m_flightgearData.xpdrCode); m_serviceProxy->setTransponderMode(m_flightgearData.xpdrMode); m_serviceProxy ->cancelAllPendingAsyncCalls(); // in case there is already a reply with some old data incoming return true; } return false; } bool CSimulatorFlightgear::updateOwnSimulatorSelcal(const CSelcal &selcal, const CIdentifier &originator) { if (originator == this->identifier()) { return false; } if (this->isShuttingDownOrDisconnected()) { return false; } //! \fixme KB 8/2017 use SELCAL?? Q_UNUSED(selcal) return false; } bool CSimulatorFlightgear::physicallyAddRemoteAircraft(const CSimulatedAircraft &newRemoteAircraft) { if (this->isShuttingDownOrDisconnected()) { return false; } // entry checks Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "thread"); Q_ASSERT_X(!newRemoteAircraft.getCallsign().isEmpty(), Q_FUNC_INFO, "empty callsign"); Q_ASSERT_X(newRemoteAircraft.hasModelString(), Q_FUNC_INFO, "missing model string"); // crosscheck if still a valid aircraft // it can happen that aircraft has been removed, timed out ... if (!this->isAircraftInRangeOrTestMode(newRemoteAircraft.getCallsign())) { // next cycle will be called by callbacks or timer CLogMessage(this).warning(u"Aircraft '%1' no longer in range, will not add") << newRemoteAircraft.getCallsign(); return false; } if (this->canAddAircraft()) { // no aircraft pending, add this->logAddingAircraftModel(newRemoteAircraft); const qint64 now = QDateTime::currentMSecsSinceEpoch(); m_addingInProgressAircraft.insert(newRemoteAircraft.getCallsign(), now); const QString callsign = newRemoteAircraft.getCallsign().asString(); CAircraftModel aircraftModel = newRemoteAircraft.getModel(); if (aircraftModel.getCallsign() != newRemoteAircraft.getCallsign()) { CLogMessage(this).warning(u"Model for '%1' has no callsign, maybe using a default model") << callsign; aircraftModel.setCallsign(callsign); } const QString livery = aircraftModel.getLivery().getCombinedCode(); //! \todo livery resolution for XP m_trafficProxy->addPlane(callsign, aircraftModel.getFileName(), newRemoteAircraft.getAircraftIcaoCode().getDesignator(), newRemoteAircraft.getAirlineIcaoCode().getDesignator(), livery); PlanesPositions pos; pos.push_back(newRemoteAircraft.getSituation()); m_trafficProxy->setPlanesPositions(pos); if (flightgear::FGSWIFTBUS_API_VERSION >= 2) { PlanesSurfaces surfaces; surfaces.push_back(newRemoteAircraft.getCallsign(), newRemoteAircraft.getParts()); m_trafficProxy->setPlanesSurfaces(surfaces); } } else { // add in queue m_pendingToBeAddedAircraft.replaceOrAdd(newRemoteAircraft); } return true; } bool CSimulatorFlightgear::physicallyRemoveRemoteAircraft(const CCallsign &callsign) { if (this->isShuttingDownOrDisconnected()) { return false; } // only remove from sim Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "wrong thread"); if (callsign.isEmpty()) { return false; } // can happen if an object is not an aircraft // really remove from simulator if (!m_flightgearAircraftObjects.contains(callsign) && !m_pendingToBeAddedAircraft.containsCallsign(callsign) && !m_addingInProgressAircraft.contains(callsign)) { // not existing aircraft return false; } // mark in provider const bool updated = this->updateAircraftRendered(callsign, false); if (updated) { if (m_flightgearAircraftObjects.contains(callsign)) { const CFlightgearMPAircraft &flightgearAircraft = m_flightgearAircraftObjects[callsign]; CSimulatedAircraft aircraft(flightgearAircraft.getAircraft()); aircraft.setRendered(false); emit this->aircraftRenderingChanged(aircraft); } else if (m_pendingToBeAddedAircraft.containsCallsign(callsign)) { CSimulatedAircraft aircraft = m_pendingToBeAddedAircraft.findFirstByCallsign(callsign); aircraft.setRendered(false); emit this->aircraftRenderingChanged(aircraft); } } if (m_addingInProgressAircraft.contains(callsign)) { // we are just about to add that aircraft QPointer myself(this); QTimer::singleShot(TimeoutAdding, this, [=] { if (!myself) { return; } m_addingInProgressAircraft.remove(callsign); // remove as "in progress" this->physicallyRemoveRemoteAircraft(callsign); // and remove from sim. if it was added in the mean time }); return false; } m_trafficProxy->removePlane(callsign.asString()); m_flightgearAircraftObjects.remove(callsign); m_pendingToBeAddedAircraft.removeByCallsign(callsign); // bye return CSimulatorPluginCommon::physicallyRemoveRemoteAircraft(callsign); } int CSimulatorFlightgear::physicallyRemoveAllRemoteAircraft() { if (!this->isConnected()) { return 0; } m_pendingToBeAddedAircraft.clear(); m_addingInProgressAircraft.clear(); return CSimulatorPluginCommon::physicallyRemoveAllRemoteAircraft(); } CCallsignSet CSimulatorFlightgear::physicallyRenderedAircraft() const { return this->getAircraftInRange().findByRendered(true).getCallsigns(); // just a poor workaround } void CSimulatorFlightgear::updateRemoteAircraft() { Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "thread"); const int remoteAircraftNo = this->getAircraftInRangeCount(); if (remoteAircraftNo < 1) { return; } // values used for position and parts m_updateRemoteAircraftInProgress = true; const qint64 currentTimestamp = QDateTime::currentMSecsSinceEpoch(); // interpolation for all remote aircraft PlanesPositions planesPositions; PlanesSurfaces planesSurfaces; PlanesTransponders planesTransponders; uint32_t aircraftNumber = 0; const bool updateAllAircraft = this->isUpdateAllRemoteAircraft(currentTimestamp); const CCallsignSet callsignsInRange = this->getAircraftInRangeCallsigns(); for (const CFlightgearMPAircraft &flightgearAircraft : std::as_const(m_flightgearAircraftObjects)) { const CCallsign callsign(flightgearAircraft.getCallsign()); const bool hasCallsign = !callsign.isEmpty(); if (!hasCallsign) { // does not make sense to continue here SWIFT_VERIFY_X(false, Q_FUNC_INFO, "missing callsign"); continue; } // skip no longer in range if (!callsignsInRange.contains(callsign)) { continue; } planesTransponders.callsigns.push_back(callsign.asString()); planesTransponders.codes.push_back(flightgearAircraft.getAircraft().getTransponderCode()); CTransponder::TransponderMode transponderMode = flightgearAircraft.getAircraft().getTransponderMode(); planesTransponders.idents.push_back(transponderMode == CTransponder::StateIdent); planesTransponders.modeCs.push_back(transponderMode == CTransponder::ModeC); // setup const CInterpolationAndRenderingSetupPerCallsign setup = this->getInterpolationSetupConsolidated(callsign, updateAllAircraft); // interpolated situation/parts const CInterpolationResult result = flightgearAircraft.getInterpolation(currentTimestamp, setup, aircraftNumber++); if (result.getInterpolationStatus().hasValidSituation()) { const CAircraftSituation interpolatedSituation(result); // update situation if (updateAllAircraft || !this->isEqualLastSent(interpolatedSituation)) { this->rememberLastSent(interpolatedSituation); planesPositions.push_back(interpolatedSituation); } } else { CLogMessage(this).warning( this->getInvalidSituationLogMessage(callsign, result.getInterpolationStatus())); } const CAircraftParts parts(result); if (result.getPartsStatus().isSupportingParts() || parts.getPartsDetails() == CAircraftParts::GuessedParts) { if (updateAllAircraft || !this->isEqualLastSent(parts, callsign)) { this->rememberLastSent(parts, callsign); planesSurfaces.push_back(flightgearAircraft.getCallsign(), parts); } } } // all callsigns if (!planesTransponders.isEmpty() && flightgear::FGSWIFTBUS_API_VERSION >= 2) { m_trafficProxy->setPlanesTransponders(planesTransponders); } if (!planesPositions.isEmpty()) { if (CBuildConfig::isLocalDeveloperDebugBuild()) { SWIFT_VERIFY_X(planesPositions.hasSameSizes(), Q_FUNC_INFO, "Mismatching sizes"); } m_trafficProxy->setPlanesPositions(planesPositions); } if (!planesSurfaces.isEmpty() && flightgear::FGSWIFTBUS_API_VERSION >= 2) { m_trafficProxy->setPlanesSurfaces(planesSurfaces); } // stats this->finishUpdateRemoteAircraftAndSetStatistics(currentTimestamp); } void CSimulatorFlightgear::requestRemoteAircraftDataFromFlightgear() { if (this->isShuttingDownOrDisconnected()) { return; } // It is not required to request all elevations and CGs, but only for aircraft "near ground relevant" // - we could use the elevation cache and CG cache to decide if we need to request // - if an aircraft is on ground but not moving, we do not need to request elevation if we already have it (it // will not change CCallsignSet callsigns = m_flightgearAircraftObjects.getAllCallsigns(); const CCallsignSet remove = this->getLastSentCanLikelySkipNearGroundInterpolation().getCallsigns(); callsigns.remove(remove); if (!callsigns.isEmpty()) { this->requestRemoteAircraftDataFromFlightgear(callsigns); } } void CSimulatorFlightgear::requestRemoteAircraftDataFromFlightgear(const CCallsignSet &callsigns) { if (callsigns.isEmpty()) { return; } if (!m_trafficProxy || this->isShuttingDown()) { return; } const QStringList csStrings = callsigns.getCallsignStrings(); QPointer myself(this); m_trafficProxy->getRemoteAircraftData( csStrings, [=](const QStringList &callsigns, const QDoubleList &latitudesDeg, const QDoubleList &longitudesDeg, const QDoubleList &elevationsMeters, const QDoubleList &verticalOffsetsMeters) { if (!myself) { return; } this->updateRemoteAircraftFromSimulator(callsigns, latitudesDeg, longitudesDeg, elevationsMeters, verticalOffsetsMeters); }); } void CSimulatorFlightgear::triggerRequestRemoteAircraftDataFromFlightgear(const CCallsignSet &callsigns) { if (callsigns.isEmpty()) { return; } QPointer myself(this); QTimer::singleShot(0, this, [=] { if (!myself) { return; } this->requestRemoteAircraftDataFromFlightgear(callsigns); }); } void CSimulatorFlightgear::updateRemoteAircraftFromSimulator(const QStringList &callsigns, const QDoubleList &latitudesDeg, const QDoubleList &longitudesDeg, const QDoubleList &elevationsMeters, const QDoubleList &verticalOffsetsMeters) { const int size = callsigns.size(); // we skip if we are not near ground if (CBuildConfig::isLocalDeveloperDebugBuild()) { Q_ASSERT_X(elevationsMeters.size() == size, Q_FUNC_INFO, "Wrong elevations"); Q_ASSERT_X(latitudesDeg.size() == size, Q_FUNC_INFO, "Wrong latitudesDeg"); Q_ASSERT_X(longitudesDeg.size() == size, Q_FUNC_INFO, "Wrong longitudesDeg"); Q_ASSERT_X(verticalOffsetsMeters.size() == size, Q_FUNC_INFO, "Wrong CG"); } const CCallsignSet logCallsigns = this->getLogCallsigns(); for (int i = 0; i < size; i++) { const bool emptyCs = callsigns[i].isEmpty(); SWIFT_VERIFY_X(!emptyCs, Q_FUNC_INFO, "Need callsign"); if (emptyCs) { continue; } const CCallsign cs(callsigns[i]); if (!m_flightgearAircraftObjects.contains(cs)) { continue; } const CFlightgearMPAircraft fgAircraft = m_flightgearAircraftObjects[cs]; SWIFT_VERIFY_X(fgAircraft.hasCallsign(), Q_FUNC_INFO, "Need callsign"); if (!fgAircraft.hasCallsign()) { continue; } CElevationPlane elevation = CElevationPlane::null(); if (!std::isnan(elevationsMeters[i])) { const CAltitude elevationAlt = CAltitude(elevationsMeters[i], CLengthUnit::m(), CLengthUnit::ft()); elevation = CElevationPlane(CLatitude(latitudesDeg[i], CAngleUnit::deg()), CLongitude(longitudesDeg[i], CAngleUnit::deg()), elevationAlt, CElevationPlane::singlePointRadius()); } const double cgValue = verticalOffsetsMeters[i]; // XP offset is swift CG const CLength cg = std::isnan(cgValue) ? CLength::null() : CLength(cgValue, CLengthUnit::m(), CLengthUnit::ft()); // if we knew "on ground" here we could set it as parameter of rememberElevationAndSimulatorCG this->rememberElevationAndSimulatorCG(cs, fgAircraft.getAircraftModel(), false, elevation, cg); // loopback if (logCallsigns.contains(cs)) { this->addLoopbackSituation(cs, elevation, cg); } } } void CSimulatorFlightgear::disconnectFromDBus() { if (m_dBusConnection.isConnected()) { if (m_trafficProxy) { m_trafficProxy->cleanup(); } if (m_dbusMode == P2P) { QDBusConnection::disconnectFromPeer(m_dBusConnection.name()); } else { QDBusConnection::disconnectFromBus(m_dBusConnection.name()); } } m_dBusConnection = QDBusConnection { "default" }; } void CSimulatorFlightgear::onRemoteAircraftAdded(const QString &callsign) { SWIFT_VERIFY_X(!callsign.isEmpty(), Q_FUNC_INFO, "Need callsign"); if (callsign.isEmpty()) { return; } const CCallsign cs(callsign); CSimulatedAircraft addedRemoteAircraft = this->getAircraftInRangeForCallsign(cs); // statistics bool wasPending = false; if (m_addingInProgressAircraft.contains(cs)) { wasPending = true; const qint64 wasStartedMs = m_addingInProgressAircraft.value(cs); const qint64 deltaTimeMs = QDateTime::currentMSecsSinceEpoch() - wasStartedMs; m_statsAddCurrentTimeMs = deltaTimeMs; if (deltaTimeMs > m_statsAddMaxTimeMs) { m_statsAddMaxTimeMs = deltaTimeMs; } m_addingInProgressAircraft.remove(cs); } if (!addedRemoteAircraft.hasCallsign()) { CLogMessage(this).warning(u"Aircraft '%1' no longer in range, will be removed") << callsign; this->triggerRemoveAircraft(cs, TimeoutAdding); return; } CLogMessage(this).info(u"Added aircraft '%1'") << callsign; if (!wasPending) { // timeout? // slow adding? CLogMessage(this).warning(u"Added callsign '%1' was not in progress anymore. Timeout?") << callsign; } const bool rendered = true; addedRemoteAircraft.setRendered(rendered); this->updateAircraftRendered(cs, rendered); this->triggerRequestRemoteAircraftDataFromFlightgear(cs); this->triggerAddNextPendingAircraft(); Q_ASSERT_X(addedRemoteAircraft.hasCallsign(), Q_FUNC_INFO, "No callsign"); Q_ASSERT_X(addedRemoteAircraft.getCallsign() == cs, Q_FUNC_INFO, "No callsign"); m_flightgearAircraftObjects.insert(cs, CFlightgearMPAircraft(addedRemoteAircraft, this, &m_interpolationLogger)); emit this->aircraftRenderingChanged(addedRemoteAircraft); } bool CSimulatorFlightgear::requestElevation(const swift::misc::geo::ICoordinateGeodetic &reference, const swift::misc::aviation::CCallsign &callsign) { if (this->isShuttingDownOrDisconnected()) { return false; } if (reference.isNull()) { return false; } CCoordinateGeodetic pos(reference); if (!pos.hasMSLGeodeticHeight()) { // testing showed: height has an influence on the returned result // - the most accurate value seems to be returned if the height is close to the elevation // - in normal scenarios there is no much difference of the results if 0 is used // - in Lukla (9200ft MSL) the difference between 0 and 9200 is around 1ft // - in the LOWW scenario using 50000ft MSL results in around 3ft too low elevation static const CAltitude alt(0, CAltitude::MeanSeaLevel, CLengthUnit::ft()); pos.setGeodeticHeight(alt); } using namespace std::placeholders; auto callback = [this](auto &&plane, auto &&callsign) { callbackReceivedRequestedElevation(std::forward(plane), std::forward(callsign), false); }; // Request m_trafficProxy->getElevationAtPosition(callsign, pos.latitude().value(CAngleUnit::deg()), pos.longitude().value(CAngleUnit::deg()), pos.geodeticHeight().value(CLengthUnit::m()), callback); emit this->requestedElevation(callsign); return true; } void CSimulatorFlightgear::onRemoteAircraftAddingFailed(const QString &callsign) { SWIFT_VERIFY_X(!callsign.isEmpty(), Q_FUNC_INFO, "Need callsign"); if (callsign.isEmpty()) { return; } const CCallsign cs(callsign); CSimulatedAircraft failedRemoteAircraft = this->getAircraftInRangeForCallsign(cs); if (failedRemoteAircraft.hasCallsign()) { CLogMessage(this).warning(u"Adding aircraft failed: '%1'") << callsign; failedRemoteAircraft.setRendered(false); } else { CLogMessage(this).warning(u"Adding '%1' failed, but aircraft no longer in range, will be removed") << callsign; } const bool wasPending = (static_cast(m_addingInProgressAircraft.remove(cs)) > 0); Q_UNUSED(wasPending) if (failedRemoteAircraft.hasCallsign() && !m_aircraftAddedFailed.containsCallsign(cs)) { m_aircraftAddedFailed.push_back(failedRemoteAircraft); m_pendingToBeAddedAircraft.replaceOrAdd(failedRemoteAircraft); // try a second time } this->triggerAddNextPendingAircraft(); } void CSimulatorFlightgear::addNextPendingAircraft() { if (m_pendingToBeAddedAircraft.isEmpty()) { return; } // no more pending // housekeeping this->detectTimeoutAdding(); // check if can add if (!this->canAddAircraft()) { return; } // next add cycle const CSimulatedAircraft newRemoteAircraft = m_pendingToBeAddedAircraft.front(); m_pendingToBeAddedAircraft.pop_front(); CLogMessage(this).info(u"Adding next pending aircraft '%1', pending %2, in progress %3") << newRemoteAircraft.getCallsignAsString() << m_pendingToBeAddedAircraft.size() << m_addingInProgressAircraft.size(); this->physicallyAddRemoteAircraft(newRemoteAircraft); } void CSimulatorFlightgear::triggerAddNextPendingAircraft() { QPointer myself(this); QTimer::singleShot(100, this, [=] { if (!myself) { return; } this->addNextPendingAircraft(); }); } int CSimulatorFlightgear::detectTimeoutAdding() { if (m_addingInProgressAircraft.isEmpty()) { return 0; } const qint64 timeout = QDateTime::currentMSecsSinceEpoch() + TimeoutAdding; CCallsignSet timeoutCallsigns; const QList addingCallsigns = m_addingInProgressAircraft.keys(); for (const CCallsign &cs : addingCallsigns) { if (m_addingInProgressAircraft.value(cs) < timeout) { continue; } timeoutCallsigns.push_back(cs); } for (const CCallsign &cs : std::as_const(timeoutCallsigns)) { m_addingInProgressAircraft.remove(cs); CLogMessage(this).warning(u"Adding for '%1' timed out") << cs.asString(); } return timeoutCallsigns.size(); } void CSimulatorFlightgear::triggerRemoveAircraft(const CCallsign &callsign, qint64 deferMs) { QPointer myself(this); QTimer::singleShot(deferMs, this, [=] { if (!myself) { return; } this->physicallyRemoveRemoteAircraft(callsign); }); } QPair CSimulatorFlightgear::minMaxTimestampsAddInProgress() const { static const QPair empty(-1, -1); if (m_addingInProgressAircraft.isEmpty()) { return empty; } const QList ts = m_addingInProgressAircraft.values(); const auto mm = std::minmax_element(ts.constBegin(), ts.constEnd()); return { *mm.first, *mm.second }; } bool CSimulatorFlightgear::canAddAircraft() const { if (this->getModelSet().isEmpty()) { return false; } if (m_addingInProgressAircraft.isEmpty()) { return true; } // check const qint64 now = QDateTime::currentMSecsSinceEpoch(); const QPair tsMM = this->minMaxTimestampsAddInProgress(); const qint64 deltaLatest = now - tsMM.second; const bool canAdd = (deltaLatest > TimeoutAdding); return canAdd; } ISimulator *CSimulatorFlightgearFactory::create(const CSimulatorPluginInfo &info, IOwnAircraftProvider *ownAircraftProvider, IRemoteAircraftProvider *remoteAircraftProvider, IClientProvider *clientProvider) { return new CSimulatorFlightgear(info, ownAircraftProvider, remoteAircraftProvider, clientProvider, this); } CSimulatorFlightgearListener::CSimulatorFlightgearListener(const CSimulatorPluginInfo &info) : ISimulatorListener(info) { constexpr int QueryInterval = 5 * 1000; // 5 seconds m_timer.setInterval(QueryInterval); m_timer.setObjectName(this->objectName().append(":m_timer")); connect(&m_timer, &QTimer::timeout, this, &CSimulatorFlightgearListener::checkConnection); } void CSimulatorFlightgearListener::startImpl() { m_timer.start(); } void CSimulatorFlightgearListener::stopImpl() { m_timer.stop(); } void CSimulatorFlightgearListener::checkImpl() { if (!m_timer.isActive()) { return; } if (this->isShuttingDown()) { return; } m_timer.start(); // restart because we will check just now QPointer myself(this); QTimer::singleShot(0, this, [=] { if (!myself) { return; } checkConnection(); }); } void CSimulatorFlightgearListener::checkConnection() { if (this->isShuttingDown()) { return; } Q_ASSERT_X(!CThreadUtils::thisIsMainThread(), Q_FUNC_INFO, "Expect to run in background"); QString dbusAddress = m_fgSswiftBusServerSetting.getThreadLocal().getDBusServerAddress(); if (CDBusServer::isSessionOrSystemAddress(dbusAddress)) { checkConnectionViaSessionBus(); } else if (CDBusServer::isQtDBusAddress(dbusAddress)) { checkConnectionViaPeer(dbusAddress); } } void CSimulatorFlightgearListener::checkConnectionViaSessionBus() { m_conn = QDBusConnection::sessionBus(); if (!m_conn.isConnected()) { m_conn.disconnectFromBus(m_conn.name()); return; } checkConnectionCommon(); m_conn.disconnectFromBus(m_conn.name()); } void CSimulatorFlightgearListener::checkConnectionViaPeer(const QString &address) { m_conn = QDBusConnection::connectToPeer(address, "fgswiftbus"); if (!m_conn.isConnected()) { // This is required to cleanup the connection in QtDBus m_conn.disconnectFromPeer(m_conn.name()); return; } checkConnectionCommon(); m_conn.disconnectFromPeer(m_conn.name()); } void CSimulatorFlightgearListener::checkConnectionCommon() { CFGSwiftBusServiceProxy service(m_conn); CFGSwiftBusTrafficProxy traffic(m_conn); bool result = service.isValid() && traffic.isValid(); if (!result) { return; } flightgear::FGSWIFTBUS_API_VERSION = service.getVersionNumber(); if (flightgear::incompatibleVersions.contains(flightgear::FGSWIFTBUS_API_VERSION)) { CLogMessage(this).error(u"This version of swift is not compatible with this Flightgear version. For " u"further information check http://wiki.flightgear.org/Swift."); return; } if (!traffic.initialize()) { CLogMessage(this).error(u"Connection to FGSwiftBus successful, but could not initialize FGSwiftBus."); return; } const MultiplayerAcquireInfo info = traffic.acquireMultiplayerPlanes(); if (!info.hasAcquired) { const QString owner = info.owner.trimmed().isEmpty() ? QStringLiteral("Some/this plugin/application") : info.owner.trimmed(); CLogMessage(this).error( u"Connection to FGSwiftBus successful, but could not acquire multiplayer planes. '%1' has acquired " u"them already. Disable '%2' or remove it if not required and reload FGSwiftBus.") << owner << owner.toLower(); return; } emit simulatorStarted(getPluginInfo()); } void CSimulatorFlightgearListener::serviceRegistered(const QString &serviceName) { if (serviceName == fgswiftbusServiceName()) { emit simulatorStarted(getPluginInfo()); } m_conn.disconnectFromBus(m_conn.name()); } void CSimulatorFlightgearListener::fgSwiftBusServerSettingChanged() { this->stop(); this->start(); } } // namespace swift::simplugin::flightgear