using DTS.Common.Classes; using DTS.Common.Classes.Groups; using DTS.Common.Classes.Sensors; using DTS.Common.DAS.Concepts; using DTS.Common.Enums; using DTS.Common.Enums.Sensors; using DTS.SensorDB; using EQX; using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Xml.Serialization; using static DTS.Common.Enums.Sensors.SensorConstants; namespace EquipmentExchange { /// /// class that handles reading an EQX file and translating into DTS SensorData and SensorCalibration objects /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Code Smell", "S101:Types should be named in PascalCase", Justification = "EQX is an acronym")] public class EQXSensorDatabase { public bool UsemVOverVForPolys { get; set; } = true; private readonly Dictionary> _calibrationLookup = new Dictionary>(); private bool _importCreateDynamicGroups; /// /// sensor lookup by UUID (EQX id) or serial number /// private readonly Dictionary _sensorLookup = new Dictionary(); /// /// whether the sensor has a null id section in EQX, this is used in DataPRO to determine whether to /// use an existing id in the db or to overwrite; from original function: /// check to see if the sensor had a NULL IDModuleString, if so /// check for an existing EID and use that rather than wiping out the EID /// 18467 Missing IDModuleString in e2x file import is clearing the EID on sensor import /// private readonly Dictionary _sensorHasNullIDModule = new Dictionary(); /// /// returns true if the sensor has a NULL IDModuleString entry record in the EQX file /// used to determine whether existing EID should be used or not /// 18467 Missing IDModuleString in e2x file import is clearing the EID on sensor import /// this method just returns whether we know if it has a value or not, not the value /// /// UUID or serial number of sensor /// true if the sensor has a null id module section, false otherwise public bool SensorHasNullIDModule(string key) { return _sensorHasNullIDModule.ContainsKey(key); } /// /// returns the value for whether a sensor has a null IDModuleString entry in the eqx /// used to determine whether to update the EID in DataPRO or not /// 18467 Missing IDModuleString in e2x file import is clearing the EID on sensor import /// /// /// public bool GetSensorNullIdModuleValue(string key) { return _sensorHasNullIDModule[key]; } /// /// returns all sensor records read in from Read method /// /// /// all sensors read in by Read public SensorData[] GetSensors() { return _sensorLookup.Values.ToArray(); } public SensorImportData GetImportData() { return new SensorImportData { GroupNameTestObjectLookup = _groupNameToTestObjectLookup, GroupNameSensorListLookup = _groupNameSensorListLookup }; } /// /// whether there is calibration information in eqx file for a given sensor /// /// UUID or serial number of sensor /// true if there are calibration entries, false otherwise public bool ContainsCalibration(string key) { return _calibrationLookup.ContainsKey(key); } /// /// returns all calibrations for a given sensor /// /// UUID or serial number for sensor /// public List GetCalibrations(string id) { if (_calibrationLookup.ContainsKey(id)) { return _calibrationLookup[id]; } return new List(); } /// /// action to perform to report an error with file /// /// public delegate void ReportErrorsDelegate(List errors); /// /// reads a EQX file and populates sensor and calibration entries /// /// /// /// /// public void Read(string path, ReportErrorsDelegate reportErrors, bool eqxUseSerialNumberFieldForSN, bool useZeroForUnfiltered, bool importCreateDynamicGroups) { _importCreateDynamicGroups = importCreateDynamicGroups; var serializer = new XmlSerializer(typeof(EqxSensors)); EqxSensors sensors = null; using (var tr = new StreamReader(path)) { try { sensors = (EqxSensors)serializer.Deserialize(tr); } catch (Exception ex) { reportErrors?.Invoke(new List(new[] { ex.Message })); return; } var errors = new List(); if (sensors?.Sensor != null) { foreach (var axis in sensors.Sensor) { AddEQXSensor(null, axis, eqxUseSerialNumberFieldForSN, useZeroForUnfiltered, ref errors); } } if (sensors?.SensorGroup != null) { foreach (var group in sensors.SensorGroup) { AddEQXGroup(group, eqxUseSerialNumberFieldForSN, useZeroForUnfiltered, ref errors); } } if ( null != errors && errors.Any()) { reportErrors?.Invoke(errors); } tr.Close(); } } private bool PopulateSensorLookup(string groupName, ref SensorData sd, ref List errors, ref Dictionary> groupNameSensorListLookup, ref Dictionary groupNameToTestObjectLookup) { var sensorTestObject = sd.TestObject; // check group name for empty if (string.IsNullOrEmpty(groupName) || string.IsNullOrWhiteSpace(groupName)) { errors.Add(string.Format("No Group Name found for sensor {0}", sd.SerialNumber)); return false; } if (!groupNameSensorListLookup.ContainsKey(groupName)) { groupNameSensorListLookup.Add(groupName, new List()); } groupNameSensorListLookup[groupName].Add(sd.SerialNumber); if (!groupNameToTestObjectLookup.ContainsKey(groupName)) { groupNameToTestObjectLookup.Add(groupName, sensorTestObject); } else { if (groupNameToTestObjectLookup[groupName] != sensorTestObject && !_importCreateDynamicGroups) { var errorMessage = string.Format("Non distinct test object for group {0}", groupName); if (!errors.Contains(errorMessage)) { errors.Add(errorMessage); } } } return errors.Count == 0; } /// /// pads the EID if needed /// 30609 EIDs can need correction when coming from crashdesigner EQX /// pads and adds checksum if less than 16 characters, otherwise returns as is /// does not pad null or empty eids. /// on exception returns the id as is /// /// /// private static string PadEIDIfNeeded(string idModuleString) { try { if (string.IsNullOrEmpty(idModuleString)) { return idModuleString; } if (idModuleString.Length < 16) { idModuleString = idModuleString.PadLeft(14, '0'); return $"{GenerateCRC(idModuleString)}{idModuleString}"; } } catch (Exception) { //consume error, no logging } return idModuleString; } /// /// code borrowed from nate for /// 30609 EIDs can need correction when coming from crashdesigner EQX /// just copied it here as is /// /// /// private static string GenerateCRC(string ShortID) { var int64 = long.Parse(ShortID, NumberStyles.HexNumber); var bytes = BitConverter.GetBytes(int64); var bitArray = new System.Collections.BitArray(bytes); var CRCbit = new System.Collections.BitArray(8); var CRCTemp = new System.Collections.BitArray(1); var CRCbyte = new byte[1]; for (var i = 0; i < 56; i++) { CRCTemp[0] = CRCbit[0] ^ bitArray[i]; CRCbit[0] = CRCbit[1]; CRCbit[1] = CRCbit[2]; CRCbit[2] = CRCbit[3] ^ CRCTemp[0]; CRCbit[3] = CRCbit[4] ^ CRCTemp[0]; CRCbit[4] = CRCbit[5]; CRCbit[5] = CRCbit[6]; CRCbit[6] = CRCbit[7]; CRCbit[7] = CRCTemp[0]; } CRCbit.CopyTo(CRCbyte, 0); return BitConverter.ToString(CRCbyte); ; } private SensorCalibration GetCalibrationFromEQXSensorAxis(EqxSensorAxis sensor, SensorData sd) { var sc = new SensorCalibration(); if (null != sd) { sc.SerialNumber = sd.SerialNumber; } sc.ModifyDate = DateTime.Now; if (sensor.ElectricalMethodSpecified) { switch (sensor.ElectricalMethod) { case EqxElectricalMethod.HalfBridgeActive: case EqxElectricalMethod.QuarterBridge: case EqxElectricalMethod.QuarterBridgeActive: case EqxElectricalMethod.ActiveSensor: sc.IsProportional = false; break; default: sc.IsProportional = true; break; } } if (sensor.CalDateSpecified) { sc.CalibrationDate = sensor.CalDate; } sc.Username = sensor.CalPerson; if (sensor.ScalingMethodSpecified) { switch (sensor.ScalingMethod) { case EqxScalingMethod.CubicPolynomial: { sc.NonLinear = true; sc.Records.Records[0].Poly.NonLinearStyle = NonLinearStyles.Polynomial; var a = Convert.ToDouble(sensor.Sensitivity2.ToString("F10", CultureInfo.InvariantCulture), CultureInfo.InvariantCulture); var b = Convert.ToDouble(sensor.Sensitivity3.ToString("F10", CultureInfo.InvariantCulture), CultureInfo.InvariantCulture); var c = Convert.ToDouble(sensor.Sensitivity4.ToString("F10", CultureInfo.InvariantCulture), CultureInfo.InvariantCulture); var m = Convert.ToDouble(sensor.Sensitivity5.ToString("F10", CultureInfo.InvariantCulture), CultureInfo.InvariantCulture); sc.Records.Records[0].Poly.UsemVOverVForPolys = UsemVOverVForPolys; sc.Records.Records[0].Poly.PolynomialCoefficients = new[] { a, b, c, m }; sc.Records.Records[0].Poly.PolynomialExponents = new[] { 3D, 2D, 1D, 0D }; sc.Records.Records[0].Poly.MarkValid(true); } break; case EqxScalingMethod.IRTRACC: { ReadEQXCalibration(sc, sensor); } break; default: sc.Records.Records[0].Sensitivity = Convert.ToDouble(sensor.Sensitivity.ToString("F10", CultureInfo.InvariantCulture), CultureInfo.InvariantCulture); break; } } if (sensor.OffsetCompensationSpecified) { sc.RemoveOffset = sensor.OffsetCompensation; } if (null != sd && sd.SupportedExcitation.Length > 0) { sc.Records.Records[0].Excitation = sd.SupportedExcitation[0]; } sc.Records.Records[0].AtCapacity = false; sc.Records.Records[0].SensitivityUnits = sc.IsProportional ? SensUnits.mVperVperEU : SensUnits.mVperEU; sc.CalVersion = sensor.CalVersion; if (sensor.SWOffsetFixValueSpecified) { sc.InitialOffsets.Offsets[0].Form = InitialOffsetTypes.EU; sc.InitialOffsets.Offsets[0].EU = sensor.SWOffsetFixValue; } foreach (var r in sc.Records.Records) { r.EngineeringUnits = sensor.PhysicalUnit; } return sc; } /// /// process an EQX sensor, creating a sensor if necessary /// /// /// /// private void AddEQXSensor(string groupName, EqxSensorAxis sensor, bool eqxUseSerialNumberFieldForSN, bool useZeroForUnfiltered, ref List errors) { var sd = MakeDefaultSensor(); if (eqxUseSerialNumberFieldForSN) { if (null == sensor.SerialNumber) { sensor.SerialNumber = ""; } sd.SerialNumber = sensor.SerialNumber.Replace("\n", ""); sd.Comment = sensor.Name; } else { sd.SerialNumber = sensor.Name.Replace("\n", ""); sd.UserSerialNumber = sensor.SerialNumber; } if (sensor.UUID == null) { sensor.UUID = Guid.NewGuid().ToString(); } sensor.UUID = (eqxUseSerialNumberFieldForSN ? sensor.SerialNumber : sensor.UUID) ?? sensor.SerialNumber; var zeroMethodStart = double.NaN; var zeroMethodEnd = double.NaN; var zeroMethodType = ZeroMethodType.AverageOverTime; var typeSpecified = false; foreach (var enumValue in Enum.GetValues(typeof(SensorData.ExchangeFields))) { var field = (SensorData.ExchangeFields)enumValue; switch (field) { case SensorData.ExchangeFields.BridgeResistance: sd.BridgeResistance = sensor.ShuntResistance; break; case SensorData.ExchangeFields.OffsetToleranceHigh: if (sensor.OffsetTolSpecified) { sd.OffsetToleranceHigh = sensor.OffsetTol; } break; case SensorData.ExchangeFields.OffsetToleranceLow: if (sensor.OffsetTolSpecified) { sd.OffsetToleranceLow = -1D * sensor.OffsetTol; } break; case SensorData.ExchangeFields.BridgeType: if (sd.Bridge != BridgeType.SQUIB) { switch (sensor.ElectricalMethod) { case EqxElectricalMethod.PiezoInput: sd.Bridge = SensorConstants.BridgeType.IEPE; break; case EqxElectricalMethod.HalfBridge: sd.Bridge = SensorConstants.BridgeType.HalfBridge; break; case EqxElectricalMethod.HalfBridgeActive: sd.Bridge = SensorConstants.BridgeType.HalfBridge; break; case EqxElectricalMethod.QuarterBridge: sd.Bridge = SensorConstants.BridgeType.QuarterBridge; break; case EqxElectricalMethod.QuarterBridgeActive: sd.Bridge = SensorConstants.BridgeType.QuarterBridge; break; default: sd.Bridge = SensorConstants.BridgeType.FullBridge; break; } } break; case SensorData.ExchangeFields.CalibrationDueDate: //FB 24428 Check if cal period is null, empty xml element if (sensor.CalPeriodSpecified && sensor.CalPeriod.HasValue && sensor.CalPeriod.Value > 0) { sd.CalInterval = sensor.CalPeriod.Value; } else { sensor.CalPeriod = 365; } break; case SensorData.ExchangeFields.Capacity: sd.Capacity = Math.Max(sensor.MaxRange, sensor.PreferredRange); sd.RangeHigh = sensor.MaxRange; sd.RangeLow = sensor.MinRange; sd.RangeMedium = sensor.PreferredRange; break; case SensorData.ExchangeFields.CheckOffset: sd.CheckOffset = sensor.OffsetCheck; break; case SensorData.ExchangeFields.Comment: if (!string.IsNullOrEmpty(sensor.Remark)) { sd.Comment = sensor.Remark.Substring(0, Math.Min(sensor.Remark.Length, 40)); } else if (!string.IsNullOrEmpty(sensor.CompanyLongname)) { sd.Comment = sensor.CompanyLongname; } sd.Comment = sd.Comment.Replace("\n", ""); break; case SensorData.ExchangeFields.ExcitationVoltage: double maxExc = Math.Max(sensor.ExcitationVoltage, sensor.SensitivityVoltage); if (maxExc == 2D) { sd.SupportedExcitation = new[] { ExcitationVoltageOptions.ExcitationVoltageOption.Volt2 }; } else if (maxExc == 2.5D) { sd.SupportedExcitation = new[] { ExcitationVoltageOptions.ExcitationVoltageOption.Volt2_5 }; } else if (maxExc == 3D) { sd.SupportedExcitation = new[] { ExcitationVoltageOptions.ExcitationVoltageOption.Volt3 }; } else if (maxExc == 5) { sd.SupportedExcitation = new[] { ExcitationVoltageOptions.ExcitationVoltageOption.Volt5 }; } else if (maxExc == 10) { sd.SupportedExcitation = new[] { ExcitationVoltageOptions.ExcitationVoltageOption.Volt10 }; } else { sd.SupportedExcitation = new[] { ExcitationVoltageOptions.ExcitationVoltageOption.Undefined }; } break; case SensorData.ExchangeFields.FilterClass: var fc = new FilterClass(FilterClassType.None); if (sensor.SWFilterClassTypeSpecified) { switch (sensor.SWFilterClassType) { case EqxFilterClassType.CFC10: fc = new FilterClass(FilterClassType.CFC10); break; case EqxFilterClassType.CFC60: fc = new FilterClass(FilterClassType.CFC60); break; case EqxFilterClassType.CFC180: fc = new FilterClass(FilterClassType.CFC180); break; case EqxFilterClassType.CFC600: fc = new FilterClass(FilterClassType.CFC600); break; case EqxFilterClassType.CFC1000: fc = new FilterClass(FilterClassType.CFC1000); break; case EqxFilterClassType.AdHoc: case EqxFilterClassType.None: fc = new FilterClass(FilterClassType.None); break; } } sd.SetFilterAndFilterClassISO(fc, useZeroForUnfiltered, false); break; case SensorData.ExchangeFields.Id: sd.EID = PadEIDIfNeeded(sensor.IDModuleString); //there could be multiple entries in the eqx for a single sensor ... because of that //right now just say if any of the entries has an IDModuleString, then the sensor has an IDModuleString //so if we find a string in xml we set it to false straight out //if we didn't find a string with this entry, but there's already a marking in the dictionary, then there's no point //in updating the dictionary (if it's false it's overriding, if it's true it's the same value we would put in) //finally if we didn't find a string and it's not in the dictionary, go ahead and set it to true if (null == sensor.IDModuleString) { if (!_sensorHasNullIDModule.ContainsKey(sensor.UUID)) { _sensorHasNullIDModule[sensor.UUID] = true; } } else { _sensorHasNullIDModule[sensor.UUID] = false; } break; case SensorData.ExchangeFields.Invert: sd.Invert = sensor.MountingPolarity == EqxPolarity.Negative; break; case SensorData.ExchangeFields.IsoCode: sd.ISOCode = sensor.LocationCode; if (sd.ISOCode?.Length == 16) { sd.FilterClassIso = sd.ISOCode?[15].ToString(); } break; case SensorData.ExchangeFields.IsoLongName: sd.ISOChannelName = sensor.LocationLongname; break; case SensorData.ExchangeFields.Manufacturer: sd.Manufacturer = sensor.Supplier; break; case SensorData.ExchangeFields.MeasurementUnit: if (string.IsNullOrWhiteSpace(sensor.PhysicalUnit)) { sensor.PhysicalUnit = DTS.Common.SharedResource.Strings.StringResources.Units_EU; errors.Add(string.Format(DTS.Common.SharedResource.Strings.StringResources.EQX_Warning_PhysicalUnitEmptyUsingEU, sd.SerialNumber)); } sd.DisplayUnit = sensor.PhysicalUnit; foreach (var r in sd.Calibration.Records.Records) { r.EngineeringUnits = sensor.PhysicalUnit; } break; case SensorData.ExchangeFields.Model: sd.Model = sensor.Model; break; case SensorData.ExchangeFields.SerialNumber: if (eqxUseSerialNumberFieldForSN) { if (!string.IsNullOrEmpty(sensor.SerialNumber)) { sd.SerialNumber = sensor.SerialNumber.Replace("\n", ""); } } else { sd.UserSerialNumber = sensor.SerialNumber; } break; case SensorData.ExchangeFields.Shunt: var shuntMode = ShuntMode.Emulation; if ((sensor.ShuntCheckPosSpecified && sensor.ShuntCheckPos == false) || (sensor.ShuntResistanceSpecified && sensor.ShuntResistance <= 0)) { shuntMode = ShuntMode.None; } sd.Shunt = shuntMode; break; case SensorData.ExchangeFields.Status: break; case SensorData.ExchangeFields.UserSerialNumber: sd.UserSerialNumber = sensor.SerialNumber; sd.UserSerialNumber = sd.UserSerialNumber?.Replace("\n", ""); break; case SensorData.ExchangeFields.UserValue1: sd.UserValue1 = sensor.CompanyCode; sd.UserCode = sensor.CompanyCode; break; case SensorData.ExchangeFields.UserValue2: sd.UserValue2 = sensor.CompanyLongname; sd.UserChannelName = sensor.CompanyLongname; break; case SensorData.ExchangeFields.UserValue3: case SensorData.ExchangeFields.OffsetMethod: case SensorData.ExchangeFields.CalZMO: break; case SensorData.ExchangeFields.FiringDelay: if (sensor.FiringDelaySpecified) { sd.SquibFireDelayMS = sensor.FiringDelay; } break; case SensorData.ExchangeFields.FiringDuration: if (sensor.FiringDurationSpecified) { sd.SquibFireDurationMS = sensor.FiringDuration / 1000D; } break; case SensorData.ExchangeFields.FireMode: if (sensor.FiringModeSpecified) { sd.SquibFireMode = GetSquibFireMode(sensor.FiringMode); sd.Bridge = BridgeType.SQUIB; } break; case SensorData.ExchangeFields.CurrentLimit: if (sensor.FiringCurrentLimitSpecified) { sd.SquibOutputCurrent = sensor.FiringCurrentLimit; } break; case SensorData.ExchangeFields.InputMode: if (sensor.InputModeSpecified) { sd.InputMode = GetInputMode(sensor.InputMode); sd.Bridge = BridgeType.DigitalInput; } break; case SensorData.ExchangeFields.SWOffsetCalculationEndSec: if (sensor.SWOffsetCalculationEndSecSpecified) { zeroMethodEnd = sensor.SWOffsetCalculationEndSec; } break; case SensorData.ExchangeFields.SWOffsetCalculationStartSec: if (sensor.SWOffsetCalculationStartSecSpecified) { zeroMethodStart = sensor.SWOffsetCalculationStartSec; } break; case SensorData.ExchangeFields.SWOffsetCompensationType: if (sensor.SWOffsetCompensationTypeSpecified) { zeroMethodType = GetZeroMethodType(sensor.SWOffsetCompensationType); typeSpecified = true; } break; } } if (string.IsNullOrEmpty(sd.SerialNumber)) { sd.SerialNumber = sd.Comment; } if (string.IsNullOrEmpty(sd.ISOCode)) { sd.ISOCode = new IsoCode(string.Empty).StringRepresentation; } if (string.IsNullOrEmpty(sd.Manufacturer) && !string.IsNullOrEmpty(sd.Model)) { sd.Manufacturer = string.Empty; } sd.UUID = sensor.UUID; //FB 42967 Assign fire delay if (sensor.FiringDelaySpecified) { sd.SquibFireDelayMS = sensor.FiringDelay; } //FB 42967 Assign fire duration and convert to ms if (sensor.FiringDurationSpecified) { sd.SquibFireDurationMS = sensor.FiringDuration / 1000.0; } var sc = GetCalibrationFromEQXSensorAxis(sensor, sd); if (typeSpecified) { sc.ZeroMethods.Methods[0].Method = zeroMethodType; } if (!double.IsNaN(zeroMethodStart)) { sc.ZeroMethods.Methods[0].Start = zeroMethodStart; } if (!double.IsNaN(zeroMethodEnd)) { sc.ZeroMethods.Methods[0].End = zeroMethodEnd; } if (!_sensorLookup.ContainsKey(sd.UUID)) { _sensorLookup.Add(sd.UUID, sd); _calibrationLookup.Add(sd.UUID, new List()); if (sc.CalibrationDate.Year < 1970 || sc.CalibrationDate.Year > DateTime.Now.Year) { } else { _calibrationLookup[sd.UUID].Add(sc); } } else { _sensorLookup[sd.UUID] = CombineSensors(sd, _sensorLookup[sd.UUID], useZeroForUnfiltered); CombineCalibrations(sc, sensor.UUID); } if (null != sensor.CalHistory) { foreach (var cal in sensor.CalHistory) { CombineCalibrations(cal, sd.UUID, sensor); } } if (!string.IsNullOrEmpty(groupName)) { _ = PopulateSensorLookup(groupName, ref sd, ref errors, ref _groupNameSensorListLookup, ref _groupNameToTestObjectLookup); } } private void ReadEQXCalibration(SensorCalibration sc, EqxSensorAxis sensor) { sc.NonLinear = true; var sens1 = Convert.ToDouble(sensor.Sensitivity); var exp = Convert.ToDouble(sensor.Sensitivity2); sc.Records.Records[0].Poly.LinearizationExponent = exp; if (!sensor.Sensitivity3Specified) { sc.Records.Records[0].Poly.NonLinearStyle = NonLinearStyles.IRTraccAverageOverTime; sc.Records.Records[0].Poly.MMPerV = sens1; } else { var sens3 = Convert.ToDouble(sensor.Sensitivity3); sc.Records.Records[0].Poly.NonLinearStyle = NonLinearStyles.IRTraccCalFactor; //set the cal factor to 1000/sensitivity1, and set intercept to Sensitivity3/Sensitivity1 sc.Records.Records[0].Poly.CalibrationFactor = 1000 / sens1; sc.Records.Records[0].Poly.ZeroPositionIntercept = sens3 / sens1; } sc.Records.Records[0].Poly.MarkValid(true); } private Dictionary> _groupNameSensorListLookup = new Dictionary>(); private Dictionary _groupNameToTestObjectLookup = new Dictionary(); /// /// combine calibrations if necessary, or add a new entry if needed /// /// /// /// private void CombineCalibrations(SensorCalibration sc1, string sensorUUID) { if (!_calibrationLookup.ContainsKey(sensorUUID)) { _calibrationLookup.Add(sensorUUID, new List()); } for (var i = 0; i < _calibrationLookup[sensorUUID].Count; i++) { var cal = _calibrationLookup[sensorUUID][i]; if (sc1.CalibrationDate.Date == cal.CalibrationDate.Date) { return; // is in the history already ... } } if (sc1.CalibrationDate.Year < 1970 || sc1.CalibrationDate.Year > DateTime.Now.Year) { return; } _calibrationLookup[sensorUUID].Add(sc1); } /// /// looks through existing calibration entries given a EQX entry and updates or combines calibrations as necessary /// /// /// /// private void CombineCalibrations(EqxCalHistoryEntry entry, string sensorUUID, EqxSensorAxis sensor) { if (!_calibrationLookup.ContainsKey(sensorUUID)) { _calibrationLookup[sensorUUID] = new List(); } var dt = DateTime.MinValue; if (entry.CalDateSpecified) { dt = entry.CalDate; } for (var i = 0; i < _calibrationLookup[sensorUUID].Count; i++) { var cal = _calibrationLookup[sensorUUID][i]; if (cal.CalibrationDate.Date != dt.Date) continue; if (cal.CalVersion == entry.CalVersion) { return; //already present } } //invalid date, reject if (dt.Year < 1970 || dt.Year > DateTime.Now.Year) { return; } _calibrationLookup[sensorUUID].Add(CreateSensorCalibration(entry, sensor)); } /// /// processes an EQX group /// /// group to process /// /// private void AddEQXGroup(EqxBaseSensorGroup group, bool eqxUseSerialNumberFieldForSN, bool useZeroForUnfiltered, ref Listerrors) { if (null != group.SensorGroup) { foreach (var subgroup in group.SensorGroup) { AddEQXGroup(subgroup, eqxUseSerialNumberFieldForSN, useZeroForUnfiltered, ref errors); } } if (null == @group.Sensor) return; foreach (var sensor in @group.Sensor) { var eqxSensorGroup = group as EqxSensorGroup; AddEQXSensor(eqxSensorGroup.Name, sensor, eqxUseSerialNumberFieldForSN, useZeroForUnfiltered, ref errors); } } /// /// creates a sensor calibration given an EQX sensor record and EQX cal entry /// /// /// /// private SensorCalibration CreateSensorCalibration(EqxCalHistoryEntry cal, EqxSensorAxis sensor) { var sc = GetCalibrationFromEQXSensorAxis(sensor, _sensorLookup.ContainsKey(sensor.UUID) ? _sensorLookup[sensor.UUID] : null); sc.CalibrationDate = cal.CalDateSpecified ? cal.CalDate : DateTime.MinValue; sc.Username = cal.CalPerson; sc.CalVersion = cal.CalVersion; sc.Records.Records[0].EngineeringUnits = sensor.PhysicalUnit; //15609 Sensors not usable after EQX import //use excitation voltage from eqx file if (cal.SensitivityVoltageSpecified) { if (cal.SensitivityVoltage.Equals(1F)) { sc.IsProportional = true; sc.Records.Records[0].Excitation = ExcitationVoltageOptions.ExcitationVoltageOption.Volt5; } else { sc.Records.Records[0].Excitation = Test.Module.Channel.Sensor.GetExcitationVoltageEnumFromMagnitude(cal.SensitivityVoltage); } } sc.CalVersion = cal.CalVersion; return sc; } /// /// keeps track of which EQX sensor entries has null id module strings /// we do this so that we can prevent overwriting the EID on existing sensors when the id module string is null /// since there could be multiple entries, any entries with an EID should take the cake /// 18467 Missing IDModuleString in e2x file import is clearing the EID on sensor import /// they key is the UUID for the sensor, the value is whether it had a NULL IDModuleString or not /// public SensorData MakeDefaultSensor() { return SensorDBTables.CreateDefaultSensor(DefaultZeroMethodStart, DefaultZeroMethodEnd); // FB12764: Default AoT data in SensorConstants } private const double DefaultOffsetToleranceLow = -100D; private const double DefaultOffsetToleranceHigh = 100D; /// /// merges sensors into one sensor /// This can happen when there are multiple entries for the same sensor in EQX /// /// /// /// /// private SensorData CombineSensors(SensorData sd1, SensorData sd2, bool useZeroForUnfiltered) { var sdNew = MakeDefaultSensor(); sdNew.UUID = sd1.UUID; foreach (var enumValue in Enum.GetValues(typeof(SensorData.ExchangeFields))) { switch ((SensorData.ExchangeFields)enumValue) { case SensorData.ExchangeFields.BridgeResistance: sdNew.BridgeResistance = Math.Max(sd1.BridgeResistance, sd2.BridgeResistance); break; case SensorData.ExchangeFields.BridgeType: if (sd1.Bridge == sd2.Bridge) { sdNew.Bridge = sd1.Bridge; } sdNew.Bridge = sd2.Bridge; break; case SensorData.ExchangeFields.CalibrationDate: break; case SensorData.ExchangeFields.CalibrationDueDate: break; case SensorData.ExchangeFields.CalUser: break; case SensorData.ExchangeFields.CalZMO: break; case SensorData.ExchangeFields.Capacity: sdNew.Capacity = Math.Max(sd1.Capacity, sd2.Capacity); break; case SensorData.ExchangeFields.CheckOffset: sdNew.CheckOffset = sd1.CheckOffset != sd2.CheckOffset || sd1.CheckOffset; break; case SensorData.ExchangeFields.Comment: sdNew.Comment = String.IsNullOrEmpty(sd1.Comment) ? sd2.Comment : sd1.Comment; break; case SensorData.ExchangeFields.OffsetToleranceLow: if (sd1.OffsetToleranceLow == sd2.OffsetToleranceLow) { sdNew.OffsetToleranceLow = sd1.OffsetToleranceLow; } else if (sd1.OffsetToleranceLow == DefaultOffsetToleranceLow) { sdNew.OffsetToleranceLow = sd2.OffsetToleranceLow; } else if (sd2.OffsetToleranceLow == DefaultOffsetToleranceLow) { sdNew.OffsetToleranceLow = sd1.OffsetToleranceLow; } else { sdNew.OffsetToleranceLow = Math.Min(sd1.OffsetToleranceLow, sd2.OffsetToleranceLow); } break; case SensorData.ExchangeFields.OffsetToleranceHigh: if (sd1.OffsetToleranceHigh == sd2.OffsetToleranceHigh) { sdNew.OffsetToleranceHigh = sd1.OffsetToleranceHigh; } else if (sd1.OffsetToleranceHigh == DefaultOffsetToleranceHigh) { sdNew.OffsetToleranceHigh = sd2.OffsetToleranceHigh; } else if (sd2.OffsetToleranceHigh == DefaultOffsetToleranceHigh) { sdNew.OffsetToleranceHigh = sd1.OffsetToleranceHigh; } else { sdNew.OffsetToleranceHigh = Math.Min(sd1.OffsetToleranceHigh, sd2.OffsetToleranceHigh); } break; case SensorData.ExchangeFields.ExcitationVoltage: if (sd1.SupportedExcitation[0] == sd2.SupportedExcitation[0]) { sdNew.SupportedExcitation = sd2.SupportedExcitation; } else if (sd1.SupportedExcitation[0] == ExcitationVoltageOptions.ExcitationVoltageOption.Undefined) { sdNew.SupportedExcitation = sd2.SupportedExcitation; } else { sdNew.SupportedExcitation = sd1.SupportedExcitation; } break; case SensorData.ExchangeFields.FilterClass: sdNew.SetFilterAndFilterClassISO(sd1.Filter, useZeroForUnfiltered, false); break; case SensorData.ExchangeFields.Id: sdNew.EID = string.IsNullOrEmpty(sd1.EID) ? sd2.EID : sd1.EID; break; case SensorData.ExchangeFields.InitialEU: break;//handled in calibration data case SensorData.ExchangeFields.Invert: sdNew.Invert = sd1.Invert != sd2.Invert || sd1.Invert; break; case SensorData.ExchangeFields.IsoCode: //if 1 is empty use 2, otherwise just use 1 sdNew.ISOCode = string.IsNullOrEmpty(sd1.ISOCode) ? sd2.ISOCode : sd1.ISOCode; //so we either had 1 empty and chose #2, or #1 wasn't empty and we used it //just incase #1 isn't empty, but is blank ... double check that if #1 is blank and #2 isn't sdNew.ISOCode = (GroupChannel.BLANK_FULL_ISO.Equals(sd1.ISOCode) && !GroupChannel.BLANK_FULL_ISO.Equals(sd2.ISOCode)) ? sd2.ISOCode : sdNew.ISOCode; if (sdNew.ISOCode?.Length == 16) { sdNew.FilterClassIso = sdNew.ISOCode?[15].ToString(); } break; case SensorData.ExchangeFields.IsoLongName: sdNew.ISOChannelName = string.IsNullOrEmpty(sd1.ISOChannelName) ? sd2.ISOChannelName : sd1.ISOChannelName; break; case SensorData.ExchangeFields.Manufacturer: sdNew.Manufacturer = string.IsNullOrEmpty(sd1.Manufacturer) ? sd2.Manufacturer : sd1.Manufacturer; break; case SensorData.ExchangeFields.MeasurementUnit: sdNew.DisplayUnit = string.IsNullOrEmpty(sd1.DisplayUnit) ? sd2.DisplayUnit : sd1.DisplayUnit; break; case SensorData.ExchangeFields.Model: sdNew.Model = string.IsNullOrEmpty(sd1.Model) ? sd2.Model : sd1.Model; break; case SensorData.ExchangeFields.Sensitivity: break; case SensorData.ExchangeFields.SerialNumber: if (string.IsNullOrEmpty(sd1.SerialNumber)) { if (!string.IsNullOrEmpty(sd2.SerialNumber)) { sdNew.SerialNumber = sd2.SerialNumber; } } else { sdNew.SerialNumber = sd1.SerialNumber; } break; case SensorData.ExchangeFields.Shunt: if (sd2.Shunt == sd1.Shunt) { sdNew.Shunt = sd1.Shunt; } break; case SensorData.ExchangeFields.Status: break; case SensorData.ExchangeFields.UserSerialNumber: sdNew.UserSerialNumber = string.IsNullOrEmpty(sd1.UserSerialNumber) ? sd2.UserSerialNumber : sd1.UserSerialNumber; break; case SensorData.ExchangeFields.UserValue1: sdNew.UserValue1 = string.IsNullOrEmpty(sd1.UserValue1) ? sd2.UserValue1 : sd1.UserValue1; sdNew.UserCode = string.IsNullOrEmpty(sd1.UserCode) ? sd2.UserCode : sd1.UserCode; sdNew.UserChannelName = string.IsNullOrEmpty(sd1.UserChannelName) ? sd2.UserChannelName : sd1.UserChannelName; break; case SensorData.ExchangeFields.UserValue2: sdNew.UserValue2 = string.IsNullOrEmpty(sd1.UserValue2) ? sd2.UserValue2 : sd1.UserValue2; break; case SensorData.ExchangeFields.UserValue3: sdNew.UserValue3 = string.IsNullOrEmpty(sd1.UserValue3) ? sd2.UserValue3 : sd1.UserValue3; break; case SensorData.ExchangeFields.IsProportional: break;//handled by calibration case SensorData.ExchangeFields.RemoveOffset: break;//handled by calibration case SensorData.ExchangeFields.OffsetMethod: //don't think EQX populates this? //16286 EQX import failed in Sensor DB when Set "EQXUseSerialNumberFieldForSN" to true in DataPRO.exe.config file break; case SensorData.ExchangeFields.FiringDelay: sdNew.DelayMS = sd1.DelayMS == 0 ? sd2.DelayMS : sd1.DelayMS; break; case SensorData.ExchangeFields.FiringDuration: sdNew.DurationMS = 0 == sd1.DurationMS ? sd2.DurationMS : sd1.DurationMS; sdNew.LimitDuration = sd1.LimitDuration || sd2.LimitDuration; break; case SensorData.ExchangeFields.FireMode: sdNew.SquibFireMode = sd1.SquibFireMode != SquibFireMode.NONE ? sd1.SquibFireMode : sd2.SquibFireMode; break; case SensorData.ExchangeFields.CurrentLimit: sdNew.SquibOutputCurrent = sd1.SquibOutputCurrent == 0 ? sd2.SquibOutputCurrent : sd1.SquibOutputCurrent; break; case SensorData.ExchangeFields.InputMode: sdNew.InputMode = sd1.InputMode == DigitalInputModes.NONE ? sd2.InputMode : sd1.InputMode; break; case SensorData.ExchangeFields.SWOffsetCalculationStartSec: case SensorData.ExchangeFields.SWOffsetCalculationEndSec: case SensorData.ExchangeFields.SWOffsetCompensationType: //calibration fields break; default: throw new NotSupportedException("ImportSensorsPreviewControl::CombineSensors unsupported field: " + enumValue); } } if (string.IsNullOrEmpty(sdNew.SerialNumber)) { sdNew.SerialNumber = sdNew.Comment; } return sdNew; } private ZeroMethodType GetZeroMethodType(EqxSWOffsetCompensationType eqxZero) { switch(eqxZero) { case EqxSWOffsetCompensationType.Averagecalculation: return ZeroMethodType.AverageOverTime; case EqxSWOffsetCompensationType.Fixvalue: return ZeroMethodType.None; case EqxSWOffsetCompensationType.Pretestmeasurement: return ZeroMethodType.UsePreEventDiagnosticsZero; default: throw new InvalidCastException($"no zero method type: {eqxZero}"); } } private DigitalInputModes GetInputMode(EqxInputModes mode) { switch (mode) { case EqxInputModes.TLH: return DigitalInputModes.TLH; case EqxInputModes.THL: return DigitalInputModes.THL; case EqxInputModes.CCNO: return DigitalInputModes.CCNO; case EqxInputModes.CCNC: default: return DigitalInputModes.CCNC; } } private SquibFireMode GetSquibFireMode(EqxFiringMode mode) { switch (mode) { case EqxFiringMode.AC: return SquibFireMode.AC; case EqxFiringMode.DC: return SquibFireMode.CAP; case EqxFiringMode.ConstantCurrent: return SquibFireMode.CONSTANT; case EqxFiringMode.CapacitorDischarge: default: return SquibFireMode.CAP; } } public EQXSensorDatabase(bool usemVOverVForPolys) { UsemVOverVForPolys = usemVOverVForPolys; } } }