Skip to content

Commit 9180445

Browse files
authored
Merge pull request #916 from SteelFill/dpu_brakes
Distributed Power Air Brake Synchronization
2 parents af8fb30 + 992cc97 commit 9180445

File tree

9 files changed

+613
-191
lines changed

9 files changed

+613
-191
lines changed

Source/Documentation/Manual/cabs.rst

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -684,14 +684,16 @@ Air Flow Meter
684684

685685
.. index::
686686
single: ORTS_AIR_FLOW_METER
687+
single: ORTS_TRAIN_AIR_FLOW_METER
687688

688689
This cabview control is used on some locomotives, particularly in North America, to show the
689690
volumetric flow rate of air moving from the main res to the brake pipe during release/recharge.
690691
Such an indication can be used to determine when brake pipe charging is complete,
691692
measure the amount of brake pipe leakage, and so on.
692693
The control will only function on locomotives with air brakes.
693694

694-
Here is an example implementation of ORTS_AIR_FLOW_METER as an analog dial::
695+
Here is an example implementation of ORTS_AIR_FLOW_METER as an analog dial (display types other
696+
than analog dials can be used)::
695697

696698

697699
Dial (
@@ -706,8 +708,10 @@ Here is an example implementation of ORTS_AIR_FLOW_METER as an analog dial::
706708
DirIncrease ( 0 )
707709
)
708710

709-
Applicable user-defined units are CUBIC_FT_MIN, LITERS_S, LITERS_MIN, and CUBIC_M_S. Cubic meters per
710-
second will be used if no units are specified.
711+
Alternately, a control type of ORTS_TRAIN_AIR_FLOW_METER can be used to display the total
712+
air flow rate of all locomotives, useful for distributed power where multiple locomotives can
713+
charge the brake pipe simultaneously. Applicable user-defined units are CUBIC_FT_MIN, LITERS_S,
714+
LITERS_MIN, and CUBIC_M_S. Cubic meters per second will be used if no units are specified.
711715

712716

713717
Animated 2D Wipers

Source/Documentation/Manual/physics.rst

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2350,6 +2350,52 @@ The actual set value of traction or dynamic brake of *async* group is shown in
23502350
lines *Throttle* and *Dynamic Brake*, respectively, in brackets, e.g.:
23512351
Throttle: 0% (50%).
23522352

2353+
In addition to applying power and dynamic brake, remote units can also manage the
2354+
train brake, independent brake, and emergency brake in sync with the lead locomotive.
2355+
This can dramatically speed up brake application and release on long trains, which has
2356+
allowed trains to increase in length substantially without major decreases in brake
2357+
performance. Only one locomotive in each group, the 'lead' DP unit, will have brakes
2358+
cut-in. Usually this is the same locomotive recieving throttle data from the lead
2359+
locomotive. In Open Rails, these locomotives are designated automatically. To determine
2360+
which units are the 'lead' in each group, check the ID row on the DPU Info window.
2361+
2362+
As described earlier, operation in *sync* mode or *async* mode has no effect on air
2363+
brake behavior. In reality, additional remote modes such as *set-out*, *bv out*,
2364+
and *isolate* would disable air brakes on remote units, but these modes are not
2365+
present for simplicity.
2366+
2367+
.. index::
2368+
single: ORTSDPBrakeSynchronization
2369+
2370+
By default, Open Rails will treat remote groups as manned helpers who typically
2371+
would not assist in train brake operations, so only independent brakes will synchronize.
2372+
To enable train brake synchronization, the token ``engine(ORTSDPBrakeSynchronization(``
2373+
should be used. The valid settings for ``ORTSDPBrakeSynchronization`` are as follows:
2374+
2375+
- ``"Apply"``: DP units will reduce the brake pipe pressure locally to match the
2376+
equalizing reservoir pressure of the controlling locomotive. (The controlling
2377+
locomotive must also have the ``"Apply"`` setting.)
2378+
- ``"Release"``: DP units will increase the brake pipe pressure locally to match
2379+
the equalizing reservoir pressure of the controlling locomotive. (The controlling
2380+
locomotive must also have the ``"Release"`` setting.)
2381+
- ``"Emergency"``: DP units will vent the brake pipe to 0 if an emergency application
2382+
is triggered by the controlling locomotive. (The controlling locomotive must also
2383+
have the ``"Emergency"`` setting.)
2384+
- ``"Independent"``: DP units will match the brake cylinder pressure of the
2385+
controlling locomotive, and will automatically bail-off automatic brake
2386+
applications if needed. (The controlling locomotive must also have the
2387+
``"Independent"`` setting.)
2388+
- NOTE: Although ``"Independent"`` is enabled by default,
2389+
if ``ORTSDPBrakeSynchronization`` is present in the .eng
2390+
file but ``"Independent"`` is not specified as an option,
2391+
independent brakes will NOT be synchronized.
2392+
2393+
All settings can be combined as needed, simply place a comma between each setting
2394+
in the string: ``ORTSDPBrakeSynchronization("Apply, Release, Emergency, Independent")``
2395+
will simulate the configuration of most modern locomotives. Unlike other distributed power
2396+
features, brake synchronization can be applied to any locomotive type to simulate a wide
2397+
variety of braking systems.
2398+
23532399
Distributed power info and commands can also be displayed and operated through
23542400
cabview controls, as explained :ref:`here <cabs-distributed-power>`
23552401

Source/Orts.Formats.Msts/CabViewFile.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ public enum CABViewControlTypes
201201
ORTS_QUICKRELEASE,
202202
ORTS_OVERCHARGE,
203203
ORTS_AIR_FLOW_METER,
204+
ORTS_TRAIN_AIR_FLOW_METER,
204205
ORTS_BATTERY_SWITCH_COMMAND_SWITCH,
205206
ORTS_BATTERY_SWITCH_COMMAND_BUTTON_CLOSE,
206207
ORTS_BATTERY_SWITCH_COMMAND_BUTTON_OPEN,

Source/Orts.Simulation/Simulation/Physics/Train.cs

Lines changed: 195 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ namespace Orts.Simulation.Physics
7676
public class Train
7777
{
7878
public List<TrainCar> Cars = new List<TrainCar>(); // listed front to back
79+
public List<TrainCar> DPLeadUnits = new List<TrainCar>(); // list of all DP lead locomotives
80+
// list of connected locomotives, each element is a list of the connected locomotives themselves
81+
public List<List<TrainCar>> LocoGroups = new List<List<TrainCar>>();
7982
public int Number;
8083
public string Name;
8184
public string TcsParametersFileName;
@@ -138,6 +141,7 @@ public TrainCar LastCar
138141
public float BrakeLine4 = -1; // extra line just in case, ep brake control line. -2: hold, -1: inactive, 0: release, 0 < value <=1: apply
139142
public RetainerSetting RetainerSetting = RetainerSetting.Exhaust;
140143
public int RetainerPercent = 100;
144+
public float TotalBrakePipeFlowM3pS; // Total flow rate of air from all MR to BP
141145
public float TotalTrainBrakePipeVolumeM3; // Total volume of train brake pipe
142146
public float TotalTrainBrakeCylinderVolumeM3; // Total volume of train brake cylinders
143147
public float TotalTrainBrakeSystemVolumeM3; // Total volume of train brake system
@@ -509,6 +513,9 @@ public TrainCar LeadLocomotive
509513
//if (lead.EngineBrakeController != null)
510514
// lead.EngineBrakeController.UpdateEngineBrakePressure(ref BrakeLine3PressurePSI, 1000);
511515
}
516+
517+
// If lead locomotive changes, distributed power needs to be updated
518+
SetDPUnitIDs(true);
512519
}
513520
}
514521

@@ -691,6 +698,7 @@ public Train(Simulator simulator, BinaryReader inf)
691698
BrakeLine2PressurePSI = inf.ReadSingle();
692699
BrakeLine3PressurePSI = inf.ReadSingle();
693700
BrakeLine4 = inf.ReadSingle();
701+
TotalBrakePipeFlowM3pS = inf.ReadSingle();
694702
aiBrakePercent = inf.ReadSingle();
695703
LeadLocomotiveIndex = inf.ReadInt32();
696704
RetainerSetting = (RetainerSetting)inf.ReadInt32();
@@ -1039,6 +1047,7 @@ public virtual void Save(BinaryWriter outf)
10391047
outf.Write(BrakeLine2PressurePSI);
10401048
outf.Write(BrakeLine3PressurePSI);
10411049
outf.Write(BrakeLine4);
1050+
outf.Write(TotalBrakePipeFlowM3pS);
10421051
outf.Write(aiBrakePercent);
10431052
outf.Write(LeadLocomotiveIndex);
10441053
outf.Write((int)RetainerSetting);
@@ -1314,6 +1323,7 @@ public TrainCar GetNextCab()
13141323
LeadLocomotiveIndex = Math.Abs(nextCabIndex) - 1;
13151324
Trace.Assert(LeadLocomotive != null, "Tried to switch to non-existent loco");
13161325
TrainCar newLead = LeadLocomotive; // Changing LeadLocomotiveIndex also changed LeadLocomotive
1326+
SetDPUnitIDs(true); // DP IDs must be reprocessed when lead locomotive changes
13171327
((MSTSLocomotive)newLead).UsingRearCab = nextCabIndex < 0;
13181328

13191329
if (oldLead != null && newLead != null && oldLead != newLead)
@@ -1372,6 +1382,7 @@ public void LeadNextLocomotive()
13721382
else if (coud > 1)
13731383
LeadLocomotiveIndex = firstLead;
13741384
TrainCar newLead = LeadLocomotive;
1385+
SetDPUnitIDs(true); // DP IDs must be reprocessed when lead locomotive changes
13751386
if (prevLead != null && newLead != null && prevLead != newLead)
13761387
newLead.CopyControllerSettings(prevLead);
13771388
}
@@ -1527,19 +1538,79 @@ public void ReverseCars()
15271538
/// </summary>
15281539
public void SetDPUnitIDs(bool keepRemoteGroups = false)
15291540
{
1541+
// List to keep track of new 'lead' DP units
1542+
// 'Lead' DP units follow the air brake commands of the master loco, 'trail' DP units do not
1543+
List<TrainCar> tempDPLeads = new List<TrainCar>();
1544+
TrainCar tempDPLead = null;
1545+
// Value judging how compatible this locomotive is to the lead locomotive for DP purposes
1546+
float dpRating = 0;
1547+
1548+
// List of each DP group's locomotives
1549+
List<List<TrainCar>> tempLocoGroups = new List<List<TrainCar>>();
1550+
1551+
var prevId = -1;
15301552
var id = 0;
1553+
15311554
foreach (var car in Cars)
15321555
{
1533-
//Console.WriteLine("___{0} {1}", car.CarID, id);
1534-
if (car is MSTSLocomotive)
1556+
if (car is MSTSLocomotive loco)
15351557
{
1536-
(car as MSTSLocomotive).DPUnitID = id;
1558+
float thisDPRating = DetermineDPCompatibility(LeadLocomotive, car);
1559+
1560+
loco.DPUnitID = id;
1561+
1562+
if (id != prevId) // New locomotive group
1563+
{
1564+
// Add the most suitable unit from the previous group as a DP lead unit
1565+
if (tempDPLead != null && !tempDPLeads.Contains(tempDPLead))
1566+
tempDPLeads.Add(tempDPLead);
1567+
1568+
dpRating = thisDPRating;
1569+
tempDPLead = car;
1570+
1571+
prevId = id;
1572+
}
1573+
else // Same locomotive group
1574+
// Check to see if this locomotive is more compatible than previous ones
1575+
if (thisDPRating > dpRating)
1576+
{
1577+
dpRating = thisDPRating;
1578+
tempDPLead = car;
1579+
}
1580+
15371581
if (car.RemoteControlGroup == 1 && !keepRemoteGroups)
15381582
car.RemoteControlGroup = 0;
15391583
}
15401584
else
15411585
id++;
15421586
}
1587+
1588+
// Add final DP lead unit
1589+
if (tempDPLead != null && !tempDPLeads.Contains(tempDPLead))
1590+
tempDPLeads.Add(tempDPLead);
1591+
1592+
foreach (TrainCar locoCar in tempDPLeads)
1593+
{
1594+
// The train's lead unit should always be a DP lead unit, even if not at the front
1595+
// If a different locomotive in the lead loco's group has been declared DP lead, replace that loco with the lead loco
1596+
if (LeadLocomotive is MSTSLocomotive lead && locoCar is MSTSLocomotive loco)
1597+
if (loco.DPUnitID == lead.DPUnitID && locoCar != LeadLocomotive)
1598+
{
1599+
tempDPLeads.Insert(tempDPLeads.IndexOf(locoCar), LeadLocomotive);
1600+
tempDPLeads.Remove(locoCar);
1601+
break; // foreach doesn't like it when the collection is modified during the loop, break to mitigate error
1602+
}
1603+
}
1604+
1605+
DPLeadUnits = tempDPLeads;
1606+
1607+
foreach (TrainCar loco in DPLeadUnits)
1608+
{
1609+
// Find all locomotives connected to each DP unit
1610+
tempLocoGroups.Add(DetermineLocomotiveGroup(loco));
1611+
}
1612+
1613+
LocoGroups = tempLocoGroups;
15431614
}
15441615

15451616
/// <summary>
@@ -4231,6 +4302,127 @@ public TrainCar FindLeadLocomotive()
42314302
return null;
42324303
}
42334304

4305+
//================================================================================================//
4306+
/// <summary>
4307+
/// Find connected locomotives
4308+
/// <\summary>
4309+
4310+
// Finds all locomotives connected to the TrainCar reference provided as input,
4311+
// returning the connected locomotives* as a list of TrainCars.
4312+
// Returns null if there are no locomotives in the given group.
4313+
// *If the input is a steam locomotive, the output will instead be the steam locomotive and any tenders connected.
4314+
// Useful for determining locomotive brake propagation on groups of locomotives other than the lead group.
4315+
4316+
public List<TrainCar> DetermineLocomotiveGroup(TrainCar loco)
4317+
{
4318+
if (loco is MSTSLocomotive)
4319+
{
4320+
List<TrainCar> tempGroup = new List<TrainCar>();
4321+
int first;
4322+
int last;
4323+
4324+
first = last = Cars.IndexOf(loco);
4325+
4326+
// If locomotive is a steam locomotive, check for tenders only
4327+
if (first >= 0 && loco is MSTSSteamLocomotive)
4328+
{
4329+
if (last < Cars.Count - 1 && !loco.Flipped && Cars[last + 1].WagonType == TrainCar.WagonTypes.Tender)
4330+
last++;
4331+
else if (first > 0 && loco.Flipped && Cars[first - 1].WagonType == TrainCar.WagonTypes.Tender)
4332+
first--;
4333+
}
4334+
else // Other locomotive types
4335+
{
4336+
for (int i = last; i < Cars.Count && (Cars[i] is MSTSLocomotive && !(Cars[i] is MSTSSteamLocomotive)); i++)
4337+
last = i;
4338+
for (int i = first; i >= 0 && (Cars[i] is MSTSLocomotive && !(Cars[i] is MSTSSteamLocomotive)); i--)
4339+
first = i;
4340+
}
4341+
4342+
if (first < 0 || last < 0)
4343+
return null;
4344+
else
4345+
{
4346+
for (int i = first; i <= last; i++)
4347+
tempGroup.Add(Cars[i]);
4348+
return tempGroup;
4349+
}
4350+
}
4351+
else
4352+
return null;
4353+
}
4354+
4355+
//================================================================================================//
4356+
/// <summary>
4357+
/// Find connected DP lead locomotive
4358+
/// <\summary>
4359+
4360+
// Finds the DP lead locomotive to the TrainCar reference provided as input,
4361+
// returning the TrainCar reference to the DP lead unit.
4362+
// Returns null if there are no DP lead units controlling the given train car.
4363+
4364+
public TrainCar DetermineDPLeadLocomotive(TrainCar locoCar)
4365+
{
4366+
if (Cars.Contains(locoCar) && locoCar is MSTSLocomotive loco)
4367+
foreach (TrainCar dpLead in DPLeadUnits)
4368+
if (dpLead is MSTSLocomotive dpLeadLoco && dpLeadLoco.DPUnitID == loco.DPUnitID)
4369+
return dpLead;
4370+
4371+
return null;
4372+
}
4373+
4374+
//================================================================================================//
4375+
/// <summary>
4376+
/// Find compatibility of DP connection between two locomotives
4377+
/// <\summary>
4378+
4379+
// Returns a score judging the compatibility of a lead locomotive (first input as a TrainCar)
4380+
// and remote locomotive (second input as a TrainCar). The more capabilities the two locomotives
4381+
// share, the higher the number returned. There is an additional slight bias for remote
4382+
// locomotives having higher capabilities than the lead locomotive.
4383+
// Returns -1 if one of the input TrainCars isn't a locomotive
4384+
4385+
public float DetermineDPCompatibility(TrainCar leadCar, TrainCar remoteCar)
4386+
{
4387+
float score = 0;
4388+
4389+
if (leadCar is MSTSLocomotive lead && remoteCar is MSTSLocomotive remote)
4390+
{
4391+
if (remote.DPSyncTrainApplication)
4392+
{
4393+
if (lead.DPSyncTrainApplication)
4394+
score++;
4395+
else
4396+
score += 0.1f;
4397+
}
4398+
if (remote.DPSyncTrainRelease)
4399+
{
4400+
if (lead.DPSyncTrainRelease)
4401+
score++;
4402+
else
4403+
score += 0.1f;
4404+
}
4405+
if (remote.DPSyncEmergency)
4406+
{
4407+
if (lead.DPSyncEmergency)
4408+
score++;
4409+
else
4410+
score += 0.1f;
4411+
}
4412+
if (remote.DPSyncIndependent)
4413+
{
4414+
if (lead.DPSyncIndependent)
4415+
score++;
4416+
else
4417+
score += 0.1f;
4418+
}
4419+
4420+
return score;
4421+
}
4422+
else
4423+
return -1;
4424+
}
4425+
42344426
//================================================================================================//
42354427
/// <summary>
42364428
/// Propagate brake pressure

Source/Orts.Simulation/Simulation/RollingStocks/MSTSDieselLocomotive.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1171,8 +1171,7 @@ public string GetDpuStatus(bool dataDpu, CABViewControlUnits loadUnits = CABView
11711171
var brakeInfoValue = brakeValue(Simulator.Catalog.GetString("BP"), Simulator.Catalog.GetString("Flow"));
11721172
status.AppendFormat("{0:F0}\t", brakeInfoValue);
11731173
// Air flow meter
1174-
brakeInfoValue = brakeValue(Simulator.Catalog.GetString("Flow"), Simulator.Catalog.GetString("EOT"));
1175-
status.AppendFormat("{0:F0}\t", brakeInfoValue);
1174+
status.AppendFormat("{0:F0}\t", FormatStrings.FormatAirFlow(FilteredBrakePipeFlowM3pS, IsMetric));
11761175

11771176
// Remote
11781177
if (dataDpu)

0 commit comments

Comments
 (0)