Files
aman-es/src/PlugIn.cpp
Sven Czarnian 0acb45dd27 force a reload
2021-11-20 09:29:01 +01:00

471 righe
18 KiB
C++

/*
* 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]);
}
bool PlugIn::OnCompileCommand(const char* cmdline) {
std::string message(cmdline);
#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 false;
if (std::string::npos != message.find("RELOAD")) {
this->validateBackendData();
return true;
}
return false;
}
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();
}