1089 lines
46 KiB
C#
1089 lines
46 KiB
C#
using DTS.Common.Enums;
|
|
using DTS.Common.Enums.DASFactory;
|
|
using DTS.Common.Enums.Hardware;
|
|
using DTS.Common.Interface;
|
|
using DTS.Common.Interface.TestDefinition;
|
|
using DTS.Common.SharedResource.Strings;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Windows;
|
|
using System.Windows.Media;
|
|
using System.Xml;
|
|
|
|
namespace DTS.Common.Utils
|
|
{
|
|
public static class Utils
|
|
{
|
|
private const float DefaultEpsilon = 1e-4f;
|
|
|
|
/// <summary>
|
|
/// Checks if a value is zero within 4 significant digits.
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static bool IsZero(float value, float epsilon = DefaultEpsilon)
|
|
{
|
|
return Math.Abs(value) < epsilon;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Compares two floats for equality within significant digits,
|
|
/// scaling the epsilon based on the magnitude of the numbers.
|
|
/// </summary>
|
|
public static bool AlmostEqual(float a, float b, float epsilon = DefaultEpsilon)
|
|
{
|
|
float diff = Math.Abs(a - b);
|
|
|
|
// Shortcut for zeros/near-zeros
|
|
if (a == b) return true;
|
|
|
|
// Use relative error for larger numbers
|
|
// This ensures "4 significant digits" works whether the number is 0.001 or 1,000,000
|
|
return diff < epsilon * Math.Max(Math.Abs(a), Math.Abs(b));
|
|
}
|
|
private const string LINEAR_PATTERN = ".lin.";
|
|
|
|
|
|
public delegate string GetIsoCodeDelegate(string binaryFileName);
|
|
|
|
//https://stackoverflow.com/questions/11412956/what-is-the-best-way-of-validating-an-ip-address
|
|
public static bool ValidateIPv4(string ipString)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(ipString))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
string[] splitValues = ipString.Split('.');
|
|
if (splitValues.Length != 4)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
byte tempForParsing;
|
|
|
|
return splitValues.All(r => byte.TryParse(r, out tempForParsing));
|
|
}
|
|
|
|
/// <summary>
|
|
/// If any of the UDP addresses are invalid, return an error
|
|
/// </summary>
|
|
/// <param name="udpAddressStreamIn"></param>
|
|
/// <param name="errors"></param>
|
|
public static void ValidateUDPAddress(string udpAddressStreamIn, ref List<string> errors)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(udpAddressStreamIn)) { return; }
|
|
|
|
string[] udpAddresses = udpAddressStreamIn.Split(',');
|
|
foreach (var udpAddress in udpAddresses)
|
|
{
|
|
var trimmedUdpAddress = udpAddress.ToLower().Replace("udp://", "");
|
|
var firstIndexOfColon = trimmedUdpAddress.IndexOf(":");
|
|
var ipAddress = firstIndexOfColon > 1 ? trimmedUdpAddress.Substring(0, firstIndexOfColon) : string.Empty;
|
|
if ((!udpAddress.ToLower().StartsWith("udp") || !Uri.IsWellFormedUriString(udpAddress, UriKind.Absolute) || !ValidateIPv4(ipAddress)) &&
|
|
!errors.Contains(StringResources.EditStreamInputOrOutputControl_InvalidUDPAddress))
|
|
{
|
|
errors.Add(StringResources.EditStreamInputOrOutputControl_InvalidUDPAddress);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static List<string> GetFileList(string path)
|
|
{
|
|
if (path.Contains("ALL") || path.Contains("ROI"))
|
|
{
|
|
return FileUtils.FindFilesInDirectory(path, "chn");
|
|
}
|
|
else
|
|
{
|
|
FileUtils.FileList = new List<string>();
|
|
FileUtils.FindFiles(path, "chn");
|
|
return FileUtils.FileList;
|
|
}
|
|
}
|
|
private static string GetFullFilePathNewStyle(List<string> fileList, ITestMetadata tmd, string testId,
|
|
ITestChannel ch)
|
|
{
|
|
if (ch.Bridge == "SQUIB")
|
|
{
|
|
return ch.Number % 2 == 0
|
|
? fileList.Where(l => l.Contains(tmd.TestRun.DataType)).FirstOrDefault(f => f.Contains(testId + "Ch" + (ch.AbsoluteDisplayOrder + 1).ToString("000")))
|
|
: fileList.Where(l => l.Contains(tmd.TestRun.DataType)).Where(f => f.Contains(testId + "Ch" + (ch.AbsoluteDisplayOrder + 1).ToString("000"))).ToList()[1];
|
|
}
|
|
else
|
|
{
|
|
var matches = fileList.Where(l => l.Contains(tmd.TestRun.DataType)).Where(f => f.Contains(testId + "Ch" + (ch.AbsoluteDisplayOrder + 1).ToString("000")));
|
|
//14581 Data for linear, cubic poly sensor differs in run test and view data tile
|
|
//if we end up with 2 matches, one is the linear added version and one is the non linear version
|
|
//determine which file we actually want, and use it.
|
|
if (2 <= matches.Count())
|
|
{
|
|
if (string.IsNullOrWhiteSpace(ch.LinearizationFormula))
|
|
{
|
|
var fullFileName = matches.FirstOrDefault(f => f.Contains(LINEAR_PATTERN));
|
|
//if there are two files and neither contains a .lin, then we've like hit a situation described in
|
|
//MS 26913 where there was multiple downloads which resulted in the same channel information in two files
|
|
//we can likely select either of the files in this case as they are probably identical
|
|
return null == fullFileName ? matches.FirstOrDefault() : fullFileName;
|
|
}
|
|
else
|
|
{
|
|
return matches.FirstOrDefault(f => !f.Contains(LINEAR_PATTERN));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return matches.FirstOrDefault();
|
|
}
|
|
}
|
|
}
|
|
private static string GetFullFilePath(List<string> fileList, ITestMetadata tmd, string testId, int channelCount)
|
|
{
|
|
// DataPRO
|
|
if (!string.IsNullOrEmpty(tmd.TestRun.Software))
|
|
{
|
|
var testFileList = fileList
|
|
.Where(l => l.Contains(tmd.TestRun.DataType) && l.Contains(testId)).ToList();
|
|
if (0 == testFileList.Count)
|
|
{
|
|
testFileList = fileList.Where(l => l.Contains(testId)).ToList();
|
|
}
|
|
if (channelCount < testFileList.Count)
|
|
{
|
|
return testFileList[channelCount];
|
|
}
|
|
}
|
|
// SLICEWare
|
|
else
|
|
{
|
|
var testFileList = fileList
|
|
.Where(l => l.Contains(testId)).ToList();
|
|
if (channelCount < testFileList.Count)
|
|
{
|
|
return testFileList[channelCount];
|
|
}
|
|
}
|
|
return string.Empty;
|
|
}
|
|
/// <summary>
|
|
/// populates Test.Channels properties with details
|
|
/// </summary>
|
|
/// <param name="tmd">Test Metadata list</param>
|
|
/// <param name="path">file location</param>
|
|
/// <param name="GetIsoCode">function that returns an isocode given a filepath</param>
|
|
public static void SetChannelInfo(ITestMetadata tmd, string path, GetIsoCodeDelegate GetIsoCode)
|
|
{
|
|
var fileList = GetFileList(path);
|
|
|
|
var testId = tmd.TestRun.Id;
|
|
tmd.TestRun.Channels = new List<ITestChannel>();
|
|
tmd.TestRun.CalculatedChannels = new List<ITestChannel>();
|
|
var channelCount = 0;
|
|
foreach (var m in tmd.TestRun.Modules)
|
|
{
|
|
SetChannelInfoModule(m, fileList, tmd, testId, ref channelCount, GetIsoCode);
|
|
}
|
|
AddGraphs(tmd);
|
|
}
|
|
private static void SetChannelInfoModule(ITestModule m, List<string> fileList, ITestMetadata tmd,
|
|
string testId, ref int channelCount, GetIsoCodeDelegate getIsoCode)
|
|
{
|
|
var sn = m.SerialNumber;
|
|
foreach (var ch in m.Channels)
|
|
{
|
|
ch.ModuleSerialNumber = sn;
|
|
|
|
var fullFileName = string.Empty;
|
|
|
|
var newStyle = fileList.Where(l => l.Contains(tmd.TestRun.DataType)).Any(f => f.Contains(testId + "Ch"));
|
|
if (!newStyle)
|
|
{
|
|
fullFileName = GetFullFilePath(fileList, tmd, testId, channelCount);
|
|
}
|
|
else
|
|
{
|
|
fullFileName = GetFullFilePathNewStyle(fileList, tmd, testId, ch);
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(fullFileName)) { ch.ErrorMessage = "File does not exist"; }
|
|
else
|
|
{
|
|
ch.BinaryFilePath = Path.GetDirectoryName(fullFileName);
|
|
ch.BinaryFileName = Path.GetFileName(fullFileName);
|
|
if (string.IsNullOrEmpty(ch.IsoCode))
|
|
{
|
|
ch.IsoCode = getIsoCode(ch.BinaryFilePath + $"\\" + ch.BinaryFileName);
|
|
}
|
|
}
|
|
|
|
ch.ParentModule = m;
|
|
ch.ParentTestSetup = tmd.TestSetup;
|
|
tmd.TestRun.Channels.Add(ch);
|
|
channelCount++;
|
|
}
|
|
foreach (var ch in m.CalculatedChannels)
|
|
{
|
|
var fullFileName = fileList.Where(l => l.Contains(tmd.TestRun.DataType)).FirstOrDefault(f => f.Contains(testId + "." + ch.Number.ToString()));
|
|
if (string.IsNullOrEmpty(fullFileName)) { ch.ErrorMessage = "File does not exist"; }
|
|
else
|
|
{
|
|
ch.ModuleSerialNumber = sn;
|
|
ch.ParentModule = m;
|
|
ch.ParentTestSetup = tmd.TestSetup;
|
|
ch.BinaryFilePath = Path.GetDirectoryName(fullFileName);
|
|
ch.BinaryFileName = Path.GetFileName(fullFileName);
|
|
}
|
|
tmd.TestRun.CalculatedChannels.Add(ch);
|
|
}
|
|
}
|
|
private static void AddGraphs(ITestMetadata tmd)
|
|
{
|
|
foreach (var g in tmd.TestSetup.TestGraphs)
|
|
{
|
|
g.Channels = new List<ITestChannel>();
|
|
foreach (var chId in g.ChannelIds)
|
|
{
|
|
var ch = (from c in tmd.TestRun.Channels where c.ChannelId == chId select c).FirstOrDefault();
|
|
if (ch != null) g.Channels.Add(ch.Copy());
|
|
}
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Copied from DataPro
|
|
/// per http://fogbugz/fogbugz/default.asp?8281 use 120%
|
|
/// The idea here is to have a moving window no smaller than +/-1% of capacity
|
|
/// Also make sure that the top of the signal is seen so make the visible window
|
|
/// +/-120% of the signal
|
|
/// this was abstrated here from two different locations for:
|
|
/// 29416 Limit view of TSR AIR data to 1% of full scale in auto range mode
|
|
/// </summary>
|
|
/// <param name="capacity">plot expected maximum EU</param>
|
|
/// <param name="dataMin">actual plot min value</param>
|
|
/// <param name="dataMax">actual plot max value</param>
|
|
/// <param name="maxAutoZoom">the scaler to apply to signal as the window max edge</param>
|
|
/// <param name="minAutoZoom">the scaler to apply to expected maximum to form window min edges</param>
|
|
/// <param name="currentYMin">min Y value to apply as plot constraint</param>
|
|
/// <param name="currentYMax">max Y value to apply as plot constraint</param>
|
|
/// <remarks>
|
|
/// </remarks>
|
|
public static void CalculateMinMaxAutoScaling(double capacity,
|
|
double dataMin,
|
|
double dataMax,
|
|
double minAutoZoom,
|
|
double maxAutoZoom,
|
|
out double currentYMax,
|
|
out double currentYMin
|
|
)
|
|
{
|
|
capacity = Math.Abs(capacity);
|
|
|
|
var middleOfSignalExtrema = ((dataMax - dataMin) / 2.0D) + dataMin;
|
|
var minZoom = capacity * minAutoZoom;
|
|
var topOfMinZoomWindow = middleOfSignalExtrema + minZoom;
|
|
var bottomOfMinZoomWindow = middleOfSignalExtrema - minZoom;
|
|
|
|
currentYMax = (dataMax + Math.Abs(dataMax * (1 - maxAutoZoom)) >= topOfMinZoomWindow) ?
|
|
dataMax + Math.Abs(dataMax * (1 - maxAutoZoom)) : topOfMinZoomWindow;
|
|
currentYMin = (dataMin - Math.Abs(dataMin * (1 - maxAutoZoom)) <= bottomOfMinZoomWindow) ?
|
|
dataMin - Math.Abs(dataMin * (1 - maxAutoZoom)) : bottomOfMinZoomWindow;
|
|
}
|
|
|
|
/// <summary>
|
|
/// test code courtesy of AutoLab XML import/conversion code via Chad Ivan
|
|
/// the CRC should be prepended to the ID (CRC + ID)
|
|
/// </summary>
|
|
/// <param name="ShortID">short ID</param>
|
|
/// <returns>returns CRC bit for given string</returns>
|
|
public static string GenerateEIDCRC(string ShortID)
|
|
{
|
|
var int64 = Int64.Parse(ShortID, System.Globalization.NumberStyles.HexNumber);
|
|
var bytes = BitConverter.GetBytes(int64);
|
|
var bitArray = new System.Collections.BitArray(bytes);
|
|
var CRCbit = new System.Collections.BitArray(8);
|
|
var CRCTemp = new System.Collections.BitArray(1);
|
|
byte[] CRCbyte = new byte[1];
|
|
|
|
for (int i = 0; i < 56; i++)
|
|
{
|
|
CRCTemp[0] = CRCbit[0] ^ bitArray[i];
|
|
CRCbit[0] = CRCbit[1];
|
|
CRCbit[1] = CRCbit[2];
|
|
CRCbit[2] = CRCbit[3] ^ CRCTemp[0];
|
|
CRCbit[3] = CRCbit[4] ^ CRCTemp[0];
|
|
CRCbit[4] = CRCbit[5];
|
|
CRCbit[5] = CRCbit[6];
|
|
CRCbit[6] = CRCbit[7];
|
|
CRCbit[7] = CRCTemp[0];
|
|
}
|
|
|
|
CRCbit.CopyTo(CRCbyte, 0);
|
|
return BitConverter.ToString(CRCbyte); ;
|
|
}
|
|
/// <summary>
|
|
/// handles indenting and formatting for xml text
|
|
/// </summary>
|
|
/// <param name="XML"></param>
|
|
/// <returns></returns>
|
|
public static string PrettyPrint(string XML)
|
|
{
|
|
var Result = "";
|
|
|
|
using (var MS = new MemoryStream())
|
|
{
|
|
using (var W = new XmlTextWriter(MS, Encoding.Unicode))
|
|
{
|
|
var D = new XmlDocument();
|
|
try
|
|
{
|
|
// Load the XmlDocument with the XML.
|
|
D.LoadXml(XML);
|
|
|
|
W.Formatting = Formatting.Indented;
|
|
|
|
// Write the XML into a formatting XmlTextWriter
|
|
D.WriteContentTo(W);
|
|
W.Flush();
|
|
MS.Flush();
|
|
|
|
// Have to rewind the MemoryStream in order to read
|
|
// its contents.
|
|
MS.Position = 0;
|
|
|
|
// Read MemoryStream contents into a StreamReader.
|
|
using (var SR = new StreamReader(MS))
|
|
{// Extract the text from the StreamReader.
|
|
var FormattedXML = SR.ReadToEnd();
|
|
Result = FormattedXML;
|
|
}
|
|
}
|
|
catch (XmlException) { }
|
|
|
|
MS.Close();
|
|
W.Close();
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
//https://docs.microsoft.com/en-us/dotnet/standard/base-types/how-to-display-milliseconds-in-date-and-time-values
|
|
//FB 29410 Format the date time to string with milliseonds
|
|
/// <summary>
|
|
/// Format a DateTime with nanoseconds part to a date time string with milliseconds in a current culture
|
|
/// </summary>
|
|
/// <param name="dateTimeWithNanoSeconds">DateTime object with nano seconds part</param>
|
|
/// <returns>Formatted date time in string</returns>
|
|
public static string FormatTimeStamp(DateTime dateTimeWithNanoSeconds)
|
|
{
|
|
string fullPattern = DateTimeFormatInfo.CurrentInfo.LongTimePattern;
|
|
|
|
// Create a format similar to .fff but based on the current culture.
|
|
string millisecondFormat = $"{NumberFormatInfo.CurrentInfo.NumberDecimalSeparator}fff";
|
|
|
|
// Append millisecond pattern to current culture's full date time pattern.
|
|
fullPattern = Regex.Replace(fullPattern, "(:ss|:s)", $"$1{millisecondFormat}");
|
|
|
|
string time = dateTimeWithNanoSeconds.ToString(fullPattern);
|
|
string date = dateTimeWithNanoSeconds.ToShortDateString();
|
|
string dateTime = $"{date} {time}";
|
|
|
|
return dateTime;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// returns the next highest power of 2
|
|
/// </summary>
|
|
/// <param name="numSamples"></param>
|
|
/// <returns></returns>
|
|
public static int GetEnclosingPower2(int numSamples)
|
|
{
|
|
//return Convert.ToInt32(Math.Pow(2D, Math.Ceiling(Math.Log(Convert.ToDouble(numSamples)) / Math.Log(2D))));
|
|
// 24418 use bitwise calc for better performance
|
|
var next = (uint)numSamples;
|
|
next--;
|
|
next |= next >> 1;
|
|
next |= next >> 2;
|
|
next |= next >> 4;
|
|
next |= next >> 8;
|
|
next |= next >> 16;
|
|
next++;
|
|
return (int)next;
|
|
}
|
|
public static long GetEnclosingPower2(long numSamples)
|
|
{
|
|
//return Convert.ToInt32(Math.Pow(2D, Math.Ceiling(Math.Log(Convert.ToDouble(numSamples)) / Math.Log(2D))));
|
|
// 24418 use bitwise calc for better performance
|
|
var next = (ulong)numSamples;
|
|
next--;
|
|
next |= next >> 1;
|
|
next |= next >> 2;
|
|
next |= next >> 4;
|
|
next |= next >> 8;
|
|
next |= next >> 16;
|
|
next |= next >> 32;
|
|
next++;
|
|
return (long)next;
|
|
}
|
|
public static string EscapeString(string str)
|
|
{
|
|
if (null == str) { return "#NOVALUE"; }
|
|
var mustQuote = (str.Contains(",") || str.Contains("\"") || str.Contains("\r") || str.Contains("\n"));
|
|
if (!mustQuote) return str;
|
|
var sb = new StringBuilder();
|
|
sb.Append("\"");
|
|
foreach (var nextChar in str)
|
|
{
|
|
sb.Append(nextChar);
|
|
if (nextChar == '"') sb.Append("\"");
|
|
}
|
|
sb.Append("\"");
|
|
return sb.ToString();
|
|
}
|
|
|
|
public static string ReplaceLast(this string str, string find, string replace)
|
|
{
|
|
var place = str.LastIndexOf(find, StringComparison.Ordinal);
|
|
return -1 == place
|
|
? str
|
|
: str.Remove(place, find.Length).Insert(place, replace);
|
|
}
|
|
public static string ReplaceFirst(this string str, string find, string replace)
|
|
{
|
|
var place = str.IndexOf(find, StringComparison.Ordinal);
|
|
return -1 == place
|
|
? str
|
|
: str.Remove(place, find.Length).Insert(place, replace);
|
|
}
|
|
public static void ForEach<T>(this IEnumerable<T> list, Action<T> action)
|
|
{
|
|
foreach (var item in list)
|
|
{
|
|
action(item);
|
|
}
|
|
}
|
|
public static string ReplaceStrings(this string str, Dictionary<string, string> toReplace, Common.Enums.StringReplacementMode mode)
|
|
{
|
|
try
|
|
{
|
|
switch (mode)
|
|
{
|
|
case StringReplacementMode.All:
|
|
toReplace.ForEach(pair => str = str.Replace(pair.Key, pair.Value));
|
|
break;
|
|
case StringReplacementMode.First:
|
|
toReplace.ForEach(pair => str = str.ReplaceFirst(pair.Key, pair.Value));
|
|
break;
|
|
case StringReplacementMode.Last:
|
|
toReplace.ForEach(pair => str = str.ReplaceLast(pair.Key, pair.Value));
|
|
break;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// ignored, bad data in
|
|
}
|
|
|
|
return str;
|
|
}
|
|
public static void GetChildren(UIElement parent, Type targetType, ref List<UIElement> children)
|
|
{
|
|
var count = VisualTreeHelper.GetChildrenCount(parent);
|
|
if (count <= 0) return;
|
|
for (var i = 0; i < count; i++)
|
|
{
|
|
var child = (UIElement)VisualTreeHelper.GetChild(parent, i);
|
|
if (child.GetType() == targetType) { children.Add(child); }
|
|
|
|
GetChildren(child, targetType, ref children);
|
|
}
|
|
}
|
|
|
|
public static void GetChildrenByName(UIElement parent, string targetName, ref List<FrameworkElement> children)
|
|
{
|
|
var count = VisualTreeHelper.GetChildrenCount(parent);
|
|
if (count <= 0) return;
|
|
for (var i = 0; i < count; i++)
|
|
{
|
|
var child = (UIElement)VisualTreeHelper.GetChild(parent, i);
|
|
if (((string)child.GetType().GetProperty("Name").GetValue(child, null)).Contains(targetName)) { children.Add((FrameworkElement)child); }
|
|
|
|
GetChildrenByName(child, targetName, ref children);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds a Child of a given item in the visual tree.
|
|
/// </summary>
|
|
/// <param name="parent">A direct parent of the queried item.</param>
|
|
/// <typeparam name="T">The type of the queried item.</typeparam>
|
|
/// <param name="childName">x:Name or Name of child. </param>
|
|
/// <returns>The first parent item that matches the submitted type parameter.
|
|
/// If not matching item can be found,
|
|
/// a null parent is being returned.</returns>
|
|
public static T FindChild<T>(DependencyObject parent, string childName)
|
|
where T : DependencyObject
|
|
{
|
|
// Confirm parent and childName are valid.
|
|
if (parent == null) return null;
|
|
|
|
T foundChild = null;
|
|
|
|
var childrenCount = VisualTreeHelper.GetChildrenCount(parent);
|
|
for (var i = 0; i < childrenCount; i++)
|
|
{
|
|
var child = VisualTreeHelper.GetChild(parent, i);
|
|
// If the child is not of the request child type child
|
|
if (!(child is T))
|
|
{
|
|
// recursively drill down the tree
|
|
foundChild = FindChild<T>(child, childName);
|
|
|
|
// If the child is found, break so we do not overwrite the found child.
|
|
if (foundChild != null) break;
|
|
}
|
|
else if (!string.IsNullOrEmpty(childName))
|
|
{
|
|
// If the child's name is set for search
|
|
if (child is FrameworkElement frameworkElement && frameworkElement.Name == childName)
|
|
{
|
|
// if the child's name is of the request name
|
|
foundChild = (T)child;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// child element found.
|
|
foundChild = (T)child;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return foundChild;
|
|
}
|
|
|
|
public static T FindParent<T>(DependencyObject child)
|
|
where T : DependencyObject
|
|
{
|
|
if (null == child) return null;
|
|
T visParent = VisualTreeHelper.GetParent(child) as T;
|
|
T logParent = LogicalTreeHelper.GetParent(child) as T;
|
|
|
|
return visParent ?? logParent ?? FindParent<T>(VisualTreeHelper.GetParent(child));
|
|
}
|
|
|
|
public static T FindVisualChild<T>(DependencyObject obj)
|
|
where T : DependencyObject
|
|
{
|
|
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
|
|
{
|
|
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
|
|
if (child is T variable)
|
|
return variable;
|
|
var childOfChild = FindVisualChild<T>(child);
|
|
if (childOfChild != null)
|
|
return childOfChild;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public static List<T> GetVisualChildren<T>(DependencyObject obj) where T : DependencyObject
|
|
{
|
|
var childList = new List<T>();
|
|
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
|
|
{
|
|
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
|
|
if (child is T)
|
|
childList.Add(child as T);
|
|
}
|
|
return childList.Count > 0 ? childList : null;
|
|
}
|
|
|
|
|
|
public static string GetEnumDescription<T>(this T enumerationValue) where T : struct
|
|
{
|
|
var type = enumerationValue.GetType();
|
|
if (!type.IsEnum)
|
|
{
|
|
throw new ArgumentException($"{nameof(enumerationValue)} must be of Enum type", nameof(enumerationValue));
|
|
}
|
|
var memberInfo = type.GetMember(enumerationValue.ToString());
|
|
if (memberInfo.Length > 0)
|
|
{
|
|
var attrs = memberInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
|
|
|
|
if (attrs.Length > 0)
|
|
{
|
|
return ((DescriptionAttribute)attrs[0]).Description;
|
|
}
|
|
}
|
|
return enumerationValue.ToString();
|
|
}
|
|
|
|
public static int FindValueInArray(double value, double[] valueArray)
|
|
{
|
|
for (var index = 0; index < valueArray.Length; index++)
|
|
{
|
|
if (value == valueArray[index])
|
|
{
|
|
return index;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
public static double StandardDeviation(this IEnumerable<double> values)
|
|
{
|
|
var avg = values.Average();
|
|
return Math.Sqrt(values.Average(val => (val - avg) * (val - avg)));
|
|
}
|
|
|
|
public static void CalculateClip(double[] data,
|
|
out double clipPeak,
|
|
out double clipStartSeconds,
|
|
out double clipEndSeconds,
|
|
double sampleRate,
|
|
int preZeroDataPoints)
|
|
{
|
|
|
|
clipPeak = 0;
|
|
clipStartSeconds = 0;
|
|
clipEndSeconds = 0;
|
|
|
|
/* From TDC */
|
|
for (var index = 0; index < data.Length - Math.Floor(0.003 / (1 / sampleRate) + 0.5); index++)
|
|
{
|
|
var localclip = 10000D;
|
|
|
|
for (var localIndex = index; localIndex < index + Math.Floor(0.003 / (1 / sampleRate) + 0.5); localIndex++)
|
|
{
|
|
if (data[localIndex] < localclip)
|
|
{
|
|
localclip = data[localIndex];
|
|
}
|
|
}
|
|
|
|
if (!(localclip > clipPeak)) continue;
|
|
clipPeak = localclip;
|
|
clipStartSeconds = (index - preZeroDataPoints) * (1 / sampleRate);
|
|
clipEndSeconds = clipStartSeconds + 0.003;
|
|
}
|
|
}
|
|
|
|
private static readonly short[] ODDPARITY = {0, 1, 1, 0, 1, 0, 0, 1,
|
|
1, 0, 0, 1, 0, 1, 1, 0 };
|
|
|
|
public static byte Math_CRC7(byte[] buffer, int length)
|
|
{
|
|
int i;
|
|
int j;
|
|
byte crc = 0xFF;
|
|
|
|
for (i = 0; i < length; i++)
|
|
{
|
|
var currentByte = buffer[i];
|
|
for (j = 0; j < 8; j++)
|
|
{
|
|
crc = (byte)(crc << 1);
|
|
if (0x00 != ((currentByte ^ crc) & 0x80))
|
|
{
|
|
crc = (byte)(crc ^ 0x09);
|
|
}
|
|
|
|
currentByte <<= 1;
|
|
}
|
|
|
|
crc = (byte)(crc & 0x7F);
|
|
}
|
|
|
|
crc <<= 1;
|
|
crc |= 0x01;
|
|
|
|
return crc;
|
|
}
|
|
|
|
/**
|
|
* Computes the 16-bit CRC of the n passed in data bytes. N should be
|
|
* even (if it is not, the last byte will not get included in the
|
|
* computation). The initial_crc is the value to seed the computation
|
|
* from - normally it will be set to 0.
|
|
*
|
|
* @param data the unsigned character buffer of which to compute the CRC16
|
|
* @param n the number of bytes in data (must be even!)
|
|
* @param initial_crc the CRC16 value to seed the computation with
|
|
*
|
|
* @return the updated CRC16 starting with initial_crc and computed over
|
|
* n data bytes
|
|
*/
|
|
/* Slightly changed to handle array of bytes */
|
|
public static ushort CalculateCRC(byte[] data, ushort initialCRC)
|
|
{
|
|
|
|
ushort crc = 0;//initial_crc98765430.
|
|
int i;
|
|
|
|
for (i = 0; i < data.Length; i += 2)
|
|
{
|
|
crc = Math_DoCRC16Step((ushort)((data[i + 1] << 8) | data[i]), crc);
|
|
}
|
|
|
|
return crc;
|
|
}
|
|
|
|
/**
|
|
* Computes the 16-bit CRC of the n passed in data bytes. CCITT implementation
|
|
* N should be
|
|
* even (if it is not, the last byte will not get included in the
|
|
* computation). The initial_crc is the value to seed the computation
|
|
* from - normally it will be set to 0.
|
|
*
|
|
* @param data the unsigned character buffer of which to compute the CRC16
|
|
* @param n the number of bytes in data (must be even!)
|
|
* @param initial_crc the CRC16 value to seed the computation with
|
|
*
|
|
* @return the updated CRC16 starting with initial_crc and computed over
|
|
* n data bytes
|
|
*/
|
|
/* Slightly changed to handle array of bytes */
|
|
public static ushort CalculateCRCCCITT(byte[] data, ushort initialCRC)
|
|
{
|
|
|
|
var crc = initialCRC;
|
|
int i;
|
|
|
|
for (i = 0; i < data.Length; i += 2)
|
|
{
|
|
crc = Math_DoCRCCCITTStep((ushort)((data[i + 1] << 8) | data[i]), crc);
|
|
}
|
|
|
|
return crc;
|
|
}
|
|
|
|
public static ushort Math_DoCRC16Step(ushort cdata, ushort currentCRC)
|
|
{
|
|
cdata = (ushort)((cdata ^ (currentCRC & 0xff)) & 0xff);
|
|
currentCRC >>= 8;
|
|
|
|
if (0 != (ODDPARITY[cdata & 0xf] ^ ODDPARITY[cdata >> 4]))
|
|
currentCRC ^= 0xc001;
|
|
|
|
cdata <<= 6;
|
|
currentCRC ^= cdata;
|
|
cdata <<= 1;
|
|
currentCRC ^= cdata;
|
|
|
|
return currentCRC;
|
|
}
|
|
|
|
private static readonly ushort[] CRC_TABLE =
|
|
{
|
|
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5,
|
|
0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b,
|
|
0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210,
|
|
0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
|
|
0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c,
|
|
0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401,
|
|
0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b,
|
|
0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
|
|
0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6,
|
|
0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738,
|
|
0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5,
|
|
0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
|
|
0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969,
|
|
0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96,
|
|
0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc,
|
|
0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
|
|
0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03,
|
|
0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd,
|
|
0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6,
|
|
0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
|
|
0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a,
|
|
0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb,
|
|
0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1,
|
|
0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
|
|
0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c,
|
|
0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2,
|
|
0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb,
|
|
0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
|
|
0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447,
|
|
0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8,
|
|
0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2,
|
|
0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
|
|
0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9,
|
|
0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827,
|
|
0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c,
|
|
0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
|
|
0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0,
|
|
0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d,
|
|
0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07,
|
|
0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
|
|
0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba,
|
|
0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74,
|
|
0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
|
|
};
|
|
|
|
public static ushort Math_DoCRCCCITTStep(ushort cdata, ushort current_crc)
|
|
{
|
|
return (ushort)((ushort)(current_crc << 8) ^ CRC_TABLE[(ushort)(((ushort)(current_crc >> 8) ^ cdata) & 0xFF)]);
|
|
}
|
|
|
|
public static void FilterDataArray(ref double[] data, double sampleRate, double filterFrequency)
|
|
{
|
|
/********************************/
|
|
/*** MCW NHTSA FILTER ROUTINE ***/
|
|
/********************************/
|
|
|
|
//
|
|
// CFILTER.C cfc filter program
|
|
//
|
|
// Filters data using SAE J211 algorithym (4 pole phaseless lo pass).
|
|
// "Pads" data with 5% of the original number of points at the beginning with the first point
|
|
// and 5% of the original number of points at the end with the last point
|
|
// to "squelch" out filter start up spikes.
|
|
// Previously read data file from disc, filtered and wrote new data file to disc.
|
|
// NOW read/writes to array(s) in memory.
|
|
// Optionally zeros baseline by subtracting the average of the first 5%
|
|
// of the original number of points; SKIPS data which is "padded" by this routine
|
|
// Optionally scales the data after filtering. SCALE = 1.0 skips to avoid unnecessary calc's.
|
|
//
|
|
// "syntax:"
|
|
//
|
|
// cfilter(INdata, SCALE, OUTdata, SampleRate, CFCnumb, BaseLine, NumPoints);
|
|
//
|
|
// INdata (pointer?): array (double) of data to be filtered
|
|
// SCALE (double): a number multiplied by the above array after it is filtered.
|
|
// Useful for "last minute" scaling and/or inversion
|
|
// Outdata (pointer?): array (double) into which the results are stored.
|
|
// INdata can = OUTdata, INdata will then be overwrittten.
|
|
// SampleRate (double): the frequency(in samples per second) at which the INdata were sampled.
|
|
// CFCnumb (double): SAE Channel Filter Class;
|
|
// 60, 180, 600, or 1000; ONLY these values have been validated.
|
|
// BaseLine (char): a single character; ONLY "Y" or "y" will zero baseline;
|
|
// (This started as a command line style FORTRAN program and used to "ask"
|
|
// the user. Some day this should be changed to an integer (1 or 0) switch.)
|
|
// NumPoints (int): the number of points in the input array (INdata);
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// int cfilter(double *indata, double calib, double *out, double freq,
|
|
// double cfc, char baseline[2], int n)
|
|
// {
|
|
double[] Z;
|
|
double[] A;
|
|
double t, pi, wd, wa, d0, d1, d2, e1, e2;
|
|
double f1, f2, f3, f4, f5;
|
|
int data_end, i, j, k, pad;
|
|
double[] NewDataArray;
|
|
var calib = 1.0;
|
|
var CFC = filterFrequency * 0.6;
|
|
|
|
// The set-up of the formulas.
|
|
|
|
t = 1.0 / sampleRate;
|
|
pi = 4.0 * Math.Atan(1.0);
|
|
wd = 2.0 * pi * CFC * 2.0775;
|
|
wa = Math.Sin(wd * (t / 2.0)) / Math.Cos(wd * (t / 2.0)); // The original formula
|
|
d0 = wa * wa / (1 + Math.Sqrt(2.0) * wa + wa * wa);
|
|
d1 = 2.0 * d0;
|
|
d2 = d0;
|
|
e1 = -2.0 * (wa * wa - 1) / (1 + Math.Sqrt(2.0) * wa + wa * wa);
|
|
e2 = (-1 + Math.Sqrt(2.0) * wa - wa * wa) / (1 + Math.Sqrt(2.0) * wa + wa * wa);
|
|
|
|
data_end = data.Length - 1; // data_end is used for loop counting corresponding
|
|
// to the number of lines that the data file has
|
|
pad = (int)(0.05 * data.Length);
|
|
Z = new double[pad + pad + data.Length];
|
|
A = new double[pad + pad + data.Length];
|
|
NewDataArray = new double[pad + pad + data.Length];
|
|
|
|
for (i = 0; i <= pad - 1; i++) // pad newdata at beginning
|
|
{ // w/ first pt of indata
|
|
NewDataArray[i] = data[i];
|
|
}
|
|
|
|
for (i = pad; i <= data_end + pad - 1; i++) // Copy indata into newdata
|
|
{ // after initial pad
|
|
NewDataArray[i] = data[i - (pad - 1)];
|
|
}
|
|
|
|
for (i = data_end + pad; i <= data_end + 2 * pad - 1; i++) // pad newdata at end
|
|
{ // w/ last pt of indata
|
|
NewDataArray[i] = data[data_end - 1];
|
|
}
|
|
|
|
data_end = data_end + 2 * pad;
|
|
|
|
// Data Passed Through Filter Backward
|
|
|
|
for (j = data_end - 2; j >= 0; j--)
|
|
{
|
|
f1 = d0 * NewDataArray[j]; // Breaking
|
|
f2 = d1 * NewDataArray[j + 1]; // down
|
|
f3 = d2 * NewDataArray[j + 2]; // a big
|
|
f4 = e1 * Z[j + 1]; // formula
|
|
f5 = e2 * Z[j + 2]; // into smaller "groups".
|
|
Z[j] = f1 + f2 + f3 + f4 + f5; // Putting the "groups" back together
|
|
}
|
|
Z[data_end - 1] = Z[data_end - 3];
|
|
Z[data_end - 2] = Z[data_end - 3];
|
|
|
|
// Data Passed Through Filter Forward
|
|
|
|
for (k = 3; k <= data_end; k++)
|
|
{
|
|
f1 = d0 * Z[k]; // Breaking
|
|
f2 = d1 * Z[k - 1]; // down
|
|
f3 = d2 * Z[k - 2]; // a big
|
|
f4 = e1 * A[k - 1]; // formula
|
|
f5 = e2 * A[k - 2]; // into smaller "groups".
|
|
A[k] = f1 + f2 + f3 + f4 + f5; // Putting the "groups" back together
|
|
}
|
|
A[1] = A[3]; // This sets the first data points to the same
|
|
A[2] = A[3]; // value as the 3rd value. Without this, the
|
|
|
|
i = 0;
|
|
for (k = pad; k <= data_end - pad; k++) // DO loop to write output file
|
|
{
|
|
if (calib != 1.0) //
|
|
{ // Multiplying by the calibration factor
|
|
A[k] = A[k] * calib; // Unless factor = 1.0
|
|
} // AFTER the filtering has been finished
|
|
|
|
NewDataArray[i] = A[k];
|
|
i++;
|
|
}
|
|
|
|
Array.Copy(NewDataArray, 0, data, 0, data.Length);
|
|
|
|
}
|
|
|
|
public static bool IsLike(string text, string pattern, bool caseSensitive = false)
|
|
{
|
|
//Check for empties
|
|
if (string.IsNullOrWhiteSpace(text) || string.IsNullOrWhiteSpace(pattern)) return true;
|
|
|
|
//Prepare for regex
|
|
pattern = pattern.Replace(".", @"\.");
|
|
pattern = pattern.Replace("(", @"\(");
|
|
pattern = pattern.Replace(")", @"\)");
|
|
pattern = pattern.Replace("?", ".");
|
|
//pattern = pattern.Replace("*", ".*?");
|
|
pattern = pattern.Replace(@"\", @"\\");
|
|
pattern = pattern.Replace(" ", @"\s");
|
|
try
|
|
{
|
|
return new Regex(pattern, caseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase).IsMatch(text);
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public static bool? IsClockSynced(IDictionary<InputClockSource, bool> clocksyncs, ClockSyncProfile profile, HardwareTypes hwType)
|
|
{
|
|
if (HardwareTypes.SLICE_Pro_Distributor == hwType) { return null; }
|
|
if (HardwareTypes.SLICE6DB3 == hwType) { return null; }
|
|
if ((HardwareTypes.SLICE6_Base == hwType || HardwareTypes.SLICE6DB == hwType || HardwareTypes.SLICE6DB_InDummy == hwType) && profile == ClockSyncProfile.Manual)
|
|
{
|
|
//18030 S6, S6DB can only do PTP so "manual" is always PTP
|
|
profile = ClockSyncProfile.Auto_E2E;
|
|
}
|
|
bool? synced = null;
|
|
switch (profile)
|
|
{
|
|
case ClockSyncProfile.Master_E2E:
|
|
break;
|
|
case ClockSyncProfile.Auto_E2E:
|
|
case ClockSyncProfile.Slave_E2E:
|
|
synced = clocksyncs[InputClockSource.PTP];
|
|
break;
|
|
case ClockSyncProfile.EXT_PPS:
|
|
case ClockSyncProfile.Master_E2E_EXT_PPS:
|
|
synced = clocksyncs[InputClockSource.OnePPS];
|
|
break;
|
|
case ClockSyncProfile.GPS:
|
|
case ClockSyncProfile.Master_E2E_GPS:
|
|
synced = clocksyncs[InputClockSource.GPS];
|
|
break;
|
|
case ClockSyncProfile.IRIG:
|
|
case ClockSyncProfile.Master_E2E_IRIG:
|
|
synced = clocksyncs[InputClockSource.IRIG];
|
|
break;
|
|
case ClockSyncProfile.GPS_EXT_PPS:
|
|
case ClockSyncProfile.Master_E2E_GPS_EXT_PPS:
|
|
synced = clocksyncs[InputClockSource.GPS] &&
|
|
clocksyncs[InputClockSource.OnePPS];
|
|
break;
|
|
case ClockSyncProfile.IRIG_EXT_PPS:
|
|
case ClockSyncProfile.Master_E2E_IRIG_EXT_PPS:
|
|
synced = clocksyncs[InputClockSource.IRIG] &&
|
|
clocksyncs[InputClockSource.OnePPS];
|
|
break;
|
|
}
|
|
return synced;
|
|
}
|
|
|
|
public static bool IsTmNSString(string tmns)
|
|
{
|
|
return tmns.StartsWith("(") &&
|
|
tmns.EndsWith(")") &&
|
|
Array.TrueForAll(tmns.TrimStart(new[] { '(' }).TrimEnd(new[] { ')' }).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries), sval => uint.TryParse(sval, out var uval) && uval < 65536);
|
|
}
|
|
|
|
const double _1 = 0.1;
|
|
const double _2 = 0.01;
|
|
const double _3 = 0.001;
|
|
const double _4 = 0.0001;
|
|
const double _5 = 0.00001;
|
|
const double _6 = 0.000001;
|
|
const double _7 = 0.0000001;
|
|
public static bool EqualsDigitPrecision(this double left, double right, uint precision)
|
|
{
|
|
var diff = Math.Abs(left - right);
|
|
switch (precision)
|
|
{
|
|
case 0:
|
|
return (int)left == (int)right;
|
|
case 1:
|
|
return diff < _1;
|
|
case 2:
|
|
return diff < _2;
|
|
case 3:
|
|
return diff < _3;
|
|
case 4:
|
|
return diff < _4;
|
|
case 5:
|
|
return diff < _5;
|
|
case 6:
|
|
return diff < _6;
|
|
default:
|
|
case 7:
|
|
return diff < _7;
|
|
}
|
|
}
|
|
public static string GetThermocouplerTypeString(int thermocouplerType)
|
|
{
|
|
switch (thermocouplerType)
|
|
{
|
|
case (int)ChannelTypes.SliceTcType_C: return Strings.Strings.ThermocouplerC;
|
|
case (int)ChannelTypes.SliceTcType_E: return Strings.Strings.ThermocouplerE;
|
|
case (int)ChannelTypes.SliceTcType_G: return Strings.Strings.ThermocouplerG;
|
|
case (int)ChannelTypes.SliceTcType_J: return Strings.Strings.ThermocouplerJ;
|
|
case (int)ChannelTypes.SliceTcType_K: return Strings.Strings.ThermocouplerK;
|
|
case (int)ChannelTypes.SliceTcType_N: return Strings.Strings.ThermocouplerN;
|
|
case (int)ChannelTypes.SliceTcType_R: return Strings.Strings.ThermocouplerR;
|
|
case (int)ChannelTypes.SliceTcType_S: return Strings.Strings.ThermocouplerS;
|
|
case (int)ChannelTypes.SliceTcType_T: return Strings.Strings.ThermocouplerT;
|
|
default: return Strings.Strings.UnknownThermocouplerType;
|
|
}
|
|
}
|
|
}
|
|
}
|