Files
DP44/Common/DTS.Common.DataModel/SensorDatabaseExport.cs
2026-04-17 14:55:32 -04:00

1755 lines
86 KiB
C#

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using DTS.Common.Base;
using DTS.Common.Enums.DBExport;
using DTS.Common.Utilities.Logging;
using DTS.Common.Enums;
using DTS.Common.Enums.Sensors;
using DTS.Common.Classes.Sensors;
using DTS.Common.Utils;
using System.Xml;
using DTS.Common.Interface.Sensors;
using DTS.SensorDB;
using System.Xml.Serialization;
using System.IO;
using EQX;
using DTS.Common.DAS.Concepts;
namespace DTS.Common.DataModel
{
/// <summary>
/// handles exporting (to TDCSensorDatabase CSV format)
/// couple of important notes.
/// 1) uses current culture list separator (not necessarily a comma)
/// 2) uses local culture decimal/number formats
/// 3) doesn't specify an encoding for export file
/// </summary>
public class SensorDatabaseExport : BasePropertyChanged
{
// ReSharper disable once InconsistentNaming
private bool _TDCOnly = false;
public bool TDCOnly
{
get => _TDCOnly;
set => SetProperty(ref _TDCOnly, value, "TDCOnly");
}
private string _defaultFolder = DataModelSettings.DefaultTDCSensorDatabaseFolder;
/// <summary>
/// the default folder for the export, is initialed using default value in config file
/// </summary>
public string DefaultFolder
{
get => _defaultFolder;
set => SetProperty(ref _defaultFolder, value, "DefaultFolder");
}
private string _defaultFile = DataModelSettings.DefaultTDCSensorDatabaseFile;
/// <summary>
/// the filename to export to, by default is controlled by config file
/// </summary>
public string DefaultFile
{
get => _defaultFile;
set => SetProperty(ref _defaultFile, value, "DefaultFile");
}
/// <summary>
/// sets the default file and folder for export
/// </summary>
/// <param name="fullPathToFile">full path to destination file. Can be a relative path, it will be resolved to absolute path.</param>
public void SetFilename(string fullPathToFile)
{
var fi = new FileInfo(fullPathToFile);
DefaultFolder = Path.GetFullPath(fi.DirectoryName);
DefaultFile = fi.Name;
}
/// <summary>
/// controls what to do if there is an error during export
/// an errored export will not finish and returns immediately
/// </summary>
/// <param name="message"></param>
public delegate void ErrorDelegate(string message);
public event ErrorDelegate OnError;
/// <summary>
/// controls what to do when export finishes
/// </summary>
public delegate void DoneDelegate();
public event DoneDelegate OnDone;
private SensorData[] _sensors = null;
/// <summary>
/// sensors to export, if null all sensors are exported
/// </summary>
public SensorData[] Sensors
{
get => _sensors;
set => SetProperty(ref _sensors, value, "Sensors");
}
/// <summary>
/// whether the user wants to export first use dates or not
/// 13065 Sensor "First Use" Date
/// </summary>
public bool ExportFirstUseDate { get; set; } = true;
private List<string> _modelStrings;
public void ExportSLICEWare()
{
try
{
if (!Directory.Exists(DefaultFolder))
{
Directory.CreateDirectory(DefaultFolder);
}
var di = new DirectoryInfo(DefaultFolder);
var calFilename = Path.Combine(DefaultFolder, "Calibration.SensorDB.xml");
var dataFilename = Path.Combine(DefaultFolder, "Data.SensorDB.xml");
var modelFilename = Path.Combine(DefaultFolder, "Model.SensorDB.xml");
if (File.Exists(calFilename)) { BackupAndDelete(calFilename); }
if (File.Exists(dataFilename)) { BackupAndDelete(dataFilename); }
if (File.Exists(modelFilename)) { BackupAndDelete(modelFilename); }
var sb = new StringBuilder(5000000);
var sb2 = new StringBuilder(5000000);
var sb3 = new StringBuilder(5000000);
//stringbuilder always uses UTF-16, so we don't _really_ get a choice here.
var xSet = new XmlWriterSettings() { Indent = true, CheckCharacters = true, Encoding = Encoding.Unicode };
var writerSensors = XmlWriter.Create(sb, xSet);
var writerCals = XmlWriter.Create(sb2, xSet);
var writerModels = XmlWriter.Create(sb3, xSet);
writerSensors.WriteStartDocument();
writerCals.WriteStartDocument();
writerModels.WriteStartDocument();
writerCals.WriteStartElement("Calibrations");
writerSensors.WriteStartElement("SensorData");
writerSensors.WriteAttributeString("Software", System.Reflection.Assembly.GetEntryAssembly().GetName().Name);
writerSensors.WriteAttributeString("SoftwareVersion", System.Reflection.Assembly.GetEntryAssembly().GetName().Version.ToString(4));
writerModels.WriteStartElement("SensorModels");
_modelStrings = new List<string>();
foreach (var s in Sensors)
{
// First Axis
var thisSensor = new SensorData(s);
WriteSLICEWareXML(thisSensor, ref writerSensors, ref writerCals, ref writerModels);
}
writerSensors.WriteEndElement();//SensorData
writerSensors.WriteEndDocument();
writerCals.WriteEndElement();//calibrations
writerCals.WriteEndDocument();
writerModels.WriteEndElement();//SensorModels
writerModels.WriteEndDocument();
writerSensors.Close();
writerCals.Close();
writerModels.Close();
File.WriteAllText(dataFilename, sb.ToString(), xSet.Encoding);
File.WriteAllText(calFilename, sb2.ToString(), xSet.Encoding);
File.WriteAllText(modelFilename, sb3.ToString(), xSet.Encoding);
OnDone?.Invoke();
}
catch (Exception ex)
{
OnError?.Invoke(ex.Message); APILogger.Log(ex);
}
finally
{
_modelStrings = null;
}
}
private void WriteElement(ref XmlWriter sensorWriter, string tag, string val)
{
sensorWriter.WriteStartElement(tag);
sensorWriter.WriteString(val);
sensorWriter.WriteEndElement();
}
private SensorCalibration GetPreferredSensorCalibration(SensorData s)
{
return s.SupportedExcitation.Select(e => SensorCalibration.GetLatestCalibrationBySerialNumberAndExcitation(s, e)).FirstOrDefault(sc => null != sc);
}
private SensorCalibration GetFirstSensorCalibration(SensorData sd)
{
var scPreferred = new SensorCalibration();
Array.ForEach(scPreferred.Records.Records, record => record.EngineeringUnits = sd.DisplayUnit); //FB16398: set units on all records, not just first
try
{
scPreferred.Records.Records[0].Excitation = sd.SupportedExcitation[0];
}
catch (Exception ex)
{
APILogger.Log(ex);
} // If no SupportedExcitation, don't crash. Then, the sensor will not be exported below.
return scPreferred;
}
private bool ShouldExport(SensorData sd, SensorCalibration scPreferred)
{
if (sd.IsSquib() || sd.IsDigitalInput() || sd.IsDigitalOutput()
|| scPreferred.NonLinear && scPreferred.IRTraccCalculationType == NonLinearStyles.IRTraccCalFactor)
{ return false; }//skip for now
return true;
}
private string GetSanitizedSN(SensorData sd)
{
var serialNumber = sd.SerialNumber;
if (1 < sd.NumberOfAxes || 0 < sd.AxisNumber)
{
// sanitize serialnumber string
if (0 < sd.AxisNumber) { serialNumber = serialNumber.Remove(serialNumber.IndexOf("_axis_", StringComparison.Ordinal)); }
// put back the axis designation
serialNumber = string.Concat(serialNumber, "_axis_", sd.AxisNumber + 1);
}
return serialNumber;
}
private string GetSanitizedModel(SensorData sd)
{
var model = sd.Model;
if (1 < sd.NumberOfAxes || 0 < sd.AxisNumber)
{
// sanitize model string
if (0 < sd.AxisNumber) { model = model.Remove(model.IndexOf("_axis_", StringComparison.Ordinal)); }
model = string.IsNullOrEmpty(model) ? string.Empty : string.Concat(model, "_axis_", sd.AxisNumber + 1);
}
return model;
}
private void WriteSLICEWareXML(SensorData sd, ref XmlWriter sensorWriter, ref XmlWriter calWriter, ref XmlWriter modelWriter)
{
var scPreferred = GetPreferredSensorCalibration(sd);
//Handle the off chance that we have no valid calibration for this sensor at this excitation.
if (null == scPreferred)
{
scPreferred = GetFirstSensorCalibration(sd);
}
if (!ShouldExport(sd, scPreferred)) { return; }
foreach (var se in sd.SupportedExcitation)
{
var sc = SensorCalibration.GetLatestCalibrationBySerialNumberAndExcitation(sd, se);
if (null == sc) continue;
WriteSupportedExcitation(ref sensorWriter, sd,
se, scPreferred, sc, ref modelWriter,
ref calWriter);
if (!sc.IsProportional) { break; }//this works for all excitation
}
}
private void WriteSupportedExcitation(ref XmlWriter sensorWriter,
SensorData sd,
ExcitationVoltageOptions.ExcitationVoltageOption se,
SensorCalibration scPreferred,
SensorCalibration sc,
ref XmlWriter modelWriter,
ref XmlWriter calWriter)
{
sensorWriter.WriteStartElement("Sensor");
WriteElement(ref sensorWriter, "Version", "1");
var serialNumber = GetSanitizedSN(sd);
var model = GetSanitizedModel(sd);
if (1 < sd.SupportedExcitation.Length)
{
serialNumber = string.Concat(serialNumber, "_", se.ToString());
}
WriteElement(ref sensorWriter, "SerialNumber", serialNumber);
WriteElement(ref sensorWriter, "UserSerialNumber", sd.UserSerialNumber);
WriteElement(ref sensorWriter, "DiagnosticsMode", sd.DiagnosticsMode.ToString(CultureInfo.InvariantCulture));
WriteElement(ref sensorWriter, "Model", model);
WriteElement(ref sensorWriter, "Status", sd.Status.ToString());
WriteElement(ref sensorWriter, "MeasurementUnit", scPreferred.Records.Records[0].EngineeringUnits);
WriteElement(ref sensorWriter, "ID", sd.EID);
WriteElement(ref sensorWriter, "Capacity", $"{sd.Capacity:0}");
WriteElement(ref sensorWriter, "Comment", sd.Comment);
WriteElement(ref sensorWriter, "IsPropotional", scPreferred.IsProportional.ToString().ToLower());
WriteElement(ref sensorWriter, "Bridge", sd.Bridge.ToString());
WriteElement(ref sensorWriter, "Shunt", sd.Shunt.ToString());
WriteElement(ref sensorWriter, "CalSignal", sd.CalSignal.ToString().ToLower());
WriteElement(ref sensorWriter, "InternalShuntResistance", sd.InternalShuntResistance.ToString(CultureInfo.InvariantCulture));
WriteElement(ref sensorWriter, "ExternalShuntResistance", sd.ExternalShuntResistance.ToString(CultureInfo.InvariantCulture));
WriteElement(ref sensorWriter, "BridgeResistance", sd.BridgeResistance.ToString(CultureInfo.InvariantCulture));
WriteElement(ref sensorWriter, "BridgeLegMode", sd.BridgeLegMode.ToString());
sensorWriter.WriteStartElement("OffsetTolerance-LowHigh");
sensorWriter.WriteAttributeString("Low", sd.OffsetToleranceLow.ToString(CultureInfo.InvariantCulture));
sensorWriter.WriteAttributeString("High", sd.OffsetToleranceHigh.ToString(CultureInfo.InvariantCulture));
sensorWriter.WriteEndElement();
WriteElement(ref sensorWriter, "Invert", sd.Invert.ToString().ToLower());
var removeOffset = scPreferred.RemoveOffset.ToString().ToLower();
if (scPreferred.NonLinear && scPreferred.Records.Records[0].Poly.NonLinearSliceWareStyle == NonLinearSLICEWareStyles.DiagnosticZeroMMmV)
{
//SLICEWare expects true for remove offset in this case
removeOffset = "true";
}
WriteElement(ref sensorWriter, "RemoveOffset", removeOffset);
WriteElement(ref sensorWriter, "UserValue1", sd.UserValue1);
WriteElement(ref sensorWriter, "UserValue2", sd.UserValue2);
WriteElement(ref sensorWriter, "UserValue3", sd.UserValue3);
sensorWriter.WriteStartElement("Range-SensorRange");
sensorWriter.WriteAttributeString("Low", sd.RangeLow.ToString(CultureInfo.InvariantCulture));
sensorWriter.WriteAttributeString("Medium", sd.RangeMedium.ToString(CultureInfo.InvariantCulture));
sensorWriter.WriteAttributeString("High", sd.RangeHigh.ToString(CultureInfo.InvariantCulture));
sensorWriter.WriteEndElement();
WriteElement(ref sensorWriter, "Excitation", se.ToString());
sensorWriter.WriteStartElement("Filter-FilterClass");
var fc = sd.Filter.FClass;
var freq = sd.Filter.Frequency;
if ( fc == FilterClassType.Unfiltered)
{
fc = FilterClassType.None;
freq = 0;
}
sensorWriter.WriteAttributeString("Class", fc.ToString());
sensorWriter.WriteString(freq.ToString(CultureInfo.InvariantCulture));
sensorWriter.WriteEndElement();
sensorWriter.WriteStartElement("Zero-ZeroMethod");//TODO: linear/nonlinear
sensorWriter.WriteAttributeString("Start", scPreferred.ZeroMethods.Methods[0].Start.ToString(CultureInfo.InvariantCulture));
sensorWriter.WriteAttributeString("End", scPreferred.ZeroMethods.Methods[0].End.ToString(CultureInfo.InvariantCulture));
sensorWriter.WriteString(scPreferred.ZeroMethods.Methods[0].Method.ToString());
sensorWriter.WriteEndElement();
WriteElement(ref sensorWriter, "InitialEU", SensorData.GetInitialEUValue(scPreferred, scPreferred.Records.Records[0].Excitation, scPreferred.InitialOffsets.Offsets.First()).ToString(CultureInfo.InvariantCulture));
WriteElement(ref sensorWriter, "Created", sd.Created.ToString(CultureInfo.InvariantCulture));
WriteElement(ref sensorWriter, "TimesUsed", sd.TimesUsed.ToString(CultureInfo.InvariantCulture));
WriteElement(ref sensorWriter, "Zmo", "0");
WriteElement(ref sensorWriter, "ISOCode", sd.ISOCode);
WriteElement(ref sensorWriter, "CheckOffset", sd.CheckOffset.ToString());
WriteElement(ref sensorWriter, "IRTraccExponent", scPreferred.Records.Records.First().Poly.LinearizationExponent.ToString(CultureInfo.InvariantCulture));
WriteElement(ref sensorWriter, "SIFFile", "");
WriteElement(ref sensorWriter, "SensorCategory", sd.SensorCategory.ToString(CultureInfo.InvariantCulture));
WriteElement(ref sensorWriter, "FilterOption", sd.ByPassFilter.ToString());
WriteElement(ref sensorWriter, "OriginalShuntMode", sd.Shunt.ToString());
WriteElement(ref sensorWriter, "OriginalZeroMethod", scPreferred.ZeroMethods.Methods.First().Method.ToString());
WriteElement(ref sensorWriter, "Manufacturer", sd.Manufacturer);
WriteElement(ref sensorWriter, "CouplingMode", sd.CouplingMode.ToString());
WriteElement(ref sensorWriter, "IgnoreRange", sd.IgnoreRange.ToString());
WriteElement(ref sensorWriter, "UniPolar", sd.UniPolar.ToString().ToLower());
WriteElement(ref sensorWriter, "NonLinear", scPreferred.NonLinear.ToString().ToLower());
WriteElement(ref sensorWriter, "Dimension", sd.PhysicalDimension);
WriteElement(ref sensorWriter, "PolyStyle", "IRTRacc");
WriteElement(ref sensorWriter, "IRTraccFormat", scPreferred.Records.Records.First().Poly.NonLinearSliceWareStyle.ToString());
sensorWriter.WriteEndElement();//Sensor
WriteSensorModel(sd, ref modelWriter, scPreferred);
WriteSensorCalibrations(sc, se, ref calWriter, serialNumber);
}
private void WriteSensorCalibrations(SensorCalibration sc,
ExcitationVoltageOptions.ExcitationVoltageOption se, ref XmlWriter calWriter, string serialNumber)
{
foreach (var record in sc.Records.Records)
{
if (true == sc.IsProportional && record.Excitation != se) { continue; }
calWriter.WriteStartElement("Calibration");
WriteElement(ref calWriter, "Version", "1");
WriteElement(ref calWriter, "SerialNumber", serialNumber);
WriteElement(ref calWriter, "Date", sc.CalibrationDate.ToString(CultureInfo.InvariantCulture));
var SLICEWareSensitivity = record.Sensitivity;
if (sc.IsProportional && SensorConstants.SensUnits.mVperEU == record.SensitivityUnits)
{
// Need to process the sensitivity in this case.
SLICEWareSensitivity = record.Sensitivity / Test.Module.Channel.Sensor.GetExcitationVoltageMagnitudeFromEnum(se);
}
WriteElement(ref calWriter, "Sensitivity", SLICEWareSensitivity.ToString(CultureInfo.InvariantCulture));
WriteElement(ref calWriter, "Username", sc.Username);
WriteElement(ref calWriter, "DocumentID", "");
WriteElement(ref calWriter, "MeasurementUnit", record.EngineeringUnits);
WriteElement(ref calWriter, "Excitation", se.ToString());
WriteElement(ref calWriter, "Zmo", "0");
WriteElement(ref calWriter, "Poly", record.Poly.ToSLICEWareSerializeString());
calWriter.WriteEndElement();
}
}
private void WriteSensorModel(SensorData sd, ref XmlWriter modelWriter,
SensorCalibration scPreferred)
{
if (!string.IsNullOrEmpty(sd.Manufacturer) && !_modelStrings.Contains(string.Concat(sd.Manufacturer, ",", sd.Model)))
{
_modelStrings.Add(string.Concat(sd.Manufacturer, ",", sd.Model));
modelWriter.WriteStartElement("SensorModel");
WriteElement(ref modelWriter, "Model", sd.Model);
WriteElement(ref modelWriter, "Manufacturer", sd.Manufacturer);
WriteElement(ref modelWriter, "UserPartNumber", "");
WriteElement(ref modelWriter, "Capacity", sd.Capacity.ToString(CultureInfo.InvariantCulture));
WriteElement(ref modelWriter, "OffsetTolerance", string.Concat(sd.OffsetToleranceLow, ",", sd.OffsetToleranceHigh));
WriteElement(ref modelWriter, "MeasurementUnit", sd.DisplayUnit);
WriteElement(ref modelWriter, "SensorRange", string.Concat(sd.RangeLow, ",", sd.RangeMedium, ",", sd.RangeHigh));
WriteElement(ref modelWriter, "Sensitivity", scPreferred.Records.Records.First().Sensitivity.ToString(CultureInfo.InvariantCulture));
WriteElement(ref modelWriter, "Excitation", scPreferred.Records.Records.First().Excitation.ToString());
WriteElement(ref modelWriter, "IsProportional", scPreferred.IsProportional.ToString());
WriteElement(ref modelWriter, "Bridge", sd.Bridge.ToString());
WriteElement(ref modelWriter, "Shunt", sd.Shunt.ToString());
WriteElement(ref modelWriter, "BridgeResistance", sd.BridgeResistance.ToString(CultureInfo.InvariantCulture));
WriteElement(ref modelWriter, "RemoveOffset", scPreferred.RemoveOffset.ToString());
WriteElement(ref modelWriter, "FilterClass", sd.Filter.FClass.ToString());
WriteElement(ref modelWriter, "ZeroMethod", string.Concat(scPreferred.ZeroMethods.Methods.First().Method.ToString(), ",", scPreferred.ZeroMethods.Methods.First().Start, ",", scPreferred.ZeroMethods.Methods.First().End));
WriteElement(ref modelWriter, "InitialEU", scPreferred.InitialOffsets.Offsets.First().EU.ToString(CultureInfo.InvariantCulture));
WriteElement(ref modelWriter, "NonLinear", scPreferred.NonLinear.ToString());
WriteElement(ref modelWriter, "UniPolar", sd.UniPolar.ToString());
WriteElement(ref modelWriter, "PolynomialStyle", "Equation");
WriteElement(ref modelWriter, "IRTraccFormat", "DiagnosticZeroMMmV");
WriteElement(ref modelWriter, "LinearizationFormula", "");
WriteElement(ref modelWriter, "PhysicalDimension", sd.PhysicalDimension);
WriteElement(ref modelWriter, "IgnoreRange", sd.IgnoreRange.ToString());
WriteElement(ref modelWriter, "Invert", sd.Invert.ToString());
WriteElement(ref modelWriter, "CouplingMode", sd.CouplingMode.ToString());
WriteElement(ref modelWriter, "CheckOffset", sd.CheckOffset.ToString());
modelWriter.WriteEndElement();//Model
}
}
private void BackupAndDelete(string filename)
{
var backupFile = filename + ".bak";
if (File.Exists(backupFile)) { File.Delete(backupFile); }
File.Move(filename, backupFile);
}
/// <summary>
/// </summary>
public void ExportTDC()
{
try
{
if (!Directory.Exists(DefaultFolder))
{
Directory.CreateDirectory(DefaultFolder);
}
var di = new DirectoryInfo(DefaultFolder);
var listSeparator = DataModelSettings.TDCSensorDatabaseExportUseCurrentLocale ? CultureInfo.CurrentCulture.TextInfo.ListSeparator :
CultureInfo.InvariantCulture.TextInfo.ListSeparator;
var ec = DataModelSettings.TDCSensorDatabaseExportUseCurrentLocale ? CultureInfo.CurrentCulture :
CultureInfo.InvariantCulture;
var fullPathToCSVFile = Path.Combine(di.FullName, DefaultFile);
if (File.Exists(fullPathToCSVFile)) { BackupAndDelete(fullPathToCSVFile); }
var fieldLookup = new Dictionary<int, List<CSVImportTags.Tags>>();
var sb = new StringBuilder();
for (var i = CSVImportTags.MIN_VALID_VERSION; i <= CSVImportTags.MAX_VALID_VERSION; i++)
{
if (1 == i) { continue; }//1 only has test tags
if (3 == i) { continue; }//3 is only group tags for tests ...
if (i > 0 && TDCOnly) { continue; } //TDC only supports v0
var tags = CSVImportTags.GetVersionTags(i);
fieldLookup[i] = new List<CSVImportTags.Tags>(tags);
foreach (var tag in tags)
{
if (tag == CSVImportTags.Tags.Unknown) { continue; } //skip
if (sb.Length > 0) { sb.Append(listSeparator); }
sb.Append(EscapeCSV(CSVImportTags.GetStringForTag(tag), false, listSeparator));
}
}
sb.AppendLine();
//done with headers, now go through all sensors, if no sensors are explicetly selected, use all sensors
var currentNumber = 0;
if (null == Sensors || Sensors.Length == 0) { Sensors = SensorsCollection.SensorsList.GetAllSensors(true); }
ExportSensors(ref currentNumber, sb, fieldLookup, listSeparator, ec);
//we may infact want to force an encoding, but for now just use the default...
File.WriteAllText(fullPathToCSVFile, sb.ToString(), Encoding.GetEncoding(DataModelSettings.TDCSensorDatabaseImportEncoding));
OnDone?.Invoke(); Sensors = null;
}
catch (Exception ex)
{
OnError?.Invoke(ex.Message); APILogger.Log(ex);
}
}
private void ExportSensors(ref int currentNumber, StringBuilder sb, IReadOnlyDictionary<int, List<CSVImportTags.Tags>> columns,
string listSeparator, CultureInfo ec)
{
foreach (var sd in Sensors)
{
ExportSensor(sd, sb, ref currentNumber, columns, listSeparator, ec);
sb.AppendLine();
}
}
private void ExportSensor(SensorData sd, StringBuilder sb, ref int currentNumber,
IReadOnlyDictionary<int, List<CSVImportTags.Tags>> columns, string listSeparator,
CultureInfo ec)
{
//for TDC sensitivities we use the preferred excitation record, there could be multiple records that are currently valid for different excitations
//for this sensor ... but those are handled in the DataPRO only columns, if they are enabled
SensorCalibration scPreferred = null;
if (!sd.SupportedExcitation.Any())
{
//Set supported excitation(s) based on any found calibration records
var scPreferredList = SensorCalibrationList.GetCalibrationsBySerialNumber(sd);
var supportedExcitationList = new List<ExcitationVoltageOptions.ExcitationVoltageOption>();
for (var i = 0; i < scPreferredList.Length; i++)
{
foreach (var calRecord in scPreferredList[i].Records.Records)
{
if (!supportedExcitationList.Contains(calRecord.Excitation))
{
supportedExcitationList.Add(calRecord.Excitation);
}
}
}
if (!supportedExcitationList.Any())
{
//There are no calibration records, so give up
return;
}
sd.SupportedExcitation = supportedExcitationList.ToArray();
}
scPreferred = SensorCalibration.GetLatestCalibrationBySerialNumberAndExcitation(sd, sd.SupportedExcitation.First());
//Handle the off chance that we have no valid calibration for this sensor at this excitation.
if (null == scPreferred)
{
scPreferred = new SensorCalibration();
scPreferred.Records.Records.First().EngineeringUnits = sd.DisplayUnit;
scPreferred.Records.Records.First().Excitation = sd.SupportedExcitation.First();
}
if (TDCOnly && scPreferred.NonLinear &&
scPreferred.Records.Records.First().Poly.NonLinearStyle != NonLinearStyles.Polynomial)
{
//TDC can not consume this sensor type. lets give up.
return;
}
currentNumber++;
var euConversionFactor = MeasurementUnitList.GetMeasurementUnit(scPreferred.Records.Records.First().EngineeringUnits).GetScalerConversionFrom(sd.DisplayUnit);
var bNeedComma = false;
for (var i = CSVImportTags.MIN_VALID_VERSION; i <= CSVImportTags.MAX_VALID_VERSION; i++)
{
if (!columns.ContainsKey(i)) { continue; }
var tags = columns[i];
if (0 == tags.Count) { continue; }
foreach (var tag in tags)
{
ExportTag(sd, scPreferred, tag, sb, ref currentNumber, listSeparator, ref bNeedComma, euConversionFactor, i,
ec);
}
}
}
private void ExportVersion0(SensorData sd, SensorCalibration scPreferred, CSVImportTags.Tags tag, StringBuilder sb,
ref int currentNumber, string listSeparator, ref bool bNeedComma, double euConversionFactor,
CultureInfo ec)
{
string sVal = null;
var bTextField = false;
switch (tag)
{
case CSVImportTags.Tags.Axis: sVal = ""; break;
case CSVImportTags.Tags.BRIDGE: sVal = sd.Bridge == SensorConstants.BridgeType.HalfBridge ? "Half" : "Full"; break;
case CSVImportTags.Tags.BridgeResistance:
{
if (double.IsNaN(sd.BridgeResistance) || sd.BridgeResistance < 0)
{
sVal = "";
}
else { sVal = sd.BridgeResistance.ToString(ec); }
}
break;
case CSVImportTags.Tags.BypassAAFilter: sVal = sd.ByPassFilter ? "Yes" : "No"; break;
case CSVImportTags.Tags.Category:
{
if (scPreferred.NonLinear)
{
switch (scPreferred.Records.Records.First().Poly.NonLinearStyle)
{
case NonLinearStyles.Polynomial: sVal = ((int)SensorInformationFile.TDCSensorCategory.Polynomial).ToString(); break;
default: sVal = ((int)SensorInformationFile.TDCSensorCategory.IRTracc).ToString(); break;
}
}
else { sVal = ((int)SensorInformationFile.TDCSensorCategory.Normal).ToString(); }
}
break;
case CSVImportTags.Tags.ChannelName: sVal = sd.Comment; bTextField = true; break;
case CSVImportTags.Tags.CommentField: sVal = sd.UserValue1; bTextField = true; break;
case CSVImportTags.Tags.CustomerCode: sVal = sd.UserValue2; bTextField = true; break;
case CSVImportTags.Tags.DatabaseReferenceNumber: sVal = currentNumber.ToString(); break;
case CSVImportTags.Tags.DesiredRange: sVal = (sd.Capacity * euConversionFactor).ToString(ec); break;
case CSVImportTags.Tags.Dimension: sVal = ""; break;
case CSVImportTags.Tags.Due: sVal = ""; break;
case CSVImportTags.Tags.DueCal: sVal = sd.GetDueDate(scPreferred).ToString("d", ec); break;
case CSVImportTags.Tags.EquivalentEUShuntResistor: sVal = ""; break;
case CSVImportTags.Tags.EU: sVal = scPreferred.Records.Records.First().EngineeringUnits; bTextField = true; break;
case CSVImportTags.Tags.Exc:
{
try
{
sVal = Test.Module.Channel.Sensor.GetExcitationVoltageMagnitudeFromEnum(sd.SupportedExcitation.First()).ToString(ec);
}
catch
{
sVal = string.Empty;
}
}
break;
case CSVImportTags.Tags.FilterClass: sVal = sd.Filter.GetFilterClassNumericValue().ToString(ec); break;
case CSVImportTags.Tags.FullScale: sVal = (sd.Capacity * euConversionFactor).ToString(ec); break;
case CSVImportTags.Tags.Group1: sVal = ""; break;
case CSVImportTags.Tags.Group2: sVal = ""; break;
case CSVImportTags.Tags.Group3: sVal = ""; break;
case CSVImportTags.Tags.Group4: sVal = ""; break;
case CSVImportTags.Tags.Group5: sVal = ""; break;
case CSVImportTags.Tags.InvertSignal: sVal = sd.Invert ? "Yes" : "No"; break;
case CSVImportTags.Tags.IRTRACCExponent:
{
if (scPreferred.NonLinear && scPreferred.Records.Records.First().Poly.NonLinearStyle != NonLinearStyles.Polynomial)
{
sVal = scPreferred.Records.Records.First().Poly.LinearizationExponent.ToString(ec);
}
else { sVal = ""; }
}
break;
case CSVImportTags.Tags.LaboratoryCode: sVal = sd.UserValue3; bTextField = true; break;
case CSVImportTags.Tags.LastCal: sVal = scPreferred.CalibrationDate.ToString("d", ec); break;
case CSVImportTags.Tags.Location: sVal = ""; break;
case CSVImportTags.Tags.Manufacturer: sVal = sd.Manufacturer ?? ""; bTextField = true; break;
case CSVImportTags.Tags.Model: sVal = sd.Model ?? ""; bTextField = true; break;
case CSVImportTags.Tags.OffsetToleranceHigh: sVal = sd.OffsetToleranceHigh.ToString(ec); break;
case CSVImportTags.Tags.OffsetToleranceLow: sVal = sd.OffsetToleranceLow.ToString(ec); break;
case CSVImportTags.Tags.OutputAtEXCFSmV: sVal = ""; break;
case CSVImportTags.Tags.PartialISOCode: sVal = sd.ISOCode; bTextField = true; break;
case CSVImportTags.Tags.Proportional: sVal = scPreferred.IsProportional ? "Yes" : "No"; break;
case CSVImportTags.Tags.Received: sVal = ""; break;
case CSVImportTags.Tags.RemoveOffset: sVal = scPreferred.RemoveOffset ? "Yes" : "No"; break;
case CSVImportTags.Tags.Sensitivity:
{
sVal = "1";
if (scPreferred.NonLinear)
{
if (scPreferred.Records.Records.First().Poly.NonLinearStyle == NonLinearStyles.Polynomial)
{
sVal = scPreferred.Records.Records.First().Sensitivity.ToString(FormatScientificNotation(), ec);
}
else { sVal = scPreferred.Records.Records.First().Poly.MMPerV.ToString(ec); }
}
else
{
if (scPreferred.IsProportional)
{
foreach (var r in scPreferred.Records.Records)
{
try
{
if (r.Excitation != sd.SupportedExcitation.First()) continue;
sVal = r.Sensitivity.ToString(ec);
break;
}
catch { break; }
}
}
else
{
sVal = scPreferred.Records.Records.First().Sensitivity.ToString(FormatScientificNotation(), ec);
}
}
}
break;
case CSVImportTags.Tags.SensorID: sVal = sd.EID; bTextField = true; break;
case CSVImportTags.Tags.SensorSN: sVal = sd.SerialNumber; bTextField = true; break;
case CSVImportTags.Tags.SensorType: sVal = ""; break;
case CSVImportTags.Tags.ShuntResistorValue: sVal = ""; break;
case CSVImportTags.Tags.SoftwareZeroEquivalentEU:
{
bTextField = true;
sVal = "";
switch (scPreferred.InitialOffsets.Offsets.First().Form)
{
case InitialOffsetTypes.EU:
case InitialOffsetTypes.None:
sVal = (scPreferred.InitialOffsets.Offsets.First().EU * euConversionFactor).ToString(ec);
break;
}
}
break;
case CSVImportTags.Tags.SoftwareZeroReference:
{
switch (scPreferred.ZeroMethods.Methods.First().Method)
{
case ZeroMethodType.AverageOverTime: sVal = ZM_STRING_AVERAGE_OVER_TIME; break;
case ZeroMethodType.None: sVal = ZM_STRING_NONE; break;
case ZeroMethodType.UsePreEventDiagnosticsZero: sVal = ZM_STRING_DIAGNOSTICS; break;
}
}
break;
case CSVImportTags.Tags.Tech: sVal = scPreferred.Username; bTextField = true; break;
case CSVImportTags.Tags.Unknown: break;
case CSVImportTags.Tags.UseShuntCal: sVal = sd.Shunt == ShuntMode.Emulation ? "Yes" : "No"; break;
}
if (null == sVal) { return; } //if we have a null it's not a field we expect to process or know how to process, so we skip those
if (bNeedComma) { sb.Append(listSeparator); }
else { bNeedComma = true; }
sb.Append(EscapeCSV(sVal, bTextField, listSeparator));
}
private void ExportVersion1(SensorData sd, SensorCalibration scPreferred, CSVImportTags.Tags tag, StringBuilder sb,
ref int currentNumber, string listSeparator, ref bool bNeedComma, double euConversionFactor,
CultureInfo ec)
{
}
private void ExportVersion2(SensorData sd, SensorCalibration scPreferred, CSVImportTags.Tags tag, StringBuilder sb,
ref int currentNumber, string listSeparator, ref bool bNeedComma, double euConversionFactor,
CultureInfo ec)
{
string sVal = null;
var bTextField = false;
switch (tag)
{
case CSVImportTags.Tags.FiveVoltExcSensitivity:
{
//if the default calibration is not propoprtional, it covers all supported excitations ...
if (scPreferred.IsProportional && sd.SupportedExcitation.Contains(ExcitationVoltageOptions.ExcitationVoltageOption.Volt5))
{
sVal = GetSensitivity(ExcitationVoltageOptions.ExcitationVoltageOption.Volt5, sd).ToString(ec);
}
else { sVal = ""; }
}
break;
case CSVImportTags.Tags.TenVoltExcSensitivity:
{
if (scPreferred.IsProportional && sd.SupportedExcitation.Contains(ExcitationVoltageOptions.ExcitationVoltageOption.Volt10))
{
sVal = GetSensitivity(ExcitationVoltageOptions.ExcitationVoltageOption.Volt10, sd).ToString(ec);
}
else { sVal = ""; }
}
break;
case CSVImportTags.Tags.TwoVoltExcSensitivity:
{
if (scPreferred.IsProportional && sd.SupportedExcitation.Contains(ExcitationVoltageOptions.ExcitationVoltageOption.Volt2))
{
sVal = GetSensitivity(ExcitationVoltageOptions.ExcitationVoltageOption.Volt2, sd).ToString(ec);
}
else { sVal = ""; }
}
break;
case CSVImportTags.Tags.Unknown: break;
case CSVImportTags.Tags.AxisNumber: sVal = sd.AxisNumber.ToString(); break;
case CSVImportTags.Tags.BridgeLegMode: sVal = sd.BridgeLegMode.ToString(); bTextField = true; break;
case CSVImportTags.Tags.BridgeType: sVal = sd.Bridge.ToString(); bTextField = true; break;
case CSVImportTags.Tags.CalInterval: sVal = sd.CalInterval.ToString(); break;
case CSVImportTags.Tags.CouplingMode: sVal = sd.CouplingMode.ToString(); bTextField = true; break;
case CSVImportTags.Tags.Created: sVal = sd.Created.ToString("F", ec); break;
case CSVImportTags.Tags.DelayMS: sVal = sd.DelayMS.ToString(ec); break;
case CSVImportTags.Tags.DigitalOutputDelayMS: sVal = sd.DigitalOutputDelayMS.ToString(ec); break;
case CSVImportTags.Tags.DigitalInputMode: sVal = sd.InputMode.ToString(); bTextField = true; break;
case CSVImportTags.Tags.NonLinearCalibration: sVal = scPreferred.Records.Records.First().Poly.ToSerializeString(); bTextField = true; break;
case CSVImportTags.Tags.AdditionalInitialOffsets:
if (scPreferred.InitialOffsets.Offsets.Length > 1)
{
var additionalInitalOffsets = new InitialOffsets(scPreferred.InitialOffsets, scPreferred.InitialOffsets.Offsets.Length - 1);
sVal = additionalInitalOffsets.ToSerializedString();
bTextField = true;
}
else
{
sVal = string.Empty; //In case there is no additional Initial Offsets
}
break;
case CSVImportTags.Tags.AdditionalLinearSensitivity:
if (scPreferred.NonLinear && scPreferred.LinearAdded)
{
if (scPreferred.IsProportional)
{
foreach (var r in scPreferred.LinearAddedRecords)
{
try
{
if (r.Excitation != sd.SupportedExcitation.First()) continue;
sVal = r.Sensitivity.ToString(ec);
break;
}
catch { break; }
}
}
else
{
sVal = scPreferred.LinearAddedSensitivity.ToString(ec);
}
bTextField = true;
}
else
{
sVal = string.Empty; //In case there is no additional linear calibration (Sensitivity)
}
break;
case CSVImportTags.Tags.AdditionalLinearZeroMethod:
if (scPreferred.NonLinear && scPreferred.LinearAdded)
{
sVal = scPreferred.LinearAddedZeroMethodType.ToString();
bTextField = true;
}
else
{
sVal = string.Empty; //In case there is no additional linear calibration (Zero Method)
}
break;
case CSVImportTags.Tags.AdditionalLinearZeroMethodEnd:
if (scPreferred.NonLinear && scPreferred.LinearAdded &&
scPreferred.LinearAddedZeroMethodType == ZeroMethodType.AverageOverTime)
{
sVal = scPreferred.LinearAddedZeroMethodEnd.ToString(ec);
}
else
{
sVal = string.Empty; //In case there is no additional linear calibration (Zero Method End)
}
break;
case CSVImportTags.Tags.AdditionalLinearZeroMethodStart:
if (scPreferred.NonLinear && scPreferred.LinearAdded &&
scPreferred.LinearAddedZeroMethodType == ZeroMethodType.AverageOverTime)
{
sVal = scPreferred.LinearAddedZeroMethodStart.ToString(ec);
}
else
{
sVal = string.Empty; //In case there is no additional linear calibration (Zero Method Start)
}
break;
case CSVImportTags.Tags.DigitalOutputMode: sVal = sd.DigitalOutputMode.ToString(); bTextField = true; break;
case CSVImportTags.Tags.DigitalScaleMultiplier: sVal = sd.ScaleMultiplier.ToSerializeDbString(); bTextField = true; break;
case CSVImportTags.Tags.Direction: sVal = sd.Direction; bTextField = true; break;
case CSVImportTags.Tags.DisplayUnits: sVal = sd.DisplayUnit; bTextField = true; break;
case CSVImportTags.Tags.DurationMS: sVal = sd.DurationMS.ToString(ec); break;
case CSVImportTags.Tags.DigitalOutputDurationMS: sVal = sd.DigitalOutputDurationMS.ToString(ec); break;
case CSVImportTags.Tags.InitialOffset:
{
// Need to export nominal EU here. Scale the EU of the InitialOffset before Serializing to String
scPreferred.InitialOffsets.Offsets.First().EU = scPreferred.InitialOffsets.Offsets.First().EU * euConversionFactor;
sVal = scPreferred.InitialOffsets.Offsets.First().ToDbSerializeString();
bTextField = true;
break;
}
case CSVImportTags.Tags.LimitDuration: sVal = sd.LimitDuration ? "Yes" : "No"; break;
case CSVImportTags.Tags.NonLinear: sVal = scPreferred.NonLinear ? "Yes" : "No"; break;
case CSVImportTags.Tags.NumberOfAxes: sVal = sd.NumberOfAxes.ToString(); break;
case CSVImportTags.Tags.PhysicalDimension: sVal = sd.PhysicalDimension; bTextField = true; break;
case CSVImportTags.Tags.Polarity: sVal = sd.Polarity; bTextField = true; break;
case CSVImportTags.Tags.RangeHigh: sVal = (sd.RangeHigh * euConversionFactor).ToString(ec); break;
case CSVImportTags.Tags.RangeLow: sVal = (sd.RangeLow * euConversionFactor).ToString(ec); break;
case CSVImportTags.Tags.RangeMedium: sVal = (sd.RangeMedium * euConversionFactor).ToString(ec); break;
case CSVImportTags.Tags.SquibFireMode: sVal = sd.SquibFireMode.ToString(); bTextField = true; break;
case CSVImportTags.Tags.SquibMeasurementType: sVal = sd.SquibMeasurementType.ToString(); bTextField = true; break;
case CSVImportTags.Tags.SquibOutputCurrent: sVal = sd.SquibOutputCurrent.ToString(ec); break;
case CSVImportTags.Tags.SupportedExcitation: sVal = sd.GetSerializedSupportedExcitation(); bTextField = true; break;
case CSVImportTags.Tags.TimesUsed: sVal = sd.TimesUsed.ToString(); break;
case CSVImportTags.Tags.Unipolar: sVal = sd.UniPolar ? "Yes" : "No"; break;
case CSVImportTags.Tags.ZeroMethod: sVal = scPreferred.ZeroMethods.Methods.First().Method.ToString(); bTextField = true; break;//TODO: linear/nonlinear
case CSVImportTags.Tags.ZeroMethodEnd: sVal = scPreferred.ZeroMethods.Methods.First().End.ToString(ec); break;
case CSVImportTags.Tags.ZeroMethodStart: sVal = scPreferred.ZeroMethods.Methods.First().Start.ToString(ec); break;
case CSVImportTags.Tags.CapacityOutputIsBasedOn: sVal = scPreferred.Records.Records.First().CapacityOutputIsBasedOn.ToString("N3"); break;
case CSVImportTags.Tags.AtCapacity: sVal = scPreferred.Records.Records.First().AtCapacity ? "Yes" : "No"; break;
case CSVImportTags.Tags.SensitivityUnits: sVal = scPreferred.Records.Records.First().SensitivityUnits.ToString(); break;
case CSVImportTags.Tags.CheckOffset: sVal = sd.CheckOffset ? "Yes" : "No"; break;
case CSVImportTags.Tags.Broken: sVal = sd.Broken ? "Yes" : "No"; break;
case CSVImportTags.Tags.DoNotUse: sVal = sd.DoNotUse ? "Yes" : "No"; break;
case CSVImportTags.Tags.ISOChannelName: sVal = sd.ISOChannelName; break;
case CSVImportTags.Tags.UserCode: sVal = sd.UserCode; break;
case CSVImportTags.Tags.UserChannelName: sVal = sd.UserChannelName; break;
default: sVal = ""; break;
}
if (null == sVal) { return; }
if (bNeedComma) { sb.Append(listSeparator); }
else { bNeedComma = true; }
sb.Append(EscapeCSV(sVal, bTextField, listSeparator));
}
private void ExportVersion3(SensorData sd, SensorCalibration scPreferred, CSVImportTags.Tags tag, StringBuilder sb,
ref int currentNumber, string listSeparator, ref bool bNeedComma, double euConversionFactor,
CultureInfo ec)
{
}
private void ExportVersion4(SensorData sd, SensorCalibration scPreferred, CSVImportTags.Tags tag, StringBuilder sb,
ref int currentNumber, string listSeparator, ref bool bNeedComma, double euConversionFactor,
CultureInfo ec)
{
var sVal = string.Empty;
var bTextField = false;
switch (tag)
{
case CSVImportTags.Tags.DASSerialNumber: break;
case CSVImportTags.Tags.DASChannelIndex: break;
case CSVImportTags.Tags.StreamProfile:
sVal = sd.StreamOutUDPProfile.ToString();
bTextField = true;
break;
case CSVImportTags.Tags.UDPAddress:
if (sd.IsStreamInput()) { sVal = sd.StreamInUDPAddress; }
else { sVal = sd.StreamOutUDPAddress; }
bTextField = true;
break;
case CSVImportTags.Tags.TimeChannelId:
sVal = sd.StreamOutUDPTimeChannelId.ToString(ec);
break;
case CSVImportTags.Tags.DataChannelId:
sVal = sd.StreamOutUDPDataChannelId.ToString(ec);
break;
case CSVImportTags.Tags.TmNSConfig:
sVal = sd.StreamOutUDPTmNSConfig;
bTextField = true;
break;
case CSVImportTags.Tags.IRIGTimeDataPacketIntervalMS:
sVal = sd.StreamOutIRIGTimeDataPacketIntervalMs.ToString(ec);
break;
case CSVImportTags.Tags.TMATSIntervalMS:
sVal = sd.StreamOutTMATSIntervalMs.ToString(ec);
break;
case CSVImportTags.Tags.BaudRate:
sVal = sd.UartBaudRate.ToString(ec);
bTextField = true;
break;
case CSVImportTags.Tags.DataBits:
sVal = sd.UartDataBits.ToString(ec);
bTextField = true;
break;
case CSVImportTags.Tags.StopBits:
sVal = sd.UartStopBits.ToString();
bTextField = true;
break;
case CSVImportTags.Tags.Parity:
sVal = sd.UartParity.ToString();
bTextField = true;
break;
case CSVImportTags.Tags.DataFormat:
sVal = sd.UartDataFormat.ToString();
bTextField = true;
break;
case CSVImportTags.Tags.TestUserCode:
sVal = sd.UserCode.ToString();
bTextField = true;
break;
case CSVImportTags.Tags.TestUserChannelName:
sVal = sd.UserChannelName.ToString();
bTextField = true;
break;
case CSVImportTags.Tags.TestIsoCode:
sVal = sd.ISOCode.ToString();
bTextField = true;
break;
case CSVImportTags.Tags.TestIsoChannelName:
sVal = sd.ISOChannelName.ToString();
bTextField = true;
break;
}
if (bNeedComma) { sb.Append(listSeparator); }
else { bNeedComma = true; }
sb.Append(EscapeCSV(sVal, bTextField, listSeparator));
}
private void ExportTag(SensorData sd, SensorCalibration scPreferred, CSVImportTags.Tags tag, StringBuilder sb,
ref int currentNumber, string listSeparator, ref bool bNeedComma, double euConversionFactor, int version,
CultureInfo ec)
{
switch (version)
{
case 0: ExportVersion0(sd, scPreferred, tag, sb, ref currentNumber, listSeparator, ref bNeedComma, euConversionFactor, ec); break;
case 1: ExportVersion1(sd, scPreferred, tag, sb, ref currentNumber, listSeparator, ref bNeedComma, euConversionFactor, ec); break;
case 2: ExportVersion2(sd, scPreferred, tag, sb, ref currentNumber, listSeparator, ref bNeedComma, euConversionFactor, ec); break;
case 3: ExportVersion3(sd, scPreferred, tag, sb, ref currentNumber, listSeparator, ref bNeedComma, euConversionFactor, ec); break;
case 4: ExportVersion4(sd, scPreferred, tag, sb, ref currentNumber, listSeparator, ref bNeedComma, euConversionFactor, ec); break;
}
}
//FB 29478 Format that is used by ToString method from Scientific Notation if applicable to double
//https://stackoverflow.com/questions/1546113/double-to-string-conversion-without-scientific-notation/36204442#36204442
private string FormatScientificNotation() => $"0.{new string('#', 339)}";
// ReSharper disable once InconsistentNaming
public void ExportDataPRO()
{
try
{
if (!Directory.Exists(DefaultFolder))
{
Directory.CreateDirectory(DefaultFolder);
}
var di = new DirectoryInfo(DefaultFolder);
var fullPathToDataProFile = Path.Combine(di.FullName, DefaultFile);
if (File.Exists(fullPathToDataProFile)) { BackupAndDelete(fullPathToDataProFile); }
var includedSensors = new Dictionary<string, SensorData>();
var includedSensorModels = new Dictionary<string, SensorModel>();
var includedCalibrations = new Dictionary<string, SensorCalibration[]>();
var includedChanges = new Dictionary<SensorData, List<ISensorChange>>();
foreach (var sd in Sensors)
{
var scs = SensorCalibration.GetCalibrationsBySerialNumber(sd);
if (null == sd) { continue; }
var analog = sd.IsAnalog();
if (analog && (null == scs || !scs.Any())) { continue; }
if (null != scs && scs.Any())
{
includedCalibrations.Add(sd.SerialNumber, scs);
}
if (analog && sd.SupportedExcitation.Length == 0)
{
//Set supported excitation(s) based on any found calibration records
var supportedExcitationList = new List<ExcitationVoltageOptions.ExcitationVoltageOption>();
for (var i = 0; i < scs.Length; i++)
{
foreach (var calRecord in scs[i].Records.Records)
{
if (!supportedExcitationList.Contains(calRecord.Excitation))
{
supportedExcitationList.Add(calRecord.Excitation);
}
}
}
sd.SupportedExcitation = supportedExcitationList.ToArray();
}
includedSensors.Add(sd.SerialNumber, sd);
includedChanges.Add(sd, new List<ISensorChange>());
includedChanges[sd].AddRange(SensorChangeTypeHelper.GetAllSensorChanges(sd));
if (!analog) { continue; } //only care about sensormodel for analog sensors ..
var sm = SensorModelCollection.SensorModelList.GetSensorModel(sd.Manufacturer, sd.Model);
if (null == sm) continue;
var key = $"{sm.Manufacturer}x_x{sm.Model}";
if (!includedSensorModels.ContainsKey(key))
{
includedSensorModels.Add(key, sm);
}
}
ExportToFile(includedSensors, includedSensorModels, includedCalibrations, fullPathToDataProFile, includedChanges, ExportFirstUseDate);
OnDone?.Invoke();
}
catch (Exception ex)
{
OnError?.Invoke(ex.Message); APILogger.Log(ex);
}
}
public void ExportEQX()
{
try
{
if (!Directory.Exists(DefaultFolder))
{
Directory.CreateDirectory(DefaultFolder);
}
var di = new DirectoryInfo(DefaultFolder);
var fullPathToDataProFile = Path.Combine(di.FullName, DefaultFile);
if (File.Exists(fullPathToDataProFile)) { BackupAndDelete(fullPathToDataProFile); }
var includedSensors = new Dictionary<string, SensorData>();
var includedCalibrations = new Dictionary<string, SensorCalibration[]>();
foreach (var sd in Sensors)
{
var scs = SensorCalibration.GetCalibrationsBySerialNumber(sd);
var sc = SensorCalibration.GetLatestCalibrationBySerialNumber(sd);
if (null == sd
|| null == scs
|| null == sc
|| scs.Length <= 0
|| sd.IsDigitalOutput()
|| (sc.NonLinear &&
sc.IRTraccCalculationType != NonLinearStyles.Polynomial &&
sc.IRTraccCalculationType != NonLinearStyles.IRTraccCalFactor &&
sc.IRTraccCalculationType != NonLinearStyles.IRTraccAverageOverTime)
//also export these types of non linear sensors?
//http://manuscript.dts.local/f/cases/edit/36885/EQX-requested-changes
)
{
//Should warn user that these sensors will not be exported
continue;
}
includedCalibrations.Add(sd.SerialNumber, scs);
if (sd.SupportedExcitation.Length == 0)
{
//Set supported excitation(s) based on any found calibration records
var supportedExcitationList = new List<ExcitationVoltageOptions.ExcitationVoltageOption>();
for (var i = 0; i < scs.Length; i++)
{
foreach (var calRecord in scs[i].Records.Records)
{
if (!supportedExcitationList.Contains(calRecord.Excitation))
{
supportedExcitationList.Add(calRecord.Excitation);
}
}
}
sd.SupportedExcitation = supportedExcitationList.ToArray();
}
includedSensors.Add(sd.SerialNumber, sd);
}
ExportToEQXFile(includedSensors, includedCalibrations, fullPathToDataProFile);
OnDone?.Invoke();
}
catch (Exception ex)
{
OnError?.Invoke(ex.Message); APILogger.Log(ex);
}
}
private static void ExportToEQXFile(Dictionary<string, SensorData> includedSensors,
Dictionary<string, SensorCalibration[]> includedCalibration,
string exportFile)
{
EqxSensors eqxSensors = GetEQXSensorsFromSensorData(includedSensors, includedCalibration);
if (null == eqxSensors)
{
return;
}
var serializer = new XmlSerializer(typeof(EqxSensors));
if (File.Exists(exportFile))
{
File.Delete(exportFile);
}
using (var fs = new FileStream(exportFile, FileMode.CreateNew))
{
using (var writer = new StreamWriter(fs, Encoding.Unicode))
{
serializer.Serialize(writer, eqxSensors);
writer.Close();
}
fs.Close();
}
}
private static EqxSensors GetEQXSensorsFromSensorData(Dictionary<string, SensorData> includedSensors, Dictionary<string, SensorCalibration[]> includedCalibration)
{
EqxSensors rv = new EqxSensors();
List<EqxSensorGroup> eqxSensors = new List<EqxSensorGroup>();
foreach (var kvp in includedSensors)
{
var sd = kvp.Value as SensorData;
var scList = includedCalibration[kvp.Key];
// 34420: Add case for equal cal dates, to be then ordered by modify date
var sc = scList.Aggregate((curMax, x) => curMax == null ||
x.CalibrationDate > curMax.CalibrationDate ||
(x.CalibrationDate == curMax.CalibrationDate && x.ModifyDate > curMax.ModifyDate)
? x
: curMax);
var eqxScalingMethod = GetEqxScalingMethod(sc);
var eqxSensor = new EqxSensorGroup
{
Name = kvp.Value.SerialNumber,
SerialNumber = kvp.Value.SerialNumber,
UUID = string.IsNullOrEmpty(kvp.Value.UUID) ? Guid.NewGuid().ToString() : kvp.Value.UUID,
CalDate = includedCalibration[kvp.Key].First().CalibrationDate,
Sensor = new EqxSensorAxis[1]
{
GetSensorAxis(sd, scList, sc, eqxScalingMethod)
},
};
SetSensitivities(sc, eqxSensor.Sensor[0], eqxScalingMethod);
eqxSensors.Add(eqxSensor);
}
rv.SensorGroup = eqxSensors.ToArray();
rv.FileInfo = new EqxFileInfo();
rv.FileInfo.DataFormatEdition = "1.5";
rv.FileInfo.Software = System.Reflection.Assembly.GetEntryAssembly().GetName().Name;
rv.FileInfo.SoftwareVersion = System.Reflection.Assembly.GetEntryAssembly().GetName().Version.ToString(4);
rv.FileInfo.CreationTime = DateTime.UtcNow;
return rv;
}
private static EqxSensorAxis GetSensorAxis(SensorData sd, SensorCalibration [] scList, SensorCalibration sc, EqxScalingMethod eqxScalingMethod)
{
var eqx = new EqxSensorAxis
{
Name = sd.SerialNumber,
ElectricalMethodSpecified = true,
ElectricalMethod = GetEqxElectricalMethod(sd.Bridge, scList.First().IsProportional),
SWFilterClassTypeSpecified = true,
SWFilterClassType = GetEqxSWFilterCLass(sd.FilterClass.FClass),
Supplier = sd.Manufacturer,
PhysicalUnit = sd.DisplayUnit,
Model = sd.Model,
IDModuleString = sd.EID,
LocationCode = sd.ISOCode,
LocationLongname = sd.ISOChannelName,
};
if (sd.IsDigitalInput()) { SetDigitalInputProperties(eqx, sd); }
else if (sd.IsSquib()) { SetSquibProperties(eqx, sd); }
else if (sd.IsAnalog()) { SetAnalogProperties(eqx, sd, eqxScalingMethod, sc); }
return eqx;
}
private static void SetAnalogProperties(EqxSensorAxis eqx, SensorData sd, EqxScalingMethod eqxScalingMethod,
SensorCalibration sc)
{
eqx.ShuntResistanceSpecified = true;
eqx.ShuntResistance = (float)sd.BridgeResistance;
eqx.OffsetTolSpecified = true;
eqx.OffsetTol = (float)sd.OffsetToleranceHigh;
eqx.CalDateSpecified = true;
eqx.CalDate = sc.CalibrationDate;
eqx.CalPeriodSpecified = true;
eqx.CalPeriod = sd.CalInterval;
eqx.CalPerson = sc.Username;
eqx.MaxRangeSpecified = true;
eqx.MaxRange = (float)sd.RangeHigh;
eqx.MinRangeSpecified = true;
eqx.MinRange = (float)sd.RangeLow;
eqx.PreferredRangeSpecified = true;
eqx.PreferredRange = (float)sd.Capacity;
eqx.OffsetCheckSpecified = true;
eqx.OffsetCheck = sd.CheckOffset;
eqx.Remark = sd.Comment;
eqx.CompanyLongname = sd.Manufacturer;
eqx.ExcitationVoltageSpecified = true;
eqx.ExcitationVoltage = (float)Test.Module.Channel.Sensor.GetExcitationVoltageMagnitudeFromEnum(sc.Records.Records.First().Excitation);
eqx.SensitivityVoltageSpecified = true;
eqx.SensitivityVoltage = sc.IsProportional ? 1F : (float)Test.Module.Channel.Sensor.GetExcitationVoltageMagnitudeFromEnum(sc.Records.Records.First().Excitation);
eqx.SWOffsetFixValueSpecified = true;
eqx.SWOffsetFixValue = (float)sc.InitialOffsets.Offsets.First().EU;
eqx.MountingPolaritySpecified = true;
eqx.MountingPolarity = sd.Invert ? EqxPolarity.Negative : EqxPolarity.Positive;
eqx.OffsetCompensationSpecified = true;
eqx.OffsetCompensation = sc.RemoveOffset;
eqx.ScalingMethodSpecified = true;
eqx.ScalingMethod = eqxScalingMethod;
eqx.SerialNumber = sd.SerialNumber;
eqx.ShuntCheckPosSpecified = true;
eqx.ShuntCheckPos = sd.PerformShuntEmulation;
eqx.SWOffsetCompensation = sc.RemoveOffset;
eqx.SWOffsetCompensationSpecified = true;
eqx.SWOffsetCompensationType = GetSWOffsetCompensationType(sc.ZeroMethods.Methods[0].Method);
eqx.SWOffsetCompensationSpecified = true;
if (sc.ZeroMethods.Methods[0].Method == ZeroMethodType.AverageOverTime)
{
eqx.SWOffsetCalculationStartSec = Convert.ToSingle(sc.ZeroMethods.Methods[0].Start);
eqx.SWOffsetCalculationStartSecSpecified = true;
eqx.SWOffsetCalculationEndSec = Convert.ToSingle(sc.ZeroMethods.Methods[0].End);
eqx.SWOffsetCalculationEndSecSpecified = true;
}
}
private static EqxSWOffsetCompensationType GetSWOffsetCompensationType(ZeroMethodType zeroMethod)
{
switch(zeroMethod)
{
case ZeroMethodType.AverageOverTime: return EqxSWOffsetCompensationType.Averagecalculation;
case ZeroMethodType.None: return EqxSWOffsetCompensationType.Fixvalue;
case ZeroMethodType.UsePreEventDiagnosticsZero: return EqxSWOffsetCompensationType.Pretestmeasurement;
default: throw new InvalidCastException($"Invalid offset type: {zeroMethod}");
}
}
private static void SetDigitalInputProperties(EqxSensorAxis eqx, SensorData sd)
{
eqx.InputMode = GetEqxInputMode(sd.InputMode);
eqx.InputModeSpecified = true;
}
private static void SetSquibProperties(EqxSensorAxis eqx, SensorData sd)
{
eqx.FiringModeSpecified = true;
eqx.FiringMode = GetEqxFiringMode(sd.SquibFireMode);
eqx.FiringDelaySpecified = true;
eqx.FiringDelay = Convert.ToSingle(sd.SquibFireDelayMS);
if (sd.LimitSquibFireDuration)
{
eqx.FiringDurationSpecified = true;
eqx.FiringDuration = Convert.ToInt32(sd.SquibFireDurationMS*1000);
}
eqx.FiringVoltageLimitSpecified = false;
eqx.FiringCurrentLimitSpecified = true;
eqx.FiringCurrentLimit = Convert.ToInt32(sd.SquibOutputCurrent*1000);
}
private static EqxFiringMode GetEqxFiringMode(SquibFireMode mode)
{
switch(mode)
{
case SquibFireMode.AC: return EqxFiringMode.AC;
case SquibFireMode.CAP: return EqxFiringMode.CapacitorDischarge;
case SquibFireMode.CONSTANT: return EqxFiringMode.ConstantCurrent;
case SquibFireMode.NONE: return EqxFiringMode.CapacitorDischarge;
default: throw new InvalidCastException($"Unknown squib firing mode: {mode}");
}
}
private static EqxInputModes GetEqxInputMode(DigitalInputModes inputMode)
{
switch(inputMode)
{
case DigitalInputModes.CCNC: return EqxInputModes.CCNC;
case DigitalInputModes.CCNO: return EqxInputModes.CCNO;
case DigitalInputModes.THL: return EqxInputModes.THL;
case DigitalInputModes.TLH: return EqxInputModes.TLH;
default: throw new InvalidCastException($"Unknown input mode: {inputMode}");
}
}
private static void SetSensitivities(SensorCalibration sc, EqxSensorAxis axis, EqxScalingMethod eqxScalingMethod)
{
if (eqxScalingMethod == EqxScalingMethod.IRTRACC)
{
if (sc.IRTraccCalculationType == NonLinearStyles.IRTraccCalFactor)
{
SetSensitivitiesCalFactor(sc, axis, eqxScalingMethod);
}
else
{
SetSensitivitiesIRTRACC(sc, axis);
}
}
else { SetSensitivitiesAllTheRest(sc, axis, eqxScalingMethod); }
}
private static void SetSensitivitiesIRTRACC(SensorCalibration sc, EqxSensorAxis axis)
{
var record = sc.Records.Records.FirstOrDefault();
if (null == record)
{
return;
}
//set EU to mV, set Sensitivity1 to 1000/Cal factor, set sensitivity 3 to (Intercept/calfactor)*1000?
axis.Sensitivity = Convert.ToSingle(record.Poly.MMPerV);
axis.SensitivitySpecified = true;
axis.Sensitivity2Specified = true;
axis.Sensitivity2 = record.Poly.LinearizationExponent;
axis.EngineeringUnit = EqxEngineeringUnit.mV;
}
private static void SetSensitivitiesCalFactor(SensorCalibration sc, EqxSensorAxis axis, EqxScalingMethod eqxScalingMethod)
{
var record = sc.Records.Records.FirstOrDefault();
if (null == record)
{
return;
}
//set EU to mV, set Sensitivity1 to 1000/Cal factor, set sensitivity 3 to (Intercept/calfactor)*1000?
axis.Sensitivity = Convert.ToSingle(1000D / record.Poly.CalibrationFactor);
axis.SensitivitySpecified = true;
axis.Sensitivity2Specified = true;
axis.Sensitivity2 = record.Poly.LinearizationExponent;
axis.Sensitivity3Specified = true;
axis.Sensitivity3 = 1000D * (record.Poly.ZeroPositionIntercept / record.Poly.CalibrationFactor);
axis.Sensitivity4Specified = false;
axis.Sensitivity5Specified = false;
axis.EngineeringUnit = EqxEngineeringUnit.mV;
}
private static void SetSensitivitiesAllTheRest(SensorCalibration sc, EqxSensorAxis axis, EqxScalingMethod eqxScalingMethod)
{
axis.SensitivitySpecified = true;
axis.Sensitivity = (float)GetEqxSensitivity(sc, 1);
axis.Sensitivity2Specified = eqxScalingMethod != EqxScalingMethod.Linear;
axis.Sensitivity2 = GetEqxSensitivity(sc, 2);
axis.Sensitivity3Specified = eqxScalingMethod != EqxScalingMethod.Linear;
axis.Sensitivity3 = GetEqxSensitivity(sc, 3);
axis.Sensitivity4Specified = eqxScalingMethod == EqxScalingMethod.CubicPolynomial;
axis.Sensitivity4 = GetEqxSensitivity(sc, 4);
axis.Sensitivity5Specified = eqxScalingMethod == EqxScalingMethod.CubicPolynomial;
axis.Sensitivity5 = GetEqxSensitivity(sc, 5);
}
private static double GetEqxSensitivity(SensorCalibration sc, int sensitivityIndex)
{
if (sensitivityIndex > 5) { return 0D; }
switch (GetEqxScalingMethod(sc))
{
case EqxScalingMethod.CubicPolynomial:
return sensitivityIndex >= 2 ? sc.Records.Records.First().Poly.PolynomialCoefficients[sensitivityIndex - 2] : 0D;
case EqxScalingMethod.IRTRACC:
case EqxScalingMethod.Linear:
return sc.Records.Records.First().Sensitivity;
default:
return 0D;
}
}
private static EqxScalingMethod GetEqxScalingMethod(SensorCalibration sc)
{
if (sc.NonLinear)
{
if (sc.Records.Records.First().Poly.NonLinearStyle == NonLinearStyles.Polynomial)
{
return EqxScalingMethod.CubicPolynomial;
}
return EqxScalingMethod.IRTRACC;
}
return EqxScalingMethod.Linear;
}
private static EqxFilterClassType GetEqxSWFilterCLass(FilterClassType filterClass)
{
switch (filterClass)
{
case FilterClassType.CFC10:
return EqxFilterClassType.CFC10;
case FilterClassType.CFC1000:
return EqxFilterClassType.CFC1000;
case FilterClassType.CFC180:
return EqxFilterClassType.CFC180;
case FilterClassType.CFC60:
return EqxFilterClassType.CFC60;
case FilterClassType.CFC600:
return EqxFilterClassType.CFC600;
case FilterClassType.AdHoc:
return EqxFilterClassType.AdHoc;
default:
return EqxFilterClassType.None;
}
}
private static EqxElectricalMethod GetEqxElectricalMethod(SensorConstants.BridgeType bridgeType, bool isProportional)
{
switch (bridgeType)
{
case SensorConstants.BridgeType.IEPE:
return EqxElectricalMethod.PiezoInput;
case SensorConstants.BridgeType.FullBridge:
if (isProportional)
{
return EqxElectricalMethod.FullBridge;
}
else
{
return EqxElectricalMethod.ActiveSensor;
}
case SensorConstants.BridgeType.HalfBridge:
if (isProportional)
{
return EqxElectricalMethod.HalfBridge;
}
else
{
return EqxElectricalMethod.HalfBridgeActive;
}
case SensorConstants.BridgeType.QuarterBridge:
if (isProportional)
{
return EqxElectricalMethod.QuarterBridge;
}
else
{
return EqxElectricalMethod.QuarterBridgeActive;
}
default:
return EqxElectricalMethod.FullBridge;
}
}
private static void ExportToFile(Dictionary<string, SensorData> includedSensors,
Dictionary<string, SensorModel> includedSensorModels,
Dictionary<string, SensorCalibration[]> includedCalibration,
string exportFile,
Dictionary<SensorData, List<ISensorChange>> includedChanges = null,
bool bExportFirstUseDate = true)
{
using (TextWriter writer = new StreamWriter(exportFile, false))
{
XmlWriter xmlWriter = null;
try
{
xmlWriter = new XmlTextWriter(writer);
xmlWriter.WriteStartDocument();
xmlWriter.WriteStartElement("ExportFile");
xmlWriter.WriteAttributeString("Version", FileUtils.CurrentXmlVersion.ToString(CultureInfo.InvariantCulture));
var fields = Enum.GetValues(typeof(TopLevelFields)).Cast<TopLevelFields>().ToArray();
foreach (var f in fields)
{
switch (f)
{
case TopLevelFields.Calibrations:
if (includedCalibration.Count > 0)
{
xmlWriter.WriteStartElement(f.ToString());
foreach (var c in includedCalibration)
{
foreach (var sc in c.Value) { xmlWriter.Flush(); sc.WriteXML(ref xmlWriter); xmlWriter.Flush(); }
}
xmlWriter.WriteEndElement();
}
break;
case TopLevelFields.SensorModels:
if (includedSensorModels.Count > 0)
{
xmlWriter.WriteStartElement(f.ToString());
foreach (var sm in includedSensorModels)
{
xmlWriter.Flush(); sm.Value.WriteXML(ref xmlWriter); xmlWriter.Flush();
}
xmlWriter.WriteEndElement();
}
break;
case TopLevelFields.Sensors:
if (includedSensors.Count > 0)
{
xmlWriter.Flush();
xmlWriter.WriteStartElement(f.ToString());
foreach (var sd in includedSensors)
{
xmlWriter.Flush(); sd.Value.WriteXML(ref xmlWriter, bExportFirstUseDate); xmlWriter.Flush();
}
xmlWriter.WriteEndElement();
}
break;
case TopLevelFields.SensorChangeHistory:
if (null != includedChanges && includedChanges.Any())
{
WriteIncludedChanges(ref xmlWriter, includedChanges);
}
break;
}
}
xmlWriter.WriteEndElement();
}
finally
{
if (xmlWriter != null)
{
xmlWriter.Flush();
xmlWriter.Close();
}
}
}
}
/// <summary>
/// writes all sensor changes to xml
/// </summary>
private static void WriteIncludedChanges(ref XmlWriter writer,
IReadOnlyDictionary<SensorData, List<ISensorChange>> changes)
{
writer.WriteStartElement("SensorChangeHistory");
using (var eSensor = changes.GetEnumerator())
{
while (eSensor.MoveNext())
{
WriteIncludedChanges(ref writer, eSensor.Current.Key, eSensor.Current.Value);
}
}
writer.WriteEndElement();
}
/// <summary>
/// writes all changes for a given sensor to xml
/// </summary>
private static void WriteIncludedChanges(ref XmlWriter writer, ISensorData sensor,
IReadOnlyList<ISensorChange> changes)
{
writer.WriteStartElement("Sensor");
writer.WriteAttributeString("SensorId", sensor.DatabaseId.ToString(CultureInfo.InvariantCulture));
foreach (var change in changes)
{
writer.WriteStartElement("Change");
writer.WriteAttributeString("RecordId", change.RecordId.ToString(CultureInfo.InvariantCulture));
writer.WriteAttributeString("ChangeType", change.ChangeType.ToString());
writer.WriteAttributeString("Timestamp", change.TimeStamp.ToString("O", CultureInfo.InvariantCulture));
writer.WriteAttributeString("Username", change.UserName);
writer.WriteAttributeString("Value1", change.Value1);
writer.WriteAttributeString("Value2", change.Value2);
writer.WriteAttributeString("Value3", change.Value3);
writer.WriteAttributeString("Value4", change.Value4);
writer.WriteEndElement();
}
writer.WriteEndElement();
}
public const string ZM_STRING_AVERAGE_OVER_TIME = "Avg";
public const string ZM_STRING_DIAGNOSTICS = "Pre";
public const string ZM_STRING_NONE = "0mV";
/// <summary>
/// gets the latest sensitivity (as a double) for a given sensor and an excitation
/// </summary>
/// <param name="option"></param>
/// <param name="sd"></param>
/// <returns></returns>
private double GetSensitivity(ExcitationVoltageOptions.ExcitationVoltageOption option, SensorData sd)
{
var sc = SensorCalibration.GetLatestCalibrationBySerialNumberAndExcitation(sd, option);
if (null == sc) return double.NaN;
foreach (var r in sc.Records.Records)
{
if (r.Excitation == option) { return r.Sensitivity; }
}
return double.NaN;
}
/// <summary>
/// escape a string to CSV friendly string
/// </summary>
/// <param name="str"></param>
/// <param name="bTextField">whether the field is intended to be text and may need to be escaped (example, sensor serial of 6-1)</param>
/// <param name="listSeparator"></param>
/// <returns></returns>
public static string EscapeCSV(string str, bool bTextField, string listSeparator)
{
//find out if we need to escape string, if so
var quote = str.Contains(listSeparator) || str.Contains("\"") || str.Contains("\r") || str.Contains("\n");
if (!string.IsNullOrWhiteSpace(str) && bTextField &&
char.IsDigit(str[0]))
{
quote = true;
}
if (!quote) return str;
//then go character by character escaping as you go
var sb = new StringBuilder();
sb.Append("\"");
foreach (var nextChar in str)
{
sb.Append(nextChar);
if (nextChar == '"') { sb.Append("\""); }
}
sb.Append("\"");
str = sb.ToString();
return str;
}
public static string UnEscapeCSV(string str)
{
if (!str.StartsWith("\"") || !str.EndsWith("\""))
{
return str;
}
str = str.Substring(1, str.Length - 2);
var sb = new StringBuilder();
for (var i = 0; i < str.Length; i++)
{
var c = str[i];
if (i == str.Length - 2) { sb.Append(c); }
else
{
if (c == '"' && str[i + 1] == '"')
{
sb.Append(c);
i++;
}
}
}
str = sb.ToString();
return str;
}
}
}