diff --git a/Data/RealTraffic/RTAPI_example.py b/Data/RealTraffic/RTAPI_example.py index 08b9763..e9df886 100755 --- a/Data/RealTraffic/RTAPI_example.py +++ b/Data/RealTraffic/RTAPI_example.py @@ -90,18 +90,18 @@ def UDPbcast(ip, bcast, port, data): # Example area: Heidiland traffic_payload = { "GUID": "%s" % GUID, "querytype": "locationtraffic", - "top": 47.8, - "bottom": 45.6, - "left": 5.7, - "right": 10.7, + "top": 52.4, # Around EDLE + "bottom": 50.4, + "left": 5.9, + "right": 7.9, "toffset": 0 } weather_payload = { "GUID": "%s" % GUID, "querytype": "locwx", - "lat": 47.8, - "lon": 7.7, - "alt": 36000, - "airports": "LSZB|LSZH|LSGG|LFSB", + "lat": 51.4069, # EDLE + "lon": 6.9391, + "alt": 0, + "airports": "UNKN", # Don't need airport METAR "toffset": 0 } ############################################################### @@ -110,6 +110,7 @@ def UDPbcast(ip, bcast, port, data): # fetch weather response = requests.post(weather_url, weather_payload, headers=header) + print(response.text) try: json_data = response.json() except Exception as e: diff --git a/Data/RealTraffic/RT_API.sjson/725717638.083122 b/Data/RealTraffic/RT_API.sjson/725717638.083122 new file mode 100644 index 0000000..13e5477 Binary files /dev/null and b/Data/RealTraffic/RT_API.sjson/725717638.083122 differ diff --git a/Data/RealTraffic/RT_API.sjson/data b/Data/RealTraffic/RT_API.sjson/data index 66da90a..e0e0b0f 100644 Binary files a/Data/RealTraffic/RT_API.sjson/data and b/Data/RealTraffic/RT_API.sjson/data differ diff --git a/Data/RealTraffic/RT_API.sjson/metaData b/Data/RealTraffic/RT_API.sjson/metaData index c28b275..7969afc 100644 Binary files a/Data/RealTraffic/RT_API.sjson/metaData and b/Data/RealTraffic/RT_API.sjson/metaData differ diff --git a/Include/CoordCalc.h b/Include/CoordCalc.h index f0a94ff..f792396 100644 --- a/Include/CoordCalc.h +++ b/Include/CoordCalc.h @@ -445,6 +445,7 @@ struct positionTy { // short-cuts to coord functions inline double angle (const positionTy& pos2 ) const { return CoordAngle ( *this, pos2); } inline double dist (const positionTy& pos2 ) const { return CoordDistance ( *this, pos2); } + double distRoughSqr (const positionTy& pos2) const { return DistLatLonSqr(lat(), lon(), pos2.lat(), pos2.lon()); } inline vectorTy between (const positionTy& pos2 ) const { return CoordVectorBetween ( *this, pos2); } inline positionTy destPos (const vectorTy& vec ) const { return CoordPlusVector ( *this, vec); } inline positionTy operator+ (const vectorTy& vec ) const { return destPos (vec); } diff --git a/Include/LTChannel.h b/Include/LTChannel.h index 2f9cf63..99468ab 100644 --- a/Include/LTChannel.h +++ b/Include/LTChannel.h @@ -400,12 +400,12 @@ inline long jog_sl (const JSON_Object *object, const char *name) // access to JSON number field (just a shorter name, returns 0 if not a number) inline double jog_n (const JSON_Object *object, const char *name) { - return json_object_get_number (object, name); + return json_object_dotget_number (object, name); } inline long jog_l (const JSON_Object *object, const char *name) { - return std::lround(json_object_get_number (object, name)); + return std::lround(json_object_dotget_number (object, name)); } // access to JSON number with 'null' returned as 'NAN' @@ -415,9 +415,9 @@ double jog_sn_nan (const JSON_Object *object, const char *name); // access to JSON boolean field (replaces -1 with false) inline bool jog_b (const JSON_Object *object, const char *name) { - // json_object_get_boolean returns -1 if field doesn't exit, so we + // json_object_dotget_boolean returns -1 if field doesn't exit, so we // 'convert' -1 and 0 both to false with the following comparison: - return json_object_get_boolean (object, name) > 0; + return json_object_dotget_boolean (object, name) > 0; } // interprets a string-encapsulated number "0" as false, all else as true @@ -444,7 +444,7 @@ double jag_n_nan (const JSON_Array *array, size_t idx); // access to JSON array boolean field (replaces -1 with false) inline bool jag_b (const JSON_Array *array, size_t idx) { - // json_object_get_boolean returns -1 if field doesn't exit, so we + // json_array_get_boolean returns -1 if field doesn't exit, so we // 'convert' -1 and 0 both to false with the following comparison: return json_array_get_boolean (array, idx) > 0; } diff --git a/Include/LTRealTraffic.h b/Include/LTRealTraffic.h index ff18e4c..f23688d 100644 --- a/Include/LTRealTraffic.h +++ b/Include/LTRealTraffic.h @@ -41,9 +41,12 @@ #define RT_AUTH_URL "https://rtw.flyrealtraffic.com/v3/auth" #define RT_AUTH_POST "license=%s&software=%s" +#define RT_WEATHER_URL "https://rtw.flyrealtraffic.com/v3/weather" +#define RT_WEATHER_POST "GUID=%s&lat=%.2f&lon=%.2f&alt=%ld&airports=UNKN&querytype=locwx&toffset=%ld" #define RT_TRAFFIC_URL "https://rtw.flyrealtraffic.com/v3/traffic" #define RT_TRAFFIC_POST "GUID=%s&top=%.2f&bottom=%.2f&left=%.2f&right=%.2f&querytype=locationtraffic&toffset=%ld" + #define RT_LOCALHOST "0.0.0.0" constexpr size_t RT_NET_BUF_SIZE = 8192; @@ -69,6 +72,13 @@ constexpr double RT_VSI_AIRBORNE = 80.0; ///< if VSI is more than this then w #define RT_TRAFFIC_XATTPSX "XATTPSX" #define RT_TRAFFIC_XGPSPSX "XGPSPSX" +// Constant for direct connection +constexpr long RT_DRCT_DEFAULT_WAIT = 8000L; ///< [ms] Default wait time between traffic requests +constexpr std::chrono::seconds RT_DRCT_ERR_WAIT = std::chrono::seconds(5); ///< standard wait between errors +constexpr std::chrono::minutes RT_DRCT_WX_WAIT = std::chrono::minutes(10); ///< How often to update weather? +constexpr long RT_DRCT_WX_DIST = 10L * M_per_NM; ///< Distance for which weather is considered valid, greater than that and we re-request +constexpr int RT_DRCT_MAX_WX_ERR = 10; ///< Max number of consecutive errors during weather requests we wait for...before not asking for weather any longer + /// Fields in a response of a direct connection's request enum RT_DIRECT_FIELDS_TY { RT_DRCT_HexId = 0, ///< hexid (7c68a1) @@ -228,17 +238,39 @@ class RealTrafficConnection : public LTFlightDataChannel // RealTraffic connection status volatile rtStatusTy status = RT_STATUS_NONE; - /// UID returned by RealTraffic upon authentication, valid for 10s only - std::string sGUID; /// RealTraffic License type enum RTLicTypeTy : int { RT_LIC_UNKNOWN = 0, RT_LIC_STANDARD = 1, ///< Standard RealTraffic license RT_LIC_PROFESSIONAL = 2, ///< Professional RT license, allowing for historical data } eLicType = RT_LIC_UNKNOWN; + /// QNH to use for altitude correction (will be historic QNH in case of historic data) + /// Data for the current request + struct CurrTy { + /// Which kind of call do we need next? + enum RTRequestTypeTy : int { + RT_REQU_AUTH = 1, ///< Perform Authentication request + RT_REQU_WEATHER, ///< Perform Weather request + RT_REQU_TRAFFIC, ///< Perform Traffic request + } eRequType = RT_REQU_AUTH; ///< Which type of request is being performed now? + std::string sGUID; ///< UID returned by RealTraffic upon authentication, valid for 10s only + positionTy pos; ///< viewer position for which we receive Realtraffic data + long tOff = 0; ///< time offset for which we request data + } curr; ///< Data for the current request /// How long to wait before making the next request? std::chrono::milliseconds rrlWait = std::chrono::milliseconds(0); + /// Weather data + struct WxTy { + double QNH = NAN; ///< baro pressure + std::chrono::steady_clock::time_point time; ///< time when RealTraffic weather was received + positionTy pos; ///< viewer position for which we received Realtraffic weather + long tOff = 0; ///< time offset for which we requested weather + int nErr = 0; ///< How many errors did we have during weather requests? + + WxTy& operator = (const CurrTy& o); ///< fill from `current` data + } rtWx; ///< Data with which latest weather was requested + // TCP connection to send current position std::thread thrTcpServer; ///< thread of the TCP listening thread (short-lived) TCPConnection tcpPosSender; ///< TCP connection to communicate with RealTraffic @@ -282,6 +314,7 @@ class RealTrafficConnection : public LTFlightDataChannel // MARK: Direct Connection by Request/Reply protected: void MainDirect (); ///< thread main function for the direct connection + void SetRequType (const positionTy& pos); ///< Which request do we need now? public: std::string GetURL (const positionTy&) override; ///< in direct mode return URL and set void ComputeBody (const positionTy& pos) override; ///< in direct mode puts together the POST request with the position data etc. diff --git a/LiveTraffic.xcodeproj/xcuserdata/birger.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/LiveTraffic.xcodeproj/xcuserdata/birger.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index 27c8aae..5c01291 100644 --- a/LiveTraffic.xcodeproj/xcuserdata/birger.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/LiveTraffic.xcodeproj/xcuserdata/birger.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -7,49 +7,33 @@ + + + + - - - - - - diff --git a/Src/LTChannel.cpp b/Src/LTChannel.cpp index 249e9ef..d5f404b 100644 --- a/Src/LTChannel.cpp +++ b/Src/LTChannel.cpp @@ -52,7 +52,7 @@ listPtrLTChannelTy listFDC; // tests for 'null', return ptr to value if wanted bool jog_is_null (const JSON_Object *object, const char *name, JSON_Value** ppValue) { - JSON_Value* pJSONVal = json_object_get_value(object, name); + JSON_Value* pJSONVal = json_object_dotget_value(object, name); if (ppValue) *ppValue = pJSONVal; return !pJSONVal || json_type(pJSONVal) == JSONNull; @@ -71,7 +71,7 @@ bool jag_is_null (const JSON_Array *array, // access to JSON string fields, with NULL or text "null" replaced by "" const char* jog_s (const JSON_Object *object, const char *name) { - const char* s = json_object_get_string ( object, name ); + const char* s = json_object_dotget_string ( object, name ); return !s ? "" : // found nothing std::strcmp(s, "null") ? s : ""; // test if text is "null" @@ -80,7 +80,7 @@ const char* jog_s (const JSON_Object *object, const char *name) // access to JSON number fields, encapsulated as string, with NULL replaced by 0 double jog_sn (const JSON_Object *object, const char *name) { - const char* s = json_object_get_string ( object, name ); + const char* s = json_object_dotget_string ( object, name ); return s ? strtod(s,NULL) : 0.0; } @@ -96,7 +96,7 @@ double jog_n_nan (const JSON_Object *object, const char *name) double jog_sn_nan (const JSON_Object *object, const char *name) { - const char* s = json_object_get_string ( object, name ); + const char* s = json_object_dotget_string ( object, name ); return s ? strtod(s,NULL) : NAN; } @@ -669,6 +669,12 @@ void LTOnlineChannel::DebugLogRaw(const char *data, long httpCode, bool bHeader) const double now = GetSysTime(); outRaw.precision(2); if (bHeader) { + + // Empty line before a (new) SENDING request + if (httpCode == HTTP_FLAG_SENDING) + outRaw << "\n"; + + // Actual header outRaw << std::fixed << now << ' ' << ts2string(now,2) << " - SimTime " diff --git a/Src/LTRealTraffic.cpp b/Src/LTRealTraffic.cpp index b87467e..025280c 100644 --- a/Src/LTRealTraffic.cpp +++ b/Src/LTRealTraffic.cpp @@ -34,6 +34,15 @@ // MARK: RealTraffic Connection // +// fill from `current` data +RealTrafficConnection::WxTy& RealTrafficConnection::WxTy::operator=(const CurrTy& o) +{ + pos = o.pos; + tOff = o.tOff; + time = std::chrono::steady_clock::now(); + return *this; +} + // Constructor doesn't do much RealTrafficConnection::RealTrafficConnection () : LTFlightDataChannel(DR_CHANNEL_REAL_TRAFFIC_ONLINE, REALTRAFFIC_NAME) @@ -162,17 +171,22 @@ void RealTrafficConnection::MainDirect () eConnType = RT_CONN_REQU_REPL; // Clear the list of historic time stamp differences dequeTS.clear(); + // Some more data resets to make sure we start over with the series of requests + curr.sGUID.clear(); + rtWx.QNH = NAN; + rtWx.nErr = 0; while ( shallRun() ) { // LiveTraffic Top Level Exception Handling try { // where are we right now? const positionTy pos (dataRefs.GetViewPos()); - rrlWait = std::chrono::seconds(5); // Standard is: retry in 5s + rrlWait = RT_DRCT_ERR_WAIT; // Standard is: retry in 5s // If the camera position is valid we can request data around it if (pos.isNormal()) { - // fetch data and process it + // determine the type of request, fetch data and process it + SetRequType(pos); if (FetchAllData(pos) && ProcessFetchedData()) // reduce error count if processed successfully // as a chance to appear OK in the long run @@ -202,6 +216,40 @@ void RealTrafficConnection::MainDirect () } } +// Which request do we need now? +void RealTrafficConnection::SetRequType (const positionTy& _pos) +{ + // Position as passed in + curr.pos = _pos; + + // Time offset: in minutes compared to now + curr.tOff = 0L; + if (dataRefs.GetRTSTC() != STC_NO_CTRL && // Configured to send any offset + !dataRefs.IsUsingSystemTime()) // and not actually using system time + { + // Simulated 'now' in seconds since the epoch + const time_t simNow = time_t(dataRefs.GetXPSimTime_ms() / 1000LL); + const time_t now = time(nullptr); + if (simNow < now) { + // offset between older 'simNow' and current 'now' in minutes + curr.tOff = (now - simNow) / 60L; + } + } + + if (curr.sGUID.empty()) // have no GUID? Need authentication + curr.eRequType = CurrTy::RT_REQU_AUTH; + else if ((rtWx.nErr < RT_DRCT_MAX_WX_ERR) && // not seen too many errors yet + (std::isnan(rtWx.QNH) || // no Weather, or wrong time offset, or outdated, or moved too far away? + std::labs(curr.tOff - rtWx.tOff) > 120 || + std::chrono::steady_clock::now() - rtWx.time > RT_DRCT_WX_WAIT || + rtWx.pos.distRoughSqr(curr.pos) > (RT_DRCT_WX_DIST*RT_DRCT_WX_DIST))) + curr.eRequType = CurrTy::RT_REQU_WEATHER; + else + // in all other cases we ask for traffic data + curr.eRequType = CurrTy::RT_REQU_TRAFFIC; +} + + // in direct mode return URL and set std::string RealTrafficConnection::GetURL (const positionTy&) { @@ -213,45 +261,48 @@ std::string RealTrafficConnection::GetURL (const positionTy&) ret, curl_errtxt); } - // Need to login or can we request actual data? - if (sGUID.empty()) - return RT_AUTH_URL; - else - return RT_TRAFFIC_URL; + // What kind of request do we need next? + switch (curr.eRequType) { + case CurrTy::RT_REQU_AUTH: + return RT_AUTH_URL; + case CurrTy::RT_REQU_WEATHER: + return RT_WEATHER_URL; + case CurrTy::RT_REQU_TRAFFIC: + return RT_TRAFFIC_URL; + } + return RT_TRAFFIC_URL; } // in direct mode puts together the POST request with the position data etc. -void RealTrafficConnection::ComputeBody (const positionTy& pos) +void RealTrafficConnection::ComputeBody (const positionTy&) { - char s[256]; + char s[256] = ""; - // Need to login or can we request actual data? - if (sGUID.empty()) { - snprintf(s,sizeof(s), RT_AUTH_POST, - dataRefs.GetRTLicense().c_str(), - HTTP_USER_AGENT); - } else { - // we add 10% to the bounding box to have some data ready once the plane is close enough for display - const boundingBoxTy box (pos, double(dataRefs.GetFdStdDistance_m()) * 1.10); - // Time offset: in minutes compared to now - long tOff = 0L; - if (dataRefs.GetRTSTC() != STC_NO_CTRL && // Configured to send any offset - !dataRefs.IsUsingSystemTime()) // and not actually using system time + // What kind of request will we need? + switch (curr.eRequType) { + case CurrTy::RT_REQU_AUTH: + snprintf(s,sizeof(s), RT_AUTH_POST, + dataRefs.GetRTLicense().c_str(), + HTTP_USER_AGENT); + break; + case CurrTy::RT_REQU_WEATHER: + snprintf(s, sizeof(s), RT_WEATHER_POST, + curr.sGUID.c_str(), + curr.pos.lat(), curr.pos.lon(), 0L, + curr.tOff); + break; + case CurrTy::RT_REQU_TRAFFIC: { - // Simulated 'now' in seconds since the epoch - const time_t simNow = time_t(dataRefs.GetXPSimTime_ms() / 1000LL); - const time_t now = time(nullptr); - if (simNow < now) { - // offset between older 'simNow' and current 'now' in minutes - tOff = (now - simNow) / 60L; - } + // we add 10% to the bounding box to have some data ready once the plane is close enough for display + const boundingBoxTy box (curr.pos, double(dataRefs.GetFdStdDistance_m()) * 1.10); + + snprintf(s,sizeof(s), RT_TRAFFIC_POST, + curr.sGUID.c_str(), + box.nw.lat(), box.se.lat(), + box.nw.lon(), box.se.lon(), + curr.tOff); + break; } - - snprintf(s,sizeof(s), RT_TRAFFIC_POST, - sGUID.c_str(), - box.nw.lat(), box.se.lat(), - box.nw.lon(), box.se.lon(), - tOff); } requBody = s; @@ -281,14 +332,22 @@ bool RealTrafficConnection::ProcessFetchedData () std::string rMsg = jog_s(pObj, "message"); // --- Error processing --- - rrlWait = std::chrono::seconds(5); // Standard is: retry in 5s + rrlWait = RT_DRCT_ERR_WAIT; // Standard is: retry in 5s + + // For failed weather requests keep a separate counter + if (curr.eRequType == CurrTy::RT_REQU_WEATHER && rStatus != HTTP_OK) { + if (++rtWx.nErr >= RT_DRCT_MAX_WX_ERR) { // Too many WX errors? + SHOW_MSG(logERR, "Too many errors trying to fetch RealTraffic weather, will continue without; planes may appear at slightly wrong altitude."); + } + } + switch (rStatus) { case HTTP_OK: break; // All good, just continue case HTTP_PAYMENT_REQU: case HTTP_NOT_FOUND: - if (sGUID.empty()) { + if (curr.eRequType == CurrTy::RT_REQU_AUTH) { SHOW_MSG(logERR, "RealTraffic license invalid: %s", rMsg.c_str()); SetValid(false,true); // set invalid, stop trying return false; @@ -298,16 +357,16 @@ bool RealTrafficConnection::ProcessFetchedData () return false; } - case HTTP_METH_NOT_ALLWD: // Send for "too many sessions" + case HTTP_METH_NOT_ALLWD: // Send for "too many sessions" / "request rate violation" LOG_MSG(logERR, "RealTraffic: %s", rMsg.c_str()); IncErrCnt(); rrlWait = std::chrono::seconds(10); // documentation says "wait 10 seconds" - sGUID.clear(); // force re-login + curr.sGUID.clear(); // force re-login return false; case HTTP_UNAUTHORIZED: // means our GUID expired LOG_MSG(logDEBUG, "Session expired"); - sGUID.clear(); // re-login immediately + curr.sGUID.clear(); // re-login immediately rrlWait = std::chrono::milliseconds(0); return false; @@ -324,22 +383,53 @@ bool RealTrafficConnection::ProcessFetchedData () } // All good, process the request + + // Wait till next request? long l = jog_l(pObj, "rrl"); // Wait time till next request - if ((!sGUID.empty() && l < 8000L) || !l) - l = 8000L; // By default we wait 8s, or more if RealTraffic instructs us so + switch (curr.eRequType) { + case CurrTy::RT_REQU_AUTH: // after an AUTH request we take the rrl unchanged, ie. as quickly as possible + break; + case CurrTy::RT_REQU_WEATHER: // unfortunately, no `rrl` in weather requests... + l = 300; // we just continue 300ms later + break; + case CurrTy::RT_REQU_TRAFFIC: // By default we wait at least 8s, or more if RealTraffic instructs us so + if (l < RT_DRCT_DEFAULT_WAIT) + l = RT_DRCT_DEFAULT_WAIT; + break; + } rrlWait = std::chrono::milliseconds(l); // --- Authorization --- - if (sGUID.empty()) { + if (curr.eRequType == CurrTy::RT_REQU_AUTH) { eLicType = RTLicTypeTy(jog_l(pObj, "type")); - sGUID = jog_s(pObj, "GUID"); - if (sGUID.empty()) { - LOG_MSG(logERR, "Did not actually receive a GUID: %s", netData); + curr.sGUID = jog_s(pObj, "GUID"); + if (curr.sGUID.empty()) { + LOG_MSG(logERR, "Did not actually receive a GUID:\n%s", netData); IncErrCnt(); return false; } LOG_MSG(logDEBUG, "Authenticated: type=%d, GUID=%s", - eLicType, sGUID.c_str()); + eLicType, curr.sGUID.c_str()); + return true; + } + + // --- Weather --- + if (curr.eRequType == CurrTy::RT_REQU_WEATHER) { + // We are interested in just a single value: local Pressure + const double wxSLP = jog_n_nan(pObj, "data.locWX.SLP"); + if (std::isnan(wxSLP) || wxSLP < 800.0) { + LOG_MSG(logERR, "RealTraffic returned no or invalid local pressure %.1f:\n%s", + wxSLP, netData); + rrlWait = std::chrono::seconds(2); // isn't exactly clear how quickly we can repeat weather requests! + if (++rtWx.nErr >= RT_DRCT_MAX_WX_ERR) { // Too many WX errors? + SHOW_MSG(logERR, "Too many errors trying to fetch RealTraffic weather, will continue without; planes may appear at slightly wrong altitude."); + } + return false; + } + LOG_MSG(logDEBUG, "Received RealTraffic locWX.SLP = %.1f", wxSLP); + rtWx.QNH = wxSLP; // Save new QNH + rtWx = curr; // and how it was requested + rtWx.nErr = 0; // reset the error counter as we now received good data return true; } @@ -371,6 +461,19 @@ bool RealTrafficConnection::ProcessFetchedData () } } } + + // If RealTraffic returns `full_count = 0` then something's wrong... + // like data requested too far in the past + l = jog_l(pObj, "full_count"); + if (l == 0) { + static std::chrono::steady_clock::time_point lastWarn; + const auto now = std::chrono::steady_clock::now(); + if (now - lastWarn > std::chrono::minutes(5)) { + SHOW_MSG(logWARN, "RealTraffic has no traffic at all! %s", + curr.tOff > 0 ? "Maybe requested historic data too far in the past?" : "(full_count=0)"); + lastWarn = now; + } + } // any a/c filter defined for debugging purposes? const std::string acFilter ( dataRefs.GetDebugAcFilter() ); @@ -437,9 +540,12 @@ bool RealTrafficConnection::ProcessFetchedData () pos.f.onGrnd = GND_ON; else { pos.f.onGrnd = GND_OFF; - double d = jag_n(pJAc, RT_DRCT_BaroAlt); // prefer (already corrected) baro altitude - if (d > 0.0) + double d = jag_n(pJAc, RT_DRCT_BaroAlt); // prefer baro altitude + if (d > 0.0) { + if (!std::isnan(rtWx.QNH)) + d = BaroAltToGeoAlt_ft(d, rtWx.QNH); pos.SetAltFt(d); + } else // else try geo altitude pos.SetAltFt(jag_n(pJAc, RT_DRCT_GeoAlt)); } diff --git a/Src/SettingsUI.cpp b/Src/SettingsUI.cpp index e8cf2d2..ee22a2b 100644 --- a/Src/SettingsUI.cpp +++ b/Src/SettingsUI.cpp @@ -562,22 +562,6 @@ void LTSettingsUI::buildInterface() ImGui::TableNextCell(); } - // RealTraffic traffic port number - if (ImGui::FilteredLabel("Traffic Port", sFilter)) { - ImGui::SetNextItemWidth(fSmallWidth); - ImGui::InputText("", &sRTPort, ImGuiInputTextFlags_CharsDecimal); - // if changed then set (then re-read) the value - if (ImGui::IsItemDeactivatedAfterEdit()) { - dataRefs.SetRTTrafficPort(std::stoi(sRTPort)); - sRTPort = std::to_string(DataRefs::GetCfgInt(DR_CFG_RT_TRAFFIC_PORT)); - } - else if (ImGui::IsItemActive()) { - ImGui::SameLine(); - ImGui::TextUnformatted("[Enter] to save. Default is 49005. Effective after restart."); - } - ImGui::TableNextCell(); - } - if (ImGui::FilteredLabel("Simulator Time Control", sFilter)) { const float cbWidth = ImGui::CalcTextSize("Send Sim Time plus Buffering Period (default)_____").x; ImGui::SetNextItemWidth(cbWidth);