swift
simulatorflightgear.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2019 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
4 #include "simulatorflightgear.h"
5 
6 #include <math.h>
7 
8 #include <QColor>
9 #include <QDBusServiceWatcher>
10 #include <QPointer>
11 #include <QString>
12 #include <QTimer>
13 #include <QtGlobal>
14 
15 #include "dbus/dbus.h"
16 #include "fgswiftbusserviceproxy.h"
17 #include "fgswiftbustrafficproxy.h"
18 #include "qcompilerdetection.h"
19 
20 #include "config/buildconfig.h"
21 #include "core/aircraftmatcher.h"
28 #include "misc/aviation/altitude.h"
29 #include "misc/aviation/callsign.h"
31 #include "misc/aviation/heading.h"
32 #include "misc/aviation/livery.h"
34 #include "misc/dbusserver.h"
36 #include "misc/geo/latitude.h"
37 #include "misc/geo/longitude.h"
38 #include "misc/iterator.h"
39 #include "misc/logmessage.h"
42 #include "misc/pq/angle.h"
43 #include "misc/pq/frequency.h"
44 #include "misc/pq/length.h"
45 #include "misc/pq/pressure.h"
46 #include "misc/pq/speed.h"
47 #include "misc/pq/temperature.h"
51 #include "misc/verify.h"
54 #include "misc/weather/windlayer.h"
56 
57 using namespace swift::config;
58 using namespace swift::misc;
59 using namespace swift::misc::aviation;
60 using namespace swift::misc::network;
61 using namespace swift::misc::physical_quantities;
62 using namespace swift::misc::simulation;
63 using namespace swift::misc::geo;
64 using namespace swift::misc::simulation;
65 using namespace swift::misc::weather;
66 using namespace swift::core;
67 
68 namespace
69 {
70  inline const QString &fgswiftbusServiceName()
71  {
72  static const QString name("org.swift-project.fgswiftbus");
73  return name;
74  }
75 } // namespace
76 
77 namespace swift::simplugin::flightgear
78 {
79  int FGSWIFTBUS_API_VERSION = -1;
80  QList<int> incompatibleVersions = { 1, 2 };
81  CSimulatorFlightgear::CSimulatorFlightgear(const CSimulatorPluginInfo &info,
82  IOwnAircraftProvider *ownAircraftProvider,
83  IRemoteAircraftProvider *remoteAircraftProvider,
84  IClientProvider *clientProvider, QObject *parent)
85  : CSimulatorPluginCommon(info, ownAircraftProvider, remoteAircraftProvider, clientProvider, parent)
86  {
87  m_watcher = new QDBusServiceWatcher(this);
88  m_watcher->setWatchMode(QDBusServiceWatcher::WatchForUnregistration);
89  m_watcher->addWatchedService(fgswiftbusServiceName());
90  m_watcher->setObjectName("QDBusServiceWatcher");
91  connect(m_watcher, &QDBusServiceWatcher::serviceUnregistered, this,
92  &CSimulatorFlightgear::onDBusServiceUnregistered, Qt::QueuedConnection);
93 
94  m_fastTimer.setObjectName(this->objectName().append(":m_fastTimer"));
95  m_slowTimer.setObjectName(this->objectName().append(":m_slowTimer"));
96  m_pendingAddedTimer.setObjectName(this->objectName().append(":m_pendingAddedTimer"));
97  connect(&m_fastTimer, &QTimer::timeout, this, &CSimulatorFlightgear::fastTimerTimeout);
98  connect(&m_slowTimer, &QTimer::timeout, this, &CSimulatorFlightgear::slowTimerTimeout);
99  connect(&m_pendingAddedTimer, &QTimer::timeout, this, &CSimulatorFlightgear::addNextPendingAircraft);
100  m_fastTimer.start(100);
101  m_slowTimer.start(1000);
102  m_airportUpdater.start(60 * 1000);
103  m_pendingAddedTimer.start(5000);
104 
105  this->setDefaultModel(
106  { "FG c172p", CAircraftModel::TypeModelMatchingDefaultModel, "C172", CAircraftIcaoCode("C172", "L1P") });
107  this->resetFlightgearData();
108  }
109 
111 
113  {
114  if (!this->isConnected()) { return; }
115 
116  // will call disconnect from
117  CSimulatorPluginCommon::unload();
118  delete m_watcher;
119  m_watcher = nullptr;
120  }
121 
123  {
124  return QStringLiteral("Add-time: %1ms/%2ms").arg(m_statsAddCurrentTimeMs).arg(m_statsAddMaxTimeMs);
125  }
126 
128  {
129  m_statsAddMaxTimeMs = -1;
130  m_statsAddCurrentTimeMs = -1;
131  }
132 
134  {
135  if (callsign.isEmpty() || !m_flightgearAircraftObjects.contains(callsign)) { return CStatusMessageList(); }
137  this->getInterpolationSetupConsolidated(callsign, false);
138  return m_flightgearAircraftObjects[callsign].getInterpolationMessages(setup.getInterpolatorMode());
139  }
140 
142  const CAircraftParts &parts)
143  {
144  if (this->isShuttingDownOrDisconnected()) { return false; }
145  if (!m_trafficProxy) { return false; }
146  if (!m_flightgearAircraftObjects.contains(callsign)) { return false; }
147 
148  int u = 0;
149  if (!situation.isNull())
150  {
151  PlanesPositions planesPositions;
152  planesPositions.push_back(situation);
153  m_trafficProxy->setPlanesPositions(planesPositions);
154  u++;
155  }
156 
157  if (parts.isNull() && flightgear::FGSWIFTBUS_API_VERSION >= 2)
158  {
159  PlanesSurfaces surfaces;
160  surfaces.push_back(callsign, parts);
161  m_trafficProxy->setPlanesSurfaces(surfaces);
162  u++;
163  }
164  return u > 0;
165  }
166 
168  {
169  m_aircraftAddedFailed.clear();
170  CSimulatorPluginCommon::clearAllRemoteAircraftData();
171  }
172 
173  // convert flightgear squawk mode to swift squawk mode
174  CTransponder::TransponderMode xpdrMode(int transponderMode, bool ident)
175  {
176  if (ident) { return CTransponder::StateIdent; }
177  if (transponderMode == 0 || transponderMode == 1 || transponderMode == 2) { return CTransponder::StateStandby; }
178  return CTransponder::ModeC;
179  }
180 
181  // convert swift squawk mode to flightgear squawk mode
182  int xpdrMode(CTransponder::TransponderMode mode) { return mode == CTransponder::StateStandby ? 1 : 4; }
183 
184  void CSimulatorFlightgear::fastTimerTimeout()
185  {
186  if (!this->isShuttingDownOrDisconnected())
187  {
188  m_serviceProxy->getOwnAircraftSituationData(&m_flightgearData);
189  m_serviceProxy->getOwnAircraftVelocityData(&m_flightgearData);
190  m_serviceProxy->isPausedAsync(&m_simulatorPaused);
191  m_serviceProxy->getCom1ActiveKhzAsync(&m_flightgearData.com1ActiveKhz);
192  m_serviceProxy->getCom1StandbyKhzAsync(&m_flightgearData.com1StandbyKhz);
193  m_serviceProxy->getCom2ActiveKhzAsync(&m_flightgearData.com2ActiveKhz);
194  m_serviceProxy->getCom2StandbyKhzAsync(&m_flightgearData.com2StandbyKhz);
195  m_serviceProxy->getTransponderCodeAsync(&m_flightgearData.xpdrCode);
196  m_serviceProxy->getTransponderModeAsync(&m_flightgearData.xpdrMode);
197  m_serviceProxy->getTransponderIdentAsync(&m_flightgearData.xpdrIdent);
198  m_serviceProxy->getAllWheelsOnGroundAsync(&m_flightgearData.onGroundAll);
199  m_serviceProxy->getGroundElevationAsync(&m_flightgearData.groundElevation);
200  m_serviceProxy->getCom1VolumeAsync(&m_flightgearData.volumeCom1);
201  m_serviceProxy->getCom2VolumeAsync(&m_flightgearData.volumeCom2);
202 
203  CAircraftSituation situation;
204  situation.setPosition({ m_flightgearData.latitudeDeg, m_flightgearData.longitudeDeg, 0 });
205  situation.setAltitude({ m_flightgearData.altitudeFt, CAltitude::MeanSeaLevel, CLengthUnit::ft() });
206  situation.setPressureAltitude({ m_flightgearData.pressureAltitudeFt, CAltitude::MeanSeaLevel,
207  CAltitude::PressureAltitude, CLengthUnit::ft() });
208  situation.setHeading({ m_flightgearData.trueHeadingDeg, CHeading::True, CAngleUnit::deg() });
209  situation.setPitch({ m_flightgearData.pitchDeg, CAngleUnit::deg() });
210  situation.setBank({ m_flightgearData.rollDeg, CAngleUnit::deg() });
211  situation.setGroundSpeed({ m_flightgearData.groundspeedKts, CSpeedUnit::kts() });
212  situation.setGroundElevation(
213  CAltitude(m_flightgearData.groundElevation, CAltitude::MeanSeaLevel, CLengthUnit::m()),
214  CAircraftSituation::FromProvider);
215 
216  if (!m_simulatorPaused)
217  {
218  situation.setVelocity({ m_flightgearData.velocityXMs, m_flightgearData.velocityYMs,
219  m_flightgearData.velocityZMs, CSpeedUnit::m_s(),
220  m_flightgearData.pitchRateRadPerSec, m_flightgearData.rollRateRadPerSec,
221  m_flightgearData.yawRateRadPerSec, CAngleUnit::rad(), CTimeUnit::s() });
222  }
223  else
224  {
225  situation.setVelocity(
226  { 0.0, 0.0, 0.0, CSpeedUnit::m_s(), 0.0, 0.0, 0.0, CAngleUnit::rad(), CTimeUnit::s() });
227  }
228 
229  // Updates
230  // Do not update ICAO codes, as this overrides reverse lookups
231  // updateOwnIcaoCodes(m_flightgearData.aircraftIcaoCode, CAirlineIcaoCode());
232  this->updateOwnSituationAndGroundElevation(situation);
233 
234  // defaults
235  CSimulatedAircraft myAircraft(getOwnAircraft());
236  CComSystem com1(myAircraft.getCom1System()); // set defaults
237  CComSystem com2(myAircraft.getCom2System());
238  CTransponder transponder(myAircraft.getTransponder());
239 
240  // updates
241  com1.setFrequencyActive(CFrequency(m_flightgearData.com1ActiveKhz, CFrequencyUnit::kHz()));
242  com1.setFrequencyStandby(CFrequency(m_flightgearData.com1StandbyKhz, CFrequencyUnit::kHz()));
243  com1.setVolumeReceive(qRound(m_flightgearData.volumeCom1 * 100));
244  const bool changedCom1 = myAircraft.getCom1System() != com1;
245 
246  com2.setFrequencyActive(CFrequency(m_flightgearData.com2ActiveKhz, CFrequencyUnit::kHz()));
247  com2.setFrequencyStandby(CFrequency(m_flightgearData.com2StandbyKhz, CFrequencyUnit::kHz()));
248  com2.setVolumeReceive(qRound(m_flightgearData.volumeCom2 * 100));
249  const bool changedCom2 = myAircraft.getCom2System() != com2;
250 
251  transponder = CTransponder::getStandardTransponder(
252  m_flightgearData.xpdrCode, xpdrMode(m_flightgearData.xpdrMode, m_flightgearData.xpdrIdent));
253  const bool changedXpr = (myAircraft.getTransponder() != transponder);
254 
255  if (changedCom1 || changedCom2 || changedXpr)
256  {
257  this->updateCockpit(com1, com2, transponder, identifier());
258  }
259  }
260  }
261 
262  void CSimulatorFlightgear::slowTimerTimeout()
263  {
264  if (!this->isShuttingDownOrDisconnected())
265  {
266  m_serviceProxy->getAircraftModelPathAsync(
267  &m_flightgearData.aircraftModelPath); // this is NOT the model string
268  m_serviceProxy->getAircraftIcaoCodeAsync(&m_flightgearData.aircraftIcaoCode);
269  m_serviceProxy->getBeaconLightsOnAsync(&m_flightgearData.beaconLightsOn);
270  m_serviceProxy->getLandingLightsOnAsync(&m_flightgearData.landingLightsOn);
271  m_serviceProxy->getNavLightsOnAsync(&m_flightgearData.navLightsOn);
272  m_serviceProxy->getStrobeLightsOnAsync(&m_flightgearData.strobeLightsOn);
273  m_serviceProxy->getTaxiLightsOnAsync(&m_flightgearData.taxiLightsOn);
274  m_serviceProxy->getFlapsDeployRatioAsync(&m_flightgearData.flapsReployRatio);
275  m_serviceProxy->getGearDeployRatioAsync(&m_flightgearData.gearReployRatio);
276  m_serviceProxy->getEngineN1PercentageAsync(&m_flightgearData.enginesN1Percentage);
277  m_serviceProxy->getSpeedBrakeRatioAsync(&m_flightgearData.speedBrakeRatio);
278 
279  CAircraftEngineList engines;
280  for (int engineNumber = 0; engineNumber < m_flightgearData.enginesN1Percentage.size(); ++engineNumber)
281  {
282  // Engine number start counting at 1
283  // We consider the engine running when N1 is bigger than 5 %
284  CAircraftEngine engine { engineNumber + 1,
285  m_flightgearData.enginesN1Percentage.at(engineNumber) > 5.0 };
286  engines.push_back(engine);
287  }
288 
289  CAircraftParts parts { { m_flightgearData.strobeLightsOn, m_flightgearData.landingLightsOn,
290  m_flightgearData.taxiLightsOn, m_flightgearData.beaconLightsOn,
291  m_flightgearData.navLightsOn, false },
292  m_flightgearData.gearReployRatio > 0,
293  static_cast<int>(m_flightgearData.flapsReployRatio * 100),
294  m_flightgearData.speedBrakeRatio > 0.5,
295  engines,
296  m_flightgearData.onGroundAll };
297 
298  this->updateOwnParts(parts);
299  this->requestRemoteAircraftDataFromFlightgear();
300 
301  CCallsignSet invalid;
302  for (CFlightgearMPAircraft &flightgearAircraft : m_flightgearAircraftObjects)
303  {
304  // Update remote aircraft to have the latest transponder modes, codes etc.
305  const CCallsign cs = flightgearAircraft.getCallsign();
306  const CSimulatedAircraft simulatedAircraft = this->getAircraftInRangeForCallsign(cs);
307  if (!simulatedAircraft.hasCallsign())
308  {
309  if (!cs.isEmpty()) { invalid.insert(cs); }
310  continue;
311  }
312  flightgearAircraft.setSimulatedAircraft(simulatedAircraft);
313  }
314 
315  int i = 0;
316 
317  for (const CCallsign &cs : invalid) { this->triggerRemoveAircraft(cs, ++i * 100); }
318  }
319  }
320 
321  bool CSimulatorFlightgear::isConnected() const { return m_serviceProxy && m_trafficProxy; }
322 
324  {
325  if (isConnected()) { return true; }
326  QString dbusAddress = m_fgswiftbusServerSetting.getThreadLocal().getDBusServerAddress();
327 
328  if (CDBusServer::isSessionOrSystemAddress(dbusAddress))
329  {
330  m_dBusConnection = QDBusConnection::sessionBus();
331  m_dbusMode = Session;
332  }
333  else if (CDBusServer::isQtDBusAddress(dbusAddress))
334  {
335  m_dBusConnection = QDBusConnection::connectToPeer(dbusAddress, "fgswiftbus");
336  if (!m_dBusConnection.isConnected()) { return false; }
337  m_dbusMode = P2P;
338  }
339 
340  m_serviceProxy = new CFGSwiftBusServiceProxy(m_dBusConnection, this);
341  m_trafficProxy = new CFGSwiftBusTrafficProxy(m_dBusConnection, this);
342 
343  const bool s = m_dBusConnection.connect(QString(), DBUS_PATH_LOCAL, DBUS_INTERFACE_LOCAL, "Disconnected", this,
344  SLOT(onDBusServiceUnregistered()));
345  Q_ASSERT(s);
346  if (!m_serviceProxy->isValid() || !m_trafficProxy->isValid())
347  {
348  this->disconnectFrom();
349  return false;
350  }
351 
352  emitOwnAircraftModelChanged(m_serviceProxy->getAircraftModelPath(), m_serviceProxy->getAircraftModelFilename(),
353  m_serviceProxy->getAircraftLivery(), m_serviceProxy->getAircraftIcaoCode(),
354  m_serviceProxy->getAircraftModelString(), m_serviceProxy->getAircraftName(),
355  m_serviceProxy->getAircraftDescription());
356  setSimulatorDetails("Flightgear", {}, "");
357  connect(m_serviceProxy, &CFGSwiftBusServiceProxy::aircraftModelChanged, this,
358  &CSimulatorFlightgear::emitOwnAircraftModelChanged);
359  connect(m_trafficProxy, &CFGSwiftBusTrafficProxy::simFrame, this, &CSimulatorFlightgear::updateRemoteAircraft);
360  connect(m_trafficProxy, &CFGSwiftBusTrafficProxy::remoteAircraftAdded, this,
361  &CSimulatorFlightgear::onRemoteAircraftAdded);
362  connect(m_trafficProxy, &CFGSwiftBusTrafficProxy::remoteAircraftAddingFailed, this,
363  &CSimulatorFlightgear::onRemoteAircraftAddingFailed);
364  if (m_watcher) { m_watcher->setConnection(m_dBusConnection); }
365  m_trafficProxy->removeAllPlanes();
367 
368  this->initSimulatorInternals();
369  return true;
370  }
371 
373  {
374  if (!this->isConnected()) { return true; } // avoid emit if already disconnected
375  this->disconnectFromDBus();
376  if (m_watcher) { m_watcher->setConnection(m_dBusConnection); }
377  delete m_serviceProxy;
378  delete m_trafficProxy;
379  m_serviceProxy = nullptr;
380  m_trafficProxy = nullptr;
382  return true;
383  }
384 
385  void CSimulatorFlightgear::onDBusServiceUnregistered()
386  {
387  if (!m_serviceProxy) { return; }
388  CLogMessage(this).info(u"FG DBus service unregistered");
389 
390  if (m_dbusMode == P2P) { m_dBusConnection.disconnectFromPeer(m_dBusConnection.name()); }
391  m_dBusConnection = QDBusConnection { "default" };
392  if (m_watcher) { m_watcher->setConnection(m_dBusConnection); }
393  delete m_serviceProxy;
394  delete m_trafficProxy;
395  m_serviceProxy = nullptr;
396  m_trafficProxy = nullptr;
398  }
399 
400  void CSimulatorFlightgear::emitOwnAircraftModelChanged(const QString &path, const QString &filename,
401  const QString &livery, const QString &icao,
402  const QString &modelString, const QString &name,
403  const QString &description)
404  {
405  CAircraftModel model(modelString, CAircraftModel::TypeOwnSimulatorModel, CSimulatorInfo::XPLANE, name,
406  description, icao);
407  if (!livery.isEmpty()) { model.setModelString(model.getModelString()); }
408  model.setFileName(path + "/" + filename);
409 
411  }
412 
414  {
415  // No assert here as status message may come because of network problems
416  if (this->isShuttingDownOrDisconnected()) { return; }
417 
418  // avoid infinite recursion in case this function is called due to a message caused by this very function
419  static bool isInFunction = false;
420  if (isInFunction) { return; }
421  isInFunction = true;
422 
423  m_serviceProxy->addTextMessage("swift: " + message.getMessage());
424  isInFunction = false;
425  }
426 
428  {
429  if (this->isShuttingDownOrDisconnected()) { return; }
430  m_serviceProxy->addTextMessage(message.getSenderCallsign().toQString() + ": " + message.getMessage());
431  }
432 
434  {
435  return m_flightgearAircraftObjects.contains(callsign);
436  }
437 
439  const CIdentifier &originator)
440  {
441  if (originator == this->identifier()) { return false; }
442  if (this->isShuttingDownOrDisconnected()) { return false; }
443 
444  auto com1 = CComSystem::getCom1System({ m_flightgearData.com1ActiveKhz, CFrequencyUnit::kHz() },
445  { m_flightgearData.com1StandbyKhz, CFrequencyUnit::kHz() });
446  auto com2 = CComSystem::getCom2System({ m_flightgearData.com2ActiveKhz, CFrequencyUnit::kHz() },
447  { m_flightgearData.com2StandbyKhz, CFrequencyUnit::kHz() });
448  auto xpdr = CTransponder::getStandardTransponder(
449  m_flightgearData.xpdrCode, xpdrMode(m_flightgearData.xpdrMode, m_flightgearData.xpdrIdent));
450  if (aircraft.hasChangedCockpitData(com1, com2, xpdr))
451  {
452  m_flightgearData.com1ActiveKhz =
453  aircraft.getCom1System().getFrequencyActive().valueInteger(CFrequencyUnit::kHz());
454  m_flightgearData.com1StandbyKhz =
455  aircraft.getCom1System().getFrequencyStandby().valueInteger(CFrequencyUnit::kHz());
456  m_flightgearData.com2ActiveKhz =
457  aircraft.getCom2System().getFrequencyActive().valueInteger(CFrequencyUnit::kHz());
458  m_flightgearData.com2StandbyKhz =
459  aircraft.getCom2System().getFrequencyStandby().valueInteger(CFrequencyUnit::kHz());
460  m_flightgearData.xpdrCode = aircraft.getTransponderCode();
461  m_flightgearData.xpdrMode = xpdrMode(aircraft.getTransponderMode());
462  m_serviceProxy->setCom1ActiveKhz(m_flightgearData.com1ActiveKhz);
463  m_serviceProxy->setCom1StandbyKhz(m_flightgearData.com1StandbyKhz);
464  m_serviceProxy->setCom2ActiveKhz(m_flightgearData.com2ActiveKhz);
465  m_serviceProxy->setCom2StandbyKhz(m_flightgearData.com2StandbyKhz);
466  m_serviceProxy->setTransponderCode(m_flightgearData.xpdrCode);
467  m_serviceProxy->setTransponderMode(m_flightgearData.xpdrMode);
468 
469  m_serviceProxy
470  ->cancelAllPendingAsyncCalls(); // in case there is already a reply with some old data incoming
471  return true;
472  }
473  return false;
474  }
475 
477  {
478  if (originator == this->identifier()) { return false; }
479  if (this->isShuttingDownOrDisconnected()) { return false; }
480 
482  Q_UNUSED(selcal)
483 
484  return false;
485  }
486 
488  {
489  if (this->isShuttingDownOrDisconnected()) { return false; }
490 
491  // entry checks
492  Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "thread");
493  Q_ASSERT_X(!newRemoteAircraft.getCallsign().isEmpty(), Q_FUNC_INFO, "empty callsign");
494  Q_ASSERT_X(newRemoteAircraft.hasModelString(), Q_FUNC_INFO, "missing model string");
495 
496  // crosscheck if still a valid aircraft
497  // it can happen that aircraft has been removed, timed out ...
498  if (!this->isAircraftInRangeOrTestMode(newRemoteAircraft.getCallsign()))
499  {
500  // next cycle will be called by callbacks or timer
501  CLogMessage(this).warning(u"Aircraft '%1' no longer in range, will not add")
502  << newRemoteAircraft.getCallsign();
503  return false;
504  }
505 
506  if (this->canAddAircraft())
507  {
508  // no aircraft pending, add
509  this->logAddingAircraftModel(newRemoteAircraft);
510  const qint64 now = QDateTime::currentMSecsSinceEpoch();
511  m_addingInProgressAircraft.insert(newRemoteAircraft.getCallsign(), now);
512  const QString callsign = newRemoteAircraft.getCallsign().asString();
513  CAircraftModel aircraftModel = newRemoteAircraft.getModel();
514  if (aircraftModel.getCallsign() != newRemoteAircraft.getCallsign())
515  {
516  CLogMessage(this).warning(u"Model for '%1' has no callsign, maybe using a default model") << callsign;
517  aircraftModel.setCallsign(callsign);
518  }
519  const QString livery = aircraftModel.getLivery().getCombinedCode();
520  m_trafficProxy->addPlane(callsign, aircraftModel.getFileName(),
521  newRemoteAircraft.getAircraftIcaoCode().getDesignator(),
522  newRemoteAircraft.getAirlineIcaoCode().getDesignator(), livery);
523 
524  PlanesPositions pos;
525  pos.push_back(newRemoteAircraft.getSituation());
526  m_trafficProxy->setPlanesPositions(pos);
527  if (flightgear::FGSWIFTBUS_API_VERSION >= 2)
528  {
529  PlanesSurfaces surfaces;
530  surfaces.push_back(newRemoteAircraft.getCallsign(), newRemoteAircraft.getParts());
531  m_trafficProxy->setPlanesSurfaces(surfaces);
532  }
533  }
534  else
535  {
536  // add in queue
537  m_pendingToBeAddedAircraft.replaceOrAdd(newRemoteAircraft);
538  }
539  return true;
540  }
541 
543  {
544  if (this->isShuttingDownOrDisconnected()) { return false; }
545 
546  // only remove from sim
547  Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "wrong thread");
548  if (callsign.isEmpty()) { return false; } // can happen if an object is not an aircraft
549 
550  // really remove from simulator
551  if (!m_flightgearAircraftObjects.contains(callsign) && !m_pendingToBeAddedAircraft.containsCallsign(callsign) &&
552  !m_addingInProgressAircraft.contains(callsign))
553  {
554  // not existing aircraft
555  return false;
556  }
557 
558  // mark in provider
559  const bool updated = this->updateAircraftRendered(callsign, false);
560  if (updated)
561  {
562  if (m_flightgearAircraftObjects.contains(callsign))
563  {
564  const CFlightgearMPAircraft &flightgearAircraft = m_flightgearAircraftObjects[callsign];
565  CSimulatedAircraft aircraft(flightgearAircraft.getAircraft());
566  aircraft.setRendered(false);
567  emit this->aircraftRenderingChanged(aircraft);
568  }
569  else if (m_pendingToBeAddedAircraft.containsCallsign(callsign))
570  {
571  CSimulatedAircraft aircraft = m_pendingToBeAddedAircraft.findFirstByCallsign(callsign);
572  aircraft.setRendered(false);
573  emit this->aircraftRenderingChanged(aircraft);
574  }
575  }
576 
577  if (m_addingInProgressAircraft.contains(callsign))
578  {
579  // we are just about to add that aircraft
580  QPointer<CSimulatorFlightgear> myself(this);
581  QTimer::singleShot(TimeoutAdding, this, [=] {
582  if (!myself) { return; }
583  m_addingInProgressAircraft.remove(callsign); // remove as "in progress"
584  this->physicallyRemoveRemoteAircraft(callsign); // and remove from sim. if it was added in the mean time
585  });
586  return false;
587  }
588 
589  m_trafficProxy->removePlane(callsign.asString());
590  m_flightgearAircraftObjects.remove(callsign);
591  m_pendingToBeAddedAircraft.removeByCallsign(callsign);
592 
593  // bye
594  return CSimulatorPluginCommon::physicallyRemoveRemoteAircraft(callsign);
595  }
596 
598  {
599  if (!this->isConnected()) { return 0; }
600  m_pendingToBeAddedAircraft.clear();
601  m_addingInProgressAircraft.clear();
602  return CSimulatorPluginCommon::physicallyRemoveAllRemoteAircraft();
603  }
604 
606  {
607  return this->getAircraftInRange().findByRendered(true).getCallsigns(); // just a poor workaround
608  }
609 
610  void CSimulatorFlightgear::updateRemoteAircraft()
611  {
612  Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "thread");
613 
614  const int remoteAircraftNo = this->getAircraftInRangeCount();
615  if (remoteAircraftNo < 1) { return; }
616 
617  // values used for position and parts
619  const qint64 currentTimestamp = QDateTime::currentMSecsSinceEpoch();
620 
621  // interpolation for all remote aircraft
622  PlanesPositions planesPositions;
623  PlanesSurfaces planesSurfaces;
624  PlanesTransponders planesTransponders;
625 
626  uint32_t aircraftNumber = 0;
627  const bool updateAllAircraft = this->isUpdateAllRemoteAircraft(currentTimestamp);
628  const CCallsignSet callsignsInRange = this->getAircraftInRangeCallsigns();
629  for (const CFlightgearMPAircraft &flightgearAircraft : std::as_const(m_flightgearAircraftObjects))
630  {
631  const CCallsign callsign(flightgearAircraft.getCallsign());
632  const bool hasCallsign = !callsign.isEmpty();
633  if (!hasCallsign)
634  {
635  // does not make sense to continue here
636  SWIFT_VERIFY_X(false, Q_FUNC_INFO, "missing callsign");
637  continue;
638  }
639 
640  // skip no longer in range
641  if (!callsignsInRange.contains(callsign)) { continue; }
642 
643  planesTransponders.callsigns.push_back(callsign.asString());
644  planesTransponders.codes.push_back(flightgearAircraft.getAircraft().getTransponderCode());
645  CTransponder::TransponderMode transponderMode = flightgearAircraft.getAircraft().getTransponderMode();
646  planesTransponders.idents.push_back(transponderMode == CTransponder::StateIdent);
647  planesTransponders.modeCs.push_back(transponderMode == CTransponder::ModeC);
648 
649  // setup
651  this->getInterpolationSetupConsolidated(callsign, updateAllAircraft);
652 
653  // interpolated situation/parts
654  const CInterpolationResult result =
655  flightgearAircraft.getInterpolation(currentTimestamp, setup, aircraftNumber++);
657  {
658  const CAircraftSituation interpolatedSituation(result);
659 
660  // update situation
661  if (updateAllAircraft || !this->isEqualLastSent(interpolatedSituation))
662  {
663  this->rememberLastSent(interpolatedSituation);
664  planesPositions.push_back(interpolatedSituation);
665  }
666  }
667  else
668  {
669  CLogMessage(this).warning(
670  this->getInvalidSituationLogMessage(callsign, result.getInterpolationStatus()));
671  }
672 
673  const CAircraftParts parts(result);
674  if (result.getPartsStatus().isSupportingParts() || parts.getPartsDetails() == CAircraftParts::GuessedParts)
675  {
676  if (updateAllAircraft || !this->isEqualLastSent(parts, callsign))
677  {
678  this->rememberLastSent(parts, callsign);
679  planesSurfaces.push_back(flightgearAircraft.getCallsign(), parts);
680  }
681  }
682 
683  } // all callsigns
684 
685  if (!planesTransponders.isEmpty() && flightgear::FGSWIFTBUS_API_VERSION >= 2)
686  {
687  m_trafficProxy->setPlanesTransponders(planesTransponders);
688  }
689 
690  if (!planesPositions.isEmpty())
691  {
692  if (CBuildConfig::isLocalDeveloperDebugBuild())
693  {
694  SWIFT_VERIFY_X(planesPositions.hasSameSizes(), Q_FUNC_INFO, "Mismatching sizes");
695  }
696  m_trafficProxy->setPlanesPositions(planesPositions);
697  }
698 
699  if (!planesSurfaces.isEmpty() && flightgear::FGSWIFTBUS_API_VERSION >= 2)
700  {
701  m_trafficProxy->setPlanesSurfaces(planesSurfaces);
702  }
703 
704  // stats
705  this->finishUpdateRemoteAircraftAndSetStatistics(currentTimestamp);
706  }
707 
708  void CSimulatorFlightgear::requestRemoteAircraftDataFromFlightgear()
709  {
710  if (this->isShuttingDownOrDisconnected()) { return; }
711 
712  // It is not required to request all elevations and CGs, but only for aircraft "near ground relevant"
713  // - we could use the elevation cache and CG cache to decide if we need to request
714  // - if an aircraft is on ground but not moving, we do not need to request elevation if we already have it (it
715  // will not change
716  CCallsignSet callsigns = m_flightgearAircraftObjects.getAllCallsigns();
718  callsigns.remove(remove);
719  if (!callsigns.isEmpty()) { this->requestRemoteAircraftDataFromFlightgear(callsigns); }
720  }
721 
722  void CSimulatorFlightgear::requestRemoteAircraftDataFromFlightgear(const CCallsignSet &callsigns)
723  {
724  if (callsigns.isEmpty()) { return; }
725  if (!m_trafficProxy || this->isShuttingDown()) { return; }
726  const QStringList csStrings = callsigns.getCallsignStrings();
727  QPointer<CSimulatorFlightgear> myself(this);
728  m_trafficProxy->getRemoteAircraftData(
729  csStrings,
730  [=](const QStringList &callsigns, const QDoubleList &latitudesDeg, const QDoubleList &longitudesDeg,
731  const QDoubleList &elevationsMeters, const QDoubleList &verticalOffsetsMeters) {
732  if (!myself) { return; }
733  this->updateRemoteAircraftFromSimulator(callsigns, latitudesDeg, longitudesDeg, elevationsMeters,
734  verticalOffsetsMeters);
735  });
736  }
737 
738  void CSimulatorFlightgear::triggerRequestRemoteAircraftDataFromFlightgear(const CCallsignSet &callsigns)
739  {
740  if (callsigns.isEmpty()) { return; }
741  QPointer<CSimulatorFlightgear> myself(this);
742  QTimer::singleShot(0, this, [=] {
743  if (!myself) { return; }
744  this->requestRemoteAircraftDataFromFlightgear(callsigns);
745  });
746  }
747 
748  void CSimulatorFlightgear::updateRemoteAircraftFromSimulator(const QStringList &callsigns,
749  const QDoubleList &latitudesDeg,
750  const QDoubleList &longitudesDeg,
751  const QDoubleList &elevationsMeters,
752  const QDoubleList &verticalOffsetsMeters)
753  {
754  const int size = callsigns.size();
755 
756  // we skip if we are not near ground
757  if (CBuildConfig::isLocalDeveloperDebugBuild())
758  {
759  Q_ASSERT_X(elevationsMeters.size() == size, Q_FUNC_INFO, "Wrong elevations");
760  Q_ASSERT_X(latitudesDeg.size() == size, Q_FUNC_INFO, "Wrong latitudesDeg");
761  Q_ASSERT_X(longitudesDeg.size() == size, Q_FUNC_INFO, "Wrong longitudesDeg");
762  Q_ASSERT_X(verticalOffsetsMeters.size() == size, Q_FUNC_INFO, "Wrong CG");
763  }
764 
765  const CCallsignSet logCallsigns = this->getLogCallsigns();
766  for (int i = 0; i < size; i++)
767  {
768  const bool emptyCs = callsigns[i].isEmpty();
769  SWIFT_VERIFY_X(!emptyCs, Q_FUNC_INFO, "Need callsign");
770  if (emptyCs) { continue; }
771  const CCallsign cs(callsigns[i]);
772  if (!m_flightgearAircraftObjects.contains(cs)) { continue; }
773  const CFlightgearMPAircraft fgAircraft = m_flightgearAircraftObjects[cs];
774  SWIFT_VERIFY_X(fgAircraft.hasCallsign(), Q_FUNC_INFO, "Need callsign");
775  if (!fgAircraft.hasCallsign()) { continue; }
776 
777  CElevationPlane elevation = CElevationPlane::null();
778  if (!std::isnan(elevationsMeters[i]))
779  {
780  const CAltitude elevationAlt = CAltitude(elevationsMeters[i], CLengthUnit::m(), CLengthUnit::ft());
781  elevation = CElevationPlane(CLatitude(latitudesDeg[i], CAngleUnit::deg()),
782  CLongitude(longitudesDeg[i], CAngleUnit::deg()), elevationAlt,
783  CElevationPlane::singlePointRadius());
784  }
785 
786  const double cgValue = verticalOffsetsMeters[i]; // XP offset is swift CG
787  const CLength cg =
788  std::isnan(cgValue) ? CLength::null() : CLength(cgValue, CLengthUnit::m(), CLengthUnit::ft());
789 
790  // if we knew "on ground" here we could set it as parameter of rememberElevationAndSimulatorCG
791  this->rememberElevationAndSimulatorCG(cs, fgAircraft.getAircraftModel(), false, elevation, cg);
792 
793  // loopback
794  if (logCallsigns.contains(cs)) { this->addLoopbackSituation(cs, elevation, cg); }
795  }
796  }
797 
798  void CSimulatorFlightgear::disconnectFromDBus()
799  {
800  if (m_dBusConnection.isConnected())
801  {
802  if (m_trafficProxy) { m_trafficProxy->cleanup(); }
803 
804  if (m_dbusMode == P2P) { QDBusConnection::disconnectFromPeer(m_dBusConnection.name()); }
805  else { QDBusConnection::disconnectFromBus(m_dBusConnection.name()); }
806  }
807  m_dBusConnection = QDBusConnection { "default" };
808  }
809 
810  void CSimulatorFlightgear::onRemoteAircraftAdded(const QString &callsign)
811  {
812  SWIFT_VERIFY_X(!callsign.isEmpty(), Q_FUNC_INFO, "Need callsign");
813  if (callsign.isEmpty()) { return; }
814  const CCallsign cs(callsign);
815  CSimulatedAircraft addedRemoteAircraft = this->getAircraftInRangeForCallsign(cs);
816 
817  // statistics
818  bool wasPending = false;
819  if (m_addingInProgressAircraft.contains(cs))
820  {
821  wasPending = true;
822  const qint64 wasStartedMs = m_addingInProgressAircraft.value(cs);
823  const qint64 deltaTimeMs = QDateTime::currentMSecsSinceEpoch() - wasStartedMs;
824  m_statsAddCurrentTimeMs = deltaTimeMs;
825  if (deltaTimeMs > m_statsAddMaxTimeMs) { m_statsAddMaxTimeMs = deltaTimeMs; }
826  m_addingInProgressAircraft.remove(cs);
827  }
828 
829  if (!addedRemoteAircraft.hasCallsign())
830  {
831  CLogMessage(this).warning(u"Aircraft '%1' no longer in range, will be removed") << callsign;
832  this->triggerRemoveAircraft(cs, TimeoutAdding);
833  return;
834  }
835 
836  CLogMessage(this).info(u"Added aircraft '%1'") << callsign;
837  if (!wasPending)
838  {
839  // timeout?
840  // slow adding?
841  CLogMessage(this).warning(u"Added callsign '%1' was not in progress anymore. Timeout?") << callsign;
842  }
843 
844  const bool rendered = true;
845  addedRemoteAircraft.setRendered(rendered);
846  this->updateAircraftRendered(cs, rendered);
847  this->triggerRequestRemoteAircraftDataFromFlightgear(cs);
848  this->triggerAddNextPendingAircraft();
849 
850  Q_ASSERT_X(addedRemoteAircraft.hasCallsign(), Q_FUNC_INFO, "No callsign");
851  Q_ASSERT_X(addedRemoteAircraft.getCallsign() == cs, Q_FUNC_INFO, "No callsign");
852  m_flightgearAircraftObjects.insert(cs,
853  CFlightgearMPAircraft(addedRemoteAircraft, this, &m_interpolationLogger));
854  emit this->aircraftRenderingChanged(addedRemoteAircraft);
855  }
856 
858  const swift::misc::aviation::CCallsign &callsign)
859  {
860  if (this->isShuttingDownOrDisconnected()) { return false; }
861  if (reference.isNull()) { return false; }
862 
863  CCoordinateGeodetic pos(reference);
864  if (!pos.hasMSLGeodeticHeight())
865  {
866  // testing showed: height has an influence on the returned result
867  // - the most accurate value seems to be returned if the height is close to the elevation
868  // - in normal scenarios there is no much difference of the results if 0 is used
869  // - in Lukla (9200ft MSL) the difference between 0 and 9200 is around 1ft
870  // - in the LOWW scenario using 50000ft MSL results in around 3ft too low elevation
871  static const CAltitude alt(0, CAltitude::MeanSeaLevel, CLengthUnit::ft());
872  pos.setGeodeticHeight(alt);
873  }
874 
875  using namespace std::placeholders;
876  auto callback = std::bind(&CSimulatorFlightgear::callbackReceivedRequestedElevation, this, _1, _2, false);
877 
878  // Request
879  m_trafficProxy->getElevationAtPosition(callsign, pos.latitude().value(CAngleUnit::deg()),
880  pos.longitude().value(CAngleUnit::deg()),
881  pos.geodeticHeight().value(CLengthUnit::m()), callback);
882  emit this->requestedElevation(callsign);
883  return true;
884  }
885 
886  void CSimulatorFlightgear::onRemoteAircraftAddingFailed(const QString &callsign)
887  {
888  SWIFT_VERIFY_X(!callsign.isEmpty(), Q_FUNC_INFO, "Need callsign");
889  if (callsign.isEmpty()) { return; }
890  const CCallsign cs(callsign);
891  CSimulatedAircraft failedRemoteAircraft = this->getAircraftInRangeForCallsign(cs);
892 
893  if (failedRemoteAircraft.hasCallsign())
894  {
895  CLogMessage(this).warning(u"Adding aircraft failed: '%1'") << callsign;
896  failedRemoteAircraft.setRendered(false);
897  }
898  else
899  {
900  CLogMessage(this).warning(u"Adding '%1' failed, but aircraft no longer in range, will be removed")
901  << callsign;
902  }
903 
904  const bool wasPending = (static_cast<int>(m_addingInProgressAircraft.remove(cs)) > 0);
905  Q_UNUSED(wasPending)
906 
907  if (failedRemoteAircraft.hasCallsign() && !m_aircraftAddedFailed.containsCallsign(cs))
908  {
909  m_aircraftAddedFailed.push_back(failedRemoteAircraft);
910  m_pendingToBeAddedAircraft.replaceOrAdd(failedRemoteAircraft); // try a second time
911  }
912  this->triggerAddNextPendingAircraft();
913  }
914 
915  void CSimulatorFlightgear::addNextPendingAircraft()
916  {
917  if (m_pendingToBeAddedAircraft.isEmpty()) { return; } // no more pending
918 
919  // housekeeping
920  this->detectTimeoutAdding();
921 
922  // check if can add
923  if (!this->canAddAircraft()) { return; }
924 
925  // next add cycle
926  const CSimulatedAircraft newRemoteAircraft = m_pendingToBeAddedAircraft.front();
927  m_pendingToBeAddedAircraft.pop_front();
928  CLogMessage(this).info(u"Adding next pending aircraft '%1', pending %2, in progress %3")
929  << newRemoteAircraft.getCallsignAsString() << m_pendingToBeAddedAircraft.size()
930  << m_addingInProgressAircraft.size();
931  this->physicallyAddRemoteAircraft(newRemoteAircraft);
932  }
933 
934  void CSimulatorFlightgear::triggerAddNextPendingAircraft()
935  {
936  QPointer<CSimulatorFlightgear> myself(this);
937  QTimer::singleShot(100, this, [=] {
938  if (!myself) { return; }
939  this->addNextPendingAircraft();
940  });
941  }
942 
943  int CSimulatorFlightgear::detectTimeoutAdding()
944  {
945  if (m_addingInProgressAircraft.isEmpty()) { return 0; }
946  const qint64 timeout = QDateTime::currentMSecsSinceEpoch() + TimeoutAdding;
947  CCallsignSet timeoutCallsigns;
948  const QList<CCallsign> addingCallsigns = m_addingInProgressAircraft.keys();
949  for (const CCallsign &cs : addingCallsigns)
950  {
951  if (m_addingInProgressAircraft.value(cs) < timeout) { continue; }
952  timeoutCallsigns.push_back(cs);
953  }
954 
955  for (const CCallsign &cs : std::as_const(timeoutCallsigns))
956  {
957  m_addingInProgressAircraft.remove(cs);
958  CLogMessage(this).warning(u"Adding for '%1' timed out") << cs.asString();
959  }
960 
961  return timeoutCallsigns.size();
962  }
963 
964  void CSimulatorFlightgear::triggerRemoveAircraft(const CCallsign &callsign, qint64 deferMs)
965  {
966  QPointer<CSimulatorFlightgear> myself(this);
967  QTimer::singleShot(deferMs, this, [=] {
968  if (!myself) { return; }
969  this->physicallyRemoveRemoteAircraft(callsign);
970  });
971  }
972 
973  QPair<qint64, qint64> CSimulatorFlightgear::minMaxTimestampsAddInProgress() const
974  {
975  static const QPair<qint64, qint64> empty(-1, -1);
976  if (m_addingInProgressAircraft.isEmpty()) { return empty; }
977  const QList<qint64> ts = m_addingInProgressAircraft.values();
978  const auto mm = std::minmax_element(ts.constBegin(), ts.constEnd());
979  return QPair<qint64, qint64>(*mm.first, *mm.second);
980  }
981 
982  bool CSimulatorFlightgear::canAddAircraft() const
983  {
984  if (this->getModelSet().isEmpty()) { return false; }
985  if (m_addingInProgressAircraft.isEmpty()) { return true; }
986 
987  // check
988  const qint64 now = QDateTime::currentMSecsSinceEpoch();
989  const QPair<qint64, qint64> tsMM = this->minMaxTimestampsAddInProgress();
990  const qint64 deltaLatest = now - tsMM.second;
991  const bool canAdd = (deltaLatest > TimeoutAdding);
992  return canAdd;
993  }
994 
996  IOwnAircraftProvider *ownAircraftProvider,
997  IRemoteAircraftProvider *remoteAircraftProvider,
998  IClientProvider *clientProvider)
999  {
1000  return new CSimulatorFlightgear(info, ownAircraftProvider, remoteAircraftProvider, clientProvider, this);
1001  }
1002 
1004  : ISimulatorListener(info)
1005  {
1006  constexpr int QueryInterval = 5 * 1000; // 5 seconds
1007  m_timer.setInterval(QueryInterval);
1008  m_timer.setObjectName(this->objectName().append(":m_timer"));
1009  connect(&m_timer, &QTimer::timeout, this, &CSimulatorFlightgearListener::checkConnection);
1010  }
1011 
1012  void CSimulatorFlightgearListener::startImpl() { m_timer.start(); }
1013 
1014  void CSimulatorFlightgearListener::stopImpl() { m_timer.stop(); }
1015 
1017  {
1018  if (!m_timer.isActive()) { return; }
1019  if (this->isShuttingDown()) { return; }
1020 
1021  m_timer.start(); // restart because we will check just now
1022  QPointer<CSimulatorFlightgearListener> myself(this);
1023  QTimer::singleShot(0, this, [=] {
1024  if (!myself) { return; }
1025  checkConnection();
1026  });
1027  }
1028 
1029  void CSimulatorFlightgearListener::checkConnection()
1030  {
1031  if (this->isShuttingDown()) { return; }
1032  Q_ASSERT_X(!CThreadUtils::thisIsMainThread(), Q_FUNC_INFO, "Expect to run in background");
1033 
1034  QString dbusAddress = m_fgSswiftBusServerSetting.getThreadLocal().getDBusServerAddress();
1035  if (CDBusServer::isSessionOrSystemAddress(dbusAddress)) { checkConnectionViaSessionBus(); }
1036  else if (CDBusServer::isQtDBusAddress(dbusAddress)) { checkConnectionViaPeer(dbusAddress); }
1037  }
1038 
1039  void CSimulatorFlightgearListener::checkConnectionViaSessionBus()
1040  {
1041  m_conn = QDBusConnection::sessionBus();
1042  if (!m_conn.isConnected())
1043  {
1044  m_conn.disconnectFromBus(m_conn.name());
1045  return;
1046  }
1047  checkConnectionCommon();
1048  m_conn.disconnectFromBus(m_conn.name());
1049  }
1050 
1051  void CSimulatorFlightgearListener::checkConnectionViaPeer(const QString &address)
1052  {
1053  m_conn = QDBusConnection::connectToPeer(address, "fgswiftbus");
1054  if (!m_conn.isConnected())
1055  {
1056  // This is required to cleanup the connection in QtDBus
1057  m_conn.disconnectFromPeer(m_conn.name());
1058  return;
1059  }
1060  checkConnectionCommon();
1061  m_conn.disconnectFromPeer(m_conn.name());
1062  }
1063 
1064  void CSimulatorFlightgearListener::checkConnectionCommon()
1065  {
1066  CFGSwiftBusServiceProxy service(m_conn);
1067  CFGSwiftBusTrafficProxy traffic(m_conn);
1068 
1069  bool result = service.isValid() && traffic.isValid();
1070  if (!result) { return; }
1071 
1072  flightgear::FGSWIFTBUS_API_VERSION = service.getVersionNumber();
1073 
1074  if (flightgear::incompatibleVersions.contains(flightgear::FGSWIFTBUS_API_VERSION))
1075  {
1076  CLogMessage(this).error(u"This version of swift is not compatible with this Flightgear version. For "
1077  u"further information check http://wiki.flightgear.org/Swift.");
1078  return;
1079  }
1080  if (!traffic.initialize())
1081  {
1082  CLogMessage(this).error(u"Connection to FGSwiftBus successful, but could not initialize FGSwiftBus.");
1083  return;
1084  }
1085 
1086  const MultiplayerAcquireInfo info = traffic.acquireMultiplayerPlanes();
1087  if (!info.hasAcquired)
1088  {
1089  const QString owner =
1090  info.owner.trimmed().isEmpty() ? QStringLiteral("Some/this plugin/application") : info.owner.trimmed();
1091  CLogMessage(this).error(
1092  u"Connection to FGSwiftBus successful, but could not acquire multiplayer planes. '%1' has acquired "
1093  u"them already. Disable '%2' or remove it if not required and reload FGSwiftBus.")
1094  << owner << owner.toLower();
1095  return;
1096  }
1097 
1099  }
1100 
1101  void CSimulatorFlightgearListener::serviceRegistered(const QString &serviceName)
1102  {
1103  if (serviceName == fgswiftbusServiceName()) { emit simulatorStarted(getPluginInfo()); }
1104  m_conn.disconnectFromBus(m_conn.name());
1105  }
1106 
1107  void CSimulatorFlightgearListener::fgSwiftBusServerSettingChanged()
1108  {
1109  this->stop();
1110  this->start();
1111  }
1112 } // namespace swift::simplugin::flightgear
Interface to a simulator.
Definition: simulator.h:59
virtual void callbackReceivedRequestedElevation(const swift::misc::geo::CElevationPlane &plane, const swift::misc::aviation::CCallsign &callsign, bool isWater)
A requested elevation has been received.
Definition: simulator.cpp:254
bool addLoopbackSituation(const swift::misc::aviation::CAircraftSituation &situation)
Add a loopback situation if logging is enabled.
Definition: simulator.cpp:180
bool isEqualLastSent(const swift::misc::aviation::CAircraftSituation &compare) const
Equal to last sent situation.
Definition: simulator.cpp:551
bool updateOwnSituationAndGroundElevation(const swift::misc::aviation::CAircraftSituation &situation)
Update own aircraft position and if suitable use it to update ground elevation.
Definition: simulator.cpp:1057
bool isUpdateAllRemoteAircraft(qint64 currentTimestamp=-1) const
Do update all remote aircraft?
Definition: simulator.cpp:212
virtual bool isShuttingDown() const
Is overall (swift) application shutting down.
Definition: simulator.h:211
void requestedElevation(const swift::misc::aviation::CCallsign &callsign)
Requested elevation, call pending.
bool m_updateRemoteAircraftInProgress
currently updating remote aircraft
Definition: simulator.h:556
void rememberElevationAndSimulatorCG(const swift::misc::aviation::CCallsign &callsign, const swift::misc::simulation::CAircraftModel &model, bool likelyOnGroundElevation, const swift::misc::geo::CElevationPlane &elevation, const swift::misc::physical_quantities::CLength &simulatorCG)
Set elevation and CG in the providers and for auto publishing.
Definition: simulator.cpp:770
bool isAircraftInRangeOrTestMode(const swift::misc::aviation::CCallsign &callsign) const
Test version aware version of isAircraftInRange.
Definition: simulator.cpp:949
void finishUpdateRemoteAircraftAndSetStatistics(qint64 startTime, bool limited=false)
Update stats and flags.
Definition: simulator.cpp:1029
void reverseLookupAndUpdateOwnAircraftModel(const swift::misc::simulation::CAircraftModel &model)
Set own model.
Definition: simulator.cpp:1212
swift::misc::aviation::CAircraftSituationList getLastSentCanLikelySkipNearGroundInterpolation() const
Last sent situations.
Definition: simulator.cpp:586
swift::misc::simulation::CAircraftModelList getModelSet() const
Get the model set.
Definition: simulator.cpp:1102
void emitSimulatorCombinedStatus(SimulatorStatus oldStatus=Unspecified)
Emit the combined status.
Definition: simulator.cpp:812
void logAddingAircraftModel(const swift::misc::simulation::CSimulatedAircraft &aircraft) const
Unified qeeing aircraft message.
Definition: simulator.cpp:1132
swift::misc::simulation::CInterpolationAndRenderingSetupPerCallsign getInterpolationSetupConsolidated(const swift::misc::aviation::CCallsign &callsign, bool forceFullUpdate) const
Consolidate setup with other data like from swift::misc::simulation::IRemoteAircraftProvider.
Definition: simulator.cpp:237
swift::misc::simulation::CInterpolationLogger m_interpolationLogger
log.interpolation
Definition: simulator.h:579
void aircraftRenderingChanged(const swift::misc::simulation::CSimulatedAircraft &aircraft)
Aircraft rendering changed.
virtual bool isShuttingDownOrDisconnected() const
Shutting down or disconnected?
Definition: simulator.h:214
virtual void initSimulatorInternals()
Init the internals info from the simulator.
Definition: simulator.cpp:762
QString getInvalidSituationLogMessage(const swift::misc::aviation::CCallsign &callsign, const swift::misc::simulation::CInterpolationStatus &status, const QString &details={}) const
Info about invalid situation.
Definition: simulator.cpp:1018
void rememberLastSent(const swift::misc::aviation::CAircraftSituation &sent)
Remember as last sent.
Definition: simulator.cpp:567
Interface to a simulator listener.
Definition: simulator.h:630
const swift::misc::simulation::CSimulatorPluginInfo & getPluginInfo() const
Corresponding info.
Definition: simulator.h:638
void simulatorStarted(const swift::misc::simulation::CSimulatorPluginInfo &info)
Emitted when the listener discovers the simulator running.
virtual bool isShuttingDown() const
Overall (swift) application shutting down.
Definition: simulator.cpp:1256
void start()
Start listening for the simulator to start.
Definition: simulator.cpp:1265
void stop()
Stops listening.
Definition: simulator.cpp:1282
const T & getThreadLocal() const
Read the current value.
Definition: valuecache.h:400
size_type size() const
Returns number of elements in the collection.
Definition: collection.h:185
void remove(const T &object)
Efficient remove using the find and erase of the implementation container. Typically O(log n).
Definition: collection.h:307
iterator insert(const_iterator hint, const T &value)
For compatibility with std::inserter.
Definition: collection.h:199
bool isEmpty() const
Synonym for empty.
Definition: collection.h:191
iterator push_back(const T &value)
Synonym for insert.
Definition: collection.h:238
const CIdentifier & identifier() const
Get identifier.
Definition: identifiable.h:27
Value object encapsulating information identifying a component of a modular distributed swift process...
Definition: identifier.h:29
Class for emitting a log message.
Definition: logmessage.h:27
Derived & warning(const char16_t(&format)[N])
Set the severity to warning, providing a format string.
bool isEmpty() const
Message empty.
Derived & error(const char16_t(&format)[N])
Set the severity to error, providing a format string.
Derived & info(const char16_t(&format)[N])
Set the severity to info, providing a format string.
bool contains(const T &object) const
Return true if there is an element equal to given object. Uses the most efficient implementation avai...
Definition: range.h:109
size_type size() const
Returns number of elements in the sequence.
Definition: sequence.h:273
void replaceOrAdd(const T &original, const T &replacement)
Replace elements matching the given element. If there is no match, push the new element on the end.
Definition: sequence.h:521
void push_back(const T &value)
Appends an element at the end of the sequence.
Definition: sequence.h:305
reference front()
Access the first element.
Definition: sequence.h:225
void clear()
Removes all elements in the sequence.
Definition: sequence.h:288
bool isEmpty() const
Synonym for empty.
Definition: sequence.h:285
void pop_front()
Removes an element at the front of the sequence.
Definition: sequence.h:374
Streamable status message, e.g.
QString getMessage() const
Message.
Status messages, e.g. from Core -> GUI.
Value object encapsulating information about aircraft's engines.
Value object encapsulating a list of aircraft engines.
Value object for ICAO classification.
const QString & getDesignator() const
Get ICAO designator, e.g. "B737".
Value object encapsulating information of aircraft's parts.
Definition: aircraftparts.h:26
bool isNull() const
NULL parts object?
Value object encapsulating information of an aircraft's situation.
void setPressureAltitude(const CAltitude &altitude)
Set pressure altitude.
void setGroundSpeed(const physical_quantities::CSpeed &groundspeed)
Set ground speed.
bool setGroundElevation(const aviation::CAltitude &altitude, GndElevationInfo info, bool transferred=false)
Elevation of the ground directly beneath at the given situation.
void setBank(const physical_quantities::CAngle &bank)
Set bank (angle)
void setHeading(const CHeading &heading)
Set heading.
void setAltitude(const CAltitude &altitude)
Set altitude.
void setPitch(const physical_quantities::CAngle &pitch)
Set pitch.
virtual bool isNull() const
Null situation.
void setVelocity(const CAircraftVelocity &velocity)
Set 6DOF velocity.
void setPosition(const geo::CCoordinateGeodetic &position)
Set position.
const QString & getDesignator() const
Get airline, e.g. "DLH".
Altitude as used in aviation, can be AGL or MSL altitude.
Definition: altitude.h:52
Value object encapsulating information of a callsign.
Definition: callsign.h:30
const QString & asString() const
Get callsign (normalized)
Definition: callsign.h:96
bool isEmpty() const
Is empty?
Definition: callsign.h:63
Value object for a set of callsigns.
Definition: callsignset.h:26
QStringList getCallsignStrings(bool sorted=false) const
The callsign strings.
Definition: callsignset.cpp:37
COM system (aka "radio")
Definition: comsystem.h:37
const QString & getCombinedCode() const
Combined code.
Definition: livery.h:71
swift::misc::physical_quantities::CFrequency getFrequencyStandby() const
Standby frequency.
Definition: modulator.cpp:36
swift::misc::physical_quantities::CFrequency getFrequencyActive() const
Active frequency.
Definition: modulator.cpp:30
Value object for SELCAL.
Definition: selcal.h:31
OBJ findFirstByCallsign(const CCallsign &callsign, const OBJ &ifNotFound={}) const
Find the first aircraft by callsign, if none return given one.
int removeByCallsign(const CCallsign &callsign)
Remove all objects with callsign.
swift::misc::aviation::CCallsignSet getCallsigns() const
All callsigns.
bool containsCallsign(const CCallsign &callsign) const
Contains callsign?
virtual CLatitude latitude() const
Latitude.
virtual const aviation::CAltitude & geodeticHeight() const
Height, ellipsoidal or geodetic height (used in GPS)
void setGeodeticHeight(const aviation::CAltitude &height)
Set height (ellipsoidal or geodetic height)
virtual CLongitude longitude() const
Longitude.
Plane of same elevation, can be a single point or larger area (e.g. airport)
Geodetic coordinate, a position in 3D space relative to the reference geoid.
bool hasMSLGeodeticHeight() const
Geodetic height not null and aviation::CAltitude::MeanSeaLevel.
virtual bool isNull() const
Is null, means vector x, y, z == 0.
QString toQString(bool i18n=false) const
Cast as QString.
Definition: mixinstring.h:76
Value object encapsulating information of a text message.
Definition: textmessage.h:31
const QString & getMessage() const
Get message.
Definition: textmessage.h:78
const aviation::CCallsign & getSenderCallsign() const
Get callsign (from)
Definition: textmessage.h:54
Direct in memory access to client (network client) data.
Physical unit length (length)
Definition: length.h:18
Specialized class for distance units (meter, foot, nautical miles).
Definition: units.h:95
int valueInteger(MU unit) const
As integer value.
double value(MU unit) const
Value in given unit.
Aircraft model (used by another pilot, my models on disk)
Definition: aircraftmodel.h:71
const aviation::CCallsign & getCallsign() const
Corresponding callsign if applicable.
const aviation::CLivery & getLivery() const
Get livery.
void setCallsign(const aviation::CCallsign &callsign)
Corresponding callsign if applicable.
const QString & getFileName() const
File name (corresponding data for simulator, only available if representing simulator model.
Value object for interpolator and rendering per callsign.
const CInterpolationStatus & getInterpolationStatus() const
Get status.
const CPartsStatus & getPartsStatus() const
Get status.
bool hasValidSituation() const
Is the corresponding position valid?
bool updateOwnParts(const aviation::CAircraftParts &parts)
Update own parts.
bool updateCockpit(const swift::misc::simulation::CSimulatedAircraft &aircraft, const swift::misc::CIdentifier &originator)
CSimulatedAircraft getOwnAircraft() const
Own aircraft.
bool isSupportingParts() const
Supporting parts.
Definition: partsstatus.h:26
aviation::CCallsignSet getAircraftInRangeCallsigns() const
Unique callsigns for aircraft in range.
CSimulatedAircraftList getAircraftInRange() const
All remote aircraft.
int getAircraftInRangeCount() const
Count remote aircraft.
CSimulatedAircraft getAircraftInRangeForCallsign(const aviation::CCallsign &callsign) const
Aircraft for callsign.
bool updateAircraftRendered(const aviation::CCallsign &callsign, bool rendered)
Set aircraft rendered.
Comprehensive information of an aircraft.
bool hasModelString() const
Has model string?
aviation::CTransponder::TransponderMode getTransponderMode() const
Get transponder mode.
const aviation::CAircraftSituation & getSituation() const
Get situation.
const aviation::CComSystem & getCom2System() const
Get COM2 system.
qint32 getTransponderCode() const
Get transponder code.
bool hasCallsign() const
Callsign not empty, no further checks.
const aviation::CCallsign & getCallsign() const
Get callsign.
const aviation::CAircraftIcaoCode & getAircraftIcaoCode() const
Get aircraft ICAO info.
bool hasChangedCockpitData(const aviation::CComSystem &com1, const aviation::CComSystem &com2, const aviation::CTransponder &transponder) const
Changed cockpit data?
const simulation::CAircraftModel & getModel() const
Get model (model used for mapping)
QString getCallsignAsString() const
Get callsign.
const aviation::CComSystem & getCom1System() const
Get COM1 system.
const aviation::CAirlineIcaoCode & getAirlineIcaoCode() const
Airline ICAO code if any.
const aviation::CAircraftParts & getParts() const
Get aircraft parts.
Q_REQUIRED_RESULT CSimulatedAircraftList findByRendered(bool rendered) const
Rendered / not rendered aircraft.
aviation::CCallsignSet getLogCallsigns() const
All callsigns marked to be logged.
Direct threadsafe in memory access to own aircraft.
Direct thread safe in memory access to remote aircraft.
void setSimulatorDetails(const QString &name, const QString &details, const QString &version)
Set version and simulator details from running simulator.
void setDefaultModel(const CAircraftModel &defaultModel)
Default model.
Proxy object connected to a real FGSwiftBus::CService object via DBus.
void getOwnAircraftSituationData(FlightgearData *o_flightgearData)
Get own aircraft situation data.
QString getAircraftModelFilename() const
Get base filename of current aircraft model.
void getGroundElevationAsync(double *o_groundElevation)
Get ground elevation [m] for current airplane position.
void cancelAllPendingAsyncCalls()
Cancel all current async slot calls.
void getAircraftModelPathAsync(QString *o_modelPath)
Get full path to current aircraft model.
void setCom2StandbyKhz(int freq)
Set the current COM2 standby frequency in kHz.
QString getAircraftModelString() const
Get canonical swift model string of current aircraft model.
QString getAircraftName() const
Get name of current aircraft model.
QString getAircraftIcaoCode() const
Get the ICAO code of the current aircraft model.
void getCom2StandbyKhzAsync(int *o_com2Standby)
Get the current COM2 standby frequency in kHz.
void getCom2VolumeAsync(double *o_com2Volume)
Get Com2 volume [0..1].
void setCom1StandbyKhz(int freq)
Set the current COM1 standby frequency in kHz.
void getStrobeLightsOnAsync(bool *o_strobeLightsOn)
Get whether strobe lights are on.
void getCom1VolumeAsync(double *o_com1Volume)
Get Com1 volume [0..1].
QString getAircraftLivery() const
Get current aircraft livery.
void getLandingLightsOnAsync(bool *o_landingLightsOn)
Get whether landing lights are on.
void getTaxiLightsOnAsync(bool *o_taxiLightsOn)
Get whether taxi lights are on.
void getTransponderModeAsync(int *o_xpdrMode)
Get the current transponder mode (depends on the aircraft, 0 and 1 usually mean standby,...
void getNavLightsOnAsync(bool *o_navLightsOn)
Get whether nav lights are on.
void getAllWheelsOnGroundAsync(bool *o_allWheels)
Get whether all wheels are on the ground.
void getCom2ActiveKhzAsync(int *o_com2Active)
Get the current COM2 active frequency in kHz.
QString getAircraftDescription() const
Get the description of the current aircraft model.
void getCom1ActiveKhzAsync(int *o_com1Active)
Get the current COM1 active frequency in kHz.
void setCom2ActiveKhz(int freq)
Set the current COM2 active frequency in kHz.
void isPausedAsync(bool *o_paused)
True if sim is paused.
void getAircraftIcaoCodeAsync(QString *o_icaoCode)
Get the ICAO code of the current aircraft model.
void getTransponderIdentAsync(bool *o_ident)
Get whether we are currently squawking ident.
void setTransponderCode(int code)
Set the current transponder code in decimal.
void getGearDeployRatioAsync(double *o_gearDeployRatio)
Get gear deploy ratio, where 0 is up and 1 is down.
void getSpeedBrakeRatioAsync(double *o_speedBrakeRatio)
Get the ratio how much the speedbrakes surfaces are extended (0.0 is fully retracted,...
void aircraftModelChanged(const QString &path, const QString &filename, const QString &livery, const QString &icao, const QString &modelString, const QString &name, const QString &description)
Own aircraft model changed.
void getBeaconLightsOnAsync(bool *o_beaconLightsOn)
Get whether beacon lights are on.
void getFlapsDeployRatioAsync(double *o_flapsDeployRatio)
Get flaps deploy ratio, where 0.0 is flaps fully retracted, and 1.0 is flaps fully extended.
void getTransponderCodeAsync(int *o_xpdrCode)
Get the current transponder code in decimal.
void getEngineN1PercentageAsync(QList< double > *o_engineN1Percentage)
Get the N1 speed as percent of max (per engine)
void getCom1StandbyKhzAsync(int *o_com1Standby)
Get the current COM1 standby frequency in kHz.
void setTransponderMode(int mode)
Set the current transponder mode (depends on the aircraft, 0 and 1 usually mean standby,...
QString getAircraftModelPath() const
Get full path to current aircraft model.
void setCom1ActiveKhz(int freq)
Set the current COM1 active frequency in kHz.
bool isValid() const
Does the remote object exist?
void getOwnAircraftVelocityData(FlightgearData *o_flightgearData)
Get own aircraft velocity data.
void addTextMessage(const QString &text)
Add a text message to the on-screen display.
Proxy object connected to a real FGSwiftBus::CTraffic object via DBus.
void remoteAircraftAdded(const QString &callsign)
Remote aircraft successfully added.
void setPlanesTransponders(const swift::simplugin::flightgear::PlanesTransponders &planesTransponders)
Set the transponders of multiple traffic aircrafts.
void setPlanesSurfaces(const swift::simplugin::flightgear::PlanesSurfaces &planesSurfaces)
Set the surfaces of multiple traffic aircrafts.
void cleanup()
Reverse the actions of initialize().
void remoteAircraftAddingFailed(const QString &callsign)
Remote aircraft adding failed.
void addPlane(const QString &callsign, const QString &modelName, const QString &aircraftIcao, const QString &airlineIcao, const QString &livery)
Introduce a new traffic aircraft.
bool isValid() const
Does the remote object exist?
void getRemoteAircraftData(const QStringList &callsigns, const RemoteAircraftDataCallback &setter) const
Get remote aircrafts data (lat, lon, elevation and CG)
void removePlane(const QString &callsign)
Remove a traffic aircraft.
void setPlanesPositions(const swift::simplugin::flightgear::PlanesPositions &planesPositions)
Set the position of multiple traffic aircrafts.
Class representing a Flightgear multiplayer aircraft.
const swift::misc::simulation::CSimulatedAircraft & getAircraft() const
Simulated aircraft (as added)
virtual swift::core::ISimulator * create(const swift::misc::simulation::CSimulatorPluginInfo &info, swift::misc::simulation::IOwnAircraftProvider *ownAircraftProvider, swift::misc::simulation::IRemoteAircraftProvider *remoteAircraftProvider, swift::misc::network::IClientProvider *clientProvider)
Create a new instance of a driver.
virtual swift::misc::CStatusMessageList getInterpolationMessages(const swift::misc::aviation::CCallsign &callsign) const
Interpolation messages for callsign.
virtual bool updateOwnSimulatorSelcal(const swift::misc::aviation::CSelcal &selcal, const swift::misc::CIdentifier &originator)
Update own aircraft cockpit (usually from context)
virtual void displayTextMessage(const swift::misc::network::CTextMessage &message) const
Display a text message.
virtual bool physicallyAddRemoteAircraft(const swift::misc::simulation::CSimulatedAircraft &newRemoteAircraft)
Add new remote aircraft physically to the simulator.
virtual bool updateOwnSimulatorCockpit(const swift::misc::simulation::CSimulatedAircraft &aircraft, const swift::misc::CIdentifier &originator)
Update own aircraft cockpit (usually from context)
virtual int physicallyRemoveAllRemoteAircraft()
Remove all remote aircraft and their data via ISimulator::clearAllRemoteAircraftData.
virtual bool requestElevation(const swift::misc::geo::ICoordinateGeodetic &reference, const swift::misc::aviation::CCallsign &callsign)
Request elevation, there is no guarantee the requested elevation will be available in the provider.
virtual bool physicallyRemoveRemoteAircraft(const swift::misc::aviation::CCallsign &callsign)
Remove remote aircraft from simulator.
virtual bool isConnected() const
Are we connected to the simulator?
CSimulatorFlightgear(const swift::misc::simulation::CSimulatorPluginInfo &info, swift::misc::simulation::IOwnAircraftProvider *ownAircraftProvider, swift::misc::simulation::IRemoteAircraftProvider *remoteAircraftProvider, swift::misc::network::IClientProvider *clientProvider, QObject *parent=nullptr)
Constructor.
virtual swift::misc::aviation::CCallsignSet physicallyRenderedAircraft() const
Physically rendered (displayed in simulator) This shall only return aircraft handled in the simulator...
virtual void resetAircraftStatistics()
Reset the statistics.
virtual void displayStatusMessage(const swift::misc::CStatusMessage &message) const
Display a status message in the simulator.
virtual QString getStatisticsSimulatorSpecific() const
Allows to print out simulator specific statistics.
virtual bool disconnectFrom()
Disconnect from simulator.
virtual bool isPhysicallyRenderedAircraft(const swift::misc::aviation::CCallsign &callsign) const
Is the aircraft rendered (displayed in simulator)? This shall only return true if the aircraft is rea...
virtual void clearAllRemoteAircraftData()
Clear all aircraft related data, but do not physically remove the aircraft.
virtual void unload()
Driver will be unloaded.
virtual bool testSendSituationAndParts(const swift::misc::aviation::CCallsign &callsign, const swift::misc::aviation::CAircraftSituation &situation, const swift::misc::aviation::CAircraftParts &parts)
Send situation/parts for testing.
virtual void stopImpl()
Plugin specific implementation to stop listener.
CSimulatorFlightgearListener(const swift::misc::simulation::CSimulatorPluginInfo &info)
Constructor.
virtual void startImpl()
Plugin specific implementation to start listener.
virtual void checkImpl()
Plugin specific implementation to check.
Backend services of the swift project, like dealing with the network or the simulators.
Definition: actionbind.cpp:7
Free functions in swift::misc.
auto singleShot(int msec, QObject *target, F &&task)
Starts a single-shot timer which will call a task in the thread of the given object when it times out...
Definition: threadutils.h:30
double pressureAltitudeFt
Pressure altitude [inhg].
QList< double > enginesN1Percentage
N1 per engine [%].
double rollRateRadPerSec
Roll angular velocity [rad/s].
double pitchRateRadPerSec
Pitch angular velocity [rad/s].
double gearReployRatio
Gear deployment ratio [%].
int xpdrMode
Transponder mode (off=0,stdby=1-2, >2 on)
double yawRateRadPerSec
Yaw angular velocity [rad/s].
double flapsReployRatio
Flaps deployment ratio [%].
void push_back(const swift::misc::aviation::CAircraftSituation &situation)
Push back the latest situation.
void push_back(const swift::misc::aviation::CCallsign &callsign, const swift::misc::aviation::CAircraftParts &parts)
Push back the latest parts.
#define SWIFT_VERIFY_X(COND, WHERE, WHAT)
A weaker kind of assert.
Definition: verify.h:26