From 87f1d960a094f5f2f17eb69170df5f9d806a6ba9 Mon Sep 17 00:00:00 2001 From: jerrrrycho Date: Sun, 8 Nov 2026 04:25:34 +0100 Subject: [PATCH] fix: EN57 brakes hauled with the brake hose alone, Knorr EP brake without control power eu07/maszyna#87: the EP2 (and EP1) valve charged its auxiliary reservoir only from the main reservoir pipe. A dead EN57 hauled by a locomotive coupled with the hook and the brake hose alone ("3") never charged it, its control reservoir stopped at about 2.5 bar and the unit didn't brake. The valve now refills the auxiliary reservoir from the brake pipe through the slide valve's check valve, as every ESt does (TESt::FillBrakeRes, shared with TESt::GetPF, which stays bit for bit the same). With the main reservoir pipe charged the auxiliary reservoir stays above the brake pipe and nothing changes; the reservoir now also takes air from the brake pipe while it is below it, e.g. at start-up before the compressor has charged the main reservoir pipe. eu07/maszyna#134: the EP brake of the Knorr St113 handle (EN57-001) worked with the reverser in neutral, the battery off or the EP fuse switched off (Ctrl+Z). It now needs the control powered, as the Oerlikon handle's does (EPBrakeControlPowered: battery, reverser out of neutral, EP fuse), and an EP brake switched on drops out (holding the cylinder pressure) when the control loses power. The positions of the EP release and brake were already moved by 810b7ad07; a test pins them. No change to game data needed. --- McZapkie/MOVER.h | 2 + McZapkie/Mover.cpp | 15 ++++- McZapkie/hamulce.cpp | 36 ++++++++--- McZapkie/hamulce.h | 9 +++ tests/unit/test_brake_handles.cpp | 103 ++++++++++++++++++++++++++++++ tests/unit/test_brakes.cpp | 46 +++++++++++++ 6 files changed, 200 insertions(+), 11 deletions(-) diff --git a/McZapkie/MOVER.h b/McZapkie/MOVER.h index 16bb207f..e6d6296a 100644 --- a/McZapkie/MOVER.h +++ b/McZapkie/MOVER.h @@ -2413,6 +2413,8 @@ class TMoverParameters bool BrakeReleaser(int state); bool UniversalBrakeButton(int button, int state); /*uniwersalny przycisk hamulca*/ bool SwitchEPBrake(int state); + // whether the EP brake control of the cab has power: battery, the reverser out of neutral and the EP fuse on + bool EPBrakeControlPowered() const; bool AntiSlippingButton(void); /*! reczny wlacznik urzadzen antyposlizgowych*/ /*funkcje dla ukladow pneumatycznych*/ diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index 20761bac..6e38ad14 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -4161,7 +4161,8 @@ bool TMoverParameters::SwitchEPBrake(int state) OK = false; if (BrakeHandle == TBrakeHandle::St113 && CabOccupied != 0) { - if (state > 0) + // the handle works the EP contactors pneumatically, their supply is cut as the Oerlikon handle's is + if (state > 0 && EPBrakeControlPowered()) EpForce = Handle->GetEP(); // TODO: przetlumaczyc else EpForce = 0; @@ -4173,6 +4174,11 @@ bool TMoverParameters::SwitchEPBrake(int state) return OK; } +bool TMoverParameters::EPBrakeControlPowered() const +{ + return Power24vIsAvailable && DirActive != 0 && EpFuse; +} + // ************************************************************************************************* // Q: 20160711 // zwiększenie ciśnienia hamowania @@ -4706,7 +4712,7 @@ void TMoverParameters::UpdatePipePressure(double dt) if ((BrakeHandle == TBrakeHandle::FVel6 || BrakeHandle == TBrakeHandle::FVE408) && CabOccupied != 0) { - if (Power24vIsAvailable && DirActive != 0 && EpFuse) // tu powinien byc jeszcze bezpiecznik EP i baterie - + if (EPBrakeControlPowered()) // tu powinien byc jeszcze bezpiecznik EP i baterie - // temp = (Handle as TFVel6).GetCP EpForce = Handle->GetEP(); else @@ -4725,6 +4731,11 @@ void TMoverParameters::UpdatePipePressure(double dt) // Ra 2014-11: na tym się wysypuje, ale nie wiem, w jakich warunkach SendCtrlToNext("Brake", EpForce, CabActive); } + else if (BrakeHandle == TBrakeHandle::St113 && CabOccupied != 0 && std::abs(EpForce) > 0.001 && !EPBrakeControlPowered()) + { + // the EP brake switched on with the button drops out with the control's supply + SwitchEPBrake(0); + } Pipe->Act(); PipePress = Pipe->P(); diff --git a/McZapkie/hamulce.cpp b/McZapkie/hamulce.cpp index c0a7d2a0..da0a9811 100644 --- a/McZapkie/hamulce.cpp +++ b/McZapkie/hamulce.cpp @@ -968,6 +968,28 @@ double TESt::BVs(double const BCP) const return 0; } +/// +/// Refills the auxiliary reservoir (ZP) from the valve pre-chamber through the +/// check valve of the slide valve, opened as far as says for the +/// given cylinder (or impulse-chamber) pressure, while the pre-chamber is more +/// than 0.05 bar above the reservoir. The flows wait for the caller's Act(). +/// +/// Brake cylinder (or impulse-chamber) pressure. +/// Time step [s]. +/// Volume taken into the auxiliary reservoir, 0.96 of it drawn from the brake pipe. +double TESt::FillBrakeRes(double const BCP, double const dt) +{ + double const BVP{BrakeRes->P()}; + double const VVP{ValveRes->P()}; + double const temp{BVs(BCP)}; + double dv{0.0}; + if (VVP - 0.05 > BVP) + dv = PF(BVP, VVP, 0.02 * SizeBR * temp / 1.87) * dt; + BrakeRes->Flow(dv); + ValveRes->Flow(-0.04 * dv); + return dv; +} + /// /// One-step Oerlikon ESt distributor advance: runs CheckState/CheckReleaser, /// integrates the ZS <-> PG, ZP <-> cylinder, ZP <-> pre-chamber and @@ -1021,15 +1043,7 @@ double TESt::GetPF(double const PP, double const dt, double const Vel) BrakeCyl->Flow(-dv); // przeplyw ZP <-> rozdzielacz - temp = BVs(BCP); - // if(BrakeStatus and b_hld)=b_off then - if (VVP - 0.05 > BVP) - dv = PF(BVP, VVP, 0.02 * SizeBR * temp / 1.87) * dt; - else - dv = 0; - BrakeRes->Flow(dv); - dV1 = dV1 + dv * 0.96; - ValveRes->Flow(-0.04 * dv); + dV1 = dV1 + FillBrakeRes(BCP, dt) * 0.96; // przeplyw PG <-> rozdzielacz dv = PF(PP, VVP, 0.01) * dt; ValveRes->Flow(-dv); @@ -1191,6 +1205,10 @@ double TEStEP2::GetPF(double const PP, double const dt, double const Vel) ValveRes->Flow(-0.04 * dv); dV1 = dV1 - 0.96 * dv; + // przeplyw ZP <-> rozdzielacz: as in every ESt, the auxiliary reservoir also charges from the brake pipe, + // not from the main reservoir pipe alone, so a unit hauled with the brake hose alone charges and brakes + dV1 = dV1 + FillBrakeRes(BCP, dt) * 0.96; + // hamulec EP EPCalc(dt); diff --git a/McZapkie/hamulce.h b/McZapkie/hamulce.h index 37b9b6a4..b9a5ffd1 100644 --- a/McZapkie/hamulce.h +++ b/McZapkie/hamulce.h @@ -620,6 +620,15 @@ class TESt : public TBrake /// Brake cylinder pressure. /// Dimensionless opening coefficient. double BVs(double BCP) const; // napelniacz pomocniczego + /// + /// Refills the auxiliary reservoir (ZP) from the valve pre-chamber through the + /// check valve of the slide valve, opened as far as says, while + /// the pre-chamber is above the reservoir. The flows wait for the caller's Act(). + /// + /// Brake cylinder (or impulse-chamber) pressure for . + /// Time step [s]. + /// Volume taken into the auxiliary reservoir, 0.96 of it drawn from the brake pipe. + double FillBrakeRes(double BCP, double dt); // napelnianie pomocniczego z rozdzielacza /// Vents the valve, brake and control reservoirs to zero. void ForceEmptiness() override; // wymuszenie bycia pustym diff --git a/tests/unit/test_brake_handles.cpp b/tests/unit/test_brake_handles.cpp index 51c25243..e6b43547 100644 --- a/tests/unit/test_brake_handles.cpp +++ b/tests/unit/test_brake_handles.cpp @@ -4,6 +4,7 @@ #include "golden.h" #include "real_vehicles.h" #include "drive.h" +#include "consist.h" #include "McZapkie/hamulce.h" @@ -152,3 +153,105 @@ TEST_CASE("brake handles: the overload and the high pressure buttons of the MHZ trace.check(); } } + +// eu07/maszyna#134: the Knorr St113 of the EN57-001 releases the EP brake in the running position (bh_RP, Num 6) +// and applies it one position on (Num 8), short of the lap position (Num 5); the positions are those the keys +// select, and the EP brake works only with the control of the cab powered: the battery, the reverser out of +// neutral and the EP fuse on (Ctrl+Z), as the Oerlikon handle's +namespace { + +// the front car of the EN57-001 driven from its cab, with the motor car behind it, which has the battery of the two; +// air in the reservoirs, the battery on +void ready_knorr_ep(test::consist &Unit) { + Unit.add(definition::fiz("en57_v1/6bai"), 1); + Unit.add(definition::fiz("en57_v1/5bgb")); + auto &cab{Unit[0]}; + REQUIRE(cab->BrakeHandle == TBrakeHandle::St113); + cab->BatterySwitch(true); + cab->CabActivisation(); + cab.hold_brake_pipe(5.0); + cab->BrakeLevelSet(cab->Handle->GetPos(bh_RP)); + Unit.run(60.0); +} + +// the EP brake button pressed in the given handle position, held for Seconds; the cylinder pressure of the cab car +double ep_brake(test::consist &Unit, double Position, double Seconds) { + auto &cab{Unit[0]}; + cab->BrakeLevelSet(Position); + Unit.run(0.1); + cab->SwitchEPBrake(1); + Unit.run(Seconds); + return cab->BrakePress; +} + +} // namespace + +TEST_CASE("brake handles: the positions of the Knorr St113 EP brake") { + test::consist unit; + ready_knorr_ep(unit); + auto &cab{unit[0]}; + auto &handle{*cab->Handle}; + auto const ep_at{[&unit, &cab, &handle](double Position) { + cab->BrakeLevelSet(Position); + unit.run(0.1); + return handle.GetEP(); + }}; + // the keys: Num 6 running, Num 8 the first position on, Num 5 the middle of the handle + CHECK(handle.GetPos(bh_RP) == doctest::Approx(0.0)); + CHECK(ep_at(0.0) == doctest::Approx(-1.0)); // EP release + CHECK(ep_at(1.0) == doctest::Approx(1.0)); // EP brake + CHECK(ep_at(cab->BrakeCtrlPosNo / 2) == doctest::Approx(0.0)); // lap, Num 5 + CHECK(ep_at(handle.GetPos(bh_FB)) == doctest::Approx(0.0)); + CHECK(ep_at(handle.GetPos(bh_EPR)) == doctest::Approx(-1.0)); + CHECK(ep_at(handle.GetPos(bh_EPB)) == doctest::Approx(1.0)); +} + +TEST_CASE("brake handles: the Knorr St113 EP brake needs the control of the cab powered") { + for (std::string const cut : {"reverser in neutral", "EP fuse off", "battery off"}) { + CAPTURE(cut); + test::consist unit; + ready_knorr_ep(unit); + auto &cab{unit[0]}; + cab->DirectionForward(); + unit.run(1.0); + REQUIRE(cab->EPBrakeControlPowered()); + if (cut == "reverser in neutral") { + cab->DirectionBackward(); + } + else if (cut == "EP fuse off") { + cab->EpFuseSwitch(false); + } + else { + cab->BatterySwitch(false); + } + unit.run(1.0); + REQUIRE_FALSE(cab->EPBrakeControlPowered()); + // the button in the EP brake position does nothing + CHECK(ep_brake(unit, 1.0, 5.0) < 0.1); + CHECK(cab->EpForce == doctest::Approx(0.0)); + cab->SwitchEPBrake(0); + } + + test::consist unit; + ready_knorr_ep(unit); + auto &cab{unit[0]}; + cab->DirectionForward(); + unit.run(1.0); + // powered, the EP brake applies + auto const applied{ep_brake(unit, 1.0, 2.0)}; + CHECK(applied > 0.5); + // the reverser put to neutral with the button still held: the EP brake drops out and holds what it applied + cab->DirectionBackward(); + unit.run(0.1); + CHECK(cab->EpForce == doctest::Approx(0.0)); + auto const held{cab->BrakePress}; + unit.run(5.0); + CHECK(cab->BrakePress < held + 0.05); + CHECK(cab->BrakePress > 0.2); + // and doesn't release either, until the control is powered again + CHECK(ep_brake(unit, 0.0, 5.0) > 0.2); + cab->SwitchEPBrake(0); + cab->DirectionForward(); + unit.run(0.1); + CHECK(ep_brake(unit, 0.0, 10.0) < 0.1); +} diff --git a/tests/unit/test_brakes.cpp b/tests/unit/test_brakes.cpp index 83416807..e12694f3 100644 --- a/tests/unit/test_brakes.cpp +++ b/tests/unit/test_brakes.cpp @@ -1,6 +1,7 @@ #include "stdafx.h" #include "doctest.h" #include "vehicle.h" +#include "consist.h" using test::definition; using test::vehicle; @@ -91,3 +92,48 @@ TEST_CASE("brakes: service braking stops a heavier wagon too") { wagon.run(40.0 /* s */, test::track{0.15 /* friction */, 0.0 /* gradient */}); CHECK(std::abs(wagon.speed()) < 0.1); } + +// eu07/maszyna#87: the ESt4d of the EN57's EP2 valve charged its auxiliary reservoir from the main reservoir pipe +// alone, so a dead unit hauled by a locomotive coupled with the hook and the brake hose ("3") never charged it, +// its control reservoir stopped half way, and it didn't brake. Like every ESt, it charges from the brake pipe too +TEST_CASE("brakes: a dead EN57 hauled with the brake hose alone charges from the brake pipe and brakes") { + test::consist train; + auto &locomotive{train.add(definition::fiz("sm42_v1/6d1"))}; + train.add(definition::fiz("en57_v1/5bgb"), 0, coupling::coupler | coupling::brakehose); + train.add(definition::fiz("en57_v1/6ba_840")); + train.add(definition::fiz("en57_v1/5bgaiii_led_spot")); + // a unit standing dead: no air anywhere, nothing in its main reservoir pipe + for (std::size_t i = 1; i < train.size(); ++i) { + auto &car{train[i].mover()}; + REQUIRE(car.BrakeValve == TBrakeValve::EP2); + car.Hamulec->ForceEmptiness(); + car.Pipe->CreatePress(0.0); + car.Pipe->Act(); + car.Pipe2->CreatePress(0.0); + car.Pipe2->Act(); + car.CompressedVolume = 0.0; + } + locomotive.hold_brake_pipe(5.0); + train.run(900.0); + for (std::size_t i = 1; i < train.size(); ++i) { + CAPTURE(i); + auto &car{train[i].mover()}; + REQUIRE(car.ScndPipePress < 0.1); // no main reservoir pipe + CHECK(car.Hamulec->GetBRP() > 4.8); + CHECK(car.Hamulec->GetCRP() > 4.8); + CHECK(car.BrakePress < 0.1); + } + // a full service application brakes the unit, and it releases again + locomotive.hold_brake_pipe(3.5); + train.run(20.0); + for (std::size_t i = 1; i < train.size(); ++i) { + CAPTURE(i); + CHECK(train[i]->BrakePress > 2.0); + } + locomotive.hold_brake_pipe(5.0); + train.run(60.0); + for (std::size_t i = 1; i < train.size(); ++i) { + CAPTURE(i); + CHECK(train[i]->BrakePress < 0.1); + } +}