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