diff --git a/src/plugins/simulator/fsxcommon/fsxcommon.pro b/src/plugins/simulator/fsxcommon/fsxcommon.pro new file mode 100644 index 000000000..e20c6332c --- /dev/null +++ b/src/plugins/simulator/fsxcommon/fsxcommon.pro @@ -0,0 +1,25 @@ +load(common_pre) + +QT += core dbus concurrent xml network widgets + +TARGET = simulatorfsxcommon +TEMPLATE = lib + +CONFIG += staticlib +CONFIG += blackmisc blackcore blackgui + +LIBS += -lsimulatorfscommon -lSimConnect -lFSUIPC_User + +# required for FSUIPC +win32:!win32-g++*: QMAKE_LFLAGS += /NODEFAULTLIB:LIBC.lib + +DEPENDPATH += . $$SourceRoot/src +INCLUDEPATH += . $$SourceRoot/src + +SOURCES += *.cpp +HEADERS += *.h +FORMS += *.ui + +DESTDIR = $$DestRoot/lib + +load(common_post) diff --git a/src/plugins/simulator/fsxcommon/simconnectdatadefinition.cpp b/src/plugins/simulator/fsxcommon/simconnectdatadefinition.cpp new file mode 100644 index 000000000..7a2692e73 --- /dev/null +++ b/src/plugins/simulator/fsxcommon/simconnectdatadefinition.cpp @@ -0,0 +1,223 @@ +/* Copyright (C) 2013 + * swift Project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project, + * including this file, may be copied, modified, propagated, or distributed except according to the terms + * contained in the LICENSE file. + */ + +#include "simconnectdatadefinition.h" +#include "blackmisc/logmessage.h" +#include + +using namespace BlackMisc; +using namespace BlackMisc::Aviation; + +namespace BlackSimPlugin +{ + namespace FsxCommon + { + CSimConnectDefinitions::CSimConnectDefinitions() { } + + HRESULT CSimConnectDefinitions::initDataDefinitionsWhenConnected(const HANDLE hSimConnect) + { + HRESULT hr = S_OK; + hr += initOwnAircraft(hSimConnect); + hr += initRemoteAircraft(hSimConnect); + hr += initRemoteAircraftSimData(hSimConnect); + hr += initSimulatorEnvironment(hSimConnect); + hr += initSbDataArea(hSimConnect); + return hr; + } + + HRESULT CSimConnectDefinitions::initOwnAircraft(const HANDLE hSimConnect) + { + HRESULT hr = S_OK; + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataOwnAircraft, "PLANE LATITUDE", "Degrees"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataOwnAircraft, "PLANE LONGITUDE", "Degrees"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataOwnAircraft, "PLANE ALTITUDE", "Feet"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataOwnAircraft, "PLANE ALT ABOVE GROUND", "Feet"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataOwnAircraft, "STATIC CG TO GROUND", "Feet"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataOwnAircraft, "PLANE HEADING DEGREES TRUE", "Degrees"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataOwnAircraft, "PLANE PITCH DEGREES", "Degrees"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataOwnAircraft, "PLANE BANK DEGREES", "Degrees"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataOwnAircraft, "GROUND VELOCITY", "Knots"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataOwnAircraft, "GROUND ALTITUDE", "Feet"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataOwnAircraft, "SIM ON GROUND", "Bool"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataOwnAircraft, "LIGHT STROBE", "Bool"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataOwnAircraft, "LIGHT LANDING", "Bool"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataOwnAircraft, "LIGHT TAXI", "Bool"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataOwnAircraft, "LIGHT BEACON", "Bool"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataOwnAircraft, "LIGHT NAV", "Bool"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataOwnAircraft, "LIGHT LOGO", "Bool"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataOwnAircraft, "TRANSPONDER CODE:1", NULL); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataOwnAircraft, "COM ACTIVE FREQUENCY:1", "MHz"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataOwnAircraft, "COM ACTIVE FREQUENCY:2", "MHz"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataOwnAircraft, "COM STANDBY FREQUENCY:1", "MHz"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataOwnAircraft, "COM STANDBY FREQUENCY:2", "MHz"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataOwnAircraft, "FLAPS HANDLE PERCENT", "Percent Over 100"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataOwnAircraft, "SPOILERS HANDLE POSITION", "Percent Over 100"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataOwnAircraft, "GEAR HANDLE POSITION", "Bool"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataOwnAircraft, "NUMBER OF ENGINES", "Number"); + // Simconnect supports index 1 - 4 + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataOwnAircraft, "GENERAL ENG COMBUSTION:1", "Bool"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataOwnAircraft, "GENERAL ENG COMBUSTION:2", "Bool"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataOwnAircraft, "GENERAL ENG COMBUSTION:3", "Bool"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataOwnAircraft, "GENERAL ENG COMBUSTION:4", "Bool"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataOwnAircraftTitle, "TITLE", NULL, SIMCONNECT_DATATYPE_STRING256); + if (hr != S_OK) + { + CLogMessage(static_cast(nullptr)).error("SimConnect error: initOwnAircraft %1") << hr; + } + return hr; + } + + HRESULT CSimConnectDefinitions::initRemoteAircraft(const HANDLE hSimConnect) + { + HRESULT hr = S_OK; + // Position + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftPosition, "Initial Position", NULL, SIMCONNECT_DATATYPE_INITPOSITION); + + // Hint: "Bool" and "Percent .." are units name + // default data type is SIMCONNECT_DATATYPE_FLOAT64 -> double + + // Flaps + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftParts, "LEADING EDGE FLAPS LEFT PERCENT", "Percent Over 100"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftParts, "LEADING EDGE FLAPS RIGHT PERCENT", "Percent Over 100"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftParts, "TRAILING EDGE FLAPS LEFT PERCENT", "Percent Over 100"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftParts, "TRAILING EDGE FLAPS RIGHT PERCENT", "Percent Over 100"); + + // Gear & Spoiler + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftParts, "GEAR HANDLE POSITION", "Bool"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftParts, "SPOILERS HANDLE POSITION", "Percent Over 100"); + + // Engines + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftParts, "GENERAL ENG COMBUSTION:1", "Bool"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftParts, "GENERAL ENG COMBUSTION:2", "Bool"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftParts, "GENERAL ENG COMBUSTION:3", "Bool"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftParts, "GENERAL ENG COMBUSTION:4", "Bool"); + + // Lights (other definition) + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftLights, "LIGHT STROBE", "Bool"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftLights, "LIGHT LANDING", "Bool"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftLights, "LIGHT TAXI", "Bool"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftLights, "LIGHT BEACON", "Bool"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftLights, "LIGHT NAV", "Bool"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftLights, "LIGHT LOGO", "Bool"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftLights, "LIGHT NAV", "Bool"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftLights, "LIGHT RECOGNITION", "Bool"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftLights, "LIGHT CABIN", "Bool"); + + if (hr != S_OK) + { + CLogMessage(static_cast(nullptr)).error("SimConnect error: initRemoteAircraftSituation %1") << hr; + } + return hr; + } + + HRESULT CSimConnectDefinitions::initRemoteAircraftSimData(const HANDLE hSimConnect) + { + HRESULT hr = S_OK; + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftSimData, "PLANE LATITUDE", "degrees"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftSimData, "PLANE LONGITUDE", "degrees"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftSimData, "PLANE ALTITUDE", "Feet"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftSimData, "GROUND ALTITUDE", "Feet"); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataRemoteAircraftSimData, "STATIC CG TO GROUND", "Feet"); + if (hr != S_OK) + { + CLogMessage(static_cast(nullptr)).error("SimConnect error: initRemoteAircraftSimData %1") << hr; + } + return hr; + } + + HRESULT CSimConnectDefinitions::initSimulatorEnvironment(const HANDLE hSimConnect) + { + HRESULT hr = S_OK; + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataSimEnvironment, "ZULU TIME", "", SIMCONNECT_DATATYPE_INT32); + hr += SimConnect_AddToDataDefinition(hSimConnect, CSimConnectDefinitions::DataSimEnvironment, "LOCAL TIME", "", SIMCONNECT_DATATYPE_INT32); + if (hr != S_OK) + { + CLogMessage(static_cast(nullptr)).error("SimConnect error: initSimulatorEnvironment %1") << hr; + } + return hr; + } + + HRESULT CSimConnectDefinitions::initSbDataArea(const HANDLE hSimConnect) + { + HRESULT hr = S_OK; + DWORD sbSize = sizeof(DataDefinitionClientAreaSb); + + // We need to know the client area 'name' and map it to a client ID + hr += SimConnect_MapClientDataNameToID(hSimConnect, "SquawkBox Data", ClientAreaSquawkBox); + if (hr != S_OK) + { + CLogMessage(static_cast(nullptr)).error("SimConnect error: SimConnect_MapClientDataNameToID %1") << hr; + return hr; + } + + // Mapping needs to be first + hr += SimConnect_CreateClientData(hSimConnect, ClientAreaSquawkBox, sbSize, SIMCONNECT_CREATE_CLIENT_DATA_FLAG_DEFAULT); + if (hr != S_OK) + { + CLogMessage(static_cast(nullptr)).error("SimConnect error: SimConnect_CreateClientData %1") << hr; + return hr; + } + + hr += SimConnect_AddToClientDataDefinition(hSimConnect, CSimConnectDefinitions::DataClientAreaSb, 0, sbSize); + hr += SimConnect_AddToClientDataDefinition(hSimConnect, CSimConnectDefinitions::DataClientAreaSbStandby, 17, 1); + hr += SimConnect_AddToClientDataDefinition(hSimConnect, CSimConnectDefinitions::DataClientAreaSbIdent, 19, 1); + if (hr != S_OK) + { + CLogMessage(static_cast(nullptr)).error("SimConnect error: SB data area data definitions %1") << hr; + return hr; + } + + // write a default client area so we are not suddenly squawking ident or so + DataDefinitionClientAreaSb sbArea; + sbArea.setDefaultValues(); + hr += SimConnect_SetClientData(hSimConnect, ClientAreaSquawkBox, CSimConnectDefinitions::DataClientAreaSb, SIMCONNECT_CLIENT_DATA_REQUEST_FLAG_DEFAULT, 0, sbSize, &sbArea); + if (hr != S_OK) + { + CLogMessage(static_cast(nullptr)).error("SimConnect error: SimConnect_SetClientData %1") << hr; + } + return hr; + } + + bool DataDefinitionRemoteAircraftParts::operator==(const DataDefinitionRemoteAircraftParts &rhs) const + { + return std::tie(flapsLeadingEdgeLeftPercent, flapsLeadingEdgeRightPercent, flapsTrailingEdgeLeftPercent, flapsTrailingEdgeRightPercent, + gearHandlePosition, spoilersHandlePosition, + engine1Combustion, engine2Combustion, engine3Combustion, engine4Combustion) == + std::tie(rhs.flapsLeadingEdgeLeftPercent, rhs.flapsLeadingEdgeRightPercent, rhs.flapsTrailingEdgeLeftPercent, rhs.flapsTrailingEdgeRightPercent, + rhs.gearHandlePosition, rhs.spoilersHandlePosition, + rhs.engine1Combustion, rhs.engine2Combustion, rhs.engine3Combustion, rhs.engine4Combustion); + } + + void DataDefinitionRemoteAircraftParts::setAllEngines(bool on) + { + engine1Combustion = on ? 1 : 0; + engine2Combustion = on ? 1 : 0; + engine3Combustion = on ? 1 : 0; + engine4Combustion = on ? 1 : 0; + } + + void DataDefinitionRemoteAircraftParts::resetAllFlaps() + { + flapsLeadingEdgeLeftPercent = 0; + flapsLeadingEdgeRightPercent = 0; + flapsTrailingEdgeLeftPercent = 0; + flapsTrailingEdgeRightPercent = 0; + } + + void DataDefinitionRemoteAircraftParts::resetSpoilers() + { + spoilersHandlePosition = 0; + } + + CAircraftLights DataDefinitionRemoteAircraftLights::toLights() const + { + return CAircraftLights(lightStrobe, lightLanding, lightTaxi, lightBeacon, lightNav, lightLogo, lightRecognition, lightCabin); + } + } // namespace +} // namespace diff --git a/src/plugins/simulator/fsxcommon/simconnectdatadefinition.h b/src/plugins/simulator/fsxcommon/simconnectdatadefinition.h new file mode 100644 index 000000000..40a5f3d36 --- /dev/null +++ b/src/plugins/simulator/fsxcommon/simconnectdatadefinition.h @@ -0,0 +1,230 @@ +/* Copyright (C) 2013 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project, + * including this file, may be copied, modified, propagated, or distributed except according to the terms + * contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKSIMPLUGIN_FSX_SIMCONNECT_DATADEFINITION_H +#define BLACKSIMPLUGIN_FSX_SIMCONNECT_DATADEFINITION_H + +#include + +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include "blackmisc/aviation/aircraftlights.h" +#include +#include +#include +#include + +namespace BlackSimPlugin +{ + namespace FsxCommon + { + //! Data struct of our own aircraft + //! \sa SimConnect variables http://msdn.microsoft.com/en-us/library/cc526981.aspx + //! \sa SimConnect events http://msdn.microsoft.com/en-us/library/cc526980.aspx + struct DataDefinitionOwnAircraft + { + double latitude; //!< Latitude (deg) + double longitude; //!< Longitude (deg) + double altitude; //!< Altitude (ft) + double altitudeAGL; //!< Altitude above ground (ft) + double cgToGround; //!< Static CG to ground (ft) + double trueHeading; //!< True heading (deg) + double pitch; //!< Pitch (deg) + double bank; //!< Bank (deg) + double velocity; //!< Ground velocity + double elevation; //!< Elevation (ft) + double simOnGround; //!< Is aircraft on ground? + + double lightStrobe; //!< Is strobe light on? + double lightLanding; //!< Is landing light on? + double lightTaxi; //!< Is taxi light on? + double lightBeacon; //!< Is beacon light on? + double lightNav; //!< Is nav light on? + double lightLogo; //!< Is logo light on? + + double transponderCode; //!< Transponder Code + double com1ActiveMHz; //!< COM1 active frequency + double com2ActiveMHz; //!< COM2 active frequency + double com1StandbyMHz; //!< COM1 standby frequency + double com2StandbyMHz; //!< COM1 standby frequency + + double flapsHandlePosition; //!< Flaps handle position in percent + double spoilersHandlePosition; //!< Spoilers out? + double gearHandlePosition; //!< Gear handle position + + double numberOfEngines; //!< Number of engines + double engine1Combustion; //!< Engine 1 combustion flag + double engine2Combustion; //!< Engine 2 combustion flag + double engine3Combustion; //!< Engine 3 combustion flag + double engine4Combustion; //!< Engine 4 combustion flag + }; + + //! Data struct of aircraft position + struct DataDefinitionOwnAircraftModel + { + char title[256]; //!< Aircraft model string + }; + + //! Data struct of remote aircraft parts + struct DataDefinitionRemoteAircraftParts + { + double flapsLeadingEdgeLeftPercent; //!< Leading edge left in percent + double flapsLeadingEdgeRightPercent; //!< Leading edge right in percent + double flapsTrailingEdgeLeftPercent; //!< Trailing edge left in percent + double flapsTrailingEdgeRightPercent; //!< Trailing edge right in percent + double gearHandlePosition; //!< Gear handle position + double spoilersHandlePosition; //!< Spoilers out? + double engine1Combustion; //!< Engine 1 combustion flag + double engine2Combustion; //!< Engine 2 combustion flag + double engine3Combustion; //!< Engine 3 combustion flag + double engine4Combustion; //!< Engine 4 combustion flag + + //! Equal to other parts + bool operator==(const DataDefinitionRemoteAircraftParts &rhs) const; + + //! All engines on/off + void setAllEngines(bool on); + + //! Reset all flaps + void resetAllFlaps(); + + //! Reset spoilers + void resetSpoilers(); + }; + + //! Data for aircraft lighs + struct DataDefinitionRemoteAircraftLights + { + double lightStrobe; //!< Is strobe light on? + double lightLanding; //!< Is landing light on? + double lightTaxi; //!< Is taxi light on? + double lightBeacon; //!< Is beacon light on? + double lightNav; //!< Is nav light on? + double lightLogo; //!< Is logo light on? + double lightRecognition; //!< Is recognition light on + double lightCabin; //!< Is cabin light on + + //! Convert to lights + BlackMisc::Aviation::CAircraftLights toLights() const; + }; + + //! Data for AI object sent back from simulator + struct DataDefinitionRemoteAircraftSimData + { + double latitude; //!< Latitude (deg) + double longitude; //!< Longitude (deg) + double altitude; //!< Altitude (ft) + double elevation; //!< Elevation (ft) + double cgToGround; //!< Static CG to ground (ft) + + //! Above ground ft + double aboveGround() const { return altitude - elevation; } + }; + + //! Data struct simulator environment + struct DataDefinitionSimEnvironment + { + qint32 zuluTimeSeconds; //!< Simulator zulu (GMT) ime in secs. + qint32 localTimeSeconds; //!< Simulator local time in secs. + }; + + //! The whole SB data area + struct DataDefinitionClientAreaSb + { + byte data[128] {}; //!< 128 bytes of data, offsets http://www.squawkbox.ca/doc/sdk/fsuipc.php + + //! Standby = 1, else 0 + byte getTransponderMode() const { return data[17]; } + + //! Ident = 1, else 0 + byte getIdent() const { return data[19]; } + + //! Ident? + bool isIdent() const { return getIdent() != 0; } + + //! Standby + bool isStandby() const { return getTransponderMode() != 0; } + + //! Set default values + void setDefaultValues() + { + std::fill(data, data + 128, static_cast(0)); + data[17] = 1; // standby + data[19] = 0; // no ident + } + }; + + //! Client areas + enum ClientAreaId + { + ClientAreaSquawkBox + }; + + //! Handles SimConnect data definitions + class CSimConnectDefinitions + { + public: + //! SimConnect definiton IDs + enum DataDefiniton + { + DataOwnAircraft, + DataOwnAircraftTitle, + DataRemoteAircraftLights, + DataRemoteAircraftParts, + DataRemoteAircraftPosition, + DataRemoteAircraftSimData, + DataSimEnvironment, + DataClientAreaSb, //!< whole SB area + DataClientAreaSbIdent, //!< ident single value + DataClientAreaSbStandby //!< standby + }; + + //! SimConnect request IDs + enum Request + { + RequestOwnAircraft, + RequestRemoveAircraft, + RequestOwnAircraftTitle, + RequestSimEnvironment, + RequestSbData, //!< SB client area / XPDR mode + RequestEndMarker //!< free request ids can start here + }; + + //! Constructor + CSimConnectDefinitions(); + + //! Initialize all data definitions + static HRESULT initDataDefinitionsWhenConnected(const HANDLE hSimConnect); + + //! Log message category + static QString getLogCategory() { return "swift.fsx.simconnect"; } + + private: + //! Initialize data definition for our own aircraft + static HRESULT initOwnAircraft(const HANDLE hSimConnect); + + //! Initialize data definition for remote aircraft + static HRESULT initRemoteAircraft(const HANDLE hSimConnect); + + //! Initialize data for remote aircraft queried from simulator + static HRESULT initRemoteAircraftSimData(const HANDLE hSimConnect); + + //! Initialize data definition for Simulator environment + static HRESULT initSimulatorEnvironment(const HANDLE hSimConnect); + + //! Initialize the SB data are + static HRESULT initSbDataArea(const HANDLE hSimConnect); + }; + } // namespace +} // namespace + +#endif // guard diff --git a/src/plugins/simulator/fsxcommon/simconnectobject.cpp b/src/plugins/simulator/fsxcommon/simconnectobject.cpp new file mode 100644 index 000000000..814dcf4bb --- /dev/null +++ b/src/plugins/simulator/fsxcommon/simconnectobject.cpp @@ -0,0 +1,108 @@ +/* Copyright (C) 2013 + * swift Project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project, + * including this file, may be copied, modified, propagated, or distributed except according to the terms + * contained in the LICENSE file. + */ + +#include "simconnectobject.h" +#include "blackmisc/simulation/interpolatorlinear.h" + +using namespace BlackMisc::Aviation; +using namespace BlackMisc::Simulation; + +namespace BlackSimPlugin +{ + namespace FsxCommon + { + CSimConnectObject::CSimConnectObject() + { } + + CSimConnectObject::CSimConnectObject(const BlackMisc::Simulation::CSimulatedAircraft &aircraft, DWORD requestId, + BlackMisc::Simulation::CInterpolationLogger *logger) : + m_aircraft(aircraft), m_requestId(requestId), m_validRequestId(true), + m_interpolator(QSharedPointer::create(aircraft.getCallsign())) + { + m_interpolator->attachLogger(logger); + } + + bool CSimConnectObject::isPendingAdded() const + { + return !this->hasValidRequestAndObjectId() || !this->m_confirmedAdded; + } + + bool CSimConnectObject::isConfirmedAdded() const + { + Q_ASSERT_X(!m_confirmedAdded || this->hasValidRequestAndObjectId(), Q_FUNC_INFO, "confirmed but invalid ids"); + return m_confirmedAdded; + } + + void CSimConnectObject::setConfirmedAdded(bool confirm) + { + m_confirmedAdded = confirm; + m_aircraft.setRendered(true); + } + + void CSimConnectObject::setPendingRemoved(bool pending) + { + m_pendingRemoved = pending; + m_aircraft.setRendered(false); + } + + bool CSimConnectObject::hasValidRequestAndObjectId() const + { + return this->hasValidRequestId() && this->hasValidObjectId(); + } + + bool CSimConnectObjects::setSimConnectObjectIdForRequestId(DWORD requestId, DWORD objectId) + { + // First check, if this request id belongs to us + auto it = std::find_if(this->begin(), this->end(), [requestId](const CSimConnectObject & obj) { return obj.getRequestId() == requestId; }); + if (it == this->end()) { return false; } + + // belongs to us + it->setObjectId(objectId); + return true; + } + + CCallsign CSimConnectObjects::getCallsignForObjectId(DWORD objectId) const + { + return getSimObjectForObjectId(objectId).getCallsign(); + } + + CSimConnectObject CSimConnectObjects::getSimObjectForObjectId(DWORD objectId) const + { + for (const CSimConnectObject &simObject : this->values()) + { + if (simObject.getObjectId() == objectId) { return simObject; } + } + return CSimConnectObject(); + } + + CSimConnectObject CSimConnectObjects::getSimObjectForRequestId(DWORD requestId) const + { + for (const CSimConnectObject &simObject : this->values()) + { + if (simObject.getRequestId() == requestId) { return simObject; } + } + return CSimConnectObject(); + } + + bool CSimConnectObjects::isKnownSimObjectId(DWORD objectId) const + { + const CSimConnectObject simObject(getSimObjectForObjectId(objectId)); + return simObject.hasValidRequestAndObjectId() && objectId == simObject.getObjectId(); + } + + bool CSimConnectObjects::containsPendingAdd() const + { + for (const CSimConnectObject &simObject : this->values()) + { + if (simObject.isPendingAdded()) { return true; } + } + return false; + } + } // namespace +} // namespace diff --git a/src/plugins/simulator/fsxcommon/simconnectobject.h b/src/plugins/simulator/fsxcommon/simconnectobject.h new file mode 100644 index 000000000..d1027948e --- /dev/null +++ b/src/plugins/simulator/fsxcommon/simconnectobject.h @@ -0,0 +1,161 @@ +/* Copyright (C) 2013 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project, + * including this file, may be copied, modified, propagated, or distributed except according to the terms + * contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKSIMPLUGIN_FSXCOMMON_SIMCONNECTOBJECT_H +#define BLACKSIMPLUGIN_FSXCOMMON_SIMCONNECTOBJECT_H + +#include "blackmisc/simulation/simulatedaircraft.h" +#include "simconnectdatadefinition.h" +#include + +namespace BlackMisc +{ + namespace Simulation + { + class CInterpolatorLinear; + class CInterpolationLogger; + } +} +namespace BlackSimPlugin +{ + namespace FsxCommon + { + //! Class representing a SimConnect object + class CSimConnectObject + { + public: + //! Constructor + CSimConnectObject(); + + //! Constructor + CSimConnectObject(const BlackMisc::Simulation::CSimulatedAircraft &aircraft, DWORD requestId, + BlackMisc::Simulation::CInterpolationLogger *logger); + + //! Destructor + ~CSimConnectObject() {} + + //! Get Callsign + const BlackMisc::Aviation::CCallsign &getCallsign() const { return m_aircraft.getCallsign(); } + + //! Simulated aircraft (as added) + const BlackMisc::Simulation::CSimulatedAircraft &getAircraft() const { return m_aircraft; } + + //! Simulated aircraft model string + const QString &getAircraftModelString() const { return m_aircraft.getModelString(); } + + //! Interpolator + BlackMisc::Simulation::CInterpolatorLinear *getInterpolator() const { return m_interpolator.data(); } + + //! Get current lights (requested from simulator) + const BlackMisc::Aviation::CAircraftLights &getCurrentLightsInSimulator() const { return m_currentLightsInSim; } + + //! Set current lights when received from simulator + void setCurrentLightsInSimulator(const BlackMisc::Aviation::CAircraftLights &lights) { m_currentLightsInSim = lights; } + + //! Parts as sent to simulator + const DataDefinitionRemoteAircraftParts &getPartsAsSent() const { return m_partsAsSent; } + + //! Parts as sent to simulator + void setPartsAsSent(const DataDefinitionRemoteAircraftParts &parts) { m_partsAsSent = parts; } + + //! Lights as sent to simulator + const BlackMisc::Aviation::CAircraftLights &getLightsAsSent() const { return m_lightsAsSent; } + + //! Lights as sent to simulator + void setLightsAsSent(const BlackMisc::Aviation::CAircraftLights &lights) { m_lightsAsSent = lights; } + + //! How often do we request data from simulator for this remote aircraft + SIMCONNECT_PERIOD getSimDataPeriod() const { return m_requestSimDataPeriod; } + + //! How often do we request data from simulator for this remote aircraft + void setSimDataPeriod(SIMCONNECT_PERIOD period) { m_requestSimDataPeriod = period; } + + //! Set Simconnect request id + void setRequestId(DWORD id) { m_requestId = id; m_validRequestId = true; } + + //! Get Simconnect request id + DWORD getRequestId() const { return m_requestId; } + + //! Set Simconnect object id + void setObjectId(DWORD id) { m_objectId = id; m_validObjectId = true; } + + //! Set Simconnect object id + DWORD getObjectId() const { return m_objectId; } + + //! Valid request id? + bool hasValidRequestId() const { return this->m_validRequestId; } + + //! Valid object id? + bool hasValidObjectId() const { return this->m_validObjectId; } + + //! Object is requested, not yet added + bool isPendingAdded() const; + + //! Adding is confirmed + bool isConfirmedAdded() const; + + //! Marked as confirmed + void setConfirmedAdded(bool confirm); + + //! Removing is pending + bool isPendingRemoved() const { return m_pendingRemoved; } + + //! Marked as confirmed + void setPendingRemoved(bool pending); + + //! VTOL? + bool isVtol() const { return m_aircraft.isVtol(); } + + //! Was the object really added to SIM + bool hasValidRequestAndObjectId() const; + + private: + BlackMisc::Simulation::CSimulatedAircraft m_aircraft; //!< corresponding aircraft + DWORD m_requestId = 0; + DWORD m_objectId = 0; + bool m_validRequestId = false; + bool m_validObjectId = false; + bool m_confirmedAdded = false; + bool m_pendingRemoved = false; + int m_lightsRequestedAt = -1; + DataDefinitionRemoteAircraftParts m_partsAsSent {}; //!< parts as sent + BlackMisc::Aviation::CAircraftLights m_currentLightsInSim { nullptr }; //!< current lights to know state for toggling + BlackMisc::Aviation::CAircraftLights m_lightsAsSent { nullptr }; //!< lights as sent to simulator + SIMCONNECT_PERIOD m_requestSimDataPeriod = SIMCONNECT_PERIOD_NEVER; //!< how often do we query ground elevation + QSharedPointer m_interpolator; //!< shared pointer because CSimConnectObject can be copied + }; + + //! Simulator objects (aka AI aircraft) + class CSimConnectObjects : public QHash + { + public: + //! Set ID of a SimConnect object, so far we only have an request id in the object + bool setSimConnectObjectIdForRequestId(DWORD requestId, DWORD objectId); + + //! Find which callsign belongs to the object id + BlackMisc::Aviation::CCallsign getCallsignForObjectId(DWORD objectId) const; + + //! Get object per object id + CSimConnectObject getSimObjectForObjectId(DWORD objectId) const; + + //! Get object per request id + CSimConnectObject getSimObjectForRequestId(DWORD requestId) const; + + //! Is the object id one of our AI objects? + bool isKnownSimObjectId(DWORD objectId) const; + + //! Pending add condition + bool containsPendingAdd() const; + }; + } // namespace +} // namespace + +#endif // guard diff --git a/src/plugins/simulator/fsxcommon/simconnectsettingscomponent.cpp b/src/plugins/simulator/fsxcommon/simconnectsettingscomponent.cpp new file mode 100644 index 000000000..5f6b0cb65 --- /dev/null +++ b/src/plugins/simulator/fsxcommon/simconnectsettingscomponent.cpp @@ -0,0 +1,175 @@ +/* Copyright (C) 2015 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project, + * including this file, may be copied, modified, propagated, or distributed except according to the terms + * contained in the LICENSE file. + */ + +#include "simconnectsettingscomponent.h" +#include "ui_simconnectsettingscomponent.h" +#include "blackgui/guiapplication.h" +#include "blackcore/context/contextapplication.h" +#include "blackcore/context/contextsimulator.h" +#include "blackmisc/network/networkutils.h" +#include "blackmisc/logmessage.h" +#include "blackmisc/simulation/fsx/simconnectutilities.h" +#include +#include +#include +#include +#include + +using namespace BlackMisc; +using namespace BlackMisc::Simulation::Fsx; +using namespace BlackMisc::Network; + +namespace BlackSimPlugin +{ + namespace FsxCommon + { + CSimConnectSettingsComponent::CSimConnectSettingsComponent(QWidget *parent) : + QFrame(parent), + ui(new Ui::CSimConnectSettingsComponent) + { + ui->setupUi(this); + + connect(ui->pb_SettingsFsxOpenSimconnectCfg, &QPushButton::clicked, this, &CSimConnectSettingsComponent::openSimConnectCfgFile); + connect(ui->pb_SettingsFsxDeleteSimconnectCfg, &QPushButton::clicked, this, &CSimConnectSettingsComponent::deleteSimConnectCfgFile); + connect(ui->pb_SettingsFsxExistsSimconncetCfg, &QPushButton::clicked, this, &CSimConnectSettingsComponent::checkSimConnectCfgFile); + connect(ui->pb_SettingsFsxSaveSimconnectCfg, &QPushButton::clicked, this, &CSimConnectSettingsComponent::saveSimConnectCfgFile); + connect(ui->pb_SettingsFsxTestConnection, &QPushButton::clicked, this, &CSimConnectSettingsComponent::testSimConnectConnection); + } + + CSimConnectSettingsComponent::~CSimConnectSettingsComponent() + { + // void + } + + void CSimConnectSettingsComponent::openSimConnectCfgFile() + { + const QFileInfo info(CSimConnectUtilities::getLocalSimConnectCfgFilename()); + const QString path = QDir::toNativeSeparators(info.absolutePath()); + QDesktopServices::openUrl(QUrl(QStringLiteral("file:///") % path)); + } + + void CSimConnectSettingsComponent::deleteSimConnectCfgFile() + { + const QString fileName = CSimConnectUtilities::getLocalSimConnectCfgFilename(); + const bool result = sGui->getIContextApplication()->removeFile(fileName); + if (result) + { + QMessageBox::information(qApp->activeWindow(), tr("File deleted"), + tr("File %1 deleted successfully.").arg(fileName)); + } + checkSimConnectCfgFile(); + } + + void CSimConnectSettingsComponent::checkSimConnectCfgFile() + { + const QString fileName = CSimConnectUtilities::getLocalSimConnectCfgFilename(); + if (sGui->getIContextApplication()->existsFile(fileName)) + { + ui->le_SettingsFsxExistsSimconncetCfg->setText(fileName); + } + else + { + ui->le_SettingsFsxExistsSimconncetCfg->setText("no file"); + } + } + + void CSimConnectSettingsComponent::testSimConnectConnection() + { + const QString address = ui->le_SettingsFsxAddress->text().trimmed(); + const QString port = ui->le_SettingsFsxPort->text().trimmed(); + + if (address.isEmpty() || port.isEmpty()) + { + QMessageBox::warning(qApp->activeWindow(), tr("Connection invalid"), + tr("Address and/or port not specified!")); + return; + } + if (!CNetworkUtils::isValidIPv4Address(address)) + { + QMessageBox::warning(qApp->activeWindow(), tr("Connection invalid"), + tr("Wrong IPv4 address!")); + return; + } + if (!CNetworkUtils::isValidPort(port)) + { + QMessageBox::warning(qApp->activeWindow(), tr("Connection invalid"), + tr("Invalid port!")); + return; + } + int p = port.toInt(); + QString msg; + if (!CNetworkUtils::canConnect(address, p, msg)) + { + QMessageBox::warning(qApp->activeWindow(), tr("Connection invalid"), msg); + return; + } + + QMessageBox::information(qApp->activeWindow(), tr("Connection successful"), + tr("Connected to %1:%2.").arg(address, port)); + } + + void CSimConnectSettingsComponent::saveSimConnectCfgFile() + { + QString address = ui->le_SettingsFsxAddress->text().trimmed(); + QString port = ui->le_SettingsFsxPort->text().trimmed(); + + if (address.isEmpty() || port.isEmpty()) + { + QMessageBox::warning(qApp->activeWindow(), tr("Connection invalid"), + tr("Address and/or port not specified!")); + return; + } + if (!CNetworkUtils::isValidIPv4Address(address)) + { + QMessageBox::warning(qApp->activeWindow(), tr("Connection invalid"), + tr("Wrong IPv4 address!")); + return; + } + if (!CNetworkUtils::isValidPort(port)) + { + QMessageBox::warning(qApp->activeWindow(), tr("Connection invalid"), + tr("Invalid port!")); + return; + } + + int p = port.toInt(); + QString fileName; + + if (sGui->getIContextSimulator()) + { + const BlackMisc::Simulation::CSimulatorInternals internals(sGui->getIContextSimulator()->getSimulatorInternals()); + fileName = internals.getStringValue("fsx/simConnectCfgFilename"); + } + + if (fileName.isEmpty()) + { + fileName = CSimConnectUtilities::getLocalSimConnectCfgFilename(); + } + + if (fileName.isEmpty()) + { + QMessageBox::warning(qApp->activeWindow(), tr("Failed writing simConnect.cfg"), + tr("No file name specified!")); + return; + } + + if (sGui->getIContextApplication()->writeToFile(fileName, CSimConnectUtilities::simConnectCfg(address, p))) + { + QMessageBox::information(qApp->activeWindow(), tr("File saved"), + tr("File %1 saved.").arg(fileName)); + checkSimConnectCfgFile(); + } + else + { + QMessageBox::warning(qApp->activeWindow(), tr("Failed writing simConnect.cfg"), + tr("Failed writing %1!").arg(fileName)); + } + } + } // ns +} // ns diff --git a/src/plugins/simulator/fsxcommon/simconnectsettingscomponent.h b/src/plugins/simulator/fsxcommon/simconnectsettingscomponent.h new file mode 100644 index 000000000..e8199b5b5 --- /dev/null +++ b/src/plugins/simulator/fsxcommon/simconnectsettingscomponent.h @@ -0,0 +1,59 @@ +/* Copyright (C) 2015 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project, + * including this file, may be copied, modified, propagated, or distributed except according to the terms + * contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKSIMPLUGIN_FSXCOMMON_SIMCONNECTSETTINGSCOMPONENT_H +#define BLACKSIMPLUGIN_FSXCOMMON_SIMCONNECTSETTINGSCOMPONENT_H + +#include +#include + +namespace Ui { class CSimConnectSettingsComponent; } +namespace BlackSimPlugin +{ + namespace FsxCommon + { + /*! + * A component that gathers all SimConnect related settings. + */ + class CSimConnectSettingsComponent : public QFrame + { + Q_OBJECT + + public: + //! Ctor + explicit CSimConnectSettingsComponent(QWidget *parent = nullptr); + + //! Dtor + virtual ~CSimConnectSettingsComponent(); + + private slots: + //! Open simConnect.cfg using default application + void openSimConnectCfgFile(); + + //! Delete simConnect.cfg file + void deleteSimConnectCfgFile(); + + //! Check whether the simConnect.cfg file exists + void checkSimConnectCfgFile(); + + //! Test the SimConnect connectivity + void testSimConnectConnection(); + + //! Save a simconnect.cfg file for FSX + void saveSimConnectCfgFile(); + + private: + QScopedPointer ui; + }; + } // ns +} // ns + +#endif // guard diff --git a/src/plugins/simulator/fsxcommon/simconnectsettingscomponent.ui b/src/plugins/simulator/fsxcommon/simconnectsettingscomponent.ui new file mode 100644 index 000000000..cc49d6e62 --- /dev/null +++ b/src/plugins/simulator/fsxcommon/simconnectsettingscomponent.ui @@ -0,0 +1,237 @@ + + + CSimConnectSettingsComponent + + + + 0 + 0 + 400 + 159 + + + + Form + + + + + + FSX/P3D SimConnect config file + + + + 4 + + + 6 + + + 4 + + + 4 + + + + + Address + + + + + + + 127.0.0.1 + + + 128 + + + e.g. "127.0.0.1" or "192.128.3.1" + + + + + + + Port + + + + + + + 500 + + + normally "500" + + + + + + + is 'SimConnect.cfg' available? + + + .cfg? + + + + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + local "SimConnect.cfg" file? + + + + + + + + 0 + 0 + + + + + 50 + 0 + + + + check + + + + + + + + + + 3 + + + + + + 0 + 0 + + + + + 50 + 0 + + + + open + + + + + + + + 0 + 0 + + + + + 50 + 0 + + + + del. + + + + + + + + 0 + 0 + + + + + 50 + 0 + + + + + 16777215 + 16777215 + + + + save + + + + + + + + 0 + 0 + + + + + 50 + 0 + + + + + 16777215 + 16777215 + + + + Test connection + + + test + + + + + + + + + + + + + diff --git a/src/plugins/simulator/fsxcommon/simulatorfsxcommon.cpp b/src/plugins/simulator/fsxcommon/simulatorfsxcommon.cpp new file mode 100644 index 000000000..9943bd83c --- /dev/null +++ b/src/plugins/simulator/fsxcommon/simulatorfsxcommon.cpp @@ -0,0 +1,1342 @@ +/* Copyright (C) 2013 + * swift Project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project, + * including this file, may be copied, modified, propagated, or distributed except according to the terms + * contained in the LICENSE file. + */ + +#include "simulatorfsxcommon.h" +#include "blackcore/application.h" +#include "blackmisc/network/textmessage.h" +#include "blackmisc/simulation/fscommon/bcdconversions.h" +#include "blackmisc/simulation/fsx/simconnectutilities.h" +#include "blackmisc/simulation/aircraftmodel.h" +#include "blackmisc/simulation/interpolatorlinear.h" +#include "blackmisc/simulation/interpolationhints.h" +#include "blackmisc/simulation/simulatorplugininfo.h" +#include "blackmisc/aviation/airportlist.h" +#include "blackmisc/geo/elevationplane.h" +#include "blackmisc/logmessage.h" +#include "blackmisc/threadutils.h" +#include "blackmisc/verify.h" +#include "blackmisc/simulation/fscommon/fscommonutil.h" + +#include +#include + +using namespace BlackMisc; +using namespace BlackMisc::Aviation; +using namespace BlackMisc::PhysicalQuantities; +using namespace BlackMisc::Geo; +using namespace BlackMisc::Network; +using namespace BlackMisc::Simulation; +using namespace BlackMisc::Simulation::FsCommon; +using namespace BlackMisc::Simulation::Fsx; +using namespace BlackMisc::Weather; +using namespace BlackCore; + +namespace BlackSimPlugin +{ + namespace FsxCommon + { + CSimulatorFsxCommon::CSimulatorFsxCommon(const CSimulatorPluginInfo &info, + IOwnAircraftProvider *ownAircraftProvider, + IRemoteAircraftProvider *remoteAircraftProvider, + IWeatherGridProvider *weatherGridProvider, + QObject *parent) : + CSimulatorFsCommon(info, ownAircraftProvider, remoteAircraftProvider, weatherGridProvider, parent) + { + Q_ASSERT_X(ownAircraftProvider, Q_FUNC_INFO, "Missing provider"); + Q_ASSERT_X(remoteAircraftProvider, Q_FUNC_INFO, "Missing provider"); + Q_ASSERT_X(sApp, Q_FUNC_INFO, "Missing global object"); + m_realityBubbleTimer.setInterval(20 * 1000); + connect(&m_realityBubbleTimer, &QTimer::timeout, this, &CSimulatorFsxCommon::ps_addAircraftCurrentlyOutOfBubble); + + m_useFsuipc = false; + m_defaultModel = + { + "Boeing 737-800 Paint1", + CAircraftModel::TypeModelMatchingDefaultModel, + "B737-800 default model", + CAircraftIcaoCode("B738", "L2J") + }; + } + + CSimulatorFsxCommon::~CSimulatorFsxCommon() + { + disconnectFrom(); + // fsuipc is disconnected in CSimulatorFsCommon + } + + bool CSimulatorFsxCommon::isConnected() const + { + return m_simConnected; + } + + bool CSimulatorFsxCommon::isSimulating() const + { + return m_simSimulating; + } + + bool CSimulatorFsxCommon::connectTo() + { + if (this->isConnected()) { return true; } + this->reset(); + if (FAILED(SimConnect_Open(&m_hSimConnect, sApp->swiftVersionChar(), nullptr, 0, 0, 0))) + { + // reset state as expected for unconnected + return false; + } + if (m_useFsuipc) { this->m_fsuipc->connect(); } // FSUIPC too + + // set structures and move on + initEvents(); + initDataDefinitionsWhenConnected(); + m_simConnectTimerId = startTimer(10); + m_realityBubbleTimer.start(); + return true; + } + + bool CSimulatorFsxCommon::disconnectFrom() + { + if (!m_simConnected) { return true; } + if (m_simConnectTimerId >= 0) { killTimer(m_simConnectTimerId); } + m_simConnectTimerId = -1; + if (m_hSimConnect) + { + SimConnect_Close(m_hSimConnect); + m_hSimConnect = nullptr; + } + + reset(); + + // emit status and disconnect FSUIPC + CSimulatorFsCommon::disconnectFrom(); + return true; + } + + bool CSimulatorFsxCommon::physicallyAddRemoteAircraft(const CSimulatedAircraft &newRemoteAircraft) + { + const CCallsign callsign(newRemoteAircraft.getCallsign()); + + Q_ASSERT_X(CThreadUtils::isCurrentThreadObjectThread(this), Q_FUNC_INFO, "thread"); + Q_ASSERT_X(!callsign.isEmpty(), Q_FUNC_INFO, "empty callsign"); + Q_ASSERT_X(newRemoteAircraft.hasModelString(), Q_FUNC_INFO, "missing model string"); + if (callsign.isEmpty()) { return false; } + + // check if we have to do something + m_outOfRealityBubble.removeByCallsign(callsign); + if (m_simConnectObjects.contains(callsign)) + { + const CSimConnectObject simObj = m_simConnectObjects[callsign]; + if (simObj.isPendingAdded()) + { + return true; // already pending + } + else + { + // same model, nothing will change, otherwise add again when removed + if (simObj.getAircraft().getModel() != newRemoteAircraft.getModel()) + { + m_aircraftToAddAgainWhenRemoved.push_back(newRemoteAircraft); + } + return false; + } + } + + // create AI + bool adding = false; + const CAircraftModel aircraftModel = newRemoteAircraft.getModel(); + CSimulatedAircraft addedAircraft(newRemoteAircraft); + if (isConnected()) + { + const DWORD requestId = obtainRequestIdSimData(); + SIMCONNECT_DATA_INITPOSITION initialPosition = aircraftSituationToFsxPosition(addedAircraft.getSituation()); + const QString modelString(addedAircraft.getModelString()); + + if (m_interpolationRenderingSetup.showSimulatorDebugMessages()) + { + CLogMessage(this).debug() << "physicallyAddRemoteAircraft" << callsign.toQString() << "request" << requestId << "model" << modelString; + CLogMessage(this).debug() << "initial position" << fsxPositionToString(initialPosition); + } + + HRESULT hr = SimConnect_AICreateNonATCAircraft(m_hSimConnect, qPrintable(modelString), qPrintable(callsign.toQString().left(12)), initialPosition, static_cast(requestId)); + if (hr != S_OK) + { + const CStatusMessage msg = CStatusMessage(this).error("SimConnect, can not create AI traffic: '%1' '%2'") << callsign.toQString() << aircraftModel.getModelString(); + CLogMessage::preformatted(msg); + emit physicallyAddingRemoteModelFailed(addedAircraft, msg); + } + else + { + // we will request a new aircraft by request ID, later we will receive its object id + // so far this object id is -1 + addedAircraft.setRendered(false); + const CSimConnectObject simObject(addedAircraft, requestId, &m_interpolationLogger); + m_simConnectObjects.insert(callsign, simObject); + adding = true; + } + } + else + { + CLogMessage(this).warning("FSX: Not connected, not added aircraft '%1' '%2'") << callsign.toQString() << aircraftModel.getModelString(); + } + return adding; + } + + bool CSimulatorFsxCommon::updateOwnSimulatorCockpit(const CSimulatedAircraft &ownAircraft, const CIdentifier &originator) + { + if (originator == this->identifier()) { return false; } + if (!this->isSimulating()) { return false; } + + // actually those data should be the same as ownAircraft + const CComSystem newCom1 = ownAircraft.getCom1System(); + const CComSystem newCom2 = ownAircraft.getCom2System(); + const CTransponder newTransponder = ownAircraft.getTransponder(); + + bool changed = false; + if (newCom1.getFrequencyActive() != this->m_simCom1.getFrequencyActive()) + { + const CFrequency newFreq = newCom1.getFrequencyActive(); + SimConnect_TransmitClientEvent(m_hSimConnect, 0, EventSetCom1Active, + CBcdConversions::comFrequencyToBcdHz(newFreq), SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY); + changed = true; + + } + if (newCom1.getFrequencyStandby() != this->m_simCom1.getFrequencyStandby()) + { + const CFrequency newFreq = newCom1.getFrequencyStandby(); + SimConnect_TransmitClientEvent(m_hSimConnect, 0, EventSetCom1Standby, + CBcdConversions::comFrequencyToBcdHz(newFreq), SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY); + changed = true; + } + + if (newCom2.getFrequencyActive() != this->m_simCom2.getFrequencyActive()) + { + const CFrequency newFreq = newCom2.getFrequencyActive(); + SimConnect_TransmitClientEvent(m_hSimConnect, 0, EventSetCom2Active, + CBcdConversions::comFrequencyToBcdHz(newFreq), SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY); + changed = true; + } + if (newCom2.getFrequencyStandby() != this->m_simCom2.getFrequencyStandby()) + { + const CFrequency newFreq = newCom2.getFrequencyStandby(); + SimConnect_TransmitClientEvent(m_hSimConnect, 0, EventSetCom2Standby, + CBcdConversions::comFrequencyToBcdHz(newFreq), SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY); + changed = true; + } + + if (newTransponder.getTransponderCode() != this->m_simTransponder.getTransponderCode()) + { + SimConnect_TransmitClientEvent(m_hSimConnect, 0, EventSetTransponderCode, + CBcdConversions::transponderCodeToBcd(newTransponder), SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY); + changed = true; + } + + if (newTransponder.getTransponderMode() != this->m_simTransponder.getTransponderMode()) + { + if (m_useSbOffsets) + { + byte ident = newTransponder.isIdentifying() ? 1U : 0U; // 1 is ident + byte standby = newTransponder.isInStandby() ? 1U : 0U; // 1 is standby + HRESULT hr = S_OK; + + hr += SimConnect_SetClientData(m_hSimConnect, ClientAreaSquawkBox, CSimConnectDefinitions::DataClientAreaSbIdent, SIMCONNECT_CLIENT_DATA_REQUEST_FLAG_DEFAULT, 0, 1, &ident); + hr += SimConnect_SetClientData(m_hSimConnect, ClientAreaSquawkBox, CSimConnectDefinitions::DataClientAreaSbStandby, SIMCONNECT_CLIENT_DATA_REQUEST_FLAG_DEFAULT, 0, 1, &standby); + if (hr != S_OK) + { + CLogMessage(this).warning("Setting transponder mode failed (SB offsets)"); + } + } + changed = true; + } + + // avoid changes of cockpit back to old values due to an outdated read back value + if (changed) { m_skipCockpitUpdateCycles = SkipUpdateCyclesForCockpit; } + + // bye + return changed; + } + + void CSimulatorFsxCommon::displayStatusMessage(const BlackMisc::CStatusMessage &message) const + { + QByteArray m = message.getMessage().toLocal8Bit().constData(); + m.append('\0'); + + SIMCONNECT_TEXT_TYPE type = SIMCONNECT_TEXT_TYPE_PRINT_BLACK; + switch (message.getSeverity()) + { + case CStatusMessage::SeverityDebug: return; + case CStatusMessage::SeverityInfo: type = SIMCONNECT_TEXT_TYPE_PRINT_GREEN; break; + case CStatusMessage::SeverityWarning: type = SIMCONNECT_TEXT_TYPE_PRINT_YELLOW; break; + case CStatusMessage::SeverityError: type = SIMCONNECT_TEXT_TYPE_PRINT_RED; break; + } + HRESULT hr = SimConnect_Text(m_hSimConnect, type, 7.5, EventTextMessage, + static_cast(m.size()), m.data()); + Q_UNUSED(hr); + } + + void CSimulatorFsxCommon::displayTextMessage(const BlackMisc::Network::CTextMessage &message) const + { + this->displayStatusMessage(message.asStatusMessage(true, true)); + } + + bool CSimulatorFsxCommon::isPhysicallyRenderedAircraft(const CCallsign &callsign) const + { + return this->m_simConnectObjects.contains(callsign); + } + + CCallsignSet CSimulatorFsxCommon::physicallyRenderedAircraft() const + { + CCallsignSet callsigns(this->m_simConnectObjects.keys()); + callsigns.push_back(m_aircraftToAddAgainWhenRemoved.getCallsigns()); // not really rendered right now, but very soon + callsigns.push_back(m_outOfRealityBubble.getCallsigns()); // not really rendered, but for the logic it should look like it is + return CCallsignSet(this->m_simConnectObjects.keys()); + } + + bool CSimulatorFsxCommon::stillDisplayReceiveExceptions() + { + m_receiveExceptionCount++; + return m_receiveExceptionCount < IgnoreReceiveExceptions; + } + + void CSimulatorFsxCommon::setSimConnected() + { + m_simConnected = true; + this->initSimulatorInternals(); + emitSimulatorCombinedStatus(); + + // Internals depends on sim data which take a while to be read + // this is a trich and I re-init again after a while (which is not really expensive) + QTimer::singleShot(1000, this, [this] { this->initSimulatorInternals(); }); + } + + void CSimulatorFsxCommon::onSimRunning() + { + if (m_simSimulating) { return; } + m_simSimulating = true; // only place where this should be set to true + m_simConnected = true; + HRESULT hr = SimConnect_RequestDataOnSimObject(m_hSimConnect, CSimConnectDefinitions::RequestOwnAircraft, + CSimConnectDefinitions::DataOwnAircraft, + SIMCONNECT_OBJECT_ID_USER, SIMCONNECT_PERIOD_VISUAL_FRAME); + + hr += SimConnect_RequestDataOnSimObject(m_hSimConnect, CSimConnectDefinitions::RequestOwnAircraftTitle, + CSimConnectDefinitions::DataOwnAircraftTitle, + SIMCONNECT_OBJECT_ID_USER, SIMCONNECT_PERIOD_SECOND, + SIMCONNECT_DATA_REQUEST_FLAG_CHANGED); + + hr += SimConnect_RequestDataOnSimObject(m_hSimConnect, CSimConnectDefinitions::RequestSimEnvironment, + CSimConnectDefinitions::DataSimEnvironment, + SIMCONNECT_OBJECT_ID_USER, SIMCONNECT_PERIOD_SECOND, + SIMCONNECT_DATA_REQUEST_FLAG_CHANGED); + + if (hr != S_OK) + { + CLogMessage(this).error("FSX plugin: SimConnect_RequestDataOnSimObject failed"); + return; + } + + // Request the data from SB only when its changed and only ONCE so we don't have to run a 1sec event to get/set this info ;) + hr += SimConnect_RequestClientData(m_hSimConnect, ClientAreaSquawkBox, CSimConnectDefinitions::RequestSbData, + CSimConnectDefinitions::DataClientAreaSb, SIMCONNECT_CLIENT_DATA_PERIOD_SECOND, SIMCONNECT_CLIENT_DATA_REQUEST_FLAG_CHANGED); + + if (hr != S_OK) + { + CLogMessage(this).error("FSX plugin: SimConnect_RequestClientData failed"); + return; + } + + emitSimulatorCombinedStatus(); + } + + void CSimulatorFsxCommon::onSimStopped() + { + const int oldStatus = getSimulatorStatus(); + m_simSimulating = false; + emitSimulatorCombinedStatus(oldStatus); + } + + void CSimulatorFsxCommon::onSimFrame() + { + updateRemoteAircraft(); + } + + void CSimulatorFsxCommon::onSimExit() + { + // reset complete state, we are going down + disconnectFrom(); + } + + DWORD CSimulatorFsxCommon::obtainRequestIdSimData() + { + const DWORD id = m_requestIdSimData++; + if (id > RequestSimDataEnd) { m_requestIdSimData = RequestSimDataStart; } + return id; + } + + void CSimulatorFsxCommon::updateOwnAircraftFromSimulator(const DataDefinitionOwnAircraft &simulatorOwnAircraft) + { + CSimulatedAircraft myAircraft(getOwnAircraft()); + BlackMisc::Geo::CCoordinateGeodetic position; + position.setLatitude(CLatitude(simulatorOwnAircraft.latitude, CAngleUnit::deg())); + position.setLongitude(CLongitude(simulatorOwnAircraft.longitude, CAngleUnit::deg())); + + if (simulatorOwnAircraft.pitch < -90.0 || simulatorOwnAircraft.pitch >= 90.0) + { + CLogMessage(this).warning("FSX: Pitch value out of limits: %1") << simulatorOwnAircraft.pitch; + } + BlackMisc::Aviation::CAircraftSituation aircraftSituation; + aircraftSituation.setPosition(position); + // MSFS has inverted pitch and bank angles + aircraftSituation.setPitch(CAngle(-simulatorOwnAircraft.pitch, CAngleUnit::deg())); + aircraftSituation.setBank(CAngle(-simulatorOwnAircraft.bank, CAngleUnit::deg())); + aircraftSituation.setHeading(CHeading(simulatorOwnAircraft.trueHeading, CHeading::True, CAngleUnit::deg())); + aircraftSituation.setGroundSpeed(CSpeed(simulatorOwnAircraft.velocity, CSpeedUnit::kts())); + aircraftSituation.setGroundElevation(CAltitude(simulatorOwnAircraft.elevation, CAltitude::MeanSeaLevel, CLengthUnit::ft())); + aircraftSituation.setAltitude(CAltitude(simulatorOwnAircraft.altitude, CAltitude::MeanSeaLevel, CLengthUnit::ft())); + + const CAircraftLights lights(simulatorOwnAircraft.lightStrobe, + simulatorOwnAircraft.lightLanding, + simulatorOwnAircraft.lightTaxi, + simulatorOwnAircraft.lightBeacon, + simulatorOwnAircraft.lightNav, + simulatorOwnAircraft.lightLogo); + + CAircraftEngineList engines; + const QList helperList + { + simulatorOwnAircraft.engine1Combustion != 0, simulatorOwnAircraft.engine2Combustion != 0, + simulatorOwnAircraft.engine3Combustion != 0, simulatorOwnAircraft.engine4Combustion != 0 + }; + + for (int index = 0; index < simulatorOwnAircraft.numberOfEngines; ++index) + { + engines.push_back(CAircraftEngine(index + 1, helperList.at(index))); + } + + const CAircraftParts parts(lights, simulatorOwnAircraft.gearHandlePosition, + simulatorOwnAircraft.flapsHandlePosition * 100, + simulatorOwnAircraft.spoilersHandlePosition, + engines, + simulatorOwnAircraft.simOnGround); + + // set values + updateOwnSituation(aircraftSituation); + updateOwnParts(parts); + + // When I change cockpit values in the sim (from GUI to simulator, not originating from simulator) + // it takes a little while before these values are set in the simulator. + // To avoid jitters, I wait some update cylces to stabilize the values + if (m_skipCockpitUpdateCycles < 1) + { + // defaults + CComSystem com1(myAircraft.getCom1System()); // set defaults + CComSystem com2(myAircraft.getCom2System()); + CTransponder transponder(myAircraft.getTransponder()); + + // updates + com1.setFrequencyActive(CFrequency(simulatorOwnAircraft.com1ActiveMHz, CFrequencyUnit::MHz())); + com1.setFrequencyStandby(CFrequency(simulatorOwnAircraft.com1StandbyMHz, CFrequencyUnit::MHz())); + const bool changedCom1 = myAircraft.getCom1System() != com1; + this->m_simCom1 = com1; + + com2.setFrequencyActive(CFrequency(simulatorOwnAircraft.com2ActiveMHz, CFrequencyUnit::MHz())); + com2.setFrequencyStandby(CFrequency(simulatorOwnAircraft.com2StandbyMHz, CFrequencyUnit::MHz())); + const bool changedCom2 = myAircraft.getCom2System() != com2; + this->m_simCom2 = com2; + + transponder.setTransponderCode(simulatorOwnAircraft.transponderCode); + const bool changedXpr = (myAircraft.getTransponderCode() != transponder.getTransponderCode()); + + if (changedCom1 || changedCom2 || changedXpr) + { + this->updateCockpit(com1, com2, transponder, identifier()); + } + } + else + { + --m_skipCockpitUpdateCycles; + } + + if (m_isWeatherActivated) + { + const auto currentPosition = CCoordinateGeodetic { aircraftSituation.latitude(), aircraftSituation.longitude(), {0} }; + if (CWeatherScenario::isRealWeatherScenario(m_weatherScenarioSettings.get()) && + calculateGreatCircleDistance(m_lastWeatherPosition, currentPosition).value(CLengthUnit::mi()) > 20) + { + m_lastWeatherPosition = currentPosition; + const auto weatherGrid = CWeatherGrid { { "GLOB", currentPosition } }; + requestWeatherGrid(weatherGrid, { this, &CSimulatorFsxCommon::injectWeatherGrid }); + } + } + } + + void CSimulatorFsxCommon::updateRemoteAircraftFromSimulator(const CSimConnectObject &simObject, const DataDefinitionRemoteAircraftSimData &remoteAircraftData) + { + // Near ground we use faster updates + if (remoteAircraftData.aboveGround() <= 100.0) + { + // switch to fast updates + if (simObject.getSimDataPeriod() != SIMCONNECT_PERIOD_VISUAL_FRAME) + { + this->requestDataForSimObject(simObject, SIMCONNECT_PERIOD_VISUAL_FRAME); + } + } + else + { + // switch to slow updates + if (simObject.getSimDataPeriod() != SIMCONNECT_PERIOD_SECOND) + { + this->requestDataForSimObject(simObject, SIMCONNECT_PERIOD_SECOND); + } + } + + CElevationPlane elevation(remoteAircraftData.latitude, remoteAircraftData.longitude, remoteAircraftData.elevation); + elevation.setSinglePointRadius(); + + // const QString debug(hints.debugInfo(elevation)); + CInterpolationHints &hints = m_hints[simObject.getCallsign()]; + hints.setElevationPlane(elevation); // update elevation + hints.setCGAboveGround({ remoteAircraftData.cgToGround, CLengthUnit::ft() }); // normally never changing, but if user changes ModelMatching + + // set it in the remote aircraft provider + this->updateAircraftGroundElevation(simObject.getCallsign(), elevation); + } + + void CSimulatorFsxCommon::updateOwnAircraftFromSimulator(const DataDefinitionClientAreaSb &sbDataArea) + { + CTransponder::TransponderMode newMode; + if (sbDataArea.isIdent()) + { + newMode = CTransponder::StateIdent; + } + else + { + newMode = sbDataArea.isStandby() ? CTransponder::StateStandby : CTransponder::ModeC; + } + const CSimulatedAircraft myAircraft(this->getOwnAircraft()); + const bool changed = (myAircraft.getTransponderMode() != newMode); + if (!changed) { return; } + CTransponder xpdr = myAircraft.getTransponder(); + xpdr.setTransponderMode(newMode); + this->updateCockpit(myAircraft.getCom1System(), myAircraft.getCom2System(), xpdr, this->identifier()); + } + + bool CSimulatorFsxCommon::simulatorReportedObjectAdded(DWORD objectID) + { + const CSimConnectObject simObject = this->m_simConnectObjects.getSimObjectForObjectId(objectID); + const CCallsign callsign(simObject.getCallsign()); + if (!simObject.hasValidRequestAndObjectId() || callsign.isEmpty()) { return false; } + + // we know the object has been created. But it can happen it is directly removed afterwards + QTimer::singleShot(500, this, [ = ] { this->ps_deferredSimulatorReportedObjectAdded(callsign); }); + return true; + } + + bool CSimulatorFsxCommon::ps_deferredSimulatorReportedObjectAdded(const CCallsign &callsign) + { + if (callsign.isEmpty()) { return false; } + if (!m_simConnectObjects.contains(callsign)) { return false; } // removed in mean time + + CSimConnectObject &simObject = m_simConnectObjects[callsign]; + if (!simObject.hasValidRequestAndObjectId() || simObject.isPendingRemoved()) { return false; } + + Q_ASSERT_X(simObject.isPendingAdded(), Q_FUNC_INFO, "already confirmed"); + simObject.setConfirmedAdded(true); + const DWORD objectId = simObject.getObjectId(); + + if (m_interpolationRenderingSetup.showSimulatorDebugMessages()) + { + CLogMessage(this).debug() << "Adding AI" << callsign.toQString() << "confirmed" << "id" << objectId << "model" << simObject.getAircraftModelString(); + } + + // P3D also has SimConnect_AIReleaseControlEx; + const DWORD requestId = obtainRequestIdSimData(); + HRESULT hr = SimConnect_AIReleaseControl(m_hSimConnect, objectId, static_cast(requestId)); + if (hr == S_OK) + { + SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventFreezeLat, 1, + SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY); + SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventFreezeAlt, 1, + SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY); + SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventFreezeAtt, 1, + SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY); + } + else + { + CLogMessage(this).error("Adding AI %1 failed") << callsign.toQString(); + return false; + } + + const bool updated = this->updateAircraftRendered(callsign, true); + if (updated) + { + emit aircraftRenderingChanged(simObject.getAircraft()); + } + return true; + } + + void CSimulatorFsxCommon::ps_addAircraftCurrentlyOutOfBubble() + { + if (m_outOfRealityBubble.isEmpty()) { return; } + const CCallsignSet aircraftCallsignsInRange(getAircraftInRangeCallsigns()); + CSimulatedAircraftList toBeAddedAircraft; + CCallsignSet toBeRemovedCallsigns; + for (const CSimulatedAircraft &aircraft : as_const(m_outOfRealityBubble)) + { + Q_ASSERT_X(!aircraft.getCallsign().isEmpty(), Q_FUNC_INFO, "missing callsign"); + if (aircraftCallsignsInRange.contains(aircraft.getCallsign())) + { + toBeAddedAircraft.push_back(aircraft); + } + else + { + toBeRemovedCallsigns.push_back(aircraft.getCallsign()); + } + } + m_outOfRealityBubble.removeByCallsigns(toBeRemovedCallsigns); + + // add aircraft, but non blocking + int t = 100; + for (const CSimulatedAircraft &aircraft : as_const(toBeAddedAircraft)) + { + QTimer::singleShot(t, this, [ = ] + { + this->physicallyAddRemoteAircraft(aircraft); + }); + t += 100; + } + } + + bool CSimulatorFsxCommon::simulatorReportedObjectRemoved(DWORD objectID) + { + const CSimConnectObject simObject = this->m_simConnectObjects.getSimObjectForObjectId(objectID); + if (!simObject.hasValidRequestAndObjectId()) { return false; } // object id from somewhere else + const CCallsign callsign(simObject.getCallsign()); + Q_ASSERT_X(!callsign.isEmpty(), Q_FUNC_INFO, "missing callsign"); + + bool ok = false; + if (simObject.isPendingRemoved()) + { + // good case, object has been removed + // we can remove the sim object + } + else + { + // object was removed, but removal was not requested by us + // this means we are out of the reality bubble (or something else went wrong) + if (!simObject.getAircraftModelString().isEmpty()) + { + this->m_outOfRealityBubble.push_back(simObject.getAircraft()); + CLogMessage(this).info("Aircraft removed, '%1' '%2' object id '%3' out of reality bubble") << callsign.toQString() << simObject.getAircraftModelString() << objectID; + } + else + { + CLogMessage(this).warning("Removed %1 from simulator, but was not initiated by us: %1 '%2' object id %3") << callsign.toQString() << simObject.getAircraftModelString() << objectID; + } + } + + // in all cases we remove + const int c = m_simConnectObjects.remove(callsign); + ok = c > 0; + const bool updated = this->updateAircraftRendered(simObject.getCallsign(), false); + if (updated) + { + emit aircraftRenderingChanged(simObject.getAircraft()); + } + + // models we have to add again after removing + if (m_aircraftToAddAgainWhenRemoved.containsCallsign(callsign)) + { + const CSimulatedAircraft aircraftAddAgain = m_aircraftToAddAgainWhenRemoved.findFirstByCallsign(callsign); + QTimer::singleShot(1000, this, [ = ] { this->physicallyAddRemoteAircraft(aircraftAddAgain); }); + } + return ok; + } + + bool CSimulatorFsxCommon::setSimConnectObjectId(DWORD requestID, DWORD objectID) + { + return this->m_simConnectObjects.setSimConnectObjectIdForRequestId(requestID, objectID); + } + + bool CSimulatorFsxCommon::setCurrentLights(const CCallsign &callsign, const CAircraftLights &lights) + { + if (!m_simConnectObjects.contains(callsign)) { return false; } + m_simConnectObjects[callsign].setCurrentLightsInSimulator(lights); + return true; + } + + bool CSimulatorFsxCommon::setLightsAsSent(const CCallsign &callsign, const CAircraftLights &lights) + { + if (!m_simConnectObjects.contains(callsign)) { return false; } + m_simConnectObjects[callsign].setLightsAsSent(lights); + return true; + } + + void CSimulatorFsxCommon::timerEvent(QTimerEvent *event) + { + Q_UNUSED(event); + ps_dispatch(); + } + + void CSimulatorFsxCommon::ps_dispatch() + { + HRESULT hr = SimConnect_CallDispatch(m_hSimConnect, SimConnectProc, this); + if (hr != S_OK) + { + m_dispatchErrors++; + if (m_dispatchErrors == 2) + { + // 2nd time, an error / avoid multiple messages + // idea: if it happens once ignore + CLogMessage(this).error("FSX: Dispatch error"); + } + else if (m_dispatchErrors > 5) + { + // this normally happens during a FSX crash or shutdown + this->disconnectFrom(); + } + return; + } + m_dispatchErrors = 0; + if (m_useFsuipc && m_fsuipc) + { + CSimulatedAircraft fsuipcAircraft(getOwnAircraft()); + //! \fixme split in high / low frequency reads + bool ok = m_fsuipc->read(fsuipcAircraft, true, true, true); + if (ok) + { + // do whatever is required + Q_UNUSED(fsuipcAircraft); + } + } + } + + bool CSimulatorFsxCommon::physicallyRemoveRemoteAircraft(const CCallsign &callsign) + { + // only remove from sim + Q_ASSERT_X(CThreadUtils::isCurrentThreadObjectThread(this), Q_FUNC_INFO, "wrong thread"); + if (callsign.isEmpty()) { return false; } // can happen if an object is not an aircraft + + m_outOfRealityBubble.removeByCallsign(callsign); + if (!m_simConnectObjects.contains(callsign)) { return false; } // already fully removed or not yet added + + CSimConnectObject &simObject = m_simConnectObjects[callsign]; + if (simObject.isPendingRemoved()) { return true; } + if (simObject.isPendingAdded()) + { + // problem: we try to delete an aircraft just requested to be added + return false; //! \fixme improve, since this scenario is not really covered + } + + simObject.setPendingRemoved(true); + if (m_interpolationRenderingSetup.showSimulatorDebugMessages()) + { + CLogMessage(this).debug() << "physicallyRemoveRemoteAircraft" << callsign.toQString(); + } + + // call in SIM + SimConnect_AIRemoveObject(m_hSimConnect, static_cast(simObject.getObjectId()), static_cast(m_requestIdSimData++)); + m_hints.remove(simObject.getCallsign()); + + // mark in provider + bool updated = updateAircraftRendered(callsign, false); + if (updated) + { + CSimulatedAircraft aircraft(simObject.getAircraft()); + aircraft.setRendered(false); + emit aircraftRenderingChanged(aircraft); + } + + // cleanup function, actually this should not be needed + QTimer::singleShot(100, this, &CSimulatorFsxCommon::ps_physicallyRemoveAircraftNotInProvider); + + // bye + return true; + } + + int CSimulatorFsxCommon::physicallyRemoveAllRemoteAircraft() + { + if (m_simConnectObjects.isEmpty()) { return 0; } + const QList callsigns(m_simConnectObjects.keys()); + int r = 0; + for (const CCallsign &cs : callsigns) + { + if (physicallyRemoveRemoteAircraft(cs)) { r++; } + } + clearAllAircraft(); + return r; + } + + HRESULT CSimulatorFsxCommon::initEvents() + { + HRESULT hr = S_OK; + // System events, see http://msdn.microsoft.com/en-us/library/cc526983.aspx#SimConnect_SubscribeToSystemEvent + hr += SimConnect_SubscribeToSystemEvent(m_hSimConnect, SystemEventSimStatus, "Sim"); + hr += SimConnect_SubscribeToSystemEvent(m_hSimConnect, SystemEventObjectAdded, "ObjectAdded"); + hr += SimConnect_SubscribeToSystemEvent(m_hSimConnect, SystemEventObjectRemoved, "ObjectRemoved"); + hr += SimConnect_SubscribeToSystemEvent(m_hSimConnect, SystemEventFrame, "Frame"); + hr += SimConnect_SubscribeToSystemEvent(m_hSimConnect, SystemEventPause, "Pause"); + hr += SimConnect_SubscribeToSystemEvent(m_hSimConnect, SystemEventFlightLoaded, "FlightLoaded"); + if (hr != S_OK) + { + CLogMessage(this).error("FSX plugin error: %1") << "SimConnect_SubscribeToSystemEvent failed"; + return hr; + } + + // Mapped events, see event ids here: http://msdn.microsoft.com/en-us/library/cc526980.aspx + hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventPauseToggle, "PAUSE_TOGGLE"); + hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, SystemEventSlewToggle, "SLEW_TOGGLE"); + hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventFreezeLat, "FREEZE_LATITUDE_LONGITUDE_SET"); + hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventFreezeAlt, "FREEZE_ALTITUDE_SET"); + hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventFreezeAtt, "FREEZE_ATTITUDE_SET"); + hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventSetCom1Active, "COM_RADIO_SET"); + hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventSetCom1Standby, "COM_STBY_RADIO_SET"); + hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventSetCom2Active, "COM2_RADIO_SET"); + hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventSetCom2Standby, "COM2_STBY_RADIO_SET"); + hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventSetTransponderCode, "XPNDR_SET"); + + hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventSetTimeZuluYear, "ZULU_YEAR_SET"); + hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventSetTimeZuluDay, "ZULU_DAY_SET"); + hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventSetTimeZuluHours, "ZULU_HOURS_SET"); + hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventSetTimeZuluMinutes, "ZULU_MINUTES_SET"); + + hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventLandingLightsOff, "LANDING_LIGHTS_OFF"); + hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventLandinglightsOn, "LANDING_LIGHTS_ON"); + hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventLandingLightsSet, "LANDING_LIGHTS_SET"); + hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventLandingLightsToggle, "LANDING_LIGHTS_TOGGLE"); + hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventPanelLightsOff, "PANEL_LIGHTS_OFF"); + hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventPanelLightsOn, "PANEL_LIGHTS_ON"); + hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventPanelLightsSet, "PANEL_LIGHTS_SET"); + hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventStrobesOff, "STROBES_OFF"); + hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventStrobesOn, "STROBES_ON"); + hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventStrobesSet, "STROBES_SET"); + hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventStrobesToggle, "STROBES_TOGGLE"); + hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventToggleBeaconLights, "TOGGLE_BEACON_LIGHTS"); + hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventToggleCabinLights, "TOGGLE_CABIN_LIGHTS"); + hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventToggleLogoLights, "TOGGLE_LOGO_LIGHTS"); + hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventToggleNavLights, "TOGGLE_NAV_LIGHTS"); + hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventToggleRecognitionLights, "TOGGLE_RECOGNITION_LIGHTS"); + hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventToggleTaxiLights, "TOGGLE_TAXI_LIGHTS"); + hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventToggleWingLights, "TOGGLE_WING_LIGHTS"); + + if (hr != S_OK) + { + CLogMessage(this).error("FSX plugin error: %1") << "SimConnect_MapClientEventToSimEvent failed"; + return hr; + } + + // facility + hr += SimConnect_SubscribeToFacilities(m_hSimConnect, SIMCONNECT_FACILITY_LIST_TYPE_AIRPORT, static_cast(m_requestIdSimData++)); + if (hr != S_OK) + { + CLogMessage(this).error("FSX plugin error: %1") << "SimConnect_SubscribeToFacilities failed"; + return hr; + } + return hr; + } + + HRESULT CSimulatorFsxCommon::initDataDefinitionsWhenConnected() + { + return CSimConnectDefinitions::initDataDefinitionsWhenConnected(m_hSimConnect); + } + + HRESULT CSimulatorFsxCommon::initWhenConnected() + { + // called when connected + + HRESULT hr = initEvents(); + if (hr != S_OK) + { + CLogMessage(this).error("FSX plugin: initEvents failed"); + return hr; + } + + // inti data definitions and SB data area + hr += initDataDefinitionsWhenConnected(); + if (hr != S_OK) + { + CLogMessage(this).error("FSX plugin: initDataDefinitionsWhenConnected failed"); + return hr; + } + + return hr; + } + + void CSimulatorFsxCommon::updateRemoteAircraft() + { + static_assert(sizeof(DataDefinitionRemoteAircraftParts) == sizeof(double) * 10, "DataDefinitionRemoteAircraftParts has an incorrect size."); + Q_ASSERT_X(CThreadUtils::isCurrentThreadObjectThread(this), Q_FUNC_INFO, "thread"); + + // nothing to do, reset request id and exit + if (this->isPaused() && this->m_pausedSimFreezesInterpolation) { return; } // no interpolation while paused + const int remoteAircraftNo = this->getAircraftInRangeCount(); + if (remoteAircraftNo < 1) { m_interpolationRequest = 0; return; } + + // interpolate and send to simulator + this->m_interpolationRequest++; + const bool enableAircraftParts = this->m_interpolationRenderingSetup.isAircraftPartsEnabled(); + const CCallsignSet aircraftWithParts = enableAircraftParts ? this->remoteAircraftSupportingParts() : CCallsignSet(); // optimization, fetch all parts supporting aircraft in one step (one lock) + + // values used for position and parts + const qint64 currentTimestamp = QDateTime::currentMSecsSinceEpoch(); + const QList simObjects(m_simConnectObjects.values()); + const CCallsignSet callsignsToLog(this->m_interpolationRenderingSetup.getLogCallsigns()); + + // interpolation for all remote aircraft + for (const CSimConnectObject &simObj : simObjects) + { + // happening if aircraft is not yet added to simulator or to be deleted + if (simObj.isPendingAdded()) { continue; } + if (simObj.isPendingRemoved()) { continue; } + + const CCallsign callsign(simObj.getCallsign()); + Q_ASSERT_X(!callsign.isEmpty(), Q_FUNC_INFO, "missing callsign"); + Q_ASSERT_X(simObj.hasValidRequestAndObjectId(), Q_FUNC_INFO, "Missing ids"); + + // fetch parts, as they are needed for ground interpolation + const bool useAircraftParts = enableAircraftParts && aircraftWithParts.contains(callsign); + const bool logInterpolationAndParts = callsignsToLog.contains(callsign); + CPartsStatus partsStatus; + partsStatus.setSupportsParts(useAircraftParts); + + const CInterpolationAndRenderingSetup setup(getInterpolationAndRenderingSetup()); + const CAircraftParts parts = useAircraftParts ? simObj.getInterpolator()->getInterpolatedParts(-1, setup, partsStatus, logInterpolationAndParts) : CAircraftParts(); + + // get interpolated situation + CInterpolationStatus interpolatorStatus; + CInterpolationHints hints(m_hints[simObj.getCallsign()]); + hints.setAircraftParts(useAircraftParts ? parts : CAircraftParts(), useAircraftParts); + hints.setLoggingInterpolation(logInterpolationAndParts); + const CAircraftSituation interpolatedSituation = simObj.getInterpolator()->getInterpolatedSituation(currentTimestamp, setup, hints, interpolatorStatus); + + if (interpolatorStatus.allTrue()) + { + // update situation + SIMCONNECT_DATA_INITPOSITION position = this->aircraftSituationToFsxPosition(interpolatedSituation); + HRESULT hr = S_OK; + hr += SimConnect_SetDataOnSimObject(m_hSimConnect, CSimConnectDefinitions::DataRemoteAircraftPosition, + static_cast(simObj.getObjectId()), 0, 0, + sizeof(SIMCONNECT_DATA_INITPOSITION), &position); + if (hr != S_OK) + { + CLogMessage(this).warning("Failed so set position on SimObject '%1' callsign: '%2'") << simObj.getObjectId() << callsign; + } + + } // interpolation data + + if (useAircraftParts) + { + this->updateRemoteAircraftParts(simObj, parts, partsStatus); + } + else + { + // guess on position, but not every frame + if (m_interpolationRequest % GuessRemoteAircraftPartsCycle == 0) + { + this->guessAndUpdateRemoteAircraftParts(simObj, interpolatedSituation, interpolatorStatus); + } + } + } // all callsigns + const qint64 dt = QDateTime::currentMSecsSinceEpoch() - currentTimestamp; + m_statsUpdateAircraftTimeTotalMs += dt; + m_statsUpdateAircraftCountMs++; + m_statsUpdateAircraftTimeAvgMs = m_statsUpdateAircraftTimeTotalMs / m_statsUpdateAircraftCountMs; + } + + bool CSimulatorFsxCommon::guessAndUpdateRemoteAircraftParts(const CSimConnectObject &simObj, const CAircraftSituation &interpolatedSituation, const CInterpolationStatus &interpolationStatus) + { + if (!simObj.hasValidRequestAndObjectId()) { return false; } + if (!interpolationStatus.didInterpolationSucceed()) { return false; } + + CAircraftLights lights; + DataDefinitionRemoteAircraftParts ddRemoteAircraftParts = {}; // init members + const bool isOnGround = interpolatedSituation.isOnGround() == CAircraftSituation::OnGround; + const double gsKts = interpolatedSituation.getGroundSpeed().value(CSpeedUnit::kts()); + ddRemoteAircraftParts.setAllEngines(true); + lights.setCabinOn(true); + lights.setRecognitionOn(true); + + // when first detected moving, lights on + if (isOnGround) + { + ddRemoteAircraftParts.gearHandlePosition = 1; + lights.setTaxiOn(true); + lights.setBeaconOn(true); + lights.setNavOn(true); + + if (gsKts > 5) + { + // mode taxi + lights.setTaxiOn(true); + lights.setLandingOn(false); + } + else if (gsKts > 30) + { + // mode accelaration for takeoff + lights.setTaxiOn(false); + lights.setLandingOn(true); + } + else + { + // slow movements or parking + lights.setTaxiOn(false); + lights.setLandingOn(false); + ddRemoteAircraftParts.setAllEngines(false); + } + } + else + { + // not on ground + ddRemoteAircraftParts.gearHandlePosition = 0; + lights.setTaxiOn(false); + lights.setBeaconOn(true); + lights.setNavOn(true); + // landing lights for < 10000ft (normally MSL, here ignored) + lights.setLandingOn(interpolatedSituation.getAltitude().value(CLengthUnit::ft()) < 10000); + + if (!simObj.isVtol() && interpolatedSituation.hasGroundElevation()) + { + if (interpolatedSituation.getHeightAboveGround().value(CLengthUnit::ft()) < 1000) + { + ddRemoteAircraftParts.gearHandlePosition = 1; + ddRemoteAircraftParts.flapsTrailingEdgeRightPercent = 25; + ddRemoteAircraftParts.flapsTrailingEdgeLeftPercent = 25; + } + else if (interpolatedSituation.getHeightAboveGround().value(CLengthUnit::ft()) < 2000) + { + ddRemoteAircraftParts.gearHandlePosition = 1; + ddRemoteAircraftParts.flapsTrailingEdgeRightPercent = 10; + ddRemoteAircraftParts.flapsTrailingEdgeLeftPercent = 10; + } + } + } + + return this->sendRemoteAircraftPartsToSimulator(simObj, ddRemoteAircraftParts, lights); + } + + bool CSimulatorFsxCommon::updateRemoteAircraftParts(const CSimConnectObject &simObj, const CAircraftParts &parts, const CPartsStatus &partsStatus) + { + if (!simObj.hasValidRequestAndObjectId()) { return false; } + if (!partsStatus.isSupportingParts()) { return false; } + + DataDefinitionRemoteAircraftParts ddRemoteAircraftParts; // no init, all values will be set + ddRemoteAircraftParts.flapsLeadingEdgeLeftPercent = parts.getFlapsPercent() / 100.0; + ddRemoteAircraftParts.flapsLeadingEdgeRightPercent = parts.getFlapsPercent() / 100.0; + ddRemoteAircraftParts.flapsTrailingEdgeLeftPercent = parts.getFlapsPercent() / 100.0; + ddRemoteAircraftParts.flapsTrailingEdgeRightPercent = parts.getFlapsPercent() / 100.0; + ddRemoteAircraftParts.spoilersHandlePosition = parts.isSpoilersOut() ? 1 : 0; + ddRemoteAircraftParts.gearHandlePosition = parts.isGearDown() ? 1 : 0; + ddRemoteAircraftParts.engine1Combustion = parts.isEngineOn(1) ? 1 : 0; + ddRemoteAircraftParts.engine2Combustion = parts.isEngineOn(2) ? 1 : 0; + ddRemoteAircraftParts.engine3Combustion = parts.isEngineOn(3) ? 1 : 0; + ddRemoteAircraftParts.engine4Combustion = parts.isEngineOn(4) ? 1 : 0; + + CAircraftLights lights = parts.getLights(); + lights.setRecognitionOn(parts.isAnyEngineOn()); + lights.setCabinOn(parts.isAnyEngineOn()); + + return this->sendRemoteAircraftPartsToSimulator(simObj, ddRemoteAircraftParts, parts.getLights()); + } + + bool CSimulatorFsxCommon::sendRemoteAircraftPartsToSimulator(const CSimConnectObject &simObj, DataDefinitionRemoteAircraftParts &ddRemoteAircraftParts, const CAircraftLights &lights) + { + Q_ASSERT(m_hSimConnect); + const DWORD objectId = simObj.getObjectId(); + + // same as in simulator or same as already send to simulator? + const CAircraftLights sentLights(simObj.getLightsAsSent()); + if (simObj.getPartsAsSent() == ddRemoteAircraftParts && sentLights == lights) { return true; } + + // in case we sent, we sent everything + const HRESULT hr = SimConnect_SetDataOnSimObject(m_hSimConnect, CSimConnectDefinitions::DataRemoteAircraftParts, + objectId, SIMCONNECT_DATA_SET_FLAG_DEFAULT, 0, + sizeof(DataDefinitionRemoteAircraftParts), &ddRemoteAircraftParts); + + if (hr == S_OK) + { + // Update data + CSimConnectObject &objUdpate = m_simConnectObjects[simObj.getCallsign()]; + objUdpate.setPartsAsSent(ddRemoteAircraftParts); + } + else + { + CLogMessage(this).warning("Failed so set parts on SimObject '%1' callsign: '%2'") << simObj.getObjectId() << simObj.getCallsign(); + } + + // lights we can set directly + SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventLandingLightsSet, lights.isLandingOn() ? 1.0 : 0.0, SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY); + SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventStrobesSet, lights.isStrobeOn() ? 1.0 : 0.0, SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY); + + // lights we need to toggle (risk with quickly changing values that we accidentally toggle back) + sendToggledLightsToSimulator(simObj, lights); + + // done + return hr == S_OK; + } + + void CSimulatorFsxCommon::sendToggledLightsToSimulator(const CSimConnectObject &simObj, const CAircraftLights &lightsWanted, bool force) + { + if (!simObj.hasValidRequestAndObjectId()) { return; } // stale + const CAircraftLights lightsIsState = simObj.getCurrentLightsInSimulator(); + if (lightsWanted == lightsIsState) { return; } + if (!force && lightsWanted == simObj.getLightsAsSent()) { return; } + const CCallsign callsign(simObj.getCallsign()); + + // Update data + CSimConnectObject &simObjToUpdate = m_simConnectObjects[callsign]; + simObjToUpdate.setLightsAsSent(lightsWanted); + + // state available, then I can toggle + if (!lightsIsState.isNull()) + { + const DWORD objectId = simObj.getObjectId(); + if (lightsWanted.isTaxiOn() != lightsIsState.isTaxiOn()) + { + SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventToggleTaxiLights, 0.0, SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY); + } + if (lightsWanted.isNavOn() != lightsIsState.isNavOn()) + { + SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventToggleNavLights, 0.0, SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY); + } + if (lightsWanted.isBeaconOn() != lightsIsState.isBeaconOn()) + { + SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventToggleBeaconLights, 0.0, SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY); + } + if (lightsWanted.isLogoOn() != lightsIsState.isLogoOn()) + { + SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventToggleLogoLights, 0.0, SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY); + } + if (lightsWanted.isRecognitionOn() != lightsIsState.isRecognitionOn()) + { + SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventToggleRecognitionLights, 0.0, SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY); + } + if (lightsWanted.isCabinOn() != lightsIsState.isCabinOn()) + { + SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventToggleCabinLights, 0.0, SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY); + } + return; + } + + // missing lights info from simulator + CLogMessage(this).info("Missing light state for '%1'") << callsign; + QTimer::singleShot(2500, this, [ = ] + { + const CSimConnectObject currentSimObj = m_simConnectObjects[callsign]; + if (!currentSimObj.hasValidRequestAndObjectId()) { return; } // stale + if (lightsWanted != currentSimObj.getLightsAsSent()) { return; } // changed in between + sendToggledLightsToSimulator(currentSimObj, lightsWanted, true); + }); + } + + SIMCONNECT_DATA_INITPOSITION CSimulatorFsxCommon::aircraftSituationToFsxPosition(const CAircraftSituation &situation) + { + SIMCONNECT_DATA_INITPOSITION position; + position.Latitude = situation.latitude().value(CAngleUnit::deg()); + position.Longitude = situation.longitude().value(CAngleUnit::deg()); + position.Altitude = situation.getAltitude().value(CLengthUnit::ft()); // already corrected in interpolator if there is an underflow + position.Heading = situation.getHeading().value(CAngleUnit::deg()); + position.Airspeed = situation.getGroundSpeed().value(CSpeedUnit::kts()); + + // MSFS has inverted pitch and bank angles + position.Pitch = -situation.getPitch().value(CAngleUnit::deg()); + position.Bank = -situation.getBank().value(CAngleUnit::deg()); + position.OnGround = 0U; + + if (situation.isOnGroundInfoAvailable()) + { + const bool onGround = situation.isOnGround() == CAircraftSituation::OnGround; + position.OnGround = onGround ? 1U : 0U; + } + return position; + } + + void CSimulatorFsxCommon::synchronizeTime(const CTime &zuluTimeSim, const CTime &localTimeSim) + { + if (!this->m_simTimeSynced) { return; } + if (!this->isConnected()) { return; } + if (m_syncDeferredCounter > 0) + { + --m_syncDeferredCounter; + } + Q_UNUSED(localTimeSim); + + QDateTime myDateTime = QDateTime::currentDateTimeUtc(); + if (!this->m_syncTimeOffset.isZeroEpsilonConsidered()) + { + int offsetSeconds = this->m_syncTimeOffset.valueRounded(CTimeUnit::s(), 0); + myDateTime = myDateTime.addSecs(offsetSeconds); + } + const QTime myTime = myDateTime.time(); + const DWORD h = static_cast(myTime.hour()); + const DWORD m = static_cast(myTime.minute()); + const int targetMins = myTime.hour() * 60 + myTime.minute(); + const int simMins = zuluTimeSim.valueRounded(CTimeUnit::min()); + const int diffMins = qAbs(targetMins - simMins); + if (diffMins < 2) { return; } + HRESULT hr = S_OK; + hr += SimConnect_TransmitClientEvent(m_hSimConnect, 0, EventSetTimeZuluHours, h, SIMCONNECT_GROUP_PRIORITY_STANDARD, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY); + hr += SimConnect_TransmitClientEvent(m_hSimConnect, 0, EventSetTimeZuluMinutes, m, SIMCONNECT_GROUP_PRIORITY_STANDARD, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY); + + if (hr != S_OK) + { + CLogMessage(this).warning("Sending time sync failed!"); + } + else + { + m_syncDeferredCounter = 5; // allow some time to sync + CLogMessage(this).info("Synchronized time to UTC: '%1'") << myTime.toString(); + } + } + + void CSimulatorFsxCommon::injectWeatherGrid(const Weather::CWeatherGrid &weatherGrid) + { + // So far, there is only global weather + auto glob = weatherGrid.frontOrDefault(); + glob.setIdentifier("GLOB"); + QString metar = CSimConnectUtilities::convertToSimConnectMetar(glob); + SimConnect_WeatherSetModeCustom(m_hSimConnect); + SimConnect_WeatherSetModeGlobal(m_hSimConnect); + SimConnect_WeatherSetObservation(m_hSimConnect, 0, qPrintable(metar)); + } + + bool CSimulatorFsxCommon::requestDataForSimObject(const CSimConnectObject &simObject, SIMCONNECT_PERIOD period) + { + if (!simObject.hasValidRequestAndObjectId()) { return false; } + if (simObject.getSimDataPeriod() == period) { return true; } // already queried like this + + // always request, not only when something has changed + const HRESULT result = SimConnect_RequestDataOnSimObject( + m_hSimConnect, simObject.getRequestId() + RequestSimDataOffset, + CSimConnectDefinitions::DataRemoteAircraftSimData, + simObject.getObjectId(), period); + if (result == S_OK) + { + m_simConnectObjects[simObject.getCallsign()].setSimDataPeriod(period); + return true; + } + CLogMessage(this).error("Cannot request sim data on object '%1'") << simObject.getObjectId(); + return false; + } + + bool CSimulatorFsxCommon::requestLightsForSimObject(const CSimConnectObject &simObject) + { + if (!simObject.hasValidRequestAndObjectId()) { return false; } + + // always request, not only when something has changed + const HRESULT result = SimConnect_RequestDataOnSimObject( + m_hSimConnect, simObject.getRequestId() + RequestLightsOffset, + CSimConnectDefinitions::DataRemoteAircraftLights, simObject.getObjectId(), + SIMCONNECT_PERIOD_SECOND); + if (result == S_OK) { return true; } + CLogMessage(this).error("Cannot request lights data on object '%1'") << simObject.getObjectId(); + return false; + } + + void CSimulatorFsxCommon::initSimulatorInternals() + { + CSimulatorFsCommon::initSimulatorInternals(); + CSimulatorInternals s = this->m_simulatorInternals; + const QString fsxPath = CFsCommonUtil::fsxDirFromRegistry(); // can be empty for remote FSX + if (!fsxPath.isEmpty()) { s.setSimulatorInstallationDirectory(fsxPath); } + + s.setValue("fsx/simConnectCfgFilename", CSimConnectUtilities::getLocalSimConnectCfgFilename()); + s.setValue("fsx/simConnectVersion", this->m_simConnectVersion); + this->m_simulatorInternals = s; + } + + void CSimulatorFsxCommon::reset() + { + if (m_simConnectTimerId >= 0) { killTimer(m_simConnectTimerId); } + m_simConnectTimerId = -1; + m_simConnected = false; + m_simSimulating = false; + m_syncDeferredCounter = 0; + m_skipCockpitUpdateCycles = 0; + m_interpolationRequest = 0; + m_requestIdSimData = RequestSimDataStart; + m_dispatchErrors = 0; + m_receiveExceptionCount = 0; + CSimulatorFsCommon::reset(); + } + + void CSimulatorFsxCommon::clearAllAircraft() + { + m_simConnectObjects.clear(); + m_outOfRealityBubble.clear(); + CSimulatorFsCommon::clearAllAircraft(); + } + + void CSimulatorFsxCommon::ps_remoteProviderAddAircraftSituation(const CAircraftSituation &situation) + { + if (!m_simConnectObjects.contains(situation.getCallsign())) { return; } + m_simConnectObjects[situation.getCallsign()].getInterpolator()->addAircraftSituation(situation); + } + + void CSimulatorFsxCommon::ps_remoteProviderAddAircraftParts(const BlackMisc::Aviation::CCallsign &callsign, const CAircraftParts &parts) + { + if (!m_simConnectObjects.contains(callsign)) { return; } + m_simConnectObjects[callsign].getInterpolator()->addAircraftParts(parts); + } + + QString CSimulatorFsxCommon::fsxPositionToString(const SIMCONNECT_DATA_INITPOSITION &position) + { + const QString positionStr("Lat: %1 lng: %2 alt: %3ft pitch: %4 bank: %5 hdg: %6 airspeed: %7kts onGround: %8"); + return positionStr. + arg(position.Latitude).arg(position.Longitude).arg(position.Altitude). + arg(position.Pitch).arg(position.Bank).arg(position.Heading).arg(position.Airspeed).arg(position.OnGround); + } + + CCallsignSet CSimulatorFsxCommon::getCallsignsMissingInProvider() const + { + CCallsignSet simObjectCallsigns(m_simConnectObjects.keys()); + CCallsignSet providerCallsigns(this->getAircraftInRangeCallsigns()); + return simObjectCallsigns.difference(providerCallsigns); + } + + CCallsignSet CSimulatorFsxCommon::ps_physicallyRemoveAircraftNotInProvider() + { + const CCallsignSet toBeRemoved(getCallsignsMissingInProvider()); + if (toBeRemoved.isEmpty()) { return toBeRemoved; } + for (const CCallsign &callsign : toBeRemoved) + { + physicallyRemoveRemoteAircraft(callsign); + } + return toBeRemoved; + } + + CSimulatorFsxCommonListener::CSimulatorFsxCommonListener(const CSimulatorPluginInfo &info) : + ISimulatorListener(info), + m_timer(new QTimer(this)) + { + constexpr int QueryInterval = 5 * 1000; // 5 seconds + m_timer->setInterval(QueryInterval); + m_timer->setObjectName(this->objectName().append(":m_timer")); + connect(m_timer, &QTimer::timeout, this, &CSimulatorFsxCommonListener::ps_checkConnection); + } + + void CSimulatorFsxCommonListener::start() + { + m_timer->start(); + } + + void CSimulatorFsxCommonListener::stop() + { + m_timer->stop(); + } + + void CSimulatorFsxCommonListener::ps_checkConnection() + { + Q_ASSERT_X(!CThreadUtils::isCurrentThreadApplicationThread(), Q_FUNC_INFO, "Expect to run in background"); + HANDLE hSimConnect; + HRESULT result = SimConnect_Open(&hSimConnect, sApp->swiftVersionChar(), nullptr, 0, 0, 0); + SimConnect_Close(hSimConnect); + if (result == S_OK) + { + emit simulatorStarted(this->getPluginInfo()); + } + } + } // namespace +} // namespace diff --git a/src/plugins/simulator/fsxcommon/simulatorfsxcommon.h b/src/plugins/simulator/fsxcommon/simulatorfsxcommon.h new file mode 100644 index 000000000..33ff42558 --- /dev/null +++ b/src/plugins/simulator/fsxcommon/simulatorfsxcommon.h @@ -0,0 +1,311 @@ +/* Copyright (C) 2013 + * swift Project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project, + * including this file, may be copied, modified, propagated, or distributed except according to the terms + * contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKSIMPLUGIN_FSXCOMMON_SIMULATORFSXCOMMON_H +#define BLACKSIMPLUGIN_FSXCOMMON_SIMULATORFSXCOMMON_H + +#include "simconnectdatadefinition.h" +#include "simconnectobject.h" +#include "../fscommon/simulatorfscommon.h" +#include "blackcore/simulator.h" +#include "blackmisc/simulation/interpolatorlinear.h" +#include "blackmisc/simulation/simulatorplugininfo.h" +#include "blackmisc/simulation/simulatorsettings.h" +#include "blackmisc/simulation/aircraftmodel.h" +#include "blackmisc/simulation/simulatedaircraft.h" +#include "blackmisc/aviation/airportlist.h" +#include "blackmisc/statusmessage.h" +#include "blackmisc/network/client.h" +#include "blackmisc/pixmap.h" + +#include +#include +#include +#include +#include + +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include + +namespace BlackSimPlugin +{ + namespace FsxCommon + { + //! SimConnect Event IDs + enum EventIds + { + SystemEventSimStatus, + SystemEventObjectAdded, + SystemEventObjectRemoved, + SystemEventSlewToggle, + SystemEventFrame, + SystemEventPause, + SystemEventFlightLoaded, + EventPauseToggle, + EventFreezeLat, + EventFreezeAlt, + EventFreezeAtt, + EventSetCom1Active, + EventSetCom2Active, + EventSetCom1Standby, + EventSetCom2Standby, + EventSetTransponderCode, + EventTextMessage, + EventSetTimeZuluYear, + EventSetTimeZuluDay, + EventSetTimeZuluHours, + EventSetTimeZuluMinutes, + // ------------ lights ------------- + EventLandingLightsOff, + EventLandinglightsOn, + EventLandingLightsSet, + EventLandingLightsToggle, + EventPanelLightsOff, + EventPanelLightsOn, + EventPanelLightsSet, + EventStrobesOff, + EventStrobesOn, + EventStrobesSet, + EventStrobesToggle, + EventToggleBeaconLights, + EventToggleCabinLights, + EventToggleLogoLights, + EventToggleNavLights, + EventToggleRecognitionLights, + EventToggleTaxiLights, + EventToggleWingLights + }; + + //! FSX Simulator Implementation + class CSimulatorFsxCommon : public BlackSimPlugin::FsCommon::CSimulatorFsCommon + { + Q_OBJECT + + public: + //! Constructor, parameters as in \sa BlackCore::ISimulatorFactory::create + CSimulatorFsxCommon( + const BlackMisc::Simulation::CSimulatorPluginInfo &info, + BlackMisc::Simulation::IOwnAircraftProvider *ownAircraftProvider, + BlackMisc::Simulation::IRemoteAircraftProvider *remoteAircraftProvider, + BlackMisc::Weather::IWeatherGridProvider *weatherGridProvider, + QObject *parent = nullptr); + + //! Destructor + virtual ~CSimulatorFsxCommon(); + + //! SimConnect Callback + static void CALLBACK SimConnectProc(SIMCONNECT_RECV *pData, DWORD cbData, void *pContext); + + //! \name ISimulator implementations + //! @{ + virtual bool connectTo() override; + virtual bool disconnectFrom() override; + virtual bool physicallyAddRemoteAircraft(const BlackMisc::Simulation::CSimulatedAircraft &newRemoteAircraft) override; + virtual bool physicallyRemoveRemoteAircraft(const BlackMisc::Aviation::CCallsign &callsign) override; + virtual int physicallyRemoveAllRemoteAircraft() override; + virtual bool updateOwnSimulatorCockpit(const BlackMisc::Simulation::CSimulatedAircraft &ownAircraft, const BlackMisc::CIdentifier &originator) override; + virtual void displayStatusMessage(const BlackMisc::CStatusMessage &message) const override; + virtual void displayTextMessage(const BlackMisc::Network::CTextMessage &message) const override; + virtual bool isPhysicallyRenderedAircraft(const BlackMisc::Aviation::CCallsign &callsign) const override; + virtual BlackMisc::Aviation::CCallsignSet physicallyRenderedAircraft() const override; + //! @} + + protected: + //! \name Interface implementations + //! @{ + virtual bool isConnected() const override; + virtual bool isSimulating() const override; + //! @} + + //! \name Base class overrides + //! @{ + virtual void reset() override; + virtual void clearAllAircraft() override; + virtual void initSimulatorInternals() override; + virtual void injectWeatherGrid(const BlackMisc::Weather::CWeatherGrid &weatherGrid) override; + virtual void ps_remoteProviderAddAircraftSituation(const BlackMisc::Aviation::CAircraftSituation &situation) override; + virtual void ps_remoteProviderAddAircraftParts(const BlackMisc::Aviation::CCallsign &callsign, const BlackMisc::Aviation::CAircraftParts &parts) override; + //! @} + + //! Timer event (our SimConnect event loop), runs ps_dispatch + //! \sa m_simconnectTimerId + virtual void timerEvent(QTimerEvent *event) override; + + private slots: + //! Dispatch SimConnect messages + void ps_dispatch(); + + //! Remove aircraft not in provider anymore + //! \remark kind of cleanup function, in an ideal this should never need to cleanup something + BlackMisc::Aviation::CCallsignSet ps_physicallyRemoveAircraftNotInProvider(); + + //! Handle that an object has been added in simulator + //! \remark checks if the object was really added after an add request and not directly removed again + bool ps_deferredSimulatorReportedObjectAdded(const BlackMisc::Aviation::CCallsign &callsign); + + //! Try to add the aircraft currently out of bubble + void ps_addAircraftCurrentlyOutOfBubble(); + + private: + //! Call this method to declare the simulator connected + void setSimConnected(); + + //! Called when sim has started + void onSimRunning(); + + //! Slot called every visual frame + void onSimFrame(); + + //! Called when simulator has stopped, e.g. by selecting the "select aircraft screen" + void onSimStopped(); + + //! Simulator is going down + void onSimExit(); + + //! Get new request id, overflow safe + DWORD obtainRequestIdSimData(); + + //! Init when connected + HRESULT initWhenConnected(); + + //! Initialize SimConnect system events + HRESULT initEvents(); + + //! Initialize SimConnect data definitions + HRESULT initDataDefinitionsWhenConnected(); + + //! Update remote aircraft + void updateRemoteAircraft(); + + //! Update remote aircraft parts (send to FSX) + bool updateRemoteAircraftParts(const CSimConnectObject &simObj, + const BlackMisc::Aviation::CAircraftParts &parts, const BlackMisc::Simulation::CPartsStatus &partsStatus); + + //! Update remote aircraft parts by guessing (send to FSX) + bool guessAndUpdateRemoteAircraftParts(const CSimConnectObject &simObj, + const BlackMisc::Aviation::CAircraftSituation &interpolatedSituation, const BlackMisc::Simulation::CInterpolationStatus &interpolationStatus); + + //! Send parts to simulator + bool sendRemoteAircraftPartsToSimulator(const CSimConnectObject &simObj, DataDefinitionRemoteAircraftParts &ddRemoteAircraftParts, const BlackMisc::Aviation::CAircraftLights &lights); + + //! Send lights to simulator (those which have to be toggled) + //! \remark challenge here is that I can only sent those value if I have already obtained the current light state from simulator + void sendToggledLightsToSimulator(const CSimConnectObject &simObj, const BlackMisc::Aviation::CAircraftLights &lightsWanted, bool force = false); + + //! Called when data about our own aircraft are received + void updateOwnAircraftFromSimulator(const DataDefinitionOwnAircraft &simulatorOwnAircraft); + + //! Remote aircraft data sent from simulator + void updateRemoteAircraftFromSimulator(const CSimConnectObject &simObject, const DataDefinitionRemoteAircraftSimData &remoteAircraftData); + + //! Update from SB client area + void updateOwnAircraftFromSimulator(const DataDefinitionClientAreaSb &sbDataArea); + + //! An AI aircraft was added in the simulator + bool simulatorReportedObjectAdded(DWORD objectID); + + //! Simulator reported that AI aircraft was removed + bool simulatorReportedObjectRemoved(DWORD objectID); + + //! Set ID of a SimConnect object, so far we only have an request id in the object + bool setSimConnectObjectId(DWORD requestID, DWORD objectID); + + //! Remember current lights + bool setCurrentLights(const BlackMisc::Aviation::CCallsign &callsign, const BlackMisc::Aviation::CAircraftLights &lights); + + //! Remember lights sent + bool setLightsAsSent(const BlackMisc::Aviation::CCallsign &callsign, const BlackMisc::Aviation::CAircraftLights &lights); + + //! Display receive exceptions? + bool stillDisplayReceiveExceptions(); + + //! The simconnect related objects + const CSimConnectObjects &getSimConnectObjects() const { return m_simConnectObjects; } + + //! Format conversion + SIMCONNECT_DATA_INITPOSITION aircraftSituationToFsxPosition(const BlackMisc::Aviation::CAircraftSituation &situation); + + //! Sync time with user's computer + void synchronizeTime(const BlackMisc::PhysicalQuantities::CTime &zuluTimeSim, const BlackMisc::PhysicalQuantities::CTime &localTimeSim); + + //! Request data for a simObject (aka remote aircraft) + bool requestDataForSimObject(const CSimConnectObject &simObject, SIMCONNECT_PERIOD period = SIMCONNECT_PERIOD_SECOND); + + //! Request lights for a simObject + bool requestLightsForSimObject(const CSimConnectObject &simObject); + + //! FSX position as string + static QString fsxPositionToString(const SIMCONNECT_DATA_INITPOSITION &position); + + //! Get the callsigns which are no longer in the provider, but still in m_simConnectObjects + BlackMisc::Aviation::CCallsignSet getCallsignsMissingInProvider() const; + + //! Request for sim data? + static bool isRequestForSimData(DWORD requestId) { return requestId >= (RequestSimDataStart + RequestSimDataOffset) && requestId < (RequestSimDataStart + RequestSimDataOffset + SimObjectNumber); } + + //! Request for sim data? + static bool isRequestForLights(DWORD requestId) { return requestId >= (RequestSimDataStart + RequestLightsOffset) && requestId < (RequestSimDataStart + RequestLightsOffset + SimObjectNumber); } + + static constexpr int GuessRemoteAircraftPartsCycle = 20; //!< guess every n-th cycle + static constexpr int SkipUpdateCyclesForCockpit = 10; //!< skip x cycles before updating cockpit again + static constexpr int IgnoreReceiveExceptions = 10; //!< skip exceptions when displayed more than x times + static constexpr int SimObjectNumber = 10000; //!< max. SimObjects at the same time + static constexpr int RequestSimDataStart = static_cast(CSimConnectDefinitions::RequestEndMarker); + static constexpr int RequestSimDataEnd = RequestSimDataStart + SimObjectNumber - 1; + static constexpr int RequestSimDataOffset = 0 * SimObjectNumber; + static constexpr int RequestLightsOffset = 1 * SimObjectNumber; + + QString m_simConnectVersion; //!< SimConnect version + bool m_simConnected = false; //!< Is simulator connected? + bool m_simSimulating = false; //!< Simulator running? + bool m_useSbOffsets = true; //!< with SB offsets + int m_syncDeferredCounter = 0; //!< Set when synchronized, used to wait some time + int m_simConnectTimerId = -1; //!< Timer identifier + int m_skipCockpitUpdateCycles = 0; //!< skip some update cycles to allow changes in simulator cockpit to be set + int m_interpolationRequest = 0; //!< current interpolation request + int m_dispatchErrors = 0; //!< number of dispatched failed, \sa ps_dispatch + int m_receiveExceptionCount = 0; //!< exceptions + HANDLE m_hSimConnect = nullptr; //!< handle to SimConnect object + CSimConnectObjects m_simConnectObjects; //!< AI objects and their object / request ids + QTimer m_realityBubbleTimer { this }; //!< updating of aircraft out of reality bubble + DWORD m_requestIdSimData = RequestSimDataStart; //!< request id, use obtainRequestId() to get id + BlackMisc::Simulation::CSimulatedAircraftList m_outOfRealityBubble; //!< aircraft removed by FSX because they are out of reality bubble + }; + + //! Listener for FSX + class CSimulatorFsxCommonListener : public BlackCore::ISimulatorListener + { + Q_OBJECT + + public: + //! Constructor + CSimulatorFsxCommonListener(const BlackMisc::Simulation::CSimulatorPluginInfo &info); + + public slots: + //! \copydoc BlackCore::ISimulatorListener::start + virtual void start() override; + + //! \copydoc BlackCore::ISimulatorListener::stop + virtual void stop() override; + + protected slots: + //! Test if connection can be established + void ps_checkConnection(); + + private: + QTimer *m_timer { nullptr }; + }; + } +} // namespace + +#endif // guard diff --git a/src/plugins/simulator/fsxcommon/simulatorfsxconfigwindow.cpp b/src/plugins/simulator/fsxcommon/simulatorfsxconfigwindow.cpp new file mode 100644 index 000000000..35c1f92b5 --- /dev/null +++ b/src/plugins/simulator/fsxcommon/simulatorfsxconfigwindow.cpp @@ -0,0 +1,34 @@ +/* Copyright (C) 2015 + * swift project Community / Contributors + * + * This file is part of swift Project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project, + * including this file, may be copied, modified, propagated, or distributed except according to the terms + * contained in the LICENSE file. + */ + +#include "simulatorfsxconfigwindow.h" +#include "ui_simulatorfsxconfigwindow.h" + +using namespace BlackGui; + +namespace BlackSimPlugin +{ + namespace FsxCommon + { + CSimulatorFsxConfigWindow::CSimulatorFsxConfigWindow(const QString &simulator, QWidget *parent) : + CPluginConfigWindow(parent), + m_simulator(simulator), + ui(new Ui::CSimulatorFsxConfigWindow) + { + ui->setupUi(this); + connect(ui->bb_OkCancel, &QDialogButtonBox::rejected, this, &QWidget::close); + this->setWindowTitle(m_simulator + " plugin configuration"); + } + + CSimulatorFsxConfigWindow::~CSimulatorFsxConfigWindow() + { + // void + } + } +} diff --git a/src/plugins/simulator/fsxcommon/simulatorfsxconfigwindow.h b/src/plugins/simulator/fsxcommon/simulatorfsxconfigwindow.h new file mode 100644 index 000000000..e180bf1e0 --- /dev/null +++ b/src/plugins/simulator/fsxcommon/simulatorfsxconfigwindow.h @@ -0,0 +1,44 @@ +/* Copyright (C) 2015 + * swift project Community / Contributors + * + * This file is part of swift Project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project, + * including this file, may be copied, modified, propagated, or distributed except according to the terms + * contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKSIMPLUGIN_FSXCOMMON_SIMULATORFSXCONFIGWINDOW_H +#define BLACKSIMPLUGIN_FSXCOMMON_SIMULATORFSXCONFIGWINDOW_H + +#include "blackgui/pluginconfigwindow.h" +#include + +namespace Ui { class CSimulatorFsxConfigWindow; } +namespace BlackSimPlugin +{ + namespace FsxCommon + { + /** + * A window that lets user set up the FSX plugin. + */ + class CSimulatorFsxConfigWindow : public BlackGui::CPluginConfigWindow + { + Q_OBJECT + + public: + //! Ctor. + CSimulatorFsxConfigWindow(const QString &simulator, QWidget *parent); + + //! Dtor. + virtual ~CSimulatorFsxConfigWindow(); + + private: + QString m_simulator { "FSX" }; + QScopedPointer ui; + }; + } +} + +#endif // guard diff --git a/src/plugins/simulator/fsxcommon/simulatorfsxconfigwindow.ui b/src/plugins/simulator/fsxcommon/simulatorfsxconfigwindow.ui new file mode 100644 index 000000000..02bdf8a98 --- /dev/null +++ b/src/plugins/simulator/fsxcommon/simulatorfsxconfigwindow.ui @@ -0,0 +1,46 @@ + + + CSimulatorFsxConfigWindow + + + + 0 + 0 + 400 + 300 + + + + FSX plugin settings + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + QDialogButtonBox::Close + + + + + + + + BlackSimPlugin::FsxCommon::CSimConnectSettingsComponent + QFrame +
simconnectsettingscomponent.h
+ 1 +
+
+ + +
diff --git a/src/plugins/simulator/fsxcommon/simulatorfsxsimconnectproc.cpp b/src/plugins/simulator/fsxcommon/simulatorfsxsimconnectproc.cpp new file mode 100644 index 000000000..ad9ded610 --- /dev/null +++ b/src/plugins/simulator/fsxcommon/simulatorfsxsimconnectproc.cpp @@ -0,0 +1,316 @@ +/* Copyright (C) 2013 + * swift Project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project, + * including this file, may be copied, modified, propagated, or distributed except according to the terms + * contained in the LICENSE file. + */ + +#include "simulatorfsxcommon.h" +#include "blackcore/application.h" +#include "simconnectdatadefinition.h" +#include "blackmisc/simulation/fscommon/bcdconversions.h" +#include "blackmisc/simulation/fsx/simconnectutilities.h" +#include "blackmisc/simulation/simulatorplugininfo.h" +#include "blackmisc/aviation/airportlist.h" +#include "blackmisc/logmessage.h" + +using namespace BlackCore; +using namespace BlackMisc; +using namespace BlackMisc::Simulation; +using namespace BlackMisc::Aviation; +using namespace BlackMisc::PhysicalQuantities; +using namespace BlackMisc::Geo; +using namespace BlackMisc::Network; +using namespace BlackMisc::Simulation; +using namespace BlackMisc::Simulation::FsCommon; +using namespace BlackMisc::Simulation::Fsx; + +namespace BlackSimPlugin +{ + namespace FsxCommon + { + void CALLBACK CSimulatorFsxCommon::SimConnectProc(SIMCONNECT_RECV *pData, DWORD cbData, void *pContext) + { + CSimulatorFsxCommon *simulatorFsx = static_cast(pContext); + switch (pData->dwID) + { + case SIMCONNECT_RECV_ID_OPEN: + { + SIMCONNECT_RECV_OPEN *event = (SIMCONNECT_RECV_OPEN *)pData; + simulatorFsx->m_simulatorVersion = QString("%1.%2.%3.%4").arg(event->dwApplicationVersionMajor).arg(event->dwApplicationVersionMinor).arg(event->dwApplicationBuildMajor).arg(event->dwApplicationBuildMinor); + simulatorFsx->m_simConnectVersion = QString("%1.%2.%3.%4").arg(event->dwSimConnectVersionMajor).arg(event->dwSimConnectVersionMinor).arg(event->dwSimConnectBuildMajor).arg(event->dwSimConnectBuildMinor); + simulatorFsx->m_simulatorName = QString(event->szApplicationName); + simulatorFsx->m_simulatorDetails = QString("Open: AppName=\"%1\" AppVersion=%2 SimConnectVersion=%3").arg(simulatorFsx->m_simulatorName, simulatorFsx->m_simulatorVersion, simulatorFsx->m_simConnectVersion); + CLogMessage(static_cast(nullptr)).info("Connect to FSX: %1") << sApp->swiftVersionString(); + simulatorFsx->setSimConnected(); + break; + } + case SIMCONNECT_RECV_ID_EXCEPTION: + { + if (!simulatorFsx->stillDisplayReceiveExceptions()) { break; } + SIMCONNECT_RECV_EXCEPTION *exception = (SIMCONNECT_RECV_EXCEPTION *)pData; + const DWORD exceptionId = exception->dwException; + const DWORD sendId = exception->dwSendID; + const DWORD index = exception->dwIndex; + const DWORD data = cbData; + const QString exStr(CSimConnectUtilities::simConnectExceptionToString((SIMCONNECT_EXCEPTION)exception->dwException)); + QString ex; + ex.sprintf("Exception=%lu | SendID=%lu | Index=%lu | cbData=%lu", exceptionId, sendId, index, data); + switch (exceptionId) + { + case SIMCONNECT_EXCEPTION_OPERATION_INVALID_FOR_OBJECT_TYPE: + break; + case SIMCONNECT_EXCEPTION_UNRECOGNIZED_ID: + break; + default: + break; + } + CLogMessage(simulatorFsx).warning("Caught FSX simConnect exception: %1 %2") << exStr << ex; + break; + } + case SIMCONNECT_RECV_ID_QUIT: + { + simulatorFsx->onSimExit(); + break; + } + case SIMCONNECT_RECV_ID_EVENT: + { + SIMCONNECT_RECV_EVENT *event = static_cast(pData); + switch (event->uEventID) + { + case SystemEventSimStatus: + { + const bool running = event->dwData ? true : false; + if (running) + { + simulatorFsx->onSimRunning(); + } + else + { + simulatorFsx->onSimStopped(); + } + break; + } + case SystemEventPause: + { + const bool p = event->dwData ? true : false; + if (simulatorFsx->m_simPaused != p) + { + simulatorFsx->m_simPaused = p; + simulatorFsx->emitSimulatorCombinedStatus(); + } + break; + } + default: + break; + } + break; + } + case SIMCONNECT_RECV_ID_EVENT_OBJECT_ADDREMOVE: + { + const SIMCONNECT_RECV_EVENT_OBJECT_ADDREMOVE *event = static_cast(pData); + const DWORD objectID = event->dwData; + const SIMCONNECT_SIMOBJECT_TYPE objectType = event->eObjType; + if (objectType != SIMCONNECT_SIMOBJECT_TYPE_AIRCRAFT && objectType != SIMCONNECT_SIMOBJECT_TYPE_HELICOPTER) + { + break; + } + + // such an object is not necessarily one of ours + // for instance, I always see object 5 when I start the simulator + if (!simulatorFsx->getSimConnectObjects().isKnownSimObjectId(objectID)) break; + switch (event->uEventID) + { + case SystemEventObjectAdded: + // added in SIMCONNECT_RECV_ID_ASSIGNED_OBJECT_ID + break; + case SystemEventObjectRemoved: + simulatorFsx->simulatorReportedObjectRemoved(objectID); + break; + default: + break; + } + break; + } + case SIMCONNECT_RECV_ID_EVENT_FRAME: + { + SIMCONNECT_RECV_EVENT_FRAME *event = (SIMCONNECT_RECV_EVENT_FRAME *) pData; + switch (event->uEventID) + { + case SystemEventFrame: + // doing interpolation + simulatorFsx->onSimFrame(); + break; + default: + break; + } + break; + } + case SIMCONNECT_RECV_ID_ASSIGNED_OBJECT_ID: + { + SIMCONNECT_RECV_ASSIGNED_OBJECT_ID *event = static_cast(pData); + const DWORD requestId = event->dwRequestID; + const DWORD objectId = event->dwObjectID; + bool success = simulatorFsx->setSimConnectObjectId(requestId, objectId); + if (!success) { break; } // not an request ID of ours + + success = simulatorFsx->simulatorReportedObjectAdded(objectId); + if (success) + { + const CSimConnectObject simObject = simulatorFsx->getSimConnectObjects().getSimObjectForObjectId(objectId); + HRESULT result = S_OK; + result += simulatorFsx->requestDataForSimObject(simObject); + result += simulatorFsx->requestLightsForSimObject(simObject); + Q_UNUSED(result); + } + else + { + const CSimulatedAircraft remoteAircraft(simulatorFsx->getSimConnectObjects().getSimObjectForObjectId(objectId).getAircraft()); + const CStatusMessage msgAdd = CStatusMessage(simulatorFsx).error("Cannot add object %1") << objectId; + CLogMessage::preformatted(msgAdd); + emit simulatorFsx->physicallyAddingRemoteModelFailed(remoteAircraft, msgAdd); + } + break; + } + case SIMCONNECT_RECV_ID_SIMOBJECT_DATA_BYTYPE: + { + // SIMCONNECT_RECV_SIMOBJECT_DATA_BYTYPE *pObjData = (SIMCONNECT_RECV_SIMOBJECT_DATA_BYTYPE *)pData; + break; + } + case SIMCONNECT_RECV_ID_SIMOBJECT_DATA: + { + SIMCONNECT_RECV_SIMOBJECT_DATA *pObjData = (SIMCONNECT_RECV_SIMOBJECT_DATA *) pData; + const DWORD requestId = pObjData->dwRequestID; + switch (requestId) + { + case CSimConnectDefinitions::RequestOwnAircraft: + { + static_assert(sizeof(DataDefinitionOwnAircraft) == 30 * sizeof(double), "DataDefinitionOwnAircraft has an incorrect size."); + const DataDefinitionOwnAircraft *ownAircaft = (DataDefinitionOwnAircraft *)&pObjData->dwData; + simulatorFsx->updateOwnAircraftFromSimulator(*ownAircaft); + break; + } + case CSimConnectDefinitions::RequestOwnAircraftTitle: + { + const DataDefinitionOwnAircraftModel *dataDefinitionModel = (DataDefinitionOwnAircraftModel *) &pObjData->dwData; + CAircraftModel model; + model.setModelString(dataDefinitionModel->title); + model.setModelType(CAircraftModel::TypeOwnSimulatorModel); + simulatorFsx->reverseLookupAndUpdateOwnAircraftModel(model); + break; + } + case CSimConnectDefinitions::RequestSimEnvironment: + { + const DataDefinitionSimEnvironment *simEnv = (DataDefinitionSimEnvironment *) &pObjData->dwData; + if (simulatorFsx->isTimeSynchronized()) + { + const int zh = simEnv->zuluTimeSeconds / 3600; + const int zm = (simEnv->zuluTimeSeconds - (zh * 3600)) / 60; + const CTime zulu(zh, zm); + const int lh = simEnv->localTimeSeconds / 3600; + const int lm = (simEnv->localTimeSeconds - (lh * 3600)) / 60; + const CTime local(lh, lm); + simulatorFsx->synchronizeTime(zulu, local); + } + break; + } + default: + { + const DWORD objectId = pObjData->dwObjectID; + if (isRequestForSimData(requestId)) + { + static_assert(sizeof(DataDefinitionRemoteAircraftSimData) == 5 * sizeof(double), "DataDefinitionRemoteAircraftSimData has an incorrect size."); + const CSimConnectObject simObj = simulatorFsx->getSimConnectObjects().getSimObjectForObjectId(objectId); + if (!simObj.hasValidRequestAndObjectId()) break; + const DataDefinitionRemoteAircraftSimData *remoteAircraftSimData = (DataDefinitionRemoteAircraftSimData *)&pObjData->dwData; + // extra check, but ids should be the same + if (objectId == simObj.getObjectId()) + { + simulatorFsx->updateRemoteAircraftFromSimulator(simObj, *remoteAircraftSimData); + } + } + else if (isRequestForLights(requestId)) + { + static_assert(sizeof(DataDefinitionRemoteAircraftLights) == 8 * sizeof(double), "DataDefinitionRemoteAircraftLights has an incorrect size."); + const CSimConnectObject simObj = simulatorFsx->getSimConnectObjects().getSimObjectForObjectId(objectId); + if (!simObj.hasValidRequestAndObjectId()) break; + const DataDefinitionRemoteAircraftLights *remoteAircraftLights = (DataDefinitionRemoteAircraftLights *)&pObjData->dwData; + // extra check, but ids should be the same + if (objectId == simObj.getObjectId()) + { + const CCallsign callsign(simObj.getCallsign()); + const CAircraftLights lights = remoteAircraftLights->toLights(); // as in simulator + simulatorFsx->setCurrentLights(callsign, lights); + if (simObj.getLightsAsSent().isNull()) + { + // allows to compare for toggle + simulatorFsx->setLightsAsSent(callsign, lights); + } + } + } + break; + } + break; + } + break; + } + case SIMCONNECT_RECV_ID_AIRPORT_LIST: + { + static const CLength maxDistance(200.0, CLengthUnit::NM()); + const CCoordinateGeodetic posAircraft(simulatorFsx->getOwnAircraftPosition()); + SIMCONNECT_RECV_AIRPORT_LIST *pAirportList = (SIMCONNECT_RECV_AIRPORT_LIST *) pData; + for (unsigned i = 0; i < pAirportList->dwArraySize; ++i) + { + SIMCONNECT_DATA_FACILITY_AIRPORT *pFacilityAirport = pAirportList->rgData + i; + if (!pFacilityAirport) { break; } + const QString icao(pFacilityAirport->Icao); + if (icao.isEmpty()) { continue; } // airfield without ICAO code + if (!CAirportIcaoCode::isValidIcaoDesignator(icao)) { continue; } // tiny airfields/strips in simulator + if (CAirportIcaoCode::containsNumbers(icao)) { continue; } // tiny airfields/strips in simulator + const CCoordinateGeodetic pos(pFacilityAirport->Latitude, pFacilityAirport->Longitude, pFacilityAirport->Altitude); + CAirport airport(CAirportIcaoCode(icao), pos); + const CLength d = airport.calculcateAndUpdateRelativeDistanceAndBearing(posAircraft); + if (d > maxDistance) { continue; } + airport.updateMissingParts(simulatorFsx->getWebServiceAirport(icao)); + simulatorFsx->m_airportsInRangeFromSimulator.replaceOrAddByIcao(airport); + } + + if (simulatorFsx->m_airportsInRangeFromSimulator.size() > simulatorFsx->maxAirportsInRange()) + { + simulatorFsx->m_airportsInRangeFromSimulator.sortByDistanceToOwnAircraft(); + simulatorFsx->m_airportsInRangeFromSimulator.truncate(simulatorFsx->maxAirportsInRange()); + } + break; + } + case SIMCONNECT_RECV_ID_CLIENT_DATA: + { + if (!simulatorFsx->m_useSbOffsets) { break; } + SIMCONNECT_RECV_CLIENT_DATA *clientData = (SIMCONNECT_RECV_CLIENT_DATA *)pData; + if (simulatorFsx->m_useSbOffsets && clientData->dwRequestID == CSimConnectDefinitions::RequestSbData) + { + //! \fixme FSUIPC vs SimConnect why is offset 19 ident 2/0? In FSUIPC it is 0/1, according to documentation it is 0/1 but I receive 2/0 here. Whoever knows, add comment or fix if wrong + DataDefinitionClientAreaSb *sbData = (DataDefinitionClientAreaSb *) &clientData->dwData; + simulatorFsx->updateOwnAircraftFromSimulator(*sbData); + } + break; + } + case SIMCONNECT_RECV_ID_EVENT_FILENAME: + { + SIMCONNECT_RECV_EVENT_FILENAME *event = static_cast(pData); + switch (event->uEventID) + { + case SystemEventFlightLoaded: + break; + default: + break; + } + break; + } + default: + break; + } // main switch + } // method + } // namespace +} // namespace