/* * Author: * Sven Czarnian * Brief: * Implements the EuroScope plug-in definition * Copyright: * 2021 Sven Czarnian * License: * GNU General Public License v3 (GPLv3) */ #include "stdafx.h" #include #include #include #include #include #include #pragma warning(push, 0) #include #pragma warning(pop) #include #include #include #include #include #include #include #include "com/ZmqContext.h" #include "PlugIn.h" EXTERN_C IMAGE_DOS_HEADER __ImageBase; using namespace aman; static std::string __receivedAmanData; static std::size_t receiveCurl(void* ptr, std::size_t size, std::size_t nmemb, void* stream) { std::ignore = stream; std::string serverResult = static_cast(ptr); __receivedAmanData += serverResult; return size * nmemb; } PlugIn::PlugIn() : EuroScopePlugIn::CPlugIn(EuroScopePlugIn::COMPATIBILITY_CODE, PLUGIN_NAME, PLUGIN_VERSION, PLUGIN_DEVELOPER, PLUGIN_COPYRIGHT), m_configuration(), m_screen(), m_updateQueueLock(), m_updateQueue(), m_inboundsQueueLock(), m_inbounds(), m_forcedToBackendCallsigns(), m_compatible(false), m_connectedToNetwork(false), m_sweatboxValid(false), m_playbackValid(false) { GOOGLE_PROTOBUF_VERIFY_VERSION; this->RegisterTagItemType("ETA", static_cast(PlugIn::TagItemElement::EstimatedTimeOfArrival)); this->RegisterTagItemType("PTA", static_cast(PlugIn::TagItemElement::PlannedTimeOfArrival)); this->RegisterTagItemType("Delta time", static_cast(PlugIn::TagItemElement::DeltaTime)); this->RegisterTagItemType("Fixed plan indicator", static_cast(PlugIn::TagItemElement::FixedPlanIndicator)); this->RegisterTagItemFunction("Force planning", static_cast(PlugIn::TagItemFunction::ForceToBackendMenu)); this->DisplayUserMessage(PLUGIN_NAME, "INFO", (std::string("Loaded ") + PLUGIN_NAME + " " + PLUGIN_VERSION).c_str(), true, true, false, false, false); /* get the dll-path */ char path[MAX_PATH + 1] = { 0 }; const gsl::span span(path); GetModuleFileNameA((HINSTANCE)&__ImageBase, span.data(), span.size()); PathRemoveFileSpecA(span.data()); std::string dllPath = span.data(); CommunicationFileFormat comFormat; if (false == comFormat.parse(dllPath + "\\AmanCommunication.txt", this->m_configuration) || true == comFormat.errorFound()) { this->DisplayUserMessage(PLUGIN_NAME, "ERROR", "Unable to parse AmanCommunication.txt", true, true, true, true, true); this->DisplayUserMessage(PLUGIN_NAME, "ERROR", ("AmanCommunication.txt:" + std::to_string(comFormat.errorLine()) + " - " + comFormat.errorMessage()).c_str(), true, true, true, true, true); return; } IdentifierFileFormat identFormat; if (false == identFormat.parse(dllPath + "\\AmanIdentity.txt", this->m_configuration) || true == identFormat.errorFound()) { this->DisplayUserMessage(PLUGIN_NAME, "ERROR", "Unable to parse AmanIdentity.txt", true, true, true, true, true); this->DisplayUserMessage(PLUGIN_NAME, "ERROR", ("AmanIdentity.txt:" + std::to_string(identFormat.errorLine()) + " - " + identFormat.errorMessage()).c_str(), true, true, true, true, true); return; } ZmqContext::instance().initialize(); if (false == Backend::instance().initialize(this->m_configuration)) this->DisplayUserMessage(PLUGIN_NAME, "ERROR", "Unable to initialize the reporter-connection to the backend", true, true, true, true, true); this->validateBackendData(); } PlugIn::~PlugIn() { Backend::instance().deinitialize(); ZmqContext::instance().deinitialize(); google::protobuf::ShutdownProtobufLibrary(); } void PlugIn::validateBackendData() { if (false == this->m_configuration.valid) { this->m_compatible = false; return; } /* set up the URL */ std::string url; if (true == this->m_configuration.httpsProtocol) url += "https://"; else url += "http://"; url += this->m_configuration.address + ":" + std::to_string(this->m_configuration.portRestAPI) + "/aman/airports"; CURL* curl = curl_easy_init(); CURLcode result; curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, static_cast(CURL_HTTP_VERSION_1_1)); curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0L); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, receiveCurl); curl_easy_setopt(curl, CURLOPT_TIMEOUT, 2L); struct curl_slist* headers = nullptr; headers = curl_slist_append(headers, "Accept: */*"); headers = curl_slist_append(headers, "Content-Type: application/json"); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); __receivedAmanData.clear(); result = curl_easy_perform(curl); this->m_compatible = false; if (CURLE_OK != result) { MessageBoxA(nullptr, "Unable to receive backend information", "AMAN-Error", MB_OK); return; } /* validate the json data */ Json::Value root; Json::CharReaderBuilder builder; std::unique_ptr reader(builder.newCharReader()); if (false == reader->parse(__receivedAmanData.c_str(), __receivedAmanData.c_str() + __receivedAmanData.length(), &root, nullptr)) { MessageBoxA(nullptr, "Received invalid backend data", "AMAN-Error", MB_OK); return; } std::vector airports; std::string version; for (auto it = root.begin(); root.end() != it; ++it) { if ("version" == it.key().asString()) { version = it->asString(); } else if ("airports" == it.key().asString() && true == it->isArray()) { auto airportArray = *it; airports.reserve(airportArray.size()); for (Json::Value::ArrayIndex i = 0; i < airportArray.size(); ++i) airports.push_back(airportArray[i].asString()); } } /* invalid version or airports info */ auto entries = String::splitString(version, "."); if (3 != entries.size() || 0 == airports.size()) { MessageBoxA(nullptr, "Unable to receive backend information", "AMAN-Error", MB_OK); return; } /* incompatible communication version */ if (PLUGIN_MAJOR_VERSION != std::atoi(gsl::at(entries, 0).c_str()) || PLUGIN_MINOR_VERSION != std::atoi(gsl::at(entries, 1).c_str())) { MessageBoxA(nullptr, "Plugin version is outdated. An update is required", "AMAN-Error", MB_OK); return; } std::lock_guard guard(this->m_updateQueueLock); this->m_updateQueue.clear(); for (const auto& airport : std::as_const(airports)) this->m_updateQueue.insert({ airport, {} }); this->m_compatible = true; this->DisplayUserMessage(PLUGIN_NAME, "INFO", "Reloaded AMAN-configuration", true, true, false, false, false); } EuroScopePlugIn::CRadarScreen* PlugIn::OnRadarScreenCreated(const char* displayName, bool needsRadarContent, bool geoReferenced, bool canBeSaved, bool canBeCreated) { std::ignore = needsRadarContent; std::ignore = geoReferenced; std::ignore = canBeSaved; std::ignore = canBeCreated; std::ignore = displayName; if (nullptr == this->m_screen) this->m_screen = std::make_shared(); return this->m_screen.get(); } aman::Aircraft* PlugIn::generateAircraftMessage(const EuroScopePlugIn::CRadarTarget& target) const { if (false == target.IsValid() || false == target.GetCorrelatedFlightPlan().IsValid()) return nullptr; const auto flightPlan = target.GetCorrelatedFlightPlan(); std::string callsign(target.GetCallsign()); aman::Aircraft* retval = new aman::Aircraft(); /* fill all available information */ retval->set_callsign(callsign); if (3 <= callsign.length()) retval->set_airline(callsign.substr(0, 3)); retval->set_type(flightPlan.GetFlightPlanData().GetAircraftFPType()); retval->set_wtc(std::to_string(flightPlan.GetFlightPlanData().GetAircraftWtc())); /* TODO get recat */ retval->set_enginecount(flightPlan.GetFlightPlanData().GetEngineNumber()); switch (flightPlan.GetFlightPlanData().GetEngineType()) { case 'P': case 'T': retval->set_enginetype(aman::Aircraft_EngineType::Aircraft_EngineType_TURBOPROB); break; case 'E': retval->set_enginetype(aman::Aircraft_EngineType::Aircraft_EngineType_ELECTRIC); break; case 'J': retval->set_enginetype(aman::Aircraft_EngineType::Aircraft_EngineType_JET); break; default: retval->set_enginetype(aman::Aircraft_EngineType::Aircraft_EngineType_UNKNOWN); break; } return retval; } void PlugIn::distanceToPredictedIaf(const EuroScopePlugIn::CRadarTarget& radarTarget, const EuroScopePlugIn::CFlightPlan& flightPlan, const EuroScopePlugIn::CPosition& iafPosition, aman::AircraftReport* report) { std::string_view direct(flightPlan.GetControllerAssignedData().GetDirectToPointName()); if (0 != direct.length()) { GeoCoordinate directCoordinate; /* find the coordinate of the direct waypoint */ for (int i = 0; i < flightPlan.GetExtractedRoute().GetPointsNumber(); ++i) { if (flightPlan.GetExtractedRoute().GetPointName(i) == direct) { directCoordinate = GeoCoordinate(static_cast(flightPlan.GetExtractedRoute().GetPointPosition(i).m_Longitude) * degree, static_cast(flightPlan.GetExtractedRoute().GetPointPosition(i).m_Latitude) * degree); break; } } if (0_m != directCoordinate.distanceTo(GeoCoordinate())) { const GeographicLib::Gnomonic projection(GeographicLib::Geodesic::WGS84()); Eigen::Vector2f currentCartesian, iafCartesian, directCartesian; /* convert to Cartesian with IAF as the reference */ projection.Forward(static_cast(iafPosition.m_Latitude), static_cast(iafPosition.m_Longitude), static_cast(radarTarget.GetPosition().GetPosition().m_Latitude), static_cast(radarTarget.GetPosition().GetPosition().m_Longitude), currentCartesian[0], currentCartesian[1]); projection.Forward(static_cast(iafPosition.m_Latitude), static_cast(iafPosition.m_Longitude), directCoordinate.latitude().convert(degree), directCoordinate.longitude().convert(degree), directCartesian[0], directCartesian[1]); projection.Forward(static_cast(iafPosition.m_Latitude), static_cast(iafPosition.m_Longitude), static_cast(iafPosition.m_Latitude), static_cast(iafPosition.m_Longitude), iafCartesian[0], iafCartesian[1]); /* project IAF on line between current position and direct */ const auto direction = (directCartesian - currentCartesian).normalized(); Eigen::ParametrizedLine line(currentCartesian, direction); const auto projectCartesian = line.projection(iafCartesian); const auto distanceStart = (currentCartesian - projectCartesian).squaredNorm(); const auto distanceEnd = (directCartesian - projectCartesian).squaredNorm(); const auto distanceDirect = (currentCartesian - directCartesian).squaredNorm(); /* projection of IAF in front of aircraft and not behind direct */ if (distanceStart <= distanceDirect && distanceEnd <= distanceDirect) { EuroScopePlugIn::CPosition projected; float lat = 0.0f, lon = 0.0f; projection.Reverse(static_cast(iafPosition.m_Latitude), static_cast(iafPosition.m_Longitude), projectCartesian[0], projectCartesian[1], lat, lon); projected.m_Latitude = lat; projected.m_Longitude = lon; const auto distanceToProjected = radarTarget.GetPosition().GetPosition().DistanceTo(projected); report->set_distancetoiaf(static_cast(std::round(distanceToProjected))); } else { report->set_distancetoiaf(0); } } /* did not find the coordinate -> fallback */ else { report->set_distancetoiaf(0); } } /* in heading mode and passed IAF */ else { report->set_distancetoiaf(0); } } void PlugIn::generateAircraftReportMessage(EuroScopePlugIn::CRadarTarget& radarTarget, aman::AircraftReport* report) { const auto flightPlan = radarTarget.GetCorrelatedFlightPlan(); /* ignore invalid flightplans */ if (false == flightPlan.IsValid()) return; /* ignore flights that are not tracked by the current controller */ if (false == flightPlan.GetTrackingControllerIsMe()) return; /* ignore non-IFR flights */ if (nullptr == flightPlan.GetFlightPlanData().GetPlanType() || 'I' != *flightPlan.GetFlightPlanData().GetPlanType()) return; /* filter invalid destinations */ const auto destination = std::string_view(flightPlan.GetFlightPlanData().GetDestination()); if (4 != destination.length()) return; /* filter by airborne identifier (assume a GS>50kn and a big distance to the origin) */ if (50 > radarTarget.GetGS() || 10.0 > flightPlan.GetDistanceFromOrigin()) return; /* generate protobuf message */ aman::Aircraft* aircraft = this->generateAircraftMessage(radarTarget); if (nullptr == aircraft) return; aman::Dynamics* dynamics = new aman::Dynamics(); dynamics->set_altitude(radarTarget.GetPosition().GetFlightLevel()); dynamics->set_heading(radarTarget.GetPosition().GetReportedHeading()); dynamics->set_groundspeed(radarTarget.GetPosition().GetReportedGS()); dynamics->set_verticalspeed(radarTarget.GetVerticalSpeed()); aman::Coordinate* coordinate = new aman::Coordinate(); coordinate->set_latitude(radarTarget.GetPosition().GetPosition().m_Latitude); coordinate->set_longitude(radarTarget.GetPosition().GetPosition().m_Longitude); /* create the report */ switch (this->ControllerMyself().GetFacility()) { case 1: case 6: report->set_reportedby(aman::AircraftReport::CENTER); break; case 2: report->set_reportedby(aman::AircraftReport::DELIVERY); break; case 3: report->set_reportedby(aman::AircraftReport::GROUND); break; case 4: report->set_reportedby(aman::AircraftReport::TOWER); break; case 5: report->set_reportedby(aman::AircraftReport::APPROACH); break; default: report->set_reportedby(aman::AircraftReport::UNKNOWN); break; } auto currentPosition = radarTarget.GetPosition().GetPosition(); EuroScopePlugIn::CPosition iafPosition; std::string iafName; for (auto element = this->SectorFileElementSelectFirst(EuroScopePlugIn::SECTOR_ELEMENT_STAR); true == element.IsValid(); element = this->SectorFileElementSelectNext(element, EuroScopePlugIn::SECTOR_ELEMENT_STAR)) { auto split = String::splitString(element.GetName(), " "); /* find the correct star */ if (0 != split.size() && destination == gsl::at(split, 0)) { /* get the IAF */ EuroScopePlugIn::CPosition position; if (true == element.GetPosition(&position, 0)) { /* match the waypoints to get the name*/ for (int i = 0; i < flightPlan.GetExtractedRoute().GetPointsNumber(); ++i) { if (1.0f >= flightPlan.GetExtractedRoute().GetPointPosition(i).DistanceTo(position)) { iafPosition = flightPlan.GetExtractedRoute().GetPointPosition(i); iafName = flightPlan.GetExtractedRoute().GetPointName(i); report->set_initialapproachfix(iafName); break; } } } } if (0 != iafName.length()) break; } if (0 != iafName.length()) { const auto iafDistance = currentPosition.DistanceTo(iafPosition); /* calculate the distance to the IAF */ double distanceToIaf = 0.0f; for (int i = 1; i < flightPlan.GetPositionPredictions().GetPointsNumber(); ++i) { const double distance = flightPlan.GetPositionPredictions().GetPosition(i).DistanceTo(currentPosition); const double headingDelta = std::abs(radarTarget.GetPosition().GetReportedHeading() - flightPlan.GetPositionPredictions().GetPosition(i).DirectionTo(iafPosition)); /* * 1. no direct way to IAF -> some direct given -> stop after lateral passing * 2. passed the IAF on a way to a direct */ if ((90.0 < headingDelta && 270.0 > headingDelta) || iafDistance < distance) break; distanceToIaf += distance; currentPosition = flightPlan.GetPositionPredictions().GetPosition(i); } if (0.01f >= std::abs(distanceToIaf)) distanceToIaf = iafDistance; report->set_distancetoiaf(static_cast(std::round(distanceToIaf))); } report->set_destination(std::string(destination)); /* support GrPlugin and TST with the stand association */ if (nullptr != radarTarget.GetCorrelatedFlightPlan().GetControllerAssignedData().GetFlightStripAnnotation(6)) { std::string stand(radarTarget.GetCorrelatedFlightPlan().GetControllerAssignedData().GetFlightStripAnnotation(6)); auto split = String::splitString(stand, "/"); if (3 == split.size() && "s" == gsl::at(split, 0) && "s" == gsl::at(split, 2)) report->set_plannedgate(gsl::at(split, 1)); } report->set_allocated_aircraft(aircraft); report->set_allocated_dynamics(dynamics); report->set_allocated_position(coordinate); /* set the report time */ std::stringstream stream; const auto reportTime = std::chrono::utc_clock::now(); stream << std::format("{0:%Y%m%d%H%M%S}", reportTime); const auto elements = String::splitString(stream.str(), "."); report->set_reporttime(gsl::at(elements, 0)); } bool PlugIn::OnCompileCommand(const char* cmdline) { std::string message(cmdline); bool retval = false; #pragma warning(disable: 4244) std::transform(message.begin(), message.end(), message.begin(), ::toupper); #pragma warning(default: 4244) /* no AMAN command */ if (0 != message.find(".AMAN")) return retval; if (std::string::npos != message.find("RELOAD")) { this->validateBackendData(); this->m_sweatboxValid = false; this->m_playbackValid = false; retval = true; } else if (std::string::npos != message.find("SWEATBOX")) { this->m_sweatboxValid = !this->m_sweatboxValid; if (true == this->m_sweatboxValid) this->m_playbackValid = false; retval = true; } else if (std::string::npos != message.find("PLAYBACK")) { this->m_playbackValid = !this->m_playbackValid; if (true == this->m_playbackValid) this->m_sweatboxValid = false; retval = true; } if (true == retval) { if (true == this->m_sweatboxValid) this->DisplayUserMessage(PLUGIN_NAME, "INFO", "Sweatbox data is used in AMAN", true, true, false, false, false); else this->DisplayUserMessage(PLUGIN_NAME, "INFO", "Sweatbox is ignored in AMAN", true, true, false, false, false); if (true == this->m_playbackValid) this->DisplayUserMessage(PLUGIN_NAME, "INFO", "Playback data is used in AMAN", true, true, false, false, false); else this->DisplayUserMessage(PLUGIN_NAME, "INFO", "Playback is ignored in AMAN", true, true, false, false, false); } return retval; } void PlugIn::OnGetTagItem(EuroScopePlugIn::CFlightPlan flightPlan, EuroScopePlugIn::CRadarTarget radarTarget, int itemCode, int tagData, char itemString[16], int* colorCode, COLORREF* rgb, double* fontSize) { std::ignore = tagData; std::ignore = rgb; std::ignore = fontSize; std::string callsign(radarTarget.GetCallsign()); std::string destination(flightPlan.GetFlightPlanData().GetDestination()); #pragma warning(disable: 4244) std::transform(destination.begin(), destination.end(), destination.begin(), ::toupper); #pragma warning(default: 4244) bool planExpected = false, forced = false; const gsl::span tagString(itemString, 16UL); this->m_updateQueueLock.lock(); /* check if the inbound is forced */ if (0 != this->m_forcedToBackendCallsigns.size()) { const auto cIt = std::find(this->m_forcedToBackendCallsigns.cbegin(), this->m_forcedToBackendCallsigns.cend(), callsign); forced = this->m_forcedToBackendCallsigns.cend() != cIt; } /* check if the inbound is expected to be planned */ planExpected = this->m_updateQueue.cend() != this->m_updateQueue.find(destination); this->m_updateQueueLock.unlock(); std::lock_guard guard(this->m_inboundsQueueLock); auto it = this->m_inbounds.find(callsign); std::string message; switch (static_cast(itemCode)) { case TagItemElement::EstimatedTimeOfArrival: { if (this->m_inbounds.cend() != it) message = UtcTime::timeToString(it->second.eta(), "%H:%M"); else if (true == forced || true == planExpected) message = "??:??"; break; } case TagItemElement::PlannedTimeOfArrival: { if (this->m_inbounds.cend() != it) message = UtcTime::timeToString(it->second.pta(), "%H:%M"); else if (true == forced || true == planExpected) message = "??:??"; break; } case TagItemElement::DeltaTime: { if (this->m_inbounds.cend() != it) { const auto ttl = static_cast(std::roundf(it->second.timeToLose().convert(second))); std::stringstream stream; stream << ttl; message = stream.str(); } else if (true == forced || true == planExpected) { message = "??"; } break; } case TagItemElement::FixedPlanIndicator: if (this->m_inbounds.cend() != it && false == it->second.fixedPlan()) message = "*"; else if (this->m_inbounds.cend() == it && (true == forced || true == planExpected)) message = "*"; break; default: break; } if (0 != message.length()) { std::strcpy(itemString, message.c_str()); *colorCode = EuroScopePlugIn::TAG_COLOR_DEFAULT; } } void PlugIn::OnFunctionCall(int functionId, const char* itemString, POINT pt, RECT area) { std::ignore = itemString; std::ignore = pt; const auto radarTarget = this->RadarTargetSelectASEL(); if (false == radarTarget.IsValid() || false == radarTarget.GetCorrelatedFlightPlan().IsValid()) return; std::string callsign(radarTarget.GetCallsign()); switch (static_cast(functionId)) { case PlugIn::TagItemFunction::ForceToBackendMenu: { this->OpenPopupList(area, "AMAN", 1); bool inList = this->m_forcedToBackendCallsigns.cend() != std::find(this->m_forcedToBackendCallsigns.cbegin(), this->m_forcedToBackendCallsigns.cend(), callsign); this->AddPopupListElement("Add to AMAN", "", static_cast(PlugIn::TagItemFunction::ForceToBackendSelect), false, EuroScopePlugIn::POPUP_ELEMENT_NO_CHECKBOX, true == inList); this->AddPopupListElement("Remove from AMAN", "", static_cast(PlugIn::TagItemFunction::ForceToBackendSelect), false, EuroScopePlugIn::POPUP_ELEMENT_NO_CHECKBOX, false == inList); break; } case PlugIn::TagItemFunction::ForceToBackendSelect: { std::string destination(radarTarget.GetCorrelatedFlightPlan().GetFlightPlanData().GetDestination()); std::transform(destination.begin(), destination.end(), destination.begin(), ::toupper); std::lock_guard guard(this->m_updateQueueLock); auto it = this->m_updateQueue.find(destination); if (0 == std::strcmp(itemString, "Add to AMAN")) { if (this->m_updateQueue.end() != it) { auto cIt = std::find(this->m_forcedToBackendCallsigns.cbegin(), this->m_forcedToBackendCallsigns.cend(), callsign); if (this->m_forcedToBackendCallsigns.cend() == cIt) this->m_forcedToBackendCallsigns.push_back(callsign); } } else { if (this->m_updateQueue.end() != it) { auto cIt = std::find(this->m_forcedToBackendCallsigns.begin(), this->m_forcedToBackendCallsigns.end(), callsign); if (this->m_forcedToBackendCallsigns.cend() != cIt) this->m_forcedToBackendCallsigns.erase(cIt); } } break; } default: break; } } void PlugIn::addUpdateQueue(EuroScopePlugIn::CRadarTarget& radarTarget) { std::lock_guard guard(this->m_updateQueueLock); auto forcedIt = std::find(this->m_forcedToBackendCallsigns.cbegin(), this->m_forcedToBackendCallsigns.cend(), radarTarget.GetCallsign()); if (false == radarTarget.GetCorrelatedFlightPlan().GetTrackingControllerIsMe() && this->m_forcedToBackendCallsigns.cend() == forcedIt) return; std::string destination(radarTarget.GetCorrelatedFlightPlan().GetFlightPlanData().GetDestination()); #pragma warning(disable: 4244) std::transform(destination.begin(), destination.end(), destination.begin(), ::toupper); #pragma warning(default: 4244) auto it = this->m_updateQueue.find(destination); if (this->m_updateQueue.end() != it) it->second.push_back(radarTarget.GetCallsign()); } void PlugIn::updateInbound(EuroScopePlugIn::CRadarTarget& radarTarget) { std::lock_guard guard(this->m_inboundsQueueLock); auto it = this->m_inbounds.find(radarTarget.GetCallsign()); if (this->m_inbounds.end() != it) it->second.update(radarTarget); } void PlugIn::updateInbound(EuroScopePlugIn::CFlightPlan& plan) { std::lock_guard guard(this->m_inboundsQueueLock); auto it = this->m_inbounds.find(plan.GetCorrelatedRadarTarget().GetCallsign()); if (this->m_inbounds.end() != it) it->second.update(plan); } void PlugIn::OnRadarTargetPositionUpdate(EuroScopePlugIn::CRadarTarget radarTarget) { /* do nothing if the reporter is not initialized and ignore invalid targets */ if (false == this->m_compatible || false == Backend::instance().initialized() || false == radarTarget.IsValid()) return; /* validate the correct connection */ bool validConnection = EuroScopePlugIn::CONNECTION_TYPE_DIRECT == this->GetConnectionType(); validConnection |= true == this->m_sweatboxValid && EuroScopePlugIn::CONNECTION_TYPE_SWEATBOX == this->GetConnectionType(); validConnection |= true == this->m_playbackValid && EuroScopePlugIn::CONNECTION_TYPE_PLAYBACK == this->GetConnectionType(); if (false == validConnection) return; this->addUpdateQueue(radarTarget); this->updateInbound(radarTarget); } void PlugIn::OnFlightPlanControllerAssignedDataUpdate(EuroScopePlugIn::CFlightPlan flightPlan, int type) { /* handle only relevant changes */ if (EuroScopePlugIn::CTR_DATA_TYPE_HEADING != type && EuroScopePlugIn::CTR_DATA_TYPE_DIRECT_TO != type) return; if (false == flightPlan.GetCorrelatedRadarTarget().IsValid()) return; this->updateInbound(flightPlan); } void PlugIn::OnFlightPlanDisconnect(EuroScopePlugIn::CFlightPlan flightPlan) { std::string callsign(flightPlan.GetCorrelatedRadarTarget().GetCallsign()); this->m_updateQueueLock.lock(); auto it = std::find(this->m_forcedToBackendCallsigns.begin(), this->m_forcedToBackendCallsigns.end(), callsign); if (this->m_forcedToBackendCallsigns.end() != it) this->m_forcedToBackendCallsigns.erase(it); this->m_updateQueueLock.unlock(); this->m_inboundsQueueLock.lock(); auto inIt = this->m_inbounds.find(flightPlan.GetCorrelatedRadarTarget().GetCallsign()); if (this->m_inbounds.end() != inIt) this->m_inbounds.erase(inIt); this->m_inboundsQueueLock.unlock(); } void PlugIn::updateSequence(std::shared_ptr& sequence) { /* cleanup the inbound list */ this->m_inboundsQueueLock.lock(); for (auto it = this->m_inbounds.begin(); this->m_inbounds.end() != it;) { bool found = false; /* check if the inbound is part of the server */ for (auto i = 0; i < sequence->sequence_size(); ++i) { if (sequence->sequence(i).callsign() == it->first) { found = true; break; } } if (false == found) it = this->m_inbounds.erase(it); else ++it; } this->m_inboundsQueueLock.unlock(); /* update the inbound list */ for (auto i = 0; i < sequence->sequence_size(); ++i) { const auto& inbound = sequence->sequence(i); EuroScopePlugIn::CRadarTarget target; bool found = false; for (target = this->RadarTargetSelectFirst(); true == target.IsValid(); target = this->RadarTargetSelectNext(target)) { if (target.GetCallsign() == inbound.callsign()) { found = true; break; } } if (true == found) { this->m_inboundsQueueLock.lock(); auto it = this->m_inbounds.find(inbound.callsign()); if (this->m_inbounds.end() != it) it->second.update(target, inbound, sequence->winddata()); else this->m_inbounds.insert({ inbound.callsign(), Inbound(target, inbound, sequence->winddata()) }); this->m_inboundsQueueLock.unlock(); } } } void PlugIn::OnTimer(int counter) { /* cleanup the internal data */ if (EuroScopePlugIn::CONNECTION_TYPE_NO == this->GetConnectionType()) { if (true == this->m_connectedToNetwork) { std::lock_guard guardUpdate(this->m_updateQueueLock); for (auto& airport : this->m_updateQueue) airport.second.clear(); std::lock_guard guardInbound(this->m_inboundsQueueLock); this->m_inbounds.clear(); this->m_connectedToNetwork = false; } return; } this->m_connectedToNetwork = true; if (false == this->m_compatible || false == Backend::instance().initialized() || 0 != (counter % 10)) return; this->m_updateQueueLock.lock(); for (auto& airport : this->m_updateQueue) { aman::AircraftUpdate update; update.set_airport(airport.first); if (0 != airport.second.size()) { for (auto target = this->RadarTargetSelectFirst(); true == target.IsValid(); target = this->RadarTargetSelectNext(target)) { auto it = std::find(airport.second.begin(), airport.second.end(), target.GetCallsign()); if (airport.second.end() != it) { auto report = update.add_reports(); this->generateAircraftReportMessage(target, report); } } } /* send the report and request the current sequence */ auto sequence = Backend::instance().update(update); if (nullptr == sequence) this->DisplayUserMessage(PLUGIN_NAME, "ERROR", "Unable to send a new aircraft report update", true, true, true, true, true); else this->updateSequence(sequence); airport.second.clear(); } this->m_updateQueueLock.unlock(); }