initial commit

This commit is contained in:
2026-04-11 12:45:18 +02:00
commit 6e1b929e2f
1246 changed files with 177580 additions and 0 deletions

425
Models/TestDefinition.cs Normal file
View File

@@ -0,0 +1,425 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Xml.Linq;
namespace HC_APTBS.Models
{
// ── Test type constants ──────────────────────────────────────────────────────
/// <summary>Well-known test type identifiers stored in <see cref="TestDefinition.Name"/>.</summary>
public static class TestType
{
public const string Dfi = "DFI";
public const string F = "F";
public const string Wl = "WL";
public const string Up = "UP";
public const string Pfp = "PFP";
public const string Svme = "SVME";
public const string Other = "OTHER";
}
// ── TestDefinition ────────────────────────────────────────────────────────────
/// <summary>
/// Defines a complete test procedure. A test consists of one or more <see cref="PhaseDefinition"/>
/// objects that are executed in order. The test is considered successful only if all phases pass.
/// </summary>
public class TestDefinition
{
// ── XML element name constants ─────────────────────────────────────────
internal const string XmlTest = "Test";
internal const string XmlPhase = "Fase";
internal const string XmlSends = "Sends";
internal const string XmlReady = "Ready";
internal const string XmlReceives = "Receives";
internal const string XmlResult = "Result";
internal const string XmlMeasure = "Measure";
public const string TimestampFormat = "yyyy/MM/dd/ HH:mm:ss";
// ── Properties ────────────────────────────────────────────────────────
/// <summary>Test type identifier (see <see cref="TestType"/>).</summary>
public string Name { get; set; } = string.Empty;
/// <summary>Conditioning time in seconds: bench must hold setpoint for this long before measurement.</summary>
public int ConditioningTimeSec { get; set; }
/// <summary>Measurement window duration in seconds.</summary>
public int MeasurementTimeSec { get; set; }
/// <summary>Number of measurements taken per second during the measurement window.</summary>
public double MeasurementsPerSecond { get; set; }
/// <summary>Execution order index within the pump's test list.</summary>
public int Order { get; set; }
/// <summary>Phase definitions, executed sequentially.</summary>
public List<PhaseDefinition> Phases { get; set; } = new();
// ── Helpers ───────────────────────────────────────────────────────────
/// <summary>Returns true if at least one phase in this test is enabled.</summary>
public bool HasActivePhase() => Phases.Any(p => p.Enabled);
/// <summary>Indicates whether this test collects result measurements (i.e. should appear in the report).</summary>
public bool HasResults() => Phases.Any(p => p.Receives?.Count > 0);
/// <summary>Returns the total estimated wall-clock time for this test in seconds.</summary>
public int EstimatedTotalSeconds()
{
int total = 0;
foreach (var p in Phases)
{
if (p.Enabled)
total += ConditioningTimeSec + MeasurementTimeSec + 5; // 5 s bench ramp-up estimate
}
return total;
}
// ── XML serialisation ─────────────────────────────────────────────────
/// <summary>Serialises this test definition to XML.</summary>
public XElement ToXml()
{
var node = new XElement(XmlTest,
new XAttribute("name", Name),
new XAttribute("Tacond", ConditioningTimeSec),
new XAttribute("Tmeasur", MeasurementTimeSec),
new XAttribute("MeasurePerSecond", MeasurementsPerSecond),
new XAttribute("orden", Order));
foreach (var phase in Phases)
node.Add(phase.ToXml());
return node;
}
/// <summary>Deserialises a test definition from XML.</summary>
public static TestDefinition FromXml(XElement element)
{
var t = new TestDefinition
{
Name = element.Attribute("name")!.Value,
ConditioningTimeSec = int.Parse(element.Attribute("Tacond")!.Value),
MeasurementTimeSec = int.Parse(element.Attribute("Tmeasur")!.Value),
MeasurementsPerSecond = double.Parse(
element.Attribute("MeasurePerSecond")!.Value, CultureInfo.InvariantCulture),
Order = int.Parse(element.Attribute("orden")!.Value)
};
foreach (var phaseEl in element.Elements(XmlPhase))
t.Phases.Add(PhaseDefinition.FromXml(phaseEl));
return t;
}
}
// ── PhaseDefinition ───────────────────────────────────────────────────────────
/// <summary>
/// Represents a single phase within a <see cref="TestDefinition"/>.
/// A phase specifies an RPM setpoint, temperature setpoint, parameter sends,
/// and expected measurement results.
/// </summary>
public class PhaseDefinition
{
/// <summary>Display name (e.g. "1000 RPM 40°C").</summary>
public string Name { get; set; } = string.Empty;
/// <summary>When true, a test failure in this phase immediately halts the entire test sequence.</summary>
public bool IsCritical { get; set; }
/// <summary>Execution order within the parent test.</summary>
public int Order { get; set; }
/// <summary>Whether this phase is included in the test run.</summary>
public bool Enabled { get; set; } = true;
/// <summary>Whether this phase passed all result criteria.</summary>
public bool Success { get; set; } = true;
/// <summary>
/// Bench readiness conditions (temperature setpoint).
/// Typically a single entry giving the required oil temperature.
/// </summary>
public List<TestParameter> Readies { get; set; } = new();
/// <summary>
/// Parameters to write to the pump before measurement
/// (RPM setpoint, DFI angle, etc.).
/// </summary>
public List<TestParameter> Sends { get; set; } = new();
/// <summary>
/// Parameters to read and compare against target values with tolerances.
/// Results are stored in <see cref="TestParameter.Result"/>.
/// </summary>
public List<TestParameter> Receives { get; set; } = new();
/// <summary>Bit positions from the Alarms CAN parameter that fired during this phase.</summary>
public List<int> ErrorBits { get; set; } = new();
// ── Helpers ───────────────────────────────────────────────────────────
/// <summary>Returns the RPM setpoint TestParameter from the Sends list.</summary>
public TestParameter? GetRpmSetpoint()
=> Sends.FirstOrDefault(tp => tp.Name == BenchParameterNames.Rpm);
/// <summary>Records an alarm bit error, notifying listeners.</summary>
public void RecordErrorBit(int bit)
{
if (!ErrorBits.Contains(bit))
ErrorBits.Add(bit);
}
/// <summary>Clears all accumulated result data for a fresh run.</summary>
public void ClearResults()
{
foreach (var tp in Receives)
tp.Result = null;
}
internal bool ReceiveResults() => Receives?.Count > 0;
// ── XML ───────────────────────────────────────────────────────────────
internal XElement ToXml()
{
var node = new XElement(TestDefinition.XmlPhase,
new XAttribute("name", Name),
new XAttribute("critica", IsCritical),
new XAttribute("orden", Order));
if (Readies.Count > 0)
{
var readyNode = new XElement(TestDefinition.XmlReady);
foreach (var tp in Readies) readyNode.Add(tp.ToXml());
node.Add(readyNode);
}
if (Sends.Count > 0)
{
var sendsNode = new XElement(TestDefinition.XmlSends);
foreach (var tp in Sends) sendsNode.Add(tp.ToXml());
node.Add(sendsNode);
}
if (Receives.Count > 0)
{
var recvNode = new XElement(TestDefinition.XmlReceives);
foreach (var tp in Receives) if (tp != null) recvNode.Add(tp.ToXml());
node.Add(recvNode);
}
return node;
}
internal static PhaseDefinition FromXml(XElement element)
{
var p = new PhaseDefinition
{
Name = element.Attribute("name")!.Value,
IsCritical = bool.Parse(element.Attribute("critica")!.Value),
Order = int.Parse(element.Attribute("orden")!.Value)
};
foreach (var group in element.Elements())
{
if (group.Name.LocalName == TestDefinition.XmlReady)
foreach (var xtp in group.Elements()) p.Readies.Add(TestParameter.FromXml(xtp));
else if (group.Name.LocalName == TestDefinition.XmlSends)
foreach (var xtp in group.Elements()) p.Sends.Add(TestParameter.FromXml(xtp));
else if (group.Name.LocalName == TestDefinition.XmlReceives)
foreach (var xtp in group.Elements()) p.Receives.Add(TestParameter.FromXml(xtp));
}
return p;
}
}
// ── TestParameter ─────────────────────────────────────────────────────────────
/// <summary>
/// A single measurement point within a <see cref="PhaseDefinition"/>: a named CAN
/// parameter with an expected value and tolerance window.
/// </summary>
public class TestParameter
{
/// <summary>CAN parameter name (see <see cref="BenchParameterNames"/>).</summary>
public string Name { get; set; } = string.Empty;
/// <summary>Target setpoint or expected measurement value.</summary>
public double Value { get; set; }
/// <summary>Acceptable deviation from <see cref="Value"/> for a pass result.</summary>
public double Tolerance { get; set; }
/// <summary>Accumulated measurement result, populated during phase execution.</summary>
public TestResult? Result { get; set; }
public TestParameter() { }
/// <param name="name">Parameter name.</param>
/// <param name="value">Target value.</param>
/// <param name="tolerance">Acceptable deviation.</param>
public TestParameter(string name, double value, double tolerance)
{
Name = name;
Value = value;
Tolerance = tolerance;
}
/// <summary>Returns a shallow clone without Result or listener.</summary>
public TestParameter Clone() => new(Name, Value, Tolerance);
// ── XML ───────────────────────────────────────────────────────────────
internal XElement ToXml()
{
var node = new XElement(Name,
new XAttribute("value", Value),
new XAttribute("tolerance", Tolerance));
if (Result != null)
node.Add(Result.ToXml());
return node;
}
internal static TestParameter FromXml(XElement element)
{
var tp = new TestParameter(
element.Name.LocalName,
double.Parse(element.Attribute("value")!.Value, CultureInfo.InvariantCulture),
double.Parse(element.Attribute("tolerance")!.Value, CultureInfo.InvariantCulture));
var resultEl = element.Element(TestDefinition.XmlResult);
if (resultEl != null)
tp.Result = TestResult.FromXml(resultEl);
return tp;
}
}
// ── TestResult ────────────────────────────────────────────────────────────────
/// <summary>
/// Accumulated measurement result for a single <see cref="TestParameter"/> over
/// one phase execution. Stores all individual samples and computes the average.
/// </summary>
public class TestResult
{
/// <summary>True if the average fell within the tolerance window.</summary>
public bool Passed { get; set; }
/// <summary>Average of all collected samples.</summary>
public double Average { get; set; }
/// <summary>Test type used to select the tolerance extension rules.</summary>
public string TestType { get; set; } = string.Empty;
/// <summary>All individual measurement samples collected during the phase.</summary>
public List<MeasurementSample> Samples { get; set; } = new();
/// <summary>Appends a new sample to the collection.</summary>
public void AddSample(MeasurementSample sample) => Samples.Add(sample);
/// <summary>Clears all accumulated samples.</summary>
public void Clear() => Samples.Clear();
/// <summary>
/// Computes the average of all samples and evaluates pass/fail against the
/// tolerance window. UP and PFP test types apply a configurable extension factor
/// supplied by <paramref name="upExtension"/> / <paramref name="pfpExtension"/>.
/// </summary>
/// <param name="expected">Target value.</param>
/// <param name="tolerance">Base tolerance.</param>
/// <param name="upExtension">Extension factor for UP tests (0 = no extension).</param>
/// <param name="pfpExtension">Extension factor for PFP tests (0 = no extension).</param>
public void Evaluate(
double expected,
double tolerance,
double upExtension = 0,
double pfpExtension = 0)
{
if (Samples.Count == 0) { Passed = false; return; }
double sum = 0;
foreach (var s in Samples) sum += s.Value;
Average = Math.Round(sum / Samples.Count, 2);
if (TestType == global::HC_APTBS.Models.TestType.Up)
{
double hi = (expected + tolerance) * (1 + upExtension);
double lo = (expected - tolerance) * (1 - upExtension);
Passed = Average >= lo && Average <= hi;
}
else if (TestType == global::HC_APTBS.Models.TestType.Pfp)
{
double hi = (expected + tolerance) * (1 + pfpExtension);
double lo = (expected - tolerance) * (1 - pfpExtension);
Passed = Average >= lo && Average <= hi;
}
else
{
Passed = Math.Abs(Average - expected) <= tolerance;
}
}
// ── XML ───────────────────────────────────────────────────────────────
internal XElement ToXml()
{
var node = new XElement(TestDefinition.XmlResult,
new XAttribute("average", Average),
new XAttribute("result", Passed),
new XAttribute("testtype", TestType));
foreach (var s in Samples)
node.Add(s.ToXml());
return node;
}
internal static TestResult FromXml(XElement element)
{
var tr = new TestResult
{
Passed = bool.Parse(element.Attribute("result")!.Value),
Average = double.Parse(element.Attribute("average")!.Value, CultureInfo.InvariantCulture),
TestType = element.Attribute("testtype")?.Value ?? string.Empty
};
foreach (var measureEl in element.Elements())
tr.Samples.Add(MeasurementSample.FromXml(measureEl));
return tr;
}
}
// ── MeasurementSample ─────────────────────────────────────────────────────────
/// <summary>A single time-stamped measurement value collected during a phase.</summary>
public class MeasurementSample
{
/// <summary>Engineering-unit measurement value.</summary>
public double Value { get; set; }
/// <summary>Timestamp when the sample was acquired (format <see cref="TestDefinition.TimestampFormat"/>).</summary>
public string Timestamp { get; set; } = string.Empty;
internal XElement ToXml()
=> new XElement(TestDefinition.XmlMeasure,
new XAttribute("value", Value),
new XAttribute("time", Timestamp));
internal static MeasurementSample FromXml(XElement element)
=> new MeasurementSample
{
Value = double.Parse(element.Attribute("value")!.Value, CultureInfo.InvariantCulture),
Timestamp = element.Attribute("time")?.Value ?? string.Empty
};
}
}