123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452 |
- /*
- * Author:
- * Sven Czarnian <devel@svcz.de>
- * Brief:
- * Implements the EuroScope plug-in definition
- * Copyright:
- * 2021 Sven Czarnian
- * License:
- * GNU General Public License v3 (GPLv3)
- */
- #include "stdafx.h"
- #include <algorithm>
- #include <cctype>
- #include <gsl/gsl>
- #include <Shlwapi.h>
- #include <Windows.h>
- #include <curl/curl.h>
- #include <json/json.h>
- #include <aman/com/Backend.h>
- #include <aman/config/CommunicationFileFormat.h>
- #include <aman/config/IdentifierFileFormat.h>
- #include <aman/helper/String.h>
- #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) {
- (void)stream;
- std::string serverResult = static_cast<char*>(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_forcedToBackendCallsigns(),
- m_compatible(false) {
- GOOGLE_PROTOBUF_VERIFY_VERSION;
- 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<char, MAX_PATH + 1> 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() noexcept {
- Backend::instance().deinitialize();
- ZmqContext::instance().deinitialize();
- google::protobuf::ShutdownProtobufLibrary();
- }
- void PlugIn::validateBackendData() {
- std::string url = "http://" + this->m_configuration.address + ":5000/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<long>(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<Json::CharReader> 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<std::string> 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(entries[0].c_str()) || PLUGIN_MINOR_VERSION != std::atoi(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;
- }
- 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<RadarScreen>();
- return this->m_screen.get();
- }
- aman::Aircraft* PlugIn::generateAircraftMessage(const EuroScopePlugIn::CRadarTarget& target) {
- if (false == target.IsValid() || false == target.GetCorrelatedFlightPlan().IsValid())
- return nullptr;
- 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::generateAircraftReportMessage(const EuroScopePlugIn::CRadarTarget& radarTarget, aman::AircraftReport* report) {
- 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 */
- auto destination = std::string_view(flightPlan.GetFlightPlanData().GetDestination());
- if (4 != destination.length())
- return;
- /* filter by distance to destination */
- double distanceNM = flightPlan.GetDistanceToDestination();
- if (5.0 > distanceNM || 250.0 < distanceNM)
- 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 == 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()) {
- auto iafDistance = currentPosition.DistanceTo(iafPosition);
- /* calculate the distance to the IAF */
- double distanceToIaf = 0.0f;
- for (int i = 1; i < flightPlan.GetPositionPredictions().GetPointsNumber(); ++i) {
- double distance = flightPlan.GetPositionPredictions().GetPosition(i).DistanceTo(currentPosition);
- 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<int>(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;
- auto reportTime = std::chrono::utc_clock::now();
- stream << std::format("{0:%Y%m%d%H%M%S}", reportTime);
- report->set_reporttime(String::splitString(stream.str(), ".")[0]);
- }
- void PlugIn::OnFunctionCall(int functionId, const char* itemString, POINT pt, RECT area) {
- std::ignore = itemString;
- std::ignore = pt;
- std::ignore = area;
- auto radarTarget = this->RadarTargetSelectASEL();
- if (false == radarTarget.IsValid() || false == radarTarget.GetCorrelatedFlightPlan().IsValid())
- return;
- std::string callsign(radarTarget.GetCallsign());
- switch (static_cast<PlugIn::TagItemFunction>(functionId)) {
- case PlugIn::TagItemFunction::ForceToBackend:
- {
- 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 (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);
- }
- break;
- }
- default:
- break;
- }
- }
- 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;
- 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::OnFlightPlanDisconnect(EuroScopePlugIn::CFlightPlan flightPlan) {
- std::string callsign(flightPlan.GetCorrelatedRadarTarget().GetCallsign());
- std::lock_guard guard(this->m_updateQueueLock);
- auto it = std::find(this->m_forcedToBackendCallsigns.begin(), this->m_forcedToBackendCallsigns.end(), callsign);
- if (this->m_forcedToBackendCallsigns.end() != it)
- this->m_forcedToBackendCallsigns.erase(it);
- }
- void PlugIn::OnTimer(int counter) {
- 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;
- bool inserted = false;
- 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);
- inserted = true;
- }
- }
- /* send the report */
- auto sequence = Backend::instance().update(update);
- if (true == inserted && nullptr != sequence)
- this->DisplayUserMessage(PLUGIN_NAME, "ERROR", "Unable to send a new aircraft report update", true, true, true, true, true);
- airport.second.clear();
- }
- this->m_updateQueue.clear();
- this->m_updateQueueLock.unlock();
- }
|