@@ -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.
@@ -7603,6 +7650,8 @@ public override string GetDebugStatus()
76037650
76047651 if (!(BrakeSystem is Orts.Simulation.RollingStocks.SubSystems.Brakes.MSTS.VacuumSinglePipe))
76057652 {
7653+ if (SteamLocomotiveFuelType == SteamLocomotiveFuelTypes.Oil)
7654+ {
76067655 // Display air compressor information
76077656 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",
76087657 Simulator.Catalog.GetString("Usage:"),
@@ -7618,6 +7667,35 @@ public override string GetDebugStatus()
76187667 FormatStrings.FormatMass(pS.TopH(Kg.FromLb(CylCockSteamUsageDisplayLBpS)), IsMetric),
76197668 Simulator.Catalog.GetString("Genertr"),
76207669 FormatStrings.FormatMass(pS.TopH(Kg.FromLb(GeneratorSteamUsageLBpS)), IsMetric),
7670+ Simulator.Catalog.GetString("OilHeat"),
7671+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(FuelOilHeatingSteamUsageLbpS)), IsMetric),
7672+ Simulator.Catalog.GetString("BlowD"),
7673+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(BlowdownSteamUsageLBpS)), IsMetric),
7674+ Simulator.Catalog.GetString("Booster"),
7675+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(HuDBoosterSteamConsumptionLbpS)), IsMetric),
7676+ Simulator.Catalog.GetString("MaxSafe"),
7677+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(MaxSafetyValveDischargeLbspS)), IsMetric),
7678+ NumSafetyValves,
7679+ SafetyValveSizeIn,
7680+ FormatStrings.h);
7681+ }
7682+ else
7683+ {
7684+ // Display air compressor information with stoker fired locomotive
7685+ 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",
7686+ Simulator.Catalog.GetString("Usage:"),
7687+ Simulator.Catalog.GetString("Cyl"),
7688+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(CylinderSteamUsageLBpS)), IsMetric),
7689+ Simulator.Catalog.GetString("Blower"),
7690+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(BlowerSteamUsageLBpS)), IsMetric),
7691+ Simulator.Catalog.GetString("Comprsr"),
7692+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(CompSteamUsageLBpS)), IsMetric),
7693+ Simulator.Catalog.GetString("SafetyV"),
7694+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(SafetyValveUsageLBpS)), IsMetric),
7695+ Simulator.Catalog.GetString("CylCock"),
7696+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(CylCockSteamUsageDisplayLBpS)), IsMetric),
7697+ Simulator.Catalog.GetString("Genertr"),
7698+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(GeneratorSteamUsageLBpS)), IsMetric),
76217699 Simulator.Catalog.GetString("Stoker"),
76227700 FormatStrings.FormatMass(pS.TopH(Kg.FromLb(StokerSteamUsageLBpS)), IsMetric),
76237701 Simulator.Catalog.GetString("BlowD"),
@@ -7630,9 +7708,44 @@ public override string GetDebugStatus()
76307708 SafetyValveSizeIn,
76317709 FormatStrings.h);
76327710 }
7711+ }
76337712 else
76347713 {
7635- // Display steam ejector information instead of air compressor
7714+
7715+ if (SteamLocomotiveFuelType == SteamLocomotiveFuelTypes.Oil)
7716+ {
7717+ // Display steam ejector information instead of air compressor with stoker
7718+ 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",
7719+ Simulator.Catalog.GetString("Usage:"),
7720+ Simulator.Catalog.GetString("Cyl"),
7721+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(CylinderSteamUsageLBpS)), IsMetric),
7722+ Simulator.Catalog.GetString("Blower"),
7723+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(BlowerSteamUsageLBpS)), IsMetric),
7724+ Simulator.Catalog.GetString("Ejector"),
7725+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(EjectorTotalSteamConsumptionLbpS)), IsMetric),
7726+ Simulator.Catalog.GetString("SafetyV"),
7727+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(SafetyValveUsageLBpS)), IsMetric),
7728+ Simulator.Catalog.GetString("CylCock"),
7729+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(CylCockSteamUsageDisplayLBpS)), IsMetric),
7730+ Simulator.Catalog.GetString("Genertr"),
7731+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(GeneratorSteamUsageLBpS)), IsMetric),
7732+ Simulator.Catalog.GetString("OilHeat"),
7733+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(FuelOilHeatingSteamUsageLbpS)), IsMetric),
7734+ Simulator.Catalog.GetString("BlowD"),
7735+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(BlowdownSteamUsageLBpS)), IsMetric),
7736+ Simulator.Catalog.GetString("Booster"),
7737+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(HuDBoosterSteamConsumptionLbpS)), IsMetric),
7738+ Simulator.Catalog.GetString("MaxSafe"),
7739+ FormatStrings.FormatMass(pS.TopH(Kg.FromLb(MaxSafetyValveDischargeLbspS)), IsMetric),
7740+ NumSafetyValves,
7741+ SafetyValveSizeIn,
7742+ FormatStrings.h);
7743+
7744+ }
7745+ else
7746+ {
7747+
7748+ // Display steam ejector information instead of air compressor with stoker
76367749 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",
76377750 Simulator.Catalog.GetString("Usage:"),
76387751 Simulator.Catalog.GetString("Cyl"),
@@ -7659,6 +7772,7 @@ public override string GetDebugStatus()
76597772 SafetyValveSizeIn,
76607773 FormatStrings.h);
76617774 }
7775+ }
76627776
76637777
76647778#if DEBUG_STEAM_CYLINDER_EVENTS
0 commit comments