Skip to content

Commit

Permalink
Merge pull request #38 from davewalker5/BSR-90-Flight-Details-Lookup
Browse files Browse the repository at this point in the history
BSR-90 Add flight details to the active flight dialog
  • Loading branch information
davewalker5 authored Sep 28, 2023
2 parents bab92b3 + 76c22a5 commit 49ca739
Show file tree
Hide file tree
Showing 26 changed files with 335 additions and 47 deletions.
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,10 +208,11 @@

- Possible values for the endpoint type are:

| Type | Description |
| -------- | ------------------------------------------------------------------------- |
| Airlines | Endpoint used to retrieve airline details given an airline IATA/ICAO code |
| Aircraft | Endpoint used to retrieve aircraft details given a 24-bit ICAO address |
| Type | Description |
| ------------- | --------------------------------------------------------------------------------------- |
| Airlines | Endpoint used to retrieve airline details given an airline IATA/ICAO code |
| Aircraft | Endpoint used to retrieve aircraft details given a 24-bit ICAO address |
| ActiveFlights | Endpoint used to retrieve active flight details given the aircrafts 24-bit ICAO address |

- Currently, only the AirLabs APIs are supported

Expand Down
4 changes: 2 additions & 2 deletions src/BaseStationReader.Data/BaseStationReader.Data.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PackageId>BaseStationReader.Data</PackageId>
<PackageVersion>1.29.0.0</PackageVersion>
<PackageVersion>1.30.0.0</PackageVersion>
<Authors>Dave Walker</Authors>
<Copyright>Copyright (c) Dave Walker 2023</Copyright>
<Owners>Dave Walker</Owners>
Expand All @@ -17,7 +17,7 @@
<PackageProjectUrl>https://github.com/davewalker5/ADS-B-BaseStationReader</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<ReleaseVersion>1.29.0.0</ReleaseVersion>
<ReleaseVersion>1.30.0.0</ReleaseVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PackageId>BaseStationReader.Entities</PackageId>
<PackageVersion>1.29.0.0</PackageVersion>
<PackageVersion>1.30.0.0</PackageVersion>
<Authors>Dave Walker</Authors>
<Copyright>Copyright (c) Dave Walker 2023</Copyright>
<Owners>Dave Walker</Owners>
Expand All @@ -17,7 +17,7 @@
<PackageProjectUrl>https://github.com/davewalker5/ADS-B-BaseStationReader</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<ReleaseVersion>1.29.0.0</ReleaseVersion>
<ReleaseVersion>1.30.0.0</ReleaseVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
3 changes: 2 additions & 1 deletion src/BaseStationReader.Entities/Config/ApiEndpointType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
public enum ApiEndpointType
{
Airlines,
Aircraft
Aircraft,
ActiveFlights
}
}
9 changes: 9 additions & 0 deletions src/BaseStationReader.Entities/Interfaces/IActiveFlightApi.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using BaseStationReader.Entities.Tracking;

namespace BaseStationReader.Entities.Interfaces
{
public interface IActiveFlightApi
{
Task<Dictionary<ApiProperty, string>?> LookupFlightByAircraft(string address);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ namespace BaseStationReader.Entities.Interfaces
public interface IAircraftLookupManager
{
Task<AircraftDetails?> LookupAircraft(string address);
Task<FlightDetails?> LookupActiveFlight(string address);
}
}
18 changes: 18 additions & 0 deletions src/BaseStationReader.Entities/Lookup/FlightDetails.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;

namespace BaseStationReader.Entities.Lookup
{
[ExcludeFromCodeCoverage]
public class FlightDetails
{
[Required]
public string Address { get; set; } = "";
public string? DepartureAirportIATA { get; set; }
public string? DepartureAirportICAO { get; set; }
public string? DestinationAirportIATA { get; set; }
public string? DestinationAirportICAO { get; set; }
public string? FlightNumberIATA { get; set; }
public string? FlightNumberICAO { get; set; }
}
}
8 changes: 7 additions & 1 deletion src/BaseStationReader.Entities/Tracking/ApiProperty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ public enum ApiProperty
AirlineName,
ManufacturerName,
ModelIATA,
ModelICAO
ModelICAO,
DepartureAirportIATA,
DepartureAirportICAO,
DestinationAirportIATA,
DestinationAirportICAO,
FlightIATA,
FlightICAO
}
}
74 changes: 74 additions & 0 deletions src/BaseStationReader.Logic/Api/AirLabs/AirLabsActiveFlightApi.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using BaseStationReader.Entities.Interfaces;
using BaseStationReader.Entities.Logging;
using BaseStationReader.Entities.Tracking;

namespace BaseStationReader.Logic.Api.AirLabs
{
public class AirLabsActiveFlightApi : ExternalApiBase, IActiveFlightApi
{
private readonly string _baseAddress;

public AirLabsActiveFlightApi(ITrackerLogger logger, ITrackerHttpClient client, string url, string key) : base(logger, client)
{
_baseAddress = $"{url}?api_key={key}";
}

/// <summary>
/// Lookup an active flight's details using the aircraft's ICAO 24-bit address
/// </summary>
/// <param name="address"></param>
/// <returns></returns>
public async Task<Dictionary<ApiProperty, string>?> LookupFlightByAircraft(string address)
{
Logger.LogMessage(Severity.Info, $"Looking up active flight for aircraft with address {address}");
var properties = await MakeApiRequest($"&hex={address}");
return properties;
}

/// <summary>
/// Make a request to the specified URL
/// </summary>
/// <param name="parameters"></param>
/// <returns></returns>
private async Task<Dictionary<ApiProperty, string>?> MakeApiRequest(string parameters)
{
Dictionary<ApiProperty, string>? properties = null;

// Make a request for the data from the API
var url = $"{_baseAddress}{parameters}";
var node = await SendRequest(url);

if (node != null)
{
try
{
// Extract the response element from the JSON DOM
var apiResponse = node!["response"]![0];

// Extract the values into a dictionary
properties = new()
{
{ ApiProperty.DepartureAirportIATA, apiResponse!["dep_iata"]?.GetValue<string?>() ?? "" },
{ ApiProperty.DepartureAirportICAO, apiResponse!["dep_icao"]?.GetValue<string?>() ?? "" },
{ ApiProperty.DestinationAirportIATA, apiResponse!["arr_iata"]?.GetValue<string>() ?? "" },
{ ApiProperty.DestinationAirportICAO, apiResponse!["arr_icao"]?.GetValue<string>() ?? "" },
{ ApiProperty.FlightIATA, apiResponse!["flight_iata"]?.GetValue<string>() ?? "" },
{ ApiProperty.FlightICAO, apiResponse!["flight_icao"]?.GetValue<string>() ?? "" }
};

// Log the properties dictionary
LogProperties(properties!);
}
catch (Exception ex)
{
var message = $"Error processing response: {ex.Message}";
Logger.LogMessage(Severity.Error, message);
Logger.LogException(ex);
properties = null;
}
}

return properties;
}
}
}
3 changes: 3 additions & 0 deletions src/BaseStationReader.Logic/Api/AirLabs/AirLabsAircraftApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ public AirLabsAircraftApi(ITrackerLogger logger, ITrackerHttpClient client, stri
{ ApiProperty.ModelIATA, apiResponse!["iata"]?.GetValue<string>() ?? "" },
{ ApiProperty.ModelICAO, apiResponse!["icao"]?.GetValue<string>() ?? "" }
};

// Log the properties dictionary
LogProperties(properties!);
}
catch (Exception ex)
{
Expand Down
3 changes: 3 additions & 0 deletions src/BaseStationReader.Logic/Api/AirLabs/AirLabsAirlinesApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ public AirLabsAirlinesApi(ITrackerLogger logger, ITrackerHttpClient client, stri
{ ApiProperty.AirlineICAO, apiResponse!["icao_code"]?.GetValue<string>() ?? "" },
{ ApiProperty.AirlineName, apiResponse!["name"]?.GetValue<string>() ?? "" },
};

// Log the properties dictionary
LogProperties(properties!);
}
catch (Exception ex)
{
Expand Down
2 changes: 2 additions & 0 deletions src/BaseStationReader.Logic/Api/ExternalApiBase.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using BaseStationReader.Entities.Interfaces;
using BaseStationReader.Entities.Logging;
using BaseStationReader.Entities.Tracking;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Nodes;

namespace BaseStationReader.Logic.Api
Expand Down Expand Up @@ -55,6 +56,7 @@ protected ExternalApiBase(ITrackerLogger logger, ITrackerHttpClient client)
/// Log the content of a properties dictionary resulting from an external API call
/// </summary>
/// <param name="properties"></param>
[ExcludeFromCodeCoverage]
protected void LogProperties(Dictionary<ApiProperty, string?>? properties)
{
// Check the properties dictionary isn't NULL
Expand Down
4 changes: 2 additions & 2 deletions src/BaseStationReader.Logic/BaseStationReader.Logic.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PackageId>BaseStationReader.Logic</PackageId>
<PackageVersion>1.29.0.0</PackageVersion>
<PackageVersion>1.30.0.0</PackageVersion>
<Authors>Dave Walker</Authors>
<Copyright>Copyright (c) Dave Walker 2023</Copyright>
<Owners>Dave Walker</Owners>
Expand All @@ -17,7 +17,7 @@
<PackageProjectUrl>https://github.com/davewalker5/ADS-B-BaseStationReader</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<ReleaseVersion>1.29.0.0</ReleaseVersion>
<ReleaseVersion>1.30.0.0</ReleaseVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
34 changes: 33 additions & 1 deletion src/BaseStationReader.Logic/Tracking/AircraftLookupManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,22 @@ public class AircraftLookupManager : IAircraftLookupManager
private readonly IModelManager _modelManager;
private readonly IAirlinesApi _airlinesApi;
private readonly IAircraftApi _aircraftApi;
private readonly IActiveFlightApi _flightsApi;

public AircraftLookupManager(
IAirlineManager airlineManager,
IAircraftDetailsManager detailsManager,
IModelManager modelManager,
IAirlinesApi airlinesApi,
IAircraftApi aircraftApi)
IAircraftApi aircraftApi,
IActiveFlightApi flightsApi)
{
_airlineManager = airlineManager;
_detailsManager = detailsManager;
_modelManager = modelManager;
_airlinesApi = airlinesApi;
_aircraftApi = aircraftApi;
_flightsApi = flightsApi;
}

/// <summary>
Expand Down Expand Up @@ -61,6 +64,35 @@ public AircraftLookupManager(
return details;
}

/// <summary>
/// Lookup an active flight using the aircraft's 24-bit ICAO address
/// </summary>
/// <param name="address"></param>
/// <returns></returns>
public async Task<FlightDetails?> LookupActiveFlight(string address)
{
FlightDetails? details = null;

// Use the API to look-up the flight
var properties = await _flightsApi!.LookupFlightByAircraft(address);
if (properties != null)
{
// Create a new flight details object containing the details
details = new FlightDetails
{
Address = address,
DepartureAirportIATA = properties[ApiProperty.DepartureAirportIATA],
DepartureAirportICAO = properties[ApiProperty.DepartureAirportICAO],
DestinationAirportIATA = properties[ApiProperty.DestinationAirportIATA],
DestinationAirportICAO = properties[ApiProperty.DestinationAirportICAO],
FlightNumberIATA = properties[ApiProperty.FlightIATA],
FlightNumberICAO = properties[ApiProperty.FlightICAO],
};
}

return details;
}

/// <summary>
/// Retrieve the model given the IATA and ICAO codes
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ReleaseVersion>1.29.0.0</ReleaseVersion>
<FileVersion>1.29.0.0</FileVersion>
<ProductVersion>1.29.0</ProductVersion>
<ReleaseVersion>1.30.0.0</ReleaseVersion>
<FileVersion>1.30.0.0</FileVersion>
<ProductVersion>1.30.0</ProductVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ReleaseVersion>1.29.0.0</ReleaseVersion>
<FileVersion>1.29.0.0</FileVersion>
<ProductVersion>1.29.0</ProductVersion>
<ReleaseVersion>1.30.0.0</ReleaseVersion>
<FileVersion>1.30.0.0</FileVersion>
<ProductVersion>1.30.0</ProductVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
Expand Down
59 changes: 59 additions & 0 deletions src/BaseStationReader.Tests/AirLabsActiveFlightApiTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using BaseStationReader.Entities.Interfaces;
using BaseStationReader.Entities.Tracking;
using BaseStationReader.Logic.Api.AirLabs;
using BaseStationReader.Tests.Mocks;

namespace BaseStationReader.Tests
{
[TestClass]
public class AirLabsActiveFlightApiTest
{
private const string AircraftAddress = "4CAC23";
private const string Response = "{\"response\": [{\"hex\": \"4CAC23\",\"reg_number\": \"EI-HGL\",\"flag\": \"IE\",\"lat\": 40.733487,\"lng\": -0.049688,\"alt\": 10683,\"dir\": 192.1,\"speed\": 822,\"v_speed\": -5.5,\"squawk\": \"2074\",\"flight_number\": \"4N\",\"flight_icao\": \"RYR4N\",\"flight_iata\": \"FR9073\",\"dep_icao\": \"EGCC\",\"dep_iata\": \"MAN\",\"arr_icao\": \"LEAL\",\"arr_iata\": \"ALC\",\"airline_icao\": \"RYR\",\"airline_iata\": \"FR\",\"aircraft_icao\": \"B38M\",\"updated\": 1695907120,\"status\": \"en-route\"}]}";

private MockTrackerHttpClient? _client = null;
private IActiveFlightApi? _api = null;

[TestInitialize]
public void Initialise()
{
var logger = new MockFileLogger();
_client = new MockTrackerHttpClient();
_api = new AirLabsActiveFlightApi(logger, _client, "", "");
}

[TestMethod]
public void GetActiveFlightTest()
{
_client!.AddResponse(Response);
var properties = Task.Run(() => _api!.LookupFlightByAircraft(AircraftAddress)).Result;

Assert.IsNotNull(properties);
Assert.AreEqual(6, properties.Count);
Assert.AreEqual("MAN", properties[ApiProperty.DepartureAirportIATA]);
Assert.AreEqual("EGCC", properties[ApiProperty.DepartureAirportICAO]);
Assert.AreEqual("ALC", properties[ApiProperty.DestinationAirportIATA]);
Assert.AreEqual("LEAL", properties[ApiProperty.DestinationAirportICAO]);
Assert.AreEqual("FR9073", properties[ApiProperty.FlightIATA]);
Assert.AreEqual("RYR4N", properties[ApiProperty.FlightICAO]);
}

[TestMethod]
public void InvalidJsonResponseTest()
{
_client!.AddResponse("{}");
var properties = Task.Run(() => _api!.LookupFlightByAircraft(AircraftAddress)).Result;

Assert.IsNull(properties);
}

[TestMethod]
public void ClientExceptionTest()
{
_client!.AddResponse(null);
var properties = Task.Run(() => _api!.LookupFlightByAircraft(AircraftAddress)).Result;

Assert.IsNull(properties);
}
}
}
Loading

0 comments on commit 49ca739

Please sign in to comment.