@@ -230,6 +230,7 @@ public class MSTSSteamLocomotive : MSTSLocomotive
230230 public float CylinderSteamUsageLBpS;
231231 public float NewCylinderSteamUsageLBpS;
232232 public float BlowerSteamUsageLBpS;
233+ public float FuelOilHeatingSteamUsageLbpS;
233234 public float EvaporationLBpS; // steam generation rate
234235 public float FireMassKG; // Mass of coal currently on grate area
235236 public float FireRatio; // Ratio of actual firemass to ideal firemass
@@ -3582,6 +3583,52 @@ protected override void UpdateControllers(float elapsedClockSeconds)
35823583
35833584 private void UpdateTender(float elapsedClockSeconds)
35843585 {
3586+ // Calculate steam usage required to heat fuel oil in oil fired locomotive
3587+ if (SteamLocomotiveFuelType == SteamLocomotiveFuelTypes.Oil )
3588+ {
3589+ // The following calculations are based upon the interpretation of information in
3590+ // https://www.spiraxsarco.com/learn-about-steam/steam-engineering-principles-and-heat-transfer/heating-with-coils-and-jackets#article-top
3591+ // and movement of the locomotive is described in this article
3592+ // https://www.spiraxsarco.com/learn-about-steam/steam-engineering-principles-and-heat-transfer/energy-consumption-of-tanks-and-vats
3593+ // Currently due to the difficulty of finding input information some assumptions have be made, and will be described through the calculation process.
3594+ //
3595+ // The first step is to calculate the amount of heat lost through the sides of the oil tank on the tender (this loss will ultimately need to be
3596+ // compensated by steam heating
3597+ // This can be calculated by Q (heat Loss) = Txf Coeff for tank sides * Area of Sides * Temp Diff (external vs internal)
3598+ // The most accurate calculation could be made if the bottom, sides and top of oil tank area was known, and also if the aount of exposed surface
3599+ // (subject to wind effect)
3600+ float TankSurfaceAreatoMassRatio = 0.015f; // based upon the inspection of the oil masses in a couple of known locomotives
3601+ float AssumedSurfaceAreaFt2 = Kg.ToLb(MaxTenderOilMassL * OilSpecificGravity) * TankSurfaceAreatoMassRatio;
3602+ float AreaExposedtoWindMovementFraction = 0.42f;
3603+ float OilTempRequired = 150; // Oil to be heated to 150degF
3604+ float HeatDiff = OilTempRequired - C.ToF(CarOutsideTempC);
3605+ float HeatTransferCoefficientBtuphft2F = 0.00001263f * HeatDiff * HeatDiff + 0.001175f * HeatDiff + 1.776f;
3606+
3607+ float HeatLossNoWindBTUph = (1- AreaExposedtoWindMovementFraction) * HeatTransferCoefficientBtuphft2F * AssumedSurfaceAreaFt2 * HeatDiff;
3608+
3609+ // To compensate for the train movement we need to add a wind factor
3610+ float WindCoeff = -0.0074f * absSpeedMpS * absSpeedMpS + 0.3817f * absSpeedMpS + 1f;
3611+ WindCoeff = MathHelper.Clamp(WindCoeff, 1.0f, 5.78f); // Wind speed effect will not cause any more impact once over about 25 m/s
3612+
3613+ float HeatLossWindBTUph = (1 - AreaExposedtoWindMovementFraction) * HeatTransferCoefficientBtuphft2F * AssumedSurfaceAreaFt2 * HeatDiff * WindCoeff;
3614+
3615+ float HeatLossTotalBTUph = HeatLossWindBTUph + HeatLossNoWindBTUph;
3616+
3617+ float HeatingCoilEfficiency = 0.12f;
3618+ float SteamEnthalpyBTUpLb = 980;
3619+ FuelOilHeatingSteamUsageLbpS = pS.FrompH(HeatLossTotalBTUph / (HeatingCoilEfficiency * SteamEnthalpyBTUpLb));
3620+
3621+ // adjust boiler heat and volume due to steam usage
3622+ BoilerMassLB -= elapsedClockSeconds * FuelOilHeatingSteamUsageLbpS; // Reduce boiler mass to reflect steam usage by fuel oil heating coils
3623+ BoilerHeatBTU -= elapsedClockSeconds * FuelOilHeatingSteamUsageLbpS * (BoilerSteamHeatBTUpLB - BoilerWaterHeatBTUpLB); // Reduce boiler Heat to reflect steam usage by fuel oil heating coils
3624+ BoilerHeatOutBTUpS += FuelOilHeatingSteamUsageLbpS * (BoilerSteamHeatBTUpLB - BoilerWaterHeatBTUpLB); // Reduce boiler Heat to reflect steam usage by mecahnical stoker
3625+ TotalSteamUsageLBpS += FuelOilHeatingSteamUsageLbpS;
3626+
3627+ // Increase tender water mass as steam heating condensate feed back into tender
3628+ CombinedTenderWaterVolumeUKG += (elapsedClockSeconds * FuelOilHeatingSteamUsageLbpS) / WaterLBpUKG;
3629+
3630+ }
3631+
35853632 TenderWaterLevelFraction = CombinedTenderWaterVolumeUKG / MaxTotalCombinedWaterVolumeUKG;
35863633 float TempCylinderSteamUsageLbpS = CylinderSteamUsageLBpS;
35873634 // Limit Cylinder steam usage to the maximum boiler evaporation rate, lower limit is for when the locomotive is at rest and "no steam" is being used by cylinder, ensures some coal is used.
@@ -7606,23 +7653,87 @@ public override string GetDebugStatus()
76067653
76077654 if (!(BrakeSystem is Orts.Simulation.RollingStocks.SubSystems.Brakes.MSTS.VacuumSinglePipe))
76087655 {
7609- // Display air compressor information
7610- status.AppendFormat("{0}\t{1}\t{2}/{23}\t{3}\t{4}/{23}\t{5}\t{6}/{23}\t{7}\t{8}/{23}\t{9}\t{10}/{23}\t{11}\t{12}/{23}\t{13}\t{14}/{23}\t{15}\t{16}/{23}\t{17}\t{18}/{23}\t{19}\t{20}/{23}\t({21}x{22:N1}\")\n",
7656+ if (SteamLocomotiveFuelType == SteamLocomotiveFuelTypes.Oil)
7657+ {
7658+ // Display air compressor information
7659+ status.AppendFormat("{0}\t{1}\t{2}/{23}\t{3}\t{4}/{23}\t{5}\t{6}/{23}\t{7}\t{8}/{23}\t{9}\t{10}/{23}\t{11}\t{12}/{23}\t{13}\t{14}/{23}\t{15}\t{16}/{23}\t{17}\t{18}/{23}\t{19}\t{20}/{23}\t({21}x{22:N1}\")\n",
7660+ Simulator.Catalog.GetString("Usage:"),
7661+ Simulator.Catalog.GetString("Cyl"),
7662+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(CylinderSteamUsageLBpS)), IsMetric),
7663+ Simulator.Catalog.GetString("Blower"),
7664+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(BlowerSteamUsageLBpS)), IsMetric),
7665+ Simulator.Catalog.GetString("Comprsr"),
7666+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(CompSteamUsageLBpS)), IsMetric),
7667+ Simulator.Catalog.GetString("SafetyV"),
7668+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(SafetyValveUsageLBpS)), IsMetric),
7669+ Simulator.Catalog.GetString("CylCock"),
7670+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(CylCockSteamUsageDisplayLBpS)), IsMetric),
7671+ Simulator.Catalog.GetString("Genertr"),
7672+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(GeneratorSteamUsageLBpS)), IsMetric),
7673+ Simulator.Catalog.GetString("OilHeat"),
7674+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(FuelOilHeatingSteamUsageLbpS)), IsMetric),
7675+ Simulator.Catalog.GetString("BlowD"),
7676+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(BlowdownSteamUsageLBpS)), IsMetric),
7677+ Simulator.Catalog.GetString("Booster"),
7678+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(HuDBoosterSteamConsumptionLbpS)), IsMetric),
7679+ Simulator.Catalog.GetString("MaxSafe"),
7680+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(MaxSafetyValveDischargeLbspS)), IsMetric),
7681+ NumSafetyValves,
7682+ SafetyValveSizeIn,
7683+ FormatStrings.h);
7684+ }
7685+ else
7686+ {
7687+ // Display air compressor information with stoker fired locomotive
7688+ status.AppendFormat("{0}\t{1}\t{2}/{23}\t{3}\t{4}/{23}\t{5}\t{6}/{23}\t{7}\t{8}/{23}\t{9}\t{10}/{23}\t{11}\t{12}/{23}\t{13}\t{14}/{23}\t{15}\t{16}/{23}\t{17}\t{18}/{23}\t{19}\t{20}/{23}\t({21}x{22:N1}\")\n",
7689+ Simulator.Catalog.GetString("Usage:"),
7690+ Simulator.Catalog.GetString("Cyl"),
7691+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(CylinderSteamUsageLBpS)), IsMetric),
7692+ Simulator.Catalog.GetString("Blower"),
7693+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(BlowerSteamUsageLBpS)), IsMetric),
7694+ Simulator.Catalog.GetString("Comprsr"),
7695+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(CompSteamUsageLBpS)), IsMetric),
7696+ Simulator.Catalog.GetString("SafetyV"),
7697+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(SafetyValveUsageLBpS)), IsMetric),
7698+ Simulator.Catalog.GetString("CylCock"),
7699+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(CylCockSteamUsageDisplayLBpS)), IsMetric),
7700+ Simulator.Catalog.GetString("Genertr"),
7701+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(GeneratorSteamUsageLBpS)), IsMetric),
7702+ Simulator.Catalog.GetString("Stoker"),
7703+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(StokerSteamUsageLBpS)), IsMetric),
7704+ Simulator.Catalog.GetString("BlowD"),
7705+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(BlowdownSteamUsageLBpS)), IsMetric),
7706+ Simulator.Catalog.GetString("Booster"),
7707+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(HuDBoosterSteamConsumptionLbpS)), IsMetric),
7708+ Simulator.Catalog.GetString("MaxSafe"),
7709+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(MaxSafetyValveDischargeLbspS)), IsMetric),
7710+ NumSafetyValves,
7711+ SafetyValveSizeIn,
7712+ FormatStrings.h);
7713+ }
7714+ }
7715+ else
7716+ {
7717+
7718+ if (SteamLocomotiveFuelType == SteamLocomotiveFuelTypes.Oil)
7719+ {
7720+ // Display steam ejector information instead of air compressor with stoker
7721+ status.AppendFormat("{0}\t{1}\t{2}/{23}\t{3}\t{4}/{23}\t{5}\t{6}/{23}\t{7}\t{8}/{23}\t{9}\t{10}/{23}\t{11}\t{12}/{23}\t{13}\t{14}/{23}\t{15}\t{16}/{23}\t{17}\t{18}/{23}\t{19}\t{20}/{23}\t({21}x{22:N1}\")\n",
76117722 Simulator.Catalog.GetString("Usage:"),
76127723 Simulator.Catalog.GetString("Cyl"),
76137724 FormatStrings.FormatMass(pS.TopH(Kg.FromLb(CylinderSteamUsageLBpS)), IsMetric),
76147725 Simulator.Catalog.GetString("Blower"),
76157726 FormatStrings.FormatMass(pS.TopH(Kg.FromLb(BlowerSteamUsageLBpS)), IsMetric),
7616- Simulator.Catalog.GetString("Comprsr "),
7617- FormatStrings.FormatMass(pS.TopH(Kg.FromLb(CompSteamUsageLBpS )), IsMetric),
7727+ Simulator.Catalog.GetString("Ejector "),
7728+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(EjectorTotalSteamConsumptionLbpS )), IsMetric),
76187729 Simulator.Catalog.GetString("SafetyV"),
76197730 FormatStrings.FormatMass(pS.TopH(Kg.FromLb(SafetyValveUsageLBpS)), IsMetric),
76207731 Simulator.Catalog.GetString("CylCock"),
76217732 FormatStrings.FormatMass(pS.TopH(Kg.FromLb(CylCockSteamUsageDisplayLBpS)), IsMetric),
76227733 Simulator.Catalog.GetString("Genertr"),
76237734 FormatStrings.FormatMass(pS.TopH(Kg.FromLb(GeneratorSteamUsageLBpS)), IsMetric),
7624- Simulator.Catalog.GetString("Stoker "),
7625- FormatStrings.FormatMass(pS.TopH(Kg.FromLb(StokerSteamUsageLBpS )), IsMetric),
7735+ Simulator.Catalog.GetString("OilHeat "),
7736+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(FuelOilHeatingSteamUsageLbpS )), IsMetric),
76267737 Simulator.Catalog.GetString("BlowD"),
76277738 FormatStrings.FormatMass(pS.TopH(Kg.FromLb(BlowdownSteamUsageLBpS)), IsMetric),
76287739 Simulator.Catalog.GetString("Booster"),
@@ -7632,11 +7743,13 @@ public override string GetDebugStatus()
76327743 NumSafetyValves,
76337744 SafetyValveSizeIn,
76347745 FormatStrings.h);
7635- }
7636- else
7637- {
7638- // Display steam ejector information instead of air compressor
7639- status.AppendFormat("{0}\t{1}\t{2}/{23}\t{3}\t{4}/{23}\t{5}\t{6}/{23}\t{7}\t{8}/{23}\t{9}\t{10}/{23}\t{11}\t{12}/{23}\t{13}\t{14}/{23}\t{15}\t{16}/{23}\t{17}\t{18}/{23}\t{19}\t{20}/{23}\t({21}x{22:N1}\")\n",
7746+
7747+ }
7748+ else
7749+ {
7750+
7751+ // Display steam ejector information instead of air compressor with stoker
7752+ status.AppendFormat("{0}\t{1}\t{2}/{23}\t{3}\t{4}/{23}\t{5}\t{6}/{23}\t{7}\t{8}/{23}\t{9}\t{10}/{23}\t{11}\t{12}/{23}\t{13}\t{14}/{23}\t{15}\t{16}/{23}\t{17}\t{18}/{23}\t{19}\t{20}/{23}\t({21}x{22:N1}\")\n",
76407753 Simulator.Catalog.GetString("Usage:"),
76417754 Simulator.Catalog.GetString("Cyl"),
76427755 FormatStrings.FormatMass(pS.TopH(Kg.FromLb(CylinderSteamUsageLBpS)), IsMetric),
@@ -7661,6 +7774,7 @@ public override string GetDebugStatus()
76617774 NumSafetyValves,
76627775 SafetyValveSizeIn,
76637776 FormatStrings.h);
7777+ }
76647778 }
76657779
76667780
0 commit comments