initial commit
This commit is contained in:
283
Models/CanBusParameter.cs
Normal file
283
Models/CanBusParameter.cs
Normal file
@@ -0,0 +1,283 @@
|
||||
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 (P1–P6) ─────────────────────────────────────
|
||||
|
||||
/// <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><Params></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";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user