swift
simulatorxplane.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2013 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
4 #include "simulatorxplane.h"
5 
6 #include <math.h>
7 
8 #include <QColor>
9 #include <QDBusServiceWatcher>
10 #include <QElapsedTimer>
11 #include <QPointer>
12 #include <QString>
13 #include <QTimer>
14 #include <QtGlobal>
15 
16 #include "dbus/dbus.h"
17 #include "qcompilerdetection.h"
18 
19 #include "config/buildconfig.h"
20 #include "core/aircraftmatcher.h"
27 #include "misc/aviation/altitude.h"
28 #include "misc/aviation/callsign.h"
30 #include "misc/aviation/heading.h"
31 #include "misc/aviation/livery.h"
33 #include "misc/dbusserver.h"
35 #include "misc/geo/latitude.h"
36 #include "misc/geo/longitude.h"
37 #include "misc/iterator.h"
38 #include "misc/logmessage.h"
41 #include "misc/pq/angle.h"
42 #include "misc/pq/frequency.h"
43 #include "misc/pq/length.h"
44 #include "misc/pq/pressure.h"
45 #include "misc/pq/speed.h"
46 #include "misc/pq/temperature.h"
47 #include "misc/setbuilder.h"
49 #include "misc/simulation/settings/xswiftbussettingsqtfree.inc"
52 #include "misc/verify.h"
55 #include "misc/weather/windlayer.h"
57 #include "xswiftbusserviceproxy.h"
58 #include "xswiftbustrafficproxy.h"
59 
60 using namespace swift::config;
61 using namespace swift::misc;
62 using namespace swift::misc::aviation;
63 using namespace swift::misc::network;
64 using namespace swift::misc::physical_quantities;
65 using namespace swift::misc::geo;
66 using namespace swift::misc::simulation;
67 using namespace swift::misc::simulation::settings;
68 using namespace swift::misc::weather;
69 using namespace swift::core;
70 
71 namespace
72 {
73  const QString &xswiftbusServiceName()
74  {
75  static const QString name("org.swift-project.xswiftbus");
76  return name;
77  }
78  const QString &commitHash()
79  {
80  static const QString hash(XSWIFTBUS_COMMIT);
81  return hash;
82  }
83 } // namespace
84 
85 namespace swift::simplugin::xplane
86 {
87  CSimulatorXPlane::CSimulatorXPlane(const CSimulatorPluginInfo &info, IOwnAircraftProvider *ownAircraftProvider,
88  IRemoteAircraftProvider *remoteAircraftProvider, IClientProvider *clientProvider,
89  QObject *parent)
90  : CSimulatorPluginCommon(info, ownAircraftProvider, remoteAircraftProvider, clientProvider, parent)
91  {
92  m_watcher = new QDBusServiceWatcher(this);
93  m_watcher->setWatchMode(QDBusServiceWatcher::WatchForUnregistration);
94  m_watcher->addWatchedService(xswiftbusServiceName());
95  m_watcher->setObjectName("QDBusServiceWatcher");
96  connect(m_watcher, &QDBusServiceWatcher::serviceUnregistered, this,
97  &CSimulatorXPlane::onDBusServiceUnregistered, Qt::QueuedConnection);
98 
99  m_fastTimer.setObjectName(this->objectName().append(":m_fastTimer"));
100  m_slowTimer.setObjectName(this->objectName().append(":m_slowTimer"));
101  m_pendingAddedTimer.setObjectName(this->objectName().append(":m_pendingAddedTimer"));
102  connect(&m_fastTimer, &QTimer::timeout, this, &CSimulatorXPlane::fastTimerTimeout);
103  connect(&m_slowTimer, &QTimer::timeout, this, &CSimulatorXPlane::slowTimerTimeout);
104  connect(&m_airportUpdater, &QTimer::timeout, this, &CSimulatorXPlane::updateAirportsInRange);
105  connect(&m_pendingAddedTimer, &QTimer::timeout, this, &CSimulatorXPlane::addNextPendingAircraft);
106  m_fastTimer.start(100);
107  m_slowTimer.start(1000);
108  m_airportUpdater.start(60 * 1000);
109  m_pendingAddedTimer.start(5000);
110 
111  this->setDefaultModel({ "Jets A320_a A320_a_Austrian_Airlines A320_a_Austrian_Airlines",
112  CAircraftModel::TypeModelMatchingDefaultModel, "A320 AUA",
113  CAircraftIcaoCode("A320", "L2J") });
114  this->resetXPlaneData();
115  }
116 
118 
120  {
121  if (!this->isConnected()) { return; }
122 
123  // will call disconnect from
124  CSimulatorPluginCommon::unload();
125  delete m_watcher;
126  m_watcher = nullptr;
127  }
128 
130  {
131  return QStringLiteral("Add-time: %1ms/%2ms").arg(m_statsAddCurrentTimeMs).arg(m_statsAddMaxTimeMs);
132  }
133 
135  {
136  m_statsAddMaxTimeMs = -1;
137  m_statsAddCurrentTimeMs = -1;
138  }
139 
141  {
142  if (callsign.isEmpty() || !m_xplaneAircraftObjects.contains(callsign)) { return CStatusMessageList(); }
144  this->getInterpolationSetupConsolidated(callsign, false);
145  return m_xplaneAircraftObjects[callsign].getInterpolationMessages(setup.getInterpolatorMode());
146  }
147 
149  const CAircraftParts &parts)
150  {
151  if (this->isShuttingDownOrDisconnected()) { return false; }
152  if (!m_trafficProxy) { return false; }
153  if (!m_xplaneAircraftObjects.contains(callsign)) { return false; }
154 
155  int u = 0;
156  if (!situation.isNull())
157  {
158  PlanesPositions planesPositions;
159  planesPositions.push_back(situation);
160  m_trafficProxy->setPlanesPositions(planesPositions);
161  u++;
162  }
163 
164  if (!parts.isNull())
165  {
166  PlanesSurfaces surfaces;
167  surfaces.push_back(callsign, parts);
168  m_trafficProxy->setPlanesSurfaces(surfaces);
169  u++;
170  }
171  return u > 0;
172  }
173 
174  bool CSimulatorXPlane::handleProbeValue(const CElevationPlane &plane, const CCallsign &callsign, bool waterFlag,
175  const QString &hint, bool ignoreOutsideRange)
176  {
177  // XPlane specific checks for T778
178  // https://discordapp.com/channels/539048679160676382/577213275184562176/696780159969132626
179  if (!plane.hasMSLGeodeticHeight()) { return false; }
180  if (isSuspiciousTerrainValue(plane))
181  {
182  // ignore values up to +- 1.0meters, those most likely mean no scenery
183  const CLength distance = this->getDistanceToOwnAircraft(plane);
184  if (ignoreOutsideRange && distance > maxTerrainRequestDistance()) { return false; }
185 
186  static const CLength threshold = maxTerrainRequestDistance() * 0.50;
187  if (distance > threshold)
188  {
189  // we expect scenery to be loaded within threshold range
190  // outside that range we assue a suspicous value "represents no scenery"
191  // of course this can be wrong, but in that case we would geth those values
192  // once we get inside range
193  this->setMinTerrainProbeDistance(distance);
194  CLogMessage(this).debug(
195  u"Suspicous XPlane probe [%1] value %2 for '%3' ignored, distance: %4 min.disance: %5 water: %6")
196  << hint << plane.getAltitude().valueRoundedAsString(CLengthUnit::m(), 4) << callsign.asString()
197  << distance.valueRoundedAsString(CLengthUnit::NM(), 2)
198  << m_minSuspicousTerrainProbe.valueRoundedAsString(CLengthUnit::NM(), 2) << boolToYesNo(waterFlag);
199  return false;
200  }
201  }
202  return true;
203  }
204 
206  bool isWater)
207  {
208  static const QString hint("probe callback");
209  if (!this->handleProbeValue(plane, callsign, isWater, hint, false))
210  {
211  this->removePendingElevationRequest(callsign);
212  return;
213  }
214  CSimulatorPluginCommon::callbackReceivedRequestedElevation(plane, callsign, isWater);
215  }
216 
218  {
219  if (connected && !this->isShuttingDownOrDisconnected()) { m_serviceProxy->resetFrameTotals(); }
220  CSimulatorPluginCommon::setFlightNetworkConnected(connected);
221  }
222 
223  bool CSimulatorXPlane::isSuspiciousTerrainValue(const CElevationPlane &elevation)
224  {
225  if (!elevation.hasMSLGeodeticHeight()) { return true; }
226  const double valueFt = qAbs(elevation.getAltitudeValue(CLengthUnit::ft()));
227  return valueFt < 1.0;
228  }
229 
230  const CLength &CSimulatorXPlane::maxTerrainRequestDistance()
231  {
232  static const CLength d(70.0, CLengthUnit::NM());
233  return d;
234  }
235 
237  {
238  m_aircraftAddedFailed.clear();
239  CSimulatorPluginCommon::clearAllRemoteAircraftData();
240  m_minSuspicousTerrainProbe.setNull();
241  }
242 
243  bool CSimulatorXPlane::requestElevation(const ICoordinateGeodetic &reference, const CCallsign &callsign)
244  {
245  if (this->isShuttingDownOrDisconnected()) { return false; }
246  if (reference.isNull()) { return false; }
247 
248  // avoid requests for NON exising aircraft (based on LINUX crashes)
249  if (callsign.isEmpty()) { return false; }
250  if (!this->isAircraftInRange(callsign)) { return false; }
251 
252  const CLength d = this->getDistanceToOwnAircraft(reference);
253  if (!d.isNull() && d > maxTerrainRequestDistance())
254  {
255  // no request, too far away
256  return false;
257  }
258 
259  CCoordinateGeodetic pos(reference);
260  if (!pos.hasMSLGeodeticHeight())
261  {
262  // testing showed: height has an influence on the returned result
263  // - the most accurate value seems to be returned if the height is close to the elevation
264  // - in normal scenarios there is no much difference of the results if 0 is used
265  // - in Lukla (9200ft MSL) the difference between 0 and 9200 is around 1ft
266  // - in the LOWW scenario using 50000ft MSL results in around 3ft too low elevation
267  static const CAltitude alt(0, CAltitude::MeanSeaLevel, CLengthUnit::ft());
268  pos.setGeodeticHeight(alt);
269  }
270 
271  using namespace std::placeholders;
272  auto callback = std::bind(&CSimulatorXPlane::callbackReceivedRequestedElevation, this, _1, _2, _3);
273 
274  // Request
275  m_trafficProxy->getElevationAtPosition(callsign, pos.latitude().value(CAngleUnit::deg()),
276  pos.longitude().value(CAngleUnit::deg()),
277  pos.geodeticHeight().value(CLengthUnit::m()), callback);
278  emit this->requestedElevation(callsign);
279  return true;
280  }
281 
282  // convert xplane squawk mode to swift squawk mode
283  CTransponder::TransponderMode xpdrMode(int xplaneMode, bool ident)
284  {
285  if (ident) { return CTransponder::StateIdent; }
286  if (xplaneMode == 0 || xplaneMode == 1) { return CTransponder::StateStandby; }
287  return CTransponder::ModeC;
288  }
289 
290  // convert swift squawk mode to xplane squawk mode
291  int xpdrMode(CTransponder::TransponderMode mode) { return mode == CTransponder::StateStandby ? 1 : 2; }
292 
293  void CSimulatorXPlane::fastTimerTimeout()
294  {
295  if (!this->isShuttingDownOrDisconnected())
296  {
297  m_fastTimerCalls++;
298 
299  m_serviceProxy->getOwnAircraftSituationDataAsync(&m_xplaneData);
300  m_serviceProxy->getOwnAircraftVelocityDataAsync(&m_xplaneData);
301  m_serviceProxy->getOwnAircraftCom1DataAsync(&m_xplaneData);
302  m_serviceProxy->getOwnAircraftCom2DataAsync(&m_xplaneData);
303  m_serviceProxy->getOwnAircraftXpdrAsync(&m_xplaneData);
304  m_serviceProxy->getAllWheelsOnGroundAsync(&m_xplaneData.onGroundAll);
305  m_serviceProxy->getHeightAglMAsync(&m_xplaneData.heightAglM);
306  m_serviceProxy->getPressureAltitudeFtAsync(&m_xplaneData.pressureAltitudeFt);
307 
308  CAircraftSituation situation;
309  situation.setPosition({ m_xplaneData.latitudeDeg, m_xplaneData.longitudeDeg, 0 });
310  const CAltitude altitude { m_xplaneData.altitudeM, CAltitude::MeanSeaLevel, CLengthUnit::m() };
311  const CPressure seaLevelPressure({ m_xplaneData.seaLevelPressureInHg, CPressureUnit::inHg() });
312  const CAltitude pressureAltitude(altitude.toPressureAltitude(seaLevelPressure));
313  if (std::isnan(m_xplaneData.pressureAltitudeFt))
314  {
315  m_altitudeDelta = {};
316  situation.setAltitude(altitude);
317  situation.setPressureAltitude(pressureAltitude);
318  }
319  else
320  {
321  const CAltitude pressureAltitudeXP12(m_xplaneData.pressureAltitudeFt, CAltitude::MeanSeaLevel,
322  CAltitude::PressureAltitude, CLengthUnit::ft());
323  m_altitudeDelta = pressureAltitude - pressureAltitudeXP12;
324 
325  situation.setAltitude({ altitude - m_altitudeDelta, CAltitude::MeanSeaLevel });
326  situation.setPressureAltitude(pressureAltitudeXP12);
327  }
328  situation.setHeading({ m_xplaneData.trueHeadingDeg, CHeading::True, CAngleUnit::deg() });
329  situation.setPitch({ m_xplaneData.pitchDeg, CAngleUnit::deg() });
330  situation.setBank({ m_xplaneData.rollDeg, CAngleUnit::deg() });
331  situation.setGroundSpeed({ m_xplaneData.groundspeedMs, CSpeedUnit::m_s() });
332  const CAltitude elevation { m_xplaneData.altitudeM - m_xplaneData.heightAglM, CAltitude::MeanSeaLevel,
333  CLengthUnit::m() };
334  situation.setGroundElevation(elevation, CAircraftSituation::FromProvider);
335  situation.setVelocity({ m_xplaneData.localXVelocityMs, m_xplaneData.localYVelocityMs,
336  m_xplaneData.localZVelocityMs, CSpeedUnit::m_s(), m_xplaneData.pitchRadPerSec,
337  m_xplaneData.rollRadPerSec, m_xplaneData.headingRadPerSec, CAngleUnit::rad(),
338  CTimeUnit::s() });
339 
340  // Updates
341  // Do not update ICAO codes, as this overrides reverse lookups
342  // updateOwnIcaoCodes(m_xplaneData.aircraftIcaoCode, CAirlineIcaoCode());
343  this->updateOwnSituationAndGroundElevation(situation);
344 
345  // defaults
346  CSimulatedAircraft myAircraft(getOwnAircraft());
347  CComSystem com1(myAircraft.getCom1System()); // set defaults
348  CComSystem com2(myAircraft.getCom2System());
349  CTransponder transponder(myAircraft.getTransponder());
350 
351  // updates
352  com1.setFrequencyActive(CFrequency(m_xplaneData.com1ActiveKhz, CFrequencyUnit::kHz()));
353  com1.setFrequencyStandby(CFrequency(m_xplaneData.com1StandbyKhz, CFrequencyUnit::kHz()));
354  const int v1 = qRound(m_xplaneData.com1Volume * 100);
355  com1.setVolumeReceive(v1);
356  com1.setReceiveEnabled(m_xplaneData.isCom1Receiving);
357  com1.setTransmitEnabled(m_xplaneData.isCom1Transmitting);
358  const bool changedCom1 = myAircraft.getCom1System() != com1;
359 
360  com2.setFrequencyActive(CFrequency(m_xplaneData.com2ActiveKhz, CFrequencyUnit::kHz()));
361  com2.setFrequencyStandby(CFrequency(m_xplaneData.com2StandbyKhz, CFrequencyUnit::kHz()));
362  const int v2 = qRound(m_xplaneData.com2Volume * 100);
363  com2.setVolumeReceive(v2);
364  com2.setReceiveEnabled(m_xplaneData.isCom2Receiving);
365  com2.setTransmitEnabled(m_xplaneData.isCom2Transmitting);
366  const bool changedCom2 = myAircraft.getCom2System() != com2;
367 
368  transponder = CTransponder::getStandardTransponder(m_xplaneData.xpdrCode,
369  xpdrMode(m_xplaneData.xpdrMode, m_xplaneData.xpdrIdent));
370  const bool changedXpr = (myAircraft.getTransponder() != transponder);
371 
372  if (changedCom1 || changedCom2 || changedXpr)
373  {
374  this->updateCockpit(com1, com2, transponder, identifier());
375  }
376  }
377  }
378 
379  void CSimulatorXPlane::slowTimerTimeout()
380  {
381  if (!this->isShuttingDownOrDisconnected())
382  {
383  m_slowTimerCalls++;
384 
385  // own aircraft data
386  m_serviceProxy->getOwnAircraftModelDataAsync(&m_xplaneData);
387  m_serviceProxy->getOwnAircraftLightsAsync(&m_xplaneData);
388  m_serviceProxy->getOwnAircraftPartsAsync(&m_xplaneData);
389 
390  CAircraftEngineList engines;
391  for (int engineNumber = 0; engineNumber < m_xplaneData.enginesN1Percentage.size(); ++engineNumber)
392  {
393  // Engine number start counting at 1
394  // We consider the engine running when N1 is bigger than 5 %
395  const CAircraftEngine engine { engineNumber + 1,
396  m_xplaneData.enginesN1Percentage.at(engineNumber) > 5.0 };
397  engines.push_back(engine);
398  }
399 
400  const CAircraftLights lights(m_xplaneData.strobeLightsOn, m_xplaneData.landingLightsOn,
401  m_xplaneData.taxiLightsOn, m_xplaneData.beaconLightsOn,
402  m_xplaneData.navLightsOn, false);
403 
404  const CAircraftParts parts { lights,
405  m_xplaneData.gearDeployRatio > 0,
406  qRound(m_xplaneData.flapsDeployRatio * 100.0),
407  m_xplaneData.speedBrakeRatio > 0.5,
408  engines,
409  m_xplaneData.onGroundAll };
410 
411  this->updateOwnParts(parts);
412 
413  CCallsignSet invalid;
414  for (CXPlaneMPAircraft &xplaneAircraft : m_xplaneAircraftObjects)
415  {
416  // Update remote aircraft to have the latest transponder modes, codes etc.
417  const CCallsign cs = xplaneAircraft.getCallsign();
418  const CSimulatedAircraft simulatedAircraft = this->getAircraftInRangeForCallsign(cs);
419  if (!simulatedAircraft.hasCallsign())
420  {
421  // removed in provider
422  if (!cs.isEmpty()) { invalid.insert(cs); }
423  continue;
424  }
425  xplaneAircraft.setSimulatedAircraft(simulatedAircraft);
426  }
427 
428  // remove the invalid ones
429  int i = 0;
430  if (this->isTestMode()) { invalid.clear(); } // skip this in test mode
431  for (const CCallsign &cs : invalid) { this->triggerRemoveAircraft(cs, ++i * 100); }
432 
433  // KB: IMHO those data are pretty useless for XPlane
434  // no need to request them all the times
435  if ((m_slowTimerCalls % 3u) == 0u) { this->requestRemoteAircraftDataFromXPlane(); }
436 
437  // FPS
438  // reading FPS resets average, so we only monitor over some time
439  if ((m_slowTimerCalls % 5u) == 0u)
440  {
441  constexpr double warningMiles = 1;
442  constexpr double disconnectMiles = 2;
443  const double previousMiles = m_trackMilesShort;
444 
446 
447  if (previousMiles < disconnectMiles && m_trackMilesShort >= disconnectMiles)
448  {
450  }
451  else if (previousMiles < warningMiles && m_trackMilesShort >= warningMiles)
452  {
453  emit insufficientFrameRateDetected(false);
454  }
455  }
456  }
457  }
458 
459  bool CSimulatorXPlane::isConnected() const { return m_serviceProxy && m_trafficProxy; }
460 
462  {
463  if (this->isConnected()) { return true; }
464  const QString dBusServerAddress = m_xSwiftBusServerSettings.getThreadLocal().getDBusServerAddressQt();
465 
466  if (CDBusServer::isSessionOrSystemAddress(dBusServerAddress))
467  {
468  m_dBusConnection = QDBusConnection::sessionBus();
469  m_dbusMode = Session;
470  }
471  else if (CDBusServer::isQtDBusAddress(dBusServerAddress))
472  {
473  m_dBusConnection = QDBusConnection::connectToPeer(dBusServerAddress, "xswiftbus");
474  if (!m_dBusConnection.isConnected()) { return false; }
475  m_dbusMode = P2P;
476  }
477 
478  m_serviceProxy = new CXSwiftBusServiceProxy(m_dBusConnection, this);
479  m_trafficProxy = new CXSwiftBusTrafficProxy(m_dBusConnection, this);
480 
481  // hook up disconnected slot of connection
482  bool s = m_dBusConnection.connect(QString(), DBUS_PATH_LOCAL, DBUS_INTERFACE_LOCAL, "Disconnected", this,
483  SLOT(onDBusServiceUnregistered()));
484  Q_ASSERT(s);
485  if (!m_serviceProxy->isValid() || !m_trafficProxy->isValid())
486  {
487  this->disconnectFrom();
488  return false;
489  }
490 
491  emitOwnAircraftModelChanged(m_serviceProxy->getAircraftModelPath(), m_serviceProxy->getAircraftModelFilename(),
492  m_serviceProxy->getAircraftLivery(), m_serviceProxy->getAircraftIcaoCode(),
493  m_serviceProxy->getAircraftModelString(), m_serviceProxy->getAircraftName(),
494  m_serviceProxy->getAircraftDescription());
495  QString xplaneVersion = QStringLiteral("%1.%2")
496  .arg(m_serviceProxy->getXPlaneVersionMajor())
497  .arg(m_serviceProxy->getXPlaneVersionMinor());
498  setSimulatorDetails("X-Plane", {}, xplaneVersion);
499  connect(m_serviceProxy, &CXSwiftBusServiceProxy::aircraftModelChanged, this,
500  &CSimulatorXPlane::emitOwnAircraftModelChanged);
501  m_serviceProxy->updateAirportsInRange();
502  connect(m_trafficProxy, &CXSwiftBusTrafficProxy::simFrame, this, &CSimulatorXPlane::updateRemoteAircraft);
503  connect(m_trafficProxy, &CXSwiftBusTrafficProxy::remoteAircraftAdded, this,
504  &CSimulatorXPlane::onRemoteAircraftAdded);
505  connect(m_trafficProxy, &CXSwiftBusTrafficProxy::remoteAircraftAddingFailed, this,
506  &CSimulatorXPlane::onRemoteAircraftAddingFailed);
507  if (m_watcher) { m_watcher->setConnection(m_dBusConnection); }
508  m_trafficProxy->removeAllPlanes();
509 
510  // send the settings
511  this->sendXSwiftBusSettings();
512 
513  // load CSL
514  this->loadCslPackages();
515 
516  // finish
517  this->initSimulatorInternals();
519 
520  return true;
521  }
522 
524  {
525  if (!this->isConnected()) { return true; } // avoid emit if already disconnected
526  this->disconnectFromDBus();
527  if (m_watcher) { m_watcher->setConnection(m_dBusConnection); }
528  delete m_serviceProxy;
529  delete m_trafficProxy;
530  m_serviceProxy = nullptr;
531  m_trafficProxy = nullptr;
532  m_fastTimerCalls = 0;
533  m_slowTimerCalls = 0;
534 
536  return true;
537  }
538 
539  void CSimulatorXPlane::onDBusServiceUnregistered()
540  {
541  if (!m_serviceProxy) { return; }
542  CLogMessage(this).info(u"XPlane xSwiftBus service unregistered");
543 
544  if (m_dbusMode == P2P) { m_dBusConnection.disconnectFromPeer(m_dBusConnection.name()); }
545  m_dBusConnection = QDBusConnection { "default" };
546  if (m_watcher) { m_watcher->setConnection(m_dBusConnection); }
547  delete m_serviceProxy;
548  delete m_trafficProxy;
549  m_serviceProxy = nullptr;
550  m_trafficProxy = nullptr;
552  }
553 
554  void CSimulatorXPlane::emitOwnAircraftModelChanged(const QString &path, const QString &filename,
555  const QString &livery, const QString &icao,
556  const QString &modelString, const QString &name,
557  const QString &description)
558  {
559  CAircraftModel model(modelString, CAircraftModel::TypeOwnSimulatorModel, CSimulatorInfo::XPLANE, name,
560  description, icao);
561  if (!livery.isEmpty()) { model.setModelString(model.getModelString() + " " + livery); }
562  model.setFileName(path + "/" + filename);
563 
565  }
566 
568  {
569  // No assert here, as status message may come in because of network problems
570  if (this->isShuttingDownOrDisconnected()) { return; }
571 
572  // avoid infinite recursion in case this function is called due to a message caused by this very function
573  static bool isInFunction = false;
574  if (isInFunction) { return; }
575  isInFunction = true;
576 
577  std::vector<int> msgBoxValues = m_xSwiftBusServerSettings.getThreadLocal().getMessageBoxValuesVector();
578  QColor color = msgBoxValues[9];
579  /* switch (message.getSeverity())
580  {
581  case CStatusMessage::SeverityDebug: color = "teal"; break;
582  case CStatusMessage::SeverityInfo: color = "cyan"; break;
583  case CStatusMessage::SeverityWarning: color = "orange"; break;
584  case CStatusMessage::SeverityError: color = "red"; break;
585  } */
586 
587  m_serviceProxy->addTextMessage("swift: " + message.getMessage(), color.redF(), color.greenF(), color.blueF());
588  isInFunction = false;
589  }
590 
592  {
593  // avoid issues during shutdown
594  if (this->isShuttingDownOrDisconnected()) { return; }
595 
596  std::vector<int> msgBoxValues = m_xSwiftBusServerSettings.getThreadLocal().getMessageBoxValuesVector();
597  QColor color;
598  if (message.isServerMessage()) { color = msgBoxValues[8]; }
599  else if (message.isSupervisorMessage()) { color = msgBoxValues[10]; }
600  else if (message.isPrivateMessage()) { color = msgBoxValues[7]; }
601  else { color = msgBoxValues[6]; }
602 
603  m_serviceProxy->addTextMessage(message.getSenderCallsign().toQString() + ": " + message.getMessage(),
604  color.redF(), color.greenF(), color.blueF());
605  }
606 
608  {
609  return m_xplaneAircraftObjects.contains(callsign);
610  }
611 
613  const CIdentifier &originator)
614  {
615  if (originator == this->identifier()) { return false; }
616  if (this->isShuttingDownOrDisconnected()) { return false; } // could happen during shutdown
617 
618  auto com1 = CComSystem::getCom1System({ m_xplaneData.com1ActiveKhz, CFrequencyUnit::kHz() },
619  { m_xplaneData.com1StandbyKhz, CFrequencyUnit::kHz() });
620  auto com2 = CComSystem::getCom2System({ m_xplaneData.com2ActiveKhz, CFrequencyUnit::kHz() },
621  { m_xplaneData.com2StandbyKhz, CFrequencyUnit::kHz() });
622  auto xpdr = CTransponder::getStandardTransponder(m_xplaneData.xpdrCode,
623  xpdrMode(m_xplaneData.xpdrMode, m_xplaneData.xpdrIdent));
624  if (aircraft.hasChangedCockpitData(com1, com2, xpdr))
625  {
626  m_xplaneData.com1ActiveKhz =
627  aircraft.getCom1System().getFrequencyActive().valueInteger(CFrequencyUnit::kHz());
628  m_xplaneData.com1StandbyKhz =
629  aircraft.getCom1System().getFrequencyStandby().valueInteger(CFrequencyUnit::kHz());
630  m_xplaneData.com2ActiveKhz =
631  aircraft.getCom2System().getFrequencyActive().valueInteger(CFrequencyUnit::kHz());
632  m_xplaneData.com2StandbyKhz =
633  aircraft.getCom2System().getFrequencyStandby().valueInteger(CFrequencyUnit::kHz());
634  m_xplaneData.xpdrCode = aircraft.getTransponderCode();
635  m_xplaneData.xpdrMode = xpdrMode(aircraft.getTransponderMode());
636  m_serviceProxy->setCom1ActiveKhz(m_xplaneData.com1ActiveKhz);
637  m_serviceProxy->setCom1StandbyKhz(m_xplaneData.com1StandbyKhz);
638  m_serviceProxy->setCom2ActiveKhz(m_xplaneData.com2ActiveKhz);
639  m_serviceProxy->setCom2StandbyKhz(m_xplaneData.com2StandbyKhz);
640  m_serviceProxy->setTransponderCode(m_xplaneData.xpdrCode);
641  m_serviceProxy->setTransponderMode(m_xplaneData.xpdrMode);
642 
643  m_serviceProxy
644  ->cancelAllPendingAsyncCalls(); // in case there is already a reply with some old data incoming
645  return true;
646  }
647  return false;
648  }
649 
650  bool CSimulatorXPlane::updateOwnSimulatorSelcal(const CSelcal &selcal, const CIdentifier &originator)
651  {
652  if (originator == this->identifier()) { return false; }
653  if (this->isShuttingDownOrDisconnected()) { return false; } // could happen during shutdown
654 
656  Q_UNUSED(selcal)
657  return false;
658  }
659 
660  void CSimulatorXPlane::loadCslPackages()
661  {
662  // An ad-hoc type for keeping track of packages as they are discovered.
663  // A model is a member of a package if the model path starts with the package path.
664  // A trailing separator is appended only for checking if a model path starts with this path.
665  struct Prefix
666  {
667  Prefix(const QString &p) : s(p + '/') {}
668  QString parent() const
669  {
670  return s.section('/', 0, -2, QString::SectionSkipEmpty | QString::SectionIncludeLeadingSep);
671  }
672  bool isPrefixOf(const QString &o) const { return o.startsWith(s); }
673  QString s;
674  };
675 
676  // Heterogeneous comparison so a package can be found by binary search
677  // (e.g. std::lower_bound) using a model path as the search key.
678  struct PrefixComparator
679  {
680  bool operator()(const Prefix &a, const QString &b) const
681  {
682  return QStringView(a.s) < QStringView(b).left(a.s.size());
683  }
684  bool operator()(const QString &a, const Prefix &b) const
685  {
686  return QStringView(a).left(b.s.size()) < QStringView(b.s);
687  }
688  };
689 
690  // The list of packages discovered so far.
691  QList<Prefix> packages;
692 
693  Q_ASSERT(this->isConnected());
694  const CAircraftModelList models = this->getModelSet();
695 
696  // Find the CSL packages for all models in the list
697  for (const auto &model : models)
698  {
699  const QString &modelFile = model.getFileName();
700  if (modelFile.isEmpty() || !QFile::exists(modelFile)) { continue; }
701 
702  // Check if this model's package was already found
703  auto it = std::lower_bound(packages.begin(), packages.end(), modelFile, PrefixComparator());
704  if (it != packages.end() && it->isPrefixOf(modelFile)) { continue; }
705 
706  // This model's package was not already found, so find it and add it to the list
707  QString package = findCslPackage(modelFile);
708  if (package.isEmpty()) { continue; }
709  packages.insert(it, package);
710  }
711 
712  // comment KB 2019-06
713  // a package is one xsb_aircraft.txt file BB has 9, X-CSL has 76
714  CSetBuilder<QString> superpackages;
715  for (const Prefix &package : std::as_const(packages)) { superpackages.insert(package.parent()); }
716  QStringList superpackagesList = superpackages;
717 
719  for (const QString &package : superpackagesList)
720  {
721  if (CDirectoryUtils::isSameOrSubDirectoryOf(package, simDir))
722  {
723  const QString message = m_trafficProxy->loadPlanesPackage(package);
724  if (!message.isEmpty())
725  {
726  CLogMessage(this).validationError(u"CSL package '%1' xpmp error: %2") << package << message;
727  }
728  }
729  else
730  {
732  u"CSL package '%1' can not be loaded as it is outside the X-Plane installation directory")
733  << package;
734  }
735  }
736  }
737 
738  QString CSimulatorXPlane::findCslPackage(const QString &modelFile)
739  {
741  const QFileInfo info(modelFile);
742  QDir dir = info.isDir() ? QDir(modelFile) : info.dir();
743  do {
744  if (dir.exists(QStringLiteral("xsb_aircraft.txt"))) { return dir.path(); }
745  }
746  while (dir.cdUp());
747  CLogMessage(this).warning(u"Failed to find CSL package for %1") << modelFile;
748  return {};
749  }
750 
752  {
753  // avoid issue in rapid shutdown
754  if (this->isShuttingDownOrDisconnected()) { return false; }
755 
756  // entry checks
757  Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "thread");
758  Q_ASSERT_X(!newRemoteAircraft.getCallsign().isEmpty(), Q_FUNC_INFO, "empty callsign");
759  Q_ASSERT_X(newRemoteAircraft.hasModelString(), Q_FUNC_INFO, "missing model string");
760 
761  // crosscheck if still a valid aircraft
762  // it can happen that aircraft has been removed, timed out ...
763  if (!this->isAircraftInRangeOrTestMode(newRemoteAircraft.getCallsign()))
764  {
765  // next cycle will be called by callbacks or timer
766  CLogMessage(this).warning(u"Aircraft '%1' no longer in range, will not add")
767  << newRemoteAircraft.getCallsign();
768  return false;
769  }
770 
771  if (this->canAddAircraft())
772  {
773  // no aircraft pending, add
774  this->logAddingAircraftModel(newRemoteAircraft);
775  const qint64 now = QDateTime::currentMSecsSinceEpoch();
776  m_addingInProgressAircraft.insert(newRemoteAircraft.getCallsign(), now);
777  const QString callsign = newRemoteAircraft.getCallsign().asString();
778  CAircraftModel aircraftModel = newRemoteAircraft.getModel();
779 
780  // some more validation
781  if (aircraftModel.getCallsign() != newRemoteAircraft.getCallsign())
782  {
783  CLogMessage(this).warning(u"Model for '%1' has no callsign, maybe using a default model") << callsign;
784  aircraftModel.setCallsign(callsign); // fix callsign to avoid follow up issues
785 
786  // we could disable the aircraft?
787  }
788 
789  const QString livery = aircraftModel.getLivery().getCombinedCode();
790  m_trafficProxy->addPlane(callsign, aircraftModel.getModelString(),
791  newRemoteAircraft.getAircraftIcaoCode().getDesignator(),
792  newRemoteAircraft.getAirlineIcaoCode().getDesignator(), livery);
793  PlanesPositions pos;
794  pos.push_back(newRemoteAircraft.getSituation());
795  m_trafficProxy->setPlanesPositions(pos);
796 
797  PlanesSurfaces surfaces;
798  surfaces.push_back(newRemoteAircraft.getCallsign(), newRemoteAircraft.getParts());
799  m_trafficProxy->setPlanesSurfaces(surfaces);
800  }
801  else
802  {
803  // add in queue
804  m_pendingToBeAddedAircraft.replaceOrAdd(newRemoteAircraft);
805  }
806  return true;
807  }
808 
810  {
811  // avoid issue in rapid shutdown
812  if (this->isShuttingDownOrDisconnected()) { return false; }
813 
814  // only remove from sim
815  Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "wrong thread");
816  if (callsign.isEmpty()) { return false; } // can happen if an object is not an aircraft
817 
818  // really remove from simulator
819  if (!this->isTestMode() && !m_xplaneAircraftObjects.contains(callsign) &&
820  !m_pendingToBeAddedAircraft.containsCallsign(callsign) && !m_addingInProgressAircraft.contains(callsign))
821  {
822  // not existing aircraft
823  return false;
824  }
825 
826  // mark in provider
827  const bool updated = this->updateAircraftRendered(callsign, false);
828  if (updated)
829  {
830  if (m_xplaneAircraftObjects.contains(callsign))
831  {
832  const CXPlaneMPAircraft &xplaneAircraft = m_xplaneAircraftObjects[callsign];
833  CSimulatedAircraft aircraft(xplaneAircraft.getAircraft());
834  aircraft.setRendered(false);
835  emit this->aircraftRenderingChanged(aircraft);
836  }
837  else if (m_pendingToBeAddedAircraft.containsCallsign(callsign))
838  {
839  CSimulatedAircraft aircraft = m_pendingToBeAddedAircraft.findFirstByCallsign(callsign);
840  aircraft.setRendered(false);
841  emit this->aircraftRenderingChanged(aircraft);
842  }
843  }
844 
845  // if adding in progress, postpone
846  if (m_addingInProgressAircraft.contains(callsign))
847  {
848  // we are just about to add that aircraft
849  QPointer<CSimulatorXPlane> myself(this);
850  QTimer::singleShot(TimeoutAdding, this, [=] {
851  if (!myself) { return; }
852  m_addingInProgressAircraft.remove(callsign); // remove as "in progress"
853  this->physicallyRemoveRemoteAircraft(callsign); // and remove from sim. if it was added in the mean time
854  });
855  return false; // do nothing right now
856  }
857 
858  m_trafficProxy->removePlane(callsign.asString());
859  m_xplaneAircraftObjects.remove(callsign);
860  m_pendingToBeAddedAircraft.removeByCallsign(callsign);
861 
862  // bye
863  return CSimulatorPluginCommon::physicallyRemoveRemoteAircraft(callsign);
864  }
865 
867  {
868  if (this->isShuttingDownOrDisconnected()) { return 0; }
869  m_pendingToBeAddedAircraft.clear();
870  m_addingInProgressAircraft.clear();
871  return CSimulatorPluginCommon::physicallyRemoveAllRemoteAircraft();
872  }
873 
875  {
877  return this->getAircraftInRange().findByRendered(true).getCallsigns(); // just a poor workaround
878  }
879 
881  {
882  if (!m_trafficProxy || !m_trafficProxy->isValid()) { return false; }
883  m_trafficProxy->setFollowedAircraft(callsign.toQString());
884  return true;
885  }
886 
887  void CSimulatorXPlane::updateRemoteAircraft()
888  {
889  Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "thread");
890 
891  const int remoteAircraftNo = this->getAircraftInRangeCount();
892  if (remoteAircraftNo < 1) { return; }
893 
894  // values used for position and parts
896  const qint64 currentTimestamp = QDateTime::currentMSecsSinceEpoch();
897 
898  // interpolation for all remote aircraft
899  PlanesPositions planesPositions;
900  PlanesSurfaces planesSurfaces;
901  PlanesTransponders planesTransponders;
902 
903  uint32_t aircraftNumber = 0;
904  const bool updateAllAircraft = this->isUpdateAllRemoteAircraft(currentTimestamp);
905  const CCallsignSet callsignsInRange = this->getAircraftInRangeCallsigns();
906  for (const CXPlaneMPAircraft &xplaneAircraft : std::as_const(m_xplaneAircraftObjects))
907  {
908  const CCallsign callsign(xplaneAircraft.getCallsign());
909  const bool hasCallsign = !callsign.isEmpty();
910  if (!hasCallsign)
911  {
912  // does not make sense to continue here
913  SWIFT_VERIFY_X(false, Q_FUNC_INFO, "missing callsign");
914  continue;
915  }
916 
917  // skip no longer in range
918  if (!callsignsInRange.contains(callsign)) { continue; }
919 
920  planesTransponders.callsigns.push_back(callsign.asString());
921  planesTransponders.codes.push_back(xplaneAircraft.getAircraft().getTransponderCode());
922  CTransponder::TransponderMode transponderMode = xplaneAircraft.getAircraft().getTransponderMode();
923  planesTransponders.idents.push_back(transponderMode == CTransponder::StateIdent);
924  planesTransponders.modeCs.push_back(transponderMode == CTransponder::ModeC);
925 
926  // setup
928  this->getInterpolationSetupConsolidated(callsign, updateAllAircraft);
929 
930  // interpolated situation/parts
931  const CInterpolationResult result =
932  xplaneAircraft.getInterpolation(currentTimestamp, setup, aircraftNumber++);
934  {
935  CAircraftSituation interpolatedSituation(result);
936 
937  // adjust altitude to compensate for XP12 temperature effect
938  const CLength relativeAltitude =
939  interpolatedSituation.geodeticHeight() - getOwnAircraftPosition().geodeticHeight();
940  const double altitudeDeltaWeight =
941  2 - qBound(3000.0, relativeAltitude.abs().value(CLengthUnit::ft()), 6000.0) / 3000;
942  const CLength alt = interpolatedSituation.getAltitude() +
943  m_altitudeDelta * altitudeDeltaWeight *
944  (1 - interpolatedSituation.getOnGroundInfo().getGroundFactor());
945  interpolatedSituation.setAltitude({ alt, interpolatedSituation.getAltitude().getReferenceDatum() });
946 
947  // update situation
948  if (updateAllAircraft || !this->isEqualLastSent(interpolatedSituation))
949  {
950  this->rememberLastSent(interpolatedSituation);
951  planesPositions.push_back(interpolatedSituation);
952  }
953  }
954  else
955  {
956  CLogMessage(this).warning(
957  this->getInvalidSituationLogMessage(callsign, result.getInterpolationStatus()));
958  }
959 
960  const CAircraftParts parts(result);
961  if (result.getPartsStatus().isSupportingParts() || parts.getPartsDetails() == CAircraftParts::GuessedParts)
962  {
963  if (updateAllAircraft || !this->isEqualLastSent(parts, callsign))
964  {
965  this->rememberLastSent(parts, callsign);
966  planesSurfaces.push_back(xplaneAircraft.getCallsign(), parts);
967  }
968  }
969 
970  } // all callsigns
971 
972  if (!planesTransponders.isEmpty()) { m_trafficProxy->setPlanesTransponders(planesTransponders); }
973 
974  if (!planesPositions.isEmpty())
975  {
976  if (CBuildConfig::isLocalDeveloperDebugBuild())
977  {
978  SWIFT_VERIFY_X(planesPositions.hasSameSizes(), Q_FUNC_INFO, "Mismatching sizes");
979  }
980  m_trafficProxy->setPlanesPositions(planesPositions);
981  }
982 
983  if (!planesSurfaces.isEmpty()) { m_trafficProxy->setPlanesSurfaces(planesSurfaces); }
984 
985  // stats
986  this->finishUpdateRemoteAircraftAndSetStatistics(currentTimestamp);
987  }
988 
989  void CSimulatorXPlane::requestRemoteAircraftDataFromXPlane()
990  {
991  if (this->isShuttingDownOrDisconnected()) { return; }
992 
993  // It is not required to request all elevations and CGs, but only for aircraft "near ground relevant"
994  // - we could use the elevation cache and CG cache to decide if we need to request
995  // - if an aircraft is on ground but not moving, we do not need to request elevation if we already have it (it
996  // will not change
997  CCallsignSet callsigns = m_xplaneAircraftObjects.getAllCallsigns();
999  callsigns.remove(remove);
1000  if (!callsigns.isEmpty()) { this->requestRemoteAircraftDataFromXPlane(callsigns); }
1001  }
1002 
1003  void CSimulatorXPlane::requestRemoteAircraftDataFromXPlane(const CCallsignSet &callsigns)
1004  {
1005  if (callsigns.isEmpty()) { return; }
1006  if (!m_trafficProxy || this->isShuttingDown()) { return; }
1007  const QStringList csStrings = callsigns.getCallsignStrings();
1008  QPointer<CSimulatorXPlane> myself(this);
1009  m_trafficProxy->getRemoteAircraftData(
1010  csStrings, [=](const QStringList &callsigns, const QDoubleList &latitudesDeg,
1011  const QDoubleList &longitudesDeg, const QDoubleList &elevationsMeters,
1012  const QBoolList &waterFlags, const QDoubleList &verticalOffsetsMeters) {
1013  if (!myself) { return; }
1014  this->updateRemoteAircraftFromSimulator(callsigns, latitudesDeg, longitudesDeg, elevationsMeters,
1015  waterFlags, verticalOffsetsMeters);
1016  });
1017  }
1018 
1019  void CSimulatorXPlane::triggerRequestRemoteAircraftDataFromXPlane(const CCallsignSet &callsigns)
1020  {
1021  if (callsigns.isEmpty()) { return; }
1022  QPointer<CSimulatorXPlane> myself(this);
1023  QTimer::singleShot(0, this, [=] {
1024  if (!myself) { return; }
1025  this->requestRemoteAircraftDataFromXPlane(callsigns);
1026  });
1027  }
1028 
1029  void CSimulatorXPlane::updateRemoteAircraftFromSimulator(
1030  const QStringList &callsigns, const QDoubleList &latitudesDeg, const QDoubleList &longitudesDeg,
1031  const QDoubleList &elevationsMeters, const QBoolList &waterFlags, const QDoubleList &verticalOffsetsMeters)
1032  {
1033  const int size = callsigns.size();
1034 
1035  // we skip if we are not near ground
1036  if (CBuildConfig::isLocalDeveloperDebugBuild())
1037  {
1038  Q_ASSERT_X(elevationsMeters.size() == size, Q_FUNC_INFO, "Wrong elevations");
1039  Q_ASSERT_X(waterFlags.size() == size, Q_FUNC_INFO, "Wrong waterFlags");
1040  Q_ASSERT_X(latitudesDeg.size() == size, Q_FUNC_INFO, "Wrong latitudesDeg");
1041  Q_ASSERT_X(longitudesDeg.size() == size, Q_FUNC_INFO, "Wrong longitudesDeg");
1042  Q_ASSERT_X(verticalOffsetsMeters.size() == size, Q_FUNC_INFO, "Wrong CG");
1043  }
1044 
1045  const CCallsignSet logCallsigns = this->getLogCallsigns();
1046  static const QString hint("remote acft.");
1047  for (int i = 0; i < size; i++)
1048  {
1049  const bool emptyCs = callsigns[i].isEmpty();
1050  SWIFT_VERIFY_X(!emptyCs, Q_FUNC_INFO, "Need callsign");
1051  if (emptyCs) { continue; }
1052 
1053  const CCallsign cs(callsigns[i]);
1054  if (!m_xplaneAircraftObjects.contains(cs)) { continue; }
1055  const CXPlaneMPAircraft xpAircraft = m_xplaneAircraftObjects[cs];
1056  SWIFT_VERIFY_X(xpAircraft.hasCallsign(), Q_FUNC_INFO, "Need callsign");
1057  if (!xpAircraft.hasCallsign()) { continue; }
1058 
1059  CElevationPlane elevation = CElevationPlane::null();
1060  if (!std::isnan(elevationsMeters[i]))
1061  {
1062  const CAltitude elevationAlt = CAltitude(elevationsMeters[i], CLengthUnit::m(), CLengthUnit::ft());
1063  elevation = CElevationPlane(CLatitude(latitudesDeg[i], CAngleUnit::deg()),
1064  CLongitude(longitudesDeg[i], CAngleUnit::deg()), elevationAlt,
1065  CElevationPlane::singlePointRadius());
1066  }
1067 
1068  const double cgValue = verticalOffsetsMeters[i]; // XP offset is swift CG
1069  CLength cg = std::isnan(cgValue) ? CLength::null() : CLength(cgValue, CLengthUnit::m(), CLengthUnit::ft());
1070  cg = fixSimulatorCg(cg, xpAircraft.getAircraftModel());
1071 
1072  // if we knew "on ground" here we could set it as parameter of rememberElevationAndSimulatorCG
1073  // with T778 we do NOT use this function for elevation here if "isSuspicious"
1074  const bool waterFlag = waterFlags[i];
1075  const bool useElevation = this->handleProbeValue(elevation, cs, waterFlag, hint, true);
1076  this->rememberElevationAndSimulatorCG(cs, xpAircraft.getAircraftModel(), false,
1077  useElevation ? elevation : CElevationPlane::null(), cg);
1078 
1079  // loopback
1080  if (logCallsigns.contains(cs)) { this->addLoopbackSituation(cs, elevation, cg); }
1081  }
1082  }
1083 
1084  CLength CSimulatorXPlane::fixSimulatorCg(const CLength &cg, const CAircraftModel &model)
1085  {
1087  // For XCSL null means no offset => 0
1088  // so not to override it with some DB value in X mode, we set it to "0"
1089  if (model.getDistributor().matchesKeyOrAlias(CDistributor::xplaneXcsl()) && cg.isNull())
1090  {
1091  return CLength(0.0, CLengthUnit::ft());
1092  }
1093  return cg;
1094  }
1095 
1096  void CSimulatorXPlane::disconnectFromDBus()
1097  {
1098  if (m_dBusConnection.isConnected())
1099  {
1100  if (m_trafficProxy) { m_trafficProxy->cleanup(); }
1101 
1102  if (m_dbusMode == P2P) { QDBusConnection::disconnectFromPeer(m_dBusConnection.name()); }
1103  else { QDBusConnection::disconnectFromBus(m_dBusConnection.name()); }
1104  }
1105  m_dBusConnection = QDBusConnection { "default" };
1106  }
1107 
1108  bool CSimulatorXPlane::sendXSwiftBusSettings()
1109  {
1110  if (this->isShuttingDownOrDisconnected()) { return false; }
1111  if (!m_serviceProxy) { return false; }
1112  CXSwiftBusSettings s = m_xSwiftBusServerSettings.get();
1113  s.setCurrentUtcTime();
1114  m_serviceProxy->setSettingsJson(s.toXSwiftBusJsonStringQt());
1115  CLogMessage(this).info(u"Send settings: %1") << s.toQString(true);
1116  return true;
1117  }
1118 
1119  CXSwiftBusSettings CSimulatorXPlane::receiveXSwiftBusSettings(bool &ok)
1120  {
1121  ok = false;
1123 
1124  if (this->isShuttingDownOrDisconnected()) { return s; }
1125  if (!m_serviceProxy) { return s; }
1126 
1127  const QString json = m_serviceProxy->getSettingsJson();
1128  s.parseXSwiftBusStringQt(json);
1129  ok = true;
1130  return s;
1131  }
1132 
1133  void CSimulatorXPlane::onXSwiftBusSettingsChanged()
1134  {
1135  bool ok;
1136  const CXSwiftBusSettings xPlaneSide = this->receiveXSwiftBusSettings(ok);
1137  if (ok)
1138  {
1139  // we only send if DBus did not change
1140  // DBus changes would require a restart
1141  const CXSwiftBusSettings swiftSide = m_xSwiftBusServerSettings.get();
1142  if (xPlaneSide.getDBusServerAddressQt() == swiftSide.getDBusServerAddressQt())
1143  {
1144  this->sendXSwiftBusSettings();
1145  }
1146  }
1147  }
1148 
1149  void CSimulatorXPlane::setMinTerrainProbeDistance(const CLength &distance)
1150  {
1151  if (distance.isNull()) { return; }
1152  if (m_minSuspicousTerrainProbe.isNull() || distance < m_minSuspicousTerrainProbe)
1153  {
1154  m_minSuspicousTerrainProbe = distance;
1155  }
1156  }
1157 
1158  void CSimulatorXPlane::updateAirportsInRange()
1159  {
1160  if (this->isShuttingDownOrDisconnected()) { return; }
1161  m_serviceProxy->updateAirportsInRange();
1162  }
1163 
1164  void CSimulatorXPlane::onRemoteAircraftAdded(const QString &callsign)
1165  {
1166  SWIFT_VERIFY_X(!callsign.isEmpty(), Q_FUNC_INFO, "Need callsign");
1167  if (callsign.isEmpty()) { return; }
1168  const CCallsign cs(callsign);
1169  CSimulatedAircraft addedRemoteAircraft = this->getAircraftInRangeForCallsign(cs);
1170 
1171  // statistics
1172  bool wasPending = false;
1173  if (m_addingInProgressAircraft.contains(cs))
1174  {
1175  wasPending = true;
1176  const qint64 wasStartedMs = m_addingInProgressAircraft.value(cs);
1177  const qint64 deltaTimeMs = QDateTime::currentMSecsSinceEpoch() - wasStartedMs;
1178  m_statsAddCurrentTimeMs = deltaTimeMs;
1179  if (deltaTimeMs > m_statsAddMaxTimeMs) { m_statsAddMaxTimeMs = deltaTimeMs; }
1180  m_addingInProgressAircraft.remove(cs);
1181  }
1182 
1183  if (!addedRemoteAircraft.hasCallsign())
1184  {
1185  CLogMessage(this).warning(u"Aircraft '%1' no longer in range, will be removed") << callsign;
1186  this->triggerRemoveAircraft(cs, TimeoutAdding);
1187  return;
1188  }
1189 
1190  CLogMessage(this).info(u"Added aircraft '%1'") << callsign;
1191  if (!wasPending)
1192  {
1193  // timeout?
1194  // slow adding?
1195  CLogMessage(this).warning(u"Added callsign '%1' was not in progress anymore. Timeout?") << callsign;
1196  }
1197 
1198  const bool rendered = true;
1199  addedRemoteAircraft.setRendered(rendered);
1200  this->updateAircraftRendered(cs, rendered);
1201  this->triggerRequestRemoteAircraftDataFromXPlane(cs);
1202  this->triggerAddNextPendingAircraft();
1203 
1204  Q_ASSERT_X(addedRemoteAircraft.hasCallsign(), Q_FUNC_INFO,
1205  "No callsign"); // already checked above, MUST never happen
1206  Q_ASSERT_X(addedRemoteAircraft.getCallsign() == cs, Q_FUNC_INFO,
1207  "No callsign"); // already checked above, MUST never happen
1208  m_xplaneAircraftObjects.insert(cs, CXPlaneMPAircraft(addedRemoteAircraft, this, &m_interpolationLogger));
1209  emit this->aircraftRenderingChanged(addedRemoteAircraft);
1210  }
1211 
1212  void CSimulatorXPlane::onRemoteAircraftAddingFailed(const QString &callsign)
1213  {
1214  SWIFT_VERIFY_X(!callsign.isEmpty(), Q_FUNC_INFO, "Need callsign");
1215  if (callsign.isEmpty()) { return; }
1216  const CCallsign cs(callsign);
1217  CSimulatedAircraft failedRemoteAircraft = this->getAircraftInRangeForCallsign(cs);
1218 
1219  if (failedRemoteAircraft.hasCallsign())
1220  {
1221  CLogMessage(this).warning(u"Adding aircraft failed: '%1'") << callsign;
1222  failedRemoteAircraft.setRendered(false);
1223  }
1224  else
1225  {
1226  CLogMessage(this).warning(u"Adding '%1' failed, but aircraft no longer in range, will be removed")
1227  << callsign;
1228  }
1229 
1230  const bool wasPending = (static_cast<int>(m_addingInProgressAircraft.remove(cs)) > 0);
1231  Q_UNUSED(wasPending)
1232 
1233  if (failedRemoteAircraft.hasCallsign() && !m_aircraftAddedFailed.containsCallsign(cs))
1234  {
1235  m_aircraftAddedFailed.push_back(failedRemoteAircraft);
1236  m_pendingToBeAddedAircraft.replaceOrAdd(failedRemoteAircraft); // try a second time
1237  }
1238  this->triggerAddNextPendingAircraft();
1239  }
1240 
1241  void CSimulatorXPlane::addNextPendingAircraft()
1242  {
1243  if (m_pendingToBeAddedAircraft.isEmpty()) { return; } // no more pending
1244 
1245  // housekeeping
1246  this->detectTimeoutAdding();
1247 
1248  // check if can add
1249  if (!this->canAddAircraft()) { return; }
1250 
1251  // next add cycle
1252  const CSimulatedAircraft newRemoteAircraft = m_pendingToBeAddedAircraft.front();
1253  m_pendingToBeAddedAircraft.pop_front();
1254  CLogMessage(this).info(u"Adding next pending aircraft '%1', pending %2, in progress %3")
1255  << newRemoteAircraft.getCallsignAsString() << m_pendingToBeAddedAircraft.size()
1256  << m_addingInProgressAircraft.size();
1257  this->physicallyAddRemoteAircraft(newRemoteAircraft);
1258  }
1259 
1260  void CSimulatorXPlane::triggerAddNextPendingAircraft()
1261  {
1262  QPointer<CSimulatorXPlane> myself(this);
1263  QTimer::singleShot(100, this, [=] {
1264  if (!myself || !sApp || sApp->isShuttingDown()) { return; }
1265  this->addNextPendingAircraft();
1266  });
1267  }
1268 
1269  int CSimulatorXPlane::detectTimeoutAdding()
1270  {
1271  if (m_addingInProgressAircraft.isEmpty()) { return 0; }
1272  const qint64 timeout = QDateTime::currentMSecsSinceEpoch() + TimeoutAdding;
1273  CCallsignSet timeoutCallsigns;
1274  const QList<CCallsign> addingCallsigns = m_addingInProgressAircraft.keys();
1275  for (const CCallsign &cs : addingCallsigns)
1276  {
1277  if (m_addingInProgressAircraft.value(cs) < timeout) { continue; }
1278  timeoutCallsigns.push_back(cs);
1279  }
1280 
1281  for (const CCallsign &cs : std::as_const(timeoutCallsigns))
1282  {
1283  m_addingInProgressAircraft.remove(cs);
1284  CLogMessage(this).warning(u"Adding for '%1' timed out") << cs.asString();
1285  }
1286 
1287  return timeoutCallsigns.size();
1288  }
1289 
1290  void CSimulatorXPlane::triggerRemoveAircraft(const CCallsign &callsign, qint64 deferMs)
1291  {
1292  QPointer<CSimulatorXPlane> myself(this);
1293  QTimer::singleShot(deferMs, this, [=] {
1294  if (!myself) { return; }
1295  this->physicallyRemoveRemoteAircraft(callsign);
1296  });
1297  }
1298 
1299  QPair<qint64, qint64> CSimulatorXPlane::minMaxTimestampsAddInProgress() const
1300  {
1301  static const QPair<qint64, qint64> empty(-1, -1);
1302  if (m_addingInProgressAircraft.isEmpty()) { return empty; }
1303  const QList<qint64> ts = m_addingInProgressAircraft.values();
1304  const auto mm = std::minmax_element(ts.constBegin(), ts.constEnd());
1305  return QPair<qint64, qint64>(*mm.first, *mm.second);
1306  }
1307 
1308  bool CSimulatorXPlane::canAddAircraft() const
1309  {
1310  if (m_addingInProgressAircraft.isEmpty()) { return true; }
1311 
1312  // check
1313  const qint64 now = QDateTime::currentMSecsSinceEpoch();
1314  const QPair<qint64, qint64> tsMM = this->minMaxTimestampsAddInProgress();
1315  const qint64 deltaLatest = now - tsMM.second;
1316  const bool canAdd = (deltaLatest > TimeoutAdding);
1317  return canAdd;
1318  }
1319 
1321  IOwnAircraftProvider *ownAircraftProvider,
1322  IRemoteAircraftProvider *remoteAircraftProvider,
1323  IClientProvider *clientProvider)
1324  {
1325  return new CSimulatorXPlane(info, ownAircraftProvider, remoteAircraftProvider, clientProvider, this);
1326  }
1327 
1329  {
1330  constexpr int QueryInterval = 5 * 1000; // 5 seconds
1331  m_timer.setInterval(QueryInterval);
1332  m_timer.setObjectName(this->objectName().append(":m_timer"));
1333  connect(&m_timer, &QTimer::timeout, this, &CSimulatorXPlaneListener::checkConnection);
1334  }
1335 
1336  void CSimulatorXPlaneListener::startImpl() { m_timer.start(); }
1337 
1338  void CSimulatorXPlaneListener::stopImpl() { m_timer.stop(); }
1339 
1341  {
1342  if (!m_timer.isActive()) { return; }
1343  if (this->isShuttingDown()) { return; }
1344 
1345  m_timer.start(); // restart because we will check just now
1346  QPointer<CSimulatorXPlaneListener> myself(this);
1347  QTimer::singleShot(0, this, [=] {
1348  if (!myself) { return; }
1349  checkConnection();
1350  });
1351  }
1352 
1353  void CSimulatorXPlaneListener::checkConnection()
1354  {
1355  if (this->isShuttingDown()) { return; }
1356  Q_ASSERT_X(!CThreadUtils::thisIsMainThread(), Q_FUNC_INFO, "Expect to run in background");
1357  QElapsedTimer t;
1358  t.start();
1359 
1360  QString via;
1361  m_dBusServerAddress = m_xSwiftBusServerSettings.getThreadLocal().getDBusServerAddressQt();
1362  if (CDBusServer::isSessionOrSystemAddress(m_dBusServerAddress))
1363  {
1364  checkConnectionViaSessionBus();
1365  via = "SessionBus";
1366  }
1367  else if (CDBusServer::isQtDBusAddress(m_dBusServerAddress))
1368  {
1369  checkConnectionViaPeer(m_dBusServerAddress);
1370  via = "P2P";
1371  }
1372 
1373  CLogMessage(this).debug(u"Checked sim. 'XPLANE' via '%1' connection in %2ms") << via << t.elapsed();
1374  }
1375 
1376  void CSimulatorXPlaneListener::checkConnectionViaSessionBus()
1377  {
1378  m_DBusConnection = QDBusConnection::sessionBus();
1379  if (!m_DBusConnection.isConnected())
1380  {
1381  m_DBusConnection.disconnectFromBus(m_DBusConnection.name());
1382  return;
1383  }
1384  checkConnectionCommon(); // bus
1385  m_DBusConnection.disconnectFromBus(m_DBusConnection.name());
1386  }
1387 
1388  void CSimulatorXPlaneListener::checkConnectionViaPeer(const QString &address)
1389  {
1390  m_DBusConnection = QDBusConnection::connectToPeer(address, "xswiftbus");
1391  if (!m_DBusConnection.isConnected())
1392  {
1393  // This is required to cleanup the connection in QtDBus
1394  m_DBusConnection.disconnectFromPeer(m_DBusConnection.name());
1395  return;
1396  }
1397  checkConnectionCommon(); // peer
1398  m_DBusConnection.disconnectFromPeer(m_DBusConnection.name());
1399  }
1400 
1401  void CSimulatorXPlaneListener::checkConnectionCommon()
1402  {
1403  CXSwiftBusServiceProxy service(m_DBusConnection);
1404  CXSwiftBusTrafficProxy traffic(m_DBusConnection);
1405 
1406  const bool result = service.isValid() && traffic.isValid();
1407  if (!result) { return; }
1408 
1409  const QString swiftVersion = CBuildConfig::getVersionString();
1410  const QString xswiftbusVersion = service.getVersionNumber();
1411  const QString xswiftbusCommitHash = service.getCommitHash();
1412  if (xswiftbusVersion.isEmpty())
1413  {
1414  CLogMessage(this).warning(u"Could not determine which version of xswiftbus is running. Mismatched versions "
1415  u"might cause instability.");
1416  }
1417  else if (commitHash() != xswiftbusCommitHash)
1418  {
1419  CLogMessage(this).error(u"You are using an incorrect version of xswiftbus. The version of xswiftbus (%1) "
1420  u"should match the version of swift (%2). Consider upgrading!")
1421  << xswiftbusVersion << swiftVersion;
1422  }
1423 
1424  if (!traffic.initialize())
1425  {
1426  CLogMessage(this).error(
1427  u"Connection to xswiftbus successful, but could not initialize xswiftbus. Check X-Plane Log.txt.");
1428  return;
1429  }
1430 
1431  const MultiplayerAcquireInfo info = traffic.acquireMultiplayerPlanes();
1432  if (!info.hasAcquired)
1433  {
1434  const QString owner =
1435  info.owner.trimmed().isEmpty() ? QStringLiteral("unknown plugin") : info.owner.trimmed();
1436  CLogMessage(this).error(
1437  u"Connection to xswiftbus successful, but could not acquire multiplayer planes. '%1' has acquired them "
1438  u"already. Disable '%2' or remove it if not required and reload xswiftbus.")
1439  << owner << owner.toLower();
1440  return;
1441  }
1442 
1444  }
1445 
1446  void CSimulatorXPlaneListener::serviceRegistered(const QString &serviceName)
1447  {
1448  if (serviceName == xswiftbusServiceName()) { emit simulatorStarted(getPluginInfo()); }
1449  m_DBusConnection.disconnectFromBus(m_DBusConnection.name());
1450  }
1451 
1452  void CSimulatorXPlaneListener::onXSwiftBusServerSettingChanged()
1453  {
1454  const CXSwiftBusSettings s = m_xSwiftBusServerSettings.get();
1455  if (m_dBusServerAddress != s.getDBusServerAddressQt())
1456  {
1457  this->stop();
1458  this->start();
1459  m_dBusServerAddress = s.getDBusServerAddressQt();
1460  }
1461  }
1462 } // namespace swift::simplugin::xplane
SWIFT_CORE_EXPORT swift::core::CApplication * sApp
Single instance of application object.
Definition: application.cpp:71
bool isShuttingDown() const
Is application shutting down?
Interface to a simulator.
Definition: simulator.h:59
double m_simTimeRatio
ratio of simulation time to real time, due to low FPS (X-Plane)
Definition: simulator.h:563
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
double m_averageFps
FPS.
Definition: simulator.h:562
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
double m_trackMilesShort
difference between real and reported groundspeed, multiplied by time
Definition: simulator.h:564
double m_minutesLate
difference between real and reported groundspeed, integrated over time
Definition: simulator.h:565
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 insufficientFrameRateDetected(bool fatal)
Frame rate has fallen too far below the threshold to maintain consistent sim rate.
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
bool isTestMode() const
Test mode? (driver can skip code parts etc., driver dependent)
Definition: simulator.h:180
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::settings::CSpecializedSimulatorSettings getSimulatorSettings() const
Settings for current simulator.
Definition: simulator.h:159
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
T get() const
Get a copy of the current value.
Definition: valuecache.h:408
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
void clear()
Removes all elements in the collection.
Definition: collection.h:194
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 & validationError(const char16_t(&format)[N])
Set the severity to error, providing a format string, and adding the validation category.
Derived & error(const char16_t(&format)[N])
Set the severity to error, providing a format string.
Derived & debug()
Set the severity to debug.
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
Build a QSet more efficiently when calling insert() in a for loop.
Definition: setbuilder.h:25
void insert(const T &value)
Add an element to the set. Runs in amortized constant time.
Definition: setbuilder.h:29
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 about aircraft's lights.
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)
const aviation::CAltitude & getAltitude() const
Altitude (synonym for geodetic height)
double getAltitudeValue(const physical_quantities::CLengthUnit &unit) const
Altitude (synonym for geodetic height)
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
bool isPrivateMessage() const
Is private message?
Definition: textmessage.cpp:53
const QString & getMessage() const
Get message.
Definition: textmessage.h:78
bool isServerMessage() const
Initial message of server?
const aviation::CCallsign & getSenderCallsign() const
Get callsign (from)
Definition: textmessage.h:54
bool isSupervisorMessage() const
Supervisor message?
Definition: textmessage.cpp:58
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.
QString valueRoundedAsString(MU unit, int digits=-1) const
Rounded 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.
const QString & getModelString() const
Model key, either queried or loaded from simulator model.
void setCallsign(const aviation::CCallsign &callsign)
Corresponding callsign if applicable.
const CDistributor & getDistributor() const
Get distributor.
Value object encapsulating a list of aircraft models.
bool matchesKeyOrAlias(const QString &keyOrAlias) const
Matches key or alias.
Definition: distributor.cpp:40
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.
swift::misc::physical_quantities::CLength getDistanceToOwnAircraft(const swift::misc::geo::ICoordinateGeodetic &position) const
Distance to own aircraft.
bool updateCockpit(const swift::misc::simulation::CSimulatedAircraft &aircraft, const swift::misc::CIdentifier &originator)
swift::misc::geo::CCoordinateGeodetic getOwnAircraftPosition() const
Own aircraft's position.
CSimulatedAircraft getOwnAircraft() const
Own aircraft.
bool isSupportingParts() const
Supporting parts.
Definition: partsstatus.h:26
aviation::CCallsignSet getAircraftInRangeCallsigns() const
Unique callsigns for aircraft in range.
bool isAircraftInRange(const aviation::CCallsign &callsign) const
Is 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 removePendingElevationRequest(const aviation::CCallsign &cs)
Remove pending timestamp.
void setDefaultModel(const CAircraftModel &defaultModel)
Default model.
const QString & getSimulatorDirectoryOrDefault() const
Simulator directory or default path.
void parseXSwiftBusStringQt(const QString &json)
Load and parse config file.
virtual void setCurrentUtcTime()
Sets both timestamps.
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 int physicallyRemoveAllRemoteAircraft()
Remove all remote aircraft and their data via ISimulator::clearAllRemoteAircraftData.
virtual bool connectTo()
Connect to simulator.
virtual bool followAircraft(const swift::misc::aviation::CCallsign &callsign)
Follow aircraft.
virtual bool isConnected() const
Are we connected to the simulator?
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.
CSimulatorXPlane(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 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 resetAircraftStatistics()
Reset the statistics.
virtual bool updateOwnSimulatorSelcal(const swift::misc::aviation::CSelcal &selcal, const swift::misc::CIdentifier &originator)
Update own aircraft cockpit (usually from context)
virtual void clearAllRemoteAircraftData()
Clear all aircraft related data, but do not physically remove the aircraft.
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 unload()
Driver will be unloaded.
virtual bool updateOwnSimulatorCockpit(const swift::misc::simulation::CSimulatedAircraft &aircraft, const swift::misc::CIdentifier &originator)
Update own aircraft cockpit (usually from context)
virtual swift::misc::aviation::CCallsignSet physicallyRenderedAircraft() const
Physically rendered (displayed in simulator) This shall only return aircraft handled in the simulator...
virtual swift::misc::CStatusMessageList getInterpolationMessages(const swift::misc::aviation::CCallsign &callsign) const
Interpolation messages for callsign.
virtual void setFlightNetworkConnected(bool connected)
Flight network has been connected.
virtual bool physicallyAddRemoteAircraft(const swift::misc::simulation::CSimulatedAircraft &newRemoteAircraft)
Add new remote aircraft physically to the simulator.
virtual bool physicallyRemoveRemoteAircraft(const swift::misc::aviation::CCallsign &callsign)
Remove remote aircraft from simulator.
virtual QString getStatisticsSimulatorSpecific() const
Allows to print out simulator specific statistics.
virtual void callbackReceivedRequestedElevation(const swift::misc::geo::CElevationPlane &plane, const swift::misc::aviation::CCallsign &callsign, bool isWater)
A requested elevation has been received.
virtual void displayStatusMessage(const swift::misc::CStatusMessage &message) const
Display a status message in the simulator.
virtual bool disconnectFrom()
Disconnect from simulator.
virtual void displayTextMessage(const swift::misc::network::CTextMessage &message) const
Display a text message.
CSimulatorXPlaneListener(const swift::misc::simulation::CSimulatorPluginInfo &info)
Constructor.
virtual void checkImpl()
Plugin specific implementation to check.
virtual void startImpl()
Plugin specific implementation to start listener.
virtual void stopImpl()
Plugin specific implementation to stop listener.
Class representing a X-Plane multiplayer aircraft.
const swift::misc::simulation::CSimulatedAircraft & getAircraft() const
Simulated aircraft (as added)
Proxy object connected to a real XSwiftBus::CService object via DBus.
void getOwnAircraftModelDataAsync(swift::simplugin::xplane::XPlaneData *o_xplaneData)
Get own model data.
void getOwnAircraftCom2DataAsync(swift::simplugin::xplane::XPlaneData *o_xplaneData)
Get own aircraft COM2 data.
void setTransponderMode(int mode)
Set the current transponder mode (depends on the aircraft, 0 and 1 usually mean standby,...
QString getAircraftModelFilename() const
Get base filename of current aircraft model.
int getXPlaneVersionMajor() const
Get major version number.
void setCom2ActiveKhz(int freq)
Set the current COM2 active frequency in kHz.
void getAllWheelsOnGroundAsync(bool *o_allWheels)
Get whether all wheels are on the ground.
QString getAircraftLivery() const
Get current aircraft livery.
void setTransponderCode(int code)
Set the current transponder code in decimal.
void addTextMessage(const QString &text, double red, double green, double blue)
Add a text message to the on-screen display, with RGB components in the range [0,1].
void setCom1StandbyKhz(int freq)
Set the current COM1 standby frequency in kHz.
void getOwnAircraftLightsAsync(swift::simplugin::xplane::XPlaneData *o_xplaneData)
Get own lights data.
void getOwnAircraftVelocityDataAsync(swift::simplugin::xplane::XPlaneData *o_xplaneData)
Get own aircraft velocity data.
void setCom1ActiveKhz(int freq)
Set the current COM1 active frequency in kHz.
void getOwnAircraftXpdrAsync(swift::simplugin::xplane::XPlaneData *o_xplaneData)
Get own XPDR data.
QString getAircraftDescription() const
Get the description of the current aircraft model.
QString getAircraftName() const
Get name of current aircraft model.
bool isValid() const
Does the remote object exist?
void setSettingsJson(const QString &json)
Set settings.
QString getAircraftModelString() const
Get canonical swift model string of current aircraft model.
QString getAircraftIcaoCode() const
Get the ICAO code of the current aircraft model.
QString getAircraftModelPath() const
Get full path to current aircraft model.
void getPressureAltitudeFtAsync(double *o_altitude)
Get aircraft pressure altitude in feet in standard atmosphere in X-Plane 12. NaN in earlier versions ...
void getFrameStats(double *o_averageFps, double *o_simTimeRatio, double *o_trackMilesShort, double *o_minutesLate) const
Frames-per-second, averaged over the last 500 frames, or since this function was last called,...
void getOwnAircraftPartsAsync(swift::simplugin::xplane::XPlaneData *o_xplaneData)
Get own parts such as gear, flaps.
void getHeightAglMAsync(double *o_height)
Get aircraft height in meters.
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 getOwnAircraftSituationDataAsync(swift::simplugin::xplane::XPlaneData *o_xplaneData)
Get own aircraft situation data.
void setCom2StandbyKhz(int freq)
Set the current COM2 standby frequency in kHz.
void resetFrameTotals()
Reset the monitoring of total miles and minutes lost due to low frame rate.
void cancelAllPendingAsyncCalls()
Cancel all current async slot calls.
int getXPlaneVersionMinor() const
Get minor version number.
void getOwnAircraftCom1DataAsync(swift::simplugin::xplane::XPlaneData *o_xplaneData)
Get own aircraft COM1 data.
Proxy object connected to a real XSwiftBus::CTraffic object via DBus.
void cleanup()
Reverse the actions of initialize().
void addPlane(const QString &callsign, const QString &modelName, const QString &aircraftIcao, const QString &airlineIcao, const QString &livery)
Introduce a new traffic aircraft.
void remoteAircraftAddingFailed(const QString &callsign)
Remote aircraft adding failed.
void setPlanesPositions(const swift::simplugin::xplane::PlanesPositions &planesPositions)
Set the position of multiple traffic aircrafts.
void setFollowedAircraft(const QString &callsign)
Sets the aircraft with callsign to be followed in plane view.
void setPlanesSurfaces(const swift::simplugin::xplane::PlanesSurfaces &planesSurfaces)
Set the flight control surfaces and lights of multiple traffic aircrafts.
QString loadPlanesPackage(const QString &path)
Load a collection of planes from the given directory and return error message if unsuccessful.
void setPlanesTransponders(const swift::simplugin::xplane::PlanesTransponders &planesTransponders)
Set the transponder of multiple traffic aircraft.
void removePlane(const QString &callsign)
Remove a traffic aircraft.
bool isValid() const
Does the remote object exist?
void remoteAircraftAdded(const QString &callsign)
Remote aircraft successfully added.
void getRemoteAircraftData(const QStringList &callsigns, const RemoteAircraftDataCallback &setter) const
Get remote aircrafts data (lat, lon, elevation and CG)
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
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.
int com2StandbyKhz
COM2 standby [kHz].
double headingRadPerSec
Heading angular velocity [rad/s].
double groundspeedMs
Ground speed [m/s].
double com2Volume
COM2 volume 0..1.
double com1Volume
COM1 volume 0..1.
double localXVelocityMs
Local x velocity [m/s].
int xpdrMode
Transponder mode (off=0,stdby=1,on=2,test=3)
bool isCom1Transmitting
COM1 transmittings.
double flapsDeployRatio
Flaps deployment ratio [%].
double localZVelocityMs
Local z velocity [m/s].
bool isCom2Transmitting
COM2 transmittings.
double rollRadPerSec
Roll angular velocity [rad/s].
double gearDeployRatio
Gear deployment ratio [%].
double trueHeadingDeg
True heading [deg].
bool xpdrIdent
Is transponder in ident?
bool onGroundAll
All wheels on ground?
double latitudeDeg
Longitude [deg].
double pitchRadPerSec
Pitch angular velocity [rad/s].
QList< double > enginesN1Percentage
N1 per engine [%].
double speedBrakeRatio
Speed break ratio [%].
double seaLevelPressureInHg
Sea level pressure [inhg].
int com1StandbyKhz
COM1 standby [kHz].
double pressureAltitudeFt
Pressure altitude [ft, XP12].
bool landingLightsOn
Landing lights on?
double localYVelocityMs
Local y velocity [m/s].
#define SWIFT_VERIFY_X(COND, WHERE, WHAT)
A weaker kind of assert.
Definition: verify.h:26