884 lines
46 KiB
C#
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]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|