Skip to content

Commit

Permalink
Merge pull request #551 from AndyTWF/stands-auto-request
Browse files Browse the repository at this point in the history
Stands auto request
  • Loading branch information
AndyTWF authored Dec 20, 2023
2 parents 0b0ffd0 + 7c1efbf commit 3fa0c13
Show file tree
Hide file tree
Showing 11 changed files with 1,194 additions and 25 deletions.
146 changes: 126 additions & 20 deletions src/plugin/stands/StandEventHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
#include "StandUnassignedMessage.h"
#include "api/ApiException.h"
#include "api/ApiInterface.h"
#include "api/ApiRequestFactory.h"
#include "api/ApiRequestException.h"
#include "euroscope/EuroScopeCControllerInterface.h"
#include "euroscope/EuroScopeCFlightPlanInterface.h"
#include "euroscope/EuroScopeCRadarTargetInterface.h"
#include "euroscope/EuroscopePluginLoopbackInterface.h"
#include "ownership/AirfieldServiceProviderCollection.h"
#include "tag/TagData.h"
#include "task/TaskRunnerInterface.h"

Expand All @@ -27,11 +30,14 @@ namespace UKControllerPlugin::Stands {
TaskRunnerInterface& taskRunner,
EuroscopePluginLoopbackInterface& plugin,
Integration::OutboundIntegrationEventHandler& integrationEventHandler,
std::shared_ptr<Ownership::AirfieldServiceProviderCollection> ownership,
std::set<Stand, CompareStands> stands,
int standSelectedCallbackId)
: api(api), taskRunner(taskRunner), plugin(plugin), stands(std::move(stands)),
integrationEventHandler(integrationEventHandler), standSelectedCallbackId(standSelectedCallbackId)
integrationEventHandler(integrationEventHandler), ownership(ownership),
standSelectedCallbackId(standSelectedCallbackId)
{
assert(this->ownership != nullptr && "Ownership must not be null");
}

/*
Expand Down Expand Up @@ -84,30 +90,43 @@ namespace UKControllerPlugin::Stands {
}
});
} else {
// Find the requested stand
auto stand = std::find_if(
this->stands.cbegin(), this->stands.cend(), [airfield, identifier](const Stand& stand) -> bool {
return stand.identifier == identifier && stand.airfieldCode == airfield;
});

if (stand == this->stands.cend()) {
LogInfo("Tried to assign a non-existant stand");
return "Tried to assign a non-existant stand";
if (identifier != this->autoStandMenuItem) {
return this->AssignStandInApi(callsign, airfield, identifier);
}

// Assign that stand
this->AssignStandToAircraft(callsign, *stand);
this->RequestStandFromApi(*aircraft);
}

int standId = stand->id;
this->taskRunner.QueueAsynchronousTask([this, standId, callsign]() {
try {
this->api.AssignStandToAircraft(callsign, standId);
} catch (ApiException&) {
LogError("Failed to create stand assignment for " + callsign);
}
return "";
}

auto StandEventHandler::AssignStandInApi(
const std::string& callsign, const std::string& airfield, const std::string& identifier) -> std::string
{
// Find the requested stand
auto stand = std::find_if(
this->stands.cbegin(), this->stands.cend(), [airfield, identifier](const Stand& stand) -> bool {
return stand.identifier == identifier && stand.airfieldCode == airfield;
});

if (stand == this->stands.cend()) {
LogInfo("Tried to assign a non-existant stand");
return "Tried to assign a non-existant stand";
}

// Assign that stand
this->AssignStandToAircraft(callsign, *stand);

int standId = stand->id;
auto callsignForRequest = callsign;
this->taskRunner.QueueAsynchronousTask([this, standId, callsignForRequest]() {
try {
this->api.AssignStandToAircraft(callsignForRequest, standId);
} catch (ApiException&) {
LogError("Failed to create stand assignment for " + callsignForRequest);
}
});

return "";
}

Expand Down Expand Up @@ -157,6 +176,12 @@ namespace UKControllerPlugin::Stands {
this->plugin.AddItemToPopupList(menuItem);
}

// Add the penultimate item, "AUTO" in a fixed position
menuItem.firstValue = this->autoStandMenuItem;
menuItem.secondValue = "Automatic";
menuItem.fixedPosition = true;
this->plugin.AddItemToPopupList(menuItem);

// Add the final item, no stand, in a fixed position
menuItem.firstValue = this->noStandMenuItem;
menuItem.secondValue = "None";
Expand Down Expand Up @@ -416,7 +441,7 @@ namespace UKControllerPlugin::Stands {
return {{PushEventSubscription::SUB_TYPE_CHANNEL, "private-stand-assignments"}};
}

auto StandEventHandler::LockStandMap() -> std::lock_guard<std::mutex>
auto StandEventHandler::LockStandMap() -> std::lock_guard<std::recursive_mutex>
{
return std::lock_guard(this->mapMutex);
}
Expand Down Expand Up @@ -502,4 +527,85 @@ namespace UKControllerPlugin::Stands {
success();
}
}

void StandEventHandler::RequestStandFromApi(const Euroscope::EuroScopeCFlightPlanInterface& flightplan)
{
// If EuroScope wont tell us where they are, do nothing.
if (flightplan.GetDistanceFromOrigin() == 0.0) {
return;
}

// If they're close to departure and we're providing delivery, assign a stand
if (flightplan.GetDistanceFromOrigin() < this->maxDistanceForDepartureStands &&
this->ownership->DeliveryControlProvidedByUser(flightplan.GetOrigin())) {
this->RequestDepartureStandFromApi(flightplan);
}

// Otherwise, assume they're close to arrival
if (!this->ownership->DeliveryControlProvidedByUser(flightplan.GetDestination())) {
return;
}

this->RequestArrivalStandFromApi(flightplan);
}
void StandEventHandler::RequestDepartureStandFromApi(const Euroscope::EuroScopeCFlightPlanInterface& flightplan)
{
const auto radarTarget = this->plugin.GetRadarTargetForCallsign(flightplan.GetCallsign());
if (!radarTarget) {
return;
}

this->DoApiStandRequest(
flightplan.GetCallsign(),
{
{"callsign", flightplan.GetCallsign()},
{"departure_airfield", flightplan.GetOrigin()},
{"assignment_type", "departure"},
{"latitude", radarTarget->GetPosition().m_Latitude},
{"longitude", radarTarget->GetPosition().m_Longitude},
});
}

void StandEventHandler::RequestArrivalStandFromApi(const Euroscope::EuroScopeCFlightPlanInterface& flightplan)
{
this->DoApiStandRequest(
flightplan.GetCallsign(),
{{"callsign", flightplan.GetCallsign()},
{"departure_airfield", flightplan.GetOrigin()},
{"arrival_airfield", flightplan.GetDestination()},
{"assignment_type", "arrival"},
{"aircraft_type", flightplan.GetAircraftType()}});
}

void StandEventHandler::DoApiStandRequest(const std::string& callsign, const nlohmann::json data)
{
LogDebug("Requesting stand assignment from API: " + data.dump());
const std::string requestCallsign = callsign;
ApiRequest()
.Post("stand/assignment/requestauto", data)
.Then([this, requestCallsign](const UKControllerPluginUtils::Api::Response& response) {
auto lock = this->LockStandMap();

const auto& data = response.Data();
if (!data.contains("stand_id") || !data.at("stand_id").is_number_integer()) {
LogWarning("Invalid stand assignment response " + data.dump());
return;
}

const auto standId = data.at("stand_id").get<int>();
if (!this->stands.contains(standId)) {
LogWarning("Invalid stand assignment response, bad id " + data.dump());
return;
}

const auto& stand = this->stands.find(standId);
LogInfo("API generated stand assignment " + std::to_string(standId) + " for " + requestCallsign);

this->AssignStandToAircraft(requestCallsign, *stand);
})
.Catch([requestCallsign](const UKControllerPluginUtils::Api::ApiRequestException& exception) {
LogError("Failed to request stand assignment for " + requestCallsign + ": " + exception.what());
});
}

} // namespace UKControllerPlugin::Stands
24 changes: 22 additions & 2 deletions src/plugin/stands/StandEventHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ namespace UKControllerPlugin {
namespace Euroscope {
class EuroscopePluginLoopbackInterface;
} // namespace Euroscope
namespace Ownership {
class AirfieldServiceProviderCollection;
} // namespace Ownership
namespace TaskManager {
class TaskRunnerInterface;
} // namespace TaskManager
Expand All @@ -36,6 +39,7 @@ namespace UKControllerPlugin::Stands {
TaskManager::TaskRunnerInterface& taskRunner,
Euroscope::EuroscopePluginLoopbackInterface& plugin,
Integration::OutboundIntegrationEventHandler& integrationEventHandler,
std::shared_ptr<Ownership::AirfieldServiceProviderCollection> ownership,
std::set<Stands::Stand, UKControllerPlugin::Stands::CompareStands> stands,
int standSelectedCallbackId);
[[nodiscard]] auto ActionsToProcess() const -> std::vector<Integration::MessageType> override;
Expand Down Expand Up @@ -91,14 +95,21 @@ namespace UKControllerPlugin::Stands {

private:
void AssignStandToAircraft(const std::string& callsign, const Stand& stand);
[[nodiscard]] auto
AssignStandInApi(const std::string& callsign, const std::string& airfield, const std::string& identifier)
-> std::string;
void RequestStandFromApi(const Euroscope::EuroScopeCFlightPlanInterface& flightplan);
void RequestDepartureStandFromApi(const Euroscope::EuroScopeCFlightPlanInterface& flightplan);
void RequestArrivalStandFromApi(const Euroscope::EuroScopeCFlightPlanInterface& flightplan);
void DoApiStandRequest(const std::string& callsign, const nlohmann::json data);
void UnassignStandForAircraft(const std::string& callsign);
[[nodiscard]] auto AssignmentMessageValid(const nlohmann::json& message) const -> bool;
auto CanAssignStand(UKControllerPlugin::Euroscope::EuroScopeCFlightPlanInterface& flightplan) const -> bool;
static auto UnassignmentMessageValid(const nlohmann::json& message) -> bool;
auto
GetAirfieldForStandAssignment(UKControllerPlugin::Euroscope::EuroScopeCFlightPlanInterface& flightplan) const
-> std::string;
auto LockStandMap() -> std::lock_guard<std::mutex>;
auto LockStandMap() -> std::lock_guard<std::recursive_mutex>;

// The last airfield that was used to populate the stand menu, used when we receive the callback
std::string lastAirfieldUsed;
Expand All @@ -119,17 +130,23 @@ namespace UKControllerPlugin::Stands {
std::map<std::string, int> standAssignments;

// Locks the stand assignments map to prevent concurrent edits
std::mutex mapMutex;
std::recursive_mutex mapMutex;

// Allows us to send events to our integrations
Integration::OutboundIntegrationEventHandler& integrationEventHandler;

// The ownership of airfields
std::shared_ptr<Ownership::AirfieldServiceProviderCollection> ownership;

// The id for the callback when a stand is selected
const int standSelectedCallbackId;

// Max distance from departure airport to decide whether we show departure or arrival airport stands
const int maxDistanceFromDepartureAirport = 7;

// The menu item to display for "auto" stand
const std::string autoStandMenuItem = "AUTO";

// The menu item to display for no assigned stand
const std::string noStandMenuItem = "--";

Expand All @@ -138,5 +155,8 @@ namespace UKControllerPlugin::Stands {

// Annotation box 4
const int annotationIndex = 3;

// Max distance from origin to do departure stands
const double maxDistanceForDepartureStands = 3.5;
};
} // namespace UKControllerPlugin::Stands
1 change: 1 addition & 0 deletions src/plugin/stands/StandModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ namespace UKControllerPlugin::Stands {
*container.taskRunner,
*container.plugin,
*container.integrationModuleContainer->outboundMessageHandler,
container.airfieldOwnership,
stands,
standSelectedCallbackId);

Expand Down
4 changes: 4 additions & 0 deletions src/utils/api/ApiCurlRequestFactory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ namespace UKControllerPluginUtils::Api {
auto ApiCurlRequestFactory::BuildCurlRequest(const ApiRequestData& data) const -> CurlRequest
{
CurlRequest request(urlBuilder.BuildUrl(data), data.Method());
if (!data.Body().empty()) {
request.SetBody(data.Body().dump());
}

headerApplicator.ApplyHeaders(request);
return request;
}
Expand Down
8 changes: 8 additions & 0 deletions src/utils/api/CurlApiRequestPerformer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,16 @@ namespace UKControllerPluginUtils::Api {

auto CurlApiRequestPerformer::Perform(const ApiRequestData& data) -> Response
{
const auto request = requestFactory.BuildCurlRequest(data);
LogDebug(
"CurlApiRequestPerformer: Performing cURL request with method " + std::string(data.Method()) + " to " +
data.Uri() + " with body " + data.Body().dump());

auto curlResponse = curl.MakeCurlRequest(requestFactory.BuildCurlRequest(data));
if (!ResponseSuccessful(curlResponse)) {
LogDebug(
"CurlApiRequestPerformer: Failed cURL request, status was: " +
std::to_string(curlResponse.GetStatusCode()) + " response body was " + curlResponse.GetResponse());
throw ApiRequestException(data.Uri(), static_cast<HttpStatusCode>(curlResponse.GetStatusCode()), false);
}

Expand Down
Loading

0 comments on commit 3fa0c13

Please sign in to comment.