From 3405698adffd70cf3e82e9fabb0463c44ef5fbb1 Mon Sep 17 00:00:00 2001 From: Christoph Lackner Date: Tue, 14 Jan 2025 15:22:25 -0500 Subject: [PATCH 1/5] Added Phasor Addition Adapter --- .../PowerCalculations/PhasorAddition.cs | 247 ++++++++++++++++++ .../PowerCalculations.csproj | 1 + 2 files changed, 248 insertions(+) create mode 100644 Source/Libraries/Adapters/PowerCalculations/PhasorAddition.cs diff --git a/Source/Libraries/Adapters/PowerCalculations/PhasorAddition.cs b/Source/Libraries/Adapters/PowerCalculations/PhasorAddition.cs new file mode 100644 index 0000000000..4aa79c9507 --- /dev/null +++ b/Source/Libraries/Adapters/PowerCalculations/PhasorAddition.cs @@ -0,0 +1,247 @@ +//****************************************************************************************************** +// PhasorAddition.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/14/2025 - C. Lackner +// Generated original version of source code. +// +//****************************************************************************************************** + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using GSF; +using GSF.Collections; +using GSF.TimeSeries; +using GSF.TimeSeries.Adapters; +using GSF.Units; +using GSF.Units.EE; +using PhasorProtocolAdapters; + +namespace PowerCalculations; + +/// +/// Calculates sum or difference between two voltages or currents. +/// +[Description("Phasor Addition: Computes the sum or difference between two phasors")] +public class PhasorAddition : CalculatedMeasurementBase +{ + #region [ Members ] + + // Constants + private const double Rad120 = 2.0D * Math.PI / 3.0D; + private const bool DefaultDifference = false; + // Fields + private MeasurementKey[] m_angles; + private MeasurementKey[] m_magnitudes; + private double m_lastMagnitudeResult; + private Angle m_lastAngleResult; + + /// + /// Defines the output measurements for the . + /// + /// + /// One output measurement should be defined for each enumeration value, in order: + /// + public enum Output + { + /// + /// Magnitude measurement. + /// + Magnitude, + /// + /// Angle measurement. + /// + Phase + } + + #endregion + + #region [ Properties ] + + /// + /// Gets or sets flag that determines if the second phasor should be subtracted. + /// + [ConnectionStringParameter] + [Description("Flag that determines if the second phasor should be subtracted.")] + [DefaultValue(DefaultDifference)] + public bool Difference { get; set; } = DefaultDifference; + + /// + /// Gets the flag indicating if this adapter supports temporal processing. + /// + public override bool SupportsTemporalProcessing => true; + + /// + /// Returns the detailed status of the . + /// + public override string Status + { + get + { + StringBuilder status = new(); + + status.Append(base.Status); + + status.AppendLine($" Phasor 1 magnitude: {m_magnitudes[0]}"); + status.AppendLine($" Phasor 2 magnitude: {m_magnitudes[1]}"); + status.AppendLine($" Phase 1 angle: {m_angles[0]}"); + status.AppendLine($" Phase 2 angle: {m_angles[1]}"); + status.AppendLine($" Difference: {Difference}"); + + status.AppendLine(); + status.Append(" Last calculated angle: "); + + status.Append(!double.IsNaN(m_lastAngleResult) ? $"{m_lastAngleResult.ToDegrees():0.00}°" : "No values calculated yet..."); + + status.AppendLine(); + status.Append(" Last calculated magnitude: "); + + status.Append(!double.IsNaN(m_lastMagnitudeResult) ? $"{m_lastMagnitudeResult:0.00}" : + "No values calculated yet..."); + + status.AppendLine(); + status.AppendLine(); + + return status.ToString(); + } + } + + #endregion + + #region [ Methods ] + + /// + /// Initializes the . + /// + public override void Initialize() + { + base.Initialize(); + + Dictionary settings = Settings; + + // Load parameters + if (settings.TryGetValue(nameof(Difference), out string setting)) + Difference = setting.ParseBoolean(); + + // Load needed phase angle measurement keys from defined InputMeasurementKeys + m_angles = InputMeasurementKeys.Where((_, index) => InputMeasurementKeyTypes[index] == SignalType.VPHA || InputMeasurementKeyTypes[index] == SignalType.IPHA).ToArray(); + + if (m_angles.Length != 2) + { + throw new InvalidOperationException("Exactly 2 angle input measurements are required."); + } + + // Load needed phase magnitude measurement keys from defined InputMeasurementKeys + m_magnitudes = InputMeasurementKeys.Where((_, index) => InputMeasurementKeyTypes[index] == SignalType.VPHM || InputMeasurementKeyTypes[index] == SignalType.IPHM).ToArray(); + + if (m_magnitudes.Length != 2) + { + throw new InvalidOperationException("Exactly 2 magnitude input measurements are required."); + } + + + // Make sure only these phasor measurements are used as input + InputMeasurementKeys = m_angles.Concat(m_magnitudes).ToArray(); + + // Validate output measurements + if (OutputMeasurements.Length < 2) + throw new InvalidOperationException("Not enough output measurements were specified for the phasor addition, expecting measurements for the \"Magnitude\", and \"Angle\" - in this order."); + + m_lastMagnitudeResult = double.NaN; + m_lastAngleResult = double.NaN; + + } + + /// + /// Publish frame of time-aligned collection of measurement values that arrived within the defined lag time. + /// + /// Frame of measurements with the same timestamp that arrived within lag time that are ready for processing. + /// Index of frame within a second ranging from zero to frames per second - 1. + protected override void PublishFrame(IFrame frame, int index) + { + ComplexNumber result = nanSeq; + + try + { + ConcurrentDictionary measurements = frame.Measurements; + double m1 = 0.0D, a1 = 0.0D, m2 = 0.0D, a2 = 0.0D; + bool allInputsReceived = false; + + // Get all needed measurement values from this frame + if (measurements.TryGetValue(m_magnitudes[0], out IMeasurement measurement)) + { + // Get first magnitude value + m1 = measurement.AdjustedValue; + + if (measurements.TryGetValue(m_angles[0], out measurement)) + { + // Get first angle value + a1 = measurement.AdjustedValue; + + if (measurements.TryGetValue(m_magnitudes[1], out measurement)) + { + // Get second magnitude value + m2 = measurement.AdjustedValue; + + if (measurements.TryGetValue(m_angles[1], out measurement)) + { + // Get second angle value + a2 = measurement.AdjustedValue; + } + } + } + } + + if (!allInputsReceived) + return; + + ComplexNumber phasor1 = new(Angle.FromDegrees(a1), m1); + ComplexNumber phasor2 = new(Angle.FromDegrees(a2), m2); + + if (Difference) + result = phasor1 - phasor2; + else + result = phasor1 + phasor2; + } + finally + { + IMeasurement[] outputMeasurements = OutputMeasurements; + + // Provide calculated measurements for external consumption + OnNewMeasurements(new IMeasurement[] + { + Measurement.Clone(outputMeasurements[0], result.Magnitude, frame.Timestamp), + Measurement.Clone(outputMeasurements[1], result.Angle.ToDegrees(), frame.Timestamp) + }); + } + } + + #endregion + + #region [ Static ] + + // Static Fields + + // a = e^((2/3) * pi * i) + private static readonly ComplexNumber nanSeq = new(double.NaN, double.NaN); + + #endregion +} \ No newline at end of file diff --git a/Source/Libraries/Adapters/PowerCalculations/PowerCalculations.csproj b/Source/Libraries/Adapters/PowerCalculations/PowerCalculations.csproj index faea7b5ab7..fb86af1069 100755 --- a/Source/Libraries/Adapters/PowerCalculations/PowerCalculations.csproj +++ b/Source/Libraries/Adapters/PowerCalculations/PowerCalculations.csproj @@ -71,6 +71,7 @@ + From 69aeb08bb8898b01fcbf7697327187b746cd79bd Mon Sep 17 00:00:00 2001 From: Christoph Lackner <52460212+clackner-gpa@users.noreply.github.com> Date: Tue, 21 Jan 2025 11:15:47 -0500 Subject: [PATCH 2/5] Update Source/Libraries/Adapters/PowerCalculations/PhasorAddition.cs Co-authored-by: Stephen C. Wills --- .../PowerCalculations/PhasorAddition.cs | 48 ++++++++----------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/Source/Libraries/Adapters/PowerCalculations/PhasorAddition.cs b/Source/Libraries/Adapters/PowerCalculations/PhasorAddition.cs index 4aa79c9507..41f985b088 100644 --- a/Source/Libraries/Adapters/PowerCalculations/PhasorAddition.cs +++ b/Source/Libraries/Adapters/PowerCalculations/PhasorAddition.cs @@ -183,36 +183,30 @@ protected override void PublishFrame(IFrame frame, int index) { ConcurrentDictionary measurements = frame.Measurements; double m1 = 0.0D, a1 = 0.0D, m2 = 0.0D, a2 = 0.0D; - bool allInputsReceived = false; + // Get first magnitude value + if (!measurements.TryGetValue(m_magnitudes[0], out IMeasurement measurement)) + return; - // Get all needed measurement values from this frame - if (measurements.TryGetValue(m_magnitudes[0], out IMeasurement measurement)) - { - // Get first magnitude value - m1 = measurement.AdjustedValue; - - if (measurements.TryGetValue(m_angles[0], out measurement)) - { - // Get first angle value - a1 = measurement.AdjustedValue; - - if (measurements.TryGetValue(m_magnitudes[1], out measurement)) - { - // Get second magnitude value - m2 = measurement.AdjustedValue; - - if (measurements.TryGetValue(m_angles[1], out measurement)) - { - // Get second angle value - a2 = measurement.AdjustedValue; - } - } - } - } - - if (!allInputsReceived) + m1 = measurement.AdjustedValue; + + // Get first angle value + if (!measurements.TryGetValue(m_angles[0], out measurement)) + return; + + a1 = measurement.AdjustedValue; + + // Get second magnitude value + if (!measurements.TryGetValue(m_magnitudes[1], out measurement)) return; + m2 = measurement.AdjustedValue; + + // Get second angle value + if (!measurements.TryGetValue(m_angles[1], out measurement)) + return; + + a2 = measurement.AdjustedValue; + ComplexNumber phasor1 = new(Angle.FromDegrees(a1), m1); ComplexNumber phasor2 = new(Angle.FromDegrees(a2), m2); From 72e4719a4ad833da9a6ec83478923e3d229c26e2 Mon Sep 17 00:00:00 2001 From: Christoph Lackner <52460212+clackner-gpa@users.noreply.github.com> Date: Tue, 21 Jan 2025 11:17:54 -0500 Subject: [PATCH 3/5] Removed unneeded commented code --- Source/Libraries/Adapters/PowerCalculations/PhasorAddition.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Source/Libraries/Adapters/PowerCalculations/PhasorAddition.cs b/Source/Libraries/Adapters/PowerCalculations/PhasorAddition.cs index 41f985b088..e5d092c0dc 100644 --- a/Source/Libraries/Adapters/PowerCalculations/PhasorAddition.cs +++ b/Source/Libraries/Adapters/PowerCalculations/PhasorAddition.cs @@ -232,9 +232,6 @@ protected override void PublishFrame(IFrame frame, int index) #region [ Static ] - // Static Fields - - // a = e^((2/3) * pi * i) private static readonly ComplexNumber nanSeq = new(double.NaN, double.NaN); #endregion From e743c0547287302e9c50a6c5f2e88508dd4839d4 Mon Sep 17 00:00:00 2001 From: Christoph Lackner <52460212+clackner-gpa@users.noreply.github.com> Date: Thu, 23 Jan 2025 08:07:48 -0500 Subject: [PATCH 4/5] Added Logic to save last value --- Source/Libraries/Adapters/PowerCalculations/PhasorAddition.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Libraries/Adapters/PowerCalculations/PhasorAddition.cs b/Source/Libraries/Adapters/PowerCalculations/PhasorAddition.cs index e5d092c0dc..f6f30e4fb4 100644 --- a/Source/Libraries/Adapters/PowerCalculations/PhasorAddition.cs +++ b/Source/Libraries/Adapters/PowerCalculations/PhasorAddition.cs @@ -219,6 +219,8 @@ protected override void PublishFrame(IFrame frame, int index) { IMeasurement[] outputMeasurements = OutputMeasurements; + m_lastAngleResult = result.Angle.ToDegrees(); + m_lastMagnitudeResult = result.Magnitude; // Provide calculated measurements for external consumption OnNewMeasurements(new IMeasurement[] { From adc8c9b25ff2ec0bffe55de21032a5fbbe919f73 Mon Sep 17 00:00:00 2001 From: "Stephen C. Wills" Date: Thu, 23 Jan 2025 09:50:35 -0500 Subject: [PATCH 5/5] Update Source/Libraries/Adapters/PowerCalculations/PhasorAddition.cs --- Source/Libraries/Adapters/PowerCalculations/PhasorAddition.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Libraries/Adapters/PowerCalculations/PhasorAddition.cs b/Source/Libraries/Adapters/PowerCalculations/PhasorAddition.cs index f6f30e4fb4..eb5ba47fa1 100644 --- a/Source/Libraries/Adapters/PowerCalculations/PhasorAddition.cs +++ b/Source/Libraries/Adapters/PowerCalculations/PhasorAddition.cs @@ -129,7 +129,7 @@ public override string Status #region [ Methods ] /// - /// Initializes the . + /// Initializes the . /// public override void Initialize() {