Files
DP44/DataPRO/EquipmentExchange/EQXSensorDatabase.cs
2026-04-17 14:55:32 -04:00

992 lines
50 KiB
C#

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
{
/// <summary>
/// class that handles reading an EQX file and translating into DTS SensorData and SensorCalibration objects
/// </summary>
[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<string, List<SensorCalibration>> _calibrationLookup = new Dictionary<string, List<SensorCalibration>>();
private bool _importCreateDynamicGroups;
/// <summary>
/// sensor lookup by UUID (EQX id) or serial number
/// </summary>
private readonly Dictionary<string, SensorData> _sensorLookup = new Dictionary<string, SensorData>();
/// <summary>
/// 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
/// </summary>
private readonly Dictionary<string, bool> _sensorHasNullIDModule = new Dictionary<string, bool>();
/// <summary>
/// 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
/// </summary>
/// <param name="key">UUID or serial number of sensor</param>
/// <returns>true if the sensor has a null id module section, false otherwise</returns>
public bool SensorHasNullIDModule(string key)
{
return _sensorHasNullIDModule.ContainsKey(key);
}
/// <summary>
/// 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
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public bool GetSensorNullIdModuleValue(string key)
{
return _sensorHasNullIDModule[key];
}
/// <summary>
/// returns all sensor records read in from Read method
/// <see cref="Read(string, ReportErrorsDelegate, bool, bool)"/>
/// </summary>
/// <returns>all sensors read in by Read</returns>
public SensorData[] GetSensors()
{
return _sensorLookup.Values.ToArray();
}
public SensorImportData GetImportData()
{
return new SensorImportData
{
GroupNameTestObjectLookup = _groupNameToTestObjectLookup,
GroupNameSensorListLookup = _groupNameSensorListLookup
};
}
/// <summary>
/// whether there is calibration information in eqx file for a given sensor
/// </summary>
/// <param name="key">UUID or serial number of sensor</param>
/// <returns>true if there are calibration entries, false otherwise</returns>
public bool ContainsCalibration(string key)
{
return _calibrationLookup.ContainsKey(key);
}
/// <summary>
/// returns all calibrations for a given sensor
/// </summary>
/// <param name="id">UUID or serial number for sensor</param>
/// <returns></returns>
public List<SensorCalibration> GetCalibrations(string id)
{
if (_calibrationLookup.ContainsKey(id))
{
return _calibrationLookup[id];
}
return new List<SensorCalibration>();
}
/// <summary>
/// action to perform to report an error with file
/// </summary>
/// <param name="errors"></param>
public delegate void ReportErrorsDelegate(List<string> errors);
/// <summary>
/// reads a EQX file and populates sensor and calibration entries
/// </summary>
/// <param name="path"></param>
/// <param name="reportErrors"></param>
/// <param name="eqxUseSerialNumberFieldForSN"></param>
/// <param name="useZeroForUnfiltered"></param>
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<string>(new[] { ex.Message })); return; }
var errors = new List<string>();
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<string> errors,
ref Dictionary<string, List<string>> groupNameSensorListLookup,
ref Dictionary<string, string> 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<string>());
}
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;
}
/// <summary>
/// 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
/// </summary>
/// <param name="idModuleString"></param>
/// <returns></returns>
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;
}
/// <summary>
/// code borrowed from nate for
/// 30609 EIDs can need correction when coming from crashdesigner EQX
/// just copied it here as is
/// </summary>
/// <param name="ShortID"></param>
/// <returns></returns>
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;
}
/// <summary>
/// process an EQX sensor, creating a sensor if necessary
/// </summary>
/// <param name="sensor"></param>
/// <param name="eqxUseSerialNumberFieldForSN"></param>
/// <param name="useZeroForUnfiltered"></param>
private void AddEQXSensor(string groupName, EqxSensorAxis sensor,
bool eqxUseSerialNumberFieldForSN,
bool useZeroForUnfiltered,
ref List<string> 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<SensorCalibration>());
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<string, List<string>> _groupNameSensorListLookup = new Dictionary<string, List<string>>();
private Dictionary<string, string> _groupNameToTestObjectLookup = new Dictionary<string, string>();
/// <summary>
/// combine calibrations if necessary, or add a new entry if needed
///
/// </summary>
/// <param name="sc1"></param>
/// <param name="sensorUUID"></param>
private void CombineCalibrations(SensorCalibration sc1, string sensorUUID)
{
if (!_calibrationLookup.ContainsKey(sensorUUID)) { _calibrationLookup.Add(sensorUUID, new List<SensorCalibration>()); }
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);
}
/// <summary>
/// looks through existing calibration entries given a EQX entry and updates or combines calibrations as necessary
/// </summary>
/// <param name="entry"></param>
/// <param name="sensorUUID"></param>
/// <param name="sensor"></param>
private void CombineCalibrations(EqxCalHistoryEntry entry, string sensorUUID, EqxSensorAxis sensor)
{
if (!_calibrationLookup.ContainsKey(sensorUUID)) { _calibrationLookup[sensorUUID] = new List<SensorCalibration>(); }
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));
}
/// <summary>
/// processes an EQX group
/// </summary>
/// <param name="group">group to process</param>
/// <param name="eqxUseSerialNumberFieldForSN"></param>
/// <param name="useZeroForUnfiltered"></param>
private void AddEQXGroup(EqxBaseSensorGroup group,
bool eqxUseSerialNumberFieldForSN,
bool useZeroForUnfiltered,
ref List<string>errors)
{
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);
}
}
/// <summary>
/// creates a sensor calibration given an EQX sensor record and EQX cal entry
/// </summary>
/// <param name="cal"></param>
/// <param name="sensor"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 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
/// </summary>
public SensorData MakeDefaultSensor()
{
return SensorDBTables.CreateDefaultSensor(DefaultZeroMethodStart, DefaultZeroMethodEnd); // FB12764: Default AoT data in SensorConstants
}
private const double DefaultOffsetToleranceLow = -100D;
private const double DefaultOffsetToleranceHigh = 100D;
/// <summary>
/// merges sensors into one sensor
/// This can happen when there are multiple entries for the same sensor in EQX
/// </summary>
/// <param name="sd1"></param>
/// <param name="sd2"></param>
/// <param name="useZeroForUnfiltered"></param>
/// <returns></returns>
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;
}
}
}