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 readonly double YardMaximumSpeed; /// Whether ATO is available or not on the train. private readonly bool AtoAvailable; /// 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. /// Whether ATO is available or not. internal AtcDimetronic(double yardMaxSpeed, bool atoAvailable) { YardMaximumSpeed = yardMaxSpeed; AtoAvailable = atoAvailable; } /// 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]) { // Allow change anytime the train is stopped or from ATP mode below YARD speed limit if ((DeviceState >= DeviceStates.NoMode && stopped) || (DeviceState == DeviceStates.ATP && train.State.Speed.KilometersPerHour < YardMaximumSpeed)) { if (train.PhysicalHandles.Reverser == 1 || train.PhysicalHandles.Reverser == -1) { DeviceState = DeviceStates.YARD; } } } break; case VirtualKeys.J: // ATP (M+ATP) mode selection button if (!OpenbveFcmbTrainPlugin.KeysPressed[(int)key]) { // Allow change anytime the train is stopped or from ATO mode when running if ((DeviceState >= DeviceStates.NoMode && DeviceState <= DeviceStates.ATO && stopped) || DeviceState == DeviceStates.ATO) { if (train.PhysicalHandles.Reverser == 1 && TrackState > TrackStates.Unprotected) { DeviceState = DeviceStates.ATP; } } } break; case VirtualKeys.K: // ATO mode selection button if (!OpenbveFcmbTrainPlugin.KeysPressed[(int)key]) { // Allow change when the train is stopped in ATP mode if (DeviceState == DeviceStates.ATP && stopped && AtoAvailable) { if (train.PhysicalHandles.Reverser == 1 && TrackState > TrackStates.Unprotected) { DeviceState = DeviceStates.ATO; } } } 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; } } } }