//******************************************************************************************************// 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{ /// <summary> /// Calculates MW and MVAR using Voltage and Current Magnitude and Angle signals input to the adapter. /// </summary> [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<double> m_powerSample; private List<double> m_reactivePowerSample; // Important: Make sure output definition defines points in the following order private enum Output { Power, ReactivePower } #endregion #region [ Properties ] /// <summary> /// Gets or sets flag that determines if the last few values should be monitored. /// </summary> [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; } } /// <summary> /// Gets or sets the sample size of the data to be monitored. /// </summary> [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; } } /// <summary> /// Gets the flag indicating if this adapter supports temporal processing. /// </summary> public override bool SupportsTemporalProcessing { get { return true; } } /// <summary> /// Returns the detailed status of the <see cref="PowerCalculator"/>. /// </summary> 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 ] /// <summary> /// Initializes the <see cref="PowerCalculator"/>. /// </summary> public override void Initialize() { base.Initialize(); Dictionary<string, string> 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<double>(); m_reactivePowerSample = new List<double>(); } // 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); } /// <summary> /// Publish frame of time-aligned collection of measurement values that arrived within the defined lag time. /// </summary> /// <param name="frame">Frame of measurements with the same timestamp that arrived within lag time that are ready for processing.</param> /// <param name="index">Index of frame within a second ranging from zero to frames per second - 1.</param> protected override void PublishFrame(IFrame frame, int index) { double power = double.NaN, reactivePower = double.NaN; try { ConcurrentDictionary<MeasurementKey, IMeasurement> 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 }}
//******************************************************************************************************
// 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
{
/// <summary>
/// Calculates MW and MVAR using Voltage and Current Magnitude and Angle signals input to the adapter.
/// </summary>
[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<double> m_powerSample;
private List<double> m_reactivePowerSample;
// Important: Make sure output definition defines points in the following order
private enum Output
{
Power,
ReactivePower
}
#endregion
#region [ Properties ]
/// <summary>
/// Gets or sets flag that determines if the last few values should be monitored.
/// </summary>
[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;
}
}
/// <summary>
/// Gets or sets the sample size of the data to be monitored.
/// </summary>
[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;
}
}
/// <summary>
/// Gets the flag indicating if this adapter supports temporal processing.
/// </summary>
public override bool SupportsTemporalProcessing
{
get
{
return true;
}
}
/// <summary>
/// Returns the detailed status of the <see cref="PowerCalculator"/>.
/// </summary>
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 ]
/// <summary>
/// Initializes the <see cref="PowerCalculator"/>.
/// </summary>
public override void Initialize()
{
base.Initialize();
Dictionary<string, string> 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<double>();
m_reactivePowerSample = new List<double>();
}
// 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);
}
/// <summary>
/// Publish frame of time-aligned collection of measurement values that arrived within the defined lag time.
/// </summary>
/// <param name="frame">Frame of measurements with the same timestamp that arrived within lag time that are ready for processing.</param>
/// <param name="index">Index of frame within a second ranging from zero to frames per second - 1.</param>
protected override void PublishFrame(IFrame frame, int index)
{
double power = double.NaN, reactivePower = double.NaN;
try
{
ConcurrentDictionary<MeasurementKey, IMeasurement> 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
}
}