feat: redesign bench calibration (factor/offset), add Ttank/P2 displays, fix sensor calibration
- Replace P1-P6 rational transfer function with factor/offset model for bench params - Add explicit rx/tx direction flags in bench XML configuration - Add T.Tank (BenchTemp) and P2 (AnalogSensor2) to temperature/pressure display - Apply SensorConfiguration calibration to pressure channels, fix empty sensors.xml fallback - Add live value labels to flowmeter charts - Hide pump live values and PSG encoder standalone label - Add K-Line connection state model, improve KWP service and status displays - Restructure .claude/skills into subdirectory format Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,13 +8,12 @@ namespace HC_APTBS.Models
|
||||
/// Represents a single logical parameter exchanged over CAN.
|
||||
/// Each parameter occupies up to two bytes within a CAN frame identified by <see cref="MessageId"/>.
|
||||
///
|
||||
/// <para><b>Transfer function (transmit / non-receive):</b><br/>
|
||||
/// <c>output = ((P1 * Value + P2) / (P3 * Value + P4)) + P5 + P6</c></para>
|
||||
/// <para><b>Bench params</b> use the simple calibration model:<br/>
|
||||
/// Linear: <c>eng = raw * Factor + Offset</c><br/>
|
||||
/// Inverse: <c>eng = Factor / raw + Offset</c></para>
|
||||
///
|
||||
/// <para><b>Inverse transfer function (receive):</b><br/>
|
||||
/// <c>output = (−P2 − P3·P5 − P4·P6 + P4·Value) / (P1 + P3·P5 + P3·P6 − P3·Value)</c></para>
|
||||
///
|
||||
/// <para>Set <see cref="DisableCalibration"/> to use the identity transform (P1=1, P2=0, P3=0, P4=1, P5=0, P6=0).</para>
|
||||
/// <para><b>Pump params</b> (legacy) use the P1–P6 rational transfer function via
|
||||
/// <see cref="GetTransformResult"/>. Set <see cref="UseLegacyTransform"/> to enable.</para>
|
||||
/// </summary>
|
||||
public class CanBusParameter
|
||||
{
|
||||
@@ -40,7 +39,18 @@ namespace HC_APTBS.Models
|
||||
/// </summary>
|
||||
public int Type { get; set; } = 0;
|
||||
|
||||
// ── Calibration coefficients (P1–P6) ─────────────────────────────────────
|
||||
// ── Simple calibration (bench params) ────────────────────────────────────
|
||||
|
||||
/// <summary>Multiplication factor: <c>eng = raw * Factor + Offset</c>.</summary>
|
||||
public double Factor { get; set; } = 1.0;
|
||||
|
||||
/// <summary>Additive offset: <c>eng = raw * Factor + Offset</c>.</summary>
|
||||
public double Offset { get; set; }
|
||||
|
||||
/// <summary>When true, calibration uses <c>eng = Factor / raw + Offset</c>.</summary>
|
||||
public bool IsInverse { get; set; }
|
||||
|
||||
// ── Legacy P1–P6 calibration (pump params) ───────────────────────────────
|
||||
|
||||
/// <summary>Transfer function coefficient P1 (numerator multiplier).</summary>
|
||||
public double P1 { get; set; }
|
||||
@@ -66,6 +76,12 @@ namespace HC_APTBS.Models
|
||||
/// </summary>
|
||||
public bool DisableCalibration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When true, <see cref="GetTransformResult"/> is used instead of
|
||||
/// <see cref="Calibrate"/>. Enabled for pump params loaded via <see cref="FromXml"/>.
|
||||
/// </summary>
|
||||
public bool UseLegacyTransform { get; set; }
|
||||
|
||||
// ── Runtime state ─────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>Current decoded engineering-unit value (updated by the CAN read thread).</summary>
|
||||
@@ -78,8 +94,8 @@ namespace HC_APTBS.Models
|
||||
public bool NeedsUpdate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When true this parameter is used in the receive direction and applies the
|
||||
/// inverse transfer function in <see cref="GetTransformResult"/>.
|
||||
/// True for receive-direction params (decoded from incoming CAN frames).
|
||||
/// False for transmit-direction params (packed into outgoing frames).
|
||||
/// </summary>
|
||||
public bool IsReceive { get; set; }
|
||||
|
||||
@@ -98,33 +114,62 @@ namespace HC_APTBS.Models
|
||||
set => MessageId = value;
|
||||
}
|
||||
|
||||
// ── Transfer function ─────────────────────────────────────────────────────
|
||||
// ── Simple calibration ────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Applies the calibration transfer function to <see cref="Value"/>.
|
||||
/// Converts a raw CAN value to engineering units using Factor/Offset.
|
||||
/// </summary>
|
||||
public double Calibrate(double raw)
|
||||
{
|
||||
if (IsInverse)
|
||||
return raw != 0 ? Factor / raw + Offset : 0;
|
||||
return raw * Factor + Offset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an engineering value back to raw CAN value (for transmit).
|
||||
/// </summary>
|
||||
public double CalibrateReverse(double eng)
|
||||
{
|
||||
if (IsInverse)
|
||||
{
|
||||
double denom = eng - Offset;
|
||||
return denom != 0 ? Factor / denom : 0;
|
||||
}
|
||||
return Factor != 0 ? (eng - Offset) / Factor : 0;
|
||||
}
|
||||
|
||||
// ── Legacy P1–P6 transfer function (pump params) ─────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Applies the P1–P6 rational transfer function to <see cref="Value"/>.
|
||||
/// Used only by pump params (<see cref="UseLegacyTransform"/> = true).
|
||||
/// </summary>
|
||||
/// <returns>Calibrated engineering-unit result.</returns>
|
||||
public double GetTransformResult()
|
||||
{
|
||||
if (IsReceive)
|
||||
{
|
||||
// Inverse function: maps a measured value back to the raw command value.
|
||||
return (-P2 - P3 * P5 - P4 * P6 + P4 * Value)
|
||||
/ (P1 + P3 * P5 + P3 * P6 - P3 * Value);
|
||||
}
|
||||
|
||||
// Forward function: maps a setpoint to the raw CAN integer to transmit.
|
||||
return ((P1 * Value + P2) / (P3 * Value + P4)) + P5 + P6;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the rounded integer value ready to be packed into a CAN frame byte pair.
|
||||
/// Returns the raw CAN value for transmission.
|
||||
/// Delegates to simple or legacy calibration depending on param type.
|
||||
/// </summary>
|
||||
public double GetTransmitValue() => GetTransformResult();
|
||||
public double GetTransmitValue()
|
||||
{
|
||||
if (UseLegacyTransform)
|
||||
return GetTransformResult();
|
||||
return CalibrateReverse(Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets calibration coefficients to the identity transform so the raw value
|
||||
/// passes through <see cref="GetTransformResult"/> unchanged.
|
||||
/// passes through unchanged.
|
||||
/// </summary>
|
||||
public void SetIdentityCalibration()
|
||||
{
|
||||
@@ -152,7 +197,8 @@ namespace HC_APTBS.Models
|
||||
StringComparison.OrdinalIgnoreCase),
|
||||
Alpha = ParseDecimal(xe.Attribute("filter")?.Value, 1.0),
|
||||
DisableCalibration = string.Equals(xe.Attribute("disableparams")?.Value,
|
||||
"true", StringComparison.OrdinalIgnoreCase)
|
||||
"true", StringComparison.OrdinalIgnoreCase),
|
||||
UseLegacyTransform = true
|
||||
};
|
||||
|
||||
if (p.DisableCalibration)
|
||||
@@ -173,7 +219,7 @@ namespace HC_APTBS.Models
|
||||
}
|
||||
|
||||
/// <summary>Parses a decimal string that may use comma or dot as separator.</summary>
|
||||
private static double ParseDecimal(string? value, double fallback)
|
||||
internal static double ParseDecimal(string? value, double fallback)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value)) return fallback;
|
||||
return double.Parse(value.Replace(',', '.'), CultureInfo.InvariantCulture);
|
||||
@@ -183,22 +229,19 @@ namespace HC_APTBS.Models
|
||||
public XElement ToXml()
|
||||
{
|
||||
var elm = new XElement(Name,
|
||||
new XAttribute("id", MessageId.ToString("X")),
|
||||
new XAttribute("byteh", ByteH),
|
||||
new XAttribute("bytel", ByteL),
|
||||
new XAttribute("filter", Alpha),
|
||||
new XAttribute("disableparams", DisableCalibration));
|
||||
new XAttribute("id", MessageId.ToString("X")),
|
||||
new XAttribute("byteh", ByteH),
|
||||
new XAttribute("bytel", ByteL),
|
||||
new XAttribute("direction", IsReceive ? "rx" : "tx"));
|
||||
|
||||
if (!DisableCalibration)
|
||||
{
|
||||
elm.Add(
|
||||
new XAttribute("p1", P1),
|
||||
new XAttribute("p2", P2),
|
||||
new XAttribute("p3", P3),
|
||||
new XAttribute("p4", P4),
|
||||
new XAttribute("p5", P5),
|
||||
new XAttribute("p6", P6));
|
||||
}
|
||||
if (Alpha != 1.0)
|
||||
elm.Add(new XAttribute("filter", Alpha));
|
||||
if (Factor != 1.0)
|
||||
elm.Add(new XAttribute("factor", Factor));
|
||||
if (Offset != 0.0)
|
||||
elm.Add(new XAttribute("offset", Offset));
|
||||
if (IsInverse)
|
||||
elm.Add(new XAttribute("type", "inverse"));
|
||||
|
||||
return elm;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user