/*
* ToyotaCsv.File.Writer.cs
*
* Copyright © 2009
* Diversified Technical Systems, Inc.
* All Rights Reserved
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Text;
using DTS.Common.DAS.Concepts;
using DTS.Common.Utilities.DotNetProgrammingConstructs;
using DTS.Common.Enums.Sensors;
using DTS.Common.Utilities.Logging;
namespace DTS.Serialization.ToyotaCsv
{
// *** see ToyotaCsv.File.cs ***
public partial class File
{ ///
///
/// Utility object for serializing s to disk
/// in the Toyota CSV format.
///
///
public class Writer : Writer, IWriter
{
///
/// Initialize an instance of the ToyotaCsv.File.Writer class.
///
///
///
/// The associated object.
///
///
internal Writer(File fileType, int encoding)
: base(fileType, encoding)
{
}
private const string NUMBER_FORMAT = "F8";
///
/// The different export modes that should theoretically be supported by this format.
///
public enum ExportMode
{
FtssExcel,
Ttc,
Standard,
}
///
/// Get/set the current CSV export format.
///
public ExportMode CurrentExportMode
{
get => _CurrentExportMode.Value;
set => _CurrentExportMode.Value = value;
}
private readonly Property _CurrentExportMode
= new Property(
typeof(Writer).Namespace + ".File.Writer.CurrentExportMode",
ExportMode.FtssExcel,
true
);
///
/// Notify subscribers that the write
/// is starting.
///
public event BeginEventHandler OnBegin;
///
/// Notify subscribers that the write
/// is finished.
///
public event EndEventHandler OnEnd;
///
/// Notify subscribers that we are one
/// tick closer to write completion.
///
public event TickEventHandler OnTick;
///
/// notify subscribers that writing is cancelling
///
public event CancelEventHandler OnCancel;
///
/// notify subscribers that writer encountered fatal error
///
public event ErrorEventHandler OnError;
///
/// The number of data samples that need to be written for a "tick" to be dispatched.
///
private int DataSamplesPerTick => 1000;
///
/// Return the number of data to be written per "tick".
///
///
///
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.ToString() + "\"" : ""), ex);
}
}
///
/// Write the specified test to the specified pathname.
///
///
///
/// The pathname to which the specified test should be serialized.
///
///
///
/// The to be written out.
///
///
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);
}
}
///
/// Get/set the filtered channel data. If this list is supplied, the corresponding test
/// channel data values will be supplied from this list.
///
public List FilteredChannelData
{
get => _FilteredChannelData.Value;
set => _FilteredChannelData.Value = value;
}
private readonly Property> _FilteredChannelData
= new Property>(
typeof(Writer).FullName + ".FilteredChannelData",
new List(),
true
);
///
/// Write the representation file/files of the specified DTS.Serialization.Test
/// at the given pathname.
///
///
///
/// The pathname of the specified object's resulting file
/// representation.
///
///
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 fs = new FileStream(pathname, FileMode.Create);
using (var fileWriter = new StreamWriter(fs, Encoding.Unicode))
{
var exportChannels = test.Channels;
switch (CurrentExportMode)
{
case ExportMode.FtssExcel:
case ExportMode.Ttc:
WriteChannelInfo(fileWriter,
test,
FilteredChannelData,
tickEventHandler,
totalWriteTicksNeeded,
ref totalWriteTicksDispatched,
cancelRequested);
break;
default:
throw new NotSupportedException("ToyotaCSV::File::Writer export mode not supported: " + CurrentExportMode.ToString());
}
}
}
catch (System.Exception ex)
{
exception = new Exception("encountered problem writing TTS test files", ex);
APILogger.Log("encountered problem writing TTS 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)
{
}
///
/// A collection of a channel and associated metadata that will all be needed together when
/// this file format starts laying down bytes.
///
private sealed class ChannelWithMeta
{
///
/// Get/set the to be serialized.
///
public Test.Module.Channel Channel { get; set; }
///
/// Get/set the associated with the
/// specified channel.
///
public DataScaler Scaler { get; }
///
/// Get/set the sample rate associated with the specified channel.
///
public double SampleRate { get; }
///
/// Get/set the start time associated with the specified channel.
///
public double StartTime { get; }
///
/// Initialize an instance of the ChannelWithMeta class.
///
///
///
/// A to be serialized.
///
///
///
/// The containing scaling information for
/// the specified channel.
///
///
///
/// The sample rate of the associated channel.
///
///
///
/// The start time of the associated channel.
///
///
public ChannelWithMeta(Test.Module.Channel channel, DataScaler scaler, double samplerate, double starttime)
{
Channel = channel;
Scaler = scaler;
SampleRate = samplerate;
StartTime = starttime;
}
}
///
/// Write the specified test to the specified stream.
///
///
///
/// The to which the specified test should be serialized.
///
///
///
/// The to be serialized.
///
///
///
/// The that should handle write-progress
/// reporting; null if none is to be used.
///
///
protected void WriteChannelInfo
(
StreamWriter fileWriter,
Test test,
List filteredData,
TickEventHandler tickEventHandler,
uint totalWriteTicksNeeded,
ref uint totalWriteTicksDispatched,
CancelRequested cancelRequested
)
{
try
{
// Row 1: Test number
fileWriter.WriteLine(test.Id);
// Row 2:
fileWriter.WriteLine(test.Id);
// Row 3: Test Date
fileWriter.WriteLine($"{test.InceptionDate.Year - 2000}-{test.InceptionDate.Month}-{test.InceptionDate.Day}");
// Row 4: Impact Speed
fileWriter.WriteLine("");
// Row 5, 6:
fileWriter.WriteLine();
fileWriter.WriteLine();
// Row 7:
fileWriter.WriteLine(test.Description);
// Row 8: Sampling Rate
fileWriter.WriteLine(test.Modules.Count > 0 ? test.Modules[0].SampleRateHz.ToString() : "NO MODULES");
// Row 9, 10, 11, 12:
fileWriter.WriteLine();
fileWriter.WriteLine();
fileWriter.WriteLine();
fileWriter.WriteLine();
// Row 13:
//fprintf(fpExport, "%s,\n", TestPtr->VehicleList[0].Description);
// Row 14: Dummies
fileWriter.WriteLine();
//for(iIndex = 0; iIndex < MAX_DEVICE_DISPLAY; iIndex++)
//{
// if(strlen(TestPtr->DeviceList[iIndex].DeviceID) > 0)
// {
// fprintf(fpExport, "%s,", TestPtr->DeviceList[iIndex].DeviceID);
// }
//}
//fprintf(fpExport, "\n");
// Row 15, 16, 17, 18, 19, 20:
fileWriter.WriteLine();
fileWriter.WriteLine();
fileWriter.WriteLine();
fileWriter.WriteLine();
fileWriter.WriteLine();
fileWriter.WriteLine();
// Row 21: Header: Time and codes
fileWriter.Write("TIME,");
foreach (var channel in test.Channels)
fileWriter.Write(channel.ChannelDescriptionString + ",");
fileWriter.WriteLine();
//for(iIndex = 0; iIndex < iChannelListCount; iIndex++)
//{
// RackIndex = ChannelList[iIndex].iRack;
// ModuleIndex = ChannelList[iIndex].iModule;
// ChannelIndex = ChannelList[iIndex].iChannel;
// ChannelPtr = &SystemPtr->HWRack[RackIndex].HWModuleList[ModuleIndex].HWChannelList[ChannelIndex];
// if(IsChannelValid(ChannelPtr))
// {
// if(strlen(ChannelPtr->SensorUserChannelDescription) > 0)
// {
// fprintf(fpExport, "%s,", ChannelPtr->SensorUserChannelDescription);
// }
// else if(ChannelPtr->UserPointerValid[0])
// {
// fprintf(fpExport, "%s,", ((SensorDBItem *)(ChannelPtr->UserPointer[0]))->Code);
// }
// }
//}
//fprintf(fpExport, "\n");
// Row 22, 23, 24:
fileWriter.WriteLine();
fileWriter.WriteLine();
fileWriter.WriteLine();
var channelsWithMeta = new List();
var filteredDataIndex = 0;
foreach (var channel in test.Channels)
{ //
// Bundle data and associated scaling information for in preparation for
// upcoming write.
//
var scaler = new DataScaler();
scaler.IsInverted = channel is Common.DAS.Concepts.DAS.Channel.IInversionAware ? (channel as Common.DAS.Concepts.DAS.Channel.IInversionAware).IsInverted : false;
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;
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.SetDigitalMultiplier((channel as Test.Module.AnalogInputChannel).DigitalMultiplier);
scaler.DigitalMode = (channel as Test.Module.AnalogInputChannel).DigitalMode;
}
if ((channel as Common.DAS.Concepts.DAS.Channel.ILinearized).LinearizationFormula.IsValid())
{
scaler.SetLinearizationFormula((channel as Common.DAS.Concepts.DAS.Channel.ILinearized).LinearizationFormula);
}
else
{
scaler.SetLinearizationFormula(null);
}
try
{
scaler.SetRemovedADC(channel.RemovedADC);
scaler.SetRemovedInternalADC(channel.RemovedInternalADC);
scaler.SetDataZeroLevelADC(channel.DataZeroLevelAdc);
scaler.SetZeroMvInADC(channel.ZeroMvInADC);
try { scaler.SetWindowAverageADC(channel.WindowAverageADC); }
catch (System.Exception) { }
//try { scaler.SetIRTraccZeros(Convert.ToDouble(channel.DataZeroLevelAdc), Convert.ToDouble(channel.PreTestZeroLevelAdc)); }
//catch (System.Exception) { }
//initialEU
}
catch (System.Exception) { }
scaler.SetMvPerEu(channel.Data.MvPerEu);
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;
try
{
scaler.SetInitialOffset(analogChannel.InitialOffset);
}
catch (System.Exception) { }
scaler.ZeroMethodType = analogChannel.ZeroMethod;
scaler.NominalExcitationVoltage = analogChannel.ExcitationVoltage;
try { scaler.FactoryExcitationVoltage = analogChannel.FactoryExcitationVoltage; }
catch { };
try { scaler.MeasuredExcitationVoltage = analogChannel.MeasuredExcitationVoltage; }
catch { };
scaler.ProportionalToExcitation = analogChannel.ProportionalToExcitation;
}
channelsWithMeta.Add(
new ChannelWithMeta(
channel,
scaler,
channel.ParentModule.SampleRateHz,
channel.ParentModule.TriggerSampleNumbers.Count > 0 ? channel.ParentModule.TriggerSampleNumbers[0] / channel.ParentModule.SampleRateHz * -1.0 : 0
)
);
}
var minStartTime = channelsWithMeta.Min(cwm => cwm.StartTime);
var maxStartTime = channelsWithMeta.Max(cwm => cwm.StartTime);
// LINQ statement will sometimes hand back float-rounding error numbers (4.999999999999995 v 5.0), use same rounding we use for time.
var minStopTime = double.Parse(channelsWithMeta.Min(cwm => cwm.StartTime + (cwm.Channel.PersistentChannelInfo.Length - 1) * 1.0 / cwm.SampleRate).ToString(NUMBER_FORMAT));
var maxStopTime = double.Parse(channelsWithMeta.Max(cwm => cwm.StartTime + (cwm.Channel.PersistentChannelInfo.Length - 1) * 1.0 / cwm.SampleRate).ToString(NUMBER_FORMAT));
var minSampleRate = channelsWithMeta.Min(cwm => cwm.SampleRate);
var maxSampleRate = channelsWithMeta.Max(cwm => cwm.SampleRate);
if (minSampleRate != maxSampleRate)
throw new UserException("Lowest sample rate (" + minSampleRate.ToString() + ") does not match highest sample rate (" + maxSampleRate.ToString() + ").\nThey must be identical.");
var sampleRate = minSampleRate;
var dataCollectionLength = (int)((maxStopTime - minStartTime) * sampleRate); //maxDataLengthInAllChannels;
for (var i = 0; i < dataCollectionLength; i++)
{
if (null != cancelRequested && cancelRequested()) { break; }
double time;
fileWriter.Write((time = minStartTime + i * 1.0 / sampleRate).ToString("F6") + ",");
foreach (var channelWithMeta in channelsWithMeta)
{
if (null != cancelRequested && cancelRequested()) { break; }
//
// 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 thisChannelsIndexAtCurrentTime = i - Convert.ToInt32(delta * Convert.ToDecimal(channelWithMeta.SampleRate));
if (null != filteredData && filteredData.Count > 0)
fileWriter.Write((thisChannelsIndexAtCurrentTime >= 0 && thisChannelsIndexAtCurrentTime < filteredData[filteredDataIndex].Length
? (filteredData[filteredDataIndex][thisChannelsIndexAtCurrentTime]).ToString("F6")
: "") + ",");
else
fileWriter.Write((thisChannelsIndexAtCurrentTime >= 0 && thisChannelsIndexAtCurrentTime < channelWithMeta.Channel.PersistentChannelInfo.Length
? channelWithMeta.Scaler.GetEU(channelWithMeta.Channel.PersistentChannelInfo[thisChannelsIndexAtCurrentTime]).ToString("F6")
: "") + ",");
filteredDataIndex++;
}
if (0 == i % DataSamplesPerTick)
{
OnTick?.Invoke(this, ((double)totalWriteTicksDispatched++) / totalWriteTicksNeeded * 100);
System.Windows.Forms.Application.DoEvents();
}
fileWriter.WriteLine();
}
}
catch (System.Exception ex)
{
throw new Exception("encountered problem writing FTSS/Excel channel headers", ex);
}
}
}
}
}