/// /// Copyright (c) 2013-2023 Sensus Slovensko a.s. /// using System; using System.Collections.Generic; using System.Globalization; using System.IO; using Common; namespace Config.Entities { /// /// Test, consisting of one or more repetitions of the test 'SingleTest'. /// public class Test : IHasName, IHasItemNr { public virtual int Id { get; protected set; } public virtual int ItemNr { get; set; } public virtual string Name { get; set; } public virtual int Part { get; set; } /// Part=0 ... test of all WM-s /// Part>0 ... part of a set of tests with the same Name: subset of WM-s is given by MetersPath public virtual TestProfile Profile { get; set; } /// UserDefined, UserDefinedCompound, UserDefinedHeatMeter, Protected, ProtectedCompound, ProtectedHeatMeter public virtual sbyte Publish { get; set; } /// 0=no, 1=in all protocols, 2=on screen, 3=internal public virtual bool DoEvaluate { get; set; } public virtual double Qtg { get; set; } /// When == -1 => Qtg is determined by the test method, otherwise Qtg is in [m3/h], public virtual double Qfrom { get; set; } /// Water flow low limit in [m3/h] or in [%] of Qtg public virtual double Qto { get; set; } /// Water flow high limit in [m3/h] or in [%] of Qtg public virtual bool IsFromToInPct { get; set; } /// true: Qfrom and Qto are in % of Qtg, false: Qfrom and Qto are in [m3/h] public virtual double Volume { get; set; } /// Test volume (target) in [l] public virtual double TestTime { get; set; } /// Test time (estimate) in [s] public virtual string Method { get; set; } public virtual float ErrLimLo { get; set; } /// in [%] usually < 0, in case of heat meters: 1=class1, 2=class2, 3=class3 public virtual float ErrLimHi { get; set; } /// in [%] usually > 0, in case of heat meters: -Qn in m3/h public virtual float Uncertainty { get; set; } /// int [%] makes error limits tighter: 0 <= Uncertainty <= abs(ErrLimXx) public virtual int Repeats { get; set; } public virtual bool DoDraining { get; set; } public virtual bool DoDrainingAfter { get; set; } public virtual float TempLimLo { get; set; } /// Lower limit for the controlled temperature public virtual float TempLimHi { get; set; } /// Upper limit for the controlled temperature public virtual string TempControl { get; set; } /// Water temperature controller, null or empty = Do not control water temp public virtual float PumpPower { get; set; } /// Power of the pump in [%] in the range 0 .. 100.0f, use values 0% and 100% for non-FM pumps public virtual int MassRepeats { get; set; } /// Number of mass. measurements at the beginning/end of test, 0 = default (=5) public virtual float MassSpread { get; set; } /// Max spread of mass. measurements at the beginning/end of test, 0 = default public virtual MassMethod MassMethod { get; set; } /// method of mass. measurement at the beginning/end of test: false=slow (precise), true=using immediate mass measurement and evaluation public virtual int TimeBeforeFlow { get; set; } /// Delay time before the start of flow control in [s] public virtual int TimeFlow2Mass { get; set; } /// Delay time from the flow stable to the 1st mass measurement in [s] public virtual int TimePump2StartV { get; set; } /// Delay time from the start of the pump to opening the start valve in [s] public virtual int TimeStop2Mass { get; set; } /// Delay time from the test end (diverted) to the 2nd mass measuremen in [s] public virtual int ShortPulses { get; set; } /// Short pulses public virtual string FeedingPath { get; set; } public virtual string BenchPath { get; set; } public virtual string OutputPath { get; set; } public virtual string MetersPath { get; set; } #if HEAT_METERS public virtual string HeatMetersPath { get; set; } #endif public virtual string TransBefore { get; set; } public virtual string TransBetween { get; set; } public virtual string TransAfter { get; set; } public virtual IList MoreParams { get; set; } public virtual Procedure Procedure { get; set; } /// Additional stuff not mapped into the database public virtual bool IsOuterLoopStart { get; set; } /// Not mapped to database public virtual bool IsOuterLoopEnd { get; set; } /// Not mapped to database public virtual bool QtgTextChngd { get; set; } /// Not mapped to database, used in ProcedureDlg / Metrology1Tab public virtual bool QfromTextChngd { get; set; } /// Not mapped to database, used in ProcedureDlg / Metrology1Tab public virtual bool QtoTextChngd { get; set; } /// Not mapped to database, used in ProcedureDlg / Metrology1Tab public virtual bool VolumeTextChngd { get; set; } /// Not mapped to database, used in ProcedureDlg / Metrology1Tab public virtual bool TempLimLoTextChngd { get; set; } /// Not mapped to database, used in ProcedureDlg / Metrology1Tab public virtual bool TempLimHiTextChngd { get; set; } /// Not mapped to database, used in ProcedureDlg / Metrology1Tab public virtual Unit VolumeUnit { get; set; } /// Not mapped to database, used in ProcedureDlg / Metrology1Tab public virtual Unit FlowUnit { get; set; } /// Not mapped to database, used in ProcedureDlg / Metrology1Tab public virtual Unit MassUnit { get; set; } /// Not mapped to database, used in ProcedureDlg / Metrology1Tab public virtual Unit TempUnit { get; set; } /// Not mapped to database, used in ProcedureDlg / Metrology1Tab public virtual Unit PressUnit { get; set; } /// Not mapped to database, used in ProcedureDlg / Metrology1Tab public virtual Unit LengthUnit { get; set; } /// Not mapped to database, used in ProcedureDlg / Metrology1Tab public virtual Unit ElectricUnit { get; set; } /// Not mapped to database, used in ProcedureDlg / Metrology1Tab /// Wrappers public virtual double QfromM3ph() { return !IsFromToInPct ? Qfrom : Qtg >= 0 ? Qtg * Qfrom / 100 : 0; } public virtual double QtoM3ph() { return !IsFromToInPct ? Qto : Qtg >= 0 ? Qtg * Qto / 100 : 0; } public Test() { MoreParams = new List(); /// /// Default values /// Part = 0; Profile = TestProfile.UserDefined; Publish = (sbyte)Common.Publish.Always; DoEvaluate = true; IsFromToInPct = false; ErrLimLo = -2.0f; /// [%] lower error limit ErrLimHi = 2.0f; /// [%] upper error limit Uncertainty = 0; Repeats = 1; DoDraining = false; DoDrainingAfter = false; TempLimLo = 15.0f; TempLimHi = 25.0f; TempControl = string.Empty; PumpPower = 60.0f; /// [%] MassRepeats = 0; /// default MassSpread = 0; /// default MassMethod = MassMethod.Scale; /// default TimeBeforeFlow = 10; /// [s] time before the start of flow control in [s] TimeFlow2Mass = 5; /// [s] time from the flow stable to the 1st mass measurement in [s] TimePump2StartV = 1; /// [s] time from the 1st mass measurement to the test start in [s] TimeStop2Mass = 5; /// [s] between the test end and the final mass measurement ShortPulses = 0; /// Short pulses parameter (0 or 1) TransBefore = string.Empty; TransBetween = string.Empty; TransAfter = string.Empty; } public Test(string name, int itemNr, Procedure procedure) : this() { Name = name; ItemNr = itemNr; Procedure = procedure; } public virtual bool IsRegular() { /// Irregular / event triggered test names have form "[event] TestName" return (Name.Length > 0 && Name[0] != '[') || !Name.Contains("]"); } public virtual bool BelongsTo(IList autoTests) { if (IsRegular()) return true; if (autoTests == null) return false; string action = Name.Substring(1, Name.IndexOf(']') - 1); return autoTests.Contains(action); } public virtual void ResetChngdFlags() { QtgTextChngd = false; QfromTextChngd = false; QtoTextChngd = false; VolumeTextChngd = false; TempLimLoTextChngd = false; TempLimHiTextChngd = false; } // Makes a new copy of this object (not just a reference) public virtual Test Clone() { Test result = new Test(Name, ItemNr, Procedure); result.Part = Part; result.Profile = Profile; result.Publish = Publish; result.DoEvaluate = DoEvaluate; result.Qtg = Qtg; result.Qfrom = Qfrom; result.Qto = Qto; result.IsFromToInPct = IsFromToInPct; result.Volume = Volume; result.TestTime = TestTime; result.Method = Method; result.ErrLimLo = ErrLimLo; result.ErrLimHi = ErrLimHi; result.Uncertainty = Uncertainty; result.Repeats = Repeats; result.DoDraining = DoDraining; result.DoDrainingAfter = DoDrainingAfter; result.TempLimLo = TempLimLo; result.TempLimHi = TempLimHi; result.TempControl = TempControl; result.PumpPower = PumpPower; result.MassRepeats = MassRepeats; result.MassSpread = MassSpread; result.MassMethod = MassMethod; result.TimeBeforeFlow = TimeBeforeFlow; result.TimeFlow2Mass = TimeFlow2Mass; result.TimePump2StartV = TimePump2StartV; result.TimeStop2Mass = TimeStop2Mass; result.ShortPulses = ShortPulses; result.FeedingPath = FeedingPath; result.BenchPath = BenchPath; result.OutputPath = OutputPath; result.MetersPath = MetersPath; #if HEAT_METERS result.HeatMetersPath = HeatMetersPath; #endif result.TransBefore = TransBefore; result.TransBetween = TransBetween; result.TransAfter = TransAfter; result.VolumeUnit = VolumeUnit; result.FlowUnit = FlowUnit; result.FlowUnit = FlowUnit; result.MassUnit = MassUnit; result.TempUnit = TempUnit; result.PressUnit = PressUnit; result.LengthUnit = LengthUnit; result.ElectricUnit = ElectricUnit; foreach (var prms in MoreParams) { result.MoreParams.Add(prms.Clone()); } return result; } public virtual void Export(StreamWriter output) { CultureInfo ci = CultureInfo.InvariantCulture; output.WriteLine(Name); output.WriteLine(Part.ToString(ci)); output.WriteLine(Publish.ToString(ci)); output.WriteLine(DoEvaluate.ToString()); output.WriteLine(Qtg.ToString(ci)); output.WriteLine(Qfrom.ToString(ci)); output.WriteLine(Qto.ToString(ci)); output.WriteLine(IsFromToInPct.ToString()); output.WriteLine(Volume.ToString(ci)); output.WriteLine(TestTime.ToString(ci)); output.WriteLine(Method); output.WriteLine(ErrLimLo.ToString(ci)); output.WriteLine(ErrLimHi.ToString(ci)); output.WriteLine(Uncertainty.ToString(ci)); output.WriteLine(Repeats.ToString(ci)); output.WriteLine(DoDraining.ToString()); output.WriteLine(DoDrainingAfter.ToString()); output.WriteLine(TempLimLo.ToString(ci)); output.WriteLine(TempLimHi.ToString(ci)); output.WriteLine((TempControl != null) ? TempControl : string.Empty); output.WriteLine(PumpPower.ToString(ci)); output.WriteLine(MassRepeats.ToString(ci)); output.WriteLine(MassSpread.ToString(ci)); output.WriteLine(((byte)MassMethod).ToString(ci)); output.WriteLine(TimeBeforeFlow.ToString(ci)); output.WriteLine(TimeFlow2Mass.ToString(ci)); output.WriteLine(TimePump2StartV.ToString(ci)); output.WriteLine(TimeStop2Mass.ToString(ci)); output.WriteLine(ShortPulses.ToString(ci)); output.WriteLine(FeedingPath); output.WriteLine(BenchPath); output.WriteLine(OutputPath); output.WriteLine(MetersPath); output.WriteLine(TransBefore); output.WriteLine(TransBetween); output.WriteLine(Profile); output.WriteLine(TransAfter); foreach (var prms in MoreParams) { prms.Export(output); } output.WriteLine(); } public static Test Import(StreamReader input, Procedure newProcedure) { string firstLine = input.ReadLine(); if (string.IsNullOrEmpty(firstLine)) { return null; } CultureInfo ci = CultureInfo.InvariantCulture; Test tst = new Test(); tst.Name = firstLine; tst.Part = int.Parse(input.ReadLine(), ci); tst.Publish = sbyte.Parse(input.ReadLine(), ci); tst.DoEvaluate = bool.Parse(input.ReadLine()); tst.Qtg = double.Parse(input.ReadLine(), ci); tst.Qfrom = double.Parse(input.ReadLine(), ci); tst.Qto = double.Parse(input.ReadLine(), ci); tst.IsFromToInPct = bool.Parse(input.ReadLine()); tst.Volume = double.Parse(input.ReadLine(), ci); tst.TestTime = double.Parse(input.ReadLine(), ci); tst.Method = input.ReadLine(); tst.ErrLimLo = float.Parse(input.ReadLine(), ci); tst.ErrLimHi = float.Parse(input.ReadLine(), ci); tst.Uncertainty = float.Parse(input.ReadLine(), ci); tst.Repeats = int.Parse(input.ReadLine(), ci); tst.DoDraining = bool.Parse(input.ReadLine()); tst.DoDrainingAfter = bool.Parse(input.ReadLine()); tst.TempLimLo = float.Parse(input.ReadLine(), ci); tst.TempLimHi = float.Parse(input.ReadLine(), ci); tst.TempControl = input.ReadLine(); tst.PumpPower = float.Parse(input.ReadLine(), ci); tst.MassRepeats = int.Parse(input.ReadLine(), ci); tst.MassSpread = float.Parse(input.ReadLine(), ci); tst.MassMethod = (MassMethod)byte.Parse(input.ReadLine(), ci); tst.TimeBeforeFlow = int.Parse(input.ReadLine(), ci); tst.TimeFlow2Mass = int.Parse(input.ReadLine(), ci); tst.TimePump2StartV = int.Parse(input.ReadLine(), ci); tst.TimeStop2Mass = int.Parse(input.ReadLine(), ci); tst.ShortPulses = int.Parse(input.ReadLine(), ci); tst.FeedingPath = input.ReadLine(); tst.BenchPath = input.ReadLine(); tst.OutputPath = input.ReadLine(); tst.MetersPath = input.ReadLine(); tst.TransBefore = input.ReadLine(); tst.TransBetween = input.ReadLine(); string line = input.ReadLine(); tst.Profile = line.Equals(TestProfile.ProtectedHeatMeter.ToString()) ? TestProfile.ProtectedHeatMeter : line.Equals(TestProfile.ProtectedCompound.ToString()) ? TestProfile.ProtectedCompound : line.Equals(TestProfile.Protected.ToString()) ? TestProfile.Protected : line.Equals(TestProfile.UserDefinedHeatMeter.ToString()) ? TestProfile.UserDefinedHeatMeter : line.Equals(TestProfile.UserDefinedCompound.ToString()) ? TestProfile.UserDefinedCompound : TestProfile.UserDefined; /// UserDefined is the default tst.TransAfter = input.ReadLine(); while (true) { ComponentTest prms = ComponentTest.Import(input, tst); if (prms == null) break; else tst.MoreParams.Add(prms); } tst.Procedure = newProcedure; return tst; } public virtual string Compare(StreamReader inp) { System.Text.StringBuilder diff = new System.Text.StringBuilder(); string fmt = string.Format("Test {0} : ", Name) + "{0} = {1}\r\n (in the file {2})\r\n"; string ln; CultureInfo ci = CultureInfo.InvariantCulture; string firstLine = inp.ReadLine(); if (string.IsNullOrEmpty(firstLine)) return string.Format("Test {0} is missing in the file\r\n", Name); if (Name != firstLine) { diff.AppendFormat(fmt, "Name", Name, firstLine); }; ln = inp.ReadLine(); if (ln != Part.ToString(ci)) { diff.AppendFormat(fmt, "Part", Part.ToString(ci), ln); }; ln = inp.ReadLine(); if (ln != Publish.ToString(ci)) { diff.AppendFormat(fmt, "Publish", Publish.ToString(ci), ln); }; ln = inp.ReadLine(); if (ln != DoEvaluate.ToString(ci)) { diff.AppendFormat(fmt, "Evaluate", DoEvaluate.ToString(ci), ln); }; ln = inp.ReadLine(); if (ln != Qtg.ToString(ci)) { diff.AppendFormat(fmt, "Qtg", Qtg.ToString(ci), ln); }; ln = inp.ReadLine(); if (ln != Qfrom.ToString(ci)) { diff.AppendFormat(fmt, "Qfrom", Qfrom.ToString(ci), ln); }; ln = inp.ReadLine(); if (ln != Qto.ToString(ci)) { diff.AppendFormat(fmt, "Qto", Qto.ToString(ci), ln); }; ln = inp.ReadLine(); if (ln != IsFromToInPct.ToString()) { diff.AppendFormat(fmt, "IsFromToInPct", IsFromToInPct.ToString(), ln); }; ln = inp.ReadLine(); if (ln != Volume.ToString(ci)) { diff.AppendFormat(fmt, "Volume", Volume.ToString(ci), ln); }; ln = inp.ReadLine(); if (ln != TestTime.ToString(ci)) { diff.AppendFormat(fmt, "TstTime", TestTime.ToString(ci), ln); }; ln = inp.ReadLine(); if (ln != Method) { diff.AppendFormat(fmt, "Method", Method, ln); }; ln = inp.ReadLine(); if (ln != ErrLimLo.ToString(ci)) { diff.AppendFormat(fmt, "ErrLimLo", ErrLimLo.ToString(ci), ln); }; ln = inp.ReadLine(); if (ln != ErrLimHi.ToString(ci)) { diff.AppendFormat(fmt, "ErrLimHi", ErrLimHi.ToString(ci), ln); }; ln = inp.ReadLine(); if (ln != Uncertainty.ToString(ci)) { diff.AppendFormat(fmt, "Uncertainty", Uncertainty.ToString(ci), ln); }; ln = inp.ReadLine(); if (ln != Repeats.ToString(ci)) { diff.AppendFormat(fmt, "Repeats", Repeats.ToString(ci), ln); }; ln = inp.ReadLine(); if (ln != DoDraining.ToString()) { diff.AppendFormat(fmt, "DoDraining", DoDraining.ToString(), ln); }; ln = inp.ReadLine(); if (ln != DoDrainingAfter.ToString()) { diff.AppendFormat(fmt, "DoDrainingAfter", DoDrainingAfter.ToString(), ln); }; ln = inp.ReadLine(); if (ln != TempLimLo.ToString(ci)) { diff.AppendFormat(fmt, "TempLimLo", TempLimLo.ToString(ci), ln); }; ln = inp.ReadLine(); if (ln != TempLimHi.ToString(ci)) { diff.AppendFormat(fmt, "TempLimHi", TempLimHi.ToString(ci), ln); }; ln = inp.ReadLine(); if (ln != TempControl) { diff.AppendFormat(fmt, "TempController", TempControl, ln); }; ln = inp.ReadLine(); if (ln != PumpPower.ToString(ci)) { diff.AppendFormat(fmt, "PumpPower", PumpPower.ToString(ci), ln); }; ln = inp.ReadLine(); if (ln != MassRepeats.ToString(ci)) { diff.AppendFormat(fmt, "MassRepeats", MassRepeats.ToString(ci), ln); }; ln = inp.ReadLine(); if (ln != MassSpread.ToString(ci)) { diff.AppendFormat(fmt, "MassSpread", MassSpread.ToString(ci), ln); }; ln = inp.ReadLine(); if (ln != ((byte)MassMethod).ToString(ci)) { diff.AppendFormat(fmt, "MassMethod", ((byte)MassMethod).ToString(ci), ln); }; ln = inp.ReadLine(); if (ln != TimeBeforeFlow.ToString(ci)) { diff.AppendFormat(fmt, "TimeBeforeFlow", TimeBeforeFlow.ToString(ci), ln); }; ln = inp.ReadLine(); if (ln != TimeFlow2Mass.ToString(ci)) { diff.AppendFormat(fmt, "TimeFlow2Mass", TimeFlow2Mass.ToString(ci), ln); }; ln = inp.ReadLine(); if (ln != TimePump2StartV.ToString(ci)) { diff.AppendFormat(fmt, "TimePump2StartV", TimePump2StartV.ToString(ci), ln); }; ln = inp.ReadLine(); if (ln != TimeStop2Mass.ToString(ci)) { diff.AppendFormat(fmt, "TimeStop2Mass", TimeStop2Mass.ToString(ci), ln); }; ln = inp.ReadLine(); if (ln != FeedingPath) { diff.AppendFormat(fmt, "FeedingPath", FeedingPath, ln); }; ln = inp.ReadLine(); if (ln != BenchPath) { diff.AppendFormat(fmt, "BenchPath", BenchPath, ln); }; ln = inp.ReadLine(); if (ln != OutputPath) { diff.AppendFormat(fmt, "OutputPath", OutputPath, ln); }; ln = inp.ReadLine(); if (ln != MetersPath) { diff.AppendFormat(fmt, "MetersPath", MetersPath, ln); }; ln = inp.ReadLine(); if (ln != TransBefore) { diff.AppendFormat(fmt, "TransBefore", TransBefore, ln); }; ln = inp.ReadLine(); if (ln != TransBetween) { diff.AppendFormat(fmt, "TransBetween", TransBetween, ln); }; ln = inp.ReadLine(); if (ln != Profile.ToString()) { diff.AppendFormat(fmt, "Profile", Profile, ln); }; ln = inp.ReadLine(); if (ln != TransAfter) { diff.AppendFormat(fmt, "TransAfter", TransAfter, ln); }; return diff.ToString(); } public virtual ComponentTest GetTestParamsEntity(string cmpntName) { foreach (var tstPrms in MoreParams) { if (tstPrms.CmpntName == cmpntName) return tstPrms; } return null; } /// /// Return test title for a this test and a given repetition number /// /// 1 .. Nr. repetitions /// Test title (string) public virtual string GetExpandedTestName(int repetitionNr) { if (Repeats == 1) { if (Part == 0) { /// Single test return Name; } else { /// A part of a single test return string.Format("{0} ({1})", Name, Part); } } else { /// More test repetitions return string.Format("{0} ({1}/{2})", Name, repetitionNr, Repeats); } } public virtual double GetErrLimLo(double volumeCTV, double testTime) { if (ErrLimLo <= ErrLimHi) { return ErrLimLo; /// Error limit of a water meter } else { /// Metrological class and nominal flow of a heat meter int metrClass = (int)Math.Round(ErrLimLo); double Qp = Math.Abs(ErrLimHi); double Q = (testTime == 0) ? 1 : Math.Max(3.6 * volumeCTV / testTime, 0.00001); /// Div. by zero avoided /// Calculate the error limit of a heat meter switch (metrClass) { default: case 1: return -Math.Min(3.5, 1.0 + 0.01 * Qp / Q); case 2: return -Math.Min(5.0, 2.0 + 0.02 * Qp / Q); case 3: return -Math.Min(5.0, 3.0 + 0.05 * Qp / Q); } } } public virtual double GetErrLimHi(double volumeCTV, double testTime) { if (ErrLimLo <= ErrLimHi) { return ErrLimHi; /// Error limit of a water meter } else { /// Metrological class and nominal flow of a heat meter int metrClass = (int)Math.Round(ErrLimLo); double Qp = Math.Abs(ErrLimHi); double Q = (testTime == 0) ? 1 : Math.Max(3.6 * volumeCTV / testTime, 0.00001); /// Div. by zero avoided /// Calculate the error limit of a heat meter switch (metrClass) { default: case 1: return +Math.Min(3.5, 1.0 + 0.01 * Qp / Q); case 2: return +Math.Min(5.0, 2.0 + 0.02 * Qp / Q); case 3: return +Math.Min(5.0, 3.0 + 0.05 * Qp / Q); } } } public virtual bool IsPartCompatible(int waterMeterPartNr) { if ((this.Part == 0) || (waterMeterPartNr == 0)) return true; if ((this.Part % 10) == waterMeterPartNr) return true; if (((this.Part / 10) % 10) == waterMeterPartNr) return true; if (((this.Part / 100) % 10) == waterMeterPartNr) return true; if (((this.Part / 1000) % 10) == waterMeterPartNr) return true; return false; } public override string ToString() { string partStr = (Part > 0) ? string.Format(", part {0}", Part) : string.Empty; return string.Format("{0}(P.{1},{2}){3}", Name, ((Publish)Publish).ToString(), DoEvaluate ? "E" : "-", partStr); } } }