Files
pilotclient/src/blackmisc/simulation/fsx/simconnectutilities.cpp
Roland Rossgotterer 9d1299741e [GFS] Use Pressure at MSL instead of surface
Pressure at surface level was the wrong value. All barometers and
altimeters use pressure reduced to mean sea level. Reading that value
from GFS instead returns much more reasonable values.

ref T537
2019-02-22 20:36:20 +00:00

472 lines
22 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 "blackmisc/simulation/fsx/simconnectutilities.h"
#include <QCoreApplication>
#include <QFile>
#include <QFlags>
#include <QIODevice>
#include <QLatin1String>
#include <QMetaEnum>
#include <QMetaObject>
#include <QTextStream>
#include <QStandardPaths>
#include <QStringBuilder>
using namespace BlackMisc::Aviation;
using namespace BlackMisc::PhysicalQuantities;
using namespace BlackMisc::Weather;
namespace BlackMisc
{
namespace Simulation
{
namespace Fsx
{
CSimConnectUtilities::CSimConnectUtilities() { }
const QString &CSimConnectUtilities::simConnectFilename()
{
static const QString fn("SimConnect.cfg");
return fn;
}
const QString &CSimConnectUtilities::getSwiftLocalSimConnectCfgFilename()
{
static const QString n = CFileUtils::appendFilePaths(QCoreApplication::applicationDirPath(), simConnectFilename());
return n;
}
const QString &CSimConnectUtilities::getUserSimConnectCfgFilename()
{
static const QString n = CFileUtils::appendFilePaths(QStandardPaths::locate(QStandardPaths::DocumentsLocation, "", QStandardPaths::LocateDirectory), simConnectFilename());
return n;
}
bool CSimConnectUtilities::hasSwiftLocalSimConnectCfgFile()
{
const QFile f(getSwiftLocalSimConnectCfgFilename());
return f.exists();
}
bool CSimConnectUtilities::hasUserSimConnectCfgFile()
{
const QFile f(getUserSimConnectCfgFilename());
return f.exists();
}
QSharedPointer<QSettings> CSimConnectUtilities::simConnectFileAsSettings(const QString &fileName)
{
QSharedPointer<QSettings> sp;
const QFile file(fileName);
if (!file.exists()) { return sp; }
sp.reset(new QSettings(fileName, QSettings::IniFormat));
return sp;
}
QString CSimConnectUtilities::ipAddress(const QSettings *simConnectSettings)
{
if (!simConnectSettings) { return {}; }
return simConnectSettings->value("SimConnect/Address").toString();
}
int CSimConnectUtilities::ipPort(const QSettings *simConnectSettings)
{
if (!simConnectSettings) { return -1; }
return simConnectSettings->value("SimConnect/Port", QVariant::fromValue(-1)).toInt();
}
bool CSimConnectUtilities::writeSimConnectCfg(const QString &fileName, const QString &ip, int port)
{
const QString sc = CSimConnectUtilities::simConnectCfg(ip, port);
QFile file(fileName);
bool success = false;
if ((success = file.open(QIODevice::WriteOnly | QIODevice::Text)))
{
QTextStream out(&file);
out << sc;
file.close();
}
return success;
}
QString CSimConnectUtilities::simConnectCfg(const QString &ip, int port)
{
const QString sc = QStringLiteral("[SimConnect]\nProtocol=Ipv4\nAddress=%1\nPort=%2\n"
"MaxReceiveSize=4096\nDisableNagle=0").arg(ip).arg(port);
return sc;
}
QString CSimConnectUtilities::resolveEnumToString(const DWORD id, const char *enumName)
{
const int i = CSimConnectUtilities::staticMetaObject.indexOfEnumerator(enumName);
if (i < 0) { return QStringLiteral("No enumerator for %1").arg(enumName); }
const QMetaEnum m = CSimConnectUtilities::staticMetaObject.enumerator(i);
const char *k = m.valueToKey(static_cast<int>(id));
return (k) ? QLatin1String(k) : QStringLiteral("Id '%1' not found for %2").arg(id).arg(enumName);
}
const QString &CSimConnectUtilities::simConnectIniFilename()
{
static const QString n("SimConnect.ini");
return n;
}
QString CSimConnectUtilities::simConnectExceptionToString(const DWORD id)
{
return CSimConnectUtilities::resolveEnumToString(id, "SIMCONNECT_EXCEPTION");
}
QString CSimConnectUtilities::simConnectSurfaceTypeToString(const DWORD type, bool beautify)
{
QString sf = CSimConnectUtilities::resolveEnumToString(type, "SIMCONNECT_SURFACE");
return beautify ? sf.replace('_', ' ') : sf;
}
QStringList CSimConnectUtilities::getSimConnectIniFileDirectories()
{
const QString docDir = QStandardPaths::locate(QStandardPaths::DocumentsLocation, "", QStandardPaths::LocateDirectory);
if (docDir.isEmpty()) return QStringList();
QDir directory(docDir);
directory.setFilter(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks);
const QStringList dirList = directory.entryList();
QStringList simDirs;
for (const QString &dir : dirList)
{
if (dir.contains("Flight Simulator", Qt::CaseInsensitive) || dir.contains("Prepar3D", Qt::CaseInsensitive))
{
simDirs.push_back(CFileUtils::appendFilePaths(docDir, dir));
}
}
// gets the latest P3D as first
simDirs.sort();
std::reverse(std::begin(simDirs), std::end(simDirs));
return simDirs;
}
QStringList CSimConnectUtilities::getSimConnectIniFiles()
{
QStringList files;
for (const QString &dir : getSimConnectIniFileDirectories())
{
const QFileInfo f(CFileUtils::appendFilePaths(dir, simConnectIniFilename()));
if (f.exists()) { files.push_back(f.absoluteFilePath()); }
}
return files;
}
QString CSimConnectUtilities::getSimConnectIniFileDirectory(CSimulatorInfo &simulator)
{
static const QString docDir = QStandardPaths::locate(QStandardPaths::DocumentsLocation, "", QStandardPaths::LocateDirectory);
if (docDir.isEmpty()) { return {}; }
if (!simulator.isSingleSimulator() || !simulator.isFsxP3DFamily()) return {};
const QString iniDir = CFileUtils::appendFilePaths(docDir, simulator.isP3D() ? "Prepar3D v4 Files" : "Flight Simulator X Files");
if (getSimConnectIniFileDirectories().isEmpty()) { return iniDir; }
for (const QString &dir : getSimConnectIniFileDirectories())
{
if (simulator.isP3D())
{
if (dir.contains("Prepar3D", Qt::CaseInsensitive)) { return dir; }
}
else if (simulator.isFSX())
{
if (dir.contains("Flight Simulator", Qt::CaseInsensitive)) { return dir; }
}
}
return iniDir;
}
QString CSimConnectUtilities::simConnectReceiveIdToString(DWORD type)
{
const QString ri = CSimConnectUtilities::resolveEnumToString(type, "SIMCONNECT_RECV_ID");
return ri;
}
int CSimConnectUtilities::lightsToLightStates(const CAircraftLights &lights)
{
int lightMask = 0;
if (lights.isBeaconOn()) { lightMask |= Beacon; }
if (lights.isLandingOn()) { lightMask |= Landing; }
if (lights.isLogoOn()) { lightMask |= Logo; }
if (lights.isNavOn()) { lightMask |= Nav; }
if (lights.isStrobeOn()) { lightMask |= Strobe; }
if (lights.isTaxiOn()) { lightMask |= Taxi; }
return lightMask;
}
QString CSimConnectUtilities::convertToSimConnectMetar(const CGridPoint &gridPoint)
{
// STATION ID
Q_ASSERT(!gridPoint.getIdentifier().isEmpty());
QString simconnectMetar = gridPoint.getIdentifier();
// SURFACE WINDS/WINDS ALOFT
CWindLayerList windLayers = gridPoint.getWindLayers();
windLayers.sortBy(&CWindLayer::getLevel);
simconnectMetar += windsToSimConnectMetar(windLayers);
// VISIBILITY
CVisibilityLayerList visibilityLayers = gridPoint.getVisibilityLayers();
visibilityLayers.sortBy(&CVisibilityLayer::getBase);
simconnectMetar += visibilitiesToSimConnectMetar(visibilityLayers);
// PRESENT CONDITIONS
// todo
// PARTIAL OBSCURATION
// todo
// SKY CONDITIONS
CCloudLayerList cloudLayers = gridPoint.getCloudLayers();
cloudLayers.sortBy(&CCloudLayer::getBase);
simconnectMetar += cloudsToSimConnectMetar(cloudLayers);
// TEMPERATURE
CTemperatureLayerList temperatureLayers = gridPoint.getTemperatureLayers();
temperatureLayers.sortBy(&CTemperatureLayer::getLevel);
simconnectMetar += temperaturesToSimConnectMetar(temperatureLayers);
// ALTIMETER
// Format:
// QNNNN
// Q = specifier for altimeter in millibars
simconnectMetar += QLatin1String(" Q");
// NNNN = altimeter in millibars
static const QString arg1s("%1");
const auto altimeter = gridPoint.getPressureAtMsl().valueInteger(CPressureUnit::mbar());
simconnectMetar += arg1s.arg(altimeter, 4, 10, QLatin1Char('0'));
return simconnectMetar;
}
void CSimConnectUtilities::registerMetadata()
{
qRegisterMetaType<CSimConnectUtilities::SIMCONNECT_EXCEPTION>();
qRegisterMetaType<CSimConnectUtilities::SIMCONNECT_SURFACE>();
}
QString CSimConnectUtilities::windsToSimConnectMetar(const CWindLayerList &windLayers)
{
static const QString arg1s("%1");
QString simconnectWinds;
bool surface = true;
for (const CWindLayer &windLayer : windLayers)
{
simconnectWinds += QLatin1Char(' ');
// Format:
// DDDSSSUUU (steady)
// DDDSSSGXXUUU (gusts)
if (windLayer.isDirectionVariable())
{
// DDD = VRB for variable
simconnectWinds += QLatin1String("VRB");
}
else
{
const int speed = windLayer.getSpeed().valueInteger(CSpeedUnit::kts());
const int direction = windLayer.getDirection().valueInteger(CAngleUnit::deg());
simconnectWinds += arg1s.arg(direction, 3, 10, QLatin1Char('0')) % // DDD = Direction (0-360 degrees)
arg1s.arg(speed, 3, 10, QLatin1Char('0')); // SSS = Speed
}
// XX = Gust speed
const int gustSpeed = windLayer.getGustSpeed().valueInteger(CSpeedUnit::kts());
if (gustSpeed > 0) { simconnectWinds += u'G' % arg1s.arg(gustSpeed, 2, 10, QLatin1Char('0')); }
// UUU = Speed units
simconnectWinds += QStringLiteral("KT");
if (surface)
{
// Surface extension:
// &DNNNNTS
static const QString surfaceWinds =
"&D" // D = specifier for surface layer
"305" // Surface default depth is 1000 feet or 305m
"NG"; // We don't have turbulence or wind shear information, hence we use the defaults
simconnectWinds += surfaceWinds;
surface = false;
}
else
{
auto altitude = windLayer.getLevel();
altitude.toMeanSeaLevel();
int altitudeValue = altitude.valueInteger(CLengthUnit::m());
// Winds aloft extension:
// &ANNNNTS
simconnectWinds +=
u"&A" % // A = specifier for altitude above mean sea-level (MSL)
arg1s.arg(altitudeValue, 4, 10, QLatin1Char('0')) % // NNNN = depth (height) in meters.
u"NG"; // We don't have turbulence or wind shear information, hence we use the defaults
}
}
return simconnectWinds;
}
QString CSimConnectUtilities::visibilitiesToSimConnectMetar(const CVisibilityLayerList &visibilityLayers)
{
// There are several format options, we use the meter format:
// NNNND&BXXXX&DYYYY
QString simconnectVisibilities;
for (const auto &visibilityLayer : visibilityLayers)
{
simconnectVisibilities += QLatin1Char(' ');
// NNNN = in meters
auto visibility = visibilityLayer.getVisibility().valueInteger(CLengthUnit::m());
visibility = qMin(9999, visibility);
simconnectVisibilities += QStringLiteral("%1").arg(visibility, 4, 10, QLatin1Char('0'));
// D = directional variation
// We set NDV - no directional variation
simconnectVisibilities += QLatin1String("NDV");
// XXXX = base of visibility layer in meters
const auto base = visibilityLayer.getBase().valueInteger(CLengthUnit::m());
simconnectVisibilities += QStringLiteral("&B%1").arg(base, 4, 10, QLatin1Char('0'));
// YYYY = depth of visibility layer in meters
const auto depth = visibilityLayer.getTop().valueInteger(CLengthUnit::m());
simconnectVisibilities += QStringLiteral("&D%1").arg(depth, 4, 10, QLatin1Char('0'));
}
return simconnectVisibilities;
}
QString CSimConnectUtilities::cloudsToSimConnectMetar(const CCloudLayerList &cloudLayers)
{
// Format:
// CCCNNN&BXXXX&DYYYY
QString simconnectClouds;
static const QString arg1s("%1");
for (const CCloudLayer &cloudLayer : cloudLayers)
{
simconnectClouds += QLatin1Char(' ');
// CCC = Coverage string
switch (cloudLayer.getCoverage())
{
case CCloudLayer::None: simconnectClouds += QLatin1String("CLR"); break;
case CCloudLayer::Few: simconnectClouds += QLatin1String("FEW"); break;
case CCloudLayer::Broken: simconnectClouds += QLatin1String("BKN"); break;
case CCloudLayer::Overcast: simconnectClouds += QLatin1String("OVC"); break;
case CCloudLayer::Scattered:
default:
simconnectClouds += QLatin1String("SCT");
}
// NNN = coded height
// If NNN is 999 the level is 100,000 feet, otherwise it is 100 x NNN in feet
auto level = cloudLayer.getTop().valueInteger(CLengthUnit::ft()) / 100;
// Ignore clouds higher than 99900 feet
if (level > 999) { continue; }
simconnectClouds += arg1s.arg(level, 3, 10, QLatin1Char('0')) %
u'&';
// TT = Cloud type
switch (cloudLayer.getClouds())
{
case CCloudLayer::Cirrus: simconnectClouds += QLatin1String("CI"); break;
case CCloudLayer::Stratus: simconnectClouds += QLatin1String("ST"); break;
case CCloudLayer::Thunderstorm: simconnectClouds += QLatin1String("CB"); break;
case CCloudLayer::Cumulus:
default:
simconnectClouds += QLatin1String("CU");
}
// 000 - Unused.
simconnectClouds += QLatin1String("000");
// F = Top of cloud
// Default to F - flat
simconnectClouds += QLatin1Char('F');
// T = Turbulence
// N - None (default)
simconnectClouds += QLatin1Char('N');
// P = precipitation rate
// http://wiki.sandaysoft.com/a/Rain_measurement#Rain_Rate
auto precipitationRate = cloudLayer.getPrecipitationRate();
// Very light rain: precipitation rate is < 0.25 mm/hour
if (precipitationRate < 0.25) { simconnectClouds += QLatin1Char('V'); }
// Light rain: precipitation rate is between 0.25mm/hour and 1.0mm/hour
else if (precipitationRate >= 0.25 && precipitationRate < 1.0) { simconnectClouds += QLatin1Char('L'); }
// Moderate rain: precipitation rate is between 1.0 mm/hour and 4.0 mm/hour
else if (precipitationRate >= 1.0 && precipitationRate < 4.0) { simconnectClouds += QLatin1Char('M'); }
// Heavy rain: recipitation rate is between 4.0 mm/hour and 16.0 mm/hour
else if (precipitationRate >= 4.0 && precipitationRate < 16.0) { simconnectClouds += QLatin1Char('H'); }
// Very heavy rain: precipitation rate is > 16.0 mm/hour
else if (precipitationRate >= 16.0) { simconnectClouds += QLatin1Char('D'); }
// Q = Type of precipitation
switch (cloudLayer.getPrecipitation())
{
case CCloudLayer::Rain: simconnectClouds += QLatin1Char('R'); break;
case CCloudLayer::Snow: simconnectClouds += QLatin1Char('S'); break;
default: simconnectClouds += QLatin1Char('N');
}
// BBB = Coded base height
// the precipitation ends at this height, set to 0 for it to land on the ground
simconnectClouds += QLatin1String("000");
// I = icing rate
// Set to None for now
simconnectClouds += QLatin1String("N");
}
return simconnectClouds;
}
QString CSimConnectUtilities::temperaturesToSimConnectMetar(const CTemperatureLayerList &temperatureLayers)
{
// Format:
// TT/DD&ANNNNN
QString simconnectTemperatures;
static const QString arg1s("%1");
for (const CTemperatureLayer &temperatureLayer : temperatureLayers)
{
simconnectTemperatures += QLatin1Char(' ');
const int temperature = temperatureLayer.getTemperature().valueInteger(CTemperatureUnit::C());
const int dewPoint = temperatureLayer.getDewPoint().valueInteger(CTemperatureUnit::C());
const int altitude = temperatureLayer.getLevel().valueInteger(CLengthUnit::m());
simconnectTemperatures += arg1s.arg(temperature, 2, 10, QLatin1Char('0')) % // TT = temperature in Celsius
u'/' %
arg1s.arg(dewPoint, 2, 10, QLatin1Char('0')) % // DD = dewpoint in Celsius
u"&A" %
arg1s.arg(altitude, 5, 10, QLatin1Char('0')); // NNNNN = altitude of the temperatures in meters.
}
return simconnectTemperatures;
}
CWinDllUtils::DLLInfo CSimConnectUtilities::simConnectDllInfo()
{
const QList<CWinDllUtils::ProcessModule> modules = CWinDllUtils::getModules(-1, "simconnect");
if (modules.isEmpty())
{
CWinDllUtils::DLLInfo info;
info.errorMsg = "No SimConnect.dll loaded";
return info;
}
return CWinDllUtils::getDllInfo(modules.first().executable);
}
} // namespace
} // namespace
} // namespace