471 lines · 26.7 KB · C#
RawDownload
//******************************************************************************************************//  PowerCalculationConfigurationValidation.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://  ----------------------------------------------------------------------------------------------------//  11/2/2015 - Ryan McCoy//       Generated original version of source code.////****************************************************************************************************** using System;using System.Collections.Generic;using System.Data;using System.Diagnostics.CodeAnalysis;using GSF.Configuration;using GSF.Data;using PhasorProtocolAdapters; namespace PowerCalculations.PowerMultiCalculator{    #region [ Static ]     /// <summary>    /// Implements validations for power calculations in the power calculation adapter    /// </summary>    public static class PowerCalculationConfigurationValidation    {        /// <summary>        /// Validates that data operation and adapter instance exist within database.        /// </summary>        public static void ValidateDatabaseDefinitions()        {            using (AdoDataConnection database = new AdoDataConnection("systemSettings"))            {                if (!DataOperationExists(database))                    CreateDataOperation(database);                 if (!AdapterInstanceExists(database))                    CreateAdapterInstance(database);            }        }         /// <summary>        /// Returns true if a data operation exists to run this class. Returns false otherwise.        /// </summary>        /// <param name="database">Database connection to use for checking the data operation</param>        /// <returns>True or false indicating whether the operation exists</returns>        private static bool DataOperationExists(AdoDataConnection database)        {            return Convert.ToInt32(database.ExecuteScalar($"SELECT COUNT(*) FROM DataOperation WHERE TypeName='{typeof(PowerCalculationConfigurationValidation).FullName}' AND MethodName='ValidatePowerCalculationConfigurations'")) > 0;        }         /// <summary>        /// Creates a data operation to run the validations in this class.        /// </summary>        /// <param name="database">Database connection to use for creating the data operation</param>        private static void CreateDataOperation(AdoDataConnection database)        {            database.ExecuteNonQuery($"INSERT INTO DataOperation(Description, AssemblyName, TypeName, MethodName, Enabled) VALUES ('Power Calculation Validations', 'PowerCalculations.dll', '{typeof(PowerCalculationConfigurationValidation).FullName}', 'ValidatePowerCalculationConfigurations', 1)");        }         /// <summary>        /// Returns true if a data operation exists to run this class. Returns false otherwise.        /// </summary>        /// <param name="database">Database connection to use for checking the data operation</param>        /// <returns>True or false indicating whether the operation exists</returns>        private static bool AdapterInstanceExists(AdoDataConnection database)        {            return Convert.ToInt32(database.ExecuteScalar($"SELECT COUNT(*) FROM CustomActionAdapter WHERE TypeName='{typeof(PowerMultiCalculatorAdapter).FullName}'")) > 0;        }         /// <summary>        /// Creates a data operation to run the validations in this class.        /// </summary>        /// <param name="database">Database connection to use for creating the data operation</param>        private static void CreateAdapterInstance(AdoDataConnection database)        {            database.ExecuteNonQuery($"INSERT INTO CustomActionAdapter(NodeID, AdapterName, AssemblyName, TypeName, ConnectionString, Enabled) VALUES ('{ConfigurationFile.Current.Settings["systemSettings"]["NodeID"].ValueAs<Guid>()}', 'PHASOR!POWERCALC', 'PowerCalculations.dll', '{typeof(PowerMultiCalculatorAdapter).FullName}', 'FramesPerSecond=30; LagTime=5.0; LeadTime=3.0', 1)");        }         /// <summary>        /// Data operation to validate power calculation configuration. This method checks that input measurements and non-null output measurements exist, are enabled, and have the correct signal type.        /// If null output measurements are found in the configuration, this method will create the output measurements and update the configuration.        /// </summary>        /// <param name="database">Database connection for configuration information</param>        /// <param name="nodeIDQueryString">Node ID for database queries</param>        /// <param name="trackingVersion">Not used</param>        /// <param name="arguments">Not used</param>        /// <param name="statusMessage">Delegate for method to communicate status updates</param>        /// <param name="processException">Exception handling delegate</param>        public static void ValidatePowerCalculationConfigurations(AdoDataConnection database, string nodeIDQueryString, ulong trackingVersion, string arguments, Action<string> statusMessage, Action<Exception> processException)        {            try            {                CheckInputMeasurementsExist(database, nodeIDQueryString, statusMessage);                CheckInputMeasurementsAreEnabled(database, nodeIDQueryString, statusMessage);                CheckInputMeasurementsSignalType(database, nodeIDQueryString, statusMessage);                CheckOutputMeasurementsExist(database, nodeIDQueryString, statusMessage);                CheckOutputMeasurementsAreEnabled(database, nodeIDQueryString, statusMessage);                CheckOutputMeasurementsSignalType(database, nodeIDQueryString, statusMessage);                CreateOutputMeasurementsWhereNull(database, nodeIDQueryString, statusMessage);            }            catch (Exception e)            {                processException(e);            }        }           /// <summary>        /// Checks power calculation configuration for null output measurements. When found, a new measurement is created, and the configuration is updated to use the new measurement.        /// </summary>        /// <param name="database">Configuration database connection</param>        /// <param name="nodeIDQueryString">Node ID formatted for query</param>        /// <param name="statusMessage">Delegate for communicating status updates</param>        [SuppressMessage("Microsoft.Security", "CA2100:Review SQL queries for security vulnerabilities", Justification = "Code has been evaluated, SQL text used by function is not based on user input.")]        private static void CreateOutputMeasurementsWhereNull(AdoDataConnection database, string nodeIDQueryString, Action<string> statusMessage)        {            statusMessage("Checking for calculations with null output measurements...");             string query =                $"SELECT " +                $"    pc.ID, " +                $"    pc.CircuitDescription, " +                $"    pc.ActivePowerOutputSignalID, " +                $"    pc.ApparentPowerOutputSignalID, " +                $"    pc.ReactivePowerOutputSignalID, " +                $"    v.Acronym AS VendorAcronym, " +                $"    d.Acronym AS DeviceAcronym, " +                $"    c.Acronym AS CompanyAcronym, " +                $"    d.id AS DeviceID, " +                $"    vm.HistorianID AS HistorianID, " +                $"    p.Label AS CurrentLabel " +                $"FROM " +                $"    PowerCalculation pc JOIN " +                $"    Measurement vm ON vm.SignalID = pc.VoltageAngleSignalID JOIN " +                $"    Measurement im ON im.SignalID = pc.CurrentAngleSignalID LEFT OUTER JOIN " +                $"    Phasor p ON im.DeviceID = p.DeviceID AND im.PhasorSourceIndex = p.SourceIndex LEFT OUTER JOIN " +                $"    Device d ON vm.DeviceID = d.ID LEFT OUTER JOIN " +                $"    VendorDevice vd ON vd.ID = d.VendorDeviceID LEFT OUTER JOIN " +                $"    Vendor v ON vd.VendorID = v.ID LEFT OUTER JOIN " +                $"    Company c ON d.CompanyID = c.ID " +                $"WHERE " +                $"    pc.Enabled <> 0 AND " +                $"    pc.NodeID = {nodeIDQueryString} AND " +                $"    ( " +                $"        pc.ActivePowerOutputSignalID IS NULL OR " +                $"        pc.ReactivePowerOutputSignalID IS NULL OR " +                $"        pc.ApparentPowerOutputSignalID IS NULL " +                $"    )";             Dictionary<int, PowerMeasurement> activePowerUpdates = new Dictionary<int, PowerMeasurement>();            Dictionary<int, PowerMeasurement> reactivePowerUpdates = new Dictionary<int, PowerMeasurement>();            Dictionary<int, PowerMeasurement> apparentPowerUpdates = new Dictionary<int, PowerMeasurement>();             using (IDbCommand cmd = database.Connection.CreateCommand())            {                cmd.CommandText = query;                using (IDataReader rdr = cmd.ExecuteReader())                {                    while (rdr.Read())                    {                        int powerCalculationID = rdr.GetInt32(0);                        string companyAcronym = rdr.IsDBNull(7) ? "" : rdr.GetString(7);                        string vendorAcronym = rdr.IsDBNull(5) ? "" : rdr.GetString(5);                        string signalTypeAcronym = "CALC";                        string circuitDescription = rdr.IsDBNull(1) ? "" : rdr.GetString(1);                        int deviceID = rdr.GetInt32(8);                        int? historianID = rdr.IsDBNull(9) ? null : (int?)rdr.GetInt32(9);                         // Remove any settings defined in circuit description                        int semicolonIndex = circuitDescription.IndexOf(';');                         if (semicolonIndex > -1)                            circuitDescription = circuitDescription.Substring(0, semicolonIndex);                         if (rdr.IsDBNull(2)) // Real - MW                        {                            // create active power output measurement                            PowerMeasurement measurement = CreateMeasurement(companyAcronym, circuitDescription + "-MW", vendorAcronym, signalTypeAcronym, circuitDescription, deviceID, historianID, "Active Power Calculation");                            activePowerUpdates.Add(powerCalculationID, measurement);                        }                         if (rdr.IsDBNull(3)) // Apparent - MVA                        {                            // create apparent power output measurement                            PowerMeasurement measurement = CreateMeasurement(companyAcronym, circuitDescription + "-MVA", vendorAcronym, signalTypeAcronym, circuitDescription, deviceID, historianID, "Apparent Power Calculation");                            apparentPowerUpdates.Add(powerCalculationID, measurement);                        }                         if (rdr.IsDBNull(4)) // Reactive - MVAR                        {                            //create reactive power output measurement                            PowerMeasurement measurement = CreateMeasurement(companyAcronym, circuitDescription + "-MVAR", vendorAcronym, signalTypeAcronym, circuitDescription, deviceID, historianID, "Reactive Power Calculation");                            reactivePowerUpdates.Add(powerCalculationID, measurement);                        }                    }                }            }             int newMeasurementsCount = activePowerUpdates.Count + reactivePowerUpdates.Count + apparentPowerUpdates.Count;             if (newMeasurementsCount > 0)            {                MeasurementRepository repo = new MeasurementRepository();                 statusMessage($"Creating {newMeasurementsCount} new output measurements for power calculation...");                 foreach (KeyValuePair<int, PowerMeasurement> update in activePowerUpdates)                {                    repo.Save(database, update.Value);                    UpdatePowerCalculation(database, update.Key, activePowerOutputSignalID: update.Value.SignalID);                }                 statusMessage("Successfully created new active power calculations.");                 foreach (KeyValuePair<int, PowerMeasurement> update in reactivePowerUpdates)                {                    repo.Save(database, update.Value);                    UpdatePowerCalculation(database, update.Key, reactivePowerOutputSignalID: update.Value.SignalID);                }                 statusMessage("Successfully created new reactive power calculations.");                 foreach (KeyValuePair<int, PowerMeasurement> update in apparentPowerUpdates)                {                    repo.Save(database, update.Value);                    UpdatePowerCalculation(database, update.Key, apparentPowerOutputSignalID: update.Value.SignalID);                }                 statusMessage("Successfully created new apparent power calculations.");                 statusMessage("Completed creation of new measurements for null output measurements on power calculations.");            }        }         /// <summary>        /// Creates a new measurement object for power calculation output measurements        /// </summary>        private static PowerMeasurement CreateMeasurement(string companyAcronym, string deviceAcronym, string vendorAcronym, string signalTypeAcronym, string circuitDescription, int deviceID, int? historianID, string descriptionSuffix)        {            PowerMeasurement measurement = new PowerMeasurement            {                PointTag = CommonPhasorServices.CreatePointTag(companyAcronym, deviceAcronym, vendorAcronym, signalTypeAcronym),                Adder = 0,                Multiplier = 1,                Description = $"{circuitDescription} {descriptionSuffix}",                DeviceID = deviceID,                HistorianID = historianID,                SignalTypeID = 10,                Enabled = true,                SignalID = Guid.Empty            };             if (measurement.PointTag != null)            {                int beginIndex = measurement.PointTag.IndexOf('_');                measurement.SignalReference = measurement.PointTag.Substring(beginIndex + 1);            }             return measurement;        }         /// <summary>        /// Updated power calculation to use new Signal IDs for output measurement        /// </summary>        /// <param name="database">Configuration database connection</param>        /// <param name="powerCalculationID">ID of the power calculation to be updated</param>        /// <param name="activePowerOutputSignalID">Active power output signal ID, if needed for update</param>        /// <param name="reactivePowerOutputSignalID">Reactive power output signal ID, if needed for update</param>        /// <param name="apparentPowerOutputSignalID">Apparent power output signal ID, if needed for update</param>        [SuppressMessage("Microsoft.Security", "CA2100:Review SQL queries for security vulnerabilities", Justification = "Code has been evaluated, SQL generated by function is based non-text based numeric input.")]        private static void UpdatePowerCalculation(AdoDataConnection database, int powerCalculationID, Guid? activePowerOutputSignalID = null, Guid? reactivePowerOutputSignalID = null, Guid? apparentPowerOutputSignalID = null)        {            if (activePowerOutputSignalID == null && reactivePowerOutputSignalID == null && apparentPowerOutputSignalID == null)                return;             List<string> updates = new List<string>();            if (activePowerOutputSignalID != null)                updates.Add($"ActivePowerOutputSignalID='{activePowerOutputSignalID.Value}'");            if (reactivePowerOutputSignalID != null)                updates.Add($"ReactivePowerOutputSignalID='{reactivePowerOutputSignalID.Value}'");            if (apparentPowerOutputSignalID != null)                updates.Add($"ApparentPowerOutputSignalID='{apparentPowerOutputSignalID.Value}'");             string query = "UPDATE PowerCalculation SET ";            query += string.Join(", ", updates) + " ";            query += $"WHERE ID={powerCalculationID} ";             using (IDbCommand cmd = database.Connection.CreateCommand())            {                cmd.CommandText = query;                cmd.ExecuteNonQuery();            }        }         /// <summary>        /// Verifies that output measurements are set to calculated signal type. Calculations are disabled if output measurements are not configured correctly.        /// </summary>        /// <param name="database">Configuration database connection</param>        /// <param name="nodeIDQueryString">Node ID formatted for query</param>        /// <param name="statusMessage">Delegate to communicate status updates</param>        private static void CheckOutputMeasurementsSignalType(AdoDataConnection database, string nodeIDQueryString, Action<string> statusMessage)        {            statusMessage("Validating if non-null output measurements have correct signal type...");             string query =                $"UPDATE PowerCalculation " +                $"SET Enabled = 0 " +                $"WHERE " +                $"    Enabled = 1 AND " +                $"    NodeID = {nodeIDQueryString} AND " +                $"    ( " +                $"        (SELECT Acronym FROM Measurement JOIN SignalType ON Measurement.SignalTypeID = SignalType.ID WHERE SignalID = ActivePowerOutputSignalID) <> 'CALC' OR " +                $"        (SELECT Acronym FROM Measurement JOIN SignalType ON Measurement.SignalTypeID = SignalType.ID WHERE SignalID = ActivePowerOutputSignalID) <> 'CALC' OR " +                $"        (SELECT Acronym FROM Measurement JOIN SignalType ON Measurement.SignalTypeID = SignalType.ID WHERE SignalID = ActivePowerOutputSignalID) <> 'CALC' " +                $"    )";             const string failureMessage = "One or more power calculations are defined with non-null output measurements having the wrong signal type. Power calculations will be disabled.";             RunDatabaseValidation(database, statusMessage, query, failureMessage);        }         /// <summary>        /// Verifies that output measurements are enabled. Power calculations will be disabled where outputs are disabled.        /// </summary>        /// <param name="database"></param>        /// <param name="nodeIDQueryString"></param>        /// <param name="statusMessage"></param>        private static void CheckOutputMeasurementsAreEnabled(AdoDataConnection database, string nodeIDQueryString, Action<string> statusMessage)        {            statusMessage("Validating if non-null output measurements are enabled...");             string query =                $"UPDATE PowerCalculation " +                $"SET Enabled = 0 " +                $"WHERE " +                $"    Enabled = 1 AND " +                $"    NodeID = {nodeIDQueryString} AND " +                $"    (SELECT COUNT(*) FROM Measurement WHERE Enabled = 0 AND SignalID IN (ActivePowerOutputSignalID, ReactivePowerOutputSignalID, ApparentPowerOutputSignalID)) > 0";             const string failureMessage = "One or more power calculations are defined with output measurements that are disabled. Power calculations will be disabled.";             RunDatabaseValidation(database, statusMessage, query, failureMessage);        }         /// <summary>        /// Verifies that output measurements exist in the measurement table. Power calculations are disabled where the configured outputs do not exist.        /// </summary>        /// <param name="database">Configuration database connection</param>        /// <param name="nodeIDQueryString">Node ID formatted for query</param>        /// <param name="statusMessage">Delegate to communicate status updates</param>        private static void CheckOutputMeasurementsExist(AdoDataConnection database, string nodeIDQueryString, Action<string> statusMessage)        {            statusMessage("Validating if non-null output measurements exist...");             string query =                 $"UPDATE PowerCalculation SET Enabled=0 WHERE Enabled=1 AND NodeID={nodeIDQueryString} " +                 "AND ((ActivePowerOutputSignalID IS NOT NULL AND ActivePowerOutputSignalID NOT IN (SELECT SignalID from Measurement)) " +                 "OR (ReactivePowerOutputSignalID IS NOT NULL AND ReactivePowerOutputSignalID NOT IN (SELECT SignalID from Measurement)) " +                 "OR (ApparentPowerOutputSignalID IS NOT NULL AND ApparentPowerOutputSignalID NOT IN (SELECT SignalID from Measurement)))";             const string failureMessage = "One or more power calculations are defined with non-null output measurements that do not exist. Power calculations will be disabled.";             RunDatabaseValidation(database, statusMessage, query, failureMessage);        }         /// <summary>        /// Validates that input measurements have the correct signal type. Power calculations are disabled where inputs have the wrong signal types.        /// </summary>        /// <param name="database">Configuration database connection</param>        /// <param name="nodeIDQueryString">Node ID formatted for database query</param>        /// <param name="statusMessage">Delegate for status updates</param>        private static void CheckInputMeasurementsSignalType(AdoDataConnection database, string nodeIDQueryString, Action<string> statusMessage)        {            statusMessage("Validating if input measurements have correct signal type...");             string query =                $"UPDATE PowerCalculation " +                $"SET Enabled = 0 " +                $"WHERE " +                $"    Enabled = 1 AND " +                $"    NodeID = {nodeIDQueryString} AND " +                $"    ( " +                $"        (SELECT Acronym FROM Measurement JOIN SignalType ON Measurement.SignalTypeID = SignalType.ID WHERE SignalID = VoltageAngleSignalID) <> 'VPHA' OR " +                $"        (SELECT Acronym FROM Measurement JOIN SignalType ON Measurement.SignalTypeID = SignalType.ID WHERE SignalID = VoltageMagSignalID) <> 'VPHM' OR " +                $"        (SELECT Acronym FROM Measurement JOIN SignalType ON Measurement.SignalTypeID = SignalType.ID WHERE SignalID = CurrentAngleSignalID) <> 'IPHA' OR " +                $"        (SELECT Acronym FROM Measurement JOIN SignalType ON Measurement.SignalTypeID = SignalType.ID WHERE SignalID = CurrentMagSignalID) <> 'IPHM' " +                $"    )";             const string failureMessage = "One or more power calculations are defined with input measurements having the wrong signal type. Power calculations will be disabled.";             RunDatabaseValidation(database, statusMessage, query, failureMessage);        }         /// <summary>        /// Verifies that input measurements are enabled. Power calculations are disabled where input measurements are disabled.        /// </summary>        /// <param name="database">Configuration database connection</param>        /// <param name="nodeIDQueryString">Node ID formatted for query</param>        /// <param name="statusMessage">Delegate for communicating status updates</param>        private static void CheckInputMeasurementsAreEnabled(AdoDataConnection database, string nodeIDQueryString, Action<string> statusMessage)        {            statusMessage("Validating if input measurements are enabled...");             string query =                $"UPDATE PowerCalculation " +                $"SET Enabled = 0 " +                $"WHERE " +                $"    Enabled = 1 AND " +                $"    NodeID = {nodeIDQueryString} AND " +                $"    (SELECT COUNT(*) FROM Measurement WHERE Enabled = 0 AND SignalID IN (VoltageAngleSignalID, VoltageMagSignalID, CurrentAngleSignalID, CurrentMagSignalID)) > 0";             const string failureMessage = "One or more power calculations are defined with input measurements that are disabled. Power calculations will be disabled.";             RunDatabaseValidation(database, statusMessage, query, failureMessage);        }         /// <summary>        /// Verifies that input measurements exist. Power calculations are disabled where input measurements do not exist.        /// </summary>        /// <param name="database">Configuration database connection</param>        /// <param name="nodeIDQueryString">Node ID formatted for query</param>        /// <param name="statusMessage">Delegate for communicating status updates</param>        private static void CheckInputMeasurementsExist(AdoDataConnection database, string nodeIDQueryString, Action<string> statusMessage)        {            statusMessage("Validating if input measurements exist...");             string query =                 $"UPDATE PowerCalculation SET Enabled=0 WHERE Enabled=1 AND NodeID={nodeIDQueryString}" +                 "AND (VoltageMagSignalID NOT IN (SELECT SignalID from Measurement) " +                 "OR VoltageAngleSignalID NOT IN (SELECT SignalID from Measurement) " +                 "OR CurrentMagSignalID NOT IN (SELECT SignalID from Measurement) " +                 "OR CurrentAngleSignalID NOT IN (SELECT SignalID from Measurement))";             const string failureMessage = "One or more power calculations are defined with input measurements that do not exist. Power calculations will be disabled.";             RunDatabaseValidation(database, statusMessage, query, failureMessage);        }         [SuppressMessage("Microsoft.Security", "CA2100:Review SQL queries for security vulnerabilities", Justification = "Code has been evaluated, functions calling this method create SQL text that is not based on user input.")]        private static void RunDatabaseValidation(AdoDataConnection database, Action<string> statusMessage, string query, string failureMessage)        {            using (IDbCommand cmd = database.Connection.CreateCommand())            {                cmd.CommandText = query;                 int affectedRows = cmd.ExecuteNonQuery();                 if (affectedRows > 0)                    statusMessage(failureMessage);            }        }         #endregion    }}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t