From f8021226c2502d65dfe2d9513f5906a373e13d28 Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Mon, 19 Jan 2026 13:30:00 -0500 Subject: [PATCH 01/58] uppgrade to netcore 9 to perpare to move to gemstone --- src/OpenSEE/OpenSEE.csproj | 247 ++------------------- src/OpenSEE/Properties/launchSettings.json | 12 + src/OpenSEE/packages.config | 8 - 3 files changed, 26 insertions(+), 241 deletions(-) create mode 100644 src/OpenSEE/Properties/launchSettings.json delete mode 100644 src/OpenSEE/packages.config diff --git a/src/OpenSEE/OpenSEE.csproj b/src/OpenSEE/OpenSEE.csproj index a7f4cdcd..5bc5caec 100644 --- a/src/OpenSEE/OpenSEE.csproj +++ b/src/OpenSEE/OpenSEE.csproj @@ -1,53 +1,15 @@ - - - - + - Debug - AnyCPU - - - 2.0 - {845F68F7-4094-4FE6-95E3-1B113BBFAD3F} - {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} - Library - Properties - OpenSEE - OpenSEE - v4.8 - false - true - - - - enabled - enabled - - - - + Exe Latest true - latest - - - true - full - false - bin\ - DEBUG;TRACE - prompt - 4 - - - true - pdbonly - true + CALL cd "$(ProjectDir)" +if $(ConfigurationName) == Debug npm run build +if $(ConfigurationName) == Release npm run buildrelease + + net9.0 bin\ - TRACE - prompt - 4 @@ -55,7 +17,6 @@ - @@ -68,169 +29,38 @@ - - - - - - + + + + - - - - - - - - - - + CSVDownload.ashx - + FaultSpecifics.aspx ASPXCodeBehind - + Global.asax - - - - - - - - - - - - - - - - - - - - - - - - - - - - ..\Dependencies\openXDA\FaultAlgorithms.dll - - - ..\Dependencies\openXDA\FaultData.dll - - - False - ..\Dependencies\GSF\GSF.Core.dll - - - False - ..\Dependencies\GSF\GSF.PQDIF.dll - - - False - ..\Dependencies\GSF\GSF.Security.dll - - - False - ..\Dependencies\GSF\GSF.Web.dll - - - ..\Dependencies\GSF\Ionic.Zlib.dll - ..\Dependencies\NuGet\MathNet.Numerics.5.0.0-alpha02\lib\net48\MathNet.Numerics.dll - - ..\Dependencies\NuGet\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.4.1.0\lib\net472\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll - - - - ..\Dependencies\GSF\Microsoft.Owin.dll - - - ..\Dependencies\NuGet\Microsoft.Owin.Host.SystemWeb.3.1.0\lib\net45\Microsoft.Owin.Host.SystemWeb.dll - - - ..\Dependencies\GSF\Microsoft.Owin.Security.dll - - - False - ..\Dependencies\GSF\Newtonsoft.Json.dll - - - ..\Dependencies\openXDA\openXDA.Model.dll - - - ..\Dependencies\GSF\Owin.dll - - - ..\Dependencies\openXDA\PQDS.dll - - - False - ..\Dependencies\GSF\RazorEngine.dll - - - - - - - - - - - - False - ..\Dependencies\GSF\System.Net.Http.Formatting.dll - - - - - - - - - - - - ..\Dependencies\GSF\System.Web.Http.dll - - - ..\Dependencies\GSF\System.Web.Http.Owin.dll - - - ..\Dependencies\GSF\System.Web.Mvc.dll - - - ..\Dependencies\NuGet\Microsoft.AspNet.Web.Optimization.1.1.3\lib\net40\System.Web.Optimization.dll - - - ..\Dependencies\NuGet\WebGrease.1.5.2\lib\WebGrease.dll - - - @@ -285,53 +115,4 @@ - - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - - - - - - - - - - True - True - 58744 - / - http://localhost:44367/openSEE - False - False - - - False - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - CALL cd "$(ProjectDir)" -if $(ConfigurationName) == Debug npm run build -if $(ConfigurationName) == Release npm run buildrelease - - - \ No newline at end of file diff --git a/src/OpenSEE/Properties/launchSettings.json b/src/OpenSEE/Properties/launchSettings.json new file mode 100644 index 00000000..dcea5e72 --- /dev/null +++ b/src/OpenSEE/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "OpenSEE": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:50951" + } + } +} \ No newline at end of file diff --git a/src/OpenSEE/packages.config b/src/OpenSEE/packages.config deleted file mode 100644 index b951020d..00000000 --- a/src/OpenSEE/packages.config +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file From c43d72108f2170d2dde98bde6ac8afd5809f8043 Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Mon, 19 Jan 2026 14:57:59 -0500 Subject: [PATCH 02/58] update and addition of packages --- src/OpenSEE/OpenSEE.csproj | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/OpenSEE/OpenSEE.csproj b/src/OpenSEE/OpenSEE.csproj index 5bc5caec..1be987b1 100644 --- a/src/OpenSEE/OpenSEE.csproj +++ b/src/OpenSEE/OpenSEE.csproj @@ -29,8 +29,9 @@ if $(ConfigurationName) == Release npm run buildrelease - - + + + From f69b8bde0d392e78a3f013f77d6a834a0fcf656b Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Mon, 19 Jan 2026 14:58:44 -0500 Subject: [PATCH 03/58] bulk change of usings --- src/OpenSEE/CSVDownload.ashx.cs | 8 ++++---- src/OpenSEE/Common.cs | 10 +++++----- src/OpenSEE/Controllers/AnalyticController.cs | 15 +++++---------- src/OpenSEE/Controllers/HomeController.cs | 10 +++++----- src/OpenSEE/Controllers/LoginController.cs | 2 +- src/OpenSEE/Controllers/OpenSEEController.cs | 6 +++--- src/OpenSEE/Controllers/OpenSeeControllerBase.cs | 4 ++-- src/OpenSEE/FaultSpecifics.aspx.cs | 6 +++--- src/OpenSEE/Global.asax.cs | 16 ++++++++-------- src/OpenSEE/Model/AppModel.cs | 14 -------------- src/OpenSEE/Model/D3Series.cs | 8 ++++---- src/OpenSEE/Startup.cs | 8 ++++---- src/OpenSEE/WebExtensions.cs | 4 ++-- 13 files changed, 46 insertions(+), 65 deletions(-) diff --git a/src/OpenSEE/CSVDownload.ashx.cs b/src/OpenSEE/CSVDownload.ashx.cs index 9f4f7d14..59f3d9c7 100644 --- a/src/OpenSEE/CSVDownload.ashx.cs +++ b/src/OpenSEE/CSVDownload.ashx.cs @@ -32,10 +32,10 @@ using System.Threading.Tasks; using System.Web; using FaultData.DataAnalysis; -using GSF.Collections; -using GSF.Data; -using GSF.Data.Model; -using GSF.Threading; +using Gemstone.Collections; +using Gemstone.Data; +using Gemstone.Data.Model; +using Gemstone.Threading; using Newtonsoft.Json; using OpenSEE.Model; using openXDA.Model; diff --git a/src/OpenSEE/Common.cs b/src/OpenSEE/Common.cs index 8742a38c..de376267 100644 --- a/src/OpenSEE/Common.cs +++ b/src/OpenSEE/Common.cs @@ -22,11 +22,11 @@ //****************************************************************************************************** using System.IO; -using GSF; -using GSF.Configuration; -using GSF.IO; -using GSF.Reflection; -using GSF.Web.Security; +using Gemstone; +using Gemstone.Configuration; +using Gemstone.IO; +using Gemstone.Reflection; +using Gemstone.Web.Security; namespace OpenSEE; diff --git a/src/OpenSEE/Controllers/AnalyticController.cs b/src/OpenSEE/Controllers/AnalyticController.cs index b1a9cadf..22842b65 100644 --- a/src/OpenSEE/Controllers/AnalyticController.cs +++ b/src/OpenSEE/Controllers/AnalyticController.cs @@ -29,21 +29,16 @@ using System.Numerics; using System.Runtime.Caching; using System.Threading.Tasks; -using System.Web.Http; -using FaultData.DataAnalysis; -using GSF; -using GSF.Console; -using GSF.Data; -using GSF.Data.Model; -using GSF.NumericalAnalysis; -using GSF.Web; +using Gemstone.Data; +using Gemstone.Web; using MathNet.Numerics.IntegralTransforms; using OpenSEE.Model; -using openXDA.Model; +using Microsoft.AspNetCore.Mvc; +using Gemstone.Data.Model; namespace OpenSEE { - [RoutePrefix("api/Analytic")] + [Route("api/Analytic")] public class AnalyticController : OpenSEEBaseController { #region [ Members ] diff --git a/src/OpenSEE/Controllers/HomeController.cs b/src/OpenSEE/Controllers/HomeController.cs index 4994bd26..89a1ba66 100644 --- a/src/OpenSEE/Controllers/HomeController.cs +++ b/src/OpenSEE/Controllers/HomeController.cs @@ -22,11 +22,11 @@ //****************************************************************************************************** using System; -using System.Web.Mvc; -using GSF.Data; -using GSF.Data.Model; -using GSF.Identity; -using GSF.Web.Model; +using Microsoft.AspNetCore.Mvc; +using Gemstone.Data; +using Gemstone.Data.Model; +using Gemstone.Identity; +using Gemstone.Web.Model; using OpenSEE.Model; using openXDA.Model; diff --git a/src/OpenSEE/Controllers/LoginController.cs b/src/OpenSEE/Controllers/LoginController.cs index 0ddd37aa..525e5717 100644 --- a/src/OpenSEE/Controllers/LoginController.cs +++ b/src/OpenSEE/Controllers/LoginController.cs @@ -24,7 +24,7 @@ using System; using System.Threading; using System.Web.Mvc; -using GSF.Web.Security; +using Gemstone.Web.Security; namespace OpenSEE.Controllers; diff --git a/src/OpenSEE/Controllers/OpenSEEController.cs b/src/OpenSEE/Controllers/OpenSEEController.cs index 926e20eb..118bfcff 100644 --- a/src/OpenSEE/Controllers/OpenSEEController.cs +++ b/src/OpenSEE/Controllers/OpenSEEController.cs @@ -35,9 +35,9 @@ using System.Threading.Tasks; using System.Web.Http; using FaultData.DataAnalysis; -using GSF.Data; -using GSF.Data.Model; -using GSF.Web; +using Gemstone.Data; +using Gemstone.Data.Model; +using Gemstone.Web; using OpenSEE.Model; using openXDA.Model; diff --git a/src/OpenSEE/Controllers/OpenSeeControllerBase.cs b/src/OpenSEE/Controllers/OpenSeeControllerBase.cs index dc1e6cd5..b799b066 100644 --- a/src/OpenSEE/Controllers/OpenSeeControllerBase.cs +++ b/src/OpenSEE/Controllers/OpenSeeControllerBase.cs @@ -28,8 +28,8 @@ using System.Threading.Tasks; using System.Web.Http; using FaultData.DataAnalysis; -using GSF.Data; -using GSF.NumericalAnalysis; +using Gemstone.Data; +using Gemstone.NumericalAnalysis; using OpenSEE.Model; using openXDA.Model; diff --git a/src/OpenSEE/FaultSpecifics.aspx.cs b/src/OpenSEE/FaultSpecifics.aspx.cs index 9e44f373..f20853dc 100644 --- a/src/OpenSEE/FaultSpecifics.aspx.cs +++ b/src/OpenSEE/FaultSpecifics.aspx.cs @@ -3,9 +3,9 @@ using System.Data.SqlClient; using System.Globalization; using System.Linq; -using GSF.Configuration; -using GSF.Data; -using GSF.Data.Model; +using Gemstone.Configuration; +using Gemstone.Data; +using Gemstone.Data.Model; using System.Web.UI; using openXDA.Model; public partial class FaultSpecifics : Page diff --git a/src/OpenSEE/Global.asax.cs b/src/OpenSEE/Global.asax.cs index aae1a995..c1645c8b 100644 --- a/src/OpenSEE/Global.asax.cs +++ b/src/OpenSEE/Global.asax.cs @@ -31,14 +31,14 @@ using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; -using GSF; -using GSF.Configuration; -using GSF.Data; -using GSF.Identity; -using GSF.IO; -using GSF.Security; -using GSF.Web.Embedded; -using GSF.Web.Model; +using Gemstone; +using Gemstone.Configuration; +using Gemstone.Data; +using Gemstone.Identity; +using Gemstone.IO; +using Gemstone.Security; +using Gemstone.Web.Embedded; +using Gemstone.Web.Model; using OpenSEE.Model; namespace OpenSEE diff --git a/src/OpenSEE/Model/AppModel.cs b/src/OpenSEE/Model/AppModel.cs index 73f2fb6d..f3ca2b12 100644 --- a/src/OpenSEE/Model/AppModel.cs +++ b/src/OpenSEE/Model/AppModel.cs @@ -21,20 +21,6 @@ // //****************************************************************************************************** - -using GSF; -using GSF.Data.Model; -using GSF.Web; -using GSF.Web.Model; -using System; -using System.Web; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using Path = System.Web.VirtualPathUtility; -using System.Web.Routing; - namespace OpenSEE.Model { /// diff --git a/src/OpenSEE/Model/D3Series.cs b/src/OpenSEE/Model/D3Series.cs index 2d5b2db0..638c52c4 100644 --- a/src/OpenSEE/Model/D3Series.cs +++ b/src/OpenSEE/Model/D3Series.cs @@ -22,10 +22,10 @@ //****************************************************************************************************** -using GSF; -using GSF.Data.Model; -using GSF.Web; -using GSF.Web.Model; +using Gemstone; +using Gemstone.Data.Model; +using Gemstone.Web; +using Gemstone.Web.Model; using System; using System.Web; using System.Collections.Generic; diff --git a/src/OpenSEE/Startup.cs b/src/OpenSEE/Startup.cs index 60e399c3..2cc07d81 100644 --- a/src/OpenSEE/Startup.cs +++ b/src/OpenSEE/Startup.cs @@ -25,10 +25,10 @@ using System.IO; using System.Reflection; using System.Web.Http; -using GSF.Diagnostics; -using GSF.IO; -using GSF.Web.Security; -using GSF.Web.Shared; +using Gemstone.Diagnostics; +using Gemstone.IO; +using Gemstone.Web.Security; +using Gemstone.Web.Shared; using Microsoft.Owin; using Owin; using static OpenSEE.Common; diff --git a/src/OpenSEE/WebExtensions.cs b/src/OpenSEE/WebExtensions.cs index ae3d8788..ef0af296 100644 --- a/src/OpenSEE/WebExtensions.cs +++ b/src/OpenSEE/WebExtensions.cs @@ -23,8 +23,8 @@ -using GSF.Data.Model; -using GSF.Web.Model; +using Gemstone.Data.Model; +using Gemstone.Web.Model; using System.Collections.Generic; namespace OpenSEE From 9db3e9da6f8f0b61f295cd963c234835a06dee74 Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Mon, 19 Jan 2026 15:45:46 -0500 Subject: [PATCH 04/58] replace connection string with gemstone settings --- src/OpenSEE/CSVDownload.ashx.cs | 23 ++--- src/OpenSEE/Common.cs | 8 +- src/OpenSEE/Controllers/AnalyticController.cs | 87 ++++++++++--------- src/OpenSEE/Controllers/HomeController.cs | 3 +- src/OpenSEE/Controllers/OpenSEEController.cs | 33 +++---- .../Controllers/OpenSeeControllerBase.cs | 13 +-- src/OpenSEE/FaultSpecifics.aspx.cs | 4 +- src/OpenSEE/Global.asax.cs | 2 +- 8 files changed, 89 insertions(+), 84 deletions(-) diff --git a/src/OpenSEE/CSVDownload.ashx.cs b/src/OpenSEE/CSVDownload.ashx.cs index 59f3d9c7..b100edab 100644 --- a/src/OpenSEE/CSVDownload.ashx.cs +++ b/src/OpenSEE/CSVDownload.ashx.cs @@ -33,6 +33,7 @@ using System.Web; using FaultData.DataAnalysis; using Gemstone.Collections; +using Gemstone.Configuration; using Gemstone.Data; using Gemstone.Data.Model; using Gemstone.Threading; @@ -220,11 +221,11 @@ public void ExportToPQDS(Stream returnStream, NameValueCollection requestParamet { int eventID = int.Parse(requestParameters["eventID"]); - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { Event evt = (new TableOperations(connection)).QueryRecordWhere("ID = {0}", eventID); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection("systemSettings"); + meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); // only get Single Voltage and Single Current Data for This.... List data = new List(); @@ -280,7 +281,7 @@ public void ExportToPQDS(Stream returnStream, NameValueCollection requestParamet Asset asset; double systemFrequency; - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { asset = (new TableOperations(connection)).QueryRecordWhere("ID = {0}", evt.AssetID); systemFrequency = connection.ExecuteScalar("SELECT Value FROM Setting WHERE Name = 'SystemFrequency'") ?? 60.0; @@ -397,7 +398,7 @@ private PQDS.DataSeries PQDSSeries(DataSeries data, string label) public void ExportStatsToCSV(Stream returnStream, NameValueCollection requestParameters) { int eventId = int.Parse(requestParameters["eventId"]); - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) using (StreamWriter writer = new StreamWriter(returnStream)) { DataTable dataTable = connection.RetrieveData("SELECT * FROM OpenSEEScalarStatView WHERE EventID = {0}", eventId); @@ -416,7 +417,7 @@ public void ExportCorrelatedSagsToCSV(Stream returnStream, NameValueCollection r { int eventID = int.Parse(requestParameters["eventId"]); - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) using (StreamWriter writer = new StreamWriter(returnStream)) { double timeTolerance = connection.ExecuteScalar("SELECT Value FROM Setting WHERE Name = 'TimeTolerance'"); @@ -454,7 +455,7 @@ public void ExportFFTToCSV(Stream returnStream, NameValueCollection requestParam { int eventId = int.Parse(requestParameters["eventID"]); - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) using (StreamWriter writer = new StreamWriter(returnStream)) { double startTime = requestParameters["startDate"] == null ? 0.0 : double.Parse(requestParameters["startDate"]); @@ -462,7 +463,7 @@ public void ExportFFTToCSV(Stream returnStream, NameValueCollection requestParam Event evt = (new TableOperations(connection)).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection("systemSettings"); + meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); AnalyticController ctrl = new AnalyticController(); @@ -493,7 +494,7 @@ public void ExportFFTToCSV(Stream returnStream, NameValueCollection requestParam public void ExportHarmonicsToCSV(Stream returnStream, NameValueCollection requestParameters) { int eventId = int.Parse(requestParameters["eventId"]); - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) using (StreamWriter writer = new StreamWriter(returnStream)) { DataTable dataTable = connection.RetrieveData(@" @@ -557,11 +558,11 @@ public List BuildDataSeries(NameValueCollection requestParameters) int harmonic = requestParameters["harmonic"] == null ? 1 : int.Parse(requestParameters["harmonic"]); List displayAnalytics = requestParameters["displayAnalytics"] == null ? new List() : requestParameters["displayAnalytics"].Split(',').ToList(); - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { Event evt = (new TableOperations(connection)).QueryRecordWhere("ID = {0}", eventID); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection("systemSettings"); + meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); IEnumerable returnList = new List(); if (displayVolt) @@ -658,7 +659,7 @@ private List QueryAnalyticData(Meter meter, Event evt, string analytic private List QueryVoltageData(Meter meter, Event evt) { bool useLL; - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { useLL = connection.ExecuteScalar("SELECT Value FROM Settings WHERE Name = 'useLLVoltage'") ?? false; } diff --git a/src/OpenSEE/Common.cs b/src/OpenSEE/Common.cs index de376267..b859826f 100644 --- a/src/OpenSEE/Common.cs +++ b/src/OpenSEE/Common.cs @@ -52,16 +52,16 @@ private static string GetApplicationName() => GetSettingValue("SecurityProvider", "ApplicationName", "GSF Authentication"); private static string GetAnonymousResourceExpression() => - GetSettingValue("SystemSettings", "AnonymousResourceExpression", AuthenticationOptions.DefaultAnonymousResourceExpression); + GetSettingValue(Settings.Default, "AnonymousResourceExpression", AuthenticationOptions.DefaultAnonymousResourceExpression); private static bool GetLogEnabled() => - GetSettingValue("SystemSettings", "LogEnabled", AssemblyInfo.ExecutingAssembly.Debuggable.ToString()).ParseBoolean(); + GetSettingValue(Settings.Default, "LogEnabled", AssemblyInfo.ExecutingAssembly.Debuggable.ToString()).ParseBoolean(); private static string GetLogPath() => - GetSettingValue("SystemSettings", "LogPath", string.Format("{0}{1}Logs{1}", FilePath.GetAbsolutePath(""), Path.DirectorySeparatorChar)); + GetSettingValue(Settings.Default, "LogPath", string.Format("{0}{1}Logs{1}", FilePath.GetAbsolutePath(""), Path.DirectorySeparatorChar)); private static int GetMaxLogFiles() => - int.TryParse(GetSettingValue("SystemSettings", "MaxLogFiles", "300"), out int maxLogFiles) ? maxLogFiles : 300; + int.TryParse(GetSettingValue(Settings.Default, "MaxLogFiles", "300"), out int maxLogFiles) ? maxLogFiles : 300; private static string GetSettingValue(string section, string keyName, string defaultValue) { diff --git a/src/OpenSEE/Controllers/AnalyticController.cs b/src/OpenSEE/Controllers/AnalyticController.cs index 22842b65..16f4c1e2 100644 --- a/src/OpenSEE/Controllers/AnalyticController.cs +++ b/src/OpenSEE/Controllers/AnalyticController.cs @@ -35,6 +35,7 @@ using OpenSEE.Model; using Microsoft.AspNetCore.Mvc; using Gemstone.Data.Model; +using Gemstone.Configuration; namespace OpenSEE { @@ -60,7 +61,7 @@ static AnalyticController() { s_memoryCache = new MemoryCache("Analytics"); - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { m_cacheSlidingExpiration = connection.ExecuteScalar("SELECT Value FROM [OpenSee.Setting] WHERE Name = 'SlidingCacheExpiration'") ?? 2.0; } @@ -410,7 +411,7 @@ private class SequenceComponents [Route("GetFaultDistanceData"),HttpGet] public JsonReturn GetFaultDistanceData() { - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { Dictionary query = Request.QueryParameters(); @@ -439,7 +440,7 @@ public JsonReturn GetFaultDistanceData() private D3Series QueryFaultDistanceData(int faultCurveID, Meter meter, Asset asset, int evtID) { - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { FaultCurve faultCurve = new TableOperations(connection).QueryRecordWhere("ID = {0}", faultCurveID); DataGroup dataGroup = new DataGroup(); @@ -479,13 +480,13 @@ private D3Series QueryFaultDistanceData(int faultCurveID, Meter meter, Asset ass [Route("GetFirstDerivativeData"), HttpGet] public async Task GetFirstDerivativeData() { - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { Dictionary query = Request.QueryParameters(); int eventId = int.Parse(query["eventId"]); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection("systemSettings"); + meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); List returnList = new List(); DataTable table = connection.RetrieveData("SELECT ID, StartTime FROM Event WHERE ID = {0}", evt.ID); @@ -595,14 +596,14 @@ public static D3Series GetFirstDerivativeSeries(DataSeries dataSeries, string la [Route("GetImpedanceData"), HttpGet] public async Task GetImpedanceData() { - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { Dictionary query = Request.QueryParameters(); int eventId = int.Parse(query["eventId"]); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection("systemSettings"); + meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); VICycleDataGroup viCycleDataGroup = await QueryVICycleDataGroupAsync(evt.ID, meter); List returnList = GetImpedanceLookup(viCycleDataGroup); @@ -785,13 +786,13 @@ private IEnumerable CalculateImpedance(CycleDataGroup Voltage, CycleDat [Route("GetRemoveCurrentData"), HttpGet] public async Task GetRemoveCurrentData() { - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { Dictionary query = Request.QueryParameters(); int eventId = int.Parse(query["eventId"]); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection("systemSettings"); + meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); DataGroup dataGroup = await QueryDataGroupAsync(evt.ID, meter); List returnList = GetRemoveCurrentLookup(dataGroup); @@ -940,13 +941,13 @@ public List GetRemoveCurrentLookup(DataGroup dataGroup) [Route("GetI2tData"), HttpGet] public async Task GetI2tData() { - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { Dictionary query = Request.QueryParameters(); int eventId = int.Parse(query["eventId"]); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection("systemSettings"); + meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); DataGroup dataGroup = await QueryDataGroupAsync(evt.ID, meter); List returnList = GetI2tLookup(dataGroup); @@ -1007,14 +1008,14 @@ private IEnumerable ComputeI2T(IEnumerable current, double [Route("GetPowerData"), HttpGet] public async Task GetPowerData() { - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { Dictionary query = Request.QueryParameters(); int eventId = int.Parse(query["eventId"]); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection("systemSettings"); + meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); VICycleDataGroup vICycleDataGroup = await QueryVICycleDataGroupAsync(evt.ID, meter); List returnList = GetPowerLookup(vICycleDataGroup); @@ -1304,13 +1305,13 @@ public List GetPowerLookup(VICycleDataGroup vICycleDataGroup) [Route("GetMissingVoltageData"), HttpGet] public async Task GetMissingVoltageData() { - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { Dictionary query = Request.QueryParameters(); int eventId = int.Parse(query["eventId"]); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection("systemSettings"); + meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); DataGroup dataGroup = await QueryDataGroupAsync(evt.ID, meter); List returnList = GetMissingVoltageLookup(dataGroup); @@ -1385,13 +1386,13 @@ public List GetMissingVoltageLookup(DataGroup dataGroup) [Route("GetClippedWaveformsData"), HttpGet] public async Task GetClippedWaveformsData() { - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { Dictionary query = Request.QueryParameters(); int eventId = int.Parse(query["eventId"]); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection("systemSettings"); + meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); DataGroup dataGroup = await QueryDataGroupAsync(evt.ID, meter); List returnList = GetClippedWaveformsLookup(dataGroup); @@ -1527,7 +1528,7 @@ private static D3Series GenerateFixedWaveform(DataSeries dataSeries, string labe [Route("GetLowPassFilterData"), HttpGet] public async Task GetLowPassFilterData() { - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { Dictionary query = Request.QueryParameters(); int filterOrder = int.Parse(query["filter"]); @@ -1535,7 +1536,7 @@ public async Task GetLowPassFilterData() Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection("systemSettings"); + meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); DataGroup dataGroup = await QueryDataGroupAsync(evt.ID, meter); List returnList = GetLowPassFilterLookup(dataGroup, filterOrder); @@ -1602,7 +1603,7 @@ public D3Series FilteredPassSignal(Filter Filt, DataSeries Data) [Route("GetHighPassFilterData"), HttpGet] public async Task GetHighPassFilterData() { - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { Dictionary query = Request.QueryParameters(); int filterOrder = int.Parse(query["filter"]); @@ -1610,7 +1611,7 @@ public async Task GetHighPassFilterData() Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection("systemSettings"); + meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); DataGroup dataGroup = await QueryDataGroupAsync(evt.ID, meter); List returnList = GetHighPassFilterLookup(dataGroup, filterOrder); @@ -1655,13 +1656,13 @@ public List GetHighPassFilterLookup(DataGroup dataGroup, int order) [Route("GetOverlappingWaveformData"), HttpGet] public async Task GetOverlappingWaveformData() { - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { Dictionary query = Request.QueryParameters(); int eventId = int.Parse(query["eventId"]); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection("systemSettings"); + meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); DataGroup dataGroup = await QueryDataGroupAsync(evt.ID, meter); List returnList = GetOverlappingWaveformLookup(dataGroup); @@ -1751,14 +1752,14 @@ private D3Series GenerateOverlappingWaveform(DataSeries dataSeries) [Route("GetRapidVoltageChangeData"), HttpGet] public async Task GetRapidVoltageChangeData() { - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { Dictionary query = Request.QueryParameters(); int eventId = int.Parse(query["eventId"]); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection("systemSettings"); + meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); VICycleDataGroup vICycleDataGroup = await QueryVICycleDataGroupAsync(evt.ID, meter); List returnList = GetRapidVoltageChangeLookup(vICycleDataGroup); @@ -1833,14 +1834,14 @@ private D3Series GetRapidVoltageChangeFlotSeries(DataSeries dataSeries) [Route("GetSymmetricalComponentsData"), HttpGet] public async Task GetSymmetricalComponentsData() { - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { Dictionary query = Request.QueryParameters(); int eventId = int.Parse(query["eventId"]); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection("systemSettings"); + meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); VICycleDataGroup vICycleDataGroup = await QueryVICycleDataGroupAsync(evt.ID, meter); List returnList = GetSymmetricalComponentsLookup(vICycleDataGroup); @@ -2018,14 +2019,14 @@ private SequenceComponents CalculateSequenceComponents(Complex an, Complex bn, C [Route("GetUnbalanceData"), HttpGet] public async Task GetUnbalanceData() { - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { Dictionary query = Request.QueryParameters(); int eventId = int.Parse(query["eventId"]); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection("systemSettings"); + meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); VICycleDataGroup vICycleDataGroup = await QueryVICycleDataGroupAsync(evt.ID, meter); List returnList = GetUnbalanceLookup(vICycleDataGroup); @@ -2169,14 +2170,14 @@ public List GetUnbalanceLookup(VICycleDataGroup vICycleDataGroup) [Route("GetRectifierData"), HttpGet] public async Task GetRectifierData() { - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { Dictionary query = Request.QueryParameters(); int eventId = int.Parse(query["eventId"]); double TRC = double.Parse(query["Trc"]); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection("systemSettings"); + meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); double systemFrequency = connection.ExecuteScalar("SELECT Value FROM Setting WHERE Name = 'SystemFrequency'") ?? 60.0; VIDataGroup dataGroup = await QueryVIDataGroupAsync(evt.ID, meter); @@ -2266,13 +2267,13 @@ public List GetRectifierLookup(VIDataGroup dataGroup, double RC) [Route("GetFrequencyData"), HttpGet] public async Task GetFrequencyData() { - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { Dictionary query = Request.QueryParameters(); int eventId = int.Parse(query["eventId"]); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection("systemSettings"); + meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); VIDataGroup viDataGroup = await QueryVIDataGroupAsync(evt.ID, meter); List returnList = GetFrequencyLookup(viDataGroup); @@ -2435,7 +2436,7 @@ private D3Series MedianFilt(D3Series input) [Route("GetTHDData"), HttpGet] public async Task GetTHDData() { - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { Dictionary query = Request.QueryParameters(); int eventId = int.Parse(query["eventId"]); @@ -2443,7 +2444,7 @@ public async Task GetTHDData() Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection("systemSettings"); + meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); DataGroup dataGroup = await QueryDataGroupAsync(evt.ID, meter); List returnList = GetTHDLookup(dataGroup, forceFullRes==1); @@ -2537,7 +2538,7 @@ private D3Series GenerateTHD( DataSeries dataSeries, bool fullRes) [Route("GetSpecifiedHarmonicData"), HttpGet] public async Task GetSpecifiedHarmonicData() { - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { Dictionary query = Request.QueryParameters(); int eventId = int.Parse(query["eventId"]); @@ -2546,7 +2547,7 @@ public async Task GetSpecifiedHarmonicData() Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); int specifiedHarmonic = int.Parse(query["specifiedHarmonic"]); - meter.ConnectionFactory = () => new AdoDataConnection("systemSettings"); + meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); DataGroup dataGroup = await QueryDataGroupAsync(evt.ID, meter); List returnList = GetSpecifiedHarmonicLookup(dataGroup, specifiedHarmonic, forceFullRes==1); @@ -2662,14 +2663,14 @@ private static IEnumerable GenerateSpecifiedHarmonic( DataSeries dataS [Route("GetBreakerRestrikeData"), HttpGet] public async Task GetBreakerRestrikeData() { - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { Dictionary query = Request.QueryParameters(); int eventId = int.Parse(query["eventId"]); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection("systemSettings"); + meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); DataGroup dataGroup = await QueryDataGroupAsync(eventId, meter); List returnList = GetBreakerRestrikeData(evt.ID, dataGroup); @@ -2682,7 +2683,7 @@ public async Task GetBreakerRestrikeData() private List GetBreakerRestrikeData(int eventID, DataGroup dataGroup) { - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { List currents; List voltages; @@ -2763,7 +2764,7 @@ private List GetBreakerRestrikeData(int eventID, DataGroup dataGroup) [Route("GetFFTData"), HttpGet] public async Task GetFFTData() { - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { Dictionary query = Request.QueryParameters(); int eventId = int.Parse(query["eventId"]); @@ -2771,7 +2772,7 @@ public async Task GetFFTData() Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection("systemSettings"); + meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); double startTime = query.ContainsKey("startDate") ? double.Parse(query["startDate"]) : evt.StartTime.Subtract(m_epoch).TotalMilliseconds; DataGroup dataGroup = await QueryDataGroupAsync(eventId, meter); @@ -2787,7 +2788,7 @@ public List GetFFTLookup(DataGroup dataGroup, double startTime, int cy { int maxHarmonic; - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) maxHarmonic = int.Parse(connection.ExecuteScalar("SELECT Value FROM [OpenSee.Setting] WHERE Name = 'maxFFTHarmonic'") ?? "50"); List dataLookup = new List(); diff --git a/src/OpenSEE/Controllers/HomeController.cs b/src/OpenSEE/Controllers/HomeController.cs index 89a1ba66..f93d4627 100644 --- a/src/OpenSEE/Controllers/HomeController.cs +++ b/src/OpenSEE/Controllers/HomeController.cs @@ -29,6 +29,7 @@ using Gemstone.Web.Model; using OpenSEE.Model; using openXDA.Model; +using Gemstone.Configuration; namespace OpenSEE.Controllers { @@ -58,7 +59,7 @@ public ActionResult Home() if (Request.QueryString.Get("eventid") != null) eventID = int.Parse(Request.QueryString["eventid"]); - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { TableOperations eventTable = new TableOperations(connection); diff --git a/src/OpenSEE/Controllers/OpenSEEController.cs b/src/OpenSEE/Controllers/OpenSEEController.cs index 118bfcff..dbb941eb 100644 --- a/src/OpenSEE/Controllers/OpenSEEController.cs +++ b/src/OpenSEE/Controllers/OpenSEEController.cs @@ -35,6 +35,7 @@ using System.Threading.Tasks; using System.Web.Http; using FaultData.DataAnalysis; +using Gemstone.Configuration; using Gemstone.Data; using Gemstone.Data.Model; using Gemstone.Web; @@ -106,7 +107,7 @@ static OpenSEEController() { s_memoryCache = new MemoryCache("openSEE"); - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { m_cacheSlidingExpiration = connection.ExecuteScalar("SELECT Value FROM [OpenSee.Setting] WHERE Name = 'SlidingCacheExpiration'") ?? 2.0; } @@ -120,7 +121,7 @@ static OpenSEEController() [Route("GetData"),HttpGet] public async Task GetData() { - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { Dictionary query = Request.QueryParameters(); @@ -140,7 +141,7 @@ public async Task GetData() Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection("systemSettings"); + meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); List returnList = new List(); @@ -178,7 +179,7 @@ private List GetD3DataLookup(DataGroup dataGroup, string type, int evt ds => { if (type == "TripCoilCurrent") { - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { RelayPerformance relayPerformance = new TableOperations(connection).QueryRecordWhere("EventID = {0} AND ChannelID = {1}", evtID, ds.SeriesInfo.ChannelID); List dataMarkers = new List(); @@ -282,7 +283,7 @@ private List GetD3FrequencyDataLookup(VICycleDataGroup vICycleDataGrou { //Determine Sbase double Sbase = 0; - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) Sbase = connection.ExecuteScalar("SELECT Value FROM Setting WHERE Name = 'SystemMVABase'"); IEnumerable names = vICycleDataGroup.CycleDataGroups.Where(ds => ds.RMS.SeriesInfo.Channel.MeasurementType.Name == type).Select(ds => ds.RMS.SeriesInfo.Channel.Phase.Name); @@ -348,7 +349,7 @@ private List GetD3FrequencyDataLookup(VICycleDataGroup vICycleDataGrou [Route("GetBreakerData"),HttpGet] public async Task GetBreakerData() { - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { Dictionary query = Request.QueryParameters(); int eventId = int.Parse(query["eventId"]); @@ -407,7 +408,7 @@ private void AdjustLegendNumbering(List data) [Route("GetAnalogsData"), HttpGet] public async Task GetAnalogsData() { - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { Dictionary query = Request.QueryParameters(); int eventId = int.Parse(query["eventId"]); @@ -461,7 +462,7 @@ public Dictionary GetHeaderData() Dictionary returnDict = new Dictionary(); - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { EventView theEvent = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); @@ -596,7 +597,7 @@ public Dictionary> GetNavData() }; - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { EventView theEvent = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); using (IDbCommand cmd = connection.Connection.CreateCommand()) @@ -646,7 +647,7 @@ public Dictionary> GetNavData() [Route("GetOverlappingEvents"),HttpGet] public DataTable GetOverlappingEvents() { - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { Dictionary query = Request.QueryParameters(); int eventId = int.Parse(query["eventId"]); @@ -713,7 +714,7 @@ public Dictionary GetScalarStats() Dictionary query = Request.QueryParameters(); int eventId = int.Parse(query["eventId"]); - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { DataTable dataTable = connection.RetrieveData("SELECT * FROM OpenSEEScalarStatView WHERE EventID = {0}", eventId); if (dataTable.Rows.Count == 0) return new Dictionary(); @@ -730,7 +731,7 @@ public DataTable GetHarmonics() Dictionary query = Request.QueryParameters(); int eventId = int.Parse(query["eventId"]); - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { DataTable dataTable = connection.RetrieveData(@" SELECT @@ -755,7 +756,7 @@ public DataTable GetTimeCorrelatedSags() int eventID = int.Parse(query["eventId"]); if (eventID <= 0) return new DataTable(); - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { double timeTolerance = connection.ExecuteScalar("SELECT Value FROM Setting WHERE Name = 'TimeTolerance'"); DateTime startTime = connection.ExecuteScalar("SELECT StartTime FROM Event WHERE ID = {0}", eventID); @@ -773,7 +774,7 @@ public IEnumerable GetLightningData() Dictionary query = Request.QueryParameters(); int eventID = int.Parse(query["eventID"]); - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { const string QueryFormat = "SELECT * " + @@ -820,7 +821,7 @@ public IHttpActionResult GetOutputChannelCount(int eventID) try { if (eventID <= 0) return BadRequest("Invalid EventID"); - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { int count = connection.ExecuteScalar(@" SELECT @@ -850,7 +851,7 @@ Event JOIN [Route("GetPQBrowser"), HttpGet] public IHttpActionResult GetPQBrowser() { - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { string pqBrowserURl = connection.ExecuteScalar(@" SELECT diff --git a/src/OpenSEE/Controllers/OpenSeeControllerBase.cs b/src/OpenSEE/Controllers/OpenSeeControllerBase.cs index b799b066..87669e98 100644 --- a/src/OpenSEE/Controllers/OpenSeeControllerBase.cs +++ b/src/OpenSEE/Controllers/OpenSeeControllerBase.cs @@ -28,6 +28,7 @@ using System.Threading.Tasks; using System.Web.Http; using FaultData.DataAnalysis; +using Gemstone.Configuration; using Gemstone.Data; using Gemstone.NumericalAnalysis; using OpenSEE.Model; @@ -59,7 +60,7 @@ public static double Sbase { { if (m_Sbase != null) return (double)m_Sbase; - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) m_Sbase = connection.ExecuteScalar("SELECT Value FROM Setting WHERE Name = 'SystemMVABase'") ?? 100.0; return (double)m_Sbase; } @@ -71,7 +72,7 @@ public static double Fbase { if (m_Fbase != null) return (double)m_Fbase; - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) m_Fbase = connection.ExecuteScalar("SELECT Value FROM Setting WHERE Name = 'SystemFrequency'")?? 60.0; return (double)m_Fbase; } @@ -84,7 +85,7 @@ public static int MaxSampleRate if (m_MaxSampleRate != null) return (int)m_MaxSampleRate; - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) m_MaxSampleRate = int.Parse(connection.ExecuteScalar("SELECT Value FROM [OpenSee.Setting] WHERE Name = 'maxSampleRate'") ?? "-1"); return (int)m_MaxSampleRate; } @@ -97,7 +98,7 @@ public static int MinSampleRate if (m_MinSampleRate != null) return (int)m_MinSampleRate; - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) m_MinSampleRate = int.Parse(connection.ExecuteScalar("SELECT Value FROM [OPenSee.Setting] WHERE Name = 'minSampleRate'") ?? "-1"); return (int)m_MinSampleRate; } @@ -130,7 +131,7 @@ public static string GetColor(Channel channel) return "random"; } - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { if (channel.MeasurementType.Name == "Voltage") @@ -423,7 +424,7 @@ public static async Task QueryDataGroupAsync(int eventID, Meter meter try { - List data = ChannelData.DataFromEvent(eventID, () => new AdoDataConnection("systemSettings")); + List data = ChannelData.DataFromEvent(eventID, () => new AdoDataConnection(Settings.Default)); DataGroup dataGroup = ToDataGroup(meter, data); taskCompletionSource.SetResult(dataGroup); return dataGroup; diff --git a/src/OpenSEE/FaultSpecifics.aspx.cs b/src/OpenSEE/FaultSpecifics.aspx.cs index f20853dc..82c9d666 100644 --- a/src/OpenSEE/FaultSpecifics.aspx.cs +++ b/src/OpenSEE/FaultSpecifics.aspx.cs @@ -25,7 +25,7 @@ public partial class FaultSpecifics : Page public string postedDoubleEndedConfidence = ""; public string postedExceptionMessage = ""; - string connectionstring = ConfigurationFile.Current.Settings["systemSettings"]["ConnectionString"].Value; + string connectionstring = ConfigurationFile.Current.Settings[Settings.Default]["ConnectionString"].Value; protected void Page_Load(object sender, EventArgs e) { @@ -38,7 +38,7 @@ protected void Page_Load(object sender, EventArgs e) { postedEventId = Request["eventId"]; - using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { try { diff --git a/src/OpenSEE/Global.asax.cs b/src/OpenSEE/Global.asax.cs index c1645c8b..4c5cd9d8 100644 --- a/src/OpenSEE/Global.asax.cs +++ b/src/OpenSEE/Global.asax.cs @@ -65,7 +65,7 @@ protected void Application_Start() GlobalSettings global = DefaultModel.Global; // Make sure LSCVSReport specific default config file service settings exist - CategorizedSettingsElementCollection systemSettings = ConfigurationFile.Current.Settings["systemSettings"]; + CategorizedSettingsElementCollection systemSettings = ConfigurationFile.Current.Settings[Settings.Default]; CategorizedSettingsElementCollection securityProvider = ConfigurationFile.Current.Settings["securityProvider"]; systemSettings.Add("ConnectionString", "Data Source=localhost; Initial Catalog=OpenSee; Integrated Security=SSPI", "Configuration connection string."); From d340d37c28370bbc7e0200ee042c638f1efffd17 Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Mon, 19 Jan 2026 15:52:55 -0500 Subject: [PATCH 05/58] add alternative local library for replacing old libraries that used to be imported from other repositories --- src/Libraries/FaultAlgorithms/Conductor.cs | 122 ++++ src/Libraries/FaultAlgorithms/Cycle.cs | 200 ++++++ src/Libraries/FaultAlgorithms/CycleData.cs | 177 +++++ src/Libraries/FaultAlgorithms/CycleDataSet.cs | 303 ++++++++ .../FaultAlgorithms/FaultAlgorithms.csproj | 13 + .../FaultAlgorithms/MeasurementData.cs | 97 +++ .../FaultAlgorithms/MeasurementDataSet.cs | 272 +++++++ .../FaultData/DataAnalysis/CycleDataGroup.cs | 115 +++ .../FaultData/DataAnalysis/DataGroup.cs | 662 ++++++++++++++++++ .../FaultData/DataAnalysis/DataSeries.cs | 588 ++++++++++++++++ .../DataAnalysis/ReportedDisturbance.cs | 58 ++ .../FaultData/DataAnalysis/Transform.cs | 358 ++++++++++ .../DataAnalysis/VICycleDataGroup.cs | 476 +++++++++++++ .../FaultData/DataAnalysis/VIDataGroup.cs | 521 ++++++++++++++ src/Libraries/FaultData/FaultData.csproj | 19 + .../openXDA.Model/Channels/Channel.cs | 580 +++++++++++++++ .../openXDA.Model/Channels/ChannelData.cs | 649 +++++++++++++++++ .../openXDA.Model/Channels/DataPoint.cs | 110 +++ .../Channels/MeasurementCharacteristic.cs | 45 ++ .../openXDA.Model/Channels/MeasurementType.cs | 44 ++ src/Libraries/openXDA.Model/Channels/Phase.cs | 43 ++ .../openXDA.Model/Channels/Series.cs | 248 +++++++ .../openXDA.Model/Channels/SeriesType.cs | 40 ++ src/Libraries/openXDA.Model/Events/Event.cs | 72 ++ src/Libraries/openXDA.Model/Files/DataFile.cs | 93 +++ src/Libraries/openXDA.Model/Files/FileBlob.cs | 38 + .../openXDA.Model/Files/FileGroup.cs | 102 +++ .../openXDA.Model/Files/FileGroupField.cs | 61 ++ .../Files/FileGroupFieldValue.cs | 39 ++ src/Libraries/openXDA.Model/LazyContext.cs | 369 ++++++++++ .../openXDA.Model/Links/AssetConnection.cs | 176 +++++ .../openXDA.Model/Links/MeterLine.cs | 174 +++++ .../openXDA.Model/Links/MeterLocationLine.cs | 192 +++++ .../openXDA.Model/Meters/Location.cs | 509 ++++++++++++++ src/Libraries/openXDA.Model/Meters/Meter.cs | 338 +++++++++ .../PQDigest/HomeScreenWidget.cs | 44 ++ .../openXDA.Model/PQDigest/Widget.cs | 51 ++ .../openXDA.Model/SEBrowser/Widget.cs | 61 ++ .../openXDA.Model/SEBrowser/WidgetCategory.cs | 52 ++ .../openXDA.Model/Settings/PQDigestSetting.cs | 33 + .../openXDA.Model/Settings/Setting.cs | 84 +++ .../TransmissionElements/Asset.cs | 615 ++++++++++++++++ .../TransmissionElements/SourceImpedance.cs | 114 +++ .../openXDA.Model/openXDA.Model.csproj | 15 + src/OpenSEE.sln | 25 + src/OpenSEE/OpenSEE.csproj | 5 + 46 files changed, 9002 insertions(+) create mode 100644 src/Libraries/FaultAlgorithms/Conductor.cs create mode 100644 src/Libraries/FaultAlgorithms/Cycle.cs create mode 100644 src/Libraries/FaultAlgorithms/CycleData.cs create mode 100644 src/Libraries/FaultAlgorithms/CycleDataSet.cs create mode 100644 src/Libraries/FaultAlgorithms/FaultAlgorithms.csproj create mode 100644 src/Libraries/FaultAlgorithms/MeasurementData.cs create mode 100644 src/Libraries/FaultAlgorithms/MeasurementDataSet.cs create mode 100644 src/Libraries/FaultData/DataAnalysis/CycleDataGroup.cs create mode 100644 src/Libraries/FaultData/DataAnalysis/DataGroup.cs create mode 100644 src/Libraries/FaultData/DataAnalysis/DataSeries.cs create mode 100644 src/Libraries/FaultData/DataAnalysis/ReportedDisturbance.cs create mode 100644 src/Libraries/FaultData/DataAnalysis/Transform.cs create mode 100644 src/Libraries/FaultData/DataAnalysis/VICycleDataGroup.cs create mode 100644 src/Libraries/FaultData/DataAnalysis/VIDataGroup.cs create mode 100644 src/Libraries/FaultData/FaultData.csproj create mode 100644 src/Libraries/openXDA.Model/Channels/Channel.cs create mode 100644 src/Libraries/openXDA.Model/Channels/ChannelData.cs create mode 100644 src/Libraries/openXDA.Model/Channels/DataPoint.cs create mode 100644 src/Libraries/openXDA.Model/Channels/MeasurementCharacteristic.cs create mode 100644 src/Libraries/openXDA.Model/Channels/MeasurementType.cs create mode 100644 src/Libraries/openXDA.Model/Channels/Phase.cs create mode 100644 src/Libraries/openXDA.Model/Channels/Series.cs create mode 100644 src/Libraries/openXDA.Model/Channels/SeriesType.cs create mode 100644 src/Libraries/openXDA.Model/Events/Event.cs create mode 100644 src/Libraries/openXDA.Model/Files/DataFile.cs create mode 100644 src/Libraries/openXDA.Model/Files/FileBlob.cs create mode 100644 src/Libraries/openXDA.Model/Files/FileGroup.cs create mode 100644 src/Libraries/openXDA.Model/Files/FileGroupField.cs create mode 100644 src/Libraries/openXDA.Model/Files/FileGroupFieldValue.cs create mode 100644 src/Libraries/openXDA.Model/LazyContext.cs create mode 100644 src/Libraries/openXDA.Model/Links/AssetConnection.cs create mode 100644 src/Libraries/openXDA.Model/Links/MeterLine.cs create mode 100644 src/Libraries/openXDA.Model/Links/MeterLocationLine.cs create mode 100644 src/Libraries/openXDA.Model/Meters/Location.cs create mode 100644 src/Libraries/openXDA.Model/Meters/Meter.cs create mode 100644 src/Libraries/openXDA.Model/PQDigest/HomeScreenWidget.cs create mode 100644 src/Libraries/openXDA.Model/PQDigest/Widget.cs create mode 100644 src/Libraries/openXDA.Model/SEBrowser/Widget.cs create mode 100644 src/Libraries/openXDA.Model/SEBrowser/WidgetCategory.cs create mode 100644 src/Libraries/openXDA.Model/Settings/PQDigestSetting.cs create mode 100644 src/Libraries/openXDA.Model/Settings/Setting.cs create mode 100644 src/Libraries/openXDA.Model/TransmissionElements/Asset.cs create mode 100644 src/Libraries/openXDA.Model/TransmissionElements/SourceImpedance.cs create mode 100644 src/Libraries/openXDA.Model/openXDA.Model.csproj diff --git a/src/Libraries/FaultAlgorithms/Conductor.cs b/src/Libraries/FaultAlgorithms/Conductor.cs new file mode 100644 index 00000000..5b5a24fe --- /dev/null +++ b/src/Libraries/FaultAlgorithms/Conductor.cs @@ -0,0 +1,122 @@ +//********************************************************************************************************************* +// Conductor.cs +// Version 1.1 and subsequent releases +// +// Copyright © 2013, 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 Eclipse Public License -v 1.0 (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/eclipse-1.0.php +// +// 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. +// +// -------------------------------------------------------------------------------------------------------------------- +// +// Version 1.0 +// +// Copyright 2012 ELECTRIC POWER RESEARCH INSTITUTE, INC. All rights reserved. +// +// openFLE ("this software") is licensed under BSD 3-Clause license. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// • Redistributions of source code must retain the above copyright notice, this list of conditions and +// the following disclaimer. +// +// • Redistributions in binary form must reproduce the above copyright notice, this list of conditions and +// the following disclaimer in the documentation and/or other materials provided with the distribution. +// +// • Neither the name of the Electric Power Research Institute, Inc. (“EPRI”) nor the names of its contributors +// may be used to endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL EPRI BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// +// This software incorporates work covered by the following copyright and permission notice: +// +// • TVA Code Library 4.0.4.3 - Tennessee Valley Authority, tvainfo@tva.gov +// No copyright is claimed pursuant to 17 USC § 105. All Other Rights Reserved. +// +// Licensed under TVA Custom License based on NASA Open Source Agreement (TVA Custom NOSA); +// you may not use TVA Code Library except in compliance with the TVA Custom NOSA. You may +// obtain a copy of the TVA Custom NOSA at http://tvacodelibrary.codeplex.com/license. +// +// TVA Code Library is provided by the copyright holders and contributors "as is" and any express +// or implied warranties, including, but not limited to, the implied warranties of merchantability +// and fitness for a particular purpose are disclaimed. +// +//********************************************************************************************************************* +// +// Code Modification History: +// ------------------------------------------------------------------------------------------------------------------- +// 06/14/2012 - Stephen C. Wills, Grid Protection Alliance +// Generated original version of source code. +// +//********************************************************************************************************************* + +namespace FaultAlgorithms +{ + /// + /// Contains data for both the voltage + /// and current on a conductor. + /// + public class Conductor + { + #region [ Members ] + + // Fields + + /// + /// One cycle of voltage data. + /// + public Cycle V; + + /// + /// One cycle of current data. + /// + public Cycle I; + + #endregion + + #region [ Constructors ] + + /// + /// Creates a new instance of the class. + /// + public Conductor() + { + V = new Cycle(); + I = new Cycle(); + } + + /// + /// Creates a new instance of the class. + /// + /// The index of the cycle to be calculated. + /// The value to divide from the sample rate to determine the starting location of the cycle. + /// The frequency of the sine wave during this cycle. + /// The voltage data points. + /// The current data points. + public Conductor(int cycleIndex, int sampleRateDivisor, double frequency, MeasurementData voltageData, MeasurementData currentData) + { + int vStart = cycleIndex * (voltageData.SampleRate / sampleRateDivisor); + int iStart = cycleIndex * (currentData.SampleRate / sampleRateDivisor); + V = new Cycle(vStart, frequency, voltageData); + I = new Cycle(iStart, frequency, currentData); + } + + #endregion + } +} diff --git a/src/Libraries/FaultAlgorithms/Cycle.cs b/src/Libraries/FaultAlgorithms/Cycle.cs new file mode 100644 index 00000000..91db47e8 --- /dev/null +++ b/src/Libraries/FaultAlgorithms/Cycle.cs @@ -0,0 +1,200 @@ +//********************************************************************************************************************* +// Cycle.cs +// Version 1.1 and subsequent releases +// +// Copyright 2013, 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 Eclipse Public License -v 1.0 (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/eclipse-1.0.php +// +// 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. +// +// -------------------------------------------------------------------------------------------------------------------- +// +// Version 1.0 +// +// Copyright 2012 ELECTRIC POWER RESEARCH INSTITUTE, INC. All rights reserved. +// +// openFLE ("this software") is licensed under BSD 3-Clause license. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// Redistributions of source code must retain the above copyright notice, this list of conditions and +// the following disclaimer. +// +// Redistributions in binary form must reproduce the above copyright notice, this list of conditions and +// the following disclaimer in the documentation and/or other materials provided with the distribution. +// +// Neither the name of the Electric Power Research Institute, Inc. (EPRI) nor the names of its contributors +// may be used to endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL EPRI BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// +// This software incorporates work covered by the following copyright and permission notice: +// +// TVA Code Library 4.0.4.3 - Tennessee Valley Authority, tvainfo@tva.gov +// No copyright is claimed pursuant to 17 USC 105. All Other Rights Reserved. +// +// Licensed under TVA Custom License based on NASA Open Source Agreement (TVA Custom NOSA); +// you may not use TVA Code Library except in compliance with the TVA Custom NOSA. You may +// obtain a copy of the TVA Custom NOSA at http://tvacodelibrary.codeplex.com/license. +// +// TVA Code Library is provided by the copyright holders and contributors "as is" and any express +// or implied warranties, including, but not limited to, the implied warranties of merchantability +// and fitness for a particular purpose are disclaimed. +// +//********************************************************************************************************************* +// +// Code Modification History: +// ------------------------------------------------------------------------------------------------------------------- +// 05/23/2012 - J. Ritchie Carroll, Grid Protection Alliance +// Generated original version of source code. +// +//********************************************************************************************************************* + +using Gemstone; +using Gemstone.Numeric; +using Gemstone.Numeric.Analysis; +using Gemstone.Units; + +namespace FaultAlgorithms +{ + /// + /// Represents a cycle of single phase power frequency-domain data. + /// + public class Cycle + { + #region [ Members ] + + // Constants + private const double PiOverTwo = Math.PI / 2.0D; + + // Fields + + /// + /// The actual frequency of the cycle in hertz. + /// + public double Frequency; + + /// + /// The complex number representation of the RMS phasor. + /// + public ComplexNumber Complex; + + /// + /// The most extreme data point in the cycle. + /// + public double Peak; + + /// + /// The error between the sine fit and the given data values. + /// + public double Error; + + #endregion + + #region [ Constructors ] + + /// + /// Creates a new instance of the class. + /// + public Cycle() + { + } + + /// + /// Creates a new instance of the class. + /// + /// The index of the start of the cycle. + /// The frequency of the measured system, in Hz. + /// The time-domain data to be used to calculate frequency-domain values. + public Cycle(int startSample, double frequency, MeasurementData waveFormData) + { + long timeStart; + double[] timeInSeconds; + double[] measurements; + SineWave sineFit; + + if (startSample < 0) + throw new ArgumentOutOfRangeException("startSample"); + + if (startSample + waveFormData.SampleRate > waveFormData.Times.Length) + throw new ArgumentOutOfRangeException("startSample"); + + if (startSample + waveFormData.SampleRate > waveFormData.Measurements.Length) + throw new ArgumentOutOfRangeException("startSample"); + + timeStart = waveFormData.Times[startSample]; + timeInSeconds = new double[waveFormData.SampleRate]; + measurements = new double[waveFormData.SampleRate]; + + for (int i = 0; i < waveFormData.SampleRate; i++) + { + timeInSeconds[i] = Ticks.ToSeconds(waveFormData.Times[i + startSample] - timeStart); + measurements[i] = waveFormData.Measurements[i + startSample]; + } + + sineFit = WaveFit.SineFit(measurements, timeInSeconds, frequency); + + RMS = Math.Sqrt(measurements.Select(vi => vi * vi).Average()); + Phase = sineFit.Phase - PiOverTwo; + Peak = sineFit.Amplitude; + Frequency = frequency; + + Error = timeInSeconds + .Select(time => sineFit.CalculateY(time)) + .Zip(measurements, (calc, measurement) => Math.Abs(calc - measurement)) + .Sum(); + } + + #endregion + + #region [ Properties ] + + /// + /// Root-mean-square of the in the cycle. + /// + public double RMS + { + get + { + return Complex.Magnitude; + } + set + { + Complex.Magnitude = value; + } + } + + /// + /// Phase angle of the start of the cycle, relative to the reference angle. + /// + public Angle Phase + { + get + { + return Complex.Angle; + } + set + { + Complex.Angle = value; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Libraries/FaultAlgorithms/CycleData.cs b/src/Libraries/FaultAlgorithms/CycleData.cs new file mode 100644 index 00000000..610d5960 --- /dev/null +++ b/src/Libraries/FaultAlgorithms/CycleData.cs @@ -0,0 +1,177 @@ +//********************************************************************************************************************* +// CycleData.cs +// Version 1.1 and subsequent releases +// +// Copyright © 2013, 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 Eclipse Public License -v 1.0 (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/eclipse-1.0.php +// +// 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. +// +// -------------------------------------------------------------------------------------------------------------------- +// +// Version 1.0 +// +// Copyright 2012 ELECTRIC POWER RESEARCH INSTITUTE, INC. All rights reserved. +// +// openFLE ("this software") is licensed under BSD 3-Clause license. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// • Redistributions of source code must retain the above copyright notice, this list of conditions and +// the following disclaimer. +// +// • Redistributions in binary form must reproduce the above copyright notice, this list of conditions and +// the following disclaimer in the documentation and/or other materials provided with the distribution. +// +// • Neither the name of the Electric Power Research Institute, Inc. (“EPRI”) nor the names of its contributors +// may be used to endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL EPRI BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// +// This software incorporates work covered by the following copyright and permission notice: +// +// • TVA Code Library 4.0.4.3 - Tennessee Valley Authority, tvainfo@tva.gov +// No copyright is claimed pursuant to 17 USC § 105. All Other Rights Reserved. +// +// Licensed under TVA Custom License based on NASA Open Source Agreement (TVA Custom NOSA); +// you may not use TVA Code Library except in compliance with the TVA Custom NOSA. You may +// obtain a copy of the TVA Custom NOSA at http://tvacodelibrary.codeplex.com/license. +// +// TVA Code Library is provided by the copyright holders and contributors "as is" and any express +// or implied warranties, including, but not limited to, the implied warranties of merchantability +// and fitness for a particular purpose are disclaimed. +// +//********************************************************************************************************************* +// +// Code Modification History: +// ------------------------------------------------------------------------------------------------------------------- +// 06/14/2012 - Stephen C. Wills, Grid Protection Alliance +// Generated original version of source code. +// +//********************************************************************************************************************* + +using Gemstone.Numeric; + +namespace FaultAlgorithms +{ + /// + /// Contains data for a single cycle over all three line-to-neutral conductors. + /// + public class CycleData + { + #region [ Members ] + + // Constants + + /// + /// 2 * pi + /// + public const double TwoPI = 2.0D * Math.PI; + + // a = e^((2/3) * pi * i) + private const double Rad120 = TwoPI / 3.0D; + private static readonly ComplexNumber a = new ComplexNumber(Math.Cos(Rad120), Math.Sin(Rad120)); + private static readonly ComplexNumber aSq = a * a; + + // Fields + + /// + /// A-to-neutral conductor + /// + public Conductor AN; + + /// + /// B-to-neutral conductor + /// + public Conductor BN; + + /// + /// C-to-neutral conductor + /// + public Conductor CN; + + /// + /// Timestamp of the start of the cycle. + /// + public DateTime StartTime; + + #endregion + + #region [ Constructors ] + + /// + /// Creates a new instance of the class. + /// + public CycleData() + { + AN = new Conductor(); + BN = new Conductor(); + CN = new Conductor(); + } + + /// + /// Creates a new instance of the class. + /// + /// The index of the cycle being created. + /// The value to divide from the sample rate to determine the index of the sample at the start of the cycle. + /// The frequency of the measured system, in Hz. + /// The data set containing voltage measurements. + /// The data set containing current measurements. + public CycleData(int cycleIndex, int sampleRateDivisor, double frequency, MeasurementDataSet voltageDataSet, MeasurementDataSet currentDataSet) + { + int sampleIndex; + + AN = new Conductor(cycleIndex, sampleRateDivisor, frequency, voltageDataSet.AN, currentDataSet.AN); + BN = new Conductor(cycleIndex, sampleRateDivisor, frequency, voltageDataSet.BN, currentDataSet.BN); + CN = new Conductor(cycleIndex, sampleRateDivisor, frequency, voltageDataSet.CN, currentDataSet.CN); + + sampleIndex = cycleIndex * (voltageDataSet.AN.SampleRate / sampleRateDivisor); + StartTime = new DateTime(voltageDataSet.AN.Times[sampleIndex]); + } + + #endregion + + #region [ Methods ] + + /// + /// Calculates the positive, negative, and zero sequence components + /// and returns them in an array with indexes 1, 2, and 0 respectively. + /// + /// The cycle of A-to-neutral data to be used. + /// The cycle of B-to-neutral data to be used. + /// The cycle of C-to-neutral data to be used. + /// An array of size 3 containing the zero sequence, positive sequence, and negative sequence components in that order. + public static ComplexNumber[] CalculateSequenceComponents(Cycle anCycle, Cycle bnCycle, Cycle cnCycle) + { + ComplexNumber an = anCycle.Complex; + ComplexNumber bn = bnCycle.Complex; + ComplexNumber cn = cnCycle.Complex; + + ComplexNumber[] sequenceComponents = new ComplexNumber[3]; + + sequenceComponents[0] = (an + bn + cn) / 3.0D; + sequenceComponents[1] = (an + a * bn + aSq * cn) / 3.0D; + sequenceComponents[2] = (an + aSq * bn + a * cn) / 3.0D; + + return sequenceComponents; + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Libraries/FaultAlgorithms/CycleDataSet.cs b/src/Libraries/FaultAlgorithms/CycleDataSet.cs new file mode 100644 index 00000000..34b810b7 --- /dev/null +++ b/src/Libraries/FaultAlgorithms/CycleDataSet.cs @@ -0,0 +1,303 @@ +//********************************************************************************************************************* +// CycleDataSet.cs +// Version 1.1 and subsequent releases +// +// Copyright © 2013, 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 Eclipse Public License -v 1.0 (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/eclipse-1.0.php +// +// 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. +// +// -------------------------------------------------------------------------------------------------------------------- +// +// Version 1.0 +// +// Copyright 2012 ELECTRIC POWER RESEARCH INSTITUTE, INC. All rights reserved. +// +// openFLE ("this software") is licensed under BSD 3-Clause license. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// • Redistributions of source code must retain the above copyright notice, this list of conditions and +// the following disclaimer. +// +// • Redistributions in binary form must reproduce the above copyright notice, this list of conditions and +// the following disclaimer in the documentation and/or other materials provided with the distribution. +// +// • Neither the name of the Electric Power Research Institute, Inc. (“EPRI”) nor the names of its contributors +// may be used to endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL EPRI BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// +// This software incorporates work covered by the following copyright and permission notice: +// +// • TVA Code Library 4.0.4.3 - Tennessee Valley Authority, tvainfo@tva.gov +// No copyright is claimed pursuant to 17 USC § 105. All Other Rights Reserved. +// +// Licensed under TVA Custom License based on NASA Open Source Agreement (TVA Custom NOSA); +// you may not use TVA Code Library except in compliance with the TVA Custom NOSA. You may +// obtain a copy of the TVA Custom NOSA at http://tvacodelibrary.codeplex.com/license. +// +// TVA Code Library is provided by the copyright holders and contributors "as is" and any express +// or implied warranties, including, but not limited to, the implied warranties of merchantability +// and fitness for a particular purpose are disclaimed. +// +//********************************************************************************************************************* +// +// Code Modification History: +// ------------------------------------------------------------------------------------------------------------------- +// 06/14/2012 - Stephen C. Wills, Grid Protection Alliance +// Generated original version of source code. +// +//********************************************************************************************************************* + +using System.Collections; +using Gemstone.Numeric; +using Gemstone.Numeric.Analysis; + +namespace FaultAlgorithms +{ + /// + /// Represents a collection of all the cycles extracted from a given data set. + /// + public class CycleDataSet : IEnumerable + { + #region [ Members ] + + // Fields + private List m_cycles; + + #endregion + + #region [ Constructors ] + + /// + /// Creates a new instance of the class. + /// + public CycleDataSet() + { + m_cycles = new List(); + } + + /// + /// Creates a new instance of the class. + /// + /// The frequency of the measured system, in Hz. + /// The data set containing voltage data points. + /// The data set containing current data points. + public CycleDataSet(double frequency, MeasurementDataSet voltageDataSet, MeasurementDataSet currentDataSet) + { + Populate(frequency, voltageDataSet, currentDataSet); + } + + #endregion + + #region [ Properties ] + + /// + /// Gets or sets the data structure containing a + /// full cycle of data at the given index. + /// + /// The index of the cycle. + /// The cycle of data at the given index. + public CycleData this[int i] + { + get + { + return m_cycles[i]; + } + set + { + while(i >= m_cycles.Count) + m_cycles.Add(null); + + m_cycles[i] = value; + } + } + + /// + /// Gets the size of the cycle data set. + /// + public int Count + { + get + { + return m_cycles.Count; + } + } + + #endregion + + #region [ Methods ] + + /// + /// Populates the cycle data set by calculating cycle + /// data based on the given measurement data sets. + /// + /// The frequency of the measured system, in Hz. + /// Data set containing voltage waveform measurements. + /// Data set containing current waveform measurements. + public void Populate(double frequency, MeasurementDataSet voltageDataSet, MeasurementDataSet currentDataSet) + { + List measurementDataList; + int sampleRateDivisor; + int numberOfCycles; + + measurementDataList = new List() + { + voltageDataSet.AN, voltageDataSet.BN, voltageDataSet.CN, + currentDataSet.AN, currentDataSet.BN, currentDataSet.CN + }; + + sampleRateDivisor = measurementDataList + .Select(measurementData => measurementData.SampleRate) + .GreatestCommonDenominator(); + + numberOfCycles = measurementDataList + .Select(measurementData => (measurementData.Measurements.Length - measurementData.SampleRate + 1) / (measurementData.SampleRate / sampleRateDivisor)) + .Min(); + + for (int i = 0; i < numberOfCycles; i++) + m_cycles.Add(new CycleData(i, sampleRateDivisor, frequency, voltageDataSet, currentDataSet)); + } + + /// + /// Returns the index of the cycle with the largest total current. + /// + /// The index of the cycle with the largest total current. + public int GetLargestCurrentIndex() + { + int index = 0; + int bestFaultIndex = -1; + double largestCurrent = 0.0D; + + foreach (CycleData cycle in m_cycles) + { + double totalCurrent = cycle.AN.I.RMS + cycle.BN.I.RMS + cycle.CN.I.RMS; + + if (totalCurrent > largestCurrent) + { + bestFaultIndex = index; + largestCurrent = totalCurrent; + } + + index++; + } + + return bestFaultIndex; + } + + /// + /// Clears the cycle data set so that it can be repopulated. + /// + public void Clear() + { + m_cycles.Clear(); + } + + /// + /// Returns an enumerator that iterates through the collection of cycles. + /// + /// An object that can be used to iterate through the collection. + public IEnumerator GetEnumerator() + { + foreach (CycleData cycle in m_cycles) + { + yield return cycle; + } + } + + /// + /// Returns an enumerator that iterates through the collection of cycles. + /// + /// An object that can be used to iterate through the collection. + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + + #region [ Static ] + + // Static Methods + + /// + /// Exports the given to a CSV file. + /// + /// The name of the CSV file. + /// The cycle data set to be exported. + public static void ExportToCSV(string fileName, CycleDataSet cycles) + { + const string Header = + "AN V RMS,AN V Phase,AN V Peak," + + "BN V RMS,BN V Phase,BN V Peak," + + "CN V RMS,CN V Phase,CN V Peak," + + "Pos V Magnitude,Pos V Angle," + + "Neg V Magnitude,Neg V Angle," + + "Zero V Magnitude,Zero V Angle," + + "AN I RMS,AN I Phase,AN I Peak," + + "BN I RMS,BN I Phase,BN I Peak," + + "CN I RMS,CN I Phase,CN I Peak," + + "Pos I Magnitude,Pos I Angle," + + "Neg I Magnitude,Neg I Angle," + + "Zero I Magnitude,Zero I Angle"; + + using (FileStream fileStream = File.OpenWrite(fileName)) + { + using (TextWriter fileWriter = new StreamWriter(fileStream)) + { + // Write the CSV header to the file + fileWriter.WriteLine(Header); + + // Write data to the file + foreach (CycleData cycleData in cycles.m_cycles) + fileWriter.WriteLine(ToCSV(cycleData)); + } + } + } + + // Converts the cycle data to a row of CSV data. + private static string ToCSV(CycleData cycleData) + { + ComplexNumber[] vSeq = CycleData.CalculateSequenceComponents(cycleData.AN.V, cycleData.BN.V, cycleData.CN.V); + ComplexNumber[] iSeq = CycleData.CalculateSequenceComponents(cycleData.AN.I, cycleData.BN.I, cycleData.CN.I); + + string vCsv = string.Format("{0},{1},{2}", ToCSV(cycleData.AN.V), ToCSV(cycleData.BN.V), ToCSV(cycleData.CN.V)); + string vSeqCsv = string.Format("{0},{1},{2}", ToCSV(vSeq[1]), ToCSV(vSeq[2]), ToCSV(vSeq[0])); + string iCsv = string.Format("{0},{1},{2}", ToCSV(cycleData.AN.I), ToCSV(cycleData.BN.I), ToCSV(cycleData.CN.I)); + string iSeqCsv = string.Format("{0},{1},{2}", ToCSV(iSeq[1]), ToCSV(iSeq[2]), ToCSV(iSeq[0])); + + return string.Format("{0},{1},{2},{3}", vCsv, vSeqCsv, iCsv, iSeqCsv); + } + + // Converts the cycle to CSV data. + private static string ToCSV(Cycle cycle) + { + return string.Format("{0},{1},{2}", cycle.RMS, cycle.Phase.ToDegrees(), cycle.Peak); + } + + // Converts the sequence component to CSV data. + private static string ToCSV(ComplexNumber sequenceComponent) + { + return string.Format("{0},{1}", sequenceComponent.Magnitude, sequenceComponent.Angle.ToDegrees()); + } + + #endregion + } +} diff --git a/src/Libraries/FaultAlgorithms/FaultAlgorithms.csproj b/src/Libraries/FaultAlgorithms/FaultAlgorithms.csproj new file mode 100644 index 00000000..bc767802 --- /dev/null +++ b/src/Libraries/FaultAlgorithms/FaultAlgorithms.csproj @@ -0,0 +1,13 @@ + + + + net9.0 + enable + enable + + + + + + + diff --git a/src/Libraries/FaultAlgorithms/MeasurementData.cs b/src/Libraries/FaultAlgorithms/MeasurementData.cs new file mode 100644 index 00000000..04da176a --- /dev/null +++ b/src/Libraries/FaultAlgorithms/MeasurementData.cs @@ -0,0 +1,97 @@ +//********************************************************************************************************************* +// MeasurementData.cs +// Version 1.1 and subsequent releases +// +// Copyright 2013, 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 Eclipse Public License -v 1.0 (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/eclipse-1.0.php +// +// 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. +// +// -------------------------------------------------------------------------------------------------------------------- +// +// Version 1.0 +// +// Copyright 2012 ELECTRIC POWER RESEARCH INSTITUTE, INC. All rights reserved. +// +// openFLE ("this software") is licensed under BSD 3-Clause license. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// Redistributions of source code must retain the above copyright notice, this list of conditions and +// the following disclaimer. +// +// Redistributions in binary form must reproduce the above copyright notice, this list of conditions and +// the following disclaimer in the documentation and/or other materials provided with the distribution. +// +// Neither the name of the Electric Power Research Institute, Inc. (EPRI) nor the names of its contributors +// may be used to endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL EPRI BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// +// This software incorporates work covered by the following copyright and permission notice: +// +// TVA Code Library 4.0.4.3 - Tennessee Valley Authority, tvainfo@tva.gov +// No copyright is claimed pursuant to 17 USC 105. All Other Rights Reserved. +// +// Licensed under TVA Custom License based on NASA Open Source Agreement (TVA Custom NOSA); +// you may not use TVA Code Library except in compliance with the TVA Custom NOSA. You may +// obtain a copy of the TVA Custom NOSA at http://tvacodelibrary.codeplex.com/license. +// +// TVA Code Library is provided by the copyright holders and contributors "as is" and any express +// or implied warranties, including, but not limited to, the implied warranties of merchantability +// and fitness for a particular purpose are disclaimed. +// +//********************************************************************************************************************* +// +// Code Modification History: +// ------------------------------------------------------------------------------------------------------------------- +// 05/23/2012 - J. Ritchie Carroll, Grid Protection Alliance +// Generated original version of source code. +// +//********************************************************************************************************************* + +namespace FaultAlgorithms +{ + /// + /// Represents a set of single phase power time-domain data. + /// + public class MeasurementData + { + #region [ Members ] + + // Fields + + /// + /// Array of times in ticks (100 nanosecond intervals). + /// + public long[] Times; + + /// + /// Array of measured values. + /// + public double[] Measurements; + + /// + /// The number of measured samples per cycle of data. + /// + public int SampleRate; + + #endregion + } +} \ No newline at end of file diff --git a/src/Libraries/FaultAlgorithms/MeasurementDataSet.cs b/src/Libraries/FaultAlgorithms/MeasurementDataSet.cs new file mode 100644 index 00000000..1a6c0fb0 --- /dev/null +++ b/src/Libraries/FaultAlgorithms/MeasurementDataSet.cs @@ -0,0 +1,272 @@ +//********************************************************************************************************************* +// MeasurementDataSet.cs +// Version 1.1 and subsequent releases +// +// Copyright 2013, 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 Eclipse Public License -v 1.0 (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/eclipse-1.0.php +// +// 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. +// +// -------------------------------------------------------------------------------------------------------------------- +// +// Version 1.0 +// +// Copyright 2012 ELECTRIC POWER RESEARCH INSTITUTE, INC. All rights reserved. +// +// openFLE ("this software") is licensed under BSD 3-Clause license. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// Redistributions of source code must retain the above copyright notice, this list of conditions and +// the following disclaimer. +// +// Redistributions in binary form must reproduce the above copyright notice, this list of conditions and +// the following disclaimer in the documentation and/or other materials provided with the distribution. +// +// Neither the name of the Electric Power Research Institute, Inc. (EPRI) nor the names of its contributors +// may be used to endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL EPRI BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// +// This software incorporates work covered by the following copyright and permission notice: +// +// TVA Code Library 4.0.4.3 - Tennessee Valley Authority, tvainfo@tva.gov +// No copyright is claimed pursuant to 17 USC 105. All Other Rights Reserved. +// +// Licensed under TVA Custom License based on NASA Open Source Agreement (TVA Custom NOSA); +// you may not use TVA Code Library except in compliance with the TVA Custom NOSA. You may +// obtain a copy of the TVA Custom NOSA at http://tvacodelibrary.codeplex.com/license. +// +// TVA Code Library is provided by the copyright holders and contributors "as is" and any express +// or implied warranties, including, but not limited to, the implied warranties of merchantability +// and fitness for a particular purpose are disclaimed. +// +//********************************************************************************************************************* +// +// Code Modification History: +// ------------------------------------------------------------------------------------------------------------------- +// 05/23/2012 - J. Ritchie Carroll, Grid Protection Alliance +// Generated original version of source code. +// +//********************************************************************************************************************* + +using Gemstone; + +namespace FaultAlgorithms +{ + /// + /// Represents a set of 3-phase line-to-neutral and line-to-line time-domain power data. + /// + public class MeasurementDataSet + { + #region [ Members ] + + // Constants + + private const string DateTimeFormat = "yyyy-MM-dd HH:mm:ss.ffffff"; + + // Fields + + /// + /// Line-to-neutral A-phase data. + /// + public MeasurementData AN; + + /// + /// Line-to-neutral B-phase data. + /// + public MeasurementData BN; + + /// + /// Line-to-neutral C-phase data. + /// + public MeasurementData CN; + + #endregion + + #region [ Constructors ] + + /// + /// Creates a new . + /// + public MeasurementDataSet() + { + AN = new MeasurementData(); + BN = new MeasurementData(); + CN = new MeasurementData(); + } + + #endregion + + #region [ Methods ] + + /// + /// Uses system frequency to calculate the sample rate for each set + /// of in this measurement data set. + /// + /// The frequency of the measured system, in Hz. + public void CalculateSampleRates(double frequency) + { + CalculateSampleRate(frequency, AN); + CalculateSampleRate(frequency, BN); + CalculateSampleRate(frequency, CN); + } + + /// + /// Explicitly sets the sample rate for each set of + /// in this measurement data set. + /// + /// The sample rate. + public void SetSampleRate(int sampleRate) + { + AN.SampleRate = sampleRate; + BN.SampleRate = sampleRate; + CN.SampleRate = sampleRate; + } + + /// + /// Writes all voltage measurement data to a CSV file. + /// + /// Export file name. + public void ExportVoltageDataToCSV(string fileName) + { + const string Header = "Time,AN,BN,CN,AB,BC,CA"; + + using (FileStream fileStream = File.OpenWrite(fileName)) + { + using (TextWriter fileWriter = new StreamWriter(fileStream)) + { + // Write the CSV header to the file + fileWriter.WriteLine(Header); + + // Write the data to the file + for (int i = 0; i < AN.Times.Length; i++) + { + string time = new DateTime(AN.Times[i]).ToString(DateTimeFormat); + + double an = AN.Measurements[i]; + double bn = BN.Measurements[i]; + double cn = CN.Measurements[i]; + + fileWriter.Write("{0},{1},{2},{3},", time, an, bn, cn); + fileWriter.WriteLine("{0},{1},{2}", an - bn, bn - cn, cn - an); + } + } + } + } + + /// + /// Writes all current measurement data to a CSV file. + /// + /// Export file name. + public void ExportCurrentDataToCSV(string fileName) + { + const string Header = "Time,AN,BN,CN"; + + using (FileStream fileStream = File.OpenWrite(fileName)) + { + using (TextWriter fileWriter = new StreamWriter(fileStream)) + { + // Write the CSV header to the file + fileWriter.WriteLine(Header); + + // Write the data to the file + for (int i = 0; i < AN.Times.Length; i++) + { + string time = new DateTime(AN.Times[i]).ToString(DateTimeFormat); + + double an = AN.Measurements[i]; + double bn = BN.Measurements[i]; + double cn = CN.Measurements[i]; + + fileWriter.WriteLine("{0},{1},{2},{3}", time, an, bn, cn); + } + } + } + } + + private void CalculateSampleRate(double frequency, MeasurementData measurementData) + { + long[] times; + long startTicks; + long endTicks; + double cycles; + + // Get the collection of measurement timestamps + times = measurementData.Times; + + // Determine the start and end time of the data set + startTicks = times[0]; + endTicks = times[times.Length - 1]; + + // Determine the number of cycles in the file, + // based on the system frequency + cycles = frequency * Ticks.ToSeconds(endTicks - startTicks); + + // Calculate the number of samples per cycle + measurementData.SampleRate = (int)Math.Round(times.Length / cycles); + } + + #endregion + + #region [ Static ] + + // Static Methods + + /// + /// Writes all measurement data to a CSV file. + /// + /// Export file name. + /// The voltage measurement data to be written to the file. + /// The current measurement data to be written to the file. + public static void ExportToCSV(string fileName, MeasurementDataSet voltageData, MeasurementDataSet currentData) + { + const string Header = "Time,AN V,BN V,CN V,AB V,BC V,CA V,AN I,BN I,CN I"; + + using (FileStream fileStream = File.Create(fileName)) + { + using (TextWriter fileWriter = new StreamWriter(fileStream)) + { + // Write the CSV header to the file + fileWriter.WriteLine(Header); + + // Write the data to the file + for (int i = 0; i < voltageData.AN.Times.Length; i++) + { + string time = new DateTime(voltageData.AN.Times[i]).ToString(DateTimeFormat); + + double vAN = voltageData.AN.Measurements[i]; + double vBN = voltageData.BN.Measurements[i]; + double vCN = voltageData.CN.Measurements[i]; + + double iAN = currentData.AN.Measurements[i]; + double iBN = currentData.BN.Measurements[i]; + double iCN = currentData.CN.Measurements[i]; + + fileWriter.Write("{0},{1},{2},{3},", time, vAN, vBN, vCN); + fileWriter.Write("{0},{1},{2},", vAN - vBN, vBN - vCN, vCN - vAN); + fileWriter.WriteLine("{0},{1},{2}", iAN, iBN, iCN); + } + } + } + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Libraries/FaultData/DataAnalysis/CycleDataGroup.cs b/src/Libraries/FaultData/DataAnalysis/CycleDataGroup.cs new file mode 100644 index 00000000..3129dd76 --- /dev/null +++ b/src/Libraries/FaultData/DataAnalysis/CycleDataGroup.cs @@ -0,0 +1,115 @@ +//****************************************************************************************************** +// CycleDataGroup.cs - Gbtc +// +// Copyright © 2014, 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 Eclipse Public License -v 1.0 (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/eclipse-1.0.php +// +// 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: +// ---------------------------------------------------------------------------------------------------- +// 08/29/2014 - Stephen C. Wills +// Generated original version of source code. +// +//****************************************************************************************************** + +using openXDA.Model; + +namespace FaultData.DataAnalysis +{ + public class CycleDataGroup + { + #region [ Members ] + + // Constants + private const int RMSIndex = 0; + private const int PhaseIndex = 1; + private const int PeakIndex = 2; + private const int ErrorIndex = 3; + private Asset m_asset; + // Fields + private DataGroup m_dataGroup; + + #endregion + + #region [ Constructors ] + + public CycleDataGroup(DataGroup dataGroup, Asset asset) + { + m_dataGroup = dataGroup; + m_asset = asset; + } + + #endregion + + #region [ Properties ] + + public DataSeries RMS + { + get + { + return m_dataGroup[RMSIndex]; + } + } + + public DataSeries Phase + { + get + { + return m_dataGroup[PhaseIndex]; + } + } + + public DataSeries Peak + { + get + { + return m_dataGroup[PeakIndex]; + } + } + + public DataSeries Error + { + get + { + return m_dataGroup[ErrorIndex]; + } + } + + public Asset Asset + { + get + { + return m_asset; + } + } + #endregion + + #region [ Methods ] + + public DataGroup ToDataGroup() + { + return m_dataGroup; + } + + public CycleDataGroup ToSubGroup(int startIndex, int endIndex) + { + return new CycleDataGroup(m_dataGroup.ToSubGroup(startIndex, endIndex), m_asset); + } + + public CycleDataGroup ToSubGroup(DateTime startTime, DateTime endTime) + { + return new CycleDataGroup(m_dataGroup.ToSubGroup(startTime, endTime), m_asset); + } + + #endregion + } +} diff --git a/src/Libraries/FaultData/DataAnalysis/DataGroup.cs b/src/Libraries/FaultData/DataAnalysis/DataGroup.cs new file mode 100644 index 00000000..20d91b75 --- /dev/null +++ b/src/Libraries/FaultData/DataAnalysis/DataGroup.cs @@ -0,0 +1,662 @@ +//****************************************************************************************************** +// DataGroup.cs - Gbtc +// +// Copyright © 2014, 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 Eclipse Public License -v 1.0 (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/eclipse-1.0.php +// +// 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/2014 - Stephen C. Wills +// Generated original version of source code. +// 12/23/2019 - C. Lackner +// Adjusted to read data from blob for each dataseries. +// +//****************************************************************************************************** + +using System.Data; +using Gemstone; +using Gemstone.Data.Model; +using Ionic.Zlib; +using Microsoft.Data.SqlClient; +using openXDA.Model; + +namespace FaultData.DataAnalysis +{ + public enum DataClassification + { + Trend, + Event, + FastRMS, + Unknown + } + + public class DataGroup + { + #region [ Members ] + + // Constants + + /// + /// Maximum sample rate, in samples per minute, of data classified as . + /// + public const double TrendThreshold = 1.0D; + + // Fields + private Asset m_asset; + private DateTime m_startTime; + private DateTime m_endTime; + private int m_samples; + + private List m_dataSeries; + private List m_disturbances; + private DataClassification m_classification; + + #endregion + + #region [ Constructors ] + + /// + /// Creates a new instance of the class. + /// + public DataGroup() + { + m_dataSeries = new List(); + m_disturbances = new List(); + m_classification = DataClassification.Unknown; + m_asset = null; + } + + /// + /// Creates a new instance of the class. + /// + /// Asset associated with this datagroup + public DataGroup(Asset asset) + { + m_dataSeries = new List(); + m_disturbances = new List(); + m_classification = DataClassification.Unknown; + m_asset = asset; + } + + /// + /// Creates a new instance of the class. + /// + /// Collection of data series to be added to the data group. + public DataGroup(IEnumerable dataSeries) + : this() + { + foreach (DataSeries series in dataSeries) + Add(series); + } + + /// + /// Creates a new instance of the class. + /// + /// Collection of data series to be added to the data group. + /// Asset associated with this datagroup + public DataGroup(IEnumerable dataSeries, Asset asset) + : this(asset) + { + foreach (DataSeries series in dataSeries) + Add(series); + } + + #endregion + + #region [ Properties ] + + /// + /// Gets the line from which measurements were taken to create the group of data. + /// + public Asset Asset + { + get + { + return m_asset; + } + } + + /// + /// Gets the start time of the group of data. + /// + public DateTime StartTime + { + get + { + return m_startTime; + } + } + + /// + /// Gets the end time of the group of data. + /// + public DateTime EndTime + { + get + { + return m_endTime; + } + } + + /// + /// Gets the number of samples in each series. + /// + public int Samples + { + get + { + return m_samples; + } + } + + /// + /// Gets the sample rate, in samples per second, + /// of the data series in this data group. + /// + public double SamplesPerSecond + { + get + { + if (!m_dataSeries.Any()) + return double.NaN; + + return m_dataSeries[0].SampleRate; + } + } + + /// + /// Gets the duration, in seconds, + /// of the data series in this data group. + /// + public double Duration + { + get + { + if (!m_dataSeries.Any()) + return double.NaN; + + return m_dataSeries[0].Duration; + } + } + + /// + /// Gets the sample rate, in samples per hour, + /// of the data series in this data group. + /// + public double SamplesPerHour + { + get + { + return (m_samples - 1) / (m_endTime - m_startTime).TotalHours; + } + } + + /// + /// Gets flag that indicates whether the data series + /// in this data group are marked as trend channels. + /// + public bool Trend => + m_dataSeries.Any(dataSeries => dataSeries.SeriesInfo?.Channel.Trend == true); + + /// + /// Gets the channels contained in this data group. + /// + public IReadOnlyList DataSeries + { + get + { + return m_dataSeries.AsReadOnly(); + } + } + + /// + /// Gets the disturbances contained in this data group. + /// + public IReadOnlyList Disturbances + { + get + { + return m_disturbances.AsReadOnly(); + } + } + + /// + /// Gets the classification of this group of data as of the last call to . + /// + public DataClassification Classification + { + get + { + if (m_classification == DataClassification.Unknown) + Classify(); + + return m_classification; + } + } + + public DataSeries this[int index] + { + get + { + return m_dataSeries[index]; + } + } + + #endregion + + #region [ Methods ] + + /// + /// Adds a channel to the group of data. + /// + /// The channel to be added to the group. + /// + /// True if the channel was successfully added. False if the channel was excluded + /// because the channel does not match the other channels already in the data group. + /// + public bool Add(DataSeries dataSeries) + { + Asset asset; + DateTime startTime; + DateTime endTime; + int samples; + bool trend; + + // Unable to add null data series + if ((object)dataSeries == null) + return false; + + // Data series without data is irrelevant to data grouping + if (!dataSeries.DataPoints.Any()) + return false; + + // Do not add the same data series twice + if (m_dataSeries.Contains(dataSeries)) + return false; + + // Get information about the line this data is associated with + if ((object)dataSeries.SeriesInfo != null) + asset = dataSeries.SeriesInfo.Channel.Asset; + else + asset = null; + + // Get the start time, end time, number of samples, and + // trend flag for the data series passed into this function + startTime = dataSeries.DataPoints[0].Time; + endTime = dataSeries.DataPoints[dataSeries.DataPoints.Count - 1].Time; + samples = dataSeries.DataPoints.Count; + trend = dataSeries.SeriesInfo?.Channel.Trend == true; + + // If there are any disturbances in this data group that do not overlap + // with the data series, do not include the data series in the data group + if (m_disturbances.Select(disturbance => disturbance.ToRange()).Any(range => range.Start > endTime || range.End < startTime)) + return false; + + // If there are any disturbances associated with the data in this group and the data + // to be added is trending data, do not include the trending data in the data group + if (m_disturbances.Any() && CalculateSamplesPerMinute(startTime, endTime, samples) <= TrendThreshold) + return false; + + // At this point, if there is no existing data in the data + // group, add the data as the first series in the data group + if (m_dataSeries.Count == 0) + { + if (m_asset == null) + { + m_asset = asset; + } + m_startTime = startTime; + m_endTime = endTime; + m_samples = samples; + + m_dataSeries.Add(dataSeries); + m_classification = DataClassification.Unknown; + + return true; + } + + // If the data being added matches the parameters for this data group, add the data to the data group + // Note that it does not have to match Asset + if (startTime == m_startTime && endTime == m_endTime && samples == m_samples && trend == Trend) + { + m_dataSeries.Add(dataSeries); + return true; + } + + return false; + } + + /// + /// Adds a disturbance to the group of data. + /// + /// The disturbance to be added to the group. + /// True if the disturbance was successfully added. + public bool Add(ReportedDisturbance disturbance) + { + // Unable to add null disturbance + if ((object)disturbance == null) + return false; + + // Do not add the same disturbance twice + if (m_disturbances.Contains(disturbance)) + return false; + + // If the data in this data group is trending data, + // do not add the disturbance to the data group + if (Classification == DataClassification.Trend) + return false; + + // Get the start time and end time of the disturbance. + DateTime startTime = disturbance.Time; + DateTime endTime = startTime + disturbance.Duration; + + // If there are no data series and no other disturbances, + // make this the first piece of data to be added to the data group + if (!m_dataSeries.Any() && !m_disturbances.Any()) + { + m_startTime = startTime; + m_endTime = endTime; + m_disturbances.Add(disturbance); + m_classification = DataClassification.Event; + return true; + } + + // If the disturbance overlaps with + // this data group, add the disturbance + if (startTime <= m_endTime && m_startTime <= endTime) + { + // If the only data in the data group is disturbances, + // adjust the start time and end time + if (!m_dataSeries.Any() && startTime < m_startTime) + m_startTime = startTime; + + if (!m_dataSeries.Any() && endTime > m_endTime) + m_endTime = endTime; + + m_disturbances.Add(disturbance); + return true; + } + + return false; + } + + /// + /// Removes a channel from the data group. + /// + /// The channel to be removed from the data group. + /// True if the channel existed in the group and was removed; false otherwise. + public bool Remove(DataSeries dataSeries) + { + if (m_dataSeries.Remove(dataSeries)) + { + m_classification = m_disturbances.Any() + ? DataClassification.Event + : DataClassification.Unknown; + + return true; + } + + return false; + } + + /// + /// Removes a disturbance from the data group. + /// + /// THe disturbance to be removed from the data group. + /// True if the disturbance existed in the group and was removed; false otherwise. + public bool Remove(ReportedDisturbance disturbance) + { + if (m_disturbances.Remove(disturbance)) + { + if (!m_disturbances.Any()) + m_classification = DataClassification.Unknown; + + return true; + } + + return false; + } + + public DataGroup ToSubGroup(int startIndex, int endIndex) + { + DataGroup subGroup = new DataGroup(); + + foreach (DataSeries dataSeries in m_dataSeries) + subGroup.Add(dataSeries.ToSubSeries(startIndex, endIndex)); + + return subGroup; + } + + public DataGroup ToSubGroup(DateTime startTime, DateTime endTime) + { + DataGroup subGroup = new DataGroup(); + + foreach (DataSeries dataSeries in m_dataSeries) + subGroup.Add(dataSeries.ToSubSeries(startTime, endTime)); + + return subGroup; + } + + // Overwrite To Data to save Data into ChannelBlob instead of File Blob + // This needs to be done to avoid data duplication + public Dictionary ToData() + { + Dictionary result = new Dictionary(); + + var timeSeries = m_dataSeries[0].DataPoints + .Select(dataPoint => new { Time = dataPoint.Time.Ticks, Compressed = false }) + .ToList(); + + for (int i = 1; i < timeSeries.Count; i++) + { + long previousTimestamp = m_dataSeries[0][i - 1].Time.Ticks; + long timestamp = timeSeries[i].Time; + long diff = timestamp - previousTimestamp; + + if (diff >= 0 && diff <= ushort.MaxValue) + timeSeries[i] = new { Time = diff, Compressed = true }; + + + } + + int timeSeriesByteLength = timeSeries.Sum(obj => obj.Compressed ? sizeof(ushort) : sizeof(int) + sizeof(long)); + int dataSeriesByteLength = sizeof(int) + (2 * sizeof(double)) + (m_samples * sizeof(ushort)); + int totalByteLength = sizeof(int) + timeSeriesByteLength + dataSeriesByteLength; + + foreach (DataSeries dataSeries in m_dataSeries) + { + byte[] data = new byte[totalByteLength]; + int offset = 0; + + offset += LittleEndian.CopyBytes(m_samples, data, offset); + + List uncompressedIndexes = timeSeries + .Select((obj, Index) => new { obj.Compressed, Index }) + .Where(obj => !obj.Compressed) + .Select(obj => obj.Index) + .ToList(); + + for (int i = 0; i < uncompressedIndexes.Count; i++) + { + int index = uncompressedIndexes[i]; + int nextIndex = (i + 1 < uncompressedIndexes.Count) ? uncompressedIndexes[i + 1] : timeSeries.Count; + + offset += LittleEndian.CopyBytes(nextIndex - index, data, offset); + offset += LittleEndian.CopyBytes(timeSeries[index].Time, data, offset); + + for (int j = index + 1; j < nextIndex; j++) + offset += LittleEndian.CopyBytes((ushort)timeSeries[j].Time, data, offset); + } + + + if (dataSeries.Calculated) continue; + + const ushort NaNValue = ushort.MaxValue; + const ushort MaxCompressedValue = ushort.MaxValue - 1; + int seriesID = dataSeries.SeriesInfo?.ID ?? 0; + double range = dataSeries.Maximum - dataSeries.Minimum; + double decompressionOffset = dataSeries.Minimum; + double decompressionScale = range / MaxCompressedValue; + double compressionScale = (decompressionScale != 0.0D) ? 1.0D / decompressionScale : 0.0D; + + offset += LittleEndian.CopyBytes(seriesID, data, offset); + offset += LittleEndian.CopyBytes(decompressionOffset, data, offset); + offset += LittleEndian.CopyBytes(decompressionScale, data, offset); + + foreach (DataPoint dataPoint in dataSeries.DataPoints) + { + ushort compressedValue = (ushort)Math.Round((dataPoint.Value - decompressionOffset) * compressionScale); + + if (compressedValue == NaNValue) + compressedValue--; + + if (double.IsNaN(dataPoint.Value)) + compressedValue = NaNValue; + + offset += LittleEndian.CopyBytes(compressedValue, data, offset); + } + byte[] returnArray = GZipStream.CompressBuffer(data); + returnArray[0] = 0x44; + returnArray[1] = 0x33; + + int dataSeriesID = dataSeries.SeriesInfo?.ID ?? 0; + result.Add(dataSeriesID, returnArray); + } + + return result ; + } + + public void FromData(List data) + { + FromData(null, data); + } + + public void FromData(Meter meter, List dataList) + { + var decompressed = dataList.SelectMany(d => ChannelData.Decompress(d)); + + foreach (Tuple> tuple in decompressed) + { + DataSeries dataSeries = new DataSeries(); + + if (tuple.Item1 > 0 && !(meter is null)) + dataSeries.SeriesInfo = meter.Series.FirstOrDefault(s => s.ID == tuple.Item1); + + dataSeries.DataPoints = tuple.Item2; + + Add(dataSeries); + } + } + + private void Classify() + { + if (IsTrend()) + m_classification = DataClassification.Trend; + else if (IsEvent()) + m_classification = DataClassification.Event; + else if (IsFastRMS()) + m_classification = DataClassification.FastRMS; + else + m_classification = DataClassification.Unknown; + } + + private bool IsTrend() + { + if (!m_dataSeries.Any() || m_disturbances.Any()) + return false; + + double samplesPerMinute = CalculateSamplesPerMinute(m_startTime, m_endTime, m_samples); + return samplesPerMinute <= TrendThreshold; + } + + private bool IsEvent() + { + if (m_disturbances.Any()) + return true; + + return m_dataSeries + .Where(dataSeries => (object)dataSeries.SeriesInfo != null) + .Where(IsInstantaneous) + .Where(dataSeries => dataSeries.SeriesInfo.Channel.MeasurementType.Name != "Digital") + .Any(); + } + + private bool IsInstantaneous(DataSeries dataSeries) + { + string characteristicName = dataSeries.SeriesInfo.Channel.MeasurementCharacteristic.Name; + string seriesTypeName = dataSeries.SeriesInfo.SeriesType.Name; + + return (characteristicName == "Instantaneous") && + (seriesTypeName == "Values" || seriesTypeName == "Instantaneous"); + } + + private bool IsFastRMS() + { + return m_dataSeries + .Where(dataSeries => (object)dataSeries.SeriesInfo != null) + .Where(IsRMS) + .Any(); + } + + private bool IsRMS(DataSeries dataSeries) + { + string characteristicName = dataSeries.SeriesInfo.Channel.MeasurementCharacteristic.Name; + string seriesTypeName = dataSeries.SeriesInfo.SeriesType.Name; + + return (characteristicName == "RMS") && + (seriesTypeName == "Values" || seriesTypeName == "Instantaneous"); + } + + private double CalculateSamplesPerMinute(DateTime startTime, DateTime endTime, int samples) + { + return (samples - 1) / (endTime - startTime).TotalMinutes; + } + + #endregion + } + + public static partial class TableOperationsExtensions + { + public static Event GetEvent(this TableOperations eventTable, FileGroup fileGroup, DataGroup dataGroup) + { + int fileGroupID = fileGroup.ID; + int assetID = dataGroup.Asset.ID; + DateTime startTime = dataGroup.StartTime; + DateTime endTime = dataGroup.EndTime; + int samples = dataGroup.Samples; + + IDbDataParameter startTimeParameter = new SqlParameter() + { + ParameterName = nameof(dataGroup.StartTime), + DbType = DbType.DateTime2, + Value = startTime + }; + + IDbDataParameter endTimeParameter = new SqlParameter() + { + ParameterName = nameof(dataGroup.EndTime), + DbType = DbType.DateTime2, + Value = endTime + }; + + RecordRestriction recordRestriction = + new RecordRestriction("FileGroupID = {0}", fileGroupID) & + new RecordRestriction("AssetID = {0}", assetID) & + new RecordRestriction("StartTime = {0}", startTimeParameter) & + new RecordRestriction("EndTime = {0}", endTimeParameter) & + new RecordRestriction("Samples = {0}", samples); + + return eventTable.QueryRecord(recordRestriction); + } + } +} diff --git a/src/Libraries/FaultData/DataAnalysis/DataSeries.cs b/src/Libraries/FaultData/DataAnalysis/DataSeries.cs new file mode 100644 index 00000000..cbc7d9d1 --- /dev/null +++ b/src/Libraries/FaultData/DataAnalysis/DataSeries.cs @@ -0,0 +1,588 @@ +//****************************************************************************************************** +// DataSeries.cs - Gbtc +// +// Copyright © 2014, 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 Eclipse Public License -v 1.0 (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/eclipse-1.0.php +// +// 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/15/2014 - Stephen C. Wills +// Generated original version of source code. +// 07/09/2019 - Christoph Lackner +// Added length property and Threshhold method. +// +//****************************************************************************************************** + +using Gemstone; +using Gemstone.Numeric.Interpolation; +using Ionic.Zlib; +using openXDA.Model; + +namespace FaultData.DataAnalysis +{ + /// + /// Represents a series of data points. + /// + public class DataSeries + { + #region [ Members ] + + // Fields + private Series m_seriesInfo; + private List m_dataPoints; + + private double? m_duration; + private double? m_sampleRate; + private double? m_minimum; + private double? m_maximum; + private double? m_average; + + #endregion + + #region [ Constructors ] + + public DataSeries() + { + m_dataPoints = new List(); + } + + #endregion + + #region [ Properties ] + + /// + /// Gets or sets the configuration information + /// that defines the data in this series. + /// + public Series SeriesInfo + { + get + { + return m_seriesInfo; + } + set + { + m_seriesInfo = value; + } + } + + /// + /// Gets or sets the data points that make up the series. + /// + public List DataPoints + { + get + { + return m_dataPoints; + } + set + { + m_dataPoints = value ?? new List(); + m_duration = null; + m_sampleRate = null; + m_minimum = null; + m_maximum = null; + m_average = null; + } + } + + /// + /// Gets the duration of the series, in seconds. + /// + public double Duration + { + get + { + if (m_duration.HasValue) + return m_duration.Value; + + if (!m_dataPoints.Any()) + return double.NaN; + + m_duration = m_dataPoints.Last().Time.Subtract(m_dataPoints.First().Time).TotalSeconds; + + return m_duration.Value; + } + } + + /// + /// Gets the Start Time of the dataseries. + /// + public DateTime StartTime + { + get + { + if (!m_dataPoints.Any()) + return DateTime.MinValue; + return m_dataPoints.First().Time; + } + } + + /// + /// Gets the End Time of the dataseries. + /// + public DateTime EndTime + { + get + { + if (!m_dataPoints.Any()) + return DateTime.MinValue; + return m_dataPoints.Last().Time; + } + } + + + /// + /// Gets the Length of the series, in datapoints. + /// + public int Length + { + get + { + + if (!m_dataPoints.Any()) + return 0; + + return m_dataPoints.Count; + } + } + + /// + /// Gets the sample rate of the series, in samples per second. + /// + public double SampleRate + { + get + { + if (m_sampleRate.HasValue) + return m_sampleRate.Value; + + if (!m_dataPoints.Any()) + return double.NaN; + + int index = (m_dataPoints.Count > 128) ? 128 : m_dataPoints.Count - 1; + + m_sampleRate = (Duration != 0.0D) + ? index / (m_dataPoints[index].Time - m_dataPoints[0].Time).TotalSeconds + : double.NaN; + + return m_sampleRate.Value; + } + } + + /// + /// Gets the maximum value in the series. + /// + public double Maximum + { + get + { + if (m_maximum.HasValue) + return m_maximum.Value; + + if (!m_dataPoints.Any(dataPoint => !double.IsNaN(dataPoint.Value))) + return double.NaN; + + m_maximum = m_dataPoints + .Select(point => point.Value) + .Where(value => !double.IsNaN(value)) + .Max(); + + return m_maximum.Value; + } + } + + /// + /// Gets the minimum value in the series. + /// + public double Minimum + { + get + { + if (m_minimum.HasValue) + return m_minimum.Value; + + if (!m_dataPoints.Any(dataPoint => !double.IsNaN(dataPoint.Value))) + return double.NaN; + + m_minimum = m_dataPoints + .Select(dataPoint => dataPoint.Value) + .Where(value => !double.IsNaN(value)) + .Min(); + + return m_minimum.Value; + } + } + + /// + /// Gets the average value in the series. + /// + public double Average + { + get + { + if (m_average.HasValue) + return m_average.Value; + + if (!m_dataPoints.Any(dataPoint => !double.IsNaN(dataPoint.Value))) + return double.NaN; + + m_average = m_dataPoints + .Select(dataPoint => dataPoint.Value) + .Where(value => !double.IsNaN(value)) + .Average(); + + return m_average.Value; + } + } + + public DataPoint this[int index] + { + get + { + return m_dataPoints[index]; + } + } + + /// + /// Flag that tells the DataGroup .ToData function not to add to data blob because this value is calculated. + /// + public bool Calculated { get; set; } = false; + + #endregion + + #region [ Methods ] + + + /// + /// Creates a new that is a subset. + /// + /// The index at which the new DataSeries starts. + /// The index at which the new DataSeries ends. + /// a new + public DataSeries ToSubSeries(int startIndex, int endIndex) + { + DataSeries subSeries = new DataSeries(); + int count; + + subSeries.SeriesInfo = m_seriesInfo; + + if (startIndex < 0) + startIndex = 0; + + if (endIndex >= m_dataPoints.Count) + endIndex = m_dataPoints.Count - 1; + + count = endIndex - startIndex + 1; + + if (count > 0) + subSeries.DataPoints = m_dataPoints.Skip(startIndex).Take(count).ToList(); + + return subSeries; + } + + /// + /// Creates a new that is a subset. + /// + /// The index at which the new DataSeries starts. + /// a new + public DataSeries ToSubSeries(int startSeries) => ToSubSeries(startSeries, this.Length); + + public DataSeries ToSubSeries(DateTime startTime, DateTime endTime) + { + DataSeries subSeries = new DataSeries(); + + subSeries.SeriesInfo = m_seriesInfo; + + subSeries.DataPoints = m_dataPoints + .SkipWhile(point => point.Time < startTime) + .TakeWhile(point => point.Time <= endTime) + .ToList(); + + return subSeries; + } + + /// + /// Creates a new that is a subset. + /// + /// The time at which the new DataSeries starts. + /// a new + public DataSeries ToSubSeries(DateTime startTime) => ToSubSeries(startTime, this[this.Length - 1].Time); + + public DataSeries Shift(TimeSpan timeShift) + { + DataSeries shifted = new DataSeries(); + + shifted.SeriesInfo = m_seriesInfo; + + shifted.DataPoints = m_dataPoints + .Select(dataPoint => dataPoint.Shift(timeShift)) + .ToList(); + + return shifted; + } + + public DataSeries Negate() + { + DataSeries negatedDataSeries = new DataSeries(); + + negatedDataSeries.DataPoints = m_dataPoints + .Select(point => point.Negate()) + .ToList(); + + return negatedDataSeries; + } + + public DataSeries Add(DataSeries operand) + { + DataSeries sum = new DataSeries(); + + if (m_dataPoints.Count != operand.DataPoints.Count) + throw new InvalidOperationException("Cannot take the sum of series with mismatched time values"); + + sum.DataPoints = m_dataPoints + .Zip(operand.DataPoints, Add) + .ToList(); + + return sum; + } + + public DataSeries Subtract(DataSeries operand) + { + return Add(operand.Negate()); + } + + public DataSeries Multiply(double value) + { + DataSeries result = new DataSeries(); + + result.DataPoints = m_dataPoints + .Select(point => point.Multiply(value)) + .ToList(); + + return result; + } + + public DataSeries Copy() + { + return Multiply(1.0D); + } + + public int Threshhold(double value) + { + return m_dataPoints.FindIndex(x => x.LargerThan(value)); + } + + /// + /// Downsamples the current DataSeries to requested sample count, if the + /// + /// + public void Downsample(int maxSampleCount) + { + // don't actually downsample, if it doesn't need it. + if (DataPoints.Count <= maxSampleCount) return; + + DateTime epoch = new DateTime(1970, 1, 1); + double startTime = StartTime.Subtract(epoch).TotalMilliseconds; + double endTime = EndTime.Subtract(epoch).TotalMilliseconds; + List data = new List(); + + // milliseconds per returned sampled size + int step = (int)(Duration*1000) / maxSampleCount; + if (step < 1) + step = 1; + + int index = 0; + for (double n = startTime * 1000; n <= endTime * 1000; n += 2 * step) + { + DataPoint min = null; + DataPoint max = null; + + while (index < DataPoints.Count() && DataPoints[index].Time.Subtract(epoch).TotalMilliseconds * 1000 < n + 2 * step) + { + if (min == null || min.Value > DataPoints[index].Value) + min = DataPoints[index]; + + if (max == null || max.Value <= DataPoints[index].Value) + max = DataPoints[index]; + + ++index; + } + + if (min != null) + { + if (min.Time < max.Time) + { + data.Add(min); + data.Add(max); + } + else if (min.Time > max.Time) + { + data.Add(max); + data.Add(min); + } + else + { + data.Add(min); + } + } + } + DataPoints = data; + } + + /// + /// Upsamples the current DataSeries to requested sample count, assuming the requested rate is larger than the current + /// + /// + public void Upsample(int minSamplesPerCycle, double systemFrequency) + { + // don't actually upsample, if it doesn't need it. + if (minSamplesPerCycle <= 0) + return; + TimeSpan duration = EndTime - StartTime; + double cycles = duration.TotalSeconds * systemFrequency; + int minSampleCount = (int)Math.Round(cycles * minSamplesPerCycle); + if (minSampleCount <= DataPoints.Count) + return; + + // Creating spline fit to perform upsampling + List xValues = DataPoints + .Select(point => (double) point.Time.Subtract(StartTime).Ticks) + .ToList(); + List yValues= DataPoints + .Select(point => point.Value) + .ToList(); + SplineFit splineFit = SplineFit.ComputeCubicSplines(xValues, yValues); + + List data = Enumerable + .Range(0, minSampleCount) + .Select(sample => sample * duration.Ticks / minSampleCount) + .Select(sampleTicks => + new DataPoint() + { + Time = StartTime.AddTicks(sampleTicks), + Value = splineFit.CalculateY(sampleTicks) + } + ).ToList(); + + DataPoints = data; + } + + #endregion + + #region [ Static ] + + // Static Methods + + public static DataSeries Merge(IEnumerable dataSeriesList) + { + if (dataSeriesList == null) + throw new ArgumentNullException(nameof(dataSeriesList)); + + DataSeries mergedSeries = new DataSeries(); + DateTime lastTime = default(DateTime); + + IEnumerable dataPoints = dataSeriesList + .Where(dataSeries => dataSeries != null) + .Where(dataSeries => dataSeries.DataPoints.Count != 0) + .OrderBy(dataSeries => dataSeries[0].Time) + .SelectMany(series => series.DataPoints); + + foreach (DataPoint next in dataPoints) + { + if (mergedSeries.DataPoints.Count == 0 || next.Time > lastTime) + { + mergedSeries.DataPoints.Add(next); + lastTime = next.Time; + } + } + + return mergedSeries; + } + + private static DataPoint Add(DataPoint point1, DataPoint point2) + { + return point1.Add(point2); + } + + public static DataSeries FromData(Meter meter, byte[] data) + { + + if (data == null) + return null; + + // Restore the GZip header before uncompressing + data[0] = 0x1F; + data[1] = 0x8B; + + byte[] uncompressedData = GZipStream.UncompressBuffer(data); + int offset = 0; + + int samples = LittleEndian.ToInt32(uncompressedData, offset); + offset += sizeof(int); + + List times = new List(); + + while (times.Count < samples) + { + int timeValues = LittleEndian.ToInt32(uncompressedData, offset); + offset += sizeof(int); + + long currentValue = LittleEndian.ToInt64(uncompressedData, offset); + offset += sizeof(long); + times.Add(new DateTime(currentValue)); + + for (int i = 1; i < timeValues; i++) + { + currentValue += LittleEndian.ToUInt16(uncompressedData, offset); + offset += sizeof(ushort); + times.Add(new DateTime(currentValue)); + } + } + + DataSeries dataSeries = new DataSeries(); + int seriesID = LittleEndian.ToInt32(uncompressedData, offset); + offset += sizeof(int); + + if (seriesID > 0 && !(meter is null)) + dataSeries.SeriesInfo = meter.Series.FirstOrDefault(s => s.ID == seriesID); + + const ushort NaNValue = ushort.MaxValue; + double decompressionOffset = LittleEndian.ToDouble(uncompressedData, offset); + double decompressionScale = LittleEndian.ToDouble(uncompressedData, offset + sizeof(double)); + offset += 2 * sizeof(double); + + for (int i = 0; i < samples; i++) + { + ushort compressedValue = LittleEndian.ToUInt16(uncompressedData, offset); + offset += sizeof(ushort); + + double decompressedValue = decompressionScale * compressedValue + decompressionOffset; + + if (compressedValue == NaNValue) + decompressedValue = double.NaN; + + dataSeries.DataPoints.Add(new DataPoint() + { + Time = times[i], + Value = decompressedValue + }); + } + + return dataSeries; + + } + + #endregion + } +} diff --git a/src/Libraries/FaultData/DataAnalysis/ReportedDisturbance.cs b/src/Libraries/FaultData/DataAnalysis/ReportedDisturbance.cs new file mode 100644 index 00000000..a078eb5b --- /dev/null +++ b/src/Libraries/FaultData/DataAnalysis/ReportedDisturbance.cs @@ -0,0 +1,58 @@ +//****************************************************************************************************** +// ReportedDisturbance.cs - Gbtc +// +// Copyright © 2017, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 12/06/2017 - Stephen C. Wills +// Generated original version of source code. +// +//****************************************************************************************************** + +using Gemstone; +using Gemstone.PQDIF.Logical; + +namespace FaultData.DataAnalysis +{ + public class ReportedDisturbance + { + public ReportedDisturbance(Phase phase, DateTime time, double max, double min, double avg, TimeSpan duration, QuantityUnits units) + { + Phase = phase; + Time = time; + Maximum = max; + Minimum = min; + Average = avg; + Duration = duration; + Units = units; + } + + public Phase Phase { get; } + public DateTime Time { get; } + public double Maximum { get; } + public double Minimum { get; } + public double Average { get; } + public TimeSpan Duration { get; } + public QuantityUnits Units { get; } + + public ReportedDisturbance ShiftTimestampTo(DateTime shiftedTime) => + new ReportedDisturbance(Phase, shiftedTime, Maximum, Minimum, Average, Duration, Units); + + public Range ToRange() + { + return new Range(Time, Time + Duration); + } + } +} diff --git a/src/Libraries/FaultData/DataAnalysis/Transform.cs b/src/Libraries/FaultData/DataAnalysis/Transform.cs new file mode 100644 index 00000000..9e4aa28d --- /dev/null +++ b/src/Libraries/FaultData/DataAnalysis/Transform.cs @@ -0,0 +1,358 @@ +//****************************************************************************************************** +// Transform.cs - Gbtc +// +// Copyright © 2014, 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 Eclipse Public License -v 1.0 (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/eclipse-1.0.php +// +// 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: +// ---------------------------------------------------------------------------------------------------- +// 08/28/2014 - Stephen C. Wills +// Generated original version of source code. +// +//****************************************************************************************************** + +using Gemstone.Numeric.Analysis; +using openXDA.Model; + +namespace FaultData.DataAnalysis +{ + public static class Transform + { + public static DataGroup Combine(params DataGroup[] dataGroups) + { + DataGroup combination = new DataGroup(); + + foreach (DataGroup dataGroup in dataGroups) + { + foreach (DataSeries dataSeries in dataGroup.DataSeries) + combination.Add(dataSeries); + } + + return combination; + } + + public static VICycleDataGroup ToVICycleDataGroup(VIDataGroup dataGroup, double frequency, bool compress = false) + { + DataSeries[] cycleSeries = dataGroup.Data; + + return new VICycleDataGroup(cycleSeries + .Where(dataSeries => (object)dataSeries != null) + .Select(dataSeries => ToCycleDataGroup(dataSeries, frequency, compress)) + .ToList(), dataGroup.Asset); + } + + public static CycleDataGroup ToCycleDataGroup(DataSeries dataSeries, double frequency, bool compress=false) + { + if (dataSeries is null) + return null; + + DataSeries rmsSeries = new DataSeries(); + DataSeries phaseSeries = new DataSeries(); + DataSeries peakSeries = new DataSeries(); + DataSeries errorSeries = new DataSeries(); + + // Set series info to the source series info + rmsSeries.SeriesInfo = dataSeries.SeriesInfo; + phaseSeries.SeriesInfo = dataSeries.SeriesInfo; + peakSeries.SeriesInfo = dataSeries.SeriesInfo; + errorSeries.SeriesInfo = dataSeries.SeriesInfo; + + // Get samples per cycle of the data series based on the given frequency + int samplesPerCycle = CalculateSamplesPerCycle(dataSeries, frequency); + + //preinitialize size of SeriesInfo + int ncycleData = dataSeries.DataPoints.Count - samplesPerCycle + 1; + + if (ncycleData <= 0) + return null; + + rmsSeries.DataPoints.Capacity = ncycleData; + phaseSeries.DataPoints.Capacity = ncycleData; + peakSeries.DataPoints.Capacity = ncycleData; + errorSeries.DataPoints.Capacity = ncycleData; + + // Initialize arrays of y-values and t-values for calculating cycle data as necessary + double[] yValues = new double[samplesPerCycle]; + double[] tValues = new double[samplesPerCycle]; + + void CaptureCycle(int cycleIndex) + { + DateTime startTime = dataSeries.DataPoints[0].Time; + + for (int i = 0; i < samplesPerCycle; i++) + { + DateTime time = dataSeries.DataPoints[cycleIndex + i].Time; + double value = dataSeries.DataPoints[cycleIndex + i].Value; + tValues[i] = time.Subtract(startTime).TotalSeconds; + yValues[i] = value; + } + } + + // Obtain a list of time gaps in the data series + List gapIndexes = Enumerable.Range(0, dataSeries.DataPoints.Count - 1) + .Where(index => + { + DataPoint p1 = dataSeries[index]; + DataPoint p2 = dataSeries[index + 1]; + double cycleDiff = (p2.Time - p1.Time).TotalSeconds * frequency; + + // Detect gaps larger than a quarter cycle. + // Tolerance of 0.000062 calculated + // assuming 3.999 samples per cycle + return (cycleDiff > 0.250062); + }) + .ToList(); + + double sum = 0; + + if (dataSeries.DataPoints.Count >= samplesPerCycle) + { + CaptureCycle(0); + sum = yValues.Sum(y => y * y); + + DateTime cycleTime = dataSeries.DataPoints[0].Time; + SineWave sineFit = WaveFit.SineFit(yValues, tValues, frequency); + double phase = sineFit.Phase; + + double ComputeSineError() => tValues + .Select(sineFit.CalculateY) + .Zip(yValues, (estimate, value) => Math.Abs(estimate - value)) + .Sum(); + + double sineError = ComputeSineError(); + double previousSineError = sineError; + + rmsSeries.DataPoints.Add(new DataPoint() + { + Time = cycleTime, + Value = Math.Sqrt(sum / samplesPerCycle) + }); + + phaseSeries.DataPoints.Add(new DataPoint() + { + Time = cycleTime, + Value = phase + }); + + peakSeries.DataPoints.Add(new DataPoint() + { + Time = cycleTime, + Value = sineFit.Amplitude + }); + + errorSeries.DataPoints.Add(new DataPoint() + { + Time = cycleTime, + Value = sineError + }); + + // Reduce RMS to max 2 pt per cycle to get half cycle RMS + int step = 1; + if (compress) + step = (int)Math.Floor(samplesPerCycle / 2.0D); + if (step == 0) + step = 1; + + for (int cycleIndex = step; cycleIndex < dataSeries.DataPoints.Count - samplesPerCycle + 1; cycleIndex += step) + { + for (int j = 0; j < step; j++) + { + int oldIndex = cycleIndex - step + j; + int newIndex = oldIndex + samplesPerCycle; + double oldValue = dataSeries.DataPoints[oldIndex].Value; + double newValue = dataSeries.DataPoints[newIndex].Value; + sum += newValue * newValue - oldValue * oldValue; + } + + // If the cycle following i contains a data gap, do not calculate cycle data + if (gapIndexes.Any(index => cycleIndex <= index && (cycleIndex + samplesPerCycle - 1) > index)) + continue; + + phase += 2 * Math.PI * frequency * (dataSeries.DataPoints[cycleIndex].Time - cycleTime).TotalSeconds; + + // Use the time of the first data point in the cycle as the time of the cycle + cycleTime = dataSeries.DataPoints[cycleIndex].Time; + + CaptureCycle(cycleIndex); + + if (compress) + sineError = ComputeSineError(); + + if (!compress || Math.Abs(previousSineError - sineError) > sineError * 0.0001) + { + sineFit = WaveFit.SineFit(yValues, tValues, frequency); + phase = sineFit.Phase; + sineError = ComputeSineError(); + } + + previousSineError = sineError; + + rmsSeries.DataPoints.Add(new DataPoint() + { + Time = cycleTime, + Value = Math.Sqrt(sum / samplesPerCycle) + }); + + phaseSeries.DataPoints.Add(new DataPoint() + { + Time = cycleTime, + Value = phase + }); + + peakSeries.DataPoints.Add(new DataPoint() + { + Time = cycleTime, + Value = sineFit.Amplitude + }); + + errorSeries.DataPoints.Add(new DataPoint() + { + Time = cycleTime, + Value = sineError + }); + } + } + + // Add a series to the data group for each series of cycle data + DataGroup dataGroup = new DataGroup(); + dataGroup.Add(rmsSeries); + dataGroup.Add(phaseSeries); + dataGroup.Add(peakSeries); + dataGroup.Add(errorSeries); + + return new CycleDataGroup(dataGroup, dataSeries.SeriesInfo.Channel.Asset); + } + + public static DataSeries ToRMS(DataSeries dataSeries, double frequency, bool compress = false) + { + DataSeries rmsSeries = new DataSeries(); + + int samplesPerCycle; + double[] yValues; + double[] tValues; + double sum; + + DateTime cycleTime; + + if ((object)dataSeries == null) + return null; + + // Set series info to the source series info + rmsSeries.SeriesInfo = dataSeries.SeriesInfo; + + + // Get samples per cycle of the data series based on the given frequency + samplesPerCycle = Transform.CalculateSamplesPerCycle(dataSeries, frequency); + + //preinitialize size of SeriesInfo + int ncycleData = dataSeries.DataPoints.Count - samplesPerCycle; + rmsSeries.DataPoints = new List(ncycleData); + + + + // Initialize arrays of y-values and t-values for calculating cycle data as necessary + yValues = new double[samplesPerCycle]; + tValues = new double[samplesPerCycle]; + + // Obtain a list of time gaps in the data series + List gapIndexes = Enumerable.Range(0, dataSeries.DataPoints.Count - 1) + .Where(index => + { + DataPoint p1 = dataSeries[index]; + DataPoint p2 = dataSeries[index + 1]; + double cycleDiff = (p2.Time - p1.Time).TotalSeconds * frequency; + + // Detect gaps larger than a quarter cycle. + // Tolerance of 0.000062 calculated + // assuming 3.999 samples per cycle + return (cycleDiff > 0.250062); + }) + .ToList(); + + sum = 0; + + if (dataSeries.DataPoints.Count > samplesPerCycle) + { + sum = dataSeries.DataPoints.Take(samplesPerCycle).Sum(pt => pt.Value * pt.Value); + + rmsSeries.DataPoints.Add(new DataPoint() + { + Time = dataSeries.DataPoints[0].Time, + Value = Math.Sqrt(sum / samplesPerCycle) + }); + + cycleTime = dataSeries.DataPoints[0].Time; + + // Reduce RMS to max 2 pt per cycle to get half cycle RMS + int step = 1; + if (compress) + step = (int)Math.Floor(samplesPerCycle / 2.0D); + if (step == 0) + step = 1; + + for (int i = step; i < dataSeries.DataPoints.Count - samplesPerCycle; i = i + step) + { + + for (int j = 0; j < step; j++) + { + sum = sum - dataSeries.DataPoints[i - step + j].Value * dataSeries.DataPoints[i - step + j].Value; + sum = sum + dataSeries.DataPoints[i - step + j + samplesPerCycle].Value * dataSeries.DataPoints[i - step + j + samplesPerCycle].Value; + } + + // If the cycle following i contains a data gap, do not calculate cycle data + if (gapIndexes.Any(index => i <= index && (i + samplesPerCycle - 1) > index)) + continue; + + // Use the time of the first data point in the cycle as the time of the cycle + cycleTime = dataSeries.DataPoints[i].Time; + + rmsSeries.DataPoints.Add(new DataPoint() + { + Time = cycleTime, + Value = Math.Sqrt(sum / samplesPerCycle) + }); + + } + } + + return rmsSeries; + } + + public static List ToValues(DataSeries series) + { + return series.DataPoints + .Select(dataPoint => dataPoint.Value) + .ToList(); + } + + public static int CalculateSamplesPerCycle(DataSeries dataSeries, double frequency) + { + return CalculateSamplesPerCycle(dataSeries.SampleRate, frequency); + } + + public static int CalculateSamplesPerCycle(double samplesPerSecond, double frequency) + { + int[] commonSampleRates = + { + 4, 8, 16, 32, + 80, 96, 100, 200, + 64, 128, 256, 512, 1024 + }; + + int calculatedRate = (int)Math.Round(samplesPerSecond / frequency); + int nearestCommonRate = commonSampleRates.MinBy(rate => Math.Abs(calculatedRate - rate)); + int diff = Math.Abs(calculatedRate - nearestCommonRate); + return (diff < nearestCommonRate * 0.1D) ? nearestCommonRate : calculatedRate; + } + } +} diff --git a/src/Libraries/FaultData/DataAnalysis/VICycleDataGroup.cs b/src/Libraries/FaultData/DataAnalysis/VICycleDataGroup.cs new file mode 100644 index 00000000..d913f052 --- /dev/null +++ b/src/Libraries/FaultData/DataAnalysis/VICycleDataGroup.cs @@ -0,0 +1,476 @@ +//****************************************************************************************************** +// VICycleDataGroup.cs - Gbtc +// +// Copyright © 2014, 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 Eclipse Public License -v 1.0 (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/eclipse-1.0.php +// +// 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: +// ---------------------------------------------------------------------------------------------------- +// 08/29/2014 - Stephen C. Wills +// Generated original version of source code. +// +//****************************************************************************************************** + +using FaultAlgorithms; +using openXDA.Model; + +namespace FaultData.DataAnalysis +{ + public class VICycleDataGroup + { + #region [ Members ] + + // Fields + private List m_vIndices; + private Asset m_asset; + + private int m_iaIndex; + private int m_ibIndex; + private int m_icIndex; + private int m_irIndex; + + private List m_cycleDataGroups; + + private class VIndices + { + public int Va; + public int Vb; + public int Vc; + + public int Vab; + public int Vbc; + public int Vca; + + public int distance; + public VIndices() + { + Va = -1; + Vb = -1; + Vc = -1; + Vab = -1; + Vbc = -1; + Vca = -1; + + distance = -1; + } + + public int DefinedNeutralVoltages + { + get + { + return ((Va > -1) ? 1 : 0) + ((Vb > -1) ? 1 : 0) + ((Vc > -1) ? 1 : 0); + } + } + + public int DefinedLineVoltages + { + get + { + return ((Vab > -1) ? 1 : 0) + ((Vbc > -1) ? 1 : 0) + ((Vca > -1) ? 1 : 0); + } + } + + public bool allVoltagesDefined + { + get + { + return ((Vab > -1) && (Vbc > -1) && (Vca > -1) && + (Va > -1) && (Vb > -1) && (Vc > -1)); + } + } + + } + + + public double VBase => m_asset.VoltageKV; + + #endregion + + #region [ Constructors ] + + public VICycleDataGroup(DataGroup dataGroup) + { + m_vIndices = new List(); + m_asset = dataGroup.Asset; + + m_cycleDataGroups = dataGroup.DataSeries + .Select((dataSeries, index) => new { DataSeries = dataSeries, Index = index }) + .GroupBy(obj => obj.Index / 4) + .Where(grouping => grouping.Count() >= 4) + .Select(grouping => grouping.Select(obj => obj.DataSeries)) + .Select(grouping => new CycleDataGroup(new DataGroup(grouping, dataGroup.Asset), dataGroup.Asset)) + .ToList(); + + MapIndexes(); + } + + public VICycleDataGroup(List cycleDataGroups, Asset asset) + { + m_vIndices = new List(); + m_cycleDataGroups = new List(cycleDataGroups); + m_asset = asset; + MapIndexes(); + } + + #endregion + + #region [ Properties ] + + public CycleDataGroup VA + { + get + { + return (m_vIndices.Count > 0 && m_vIndices[0].Va >= 0) ? m_cycleDataGroups[m_vIndices[0].Va] : null; + } + } + + public CycleDataGroup VB + { + get + { + return (m_vIndices.Count > 0 && m_vIndices[0].Vb >= 0) ? m_cycleDataGroups[m_vIndices[0].Vb] : null; + } + } + + public CycleDataGroup VC + { + get + { + return (m_vIndices.Count > 0 && m_vIndices[0].Vc >= 0) ? m_cycleDataGroups[m_vIndices[0].Vc] : null; + } + } + + public CycleDataGroup VAB + { + get + { + return (m_vIndices.Count > 0 && m_vIndices[0].Vab >= 0) ? m_cycleDataGroups[m_vIndices[0].Vab] : null; + } + } + + public CycleDataGroup VBC + { + get + { + return (m_vIndices.Count > 0 && m_vIndices[0].Vbc >= 0) ? m_cycleDataGroups[m_vIndices[0].Vbc] : null; + } + } + + public CycleDataGroup VCA + { + get + { + return (m_vIndices.Count > 0 && m_vIndices[0].Vca >= 0) ? m_cycleDataGroups[m_vIndices[0].Vca] : null; + } + } + + public CycleDataGroup IA + { + get + { + return (m_iaIndex >= 0) ? m_cycleDataGroups[m_iaIndex] : null; + } + } + + public CycleDataGroup IB + { + get + { + return (m_ibIndex >= 0) ? m_cycleDataGroups[m_ibIndex] : null; + } + } + + public CycleDataGroup IC + { + get + { + return (m_icIndex >= 0) ? m_cycleDataGroups[m_icIndex] : null; + } + } + + public CycleDataGroup IR + { + get + { + return (m_irIndex >= 0) ? m_cycleDataGroups[m_irIndex] : null; + } + } + + public List CycleDataGroups { + get { + return m_cycleDataGroups; + } + } + + #endregion + + #region [ Methods ] + + public DataGroup ToDataGroup() + { + return Transform.Combine(m_cycleDataGroups + .Select(cycleDataGroup => cycleDataGroup.ToDataGroup()) + .ToArray()); + } + + public VICycleDataGroup ToSubSet(int startIndex, int endIndex) + { + return new VICycleDataGroup(m_cycleDataGroups + .Select(cycleDataGroup => cycleDataGroup.ToSubGroup(startIndex, endIndex)) + .ToList(), m_asset); + } + + public VICycleDataGroup ToSubSet(DateTime startTime, DateTime endTime) + { + return new VICycleDataGroup(m_cycleDataGroups + .Select(cycleDataGroup => cycleDataGroup.ToSubGroup(startTime, endTime)) + .ToList(), m_asset); + } + + public void PushDataTo(CycleDataSet cycleDataSet) + { + FaultAlgorithms.CycleData cycleData; + Cycle[] cycles; + CycleDataGroup[] cycleDataGroups; + + cycleDataGroups = new CycleDataGroup[] { VA, VB, VC, IA, IB, IC }; + cycles = new Cycle[cycleDataGroups.Length]; + + for (int i = 0; i < VA.ToDataGroup().Samples; i++) + { + cycleData = new FaultAlgorithms.CycleData(); + + cycles[0] = cycleData.AN.V; + cycles[1] = cycleData.BN.V; + cycles[2] = cycleData.CN.V; + cycles[3] = cycleData.AN.I; + cycles[4] = cycleData.BN.I; + cycles[5] = cycleData.CN.I; + + for (int j = 0; j < cycles.Length; j++) + { + if (cycleDataGroups[j] == null) + continue; + + cycles[j].RMS = cycleDataGroups[j].RMS[i].Value; + cycles[j].Phase = cycleDataGroups[j].Phase[i].Value; + cycles[j].Peak = cycleDataGroups[j].Peak[i].Value; + cycles[j].Error = cycleDataGroups[j].Error[i].Value; + } + + cycleDataSet[i] = cycleData; + } + } + + private void MapIndexes() + { + + m_iaIndex = -1; + m_ibIndex = -1; + m_icIndex = -1; + m_irIndex = -1; + + List vaIndices = new List(); + List vbIndices = new List(); + List vcIndices = new List(); + List vabIndices = new List(); + List vbcIndices = new List(); + List vcaIndices = new List(); + + for (int i = 0; i < m_cycleDataGroups.Count; i++) + { + if (isVoltage("AN", m_cycleDataGroups[i])) + vaIndices.Add(i); + else if (isVoltage("BN", m_cycleDataGroups[i])) + vbIndices.Add(i); + else if (isVoltage("CN", m_cycleDataGroups[i])) + vcIndices.Add(i); + else if (isVoltage("AB", m_cycleDataGroups[i])) + vabIndices.Add(i); + else if (isVoltage("BC", m_cycleDataGroups[i])) + vbcIndices.Add(i); + else if (isVoltage("CA", m_cycleDataGroups[i])) + vcaIndices.Add(i); + + } + + //Walk through all Va and try to get corresponding Vb and Vc... + List ProcessedIndices = new List(); + foreach (int? VaIndex in vaIndices) + { + int assetID = m_cycleDataGroups[(int)VaIndex].Asset.ID; + + int VbIndex = vbIndices.Cast().FirstOrDefault(i => m_cycleDataGroups[(int)i].Asset.ID == assetID && !ProcessedIndices.Contains(i)) ?? -1; + int VcIndex = vcIndices.Cast().FirstOrDefault(i => m_cycleDataGroups[(int)i].Asset.ID == assetID && !ProcessedIndices.Contains(i)) ?? -1; + int VabIndex = vabIndices.Cast().FirstOrDefault(i => m_cycleDataGroups[(int)i].Asset.ID == assetID && !ProcessedIndices.Contains(i)) ?? -1; + int VbcIndex = vbcIndices.Cast().FirstOrDefault(i => m_cycleDataGroups[(int)i].Asset.ID == assetID && !ProcessedIndices.Contains(i)) ?? -1; + int VcaIndex = vcaIndices.Cast().FirstOrDefault(i => m_cycleDataGroups[(int)i].Asset.ID == assetID && !ProcessedIndices.Contains(i)) ?? -1; + + VIndices set = new VIndices(); + ProcessedIndices.Add(VaIndex); + set.Va = (int)VaIndex; + + if (VbIndex > -1) + { + ProcessedIndices.Add(VbIndex); + set.Vb = VbIndex; + } + if (VcIndex > -1) + { + ProcessedIndices.Add(VcIndex); + set.Vc = VcIndex; + } + + if (VabIndex > -1) + { + ProcessedIndices.Add(VabIndex); + set.Vab = VabIndex; + } + if (VbcIndex > -1) + { + ProcessedIndices.Add(VbcIndex); + set.Vbc = VbcIndex; + } + if (VcaIndex > -1) + { + ProcessedIndices.Add(VcaIndex); + set.Vca = VcaIndex; + } + + + if (assetID == m_asset.ID) + { + set.distance = 0; + } + else + { + set.distance = m_asset.DistanceToAsset(assetID); + } + + m_vIndices.Add(set); + } + + // Also walk though all Vab to catch Leftover Cases where Va is not present + foreach (int? VabIndex in vabIndices) + { + int assetID = m_cycleDataGroups[(int)VabIndex].Asset.ID; + + int VaIndex = vaIndices.Cast().FirstOrDefault(i => m_cycleDataGroups[(int)i].Asset.ID == assetID && !ProcessedIndices.Contains(i)) ?? -1; + int VbIndex = vbIndices.Cast().FirstOrDefault(i => m_cycleDataGroups[(int)i].Asset.ID == assetID && !ProcessedIndices.Contains(i)) ?? -1; + int VcIndex = vcIndices.Cast().FirstOrDefault(i => m_cycleDataGroups[(int)i].Asset.ID == assetID && !ProcessedIndices.Contains(i)) ?? -1; + + int VbcIndex = vbcIndices.Cast().FirstOrDefault(i => m_cycleDataGroups[(int)i].Asset.ID == assetID && !ProcessedIndices.Contains(i)) ?? -1; + int VcaIndex = vcaIndices.Cast().FirstOrDefault(i => m_cycleDataGroups[(int)i].Asset.ID == assetID && !ProcessedIndices.Contains(i)) ?? -1; + + VIndices set = new VIndices(); + ProcessedIndices.Add(VabIndex); + set.Vab = (int)VabIndex; + + if (VbIndex > -1) + { + ProcessedIndices.Add(VbIndex); + set.Vb = VbIndex; + } + if (VcIndex > -1) + { + ProcessedIndices.Add(VcIndex); + set.Vc = VcIndex; + } + + if (VaIndex > -1) + { + ProcessedIndices.Add(VaIndex); + set.Va = VaIndex; + } + if (VbcIndex > -1) + { + ProcessedIndices.Add(VbcIndex); + set.Vbc = VbcIndex; + } + if (VcaIndex > -1) + { + ProcessedIndices.Add(VcaIndex); + set.Vca = VcaIndex; + } + + + if (assetID == m_asset.ID) + { + set.distance = 0; + } + else + { + set.distance = m_asset.DistanceToAsset(assetID); + } + + m_vIndices.Add(set); + } + + for (int i = 0; i < m_cycleDataGroups.Count; i++) + { + string measurementType = m_cycleDataGroups[i].RMS.SeriesInfo.Channel.MeasurementType.Name; + string phase = m_cycleDataGroups[i].RMS.SeriesInfo.Channel.Phase.Name; + + + if (measurementType == "Current" && phase == "AN") + m_iaIndex = i; + else if (measurementType == "Current" && phase == "BN") + m_ibIndex = i; + else if (measurementType == "Current" && phase == "CN") + m_icIndex = i; + else if (measurementType == "Current" && phase == "RES") + m_irIndex = i; + } + } + + #endregion + + #region [ Static ] + + // Static Methods + + private static bool isVoltage(string phase, CycleDataGroup dataGroup) + { + + string measurementType = dataGroup.RMS.SeriesInfo.Channel.MeasurementType.Name; + string seriesPhase = dataGroup.RMS.SeriesInfo.Channel.Phase.Name; + + if (measurementType != "Voltage") + return false; + + if (seriesPhase != phase) + return false; + + return true; + + } + + private static bool isCurrent(string phase, CycleDataGroup dataGroup) + { + string measurementType = dataGroup.RMS.SeriesInfo.Channel.MeasurementType.Name; + string seriesPhase = dataGroup.RMS.SeriesInfo.Channel.Phase.Name; + + if (measurementType != "Current") + return false; + + if (seriesPhase != phase) + return false; + + return true; + + } + + #endregion + + } +} diff --git a/src/Libraries/FaultData/DataAnalysis/VIDataGroup.cs b/src/Libraries/FaultData/DataAnalysis/VIDataGroup.cs new file mode 100644 index 00000000..ae088e05 --- /dev/null +++ b/src/Libraries/FaultData/DataAnalysis/VIDataGroup.cs @@ -0,0 +1,521 @@ +//****************************************************************************************************** +// VIDataGroup.cs - Gbtc +// +// Copyright © 2014, 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 Eclipse Public License -v 1.0 (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/eclipse-1.0.php +// +// 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: +// ---------------------------------------------------------------------------------------------------- +// 08/29/2014 - Stephen C. Wills +// Generated original version of source code. +// +//****************************************************************************************************** + +using Gemstone.Data; +using openXDA.Model; + +namespace FaultData.DataAnalysis +{ + public class VIDataGroup + { + #region [ Members ] + + // Fields + private List m_vIndices; + + private int m_iaIndex; + private int m_ibIndex; + private int m_icIndex; + private int m_irIndex; + + private DataGroup m_dataGroup; + + private class VIndices + { + public int Va { get; set; } = -1; + public int Vb { get; set; } = -1; + public int Vc { get; set; } = -1; + + public int Vab { get; set; } = -1; + public int Vbc { get; set; } = -1; + public int Vca { get; set; } = -1; + + public int Distance { get; set; } = -1; + + public int DefinedNeutralVoltages => + (Va >= 0 ? 1 : 0) + + (Vb >= 0 ? 1 : 0) + + (Vc >= 0 ? 1 : 0); + + public int DefinedLineVoltages => + (Vab >= 0 ? 1 : 0) + + (Vbc >= 0 ? 1 : 0) + + (Vca >= 0 ? 1 : 0); + + public bool AllVoltagesDefined => + (Va >= 0) && (Vb >= 0) && (Vc >= 0) && + (Vab >= 0) && (Vbc >= 0) && (Vca >= 0); + } + + #endregion + + #region [ Constructors ] + + public VIDataGroup(DataGroup dataGroup) + { + + // Initialize each of + // the indexes to -1 + m_vIndices = new List(); + + m_iaIndex = -1; + m_ibIndex = -1; + m_icIndex = -1; + m_irIndex = -1; + + // Initialize the data group + m_dataGroup = new DataGroup(dataGroup.DataSeries, dataGroup.Asset); + + HashSet connectedAssets = new HashSet(dataGroup.Asset.ConnectedAssets.Select(item => item.ID)); + + var groupings = dataGroup.DataSeries + .Select((DataSeries, Index) => new { DataSeries, Index }) + .Where(item => !(item.DataSeries.SeriesInfo is null)) + .Where(item => item.DataSeries.SeriesInfo.Channel.MeasurementCharacteristic.Name == "Instantaneous") + .Where(item => new[] { "Instantaneous", "Values" }.Contains(item.DataSeries.SeriesInfo.SeriesType.Name)) + .GroupBy(item => item.DataSeries.SeriesInfo.Channel.AssetID) + .OrderBy(grouping => grouping.Key == dataGroup.Asset.ID ? 0 : 1) + .ThenBy(grouping => connectedAssets.Contains(grouping.Key) ? 0 : 1) + .ToList(); + + foreach (var grouping in groupings) + { + VIndices set = new VIndices() { Distance = 0 }; + + int assetID = grouping.Key; + + if (assetID != dataGroup.Asset.ID) + set.Distance = dataGroup.Asset.DistanceToAsset(assetID); + + foreach (var item in grouping) + { + string measurementType = item.DataSeries.SeriesInfo.Channel.MeasurementType.Name; + string phase = item.DataSeries.SeriesInfo.Channel.Phase.Name; + + if (measurementType == "Voltage" && phase == "AN") + set.Va = item.Index; + + if (measurementType == "Voltage" && phase == "BN") + set.Vb = item.Index; + + if (measurementType == "Voltage" && phase == "CN") + set.Vc = item.Index; + + if (measurementType == "Voltage" && phase == "AB") + set.Vab = item.Index; + + if (measurementType == "Voltage" && phase == "BC") + set.Vbc = item.Index; + + if (measurementType == "Voltage" && phase == "CA") + set.Vca = item.Index; + + if (m_iaIndex < 0 && measurementType == "Current" && phase == "AN") + m_iaIndex = item.Index; + + if (m_ibIndex < 0 && measurementType == "Current" && phase == "BN") + m_ibIndex = item.Index; + + if (m_icIndex < 0 && measurementType == "Current" && phase == "CN") + m_icIndex = item.Index; + + if (m_irIndex < 0 && measurementType == "Current" && phase == "RES") + m_irIndex = item.Index; + } + + if (set.DefinedLineVoltages + set.DefinedNeutralVoltages > 0) + m_vIndices.Add(set); + } + + if (m_vIndices.Count() == 0) + m_vIndices.Add(new VIndices()); + + CalculateMissingCurrentChannel(); + CalculateMissingLLVoltageChannels(); + + m_vIndices.Sort((a, b) => + { + if (b.AllVoltagesDefined && !a.AllVoltagesDefined) + return 1; + if (a.AllVoltagesDefined && !b.AllVoltagesDefined) + return -1; + if (!(a.Distance >= 0 && b.Distance >= 0)) + return b.Distance.CompareTo(a.Distance); + return a.Distance.CompareTo(b.Distance); + }); + } + + private VIDataGroup() + { + } + + #endregion + + #region [ Properties ] + + public DataSeries VA => (m_vIndices[0].Va >= 0) + ? m_dataGroup[m_vIndices[0].Va] + : null; + + public DataSeries VB => (m_vIndices[0].Vb >= 0) + ? m_dataGroup[m_vIndices[0].Vb] + : null; + + public DataSeries VC => (m_vIndices[0].Vc >= 0) + ? m_dataGroup[m_vIndices[0].Vc] + : null; + + public DataSeries VAB => (m_vIndices[0].Vab >= 0) + ? m_dataGroup[m_vIndices[0].Vab] + : null; + + public DataSeries VBC => (m_vIndices[0].Vbc >= 0) + ? m_dataGroup[m_vIndices[0].Vbc] + : null; + + public DataSeries VCA => (m_vIndices[0].Vca >= 0) + ? m_dataGroup[m_vIndices[0].Vca] + : null; + + public DataSeries IA => (m_iaIndex >= 0) + ? m_dataGroup[m_iaIndex] + : null; + + public DataSeries IB => (m_ibIndex >= 0) + ? m_dataGroup[m_ibIndex] + : null; + + public DataSeries IC => (m_icIndex >= 0) + ? m_dataGroup[m_icIndex] + : null; + + public DataSeries IR => (m_irIndex >= 0) + ? m_dataGroup[m_irIndex] + : null; + + public int DefinedNeutralVoltages => m_vIndices + .Select(item => item.DefinedNeutralVoltages) + .FirstOrDefault(); + + public int DefinedLineVoltages => m_vIndices + .Select(item => item.DefinedLineVoltages) + .FirstOrDefault(); + + public int DefinedCurrents => + CurrentIndexes.Count(index => index >= 0); + + public int DefinedPhaseCurrents => + PhaseCurrentIndexes.Count(index => index >= 0); + + public bool AllVIChannelsDefined => + m_vIndices[0].AllVoltagesDefined && + CurrentIndexes.All(index => index >= 0); + + private int[] CurrentIndexes => + new int[] { m_iaIndex, m_ibIndex, m_icIndex, m_irIndex }; + + private int[] PhaseCurrentIndexes => + new int[] { m_iaIndex, m_ibIndex, m_icIndex }; + + public Asset Asset => m_dataGroup.Asset; + + public DataSeries[] Data + { + get + { + List result = new List(); + + foreach (VIndices Vindex in m_vIndices) + { + if (Vindex.Va > -1) + result.Add(m_dataGroup[Vindex.Va]); + if (Vindex.Vb > -1) + result.Add(m_dataGroup[Vindex.Vb]); + if (Vindex.Vc > -1) + result.Add(m_dataGroup[Vindex.Vc]); + + if (Vindex.Vab > -1) + result.Add(m_dataGroup[Vindex.Vab]); + if (Vindex.Vbc > -1) + result.Add(m_dataGroup[Vindex.Vbc]); + if (Vindex.Vca > -1) + result.Add(m_dataGroup[Vindex.Vca]); + } + + if (m_iaIndex > -1) + result.Add(m_dataGroup[m_iaIndex]); + if (m_ibIndex > -1) + result.Add(m_dataGroup[m_ibIndex]); + if (m_icIndex > -1) + result.Add(m_dataGroup[m_icIndex]); + if (m_irIndex > -1) + result.Add(m_dataGroup[m_irIndex]); + + return result.ToArray(); + } + } + + #endregion + + #region [ Methods ] + + /// + /// Given three of the four current channels, calculates the + /// missing channel based on the relationship IR = IA + IB + IC. + /// + private void CalculateMissingCurrentChannel() + { + Meter meter; + DataSeries missingSeries; + + // If the data group does not have exactly 3 channels, + // then there is no missing channel or there is not + // enough data to calculate the missing channel + if (DefinedCurrents != 3) + return; + + // Get the meter associated with the channels in this data group + meter = (IA ?? IB).SeriesInfo.Channel.Meter; + + if (m_iaIndex == -1) + { + // Calculate IA = IR - IB - IC + missingSeries = IR.Add(IB.Negate()).Add(IC.Negate()); + missingSeries.SeriesInfo = GetSeriesInfo(meter, IR.SeriesInfo.Channel.Asset, "Current", "AN", m_dataGroup.SamplesPerHour); + missingSeries.Calculated = true; + m_iaIndex = m_dataGroup.DataSeries.Count; + m_dataGroup.Add(missingSeries); + } + else if (m_ibIndex == -1) + { + // Calculate IB = IR - IA - IC + missingSeries = IR.Add(IA.Negate()).Add(IC.Negate()); + missingSeries.SeriesInfo = GetSeriesInfo(meter, IR.SeriesInfo.Channel.Asset, "Current", "BN", m_dataGroup.SamplesPerHour); + missingSeries.Calculated = true; + m_ibIndex = m_dataGroup.DataSeries.Count; + m_dataGroup.Add(missingSeries); + } + else if (m_icIndex == -1) + { + // Calculate IC = IR - IA - IB + missingSeries = IR.Add(IA.Negate()).Add(IB.Negate()); + missingSeries.SeriesInfo = GetSeriesInfo(meter, IR.SeriesInfo.Channel.Asset, "Current", "CN", m_dataGroup.SamplesPerHour); + missingSeries.Calculated = true; + m_icIndex = m_dataGroup.DataSeries.Count; + m_dataGroup.Add(missingSeries); + } + else + { + // Calculate IR = IA + IB + IC + missingSeries = IA.Add(IB).Add(IC); + missingSeries.SeriesInfo = GetSeriesInfo(meter, IA.SeriesInfo.Channel.Asset, "Current", "RES", m_dataGroup.SamplesPerHour); + missingSeries.Calculated = true; + m_irIndex = m_dataGroup.DataSeries.Count; + m_dataGroup.Add(missingSeries); + } + } + + private void CalculateMissingLLVoltageChannels() + { + Meter meter; + DataSeries missingSeries; + + //Do this for every Voltage set + for (int i = 0; i < m_vIndices.Count(); i++) + { + // If all line voltages are already present or there are not + // at least 2 lines we will not perform line to line calculations + if (m_vIndices[i].DefinedLineVoltages == 3 || m_vIndices[i].DefinedNeutralVoltages < 2) + continue; + + // Get the meter associated with the channels in this data group + DataSeries VA = null; + DataSeries VB = null; + DataSeries VC = null; + + if (m_vIndices[i].Va > -1) + VA = m_dataGroup[m_vIndices[i].Va]; + if (m_vIndices[i].Vb > -1) + VB = m_dataGroup[m_vIndices[i].Vb]; + if (m_vIndices[i].Vc > -1) + VC = m_dataGroup[m_vIndices[i].Vc]; + + meter = (VA ?? VB ?? VC).SeriesInfo.Channel.Meter; + + if (m_vIndices[i].Vab == -1 && !(VA is null) && !(VB is null)) + { + // Calculate VAB = VA - VB + missingSeries = VA.Add(VB.Negate()); + missingSeries.SeriesInfo = GetSeriesInfo(meter, VA.SeriesInfo.Channel.Asset, "Voltage", "AB", m_dataGroup.SamplesPerHour); + missingSeries.Calculated = true; + m_vIndices[i].Vab = m_dataGroup.DataSeries.Count; + m_dataGroup.Add(missingSeries); + } + + if (m_vIndices[i].Vbc == -1 && !(VB is null) && !(VC is null)) + { + // Calculate VBC = VB - VC + missingSeries = VB.Add(VC.Negate()); + missingSeries.SeriesInfo = GetSeriesInfo(meter, VB.SeriesInfo.Channel.Asset, "Voltage", "BC", m_dataGroup.SamplesPerHour); + missingSeries.Calculated = true; + m_vIndices[i].Vbc = m_dataGroup.DataSeries.Count; + m_dataGroup.Add(missingSeries); + } + + if (m_vIndices[i].Vca == -1 && !(VC is null) && !(VA is null)) + { + // Calculate VCA = VC - VA + missingSeries = VC.Add(VA.Negate()); + missingSeries.SeriesInfo = GetSeriesInfo(meter, VC.SeriesInfo.Channel.Asset, "Voltage", "CA", m_dataGroup.SamplesPerHour); + missingSeries.Calculated = true; + m_vIndices[i].Vca = m_dataGroup.DataSeries.Count; + m_dataGroup.Add(missingSeries); + } + } + } + + public DataGroup ToDataGroup() + { + return new DataGroup(m_dataGroup.DataSeries, m_dataGroup.Asset); + } + + public VIDataGroup ToSubGroup(int startIndex, int endIndex) + { + VIDataGroup subGroup = new VIDataGroup(); + + subGroup.m_vIndices = m_vIndices; + subGroup.m_iaIndex = m_iaIndex; + subGroup.m_ibIndex = m_ibIndex; + subGroup.m_icIndex = m_icIndex; + subGroup.m_irIndex = m_irIndex; + + subGroup.m_dataGroup = m_dataGroup.ToSubGroup(startIndex, endIndex); + + return subGroup; + } + + public VIDataGroup ToSubGroup(DateTime startTime, DateTime endTime) + { + VIDataGroup subGroup = new VIDataGroup(); + + subGroup.m_vIndices = m_vIndices; + subGroup.m_iaIndex = m_iaIndex; + subGroup.m_ibIndex = m_ibIndex; + subGroup.m_icIndex = m_icIndex; + subGroup.m_irIndex = m_irIndex; + + subGroup.m_dataGroup = m_dataGroup.ToSubGroup(startTime, endTime); + + return subGroup; + } + + #endregion + + #region [ Static ] + + // Static Methods + private static Series GetSeriesInfo(Meter meter, Asset asset, string measurementTypeName, string phaseName, double samplesPerHour) + { + string measurementCharacteristicName = "Instantaneous"; + string seriesTypeName = "Values"; + + char typeDesignation = (measurementTypeName == "Current") ? 'I' : measurementTypeName[0]; + string phaseDesignation = (phaseName == "RES") ? "R" : phaseName.TrimEnd('N'); + string channelName = string.Concat(typeDesignation, phaseDesignation); + + ChannelKey channelKey = new ChannelKey(asset.ID, 0, channelName, measurementTypeName, measurementCharacteristicName, phaseName); + SeriesKey seriesKey = new SeriesKey(channelKey, seriesTypeName); + + Channel dbChannel = (meter.ConnectionFactory is null) + ? meter.Channels.FirstOrDefault(channel => channelKey.Equals(new ChannelKey(channel))) + : FastSearch(meter, channelKey); + + Series dbSeries = dbChannel?.Series + .FirstOrDefault(series => seriesKey.Equals(new SeriesKey(series))); + + if (dbSeries is null) + { + if (dbChannel is null) + { + MeasurementType measurementType = new MeasurementType() { Name = measurementTypeName }; + MeasurementCharacteristic measurementCharacteristic = new MeasurementCharacteristic() { Name = measurementCharacteristicName }; + Phase phase = new Phase() { Name = phaseName }; + + dbChannel = new Channel() + { + MeterID = meter.ID, + AssetID = asset.ID, + MeasurementTypeID = measurementType.ID, + MeasurementCharacteristicID = measurementCharacteristic.ID, + PhaseID = phase.ID, + Name = channelKey.Name, + SamplesPerHour = samplesPerHour, + Description = string.Concat(measurementCharacteristicName, " ", measurementTypeName, " ", phaseName), + Enabled = true, + + Meter = meter, + Asset = asset, + MeasurementType = measurementType, + MeasurementCharacteristic = measurementCharacteristic, + Phase = phase, + Series = new List() + }; + + meter.Channels.Add(dbChannel); + } + + SeriesType seriesType = new SeriesType() { Name = seriesTypeName }; + + dbSeries = new Series() + { + ChannelID = dbChannel.ID, + SeriesTypeID = seriesType.ID, + SourceIndexes = string.Empty, + + Channel = dbChannel, + SeriesType = seriesType + }; + + dbChannel.Series.Add(dbSeries); + } + + return dbSeries; + } + + private static Channel FastSearch(Meter meter, ChannelKey channelKey) + { + using (AdoDataConnection connection = meter.ConnectionFactory()) + { + Channel search = channelKey.Find(connection, meter.ID); + + if (search is null) + return null; + + return meter.Channels + .FirstOrDefault(channel => channel.ID == search.ID); + } + } + + #endregion + } +} diff --git a/src/Libraries/FaultData/FaultData.csproj b/src/Libraries/FaultData/FaultData.csproj new file mode 100644 index 00000000..f4297a8d --- /dev/null +++ b/src/Libraries/FaultData/FaultData.csproj @@ -0,0 +1,19 @@ + + + + net9.0 + enable + enable + + + + + + + + + + + + + diff --git a/src/Libraries/openXDA.Model/Channels/Channel.cs b/src/Libraries/openXDA.Model/Channels/Channel.cs new file mode 100644 index 00000000..491748e4 --- /dev/null +++ b/src/Libraries/openXDA.Model/Channels/Channel.cs @@ -0,0 +1,580 @@ +//****************************************************************************************************** +// Channel.cs - Gbtc +// +// Copyright © 2017, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 08/29/2017 - Billy Ernest +// Generated original version of source code. +// +//****************************************************************************************************** + +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Data; +using System.Transactions; +using Gemstone.Data; +using Gemstone.Data.Model; +using Newtonsoft.Json; +using IsolationLevel = System.Transactions.IsolationLevel; + +namespace openXDA.Model +{ + public class ChannelKey : IEquatable + { + #region [ Constructors ] + + public ChannelKey(int assetID, int harmonicGroup, string name, string measurementType, string measurementCharacteristic, string phase) + { + LineID = assetID; + HarmonicGroup = harmonicGroup; + Name = name; + MeasurementType = measurementType; + MeasurementCharacteristic = measurementCharacteristic; + Phase = phase; + } + + public ChannelKey(Channel channel) + : this(channel.AssetID, channel.HarmonicGroup, channel.Name, channel.MeasurementType.Name, channel.MeasurementCharacteristic.Name, channel.Phase.Name) + { + } + + #endregion + + #region [ Properties ] + + public int LineID { get; } + public int HarmonicGroup { get; } + public string Name { get; } + public string MeasurementType { get; } + public string MeasurementCharacteristic { get; } + public string Phase { get; } + + #endregion + + #region [ Methods ] + + public Channel Find(AdoDataConnection connection, int meterID) + { + const string QueryFormat = + "SELECT Channel.* " + + "FROM " + + " Channel JOIN " + + " MeasurementType ON Channel.MeasurementTypeID = MeasurementType.ID JOIN " + + " MeasurementCharacteristic ON Channel.MeasurementCharacteristicID = MeasurementCharacteristic.ID JOIN " + + " Phase ON Channel.PhaseID = Phase.ID " + + "WHERE " + + " Channel.MeterID = {0} AND " + + " Channel.AssetID = {1} AND " + + " Channel.HarmonicGroup = {2} AND " + + " Channel.Name = {3} AND " + + " MeasurementType.Name = {4} AND " + + " MeasurementCharacteristic.Name = {5} AND " + + " Phase.Name = {6}"; + + object[] parameters = + { + meterID, + LineID, + HarmonicGroup, + Name, + MeasurementType, + MeasurementCharacteristic, + Phase + }; + + using (DataTable table = connection.RetrieveData(QueryFormat, parameters)) + { + if (table.Rows.Count == 0) + return null; + + TableOperations channelTable = new TableOperations(connection); + return channelTable.LoadRecord(table.Rows[0]); + } + } + + public override int GetHashCode() + { + StringComparer stringComparer = StringComparer.OrdinalIgnoreCase; + + int hash = 1009; + hash = 9176 * hash + LineID.GetHashCode(); + hash = 9176 * hash + HarmonicGroup.GetHashCode(); + hash = 9176 * hash + stringComparer.GetHashCode(Name); + hash = 9176 * hash + stringComparer.GetHashCode(MeasurementType); + hash = 9176 * hash + stringComparer.GetHashCode(MeasurementCharacteristic); + hash = 9176 * hash + stringComparer.GetHashCode(Phase); + return hash; + } + + public override bool Equals(object obj) + { + return Equals(obj as ChannelKey); + } + + public bool Equals(ChannelKey other) + { + if (other is null) + return false; + + StringComparison stringComparison = StringComparison.OrdinalIgnoreCase; + + return + LineID.Equals(other.LineID) && + HarmonicGroup.Equals(other.HarmonicGroup) && + Name.Equals(other.Name, stringComparison) && + MeasurementType.Equals(other.MeasurementType, stringComparison) && + MeasurementCharacteristic.Equals(other.MeasurementCharacteristic, stringComparison) && + Phase.Equals(other.Phase, stringComparison); + } + + #endregion + } + + [TableName("Channel")] + public class ChannelBase + { + [PrimaryKey(true)] + public int ID { get; set; } + + [ParentKey(typeof(Meter))] + public int MeterID { get; set; } + + public int AssetID { get; set; } + + public int MeasurementTypeID { get; set; } + + public int MeasurementCharacteristicID { get; set; } + + public int PhaseID { get; set; } + + [StringLength(200)] + public string Name { get; set; } + + public double Adder { get; set; } + + [DefaultValue(1.0D)] + public double Multiplier { get; set; } = 1.0D; + + public double SamplesPerHour { get; set; } + + public double? PerUnitValue { get; set; } + + public int HarmonicGroup { get; set; } + + public string Description { get; set; } + + public bool Enabled { get; set; } + + [DefaultValue(false)] + public bool Trend { get; set; } + + [DefaultValue(0)] + public int ConnectionPriority { get; set; } = 0; + } + + public class Channel : ChannelBase + { + #region [ Members ] + + // Fields + private MeasurementType m_measurementType; + private MeasurementCharacteristic m_measurementCharacteristic; + private Phase m_phase; + private Meter m_meter; + private Asset m_asset; + private List m_series; + + #endregion + + #region [ Properties ] + + [JsonIgnore] + [NonRecordField] + public MeasurementType MeasurementType + { + get + { + if (m_measurementType is null) + m_measurementType = LazyContext.GetMeasurementType(MeasurementTypeID); + + if (m_measurementType is null) + m_measurementType = QueryMeasurementType(); + + return m_measurementType; + } + set => m_measurementType = value; + } + + [JsonIgnore] + [NonRecordField] + public MeasurementCharacteristic MeasurementCharacteristic + { + get + { + if (m_measurementCharacteristic is null) + m_measurementCharacteristic = LazyContext.GetMeasurementCharacteristic(MeasurementCharacteristicID); + + if (m_measurementCharacteristic is null) + m_measurementCharacteristic = QueryMeasurementCharacteristic(); + + return m_measurementCharacteristic; + } + set => m_measurementCharacteristic = value; + } + + [JsonIgnore] + [NonRecordField] + public Phase Phase + { + get + { + if (m_phase is null) + m_phase = LazyContext.GetPhase(PhaseID); + + if (m_phase is null) + m_phase = QueryPhase(); + + return m_phase; + } + set => m_phase = value; + } + + [JsonIgnore] + [NonRecordField] + public Meter Meter + { + get + { + if (m_meter is null) + m_meter = LazyContext.GetMeter(MeterID); + + if (m_meter is null) + m_meter = QueryMeter(); + + return m_meter; + } + set => m_meter = value; + } + + [JsonIgnore] + [NonRecordField] + public Asset Asset + { + get + { + if (m_asset is null) + m_asset = LazyContext.GetAsset(AssetID); + + if (m_asset is null) + m_asset = QueryAsset(); + + return m_asset; + } + set => m_asset = value; + } + + [JsonIgnore] + [NonRecordField] + public List Series + { + get => m_series ?? (m_series = QuerySeries()); + set => m_series = value; + } + + [JsonIgnore] + [NonRecordField] + public Func ConnectionFactory + { + get => LazyContext.ConnectionFactory; + set => LazyContext.ConnectionFactory = value; + } + + [JsonIgnore] + [NonRecordField] + internal LazyContext LazyContext { get; set; } = new LazyContext(); + + #endregion + + #region [ Methods ] + + public MeasurementType GetMeasurementType(AdoDataConnection connection) + { + if ((object)connection == null) + return null; + + TableOperations measurementTypeTable = new TableOperations(connection); + return measurementTypeTable.QueryRecordWhere("ID = {0}", MeasurementTypeID); + } + + public MeasurementCharacteristic GetMeasurementCharacteristic(AdoDataConnection connection) + { + if ((object)connection == null) + return null; + + TableOperations measurementCharacteristicTable = new TableOperations(connection); + return measurementCharacteristicTable.QueryRecordWhere("ID = {0}", MeasurementCharacteristicID); + } + + public Phase GetPhase(AdoDataConnection connection) + { + if ((object)connection == null) + return null; + + TableOperations phaseTable = new TableOperations(connection); + return phaseTable.QueryRecordWhere("ID = {0}", PhaseID); + } + + public Meter GetMeter(AdoDataConnection connection) + { + if ((object)connection == null) + return null; + + TableOperations meterTable = new TableOperations(connection); + return meterTable.QueryRecordWhere("ID = {0}", MeterID); + } + + public Asset GetAsset(AdoDataConnection connection) + { + if ((object)connection == null) + return null; + + TableOperations assetTable = new TableOperations(connection); + return assetTable.QueryRecordWhere("ID = {0}", AssetID); + } + + public IEnumerable GetSeries(AdoDataConnection connection) + { + if ((object)connection == null) + return null; + + TableOperations seriesTable = new TableOperations(connection); + return seriesTable.QueryRecordsWhere("ChannelID = {0}", ID); + } + + private MeasurementType QueryMeasurementType() + { + MeasurementType measurementType; + + using (AdoDataConnection connection = ConnectionFactory?.Invoke()) + { + measurementType = GetMeasurementType(connection); + } + + return LazyContext.GetMeasurementType(measurementType); + } + + private MeasurementCharacteristic QueryMeasurementCharacteristic() + { + MeasurementCharacteristic measurementCharacteristic; + + using (AdoDataConnection connection = ConnectionFactory?.Invoke()) + { + measurementCharacteristic = GetMeasurementCharacteristic(connection); + } + + return LazyContext.GetMeasurementCharacteristic(measurementCharacteristic); + } + + private Phase QueryPhase() + { + Phase phase; + + using (AdoDataConnection connection = ConnectionFactory?.Invoke()) + { + phase = GetPhase(connection); + } + + return LazyContext.GetPhase(phase); + } + + private Meter QueryMeter() + { + Meter meter; + + using (AdoDataConnection connection = ConnectionFactory?.Invoke()) + { + meter = GetMeter(connection); + } + + if ((object)meter != null) + meter.LazyContext = LazyContext; + + return LazyContext.GetMeter(meter); + } + + private Asset QueryAsset() + { + Asset asset; + + using (AdoDataConnection connection = ConnectionFactory?.Invoke()) + { + asset = GetAsset(connection); + } + + if ((object)asset != null) + asset.LazyContext = LazyContext; + + return LazyContext.GetAsset(asset); + } + + private List QuerySeries() + { + List seriesList; + + using (AdoDataConnection connection = ConnectionFactory?.Invoke()) + { + seriesList = GetSeries(connection)? + .Select(LazyContext.GetSeries) + .ToList(); + } + + if ((object)seriesList != null) + { + foreach (Series series in seriesList) + { + series.Channel = this; + series.LazyContext = LazyContext; + } + } + + return seriesList; + } + + #endregion + } + + public class ChannelComparer : IEqualityComparer + { + public bool Equals(Channel x, Channel y) + { + if (Object.ReferenceEquals(x, y)) return true; + + //Check whether any of the compared objects is null. + if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null)) + return false; + + //Check whether the channels are equal. + return x.ID == y.ID; + } + + public int GetHashCode(Channel obj) + { + return obj.ID; + } + } + + public class ChannelInfo + { + [PrimaryKey(true)] + public int ChannelID { get; set; } + + public string ChannelName { get; set; } + + public string ChannelDescription { get; set; } + + public string MeasurementType { get; set; } + + public string MeasurementCharacteristic { get; set; } + + public string Phase { get; set; } + + public string SeriesType { get; set; } + + public string Orientation { get; set; } + + public string Phasing { get; set; } + } + + public static partial class TableOperationsExtensions + { + public static DashSettings GetOrAdd(this TableOperations table, string name, string value, bool enabled = true) + { + TransactionScopeOption required = TransactionScopeOption.Required; + + TransactionOptions transactionOptions = new TransactionOptions() + { + IsolationLevel = IsolationLevel.ReadCommitted, + Timeout = TransactionManager.MaximumTimeout + }; + + DashSettings dashSettings; + + using (TransactionScope transactionScope = new TransactionScope(required, transactionOptions)) + { + if (value.Contains(",")) + dashSettings = table.QueryRecordWhere("Name = {0} AND SUBSTRING(Value, 0, CHARINDEX(',', Value)) = {1}", name, value.Split(',').First()); + else + dashSettings = table.QueryRecordWhere("Name = {0} AND Value = {1}", name, value); + + if ((object)dashSettings == null) + { + dashSettings = new DashSettings(); + dashSettings.Name = name; + dashSettings.Value = value; + dashSettings.Enabled = enabled; + + table.AddNewRecord(dashSettings); + + dashSettings.ID = table.Connection.ExecuteScalar("SELECT @@IDENTITY"); + } + + transactionScope.Complete(); + } + + return dashSettings; + } + + public static UserDashSettings GetOrAdd(this TableOperations table, string name, Guid user, string value, bool enabled = true) + { + TransactionScopeOption required = TransactionScopeOption.Required; + + TransactionOptions transactionOptions = new TransactionOptions() + { + IsolationLevel = IsolationLevel.ReadCommitted, + Timeout = TransactionManager.MaximumTimeout + }; + + UserDashSettings dashSettings; + + using (TransactionScope transactionScope = new TransactionScope(required, transactionOptions)) + { + if (value.Contains(",")) + dashSettings = table.QueryRecordWhere("Name = {0} AND SUBSTRING(Value, 0, CHARINDEX(',', Value)) = {1} AND UserAccountID = {2}", name, value.Split(',').First(), user); + else + dashSettings = table.QueryRecordWhere("Name = {0} AND Value = {1} AND UserAccountID = {2}", name, value, user); + + if ((object)dashSettings == null) + { + dashSettings = new UserDashSettings(); + dashSettings.Name = name; + dashSettings.Value = value; + dashSettings.Enabled = enabled; + dashSettings.UserAccountID = user; + + table.AddNewRecord(dashSettings); + + dashSettings.ID = table.Connection.ExecuteScalar("SELECT @@IDENTITY"); + } + + transactionScope.Complete(); + } + + return dashSettings; + } + + } + +} \ No newline at end of file diff --git a/src/Libraries/openXDA.Model/Channels/ChannelData.cs b/src/Libraries/openXDA.Model/Channels/ChannelData.cs new file mode 100644 index 00000000..2ce28f94 --- /dev/null +++ b/src/Libraries/openXDA.Model/Channels/ChannelData.cs @@ -0,0 +1,649 @@ +//****************************************************************************************************** +// ChannelData.cs - Gbtc +// +// Copyright © 2017, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 12/12/2019 - C. Lackner +// Generated original version of source code. +// +//****************************************************************************************************** + +using System.Data; +using Gemstone; +using Gemstone.Data; +using Gemstone.Data.DataExtensions; +using Gemstone.Data.Model; +using Ionic.Zlib; + +namespace openXDA.Model +{ + [TableName("ChannelData")] + public class ChannelData + { + #region [ Members ] + + // Nested Types + private class DigitalSection + { + public DateTime Start { get; set; } + public DateTime End { get; set; } + public int NumPoints { get; set; } + public double Value { get; set; } + public static int Size => 2 * sizeof(long) + sizeof(ushort) + sizeof(int); + + public int CopyBytes(byte[] byteArray, int offset, double compressionScale, double compressionOffset) + { + ushort compressedValue = (ushort)Math.Round((Value - compressionOffset) * compressionScale); + const ushort NaNValue = ushort.MaxValue; + + if (compressedValue == NaNValue) + compressedValue--; + + if (double.IsNaN(Value)) + compressedValue = NaNValue; + + int startOffset = offset; + offset += LittleEndian.CopyBytes(Start.Ticks, byteArray, offset); + offset += LittleEndian.CopyBytes(End.Ticks, byteArray, offset); + offset += LittleEndian.CopyBytes(compressedValue, byteArray, offset); + offset += LittleEndian.CopyBytes(NumPoints, byteArray, offset); + return offset - startOffset; + } + + public static DigitalSection FromBytes(byte[] bytes, int offset, double decompressionOffset, double decompressionScale) + { + DigitalSection section = new DigitalSection(); + + section.Start = new DateTime(LittleEndian.ToInt64(bytes, offset)); + offset += sizeof(long); + + section.End = new DateTime(LittleEndian.ToInt64(bytes, offset)); + offset += sizeof(long); + + ushort compressedValue = LittleEndian.ToUInt16(bytes, offset); + section.Value = decompressionScale * compressedValue + decompressionOffset; + offset += sizeof(ushort); + + section.NumPoints = LittleEndian.ToInt32(bytes, offset); + + return section; + } + } + #endregion + + #region [ Properties ] + + [PrimaryKey(true)] + public int ID { get; set; } + + public int SeriesID { get; set; } + + public int EventID { get; set; } + + public byte[] TimeDomainData { get; set; } + + public int MarkedForDeletion { get; set; } + + #endregion + + #region [ Methods ] + + /// + /// Adjusts the TimeDomain Data by Moving it a certain ammount of Time + /// + /// The number of Ticks the Data is moved. For moving it backwards in Time this needs to be < 0 + public void AdjustData(Ticks ticks) + { + // Initially we assume Data is already migrated... + if (TimeDomainData == null) + return; + + Tuple> decompressed = Decompress(TimeDomainData)[0]; + List data = decompressed.Item2; + + foreach (DataPoint dataPoint in data) + dataPoint.Time = dataPoint.Time.AddTicks(ticks); + + TimeDomainData = ToData(data, decompressed.Item1); + } + + #endregion + + #region [ Static ] + + public static List DataFromEvent(int eventID, Func connectionFactory) + { + using (AdoDataConnection connection = connectionFactory()) + { + TableOperations eventTable = new TableOperations(connection); + Event evt = eventTable.QueryRecordWhere("ID = {0}", eventID); + + TableOperations assetTable = new TableOperations(connection); + Asset asset = assetTable.QueryRecordWhere("ID = {0}", evt.AssetID); + asset.ConnectionFactory = connectionFactory; + + List channels = asset.DirectChannels + .Concat(asset.ConnectedChannels) + .Where(channel => channel.MeterID == evt.MeterID) + .ToList(); + + if (!channels.Any()) + return new List(); + + IEnumerable assetIDs = channels + .Select(channel => channel.AssetID) + .Distinct(); + + foreach (int assetID in assetIDs) + MigrateLegacyBlob(connection, evt.FileGroupID, assetID, evt.StartTime); + + // Optimization to avoid individually querying channels that don't have any data + HashSet channelsWithData = QueryChannelsWithData(connection, evt); + channels.RemoveAll(channel => !channelsWithData.Contains(channel.ID)); + + List eventData = new List(); + + foreach (Channel channel in channels) + { + const string DataQueryFormat = + "SELECT ChannelData.TimeDomainData " + + "FROM " + + " ChannelData JOIN " + + " Series ON ChannelData.SeriesID = Series.ID JOIN " + + " Event ON ChannelData.EventID = Event.ID " + + "WHERE " + + " Event.FileGroupID = {0} AND " + + " Series.ChannelID = {1} AND " + + " Event.StartTime = {2}"; + + object startTime2 = ToDateTime2(connection, evt.StartTime); + byte[] timeDomainData = connection.ExecuteScalar(DataQueryFormat, evt.FileGroupID, channel.ID, startTime2); + + if (timeDomainData is null) + continue; + + eventData.Add(timeDomainData); + } + + return eventData; + } + } + + public static byte[] DataFromEvent(int eventID, int channelID, Func connectionFactory) + { + using (AdoDataConnection connection = connectionFactory()) + { + TableOperations eventTable = new TableOperations(connection); + Event evt = eventTable.QueryRecordWhere("ID = {0}", eventID); + MigrateLegacyBlob(connection, evt); + + const string QueryFormat = + "SELECT ChannelData.TimeDomainData " + + "FROM " + + " ChannelData JOIN " + + " Series ON ChannelData.SeriesID = Series.ID " + + "WHERE " + + " ChannelData.EventID = {0} AND " + + " Series.ChannelID = {1}"; + + return connection.ExecuteScalar(QueryFormat, eventID, channelID); + } + } + + private static void MigrateLegacyBlob(AdoDataConnection connection, int fileGroupID, int assetID, DateTime startTime) + { + const string AssetQueryFilter = "FileGroupID = {0} AND AssetID = {1} AND StartTime = {2}"; + object startTime2 = ToDateTime2(connection, startTime); + + TableOperations eventTable = new TableOperations(connection); + Event evt = eventTable.QueryRecordWhere(AssetQueryFilter, fileGroupID, assetID, startTime2); + MigrateLegacyBlob(connection, evt); + } + + private static void MigrateLegacyBlob(AdoDataConnection connection, Event evt) + { + if (evt is null || evt.EventDataID is null) + return; + + int eventDataID = evt.EventDataID.GetValueOrDefault(); + byte[] timeDomainData = connection.ExecuteScalar("SELECT TimeDomainData FROM EventData WHERE ID = {0}", eventDataID); + List>> decompressedData = Decompress(timeDomainData); + + TableOperations channelDataTable = new TableOperations(connection); + + foreach (Tuple> tuple in decompressedData) + { + int seriesID = tuple.Item1; + List data = tuple.Item2; + + ChannelData channelData = new ChannelData(); + channelData.SeriesID = seriesID; + channelData.EventID = evt.ID; + channelData.TimeDomainData = ToData(data, seriesID); + channelDataTable.AddNewRecord(channelData); + } + + connection.ExecuteNonQuery("UPDATE Event SET EventDataID = NULL WHERE ID = {0}", evt.ID); + connection.ExecuteNonQuery("DELETE FROM EventData WHERE ID = {0}", eventDataID); + } + + /// + /// Turns a list of DataPoints into a blob to be saved in the database. + /// + /// The data as a + /// The SeriesID to be encoded into the blob + /// The byte array to be saved as a blob in the database. + public static byte[] ToData(List data, int seriesID) + { + // We can use Digital compression if the data changes no more than 10% of the time. + bool useDigitalCompression = data + .Skip(1) + .Zip(data, (p2, p1) => new { p1, p2 }) + .Where(obj => obj.p1.Value != obj.p2.Value) + .Select((_, index) => index + 1) + .All(nChanges => nChanges <= 0.1 * data.Count); + + if (useDigitalCompression) + return ToDigitalData(data, seriesID); + + var timeSeries = data.Select(dataPoint => new { Time = dataPoint.Time.Ticks, Compressed = false }).ToList(); + + for (int i = 1; i < timeSeries.Count; i++) + { + long previousTimestamp = data[i - 1].Time.Ticks; + long timestamp = timeSeries[i].Time; + long diff = timestamp - previousTimestamp; + + if (diff >= 0 && diff <= ushort.MaxValue) + timeSeries[i] = new { Time = diff, Compressed = true }; + } + + int timeSeriesByteLength = timeSeries.Sum(obj => obj.Compressed ? sizeof(ushort) : sizeof(int) + sizeof(long)); + int dataSeriesByteLength = sizeof(int) + (2 * sizeof(double)) + (data.Count * sizeof(ushort)); + int totalByteLength = sizeof(int) + timeSeriesByteLength + dataSeriesByteLength; + + byte[] result = new byte[totalByteLength]; + int offset = 0; + + offset += LittleEndian.CopyBytes(data.Count, result, offset); + + List uncompressedIndexes = timeSeries + .Select((obj, Index) => new { obj.Compressed, Index }) + .Where(obj => !obj.Compressed) + .Select(obj => obj.Index) + .ToList(); + + for (int i = 0; i < uncompressedIndexes.Count; i++) + { + int index = uncompressedIndexes[i]; + int nextIndex = (i + 1 < uncompressedIndexes.Count) ? uncompressedIndexes[i + 1] : timeSeries.Count; + + offset += LittleEndian.CopyBytes(nextIndex - index, result, offset); + offset += LittleEndian.CopyBytes(timeSeries[index].Time, result, offset); + + for (int j = index + 1; j < nextIndex; j++) + offset += LittleEndian.CopyBytes((ushort)timeSeries[j].Time, result, offset); + } + + const ushort NaNValue = ushort.MaxValue; + const ushort MaxCompressedValue = ushort.MaxValue - 1; + double range = data.Select(item => item.Value).Max() - data.Select(item => item.Value).Min(); + double decompressionOffset = data.Select(item => item.Value).Min(); + double decompressionScale = range / MaxCompressedValue; + double compressionScale = (decompressionScale != 0.0D) ? 1.0D / decompressionScale : 0.0D; + + offset += LittleEndian.CopyBytes(seriesID, result, offset); + offset += LittleEndian.CopyBytes(decompressionOffset, result, offset); + offset += LittleEndian.CopyBytes(decompressionScale, result, offset); + + foreach (DataPoint dataPoint in data) + { + ushort compressedValue = (ushort)Math.Round((dataPoint.Value - decompressionOffset) * compressionScale); + + if (compressedValue == NaNValue) + compressedValue--; + + if (double.IsNaN(dataPoint.Value)) + compressedValue = NaNValue; + + offset += LittleEndian.CopyBytes(compressedValue, result, offset); + } + + byte[] returnArray = GZipStream.CompressBuffer(result); + returnArray[0] = 0x44; + returnArray[1] = 0x33; + + return returnArray; + } + + private static byte[] ToDigitalData(List data, int seriesID) + { + List digitalData = new List(); + DigitalSection currentSection = null; + foreach (DataPoint dataPoint in data) + { + if (currentSection is null) + { + currentSection = new DigitalSection() + { + Start = dataPoint.Time, + End = dataPoint.Time, + Value = dataPoint.Value, + NumPoints = 1 + }; + } + else if (currentSection.Value != dataPoint.Value) + { + digitalData.Add(currentSection); + currentSection = new DigitalSection() + { + Start = dataPoint.Time, + End = dataPoint.Time, + Value = dataPoint.Value, + NumPoints = 1 + }; + } + else + { + currentSection.NumPoints++; + currentSection.End = dataPoint.Time; + } + } + + if (!(currentSection is null)) + digitalData.Add(currentSection); + + int totalByteLength = sizeof(int) + 2 * sizeof(double) + digitalData.Count * DigitalSection.Size; + byte[] result = new byte[totalByteLength]; + int offset = 0; + + const ushort MaxCompressedValue = ushort.MaxValue - 1; + double range = data.Select(item => item.Value).Max() - data.Select(item => item.Value).Min(); + double decompressionOffset = data.Select(item => item.Value).Min(); + double decompressionScale = range / MaxCompressedValue; + double compressionScale = (decompressionScale != 0.0D) ? 1.0D / decompressionScale : 0.0D; + + offset += LittleEndian.CopyBytes(seriesID, result, offset); + offset += LittleEndian.CopyBytes(decompressionOffset, result, offset); + offset += LittleEndian.CopyBytes(decompressionScale, result, offset); + + foreach (DigitalSection digitalSection in digitalData) + offset += digitalSection.CopyBytes(result, offset, compressionScale, decompressionOffset); + + byte[] returnArray = GZipStream.CompressBuffer(result); + returnArray[0] = DigitalHeader[0]; + returnArray[1] = DigitalHeader[1]; + return returnArray; + } + + /// + /// Decompresses a byte array into a List of DataPoints + /// + /// The byte array filled with compressed data + /// List of data series consisting of series ID and data points. + public static List>> Decompress(byte[] data) + { + List>> result = new List>>(); + + if (data == null) + return result; + // If the blob contains the GZip header, + // use the legacy deserialization algorithm + if (data[0] == LegacyHeader[0] && data[1] == LegacyHeader[1]) + { + return Decompress_Legacy(data); + } + // If this blob uses digital decompression use that algorithm + if (data[0] == DigitalHeader[0] && data[1] == DigitalHeader[1]) + { + return Decompress_Digital(data); + } + + // Restore the GZip header before uncompressing + data[0] = LegacyHeader[0]; + data[1] = LegacyHeader[1]; + + byte[] uncompressedData; + int offset; + + uncompressedData = GZipStream.UncompressBuffer(data); + offset = 0; + + int m_samples = LittleEndian.ToInt32(uncompressedData, offset); + offset += sizeof(int); + + List times = new List(); + + while (times.Count < m_samples) + { + int timeValues = LittleEndian.ToInt32(uncompressedData, offset); + offset += sizeof(int); + + long currentValue = LittleEndian.ToInt64(uncompressedData, offset); + offset += sizeof(long); + times.Add(new DateTime(currentValue)); + + for (int i = 1; i < timeValues; i++) + { + currentValue += LittleEndian.ToUInt16(uncompressedData, offset); + offset += sizeof(ushort); + times.Add(new DateTime(currentValue)); + } + } + + while (offset < uncompressedData.Length) + { + List dataSeries = new List(); + int seriesID = LittleEndian.ToInt32(uncompressedData, offset); + offset += sizeof(int); + + + const ushort NaNValue = ushort.MaxValue; + double decompressionOffset = LittleEndian.ToDouble(uncompressedData, offset); + double decompressionScale = LittleEndian.ToDouble(uncompressedData, offset + sizeof(double)); + offset += 2 * sizeof(double); + + for (int i = 0; i < m_samples; i++) + { + ushort compressedValue = LittleEndian.ToUInt16(uncompressedData, offset); + offset += sizeof(ushort); + + double decompressedValue = decompressionScale * compressedValue + decompressionOffset; + + if (compressedValue == NaNValue) + decompressedValue = double.NaN; + + dataSeries.Add(new DataPoint() + { + Time = times[i], + Value = decompressedValue + }); + } + + result.Add(new Tuple>(seriesID, dataSeries)); + } + + return result; + } + + private static List>> Decompress_Legacy(byte[] data) + { + List>> result = new List>>(); + byte[] uncompressedData; + int offset; + DateTime[] times; + int seriesID; + + uncompressedData = GZipStream.UncompressBuffer(data); + offset = 0; + + int m_samples = LittleEndian.ToInt32(uncompressedData, offset); + offset += sizeof(int); + + times = new DateTime[m_samples]; + + for (int i = 0; i < m_samples; i++) + { + times[i] = new DateTime(LittleEndian.ToInt64(uncompressedData, offset)); + offset += sizeof(long); + } + + while (offset < uncompressedData.Length) + { + seriesID = LittleEndian.ToInt32(uncompressedData, offset); + offset += sizeof(int); + + List points = new List(); + + for (int i = 0; i < m_samples; i++) + { + points.Add(new DataPoint() + { + Time = times[i], + Value = LittleEndian.ToDouble(uncompressedData, offset) + }); + + offset += sizeof(double); + } + + result.Add(new Tuple>(seriesID, points)); + } + return result; + } + + /// + /// Decompresses a Digital stored as compresed series of changes + /// + /// The compressed + /// a Dictionary mapping a SeriesID to a decopmressed + private static List>> Decompress_Digital(byte[] data) + { + List>> result = new List>>(); + byte[] uncompressedData; + int offset; + int seriesID; + List points = new List(); + + // Restore the GZip header before uncompressing + data[0] = LegacyHeader[0]; + data[1] = LegacyHeader[1]; + + uncompressedData = GZipStream.UncompressBuffer(data); + offset = 0; + + seriesID = LittleEndian.ToInt32(uncompressedData, offset); + offset += sizeof(int); + + double decompressionOffset = LittleEndian.ToDouble(uncompressedData, offset); + double decompressionScale = LittleEndian.ToDouble(uncompressedData, offset + sizeof(double)); + offset += 2 * sizeof(double); + + while(offset < uncompressedData.Length) + { + DigitalSection section = DigitalSection.FromBytes(uncompressedData, offset, decompressionOffset, decompressionScale); + offset += DigitalSection.Size; + + points.Add(new DataPoint() + { + Time = section.Start, + Value = section.Value + }); + + if (section.NumPoints == 1) + continue; + + // Use a fixed-point offset with 6 bits of additional + // precision to help avoid accumulation of rounding errors + long diff = (section.End - section.Start).Ticks << 6; + long step = diff / (section.NumPoints - 1); + long lastOffset = step; + + for (int i = 1; i < section.NumPoints - 1; i++) + { + points.Add(new DataPoint() + { + Time = section.Start.AddTicks(lastOffset >> 6), + Value = section.Value + }); + + lastOffset += step; + } + + points.Add(new DataPoint() + { + Time = section.End, + Value = section.Value + }); + } + + result.Add(new Tuple>(seriesID, points)); + + return result; + } + + private static HashSet QueryChannelsWithData(AdoDataConnection connection, Event evt) + { + const string FilterQueryFormat = + "SELECT Series.ChannelID " + + "FROM " + + " ChannelData JOIN " + + " Series ON ChannelData.SeriesID = Series.ID JOIN " + + " Event ON ChannelData.EventID = Event.ID " + + "WHERE " + + " Event.FileGroupID = {0} AND " + + " Event.StartTime = {1}"; + + object startTime2 = ToDateTime2(connection, evt.StartTime); + + using (DataTable table = connection.RetrieveData(FilterQueryFormat, evt.FileGroupID, startTime2)) + { + IEnumerable channelsWithData = table + .AsEnumerable() + .Select(row => row.ConvertField("ChannelID")); + + return new HashSet(channelsWithData); + } + } + + private static object ToDateTime2(AdoDataConnection connection, DateTime dateTime) + { + using (IDbCommand command = connection.Connection.CreateCommand()) + { + IDbDataParameter parameter = command.CreateParameter(); + parameter.DbType = DbType.DateTime2; + parameter.Value = dateTime; + return parameter; + } + } + + /// + /// The header of a datablob compressed as analog Data + /// + public static readonly byte[] AnalogHeader = { 0x11, 0x11 }; + + /// + /// The header of a datablob compressed as Digital State Changes + /// + public static readonly byte[] DigitalHeader = { 0x22, 0x22 }; + + /// + /// The header of a datablob compressed as Legacy Data + /// + public static readonly byte[] LegacyHeader = { 0x1F, 0x8B }; + + #endregion + } +} diff --git a/src/Libraries/openXDA.Model/Channels/DataPoint.cs b/src/Libraries/openXDA.Model/Channels/DataPoint.cs new file mode 100644 index 00000000..88c39332 --- /dev/null +++ b/src/Libraries/openXDA.Model/Channels/DataPoint.cs @@ -0,0 +1,110 @@ +//****************************************************************************************************** +// DataPoint.cs - Gbtc +// +// Copyright © 2025, 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://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/15/2025 - C. Lackner +// Generated original version of source code. +// +//****************************************************************************************************** + +namespace openXDA.Model +{ + /// + /// Represents a single data point in a time series. + /// + public class DataPoint + { + #region [ Properties ] + + public DateTime Time { get; set; } + public double Value { get; set; } + + #endregion + + #region [ Methods ] + + public DataPoint Shift(TimeSpan timeShift) + { + return new DataPoint() + { + Time = Time.Add(timeShift), + Value = Value + }; + } + + public DataPoint Negate() + { + return new DataPoint() + { + Time = Time, + Value = -Value + }; + } + + public DataPoint Add(DataPoint point) + { + if (Time != point.Time) + throw new InvalidOperationException("Cannot add datapoints with mismatched times"); + + return new DataPoint() + { + Time = Time, + Value = Value + point.Value + }; + } + + public DataPoint Subtract(DataPoint point) + { + return Add(point.Negate()); + } + + public DataPoint Add(double value) + { + return new DataPoint() + { + Time = Time, + Value = Value + value + }; + } + + public DataPoint Subtract(double value) + { + return Add(-value); + } + + public DataPoint Multiply(double value) + { + return new DataPoint() + { + Time = Time, + Value = Value * value + }; + } + + public bool LargerThan(double comparison) + { + return Value > comparison; + } + + public bool LargerThan(DataPoint point) + { + return LargerThan(point.Value); + } + + #endregion + } +} diff --git a/src/Libraries/openXDA.Model/Channels/MeasurementCharacteristic.cs b/src/Libraries/openXDA.Model/Channels/MeasurementCharacteristic.cs new file mode 100644 index 00000000..a3e49898 --- /dev/null +++ b/src/Libraries/openXDA.Model/Channels/MeasurementCharacteristic.cs @@ -0,0 +1,45 @@ +//****************************************************************************************************** +// MeasurementCharacteristic.cs - Gbtc +// +// Copyright © 2017, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 08/29/2017 - Billy Ernest +// Generated original version of source code. +// +//****************************************************************************************************** + +using System.ComponentModel.DataAnnotations; +using Gemstone.Data.Model; + +namespace openXDA.Model +{ + [PostRoles("Administrator, Transmission SME")] + [DeleteRoles("Administrator, Transmission SME")] + [PatchRoles("Administrator, Transmission SME")] + public class MeasurementCharacteristic + { + [PrimaryKey(true)] + public int ID { get; set; } + + [StringLength(200)] + [DefaultSortOrder] + public string Name { get; set; } + + public string Description { get; set; } + + public bool Display { get; set; } + } +} diff --git a/src/Libraries/openXDA.Model/Channels/MeasurementType.cs b/src/Libraries/openXDA.Model/Channels/MeasurementType.cs new file mode 100644 index 00000000..20d3ea30 --- /dev/null +++ b/src/Libraries/openXDA.Model/Channels/MeasurementType.cs @@ -0,0 +1,44 @@ +//****************************************************************************************************** +// MeasurementType.cs - Gbtc +// +// Copyright © 2017, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 08/29/2017 - Billy Ernest +// Generated original version of source code. +// +//****************************************************************************************************** + +using System; +using System.ComponentModel.DataAnnotations; +using Gemstone.Data.Model; + +namespace openXDA.Model +{ + [PostRoles("Administrator, Transmission SME")] + [DeleteRoles("Administrator, Transmission SME")] + [PatchRoles("Administrator, Transmission SME")] + public class MeasurementType + { + [PrimaryKey(true)] + public int ID { get; set; } + + [StringLength(200)] + [DefaultSortOrder] + public string Name { get; set; } + + public string Description { get; set; } + } +} diff --git a/src/Libraries/openXDA.Model/Channels/Phase.cs b/src/Libraries/openXDA.Model/Channels/Phase.cs new file mode 100644 index 00000000..705ec274 --- /dev/null +++ b/src/Libraries/openXDA.Model/Channels/Phase.cs @@ -0,0 +1,43 @@ +//****************************************************************************************************** +// Phase.cs - Gbtc +// +// Copyright © 2017, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 08/29/2017 - Billy Ernest +// Generated original version of source code. +// +//****************************************************************************************************** + +using System.ComponentModel.DataAnnotations; +using Gemstone.Data.Model; + +namespace openXDA.Model +{ + [PostRoles("Administrator, Transmission SME")] + [DeleteRoles("Administrator, Transmission SME")] + [PatchRoles("Administrator, Transmission SME")] + public class Phase + { + [PrimaryKey(true)] + public int ID { get; set; } + + [StringLength(200)] + [DefaultSortOrder] + public string Name { get; set; } + + public string Description { get; set; } + } +} diff --git a/src/Libraries/openXDA.Model/Channels/Series.cs b/src/Libraries/openXDA.Model/Channels/Series.cs new file mode 100644 index 00000000..d6c96a6c --- /dev/null +++ b/src/Libraries/openXDA.Model/Channels/Series.cs @@ -0,0 +1,248 @@ +//****************************************************************************************************** +// Series.cs - Gbtc +// +// Copyright © 2017, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 06/20/2017 - Billy Ernest +// Generated original version of source code. +// +//****************************************************************************************************** + +using System.Data; +using Gemstone.Data; +using Gemstone.Data.Model; +using Newtonsoft.Json; + +namespace openXDA.Model +{ + public class SeriesKey : IEquatable + { + #region [ Constructors ] + + public SeriesKey(ChannelKey channelKey, string seriesType) + { + ChannelKey = channelKey; + SeriesType = seriesType; + } + + public SeriesKey(Series series) + : this(new ChannelKey(series.Channel), series.SeriesType.Name) + { + } + + #endregion + + #region [ Properties ] + + public ChannelKey ChannelKey { get; } + public string SeriesType { get; } + + #endregion + + #region [ Methods ] + + public Series Find(AdoDataConnection connection, int meterID) + { + const string QueryFormat = + "SELECT Series.* " + + "FROM " + + " Series JOIN " + + " Channel ON Series.ChannelID = Channel.ID JOIN " + + " MeasurementType ON Channel.MeasurementTypeID = MeasurementType.ID JOIN " + + " MeasurementCharacteristic ON Channel.MeasurementCharacteristicID = MeasurementCharacteristic.ID JOIN " + + " Phase ON Channel.PhaseID = Phase.ID JOIN " + + " SeriesType ON Series.SeriesTypeID = SeriesType.ID " + + "WHERE " + + " Channel.MeterID = {0} AND " + + " Channel.AssetID = {1} AND " + + " Channel.HarmonicGroup = {2} AND " + + " Channel.Name = {3} AND " + + " MeasurementType.Name = {4} AND " + + " MeasurementCharacteristic.Name = {5} AND " + + " Phase.Name = {6} AND " + + " SeriesType.Name = {7}"; + + object[] parameters = + { + meterID, + ChannelKey.LineID, + ChannelKey.HarmonicGroup, + ChannelKey.Name, + ChannelKey.MeasurementType, + ChannelKey.MeasurementCharacteristic, + ChannelKey.Phase, + SeriesType + }; + + using (DataTable table = connection.RetrieveData(QueryFormat, parameters)) + { + if (table.Rows.Count == 0) + return null; + + TableOperations seriesTable = new TableOperations(connection); + return seriesTable.LoadRecord(table.Rows[0]); + } + } + + public override int GetHashCode() + { + StringComparer stringComparer = StringComparer.OrdinalIgnoreCase; + + int hash = 1009; + hash = 9176 * hash + ChannelKey.GetHashCode(); + hash = 9176 * hash + stringComparer.GetHashCode(SeriesType); + return hash; + } + + public override bool Equals(object obj) + { + return Equals(obj as SeriesKey); + } + + public bool Equals(SeriesKey other) + { + if (other is null) + return false; + + StringComparison stringComparison = StringComparison.OrdinalIgnoreCase; + + return + ChannelKey.Equals(other.ChannelKey) && + SeriesType.Equals(other.SeriesType, stringComparison); + } + + #endregion + } + + public class Series + { + #region [ Members ] + + // Fields + private SeriesType m_seriesType; + private Channel m_channel; + + #endregion + + #region [ Properties ] + + [PrimaryKey(true)] + public int ID { get; set; } + + public int ChannelID { get; set; } + + public int SeriesTypeID { get; set; } + + public string SourceIndexes { get; set; } + + [JsonIgnore] + [NonRecordField] + public SeriesType SeriesType + { + get + { + if (m_seriesType is null) + m_seriesType = LazyContext.GetSeriesType(SeriesTypeID); + + if (m_seriesType is null) + m_seriesType = QuerySeriesType(); + + return m_seriesType; + } + set => m_seriesType = value; + } + + [JsonIgnore] + [NonRecordField] + public Channel Channel + { + get + { + if (m_channel is null) + m_channel = LazyContext.GetChannel(ChannelID); + + if (m_channel is null) + m_channel = QueryChannel(); + + return m_channel; + } + set => m_channel = value; + } + + [JsonIgnore] + [NonRecordField] + public Func ConnectionFactory + { + get => LazyContext.ConnectionFactory; + set => LazyContext.ConnectionFactory = value; + } + + [JsonIgnore] + [NonRecordField] + internal LazyContext LazyContext { get; set; } = new LazyContext(); + + #endregion + + #region [ Methods ] + + public SeriesType GetSeriesType(AdoDataConnection connection) + { + if ((object)connection == null) + return null; + + TableOperations seriesTypeTable = new TableOperations(connection); + return seriesTypeTable.QueryRecordWhere("ID = {0}", SeriesTypeID); + } + + public Channel GetChannel(AdoDataConnection connection) + { + if ((object)connection == null) + return null; + + TableOperations channelTable = new TableOperations(connection); + return channelTable.QueryRecordWhere("ID = {0}", ChannelID); + } + + private SeriesType QuerySeriesType() + { + SeriesType seriesType; + + using (AdoDataConnection connection = ConnectionFactory?.Invoke()) + { + seriesType = GetSeriesType(connection); + } + + return LazyContext.GetSeriesType(seriesType); + } + + private Channel QueryChannel() + { + Channel channel; + + using (AdoDataConnection connection = ConnectionFactory?.Invoke()) + { + channel = GetChannel(connection); + } + + if ((object)channel != null) + channel.LazyContext = LazyContext; + + return LazyContext.GetChannel(channel); + } + + #endregion + } +} diff --git a/src/Libraries/openXDA.Model/Channels/SeriesType.cs b/src/Libraries/openXDA.Model/Channels/SeriesType.cs new file mode 100644 index 00000000..c7621b5d --- /dev/null +++ b/src/Libraries/openXDA.Model/Channels/SeriesType.cs @@ -0,0 +1,40 @@ +//****************************************************************************************************** +// SeriesType.cs - Gbtc +// +// Copyright © 2017, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 08/29/2017 - Billy Ernest +// Generated original version of source code. +// +//****************************************************************************************************** + +using System.ComponentModel.DataAnnotations; +using Gemstone.Data.Model; + +namespace openXDA.Model +{ + [TableName("SeriesType")] + public class SeriesType + { + [PrimaryKey(true)] + public int ID { get; set; } + + [StringLength(200)] + public string Name { get; set; } + + public string Description { get; set; } + } +} \ No newline at end of file diff --git a/src/Libraries/openXDA.Model/Events/Event.cs b/src/Libraries/openXDA.Model/Events/Event.cs new file mode 100644 index 00000000..0b60dc4b --- /dev/null +++ b/src/Libraries/openXDA.Model/Events/Event.cs @@ -0,0 +1,72 @@ +//****************************************************************************************************** +// Event.cs - Gbtc +// +// Copyright © 2017, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 08/29/2017 - Billy Ernest +// Generated original version of source code. +// +//****************************************************************************************************** + +using System.Data; +using Gemstone.Data; +using Gemstone.Data.Model; + +namespace openXDA.Model +{ + [TableName("Event")] + public class Event + { + [PrimaryKey(true)] + public int ID { get; set; } + + public int FileGroupID { get; set; } + + public int MeterID { get; set; } + + public int AssetID { get; set; } + + public int EventTypeID { get; set; } + + public int? EventDataID { get; set; } + + public string Name { get; set; } + + public string Alias { get; set; } + + public string ShortName { get; set; } + + [FieldDataType(DbType.DateTime2, DatabaseType.SQLServer)] + public DateTime StartTime { get; set; } + + [FieldDataType(DbType.DateTime2, DatabaseType.SQLServer)] + public DateTime EndTime { get; set; } + + public int Samples { get; set; } + + public int TimeZoneOffset { get; set; } + + public int SamplesPerSecond { get; set; } + + public int SamplesPerCycle { get; set; } + + public string Description { get; set; } + + public int FileVersion { get; set; } + + public string UpdatedBy { get; set; } + } +} \ No newline at end of file diff --git a/src/Libraries/openXDA.Model/Files/DataFile.cs b/src/Libraries/openXDA.Model/Files/DataFile.cs new file mode 100644 index 00000000..10b9d4b5 --- /dev/null +++ b/src/Libraries/openXDA.Model/Files/DataFile.cs @@ -0,0 +1,93 @@ +//****************************************************************************************************** +// DataFile.cs - Gbtc +// +// Copyright © 2017, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 08/29/2017 - Billy Ernest +// Generated original version of source code. +// +//****************************************************************************************************** + +using System.Text; +using Gemstone.Data.Model; +using Gemstone.IO.Checksums; +using Newtonsoft.Json; + +namespace openXDA.Model +{ + [Serializable] + public class DataFile + { + [PrimaryKey(true)] + public int ID { get; set; } + + public int FileGroupID { get; set; } + + public string FilePath { get; set; } + + public int FilePathHash { get; set; } + + public long FileSize { get; set; } + + public DateTime CreationTime { get; set; } + + public DateTime LastWriteTime { get; set; } + + public DateTime LastAccessTime { get; set; } + + [NonRecordField] + [JsonIgnore] + public FileBlob FileBlob { get; set; } + + public static int GetHash(string filePath) + { + Encoding utf8 = new UTF8Encoding(false); + byte[] pathData = utf8.GetBytes(filePath); + return unchecked((int)Crc32.Compute(pathData, 0, pathData.Length)); + } + } + + [TableName("DataFile")] + public class DataFileDb : DataFile { } + + public static partial class TableOperationsExtensions + { + public static DataFile QueryDataFile(this TableOperations dataFileTable, string filePath) + { + int hashCode = DataFile.GetHash(filePath); + DataFile dataFile = QueryDataFile(dataFileTable, filePath, hashCode); + + if (dataFile != null) + return dataFile; + + int legacyHashCode = filePath.GetHashCode(); + dataFile = QueryDataFile(dataFileTable, filePath, legacyHashCode); + + if (dataFile == null) + return null; + + dataFile.FilePathHash = hashCode; + dataFileTable.UpdateRecord(dataFile); + return dataFile; + } + + private static DataFile QueryDataFile(TableOperations dataFileTable, string filePath, int hashCode) + { + IEnumerable dataFiles = dataFileTable.QueryRecordsWhere("FilePathHash = {0}", hashCode); + return dataFiles.FirstOrDefault(dataFile => dataFile.FilePath == filePath); + } + } +} diff --git a/src/Libraries/openXDA.Model/Files/FileBlob.cs b/src/Libraries/openXDA.Model/Files/FileBlob.cs new file mode 100644 index 00000000..00beeef8 --- /dev/null +++ b/src/Libraries/openXDA.Model/Files/FileBlob.cs @@ -0,0 +1,38 @@ +//****************************************************************************************************** +// FileBlob.cs - Gbtc +// +// Copyright © 2017, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 08/29/2017 - Billy Ernest +// Generated original version of source code. +// +//****************************************************************************************************** + +using Gemstone.Data.Model; + +namespace openXDA.Model +{ + [Serializable] + public class FileBlob + { + [PrimaryKey(true)] + public int ID { get; set; } + + public int DataFileID { get; set; } + + public byte[] Blob { get; set; } + } +} \ No newline at end of file diff --git a/src/Libraries/openXDA.Model/Files/FileGroup.cs b/src/Libraries/openXDA.Model/Files/FileGroup.cs new file mode 100644 index 00000000..d6c50ab2 --- /dev/null +++ b/src/Libraries/openXDA.Model/Files/FileGroup.cs @@ -0,0 +1,102 @@ +//****************************************************************************************************** +// FileGroup.cs - Gbtc +// +// Copyright © 2017, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 08/29/2017 - Billy Ernest +// Generated original version of source code. +// +//****************************************************************************************************** + +using Gemstone.Data; +using Gemstone.Data.Model; + +namespace openXDA.Model +{ + [Serializable] + public class FileGroup + { + [PrimaryKey(true)] + public int ID { get; set; } + + public int MeterID { get; set; } + + [FieldDataType(System.Data.DbType.DateTime2, DatabaseType.SQLServer)] + public DateTime DataStartTime { get; set; } + + [FieldDataType(System.Data.DbType.DateTime2, DatabaseType.SQLServer)] + public DateTime DataEndTime { get; set; } + + [FieldDataType(System.Data.DbType.DateTime2, DatabaseType.SQLServer)] + public DateTime ProcessingStartTime { get; set; } + + [FieldDataType(System.Data.DbType.DateTime2, DatabaseType.SQLServer)] + public DateTime ProcessingEndTime { get; set; } + + public int ProcessingVersion { get; set; } + + public int ProcessingStatus { get; set; } + + [NonRecordField] + public List DataFiles { get; set; } = new List(); + + public void AddFieldValue(AdoDataConnection connection, string name, string value, string description = null) + { + TableOperations fileGroupFieldTable = new TableOperations(connection); + FileGroupField fileGroupField = fileGroupFieldTable.GetOrAdd(name, description); + + TableOperations fileGroupFieldValueTable = new TableOperations(connection); + FileGroupFieldValue fileGroupFieldValue = new FileGroupFieldValue(); + fileGroupFieldValue.FileGroupID = ID; + fileGroupFieldValue.FileGroupFieldID = fileGroupField.ID; + fileGroupFieldValue.Value = value; + fileGroupFieldValueTable.AddNewRecord(fileGroupFieldValue); + } + + public void AddOrUpdateFieldValue(AdoDataConnection connection, string name, string value, string description = null) + { + TableOperations fileGroupFieldTable = new TableOperations(connection); + FileGroupField fileGroupField = fileGroupFieldTable.GetOrAdd(name, description); + + TableOperations fileGroupFieldValueTable = new TableOperations(connection); + RecordRestriction fileGroupRestriction = new RecordRestriction("FileGroupID = {0}", ID); + RecordRestriction fileGroupFieldRestriction = new RecordRestriction("FileGroupFieldID = {0}", fileGroupField.ID); + RecordRestriction queryRestriction = fileGroupRestriction & fileGroupFieldRestriction; + + FileGroupFieldValue fileGroupFieldValue = fileGroupFieldValueTable.QueryRecord(queryRestriction) ?? new FileGroupFieldValue() + { + FileGroupID = ID, + FileGroupFieldID = fileGroupField.ID + }; + + fileGroupFieldValue.Value = value; + fileGroupFieldValueTable.AddNewOrUpdateRecord(fileGroupFieldValue); + } + } + + /// + /// Number indicating the processing status of a file group. + /// + public enum FileGroupProcessingStatus + { + Created = 0, + Queued = 1, + Processing = 2, + Success = 3, + Failed = 4, + PartialSuccess = 5 + } +} diff --git a/src/Libraries/openXDA.Model/Files/FileGroupField.cs b/src/Libraries/openXDA.Model/Files/FileGroupField.cs new file mode 100644 index 00000000..31021f88 --- /dev/null +++ b/src/Libraries/openXDA.Model/Files/FileGroupField.cs @@ -0,0 +1,61 @@ +//****************************************************************************************************** +// FileGroupField.cs - Gbtc +// +// Copyright © 2019, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 06/18/2019 - Stephen C. Wills +// Generated original version of source code. +// +//****************************************************************************************************** + +using System; +using System.ComponentModel.DataAnnotations; +using Gemstone.Data.Model; + +namespace openXDA.Model +{ + public class FileGroupField + { + [PrimaryKey(true)] + public int ID { get; set; } + + [StringLength(200)] + public string Name { get; set; } + + public string Description { get; set; } + } + + public static partial class TableOperationsExtensions + { + public static FileGroupField GetOrAdd(this TableOperations fileGroupFieldTable, string name, string description = null) + { + FileGroupField fileGroupField = fileGroupFieldTable.QueryRecordWhere("Name = {0}", name); + + if ((object)fileGroupField == null) + { + fileGroupField = new FileGroupField(); + fileGroupField.Name = name; + fileGroupField.Description = description; + + fileGroupFieldTable.AddNewRecord(fileGroupField); + + fileGroupField.ID = fileGroupFieldTable.Connection.ExecuteScalar("SELECT @@IDENTITY"); + } + + return fileGroupField; + } + } +} diff --git a/src/Libraries/openXDA.Model/Files/FileGroupFieldValue.cs b/src/Libraries/openXDA.Model/Files/FileGroupFieldValue.cs new file mode 100644 index 00000000..55140bae --- /dev/null +++ b/src/Libraries/openXDA.Model/Files/FileGroupFieldValue.cs @@ -0,0 +1,39 @@ +//****************************************************************************************************** +// FileGroupFieldValue.cs - Gbtc +// +// Copyright © 2019, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 06/18/2019 - Stephen C. Wills +// Generated original version of source code. +// +//****************************************************************************************************** + +using Gemstone.Data.Model; + +namespace openXDA.Model +{ + public class FileGroupFieldValue + { + [PrimaryKey(true)] + public int ID { get; set; } + + public int FileGroupID { get; set; } + + public int FileGroupFieldID { get; set; } + + public string Value { get; set; } + } +} diff --git a/src/Libraries/openXDA.Model/LazyContext.cs b/src/Libraries/openXDA.Model/LazyContext.cs new file mode 100644 index 00000000..6f5c7acc --- /dev/null +++ b/src/Libraries/openXDA.Model/LazyContext.cs @@ -0,0 +1,369 @@ +//****************************************************************************************************** +// LazyContext.cs - Gbtc +// +// Copyright © 2017, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 09/04/2017 - Stephen C. Wills +// Generated original version of source code. +// +//****************************************************************************************************** + +using System; +using System.Collections.Generic; +using Gemstone.Data; + +namespace openXDA.Model +{ + internal class LazyContext + { + #region [ Members ] + + // Fields + private Dictionary m_locations; + private Dictionary m_meters; + private Dictionary m_assetLocations; + private Dictionary m_sourceImpedances; + private Dictionary m_meterAssets; + private Dictionary m_channels; + private Dictionary m_series; + private Dictionary m_measurementTypes; + private Dictionary m_measurementCharacteristics; + private Dictionary m_phases; + private Dictionary m_seriesTypes; + + private Dictionary m_assets; + private Dictionary m_assetConnections; + + #endregion + + #region [ Constructors ] + + public LazyContext() + { + m_locations = new Dictionary(); + m_meters = new Dictionary(); + m_assets = new Dictionary(); + m_assetLocations = new Dictionary(); + m_sourceImpedances = new Dictionary(); + m_meterAssets = new Dictionary(); + m_channels = new Dictionary(); + m_series = new Dictionary(); + m_measurementTypes = new Dictionary(); + m_measurementCharacteristics = new Dictionary(); + m_phases = new Dictionary(); + m_seriesTypes = new Dictionary(); + m_assetConnections = new Dictionary(); + } + + #endregion + + #region [ Properties ] + + public Func ConnectionFactory { get; set; } + + #endregion + + #region [ Methods ] + + public Location GetLocation(int locationID) => + m_locations.TryGetValue(locationID, out Location location) + ? location + : null; + + public Location GetLocation(Location location) + { + Location cachedLocation; + + if ((object)location == null) + return null; + + if (location.ID == 0) + return location; + + if (m_locations.TryGetValue(location.ID, out cachedLocation)) + return cachedLocation; + + m_locations.Add(location.ID, location); + return location; + } + + public Meter GetMeter(int meterID) => + m_meters.TryGetValue(meterID, out Meter meter) + ? meter + : null; + + public Meter GetMeter(Meter meter) + { + Meter cachedMeter; + + if ((object)meter == null) + return null; + + if (meter.ID == 0) + return meter; + + if (m_meters.TryGetValue(meter.ID, out cachedMeter)) + return cachedMeter; + + m_meters.Add(meter.ID, meter); + return meter; + } + + public Asset GetAsset(int assetID) => + m_assets.TryGetValue(assetID, out Asset asset) + ? asset + : null; + + public Asset GetAsset(Asset asset) + { + Asset cachedAsset; + + if ((object)asset == null) + return null; + + if (asset.ID == 0) + return asset; + + if (m_assets.TryGetValue(asset.ID, out cachedAsset)) + return cachedAsset; + + m_assets.Add(asset.ID, asset); + return asset; + } + + public AssetLocation GetAssetLocation(int assetLocationID) => + m_assetLocations.TryGetValue(assetLocationID, out AssetLocation assetLocation) + ? assetLocation + : null; + + public AssetLocation GetAssetLocation(AssetLocation assetLocation) + { + AssetLocation cachedAssetLocation; + + if ((object)assetLocation == null) + return null; + + if (assetLocation.ID == 0) + return assetLocation; + + if (m_assetLocations.TryGetValue(assetLocation.ID, out cachedAssetLocation)) + return cachedAssetLocation; + + m_assetLocations.Add(assetLocation.ID, assetLocation); + return assetLocation; + } + + public SourceImpedance GetSourceImpedance(int sourceImpedanceID) => + m_sourceImpedances.TryGetValue(sourceImpedanceID, out SourceImpedance sourceImpedance) + ? sourceImpedance + : null; + + public SourceImpedance GetSourceImpedance(SourceImpedance sourceImpedance) + { + SourceImpedance cachedSourceImpedance; + + if ((object)sourceImpedance == null) + return null; + + if (sourceImpedance.ID == 0) + return sourceImpedance; + + if (m_sourceImpedances.TryGetValue(sourceImpedance.ID, out cachedSourceImpedance)) + return cachedSourceImpedance; + + m_sourceImpedances.Add(sourceImpedance.ID, sourceImpedance); + return sourceImpedance; + } + + public MeterAsset GetMeterAsset(int meterAssetID) => + m_meterAssets.TryGetValue(meterAssetID, out MeterAsset meterAsset) + ? meterAsset + : null; + + public MeterAsset GetMeterAsset(MeterAsset meterAsset) + { + MeterAsset cachedMeterAsset; + + if ((object)meterAsset == null) + return null; + + if (meterAsset.ID == 0) + return meterAsset; + + if (m_meterAssets.TryGetValue(meterAsset.ID, out cachedMeterAsset)) + return cachedMeterAsset; + + m_meterAssets.Add(meterAsset.ID, meterAsset); + return meterAsset; + } + + public Channel GetChannel(int channelID) => + m_channels.TryGetValue(channelID, out Channel channel) + ? channel + : null; + + public Channel GetChannel(Channel channel) + { + Channel cachedChannelLine; + + if ((object)channel == null) + return null; + + if (channel.ID == 0) + return channel; + + if (m_channels.TryGetValue(channel.ID, out cachedChannelLine)) + return cachedChannelLine; + + m_channels.Add(channel.ID, channel); + return channel; + } + + public Series GetSeries(int seriesID) => + m_series.TryGetValue(seriesID, out Series series) + ? series + : null; + + public Series GetSeries(Series series) + { + Series cachedSeriesLine; + + if ((object)series == null) + return null; + + if (series.ID == 0) + return series; + + if (m_series.TryGetValue(series.ID, out cachedSeriesLine)) + return cachedSeriesLine; + + m_series.Add(series.ID, series); + return series; + } + + public MeasurementType GetMeasurementType(int measurementTypeID) => + m_measurementTypes.TryGetValue(measurementTypeID, out MeasurementType measurementType) + ? measurementType + : null; + + public MeasurementType GetMeasurementType(MeasurementType measurementType) + { + MeasurementType cachedMeasurementTypeLine; + + if ((object)measurementType == null) + return null; + + if (measurementType.ID == 0) + return measurementType; + + if (m_measurementTypes.TryGetValue(measurementType.ID, out cachedMeasurementTypeLine)) + return cachedMeasurementTypeLine; + + m_measurementTypes.Add(measurementType.ID, measurementType); + return measurementType; + } + + public MeasurementCharacteristic GetMeasurementCharacteristic(int measurementCharacteristicID) => + m_measurementCharacteristics.TryGetValue(measurementCharacteristicID, out MeasurementCharacteristic measurementCharacteristic) + ? measurementCharacteristic + : null; + + public MeasurementCharacteristic GetMeasurementCharacteristic(MeasurementCharacteristic measurementCharacteristic) + { + MeasurementCharacteristic cachedMeasurementCharacteristicLine; + + if ((object)measurementCharacteristic == null) + return null; + + if (measurementCharacteristic.ID == 0) + return measurementCharacteristic; + + if (m_measurementCharacteristics.TryGetValue(measurementCharacteristic.ID, out cachedMeasurementCharacteristicLine)) + return cachedMeasurementCharacteristicLine; + + m_measurementCharacteristics.Add(measurementCharacteristic.ID, measurementCharacteristic); + return measurementCharacteristic; + } + + public Phase GetPhase(int phaseID) => + m_phases.TryGetValue(phaseID, out Phase phase) + ? phase + : null; + + public Phase GetPhase(Phase phase) + { + Phase cachedPhaseLine; + + if ((object)phase == null) + return null; + + if (phase.ID == 0) + return phase; + + if (m_phases.TryGetValue(phase.ID, out cachedPhaseLine)) + return cachedPhaseLine; + + m_phases.Add(phase.ID, phase); + return phase; + } + + public SeriesType GetSeriesType(int seriesTypeID) => + m_seriesTypes.TryGetValue(seriesTypeID, out SeriesType seriesType) + ? seriesType + : null; + + public SeriesType GetSeriesType(SeriesType seriesType) + { + SeriesType cachedSeriesTypeLine; + + if ((object)seriesType == null) + return null; + + if (seriesType.ID == 0) + return seriesType; + + if (m_seriesTypes.TryGetValue(seriesType.ID, out cachedSeriesTypeLine)) + return cachedSeriesTypeLine; + + m_seriesTypes.Add(seriesType.ID, seriesType); + return seriesType; + } + + public AssetConnection GetAssetConnection(int connectionID) => + m_assetConnections.TryGetValue(connectionID, out AssetConnection connection) + ? connection + : null; + + public AssetConnection GetAssetConnection(AssetConnection connection) + { + AssetConnection cachedConnection; + + if ((object)connection == null) + return null; + + if (connection.ID == 0) + return connection; + + if (m_assetConnections.TryGetValue(connection.ID, out cachedConnection)) + return cachedConnection; + + m_assetConnections.Add(connection.ID, connection); + return connection; + } + + #endregion + } +} diff --git a/src/Libraries/openXDA.Model/Links/AssetConnection.cs b/src/Libraries/openXDA.Model/Links/AssetConnection.cs new file mode 100644 index 00000000..dc03ab97 --- /dev/null +++ b/src/Libraries/openXDA.Model/Links/AssetConnection.cs @@ -0,0 +1,176 @@ +//****************************************************************************************************** +// AssetConnection.cs - Gbtc +// +// Copyright © 2017, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 12/13/2019 - C. Lackner +// Generated original version of source code. +// +//****************************************************************************************************** + +using Gemstone.Data; +using Gemstone.Data.Model; +using Newtonsoft.Json; + +namespace openXDA.Model +{ + [TableName("AssetRelationship")] + [PostRoles("Administrator, Transmission SME")] + [PatchRoles("Administrator, Transmission SME")] + [DeleteRoles("Administrator, Transmission SME")] + public class AssetConnection + { + #region [ Members ] + + // Fields + private Asset m_parent; + private Asset m_child; + + #endregion + + #region [ Properties ] + + [PrimaryKey(true)] + public int ID { get; set; } + + public int AssetRelationshipTypeID { get; set; } + + public int ParentID { get; set; } + + public int ChildID { get; set; } + + [JsonIgnore] + [NonRecordField] + public Asset Parent + { + get + { + if (m_parent is null) + m_parent = LazyContext.GetAsset(ParentID); + + if (m_parent is null) + m_parent = QueryParent(); + + return m_parent; + } + set => m_parent = value; + } + + [JsonIgnore] + [NonRecordField] + public Asset Child + { + get + { + if (m_child is null) + m_child = LazyContext.GetAsset(ChildID); + + if (m_child is null) + m_child = QueryChild(); + + return m_child; + } + set => m_child = value; + } + + [JsonIgnore] + [NonRecordField] + public Func ConnectionFactory + { + get => LazyContext.ConnectionFactory; + set => LazyContext.ConnectionFactory = value; + } + + [JsonIgnore] + [NonRecordField] + internal LazyContext LazyContext { get; set; } = new LazyContext(); + + #endregion + + #region [ Methods ] + + public Asset GetParent(AdoDataConnection connection) + { + if ((object)connection == null) + return null; + + TableOperations assetTable = new TableOperations(connection); + Asset parent = assetTable.QueryRecordWhere("ID = {0}", ParentID); + return parent; + } + + public Asset GetChild(AdoDataConnection connection) + { + if ((object)connection == null) + return null; + + TableOperations assetTable = new TableOperations(connection); + Asset child = assetTable.QueryRecordWhere("ID = {0}", ChildID); + + return child; + } + + public Asset QueryParent() + { + Asset parent; + + using (AdoDataConnection connection = ConnectionFactory?.Invoke()) + { + parent = GetParent(connection); + } + + if ((object)parent != null) + parent.LazyContext = LazyContext; + + return LazyContext.GetAsset(parent); + } + + public Asset QueryChild() + { + Asset child; + + using (AdoDataConnection connection = ConnectionFactory?.Invoke()) + { + child = GetChild(connection); + } + + if ((object)child != null) + child.LazyContext = LazyContext; + + return LazyContext.GetAsset(child); + } + + #endregion + } + + public class AssetConnectionDetail + { + [PrimaryKey(true)] + public int ID { get; set; } + + public int AssetRelationshipTypeID { get; set; } + + public int ParentID { get; set; } + + public int ChildID { get; set; } + + public string ChildKey { get; set; } + + public string ParentKey { get; set; } + + public string AssetRelationshipType { get; set; } + } +} diff --git a/src/Libraries/openXDA.Model/Links/MeterLine.cs b/src/Libraries/openXDA.Model/Links/MeterLine.cs new file mode 100644 index 00000000..4d44e704 --- /dev/null +++ b/src/Libraries/openXDA.Model/Links/MeterLine.cs @@ -0,0 +1,174 @@ +//****************************************************************************************************** +// MeterLine.cs - Gbtc +// +// Copyright © 2017, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 08/29/2017 - Billy Ernest +// Generated original version of source code. +// +// 12/13/2019 - C. Lackner +// Modified to fit new Asset Model Structure. +// +//****************************************************************************************************** + +using System; +using System.ComponentModel.DataAnnotations; +using Gemstone.Data; +using Gemstone.Data.Model; +using Newtonsoft.Json; + +namespace openXDA.Model +{ + public class MeterAsset + { + #region [ Members ] + + // Fields + private Meter m_meter; + private Asset m_asset; + + #endregion + + #region [ Properties ] + + [PrimaryKey(true)] + public int ID { get; set; } + + public int MeterID { get; set; } + + public int AssetID { get; set; } + + [JsonIgnore] + [NonRecordField] + public Meter Meter + { + get + { + if (m_meter is null) + m_meter = LazyContext.GetMeter(MeterID); + + if (m_meter is null) + m_meter = QueryMeter(); + + return m_meter; + } + set => m_meter = value; + } + + [JsonIgnore] + [NonRecordField] + public Asset Asset + { + get + { + if (m_asset is null) + m_asset = LazyContext.GetAsset(AssetID); + + if (m_asset is null) + m_asset = QueryAsset(); + + return m_asset; + } + set => m_asset = value; + } + + [JsonIgnore] + [NonRecordField] + public Func ConnectionFactory + { + get => LazyContext.ConnectionFactory; + set => LazyContext.ConnectionFactory = value; + } + + [JsonIgnore] + [NonRecordField] + internal LazyContext LazyContext { get; set; } = new LazyContext(); + + #endregion + + #region [ Methods ] + + public Meter GetMeter(AdoDataConnection connection) + { + if ((object)connection == null) + return null; + + TableOperations meterTable = new TableOperations(connection); + return meterTable.QueryRecordWhere("ID = {0}", MeterID); + } + + public Asset GetAsset(AdoDataConnection connection) + { + if ((object)connection == null) + return null; + + TableOperations assetTable = new TableOperations(connection); + return assetTable.QueryRecordWhere("ID = {0}", AssetID); + } + + public Meter QueryMeter() + { + Meter meter; + + using (AdoDataConnection connection = ConnectionFactory?.Invoke()) + { + meter = GetMeter(connection); + } + + if ((object)meter != null) + meter.LazyContext = LazyContext; + + return LazyContext.GetMeter(meter); + } + + public Asset QueryAsset() + { + Asset asset; + + using (AdoDataConnection connection = ConnectionFactory?.Invoke()) + { + asset = GetAsset(connection); + } + + if ((object)asset != null) + asset.LazyContext = LazyContext; + + return LazyContext.GetAsset(asset); + } + + #endregion + } + + public class MeterAssetDetail + { + [PrimaryKey(true)] + public int ID { get; set; } + + public int MeterID { get; set; } + + public int AssetID { get; set; } + + public string MeterKey { get; set; } + + public string AssetKey { get; set; } + + public string AssetName { get; set; } + + public string AssetType { get; set; } + + public string FaultDetectionLogic { get; set; } + } +} diff --git a/src/Libraries/openXDA.Model/Links/MeterLocationLine.cs b/src/Libraries/openXDA.Model/Links/MeterLocationLine.cs new file mode 100644 index 00000000..377ec143 --- /dev/null +++ b/src/Libraries/openXDA.Model/Links/MeterLocationLine.cs @@ -0,0 +1,192 @@ +//****************************************************************************************************** +// MeterLocationLine.cs - Gbtc +// +// Copyright © 2017, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 06/19/2017 - Billy Ernest +// Generated original version of source code. +// 12/13/2019 - C. Lackner +// Update to reflect changes in Location and move from Line to Asset. +// +//****************************************************************************************************** + +using Gemstone.Data; +using Gemstone.Data.Model; +using Newtonsoft.Json; + +namespace openXDA.Model +{ + public class AssetLocation + { + #region [ Members ] + + // Fields + private Location m_location; + private Asset m_asset; + private SourceImpedance m_sourceImpedance; + + #endregion + + #region [ Properties ] + + [PrimaryKey(true)] + public int ID { get; set; } + + public int LocationID { get; set; } + + public int AssetID { get; set; } + + [JsonIgnore] + [NonRecordField] + public Location Location + { + get + { + if (m_location is null) + m_location = LazyContext.GetLocation(LocationID); + + if (m_location is null) + m_location = QueryLocation(); + + return m_location; + } + set => m_location = value; + } + + [JsonIgnore] + [NonRecordField] + public Asset Asset + { + get + { + if (m_asset is null) + m_asset = LazyContext.GetAsset(AssetID); + + if (m_asset is null) + m_asset = QueryAsset(); + + return m_asset; + } + set => m_asset = value; + } + + [JsonIgnore] + [NonRecordField] + public SourceImpedance SourceImpedance + { + get => m_sourceImpedance ?? (m_sourceImpedance ?? QuerySourceImpedance()); + set => m_sourceImpedance = value; + } + + [JsonIgnore] + [NonRecordField] + public Func ConnectionFactory + { + get => LazyContext.ConnectionFactory; + set => LazyContext.ConnectionFactory = value; + } + + [JsonIgnore] + [NonRecordField] + internal LazyContext LazyContext { get; set; } = new LazyContext(); + + #endregion + + #region [ Methods ] + + public Location GetLocation(AdoDataConnection connection) + { + if ((object)connection == null) + return null; + + TableOperations locationTable = new TableOperations(connection); + + try + { + return locationTable.QueryRecordWhere("ID = {0}", LocationID); + } + catch + { + return null; + } + } + + public Asset GetAsset(AdoDataConnection connection) + { + if ((object)connection == null) + return null; + + TableOperations assetTable = new TableOperations(connection); + return assetTable.QueryRecordWhere("ID = {0}", AssetID); + } + + public SourceImpedance GetSourceImpedance(AdoDataConnection connection) + { + if ((object)connection == null) + return null; + + TableOperations sourceImpedanceTable = new TableOperations(connection); + return sourceImpedanceTable.QueryRecordWhere("AssetLocationID = {0}", ID); + } + + private Location QueryLocation() + { + Location location; + + using (AdoDataConnection connection = ConnectionFactory?.Invoke()) + { + location = GetLocation(connection); + } + + if ((object)location != null) + location.LazyContext = LazyContext; + + return LazyContext.GetLocation(location); + } + + private Asset QueryAsset() + { + Asset asset; + + using (AdoDataConnection connection = ConnectionFactory?.Invoke()) + { + asset = GetAsset(connection); + } + + if ((object)asset != null) + asset.LazyContext = LazyContext; + + return LazyContext.GetAsset(asset); + } + + private SourceImpedance QuerySourceImpedance() + { + SourceImpedance sourceImpedance; + + using (AdoDataConnection connection = ConnectionFactory?.Invoke()) + { + sourceImpedance = GetSourceImpedance(connection); + } + + if ((object)sourceImpedance != null) + sourceImpedance.LazyContext = LazyContext; + + return LazyContext.GetSourceImpedance(sourceImpedance); + } + + #endregion + } +} diff --git a/src/Libraries/openXDA.Model/Meters/Location.cs b/src/Libraries/openXDA.Model/Meters/Location.cs new file mode 100644 index 00000000..6c8515d1 --- /dev/null +++ b/src/Libraries/openXDA.Model/Meters/Location.cs @@ -0,0 +1,509 @@ +//****************************************************************************************************** +// Location.cs - Gbtc +// +// Copyright © 2017, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 08/29/2017 - Billy Ernest +// Generated original version of source code. +// 12/13/2019 - Christoph Lackner +// Updated MeterLocation to more Generic Location. +// +//****************************************************************************************************** + +using System.ComponentModel.DataAnnotations; +using System.Data; +using System.Text.RegularExpressions; +using Gemstone.Collections.CollectionExtensions; +using Gemstone.Data; +using Gemstone.Data.DataExtensions; +using Gemstone.Data.Model; +using Newtonsoft.Json; + +namespace openXDA.Model +{ + [PostRoles("Administrator, Transmission SME")] + [DeleteRoles("Administrator, Transmission SME")] + [PatchRoles("Administrator, Transmission SME")] + public class Location + { + #region [ Members ] + + // Nested Types + private delegate void ChannelConnector(int assetID, IEnumerable channels); + private delegate IEnumerable AssetConnectionLookup(int assetID); + + private class AssetConnectionDetail + { + #region [ Constructors ] + + public AssetConnectionDetail(int parentID, int childID, string jumpSQL, string passthroughSQL) + { + ParentID = parentID; + ChildID = childID; + JumpSQL = jumpSQL; + PassthroughSQL = passthroughSQL; + } + + #endregion + + #region [ Properties ] + + public int ParentID { get; } + public int ChildID { get; } + private string JumpSQL { get; } + private string PassthroughSQL { get; } + + private HashSet JumpChannels { get; } = new HashSet(); + private HashSet PassthroughChannels { get; } = new HashSet(); + private bool Populated { get; set; } + + #endregion + + #region [ Methods ] + + public void PopulateChannelSets(AdoDataConnection connection, int locationID, Func channelLookup) + { + if (Populated) + return; + + // lang=regex + const string Pattern = @"\{(?:parentid|childid|channelid)\}"; + string jumpSQL = Regex.Replace(JumpSQL, Pattern, ReplaceFormatParameter, RegexOptions.IgnoreCase); + string passthroughSQL = Regex.Replace(PassthroughSQL, Pattern, ReplaceFormatParameter, RegexOptions.IgnoreCase); + + string queryFormat = + $"SELECT " + + $" SourceChannel.ID ChannelID, " + + $" Jump.Value Jump, " + + $" Passthrough.Value Passthrough " + + $"FROM " + + $" Asset ParentAsset JOIN " + + $" Asset ChildAsset ON " + + $" ParentAsset.ID = {{0}} AND " + + $" ChildAsset.ID = {{1}} JOIN " + + $" Location ON Location.ID = {{2}} JOIN " + + $" Meter ON Meter.LocationID = Location.ID JOIN " + + $" Channel SourceChannel ON SourceChannel.MeterID = Meter.ID CROSS APPLY " + + $" ({jumpSQL}) Jump(Value) CROSS APPLY " + + $" ({passthroughSQL}) Passthrough(Value) " + + $"WHERE " + + $" Jump.Value <> 0 OR " + + $" Passthrough.Value <> 0"; + + using (DataTable table = connection.RetrieveData(queryFormat, ParentID, ChildID, locationID)) + { + foreach (DataRow row in table.AsEnumerable()) + { + int channelID = row.ConvertField("ChannelID"); + bool jump = row.ConvertField("Jump"); + bool passthrough = row.ConvertField("Passthrough"); + Channel channel = channelLookup(channelID); + if (channel is null) continue; + if (jump) JumpChannels.Add(channel); + if (passthrough) PassthroughChannels.Add(channel); + } + } + + Populated = true; + + string ReplaceFormatParameter(Match match) + { + switch (match.Value.ToLowerInvariant()) + { + case "{parentid}": return "ParentAsset.ID"; + case "{childid}": return "ChildAsset.ID"; + case "{channelid}": return "SourceChannel.ID"; + default: return match.Value; + } + } + } + + public bool CanJump(Channel channel) => + JumpChannels.Contains(channel); + + public bool CanPassThrough(Channel channel) => + PassthroughChannels.Contains(channel); + + #endregion + } + + private class TraversalContext + { + public AdoDataConnection Connection { get; } + public HashSet VisitedAssets { get; } + public AssetConnectionLookup FindAssetConnections { get; } + public ChannelConnector ConnectChannels { get; } + + public TraversalContext(AdoDataConnection connection, HashSet visitedAssets, AssetConnectionLookup findAssetConnections, ChannelConnector connectChannels) + { + Connection = connection; + VisitedAssets = visitedAssets; + FindAssetConnections = findAssetConnections; + ConnectChannels = connectChannels; + } + } + + // Fields + private List m_meters; + private List m_assetLocations; + + #endregion + + #region [ Properties ] + + [PrimaryKey(true)] + public int ID { get; set; } + + [StringLength(50)] + [Required] + [DefaultSortOrder] + public string LocationKey { get; set; } + + [StringLength(200)] + [Required] + public string Name { get; set; } + + [StringLength(200)] + public string Alias { get; set; } + + [StringLength(50)] + public string ShortName { get; set; } + + [Required] + public double Latitude { get; set; } + + [Required] + public double Longitude { get; set; } + + [StringLength(200)] + public string Description { get; set; } + + [JsonIgnore] + [NonRecordField] + public List Meters + { + get + { + return m_meters ?? (m_meters = QueryMeters()); + } + set + { + m_meters = value; + } + } + + [JsonIgnore] + [NonRecordField] + public List AssetLocations + { + get + { + return m_assetLocations ?? (m_assetLocations = QueryAssetLocations()); + } + set + { + m_assetLocations = value; + } + } + + [JsonIgnore] + [NonRecordField] + public Func ConnectionFactory + { + get + { + return LazyContext.ConnectionFactory; + } + set + { + LazyContext.ConnectionFactory = value; + } + } + + [JsonIgnore] + [NonRecordField] + internal LazyContext LazyContext { get; set; } = new LazyContext(); + + #endregion + + #region [ Methods ] + + public IEnumerable GetMeters(AdoDataConnection connection) + { + if ((object)connection == null) + return null; + + TableOperations meterTable = new TableOperations(connection); + return meterTable.QueryRecordsWhere("MeterLocationID = {0}", ID); + } + + public IEnumerable GetAssetLocations(AdoDataConnection connection) + { + if ((object)connection == null) + return null; + + TableOperations assetLocationTable = new TableOperations(connection); + return assetLocationTable.QueryRecordsWhere("LocationID = {0}", ID); + } + + private List QueryMeters() + { + List meters; + + using (AdoDataConnection connection = ConnectionFactory?.Invoke()) + { + meters = GetMeters(connection)? + .Select(LazyContext.GetMeter) + .ToList(); + } + + if ((object)meters != null) + { + foreach (Meter meter in meters) + { + meter.Location = this; + meter.LazyContext = LazyContext; + } + } + + return meters; + } + + private List QueryAssetLocations() + { + List assetLocations; + + using (AdoDataConnection connection = ConnectionFactory?.Invoke()) + { + assetLocations = GetAssetLocations(connection)? + .Select(LazyContext.GetAssetLocation) + .ToList(); + } + + if ((object)assetLocations != null) + { + foreach (AssetLocation assetLocation in assetLocations) + { + assetLocation.Location = this; + assetLocation.LazyContext = LazyContext; + } + } + + return assetLocations; + } + + public void ConnectAllChannels() + { + if (ConnectionFactory is null) + return; + + Dictionary> connectedChannelLookup = new Dictionary>(); + ChannelConnector connectChannels = CreateChannelConnector(connectedChannelLookup); + + using (AdoDataConnection connection = ConnectionFactory()) + { + TraverseAssetConnections(connection, connectChannels); + + TableOperations assetTable = new TableOperations(connection); + + foreach (KeyValuePair> kvp in connectedChannelLookup) + { + int assetID = kvp.Key; + HashSet connectedChannels = kvp.Value; + Asset asset = assetTable.QueryRecordWhere("ID = {0}", assetID); + asset = LazyContext.GetAsset(asset); + asset.ConnectedChannels = connectedChannels.ToList(); + asset.LazyContext = LazyContext; + } + } + + // Assign empty lists to any assets that were missed by the recursive search + foreach (Asset asset in AssetLocations.Select(al => al.Asset)) + EnsureConnectedChannels(asset); + } + + private void TraverseAssetConnections(AdoDataConnection connection, ChannelConnector connectChannels) + { + List allChannels = RetrieveAllChannels(connection).ToList(); + List allAssetConnections = RetrieveAllAssetConnections(connection).ToList(); + AssetConnectionLookup findAssetConnections = CreateAssetConnectionLookup(connection, allChannels, allAssetConnections); + + foreach (IGrouping rootChannels in allChannels.GroupBy(channel => channel.AssetID)) + { + int rootAssetID = rootChannels.Key; + + // Initialize an empty set of connected channels in case the root asset has no connected channels + connectChannels(rootAssetID, Enumerable.Empty()); + + foreach (AssetConnectionDetail assetConnection in findAssetConnections(rootAssetID)) + { + List jumpChannels = rootChannels + .Where(assetConnection.CanJump) + .ToList(); + + if (jumpChannels.Count == 0) + continue; + + int connectedAssetID = assetConnection.ChildID; + connectChannels(connectedAssetID, jumpChannels); + + HashSet visitedAssets = new HashSet() { rootAssetID }; + TraversalContext context = new TraversalContext(connection, visitedAssets, findAssetConnections, connectChannels); + TraverseAssetConnections(context, jumpChannels, connectedAssetID); + } + } + } + + private void TraverseAssetConnections(TraversalContext context, List connectedChannels, int visitedAssetID) + { + AdoDataConnection connection = context.Connection; + HashSet visitedAssets = context.VisitedAssets; + AssetConnectionLookup findAssetConnections = context.FindAssetConnections; + ChannelConnector connectChannels = context.ConnectChannels; + + visitedAssets.Add(visitedAssetID); + + foreach (AssetConnectionDetail assetConnection in findAssetConnections(visitedAssetID)) + { + int connectedAssetID = assetConnection.ChildID; + + if (visitedAssets.Contains(connectedAssetID)) + continue; + + List passthroughChannels = connectedChannels + .Where(assetConnection.CanPassThrough) + .ToList(); + + if (passthroughChannels.Count == 0) + continue; + + connectChannels(connectedAssetID, passthroughChannels); + TraverseAssetConnections(context, passthroughChannels, connectedAssetID); + } + + visitedAssets.Remove(visitedAssetID); + } + + private IEnumerable RetrieveAllChannels(AdoDataConnection connection) + { + const string QueryFormat = + "SELECT Channel.* " + + "FROM " + + " Channel JOIN " + + " Meter ON Channel.MeterID = Meter.ID " + + "WHERE Meter.LocationID = {0}"; + + TableOperations channelTable = new TableOperations(connection); + + using (DataTable table = connection.RetrieveData(QueryFormat, ID)) + { + foreach (DataRow row in table.AsEnumerable()) + { + Channel channel = channelTable.LoadRecord(row); + channel = LazyContext.GetChannel(channel); + yield return channel; + } + } + } + + private IEnumerable RetrieveAllAssetConnections(AdoDataConnection connection) + { + const string QueryFormat = + "SELECT " + + " AssetConnection.ParentID, " + + " AssetConnection.ChildID, " + + " AssetRelationshipType.JumpConnection JumpSQL, " + + " AssetRelationshipType.PassThrough PassthroughSQL " + + "FROM " + + " Location JOIN " + + " AssetConnection ON Location.ID = {0} JOIN " + + " AssetRelationshipType ON AssetConnection.AssetRelationshipTypeID = AssetRelationshipType.ID JOIN " + + " AssetLocation ParentLocation ON " + + " ParentLocation.LocationID = Location.ID AND " + + " ParentLocation.AssetID = AssetConnection.ParentID JOIN " + + " AssetLocation ChildLocation ON " + + " ChildLocation.LocationID = Location.ID AND " + + " ChildLocation.AssetID = AssetConnection.ChildID"; + + using (DataTable table = connection.RetrieveData(QueryFormat, ID)) + { + foreach (DataRow row in table.AsEnumerable()) + { + int parentID = row.ConvertField("ParentID"); + int childID = row.ConvertField("ChildID"); + string jumpSQL = row.ConvertField("JumpSQL"); + string passthroughSQL = row.ConvertField("PassthroughSQL"); + yield return new AssetConnectionDetail(parentID, childID, jumpSQL, passthroughSQL); + yield return new AssetConnectionDetail(childID, parentID, jumpSQL, passthroughSQL); + } + } + } + + private AssetConnectionLookup CreateAssetConnectionLookup(AdoDataConnection connection, List allChannels, List allAssetConnections) + { + Dictionary channelLookup = allChannels.ToDictionary(channel => channel.ID); + ILookup assetConnectionLookup = allAssetConnections.ToLookup(conn => conn.ParentID); + return findAssetConnection; + + Channel findChannel(int channelID) => + channelLookup.TryGetValue(channelID, out Channel channel) + ? channel + : null; + + IEnumerable findAssetConnection(int assetID) + { + foreach (AssetConnectionDetail assetConnection in assetConnectionLookup[assetID]) + { + assetConnection.PopulateChannelSets(connection, ID, findChannel); + yield return assetConnection; + } + } + } + + #endregion + + #region [ Static ] + + // Static Methods + private static ChannelConnector CreateChannelConnector(Dictionary> connectedChannelLookup) + { + return (assetID, channels) => + { + HashSet connectedChannels = connectedChannelLookup.GetOrAdd(assetID, _ => new HashSet()); + connectedChannels.UnionWith(channels); + }; + } + + private static void EnsureConnectedChannels(Asset asset) + { + Func connectionFactory = asset.ConnectionFactory; + + try + { + asset.ConnectionFactory = null; + + if (asset.ConnectedChannels is null) + asset.ConnectedChannels = new List(); + } + finally + { + asset.ConnectionFactory = connectionFactory; + } + } + + #endregion + } +} diff --git a/src/Libraries/openXDA.Model/Meters/Meter.cs b/src/Libraries/openXDA.Model/Meters/Meter.cs new file mode 100644 index 00000000..47c6b517 --- /dev/null +++ b/src/Libraries/openXDA.Model/Meters/Meter.cs @@ -0,0 +1,338 @@ +//****************************************************************************************************** +// Meter.cs - Gbtc +// +// Copyright © 2017, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 08/29/2017 - Billy Ernest +// Generated original version of source code. +// +// 12/13/2019 - C. Lackner +// Updated to fit in new Asset based model structure. +//****************************************************************************************************** + +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using Gemstone.ComponentModel.DataAnnotations; +using Gemstone.Data; +using Gemstone.Data.Model; +using Newtonsoft.Json; + +namespace openXDA.Model +{ + [PostRoles("Administrator, Transmission SME")] + [DeleteRoles("Administrator, Transmission SME")] + [PatchRoles("Administrator, Transmission SME")] + public class Meter + { + #region [ Members ] + + // Fields + private Location m_location; + private List m_meterAssets; + private List m_channels; + + #endregion + + #region [ Properties ] + + [PrimaryKey(true)] + public int ID { get; set; } + + [Required] + [StringLength(50)] + [DefaultSortOrder] + public string AssetKey { get; set; } + + [Required] + [Label("Location")] + public int LocationID { get; set; } + + [Required] + [StringLength(200)] + public string Name { get; set; } + + [StringLength(200)] + public string Alias { get; set; } + + [StringLength(12)] + public string ShortName { get; set; } + + [Required] + [StringLength(200)] + public string Make { get; set; } + + [Required] + [StringLength(200)] + public string Model { get; set; } + + + [StringLength(200)] + public string TimeZone { get; set; } + + public string Description { get; set; } + + [JsonIgnore] + [NonRecordField] + public Location Location + { + get + { + if (m_location is null) + m_location = LazyContext.GetLocation(LocationID); + + if (m_location is null) + m_location = QueryLocation(); + + return m_location; + } + set => m_location = value; + } + + [JsonIgnore] + [NonRecordField] + public List MeterAssets + { + get => m_meterAssets ?? (m_meterAssets = QueryMeterAssets()); + set => m_meterAssets = value; + } + + [JsonIgnore] + [NonRecordField] + public List Channels + { + get => m_channels ?? (m_channels = QueryChannels()); + set => m_channels = value; + } + + public List Series + { + get + { + List channels = Channels; + + if (channels is null) + return null; + + bool IsQueryRequired() + { + var connectionFactory = ConnectionFactory; + + try + { + // Don't trigger individual queries for each channel + ConnectionFactory = null; + return channels.Any(channel => channel.Series is null); + } + finally + { + ConnectionFactory = connectionFactory; + } + } + + if (IsQueryRequired()) + return QuerySeries(); + + return channels + .SelectMany(channel => channel.Series) + .ToList(); + } + } + + [JsonIgnore] + [NonRecordField] + public Func ConnectionFactory + { + get => LazyContext.ConnectionFactory; + set => LazyContext.ConnectionFactory = value; + } + + [JsonIgnore] + [NonRecordField] + internal LazyContext LazyContext { get; set; } = new LazyContext(); + + #endregion + + #region [ Methods ] + + public Location GetLocation(AdoDataConnection connection) + { + if ((object)connection == null) + return null; + + TableOperations locationTable = new TableOperations(connection); + return locationTable.QueryRecordWhere("ID = {0}", LocationID); + } + + public IEnumerable GetMeterAssets(AdoDataConnection connection) + { + if ((object)connection == null) + return null; + + TableOperations meterAssetTable = new TableOperations(connection); + return meterAssetTable.QueryRecordsWhere("MeterID = {0}", ID); + } + + public IEnumerable GetChannels(AdoDataConnection connection) + { + if ((object)connection == null) + return null; + + TableOperations channelTable = new TableOperations(connection); + return channelTable.QueryRecordsWhere("MeterID = {0}", ID); + } + + public IEnumerable GetSeries(AdoDataConnection connection) + { + if ((object)connection == null) + return null; + + TableOperations seriesTable = new TableOperations(connection); + return seriesTable.QueryRecordsWhere("ChannelID IN (SELECT ID FROM Channel WHERE MeterID = {0})", ID); + } + + public TimeZoneInfo GetTimeZoneInfo(TimeZoneInfo defaultTimeZone) + { + if (!string.IsNullOrEmpty(TimeZone)) + return TimeZoneInfo.FindSystemTimeZoneById(TimeZone); + + return defaultTimeZone; + } + + private Location QueryLocation() + { + Location location; + + using (AdoDataConnection connection = ConnectionFactory?.Invoke()) + { + location = GetLocation(connection); + } + + if ((object)location != null) + location.LazyContext = LazyContext; + + return LazyContext.GetLocation(location); + } + + private List QueryMeterAssets() + { + List meterAssets; + + using (AdoDataConnection connection = ConnectionFactory?.Invoke()) + { + meterAssets = GetMeterAssets(connection)? + .Select(LazyContext.GetMeterAsset) + .ToList(); + } + + if ((object)meterAssets != null) + { + foreach (MeterAsset meterAsset in meterAssets) + { + meterAsset.Meter = this; + meterAsset.LazyContext = LazyContext; + } + } + + return meterAssets; + } + + private List QueryChannels() + { + List channels; + + using (AdoDataConnection connection = ConnectionFactory?.Invoke()) + { + channels = GetChannels(connection)? + .Select(LazyContext.GetChannel) + .ToList(); + } + + if ((object)channels != null) + { + foreach (Channel channel in channels) + { + channel.Meter = this; + channel.LazyContext = LazyContext; + } + } + + return channels; + } + + private List QuerySeries() + { + List seriesList; + + using (AdoDataConnection connection = ConnectionFactory?.Invoke()) + { + seriesList = GetSeries(connection)? + .Select(LazyContext.GetSeries) + .ToList(); + } + + if (!(seriesList is null)) + { + ILookup seriesLookup = seriesList.ToLookup(series => series.ChannelID); + + foreach (Channel channel in Channels) + { + channel.Series = seriesLookup[channel.ID].ToList(); + + foreach (Series series in channel.Series) + { + series.Channel = channel; + series.LazyContext = LazyContext; + } + } + } + + return seriesList; + } + + #endregion + } + + public class MeterDetail : Meter + { + public new string Location { get; set; } + + public string TimeZoneLabel + { + get + { + try + { + if (TimeZone != "UTC") + return TimeZoneInfo.FindSystemTimeZoneById(TimeZone).ToString(); + } + catch + { + // Do not fail if the time zone cannot be found -- + // instead, fall through to the logic below to + // find the label for UTC + } + + return TimeZoneInfo.GetSystemTimeZones() + .Where(info => info.Id == "UTC") + .DefaultIfEmpty(TimeZoneInfo.Utc) + .First() + .ToString(); + } + } + } +} diff --git a/src/Libraries/openXDA.Model/PQDigest/HomeScreenWidget.cs b/src/Libraries/openXDA.Model/PQDigest/HomeScreenWidget.cs new file mode 100644 index 00000000..ee826388 --- /dev/null +++ b/src/Libraries/openXDA.Model/PQDigest/HomeScreenWidget.cs @@ -0,0 +1,44 @@ +//****************************************************************************************************** +// HomeScreenWidget.cs - Gbtc +// +// Copyright © 2020, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 08/10/2020 - C. Lackner +// Generated original version of source code. +// +//****************************************************************************************************** + +using Gemstone.Data.Model; + +namespace PQDigest.Model +{ + /// + /// Defines a widget used in PQDigest Home Screen + /// + [TableName("PQDigest.HomeScreenWidget"), UseEscapedName] + [PostRoles("Administrator")] + [DeleteRoles("Administrator")] + [PatchRoles("Administrator")] + public class HomeScreenWidget : Widget + { + #region [ Properties ] + + public int TimeFrame { get; set; } + + #endregion + } + +} \ No newline at end of file diff --git a/src/Libraries/openXDA.Model/PQDigest/Widget.cs b/src/Libraries/openXDA.Model/PQDigest/Widget.cs new file mode 100644 index 00000000..6042dea0 --- /dev/null +++ b/src/Libraries/openXDA.Model/PQDigest/Widget.cs @@ -0,0 +1,51 @@ +//****************************************************************************************************** +// Widget.cs - Gbtc +// +// Copyright © 2020, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 08/10/2020 - C. Lackner +// Generated original version of source code. +// +//****************************************************************************************************** + +using Gemstone.Data.Model; + +namespace PQDigest.Model +{ + /// + /// Defines a widget used in PQDigest + /// + [TableName("PQDigest.EventViewWidget"), UseEscapedName] + [PostRoles("Administrator")] + [DeleteRoles("Administrator")] + [PatchRoles("Administrator")] + public class Widget + { + #region [ Properties ] + + [PrimaryKey(true)] + public int ID { get; set; } + + public string Name { get; set; } + + public string Setting { get; set; } + + public string Type { get; set; } + + #endregion + } + +} \ No newline at end of file diff --git a/src/Libraries/openXDA.Model/SEBrowser/Widget.cs b/src/Libraries/openXDA.Model/SEBrowser/Widget.cs new file mode 100644 index 00000000..dcf2b368 --- /dev/null +++ b/src/Libraries/openXDA.Model/SEBrowser/Widget.cs @@ -0,0 +1,61 @@ +//****************************************************************************************************** +// Widget.cs - Gbtc +// +// Copyright © 2020, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 08/10/2020 - C. Lackner +// Generated original version of source code. +// +//****************************************************************************************************** + +using Gemstone.Data.Model; + +namespace SEBrowser.Model +{ + /// + /// Defines a widget used in SEBrowser + /// + [TableName("SEBrowser.Widget"), UseEscapedName] + [PostRoles("Administrator")] + [DeleteRoles("Administrator")] + [PatchRoles("Administrator")] + public class Widget + { + #region [ Properties ] + + [PrimaryKey(true)] + public int ID { get; set; } + + public string Name { get; set; } + + public string Setting { get; set; } + + public string Type { get; set; } + + #endregion + } + + [TableName("SEbrowser.WidgetView"), UseEscapedName] + [PostRoles("Administrator")] + [DeleteRoles("Administrator")] + [PatchRoles("Administrator")] + public class WidgetView : Widget + { + [ParentKey(typeof (WidgetCategory))] + public int CategoryID { get; set; } + } + +} \ No newline at end of file diff --git a/src/Libraries/openXDA.Model/SEBrowser/WidgetCategory.cs b/src/Libraries/openXDA.Model/SEBrowser/WidgetCategory.cs new file mode 100644 index 00000000..62faf93e --- /dev/null +++ b/src/Libraries/openXDA.Model/SEBrowser/WidgetCategory.cs @@ -0,0 +1,52 @@ +//****************************************************************************************************** +// WidgetCategory.cs - Gbtc +// +// Copyright © 2020, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 08/10/2020 - C. Lackner +// Generated original version of source code. +// +//****************************************************************************************************** + +using Gemstone.Data.Model; + +namespace SEBrowser.Model +{ + /// + /// Defines the categories of widgets used in SEBrowser + /// + /// + /// Will need to use in SEbrowser ans OpenSEE too. + /// + [TableName("SEBrowser.WidgetCategory"), UseEscapedName] + [PostRoles("Administrator")] + [DeleteRoles("Administrator")] + [PatchRoles("Administrator")] + public class WidgetCategory + { + #region [ Properties ] + + [PrimaryKey(true)] + public int ID { get; set; } + + public string Name { get; set; } + + [DefaultSortOrder(true)] + public int OrderBy { get; set; } + + #endregion + } +} \ No newline at end of file diff --git a/src/Libraries/openXDA.Model/Settings/PQDigestSetting.cs b/src/Libraries/openXDA.Model/Settings/PQDigestSetting.cs new file mode 100644 index 00000000..877bff81 --- /dev/null +++ b/src/Libraries/openXDA.Model/Settings/PQDigestSetting.cs @@ -0,0 +1,33 @@ +//****************************************************************************************************** +// PQDigestSetting.cs - Gbtc +// +// Copyright © 2017, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 10/20/2025 - Gabriel Santos +// Generated original version of source code. +// +//****************************************************************************************************** + +using Gemstone.Data.Model; + +namespace openXDA.Model +{ + [TableName("PQDigest.Setting"), UseEscapedName] + [PostRoles("Administrator")] + [DeleteRoles("Administrator")] + [PatchRoles("Administrator")] + public class PQDigestSetting : Setting { } +} \ No newline at end of file diff --git a/src/Libraries/openXDA.Model/Settings/Setting.cs b/src/Libraries/openXDA.Model/Settings/Setting.cs new file mode 100644 index 00000000..b9f2a866 --- /dev/null +++ b/src/Libraries/openXDA.Model/Settings/Setting.cs @@ -0,0 +1,84 @@ +//****************************************************************************************************** +// Setting.cs - Gbtc +// +// Copyright © 2017, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 08/29/2017 - Billy Ernest +// Generated original version of source code. +// +//****************************************************************************************************** + +using System.ComponentModel.DataAnnotations; +using Gemstone.ComponentModel.DataAnnotations; +using Gemstone.Data.Model; + +namespace openXDA.Model +{ + [DeleteRoles("Administrator")] + [PatchRoles("Administrator")] + [PostRoles("Administrator")] + [TableName("Setting")] + [UseEscapedName] + public class Setting + { + [PrimaryKey(true)] + public int ID { get; set; } + + public string Name { get; set; } + + public string Value { get; set; } + + public string DefaultValue { get; set; } + } + + [TableName("DashSettings")] + public class DashSettings + { + [PrimaryKey(true)] + public int ID { get; set; } + + [Required] + [StringLength(500)] + public string Name { get; set; } + + [Required] + [StringLength(500)] + public string Value { get; set; } + + [Required] + public bool Enabled { get; set; } + } + + [PrimaryLabel("Name")] + [TableName("UserDashSettings")] + public class UserDashSettings + { + [PrimaryKey(true)] + public int ID { get; set; } + + [Required] + [Label("User Account")] + public Guid UserAccountID { get; set; } + + [Required] + public string Name { get; set; } + + [Required] + public string Value { get; set; } + + public bool Enabled { get; set; } + } +} \ No newline at end of file diff --git a/src/Libraries/openXDA.Model/TransmissionElements/Asset.cs b/src/Libraries/openXDA.Model/TransmissionElements/Asset.cs new file mode 100644 index 00000000..1d484d37 --- /dev/null +++ b/src/Libraries/openXDA.Model/TransmissionElements/Asset.cs @@ -0,0 +1,615 @@ +//****************************************************************************************************** +// Asset.cs - Gbtc +// +// Copyright © 2019, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 12/12/2019 - C. Lackner +// Generated original version of source code. +// +//****************************************************************************************************** + +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Data; +using System.Text.RegularExpressions; +using Gemstone.Collections.CollectionExtensions; +using Gemstone.Data; +using Gemstone.Data.DataExtensions; +using Gemstone.Data.Model; +using Gemstone.StringExtensions; +using Newtonsoft.Json; + +namespace openXDA.Model +{ + [PostRoles("Administrator, Transmission SME")] + [DeleteRoles("Administrator, Transmission SME")] + [PatchRoles("Administrator, Transmission SME")] + public class Asset + { + #region [ Members ] + + // Nested Types + private delegate List ConnectedChannelLookup(int parentID, int childID, string traversalSQL); + + private class TraversalContext + { + public AdoDataConnection Connection { get; } + public int LocationID { get; } + + public ConnectedChannelLookup ConnectedChannelLookup { get; } + public HashSet VisitedAssets { get; } + public HashSet ConnectedChannels { get; } + + public TraversalContext(AdoDataConnection connection, int locationID) + { + Connection = connection; + LocationID = locationID; + + ConnectedChannelLookup = GetConnectedChannelLookup(connection, locationID); + VisitedAssets = new HashSet(); + ConnectedChannels = new HashSet(); + } + } + + // Fields + private List m_assetLocations; + private List m_meterAssets; + private List m_directChannels; + private List m_connectedChannels; + private List m_connections; + + #endregion + + #region [ Properties ] + + [PrimaryKey(true)] + public int ID { get; set; } + + [Required] + public double VoltageKV { get; set; } + + [Required] + [StringLength(50)] + [DefaultSortOrder] + public string AssetKey { get; set; } + + public string Description { get; set; } + + [DefaultValue("")] + public string AssetName { get; set; } + + [Required] + public int AssetTypeID {get; set; } + + public bool Spare { get; set; } + + [JsonIgnore] + [NonRecordField] + internal LazyContext LazyContext { get; set; } = new LazyContext(); + + + [JsonIgnore] + [NonRecordField] + public List AssetLocations + { + get + { + return m_assetLocations ?? (m_assetLocations = QueryAssetLocations()); + } + set + { + m_assetLocations = value; + } + } + + [JsonIgnore] + [NonRecordField] + public List MeterAssets + { + get + { + return m_meterAssets ?? (m_meterAssets = QueryMeterAssets()); + } + set + { + m_meterAssets = value; + } + } + + [JsonIgnore] + [NonRecordField] + public List DirectChannels + { + get + { + return m_directChannels ?? (m_directChannels = QueryChannels()); + } + set + { + m_directChannels = value; + } + } + + [JsonIgnore] + [NonRecordField] + public List Connections + { + get + { + return m_connections ?? (m_connections = QueryConnections()); + } + set + { + m_connections = value; + } + } + + [JsonIgnore] + [NonRecordField] + public List ConnectedChannels + { + get + { + return m_connectedChannels ?? (m_connectedChannels = QueryConnectedChannels()); + } + set + { + m_connectedChannels = value; + } + } + + [JsonIgnore] + [NonRecordField] + public List ConnectedAssets => Connections? + .SelectMany(connection => new[] { connection.Parent, connection.Child }) + .Where(asset => asset.ID != ID) + .ToList(); + + [JsonIgnore] + [NonRecordField] + public Func ConnectionFactory + { + get + { + return LazyContext.ConnectionFactory; + } + set + { + LazyContext.ConnectionFactory = value; + } + } + + #endregion + + #region [ Methods ] + + public IEnumerable GetAssetLocations(AdoDataConnection connection) + { + if (connection is null) + return null; + + TableOperations assetLocationTable = new TableOperations(connection); + return assetLocationTable.QueryRecordsWhere("AssetID = {0}", ID); + } + + private List QueryAssetLocations() + { + List assetLocations; + + using (AdoDataConnection connection = ConnectionFactory?.Invoke()) + { + assetLocations = GetAssetLocations(connection)? + .Select(LazyContext.GetAssetLocation) + .ToList(); + } + + if (!(assetLocations is null)) + { + foreach (AssetLocation assetLocation in assetLocations) + { + assetLocation.Asset = this; + assetLocation.LazyContext = LazyContext; + } + } + else + return new List(); + + return assetLocations; + } + + private List QueryMeterAssets() + { + List meterAssets; + + using (AdoDataConnection connection = ConnectionFactory?.Invoke()) + { + meterAssets = GetMeterAssets(connection)? + .Select(LazyContext.GetMeterAsset) + .ToList(); + } + + if (!(meterAssets is null)) + { + foreach (MeterAsset meterAsset in meterAssets) + { + meterAsset.Asset = this; + meterAsset.LazyContext = LazyContext; + } + } + + return meterAssets; + } + + private List QueryChannels() + { + List channels; + + using (AdoDataConnection connection = ConnectionFactory?.Invoke()) + { + channels = GetChannels(connection)? + .Select(LazyContext.GetChannel) + .ToList(); + } + + if (!(channels is null)) + { + foreach (Channel channel in channels) + { + channel.Asset = this; + channel.LazyContext = LazyContext; + } + } + + return channels; + } + + private List QueryConnectedChannels() + { + List channels; + + using (AdoDataConnection connection = ConnectionFactory?.Invoke()) + { + channels = GetConnectedChannels(connection)? + .Select(LazyContext.GetChannel) + .ToList(); + } + + if (!(channels is null)) + { + foreach (Channel channel in channels) + { + channel.LazyContext = LazyContext; + } + } + + return channels; + } + + private List QueryConnections() + { + List connections; + + using (AdoDataConnection connection = ConnectionFactory?.Invoke()) + { + connections = GetConnections(connection)? + .Select(LazyContext.GetAssetConnection) + .ToList(); + } + + if (!(connections is null)) + { + foreach (AssetConnection connection in connections) + { + connection.LazyContext = LazyContext; + } + } + + return connections; + } + + public IEnumerable GetMeterAssets(AdoDataConnection connection) + { + if (connection is null) + return null; + + TableOperations meterAssetTable = new TableOperations(connection); + return meterAssetTable.QueryRecordsWhere("AssetID = {0}", ID); + } + + public IEnumerable GetChannels(AdoDataConnection connection) + { + if (connection is null) + return null; + + TableOperations channelTable = new TableOperations(connection); + return channelTable.QueryRecordsWhere("AssetID = {0}", ID); + } + + public IEnumerable GetConnections(AdoDataConnection connection) + { + if (connection is null) + return null; + + TableOperations channelTable = new TableOperations(connection); + return channelTable.QueryRecordsWhere("ParentID = {0} OR ChildID = {1}", ID, ID); + } + + // Logic for Channels across Asset Connections + public IEnumerable GetConnectedChannels(AdoDataConnection connection) + { + if (connection is null) + return null; + + return AssetLocations + .SelectMany(assetLocation => TraverseConnectedChannels(connection, assetLocation.LocationID, ID)) + .Distinct(new ChannelComparer()); + } + + private IEnumerable TraverseConnectedChannels(AdoDataConnection connection, int locationID, int assetID) + { + TraversalContext context = new TraversalContext(connection, locationID); + ConnectedChannelLookup channelLookup = context.ConnectedChannelLookup; + context.VisitedAssets.Add(assetID); + + using (DataTable connectionTable = RetrieveConnectedAssets(connection, locationID, assetID)) + { + foreach (DataRow traversalRow in connectionTable.AsEnumerable()) + { + int connectedAssetID = traversalRow.ConvertField("ConnectedAssetID"); + string jumpSQL = traversalRow.ConvertField("JumpSQL"); + + IEnumerable jumpChannels = channelLookup(connectedAssetID, assetID, jumpSQL) + .Where(channel => channel.AssetID == connectedAssetID); + + context.ConnectedChannels.UnionWith(jumpChannels); + } + + foreach (DataRow traversalRow in connectionTable.AsEnumerable()) + { + int connectedAssetID = traversalRow.ConvertField("ConnectedAssetID"); + string passthroughSQL = traversalRow.ConvertField("PassthroughSQL"); + + List pathChannels = channelLookup(connectedAssetID, assetID, passthroughSQL) + .Where(channel => channel.AssetID != assetID) + .Except(context.ConnectedChannels) + .ToList(); + + if (pathChannels.Count == 0) + continue; + + TraverseConnectedChannels(context, pathChannels, connectedAssetID); + } + } + + return context.ConnectedChannels; + } + + private void TraverseConnectedChannels(TraversalContext context, List pathChannels, int visitedAssetID) + { + AdoDataConnection connection = context.Connection; + ConnectedChannelLookup channelLookup = context.ConnectedChannelLookup; + int locationID = context.LocationID; + + context.VisitedAssets.Add(visitedAssetID); + + using (DataTable connectionTable = RetrieveConnectedAssets(connection, locationID, visitedAssetID)) + { + foreach (DataRow traversalRow in connectionTable.AsEnumerable()) + { + int connectedAssetID = traversalRow.ConvertField("ConnectedAssetID"); + + if (context.VisitedAssets.Contains(connectedAssetID)) + continue; + + string jumpSQL = traversalRow.ConvertField("JumpSQL"); + + IEnumerable jumpChannels = channelLookup(connectedAssetID, visitedAssetID, jumpSQL) + .Where(channel => channel.AssetID == connectedAssetID) + .Intersect(pathChannels); + + context.ConnectedChannels.UnionWith(jumpChannels); + } + + HashSet filteredPathChannels = new HashSet(pathChannels); + filteredPathChannels.ExceptWith(context.ConnectedChannels); + + foreach (DataRow traversalRow in connectionTable.AsEnumerable()) + { + if (filteredPathChannels.Count == 0) + break; + + int connectedAssetID = traversalRow.ConvertField("ConnectedAssetID"); + + if (context.VisitedAssets.Contains(connectedAssetID)) + continue; + + string passthroughSQL = traversalRow.ConvertField("PassthroughSQL"); + + List passthroughChannels = channelLookup(connectedAssetID, visitedAssetID, passthroughSQL) + .Intersect(filteredPathChannels) + .ToList(); + + if (passthroughChannels.Count == 0) + continue; + + int connectedCount = context.ConnectedChannels.Count; + TraverseConnectedChannels(context, passthroughChannels, connectedAssetID); + + if (context.ConnectedChannels.Count != connectedCount) + filteredPathChannels.ExceptWith(context.ConnectedChannels); + } + } + + context.VisitedAssets.Remove(visitedAssetID); + } + + private DataTable RetrieveConnectedAssets(AdoDataConnection connection, int locationID, int visitedAssetID) + { + const string TraversalQueryFormat = + "SELECT DISTINCT " + + " ConnectedAsset.ID ConnectedAssetID, " + + " AssetRelationshipType.JumpConnection JumpSQL, " + + " AssetRelationshipType.PassThrough PassthroughSQL " + + "FROM " + + " Location JOIN " + + " Asset VisitedAsset ON " + + " Location.ID = {0} AND " + + " VisitedAsset.ID = {1} JOIN " + + " AssetConnection ON VisitedAsset.ID IN (AssetConnection.ParentID, AssetConnection.ChildID) JOIN " + + " Asset ConnectedAsset ON " + + " ConnectedAsset.ID IN (AssetConnection.ParentID, AssetConnection.ChildID) AND " + + " ConnectedAsset.ID <> VisitedAsset.ID JOIN " + + " AssetRelationshipType ON AssetConnection.AssetRelationshipTypeID = AssetRelationshipType.ID JOIN " + + " AssetLocation ON " + + " AssetLocation.AssetID = ConnectedAsset.ID AND " + + " AssetLocation.LocationID = Location.ID"; + + return connection.RetrieveData(TraversalQueryFormat, locationID, visitedAssetID); + } + + // Logic to find distance between two Assets + public int DistanceToAsset(int assetID) + { + int distance = 0; + HashSet visited = new HashSet(); + List next = new List() { this }; + + while (next.Count > 0) + { + if (next.Any(n => n.ID == assetID)) + return distance; + + foreach (Asset n in next) + visited.Add(n.ID); + + next = next + .SelectMany(n => n.ConnectedAssets) + .DistinctBy(n => n.ID) + .Where(n => !visited.Contains(n.ID)) + .ToList(); + + distance++; + } + + return -1; + } + + public T QueryAs(AdoDataConnection connection = null) where T : Asset, new() + { + if (this is T typedAsset) + return typedAsset; + + if (connection is null && ConnectionFactory is null) + throw new ArgumentNullException(nameof(connection)); + + Lazy lazyConnection = new Lazy(ConnectionFactory); + + try + { + TableOperations table = new TableOperations(connection ?? lazyConnection.Value); + return table.QueryRecordWhere("ID = {0}", ID); + } + finally + { + if (lazyConnection.IsValueCreated) + lazyConnection.Value.Dispose(); + } + } + + [Obsolete("Replaced by GetChannels")] + public IEnumerable GetChannel(AdoDataConnection connection) => GetChannels(connection); + + [Obsolete("Replaced by GetConnections")] + public IEnumerable GetConnection(AdoDataConnection connection) => GetConnections(connection); + + [Obsolete("Replaced by GetConnectedChannels")] + public IEnumerable GetConnectedChannel(AdoDataConnection connection) => GetConnectedChannels(connection); + + #endregion + + #region [ Static ] + + // Static Methods + private static ConnectedChannelLookup GetConnectedChannelLookup(AdoDataConnection connection, int locationID) + { + const string ChannelQueryFormat = + "SELECT SourceChannel.* " + + "FROM " + + " Channel SourceChannel JOIN " + + " Meter ON " + + " SourceChannel.MeterID = Meter.ID AND " + + " Meter.LocationID = {{0}} JOIN " + + " Asset ParentAsset ON ParentAsset.ID = {{1}} JOIN " + + " Asset ChildAsset ON ChildAsset.ID = {{2}} CROSS APPLY " + + " ({TraversalSQL}) Traversal(Traverse) " + + "WHERE Traversal.Traverse <> 0"; + + Dictionary channelLookup = new Dictionary(); + + var connectedChannelLookup = Enumerable + .Empty>() + .ToDictionary(_ => new { ParentID = 0, ChildID = 0, TraversalSQL = "" }); + + return (parentID, childID, traversalSQL) => + { + var key = new { ParentID = parentID, ChildID = childID, TraversalSQL = traversalSQL }; + return connectedChannelLookup.GetOrAdd(key, _ => RetrieveConnectedChannels(parentID, childID, traversalSQL)); + }; + + List RetrieveConnectedChannels(int parentID, int childID, string traversalSQL) + { + TableOperations channelTable = new TableOperations(connection); + + // lang=regex + const string Pattern = @"\{(?:parentid|childid|channelid)\}"; + string replacedSQL = Regex.Replace(traversalSQL, Pattern, ReplaceFormatParameter, RegexOptions.IgnoreCase); + + string channelQuery = ChannelQueryFormat + .Interpolate(new { TraversalSQL = replacedSQL }); + + return RetrieveRows(channelQuery, locationID, parentID, childID) + .Select(channelTable.LoadRecord) + .Select(LookUpChannel) + .ToList(); + } + + Channel LookUpChannel(Channel channel) => + channelLookup.GetOrAdd(channel.ID, _ => channel); + + string ReplaceFormatParameter(Match match) + { + switch (match.Value.ToLowerInvariant()) + { + case "{parentid}": return "ParentAsset.ID"; + case "{childid}": return "ChildAsset.ID"; + case "{channelid}": return "SourceChannel.ID"; + default: return match.Value; + } + } + + IEnumerable RetrieveRows(string query, params object[] args) + { + using (DataTable table = connection.RetrieveData(query, args)) + { + foreach (DataRow row in table.Rows) + yield return row; + } + } + } + + #endregion + } +} diff --git a/src/Libraries/openXDA.Model/TransmissionElements/SourceImpedance.cs b/src/Libraries/openXDA.Model/TransmissionElements/SourceImpedance.cs new file mode 100644 index 00000000..998f1d16 --- /dev/null +++ b/src/Libraries/openXDA.Model/TransmissionElements/SourceImpedance.cs @@ -0,0 +1,114 @@ +//****************************************************************************************************** +// SourceImpedance.cs - Gbtc +// +// Copyright © 2017, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 06/19/2017 - Billy Ernest +// Generated original version of source code. +// +//****************************************************************************************************** + +using Gemstone.Data; +using Gemstone.Data.Model; +using Newtonsoft.Json; + +namespace openXDA.Model +{ + [TableName("SourceImpedance")] + [PostRoles("Administrator, Transmission SME")] + [PatchRoles("Administrator, Transmission SME")] + [DeleteRoles("Administrator, Transmission SME")] + public class SourceImpedance + { + #region [ Members ] + + // Fields + private AssetLocation m_assetLocation; + + #endregion + + #region [ Properties ] + + [PrimaryKey(true)] + public int ID { get; set; } + + [ParentKey(typeof(AssetLocation))] + public int AssetLocationID { get; set; } + + public double RSrc { get; set; } + + public double XSrc { get; set; } + + [JsonIgnore] + [NonRecordField] + public AssetLocation AssetLocation + { + get + { + if (m_assetLocation is null) + m_assetLocation = LazyContext.GetAssetLocation(AssetLocationID); + + if (m_assetLocation is null) + m_assetLocation = QueryAssetLocation(); + + return m_assetLocation; + } + set => m_assetLocation = value; + } + + [JsonIgnore] + [NonRecordField] + public Func ConnectionFactory + { + get => LazyContext.ConnectionFactory; + set => LazyContext.ConnectionFactory = value; + } + + [JsonIgnore] + [NonRecordField] + internal LazyContext LazyContext { get; set; } = new LazyContext(); + + #endregion + + #region [ Methods ] + + public AssetLocation GetAssetLocation(AdoDataConnection connection) + { + if ((object)connection == null) + return null; + + TableOperations assetLocationTable = new TableOperations(connection); + return assetLocationTable.QueryRecordWhere("ID = {0}", AssetLocationID); + } + + private AssetLocation QueryAssetLocation() + { + AssetLocation assetLocation; + + using (AdoDataConnection connection = ConnectionFactory?.Invoke()) + { + assetLocation = GetAssetLocation(connection); + } + + if ((object)assetLocation != null) + assetLocation.LazyContext = LazyContext; + + return LazyContext.GetAssetLocation(assetLocation); + } + + #endregion + } +} diff --git a/src/Libraries/openXDA.Model/openXDA.Model.csproj b/src/Libraries/openXDA.Model/openXDA.Model.csproj new file mode 100644 index 00000000..68b24c0c --- /dev/null +++ b/src/Libraries/openXDA.Model/openXDA.Model.csproj @@ -0,0 +1,15 @@ + + + + net9.0 + enable + enable + + + + + + + + + diff --git a/src/OpenSEE.sln b/src/OpenSEE.sln index 1423d43c..b16dcff9 100644 --- a/src/OpenSEE.sln +++ b/src/OpenSEE.sln @@ -10,6 +10,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution OpenSEE\.eslintrc = OpenSEE\.eslintrc EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Libraries", "Libraries", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FaultAlgorithms", "Libraries\FaultAlgorithms\FaultAlgorithms.csproj", "{79E70E44-FB78-B280-97D7-1650186161DF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FaultData", "Libraries\FaultData\FaultData.csproj", "{1C77C616-BE00-A5A0-8109-C0D9F7C0202E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "openXDA.Model", "Libraries\openXDA.Model\openXDA.Model.csproj", "{F463A000-041D-CC7D-0954-3942A5D4A946}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -20,10 +28,27 @@ Global {845F68F7-4094-4FE6-95E3-1B113BBFAD3F}.Debug|Any CPU.Build.0 = Debug|Any CPU {845F68F7-4094-4FE6-95E3-1B113BBFAD3F}.Release|Any CPU.ActiveCfg = Release|Any CPU {845F68F7-4094-4FE6-95E3-1B113BBFAD3F}.Release|Any CPU.Build.0 = Release|Any CPU + {79E70E44-FB78-B280-97D7-1650186161DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {79E70E44-FB78-B280-97D7-1650186161DF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {79E70E44-FB78-B280-97D7-1650186161DF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {79E70E44-FB78-B280-97D7-1650186161DF}.Release|Any CPU.Build.0 = Release|Any CPU + {1C77C616-BE00-A5A0-8109-C0D9F7C0202E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1C77C616-BE00-A5A0-8109-C0D9F7C0202E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1C77C616-BE00-A5A0-8109-C0D9F7C0202E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1C77C616-BE00-A5A0-8109-C0D9F7C0202E}.Release|Any CPU.Build.0 = Release|Any CPU + {F463A000-041D-CC7D-0954-3942A5D4A946}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F463A000-041D-CC7D-0954-3942A5D4A946}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F463A000-041D-CC7D-0954-3942A5D4A946}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F463A000-041D-CC7D-0954-3942A5D4A946}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {79E70E44-FB78-B280-97D7-1650186161DF} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {1C77C616-BE00-A5A0-8109-C0D9F7C0202E} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {F463A000-041D-CC7D-0954-3942A5D4A946} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3E81769A-8E91-4DE2-AD77-438C680437A5} EndGlobalSection diff --git a/src/OpenSEE/OpenSEE.csproj b/src/OpenSEE/OpenSEE.csproj index 1be987b1..958caefd 100644 --- a/src/OpenSEE/OpenSEE.csproj +++ b/src/OpenSEE/OpenSEE.csproj @@ -110,6 +110,11 @@ if $(ConfigurationName) == Release npm run buildrelease + + + + + From 2285480804aec3d9f8630b67b50bcea2a57bc66d Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Tue, 20 Jan 2026 11:00:14 -0500 Subject: [PATCH 06/58] update to be compatible with netcore 9 --- src/OpenSEE/Controllers/OpenSeeControllerBase.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/OpenSEE/Controllers/OpenSeeControllerBase.cs b/src/OpenSEE/Controllers/OpenSeeControllerBase.cs index 87669e98..c45a662f 100644 --- a/src/OpenSEE/Controllers/OpenSeeControllerBase.cs +++ b/src/OpenSEE/Controllers/OpenSeeControllerBase.cs @@ -26,17 +26,17 @@ using System.Linq; using System.Runtime.Caching; using System.Threading.Tasks; -using System.Web.Http; using FaultData.DataAnalysis; using Gemstone.Configuration; using Gemstone.Data; -using Gemstone.NumericalAnalysis; +using Gemstone.Numeric.Interpolation; +using Microsoft.AspNetCore.Mvc; using OpenSEE.Model; using openXDA.Model; namespace OpenSEE { - public class OpenSEEBaseController : ApiController + public class OpenSEEBaseController : Controller { #region [ Members ] From 2e3701c447354e577ed34e36b832da4181457182 Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Tue, 20 Jan 2026 11:20:36 -0500 Subject: [PATCH 07/58] additional required xda model imports --- .../Events/Disturbances/Disturbance.cs | 67 +++++++++ src/Libraries/openXDA.Model/Events/Event.cs | 19 +++ .../openXDA.Model/Events/Faults/Fault.cs | 137 ++++++++++++++++++ .../openXDA.Model/Events/RelayPerformance.cs | 63 ++++++++ .../TransmissionElements/BreakerOperation.cs | 111 ++++++++++++++ 5 files changed, 397 insertions(+) create mode 100644 src/Libraries/openXDA.Model/Events/Disturbances/Disturbance.cs create mode 100644 src/Libraries/openXDA.Model/Events/Faults/Fault.cs create mode 100644 src/Libraries/openXDA.Model/Events/RelayPerformance.cs create mode 100644 src/Libraries/openXDA.Model/TransmissionElements/BreakerOperation.cs diff --git a/src/Libraries/openXDA.Model/Events/Disturbances/Disturbance.cs b/src/Libraries/openXDA.Model/Events/Disturbances/Disturbance.cs new file mode 100644 index 00000000..c83ce0e6 --- /dev/null +++ b/src/Libraries/openXDA.Model/Events/Disturbances/Disturbance.cs @@ -0,0 +1,67 @@ +//****************************************************************************************************** +// Disturbance.cs - Gbtc +// +// Copyright © 2017, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 08/29/2017 - Billy Ernest +// Generated original version of source code. +// +//****************************************************************************************************** + +using System; +using Gemstone.Data.Model; + +namespace openXDA.Model +{ + public class Disturbance + { + [PrimaryKey(true)] + public int ID { get; set; } + public int EventID { get; set; } + public int EventTypeID { get; set; } + public int PhaseID { get; set; } + public double Magnitude { get; set; } + public double PerUnitMagnitude { get; set; } + + [FieldDataType(System.Data.DbType.DateTime2, Gemstone.Data.DatabaseType.SQLServer)] + public DateTime StartTime { get; set; } + + [FieldDataType(System.Data.DbType.DateTime2, Gemstone.Data.DatabaseType.SQLServer)] + public DateTime EndTime { get; set; } + + public double DurationSeconds { get; set; } + public double DurationCycles { get; set; } + public int StartIndex { get; set; } + public int EndIndex { get; set; } + public string UpdatedBy { get; set; } + } + + [TableName("DisturbanceView")] + public class DisturbanceView: Disturbance + { + public int MeterID { get; set; } + public int LineID { get; set; } + public int? SeverityCode { get; set; } + public string MeterName { get; set; } + public string PhaseName { get; set; } + } + + [TableName("DisturbanceView")] + public class DisturbancesForDay : DisturbanceView { } + + [TableName("DisturbanceView")] + public class DisturbancesForMeter : DisturbanceView { } +} \ No newline at end of file diff --git a/src/Libraries/openXDA.Model/Events/Event.cs b/src/Libraries/openXDA.Model/Events/Event.cs index 0b60dc4b..992ad47e 100644 --- a/src/Libraries/openXDA.Model/Events/Event.cs +++ b/src/Libraries/openXDA.Model/Events/Event.cs @@ -69,4 +69,23 @@ public class Event public string UpdatedBy { get; set; } } + + [TableName("EventView")] + public class EventView : Event + { + [PrimaryKey(true)] + public new int ID + { + get => base.ID; + set => base.ID = value; + } + + public string AssetName { get; set; } + + public string MeterName { get; set; } + + public string StationName { get; set; } + + public string EventTypeName { get; set; } + } } \ No newline at end of file diff --git a/src/Libraries/openXDA.Model/Events/Faults/Fault.cs b/src/Libraries/openXDA.Model/Events/Faults/Fault.cs new file mode 100644 index 00000000..dbf8b955 --- /dev/null +++ b/src/Libraries/openXDA.Model/Events/Faults/Fault.cs @@ -0,0 +1,137 @@ +//****************************************************************************************************** +// Fault.cs - Gbtc +// +// Copyright © 2017, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 08/29/2017 - Billy Ernest +// Generated original version of source code. +// +//****************************************************************************************************** + +using System; +using Gemstone.Data.Model; + +namespace openXDA.Model +{ + [TableName("FaultSummary")] + public class Fault + { + [PrimaryKey(true)] + public int ID { get; set; } + + public int EventID { get; set; } + + public string Algorithm { get; set; } + + public int FaultNumber { get; set; } + + public int CalculationCycle { get; set; } + + public double Distance { get; set; } + + public int PathNumber { get; set; } + + public int LineSegmentID { get; set; } + + public double LineSegmentDistance { get; set; } + + public double CurrentMagnitude { get; set; } + + public double CurrentLag { get; set; } + + public double PrefaultCurrent { get; set; } + + public double PostfaultCurrent { get; set; } + + public double ReactanceRatio { get; set; } + + [FieldDataType(System.Data.DbType.DateTime2, Gemstone.Data.DatabaseType.SQLServer)] + public DateTime Inception { get; set; } + + public double DurationSeconds { get; set; } + + public double DurationCycles { get; set; } + + public string FaultType { get; set; } + + public bool IsSelectedAlgorithm { get; set; } + + public bool IsValid { get; set; } + + public bool IsSuppressed { get; set; } + } + + public class FaultSummary : Fault { } + + [TableName("FaultView")] + public class FaultView : Fault + { + public string MeterName { get; set; } + + public string ShortName { get; set; } + + public string LocationName { get; set; } + + public int MeterID { get; set; } + + public int LineID { get; set; } + + public string LineName { get; set; } + + public int Voltage { get; set; } + + public DateTime InceptionTime { get; set; } + + public double CurrentDistance { get; set; } + + public int RK { get; set; } + } + + [TableName("FaultView")] + public class FaultForMeter: FaultView { } + + public class FaultsDetailsByDate + { + public int thefaultid { get; set; } + + public string thesite { get; set; } + + public string locationname { get; set; } + + public int themeterid { get; set; } + + public int thelineid { get; set; } + + public int theeventid { get; set; } + + public string thelinename { get; set; } + + public int voltage { get; set; } + + public string theinceptiontime { get; set; } + + public string thefaulttype { get; set; } + + public double thecurrentdistance { get; set; } + + public int notecount { get; set; } + + public int rk { get; set; } + + [NonRecordField] + public string theeventtype { get; set; } + } +} \ No newline at end of file diff --git a/src/Libraries/openXDA.Model/Events/RelayPerformance.cs b/src/Libraries/openXDA.Model/Events/RelayPerformance.cs new file mode 100644 index 00000000..4950235e --- /dev/null +++ b/src/Libraries/openXDA.Model/Events/RelayPerformance.cs @@ -0,0 +1,63 @@ +//****************************************************************************************************** +// RelayPerformance.cs - Gbtc +// +// Copyright © 2019, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 07/10/2019 - Christoph Lackner +// Generated original version of source code. +// 08/20/2021 - Christoph Lackner +// Added additional Trip Coil Curve points. +// +//****************************************************************************************************** + +using System.Data; +using Gemstone.Data; +using Gemstone.Data.Model; + +namespace openXDA.Model +{ + public class RelayPerformance + { + [PrimaryKey(true)] + public int ID { get; set; } + public int EventID { get; set; } + public int ChannelID { get; set; } + public double? Imax1 { get; set; } + public int? Tmax1 { get; set; } + public double? Imax2 { get; set; } + public int? TplungerLatch { get; set; } + public double IplungerLatch { get; set; } + public double? Idrop { get; set; } + public int? TiDrop { get; set; } + public int? Tend { get; set; } + + [FieldDataType(DbType.DateTime2, DatabaseType.SQLServer)] + public DateTime? TripInitiate { get; set; } + public int? TripTime { get; set; } + public int? PickupTime { get; set; } + public double? TripTimeCurrent { get; set;} + public double? PickupTimeCurrent { get; set; } + public double? TripCoilCondition { get; set; } + public int TripCoilConditionTime { get; set; } + public int? ExtinctionTimeA { get; set; } + public int? ExtinctionTimeB { get; set; } + public int? ExtinctionTimeC { get; set; } + public double? I2CA { get; set; } + public double? I2CB { get; set; } + public double? I2CC { get; set; } + + } +} diff --git a/src/Libraries/openXDA.Model/TransmissionElements/BreakerOperation.cs b/src/Libraries/openXDA.Model/TransmissionElements/BreakerOperation.cs new file mode 100644 index 00000000..fdf4ab18 --- /dev/null +++ b/src/Libraries/openXDA.Model/TransmissionElements/BreakerOperation.cs @@ -0,0 +1,111 @@ +//****************************************************************************************************** +// BreakerOperation.cs - Gbtc +// +// Copyright © 2017, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 08/29/2017 - Billy Ernest +// Generated original version of source code. +// +//****************************************************************************************************** + +using System; +using System.Data; +using GSF.Data; +using GSF.Data.Model; + +namespace openXDA.Model +{ + public class BreakerOperation + { + [PrimaryKey(true)] + public int ID { get; set; } + + public int EventID { get; set; } + + public int PhaseID { get; set; } + + public int BreakerOperationTypeID { get; set; } + + public string BreakerNumber { get; set; } + + [FieldDataType(DbType.DateTime2, DatabaseType.SQLServer)] + public DateTime TripCoilEnergized { get; set; } + + [FieldDataType(DbType.DateTime2, DatabaseType.SQLServer)] + public DateTime StatusBitSet { get; set; } + + public bool StatusBitChatter { get; set; } + + [FieldDataType(DbType.DateTime2, DatabaseType.SQLServer)] + public DateTime APhaseCleared { get; set; } + + [FieldDataType(DbType.DateTime2, DatabaseType.SQLServer)] + public DateTime BPhaseCleared { get; set; } + + [FieldDataType(DbType.DateTime2, DatabaseType.SQLServer)] + public DateTime CPhaseCleared { get; set; } + + public double BreakerTiming { get; set; } + + public double StatusTiming { get; set; } + + public double APhaseBreakerTiming { get; set; } + + public double BPhaseBreakerTiming { get; set; } + + public double CPhaseBreakerTiming { get; set; } + + public bool DcOffsetDetected { get; set; } + + public double BreakerSpeed { get; set; } + + public string UpdatedBy { get; set; } + } + + [TableName("BreakerOperation")] + public class BreakersForDay : BreakerOperation { } + + public class BreakerView + { + [PrimaryKey(true)] + public int ID { get; set; } + + public int MeterID { get; set; } + + public int EventID { get; set; } + + public string EventType { get; set; } + + public string Energized { get; set; } + + [Searchable] + public int BreakerNumber { get; set; } + + [Searchable] + public string LineName { get; set; } + + public string PhaseName { get; set; } + + public double Timing { get; set; } + + public int Speed { get; set; } + + [Searchable] + public string OperationType { get; set; } + + public string UpdatedBy { get; set; } + } +} \ No newline at end of file From d85179903bc0e67383a0d40b1925f5d22bee2db7 Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Tue, 20 Jan 2026 11:20:47 -0500 Subject: [PATCH 08/58] fix to work with core 9 --- src/OpenSEE/Controllers/OpenSEEController.cs | 85 ++++++++------------ 1 file changed, 35 insertions(+), 50 deletions(-) diff --git a/src/OpenSEE/Controllers/OpenSEEController.cs b/src/OpenSEE/Controllers/OpenSEEController.cs index dbb941eb..1352d522 100644 --- a/src/OpenSEE/Controllers/OpenSEEController.cs +++ b/src/OpenSEE/Controllers/OpenSEEController.cs @@ -28,23 +28,24 @@ using System; using System.Collections.Generic; using System.Data; -using System.Data.SqlClient; using System.Globalization; using System.Linq; using System.Runtime.Caching; using System.Threading.Tasks; -using System.Web.Http; using FaultData.DataAnalysis; using Gemstone.Configuration; using Gemstone.Data; +using Gemstone.Data.DataExtensions; using Gemstone.Data.Model; -using Gemstone.Web; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Data.SqlClient; +using Microsoft.Extensions.Primitives; using OpenSEE.Model; using openXDA.Model; namespace OpenSEE { - [RoutePrefix("api/OpenSEE")] + [Route("api/OpenSEE")] public class OpenSEEController : OpenSEEBaseController { #region [ Members ] @@ -123,20 +124,18 @@ public async Task GetData() { using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { - Dictionary query = Request.QueryParameters(); - - int eventId = int.Parse(query["eventId"]); - string type = query["type"]; - string dataType = query["dataType"]; + int eventId = int.Parse(Request.Query["eventId"].ToString()); + string type = Request.Query["type"].ToString(); + string dataType = Request.Query["dataType"].ToString(); bool forceFullRes = - query.TryGetValue("fullRes", out string fullResSetting) && - int.TryParse(fullResSetting, out int fullResNum) && + Request.Query.TryGetValue("fullRes", out StringValues fullResSetting) && + int.TryParse(fullResSetting.ToString(), out int fullResNum) && fullResNum != 0; bool dbgNocompress = - query.TryGetValue("dbgNocompress", out string dbgNocompressSetting) && - int.TryParse(dbgNocompressSetting, out int dbgNocompressNum) && + Request.Query.TryGetValue("dbgNocompress", out StringValues dbgNocompressSetting) && + int.TryParse(dbgNocompressSetting.ToString(), out int dbgNocompressNum) && dbgNocompressNum != 0; Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); @@ -350,13 +349,12 @@ private List GetD3FrequencyDataLookup(VICycleDataGroup vICycleDataGrou public async Task GetBreakerData() { using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) - { - Dictionary query = Request.QueryParameters(); - int eventId = int.Parse(query["eventId"]); + {; + int eventId = int.Parse(Request.Query["eventId"].ToString()); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection(connection.Connection, typeof(SqlDataAdapter), false); + meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); DataGroup dataGroup = await QueryDataGroupAsync(evt.ID, meter); List resultList = GetBreakerLookup(dataGroup); @@ -410,12 +408,11 @@ public async Task GetAnalogsData() { using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { - Dictionary query = Request.QueryParameters(); - int eventId = int.Parse(query["eventId"]); + int eventId = int.Parse(Request.Query["eventId"].ToString()); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection(connection.Connection, typeof(SqlDataAdapter), false); + meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); DataGroup dataGroup = await QueryDataGroupAsync(evt.ID, meter); List returnList = GetAnalogsLookup(dataGroup); @@ -456,9 +453,8 @@ private List GetAnalogsLookup(DataGroup dataGroup) [Route("GetHeaderData"),HttpGet] public Dictionary GetHeaderData() { - Dictionary query = Request.QueryParameters(); - int eventId = int.Parse(query["eventId"]); - string breakerOperationID = (query.ContainsKey("breakeroperation") ? query["breakeroperation"] : "-1"); + int eventId = int.Parse(Request.Query["eventId"].ToString()); + string breakerOperationID = (Request.Query.ContainsKey("breakeroperation") ? Request.Query["breakeroperation"].ToString() : "-1"); Dictionary returnDict = new Dictionary(); @@ -571,8 +567,7 @@ public Dictionary GetHeaderData() [Route("GetNavData"), HttpGet] public Dictionary> GetNavData() { - Dictionary query = Request.QueryParameters(); - int eventId = int.Parse(query["eventId"]); + int eventId = int.Parse(Request.Query["eventId"].ToString()); Dictionary> nextBackLookup = new Dictionary>() { @@ -649,12 +644,13 @@ public DataTable GetOverlappingEvents() { using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { - Dictionary query = Request.QueryParameters(); - int eventId = int.Parse(query["eventId"]); + int eventId = int.Parse(Request.Query["eventId"].ToString()); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); - DateTime startTime = ((query.ContainsKey("startDate") && query["startDate"] != "null") ? DateTime.Parse(query["startDate"]) : evt.StartTime); - DateTime endTime = ((query.ContainsKey("endDate") && query["endDate"] != "null") ? DateTime.Parse(query["endDate"]) : evt.EndTime); + DateTime startTime = (Request.Query.ContainsKey("startDate") && Request.Query["startDate"].ToString() != "null") ? + DateTime.Parse(Request.Query["startDate"].ToString()) : evt.StartTime; + DateTime endTime = (Request.Query.ContainsKey("endDate") && Request.Query["endDate"].ToString() != "null") ? + DateTime.Parse(Request.Query["endDate"].ToString()) : evt.EndTime; DataTable dataTable = connection.RetrieveData(@" @@ -711,8 +707,7 @@ FROM Disturbance [Route("GetScalarStats"),HttpGet] public Dictionary GetScalarStats() { - Dictionary query = Request.QueryParameters(); - int eventId = int.Parse(query["eventId"]); + int eventId = int.Parse(Request.Query["eventId"].ToString()); using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { @@ -728,8 +723,7 @@ public Dictionary GetScalarStats() [Route("GetHarmonics"),HttpGet] public DataTable GetHarmonics() { - Dictionary query = Request.QueryParameters(); - int eventId = int.Parse(query["eventId"]); + int eventId = int.Parse(Request.Query["eventId"].ToString()); using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { @@ -752,8 +746,7 @@ SnapshotHarmonics JOIN [Route("GetTimeCorrelatedSags"), HttpGet] public DataTable GetTimeCorrelatedSags() { - Dictionary query = Request.QueryParameters(); - int eventID = int.Parse(query["eventId"]); + int eventID = int.Parse(Request.Query["eventId"].ToString()); if (eventID <= 0) return new DataTable(); using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) @@ -771,8 +764,7 @@ public DataTable GetTimeCorrelatedSags() [Route("GetLightningData"), HttpGet] public IEnumerable GetLightningData() { - Dictionary query = Request.QueryParameters(); - int eventID = int.Parse(query["eventID"]); + int eventID = int.Parse(Request.Query["eventID"].ToString()); using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { @@ -816,14 +808,12 @@ public IEnumerable GetLightningData() } [Route("GetOutputChannelCount/{eventID}"), HttpGet] - public IHttpActionResult GetOutputChannelCount(int eventID) + public ActionResult GetOutputChannelCount(int eventID) { - try + if (eventID <= 0) return BadRequest("Invalid EventID"); + using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { - if (eventID <= 0) return BadRequest("Invalid EventID"); - using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) - { - int count = connection.ExecuteScalar(@" + int count = connection.ExecuteScalar(@" SELECT COUNT(*) FROM @@ -834,12 +824,7 @@ Event JOIN WHERE Event.ID = {0} ", eventID); - return Ok(count); - } - } - catch(Exception ex) - { - return InternalServerError(ex); + return Ok(count); } } @@ -849,7 +834,7 @@ Event JOIN #region [ Note Management ] [Route("GetPQBrowser"), HttpGet] - public IHttpActionResult GetPQBrowser() + public ActionResult GetPQBrowser() { using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { From aa2fcbb88874d6f5ecc8f2fad3d0bf062859707b Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Tue, 20 Jan 2026 13:31:58 -0500 Subject: [PATCH 09/58] migrated to netcore --- src/OpenSEE/Controllers/HomeController.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/OpenSEE/Controllers/HomeController.cs b/src/OpenSEE/Controllers/HomeController.cs index f93d4627..239b7436 100644 --- a/src/OpenSEE/Controllers/HomeController.cs +++ b/src/OpenSEE/Controllers/HomeController.cs @@ -22,14 +22,13 @@ //****************************************************************************************************** using System; -using Microsoft.AspNetCore.Mvc; +using Gemstone.Configuration; using Gemstone.Data; using Gemstone.Data.Model; -using Gemstone.Identity; -using Gemstone.Web.Model; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Primitives; using OpenSEE.Model; using openXDA.Model; -using Gemstone.Configuration; namespace OpenSEE.Controllers { @@ -56,8 +55,8 @@ public ActionResult Home() int eventID = -1; Event evt; - if (Request.QueryString.Get("eventid") != null) - eventID = int.Parse(Request.QueryString["eventid"]); + if (Request.Query.TryGetValue("eventid", out StringValues evtString)) + eventID = int.Parse(evtString.ToString()); using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { From 5e33f8ac43b600d83391601dd09de3a54f0e232c Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Tue, 20 Jan 2026 13:34:14 -0500 Subject: [PATCH 10/58] change to migrate, security is a concern for later --- src/OpenSEE/Controllers/LoginController.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/OpenSEE/Controllers/LoginController.cs b/src/OpenSEE/Controllers/LoginController.cs index 525e5717..d7a99357 100644 --- a/src/OpenSEE/Controllers/LoginController.cs +++ b/src/OpenSEE/Controllers/LoginController.cs @@ -23,8 +23,8 @@ using System; using System.Threading; -using System.Web.Mvc; -using Gemstone.Web.Security; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; namespace OpenSEE.Controllers; @@ -40,7 +40,6 @@ public ActionResult Index() } [Route("~/AuthTest")] - [AuthorizeControllerRole] public ActionResult AuthTest() { return View(); @@ -54,7 +53,6 @@ public ActionResult Logout() } [Route("~/UserInfo")] - [AuthorizeControllerRole] public ActionResult UserInfo() { Thread.CurrentPrincipal = ViewBag.SecurityPrincipal = User; From e066516784b582ddafa0f3df550ecfa92c8507a0 Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Tue, 20 Jan 2026 14:29:34 -0500 Subject: [PATCH 11/58] model additions for analysis controller --- .../openXDA.Model/Events/BreakerRestrike.cs | 65 +++++ .../openXDA.Model/Events/Faults/FaultCurve.cs | 250 ++++++++++++++++++ 2 files changed, 315 insertions(+) create mode 100644 src/Libraries/openXDA.Model/Events/BreakerRestrike.cs create mode 100644 src/Libraries/openXDA.Model/Events/Faults/FaultCurve.cs diff --git a/src/Libraries/openXDA.Model/Events/BreakerRestrike.cs b/src/Libraries/openXDA.Model/Events/BreakerRestrike.cs new file mode 100644 index 00000000..e76661d5 --- /dev/null +++ b/src/Libraries/openXDA.Model/Events/BreakerRestrike.cs @@ -0,0 +1,65 @@ +//****************************************************************************************************** +// BreakerRestrike.cs - Gbtc +// +// Copyright © 2019, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 08/30/2019 - Stephen C. Wills +// Generated original version of source code. +// +//****************************************************************************************************** + +using System.Data; +using Gemstone.Data; +using Gemstone.Data.Model; + +namespace openXDA.Model +{ + public class BreakerRestrike + { + [PrimaryKey(true)] + public int ID { get; set; } + + public int EventID { get; set; } + + public int PhaseID { get; set; } + + public int InitialExtinguishSample { get; set; } + + [FieldDataType(DbType.DateTime2, DatabaseType.SQLServer)] + public DateTime InitialExtinguishTime { get; set; } + public double InitialExtinguishVoltage { get; set; } + public int RestrikeSample { get; set; } + + [FieldDataType(DbType.DateTime2, DatabaseType.SQLServer)] + public DateTime RestrikeTime { get; set; } + public double RestrikeVoltage { get; set; } + public double RestrikeCurrentPeak { get; set; } + public double RestrikeVoltageDip { get; set; } + public int TransientPeakSample { get; set; } + + [FieldDataType(DbType.DateTime2, DatabaseType.SQLServer)] + public DateTime TransientPeakTime { get; set; } + public double TransientPeakVoltage { get; set; } + public double PerUnitTransientPeakVoltage { get; set; } + public int FinalExtinguishSample { get; set; } + + [FieldDataType(DbType.DateTime2, DatabaseType.SQLServer)] + public DateTime FinalExtinguishTime { get; set; } + public double FinalExtinguishVoltage { get; set; } + public double I2t { get; set; } + + } +} diff --git a/src/Libraries/openXDA.Model/Events/Faults/FaultCurve.cs b/src/Libraries/openXDA.Model/Events/Faults/FaultCurve.cs new file mode 100644 index 00000000..02268fa2 --- /dev/null +++ b/src/Libraries/openXDA.Model/Events/Faults/FaultCurve.cs @@ -0,0 +1,250 @@ +//****************************************************************************************************** +// FaultCurve.cs - Gbtc +// +// Copyright © 2017, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 09/06/2017 - Stephen C. Wills +// Generated original version of source code. +// +//****************************************************************************************************** + +using System.ComponentModel.DataAnnotations; +using Gemstone; +using Gemstone.Data.Model; +using Ionic.Zlib; + +namespace openXDA.Model +{ + public class FaultCurve + { + [PrimaryKey(true)] + public int ID { get; set; } + + public int EventID { get; set; } + + public int PathNumber { get; set; } + + [StringLength(200)] + public string Algorithm { get; set; } + + public byte[] Data { get; set; } + + public byte[] AngleData { get; set; } + + #region [Private Class] + private class DataPoint + { + public DateTime Time; + public double Value; + } + + #endregion + + #region [Methods] + + public void Adjust(Ticks ticks) + { + // If the blob contains the GZip header, + // move from Legacy Compression to normal Compression + if (this.Data[0] == 0x1F && this.Data[1] == 0x8B) + { + this.Data = MigrateCompression(this.Data); + } + + // If the blob contains the GZip header, + // move from Legacy Compression to normal Compression + if (this.AngleData[0] == 0x1F && this.AngleData[1] == 0x8B) + { + this.AngleData = MigrateCompression(this.AngleData); + } + + this.Data = ChangeTS(this.Data, ticks); + this.AngleData = ChangeTS(this.AngleData, ticks); + + + } + + private static byte[] ChangeTS(byte[] data, Ticks ticks) + { + data[0] = 0x1F; + data[1] = 0x8B; + + byte[] uncompressedData = GZipStream.UncompressBuffer(data); + byte[] resultData = new byte[uncompressedData.Length]; + + uncompressedData.CopyTo(resultData,0); + + int offset = 0; + + int m_samples = LittleEndian.ToInt32(uncompressedData, offset); + offset += sizeof(int); + + int timeValues = LittleEndian.ToInt32(uncompressedData, offset); + + int startTS = offset; + + offset += sizeof(int); + + long currentValue = LittleEndian.ToInt64(uncompressedData, offset); + + DateTime startTime = new DateTime(currentValue); + startTime = startTime.AddTicks(ticks); + + LittleEndian.CopyBytes(startTime.Ticks, resultData, startTS); + + resultData = GZipStream.CompressBuffer(resultData); + resultData[0] = 0x44; + resultData[1] = 0x33; + return resultData; + } + + private static byte[] MigrateCompression(byte[] data) + { + byte[] uncompressedData; + int offset; + DateTime[] times; + List series; + int seriesID = 0; + + uncompressedData = GZipStream.UncompressBuffer(data); + offset = 0; + + int m_samples = LittleEndian.ToInt32(uncompressedData, offset); + offset += sizeof(int); + + times = new DateTime[m_samples]; + + for (int i = 0; i < m_samples; i++) + { + times[i] = new DateTime(LittleEndian.ToInt64(uncompressedData, offset)); + offset += sizeof(long); + } + + series = new List(); + + while (offset < uncompressedData.Length) + { + + seriesID = LittleEndian.ToInt32(uncompressedData, offset); + offset += sizeof(int); + + + for (int i = 0; i < m_samples; i++) + { + series.Add(new DataPoint() + { + Time = times[i], + Value = LittleEndian.ToDouble(uncompressedData, offset) + }); + + offset += sizeof(double); + } + } + + var timeSeries = series.Select(dataPoint => new { Time = dataPoint.Time.Ticks, Compressed = false }).ToList(); + + for (int i = 1; i < timeSeries.Count; i++) + { + long previousTimestamp = series[i - 1].Time.Ticks; + long timestamp = timeSeries[i].Time; + long diff = timestamp - previousTimestamp; + + if (diff >= 0 && diff <= ushort.MaxValue) + timeSeries[i] = new { Time = diff, Compressed = true }; + + + } + + int timeSeriesByteLength = timeSeries.Sum(obj => obj.Compressed ? sizeof(ushort) : sizeof(int) + sizeof(long)); + int dataSeriesByteLength = sizeof(int) + (2 * sizeof(double)) + (m_samples * sizeof(ushort)); + int totalByteLength = sizeof(int) + timeSeriesByteLength + dataSeriesByteLength; + + + byte[] result = new byte[totalByteLength]; + offset = 0; + + offset += LittleEndian.CopyBytes(m_samples, result, offset); + + List uncompressedIndexes = timeSeries + .Select((obj, Index) => new { obj.Compressed, Index }) + .Where(obj => !obj.Compressed) + .Select(obj => obj.Index) + .ToList(); + + for (int i = 0; i < uncompressedIndexes.Count; i++) + { + int index = uncompressedIndexes[i]; + int nextIndex = (i + 1 < uncompressedIndexes.Count) ? uncompressedIndexes[i + 1] : timeSeries.Count; + + offset += LittleEndian.CopyBytes(nextIndex - index, result, offset); + offset += LittleEndian.CopyBytes(timeSeries[index].Time, result, offset); + + for (int j = index + 1; j < nextIndex; j++) + offset += LittleEndian.CopyBytes((ushort)timeSeries[j].Time, result, offset); + } + + const ushort NaNValue = ushort.MaxValue; + const ushort MaxCompressedValue = ushort.MaxValue - 1; + double range = series.Select(item => item.Value).Max() - series.Select(item => item.Value).Min(); + double decompressionOffset = series.Select(item => item.Value).Min(); + double decompressionScale = range / MaxCompressedValue; + double compressionScale = (decompressionScale != 0.0D) ? 1.0D / decompressionScale : 0.0D; + + offset += LittleEndian.CopyBytes(seriesID, result, offset); + offset += LittleEndian.CopyBytes(decompressionOffset, result, offset); + offset += LittleEndian.CopyBytes(decompressionScale, result, offset); + + foreach (DataPoint dataPoint in series) + { + ushort compressedValue = (ushort)Math.Round((dataPoint.Value - decompressionOffset) * compressionScale); + + if (compressedValue == NaNValue) + compressedValue--; + + if (double.IsNaN(dataPoint.Value)) + compressedValue = NaNValue; + + offset += LittleEndian.CopyBytes(compressedValue, result, offset); + } + + byte[] returnArray = GZipStream.CompressBuffer(result); + returnArray[0] = 0x44; + returnArray[1] = 0x33; + + return returnArray; + + } + #endregion + } + + public class FaultCurveStatistic + { + [PrimaryKey(true)] + public int ID { get; set; } + + public int FaultCurveID { get; set; } + + public int FaultNumber { get; set; } + + public double Maximum { get; set; } + + public double Minimum { get; set; } + + public double Average { get; set; } + + public double StandardDeviation { get; set; } + } +} From 1c80d1a25966fbec8bdc8e84893cc348640a0861 Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Tue, 20 Jan 2026 14:31:08 -0500 Subject: [PATCH 12/58] convert to net core 9 --- src/OpenSEE/Controllers/AnalyticController.cs | 96 +++++++++---------- 1 file changed, 45 insertions(+), 51 deletions(-) diff --git a/src/OpenSEE/Controllers/AnalyticController.cs b/src/OpenSEE/Controllers/AnalyticController.cs index 16f4c1e2..2681fcc0 100644 --- a/src/OpenSEE/Controllers/AnalyticController.cs +++ b/src/OpenSEE/Controllers/AnalyticController.cs @@ -36,6 +36,11 @@ using Microsoft.AspNetCore.Mvc; using Gemstone.Data.Model; using Gemstone.Configuration; +using openXDA.Model; +using FaultData.DataAnalysis; +using Microsoft.Extensions.Primitives; +using Gemstone.Data.DataExtensions; +using Gemstone.Numeric.Analysis; namespace OpenSEE { @@ -413,13 +418,11 @@ public JsonReturn GetFaultDistanceData() { using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { - Dictionary query = Request.QueryParameters(); - - int eventId = int.Parse(query["eventId"]); + int eventId = int.Parse(Request.Query["eventId"].ToString()); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); Asset asset = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.AssetID); - meter.ConnectionFactory = () => new AdoDataConnection(connection.Connection, typeof(SqlDataAdapter), false); + meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); List returnList = new List(); @@ -482,8 +485,7 @@ public async Task GetFirstDerivativeData() { using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { - Dictionary query = Request.QueryParameters(); - int eventId = int.Parse(query["eventId"]); + int eventId = int.Parse(Request.Query["eventId"].ToString()); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); @@ -598,8 +600,7 @@ public async Task GetImpedanceData() { using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { - Dictionary query = Request.QueryParameters(); - int eventId = int.Parse(query["eventId"]); + int eventId = int.Parse(Request.Query["eventId"].ToString()); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); @@ -788,8 +789,7 @@ public async Task GetRemoveCurrentData() { using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { - Dictionary query = Request.QueryParameters(); - int eventId = int.Parse(query["eventId"]); + int eventId = int.Parse(Request.Query["eventId"].ToString()); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); @@ -943,8 +943,7 @@ public async Task GetI2tData() { using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { - Dictionary query = Request.QueryParameters(); - int eventId = int.Parse(query["eventId"]); + int eventId = int.Parse(Request.Query["eventId"].ToString()); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); @@ -1010,8 +1009,7 @@ public async Task GetPowerData() { using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { - Dictionary query = Request.QueryParameters(); - int eventId = int.Parse(query["eventId"]); + int eventId = int.Parse(Request.Query["eventId"].ToString()); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); @@ -1307,8 +1305,7 @@ public async Task GetMissingVoltageData() { using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { - Dictionary query = Request.QueryParameters(); - int eventId = int.Parse(query["eventId"]); + int eventId = int.Parse(Request.Query["eventId"].ToString()); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); @@ -1388,8 +1385,7 @@ public async Task GetClippedWaveformsData() { using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { - Dictionary query = Request.QueryParameters(); - int eventId = int.Parse(query["eventId"]); + int eventId = int.Parse(Request.Query["eventId"].ToString()); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); @@ -1530,9 +1526,8 @@ public async Task GetLowPassFilterData() { using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { - Dictionary query = Request.QueryParameters(); - int filterOrder = int.Parse(query["filter"]); - int eventId = int.Parse(query["eventId"]); + int filterOrder = int.Parse(Request.Query["filter"].ToString()); + int eventId = int.Parse(Request.Query["eventId"].ToString()); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); @@ -1605,9 +1600,8 @@ public async Task GetHighPassFilterData() { using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { - Dictionary query = Request.QueryParameters(); - int filterOrder = int.Parse(query["filter"]); - int eventId = int.Parse(query["eventId"]); + int filterOrder = int.Parse(Request.Query["filter"].ToString()); + int eventId = int.Parse(Request.Query["eventId"].ToString()); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); @@ -1658,8 +1652,7 @@ public async Task GetOverlappingWaveformData() { using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { - Dictionary query = Request.QueryParameters(); - int eventId = int.Parse(query["eventId"]); + int eventId = int.Parse(Request.Query["eventId"].ToString()); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); @@ -1754,8 +1747,7 @@ public async Task GetRapidVoltageChangeData() { using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { - Dictionary query = Request.QueryParameters(); - int eventId = int.Parse(query["eventId"]); + int eventId = int.Parse(Request.Query["eventId"].ToString()); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); @@ -1836,8 +1828,7 @@ public async Task GetSymmetricalComponentsData() { using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { - Dictionary query = Request.QueryParameters(); - int eventId = int.Parse(query["eventId"]); + int eventId = int.Parse(Request.Query["eventId"].ToString()); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); @@ -2021,8 +2012,7 @@ public async Task GetUnbalanceData() { using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { - Dictionary query = Request.QueryParameters(); - int eventId = int.Parse(query["eventId"]); + int eventId = int.Parse(Request.Query["eventId"].ToString()); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); @@ -2172,9 +2162,8 @@ public async Task GetRectifierData() { using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { - Dictionary query = Request.QueryParameters(); - int eventId = int.Parse(query["eventId"]); - double TRC = double.Parse(query["Trc"]); + int eventId = int.Parse(Request.Query["eventId"].ToString()); + double TRC = double.Parse(Request.Query["Trc"].ToString()); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); @@ -2269,8 +2258,7 @@ public async Task GetFrequencyData() { using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { - Dictionary query = Request.QueryParameters(); - int eventId = int.Parse(query["eventId"]); + int eventId = int.Parse(Request.Query["eventId"].ToString()); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); @@ -2438,11 +2426,12 @@ public async Task GetTHDData() { using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { - Dictionary query = Request.QueryParameters(); - int eventId = int.Parse(query["eventId"]); - int forceFullRes = int.Parse(query.ContainsKey("fullRes") ? query["fullRes"] : "0"); + int eventId = int.Parse(Request.Query["eventId"].ToString()); + int forceFullRes = 0; + if (Request.Query.TryGetValue("fullRes", out StringValues fullRes)) + forceFullRes = int.Parse(fullRes.ToString()); - Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); + Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); DataGroup dataGroup = await QueryDataGroupAsync(evt.ID, meter); @@ -2540,13 +2529,14 @@ public async Task GetSpecifiedHarmonicData() { using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { - Dictionary query = Request.QueryParameters(); - int eventId = int.Parse(query["eventId"]); - int forceFullRes = int.Parse(query.ContainsKey("fullRes") ? query["fullRes"] : "0"); + int eventId = int.Parse(Request.Query["eventId"].ToString()); + int forceFullRes = 0; + if (Request.Query.TryGetValue("fullRes", out StringValues fullRes)) + forceFullRes = int.Parse(fullRes.ToString()); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - int specifiedHarmonic = int.Parse(query["specifiedHarmonic"]); + int specifiedHarmonic = int.Parse(Request.Query["specifiedHarmonic"].ToString()); meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); DataGroup dataGroup = await QueryDataGroupAsync(evt.ID, meter); @@ -2665,8 +2655,7 @@ public async Task GetBreakerRestrikeData() { using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { - Dictionary query = Request.QueryParameters(); - int eventId = int.Parse(query["eventId"]); + int eventId = int.Parse(Request.Query["eventId"].ToString()); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); @@ -2766,15 +2755,20 @@ public async Task GetFFTData() { using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { - Dictionary query = Request.QueryParameters(); - int eventId = int.Parse(query["eventId"]); - int cycles = query.ContainsKey("cycles") ? int.Parse(query["cycles"]) : 1; + int eventId = int.Parse(Request.Query["eventId"].ToString()); + int cycles = 1; + if (Request.Query.TryGetValue("cycles", out StringValues cycleString)) + cycles = int.Parse(cycleString.ToString()); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); - double startTime = query.ContainsKey("startDate") ? double.Parse(query["startDate"]) : evt.StartTime.Subtract(m_epoch).TotalMilliseconds; + double startTime; + if (Request.Query.TryGetValue("startDate", out StringValues start)) + startTime = double.Parse(start.ToString()); + else + startTime = evt.StartTime.Subtract(m_epoch).TotalMilliseconds; DataGroup dataGroup = await QueryDataGroupAsync(eventId, meter); List returnList = GetFFTLookup(dataGroup, startTime, cycles); From a01ed95e15f0752dcb217422e4f19726212dfee0 Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Tue, 20 Jan 2026 15:04:24 -0500 Subject: [PATCH 13/58] update to netcore 9 --- src/OpenSEE/Views/Home/Index.cshtml | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/src/OpenSEE/Views/Home/Index.cshtml b/src/OpenSEE/Views/Home/Index.cshtml index 588e3649..527a3f92 100644 --- a/src/OpenSEE/Views/Home/Index.cshtml +++ b/src/OpenSEE/Views/Home/Index.cshtml @@ -24,8 +24,9 @@ // Moved OpenSee out of PQ Dashboard // //*****************************************************************************************************@ -@using GSF.IO; -@using GSF.IO.Checksums; +@using Gemstone.IO; +@using Gemstone.IO.Checksums; +@using System.IO @{ Layout = ""; @@ -33,14 +34,6 @@ Version assemblyVersionInfo = typeof(OpenSEE.MvcApplication).Assembly.GetName().Version; string applicationVersion = assemblyVersionInfo.Major + "." + assemblyVersionInfo.Minor + "." + assemblyVersionInfo.Build; } - -@helper ReferenceScript(string path) -{ - string fullPath = FilePath.GetAbsolutePath(path.Substring(2)); - byte[] bytes = File.ReadAllBytes(fullPath); - uint checksum = Crc32.Compute(bytes, 0, bytes.Length); - -} @@ -92,11 +85,6 @@ var analyticHandle = null; var version = '@applicationVersion'; - @if (Request.Browser.Browser == "InternetExplorer" || Request.Browser.Browser == "IE") - { - @**@ - - } From 69eda93b5cb2609f07bf5cdb5b6cb3baea88c949 Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Tue, 20 Jan 2026 15:04:40 -0500 Subject: [PATCH 14/58] cleanedup imports --- src/OpenSEE/Model/D3Series.cs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/OpenSEE/Model/D3Series.cs b/src/OpenSEE/Model/D3Series.cs index 638c52c4..39e651e1 100644 --- a/src/OpenSEE/Model/D3Series.cs +++ b/src/OpenSEE/Model/D3Series.cs @@ -21,18 +21,7 @@ // //****************************************************************************************************** - -using Gemstone; -using Gemstone.Data.Model; -using Gemstone.Web; -using Gemstone.Web.Model; -using System; -using System.Web; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; - namespace OpenSEE.Model { From 13335086e2d9945553a9fb273df979ce2c89ff9c Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Tue, 20 Jan 2026 15:29:43 -0500 Subject: [PATCH 15/58] removing because security will need to be redone and this relies on GSF only items --- src/OpenSEE/Views/Login/UserInfo.cshtml | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 src/OpenSEE/Views/Login/UserInfo.cshtml diff --git a/src/OpenSEE/Views/Login/UserInfo.cshtml b/src/OpenSEE/Views/Login/UserInfo.cshtml deleted file mode 100644 index 812f5b45..00000000 --- a/src/OpenSEE/Views/Login/UserInfo.cshtml +++ /dev/null @@ -1,5 +0,0 @@ -@using GSF.Web -@{ - Layout = null; -} -@Html.RenderResource("GSF.Web.Shared.Views.UserInfo.cshtml") \ No newline at end of file From 3ca4cd61fa29ceb156df38b9bf38873f742829c2 Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Wed, 28 Jan 2026 08:52:43 -0500 Subject: [PATCH 16/58] migrated web components to wwwroot for easier setup in startup due to aspnetcore migration --- .gitignore | 2 +- src/OpenSEE/OpenSEE.csproj | 118 +++++++++--------- src/OpenSEE/tsconfig.json | 2 +- src/OpenSEE/webpack.config.js | 26 ++-- .../{ => wwwroot}/Content/FaultSpecifics.css | 0 .../Content/bootstrap4-datetimepicker.css | 0 .../{ => wwwroot}/Images/2-Line - 500.png | Bin .../openSEE - Waveform Viewer Header.png | Bin src/OpenSEE/{ => wwwroot}/Images/openSEE.jpg | Bin .../{ => wwwroot}/Images/openSEELogo.png | Bin .../Scripts/TSX/Components/About.tsx | 0 .../TSX/Components/AnalyticOptions.tsx | 0 .../Scripts/TSX/Components/Menu.tsx | 0 .../Scripts/TSX/Components/OpenSEENavbar.tsx | 0 .../TSX/Components/OverlappingEvents.tsx | 0 .../Scripts/TSX/Context/HoverContext.tsx | 0 .../Scripts/TSX/Context/HoverProvider.tsx | 0 .../Scripts/TSX/Graphs/BarChartBase.tsx | 0 .../Scripts/TSX/Graphs/ChartIcons.tsx | 0 .../Scripts/TSX/Graphs/LegendBase.tsx | 0 .../Scripts/TSX/Graphs/LineChartBase.tsx | 0 .../Scripts/TSX/Graphs/Utilities.tsx | 0 .../{ => wwwroot}/Scripts/TSX/defaults.ts | 0 .../{ => wwwroot}/Scripts/TSX/global.d.ts | 1 - .../{ => wwwroot}/Scripts/TSX/hooks.ts | 0 .../jQueryUI Widgets/AccumulatedPoints.tsx | 0 .../TSX/jQueryUI Widgets/EventInfo.tsx | 0 .../Scripts/TSX/jQueryUI Widgets/FFTTable.tsx | 0 .../TSX/jQueryUI Widgets/HarmonicStats.tsx | 0 .../TSX/jQueryUI Widgets/LightningData.tsx | 0 .../TSX/jQueryUI Widgets/PhasorChart.tsx | 0 .../TSX/jQueryUI Widgets/ScalarStats.tsx | 0 .../TSX/jQueryUI Widgets/SettingWindow.tsx | 0 .../jQueryUI Widgets/TimeCorrelatedSags.tsx | 0 .../Scripts/TSX/jQueryUI Widgets/Tooltip.tsx | 0 .../TSX/jQueryUI Widgets/TooltipWithDelta.tsx | 0 .../{ => wwwroot}/Scripts/TSX/openSEE.tsx | 0 .../Scripts/TSX/store/GraphLogic.tsx | 0 .../Scripts/TSX/store/RequestHandler.tsx | 0 .../Scripts/TSX/store/analyticSlice.tsx | 0 .../Scripts/TSX/store/dataSlice.tsx | 0 .../Scripts/TSX/store/eventInfoSlice.tsx | 0 .../TSX/store/overlappingEventsSlice.tsx | 0 .../Scripts/TSX/store/queryThunk.tsx | 0 .../Scripts/TSX/store/settingSlice.tsx | 0 .../{ => wwwroot}/Scripts/TSX/store/store.tsx | 0 46 files changed, 73 insertions(+), 76 deletions(-) rename src/OpenSEE/{ => wwwroot}/Content/FaultSpecifics.css (100%) rename src/OpenSEE/{ => wwwroot}/Content/bootstrap4-datetimepicker.css (100%) rename src/OpenSEE/{ => wwwroot}/Images/2-Line - 500.png (100%) rename src/OpenSEE/{ => wwwroot}/Images/openSEE - Waveform Viewer Header.png (100%) rename src/OpenSEE/{ => wwwroot}/Images/openSEE.jpg (100%) rename src/OpenSEE/{ => wwwroot}/Images/openSEELogo.png (100%) rename src/OpenSEE/{ => wwwroot}/Scripts/TSX/Components/About.tsx (100%) rename src/OpenSEE/{ => wwwroot}/Scripts/TSX/Components/AnalyticOptions.tsx (100%) rename src/OpenSEE/{ => wwwroot}/Scripts/TSX/Components/Menu.tsx (100%) rename src/OpenSEE/{ => wwwroot}/Scripts/TSX/Components/OpenSEENavbar.tsx (100%) rename src/OpenSEE/{ => wwwroot}/Scripts/TSX/Components/OverlappingEvents.tsx (100%) rename src/OpenSEE/{ => wwwroot}/Scripts/TSX/Context/HoverContext.tsx (100%) rename src/OpenSEE/{ => wwwroot}/Scripts/TSX/Context/HoverProvider.tsx (100%) rename src/OpenSEE/{ => wwwroot}/Scripts/TSX/Graphs/BarChartBase.tsx (100%) rename src/OpenSEE/{ => wwwroot}/Scripts/TSX/Graphs/ChartIcons.tsx (100%) rename src/OpenSEE/{ => wwwroot}/Scripts/TSX/Graphs/LegendBase.tsx (100%) rename src/OpenSEE/{ => wwwroot}/Scripts/TSX/Graphs/LineChartBase.tsx (100%) rename src/OpenSEE/{ => wwwroot}/Scripts/TSX/Graphs/Utilities.tsx (100%) rename src/OpenSEE/{ => wwwroot}/Scripts/TSX/defaults.ts (100%) rename src/OpenSEE/{ => wwwroot}/Scripts/TSX/global.d.ts (99%) rename src/OpenSEE/{ => wwwroot}/Scripts/TSX/hooks.ts (100%) rename src/OpenSEE/{ => wwwroot}/Scripts/TSX/jQueryUI Widgets/AccumulatedPoints.tsx (100%) rename src/OpenSEE/{ => wwwroot}/Scripts/TSX/jQueryUI Widgets/EventInfo.tsx (100%) rename src/OpenSEE/{ => wwwroot}/Scripts/TSX/jQueryUI Widgets/FFTTable.tsx (100%) rename src/OpenSEE/{ => wwwroot}/Scripts/TSX/jQueryUI Widgets/HarmonicStats.tsx (100%) rename src/OpenSEE/{ => wwwroot}/Scripts/TSX/jQueryUI Widgets/LightningData.tsx (100%) rename src/OpenSEE/{ => wwwroot}/Scripts/TSX/jQueryUI Widgets/PhasorChart.tsx (100%) rename src/OpenSEE/{ => wwwroot}/Scripts/TSX/jQueryUI Widgets/ScalarStats.tsx (100%) rename src/OpenSEE/{ => wwwroot}/Scripts/TSX/jQueryUI Widgets/SettingWindow.tsx (100%) rename src/OpenSEE/{ => wwwroot}/Scripts/TSX/jQueryUI Widgets/TimeCorrelatedSags.tsx (100%) rename src/OpenSEE/{ => wwwroot}/Scripts/TSX/jQueryUI Widgets/Tooltip.tsx (100%) rename src/OpenSEE/{ => wwwroot}/Scripts/TSX/jQueryUI Widgets/TooltipWithDelta.tsx (100%) rename src/OpenSEE/{ => wwwroot}/Scripts/TSX/openSEE.tsx (100%) rename src/OpenSEE/{ => wwwroot}/Scripts/TSX/store/GraphLogic.tsx (100%) rename src/OpenSEE/{ => wwwroot}/Scripts/TSX/store/RequestHandler.tsx (100%) rename src/OpenSEE/{ => wwwroot}/Scripts/TSX/store/analyticSlice.tsx (100%) rename src/OpenSEE/{ => wwwroot}/Scripts/TSX/store/dataSlice.tsx (100%) rename src/OpenSEE/{ => wwwroot}/Scripts/TSX/store/eventInfoSlice.tsx (100%) rename src/OpenSEE/{ => wwwroot}/Scripts/TSX/store/overlappingEventsSlice.tsx (100%) rename src/OpenSEE/{ => wwwroot}/Scripts/TSX/store/queryThunk.tsx (100%) rename src/OpenSEE/{ => wwwroot}/Scripts/TSX/store/settingSlice.tsx (100%) rename src/OpenSEE/{ => wwwroot}/Scripts/TSX/store/store.tsx (100%) diff --git a/.gitignore b/.gitignore index 03900605..9416d3cc 100644 --- a/.gitignore +++ b/.gitignore @@ -233,4 +233,4 @@ readme.txt **/src/OpenSEE/NUglify.dll **/src/OpenSEE/web.config.backup -**/src/OpenSee/scripts/*.js \ No newline at end of file +**/src/OpenSee/wwwroot/scripts/*.js \ No newline at end of file diff --git a/src/OpenSEE/OpenSEE.csproj b/src/OpenSEE/OpenSEE.csproj index 958caefd..037c8770 100644 --- a/src/OpenSEE/OpenSEE.csproj +++ b/src/OpenSEE/OpenSEE.csproj @@ -12,17 +12,16 @@ if $(ConfigurationName) == Release npm run buildrelease bin\ - - + + + + + + + - - - - - - @@ -50,6 +49,53 @@ if $(ConfigurationName) == Release npm run buildrelease + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -62,63 +108,15 @@ if $(ConfigurationName) == Release npm run buildrelease ..\Dependencies\NuGet\WebGrease.1.5.2\lib\WebGrease.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + \ No newline at end of file diff --git a/src/OpenSEE/tsconfig.json b/src/OpenSEE/tsconfig.json index ce886a01..de72e82a 100644 --- a/src/OpenSEE/tsconfig.json +++ b/src/OpenSEE/tsconfig.json @@ -17,5 +17,5 @@ "exclude": [ "node_modules" ], - "include": [ "Scripts/TSX", "Scripts/TS/*" ] + "include": [ "wwwroot/Scripts/TSX", "wwwroot/Scripts/TS/*" ] } diff --git a/src/OpenSEE/webpack.config.js b/src/OpenSEE/webpack.config.js index 8bcba5c1..dc98cd63 100644 --- a/src/OpenSEE/webpack.config.js +++ b/src/OpenSEE/webpack.config.js @@ -12,20 +12,20 @@ function buildConfig(env, argv) { context: path.resolve(__dirname), cache: true, entry: { - OpenSee: "./Scripts/TSX/OpenSee.tsx", - ToolTipDeltaWidget: "./Scripts/TSX/jQueryUI Widgets/TooltipWithDelta.tsx", - ToolTipWidget: "./Scripts/TSX/jQueryUI Widgets/Tooltip.tsx", - TimeCorrelatedSagsWidget: "./Scripts/TSX/jQueryUI Widgets/TimeCorrelatedSags.tsx", - PointWidget: "./Scripts/TSX/jQueryUI Widgets/AccumulatedPoints.tsx", - PhasorChartWidget: "./Scripts/TSX/jQueryUI Widgets/PhasorChart.tsx", - ScalarStatsWidget: "./Scripts/TSX/jQueryUI Widgets/ScalarStats.tsx", - LightningDataWidget: "./Scripts/TSX/jQueryUI Widgets/LightningData.tsx", - SettingsWidget: "./Scripts/TSX/jQueryUI Widgets/SettingWindow.tsx", - FFTTable: "./Scripts/TSX/jQueryUI Widgets/FFTTable.tsx", - HarmonicStatsWidget: "./Scripts/TSX/jQueryUI Widgets/HarmonicStats.tsx", + OpenSee: "./wwwroot/Scripts/TSX/OpenSee.tsx", + ToolTipDeltaWidget: "./wwwroot/Scripts/TSX/jQueryUI Widgets/TooltipWithDelta.tsx", + ToolTipWidget: "./wwwroot/Scripts/TSX/jQueryUI Widgets/Tooltip.tsx", + TimeCorrelatedSagsWidget: "./wwwroot/Scripts/TSX/jQueryUI Widgets/TimeCorrelatedSags.tsx", + PointWidget: "./wwwroot/Scripts/TSX/jQueryUI Widgets/AccumulatedPoints.tsx", + PhasorChartWidget: "./wwwroot/Scripts/TSX/jQueryUI Widgets/PhasorChart.tsx", + ScalarStatsWidget: "./wwwroot/Scripts/TSX/jQueryUI Widgets/ScalarStats.tsx", + LightningDataWidget: "./wwwroot/Scripts/TSX/jQueryUI Widgets/LightningData.tsx", + SettingsWidget: "./wwwroot/Scripts/TSX/jQueryUI Widgets/SettingWindow.tsx", + FFTTable: "./wwwroot/Scripts/TSX/jQueryUI Widgets/FFTTable.tsx", + HarmonicStatsWidget: "./wwwroot/Scripts/TSX/jQueryUI Widgets/HarmonicStats.tsx", }, output: { - path: path.resolve(__dirname, 'Scripts'), + path: path.resolve(__dirname, './wwwroot/Scripts'), filename: "[name].js", }, // Enable sourcemaps for debugging webpack's output. @@ -39,7 +39,7 @@ function buildConfig(env, argv) { // All files with a '.ts' or '.tsx' extension will be handled by 'ts-loader'. { test: /\.tsx?$/, - include: path.resolve(__dirname, "Scripts"), + include: path.resolve(__dirname, 'wwwroot', "Scripts"), loader: "ts-loader", options: { transpileOnly: true } }, { diff --git a/src/OpenSEE/Content/FaultSpecifics.css b/src/OpenSEE/wwwroot/Content/FaultSpecifics.css similarity index 100% rename from src/OpenSEE/Content/FaultSpecifics.css rename to src/OpenSEE/wwwroot/Content/FaultSpecifics.css diff --git a/src/OpenSEE/Content/bootstrap4-datetimepicker.css b/src/OpenSEE/wwwroot/Content/bootstrap4-datetimepicker.css similarity index 100% rename from src/OpenSEE/Content/bootstrap4-datetimepicker.css rename to src/OpenSEE/wwwroot/Content/bootstrap4-datetimepicker.css diff --git a/src/OpenSEE/Images/2-Line - 500.png b/src/OpenSEE/wwwroot/Images/2-Line - 500.png similarity index 100% rename from src/OpenSEE/Images/2-Line - 500.png rename to src/OpenSEE/wwwroot/Images/2-Line - 500.png diff --git a/src/OpenSEE/Images/openSEE - Waveform Viewer Header.png b/src/OpenSEE/wwwroot/Images/openSEE - Waveform Viewer Header.png similarity index 100% rename from src/OpenSEE/Images/openSEE - Waveform Viewer Header.png rename to src/OpenSEE/wwwroot/Images/openSEE - Waveform Viewer Header.png diff --git a/src/OpenSEE/Images/openSEE.jpg b/src/OpenSEE/wwwroot/Images/openSEE.jpg similarity index 100% rename from src/OpenSEE/Images/openSEE.jpg rename to src/OpenSEE/wwwroot/Images/openSEE.jpg diff --git a/src/OpenSEE/Images/openSEELogo.png b/src/OpenSEE/wwwroot/Images/openSEELogo.png similarity index 100% rename from src/OpenSEE/Images/openSEELogo.png rename to src/OpenSEE/wwwroot/Images/openSEELogo.png diff --git a/src/OpenSEE/Scripts/TSX/Components/About.tsx b/src/OpenSEE/wwwroot/Scripts/TSX/Components/About.tsx similarity index 100% rename from src/OpenSEE/Scripts/TSX/Components/About.tsx rename to src/OpenSEE/wwwroot/Scripts/TSX/Components/About.tsx diff --git a/src/OpenSEE/Scripts/TSX/Components/AnalyticOptions.tsx b/src/OpenSEE/wwwroot/Scripts/TSX/Components/AnalyticOptions.tsx similarity index 100% rename from src/OpenSEE/Scripts/TSX/Components/AnalyticOptions.tsx rename to src/OpenSEE/wwwroot/Scripts/TSX/Components/AnalyticOptions.tsx diff --git a/src/OpenSEE/Scripts/TSX/Components/Menu.tsx b/src/OpenSEE/wwwroot/Scripts/TSX/Components/Menu.tsx similarity index 100% rename from src/OpenSEE/Scripts/TSX/Components/Menu.tsx rename to src/OpenSEE/wwwroot/Scripts/TSX/Components/Menu.tsx diff --git a/src/OpenSEE/Scripts/TSX/Components/OpenSEENavbar.tsx b/src/OpenSEE/wwwroot/Scripts/TSX/Components/OpenSEENavbar.tsx similarity index 100% rename from src/OpenSEE/Scripts/TSX/Components/OpenSEENavbar.tsx rename to src/OpenSEE/wwwroot/Scripts/TSX/Components/OpenSEENavbar.tsx diff --git a/src/OpenSEE/Scripts/TSX/Components/OverlappingEvents.tsx b/src/OpenSEE/wwwroot/Scripts/TSX/Components/OverlappingEvents.tsx similarity index 100% rename from src/OpenSEE/Scripts/TSX/Components/OverlappingEvents.tsx rename to src/OpenSEE/wwwroot/Scripts/TSX/Components/OverlappingEvents.tsx diff --git a/src/OpenSEE/Scripts/TSX/Context/HoverContext.tsx b/src/OpenSEE/wwwroot/Scripts/TSX/Context/HoverContext.tsx similarity index 100% rename from src/OpenSEE/Scripts/TSX/Context/HoverContext.tsx rename to src/OpenSEE/wwwroot/Scripts/TSX/Context/HoverContext.tsx diff --git a/src/OpenSEE/Scripts/TSX/Context/HoverProvider.tsx b/src/OpenSEE/wwwroot/Scripts/TSX/Context/HoverProvider.tsx similarity index 100% rename from src/OpenSEE/Scripts/TSX/Context/HoverProvider.tsx rename to src/OpenSEE/wwwroot/Scripts/TSX/Context/HoverProvider.tsx diff --git a/src/OpenSEE/Scripts/TSX/Graphs/BarChartBase.tsx b/src/OpenSEE/wwwroot/Scripts/TSX/Graphs/BarChartBase.tsx similarity index 100% rename from src/OpenSEE/Scripts/TSX/Graphs/BarChartBase.tsx rename to src/OpenSEE/wwwroot/Scripts/TSX/Graphs/BarChartBase.tsx diff --git a/src/OpenSEE/Scripts/TSX/Graphs/ChartIcons.tsx b/src/OpenSEE/wwwroot/Scripts/TSX/Graphs/ChartIcons.tsx similarity index 100% rename from src/OpenSEE/Scripts/TSX/Graphs/ChartIcons.tsx rename to src/OpenSEE/wwwroot/Scripts/TSX/Graphs/ChartIcons.tsx diff --git a/src/OpenSEE/Scripts/TSX/Graphs/LegendBase.tsx b/src/OpenSEE/wwwroot/Scripts/TSX/Graphs/LegendBase.tsx similarity index 100% rename from src/OpenSEE/Scripts/TSX/Graphs/LegendBase.tsx rename to src/OpenSEE/wwwroot/Scripts/TSX/Graphs/LegendBase.tsx diff --git a/src/OpenSEE/Scripts/TSX/Graphs/LineChartBase.tsx b/src/OpenSEE/wwwroot/Scripts/TSX/Graphs/LineChartBase.tsx similarity index 100% rename from src/OpenSEE/Scripts/TSX/Graphs/LineChartBase.tsx rename to src/OpenSEE/wwwroot/Scripts/TSX/Graphs/LineChartBase.tsx diff --git a/src/OpenSEE/Scripts/TSX/Graphs/Utilities.tsx b/src/OpenSEE/wwwroot/Scripts/TSX/Graphs/Utilities.tsx similarity index 100% rename from src/OpenSEE/Scripts/TSX/Graphs/Utilities.tsx rename to src/OpenSEE/wwwroot/Scripts/TSX/Graphs/Utilities.tsx diff --git a/src/OpenSEE/Scripts/TSX/defaults.ts b/src/OpenSEE/wwwroot/Scripts/TSX/defaults.ts similarity index 100% rename from src/OpenSEE/Scripts/TSX/defaults.ts rename to src/OpenSEE/wwwroot/Scripts/TSX/defaults.ts diff --git a/src/OpenSEE/Scripts/TSX/global.d.ts b/src/OpenSEE/wwwroot/Scripts/TSX/global.d.ts similarity index 99% rename from src/OpenSEE/Scripts/TSX/global.d.ts rename to src/OpenSEE/wwwroot/Scripts/TSX/global.d.ts index 7c1697b3..3906e91b 100644 --- a/src/OpenSEE/Scripts/TSX/global.d.ts +++ b/src/OpenSEE/wwwroot/Scripts/TSX/global.d.ts @@ -25,7 +25,6 @@ // global variables declared in openSEE.cshtml scripts section declare global { var homePath: string; - var userIsAdmin: boolean; var eventID: number; var eventStartTime: string; var eventEndTime: string; diff --git a/src/OpenSEE/Scripts/TSX/hooks.ts b/src/OpenSEE/wwwroot/Scripts/TSX/hooks.ts similarity index 100% rename from src/OpenSEE/Scripts/TSX/hooks.ts rename to src/OpenSEE/wwwroot/Scripts/TSX/hooks.ts diff --git a/src/OpenSEE/Scripts/TSX/jQueryUI Widgets/AccumulatedPoints.tsx b/src/OpenSEE/wwwroot/Scripts/TSX/jQueryUI Widgets/AccumulatedPoints.tsx similarity index 100% rename from src/OpenSEE/Scripts/TSX/jQueryUI Widgets/AccumulatedPoints.tsx rename to src/OpenSEE/wwwroot/Scripts/TSX/jQueryUI Widgets/AccumulatedPoints.tsx diff --git a/src/OpenSEE/Scripts/TSX/jQueryUI Widgets/EventInfo.tsx b/src/OpenSEE/wwwroot/Scripts/TSX/jQueryUI Widgets/EventInfo.tsx similarity index 100% rename from src/OpenSEE/Scripts/TSX/jQueryUI Widgets/EventInfo.tsx rename to src/OpenSEE/wwwroot/Scripts/TSX/jQueryUI Widgets/EventInfo.tsx diff --git a/src/OpenSEE/Scripts/TSX/jQueryUI Widgets/FFTTable.tsx b/src/OpenSEE/wwwroot/Scripts/TSX/jQueryUI Widgets/FFTTable.tsx similarity index 100% rename from src/OpenSEE/Scripts/TSX/jQueryUI Widgets/FFTTable.tsx rename to src/OpenSEE/wwwroot/Scripts/TSX/jQueryUI Widgets/FFTTable.tsx diff --git a/src/OpenSEE/Scripts/TSX/jQueryUI Widgets/HarmonicStats.tsx b/src/OpenSEE/wwwroot/Scripts/TSX/jQueryUI Widgets/HarmonicStats.tsx similarity index 100% rename from src/OpenSEE/Scripts/TSX/jQueryUI Widgets/HarmonicStats.tsx rename to src/OpenSEE/wwwroot/Scripts/TSX/jQueryUI Widgets/HarmonicStats.tsx diff --git a/src/OpenSEE/Scripts/TSX/jQueryUI Widgets/LightningData.tsx b/src/OpenSEE/wwwroot/Scripts/TSX/jQueryUI Widgets/LightningData.tsx similarity index 100% rename from src/OpenSEE/Scripts/TSX/jQueryUI Widgets/LightningData.tsx rename to src/OpenSEE/wwwroot/Scripts/TSX/jQueryUI Widgets/LightningData.tsx diff --git a/src/OpenSEE/Scripts/TSX/jQueryUI Widgets/PhasorChart.tsx b/src/OpenSEE/wwwroot/Scripts/TSX/jQueryUI Widgets/PhasorChart.tsx similarity index 100% rename from src/OpenSEE/Scripts/TSX/jQueryUI Widgets/PhasorChart.tsx rename to src/OpenSEE/wwwroot/Scripts/TSX/jQueryUI Widgets/PhasorChart.tsx diff --git a/src/OpenSEE/Scripts/TSX/jQueryUI Widgets/ScalarStats.tsx b/src/OpenSEE/wwwroot/Scripts/TSX/jQueryUI Widgets/ScalarStats.tsx similarity index 100% rename from src/OpenSEE/Scripts/TSX/jQueryUI Widgets/ScalarStats.tsx rename to src/OpenSEE/wwwroot/Scripts/TSX/jQueryUI Widgets/ScalarStats.tsx diff --git a/src/OpenSEE/Scripts/TSX/jQueryUI Widgets/SettingWindow.tsx b/src/OpenSEE/wwwroot/Scripts/TSX/jQueryUI Widgets/SettingWindow.tsx similarity index 100% rename from src/OpenSEE/Scripts/TSX/jQueryUI Widgets/SettingWindow.tsx rename to src/OpenSEE/wwwroot/Scripts/TSX/jQueryUI Widgets/SettingWindow.tsx diff --git a/src/OpenSEE/Scripts/TSX/jQueryUI Widgets/TimeCorrelatedSags.tsx b/src/OpenSEE/wwwroot/Scripts/TSX/jQueryUI Widgets/TimeCorrelatedSags.tsx similarity index 100% rename from src/OpenSEE/Scripts/TSX/jQueryUI Widgets/TimeCorrelatedSags.tsx rename to src/OpenSEE/wwwroot/Scripts/TSX/jQueryUI Widgets/TimeCorrelatedSags.tsx diff --git a/src/OpenSEE/Scripts/TSX/jQueryUI Widgets/Tooltip.tsx b/src/OpenSEE/wwwroot/Scripts/TSX/jQueryUI Widgets/Tooltip.tsx similarity index 100% rename from src/OpenSEE/Scripts/TSX/jQueryUI Widgets/Tooltip.tsx rename to src/OpenSEE/wwwroot/Scripts/TSX/jQueryUI Widgets/Tooltip.tsx diff --git a/src/OpenSEE/Scripts/TSX/jQueryUI Widgets/TooltipWithDelta.tsx b/src/OpenSEE/wwwroot/Scripts/TSX/jQueryUI Widgets/TooltipWithDelta.tsx similarity index 100% rename from src/OpenSEE/Scripts/TSX/jQueryUI Widgets/TooltipWithDelta.tsx rename to src/OpenSEE/wwwroot/Scripts/TSX/jQueryUI Widgets/TooltipWithDelta.tsx diff --git a/src/OpenSEE/Scripts/TSX/openSEE.tsx b/src/OpenSEE/wwwroot/Scripts/TSX/openSEE.tsx similarity index 100% rename from src/OpenSEE/Scripts/TSX/openSEE.tsx rename to src/OpenSEE/wwwroot/Scripts/TSX/openSEE.tsx diff --git a/src/OpenSEE/Scripts/TSX/store/GraphLogic.tsx b/src/OpenSEE/wwwroot/Scripts/TSX/store/GraphLogic.tsx similarity index 100% rename from src/OpenSEE/Scripts/TSX/store/GraphLogic.tsx rename to src/OpenSEE/wwwroot/Scripts/TSX/store/GraphLogic.tsx diff --git a/src/OpenSEE/Scripts/TSX/store/RequestHandler.tsx b/src/OpenSEE/wwwroot/Scripts/TSX/store/RequestHandler.tsx similarity index 100% rename from src/OpenSEE/Scripts/TSX/store/RequestHandler.tsx rename to src/OpenSEE/wwwroot/Scripts/TSX/store/RequestHandler.tsx diff --git a/src/OpenSEE/Scripts/TSX/store/analyticSlice.tsx b/src/OpenSEE/wwwroot/Scripts/TSX/store/analyticSlice.tsx similarity index 100% rename from src/OpenSEE/Scripts/TSX/store/analyticSlice.tsx rename to src/OpenSEE/wwwroot/Scripts/TSX/store/analyticSlice.tsx diff --git a/src/OpenSEE/Scripts/TSX/store/dataSlice.tsx b/src/OpenSEE/wwwroot/Scripts/TSX/store/dataSlice.tsx similarity index 100% rename from src/OpenSEE/Scripts/TSX/store/dataSlice.tsx rename to src/OpenSEE/wwwroot/Scripts/TSX/store/dataSlice.tsx diff --git a/src/OpenSEE/Scripts/TSX/store/eventInfoSlice.tsx b/src/OpenSEE/wwwroot/Scripts/TSX/store/eventInfoSlice.tsx similarity index 100% rename from src/OpenSEE/Scripts/TSX/store/eventInfoSlice.tsx rename to src/OpenSEE/wwwroot/Scripts/TSX/store/eventInfoSlice.tsx diff --git a/src/OpenSEE/Scripts/TSX/store/overlappingEventsSlice.tsx b/src/OpenSEE/wwwroot/Scripts/TSX/store/overlappingEventsSlice.tsx similarity index 100% rename from src/OpenSEE/Scripts/TSX/store/overlappingEventsSlice.tsx rename to src/OpenSEE/wwwroot/Scripts/TSX/store/overlappingEventsSlice.tsx diff --git a/src/OpenSEE/Scripts/TSX/store/queryThunk.tsx b/src/OpenSEE/wwwroot/Scripts/TSX/store/queryThunk.tsx similarity index 100% rename from src/OpenSEE/Scripts/TSX/store/queryThunk.tsx rename to src/OpenSEE/wwwroot/Scripts/TSX/store/queryThunk.tsx diff --git a/src/OpenSEE/Scripts/TSX/store/settingSlice.tsx b/src/OpenSEE/wwwroot/Scripts/TSX/store/settingSlice.tsx similarity index 100% rename from src/OpenSEE/Scripts/TSX/store/settingSlice.tsx rename to src/OpenSEE/wwwroot/Scripts/TSX/store/settingSlice.tsx diff --git a/src/OpenSEE/Scripts/TSX/store/store.tsx b/src/OpenSEE/wwwroot/Scripts/TSX/store/store.tsx similarity index 100% rename from src/OpenSEE/Scripts/TSX/store/store.tsx rename to src/OpenSEE/wwwroot/Scripts/TSX/store/store.tsx From 54a67c4ab08b0fc0f8d08913f3f88977ceeb752f Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Wed, 28 Jan 2026 08:53:15 -0500 Subject: [PATCH 17/58] change to use gemstone class for reflection --- src/OpenSEE/Views/Home/Index.cshtml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/OpenSEE/Views/Home/Index.cshtml b/src/OpenSEE/Views/Home/Index.cshtml index 527a3f92..01d609d6 100644 --- a/src/OpenSEE/Views/Home/Index.cshtml +++ b/src/OpenSEE/Views/Home/Index.cshtml @@ -27,11 +27,12 @@ @using Gemstone.IO; @using Gemstone.IO.Checksums; @using System.IO +@using Gemstone.Reflection @{ Layout = ""; - Version assemblyVersionInfo = typeof(OpenSEE.MvcApplication).Assembly.GetName().Version; + Version assemblyVersionInfo = AssemblyInfo.EntryAssembly.Version; string applicationVersion = assemblyVersionInfo.Major + "." + assemblyVersionInfo.Minor + "." + assemblyVersionInfo.Build; } From c2bb405759539638095725bc24aa92802962b617 Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Wed, 28 Jan 2026 08:56:27 -0500 Subject: [PATCH 18/58] replace auth with tempauth --- src/OpenSEE/TempAuth.cs | 30 ++++++ src/OpenSEE/Views/Login/AuthTest.cshtml | 14 --- src/OpenSEE/Views/Login/Index.cshtml | 6 -- src/OpenSEE/Views/Login/Logout.cshtml | 129 ------------------------ 4 files changed, 30 insertions(+), 149 deletions(-) create mode 100644 src/OpenSEE/TempAuth.cs delete mode 100644 src/OpenSEE/Views/Login/AuthTest.cshtml delete mode 100644 src/OpenSEE/Views/Login/Index.cshtml delete mode 100644 src/OpenSEE/Views/Login/Logout.cshtml diff --git a/src/OpenSEE/TempAuth.cs b/src/OpenSEE/TempAuth.cs new file mode 100644 index 00000000..f33c224d --- /dev/null +++ b/src/OpenSEE/TempAuth.cs @@ -0,0 +1,30 @@ +using System; +using System.Security.Principal; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace OpenSEE.Security +{ + public class TestAuthHandlerOptions : AuthenticationSchemeOptions { } + + public class TestAuthHandler : AuthenticationHandler + { + public const string AuthenticationScheme = "Test"; + + public TestAuthHandler( IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder) : base(options, logger, encoder) { } + + protected override Task HandleAuthenticateAsync() + { + var identity = new GenericIdentity(AuthenticationScheme); + var principal = new GenericPrincipal(identity, ["Administrator"]); + var ticket = new AuthenticationTicket(principal, AuthenticationScheme); + + var result = AuthenticateResult.Success(ticket); + + return Task.FromResult(result); + } + } +} diff --git a/src/OpenSEE/Views/Login/AuthTest.cshtml b/src/OpenSEE/Views/Login/AuthTest.cshtml deleted file mode 100644 index 12280676..00000000 --- a/src/OpenSEE/Views/Login/AuthTest.cshtml +++ /dev/null @@ -1,14 +0,0 @@ -@{ - Layout = null; - string statusCode = $"{(HttpStatusCode)Response.StatusCode}"; -} - - - Authentication Test - - - - Auth test = @Response.StatusCode (@statusCode) for user - @(User?.Identity.Name ?? "Undefined") - - diff --git a/src/OpenSEE/Views/Login/Index.cshtml b/src/OpenSEE/Views/Login/Index.cshtml deleted file mode 100644 index 349ff700..00000000 --- a/src/OpenSEE/Views/Login/Index.cshtml +++ /dev/null @@ -1,6 +0,0 @@ -@using GSF.Web -@{ - Layout = null; - ViewBag.AuthenticationOptions = Startup.AuthenticationOptions; -} -@Html.RenderResource("GSF.Web.Security.Views.Login.cshtml") \ No newline at end of file diff --git a/src/OpenSEE/Views/Login/Logout.cshtml b/src/OpenSEE/Views/Login/Logout.cshtml deleted file mode 100644 index dace6a76..00000000 --- a/src/OpenSEE/Views/Login/Logout.cshtml +++ /dev/null @@ -1,129 +0,0 @@ -@******************************************************************************************************* -// Logout.cshtml - Gbtc -// -// Copyright © 2017, 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://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: -// ---------------------------------------------------------------------------------------------------- -// 12/04/2017 - J. Ritchie Carroll -// Generated original version of source code. -// -//*****************************************************************************************************@ -@using GSF.Web -@using GSF.Web.Security -@using GSF.Web.Shared -@{ - Layout = null; - ReadonlyAuthenticationOptions options = Startup.AuthenticationOptions; - ViewBag.AuthenticationOptions = options; - string loginPage = Url.Content("~" + AuthenticationOptions.DefaultLoginPage); - - if (!(options is null)) { - if (!string.IsNullOrWhiteSpace(options.LoginPage)) { - loginPage = Url.Content(options.LoginPage); - } - } -} - - - - - - - Logout - - - - @Html.Raw(Resources.HeaderIcons) - - - - -


-

- Logging out...   -

- Click here if page does not redirect -

- - - - - - - - From 8464c7be89c0390a7b51257e185dc4f5c144d907 Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Wed, 28 Jan 2026 08:57:23 -0500 Subject: [PATCH 19/58] changes to migrate app startup to aspnetcore --- src/OpenSEE/Program.cs | 137 +++++++++++++++++++++++++++++++++++++++++ src/OpenSEE/Startup.cs | 123 +++++++++++++++++------------------- 2 files changed, 195 insertions(+), 65 deletions(-) create mode 100644 src/OpenSEE/Program.cs diff --git a/src/OpenSEE/Program.cs b/src/OpenSEE/Program.cs new file mode 100644 index 00000000..a8678ed5 --- /dev/null +++ b/src/OpenSEE/Program.cs @@ -0,0 +1,137 @@ +//****************************************************************************************************** +// Program.cs - Gbtc +// +// Copyright © 2020, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 01/23/2026 - Billy Ernest +// Generated original version of source code. +// +//****************************************************************************************************** + +using System; +using System.IO; +using Gemstone.Configuration; +using Gemstone.Data; +using Gemstone.Diagnostics; +using Gemstone.Threading; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Debug; +using OpenSEE.Models; + +namespace OpenSEE +{ + public class Program + { + public static IConfiguration Configuration { get; } = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true) + .Build(); + + public static void Main(string[] args) + { + try + { + ShutdownHandler.Initialize(); + + Settings settings = new() + { + INIFile = ConfigurationOperation.ReadWrite, + SQLite = ConfigurationOperation.Disabled + }; + + DefineSettings(settings); + + // Bind settings to configuration sources + settings.Bind(new ConfigurationBuilder() + .ConfigureGemstoneDefaults(settings) + .AddCommandLine(args, settings.SwitchMappings)); + + HostApplicationBuilderSettings appSettings = new() + { + Args = args, + ApplicationName = nameof(OpenSEE), + DisableDefaults = true, + }; + + CreateHostBuilder(args).Build().Run(); + + #if DEBUG + Settings.Save(forceSave: true); + #else + Settings.Save(); + #endif + } + finally + { + ShutdownHandler.InitiateSafeShutdown(); + } + } + + /// + /// Establishes default settings for the config file. + /// + public static void DefineSettings(Settings settings) + { + using (Logger.SuppressFirstChanceExceptionLogMessages()) + { + DiagnosticsLogger.DefineSettings(settings); + AdoDataConnection.DefineSettings(settings); + } + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }) + .ConfigureServices((hostContext, services) => + { + // load settings from config file + services.Configure(hostContext.Configuration.GetSection("systemSettings")); + }) + .ConfigureLogging(builder => + { + builder.ClearProviders(); + builder.SetMinimumLevel(LogLevel.Information); + + builder.AddFilter("Microsoft", LogLevel.Warning); + builder.AddFilter("Microsoft.Hosting.Lifetime", LogLevel.Error); + builder.AddFilter("", LogLevel.Debug); + builder.AddFilter("", LogLevel.Trace); + + builder.AddConsole(options => options.LogToStandardErrorThreshold = LogLevel.Error); + builder.AddDebug(); + + // Add Gemstone diagnostics logging + builder.AddGemstoneDiagnostics(); + + #if RELEASE + if (OperatingSystem.IsWindows()) + { + builder.AddFilter("Application", LogLevel.Warning); + builder.AddEventLog(); + } + #endif + }); + } +} diff --git a/src/OpenSEE/Startup.cs b/src/OpenSEE/Startup.cs index 2cc07d81..a9dbe025 100644 --- a/src/OpenSEE/Startup.cs +++ b/src/OpenSEE/Startup.cs @@ -23,95 +23,88 @@ using System; using System.IO; -using System.Reflection; -using System.Web.Http; using Gemstone.Diagnostics; using Gemstone.IO; -using Gemstone.Web.Security; -using Gemstone.Web.Shared; -using Microsoft.Owin; -using Owin; -using static OpenSEE.Common; - -[assembly: OwinStartup(typeof(OpenSEE.Startup))] +using Gemstone.Web; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.HttpOverrides; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Newtonsoft.Json.Serialization; +using OpenSEE.Security; namespace OpenSEE; public class Startup { - public void Configuration(IAppBuilder app) + public Startup(IConfiguration configuration, IWebHostEnvironment env) { - // Enable GSF role-based security authentication - app.UseAuthentication(s_authenticationOptions); - - OwinLoaded = true; - - // Configure Web API for self-host - HttpConfiguration config = new HttpConfiguration(); - - // Enable GSF session management - config.EnableSessions(s_authenticationOptions); - - // Set configuration to use reflection to setup routes - config.MapHttpAttributeRoutes(); - - app.UseWebApi(config); + SetupTempPath(); + Configuration = configuration; + Env = env; } - private static readonly AuthenticationOptions s_authenticationOptions; + public IWebHostEnvironment Env { get; set; } + public IConfiguration Configuration { get; } - static Startup() + public void ConfigureServices(IServiceCollection services) { - SetupTempPath(); + IMvcBuilder builder = services + .AddControllersWithViews() + .AddNewtonsoftJson(options => + { + options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore; + options.SerializerSettings.ContractResolver = new DefaultContractResolver(); + }); - s_authenticationOptions = new AuthenticationOptions - { - LoginPage = "~/Login", - LogoutPage = "~/Security/logout", - LoginHeader = $"

{ApplicationName}

", - AuthTestPage = "~/AuthTest", - AnonymousResourceExpression = AnonymousResourceExpression, - AuthFailureRedirectResourceExpression = @"^/$|^/.+$" - }; + // Todo: Temp Auth + services.AddAuthentication(TestAuthHandler.AuthenticationScheme) + .AddScheme(TestAuthHandler.AuthenticationScheme, (options) => { }); - AuthenticationOptions = CreateInstance(s_authenticationOptions); - if (!LogEnabled) - return; - - // Retrieve application log path as defined in the config file - string logPath = LogPath; + services.AddMvc(); + } - // Make sure log directory exists - try + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) { - if (!Directory.Exists(logPath)) - Directory.CreateDirectory(logPath); + app.UseDeveloperExceptionPage(); } - catch + else { - logPath = FilePath.GetAbsolutePath(""); + app.UseExceptionHandler("/Error"); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); } - try - { - Logger.FileWriter.SetPath(logPath); - Logger.FileWriter.SetLoggingFileCount(MaxLogFiles); - } - catch + app.UseForwardedHeaders(new ForwardedHeadersOptions() { - // ignored - } - } + ForwardedHeaders = ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost + }); - public static bool OwinLoaded { get; private set; } + app.UseStaticFiles(WebExtensions.StaticFileEmbeddedResources()); + app.UseStaticFiles(); - public static ReadonlyAuthenticationOptions AuthenticationOptions { get; } + app.UseRouting(); - private static T CreateInstance(params object[] args) - { - Type type = typeof(T); - object instance = type.Assembly.CreateInstance(type.FullName!, false, BindingFlags.Instance | BindingFlags.NonPublic, null, args, null, null); - return (T)instance; + app.UseAuthentication(); + app.UseAuthorization(); + + app.UseEndpoints(endpoints => + { + endpoints.MapControllerRoute( + name: "default", + pattern: "{controller}/{newaction?}/{id?}", + defaults: new + { + controller = "Home", + action = "Index" + }); + + endpoints.MapControllers(); + }); } private static void SetupTempPath() From a5d3355bbdaf2bd789b4632d77f6bafb5da04909 Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Wed, 28 Jan 2026 08:58:50 -0500 Subject: [PATCH 20/58] removed webgrease bundling, is not compatable with aspnetcore --- src/OpenSEE/App_Start/BundleConfig.cs | 41 --------------------------- src/OpenSEE/OpenSEE.csproj | 4 --- 2 files changed, 45 deletions(-) delete mode 100644 src/OpenSEE/App_Start/BundleConfig.cs diff --git a/src/OpenSEE/App_Start/BundleConfig.cs b/src/OpenSEE/App_Start/BundleConfig.cs deleted file mode 100644 index 4716bfc5..00000000 --- a/src/OpenSEE/App_Start/BundleConfig.cs +++ /dev/null @@ -1,41 +0,0 @@ -//****************************************************************************************************** -// BundleConfig.cs - Gbtc -// -// Copyright © 2020, 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://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: -// ---------------------------------------------------------------------------------------------------- -// 02/19/2020 - Billy Ernest -// Generated original version of source code. -// -//****************************************************************************************************** - -using System.Web; -using System.Web.Optimization; - -namespace OpenSEE -{ - public class BundleConfig - { - // For more information on bundling, visit https://go.microsoft.com/fwlink/?LinkId=301862 - public static void RegisterBundles(BundleCollection bundles) - { - // Code removed for clarity. -#if Debug - BundleTable.EnableOptimizations = true; -#endif - - } - } -} diff --git a/src/OpenSEE/OpenSEE.csproj b/src/OpenSEE/OpenSEE.csproj index 037c8770..57a8d2de 100644 --- a/src/OpenSEE/OpenSEE.csproj +++ b/src/OpenSEE/OpenSEE.csproj @@ -30,7 +30,6 @@ if $(ConfigurationName) == Release npm run buildrelease -
@@ -104,9 +103,6 @@ if $(ConfigurationName) == Release npm run buildrelease ..\Dependencies\NuGet\MathNet.Numerics.5.0.0-alpha02\lib\net48\MathNet.Numerics.dll - - ..\Dependencies\NuGet\WebGrease.1.5.2\lib\WebGrease.dll - From b2a7593c0a81632816be41948f013eff11f1c3ef Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Wed, 28 Jan 2026 08:59:15 -0500 Subject: [PATCH 21/58] handle errors is taken care of by startup/program --- src/OpenSEE/App_Start/FilterConfig.cs | 36 --------------------------- 1 file changed, 36 deletions(-) delete mode 100644 src/OpenSEE/App_Start/FilterConfig.cs diff --git a/src/OpenSEE/App_Start/FilterConfig.cs b/src/OpenSEE/App_Start/FilterConfig.cs deleted file mode 100644 index 97498ed4..00000000 --- a/src/OpenSEE/App_Start/FilterConfig.cs +++ /dev/null @@ -1,36 +0,0 @@ -//****************************************************************************************************** -// FilterConfig.cs - Gbtc -// -// Copyright © 2020, 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://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: -// ---------------------------------------------------------------------------------------------------- -// 02/19/2020 - Billy Ernest -// Generated original version of source code. -// -//****************************************************************************************************** - -using System.Web; -using System.Web.Mvc; - -namespace OpenSEE -{ - public class FilterConfig - { - public static void RegisterGlobalFilters(GlobalFilterCollection filters) - { - filters.Add(new HandleErrorAttribute()); - } - } -} From 3fdbba52590b4692d54d3ef37d8f6eb65014865d Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Wed, 28 Jan 2026 09:00:12 -0500 Subject: [PATCH 22/58] route registration has been migrated to useendpoints as per microsoft recommendation --- src/OpenSEE/App_Start/RouteConfig.cs | 70 ---------------------------- 1 file changed, 70 deletions(-) delete mode 100644 src/OpenSEE/App_Start/RouteConfig.cs diff --git a/src/OpenSEE/App_Start/RouteConfig.cs b/src/OpenSEE/App_Start/RouteConfig.cs deleted file mode 100644 index 8efe6492..00000000 --- a/src/OpenSEE/App_Start/RouteConfig.cs +++ /dev/null @@ -1,70 +0,0 @@ -//****************************************************************************************************** -// RouteConfig.cs - Gbtc -// -// Copyright © 2020, 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://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: -// ---------------------------------------------------------------------------------------------------- -// 02/19/2020 - Billy Ernest -// Generated original version of source code. -// -//****************************************************************************************************** - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.Web.Mvc; -using System.Web.Routing; - -namespace OpenSEE -{ - public class RouteConfig - { - public static void RegisterRoutes(RouteCollection routes) - { - routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); - - routes.MapRoute( - name: "LoginRoute", - url: "Login", - defaults: new { controller = "Login", action = "Index" } - ); - - routes.MapRoute( - name: "AuthTestRoute", - url: "AuthTest", - defaults: new { controller = "Login", action = "AuthTest" } - ); - - routes.MapRoute( - name: "LogoutRoute", - url: "Logout", - defaults: new { controller = "Login", action = "Logout" } - ); - - routes.MapRoute( - name: "UserInfoRoute", - url: "UserInfo", - defaults: new { controller = "Login", action = "UserInfo" } - ); - - routes.MapRoute( - name: "Default", - url: "{controller}/{action}/{id}", - defaults: new { controller = "Home", action = "Home", id = UrlParameter.Optional } - ); - } - } -} From 7c139a5c724a0dcceec75915ae21faac78bcb113 Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Wed, 28 Jan 2026 09:00:37 -0500 Subject: [PATCH 23/58] changed to be compatable with gemstone --- .../TransmissionElements/BreakerOperation.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Libraries/openXDA.Model/TransmissionElements/BreakerOperation.cs b/src/Libraries/openXDA.Model/TransmissionElements/BreakerOperation.cs index fdf4ab18..3be64ebe 100644 --- a/src/Libraries/openXDA.Model/TransmissionElements/BreakerOperation.cs +++ b/src/Libraries/openXDA.Model/TransmissionElements/BreakerOperation.cs @@ -21,10 +21,9 @@ // //****************************************************************************************************** -using System; using System.Data; -using GSF.Data; -using GSF.Data.Model; +using Gemstone.Data; +using Gemstone.Data.Model; namespace openXDA.Model { @@ -91,10 +90,8 @@ public class BreakerView public string Energized { get; set; } - [Searchable] public int BreakerNumber { get; set; } - [Searchable] public string LineName { get; set; } public string PhaseName { get; set; } @@ -103,7 +100,6 @@ public class BreakerView public int Speed { get; set; } - [Searchable] public string OperationType { get; set; } public string UpdatedBy { get; set; } From 66d4ca476a6a43a2a1caf1a428d0f05ef85e451b Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Wed, 28 Jan 2026 09:01:12 -0500 Subject: [PATCH 24/58] removed uneeded files --- src/OpenSEE/Controllers/LoginController.cs | 61 -------------------- src/OpenSEE/Model/AppModel.cs | 67 ---------------------- 2 files changed, 128 deletions(-) delete mode 100644 src/OpenSEE/Controllers/LoginController.cs delete mode 100644 src/OpenSEE/Model/AppModel.cs diff --git a/src/OpenSEE/Controllers/LoginController.cs b/src/OpenSEE/Controllers/LoginController.cs deleted file mode 100644 index d7a99357..00000000 --- a/src/OpenSEE/Controllers/LoginController.cs +++ /dev/null @@ -1,61 +0,0 @@ -//****************************************************************************************************** -// LoginController.cs - Gbtc -// -// Copyright © 2023, 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://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: -// ---------------------------------------------------------------------------------------------------- -// 02/26/2023 - J. Ritchie Carroll -// Generated original version of source code. -// -//****************************************************************************************************** - -using System; -using System.Threading; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace OpenSEE.Controllers; - -public class LoginController : Controller -{ - [AllowAnonymous] - public ActionResult Index() - { - if (!Startup.OwinLoaded) - throw new InvalidOperationException("Owin pipeline not loaded. Try running 'update-package Microsoft.Owin.Host.SystemWeb -reinstall' from NuGet Package Manager Console."); - - return View(); - } - - [Route("~/AuthTest")] - public ActionResult AuthTest() - { - return View(); - } - - [Route("~/Logout")] - [AllowAnonymous] - public ActionResult Logout() - { - return View(); - } - - [Route("~/UserInfo")] - public ActionResult UserInfo() - { - Thread.CurrentPrincipal = ViewBag.SecurityPrincipal = User; - return View(); - } -} \ No newline at end of file diff --git a/src/OpenSEE/Model/AppModel.cs b/src/OpenSEE/Model/AppModel.cs deleted file mode 100644 index f3ca2b12..00000000 --- a/src/OpenSEE/Model/AppModel.cs +++ /dev/null @@ -1,67 +0,0 @@ -//****************************************************************************************************** -// AppModel.cs - Gbtc -// -// Copyright © 2020, 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://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: -// ---------------------------------------------------------------------------------------------------- -// 02/19/2020 - Billy Ernest -// Generated original version of source code. -// -//****************************************************************************************************** - -namespace OpenSEE.Model -{ - /// - /// Defines a base application model with convenient global settings and functions. - /// - /// - /// Custom view models should inherit from AppModel because the "Global" property is used by _Layout.cshtml. - /// - public class AppModel - { - #region [ Constructors ] - - /// - /// Creates a new . - /// - public AppModel() - { - Global = MvcApplication.DefaultModel != null ? MvcApplication.DefaultModel.Global : new GlobalSettings(); - } - - #endregion - - #region [ Properties ] - - /// - /// Gets global settings for application. - /// - public GlobalSettings Global { get; } - #endregion - - #region [ Methods ] - - public bool IsDebug() - { -#if DEBUG - return true; -#else - return false; -#endif - - } - #endregion - } -} \ No newline at end of file From b2eac6feffbc016563272a861212267a79ba444b Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Wed, 28 Jan 2026 09:02:20 -0500 Subject: [PATCH 25/58] removed uneeded extension, added static file function for providing aspnetcore with gemstone shared css files --- src/OpenSEE/WebExtensions.cs | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/src/OpenSEE/WebExtensions.cs b/src/OpenSEE/WebExtensions.cs index ef0af296..2b450b60 100644 --- a/src/OpenSEE/WebExtensions.cs +++ b/src/OpenSEE/WebExtensions.cs @@ -21,32 +21,27 @@ // //****************************************************************************************************** +using System.Reflection; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.FileProviders; - -using Gemstone.Data.Model; -using Gemstone.Web.Model; -using System.Collections.Generic; - -namespace OpenSEE +namespace Gesmtone.Web { - - [TableName("OpenSEE.Setting")] - [UseEscapedName] - public class OpenSEESetting : openXDA.Model.Setting { }; - public static class WebExtensions { - public static Dictionary LoadDatabaseSettings(this DataContext dataContext, string scope) + /// + /// Used to create for serving Gemstone.Web embedded javascript and css stylesheets. + /// + /// + public static StaticFileOptions StaticFileEmbeddedResources() { - Dictionary settings = new Dictionary(); - - foreach (OpenSEESetting setting in dataContext.Table().QueryRecords("Name")) + ManifestEmbeddedFileProvider embeddedFileProvider = new(Assembly.GetExecutingAssembly(), "Shared"); + return new StaticFileOptions { - if (!string.IsNullOrEmpty(setting.Name)) - settings.Add(setting.Name, setting.Value); - } - - return settings; + FileProvider = embeddedFileProvider, + RequestPath = new PathString("/@Gemstone") + }; } } } \ No newline at end of file From e4367339f642b6bc13e7134ecacd8768b928e172 Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Wed, 28 Jan 2026 09:02:47 -0500 Subject: [PATCH 26/58] added needed iis options --- src/OpenSEE/Properties/launchSettings.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/OpenSEE/Properties/launchSettings.json b/src/OpenSEE/Properties/launchSettings.json index dcea5e72..8e259806 100644 --- a/src/OpenSEE/Properties/launchSettings.json +++ b/src/OpenSEE/Properties/launchSettings.json @@ -1,5 +1,20 @@ { + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "https://localhost:50951", + "sslPort": 0 + } + }, "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, "OpenSEE": { "commandName": "Project", "launchBrowser": true, From 1c6a80a09e71575eddd74dc9b9e336cb84cd4ef1 Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Wed, 28 Jan 2026 09:03:18 -0500 Subject: [PATCH 27/58] removed, most of this is covered by gemstone.configuration --- src/OpenSEE/Common.cs | 79 ------------------------------------------- 1 file changed, 79 deletions(-) delete mode 100644 src/OpenSEE/Common.cs diff --git a/src/OpenSEE/Common.cs b/src/OpenSEE/Common.cs deleted file mode 100644 index b859826f..00000000 --- a/src/OpenSEE/Common.cs +++ /dev/null @@ -1,79 +0,0 @@ -//****************************************************************************************************** -// Common.cs - Gbtc -// -// Copyright © 2023, 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://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: -// ---------------------------------------------------------------------------------------------------- -// 02/26/2023 - J. Ritchie Carroll -// Generated original version of source code. -// -//****************************************************************************************************** - -using System.IO; -using Gemstone; -using Gemstone.Configuration; -using Gemstone.IO; -using Gemstone.Reflection; -using Gemstone.Web.Security; - -namespace OpenSEE; - -public static class Common -{ - private static string s_applicationName; - private static string s_anonymousResourceExpression; - - public static string ApplicationName => s_applicationName ??= GetApplicationName(); - - public static string AnonymousResourceExpression => s_anonymousResourceExpression ??= GetAnonymousResourceExpression(); - - public static bool LogEnabled => GetLogEnabled(); - - public static string LogPath => GetLogPath(); - - public static int MaxLogFiles => GetMaxLogFiles(); - - private static string GetApplicationName() => - // Try database configured application name (if loaded yet) - MvcApplication.DefaultModel.Global.ApplicationName ?? - // Fall back on setting defined in web.config - GetSettingValue("SecurityProvider", "ApplicationName", "GSF Authentication"); - - private static string GetAnonymousResourceExpression() => - GetSettingValue(Settings.Default, "AnonymousResourceExpression", AuthenticationOptions.DefaultAnonymousResourceExpression); - - private static bool GetLogEnabled() => - GetSettingValue(Settings.Default, "LogEnabled", AssemblyInfo.ExecutingAssembly.Debuggable.ToString()).ParseBoolean(); - - private static string GetLogPath() => - GetSettingValue(Settings.Default, "LogPath", string.Format("{0}{1}Logs{1}", FilePath.GetAbsolutePath(""), Path.DirectorySeparatorChar)); - - private static int GetMaxLogFiles() => - int.TryParse(GetSettingValue(Settings.Default, "MaxLogFiles", "300"), out int maxLogFiles) ? maxLogFiles : 300; - - private static string GetSettingValue(string section, string keyName, string defaultValue) - { - try - { - ConfigurationFile config = ConfigurationFile.Current; - CategorizedSettingsElementCollection settings = config.Settings[section]; - return settings[keyName, true].ValueAs(defaultValue); - } - catch - { - return defaultValue; - } - } -} \ No newline at end of file From c627f48ab4fc2f3d297b4bac4e32db29a73838d8 Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Wed, 28 Jan 2026 09:05:06 -0500 Subject: [PATCH 28/58] removed due to removing appmodel (is not needed by frontend) --- src/OpenSEE/Controllers/HomeController.cs | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/OpenSEE/Controllers/HomeController.cs b/src/OpenSEE/Controllers/HomeController.cs index 239b7436..9c6f1cab 100644 --- a/src/OpenSEE/Controllers/HomeController.cs +++ b/src/OpenSEE/Controllers/HomeController.cs @@ -37,18 +37,7 @@ namespace OpenSEE.Controllers /// public class HomeController : Controller { - #region [ Constructors ] - - public HomeController() - { - ViewData.Model = new AppModel(); - } - - #endregion - - #region [ Methods ] - - public ActionResult Home() + public IActionResult Index() { ViewBag.IsAdmin = User.IsInRole("Administrator"); @@ -75,7 +64,5 @@ public ActionResult Home() ViewBag.Cycles = Math.Floor((evt.EndTime - evt.StartTime).TotalSeconds * 60.0D); return View("Index"); } - - #endregion } } \ No newline at end of file From fb6ff2c66b0b9f1ee5afcfb65191cceaa400932d Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Wed, 28 Jan 2026 09:14:13 -0500 Subject: [PATCH 29/58] removed isAdmin --- src/OpenSEE/Controllers/HomeController.cs | 3 --- src/OpenSEE/Views/Home/Index.cshtml | 1 - 2 files changed, 4 deletions(-) diff --git a/src/OpenSEE/Controllers/HomeController.cs b/src/OpenSEE/Controllers/HomeController.cs index 9c6f1cab..361ce1d9 100644 --- a/src/OpenSEE/Controllers/HomeController.cs +++ b/src/OpenSEE/Controllers/HomeController.cs @@ -27,7 +27,6 @@ using Gemstone.Data.Model; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Primitives; -using OpenSEE.Model; using openXDA.Model; namespace OpenSEE.Controllers @@ -39,8 +38,6 @@ public class HomeController : Controller { public IActionResult Index() { - ViewBag.IsAdmin = User.IsInRole("Administrator"); - int eventID = -1; Event evt; diff --git a/src/OpenSEE/Views/Home/Index.cshtml b/src/OpenSEE/Views/Home/Index.cshtml index 01d609d6..b25e5216 100644 --- a/src/OpenSEE/Views/Home/Index.cshtml +++ b/src/OpenSEE/Views/Home/Index.cshtml @@ -76,7 +76,6 @@
- - - - - - @*@Scripts.Render("~/Scripts/OpenSEE")*@ From 525deced0599ac989d1afcb5a16a9c9cbbe01d92 Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Thu, 5 Feb 2026 10:42:42 -0500 Subject: [PATCH 36/58] Add eventStat for CSVController --- .../openXDA.Model/Events/EventStat.cs | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/Libraries/openXDA.Model/Events/EventStat.cs diff --git a/src/Libraries/openXDA.Model/Events/EventStat.cs b/src/Libraries/openXDA.Model/Events/EventStat.cs new file mode 100644 index 00000000..6e004a3e --- /dev/null +++ b/src/Libraries/openXDA.Model/Events/EventStat.cs @@ -0,0 +1,57 @@ +//****************************************************************************************************** +// EventStat.cs - Gbtc +// +// Copyright © 2018, 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://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/07/2018 - Billy Ernest +// Generated original version of source code. +// +//****************************************************************************************************** + +using Gemstone.Data.Model; + +namespace openXDA.Model +{ + public class EventStat + { + [PrimaryKey(true)] + public int ID { get; set; } + public int EventID { get; set; } + public double? VPeak { get; set; } + public double? VAMax { get; set; } + public double? VBMax { get; set; } + public double? VCMax { get; set; } + public double? VABMax { get; set; } + public double? VBCMax { get; set; } + public double? VCAMax { get; set; } + public double? VAMin { get; set; } + public double? VBMin { get; set; } + public double? VCMin { get; set; } + public double? VABMin { get; set; } + public double? VBCMin { get; set; } + public double? VCAMin { get; set; } + public double? IPeak { get; set; } + public double? IAMax { get; set; } + public double? IBMax { get; set; } + public double? ICMax { get; set; } + public double? IA2t { get; set; } + public double? IB2t { get; set; } + public double? IC2t { get; set; } + public double? InitialMW { get; set; } + public double? FinalMW { get; set; } + public int? PQViewID { get; set; } + } +} From 2e4f930d07d189caa83bb00f22bfed760c9a4533 Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Thu, 5 Feb 2026 10:43:17 -0500 Subject: [PATCH 37/58] add PQDS project for CSV Download --- src/Libraries/PQDS/DataSeries.cs | 94 +++ src/Libraries/PQDS/MetaDataTag.cs | 421 ++++++++++++++ src/Libraries/PQDS/PQDS.csproj | 23 + src/Libraries/PQDS/PQDSFile.cs | 541 ++++++++++++++++++ src/Libraries/PQDS/Properties/AssemblyInfo.cs | 13 + src/OpenSEE/OpenSEE.csproj | 1 + 6 files changed, 1093 insertions(+) create mode 100644 src/Libraries/PQDS/DataSeries.cs create mode 100644 src/Libraries/PQDS/MetaDataTag.cs create mode 100644 src/Libraries/PQDS/PQDS.csproj create mode 100644 src/Libraries/PQDS/PQDSFile.cs create mode 100644 src/Libraries/PQDS/Properties/AssemblyInfo.cs diff --git a/src/Libraries/PQDS/DataSeries.cs b/src/Libraries/PQDS/DataSeries.cs new file mode 100644 index 00000000..5767e178 --- /dev/null +++ b/src/Libraries/PQDS/DataSeries.cs @@ -0,0 +1,94 @@ +//****************************************************************************************************** +// DataSeries.cs - Gbtc +// +// Copyright © 2020, 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 Eclipse Public License -v 1.0 (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/eclipse-1.0.php +// +// 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: +// ---------------------------------------------------------------------------------------------------- +// 03/06/2020 - Christoph Lackner +// Generated original version of source code. +// +//****************************************************************************************************** + + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace PQDS +{ + /// + /// Represents a channel in a PQDS File. + /// + public class DataSeries + { + #region[Properties] + + private List m_series; + private string m_label; + + /// + /// A collection of DataPoints. + /// + public List Series + { + get { return m_series; } + set { m_series = value; } + } + + /// + /// Label of the + /// + public string Label { get { return m_label; } } + + /// + /// length in number of points + /// + public int Length => m_series.Count(); + + #endregion[Properties] + + /// + /// Creates a new . + /// + /// Label of the DataSeries + public DataSeries(string label) + { + m_label = label; + m_series = new List(); + + } + #region[methods] + + #endregion[methods] + } + + /// + /// Represents a single Point in the . + /// + public class DataPoint + { + /// + /// Timestamp of the point. + /// + public DateTime Time; + + /// + /// Value of the point. + /// + public double Value; + } + + +} diff --git a/src/Libraries/PQDS/MetaDataTag.cs b/src/Libraries/PQDS/MetaDataTag.cs new file mode 100644 index 00000000..d08fc836 --- /dev/null +++ b/src/Libraries/PQDS/MetaDataTag.cs @@ -0,0 +1,421 @@ +//****************************************************************************************************** +// MetaDataTag.cs - Gbtc +// +// Copyright © 2020, 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 Eclipse Public License -v 1.0 (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/eclipse-1.0.php +// +// 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: +// ---------------------------------------------------------------------------------------------------- +// 03/06/2020 - Christoph Lackner +// Generated original version of source code. +// +//****************************************************************************************************** + +using System; +using System.Collections.Generic; + +namespace PQDS +{ + /// + /// PQDS metadata tag Datatypes according to PQDS spec. + /// + public enum PQDSMetaDataType + { + /// + /// An integer representing a single value selected + /// from among a custom, finite set of possibilities + /// + Enumeration = 0, + + /// + /// A number + /// + Numeric = 1, + + /// + /// Text consisting only of alphabetical characters and digits + /// + AlphaNumeric = 2, + + /// + /// Freeform text + /// + Text = 3, + + /// + /// A Boolean value (true/false) + /// + Binary = 4 + + } + + /// + /// Abstract Class of MetaData Tags for a . + /// + public abstract class MetaDataTag + { + #region[Properties] + + /// + /// The key that identifies the metadata tag. + /// + protected string m_key; + + /// + /// The unit of measurement. + /// + protected string m_unit; + + /// + /// The data type the parser expects to encounter for the value of the metdata. + /// + protected PQDSMetaDataType m_expectedDataType; + + /// + /// Additional notes about the metadata field. + /// + protected string m_note; + + #endregion[Properties] + + #region[Methods] + + /// + /// the Metadata Tag key. + /// + public String Key { get { return (this.m_key); } } + + /// + /// Converst the Metadata tag into a line of a PQDS file + /// + /// The metadataTag as a String + public abstract String Write(); + + /// + /// Returns the PQDS datatype + /// + /// The PQDS Datatype + public abstract PQDSMetaDataType Type(); + + #endregion[Methods] + } + + /// + /// Class of MetaData Tags for a . + /// + public class MetaDataTag : MetaDataTag + { + #region[Properties] + + private DataType m_value; + + /// + /// Value of the MetadataTag. + /// + public DataType Value { get { return m_value; } } + + #endregion[Properties] + + #region[Constructor] + + /// + /// Creates a . + /// + /// key of the MetadataTag + /// Value of the MetadataTag + public MetaDataTag(String key, DataType value) + { + this.m_value = value; + + this.m_key = key; + if (!keyToDataTypeLookup.TryGetValue(key, out this.m_expectedDataType)) + this.m_expectedDataType = PQDSMetaDataType.Text; + + if (!keyToUnitLookup.TryGetValue(key, out this.m_unit)) + this.m_unit = null; + + if (!keyToNoteLookup.TryGetValue(key, out this.m_note)) + this.m_note = null; + + //Check to ensure a string does not end up being a number etc... + if (this.m_expectedDataType == PQDSMetaDataType.AlphaNumeric) + { + if (!((value is string) | (value is Guid))) + { throw new InvalidCastException("Can not cast object to Alphanumeric Type"); } + } + else if (this.m_expectedDataType == PQDSMetaDataType.Numeric) + { + if (!((value is int) | (value is double))) + { throw new InvalidCastException("Can not cast object to Numeric Type"); } + } + else if (this.m_expectedDataType == PQDSMetaDataType.Enumeration) + { + if (!((value is int))) + { throw new InvalidCastException("Can not cast object to Numeric Type"); } + } + else if (this.m_expectedDataType == PQDSMetaDataType.Binary) + { + if (!((value is int) | (value is Boolean))) + { throw new InvalidCastException("Can not cast object to Numeric Type"); } + } + + } + + /// + /// Creates a custom . + /// + /// key of the MetadataTag + /// Value of the MetadataTag + /// The of the metadata tag + /// The unit of the metadata tag + /// a describtion of the metadata tag + public MetaDataTag(String key, DataType value, PQDSMetaDataType valueType, String unit, String description) + { + this.m_value = value; + + this.m_key = key; + this.m_expectedDataType = valueType; + + if (unit.Trim('"') == "") { this.m_unit = null; } + else { this.m_unit = unit.Trim('"'); } + + if (description.Trim('"') == "") { this.m_note = null; } + else { this.m_note = description.Trim('"'); } + + } + + #endregion[Constructor] + + #region[Methods] + + /// + /// Converst the Metadata tag into a line of a PQDS file + /// + /// The metadataTag as a String + public override string Write() + { + string result = String.Format("{0},\"{1}\",{2},{3},\"{4}\"", + this.m_key, this.m_value, this.m_unit, DataTypeToCSV(this.m_expectedDataType), this.m_note); + + return result; + } + + /// + /// Returns the PQDS datatype + /// + /// The PQDS Datatype + public override PQDSMetaDataType Type() + { + return this.m_expectedDataType; + } + + #endregion[Methods] + + #region[Statics] + + private static readonly Dictionary keyToDataTypeLookup = new Dictionary() + { + {"DeviceName", PQDSMetaDataType.Text }, + {"DeviceAlias", PQDSMetaDataType.Text }, + {"DeviceLocation", PQDSMetaDataType.Text }, + {"DeviceLocationAlias", PQDSMetaDataType.Text }, + {"DeviceLatitude", PQDSMetaDataType.Text }, + {"DeviceLongitude", PQDSMetaDataType.Text }, + {"Accountname", PQDSMetaDataType.Text }, + {"AccountNameAlias", PQDSMetaDataType.Text }, + {"DeviceDistanceToXFMR", PQDSMetaDataType.Numeric }, + {"DeviceConnectionTypeCode", PQDSMetaDataType.Enumeration }, + {"DeviceOwner", PQDSMetaDataType.Text }, + {"NominalVoltage-LG", PQDSMetaDataType.Numeric }, + {"NominalFrequency", PQDSMetaDataType.Numeric }, + {"UpstreamXFMR-kVA", PQDSMetaDataType.Numeric }, + {"LineLength", PQDSMetaDataType.Numeric }, + {"AssetName", PQDSMetaDataType.Text }, + {"EventGUID", PQDSMetaDataType.AlphaNumeric }, + {"EventID", PQDSMetaDataType.Text }, + {"EventYear", PQDSMetaDataType.Enumeration }, + {"EventMonth", PQDSMetaDataType.Enumeration }, + {"EventDay", PQDSMetaDataType.Enumeration }, + {"EventHour", PQDSMetaDataType.Enumeration }, + {"EventMinute", PQDSMetaDataType.Enumeration }, + {"EventSecond", PQDSMetaDataType.Enumeration }, + {"EventNanoSecond", PQDSMetaDataType.Numeric }, + {"EventDate", PQDSMetaDataType.Text }, + {"EventTime", PQDSMetaDataType.Text }, + {"EventTypeCode", PQDSMetaDataType.Enumeration }, + {"EventFaultTypeCode", PQDSMetaDataType.Enumeration }, + {"EventPeakCurrent", PQDSMetaDataType.Numeric }, + {"EventPeakVoltage", PQDSMetaDataType.Numeric }, + {"EventMaxVA", PQDSMetaDataType.Numeric }, + {"EventMaxVB", PQDSMetaDataType.Numeric }, + {"EventMaxVC", PQDSMetaDataType.Numeric }, + {"EventMinVA", PQDSMetaDataType.Numeric }, + {"EventMinVB", PQDSMetaDataType.Numeric }, + {"EventMinVC", PQDSMetaDataType.Numeric }, + {"EventMaxIA", PQDSMetaDataType.Numeric }, + {"EventMaxIB", PQDSMetaDataType.Numeric }, + {"EventMaxIC", PQDSMetaDataType.Numeric }, + {"EventPreEventCurrent", PQDSMetaDataType.Numeric }, + {"EventPreEventVoltage", PQDSMetaDataType.Numeric }, + {"EventDuration", PQDSMetaDataType.Numeric }, + {"EventFaultI2T", PQDSMetaDataType.Numeric }, + {"DistanceToFault", PQDSMetaDataType.Numeric }, + {"EventCauseCode", PQDSMetaDataType.Enumeration }, + {"WaveformDataType", PQDSMetaDataType.Enumeration }, + {"WaveFormSensitivityCode", PQDSMetaDataType.Enumeration }, + {"WaveFormSensitivityNote", PQDSMetaDataType.Text }, + {"Utility", PQDSMetaDataType.Text }, + {"ContactEmail", PQDSMetaDataType.Text } + }; + + private static readonly Dictionary keyToUnitLookup = new Dictionary() + { + {"DeviceName", null }, + {"DeviceAlias", null }, + {"DeviceLocation", null }, + {"DeviceLocationAlias", null }, + {"DeviceLatitude", null }, + {"DeviceLongitude", null }, + {"Accountname", null }, + {"AccountNameAlias", null }, + {"DeviceDistanceToXFMR", "feet" }, + {"DeviceConnectionTypeCode", null }, + {"DeviceOwner", null }, + {"NominalVoltage-LG", "Volts" }, + {"NominalFrequency", "Hz" }, + {"UpstreamXFMR-kVA", "kVA" }, + {"LineLength", "miles" }, + {"AssetName", null }, + {"EventGUID", null }, + {"EventID", null }, + {"EventYear", null }, + {"EventMonth", null }, + {"EventDay", null }, + {"EventHour", null }, + {"EventMinute", null }, + {"EventSecond", null }, + {"EventNanoSecond", null }, + {"EventDate", null }, + {"EventTime", null }, + {"EventTypeCode", null }, + {"EventFaultTypeCode", null }, + {"EventPeakCurrent", "Amps" }, + {"EventPeakVoltage", "Volts" }, + {"EventMaxVA", "Volts" }, + {"EventMaxVB", "Volts" }, + {"EventMaxVC", "Volts" }, + {"EventMinVA", "Volts" }, + {"EventMinVB", "Volts" }, + {"EventMinVC", "Volts" }, + {"EventMaxIA", "Amps" }, + {"EventMaxIB", "Amps" }, + {"EventMaxIC", "Amps" }, + {"EventPreEventCurrent", "Amps" }, + {"EventPreEventVoltage", "Volts" }, + {"EventDuration", "ms" }, + {"EventFaultI2T", "A2s" }, + {"DistanceToFault", "miles" }, + {"EventCauseCode", null }, + {"WaveformDataType", null }, + {"WaveFormSensitivityCode", null }, + {"WaveFormSensitivityNote", null }, + {"Utility", null }, + {"ContactEmail", null } + }; + + private static readonly Dictionary keyToNoteLookup = new Dictionary() + { + {"DeviceName", "Meter or measurement device name" }, + {"DeviceAlias", "Alternate meter or measurement device name" }, + {"DeviceLocation", "Meter or measurment device location name" }, + {"DeviceLocationAlias", "Alternate meter or device location name" }, + {"DeviceLatitude", "Latitude" }, + {"DeviceLongitude", "Longtitude" }, + {"Accountname", "Name of customer or account" }, + {"AccountNameAlias", "Alternate name of customer or account" }, + {"DeviceDistanceToXFMR", "Distance to the upstream transformer" }, + {"DeviceConnectionTypeCode", "PQDS code for meter connection type" }, + {"DeviceOwner", "Utility name" }, + {"NominalVoltage-LG", "Nominal Line to Ground Voltage" }, + {"NominalFrequency", "Nominal System frequency" }, + {"UpstreamXFMR-kVA", "Upstream Transformer size" }, + {"LineLength", "Length of the Line" }, + {"AssetName", "Asset name" }, + {"EventGUID", "Globally Unique Event Identifier" }, + {"EventID", "A user defined Event Name" }, + {"EventYear", "Year" }, + {"EventMonth", "Month" }, + {"EventDay", "Day" }, + {"EventHour", "Hour" }, + {"EventMinute", "Minute" }, + {"EventSecond", "Second" }, + {"EventNanoSecond", "Nanosconds" }, + {"EventDate", "Event Date" }, + {"EventTime", "Event Time" }, + {"EventTypeCode", "PQDS Event Type Code" }, + {"EventFaultTypeCode", "PQDS Fault Type Code" }, + {"EventPeakCurrent", "Peak Current"}, + {"EventPeakVoltage", "Peak Voltage" }, + {"EventMaxVA", "RMS Maximum A Phase Voltage" }, + {"EventMaxVB", "RMS Maximum B Phase Voltage" }, + {"EventMaxVC", "RMS Maximum C Phase Voltage" }, + {"EventMinVA", "RMS Minimum A Phase Voltage" }, + {"EventMinVB", "RMS Minimum B Phase Voltage" }, + {"EventMinVC", "RMS Minimum C Phase Voltage" }, + {"EventMaxIA", "RMS Maximum A Phase Current" }, + {"EventMaxIB", "RMS Maximum B Phase Current" }, + {"EventMaxIC", "RMS Maximum C Phase Current" }, + {"EventPreEventCurrent", "Pre Event Current" }, + {"EventPreEventVoltage", "pre Event Voltage" }, + {"EventDuration", "Event Duration" }, + {"EventFaultI2T", "I2(t) during Fault duration" }, + {"DistanceToFault", "Distance to Fault" }, + {"EventCauseCode", "PQDS Event Cause Code" }, + { "WaveformDataType", "PQDS Data Type Code"}, + {"WaveFormSensitivityCode", "PQDS Data Sensitivity Code" }, + {"WaveFormSensitivityNote", "Notes on the PQDS Data Sensitivity Code" }, + {"Utility", "Utility that Generated this Dataset" }, + {"ContactEmail", "Contact for Utility that Created this Dataset" } + }; + + private static string DataTypeToCSV(PQDSMetaDataType dataType) + { + switch (dataType) + { + case (PQDSMetaDataType.Text): + return "T"; + case (PQDSMetaDataType.Numeric): + return "N"; + case (PQDSMetaDataType.Enumeration): + return "E"; + case (PQDSMetaDataType.AlphaNumeric): + return "A"; + case (PQDSMetaDataType.Binary): + return "B"; + default: + return "T"; + } + } + + + #endregion[Statics] + + + + + + + } + + +} diff --git a/src/Libraries/PQDS/PQDS.csproj b/src/Libraries/PQDS/PQDS.csproj new file mode 100644 index 00000000..c29cf134 --- /dev/null +++ b/src/Libraries/PQDS/PQDS.csproj @@ -0,0 +1,23 @@ + + + netstandard2.0 + Library + PQDS + PQDS + Copyright © 2020 + 3.0.5.69 + 3.0.5.69 + + + ..\..\..\Build\Output\Debug\Libraries\ + ..\..\..\Build\Output\Debug\Libraries\PQDS.xml + + + ..\..\..\Build\Output\Release\Libraries\ + ..\..\..\Build\Output\Release\Libraries\PQDS.xml + + + + + + \ No newline at end of file diff --git a/src/Libraries/PQDS/PQDSFile.cs b/src/Libraries/PQDS/PQDSFile.cs new file mode 100644 index 00000000..17f3cc52 --- /dev/null +++ b/src/Libraries/PQDS/PQDSFile.cs @@ -0,0 +1,541 @@ +//****************************************************************************************************** +// PQDSFile.cs - Gbtc +// +// Copyright © 2020, 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 Eclipse Public License -v 1.0 (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/eclipse-1.0.php +// +// 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: +// ---------------------------------------------------------------------------------------------------- +// 03/06/2020 - Christoph Lackner +// Generated original version of source code. +// +//****************************************************************************************************** + + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; + +namespace PQDS +{ + /// + /// Class that represents a PQDS file. + /// + public class PQDSFile + { + #region[Properties] + + private List m_metaData; + private List m_Data; + private DateTime m_initialTS; + + #endregion[Properties] + + #region[Constructors] + /// + /// Creates a new PQDS file. + /// + /// Measurment data to be included as + /// Timestamp used as the beginning of the PQDS file + /// List of MetaData to be included in the PQDS file as + public PQDSFile(List metaData, List dataSeries, DateTime initialTimeStamp) + { + if (metaData is null) { this.m_metaData = new List(); } + else { this.m_metaData = metaData; } + + this.m_initialTS = initialTimeStamp; + this.m_Data = dataSeries; + } + + /// + /// Creates a new PQDS file. + /// + public PQDSFile() + { + this.m_metaData = new List(); + this.m_Data = new List(); + } + + #endregion[Constructors] + + #region[Methods] + + private void GetStartTime() + { + DateTime result; + int? day = null; + int? month = null; + int? year = null; + + if (this.m_metaData.Select(item => item.Key).Contains("eventdate")) + { + string val = ((MetaDataTag)this.m_metaData.Find(item => item.Key == "eventdate")).Value; + if (DateTime.TryParseExact(val, "MM/dd/yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out result)) + { + day = result.Day; + month = result.Month; + year = result.Year; + } + } + if (day is null) + { + if (this.m_metaData.Select(item => item.Key).Contains("eventday")) + { + day = ((MetaDataTag)this.m_metaData.Find(item => item.Key == "eventday")).Value; + } + else + { + day = DateTime.Now.Day; + } + } + if (month is null) + { + if (this.m_metaData.Select(item => item.Key).Contains("eventmonth")) + { + month = ((MetaDataTag)this.m_metaData.Find(item => item.Key == "eventmonth")).Value; + } + else + { + month = DateTime.Now.Month; + } + } + if (year is null) + { + if (this.m_metaData.Select(item => item.Key).Contains("eventyear")) + { + year = ((MetaDataTag)this.m_metaData.Find(item => item.Key == "eventyear")).Value; + } + else + { + year = DateTime.Now.Year; + } + } + + int? hour = null; + int? minute = null; + int? second = null; + + if (this.m_metaData.Select(item => item.Key).Contains("eventtime")) + { + string val = ((MetaDataTag)this.m_metaData.Find(item => item.Key == "eventtime")).Value; + if (DateTime.TryParseExact(val, "HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out result)) + { + hour = result.Hour; + minute = result.Minute; + second = result.Second; + } + } + if (hour is null) + { + if (this.m_metaData.Select(item => item.Key).Contains("eventhour")) + { + hour = ((MetaDataTag)this.m_metaData.Find(item => item.Key == "eventhour")).Value; + } + else + { + hour = DateTime.Now.Hour; + } + } + if (minute is null) + { + if (this.m_metaData.Select(item => item.Key).Contains("eventminute")) + { + minute = ((MetaDataTag)this.m_metaData.Find(item => item.Key == "eventminute")).Value; + } + else + { + minute = DateTime.Now.Minute; + } + } + if (second is null) + { + if (this.m_metaData.Select(item => item.Key).Contains("eventsecond")) + { + second = ((MetaDataTag)this.m_metaData.Find(item => item.Key == "eventsecond")).Value; + } + else + { + second = DateTime.Now.Second; + } + } + + + result = new DateTime((int)year, (int)month, (int)day, (int)hour, (int)minute, (int)second); + + this.m_initialTS = result; + } + + private MetaDataTag CreateMetaData(string[] flds) + { + + string dataTypeString = flds[3].Trim().ToUpper(); + PQDSMetaDataType dataType; + + switch (dataTypeString) + { + case "N": + { + dataType = PQDSMetaDataType.Numeric; + break; + } + case "E": + { + dataType = PQDSMetaDataType.Enumeration; + break; + } + case "B": + { + dataType = PQDSMetaDataType.Binary; + break; + } + case "A": + { + dataType = PQDSMetaDataType.AlphaNumeric; + break; + } + default: + { + dataType = PQDSMetaDataType.Text; + break; + } + } + + string key = flds[0].Trim().ToLower(); + string note = flds[4].Trim('"'); + string unit = flds[2].Trim('"'); + + switch (dataType) + { + case (PQDSMetaDataType.AlphaNumeric): + { + string value = flds[1].Trim('"'); + return new MetaDataTag(key, value, dataType, unit, note); + } + case (PQDSMetaDataType.Text): + { + string value = flds[1].Trim('"'); + return new MetaDataTag(key, value, dataType, unit, note); + } + case (PQDSMetaDataType.Enumeration): + { + int value = Convert.ToInt32(flds[1].Trim('"')); + return new MetaDataTag(key, value, dataType, unit, note); + } + case (PQDSMetaDataType.Numeric): + { + double value = Convert.ToDouble(flds[1].Trim('"')); + return new MetaDataTag(key, value, dataType, unit, note); + } + case (PQDSMetaDataType.Binary): + { + Boolean value = Convert.ToBoolean(flds[1].Trim('"')); + return new MetaDataTag(key, value, dataType, unit, note); + } + default: + { + string value = flds[1].Trim('"'); + return new MetaDataTag(key, value, dataType, unit, note); + } + } + + + } + + private Boolean IsDataHeader(string line) + { + if (!line.Contains(",")) + return false; + String[] flds = line.Split(','); + + if (flds[0].ToLower().Trim() == "waveform-data") + return true; + + return false; + } + + /// + /// List of included Metadata tags. + /// + public List MetaData + { + get { return this.m_metaData; } + } + + /// + /// List of data included in PQDS file as . + /// + public List Data + { + get { return this.m_Data; } + } + + /// + /// Writes the content to a .csv file. + /// + /// The to write the data to. + /// Progress Token + public void WriteToStream(StreamWriter stream, IProgress progress) + { + int n_data = this.Data.Select((item) => item.Length).Max(); + int n_total = n_data + n_data + this.m_metaData.Count() + 1; + + //create the metadata header + List lines = new List(); + lines = this.m_metaData.Select(item => item.Write()).ToList(); + + lines.AddRange(DataLines(n_total, progress)); + + for (int i = 0; i < lines.Count(); i++) + { + stream.WriteLine(lines[i]); + progress.Report((double)(n_data + i) / n_total); + } + + + } + + /// + /// Writes the content to a .csv file. + /// + /// file name + /// Progress Token + public void WriteToFile(string file, IProgress progress) + { + // Open the file and write in each line + using (StreamWriter fileWriter = new StreamWriter(File.OpenWrite(file))) + { + WriteToStream(fileWriter, progress); + } + + } + /// + /// Writes the content to a .csv file. + /// + /// file name + public void WriteToFile(string file) + { + Progress prog = new Progress(); + WriteToFile(file, prog); + } + + /// + /// Writes the content to an output Stream. + /// + /// The to write the data to. + public void WriteToStream(StreamWriter stream) + { + Progress prog = new Progress(); + WriteToStream(stream, prog); + } + + + + /// + /// Reads the content from a PQDS File. + /// + /// file name + public void ReadFromFile(string filename) + { + Progress prog = new Progress(); + ReadFromFile(filename, prog); + } + + + /// + /// Reads the content from a PQDS File. + /// + /// file name + /// Progress Token + public void ReadFromFile(string filename, IProgress progress) + { + List lines = new List(); + // Open the file and read each line + using (StreamReader fileReader = new StreamReader(File.OpenRead(filename))) + { + while (!fileReader.EndOfStream) + { + lines.Add(fileReader.ReadLine().Trim()); + } + } + + int index = 0; + String[] flds; + // Parse MetaData Section + this.m_metaData = new List(); + + while (!(IsDataHeader(lines[index]))) + { + if (!lines[index].Contains(",")) + { + index++; + continue; + } + + flds = lines[index].Split(','); + + if (flds.Count() < 5) + { + index++; + continue; + } + this.m_metaData.Add(CreateMetaData(flds)); + index++; + + if (index == lines.Count()) + { throw new InvalidDataException("PQDS File not valid"); } + progress.Report((double)index / (double)lines.Count()); + } + + //Parse Data Header + flds = lines[index].Split(','); + + if (flds.Count() < 2) + { + throw new InvalidDataException("PQDS File has invalid data section or no data"); + } + + this.m_Data = new List(); + List signals = new List(); + List> data = new List>(); + + + for (int i = 1; i < flds.Count(); i++) + { + if (signals.Contains(flds[i].Trim().ToLower())) + { + continue; + } + this.m_Data.Add(new DataSeries(flds[i].Trim().ToLower())); + signals.Add(flds[i].Trim().ToLower()); + data.Add(new List()); + } + + index++; + //Parse Data + GetStartTime(); + + while (index < lines.Count()) + { + if (!lines[index].Contains(",")) + { + index++; + continue; + } + + flds = lines[index].Split(','); + + if (flds.Count() != (this.m_Data.Count() + 1)) + { + index++; + continue; + } + DateTime TS; + try + { + double ticks = Convert.ToDouble(flds[0].Trim()); + TS = this.m_initialTS + new TimeSpan((Int64)(ticks * 100)); + } + catch + { + index++; + continue; + } + + for (int i = 0; i < signals.Count(); i++) + { + try + { + double value = Convert.ToDouble(flds[i + 1].Trim()); + data[i].Add(new DataPoint() { Time = TS, Value = value }); + } + catch + { + continue; + } + } + + progress.Report((double)index / (double)lines.Count()); + index++; + } + + for (int i = 0; i < signals.Count(); i++) + { + int j = this.m_Data.FindIndex(item => item.Label == signals[i]); + this.m_Data[j].Series = data[j]; + } + } + + private List DataLines(int n_total, IProgress progress) + { + List result = new List(); + + //ensure they all start at the same Time + List measurements = this.m_Data.Select(item => item.Label).ToList(); + DateTime initalStart = this.m_Data.Select(item => item.Series[0].Time).Min(); + List startTime = this.m_Data.Select(item => item.Series[0].Time - initalStart).ToList(); + + //1 ms difference is ok + if (startTime.Max().TotalMilliseconds > 1) + { + throw new Exception("The measurements start at different times"); + } + + //write the header + result.Add("waveform-data," + String.Join(",", measurements)); + + + //write the Data + // Logic for skipping datapoints if they don't have the same sampling rate + List samplingRates = m_Data.Select(item => item.Length).Distinct().ToList(); + + int n_data = samplingRates.Max(); + + Dictionary> reSampling = new Dictionary>(); + + if (samplingRates.Any(f => ((double)n_data / (double)f) % 1 != 0)) + throw new Exception("Sampling Rates in this File do not match and are not multiples of each other."); + + reSampling = samplingRates.Select(item => new KeyValuePair>(item, (int index, DataSeries ds) => { + int n = n_data / item; + if (index % n == 0) + return ds.Series[index / n].Value; + else + return double.NaN; + })) + .ToDictionary(item => item.Key, item => item.Value); + + for (int i = 0; i < n_data; i++) + { + TimeSpan dT = m_Data[0].Series[i].Time - m_initialTS; + result.Add(Convert.ToString(dT.TotalMilliseconds) + "," + + String.Join(",", m_Data.Select(item => { + double v = reSampling[item.Length](i, item); + if (double.IsNaN(v)) + return "NaN".PadLeft(12); + return String.Format("{0:F12}", v); + }).ToList())); + progress.Report((double)i / (double)n_total); + } + + return result; + + } + + #endregion[Methods] + + } + + +} diff --git a/src/Libraries/PQDS/Properties/AssemblyInfo.cs b/src/Libraries/PQDS/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..b6c46133 --- /dev/null +++ b/src/Libraries/PQDS/Properties/AssemblyInfo.cs @@ -0,0 +1,13 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c6e64ba2-dca7-4a34-973a-2306a1f9effc")] diff --git a/src/OpenSEE/OpenSEE.csproj b/src/OpenSEE/OpenSEE.csproj index 0881ea62..974dab2e 100644 --- a/src/OpenSEE/OpenSEE.csproj +++ b/src/OpenSEE/OpenSEE.csproj @@ -104,6 +104,7 @@ if $(ConfigurationName) == Release npm run buildrelease +
From 1cabe7fede1f1f78c11728ea5955cb7f0097e446 Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Thu, 5 Feb 2026 10:44:51 -0500 Subject: [PATCH 38/58] replace CSV IHttpHandler with aspnetcore compatible controller --- src/OpenSEE/CSVDownload.ashx | 1 - .../CSVController.cs} | 266 ++++++------------ src/OpenSEE/OpenSEE.csproj | 4 - 3 files changed, 81 insertions(+), 190 deletions(-) delete mode 100644 src/OpenSEE/CSVDownload.ashx rename src/OpenSEE/{CSVDownload.ashx.cs => Controllers/CSVController.cs} (79%) diff --git a/src/OpenSEE/CSVDownload.ashx b/src/OpenSEE/CSVDownload.ashx deleted file mode 100644 index fb385e0f..00000000 --- a/src/OpenSEE/CSVDownload.ashx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebHandler Language="C#" CodeBehind="CSVDownload.ashx.cs" Class="OpenSEE.CSVDownload" %> diff --git a/src/OpenSEE/CSVDownload.ashx.cs b/src/OpenSEE/Controllers/CSVController.cs similarity index 79% rename from src/OpenSEE/CSVDownload.ashx.cs rename to src/OpenSEE/Controllers/CSVController.cs index b100edab..58aa4bd7 100644 --- a/src/OpenSEE/CSVDownload.ashx.cs +++ b/src/OpenSEE/Controllers/CSVController.cs @@ -23,161 +23,88 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.Data; using System.IO; using System.Linq; -using System.Net.Http; -using System.Net.Http.Headers; using System.Threading.Tasks; -using System.Web; using FaultData.DataAnalysis; -using Gemstone.Collections; using Gemstone.Configuration; using Gemstone.Data; using Gemstone.Data.Model; -using Gemstone.Threading; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; using OpenSEE.Model; using openXDA.Model; -using CancellationToken = System.Threading.CancellationToken; namespace OpenSEE { /// - /// Summary description for OpenSEECSVDownload + /// An MVC controller for downloading /// - public class CSVDownload : IHttpHandler + [Route("api/CSV")] + public class CSVController : Controller { - #region [ Members ] - // Fields private DateTime m_epoch = new DateTime(1970, 1, 1); - // Nested Types - private class HttpResponseCancellationToken : CompatibleCancellationToken - { - private readonly HttpResponse m_reponse; - - public HttpResponseCancellationToken(HttpResponse response) : base(CancellationToken.None) - { - m_reponse = response; - } - - public override bool IsCancelled => !m_reponse.IsClientConnected; - } - - const string CsvContentType = "text/csv"; - - #endregion - - #region [ Properties ] - - /// - /// Gets a value indicating whether another request can use the instance. - /// - /// - /// true if the instance is reusable; otherwise, false. - /// - public bool IsReusable => false; - - /// - /// Determines if client cache should be enabled for rendered handler content. - /// - /// - /// If rendered handler content does not change often, the server and client will use the - /// to determine if the client needs to refresh the content. - /// - public bool UseClientCache => false; - - public string Filename { get; private set; } - #endregion #region [ Methods ] - public void ProcessRequest(HttpContext context) + [HttpGet, Route("Download")] + public ActionResult GetCSV() { - HttpResponse response = HttpContext.Current.Response; - HttpResponseCancellationToken cancellationToken = new HttpResponseCancellationToken(response); - NameValueCollection requestParameters = context.Request.QueryString; - - try - { - Filename = (requestParameters["Meter"] == null? "" : (requestParameters["Meter"] + "_")) + (requestParameters["EventType"] == null? "" : requestParameters["EventType"] + "_") + "Event_" + requestParameters["eventID"] + ".csv"; - if (requestParameters["type"] == "pqds") - Filename = "PQDS_" + Filename; - response.ClearContent(); - response.Clear(); - response.AddHeader("Content-Type", CsvContentType); - response.AddHeader("Content-Disposition", "attachment;filename=" + Filename); - response.BufferOutput = true; - - WriteTableToStream(requestParameters, response.OutputStream, response.Flush, cancellationToken); - } - catch (Exception e) - { - LogExceptionHandler?.Invoke(e); - throw; - } - finally - { - response.End(); - } - } + IQueryCollection requestParameters = Request.Query; - public Task ProcessRequestAsync(HttpRequestMessage request, HttpResponseMessage response, CancellationToken cancellationToken) - { - NameValueCollection requestParameters = request.RequestUri.ParseQueryString(); + string fileName = + $"{(requestParameters["type"] == "pqds" ? "PQDS_" : "")}" + + $"{(requestParameters["Meters"].Any() ? (requestParameters["Meter"].ToString() + "_") : "")}" + + $"{(requestParameters["EventType"].Any() ? (requestParameters["EventType"].ToString() + "_") : "")}" + + $"Event_{requestParameters["eventID"].ToString()}.csv"; - response.Content = new PushStreamContent((stream, content, context) => + // No using block, asp.net takes care of it for us + MemoryStream stream = new MemoryStream(); + using (StreamWriter writer = new StreamWriter(stream)) { - try - { - Filename = (requestParameters["Meter"] == null ? "" : (requestParameters["Meter"] + "_")) + (requestParameters["EventType"] == null ? "" : requestParameters["EventType"] + "_") + "Event_" + requestParameters["eventID"] + ".csv"; - if (requestParameters["type"] == "pqds") - Filename = "PQDS_" + Filename; - - WriteTableToStream(requestParameters, stream, null, cancellationToken); - } - catch (Exception e) + // todo: this can probably be turned async + WriteTableToStream(writer, requestParameters); + return new FileStreamResult(stream, "text/csv") { - LogExceptionHandler?.Invoke(e); - throw; - } - finally - { - stream.Close(); - } - }, - new MediaTypeHeaderValue(CsvContentType)); - - response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") - { - FileName = Filename - }; - - return Task.CompletedTask; + FileDownloadName = fileName + }; + } } - private void WriteTableToStream(NameValueCollection requestParameters, Stream responseStream, Action flushResponse, CompatibleCancellationToken cancellationToken) + private void WriteTableToStream(StreamWriter writer, IQueryCollection requestParameters) { - if (requestParameters["type"] == "csv") - ExportToCSV(responseStream, requestParameters); - else if (requestParameters["type"] == "pqds") - ExportToPQDS(responseStream, requestParameters); - else if (requestParameters["type"] == "stats") - ExportStatsToCSV(responseStream, requestParameters); - else if (requestParameters["type"] == "harmonics") - ExportHarmonicsToCSV(responseStream, requestParameters); - else if (requestParameters["type"] == "correlatedsags") - ExportCorrelatedSagsToCSV(responseStream, requestParameters); - else if (requestParameters["type"] == "fft") - ExportFFTToCSV(responseStream, requestParameters); + switch (requestParameters["type"].ToString()) + { + case "csv": + ExportToCSV(writer, requestParameters); + return; + case "pqds": + ExportToPQDS(writer, requestParameters); + return; + case "stats": + ExportStatsToCSV(writer, requestParameters); + return; + case "harmonics": + ExportHarmonicsToCSV(writer, requestParameters); + return; + case "correlatedsags": + ExportCorrelatedSagsToCSV(writer, requestParameters); + return; + case "fft": + ExportFFTToCSV(writer, requestParameters); + return; + default: + throw new ArgumentException("Parameter 'type' is not a valid value."); + } } // Converts the data group row of CSV data. private string ToCSV(IEnumerable data, int index) { - DateTime timestamp = this.m_epoch.Add(new TimeSpan((long)(data.First().DataPoints[index][1] * TimeSpan.TicksPerMillisecond))); + DateTime timestamp = m_epoch.Add(new TimeSpan((long)(data.First().DataPoints[index][1] * TimeSpan.TicksPerMillisecond))); IEnumerable row = new List() { timestamp.ToString("MM/dd/yyyy HH:mm:ss.fffffff"), timestamp.ToString("fffffff") }; @@ -199,27 +126,23 @@ private string GetCSVHeader(IEnumerable keys) return string.Join(",", headers); } - public void ExportToCSV(Stream returnStream, NameValueCollection requestParameters) + public void ExportToCSV(StreamWriter writer, IQueryCollection requestParameters) { IEnumerable data = BuildDataSeries(requestParameters); if (data.Count() == 0) return; - using (StreamWriter writer = new StreamWriter(returnStream)) - { - IEnumerable keys = data.Select(item => (item.LegendGroup + "-" + item.ChartLabel)) ; - // Write the CSV header to the file - writer.WriteLine(GetCSVHeader(keys)); + IEnumerable keys = data.Select(item => (item.LegendGroup + "-" + item.ChartLabel)) ; + // Write the CSV header to the file + writer.WriteLine(GetCSVHeader(keys)); - // Write data to the file - for (int i = 0; i < data.First().DataPoints.Count; ++i) - writer.WriteLine(ToCSV(data,i)); - } + // Write data to the file + for (int i = 0; i < data.First().DataPoints.Count; ++i) + writer.WriteLine(ToCSV(data, i)); } - - public void ExportToPQDS(Stream returnStream, NameValueCollection requestParameters) + public void ExportToPQDS(StreamWriter writer, IQueryCollection requestParameters) { - int eventID = int.Parse(requestParameters["eventID"]); + int eventID = int.Parse(requestParameters["eventID"].ToString()); using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { @@ -260,10 +183,7 @@ public void ExportToPQDS(Stream returnStream, NameValueCollection requestParamet PQDS.PQDSFile file = new PQDS.PQDSFile(metaData, data, evt.StartTime); - using (StreamWriter writer = new StreamWriter(returnStream)) - { - file.WriteToStream(writer); - } + file.WriteToStream(writer); } } @@ -271,7 +191,6 @@ public void ExportToPQDS(Stream returnStream, NameValueCollection requestParamet { List result = new List(); - result.Add(new PQDS.MetaDataTag("DeviceName", meter.Name)); result.Add(new PQDS.MetaDataTag("DeviceAlias", meter.ShortName)); result.Add(new PQDS.MetaDataTag("DeviceLocation", meter.Location.Name)); @@ -343,12 +262,9 @@ public void ExportToPQDS(Stream returnStream, NameValueCollection requestParamet { result.Add(new PQDS.MetaDataTag("EventMaxIC", (double)stat.ICMax)); } - - } result.Add(new PQDS.MetaDataTag("EventYear", ((DateTime)evt.StartTime).Year)); - result.Add(new PQDS.MetaDataTag("EventMonth", (evt.StartTime).Month)); result.Add(new PQDS.MetaDataTag("EventDay", (evt.StartTime).Day)); result.Add(new PQDS.MetaDataTag("EventHour", (evt.StartTime).Hour)); @@ -360,14 +276,8 @@ public void ExportToPQDS(Stream returnStream, NameValueCollection requestParamet String time = String.Format("{0:D2}:{1:D2}:{2:D2}", (evt.StartTime).Hour, (evt.StartTime).Minute, (evt.StartTime).Second); result.Add(new PQDS.MetaDataTag("EventDate", date)); result.Add(new PQDS.MetaDataTag("EventTime", time)); - - } - - - return result; - } private int Get_nanoseconds(DateTime date) @@ -377,15 +287,11 @@ private int Get_nanoseconds(DateTime date) result = result - (long)day.Hours * (60L * 60L * 10000000L); result = result - (long)day.Minutes * (60L * 10000000L); result = result - (long)day.Seconds * 10000000L; - - return ((int)result * 100); } - private int PQDSEventTypeCode(int XDAevtTypeID) - { - return 0; - } + private int PQDSEventTypeCode(int XDAevtTypeID) => 0; + private PQDS.DataSeries PQDSSeries(DataSeries data, string label) { PQDS.DataSeries result = new PQDS.DataSeries(label); @@ -395,11 +301,10 @@ private PQDS.DataSeries PQDSSeries(DataSeries data, string label) return result; } - public void ExportStatsToCSV(Stream returnStream, NameValueCollection requestParameters) + public void ExportStatsToCSV(StreamWriter writer, IQueryCollection requestParameters) { - int eventId = int.Parse(requestParameters["eventId"]); + int eventId = int.Parse(requestParameters["eventId"].ToString()); using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) - using (StreamWriter writer = new StreamWriter(returnStream)) { DataTable dataTable = connection.RetrieveData("SELECT * FROM OpenSEEScalarStatView WHERE EventID = {0}", eventId); DataRow row = dataTable.AsEnumerable().First(); @@ -413,12 +318,11 @@ public void ExportStatsToCSV(Stream returnStream, NameValueCollection requestPar } } - public void ExportCorrelatedSagsToCSV(Stream returnStream, NameValueCollection requestParameters) + public void ExportCorrelatedSagsToCSV(StreamWriter writer, IQueryCollection requestParameters) { - int eventID = int.Parse(requestParameters["eventId"]); + int eventID = int.Parse(requestParameters["eventId"].ToString()); using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) - using (StreamWriter writer = new StreamWriter(returnStream)) { double timeTolerance = connection.ExecuteScalar("SELECT Value FROM Setting WHERE Name = 'TimeTolerance'"); DateTime startTime = connection.ExecuteScalar("SELECT StartTime FROM Event WHERE ID = {0}", eventID); @@ -451,15 +355,14 @@ private class PhasorResult { public double Angle; } - public void ExportFFTToCSV(Stream returnStream, NameValueCollection requestParameters) + public void ExportFFTToCSV(StreamWriter writer, IQueryCollection requestParameters) { - int eventId = int.Parse(requestParameters["eventID"]); + int eventId = int.Parse(requestParameters["eventID"].ToString()); using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) - using (StreamWriter writer = new StreamWriter(returnStream)) { - double startTime = requestParameters["startDate"] == null ? 0.0 : double.Parse(requestParameters["startDate"]); - int cycles = requestParameters["cycles"] == null ? 0 : int.Parse(requestParameters["cycles"]); + double startTime = requestParameters["startDate"].Any() ? double.Parse(requestParameters["startDate"].ToString()) : 0.0; + int cycles = requestParameters["cycles"].Any() ? int.Parse(requestParameters["cycles"].ToString()) : 0; Event evt = (new TableOperations(connection)).QueryRecordWhere("ID = {0}", eventId); Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); @@ -491,11 +394,10 @@ public void ExportFFTToCSV(Stream returnStream, NameValueCollection requestParam } } - public void ExportHarmonicsToCSV(Stream returnStream, NameValueCollection requestParameters) + public void ExportHarmonicsToCSV(StreamWriter writer, IQueryCollection requestParameters) { - int eventId = int.Parse(requestParameters["eventId"]); + int eventId = int.Parse(requestParameters["eventId"].ToString()); using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) - using (StreamWriter writer = new StreamWriter(returnStream)) { DataTable dataTable = connection.RetrieveData(@" SELECT @@ -544,19 +446,19 @@ SnapshotHarmonics JOIN } } - public List BuildDataSeries(NameValueCollection requestParameters) + public List BuildDataSeries(IQueryCollection requestParameters) { - int eventID = int.Parse(requestParameters["eventID"]); - bool displayVolt = requestParameters["displayVolt"] == null ? true: bool.Parse(requestParameters["displayVolt"]); - bool displayCur = requestParameters["displayCur"] == null ? true : bool.Parse(requestParameters["displayCur"]); - bool displayTCE = requestParameters["displayTCE"] == null ? false : bool.Parse(requestParameters["displayTCE"]); - bool breakerdigitals = requestParameters["breakerdigitals"] == null ? false : bool.Parse(requestParameters["breakerdigitals"]); - bool displayAnalogs = requestParameters["displayAnalogs"] == null ? false : bool.Parse(requestParameters["displayAnalogs"]); - int lpfOrder = requestParameters["lpfOrder"] == null ? 0 : int.Parse(requestParameters["lpfOrder"]); - int hpfOrder = requestParameters["hpfOrder"] == null ? 0 : int.Parse(requestParameters["hpfOrder"]); - double Trc = requestParameters["Trc"] == null ? 0.0 : double.Parse(requestParameters["Trc"]); - int harmonic = requestParameters["harmonic"] == null ? 1 : int.Parse(requestParameters["harmonic"]); - List displayAnalytics = requestParameters["displayAnalytics"] == null ? new List() : requestParameters["displayAnalytics"].Split(',').ToList(); + int eventID = int.Parse(requestParameters["eventID"].ToString()); + bool displayVolt = requestParameters["displayVolt"].Any() ? bool.Parse(requestParameters["displayVolt"]) : true; + bool displayCur = requestParameters["displayCur"].Any() ? bool.Parse(requestParameters["displayCur"]) : true; + bool displayTCE = requestParameters["displayTCE"].Any() ? bool.Parse(requestParameters["displayTCE"]) : false; + bool breakerdigitals = requestParameters["breakerdigitals"].Any() ? bool.Parse(requestParameters["breakerdigitals"]) : false; + bool displayAnalogs = requestParameters["displayAnalogs"].Any() ? bool.Parse(requestParameters["displayAnalogs"]) : false; + int lpfOrder = requestParameters["lpfOrder"].Any() ? int.Parse(requestParameters["lpfOrder"]) : 0; + int hpfOrder = requestParameters["hpfOrder"].Any() ? int.Parse(requestParameters["hpfOrder"]) : 0; + double Trc = requestParameters["Trc"].Any() ? double.Parse(requestParameters["Trc"]) : 0.0; + int harmonic = requestParameters["harmonic"].Any() ? int.Parse(requestParameters["harmonic"]) : 1; + List displayAnalytics = requestParameters["displayAnalytics"].Any() ? requestParameters["displayAnalytics"].ToString().Split(',').ToList() : new List(); using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { @@ -884,11 +786,5 @@ private List QueryAnalogData(Meter meter, Event evt) } #endregion - - #region [ Static ] - - public static Action LogExceptionHandler; - - #endregion } } \ No newline at end of file diff --git a/src/OpenSEE/OpenSEE.csproj b/src/OpenSEE/OpenSEE.csproj index 974dab2e..c3f07498 100644 --- a/src/OpenSEE/OpenSEE.csproj +++ b/src/OpenSEE/OpenSEE.csproj @@ -20,7 +20,6 @@ if $(ConfigurationName) == Release npm run buildrelease - @@ -33,9 +32,6 @@ if $(ConfigurationName) == Release npm run buildrelease - - CSVDownload.ashx - FaultSpecifics.aspx ASPXCodeBehind From 2afd9773c9b05d0920b09cb67c8b004b66610876 Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Thu, 5 Feb 2026 10:45:16 -0500 Subject: [PATCH 39/58] add pqds project to solution --- src/OpenSEE.sln | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/OpenSEE.sln b/src/OpenSEE.sln index b16dcff9..41c023a7 100644 --- a/src/OpenSEE.sln +++ b/src/OpenSEE.sln @@ -18,6 +18,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FaultData", "Libraries\Faul EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "openXDA.Model", "Libraries\openXDA.Model\openXDA.Model.csproj", "{F463A000-041D-CC7D-0954-3942A5D4A946}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PQDS", "Libraries\PQDS\PQDS.csproj", "{C6E64BA2-DCA7-4A34-973A-2306A1F9EFFC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -40,6 +42,10 @@ Global {F463A000-041D-CC7D-0954-3942A5D4A946}.Debug|Any CPU.Build.0 = Debug|Any CPU {F463A000-041D-CC7D-0954-3942A5D4A946}.Release|Any CPU.ActiveCfg = Release|Any CPU {F463A000-041D-CC7D-0954-3942A5D4A946}.Release|Any CPU.Build.0 = Release|Any CPU + {C6E64BA2-DCA7-4A34-973A-2306A1F9EFFC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C6E64BA2-DCA7-4A34-973A-2306A1F9EFFC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C6E64BA2-DCA7-4A34-973A-2306A1F9EFFC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C6E64BA2-DCA7-4A34-973A-2306A1F9EFFC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -48,6 +54,7 @@ Global {79E70E44-FB78-B280-97D7-1650186161DF} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {1C77C616-BE00-A5A0-8109-C0D9F7C0202E} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {F463A000-041D-CC7D-0954-3942A5D4A946} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {C6E64BA2-DCA7-4A34-973A-2306A1F9EFFC} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3E81769A-8E91-4DE2-AD77-438C680437A5} From f3f815a8f1dde2e86322a0221ebaaa9f8551a191 Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Thu, 5 Feb 2026 14:24:14 -0500 Subject: [PATCH 40/58] fix settings pull --- .../openXDA.Model/Settings/OpenSEESetting.cs | 33 +++++++++++++++++++ src/OpenSEE/Controllers/CSVController.cs | 2 +- 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 src/Libraries/openXDA.Model/Settings/OpenSEESetting.cs diff --git a/src/Libraries/openXDA.Model/Settings/OpenSEESetting.cs b/src/Libraries/openXDA.Model/Settings/OpenSEESetting.cs new file mode 100644 index 00000000..a1e5adc9 --- /dev/null +++ b/src/Libraries/openXDA.Model/Settings/OpenSEESetting.cs @@ -0,0 +1,33 @@ +//****************************************************************************************************** +// OpenSEESetting.cs - Gbtc +// +// Copyright © 2017, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 02/05/2026 - Gabriel Santos +// Generated original version of source code. +// +//****************************************************************************************************** + +using Gemstone.Data.Model; + +namespace openXDA.Model +{ + [TableName("OpenSEE.Setting"), UseEscapedName] + [PostRoles("Administrator")] + [DeleteRoles("Administrator")] + [PatchRoles("Administrator")] + public class OpenSEESetting : Setting { } +} \ No newline at end of file diff --git a/src/OpenSEE/Controllers/CSVController.cs b/src/OpenSEE/Controllers/CSVController.cs index 58aa4bd7..41740500 100644 --- a/src/OpenSEE/Controllers/CSVController.cs +++ b/src/OpenSEE/Controllers/CSVController.cs @@ -563,7 +563,7 @@ private List QueryVoltageData(Meter meter, Event evt) bool useLL; using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { - useLL = connection.ExecuteScalar("SELECT Value FROM Settings WHERE Name = 'useLLVoltage'") ?? false; + useLL = bool.Parse(new TableOperations(connection).QueryRecordWhere("Name = {0}", "useLLVoltage")?.Value ?? bool.FalseString); } DataGroup dataGroup = OpenSEEBaseController From 200008fba2ee647f12a64483ce1dd889295e83c6 Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Thu, 5 Feb 2026 15:39:57 -0500 Subject: [PATCH 41/58] fix bug where streamwriter closing will close the output filestream, aspnet assumes stream is owned by controller --- src/OpenSEE/Controllers/CSVController.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/OpenSEE/Controllers/CSVController.cs b/src/OpenSEE/Controllers/CSVController.cs index 41740500..1f24dd60 100644 --- a/src/OpenSEE/Controllers/CSVController.cs +++ b/src/OpenSEE/Controllers/CSVController.cs @@ -50,6 +50,22 @@ public class CSVController : Controller #region [ Methods ] + /// + /// This is just with slightly different dispose logic. + /// + /// + /// MVC owns the so we shouldn't close it. + /// + private class NoCloseStreamWriter : StreamWriter + { + public NoCloseStreamWriter(Stream stream) : base(stream) { } + + protected override void Dispose(bool disposing) + { + base.Dispose(false); + } + } + [HttpGet, Route("Download")] public ActionResult GetCSV() { @@ -63,7 +79,7 @@ public ActionResult GetCSV() // No using block, asp.net takes care of it for us MemoryStream stream = new MemoryStream(); - using (StreamWriter writer = new StreamWriter(stream)) + using (StreamWriter writer = new NoCloseStreamWriter(stream)) { // todo: this can probably be turned async WriteTableToStream(writer, requestParameters); From 8f1ca2ed03d546600c0c8dc618b8218f2f28425c Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Thu, 5 Feb 2026 15:40:09 -0500 Subject: [PATCH 42/58] bugfix to reset stream position --- src/OpenSEE/Controllers/CSVController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/OpenSEE/Controllers/CSVController.cs b/src/OpenSEE/Controllers/CSVController.cs index 1f24dd60..25c4db5d 100644 --- a/src/OpenSEE/Controllers/CSVController.cs +++ b/src/OpenSEE/Controllers/CSVController.cs @@ -83,6 +83,7 @@ public ActionResult GetCSV() { // todo: this can probably be turned async WriteTableToStream(writer, requestParameters); + stream.Position = 0; return new FileStreamResult(stream, "text/csv") { FileDownloadName = fileName From 05779006b9fbd6d2bccefd724370e2b850eab68f Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Thu, 5 Feb 2026 15:40:51 -0500 Subject: [PATCH 43/58] bug exists where datagroup has no asset, this change also lets us cutdown on repeated code --- src/OpenSEE/Controllers/AnalyticController.cs | 131 ++----- src/OpenSEE/Controllers/CSVController.cs | 332 ++++++++---------- src/OpenSEE/Controllers/OpenSEEController.cs | 18 +- .../Controllers/OpenSeeControllerBase.cs | 23 +- 4 files changed, 184 insertions(+), 320 deletions(-) diff --git a/src/OpenSEE/Controllers/AnalyticController.cs b/src/OpenSEE/Controllers/AnalyticController.cs index 2681fcc0..c78ba366 100644 --- a/src/OpenSEE/Controllers/AnalyticController.cs +++ b/src/OpenSEE/Controllers/AnalyticController.cs @@ -486,17 +486,15 @@ public async Task GetFirstDerivativeData() using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { int eventId = int.Parse(Request.Query["eventId"].ToString()); - Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); - Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); List returnList = new List(); - DataTable table = connection.RetrieveData("SELECT ID, StartTime FROM Event WHERE ID = {0}", evt.ID); + DataTable table = connection.RetrieveData("SELECT ID, StartTime FROM Event WHERE ID = {0}", eventId); foreach (DataRow row in table.Rows) { int eventID = row.ConvertField("ID"); - DataGroup dataGroup = await QueryDataGroupAsync(eventId, meter); - VICycleDataGroup viCycleDataGroup = await QueryVICycleDataGroupAsync(eventID, meter); + // ToDo: this logic seems wrong, we look this up every time but it should be the same result, same eventId, unlike the line after... + DataGroup dataGroup = await QueryDataGroupAsync(eventId, connection); + VICycleDataGroup viCycleDataGroup = await QueryVICycleDataGroupAsync(eventID, connection); returnList = returnList.Concat(GetFirstDerivativeLookup(dataGroup, viCycleDataGroup)).ToList(); } @@ -602,11 +600,7 @@ public async Task GetImpedanceData() { int eventId = int.Parse(Request.Query["eventId"].ToString()); - Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); - Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); - - VICycleDataGroup viCycleDataGroup = await QueryVICycleDataGroupAsync(evt.ID, meter); + VICycleDataGroup viCycleDataGroup = await QueryVICycleDataGroupAsync(eventId, connection); List returnList = GetImpedanceLookup(viCycleDataGroup); JsonReturn returnDict = new JsonReturn(); @@ -790,11 +784,7 @@ public async Task GetRemoveCurrentData() using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { int eventId = int.Parse(Request.Query["eventId"].ToString()); - Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); - Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); - - DataGroup dataGroup = await QueryDataGroupAsync(evt.ID, meter); + DataGroup dataGroup = await QueryDataGroupAsync(eventId, connection); List returnList = GetRemoveCurrentLookup(dataGroup); JsonReturn returnDict = new JsonReturn(); @@ -944,11 +934,7 @@ public async Task GetI2tData() using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { int eventId = int.Parse(Request.Query["eventId"].ToString()); - Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); - Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); - - DataGroup dataGroup = await QueryDataGroupAsync(evt.ID, meter); + DataGroup dataGroup = await QueryDataGroupAsync(eventId, connection); List returnList = GetI2tLookup(dataGroup); JsonReturn returnDict = new JsonReturn(); @@ -1010,12 +996,7 @@ public async Task GetPowerData() using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { int eventId = int.Parse(Request.Query["eventId"].ToString()); - - Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); - Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); - - VICycleDataGroup vICycleDataGroup = await QueryVICycleDataGroupAsync(evt.ID, meter); + VICycleDataGroup vICycleDataGroup = await QueryVICycleDataGroupAsync(eventId, connection); List returnList = GetPowerLookup(vICycleDataGroup); JsonReturn returnDict = new JsonReturn(); @@ -1306,11 +1287,7 @@ public async Task GetMissingVoltageData() using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { int eventId = int.Parse(Request.Query["eventId"].ToString()); - Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); - Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); - - DataGroup dataGroup = await QueryDataGroupAsync(evt.ID, meter); + DataGroup dataGroup = await QueryDataGroupAsync(eventId, connection); List returnList = GetMissingVoltageLookup(dataGroup); JsonReturn returnDict = new JsonReturn(); @@ -1386,11 +1363,7 @@ public async Task GetClippedWaveformsData() using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { int eventId = int.Parse(Request.Query["eventId"].ToString()); - Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); - Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); - - DataGroup dataGroup = await QueryDataGroupAsync(evt.ID, meter); + DataGroup dataGroup = await QueryDataGroupAsync(eventId, connection); List returnList = GetClippedWaveformsLookup(dataGroup); JsonReturn returnDict = new JsonReturn(); @@ -1528,12 +1501,7 @@ public async Task GetLowPassFilterData() { int filterOrder = int.Parse(Request.Query["filter"].ToString()); int eventId = int.Parse(Request.Query["eventId"].ToString()); - - Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); - Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); - - DataGroup dataGroup = await QueryDataGroupAsync(evt.ID, meter); + DataGroup dataGroup = await QueryDataGroupAsync(eventId, connection); List returnList = GetLowPassFilterLookup(dataGroup, filterOrder); JsonReturn returnDict = new JsonReturn(); @@ -1602,12 +1570,7 @@ public async Task GetHighPassFilterData() { int filterOrder = int.Parse(Request.Query["filter"].ToString()); int eventId = int.Parse(Request.Query["eventId"].ToString()); - - Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); - Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); - - DataGroup dataGroup = await QueryDataGroupAsync(evt.ID, meter); + DataGroup dataGroup = await QueryDataGroupAsync(eventId, connection); List returnList = GetHighPassFilterLookup(dataGroup, filterOrder); JsonReturn returnDict = new JsonReturn(); @@ -1653,11 +1616,7 @@ public async Task GetOverlappingWaveformData() using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { int eventId = int.Parse(Request.Query["eventId"].ToString()); - Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); - Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); - - DataGroup dataGroup = await QueryDataGroupAsync(evt.ID, meter); + DataGroup dataGroup = await QueryDataGroupAsync(eventId, connection); List returnList = GetOverlappingWaveformLookup(dataGroup); JsonReturn returnDict = new JsonReturn(); @@ -1748,12 +1707,7 @@ public async Task GetRapidVoltageChangeData() using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { int eventId = int.Parse(Request.Query["eventId"].ToString()); - - Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); - Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); - - VICycleDataGroup vICycleDataGroup = await QueryVICycleDataGroupAsync(evt.ID, meter); + VICycleDataGroup vICycleDataGroup = await QueryVICycleDataGroupAsync(eventId, connection); List returnList = GetRapidVoltageChangeLookup(vICycleDataGroup); JsonReturn returnDict = new JsonReturn(); @@ -1829,12 +1783,7 @@ public async Task GetSymmetricalComponentsData() using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { int eventId = int.Parse(Request.Query["eventId"].ToString()); - - Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); - Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); - - VICycleDataGroup vICycleDataGroup = await QueryVICycleDataGroupAsync(evt.ID, meter); + VICycleDataGroup vICycleDataGroup = await QueryVICycleDataGroupAsync(eventId, connection); List returnList = GetSymmetricalComponentsLookup(vICycleDataGroup); JsonReturn returnDict = new JsonReturn(); @@ -2013,12 +1962,7 @@ public async Task GetUnbalanceData() using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { int eventId = int.Parse(Request.Query["eventId"].ToString()); - - Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); - Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); - - VICycleDataGroup vICycleDataGroup = await QueryVICycleDataGroupAsync(evt.ID, meter); + VICycleDataGroup vICycleDataGroup = await QueryVICycleDataGroupAsync(eventId, connection); List returnList = GetUnbalanceLookup(vICycleDataGroup); JsonReturn returnDict = new JsonReturn(); @@ -2164,12 +2108,7 @@ public async Task GetRectifierData() { int eventId = int.Parse(Request.Query["eventId"].ToString()); double TRC = double.Parse(Request.Query["Trc"].ToString()); - Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); - Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); - - double systemFrequency = connection.ExecuteScalar("SELECT Value FROM Setting WHERE Name = 'SystemFrequency'") ?? 60.0; - VIDataGroup dataGroup = await QueryVIDataGroupAsync(evt.ID, meter); + VIDataGroup dataGroup = await QueryVIDataGroupAsync(eventId, connection); List returnList = GetRectifierLookup(dataGroup, TRC); JsonReturn returnDict = new JsonReturn(); @@ -2259,11 +2198,7 @@ public async Task GetFrequencyData() using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { int eventId = int.Parse(Request.Query["eventId"].ToString()); - Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); - Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); - - VIDataGroup viDataGroup = await QueryVIDataGroupAsync(evt.ID, meter); + VIDataGroup viDataGroup = await QueryVIDataGroupAsync(eventId, connection); List returnList = GetFrequencyLookup(viDataGroup); JsonReturn returnDict = new JsonReturn(); @@ -2431,10 +2366,7 @@ public async Task GetTHDData() if (Request.Query.TryGetValue("fullRes", out StringValues fullRes)) forceFullRes = int.Parse(fullRes.ToString()); - Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); - Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); - DataGroup dataGroup = await QueryDataGroupAsync(evt.ID, meter); + DataGroup dataGroup = await QueryDataGroupAsync(eventId, connection); List returnList = GetTHDLookup(dataGroup, forceFullRes==1); JsonReturn returnDict = new JsonReturn(); @@ -2533,13 +2465,9 @@ public async Task GetSpecifiedHarmonicData() int forceFullRes = 0; if (Request.Query.TryGetValue("fullRes", out StringValues fullRes)) forceFullRes = int.Parse(fullRes.ToString()); - - Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); - Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); int specifiedHarmonic = int.Parse(Request.Query["specifiedHarmonic"].ToString()); - meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); - DataGroup dataGroup = await QueryDataGroupAsync(evt.ID, meter); + DataGroup dataGroup = await QueryDataGroupAsync(eventId, connection); List returnList = GetSpecifiedHarmonicLookup(dataGroup, specifiedHarmonic, forceFullRes==1); JsonReturn returnDict = new JsonReturn(); @@ -2656,13 +2584,8 @@ public async Task GetBreakerRestrikeData() using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { int eventId = int.Parse(Request.Query["eventId"].ToString()); - - Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); - Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); - - DataGroup dataGroup = await QueryDataGroupAsync(eventId, meter); - List returnList = GetBreakerRestrikeData(evt.ID, dataGroup); + DataGroup dataGroup = await QueryDataGroupAsync(eventId, connection); + List returnList = GetBreakerRestrikeData(eventId, dataGroup); JsonReturn returnDict = new JsonReturn(); returnDict.Data = returnList; @@ -2760,16 +2683,16 @@ public async Task GetFFTData() if (Request.Query.TryGetValue("cycles", out StringValues cycleString)) cycles = int.Parse(cycleString.ToString()); - Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); - Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); - double startTime; if (Request.Query.TryGetValue("startDate", out StringValues start)) startTime = double.Parse(start.ToString()); else + { + Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); startTime = evt.StartTime.Subtract(m_epoch).TotalMilliseconds; - DataGroup dataGroup = await QueryDataGroupAsync(eventId, meter); + } + + DataGroup dataGroup = await QueryDataGroupAsync(eventId, connection); List returnList = GetFFTLookup(dataGroup, startTime, cycles); JsonReturn returnDict = new JsonReturn(); diff --git a/src/OpenSEE/Controllers/CSVController.cs b/src/OpenSEE/Controllers/CSVController.cs index 25c4db5d..84b878a7 100644 --- a/src/OpenSEE/Controllers/CSVController.cs +++ b/src/OpenSEE/Controllers/CSVController.cs @@ -26,7 +26,6 @@ using System.Data; using System.IO; using System.Linq; -using System.Threading.Tasks; using FaultData.DataAnalysis; using Gemstone.Configuration; using Gemstone.Data; @@ -163,16 +162,12 @@ public void ExportToPQDS(StreamWriter writer, IQueryCollection requestParameters using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { - Event evt = (new TableOperations(connection)).QueryRecordWhere("ID = {0}", eventID); - Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); - // only get Single Voltage and Single Current Data for This.... List data = new List(); List metaData = new List(); VIDataGroup dataGroup = OpenSEEBaseController - .QueryVIDataGroupAsync(evt.ID, meter) + .QueryVIDataGroupAsync(eventID, connection) .GetAwaiter() .GetResult(); @@ -196,7 +191,8 @@ public void ExportToPQDS(StreamWriter writer, IQueryCollection requestParameters return; // Add MetaData Information - metaData = PQDSMetaData(evt, meter); + Event evt = (new TableOperations(connection)).QueryRecordWhere("ID = {0}", eventID); + metaData = PQDSMetaData(evt, connection); PQDS.PQDSFile file = new PQDS.PQDSFile(metaData, data, evt.StartTime); @@ -204,9 +200,12 @@ public void ExportToPQDS(StreamWriter writer, IQueryCollection requestParameters } } - private List PQDSMetaData(Event evt, Meter meter) + private List PQDSMetaData(Event evt, AdoDataConnection connection) { List result = new List(); + Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); + Asset asset = (new TableOperations(connection)).QueryRecordWhere("ID = {0}", evt.AssetID); + meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); result.Add(new PQDS.MetaDataTag("DeviceName", meter.Name)); result.Add(new PQDS.MetaDataTag("DeviceAlias", meter.ShortName)); @@ -215,85 +214,79 @@ public void ExportToPQDS(StreamWriter writer, IQueryCollection requestParameters result.Add(new PQDS.MetaDataTag("Latitude", Convert.ToString(meter.Location.Latitude))); result.Add(new PQDS.MetaDataTag("Longitude", Convert.ToString(meter.Location.Longitude))); - Asset asset; - double systemFrequency; - using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) - { - asset = (new TableOperations(connection)).QueryRecordWhere("ID = {0}", evt.AssetID); - systemFrequency = connection.ExecuteScalar("SELECT Value FROM Setting WHERE Name = 'SystemFrequency'") ?? 60.0; + double systemFrequency = connection.ExecuteScalar("SELECT Value FROM Setting WHERE Name = 'SystemFrequency'") ?? 60.0; - if (asset != null) - { - result.Add(new PQDS.MetaDataTag("NominalVoltage-LG", asset.VoltageKV)); - result.Add(new PQDS.MetaDataTag("NominalFrequency", systemFrequency)); - result.Add(new PQDS.MetaDataTag("AssetName", asset.AssetKey)); + if (asset != null) + { + result.Add(new PQDS.MetaDataTag("NominalVoltage-LG", asset.VoltageKV)); + result.Add(new PQDS.MetaDataTag("NominalFrequency", systemFrequency)); + result.Add(new PQDS.MetaDataTag("AssetName", asset.AssetKey)); - if (asset.AssetTypeID == connection.ExecuteScalar("SELECT ID FROM AssetType WHERE Name = 'Line'")) - result.Add(new PQDS.MetaDataTag("LineLength", connection.ExecuteScalar("SELECT Length FROM LineView WHERE ID = {0}", asset.ID))); - } + if (asset.AssetTypeID == connection.ExecuteScalar("SELECT ID FROM AssetType WHERE Name = 'Line'")) + result.Add(new PQDS.MetaDataTag("LineLength", connection.ExecuteScalar("SELECT Length FROM LineView WHERE ID = {0}", asset.ID))); + } - result.Add(new PQDS.MetaDataTag("EventID", evt.Name)); - result.Add(new PQDS.MetaDataTag("EventGUID", Guid.NewGuid().ToString())); - result.Add(new PQDS.MetaDataTag("EventDuration", (evt.EndTime - evt.StartTime).TotalMilliseconds)); - result.Add(new PQDS.MetaDataTag("EventTypeCode", PQDSEventTypeCode(evt.EventTypeID))); + result.Add(new PQDS.MetaDataTag("EventID", evt.Name)); + result.Add(new PQDS.MetaDataTag("EventGUID", Guid.NewGuid().ToString())); + result.Add(new PQDS.MetaDataTag("EventDuration", (evt.EndTime - evt.StartTime).TotalMilliseconds)); + result.Add(new PQDS.MetaDataTag("EventTypeCode", PQDSEventTypeCode(evt.EventTypeID))); - EventStat stat = (new TableOperations(connection)).QueryRecordWhere("EventID = {0}", evt.ID); + EventStat stat = (new TableOperations(connection)).QueryRecordWhere("EventID = {0}", evt.ID); - if (stat != null) + if (stat != null) + { + if (stat.VAMax != null) { - if (stat.VAMax != null) - { - result.Add(new PQDS.MetaDataTag("EventMaxVA", (double)stat.VAMax)); - } - if (stat.VBMax != null) - { - result.Add(new PQDS.MetaDataTag("EventMaxVB", (double)stat.VBMax)); - } - if (stat.VCMax != null) - { - result.Add(new PQDS.MetaDataTag("EventMaxVC", (double)stat.VCMax)); - } - if (stat.VAMin != null) - { - result.Add(new PQDS.MetaDataTag("EventMinVA", (double)stat.VAMin)); - } - if (stat.VBMin != null) - { - result.Add(new PQDS.MetaDataTag("EventMinVB", (double)stat.VBMin)); - } - if (stat.VCMin != null) - { - result.Add(new PQDS.MetaDataTag("EventMinVC", (double)stat.VCMin)); - } - - if (stat.IAMax != null) - { - result.Add(new PQDS.MetaDataTag("EventMaxIA", (double)stat.IAMax)); - } - if (stat.IBMax != null) - { - result.Add(new PQDS.MetaDataTag("EventMaxIB", (double)stat.IBMax)); - } - if (stat.ICMax != null) - { - result.Add(new PQDS.MetaDataTag("EventMaxIC", (double)stat.ICMax)); - } + result.Add(new PQDS.MetaDataTag("EventMaxVA", (double)stat.VAMax)); + } + if (stat.VBMax != null) + { + result.Add(new PQDS.MetaDataTag("EventMaxVB", (double)stat.VBMax)); + } + if (stat.VCMax != null) + { + result.Add(new PQDS.MetaDataTag("EventMaxVC", (double)stat.VCMax)); + } + if (stat.VAMin != null) + { + result.Add(new PQDS.MetaDataTag("EventMinVA", (double)stat.VAMin)); + } + if (stat.VBMin != null) + { + result.Add(new PQDS.MetaDataTag("EventMinVB", (double)stat.VBMin)); + } + if (stat.VCMin != null) + { + result.Add(new PQDS.MetaDataTag("EventMinVC", (double)stat.VCMin)); } - result.Add(new PQDS.MetaDataTag("EventYear", ((DateTime)evt.StartTime).Year)); - result.Add(new PQDS.MetaDataTag("EventMonth", (evt.StartTime).Month)); - result.Add(new PQDS.MetaDataTag("EventDay", (evt.StartTime).Day)); - result.Add(new PQDS.MetaDataTag("EventHour", (evt.StartTime).Hour)); - result.Add(new PQDS.MetaDataTag("EventMinute", (evt.StartTime).Minute)); - result.Add(new PQDS.MetaDataTag("EventSecond", (evt.StartTime).Second)); - result.Add(new PQDS.MetaDataTag("EventNanoSecond", Get_nanoseconds(evt.StartTime))); - - String date = String.Format("{0:D2}/{1:D2}/{2:D4}", (evt.StartTime).Month, (evt.StartTime).Day, (evt.StartTime).Year); - String time = String.Format("{0:D2}:{1:D2}:{2:D2}", (evt.StartTime).Hour, (evt.StartTime).Minute, (evt.StartTime).Second); - result.Add(new PQDS.MetaDataTag("EventDate", date)); - result.Add(new PQDS.MetaDataTag("EventTime", time)); + if (stat.IAMax != null) + { + result.Add(new PQDS.MetaDataTag("EventMaxIA", (double)stat.IAMax)); + } + if (stat.IBMax != null) + { + result.Add(new PQDS.MetaDataTag("EventMaxIB", (double)stat.IBMax)); + } + if (stat.ICMax != null) + { + result.Add(new PQDS.MetaDataTag("EventMaxIC", (double)stat.ICMax)); + } } + + result.Add(new PQDS.MetaDataTag("EventYear", ((DateTime)evt.StartTime).Year)); + result.Add(new PQDS.MetaDataTag("EventMonth", (evt.StartTime).Month)); + result.Add(new PQDS.MetaDataTag("EventDay", (evt.StartTime).Day)); + result.Add(new PQDS.MetaDataTag("EventHour", (evt.StartTime).Hour)); + result.Add(new PQDS.MetaDataTag("EventMinute", (evt.StartTime).Minute)); + result.Add(new PQDS.MetaDataTag("EventSecond", (evt.StartTime).Second)); + result.Add(new PQDS.MetaDataTag("EventNanoSecond", Get_nanoseconds(evt.StartTime))); + + String date = String.Format("{0:D2}/{1:D2}/{2:D4}", (evt.StartTime).Month, (evt.StartTime).Day, (evt.StartTime).Year); + String time = String.Format("{0:D2}:{1:D2}:{2:D2}", (evt.StartTime).Hour, (evt.StartTime).Minute, (evt.StartTime).Second); + result.Add(new PQDS.MetaDataTag("EventDate", date)); + result.Add(new PQDS.MetaDataTag("EventTime", time)); return result; } @@ -381,14 +374,10 @@ public void ExportFFTToCSV(StreamWriter writer, IQueryCollection requestParamete double startTime = requestParameters["startDate"].Any() ? double.Parse(requestParameters["startDate"].ToString()) : 0.0; int cycles = requestParameters["cycles"].Any() ? int.Parse(requestParameters["cycles"].ToString()) : 0; - Event evt = (new TableOperations(connection)).QueryRecordWhere("ID = {0}", eventId); - Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); - AnalyticController ctrl = new AnalyticController(); DataGroup dataGroup = OpenSEEBaseController - .QueryDataGroupAsync(evt.ID, meter) + .QueryDataGroupAsync(eventId, connection) .GetAwaiter() .GetResult(); @@ -479,30 +468,44 @@ public List BuildDataSeries(IQueryCollection requestParameters) using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { - Event evt = (new TableOperations(connection)).QueryRecordWhere("ID = {0}", eventID); - Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); + DataGroup dataGroup = OpenSEEBaseController + .QueryDataGroupAsync(eventID, connection) + .GetAwaiter() + .GetResult(); + + VICycleDataGroup viCycleDataGroup = OpenSEEBaseController + .QueryVICycleDataGroupAsync(eventID, connection) + .GetAwaiter() + .GetResult(); + + Lazy lazyVIDataGroup = new Lazy(() => + { + return OpenSEEBaseController + .QueryVIDataGroupAsync(eventID, connection) + .GetAwaiter() + .GetResult(); + }); IEnumerable returnList = new List(); if (displayVolt) - returnList = returnList.Concat(QueryVoltageData(meter, evt)); + returnList = returnList.Concat(QueryVoltageData(dataGroup, viCycleDataGroup)); if(displayCur) - returnList = returnList.Concat(QueryCurrentData(meter, evt)); + returnList = returnList.Concat(QueryCurrentData(dataGroup, viCycleDataGroup)); if(displayTCE) - returnList = returnList.Concat(QueryTCEData(meter, evt)); + returnList = returnList.Concat(QueryTCEData(dataGroup)); if (displayAnalogs) - returnList = returnList.Concat(QueryAnalogData(meter, evt)); + returnList = returnList.Concat(QueryAnalogData(dataGroup)); if (breakerdigitals) - returnList = returnList.Concat(QueryDigitalData(meter, evt)); + returnList = returnList.Concat(QueryDigitalData(dataGroup)); foreach (var analytics in displayAnalytics) { if (!string.IsNullOrEmpty(analytics)) - returnList = returnList.Concat(QueryAnalyticData(meter, evt, analytics, lpfOrder, hpfOrder, Trc, harmonic)); + returnList = returnList.Concat(QueryAnalyticData(dataGroup, viCycleDataGroup, lazyVIDataGroup, analytics, lpfOrder, hpfOrder, Trc, harmonic)); } returnList = AlignData(returnList.ToList()); @@ -511,71 +514,48 @@ public List BuildDataSeries(IQueryCollection requestParameters) } } - private List QueryAnalyticData(Meter meter, Event evt, string analytic, int lowPassOrder, int highPassOrder, double Trc, int harmonic) + private List QueryAnalyticData(DataGroup dataGroup, VICycleDataGroup viCycleData, Lazy lazyVIDataGroup, string analytic, int lowPassOrder, int highPassOrder, double Trc, int harmonic) { - Lazy lazyDataGroup = new Lazy(() => - { - return OpenSEEBaseController - .QueryDataGroupAsync(evt.ID, meter) - .GetAwaiter() - .GetResult(); - }); - - Lazy lazyVIDataGroup = new Lazy(() => - { - return OpenSEEBaseController - .QueryVIDataGroupAsync(evt.ID, meter) - .GetAwaiter() - .GetResult(); - }); - - Lazy lazyVICycleDataGroup = new Lazy(() => - { - return OpenSEEBaseController - .QueryVICycleDataGroupAsync(evt.ID, meter) - .GetAwaiter() - .GetResult(); - }); AnalyticController controller = new AnalyticController(); if (analytic == "FirstDerivative") - return controller.GetFirstDerivativeLookup(lazyDataGroup.Value, lazyVICycleDataGroup.Value); + return controller.GetFirstDerivativeLookup(dataGroup, viCycleData); if (analytic == "ClippedWaveforms") - return controller.GetClippedWaveformsLookup(lazyDataGroup.Value); + return controller.GetClippedWaveformsLookup(dataGroup); if (analytic == "Frequency") return controller.GetFrequencyLookup(lazyVIDataGroup.Value); if (analytic == "Impedance") - return controller.GetImpedanceLookup(lazyVICycleDataGroup.Value); + return controller.GetImpedanceLookup(viCycleData); if (analytic == "Power") - return controller.GetPowerLookup(lazyVICycleDataGroup.Value); + return controller.GetPowerLookup(viCycleData); if (analytic == "RemoveCurrent") - return controller.GetRemoveCurrentLookup(lazyDataGroup.Value); + return controller.GetRemoveCurrentLookup(dataGroup); if (analytic == "MissingVoltage") - return controller.GetMissingVoltageLookup(lazyDataGroup.Value); + return controller.GetMissingVoltageLookup(dataGroup); if (analytic == "LowPassFilter") - return controller.GetLowPassFilterLookup(lazyDataGroup.Value, lowPassOrder); + return controller.GetLowPassFilterLookup(dataGroup, lowPassOrder); if (analytic == "HighPassFilter") - return controller.GetHighPassFilterLookup(lazyDataGroup.Value, highPassOrder); + return controller.GetHighPassFilterLookup(dataGroup, highPassOrder); if (analytic == "SymmetricalComponents") - return controller.GetSymmetricalComponentsLookup(lazyVICycleDataGroup.Value); + return controller.GetSymmetricalComponentsLookup(viCycleData); if (analytic == "Unbalance") - return controller.GetUnbalanceLookup(lazyVICycleDataGroup.Value); + return controller.GetUnbalanceLookup(viCycleData); if (analytic == "Rectifier") return controller.GetRectifierLookup(lazyVIDataGroup.Value, Trc); if (analytic == "RapidVoltageChange") - return controller.GetRapidVoltageChangeLookup(lazyVICycleDataGroup.Value); + return controller.GetRapidVoltageChangeLookup(viCycleData); if (analytic == "THD") - return controller.GetTHDLookup(lazyDataGroup.Value, true); + return controller.GetTHDLookup(dataGroup, true); if (analytic == "SpecifiedHarmonic") - return controller.GetSpecifiedHarmonicLookup(lazyDataGroup.Value, harmonic, true); + return controller.GetSpecifiedHarmonicLookup(dataGroup, harmonic, true); if (analytic == "OverlappingWaveform") - return controller.GetOverlappingWaveformLookup(lazyDataGroup.Value); + return controller.GetOverlappingWaveformLookup(dataGroup); return new List(); } - private List QueryVoltageData(Meter meter, Event evt) + private List QueryVoltageData(DataGroup dataGroup, VICycleDataGroup viCycleDataGroup) { bool useLL; using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) @@ -583,11 +563,6 @@ private List QueryVoltageData(Meter meter, Event evt) useLL = bool.Parse(new TableOperations(connection).QueryRecordWhere("Name = {0}", "useLLVoltage")?.Value ?? bool.FalseString); } - DataGroup dataGroup = OpenSEEBaseController - .QueryDataGroupAsync(evt.ID, meter) - .GetAwaiter() - .GetResult(); - List WaveForm = dataGroup.DataSeries.Where(ds => ds.SeriesInfo.Channel.MeasurementType.Name == "Voltage" && ( (useLL && !(ds.SeriesInfo.Channel.Phase.Name == "AB" || ds.SeriesInfo.Channel.Phase.Name == "BC" || ds.SeriesInfo.Channel.Phase.Name == "CA")) || (!useLL && (ds.SeriesInfo.Channel.Phase.Name == "AB" || ds.SeriesInfo.Channel.Phase.Name == "BC" || ds.SeriesInfo.Channel.Phase.Name == "CA"))) @@ -608,11 +583,6 @@ private List QueryVoltageData(Meter meter, Event evt) return a.LegendGroup.CompareTo(b.LegendGroup); }); - VICycleDataGroup viCycleDataGroup = OpenSEEBaseController - .QueryVICycleDataGroupAsync(evt.ID, meter) - .GetAwaiter() - .GetResult(); - List result = new List(); foreach(D3Series w in WaveForm) @@ -644,12 +614,8 @@ private List QueryVoltageData(Meter meter, Event evt) return result; } - private List QueryCurrentData(Meter meter, Event evt) + private List QueryCurrentData(DataGroup dataGroup, VICycleDataGroup viCycleDataGroup) { - DataGroup dataGroup = OpenSEEBaseController - .QueryDataGroupAsync(evt.ID, meter) - .GetAwaiter() - .GetResult(); List WaveForm = dataGroup.DataSeries.Where(ds => ds.SeriesInfo.Channel.MeasurementType.Name == "Current" ).Select( @@ -669,11 +635,6 @@ private List QueryCurrentData(Meter meter, Event evt) return a.LegendGroup.CompareTo(b.LegendGroup); }); - VICycleDataGroup viCycleDataGroup = OpenSEEBaseController - .QueryVICycleDataGroupAsync(evt.ID, meter) - .GetAwaiter() - .GetResult(); - List result = new List(); foreach (D3Series w in WaveForm) @@ -705,44 +666,34 @@ private List QueryCurrentData(Meter meter, Event evt) return result; } - private List QueryTCEData(Meter meter, Event evt) + private List QueryTCEData(DataGroup dataGroup) { - DataGroup dataGroup = OpenSEEBaseController - .QueryDataGroupAsync(evt.ID, meter) - .GetAwaiter() - .GetResult(); - - List result = dataGroup.DataSeries.Where(ds => ds.SeriesInfo.Channel.MeasurementType.Name == "TripCoilCurrent" - ).Select( + return dataGroup.DataSeries + .Where(ds => ds.SeriesInfo.Channel.MeasurementType.Name == "TripCoilCurrent") + .Select( ds => new D3Series() { ChannelID = ds.SeriesInfo.Channel.ID, ChartLabel = OpenSEEBaseController.GetChartLabel(ds.SeriesInfo.Channel), LegendGroup = ds.SeriesInfo.Channel.Asset.AssetName, DataPoints = ds.DataPoints.Select(dataPoint => new double[] { dataPoint.Time.Subtract(m_epoch).TotalMilliseconds, dataPoint.Value }).ToList(), - }).ToList(); - - return result; + }) + .ToList(); } - private List QueryDigitalData(Meter meter, Event evt) + private List QueryDigitalData(DataGroup dataGroup) { - DataGroup dataGroup = OpenSEEBaseController - .QueryDataGroupAsync(evt.ID, meter) - .GetAwaiter() - .GetResult(); - - List result = dataGroup.DataSeries.Where(ds => ds.SeriesInfo.Channel.MeasurementType.Name == "Digital" - ).Select( + return dataGroup.DataSeries + .Where(ds => ds.SeriesInfo.Channel.MeasurementType.Name == "Digital") + .Select( ds => new D3Series() { ChannelID = ds.SeriesInfo.Channel.ID, ChartLabel = OpenSEEController.GetChartLabel(ds.SeriesInfo.Channel), LegendGroup = ds.SeriesInfo.Channel.Asset.AssetName, DataPoints = ds.DataPoints.Select(dataPoint => new double[] { dataPoint.Time.Subtract(m_epoch).TotalMilliseconds, dataPoint.Value }).ToList(), - }).ToList(); - - return result; + }) + .ToList(); } private List AlignData(List data) @@ -774,32 +725,25 @@ private List AlignData(List data) return item; }); - return result.ToList(); - } - private List QueryAnalogData(Meter meter, Event evt) + private List QueryAnalogData(DataGroup dataGroup) { - DataGroup dataGroup = OpenSEEBaseController - .QueryDataGroupAsync(evt.ID, meter) - .GetAwaiter() - .GetResult(); - - List dataLookup = dataGroup.DataSeries.Where(ds => - ds.SeriesInfo.Channel.MeasurementType.Name != "Digital" && - ds.SeriesInfo.Channel.MeasurementType.Name != "Voltage" && - ds.SeriesInfo.Channel.MeasurementType.Name != "Current" && - ds.SeriesInfo.Channel.MeasurementType.Name != "TripCoilCurrent").Select(ds => - new D3Series() - { - ChannelID = ds.SeriesInfo.Channel.ID, - ChartLabel = ds.SeriesInfo.Channel.Description ?? OpenSEEBaseController.GetChartLabel(ds.SeriesInfo.Channel), - LegendGroup = ds.SeriesInfo.Channel.Asset.AssetName, - DataPoints = ds.DataPoints.Select(dataPoint => new double[] { dataPoint.Time.Subtract(m_epoch).TotalMilliseconds, dataPoint.Value }).ToList(), - }).ToList(); - - return dataLookup; + return dataGroup.DataSeries + .Where(ds => + ds.SeriesInfo.Channel.MeasurementType.Name != "Digital" && + ds.SeriesInfo.Channel.MeasurementType.Name != "Voltage" && + ds.SeriesInfo.Channel.MeasurementType.Name != "Current" && + ds.SeriesInfo.Channel.MeasurementType.Name != "TripCoilCurrent").Select(ds => + new D3Series() + { + ChannelID = ds.SeriesInfo.Channel.ID, + ChartLabel = ds.SeriesInfo.Channel.Description ?? OpenSEEBaseController.GetChartLabel(ds.SeriesInfo.Channel), + LegendGroup = ds.SeriesInfo.Channel.Asset.AssetName, + DataPoints = ds.DataPoints.Select(dataPoint => new double[] { dataPoint.Time.Subtract(m_epoch).TotalMilliseconds, dataPoint.Value }).ToList(), + } + ).ToList(); } #endregion diff --git a/src/OpenSEE/Controllers/OpenSEEController.cs b/src/OpenSEE/Controllers/OpenSEEController.cs index 1352d522..79f76cc7 100644 --- a/src/OpenSEE/Controllers/OpenSEEController.cs +++ b/src/OpenSEE/Controllers/OpenSEEController.cs @@ -139,19 +139,17 @@ public async Task GetData() dbgNocompressNum != 0; Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); - Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); List returnList = new List(); if (dataType == "Time") { - DataGroup dataGroup = await QueryDataGroupAsync(eventId, meter); + DataGroup dataGroup = await QueryDataGroupAsync(eventId, connection); returnList = GetD3DataLookup(dataGroup, type, evt.ID); } else { - VICycleDataGroup viCycleDataGroup = await QueryVICycleDataGroupAsync(eventId, meter, !dbgNocompress); + VICycleDataGroup viCycleDataGroup = await QueryVICycleDataGroupAsync(eventId, connection, !dbgNocompress); returnList = GetD3FrequencyDataLookup(viCycleDataGroup, type, !forceFullRes); } @@ -353,10 +351,7 @@ public async Task GetBreakerData() int eventId = int.Parse(Request.Query["eventId"].ToString()); Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); - Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); - - DataGroup dataGroup = await QueryDataGroupAsync(evt.ID, meter); + DataGroup dataGroup = await QueryDataGroupAsync(evt.ID, connection); List resultList = GetBreakerLookup(dataGroup); JsonReturn returnDict = new JsonReturn(); @@ -409,12 +404,7 @@ public async Task GetAnalogsData() using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { int eventId = int.Parse(Request.Query["eventId"].ToString()); - - Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventId); - Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); - meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); - - DataGroup dataGroup = await QueryDataGroupAsync(evt.ID, meter); + DataGroup dataGroup = await QueryDataGroupAsync(eventId, connection); List returnList = GetAnalogsLookup(dataGroup); JsonReturn returnDict = new JsonReturn(); diff --git a/src/OpenSEE/Controllers/OpenSeeControllerBase.cs b/src/OpenSEE/Controllers/OpenSeeControllerBase.cs index c45a662f..990a7484 100644 --- a/src/OpenSEE/Controllers/OpenSeeControllerBase.cs +++ b/src/OpenSEE/Controllers/OpenSeeControllerBase.cs @@ -29,6 +29,7 @@ using FaultData.DataAnalysis; using Gemstone.Configuration; using Gemstone.Data; +using Gemstone.Data.Model; using Gemstone.Numeric.Interpolation; using Microsoft.AspNetCore.Mvc; using OpenSEE.Model; @@ -409,7 +410,7 @@ public static void DownSample(JsonReturn dict) #region [ Shared Functions ] - public static async Task QueryDataGroupAsync(int eventID, Meter meter) + public static async Task QueryDataGroupAsync(int eventID, AdoDataConnection connection) { string target = $"DataGroup-{eventID}"; @@ -424,8 +425,14 @@ public static async Task QueryDataGroupAsync(int eventID, Meter meter try { + Event evt = new TableOperations(connection).QueryRecordWhere("ID = {0}", eventID); + Meter meter = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.MeterID); + meter.ConnectionFactory = () => new AdoDataConnection(Settings.Default); + Asset asset = new TableOperations(connection).QueryRecordWhere("ID = {0}", evt.AssetID); + asset.ConnectionFactory = () => new AdoDataConnection(Settings.Default); + List data = ChannelData.DataFromEvent(eventID, () => new AdoDataConnection(Settings.Default)); - DataGroup dataGroup = ToDataGroup(meter, data); + DataGroup dataGroup = ToDataGroup(meter, asset, data); taskCompletionSource.SetResult(dataGroup); return dataGroup; } @@ -437,7 +444,7 @@ public static async Task QueryDataGroupAsync(int eventID, Meter meter } } - public static async Task QueryVIDataGroupAsync(int eventID, Meter meter) + public static async Task QueryVIDataGroupAsync(int eventID, AdoDataConnection connection) { string target = $"VIDataGroup-{eventID}"; @@ -452,7 +459,7 @@ public static async Task QueryVIDataGroupAsync(int eventID, Meter m try { - DataGroup dataGroup = await QueryDataGroupAsync(eventID, meter); + DataGroup dataGroup = await QueryDataGroupAsync(eventID, connection); VIDataGroup viDataGroup = new VIDataGroup(dataGroup); taskCompletionSource.SetResult(viDataGroup); return viDataGroup; @@ -465,7 +472,7 @@ public static async Task QueryVIDataGroupAsync(int eventID, Meter m } } - public static async Task QueryVICycleDataGroupAsync(int eventID, Meter meter, bool compress = true) + public static async Task QueryVICycleDataGroupAsync(int eventID, AdoDataConnection connection, bool compress = true) { string compression = compress ? "compressed" : "uncompressed"; string target = $"VICycleDataGroup-{eventID}-{compression}"; @@ -481,7 +488,7 @@ public static async Task QueryVICycleDataGroupAsync(int eventI try { - VIDataGroup viDataGroup = await QueryVIDataGroupAsync(eventID, meter); + VIDataGroup viDataGroup = await QueryVIDataGroupAsync(eventID, connection); VICycleDataGroup viCycleDataGroup = Transform.ToVICycleDataGroup(viDataGroup, Fbase, compress); taskCompletionSource.SetResult(viCycleDataGroup); return viCycleDataGroup; @@ -494,9 +501,9 @@ public static async Task QueryVICycleDataGroupAsync(int eventI } } - public static DataGroup ToDataGroup(Meter meter, List data) + public static DataGroup ToDataGroup(Meter meter, Asset asset, List data) { - DataGroup dataGroup = new DataGroup(); + DataGroup dataGroup = new DataGroup(asset); dataGroup.FromData(meter, data); VIDataGroup vIDataGroup = new VIDataGroup(dataGroup); return vIDataGroup.ToDataGroup(); From f96a14b3b32562032d269d9566463d4cbe9a1527 Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Thu, 5 Feb 2026 16:14:51 -0500 Subject: [PATCH 44/58] update to new uri for download --- .../wwwroot/Scripts/TSX/Components/OpenSEENavbar.tsx | 10 +++++----- src/OpenSEE/wwwroot/Scripts/TSX/openSEE.tsx | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/OpenSEE/wwwroot/Scripts/TSX/Components/OpenSEENavbar.tsx b/src/OpenSEE/wwwroot/Scripts/TSX/Components/OpenSEENavbar.tsx index 05464fcc..5eb985b2 100644 --- a/src/OpenSEE/wwwroot/Scripts/TSX/Components/OpenSEENavbar.tsx +++ b/src/OpenSEE/wwwroot/Scripts/TSX/Components/OpenSEENavbar.tsx @@ -82,10 +82,10 @@ const OpenSeeNavBar = (props: IProps) => { } return () => { } - }, [props.OpenDrawers.AccumulatedPoints]) + }, [props.OpenDrawers.AccumulatedPoints]); function exportData(type) { - window.open(homePath + `CSVDownload.ashx?type=${type}&eventID=${eventID}` + + const uri = homePath + `api/CSV/Download?type=${type}&eventID=${eventID}` + `${showPlots.Voltage != undefined ? `&displayVolt=${showPlots.Voltage}` : ``}` + `${showPlots.Current != undefined ? `&displayCur=${showPlots.Current}` : ``}` + `${showPlots.TripCoil != undefined ? `&displayTCE=${showPlots.TripCoil}` : ``}` + @@ -99,9 +99,9 @@ const OpenSeeNavBar = (props: IProps) => { `${type == 'fft' ? `&startDate=${fftTime[0]}` : ``}` + `${type == 'fft' ? `&cycles=${cycles}` : ``}` + `&Meter=${eventInfo.MeterName}` + - `&EventType=${eventInfo.EventName}` - ); - } + `&EventType=${eventInfo.EventName}`; + window.open(uri, '_blank'); + }; return ( <> diff --git a/src/OpenSEE/wwwroot/Scripts/TSX/openSEE.tsx b/src/OpenSEE/wwwroot/Scripts/TSX/openSEE.tsx index 939fb990..514d6d6a 100644 --- a/src/OpenSEE/wwwroot/Scripts/TSX/openSEE.tsx +++ b/src/OpenSEE/wwwroot/Scripts/TSX/openSEE.tsx @@ -217,7 +217,7 @@ const OpenSeeHome = () => { }; function exportData(type) { - window.open(homePath + `CSVDownload.ashx?type=${type}&eventID=${eventID}` + + const uri = homePath + `api/CSV/Download?type=${type}&eventID=${eventID}` + `${showPlots.Voltage != undefined ? `&displayVolt=${showPlots.Voltage}` : ``}` + `${showPlots.Current != undefined ? `&displayCur=${showPlots.Current}` : ``}` + `${showPlots.TripCoil != undefined ? `&displayTCE=${showPlots.TripCoil}` : ``}` + @@ -226,8 +226,8 @@ const OpenSeeHome = () => { `${type == 'fft' ? `&startDate=${fftTime[0]}` : ``}` + `${type == 'fft' ? `&cycles=${cycles}` : ``}` + `&Meter=${eventInfo.MeterName}` + - `&EventType=${eventInfo.MeterName}` - ); + `&EventType=${eventInfo.MeterName}`; + window.open(uri, "_blank"); } return ( From 2a714bac1c67e694ff750c426bb800297dec424e Mon Sep 17 00:00:00 2001 From: Gabriel Santos Date: Thu, 5 Feb 2026 16:16:00 -0500 Subject: [PATCH 45/58] change bootstrap to be npm loaded gemstone is on 3.x, which can cause issues with code written in gpa gemstone --- src/OpenSEE/Views/Home/Index.cshtml | 1 - src/OpenSEE/package.json | 1 + src/OpenSEE/webpack.config.js | 1 - src/OpenSEE/wwwroot/Scripts/TSX/openSEE.tsx | 1 + 4 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OpenSEE/Views/Home/Index.cshtml b/src/OpenSEE/Views/Home/Index.cshtml index 1abff3c7..74834390 100644 --- a/src/OpenSEE/Views/Home/Index.cshtml +++ b/src/OpenSEE/Views/Home/Index.cshtml @@ -51,7 +51,6 @@ - - - - - -
- - - - - - - - - - - - - -
<%=postedMeterName %>
Fault Type:    <%=postedFaultType %>
Start Time:    <%=postedStartTime %>
Inception Time:    <%=postedInceptionTime %>
Delta Time:    <%=postedDeltaTime %>
Fault Duration:    <%=postedDurationPeriod %>
Fault Current:    <%=postedFaultCurrent %>
Distance Method:    <%=postedDistanceMethod %>
Single-ended Distance:    <%=postedSingleEndedDistance %>
Double-ended Distance:    <%=postedDoubleEndedDistance %>
Double-ended Angle:    <%=postedDoubleEndedConfidence %>
OpenXDA EventID:    <%=postedEventId %>
-
- - \ No newline at end of file diff --git a/src/OpenSEE/FaultSpecifics.aspx.cs b/src/OpenSEE/FaultSpecifics.aspx.cs deleted file mode 100644 index 82c9d666..00000000 --- a/src/OpenSEE/FaultSpecifics.aspx.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using System.Data; -using System.Data.SqlClient; -using System.Globalization; -using System.Linq; -using Gemstone.Configuration; -using Gemstone.Data; -using Gemstone.Data.Model; -using System.Web.UI; -using openXDA.Model; -public partial class FaultSpecifics : Page -{ - public string postedFaultType = ""; - public string postedDeltaTime = ""; - public string postedStartTime = ""; - public string postedInceptionTime = ""; - public string postedDurationPeriod = ""; - public string postedFaultCurrent = ""; - public string postedDistanceMethod = ""; - public string postedSingleEndedDistance = ""; - public string postedEventId = ""; - public string postedMeterId = ""; - public string postedMeterName = ""; - public string postedDoubleEndedDistance = ""; - public string postedDoubleEndedConfidence = ""; - public string postedExceptionMessage = ""; - - string connectionstring = ConfigurationFile.Current.Settings[Settings.Default]["ConnectionString"].Value; - - protected void Page_Load(object sender, EventArgs e) - { - SqlConnection conn = null; - SqlDataReader rdr = null; - - if (!IsPostBack) - { - if (Request["eventId"] != null) - { - postedEventId = Request["eventId"]; - - using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) - { - try - { - Event theevent = (new TableOperations(connection)).QueryRecordWhere("ID = {0}", Convert.ToInt32(postedEventId)); - FaultSummary thesummary = (new TableOperations(connection)).QueryRecordWhere("EventID = {0} AND IsSelectedAlgorithm = 1", Convert.ToInt32(postedEventId)); - - if ((object)thesummary == null) - { - postedFaultType = "Invalid"; - postedInceptionTime = "Invalid"; - postedDurationPeriod = "Invalid"; - postedFaultCurrent = "Invalid"; - postedDistanceMethod = "Invalid"; - postedSingleEndedDistance = "Invalid"; - postedDeltaTime = "Invalid"; - postedDoubleEndedDistance = "Invalid"; - postedDoubleEndedConfidence = "Invalid"; - return; - } - - postedFaultType = thesummary.FaultType; - postedInceptionTime = thesummary.Inception.TimeOfDay.ToString(); - postedDurationPeriod = (thesummary.DurationSeconds * 1000).ToString("##.###", CultureInfo.InvariantCulture) + "msec (" + thesummary.DurationCycles.ToString("##.##", CultureInfo.InvariantCulture) + " cycles)"; - postedFaultCurrent = thesummary.CurrentMagnitude.ToString("####.#", CultureInfo.InvariantCulture) + " Amps (RMS)"; - postedDistanceMethod = thesummary.Algorithm; - postedSingleEndedDistance = thesummary.Distance.ToString("####.###", CultureInfo.InvariantCulture) + " miles"; - double deltatime = (thesummary.Inception - theevent.StartTime).Ticks / 10000000.0; - postedDeltaTime = deltatime.ToString(); - postedStartTime = theevent.StartTime.TimeOfDay.ToString(); - postedMeterName = connection.ExecuteScalar("SELECT Name From Meter WHERE ID = {0}", theevent.MeterID); - postedMeterId = theevent.MeterID.ToString(); - - conn = new SqlConnection(connectionstring); - conn.Open(); - SqlCommand cmd = new SqlCommand("dbo.selectDoubleEndedFaultDistanceForEventID", conn); - cmd.CommandType = CommandType.StoredProcedure; - cmd.Parameters.Add(new SqlParameter("@EventID", postedEventId)); - cmd.CommandTimeout = 300; - rdr = cmd.ExecuteReader(); - - if (rdr.HasRows) - { - while (rdr.Read()) - { - postedDoubleEndedDistance = ((double)rdr["Distance"]).ToString("####.###", CultureInfo.InvariantCulture) + " miles"; - postedDoubleEndedConfidence = ((double)rdr["Angle"]).ToString("####.####", CultureInfo.InvariantCulture) + " degrees"; - } - } - } - catch (Exception ex) - { - postedExceptionMessage = ex.Message; - } - finally - { - if (rdr != null) - rdr.Dispose(); - - if (conn != null) - conn.Dispose(); - } - } - } - } - } -} \ No newline at end of file diff --git a/src/OpenSEE/Model/FaultSpecifics.cs b/src/OpenSEE/Model/FaultSpecifics.cs new file mode 100644 index 00000000..86817876 --- /dev/null +++ b/src/OpenSEE/Model/FaultSpecifics.cs @@ -0,0 +1,52 @@ +//****************************************************************************************************** +// SystemSettings.cs - Gbtc +// +// Copyright © 2020, 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://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: +// ---------------------------------------------------------------------------------------------------- +// 01/23/2026 - Billy Ernest +// Generated original version of source code. +// +//****************************************************************************************************** + +using System; +using System.Data; +using Gemstone.Data; +using Gemstone.Data.Model; + +namespace OpenSEE.Models +{ + [TableName("openSee.FaultSpecifics"), UseEscapedName] + public class FaultSpecifics + { + [PrimaryKey(true)] + public int ID { get; set; } + public string FaultType { get; set; } + [FieldDataType(DbType.DateTime2, DatabaseType.SQLServer)] + public DateTime Inception { get; set; } + public double DurationMs { get; set; } + public double DurationCycles { get; set; } + public double DeltaTime { get; set; } + public double CurrentMagnitude { get; set; } + public string Algorithm { get; set; } + public double Distance { get; set; } + public double DoubleFaultDistance { get; set; } + public double DoubleFaultAngle { get; set; } + [FieldDataType(DbType.DateTime2, DatabaseType.SQLServer)] + public DateTime StartTime { get; set; } + public string MeterName { get; set; } + + } +} diff --git a/src/OpenSEE/OpenSEE.csproj b/src/OpenSEE/OpenSEE.csproj index ea847e2b..c2804904 100644 --- a/src/OpenSEE/OpenSEE.csproj +++ b/src/OpenSEE/OpenSEE.csproj @@ -34,12 +34,6 @@ if $(ConfigurationName) == Release npm run buildrelease
- - - FaultSpecifics.aspx - ASPXCodeBehind - - diff --git a/src/OpenSEE/wwwroot/Content/FaultSpecifics.css b/src/OpenSEE/wwwroot/Content/FaultSpecifics.css deleted file mode 100644 index cc9d3137..00000000 --- a/src/OpenSEE/wwwroot/Content/FaultSpecifics.css +++ /dev/null @@ -1,37 +0,0 @@ -/****************************************************************************************************** -// FaultSpecifics.css - Gbtc -// -// Copyright © 2014, 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 Eclipse Public License -v 1.0 (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/eclipse-1.0.php -// -// 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: -// ---------------------------------------------------------------------------------------------------- -// 04/14/2015 - Jeff Walker -// Generated original version of source code. -// -//******************************************************************************************************/ - - body { - margin: 0; - padding: 0; - height: 100%; - width: 100%; - overflow: hidden; - -webkit-user-select: none; - -moz-user-select: -moz-none; - -ms-user-select: none; - user-select: none; - font-size: 12px; - font-family: Lucida Grande, Lucida Sans, Arial, sans-serif; - } -