From 99ab20bcc52a46a4254e0b549fabf2792c4f4335 Mon Sep 17 00:00:00 2001 From: Roland Winklmeier Date: Tue, 1 Mar 2016 23:09:21 +0100 Subject: [PATCH] Inject global weather grid to Flight Simulators via FSUIPC In the short term FSUIPC is used for all MSFS simulators, but in the mid term this will be superceded by the Simconnect Weather API. refs #579 --- externals | 2 +- src/plugins/simulator/fs9/simulatorfs9.cpp | 11 ++ src/plugins/simulator/fs9/simulatorfs9.h | 3 + src/plugins/simulator/fscommon/fsuipc.cpp | 184 ++++++++++++++++++++- src/plugins/simulator/fscommon/fsuipc.h | 27 ++- src/plugins/simulator/fsx/simulatorfsx.cpp | 12 +- src/plugins/simulator/fsx/simulatorfsx.h | 3 + 7 files changed, 238 insertions(+), 4 deletions(-) diff --git a/externals b/externals index ef0f5499a..685c8fffb 160000 --- a/externals +++ b/externals @@ -1 +1 @@ -Subproject commit ef0f5499a58655a47015ec659c7585476c1a5644 +Subproject commit 685c8fffbcaedbafd4a3186291c753a02f496f08 diff --git a/src/plugins/simulator/fs9/simulatorfs9.cpp b/src/plugins/simulator/fs9/simulatorfs9.cpp index f40e1016c..b5682c0ec 100644 --- a/src/plugins/simulator/fs9/simulatorfs9.cpp +++ b/src/plugins/simulator/fs9/simulatorfs9.cpp @@ -32,6 +32,7 @@ using namespace BlackMisc::PhysicalQuantities; using namespace BlackMisc::Geo; using namespace BlackMisc::Simulation; using namespace BlackMisc::Simulation::FsCommon; +using namespace BlackMisc::Weather; using namespace BlackSimPlugin::Fs9; using namespace BlackSimPlugin::FsCommon; @@ -133,6 +134,11 @@ namespace BlackSimPlugin m_fsuipc->connect(); // connect FSUIPC too } m_dispatchTimerId = startTimer(50); + + // Pull weather data from core. + // Since we don't get weather data from core yet, use hard coded weather. + injectWeatherGrid(CWeatherGrid::getCavokGrid()); + return true; } @@ -361,6 +367,11 @@ namespace BlackSimPlugin } } + void CSimulatorFs9::injectWeatherGrid(const Weather::CWeatherGrid &weatherGrid) + { + m_fsuipc->write(weatherGrid); + } + CSimulatorFs9Listener::CSimulatorFs9Listener(const CSimulatorPluginInfo &info, const QSharedPointer &fs9Host, const QSharedPointer &lobbyClient) : diff --git a/src/plugins/simulator/fs9/simulatorfs9.h b/src/plugins/simulator/fs9/simulatorfs9.h index a768ab748..7802c1144 100644 --- a/src/plugins/simulator/fs9/simulatorfs9.h +++ b/src/plugins/simulator/fs9/simulatorfs9.h @@ -105,6 +105,9 @@ namespace BlackSimPlugin void disconnectAllClients(); + //! Inject weather grid to simulator + void injectWeatherGrid(const BlackMisc::Weather::CWeatherGrid &weatherGrid); + QHash> m_hashFs9Clients; QMetaObject::Connection m_connectionHostMessages; int m_dispatchTimerId = -1; diff --git a/src/plugins/simulator/fscommon/fsuipc.cpp b/src/plugins/simulator/fscommon/fsuipc.cpp index 6e55a3b83..7afcbb99d 100644 --- a/src/plugins/simulator/fscommon/fsuipc.cpp +++ b/src/plugins/simulator/fscommon/fsuipc.cpp @@ -15,6 +15,7 @@ #include // bug in FSUIPC_User.h, windows.h not included, so we have to import it first #include "FSUIPC/FSUIPC_User.h" +#include "FSUIPC/NewWeather.h" #include "blackmisc/simulation/fscommon/bcdconversions.h" #include "blackmisc/logmessage.h" @@ -29,13 +30,17 @@ using namespace BlackMisc::Network; using namespace BlackMisc::Geo; using namespace BlackMisc::Simulation; using namespace BlackMisc::PhysicalQuantities; +using namespace BlackMisc::Weather; namespace BlackSimPlugin { namespace FsCommon { - CFsuipc::CFsuipc() { } + CFsuipc::CFsuipc() + { + startTimer(100); + } CFsuipc::~CFsuipc() { @@ -89,6 +94,121 @@ namespace BlackSimPlugin return false; } + bool CFsuipc::write(const BlackMisc::Weather::CWeatherGrid &weatherGrid) + { + if (!this->isConnected()) { return false; } + + clearAllWeather(); + + CGridPoint gridPoint = weatherGrid.front(); + + NewWeather nw; + // Clear new weather + nw.uCommand = NW_SET; + nw.uFlags = 0; + nw.ulSignature = 0; + nw.uDynamics = 0; + for (std::size_t i = 0; i < sizeof(nw.uSpare) / sizeof(nw.uSpare[0]); i++) { nw.uSpare[i] = 0; } + + nw.dLatitude = 0.0; + nw.dLongitude = 0.0; + nw.nElevation = 0; + nw.ulTimeStamp = 0; + nw.nTempCtr = 0; + nw.nWindsCtr = 0; + nw.nCloudsCtr = 0; + nw.nElevation = 0; // metres * 65536; + nw.nUpperVisCtr = 0; + + // todo: Take station from weather grid + memcpy(nw.chICAO, "GLOB", 4); + + const CVisibilityLayerList visibilityLayers = gridPoint.getVisibilityLayers(); + for (const auto &visibilityLayer : visibilityLayers) + { + NewVis vis; + vis.LowerAlt = visibilityLayer.getBase().value(CLengthUnit::m()); + vis.UpperAlt = visibilityLayer.getCeiling().value(CLengthUnit::m()); + vis.Range = visibilityLayer.getVisibility().value(CLengthUnit::mi()) * 100; + nw.Vis = vis; + } + + const CTemperatureLayerList temperatureLayers = gridPoint.getTemperatureLayers(); + for (const auto &temperatureLayer : temperatureLayers) + { + NewTemp temp; + temp.Alt = temperatureLayer.getLevel().value(CLengthUnit::m()); + temp.Day = temperatureLayer.getTemperature().value(CTemperatureUnit::C()); + temp.DayNightVar = 3; + temp.DewPoint = temperatureLayer.getDewPoint().value(CTemperatureUnit::C()); + nw.Temp[nw.nTempCtr++] = temp; + } + + const CCloudLayerList cloudLayers = gridPoint.getCloudLayers(); + for (const auto &cloudLayer : cloudLayers) + { + NewCloud cloud; + cloud.Coverage = static_cast(cloudLayer.getCoverage()); + + switch (cloudLayer.getCoverage()) + { + case CCloudLayer::None: cloud.Coverage = 0; break; + case CCloudLayer::Few: cloud.Coverage = 2; break; + case CCloudLayer::Scattered: cloud.Coverage = 4; break; + case CCloudLayer::Broken: cloud.Coverage = 6; break; + case CCloudLayer::Overcast: cloud.Coverage = 8; break; + default: cloud.Coverage = 0; + } + + cloud.Deviation = 0; + cloud.Icing = 0; + cloud.LowerAlt = cloudLayer.getBase().value(CLengthUnit::m()); + cloud.PrecipBase = 0; + cloud.PrecipRate = static_cast(cloudLayer.getPrecipitationRate()); + cloud.PrecipType = static_cast(cloudLayer.getPrecipitation()); + cloud.TopShape = 0; + cloud.Turbulence = 0; + + switch (cloudLayer.getClouds()) + { + case CCloudLayer::NoClouds: cloud.Type = 0; break; + case CCloudLayer::Cirrus: cloud.Type = 1; break; + case CCloudLayer::Stratus: cloud.Type = 8; break; + case CCloudLayer::Cumulus: cloud.Type = 9; break; + case CCloudLayer::Thunderstorm: cloud.Type = 10; break; + default: cloud.Type = 0; + } + + cloud.UpperAlt = cloudLayer.getCeiling().value(CLengthUnit::m()); + nw.Cloud[nw.nCloudsCtr++] = cloud; + } + + const CWindLayerList windLayers = gridPoint.getWindLayers(); + for (const auto &windLayer : windLayers) + { + NewWind wind; + wind.Direction = windLayer.getDirection().value(CAngleUnit::deg()) * 65536 / 360.0; + wind.GapAbove = 0; + wind.Gust = windLayer.getGustSpeed().value(CSpeedUnit::kts()); + wind.Shear = 0; + wind.Speed = windLayer.getSpeed().value(CSpeedUnit::kts()); + wind.SpeedFract = 0; + wind.Turbulence = 0; + wind.UpperAlt = windLayer.getLevel().value(CLengthUnit::m()); + wind.Variance = 0; + nw.Wind[nw.nWindsCtr++] = wind; + } + + NewPress press; + press.Drift = 0; + press.Pressure = 15827; // 16 x mb + nw.Press = press; + + QByteArray weatherData(reinterpret_cast(&nw), sizeof(NewWeather)); + m_weatherMessageQueue.append(FsuipcWeatherMessage(0xC800, weatherData, 5)); + return true; + } + bool CFsuipc::read(CSimulatedAircraft &aircraft, bool cockpit, bool situation, bool aircraftParts) { DWORD dwResult; @@ -265,6 +385,68 @@ namespace BlackSimPlugin return read; } + void CFsuipc::timerEvent(QTimerEvent *event) + { + Q_UNUSED(event); + processWeatherMessages(); + } + + CFsuipc::FsuipcWeatherMessage::FsuipcWeatherMessage(unsigned int offset, const QByteArray &data, int leftTrials) : + m_offset(offset), m_messageData(data), m_leftTrials(leftTrials) + { } + + + void CFsuipc::clearAllWeather() + { + if (!this->isConnected()) { return; } + + // clear all weather + NewWeather nw; + + // Clear new weather + nw.uCommand = NW_CLEAR; + nw.uFlags = 0; + nw.ulSignature = 0; + nw.uDynamics = 0; + for (std::size_t i = 0; i < sizeof(nw.uSpare) / sizeof(nw.uSpare[0]); i++) { nw.uSpare[i] = 0; } + + nw.dLatitude = 0.; + nw.dLongitude = 0.; + nw.nElevation = 0; + nw.ulTimeStamp = 0; + nw.nTempCtr = 0; + nw.nWindsCtr = 0; + nw.nCloudsCtr = 0; + QByteArray clearWeather(reinterpret_cast(&nw), sizeof(NewWeather)); + m_weatherMessageQueue.append(FsuipcWeatherMessage(0xC800, clearWeather, 1)); + } + + void CFsuipc::processWeatherMessages() + { + if (m_weatherMessageQueue.empty()) { return; } + FsuipcWeatherMessage &weatherMessage = m_weatherMessageQueue.first(); + + DWORD dwResult; + weatherMessage.m_leftTrials--; + FSUIPC_Write(weatherMessage.m_offset, weatherMessage.m_messageData.size(), reinterpret_cast(weatherMessage.m_messageData.data()), &dwResult); + + unsigned int timeStamp = 0; + FSUIPC_Read(0xC824, sizeof(timeStamp), &timeStamp, &dwResult); + FSUIPC_Process(&dwResult); + if (timeStamp > m_lastTimestamp) + { + m_weatherMessageQueue.removeFirst(); + m_lastTimestamp = timeStamp; + return; + } + + if (weatherMessage.m_leftTrials == 0) + { + CLogMessage(this).debug() << "Number of trials reached for weather message. Dropping it."; + m_weatherMessageQueue.removeFirst(); + } + } + double CFsuipc::intToFractional(double fractional) { double f = fractional / 10.0; diff --git a/src/plugins/simulator/fscommon/fsuipc.h b/src/plugins/simulator/fscommon/fsuipc.h index 350bc1356..ad8731f2a 100644 --- a/src/plugins/simulator/fscommon/fsuipc.h +++ b/src/plugins/simulator/fscommon/fsuipc.h @@ -13,6 +13,7 @@ #define BLACKSIMPLUGIN_FSUIPC_H #include "blackmisc/simulation/simulatedaircraft.h" +#include "blackmisc/weather/weathergrid.h" #include namespace BlackSimPlugin @@ -20,8 +21,10 @@ namespace BlackSimPlugin namespace FsCommon { //! Class representing a FSUIPC "interface" - class CFsuipc + class CFsuipc : public QObject { + Q_OBJECT + public: //! Constructor @@ -45,6 +48,9 @@ namespace BlackSimPlugin //! Write variables bool write(const BlackMisc::Simulation::CSimulatedAircraft &aircraft); + //! Write weather grid to simulator + bool write(const BlackMisc::Weather::CWeatherGrid &weatherGrid); + //! Read data from FSUIPC //! \param aircraft object to be updated //! \param cockpit update cockpit data @@ -97,11 +103,30 @@ namespace BlackSimPlugin //! Log message category static QString getMessageCategory() { return "swift.fscommon.fsuipc"; } + protected: + //! \copydoc QObject::timerEvent + void timerEvent(QTimerEvent *event); + private: + struct FsuipcWeatherMessage + { + FsuipcWeatherMessage() = default; + FsuipcWeatherMessage(unsigned int offset, const QByteArray &data, int leftTrials); + int m_offset = 0; + QByteArray m_messageData; + int m_leftTrials = 0; + }; + + void clearAllWeather(); + void processWeatherMessages(); + bool m_connected = false; QString m_lastErrorMessage; QString m_fsuipcVersion; + QVector m_weatherMessageQueue; + unsigned int m_lastTimestamp = 0; + //! Integer representing fractional static double intToFractional(double fractional); diff --git a/src/plugins/simulator/fsx/simulatorfsx.cpp b/src/plugins/simulator/fsx/simulatorfsx.cpp index faa3fe968..ebd84f706 100644 --- a/src/plugins/simulator/fsx/simulatorfsx.cpp +++ b/src/plugins/simulator/fsx/simulatorfsx.cpp @@ -33,6 +33,7 @@ using namespace BlackMisc::Simulation; using namespace BlackMisc::Simulation; using namespace BlackMisc::Simulation::FsCommon; using namespace BlackMisc::Simulation::Fsx; +using namespace BlackMisc::Weather; using namespace BlackCore; namespace BlackSimPlugin @@ -50,7 +51,7 @@ namespace BlackSimPlugin Q_ASSERT(remoteAircraftProvider); this->m_simulatorSetup = CFsxSimulatorSetup::getInitialSetup(); - m_useFsuipc = false; // do not use FSUIPC at the moment with FSX + m_useFsuipc = true; // Temporarily enabled until Simconnect Weather is implemented. this->m_interpolator = new CInterpolatorLinear(remoteAircraftProvider, this); m_modelMatcher.setDefaultModel(CAircraftModel( "Boeing 737-800 Paint1", @@ -95,6 +96,10 @@ namespace BlackSimPlugin m_simconnectTimerId = startTimer(10); m_simConnected = true; emitSimulatorCombinedStatus(); + + // Pull weather data from core. + // Since we don't get weather data from core yet, use hard coded weather. + injectWeatherGrid(CWeatherGrid::getCavokGrid()); return true; } @@ -824,6 +829,11 @@ namespace BlackSimPlugin } } + void CSimulatorFsx::injectWeatherGrid(const Weather::CWeatherGrid &weatherGrid) + { + m_fsuipc->write(weatherGrid); + } + CSimulatorFsxListener::CSimulatorFsxListener(const CSimulatorPluginInfo &info) : ISimulatorListener(info), m_timer(new QTimer(this)) diff --git a/src/plugins/simulator/fsx/simulatorfsx.h b/src/plugins/simulator/fsx/simulatorfsx.h index 8bb90a5c2..53e14242e 100644 --- a/src/plugins/simulator/fsx/simulatorfsx.h +++ b/src/plugins/simulator/fsx/simulatorfsx.h @@ -179,6 +179,9 @@ namespace BlackSimPlugin //! Sync time with user's computer void synchronizeTime(const BlackMisc::PhysicalQuantities::CTime &zuluTimeSim, const BlackMisc::PhysicalQuantities::CTime &localTimeSim); + //! Inject weather grid to simulator + void injectWeatherGrid(const BlackMisc::Weather::CWeatherGrid &weatherGrid); + static const int SkipUpdateCyclesForCockpit = 10; //!< skip x cycles before updating cockpit again bool m_simConnected = false; //!< Is simulator connected? bool m_simSimulating = false; //!< Simulator running?