mirror of
https://github.com/swift-project/pilotclient.git
synced 2026-05-04 17:30:12 +08:00
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
This commit is contained in:
Submodule externals updated: ef0f5499a5...685c8fffbc
@@ -32,6 +32,7 @@ using namespace BlackMisc::PhysicalQuantities;
|
|||||||
using namespace BlackMisc::Geo;
|
using namespace BlackMisc::Geo;
|
||||||
using namespace BlackMisc::Simulation;
|
using namespace BlackMisc::Simulation;
|
||||||
using namespace BlackMisc::Simulation::FsCommon;
|
using namespace BlackMisc::Simulation::FsCommon;
|
||||||
|
using namespace BlackMisc::Weather;
|
||||||
using namespace BlackSimPlugin::Fs9;
|
using namespace BlackSimPlugin::Fs9;
|
||||||
using namespace BlackSimPlugin::FsCommon;
|
using namespace BlackSimPlugin::FsCommon;
|
||||||
|
|
||||||
@@ -133,6 +134,11 @@ namespace BlackSimPlugin
|
|||||||
m_fsuipc->connect(); // connect FSUIPC too
|
m_fsuipc->connect(); // connect FSUIPC too
|
||||||
}
|
}
|
||||||
m_dispatchTimerId = startTimer(50);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -361,6 +367,11 @@ namespace BlackSimPlugin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CSimulatorFs9::injectWeatherGrid(const Weather::CWeatherGrid &weatherGrid)
|
||||||
|
{
|
||||||
|
m_fsuipc->write(weatherGrid);
|
||||||
|
}
|
||||||
|
|
||||||
CSimulatorFs9Listener::CSimulatorFs9Listener(const CSimulatorPluginInfo &info,
|
CSimulatorFs9Listener::CSimulatorFs9Listener(const CSimulatorPluginInfo &info,
|
||||||
const QSharedPointer<CFs9Host> &fs9Host,
|
const QSharedPointer<CFs9Host> &fs9Host,
|
||||||
const QSharedPointer<CLobbyClient> &lobbyClient) :
|
const QSharedPointer<CLobbyClient> &lobbyClient) :
|
||||||
|
|||||||
@@ -105,6 +105,9 @@ namespace BlackSimPlugin
|
|||||||
|
|
||||||
void disconnectAllClients();
|
void disconnectAllClients();
|
||||||
|
|
||||||
|
//! Inject weather grid to simulator
|
||||||
|
void injectWeatherGrid(const BlackMisc::Weather::CWeatherGrid &weatherGrid);
|
||||||
|
|
||||||
QHash<BlackMisc::Aviation::CCallsign, QPointer<CFs9Client>> m_hashFs9Clients;
|
QHash<BlackMisc::Aviation::CCallsign, QPointer<CFs9Client>> m_hashFs9Clients;
|
||||||
QMetaObject::Connection m_connectionHostMessages;
|
QMetaObject::Connection m_connectionHostMessages;
|
||||||
int m_dispatchTimerId = -1;
|
int m_dispatchTimerId = -1;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
// bug in FSUIPC_User.h, windows.h not included, so we have to import it first
|
// bug in FSUIPC_User.h, windows.h not included, so we have to import it first
|
||||||
#include "FSUIPC/FSUIPC_User.h"
|
#include "FSUIPC/FSUIPC_User.h"
|
||||||
|
#include "FSUIPC/NewWeather.h"
|
||||||
|
|
||||||
#include "blackmisc/simulation/fscommon/bcdconversions.h"
|
#include "blackmisc/simulation/fscommon/bcdconversions.h"
|
||||||
#include "blackmisc/logmessage.h"
|
#include "blackmisc/logmessage.h"
|
||||||
@@ -29,13 +30,17 @@ using namespace BlackMisc::Network;
|
|||||||
using namespace BlackMisc::Geo;
|
using namespace BlackMisc::Geo;
|
||||||
using namespace BlackMisc::Simulation;
|
using namespace BlackMisc::Simulation;
|
||||||
using namespace BlackMisc::PhysicalQuantities;
|
using namespace BlackMisc::PhysicalQuantities;
|
||||||
|
using namespace BlackMisc::Weather;
|
||||||
|
|
||||||
namespace BlackSimPlugin
|
namespace BlackSimPlugin
|
||||||
{
|
{
|
||||||
namespace FsCommon
|
namespace FsCommon
|
||||||
{
|
{
|
||||||
|
|
||||||
CFsuipc::CFsuipc() { }
|
CFsuipc::CFsuipc()
|
||||||
|
{
|
||||||
|
startTimer(100);
|
||||||
|
}
|
||||||
|
|
||||||
CFsuipc::~CFsuipc()
|
CFsuipc::~CFsuipc()
|
||||||
{
|
{
|
||||||
@@ -89,6 +94,121 @@ namespace BlackSimPlugin
|
|||||||
return false;
|
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<char>(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<unsigned char>(cloudLayer.getPrecipitationRate());
|
||||||
|
cloud.PrecipType = static_cast<unsigned char>(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<const char *>(&nw), sizeof(NewWeather));
|
||||||
|
m_weatherMessageQueue.append(FsuipcWeatherMessage(0xC800, weatherData, 5));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool CFsuipc::read(CSimulatedAircraft &aircraft, bool cockpit, bool situation, bool aircraftParts)
|
bool CFsuipc::read(CSimulatedAircraft &aircraft, bool cockpit, bool situation, bool aircraftParts)
|
||||||
{
|
{
|
||||||
DWORD dwResult;
|
DWORD dwResult;
|
||||||
@@ -265,6 +385,68 @@ namespace BlackSimPlugin
|
|||||||
return read;
|
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<const char *>(&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<void *>(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 CFsuipc::intToFractional(double fractional)
|
||||||
{
|
{
|
||||||
double f = fractional / 10.0;
|
double f = fractional / 10.0;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#define BLACKSIMPLUGIN_FSUIPC_H
|
#define BLACKSIMPLUGIN_FSUIPC_H
|
||||||
|
|
||||||
#include "blackmisc/simulation/simulatedaircraft.h"
|
#include "blackmisc/simulation/simulatedaircraft.h"
|
||||||
|
#include "blackmisc/weather/weathergrid.h"
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
|
||||||
namespace BlackSimPlugin
|
namespace BlackSimPlugin
|
||||||
@@ -20,8 +21,10 @@ namespace BlackSimPlugin
|
|||||||
namespace FsCommon
|
namespace FsCommon
|
||||||
{
|
{
|
||||||
//! Class representing a FSUIPC "interface"
|
//! Class representing a FSUIPC "interface"
|
||||||
class CFsuipc
|
class CFsuipc : public QObject
|
||||||
{
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
//! Constructor
|
//! Constructor
|
||||||
@@ -45,6 +48,9 @@ namespace BlackSimPlugin
|
|||||||
//! Write variables
|
//! Write variables
|
||||||
bool write(const BlackMisc::Simulation::CSimulatedAircraft &aircraft);
|
bool write(const BlackMisc::Simulation::CSimulatedAircraft &aircraft);
|
||||||
|
|
||||||
|
//! Write weather grid to simulator
|
||||||
|
bool write(const BlackMisc::Weather::CWeatherGrid &weatherGrid);
|
||||||
|
|
||||||
//! Read data from FSUIPC
|
//! Read data from FSUIPC
|
||||||
//! \param aircraft object to be updated
|
//! \param aircraft object to be updated
|
||||||
//! \param cockpit update cockpit data
|
//! \param cockpit update cockpit data
|
||||||
@@ -97,11 +103,30 @@ namespace BlackSimPlugin
|
|||||||
//! Log message category
|
//! Log message category
|
||||||
static QString getMessageCategory() { return "swift.fscommon.fsuipc"; }
|
static QString getMessageCategory() { return "swift.fscommon.fsuipc"; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
//! \copydoc QObject::timerEvent
|
||||||
|
void timerEvent(QTimerEvent *event);
|
||||||
|
|
||||||
private:
|
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;
|
bool m_connected = false;
|
||||||
QString m_lastErrorMessage;
|
QString m_lastErrorMessage;
|
||||||
QString m_fsuipcVersion;
|
QString m_fsuipcVersion;
|
||||||
|
|
||||||
|
QVector<FsuipcWeatherMessage> m_weatherMessageQueue;
|
||||||
|
unsigned int m_lastTimestamp = 0;
|
||||||
|
|
||||||
//! Integer representing fractional
|
//! Integer representing fractional
|
||||||
static double intToFractional(double fractional);
|
static double intToFractional(double fractional);
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ using namespace BlackMisc::Simulation;
|
|||||||
using namespace BlackMisc::Simulation;
|
using namespace BlackMisc::Simulation;
|
||||||
using namespace BlackMisc::Simulation::FsCommon;
|
using namespace BlackMisc::Simulation::FsCommon;
|
||||||
using namespace BlackMisc::Simulation::Fsx;
|
using namespace BlackMisc::Simulation::Fsx;
|
||||||
|
using namespace BlackMisc::Weather;
|
||||||
using namespace BlackCore;
|
using namespace BlackCore;
|
||||||
|
|
||||||
namespace BlackSimPlugin
|
namespace BlackSimPlugin
|
||||||
@@ -50,7 +51,7 @@ namespace BlackSimPlugin
|
|||||||
Q_ASSERT(remoteAircraftProvider);
|
Q_ASSERT(remoteAircraftProvider);
|
||||||
this->m_simulatorSetup = CFsxSimulatorSetup::getInitialSetup();
|
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);
|
this->m_interpolator = new CInterpolatorLinear(remoteAircraftProvider, this);
|
||||||
m_modelMatcher.setDefaultModel(CAircraftModel(
|
m_modelMatcher.setDefaultModel(CAircraftModel(
|
||||||
"Boeing 737-800 Paint1",
|
"Boeing 737-800 Paint1",
|
||||||
@@ -95,6 +96,10 @@ namespace BlackSimPlugin
|
|||||||
m_simconnectTimerId = startTimer(10);
|
m_simconnectTimerId = startTimer(10);
|
||||||
m_simConnected = true;
|
m_simConnected = true;
|
||||||
emitSimulatorCombinedStatus();
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -824,6 +829,11 @@ namespace BlackSimPlugin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CSimulatorFsx::injectWeatherGrid(const Weather::CWeatherGrid &weatherGrid)
|
||||||
|
{
|
||||||
|
m_fsuipc->write(weatherGrid);
|
||||||
|
}
|
||||||
|
|
||||||
CSimulatorFsxListener::CSimulatorFsxListener(const CSimulatorPluginInfo &info) :
|
CSimulatorFsxListener::CSimulatorFsxListener(const CSimulatorPluginInfo &info) :
|
||||||
ISimulatorListener(info),
|
ISimulatorListener(info),
|
||||||
m_timer(new QTimer(this))
|
m_timer(new QTimer(this))
|
||||||
|
|||||||
@@ -179,6 +179,9 @@ namespace BlackSimPlugin
|
|||||||
//! Sync time with user's computer
|
//! Sync time with user's computer
|
||||||
void synchronizeTime(const BlackMisc::PhysicalQuantities::CTime &zuluTimeSim, const BlackMisc::PhysicalQuantities::CTime &localTimeSim);
|
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
|
static const int SkipUpdateCyclesForCockpit = 10; //!< skip x cycles before updating cockpit again
|
||||||
bool m_simConnected = false; //!< Is simulator connected?
|
bool m_simConnected = false; //!< Is simulator connected?
|
||||||
bool m_simSimulating = false; //!< Simulator running?
|
bool m_simSimulating = false; //!< Simulator running?
|
||||||
|
|||||||
Reference in New Issue
Block a user