From 09fa50602a4af43852eba91a1ae59d3e34e8c5fc Mon Sep 17 00:00:00 2001 From: Marc Riera Date: Sun, 24 Nov 2024 22:10:10 +0100 Subject: [PATCH] Devices: Add ATC Dimetronic --- src/Devices/AtcDimetronic.cs | 410 ++++++++++++++++++++++++++++++ src/OpenbveFcmbTrainPlugin.cs | 1 + src/OpenbveFcmbTrainPlugin.csproj | 1 + src/Train/Train.cs | 11 +- 4 files changed, 421 insertions(+), 2 deletions(-) create mode 100644 src/Devices/AtcDimetronic.cs diff --git a/src/Devices/AtcDimetronic.cs b/src/Devices/AtcDimetronic.cs new file mode 100644 index 0000000..3f834f8 --- /dev/null +++ b/src/Devices/AtcDimetronic.cs @@ -0,0 +1,410 @@ +using OpenBveApi.Runtime; + +namespace OpenbveFcmbTrainPlugin +{ + /// A device simulating ATC by Dimetronic. + internal class AtcDimetronic : Device + { + /// Represents the state of the device. + private enum DeviceStates + { + /// The device is being overriden. + Override, + /// The device has been initialized and no driving mode is selected. + NoMode, + /// The device is in YARD (M+25) driving mode. + YARD, + /// The device is in ATP (M+ATP) driving mode. + ATP, + /// The device is in ATO mode. + ATO, + } + + /// The current state of the device. + private DeviceStates DeviceState; + + /// Represents the state of the ATC control. + private enum AtcControlStates + { + /// The brakes are released. + Released, + /// The power is cut. + NoPower, + /// The emergency brake is applied. + BrakeEmergency, + } + + /// The current state of the ATC control. + private AtcControlStates AtcControlState; + + /// Represents the state of the track. + private enum TrackStates + { + Unprotected, + Enable, + ATC, + Disable + } + + /// The current state of the track. + private TrackStates TrackState; + + /// The maximum speed in YARD mode, in km/h. + private double YardMaximumSpeed; + + /// Represents an ATC speed code. + private class SpeedCode + { + internal Speed CurrentLimit { get; private set; } + internal Speed WarningOn { get; private set; } + internal Speed WarningOff { get; private set; } + internal Speed TargetLimit { get; private set; } + + internal SpeedCode(Speed limit, Speed target) + { + CurrentLimit = limit; + TargetLimit = target; + if (limit == target) + { + // Constant speed + WarningOn = new Speed(limit.MetersPerSecond - 3 / 3.6); + WarningOff = new Speed(limit.MetersPerSecond - 5 / 3.6); + } + else + { + // Reduce speed + WarningOn = target; + WarningOff = new Speed(target.MetersPerSecond - 2 / 3.6); + } + } + } + + /// The current speed code received by the train. + private SpeedCode CurrentSpeedCode; + + /// The ideal power notch for the AI. + private int AiPowerNotch; + + /// The ideal brake notch for the AI. + private int AiBrakeNotch; + + /// Creates an instance of the Bombardier ATC device. + /// The maximum speed in YARD mode, in km/h. + internal AtcDimetronic(double yardMaxSpeed) + { + YardMaximumSpeed = yardMaxSpeed; + } + + /// Is called when the device state should be updated. + /// The current train. + /// The current route. + /// Whether the device should initialize. + /// The time elapsed since the previous call. + internal override void Update(Train train, Route route, bool init, Time elapsedTime) + { + if (init) + { + // Initialize device on start + DeviceState = DeviceStates.NoMode; + } + + // Update ATC movement permission + UpdateSpeedCode(); + + double speed = train.State.Speed.KilometersPerHour; + bool stopped = speed < 0.05; + + // Speed limit enforcement + if (DeviceState == DeviceStates.YARD || DeviceState == DeviceStates.ATP || DeviceState == DeviceStates.ATO) + { + if (AtcControlState == AtcControlStates.Released && speed > CurrentSpeedCode.WarningOn.KilometersPerHour) + { + // Cut power above warning on threshold + AtcControlState = AtcControlStates.NoPower; + } + if (AtcControlState == AtcControlStates.NoPower && speed < CurrentSpeedCode.WarningOff.KilometersPerHour) + { + // Stop cutting power below warning off threshold + AtcControlState = AtcControlStates.Released; + } + if (speed > CurrentSpeedCode.CurrentLimit.KilometersPerHour) + { + // Unselect driving mode to apply emergency brake if overspeeding + DeviceState = DeviceStates.NoMode; + } + } + + // Brake application + switch (AtcControlState) + { + case AtcControlStates.Released: + RequestedBrakeNotch = -1; + RequestedPowerNotch = -1; + break; + case AtcControlStates.NoPower: + RequestedBrakeNotch = -1; + RequestedPowerNotch = 0; + break; + case AtcControlStates.BrakeEmergency: + RequestedBrakeNotch = train.Specs.BrakeNotches + 1; + RequestedPowerNotch = 0; + break; + } + + switch (DeviceState) + { + // ATC device is disabled (also called "Special Mode") + case DeviceStates.Override: + train.ContinuousProtection = false; + train.VigilanceOverride = false; + // Release control of the brakes and power + AtcControlState = AtcControlStates.Released; + break; + // ATC device is initialized and a driving mode is required + case DeviceStates.NoMode: + train.ContinuousProtection = false; + train.VigilanceOverride = false; + // Apply service/emergency brake while no driving mode is selected + AtcControlState = AtcControlStates.BrakeEmergency; + // ATC movement permission + CurrentSpeedCode = new SpeedCode(new Speed(0), new Speed(0)); + break; + // ATC device is in YARD (M+25) driving mode + case DeviceStates.YARD: + train.ContinuousProtection = false; + train.VigilanceOverride = false; + // Apply brake if any door opens + if (train.DoorState != DoorStates.None) + { + RequestedBrakeNotch = stopped ? train.Specs.BrakeNotches + 1 : train.ServiceBrakeNotch; + } + // If the train is not moving, brakes are released + if (stopped) + { + AtcControlState = AtcControlStates.Released; + } + break; + // ATC device is in ATP (M+ATP) driving mode + case DeviceStates.ATP: + train.ContinuousProtection = true; + train.VigilanceOverride = false; + break; + // ATC device is in ATO driving mode + case DeviceStates.ATO: + train.ContinuousProtection = true; + train.VigilanceOverride = true; + break; + } + + // Panel indicators + train.Panel[25] = DeviceState == DeviceStates.Override ? 1 : 0; + train.Panel[26] = DeviceState == DeviceStates.YARD ? 1 : 0; + train.Panel[27] = DeviceState == DeviceStates.ATP ? 1 : 0; + train.Panel[34] = (int)CurrentSpeedCode.CurrentLimit.KilometersPerHour * 1000; + } + + + /// Is called when the state of a key changes. + /// The key. + /// Whether the key is pressed or released. + /// 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) + { + case VirtualKeys.I: + // YARD (M+25) mode selection button + if (!OpenbveFcmbTrainPlugin.KeysPressed[(int)key]) + { + if (DeviceState == DeviceStates.NoMode && stopped) + { + DeviceState = DeviceStates.YARD; + } + } + break; + case VirtualKeys.J: + // ATP (M+ATP) mode selection button + if (!OpenbveFcmbTrainPlugin.KeysPressed[(int)key]) + { + } + break; + case VirtualKeys.K: + // ATO mode selection button + if (!OpenbveFcmbTrainPlugin.KeysPressed[(int)key]) + { + } + break; + case VirtualKeys.L: + // ATO start button + if (!OpenbveFcmbTrainPlugin.KeysPressed[(int)key]) + { + } + break; + case VirtualKeys.B1: + // ATC alarm acknowledge + if (!OpenbveFcmbTrainPlugin.KeysPressed[(int)key]) + { + } + break; + case VirtualKeys.C1: + // Override toggle + if (!OpenbveFcmbTrainPlugin.KeysPressed[(int)key]) + { + switch (DeviceState) + { + case DeviceStates.NoMode: + case DeviceStates.YARD: + // Override device if possible + if (stopped && train.PhysicalHandles.PowerNotch == 0) + { + DeviceState = DeviceStates.Override; + } + break; + case DeviceStates.Override: + // Disable override if possible + if (stopped && train.PhysicalHandles.PowerNotch == 0) + { + DeviceState = DeviceStates.NoMode; + } + break; + } + } + break; + } + } + } + + /// Is called to inform about signals. + /// The signal data. + internal override void SetSignal(SignalData[] signal) + { + + } + + /// Is called when a beacon is passed. + /// The beacon data. + internal override void SetBeacon(BeaconData beacon) + { + int type = beacon.Type; + switch (type) + { + case -16777215: + { + if (!(beacon.Optional >= 0 & beacon.Optional <= 3)) + { + break; + } + TrackState = (TrackStates)beacon.Optional; + return; + } + } + } + + /// Is called when the device should perform the AI. + /// The AI data. + /// The current train. + /// 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.NoMode: + // Apply brake + data.Handles.BrakeNotch = train.Specs.BrakeNotches; + data.Response = AIResponse.Short; + // If the train is stopped, select YARD (M+25) mode + if (stopped) + { + KeyChange(VirtualKeys.I, true, train); + data.Response = AIResponse.Short; + KeyChange(VirtualKeys.I, false, train); + } + break; + case DeviceStates.YARD: + case DeviceStates.ATP: + // Calculate ideal notch for AI when approaching the speed limit + if (speed > CurrentSpeedCode.WarningOn.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 < CurrentSpeedCode.WarningOff.KilometersPerHour - 2 && 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 > CurrentSpeedCode.CurrentLimit.KilometersPerHour - 5) + { + data.Handles.PowerNotch = AiPowerNotch; + data.Handles.BrakeNotch = AiBrakeNotch; + data.Response = AIResponse.Medium; + } + } + break; + } + } + + /// Updates the data for the current speed code. + internal void UpdateSpeedCode() + { + Speed zero = new Speed(0); + Speed yard = new Speed(YardMaximumSpeed / 3.6); + + switch (DeviceState) + { + case DeviceStates.Override: + case DeviceStates.NoMode: + if (CurrentSpeedCode == null || CurrentSpeedCode.CurrentLimit != zero || CurrentSpeedCode.TargetLimit != zero) + { + CurrentSpeedCode = new SpeedCode(zero, zero); + } + break; + case DeviceStates.YARD: + if (CurrentSpeedCode == null || CurrentSpeedCode.CurrentLimit != yard || CurrentSpeedCode.TargetLimit != yard) + { + CurrentSpeedCode = new SpeedCode(yard, yard); + } + break; + case DeviceStates.ATP: + case DeviceStates.ATO: + if (CurrentSpeedCode == null || CurrentSpeedCode.CurrentLimit != zero || CurrentSpeedCode.TargetLimit != zero) + { + CurrentSpeedCode = new SpeedCode(zero, zero); + } + break; + } + } + + } +} diff --git a/src/OpenbveFcmbTrainPlugin.cs b/src/OpenbveFcmbTrainPlugin.cs index a0ded43..218f572 100644 --- a/src/OpenbveFcmbTrainPlugin.cs +++ b/src/OpenbveFcmbTrainPlugin.cs @@ -118,6 +118,7 @@ namespace OpenbveFcmbTrainPlugin /// The signal array is guaranteed to have at least one element. When accessing elements other than index 0, you must check the bounds of the array first. public void SetSignal(SignalData[] signal) { + Train.SetSignal(signal); } /// Is called when the train passes a beacon. diff --git a/src/OpenbveFcmbTrainPlugin.csproj b/src/OpenbveFcmbTrainPlugin.csproj index c15573a..b9f9dea 100644 --- a/src/OpenbveFcmbTrainPlugin.csproj +++ b/src/OpenbveFcmbTrainPlugin.csproj @@ -45,6 +45,7 @@ + diff --git a/src/Train/Train.cs b/src/Train/Train.cs index 1809cf7..08f4560 100644 --- a/src/Train/Train.cs +++ b/src/Train/Train.cs @@ -69,7 +69,8 @@ namespace OpenbveFcmbTrainPlugin Devices.Add(new Doors(false, 5, 15, 10)); Devices.Add(new Deadman(5.0, false)); Devices.Add(new TrainStop()); - Devices.Add(new AtcBombardier(30, 60, 20, 500)); + //Devices.Add(new AtcBombardier(30, 60, 20, 500)); + Devices.Add(new AtcDimetronic(25)); } /// Is called when the train should initialize. @@ -183,7 +184,13 @@ namespace OpenbveFcmbTrainPlugin /// Is called to inform about signals. /// The signal data. - internal void SetSignal(SignalData[] signal) { } + internal void SetSignal(SignalData[] signal) + { + foreach (Device dev in Devices) + { + dev.SetSignal(signal); + } + } /// Is called when a beacon is passed. /// The beacon data.