using System; using System.Globalization; using System.Xml.Linq; 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 . /// /// Bench params use the simple calibration model:
/// Linear: eng = raw * Factor + Offset
/// Inverse: eng = Factor / raw + Offset
/// /// Pump params (legacy) use the P1–P6 rational transfer function via /// . Set to enable. ///
public class CanBusParameter { // ── Frame addressing ───────────────────────────────────────────────────── /// CAN message identifier this parameter belongs to. public uint MessageId { get; set; } /// Index of the high byte within the 8-byte CAN payload. public ushort ByteH { get; set; } /// Index of the low byte within the 8-byte CAN payload. public ushort ByteL { get; set; } // ── Metadata ───────────────────────────────────────────────────────────── /// Human-readable parameter name used for lookup (see ). public string Name { get; set; } = string.Empty; /// /// Sensor/encoding type selector. Used by the receive decoder to choose /// the correct bit-extraction formula (e.g. for temperature and RPM). /// public int Type { get; set; } = 0; // ── Simple calibration (bench params) ──────────────────────────────────── /// Multiplication factor: eng = raw * Factor + Offset. public double Factor { get; set; } = 1.0; /// Additive offset: eng = raw * Factor + Offset. public double Offset { get; set; } /// When true, calibration uses eng = Factor / raw + Offset. public bool IsInverse { get; set; } // ── Legacy P1–P6 calibration (pump params) ─────────────────────────────── /// Transfer function coefficient P1 (numerator multiplier). public double P1 { get; set; } /// Transfer function coefficient P2 (numerator offset). public double P2 { get; set; } /// Transfer function coefficient P3 (denominator multiplier). public double P3 { get; set; } /// Transfer function coefficient P4 (denominator offset). public double P4 { get; set; } /// Transfer function coefficient P5 (additive offset 1). public double P5 { get; set; } /// Transfer function coefficient P6 (additive offset 2). public double P6 { get; set; } /// /// 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. /// public bool DisableCalibration { get; set; } /// /// When true, is used instead of /// . Enabled for pump params loaded via . /// public bool UseLegacyTransform { get; set; } // ── Runtime state ───────────────────────────────────────────────────────── /// Current decoded engineering-unit value (updated by the CAN read thread). public double Value { get; set; } /// /// True when has been updated since the last UI refresh tick. /// Reset to false by the consumer after reading. /// public bool NeedsUpdate { get; set; } /// /// True for receive-direction params (decoded from incoming CAN frames). /// False for transmit-direction params (packed into outgoing frames). /// public bool IsReceive { get; set; } /// /// Exponential moving average coefficient α ∈ (0, 1]. /// α = 1 means no smoothing (pass-through); smaller values give more smoothing. /// public double Alpha { get; set; } = 1.0; // ── Convenience alias kept for cross-file compatibility ─────────────────── /// Alias for — used by legacy call sites. public uint ID { get => MessageId; set => MessageId = value; } // ── Simple calibration ──────────────────────────────────────────────────── /// /// Converts a raw CAN value to engineering units using Factor/Offset. /// public double Calibrate(double raw) { if (IsInverse) return raw != 0 ? Factor / raw + Offset : 0; return raw * Factor + Offset; } /// /// Converts an engineering value back to raw CAN value (for transmit). /// 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) ───────────────────────── /// /// Applies the P1–P6 rational transfer function to . /// Used only by pump params ( = true). /// public double GetTransformResult() { if (IsReceive) { return (-P2 - P3 * P5 - P4 * P6 + P4 * Value) / (P1 + P3 * P5 + P3 * P6 - P3 * Value); } return ((P1 * Value + P2) / (P3 * Value + P4)) + P5 + P6; } /// /// Returns the raw CAN value for transmission. /// Delegates to simple or legacy calibration depending on param type. /// public double GetTransmitValue() { if (UseLegacyTransform) return GetTransformResult(); return CalibrateReverse(Value); } /// /// Resets calibration coefficients to the identity transform so the raw value /// passes through unchanged. /// public void SetIdentityCalibration() { P1 = 1; P2 = 0; P3 = 0; P4 = 1; P5 = 0; P6 = 0; DisableCalibration = true; } // ── XML serialisation ───────────────────────────────────────────────────── /// /// Deserialises a pump CAN parameter from an XML element inside the /// <Params> section of a pump definition. /// 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), UseLegacyTransform = true }; 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; } /// Parses a decimal string that may use comma or dot as separator. internal static double ParseDecimal(string? value, double fallback) { if (string.IsNullOrEmpty(value)) return fallback; return double.Parse(value.Replace(',', '.'), CultureInfo.InvariantCulture); } /// /// Serialises this parameter to an XML element using the pump-param format /// (busid, p1–p6, send, disableparams). /// Used when persisting pump definitions to pumps.xml. /// public XElement ToPumpXml() { var elm = new XElement(Name, new XAttribute("busid", MessageId.ToString("X")), new XAttribute("byteh", ByteH), new XAttribute("bytel", ByteL), new XAttribute("type", Type)); if (!IsReceive) elm.Add(new XAttribute("send", "true")); if (Alpha != 1.0) elm.Add(new XAttribute("filter", Alpha.ToString(CultureInfo.InvariantCulture))); if (DisableCalibration) { elm.Add(new XAttribute("disableparams", "true")); } else { elm.Add(new XAttribute("p1", P1.ToString(CultureInfo.InvariantCulture))); elm.Add(new XAttribute("p2", P2.ToString(CultureInfo.InvariantCulture))); elm.Add(new XAttribute("p3", P3.ToString(CultureInfo.InvariantCulture))); elm.Add(new XAttribute("p4", P4.ToString(CultureInfo.InvariantCulture))); elm.Add(new XAttribute("p5", P5.ToString(CultureInfo.InvariantCulture))); elm.Add(new XAttribute("p6", P6.ToString(CultureInfo.InvariantCulture))); } return elm; } /// Serialises this parameter to an XML element for persistence in bench.xml. public XElement ToXml() { var elm = new XElement(Name, new XAttribute("id", MessageId.ToString("X")), new XAttribute("byteh", ByteH), new XAttribute("bytel", ByteL), new XAttribute("direction", IsReceive ? "rx" : "tx")); 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; } } // ── Well-known parameter name constants ────────────────────────────────────── /// String constants for bench CAN parameter names. 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"; } /// String constants for pump CAN parameter names. 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"; } /// String constants for K-Line / KWP data dictionary keys. 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"; } /// Relay name constants — mirror the bench XML definition. 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"; } }