using DTS.Common; using DTS.Common.Behaviors; using DTS.Common.Classes.Sensors; using DTS.Common.Classes.TMAT; using DTS.Common.Enums; using DTS.Common.Enums.DASFactory; using DTS.Common.Enums.Sensors; using DTS.Common.Interface.DASFactory; using DTS.Common.Interface.DASFactory.Config; using DTS.Common.Interface.DASFactory.Diagnostics; using DTS.Common.Utilities.Logging; using DTS.DASLib.Command.SLICE; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DTS.DASLib.Service.Classes { public interface ITmtFile { /// /// Writes the TMT file to the device /// void WriteTmtFile(IDASCommunication das, Dictionary dataChannelIds, Dictionary timeChannelIds, int dasIndex, Common.Interface.DASFactory.ICommunication communication, float[] scaleFactors = null, float[] ranges = null, float[] measuredOffset = null); } //FB 25526 This class encapsulates the methods used in SLICE6AIR.cs before to intract with TMT files, the subclasses can support multiple types need like SLICE6AIR & TSRAIR public abstract class TmtFile : ITmtFile { protected TmtFile(string serialNumber, IInfoResult dasInfo, IConfigurationData configData, IDiagnosticResult[] channelDiagnosticsResults) { SerialNumber = serialNumber; DASInfo = dasInfo; ConfigData = configData; ChannelDiagnosticsResults = channelDiagnosticsResults; } protected ITMTTemplate GetTMTTemplate(TMAT_TEMPLATES tmat_TEMPLATES) { var tmtFileName = StringMetaDataAttr.GetStringMetaData(tmat_TEMPLATES); if (string.IsNullOrWhiteSpace(tmtFileName)) { tmtFileName = "S6ATMTTemplate_PCM.tmt"; } var path = $"TMTTemplates/{tmtFileName}"; return new TMTTemplate(path); } protected TMAT_TEMPLATES Template { get; set; } protected int MaxChannels { get; set; } protected string SerialNumber { get; set; } protected IInfoResult DASInfo { get; set; } protected IConfigurationData ConfigData { get; set; } protected IDiagnosticResult[] ChannelDiagnosticsResults { get; set; } /// /// writes tmats file to the DASConfigs log directory, file is serial_TMT.txt /// protected static void WriteTMTFilePC(byte[] data, string testDirectory, string serialNumber) { const string dasConfigs = "DASConfigs"; try { var fileName = Path.Combine(Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), dasConfigs), $"{serialNumber}_TMT.txt"); if (File.Exists(fileName)) { File.Delete(fileName); } File.WriteAllBytes(fileName, data); //43789 Now, also write the file to the Data folder because the copy in the current directory gets overwritten every test fileName = Path.Combine(testDirectory); if (!Directory.Exists(fileName)) { Directory.CreateDirectory(fileName); } fileName = Path.Combine(fileName, $"{serialNumber}_TMT.txt"); File.WriteAllBytes(fileName, data); } catch (Exception ex) { APILogger.Log(ex); } try { var fileName = Path.Combine(testDirectory, dasConfigs); if (!Directory.Exists(fileName)) { Directory.CreateDirectory(fileName); } fileName = Path.Combine(fileName, $"{serialNumber}_TMT.txt"); File.WriteAllBytes(fileName, data); } catch (Exception ex) { APILogger.Log(ex); } } private static bool DoesChannelStreamUnsigned(AnalogInputDASChannel aic) { if (null == aic) { return true; } if (null == aic.OwningModule) { return true; } if (null == aic.OwningModule.OwningDAS) { return true; } switch (aic.OwningModule.OwningDAS.GetHardwareType()) { case Common.Enums.Hardware.HardwareTypes.TSR_AIR_RevB: return false; default: return true; } } /// /// writes a channel to the template in memory /// protected void UpdateTMTChannel(ITMTTemplate template, int channelIndex, float[] scaleFactors = null, float[] ranges = null, float[] measuredOffset = null) { var channelKeys = Enum.GetValues(typeof(TMTChannelKeys)).Cast() .ToArray(); var number = (1 + channelIndex).ToString(); var moduleArrayIndex = DASInfo.MapDASChannelNumber2ModuleArrayIndex(channelIndex); var moduleChannelIndex = DASInfo.MapDASChannelNumber2ModuleChannelNumber(channelIndex); var aidc = ConfigData.Modules[moduleArrayIndex].Channels[moduleChannelIndex] as AnalogInputDASChannel; var diagnosticResult = (from dr in ChannelDiagnosticsResults where dr.DASChannelNumber == channelIndex select dr) .FirstOrDefault(); var scaler = aidc.GetDataScaler(); var tempEU = scaler.GetEU(short.MinValue); var tempEU2 = scaler.GetEU(short.MaxValue); var minEU = Math.Min(tempEU, tempEU2); var maxEU = Math.Max(tempEU, tempEU2); foreach (var key in channelKeys) { switch (key) { case TMTChannelKeys.HardwareChannelNumber: template.UpdateValue(key, number, 1 + channelIndex); break; case TMTChannelKeys.ChannelName: template.UpdateValue(key, TMTTemplate.TMT_LimitString(aidc.ChannelName2), 1 + channelIndex); break; case TMTChannelKeys.CouplingMode: var couplingMode = "D"; if (aidc.CouplingMode == SensorConstants.CouplingModes.AC && aidc.IEPEChannel) { couplingMode = "A"; } template.UpdateValue(key, couplingMode, 1 + channelIndex); break; case TMTChannelKeys.BridgeResistance: if (null != diagnosticResult.BridgeResistance) { template.UpdateValue(key, ((double)diagnosticResult.BridgeResistance).ToString("F0"), 1 + channelIndex); } else { template.UpdateValue(key, "0", 1 + channelIndex); } break; case TMTChannelKeys.AAF: template.UpdateValue(key, ConfigData.Modules[0].AAFilterRateHz.ToString("F0"), 1 + channelIndex); break; case TMTChannelKeys.OffsetMV: template.UpdateValue(key, RunTestVariables.MaskEUMetaData ? "0" : ((short)diagnosticResult.FinalOffsetADC * diagnosticResult.ScalefactorMilliVoltsPerADC) .ToString("F2"), 1 + channelIndex); break; case TMTChannelKeys.InputRangeMV: template.UpdateValue(key, RunTestVariables.MaskEUMetaData ? "1" : (diagnosticResult.ScalefactorMilliVoltsPerADC * ushort.MaxValue).ToString("F0"), 1 + channelIndex); break; // Additional Max Range EU needed for C-1/MOT1 entry for Max input range EU case TMTChannelKeys.MaxRangeEU: if (ranges != null) { template.UpdateValue(key, ranges[channelIndex].ToString("F0"), 1 + channelIndex); } else { template.UpdateValue(key, RunTestVariables.MaskEUMetaData ? "1" : maxEU.ToString("F0"), 1 + channelIndex); } break; case TMTChannelKeys.MinRangeEU: if (ranges != null) { template.UpdateValue(key, (-1 * ranges[channelIndex]).ToString("F0"), 1 + channelIndex); } else { template.UpdateValue(key, RunTestVariables.MaskEUMetaData ? "1" : minEU.ToString("F0"), 1 + channelIndex); } break; case TMTChannelKeys.EU: template.UpdateValue(key, RunTestVariables.MaskEUMetaData ? "---" : aidc.EngineeringUnits?.Trim() ?? "", 1 + channelIndex); break; case TMTChannelKeys.ScaleFactorEU: if (scaleFactors != null) { template.UpdateValue(key, scaleFactors[channelIndex].ToString("F11"), 1 + channelIndex); } else { template.UpdateValue(key, RunTestVariables.MaskEUMetaData ? "1" : scaler.GetAdcToEuScalingFactor().ToString("F11"), 1 + channelIndex); } break; case TMTChannelKeys.OffsetEU: { var offsetEU = GetOffsetEUString(aidc, scaler, measuredOffset, channelIndex, diagnosticResult); template.UpdateValue(key, offsetEU, 1 + channelIndex); } break; } } } /// /// returns a string suitable for the TMATS file for the EU offset for a channel /// public static string GetOffsetEUString(AnalogInputDASChannel aidc, Common.DAS.Concepts.DataScaler scaler, float [] measuredOffset, int channelIndex, IDiagnosticResult diagnosticResult) { var isUnsigned = DoesChannelStreamUnsigned(aidc); var adcToEU = scaler.GetAdcToEuScalingFactor(); var midPointRemoval = isUnsigned ? adcToEU * Constants.ADC_MIDPOINT : 0; //FB15339 take the final offset ADC value and normalize it to zero rather than the ADC midpoint before converting to EU var offset = (scaler.GetEU((short)diagnosticResult.FinalOffsetADC) - midPointRemoval).ToString("F11"); if (aidc.ZeroMethod == ZeroMethodType.None) { //18806 & 44255 //the above offset takes into consideration midpoint, initial EU, AND final offset //with none we don't want the final offset, but we want the midpoint and initial EU offset = (-1D * midPointRemoval + aidc.InitialEU).ToString("F11"); } AdjustOffsetForEUAtMv(ref offset, aidc, midPointRemoval, scaler, diagnosticResult); if (measuredOffset != null) { return measuredOffset[channelIndex].ToString(); } else { return RunTestVariables.MaskEUMetaData ? "0" : offset; } } private static void AdjustOffsetForEUAtMv(ref string offset, AnalogInputDASChannel aidc, double midPointRemoval, Common.DAS.Concepts.DataScaler scaler, IDiagnosticResult diagnosticResult) { try { if (null == aidc) { return; } if (null == scaler) { return; } if (string.IsNullOrWhiteSpace(aidc.InitialOffset)) { return; } var io = new InitialOffset(); io.FromDbSerializeString(aidc.InitialOffset); if (io.Form != InitialOffsetTypes.EUAtMV) { return; } //DataScaler should take care of most of the work here, if we feed it in the final offset ADC it should adjust that by the eu@mv difference var eu = scaler.GetEU((short)diagnosticResult.FinalOffsetADC); offset = ( eu - midPointRemoval).ToString("F11"); } catch( Exception ex) { APILogger.Log(ex); } } /// /// Writes the TMT file to the S6A /// 14531 Implement TMATS support for S6A stream on boot /// public virtual void WriteTmtFile(IDASCommunication das, Dictionary dataChannelIds, Dictionary timeChannelIds, int dasIndex, Common.Interface.DASFactory.ICommunication communication, float[] scaleFactors = null, float[] ranges = null, float[] measuredOffset = null) { var template = GetTMTTemplate(Template); var globalKeys = Enum.GetValues(typeof(TMTGlobalKeys)).Cast() .ToArray(); // FB 26736 Get time & data channel Ids from ddictionary based on serial ushort? dataChannelId = null; ushort? timeChannelId = null; var dataChannelIdPair = dataChannelIds.FirstOrDefault(p => p.Key.SerialNumber == SerialNumber); var bitsPerFrame = Constants.BITS_PER_MINOR_FRAME_S6A; if (dataChannelIdPair.Key != null) { dataChannelId = dataChannelIdPair.Value; if (dataChannelIdPair.Key.IsTSRAIR()) { bitsPerFrame = Constants.BITS_PER_MINOR_FRAME_TSRAIR; } } var timeChannelIdPair = timeChannelIds.FirstOrDefault(p => p.Key.SerialNumber == SerialNumber); if (timeChannelIdPair.Key != null) { timeChannelId = timeChannelIdPair.Value; } foreach (var key in globalKeys) { TMTTemplate.UpdateGlobalField(das, key, template, ConfigData, SerialNumber, timeChannelId, dataChannelId, dasIndex, bitsPerFrame); } for (int i = 0; i < MaxChannels; i++) { UpdateTMTChannel(template, i, scaleFactors, ranges, measuredOffset); } WriteTMTFile((ICommunication)das, template.GetAllLines(), das.TestDirectory); } public static void WriteTMTFile(ICommunication communication, string [] allLines, string testDirectory) { var sfd = new SetFileData(communication); var byteData = Encoding.UTF8.GetBytes(string.Join("\n", allLines)); if (byteData.Length >= InformationCommands.MAX_FILE_LENGTH_ID100) { throw new ArgumentOutOfRangeException( $"TMT File is too large, {byteData.Length} >= {InformationCommands.MAX_FILE_LENGTH_ID100}"); } //15241 store uploaded TMATS file to DASConfigs folder WriteTMTFilePC(byteData, testDirectory, communication.SerialNumber); sfd.StartByteCount = 0; sfd.FileID = SetFileData.TMT_FILE_ID; sfd.Data = Constants.XML_STORE_MAGIC_BYTES; sfd.SyncExecute(); //Store Header - data length var maxLen = Convert.ToUInt32(byteData.Length); sfd.StartByteCount = Constants.XML_HEADER_LENGTH / 2; sfd.FileID = SetFileData.TMT_FILE_ID; sfd.Data = BitConverter.GetBytes(maxLen); sfd.SyncExecute(); //Store Data for (uint i = 0; i < maxLen; i += (uint)sfd.MaximumFileStreamBytes) { long array_size = sfd.MaximumFileStreamBytes; if ((i + sfd.MaximumFileStreamBytes) > maxLen) { array_size = maxLen - i; } var dataToSend = new byte[array_size]; Array.Copy(byteData, i, dataToSend, 0, array_size); sfd.Data = dataToSend; sfd.FileID = SetFileData.TMT_FILE_ID; sfd.StartByteCount = i + Constants.XML_HEADER_LENGTH; sfd.SyncExecute(); } } //FB 30035 Refactored to create TmtFile object and return it based on profile /// /// returns a new template file object given a udp stream profile /// 29430 Store TMAT file that corresponds to Stream Profile /// /// /// /// /// /// /// public static ITmtFile GetS6ATMATSFileTypeForProfile(UDPStreamProfile profile, string serialNumber, IInfoResult dasInfo, IConfigurationData configData, IDiagnosticResult[] channelDiagnosticsResults) { switch (profile) { case UDPStreamProfile.CH10_ANALOG: case UDPStreamProfile.CH10_ANALOG_2HDR: return new Slice6AirAnalogTmtFile(serialNumber, dasInfo, configData, channelDiagnosticsResults); case UDPStreamProfile.CH10_PCM128_MM: case UDPStreamProfile.CH10_PCM_128BIT_2HDR: case UDPStreamProfile.CH10_PCM_STANDARD: case UDPStreamProfile.CH10_PCM_STANDARD_2HDR: case UDPStreamProfile.CH10_PCM_SUPERCOM: case UDPStreamProfile.CH10_PCM_SUPERCOM_2HDR: return new Slice6AirPcmTmtFile(serialNumber, dasInfo, configData, channelDiagnosticsResults); //FB 28292 "TmNS 144 bit PCM" and "TmNS Supercom 4x ADC PCM" profiles case UDPStreamProfile.TMNS_PCM_STANDARD: return new Slice6AirTmNs144PcmTmtFile(serialNumber, dasInfo, configData, channelDiagnosticsResults); case UDPStreamProfile.TMNS_PCM_SUPERCOM: return new Slice6AirTmNsSuperCom4XPcmTmtFile(serialNumber, dasInfo, configData, channelDiagnosticsResults); default: APILogger.Log($"GetTMATSTypeForProfile unsupported profile type: {profile}"); return new Slice6AirAnalogTmtFile(serialNumber, dasInfo, configData, channelDiagnosticsResults); } } } public class Slice6AirAnalogTmtFile : TmtFile { public Slice6AirAnalogTmtFile(string serialNumber, IInfoResult dasInfo, IConfigurationData configData, IDiagnosticResult[] channelDiagnosticsResults) : base(serialNumber, dasInfo, configData, channelDiagnosticsResults) { MaxChannels = 6; Template = TMAT_TEMPLATES.S6Air_ANALOG; } } public class Slice6AirPcmTmtFile : TmtFile { public Slice6AirPcmTmtFile(string serialNumber, IInfoResult dasInfo, IConfigurationData configData, IDiagnosticResult[] channelDiagnosticsResults) : base(serialNumber, dasInfo, configData, channelDiagnosticsResults) { MaxChannels = 6; Template = TMAT_TEMPLATES.S6Air_PCM; } } public class Slice6AirTmNs144PcmTmtFile : TmtFile { public Slice6AirTmNs144PcmTmtFile(string serialNumber, IInfoResult dasInfo, IConfigurationData configData, IDiagnosticResult[] channelDiagnosticsResults) : base(serialNumber, dasInfo, configData, channelDiagnosticsResults) { MaxChannels = 6; Template = TMAT_TEMPLATES.S6Air_TmNS_144PPCM; } } public class Slice6AirTmNsSuperCom4XPcmTmtFile : TmtFile { public Slice6AirTmNsSuperCom4XPcmTmtFile(string serialNumber, IInfoResult dasInfo, IConfigurationData configData, IDiagnosticResult[] channelDiagnosticsResults) : base(serialNumber, dasInfo, configData, channelDiagnosticsResults) { MaxChannels = 6; Template = TMAT_TEMPLATES.S6Air_TmNS_SuperCom4xPCM; } } }