diff --git a/OpenSky.Agent.SimConnectMSFS/SimConnect.cs b/OpenSky.Agent.SimConnectMSFS/SimConnect.cs index aab1042..5c285ee 100644 --- a/OpenSky.Agent.SimConnectMSFS/SimConnect.cs +++ b/OpenSky.Agent.SimConnectMSFS/SimConnect.cs @@ -24,19 +24,19 @@ namespace OpenSky.Agent.SimConnectMSFS using OpenSkyApi; - using AircraftIdentity = OpenSky.Agent.SimConnectMSFS.Structs.AircraftIdentity; - using FuelTanks = OpenSky.Agent.SimConnectMSFS.Structs.FuelTanks; - using LandingAnalysis = OpenSky.Agent.SimConnectMSFS.Structs.LandingAnalysis; - using PayloadStations = OpenSky.Agent.SimConnectMSFS.Structs.PayloadStations; - using PrimaryTracking = OpenSky.Agent.SimConnectMSFS.Structs.PrimaryTracking; - using SecondaryTracking = OpenSky.Agent.SimConnectMSFS.Structs.SecondaryTracking; - using Simulator = OpenSky.Agent.Simulator.Simulator; - using SlewAircraftIntoPosition = OpenSky.Agent.SimConnectMSFS.Structs.SlewAircraftIntoPosition; - using WeightAndBalance = OpenSky.Agent.SimConnectMSFS.Structs.WeightAndBalance; + using AircraftIdentity = Structs.AircraftIdentity; + using FuelTanks = Structs.FuelTanks; + using LandingAnalysis = Structs.LandingAnalysis; + using PayloadStations = Structs.PayloadStations; + using PrimaryTracking = Structs.PrimaryTracking; + using SecondaryTracking = Structs.SecondaryTracking; + using Simulator = Simulator.Simulator; + using SlewAircraftIntoPosition = Structs.SlewAircraftIntoPosition; + using WeightAndBalance = Structs.WeightAndBalance; /// ------------------------------------------------------------------------------------------------- /// - /// Simconnect client. + /// Simconnect client for Microsoft Flight Simulator 2020. /// /// /// sushi.at, 13/03/2021. @@ -174,7 +174,7 @@ public override void SetAircraftRegistry(string registry) if (this.fsConnect.Connected) { var planeRegistry = new PlaneRegistry { AtcID = registry }; - this.fsConnect.UpdateData(Requests.PlaneRegistry, planeRegistry); + this.fsConnect.UpdateData(Requests.AircraftRegistry, planeRegistry); } else { @@ -527,10 +527,10 @@ private void FsDataReceived(object sender, FsDataReceivedEventArgs e) this.LastReceivedTimes[Requests.PayloadStations] = DateTime.UtcNow; } - if (simConnectObject is AircraftIdentity isPlaneIdentity) + if (simConnectObject is AircraftIdentity isAircraftIdentity) { - this.AircraftIdentity = isPlaneIdentity.Convert(); - this.LastReceivedTimes[Requests.PlaneIdentity] = DateTime.UtcNow; + this.AircraftIdentity = isAircraftIdentity.Convert(); + this.LastReceivedTimes[Requests.AircraftIdentity] = DateTime.UtcNow; new Thread(this.ProcessAircraftIdentity) { Name = "OpenSky.ProcessAircraftIdentity" }.Start(); } @@ -591,11 +591,11 @@ private void ReadFromSimconnect() this.fsConnect.RegisterDataDefinition(Requests.Secondary, SecondaryTrackingDefinition.Definition); this.fsConnect.RegisterDataDefinition(Requests.FuelTanks, FuelTanksDefinition.Definition); this.fsConnect.RegisterDataDefinition(Requests.PayloadStations, PayloadStationsDefinition.Definition); - this.fsConnect.RegisterDataDefinition(Requests.PlaneIdentity, AircraftIdentityDefinition.Definition); + this.fsConnect.RegisterDataDefinition(Requests.AircraftIdentity, AircraftIdentityDefinition.Definition); this.fsConnect.RegisterDataDefinition(Requests.WeightAndBalance, WeightAndBalanceDefinition.Definition); this.fsConnect.RegisterDataDefinition(Requests.LandingAnalysis, LandingAnalysisDefinition.Definition); this.fsConnect.RegisterDataDefinition(Requests.SlewPlaneIntoPosition, SlewAircraftIntoPositionDefinition.Definition); - this.fsConnect.RegisterDataDefinition(Requests.PlaneRegistry, PlaneRegistryDefinition.Definition); + this.fsConnect.RegisterDataDefinition(Requests.AircraftRegistry, PlaneRegistryDefinition.Definition); // Register client events this.fsConnect.MapClientEventToSimEvent(ClientEvents.SetTime, ClientEvents.SetZuluYears, "ZULU_YEAR_SET"); diff --git a/OpenSky.Agent.SimConnectMSFS/Structs/PayloadStations.cs b/OpenSky.Agent.SimConnectMSFS/Structs/PayloadStations.cs index 875f7f7..d18453f 100644 --- a/OpenSky.Agent.SimConnectMSFS/Structs/PayloadStations.cs +++ b/OpenSky.Agent.SimConnectMSFS/Structs/PayloadStations.cs @@ -402,7 +402,7 @@ public static Simulator.Models.PayloadStations Convert(this PayloadStations stat Weight17 = stations.Weight17, Weight18 = stations.Weight18, Weight19 = stations.Weight19, - Weight20 = stations.Weight20, + Weight20 = stations.Weight20 }; } @@ -464,7 +464,7 @@ public static PayloadStations ConvertBack(this Simulator.Models.PayloadStations Weight17 = stations.Weight17, Weight18 = stations.Weight18, Weight19 = stations.Weight19, - Weight20 = stations.Weight20, + Weight20 = stations.Weight20 }; } } diff --git a/OpenSky.Agent.SimConnectMSFS/Structs/SlewAircraftIntoPosition.cs b/OpenSky.Agent.SimConnectMSFS/Structs/SlewAircraftIntoPosition.cs index 7bfdf34..c94a866 100644 --- a/OpenSky.Agent.SimConnectMSFS/Structs/SlewAircraftIntoPosition.cs +++ b/OpenSky.Agent.SimConnectMSFS/Structs/SlewAircraftIntoPosition.cs @@ -38,6 +38,13 @@ public struct SlewAircraftIntoPosition /// ------------------------------------------------------------------------------------------------- public double Longitude { get; set; } + /// ------------------------------------------------------------------------------------------------- + /// + /// The altitude in feet. + /// + /// ------------------------------------------------------------------------------------------------- + public double Altitude { get; set; } + /// ------------------------------------------------------------------------------------------------- /// /// The radio height in feet. @@ -118,6 +125,7 @@ public static Agent.Simulator.Models.SlewAircraftIntoPosition Convert(this SlewA { Latitude = position.Latitude, Longitude = position.Longitude, + Altitude = position.Altitude, RadioHeight = position.RadioHeight, Heading = position.Heading, AirspeedTrue = position.AirspeedTrue, @@ -148,6 +156,7 @@ public static SlewAircraftIntoPosition ConvertBack(this Agent.Simulator.Models.S { Latitude = position.Latitude, Longitude = position.Longitude, + Altitude = position.Altitude, RadioHeight = position.RadioHeight, Heading = position.Heading, AirspeedTrue = position.AirspeedTrue, @@ -179,6 +188,7 @@ public static class SlewAircraftIntoPositionDefinition { new SimVar("PLANE LATITUDE", "Degrees", SIMCONNECT_DATATYPE.FLOAT64), new SimVar("PLANE LONGITUDE", "Degrees", SIMCONNECT_DATATYPE.FLOAT64), + new SimVar("PLANE ALTITUDE", "Feet", SIMCONNECT_DATATYPE.FLOAT64), new SimVar("PLANE ALT ABOVE GROUND", "Feet", SIMCONNECT_DATATYPE.FLOAT64), new SimVar("PLANE HEADING DEGREES MAGNETIC", "Degrees", SIMCONNECT_DATATYPE.FLOAT64), new SimVar("AIRSPEED TRUE", "Knots", SIMCONNECT_DATATYPE.FLOAT64), diff --git a/OpenSky.Agent.Simulator/Enums/Requests.cs b/OpenSky.Agent.Simulator/Enums/Requests.cs index ed82159..25cfc19 100644 --- a/OpenSky.Agent.Simulator/Enums/Requests.cs +++ b/OpenSky.Agent.Simulator/Enums/Requests.cs @@ -40,9 +40,9 @@ public enum Requests : uint PayloadStations = 3, /// - /// Plane identity request + /// Aircraft identity request /// - PlaneIdentity = 4, + AircraftIdentity = 4, /// /// Weight and balance request @@ -60,8 +60,8 @@ public enum Requests : uint SlewPlaneIntoPosition = 7, /// - /// Plane registry request + /// Aircraft registry request /// - PlaneRegistry = 8 + AircraftRegistry = 8 } } \ No newline at end of file diff --git a/OpenSky.Agent.Simulator/Models/FuelTanks.cs b/OpenSky.Agent.Simulator/Models/FuelTanks.cs index 2b75fde..7875edb 100644 --- a/OpenSky.Agent.Simulator/Models/FuelTanks.cs +++ b/OpenSky.Agent.Simulator/Models/FuelTanks.cs @@ -235,6 +235,32 @@ public double TotalQuantity } } + /// ------------------------------------------------------------------------------------------------- + /// + /// Updates the capacities from a modified dictionary. + /// + /// + /// sushi.at, 10/02/2022. + /// + /// + /// The modified dictionary containing the new capacity values. + /// + /// ------------------------------------------------------------------------------------------------- + public void UpdateCapactiesFromDictionary(Dictionary modifiedDictionary) + { + this.FuelTankCenterCapacity = modifiedDictionary[FuelTank.Center]; + this.FuelTankCenter2Capacity = modifiedDictionary[FuelTank.Center2]; + this.FuelTankCenter3Capacity = modifiedDictionary[FuelTank.Center3]; + this.FuelTankLeftMainCapacity = modifiedDictionary[FuelTank.LeftMain]; + this.FuelTankLeftAuxCapacity = modifiedDictionary[FuelTank.LeftAux]; + this.FuelTankLeftTipCapacity = modifiedDictionary[FuelTank.LeftTip]; + this.FuelTankRightMainCapacity = modifiedDictionary[FuelTank.RightMain]; + this.FuelTankRightAuxCapacity = modifiedDictionary[FuelTank.RightAux]; + this.FuelTankRightTipCapacity = modifiedDictionary[FuelTank.RightTip]; + this.FuelTankExternal1Capacity = modifiedDictionary[FuelTank.External1]; + this.FuelTankExternal2Capacity = modifiedDictionary[FuelTank.External2]; + } + /// ------------------------------------------------------------------------------------------------- /// /// Updates the quantities from a modified dictionary. diff --git a/OpenSky.Agent.Simulator/Models/SlewAircraftIntoPosition.cs b/OpenSky.Agent.Simulator/Models/SlewAircraftIntoPosition.cs index 961be38..a6fcd83 100644 --- a/OpenSky.Agent.Simulator/Models/SlewAircraftIntoPosition.cs +++ b/OpenSky.Agent.Simulator/Models/SlewAircraftIntoPosition.cs @@ -25,6 +25,13 @@ public class SlewAircraftIntoPosition /// ------------------------------------------------------------------------------------------------- public double AirspeedTrue { get; set; } + /// ------------------------------------------------------------------------------------------------- + /// + /// Gets or sets the altitude. + /// + /// ------------------------------------------------------------------------------------------------- + public double Altitude { get; set; } + /// ------------------------------------------------------------------------------------------------- /// /// The bank angle in degrees. @@ -109,6 +116,7 @@ public static SlewAircraftIntoPosition FromPrimaryTracking(PrimaryTracking prima Latitude = primary.Latitude, Longitude = primary.Longitude, RadioHeight = primary.RadioHeight, + Altitude = primary.Altitude, Heading = primary.Heading, AirspeedTrue = primary.AirspeedTrue, PitchAngle = primary.PitchAngle, diff --git a/OpenSky.Agent.Simulator/OpenSky.Agent.Simulator.csproj b/OpenSky.Agent.Simulator/OpenSky.Agent.Simulator.csproj index c3dc59c..57579ef 100644 --- a/OpenSky.Agent.Simulator/OpenSky.Agent.Simulator.csproj +++ b/OpenSky.Agent.Simulator/OpenSky.Agent.Simulator.csproj @@ -128,6 +128,7 @@ + diff --git a/OpenSky.Agent.Simulator/Simulator.Flight.cs b/OpenSky.Agent.Simulator/Simulator.Flight.cs index ce15ac9..2b0cae7 100644 --- a/OpenSky.Agent.Simulator/Simulator.Flight.cs +++ b/OpenSky.Agent.Simulator/Simulator.Flight.cs @@ -28,7 +28,7 @@ namespace OpenSky.Agent.Simulator using OpenSkyApi; using PositionReport = OpenSkyApi.PositionReport; - using TrackingEventMarker = OpenSky.Agent.Simulator.Models.TrackingEventMarker; + using TrackingEventMarker = Models.TrackingEventMarker; /// ------------------------------------------------------------------------------------------------- /// @@ -261,13 +261,13 @@ public Flight Flight } }; - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.Fuel].Expected = + this.TrackingConditions[(int)Models.TrackingConditions.Fuel].Expected = $"{value.FuelGallons:F1} gal, {value.FuelGallons * 3.78541:F1} liters ▶ {value.FuelGallons * value.Aircraft.Type.FuelWeightPerGallon:F1} lbs, {value.FuelGallons * value.Aircraft.Type.FuelWeightPerGallon * 0.453592:F1} kg"; - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.Payload].Expected = $"{value.PayloadPounds:F1} lbs, {value.PayloadPounds * 0.453592:F1} kg"; - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.PlaneModel].Expected = $"{value.Aircraft.Type.Name} (v{value.Aircraft.Type.VersionNumber})"; + this.TrackingConditions[(int)Models.TrackingConditions.Payload].Expected = $"{value.PayloadPounds:F1} lbs, {value.PayloadPounds * 0.453592:F1} kg"; + this.TrackingConditions[(int)Models.TrackingConditions.PlaneModel].Expected = $"{value.Aircraft.Type.Name} (v{value.Aircraft.Type.VersionNumber})"; - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.Fuel].AutoSet = !value.Aircraft.Type.RequiresManualFuelling; - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.Payload].AutoSet = !value.Aircraft.Type.RequiresManualLoading; + this.TrackingConditions[(int)Models.TrackingConditions.Fuel].AutoSet = !value.Aircraft.Type.RequiresManualFuelling; + this.TrackingConditions[(int)Models.TrackingConditions.Payload].AutoSet = !value.Aircraft.Type.RequiresManualLoading; if (!value.Resume) { @@ -332,12 +332,13 @@ public Flight Flight PitchAngle = value.PitchAngle, OnGround = value.OnGround, AirspeedTrue = value.AirspeedTrue ?? 0, + Altitude = value.Altitude ?? 0, RadioHeight = value.RadioHeight ?? 0, VerticalSpeedSeconds = value.VerticalSpeedSeconds } }; - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.Fuel].Expected = + this.TrackingConditions[(int)Models.TrackingConditions.Fuel].Expected = $"{this.flightLoadingTempModels.FuelTanks.TotalQuantity:F1} gal, {this.flightLoadingTempModels.FuelTanks.TotalQuantity * 3.78541:F1} liters ▶ {this.flightLoadingTempModels.FuelTanks.TotalQuantity * value.Aircraft.Type.FuelWeightPerGallon:F1} lbs, {this.flightLoadingTempModels.FuelTanks.TotalQuantity * value.Aircraft.Type.FuelWeightPerGallon * 0.453592:F1} kg"; } } @@ -531,7 +532,7 @@ public void StopTracking(bool resumeLater) this.TrackingConditions[(int)condition].Reset(); } - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.RealismSettings].Expected = "No slew, No unlimited fuel,\r\nCrash detection, SimRate=1"; + this.TrackingConditions[(int)Models.TrackingConditions.RealismSettings].Expected = "No slew, No unlimited fuel,\r\nCrash detection, SimRate=0 or 1"; } if (!resumeLater) diff --git a/OpenSky.Agent.Simulator/Simulator.Process.FlightPhases.cs b/OpenSky.Agent.Simulator/Simulator.Process.FlightPhases.cs index cee0c20..c01ba76 100644 --- a/OpenSky.Agent.Simulator/Simulator.Process.FlightPhases.cs +++ b/OpenSky.Agent.Simulator/Simulator.Process.FlightPhases.cs @@ -202,7 +202,7 @@ private void TransitionFlightPhase() var player = new SoundPlayer(assembly.GetManifestResourceStream("OpenSky.Agent.Resources.OSnegative.wav")); player.Play(); SpeechSoundPacks.Instance.PlaySpeechEvent(SpeechEvent.AbortedSimMainMenu); - this.StopTracking(false); + this.StopTracking(true); } } diff --git a/OpenSky.Agent.Simulator/Simulator.Process.Systems.cs b/OpenSky.Agent.Simulator/Simulator.Process.Systems.cs index fef07b6..9b9b5ea 100644 --- a/OpenSky.Agent.Simulator/Simulator.Process.Systems.cs +++ b/OpenSky.Agent.Simulator/Simulator.Process.Systems.cs @@ -103,7 +103,7 @@ private void MonitorPrimarySystems(ProcessPrimaryTracking ppt) var player = new SoundPlayer(assembly.GetManifestResourceStream("OpenSky.Agent.Resources.OSnegative.wav")); player.Play(); SpeechSoundPacks.Instance.PlaySpeechEvent(SpeechEvent.AbortedSlew); - this.StopTracking(false); + this.StopTracking(true); } // Teleport to another position? @@ -119,7 +119,7 @@ private void MonitorPrimarySystems(ProcessPrimaryTracking ppt) var player = new SoundPlayer(assembly.GetManifestResourceStream("OpenSky.Agent.Resources.OSnegative.wav")); player.Play(); SpeechSoundPacks.Instance.PlaySpeechEvent(SpeechEvent.AbortedTeleport); - this.StopTracking(false); + this.StopTracking(true); } } } @@ -154,7 +154,7 @@ private void MonitorSecondarySystems(ProcessSecondaryTracking pst) var player = new SoundPlayer(assembly.GetManifestResourceStream("OpenSky.Agent.Resources.OSnegative.wav")); player.PlaySync(); SpeechSoundPacks.Instance.PlaySpeechEvent(SpeechEvent.AbortedEnginesGroundHandling); - this.StopTracking(false); + this.StopTracking(true); } // Was the beacon light off when the engine was started? @@ -231,7 +231,7 @@ private void MonitorSecondarySystems(ProcessSecondaryTracking pst) var player = new SoundPlayer(assembly.GetManifestResourceStream("OpenSky.Agent.Resources.OSnegative.wav")); player.PlaySync(); SpeechSoundPacks.Instance.PlaySpeechEvent(SpeechEvent.AbortedPushbackGroundHandling); - this.StopTracking(false); + this.StopTracking(true); } // Pushback start? @@ -345,7 +345,7 @@ private void MonitorSecondarySystems(ProcessSecondaryTracking pst) var player = new SoundPlayer(assembly.GetManifestResourceStream("OpenSky.Agent.Resources.OSnegative.wav")); player.PlaySync(); SpeechSoundPacks.Instance.PlaySpeechEvent(SpeechEvent.AbortedTimeBackwards); - this.StopTracking(false); + this.StopTracking(true); } if (timeDelta.TotalSeconds > 30) @@ -355,7 +355,7 @@ private void MonitorSecondarySystems(ProcessSecondaryTracking pst) var player = new SoundPlayer(assembly.GetManifestResourceStream("OpenSky.Agent.Resources.OSnegative.wav")); player.PlaySync(); SpeechSoundPacks.Instance.PlaySpeechEvent(SpeechEvent.AbortedTimeChanged); - this.StopTracking(false); + this.StopTracking(true); } } } diff --git a/OpenSky.Agent.Simulator/Simulator.Process.cs b/OpenSky.Agent.Simulator/Simulator.Process.cs index c7b6264..17693be 100644 --- a/OpenSky.Agent.Simulator/Simulator.Process.cs +++ b/OpenSky.Agent.Simulator/Simulator.Process.cs @@ -118,36 +118,36 @@ protected void MonitorTrackingStartConditions(SecondaryTracking secondary) { if (this.Flight != null && this.TrackingStatus == TrackingStatus.Preparing || this.TrackingStatus == TrackingStatus.Resuming) { - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.DateTime].Expected = $"{DateTime.UtcNow.AddHours(this.Flight?.UtcOffset ?? 0):HH:mm dd.MM.yyyy}"; - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.DateTime].Current = $"{secondary.UtcDateTime:HH:mm dd.MM.yyyy}"; - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.PlaneModel].Current = this.AircraftIdentity.Type; + this.TrackingConditions[(int)Models.TrackingConditions.DateTime].Expected = $"{DateTime.UtcNow.AddHours(this.Flight?.UtcOffset ?? 0):HH:mm dd.MM.yyyy}"; + this.TrackingConditions[(int)Models.TrackingConditions.DateTime].Current = $"{secondary.UtcDateTime:HH:mm dd.MM.yyyy}"; + this.TrackingConditions[(int)Models.TrackingConditions.PlaneModel].Current = this.AircraftIdentity.Type; - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.DateTime].ConditionMet = - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.DateTime].AutoSet || Math.Abs((DateTime.UtcNow.AddHours(this.Flight?.UtcOffset ?? 0) - secondary.UtcDateTime).TotalMinutes) < 1; - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.PlaneModel].ConditionMet = this.Flight?.Aircraft.Type.MatchesAircraftInSimulator() ?? false; + this.TrackingConditions[(int)Models.TrackingConditions.DateTime].ConditionMet = + this.TrackingConditions[(int)Models.TrackingConditions.DateTime].AutoSet || Math.Abs((DateTime.UtcNow.AddHours(this.Flight?.UtcOffset ?? 0) - secondary.UtcDateTime).TotalMinutes) < 1; + this.TrackingConditions[(int)Models.TrackingConditions.PlaneModel].ConditionMet = this.Flight?.Aircraft.Type.MatchesAircraftInSimulator() ?? false; if (this.TrackingStatus == TrackingStatus.Preparing) { - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.Fuel].Enabled = true; - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.Payload].Enabled = true; + this.TrackingConditions[(int)Models.TrackingConditions.Fuel].Enabled = true; + this.TrackingConditions[(int)Models.TrackingConditions.Payload].Enabled = true; - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.Fuel].Current = + this.TrackingConditions[(int)Models.TrackingConditions.Fuel].Current = $"{this.WeightAndBalance.FuelTotalQuantity:F1} gal, {this.WeightAndBalance.FuelTotalQuantity * 3.78541:F1} liters ▶ {this.WeightAndBalance.FuelTotalQuantity * this.Flight?.Aircraft.Type.FuelWeightPerGallon:F1} lbs, {this.WeightAndBalance.FuelTotalQuantity * this.Flight?.Aircraft.Type.FuelWeightPerGallon * 0.453592:F1} kg"; - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.Payload].Current = $"{this.PayloadStations.TotalWeight:F1} lbs, {this.PayloadStations.TotalWeight * 0.453592:F1} kg"; + this.TrackingConditions[(int)Models.TrackingConditions.Payload].Current = $"{this.PayloadStations.TotalWeight:F1} lbs, {this.PayloadStations.TotalWeight * 0.453592:F1} kg"; - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.Fuel].ConditionMet = - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.Fuel].AutoSet || Math.Abs((int)(this.WeightAndBalance.FuelTotalQuantity - this.Flight?.FuelGallons ?? 0)) < 0.27; // Allow roughly 1 liter of margin + this.TrackingConditions[(int)Models.TrackingConditions.Fuel].ConditionMet = + this.TrackingConditions[(int)Models.TrackingConditions.Fuel].AutoSet || Math.Abs((int)(this.WeightAndBalance.FuelTotalQuantity - this.Flight?.FuelGallons ?? 0)) < 0.27; // Allow roughly 1 liter of margin - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.Payload].ConditionMet = - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.Payload].AutoSet || Math.Abs((int)(this.PayloadStations.TotalWeight - this.Flight?.PayloadPounds ?? 0)) < 2.2; // Allow 1 kg of margin + this.TrackingConditions[(int)Models.TrackingConditions.Payload].ConditionMet = + this.TrackingConditions[(int)Models.TrackingConditions.Payload].AutoSet || Math.Abs((int)(this.PayloadStations.TotalWeight - this.Flight?.PayloadPounds ?? 0)) < 2.2; // Allow 1 kg of margin - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.RealismSettings].Expected = "No slew, No unlimited fuel,\r\nCrash detection, SimRate=1"; - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.RealismSettings].ConditionMet = - !this.PrimaryTracking.SlewActive && !secondary.UnlimitedFuel && secondary.CrashDetection && Math.Abs((int)(this.PrimaryTracking.SimulationRate - 1)) == 0; + this.TrackingConditions[(int)Models.TrackingConditions.RealismSettings].Expected = "No slew, No unlimited fuel,\r\nCrash detection, SimRate=0 or 1"; + this.TrackingConditions[(int)Models.TrackingConditions.RealismSettings].ConditionMet = + !this.PrimaryTracking.SlewActive && !secondary.UnlimitedFuel && secondary.CrashDetection && ((int)this.PrimaryTracking.SimulationRate == 0 || (int)this.PrimaryTracking.SimulationRate == 1); - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.Location].Current = + this.TrackingConditions[(int)Models.TrackingConditions.Location].Current = $"{this.PrimaryTracking.GeoCoordinate.GetDistanceTo(new GeoCoordinate(this.Flight?.Origin.Latitude ?? 0, this.Flight?.Origin.Longitude ?? 0)) / 1000:F2} km from starting location - {(this.PrimaryTracking.OnGround ? "On ground" : "Airborne")}"; - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.Location].ConditionMet = + this.TrackingConditions[(int)Models.TrackingConditions.Location].ConditionMet = this.PrimaryTracking.GeoCoordinate.GetDistanceTo(new GeoCoordinate(this.Flight?.Origin.Latitude ?? 0, this.Flight?.Origin.Longitude ?? 0)) < 5000; } @@ -155,28 +155,28 @@ protected void MonitorTrackingStartConditions(SecondaryTracking secondary) { if (this.Flight?.Aircraft.Type.RequiresManualFuelling != true) { - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.Fuel].Enabled = false; + this.TrackingConditions[(int)Models.TrackingConditions.Fuel].Enabled = false; } else { - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.Fuel].Enabled = true; - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.Fuel].Current = + this.TrackingConditions[(int)Models.TrackingConditions.Fuel].Enabled = true; + this.TrackingConditions[(int)Models.TrackingConditions.Fuel].Current = $"{this.WeightAndBalance.FuelTotalQuantity:F1} gal, {this.WeightAndBalance.FuelTotalQuantity * 3.78541:F1} liters ▶ {this.WeightAndBalance.FuelTotalQuantity * this.Flight?.Aircraft.Type.FuelWeightPerGallon:F1} lbs, {this.WeightAndBalance.FuelTotalQuantity * this.Flight?.Aircraft.Type.FuelWeightPerGallon * 0.453592:F1} kg"; - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.Fuel].ConditionMet = - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.Fuel].AutoSet || + this.TrackingConditions[(int)Models.TrackingConditions.Fuel].ConditionMet = + this.TrackingConditions[(int)Models.TrackingConditions.Fuel].AutoSet || Math.Abs(this.WeightAndBalance.FuelTotalQuantity - this.flightLoadingTempModels.FuelTanks.TotalQuantity) < 0.81; // Allow roughly 3 liters of margin, as it can be very hard to get this right otherwise } if (this.Flight?.Aircraft.Type.RequiresManualLoading != true) { - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.Payload].Enabled = false; + this.TrackingConditions[(int)Models.TrackingConditions.Payload].Enabled = false; } else { - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.Payload].Enabled = true; - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.Payload].Current = $"{this.PayloadStations.TotalWeight:F1} lbs, {this.PayloadStations.TotalWeight * 0.453592:F1} kg"; - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.Payload].ConditionMet = - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.Payload].AutoSet || Math.Abs((double)(this.PayloadStations.TotalWeight - this.Flight?.PayloadPounds)) < 2.2; // Allow 1 kg of margin + this.TrackingConditions[(int)Models.TrackingConditions.Payload].Enabled = true; + this.TrackingConditions[(int)Models.TrackingConditions.Payload].Current = $"{this.PayloadStations.TotalWeight:F1} lbs, {this.PayloadStations.TotalWeight * 0.453592:F1} kg"; + this.TrackingConditions[(int)Models.TrackingConditions.Payload].ConditionMet = + this.TrackingConditions[(int)Models.TrackingConditions.Payload].AutoSet || Math.Abs((double)(this.PayloadStations.TotalWeight - this.Flight?.PayloadPounds)) < 2.2; // Allow 1 kg of margin } var currentLocation = $"{this.PrimaryTracking.GeoCoordinate.GetDistanceTo(this.flightLoadingTempModels?.SlewAircraftIntoPosition.GeoCoordinate ?? new GeoCoordinate(0, 0, 0)) / 1000:F2} km from resume location"; @@ -184,11 +184,11 @@ protected void MonitorTrackingStartConditions(SecondaryTracking secondary) currentLocation += $"\r\nLongitude: {this.flightLoadingTempModels?.SlewAircraftIntoPosition.Longitude:F4}"; currentLocation += $"\r\nAltitude (AGL): {this.flightLoadingTempModels?.SlewAircraftIntoPosition.RadioHeight:F0}"; - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.RealismSettings].Expected = "No unlimited fuel,\r\nCrash detection, SimRate=1"; - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.RealismSettings].ConditionMet = !secondary.UnlimitedFuel && secondary.CrashDetection && Math.Abs((int)(this.PrimaryTracking.SimulationRate - 1)) == 0; + this.TrackingConditions[(int)Models.TrackingConditions.RealismSettings].Expected = "No unlimited fuel,\r\nCrash detection, SimRate=0 or 1"; + this.TrackingConditions[(int)Models.TrackingConditions.RealismSettings].ConditionMet = !secondary.UnlimitedFuel && secondary.CrashDetection && ((int)this.PrimaryTracking.SimulationRate == 0 || (int)this.PrimaryTracking.SimulationRate == 1); - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.Location].Current = currentLocation; - this.TrackingConditions[(int)Agent.Simulator.Models.TrackingConditions.Location].ConditionMet = + this.TrackingConditions[(int)Models.TrackingConditions.Location].Current = currentLocation; + this.TrackingConditions[(int)Models.TrackingConditions.Location].ConditionMet = (this.PrimaryTracking.GeoCoordinate.GetDistanceTo(this.flightLoadingTempModels?.SlewAircraftIntoPosition.GeoCoordinate ?? new GeoCoordinate(0, 0, 0)) < 100) && Math.Abs((int)(this.PrimaryTracking.RadioHeight - this.flightLoadingTempModels?.SlewAircraftIntoPosition.RadioHeight ?? -1000)) < 50; } @@ -253,7 +253,7 @@ protected void ProcessPayloadStations(PayloadStations older, PayloadStations new var player = new SoundPlayer(assembly.GetManifestResourceStream("OpenSky.Agent.Resources.OSnegative.wav")); player.PlaySync(); SpeechSoundPacks.Instance.PlaySpeechEvent(SpeechEvent.AbortedPayloadChange); - this.StopTracking(false); + this.StopTracking(true); } } } @@ -370,6 +370,12 @@ private void ProcessPrimaryTracking() // Are we close to landing? this.SampleRates[Requests.LandingAnalysis] = this.WasAirborne && ppt.New.RadioHeight < 500 ? 25 : 500; this.OnPropertyChanged(nameof(this.SampleRates)); + + // Was the sim paused/un-paused? + if (Math.Abs(ppt.Old.SimulationRate - ppt.New.SimulationRate) > 0.1) + { + this.OnPropertyChanged(nameof(this.IsPaused)); + } } else { diff --git a/OpenSky.Agent.Simulator/Simulator.cs b/OpenSky.Agent.Simulator/Simulator.cs index e72442b..793a2fc 100644 --- a/OpenSky.Agent.Simulator/Simulator.cs +++ b/OpenSky.Agent.Simulator/Simulator.cs @@ -115,7 +115,7 @@ protected Simulator(OpenSkyService openSkyServiceInstance) { Requests.Secondary, 500 }, { Requests.FuelTanks, 15000 }, { Requests.PayloadStations, 15000 }, - { Requests.PlaneIdentity, 15000 }, + { Requests.AircraftIdentity, 15000 }, { Requests.WeightAndBalance, 15000 }, { Requests.LandingAnalysis, 500 } }; @@ -128,12 +128,12 @@ protected Simulator(OpenSkyService openSkyServiceInstance) this.TrackingConditions = new Dictionary { - { (int)Agent.Simulator.Models.TrackingConditions.DateTime, new TrackingCondition { AutoSet = true } }, - { (int)Agent.Simulator.Models.TrackingConditions.Fuel, new TrackingCondition { AutoSet = true } }, - { (int)Agent.Simulator.Models.TrackingConditions.Payload, new TrackingCondition { AutoSet = true } }, - { (int)Agent.Simulator.Models.TrackingConditions.PlaneModel, new TrackingCondition() }, - { (int)Agent.Simulator.Models.TrackingConditions.RealismSettings, new TrackingCondition { Expected = "No slew, No unlimited fuel,\r\nCrash detection, SimRate=1" } }, - { (int)Agent.Simulator.Models.TrackingConditions.Location, new TrackingCondition() } + { (int)Models.TrackingConditions.DateTime, new TrackingCondition { AutoSet = true } }, + { (int)Models.TrackingConditions.Fuel, new TrackingCondition { AutoSet = true } }, + { (int)Models.TrackingConditions.Payload, new TrackingCondition { AutoSet = true } }, + { (int)Models.TrackingConditions.PlaneModel, new TrackingCondition() }, + { (int)Models.TrackingConditions.RealismSettings, new TrackingCondition { Expected = "No slew, No unlimited fuel,\r\nCrash detection, SimRate=0 or 1" } }, + { (int)Models.TrackingConditions.Location, new TrackingCondition() } }; // Start our worker threads diff --git a/OpenSky.Agent.Simulator/Tools/AircraftRegistryExtensions.cs b/OpenSky.Agent.Simulator/Tools/AircraftRegistryExtensions.cs new file mode 100644 index 0000000..a268225 --- /dev/null +++ b/OpenSky.Agent.Simulator/Tools/AircraftRegistryExtensions.cs @@ -0,0 +1,43 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// OpenSky project 2021-2022 +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace OpenSky.Agent.Simulator.Tools +{ + /// ------------------------------------------------------------------------------------------------- + /// + /// Extension methods for removing simulator prefixes from aircraft registries. + /// + /// + /// sushi.at, 09/02/2022. + /// + /// ------------------------------------------------------------------------------------------------- + public static class AircraftRegistryExtensions + { + /// ------------------------------------------------------------------------------------------------- + /// + /// A string extension method that removes the simulation prefix from an aircraft registry. + /// + /// + /// sushi.at, 09/02/2022. + /// + /// + /// The registry to act on. + /// + /// + /// The registry without the prefix. + /// + /// ------------------------------------------------------------------------------------------------- + public static string RemoveSimPrefix(this string registry) + { + if (registry is { Length: >= 3 } && registry[1] == '.') + { + return registry.Substring(2); + } + + return registry; + } + } +} diff --git a/OpenSky.Agent.UdpXPlane11/Models/AircraftIdentityDataRef.cs b/OpenSky.Agent.UdpXPlane11/Models/AircraftIdentityDataRef.cs new file mode 100644 index 0000000..23a00f1 --- /dev/null +++ b/OpenSky.Agent.UdpXPlane11/Models/AircraftIdentityDataRef.cs @@ -0,0 +1,186 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// OpenSky project 2021-2022 +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace OpenSky.Agent.UdpXPlane11.Models +{ + using OpenSkyApi; + + using XPlaneConnector; + using XPlaneConnector.DataRefs; + + /// ------------------------------------------------------------------------------------------------- + /// + /// XPlane 11 dataref enabled version of aircraft identity model. + /// + /// + /// sushi.at, 06/02/2022. + /// + /// + /// ------------------------------------------------------------------------------------------------- + public class AircraftIdentityDataRef : Agent.Simulator.Models.AircraftIdentity + { + /// ------------------------------------------------------------------------------------------------- + /// + /// (Immutable) The aircraft ICAO character array. + /// + /// ------------------------------------------------------------------------------------------------- + private readonly char[] acfICAO = new char[40]; + + /// ------------------------------------------------------------------------------------------------- + /// + /// (Immutable) The aircraft description character array. + /// + /// ------------------------------------------------------------------------------------------------- + private readonly char[] acfDescription = new char[50]; + + /// ------------------------------------------------------------------------------------------------- + /// + /// Initializes a new instance of the class. + /// + /// + /// sushi.at, 06/02/2022. + /// + /// ------------------------------------------------------------------------------------------------- + public AircraftIdentityDataRef() + { + // todo Not sure how to determine otherwise + this.FlapsAvailable = true; + this.AtcType = "n/a"; + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Makes a copy of this object. + /// + /// + /// sushi.at, 06/02/2022. + /// + /// + /// A copy of this object. + /// + /// ------------------------------------------------------------------------------------------------- + public Agent.Simulator.Models.AircraftIdentity Clone() + { + return new Agent.Simulator.Models.AircraftIdentity + { + Type = this.Type, + EngineType = this.EngineType, + EngineCount = this.EngineCount, + AtcType = this.AtcType, + AtcModel = this.AtcModel, + FlapsAvailable = this.FlapsAvailable, + GearRetractable = this.GearRetractable + }; + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Register with Xplane connector. + /// + /// + /// sushi.at, 06/02/2022. + /// + /// + /// The Xplane connector. + /// + /// + /// The configured sample rate. + /// + /// ------------------------------------------------------------------------------------------------- + public void RegisterWithConnector(XPlaneConnector connector, int sampleRate) + { + for (var i = 0; i < 40; i++) + { + var icao = DataRefs.AircraftViewAcfICAO; + icao.DataRef += $"[{i}]"; + connector.Subscribe(icao, 1000 / sampleRate, this.DataRefUpdated); + } + + for (var i = 0; i < 50; i++) + { + var description = DataRefs.AircraftViewAcfDescrip; + description.DataRef += $"[{i}]"; + connector.Subscribe(description, 1000 / sampleRate, this.DataRefUpdated); + } + + connector.Subscribe(DataRefs.AircraftEngineAcfNumEngines, 1000 / sampleRate, this.DataRefUpdated); + var engineType = DataRefs.AircraftPropAcfEnType; + engineType.DataRef += "[0]"; + connector.Subscribe(engineType, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.AircraftGearAcfGearRetract, 1000 / sampleRate, this.DataRefUpdated); + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Dataref subscription updated. + /// + /// + /// sushi.at, 06/02/2022. + /// + /// + /// The element. + /// + /// + /// The value. + /// + /// ------------------------------------------------------------------------------------------------- + private void DataRefUpdated(DataRefElement element, float value) + { + if (element.DataRef.StartsWith(DataRefs.AircraftViewAcfICAO.DataRef)) + { + if (element.DataRef.Contains("[") && element.DataRef.EndsWith("]")) + { + var indexString = element.DataRef.Split('[')[1].Replace("]", string.Empty); + if (int.TryParse(indexString, out var index) && index is >= 0 and < 40) + { + this.acfICAO[index] = (char)value; + this.AtcModel = new string(this.acfICAO).Replace("\0", string.Empty); + } + } + } + + if (element.DataRef.StartsWith(DataRefs.AircraftViewAcfDescrip.DataRef)) + { + if (element.DataRef.Contains("[") && element.DataRef.EndsWith("]")) + { + var indexString = element.DataRef.Split('[')[1].Replace("]", string.Empty); + if (int.TryParse(indexString, out var index) && index is >= 0 and < 50) + { + this.acfDescription[index] = (char)value; + this.Type = new string(this.acfDescription).Replace("\0", string.Empty); + } + } + } + + if (element.DataRef == DataRefs.AircraftEngineAcfNumEngines.DataRef) + { + this.EngineCount = (int)value; + } + + if (element.DataRef.StartsWith(DataRefs.AircraftPropAcfEnType.DataRef)) + { + this.EngineType = (int)value switch + { + 0 => EngineType.Piston, + 1 => EngineType.Piston, + 2 => EngineType.Turboprop, + 3 => EngineType.Unsupported, // Electric engine + 4 => EngineType.Jet, + 5 => EngineType.Jet, + 6 => EngineType.Unsupported, // Rocket + 7 => EngineType.Unsupported, // Tip rockets + 8 => EngineType.Turboprop, + _ => EngineType.None + }; + } + + if (element.DataRef == DataRefs.AircraftGearAcfGearRetract.DataRef) + { + this.GearRetractable = (int)value == 1; + } + } + } +} \ No newline at end of file diff --git a/OpenSky.Agent.UdpXPlane11/Models/FuelTanksDataRef.cs b/OpenSky.Agent.UdpXPlane11/Models/FuelTanksDataRef.cs new file mode 100644 index 0000000..2c03cee --- /dev/null +++ b/OpenSky.Agent.UdpXPlane11/Models/FuelTanksDataRef.cs @@ -0,0 +1,399 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// OpenSky project 2021-2022 +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace OpenSky.Agent.UdpXPlane11.Models +{ + using System; + using System.Collections.Generic; + using System.Linq; + + using OpenSky.Agent.Simulator.Enums; + + using XPlaneConnector; + using XPlaneConnector.DataRefs; + + /// ------------------------------------------------------------------------------------------------- + /// + /// XPlane 11 dataref enabled version of fuel tanks model. + /// + /// + /// sushi.at, 06/02/2022. + /// + /// + /// ------------------------------------------------------------------------------------------------- + public class FuelTanksDataRef : Agent.Simulator.Models.FuelTanks + { + /// ------------------------------------------------------------------------------------------------- + /// + /// The tank indices. + /// + /// ------------------------------------------------------------------------------------------------- + internal Dictionary tankIndices = new(); + + /// ------------------------------------------------------------------------------------------------- + /// + /// (Immutable) The current fuel weight for each tank. + /// + /// ------------------------------------------------------------------------------------------------- + private readonly float[] tankFuelWeight = new float[9]; + + /// ------------------------------------------------------------------------------------------------- + /// + /// (Immutable) The fuel tank rations. + /// + /// ------------------------------------------------------------------------------------------------- + private readonly float[] tankRatios = { -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999, }; + + /// ------------------------------------------------------------------------------------------------- + /// + /// (Immutable) the fuel tank X-axis coordinates. + /// + /// ------------------------------------------------------------------------------------------------- + private readonly float[] tankX = { -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999 }; + + /// ------------------------------------------------------------------------------------------------- + /// + /// The fuel maximum weight. + /// + /// ------------------------------------------------------------------------------------------------- + private float fuelMaxWeight; + + /// ------------------------------------------------------------------------------------------------- + /// + /// The fuel weight lbs/gal. + /// + /// ------------------------------------------------------------------------------------------------- + private double fuelWeight; + + /// ------------------------------------------------------------------------------------------------- + /// + /// Makes a copy of this object. + /// + /// + /// sushi.at, 06/02/2022. + /// + /// + /// A copy of this object. + /// + /// ------------------------------------------------------------------------------------------------- + public Simulator.Models.FuelTanks Clone() + { + return new Simulator.Models.FuelTanks + { + // Omitted external tanks since XPlane only supports 9 tanks + FuelTankCenterCapacity = this.FuelTankCenterCapacity, + FuelTankCenter2Capacity = this.FuelTankCenter2Capacity, + FuelTankCenter3Capacity = this.FuelTankCenter3Capacity, + FuelTankLeftMainCapacity = this.FuelTankLeftMainCapacity, + FuelTankLeftAuxCapacity = this.FuelTankLeftAuxCapacity, + FuelTankLeftTipCapacity = this.FuelTankLeftTipCapacity, + FuelTankRightMainCapacity = this.FuelTankRightMainCapacity, + FuelTankRightAuxCapacity = this.FuelTankRightAuxCapacity, + FuelTankRightTipCapacity = this.FuelTankRightTipCapacity, + + FuelTankCenterQuantity = this.FuelTankCenterQuantity, + FuelTankCenter2Quantity = this.FuelTankCenter2Quantity, + FuelTankCenter3Quantity = this.FuelTankCenter3Quantity, + FuelTankLeftMainQuantity = this.FuelTankLeftMainQuantity, + FuelTankLeftAuxQuantity = this.FuelTankLeftAuxQuantity, + FuelTankLeftTipQuantity = this.FuelTankLeftTipQuantity, + FuelTankRightMainQuantity = this.FuelTankRightMainQuantity, + FuelTankRightAuxQuantity = this.FuelTankRightAuxQuantity, + FuelTankRightTipQuantity = this.FuelTankRightTipQuantity, + }; + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Register with Xplane connector. + /// + /// + /// sushi.at, 06/02/2022. + /// + /// + /// The Xplane connector. + /// + /// + /// The configured sample rate. + /// + /// ------------------------------------------------------------------------------------------------- + public void RegisterWithConnector(XPlaneConnector connector, int sampleRate) + { + for (var i = 0; i < 9; i++) + { + var tankRatio = DataRefs.AircraftOverflowAcfTankRat; + tankRatio.DataRef += $"[{i}]"; + connector.Subscribe(tankRatio, 1000 / sampleRate, this.DataRefUpdated); + + var tankWeight = DataRefs.FlightmodelWeightMFuel; + tankWeight.DataRef += $"[{i}]"; + connector.Subscribe(tankWeight, 1000 / sampleRate, this.DataRefUpdated); + + var tankXRef = DataRefs.AircraftOverflowAcfTankX; + tankXRef.DataRef += $"[{i}]"; + connector.Subscribe(tankXRef, 1000 / sampleRate, this.DataRefUpdated); + } + + connector.Subscribe(DataRefs.AircraftWeightAcfMFuelTot, 1000 / sampleRate, this.DataRefUpdated); + var engineType = DataRefs.AircraftPropAcfEnType; + engineType.DataRef += "[0]"; + connector.Subscribe(engineType, 1000 / sampleRate, this.DataRefUpdated); + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Dataref subscription updated. + /// + /// + /// sushi.at, 06/02/2022. + /// + /// + /// The element. + /// + /// + /// The value. + /// + /// ------------------------------------------------------------------------------------------------- + private void DataRefUpdated(DataRefElement element, float value) + { + var updateCapacities = false; + var updateQuantities = false; + var updateIndices = false; + if (element.DataRef.StartsWith(DataRefs.AircraftOverflowAcfTankRat.DataRef)) + { + if (element.DataRef.Contains("[") && element.DataRef.EndsWith("]")) + { + var indexString = element.DataRef.Split('[')[1].Replace("]", string.Empty); + if (int.TryParse(indexString, out var index) && index is >= 0 and < 9) + { + this.tankRatios[index] = value; + updateCapacities = true; + } + } + } + + if (element.DataRef.StartsWith(DataRefs.FlightmodelWeightMFuel.DataRef)) + { + if (element.DataRef.Contains("[") && element.DataRef.EndsWith("]")) + { + var indexString = element.DataRef.Split('[')[1].Replace("]", string.Empty); + if (int.TryParse(indexString, out var index) && index is >= 0 and < 9) + { + this.tankFuelWeight[index] = value; + updateQuantities = true; + } + } + } + + if (element.DataRef.StartsWith(DataRefs.AircraftOverflowAcfTankX.DataRef)) + { + if (element.DataRef.Contains("[") && element.DataRef.EndsWith("]")) + { + var indexString = element.DataRef.Split('[')[1].Replace("]", string.Empty); + if (int.TryParse(indexString, out var index) && index is >= 0 and < 9) + { + this.tankX[index] = value; + updateIndices = true; + } + } + } + + if (element.DataRef == DataRefs.AircraftWeightAcfMFuelTot.DataRef) + { + this.fuelMaxWeight = value; + updateCapacities = true; + } + + if (element.DataRef.StartsWith(DataRefs.AircraftPropAcfEnType.DataRef)) + { + this.fuelWeight = (int)value switch + { + 0 => 6, + 1 => 6, + 2 => 6.7, + 3 => 0, // Electric engine + 4 => 6.7, + 5 => 6.7, + 6 => 0, // Rocket + 7 => 0, // Tip rockets + 8 => 6.7, + _ => 0 + }; + updateCapacities = true; + updateQuantities = true; + } + + // ReSharper disable CompareOfFloatsByEqualityOperator + if ((updateIndices || updateCapacities) && this.tankX.All(t => t != -9999) && this.tankRatios.All(t => t != -9999)) + { + this.tankIndices.Clear(); + var leftTanks = new List(); + var rightTanks = new List(); + for (var i = 0; i < 9; i++) + { + // Is the tank used? + if (this.tankRatios[i] == 0) + { + continue; + } + + // A center tank + if (this.tankX[i] < 0.5 && this.tankX[i] > -0.5) + { + if (!this.tankIndices.ContainsKey(FuelTank.Center)) + { + this.tankIndices.Add(FuelTank.Center, i); + continue; + } + + if (!this.tankIndices.ContainsKey(FuelTank.Center2)) + { + this.tankIndices.Add(FuelTank.Center2, i); + continue; + } + + if (!this.tankIndices.ContainsKey(FuelTank.Center3)) + { + this.tankIndices.Add(FuelTank.Center3, i); + continue; + } + + throw new Exception("Can't support more than 3 center tanks!"); + } + + // A right side tank + if (this.tankX[i] >= 0.5) + { + rightTanks.Add(new TankIndexWithRatio { Index = i, Ratio = this.tankRatios[i] }); + continue; + } + + // A left side tank + { + leftTanks.Add(new TankIndexWithRatio { Index = i, Ratio = this.tankRatios[i] }); + } + } + + // Add left and right tanks in order of their size + foreach (var tankIndexWithRatio in leftTanks.OrderByDescending(t => t.Ratio)) + { + if (!this.tankIndices.ContainsKey(FuelTank.LeftMain)) + { + this.tankIndices.Add(FuelTank.LeftMain, tankIndexWithRatio.Index); + continue; + } + + if (!this.tankIndices.ContainsKey(FuelTank.LeftTip)) + { + this.tankIndices.Add(FuelTank.LeftTip, tankIndexWithRatio.Index); + continue; + } + + if (!this.tankIndices.ContainsKey(FuelTank.LeftAux)) + { + this.tankIndices.Add(FuelTank.LeftAux, tankIndexWithRatio.Index); + continue; + } + + if (!this.tankIndices.ContainsKey(FuelTank.External1)) + { + this.tankIndices.Add(FuelTank.External1, tankIndexWithRatio.Index); + continue; + } + + throw new Exception("Can't support more than 4 left side tanks!"); + } + + foreach (var tankIndexWithRatio in rightTanks.OrderByDescending(t => t.Ratio)) + { + if (!this.tankIndices.ContainsKey(FuelTank.RightMain)) + { + this.tankIndices.Add(FuelTank.RightMain, tankIndexWithRatio.Index); + continue; + } + + if (!this.tankIndices.ContainsKey(FuelTank.RightTip)) + { + this.tankIndices.Add(FuelTank.RightTip, tankIndexWithRatio.Index); + continue; + } + + if (!this.tankIndices.ContainsKey(FuelTank.RightAux)) + { + this.tankIndices.Add(FuelTank.RightAux, tankIndexWithRatio.Index); + continue; + } + + if (!this.tankIndices.ContainsKey(FuelTank.External2)) + { + this.tankIndices.Add(FuelTank.External2, tankIndexWithRatio.Index); + continue; + } + + throw new Exception("Can't support more than 4 right side tanks!"); + } + } + + if (updateCapacities) + { + var capacities = new Dictionary(); + foreach (FuelTank tank in Enum.GetValues(typeof(FuelTank))) + { + capacities.Add(tank, 0); + } + + var totalCapacityGallons = (this.fuelMaxWeight * 2.205) / this.fuelWeight; + foreach (var tankIndex in this.tankIndices) + { + capacities[tankIndex.Key] = this.tankRatios[tankIndex.Value] * totalCapacityGallons; + } + + this.UpdateCapactiesFromDictionary(capacities); + } + + if (updateQuantities) + { + var quantities = new Dictionary(); + foreach (FuelTank tank in Enum.GetValues(typeof(FuelTank))) + { + quantities.Add(tank, 0); + } + + foreach (var tankIndex in this.tankIndices) + { + quantities[tankIndex.Key] = this.tankFuelWeight[tankIndex.Value] * 2.205 / this.fuelWeight; + } + + this.UpdateQuantitiesFromDictionary(quantities); + } + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// A tank index with ratio (for sorting). + /// + /// + /// sushi.at, 11/02/2022. + /// + /// ------------------------------------------------------------------------------------------------- + private class TankIndexWithRatio + { + /// ------------------------------------------------------------------------------------------------- + /// + /// Gets or sets the zero-based index of the fuel tank. + /// + /// ------------------------------------------------------------------------------------------------- + public int Index { get; set; } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Gets or sets the ratio. + /// + /// ------------------------------------------------------------------------------------------------- + public float Ratio { get; set; } + } + } +} \ No newline at end of file diff --git a/OpenSky.Agent.UdpXPlane11/Models/LandingAnalysisDataRef.cs b/OpenSky.Agent.UdpXPlane11/Models/LandingAnalysisDataRef.cs new file mode 100644 index 0000000..cd3a759 --- /dev/null +++ b/OpenSky.Agent.UdpXPlane11/Models/LandingAnalysisDataRef.cs @@ -0,0 +1,203 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// OpenSky project 2021-2022 +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace OpenSky.Agent.UdpXPlane11.Models +{ + using System; + + using XPlaneConnector; + using XPlaneConnector.DataRefs; + + /// ------------------------------------------------------------------------------------------------- + /// + /// XPlane 11 dataref enabled version of landing analysis model. + /// + /// + /// sushi.at, 07/02/2022. + /// + /// + /// ------------------------------------------------------------------------------------------------- + public class LandingAnalysisDataRef : Simulator.Models.LandingAnalysis + { + /// ------------------------------------------------------------------------------------------------- + /// + /// The heading. + /// + /// ------------------------------------------------------------------------------------------------- + private float heading; + + /// ------------------------------------------------------------------------------------------------- + /// + /// The wind degrees. + /// + /// ------------------------------------------------------------------------------------------------- + private float windDegrees; + + /// ------------------------------------------------------------------------------------------------- + /// + /// The wind speed. + /// + /// ------------------------------------------------------------------------------------------------- + private float windSpeed; + + /// ------------------------------------------------------------------------------------------------- + /// + /// Initializes a new instance of the class. + /// + /// + /// sushi.at, 10/02/2022. + /// + /// ------------------------------------------------------------------------------------------------- + public LandingAnalysisDataRef() + { + // Todo figure out how to calculate that correctly in XP11 + this.SpeedLat = 0; + this.SpeedLong = 1; + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Makes a copy of this object. + /// + /// + /// sushi.at, 06/02/2022. + /// + /// + /// A copy of this object. + /// + /// ------------------------------------------------------------------------------------------------- + public Simulator.Models.LandingAnalysis Clone() + { + return new Simulator.Models.LandingAnalysis + { + Latitude = this.Latitude, + Longitude = this.Longitude, + Altitude = this.Altitude, + OnGround = this.OnGround, + WindLat = this.WindLat, + WindLong = this.WindLong, + AirspeedTrue = this.AirspeedTrue, + GroundSpeed = this.GroundSpeed, + SpeedLat = this.SpeedLat, + SpeedLong = this.SpeedLong, + Gforce = this.Gforce, + LandingRateSeconds = this.LandingRateSeconds, + BankAngle = this.BankAngle + }; + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Register with Xplane connector. + /// + /// + /// sushi.at, 06/02/2022. + /// + /// + /// The Xplane connector. + /// + /// + /// The configured sample rate. + /// + /// ------------------------------------------------------------------------------------------------- + public void RegisterWithConnector(XPlaneConnector connector, int sampleRate) + { + connector.Subscribe(DataRefs.FlightmodelPositionLatitude, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.FlightmodelPositionLongitude, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.FlightmodelPositionElevation, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.FlightmodelPositionYAgl, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.FlightmodelPositionMagPsi, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.WeatherWindDirectionDegt, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.WeatherWindSpeedKt, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.FlightmodelPositionTrueAirspeed, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.FlightmodelPositionGroundspeed, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.Flightmodel2MiscGforceNormal, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.FlightmodelPositionVhInd, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.FlightmodelPositionTruePhi, 1000 / sampleRate, this.DataRefUpdated); + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Dataref subscription updated. + /// + /// + /// sushi.at, 06/02/2022. + /// + /// + /// The element. + /// + /// + /// The value. + /// + /// ------------------------------------------------------------------------------------------------- + private void DataRefUpdated(DataRefElement element, float value) + { + if (element.DataRef == DataRefs.FlightmodelPositionLatitude.DataRef) + { + this.Latitude = value; + } + + if (element.DataRef == DataRefs.FlightmodelPositionLongitude.DataRef) + { + this.Longitude = value; + } + + if (element.DataRef == DataRefs.FlightmodelPositionElevation.DataRef) + { + this.Altitude = value * 3.281; + } + + if (element.DataRef == DataRefs.FlightmodelPositionYAgl.DataRef) + { + this.OnGround = value < 1; // todo there has to be a better way + } + + if (element.DataRef == DataRefs.FlightmodelPositionTrueAirspeed.DataRef) + { + this.AirspeedTrue = value * 1.944; + } + + if (element.DataRef == DataRefs.FlightmodelPositionGroundspeed.DataRef) + { + this.GroundSpeed = value * 1.944; + } + + if (element.DataRef == DataRefs.Flightmodel2MiscGforceNormal.DataRef) + { + this.Gforce = value; + } + + if (element.DataRef == DataRefs.FlightmodelPositionVhInd.DataRef) + { + this.LandingRateSeconds = value * 3.281; + } + + if (element.DataRef == DataRefs.FlightmodelPositionTruePhi.DataRef) + { + this.BankAngle = value; + } + + if (element.DataRef == DataRefs.FlightmodelPositionMagPsi.DataRef) + { + this.heading = value; + } + + if (element.DataRef == DataRefs.WeatherWindDirectionDegt.DataRef) + { + this.windDegrees = value; + } + + if (element.DataRef == DataRefs.WeatherWindSpeedKt.DataRef) + { + this.windSpeed = value * 1.944f; + } + + var windDelta = Math.Abs(this.heading - this.windDegrees); + this.WindLat = Math.Sin((Math.PI / 180) * windDelta) * this.windSpeed; + this.WindLong = Math.Cos((Math.PI / 180) * windDelta) * this.windSpeed; + } + } +} \ No newline at end of file diff --git a/OpenSky.Agent.UdpXPlane11/Models/PayloadStationsDataRef.cs b/OpenSky.Agent.UdpXPlane11/Models/PayloadStationsDataRef.cs new file mode 100644 index 0000000..e7ea8ae --- /dev/null +++ b/OpenSky.Agent.UdpXPlane11/Models/PayloadStationsDataRef.cs @@ -0,0 +1,100 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// OpenSky project 2021-2022 +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace OpenSky.Agent.UdpXPlane11.Models +{ + using XPlaneConnector; + using XPlaneConnector.DataRefs; + + /// ------------------------------------------------------------------------------------------------- + /// + /// XPlane 11 dataref enabled version of payload stations model. + /// + /// + /// sushi.at, 07/02/2022. + /// + /// + /// ------------------------------------------------------------------------------------------------- + public class PayloadStationsDataRef : Simulator.Models.PayloadStations + { + /// ------------------------------------------------------------------------------------------------- + /// + /// Initializes a new instance of the class. + /// + /// + /// sushi.at, 07/02/2022. + /// + /// ------------------------------------------------------------------------------------------------- + public PayloadStationsDataRef() + { + this.Count = 1; + this.Name1 = "Payload"; + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Makes a copy of this object. + /// + /// + /// sushi.at, 06/02/2022. + /// + /// + /// A copy of this object. + /// + /// ------------------------------------------------------------------------------------------------- + public Simulator.Models.PayloadStations Clone() + { + return new Simulator.Models.PayloadStations + { + // Omitted the other 19 stations as they will never be populated + Count = this.Count, + Name1 = this.Name1, + Weight1 = this.Weight1 + }; + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Register with Xplane connector. + /// + /// + /// sushi.at, 06/02/2022. + /// + /// + /// The Xplane connector. + /// + /// + /// The configured sample rate. + /// + /// ------------------------------------------------------------------------------------------------- + public void RegisterWithConnector(XPlaneConnector connector, int sampleRate) + { + connector.Subscribe(DataRefs.FlightmodelWeightMFixed, 1000 / sampleRate, this.DataRefUpdated); + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Dataref subscription updated. + /// + /// + /// sushi.at, 06/02/2022. + /// + /// + /// The element. + /// + /// + /// The value. + /// + /// ------------------------------------------------------------------------------------------------- + private void DataRefUpdated(DataRefElement element, float value) + { + if (element.DataRef == DataRefs.FlightmodelWeightMFixed.DataRef) + { + this.Weight1 = value * 2.205; + } + } + } +} \ No newline at end of file diff --git a/OpenSky.Agent.UdpXPlane11/Models/PrimaryTrackingDataRef.cs b/OpenSky.Agent.UdpXPlane11/Models/PrimaryTrackingDataRef.cs new file mode 100644 index 0000000..c160f84 --- /dev/null +++ b/OpenSky.Agent.UdpXPlane11/Models/PrimaryTrackingDataRef.cs @@ -0,0 +1,196 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// OpenSky project 2021-2022 +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace OpenSky.Agent.UdpXPlane11.Models +{ + using XPlaneConnector; + using XPlaneConnector.DataRefs; + + /// ------------------------------------------------------------------------------------------------- + /// + /// XPlane 11 dataref enabled version of primary tracking model. + /// + /// + /// sushi.at, 02/02/2022. + /// + /// + /// ------------------------------------------------------------------------------------------------- + public class PrimaryTrackingDataRef : Simulator.Models.PrimaryTracking + { + /// ------------------------------------------------------------------------------------------------- + /// + /// Makes a copy of this object. + /// + /// + /// sushi.at, 03/02/2022. + /// + /// + /// A copy of this object. + /// + /// ------------------------------------------------------------------------------------------------- + public Simulator.Models.PrimaryTracking Clone() + { + return new Simulator.Models.PrimaryTracking + { + Latitude = this.Latitude, + Longitude = this.Longitude, + Altitude = this.Altitude, + RadioHeight = this.RadioHeight, + IndicatedAltitude = this.IndicatedAltitude, + OnGround = this.OnGround, + Heading = this.Heading, + AirspeedTrue = this.AirspeedTrue, + GroundSpeed = this.GroundSpeed, + AirspeedIndicated = this.AirspeedIndicated, + PitchAngle = this.PitchAngle, + BankAngle = this.BankAngle, + VerticalSpeedSeconds = this.VerticalSpeedSeconds, + StallWarning = this.StallWarning, + OverspeedWarning = this.OverspeedWarning, + GForce = this.GForce, + SimulationRate = this.SimulationRate, + SlewActive = this.SlewActive, + Crash = this.Crash + }; + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Register with Xplane connector. + /// + /// + /// sushi.at, 03/02/2022. + /// + /// + /// The Xplane connector. + /// + /// + /// The configured sample rate. + /// + /// ------------------------------------------------------------------------------------------------- + public void RegisterWithConnector(XPlaneConnector connector, int sampleRate) + { + connector.Subscribe(DataRefs.FlightmodelPositionLatitude, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.FlightmodelPositionLongitude, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.FlightmodelPositionElevation, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.FlightmodelPositionYAgl, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.FlightmodelMiscHInd2, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.FlightmodelPositionMagPsi, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.FlightmodelPositionTrueAirspeed, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.FlightmodelPositionGroundspeed, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.FlightmodelPositionIndicatedAirspeed, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.FlightmodelPositionTrueTheta, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.FlightmodelPositionTruePhi, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.FlightmodelPositionVhInd, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.Cockpit2AnnunciatorsStallWarning, 1000 / sampleRate, this.DataRefUpdated); + + // todo detect overspeed possible? + connector.Subscribe(DataRefs.Flightmodel2MiscGforceNormal, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.TimeSimSpeed, 1000 / sampleRate, this.DataRefUpdated); + + // no slew mode really + connector.Subscribe(DataRefs.Flightmodel2MiscHasCrashed, 1000 / sampleRate, this.DataRefUpdated); + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Dataref subscription updated. + /// + /// + /// sushi.at, 03/02/2022. + /// + /// + /// The element. + /// + /// + /// The value. + /// + /// ------------------------------------------------------------------------------------------------- + private void DataRefUpdated(DataRefElement element, float value) + { + if (element.DataRef == DataRefs.FlightmodelPositionLatitude.DataRef) + { + this.Latitude = value; + } + + if (element.DataRef == DataRefs.FlightmodelPositionLongitude.DataRef) + { + this.Longitude = value; + } + + if (element.DataRef == DataRefs.FlightmodelPositionElevation.DataRef) + { + this.Altitude = value * 3.281; + } + + if (element.DataRef == DataRefs.FlightmodelPositionYAgl.DataRef) + { + this.RadioHeight = value * 3.281; + this.OnGround = value < 1; // todo there has to be a better way + } + + if (element.DataRef == DataRefs.FlightmodelMiscHInd2.DataRef) + { + this.IndicatedAltitude = value; + } + + if (element.DataRef == DataRefs.FlightmodelPositionMagPsi.DataRef) + { + this.Heading = value; + } + + if (element.DataRef == DataRefs.FlightmodelPositionTrueAirspeed.DataRef) + { + this.AirspeedTrue = value * 1.944; + } + + if (element.DataRef == DataRefs.FlightmodelPositionGroundspeed.DataRef) + { + this.GroundSpeed = value * 1.944; + } + + if (element.DataRef == DataRefs.FlightmodelPositionIndicatedAirspeed.DataRef) + { + this.AirspeedIndicated = value * 1.944; + } + + if (element.DataRef == DataRefs.FlightmodelPositionTrueTheta.DataRef) + { + this.PitchAngle = value; + } + + if (element.DataRef == DataRefs.FlightmodelPositionTruePhi.DataRef) + { + this.BankAngle = value; + } + + if (element.DataRef == DataRefs.FlightmodelPositionVhInd.DataRef) + { + this.VerticalSpeedSeconds = value * 3.281; + } + + if (element.DataRef == DataRefs.Cockpit2AnnunciatorsStallWarning.DataRef) + { + this.StallWarning = (int)value == 1; + } + + if (element.DataRef == DataRefs.Flightmodel2MiscGforceNormal.DataRef) + { + this.GForce = value; + } + + if (element.DataRef == DataRefs.TimeSimSpeed.DataRef) + { + this.SimulationRate = value; + } + + if (element.DataRef == DataRefs.Flightmodel2MiscHasCrashed.DataRef) + { + this.Crash = (int)value == 1; + } + } + } +} \ No newline at end of file diff --git a/OpenSky.Agent.UdpXPlane11/Models/SecondaryTrackingDataRef.cs b/OpenSky.Agent.UdpXPlane11/Models/SecondaryTrackingDataRef.cs new file mode 100644 index 0000000..3ced2da --- /dev/null +++ b/OpenSky.Agent.UdpXPlane11/Models/SecondaryTrackingDataRef.cs @@ -0,0 +1,340 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// OpenSky project 2021-2022 +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace OpenSky.Agent.UdpXPlane11.Models +{ + using System; + using System.Diagnostics; + + using OpenSky.Agent.Simulator.Enums; + using OpenSky.FlightLogXML; + + using XPlaneConnector; + using XPlaneConnector.DataRefs; + + /// ------------------------------------------------------------------------------------------------- + /// + /// XPlane 11 dataref enabled version of secondary tracking model. + /// + /// + /// sushi.at, 03/02/2022. + /// + /// + /// ------------------------------------------------------------------------------------------------- + public class SecondaryTrackingDataRef : Simulator.Models.SecondaryTracking + { + /// ------------------------------------------------------------------------------------------------- + /// + /// Number of engines. + /// + /// ------------------------------------------------------------------------------------------------- + private int numberOfEngines; + + /// ------------------------------------------------------------------------------------------------- + /// + /// Initializes a new instance of the class. + /// + /// + /// sushi.at, 03/02/2022. + /// + /// ------------------------------------------------------------------------------------------------- + public SecondaryTrackingDataRef() + { + // These can't be changed in XPlane + this.CrashDetection = true; + this.UnlimitedFuel = false; + + this.UtcYear = DateTime.UtcNow.Year; // Doesn't seem to exist in xplane + this.TimeOfDay = TimeOfDay.Unknown; // todo check if we can determine this somehow + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Makes a copy of this object. + /// + /// + /// sushi.at, 06/02/2022. + /// + /// + /// A copy of this object. + /// + /// ------------------------------------------------------------------------------------------------- + public Simulator.Models.SecondaryTracking Clone() + { + return new Simulator.Models.SecondaryTracking + { + UtcTime = this.UtcTime, + UtcDay = this.UtcDay, + UtcMonth = this.UtcMonth, + UtcYear = this.UtcYear, + TimeOfDay = this.TimeOfDay, + CrashDetection = this.CrashDetection, + UnlimitedFuel = this.UnlimitedFuel, + ElectricalMasterBattery = this.ElectricalMasterBattery, + EngineCombustion1 = this.EngineCombustion1, + EngineCombustion2 = this.EngineCombustion2, + EngineCombustion3 = this.EngineCombustion3, + EngineCombustion4 = this.EngineCombustion4, + Pushback = this.Pushback, + ApuGenerator = this.ApuGenerator, + LightBeacon = this.LightBeacon, + LightNav = this.LightNav, + LightStrobe = this.LightStrobe, + LightTaxi = this.LightTaxi, + LightLanding = this.LightLanding, + FlapsHandle = this.FlapsHandle, + FlapsPercentage = this.FlapsPercentage, + GearHandle = this.GearHandle, + GearPercentage = this.GearPercentage, + AutoPilot = this.AutoPilot, + ParkingBrake = this.ParkingBrake, + SpoilersArmed = this.SpoilersArmed, + SeatBeltSign = this.SeatBeltSign, + NoSmokingSign = this.NoSmokingSign + }; + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Register with Xplane connector. + /// + /// + /// sushi.at, 03/02/2022. + /// + /// + /// The Xplane connector. + /// + /// + /// The configured sample rate. + /// + /// ------------------------------------------------------------------------------------------------- + public void RegisterWithConnector(XPlaneConnector connector, int sampleRate) + { + connector.Subscribe(DataRefs.TimeZuluTimeSec, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.Cockpit2ClockTimerCurrentDay, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.Cockpit2ClockTimerCurrentMonth, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.CockpitElectricalBatteryOn, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.AircraftEngineAcfNumEngines, 1000 / sampleRate, this.DataRefUpdated); + var engine1Running = DataRefs.FlightmodelEngineENGNRunning; + engine1Running.DataRef += "[0]"; + connector.Subscribe(engine1Running, 1000 / sampleRate, this.DataRefUpdated); + var engine2Running = DataRefs.FlightmodelEngineENGNRunning; + engine2Running.DataRef += "[1]"; + connector.Subscribe(engine2Running, 1000 / sampleRate, this.DataRefUpdated); + var engine3Running = DataRefs.FlightmodelEngineENGNRunning; + engine3Running.DataRef += "[2]"; + connector.Subscribe(engine3Running, 1000 / sampleRate, this.DataRefUpdated); + var engine4Running = DataRefs.FlightmodelEngineENGNRunning; + engine4Running.DataRef += "[3]"; + connector.Subscribe(engine4Running, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.GraphicsAnimationGroundTrafficTowbarHeadingDeg, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.Cockpit2ElectricalAPURunning, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.CockpitElectricalBeaconLightsOn, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.CockpitElectricalNavLightsOn, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.CockpitElectricalStrobeLightsOn, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.CockpitElectricalTaxiLightOn, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.CockpitElectricalLandingLightsOn, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.FlightmodelControlsFlaprqst, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.FlightmodelControlsFlaprat, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.CockpitSwitchesGearHandleStatus, 1000 / sampleRate, this.DataRefUpdated); + var gearDeploy = DataRefs.Flightmodel2GearDeployRatio; + gearDeploy.DataRef += "[0]"; + connector.Subscribe(gearDeploy, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.CockpitAutopilotAutopilotMode, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.FlightmodelControlsParkbrake, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.FlightmodelControlsSbrkrqst, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.CockpitSwitchesFastenSeatBelts, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.CockpitSwitchesNoSmoking, 1000 / sampleRate, this.DataRefUpdated); + + for (var i = 0; i < 40; i++) + { + var tailNum = DataRefs.AircraftViewAcfTailnum; + tailNum.DataRef += $"[{i}]"; + connector.Subscribe(tailNum, 1000 / sampleRate, this.DataRefUpdated); + } + } + + private readonly char[] tailNumber = new char[40]; + + /// ------------------------------------------------------------------------------------------------- + /// + /// Dataref subscription updated. + /// + /// + /// sushi.at, 03/02/2022. + /// + /// + /// The element. + /// + /// + /// The value. + /// + /// ------------------------------------------------------------------------------------------------- + private void DataRefUpdated(DataRefElement element, float value) + { + if (element.DataRef.StartsWith(DataRefs.AircraftViewAcfTailnum.DataRef)) + { + if (element.DataRef.Contains("[") && element.DataRef.EndsWith("]")) + { + var indexString = element.DataRef.Split('[')[1].Replace("]", string.Empty); + if (int.TryParse(indexString, out var index) && index is >= 0 and < 40) + { + this.tailNumber[index] = (char)value; + var tailString = new string(this.tailNumber).Replace("\0", string.Empty); + Debug.WriteLine($"Registry: {tailString}"); + } + } + } + + if (element.DataRef == DataRefs.TimeZuluTimeSec.DataRef) + { + this.UtcTime = value; + } + + if (element.DataRef == DataRefs.Cockpit2ClockTimerCurrentDay.DataRef) + { + this.UtcDay = (int)value; + } + + if (element.DataRef == DataRefs.Cockpit2ClockTimerCurrentMonth.DataRef) + { + this.UtcMonth = (int)value; + } + + if (element.DataRef == DataRefs.CockpitElectricalBatteryOn.DataRef) + { + this.ElectricalMasterBattery = (int)value == 1; + } + + if (element.DataRef == DataRefs.AircraftEngineAcfNumEngines.DataRef) + { + this.numberOfEngines = (int)value; + if (this.numberOfEngines < 1) + { + this.EngineCombustion1 = false; + } + + if (this.numberOfEngines < 2) + { + this.EngineCombustion2 = false; + } + + if (this.numberOfEngines < 3) + { + this.EngineCombustion3 = false; + } + + if (this.numberOfEngines < 4) + { + this.EngineCombustion4 = false; + } + } + + if (element.DataRef.StartsWith(DataRefs.FlightmodelEngineENGNRunning.DataRef)) + { + if (element.DataRef.EndsWith("[0]")) + { + this.EngineCombustion1 = (int)value == 1 && this.numberOfEngines >= 1; + } + + if (element.DataRef.EndsWith("[1]")) + { + this.EngineCombustion2 = (int)value == 1 && this.numberOfEngines >= 2; + } + + if (element.DataRef.EndsWith("[2]")) + { + this.EngineCombustion3 = (int)value == 1 && this.numberOfEngines >= 3; + } + + if (element.DataRef.EndsWith("[3]")) + { + this.EngineCombustion4 = (int)value == 1 && this.numberOfEngines >= 4; + } + } + + if (element.DataRef == DataRefs.GraphicsAnimationGroundTrafficTowbarHeadingDeg.DataRef) + { + this.Pushback = value != 0 ? Pushback.Straight : Pushback.NoPushback; // todo check + } + + if (element.DataRef == DataRefs.Cockpit2ElectricalAPURunning.DataRef) + { + this.ApuGenerator = (int)value == 1; + } + + if (element.DataRef == DataRefs.CockpitElectricalBeaconLightsOn.DataRef) + { + this.LightBeacon = (int)value == 1; + } + + if (element.DataRef == DataRefs.CockpitElectricalNavLightsOn.DataRef) + { + this.LightNav = (int)value == 1; + } + + if (element.DataRef == DataRefs.CockpitElectricalStrobeLightsOn.DataRef) + { + this.LightStrobe = (int)value == 1; + } + + if (element.DataRef == DataRefs.CockpitElectricalTaxiLightOn.DataRef) + { + this.LightTaxi = (int)value == 1; + } + + if (element.DataRef == DataRefs.CockpitElectricalLandingLightsOn.DataRef) + { + this.LightLanding = (int)value == 1; + } + + if (element.DataRef == DataRefs.FlightmodelControlsFlaprqst.DataRef) + { + this.FlapsHandle = value * 100; + } + + if (element.DataRef == DataRefs.FlightmodelControlsFlaprat.DataRef) + { + this.FlapsPercentage = value * 100; + } + + if (element.DataRef == DataRefs.CockpitSwitchesGearHandleStatus.DataRef) + { + this.GearHandle = (int)value == 1; + } + + if (element.DataRef.StartsWith(DataRefs.Flightmodel2GearDeployRatio.DataRef)) + { + this.GearPercentage = value; + } + + if (element.DataRef == DataRefs.CockpitAutopilotAutopilotMode.DataRef) + { + this.AutoPilot = (int)value == 2; + } + + if (element.DataRef == DataRefs.FlightmodelControlsParkbrake.DataRef) + { + this.ParkingBrake = value > 0.1; + } + + if (element.DataRef == DataRefs.FlightmodelControlsSbrkrqst.DataRef) + { + this.SpoilersArmed = value < 0; + } + + if (element.DataRef == DataRefs.CockpitSwitchesFastenSeatBelts.DataRef) + { + this.SeatBeltSign = (int)value == 1; + } + + if (element.DataRef == DataRefs.CockpitSwitchesNoSmoking.DataRef) + { + this.NoSmokingSign = (int)value == 1; + } + } + } +} \ No newline at end of file diff --git a/OpenSky.Agent.UdpXPlane11/Models/WeightAndBalanceDataRef.cs b/OpenSky.Agent.UdpXPlane11/Models/WeightAndBalanceDataRef.cs new file mode 100644 index 0000000..d4f40a2 --- /dev/null +++ b/OpenSky.Agent.UdpXPlane11/Models/WeightAndBalanceDataRef.cs @@ -0,0 +1,188 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// OpenSky project 2021-2022 +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace OpenSky.Agent.UdpXPlane11.Models +{ + using XPlaneConnector; + using XPlaneConnector.DataRefs; + + /// ------------------------------------------------------------------------------------------------- + /// + /// XPlane 11 dataref enabled version of weight and balance model. + /// + /// + /// sushi.at, 07/02/2022. + /// + /// + /// ------------------------------------------------------------------------------------------------- + public class WeightAndBalanceDataRef : Simulator.Models.WeightAndBalance + { + /// ------------------------------------------------------------------------------------------------- + /// + /// The fuel maximum weight. + /// + /// ------------------------------------------------------------------------------------------------- + private float fuelMaxWeight; + + /// ------------------------------------------------------------------------------------------------- + /// + /// The fuel weight. + /// + /// ------------------------------------------------------------------------------------------------- + private float fuelWeight; + + /// ------------------------------------------------------------------------------------------------- + /// + /// Initializes a new instance of the class. + /// + /// + /// sushi.at, 07/02/2022. + /// + /// ------------------------------------------------------------------------------------------------- + public WeightAndBalanceDataRef() + { + // XPlane doesn't have a variables for the current CG + this.CgPercentLateral = 0; + this.CgPercent = 0; + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Makes a copy of this object. + /// + /// + /// sushi.at, 06/02/2022. + /// + /// + /// A copy of this object. + /// + /// ------------------------------------------------------------------------------------------------- + public Simulator.Models.WeightAndBalance Clone() + { + return new Simulator.Models.WeightAndBalance + { + EmptyWeight = this.EmptyWeight, + TotalWeight = this.TotalWeight, + MaxGrossWeight = this.MaxGrossWeight, + FuelTotalCapacity = this.FuelTotalCapacity, + FuelTotalQuantity = this.FuelTotalQuantity, + FuelWeightPerGallon = this.FuelWeightPerGallon, + CgAftLimit = this.CgAftLimit, + CgFwdLimit = this.CgFwdLimit, + CgPercent = this.CgPercent, + CgPercentLateral = this.CgPercentLateral + }; + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Register with Xplane connector. + /// + /// + /// sushi.at, 06/02/2022. + /// + /// + /// The Xplane connector. + /// + /// + /// The configured sample rate. + /// + /// ------------------------------------------------------------------------------------------------- + public void RegisterWithConnector(XPlaneConnector connector, int sampleRate) + { + connector.Subscribe(DataRefs.AircraftWeightAcfMEmpty, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.FlightmodelWeightMTotal, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.AircraftWeightAcfMMax, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.AircraftWeightAcfMFuelTot, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.FlightmodelWeightMFuelTotal, 1000 / sampleRate, this.DataRefUpdated); + var engineType = DataRefs.AircraftPropAcfEnType; + engineType.DataRef += "[0]"; + connector.Subscribe(engineType, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.AircraftOverflowAcfCgzAft, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.AircraftOverflowAcfCgzFwd, 1000 / sampleRate, this.DataRefUpdated); + connector.Subscribe(DataRefs.Cockpit2GaugesIndicatorsCGIndicator, 1000 / sampleRate, this.DataRefUpdated); + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Dataref subscription updated. + /// + /// + /// sushi.at, 06/02/2022. + /// + /// + /// The element. + /// + /// + /// The value. + /// + /// ------------------------------------------------------------------------------------------------- + private void DataRefUpdated(DataRefElement element, float value) + { + var updateFuelValues = false; + if (element.DataRef == DataRefs.AircraftWeightAcfMEmpty.DataRef) + { + this.EmptyWeight = value * 2.205; + } + + if (element.DataRef == DataRefs.FlightmodelWeightMTotal.DataRef) + { + this.TotalWeight = value * 2.205; + } + + if (element.DataRef == DataRefs.AircraftWeightAcfMMax.DataRef) + { + this.MaxGrossWeight = value * 2.205; + } + + if (element.DataRef == DataRefs.AircraftWeightAcfMFuelTot.DataRef) + { + this.fuelMaxWeight = value; + updateFuelValues = true; + } + + if (element.DataRef == DataRefs.FlightmodelWeightMFuelTotal.DataRef) + { + this.fuelWeight = value; + updateFuelValues = true; + } + + if (element.DataRef.StartsWith(DataRefs.AircraftPropAcfEnType.DataRef)) + { + this.FuelWeightPerGallon = (int)value switch + { + 0 => 6, + 1 => 6, + 2 => 6.7, + 3 => 0, // Electric engine + 4 => 6.7, + 5 => 6.7, + 6 => 0, // Rocket + 7 => 0, // Tip rockets + 8 => 6.7, + _ => 0 + }; + updateFuelValues = true; + } + + if (element.DataRef == DataRefs.AircraftOverflowAcfCgzAft.DataRef) + { + this.CgAftLimit = value; + } + + if (element.DataRef == DataRefs.AircraftOverflowAcfCgzFwd.DataRef) + { + this.CgFwdLimit = value; + } + + if (updateFuelValues) + { + this.FuelTotalCapacity = (this.fuelMaxWeight * 2.205) / this.FuelWeightPerGallon; + this.FuelTotalQuantity = (this.fuelWeight * 2.205) / this.FuelWeightPerGallon; + } + } + } +} \ No newline at end of file diff --git a/OpenSky.Agent.UdpXPlane11/OpenSky.Agent.UdpXPlane11.csproj b/OpenSky.Agent.UdpXPlane11/OpenSky.Agent.UdpXPlane11.csproj new file mode 100644 index 0000000..3e571da --- /dev/null +++ b/OpenSky.Agent.UdpXPlane11/OpenSky.Agent.UdpXPlane11.csproj @@ -0,0 +1,86 @@ + + + + + Debug + AnyCPU + {DFBDA2B8-5775-4766-BE86-D729FCF20DE1} + Library + Properties + OpenSky.Agent.UdpXPlane11 + OpenSky.Agent.UdpXPlane11 + v4.8 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + x64 + latest + bin\Debug\OpenSky.Agent.UdpXPlane11.xml + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + x64 + latest + bin\Release\OpenSky.Agent.UdpXPlane11.xml + + + + ..\packages\JetBrains.Annotations.2021.3.0\lib\net20\JetBrains.Annotations.dll + + + ..\packages\OpenSky.FlightLogXML.0.1.5\lib\net48\OpenSky.FlightLogXML.dll + + + ..\packages\MSFT.ParallelExtensionsExtras.1.2.0\lib\ParallelExtensionsExtras.dll + + + + + + + + + + + ..\packages\XPlaneConnector.1.3.0\lib\netstandard2.0\XPlaneConnector.dll + + + ..\packages\XPlaneConnector.DataRefs.11.51.0\lib\netstandard2.0\XPlaneConnector.DataRefs.dll + + + + + + + + + + + + + + + + + + + {30c467e8-2eee-41e5-be01-0142a61ba171} + OpenSky.Agent.Simulator + + + + + \ No newline at end of file diff --git a/OpenSky.Agent.UdpXPlane11/Properties/AssemblyInfo.cs b/OpenSky.Agent.UdpXPlane11/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..d429d0e --- /dev/null +++ b/OpenSky.Agent.UdpXPlane11/Properties/AssemblyInfo.cs @@ -0,0 +1,21 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// OpenSky project 2021-2022 +// +// -------------------------------------------------------------------------------------------------------------------- + +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("OpenSky.Agent.UdpXP11")] +[assembly: AssemblyDescription("OpenSky Agent UDP library for X-Plane 11")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("OpenSky")] +[assembly: AssemblyProduct("OpenSky")] +[assembly: AssemblyCopyright("OpenSky project 2021-2022")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("dfbda2b8-5775-4766-be86-d729fcf20de1")] +[assembly: AssemblyVersion("0.4.0")] +[assembly: AssemblyFileVersion("0.4.0")] \ No newline at end of file diff --git a/OpenSky.Agent.UdpXPlane11/UdpXPlane11.cs b/OpenSky.Agent.UdpXPlane11/UdpXPlane11.cs new file mode 100644 index 0000000..5c4e7e8 --- /dev/null +++ b/OpenSky.Agent.UdpXPlane11/UdpXPlane11.cs @@ -0,0 +1,540 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// OpenSky project 2021-2022 +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace OpenSky.Agent.UdpXPlane11 +{ + using System; + using System.Diagnostics; + using System.Linq; + using System.Net.Sockets; + using System.Threading; + + using OpenSky.Agent.Simulator.Enums; + using OpenSky.Agent.Simulator.Models; + using OpenSky.Agent.Simulator.Tools; + using OpenSky.Agent.UdpXPlane11.Models; + + using OpenSkyApi; + + using XPlaneConnector; + using XPlaneConnector.DataRefs; + + using Simulator = Simulator.Simulator; + + /// ------------------------------------------------------------------------------------------------- + /// + /// UDP client for X-Plane 11. + /// + /// + /// sushi.at, 02/02/2022. + /// + /// ------------------------------------------------------------------------------------------------- + public class UdpXPlane11 : Simulator + { + /// ------------------------------------------------------------------------------------------------- + /// + /// (Immutable) the simulator IP address. + /// + /// ------------------------------------------------------------------------------------------------- + private readonly string simulatorIPAddress; + + /// ------------------------------------------------------------------------------------------------- + /// + /// (Immutable) the simulator port. + /// + /// ------------------------------------------------------------------------------------------------- + private readonly uint simulatorPort; + + /// ------------------------------------------------------------------------------------------------- + /// + /// The UDP xplane connector. + /// + /// ------------------------------------------------------------------------------------------------- + private XPlaneConnector connector; + + /// ------------------------------------------------------------------------------------------------- + /// + /// Initializes a new instance of the class. + /// + /// + /// sushi.at, 02/02/2022. + /// + /// + /// Name of the simulator IP address. + /// + /// + /// The simulator port. + /// + /// + /// The OpenSky service instance. + /// + /// ------------------------------------------------------------------------------------------------- + public UdpXPlane11(string simulatorIPAddress, uint simulatorPort, OpenSkyService openSkyServiceInstance) : base(openSkyServiceInstance) + { + this.simulatorIPAddress = simulatorIPAddress; + this.simulatorPort = simulatorPort; + + // Initialize empty data structures, to prevent NullReferenceExceptions + this.PrimaryTracking = new PrimaryTracking(); + this.SecondaryTracking = new SecondaryTracking(); + this.FuelTanks = new FuelTanks(); + this.PayloadStations = new PayloadStations(); + this.AircraftIdentity = new AircraftIdentity(); + this.WeightAndBalance = new WeightAndBalance(); + + // Start our worker thread + new Thread(this.ReadFromXPlane) { Name = "UdpXPlane11.ReadFromXPlane" }.Start(); + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Gets the name of the simulator interface. + /// + /// ------------------------------------------------------------------------------------------------- + public static string SimulatorInterfaceName => "UdpXPlane11"; + + /// ------------------------------------------------------------------------------------------------- + /// + /// Gets a value indicating whether the sim is paused (proper pause, not ESC menu and definitely + /// not active pause). + /// + /// + /// ------------------------------------------------------------------------------------------------- + public override bool IsPaused => this.PrimaryTracking?.SimulationRate == 0; + + /// ------------------------------------------------------------------------------------------------- + /// + /// Gets the type of the simulator. + /// + /// + /// ------------------------------------------------------------------------------------------------- + public override OpenSkyApi.Simulator SimulatorType => OpenSkyApi.Simulator.XPlane11; + + /// ------------------------------------------------------------------------------------------------- + /// + /// Pauses the simulator, in XPlane 11 that means setting the simrate to 0. + /// + /// + /// sushi.at, 31/01/2022. + /// + /// + /// True to pause, false to un-pause. + /// + /// + /// ------------------------------------------------------------------------------------------------- + public override void Pause(bool pause) + { + this.connector?.SetDataRefValue(DataRefs.TimeSimSpeed, pause ? 0 : 1); + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Sets the aircraft registration in the simulator. + /// + /// + /// sushi.at, 31/01/2022. + /// + /// + /// The registry to set. + /// + /// + /// ------------------------------------------------------------------------------------------------- + public override void SetAircraftRegistry(string registry) + { + // Doesn't work in XP11 (comes from livery, the dataref is write-able but has no effect) + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Sets fuel and payload to values restored from a save file. + /// + /// + /// sushi.at, 31/01/2022. + /// + /// + /// Thrown when an exception error condition occurs. + /// + /// + /// ------------------------------------------------------------------------------------------------- + public override void SetFuelAndPayloadFromSave() + { + if (this.Connected) + { + if (this.flightLoadingTempModels == null) + { + throw new Exception("No restored fuel and payload station values found."); + } + + Debug.WriteLine("UdpXplane11 setting fuel and payload stations from temp structs restored from save"); + this.connector?.SetDataRefValue(DataRefs.FlightmodelWeightMFuel.DataRef + "[0]", (float)this.flightLoadingTempModels.FuelTanks.FuelTankLeftMainQuantity / 2.205f * (float)this.WeightAndBalance.FuelWeightPerGallon); + this.connector?.SetDataRefValue(DataRefs.FlightmodelWeightMFuel.DataRef + "[1]", (float)this.flightLoadingTempModels.FuelTanks.FuelTankRightMainQuantity / 2.205f * (float)this.WeightAndBalance.FuelWeightPerGallon); + this.connector?.SetDataRefValue(DataRefs.FlightmodelWeightMFuel.DataRef + "[2]", (float)this.flightLoadingTempModels.FuelTanks.FuelTankLeftTipQuantity / 2.205f * (float)this.WeightAndBalance.FuelWeightPerGallon); + this.connector?.SetDataRefValue(DataRefs.FlightmodelWeightMFuel.DataRef + "[3]", (float)this.flightLoadingTempModels.FuelTanks.FuelTankRightTipQuantity / 2.205f * (float)this.WeightAndBalance.FuelWeightPerGallon); + this.connector?.SetDataRefValue(DataRefs.FlightmodelWeightMFuel.DataRef + "[4]", (float)this.flightLoadingTempModels.FuelTanks.FuelTankLeftAuxQuantity / 2.205f * (float)this.WeightAndBalance.FuelWeightPerGallon); + this.connector?.SetDataRefValue(DataRefs.FlightmodelWeightMFuel.DataRef + "[5]", (float)this.flightLoadingTempModels.FuelTanks.FuelTankRightAuxQuantity / 2.205f * (float)this.WeightAndBalance.FuelWeightPerGallon); + this.connector?.SetDataRefValue(DataRefs.FlightmodelWeightMFuel.DataRef + "[6]", (float)this.flightLoadingTempModels.FuelTanks.FuelTankCenterQuantity / 2.205f * (float)this.WeightAndBalance.FuelWeightPerGallon); + this.connector?.SetDataRefValue(DataRefs.FlightmodelWeightMFuel.DataRef + "[7]", (float)this.flightLoadingTempModels.FuelTanks.FuelTankCenter2Quantity / 2.205f * (float)this.WeightAndBalance.FuelWeightPerGallon); + this.connector?.SetDataRefValue(DataRefs.FlightmodelWeightMFuel.DataRef + "[8]", (float)this.flightLoadingTempModels.FuelTanks.FuelTankCenter3Quantity / 2.205f * (float)this.WeightAndBalance.FuelWeightPerGallon); + this.connector?.SetDataRefValue(DataRefs.FlightmodelWeightMFixed, (float)this.flightLoadingTempModels.PayloadStations.Weight1 / 2.205f); + + this.RefreshModelNow(Requests.FuelTanks); + this.RefreshModelNow(Requests.PayloadStations); + } + else + { + throw new Exception("Not connected to sim!"); + } + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Sets the fuel tanks quantities in the simulator. + /// + /// + /// sushi.at, 31/01/2022. + /// + /// + /// The new fuel tank quantities to set. + /// + /// + /// ------------------------------------------------------------------------------------------------- + public override void SetFuelTanks(FuelTanks newFuelTanks) + { + this.connector?.SetDataRefValue(DataRefs.FlightmodelWeightMFuel.DataRef + "[0]", (float)newFuelTanks.FuelTankLeftMainQuantity / 2.205f * (float)this.WeightAndBalance.FuelWeightPerGallon); + this.connector?.SetDataRefValue(DataRefs.FlightmodelWeightMFuel.DataRef + "[1]", (float)newFuelTanks.FuelTankRightMainQuantity / 2.205f * (float)this.WeightAndBalance.FuelWeightPerGallon); + this.connector?.SetDataRefValue(DataRefs.FlightmodelWeightMFuel.DataRef + "[2]", (float)newFuelTanks.FuelTankLeftTipQuantity / 2.205f * (float)this.WeightAndBalance.FuelWeightPerGallon); + this.connector?.SetDataRefValue(DataRefs.FlightmodelWeightMFuel.DataRef + "[3]", (float)newFuelTanks.FuelTankRightTipQuantity / 2.205f * (float)this.WeightAndBalance.FuelWeightPerGallon); + this.connector?.SetDataRefValue(DataRefs.FlightmodelWeightMFuel.DataRef + "[4]", (float)newFuelTanks.FuelTankLeftAuxQuantity / 2.205f * (float)this.WeightAndBalance.FuelWeightPerGallon); + this.connector?.SetDataRefValue(DataRefs.FlightmodelWeightMFuel.DataRef + "[5]", (float)newFuelTanks.FuelTankRightAuxQuantity / 2.205f * (float)this.WeightAndBalance.FuelWeightPerGallon); + this.connector?.SetDataRefValue(DataRefs.FlightmodelWeightMFuel.DataRef + "[6]", (float)newFuelTanks.FuelTankCenterQuantity / 2.205f * (float)this.WeightAndBalance.FuelWeightPerGallon); + this.connector?.SetDataRefValue(DataRefs.FlightmodelWeightMFuel.DataRef + "[7]", (float)newFuelTanks.FuelTankCenter2Quantity / 2.205f * (float)this.WeightAndBalance.FuelWeightPerGallon); + this.connector?.SetDataRefValue(DataRefs.FlightmodelWeightMFuel.DataRef + "[8]", (float)newFuelTanks.FuelTankCenter3Quantity / 2.205f * (float)this.WeightAndBalance.FuelWeightPerGallon); + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Sets the payload station weights in the simulator. + /// + /// + /// sushi.at, 31/01/2022. + /// + /// + /// The new payload station weights to set. + /// + /// + /// ------------------------------------------------------------------------------------------------- + public override void SetPayloadStations(PayloadStations newPayloadStations) + { + this.connector?.SetDataRefValue(DataRefs.FlightmodelWeightMFixed, (float)newPayloadStations.Weight1 / 2.205f); + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Sets slew to on or off. + /// + /// + /// sushi.at, 31/01/2022. + /// + /// + /// True to enable, false to disable. + /// + /// + /// ------------------------------------------------------------------------------------------------- + public override void SetSlew(bool enable) + { + // Slew does not exist in XPlane11, so we are going to use pause instead + this.connector?.SetDataRefValue(DataRefs.TimeSimSpeed, enable ? 0 : 1); + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Sets the UTC time in the sim. + /// + /// + /// sushi.at, 31/01/2022. + /// + /// + /// The new UTC time. + /// + /// + /// ------------------------------------------------------------------------------------------------- + public override void SetTime(DateTime time) + { + this.connector?.SetDataRefValue(DataRefs.Cockpit2ClockTimerCurrentDay, time.Day); + this.connector?.SetDataRefValue(DataRefs.Cockpit2ClockTimerCurrentMonth, time.Month); + this.connector?.SetDataRefValue(DataRefs.TimeZuluTimeSec, time.Hour * 60 * 60 + time.Minute * 60 + time.Second); + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Slew plane to flight position - flight object determines where, either moves plane to + /// starting position or to last reported flight position. + /// + /// + /// sushi.at, 31/01/2022. + /// + /// + /// Thrown when an exception error condition occurs. + /// + /// + /// ------------------------------------------------------------------------------------------------- + public override void SlewPlaneToFlightPosition() + { + if (this.Connected) + { + if (this.Flight == null) + { + throw new Exception("Not currently tracking a flight!"); + } + + // Slew into starting position at origin airport? + if (!this.Flight.Resume) + { + throw new Exception("Slew to Origin is currently not supported for X-Plane 11. Please use the map in the simulator."); + + //if (!this.PrimaryTracking.OnGround || this.PrimaryTracking.GroundSpeed > 1) + //{ + // throw new Exception("Plane needs to be stationary on the ground for this!"); + //} + + //if (!this.PrimaryTracking.SlewActive) + //{ + // this.SetSlew(true); + //} + + //var dg = new XPDatagram(); + //dg.Add("POSI"); + //dg.Bytes.Add(0); // aircraft index, 0 is the player's + + //// Long, Lat and Alt are doubles + //{ + // var doubleBytes = BitConverter.GetBytes(this.Flight?.Origin.Latitude ?? 0); + // dg.Bytes.AddRange(BitConverter.IsLittleEndian ? doubleBytes : doubleBytes.Reverse()); + //} + //{ + // var doubleBytes = BitConverter.GetBytes(this.Flight?.Origin.Longitude ?? 0); + // dg.Bytes.AddRange(BitConverter.IsLittleEndian ? doubleBytes : doubleBytes.Reverse()); + //} + //{ + // var doubleBytes = BitConverter.GetBytes(-998d); // TODO what would you put there without knowing the ground elevation at the target coordinates? + // dg.Bytes.AddRange(BitConverter.IsLittleEndian ? doubleBytes : doubleBytes.Reverse()); + //} + + ////dg.Add((float)(this.Flight?.Origin.Latitude ?? 0)); + ////dg.Add((float)(this.Flight?.Origin.Longitude ?? 0)); + ////dg.Add(-998f); + + //dg.Add(-998f); + //dg.Add(-998f); + //dg.Add(-998f); + //dg.Add(-998f); + ////dg.FillTo(509); + + //var client = new UdpClient(); + //client.Connect(this.simulatorIPAddress, 49009); + //client.Send(dg.Get(), dg.Len); + } + else + { + if (this.flightLoadingTempModels == null) + { + throw new Exception("No resume position available."); + } + + if (!this.PrimaryTracking.SlewActive) + { + this.SetSlew(true); + } + + var dg = new XPDatagram(); + dg.Add("POSI"); + dg.Bytes.Add(0); // aircraft index, 0 is the player's + + // Long, Lat and Alt are doubles + { + var doubleBytes = BitConverter.GetBytes(this.flightLoadingTempModels.SlewAircraftIntoPosition.Latitude); + dg.Bytes.AddRange(BitConverter.IsLittleEndian ? doubleBytes : doubleBytes.Reverse()); + } + { + var doubleBytes = BitConverter.GetBytes(this.flightLoadingTempModels.SlewAircraftIntoPosition.Longitude); + dg.Bytes.AddRange(BitConverter.IsLittleEndian ? doubleBytes : doubleBytes.Reverse()); + } + { + var doubleBytes = BitConverter.GetBytes(this.flightLoadingTempModels.SlewAircraftIntoPosition.Altitude / 3.281); + dg.Bytes.AddRange(BitConverter.IsLittleEndian ? doubleBytes : doubleBytes.Reverse()); + } + + dg.Add((float)this.flightLoadingTempModels.SlewAircraftIntoPosition.PitchAngle); + dg.Add((float)this.flightLoadingTempModels.SlewAircraftIntoPosition.BankAngle); + dg.Add((float)this.flightLoadingTempModels.SlewAircraftIntoPosition.Heading); + dg.Add(-998f); // Don't change gear + + var client = new UdpClient(); + client.Connect(this.simulatorIPAddress, 49009); + client.Send(dg.Get(), dg.Len); + } + } + else + { + throw new Exception("Not connected to sim!"); + } + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Read from X-Plane 11 UDP connector on background thread. + /// + /// + /// sushi.at, 02/02/2022. + /// + /// ------------------------------------------------------------------------------------------------- + private void ReadFromXPlane() + { + try + { + // Create dataref handlers + var primaryTracking = new PrimaryTrackingDataRef(); + var secondaryTracking = new SecondaryTrackingDataRef(); + var aircraftIdentity = new AircraftIdentityDataRef(); + var fuelTanks = new FuelTanksDataRef(); + var payloadStations = new PayloadStationsDataRef(); + var weightAndBalance = new WeightAndBalanceDataRef(); + var landingAnalysis = new LandingAnalysisDataRef(); + + while (!this.close) + { + try + { + if ((DateTime.Now - (this.connector?.LastReceive ?? DateTime.MinValue)) > TimeSpan.FromSeconds(10)) + { + this.Connected = false; + try + { + this.connector?.Stop(); + } + catch + { + // Ignore + } + + this.connector = new XPlaneConnector(this.simulatorIPAddress, (int)this.simulatorPort); + primaryTracking.RegisterWithConnector(this.connector, this.SampleRates[Requests.Primary]); + secondaryTracking.RegisterWithConnector(this.connector, this.SampleRates[Requests.Secondary]); + aircraftIdentity.RegisterWithConnector(this.connector, this.SampleRates[Requests.AircraftIdentity]); + fuelTanks.RegisterWithConnector(this.connector, this.SampleRates[Requests.FuelTanks]); + payloadStations.RegisterWithConnector(this.connector, this.SampleRates[Requests.PayloadStations]); + weightAndBalance.RegisterWithConnector(this.connector, this.SampleRates[Requests.WeightAndBalance]); + landingAnalysis.RegisterWithConnector(this.connector, this.SampleRates[Requests.LandingAnalysis]); + this.connector.Start(); + } + else if ((DateTime.Now - this.connector.LastReceive) < TimeSpan.FromSeconds(3)) + { + this.Connected = true; + } + + if (this.Connected) + { + // Primary tracking + { + var clone = primaryTracking.Clone(); + this.primaryTrackingProcessingQueue.Enqueue(new ProcessPrimaryTracking { Old = this.PrimaryTracking, New = clone }); + this.OnPropertyChanged(nameof(this.PrimaryTrackingProcessingQueueLength)); + + this.PrimaryTracking = clone; + this.LastReceivedTimes[Requests.Primary] = DateTime.UtcNow; + } + foreach (Requests request in Enum.GetValues(typeof(Requests))) + { + if (this.SampleRates.ContainsKey(request)) + { + var lastTime = this.LastReceivedTimes[request]; + if (request != Requests.Primary && (!lastTime.HasValue || (DateTime.UtcNow - lastTime.Value).TotalMilliseconds > this.SampleRates[request])) + { + if (request == Requests.Secondary) + { + var clone = secondaryTracking.Clone(); + this.secondaryTrackingProcessingQueue.Enqueue(new ProcessSecondaryTracking { Old = this.SecondaryTracking, New = clone }); + this.OnPropertyChanged(nameof(this.SecondaryTrackingProcessingQueueLength)); + + this.SecondaryTracking = clone; + this.LastReceivedTimes[Requests.Secondary] = DateTime.UtcNow; + } + + if (request == Requests.AircraftIdentity) + { + this.AircraftIdentity = aircraftIdentity.Clone(); + this.LastReceivedTimes[Requests.AircraftIdentity] = DateTime.UtcNow; + new Thread(this.ProcessAircraftIdentity) { Name = "OpenSky.ProcessAircraftIdentity" }.Start(); + } + + if (request == Requests.FuelTanks) + { + this.FuelTanks = fuelTanks.Clone(); + this.LastReceivedTimes[Requests.FuelTanks] = DateTime.UtcNow; + } + + if (request == Requests.PayloadStations) + { + new Thread( + () => + { + var clone = payloadStations.Clone(); + this.ProcessPayloadStations(this.PayloadStations, clone); + this.PayloadStations = clone; + }) + { Name = "OpenSky.ProcessPayloadStations" }.Start(); + this.LastReceivedTimes[Requests.PayloadStations] = DateTime.UtcNow; + } + + if (request == Requests.WeightAndBalance) + { + new Thread( + () => + { + var clone = weightAndBalance.Clone(); + this.ProcessWeightAndBalance(this.WeightAndBalance, clone); + this.WeightAndBalance = clone; + }) + { Name = "OpenSky.ProcessWeightAndBalance" }.Start(); + this.LastReceivedTimes[Requests.WeightAndBalance] = DateTime.UtcNow; + } + + if (request == Requests.LandingAnalysis) + { + var clone = landingAnalysis.Clone(); + this.landingAnalysisProcessingQueue.Enqueue(new ProcessLandingAnalysis { Old = this.LandingAnalysis, New = clone }); + this.OnPropertyChanged(nameof(this.LandingAnalysisProcessingQueueLength)); + + this.LandingAnalysis = clone; + this.LastReceivedTimes[Requests.LandingAnalysis] = DateTime.UtcNow; + } + } + } + } + + Thread.Sleep(Math.Min(this.SampleRates[Requests.Primary], this.SampleRates[Requests.LandingAnalysis])); + } + else + { + SleepScheduler.SleepFor(TimeSpan.FromSeconds(10)); + } + } + catch (Exception ex) + { + Debug.WriteLine(ex); + SleepScheduler.SleepFor(TimeSpan.FromSeconds(this.Flight == null ? 30 : 5)); + } + } + + this.connector.Stop(); + } + catch (Exception ex) + { + Debug.WriteLine("Error managing XPlane connector: " + ex); + } + } + } +} \ No newline at end of file diff --git a/OpenSky.Agent.UdpXPlane11/packages.config b/OpenSky.Agent.UdpXPlane11/packages.config new file mode 100644 index 0000000..945aec4 --- /dev/null +++ b/OpenSky.Agent.UdpXPlane11/packages.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/OpenSky.Agent.sln b/OpenSky.Agent.sln index b5297e4..6c9c735 100644 --- a/OpenSky.Agent.sln +++ b/OpenSky.Agent.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.31025.194 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.32112.339 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenSky.Agent", "OpenSky.Agent\OpenSky.Agent.csproj", "{26FAA45D-B4A3-4228-8AF3-E5811BCFC765}" EndProject @@ -17,6 +17,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenSky.Agent.SimConnectMSF EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenSky.Agent.Simulator", "OpenSky.Agent.Simulator\OpenSky.Agent.Simulator.csproj", "{30C467E8-2EEE-41E5-BE01-0142A61BA171}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenSky.Agent.UdpXPlane11", "OpenSky.Agent.UdpXPlane11\OpenSky.Agent.UdpXPlane11.csproj", "{DFBDA2B8-5775-4766-BE86-D729FCF20DE1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -55,6 +57,14 @@ Global {30C467E8-2EEE-41E5-BE01-0142A61BA171}.Release|Any CPU.Build.0 = Release|Any CPU {30C467E8-2EEE-41E5-BE01-0142A61BA171}.Release|x86.ActiveCfg = Release|Any CPU {30C467E8-2EEE-41E5-BE01-0142A61BA171}.Release|x86.Build.0 = Release|Any CPU + {DFBDA2B8-5775-4766-BE86-D729FCF20DE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DFBDA2B8-5775-4766-BE86-D729FCF20DE1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DFBDA2B8-5775-4766-BE86-D729FCF20DE1}.Debug|x86.ActiveCfg = Debug|Any CPU + {DFBDA2B8-5775-4766-BE86-D729FCF20DE1}.Debug|x86.Build.0 = Debug|Any CPU + {DFBDA2B8-5775-4766-BE86-D729FCF20DE1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DFBDA2B8-5775-4766-BE86-D729FCF20DE1}.Release|Any CPU.Build.0 = Release|Any CPU + {DFBDA2B8-5775-4766-BE86-D729FCF20DE1}.Release|x86.ActiveCfg = Release|Any CPU + {DFBDA2B8-5775-4766-BE86-D729FCF20DE1}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/OpenSky.Agent.sln.DotSettings b/OpenSky.Agent.sln.DotSettings index fee4d16..34f700b 100644 --- a/OpenSky.Agent.sln.DotSettings +++ b/OpenSky.Agent.sln.DotSettings @@ -527,6 +527,7 @@ OpenSky project $CURRENT_YEAR$ WB WCF XML + XP XY ZIP <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> @@ -586,14 +587,18 @@ OpenSky project $CURRENT_YEAR$ True True True + True + True True True True True True True + True True True True True - True \ No newline at end of file + True + True \ No newline at end of file diff --git a/OpenSky.Agent/App.xaml.cs b/OpenSky.Agent/App.xaml.cs index 4687568..64b7a0a 100644 --- a/OpenSky.Agent/App.xaml.cs +++ b/OpenSky.Agent/App.xaml.cs @@ -251,6 +251,11 @@ protected override void OnStartup([NotNull] StartupEventArgs e) Simulator.Simulator.SetSimulatorInstance(new SimConnect(Settings.Default.SimConnectHostName, Settings.Default.SimConnectPort, AgentOpenSkyService.Instance)); } + if (UdpXPlane11.UdpXPlane11.SimulatorInterfaceName.Equals(simulatorInterface, StringComparison.InvariantCultureIgnoreCase)) + { + Simulator.Simulator.SetSimulatorInstance(new UdpXPlane11.UdpXPlane11(Settings.Default.XPlaneIPAddress, Settings.Default.XPlanePort, AgentOpenSkyService.Instance)); + } + if (Simulator.Simulator.Instance != null) { Simulator.Simulator.Instance.LandingReported += (_, landingReportNotification) => @@ -324,7 +329,7 @@ private static void AppDispatcherUnhandledException( private void PerformShutdown() { // Check if we are currently tracking a flight - if (Agent.Simulator.Simulator.Instance.TrackingStatus is TrackingStatus.GroundOperations or TrackingStatus.Tracking) + if (Simulator.Simulator.Instance.TrackingStatus is TrackingStatus.GroundOperations or TrackingStatus.Tracking) { Debug.WriteLine("User requested shutdown, but flight tracking is still in progress..."); MessageBoxResult? answer = MessageBoxResult.None; diff --git a/OpenSky.Agent/OpenSky.Agent.csproj b/OpenSky.Agent/OpenSky.Agent.csproj index 8c987f3..a085d37 100644 --- a/OpenSky.Agent/OpenSky.Agent.csproj +++ b/OpenSky.Agent/OpenSky.Agent.csproj @@ -392,13 +392,13 @@ 0.1.5 - 19.4.0.48 + 19.4.0.50 - 19.4.0.48 + 19.4.0.50 - 19.4.0.48 + 19.4.0.50 2.7.4 @@ -444,10 +444,12 @@ {30c467e8-2eee-41e5-be01-0142a61ba171} OpenSky.Agent.Simulator + + {dfbda2b8-5775-4766-be86-d729fcf20de1} + OpenSky.Agent.UdpXPlane11 + - - - + call "$(ProjectDir)pre-build.bat" "$(ProjectDir)" "$(ConfigurationName)" diff --git a/OpenSky.Agent/Properties/Settings.Designer.cs b/OpenSky.Agent/Properties/Settings.Designer.cs index 512fdf9..7901847 100644 --- a/OpenSky.Agent/Properties/Settings.Designer.cs +++ b/OpenSky.Agent/Properties/Settings.Designer.cs @@ -203,25 +203,25 @@ public string SimulatorInterface { [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("localhost")] - public string SimulatorHostName { + [global::System.Configuration.DefaultSettingValueAttribute("127.0.0.1")] + public string XPlaneIPAddress { get { - return ((string)(this["SimulatorHostName"])); + return ((string)(this["XPlaneIPAddress"])); } set { - this["SimulatorHostName"] = value; + this["XPlaneIPAddress"] = value; } } [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("500")] - public string SimulatorPort { + [global::System.Configuration.DefaultSettingValueAttribute("49000")] + public uint XPlanePort { get { - return ((string)(this["SimulatorPort"])); + return ((uint)(this["XPlanePort"])); } set { - this["SimulatorPort"] = value; + this["XPlanePort"] = value; } } } diff --git a/OpenSky.Agent/Properties/Settings.settings b/OpenSky.Agent/Properties/Settings.settings index 1935d6b..deb8066 100644 --- a/OpenSky.Agent/Properties/Settings.settings +++ b/OpenSky.Agent/Properties/Settings.settings @@ -50,11 +50,11 @@ SimConnectMSFS - - localhost + + 127.0.0.1 - - 500 + + 49000 \ No newline at end of file diff --git a/OpenSky.Agent/Views/Models/AircraftTypesViewModel.cs b/OpenSky.Agent/Views/Models/AircraftTypesViewModel.cs index 0043387..b2f5153 100644 --- a/OpenSky.Agent/Views/Models/AircraftTypesViewModel.cs +++ b/OpenSky.Agent/Views/Models/AircraftTypesViewModel.cs @@ -996,7 +996,8 @@ private void AddAircraftType() MinPrice = this.MinimumPrice, MaxPrice = this.MaximumPrice, MaxPayloadDeltaAllowed = this.MaxPayloadDeltaAllowed, - Comments = this.Comments + Comments = this.Comments, + Simulator = this.Simulator.SimulatorType }; this.LoadingText = "Adding new aircraft type"; diff --git a/OpenSky.Agent/Views/Models/FlightTrackingViewModel.cs b/OpenSky.Agent/Views/Models/FlightTrackingViewModel.cs index 97af1a6..9e52f3c 100644 --- a/OpenSky.Agent/Views/Models/FlightTrackingViewModel.cs +++ b/OpenSky.Agent/Views/Models/FlightTrackingViewModel.cs @@ -898,7 +898,7 @@ private void StartTracking() } // Set the plane registration - this.Simulator.SetAircraftRegistry(this.Simulator.Flight?.Aircraft.Registry); + this.Simulator.SetAircraftRegistry(this.Simulator.Flight?.Aircraft.Registry.RemoveSimPrefix()); // Wait a bit to make sure all structs have updated, especially time in sim Thread.Sleep(this.Simulator.SampleRates[Requests.Secondary] + 1000); @@ -1093,7 +1093,7 @@ private void StartTracking() this.Simulator.SetFuelAndPayloadFromSave(); // Set the plane registration - this.Simulator.SetAircraftRegistry(this.Simulator.Flight?.Aircraft.Registry); + this.Simulator.SetAircraftRegistry(this.Simulator.Flight?.Aircraft.Registry.RemoveSimPrefix()); // Start five second countdown? if (this.Simulator.PrimaryTracking.SlewActive) diff --git a/OpenSky.Agent/Views/Models/SettingsViewModel.cs b/OpenSky.Agent/Views/Models/SettingsViewModel.cs index e43f5dc..14f8f7a 100644 --- a/OpenSky.Agent/Views/Models/SettingsViewModel.cs +++ b/OpenSky.Agent/Views/Models/SettingsViewModel.cs @@ -29,10 +29,11 @@ namespace OpenSky.Agent.Views.Models using OpenSky.Agent.Simulator.Models; using OpenSky.Agent.Simulator.Tools; using OpenSky.Agent.Tools; + using OpenSky.Agent.UdpXPlane11; using OpenSkyApi; - using Simulator = Simulator.Simulator; + using Simulator = OpenSky.Agent.Simulator.Simulator; /// ------------------------------------------------------------------------------------------------- /// @@ -103,45 +104,45 @@ public class SettingsViewModel : ViewModel /// ------------------------------------------------------------------------------------------------- /// - /// The simulator host name. + /// The simconnect simulator host name. /// /// ------------------------------------------------------------------------------------------------- - private string simulatorHostName; + private string simConnectHostName; /// ------------------------------------------------------------------------------------------------- /// - /// The simulator port. + /// True if simConnect MSFS is checked. /// /// ------------------------------------------------------------------------------------------------- - private uint simulatorPort; + private bool simConnectMSFSChecked; /// ------------------------------------------------------------------------------------------------- /// - /// True if simConnect MSFS is checked. + /// The simconnect simulator port. /// /// ------------------------------------------------------------------------------------------------- - private bool simConnectMSFSChecked; + private uint simConnectPort; /// ------------------------------------------------------------------------------------------------- /// - /// Gets or sets a value indicating whether simConnect MSFS is checked. + /// True if UDP X-Plane 11 is checked. /// /// ------------------------------------------------------------------------------------------------- - public bool SimConnectMSFSChecked - { - get => this.simConnectMSFSChecked; + private bool udpXplaneChecked; - set - { - if (Equals(this.simConnectMSFSChecked, value)) - { - return; - } + /// ------------------------------------------------------------------------------------------------- + /// + /// Name of the X-Plane 11 IP address. + /// + /// ------------------------------------------------------------------------------------------------- + private string xplaneIPAddress; - this.simConnectMSFSChecked=value; - this.NotifyPropertyChanged(); - } - } + /// ------------------------------------------------------------------------------------------------- + /// + /// The X-Plane 11 UDP port. + /// + /// ------------------------------------------------------------------------------------------------- + private uint xplanePort; /// ------------------------------------------------------------------------------------------------- /// @@ -177,8 +178,10 @@ public SettingsViewModel() // Load settings Settings.Default.Reload(); - this.SimulatorHostName = Settings.Default.SimConnectHostName; - this.SimulatorPort = Settings.Default.SimConnectPort; + this.SimConnectHostName = Settings.Default.SimConnectHostName; + this.SimConnectPort = Settings.Default.SimConnectPort; + this.XplaneIPAddress = Settings.Default.XPlaneIPAddress; + this.XplanePort = Settings.Default.XPlanePort; this.BingMapsKey = UserSessionService.Instance.LinkedAccounts?.BingMapsKey; this.SimBriefUsername = UserSessionService.Instance.LinkedAccounts?.SimbriefUsername; this.SelectedLandingReportNotification = LandingReportNotification.Parse(Settings.Default.LandingReportNotification); @@ -198,6 +201,11 @@ public SettingsViewModel() this.SimConnectMSFSChecked = true; } + if (UdpXPlane11.SimulatorInterfaceName.Equals(simulatorInterface, StringComparison.InvariantCultureIgnoreCase)) + { + this.UdpXplaneChecked = true; + } + // Load profile image if (UserSessionService.Instance.AccountOverview?.ProfileImage?.Length > 0) { @@ -454,21 +462,43 @@ public string SimBriefUsername /// ------------------------------------------------------------------------------------------------- /// - /// Gets or sets the simulator host name. + /// Gets or sets the simconnect simulator host name. + /// + /// ------------------------------------------------------------------------------------------------- + public string SimConnectHostName + { + get => this.simConnectHostName; + + set + { + if (Equals(this.simConnectHostName, value)) + { + return; + } + + this.simConnectHostName = value; + this.NotifyPropertyChanged(); + this.IsDirty = true; + } + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Gets or sets a value indicating whether simConnect MSFS is checked. /// /// ------------------------------------------------------------------------------------------------- - public string SimulatorHostName + public bool SimConnectMSFSChecked { - get => this.simulatorHostName; + get => this.simConnectMSFSChecked; set { - if (Equals(this.simulatorHostName, value)) + if (Equals(this.simConnectMSFSChecked, value)) { return; } - this.simulatorHostName = value; + this.simConnectMSFSChecked = value; this.NotifyPropertyChanged(); this.IsDirty = true; } @@ -476,21 +506,21 @@ public string SimulatorHostName /// ------------------------------------------------------------------------------------------------- /// - /// Gets or sets the simulator port. + /// Gets or sets the simconnect simulator port. /// /// ------------------------------------------------------------------------------------------------- - public uint SimulatorPort + public uint SimConnectPort { - get => this.simulatorPort; + get => this.simConnectPort; set { - if (Equals(this.simulatorPort, value)) + if (Equals(this.simConnectPort, value)) { return; } - this.simulatorPort = value; + this.simConnectPort = value; this.NotifyPropertyChanged(); this.IsDirty = true; } @@ -517,6 +547,27 @@ public uint SimulatorPort /// ------------------------------------------------------------------------------------------------- public List TextToSpeechVoices { get; } + /// ------------------------------------------------------------------------------------------------- + /// + /// Gets or sets a value indicating whether UDP X-Plane 11 is checked. + /// + /// ------------------------------------------------------------------------------------------------- + public bool UdpXplaneChecked + { + get => this.udpXplaneChecked; + set + { + if (Equals(this.udpXplaneChecked, value)) + { + return; + } + + this.udpXplaneChecked = value; + this.NotifyPropertyChanged(); + this.IsDirty = true; + } + } + /// ------------------------------------------------------------------------------------------------- /// /// Gets the update profile image command. @@ -531,6 +582,48 @@ public uint SimulatorPort /// ------------------------------------------------------------------------------------------------- public UserSessionService UserSession => UserSessionService.Instance; + /// ------------------------------------------------------------------------------------------------- + /// + /// Gets or sets the IP address of the X-Plane 11 host. + /// + /// ------------------------------------------------------------------------------------------------- + public string XplaneIPAddress + { + get => this.xplaneIPAddress; + set + { + if (Equals(this.xplaneIPAddress, value)) + { + return; + } + + this.xplaneIPAddress = value; + this.NotifyPropertyChanged(); + this.IsDirty = true; + } + } + + /// ------------------------------------------------------------------------------------------------- + /// + /// Gets or sets the X-Plane 11 port. + /// + /// ------------------------------------------------------------------------------------------------- + public uint XplanePort + { + get => this.xplanePort; + set + { + if (Equals(this.xplanePort, value)) + { + return; + } + + this.xplanePort = value; + this.NotifyPropertyChanged(); + this.IsDirty = true; + } + } + /// ------------------------------------------------------------------------------------------------- /// /// Change password (opens page in browser). @@ -639,8 +732,8 @@ private void RestoreDefaults() if (messageBox.Result == ExtendedMessageBoxResult.Yes) { Debug.WriteLine("Resetting settings to defaults..."); - this.SimulatorHostName = "localhost"; - this.SimulatorPort = 500; + this.SimConnectHostName = "localhost"; + this.SimConnectPort = 500; } }; this.ViewReference.ShowMessageBox(messageBox); @@ -666,9 +759,19 @@ private void SaveSettings() if (this.SimConnectMSFSChecked) { Settings.Default.SimulatorInterface = SimConnect.SimulatorInterfaceName; - if (Simulator.Instance == null || Simulator.Instance.GetType() != typeof(SimConnect)) + if (Simulator.Instance == null || Simulator.Instance.GetType() != typeof(SimConnect) || !Equals(Settings.Default.SimConnectHostName, this.SimConnectHostName) || Settings.Default.SimConnectPort != this.SimConnectPort) + { + Simulator.SetSimulatorInstance(new SimConnect(this.SimConnectHostName, this.SimConnectPort, AgentOpenSkyService.Instance)); + simulatorWasChanged = true; + } + } + + if (this.UdpXplaneChecked) + { + Settings.Default.SimulatorInterface = UdpXPlane11.SimulatorInterfaceName; + if (Simulator.Instance == null || Simulator.Instance.GetType() != typeof(UdpXPlane11) || !Equals(Settings.Default.XPlaneIPAddress, this.XplaneIPAddress) || Settings.Default.XPlanePort != this.XplanePort) { - Simulator.SetSimulatorInstance(new SimConnect(this.SimulatorHostName, this.SimulatorPort, AgentOpenSkyService.Instance)); + Simulator.SetSimulatorInstance(new UdpXPlane11(this.XplaneIPAddress, this.XplanePort, AgentOpenSkyService.Instance)); simulatorWasChanged = true; } } @@ -685,8 +788,10 @@ private void SaveSettings() }; } - Settings.Default.SimConnectHostName = this.SimulatorHostName; - Settings.Default.SimConnectPort = this.SimulatorPort; + Settings.Default.SimConnectHostName = this.SimConnectHostName; + Settings.Default.SimConnectPort = this.SimConnectPort; + Settings.Default.XPlaneIPAddress = this.XplaneIPAddress; + Settings.Default.XPlanePort = this.XplanePort; Settings.Default.LandingReportNotification = this.SelectedLandingReportNotification?.NotificationID ?? 1; Settings.Default.SoundPack = this.SelectedSoundPack; SpeechSoundPacks.Instance.SelectedSoundPack = this.SelectedSoundPack; diff --git a/OpenSky.Agent/Views/Models/StartupViewModel.cs b/OpenSky.Agent/Views/Models/StartupViewModel.cs index 4cce746..be31e97 100644 --- a/OpenSky.Agent/Views/Models/StartupViewModel.cs +++ b/OpenSky.Agent/Views/Models/StartupViewModel.cs @@ -367,7 +367,7 @@ private void SimConnectPropertyChanged(object sender, PropertyChangedEventArgs e this.NotificationIcon = this.greyIcon; this.NotificationStatusString = "OpenSky is trying to connect to the simulator..."; - this.DiscordRpcClient.SetPresence(new RichPresence + this.DiscordRpcClient?.SetPresence(new RichPresence { State = "Not Connected", Details = "Trying to connect to simulator", @@ -388,7 +388,7 @@ private void SimConnectPropertyChanged(object sender, PropertyChangedEventArgs e this.NotificationIcon = this.openSkyIcon; this.NotificationStatusString = "OpenSky is connected to the sim but not tracking a flight"; - this.DiscordRpcClient.SetPresence(new RichPresence + this.DiscordRpcClient?.SetPresence(new RichPresence { State = "Idle", Details = "Waiting for a flight", @@ -405,7 +405,7 @@ private void SimConnectPropertyChanged(object sender, PropertyChangedEventArgs e this.NotificationIcon = this.openSkyIcon; this.NotificationStatusString = $"OpenSky is preparing to track flight {Agent.Simulator.Simulator.Instance.Flight?.FullFlightNumber}"; - this.DiscordRpcClient.SetPresence(new RichPresence + this.DiscordRpcClient?.SetPresence(new RichPresence { State = Agent.Simulator.Simulator.Instance.TrackingStatus.ToString(), Details = $"Preparing flight {Agent.Simulator.Simulator.Instance.Flight?.FullFlightNumber}", @@ -423,7 +423,7 @@ private void SimConnectPropertyChanged(object sender, PropertyChangedEventArgs e this.NotificationIcon = this.pauseIcon; this.NotificationStatusString = $"OpenSky tracking and your flight {Agent.Simulator.Simulator.Instance.Flight?.FullFlightNumber} are paused"; - this.DiscordRpcClient.SetPresence(new RichPresence + this.DiscordRpcClient?.SetPresence(new RichPresence { State = $"Paused, {Agent.Simulator.Simulator.Instance.FlightPhase}", Details = $"Tracking flight {Agent.Simulator.Simulator.Instance.Flight?.FullFlightNumber}", @@ -442,7 +442,7 @@ private void SimConnectPropertyChanged(object sender, PropertyChangedEventArgs e this.redFlashing = true; this.NotificationStatusString = $"OpenSky is tracking your flight {Agent.Simulator.Simulator.Instance.Flight?.FullFlightNumber}"; - this.DiscordRpcClient.SetPresence(new RichPresence + this.DiscordRpcClient?.SetPresence(new RichPresence { State = $"Recording, {Agent.Simulator.Simulator.Instance.FlightPhase}", Details = $"Tracking flight {Agent.Simulator.Simulator.Instance.Flight?.FullFlightNumber}", diff --git a/OpenSky.Agent/Views/Settings.xaml b/OpenSky.Agent/Views/Settings.xaml index 3f9a495..7bc8685 100644 --- a/OpenSky.Agent/Views/Settings.xaml +++ b/OpenSky.Agent/Views/Settings.xaml @@ -121,27 +121,54 @@ - - - Microsoft Flight Simulator (SimConnect) - - - - - - - - - - - - Use these settings if you are running the OpenSky Agent on a different computer than your simulator. If you both are running on the same computer leave these settings unchanged. - Simulator host name - Simulator network port - - - - + + + + + + + + Microsoft Flight Simulator (SimConnect) + + + + + + + + + + + + Use these settings if you are running the OpenSky Agent on a different computer than your simulator. If you both are running on the same computer leave these settings unchanged. + Simulator host name + Simulator network port + + + + + + + X-Plane 11 (UDP) + + + + + + + + + + + + Use these settings if you are running the OpenSky Agent on a different computer than your simulator. If you both are running on the same computer leave these settings unchanged. + Simulator IP address + Simulator network port + + + + + Notifications diff --git a/changelog.txt b/changelog.txt index 851b94e..3ac1604 100644 --- a/changelog.txt +++ b/changelog.txt @@ -7,6 +7,7 @@ Version 0.4.0 (ALPHA4) -------------------------------------------------------------------------------------- - Common simulator interface code extracted and separated from SimConnect code - Minor bug fixes and improvements +- Experimental X-Plane 11 support -------------------------------------------------------------------------------------- Version 0.3.7 (ALPHA3)