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  engines.push_back(engine);
407  }
408 
409  const CAircraftLights lights(m_xplaneData.strobeLightsOn, m_xplaneData.landingLightsOn,
410  m_xplaneData.taxiLightsOn, m_xplaneData.beaconLightsOn,
411  m_xplaneData.navLightsOn, false);
412 
413  const CAircraftParts parts { lights,
414  m_xplaneData.gearDeployRatio > 0,
415  qRound(m_xplaneData.flapsDeployRatio * 100.0),
416  m_xplaneData.speedBrakeRatio > 0.5,
417  engines,
418  m_xplaneData.onGroundAll };
419 
420  this->updateOwnParts(parts);
421 
422  CCallsignSet invalid;
423  for (CXPlaneMPAircraft &xplaneAircraft : m_xplaneAircraftObjects)
424  {
425  // Update remote aircraft to have the latest transponder modes, codes etc.
426  const CCallsign cs = xplaneAircraft.getCallsign();
427  const CSimulatedAircraft simulatedAircraft = this->getAircraftInRangeForCallsign(cs);
428  if (!simulatedAircraft.hasCallsign())
429  {
430  // removed in provider
431  if (!cs.isEmpty()) { invalid.insert(cs); }
432  continue;
433  }
434  xplaneAircraft.setSimulatedAircraft(simulatedAircraft);
435  }
436 
437  // remove the invalid ones
438  int i = 0;
439  if (this->isTestMode()) { invalid.clear(); } // skip this in test mode
440  for (const CCallsign &cs : invalid) { this->triggerRemoveAircraft(cs, ++i * 100); }
441 
442  // KB: IMHO those data are pretty useless for XPlane
443  // no need to request them all the times
444  if ((m_slowTimerCalls % 3u) == 0u) { this->requestRemoteAircraftDataFromXPlane(); }
445 
446  // FPS
447  // reading FPS resets average, so we only monitor over some time
448  if ((m_slowTimerCalls % 5u) == 0u)
449  {
450  constexpr double warningMiles = 1;
451  constexpr double disconnectMiles = 2;
452  const double previousMiles = m_trackMilesShort;
453 
455 
456  if (previousMiles < disconnectMiles && m_trackMilesShort >= disconnectMiles)
457  {
459  }
460  else if (previousMiles < warningMiles && m_trackMilesShort >= warningMiles)
461  {
462  emit insufficientFrameRateDetected(false);
463  }
464  }
465  }
466  }
467 
468  bool CSimulatorXPlane::isConnected() const { return m_serviceProxy && m_trafficProxy; }
469 
471  {
472  if (this->isConnected()) { return true; }
473  const QString dBusServerAddress = m_xSwiftBusServerSettings.getThreadLocal().getDBusServerAddressQt();
474 
475  if (CDBusServer::isSessionOrSystemAddress(dBusServerAddress))
476  {
477  m_dBusConnection = QDBusConnection::sessionBus();
478  m_dbusMode = Session;
479  }
480  else if (CDBusServer::isQtDBusAddress(dBusServerAddress))
481  {
482  m_dBusConnection = QDBusConnection::connectToPeer(dBusServerAddress, "xswiftbus");
483  if (!m_dBusConnection.isConnected()) { return false; }
484  m_dbusMode = P2P;
485  }
486 
487  m_serviceProxy = new CXSwiftBusServiceProxy(m_dBusConnection, this);
488  m_trafficProxy = new CXSwiftBusTrafficProxy(m_dBusConnection, this);
489 
490  // hook up disconnected slot of connection
491  bool s = m_dBusConnection.connect(QString(), DBUS_PATH_LOCAL, DBUS_INTERFACE_LOCAL, "Disconnected", this,
492  SLOT(onDBusServiceUnregistered()));
493  Q_ASSERT(s);
494  if (!m_serviceProxy->isValid() || !m_trafficProxy->isValid())
495  {
496  this->disconnectFrom();
497  return false;
498  }
499 
500  emitOwnAircraftModelChanged(m_serviceProxy->getAircraftModelPath(), m_serviceProxy->getAircraftModelFilename(),
501  m_serviceProxy->getAircraftLivery(), m_serviceProxy->getAircraftIcaoCode(),
502  m_serviceProxy->getAircraftModelString(), m_serviceProxy->getAircraftName(),
503  m_serviceProxy->getAircraftDescription());
504  QString xplaneVersion = QStringLiteral("%1.%2")
505  .arg(m_serviceProxy->getXPlaneVersionMajor())
506  .arg(m_serviceProxy->getXPlaneVersionMinor());
507  setSimulatorDetails("X-Plane", {}, xplaneVersion);
509  &CSimulatorXPlane::emitOwnAircraftModelChanged);
510  connect(m_trafficProxy, &CXSwiftBusTrafficProxy::simFrame, this, &CSimulatorXPlane::updateRemoteAircraft);
512  &CSimulatorXPlane::onRemoteAircraftAdded);
514  &CSimulatorXPlane::onRemoteAircraftAddingFailed);
515  if (m_watcher) { m_watcher->setConnection(m_dBusConnection); }
516  m_trafficProxy->removeAllPlanes();
517 
518  // send the settings
519  this->sendXSwiftBusSettings();
520 
521  // load CSL
522  this->loadCslPackages();
523 
524  // finish
525  this->initSimulatorInternals();
527 
528  return true;
529  }
530 
532  {
533  if (!this->isConnected()) { return true; } // avoid emit if already disconnected
534  this->disconnectFromDBus();
535  if (m_watcher) { m_watcher->setConnection(m_dBusConnection); }
536  delete m_serviceProxy;
537  delete m_trafficProxy;
538  m_serviceProxy = nullptr;
539  m_trafficProxy = nullptr;
540  m_fastTimerCalls = 0;
541  m_slowTimerCalls = 0;
542 
544  return true;
545  }
546 
547  void CSimulatorXPlane::onDBusServiceUnregistered()
548  {
549  if (!m_serviceProxy) { return; }
550  CLogMessage(this).info(u"XPlane xSwiftBus service unregistered");
551 
552  if (m_dbusMode == P2P) { QDBusConnection::disconnectFromPeer(m_dBusConnection.name()); }
553  m_dBusConnection = QDBusConnection { "default" };
554  if (m_watcher) { m_watcher->setConnection(m_dBusConnection); }
555  delete m_serviceProxy;
556  delete m_trafficProxy;
557  m_serviceProxy = nullptr;
558  m_trafficProxy = nullptr;
560  }
561 
562  void CSimulatorXPlane::emitOwnAircraftModelChanged(const QString &path, const QString &filename,
563  const QString &livery, const QString &icao,
564  const QString &modelString, const QString &name,
565  const QString &description)
566  {
567  CAircraftModel model(modelString, CAircraftModel::TypeOwnSimulatorModel, CSimulatorInfo::XPLANE, name,
568  description, icao);
569  if (!livery.isEmpty()) { model.setModelString(model.getModelString() + " " + livery); }
570  model.setFileName(path + "/" + filename);
571 
573  }
574 
576  {
577  // No assert here, as status message may come in because of network problems
578  if (this->isShuttingDownOrDisconnected()) { return; }
579 
580  // avoid infinite recursion in case this function is called due to a message caused by this very function
581  static bool isInFunction = false;
582  if (isInFunction) { return; }
583  isInFunction = true;
584 
585  std::vector<int> msgBoxValues = m_xSwiftBusServerSettings.getThreadLocal().getMessageBoxValuesVector();
586  QColor color = msgBoxValues[9];
587  /* switch (message.getSeverity())
588  {
589  case CStatusMessage::SeverityDebug: color = "teal"; break;
590  case CStatusMessage::SeverityInfo: color = "cyan"; break;
591  case CStatusMessage::SeverityWarning: color = "orange"; break;
592  case CStatusMessage::SeverityError: color = "red"; break;
593  } */
594 
595  m_serviceProxy->addTextMessage("swift: " + message.getMessage(), color.redF(), color.greenF(), color.blueF());
596  isInFunction = false;
597  }
598 
600  {
601  // avoid issues during shutdown
602  if (this->isShuttingDownOrDisconnected()) { return; }
603 
604  std::vector<int> msgBoxValues = m_xSwiftBusServerSettings.getThreadLocal().getMessageBoxValuesVector();
605  QColor color;
606  if (message.isServerMessage()) { color = msgBoxValues[8]; }
607  else if (message.isSupervisorMessage()) { color = msgBoxValues[10]; }
608  else if (message.isPrivateMessage()) { color = msgBoxValues[7]; }
609  else { color = msgBoxValues[6]; }
610 
611  m_serviceProxy->addTextMessage(message.getSenderCallsign().toQString() + ": " + message.getMessage(),
612  color.redF(), color.greenF(), color.blueF());
613  }
614 
616  {
617  return m_xplaneAircraftObjects.contains(callsign);
618  }
619 
621  const CIdentifier &originator)
622  {
623  if (originator == this->identifier()) { return false; }
624  if (this->isShuttingDownOrDisconnected()) { return false; } // could happen during shutdown
625 
626  auto com1 = CComSystem::getCom1System({ m_xplaneData.com1ActiveKhz, CFrequencyUnit::kHz() },
627  { m_xplaneData.com1StandbyKhz, CFrequencyUnit::kHz() });
628  auto com2 = CComSystem::getCom2System({ m_xplaneData.com2ActiveKhz, CFrequencyUnit::kHz() },
629  { m_xplaneData.com2StandbyKhz, CFrequencyUnit::kHz() });
630  auto xpdr = CTransponder::getStandardTransponder(m_xplaneData.xpdrCode,
631  xpdrMode(m_xplaneData.xpdrMode, m_xplaneData.xpdrIdent));
632  if (aircraft.hasChangedCockpitData(com1, com2, xpdr))
633  {
634  m_xplaneData.com1ActiveKhz =
635  aircraft.getCom1System().getFrequencyActive().valueInteger(CFrequencyUnit::kHz());
636  m_xplaneData.com1StandbyKhz =
637  aircraft.getCom1System().getFrequencyStandby().valueInteger(CFrequencyUnit::kHz());
638  m_xplaneData.com2ActiveKhz =
639  aircraft.getCom2System().getFrequencyActive().valueInteger(CFrequencyUnit::kHz());
640  m_xplaneData.com2StandbyKhz =
641  aircraft.getCom2System().getFrequencyStandby().valueInteger(CFrequencyUnit::kHz());
642  m_xplaneData.xpdrCode = aircraft.getTransponderCode();
643  m_xplaneData.xpdrMode = xpdrMode(aircraft.getTransponderMode());
644  m_serviceProxy->setCom1ActiveKhz(m_xplaneData.com1ActiveKhz);
645  m_serviceProxy->setCom1StandbyKhz(m_xplaneData.com1StandbyKhz);
646  m_serviceProxy->setCom2ActiveKhz(m_xplaneData.com2ActiveKhz);
647  m_serviceProxy->setCom2StandbyKhz(m_xplaneData.com2StandbyKhz);
648  m_serviceProxy->setTransponderCode(m_xplaneData.xpdrCode);
649  m_serviceProxy->setTransponderMode(m_xplaneData.xpdrMode);
650 
651  m_serviceProxy
652  ->cancelAllPendingAsyncCalls(); // in case there is already a reply with some old data incoming
653  return true;
654  }
655  return false;
656  }
657 
658  bool CSimulatorXPlane::updateOwnSimulatorSelcal(const CSelcal &selcal, const CIdentifier &originator)
659  {
660  if (originator == this->identifier()) { return false; }
661  if (this->isShuttingDownOrDisconnected()) { return false; } // could happen during shutdown
662 
664  Q_UNUSED(selcal)
665  return false;
666  }
667 
668  void CSimulatorXPlane::loadCslPackages()
669  {
670  // An ad-hoc type for keeping track of packages as they are discovered.
671  // A model is a member of a package if the model path starts with the package path.
672  // A trailing separator is appended only for checking if a model path starts with this path.
673  struct Prefix
674  {
675  Prefix(const QString &p) : s(p + '/') {}
676  QString parent() const
677  {
678  return s.section('/', 0, -2, QString::SectionSkipEmpty | QString::SectionIncludeLeadingSep);
679  }
680  bool isPrefixOf(const QString &o) const { return o.startsWith(s); }
681  QString s;
682  };
683 
684  // Heterogeneous comparison so a package can be found by binary search
685  // (e.g. std::lower_bound) using a model path as the search key.
686  struct PrefixComparator
687  {
688  bool operator()(const Prefix &a, const QString &b) const
689  {
690  return QStringView(a.s) < QStringView(b).left(a.s.size());
691  }
692  bool operator()(const QString &a, const Prefix &b) const
693  {
694  return QStringView(a).left(b.s.size()) < QStringView(b.s);
695  }
696  };
697 
698  // The list of packages discovered so far.
699  QList<Prefix> packages;
700 
701  Q_ASSERT(this->isConnected());
702  const CAircraftModelList models = this->getModelSet();
703 
704  // Find the CSL packages for all models in the list
705  for (const auto &model : models)
706  {
707  const QString &modelFile = model.getFileName();
708  if (modelFile.isEmpty() || !QFile::exists(modelFile)) { continue; }
709 
710  // Check if this model's package was already found
711  auto it = std::lower_bound(packages.begin(), packages.end(), modelFile, PrefixComparator());
712  if (it != packages.end() && it->isPrefixOf(modelFile)) { continue; }
713 
714  // This model's package was not already found, so find it and add it to the list
715  QString package = findCslPackage(modelFile);
716  if (package.isEmpty()) { continue; }
717  packages.insert(it, package);
718  }
719 
720  // comment KB 2019-06
721  // a package is one xsb_aircraft.txt file BB has 9, X-CSL has 76
722  CSetBuilder<QString> superpackages;
723  for (const Prefix &package : std::as_const(packages)) { superpackages.insert(package.parent()); }
724  QStringList superpackagesList = superpackages;
725 
727  for (const QString &package : superpackagesList)
728  {
729  if (CDirectoryUtils::isSameOrSubDirectoryOf(package, simDir))
730  {
731  const QString message = m_trafficProxy->loadPlanesPackage(package);
732  if (!message.isEmpty())
733  {
734  CLogMessage(this).validationError(u"CSL package '%1' xpmp error: %2") << package << message;
735  }
736  }
737  else
738  {
740  u"CSL package '%1' can not be loaded as it is outside the X-Plane installation directory")
741  << package;
742  }
743  }
744  }
745 
746  QString CSimulatorXPlane::findCslPackage(const QString &modelFile)
747  {
749  const QFileInfo info(modelFile);
750  QDir dir = info.isDir() ? QDir(modelFile) : info.dir();
751  do {
752  if (dir.exists(QStringLiteral("xsb_aircraft.txt"))) { return dir.path(); }
753  }
754  while (dir.cdUp());
755  CLogMessage(this).warning(u"Failed to find CSL package for %1") << modelFile;
756  return {};
757  }
758 
760  {
761  // avoid issue in rapid shutdown
762  if (this->isShuttingDownOrDisconnected()) { return false; }
763 
764  // entry checks
765  Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "thread");
766  Q_ASSERT_X(!newRemoteAircraft.getCallsign().isEmpty(), Q_FUNC_INFO, "empty callsign");
767  Q_ASSERT_X(newRemoteAircraft.hasModelString(), Q_FUNC_INFO, "missing model string");
768 
769  // crosscheck if still a valid aircraft
770  // it can happen that aircraft has been removed, timed out ...
771  if (!this->isAircraftInRangeOrTestMode(newRemoteAircraft.getCallsign()))
772  {
773  // next cycle will be called by callbacks or timer
774  CLogMessage(this).warning(u"Aircraft '%1' no longer in range, will not add")
775  << newRemoteAircraft.getCallsign();
776  return false;
777  }
778 
779  if (this->canAddAircraft())
780  {
781  // no aircraft pending, add
782  this->logAddingAircraftModel(newRemoteAircraft);
783  const qint64 now = QDateTime::currentMSecsSinceEpoch();
784  m_addingInProgressAircraft.insert(newRemoteAircraft.getCallsign(), now);
785  const QString callsign = newRemoteAircraft.getCallsign().asString();
786  CAircraftModel aircraftModel = newRemoteAircraft.getModel();
787 
788  // some more validation
789  if (aircraftModel.getCallsign() != newRemoteAircraft.getCallsign())
790  {
791  CLogMessage(this).warning(u"Model for '%1' has no callsign, maybe using a default model") << callsign;
792  aircraftModel.setCallsign(callsign); // fix callsign to avoid follow up issues
793 
794  // we could disable the aircraft?
795  }
796 
797  const QString livery = aircraftModel.getLivery().getCombinedCode();
798  m_trafficProxy->addPlane(callsign, aircraftModel.getModelString(),
799  newRemoteAircraft.getAircraftIcaoCode().getDesignator(),
800  newRemoteAircraft.getAirlineIcaoCode().getDesignator(), livery);
801  PlanesPositions pos;
802  pos.push_back(newRemoteAircraft.getSituation());
803  m_trafficProxy->setPlanesPositions(pos);
804 
805  PlanesSurfaces surfaces;
806  surfaces.push_back(newRemoteAircraft.getCallsign(), newRemoteAircraft.getParts());
807  m_trafficProxy->setPlanesSurfaces(surfaces);
808  }
809  else
810  {
811  // add in queue
812  m_pendingToBeAddedAircraft.replaceOrAdd(newRemoteAircraft);
813  }
814  return true;
815  }
816 
818  {
819  // avoid issue in rapid shutdown
820  if (this->isShuttingDownOrDisconnected()) { return false; }
821 
822  // only remove from sim
823  Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "wrong thread");
824  if (callsign.isEmpty()) { return false; } // can happen if an object is not an aircraft
825 
826  // really remove from simulator
827  if (!this->isTestMode() && !m_xplaneAircraftObjects.contains(callsign) &&
828  !m_pendingToBeAddedAircraft.containsCallsign(callsign) && !m_addingInProgressAircraft.contains(callsign))
829  {
830  // not existing aircraft
831  return false;
832  }
833 
834  // mark in provider
835  const bool updated = this->updateAircraftRendered(callsign, false);
836  if (updated)
837  {
838  if (m_xplaneAircraftObjects.contains(callsign))
839  {
840  const CXPlaneMPAircraft &xplaneAircraft = m_xplaneAircraftObjects[callsign];
841  CSimulatedAircraft aircraft(xplaneAircraft.getAircraft());
842  aircraft.setRendered(false);
843  emit this->aircraftRenderingChanged(aircraft);
844  }
845  else if (m_pendingToBeAddedAircraft.containsCallsign(callsign))
846  {
847  CSimulatedAircraft aircraft = m_pendingToBeAddedAircraft.findFirstByCallsign(callsign);
848  aircraft.setRendered(false);
849  emit this->aircraftRenderingChanged(aircraft);
850  }
851  }
852 
853  // if adding in progress, postpone
854  if (m_addingInProgressAircraft.contains(callsign))
855  {
856  // we are just about to add that aircraft
857  QPointer<CSimulatorXPlane> myself(this);
858  QTimer::singleShot(TimeoutAdding, this, [=] {
859  if (!myself) { return; }
860  m_addingInProgressAircraft.remove(callsign); // remove as "in progress"
861  this->physicallyRemoveRemoteAircraft(callsign); // and remove from sim. if it was added in the mean time
862  });
863  return false; // do nothing right now
864  }
865 
866  m_trafficProxy->removePlane(callsign.asString());
867  m_xplaneAircraftObjects.remove(callsign);
868  m_pendingToBeAddedAircraft.removeByCallsign(callsign);
869 
870  // bye
871  return CSimulatorPluginCommon::physicallyRemoveRemoteAircraft(callsign);
872  }
873 
875  {
876  if (this->isShuttingDownOrDisconnected()) { return 0; }
877  m_pendingToBeAddedAircraft.clear();
878  m_addingInProgressAircraft.clear();
879  return CSimulatorPluginCommon::physicallyRemoveAllRemoteAircraft();
880  }
881 
883  {
885  return this->getAircraftInRange().findByRendered(true).getCallsigns(); // just a poor workaround
886  }
887 
889  {
890  if (!m_trafficProxy || !m_trafficProxy->isValid()) { return false; }
891  m_trafficProxy->setFollowedAircraft(callsign.toQString());
892  return true;
893  }
894 
895  void CSimulatorXPlane::updateRemoteAircraft()
896  {
897  Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "thread");
898 
899  const int remoteAircraftNo = this->getAircraftInRangeCount();
900  if (remoteAircraftNo < 1) { return; }
901 
902  // values used for position and parts
904  const qint64 currentTimestamp = QDateTime::currentMSecsSinceEpoch();
905 
906  // interpolation for all remote aircraft
907  PlanesPositions planesPositions;
908  PlanesSurfaces planesSurfaces;
909  PlanesTransponders planesTransponders;
910 
911  uint32_t aircraftNumber = 0;
912  const bool updateAllAircraft = this->isUpdateAllRemoteAircraft(currentTimestamp);
913  const CCallsignSet callsignsInRange = this->getAircraftInRangeCallsigns();
914  for (const CXPlaneMPAircraft &xplaneAircraft : std::as_const(m_xplaneAircraftObjects))
915  {
916  const CCallsign callsign(xplaneAircraft.getCallsign());
917  const bool hasCallsign = !callsign.isEmpty();
918  if (!hasCallsign)
919  {
920  // does not make sense to continue here
921  SWIFT_VERIFY_X(false, Q_FUNC_INFO, "missing callsign");
922  continue;
923  }
924 
925  // skip no longer in range
926  if (!callsignsInRange.contains(callsign)) { continue; }
927 
928  planesTransponders.callsigns.push_back(callsign.asString());
929  planesTransponders.codes.push_back(xplaneAircraft.getAircraft().getTransponderCode());
930  CTransponder::TransponderMode transponderMode = xplaneAircraft.getAircraft().getTransponderMode();
931  planesTransponders.idents.push_back(transponderMode == CTransponder::StateIdent);
932  planesTransponders.modeCs.push_back(transponderMode == CTransponder::ModeC);
933 
934  // setup
936  this->getInterpolationSetupConsolidated(callsign, updateAllAircraft);
937 
938  // interpolated situation/parts
939  const CInterpolationResult result =
940  xplaneAircraft.getInterpolation(currentTimestamp, setup, aircraftNumber++);
942  {
943  CAircraftSituation interpolatedSituation(result);
944 
945  // adjust altitude to compensate for XP12 temperature effect
946  const CLength relativeAltitude =
947  interpolatedSituation.geodeticHeight() - getOwnAircraftPosition().geodeticHeight();
948  const double altitudeDeltaWeight =
949  2 - qBound(3000.0, relativeAltitude.abs().value(CLengthUnit::ft()), 6000.0) / 3000;
950  const CLength alt = interpolatedSituation.getAltitude() +
951  m_altitudeDelta * altitudeDeltaWeight *
952  (1 - interpolatedSituation.getOnGroundInfo().getGroundFactor());
953  interpolatedSituation.setAltitude({ alt, interpolatedSituation.getAltitude().getReferenceDatum() });
954 
955  // update situation
956  if (updateAllAircraft || !this->isEqualLastSent(interpolatedSituation))
957  {
958  this->rememberLastSent(interpolatedSituation);
959  planesPositions.push_back(interpolatedSituation);
960  }
961  }
962  else
963  {
964  CLogMessage(this).warning(
965  this->getInvalidSituationLogMessage(callsign, result.getInterpolationStatus()));
966  }
967 
968  const CAircraftParts parts(result);
969  if (result.getPartsStatus().isSupportingParts() || parts.getPartsDetails() == CAircraftParts::GuessedParts)
970  {
971  if (updateAllAircraft || !this->isEqualLastSent(parts, callsign))
972  {
973  this->rememberLastSent(parts, callsign);
974  planesSurfaces.push_back(xplaneAircraft.getCallsign(), parts);
975  }
976  }
977 
978  } // all callsigns
979 
980  if (!planesTransponders.isEmpty()) { m_trafficProxy->setPlanesTransponders(planesTransponders); }
981 
982  if (!planesPositions.isEmpty())
983  {
984  if (CBuildConfig::isLocalDeveloperDebugBuild())
985  {
986  SWIFT_VERIFY_X(planesPositions.hasSameSizes(), Q_FUNC_INFO, "Mismatching sizes");
987  }
988  m_trafficProxy->setPlanesPositions(planesPositions);
989  }
990 
991  if (!planesSurfaces.isEmpty()) { m_trafficProxy->setPlanesSurfaces(planesSurfaces); }
992 
993  // stats
994  this->finishUpdateRemoteAircraftAndSetStatistics(currentTimestamp);
995  }
996 
997  void CSimulatorXPlane::requestRemoteAircraftDataFromXPlane()
998  {
999  if (this->isShuttingDownOrDisconnected()) { return; }
1000 
1001  // It is not required to request all elevations and CGs, but only for aircraft "near ground relevant"
1002  // - we could use the elevation cache and CG cache to decide if we need to request
1003  // - if an aircraft is on ground but not moving, we do not need to request elevation if we already have it (it
1004  // will not change
1005  CCallsignSet callsigns = m_xplaneAircraftObjects.getAllCallsigns();
1007  callsigns.remove(remove);
1008  if (!callsigns.isEmpty()) { this->requestRemoteAircraftDataFromXPlane(callsigns); }
1009  }
1010 
1011  void CSimulatorXPlane::requestRemoteAircraftDataFromXPlane(const CCallsignSet &callsigns)
1012  {
1013  if (callsigns.isEmpty()) { return; }
1014  if (!m_trafficProxy || this->isShuttingDown()) { return; }
1015  const QStringList csStrings = callsigns.getCallsignStrings();
1016  QPointer<CSimulatorXPlane> myself(this);
1017  m_trafficProxy->getRemoteAircraftData(
1018  csStrings, [=](const QStringList &callsigns, const QDoubleList &latitudesDeg,
1019  const QDoubleList &longitudesDeg, const QDoubleList &elevationsMeters,
1020  const QBoolList &waterFlags, const QDoubleList &verticalOffsetsMeters) {
1021  if (!myself) { return; }
1022  this->updateRemoteAircraftFromSimulator(callsigns, latitudesDeg, longitudesDeg, elevationsMeters,
1023  waterFlags, verticalOffsetsMeters);
1024  });
1025  }
1026 
1027  void CSimulatorXPlane::triggerRequestRemoteAircraftDataFromXPlane(const CCallsignSet &callsigns)
1028  {
1029  if (callsigns.isEmpty()) { return; }
1030  QPointer<CSimulatorXPlane> myself(this);
1031  QTimer::singleShot(0, this, [=] {
1032  if (!myself) { return; }
1033  this->requestRemoteAircraftDataFromXPlane(callsigns);
1034  });
1035  }
1036 
1037  void CSimulatorXPlane::updateRemoteAircraftFromSimulator(
1038  const QStringList &callsigns, const QDoubleList &latitudesDeg, const QDoubleList &longitudesDeg,
1039  const QDoubleList &elevationsMeters, const QBoolList &waterFlags, const QDoubleList &verticalOffsetsMeters)
1040  {
1041  const int size = callsigns.size();
1042 
1043  // we skip if we are not near ground
1044  if (CBuildConfig::isLocalDeveloperDebugBuild())
1045  {
1046  Q_ASSERT_X(elevationsMeters.size() == size, Q_FUNC_INFO, "Wrong elevations");
1047  Q_ASSERT_X(waterFlags.size() == size, Q_FUNC_INFO, "Wrong waterFlags");
1048  Q_ASSERT_X(latitudesDeg.size() == size, Q_FUNC_INFO, "Wrong latitudesDeg");
1049  Q_ASSERT_X(longitudesDeg.size() == size, Q_FUNC_INFO, "Wrong longitudesDeg");
1050  Q_ASSERT_X(verticalOffsetsMeters.size() == size, Q_FUNC_INFO, "Wrong CG");
1051  }
1052 
1053  const CCallsignSet logCallsigns = this->getLogCallsigns();
1054  static const QString hint("remote acft.");
1055  for (int i = 0; i < size; i++)
1056  {
1057  const bool emptyCs = callsigns[i].isEmpty();
1058  SWIFT_VERIFY_X(!emptyCs, Q_FUNC_INFO, "Need callsign");
1059  if (emptyCs) { continue; }
1060 
1061  const CCallsign cs(callsigns[i]);
1062  if (!m_xplaneAircraftObjects.contains(cs)) { continue; }
1063  const CXPlaneMPAircraft xpAircraft = m_xplaneAircraftObjects[cs];
1064  SWIFT_VERIFY_X(xpAircraft.hasCallsign(), Q_FUNC_INFO, "Need callsign");
1065  if (!xpAircraft.hasCallsign()) { continue; }
1066 
1067  CElevationPlane elevation = CElevationPlane::null();
1068  if (!std::isnan(elevationsMeters[i]))
1069  {
1070  const CAltitude elevationAlt = CAltitude(elevationsMeters[i], CLengthUnit::m(), CLengthUnit::ft());
1071  elevation = CElevationPlane(CLatitude(latitudesDeg[i], CAngleUnit::deg()),
1072  CLongitude(longitudesDeg[i], CAngleUnit::deg()), elevationAlt,
1073  CElevationPlane::singlePointRadius());
1074  }
1075 
1076  const double cgValue = verticalOffsetsMeters[i]; // XP offset is swift CG
1077  CLength cg = std::isnan(cgValue) ? CLength::null() : CLength(cgValue, CLengthUnit::m(), CLengthUnit::ft());
1078  cg = fixSimulatorCg(cg, xpAircraft.getAircraftModel());
1079 
1080  // if we knew "on ground" here we could set it as parameter of rememberElevationAndSimulatorCG
1081  // with T778 we do NOT use this function for elevation here if "isSuspicious"
1082  const bool waterFlag = waterFlags[i];
1083  const bool useElevation = this->handleProbeValue(elevation, cs, waterFlag, hint, true);
1084  this->rememberElevationAndSimulatorCG(cs, xpAircraft.getAircraftModel(), false,
1085  useElevation ? elevation : CElevationPlane::null(), cg);
1086 
1087  // loopback
1088  if (logCallsigns.contains(cs)) { this->addLoopbackSituation(cs, elevation, cg); }
1089  }
1090  }
1091 
1092  CLength CSimulatorXPlane::fixSimulatorCg(const CLength &cg, const CAircraftModel &model)
1093  {
1095  // For XCSL null means no offset => 0
1096  // so not to override it with some DB value in X mode, we set it to "0"
1097  if (model.getDistributor().matchesKeyOrAlias(CDistributor::xplaneXcsl()) && cg.isNull())
1098  {
1099  return { 0.0, CLengthUnit::ft() };
1100  }
1101  return cg;
1102  }
1103 
1104  void CSimulatorXPlane::disconnectFromDBus()
1105  {
1106  if (m_dBusConnection.isConnected())
1107  {
1108  if (m_trafficProxy) { m_trafficProxy->cleanup(); }
1109 
1110  if (m_dbusMode == P2P) { QDBusConnection::disconnectFromPeer(m_dBusConnection.name()); }
1111  else { QDBusConnection::disconnectFromBus(m_dBusConnection.name()); }
1112  }
1113  m_dBusConnection = QDBusConnection { "default" };
1114  }
1115 
1116  bool CSimulatorXPlane::sendXSwiftBusSettings()
1117  {
1118  if (this->isShuttingDownOrDisconnected()) { return false; }
1119  if (!m_serviceProxy) { return false; }
1120  CXSwiftBusSettings s = m_xSwiftBusServerSettings.get();
1121  s.setCurrentUtcTime();
1122  m_serviceProxy->setSettingsJson(s.toXSwiftBusJsonStringQt());
1123  CLogMessage(this).info(u"Send settings: %1") << s.toQString(true);
1124  return true;
1125  }
1126 
1127  CXSwiftBusSettings CSimulatorXPlane::receiveXSwiftBusSettings(bool &ok)
1128  {
1129  ok = false;
1131 
1132  if (this->isShuttingDownOrDisconnected()) { return s; }
1133  if (!m_serviceProxy) { return s; }
1134 
1135  const QString json = m_serviceProxy->getSettingsJson();
1136  s.parseXSwiftBusStringQt(json);
1137  ok = true;
1138  return s;
1139  }
1140 
1141  void CSimulatorXPlane::onXSwiftBusSettingsChanged()
1142  {
1143  bool ok {};
1144  const CXSwiftBusSettings xPlaneSide = this->receiveXSwiftBusSettings(ok);
1145  if (ok)
1146  {
1147  // we only send if DBus did not change
1148  // DBus changes would require a restart
1149  const CXSwiftBusSettings swiftSide = m_xSwiftBusServerSettings.get();
1150  if (xPlaneSide.getDBusServerAddressQt() == swiftSide.getDBusServerAddressQt())
1151  {
1152  this->sendXSwiftBusSettings();
1153  }
1154  }
1155  }
1156 
1157  void CSimulatorXPlane::setMinTerrainProbeDistance(const CLength &distance)
1158  {
1159  if (distance.isNull()) { return; }
1160  if (m_minSuspicousTerrainProbe.isNull() || distance < m_minSuspicousTerrainProbe)
1161  {
1162  m_minSuspicousTerrainProbe = distance;
1163  }
1164  }
1165 
1166  void CSimulatorXPlane::onRemoteAircraftAdded(const QString &callsign)
1167  {
1168  SWIFT_VERIFY_X(!callsign.isEmpty(), Q_FUNC_INFO, "Need callsign");
1169  if (callsign.isEmpty()) { return; }
1170  const CCallsign cs(callsign);
1171  CSimulatedAircraft addedRemoteAircraft = this->getAircraftInRangeForCallsign(cs);
1172 
1173  // statistics
1174  bool wasPending = false;
1175  if (m_addingInProgressAircraft.contains(cs))
1176  {
1177  wasPending = true;
1178  const qint64 wasStartedMs = m_addingInProgressAircraft.value(cs);
1179  const qint64 deltaTimeMs = QDateTime::currentMSecsSinceEpoch() - wasStartedMs;
1180  m_statsAddCurrentTimeMs = deltaTimeMs;
1181  m_statsAddMaxTimeMs = std::max(deltaTimeMs, m_statsAddMaxTimeMs);
1182  m_addingInProgressAircraft.remove(cs);
1183  }
1184 
1185  if (!addedRemoteAircraft.hasCallsign())
1186  {
1187  CLogMessage(this).warning(u"Aircraft '%1' no longer in range, will be removed") << callsign;
1188  this->triggerRemoveAircraft(cs, TimeoutAdding);
1189  return;
1190  }
1191 
1192  CLogMessage(this).info(u"Added aircraft '%1'") << callsign;
1193  if (!wasPending)
1194  {
1195  // timeout?
1196  // slow adding?
1197  CLogMessage(this).warning(u"Added callsign '%1' was not in progress anymore. Timeout?") << callsign;
1198  }
1199 
1200  const bool rendered = true;
1201  addedRemoteAircraft.setRendered(rendered);
1202  this->updateAircraftRendered(cs, rendered);
1203  this->triggerRequestRemoteAircraftDataFromXPlane(cs);
1204  this->triggerAddNextPendingAircraft();
1205 
1206  Q_ASSERT_X(addedRemoteAircraft.hasCallsign(), Q_FUNC_INFO,
1207  "No callsign"); // already checked above, MUST never happen
1208  Q_ASSERT_X(addedRemoteAircraft.getCallsign() == cs, Q_FUNC_INFO,
1209  "No callsign"); // already checked above, MUST never happen
1210  m_xplaneAircraftObjects.insert(cs, CXPlaneMPAircraft(addedRemoteAircraft, this, &m_interpolationLogger));
1211  emit this->aircraftRenderingChanged(addedRemoteAircraft);
1212  }
1213 
1214  void CSimulatorXPlane::onRemoteAircraftAddingFailed(const QString &callsign)
1215  {
1216  SWIFT_VERIFY_X(!callsign.isEmpty(), Q_FUNC_INFO, "Need callsign");
1217  if (callsign.isEmpty()) { return; }
1218  const CCallsign cs(callsign);
1219  CSimulatedAircraft failedRemoteAircraft = this->getAircraftInRangeForCallsign(cs);
1220 
1221  if (failedRemoteAircraft.hasCallsign())
1222  {
1223  CLogMessage(this).warning(u"Adding aircraft failed: '%1'") << callsign;
1224  failedRemoteAircraft.setRendered(false);
1225  }
1226  else
1227  {
1228  CLogMessage(this).warning(u"Adding '%1' failed, but aircraft no longer in range, will be removed")
1229  << callsign;
1230  }
1231 
1232  const bool wasPending = (static_cast<int>(m_addingInProgressAircraft.remove(cs)) > 0);
1233  Q_UNUSED(wasPending)
1234 
1235  if (failedRemoteAircraft.hasCallsign() && !m_aircraftAddedFailed.containsCallsign(cs))
1236  {
1237  m_aircraftAddedFailed.push_back(failedRemoteAircraft);
1238  m_pendingToBeAddedAircraft.replaceOrAdd(failedRemoteAircraft); // try a second time
1239  }
1240  this->triggerAddNextPendingAircraft();
1241  }
1242 
1243  void CSimulatorXPlane::addNextPendingAircraft()
1244  {
1245  if (m_pendingToBeAddedAircraft.isEmpty()) { return; } // no more pending
1246 
1247  // housekeeping
1248  this->detectTimeoutAdding();
1249 
1250  // check if can add
1251  if (!this->canAddAircraft()) { return; }
1252 
1253  // next add cycle
1254  const CSimulatedAircraft newRemoteAircraft = m_pendingToBeAddedAircraft.front();
1255  m_pendingToBeAddedAircraft.pop_front();
1256  CLogMessage(this).info(u"Adding next pending aircraft '%1', pending %2, in progress %3")
1257  << newRemoteAircraft.getCallsignAsString() << m_pendingToBeAddedAircraft.size()
1258  << m_addingInProgressAircraft.size();
1259  this->physicallyAddRemoteAircraft(newRemoteAircraft);
1260  }
1261 
1262  void CSimulatorXPlane::triggerAddNextPendingAircraft()
1263  {
1264  QPointer<CSimulatorXPlane> myself(this);
1265  QTimer::singleShot(100, this, [=] {
1266  if (!myself || !sApp || sApp->isShuttingDown()) { return; }
1267  this->addNextPendingAircraft();
1268  });
1269  }
1270 
1271  int CSimulatorXPlane::detectTimeoutAdding()
1272  {
1273  if (m_addingInProgressAircraft.isEmpty()) { return 0; }
1274  const qint64 timeout = QDateTime::currentMSecsSinceEpoch() + TimeoutAdding;
1275  CCallsignSet timeoutCallsigns;
1276  const QList<CCallsign> addingCallsigns = m_addingInProgressAircraft.keys();
1277  for (const CCallsign &cs : addingCallsigns)
1278  {
1279  if (m_addingInProgressAircraft.value(cs) < timeout) { continue; }
1280  timeoutCallsigns.push_back(cs);
1281  }
1282 
1283  for (const CCallsign &cs : std::as_const(timeoutCallsigns))
1284  {
1285  m_addingInProgressAircraft.remove(cs);
1286  CLogMessage(this).warning(u"Adding for '%1' timed out") << cs.asString();
1287  }
1288 
1289  return timeoutCallsigns.size();
1290  }
1291 
1292  void CSimulatorXPlane::triggerRemoveAircraft(const CCallsign &callsign, qint64 deferMs)
1293  {
1294  QPointer<CSimulatorXPlane> myself(this);
1295  QTimer::singleShot(deferMs, this, [=] {
1296  if (!myself) { return; }
1297  this->physicallyRemoveRemoteAircraft(callsign);
1298  });
1299  }
1300 
1301  QPair<qint64, qint64> CSimulatorXPlane::minMaxTimestampsAddInProgress() const
1302  {
1303  static const QPair<qint64, qint64> empty(-1, -1);
1304  if (m_addingInProgressAircraft.isEmpty()) { return empty; }
1305  const QList<qint64> ts = m_addingInProgressAircraft.values();
1306  const auto mm = std::minmax_element(ts.constBegin(), ts.constEnd());
1307  return { *mm.first, *mm.second };
1308  }
1309 
1310  bool CSimulatorXPlane::canAddAircraft() const
1311  {
1312  if (m_addingInProgressAircraft.isEmpty()) { return true; }
1313 
1314  // check
1315  const qint64 now = QDateTime::currentMSecsSinceEpoch();
1316  const QPair<qint64, qint64> tsMM = this->minMaxTimestampsAddInProgress();
1317  const qint64 deltaLatest = now - tsMM.second;
1318  const bool canAdd = (deltaLatest > TimeoutAdding);
1319  return canAdd;
1320  }
1321 
1323  IOwnAircraftProvider *ownAircraftProvider,
1324  IRemoteAircraftProvider *remoteAircraftProvider,
1325  IClientProvider *clientProvider)
1326  {
1327  return new CSimulatorXPlane(info, ownAircraftProvider, remoteAircraftProvider, clientProvider, this);
1328  }
1329 
1331  {
1332  constexpr int QueryInterval = 5 * 1000; // 5 seconds
1333  m_timer.setInterval(QueryInterval);
1334  m_timer.setObjectName(this->objectName().append(":m_timer"));
1335  connect(&m_timer, &QTimer::timeout, this, &CSimulatorXPlaneListener::checkConnection);
1336  }
1337 
1339 
1341 
1343  {
1344  if (!m_timer.isActive()) { return; }
1345  if (this->isShuttingDown()) { return; }
1346 
1347  m_timer.start(); // restart because we will check just now
1349  QTimer::singleShot(0, this, [=] {
1350  if (!myself) { return; }
1351  checkConnection();
1352  });
1353  }
1354 
1355  void CSimulatorXPlaneListener::checkConnection()
1356  {
1357  if (this->isShuttingDown()) { return; }
1358  Q_ASSERT_X(!CThreadUtils::thisIsMainThread(), Q_FUNC_INFO, "Expect to run in background");
1359  QElapsedTimer t;
1360  t.start();
1361 
1362  QString via;
1363  m_dBusServerAddress = m_xSwiftBusServerSettings.getThreadLocal().getDBusServerAddressQt();
1364  if (CDBusServer::isSessionOrSystemAddress(m_dBusServerAddress))
1365  {
1366  checkConnectionViaSessionBus();
1367  via = "SessionBus";
1368  }
1369  else if (CDBusServer::isQtDBusAddress(m_dBusServerAddress))
1370  {
1371  checkConnectionViaPeer(m_dBusServerAddress);
1372  via = "P2P";
1373  }
1374 
1375  CLogMessage(this).debug(u"Checked sim. 'XPLANE' via '%1' connection in %2ms") << via << t.elapsed();
1376  }
1377 
1378  void CSimulatorXPlaneListener::checkConnectionViaSessionBus()
1379  {
1380  m_DBusConnection = QDBusConnection::sessionBus();
1381  if (!m_DBusConnection.isConnected())
1382  {
1383  QDBusConnection::disconnectFromBus(m_DBusConnection.name());
1384  return;
1385  }
1386  checkConnectionCommon(); // bus
1387  QDBusConnection::disconnectFromBus(m_DBusConnection.name());
1388  }
1389 
1390  void CSimulatorXPlaneListener::checkConnectionViaPeer(const QString &address)
1391  {
1392  m_DBusConnection = QDBusConnection::connectToPeer(address, "xswiftbus");
1393  if (!m_DBusConnection.isConnected())
1394  {
1395  // This is required to cleanup the connection in QtDBus
1396  QDBusConnection::disconnectFromPeer(m_DBusConnection.name());
1397  return;
1398  }
1399  checkConnectionCommon(); // peer
1400  QDBusConnection::disconnectFromPeer(m_DBusConnection.name());
1401  }
1402 
1403  void CSimulatorXPlaneListener::checkConnectionCommon()
1404  {
1405  CXSwiftBusServiceProxy service(m_DBusConnection);
1406  CXSwiftBusTrafficProxy traffic(m_DBusConnection);
1407 
1408  const bool result = service.isValid() && traffic.isValid();
1409  if (!result) { return; }
1410 
1411  const QString swiftVersion = CBuildConfig::getVersionString();
1412  const QString xswiftbusVersion = service.getVersionNumber();
1413  const QString xswiftbusCommitHash = service.getCommitHash();
1414  if (xswiftbusVersion.isEmpty())
1415  {
1416  CLogMessage(this).warning(u"Could not determine which version of xswiftbus is running. Mismatched versions "
1417  u"might cause instability.");
1418  }
1419  else if (commitHash() != xswiftbusCommitHash)
1420  {
1421  CLogMessage(this).error(u"You are using an incorrect version of xswiftbus. The version of xswiftbus (%1) "
1422  u"should match the version of swift (%2). Consider upgrading!")
1423  << xswiftbusVersion << swiftVersion;
1424  }
1425 
1426  if (!traffic.initialize())
1427  {
1428  CLogMessage(this).error(
1429  u"Connection to xswiftbus successful, but could not initialize xswiftbus. Check X-Plane Log.txt.");
1430  return;
1431  }
1432 
1433  const MultiplayerAcquireInfo info = traffic.acquireMultiplayerPlanes();
1434  if (!info.hasAcquired)
1435  {
1436  const QString owner =
1437  info.owner.trimmed().isEmpty() ? QStringLiteral("unknown plugin") : info.owner.trimmed();
1438  CLogMessage(this).error(
1439  u"Connection to xswiftbus successful, but could not acquire multiplayer planes. '%1' has acquired them "
1440  u"already. Disable '%2' or remove it if not required and reload xswiftbus.")
1441  << owner << owner.toLower();
1442  return;
1443  }
1444 
1446  }
1447 
1448  void CSimulatorXPlaneListener::serviceRegistered(const QString &serviceName)
1449  {
1450  if (serviceName == xswiftbusServiceName()) { emit simulatorStarted(getPluginInfo()); }
1451  QDBusConnection::disconnectFromBus(m_DBusConnection.name());
1452  }
1453 
1454  void CSimulatorXPlaneListener::onXSwiftBusServerSettingChanged()
1455  {
1456  const CXSwiftBusSettings s = m_xSwiftBusServerSettings.get();
1457  if (m_dBusServerAddress != s.getDBusServerAddressQt())
1458  {
1459  this->stop();
1460  this->start();
1461  m_dBusServerAddress = s.getDBusServerAddressQt();
1462  }
1463  }
1464 } // 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(const Key &key, const 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