PlugIn.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. /*
  2. * Author:
  3. * Sven Czarnian <devel@svcz.de>
  4. * Brief:
  5. * Implements the EuroScope plug-in definition
  6. * Copyright:
  7. * 2021 Sven Czarnian
  8. * License:
  9. * GNU General Public License v3 (GPLv3)
  10. */
  11. #include "stdafx.h"
  12. #include <algorithm>
  13. #include <cctype>
  14. #include <gsl/gsl>
  15. #include <Shlwapi.h>
  16. #include <Windows.h>
  17. #include <curl/curl.h>
  18. #include <json/json.h>
  19. #include <aman/com/Backend.h>
  20. #include <aman/config/CommunicationFileFormat.h>
  21. #include <aman/config/IdentifierFileFormat.h>
  22. #include <aman/helper/String.h>
  23. #include "com/ZmqContext.h"
  24. #include "PlugIn.h"
  25. EXTERN_C IMAGE_DOS_HEADER __ImageBase;
  26. using namespace aman;
  27. static std::string __receivedAmanData;
  28. static std::size_t receiveCurl(void* ptr, std::size_t size, std::size_t nmemb, void* stream) {
  29. (void)stream;
  30. std::string serverResult = static_cast<char*>(ptr);
  31. __receivedAmanData += serverResult;
  32. return size * nmemb;
  33. }
  34. PlugIn::PlugIn() :
  35. EuroScopePlugIn::CPlugIn(EuroScopePlugIn::COMPATIBILITY_CODE,
  36. PLUGIN_NAME,
  37. PLUGIN_VERSION,
  38. PLUGIN_DEVELOPER,
  39. PLUGIN_COPYRIGHT),
  40. m_configuration(),
  41. m_screen(),
  42. m_updateQueueLock(),
  43. m_updateQueue(),
  44. m_forcedToBackendCallsigns(),
  45. m_compatible(false) {
  46. GOOGLE_PROTOBUF_VERIFY_VERSION;
  47. this->DisplayUserMessage(PLUGIN_NAME, "INFO", (std::string("Loaded ") + PLUGIN_NAME + " " + PLUGIN_VERSION).c_str(), true, true, false, false, false);
  48. /* get the dll-path */
  49. char path[MAX_PATH + 1] = { 0 };
  50. const gsl::span<char, MAX_PATH + 1> span(path);
  51. GetModuleFileNameA((HINSTANCE)&__ImageBase, span.data(), span.size());
  52. PathRemoveFileSpecA(span.data());
  53. std::string dllPath = span.data();
  54. CommunicationFileFormat comFormat;
  55. if (false == comFormat.parse(dllPath + "\\AmanCommunication.txt", this->m_configuration) || true == comFormat.errorFound()) {
  56. this->DisplayUserMessage(PLUGIN_NAME, "ERROR", "Unable to parse AmanCommunication.txt", true, true, true, true, true);
  57. this->DisplayUserMessage(PLUGIN_NAME, "ERROR", ("AmanCommunication.txt:" + std::to_string(comFormat.errorLine()) + " - " + comFormat.errorMessage()).c_str(), true, true, true, true, true);
  58. return;
  59. }
  60. IdentifierFileFormat identFormat;
  61. if (false == identFormat.parse(dllPath + "\\AmanIdentity.txt", this->m_configuration) || true == identFormat.errorFound()) {
  62. this->DisplayUserMessage(PLUGIN_NAME, "ERROR", "Unable to parse AmanIdentity.txt", true, true, true, true, true);
  63. this->DisplayUserMessage(PLUGIN_NAME, "ERROR", ("AmanIdentity.txt:" + std::to_string(identFormat.errorLine()) + " - " + identFormat.errorMessage()).c_str(), true, true, true, true, true);
  64. return;
  65. }
  66. ZmqContext::instance().initialize();
  67. if (false == Backend::instance().initialize(this->m_configuration))
  68. this->DisplayUserMessage(PLUGIN_NAME, "ERROR", "Unable to initialize the reporter-connection to the backend", true, true, true, true, true);
  69. this->validateBackendData();
  70. }
  71. PlugIn::~PlugIn() noexcept {
  72. Backend::instance().deinitialize();
  73. ZmqContext::instance().deinitialize();
  74. google::protobuf::ShutdownProtobufLibrary();
  75. }
  76. void PlugIn::validateBackendData() {
  77. std::string url = "http://" + this->m_configuration.address + ":5000/aman/airports";
  78. CURL* curl = curl_easy_init();
  79. CURLcode result;
  80. curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
  81. curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
  82. curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
  83. curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, static_cast<long>(CURL_HTTP_VERSION_1_1));
  84. curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
  85. curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0L);
  86. curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, receiveCurl);
  87. curl_easy_setopt(curl, CURLOPT_TIMEOUT, 2L);
  88. struct curl_slist* headers = nullptr;
  89. headers = curl_slist_append(headers, "Accept: */*");
  90. headers = curl_slist_append(headers, "Content-Type: application/json");
  91. curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
  92. __receivedAmanData.clear();
  93. result = curl_easy_perform(curl);
  94. this->m_compatible = false;
  95. if (CURLE_OK != result) {
  96. MessageBoxA(nullptr, "Unable to receive backend information", "AMAN-Error", MB_OK);
  97. return;
  98. }
  99. /* validate the json data */
  100. Json::Value root;
  101. Json::CharReaderBuilder builder;
  102. std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
  103. if (false == reader->parse(__receivedAmanData.c_str(), __receivedAmanData.c_str() + __receivedAmanData.length(), &root, nullptr)) {
  104. MessageBoxA(nullptr, "Received invalid backend data", "AMAN-Error", MB_OK);
  105. return;
  106. }
  107. std::vector<std::string> airports;
  108. std::string version;
  109. for (auto it = root.begin(); root.end() != it; ++it) {
  110. if ("version" == it.key().asString()) {
  111. version = it->asString();
  112. }
  113. else if ("airports" == it.key().asString() && true == it->isArray()) {
  114. auto airportArray = *it;
  115. airports.reserve(airportArray.size());
  116. for (Json::Value::ArrayIndex i = 0; i < airportArray.size(); ++i)
  117. airports.push_back(airportArray[i].asString());
  118. }
  119. }
  120. /* invalid version or airports info */
  121. auto entries = String::splitString(version, ".");
  122. if (3 != entries.size() || 0 == airports.size()) {
  123. MessageBoxA(nullptr, "Unable to receive backend information", "AMAN-Error", MB_OK);
  124. return;
  125. }
  126. /* incompatible communication version */
  127. if (PLUGIN_MAJOR_VERSION != std::atoi(entries[0].c_str()) || PLUGIN_MINOR_VERSION != std::atoi(entries[1].c_str())) {
  128. MessageBoxA(nullptr, "Plugin version is outdated. An update is required", "AMAN-Error", MB_OK);
  129. return;
  130. }
  131. std::lock_guard guard(this->m_updateQueueLock);
  132. this->m_updateQueue.clear();
  133. for (const auto& airport : std::as_const(airports))
  134. this->m_updateQueue.insert({ airport, {} });
  135. this->m_compatible = true;
  136. }
  137. EuroScopePlugIn::CRadarScreen* PlugIn::OnRadarScreenCreated(const char* displayName, bool needsRadarContent, bool geoReferenced,
  138. bool canBeSaved, bool canBeCreated) {
  139. std::ignore = needsRadarContent;
  140. std::ignore = geoReferenced;
  141. std::ignore = canBeSaved;
  142. std::ignore = canBeCreated;
  143. std::ignore = displayName;
  144. if (nullptr == this->m_screen)
  145. this->m_screen = std::make_shared<RadarScreen>();
  146. return this->m_screen.get();
  147. }
  148. aman::Aircraft* PlugIn::generateAircraftMessage(const EuroScopePlugIn::CRadarTarget& target) {
  149. if (false == target.IsValid() || false == target.GetCorrelatedFlightPlan().IsValid())
  150. return nullptr;
  151. auto flightPlan = target.GetCorrelatedFlightPlan();
  152. std::string callsign(target.GetCallsign());
  153. aman::Aircraft* retval = new aman::Aircraft();
  154. /* fill all available information */
  155. retval->set_callsign(callsign);
  156. if (3 <= callsign.length())
  157. retval->set_airline(callsign.substr(0, 3));
  158. retval->set_type(flightPlan.GetFlightPlanData().GetAircraftFPType());
  159. retval->set_wtc(std::to_string(flightPlan.GetFlightPlanData().GetAircraftWtc()));
  160. /* TODO get recat */
  161. retval->set_enginecount(flightPlan.GetFlightPlanData().GetEngineNumber());
  162. switch (flightPlan.GetFlightPlanData().GetEngineType()) {
  163. case 'P':
  164. case 'T':
  165. retval->set_enginetype(aman::Aircraft_EngineType::Aircraft_EngineType_TURBOPROB);
  166. break;
  167. case 'E':
  168. retval->set_enginetype(aman::Aircraft_EngineType::Aircraft_EngineType_ELECTRIC);
  169. break;
  170. case 'J':
  171. retval->set_enginetype(aman::Aircraft_EngineType::Aircraft_EngineType_JET);
  172. break;
  173. default:
  174. retval->set_enginetype(aman::Aircraft_EngineType::Aircraft_EngineType_UNKNOWN);
  175. break;
  176. }
  177. return retval;
  178. }
  179. void PlugIn::generateAircraftReportMessage(const EuroScopePlugIn::CRadarTarget& radarTarget, aman::AircraftReport* report) {
  180. auto flightPlan = radarTarget.GetCorrelatedFlightPlan();
  181. /* ignore invalid flightplans */
  182. if (false == flightPlan.IsValid())
  183. return;
  184. /* ignore flights that are not tracked by the current controller */
  185. if (false == flightPlan.GetTrackingControllerIsMe())
  186. return;
  187. /* ignore non-IFR flights */
  188. if (nullptr == flightPlan.GetFlightPlanData().GetPlanType() || 'I' != *flightPlan.GetFlightPlanData().GetPlanType())
  189. return;
  190. /* filter invalid destinations */
  191. auto destination = std::string_view(flightPlan.GetFlightPlanData().GetDestination());
  192. if (4 != destination.length())
  193. return;
  194. /* filter by distance to destination */
  195. double distanceNM = flightPlan.GetDistanceToDestination();
  196. if (5.0 > distanceNM || 250.0 < distanceNM)
  197. return;
  198. /* filter by airborne identifier (assume a GS>50kn and a big distance to the origin) */
  199. if (50 > radarTarget.GetGS() || 10.0 > flightPlan.GetDistanceFromOrigin())
  200. return;
  201. /* generate protobuf message */
  202. aman::Aircraft* aircraft = this->generateAircraftMessage(radarTarget);
  203. if (nullptr == aircraft)
  204. return;
  205. aman::Dynamics* dynamics = new aman::Dynamics();
  206. dynamics->set_altitude(radarTarget.GetPosition().GetFlightLevel());
  207. dynamics->set_heading(radarTarget.GetPosition().GetReportedHeading());
  208. dynamics->set_groundspeed(radarTarget.GetPosition().GetReportedGS());
  209. dynamics->set_verticalspeed(radarTarget.GetVerticalSpeed());
  210. aman::Coordinate* coordinate = new aman::Coordinate();
  211. coordinate->set_latitude(radarTarget.GetPosition().GetPosition().m_Latitude);
  212. coordinate->set_longitude(radarTarget.GetPosition().GetPosition().m_Longitude);
  213. /* create the report */
  214. switch (this->ControllerMyself().GetFacility()) {
  215. case 1:
  216. case 6:
  217. report->set_reportedby(aman::AircraftReport::CENTER);
  218. break;
  219. case 2:
  220. report->set_reportedby(aman::AircraftReport::DELIVERY);
  221. break;
  222. case 3:
  223. report->set_reportedby(aman::AircraftReport::GROUND);
  224. break;
  225. case 4:
  226. report->set_reportedby(aman::AircraftReport::TOWER);
  227. break;
  228. case 5:
  229. report->set_reportedby(aman::AircraftReport::APPROACH);
  230. break;
  231. default:
  232. report->set_reportedby(aman::AircraftReport::UNKNOWN);
  233. break;
  234. }
  235. auto currentPosition = radarTarget.GetPosition().GetPosition();
  236. EuroScopePlugIn::CPosition iafPosition;
  237. std::string iafName;
  238. for (auto element = this->SectorFileElementSelectFirst(EuroScopePlugIn::SECTOR_ELEMENT_STAR);
  239. true == element.IsValid();
  240. element = this->SectorFileElementSelectNext(element, EuroScopePlugIn::SECTOR_ELEMENT_STAR))
  241. {
  242. auto split = String::splitString(element.GetName(), " ");
  243. /* find the correct star */
  244. if (0 != split.size() && destination == split[0]) {
  245. /* get the IAF */
  246. EuroScopePlugIn::CPosition position;
  247. if (true == element.GetPosition(&position, 0)) {
  248. /* match the waypoints to get the name*/
  249. for (int i = 0; i < flightPlan.GetExtractedRoute().GetPointsNumber(); ++i) {
  250. if (1.0f >= flightPlan.GetExtractedRoute().GetPointPosition(i).DistanceTo(position)) {
  251. iafPosition = flightPlan.GetExtractedRoute().GetPointPosition(i);
  252. iafName = flightPlan.GetExtractedRoute().GetPointName(i);
  253. report->set_initialapproachfix(iafName);
  254. break;
  255. }
  256. }
  257. }
  258. }
  259. if (0 != iafName.length())
  260. break;
  261. }
  262. if (0 != iafName.length()) {
  263. auto iafDistance = currentPosition.DistanceTo(iafPosition);
  264. /* calculate the distance to the IAF */
  265. double distanceToIaf = 0.0f;
  266. for (int i = 1; i < flightPlan.GetPositionPredictions().GetPointsNumber(); ++i) {
  267. double distance = flightPlan.GetPositionPredictions().GetPosition(i).DistanceTo(currentPosition);
  268. double headingDelta = std::abs(radarTarget.GetPosition().GetReportedHeading() - flightPlan.GetPositionPredictions().GetPosition(i).DirectionTo(iafPosition));
  269. /*
  270. * 1. no direct way to IAF -> some direct given -> stop after lateral passing
  271. * 2. passed the IAF on a way to a direct
  272. */
  273. if ((90.0 < headingDelta && 270.0 > headingDelta) || iafDistance < distance)
  274. break;
  275. distanceToIaf += distance;
  276. currentPosition = flightPlan.GetPositionPredictions().GetPosition(i);
  277. }
  278. if (0.01f >= std::abs(distanceToIaf))
  279. distanceToIaf = iafDistance;
  280. report->set_distancetoiaf(static_cast<int>(std::round(distanceToIaf)));
  281. }
  282. report->set_destination(std::string(destination));
  283. /* support GrPlugin and TST with the stand association */
  284. if (nullptr != radarTarget.GetCorrelatedFlightPlan().GetControllerAssignedData().GetFlightStripAnnotation(6)) {
  285. std::string stand(radarTarget.GetCorrelatedFlightPlan().GetControllerAssignedData().GetFlightStripAnnotation(6));
  286. auto split = String::splitString(stand, "/");
  287. if (3 == split.size() && "s" == gsl::at(split, 0) && "s" == gsl::at(split, 2))
  288. report->set_plannedgate(gsl::at(split, 1));
  289. }
  290. report->set_allocated_aircraft(aircraft);
  291. report->set_allocated_dynamics(dynamics);
  292. report->set_allocated_position(coordinate);
  293. /* set the report time */
  294. std::stringstream stream;
  295. auto reportTime = std::chrono::utc_clock::now();
  296. stream << std::format("{0:%Y%m%d%H%M%S}", reportTime);
  297. report->set_reporttime(String::splitString(stream.str(), ".")[0]);
  298. }
  299. void PlugIn::OnFunctionCall(int functionId, const char* itemString, POINT pt, RECT area) {
  300. std::ignore = itemString;
  301. std::ignore = pt;
  302. std::ignore = area;
  303. auto radarTarget = this->RadarTargetSelectASEL();
  304. if (false == radarTarget.IsValid() || false == radarTarget.GetCorrelatedFlightPlan().IsValid())
  305. return;
  306. std::string callsign(radarTarget.GetCallsign());
  307. switch (static_cast<PlugIn::TagItemFunction>(functionId)) {
  308. case PlugIn::TagItemFunction::ForceToBackend:
  309. {
  310. std::string destination(radarTarget.GetCorrelatedFlightPlan().GetFlightPlanData().GetDestination());
  311. std::transform(destination.begin(), destination.end(), destination.begin(), ::toupper);
  312. std::lock_guard guard(this->m_updateQueueLock);
  313. auto it = this->m_updateQueue.find(destination);
  314. if (this->m_updateQueue.end() != it) {
  315. auto cIt = std::find(this->m_forcedToBackendCallsigns.cbegin(), this->m_forcedToBackendCallsigns.cend(), callsign);
  316. if (this->m_forcedToBackendCallsigns.cend() == cIt)
  317. this->m_forcedToBackendCallsigns.push_back(callsign);
  318. }
  319. break;
  320. }
  321. default:
  322. break;
  323. }
  324. }
  325. void PlugIn::OnRadarTargetPositionUpdate(EuroScopePlugIn::CRadarTarget radarTarget) {
  326. /* do nothing if the reporter is not initialized and ignore invalid targets */
  327. if (false == this->m_compatible || false == Backend::instance().initialized() || false == radarTarget.IsValid())
  328. return;
  329. std::lock_guard guard(this->m_updateQueueLock);
  330. auto forcedIt = std::find(this->m_forcedToBackendCallsigns.cbegin(), this->m_forcedToBackendCallsigns.cend(), radarTarget.GetCallsign());
  331. if (false == radarTarget.GetCorrelatedFlightPlan().GetTrackingControllerIsMe() && this->m_forcedToBackendCallsigns.cend() == forcedIt)
  332. return;
  333. std::string destination(radarTarget.GetCorrelatedFlightPlan().GetFlightPlanData().GetDestination());
  334. #pragma warning(disable: 4244)
  335. std::transform(destination.begin(), destination.end(), destination.begin(), ::toupper);
  336. #pragma warning(default: 4244)
  337. auto it = this->m_updateQueue.find(destination);
  338. if (this->m_updateQueue.end() != it)
  339. it->second.push_back(radarTarget.GetCallsign());
  340. }
  341. void PlugIn::OnFlightPlanDisconnect(EuroScopePlugIn::CFlightPlan flightPlan) {
  342. std::string callsign(flightPlan.GetCorrelatedRadarTarget().GetCallsign());
  343. std::lock_guard guard(this->m_updateQueueLock);
  344. auto it = std::find(this->m_forcedToBackendCallsigns.begin(), this->m_forcedToBackendCallsigns.end(), callsign);
  345. if (this->m_forcedToBackendCallsigns.end() != it)
  346. this->m_forcedToBackendCallsigns.erase(it);
  347. }
  348. void PlugIn::OnTimer(int counter) {
  349. if (false == this->m_compatible || false == Backend::instance().initialized() || 0 != (counter % 10))
  350. return;
  351. this->m_updateQueueLock.lock();
  352. for (auto& airport : this->m_updateQueue) {
  353. aman::AircraftUpdate update;
  354. bool inserted = false;
  355. for (auto target = this->RadarTargetSelectFirst(); true == target.IsValid(); target = this->RadarTargetSelectNext(target)) {
  356. auto it = std::find(airport.second.begin(), airport.second.end(), target.GetCallsign());
  357. if (airport.second.end() != it) {
  358. auto report = update.add_reports();
  359. this->generateAircraftReportMessage(target, report);
  360. inserted = true;
  361. }
  362. }
  363. /* send the report */
  364. auto sequence = Backend::instance().update(update);
  365. if (true == inserted && nullptr != sequence)
  366. this->DisplayUserMessage(PLUGIN_NAME, "ERROR", "Unable to send a new aircraft report update", true, true, true, true, true);
  367. airport.second.clear();
  368. }
  369. this->m_updateQueue.clear();
  370. this->m_updateQueueLock.unlock();
  371. }