18
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-15 22:14:10 +01:00

Merge pull request #291 from MaSzyna-EU07/fix/sod-lamp-whole-consist

SOD lamp: doors of the whole consist, three states (eu07/maszyna#288)
This commit is contained in:
maj00r
2026-11-08 20:56:20 +01:00
committed by GitHub
6 changed files with 238 additions and 1 deletions

View File

@@ -261,6 +261,22 @@ enum class range_t
/// <summary>Affects the entire consist.</summary>
consist
};
/// <summary>State of the door closure signalling (SOD) of a vehicle or a consist, ordered from the least to the most
/// severe: the state of a consist is the most severe state of its vehicles.</summary>
enum class door_closure_t
{
/// <summary>No doors worked by the crew: the vehicle doesn't take part in the signalling.</summary>
none,
/// <summary>All doors closed and locked: they can't be opened.</summary>
locked,
/// <summary>All doors closed, but released: a permit is given, or no lock holds them shut.</summary>
released,
/// <summary>A door is open, or not fully closed yet.</summary>
open
};
/// <summary>Whether the door closure signalling lamp is lit: steadily for locked doors, in the lit phase of the
/// blinking for released ones, never with a door open or with no doors signalled.</summary>
bool door_closure_lamp(door_closure_t const State, bool const BlinkLit);
/// <summary>Possible settings for an enable/disable input pair (mutually exclusive).</summary>
enum class operation_t
{
@@ -1439,6 +1455,7 @@ class TMoverParameters
float auto_velocity{-1.f}; // automatic door closure velocity threshold
bool auto_include_remote{false}; // automatic door closure applies also to remote control
bool permit_needed{false};
bool closure_signal{false}; // the door closure lamp (SOD) of the cab signals the doors of the whole consist in three states
std::vector<int> permit_presets; // permit presets selectable with preset switch
float voltage{0.f}; // power type required for door movement
// ld inputs
@@ -2593,6 +2610,10 @@ class TMoverParameters
bool LockDoors(bool const State, range_t const Notify = range_t::consist);
bool signal_departure(bool const State, range_t const Notify = range_t::consist); // toggles departure warning
void update_doors(double const Deltatime); // door controller update
door_closure_t door_closure() const; // state of the door closure signalling of this vehicle
// whether the door closure lamp in the cab of this vehicle is lit: with DoorClosureSignal, for the given state of the
// consist; without it, for the lock of the vehicle's own doors, as the lamp always worked
bool door_closure_lamp_lit(door_closure_t const Consist, bool const BlinkLit) const;
/* funkcje dla samochodow*/
bool ChangeOffsetH(double DeltaOffset);

View File

@@ -8699,6 +8699,48 @@ bool TMoverParameters::signal_departure(bool const State, range_t const Notify)
return true;
}
// state of the door closure signalling (SOD) of this vehicle: open while a door isn't fully closed, released while
// the closed doors can be opened (a permit given, or, for doors that need none, no lock engaged), locked otherwise.
// doors the crew doesn't close (autonomous) aren't signalled, the same as for the door lights of the consist
door_closure_t TMoverParameters::door_closure() const
{
if (Doors.range == 0.f || Doors.close_control == control_t::autonomous)
{
return door_closure_t::none;
}
if (std::ranges::any_of(Doors.instances, [](basic_door const &Door) { return false == Door.is_closed; }))
{
return door_closure_t::open;
}
if (Doors.is_locked)
{
return door_closure_t::locked;
}
if (Doors.permit_needed && std::ranges::none_of(Doors.instances, [](basic_door const &Door) { return Door.open_permit; }))
{
return door_closure_t::locked;
}
return door_closure_t::released;
}
bool TMoverParameters::door_closure_lamp_lit(door_closure_t const Consist, bool const BlinkLit) const
{
return Doors.closure_signal ? door_closure_lamp(Consist, BlinkLit) : Doors.is_locked;
}
bool door_closure_lamp(door_closure_t const State, bool const BlinkLit)
{
switch (State)
{
case door_closure_t::locked:
return true;
case door_closure_t::released:
return BlinkLit;
default:
return false;
}
}
// automatic door controller update
void TMoverParameters::update_doors(double const Deltatime)
{
@@ -10576,6 +10618,8 @@ void TMoverParameters::LoadFIZ_Doors(std::string const &line)
extract_value(Doors.auto_include_remote, "DoorAutoCloseRemote", line, "");
// operation permit
extract_value(Doors.permit_needed, "DoorNeedPermit", line, "");
// the door closure lamp (SOD) of the cab signals the doors of the whole consist
extract_value(Doors.closure_signal, "DoorClosureSignal", line, "");
{
auto permitpresets = Split(extract_value("DoorPermitList", line), '|');
for (auto const &permit : permitpresets)

View File

@@ -0,0 +1,158 @@
#include "stdafx.h"
#include "doctest.h"
#include "scenario.h"
#include "vehicle.h"
// the door closure signalling (SOD, the i-door_blocked lamp of a cab): the state of each vehicle's doors and of the
// whole train, the most severe of its vehicles, as the driver gathers it for the lamp (eu07/maszyna#288)
using test::scenario;
using test::definition;
using test::vehicle;
using namespace test::channels;
namespace {
// the state of the whole train, the way TController::determine_consist_state() gathers it
door_closure_t consist_closure(scenario &Scenario) {
auto closure{door_closure_t::none};
for (std::size_t i = 0; i < Scenario.train().size(); ++i) {
closure = std::max(closure, Scenario.vehicle(i).door_closure());
}
return closure;
}
} // namespace
TEST_CASE("door closure: the lamp is lit for locked doors, blinks for released ones and is dark for open ones") {
for (auto const lit : {true, false}) {
CAPTURE(lit);
CHECK(door_closure_lamp(door_closure_t::locked, lit));
CHECK(door_closure_lamp(door_closure_t::released, lit) == lit);
CHECK_FALSE(door_closure_lamp(door_closure_t::open, lit));
CHECK_FALSE(door_closure_lamp(door_closure_t::none, lit));
}
// a consist takes the most severe state of its vehicles
CHECK(std::max(door_closure_t::none, door_closure_t::locked) == door_closure_t::locked);
CHECK(std::max(door_closure_t::locked, door_closure_t::released) == door_closure_t::released);
CHECK(std::max(door_closure_t::released, door_closure_t::open) == door_closure_t::open);
}
TEST_CASE("door closure: a unit with permits, locked, released by the permit and open, in any of its cars") {
scenario scenario{"door closure/elf", motion() + doors()};
scenario.add("elf_v1/21we-a", 1).add("elf_v1/21we-b").add("elf_v1/21we-c");
scenario.start_up();
auto &cab{scenario.cab()};
auto &last{scenario.vehicle(scenario.train().size() - 1)};
// closed with no permit: no door can be opened
CHECK(cab.door_closure() == door_closure_t::locked);
CHECK(last.door_closure() == door_closure_t::locked);
CHECK(consist_closure(scenario) == door_closure_t::locked);
scenario.act("permit left");
cab.PermitDoors(side::left, true);
scenario.wait(2.0);
CHECK(last.door_closure() == door_closure_t::released);
CHECK(consist_closure(scenario) == door_closure_t::released);
scenario.act("the last car opens its own door");
last.OperateDoors(side::left, true, range_t::local);
scenario.wait(5.0);
// the cab's own doors stay closed, the train is open all the same
CHECK(cab.door_closure() == door_closure_t::released);
CHECK(last.door_closure() == door_closure_t::open);
CHECK(consist_closure(scenario) == door_closure_t::open);
scenario.act("close, the steps stay out under the permit");
last.OperateDoors(side::left, false, range_t::local);
cab.OperateDoors(side::left, false);
scenario.wait(10.0);
CHECK(last.Doors.instances[side::left].position <= 0.f);
// a step out isn't a closed door
CHECK(consist_closure(scenario) == door_closure_t::open);
scenario.act("revoke the permit");
cab.PermitDoors(side::left, false);
scenario.wait(10.0);
CHECK(consist_closure(scenario) == door_closure_t::locked);
}
TEST_CASE("door closure: the doors of a unit without permits are released at a standstill") {
scenario scenario{"door closure/en57", motion() + doors()};
scenario.add("en57_v1/5bgaiii_led_spot", 1).add("en57_v1/6ba_840").add("en57_v1/5bgb");
scenario.start_up();
auto &cab{scenario.cab()};
auto &middle{scenario.vehicle(1)};
REQUIRE_FALSE(cab.Doors.permit_needed);
REQUIRE(middle.Doors.permit_needed);
// the door lock engages only once the train runs (DoorLockSpeed), till then the end cars' doors can be opened
CHECK(cab.door_closure() == door_closure_t::released);
CHECK(middle.door_closure() == door_closure_t::locked);
CHECK(consist_closure(scenario) == door_closure_t::released);
scenario.act("open right");
cab.OperateDoors(side::right, true);
scenario.wait(10.0);
CHECK(consist_closure(scenario) == door_closure_t::open);
scenario.act("close right");
cab.OperateDoors(side::right, false);
scenario.wait(10.0);
CHECK(consist_closure(scenario) == door_closure_t::released);
}
TEST_CASE("door closure: a locomotive has no doors to signal, its coach does") {
scenario scenario{"door closure/eu07 with a coach", motion() + doors()};
scenario.add("4e_v1/4e", 1).add("bdhpumn_v3/bdhpumn");
scenario.start_up();
CHECK(scenario.cab().door_closure() == door_closure_t::none);
scenario.act("open right");
scenario.cab().PermitDoors(side::right, true);
scenario.cab().OperateDoors(side::right, true);
scenario.wait(10.0);
CHECK(scenario.vehicle(1).door_closure() == door_closure_t::open);
CHECK(consist_closure(scenario) == door_closure_t::open);
}
TEST_CASE("door closure: the signalling is switched on by DoorClosureSignal in the Doors section, off without it") {
SUBCASE("without the key") {
vehicle wagon{definition::load("wagon")};
CHECK_FALSE(wagon->Doors.closure_signal);
}
SUBCASE("with the key") {
vehicle wagon{definition::load("wagon").set("Doors", "DoorClosureSignal", "Yes")};
CHECK(wagon->Doors.closure_signal);
}
}
TEST_CASE("door closure: without DoorClosureSignal the lamp shows the lock of the vehicle's own doors, as before") {
vehicle wagon{definition::load("wagon")};
REQUIRE_FALSE(wagon->Doors.closure_signal);
for (auto const locked : {true, false}) {
wagon->Doors.is_locked = locked;
for (auto const consist : {door_closure_t::none, door_closure_t::locked, door_closure_t::released, door_closure_t::open}) {
for (auto const lit : {true, false}) {
CAPTURE(locked);
CAPTURE(static_cast<int>(consist));
CAPTURE(lit);
// no blinking at a standstill with the doors closed, whatever the consist does
CHECK(wagon->door_closure_lamp_lit(consist, lit) == locked);
}
}
}
}
TEST_CASE("door closure: with DoorClosureSignal the lamp shows the consist in three states") {
vehicle wagon{definition::load("wagon").set("Doors", "DoorClosureSignal", "Yes")};
REQUIRE(wagon->Doors.closure_signal);
for (auto const locked : {true, false}) {
// the vehicle's own lock doesn't matter, the consist does
wagon->Doors.is_locked = locked;
for (auto const lit : {true, false}) {
CAPTURE(locked);
CAPTURE(lit);
CHECK(wagon->door_closure_lamp_lit(door_closure_t::locked, lit));
CHECK(wagon->door_closure_lamp_lit(door_closure_t::released, lit) == lit);
CHECK_FALSE(wagon->door_closure_lamp_lit(door_closure_t::open, lit));
CHECK_FALSE(wagon->door_closure_lamp_lit(door_closure_t::none, lit));
}
}
}

View File

@@ -6019,6 +6019,7 @@ TController::determine_consist_state() {
IsAnyDoorOnlyOpen[ side::right ] = IsAnyDoorOnlyOpen[ side::left ] = false;
IsAnyDoorOpen[ side::right ] = IsAnyDoorOpen[ side::left ] = false;
IsAnyDoorPermitActive[ side::right ] = IsAnyDoorPermitActive[ side::left ] = false;
auto doorclosure { door_closure_t::none };
ConsistShade = 0.0;
auto *p { pVehicles[ end::front ] }; // pojazd na czole składu
double dy; // składowa styczna grawitacji, w przedziale <0,1>
@@ -6080,12 +6081,14 @@ TController::determine_consist_state() {
IsAnyDoorPermitActive[ side::right ] |= rightdoor.open_permit;
IsAnyDoorPermitActive[ side::left ] |= leftdoor.open_permit;
}
doorclosure = std::max( doorclosure, vehicle->door_closure() );
}
// measure lighting level
// TBD: apply weight (multiplier) to partially lit vehicles?
ConsistShade += p->fShade > 0.0 ? p->fShade : 1.0;
p = p->Next(); // pojazd podłączony z tyłu (patrząc od czoła)
}
DoorClosure = doorclosure;
// calculate average amount of received sunlight
ConsistShade /= iVehicles;

View File

@@ -637,6 +637,7 @@ private:
bool IsAnyDoorOpen[ 2 ]; // state of door in the consist
bool IsAnyDoorOnlyOpen[ 2 ]; // state of door in the consist regardless of step
bool IsAnyDoorPermitActive[ 2 ]; // state of door permit in the consist
door_closure_t DoorClosure{ door_closure_t::none }; // state of the door closure signalling (SOD) of the consist
bool IsAnyLineBreakerOpen{ false }; // state of line breaker in all powered vehicles under control
bool IsAnyConverterOverloadRelayOpen{ false }; // state of converter overload relays in all vehicles under control
bool IsAnyMotorOverloadRelayOpen{ false }; // state of motor overload relays in all vehicles under control

View File

@@ -9116,7 +9116,17 @@ bool TTrain::Update(double const Deltatime)
btLampkaDoorLeft.Turn(DynamicObject->Mechanik->IsAnyDoorOpen[cab_to_end() == end::front ? side::left : side::right]);
btLampkaDoorRight.Turn(DynamicObject->Mechanik->IsAnyDoorOpen[cab_to_end() == end::front ? side::right : side::left]);
}
btLampkaBlokadaDrzwi.Turn(mvOccupied->Doors.is_locked);
if (DynamicObject->Mechanik != nullptr)
{
// with DoorClosureSignal in the definition of the cab's vehicle, the door closure signalling (SOD) of the whole
// consist: lit with all doors closed and locked, blinking with them closed but released, dark with a door open.
// without it, the lock of the vehicle's own doors, as before
btLampkaBlokadaDrzwi.Turn(mvOccupied->door_closure_lamp_lit(DynamicObject->Mechanik->DoorClosure, simulation::Time.data().wSecond % 2 < 1));
}
else
{
btLampkaBlokadaDrzwi.Turn(mvOccupied->Doors.is_locked);
}
btLampkaDoorLockOff.Turn(false == mvOccupied->Doors.lock_enabled);
btLampkaDepartureSignal.Turn(mvControlled->DepartureSignal);
btLampkaNapNastHam.Turn(mvControlled->DirActive != 0 && mvOccupied->EpFuse); // napiecie na nastawniku hamulcowym