diff --git a/Include/ACInfoWnd.h b/Include/ACInfoWnd.h index 01673ec9..738dfbc9 100644 --- a/Include/ACInfoWnd.h +++ b/Include/ACInfoWnd.h @@ -38,7 +38,7 @@ class TFACSearchEditWidget : public TFTextFieldWidget TFACSearchEditWidget (XPWidgetID _me = NULL, const char* szKey = NULL); // Find my aircraft - const LTFlightData* SearchFlightData (const std::string ac_key); + const LTFlightData* SearchFlightData (std::string ac_key); void SetAcKey (const LTFlightData::FDKeyTy& _key); // Get the found aircraft diff --git a/Include/Constants.h b/Include/Constants.h index 388adfb8..e306f5bc 100644 --- a/Include/Constants.h +++ b/Include/Constants.h @@ -29,7 +29,7 @@ // // MARK: Version Information (CHANGE VERSION HERE) // -constexpr float VERSION_NR = 2.02f; +constexpr float VERSION_NR = 2.03f; constexpr bool VERSION_BETA = true; extern float verXPlaneOrg; // version on X-Plane.org extern int verDateXPlaneOrg; // and its date @@ -105,6 +105,7 @@ constexpr double MDL_TIRE_SLOW_TIME = 5.0; ///< [s] time till tires stop rotati constexpr double MDL_TIRE_MAX_RPM = 2000; ///< [rpm] max tire rotation speed constexpr double MDL_TIRE_CF_M = 3.2; ///< [m] tire circumfence (3.2m for a 40-inch tire) constexpr double MDL_GEAR_DEFL_TIME = 0.5; ///< [s] time for gear deflection (one direction...up down is twice this value) +constexpr double MDL_CAR_MAX_TAXI = 80.0; ///< [kn] Maximum allowed taxi speed for ground vehicles (before they turn into planes) constexpr int COLOR_YELLOW = 0xFFFF00; constexpr int COLOR_RED = 0xFF0000; @@ -145,9 +146,10 @@ constexpr int LT_NEW_VER_CHECK_TIME = 48; // [h] between two checks of a new //MARK: Text Constants #define LIVE_TRAFFIC "LiveTraffic" +#define LIVE_TRAFFIC_XPMP2 " LT" ///< short form for logging by XPMP2, so that log entries are aligned #define LT_CFG_VER_NM_CONV "1.0" // version of config file format, from which to convert distances from km to nm #define LT_CFG_VERSION "1.1" // current version of config file format -#define LT_FM_VERSION "1.1" // version of flight model file format +#define LT_FM_VERSION "2.0" // version of flight model file format #define PLUGIN_SIGNATURE "TwinFan.plugin.LiveTraffic" #define PLUGIN_DESCRIPTION "Create Multiplayer Aircraft based on live traffic." #define LT_DOWNLOAD_URL "https://forums.x-plane.org/index.php?/files/file/49749-livetraffic/" @@ -158,6 +160,7 @@ constexpr int LT_NEW_VER_CHECK_TIME = 48; // [h] between two checks of a new #define MSG_REINIT "LiveTraffic is re-initializing itself" #define MSG_DISABLE_MYSELF "LiveTraffic disables itself due to unhandable exceptions" #define MSG_LT_NEW_VER_AVAIL "The new version %01.2f of LiveTraffic is available at X-Plane.com!" +#define MSG_TIMESTAMPS "Current System time is %sZ, current simulated time is %sZ" #define MSG_AI_LOAD_ACF "Changing AI control: X-Plane is now loading AI Aircraft models..." #define MSG_REQUESTING_LIVE_FD "Requesting live flight data online..." #define MSG_READING_HIST_FD "Reading historic flight data..." @@ -168,6 +171,8 @@ constexpr int LT_NEW_VER_CHECK_TIME = 48; // [h] between two checks of a new #define MSG_ADSBEX_LIMITE "%ld / %ld requests left" #define INFO_AC_ADDED "Added aircraft %s, operator '%s', a/c model '%s', flight model [%s], bearing %.0f, distance %.1fnm, from channel %s" #define INFO_AC_MDL_CHANGED "Changed CSL model for aircraft %s, operator '%s': a/c model now '%s'" +#define INFO_GND_VEHICLE_APT "Vehicle %s: Decided for ground vehicle based on operator name '%s'" +#define INFO_GND_VEHICLE_CALL "Vehicle %s: Decided for ground vehicle based on call sign '%s'" #define INFO_AC_REMOVED "Removed aircraft %s" #define INFO_AC_ALL_REMOVED "Removed all aircraft" #define INFO_WND_AUTO_AC "AUTO" @@ -183,6 +188,7 @@ constexpr int LT_NEW_VER_CHECK_TIME = 48; // [h] between two checks of a new #define CSL_DEFAULT_ICAO_TYPE "A320" #define CSL_CAR_ICAO_TYPE "ZZZC" // fake code for a ground vehicle #define FM_MAP_SECTION "Map" +#define FM_CAR_SECTION "GroundVehicles" #define FM_PARENT_SEPARATOR ":" #define CFG_CSL_SECTION "[CSLPaths]" #define CFG_DEFAULT_AC_TYPE "DEFAULT_AC_TYPE" @@ -273,7 +279,7 @@ constexpr int SERR_LEN = 100; // size of buffer for IO error t #define ERR_CH_UNKNOWN_NAME "(unknown channel)" #define ERR_CH_INVALID "%s: Channel invalid and disabled" #define ERR_CH_MAX_ERR_INV "%s: Channel invalid and disabled after too many errors" -#define ERR_NO_AC_TYPE "Tracking data for '%s' (man '%s', mdl '%s') lacks ICAO a/c type code, will be rendered with standard a/c %s" +#define ERR_NO_AC_TYPE "Tracking data for '%s' (man '%s', mdl '%s') lacks ICAO a/c type code, can't derive type -> will be rendered with standard a/c %s" #define ERR_NO_AC_TYPE_BUT_MDL "Tracking data for '%s' (man '%s', mdl '%s') lacks ICAO a/c type code, but derived %s from mdl text" #define ERR_DATAREF_FIND "Could not find DataRef/CmdRef: %s" #define ERR_DATAREF_ACCESSOR "Could not register accessor for DataRef: %s" @@ -318,7 +324,7 @@ constexpr int SERR_LEN = 100; // size of buffer for IO error t #define ERR_CFG_AC_DEFAULT "A/c default ICAO type '%s' invalid, still using '%s' as default. Verify Settings > CSL!" #define ERR_CFG_CAR_DEFAULT "Car default ICAO type '%s' invalid, still using '%s' as default. Verify Settings > CSL!" #define ERR_CFG_TYPE_INVALID "%s, line %d: ICAO type designator '%s' unknown" -#define ERR_FM_NOT_AFTER_MAP "Remainder after [Map] section ignored" +#define ERR_FM_NOT_AFTER_MAP "Unknown section after [Map] section ignored" #define ERR_FM_NOT_BEFORE_SEC "Lines before first section ignored" #define ERR_FM_UNKNOWN_NAME "Unknown parameter in '%s', line %d: %s" #define ERR_FM_UNKNOWN_SECTION "Referring to unknown model section in '%s', line %d: %s" diff --git a/Include/DataRefs.h b/Include/DataRefs.h index bc51130a..78f15cfa 100644 --- a/Include/DataRefs.h +++ b/Include/DataRefs.h @@ -241,7 +241,6 @@ enum dataRefsLT { DR_CFG_AUTO_START, DR_CFG_AI_ON_REQUEST, DR_CFG_AI_UNDER_CONTROL, - DR_CFG_AI_SKIP_NOPLANE, DR_CFG_LABELS, DR_CFG_LABEL_SHOWN, DR_CFG_LABEL_COL_DYN, @@ -477,7 +476,6 @@ class DataRefs // generic config values int bAutoStart = true; // shall display a/c right after startup? int bAIonRequest = false;// acquire multiplayer control for TCAS on request only, not automatically? - int nAISkipAssignNoPlane = -1; ///< Skip assigning NoPlane.acf to AI planes (-1 = use defaults, 0 = force off, 1 = force on) // which elements make up an a/c label? LabelCfgTy labelCfg = { 0,1,0,0,0,0,0,0, 0,0,0,0,0,0 }; LabelShowCfgTy labelShown = { 1, 1, 1 }; // when to show? (default: always) @@ -626,7 +624,6 @@ class DataRefs // specific access inline bool GetAutoStart() const { return bAutoStart != 0; } inline bool IsAIonRequest() const { return bAIonRequest != 0; } - bool ShallAISkipAssignNoPlane() const; static int HaveAIUnderControl(void* =NULL) { return XPMPHasControlOfAIAircraft(); } inline LabelCfgTy GetLabelCfg() const { return labelCfg; } inline LabelShowCfgTy GetLabelShowCfg() const { return labelShown; } diff --git a/Include/LTAircraft.h b/Include/LTAircraft.h index a7a286d4..9ce55cfc 100644 --- a/Include/LTAircraft.h +++ b/Include/LTAircraft.h @@ -287,8 +287,10 @@ class LTAircraft : public XPCAircraft public: static bool ReadFlightModelFile (); - static const FlightModel& FindFlightModel (const std::string acTypeIcao); - static const FlightModel* GetFlightModel (const std::string modelName); + static const FlightModel& FindFlightModel (const std::string& acTypeIcao); + static const FlightModel* GetFlightModel (const std::string& modelName); + /// Tests if the given call sign matches typical call signs of ground vehicles + static bool MatchesCar (const std::string& _callSign); }; public: @@ -350,6 +352,7 @@ class LTAircraft : public XPCAircraft #ifdef DEBUG bool bIsSelected = false; // is selected for logging/debugging? #endif + bool bChangeModel = false; ///< shall the model be updated at next chance? bool bSendNewInfoData = false; ///< is there new static data to announce? // visibility bool bVisible = true; // is a/c visible? @@ -368,8 +371,6 @@ class LTAircraft : public XPCAircraft void LabelUpdate(); // stringify e.g. for debugging info purposes operator std::string() const; - // change the model (e.g. when model-defining static data changed) - void ChangeModel (const LTFlightData::FDStaticData& statData); // current position inline const positionTy& GetPPos() const { return ppos; } inline positionTy GetPPosLocal() const { return positionTy(ppos).WorldToLocal(); } @@ -407,6 +408,8 @@ class LTAircraft : public XPCAircraft // object valid? (set to false after exceptions) inline bool IsValid() const { return bValid; } void SetInvalid() { bValid = false; } + bool ShallUpdateModel () const { return bChangeModel; } + void SetUpdateModel () { bChangeModel = true; } inline bool ShallSendNewInfoData () const { return bSendNewInfoData; } inline void SetSendNewInfoData () { bSendNewInfoData = true; } // Visibility @@ -434,6 +437,10 @@ class LTAircraft : public XPCAircraft /// Determines AI priority based on bearing to user's plane and ground status void CalcAIPrio (); + /// @brief change the model (e.g. when model-defining static data changed) + /// @note Should be used in main thread only + void ChangeModel (); + protected: // *** Camera view *** static LTAircraft* pExtViewAc; // the a/c to show in external view, NULL if none/stop ext view diff --git a/Include/LTApt.h b/Include/LTApt.h index 415b8af5..a3c0d4db 100644 --- a/Include/LTApt.h +++ b/Include/LTApt.h @@ -63,7 +63,7 @@ bool LTAptSnap (LTFlightData& fd, dequePositionTy::iterator& posIter, /// Cleanup void LTAptDisable (); -#ifdef DEBUG +#ifdef APT_DUMP /// @brief Dumps the entire taxi network into a CSV file readable by GPS Visualizer /// @see https://www.gpsvisualizer.com/ void LTAptDump (const std::string& _aptId); diff --git a/Include/LTFlightData.h b/Include/LTFlightData.h index c66c0d9a..80a54f4d 100644 --- a/Include/LTFlightData.h +++ b/Include/LTFlightData.h @@ -140,8 +140,9 @@ class LTFlightData FDStaticData(FDStaticData&&) = default; FDStaticData& operator=(const FDStaticData&) = default; FDStaticData& operator=(FDStaticData&&) = default; - // 'merge' data, i.e. copy only filled fields from 'other' - FDStaticData& operator |= (const FDStaticData& other); + /// @brief Merges data, i.e. copy only filled fields from 'other' + /// @return Have matching-relevant fields now changed? + bool merge (const FDStaticData& other); // returns flight, call sign, registration, or provieded _default (e.g. transp hex code) std::string acId (const std::string _default) const; // route (this is "originAp-destAp", but considers empty txt) @@ -151,6 +152,8 @@ class LTFlightData // best guess for an airline livery: opIcao if exists, otherwise first 3 digits of call sign inline std::string airlineCode() const { return opIcao.empty() ? call.substr(0,3) : opIcao; } + /// is this a ground vehicle? + bool isGrndVehicle() const; // has been initialized at least once? bool isInit() const { return bInit; } }; diff --git a/Lib/CURL/libcurl.pdb b/Lib/CURL/libcurl.pdb new file mode 100644 index 00000000..35dec749 Binary files /dev/null and b/Lib/CURL/libcurl.pdb differ diff --git a/Lib/XPMP2/XPMP2.framework/Versions/1.0/Headers/XPCAircraft.h b/Lib/XPMP2/XPMP2.framework/Versions/1.0/Headers/XPCAircraft.h index 2d709614..26ad637a 100644 --- a/Lib/XPMP2/XPMP2.framework/Versions/1.0/Headers/XPCAircraft.h +++ b/Lib/XPMP2/XPMP2.framework/Versions/1.0/Headers/XPCAircraft.h @@ -44,11 +44,18 @@ XPCAircraft : public XPMP2::Aircraft { public: - /// Legacy constructor creates a plane and puts it under control of XPlaneMP - XPCAircraft(const char* inICAOCode, - const char* inAirline, - const char* inLivery, - const char* inModelName = nullptr); // this new parameter is defaulted, so that old code should compile + /// @brief Legacy constructor creates a plane and puts it under control of XPlaneMP + /// @exception XPMP2::XPMP2Error Mode S id invalid or duplicate, no model found during model matching + /// @param _icaoType ICAO aircraft type designator, like 'A320', 'B738', 'C172' + /// @param _icaoAirline ICAO airline code, like 'BAW', 'DLH', can be an empty string + /// @param _livery Special livery designator, can be an empty string + /// @param _modeS_id (optional) Unique identification of the plane [0x01..0xFFFFFF], e.g. the 24bit mode S transponder code. XPMP2 assigns an arbitrary unique number of not given + /// @param _modelId (optional) specific model id to be used (no folder/package name, just the id as defined in the `OBJ8_AIRCRAFT` line) + XPCAircraft(const char* _icaoType, + const char* _icaoAirline, + const char* _livery, + XPMPPlaneID _modeS_id = 0, // new parameters are defaulted, so that old code should compile + const char* _modelId = nullptr); /// Legacy: Called before rendering to query plane's current position, overwrite to provide your implementation virtual XPMPPlaneCallbackResult GetPlanePosition(XPMPPlanePosition_t* outPosition) = 0; @@ -61,7 +68,7 @@ XPCAircraft : public XPMP2::Aircraft { { return xpmpData_Unavailable; } /// Just calls all 4 previous `Get...` functions and copies the provided values into `drawInfo` and `v` - virtual void UpdatePosition (); + virtual void UpdatePosition (float _elapsedSinceLastCall, int _flCounter); }; diff --git a/Lib/XPMP2/XPMP2.framework/Versions/1.0/Headers/XPMPAircraft.h b/Lib/XPMP2/XPMP2.framework/Versions/1.0/Headers/XPMPAircraft.h index 946e8b3a..237318b2 100644 --- a/Lib/XPMP2/XPMP2.framework/Versions/1.0/Headers/XPMPAircraft.h +++ b/Lib/XPMP2/XPMP2.framework/Versions/1.0/Headers/XPMPAircraft.h @@ -89,8 +89,11 @@ enum DR_VALS { class Aircraft { protected: - /// Legacy: The id of the represented plane (in XPMP2, this now is an arbitrary, ever increasing number) - XPMPPlaneID mPlane = 0; + /// @brief A plane is uniquely identified by a 24bit number [0x01..0xFFFFFF] + /// @details This number is directly used as `modeS_id` n the new + /// [TCAS override](https://developer.x-plane.com/article/overriding-tcas-and-providing-traffic-information/) + /// approach. + XPMPPlaneID modeS_id = 0; public: std::string acIcaoType; ///< ICAO aircraft type of this plane @@ -144,8 +147,8 @@ class Aircraft { /// X-Plane instance handles for all objects making up the model std::list listInst; - /// Which sim/multiplayer/plane-index used last? - int multiIdx = -1; + /// Which sim/cockpit2/tcas/targets-index used last? + int tcasTargetIdx = -1; /// Timestamp of last update of camera dist/bearing float camTimLstUpd = 0.0f; @@ -162,31 +165,38 @@ class Aircraft { int mapIconCol = 0; ///< map icon coordinates, column float mapX = 0.0f; ///< temporary: map coordinates (NAN = not to be drawn) float mapY = 0.0f; ///< temporary: map coordinates (NAN = not to be drawn) + std::string mapLabel; ///< label for map drawing public: - /// Constructor + /// Constructor creates a new aircraft object, which will be managed and displayed + /// @exception XPMP2::XPMP2Error Mode S id invalid or duplicate, no model found during model matching + /// @param _icaoType ICAO aircraft type designator, like 'A320', 'B738', 'C172' + /// @param _icaoAirline ICAO airline code, like 'BAW', 'DLH', can be an empty string + /// @param _livery Special livery designator, can be an empty string + /// @param _modeS_id (optional) Unique identification of the plane [0x01..0xFFFFFF], e.g. the 24bit mode S transponder code. XPMP2 assigns an arbitrary unique number of not given + /// @param _modelId (optional) specific model id to be used (no folder/package name, just the id as defined in the `OBJ8_AIRCRAFT` line) Aircraft (const std::string& _icaoType, const std::string& _icaoAirline, const std::string& _livery, - const std::string& _modelName = ""); + XPMPPlaneID _modeS_id = 0, + const std::string& _modelId = ""); /// Destructor cleans up all resources acquired virtual ~Aircraft(); /// return the XPMP2 plane id - XPMPPlaneID GetPlaneID () const { return mPlane; } - /// @brief return the current multiplayer-index - /// @note This is a 0-based index into our internal tables! - /// It is one less than the number used in sim/multiplayer/plane dataRefs, - /// use Aircraft::GetAIPlaneIdx() for that purpose. - int GetMultiIdx () const { return multiIdx; } - /// @brief return the plane's index in XP's sim/multiplayer/plane dataRefs - int GetAIPlaneIdx () const { return multiIdx >= 0 ? multiIdx+1 : -1; } - /// Is this plane currently also being tracked by X-Plane's AI/multiplayer, ie. will appear on TCAS? - bool IsCurrentlyShownAsAI () const { return multiIdx >= 0; } - /// Will this plane show up on TCAS / in multiplayer views? (It will if transponder is not switched off) + XPMPPlaneID GetModeS_ID () const { return modeS_id; } + /// Is this object a ground vehicle? + bool IsGroundVehicle() const; + /// @brief return the current TCAS target index (into sim/cockpit2/tcas/targets), 1-based, -1 if not used + int GetTcasTargetIdx () const { return tcasTargetIdx; } + /// Is this plane currently also being tracked as a TCAS target, ie. will appear on TCAS? + bool IsCurrentlyShownAsTcasTarget () const { return tcasTargetIdx >= 1; } + /// Is this plane currently also being tracked by X-Plane's classic AI/multiplayer? + bool IsCurrentlyShownAsAI () const { return 1 <= tcasTargetIdx && tcasTargetIdx <= 20; } + /// Is this plane to be drawn on TCAS? (It will if transponder is not switched off) bool ShowAsAIPlane () const { return IsVisible() && acRadar.mode != xpmpTransponderMode_Standby; } - /// Reset multiplayer slot index - void ResetMultiIdx () { multiIdx = -1; } + /// Reset TCAS target slot index + void ResetTcasTargetIdx () { tcasTargetIdx = -1; } /// (Potentially) change the plane's model after doing a new match attempt int ChangeModel (const std::string& _icaoType, @@ -208,7 +218,11 @@ class Aircraft { /// @brief Called right before updating the aircraft's placement in the world /// @details Abstract virtual function. Override in derived classes and fill /// `drawInfo`, the `v` array of dataRefs, `label`, and `infoTexts` with current values. - virtual void UpdatePosition () = 0; + /// @see See [XPLMFlightLoop_f](https://developer.x-plane.com/sdk/XPLMProcessing/#XPLMFlightLoop_f) + /// for background on the two passed-on parameters: + /// @param _elapsedSinceLastCall The wall time since last call + /// @param _flCounter A monotonically increasing counter, bumped once per flight loop dispatch from the sim. + virtual void UpdatePosition (float _elapsedSinceLastCall, int _flCounter) = 0; /// Distance to camera [m] float GetCameraDist () const { return camDist; } /// Bearing from camera [°] @@ -254,12 +268,13 @@ class Aircraft { bool CreateInstances (); /// Destroy all instances void DestroyInstances (); + + /// Put together the map label, depends on tcasTargetIdx + virtual void ComputeMapLabel (); // The following functions are implemented in AIMultiplayer.cpp: - /// AI/Multiplayer handling: Find next AI slot - int AISlotReserve (); - /// AI/Multiplayer handling: Clear AI slot - void AISlotClear (); + /// Define the TCAS target index in use + virtual void SetTcasTargetIdx (int _idx) { tcasTargetIdx = _idx; } // These functions are called from AIMultiUpdate() friend void AIMultiUpdate (); }; @@ -271,7 +286,7 @@ Aircraft* AcFindByID (XPMPPlaneID _id); // MARK: XPMP2 Exception class // -/// XPMP2 Exception class, e.g. thrown if there are no CSL models when creating an Aircraft +/// XPMP2 Exception class, e.g. thrown if there are no CSL models or duplicate modeS_ids when creating an Aircraft class XPMP2Error : public std::logic_error { protected: std::string fileName; ///< filename of the line of code where exception occurred diff --git a/Lib/XPMP2/XPMP2.framework/Versions/1.0/Headers/XPMPMultiplayer.h b/Lib/XPMP2/XPMP2.framework/Versions/1.0/Headers/XPMPMultiplayer.h index a8013a88..b8e7f85f 100644 --- a/Lib/XPMP2/XPMP2.framework/Versions/1.0/Headers/XPMPMultiplayer.h +++ b/Lib/XPMP2/XPMP2.framework/Versions/1.0/Headers/XPMPMultiplayer.h @@ -233,8 +233,15 @@ enum XPMPPlaneCallbackResult { /// @brief Unique ID for an aircraft created by a plugin. /// @note In XPMP2 this value is no longer a pointer to an internal memory address, -/// but just an ever increasing number. Don't use it as a pointer. -typedef void * XPMPPlaneID; +/// but will directly be used as `modeS_id` in the new +/// [TCAS override](https://developer.x-plane.com/article/overriding-tcas-and-providing-traffic-information/) +/// approach. +typedef unsigned XPMPPlaneID; + +/// Minimum allowed XPMPPlaneID / mode S id +constexpr XPMPPlaneID MIN_MODE_S_ID = 0x00000001; +/// Maximum allowed XPMPPlaneID / mode S id +constexpr XPMPPlaneID MAX_MODE_S_ID = 0x00FFFFFF; /************************************************************************************ @@ -247,8 +254,6 @@ typedef void * XPMPPlaneID; // Config key definitions #define XPMP_CFG_ITM_CLAMPALL "clamp_all_to_ground" ///< Config key: Ensure no plane sinks below ground, no matter of Aircraft::bClampToGround -#define XPMP_CFG_ITM_SKIP_NOPLANE "skip_assign_noplane" ///< Config key: For faking TCAS, all AI/multiplayer planes are assigned the `NoPlane.acf` type when XPMP2 takes over control. This option allows to skip assining `NoPlane.acf`...just in case. -#define XPMP_CFG_ITM_COPY_NOPLANE "copy_noplane" ///< Config key: On startup, shall `NoPlane.acf` be copied from the resource directory to `/Aircraft/`? This is highly recommended to reduce side-effects of using `NoPlane` as AI Aircraft model. #define XPMP_CFG_ITM_LOGLEVEL "log_level" ///< Config key: General level of logging into Log.txt (0 = Debug, 1 = Info, 2 = Warning, 3 = Error, 4 = Fatal) #define XPMP_CFG_ITM_MODELMATCHING "model_matching" ///< Config key: Write information on model matching into Log.txt @@ -259,8 +264,6 @@ typedef void * XPMPPlaneID; /// `section | key                 | type | default | description`\n /// `------- | ------------------- | ---- | ------- | -------------------------------------------------------------------------`\n /// `planes  | clamp_all_to_ground | int  |    1    | Ensure no plane sinks below ground, no matter of Aircraft::bClampToGround`\n -/// `planes  | skip_assign_noplane | int  |    0    | If non-zero, then AI planes will not be assigned the NoPlane.acf type.`\n -/// `planes  | copy_noplane        | int  |    1    | Shall NoPlane.acf be copied to Aircraft folder if missing/newer? (Recommended!)`\n /// `debug   | log_level           | int  |    2    | General level of logging into Log.txt (0 = Debug, 1 = Info, 2 = Warning, 3 = Error, 4 = Fatal)`\n /// `debug   | model_matching      | int  |    0    | Write information on model matching into Log.txt`\n /// @note There is no immediate requirement to check the value of `_section` in your implementation. @@ -281,25 +284,31 @@ const char * XPMPMultiplayerInitLegacyData(const char* inCSLFolder, const char* inPluginName, const char* resourceDir, XPMPIntPrefsFuncTy inIntPrefsFunc = nullptr, - const char* inDefaultICAO = nullptr); + const char* inDefaultICAO = nullptr, + const char* inPluginLogAcronym = nullptr); /// @brief Initializes the XPMP2 library. This shall be your first call to the library. /// @note Parameters changed compared to libxplanemp! -/// @param inPluginName Your plugin's name, used only in output to `Log.txt` -/// @param resourceDir The directory where XPMP2 finds all required supplemental files (`Doc8643.txt`, `MapIcons.png`, `NoPlane.acf`, `related.txt`) -/// @param inIntPrefsFunc A pointer to a callback function providing integer config values. See ::XPMPIntPrefsFuncTy for details. -/// @param inDefaultICAO A fallback aircraft type if no type can be deduced otherwise for an aircraft. +/// @param inPluginName Your plugin's name, used as map layer name, and as folder name under `Aircraft` +/// @param resourceDir The directory where XPMP2 finds all required supplemental files (`Doc8643.txt`, `MapIcons.png`, `related.txt`) +/// @param inIntPrefsFunc (optional) A pointer to a callback function providing integer config values. See ::XPMPIntPrefsFuncTy for details. +/// @param inDefaultICAO (optional) A fallback aircraft type if no type can be deduced otherwise for an aircraft. +/// @param inPluginLogAcronym (optional) A short text to be used in log output. If not given then `inPluginName` is used also for this purpse. /// @return Empty string in case of success, otherwise a human-readable error message. const char * XPMPMultiplayerInit(const char* inPluginName, const char* resourceDir, XPMPIntPrefsFuncTy inIntPrefsFunc = nullptr, - const char* inDefaultICAO = nullptr); + const char* inDefaultICAO = nullptr, + const char* inPluginLogAcronym = nullptr); /// @brief Overrides the plugin's name to be used in Log output /// @details The same as providing a plugin name with XPMPMultiplayerInit(). /// If no name is provided, it defaults to the plugin's name as set in XPluginStart(). /// @note Replaces the compile-time macro `XPMP_CLIENT_LONGNAME` needed in `libxplanemp`. -void XPMPSetPluginName (const char* inPluginName); +/// @param inPluginName Your plugin's name, used as map layer name, and as folder name under `Aircraft` +/// @param inPluginLogAcronym (optional) A short text to be used in log output. If not given then `inPluginName` is used also for this purpse. +void XPMPSetPluginName (const char* inPluginName, + const char* inPluginLogAcronym = nullptr); /// @brief Clean up the multiplayer library @@ -459,12 +468,14 @@ typedef XPMPPlaneCallbackResult (* XPMPPlaneData_f)(XPMPPlaneID inPlane, /// @param inLivery Special livery designator, can be an empty string /// @param inDataFunc Callback function called by XPMP2 to fetch updated data /// @param inRefcon A refcon value passed back to you in all calls to the `inDataFunc` +/// @param inModeS_id (optional) Unique identification of the plane [0x01..0xFFFFFF], e.g. the 24bit mode S transponder code. XPMP2 assigns an arbitrary unique number of not given [[deprecated("Subclass XPMP2::Aircraft instead")]] XPMPPlaneID XPMPCreatePlane(const char * inICAOCode, const char * inAirline, const char * inLivery, XPMPPlaneData_f inDataFunc, - void * inRefcon); + void * inRefcon, + XPMPPlaneID inModeS_id = 0); /// @brief Creates a new plane, providing a specific CSL model name /// @deprecated Subclass XPMP2::Aircraft instead @@ -474,13 +485,15 @@ XPMPPlaneID XPMPCreatePlane(const char * inICAOCode, /// @param inLivery Special livery designator, can be an empty string /// @param inDataFunc Callback function called by XPMP2 to fetch updated data /// @param inRefcon A refcon value passed back to you in all calls to the `inDataFunc` +/// @param inModeS_id (optional) Unique identification of the plane [0x01..0xFFFFFF], e.g. the 24bit mode S transponder code. XPMP2 assigns an arbitrary unique number of not given [[deprecated("Subclass XPMP2::Aircraft instead")]] XPMPPlaneID XPMPCreatePlaneWithModelName(const char * inModelName, const char * inICAOCode, const char * inAirline, const char * inLivery, - XPMPPlaneData_f inDataFunc, - void * inRefcon); + XPMPPlaneData_f inDataFunc, + void * inRefcon, + XPMPPlaneID inModeS_id = 0); /// @brief [Deprecated] Removes a plane previously created with XPMPCreatePlane() /// @deprecated Delete subclassed XPMP2::Aircraft object instead. @@ -492,20 +505,15 @@ void XPMPDestroyPlane(XPMPPlaneID); void XPMPSetPlaneVisibility(XPMPPlaneID _id, bool _bVisible); -/* - * XPMPChangePlaneModel - * - * This routine lets you change an aircraft's model. This can be useful if a remote - * player changes planes or new information comes over the network asynchronously. - * - * this function returns an integer which, abstractly, represents the match quality. - * - * lower numbers are better, 2 or lower indicates an exact match on model. - * negative values indicate failure to match at all. - * - */ -int XPMPChangePlaneModel( - XPMPPlaneID inPlaneID, +/// @brief Perform model matching again and change the CSL model to the resulting match +/// @note Effectively calls XPMP2::Aircraft::ChangeModel(), +/// so if you have the aircraft object, prefer call that function directly. +/// @param inPlaneID Which plane to change? +/// @param inICAOCode ICAO aircraft type designator, like 'A320', 'B738', 'C172' +/// @param inAirline ICAO airline code, like 'BAW', 'DLH', can be an empty string +/// @param inLivery Special livery designator, can be an empty string +/// @return Match quality, the lower the better +int XPMPChangePlaneModel(XPMPPlaneID inPlaneID, const char * inICAOCode, const char * inAirline, const char * inLivery); @@ -582,14 +590,11 @@ XPMPPlaneID XPMPGetNthPlane( long index); -/* - * XPMPSetDefaultPlaneICAO - * - * This routine controls what ICAO is used as a backup search criteria for a not-found plane. - * - */ -void XPMPSetDefaultPlaneICAO( - const char * inICAO); +/// @brief Define default aircraft and ground vehicle ICAO types +/// @param _acIcaoType Default ICAO aircraft type designator, used when matching returns nothing +/// @param _carIcaoType Type used to identify ground vehicels (internally defaults to "ZZZC") +void XPMPSetDefaultPlaneICAO(const char* _acIcaoType, + const char* _carIcaoType = nullptr); /************************************************************************************ * MARK: PLANE OBSERVATION API diff --git a/Lib/XPMP2/XPMP2.framework/Versions/1.0/XPMP2 b/Lib/XPMP2/XPMP2.framework/Versions/1.0/XPMP2 index 57d9a69e..ffee4e54 100644 Binary files a/Lib/XPMP2/XPMP2.framework/Versions/1.0/XPMP2 and b/Lib/XPMP2/XPMP2.framework/Versions/1.0/XPMP2 differ diff --git a/Lib/XPMP2/XPMP2.lib b/Lib/XPMP2/XPMP2.lib index f3470f54..b43c1472 100644 Binary files a/Lib/XPMP2/XPMP2.lib and b/Lib/XPMP2/XPMP2.lib differ diff --git a/Lib/XPMP2/libXPMP2.a b/Lib/XPMP2/libXPMP2.a index 7179a5e5..85e3c452 100644 Binary files a/Lib/XPMP2/libXPMP2.a and b/Lib/XPMP2/libXPMP2.a differ diff --git a/LiveTraffic.rc b/LiveTraffic.rc index e290b3c0..0909e396 100644 Binary files a/LiveTraffic.rc and b/LiveTraffic.rc differ diff --git a/LiveTraffic.vcxproj b/LiveTraffic.vcxproj index e41f4558..976dfc99 100644 --- a/LiveTraffic.vcxproj +++ b/LiveTraffic.vcxproj @@ -159,10 +159,8 @@ $(IntDir)$(TargetName).lib - copy /Y "$(TargetDir)$(TargetName)$(TargetExt)" "D:\X-Plane 11\Resources\plugins\LiveTraffic\win_x64)" -copy /Y "$(TargetDir)$(TargetName)$(TargetExt)" "D:\X-Plane BETA\Resources\plugins\LiveTraffic\win_x64" -copy /Y "$(TargetDir)$(TargetName)$(TargetExt)" "G:\11\Resources\plugins\LiveTraffic\win_x64" -copy /Y "$(TargetDir)$(TargetName)$(TargetExt)" "G:\BETA\Resources\plugins\LiveTraffic\win_x64" + copy /Y "$(TargetDir)$(TargetName)$(TargetExt)" "C:\X-Plane\11\Resources\plugins\LiveTraffic\win_x64" +copy /Y "$(TargetDir)$(TargetName)$(TargetExt)" "C:\X-Plane\BETA\Resources\plugins\LiveTraffic\win_x64" Copying win.xpl into my X-Plane plugin paths @@ -208,10 +206,8 @@ copy /Y "$(TargetDir)$(TargetName)$(TargetExt)" "G:\BETA\Resources\plugins\LiveT $(IntDir)$(TargetName).lib - copy /Y "$(TargetDir)$(TargetName)$(TargetExt)" "D:\X-Plane 11\Resources\plugins\LiveTraffic\win_x64)" -copy /Y "$(TargetDir)$(TargetName)$(TargetExt)" "D:\X-Plane BETA\Resources\plugins\LiveTraffic\win_x64" -copy /Y "$(TargetDir)$(TargetName)$(TargetExt)" "G:\11\Resources\plugins\LiveTraffic\win_x64" -copy /Y "$(TargetDir)$(TargetName)$(TargetExt)" "G:\BETA\Resources\plugins\LiveTraffic\win_x64" + copy /Y "$(TargetDir)$(TargetName)$(TargetExt)" "C:\X-Plane\11\Resources\plugins\LiveTraffic\win_x64" +copy /Y "$(TargetDir)$(TargetName)$(TargetExt)" "C:\X-Plane\BETA\Resources\plugins\LiveTraffic\win_x64" Copying win.xpl into my X-Plane plugin paths diff --git a/LiveTraffic.xcodeproj/project.xcworkspace/xcuserdata/birger.xcuserdatad/xcdebugger/Expressions.xcexplist b/LiveTraffic.xcodeproj/project.xcworkspace/xcuserdata/birger.xcuserdatad/xcdebugger/Expressions.xcexplist index 48e7ab19..2759bf95 100644 --- a/LiveTraffic.xcodeproj/project.xcworkspace/xcuserdata/birger.xcuserdatad/xcdebugger/Expressions.xcexplist +++ b/LiveTraffic.xcodeproj/project.xcworkspace/xcuserdata/birger.xcuserdatad/xcdebugger/Expressions.xcexplist @@ -45,13 +45,13 @@ contextName = "Apt::FindEdgesForHeading(double, double, std::__1::vector<unsigned long, std::__1::allocator<unsigned long> >&, TaxiEdge::edgeTy) const:LTApt.cpp"> + value = "vecTaxiEdges[222]"> + value = "lst[1385]"> + value = "vecTaxiEdgesIdxHead[3321]"> @@ -84,10 +84,10 @@ contextName = "LTFlightData::AddDynData(LTFlightData::FDDynamicData const&, int, int, positionTy*):LTFlightData.cpp"> + value = "posDeque"> + value = "dynDataDeque"> @@ -215,10 +215,10 @@ value = "cull"> + value = "iter->second"> + value = "tcas"> @@ -394,7 +394,7 @@ value = "from"> + value = "ppos.v[6]"> @@ -403,7 +403,7 @@ value = "ppos"> + value = "turn"> @@ -493,10 +493,10 @@ value = "mapFd"> + value = "sizeof(bool)"> + value = "dataRefs"> @@ -509,6 +509,9 @@ + + @@ -823,6 +826,9 @@ + + @@ -973,10 +979,10 @@ value = "_startTime"> + value = "_targetTime"> + value = "_deltaDist"> diff --git a/LiveTraffic.xcodeproj/xcuserdata/birger.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/LiveTraffic.xcodeproj/xcuserdata/birger.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index f25bc9d1..e96eec2e 100644 --- a/LiveTraffic.xcodeproj/xcuserdata/birger.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/LiveTraffic.xcodeproj/xcuserdata/birger.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -3,22 +3,4 @@ uuid = "C8095CDD-1C39-4D01-99B0-6AD4723390BD" type = "1" version = "2.0"> - - - - - - diff --git a/Resources/FlightModels.prf b/Resources/FlightModels.prf index eca5113c..39a8b082 100644 --- a/Resources/FlightModels.prf +++ b/Resources/FlightModels.prf @@ -1,4 +1,4 @@ -LiveTraffic 1.1 +LiveTraffic 2.0 # LiveTraffic FlightModels configuration file @@ -187,5 +187,20 @@ LightAC ^-; Heli ^L;H # This last line matches anything and serves as a default: - MediumJets .* + +[GroundVehicles] +# This section contains regular expressions to mach against call signs +# to help identifying ground vehicles +^[[:alpha:]]{2}[[:digit:]]{1,2}$ +^[[:alpha:]]{1}[[:digit:]]{1,3}$ +^DFS[[:digit:]]*$ +^FRA[[:digit:]]*$ +^LEOS[[:digit:]]*$ +^TUG[[:digit:]]*$ +^AIR[[:digit:]]*$ +^AGL[[:digit:]]*$ +^SAFE[[:digit:]]*$ +^MEDIC[[:digit:]]*$ +^ARFF[[:digit:]]*$ +^MICC$ diff --git a/Src/ACInfoWnd.cpp b/Src/ACInfoWnd.cpp index c9619312..5874214d 100644 --- a/Src/ACInfoWnd.cpp +++ b/Src/ACInfoWnd.cpp @@ -193,10 +193,11 @@ TFTextFieldWidget(_me) // - registration // - call sign // - flight number -const LTFlightData* TFACSearchEditWidget::SearchFlightData (const std::string ac_key) +const LTFlightData* TFACSearchEditWidget::SearchFlightData (std::string ac_key) { mapLTFlightDataTy::const_iterator fdIter = mapFd.cend(); + trim(ac_key); if (!ac_key.empty()) { // is it a small integer number, i.e. used as index? if (ac_key.length() <= 3 && diff --git a/Src/CoordCalc.cpp b/Src/CoordCalc.cpp index bd0e39c7..728f749c 100644 --- a/Src/CoordCalc.cpp +++ b/Src/CoordCalc.cpp @@ -378,7 +378,7 @@ const char* positionTy::GrndE2String (onGrndE grnd) std::string positionTy::dbgTxt () const { - char buf[120]; + char buf[200]; snprintf(buf, sizeof(buf), "%.1f: (%7.5f, %7.5f) %7.1fft %8.8s %3.3s %2.2s %-13.13s %4.*zu {h %3.0f%c, p %3.0f, r %3.0f}", ts(), lat(), lon(), diff --git a/Src/DataRefs.cpp b/Src/DataRefs.cpp index 58d50c48..019c4278 100644 --- a/Src/DataRefs.cpp +++ b/Src/DataRefs.cpp @@ -396,7 +396,6 @@ DataRefs::dataRefDefinitionT DATA_REFS_LT[CNT_DATAREFS_LT] = { {"livetraffic/cfg/auto_start", DataRefs::LTGetInt, DataRefs::LTSetCfgValue, GET_VAR, true }, {"livetraffic/cfg/ai_on_request", DataRefs::LTGetInt, DataRefs::LTSetCfgValue, GET_VAR, true }, {"livetraffic/cfg/ai_controlled", DataRefs::HaveAIUnderControl, NULL, NULL, false }, - {"livetraffic/cfg/ai_skip_noplane", DataRefs::LTGetInt, DataRefs::LTSetCfgValue, GET_VAR, true }, {"livetraffic/cfg/labels", DataRefs::LTGetInt, DataRefs::LTSetCfgValue, GET_VAR, true }, {"livetraffic/cfg/label_shown", DataRefs::LTGetInt, DataRefs::LTSetCfgValue, GET_VAR, true }, {"livetraffic/cfg/label_col_dyn", DataRefs::LTGetInt, DataRefs::LTSetCfgValue, GET_VAR, true }, @@ -451,7 +450,6 @@ void* DataRefs::getVarAddr (dataRefsLT dr) case DR_CFG_AIRCRAFT_DISPLAYED: return &bShowingAircraft; case DR_CFG_AUTO_START: return &bAutoStart; case DR_CFG_AI_ON_REQUEST: return &bAIonRequest; - case DR_CFG_AI_SKIP_NOPLANE: return &nAISkipAssignNoPlane; case DR_CFG_LABELS: return &labelCfg; case DR_CFG_LABEL_SHOWN: return &labelShown; case DR_CFG_LABEL_COL_DYN: return &bLabelColDynamic; @@ -1376,19 +1374,6 @@ float DataRefs::GetCfgFloat (dataRefsLT dr) } -bool DataRefs::ShallAISkipAssignNoPlane() const -{ - // Forced a certain value? - if (nAISkipAssignNoPlane == 0) - return false; - if (nAISkipAssignNoPlane == 1) - return true; - - // Using defaults - return false; -} - - // more than 24h passed since last version check? bool DataRefs::NeedNewVerCheck () const { @@ -1852,6 +1837,7 @@ bool DataRefs::SetDefaultCarIcaoType(const std::string type) { if (1 <= type.length() && type.length() <= 4) { sDefaultCarIcaoType = type; + XPMPSetDefaultPlaneICAO(nullptr, type.c_str()); // inform libxplanemp LOG_MSG(logINFO,CFG_DEFAULT_CAR_TYP_INFO,sDefaultCarIcaoType.c_str()); return true; } diff --git a/Src/LTADSBEx.cpp b/Src/LTADSBEx.cpp index bf0ebf42..3fb9d749 100644 --- a/Src/LTADSBEx.cpp +++ b/Src/LTADSBEx.cpp @@ -143,60 +143,70 @@ bool ADSBExchangeConnection::ProcessFetchedData (mapLTFlightDataTy& fdMap) if ( fd.empty() ) fd.SetKey(fdKey); - // fill static data + // -- fill static data -- + LTFlightData::FDStaticData stat; + stat.reg = jog_s(pJAc, ADSBEX_REG); + stat.country = jog_s(pJAc, ADSBEX_COUNTRY); + stat.acTypeIcao = jog_s(pJAc, ADSBEX_AC_TYPE_ICAO); + stat.mil = jog_sb(pJAc, ADSBEX_MIL); + stat.trt = transpTy(jog_sl(pJAc,ADSBEX_TRT)); + stat.opIcao = jog_s(pJAc, ADSBEX_OP_ICAO); + stat.call = jog_s(pJAc, ADSBEX_CALL); + + // -- dynamic data -- + LTFlightData::FDDynamicData dyn; + + // ADS-B returns Java tics, that is milliseconds, we use seconds + double posTime = jog_sn(pJAc, ADSBEX_POS_TIME) / 1000.0; + + // non-positional dynamic data + dyn.radar.code = jog_sl(pJAc, ADSBEX_RADAR_CODE); + dyn.gnd = jog_sb(pJAc, ADSBEX_GND); + dyn.heading = jog_sn_nan(pJAc, ADSBEX_HEADING); + dyn.spd = jog_sn(pJAc, ADSBEX_SPD); + dyn.vsi = jog_sn(pJAc, ADSBEX_VSI); + dyn.ts = posTime; + dyn.pChannel = this; + + // altitude, if airborne; correct for baro pressure difference + const double alt_ft = dyn.gnd ? NAN : jog_sn_nan(pJAc, ADSBEX_ELEVATION); + + // position and its ground status + positionTy pos (jog_sn_nan(pJAc, ADSBEX_LAT), + jog_sn_nan(pJAc, ADSBEX_LON), + alt_ft * M_per_FT, + posTime, + dyn.heading); + pos.f.onGrnd = dyn.gnd ? GND_ON : GND_OFF; + + // -- Ground vehicle identification -- + // ADSBEx doesn't send a clear indicator, but data analysis + // suggests that EngType/Mount == 0 is a good indicator + if (stat.acTypeIcao.empty() && // don't know a/c type yet + dyn.gnd && // on the ground + dyn.spd < 50.0 && // reasonable speed + stat.engType == 0 && // no engines + stat.engMount == 0) { - LTFlightData::FDStaticData stat; - stat.reg = jog_s(pJAc, ADSBEX_REG); - stat.country = jog_s(pJAc, ADSBEX_COUNTRY); - stat.acTypeIcao = jog_s(pJAc, ADSBEX_AC_TYPE_ICAO); - stat.mil = jog_sb(pJAc, ADSBEX_MIL); - stat.trt = transpTy(jog_sl(pJAc,ADSBEX_TRT)); - stat.opIcao = jog_s(pJAc, ADSBEX_OP_ICAO); - stat.call = jog_s(pJAc, ADSBEX_CALL); - - // update the a/c's master data - fd.UpdateData(std::move(stat)); + // we assume ground vehicle + stat.acTypeIcao = dataRefs.GetDefaultCarIcaoType(); } - - // dynamic data - { // unconditional...block is only for limiting local variables - LTFlightData::FDDynamicData dyn; - - // ADS-B returns Java tics, that is milliseconds, we use seconds - double posTime = jog_sn(pJAc, ADSBEX_POS_TIME) / 1000.0; - - // non-positional dynamic data - dyn.radar.code = jog_sl(pJAc, ADSBEX_RADAR_CODE); - dyn.gnd = jog_sb(pJAc, ADSBEX_GND); - dyn.heading = jog_sn_nan(pJAc, ADSBEX_HEADING); - dyn.spd = jog_sn(pJAc, ADSBEX_SPD); - dyn.vsi = jog_sn(pJAc, ADSBEX_VSI); - dyn.ts = posTime; - dyn.pChannel = this; - - // altitude, if airborne; correct for baro pressure difference - const double alt_ft = dyn.gnd ? NAN : jog_sn_nan(pJAc, ADSBEX_ELEVATION); - // position and its ground status - positionTy pos (jog_sn_nan(pJAc, ADSBEX_LAT), - jog_sn_nan(pJAc, ADSBEX_LON), - alt_ft * M_per_FT, - posTime, - dyn.heading); - pos.f.onGrnd = dyn.gnd ? GND_ON : GND_OFF; - - // position is rather important, we check for validity - if ( pos.isNormal(true) ) { - // ADSBEx, especially the RAPID API version, returns - // aircraft regardless of distance. To avoid planes - // created and immediately removed due to distanced settings - // we continue only if pos is within wanted range - if ( pos.dist(viewPos) <= dataRefs.GetFdStdDistance_m() ) - fd.AddDynData(dyn, 0, 0, &pos); - } - else - LOG_MSG(logDEBUG,ERR_POS_UNNORMAL,fdKey.c_str(),pos.dbgTxt().c_str()); + // update the a/c's master data + fd.UpdateData(std::move(stat)); + + // position is rather important, we check for validity + if ( pos.isNormal(true) ) { + // ADSBEx, especially the RAPID API version, returns + // aircraft regardless of distance. To avoid planes + // created and immediately removed due to distanced settings + // we continue only if pos is within wanted range + if ( pos.dist(viewPos) <= dataRefs.GetFdStdDistance_m() ) + fd.AddDynData(dyn, 0, 0, &pos); } + else + LOG_MSG(logDEBUG,ERR_POS_UNNORMAL,fdKey.c_str(),pos.dbgTxt().c_str()); + } catch(const std::system_error& e) { LOG_MSG(logERR, ERR_LOCK_ERROR, "mapFd", e.what()); } diff --git a/Src/LTAircraft.cpp b/Src/LTAircraft.cpp index 191a266e..7b24996d 100644 --- a/Src/LTAircraft.cpp +++ b/Src/LTAircraft.cpp @@ -789,6 +789,9 @@ std::list listFlightModels; typedef std::pair regexFM; std::list listFMRegex; +/// List of regular expressions matching call signs of ground vehicles +std::list listCarRegex; + // global constant for a default model const LTAircraft::FlightModel MDL_DEFAULT; @@ -934,6 +937,24 @@ bool fm_processMapLine (const char* fileName, int ln, return true; } +/// Processes a line in the [GroundVehicles] section by just storing the regEx +bool fm_processCarLine (const char* fileName, int ln, + const std::string& text) +{ + // check for valid regular expression + std::regex carRe; + try { + carRe.assign (text); + } catch (const std::regex_error& e) { + LOG_MSG(logWARN, ERR_FM_REGEX, e.what(), fileName, ln, text.c_str()); + return false; + } + + // add a list entry + listCarRegex.emplace_back(std::move(carRe)); + return true; +} + // read and process the FlightNodel.prf file bool LTAircraft::FlightModel::ReadFlightModelFile () @@ -966,8 +987,14 @@ bool LTAircraft::FlightModel::ReadFlightModelFile () } // then follow sections and their entries - // state signifies what kind of section we are currently reading - enum fmFileStateTy { FM_NO_SECTION, FM_MODEL_SECTION, FM_MAP } fmState = FM_NO_SECTION; + /// state signifies what kind of section we are currently reading + enum fmFileStateTy { + FM_NO_SECTION = 0, ///< before the first flight model section + FM_MODEL_SECTION, ///< processing a flight model section + FM_MAP, ///< Processing the [Map] section + FM_CAR, ///< Processing the [GroundVehicles] section + FM_IGNORE, ///< Found a section after [Map], which we just ignore + } fmState = FM_NO_SECTION; FlightModel fm; int errCnt = 0; for (int ln=1; fIn && errCnt <= ERR_CFG_FILE_MAXWARN; ln++) { @@ -999,23 +1026,26 @@ bool LTAircraft::FlightModel::ReadFlightModelFile () text.erase(text.length()-1); // finish previous model section first - // (as [Map] has to be last this will safely be executed for all FlightModel sections) + // (as [Map] has to be after FlightModel sections + // this will safely be executed for all FlightModel sections) if (fm && fmState == FM_MODEL_SECTION) { // i.e. add the defined model to the list push_back_unique(listFlightModels, fm); } - else if (fmState == FM_MAP) { - // there should be no section after [Map], - // ignore remainder of file - LOG_MSG(logWARN, ERR_CFG_FORMAT, sFileName.c_str(), ln, - ERR_FM_NOT_AFTER_MAP); - errCnt++; - break; - } // identify map section? if (text == FM_MAP_SECTION) { fmState = FM_MAP; + // identify cars section? + } else if (text == FM_CAR_SECTION) { + fmState = FM_CAR; + // are we beyond all flightModel sections already? + } else if (fmState > FM_MODEL_SECTION) { + // There must not be unknown section any longer! + LOG_MSG(logWARN, ERR_CFG_FORMAT, sFileName.c_str(), ln, + ERR_FM_NOT_AFTER_MAP); + errCnt++; + fmState = FM_IGNORE; } else { // no, so it must be a new model section fmState = FM_MODEL_SECTION; @@ -1064,6 +1094,14 @@ bool LTAircraft::FlightModel::ReadFlightModelFile () if (!fm_processMapLine(sFileName.c_str(), ln, text)) errCnt++; break; + + case FM_CAR: + if (!fm_processCarLine(sFileName.c_str(), ln, text)) + errCnt++; + break; + + case FM_IGNORE: + break; } } @@ -1093,14 +1131,14 @@ bool LTAircraft::FlightModel::ReadFlightModelFile () // based on an aircraft type find a matching flight model const LTAircraft::FlightModel& LTAircraft::FlightModel::FindFlightModel - (const std::string acTypeIcao) + (const std::string& acTypeIcao) { // 1. find aircraft type specification in the Doc8643 const Doc8643& acType = Doc8643::get(acTypeIcao); const std::string acSpec (acType); // the string to match // 2. walk through the Flight Model map list and try each regEx pattern - for (auto mapIt: listFMRegex) { + for (const auto& mapIt: listFMRegex) { std::smatch m; std::regex_search(acSpec, m, mapIt.first); if (m.size() > 0) // matches? @@ -1116,7 +1154,7 @@ const LTAircraft::FlightModel& LTAircraft::FlightModel::FindFlightModel // return a ptr to a flight model based on its model or [section] name // returns nullptr if not found const LTAircraft::FlightModel* LTAircraft::FlightModel::GetFlightModel - (const std::string modelName) + (const std::string& modelName) { // search through list of flight models, match by modelName auto fmIt = std::find_if(listFlightModels.cbegin(), @@ -1126,6 +1164,20 @@ const LTAircraft::FlightModel* LTAircraft::FlightModel::GetFlightModel return fmIt == listFlightModels.cend() ? nullptr : &*fmIt; } +// Tests if the given call sign matches typical call signs of ground vehicles +bool LTAircraft::FlightModel::MatchesCar (const std::string& _callSign) +{ + // Walk the car regEx list and try each pattern + for (const std::regex& re: listCarRegex) { + std::smatch m; + std::regex_search(_callSign, m, re); + if (m.size() > 0) // matches? + return true; + } + // no match found + return false; +} + // //MARK: LTAircraft::FlightPhase // @@ -1168,7 +1220,8 @@ LTAircraft::LTAircraft(LTFlightData& inFd) : // Debug options to set fixed type/op/livery take precedence XPCAircraft(str_first_non_empty({dataRefs.cslFixAcIcaoType, inFd.WaitForSafeCopyStat().acTypeIcao}).c_str(), str_first_non_empty({dataRefs.cslFixOpIcao, inFd.WaitForSafeCopyStat().airlineCode()}).c_str(), - str_first_non_empty({dataRefs.cslFixLivery, inFd.WaitForSafeCopyStat().reg}).c_str()), + str_first_non_empty({dataRefs.cslFixLivery, inFd.WaitForSafeCopyStat().reg}).c_str(), + (XPMPPlaneID)inFd.key().num), // class members fd(inFd), mdl(FlightModel::FindFlightModel(inFd.WaitForSafeCopyStat().acTypeIcao)), // find matching flight model @@ -1906,6 +1959,14 @@ void LTAircraft::CalcFlightModel (const positionTy& /*from*/, const positionTy& if ( !bOnGrndPrev && bOnGrnd && bFPhPrev != FPH_UNKNOWN ) { phase = FPH_TOUCH_DOWN; } + + // *** Cars will not actually fly, so we only allow for a limited set of status + if (fd.GetUnsafeStat().isGrndVehicle() && + phase != FPH_TAXI && + phase != FPH_STOPPED_ON_RWY) + { + phase = FPH_TAXI; + } // *** take action based on flight phase (change) *** @@ -2612,6 +2673,10 @@ XPMPPlaneCallbackResult LTAircraft::GetPlanePosition(XPMPPlanePosition_t* outPos if (!dataRefs.IsReInitAll() && // avoid any calc if to be re-initialized CalcPPos()) { + // If needed update the chosen CSL model + if (ShallUpdateModel()) + ChangeModel(); + // copy ppos (by type conversion) *outPosition = ppos; @@ -2804,14 +2869,19 @@ XPMPPlaneCallbackResult LTAircraft::GetInfoTexts(XPMPInfoTexts_t* outInfo) } // change the model (e.g. when model-defining static data changed) -void LTAircraft::ChangeModel (const LTFlightData::FDStaticData& statData) +void LTAircraft::ChangeModel () { + // Try to fetch the static data + LTFlightData::FDStaticData statData; + if (!fd.TryGetSafeCopy(statData)) + return; + + // Save previous model name to identify an actual change const std::string oldModelName(GetModelName()); CalcLabelInternal(statData); - XPMPChangePlaneModel(mPlane, - statData.acTypeIcao.c_str(), - statData.opIcao.c_str(), - statData.reg.c_str()); + XPMP2::Aircraft::ChangeModel(str_first_non_empty({dataRefs.cslFixAcIcaoType, statData.acTypeIcao}), + str_first_non_empty({dataRefs.cslFixOpIcao, statData.airlineCode()}), + str_first_non_empty({dataRefs.cslFixLivery, statData.reg})); // if there was an actual change inform the log if (oldModelName != GetModelName()) { @@ -2820,4 +2890,7 @@ void LTAircraft::ChangeModel (const LTFlightData::FDStaticData& statData) statData.opIcao.c_str(), GetModelName().c_str()); } + + // reset the flag that we needed to change the model + bChangeModel = false; } diff --git a/Src/LTApt.cpp b/Src/LTApt.cpp index 13b067f0..35562e6f 100644 --- a/Src/LTApt.cpp +++ b/Src/LTApt.cpp @@ -1851,7 +1851,7 @@ void Apt::AddApt (Apt&& apt) listPaths.clear(); vecPathEnds.clear(); -#ifdef DEBUG +#ifdef APT_DUMP if (dataRefs.GetLogLevel() == logDEBUG) LTAptDump(key); #endif @@ -2606,7 +2606,7 @@ void LTAptDisable () } -#ifdef DEBUG +#ifdef APT_DUMP // Dumps the entire taxi network into a CSV file readable by GPS Visualizer /// @see For a suggestion of settings for display: /// https://www.gpsvisualizer.com/map_input?bg_map=google_openstreetmap&bg_opacity=70&form=leaflet&google_wpt_sym=diamond&trk_list=0&trk_opacity=100&trk_width=2&units=metric&width=1400&wpt_color=aqua diff --git a/Src/LTFlightData.cpp b/Src/LTFlightData.cpp index bf1b3fff..a8cd6d76 100644 --- a/Src/LTFlightData.cpp +++ b/Src/LTFlightData.cpp @@ -70,22 +70,38 @@ std::string LTFlightData::FDDynamicData::GetSquawk() const } } -LTFlightData::FDStaticData& LTFlightData::FDStaticData::operator |= (const FDStaticData& other) +// Merges data, i.e. copy only filled fields from 'other' +bool LTFlightData::FDStaticData::merge (const FDStaticData& other) { + // Have matching-relevant fields changed? + bool bRet = false; + // copy filled, and only filled data over current data // do it field-by-field only for fields which are actually filled - - // acTypeICAO: accept another value only if our current one is not helpful - if ((acTypeIcao.empty() || - acTypeIcao == dataRefs.GetDefaultAcIcaoType() || - acTypeIcao == dataRefs.GetDefaultCarIcaoType()) && - !other.acTypeIcao.empty()) - acTypeIcao = other.acTypeIcao; + + // acTypeICAO + // We never overwrite with nothing, ie. the new value must be _something_ + if (!other.acTypeIcao.empty() && + acTypeIcao != other.acTypeIcao) + { + // Accept anything if we are currently empty (unknown/default) or a car, can't be worse... + if (acTypeIcao.empty() || + acTypeIcao == dataRefs.GetDefaultCarIcaoType()) + { + acTypeIcao = other.acTypeIcao; + bRet = true; + } + // else: we are non-empty, non-default -> no change, no matter what is delivered, + // to avoid ping-ponging the a/c plane when different channels have different opinion + } // a/c details if (!other.country.empty()) country = other.country; if (!other.man.empty()) man = other.man; - if (other.mdl.length() > mdl.length()) mdl = other.mdl; + if (other.mdl.length() > mdl.length()) { + mdl = other.mdl; + bRet = true; + } if (!other.catDescr.empty()) catDescr = other.catDescr; if (other.year) year = other.year; if (other.mil) mil = other.mil; // this only overwrite if 'true'... @@ -105,8 +121,18 @@ LTFlightData::FDStaticData& LTFlightData::FDStaticData::operator |= (const FDSta // operator / Airline if (!other.op.empty()) op = other.op; - if (!other.opIcao.empty()) opIcao = other.opIcao; + // operator ICAO: we only accept a change from nothing to something + if (opIcao.empty() && !other.opIcao.empty()) { + opIcao = other.opIcao; + bRet = true; + } + // registration: we only accept a change from nothing to something + if (reg.empty() && !other.reg.empty()) { + reg = other.reg; + bRet = true; + } + // now initialized bInit = true; @@ -118,7 +144,7 @@ LTFlightData::FDStaticData& LTFlightData::FDStaticData::operator |= (const FDSta if (mdl.empty()) mdl = pDoc8643->model; - return *this; + return bRet; } // route (this is "originAp - destAp", but considers emoty txt) @@ -163,6 +189,12 @@ std::string LTFlightData::FDStaticData::flightRoute() const return (flight + ": ") + r; } +// is this a ground vehicle? +bool LTFlightData::FDStaticData::isGrndVehicle() const +{ + return acTypeIcao == dataRefs.GetDefaultCarIcaoType(); +} + // set the key value std::string LTFlightData::FDKeyTy::SetKey (FDKeyType _eType, unsigned long _num) { @@ -943,7 +975,7 @@ bool LTFlightData::CalcNextPos ( double simTime ) stopPos.f.flightPhase != FPH_TOUCH_DOWN && // don't copy touch down pos, that looks ugly, and hinders auto-land/stop stopPos.f.flightPhase != FPH_STOPPED_ON_RWY) // avoid adding several stops { - stopPos.ts() += simTime + 10.0; // just assume _some_ time + stopPos.ts() = simTime + 10.0; // just assume _some_ time stopPos.f.flightPhase = FPH_STOPPED_ON_RWY; // indicator for aritifical stop (not only on rwy now...) if (dataRefs.GetDebugAcPos(key())) LOG_MSG(logDEBUG, "%s: Added stop-position %s", @@ -2121,38 +2153,49 @@ void LTFlightData::UpdateData (const LTFlightData::FDStaticData& inStat) LTACMasterdataChannel::RequestMasterData (key(), inStat.call); } - // merge inStat into our statData (copy only filled fields, - // which are not model-defining): - statData |= inStat; - - // *** take care of changes in model-defining fields *** - // (a/c type, operator, registration) + // If no a/c type is yet known try if the call sign / operator looks like a ground vehicle bool bMdlInfoChange = false; - - // operator ICAO: we only accept a change from nothing to something - if (statData.opIcao.empty() && !inStat.opIcao.empty()) + if (statData.acTypeIcao.empty() && inStat.acTypeIcao.empty()) { - statData.opIcao = inStat.opIcao; - bMdlInfoChange = true; + // Try operator first + if (!inStat.op.empty()) { + std::string op_u = inStat.op; + str_toupper(op_u); + if (op_u.find("AIRPORT") != std::string::npos) { + statData.op = inStat.op; + statData.acTypeIcao = dataRefs.GetDefaultCarIcaoType(); + LOG_MSG(logINFO, INFO_GND_VEHICLE_APT, key().c_str(), inStat.op.c_str()); + bMdlInfoChange = true; + } + } + + // Try callsign next + if (statData.acTypeIcao.empty() && + !inStat.call.empty() && inStat.call != statData.call && + LTAircraft::FlightModel::MatchesCar(inStat.call)) + { + statData.call = inStat.call; + statData.acTypeIcao = dataRefs.GetDefaultCarIcaoType(); + LOG_MSG(logINFO, INFO_GND_VEHICLE_CALL, key().c_str(), inStat.call.c_str()); + bMdlInfoChange = true; + } } - // registration: we only accept a change from nothing to something - if (statData.reg.empty() && !inStat.reg.empty()) - { - statData.reg = inStat.reg; + // merge inStat into our statData and save if matching-relevant stuff changed + if (statData.merge(inStat)) bMdlInfoChange = true; - } // Re-determine a/c model (only if it was determined before: - // the first determination shall be made as late as possible + // the very first determination shall be made as late as possible // in LTFlightData::CreateAircraft()) if (pAc && DetermineAcModel()) bMdlInfoChange = true; - // if model-defining fields changed then (potentially) change the CSL model if (pAc) { + // if model-defining fields changed then (potentially) change the CSL model if (bMdlInfoChange) - pAc->ChangeModel (statData); + pAc->SetUpdateModel(); + // Make Aircraft send updated info texts pAc->SetSendNewInfoData(); } @@ -2262,10 +2305,16 @@ bool LTFlightData::AircraftMaintenance ( double simTime ) // try interpreting model text or check for ground vehicle bool LTFlightData::DetermineAcModel() { - // We don't change the a/c type if it is already something reasonable const std::string prevType = statData.acTypeIcao; + + // Debugging model matching: If the model is fixed, then it is what it is + if (!dataRefs.cslFixAcIcaoType.empty()) { + statData.acTypeIcao = dataRefs.cslFixAcIcaoType; + return statData.acTypeIcao != prevType; + } + + // We don't change the a/c type if it is already something reasonable if (!prevType.empty() && - prevType != dataRefs.GetDefaultAcIcaoType() && prevType != dataRefs.GetDefaultCarIcaoType()) return false; @@ -2286,47 +2335,29 @@ bool LTFlightData::DetermineAcModel() return false; } - // Ground vehicle maybe? Shall be on the ground then - if (( - (pAc && pAc->IsOnGrnd() && pAc->GetSpeed_kt() < pAc->mdl.MAX_TAXI_SPEED) || - (!posDeque.empty() && posDeque.front().IsOnGnd()) - ) && - // OpenSky only delivers "category description" and has a - // pretty clear indicator for a ground vehicle - (statData.catDescr.find(OPSKY_MD_TEXT_VEHICLE) != std::string::npos || - // I'm having the feeling that if nearly all is empty and the category description is "No Info" then it's often also a ground vehicle - (statData.catDescr.find(OPSKY_MD_TEXT_NO_CAT) != std::string::npos && - statData.man.empty() && statData.mdl.empty() && statData.opIcao.empty()) || - // ADSBEx doesn't send as clear an indicator, but data analysis - // suggests that EngType/Mount == 0 is a good indicator - (statData.engType == 0 && statData.engMount == 0) || - // for RealTraffic it is even more difficult...we best identify RT-only data with no operator (opIcao is taken from call sign) - (statData.op.empty() && statData.reg.empty() && statData.destAp.empty())) - ) + // Ground vehicle maybe? Shall be on the ground then with reasonable speed + // (The info if this _could_ be a car is delivered by the channels via + // the acTypeIcao, here we just validate if the dynamic situation + // fits a car.) + if (prevType == dataRefs.GetDefaultCarIcaoType()) { - // We would now decide for surface vehicle - // but we only do so if we had no other type before - // (ie. if we previously had decided for standard a/c then we keep that!) - if (prevType.empty()) { + if ((pAc && // plane exists? + pAc->IsOnGrnd() && // must be on ground with reasonable speed + pAc->GetSpeed_kt() <= MDL_CAR_MAX_TAXI) || + (!pAc && // no plane yet: + posDeque.size() >= 2 && // analyse ground status of and speed between first two positions + posDeque.front().IsOnGnd() && posDeque[1].IsOnGnd() && + posDeque.front().speed_kt(posDeque[1]) <= MDL_CAR_MAX_TAXI)) + { + // We now decide for surface vehicle statData.acTypeIcao = dataRefs.GetDefaultCarIcaoType(); - return true; - } else { - statData.acTypeIcao = std::move(prevType); - return false; + return statData.acTypeIcao != prevType; } } - - // we have no better idea than standard - statData.acTypeIcao = dataRefs.GetDefaultAcIcaoType(); - if (prevType != statData.acTypeIcao) - { - LOG_MSG(logWARN,ERR_NO_AC_TYPE, - key().c_str(), - statData.man.c_str(), statData.mdl.c_str(), - statData.acTypeIcao.c_str()); - return true; - } - return false; + + // we have no better idea than default + statData.acTypeIcao.clear(); + return prevType != statData.acTypeIcao; } // checks if there is a slot available to create this a/c, tries to remove the farest a/c if too many a/c rendered @@ -2415,7 +2446,13 @@ bool LTFlightData::CreateAircraft ( double simTime ) // Make sure we have a valid a/c model now DetermineAcModel(); - + if (statData.acTypeIcao.empty()) { // we don't... + LOG_MSG(logWARN,ERR_NO_AC_TYPE, + key().c_str(), + statData.man.c_str(), statData.mdl.c_str(), + dataRefs.GetDefaultAcIcaoType().c_str()); + } + // create the object (constructor will recursively re-access the lock) try { pAc = new LTAircraft(*this); @@ -2465,7 +2502,7 @@ void LTFlightData::UpdateAllModels () // if there is an aircraft update it's flight model LTAircraft* pAc = fdPair.second.GetAircraft(); if (pAc) - pAc->ChangeModel(fdPair.second.GetUnsafeStat()); + pAc->SetUpdateModel(); } } catch(const std::system_error& e) { LOG_MSG(logERR, ERR_LOCK_ERROR, "mapFd", e.what()); diff --git a/Src/LTMain.cpp b/Src/LTMain.cpp index 4474a7d3..fc7296de 100644 --- a/Src/LTMain.cpp +++ b/Src/LTMain.cpp @@ -28,6 +28,9 @@ #include // for ShellExecuteA #endif +// Puts some timestamps into the log for analysis purposes +void LogTimestamps (); + //MARK: Path helpers // construct path: if passed-in base is a full path just take it @@ -268,6 +271,8 @@ float LoopCBAircraftMaintenance (float inElapsedSinceLastCall, float, int, void* dataRefs.SetUseHistData(dataRefs.GetUseHistData(), true); // and reset the re-init flag dataRefs.SetReInitAll(false); + // Log a new timestamp + LogTimestamps(); } } catch (const std::exception& e) { // Exception during re-init...we give up and disable ourselves @@ -305,8 +310,12 @@ float LoopCBAircraftMaintenance (float inElapsedSinceLastCall, float, int, void* int MPIntPrefsFunc (const char*, const char* key, int iDefault) { // debug XPMP's CSL model matching if requested - if (!strcmp(key, XPMP_CFG_ITM_MODELMATCHING)) - return dataRefs.GetDebugModelMatching(); + if (!strcmp(key, XPMP_CFG_ITM_MODELMATCHING)) { + if constexpr (VERSION_BETA) // force logging of model-matching in BETA versions + return true; + else + return dataRefs.GetDebugModelMatching(); + } // logging level to match ours if (!strcmp(key, XPMP_CFG_ITM_LOGLEVEL)) { if constexpr (VERSION_BETA) // force DEBUG-level logging in BETA versions @@ -317,10 +326,6 @@ int MPIntPrefsFunc (const char*, const char* key, int iDefault) // We don't want clamping to the ground, we take care of the ground ourselves if (!strcmp(key, XPMP_CFG_ITM_CLAMPALL)) return 0; - // Backdoor to skip assigning the NoPlane.acf to AI planes - if (!strcmp(key, XPMP_CFG_ITM_SKIP_NOPLANE)) - return dataRefs.ShallAISkipAssignNoPlane(); - // dont' know/care about the option, return the default value return iDefault; } @@ -374,7 +379,8 @@ bool LTMainInit () const char* cszResult = XPMPMultiplayerInit (LIVE_TRAFFIC, LTCalcFullPluginPath(PATH_RESOURCES).c_str(), &MPIntPrefsFunc, - dataRefs.GetDefaultAcIcaoType().c_str()); + dataRefs.GetDefaultAcIcaoType().c_str(), + LIVE_TRAFFIC_XPMP2); if ( cszResult[0] ) { LOG_MSG(logFATAL,ERR_XPMP_ENABLE, cszResult); XPMPMultiplayerCleanup(); @@ -495,6 +501,7 @@ static float CBToggleAI (float, float, int, void *) LTMainReleaseAIAircraft(); else LTMainTryGetAIAircraft(); + MenuUpdateAllItemStatus(); return 0.0f; } @@ -540,6 +547,9 @@ void LTMainHideAircraft () // hide aircraft, disconnect internet streams LTFlightDataHideAircraft (); + + // Remove any message about seeing planes + CreateMsgWindow(float(AC_MAINT_INTVL), 0, 0, -1); // disable the flight loop callback XPLMSetFlightLoopCallbackInterval(LoopCBAircraftMaintenance, @@ -548,7 +558,9 @@ void LTMainHideAircraft () NULL); // disable aircraft drawing, free up multiplayer planes - XPMPMultiplayerDisable(); + // (the "soft way", which requires a few more drawing cycles, + // this will _not_ work while being shut down) + LTMainToggleAI(false); // tell the user there are no more SHOW_MSG(logINFO, MSG_NUM_AC_ZERO); @@ -562,6 +574,7 @@ void LTMainDisable () // remove aircraft...just to be sure dataRefs.SetAircraftDisplayed(false); + LTMainReleaseAIAircraft(); // to be absolutely sure // disable fetching flight data LTFlightDataDisable(); diff --git a/Src/LTOpenSky.cpp b/Src/LTOpenSky.cpp index f65fec25..77718e4d 100644 --- a/Src/LTOpenSky.cpp +++ b/Src/LTOpenSky.cpp @@ -245,16 +245,6 @@ bool OpenSkyAcMasterdata::FetchAllData (const positionTy& /*pos*/) break; case HTTP_NOT_FOUND: // doesn't know a/c, don't query again invIcaos.emplace_back(info.acKey.icao); - - // We add "model=[?], owner=[?]". By this way we don't trigger the car detection, - // that means: If we don't know the transponder at all we'd rather decide on standard a/c but not car - if (data.length() > 1) // concatenate both JSON groups - data += ", "; - data += "\"" OPSKY_MD_GROUP "\": { \"" OPSKY_MD_TRANSP_ICAO "\": \""; - data += currKey; - data += "\", \"" OPSKY_MD_MDL "\": \"" OPSKY_MD_MDL_UNKNOWN "\", " - "\"" OPSKY_MD_OP "\": \"" OPSKY_MD_MDL_UNKNOWN "\" }"; - bChannelOK = true; // but technically a valid response break; case HTTP_BAD_REQUEST: // uh uh...done something wrong, don't do that again @@ -412,6 +402,21 @@ bool OpenSkyAcMasterdata::ProcessFetchedData (mapLTFlightDataTy& /*fdMap*/) statDat.catDescr = jog_s(pJAc, OPSKY_MD_CAT_DESCR); statDat.op = jog_s(pJAc, OPSKY_MD_OP); statDat.opIcao = jog_s(pJAc, OPSKY_MD_OP_ICAO); + + // -- Ground vehicle identification -- + // OpenSky only delivers "category description" and has a + // pretty clear indicator for a ground vehicle + if (statDat.acTypeIcao.empty() && // don't know a/c type yet + (statDat.catDescr.find(OPSKY_MD_TEXT_VEHICLE) != std::string::npos || + // I'm having the feeling that if nearly all is empty and the category description is "No Info" then it's often also a ground vehicle + (statDat.catDescr.find(OPSKY_MD_TEXT_NO_CAT) != std::string::npos && + statDat.man.empty() && + statDat.mdl.empty() && + statDat.opIcao.empty()))) + { + // we assume ground vehicle + statDat.acTypeIcao = dataRefs.GetDefaultCarIcaoType(); + } } // *** Route Information *** diff --git a/Src/LTRealTraffic.cpp b/Src/LTRealTraffic.cpp index 961a3c19..14591ed0 100644 --- a/Src/LTRealTraffic.cpp +++ b/Src/LTRealTraffic.cpp @@ -224,7 +224,7 @@ std::string RealTrafficConnection::GetStatusStr() const { switch (status) { case RT_STATUS_NONE: return ""; - case RT_STATUS_STARTING: return "Starting..."; + case RT_STATUS_STARTING: return "Waiting for RealTraffic..."; case RT_STATUS_CONNECTED_PASSIVELY: return "Connected passively"; case RT_STATUS_CONNECTED_TO: return "Connected, waiting..."; case RT_STATUS_CONNECTED_FULL: return "Fully connected"; @@ -719,69 +719,79 @@ bool RealTrafficConnection::ProcessRecvedTrafficData (const char* traffic) if ( fd.empty() ) fd.SetKey(fdKey); - // fill static data - { - LTFlightData::FDStaticData stat; - - stat.acTypeIcao = tfc[RT_TFC_TYPE]; - stat.call = tfc[RT_TFC_CS]; - - if (tfc[RT_TFC_MSG_TYPE] == RT_TRAFFIC_AITFC) { - stat.reg = tfc[RT_TFC_TAIL]; - stat.originAp = tfc[RT_TFC_FROM]; - stat.destAp = tfc[RT_TFC_TO]; - } + // -- fill static data -- + LTFlightData::FDStaticData stat; + + stat.acTypeIcao = tfc[RT_TFC_TYPE]; + stat.call = tfc[RT_TFC_CS]; + + if (tfc[RT_TFC_MSG_TYPE] == RT_TRAFFIC_AITFC) { + stat.reg = tfc[RT_TFC_TAIL]; + stat.originAp = tfc[RT_TFC_FROM]; + stat.destAp = tfc[RT_TFC_TO]; + } - fd.UpdateData(std::move(stat)); + // -- dynamic data -- + LTFlightData::FDDynamicData dyn; + + // non-positional dynamic data + dyn.gnd = tfc[RT_TFC_AIRBORNE] == "0"; + dyn.heading = std::stoi(tfc[RT_TFC_HDG]); + dyn.spd = std::stoi(tfc[RT_TFC_SPD]); + dyn.vsi = std::stoi(tfc[RT_TFC_VS]); + dyn.ts = posTime; + dyn.pChannel = this; + + // *** gnd detection hack *** + // RealTraffic keeps the airborne flag always 1, + // even with traffic which definitely sits on the gnd. + // Also, reported altitude never seems to become negative, + // though this would be required in high pressure weather + // at airports roughly at sea level. + // And altitude is rounded to 250ft which means that close + // to the ground it could be rounded down to 0! + // + // If "0" is reported we need to assume "on gnd" and bypass + // the pressure correction. + // If at the same time VSI is reported significantly (> +/- 100) + // then we assume plane is already/still flying, but as we + // don't know exact altitude we just skip this record. + if (tfc[RT_TFC_ALT] == "0") { + // skip this dynamic record in case VSI is too large + if (std::abs(dyn.vsi) > RT_VSI_AIRBORNE) + return true; + // have proper gnd altitude calculated + pos.alt_m() = NAN; + dyn.gnd = true; + } else { + // probably not on gnd, so take care of altitude + // altitude comes without local pressure applied + double alt_f = std::stod(tfc[RT_TFC_ALT]); + alt_f += (hPa - HPA_STANDARD) * FT_per_HPA; + pos.SetAltFt(alt_f); } - // dynamic data - { // unconditional...block is only for limiting local variables - LTFlightData::FDDynamicData dyn; - - // non-positional dynamic data - dyn.gnd = tfc[RT_TFC_AIRBORNE] == "0"; - dyn.heading = std::stoi(tfc[RT_TFC_HDG]); - dyn.spd = std::stoi(tfc[RT_TFC_SPD]); - dyn.vsi = std::stoi(tfc[RT_TFC_VS]); - dyn.ts = posTime; - dyn.pChannel = this; - - // *** gnd detection hack *** - // RealTraffic keeps the airborne flag always 1, - // even with traffic which definitely sits on the gnd. - // Also, reported altitude never seems to become negative, - // though this would be required in high pressure weather - // at airports roughly at sea level. - // And altitude is rounded to 250ft which means that close - // to the ground it could be rounded down to 0! - // - // If "0" is reported we need to assume "on gnd" and bypass - // the pressure correction. - // If at the same time VSI is reported significantly (> +/- 100) - // then we assume plane is already/still flying, but as we - // don't know exact altitude we just skip this record. - if (tfc[RT_TFC_ALT] == "0") { - // skip this dynamic record in case VSI is too large - if (std::abs(dyn.vsi) > RT_VSI_AIRBORNE) - return true; - // have proper gnd altitude calculated - pos.alt_m() = NAN; - dyn.gnd = true; - } else { - // probably not on gnd, so take care of altitude - // altitude comes without local pressure applied - double alt_f = std::stod(tfc[RT_TFC_ALT]); - alt_f += (hPa - HPA_STANDARD) * FT_per_HPA; - pos.SetAltFt(alt_f); - } - - // don't forget gnd-flag in position - pos.f.onGrnd = dyn.gnd ? GND_ON : GND_OFF; - - // add dynamic data - fd.AddDynData(dyn, 0, 0, &pos); + // don't forget gnd-flag in position + pos.f.onGrnd = dyn.gnd ? GND_ON : GND_OFF; + + // -- Ground vehicle identification -- + // is really difficult with RealTraffic as we only have very few information: + if (stat.acTypeIcao.empty() && // don't know a/c type yet + dyn.gnd && // on the ground + dyn.spd < 50.0 && // reasonable speed + stat.reg.empty() && // no tail number + stat.destAp.empty()) // no destination airport + { + // we assume ground vehicle + stat.acTypeIcao = dataRefs.GetDefaultCarIcaoType(); } + + // add the static data + fd.UpdateData(std::move(stat)); + + // add the dynamic data + fd.AddDynData(dyn, 0, 0, &pos); + } catch(const std::system_error& e) { LOG_MSG(logERR, ERR_LOCK_ERROR, "mapFd", e.what()); return false; diff --git a/Src/LiveTraffic.cpp b/Src/LiveTraffic.cpp index d5ca4428..2f4ccadb 100755 --- a/Src/LiveTraffic.cpp +++ b/Src/LiveTraffic.cpp @@ -342,6 +342,26 @@ bool RegisterCommandHandlers () //MARK: One-Time Setup (Flight Loop Callback) +/// Puts some timestamps into the log for analysis purposes +void LogTimestamps () +{ + // current Zulu time + char tZuluS[100]; + struct tm zulu; + std::time_t t = std::time(nullptr); + gmtime_s(&zulu, &t); + std::strftime(tZuluS, sizeof(tZuluS), "%d-%b-%Y %T", &zulu); + + // current simTime + char tSimZ[100]; + t = std::time_t(dataRefs.GetSimTime()); + gmtime_s(&zulu, &t); + std::strftime(tSimZ, sizeof(tSimZ), "%d-%b-%Y %T", &zulu); + + // Log it + LOG_MSG(logMSG, MSG_TIMESTAMPS, tZuluS, tSimZ); +} + // For informing dataRe Editor and tool see // http://www.xsquawkbox.net/xpsdk/mediawiki/DataRefEditor and // https://github.com/leecbaker/datareftool/blob/master/src/plugin_custom_dataref.cpp @@ -383,6 +403,9 @@ float LoopCBOneTimeSetup (float, float, int, void*) return 2; case ONCE_CB_AUTOSTART: + // Log a timestamp to synch timing for analysis purposes + LogTimestamps (); + // Auto Start display of aircraft if (dataRefs.GetAutoStart()) dataRefs.SetAircraftDisplayed(true); @@ -444,6 +467,9 @@ PLUGIN_API int XPluginStart( // use native paths, i.e. Posix style (as opposed to HFS style) // https://developer.x-plane.com/2014/12/mac-plugin-developers-you-should-be-using-native-paths/ XPLMEnableFeature("XPLM_USE_NATIVE_PATHS",1); + + // For nice logging, let XPMP2 know our names already + XPMPSetPluginName(LIVE_TRAFFIC, LIVE_TRAFFIC_XPMP2); // init DataRefs if (!dataRefs.Init()) { DestroyWindow(); return 0; } diff --git a/Src/TFWidgets.cpp b/Src/TFWidgets.cpp index 0123e2d2..0e676089 100644 --- a/Src/TFWidgets.cpp +++ b/Src/TFWidgets.cpp @@ -218,7 +218,7 @@ std::string TFGetWidgetDescriptor (XPWidgetID me) return std::string(); char* buf = new char[len+1]; - XPGetWidgetDescriptor (me, buf, len); + XPGetWidgetDescriptor (me, buf, len+1); std::string ret = buf; delete[] buf; return ret; diff --git a/Src/TextIO.cpp b/Src/TextIO.cpp index 7820fa06..3161626f 100644 --- a/Src/TextIO.cpp +++ b/Src/TextIO.cpp @@ -317,23 +317,14 @@ const char* GetLogString (const char* szPath, int ln, const char* szFunc, // prepare timestamp if (lvl < logMSG) // normal messages without, all other with location info { - // prepare current simTime in string form - const double simTime = dataRefs.GetSimTime(); - std::time_t t = std::time_t(simTime); - struct tm zulu; - gmtime_s(&zulu, &t); - char tZuluS[100]; - std::strftime(tZuluS, sizeof(tZuluS), "%d-%b %T", &zulu); - #if IBM const char* szFile = strrchr(szPath, '\\'); // extract file from path #else const char* szFile = strrchr(szPath, '/'); // extract file from path #endif if (!szFile) szFile = szPath; else szFile++; - snprintf(aszMsg, sizeof(aszMsg), "%u:%02u:%06.3f " LIVE_TRAFFIC " %s%5.4fZ %s %s:%d/%s: ", + snprintf(aszMsg, sizeof(aszMsg), "%u:%02u:%06.3f " LIVE_TRAFFIC " %s %s:%d/%s: ", runH, runM, runS, // Running time stamp - tZuluS, fmod(simTime, 1.0), // simTime formated as "DD-MMM HH:MM:SS.SSS" LOG_LEVEL[lvl], // logging level szFile, ln, szFunc); // source code location info } diff --git a/docs/readme.html b/docs/readme.html index 440ee31b..5cc5e1fe 100755 --- a/docs/readme.html +++ b/docs/readme.html @@ -32,11 +32,11 @@

Documentation

Upgrade from v1.5 to v2.0

-

Please follow +

Please follow upgrade instructions for upgrading an existing LiveTraffic installation to version 2.0.

- +

Essential Installation

LiveTraffic plugin

@@ -83,24 +83,106 @@

Resulting Directory Structure

Release Notes

The number-link refers to the issue in GitHub with (often technical) details.

- +

v2.0

- -

v2.01 and v2.02

- -

The published Windows version of v2.01 had not included all fixes, so v2.02 - is published only to also provide the correct Windows binary.

- + +

v2.03

+

Please follow upgrade instructions for upgrading an existing LiveTraffic v1.5 (or earlier) installation to version 2.0.

- + +

For updating coming from v2.01 or v2.02 +

    +
  • copy +
    • lin|mac|win_x64/LiveTraffic.xpl,
    • +
    • Resources/CSL2XSB.py, and
    • +
    • Resources/FlightModels.prf.
    • +
    +
  • +
  • remove
    • Resources/NoPlane.acf as well as
    • +
    • <X-Plane>/Aircraft/LiveTraffic/.
    • +
  • +
  • It is recommended to + run CSL2XSB.py + on your CSL models again if you ever did so before, + no matter the settings you had used.
  • +
+

+ +

Change log:

+ +
    +
  • CHANGED TCAS handling to new + TCAS override + approach that was introduced with X-Plane 11.50 Beta 8. +
      +
    • TCAS functionality requires X-Plane 11.50b8 or later. + On earlier version TCAS can no longer be activated.
    • +
    • Up to 63 TCAS blibs will show on Laminar's instruments.
    • +
    • You no longer need to configure AI Aircraft + in X-Plane's settings just for LiveTraffic's purposes.
    • +
    • Other plugins' TCAS implementations as well as 3rd party plugins + currently accessing the classic up to 19 multiplayer + dataRefs will first need to adapt to the new approach + to benefit from the up to 63 planes.
    • +
    • Attention developers, + if using LiveTraffic's AI multiplayer data: + The previous slotting mechanism is removed, that means + that planes can potentially change AI slots with every + flight loop cycle. They are sorted by distance. + This is because the new + TCAS target mechanism + offers the sim/cockpit2/tcas/targets/modeS_id[64] + dataRef array to keep track of an aircraft's identity.
    • +
    +
  • +
  • TEMPORARILY DEACTIVATED LiveTraffic's own rendering of the map due to a + bug in X-Plane 11.50b9, causing crashes when the location changes. + Instead, X-Plane's rendering of the now up to 63 TCAS planes + is activated so that you see at least some standard icons.
    + Bug is filed with Laminar. Map will come back once that bug is fixed.
  • +
  • CHANGED #186 + identification of ground vehicles, takes operator and call sign + into consideration. Call sign matches configured in + FlightModel.prf.
  • +
  • FIXED identification of ground vehicles for the purpose of chosing a map icon + if car ICAO type is configured different from standard ZZZC. +
  • FIXED XPMP2 #9: + Allowing models with same name from different CSL packages. + Matching will pick randomly, also across packages.
    + This removes many "Duplicate model" warnings from the log. + It remains a warning if the same name is reused within one + package.
  • +
  • FIXED #183 + ghost planes: Some CSL models were not drawn due to a bug while reading xsb_aircraft.txt.
  • +
  • FIXED #185 + garbage character read from Settings text fields.
  • +
  • FIXED #184: + Aircraft on the ground, for which no updates are available any longer, + were not properly removed but could just sit there forever.
  • +
  • REMOVED handling of ICAO/AIRLINE lines from + CSL2XSB.py, which previously was meant to define default models + per aircraft type and airline. With XPMP2's random pick mechanism this is no longer needed + and would even reduce the match quality of the models previously chosen as defaults.
    + Execute CSL2XSB.py again on your models to benefit + from this change.
  • +
  • In Beta versions like this, Log Model Matching + (Debug Settings) + is forced to be activated.
  • +
+ +

v2.01 and v2.02

+ +

The published Windows version of v2.01 had not included all fixes, so v2.02 + is published only to also provide the correct Windows binary.

+

For updating coming from v2.00 copy lin|mac|win_x64/LiveTraffic.xpl - and Resources/NoPlane.acf

- + and Resources/NoPlane.acf.

+

- +

  • CSL Models: If VERT_OFFSET is not specified in xsb_aircraft.txt then it is extracted from the model's .obj file. @@ -140,9 +222,9 @@

    v2.01 and v2.02

- +

v2.00

- +
  • Supports X-Plane 11 only.
    @@ -185,15 +267,19 @@

    v2.00

    is neither support nor needed any longer. The "Missing Gear" issue is history when using instancing.
+ +
  • In Beta versions like this, Log Level + (Advanced Settings) + is forced to be Debug.
  • v1.5

    - +

    v1.52

    - +

    To update coming from v1.50 or v1.51: Just copy the 3 executables LiveTraffic/64/*.xpl.

    - +
    • FIXED #174: Aircraft sometimes failed to create when best available data was just half @@ -205,25 +291,25 @@

      v1.52

      (introduced with v1.51)
    - +

    v1.51

    - +

    To update coming from v1.50: Just copy the 3 executables LiveTraffic/64/*.xpl.

    - +
    • PARTICIALLY FIXED #174: Should avoid crashing after a rare and yet unexplained event leading to missing data while trying to create a new aircraft.
    - +

    v1.50

    To update coming from v1.24: Copy the 3 executables LiveTraffic/64/*.xpl and the updated Resources/Doc8643.txt file.

    - +
    • ADDED #100: Snapping to taxiways to make aircraft stay on taxiways more often. @@ -252,7 +338,7 @@

      v1.50

    v1.2

    - +

    v1.24

      @@ -269,9 +355,9 @@

      v1.23

    • copy the updated files LiveTraffic/Resources/Doc8643.txt and LiveTraffic/Resources/related.txt.
    - +

    Changes to LiveTraffic:

    - +
    • FIXED handling of OpenSky master data, avoiding duplicate requests. Reduces likelihood of HTTP 503 error and improves handling of it.
    • @@ -296,7 +382,7 @@

      v1.23

    v1.22 Fixits

    - +
    • ADDED #44 @@ -321,9 +407,9 @@

      v1.22 Fixits

    - +

    v1.21 Fixits

    - +
    • ADDED #152 @@ -458,9 +544,9 @@

      v1.20.190718 CSL / AI / Multiplayer enhancements

      v1.10

      v1.16.190507 ADSBEx with RapidAPI

      - +

      - +

      • ADDED #138 @@ -481,7 +567,7 @@

        v1.16.190507 ADSBEx with RapidAPI

        using LiveTraffic even on outdated vesions like XP10.45 (untested).
      - +

      v1.15.190428 ADSBEx with API Key

        @@ -502,7 +588,7 @@

        v1.15.190428 ADSBEx with API Key

        errorneous warning valid OBJ8 part types are LIGHTS or SOLID. Got LIGHTS..
      - +

      v1.10.190406 RealTraffic, ForeFlight