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