From 7e426192b7962f0e2d0c1610e4639fe746b67fd9 Mon Sep 17 00:00:00 2001 From: jerrrrycho Date: Sat, 7 Nov 2026 10:05:10 +0100 Subject: [PATCH] fix(cab): a diesel with only the starter button stops its engine with the main switch off command (eu07/maszyna#427) The SM03 had no way to stop its engine. Its fuel pump starts on its own, so the fuel pump switch is ignored; the engine runs the converter feeding the low voltage circuit, so the battery switch doesn't starve the fuel pump either; and its cab has only the starter button, for which TTrain::OnCommand_linebreakeropen() kept a legacy branch which animated the button and returned without opening the circuit. MainSwitch(false) itself stops the engine fine. The command now opens the circuit on diesels whose cab has only the starter button (TMoverParameters::MainCircuitOpensWithStarterOnly()), without animating the button, which held down would start the engine again. Other vehicles with only the button closing the circuit keep ignoring the command. Tests: tests/unit/test_engine_shutdown.cpp, on a shunter made after the SM03. --- McZapkie/MOVER.h | 7 +++ tests/unit/data/diesel_shunter.json | 53 +++++++++++++++++++++ tests/unit/test_engine_shutdown.cpp | 74 +++++++++++++++++++++++++++++ vehicle/Train.cpp | 3 +- 4 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 tests/unit/data/diesel_shunter.json create mode 100644 tests/unit/test_engine_shutdown.cpp diff --git a/McZapkie/MOVER.h b/McZapkie/MOVER.h index 199c65a0c..79a73f1a0 100644 --- a/McZapkie/MOVER.h +++ b/McZapkie/MOVER.h @@ -2466,6 +2466,13 @@ class TMoverParameters { return false == StLinFlag && MainCircuitLampsEnabled(); } + // whether the cab command opening the main circuit opens it where the only cab switch of the circuit is the button + // closing it: on a diesel that's the starter button, and the command is the one way to stop an engine whose fuel pump + // starts on its own (eu07/maszyna#427). the other vehicles with such a button keep ignoring the command + bool MainCircuitOpensWithStarterOnly() const + { + return EngineType == TEngineType::DieselEngine || EngineType == TEngineType::DieselElectric; + } // whether the pumps a joint oil and fuel pump switch works, those not started automatically, are all switched on bool OilFuelPumpsEnabled() const { diff --git a/tests/unit/data/diesel_shunter.json b/tests/unit/data/diesel_shunter.json new file mode 100644 index 000000000..3cbcdb992 --- /dev/null +++ b/tests/unit/data/diesel_shunter.json @@ -0,0 +1,53 @@ +{ + "_comment": "Diesel shunter with a mechanical gearbox, made after the SM03 (Ls150): the fuel pump starts on its own with the engine, a converter run by the engine feeds the low voltage circuit, the only cab control of the engine is the starter button.", + "Param": { "Category": "train", "M": 23200, "Vmax": 25.68, "PWR": 111.855, "SandCap": 200 }, + "Dimensions": { "L": 6.94, "H": 3.21, "W": 2.97, "Cx": 0.3 }, + "Wheels": { "D": 0.94, "Tw": 1.435, "AIM": 250, "Axle": "B", "Ad": 2.5, "Bd": 2.5, "Rmin": 50, "BearingType": "Roll" }, + "Brake": { + "BrakeValve": "K", "NBpA": 2, "MBF": 85.0, "MaxBP": 3.5, "LocMaxBP": 5.0, "BCN": 1, "BCR": 0.114, "BCD": 0.12, "BCM": 10.0, + "HiPP": 5, "LoPP": 3.5, "Vv": 0.3, "MinCP": 7.0, "MaxCP": 8.0, "CompressorSpeed": 0.0125, "CompressorPower": "Main", + "BCS": 0.8, "BSA": 0, "BM": "P10-Bg", "BVV": 40, "BRE": 0.9 + }, + "BuffCoupl": { "CType": "Screw", "kB": 9200, "DmaxB": 0.15, "FmaxB": 740, "kC": 4800, "DmaxC": 0.15, "FmaxC": 780, "beta": 0.58, "AllowedFlag": 3 }, + "Cntrl": { + "BrakeSystem": "Pneumatic", "BDelay1": 15, "BDelay2": 4, "ASB": "Manual", "LocalBrake": "PneumaticBrake", "ManualBrake": "Yes", + "DynamicBrake": "Passive", "MCPN": 9, "SCPN": 7, "AutoRelay": "No", "IniCDelay": 0.5, "SCDelay": 0.1, "BrakeDelays": "P", + "BrakeHandle": "Knorr", "LocBrakeHandle": "Knorr", "FuelStart": "Automatic", "ConverterStart": "Automatic" + }, + "Light": { "Light": "Generator", "LGeneratorEngine": "ElectricSeriesMotor", "LMaxVoltage": 24, "LMaxCurrent": 100 }, + "Engine": { + "EngineType": "DieselEngine", "Trans": "1:1", "nmin": 900, "nmax": 1500, "nmax_cutoff": 1550, "AIM": 100, + "OilMinPressure": 0.343, "OilMaxPressure": 0.686, "WaterCoolingTemperature": 80, "WaterShutters": "yes", "WaterAuxCircuit": "no", + "HeatKFO": 160, "HeatKFO2": 180, "HeatKFS": 160, "HeatKFE": 7, "HeatKW": 2, "HeatKV": 0.3, "IsTC": "No", "EUS": 0.35, "EDS": 1.0 + }, + "_gears": "rows: gear, ratio, engagement speed, top speed; the even ones neutral", + "MotorParamTable": { + "minVelfullengage": 3.8, "engageDia": 1, "engageMaxForce": 800, "engagefriction": 1, + "rows": [ + [0, 0, 0, -1], + [1, 46.88, -1, 5.73], + [2, 0, 0, -1], + [3, 28.65, 2.9, 9.42], + [4, 0, 0, -1], + [5, 17.12, 4.7, 15.69], + [6, 0, 0, -1], + [7, 10.46, 10.6, 100] + ] + }, + "_throttle": "rows: position, fill, speed governor mode; position 0 idles", + "DList": { + "Size": 9, "Mmax": 784.5, "nMmax": 15, "Mnmax": 686.4, "nmax": 25, "nominalfill": 1, "Mstand": 200.0, + "rows": [ + [0, 0.1, 0], + [1, 0.2, 2], + [2, 0.3, 2], + [3, 0.4, 2], + [4, 0.5, 2], + [5, 0.6, 2], + [6, 0.7, 2], + [7, 0.8, 2], + [8, 0.9, 2], + [9, 1.0, 2] + ] + } +} diff --git a/tests/unit/test_engine_shutdown.cpp b/tests/unit/test_engine_shutdown.cpp new file mode 100644 index 000000000..9230330f4 --- /dev/null +++ b/tests/unit/test_engine_shutdown.cpp @@ -0,0 +1,74 @@ +#include "stdafx.h" +#include "doctest.h" +#include "vehicle.h" + +// stopping the engine of a diesel shunter like the SM03 (eu07/maszyna#427): its fuel pump starts on its own with the +// engine and the engine runs the converter which feeds the low voltage circuit, so neither the fuel pump switch nor the +// battery switch stop it. the cab has only the starter button, and the command opening the main circuit has to stop it + +namespace { + +// the shunter with its engine started from the cab and idling +test::vehicle running_shunter() { + test::vehicle shunter{test::definition::load("diesel_shunter")}; + shunter->CabOccupied = 1; + shunter->CabActivisation(); + shunter->BatterySwitch(true); + shunter->OilPumpSwitch(true); + shunter.run(20.0); // the oil pressure builds up + shunter->MainSwitch(true); + shunter.run(15.0); + return shunter; +} + +} // namespace + +TEST_CASE("engine shutdown: the shunter starts with the starter button") { + auto shunter{running_shunter()}; + REQUIRE(shunter->EngineType == TEngineType::DieselEngine); + REQUIRE(shunter->FuelPump.start_type == start_t::automatic); + CHECK(shunter->Mains); + CHECK(shunter->FuelPump.is_active); + CHECK(shunter->enrot > 0.9 * shunter->dizel_nmin); +} + +TEST_CASE("engine shutdown: neither the fuel pump switch nor the battery switch stop the engine") { + auto shunter{running_shunter()}; + shunter->FuelPumpSwitchOff(true, range_t::local); // the pump started automatically ignores its switch + shunter.run(10.0); + CHECK(shunter->Mains); + shunter->BatterySwitch(false); + shunter.run(10.0); + CHECK(shunter->Power110vIsAvailable); // the converter run by the engine keeps feeding the fuel pump + CHECK(shunter->Mains); + CHECK(shunter->enrot > 0.9 * shunter->dizel_nmin); +} + +TEST_CASE("engine shutdown: the command opening the main circuit stops the engine and it stays stopped") { + auto shunter{running_shunter()}; + REQUIRE(shunter->MainCircuitOpensWithStarterOnly()); // the cab passes the command on with only the starter button + CHECK(shunter->MainSwitch(false)); + shunter.run(20.0); + CHECK_FALSE(shunter->Mains); + CHECK_FALSE(shunter->FuelPump.is_active); + CHECK(shunter->enrot == 0.0); +} + +TEST_CASE("engine shutdown: the engine starts again after it was stopped") { + auto shunter{running_shunter()}; + shunter->MainSwitch(false); + shunter.run(20.0); + REQUIRE(shunter->enrot == 0.0); + shunter->MainSwitch(true); + shunter.run(15.0); + CHECK(shunter->Mains); + CHECK(shunter->enrot > 0.9 * shunter->dizel_nmin); +} + +TEST_CASE("engine shutdown: vehicles without a diesel keep ignoring the command with only the button closing the circuit") { + test::vehicle wagon{test::definition::load("wagon")}; + CHECK_FALSE(wagon->MainCircuitOpensWithStarterOnly()); + test::vehicle electric{test::definition::load("wagon").set("Engine", "EngineType", "ElectricSeriesMotor")}; + REQUIRE(electric->EngineType == TEngineType::ElectricSeriesMotor); + CHECK_FALSE(electric->MainCircuitOpensWithStarterOnly()); +} diff --git a/vehicle/Train.cpp b/vehicle/Train.cpp index 201b8972f..1b7d06a8f 100644 --- a/vehicle/Train.cpp +++ b/vehicle/Train.cpp @@ -3702,13 +3702,14 @@ void TTrain::OnCommand_linebreakeropen(TTrain *Train, command_data const &Comman { Train->ggMainButton.UpdateValue(0.0, Train->dsbSwitch); } - else if (Train->ggMainOnButton.SubModel != nullptr) + else if (Train->ggMainOnButton.SubModel != nullptr && false == Train->mvControlled->MainCircuitOpensWithStarterOnly()) { // NOTE: legacy behaviour, for vehicles equipped only with impulse close switch // it doesn't make any real sense to animate this one, but some people can't get over how there's no visual reaction to their keypress Train->ggMainOnButton.UpdateValue(1.0, Train->dsbSwitch); return; } + // a diesel with only the starter button gets its engine stopped. the button stays put: held down, it would start the engine again // play sound immediately when the switch is hit, not after release Train->fMainRelayTimer = 0.0f;