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 _values = new Dictionary(); 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().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(); 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"); } } }