/* ======================================================================== * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. * * OPC Foundation MIT License 1.00 * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * The complete license agreement can be found here: * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ using System; namespace Generators { /// /// The GenX-500 product datasheet as compile-time constants. /// /// /// /// This is the single source of truth shared by DATASHEET.md, the /// address space (nameplate, engineering ranges, trip points), the simulation /// and the conformance tests. Changing a value here moves the published model, /// the simulated behaviour and the OpenUSD gauges together, and /// GeneratorDatasheetConformanceTests fails if the document and these /// constants ever disagree. /// /// /// The machine is fictitious. Its parameters are ordinary engineering values /// for a mid-size industrial diesel generating set - a 50 Hz, four-pole, /// 1500 min-1 set in the 400 kW prime class on a turbocharged and aftercooled /// inline-six - chosen so the simulation is physically self-consistent rather /// than to represent any particular product. /// /// /// Units here are the datasheet's own engineering units (kW, bar, degrees /// Celsius, litres per hour). OPC UA publishes SI, so the node manager /// converts on the way out; see . /// /// internal static class GeneratorDatasheet { /// /// Nameplate identity. /// public static class Identity { /// /// Manufacturer name carried by the DI and Machinery nameplates. /// public const string Manufacturer = "SimGen Systems"; /// /// Marketing model designation. /// public const string Model = "GenX-500"; /// /// Manufacturer's product page, used as ManufacturerUri. /// public const string ManufacturerUri = "https://simgen.example.com"; /// /// Orderable product code. /// public const string ProductCode = "GX500-400-50-4W"; /// /// Hardware revision of the skid and driveline. /// public const string HardwareRevision = "3.1"; /// /// Controller firmware revision. /// public const string SoftwareRevision = "5.4.2"; /// /// Combined device revision reported through DI. /// public const string DeviceRevision = "3.1/5.4.2"; /// /// Device class reported through the Machinery identification add-in. /// public const string DeviceClass = "GeneratingSet"; /// /// Year the set left the factory. /// public const ushort YearOfConstruction = 2025; /// /// Month the set left the factory. /// public const byte MonthOfConstruction = 4; } /// /// ISO 8528 duty ratings. /// public static class Ratings { /// /// Prime power (PRP): unlimited hours at variable load, in watts. /// public const double PrimePowerWatts = 400_000.0; /// /// Prime apparent power, in volt-amperes. /// public const double PrimeApparentPowerVoltAmperes = PrimePowerWatts / Electrical.RatedPowerFactor; /// /// Standby power (ESP): emergency use at varying load, in watts. /// public const double StandbyPowerWatts = 440_000.0; /// /// Standby apparent power, in volt-amperes. /// public const double StandbyApparentPowerVoltAmperes = StandbyPowerWatts / Electrical.RatedPowerFactor; /// /// Reference ambient temperature the ratings assume, in degrees Celsius. /// public const double ReferenceAmbientCelsius = 25.0; /// /// Reference site altitude the ratings assume, in metres. /// public const double ReferenceAltitudeMetres = 150.0; } /// /// Alternator and electrical system. /// public static class Electrical { /// /// Nominal line-to-line voltage, in volts. /// public const double RatedLineVoltage = 400.0; /// /// Nominal line-to-neutral voltage, in volts. /// public const double RatedPhaseVoltage = 230.0; /// /// Rated power factor, lagging. /// public const double RatedPowerFactor = 0.8; /// /// Nominal output frequency, in hertz. /// public const double RatedFrequency = 50.0; /// /// Number of alternator poles. /// public const int Poles = 4; /// /// Number of phases. /// public const int Phases = 3; /// /// Rated line current at prime power and rated power factor, in amperes. /// /// /// I = S / (sqrt(3) * V_LL). Computed rather than tabulated so it can /// never drift away from the rating it is derived from. /// public static double RatedCurrentAmperes => Ratings.PrimeApparentPowerVoltAmperes / (Math.Sqrt(3.0) * RatedLineVoltage); } /// /// Engine mechanical data. /// public static class Engine { /// /// Rated engine speed, in revolutions per minute. /// /// /// Fixed by the pole count and the output frequency: /// N = 120 * f / p = 120 * 50 / 4. /// public const double RatedSpeedRpm = 120.0 * Electrical.RatedFrequency / Electrical.Poles; /// /// Number of cylinders. /// public const int Cylinders = 6; /// /// Swept volume, in litres. /// public const double DisplacementLitres = 12.5; /// /// Cylinder bore, in millimetres. /// public const double BoreMillimetres = 130.0; /// /// Piston stroke, in millimetres. /// public const double StrokeMillimetres = 157.0; /// /// Compression ratio. /// public const double CompressionRatio = 16.5; /// /// Oil pressure at rated speed and normal temperature, in bar. /// public const double RatedOilPressureBar = 4.8; /// /// Coolant thermostat opening temperature, in degrees Celsius. /// public const double ThermostatCelsius = 82.0; } /// /// Fuel system and consumption. /// public static class Fuel { /// /// Usable capacity of the base tank, in litres. /// public const double TankCapacityLitres = 1000.0; /// /// Density of the reference diesel fuel, in kilograms per litre. /// public const double DensityKilogramsPerLitre = 0.832; /// /// Lower heating value of the reference diesel fuel, in megajoules per kilogram. /// public const double LowerHeatingValueMegajoulesPerKilogram = 42.7; /// /// Usable energy per litre of fuel, in kilowatt-hours. /// /// /// (MJ/kg * kg/L) / 3.6 MJ per kWh. /// public const double EnergyPerLitreKilowattHours = LowerHeatingValueMegajoulesPerKilogram * DensityKilogramsPerLitre / 3.6; } /// /// Physical envelope. /// public static class Dimensions { /// /// Overall length of the skid, in metres. /// public const double LengthMetres = 4.00; /// /// Overall width of the skid, in metres. /// public const double WidthMetres = 1.50; /// /// Overall height of the set, in metres. /// public const double HeightMetres = 2.20; /// /// Dry mass, in kilograms. /// public const double DryMassKilograms = 4500.0; /// /// Sound pressure level at one metre with the enclosure fitted, in decibels A. /// public const double SoundPressureDecibelsA = 75.0; } /// /// The published characteristic curves. /// /// /// Load fraction is the only independent variable in the simulation. /// Everything the set reports follows from these curves, which is what /// keeps the published values mutually consistent at every tick. /// public static class Curves { /// /// No-load intercept of the fuel-consumption curve, in litres per hour. /// public const double FuelNoLoadLitresPerHour = 3.67; /// /// Slope of the fuel-consumption curve, in litres per hour at full load. /// public const double FuelSlopeLitresPerHour = 100.0; /// /// Coolant temperature rise from thermostat to full load, in kelvin. /// public const double CoolantRiseKelvin = 13.0; /// /// Exhaust temperature at no load, in degrees Celsius. /// public const double ExhaustNoLoadCelsius = 250.0; /// /// Exhaust temperature rise from no load to full load, in kelvin. /// public const double ExhaustRiseKelvin = 300.0; /// /// Fuel consumption at a given load fraction, in litres per hour. /// /// /// Electrical output as a fraction of prime power. /// /// /// Volumetric fuel rate in litres per hour. /// public static double FuelLitresPerHour(double loadFraction) { return FuelNoLoadLitresPerHour + (FuelSlopeLitresPerHour * loadFraction); } /// /// Electrical efficiency at a given load fraction, in percent. /// /// /// Electrical output as a fraction of prime power. /// /// /// Fuel-to-electricity conversion efficiency in percent, or zero when the /// set is not producing power. /// /// /// The identity that makes the model self-consistent: /// eta = P / (Vdot * rho * LHV). Because both P and Vdot are functions of /// the same load fraction, the published efficiency always reconciles with /// the published power and fuel rate. /// public static double EfficiencyPercent(double loadFraction) { if (loadFraction <= 0.0) { return 0.0; } double outputKilowatts = loadFraction * Ratings.PrimePowerWatts / 1000.0; double inputKilowatts = FuelLitresPerHour(loadFraction) * Fuel.EnergyPerLitreKilowattHours; return 100.0 * outputKilowatts / inputKilowatts; } /// /// Coolant temperature at a given load fraction, in degrees Celsius. /// /// /// Electrical output as a fraction of prime power. /// /// /// Jacket-water temperature in degrees Celsius. /// public static double CoolantCelsius(double loadFraction) { return Engine.ThermostatCelsius + (CoolantRiseKelvin * loadFraction); } /// /// Exhaust gas temperature at a given load fraction, in degrees Celsius. /// /// /// Electrical output as a fraction of prime power. /// /// /// Exhaust temperature in degrees Celsius. /// public static double ExhaustCelsius(double loadFraction) { return ExhaustNoLoadCelsius + (ExhaustRiseKelvin * loadFraction); } } /// /// Protection trip points. /// public static class TripPoints { /// /// Low oil pressure shutdown, in bar. /// public const double LowOilPressureBar = 1.7; /// /// High coolant temperature shutdown, in degrees Celsius. /// public const double HighCoolantCelsius = 98.0; /// /// Overspeed shutdown, in revolutions per minute. /// public const double OverspeedRpm = 1.15 * Engine.RatedSpeedRpm; /// /// Overload alarm, as a fraction of prime power. /// public const double OverloadFraction = 1.10; /// /// Low fuel level warning, in percent of tank capacity. /// public const double LowFuelPercent = 15.0; /// /// Low battery voltage warning, in volts. /// public const double LowBatteryVolts = 22.0; } /// /// Engineering ranges published as EURange on the measured variables. /// public static class Ranges { /// /// Upper bound of the real-power range, in watts. /// public const double RealPowerMaxWatts = 550_000.0; /// /// Lower bound of the frequency range, in hertz. /// public const double FrequencyMinHertz = 45.0; /// /// Upper bound of the frequency range, in hertz. /// public const double FrequencyMaxHertz = 55.0; /// /// Upper bound of the voltage range, in volts. /// public const double VoltageMaxVolts = 480.0; /// /// Upper bound of the current range, in amperes. /// public const double CurrentMaxAmperes = 800.0; /// /// Upper bound of the engine-speed range, in revolutions per minute. /// public const double SpeedMaxRpm = 2000.0; /// /// Upper bound of the coolant-temperature range, in degrees Celsius. /// public const double CoolantMaxCelsius = 120.0; /// /// Upper bound of the exhaust-temperature range, in degrees Celsius. /// public const double ExhaustMaxCelsius = 700.0; /// /// Upper bound of the oil-pressure range, in bar. /// public const double OilPressureMaxBar = 8.0; /// /// Upper bound of the fuel-rate range, in litres per hour. /// public const double FuelRateMaxLitresPerHour = 120.0; /// /// Upper bound of the battery-voltage range, in volts. /// public const double BatteryVoltageMaxVolts = 32.0; /// /// Upper bound of the load-percent range. /// public const double LoadPercentMax = 120.0; } /// /// Nominal values the simulation starts from and returns to. /// public static class Simulation { /// /// Nominal battery voltage of the starting system, in volts. /// public const double BatteryVolts = 24.0; /// /// Ambient air temperature, in degrees Celsius. /// public const double AmbientCelsius = 25.0; /// /// Fuel level the tank starts at, in percent of capacity. /// public const double InitialFuelPercent = 88.0; /// /// Load fraction the sets settle around when loaded. /// public const double NominalLoadFraction = 0.72; /// /// Peak-to-peak load swing about the nominal load fraction. /// public const double LoadSwingFraction = 0.18; } /// /// Conversions between the datasheet's engineering units and the SI units /// OPC UA publishes. /// public static class Convert { /// /// Zero degrees Celsius expressed in kelvin. /// public const double KelvinOffset = 273.15; /// /// Pascals per bar. /// public const double PascalsPerBar = 100_000.0; /// /// Seconds per hour. /// public const double SecondsPerHour = 3600.0; /// /// Converts degrees Celsius to kelvin. /// /// Temperature in degrees Celsius. /// Temperature in kelvin. public static double ToKelvin(double celsius) { return celsius + KelvinOffset; } /// /// Converts bar to pascal. /// /// Pressure in bar. /// Pressure in pascal. public static double ToPascal(double bar) { return bar * PascalsPerBar; } /// /// Converts litres per hour to cubic metres per second. /// /// Volumetric rate in litres per hour. /// Volumetric rate in cubic metres per second. public static double ToCubicMetresPerSecond(double litresPerHour) { return litresPerHour / 1000.0 / SecondsPerHour; } } } }