From 126cb3f3815d9c12bd50387ec22afc1c7c4c28a0 Mon Sep 17 00:00:00 2001 From: Marc Riera Date: Mon, 21 Oct 2024 00:52:15 +0200 Subject: [PATCH] ATC: speed restrictions and AI --- src/Devices/AtcBombardier.cs | 224 +++++++++++++++++++++++++---------- src/Train/Train.cs | 24 +++- 2 files changed, 185 insertions(+), 63 deletions(-) diff --git a/src/Devices/AtcBombardier.cs b/src/Devices/AtcBombardier.cs index 34aa334..46795d8 100644 --- a/src/Devices/AtcBombardier.cs +++ b/src/Devices/AtcBombardier.cs @@ -63,6 +63,40 @@ namespace OpenbveFcmbTrainPlugin /// A counter to keep track of YARD mode idle time, in seconds. private double YardIdleCounter; + /// The maximum speed in YARD mode. + private double YardMaximumSpeed = 20; + + /// Represents an ATC movement permission order. + private class MovementPermission + { + internal Speed LimitBase { get; private set; } + internal Speed LimitNoTraction { get; private set; } + internal Speed LimitService { get; private set; } + internal Speed LimitEmergency { get; private set; } + internal Speed TargetLimit { get; private set; } + internal double TargetLocation { get; private set; } + + internal MovementPermission(Speed limit, Speed target, double targetLocation) + { + LimitBase = limit; + LimitNoTraction = new Speed(limit.MetersPerSecond + 3 / 3.6); + LimitService = new Speed(limit.MetersPerSecond + 6 / 3.6); + LimitEmergency = new Speed(limit.MetersPerSecond + 9 / 3.6); + TargetLimit = target; + TargetLocation = targetLocation; + } + } + + /// The current permission regarding speed and travel distance. + private MovementPermission CurrentPermission; + + /// The ideal power notch for the AI. + private int AiPowerNotch; + + /// The ideal brake notch for the AI. + private int AiBrakeNotch; + + /// Is called when the device state should be updated. /// The current train. /// The current route. @@ -76,70 +110,63 @@ namespace OpenbveFcmbTrainPlugin DeviceState = DeviceStates.Initializing; } + // Update ATC movement permission + UpdatePermission(); + double speed = train.State.Speed.KilometersPerHour; bool stopped = speed < 0.05; - // Speed limit calculation - // By default, use YARD mode speed limit - double limit = 25; - double powerLimit = limit - 2; - double releaseLimit = limit - 5; - double emergencyLimit = limit + 4; - // Speed limit enforcement if (DeviceState == DeviceStates.YARD || DeviceState == DeviceStates.ATP || DeviceState == DeviceStates.ATO) { - if (AtcControlState == AtcControlStates.Released && speed >= powerLimit) + if (AtcControlState == AtcControlStates.Released && speed > CurrentPermission.LimitNoTraction.KilometersPerHour) { // Cut power on power limit AtcControlState = AtcControlStates.NoPower; } - if (AtcControlState == AtcControlStates.NoPower && speed < powerLimit) + if (AtcControlState == AtcControlStates.NoPower && speed < CurrentPermission.LimitNoTraction.KilometersPerHour) { // Stop cutting power below power limit AtcControlState = AtcControlStates.Released; } - if (speed > limit && speed < emergencyLimit) + if (speed > CurrentPermission.LimitService.KilometersPerHour && speed < CurrentPermission.LimitEmergency.KilometersPerHour) { // Apply service brake on limit AtcControlState = AtcControlStates.BrakeServiceNoRelease; } - if (speed > emergencyLimit) + if (speed > CurrentPermission.LimitEmergency.KilometersPerHour) { // Unselect driving mode to apply emergency brake if overspeeding DeviceState = DeviceStates.Initialized; } // Allow brake release under defined threshold - if (AtcControlState == AtcControlStates.BrakeServiceNoRelease && speed < releaseLimit) + if (AtcControlState == AtcControlStates.BrakeServiceNoRelease && speed < CurrentPermission.LimitBase.KilometersPerHour) { AtcControlState = AtcControlStates.BrakeService; } } // Brake application - if (DeviceState != DeviceStates.Override) + switch (AtcControlState) { - switch (AtcControlState) - { - case AtcControlStates.Released: - RequestedBrakeNotch = -1; - RequestedPowerNotch = -1; - break; - case AtcControlStates.NoPower: - RequestedBrakeNotch = -1; - RequestedPowerNotch = 0; - break; - case AtcControlStates.BrakeService: - case AtcControlStates.BrakeServiceNoRelease: - RequestedBrakeNotch = train.ServiceBrakeNotch; - RequestedPowerNotch = 0; - break; - case AtcControlStates.BrakeEmergency: - RequestedBrakeNotch = train.Specs.BrakeNotches + 1; - RequestedPowerNotch = 0; - break; - } + case AtcControlStates.Released: + RequestedBrakeNotch = -1; + RequestedPowerNotch = -1; + break; + case AtcControlStates.NoPower: + RequestedBrakeNotch = -1; + RequestedPowerNotch = 0; + break; + case AtcControlStates.BrakeService: + case AtcControlStates.BrakeServiceNoRelease: + RequestedBrakeNotch = train.ServiceBrakeNotch; + RequestedPowerNotch = 0; + break; + case AtcControlStates.BrakeEmergency: + RequestedBrakeNotch = train.Specs.BrakeNotches + 1; + RequestedPowerNotch = 0; + break; } switch (DeviceState) @@ -149,8 +176,7 @@ namespace OpenbveFcmbTrainPlugin train.ContinuousProtection = false; train.VigilanceOverride = false; // Release control of the brakes and power - RequestedBrakeNotch = -1; - RequestedPowerNotch = -1; + AtcControlState = AtcControlStates.Released; break; // ATC device is initializing case DeviceStates.Initializing: @@ -173,6 +199,8 @@ namespace OpenbveFcmbTrainPlugin train.VigilanceOverride = false; // Apply service/emergency brake while no driving mode is selected AtcControlState = stopped ? AtcControlStates.BrakeServiceNoRelease : AtcControlStates.BrakeEmergency; + // ATC movement permission + CurrentPermission = new MovementPermission(new Speed(0), new Speed(0), 0); break; // ATC device is in YARD (M+25) driving mode case DeviceStates.YARD: @@ -182,36 +210,18 @@ namespace OpenbveFcmbTrainPlugin if (stopped) { YardIdleCounter += elapsedTime.Seconds; + // If above idle time, apply the service brake + AtcControlState = YardIdleCounter >= YardIdleTime ? AtcControlStates.BrakeService : AtcControlStates.Released; } else { YardIdleCounter = 0; } - // If above idle time, apply the service brake - if (YardIdleCounter >= YardIdleTime) - { - AtcControlState = AtcControlStates.BrakeService; - } - else - { - AtcControlState = AtcControlStates.Released; - } // Apply brake if any door opens if (train.DoorState != DoorStates.None) { RequestedBrakeNotch = stopped ? train.Specs.BrakeNotches + 1 : train.ServiceBrakeNotch; } - // Speed limitations - if (speed > 23) - { - // Cut power over 23 km/h - RequestedPowerNotch = 0; - if (speed > 25) - { - // Apply service brake over 25 km/h - AtcControlState = AtcControlStates.BrakeServiceNoRelease; - } - } break; // ATC device is in ATP (M+ATP) driving mode case DeviceStates.ATP: @@ -239,17 +249,19 @@ namespace OpenbveFcmbTrainPlugin switch (AtcControlState) { case AtcControlStates.Released: - case AtcControlStates.BrakeEmergency: + case AtcControlStates.NoPower: train.Panel[32] = 0; break; case AtcControlStates.BrakeServiceNoRelease: case AtcControlStates.BrakeFull: - train.Panel[32] = 1; + case AtcControlStates.BrakeEmergency: + train.Panel[32] = DeviceState != DeviceStates.Initializing ? 1 : 0; break; case AtcControlStates.BrakeService: train.Panel[32] = blink ? 1 : 0; break; } + train.Panel[34] = (int)CurrentPermission.LimitBase.KilometersPerHour * 1000; } @@ -259,6 +271,9 @@ namespace OpenbveFcmbTrainPlugin /// The current train. internal override void KeyChange(VirtualKeys key, bool pressed, Train train) { + double speed = train.State.Speed.KilometersPerHour; + bool stopped = speed < 0.05; + if (pressed) { switch (key) @@ -267,7 +282,7 @@ namespace OpenbveFcmbTrainPlugin // YARD (M+25) mode selection button if (!OpenbveFcmbTrainPlugin.KeysPressed[(int)key]) { - if (DeviceState == DeviceStates.Initialized && train.State.Speed.KilometersPerHour < 0.05) + if (DeviceState == DeviceStates.Initialized && stopped) { DeviceState = DeviceStates.YARD; } @@ -314,14 +329,14 @@ namespace OpenbveFcmbTrainPlugin case DeviceStates.Initialized: case DeviceStates.YARD: // Override device if possible - if (train.State.Speed.KilometersPerHour < 0.05 && train.PhysicalHandles.PowerNotch == 0) + if (stopped && train.PhysicalHandles.PowerNotch == 0) { DeviceState = DeviceStates.Override; } break; case DeviceStates.Override: // Disable override if possible - if (train.State.Speed.KilometersPerHour < 0.05 && train.PhysicalHandles.PowerNotch == 0) + if (stopped && train.PhysicalHandles.PowerNotch == 0) { DeviceState = DeviceStates.Initializing; } @@ -346,6 +361,13 @@ namespace OpenbveFcmbTrainPlugin /// The current route. internal override void PerformAI(AIData data, Train train, Route route) { + // Set AI notches to what the AI is doing right now + AiBrakeNotch = data.Handles.BrakeNotch; + AiPowerNotch = data.Handles.PowerNotch; + + double speed = train.State.Speed.KilometersPerHour; + bool stopped = speed < 0.05; + switch (DeviceState) { case DeviceStates.Initializing: @@ -357,14 +379,92 @@ namespace OpenbveFcmbTrainPlugin data.Handles.BrakeNotch = train.Specs.BrakeNotches; data.Response = AIResponse.Short; // If the train is stopped, select YARD (M+25) mode - if (train.State.Speed.KilometersPerHour < 0.05) + if (stopped) { - data.Response = AIResponse.Long; KeyChange(VirtualKeys.I, true, train); data.Response = AIResponse.Short; KeyChange(VirtualKeys.I, false, train); } break; + case DeviceStates.YARD: + case DeviceStates.ATP: + // If the ATC brakes can be released, press the ATC brake release button + if (AtcControlState == AtcControlStates.BrakeService) + { + KeyChange(VirtualKeys.B1, true, train); + data.Response = AIResponse.Medium; + KeyChange(VirtualKeys.B1, false, train); + } + // Calculate ideal notch for AI when approaching the speed limit + if (speed > CurrentPermission.LimitBase.KilometersPerHour - 2 && train.Acceleration > 0.1) + { + if (data.Handles.PowerNotch > 0) + { + AiPowerNotch -= 1; + } + else if (train.Specs.HasHoldBrake) + { + data.Handles.HoldBrake = true; + } + else + { + AiBrakeNotch += 1; + } + } + else if (speed < CurrentPermission.LimitBase.KilometersPerHour - 4 && train.Acceleration < 0.25) + { + if (data.Handles.BrakeNotch > 0) + { + AiBrakeNotch -= 1; + } + else + { + AiPowerNotch += 1; + } + } + // If still far from next station, take control of AI handles + if (speed / (route.CurrentStation.StopPosition - train.State.Location) < 0.2) + { + if (speed > CurrentPermission.LimitBase.KilometersPerHour - 5) + { + data.Handles.PowerNotch = AiPowerNotch; + data.Handles.BrakeNotch = AiBrakeNotch; + data.Response = AIResponse.Medium; + } + } + break; + } + } + + /// Updates the data for the current and next station. + internal void UpdatePermission() + { + Speed zero = new Speed(0); + Speed yard = new Speed(YardMaximumSpeed / 3.6); + + switch (DeviceState) + { + case DeviceStates.Override: + case DeviceStates.Initializing: + case DeviceStates.Initialized: + if (CurrentPermission == null || CurrentPermission.LimitBase != zero || CurrentPermission.TargetLimit != zero) + { + CurrentPermission = new MovementPermission(zero, zero, 0); + } + break; + case DeviceStates.YARD: + if (CurrentPermission == null || CurrentPermission.LimitBase != yard || CurrentPermission.TargetLimit != yard) + { + CurrentPermission = new MovementPermission(yard, yard, 0); + } + break; + case DeviceStates.ATP: + case DeviceStates.ATO: + if (CurrentPermission == null || CurrentPermission.LimitBase != zero || CurrentPermission.TargetLimit != zero) + { + CurrentPermission = new MovementPermission(zero, zero, 0); + } + break; } } diff --git a/src/Train/Train.cs b/src/Train/Train.cs index 68cec90..4f44b82 100644 --- a/src/Train/Train.cs +++ b/src/Train/Train.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using OpenBveApi.Runtime; namespace OpenbveFcmbTrainPlugin @@ -47,6 +48,15 @@ namespace OpenbveFcmbTrainPlugin /// The service brake notch for ATC. internal int ServiceBrakeNotch { get; private set; } + /// The current acceleration of the train. + internal double Acceleration { get; private set; } + + /// The time when acceleration was calculated. + private Time AccelerationTime = new Time(0); + + /// The speed when acceleration was calculated. + private Speed AccelerationSpeed = new Speed(0); + /// Creates a new train with the device configuration provided. /// The array of panel variables. 200) + { + Acceleration = Math.Round((State.Speed.MetersPerSecond - AccelerationSpeed.MetersPerSecond) / (data.TotalTime.Seconds - AccelerationTime.Seconds), 4); + AccelerationTime = data.TotalTime; + AccelerationSpeed = State.Speed; + } + // Retrieve data from all devices foreach (Device dev in Devices) {