//****************************************************************************************************** // PowerCalculator.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/16/2012 - J. Ritchie Carroll // Generated original version of source code. // 12/13/2012 - Starlynn Danyelle Gilliam // Modified Header. // //****************************************************************************************************** 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 MW and MVAR using Voltage and Current Magnitude and Angle signals input to the adapter. /// [Description("Power Calculator: Calculates power and reactive power for synchrophasor measurements")] public class PowerCalculator : CalculatedMeasurementBase { #region [ Members ] // Constants private const double SqrtOf3 = 1.7320508075688772935274463415059D; // Fields private MeasurementKey m_voltageAngle; private MeasurementKey m_voltageMagnitude; private MeasurementKey m_currentAngle; private MeasurementKey m_currentMagnitude; private bool m_trackRecentValues; private int m_sampleSize; private List m_powerSample; private List m_reactivePowerSample; // Important: Make sure output definition defines points in the following order private enum Output { Power, ReactivePower } #endregion #region [ Properties ] /// /// Gets or sets flag that determines if the last few values should be monitored. /// [ConnectionStringParameter, Description("Flag that determines if the last few values should be monitored."), DefaultValue(true)] public bool TrackRecentValues { get { return m_trackRecentValues; } set { m_trackRecentValues = value; } } /// /// Gets or sets the sample size of the data to be monitored. /// [ConnectionStringParameter, Description("Define the sample size of the data to be monitored."), DefaultValue(5)] public int SampleSize { get { return m_sampleSize; } set { m_sampleSize = value; } } /// /// Gets the flag indicating if this adapter supports temporal processing. /// public override bool SupportsTemporalProcessing { get { return true; } } /// /// Returns the detailed status of the . /// public override string Status { get { const int ValuesToShow = 3; StringBuilder status = new StringBuilder(); if (m_trackRecentValues) { status.Append(" Last power values: "); lock (m_powerSample) { // Display last several values if (m_powerSample.Count > ValuesToShow) status.Append(m_powerSample.GetRange(m_powerSample.Count - ValuesToShow - 1, ValuesToShow).Select(v => v.ToString("0.00MW")).ToDelimitedString(", ")); else status.Append("Not enough values calculated yet..."); } status.AppendLine(); status.Append("Last reactive power values: "); lock (m_reactivePowerSample) { // Display last several values if (m_reactivePowerSample.Count > ValuesToShow) status.Append(m_reactivePowerSample.GetRange(m_reactivePowerSample.Count - ValuesToShow - 1, ValuesToShow).Select(v => v.ToString("0.00MVAR")).ToDelimitedString(", ")); else status.Append("Not enough values calculated yet..."); } status.AppendLine(); } status.Append(base.Status); return status.ToString(); } } #endregion #region [ Methods ] /// /// Initializes the . /// public override void Initialize() { base.Initialize(); Dictionary settings = Settings; string setting; // Load parameters if (settings.TryGetValue("trackRecentValues", out setting)) m_trackRecentValues = setting.ParseBoolean(); else m_trackRecentValues = true; if (settings.TryGetValue("sampleSize", out setting)) // Data sample size to monitor, in seconds m_sampleSize = int.Parse(setting); else m_sampleSize = 5; // Load needed phase angle and magnitude measurement keys from defined InputMeasurementKeys m_voltageAngle = InputMeasurementKeys.Where((key, index) => InputMeasurementKeyTypes[index] == SignalType.VPHA).FirstOrDefault(); m_voltageMagnitude = InputMeasurementKeys.Where((key, index) => InputMeasurementKeyTypes[index] == SignalType.VPHM).FirstOrDefault(); m_currentAngle = InputMeasurementKeys.Where((key, index) => InputMeasurementKeyTypes[index] == SignalType.IPHA).FirstOrDefault(); m_currentMagnitude = InputMeasurementKeys.Where((key, index) => InputMeasurementKeyTypes[index] == SignalType.IPHM).FirstOrDefault(); if ((object)m_voltageAngle == null || m_voltageAngle.ID == 0) throw new InvalidOperationException("No voltage angle input was defined - one voltage angle input measurement is required for the power calculator."); if ((object)m_voltageMagnitude == null || m_voltageMagnitude.ID == 0) throw new InvalidOperationException("No voltage magnitude input was defined - one voltage magnitude input measurement is required for the power calculator."); if ((object)m_currentAngle == null || m_currentAngle.ID == 0) throw new InvalidOperationException("No current angle input was defined - one current angle input measurement is required for the power calculator."); if ((object)m_currentMagnitude == null || m_currentMagnitude.ID == 0) throw new InvalidOperationException("No current magnitude input measurement was defined - one current magnitude input measurement is required for the power calculator."); // Make sure only these four phasor measurements are used as input (any others will be ignored) InputMeasurementKeys = new[] { m_voltageAngle, m_voltageMagnitude, m_currentAngle, m_currentMagnitude }; // Validate output measurements if (OutputMeasurements.Length < Enum.GetValues(typeof(Output)).Length) throw new InvalidOperationException("Not enough output measurements were specified for the power calculator, expecting measurements for the \"Calculated Power\" and the \"Calculated Reactive Power\" - in this order."); if (m_trackRecentValues) { m_powerSample = new List(); m_reactivePowerSample = new List(); } // Assign a default adapter name to be used if power calculator is loaded as part of automated collection if (string.IsNullOrWhiteSpace(Name)) Name = string.Format("PC!{0}", OutputMeasurements[(int)Output.Power].Key); } /// /// 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) { double power = double.NaN, reactivePower = double.NaN; try { ConcurrentDictionary measurements = frame.Measurements; double voltageMagnitude = 0.0D, voltageAngle = 0.0D, currentMagnitude = 0.0D, currentAngle = 0.0D; IMeasurement measurement; bool allValuesReceived = false; // Get each needed value from this frame if (measurements.TryGetValue(m_voltageMagnitude, out measurement) && measurement.ValueQualityIsGood()) { // Get voltage magnitude value voltageMagnitude = measurement.AdjustedValue; if (measurements.TryGetValue(m_voltageAngle, out measurement) && measurement.ValueQualityIsGood()) { // Get voltage angle value voltageAngle = measurement.AdjustedValue; if (measurements.TryGetValue(m_currentMagnitude, out measurement) && measurement.ValueQualityIsGood()) { // Get current magnitude value currentMagnitude = measurement.AdjustedValue; if (measurements.TryGetValue(m_currentAngle, out measurement) && measurement.ValueQualityIsGood()) { // Get current angle value currentAngle = measurement.AdjustedValue; allValuesReceived = true; } } } } if (allValuesReceived) { double angleDifference = Math.Abs(voltageAngle - currentAngle); if (angleDifference > 180) angleDifference = 360 - angleDifference; // Convert phase angle difference to radians double impedancePhaseAngle = Angle.FromDegrees(angleDifference); // Calculate line-to-neutral apparent power (S) vector magnitude in Mega volt-amps double apparentPower = SqrtOf3 * (Math.Abs(voltageMagnitude) / SI.Mega) * Math.Abs(currentMagnitude); // Calculate power (P) and reactive power (Q) power = apparentPower * Math.Cos(impedancePhaseAngle); reactivePower = apparentPower * Math.Sin(impedancePhaseAngle); if (m_trackRecentValues) { // Add latest calculated power to data sample lock (m_powerSample) { m_powerSample.Add(power); // Maintain sample size while (m_powerSample.Count > m_sampleSize) m_powerSample.RemoveAt(0); } // Add latest calculated reactive power to data sample lock (m_reactivePowerSample) { m_reactivePowerSample.Add(reactivePower); // Maintain sample size while (m_reactivePowerSample.Count > m_sampleSize) m_reactivePowerSample.RemoveAt(0); } } } } finally { IMeasurement[] outputMeasurements = OutputMeasurements; Measurement powerMeasurement = Measurement.Clone(outputMeasurements[(int)Output.Power], power, frame.Timestamp); Measurement stdevMeasurement = Measurement.Clone(outputMeasurements[(int)Output.ReactivePower], reactivePower, frame.Timestamp); // Provide calculated measurements for external consumption OnNewMeasurements(new IMeasurement[] { powerMeasurement, stdevMeasurement }); } } #endregion } }