Files
pilotclient/src/plugins/simulator/fsx/simulatorfsx.cpp
Roland Winklmeier fb3df51013 Move aircraft matching out of simulator plugins
All model matching will be done simulator independent in
CContextSimulator. The simulator specific part is kept in the model
set.
This also caused the signal modelMatchingCompleted to be renamed to
aircraftRenderingChanged, since the name wasn't accurate anymore.
Both getInstalledModels(), getInstalledModelsCount() and iconForModel()
were removed from the ISimulator interface.

refs #765
2016-09-19 16:30:05 +02:00

891 lines
42 KiB
C++

/* 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 "simulatorfsx.h"
#include "blackcore/application.h"
#include "blackmisc/interpolatorlinear.h"
#include "blackmisc/network/textmessage.h"
#include "blackmisc/simulation/fscommon/bcdconversions.h"
#include "blackmisc/simulation/fsx/simconnectutilities.h"
#include "blackmisc/simulation/fsx/fsxsimulatorsetup.h"
#include "blackmisc/simulation/simulatorplugininfo.h"
#include "blackmisc/simulation/aircraftmodel.h"
#include "blackmisc/aviation/airportlist.h"
#include "blackmisc/logmessage.h"
#include "blackmisc/threadutils.h"
#include "blackmisc/simulation/fscommon/fscommonutil.h"
#include <QTimer>
#include <type_traits>
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 Fsx
{
CSimulatorFsx::CSimulatorFsx(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");
this->m_simulatorSetup = CFsxSimulatorSetup::getInitialSetup();
m_useFsuipc = true; // Temporarily enabled until Simconnect Weather is implemented.
this->m_interpolator = new CInterpolatorLinear(remoteAircraftProvider, this);
m_defaultModel = {
"Boeing 737-800 Paint1",
CAircraftModel::TypeModelMatchingDefaultModel,
"B737-800 default model",
CAircraftIcaoCode("B738", "L2J")
};
}
CSimulatorFsx::~CSimulatorFsx()
{
disconnectFrom();
// fsuipc is disconnected in CSimulatorFsCommon
}
bool CSimulatorFsx::isConnected() const
{
return m_simConnected;
}
bool CSimulatorFsx::isSimulating() const
{
return m_simSimulating;
}
bool CSimulatorFsx::connectTo()
{
if (this->isConnected()) { return true; }
if (FAILED(SimConnect_Open(&m_hSimConnect, sApp->swiftVersionChar(), nullptr, 0, 0, 0)))
{
// reset state as expected for unconnected
if (m_simconnectTimerId >= 0) { killTimer(m_simconnectTimerId); }
m_simConnected = false;
m_simSimulating = false;
return false;
}
if (m_useFsuipc) { this->m_fsuipc->connect(); } // FSUIPC too
// set structures and move on
initEvents();
initDataDefinitionsWhenConnected();
m_simconnectTimerId = startTimer(10);
reloadWeatherSettings();
return true;
}
bool CSimulatorFsx::disconnectFrom()
{
if (!m_simConnected) { return true; }
// stop mapper init
//! \todo mapper shutdown in FSX, review keep it?
// mapperInstance()->gracefulShutdown();
if (m_simconnectTimerId)
{
killTimer(m_simconnectTimerId);
}
if (m_hSimConnect)
{
SimConnect_Close(m_hSimConnect);
m_hSimConnect = nullptr;
}
m_simConnected = false;
m_simSimulating = false;
m_simconnectTimerId = -1;
// emit status and disconnect FSUIPC
CSimulatorFsCommon::disconnectFrom();
return true;
}
bool CSimulatorFsx::physicallyAddRemoteAircraft(const CSimulatedAircraft &newRemoteAircraft)
{
CCallsign callsign(newRemoteAircraft.getCallsign());
Q_ASSERT_X(CThreadUtils::isCurrentThreadObjectThread(this), Q_FUNC_INFO, "thread");
Q_ASSERT_X(!callsign.isEmpty(), Q_FUNC_INFO, "empty callsign");
if (callsign.isEmpty()) { return false; }
bool aircraftAlreadyExistsInSim = this->m_simConnectObjects.contains(callsign);
if (aircraftAlreadyExistsInSim)
{
// remove first
this->physicallyRemoveRemoteAircraft(callsign);
CLogMessage(this).warning("Have to remove aircraft %1 before I can add it") << callsign;
}
CSimConnectObject simObj(callsign, m_nextObjID, 0, newRemoteAircraft.isVtol());
++m_nextObjID;
CAircraftModel aircraftModel = newRemoteAircraft.getModel();
// create AI
CSimulatedAircraft remoteAircraftCopy(newRemoteAircraft);
bool rendered = false;
if (isConnected())
{
// initial position
this->setInitialAircraftSituation(remoteAircraftCopy); // set interpolated data/parts if available
SIMCONNECT_DATA_INITPOSITION initialPosition = aircraftSituationToFsxInitPosition(remoteAircraftCopy.getSituation());
QByteArray m = aircraftModel.getModelString().toLocal8Bit();
HRESULT hr = SimConnect_AICreateNonATCAircraft(m_hSimConnect, m.constData(), qPrintable(callsign.toQString().left(12)), initialPosition, static_cast<SIMCONNECT_DATA_REQUEST_ID>(simObj.getRequestId()));
if (hr != S_OK) { CLogMessage(this).error("SimConnect, can not create AI traffic"); }
m_simConnectObjects.insert(callsign, simObj);
CLogMessage(this).info("FSX: Added aircraft %1") << callsign.toQString();
rendered = true;
}
else
{
CLogMessage(this).warning("FSX: Not connected, not added aircraft %1") << callsign.toQString();
}
remoteAircraftCopy.setRendered(rendered);
this->updateAircraftRendered(callsign, rendered);
emit aircraftRenderingChanged(remoteAircraftCopy);
return rendered;
}
bool CSimulatorFsx::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
CComSystem newCom1 = ownAircraft.getCom1System();
CComSystem newCom2 = ownAircraft.getCom2System();
CTransponder newTransponder = ownAircraft.getTransponder();
bool changed = false;
if (newCom1.getFrequencyActive() != this->m_simCom1.getFrequencyActive())
{
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())
{
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())
{
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())
{
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 CSimulatorFsx::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<DWORD>(m.size()),
m.data()
);
Q_UNUSED(hr);
}
void CSimulatorFsx::displayTextMessage(const BlackMisc::Network::CTextMessage &message) const
{
this->displayStatusMessage(message.asStatusMessage(true, true));
}
bool CSimulatorFsx::isPhysicallyRenderedAircraft(const CCallsign &callsign) const
{
return this->m_simConnectObjects.contains(callsign);
}
CCallsignSet CSimulatorFsx::physicallyRenderedAircraft() const
{
return CCollection<CCallsign>(this->m_simConnectObjects.keys());
}
void CSimulatorFsx::setSimConnected()
{
m_simConnected = true;
emitSimulatorCombinedStatus();
}
void CSimulatorFsx::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 CSimulatorFsx::onSimStopped()
{
int oldStatus = getSimulatorStatus();
m_simSimulating = false;
emitSimulatorCombinedStatus(oldStatus);
}
void CSimulatorFsx::onSimFrame()
{
updateRemoteAircraft();
}
void CSimulatorFsx::onSimExit()
{
// reset complete state, we are going down
disconnectFrom();
}
void CSimulatorFsx::updateOwnAircraftFromSimulator(DataDefinitionOwnAircraft simulatorOwnAircraft)
{
CSimulatedAircraft myAircraft(getOwnAircraft());
BlackMisc::Geo::CCoordinateGeodetic position;
position.setLatitude(CLatitude(simulatorOwnAircraft.latitude, CAngleUnit::deg()));
position.setLongitude(CLongitude(simulatorOwnAircraft.longitude, CAngleUnit::deg()));
BlackMisc::Aviation::CAircraftSituation aircraftSituation;
aircraftSituation.setPosition(position);
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.setAltitude(CAltitude(simulatorOwnAircraft.altitude, CAltitude::MeanSeaLevel, CLengthUnit::ft()));
CAircraftLights lights(simulatorOwnAircraft.lightStrobe,
simulatorOwnAircraft.lightLanding,
simulatorOwnAircraft.lightTaxi,
simulatorOwnAircraft.lightBeacon,
simulatorOwnAircraft.lightNav,
simulatorOwnAircraft.lightLogo);
QList<bool> helperList {simulatorOwnAircraft.engine1Combustion != 0, simulatorOwnAircraft.engine2Combustion != 0,
simulatorOwnAircraft.engine3Combustion != 0, simulatorOwnAircraft.engine4Combustion != 0
};
CAircraftEngineList engines;
for (int index = 0; index < simulatorOwnAircraft.numberOfEngines; ++index)
{
engines.push_back(CAircraftEngine(index + 1, helperList.at(index)));
}
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()));
bool changedCom1 = myAircraft.getCom1System() != com1;
this->m_simCom1 = com1;
com2.setFrequencyActive(CFrequency(simulatorOwnAircraft.com2ActiveMHz, CFrequencyUnit::MHz()));
com2.setFrequencyStandby(CFrequency(simulatorOwnAircraft.com2StandbyMHz, CFrequencyUnit::MHz()));
bool changedCom2 = myAircraft.getCom2System() != com2;
this->m_simCom2 = com2;
transponder.setTransponderCode(simulatorOwnAircraft.transponderCode);
bool changedXpr = (myAircraft.getTransponderCode() != transponder.getTransponderCode());
if (changedCom1 || changedCom2 || changedXpr)
{
this->updateCockpit(com1, com2, transponder, identifier());
}
}
else
{
--m_skipCockpitUpdateCycles;
}
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, &CSimulatorFsx::injectWeatherGrid });
}
}
void CSimulatorFsx::updateOwnAircraftFromSimulator(DataDefinitionClientAreaSb sbDataArea)
{
CTransponder::TransponderMode newMode;
if (sbDataArea.isIdent())
{
newMode = CTransponder::StateIdent;
}
else
{
newMode = sbDataArea.isStandby() ? CTransponder::StateStandby : CTransponder::ModeC;
}
CSimulatedAircraft myAircraft(this->getOwnAircraft());
bool changed = (myAircraft.getTransponderMode() != newMode);
if (!changed) { return; }
CTransponder xpdr = myAircraft.getTransponder();
xpdr.setTransponderMode(newMode);
this->updateCockpit(myAircraft.getCom1System(), myAircraft.getCom2System(), xpdr, this->identifier());
}
void CSimulatorFsx::setSimConnectObjectID(DWORD requestID, DWORD objectID)
{
// First check, if this request id belongs to us
auto it = std::find_if(m_simConnectObjects.begin(), m_simConnectObjects.end(),
[requestID](const CSimConnectObject & obj) { return obj.getRequestId() == static_cast<int>(requestID); });
if (it == m_simConnectObjects.end()) { return; }
// belongs to us
it->setObjectId(static_cast<int>(objectID));
SimConnect_AIReleaseControl(m_hSimConnect, objectID, requestID);
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);
}
void CSimulatorFsx::timerEvent(QTimerEvent *event)
{
Q_UNUSED(event);
ps_dispatch();
}
void CSimulatorFsx::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());
//! \todo 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 CSimulatorFsx::physicallyRemoveRemoteAircraft(const CCallsign &callsign)
{
// only remove from sim
Q_ASSERT_X(CThreadUtils::isCurrentThreadObjectThread(this), Q_FUNC_INFO, "wrong thred");
if (!m_simConnectObjects.contains(callsign)) { return false; }
return physicallyRemoveRemoteAircraft(m_simConnectObjects.value(callsign));
}
int CSimulatorFsx::physicallyRemoveAllRemoteAircraft()
{
if (m_simConnectObjects.isEmpty()) { return 0; }
QList<CCallsign> callsigns(m_simConnectObjects.keys());
int r = 0;
for (const CCallsign &cs : callsigns)
{
if (physicallyRemoveRemoteAircraft(cs)) { r++; }
}
return r;
}
bool CSimulatorFsx::physicallyRemoveRemoteAircraft(const CSimConnectObject &simObject)
{
CCallsign callsign(simObject.getCallsign());
m_simConnectObjects.remove(callsign);
SimConnect_AIRemoveObject(m_hSimConnect, static_cast<SIMCONNECT_OBJECT_ID>(simObject.getObjectId()), static_cast<SIMCONNECT_DATA_REQUEST_ID>(simObject.getRequestId()));
updateAircraftRendered(callsign, false);
CLogMessage(this).info("FSX: Removed aircraft %1") << simObject.getCallsign().toQString();
return true;
}
HRESULT CSimulatorFsx::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, EventToggleTaxiLights, "TOGGLE_TAXI_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<SIMCONNECT_DATA_REQUEST_ID>(m_nextObjID++));
if (hr != S_OK)
{
CLogMessage(this).error("FSX plugin error: %1") << "SimConnect_SubscribeToFacilities failed";
return hr;
}
return hr;
}
HRESULT CSimulatorFsx::initDataDefinitionsWhenConnected()
{
return CSimConnectDefinitions::initDataDefinitionsWhenConnected(m_hSimConnect);
}
HRESULT CSimulatorFsx::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 CSimulatorFsx::updateRemoteAircraft()
{
static_assert(sizeof(DataDefinitionRemoteAircraftParts) == 120, "DataDefinitionRemoteAircraftParts has an incorrect size.");
Q_ASSERT_X(this->m_interpolator, Q_FUNC_INFO, "missing interpolator");
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
int remoteAircraftNo = this->getAircraftInRangeCount();
if (remoteAircraftNo < 1) { m_interpolationRequest = 0; return; }
// interpolate and send to SIM
m_interpolationRequest++;
// values used for position and parts
bool isOnGround = false;
qint64 currentTimestamp = QDateTime::currentMSecsSinceEpoch();
CCallsignSet aircraftWithParts(this->remoteAircraftSupportingParts()); // optimization, fetch all parts supporting aircraft in one step (one lock)
const QList<CSimConnectObject> simObjects(m_simConnectObjects.values());
for (const CSimConnectObject &simObj : simObjects)
{
if (simObj.getObjectId() < 1) { continue; }
const CCallsign callsign(simObj.getCallsign());
Q_ASSERT_X(!callsign.isEmpty(), Q_FUNC_INFO, "missing callsign");
IInterpolator::InterpolationStatus interpolatorStatus;
CAircraftSituation interpolatedSituation = this->m_interpolator->getInterpolatedSituation(callsign, currentTimestamp, simObj.isVtol(), interpolatorStatus);
// having the onGround flag in parts forces me to obtain parts here
// which is not the smartest thing regarding performance
IInterpolator::PartsStatus partsStatus;
partsStatus.supportsParts = aircraftWithParts.contains(callsign);
CAircraftPartsList parts;
if (partsStatus.supportsParts)
{
this->m_interpolator->getPartsBeforeTime(callsign, currentTimestamp, partsStatus);
}
if (interpolatorStatus.allTrue())
{
// update situation
SIMCONNECT_DATA_INITPOSITION position = aircraftSituationToFsxInitPosition(interpolatedSituation);
//! \todo The onGround in parts is nuts, as already mentioned in the discussion
// Currently ignored here, only guessing which is faster as aircraft without parts can just be
// a) I am forced to read parts even if i just want to update position
// b) Unlike the other values it is not a fire and forget value, as I need it again in the next cycle
if (partsStatus.supportsParts && !parts.isEmpty())
{
// we have parts, and use the closest ground
isOnGround = parts.front().isOnGround();
}
else
{
isOnGround = interpolatedSituation.isOnGroundGuessed();
}
position.OnGround = isOnGround ? 1U : 0U;
HRESULT hr = S_OK;
hr += SimConnect_SetDataOnSimObject(m_hSimConnect, CSimConnectDefinitions::DataRemoteAircraftPosition,
static_cast<SIMCONNECT_OBJECT_ID>(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 (interpolatorStatus.interpolationSucceeded)
{
// aircraft parts
// inside "interpolator if", as no parts can be sent without position
updateRemoteAircraftParts(simObj, parts, partsStatus, interpolatedSituation, isOnGround); // update and retrieve parts in the same step
}
} // all callsigns
qint64 dt = QDateTime::currentMSecsSinceEpoch() - currentTimestamp;
m_statsUpdateAircraftTimeTotal += dt;
m_statsUpdateAircraftCount++;
m_statsUpdateAircraftTimeAvg = m_statsUpdateAircraftTimeTotal / m_statsUpdateAircraftCount;
}
bool CSimulatorFsx::updateRemoteAircraftParts(const CSimConnectObject &simObj, const CAircraftPartsList &parts, IInterpolator::PartsStatus partsStatus, const CAircraftSituation &interpolatedSituation, bool isOnGround) const
{
// set parts
DataDefinitionRemoteAircraftParts ddRemoteAircraftParts;
if (partsStatus.supportsParts)
{
// parts is supported, but do we need to update?
if (parts.isEmpty()) { return false; }
// we have parts
CAircraftParts newestParts = parts.front();
ddRemoteAircraftParts.lightStrobe = newestParts.getLights().isStrobeOn() ? 1.0 : 0.0;
ddRemoteAircraftParts.lightLanding = newestParts.getLights().isLandingOn() ? 1.0 : 0.0;
// ddRemoteAircraftParts.lightTaxi = newestParts.getLights().isTaxiOn() ? 1.0 : 0.0;
ddRemoteAircraftParts.lightBeacon = newestParts.getLights().isBeaconOn() ? 1.0 : 0.0;
ddRemoteAircraftParts.lightNav = newestParts.getLights().isNavOn() ? 1.0 : 0.0;
ddRemoteAircraftParts.lightLogo = newestParts.getLights().isLogoOn() ? 1.0 : 0.0;
ddRemoteAircraftParts.flapsLeadingEdgeLeftPercent = newestParts.getFlapsPercent() / 100.0;
ddRemoteAircraftParts.flapsLeadingEdgeRightPercent = newestParts.getFlapsPercent() / 100.0;
ddRemoteAircraftParts.flapsTrailingEdgeLeftPercent = newestParts.getFlapsPercent() / 100.0;
ddRemoteAircraftParts.flapsTrailingEdgeRightPercent = newestParts.getFlapsPercent() / 100.0;
ddRemoteAircraftParts.spoilersHandlePosition = newestParts.isSpoilersOut() ? 1.0 : 0.0;
ddRemoteAircraftParts.gearHandlePosition = newestParts.isGearDown() ? 1 : 0;
ddRemoteAircraftParts.engine1Combustion = newestParts.isEngineOn(1) ? 1 : 0;
ddRemoteAircraftParts.engine2Combustion = newestParts.isEngineOn(2) ? 1 : 0;;
ddRemoteAircraftParts.engine3Combustion = newestParts.isEngineOn(3) ? 1 : 0;
ddRemoteAircraftParts.engine4Combustion = newestParts.isEngineOn(4) ? 1 : 0;
}
else
{
// mode is guessing parts
if (this->m_interpolationRequest % 20 != 0) { return false; } // only update every 20th cycle
ddRemoteAircraftParts.gearHandlePosition = isOnGround ? 1 : 0;
// when first detected moving, lights on
if (isOnGround)
{
// ddRemoteAircraftParts.lightTaxi = 1.0;
ddRemoteAircraftParts.lightBeacon = 1.0;
ddRemoteAircraftParts.lightNav = 1.0;
double gskmh = interpolatedSituation.getGroundSpeed().value(CSpeedUnit::km_h());
if (gskmh > 7.5)
{
// mode taxi
// ddRemoteAircraftParts.lightTaxi = 1.0;
ddRemoteAircraftParts.lightLanding = 0.0;
}
else if (gskmh > 25)
{
// mode accelaration for takeoff
// ddRemoteAircraftParts.lightTaxi = 0.0;
ddRemoteAircraftParts.lightLanding = 1.0;
}
else
{
// slow movements or parking
// ddRemoteAircraftParts.lightTaxi = 0.0;
ddRemoteAircraftParts.lightLanding = 0.0;
}
}
else
{
// ddRemoteAircraftParts.lightTaxi = 0.0;
ddRemoteAircraftParts.lightBeacon = 1.0;
ddRemoteAircraftParts.lightNav = 1.0;
// landing lights for < 10000ft (normally MSL, here ignored)
ddRemoteAircraftParts.lightLanding = (interpolatedSituation.getAltitude().value(CLengthUnit::ft()) < 10000) ? 1.0 : 0;
}
}
Q_ASSERT(m_hSimConnect);
HRESULT hr = S_OK;
hr += SimConnect_SetDataOnSimObject(m_hSimConnect, CSimConnectDefinitions::DataRemoteAircraftParts,
static_cast<SIMCONNECT_OBJECT_ID>(simObj.getObjectId()), 0, 0,
sizeof(DataDefinitionRemoteAircraftParts), &ddRemoteAircraftParts);
if (hr != S_OK) { CLogMessage(this).warning("Failed so set parts on SimObject %1 callsign: %2") << simObj.getObjectId() << simObj.getCallsign(); }
return hr == S_OK;
}
SIMCONNECT_DATA_INITPOSITION CSimulatorFsx::aircraftSituationToFsxInitPosition(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());
position.Pitch = situation.getPitch().value(CAngleUnit::deg());
position.Bank = situation.getBank().value(CAngleUnit::deg());
position.Heading = situation.getHeading().value(CAngleUnit::deg());
position.Airspeed = situation.getGroundSpeed().value(CSpeedUnit::kts());
return position;
}
void CSimulatorFsx::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);
}
QTime myTime = myDateTime.time();
DWORD h = static_cast<DWORD>(myTime.hour());
DWORD m = static_cast<DWORD>(myTime.minute());
int targetMins = myTime.hour() * 60 + myTime.minute();
int simMins = zuluTimeSim.valueRounded(CTimeUnit::min());
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 CSimulatorFsx::injectWeatherGrid(const Weather::CWeatherGrid &weatherGrid)
{
m_fsuipc->write(weatherGrid);
}
void CSimulatorFsx::reloadWeatherSettings()
{
if (m_fsuipc->isConnected())
{
auto selectedWeatherScenario = m_weatherScenarioSettings.get();
if (!CWeatherScenario::isRealWeatherScenario(selectedWeatherScenario))
{
m_lastWeatherPosition = {};
injectWeatherGrid(CWeatherGrid::getByScenario(selectedWeatherScenario));
}
}
}
CSimulatorFsxListener::CSimulatorFsxListener(const CSimulatorPluginInfo &info) :
ISimulatorListener(info),
m_timer(new QTimer(this))
{
constexpr int QueryInterval = 5 * 1000; // 5 seconds
m_timer->setInterval(QueryInterval);
this->m_timer->setObjectName(this->objectName().append(":m_timer"));
connect(m_timer, &QTimer::timeout, this, &CSimulatorFsxListener::ps_checkConnection);
}
void CSimulatorFsxListener::start()
{
m_timer->start();
}
void CSimulatorFsxListener::stop()
{
m_timer->stop();
}
void CSimulatorFsxListener::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