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