swift
fsuipcimpl.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2017 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
4 #if defined(SWIFT_USING_FSUIPC32) || defined(SWIFT_USING_FSUIPC64)
5 
6 # ifndef NOMINMAX
7 # define NOMINMAX
8 # endif
9 
10 # include <Windows.h>
11 
12 # include "fsuipc.h"
13 // bug in FSUIPC_User.h, windows.h not included, so we have to import it first
14 
15 # ifdef SWIFT_USING_FSUIPC32
16 extern "C"
17 {
18 # include "../fsuipc32/FSUIPC_User.h"
19 # include "../fsuipc32/IPCuser.h"
20 # include "../fsuipc32/NewWeather.h"
21 }
22 # elif SWIFT_USING_FSUIPC64
23 extern "C"
24 {
25 # include "../fsuipc64/FSUIPC_User64.h"
26 # include "../fsuipc64/IPCuser64.h"
27 # include "../fsuipc64/NewWeather.h"
28 }
29 # endif
30 
31 # include <QDateTime>
32 # include <QDebug>
33 # include <QLatin1Char>
34 
35 # include "misc/logmessage.h"
37 # include "misc/threadutils.h"
38 
39 using namespace swift::misc;
40 using namespace swift::misc::simulation::fscommon;
41 using namespace swift::misc::aviation;
42 using namespace swift::misc::network;
43 using namespace swift::misc::geo;
44 using namespace swift::misc::simulation;
45 using namespace swift::misc::physical_quantities;
46 
47 namespace swift::simplugin::fscommon
48 {
49  CFsuipc::CFsuipc(QObject *parent) : QObject(parent) { startTimer(100); }
50 
51  CFsuipc::~CFsuipc() { this->close(); }
52 
53  bool CFsuipc::open(bool force)
54  {
55  Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "Open not threadsafe");
56  DWORD dwResult;
57  m_lastErrorMessage = "";
58  m_lastErrorIndex = 0;
59  if (!force && m_opened) { return m_opened; } // already connected
60 
61  if (FSUIPC_Open(SIM_ANY, &dwResult))
62  {
63  m_opened = true; // temp status
64  m_openCount++;
65 
66  if (this->isOpen())
67  {
68  const int simIndex = static_cast<int>(FSUIPC_FS_Version);
69  const QString sim = CFsuipc::simulator(simIndex);
70  const QString ver =
71  QStringLiteral("%1.%2.%3.%4%5")
72  .arg(QLatin1Char(48 + (0x0f & (FSUIPC_Version >> 28))))
73  .arg(QLatin1Char(48 + (0x0f & (FSUIPC_Version >> 24))))
74  .arg(QLatin1Char(48 + (0x0f & (FSUIPC_Version >> 20))))
75  .arg(QLatin1Char(48 + (0x0f & (FSUIPC_Version >> 16))))
76  .arg((FSUIPC_Version & 0xffff) ?
77  QString(QLatin1Char('a' + static_cast<char>(FSUIPC_Version & 0xff) - 1)) :
78  "");
79  m_fsuipcVersion = QStringLiteral("FSUIPC %1 (%2)").arg(ver, sim);
80  CLogMessage(this).info(u"FSUIPC connected: %1") << m_fsuipcVersion;
81  }
82  else
83  {
84  CLogMessage(this).warning(u"FSUIPC opened, but verification failed");
85  m_opened = false;
86  FSUIPC_Close(); // under any circumstances close
87  }
88  }
89  else
90  {
91  const int index = static_cast<int>(dwResult);
92  m_lastErrorIndex = index;
93  m_lastErrorMessage = CFsuipc::errorMessages().at(index);
94  CLogMessage(this).warning(u"FSUIPC not connected: %1") << m_lastErrorMessage;
95  m_opened = false;
96  FSUIPC_Close(); // under any circumstances close
97  }
98 
99  return m_opened;
100  }
101 
102  void CFsuipc::close()
103  {
104  Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "Open not threadsafe");
105  if (m_opened) { CLogMessage(this).info(u"Closing FSUIPC: %1") << m_fsuipcVersion; }
106  FSUIPC_Close(); // Closing when it wasn't open is okay, so this is safe here
107  m_closeCount++;
108  m_opened = false;
109  }
110 
111  bool CFsuipc::isOpened() const { return m_opened; }
112 
113  bool CFsuipc::isOpen() const
114  {
115  if (!this->isOpened()) { return false; }
116 
117  // test read
118  DWORD dwResult;
119  char localFsTimeRaw[3];
120  if (FSUIPC_Read(0x0238, 3, localFsTimeRaw, &dwResult) && FSUIPC_Process(&dwResult)) { return dwResult == 0; }
121  return false;
122  }
123 
124  bool CFsuipc::write(const CTransponder &xpdr)
125  {
126  Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "Open not threadsafe");
127  if (!this->isOpened()) { return false; }
128 
129  // should be the same as writing via SimConnect data area
130  quint16 transponderCodeRaw = static_cast<quint16>(xpdr.getTransponderCode());
131  transponderCodeRaw = static_cast<quint16>(CBcdConversions::dec2Bcd(transponderCodeRaw));
132  DWORD dwResult;
133  byte xpdrModeSb3Raw = xpdr.isInStandby() ? 1U : 0U;
134  byte xpdrIdentSb3Raw = xpdr.isIdentifying() ? 1U : 0U;
135  const bool ok = FSUIPC_Write(0x7b91, 1, &xpdrModeSb3Raw, &dwResult) &&
136  FSUIPC_Write(0x7b93, 1, &xpdrIdentSb3Raw, &dwResult) &&
137  FSUIPC_Write(0x0354, 2, &transponderCodeRaw, &dwResult);
138  if (ok) { FSUIPC_Process(&dwResult); }
139  return ok && dwResult == 0;
140  }
141 
142  bool CFsuipc::setSimulatorTime(int hour, int minute)
143  {
144  Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "Open not threadsafe");
145  if (!this->isOpened()) { return false; }
146 
147  // should be the same as writing via SimConnect data area
148  DWORD dwResult;
149  quint8 hourRaw = static_cast<quint8>(hour);
150  quint8 minuteRaw = static_cast<quint8>(minute);
151 
152  const bool ok = FSUIPC_Write(0x023b, 1, &hourRaw, &dwResult) && FSUIPC_Write(0x023c, 1, &minuteRaw, &dwResult);
153  if (ok) { FSUIPC_Process(&dwResult); }
154  return ok && dwResult == 0;
155  }
156 
157  QString CFsuipc::getVersion() const { return m_fsuipcVersion; }
158 
159  bool CFsuipc::read(CSimulatedAircraft &aircraft, bool cockpit, bool situation, bool aircraftParts)
160  {
161  Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "Open not threadsafe");
162  DWORD dwResult = 0;
163  char localFsTimeRaw[3];
164  char modelNameRaw[256];
165  qint16 com1ActiveRaw = 0, com2ActiveRaw = 0, com1StandbyRaw = 0, com2StandbyRaw = 0;
166  qint16 transponderCodeRaw = 0;
167  byte xpdrModeSb3Raw = 1, xpdrIdentSb3Raw = 1;
168  qint32 groundspeedRaw = 0, pitchRaw = 0, bankRaw = 0, headingRaw = 0;
169  qint64 altitudeRaw = 0;
170  double pressureAltitudeRaw = 0; // 34B0
171  qint32 groundAltitudeRaw = 0;
172  qint64 latitudeRaw = 0, longitudeRaw = 0;
173  qint16 lightsRaw = 0;
174  qint16 onGroundRaw = 0;
175  qint32 flapsControlRaw = 0, gearControlRaw = 0, spoilersControlRaw = 0;
176  qint16 numberOfEngines = 0;
177  qint16 engine1CombustionFlag = 0, engine2CombustionFlag = 0, engine3CombustionFlag = 0,
178  engine4CombustionFlag = 0;
179  double velocityWorld[3];
180  double rotationVelocityBody[3];
181 
182  // http://www.projectmagenta.com/all-fsuipc-offsets/
183  // https://www.ivao.aero/softdev/ivap/fsuipc_sdk.asp
184  // http://squawkbox.ca/doc/sdk/fsuipc.php
185 
186  if (!this->isOpened()) { return false; }
187  if (!(aircraftParts || situation || cockpit)) { return false; }
188 
189  bool read = false;
190  bool cockpitN = !cockpit;
191  bool situationN = !situation;
192  bool aircraftPartsN = !aircraftParts;
193 
194  if (FSUIPC_Read(0x0238, 3, localFsTimeRaw, &dwResult) &&
195 
196  // COM settings
197  (cockpitN || FSUIPC_Read(0x034e, 2, &com1ActiveRaw, &dwResult)) &&
198  (cockpitN || FSUIPC_Read(0x3118, 2, &com2ActiveRaw, &dwResult)) &&
199  (cockpitN || FSUIPC_Read(0x311a, 2, &com1StandbyRaw, &dwResult)) &&
200  (cockpitN || FSUIPC_Read(0x311c, 2, &com2StandbyRaw, &dwResult)) &&
201  (cockpitN || FSUIPC_Read(0x0354, 2, &transponderCodeRaw, &dwResult)) &&
202 
203  // COM Settings, transponder, SB3
204  (cockpitN || FSUIPC_Read(0x7b91, 1, &xpdrModeSb3Raw, &dwResult)) &&
205  (cockpitN || FSUIPC_Read(0x7b93, 1, &xpdrIdentSb3Raw, &dwResult)) &&
206 
207  // Speeds, situation
208  (situationN || FSUIPC_Read(0x02b4, 4, &groundspeedRaw, &dwResult)) &&
209  (situationN || FSUIPC_Read(0x0578, 4, &pitchRaw, &dwResult)) &&
210  (situationN || FSUIPC_Read(0x057c, 4, &bankRaw, &dwResult)) &&
211  (situationN || FSUIPC_Read(0x0580, 4, &headingRaw, &dwResult)) &&
212  (situationN || FSUIPC_Read(0x0570, 8, &altitudeRaw, &dwResult)) &&
213 
214  (situationN || FSUIPC_Read(0x3198, 8, &velocityWorld[0], &dwResult)) &&
215  (situationN || FSUIPC_Read(0x31a0, 8, &velocityWorld[1], &dwResult)) &&
216  (situationN || FSUIPC_Read(0x3190, 8, &velocityWorld[2], &dwResult)) &&
217 
218  (situationN || FSUIPC_Read(0x30A8, 8, &rotationVelocityBody[0], &dwResult)) &&
219  (situationN || FSUIPC_Read(0x30B0, 8, &rotationVelocityBody[1], &dwResult)) &&
220  (situationN || FSUIPC_Read(0x30B8, 8, &rotationVelocityBody[2], &dwResult)) &&
221 
222  // Position
223  (situationN || FSUIPC_Read(0x0560, 8, &latitudeRaw, &dwResult)) &&
224  (situationN || FSUIPC_Read(0x0568, 8, &longitudeRaw, &dwResult)) &&
225  (situationN || FSUIPC_Read(0x0020, 4, &groundAltitudeRaw, &dwResult)) &&
226  (situationN || FSUIPC_Read(0x34B0, 8, &pressureAltitudeRaw, &dwResult)) &&
227 
228  // model name
229  FSUIPC_Read(0x3d00, 256, &modelNameRaw, &dwResult) &&
230 
231  // aircraft parts
232  (aircraftPartsN || FSUIPC_Read(0x0D0C, 2, &lightsRaw, &dwResult)) &&
233  (aircraftPartsN || FSUIPC_Read(0x0366, 2, &onGroundRaw, &dwResult)) &&
234  (aircraftPartsN || FSUIPC_Read(0x0BDC, 4, &flapsControlRaw, &dwResult)) &&
235  (aircraftPartsN || FSUIPC_Read(0x0BE8, 4, &gearControlRaw, &dwResult)) &&
236  (aircraftPartsN || FSUIPC_Read(0x0BD0, 4, &spoilersControlRaw, &dwResult)) &&
237 
238  // engines
239  (aircraftPartsN || FSUIPC_Read(0x0AEC, 2, &numberOfEngines, &dwResult)) &&
240  (aircraftPartsN || FSUIPC_Read(0x0894, 2, &engine1CombustionFlag, &dwResult)) &&
241  (aircraftPartsN || FSUIPC_Read(0x092C, 2, &engine2CombustionFlag, &dwResult)) &&
242  (aircraftPartsN || FSUIPC_Read(0x09C4, 2, &engine3CombustionFlag, &dwResult)) &&
243  (aircraftPartsN || FSUIPC_Read(0x0A5C, 2, &engine4CombustionFlag, &dwResult)) &&
244 
245  // If we wanted other reads/writes at the same time, we could put them here
246  FSUIPC_Process(&dwResult))
247  {
248  read = true;
249 
250  if (cockpit)
251  {
252  // COMs
253  CComSystem com1 = aircraft.getCom1System();
254  CComSystem com2 = aircraft.getCom2System();
255  CTransponder xpdr = aircraft.getTransponder();
256 
257  // 2710 => 12710 => / 100.0 => 127.1
258  com1ActiveRaw = static_cast<short>(10000 + CBcdConversions::bcd2Dec(com1ActiveRaw));
259  com2ActiveRaw = static_cast<short>(10000 + CBcdConversions::bcd2Dec(com2ActiveRaw));
260  com1StandbyRaw = static_cast<short>(10000 + CBcdConversions::bcd2Dec(com1StandbyRaw));
261  com2StandbyRaw = static_cast<short>(10000 + CBcdConversions::bcd2Dec(com2StandbyRaw));
262  com1.setFrequencyActiveMHz(com1ActiveRaw / 100.0);
263  com2.setFrequencyActiveMHz(com2ActiveRaw / 100.0);
264  com1.setFrequencyStandbyMHz(com1StandbyRaw / 100.0);
265  com2.setFrequencyStandbyMHz(com2StandbyRaw / 100.0);
266 
267  transponderCodeRaw = static_cast<qint16>(CBcdConversions::bcd2Dec(transponderCodeRaw));
268  xpdr.setTransponderCode(transponderCodeRaw);
269 
270  // Mode by SB3
271  xpdr.setTransponderMode(xpdrModeSb3Raw == 0 ? CTransponder::ModeC : CTransponder::StateStandby);
272  if (xpdrIdentSb3Raw != 0)
273  {
274  // will be reset in CFsuipc::write
275  xpdr.setTransponderMode(CTransponder::StateIdent);
276  }
277  aircraft.setCockpit(com1, com2, xpdr);
278  } // cockpit
279 
280  if (situation)
281  {
282  // position
283  const double latCorrectionFactor = 90.0 / (10001750.0 * 65536.0 * 65536.0);
284  const double lonCorrectionFactor = 360.0 / (65536.0 * 65536.0 * 65536.0 * 65536.0);
285  CAircraftSituation situation = aircraft.getSituation(); // cppcheck-suppress shadowArgument
286  CCoordinateGeodetic position = situation.getPosition();
287  CLatitude lat(latitudeRaw * latCorrectionFactor, CAngleUnit::deg());
288  CLongitude lon(longitudeRaw * lonCorrectionFactor, CAngleUnit::deg());
289  CAltitude groundAltitude(groundAltitudeRaw / 256.0, CAltitude::MeanSeaLevel, CLengthUnit::m());
290  position.setLatitude(lat);
291  position.setLongitude(lon);
292  position.setGeodeticHeight(groundAltitude);
293  situation.setPosition(position);
294 
295  const double angleCorrectionFactor = 360.0 / 65536.0 / 65536.0; // see FSUIPC docu
296  pitchRaw = qRound(std::floor(pitchRaw * angleCorrectionFactor));
297  bankRaw = qRound(std::floor(bankRaw * angleCorrectionFactor));
298 
299  // MSFS has inverted pitch and bank angles
300  pitchRaw = ~pitchRaw;
301  bankRaw = ~bankRaw;
302  if (pitchRaw < -90 || pitchRaw > 89)
303  {
304  CLogMessage(this).warning(u"FSUIPC: Pitch value out of limits: %1") << pitchRaw;
305  }
306 
307  // speeds, situation
308  CAngle pitch = CAngle(pitchRaw, CAngleUnit::deg());
309  CAngle bank = CAngle(bankRaw, CAngleUnit::deg());
310  CHeading heading = CHeading(headingRaw * angleCorrectionFactor, CHeading::True, CAngleUnit::deg());
311  CSpeed groundspeed(groundspeedRaw / 65536.0, CSpeedUnit::m_s());
312  CAltitude altitude(altitudeRaw / (65536.0 * 65536.0), CAltitude::MeanSeaLevel, CLengthUnit::m());
313  CAltitude pressureAltitude(pressureAltitudeRaw, CAltitude::MeanSeaLevel, CAltitude::PressureAltitude,
314  CLengthUnit::m());
315  situation.setBank(bank);
316  situation.setHeading(heading);
317  situation.setPitch(pitch);
318  situation.setGroundSpeed(groundspeed);
319  situation.setAltitude(altitude);
320  situation.setPressureAltitude(pressureAltitude);
321  situation.setVelocity({ velocityWorld[0], velocityWorld[1], velocityWorld[2], CSpeedUnit::ft_s(),
322  rotationVelocityBody[0], rotationVelocityBody[1], rotationVelocityBody[2],
323  CAngleUnit::rad(), CTimeUnit::s() });
324  situation.setGroundElevation(groundAltitude, CAircraftSituation::FromProvider);
325  aircraft.setSituation(situation);
326  // aircraft.setCG(altitude - groundAltitude); // calculate the CG
327  } // situation
328 
329  // model
330  const QString modelName = QString(modelNameRaw); // to be used to distinguish offsets for different models
331  aircraft.setModelString(modelName);
332 
333  if (aircraftParts)
334  {
335  const CAircraftLights lights(lightsRaw & (1 << 4), lightsRaw & (1 << 2), lightsRaw & (1 << 3),
336  lightsRaw & (1 << 1), lightsRaw & (1 << 0), lightsRaw & (1 << 8));
337 
338  const QList<bool> helperList { engine1CombustionFlag != 0, engine2CombustionFlag != 0,
339  engine3CombustionFlag != 0, engine4CombustionFlag != 0 };
340 
341  CAircraftEngineList engines;
342  for (int index = 0; index < numberOfEngines; ++index)
343  {
344  engines.push_back(CAircraftEngine(index + 1, helperList.at(index)));
345  }
346 
347  CAircraftParts parts(lights, gearControlRaw == 16383, flapsControlRaw * 100 / 16383,
348  spoilersControlRaw == 16383, engines, onGroundRaw == 1);
349 
350  aircraft.setParts(parts);
351  } // parts
352  } // read
353 
354  const int result = static_cast<int>(dwResult);
355  if (m_lastErrorIndex != result && result > 0)
356  {
357  m_lastErrorIndex = result;
358  m_lastErrorMessage = CFsuipc::errorMessage(result);
359  CLogMessage(this).warning(u"FSUIPC read error '%1'") << m_lastErrorMessage;
360  }
361  return read;
362  }
363 
364  double CFsuipc::intToFractional(double fractional)
365  {
366  const double f = fractional / 10.0;
367  if (f < 1.0) { return f; }
368  return intToFractional(f);
369  }
370 } // namespace swift::simplugin::fscommon
371 
372 #endif // defined(SWIFT_USING_FSUIPC32) || defined(SWIFT_USING_FSUIPC64)
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 & info(const char16_t(&format)[N])
Set the severity to info, providing a format string.
void push_back(const T &value)
Appends an element at the end of the sequence.
Definition: sequence.h:305
Value object encapsulating information about aircraft's engines.
Value object encapsulating a list of aircraft engines.
Value object encapsulating information about aircraft's lights.
Value object encapsulating information of aircraft's parts.
Definition: aircraftparts.h:26
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.
const geo::CCoordinateGeodetic & getPosition() const
Get position.
void setPosition(const geo::CCoordinateGeodetic &position)
Set position.
Altitude as used in aviation, can be AGL or MSL altitude.
Definition: altitude.h:52
COM system (aka "radio")
Definition: comsystem.h:37
void setFrequencyActiveMHz(double frequencyMHz)
Set active frequency.
Definition: comsystem.cpp:25
void setFrequencyStandbyMHz(double frequencyMHz)
Set standby frequency.
Definition: comsystem.cpp:31
Heading as used in aviation, can be true or magnetic heading.
Definition: heading.h:41
bool isIdentifying() const
Standby?
Definition: transponder.h:89
bool isInStandby() const
Standby?
Definition: transponder.h:86
void setTransponderCode(int transponderCode)
Set transponder code.
Definition: transponder.h:116
int getTransponderCode() const
Transponder code.
Definition: transponder.h:107
bool setTransponderMode(TransponderMode mode)
Set transponder mode.
Definition: transponder.cpp:97
void setLatitude(const CLatitude &latitude)
Set latitude.
void setGeodeticHeight(const aviation::CAltitude &height)
Set height (ellipsoidal or geodetic height)
void setLongitude(const CLongitude &longitude)
Set longitude.
Physical unit angle (radians, degrees)
Definition: angle.h:23
Comprehensive information of an aircraft.
const aviation::CAircraftSituation & getSituation() const
Get situation.
void setCockpit(const CSimulatedAircraft &aircraft)
Set COM unit (all values + transponder and SELCAL)
const aviation::CComSystem & getCom2System() const
Get COM2 system.
const aviation::CTransponder & getTransponder() const
Get transponder.
void setModelString(const QString &modelString)
Set model string.
void setSituation(const aviation::CAircraftSituation &situation)
Set situation. Won't overwrite the velocity unless it held the default value.
void setParts(const aviation::CAircraftParts &parts)
Set aircraft parts.
const aviation::CComSystem & getCom1System() const
Get COM1 system.
Free functions in swift::misc.
unsigned long DWORD
Fake Windows DWORD.