811 lines
38 KiB
C#
811 lines
38 KiB
C#
using DTS.Common.DAS.Concepts;
|
|
using DTS.Common.DAS.Concepts.DAS.Channel;
|
|
using DTS.Common.Enums.Sensors;
|
|
using DTS.Common.Utilities;
|
|
using DTS.Common.Utilities.DotNetProgrammingConstructs;
|
|
using DTS.Common.Utilities.Logging;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using static DTS.Serialization.Test.Module;
|
|
|
|
namespace DTS.Serialization.FIAT_ASC
|
|
{
|
|
// *** see FIAT_Asc.File.cs ***
|
|
public partial class File
|
|
{
|
|
///
|
|
/// <summary>
|
|
/// Utility object for serializing <see cref="DTS.Serialization.Test"/>s to disk
|
|
/// in the FTSS CSV format.
|
|
/// </summary>
|
|
///
|
|
public partial class Writer : Writer<File>, IWriter<Test>
|
|
{
|
|
/// <summary>
|
|
/// Initialize an instance of the FIAT_Asc.File.Writer class.
|
|
/// </summary>
|
|
///
|
|
/// <param name="fileType">
|
|
/// The associated <see cref="DTS.SErialization.FIAT_Asc.File"/> object.
|
|
/// </param>
|
|
///
|
|
internal Writer(File fileType, int encoding)
|
|
: base(fileType, encoding)
|
|
{
|
|
WriterParent = fileType;
|
|
}
|
|
|
|
/// <summary>
|
|
/// the owning file that controls this writer
|
|
/// </summary>
|
|
internal File WriterParent { get; }
|
|
|
|
private const string NUMBER_FORMAT = "F8";
|
|
/// <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 subscribers that the write was cancelled
|
|
/// </summary>
|
|
public event CancelEventHandler OnCancel;
|
|
|
|
/// <summary>
|
|
/// notify subscribers 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;
|
|
|
|
/// <summary>
|
|
/// Return the number of data to be written per "tick".
|
|
/// </summary>
|
|
/// <param name="channel"></param>
|
|
/// <returns></returns>
|
|
private uint GetChannelTicks(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 + "\"" : "<NULL>"), ex);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get/set the filtered channel data. If this list is supplied, the corresponding test
|
|
/// channel data values will be supplied from this list.
|
|
/// </summary>
|
|
public Dictionary<string, FilteredData> FilteredChannelData
|
|
{
|
|
get => _FilteredChannelData.Value;
|
|
set => _FilteredChannelData.Value = value;
|
|
}
|
|
|
|
private readonly Property<Dictionary<string, FilteredData>> _FilteredChannelData
|
|
= new Property<Dictionary<string, FilteredData>>(
|
|
"FilteredChannelData",
|
|
new Dictionary<string, FilteredData>(),
|
|
true
|
|
);
|
|
|
|
/// <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="DTS.Serialization.Test"/> to be written out.
|
|
/// </param>
|
|
///
|
|
public void Write(string pathname, string id, 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);
|
|
}
|
|
}
|
|
public bool UseFlatExportFolder { get; set; } = false;
|
|
/// <summary>
|
|
/// Write the representation file/files of the specified DTS.Serialization.Test
|
|
/// at the given pathname.
|
|
/// </summary>
|
|
///
|
|
/// <param name="targetPathname">
|
|
/// The <see cref="string"/> pathname of the specified object's resulting file
|
|
/// representation.
|
|
/// </param>
|
|
///
|
|
public void Write(string pathname,
|
|
string id,
|
|
string dataFolder,
|
|
Test test,
|
|
bool bFiltering,
|
|
bool includeGroupNameInISOExport,
|
|
FilteredData fd,
|
|
Test.Module.Channel tmChannel,
|
|
int channelNumber,
|
|
BeginEventHandler beginEventHandler,
|
|
CancelEventHandler cancelEventHandler,
|
|
EndEventHandler endEventHandler,
|
|
TickEventHandler tickEventHandler,
|
|
ErrorEventHandler errorEventHandler,
|
|
CancelRequested cancelRequested,
|
|
double minStartTime,
|
|
int dataCollectionLength)
|
|
{
|
|
System.Exception exception = null;
|
|
try
|
|
{
|
|
OnBegin += beginEventHandler;
|
|
OnEnd += endEventHandler;
|
|
OnTick += tickEventHandler;
|
|
OnCancel += cancelEventHandler;
|
|
OnError += errorEventHandler;
|
|
|
|
// Compute the total number of write ticks that will be dispatched during this
|
|
// write, and let the caller know that we're underway.
|
|
uint totalWriteTicksNeeded = 0;
|
|
uint totalWriteTicksDispatched = 0;
|
|
if (test.Channels.Count > 0)
|
|
totalWriteTicksNeeded = GetChannelTicks(test.Channels[0]);
|
|
OnBegin?.Invoke(this, totalWriteTicksNeeded);
|
|
var folderPath = Path.GetDirectoryName(pathname);
|
|
var fileName = Path.GetFileNameWithoutExtension(pathname);
|
|
var destFolder = folderPath;
|
|
|
|
foreach (var channel in test.Channels)
|
|
{
|
|
if (!Directory.Exists(destFolder))
|
|
{
|
|
Directory.CreateDirectory(destFolder);
|
|
}
|
|
var channelName = channel.ChannelName2;
|
|
if (channel is AnalogInputChannel AIC)
|
|
{
|
|
channelName = AIC.Description;
|
|
}
|
|
var channelFileName = Path.Combine(destFolder, $"{channelName.Replace("\\", "_").Replace("/", "_")}{Extension}");
|
|
APILogger.Log("opening ", channelFileName);
|
|
using (var fs = new FileStream(channelFileName, FileMode.Create))
|
|
{
|
|
|
|
Encoding encoder;
|
|
|
|
try
|
|
{
|
|
encoder = Common.Utils.FileUtils.GetEncoding(DefaultEncoding);
|
|
}
|
|
catch (System.Exception ex)
|
|
{
|
|
APILogger.Log("Problem getting encoder", ex);
|
|
encoder = Encoding.Default;
|
|
}
|
|
|
|
//DateTime start = DateTime.Now;
|
|
using (var fileWriter = new StreamWriter(fs, encoder, 1024 * 1000))
|
|
{
|
|
//List<Test.Module.Channel> exportChannels = test.Channels;
|
|
|
|
WriteChannelInfo(fileWriter,
|
|
id,
|
|
test,
|
|
FilteredChannelData,
|
|
channelFileName,
|
|
tickEventHandler,
|
|
totalWriteTicksNeeded,
|
|
ref totalWriteTicksDispatched,
|
|
cancelRequested,
|
|
channel);
|
|
}
|
|
|
|
fs.Close();
|
|
}
|
|
}
|
|
}
|
|
|
|
catch (System.Exception ex)
|
|
{
|
|
APILogger.Log("encountered problem writing CSV test files", ex);
|
|
exception = new Exception("encountered problem writing CSV test files", ex);
|
|
}
|
|
|
|
finally
|
|
{
|
|
OnEnd?.Invoke(this);
|
|
|
|
if (null != cancelRequested && cancelRequested())
|
|
{
|
|
OnCancel?.Invoke(this);
|
|
}
|
|
|
|
if (null != exception && null != errorEventHandler)
|
|
{
|
|
errorEventHandler(this, exception);
|
|
}
|
|
else if (null != exception)
|
|
{
|
|
throw exception;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Initialize(string pathname,
|
|
string id,
|
|
string dataFolder,
|
|
Test test,
|
|
bool bFiltering,
|
|
bool includeGroupNameInISOExport,
|
|
FilteredData fd,
|
|
Test.Module.Channel tmChannel,
|
|
int channelNumber,
|
|
BeginEventHandler beginEventHandler,
|
|
CancelEventHandler cancelEventHandler,
|
|
EndEventHandler endEventHandler,
|
|
TickEventHandler tickEventHandler,
|
|
ErrorEventHandler errorEventHandler,
|
|
CancelRequested cancelRequested)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generate a best-estimate export size for the specified dataset, and check it against the
|
|
/// current disk availability stats.
|
|
/// </summary>
|
|
///
|
|
/// <param name="testname">
|
|
/// The <see cref="string"/> name of the test to be verified.
|
|
/// </param>
|
|
///
|
|
/// <param name="saveLocation">
|
|
/// The <see cref="string"/> name of the target location into which the test must fit.
|
|
/// </param>
|
|
///
|
|
/// <param name="headerLines">
|
|
/// An <see cref="System.Collections.Generic.IDictionary<TKey,TValue>"/> containing pairs
|
|
/// of header entry name <see cref="string"/>s and corresponding value <see cref="string"/>s.
|
|
/// </param>
|
|
///
|
|
/// <param name="coder">
|
|
/// A <see cref="DescriptionAttributeCoder<TargetType>"/> for en/coding
|
|
/// <see cref="FtssHeaderLine"/> header enumerations.
|
|
/// </param>
|
|
///
|
|
/// <param name="channelsWithMeta">
|
|
/// A populated <see cref="System.Collections.Generic.List<T>"/> of
|
|
/// <see cref="ChannelWithMeta"/>s generated from
|
|
/// the test to be exported.
|
|
/// </param>
|
|
///
|
|
/// <param name="dataCollectionLength">
|
|
/// The <see cref="int"/> length of the data collection within this test that is to be
|
|
/// exported.
|
|
/// </param>
|
|
///
|
|
/// <param name="minStartTime">
|
|
/// The earliest <see cref="double"/> T0-relative start time on any channel within the
|
|
/// test to be exported.
|
|
/// </param>
|
|
///
|
|
/// <param name="sampleRate">
|
|
/// The <see cref="double"/> sample rate of the test to be exported.
|
|
/// </param>
|
|
///
|
|
private void VerifyExportedFileWillFitOnDisk(
|
|
string testname,
|
|
string saveLocation,
|
|
List<ChannelWithMeta> channelsWithMeta,
|
|
int dataCollectionLength,
|
|
double minStartTime,
|
|
double sampleRate,
|
|
CancelRequested cancelRequested
|
|
)
|
|
{
|
|
try
|
|
{
|
|
//
|
|
// Compute the size of the header information.
|
|
//
|
|
ulong predictedExportSize = 0;
|
|
|
|
//
|
|
// Compute the size of the data portion.
|
|
// this computation is expensive for large data collections, so lets just subsample them for now
|
|
// for every 2 million samples collected we'll increase the size of subsamples by 1.
|
|
var segmentSize = dataCollectionLength / 2000000;
|
|
if (segmentSize < 1)
|
|
{
|
|
segmentSize = 1;
|
|
}
|
|
|
|
for (var i = 0; i < dataCollectionLength; i += segmentSize)
|
|
{
|
|
if (null != cancelRequested && cancelRequested())
|
|
{
|
|
break;
|
|
}
|
|
|
|
var thisSegmentSize =
|
|
(ulong)((minStartTime + i * 1.0 / sampleRate).ToString(NUMBER_FORMAT) +
|
|
System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator).Length;
|
|
foreach (var channelWithMeta in channelsWithMeta)
|
|
{
|
|
if (null != cancelRequested && cancelRequested())
|
|
{
|
|
break;
|
|
}
|
|
|
|
var thisChannelsIndexAtCurrentTime =
|
|
i - (int)((channelWithMeta.StartTime - minStartTime) * channelWithMeta.SampleRate);
|
|
|
|
thisSegmentSize += thisChannelsIndexAtCurrentTime >= 0 && thisChannelsIndexAtCurrentTime <
|
|
channelWithMeta.Channel.PersistentChannelInfo.Length
|
|
? 9U
|
|
: 1U;
|
|
}
|
|
|
|
var segmentJump = i + segmentSize > dataCollectionLength
|
|
? dataCollectionLength - i
|
|
: segmentSize;
|
|
predictedExportSize += thisSegmentSize * (ulong)segmentJump;
|
|
}
|
|
|
|
predictedExportSize *= sizeof(char);
|
|
|
|
// Get the stats on available disk space.
|
|
var errorcode = DiskUtility.GetDiskFreeSpaceEx(saveLocation, out ulong freeBytesAvailable,
|
|
out ulong totalNumberOfBytes, out ulong totalNumberOfFreeBytes);
|
|
if (0 != errorcode)
|
|
{
|
|
|
|
// 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 + " bytes available on \"" + saveLocation + "\"");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
APILogger.Log("Failed to get free disk space, windows error: ", errorcode);
|
|
}
|
|
}
|
|
|
|
catch (System.Exception ex)
|
|
{
|
|
throw new Exception(
|
|
"encountered problem trying to determing if CSV export of test " +
|
|
(testname ?? "<NULL>") + " will fit at location " +
|
|
(saveLocation ?? "<NULL>"), ex);
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Write the specified test to the specified stream.
|
|
/// </summary>
|
|
///
|
|
/// <param name="fileWriter">
|
|
/// The <see cref="StreamWriter"/> to which the specified test should be serialized.
|
|
/// </param>
|
|
///
|
|
/// <param name="channel">
|
|
/// The <see cref="Test"/> to be serialized.
|
|
/// </param>
|
|
///
|
|
/// <param name="writeEvent">
|
|
/// The <see cref="Writer.EventHandler"/> that should handle write-progress
|
|
/// reporting; null if none is to be used.
|
|
/// <param name="targetPath">
|
|
/// The <see cref="string"/> path that the file writer will be writing to.
|
|
/// </param>
|
|
///
|
|
/// <param name="tickEventHandler">
|
|
/// The <see cref="TickEventHandler"/> that will process "tick" events
|
|
/// indicating progress during the export procedure.
|
|
/// </param>
|
|
///
|
|
/// <param name="totalWriteTicksNeeded">
|
|
/// The total <see cref="uint"/> number of progress ticks that will be issued during the
|
|
/// export procedure.
|
|
/// </param>
|
|
///
|
|
/// <param name="totalWriteTicksDispatched">
|
|
/// Returns the total <see cref="uint"/> number of tick events that have been dispatched.
|
|
/// </param>
|
|
///
|
|
protected void WriteChannelInfo
|
|
(
|
|
StreamWriter fileWriter,
|
|
string id,
|
|
Test test,
|
|
Dictionary<string, FilteredData> filteredData,
|
|
string targetPath,
|
|
TickEventHandler tickEventHandler,
|
|
uint totalWriteTicksNeeded,
|
|
ref uint totalWriteTicksDispatched,
|
|
CancelRequested cancelRequested,
|
|
Test.Module.Channel channel)
|
|
{
|
|
try
|
|
{
|
|
double sampleRate = channel.ParentModule.SampleRateHz;
|
|
if (1 < SubSampleInterval)
|
|
{
|
|
if (true == Filtered)
|
|
{
|
|
var SubSample = new NHTSASubSample<double>();
|
|
SubSample.data = filteredData[channel.ChannelId].Data;
|
|
SubSample.preTriggerSamples =
|
|
channel.ParentModule.TriggerSampleNumbers[0] - channel.ParentModule.StartRecordSampleNumber;
|
|
SubSample.sampleRate = sampleRate;
|
|
SubSample.subSampleInterval = SubSampleInterval;
|
|
SubSample.SubSample();
|
|
filteredData[channel.ChannelId].Data = SubSample.data;
|
|
channel.IsSubsampled = true; // I don't think anyone will consume this, but for completeness.
|
|
}
|
|
else
|
|
{
|
|
var SubSample = new NHTSASubSample<short>();
|
|
SubSample.data = channel.PersistentChannelInfo.Data;
|
|
SubSample.preTriggerSamples =
|
|
channel.ParentModule.TriggerSampleNumbers[0] - channel.ParentModule.StartRecordSampleNumber;
|
|
SubSample.sampleRate = sampleRate;
|
|
SubSample.subSampleInterval = SubSampleInterval;
|
|
SubSample.SubSample();
|
|
channel.PersistentChannelInfo.Data = SubSample.data;
|
|
channel.IsSubsampled =
|
|
true; // I don't think anyone will consume this, but for completeness.
|
|
}
|
|
}
|
|
|
|
var channelsWithMeta = new List<ChannelWithMeta>();
|
|
|
|
var filteredChannelData = filteredData.ContainsKey(channel.ChannelId) ? filteredData[channel.ChannelId] : null;
|
|
|
|
|
|
bool isDigitalInput = false;
|
|
bool isSquib = false;
|
|
if (channel is Test.Module.AnalogInputChannel)
|
|
{
|
|
isDigitalInput = (channel as Test.Module.AnalogInputChannel).Bridge == SensorConstants.BridgeType.DigitalInput;
|
|
isSquib = (channel as Test.Module.AnalogInputChannel).Bridge == SensorConstants.BridgeType.SQUIB;
|
|
}
|
|
|
|
var rate = Convert.ToInt32(Math.Ceiling(sampleRate / channel.ParentModule.SampleRateHz));
|
|
|
|
var preTriggerSamples = rate * (channel.ParentModule.TriggerSampleNumbers[0] - (double)channel.ParentModule.StartRecordSampleNumber);
|
|
|
|
if (preTriggerSamples < 0)
|
|
{
|
|
preTriggerSamples = 0D;
|
|
}
|
|
|
|
if (preTriggerSamples > channel.ParentModule.NumberOfSamples)
|
|
{
|
|
preTriggerSamples = channel.ParentModule.NumberOfSamples;
|
|
}
|
|
|
|
if (preTriggerSamples > Start * sampleRate)
|
|
{
|
|
preTriggerSamples = Math.Truncate(Start * sampleRate);
|
|
}
|
|
|
|
var postTriggerSamples = (channel.ParentModule.NumberOfSamples - preTriggerSamples) * rate;
|
|
if (postTriggerSamples < 0)
|
|
{
|
|
postTriggerSamples = 0D;
|
|
}
|
|
|
|
if (postTriggerSamples > Stop * sampleRate)
|
|
{
|
|
postTriggerSamples = Math.Truncate(Stop * sampleRate);
|
|
}
|
|
|
|
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 Test.Module.AnalogInputChannel).IsDigital();
|
|
|
|
scaler.SetDigitalMultiplier((channel as Test.Module.AnalogInputChannel).DigitalMultiplier);
|
|
scaler.DigitalMode = (channel as Test.Module.AnalogInputChannel).DigitalMode;
|
|
|
|
scaler.SetScaleFactorMv(channel.Data.ScaleFactorMv);
|
|
scaler.SetScaleFactorEU(channel.Data.ScaleFactorEU);
|
|
scaler.SetUseEUScaleFactors(channel.Data.UseEUScaleFactors);
|
|
scaler.UnitConversion = (channel as Test.Module.AnalogInputChannel).UnitConversion;
|
|
|
|
scaler.BasedOnOutputAtCapacity = (channel as Test.Module.AnalogInputChannel).AtCapacity;
|
|
scaler.CapacityOutputIsBasedOn =
|
|
(channel as Test.Module.AnalogInputChannel).CapacityOutputIsBasedOn;
|
|
|
|
scaler.SensitivityUnits = (channel as Test.Module.AnalogInputChannel).SensitivityUnits;
|
|
scaler.Multiplier = (channel as Test.Module.AnalogInputChannel).Multiplier;
|
|
scaler.UserOffsetEU = (channel as Test.Module.AnalogInputChannel).UserOffsetEU;
|
|
|
|
if (channel is Test.Module.AnalogInputChannel)
|
|
{
|
|
scaler.IEPE = (channel as Test.Module.AnalogInputChannel).Bridge ==
|
|
SensorConstants.BridgeType.IEPE;
|
|
scaler.Digital = (channel as 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 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 Test.Module.AnalogInputChannel;
|
|
scaler.SetInitialOffset(analogChannel.InitialOffset);
|
|
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;
|
|
}
|
|
|
|
var dStartTime = channel.ParentModule.StartRecordSampleNumber /
|
|
(double)channel.ParentModule.SampleRateHz;
|
|
if (channel.ParentModule.TriggerSampleNumbers.Count > 0)
|
|
{
|
|
dStartTime -= channel.ParentModule.TriggerSampleNumbers[0] /
|
|
(double)channel.ParentModule.SampleRateHz;
|
|
}
|
|
|
|
channelsWithMeta.Add(
|
|
new ChannelWithMeta(
|
|
channel,
|
|
scaler,
|
|
channel.ParentModule.SampleRateHz,
|
|
dStartTime
|
|
)
|
|
);
|
|
var originalChannelName = channel.OriginalChannelName;
|
|
|
|
if (string.IsNullOrWhiteSpace(originalChannelName))
|
|
{
|
|
//if original channel name is blank, attempt to fall back on names based on whatever the current iso view mode is
|
|
//this has to be done because outputsquibchannel does not currently store the property OriginalChannelName
|
|
//15619 Channel name is empty in csv export if data is from squib
|
|
if (channel is Test.Module.AnalogInputChannel aic)
|
|
{
|
|
switch (WriterParent.ISOViewMode)
|
|
{
|
|
case Common.Enums.IsoViewMode.ISOOnly:
|
|
originalChannelName = aic.IsoChannelName;
|
|
break;
|
|
case Common.Enums.IsoViewMode.ISOAndUserCode:
|
|
originalChannelName = $"{aic.IsoChannelName}\\{aic.UserChannelName}";
|
|
break;
|
|
case Common.Enums.IsoViewMode.UserCodeOnly:
|
|
originalChannelName = aic.UserChannelName;
|
|
break;
|
|
case Common.Enums.IsoViewMode.ChannelNameOnly:
|
|
originalChannelName = aic.UserChannelName;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
//Zero Method and if Average Over Time, then Begin/End
|
|
var sb = new StringBuilder(scaler.ZeroMethodType.ToString());
|
|
if (scaler.ZeroMethodType == Common.Enums.Sensors.ZeroMethodType.AverageOverTime)
|
|
{
|
|
sb.Append(" (");
|
|
sb.Append((channel as Test.Module.AnalogInputChannel).ZeroAverageWindow.Begin);
|
|
sb.Append("/");
|
|
sb.Append((channel as Test.Module.AnalogInputChannel).ZeroAverageWindow.End);
|
|
sb.Append(")");
|
|
}
|
|
|
|
var minStartTime = ChannelWithMeta.GetMinStartTime(Start, channelsWithMeta);
|
|
var minStopTime = ChannelWithMeta.GetMinStopTime(Stop, channelsWithMeta);
|
|
var dataCollectionLength = (int)((minStopTime - minStartTime) * sampleRate);
|
|
|
|
VerifyExportedFileWillFitOnDisk(test.Id,
|
|
targetPath.Remove(targetPath.IndexOf(Path.VolumeSeparatorChar) + 1),
|
|
channelsWithMeta,
|
|
dataCollectionLength,
|
|
minStartTime,
|
|
sampleRate,
|
|
cancelRequested);
|
|
|
|
|
|
fileWriter.WriteLine($"{minStartTime.ToString(NUMBER_FORMAT)} {(1 / sampleRate).ToString(NUMBER_FORMAT)}");
|
|
fileWriter.WriteLine(dataCollectionLength);
|
|
|
|
for (var i = 0; i <= dataCollectionLength; i++)
|
|
{
|
|
if (null != cancelRequested && cancelRequested())
|
|
{
|
|
break;
|
|
}
|
|
|
|
|
|
var bNeedComma = false;
|
|
foreach (var channelWithMeta in channelsWithMeta)
|
|
{
|
|
if (bNeedComma)
|
|
{
|
|
fileWriter.Write(System.Globalization.CultureInfo.CurrentCulture.TextInfo
|
|
.ListSeparator);
|
|
}
|
|
else
|
|
{
|
|
bNeedComma = true;
|
|
}
|
|
|
|
//
|
|
// If this channel's data is valid at the current time/index then go ahead and send it out
|
|
// to the file; otherwise write out a blank space.
|
|
//14513 double rounding error causing data to be offset by one sample incorrectly
|
|
// note that using decimals will fix the imprecision issue, but will use more time ...
|
|
var delta = Convert.ToDecimal(channelWithMeta.StartTime) - Convert.ToDecimal(minStartTime);
|
|
var channelOffsetStart = Convert.ToInt32(delta * Convert.ToDecimal(channelWithMeta.SampleRate));
|
|
var rateMeta = sampleRate / channelWithMeta.SampleRate;
|
|
var indexAtCurrentTime = (i - channelOffsetStart) / rateMeta;
|
|
var thisChannelsIndexAtCurrentTime = Convert.ToInt32(Math.Floor(indexAtCurrentTime));
|
|
var step = Convert.ToInt32(Math.Ceiling(indexAtCurrentTime) - thisChannelsIndexAtCurrentTime);
|
|
if (Filtered)
|
|
{
|
|
if (filteredData.ContainsKey(channelWithMeta.Channel.ChannelId))
|
|
{
|
|
var channelFilteredData = filteredData[channelWithMeta.Channel.ChannelId];
|
|
if (thisChannelsIndexAtCurrentTime >= 0 && thisChannelsIndexAtCurrentTime <
|
|
channelFilteredData.Data.Length)
|
|
{
|
|
var indexValue = channelFilteredData.Data[thisChannelsIndexAtCurrentTime];
|
|
if (0 == step) { fileWriter.Write(indexValue.ToString(NUMBER_FORMAT)); }
|
|
else
|
|
{
|
|
var increment = 0D;
|
|
if ((1 + thisChannelsIndexAtCurrentTime) < channelFilteredData.Data.Length)
|
|
{
|
|
increment = (channelFilteredData.Data[1 + thisChannelsIndexAtCurrentTime] - indexValue) / rateMeta;
|
|
}
|
|
else
|
|
{
|
|
increment = (indexValue - channelFilteredData.Data[thisChannelsIndexAtCurrentTime - 1]) / rateMeta;
|
|
}
|
|
fileWriter.Write((indexValue + increment * step).ToString(NUMBER_FORMAT));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fileWriter.Write(double.NaN.ToString());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
double dInitialEU = 0;
|
|
var aChannel = channelWithMeta.Channel as Test.Module.AnalogInputChannel;
|
|
if (thisChannelsIndexAtCurrentTime >= 0 && thisChannelsIndexAtCurrentTime <
|
|
channelWithMeta.Channel.PersistentChannelInfo.Length)
|
|
{
|
|
var adc = channelWithMeta.Channel.PersistentChannelInfo[thisChannelsIndexAtCurrentTime];
|
|
var increment = 0D;
|
|
if ((1 + thisChannelsIndexAtCurrentTime) < channelWithMeta.Channel.PersistentChannelInfo.Length)
|
|
{
|
|
increment = (channelWithMeta.Channel.PersistentChannelInfo[1 + thisChannelsIndexAtCurrentTime] - adc) / rateMeta;
|
|
}
|
|
else { increment = (adc - channelWithMeta.Channel.PersistentChannelInfo[thisChannelsIndexAtCurrentTime - 1]) / rateMeta; }
|
|
if (null != aChannel)
|
|
{
|
|
dInitialEU = aChannel.InitialEu;
|
|
}
|
|
fileWriter.Write(channelWithMeta.Scaler.GetEU((adc + increment * step)).ToString(NUMBER_FORMAT));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (0 == i % DataSamplesPerTick)
|
|
{
|
|
OnTick?.Invoke(this, (double)totalWriteTicksDispatched++ / totalWriteTicksNeeded * 100);
|
|
System.Windows.Forms.Application.DoEvents();
|
|
}
|
|
|
|
fileWriter.WriteLine();
|
|
}
|
|
|
|
fileWriter.Flush();
|
|
}
|
|
|
|
catch (System.Exception ex)
|
|
{
|
|
throw new Exception("encountered problem writing FTSS/Excel channel headers", ex);
|
|
}
|
|
}
|
|
|
|
public double Start { get; set; } = 0D;
|
|
|
|
public double Stop { get; set; } = 0D;
|
|
|
|
public ushort SubSampleInterval { get; set; } = 1;
|
|
|
|
public bool Filtered { get; set; } = true;
|
|
}
|
|
}
|
|
}
|