Files
HC_APTBS/Models/CanBusParameter.cs
2026-04-11 12:45:18 +02:00

284 lines
13 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Globalization;
using System.Xml.Linq;
namespace HC_APTBS.Models
{
/// <summary>
/// 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>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>
/// </summary>
public class CanBusParameter
{
// ── Frame addressing ─────────────────────────────────────────────────────
/// <summary>CAN message identifier this parameter belongs to.</summary>
public uint MessageId { get; set; }
/// <summary>Index of the high byte within the 8-byte CAN payload.</summary>
public ushort ByteH { get; set; }
/// <summary>Index of the low byte within the 8-byte CAN payload.</summary>
public ushort ByteL { get; set; }
// ── Metadata ─────────────────────────────────────────────────────────────
/// <summary>Human-readable parameter name used for lookup (see <see cref="BenchParameterNames"/>).</summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// Sensor/encoding type selector. Used by the receive decoder to choose
/// the correct bit-extraction formula (e.g. for temperature and RPM).
/// </summary>
public int Type { get; set; } = 0;
// ── Calibration coefficients (P1P6) ─────────────────────────────────────
/// <summary>Transfer function coefficient P1 (numerator multiplier).</summary>
public double P1 { get; set; }
/// <summary>Transfer function coefficient P2 (numerator offset).</summary>
public double P2 { get; set; }
/// <summary>Transfer function coefficient P3 (denominator multiplier).</summary>
public double P3 { get; set; }
/// <summary>Transfer function coefficient P4 (denominator offset).</summary>
public double P4 { get; set; }
/// <summary>Transfer function coefficient P5 (additive offset 1).</summary>
public double P5 { get; set; }
/// <summary>Transfer function coefficient P6 (additive offset 2).</summary>
public double P6 { get; set; }
/// <summary>
/// When true, the calibration coefficients are set to the identity transform
/// (P1=1, P4=1, all others 0) so the raw value passes through unchanged.
/// </summary>
public bool DisableCalibration { get; set; }
// ── Runtime state ─────────────────────────────────────────────────────────
/// <summary>Current decoded engineering-unit value (updated by the CAN read thread).</summary>
public double Value { get; set; }
/// <summary>
/// True when <see cref="Value"/> has been updated since the last UI refresh tick.
/// Reset to false by the consumer after reading.
/// </summary>
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"/>.
/// </summary>
public bool IsReceive { get; set; }
/// <summary>
/// Exponential moving average coefficient α ∈ (0, 1].
/// α = 1 means no smoothing (pass-through); smaller values give more smoothing.
/// </summary>
public double Alpha { get; set; } = 1.0;
// ── Convenience alias kept for cross-file compatibility ───────────────────
/// <summary>Alias for <see cref="MessageId"/> — used by legacy call sites.</summary>
public uint ID
{
get => MessageId;
set => MessageId = value;
}
// ── Transfer function ─────────────────────────────────────────────────────
/// <summary>
/// Applies the calibration transfer function to <see cref="Value"/>.
/// </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.
/// </summary>
public double GetTransmitValue() => GetTransformResult();
/// <summary>
/// Resets calibration coefficients to the identity transform so the raw value
/// passes through <see cref="GetTransformResult"/> unchanged.
/// </summary>
public void SetIdentityCalibration()
{
P1 = 1; P2 = 0; P3 = 0; P4 = 1; P5 = 0; P6 = 0;
DisableCalibration = true;
}
// ── XML serialisation ─────────────────────────────────────────────────────
/// <summary>
/// Deserialises a pump CAN parameter from an XML element inside the
/// <c>&lt;Params&gt;</c> section of a pump definition.
/// </summary>
public static CanBusParameter FromXml(XElement xe)
{
var p = new CanBusParameter
{
Name = xe.Name.LocalName,
MessageId = uint.Parse(xe.Attribute("busid")?.Value ?? "0",
NumberStyles.HexNumber),
ByteH = ushort.Parse(xe.Attribute("byteh")?.Value ?? "0"),
ByteL = ushort.Parse(xe.Attribute("bytel")?.Value ?? "0"),
Type = int.Parse(xe.Attribute("type")?.Value ?? "0"),
IsReceive = !string.Equals(xe.Attribute("send")?.Value, "true",
StringComparison.OrdinalIgnoreCase),
Alpha = ParseDecimal(xe.Attribute("filter")?.Value, 1.0),
DisableCalibration = string.Equals(xe.Attribute("disableparams")?.Value,
"true", StringComparison.OrdinalIgnoreCase)
};
if (p.DisableCalibration)
{
p.SetIdentityCalibration();
}
else
{
p.P1 = ParseDecimal(xe.Attribute("p1")?.Value, 1.0);
p.P2 = ParseDecimal(xe.Attribute("p2")?.Value, 0.0);
p.P3 = ParseDecimal(xe.Attribute("p3")?.Value, 0.0);
p.P4 = ParseDecimal(xe.Attribute("p4")?.Value, 1.0);
p.P5 = ParseDecimal(xe.Attribute("p5")?.Value, 0.0);
p.P6 = ParseDecimal(xe.Attribute("p6")?.Value, 0.0);
}
return p;
}
/// <summary>Parses a decimal string that may use comma or dot as separator.</summary>
private static double ParseDecimal(string? value, double fallback)
{
if (string.IsNullOrEmpty(value)) return fallback;
return double.Parse(value.Replace(',', '.'), CultureInfo.InvariantCulture);
}
/// <summary>Serialises this parameter to an XML element for persistence in bench.xml.</summary>
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));
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));
}
return elm;
}
}
// ── Well-known parameter name constants ──────────────────────────────────────
/// <summary>String constants for bench CAN parameter names.</summary>
public static class BenchParameterNames
{
public const string Rpm = "RPM";
public const string Counter = "Counter";
public const string BaudRate = "BaudRate";
public const string BenchRpm = "BenchRPM";
public const string BenchCounter = "BenchCounter";
public const string Temp = "BenchTemp";
public const string TempIn = "T-in";
public const string TempOut = "T-out";
public const string Temp4 = "T4";
public const string QDelivery = "QDelivery";
public const string QOver = "QOver";
public const string ElectronicMsg = "ElectronicMsg";
public const string EncoderResolution = "EncoderResolution";
public const string PsgEncoderValue = "PSGEncoderValue";
public const string PsgEncoderWorking = "PSGEncoderWorking";
public const string InjEncoderValue = "InyectorEncoderValue";
public const string InjEncoderWorking = "InyectorEncoderWorking";
public const string ManualEncoderValue = "ManualEncoderValue";
public const string Alarms = "Alarms";
public const string Pressure = "Pressure";
public const string AnalogSensor2 = "AnalogicSensor2";
}
/// <summary>String constants for pump CAN parameter names.</summary>
public static class PumpParameterNames
{
public const string MemoryRequest = "MemoryRequest";
public const string Me = "me";
public const string Fbkw = "FBKW";
public const string PreIn = "mepi";
public const string Rpm = "RPM";
public const string Temp = "Temp";
public const string Tein = "Tein";
public const string TestUnlock = "TestUnlock";
public const string TestImmo = "TestImmo";
public const string Status = "Status";
public const string Empf3 = "Empf3";
}
/// <summary>String constants for K-Line / KWP data dictionary keys.</summary>
public static class KlineKeys
{
public const string PumpId = "pumpID";
public const string SerialNumber = "SerialNumber";
public const string ModelReference = "ModelReference";
public const string PumpControl = "PumpControl";
public const string ModelIndex = "ModelIndex";
public const string DataRecord = "DataRecord";
public const string SwVersion1 = "SWV1";
public const string SwVersion2 = "SWV2";
public const string Dfi = "dfi";
public const string Errors = "ErrorCodes";
public const string Result = "result";
public const string NoErrors = "No fault codes";
public const string ConnectError = "CON ERROR";
}
/// <summary>Relay name constants — mirror the bench XML definition.</summary>
public static class RelayNames
{
public const string Electronic = "Electronic";
public const string OilPump = "OilPump";
public const string DepositCooler = "DepositCooler";
public const string DepositHeater = "DepositHeater";
public const string DirectionRight = "Reserve";
public const string Counter = "Counter";
public const string DirectionLeft = "Direction";
public const string TinCooler = "TinCooler";
public const string Pulse4Signal = "Pulse4Signal";
public const string Flasher = "Flasher";
}
}