PlugIn.cpp 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799
  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. #pragma warning(push, 0)
  19. #include <Eigen/Geometry>
  20. #pragma warning(pop)
  21. #include <GeographicLib/Gnomonic.hpp>
  22. #include <json/json.h>
  23. #include <aman/com/Backend.h>
  24. #include <aman/config/CommunicationFileFormat.h>
  25. #include <aman/config/IdentifierFileFormat.h>
  26. #include <aman/helper/String.h>
  27. #include <aman/types/GeoCoordinate.h>
  28. #include "com/ZmqContext.h"
  29. #include "PlugIn.h"
  30. EXTERN_C IMAGE_DOS_HEADER __ImageBase;
  31. using namespace aman;
  32. static std::string __receivedAmanData;
  33. static std::size_t receiveCurl(void* ptr, std::size_t size, std::size_t nmemb, void* stream) {
  34. std::ignore = stream;
  35. std::string serverResult = static_cast<char*>(ptr);
  36. __receivedAmanData += serverResult;
  37. return size * nmemb;
  38. }
  39. PlugIn::PlugIn() :
  40. EuroScopePlugIn::CPlugIn(EuroScopePlugIn::COMPATIBILITY_CODE,
  41. PLUGIN_NAME,
  42. PLUGIN_VERSION,
  43. PLUGIN_DEVELOPER,
  44. PLUGIN_COPYRIGHT),
  45. m_configuration(),
  46. m_screen(),
  47. m_updateQueueLock(),
  48. m_updateQueue(),
  49. m_inboundsQueueLock(),
  50. m_inbounds(),
  51. m_forcedToBackendCallsigns(),
  52. m_compatible(false),
  53. m_connectedToNetwork(false),
  54. m_sweatboxValid(false),
  55. m_playbackValid(false) {
  56. GOOGLE_PROTOBUF_VERIFY_VERSION;
  57. this->RegisterTagItemType("ETA", static_cast<int>(PlugIn::TagItemElement::EstimatedTimeOfArrival));
  58. this->RegisterTagItemType("PTA", static_cast<int>(PlugIn::TagItemElement::PlannedTimeOfArrival));
  59. this->RegisterTagItemType("Delta time", static_cast<int>(PlugIn::TagItemElement::DeltaTime));
  60. this->RegisterTagItemType("Fixed plan indicator", static_cast<int>(PlugIn::TagItemElement::FixedPlanIndicator));
  61. this->RegisterTagItemFunction("Force planning", static_cast<int>(PlugIn::TagItemFunction::ForceToBackendMenu));
  62. this->DisplayUserMessage(PLUGIN_NAME, "INFO", (std::string("Loaded ") + PLUGIN_NAME + " " + PLUGIN_VERSION).c_str(), true, true, false, false, false);
  63. /* get the dll-path */
  64. char path[MAX_PATH + 1] = { 0 };
  65. const gsl::span<char, MAX_PATH + 1> span(path);
  66. GetModuleFileNameA((HINSTANCE)&__ImageBase, span.data(), span.size());
  67. PathRemoveFileSpecA(span.data());
  68. std::string dllPath = span.data();
  69. CommunicationFileFormat comFormat;
  70. if (false == comFormat.parse(dllPath + "\\AmanCommunication.txt", this->m_configuration) || true == comFormat.errorFound()) {
  71. this->DisplayUserMessage(PLUGIN_NAME, "ERROR", "Unable to parse AmanCommunication.txt", true, true, true, true, true);
  72. this->DisplayUserMessage(PLUGIN_NAME, "ERROR", ("AmanCommunication.txt:" + std::to_string(comFormat.errorLine()) + " - " + comFormat.errorMessage()).c_str(), true, true, true, true, true);
  73. return;
  74. }
  75. IdentifierFileFormat identFormat;
  76. if (false == identFormat.parse(dllPath + "\\AmanIdentity.txt", this->m_configuration) || true == identFormat.errorFound()) {
  77. this->DisplayUserMessage(PLUGIN_NAME, "ERROR", "Unable to parse AmanIdentity.txt", true, true, true, true, true);
  78. this->DisplayUserMessage(PLUGIN_NAME, "ERROR", ("AmanIdentity.txt:" + std::to_string(identFormat.errorLine()) + " - " + identFormat.errorMessage()).c_str(), true, true, true, true, true);
  79. return;
  80. }
  81. ZmqContext::instance().initialize();
  82. if (false == Backend::instance().initialize(this->m_configuration))
  83. this->DisplayUserMessage(PLUGIN_NAME, "ERROR", "Unable to initialize the reporter-connection to the backend", true, true, true, true, true);
  84. this->validateBackendData();
  85. }
  86. PlugIn::~PlugIn() {
  87. Backend::instance().deinitialize();
  88. ZmqContext::instance().deinitialize();
  89. google::protobuf::ShutdownProtobufLibrary();
  90. }
  91. void PlugIn::validateBackendData() {
  92. if (false == this->m_configuration.valid) {
  93. this->m_compatible = false;
  94. return;
  95. }
  96. /* set up the URL */
  97. std::string url;
  98. if (true == this->m_configuration.httpsProtocol)
  99. url += "https://";
  100. else
  101. url += "http://";
  102. url += this->m_configuration.address + ":" + std::to_string(this->m_configuration.portRestAPI) + "/aman/airports";
  103. CURL* curl = curl_easy_init();
  104. CURLcode result;
  105. curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
  106. curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
  107. curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
  108. curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, static_cast<long>(CURL_HTTP_VERSION_1_1));
  109. curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
  110. curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0L);
  111. curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, receiveCurl);
  112. curl_easy_setopt(curl, CURLOPT_TIMEOUT, 2L);
  113. struct curl_slist* headers = nullptr;
  114. headers = curl_slist_append(headers, "Accept: */*");
  115. headers = curl_slist_append(headers, "Content-Type: application/json");
  116. curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
  117. __receivedAmanData.clear();
  118. result = curl_easy_perform(curl);
  119. this->m_compatible = false;
  120. if (CURLE_OK != result) {
  121. MessageBoxA(nullptr, "Unable to receive backend information", "AMAN-Error", MB_OK);
  122. return;
  123. }
  124. /* validate the json data */
  125. Json::Value root;
  126. Json::CharReaderBuilder builder;
  127. std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
  128. if (false == reader->parse(__receivedAmanData.c_str(), __receivedAmanData.c_str() + __receivedAmanData.length(), &root, nullptr)) {
  129. MessageBoxA(nullptr, "Received invalid backend data", "AMAN-Error", MB_OK);
  130. return;
  131. }
  132. std::vector<std::string> airports;
  133. std::string version;
  134. for (auto it = root.begin(); root.end() != it; ++it) {
  135. if ("version" == it.key().asString()) {
  136. version = it->asString();
  137. }
  138. else if ("airports" == it.key().asString() && true == it->isArray()) {
  139. auto airportArray = *it;
  140. airports.reserve(airportArray.size());
  141. for (Json::Value::ArrayIndex i = 0; i < airportArray.size(); ++i)
  142. airports.push_back(airportArray[i].asString());
  143. }
  144. }
  145. /* invalid version or airports info */
  146. auto entries = String::splitString(version, ".");
  147. if (3 != entries.size() || 0 == airports.size()) {
  148. MessageBoxA(nullptr, "Unable to receive backend information", "AMAN-Error", MB_OK);
  149. return;
  150. }
  151. /* incompatible communication version */
  152. if (PLUGIN_MAJOR_VERSION != std::atoi(gsl::at(entries, 0).c_str()) || PLUGIN_MINOR_VERSION != std::atoi(gsl::at(entries, 1).c_str())) {
  153. MessageBoxA(nullptr, "Plugin version is outdated. An update is required", "AMAN-Error", MB_OK);
  154. return;
  155. }
  156. std::lock_guard guard(this->m_updateQueueLock);
  157. this->m_updateQueue.clear();
  158. for (const auto& airport : std::as_const(airports))
  159. this->m_updateQueue.insert({ airport, {} });
  160. this->m_compatible = true;
  161. this->DisplayUserMessage(PLUGIN_NAME, "INFO", "Reloaded AMAN-configuration", true, true, false, false, false);
  162. }
  163. EuroScopePlugIn::CRadarScreen* PlugIn::OnRadarScreenCreated(const char* displayName, bool needsRadarContent, bool geoReferenced,
  164. bool canBeSaved, bool canBeCreated) {
  165. std::ignore = needsRadarContent;
  166. std::ignore = geoReferenced;
  167. std::ignore = canBeSaved;
  168. std::ignore = canBeCreated;
  169. std::ignore = displayName;
  170. if (nullptr == this->m_screen)
  171. this->m_screen = std::make_shared<RadarScreen>();
  172. return this->m_screen.get();
  173. }
  174. aman::Aircraft* PlugIn::generateAircraftMessage(const EuroScopePlugIn::CRadarTarget& target) const {
  175. if (false == target.IsValid() || false == target.GetCorrelatedFlightPlan().IsValid())
  176. return nullptr;
  177. const auto flightPlan = target.GetCorrelatedFlightPlan();
  178. std::string callsign(target.GetCallsign());
  179. aman::Aircraft* retval = new aman::Aircraft();
  180. /* fill all available information */
  181. retval->set_callsign(callsign);
  182. if (3 <= callsign.length())
  183. retval->set_airline(callsign.substr(0, 3));
  184. retval->set_type(flightPlan.GetFlightPlanData().GetAircraftFPType());
  185. retval->set_wtc(std::to_string(flightPlan.GetFlightPlanData().GetAircraftWtc()));
  186. /* TODO get recat */
  187. retval->set_enginecount(flightPlan.GetFlightPlanData().GetEngineNumber());
  188. switch (flightPlan.GetFlightPlanData().GetEngineType()) {
  189. case 'P':
  190. case 'T':
  191. retval->set_enginetype(aman::Aircraft_EngineType::Aircraft_EngineType_TURBOPROB);
  192. break;
  193. case 'E':
  194. retval->set_enginetype(aman::Aircraft_EngineType::Aircraft_EngineType_ELECTRIC);
  195. break;
  196. case 'J':
  197. retval->set_enginetype(aman::Aircraft_EngineType::Aircraft_EngineType_JET);
  198. break;
  199. default:
  200. retval->set_enginetype(aman::Aircraft_EngineType::Aircraft_EngineType_UNKNOWN);
  201. break;
  202. }
  203. return retval;
  204. }
  205. void PlugIn::distanceToPredictedIaf(const EuroScopePlugIn::CRadarTarget& radarTarget, const EuroScopePlugIn::CFlightPlan& flightPlan,
  206. const EuroScopePlugIn::CPosition& iafPosition, aman::AircraftReport* report) {
  207. std::string_view direct(flightPlan.GetControllerAssignedData().GetDirectToPointName());
  208. if (0 != direct.length()) {
  209. GeoCoordinate directCoordinate;
  210. /* find the coordinate of the direct waypoint */
  211. for (int i = 0; i < flightPlan.GetExtractedRoute().GetPointsNumber(); ++i) {
  212. if (flightPlan.GetExtractedRoute().GetPointName(i) == direct) {
  213. directCoordinate = GeoCoordinate(static_cast<float>(flightPlan.GetExtractedRoute().GetPointPosition(i).m_Longitude) * degree,
  214. static_cast<float>(flightPlan.GetExtractedRoute().GetPointPosition(i).m_Latitude) * degree);
  215. break;
  216. }
  217. }
  218. if (0_m != directCoordinate.distanceTo(GeoCoordinate())) {
  219. const GeographicLib::Gnomonic projection(GeographicLib::Geodesic::WGS84());
  220. Eigen::Vector2f currentCartesian, iafCartesian, directCartesian;
  221. /* convert to Cartesian with IAF as the reference */
  222. projection.Forward(static_cast<float>(iafPosition.m_Latitude), static_cast<float>(iafPosition.m_Longitude),
  223. static_cast<float>(radarTarget.GetPosition().GetPosition().m_Latitude),
  224. static_cast<float>(radarTarget.GetPosition().GetPosition().m_Longitude),
  225. currentCartesian[0], currentCartesian[1]);
  226. projection.Forward(static_cast<float>(iafPosition.m_Latitude), static_cast<float>(iafPosition.m_Longitude),
  227. directCoordinate.latitude().convert(degree),
  228. directCoordinate.longitude().convert(degree),
  229. directCartesian[0], directCartesian[1]);
  230. projection.Forward(static_cast<float>(iafPosition.m_Latitude), static_cast<float>(iafPosition.m_Longitude),
  231. static_cast<float>(iafPosition.m_Latitude), static_cast<float>(iafPosition.m_Longitude),
  232. iafCartesian[0], iafCartesian[1]);
  233. /* project IAF on line between current position and direct */
  234. const auto direction = (directCartesian - currentCartesian).normalized();
  235. Eigen::ParametrizedLine<float, 2> line(currentCartesian, direction);
  236. const auto projectCartesian = line.projection(iafCartesian);
  237. const auto distanceStart = (currentCartesian - projectCartesian).squaredNorm();
  238. const auto distanceEnd = (directCartesian - projectCartesian).squaredNorm();
  239. const auto distanceDirect = (currentCartesian - directCartesian).squaredNorm();
  240. /* projection of IAF in front of aircraft and not behind direct */
  241. if (distanceStart <= distanceDirect && distanceEnd <= distanceDirect) {
  242. EuroScopePlugIn::CPosition projected;
  243. float lat = 0.0f, lon = 0.0f;
  244. projection.Reverse(static_cast<float>(iafPosition.m_Latitude), static_cast<float>(iafPosition.m_Longitude),
  245. projectCartesian[0], projectCartesian[1], lat, lon);
  246. projected.m_Latitude = lat;
  247. projected.m_Longitude = lon;
  248. const auto distanceToProjected = radarTarget.GetPosition().GetPosition().DistanceTo(projected);
  249. report->set_distancetoiaf(static_cast<int>(std::round(distanceToProjected)));
  250. }
  251. else {
  252. report->set_distancetoiaf(0);
  253. }
  254. }
  255. /* did not find the coordinate -> fallback */
  256. else {
  257. report->set_distancetoiaf(0);
  258. }
  259. }
  260. /* in heading mode and passed IAF */
  261. else {
  262. report->set_distancetoiaf(0);
  263. }
  264. }
  265. void PlugIn::generateAircraftReportMessage(EuroScopePlugIn::CRadarTarget& radarTarget, aman::AircraftReport* report) {
  266. const auto flightPlan = radarTarget.GetCorrelatedFlightPlan();
  267. /* ignore invalid flightplans */
  268. if (false == flightPlan.IsValid())
  269. return;
  270. /* ignore flights that are not tracked by the current controller */
  271. if (false == flightPlan.GetTrackingControllerIsMe())
  272. return;
  273. /* ignore non-IFR flights */
  274. if (nullptr == flightPlan.GetFlightPlanData().GetPlanType() || 'I' != *flightPlan.GetFlightPlanData().GetPlanType())
  275. return;
  276. /* filter invalid destinations */
  277. const auto destination = std::string_view(flightPlan.GetFlightPlanData().GetDestination());
  278. if (4 != destination.length())
  279. return;
  280. /* filter by airborne identifier (assume a GS>50kn and a big distance to the origin) */
  281. if (50 > radarTarget.GetGS() || 10.0 > flightPlan.GetDistanceFromOrigin())
  282. return;
  283. /* generate protobuf message */
  284. aman::Aircraft* aircraft = this->generateAircraftMessage(radarTarget);
  285. if (nullptr == aircraft)
  286. return;
  287. aman::Dynamics* dynamics = new aman::Dynamics();
  288. dynamics->set_altitude(radarTarget.GetPosition().GetFlightLevel());
  289. dynamics->set_heading(radarTarget.GetPosition().GetReportedHeading());
  290. dynamics->set_groundspeed(radarTarget.GetPosition().GetReportedGS());
  291. dynamics->set_verticalspeed(radarTarget.GetVerticalSpeed());
  292. aman::Coordinate* coordinate = new aman::Coordinate();
  293. coordinate->set_latitude(radarTarget.GetPosition().GetPosition().m_Latitude);
  294. coordinate->set_longitude(radarTarget.GetPosition().GetPosition().m_Longitude);
  295. /* create the report */
  296. switch (this->ControllerMyself().GetFacility()) {
  297. case 1:
  298. case 6:
  299. report->set_reportedby(aman::AircraftReport::CENTER);
  300. break;
  301. case 2:
  302. report->set_reportedby(aman::AircraftReport::DELIVERY);
  303. break;
  304. case 3:
  305. report->set_reportedby(aman::AircraftReport::GROUND);
  306. break;
  307. case 4:
  308. report->set_reportedby(aman::AircraftReport::TOWER);
  309. break;
  310. case 5:
  311. report->set_reportedby(aman::AircraftReport::APPROACH);
  312. break;
  313. default:
  314. report->set_reportedby(aman::AircraftReport::UNKNOWN);
  315. break;
  316. }
  317. auto currentPosition = radarTarget.GetPosition().GetPosition();
  318. EuroScopePlugIn::CPosition iafPosition;
  319. std::string iafName;
  320. for (auto element = this->SectorFileElementSelectFirst(EuroScopePlugIn::SECTOR_ELEMENT_STAR);
  321. true == element.IsValid();
  322. element = this->SectorFileElementSelectNext(element, EuroScopePlugIn::SECTOR_ELEMENT_STAR))
  323. {
  324. auto split = String::splitString(element.GetName(), " ");
  325. /* find the correct star */
  326. if (0 != split.size() && destination == gsl::at(split, 0)) {
  327. /* get the IAF */
  328. EuroScopePlugIn::CPosition position;
  329. if (true == element.GetPosition(&position, 0)) {
  330. /* match the waypoints to get the name*/
  331. for (int i = 0; i < flightPlan.GetExtractedRoute().GetPointsNumber(); ++i) {
  332. if (1.0f >= flightPlan.GetExtractedRoute().GetPointPosition(i).DistanceTo(position)) {
  333. iafPosition = flightPlan.GetExtractedRoute().GetPointPosition(i);
  334. iafName = flightPlan.GetExtractedRoute().GetPointName(i);
  335. report->set_initialapproachfix(iafName);
  336. break;
  337. }
  338. }
  339. }
  340. }
  341. if (0 != iafName.length())
  342. break;
  343. }
  344. if (0 != iafName.length()) {
  345. const auto iafDistance = currentPosition.DistanceTo(iafPosition);
  346. /* calculate the distance to the IAF */
  347. double distanceToIaf = 0.0f;
  348. for (int i = 1; i < flightPlan.GetPositionPredictions().GetPointsNumber(); ++i) {
  349. const double distance = flightPlan.GetPositionPredictions().GetPosition(i).DistanceTo(currentPosition);
  350. const double headingDelta = std::abs(radarTarget.GetPosition().GetReportedHeading() - flightPlan.GetPositionPredictions().GetPosition(i).DirectionTo(iafPosition));
  351. /*
  352. * 1. no direct way to IAF -> some direct given -> stop after lateral passing
  353. * 2. passed the IAF on a way to a direct
  354. */
  355. if ((90.0 < headingDelta && 270.0 > headingDelta) || iafDistance < distance)
  356. break;
  357. distanceToIaf += distance;
  358. currentPosition = flightPlan.GetPositionPredictions().GetPosition(i);
  359. }
  360. if (0.01f >= std::abs(distanceToIaf))
  361. distanceToIaf = iafDistance;
  362. report->set_distancetoiaf(static_cast<int>(std::round(distanceToIaf)));
  363. }
  364. report->set_destination(std::string(destination));
  365. /* support GrPlugin and TST with the stand association */
  366. if (nullptr != radarTarget.GetCorrelatedFlightPlan().GetControllerAssignedData().GetFlightStripAnnotation(6)) {
  367. std::string stand(radarTarget.GetCorrelatedFlightPlan().GetControllerAssignedData().GetFlightStripAnnotation(6));
  368. auto split = String::splitString(stand, "/");
  369. if (3 == split.size() && "s" == gsl::at(split, 0) && "s" == gsl::at(split, 2))
  370. report->set_plannedgate(gsl::at(split, 1));
  371. }
  372. report->set_allocated_aircraft(aircraft);
  373. report->set_allocated_dynamics(dynamics);
  374. report->set_allocated_position(coordinate);
  375. /* set the report time */
  376. std::stringstream stream;
  377. const auto reportTime = std::chrono::utc_clock::now();
  378. stream << std::format("{0:%Y%m%d%H%M%S}", reportTime);
  379. const auto elements = String::splitString(stream.str(), ".");
  380. report->set_reporttime(gsl::at(elements, 0));
  381. }
  382. bool PlugIn::OnCompileCommand(const char* cmdline) {
  383. std::string message(cmdline);
  384. bool retval = false;
  385. #pragma warning(disable: 4244)
  386. std::transform(message.begin(), message.end(), message.begin(), ::toupper);
  387. #pragma warning(default: 4244)
  388. /* no AMAN command */
  389. if (0 != message.find(".AMAN"))
  390. return retval;
  391. if (std::string::npos != message.find("RELOAD")) {
  392. this->validateBackendData();
  393. this->m_sweatboxValid = false;
  394. this->m_playbackValid = false;
  395. retval = true;
  396. }
  397. else if (std::string::npos != message.find("SWEATBOX")) {
  398. this->m_sweatboxValid = !this->m_sweatboxValid;
  399. if (true == this->m_sweatboxValid)
  400. this->m_playbackValid = false;
  401. retval = true;
  402. }
  403. else if (std::string::npos != message.find("PLAYBACK")) {
  404. this->m_playbackValid = !this->m_playbackValid;
  405. if (true == this->m_playbackValid)
  406. this->m_sweatboxValid = false;
  407. retval = true;
  408. }
  409. if (true == retval) {
  410. if (true == this->m_sweatboxValid)
  411. this->DisplayUserMessage(PLUGIN_NAME, "INFO", "Sweatbox data is used in AMAN", true, true, false, false, false);
  412. else
  413. this->DisplayUserMessage(PLUGIN_NAME, "INFO", "Sweatbox is ignored in AMAN", true, true, false, false, false);
  414. if (true == this->m_playbackValid)
  415. this->DisplayUserMessage(PLUGIN_NAME, "INFO", "Playback data is used in AMAN", true, true, false, false, false);
  416. else
  417. this->DisplayUserMessage(PLUGIN_NAME, "INFO", "Playback is ignored in AMAN", true, true, false, false, false);
  418. }
  419. return retval;
  420. }
  421. void PlugIn::OnGetTagItem(EuroScopePlugIn::CFlightPlan flightPlan, EuroScopePlugIn::CRadarTarget radarTarget,
  422. int itemCode, int tagData, char itemString[16], int* colorCode, COLORREF* rgb,
  423. double* fontSize) {
  424. std::ignore = tagData;
  425. std::ignore = rgb;
  426. std::ignore = fontSize;
  427. std::string callsign(radarTarget.GetCallsign());
  428. std::string destination(flightPlan.GetFlightPlanData().GetDestination());
  429. #pragma warning(disable: 4244)
  430. std::transform(destination.begin(), destination.end(), destination.begin(), ::toupper);
  431. #pragma warning(default: 4244)
  432. bool planExpected = false, forced = false;
  433. const gsl::span<char, 16> tagString(itemString, 16UL);
  434. this->m_updateQueueLock.lock();
  435. /* check if the inbound is forced */
  436. if (0 != this->m_forcedToBackendCallsigns.size()) {
  437. const auto cIt = std::find(this->m_forcedToBackendCallsigns.cbegin(), this->m_forcedToBackendCallsigns.cend(), callsign);
  438. forced = this->m_forcedToBackendCallsigns.cend() != cIt;
  439. }
  440. /* check if the inbound is expected to be planned */
  441. planExpected = this->m_updateQueue.cend() != this->m_updateQueue.find(destination);
  442. this->m_updateQueueLock.unlock();
  443. std::lock_guard guard(this->m_inboundsQueueLock);
  444. auto it = this->m_inbounds.find(callsign);
  445. std::string message;
  446. switch (static_cast<TagItemElement>(itemCode)) {
  447. case TagItemElement::EstimatedTimeOfArrival:
  448. {
  449. if (this->m_inbounds.cend() != it)
  450. message = UtcTime::timeToString(it->second.eta(), "%H:%M");
  451. else if (true == forced || true == planExpected)
  452. message = "??:??";
  453. break;
  454. }
  455. case TagItemElement::PlannedTimeOfArrival:
  456. {
  457. if (this->m_inbounds.cend() != it)
  458. message = UtcTime::timeToString(it->second.pta(), "%H:%M");
  459. else if (true == forced || true == planExpected)
  460. message = "??:??";
  461. break;
  462. }
  463. case TagItemElement::DeltaTime:
  464. {
  465. if (this->m_inbounds.cend() != it) {
  466. const auto ttl = static_cast<int>(std::roundf(it->second.timeToLose().convert(second)));
  467. std::stringstream stream;
  468. stream << ttl;
  469. message = stream.str();
  470. }
  471. else if (true == forced || true == planExpected) {
  472. message = "??";
  473. }
  474. break;
  475. }
  476. case TagItemElement::FixedPlanIndicator:
  477. if (this->m_inbounds.cend() != it && false == it->second.fixedPlan())
  478. message = "*";
  479. else if (this->m_inbounds.cend() == it && (true == forced || true == planExpected))
  480. message = "*";
  481. break;
  482. default:
  483. break;
  484. }
  485. if (0 != message.length()) {
  486. std::strcpy(itemString, message.c_str());
  487. *colorCode = EuroScopePlugIn::TAG_COLOR_DEFAULT;
  488. }
  489. }
  490. void PlugIn::OnFunctionCall(int functionId, const char* itemString, POINT pt, RECT area) {
  491. std::ignore = itemString;
  492. std::ignore = pt;
  493. const auto radarTarget = this->RadarTargetSelectASEL();
  494. if (false == radarTarget.IsValid() || false == radarTarget.GetCorrelatedFlightPlan().IsValid())
  495. return;
  496. std::string callsign(radarTarget.GetCallsign());
  497. switch (static_cast<PlugIn::TagItemFunction>(functionId)) {
  498. case PlugIn::TagItemFunction::ForceToBackendMenu:
  499. {
  500. this->OpenPopupList(area, "AMAN", 1);
  501. bool inList = this->m_forcedToBackendCallsigns.cend() != std::find(this->m_forcedToBackendCallsigns.cbegin(), this->m_forcedToBackendCallsigns.cend(), callsign);
  502. this->AddPopupListElement("Add to AMAN", "", static_cast<int>(PlugIn::TagItemFunction::ForceToBackendSelect),
  503. false, EuroScopePlugIn::POPUP_ELEMENT_NO_CHECKBOX, true == inList);
  504. this->AddPopupListElement("Remove from AMAN", "", static_cast<int>(PlugIn::TagItemFunction::ForceToBackendSelect),
  505. false, EuroScopePlugIn::POPUP_ELEMENT_NO_CHECKBOX, false == inList);
  506. break;
  507. }
  508. case PlugIn::TagItemFunction::ForceToBackendSelect:
  509. {
  510. std::string destination(radarTarget.GetCorrelatedFlightPlan().GetFlightPlanData().GetDestination());
  511. std::transform(destination.begin(), destination.end(), destination.begin(), ::toupper);
  512. std::lock_guard guard(this->m_updateQueueLock);
  513. auto it = this->m_updateQueue.find(destination);
  514. if (0 == std::strcmp(itemString, "Add to AMAN")) {
  515. if (this->m_updateQueue.end() != it) {
  516. auto cIt = std::find(this->m_forcedToBackendCallsigns.cbegin(), this->m_forcedToBackendCallsigns.cend(), callsign);
  517. if (this->m_forcedToBackendCallsigns.cend() == cIt)
  518. this->m_forcedToBackendCallsigns.push_back(callsign);
  519. }
  520. }
  521. else {
  522. if (this->m_updateQueue.end() != it) {
  523. auto cIt = std::find(this->m_forcedToBackendCallsigns.begin(), this->m_forcedToBackendCallsigns.end(), callsign);
  524. if (this->m_forcedToBackendCallsigns.cend() != cIt)
  525. this->m_forcedToBackendCallsigns.erase(cIt);
  526. }
  527. }
  528. break;
  529. }
  530. default:
  531. break;
  532. }
  533. }
  534. void PlugIn::addUpdateQueue(EuroScopePlugIn::CRadarTarget& radarTarget) {
  535. std::lock_guard guard(this->m_updateQueueLock);
  536. auto forcedIt = std::find(this->m_forcedToBackendCallsigns.cbegin(), this->m_forcedToBackendCallsigns.cend(), radarTarget.GetCallsign());
  537. if (false == radarTarget.GetCorrelatedFlightPlan().GetTrackingControllerIsMe() && this->m_forcedToBackendCallsigns.cend() == forcedIt)
  538. return;
  539. std::string destination(radarTarget.GetCorrelatedFlightPlan().GetFlightPlanData().GetDestination());
  540. #pragma warning(disable: 4244)
  541. std::transform(destination.begin(), destination.end(), destination.begin(), ::toupper);
  542. #pragma warning(default: 4244)
  543. auto it = this->m_updateQueue.find(destination);
  544. if (this->m_updateQueue.end() != it)
  545. it->second.push_back(radarTarget.GetCallsign());
  546. }
  547. void PlugIn::updateInbound(EuroScopePlugIn::CRadarTarget& radarTarget) {
  548. std::lock_guard guard(this->m_inboundsQueueLock);
  549. auto it = this->m_inbounds.find(radarTarget.GetCallsign());
  550. if (this->m_inbounds.end() != it)
  551. it->second.update(radarTarget);
  552. }
  553. void PlugIn::updateInbound(EuroScopePlugIn::CFlightPlan& plan) {
  554. std::lock_guard guard(this->m_inboundsQueueLock);
  555. auto it = this->m_inbounds.find(plan.GetCorrelatedRadarTarget().GetCallsign());
  556. if (this->m_inbounds.end() != it)
  557. it->second.update(plan);
  558. }
  559. void PlugIn::OnRadarTargetPositionUpdate(EuroScopePlugIn::CRadarTarget radarTarget) {
  560. /* do nothing if the reporter is not initialized and ignore invalid targets */
  561. if (false == this->m_compatible || false == Backend::instance().initialized() || false == radarTarget.IsValid())
  562. return;
  563. /* validate the correct connection */
  564. bool validConnection = EuroScopePlugIn::CONNECTION_TYPE_DIRECT == this->GetConnectionType();
  565. validConnection |= true == this->m_sweatboxValid && EuroScopePlugIn::CONNECTION_TYPE_SWEATBOX == this->GetConnectionType();
  566. validConnection |= true == this->m_playbackValid && EuroScopePlugIn::CONNECTION_TYPE_PLAYBACK == this->GetConnectionType();
  567. if (false == validConnection)
  568. return;
  569. this->addUpdateQueue(radarTarget);
  570. this->updateInbound(radarTarget);
  571. }
  572. void PlugIn::OnFlightPlanControllerAssignedDataUpdate(EuroScopePlugIn::CFlightPlan flightPlan, int type) {
  573. /* handle only relevant changes */
  574. if (EuroScopePlugIn::CTR_DATA_TYPE_HEADING != type && EuroScopePlugIn::CTR_DATA_TYPE_DIRECT_TO != type)
  575. return;
  576. if (false == flightPlan.GetCorrelatedRadarTarget().IsValid())
  577. return;
  578. this->updateInbound(flightPlan);
  579. }
  580. void PlugIn::OnFlightPlanDisconnect(EuroScopePlugIn::CFlightPlan flightPlan) {
  581. std::string callsign(flightPlan.GetCorrelatedRadarTarget().GetCallsign());
  582. this->m_updateQueueLock.lock();
  583. auto it = std::find(this->m_forcedToBackendCallsigns.begin(), this->m_forcedToBackendCallsigns.end(), callsign);
  584. if (this->m_forcedToBackendCallsigns.end() != it)
  585. this->m_forcedToBackendCallsigns.erase(it);
  586. this->m_updateQueueLock.unlock();
  587. this->m_inboundsQueueLock.lock();
  588. auto inIt = this->m_inbounds.find(flightPlan.GetCorrelatedRadarTarget().GetCallsign());
  589. if (this->m_inbounds.end() != inIt)
  590. this->m_inbounds.erase(inIt);
  591. this->m_inboundsQueueLock.unlock();
  592. }
  593. void PlugIn::updateSequence(std::shared_ptr<aman::AircraftSequence>& sequence) {
  594. /* cleanup the inbound list */
  595. this->m_inboundsQueueLock.lock();
  596. for (auto it = this->m_inbounds.begin(); this->m_inbounds.end() != it;) {
  597. bool found = false;
  598. /* check if the inbound is part of the server */
  599. for (auto i = 0; i < sequence->sequence_size(); ++i) {
  600. if (sequence->sequence(i).callsign() == it->first) {
  601. found = true;
  602. break;
  603. }
  604. }
  605. if (false == found)
  606. it = this->m_inbounds.erase(it);
  607. else
  608. ++it;
  609. }
  610. this->m_inboundsQueueLock.unlock();
  611. /* update the inbound list */
  612. for (auto i = 0; i < sequence->sequence_size(); ++i) {
  613. const auto& inbound = sequence->sequence(i);
  614. EuroScopePlugIn::CRadarTarget target;
  615. bool found = false;
  616. for (target = this->RadarTargetSelectFirst(); true == target.IsValid(); target = this->RadarTargetSelectNext(target)) {
  617. if (target.GetCallsign() == inbound.callsign()) {
  618. found = true;
  619. break;
  620. }
  621. }
  622. if (true == found) {
  623. this->m_inboundsQueueLock.lock();
  624. auto it = this->m_inbounds.find(inbound.callsign());
  625. if (this->m_inbounds.end() != it)
  626. it->second.update(target, inbound, sequence->winddata());
  627. else
  628. this->m_inbounds.insert({ inbound.callsign(), Inbound(target, inbound, sequence->winddata()) });
  629. this->m_inboundsQueueLock.unlock();
  630. }
  631. }
  632. }
  633. void PlugIn::OnTimer(int counter) {
  634. /* cleanup the internal data */
  635. if (EuroScopePlugIn::CONNECTION_TYPE_NO == this->GetConnectionType()) {
  636. if (true == this->m_connectedToNetwork) {
  637. std::lock_guard guardUpdate(this->m_updateQueueLock);
  638. for (auto& airport : this->m_updateQueue)
  639. airport.second.clear();
  640. std::lock_guard guardInbound(this->m_inboundsQueueLock);
  641. this->m_inbounds.clear();
  642. this->m_connectedToNetwork = false;
  643. }
  644. return;
  645. }
  646. this->m_connectedToNetwork = true;
  647. if (false == this->m_compatible || false == Backend::instance().initialized() || 0 != (counter % 10))
  648. return;
  649. this->m_updateQueueLock.lock();
  650. for (auto& airport : this->m_updateQueue) {
  651. aman::AircraftUpdate update;
  652. update.set_airport(airport.first);
  653. if (0 != airport.second.size()) {
  654. for (auto target = this->RadarTargetSelectFirst(); true == target.IsValid(); target = this->RadarTargetSelectNext(target)) {
  655. auto it = std::find(airport.second.begin(), airport.second.end(), target.GetCallsign());
  656. if (airport.second.end() != it) {
  657. auto report = update.add_reports();
  658. this->generateAircraftReportMessage(target, report);
  659. }
  660. }
  661. }
  662. /* send the report and request the current sequence */
  663. auto sequence = Backend::instance().update(update);
  664. if (nullptr == sequence)
  665. this->DisplayUserMessage(PLUGIN_NAME, "ERROR", "Unable to send a new aircraft report update", true, true, true, true, true);
  666. else
  667. this->updateSequence(sequence);
  668. airport.second.clear();
  669. }
  670. this->m_updateQueueLock.unlock();
  671. }