318 lines
16 KiB
C#
318 lines
16 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text;
|
|
|
|
namespace DTS.Serialization.DDAS
|
|
{
|
|
public class DDASChannel
|
|
{
|
|
private readonly DDASTest _parentTest;
|
|
|
|
private readonly Dictionary<DDASTest.Fields, string> _values = new Dictionary<DDASTest.Fields, string>();
|
|
|
|
public string GetValue(DDASTest.Fields field)
|
|
{
|
|
if (!_values.ContainsKey(field)) _values.Add(field, "#NOVALUE");
|
|
return _values[field];
|
|
}
|
|
|
|
public void SetValue(DDASTest.Fields field, string value)
|
|
{
|
|
_values[field] = value;
|
|
switch (field)
|
|
{
|
|
case DDASTest.Fields.DataType:
|
|
if (value == "Raw")
|
|
{
|
|
SetValue(DDASTest.Fields.EngineeringUnits, "ADC");
|
|
SetValue(DDASTest.Fields.DigitalFilterType, "");
|
|
var actualRange = _parentTest.ActualRangesADC[_channelIndex];
|
|
|
|
SetValue(DDASTest.Fields.BitResolution, (2D * actualRange / ushort.MaxValue).ToString());
|
|
|
|
FileName = Path.Combine(Path.Combine(_path, "DDAS"), "Raw");
|
|
FileName = Path.Combine(FileName, $"{_parentTest.Test.Id}_{ChannelNumber}.DDAS");
|
|
}
|
|
else if (value == "Processed")
|
|
{
|
|
SetValue(DDASTest.Fields.EngineeringUnits, _engineeringUnits);
|
|
SetValue(DDASTest.Fields.DigitalFilterType,
|
|
$"CFC {_parentTest.DataUnfilteredEU[_channelIndex].FilterDescription}/{_parentTest.DataUnfilteredEU[_channelIndex].FilterFrequencyHz}");
|
|
var actualRange = _parentTest.ActualRangesEUFiltered[_channelIndex];
|
|
|
|
SetValue(DDASTest.Fields.BitResolution, (2D * actualRange / ushort.MaxValue).ToString());
|
|
FileName = Path.Combine(Path.Combine(_path, "DDAS"), "Processed");
|
|
FileName = Path.Combine(FileName, $"{_parentTest.Test.Id}_{ChannelNumber}.DDAS");
|
|
}
|
|
else if (value == "Converted")
|
|
{
|
|
SetValue(DDASTest.Fields.EngineeringUnits, _engineeringUnits);
|
|
SetValue(DDASTest.Fields.DigitalFilterType, "");
|
|
var actualRange = _parentTest.ActualRangesEUUnfiltered[_channelIndex];
|
|
|
|
SetValue(DDASTest.Fields.BitResolution, (2D * actualRange / ushort.MaxValue).ToString());
|
|
FileName = Path.Combine(Path.Combine(_path, "DDAS"), "Converted");
|
|
FileName = Path.Combine(FileName, $"{_parentTest.Test.Id}_{ChannelNumber}.DDAS");
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
private readonly string _engineeringUnits = "";
|
|
|
|
public string FileName { get; set; }
|
|
|
|
public void Serialize(TickEventHandler tickHandler)
|
|
{
|
|
if (!Directory.Exists(new FileInfo(FileName).Directory.FullName))
|
|
Directory.CreateDirectory(new FileInfo(FileName).Directory.FullName);
|
|
using (var bw = new BinaryWriter(System.IO.File.Open(FileName, FileMode.Create)))
|
|
{
|
|
// First write the "fileinfoblock"
|
|
//typedef struct tagFILEINFOBLOCK
|
|
//{
|
|
// UINT Size; // Block Size (including nSize)
|
|
// char FileTypeName[12]; // Software Type Name
|
|
// char FileTypeVers[12]; // File Version Name
|
|
// UINT FileTypeFlags; // File Type Flags
|
|
// // - Hardware Type in upper 16 bits
|
|
// // - File Type in lower 16 bits
|
|
// char CreatedByName[16]; // Created by T-number
|
|
// char UpdatedByName[16]; // Updated by T-number
|
|
//}FILEINFOBLOCK;
|
|
|
|
bw.Write((uint)0x40);
|
|
var fileType = "DDAS FlPt";
|
|
bw.Write(Encoding.ASCII.GetBytes(fileType.PadRight(12, '\0')));
|
|
var fileTypeVersion = "Ver 500";
|
|
bw.Write(Encoding.ASCII.GetBytes(fileTypeVersion.PadRight(12, '\0')));
|
|
bw.Write((uint)0);
|
|
bw.Write(
|
|
Encoding.ASCII.GetBytes(
|
|
_parentTest.Test.Id.Substring(0, Math.Min(16, _parentTest.Test.Id.Length)).PadRight(16, '\0')));
|
|
bw.Write(
|
|
Encoding.ASCII.GetBytes(
|
|
_parentTest.Test.Id.Substring(0, Math.Min(16, _parentTest.Test.Id.Length)).PadRight(16, '\0')));
|
|
|
|
// Now the test info
|
|
// typedef struct tagTESTINFO
|
|
// {
|
|
// unsigned long Size; // Block Size (including Size)
|
|
// unsigned long DeviceID; // DAQ device ID
|
|
// long ChannelNo; // Channel number (1-32)
|
|
// long SampleRate; // Samples per second
|
|
// long TotalSamples; // Total samples in data record
|
|
// long PreEventSamples; // Samples before event
|
|
// short ChanNumInSys; // Channel Number (1-128) in system (many devices)
|
|
// short NumPreCalPts; // Number of preCal points included with data
|
|
// short NumPostCalPts; // Number of preCal points included with data
|
|
// char TestCreation[TESTPATHSIZE]; // Test path and date
|
|
// char TimeAxisTitle[32]; // Time axis title
|
|
// byte SpareBytes[2]; // 2 bytes of spare (for 4 byte alignment)
|
|
//
|
|
// } TESTINFO;
|
|
|
|
var channel = _parentTest.Test.Channels[_channelIndex] as Test.Module.AnalogInputChannel;
|
|
|
|
bw.Write((uint)0xC0);
|
|
bw.Write((uint)(1 + channel.ParentModule.Number));
|
|
bw.Write((uint)(1 + _channelIndex));
|
|
bw.Write((uint)channel.ParentModule.SampleRateHz);
|
|
bw.Write((uint)channel.ParentModule.NumberOfSamples);
|
|
bw.Write((uint)(channel.ParentModule.PreTriggerSeconds * channel.ParentModule.SampleRateHz));
|
|
bw.Write((ushort)channel.Number);
|
|
bw.Write((ushort)0);
|
|
bw.Write((ushort)0);
|
|
var testPathAndDate = FileName + " " + channel.ParentModule.ParentTest.InceptionDate.ToLongDateString();
|
|
bw.Write(
|
|
Encoding.ASCII.GetBytes(
|
|
testPathAndDate.Substring(0, Math.Min(128, testPathAndDate.Length)).PadRight(128, '\0')));
|
|
var TimeLabel = "Time (msec)";
|
|
bw.Write(Encoding.ASCII.GetBytes(TimeLabel.PadRight(32, '\0')));
|
|
bw.Write((byte)0x00);
|
|
bw.Write((byte)0x00);
|
|
|
|
// Channel
|
|
//typedef struct tagCHANNEL
|
|
// {
|
|
// short Size; // Size of this object
|
|
// short Flags; // Channel Flags
|
|
// char Name[64]; // Channel Name
|
|
// char Sign[8]; // Sign +, -, or blank
|
|
// char Axis[8]; // X,Y,Z,FX,MX,AX,...
|
|
// float FilterFreq; // Channel Filter Class (in Hz)
|
|
// float SetGain; // Gain setting (1 - n)
|
|
// float ActGain; // Actual (measured?) gain setting.
|
|
// float Rcal; // Shunt cal resistance
|
|
// float Excitation; // Excitation Voltage (when programable)
|
|
// byte byteSpares[4]; // Spare bytes (was Cal Date)
|
|
// TRANSDUCER Transducer; // "Snapshot" of transducer values
|
|
// } CHANNEL;
|
|
|
|
bw.Write((ushort)0x1001);
|
|
bw.Write((ushort)0x0100);
|
|
var description =
|
|
Encoding.ASCII.GetBytes(
|
|
channel.Description.Substring(0, Math.Min(64, channel.Description.Length)).PadRight(64, '\0'));
|
|
bw.Write(description);
|
|
|
|
var polarity = "+";
|
|
if (channel.IsInverted)
|
|
polarity = "-";
|
|
bw.Write(Encoding.ASCII.GetBytes(polarity.PadRight(8, (char)0x00)));
|
|
|
|
const string empty = "";
|
|
bw.Write(Encoding.ASCII.GetBytes(empty.PadRight(8, (char)0x00)));
|
|
|
|
//ChannelFilter[] filterClasses = Enum.GetValues(typeof(ChannelFilter)).Cast<ChannelFilter>().ToArray();
|
|
//float effectiveCFCFrequency = 0;
|
|
//foreach(var f in filterClasses)
|
|
//{
|
|
// if((int)f == (int)_parentTest.DataUnfilteredEU[_channelIndex].FilterFrequencyHz)
|
|
// {
|
|
// var type = typeof(ChannelFilter);
|
|
// var memInfo = type.GetMember(f.ToString());
|
|
// var attributes = memInfo[0].GetCustomAttributes(typeof(CFCValueAttribute),
|
|
// false);
|
|
// effectiveCFCFrequency = (float)((CFCValueAttribute)attributes[0]).CFCValue;
|
|
// }
|
|
//}
|
|
//if (0 == effectiveCFCFrequency) { effectiveCFCFrequency = (float)_parentTest.DataUnfilteredEU[_channelIndex].FilterFrequencyHz; }
|
|
bw.Write(channel.ParentModule.AaFilterRateHz);
|
|
|
|
if (channel.ExpectedGainValid)
|
|
bw.Write((float)channel.ExpectedGain);
|
|
else
|
|
bw.Write((float)(2500.0 / channel.DesiredRange));
|
|
if (channel.MeasuredGainValid)
|
|
bw.Write((float)channel.MeasuredGain);
|
|
else if (channel.ExpectedGainValid)
|
|
bw.Write((float)channel.ExpectedGain);
|
|
else
|
|
bw.Write((float)1.0);
|
|
|
|
bw.Write((float)channel.BridgeResistanceOhms);
|
|
|
|
if (channel.MeasuredExcitationVoltageValid)
|
|
bw.Write((float)channel.MeasuredExcitationVoltage);
|
|
else
|
|
bw.Write((float)channel.ExcitationVoltage);
|
|
bw.Write((byte)0x00);
|
|
bw.Write((byte)0x00);
|
|
bw.Write((byte)0x00);
|
|
bw.Write((byte)0x00);
|
|
|
|
//typedef struct tagTRANSDUCER
|
|
//{
|
|
//char Mfr[32]; // Manufacturer
|
|
//char Model[32]; // Model
|
|
//char SN[32]; // Transducer Serial No
|
|
//char Type[16]; // Transducer Type (load cell, accel, etc)
|
|
//char EngUnits[16]; // Engineering Units (applies to Capacity,
|
|
// // Sensitivity, and EUCalValue
|
|
//float Capacity; // Max applied accel, force, displacement, etc
|
|
//float XdcrRactive; // Bridge arm resistance of active half
|
|
//float XdcrRinactive; // Bridge arm resistance of inactive half
|
|
//float Sensitivity; // Sensitivity in V/EU
|
|
//float EUCalValue; // Engineering unit cal value with Rcal
|
|
//float Rcal; // Cal Resistor in Ohms
|
|
//float Excitation; // Excitation Voltage (when specified)
|
|
//long CalDate; // Cal date as time_t (seconds since 1970)
|
|
// // (Good til 2038...shouldn't be an issue
|
|
// // for this developer).
|
|
// long Spare; // Spare long
|
|
//} TRANSDUCER;
|
|
|
|
|
|
var Manufacturer = "CHRYSLER";
|
|
bw.Write(
|
|
Encoding.ASCII.GetBytes(Manufacturer.Substring(0, Math.Min(32, Manufacturer.Length))
|
|
.PadRight(32, '\0')));
|
|
bw.Write(Encoding.ASCII.GetBytes(empty.PadRight(32, (char)0x00)));
|
|
bw.Write(
|
|
Encoding.ASCII.GetBytes(
|
|
channel.SerialNumber.Substring(0, Math.Min(32, channel.SerialNumber.Length)).PadRight(32, '\0')));
|
|
var SensorType = "UNKNOWN";
|
|
bw.Write(
|
|
Encoding.ASCII.GetBytes(SensorType.Substring(0, Math.Min(16, SensorType.Length)).PadRight(16, '\0')));
|
|
bw.Write(
|
|
Encoding.ASCII.GetBytes(
|
|
channel.EngineeringUnits.Substring(0, Math.Min(16, channel.EngineeringUnits.Length))
|
|
.PadRight(16, '\0')));
|
|
|
|
bw.Write((float)channel.DesiredRange);
|
|
bw.Write((float)0);
|
|
bw.Write((float)0);
|
|
bw.Write((float)(1000 * channel.Sensitivity));
|
|
bw.Write((float)0);
|
|
bw.Write((float)channel.BridgeResistanceOhms);
|
|
bw.Write((float)channel.ExcitationVoltage);
|
|
|
|
var Epoch = new DateTime(1971, 1, 1, 0, 0, 0);
|
|
bw.Write((int)(channel.LastCalibrationDate - Epoch).TotalSeconds);
|
|
var TransducerSpare = 0;
|
|
bw.Write(TransducerSpare);
|
|
|
|
var Spare = new byte[0x0230 - 0x0210];
|
|
bw.Write(Spare);
|
|
|
|
double percentageComplete = (float)_channelIndex / _parentTest.Channels.Length;
|
|
var weight = 1D / _parentTest.Channels.Length;
|
|
|
|
for (var i = 0; i < _parentTest.DataUnfilteredEU[_channelIndex].Data.Length; i++)
|
|
{
|
|
bw.Write((float)_parentTest.DataUnfilteredEU[_channelIndex].Data[i]);
|
|
if (0 == i % 1000)
|
|
{
|
|
var percent = 100D * (percentageComplete + i * weight / channel.ParentModule.NumberOfSamples);
|
|
tickHandler(this, percent);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private readonly int _channelIndex;
|
|
|
|
public int ChannelNumber => 1 + _channelIndex;
|
|
|
|
private readonly string _path;
|
|
|
|
public DDASChannel(DDASTest parentTest, int channelIndex, string path)
|
|
{
|
|
_path = path;
|
|
_parentTest = parentTest;
|
|
_channelIndex = channelIndex;
|
|
var aic = parentTest.Test.Channels[channelIndex] as Test.Module.AnalogInputChannel;
|
|
|
|
_engineeringUnits = aic.EngineeringUnits;
|
|
|
|
SetValue(DDASTest.Fields.EngineeringUnits, aic.EngineeringUnits);
|
|
SetValue(DDASTest.Fields.SensorMakeModelSerial, aic.SerialNumber);
|
|
SetValue(DDASTest.Fields.SensorLocation, aic.Description);
|
|
|
|
// double actualRange = _parentTest.ActualRangesEUFiltered[_channelIndex];
|
|
double actualRange = 0;
|
|
|
|
SetValue(DDASTest.Fields.BitResolution, (2D * actualRange / ushort.MaxValue).ToString());
|
|
//FB14282: Option to Flatten Export Folders
|
|
FileName = path;
|
|
|
|
var channel = _parentTest.Test.Channels[_channelIndex] as Test.Module.AnalogInputChannel;
|
|
|
|
// We need a list of bases in order to properly formulate the file name.
|
|
var moduleSerialNumbers = new List<string>();
|
|
foreach (var currentChannel in _parentTest.Test.Channels)
|
|
if (!moduleSerialNumbers.Contains(currentChannel.ParentModule.SerialNumber))
|
|
moduleSerialNumbers.Add(currentChannel.ParentModule.SerialNumber);
|
|
|
|
var baseIndex = moduleSerialNumbers.IndexOf(channel.ParentModule.SerialNumber);
|
|
// Another SLICE Specific short cut. To properly number the channels, we need to know how many channels per module. However, in the case
|
|
// of SLICE where a bridge only has one channel _assigned_, channel.ParentModule.NumberOfChannels is 1. AFAIK, in this context, there is
|
|
// no way other than assumption to know a bridge has three channels.
|
|
var numberOfChannelsPerModule = (channel.ParentModule.NumberOfChannels + 2) / 3 * 3;
|
|
FileName = Path.Combine(FileName, $"ch{1 + baseIndex:D2}{1 + channel.Number + numberOfChannelsPerModule * channel.ParentModule.Number:D2}.fpd");
|
|
}
|
|
}
|
|
}
|