using DTS.Common.Interface.Sensors; using System; using System.Collections.Generic; using System.Linq; using System.Text; using DTS.Common.Enums; using DTS.Common.Enums.Sensors; using DTS.Common.Utilities.Logging; using System.Globalization; namespace DTS.Common.Classes.Sensors { public class CalibrationRecords : ICalibrationRecords { public ICalibrationRecord[] Records { get; set; } = new CalibrationRecord[] { new CalibrationRecord() }; public CalibrationRecords(ICalibrationRecords copy) { var records = new CalibrationRecord[copy.Records.Length]; for (var i = 0; i < copy.Records.Length; i++) { records[i] = new CalibrationRecord(copy.Records[i]); } Records = records; } public CalibrationRecords() { Records = new CalibrationRecord[] { new CalibrationRecord() }; } public CalibrationRecords(string records) { FromSerializedString(records); } public bool IsEqual(object obj, ISensorCalibration sc) { if (obj is CalibrationRecords r) { if (r.Records.Length != Records.Length) { return false; } for (var i = 0; i < r.Records.Length; i++) { if (!r.Records[i].IsEqual(Records[i], sc)) { return false; } } return true; } return base.Equals(obj); } public void FromSerializedString(string s) { var tokens = s.Split(new string[] { MySeparator }, StringSplitOptions.None); for (var i = 0; i < tokens.Length; i++) { tokens[i] = tokens[i].Replace(MySeparatorBackup, MySeparator); } var records = new List(); foreach (string token in tokens) { records.Add(new CalibrationRecord(token)); } Records = records.ToArray(); } private const string MySeparator = "__x__"; private const string MySeparatorBackup = "___xx___"; public string ToSerializedString(ISensorCalDbRecord sc) { var records = new List(); foreach (var r in Records) { records.Add(r.ToSerializedString(sc)); } for (var i = 0; i < records.Count; i++) { System.Diagnostics.Trace.Assert(!records[i].Contains(MySeparatorBackup)); records[i] = records[i].Replace(MySeparator, MySeparatorBackup); } return string.Join(MySeparator, records.ToArray()); } public string ToDisplayString(ISensorCalibration sc, ISensorCalibration previous, string linearFormat, string nonlinearFormat) { var sb = new StringBuilder(); for (var i = 0; i < Records.Length; i++) { if (i > 0) { sb.AppendLine(); } var r = Records[i]; ICalibrationRecord r2 = null; if (null != previous) { if (i < previous.Records.Records.Length) { r2 = previous.Records.Records[i]; } } var s = r.ToDisplayString(sc, r2, previous, linearFormat, nonlinearFormat); if (!string.IsNullOrEmpty(s)) { sb.Append(s); } } return sb.ToString(); } } public class CalibrationRecord : ICalibrationRecord { public double Sensitivity { get; set; } /// /// ZeroPoint is used to hold the calibration certificate field for 2D/3D IR-TRACC cal certs /// it is used to zero the IR-TRACC and POT data prior to being fed into the 3D equations /// private double _zeroPoint = 0D; public double ZeroPoint { get { if (false == Equals(Poly.CalibrationFactor, 0.0)) { // This field is always calculated. Do not return stored value unless we are unable to calculate return Poly.ZeroPositionIntercept / Poly.CalibrationFactor; } return _zeroPoint; } set => _zeroPoint = value; } public LinearizationFormula Poly { get; set; } public bool AtCapacity { get; set; } = false; public string EngineeringUnits { get; set; } = "g"; public SensorConstants.SensUnits SensitivityUnits { get; set; } = SensorConstants.SensUnits.NONE; public ExcitationVoltageOptions.ExcitationVoltageOption Excitation { get; set; } = ExcitationVoltageOptions.ExcitationVoltageOption.Volt5; public double CapacityOutputIsBasedOn { get; set; } = 1.000; public InitialOffsetTypes InitialOffsetMethod { get; set; } = InitialOffsetTypes.EU; public string ISOCode { get; set; } = String.Empty; private enum Fields { Sensitivity, Poly, AtCapacity, EngineeringUnits, Excitation, CapacityOutputIsBasedOn, SensitivityUnits, ZeroPoint, ISOCode }; public string ToSerializedString(ISensorCalDbRecord parentCal) { var tokens = new List(); var fields = Enum.GetValues(typeof(Fields)).Cast().ToArray(); foreach (var field in fields) { switch (field) { case Fields.AtCapacity: tokens.Add(AtCapacity.ToString()); break; case Fields.EngineeringUnits: tokens.Add(EngineeringUnits); break; case Fields.Excitation: tokens.Add(Excitation.ToString()); break; case Fields.Poly: Poly.MarkValid(parentCal.NonLinear); if (parentCal.LinearAdded) { //We have a mixed-sensitivity sensor. Mark the first CR valid, kill the rest for (var i = 0; i < parentCal.Records.Records.Length; i++) { parentCal.Records.Records[i].Poly.MarkValid(i == 0); } } tokens.Add(Poly.ToSerializeString()); break; case Fields.Sensitivity: tokens.Add(Sensitivity.ToString(System.Globalization.CultureInfo.InvariantCulture)); break; case Fields.CapacityOutputIsBasedOn: tokens.Add(CapacityOutputIsBasedOn.ToString()); break; case Fields.SensitivityUnits: tokens.Add(SensitivityUnits.ToString()); break; case Fields.ZeroPoint: tokens.Add(ZeroPoint.ToString(System.Globalization.CultureInfo.InvariantCulture)); break; case Fields.ISOCode: if (!string.IsNullOrWhiteSpace(ISOCode)) tokens.Add(ISOCode); break; default: throw new NotSupportedException("unknown CalibrationRecord field: " + field.ToString()); } } for (var i = 0; i < tokens.Count; i++) { if (null == tokens[i]) { tokens[i] = ""; } tokens[i] = tokens[i].Replace(System.Globalization.CultureInfo.InvariantCulture.TextInfo.ListSeparator, "x_Separator_x"); } return string.Join(System.Globalization.CultureInfo.InvariantCulture.TextInfo.ListSeparator, tokens.ToArray()); } public string ToDisplayString(ISensorCalibration sc, ICalibrationRecord previous, ISensorCalibration sc2, string linearFormat, string nonlinearFormat) { var sb = new StringBuilder(); var fields = Enum.GetValues(typeof(Fields)).Cast().ToArray(); foreach (var field in fields) { switch (field) { case Fields.AtCapacity: if (null == previous || AtCapacity != previous.AtCapacity) { if (sb.Length > 1) { sb.AppendLine(); } sb.AppendFormat("{0}: {1}", Strings.Strings.SensorFields_AtCapacity, AtCapacity); } break; case Fields.EngineeringUnits: if (null == previous || false == EngineeringUnits.Equals(previous.EngineeringUnits)) { if (sb.Length > 1) { sb.AppendLine(); } sb.AppendFormat("{0}: {1}", Strings.Strings.SensorFields_EngineeringUnits, EngineeringUnits); } break; case Fields.Excitation: if (null == previous || sc.IsProportional != sc2.IsProportional || Excitation != previous.Excitation) { if (sc.IsProportional) { if (sb.Length > 1) { sb.AppendLine(); } sb.AppendFormat("{0}: {1:0.0}", Strings.Strings.SensorFields_Excitation, GetExcitationVoltageMagnitudeFromEnum(Excitation)); } } break; case Fields.Poly: if (null == previous || sc.NonLinear || sc.NonLinear != sc2.NonLinear) { if (sc.NonLinear) { if (sb.Length > 1) { sb.AppendLine(); } sb.AppendFormat("{0}: {1}", Strings.Strings.SensorFields_NonLinearFormat, Poly.ToDisplayString(nonlinearFormat)); } } break; case Fields.Sensitivity: if (null == previous || Sensitivity != previous.Sensitivity) { if (sb.Length > 1) { sb.AppendLine(); } sb.AppendFormat("{0}: {1}", Strings.Strings.SensorFields_Sensitivity, Sensitivity.ToString(linearFormat)); } break; case Fields.CapacityOutputIsBasedOn: if (false == AtCapacity) { if (null == previous || CapacityOutputIsBasedOn != previous.CapacityOutputIsBasedOn) { if (sb.Length > 1) { sb.AppendLine(); } sb.AppendFormat("{0}: {1}", Strings.Strings.SensorFields_CapacityOutputIsBasedOn, CapacityOutputIsBasedOn); } } break; case Fields.SensitivityUnits: if (null == previous || false == SensitivityUnits.Equals(previous.SensitivityUnits)) { if (sb.Length > 1) { sb.AppendLine(); } sb.AppendFormat("{0}: {1}", Strings.Strings.SensorFields_SensitivityUnits, SensitivityUnits); } break; case Fields.ZeroPoint: if (null == previous || ZeroPoint != previous.ZeroPoint) { if (sb.Length > 1) { sb.AppendLine(); } sb.AppendFormat("{0}: {1}", Strings.Strings.SensorFields_ZeroPoint, ZeroPoint); } break; default: break; } } return sb.ToString(); } public bool IsEqual(object obj, ISensorCalibration sc) { if (obj is CalibrationRecord r) { return r.ToSerializedString(sc) == ToSerializedString(sc); } return base.Equals(obj); } public void FromString(string s) { var tokens = s.Split(new string[] { CultureInfo.InvariantCulture.TextInfo.ListSeparator }, StringSplitOptions.None); var fields = Enum.GetValues(typeof(Fields)).Cast().ToArray(); for (var i = 0; i < tokens.Length && i < fields.Length; i++) { var token = tokens[i].Replace("x_Separator_x", CultureInfo.InvariantCulture.TextInfo.ListSeparator); switch (fields[i]) { case Fields.AtCapacity: AtCapacity = Convert.ToBoolean(token); break; case Fields.EngineeringUnits: EngineeringUnits = token; break; case Fields.Excitation: if (Enum.TryParse(token, out ExcitationVoltageOptions.ExcitationVoltageOption excitation)) { Excitation = excitation; } else { APILogger.Log($"failed to parse excitation: {token} from {s}"); } break; case Fields.Poly: Poly = new LinearizationFormula(); Poly.FromSerializeString(token); break; case Fields.Sensitivity: Sensitivity = Convert.ToDouble(token, System.Globalization.CultureInfo.InvariantCulture); break; case Fields.CapacityOutputIsBasedOn: CapacityOutputIsBasedOn = Convert.ToDouble(token); break; case Fields.SensitivityUnits: if (Enum.TryParse(token, out SensorConstants.SensUnits unit)) { SensitivityUnits = unit; } else { APILogger.Log($"failed to parse sensitivity units: {token} from {s}"); } break; case Fields.ZeroPoint: ZeroPoint = Convert.ToDouble(token, System.Globalization.CultureInfo.InvariantCulture); break; case Fields.ISOCode: ISOCode = token; break; default: throw new NotSupportedException("unknown CalibrationRecord field: " + fields.ToArray()); } } } public CalibrationRecord(string s) { FromString(s); } public CalibrationRecord(ICalibrationRecord copy) { var fields = Enum.GetValues(typeof(Fields)).Cast().ToArray(); foreach (var field in fields) { switch (field) { case Fields.AtCapacity: AtCapacity = copy.AtCapacity; break; case Fields.EngineeringUnits: EngineeringUnits = copy.EngineeringUnits; break; case Fields.Excitation: Excitation = copy.Excitation; break; case Fields.Poly: Poly = new LinearizationFormula(copy.Poly); break; case Fields.Sensitivity: Sensitivity = copy.Sensitivity; break; case Fields.CapacityOutputIsBasedOn: CapacityOutputIsBasedOn = copy.CapacityOutputIsBasedOn; break; case Fields.SensitivityUnits: SensitivityUnits = copy.SensitivityUnits; break; case Fields.ZeroPoint: ZeroPoint = copy.ZeroPoint; break; case Fields.ISOCode: ISOCode = copy.ISOCode; break; default: throw new NotSupportedException("unknown calibrationrecord field: " + field.ToString()); } } } public CalibrationRecord() { Poly = new LinearizationFormula(); } //helpers moved from DAS.Concepts public static double GetExcitationVoltageMagnitudeFromEnum(ExcitationVoltageOptions.ExcitationVoltageOption target) { try { return new ExcitationVoltageOptions.VoltageMagnitudeAttributeCoder().DecodeAttributeValue(target); } catch (Exception ex) { throw new Exception("encountered problem attempting to get excitation voltage magnitude from enum", ex); } } public static ExcitationVoltageOptions.ExcitationVoltageOption GetExcitationVoltageEnumFromMagnitude(double magnitude) { try { return new ExcitationVoltageOptions.VoltageMagnitudeAttributeCoder().EncodeAttributeValue(magnitude); } catch (Exception ex) { APILogger.Log("encountered problem attempting to get excitation voltage enum from magnitude", ex); return ExcitationVoltageOptions.ExcitationVoltageOption.Undefined; } } } }