//****************************************************************************************************** // AnalogChannel.cs - Gbtc // // Copyright © 2012, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://www.opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 05/19/2011 - J. Ritchie Carroll // Generated original version of source code. // 12/13/2012 - Starlynn Danyelle Gilliam // Modified Header. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.Globalization; using GSF.PhasorProtocols; using GSF.Units; using GSF.Units.EE; using Newtonsoft.Json; namespace GSF.COMTRADE { /// /// Represents an analog channel definition of the . /// public class AnalogChannel { #region [ Members ] // Constants /// /// Default multiplier for current magnitude values. /// public const double DefaultCurrentMagnitudeMultiplier = 0.05D; /// /// Default multiplier for voltage magnitude values. /// public const double DefaultVoltageMagnitudeMultiplier = 5.77362D; /// /// Default multiplier for phase angle values. /// public const double DefaultPhaseAngleMultiplier = 1.0E-4D; /// /// Default multiplier for frequency values. /// public const double DefaultFrequencyMultiplier = 0.001D; /// /// Default multiplier for dF/dt values. /// public const double DefaultDfDtMultiplier = 0.01D; /// /// Default multiplier for an analog value. /// public const double DefaultAnalogMultipler = 0.04D; // Fields private readonly bool m_targetFloatingPoint; private string m_stationName; private string m_channelName; private char m_phaseDesignation; private SignalKind m_signalKind; private double m_nominalFrequency; private string m_circuitComponent; private string m_units; private AngleUnit? m_angleUnit; private char m_scalingIdentifier; #endregion #region [ Constructors ] /// /// Creates a new instance of the . /// /// Target schema version. /// Determines if file type is targeting floating point. public AnalogChannel(int version = 1999, bool targetFloatingPoint = false) { Version = version; m_targetFloatingPoint = targetFloatingPoint; m_phaseDesignation = char.MinValue; m_signalKind = SignalKind.Analog; CoordinateFormat = CoordinateFormat.Polar; Multiplier = targetFloatingPoint ? 1.0D : DefaultAnalogMultipler; Adder = 0.0D; m_nominalFrequency = 60.0D; MinValue = targetFloatingPoint ? float.MinValue : -99999; MaxValue = targetFloatingPoint ? float.MaxValue : 99998; PrimaryRatio = 1.0D; SecondaryRatio = 1.0D; m_scalingIdentifier = 'P'; } /// /// Creates a new instance of the from an existing line image. /// /// Line image to parse. /// Target schema version. /// Determines if file type is targeting floating point. /// Indicates whether to relax validation on the number of line image elements. public AnalogChannel(string lineImage, int version = 1999, bool targetFloatingPoint = false, bool useRelaxedValidation = false) { // An,ch_id,ph,ccbm,uu,a,b,skew,min,max,primary,secondary,PS string[] parts = lineImage.Split(','); Version = version; m_targetFloatingPoint = targetFloatingPoint; if (parts.Length < 10 || (!useRelaxedValidation && parts.Length != 10 && parts.Length != 13)) throw new InvalidOperationException($"Unexpected number of line image elements for analog channel definition: {parts.Length} - expected 10 or 13{Environment.NewLine}Image = {lineImage}"); Index = int.Parse(parts[0].Trim()); Name = parts[1]; Units = parts[4]; // Assign Units before PhaseID PhaseID = parts[2]; CircuitComponent = parts[3]; Multiplier = double.Parse(parts[5].Trim()); Adder = double.Parse(parts[6].Trim()); Skew = double.Parse(parts[7].Trim()); MinValue = double.Parse(parts[8].Trim()); MaxValue = double.Parse(parts[9].Trim()); if (parts.Length >= 13) { PrimaryRatio = double.Parse(parts[10].Trim()); SecondaryRatio = double.Parse(parts[11].Trim()); ScalingIdentifier = parts[12].Trim()[0]; } } #endregion #region [ Properties ] /// /// Gets or sets index of this . /// public int Index { get; set; } /// /// Gets or sets name of this formatted as station_name:channel_name. /// /// Name must be formatted as station_name:channel_name. public string Name { get { if (string.IsNullOrEmpty(m_stationName)) return null; if (!string.IsNullOrEmpty(m_channelName)) return $"{m_stationName}:{m_channelName}"; return m_stationName; } set { string[] parts = value.Split(':'); if (parts.Length == 2) { m_stationName = parts[0].Trim(); m_channelName = parts[1].Trim(); } else { m_stationName = value; } } } /// /// Gets or sets station name component of this . /// public string StationName { get => m_stationName; set => m_stationName = value.Replace(":", "_").Trim(); } /// /// Gets or sets channel name component of this . /// public string ChannelName { get => m_channelName; set => m_channelName = value.Replace(":", "_").Trim(); } /// /// Gets or sets the 2-character phase identifier for this . /// public string PhaseID { get { switch (m_signalKind) { case SignalKind.Magnitude: if (m_phaseDesignation != char.MinValue) { if (CoordinateFormat == CoordinateFormat.Rectangular) return m_phaseDesignation + "r"; return m_phaseDesignation + "m"; } break; case SignalKind.Angle: if (m_phaseDesignation != char.MinValue) { if (CoordinateFormat == CoordinateFormat.Rectangular) return m_phaseDesignation + "i"; return m_phaseDesignation + "a"; } break; case SignalKind.Frequency: return "F"; case SignalKind.DfDt: return "df"; } return PhaseDesignation; } set { value = value.Trim(); if (string.IsNullOrEmpty(value)) { m_phaseDesignation = char.MinValue; SignalKind = SignalKind.Analog; CoordinateFormat = CoordinateFormat.Polar; } else { if (string.Compare(value, "F", StringComparison.OrdinalIgnoreCase) == 0) { m_phaseDesignation = char.MinValue; SignalKind = SignalKind.Frequency; CoordinateFormat = CoordinateFormat.Polar; } else if (string.Compare(value, "df", StringComparison.OrdinalIgnoreCase) == 0) { m_phaseDesignation = char.MinValue; SignalKind = SignalKind.DfDt; CoordinateFormat = CoordinateFormat.Polar; } else if (value.Length > 1) { PhaseDesignation = value[0].ToString(); char component = char.ToLowerInvariant(value[1]); switch (component) { case 'r': SignalKind = SignalKind.Magnitude; CoordinateFormat = CoordinateFormat.Rectangular; break; case 'i': SignalKind = SignalKind.Angle; CoordinateFormat = CoordinateFormat.Rectangular; break; case 'm': SignalKind = SignalKind.Magnitude; CoordinateFormat = CoordinateFormat.Polar; break; case 'a': SignalKind = SignalKind.Angle; CoordinateFormat = CoordinateFormat.Polar; break; } } else { PhaseDesignation = value[0].ToString(); SignalKind = SignalKind.Analog; CoordinateFormat = CoordinateFormat.Polar; } } } } /// /// Gets or sets phase designation of this . /// /// Value is not a valid phase designation. public string PhaseDesignation { get => m_phaseDesignation.ToString().RemoveNull(); set { value = value.Trim(); if (string.IsNullOrEmpty(value)) { m_phaseDesignation = char.MinValue; } else { char phaseDesignation = char.ToUpper(value[0]); switch (phaseDesignation) { case 'A': case 'R': case '1': m_phaseDesignation = 'A'; break; case 'B': case 'S': case '2': m_phaseDesignation = 'B'; break; case 'C': case 'T': case '3': m_phaseDesignation = 'C'; break; case 'P': case '+': m_phaseDesignation = 'P'; break; case 'N': case '-': m_phaseDesignation = '-'; break; case 'Z': case '0': m_phaseDesignation = '0'; break; default: m_phaseDesignation = char.MinValue; break; } } } } /// /// Gets or sets phasor type of this , if applicable. /// public PhasorType PhasorType { get; set; } /// /// Gets or sets nominal frequency of this . /// public double NominalFrequency { get => m_nominalFrequency; set { m_nominalFrequency = value; if (m_signalKind == SignalKind.Frequency) Adder = (double)m_nominalFrequency; } } /// /// Gets or sets signal kind of this . /// /// Value is not a valid analog signal kind. public SignalKind SignalKind { get => m_signalKind; set { if (s_validAnalogSignalKinds.BinarySearch(value) < 0) throw new ArgumentException(value + " is not a valid analog signal kind."); m_signalKind = value; if (m_targetFloatingPoint) { switch (m_signalKind) { case SignalKind.Angle: Angle minAngle = new Angle(-Math.PI); Angle maxAngle = new Angle(Math.PI); MinValue = minAngle.ConvertTo(AngleUnit); MaxValue = maxAngle.ConvertTo(AngleUnit); break; case SignalKind.Frequency: MinValue = m_nominalFrequency - 4.0D; MaxValue = m_nominalFrequency + 4.0D; break; } return; } switch (m_signalKind) { case SignalKind.Angle: Multiplier = DefaultPhaseAngleMultiplier; Adder = 0.0D; break; case SignalKind.Magnitude: if (PhasorType == PhasorType.Current) Multiplier = DefaultCurrentMagnitudeMultiplier; else Multiplier = DefaultVoltageMagnitudeMultiplier; Adder = 0.0D; break; case SignalKind.Frequency: Multiplier = DefaultFrequencyMultiplier; Adder = m_nominalFrequency; break; case SignalKind.DfDt: Multiplier = DefaultDfDtMultiplier; Adder = 0.0D; break; case SignalKind.Status: case SignalKind.Digital: Multiplier = 1.0D; Adder = 0.0D; break; default: Multiplier = DefaultAnalogMultipler; Adder = 0.0D; break; } } } /// /// Gets or sets coordinate format of this . /// public CoordinateFormat CoordinateFormat { get; set; } /// /// Gets or sets circuit component of this . /// public string CircuitComponent { get => m_circuitComponent; set { if (!string.IsNullOrWhiteSpace(value)) m_circuitComponent = value.Trim(); else m_circuitComponent = ""; if (m_circuitComponent.Length > 64) m_circuitComponent = m_circuitComponent.Substring(0, 64); } } /// /// Gets or sets units of this . /// public string Units { get => m_units; set { m_units = string.IsNullOrWhiteSpace(value) ? "" : value.Trim(); if (m_units.Length > 32) m_units = m_units.Substring(0, 32); m_angleUnit = null; } } /// /// Gets or sets value multiplier of this . /// public double Multiplier { get; set; } /// /// Gets or sets adder of this . /// public double Adder { get; set; } /// /// Gets or sets time skew between channels of this . /// public double Skew { get; set; } /// /// Gets or sets minimum unscaled value of this . /// public double MinValue { get; set; } /// /// Gets or sets maximum unscaled value of this . /// public double MaxValue { get; set; } /// /// Gets or sets the channel voltage or current transformer ratio primary factor of this . /// public double PrimaryRatio { get; set; } /// /// Gets or sets the channel voltage or current transformer ratio secondary factor of this . /// public double SecondaryRatio { get; set; } /// /// Gets or sets the primary or secondary data scaling identifier of this . /// public char ScalingIdentifier { get => m_scalingIdentifier; set { value = char.ToUpper(value); if (value != 'P' && value != 'S') throw new ArgumentException(value + " is not a valid primary or secondary data scaling identifier - must be either \'P\' or \'S\'."); m_scalingIdentifier = value; } } /// /// Gets derived from , if applicable to channel type. /// public AngleUnit AngleUnit => m_angleUnit ?? (m_angleUnit = GetAngleUnit(Units)).Value; /// /// Gets target schema version. /// [JsonIgnore] public int Version { get; } #endregion #region [ Methods ] /// /// Converts to its string format. /// public override string ToString() { // An,ch_id,ph,ccbm,uu,a,b,skew,min,max List values = new List { Index.ToString(), Name, PhaseID, CircuitComponent, Units, Multiplier.ToString(CultureInfo.InvariantCulture), Adder.ToString(CultureInfo.InvariantCulture), Skew.ToString(CultureInfo.InvariantCulture), MinValue.ToString(CultureInfo.InvariantCulture), MaxValue.ToString(CultureInfo.InvariantCulture) }; // ...,primary,secondary,PS if (Version >= 1999) { values.Add(PrimaryRatio.ToString(CultureInfo.InvariantCulture)); values.Add(SecondaryRatio.ToString(CultureInfo.InvariantCulture)); values.Add(ScalingIdentifier.ToString()); } return string.Join(",", values); } #endregion #region [ Static ] // Static Fields private static readonly List s_validAnalogSignalKinds; // Static Constructor static AnalogChannel() { s_validAnalogSignalKinds = new List(new[] { SignalKind.Analog, SignalKind.Angle, SignalKind.Calculation, SignalKind.DfDt, SignalKind.Frequency, SignalKind.Magnitude, SignalKind.Statistic }); s_validAnalogSignalKinds.Sort(); } // Static Methods // Attempt to parse units as an AngleUnit enum value private static AngleUnit GetAngleUnit(string units) { if (!Enum.TryParse(units, true, out AngleUnit angleUnit)) { // Fall back on other common names for angle units if (units.StartsWith("deg", StringComparison.OrdinalIgnoreCase)) angleUnit = AngleUnit.Degrees; else if (units.StartsWith("grad", StringComparison.OrdinalIgnoreCase) || units.StartsWith("gon", StringComparison.OrdinalIgnoreCase)) angleUnit = AngleUnit.Grads; else if (units.StartsWith("arcm", StringComparison.OrdinalIgnoreCase) || units.StartsWith("min", StringComparison.OrdinalIgnoreCase) || units.StartsWith("moa", StringComparison.OrdinalIgnoreCase)) angleUnit = AngleUnit.ArcMinutes; else if (units.StartsWith("arcs", StringComparison.OrdinalIgnoreCase) || units.StartsWith("sec", StringComparison.OrdinalIgnoreCase)) angleUnit = AngleUnit.ArcSeconds; else if (units.StartsWith("ang", StringComparison.OrdinalIgnoreCase) || units.StartsWith("mil", StringComparison.OrdinalIgnoreCase)) angleUnit = AngleUnit.AngularMil; else // rad angleUnit = AngleUnit.Radians; } return angleUnit; } #endregion } }