Files
DP44/Common/DTS.Common.Serialization/Iso/Iso.File.Writer.cs
2026-04-17 14:55:32 -04:00

884 lines
46 KiB
C#

/*
* Iso.File.Writer.cs
*
* Copyright © 2009
* Diversified Technical Systems, Inc.
* All Rights Reserved
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using DTS.Common.Classes.Sensors;
using DTS.Common.DAS.Concepts;
using DTS.Common.DAS.Concepts.DAS.Channel;
using DTS.Common.Enums;
using DTS.Common.Enums.Sensors;
using DTS.Common.Utilities;
using DTS.Common.Utilities.Logging;
using DTS.Common.Utils;
using DTS.Serialization.Iso.Report;
namespace DTS.Serialization.Iso
{
// *** see Iso.File.cs ***
public partial class File
{
///
/// <summary>
/// Utility object for serializing <see cref="Serialization.Test"/>s to disk
/// in the ISO format.
/// </summary>
///
public class Writer : Writer<File>, IWriter<Serialization.Test>, IProgressAware
{
/// <summary>
/// whether to export the iso summary report
/// http://manuscript.dts.local/f/cases/43833/SW-Test-Summary-Report-for-data-collection-THF
/// this property is normally set by Iso.File when creating the Iso.File.Writer
/// </summary>
public bool ExportSummaryReport { get; set; } = false;
private const string VARIABLE_NAME_HEADER = "[";
private const string VARIABLE_NAME_FOOTER = "]";
/// <summary>
/// used in mme file, this is the number of channels that will be exported
/// 14226 Exports not using test obj/pos set by the group in a test when using a nonlinear sensor with a linear CAL
/// (issue #2)
/// </summary>
public int NumberOfChannels { get; set; }
private File _file = null;
public File GetFile() { return _file; }
public void SetFile(File f) { _file = f; }
///
/// <summary>
/// Initialize an instance of the Iso.File.Writer class.
/// </summary>
///
/// <param name="fileType">
/// The associated <see cref="File"/> object.
/// </param>
///
internal Writer(File fileType, int encoding, File f)
: base(fileType, encoding)
{
SetFile(f);
}
/// <summary>
/// Notify <see cref="BeginEventHandler"/> subscribers that the write
/// is starting.
/// </summary>
public event BeginEventHandler OnBegin;
/// <summary>
/// Notify <see cref="EndEventHandler"/> subscribers that the write
/// is finished.
/// </summary>
public event EndEventHandler OnEnd;
/// <summary>
/// Notify <see cref="TickEventHandler"/> subscribers that we are one
/// tick closer to write completion.
/// </summary>
public event TickEventHandler OnTick;
/// <summary>
/// notify that the writer is cancelling
/// </summary>
public event CancelEventHandler OnCancel;
/// <summary>
/// notify that the writer encountered fatal error
/// </summary>
public event ErrorEventHandler OnError;
/// <summary>
/// The number of data samples that need to be written for a "tick" to be dispatched.
/// </summary>
private int DataSamplesPerTick => 1000;
public bool UseZeroForUnfiltered { get; set; }
public bool FilteredExport { get; set; }
public bool ExportISOChannelName { get; set; }
/// <summary>
/// Return the number of data to be written per "tick".
/// </summary>
/// <param name="channel"></param>
/// <returns></returns>
private uint GetChannelTicks(Serialization.Test.Module.Channel channel)
{
try
{ //
// Most of our wait time will be spent writing data, so we need to give
// the process a little finer granularity.
//
return (uint)(channel.PersistentChannelInfo.Length / DataSamplesPerTick);
}
catch (System.Exception ex)
{
throw new Exception("encountered problem determining number of status ticks for channel " + (null != channel ? "\"" + channel.Number.ToString() + "\"" : "<NULL>"), ex);
}
}
private static readonly object ChannelLookupLock = new object();
private readonly Dictionary<string, FilteredData> _channelDictionary = new Dictionary<string, FilteredData>();
public FilteredData GetChannel(Serialization.Test.Module.Channel channel)
{
lock (ChannelLookupLock)
{
return _channelDictionary[$"{channel.ParentModule.SerialNumber}_{channel.ParentModule.Number}_{channel.Number}_{channel.ChannelDescriptionString}"];
}
}
public void AddChannel(string dasserial, int moduleNumber, int number, string description, FilteredData data)
{
lock (ChannelLookupLock)
{
_channelDictionary.Add($"{dasserial}_{moduleNumber}_{number}_{description}", data);
}
}
// xxx May be able to do the check here?
private void VerifyExportedFileWillFitOnDisk(
string testname,
string saveLocation,
CancelRequested cancelRequested)
{
try
{
ulong predictedExportSize = 0;
foreach (var sizedTestObject in FileType.TestInstance.Objects)
predictedExportSize += (ulong)sizedTestObject.ToString().Length;
var sizedChannelNumber = 1;
predictedExportSize += (ulong)("Instrumentation standard".PadRight(SeparatorOffset) + Separator + "ISO 6487 (1987) / SAE J211 (MAR95)").Length;
predictedExportSize += (ulong)("Number of channels".PadRight(SeparatorOffset) + Separator + FileType.TestInstance.Channels.Count.ToString()).Length;
int thisChannelNumber;
foreach (var channel in FileType.TestInstance.Channels)
{
if (null != cancelRequested && cancelRequested()) { break; }
if (channel.Samples != null)
{
thisChannelNumber = sizedChannelNumber++;
predictedExportSize += (ulong)channel.ToString().Length;
long numSamples = channel.Samples.Data.Length;
//this lets us do a little subsampling when predicting the export file size
var segmentLength = numSamples / 20000;
if (segmentLength < 1) { segmentLength = 1; }
for (long i = 0; i < channel.Samples.Data.Length; i += segmentLength)
{
if (null != cancelRequested && cancelRequested()) { break; }
var segmentSize = (ulong)(channel.Samples.Data[i] - channel.DataZeroOffsetEu).ToString("F6", System.Globalization.CultureInfo.InvariantCulture.NumberFormat).Length;
var nextJump = i + segmentLength > numSamples ? numSamples - i : segmentLength;
//sanity check - if numSamples is 0 for some reason we don't want a negative nextJump, we still need to count space
if (nextJump < 0) { nextJump = segmentLength; }
predictedExportSize += (ulong)nextJump * segmentSize;
}
var channelName = channel.IsSquib ? channel.Name.ReplaceStrings(Common.Constants.ExportNameFilters, StringReplacementMode.Last) : channel.Name ?? ""; //17650: sanitize name output for certain exports
predictedExportSize += (ulong)(("Name of channel " + thisChannelNumber.ToString("D3")).PadRight(SeparatorOffset) + Separator + channel.Code + " /" + channelName).Length;
}
}
predictedExportSize *= sizeof(char);
// Get the stats on available disk space.
DiskUtility.GetDiskFreeSpaceEx(saveLocation, out ulong freeBytesAvailable, out ulong totalNumberOfBytes, out ulong totalNumberOfFreeBytes);
// Do the comparison.
if (freeBytesAvailable < predictedExportSize)
{
var bytesNeeded = DiskUtility.GetHumanReadableBytes(predictedExportSize);
var bytesAvailable = DiskUtility.GetHumanReadableBytes(freeBytesAvailable);
throw new UserException("Export requires " + bytesNeeded + " but there are only " + bytesAvailable + " available on \"" + saveLocation + "\"");
}
}
catch (System.Exception ex)
{
throw new Exception("encountered problem trying to determing if ISO export of test " + (testname ?? "<NULL>") + " will fit at location " + (saveLocation ?? "<NULL>"), ex);
}
}
/// <summary>
/// Write the specified test to the specified pathname.
/// </summary>
///
/// <param name="pathname">
/// The <see cref="string"/> pathname to which the specified test should be serialized.
/// </param>
///
/// <param name="test">
/// The <see cref="Serialization.Test"/> to be written out.
/// </param>
///
public void Write(string pathname, string id, Serialization.Test test, bool bFiltering, bool includeGroupNameInISOExport, double minStartTime, int dataCollectionLength)
{
try
{
Write(pathname, id, null, test, bFiltering, includeGroupNameInISOExport, null, null, 0, null, null, null, null, null, null, minStartTime, dataCollectionLength);
}
catch (System.Exception ex)
{
throw new Exception("encountered problem non-event notified writing test", ex);
}
}
private void AssignTestObject(ref Test.Object o, Common.ISO.MMETestObjectWrapper tow)
{
o.Class = tow.ClassOfTestObject;
o.Code = tow.CodeOfTestObject;
o.Comments = tow.Comments; // string.Join("\n", tow.Comments);
o.Mass = tow.MassOfTestObject;
//o.Name = tow.Name;
o.Position = tow.DriverPosition;
o.TestObjectImpactSide = tow.ImpactSide;
o.Velocity = tow.VelocityMeterPerSecond;
}
private void ApplyISOTestChannel(ref Serialization.Test.Module.AnalogInputChannel channel, Common.ISO.TestObjectChannel toc)
{
//toc.CustomerChannelCode;
}
private uint totalWriteTicksDispatched = 0;
private uint totalWriteTicksNeeded = 0;
/// <summary>
/// populates a test object with meta data information
/// this code already existed, I just abstracted it out for easier reading
/// http://manuscript.dts.local/f/cases/13735/Need-the-ability-to-add-ISO-metadata-without-an-associated-Group
/// </summary>
/// <param name="testObject"></param>
/// <param name="testObjects"></param>
/// <param name="testObjectNumber"></param>
/// <param name="ISOTestObjectType"></param>
private void PopulateTestObject(Test.Object testObject, Common.ISO.MMETestObjectWrapper[] testObjects,
int testObjectNumber, string ISOTestObjectType)
{
testObject.Comments = testObjects[testObjectNumber].Comments;
testObject.Name = testObjects[testObjectNumber].Name;
testObject.Velocity = testObjects[testObjectNumber].VelocityMeterPerSecond;
testObject.Mass = testObjects[testObjectNumber].MassOfTestObject;
testObject.Position = testObjects[testObjectNumber].DriverPosition;
testObject.TestObjectImpactSide = testObjects[testObjectNumber].ImpactSide;
testObject.Type = ISOTestObjectType;
testObject.Class = testObjects[testObjectNumber].ClassOfTestObject;
testObject.Code = testObjects[testObjectNumber].CodeOfTestObject;
testObject.Number = testObjects[testObjectNumber].ReferenceNumberOfTestObject;
testObject.Offset = testObjects[testObjectNumber].OffsetOfTestObject;
testObject.BarrierWidth = testObjects[testObjectNumber].BarrierWidthOfTestObject;
testObject.BarrierHeight = testObjects[testObjectNumber].BarrierHeightOfTestObject;
testObject.YawAngle = testObjects[testObjectNumber].YawAngleOfTestObject;
testObject.ReferenceSystem = testObjects[testObjectNumber].ReferenceSystemOfTestObject;
testObject.OriginX = testObjects[testObjectNumber].OriginXOfTestObject;
testObject.OriginY = testObjects[testObjectNumber].OriginYOfTestObject;
testObject.OriginZ = testObjects[testObjectNumber].OriginZOfTestObject;
testObject.NumberOfLoadcells = testObjects[testObjectNumber].NumberOfLoadcellsOfTestObject;
testObject.ExtraProperties = testObjects[testObjectNumber].ExtraProperties.Select(kvp => new Test.ExtraProperty(kvp.Key, kvp.Value)).ToList();
// Assign our object number.
}
/// <summary>
/// exports a summary report to the given path based on the input test
/// </summary>
/// <param name="test"></param>
/// <param name="path"></param>
private static void ExportReport(DTS.Serialization.Test test, string path)
{
if (null == test)
{
return;
}
var summary = SummaryReport.CreateSummaryReport(test);
SummaryReport.OutputToPath(path, summary);
}
/// <summary>
/// Write the representation file/files of the specified DTS.Serialization.Test
/// at the given pathname.
/// </summary>
///
/// <param name="pathname">
/// The <see cref="string"/> pathname of the specified object's resulting file
/// representation.
/// </param>
///
/// <param name="test">
/// The <see cref="Serialization.Test"/> to be written out.
/// </param>
///
/// <param name="beginEvent">
/// <see cref="Serialization.BeginWriteEventHandler"/> delegate to be notified
/// when this object begins writing.
/// </param>
///
/// <param name="endEvent">
/// <see cref="Serialization.EndWriteEventHandler"/> delegate to be notified
/// when this object ends writing.
/// </param>
///
/// <param name="tickEvent">
/// <see cref="TickEventHandler"/> delegate to be notified
/// when this object completes a write "tick".
/// </param>
///
public void Write(string pathname,
string id,
string dataFolder,
Serialization.Test test,
bool bFiltering,
bool includeGroupNameInISOExport,
FilteredData fd,
Serialization.Test.Module.Channel tmChannel,
int channelNumber,
BeginEventHandler beginEventHandler,
CancelEventHandler cancelEventHandler,
EndEventHandler endEventHandler,
TickEventHandler tickEventHandler,
ErrorEventHandler errorEventHandler,
CancelRequested cancelRequested,
double minStartTime,
int dataCollectionLength)
{
try
{
var mmeDirectory = pathname + "\\";
var testSampleRate = test.Channels.Select(ch => ch.ParentModule.SampleRateHz).Max();
ISOChannelScale(totalWriteTicksDispatched, totalWriteTicksNeeded, tmChannel, bFiltering, fd, test.Id, includeGroupNameInISOExport, false, testSampleRate);
var testObjectNumber = 0;
var testObjects = GetFile().GetTestPlan().ISOTestObjects;
foreach (var ISOTestObjectType in testObjectChannels.Keys)
{
if (ISOTestObjectType != (tmChannel as IIsoCodeAware).IsoCode.Substring(0, 1))
{
testObjectNumber++;
continue;
}
var thisObjectChannels = testObjectChannels[ISOTestObjectType];
Test.Object testObject;
FileType.TestInstance.Objects.Add(testObject = new Test.Object());
PopulateTestObject(testObject, testObjects, testObjectNumber, ISOTestObjectType);
testObject.MmeNumber = ++testObjectNumber;
FileType.TestInstance.Channels[0].TestObjectNumber = testObject.MmeNumber;
FileType.TestInstance.Channels[0].ExtraProperties = testObject.ExtraProperties;
//FB14276: If a Iso.File.Test.Channel's Extra Property's value contains "[Test.Module.Channel Property Name]", substitute in the tmc property's value
IList<PropertyInfo> props = new List<PropertyInfo>(tmChannel.GetType().GetProperties());
var propVals = props.Select(prop => new KeyValuePair<string, string>(prop.Name, prop.GetValue(tmChannel)?.ToString())).ToList();
foreach (var exp in FileType.TestInstance.Channels[0].ExtraProperties)
{
foreach (var kvp in propVals)
{
exp.Value = exp.Value.Replace(VARIABLE_NAME_HEADER + kvp.Key + VARIABLE_NAME_FOOTER, kvp.Value);
}
}
testObject.TestObject = ISOTestObjectType; //not needed?
break; //We found what we were looking for, so no need to keep looping.
}
//add any iso test objects from groups without channels ...
foreach (var testObjectType in testObjects.Select(x => x.TypeOfTestObject))
{
var exists = FileType.TestInstance.Objects.Exists(o => o.Type == testObjectType);
if (!exists)
{
var newObject = new Test.Object();
PopulateTestObject(newObject, testObjects, testObjectNumber, testObjectType);
newObject.MmeNumber = ++testObjectNumber;
FileType.TestInstance.Objects.Add(newObject);
}
}
var channelLookup = new Dictionary<string, Common.ISO.TestObjectChannel>();
var testObjectLookkup = new Dictionary<string, Common.ISO.MMETestObjectWrapper>();
ApplyAnalogChannel(tmChannel, channelLookup);
for (var i = 0; i < FileType.TestInstance.Objects.Count; i++)
{
var o = FileType.TestInstance.Objects[i];
if (testObjectLookkup.ContainsKey(o.TestObject))
{
AssignTestObject(ref o, testObjectLookkup[o.TestObject]);
}
}
var channelDirectory = mmeDirectory + "Channel\\";
var filename = channelDirectory + id + ".chn";
APILogger.Log("writing 1 ", filename);
Encoding encoder;
try
{
encoder = FileUtils.GetEncoding(DefaultEncoding);
}
catch (System.Exception ex)
{
APILogger.Log("Problem getting encoder", ex);
encoder = Encoding.Default;
}
using (var channelsFileWriter = new StreamWriter(filename, true, encoder))
{
foreach (var channel in FileType.TestInstance.Channels)
{
using (var channelFileWriter = new StreamWriter(channelDirectory + id + "." + channelNumber.ToString("D3"), false, encoder))
{
if (null != cancelRequested && cancelRequested()) { break; }
channelFileWriter.Write(channel.ToString());
if (channel.Samples != null)
{
for (long i = 0; i < channel.Samples.Data.Length; i++) // xxx change this back!!!
{
if (null != cancelRequested && cancelRequested()) { break; }
channelFileWriter.WriteLine((channel.Samples.Data[i] - channel.DataZeroOffsetEu).ToString("F6", System.Globalization.CultureInfo.InvariantCulture.NumberFormat));
if (0 == i % DataSamplesPerTick)
{
OnTick?.Invoke(this, (double)totalWriteTicksDispatched++ / totalWriteTicksNeeded * 100);
System.Windows.Forms.Application.DoEvents();
}
}
}
channelFileWriter.Close();
var channelName = channel.IsSquib ? channel.Name.ReplaceStrings(Common.Constants.ExportNameFilters, StringReplacementMode.Last) : channel.Name ?? ""; //17650: sanitize name output for certain exports
channelsFileWriter.WriteLine(("Name of channel " + channelNumber.ToString("D3")).PadRight(SeparatorOffset) + Separator + channel.Code + " /" + channelName);
}
}
channelsFileWriter.Close();
FileType.TestInstance.Channels.Clear();
}
}
catch (System.Exception ex)
{
APILogger.Log(ex);
throw;
}
}
public void Initialize(string pathname,
string id,
string dataFolder,
Serialization.Test test,
bool bFiltering,
bool includeGroupNameInISOExport,
FilteredData fd,
Serialization.Test.Module.Channel tmChannel,
int channelNumber,
BeginEventHandler beginEventHandler,
CancelEventHandler cancelEventHandler,
EndEventHandler endEventHandler,
TickEventHandler tickEventHandler,
ErrorEventHandler errorEventHandler,
CancelRequested cancelRequested)
{
try
{
var mmeDirectory = pathname + "\\";
OnBegin += beginEventHandler;
OnTick += tickEventHandler;
OnCancel += cancelEventHandler;
OnError += errorEventHandler;
foreach (var module in test.Modules)
{
foreach (var calculatedChannel in module.CalculatedChannels)
{
totalWriteTicksNeeded++;
}
}
uint totalWriteTicksDispatched = 0;
foreach (var channel in test.Channels)
totalWriteTicksNeeded += GetChannelTicks(channel);
foreach (var module in test.Modules)
{
foreach (var calculatedChannel in module.CalculatedChannels)
{
totalWriteTicksNeeded += GetChannelTicks(calculatedChannel);
}
}
OnBegin?.Invoke(this, totalWriteTicksNeeded);
var testSampleRate = test.Channels.Select(ch => ch.ParentModule.SampleRateHz).Max();
foreach (var channel in test.Channels)
{
ISOChannelScale(totalWriteTicksDispatched, totalWriteTicksNeeded, channel, bFiltering, fd, test.Id, includeGroupNameInISOExport, true, testSampleRate);
}
foreach (var module in test.Modules)
{
foreach (var calculatedChannel in module.CalculatedChannels)
{
ISOChannelScale(totalWriteTicksDispatched, totalWriteTicksNeeded, calculatedChannel, bFiltering, fd, test.Id, includeGroupNameInISOExport, true, testSampleRate);
}
}
var testObjectNumber = 0;
var testObjects = GetFile().GetTestPlan().ISOTestObjects;
foreach (var ISOTestObjectType in testObjectChannels.Keys)
{
var thisObjectChannels = testObjectChannels[ISOTestObjectType];
Test.Object testObject;
FileType.TestInstance.Objects.Add(testObject = new Test.Object());
PopulateTestObject(testObject, testObjects, testObjectNumber, ISOTestObjectType);
// Assign our object number.
testObject.MmeNumber = ++testObjectNumber;
// Reference the associated ISO channels back to this object.
foreach (var channel in thisObjectChannels)
{
channel.TestObjectNumber = testObject.MmeNumber;
var code = new Common.ISO.IsoCode(channel.Code);
channel.ChannelFrequencyClass = code.FilterClass;
channel.Dimension = code.PhysicalDimension;
channel.Direction = code.Direction;
channel.ExtraProperties = testObject.ExtraProperties.ToList();
}
testObject.TestObject = ISOTestObjectType; //not needed?
}
//add any iso test objects from groups without channels ...
foreach (var testObjectType in testObjects.Select(x => x.TypeOfTestObject))
{
var exists = FileType.TestInstance.Objects.Exists(o => o.Type == testObjectType);
if (!exists)
{
var newObject = new Test.Object();
PopulateTestObject(newObject, testObjects, testObjectNumber, testObjectType);
newObject.MmeNumber = ++testObjectNumber;
FileType.TestInstance.Objects.Add(newObject);
}
}
var channelLookup = new Dictionary<string, Common.ISO.TestObjectChannel>();
var testObjectLookkup = new Dictionary<string, Common.ISO.MMETestObjectWrapper>();
for (var i = 0; i < test.Channels.Count; i++)
{
ApplyAnalogChannel(test.Channels[i], channelLookup);
}
foreach (var module in test.Modules)
{
foreach (var calculatedChannel in module.CalculatedChannels)
{
ApplyAnalogChannel(calculatedChannel, channelLookup);
}
}
for (var i = 0; i < FileType.TestInstance.Objects.Count; i++)
{
var o = FileType.TestInstance.Objects[i];
if (testObjectLookkup.ContainsKey(o.TestObject))
{
AssignTestObject(ref o, testObjectLookkup[o.TestObject]);
}
}
if (!Directory.Exists(mmeDirectory))
Directory.CreateDirectory(mmeDirectory);
VerifyExportedFileWillFitOnDisk(test.Id, mmeDirectory, cancelRequested);
Encoding encoder;
try
{
encoder = Common.Utils.FileUtils.GetEncoding(DefaultEncoding);
}
catch (System.Exception ex)
{
APILogger.Log("Problem getting encoder", ex);
encoder = Encoding.Default;
}
using (var fileWriter = new StreamWriter(mmeDirectory + id + TestFileExtension, false, encoder))
{
var enc = fileWriter.Encoding;
fileWriter.Write(FileType.TestInstance.ToString());
foreach (var testObject in FileType.TestInstance.Objects)
{
fileWriter.Write(testObject.ToString());
}
fileWriter.Close();
}
FileType.TestInstance.Objects.Clear();
var channelDirectory = mmeDirectory + "Channel\\";
if (!Directory.Exists(channelDirectory))
Directory.CreateDirectory(channelDirectory);
VerifyExportedFileWillFitOnDisk(test.Id, channelDirectory, cancelRequested);
var filename = channelDirectory + id + ".chn";
APILogger.Log("writing 1 ", filename);
using (var channelsFileWriter = new StreamWriter(filename, false, encoder))
{
channelsFileWriter.WriteLine("Instrumentation standard".PadRight(SeparatorOffset) + Separator + "ISO 6487 (1987) / SAE J211 (MAR95)");
channelsFileWriter.WriteLine("Number of channels".PadRight(SeparatorOffset) + Separator + NumberOfChannels.ToString());
FileType.TestInstance.Channels.Clear();
}
try
{
if (ExportSummaryReport)
{
ExportReport(test, pathname);
}
}
catch (System.Exception ex)
{
//this feature is lower priority than the rest of the export
//for now if there's an error while exporting the report, just log it
APILogger.Log(ex);
}
}
catch (System.Exception ex)
{
APILogger.Log($"encountered problem writing ISO test files", ex);
}
}
/// <summary>
/// controls whether to use desired range or actual range
/// Channel amplitude class
/// 15392 Exported CAC in the MME file is the requested CAC not the actual CAC
/// </summary>
public bool ExportActualRange { get; set; } = false;
private readonly IDictionary<string, List<Test.Channel>> testObjectChannels = new Dictionary<string, List<Test.Channel>>();
public bool UseIsoCodeFilterMapping { get; set; } = true;
private void ISOChannelScale(uint totalWriteTicksDispatched, uint totalWriteTicksNeeded, Serialization.Test.Module.Channel channel,
bool bFiltering, FilteredData fd, string testId, bool includeGroupNameInISOExport, bool initializing, double testSampleRate)
{
Test.Channel isoChannel;
FileType.TestInstance.Channels.Add(isoChannel = new Test.Channel());
isoChannel.BitResolution = 16;
isoChannel.Code = channel is IIsoCodeAware ? (channel as IIsoCodeAware).IsoCode : NoValue;
var code = new Common.ISO.IsoCode(isoChannel.Code);
isoChannel.ChannelFrequencyClass = code.FilterClass;
isoChannel.Dimension = code.PhysicalDimension;
isoChannel.Direction = code.Direction;
isoChannel.Location = code.MainLocation;
if (initializing)
{
if (!isoChannel.Code.Equals(NoValue, StringComparison.OrdinalIgnoreCase))
{ //
// Push each channel into our dictionary of ISO-object-indexable ISO channels.
//
var objectCode = isoChannel.Code.Substring(0, 1);
if (!testObjectChannels.Keys.Contains(objectCode))
testObjectChannels.Add(objectCode, new List<Test.Channel>());
testObjectChannels[objectCode].Add(isoChannel);
}
}
isoChannel.PrefilterType = "Butterworth, 5 pole"; // This will need to change for SlicePRO
if (channel is Serialization.Test.Module.AnalogInputChannel)
{
isoChannel.IsDigitalInput = (channel as Serialization.Test.Module.AnalogInputChannel).Bridge == SensorConstants.BridgeType.DigitalInput;
isoChannel.IsSquib = (channel as Serialization.Test.Module.AnalogInputChannel).Bridge == SensorConstants.BridgeType.SQUIB;
}
isoChannel.CutOffFrequency = channel.ParentModule.AaFilterRateHz;
if (ExportISOChannelName)
{
if (includeGroupNameInISOExport)
{
isoChannel.Name = channel.ChannelGroupName + ":" + channel.ChannelName2;
}
else
{
isoChannel.Name = channel.ChannelName2;
}
}
else
{
if (includeGroupNameInISOExport)
{
isoChannel.Name = channel.ChannelGroupName + ":" + channel.ChannelDescriptionString;
}
else
{
isoChannel.Name = channel.ChannelDescriptionString;
}
}
isoChannel.SamplingInterval = 1.0 / channel.ParentModule.SampleRateHz;
isoChannel.SamplingIntervalTest = 1D / testSampleRate;
var aic = channel as Serialization.Test.Module.AnalogInputChannel;
if (null != aic)
{
isoChannel.TransducerId = string.IsNullOrWhiteSpace(aic.SerialNumber) ? aic.ToString() : aic.SerialNumber;
}
if (null != channel.ParentModule.TriggerSampleNumbers && 1 >= channel.ParentModule.TriggerSampleNumbers.Count)
{
isoChannel.TimeOfFirstSample = (channel.ParentModule.StartRecordSampleNumber - (double)channel.ParentModule.TriggerSampleNumbers[0])
/ channel.ParentModule.SampleRateHz;
}
else { isoChannel.TimeOfFirstSample = 0; }
isoChannel.StartOffsetInterval = (channel as Serialization.Test.Module.AnalogInputChannel).ZeroAverageWindow.Begin;
isoChannel.EndOffsetInterval = (channel as Serialization.Test.Module.AnalogInputChannel).ZeroAverageWindow.End;
if (channel is IEngineeringUnitAware)
{
isoChannel.Unit = (channel as IEngineeringUnitAware).EngineeringUnits.TrimEnd();
if (isoChannel.Unit == "G")
{
isoChannel.Unit = "g"; //X-Crash doesn't like "G"
}
}
var scaler = new DataScaler();
scaler.IsInverted = channel is IInversionAware ? (channel as IInversionAware).IsInverted : false;
if ((channel as ILinearized).LinearizationFormula.IsValid())
{
scaler.SetLinearizationFormula((channel as ILinearized).LinearizationFormula);
}
else
{
scaler.SetLinearizationFormula(null);
}
scaler.Digital = (channel as Serialization.Test.Module.AnalogInputChannel).IsDigital();
scaler.SetDigitalMultiplier((channel as Serialization.Test.Module.AnalogInputChannel).DigitalMultiplier);
scaler.DigitalMode = (channel as Serialization.Test.Module.AnalogInputChannel).DigitalMode;
scaler.SetScaleFactorMv(channel.Data.ScaleFactorMv);
scaler.SetScaleFactorEU(channel.Data.ScaleFactorEU);
scaler.SetUseEUScaleFactors(channel.Data.UseEUScaleFactors);
scaler.UnitConversion = (channel as Serialization.Test.Module.AnalogInputChannel).UnitConversion;
scaler.BasedOnOutputAtCapacity = (channel as Serialization.Test.Module.AnalogInputChannel).AtCapacity;
scaler.CapacityOutputIsBasedOn = (channel as Serialization.Test.Module.AnalogInputChannel).CapacityOutputIsBasedOn;
scaler.SensitivityUnits = (channel as Serialization.Test.Module.AnalogInputChannel).SensitivityUnits;
scaler.Multiplier = (channel as Serialization.Test.Module.AnalogInputChannel).Multiplier;
scaler.UserOffsetEU = (channel as Serialization.Test.Module.AnalogInputChannel).UserOffsetEU;
if (channel is Serialization.Test.Module.AnalogInputChannel)
{
scaler.IEPE = (channel as Serialization.Test.Module.AnalogInputChannel).Bridge == SensorConstants.BridgeType.IEPE;
scaler.Digital = (channel as Serialization.Test.Module.AnalogInputChannel).Bridge == SensorConstants.BridgeType.DigitalInput;
}
scaler.SetMvPerEu(channel.Data.MvPerEu);
scaler.SetDataZeroLevelADC(channel.DataZeroLevelAdc);
scaler.SetRemovedADC(channel.RemovedADC);
scaler.SetRemovedInternalADC(channel.RemovedInternalADC);
scaler.SetZeroMvInADC(channel.ZeroMvInADC);
try { scaler.SetWindowAverageADC(channel.WindowAverageADC); }
catch (System.Exception) { }
if (channel is Serialization.Test.Module.AnalogInputChannel)
{
//
// Maybe these should be replaced by DTS.DAS.Concepts versions for casting? Would need to
// add excitation voltage as a concept, and proportional to excitation.
//
var analogChannel = channel as Serialization.Test.Module.AnalogInputChannel;
try { scaler.SetInitialOffset(analogChannel.InitialOffset); }
catch (System.Exception) { }
scaler.ZeroMethodType = analogChannel.ZeroMethod;
scaler.NominalExcitationVoltage = analogChannel.ExcitationVoltage;
if (analogChannel.MeasuredExcitationVoltageValid)
{
try { scaler.MeasuredExcitationVoltage = analogChannel.MeasuredExcitationVoltage; }
catch { };
}
if (analogChannel.FactoryExcitationVoltageValid)
{
try { scaler.FactoryExcitationVoltage = analogChannel.FactoryExcitationVoltage; }
catch { };
}
scaler.ProportionalToExcitation = analogChannel.ProportionalToExcitation;
isoChannel.StartOffsetInterval = analogChannel.ZeroAverageWindow.Begin;
isoChannel.EndOffsetInterval = analogChannel.ZeroAverageWindow.End;
}
// xxx Probably need to create an object for isoChannel.Samples that wraps the memory mapped data
// object and just converts the samples as they're accessed. And maybe for the data viewer, we can
// do an "is a" test on the data object and do the right thing accordingly...?
// xxx Or is there any reason why we can't just keep it short and cast it each time?
if ((channel as ILinearized).LinearizationFormula.IsValid())
{
isoChannel.DataZeroOffsetEu = 0;
}
else
{
//19038 Export data in ISO not respecting initial offset EU at mV settings.
//DataZeroOffsetEU in linear sensors should be a constant, but we can't
//just subtract GetEU(DataZeroLevelADC), normally EU= GetEU(ADC-ZeroLevelADC) but
//this overlooks and overapplies things like InitialOffsetEU, so
//EU = GetEU(ADC)-GetEU(ZeroLevelADC) is applying InitialOffset twice while
//EU = GetEU(ADC-ZeroLevelADC) only applies it once ... so don't apply any initial offset to DataZeroOfsetEU
var scaler2 = new DataScaler(scaler);
scaler2.SetInitialOffset(new InitialOffset() { Form = InitialOffsetTypes.None, EU = 0, MV = 0 });
//also close out useroffset or it will be double counted ...
//29678 ISO export does not match CSV/ Viewer output
scaler2.UserOffsetEU = 0;
isoChannel.DataZeroOffsetEu = scaler2.GetEU(channel.DataZeroLevelAdc);
}
isoChannel.Samples = fd;
isoChannel.OffsetPostTest = scaler.GetEU(channel.PreTestZeroLevelAdc); //Change this when we have post-test diagnostics
// 15392 Exported CAC in the MME file is the requested CAC not the actual CAC
if (ExportActualRange)
{
try
{
if (scaler.GetLinearizationFormula().IsValid())
{
isoChannel.AmplitudeClass = aic?.DesiredRange ?? 0;
}
else
{
var a = scaler.GetEU(short.MinValue);
var b = scaler.GetEU(short.MaxValue);
isoChannel.AmplitudeClass = Math.Abs((b - a) / 2);
}
}
catch (Exception ex)
{
APILogger.Log("Actual range unavailable for channel export", ex);
isoChannel.AmplitudeClass = aic.DesiredRange;
}
}
else
{
isoChannel.AmplitudeClass = aic.DesiredRange;
}
channel.PersistentChannelInfo.Dispose();
}
private void ApplyAnalogChannel(Serialization.Test.Module.Channel channel, Dictionary<string, Common.ISO.TestObjectChannel> channelLookup)
{
if (!(channel is Serialization.Test.Module.AnalogInputChannel analogChannel)) { return; }
if (channelLookup.ContainsKey(analogChannel.SerialNumber))
{
ApplyISOTestChannel(ref analogChannel, channelLookup[analogChannel.SerialNumber]);
}
}
}
}
}