From 1a3a1e175e5aec750006528f36ab32e0dcac850b Mon Sep 17 00:00:00 2001 From: Mathew Sutcliffe Date: Sun, 22 Jun 2014 02:05:39 +0100 Subject: [PATCH] refs #272 added new DBus service object XBus::CTraffic providing access to traffic aircraft in the sim --- src/xbus/main.cpp | 3 + src/xbus/plugin.cpp | 4 + src/xbus/plugin.h | 5 +- src/xbus/traffic.cpp | 310 +++++++++++++++++++++++++++++++++++++++++++ src/xbus/traffic.h | 131 ++++++++++++++++++ 5 files changed, 452 insertions(+), 1 deletion(-) create mode 100644 src/xbus/traffic.cpp create mode 100644 src/xbus/traffic.h diff --git a/src/xbus/main.cpp b/src/xbus/main.cpp index 26ea53113..30b70dfdd 100644 --- a/src/xbus/main.cpp +++ b/src/xbus/main.cpp @@ -6,6 +6,7 @@ #define NOMINMAX #include "plugin.h" #include "utils.h" +#include "traffic.h" #include #if ! defined(XPLM210) @@ -20,6 +21,8 @@ PLUGIN_API int XPluginStart(char *o_name, char *o_sig, char *o_desc) std::strcpy(o_name, "X-Bus"); std::strcpy(o_sig, "net.vatsim.XBus"); std::strcpy(o_desc, "Allows pilot client to connect to X-Plane via D-Bus"); + + XBus::CTraffic::initLegacyData(); return 1; } diff --git a/src/xbus/plugin.cpp b/src/xbus/plugin.cpp index f136f38ff..dd91c25dc 100644 --- a/src/xbus/plugin.cpp +++ b/src/xbus/plugin.cpp @@ -4,6 +4,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "plugin.h" +#include "service.h" +#include "traffic.h" #define XBUS_SERVICE_SERVICENAME "net.vatsim.xbus" @@ -25,7 +27,9 @@ namespace XBus m_server = new BlackCore::CDBusServer(XBUS_SERVICE_SERVICENAME, address, this); m_service = new CService(this); + m_traffic = new CTraffic(this); m_server->addObject(CService::ObjectPath(), m_service); + m_server->addObject(CTraffic::ObjectPath(), m_traffic); } void CPlugin::onAircraftModelChanged() diff --git a/src/xbus/plugin.h b/src/xbus/plugin.h index 6565fb8e5..72f8c6b30 100644 --- a/src/xbus/plugin.h +++ b/src/xbus/plugin.h @@ -15,7 +15,6 @@ #define NOMINMAX #include "menus.h" -#include "service.h" #pragma push_macro("interface") #undef interface @@ -26,6 +25,9 @@ namespace XBus { + class CService; + class CTraffic; + /*! * Main plugin class */ @@ -43,6 +45,7 @@ namespace XBus private: BlackCore::CDBusServer *m_server = nullptr; CService *m_service = nullptr; + CTraffic *m_traffic = nullptr; CMenu m_menu; QVector m_startServerMenuItems; diff --git a/src/xbus/traffic.cpp b/src/xbus/traffic.cpp new file mode 100644 index 000000000..ca2dd931e --- /dev/null +++ b/src/xbus/traffic.cpp @@ -0,0 +1,310 @@ +/* Copyright (C) 2013 VATSIM Community / contributors + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#define NOMINMAX +#include "traffic.h" +#include "XPMPMultiplayer.h" +#include +#include +#include +#include + +namespace XBus +{ + + CTraffic::Plane::Plane(void *id_, QString callsign_, QString aircraftIcao_, QString airlineIcao_, QString livery_) + : id(id_), callsign(callsign_), aircraftIcao(aircraftIcao_), airlineIcao(airlineIcao_), livery(livery_) + { + position0.size = sizeof(position0); + position1.size = sizeof(position1); + surfaces.size = sizeof(surfaces); + xpdr.size = sizeof(xpdr); + + std::strncpy(position1.label, qPrintable(callsign), sizeof(position1.label)); + surfaces.lights.timeOffset = static_cast(qrand() % 0xffff); + } + + CTraffic::CTraffic(QObject *parent) : QObject(parent) + { + } + + CTraffic::~CTraffic() + { + cleanup(); + } + + bool s_legacyDataOK = true; + + void CTraffic::initLegacyData() + { + char xplanePath[512]; + XPLMGetSystemPath(xplanePath); + auto dir = QString(xplanePath) + "Resources" + XPLMGetDirectorySeparator() + "plugins" + XPLMGetDirectorySeparator() + + "xbus" + XPLMGetDirectorySeparator() + "LegacyData" + XPLMGetDirectorySeparator(); + + auto err = XPMPMultiplayerInitLegacyData(qPrintable(dir + "CSL"), qPrintable(dir + "related.txt"), + qPrintable(dir + "lights.png"), qPrintable(dir + "Doc8643.txt"), "C172", preferences, preferences); + if (*err) { s_legacyDataOK = false; } + } + + bool CTraffic::initialize() + { + if (! s_legacyDataOK) { return false; } + + auto err = XPMPMultiplayerInit(preferences, preferences); + if (*err) { cleanup(); return false; } + m_initialized = true; + + err = XPMPMultiplayerEnable(); + if (*err) { cleanup(); return false; } + m_enabled = true; + + XPMPLoadPlanesIfNecessary(); + return true; + } + + void CTraffic::cleanup() + { + removeAllPlanes(); + + if (m_enabled) + { + m_enabled = false; + XPMPMultiplayerDisable(); + } + + if (m_initialized) + { + m_initialized = false; + XPMPMultiplayerCleanup(); + } + } + + int CTraffic::preferences(const char *section, const char *name, int def) + { + // TODO [planes] max_full_count + return def; + } + + float CTraffic::preferences(const char *section, const char *name, float def) + { + // TODO [planes] full_distance + return def; + } + + bool CTraffic::loadPlanesPackage(const QString &path) + { + char xplanePath[512]; + XPLMGetSystemPath(xplanePath); + auto dir = QString(xplanePath) + "Resources" + XPLMGetDirectorySeparator() + "plugins" + XPLMGetDirectorySeparator() + + "xbus" + XPLMGetDirectorySeparator() + "LegacyData" + XPLMGetDirectorySeparator(); + + auto err = XPMPLoadCSLPackage(qPrintable(path), qPrintable(dir + "related.txt"), qPrintable(dir + "Doc8643.txt")); + if (*err) { return false; } + return true; + } + + void CTraffic::setDefaultIcao(const QString &defaultIcao) + { + XPMPSetDefaultPlaneICAO(qPrintable(defaultIcao)); + } + + void CTraffic::setDrawingLabels(bool drawing) + { + if (drawing) + { + XPMPEnableAircraftLabels(); + } + else + { + XPMPDisableAircraftLabels(); + } + } + + bool CTraffic::isDrawingLabels() const + { + return XPMPDrawingAircraftLabels(); + } + + void CTraffic::addPlane(const QString &callsign, const QString &aircraftIcao, const QString &airlineIcao, const QString &livery) + { + auto id = XPMPCreatePlane(qPrintable(aircraftIcao), qPrintable(airlineIcao), qPrintable(livery), getPlaneData, static_cast(this)); + auto plane = new Plane(id, callsign, aircraftIcao, airlineIcao, livery); + m_planesByCallsign[callsign] = plane; + m_planesById[id] = plane; + } + + void CTraffic::removePlane(const QString &callsign) + { + auto plane = m_planesByCallsign.value(callsign, nullptr); + if (plane) + { + m_planesByCallsign.remove(callsign); + m_planesById.remove(plane->id); + XPMPDestroyPlane(plane->id); + delete plane; + } + } + + void CTraffic::removeAllPlanes() + { + for (auto plane : m_planesByCallsign) + { + XPMPDestroyPlane(plane->id); + delete plane; + } + m_planesByCallsign.clear(); + m_planesById.clear(); + } + + void CTraffic::setPlanePosition(const QString &callsign, double latitude, double longitude, double altitude, double pitch, double roll, double heading) + { + const auto plane = m_planesByCallsign.value(callsign, nullptr); + if (plane) + { + plane->time0 = plane->time1; + plane->time1 = QDateTime::currentMSecsSinceEpoch(); + + std::memcpy(reinterpret_cast(&plane->position0) + sizeof(plane->position0.size), + reinterpret_cast(&plane->position1) + sizeof(plane->position1.size), + sizeof(plane->position0) - sizeof(plane->position0.size)); + + plane->position1.lat = latitude; + plane->position1.lon = longitude; + plane->position1.elevation = altitude; + plane->position1.pitch = static_cast(pitch); + plane->position1.roll = static_cast(roll); + plane->position1.heading = static_cast(heading); + } + } + + void CTraffic::setPlaneSurfaces(const QString &callsign, double gear, double flap, double spoiler, double speedBrake, double slat, double wingSweep, double thrust, + double elevator, double rudder, double aileron, bool landLight, bool beaconLight, bool strobeLight, bool navLight, int lightPattern) + { + const auto plane = m_planesByCallsign.value(callsign, nullptr); + if (plane) + { + plane->hasSurfaces = true; + plane->surfaces.gearPosition = gear; + plane->surfaces.flapRatio = flap; + plane->surfaces.spoilerRatio = spoiler; + plane->surfaces.speedBrakeRatio = speedBrake; + plane->surfaces.slatRatio = slat; + plane->surfaces.wingSweep = wingSweep; + plane->surfaces.thrust = thrust; + plane->surfaces.yokePitch = elevator; + plane->surfaces.yokeHeading = rudder; + plane->surfaces.yokeRoll = aileron; + plane->surfaces.lights.landLights = landLight; + plane->surfaces.lights.bcnLights = beaconLight; + plane->surfaces.lights.strbLights = strobeLight; + plane->surfaces.lights.navLights = navLight; + plane->surfaces.lights.flashPattern = lightPattern; + } + } + + void CTraffic::setPlaneTransponder(const QString &callsign, int code, bool modeC, bool ident) + { + const auto plane = m_planesByCallsign.value(callsign, nullptr); + if (plane) + { + plane->hasXpdr = true; + plane->xpdr.code = code; + if (ident) { plane->xpdr.mode = xpmpTransponderMode_ModeC_Ident; } + else if (modeC) { plane->xpdr.mode = xpmpTransponderMode_ModeC; } + else { plane->xpdr.mode = xpmpTransponderMode_Standby; } + } + } + + // memcmp function which ignores the header ("size" member) and compares only the payload (the rest of the struct) + template + int memcmpPayload(T *dst, T *src) + { + return std::memcmp(reinterpret_cast(dst) + sizeof(dst->size), + reinterpret_cast(src) + sizeof(src->size), + sizeof(*dst) - sizeof(dst->size)); + } + + // linearly interpolate angle in degrees + template + T lerpDegrees(T from, T to, double factor) + { + if (std::fabs(to - from) > 180) + { + if (to > from) { to -= 360; } + else { to += 360; } + } + return from + (to - from) * static_cast(factor); + } + + int CTraffic::getPlaneData(void *id, int dataType, void *io_data) + { + auto plane = m_planesById.value(id, nullptr); + if (! plane) { return xpmpData_Unavailable; } + + switch (dataType) + { + case xpmpDataType_Position: + if (plane->time1) + { + const auto io_position = static_cast(io_data); + + if (plane->time0) // we have two positions between which to interpolate + { + const auto currentTime = QDateTime::currentMSecsSinceEpoch(); + const auto factor = static_cast(currentTime - plane->time0) / (plane->time1 - plane->time0); + io_position->lat = lerpDegrees(plane->position0.lat, plane->position1.lat, factor); + io_position->lon = lerpDegrees(plane->position0.lon, plane->position1.lon, factor); + io_position->pitch = lerpDegrees(plane->position0.pitch, plane->position1.pitch, factor); + io_position->roll = lerpDegrees(plane->position0.roll, plane->position1.roll, factor); + io_position->heading = lerpDegrees(plane->position0.heading, plane->position1.heading, factor); + io_position->elevation = plane->position0.elevation + (plane->position1.elevation - plane->position0.elevation) * factor; + return xpmpData_NewData; + } + else // we only received one position so far + { + if (memcmpPayload(io_position, &plane->position1)) + { + std::memcpy(io_position, &plane->position1, sizeof(*io_position)); + return xpmpData_NewData; + } + else { return xpmpData_Unchanged; } + } + } + else { return xpmpData_Unavailable; } + + case xpmpDataType_Surfaces: + if (plane->hasSurfaces) + { + const auto io_surfaces = static_cast(io_data); + + if (memcmpPayload(io_surfaces, &plane->surfaces)) + { + std::memcpy(io_surfaces, &plane->surfaces, sizeof(*io_surfaces)); + return xpmpData_NewData; + } + else { return xpmpData_Unchanged; } + } + else { return xpmpData_Unavailable; } + + case xpmpDataType_Radar: + if (plane->hasXpdr) + { + const auto io_xpdr = static_cast(io_data); + + if (memcmpPayload(io_xpdr, &plane->xpdr)) + { + std::memcpy(io_xpdr, &plane->xpdr, sizeof(*io_xpdr)); + return xpmpData_NewData; + } + else { return xpmpData_Unchanged; } + } + else { return xpmpData_Unavailable; } + + default: return xpmpData_Unavailable; + } + } + +} diff --git a/src/xbus/traffic.h b/src/xbus/traffic.h new file mode 100644 index 000000000..d5dcd12af --- /dev/null +++ b/src/xbus/traffic.h @@ -0,0 +1,131 @@ +/* Copyright (C) 2013 VATSIM Community / contributors + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef BLACKSIM_XBUS_TRAFFIC_H +#define BLACKSIM_XBUS_TRAFFIC_H + +//! \file + +#include "datarefs.h" +#include +#include +#include "XPMPMultiplayer.h" + +//! \cond PRIVATE +#define XBUS_TRAFFIC_INTERFACENAME "net.vatsim.xbus.traffic" +#define XBUS_TRAFFIC_OBJECTPATH "/xbus/traffic" +//! \endcond + +namespace XBus +{ + + /*! + * XBus service object for traffic aircraft which is accessible through DBus + */ + class CTraffic : public QObject + { + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", XBUS_TRAFFIC_INTERFACENAME) + + public: + //! Constructor + CTraffic(QObject *parent); + + //! Destructor + ~CTraffic(); + + //! DBus interface name + static const QString &InterfaceName() + { + static QString s(XBUS_TRAFFIC_INTERFACENAME); + return s; + } + + //! DBus object path + static const QString &ObjectPath() + { + static QString s(XBUS_TRAFFIC_OBJECTPATH); + return s; + } + + //! Called by XPluginStart + static void initLegacyData(); + + public slots: + //! Initialize the multiplayer planes rendering and return true if successful + bool initialize(); + + //! Reverse the actions of initialize(). + void cleanup(); + + //! Load a collection of planes from the given directory and return true if successful + bool loadPlanesPackage(const QString &path); + + //! Set the ICAO code to use for aircraft without a model match + void setDefaultIcao(const QString &defaultIcao); + + //! Set whether the plugin draws type and callsign labels above aircraft + void setDrawingLabels(bool drawing); + + //! Get whether the plugin draws type and callsign labels above aircraft + bool isDrawingLabels() const; + + //! Introduce a new traffic aircraft + void addPlane(const QString &callsign, const QString &aircraftIcao, const QString &airlineIcao, const QString &livery); + + //! Remove a traffic aircraft + void removePlane(const QString &callsign); + + //! Remove all traffic aircraft + void removeAllPlanes(); + + //! Set the position of a traffic aircraft + void setPlanePosition(const QString &callsign, double latitude, double longitude, double altitude, double pitch, double roll, double heading); + + //! Set the flight control surfaces and lights of a traffic aircraft + void setPlaneSurfaces(const QString &callsign, double gear, double flap, double spoiler, double speedBrake, double slat, double wingSweep, double thrust, + double elevator, double rudder, double aileron, bool landLight, bool beaconLight, bool strobeLight, bool navLight, int lightPattern); + + //! Set the transponder of a traffic aircraft + void setPlaneTransponder(const QString &callsign, int code, bool modeC, bool ident); + + private: + bool m_initialized = false; + bool m_enabled = false; + + static int preferences(const char *section, const char *name, int def); + static float preferences(const char *section, const char *name, float def); + + struct Plane + { + void *id = nullptr; + QString callsign; + QString aircraftIcao; + QString airlineIcao; + QString livery; + qint64 time0 = 0; + qint64 time1 = 0; + bool hasSurfaces = false; + bool hasXpdr = false; + XPMPPlanePosition_t position0; + XPMPPlanePosition_t position1; + XPMPPlaneSurfaces_t surfaces; + XPMPPlaneRadar_t xpdr; + Plane(void *id_, QString callsign_, QString aircraftIcao_, QString airlineIcao_, QString livery_); + }; + QHash m_planesByCallsign; + QHash m_planesById; + + int getPlaneData(void *id, int dataType, void *io_data); + static int getPlaneData(void *id, int dataType, void *io_data, void *self) + { + return static_cast(self)->getPlaneData(id, dataType, io_data); + } + }; + +} + +#endif // guard +