init
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* TDAS.File.Writer.cs
|
||||
*
|
||||
* Copyright © 2009
|
||||
* Diversified Technical Systems, Inc.
|
||||
* All Rights Reserved
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using DTS.Common.Utilities.Logging;
|
||||
|
||||
namespace DTS.Serialization.TDAS
|
||||
{
|
||||
// *** see TDAS.File.cs ***
|
||||
public partial class File
|
||||
{ ///
|
||||
/// <summary>
|
||||
/// Utility object for serializing <see cref="DTS.Serialization.Test"/>s to disk
|
||||
/// in the Diadem
|
||||
/// </summary>
|
||||
///
|
||||
public class Writer : Writer<File>, IWriter<Test>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initialize an instance of the Diadem.File.Writer class.
|
||||
/// </summary>
|
||||
///
|
||||
/// <param name="fileType">
|
||||
/// The associated <see cref="DTS.SErialization.Diadem.File"/> object.
|
||||
/// </param>
|
||||
///
|
||||
internal Writer(File fileType, int encoding)
|
||||
: base(fileType, encoding)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/// <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)
|
||||
{
|
||||
throw new NotSupportedException("TDAS::File::Writer Write(pathname, id, test, bFiltering) not supported");
|
||||
}
|
||||
private const string TLF_FILE_BACKUP_EXTENSION = ".TLF PreTest Backup";
|
||||
/// <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
|
||||
{
|
||||
beginEventHandler?.Invoke(this, Convert.ToUInt32(test.Channels.Count));
|
||||
var tlf = new TLF(pathname, test, id);
|
||||
using (var sw = new StreamWriter(pathname, false, Encoding.Default))
|
||||
{
|
||||
tlf.Serialize(sw);
|
||||
sw.Flush();
|
||||
sw.Close();
|
||||
}
|
||||
var tlfFileInfo = new FileInfo(pathname);
|
||||
var backupTLFFileName = tlfFileInfo.Name.Replace(tlfFileInfo.Extension, TLF_FILE_BACKUP_EXTENSION);
|
||||
var backupFilePath = Path.Combine(tlfFileInfo.DirectoryName, backupTLFFileName);
|
||||
try
|
||||
{
|
||||
System.IO.File.Copy(tlfFileInfo.FullName, backupFilePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
APILogger.Log($"Failed to backup file {tlfFileInfo.FullName} to {backupFilePath}", ex);
|
||||
}
|
||||
|
||||
var startingChannel = tlf.LastChannelNumber + 1;
|
||||
var startingSquibChannel = 901;
|
||||
var maxSampleRate = test.Channels.Select(ch => ch.ParentModule.SampleRateHz).Max();
|
||||
for (var i = 0; i < test.Channels.Count; i++)
|
||||
{
|
||||
var fi = new FileInfo(pathname);
|
||||
var newFile = string.Empty;
|
||||
if ((test.Channels[i] as Test.Module.AnalogInputChannel).IsSquibChannel)
|
||||
{
|
||||
newFile = fi.FullName.Replace(fi.Extension, string.Format("{0:000}.BIN", startingSquibChannel));
|
||||
startingSquibChannel++;
|
||||
}
|
||||
else
|
||||
{
|
||||
newFile = fi.FullName.Replace(fi.Extension, string.Format("{0:000}.BIN", startingChannel));
|
||||
startingChannel++;
|
||||
}
|
||||
using (var bw = new BinaryWriter(new FileStream(newFile, FileMode.OpenOrCreate)))
|
||||
{
|
||||
var bin = new TLFBin(test.Channels[i], maxSampleRate);
|
||||
bin.Serialize(bw, test.Channels[i]);
|
||||
bw.Flush();
|
||||
bw.Close();
|
||||
}
|
||||
tickEventHandler?.Invoke(this, Convert.ToDouble(i + 1));
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
exception = ex;
|
||||
APILogger.Log("encountered problem writing TDAS test files", ex);
|
||||
}
|
||||
|
||||
if (null != errorEventHandler && null != exception)
|
||||
{
|
||||
endEventHandler(this);
|
||||
errorEventHandler(this, exception);
|
||||
}
|
||||
else if (null != exception) { throw exception; }
|
||||
else
|
||||
{
|
||||
tickEventHandler?.Invoke(this, 100.0);
|
||||
endEventHandler?.Invoke(this);
|
||||
}
|
||||
}
|
||||
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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* SoMat.File.cs
|
||||
*
|
||||
* Copyright © 2013
|
||||
* Diversified Technical Systems, Inc.
|
||||
* All Rights Reserved
|
||||
*/
|
||||
|
||||
namespace DTS.Serialization.DDAS
|
||||
{
|
||||
///
|
||||
/// <summary>
|
||||
/// File
|
||||
/// </summary>
|
||||
///
|
||||
public partial class File
|
||||
: Serialization.File, IWritable<Test>
|
||||
{ ///
|
||||
/// <summary>
|
||||
/// Initialize an instance of the FtssCsv.File class.
|
||||
/// </summary>
|
||||
///
|
||||
public File()
|
||||
: base("DDAS")
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get this file format's extension.
|
||||
/// </summary>
|
||||
public static string Extension => ".ddas";
|
||||
|
||||
/// <summary>
|
||||
/// Get the file writer for this file type.
|
||||
/// </summary>
|
||||
public IWriter<Test> Exporter
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
if (null == _Exporter) { _Exporter = new Writer(this, DefaultEncoding); }
|
||||
return _Exporter;
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
throw new Exception("encountered problem getting exporter", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
private IWriter<Test> _Exporter = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
using DTS.Serialization.IRIGCH10.Enums;
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
namespace DTS.Serialization.IRIGCH10
|
||||
{
|
||||
/// <summary>
|
||||
/// implements the PCM/Pulse Control Modulation packet for CH 10
|
||||
/// </summary>
|
||||
public class PCMPacket : AbstractDataPacket, IDataPacket
|
||||
{
|
||||
private byte [] ChannelSpecificData;
|
||||
|
||||
public PCMPacket()
|
||||
: base(DataFileDataTypes.PCMDataFormat1)
|
||||
{
|
||||
ChannelSpecificData = new byte[4];
|
||||
_dataBytes = new byte[ChannelSpecificData.Length];
|
||||
}
|
||||
/// <summary>
|
||||
/// Intra-Packet Header (IPH). (Bit 30) indicates if Intra-Packet Headers (IntraPacket Time Stamp and Intra-Packet Data Header) are inserted before each
|
||||
/// minor frame. Intra-Packet Headers are only optional because of the mode
|
||||
/// selection. This determines whether Intra-Packet Headers are included or
|
||||
/// omitted.
|
||||
/// false = Intra-Packet Headers are omitted for Throughput Mode.
|
||||
/// true = Intra-Packet Headers are required for Packed Data and Unpacked Data
|
||||
/// modes.
|
||||
/// </summary>
|
||||
public bool IntraPacketHeader
|
||||
{
|
||||
get
|
||||
{
|
||||
var bitArray = new BitArray(ChannelSpecificData);
|
||||
return bitArray[30];
|
||||
}
|
||||
set
|
||||
{
|
||||
var bitArray = new BitArray(ChannelSpecificData);
|
||||
bitArray[30] = value;
|
||||
bitArray.CopyTo(ChannelSpecificData, 0);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Major Frame Indicator (MA). (Bit 29) indicates if the first word in the packet is
|
||||
/// the beginning of a major frame. Not valid for Throughput Mode.
|
||||
/// false = First word is not the beginning of a major frame.
|
||||
/// true = First word is the beginning of a major frame.
|
||||
/// </summary>
|
||||
public bool MajorFrameIndicator
|
||||
{
|
||||
get
|
||||
{
|
||||
var bitArray = new BitArray(ChannelSpecificData);
|
||||
return bitArray[29];
|
||||
}
|
||||
set
|
||||
{
|
||||
var bitArray = new BitArray(ChannelSpecificData);
|
||||
bitArray[29] = value;
|
||||
bitArray.CopyTo(ChannelSpecificData, 0);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Minor Frame Indicator (MI). (Bit 28) indicates if the first word in the packet is
|
||||
/// the beginning of a minor frame. Not valid for Throughput Mode.
|
||||
/// false = First word is not the beginning of a minor frame.
|
||||
/// true = First word is the beginning of a minor frame.
|
||||
/// </summary>
|
||||
public bool MinorFrameIndicator
|
||||
{
|
||||
get
|
||||
{
|
||||
var bitArray = new BitArray(ChannelSpecificData);
|
||||
return bitArray[28];
|
||||
}
|
||||
set
|
||||
{
|
||||
var bitArray = new BitArray(ChannelSpecificData);
|
||||
bitArray[28] = value;
|
||||
bitArray.CopyTo(ChannelSpecificData, 0);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Indicate Minor Frame Status
|
||||
/// true = Minor Frame Lock
|
||||
/// false = Minor Frame Check (after losing lock)
|
||||
/// </summary>
|
||||
public bool MinorFrameLockStatus
|
||||
{
|
||||
get
|
||||
{
|
||||
var bitArray = new BitArray(ChannelSpecificData);
|
||||
return bitArray[27] && bitArray[26];
|
||||
}
|
||||
set
|
||||
{
|
||||
var bitArray = new BitArray(ChannelSpecificData);
|
||||
bitArray[27] = true;
|
||||
bitArray[26] = value;
|
||||
bitArray.CopyTo(ChannelSpecificData, 0);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// true - Major Frame Lock
|
||||
/// false - Major Frame Check (after losing Lock)
|
||||
/// </summary>
|
||||
public bool MajorFrameStatus
|
||||
{
|
||||
get
|
||||
{
|
||||
var bitArray = new BitArray(ChannelSpecificData);
|
||||
return bitArray[25] && bitArray[24];
|
||||
}
|
||||
set
|
||||
{
|
||||
var bitArray = new BitArray(ChannelSpecificData);
|
||||
|
||||
bitArray[25] = true;
|
||||
bitArray[24] = value;
|
||||
|
||||
bitArray.CopyTo(ChannelSpecificData, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// true = 32Bit Alignment Mode
|
||||
/// false = 16Bit Alignment Mode
|
||||
/// </summary>
|
||||
public bool AlignmentMode
|
||||
{
|
||||
get
|
||||
{
|
||||
var bitArray = new BitArray(ChannelSpecificData);
|
||||
return bitArray[21];
|
||||
}
|
||||
set
|
||||
{
|
||||
var bitArray = new BitArray(ChannelSpecificData);
|
||||
|
||||
bitArray[21] = value;
|
||||
|
||||
bitArray.CopyTo(ChannelSpecificData, 0);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Throughput DataMode enabled or not
|
||||
/// </summary>
|
||||
public bool ThroughputDataMode
|
||||
{
|
||||
get
|
||||
{
|
||||
var bitArray = new BitArray(ChannelSpecificData);
|
||||
return bitArray[20];
|
||||
}
|
||||
set
|
||||
{
|
||||
var bitArray = new BitArray(ChannelSpecificData);
|
||||
bitArray[20] = value;
|
||||
bitArray.CopyTo(ChannelSpecificData, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public bool PackedData
|
||||
{
|
||||
get
|
||||
{
|
||||
var bitArray = new BitArray(ChannelSpecificData);
|
||||
return bitArray[19];
|
||||
}
|
||||
set
|
||||
{
|
||||
var bitArray = new BitArray(ChannelSpecificData);
|
||||
bitArray[19] = value;
|
||||
bitArray.CopyTo(ChannelSpecificData, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public bool UnpackedData
|
||||
{
|
||||
get
|
||||
{
|
||||
var bitArray = new BitArray(ChannelSpecificData);
|
||||
return bitArray[18];
|
||||
}
|
||||
set
|
||||
{
|
||||
var bitArray = new BitArray(ChannelSpecificData);
|
||||
bitArray[18] = value;
|
||||
bitArray.CopyTo(ChannelSpecificData, 0);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// contains an 18-bit binary value
|
||||
/// representing the Word offset into the major frame for the first data word in the
|
||||
/// packet. Not valid for Packed or Throughput Mode.
|
||||
/// </summary>
|
||||
public byte[] SyncOffset
|
||||
{
|
||||
get
|
||||
{
|
||||
var output = new byte[2];
|
||||
var bitArray = new BitArray(ChannelSpecificData);
|
||||
var bitArray2 = new BitArray(output);
|
||||
for (int i = 0; i <= 17; i++)
|
||||
{
|
||||
bitArray2[i] = bitArray[i];
|
||||
}
|
||||
bitArray2.CopyTo(output, 0);
|
||||
|
||||
return output;
|
||||
}
|
||||
set
|
||||
{
|
||||
var bitArray = new BitArray(ChannelSpecificData);
|
||||
var bitArray2 = new BitArray(value);
|
||||
|
||||
for (int i = 0; i <= 17; i++)
|
||||
{
|
||||
bitArray[i] = bitArray2[i];
|
||||
}
|
||||
bitArray.CopyTo(ChannelSpecificData, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly byte[] FrameSync = new byte[] { 0x90, 0xEB };
|
||||
|
||||
//private readonly byte[] FrameSync = new byte[] { 0x90, 0xEB, 0xEC, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x90, 0xEB };
|
||||
|
||||
/// <summary>
|
||||
/// adds data in unpacked 16 bit alignment into the data buffer, resizing as needed
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
public void AddUnpacked16BitData(ushort[] data, long timeStamp, bool bFirst)
|
||||
{
|
||||
var intraPacketBytes = GetIntraPacketTimeStampBytes(timeStamp);
|
||||
var intraHeaderBytes = GetIntraPacketHeader();
|
||||
var insertSpot = 0;
|
||||
|
||||
//for some reason the spec seems to call out a different first order than all subsequent data sequences.
|
||||
//the first sequence has a timestamp at the front, while all subsequent do not, but have a trailer timestamp.
|
||||
if (bFirst)
|
||||
{
|
||||
var newBuffer = new byte[_dataBytes.Length + data.Length * 2 + FrameSync.Length + intraPacketBytes.Length + intraHeaderBytes.Length];
|
||||
Buffer.BlockCopy(_dataBytes, 0, newBuffer, insertSpot, _dataBytes.Length);
|
||||
insertSpot += _dataBytes.Length;
|
||||
Buffer.BlockCopy(intraPacketBytes, 0, newBuffer, insertSpot, intraPacketBytes.Length);
|
||||
insertSpot += intraPacketBytes.Length;
|
||||
Buffer.BlockCopy(intraHeaderBytes, 0, newBuffer, insertSpot, intraHeaderBytes.Length);
|
||||
insertSpot += intraHeaderBytes.Length;
|
||||
Buffer.BlockCopy(FrameSync, 0, newBuffer, insertSpot, FrameSync.Length);
|
||||
insertSpot += FrameSync.Length;
|
||||
Buffer.BlockCopy(data, 0, newBuffer, insertSpot, data.Length * 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
var newBuffer = new byte[_dataBytes.Length + data.Length * 2 + intraPacketBytes.Length + intraHeaderBytes.Length];
|
||||
Buffer.BlockCopy(_dataBytes, 0, newBuffer, insertSpot, _dataBytes.Length);
|
||||
insertSpot += _dataBytes.Length;
|
||||
Buffer.BlockCopy(data, 0, newBuffer, insertSpot, data.Length * 2);
|
||||
insertSpot += data.Length * 2;
|
||||
Buffer.BlockCopy(intraPacketBytes, 0, newBuffer, insertSpot, intraPacketBytes.Length);
|
||||
insertSpot += intraPacketBytes.Length;
|
||||
Buffer.BlockCopy(intraHeaderBytes, 0, newBuffer, insertSpot, intraHeaderBytes.Length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// adds data in unpacked 16 bit alignment into the data buffer, resizing as needed
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
public void AddPacked16BitData(ushort[] data, long timeStamp, bool bFirst)
|
||||
{
|
||||
var intraPacketBytes = GetIntraPacketTimeStampBytes(timeStamp);
|
||||
var intraHeaderBytes = GetIntraPacketHeader();
|
||||
var insertSpot = 0;
|
||||
|
||||
//for some reason the spec seems to call out a different first order than all subsequent data sequences.
|
||||
//the first sequence has a timestamp at the front, while all subsequent do not, but have a trailer timestamp.
|
||||
if (bFirst)
|
||||
{
|
||||
var newBuffer = new byte[_dataBytes.Length + data.Length * 2 + FrameSync.Length + intraPacketBytes.Length + intraHeaderBytes.Length];
|
||||
Buffer.BlockCopy(_dataBytes, 0, newBuffer, insertSpot, _dataBytes.Length);
|
||||
insertSpot += _dataBytes.Length;
|
||||
Buffer.BlockCopy(intraPacketBytes, 0, newBuffer, insertSpot, intraPacketBytes.Length);
|
||||
insertSpot += intraPacketBytes.Length;
|
||||
Buffer.BlockCopy(intraHeaderBytes, 0, newBuffer, insertSpot, intraHeaderBytes.Length);
|
||||
insertSpot += intraHeaderBytes.Length;
|
||||
Buffer.BlockCopy(FrameSync, 0, newBuffer, insertSpot, FrameSync.Length);
|
||||
insertSpot += FrameSync.Length;
|
||||
Buffer.BlockCopy(data, 0, newBuffer, insertSpot, data.Length * 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
var newBuffer = new byte[_dataBytes.Length + data.Length * 2 + intraPacketBytes.Length + intraHeaderBytes.Length];
|
||||
Buffer.BlockCopy(_dataBytes, 0, newBuffer, insertSpot, _dataBytes.Length);
|
||||
insertSpot += _dataBytes.Length;
|
||||
Buffer.BlockCopy(data, 0, newBuffer, insertSpot, data.Length * 2);
|
||||
insertSpot += data.Length * 2;
|
||||
Buffer.BlockCopy(intraPacketBytes, 0, newBuffer, insertSpot, intraPacketBytes.Length);
|
||||
insertSpot += intraPacketBytes.Length;
|
||||
Buffer.BlockCopy(intraHeaderBytes, 0, newBuffer, insertSpot, intraHeaderBytes.Length);
|
||||
}
|
||||
}
|
||||
|
||||
private byte [] GetIntraPacketHeader()
|
||||
{
|
||||
var array = new BitArray(new byte[2]);
|
||||
//bits 15-14 minor frame status
|
||||
//00,01 reserved
|
||||
//10 minor frame check
|
||||
//11 minor frame lock
|
||||
|
||||
//bits 13-12 major frame status
|
||||
// 00 - major frame not locked
|
||||
// 01 - reserved
|
||||
// 10 major frame check
|
||||
// 11 major frame lock
|
||||
array.Set(15, true);
|
||||
array.Set(14, true);
|
||||
|
||||
array.Set(12, true);
|
||||
array.Set(13, true);
|
||||
|
||||
var output = new byte[2];
|
||||
|
||||
array.CopyTo(output, 0);
|
||||
return output;
|
||||
}
|
||||
private byte [] GetIntraPacketTimeStampBytes(long timeStamp)
|
||||
{
|
||||
var output = new byte[8];
|
||||
var stamp = IRIGCH10.PacketHeader.GetRTCBytes(timeStamp);
|
||||
Buffer.BlockCopy(output, 0, stamp, 0, stamp.Length);
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// adds data in throughput mode to the packet, resizing the data buffer in the process
|
||||
/// [throughput mode as opposed to a packed/or aligned mode]
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
//public void AddThroughputData(ushort[] data)
|
||||
//{
|
||||
// var newBuffer = new byte[_dataBytes.Length + data.Length * 2 + FrameSync.Length];
|
||||
// Buffer.BlockCopy(_dataBytes, 0, newBuffer, 0, _dataBytes.Length);
|
||||
// Buffer.BlockCopy(FrameSync, 0, newBuffer, _dataBytes.Length, FrameSync.Length);
|
||||
// Buffer.BlockCopy(data, 0, newBuffer, _dataBytes.Length + FrameSync.Length, data.Length * 2);
|
||||
// _dataBytes = newBuffer;
|
||||
//}
|
||||
public override void ComputeCheckSum()
|
||||
{
|
||||
RefreshChannelSpecificDataInDataBytes();
|
||||
base.ComputeCheckSum();
|
||||
}
|
||||
public override byte[] GetBytes()
|
||||
{
|
||||
RefreshChannelSpecificDataInDataBytes();
|
||||
return base.GetBytes();
|
||||
}
|
||||
|
||||
private void RefreshChannelSpecificDataInDataBytes()
|
||||
{
|
||||
//make sure ChannelSpecificData is set ...
|
||||
Buffer.BlockCopy(ChannelSpecificData, 0, _dataBytes, 0, ChannelSpecificData.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,912 @@
|
||||
/*
|
||||
* FtssTsv.File.Writer.cs
|
||||
*
|
||||
* Copyright © 2009
|
||||
* Diversified Technical Systems, Inc.
|
||||
* All Rights Reserved
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using DTS.Common.DAS.Concepts;
|
||||
using DTS.Common.DAS.Concepts.DAS.Channel;
|
||||
using DTS.Common.Utilities;
|
||||
using DTS.Common.Utilities.DotNetProgrammingConstructs;
|
||||
using DTS.Common.Utilities.Logging;
|
||||
using DTS.Common;
|
||||
using DTS.Common.Enums.Sensors;
|
||||
using DTS.Common.Utils;
|
||||
|
||||
namespace DTS.Serialization.FtssTsv
|
||||
{
|
||||
// *** see FtssTsv.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 FtssTsv.File.Writer class.
|
||||
/// </summary>
|
||||
///
|
||||
/// <param name="fileType">
|
||||
/// The associated <see cref="DTS.SErialization.FtssTsv.File"/> object.
|
||||
/// </param>
|
||||
///
|
||||
internal Writer(File fileType, int encoding)
|
||||
: base(fileType, encoding)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The different export modes that should theoretically be supported by this format.
|
||||
/// </summary>
|
||||
public enum ExportMode
|
||||
{
|
||||
FtssExcel,
|
||||
Ttc,
|
||||
Standard,
|
||||
}
|
||||
|
||||
private const string NUMBER_FORMAT = "F8";
|
||||
private const string TAB_LIST_SEPARATOR = "\t"; //Tab
|
||||
|
||||
/// <summary>
|
||||
/// Get/set the current CSV export format.
|
||||
/// </summary>
|
||||
public ExportMode CurrentExportMode
|
||||
{
|
||||
get => _CurrentExportMode.Value;
|
||||
set => _CurrentExportMode.Value = value;
|
||||
}
|
||||
private readonly Property<ExportMode> _CurrentExportMode
|
||||
= new Property<ExportMode>(
|
||||
typeof(Writer).Namespace + ".File.Writer.CurrentExportMode",
|
||||
ExportMode.FtssExcel,
|
||||
true
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Notify <see cref="DTS.Serialization.BeginEventHandler"/> subscribers that the write
|
||||
/// is starting.
|
||||
/// </summary>
|
||||
public event BeginEventHandler OnBegin;
|
||||
|
||||
/// <summary>
|
||||
/// Notify <see cref="DTS.Serialization.EndEventHandler"/> subscribers that the write
|
||||
/// is finished.
|
||||
/// </summary>
|
||||
public event EndEventHandler OnEnd;
|
||||
|
||||
/// <summary>
|
||||
/// Notify <see cref="DTS.Serialization.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.ToString() + "\"" : "<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 List<FilteredData> FilteredChannelData
|
||||
{
|
||||
get => _FilteredChannelData.Value;
|
||||
set => _FilteredChannelData.Value = value;
|
||||
}
|
||||
private readonly Property<List<FilteredData>> _FilteredChannelData
|
||||
= new Property<List<FilteredData>>(
|
||||
"FilteredChannelData",
|
||||
new List<FilteredData>(),
|
||||
true
|
||||
);
|
||||
|
||||
public string DataChannelFilename { get; set; }
|
||||
public string LaboratoryName { get; set; }
|
||||
public string LaboratoryContactName { get; set; }
|
||||
public string LaboratoryContactPhone { get; set; }
|
||||
public string LaboratoryContactEmail { get; set; }
|
||||
public string TestEngineerName { get; set; }
|
||||
public string TestEngineerPhone { get; set; }
|
||||
public string TestEngineerEmail { get; set; }
|
||||
public int NumChannelsWritten { get; set; }
|
||||
public bool UseISOCodeFilterMapping { get; set; }
|
||||
public bool UseZeroForUnfiltered { get; set; }
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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
|
||||
{
|
||||
//this.OnBegin += beginEventHandler; //Since we are called once for every file, we don't want to continually set progress to 0%
|
||||
//this.OnEnd += endEventHandler; //Nor do we want to increment the number of exports that have finished
|
||||
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 channelTicks = 0;
|
||||
uint totalWriteTicksNeeded = 0;
|
||||
if (test.Channels.Count > 0)
|
||||
{
|
||||
channelTicks = GetChannelTicks(test.Channels[0]);
|
||||
totalWriteTicksNeeded = channelTicks * (uint)test.Channels.Count;
|
||||
}
|
||||
var totalWriteTicksDispatched = channelTicks * (uint)NumChannelsWritten;
|
||||
|
||||
APILogger.Log("opening ", pathname);
|
||||
//DateTime start = DateTime.Now;
|
||||
if (!Directory.Exists(Path.GetDirectoryName(pathname)))
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(pathname));
|
||||
}
|
||||
|
||||
if (System.IO.File.Exists(pathname))
|
||||
{
|
||||
FileUtils.DeleteFileOrMove(pathname, APILogger.Log);
|
||||
}
|
||||
|
||||
using (var fileWriter = new StreamWriter(pathname, false, Encoding.Default))
|
||||
{
|
||||
//List<Test.Module.Channel> exportChannels = test.Channels;
|
||||
|
||||
switch (CurrentExportMode)
|
||||
{
|
||||
case ExportMode.FtssExcel:
|
||||
WriteChannelInfo(fileWriter,
|
||||
id,
|
||||
test,
|
||||
FilteredChannelData,
|
||||
pathname,
|
||||
tickEventHandler,
|
||||
totalWriteTicksNeeded,
|
||||
ref totalWriteTicksDispatched,
|
||||
cancelRequested,
|
||||
channelNumber,
|
||||
minStartTime,
|
||||
dataCollectionLength);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotSupportedException("FtssTsv::File::Writer ExportMode not supported: " + CurrentExportMode.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
APILogger.Log("encountered problem writing TSV test files", ex);
|
||||
exception = new Exception("encountered problem writing TSV test files", ex);
|
||||
}
|
||||
|
||||
finally
|
||||
{
|
||||
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>
|
||||
/// The various header lines (and associated text) that appear in this file format.
|
||||
/// </summary>
|
||||
private enum FtssHeaderLine
|
||||
{
|
||||
[Description("Headers")]
|
||||
Headers = 0,
|
||||
|
||||
[Description("Test Date")]
|
||||
TestDate,
|
||||
|
||||
[Description("Test Time")]
|
||||
TestTime,
|
||||
|
||||
[Description("Test ID")]
|
||||
TestId,
|
||||
|
||||
[Description("Test Description")]
|
||||
TestDescription,
|
||||
|
||||
[Description("Laboratory Name")]
|
||||
LaboratoryName,
|
||||
|
||||
[Description("Laboratory Contact Name")]
|
||||
LaboratoryContactName,
|
||||
|
||||
[Description("Laboratory Contact Phone")]
|
||||
LaboratoryContactPhone,
|
||||
|
||||
[Description("Laboratory Contact Email")]
|
||||
LaboratoryContactEmail,
|
||||
|
||||
[Description("Test Engineer Name")]
|
||||
TestEngineerName,
|
||||
|
||||
[Description("Test Engineer Phone")]
|
||||
TestEngineerPhone,
|
||||
|
||||
[Description("Test Engineer Email")]
|
||||
TestEngineerEmail,
|
||||
|
||||
[Description("Sample Rate (Hz)")]
|
||||
SampleRate,
|
||||
|
||||
[Description("Hardware AA Filter (-3dB)")]
|
||||
HardwareAntiAliasFilter,
|
||||
|
||||
[Description("Data Channel Name")]
|
||||
DataChannelName,
|
||||
|
||||
[Description("ISO Channel Code")]
|
||||
IsoCode,
|
||||
|
||||
[Description("Channel Description")]
|
||||
ChannelDescription,
|
||||
|
||||
[Description("Channel Location")]
|
||||
ChannelLocation,
|
||||
|
||||
[Description("Sensor S/N")]
|
||||
SensorSerialNumber,
|
||||
|
||||
[Description("Software Filter (SAE Class)")]
|
||||
SoftwareFilter,
|
||||
|
||||
[Description("Software Filter (-3dB)")]
|
||||
SoftwareFilterDb,
|
||||
|
||||
[Description("Engineering Unit")]
|
||||
EngineeringUnits,
|
||||
|
||||
[Description("User Comment")]
|
||||
UserComment,
|
||||
|
||||
[Description("Number of Pre-Zero Data Pts")]
|
||||
PreZero,
|
||||
|
||||
[Description("Number of Post-Zero Data Pts")]
|
||||
PostZero,
|
||||
|
||||
[Description("Data Zero (CNTS)")]
|
||||
DataZero,
|
||||
|
||||
[Description("Scale Factor (EU/CNT)")]
|
||||
ScaleEu,
|
||||
|
||||
[Description("Scale Factor (mV/CNT)")]
|
||||
ScaleMv,
|
||||
|
||||
[Description("Data Starts Here")]
|
||||
DataStart,
|
||||
|
||||
[Description("Time")]
|
||||
Labels,
|
||||
}
|
||||
|
||||
/// <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="DTS.Serialization.FtssTsv.File.Writer.FtssHeaderLine"/> header enumerations.
|
||||
/// </param>
|
||||
///
|
||||
/// <param name="channelsWithMeta">
|
||||
/// A populated <see cref="System.Collections.Generic.List<T>"/> of
|
||||
/// <see cref="DTS.Serialization.FtssTsv.File.Writer.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,
|
||||
IDictionary<FtssHeaderLine, List<string>> headerLines,
|
||||
DescriptionAttributeCoder<FtssHeaderLine> coder,
|
||||
List<ChannelWithMeta> channelsWithMeta,
|
||||
int dataCollectionLength,
|
||||
double minStartTime,
|
||||
double sampleRate,
|
||||
CancelRequested cancelRequested
|
||||
)
|
||||
{
|
||||
try
|
||||
{ //
|
||||
// Compute the size of the header information.
|
||||
//
|
||||
ulong predictedExportSize = 0;
|
||||
foreach (FtssHeaderLine ftssSizeHeaderLine in Enum.GetValues(typeof(FtssHeaderLine)))
|
||||
{
|
||||
if (headerLines.Keys.Contains(ftssSizeHeaderLine))
|
||||
{
|
||||
var channelValues = headerLines[ftssSizeHeaderLine];
|
||||
predictedExportSize += (ulong)(coder.DecodeAttributeValue(ftssSizeHeaderLine).Length + TAB_LIST_SEPARATOR.Length);
|
||||
foreach (var channelValue in channelValues)
|
||||
predictedExportSize += (ulong)(channelValue.Length + TAB_LIST_SEPARATOR.Length);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// 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) + TAB_LIST_SEPARATOR).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="System.IO.StreamWriter"/> to which the specified test should be serialized.
|
||||
/// </param>
|
||||
///
|
||||
/// <param name="test">
|
||||
/// The <see cref="DTS.Serialization.Test"/> to be serialized.
|
||||
/// </param>
|
||||
///
|
||||
/// <param name="writeEvent">
|
||||
/// The <see cref="FtssTsv.File.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="DTS.Serialization.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,
|
||||
List<FilteredData> filteredData,
|
||||
string targetPath,
|
||||
TickEventHandler tickEventHandler,
|
||||
uint totalWriteTicksNeeded,
|
||||
ref uint totalWriteTicksDispatched,
|
||||
CancelRequested cancelRequested,
|
||||
int channelNumber,
|
||||
double minStartTime,
|
||||
int dataCollectionLength)
|
||||
{
|
||||
try
|
||||
{
|
||||
//FB14621: Get the sample rate of the channels we're going to care about (i.e. channels with meta), rather than the whole test/roi
|
||||
var sampleRate = test.Channels.Find(ch => DataChannelFilename == Path.GetFileName(((Test.Module.AnalogInputChannel)ch).FileName)).ParentModule.SampleRateHz;
|
||||
|
||||
if (1 < SubSampleInterval)
|
||||
{
|
||||
var ChannelList = test.Channels;
|
||||
|
||||
for (var iChannel = 0; iChannel < ChannelList.Count; iChannel++)
|
||||
{
|
||||
if (true == Filtered)
|
||||
{
|
||||
var SubSample = new NHTSASubSample<double>();
|
||||
SubSample.data = FilteredChannelData[iChannel].Data;
|
||||
SubSample.preTriggerSamples = test.Modules[0].TriggerSampleNumbers[0] - test.Modules[0].StartRecordSampleNumber;
|
||||
SubSample.sampleRate = sampleRate;
|
||||
SubSample.subSampleInterval = SubSampleInterval;
|
||||
SubSample.SubSample();
|
||||
FilteredChannelData[iChannel].Data = SubSample.data;
|
||||
ChannelList[iChannel].IsSubsampled = true; // I don't think anyone will consume this, but for completeness.
|
||||
}
|
||||
else
|
||||
{
|
||||
var SubSample = new NHTSASubSample<short>();
|
||||
SubSample.data = ChannelList[iChannel].PersistentChannelInfo.Data;
|
||||
SubSample.preTriggerSamples = test.Modules[0].TriggerSampleNumbers[0] - test.Modules[0].StartRecordSampleNumber;
|
||||
SubSample.sampleRate = sampleRate;
|
||||
SubSample.subSampleInterval = SubSampleInterval;
|
||||
SubSample.SubSample();
|
||||
ChannelList[iChannel].PersistentChannelInfo.Data = SubSample.data;
|
||||
ChannelList[iChannel].IsSubsampled = true; // I don't think anyone will consume this, but for completeness.
|
||||
}
|
||||
}
|
||||
sampleRate = sampleRate / SubSampleInterval;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Initialize the header information.
|
||||
//
|
||||
var coder = new DescriptionAttributeCoder<FtssHeaderLine>();
|
||||
IDictionary<FtssHeaderLine, List<string>> headerLines = new Dictionary<FtssHeaderLine, List<string>>();
|
||||
|
||||
headerLines.Add(FtssHeaderLine.Headers, new List<string>());
|
||||
headerLines.Add(FtssHeaderLine.TestDate, new List<string>());
|
||||
headerLines.Add(FtssHeaderLine.TestTime, new List<string>());
|
||||
headerLines.Add(FtssHeaderLine.TestId, new List<string>());
|
||||
headerLines.Add(FtssHeaderLine.TestDescription, new List<string>());
|
||||
headerLines.Add(FtssHeaderLine.LaboratoryName, new List<string>());
|
||||
headerLines.Add(FtssHeaderLine.LaboratoryContactName, new List<string>());
|
||||
headerLines.Add(FtssHeaderLine.LaboratoryContactPhone, new List<string>());
|
||||
headerLines.Add(FtssHeaderLine.LaboratoryContactEmail, new List<string>());
|
||||
headerLines.Add(FtssHeaderLine.TestEngineerName, new List<string>());
|
||||
headerLines.Add(FtssHeaderLine.TestEngineerPhone, new List<string>());
|
||||
headerLines.Add(FtssHeaderLine.TestEngineerEmail, new List<string>());
|
||||
headerLines.Add(FtssHeaderLine.SampleRate, new List<string>());
|
||||
headerLines.Add(FtssHeaderLine.HardwareAntiAliasFilter, new List<string>());
|
||||
headerLines.Add(FtssHeaderLine.DataChannelName, new List<string>());
|
||||
headerLines.Add(FtssHeaderLine.IsoCode, new List<string>());
|
||||
headerLines.Add(FtssHeaderLine.ChannelDescription, new List<string>());
|
||||
headerLines.Add(FtssHeaderLine.ChannelLocation, new List<string>());
|
||||
headerLines.Add(FtssHeaderLine.SensorSerialNumber, new List<string>());
|
||||
headerLines.Add(FtssHeaderLine.SoftwareFilter, new List<string>());
|
||||
headerLines.Add(FtssHeaderLine.SoftwareFilterDb, new List<string>());
|
||||
headerLines.Add(FtssHeaderLine.EngineeringUnits, new List<string>());
|
||||
headerLines.Add(FtssHeaderLine.UserComment, new List<string>());
|
||||
headerLines.Add(FtssHeaderLine.PreZero, new List<string>());
|
||||
headerLines.Add(FtssHeaderLine.PostZero, new List<string>());
|
||||
headerLines.Add(FtssHeaderLine.DataZero, new List<string>());
|
||||
headerLines.Add(FtssHeaderLine.ScaleEu, new List<string>());
|
||||
headerLines.Add(FtssHeaderLine.ScaleMv, new List<string>());
|
||||
headerLines.Add(FtssHeaderLine.DataStart, new List<string>());
|
||||
headerLines.Add(FtssHeaderLine.Labels, new List<string>());
|
||||
|
||||
var channelsWithMeta = new List<ChannelWithMeta>();
|
||||
var allChannelsWithMeta = new List<ChannelWithMeta>();
|
||||
var filteredDataIndex = 0;
|
||||
|
||||
foreach (var channel in test.Channels)
|
||||
{
|
||||
if (Path.GetFileName((channel as Test.Module.AnalogInputChannel).FileName) == DataChannelFilename)
|
||||
{
|
||||
//
|
||||
// Tack on the header information for each channel.
|
||||
//
|
||||
var filteredChannelData = null != filteredData && filteredData.Count == 1 ? filteredData[0] : null;
|
||||
|
||||
headerLines[FtssHeaderLine.Headers].Add(TAB_LIST_SEPARATOR);
|
||||
headerLines[FtssHeaderLine.TestDate].Add(test.InceptionDate.ToShortDateString());
|
||||
headerLines[FtssHeaderLine.TestTime].Add(test.InceptionDate.ToLongTimeString());
|
||||
headerLines[FtssHeaderLine.TestId].Add(id);
|
||||
headerLines[FtssHeaderLine.TestDescription].Add(test.Description);
|
||||
headerLines[FtssHeaderLine.LaboratoryName].Add(LaboratoryName);
|
||||
headerLines[FtssHeaderLine.LaboratoryContactName].Add(LaboratoryContactName);
|
||||
headerLines[FtssHeaderLine.LaboratoryContactPhone].Add(LaboratoryContactPhone);
|
||||
headerLines[FtssHeaderLine.LaboratoryContactEmail].Add(LaboratoryContactEmail);
|
||||
headerLines[FtssHeaderLine.TestEngineerName].Add(TestEngineerName);
|
||||
headerLines[FtssHeaderLine.TestEngineerPhone].Add(TestEngineerPhone);
|
||||
headerLines[FtssHeaderLine.TestEngineerEmail].Add(TestEngineerEmail);
|
||||
headerLines[FtssHeaderLine.SampleRate].Add(channel.ParentModule.SampleRateHz.ToString("F0"));
|
||||
headerLines[FtssHeaderLine.HardwareAntiAliasFilter].Add(channel.ParentModule.AaFilterRateHz.ToString("F0"));
|
||||
headerLines[FtssHeaderLine.DataChannelName].Add(DataChannelFilename);
|
||||
|
||||
//13157 Software filter class (SAE Class) incorrect in TSV filtered export.
|
||||
//if we are filtering AND we have use isocode filter mapping on, then adjust the isocode to match
|
||||
var isocode = channel is IIsoCodeAware ? (channel as IIsoCodeAware).IsoCode : "";
|
||||
var swFilter = Filtered && filteredChannelData != null ? filteredChannelData.FilterDescription : "NONE";
|
||||
if (UseISOCodeFilterMapping)
|
||||
{
|
||||
var iso = new Common.ISO.IsoCode(isocode);
|
||||
iso.FilterClass = CFCFilterDTSFileStringConverter.GetIsoCodeFromString(swFilter, UseZeroForUnfiltered);
|
||||
isocode = iso.StringRepresentation;
|
||||
}
|
||||
headerLines[FtssHeaderLine.IsoCode].Add(isocode);
|
||||
headerLines[FtssHeaderLine.ChannelDescription].Add(channel.ChannelDescriptionString);
|
||||
headerLines[FtssHeaderLine.ChannelLocation].Add(channel.ChannelName2);
|
||||
headerLines[FtssHeaderLine.SensorSerialNumber].Add(channel is ISerialNumberAware ? (channel as ISerialNumberAware).SerialNumber : "");
|
||||
headerLines[FtssHeaderLine.SoftwareFilter].Add(swFilter);
|
||||
headerLines[FtssHeaderLine.SoftwareFilterDb].Add(Filtered && filteredChannelData != null ? filteredChannelData.FilterFrequencyHz.ToString("F0") : "NONE");
|
||||
headerLines[FtssHeaderLine.EngineeringUnits].Add(channel is IEngineeringUnitAware ? (channel as IEngineeringUnitAware).EngineeringUnits.TrimEnd() : "");
|
||||
headerLines[FtssHeaderLine.UserComment].Add("");
|
||||
|
||||
var preTriggerSamples = 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); }
|
||||
headerLines[FtssHeaderLine.PreZero].Add((preTriggerSamples / SubSampleInterval).ToString());
|
||||
|
||||
var postTriggerSamples = channel.ParentModule.NumberOfSamples - preTriggerSamples;
|
||||
if (postTriggerSamples < 0) { postTriggerSamples = 0D; }
|
||||
if (postTriggerSamples > Stop * sampleRate) { postTriggerSamples = Math.Truncate(Stop * sampleRate); }
|
||||
headerLines[FtssHeaderLine.PostZero].Add((postTriggerSamples / SubSampleInterval).ToString());
|
||||
|
||||
headerLines[FtssHeaderLine.DataZero].Add(channel.DataZeroLevelAdc.ToString());
|
||||
|
||||
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;
|
||||
|
||||
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
|
||||
)
|
||||
);
|
||||
|
||||
headerLines[FtssHeaderLine.ScaleEu].Add(scaler.GetAdcToEuScalingFactor().ToString());
|
||||
headerLines[FtssHeaderLine.ScaleMv].Add(scaler.GetAdcToMvScalingFactor().ToString());
|
||||
headerLines[FtssHeaderLine.DataStart].Add(TAB_LIST_SEPARATOR);
|
||||
headerLines[FtssHeaderLine.Labels].Add(channel.ChannelDescriptionString);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//FB14621: Recalculate minStartTime and dataCollectionLength here after pulling out channelsWithMeta,
|
||||
// rather than in Export which is doing the whole test/ROI
|
||||
minStartTime = ChannelWithMeta.GetMinStartTime(Start, channelsWithMeta);
|
||||
var minStopTime = ChannelWithMeta.GetMinStopTime(Stop, channelsWithMeta);
|
||||
dataCollectionLength = (int)((minStopTime - minStartTime) * sampleRate);
|
||||
|
||||
VerifyExportedFileWillFitOnDisk(test.Id,
|
||||
targetPath.Remove(targetPath.IndexOf(Path.VolumeSeparatorChar) + 1),
|
||||
headerLines,
|
||||
coder,
|
||||
channelsWithMeta,
|
||||
dataCollectionLength,
|
||||
minStartTime,
|
||||
sampleRate,
|
||||
cancelRequested);
|
||||
|
||||
// Apply filtering to channel data at this point?
|
||||
// Thinking maybe since all the cool filter utilities are up in the Slice Control layer, it
|
||||
// may just be a better idea to create some filtered test at that level using the event channels
|
||||
// and then just add some code here that gracefully handles Data-data instead of PersistentChannelInfo
|
||||
// data...?
|
||||
|
||||
//Dictionary<ChannelWithMeta, double[ ]> filteredData = new Dictionary<ChannelWithMeta, double[ ]>( );
|
||||
//SaeJ211.FilterUtility filterUtility = new SaeJ211.FilterUtility( );
|
||||
//foreach ( ChannelWithMeta channelWithMeta in channelsWithMeta )
|
||||
//{
|
||||
// this.CurrentFilter = Slice.Control.Event.Module.Channel.DefaultSaeJ211Filter.Parse( thatAnalogChannel.SoftwareFilter );
|
||||
// filterUtility.SampleRate = channelWithMeta.SampleRate;
|
||||
// filterUtility.Cfc = ( channelWithMeta.Channel as Test.Module.AnalogInputChannel ).SoftwareFilter;
|
||||
//}
|
||||
|
||||
foreach (FtssHeaderLine ftssHeaderLine in Enum.GetValues(typeof(FtssHeaderLine)))
|
||||
{ //
|
||||
// Write out the assembled header information to the specified filestream.
|
||||
//
|
||||
if (headerLines.Keys.Contains(ftssHeaderLine))
|
||||
{
|
||||
var channelValues = headerLines[ftssHeaderLine];
|
||||
fileWriter.Write(coder.DecodeAttributeValue(ftssHeaderLine) + TAB_LIST_SEPARATOR);
|
||||
foreach (var channelValue in channelValues)
|
||||
fileWriter.Write(channelValue + TAB_LIST_SEPARATOR);
|
||||
fileWriter.WriteLine();
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i <= dataCollectionLength; i++)
|
||||
{
|
||||
if (null != cancelRequested && cancelRequested()) { break; }
|
||||
fileWriter.Write(string.Format("{0}{1}", (minStartTime + i * 1.0 / sampleRate).ToString(NUMBER_FORMAT), TAB_LIST_SEPARATOR));
|
||||
var bNeedComma = false;
|
||||
foreach (var channelWithMeta in channelsWithMeta)
|
||||
{
|
||||
if (bNeedComma) { fileWriter.Write(TAB_LIST_SEPARATOR); }
|
||||
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.
|
||||
//
|
||||
var thisChannelsIndexAtCurrentTime = i - (int)((channelWithMeta.StartTime - minStartTime) * channelWithMeta.SampleRate);
|
||||
if (Filtered)
|
||||
{
|
||||
if (filteredDataIndex < filteredData.Count)
|
||||
{
|
||||
if (thisChannelsIndexAtCurrentTime >= 0 && thisChannelsIndexAtCurrentTime < filteredData[filteredDataIndex].Data.Length)
|
||||
{
|
||||
fileWriter.Write(filteredData[filteredDataIndex].Data[thisChannelsIndexAtCurrentTime].ToString(NUMBER_FORMAT));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fileWriter.Write(double.NaN.ToString());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
double dInitialEU = 0;
|
||||
var aChannel = channelWithMeta.Channel as Test.Module.AnalogInputChannel;
|
||||
if (null != aChannel) { dInitialEU = aChannel.InitialEu; }
|
||||
|
||||
if (thisChannelsIndexAtCurrentTime >= 0 && (ulong)thisChannelsIndexAtCurrentTime < aChannel.ParentModule.NumberOfSamples)
|
||||
{
|
||||
fileWriter.Write(channelWithMeta.Scaler.GetEU(channelWithMeta.Channel.PersistentChannelInfo[thisChannelsIndexAtCurrentTime]).ToString(NUMBER_FORMAT));
|
||||
}
|
||||
}
|
||||
filteredDataIndex++;
|
||||
}
|
||||
|
||||
if (0 == i % DataSamplesPerTick)
|
||||
{
|
||||
OnTick?.Invoke(this, (double)totalWriteTicksDispatched++ / totalWriteTicksNeeded * 100);
|
||||
System.Windows.Forms.Application.DoEvents();
|
||||
}
|
||||
fileWriter.WriteLine();
|
||||
filteredDataIndex = 0;
|
||||
}
|
||||
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; }
|
||||
|
||||
public bool Filtered { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user