From fdc8b16f05f93ee25ee07ae5e77fd2b160edc5ba Mon Sep 17 00:00:00 2001 From: prestoncraw Date: Mon, 3 Aug 2026 16:35:37 -0400 Subject: [PATCH 1/4] Migrate libraries to PQDigest --- Dependencies/OpenXDA/FaultAlgorithms.dll | Bin 0 -> 15360 bytes Dependencies/OpenXDA/FaultData.dll | Bin 0 -> 54272 bytes .../OpenXDA/openHistorian.XDALink.dll | Bin 0 -> 18432 bytes .../OpenXDA/openXDA.APIAuthentication.dll | Bin 0 -> 26624 bytes .../OpenXDA/openXDA.Configuration.dll | Bin 0 -> 44032 bytes Dependencies/OpenXDA/openXDA.Model.dll | Bin 0 -> 178176 bytes Dependencies/OpenXDA/openXDA.PQI.dll | Bin 0 -> 66048 bytes Dependencies/OpenXDA/openXDA.Reports.dll | Bin 0 -> 164352 bytes Libraries/FaultAlgorithms/Conductor.cs | 122 ---- Libraries/FaultAlgorithms/Cycle.cs | 200 ------ Libraries/FaultAlgorithms/CycleData.cs | 177 ----- Libraries/FaultAlgorithms/CycleDataSet.cs | 303 -------- .../FaultAlgorithms/FaultAlgorithms.csproj | 15 - Libraries/FaultAlgorithms/MeasurementData.cs | 97 --- .../FaultAlgorithms/MeasurementDataSet.cs | 272 ------- .../FaultData/DataAnalysis/CycleDataGroup.cs | 115 --- Libraries/FaultData/DataAnalysis/DataGroup.cs | 662 ------------------ .../FaultData/DataAnalysis/DataSeries.cs | 588 ---------------- .../DataAnalysis/ReportedDisturbance.cs | 58 -- Libraries/FaultData/DataAnalysis/Transform.cs | 358 ---------- .../DataAnalysis/VICycleDataGroup.cs | 476 ------------- .../FaultData/DataAnalysis/VIDataGroup.cs | 521 -------------- Libraries/FaultData/FaultData.csproj | 22 - .../openXDA.Configuration.csproj | 6 +- Libraries/openXDA.Model/Channels/Channel.cs | 629 ----------------- .../openXDA.Model/Channels/ChannelData.cs | 649 ----------------- .../openXDA.Model/Channels/ChannelGroup.cs | 43 -- Libraries/openXDA.Model/Channels/DataPoint.cs | 110 --- .../Channels/MeasurementCharacteristic.cs | 45 -- .../openXDA.Model/Channels/MeasurementType.cs | 44 -- Libraries/openXDA.Model/Channels/Phase.cs | 43 -- Libraries/openXDA.Model/Channels/Series.cs | 248 ------- .../openXDA.Model/Channels/SeriesType.cs | 40 -- .../DERAnalytic/DERAnalyticResult.cs | 54 -- .../openXDA.Model/Events/BreakerRestrike.cs | 65 -- .../Events/Disturbances/Disturbance.cs | 67 -- Libraries/openXDA.Model/Events/Event.cs | 91 --- Libraries/openXDA.Model/Events/EventStat.cs | 57 -- Libraries/openXDA.Model/Events/EventType.cs | 42 -- .../openXDA.Model/Events/Faults/Fault.cs | 137 ---- .../openXDA.Model/Events/Faults/FaultCurve.cs | 250 ------- .../openXDA.Model/Events/RelayPerformance.cs | 63 -- Libraries/openXDA.Model/Files/DataFile.cs | 93 --- Libraries/openXDA.Model/Files/FileBlob.cs | 38 - Libraries/openXDA.Model/Files/FileGroup.cs | 102 --- .../openXDA.Model/Files/FileGroupField.cs | 61 -- .../Files/FileGroupFieldValue.cs | 39 -- Libraries/openXDA.Model/LazyContext.cs | 369 ---------- .../openXDA.Model/Links/AssetConnection.cs | 176 ----- Libraries/openXDA.Model/Links/MeterLine.cs | 174 ----- .../openXDA.Model/Links/MeterLocationLine.cs | 192 ----- Libraries/openXDA.Model/Meters/AssetGroup.cs | 60 -- Libraries/openXDA.Model/Meters/Location.cs | 509 -------------- Libraries/openXDA.Model/Meters/Meter.cs | 338 --------- Libraries/openXDA.Model/Note.cs | 63 -- .../PQDigest/HomeScreenWidget.cs | 44 -- Libraries/openXDA.Model/PQDigest/Widget.cs | 51 -- Libraries/openXDA.Model/Reports/Report.cs | 46 -- .../openXDA.Model/SEBrowser/DetailedSeries.cs | 37 - .../openXDA.Model/SEBrowser/TrendChannel.cs | 48 -- Libraries/openXDA.Model/SEBrowser/Widget.cs | 61 -- .../openXDA.Model/SEBrowser/WidgetCategory.cs | 52 -- .../openXDA.Model/SearchRestrictionHelper.cs | 55 -- .../Settings/BreakerReportSettings.cs | 64 -- .../openXDA.Model/Settings/OpenSEESetting.cs | 33 - .../openXDA.Model/Settings/PQDigestSetting.cs | 33 - .../Settings/PQReportsSettings.cs | 116 --- Libraries/openXDA.Model/Settings/Setting.cs | 84 --- .../SystemCenter/AdditionalField.cs | 44 -- .../SystemCenter/DetailedAsset.cs | 93 --- .../SystemCenter/DetailedLocation.cs | 104 --- .../SystemCenter/DetailedMeter.cs | 68 -- .../openXDA.Model/SystemCenter/ValueList.cs | 211 ------ .../SystemCenter/ValueListGroup.cs | 205 ------ .../TransmissionElements/Asset.cs | 615 ---------------- .../TransmissionElements/BreakerOperation.cs | 107 --- .../openXDA.Model/TransmissionElements/DER.cs | 66 -- .../TransmissionElements/SourceImpedance.cs | 114 --- .../StandardMagDurCurve.cs | 41 -- Libraries/openXDA.Model/openXDA.Model.csproj | 21 - .../openXDA.Reports/openXDA.Reports.csproj | 4 +- SEBrowser-dev.slnx | 3 - SEBrowser/SEBrowser.csproj | 12 +- 83 files changed, 16 insertions(+), 11299 deletions(-) create mode 100644 Dependencies/OpenXDA/FaultAlgorithms.dll create mode 100644 Dependencies/OpenXDA/FaultData.dll create mode 100644 Dependencies/OpenXDA/openHistorian.XDALink.dll create mode 100644 Dependencies/OpenXDA/openXDA.APIAuthentication.dll create mode 100644 Dependencies/OpenXDA/openXDA.Configuration.dll create mode 100644 Dependencies/OpenXDA/openXDA.Model.dll create mode 100644 Dependencies/OpenXDA/openXDA.PQI.dll create mode 100644 Dependencies/OpenXDA/openXDA.Reports.dll delete mode 100644 Libraries/FaultAlgorithms/Conductor.cs delete mode 100644 Libraries/FaultAlgorithms/Cycle.cs delete mode 100644 Libraries/FaultAlgorithms/CycleData.cs delete mode 100644 Libraries/FaultAlgorithms/CycleDataSet.cs delete mode 100644 Libraries/FaultAlgorithms/FaultAlgorithms.csproj delete mode 100644 Libraries/FaultAlgorithms/MeasurementData.cs delete mode 100644 Libraries/FaultAlgorithms/MeasurementDataSet.cs delete mode 100644 Libraries/FaultData/DataAnalysis/CycleDataGroup.cs delete mode 100644 Libraries/FaultData/DataAnalysis/DataGroup.cs delete mode 100644 Libraries/FaultData/DataAnalysis/DataSeries.cs delete mode 100644 Libraries/FaultData/DataAnalysis/ReportedDisturbance.cs delete mode 100644 Libraries/FaultData/DataAnalysis/Transform.cs delete mode 100644 Libraries/FaultData/DataAnalysis/VICycleDataGroup.cs delete mode 100644 Libraries/FaultData/DataAnalysis/VIDataGroup.cs delete mode 100644 Libraries/FaultData/FaultData.csproj delete mode 100644 Libraries/openXDA.Model/Channels/Channel.cs delete mode 100644 Libraries/openXDA.Model/Channels/ChannelData.cs delete mode 100644 Libraries/openXDA.Model/Channels/ChannelGroup.cs delete mode 100644 Libraries/openXDA.Model/Channels/DataPoint.cs delete mode 100644 Libraries/openXDA.Model/Channels/MeasurementCharacteristic.cs delete mode 100644 Libraries/openXDA.Model/Channels/MeasurementType.cs delete mode 100644 Libraries/openXDA.Model/Channels/Phase.cs delete mode 100644 Libraries/openXDA.Model/Channels/Series.cs delete mode 100644 Libraries/openXDA.Model/Channels/SeriesType.cs delete mode 100644 Libraries/openXDA.Model/DERAnalytic/DERAnalyticResult.cs delete mode 100644 Libraries/openXDA.Model/Events/BreakerRestrike.cs delete mode 100644 Libraries/openXDA.Model/Events/Disturbances/Disturbance.cs delete mode 100644 Libraries/openXDA.Model/Events/Event.cs delete mode 100644 Libraries/openXDA.Model/Events/EventStat.cs delete mode 100644 Libraries/openXDA.Model/Events/EventType.cs delete mode 100644 Libraries/openXDA.Model/Events/Faults/Fault.cs delete mode 100644 Libraries/openXDA.Model/Events/Faults/FaultCurve.cs delete mode 100644 Libraries/openXDA.Model/Events/RelayPerformance.cs delete mode 100644 Libraries/openXDA.Model/Files/DataFile.cs delete mode 100644 Libraries/openXDA.Model/Files/FileBlob.cs delete mode 100644 Libraries/openXDA.Model/Files/FileGroup.cs delete mode 100644 Libraries/openXDA.Model/Files/FileGroupField.cs delete mode 100644 Libraries/openXDA.Model/Files/FileGroupFieldValue.cs delete mode 100644 Libraries/openXDA.Model/LazyContext.cs delete mode 100644 Libraries/openXDA.Model/Links/AssetConnection.cs delete mode 100644 Libraries/openXDA.Model/Links/MeterLine.cs delete mode 100644 Libraries/openXDA.Model/Links/MeterLocationLine.cs delete mode 100644 Libraries/openXDA.Model/Meters/AssetGroup.cs delete mode 100644 Libraries/openXDA.Model/Meters/Location.cs delete mode 100644 Libraries/openXDA.Model/Meters/Meter.cs delete mode 100644 Libraries/openXDA.Model/Note.cs delete mode 100644 Libraries/openXDA.Model/PQDigest/HomeScreenWidget.cs delete mode 100644 Libraries/openXDA.Model/PQDigest/Widget.cs delete mode 100644 Libraries/openXDA.Model/Reports/Report.cs delete mode 100644 Libraries/openXDA.Model/SEBrowser/DetailedSeries.cs delete mode 100644 Libraries/openXDA.Model/SEBrowser/TrendChannel.cs delete mode 100644 Libraries/openXDA.Model/SEBrowser/Widget.cs delete mode 100644 Libraries/openXDA.Model/SEBrowser/WidgetCategory.cs delete mode 100644 Libraries/openXDA.Model/SearchRestrictionHelper.cs delete mode 100644 Libraries/openXDA.Model/Settings/BreakerReportSettings.cs delete mode 100644 Libraries/openXDA.Model/Settings/OpenSEESetting.cs delete mode 100644 Libraries/openXDA.Model/Settings/PQDigestSetting.cs delete mode 100644 Libraries/openXDA.Model/Settings/PQReportsSettings.cs delete mode 100644 Libraries/openXDA.Model/Settings/Setting.cs delete mode 100644 Libraries/openXDA.Model/SystemCenter/AdditionalField.cs delete mode 100644 Libraries/openXDA.Model/SystemCenter/DetailedAsset.cs delete mode 100644 Libraries/openXDA.Model/SystemCenter/DetailedLocation.cs delete mode 100644 Libraries/openXDA.Model/SystemCenter/DetailedMeter.cs delete mode 100644 Libraries/openXDA.Model/SystemCenter/ValueList.cs delete mode 100644 Libraries/openXDA.Model/SystemCenter/ValueListGroup.cs delete mode 100644 Libraries/openXDA.Model/TransmissionElements/Asset.cs delete mode 100644 Libraries/openXDA.Model/TransmissionElements/BreakerOperation.cs delete mode 100644 Libraries/openXDA.Model/TransmissionElements/DER.cs delete mode 100644 Libraries/openXDA.Model/TransmissionElements/SourceImpedance.cs delete mode 100644 Libraries/openXDA.Model/TransmissionElements/StandardMagDurCurve.cs delete mode 100644 Libraries/openXDA.Model/openXDA.Model.csproj diff --git a/Dependencies/OpenXDA/FaultAlgorithms.dll b/Dependencies/OpenXDA/FaultAlgorithms.dll new file mode 100644 index 0000000000000000000000000000000000000000..6c0bd42576089782ad686ca589f148b9057c9619 GIT binary patch literal 15360 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 Y2hZVO@ah)7`K6=$gTB9B|L-F3Kht$C@c;k- literal 0 HcmV?d00001 diff --git a/Dependencies/OpenXDA/FaultData.dll b/Dependencies/OpenXDA/FaultData.dll new file mode 100644 index 0000000000000000000000000000000000000000..d94d4e3416336badc16fd031b5688f5498a3b784 GIT binary patch literal 54272 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 literal 0 HcmV?d00001 diff --git a/Dependencies/OpenXDA/openHistorian.XDALink.dll b/Dependencies/OpenXDA/openHistorian.XDALink.dll new file mode 100644 index 0000000000000000000000000000000000000000..5e175351c915694f6ae98d68f5a04d009639a25a GIT binary patch literal 18432 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
    &Qo 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 literal 0 HcmV?d00001 diff --git a/Dependencies/OpenXDA/openXDA.PQI.dll b/Dependencies/OpenXDA/openXDA.PQI.dll new file mode 100644 index 0000000000000000000000000000000000000000..0df0239e4a984cc1ee90ca62bf9e19faa53e2f8c GIT binary patch literal 66048 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 literal 0 HcmV?d00001 diff --git a/Dependencies/OpenXDA/openXDA.Reports.dll b/Dependencies/OpenXDA/openXDA.Reports.dll new file mode 100644 index 0000000000000000000000000000000000000000..63684b2dd0e468845cb79152b5feaeed4095f202 GIT binary patch literal 164352 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 literal 0 HcmV?d00001 diff --git a/Libraries/FaultAlgorithms/Conductor.cs b/Libraries/FaultAlgorithms/Conductor.cs deleted file mode 100644 index 5b5a24fe..00000000 --- a/Libraries/FaultAlgorithms/Conductor.cs +++ /dev/null @@ -1,122 +0,0 @@ -//********************************************************************************************************************* -// 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/Libraries/FaultAlgorithms/Cycle.cs b/Libraries/FaultAlgorithms/Cycle.cs deleted file mode 100644 index 91db47e8..00000000 --- a/Libraries/FaultAlgorithms/Cycle.cs +++ /dev/null @@ -1,200 +0,0 @@ -//********************************************************************************************************************* -// 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/Libraries/FaultAlgorithms/CycleData.cs b/Libraries/FaultAlgorithms/CycleData.cs deleted file mode 100644 index 610d5960..00000000 --- a/Libraries/FaultAlgorithms/CycleData.cs +++ /dev/null @@ -1,177 +0,0 @@ -//********************************************************************************************************************* -// 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/Libraries/FaultAlgorithms/CycleDataSet.cs b/Libraries/FaultAlgorithms/CycleDataSet.cs deleted file mode 100644 index 34b810b7..00000000 --- a/Libraries/FaultAlgorithms/CycleDataSet.cs +++ /dev/null @@ -1,303 +0,0 @@ -//********************************************************************************************************************* -// 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/Libraries/FaultAlgorithms/FaultAlgorithms.csproj b/Libraries/FaultAlgorithms/FaultAlgorithms.csproj deleted file mode 100644 index b8917102..00000000 --- a/Libraries/FaultAlgorithms/FaultAlgorithms.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - net9.0 - Debug;Development;Release - enable - enable - - - - - - - - diff --git a/Libraries/FaultAlgorithms/MeasurementData.cs b/Libraries/FaultAlgorithms/MeasurementData.cs deleted file mode 100644 index 04da176a..00000000 --- a/Libraries/FaultAlgorithms/MeasurementData.cs +++ /dev/null @@ -1,97 +0,0 @@ -//********************************************************************************************************************* -// 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/Libraries/FaultAlgorithms/MeasurementDataSet.cs b/Libraries/FaultAlgorithms/MeasurementDataSet.cs deleted file mode 100644 index 1a6c0fb0..00000000 --- a/Libraries/FaultAlgorithms/MeasurementDataSet.cs +++ /dev/null @@ -1,272 +0,0 @@ -//********************************************************************************************************************* -// 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/Libraries/FaultData/DataAnalysis/CycleDataGroup.cs b/Libraries/FaultData/DataAnalysis/CycleDataGroup.cs deleted file mode 100644 index 3129dd76..00000000 --- a/Libraries/FaultData/DataAnalysis/CycleDataGroup.cs +++ /dev/null @@ -1,115 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/FaultData/DataAnalysis/DataGroup.cs b/Libraries/FaultData/DataAnalysis/DataGroup.cs deleted file mode 100644 index 20d91b75..00000000 --- a/Libraries/FaultData/DataAnalysis/DataGroup.cs +++ /dev/null @@ -1,662 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/FaultData/DataAnalysis/DataSeries.cs b/Libraries/FaultData/DataAnalysis/DataSeries.cs deleted file mode 100644 index cbc7d9d1..00000000 --- a/Libraries/FaultData/DataAnalysis/DataSeries.cs +++ /dev/null @@ -1,588 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/FaultData/DataAnalysis/ReportedDisturbance.cs b/Libraries/FaultData/DataAnalysis/ReportedDisturbance.cs deleted file mode 100644 index a078eb5b..00000000 --- a/Libraries/FaultData/DataAnalysis/ReportedDisturbance.cs +++ /dev/null @@ -1,58 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/FaultData/DataAnalysis/Transform.cs b/Libraries/FaultData/DataAnalysis/Transform.cs deleted file mode 100644 index 9e4aa28d..00000000 --- a/Libraries/FaultData/DataAnalysis/Transform.cs +++ /dev/null @@ -1,358 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/FaultData/DataAnalysis/VICycleDataGroup.cs b/Libraries/FaultData/DataAnalysis/VICycleDataGroup.cs deleted file mode 100644 index d913f052..00000000 --- a/Libraries/FaultData/DataAnalysis/VICycleDataGroup.cs +++ /dev/null @@ -1,476 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/FaultData/DataAnalysis/VIDataGroup.cs b/Libraries/FaultData/DataAnalysis/VIDataGroup.cs deleted file mode 100644 index ae088e05..00000000 --- a/Libraries/FaultData/DataAnalysis/VIDataGroup.cs +++ /dev/null @@ -1,521 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/FaultData/FaultData.csproj b/Libraries/FaultData/FaultData.csproj deleted file mode 100644 index b19aaa40..00000000 --- a/Libraries/FaultData/FaultData.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - - net9.0 - Debug;Development;Release - enable - enable - - - - - - - - - - - - - - - diff --git a/Libraries/openXDA.Configuration/openXDA.Configuration.csproj b/Libraries/openXDA.Configuration/openXDA.Configuration.csproj index 3d799a9b..147221b2 100644 --- a/Libraries/openXDA.Configuration/openXDA.Configuration.csproj +++ b/Libraries/openXDA.Configuration/openXDA.Configuration.csproj @@ -42,7 +42,9 @@ - + + ..\..\Dependencies\OpenXDA\openXDA.Model.dll + - \ No newline at end of file + diff --git a/Libraries/openXDA.Model/Channels/Channel.cs b/Libraries/openXDA.Model/Channels/Channel.cs deleted file mode 100644 index 9d65c5bc..00000000 --- a/Libraries/openXDA.Model/Channels/Channel.cs +++ /dev/null @@ -1,629 +0,0 @@ -//****************************************************************************************************** -// 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 Gemstone.Data; -using Gemstone.Data.Model; -using Newtonsoft.Json; - -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; -using System.Data; -using System.Transactions; -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; - } - } - - [TableName("ChannelDetail")] - public class ChannelDetail : Channel - { - public string MeterName { get; set; } - - public string AssetKey { get; set; } - - public string AssetName { get; set; } - - public new string MeasurementType { get; set; } - - public new string MeasurementCharacteristic { get; set; } - - public new string Phase { get; set; } - - public string Mapping { get; set; } - - public int SeriesTypeID { get; set; } - - public string SeriesType { get; set; } - - [SearchExtension("^VoltageKV$")] - public static RecordRestriction GetVoltageKVRestriction(IRecordFilter filter) - { - return SearchRestrictionHelper.GetSearchRestriction("(SELECT VoltageKV FROM Asset WHERE Asset.ID = FullTbl.AssetID)", filter); - } - - [SearchExtension("^ChannelGroupTypeID$")] - public static RecordRestriction GetChannelGroupTypeRestriction(IRecordFilter filter) - { - RecordRestriction restriction = SearchRestrictionHelper.GetSearchRestriction("ID", filter); - - return new RecordRestriction( - $"EXISTS(SELECT * FROM ChannelGroupType WHERE {restriction.FilterExpression} AND MeasurementTypeID = FullTbl.MeasurementTypeID AND MeasurementCharacteristicID = FullTbl.MeasurementCharacteristicID)", - restriction.Parameters); - } - - [SearchExtension("^SeriesID$")] - public static RecordRestriction GetSeriesRestriction(IRecordFilter filter) - { - RecordRestriction restriction = SearchRestrictionHelper.GetSearchRestriction("Series.SeriesTypeID", filter); - - return new RecordRestriction( - $"(SELECT COUNT(Series.ID) FROM SERIES WHERE Series.ChannelID = FullTbl.ID AND {restriction.FilterExpression}) > 0", - restriction.Parameters); - } - } - - 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; - } - - } - -} diff --git a/Libraries/openXDA.Model/Channels/ChannelData.cs b/Libraries/openXDA.Model/Channels/ChannelData.cs deleted file mode 100644 index 2ce28f94..00000000 --- a/Libraries/openXDA.Model/Channels/ChannelData.cs +++ /dev/null @@ -1,649 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/openXDA.Model/Channels/ChannelGroup.cs b/Libraries/openXDA.Model/Channels/ChannelGroup.cs deleted file mode 100644 index b5b463c2..00000000 --- a/Libraries/openXDA.Model/Channels/ChannelGroup.cs +++ /dev/null @@ -1,43 +0,0 @@ -//****************************************************************************************************** -// ChannelGroup.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: -// ---------------------------------------------------------------------------------------------------- -// 10/12/2020 - Billy Ernest -// Generated original version of source code. -// -//****************************************************************************************************** - - - -using Gemstone.Data.Model; -using System.ComponentModel.DataAnnotations; - -namespace openXDA.Model; - -[PostRoles("Administrator, Transmission SME")] -[PatchRoles("Administrator, Transmission SME")] -[DeleteRoles("Administrator, Transmission SME")] -public class ChannelGroup -{ - [PrimaryKey(true)] - public int ID { get; set; } - - [StringLength(200)] - public string Name { get; set; } - - public string Description { get; set; } -} diff --git a/Libraries/openXDA.Model/Channels/DataPoint.cs b/Libraries/openXDA.Model/Channels/DataPoint.cs deleted file mode 100644 index 88c39332..00000000 --- a/Libraries/openXDA.Model/Channels/DataPoint.cs +++ /dev/null @@ -1,110 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/openXDA.Model/Channels/MeasurementCharacteristic.cs b/Libraries/openXDA.Model/Channels/MeasurementCharacteristic.cs deleted file mode 100644 index a3e49898..00000000 --- a/Libraries/openXDA.Model/Channels/MeasurementCharacteristic.cs +++ /dev/null @@ -1,45 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/openXDA.Model/Channels/MeasurementType.cs b/Libraries/openXDA.Model/Channels/MeasurementType.cs deleted file mode 100644 index 20d3ea30..00000000 --- a/Libraries/openXDA.Model/Channels/MeasurementType.cs +++ /dev/null @@ -1,44 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/openXDA.Model/Channels/Phase.cs b/Libraries/openXDA.Model/Channels/Phase.cs deleted file mode 100644 index 705ec274..00000000 --- a/Libraries/openXDA.Model/Channels/Phase.cs +++ /dev/null @@ -1,43 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/openXDA.Model/Channels/Series.cs b/Libraries/openXDA.Model/Channels/Series.cs deleted file mode 100644 index d6c96a6c..00000000 --- a/Libraries/openXDA.Model/Channels/Series.cs +++ /dev/null @@ -1,248 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/openXDA.Model/Channels/SeriesType.cs b/Libraries/openXDA.Model/Channels/SeriesType.cs deleted file mode 100644 index c7621b5d..00000000 --- a/Libraries/openXDA.Model/Channels/SeriesType.cs +++ /dev/null @@ -1,40 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/openXDA.Model/DERAnalytic/DERAnalyticResult.cs b/Libraries/openXDA.Model/DERAnalytic/DERAnalyticResult.cs deleted file mode 100644 index c667215e..00000000 --- a/Libraries/openXDA.Model/DERAnalytic/DERAnalyticResult.cs +++ /dev/null @@ -1,54 +0,0 @@ -//****************************************************************************************************** -// DERAnalyticResult.cs - Gbtc -// -// Copyright © 2021, 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/09/2021 - Billy Ernest -// Generated original version of source code. -// -//****************************************************************************************************** - -using Gemstone.Data.Model; - -namespace openXDA.Model; - -public class DERAnalyticResult -{ - [PrimaryKey(true)] - public int ID { get; set; } - - public int? EventID { get; set; } - - public int MeterID { get; set; } - - public int AssetID { get; set; } - - public int ChannelID { get; set; } - - public string Regulation { get; set; } - - - public string Parameter { get; set; } - - public double Threshold { get; set; } - - public double Value { get; set; } - - [UseEscapedName] - public DateTime Time { get; set; } - - public string DataType { get; set; } -} \ No newline at end of file diff --git a/Libraries/openXDA.Model/Events/BreakerRestrike.cs b/Libraries/openXDA.Model/Events/BreakerRestrike.cs deleted file mode 100644 index e76661d5..00000000 --- a/Libraries/openXDA.Model/Events/BreakerRestrike.cs +++ /dev/null @@ -1,65 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/openXDA.Model/Events/Disturbances/Disturbance.cs b/Libraries/openXDA.Model/Events/Disturbances/Disturbance.cs deleted file mode 100644 index c83ce0e6..00000000 --- a/Libraries/openXDA.Model/Events/Disturbances/Disturbance.cs +++ /dev/null @@ -1,67 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/openXDA.Model/Events/Event.cs b/Libraries/openXDA.Model/Events/Event.cs deleted file mode 100644 index 992ad47e..00000000 --- a/Libraries/openXDA.Model/Events/Event.cs +++ /dev/null @@ -1,91 +0,0 @@ -//****************************************************************************************************** -// 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; } - } - - [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/Libraries/openXDA.Model/Events/EventStat.cs b/Libraries/openXDA.Model/Events/EventStat.cs deleted file mode 100644 index 6e004a3e..00000000 --- a/Libraries/openXDA.Model/Events/EventStat.cs +++ /dev/null @@ -1,57 +0,0 @@ -//****************************************************************************************************** -// 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; } - } -} diff --git a/Libraries/openXDA.Model/Events/EventType.cs b/Libraries/openXDA.Model/Events/EventType.cs deleted file mode 100644 index 3d5a0c19..00000000 --- a/Libraries/openXDA.Model/Events/EventType.cs +++ /dev/null @@ -1,42 +0,0 @@ -//****************************************************************************************************** -// EventType.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/20/2022 - C. Lackner -// Generated original version of source code. -// -//****************************************************************************************************** - -using Gemstone.Data.Model; - -namespace openXDA.Model; - -public class EventType -{ - [PrimaryKey(true)] - public int ID { get; set; } - - public string Name { get; set; } - - public string Description { get; set; } - - public bool ShowInFilter { get; set; } - - public string Category { get; set; } -} \ No newline at end of file diff --git a/Libraries/openXDA.Model/Events/Faults/Fault.cs b/Libraries/openXDA.Model/Events/Faults/Fault.cs deleted file mode 100644 index dbf8b955..00000000 --- a/Libraries/openXDA.Model/Events/Faults/Fault.cs +++ /dev/null @@ -1,137 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/openXDA.Model/Events/Faults/FaultCurve.cs b/Libraries/openXDA.Model/Events/Faults/FaultCurve.cs deleted file mode 100644 index 02268fa2..00000000 --- a/Libraries/openXDA.Model/Events/Faults/FaultCurve.cs +++ /dev/null @@ -1,250 +0,0 @@ -//****************************************************************************************************** -// 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; } - } -} diff --git a/Libraries/openXDA.Model/Events/RelayPerformance.cs b/Libraries/openXDA.Model/Events/RelayPerformance.cs deleted file mode 100644 index 4950235e..00000000 --- a/Libraries/openXDA.Model/Events/RelayPerformance.cs +++ /dev/null @@ -1,63 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/openXDA.Model/Files/DataFile.cs b/Libraries/openXDA.Model/Files/DataFile.cs deleted file mode 100644 index 10b9d4b5..00000000 --- a/Libraries/openXDA.Model/Files/DataFile.cs +++ /dev/null @@ -1,93 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/openXDA.Model/Files/FileBlob.cs b/Libraries/openXDA.Model/Files/FileBlob.cs deleted file mode 100644 index 00beeef8..00000000 --- a/Libraries/openXDA.Model/Files/FileBlob.cs +++ /dev/null @@ -1,38 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/openXDA.Model/Files/FileGroup.cs b/Libraries/openXDA.Model/Files/FileGroup.cs deleted file mode 100644 index d6c50ab2..00000000 --- a/Libraries/openXDA.Model/Files/FileGroup.cs +++ /dev/null @@ -1,102 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/openXDA.Model/Files/FileGroupField.cs b/Libraries/openXDA.Model/Files/FileGroupField.cs deleted file mode 100644 index 31021f88..00000000 --- a/Libraries/openXDA.Model/Files/FileGroupField.cs +++ /dev/null @@ -1,61 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/openXDA.Model/Files/FileGroupFieldValue.cs b/Libraries/openXDA.Model/Files/FileGroupFieldValue.cs deleted file mode 100644 index 55140bae..00000000 --- a/Libraries/openXDA.Model/Files/FileGroupFieldValue.cs +++ /dev/null @@ -1,39 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/openXDA.Model/LazyContext.cs b/Libraries/openXDA.Model/LazyContext.cs deleted file mode 100644 index 6f5c7acc..00000000 --- a/Libraries/openXDA.Model/LazyContext.cs +++ /dev/null @@ -1,369 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/openXDA.Model/Links/AssetConnection.cs b/Libraries/openXDA.Model/Links/AssetConnection.cs deleted file mode 100644 index dc03ab97..00000000 --- a/Libraries/openXDA.Model/Links/AssetConnection.cs +++ /dev/null @@ -1,176 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/openXDA.Model/Links/MeterLine.cs b/Libraries/openXDA.Model/Links/MeterLine.cs deleted file mode 100644 index 4d44e704..00000000 --- a/Libraries/openXDA.Model/Links/MeterLine.cs +++ /dev/null @@ -1,174 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/openXDA.Model/Links/MeterLocationLine.cs b/Libraries/openXDA.Model/Links/MeterLocationLine.cs deleted file mode 100644 index 377ec143..00000000 --- a/Libraries/openXDA.Model/Links/MeterLocationLine.cs +++ /dev/null @@ -1,192 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/openXDA.Model/Meters/AssetGroup.cs b/Libraries/openXDA.Model/Meters/AssetGroup.cs deleted file mode 100644 index 9848035e..00000000 --- a/Libraries/openXDA.Model/Meters/AssetGroup.cs +++ /dev/null @@ -1,60 +0,0 @@ -//****************************************************************************************************** -// AssetGroup.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; -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; - -namespace openXDA.Model -{ - public class AssetGroup - { - [Required] - [PrimaryKey(true)] - public int ID { get; set; } - - [Required] - [StringLength(100)] - [DefaultSortOrder] - public string Name { get; set; } - - [DefaultValue(true)] - public bool DisplayDashboard { get; set; } - - [DefaultValue(false)] - public bool DisplayEmail { get; set; } - } - - [TableName("AssetGroupView")] - [PostRoles("Administrator, Transmission SME")] - [PatchRoles("Administrator, Transmission SME")] - [DeleteRoles("Administrator, Transmission SME")] - public class AssetGroupView : AssetGroup - { - public int AssetGroups { get; set; } - public int Meters { get; set; } - public int Assets { get; set; } - public int Users { get; set; } - - } -} diff --git a/Libraries/openXDA.Model/Meters/Location.cs b/Libraries/openXDA.Model/Meters/Location.cs deleted file mode 100644 index 6c8515d1..00000000 --- a/Libraries/openXDA.Model/Meters/Location.cs +++ /dev/null @@ -1,509 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/openXDA.Model/Meters/Meter.cs b/Libraries/openXDA.Model/Meters/Meter.cs deleted file mode 100644 index 47c6b517..00000000 --- a/Libraries/openXDA.Model/Meters/Meter.cs +++ /dev/null @@ -1,338 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/openXDA.Model/Note.cs b/Libraries/openXDA.Model/Note.cs deleted file mode 100644 index dd2407cf..00000000 --- a/Libraries/openXDA.Model/Note.cs +++ /dev/null @@ -1,63 +0,0 @@ -//****************************************************************************************************** -// Note.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/14/2020 - Billy Ernest -// Generated original version of source code. -// 05/16/2021 - C. Lackner -// Merged notes model from SystemCenter. -// -//****************************************************************************************************** - -using Gemstone.Data; -using Gemstone.Data.Model; -using Gemstone.Web.APIController; -using Microsoft.AspNetCore.Mvc; - -namespace openXDA.Model; - -[TableName("Note"), UseEscapedName] -[PostRoles("Administrator, Transmission SME, PQ Data Viewer")] -[DeleteRoles("Administrator, Transmission SME")] -[PatchRoles("Administrator, Transmission SME")] -public class Notes -{ - [PrimaryKey(true)] - public int ID { get; set; } - public int NoteTypeID { get; set; } - public int NoteApplicationID { get; set; } = 2; - public int NoteTagID { get; set; } = 1; - public int ReferenceTableID { get; set; } - public string Note { get; set; } - public string UserAccount { get; set; } - - [DefaultSortOrder(false)] - public DateTime Timestamp { get; set; } -} - -public class NotesController : ModelController where T : Notes, new() -{ - public override async Task Post([FromBody] T record, CancellationToken cancellationToken) - { - using AdoDataConnection connection = CreateConnection(); - - record.UserAccount = User.Identity.Name; - - int result = new TableOperations(connection).AddNewRecord(record); - return Ok(result); - } -} diff --git a/Libraries/openXDA.Model/PQDigest/HomeScreenWidget.cs b/Libraries/openXDA.Model/PQDigest/HomeScreenWidget.cs deleted file mode 100644 index ee826388..00000000 --- a/Libraries/openXDA.Model/PQDigest/HomeScreenWidget.cs +++ /dev/null @@ -1,44 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/openXDA.Model/PQDigest/Widget.cs b/Libraries/openXDA.Model/PQDigest/Widget.cs deleted file mode 100644 index 6042dea0..00000000 --- a/Libraries/openXDA.Model/PQDigest/Widget.cs +++ /dev/null @@ -1,51 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/openXDA.Model/Reports/Report.cs b/Libraries/openXDA.Model/Reports/Report.cs deleted file mode 100644 index a4b4dea7..00000000 --- a/Libraries/openXDA.Model/Reports/Report.cs +++ /dev/null @@ -1,46 +0,0 @@ -//****************************************************************************************************** -// Report.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: -// ---------------------------------------------------------------------------------------------------- -// 07/02/2018 - Billy Ernest -// Generated original version of source code. -// -//****************************************************************************************************** - -using Gemstone.Data.Model; -using System.ComponentModel.DataAnnotations; - -namespace openXDA.Model -{ - public class Report - { - [PrimaryKey(true)] - public int ID { get; set; } - - [Required] - public int MeterID { get; set; } - [Required] - public int Month { get; set; } - [Required] - public int Year { get; set; } - [Required] - [StringLength(4)] - public string Results { get; set; } - - public byte[] PDF { get; set; } - } -} \ No newline at end of file diff --git a/Libraries/openXDA.Model/SEBrowser/DetailedSeries.cs b/Libraries/openXDA.Model/SEBrowser/DetailedSeries.cs deleted file mode 100644 index b3280421..00000000 --- a/Libraries/openXDA.Model/SEBrowser/DetailedSeries.cs +++ /dev/null @@ -1,37 +0,0 @@ -//****************************************************************************************************** -// DetailedSeries.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: -// ---------------------------------------------------------------------------------------------------- -// 07/31/2025 - G. Santos -// Generated original version of source code. -// -//****************************************************************************************************** - -using Gemstone.Data.Model; - -namespace SEBrowser.Model -{ - public class DetailedSeries - { - [PrimaryKey(true)] - public int ID { get; set; } - [DefaultSortOrder] - public int ChannelID { get; set; } - public string TypeName { get; set; } - public string TypeDescription { get; set; } - } -} diff --git a/Libraries/openXDA.Model/SEBrowser/TrendChannel.cs b/Libraries/openXDA.Model/SEBrowser/TrendChannel.cs deleted file mode 100644 index 6b084f6a..00000000 --- a/Libraries/openXDA.Model/SEBrowser/TrendChannel.cs +++ /dev/null @@ -1,48 +0,0 @@ -//****************************************************************************************************** -// TrendChannel.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: -// ---------------------------------------------------------------------------------------------------- -// 07/31/2025 - G. Santos -// Generated original version of source code. -// -//****************************************************************************************************** - -using Gemstone.Data.Model; - -namespace SEBrowser.Model -{ - public class TrendChannel - { - [PrimaryKey(true)] - public string ID { get; set; } - public int ChannelID { get; set; } - [DefaultSortOrder] - public string Name { get; set; } - public string Description { get; set; } - public int AssetID { get; set; } - public string AssetKey { get; set; } - public string AssetName { get; set; } - public int MeterID { get; set; } - public string MeterKey { get; set; } - public string MeterName { get; set; } - public string MeterShortName { get; set; } - public string Phase { get; set; } - public string ChannelGroup { get; set; } - public string ChannelGroupType { get; set; } - public string Unit { get; set; } - } -} diff --git a/Libraries/openXDA.Model/SEBrowser/Widget.cs b/Libraries/openXDA.Model/SEBrowser/Widget.cs deleted file mode 100644 index dcf2b368..00000000 --- a/Libraries/openXDA.Model/SEBrowser/Widget.cs +++ /dev/null @@ -1,61 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/openXDA.Model/SEBrowser/WidgetCategory.cs b/Libraries/openXDA.Model/SEBrowser/WidgetCategory.cs deleted file mode 100644 index 62faf93e..00000000 --- a/Libraries/openXDA.Model/SEBrowser/WidgetCategory.cs +++ /dev/null @@ -1,52 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/openXDA.Model/SearchRestrictionHelper.cs b/Libraries/openXDA.Model/SearchRestrictionHelper.cs deleted file mode 100644 index ca740df9..00000000 --- a/Libraries/openXDA.Model/SearchRestrictionHelper.cs +++ /dev/null @@ -1,55 +0,0 @@ -//****************************************************************************************************** -// SearchRestrictionHelper.cs - Gbtc -// -// Copyright © 2026, 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/07/2026 - P. Crawford -// Generated original version of source code. -// -//****************************************************************************************************** - -using Gemstone.Data.Model; - -namespace openXDA.Model -{ - internal static class SearchRestrictionHelper - { - // IN / NOT IN filters carry an array SearchParameter that must expand to one placeholder per element; - // a single placeholder cannot bind an array. - internal static RecordRestriction GetSearchRestriction(string fieldExpression, IRecordFilter filter, int parameterOffset = 0) - { - if (!string.Equals(filter.Operator, "IN", StringComparison.OrdinalIgnoreCase) && - !string.Equals(filter.Operator, "NOT IN", StringComparison.OrdinalIgnoreCase)) - return new RecordRestriction($"{fieldExpression} {filter.Operator} {{{parameterOffset}}}", filter.SearchParameter); - - object[] parameters = GetSearchParameters(filter.SearchParameter).ToArray(); - string placeholders = string.Join(", ", parameters.Select((_, index) => $"{{{index + parameterOffset}}}")); - - return new RecordRestriction($"{fieldExpression} {filter.Operator} ({placeholders})", parameters); - } - - private static IEnumerable GetSearchParameters(object searchParameter) - { - if (searchParameter is string searchText) - return searchText.Trim('(', ')').Split(',').Select(value => (object)value.Trim()); - - if (searchParameter is System.Collections.IEnumerable parameters) - return parameters.Cast(); - - return [searchParameter]; - } - } -} diff --git a/Libraries/openXDA.Model/Settings/BreakerReportSettings.cs b/Libraries/openXDA.Model/Settings/BreakerReportSettings.cs deleted file mode 100644 index 2d87837b..00000000 --- a/Libraries/openXDA.Model/Settings/BreakerReportSettings.cs +++ /dev/null @@ -1,64 +0,0 @@ -//****************************************************************************************************** -// BreakerReportsSettings.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/09/2019 - Billy Ernest -// Generated original version of source code. -// -//****************************************************************************************************** - - -using System.ComponentModel; -using System.Configuration; - -namespace openXDA.Model -{ - public class BreakerReportsSettings - { - #region [ Members ] - - // Constants - public const string CategoryName = "BreakerReports"; - - #endregion - - #region [ Properties ] - - /// - /// Indicates whether operation is enabled. - /// - [Setting] - [DefaultValue(false)] - public bool Enabled { get; set; } - - /// - /// Indicates whether operation is enabled. - /// - [Setting] - [DefaultValue("0 0 2 * *")] // Runs on second day of month to ensure all data is in - public string Schedule { get; set; } - - /// - /// Comma separated list of emails to send breaker reports to. - /// - [Setting] - [DefaultValue("")] - public string EmailList { get; set; } - - #endregion - } -} diff --git a/Libraries/openXDA.Model/Settings/OpenSEESetting.cs b/Libraries/openXDA.Model/Settings/OpenSEESetting.cs deleted file mode 100644 index a1e5adc9..00000000 --- a/Libraries/openXDA.Model/Settings/OpenSEESetting.cs +++ /dev/null @@ -1,33 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/openXDA.Model/Settings/PQDigestSetting.cs b/Libraries/openXDA.Model/Settings/PQDigestSetting.cs deleted file mode 100644 index 877bff81..00000000 --- a/Libraries/openXDA.Model/Settings/PQDigestSetting.cs +++ /dev/null @@ -1,33 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/openXDA.Model/Settings/PQReportsSettings.cs b/Libraries/openXDA.Model/Settings/PQReportsSettings.cs deleted file mode 100644 index 45f77e93..00000000 --- a/Libraries/openXDA.Model/Settings/PQReportsSettings.cs +++ /dev/null @@ -1,116 +0,0 @@ -//****************************************************************************************************** -// ReportsSettings.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: -// ---------------------------------------------------------------------------------------------------- -// 06/14/2018 - Billy Ernest -// Generated original version of source code. -// -//****************************************************************************************************** - -using System.ComponentModel; -using System.Configuration; - -namespace openXDA.Model -{ - public class PQReportsSettings - { - #region [ Members ] - - // Constants - public const string CategoryName = "PQReports"; - - #endregion - - #region [ Properties ] - - /// - /// Indicates whether operation is enabled. - /// - [Setting] - [DefaultValue(false)] - public bool Enabled { get; set; } - - /// - /// Indicates whether operation is enabled. - /// - [Setting] - [DefaultValue("0 0 2 * *")] // Runs on second day of month to ensure all data is in - public string Schedule { get; set; } - - [Setting] - [DefaultValue(99.0D)] - public double FirstFrequencyPercentile { get; set; } - - [Setting] - [DefaultValue(0.3D)] - public double FirstFrequencyDeviationLimit { get; set; } - - [Setting] - [DefaultValue(99.95D)] - public double SecondFrequencyPercentile { get; set; } - - [Setting] - [DefaultValue(0.5D)] - public double SecondFrequencyDeviationLimit { get; set; } - - [Setting] - [DefaultValue(99.0D)] - public double FirstVoltagePercentile { get; set; } - - [Setting] - [DefaultValue(3.0D)] - public double FirstVoltageDeviationLimit { get; set; } - - [Setting] - [DefaultValue(99.95D)] - public double SecondVoltagePercentile { get; set; } - - [Setting] - [DefaultValue(5.0D)] - public double SecondVoltageDeviationLimit { get; set; } - - [Setting] - [DefaultValue(98.5D)] - public double FlickerPercentile { get; set; } - - [Setting] - [DefaultValue(1.0D)] - public double FlickerHighLimit { get; set; } - - [Setting] - [DefaultValue(99.95D)] - public double VoltageUnbalancePercentile { get; set; } - - [Setting] - [DefaultValue(2.0D)] - public double VoltageUnbalanceHighLimit { get; set; } - - [Setting] - [DefaultValue(0.0D)] - public double VoltageUnbalanceLowLimit { get; set; } - - [Setting] - [DefaultValue(99.95D)] - public double VoltageTHDPercentile { get; set; } - - [Setting] - [DefaultValue(8.0D)] - public double VoltageTHDHighLimit { get; set; } - - #endregion - } -} diff --git a/Libraries/openXDA.Model/Settings/Setting.cs b/Libraries/openXDA.Model/Settings/Setting.cs deleted file mode 100644 index b9f2a866..00000000 --- a/Libraries/openXDA.Model/Settings/Setting.cs +++ /dev/null @@ -1,84 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/openXDA.Model/SystemCenter/AdditionalField.cs b/Libraries/openXDA.Model/SystemCenter/AdditionalField.cs deleted file mode 100644 index adf860e8..00000000 --- a/Libraries/openXDA.Model/SystemCenter/AdditionalField.cs +++ /dev/null @@ -1,44 +0,0 @@ -//****************************************************************************************************** -// AdditionalField.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: -// ---------------------------------------------------------------------------------------------------- -// 09/20/2019 - Billy Ernest -// Generated original version of source code. -// -//****************************************************************************************************** - -using Gemstone.Data.Model; - -namespace SystemCenter.Model; - -[UseEscapedName] -[PatchRoles("Administrator, Transmission SME")] -[PostRoles("Administrator, Transmission SME")] -[DeleteRoles("Administrator, Transmission SME")] -public class AdditionalField -{ - [PrimaryKey(true)] - public int ID { get; set; } - public string ParentTable { get; set; } - public string FieldName { get; set; } - public string Type { get; set; } - public int? ExternalDBTableID { get; set; } - public bool IsSecure { get; set; } - public bool IsInfo { get; set; } - public bool IsKey { get; set; } - public bool Searchable { get; set; } -} diff --git a/Libraries/openXDA.Model/SystemCenter/DetailedAsset.cs b/Libraries/openXDA.Model/SystemCenter/DetailedAsset.cs deleted file mode 100644 index 1bf65f93..00000000 --- a/Libraries/openXDA.Model/SystemCenter/DetailedAsset.cs +++ /dev/null @@ -1,93 +0,0 @@ -//****************************************************************************************************** -// DetailedAsset.cs - Gbtc -// -// Copyright © 2021, 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/22/2021 - C. Lackner -// Generated original version of source code. -// -//****************************************************************************************************** - -using Gemstone.Configuration; -using Gemstone.Data; -using Gemstone.Data.Model; -using openXDA.Model; - -namespace SystemCenter.Model -{ - public class DetailedAsset - { - [PrimaryKey(true)] - public int ID { get; set; } - - [DefaultSortOrder] - public string AssetKey { get; set; } - public string AssetName { get; set; } - public double VoltageKV { get; set; } - public string AssetType { get; set; } - public int Meters { get; set; } - public int Locations { get; set; } - - // The frontend prefixes any user-defined Additional Field filter with "AdditionalField.", so this matches that prefix, strips - // it back to the real field name, and resolves the value through the AdditionalFieldSearch view. - [SearchExtension(@"^AdditionalField\.")] - public static RecordRestriction GetAdditionalFieldRestriction(IRecordFilter filter) - { - string fieldName = filter.FieldName["AdditionalField.".Length..]; - - using AdoDataConnection connection = new(Settings.Default); - TableOperations tableOps = new(connection); - if (RecordFilter.WildCardOperators.Contains(filter.Operator, StringComparer.OrdinalIgnoreCase) && filter.SearchParameter is string stringVal) - filter.SearchParameter = stringVal.Replace("*", tableOps.WildcardChar); - - RecordRestriction valueRestriction = SearchRestrictionHelper.GetSearchRestriction("Value", filter, 1); - - return new RecordRestriction( - $"ID IN (SELECT ParentTableID FROM AdditionalFieldSearch WHERE ParentTable IN ('Line', 'Transformer', 'Breaker', 'CapBank', 'Bus', 'Generation', 'StationAux', 'StationBattery') AND FieldName = {{0}} AND {valueRestriction.FilterExpression})", - new object[] { fieldName }.Concat(valueRestriction.Parameters).ToArray()); - } - - [SearchExtension("^Meter$")] - public static RecordRestriction GetMeterRestriction(IRecordFilter filter) - { - using AdoDataConnection connection = new(Settings.Default); - TableOperations tableOps = new(connection); - if (RecordFilter.WildCardOperators.Contains(filter.Operator, StringComparer.OrdinalIgnoreCase) && filter.SearchParameter is string stringVal) - filter.SearchParameter = stringVal.Replace("*", tableOps.WildcardChar); - - RecordRestriction restriction = SearchRestrictionHelper.GetSearchRestriction("Meter.AssetKey", filter); - - return new RecordRestriction( - $"ID IN (SELECT MeterAsset.AssetID FROM Meter JOIN MeterAsset ON Meter.ID = MeterAsset.MeterID WHERE {restriction.FilterExpression})", - restriction.Parameters); - } - - [SearchExtension("^Location$")] - public static RecordRestriction GetLocationRestriction(IRecordFilter filter) - { - using AdoDataConnection connection = new(Settings.Default); - TableOperations tableOps = new(connection); - if (RecordFilter.WildCardOperators.Contains(filter.Operator, StringComparer.OrdinalIgnoreCase) && filter.SearchParameter is string stringVal) - filter.SearchParameter = stringVal.Replace("*", tableOps.WildcardChar); - - RecordRestriction restriction = SearchRestrictionHelper.GetSearchRestriction("Location.LocationKey", filter); - - return new RecordRestriction( - $"ID IN (SELECT AssetLocation.AssetID FROM Location JOIN AssetLocation ON AssetLocation.LocationID = Location.ID WHERE {restriction.FilterExpression})", - restriction.Parameters); - } - } -} diff --git a/Libraries/openXDA.Model/SystemCenter/DetailedLocation.cs b/Libraries/openXDA.Model/SystemCenter/DetailedLocation.cs deleted file mode 100644 index 0a13fbc6..00000000 --- a/Libraries/openXDA.Model/SystemCenter/DetailedLocation.cs +++ /dev/null @@ -1,104 +0,0 @@ -//****************************************************************************************************** -// DetailedLocation.cs - Gbtc -// -// Copyright © 2021, 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/22/2021 - C. Lackner -// Generated original version of source code. -// -//****************************************************************************************************** - -using Gemstone.Configuration; -using Gemstone.Data; -using Gemstone.Data.Model; -using openXDA.Model; - -namespace SystemCenter.Model; - -[PostRoles("Administrator, Transmission SME")] -public class DetailedLocation -{ - [PrimaryKey(true)] - public int ID { get; set; } - - [DefaultSortOrder] - public string LocationKey { get; set; } - - public string Name { get; set; } - - public string Alias { get; set; } - - public string ShortName { get; set; } - - public double Longitude { get; set; } - - public double Latitude { get; set; } - - public string Description { get; set; } - - public int Meters { get; set; } - - public int Assets { get; set; } - - // The frontend prefixes any user-defined Additional Field filter with "AdditionalField.", so this matches that prefix, strips - // it back to the real field name, and resolves the value through the AdditionalFieldSearch view. - [SearchExtension(@"^AdditionalField\.")] - public static RecordRestriction GetAdditionalFieldRestriction(IRecordFilter filter) - { - string fieldName = filter.FieldName["AdditionalField.".Length..]; - - using AdoDataConnection connection = new(Settings.Default); - TableOperations tableOps = new(connection); - if (RecordFilter.WildCardOperators.Contains(filter.Operator, StringComparer.OrdinalIgnoreCase) && filter.SearchParameter is string stringVal) - filter.SearchParameter = stringVal.Replace("*", tableOps.WildcardChar); - - RecordRestriction valueRestriction = SearchRestrictionHelper.GetSearchRestriction("Value", filter, 1); - - return new RecordRestriction( - $"ID IN (SELECT ParentTableID FROM AdditionalFieldSearch WHERE ParentTable = 'Location' AND FieldName = {{0}} AND {valueRestriction.FilterExpression})", - new object[] { fieldName }.Concat(valueRestriction.Parameters).ToArray()); - } - - [SearchExtension("^Meter$")] - public static RecordRestriction GetMeterRestriction(IRecordFilter filter) - { - using AdoDataConnection connection = new(Settings.Default); - TableOperations tableOps = new(connection); - if (RecordFilter.WildCardOperators.Contains(filter.Operator, StringComparer.OrdinalIgnoreCase) && filter.SearchParameter is string stringVal) - filter.SearchParameter = stringVal.Replace("*", tableOps.WildcardChar); - - RecordRestriction restriction = SearchRestrictionHelper.GetSearchRestriction("Meter.AssetKey", filter); - - return new RecordRestriction( - $"ID IN (SELECT Meter.LocationID FROM Meter WHERE {restriction.FilterExpression})", - restriction.Parameters); - } - - [SearchExtension("^Asset$")] - public static RecordRestriction GetAssetRestriction(IRecordFilter filter) - { - using AdoDataConnection connection = new(Settings.Default); - TableOperations tableOps = new(connection); - if (RecordFilter.WildCardOperators.Contains(filter.Operator, StringComparer.OrdinalIgnoreCase) && filter.SearchParameter is string stringVal) - filter.SearchParameter = stringVal.Replace("*", tableOps.WildcardChar); - - RecordRestriction restriction = SearchRestrictionHelper.GetSearchRestriction("Asset.AssetKey", filter); - - return new RecordRestriction( - $"ID IN (SELECT AssetLocation.LocationID FROM Asset JOIN AssetLocation ON AssetLocation.AssetID = Asset.ID WHERE {restriction.FilterExpression})", - restriction.Parameters); - } -} diff --git a/Libraries/openXDA.Model/SystemCenter/DetailedMeter.cs b/Libraries/openXDA.Model/SystemCenter/DetailedMeter.cs deleted file mode 100644 index 667d1f43..00000000 --- a/Libraries/openXDA.Model/SystemCenter/DetailedMeter.cs +++ /dev/null @@ -1,68 +0,0 @@ -//****************************************************************************************************** -// DetailedMeter.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/01/2020 - C. Lackner -// Generated original version of source code. -// -//****************************************************************************************************** - -using Gemstone.Configuration; -using Gemstone.Data; -using Gemstone.Data.Model; -using openXDA.Model; - -namespace SystemCenter.Model -{ - public class DetailedMeter - { - [PrimaryKey(true)] - public int ID { get; set; } - - [DefaultSortOrder] - public string AssetKey { get; set; } - - public string Name { get; set; } - - public string Location { get; set; } - - public int MappedAssets { get; set; } - - public string Make { get; set; } - - public string Model { get; set; } - - // The frontend prefixes any user-defined Additional Field filter with "AdditionalField.", so this matches that prefix, strips - // it back to the real field name, and resolves the value through the AdditionalFieldSearch view. - [SearchExtension(@"^AdditionalField\.")] - public static RecordRestriction GetAdditionalFieldRestriction(IRecordFilter filter) - { - string fieldName = filter.FieldName["AdditionalField.".Length..]; - - using AdoDataConnection connection = new(Settings.Default); - TableOperations tableOps = new(connection); - if (RecordFilter.WildCardOperators.Contains(filter.Operator, StringComparer.OrdinalIgnoreCase) && filter.SearchParameter is string stringVal) - filter.SearchParameter = stringVal.Replace("*", tableOps.WildcardChar); - - RecordRestriction valueRestriction = SearchRestrictionHelper.GetSearchRestriction("Value", filter, 1); - - return new RecordRestriction( - $"ID IN (SELECT ParentTableID FROM AdditionalFieldSearch WHERE ParentTable = 'Meter' AND FieldName = {{0}} AND {valueRestriction.FilterExpression})", - new object[] { fieldName }.Concat(valueRestriction.Parameters).ToArray()); - } - } -} diff --git a/Libraries/openXDA.Model/SystemCenter/ValueList.cs b/Libraries/openXDA.Model/SystemCenter/ValueList.cs deleted file mode 100644 index fec40b4c..00000000 --- a/Libraries/openXDA.Model/SystemCenter/ValueList.cs +++ /dev/null @@ -1,211 +0,0 @@ -//****************************************************************************************************** -// ValueList.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: -// ---------------------------------------------------------------------------------------------------- -// 09/10/2018 - Billy Ernest -// Generated original version of source code. -// -//****************************************************************************************************** - -using Gemstone.ComponentModel.DataAnnotations; -using Gemstone.Data; -using Gemstone.Data.Model; -using Gemstone.Web.APIController; -using Microsoft.AspNetCore.Mvc; -using Newtonsoft.Json.Linq; - -namespace SystemCenter.Model; - -[TableName("ValueList"), UseEscapedName, PrimaryLabel("Text")] -public class ValueList -{ - [PrimaryKey(true)] - public int ID { get; set; } - - [ParentKey(typeof(ValueListGroup))] - public int GroupID { get; set; } - public string Value { get; set; } - public string AltValue { get; set; } - public int SortOrder { get; set; } -} - -public class ValueListController : ModelController -{ - [HttpGet, Route("Group/{groupName}")] - public IActionResult GetValueListForGroup(string groupName) - { - using AdoDataConnection connection = CreateConnection(); - return Ok(GetGroup(groupName, connection)); - } - - [HttpGet, Route("Count/{groupName}")] - public IActionResult GetValueListCountDictionary(string groupName) - { - using AdoDataConnection connection = CreateConnection(); - - JObject dictionary = []; - foreach (ValueList item in GetGroup(groupName, connection)) - dictionary.Add(item.ID.ToString(), GetCount(groupName, item.Value, connection).ToString()); - - return Ok(dictionary); - } - - [HttpPatch] - public override Task Patch([FromBody] ValueList newRecord, CancellationToken cancellationToken) - { - // Check if Value changed - bool changeVal = false; - ValueList oldRecord; - - using (AdoDataConnection connection = CreateConnection()) - { - oldRecord = new TableOperations(connection).QueryRecordWhere("ID = {0}", newRecord.ID); - changeVal = !(newRecord.Value == oldRecord.Value); - } - - if (changeVal) - { - ValueListGroup? group; - using AdoDataConnection connection = CreateConnection(); - - group = new TableOperations(connection).QueryRecordWhere("ID = {0}", newRecord.GroupID); - - // Wrapping is needed here, since C# tries to use the wrong method signature otherwise - object[] parameters = { newRecord.Value, oldRecord.Value, group?.Name ?? "" }; - // Update Additional Fields - connection.ExecuteScalar(@"UPDATE - AdditionalFieldValue - SET [Value] = {0} - WHERE - [Value] = {1} AND - ( - SELECT TOP 1 Type - FROM AdditionalField - WHERE AdditionalField.ID = AdditionalFieldValue.AdditionalFieldID - ) = {2}", parameters); - - RestrictedValueList? restriction = RestrictedValueList.List.Find((g) => g.Name == group?.Name); - - if (!(restriction?.UpdateSQL is null)) - { - object[] updateSqlParams = { newRecord.Value, oldRecord.Value }; - connection.ExecuteScalar(restriction.UpdateSQL, updateSqlParams); - } - - } - - return base.Patch(newRecord, cancellationToken); - } - - public override Task Delete(ValueList record, CancellationToken cancellationToken) - { - ValueListGroup? group; - using AdoDataConnection connection = CreateConnection(); - - group = new TableOperations(connection).QueryRecordWhere("ID = {0}", record.GroupID); - RestrictedValueList? restriction = RestrictedValueList.List.Find((g) => g.Name == group.Name); - - if (!(restriction?.CountSQL is null)) - { - int count = connection.ExecuteScalar(restriction.CountSQL, record.Value); - - if (count > 0) - throw new Exception("Cannot delete value that is still in use."); - } - - connection.ExecuteScalar(@"DELETE FROM AdditionalFieldValue - WHERE - [Value] = {0} AND - (SELECT TOP 1 Type FROM AdditionalField AF WHERE AF.ID = AdditionalFieldValue.AdditionalFieldID) = {1}", (object)record.Value, group?.Name); - - return base.Delete(record, cancellationToken); - } - - [HttpGet, Route("Count/{groupName}/{value}")] - public IActionResult GetCount(string groupName, string value) - { - using AdoDataConnection connection = CreateConnection(); - - return Ok(GetCount(groupName, value, connection)); - } - - private IEnumerable GetGroup(string groupName, AdoDataConnection connection) - { - TableOperations groupTable = new(connection); - TableOperations valueTable = new(connection); - List groupIds = groupTable.QueryRecordsWhere("Name = {0}", groupName).Select(group => group?.ID ?? -1).ToList(); - - if (groupIds.Count() == 0) - { - RestrictedValueList restriction = RestrictedValueList.List.Find((g) => g.Name == groupName); - if (!(restriction is null)) - { - groupTable.AddNewRecord( - new ValueListGroup() - { - Description = "", - Name = restriction.Name - }); - groupIds.Add(connection.ExecuteScalar("SELECT @@IDENTITY")); - - int sortOrder = 1; - foreach (object item in restriction.DefaultItems) - { - string value; - string altValue; - if (item.GetType() == typeof(Tuple)) - { - value = ((Tuple)item).Item1; - altValue = ((Tuple)item).Item2; - } - else if (item.GetType() == typeof(string)) - { - value = (string)item; - altValue = (string)item; - } - else - throw new InvalidCastException($"Could not convert object in DefaultItems of value list {restriction.Name} to either tuple or string."); - valueTable.AddNewRecord( - new ValueList() - { - GroupID = groupIds[0], - Value = value, - AltValue = altValue, - SortOrder = sortOrder - }); - sortOrder++; - } - } - else - return new List(); - } - return valueTable.QueryRecordsWhere($"GroupID in ({string.Join(",", groupIds)})").OrderBy(v => v.SortOrder); - } - - private int GetCount(string groupName, string value, AdoDataConnection connection) - { - int nAddlFields = connection.ExecuteScalar(@"SELECT COUNT(AFV.ID) FROM AdditionalFieldValue AFV WHERE - [Value] = {0} AND (SELECT TOP 1 AF.ID FROM AdditionalField AF WHERE Type = {1}) = AFV.AdditionalFieldID - ", value, groupName); - RestrictedValueList restriction = RestrictedValueList.List.Find((g) => g.Name == groupName); - int count = 0; - if (!(restriction?.CountSQL is null)) - count = connection.ExecuteScalar(restriction.CountSQL, value); - - return nAddlFields + count; - } -} diff --git a/Libraries/openXDA.Model/SystemCenter/ValueListGroup.cs b/Libraries/openXDA.Model/SystemCenter/ValueListGroup.cs deleted file mode 100644 index 000908e3..00000000 --- a/Libraries/openXDA.Model/SystemCenter/ValueListGroup.cs +++ /dev/null @@ -1,205 +0,0 @@ -//****************************************************************************************************** -// ValueListGroup.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: -// ---------------------------------------------------------------------------------------------------- -// 09/10/2018 - Billy Ernest -// Generated original version of source code. -// -//****************************************************************************************************** - -using Gemstone.ComponentModel.DataAnnotations; -using Gemstone.Data; -using Gemstone.Data.Model; -using Gemstone.Web.APIController; -using Microsoft.AspNetCore.Mvc; -using System.ComponentModel.DataAnnotations; - -namespace SystemCenter.Model -{ - [TableName("ValueListGroup"), UseEscapedName, PrimaryLabel("Name")] - public class ValueListGroup - { - [PrimaryKey(true)] - public int ID { get; set; } - - [StringLength(200)] - public string Name { get; set; } - public string Description { get; set; } - } - - public class RestrictedValueList - { - public string Name { get; set; } - public string CountSQL { get; set; } - public string UpdateSQL { get; set; } - - /// - /// Default items in a . - /// Objects may be either or of two , - /// where the represents <, > - /// - public object[] DefaultItems { get; set; } - - public static List List = new List(){ - new RestrictedValueList() { - Name = "TimeZones", - CountSQL = @"SELECT COUNT(ID) FROM - Meter - WHERE [TimeZone] = {0}", - UpdateSQL = @"UPDATE Meter - SET [TimeZone] = {0} - WHERE - [TimeZone] = {1}", - DefaultItems = new string[] {"UTC"} - }, - new RestrictedValueList() { - Name = "Make", - CountSQL = @"SELECT COUNT(ID) FROM - Meter - WHERE [Make] = {0}", - UpdateSQL = @"UPDATE - Meter - SET [Make] = {0} - WHERE - [Make] = {1}", - DefaultItems = new string[] {"GPA"} - }, - new RestrictedValueList() { - Name = "Model", - CountSQL = @"SELECT COUNT(ID) FROM - Meter - WHERE [Model] = {0}", - UpdateSQL = @"UPDATE - Meter - SET [Model] = {0} - WHERE - [Model] = {1}", - DefaultItems = new string[] {"PQMeter"} - }, - new RestrictedValueList() { - Name = "Unit", - CountSQL = @"SELECT COUNT(ID) FROM - ChannelGroupType - WHERE - [Unit] = {0}", - UpdateSQL = @"UPDATE - ChannelGroupType - SET [Unit] = {0} - WHERE - [Unit] = {1}", - DefaultItems = new string[] {"Unknown"} - }, - new RestrictedValueList() { - Name = "Category", - CountSQL = @"SELECT COUNT(ID) FROM - LocationDrawing - WHERE - [Category] = {0}", - UpdateSQL = @"UPDATE - LocationDrawing - SET [Category] = {0} - WHERE - [Category] = {1}", - DefaultItems = new string[] {"Oneline"} - }, - new RestrictedValueList() { - Name = "SpareChannel", - DefaultItems = new string[] {"Spare Channel"} - }, - new RestrictedValueList() { - Name = "TrendLabelDefaults", - DefaultItems = new Tuple[] { - new Tuple("Channel.MeterName", "Meter Name"), - new Tuple("Channel.AssetKey", "Asset Key"), - new Tuple("Channel.Name", "Channel Label"), - new Tuple("Channel.ChannelGroup", "Channel Group Name"), - new Tuple("Series.TypeName", "Channel Series") - } - }, - new RestrictedValueList() { - Name = "TrendLabelOptions", - DefaultItems = new Tuple[] { - new Tuple("Channel.MeterName", "Meter Name"), - new Tuple("Channel.MeterShortName", "Meter Short Name"), - new Tuple("Channel.MeterKey", "Meter Asset Key"), - new Tuple("Channel.AssetName", "Asset Name"), - new Tuple("Channel.AssetKey", "Asset Key"), - new Tuple("Channel.Phase", "Phase Name"), - new Tuple("Channel.Name", "Channel Label"), - new Tuple("Channel.Description", "Channel Description"), - new Tuple("Channel.ChannelGroup", "Channel Group Name"), - new Tuple("Channel.ChannelGroupType", "Channel Group Type"), - new Tuple("Series.TypeName", "Channel Series"), - new Tuple("Channel.Unit", "Unit") - } - } - }; - } - - [Route("api/ValueListGroup")] - public class ValueListGroupController : ModelController - { - public override Task Patch([FromBody] ValueListGroup newRecord, CancellationToken cancellationToken) - { - // Check if Value changed - bool changeVal = false; - ValueListGroup oldRecord; - - using (AdoDataConnection connection = CreateConnection()) - { - oldRecord = new TableOperations(connection).QueryRecordWhere("ID = {0}", newRecord.ID); - changeVal = !(newRecord.Name == oldRecord.Name); - } - - if (changeVal) - { - using AdoDataConnection connection = CreateConnection(); - - // Wrapping is needed here, since C# tries to use the wrong method signature otherwise - object[] parameters = { newRecord.Name, oldRecord.Name }; - - // Update Additional Fields - connection.ExecuteScalar(@"UPDATE - AdditionalField - SET [Type] = {0} - WHERE - [Type] = {1}", parameters); - - } - - return base.Patch(newRecord, cancellationToken); - } - - public override Task Delete(ValueListGroup record, CancellationToken cancellationToken) - { - using (AdoDataConnection connection = CreateConnection()) - { - // Wrapping is needed here, since C# tries to use the wrong method signature otherwise - object[] parameters = { record.Name }; - - // Update Additional Fields - connection.ExecuteScalar(@"UPDATE - AdditionalField - SET [Type] = 'string' - WHERE - [Type] = {0}", parameters); - } - - return base.Delete(record, cancellationToken); - } - } -} \ No newline at end of file diff --git a/Libraries/openXDA.Model/TransmissionElements/Asset.cs b/Libraries/openXDA.Model/TransmissionElements/Asset.cs deleted file mode 100644 index 1d484d37..00000000 --- a/Libraries/openXDA.Model/TransmissionElements/Asset.cs +++ /dev/null @@ -1,615 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/openXDA.Model/TransmissionElements/BreakerOperation.cs b/Libraries/openXDA.Model/TransmissionElements/BreakerOperation.cs deleted file mode 100644 index 3be64ebe..00000000 --- a/Libraries/openXDA.Model/TransmissionElements/BreakerOperation.cs +++ /dev/null @@ -1,107 +0,0 @@ -//****************************************************************************************************** -// 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.Data; -using Gemstone.Data; -using Gemstone.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; } - - public int BreakerNumber { get; set; } - - public string LineName { get; set; } - - public string PhaseName { get; set; } - - public double Timing { get; set; } - - public int Speed { get; set; } - - public string OperationType { get; set; } - - public string UpdatedBy { get; set; } - } -} \ No newline at end of file diff --git a/Libraries/openXDA.Model/TransmissionElements/DER.cs b/Libraries/openXDA.Model/TransmissionElements/DER.cs deleted file mode 100644 index 9a0c78f0..00000000 --- a/Libraries/openXDA.Model/TransmissionElements/DER.cs +++ /dev/null @@ -1,66 +0,0 @@ -//****************************************************************************************************** -// DER.cs - Gbtc -// -// Copyright © 2021, 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/19/2021 - Billy Ernest -// Generated original version of source code. -// -//****************************************************************************************************** - - -using Gemstone.Data; -using Gemstone.Data.Model; -using System.ComponentModel.DataAnnotations; - -namespace openXDA.Model; - -[MetadataType(typeof(Asset))] -public class DER : Asset -{ - - #region [ Properties ] - - public double FullRatedOutputCurrent { get; set; } - - public string VoltageLevel { get; set; } - - #endregion - - #region [ Methods ] - - public static DER DetailedDER(Asset asset, AdoDataConnection connection) - { - if ((object)connection == null) - return null; - - TableOperations table = new TableOperations(connection); - DER record = table.QueryRecordWhere("ID = {0}", asset.ID); - if (record == null) - return null; - - record.LazyContext = asset.LazyContext; - record.ConnectionFactory = asset.ConnectionFactory; - - return record; - } - - public static DER DetailedDER(Asset asset) - { - return DetailedDER(asset, asset.ConnectionFactory.Invoke()); - } - #endregion -} diff --git a/Libraries/openXDA.Model/TransmissionElements/SourceImpedance.cs b/Libraries/openXDA.Model/TransmissionElements/SourceImpedance.cs deleted file mode 100644 index 998f1d16..00000000 --- a/Libraries/openXDA.Model/TransmissionElements/SourceImpedance.cs +++ /dev/null @@ -1,114 +0,0 @@ -//****************************************************************************************************** -// 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/Libraries/openXDA.Model/TransmissionElements/StandardMagDurCurve.cs b/Libraries/openXDA.Model/TransmissionElements/StandardMagDurCurve.cs deleted file mode 100644 index 2be02adf..00000000 --- a/Libraries/openXDA.Model/TransmissionElements/StandardMagDurCurve.cs +++ /dev/null @@ -1,41 +0,0 @@ -//****************************************************************************************************** -// OpenXDAController.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: -// ---------------------------------------------------------------------------------------------------- -// 03/04/2020 - Billy Ernest -// Generated original version of source code. -// 04/11/2022 - G. Santos -// Copied portion of source code from SEBrowser. -// -//****************************************************************************************************** - -using Gemstone.Data.Model; - -namespace openXDA.Model -{ - [PostRoles("Administrator")] - [DeleteRoles("Administrator")] - [PatchRoles("Administrator")] - public class StandardMagDurCurve - { - [PrimaryKey] - public int ID { get; set; } - public string Name { get; set; } - public string Area { get; set; } - public string Color { get; set; } - } -} \ No newline at end of file diff --git a/Libraries/openXDA.Model/openXDA.Model.csproj b/Libraries/openXDA.Model/openXDA.Model.csproj deleted file mode 100644 index d7bb48d9..00000000 --- a/Libraries/openXDA.Model/openXDA.Model.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - net9.0 - Debug;Development;Release - enable - enable - - - - - - - - - - - - - - diff --git a/Libraries/openXDA.Reports/openXDA.Reports.csproj b/Libraries/openXDA.Reports/openXDA.Reports.csproj index 239e67eb..d16fe4a1 100644 --- a/Libraries/openXDA.Reports/openXDA.Reports.csproj +++ b/Libraries/openXDA.Reports/openXDA.Reports.csproj @@ -45,7 +45,9 @@ - + + ..\..\Dependencies\OpenXDA\openXDA.Model.dll + diff --git a/SEBrowser-dev.slnx b/SEBrowser-dev.slnx index ab2cbf62..d5f00332 100644 --- a/SEBrowser-dev.slnx +++ b/SEBrowser-dev.slnx @@ -6,12 +6,9 @@ - - - diff --git a/SEBrowser/SEBrowser.csproj b/SEBrowser/SEBrowser.csproj index b1484c53..f8710a8d 100644 --- a/SEBrowser/SEBrowser.csproj +++ b/SEBrowser/SEBrowser.csproj @@ -51,9 +51,15 @@ - - - + + ..\Dependencies\OpenXDA\FaultAlgorithms.dll + + + ..\Dependencies\OpenXDA\FaultData.dll + + + ..\Dependencies\OpenXDA\openXDA.Model.dll + From c4c8322015c1e954d16ac26153ced56c0a2345ed Mon Sep 17 00:00:00 2001 From: prestoncraw Date: Mon, 3 Aug 2026 16:37:57 -0400 Subject: [PATCH 2/4] update pointer --- SEBrowser/EventWidgets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SEBrowser/EventWidgets b/SEBrowser/EventWidgets index 8203aa8e..b414a37c 160000 --- a/SEBrowser/EventWidgets +++ b/SEBrowser/EventWidgets @@ -1 +1 @@ -Subproject commit 8203aa8eb88900156f81ec4df027f8bee1d0b460 +Subproject commit b414a37c073460c6a0f7471ade180fc7bd768794 From 1df0df2bde298585d00eacde703d9e2103cd643a Mon Sep 17 00:00:00 2001 From: prestoncraw Date: Mon, 3 Aug 2026 16:57:27 -0400 Subject: [PATCH 3/4] Remove remaining libaries that live in PQDigest --- Libraries/openHistorian.XDALink/Historian.cs | 203 -- .../ImportedMeasurement.cs | 222 -- .../TrendingDataPoint.cs | 92 - .../openHistorian.XDALink.csproj | 48 - .../openXDA.APIAuthentication/APIQuery.cs | 311 --- .../IAPICredentialRetriever.cs | 65 - .../Properties/AssemblyInfo.cs | 36 - Libraries/openXDA.APIAuthentication/XDAAPI.cs | 232 -- .../openXDA.APIAuthentication/XDAAPIHelper.cs | 149 -- .../openXDA.APIAuthentication.csproj | 26 - .../openXDA.Configuration/BreakerSection.cs | 119 - .../openXDA.Configuration/COMTRADESection.cs | 67 - .../DataAnalysisSection.cs | 120 - .../DataPusherSection.cs | 37 - .../openXDA.Configuration/EMAXSection.cs | 66 - .../EPRICapBankAnalyticSection.cs | 89 - .../Edition/EditionChecker.cs | 98 - .../Edition/HttpEditionFilterAttribute.cs | 57 - .../openXDA.Configuration/EmailSection.cs | 111 - .../EventEmailSection.cs | 68 - .../FaultLocationSection.cs | 104 - .../FileEnumeratorSection.cs | 80 - .../FileProcessorSection.cs | 91 - .../FilePrunerSection.cs | 58 - .../FileWatcherSection.cs | 308 --- .../openXDA.Configuration/GrafanaSection.cs | 72 - .../openXDA.Configuration/LSCVSSection.cs | 68 - .../openXDA.Configuration/OSIPISection.cs | 71 - .../openXDA.Configuration/PQDIFSection.cs | 46 - Libraries/openXDA.Configuration/PQISection.cs | 66 - .../Properties/AssemblyInfo.cs | 36 - .../openXDA.Configuration/RabbitMQSection.cs | 90 - .../openXDA.Configuration/SCADASection.cs | 94 - .../openXDA.Configuration/SSAMSSection.cs | 73 - .../openXDA.Configuration/Subscription.cs | 65 - .../openXDA.Configuration/SystemSection.cs | 109 - .../TaskProcessorSection.cs | 68 - .../TrendingDataSection.cs | 122 - .../openXDA.Configuration.csproj | 50 - Libraries/openXDA.PQI/Address.cs | 81 - Libraries/openXDA.PQI/AuditCurve.cs | 56 - Libraries/openXDA.PQI/Company.cs | 56 - Libraries/openXDA.PQI/Equipment.cs | 74 - Libraries/openXDA.PQI/Facility.cs | 56 - Libraries/openXDA.PQI/FacilityAudit.cs | 53 - Libraries/openXDA.PQI/FacilityInfo.cs | 83 - Libraries/openXDA.PQI/HttpClientExtensions.cs | 42 - Libraries/openXDA.PQI/HttpClientProvider.cs | 37 - Libraries/openXDA.PQI/PQIEquipment.cs | 65 - Libraries/openXDA.PQI/PQIModel.cs | 36 - Libraries/openXDA.PQI/PQIWSClient.cs | 325 --- Libraries/openXDA.PQI/PQIWSQueryHelper.cs | 282 --- Libraries/openXDA.PQI/PingClient.cs | 97 - .../openXDA.PQI/Properties/AssemblyInfo.cs | 36 - Libraries/openXDA.PQI/TestCurve.cs | 96 - Libraries/openXDA.PQI/TestCurvePoint.cs | 53 - Libraries/openXDA.PQI/openXDA.PQI.csproj | 32 - .../openXDA.Reports/AllBreakersReport.cs | 411 ---- Libraries/openXDA.Reports/EmailWriter.cs | 205 -- .../IndividualBreakerReport.cs | 701 ------ Libraries/openXDA.Reports/PQReport.cs | 2034 ----------------- .../Properties/AssemblyInfo.cs | 36 - Libraries/openXDA.Reports/README.md | 7 - Libraries/openXDA.Reports/ReportsEngine.cs | 382 ---- .../openXDA.Reports/openXDA.Reports.csproj | 54 - SEBrowser-dev.slnx | 7 - SEBrowser/SEBrowser.csproj | 8 +- 67 files changed, 6 insertions(+), 9286 deletions(-) delete mode 100644 Libraries/openHistorian.XDALink/Historian.cs delete mode 100644 Libraries/openHistorian.XDALink/ImportedMeasurement.cs delete mode 100644 Libraries/openHistorian.XDALink/TrendingDataPoint.cs delete mode 100644 Libraries/openHistorian.XDALink/openHistorian.XDALink.csproj delete mode 100644 Libraries/openXDA.APIAuthentication/APIQuery.cs delete mode 100644 Libraries/openXDA.APIAuthentication/IAPICredentialRetriever.cs delete mode 100644 Libraries/openXDA.APIAuthentication/Properties/AssemblyInfo.cs delete mode 100644 Libraries/openXDA.APIAuthentication/XDAAPI.cs delete mode 100644 Libraries/openXDA.APIAuthentication/XDAAPIHelper.cs delete mode 100644 Libraries/openXDA.APIAuthentication/openXDA.APIAuthentication.csproj delete mode 100644 Libraries/openXDA.Configuration/BreakerSection.cs delete mode 100644 Libraries/openXDA.Configuration/COMTRADESection.cs delete mode 100644 Libraries/openXDA.Configuration/DataAnalysisSection.cs delete mode 100644 Libraries/openXDA.Configuration/DataPusherSection.cs delete mode 100644 Libraries/openXDA.Configuration/EMAXSection.cs delete mode 100644 Libraries/openXDA.Configuration/EPRICapBankAnalyticSection.cs delete mode 100644 Libraries/openXDA.Configuration/Edition/EditionChecker.cs delete mode 100644 Libraries/openXDA.Configuration/Edition/HttpEditionFilterAttribute.cs delete mode 100644 Libraries/openXDA.Configuration/EmailSection.cs delete mode 100644 Libraries/openXDA.Configuration/EventEmailSection.cs delete mode 100644 Libraries/openXDA.Configuration/FaultLocationSection.cs delete mode 100644 Libraries/openXDA.Configuration/FileEnumeratorSection.cs delete mode 100644 Libraries/openXDA.Configuration/FileProcessorSection.cs delete mode 100644 Libraries/openXDA.Configuration/FilePrunerSection.cs delete mode 100644 Libraries/openXDA.Configuration/FileWatcherSection.cs delete mode 100644 Libraries/openXDA.Configuration/GrafanaSection.cs delete mode 100644 Libraries/openXDA.Configuration/LSCVSSection.cs delete mode 100644 Libraries/openXDA.Configuration/OSIPISection.cs delete mode 100644 Libraries/openXDA.Configuration/PQDIFSection.cs delete mode 100644 Libraries/openXDA.Configuration/PQISection.cs delete mode 100644 Libraries/openXDA.Configuration/Properties/AssemblyInfo.cs delete mode 100644 Libraries/openXDA.Configuration/RabbitMQSection.cs delete mode 100644 Libraries/openXDA.Configuration/SCADASection.cs delete mode 100644 Libraries/openXDA.Configuration/SSAMSSection.cs delete mode 100644 Libraries/openXDA.Configuration/Subscription.cs delete mode 100644 Libraries/openXDA.Configuration/SystemSection.cs delete mode 100644 Libraries/openXDA.Configuration/TaskProcessorSection.cs delete mode 100644 Libraries/openXDA.Configuration/TrendingDataSection.cs delete mode 100644 Libraries/openXDA.Configuration/openXDA.Configuration.csproj delete mode 100644 Libraries/openXDA.PQI/Address.cs delete mode 100644 Libraries/openXDA.PQI/AuditCurve.cs delete mode 100644 Libraries/openXDA.PQI/Company.cs delete mode 100644 Libraries/openXDA.PQI/Equipment.cs delete mode 100644 Libraries/openXDA.PQI/Facility.cs delete mode 100644 Libraries/openXDA.PQI/FacilityAudit.cs delete mode 100644 Libraries/openXDA.PQI/FacilityInfo.cs delete mode 100644 Libraries/openXDA.PQI/HttpClientExtensions.cs delete mode 100644 Libraries/openXDA.PQI/HttpClientProvider.cs delete mode 100644 Libraries/openXDA.PQI/PQIEquipment.cs delete mode 100644 Libraries/openXDA.PQI/PQIModel.cs delete mode 100644 Libraries/openXDA.PQI/PQIWSClient.cs delete mode 100644 Libraries/openXDA.PQI/PQIWSQueryHelper.cs delete mode 100644 Libraries/openXDA.PQI/PingClient.cs delete mode 100644 Libraries/openXDA.PQI/Properties/AssemblyInfo.cs delete mode 100644 Libraries/openXDA.PQI/TestCurve.cs delete mode 100644 Libraries/openXDA.PQI/TestCurvePoint.cs delete mode 100644 Libraries/openXDA.PQI/openXDA.PQI.csproj delete mode 100644 Libraries/openXDA.Reports/AllBreakersReport.cs delete mode 100644 Libraries/openXDA.Reports/EmailWriter.cs delete mode 100644 Libraries/openXDA.Reports/IndividualBreakerReport.cs delete mode 100644 Libraries/openXDA.Reports/PQReport.cs delete mode 100644 Libraries/openXDA.Reports/Properties/AssemblyInfo.cs delete mode 100644 Libraries/openXDA.Reports/README.md delete mode 100644 Libraries/openXDA.Reports/ReportsEngine.cs delete mode 100644 Libraries/openXDA.Reports/openXDA.Reports.csproj diff --git a/Libraries/openHistorian.XDALink/Historian.cs b/Libraries/openHistorian.XDALink/Historian.cs deleted file mode 100644 index a7517018..00000000 --- a/Libraries/openHistorian.XDALink/Historian.cs +++ /dev/null @@ -1,203 +0,0 @@ -//****************************************************************************************************** -// Historian.cs - Gbtc -// -// Copyright © 2015, 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/23/2015 - Stephen C. Wills -// Generated original version of source code. -// -//****************************************************************************************************** - -using Gemstone; -using Gemstone.WordExtensions; -using openHistorian.Net; -using openHistorian.Queues; -using openHistorian.Snap; -using SnapDB.Snap; -using SnapDB.Snap.Filters; -using SnapDB.Snap.Services; -using SnapDB.Snap.Services.Reader; - -namespace openHistorian.XDALink -{ - /// - /// Series ID values. - /// - public enum SeriesID - { - Minimum = 0, - Maximum = 1, - Average = 2 - } - - public class Historian : IDisposable - { - #region [ Members ] - - // Constants - - /// - /// Default historian server port number. - /// - public const int DefaultHistorianPort = 38402; - - // Fields - private HistorianClient m_client; - private ClientDatabaseBase m_database; - private Lazy m_queue; - private List> m_treeStreams; - - private bool m_disposed; - - #endregion - - #region [ Constructors ] - - public Historian(string server, string database) - { - string[] split = server.Split(':'); - int port; - - if (split.Length == 1) - { - m_client = new HistorianClient(split[0], DefaultHistorianPort); - } - else - { - if (!int.TryParse(split[1], out port)) - throw new ArgumentException("Invalid format for server[:port]. " + server, "server"); - - m_client = new HistorianClient(split[0], port); - } - - m_database = m_client.GetDatabase(database); - m_queue = new Lazy(() => new HistorianInputQueue(() => m_database)); - m_treeStreams = new List>(); - } - - #endregion - - #region [ Methods ] - - public IEnumerable Read(IEnumerable channels, DateTime startTime, DateTime stopTime) - { - IEnumerable measurementIDs = channels.SelectMany(GetAllMeasurementIDs); - return Read(measurementIDs, startTime, stopTime); - } - - public void Write(int channelID, SeriesID seriesID, DateTime timestamp, double value) - { - HistorianKey historianKey = new HistorianKey(); - HistorianValue historianValue = new HistorianValue(); - - historianKey.PointID = ToPointID(channelID, (int)seriesID); - historianKey.TimestampAsDate = timestamp; - historianValue.AsSingle = (float)value; - - m_queue.Value.Enqueue(historianKey, historianValue); - } - - public void Flush() - { - Flush(1); - } - - public void Flush(int pollingFrequency) - { - if (m_queue.IsValueCreated) - { - while (m_queue.Value.Size > 0) - Thread.Sleep(1000 / pollingFrequency); - } - } - - public void Dispose() - { - if (!m_disposed) - { - try - { - foreach (TreeStream stream in m_treeStreams) - stream.Dispose(); - - if (m_queue.IsValueCreated) - { - Flush(10); - m_queue.Value.Dispose(); - } - - m_database.Dispose(); - m_client.Dispose(); - } - finally - { - m_disposed = true; - } - } - } - - private IEnumerable Read(IEnumerable measurementIDs, DateTime startTime, DateTime stopTime) - { - SeekFilterBase timeFilter = TimestampSeekFilter.CreateFromRange(startTime, stopTime); - MatchFilterBase pointFilter = null; - HistorianKey key = new HistorianKey(); - HistorianValue value = new HistorianValue(); - - if ((object)measurementIDs != null) - pointFilter = PointIDMatchFilter.CreateFromList(measurementIDs); - - // Start stream reader for the provided time window and selected points - using (TreeStream stream = m_database.Read(SortedTreeEngineReaderOptions.Default, timeFilter, pointFilter)) - { - m_treeStreams.Add(stream); - - while (stream.Read(key, value)) - { - yield return new TrendingDataPoint() - { - ChannelID = (int)key.PointID.HighDoubleWord(), - SeriesID = (SeriesID)(int)key.PointID.LowDoubleWord(), - Timestamp = key.TimestampAsDate, - Value = value.AsSingle - }; - } - } - } - - private IEnumerable GetAllMeasurementIDs(int channel) - { - return SeriesIDs.Select(series => ToPointID(channel, (int)series)); - } - - #endregion - - #region [ Static ] - - // Static Fields - private static readonly List SeriesIDs = Enum.GetValues(typeof(SeriesID)) - .Cast() - .ToList(); - - // Static Methods - - public static ulong ToPointID(int channel, int series) - { - return Word.MakeQuadWord((uint)channel, (uint)series); - } - - #endregion - } -} diff --git a/Libraries/openHistorian.XDALink/ImportedMeasurement.cs b/Libraries/openHistorian.XDALink/ImportedMeasurement.cs deleted file mode 100644 index e6721fb5..00000000 --- a/Libraries/openHistorian.XDALink/ImportedMeasurement.cs +++ /dev/null @@ -1,222 +0,0 @@ -//****************************************************************************************************** -// ImportedMeasurement.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/27/2017 - Stephen C. Wills -// Generated original version of source code. -// -//****************************************************************************************************** - -#pragma warning disable 1591 - -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; -using System.IO; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; -using Newtonsoft.Json; - -namespace openHistorian.XDALink -{ - public class ImportedMeasurement - { - public Guid? NodeID { get; set; } - - public Guid? SourceNodeID { get; set; } - - public Guid? SignalID { get; set; } - - [StringLength(200)] - public string Source { get; set; } - - public long PointID { get; set; } - - [StringLength(200)] - public string PointTag { get; set; } - - [StringLength(200)] - public string AlternateTag { get; set; } - - [StringLength(4)] - public string SignalTypeAcronym { get; set; } - - [StringLength(200)] - public string SignalReference { get; set; } - - public int? FramesPerSecond { get; set; } - - [StringLength(200)] - public string ProtocolAcronym { get; set; } - - [StringLength(200)] - [DefaultValue("Frame")] - public string ProtocolType { get; set; } - - public int? PhasorID { get; set; } - - public char? PhasorType { get; set; } - - public char? Phase { get; set; } - - [DefaultValue(0.0D)] - public double Adder { get; set; } - - [DefaultValue(1.0D)] - public double Multiplier { get; set; } - - [StringLength(200)] - public string CompanyAcronym { get; set; } - - public double? Longitude { get; set; } - - public double? Latitude { get; set; } - - public string Description { get; set; } - - [DefaultValue(false)] - public bool Enabled { get; set; } - } - - public class ImportedMeasurementsTable - { - #region [ Members ] - - // Fields - private string m_historianURL; - private string m_authString; - - #endregion - - #region [ Constructors ] - - public ImportedMeasurementsTable(string historianURL, string username, string password) - { - m_historianURL = historianURL; - - string unencodedAuthString = $"{username}:{password}"; - byte[] authBytes = Encoding.UTF8.GetBytes(unencodedAuthString); - m_authString = Convert.ToBase64String(authBytes); - } - - #endregion - - #region [ Methods ] - - public IEnumerable FindAll() - { - string url = GetURL("/api/importedmeasurements/findall"); - return RequestMeasurements(url); - } - - public IEnumerable FindByID(long pointID) - { - string url = GetURL($"/api/importedmeasurements/findbyid/{pointID}"); - return RequestMeasurements(url); - } - - public IEnumerable FindByPointTag(string pointTag) - { - string url = GetURL($"/api/importedmeasurements/findbypointtag/{pointTag}"); - return RequestMeasurements(url); - } - - public IEnumerable FindByAlternateTag(string alternateTag) - { - string url = GetURL($"/api/importedmeasurements/findbyalternatetag/{alternateTag}"); - return RequestMeasurements(url); - } - - public void ImportMeasurements(IEnumerable measurements) - { - string antiForgeryToken = GenerateAntiForgeryToken(); - string url = GetURL("/api/importedmeasurements/importmeasurements"); - - using (MemoryStream stream = new MemoryStream()) - using (StreamWriter writer = new StreamWriter(stream)) - using (JsonTextWriter jsonWriter = new JsonTextWriter(writer)) - { - JsonSerializer serializer = new JsonSerializer(); - serializer.Serialize(jsonWriter, measurements); - jsonWriter.Flush(); - - using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, url)) - using (ByteArrayContent content = new ByteArrayContent(stream.ToArray())) - using (HttpClient client = CreateClient()) - { - request.Content = content; - request.Headers.Add("X-GSF-Verify", antiForgeryToken); - content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); - client.SendAsync(request).Result.Dispose(); - } - } - } - - public void DeleteMeasurement(long pointID) - { - string antiForgeryToken = GenerateAntiForgeryToken(); - string url = GetURL($"/api/importedmeasurements/deletemeasurement/{pointID}"); - - using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Delete, url)) - using (HttpClient client = CreateClient()) - { - request.Headers.Add("X-GSF-Verify", antiForgeryToken); - client.SendAsync(request).Result.Dispose(); - } - } - - private IEnumerable RequestMeasurements(string url) - { - using (HttpClient client = CreateClient()) - using (Stream response = client.GetStreamAsync(url).Result) - using (StreamReader streamReader = new StreamReader(response)) - using (JsonTextReader jsonReader = new JsonTextReader(streamReader)) - { - JsonSerializer serializer = new JsonSerializer(); - return serializer.Deserialize>(jsonReader); - } - } - - private string GenerateAntiForgeryToken() - { - string url = GetURL($"/api/importedmeasurements/generaterequestverficationtoken"); - - using (HttpClient client = CreateClient()) - { - return client.GetStringAsync(url).Result; - } - } - - private string GetURL(string relativePath) - { - string baseURL = m_historianURL.TrimEnd('/'); - string subURL = relativePath.TrimStart('/'); - return $"{baseURL}/{subURL}"; - } - - private HttpClient CreateClient() - { - HttpClient client = new HttpClient(); - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", m_authString); - return client; - } - - #endregion - } -} diff --git a/Libraries/openHistorian.XDALink/TrendingDataPoint.cs b/Libraries/openHistorian.XDALink/TrendingDataPoint.cs deleted file mode 100644 index 6548742f..00000000 --- a/Libraries/openHistorian.XDALink/TrendingDataPoint.cs +++ /dev/null @@ -1,92 +0,0 @@ -//****************************************************************************************************** -// TrendingDataPoint.cs - Gbtc -// -// Copyright © 2015, 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/23/2015 - Stephen C. Wills -// Generated original version of source code. -// -//****************************************************************************************************** - -using System; - -namespace openHistorian.XDALink -{ - public class TrendingDataPoint - { - #region [ Members ] - - // Fields - private int m_channelID; - private SeriesID m_seriesID; - private DateTime m_timestamp; - private double m_value; - - #endregion - - #region [ Properties ] - - public int ChannelID - { - get - { - return m_channelID; - } - set - { - m_channelID = value; - } - } - - public SeriesID SeriesID - { - get - { - return m_seriesID; - } - set - { - m_seriesID = value; - } - } - - public DateTime Timestamp - { - get - { - return m_timestamp; - } - set - { - m_timestamp = value; - } - } - - public double Value - { - get - { - return m_value; - } - set - { - m_value = value; - } - } - - #endregion - } -} diff --git a/Libraries/openHistorian.XDALink/openHistorian.XDALink.csproj b/Libraries/openHistorian.XDALink/openHistorian.XDALink.csproj deleted file mode 100644 index 5e8ee63c..00000000 --- a/Libraries/openHistorian.XDALink/openHistorian.XDALink.csproj +++ /dev/null @@ -1,48 +0,0 @@ - - - Debug - AnyCPU - Library - openHistorian.XDALink - openHistorian.XDALink - net9.0 - Debug;Development;Release - enable - true - enable - - - true - full - false - ..\..\..\Build\Output\Debug\Libraries\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - ..\..\..\Build\Output\Release\Libraries\ - TRACE - prompt - 4 - - - - - - - - - - - ..\..\Dependencies\openHistorian\openHistorian.Core.dll - - - - - - - - diff --git a/Libraries/openXDA.APIAuthentication/APIQuery.cs b/Libraries/openXDA.APIAuthentication/APIQuery.cs deleted file mode 100644 index 58f8643a..00000000 --- a/Libraries/openXDA.APIAuthentication/APIQuery.cs +++ /dev/null @@ -1,311 +0,0 @@ -//****************************************************************************************************** -// APIQuery.cs - Gbtc -// -// Copyright © 2022, 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: -// ---------------------------------------------------------------------------------------------------- -// 07/01/2022 - Christoph Lackner -// Generated original version of source code. -// -//****************************************************************************************************** - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Net.Sockets; -using System.Security.Principal; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace openXDA.APIAuthentication -{ - /// - /// Exceptions thrown during an API query. - /// - public class APIQueryException : Exception - { - /// - /// Creates a new instance of the class. - /// - /// The URL of the host that issued the failure. - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception. - public APIQueryException(string hostURL, string message, Exception innerException) - : base(message, innerException) - { - HostURL = hostURL; - } - - /// - /// The URL of the host that issued the failure. - /// - public string HostURL { get; } - } - - /// - /// Issues requests to the API using API authentication. - /// This class handles the GSF token and authentication - /// - public class APIQuery - { - #region [ Members ] - - // Nested Types - private class Host - { - public Host(string url) => - URL = url; - - public string URL { get; } - public string AntiForgeryToken { get; set; } - } - - private class HostUnreachableException : Exception - { - } - - // Fields - private int m_hostIndex; - - #endregion - - #region [ Constructors ] - - /// - /// Creates a new instance of the class. - /// - /// The API key used to identify the user of the API. - /// The token used to authenticate the user of the API. - /// URL that locates the host providing access to the API. - public APIQuery(string apiKey, string apiToken, string hostURL) - : this(apiKey, apiToken, new[] { hostURL }) - { - } - - /// - /// Creates a new instance of the class. - /// - /// The API key used to identify the user of the API. - /// The token used to authenticate the user of the API. - /// List of URLs that locate the hosts providing access to the API. - public APIQuery(string apiKey, string apiToken, IEnumerable hostURLs) - { - APIKey = apiKey; - APIToken = apiToken; - - Hosts = hostURLs - .Select(url => new Host(url)) - .ToList(); - - // Select a random host for the first API call attempt - if (Hosts.Count > 1) - Interlocked.Exchange(ref m_hostIndex, InitialHostIndex); - } - - #endregion - - #region[ Properties ] - - /// - /// The API key identifying the user of the API. - /// - public string APIKey { get; } - - /// - /// The API token used to authenticate the user. - /// - public string APIToken { get; } - - /// - /// The list of URLs that locate the hosts providing access to the API. - /// - public IEnumerable HostsURLs => Hosts - .Select(host => host.URL); - - private List Hosts { get; } - - private int InitialHostIndex => Tuple - .Create(Environment.TickCount, Environment.MachineName) - .GetHashCode() % Hosts.Count; - - #endregion - - #region [ Methods ] - - /// - /// Sends a web request to the host using the credentials for API authentication. - /// - /// Action that configures the HTTP request. - /// Path to the API endpoint locating the resource to be requested. - /// being impersonated if the APIToken supports impersonation. set to NULL for using APIAuthentication only - /// Token used to cancel the request before it has completed. - /// The HTTP response returned by the host that handled the request. - public Task SendWebRequestAsync(Action configure, string path, IPrincipal user = null, CancellationToken cancellationToken = default) => - SendWebRequestAsync(configure, path, HttpCompletionOption.ResponseContentRead, user, cancellationToken); - - /// - /// Sends a web request to the host using the credentials for API authentication. - /// - /// Action that configures the HTTP request. - /// Path to the API endpoint locating the resource to be requested. - /// When the operation should complete (as soon as a response is available or after reading the whole response content). - /// being impersonated if the APIToken supports impersonation. set to NULL for using APIAuthentication only - /// Token used to cancel the request before it has completed. - /// The HTTP response returned by the host that handled the request. - public async Task SendWebRequestAsync(Action configure, string path, HttpCompletionOption httpCompletionOption, IPrincipal user = null, CancellationToken cancellationToken = default) - { - int initialHostIndex = Interlocked.CompareExchange(ref m_hostIndex, 0, 0); - - void UpdateHostIndex(int hostIndex) => - Interlocked.CompareExchange(ref m_hostIndex, hostIndex, initialHostIndex); - - for (int i = 0; i < Hosts.Count; i++) - { - int hostIndex = (initialHostIndex + i) % Hosts.Count; - Host host = Hosts[hostIndex]; - - try - { - HttpResponseMessage response = await SendWebRequestToAsync(host, configure, path, httpCompletionOption, user, cancellationToken).ConfigureAwait(false); - UpdateHostIndex(hostIndex); - return response; - } - catch (HostUnreachableException) - { - continue; - } - catch (Exception ex) - { - UpdateHostIndex(hostIndex); - - string message = $"Failed when sending API query to host {host.URL}: {ex.Message}"; - throw new APIQueryException(host.URL, message, ex); - } - } - - return new HttpResponseMessage(HttpStatusCode.NotFound); - } - - private async Task SendWebRequestToAsync(Host host, Action configure, string path, HttpCompletionOption httpCompletionOption,IPrincipal user, CancellationToken cancellationToken) - { - using (HttpRequestMessage request = BuildRequest(host, path, configure, user)) - { - if (request.Method != HttpMethod.Get) - { - if (host.AntiForgeryToken == null) - host.AntiForgeryToken = await GetAntiForgeryTokenAsync(host, cancellationToken).ConfigureAwait(false); - - request.Headers.Add("X-GSF-Verify", host.AntiForgeryToken); - } - - return await CallAPIAsync(request, httpCompletionOption, cancellationToken).ConfigureAwait(false); - } - } - - private async Task GetAntiForgeryTokenAsync(Host host, CancellationToken cancellationToken) - { - void ConfigureTokenRequest(HttpRequestMessage tokenRequest) - { - MediaTypeWithQualityHeaderValue mediaType = new MediaTypeWithQualityHeaderValue("text/plain"); - tokenRequest.Headers.Accept.Add(mediaType); - tokenRequest.Method = HttpMethod.Get; - } - - using (HttpRequestMessage tokenRequest = BuildRequest(host, "api/rvht", ConfigureTokenRequest, null)) - using (HttpResponseMessage tokenResponse = await CallAPIAsync(tokenRequest, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false)) - { - tokenResponse.EnsureSuccessStatusCode(); - return await tokenResponse.Content.ReadAsStringAsync().ConfigureAwait(false); - } - } - - private async Task CallAPIAsync(HttpRequestMessage request, HttpCompletionOption httpCompletionOption, CancellationToken cancellationToken) - { - try - { - return await HttpClient.SendAsync(request, httpCompletionOption, cancellationToken).ConfigureAwait(false); - } - catch (HttpRequestException ex) - { - // If there are no other hosts to try, - // then we can just throw the exception as-is - if (Hosts.Count == 1) - throw; - - if (IndicatesHostIsUnreachable(ex)) - throw new HostUnreachableException(); - - throw; - } - } - - private HttpRequestMessage BuildRequest(Host host, string path, Action configure, IPrincipal user) - { - HttpRequestMessage request = new HttpRequestMessage(); - - try - { - string cleanHostURL = host.URL.Trim().TrimEnd('/'); - string cleanPath = path.Trim().TrimStart('/'); - string fullurl = $"{cleanHostURL}/{cleanPath}"; - request.RequestUri = new Uri(fullurl); - configure(request); - - string type = "XDA-API"; - string decode = $"{APIKey}:{APIToken}"; - - if (!(user is null)) - { - type= "XDA-API-IMP"; - decode = $"{decode}:{user.Identity.Name}"; - } - - Encoding utf8 = new UTF8Encoding(false); - byte[] credentialData = utf8.GetBytes(decode); - string credentials = Convert.ToBase64String(credentialData); - request.Headers.Authorization = new AuthenticationHeaderValue(type, credentials); - - return request; - } - catch - { - request.Dispose(); - throw; - } - } - - private bool IndicatesHostIsUnreachable(Exception ex) - { - if (ex is SocketException socketException) - return socketException.SocketErrorCode == SocketError.ConnectionRefused || socketException.SocketErrorCode == SocketError.TimedOut; - if (ex.InnerException is null) - return false; - return IndicatesHostIsUnreachable(ex.InnerException); - } - - #endregion - - #region [ Static ] - - private static HttpClient HttpClient { get; } - = new HttpClient(); - - #endregion - } -} diff --git a/Libraries/openXDA.APIAuthentication/IAPICredentialRetriever.cs b/Libraries/openXDA.APIAuthentication/IAPICredentialRetriever.cs deleted file mode 100644 index 3b4d8b04..00000000 --- a/Libraries/openXDA.APIAuthentication/IAPICredentialRetriever.cs +++ /dev/null @@ -1,65 +0,0 @@ -//****************************************************************************************************** -// XDAAPIHelper.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 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: -// ---------------------------------------------------------------------------------------------------- -// 10/17/2025 - Gabriel Santos -// Generated original version of source code. -// -//****************************************************************************************************** - -using System.Security.Claims; - -namespace openXDA.APIAuthentication -{ - /// - /// Interface of credential retriever object for . - /// - public interface IAPICredentialRetriever - { - #region [ Properties ] - - /// - /// API Token used to access OpenXDA - /// - string Token { get; } - - /// - /// API Key used to access OpenXDA - /// - string Key { get; } - - /// - /// API Key used to access OpenXDA - /// - string Host { get; } - - /// - /// Refreshes the settings from the original source. - /// - /// A flag indicating if the operation was successful. - bool TryRefreshSettings(); - - /// - /// Retrieves customer key from the claims principle. - /// - /// A value of signifies the current user is authorized to view any object the controller may retrieve. - /// A flag indicating if the operation was successful. - bool TryRetrieveCustomer(ClaimsPrincipal principal, out string customerKey); - - #endregion - } -} diff --git a/Libraries/openXDA.APIAuthentication/Properties/AssemblyInfo.cs b/Libraries/openXDA.APIAuthentication/Properties/AssemblyInfo.cs deleted file mode 100644 index 33057741..00000000 --- a/Libraries/openXDA.APIAuthentication/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("openXDA.APIAuthentication")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("GPA")] -[assembly: AssemblyProduct("openXDA.APIAuthentication")] -[assembly: AssemblyCopyright("Copyright © 2022")] -[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("93FF1E73-CD8B-4F01-89BC-A4A242FA06A6")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("3.0.5.93")] -[assembly: AssemblyVersion("3.0.5.93")] -[assembly: AssemblyFileVersion("3.0.5.93")] diff --git a/Libraries/openXDA.APIAuthentication/XDAAPI.cs b/Libraries/openXDA.APIAuthentication/XDAAPI.cs deleted file mode 100644 index 46315a82..00000000 --- a/Libraries/openXDA.APIAuthentication/XDAAPI.cs +++ /dev/null @@ -1,232 +0,0 @@ -//****************************************************************************************************** -// XDAAPI.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 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: -// ---------------------------------------------------------------------------------------------------- -// 10/21/2025 - Gabriel Santos -// Generated original version of source code. -// -//****************************************************************************************************** - -using System; -using System.Collections.Generic; -using System.IO; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Security.Claims; -using System.Threading.Tasks; -using Newtonsoft.Json; - -namespace openXDA.APIAuthentication -{ - /// - /// Helper class that provides openXDA API Calls. - /// - public class XDAAPI - { - #region [ Properties ] - /// - /// Tells users if the helper has been intialized or not. - /// - public bool IsIntialized { get; private set; } - - /// - /// API Token used to access OpenXDA - /// - public string Token => SettingsRetriever.Token; - - /// - /// API Key used to access OpenXDA - /// - public string Key => SettingsRetriever.Key; - - /// - /// API Key used to access OpenXDA - /// - public string Host => SettingsRetriever.Host; - - /// - /// Object that retireves the token,key, and host of the helper. - /// - private IAPICredentialRetriever SettingsRetriever { get; set; } - - #endregion - - #region [ Methods ] - /// - /// Creates API object and registers retriever. - /// - public XDAAPI(IAPICredentialRetriever retriever) - { - SettingsRetriever = retriever; - if (!TryRefreshSettings()) - throw new ArgumentException("Unable to load settings from retriever."); - } - - /// - /// Recalls the setup settings function from to refetch settings. - /// - public bool TryRefreshSettings() - { - bool success = SettingsRetriever.TryRefreshSettings(); - if (success) - IsIntialized = true; - return success; - } - - /// - /// Retrieves customer key from the claims principle. - /// - /// A value of signifies the current user is authorized to view any object the controller may retrieve. - /// A flag indicating if the operation was successful. - public bool TryRetrieveCustomer(ClaimsPrincipal principal, out string customerKey) => SettingsRetriever.TryRetrieveCustomer(principal, out customerKey); - - /// - /// Gets Response Task from XDA - /// - /// Path to specific API request - /// The of the request - /// response as a - public Task GetResponseTask(string requestURI, HttpContent content = null) - { - if (!IsIntialized) - throw new InvalidOperationException("API helper has not been intialized."); - - APIQuery query = new APIQuery(Key, Token, Host.Split(';')); - - void ConfigureRequest(HttpRequestMessage request) - { - if (content == null) - { - request.Method = HttpMethod.Get; - request.Headers.Accept.Clear(); - request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - } - else - { - request.Method = HttpMethod.Post; - request.Content = content; - } - } - - return query.SendWebRequestAsync(ConfigureRequest, requestURI); - } - - /// - /// Makes a Get Request to OpenXDA to obtain a string - /// - /// Path to specific API request - /// string - public async Task GetAsync(string requestURI) - { - if (!IsIntialized) - throw new InvalidOperationException("API helper has not been intialized."); - - APIQuery query = new APIQuery(Key, Token, Host.Split(';')); - - void ConfigureRequest(HttpRequestMessage request) - { - request.Method = HttpMethod.Get; - request.Headers.Accept.Clear(); - request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - } - - HttpResponseMessage responseMessage = await query.SendWebRequestAsync(ConfigureRequest, requestURI).ConfigureAwait(false); - responseMessage.EnsureSuccessStatusCode(); - return await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false); - } - - /// - /// Makes a Get Request to OpenXDA to obtain a stream - /// - /// Path to specific API request - /// stream - public async Task GetStreamAsync(string requestURI) - { - if (!IsIntialized) - throw new InvalidOperationException("API helper has not been intialized."); - - APIQuery query = new APIQuery(Key, Token, Host.Split(';')); - - void ConfigureRequest(HttpRequestMessage request) - { - request.Method = HttpMethod.Get; - request.Headers.Accept.Clear(); - request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - } - - HttpResponseMessage responseMessage = await query.SendWebRequestAsync(ConfigureRequest, requestURI).ConfigureAwait(false); - responseMessage.EnsureSuccessStatusCode(); - return await responseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false); - } - - /// - /// Makes a Get Request to OpenXDA to obtain an object - /// - /// Path to specific API request - /// a object - public async Task GetAsync(string requestURI) - { - if (!IsIntialized) - throw new InvalidOperationException("API helper has not been intialized."); - - string result = await GetAsync(requestURI).ConfigureAwait(false); - T resultObject = JsonConvert.DeserializeObject(result); - return resultObject; - } - - /// - /// Makes Post request on OpenXDA - /// - /// Path to specific API request - /// The of the request - /// response as a - public async Task PostAsync(string requestURI, HttpContent content) - { - if (!IsIntialized) - throw new InvalidOperationException("API helper has not been intialized."); - - APIQuery query = new APIQuery(Key, Token, Host.Split(';')); - - void ConfigureRequest(HttpRequestMessage request) - { - request.Method = HttpMethod.Post; - request.Content = content; - } - - HttpResponseMessage responseMessage = await query.SendWebRequestAsync(ConfigureRequest, requestURI).ConfigureAwait(false); - return await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false); - } - - /// - /// Makes Post request on OpenXDA - /// - /// Path to specific API request - /// The of the request - /// response as a - public async Task PostAllAsync(string endpoint, HttpContent content) => await PostAsync(endpoint, content).ConfigureAwait(false); - - /// - /// Makes a Get Request to OpenXDA to obtain a objects - /// - /// Path to specific API request - /// a object - public async Task> GetAllAsync(string requestURI) => await GetAsync>(requestURI).ConfigureAwait(false); - - #endregion - - } -} diff --git a/Libraries/openXDA.APIAuthentication/XDAAPIHelper.cs b/Libraries/openXDA.APIAuthentication/XDAAPIHelper.cs deleted file mode 100644 index ace01df4..00000000 --- a/Libraries/openXDA.APIAuthentication/XDAAPIHelper.cs +++ /dev/null @@ -1,149 +0,0 @@ -//****************************************************************************************************** -// XDAAPIHelper.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 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/15/2024 - Gabriel Santos -// Generated original version of source code. -// -//****************************************************************************************************** - -using System.Collections.Generic; -using System.IO; -using System.Net.Http; -using System.Security.Claims; -using System.Threading.Tasks; - -namespace openXDA.APIAuthentication -{ - /// - /// Wrapper class that turns into a static for easy use across an application. - /// - /// - /// Must be initialized with the method to fetch settings. - /// - public static class XDAAPIHelper - { - #region [ Properties ] - /// - /// Tells users if the helper has been intialized or not. - /// - public static bool IsIntialized { - get - { - return !(API is null); - } - } - - /// - /// API Token used to access OpenXDA - /// - public static string Token => API.Token; - - /// - /// API Key used to access OpenXDA - /// - public static string Key => API.Key; - - /// - /// API Key used to access OpenXDA - /// - public static string Host => API.Host; - - /// - /// Object that retireves the token, key, and host of the helper. - /// - private static XDAAPI API { get; set; } - - #endregion - - #region [ Methods ] - /// - /// Function for setup of static helper. This must be ran once before using the helper. - /// - public static void InitializeHelper(IAPICredentialRetriever retriever) - { - API = new XDAAPI(retriever); - } - - /// - /// Recalls the setup settings function from to refetch settings. - /// - public static bool TryRefreshSettings() => API.TryRefreshSettings(); - - /// - /// Retrieves customer key from the claims principle. - /// - /// A value of signifies the current user is authorized to view any object the controller may retrieve. - /// A flag indicating if the operation was successful. - public static bool TryRetrieveCustomer(ClaimsPrincipal principal, out string customerKey) => API.TryRetrieveCustomer(principal, out customerKey); - - /// - /// Gets Response Task from XDA - /// - /// Path to specific API request - /// The of the request - /// response as a - public static Task GetResponseTask(string requestURI, HttpContent content = null) => API.GetResponseTask(requestURI, content); - - /// - /// Makes a Get Request to OpenXDA to obtain a string - /// - /// Path to specific API request - /// string - public static async Task GetAsync(string requestURI) => await API.GetAsync(requestURI).ConfigureAwait(false); - - /// - /// Makes a Get Request to OpenXDA to obtain a stream - /// - /// Path to specific API request - /// stream - public static async Task GetStreamAsync(string requestURI) => await API.GetStreamAsync(requestURI).ConfigureAwait(false); - - /// - /// Makes a Get Request to OpenXDA to obtain an object - /// - /// Path to specific API request - /// a object - public static async Task GetAsync(string requestURI) => await API.GetAsync(requestURI).ConfigureAwait(false); - - /// - /// Makes Post request on OpenXDA - /// - /// Path to specific API request - /// The of the request - /// response as a - public static async Task PostAsync(string requestURI, HttpContent content) => await API.PostAsync(requestURI, content).ConfigureAwait(false); - - /// - /// Makes Post request on OpenXDA - /// - /// Path to specific API request - /// The of the request - /// response as a - public static async Task PostAllAsync(string endpoint, HttpContent content) => await API.PostAllAsync(endpoint, content).ConfigureAwait(false); - - /// - /// Makes a Get Request to OpenXDA to obtain a objects - /// - /// Path to specific API request - /// a object - public static async Task> GetAllAsync(string requestURI) => await API.GetAllAsync(requestURI).ConfigureAwait(false); - - #endregion - - } -} diff --git a/Libraries/openXDA.APIAuthentication/openXDA.APIAuthentication.csproj b/Libraries/openXDA.APIAuthentication/openXDA.APIAuthentication.csproj deleted file mode 100644 index facf64dd..00000000 --- a/Libraries/openXDA.APIAuthentication/openXDA.APIAuthentication.csproj +++ /dev/null @@ -1,26 +0,0 @@ - - - net9.0 - Debug;Development;Release - Library - false - openXDA.APIAuthentication - GPA - openXDA.APIAuthentication - Copyright © 2022 - - - ..\..\..\Build\Output\Debug\Libraries\ - ..\..\..\Build\Output\Debug\Libraries\openXDA.APIAuthentication.xml - - - ..\..\..\Build\Output\Release\Libraries\ - ..\..\..\Build\Output\Release\Libraries\openXDA.APIAuthentication.xml - - - - - - - - \ No newline at end of file diff --git a/Libraries/openXDA.Configuration/BreakerSection.cs b/Libraries/openXDA.Configuration/BreakerSection.cs deleted file mode 100644 index d6d07ff3..00000000 --- a/Libraries/openXDA.Configuration/BreakerSection.cs +++ /dev/null @@ -1,119 +0,0 @@ -//****************************************************************************************************** -// BreakerSection.cs - Gbtc -// -// Copyright © 2021, 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/02/2015 - Stephen C. Wills -// Generated original version of source code. -// -//****************************************************************************************************** - -using System.ComponentModel; -using System.Configuration; - -namespace openXDA.Configuration -{ - public class BreakerSection - { - #region [ Members ] - - // Constants - public const string CategoryName = "Breakers"; - - #endregion - - #region [ Properties ] - - /// - /// Gets or sets the flag that determines whether to apply additional logic - /// to help obtain more accurate breaker timing results in cases where DC - /// current gradually drains from the line after the breaker is open. - /// - [Setting] - [DefaultValue(false)] - public bool ApplyDCOffsetLogic { get; set; } - - /// - /// Gets or sets the size of the window, in cycles, - /// to use when applying the DC offset logic. - /// - [Setting] - [DefaultValue(9.0D / 8.0D)] - public double DCOffsetWindowSize { get; set; } - - /// - /// Gets or sets the maximum number of cycles that a breaker - /// operation's timing can exceed the configured breaker speed. - /// - [Setting] - [DefaultValue(0.0D)] - public double LateBreakerThreshold { get; set; } - - /// - /// Gets or sets the minimum number of cycles that the breaker is expected - /// to remain closed after receiving the trip coil energized signal. - /// - /// - /// This value helps to prevent phase timing calculations when the current - /// signal is not large enough to detect the point at which the breaker opened. - /// - [Setting] - [DefaultValue(0.0D)] - public double MinCyclesBeforeOpen { get; set; } - - /// - /// Gets or sets the minimum amount of time, in cycles, the system must wait - /// before automatically reclosing after a breaker operation has occurred. - /// - [Setting] - [DefaultValue(15.0D)] - public double MinWaitBeforeReclose { get; set; } - - /// - /// Gets or sets the maximum RMS current, in amps, - /// at which the breaker can be considered open. - /// - [Setting] - [DefaultValue(20.0D)] - public double OpenBreakerThreshold { get; set; } - - /// - /// Gets or sets the minimum duration, in cycles, for which the current must remain - /// at zero in order for a subsequent current spike to be considered a restrike. - /// - [Setting] - [DefaultValue(0.125D)] - public double MinCyclesBeforeRestrike { get; set; } - - /// - /// Gets or sets the maximum duration, in cycles, for which the current must remain - /// at zero in order for a subsequent current spike to be considered a restrike. - /// - [Setting] - [DefaultValue(70.0D)] - public double MaxCyclesBeforeRestrike { get; set; } - - /// - /// Gets or sets the flag that determines whether the system should apply the - /// BreakerOpen event type to events. - /// - [Setting] - [DefaultValue(true)] - public bool BreakerOpenEventTypeEnabled { get; set; } - - #endregion - } -} diff --git a/Libraries/openXDA.Configuration/COMTRADESection.cs b/Libraries/openXDA.Configuration/COMTRADESection.cs deleted file mode 100644 index e457f275..00000000 --- a/Libraries/openXDA.Configuration/COMTRADESection.cs +++ /dev/null @@ -1,67 +0,0 @@ -//****************************************************************************************************** -// COMTRADESection.cs - Gbtc -// -// Copyright © 2021, 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/07/2019 - Stephen C. Wills -// Generated original version of source code. -// -//****************************************************************************************************** - -using Gemstone.Configuration; -using System.ComponentModel; -using System.Configuration; - -namespace openXDA.Configuration -{ - public class COMTRADESection - { - #region [ Members ] - - // Constants - public const string CategoryName = "COMTRADE"; - - #endregion - - #region [ Properties ] - - public TimeSpan MinWaitTime { get; set; } - - [Setting] - [DefaultValue(false)] - public bool WaitForINF { get; set; } - - [Setting] - [DefaultValue(false)] - public bool UseRelaxedValidation { get; set; } - - [Setting] - [DefaultValue("SELECT ID FROM Meter WHERE Make = 'SEL'")] - public string Root2AdjustmentQuery { get; set; } - - [Setting] - [SettingName(nameof(MinWaitTime))] - [DefaultValue(15.0D)] - [EditorBrowsable(EditorBrowsableState.Never)] - public double _MinWaitTime - { - get => MinWaitTime.TotalSeconds; - set => MinWaitTime = TimeSpan.FromSeconds(value); - } - - #endregion - } -} diff --git a/Libraries/openXDA.Configuration/DataAnalysisSection.cs b/Libraries/openXDA.Configuration/DataAnalysisSection.cs deleted file mode 100644 index d62142e7..00000000 --- a/Libraries/openXDA.Configuration/DataAnalysisSection.cs +++ /dev/null @@ -1,120 +0,0 @@ -//****************************************************************************************************** -// DataAnalysisSection.cs - Gbtc -// -// Copyright © 2021, 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/16/2021 - Stephen C. Wills -// Generated original version of source code. -// -//****************************************************************************************************** - -using System.ComponentModel; -using System.Configuration; - -namespace openXDA.Configuration -{ - public class DataAnalysisSection - { - public const string CategoryName = "DataAnalysis"; - - /// - /// Gets or sets the threshold, in amps, at which the - /// current exceeds engineering reasonableness. - /// - [Setting] - [DefaultValue(0.1D)] - public double InterruptionThreshold { get; set; } - - /// - /// Gets or sets the units of measure to use - /// for lengths (line length and fault distance). - /// - [Setting] - [DefaultValue("miles")] - public string LengthUnits { get; set; } - - /// - /// Gets or sets the threshold, in amps, at which the - /// current exceeds engineering reasonableness. - /// - [Setting] - [DefaultValue(1000000.0D)] - public double MaxCurrent { get; set; } - - /// - /// Gets or sets the maximum duration, in seconds, - /// of the events processed by openXDA. - /// - [Setting] - [DefaultValue(0.0D)] - public double MaxEventDuration { get; set; } - - /// - /// Gets or sets the maximum number of hours beyond the current system time - /// before the time of the record indicates that the data is unreasonable. - /// - [Setting] - [DefaultValue(0.0D)] - public double MaxTimeOffset { get; set; } - - /// - /// Gets or sets the per-unit threshold at which the - /// voltage exceeds engineering reasonableness. - /// - [Setting] - [DefaultValue(2.0D)] - public double MaxVoltage { get; set; } - - /// - /// Gets or sets the maximum number of hours prior to the current system time - /// before the time of the record indicates that the data is unreasonable. - /// - [Setting] - [DefaultValue(0.0D)] - public double MinTimeOffset { get; set; } - - /// - /// Gets or sets the threshold, in amps, at which the - /// current exceeds engineering reasonableness. - /// - [Setting] - [DefaultValue(0.9D)] - public double SagThreshold { get; set; } - - /// - /// Gets or sets the threshold, in amps, at which the - /// current exceeds engineering reasonableness. - /// - [Setting] - [DefaultValue(1.1D)] - public double SwellThreshold { get; set; } - - /// - /// Gets or sets the system frequency. - /// - [Setting] - [DefaultValue(60.0D)] - public double SystemFrequency { get; set; } - - /// - /// Gets or sets the maximum distance, in seconds, - /// between a meter's clock and real time. - /// - [Setting] - [DefaultValue(0.5D)] - public double TimeTolerance { get; set; } - } -} diff --git a/Libraries/openXDA.Configuration/DataPusherSection.cs b/Libraries/openXDA.Configuration/DataPusherSection.cs deleted file mode 100644 index 78cd591d..00000000 --- a/Libraries/openXDA.Configuration/DataPusherSection.cs +++ /dev/null @@ -1,37 +0,0 @@ -//****************************************************************************************************** -// DataPusherSection.cs - Gbtc -// -// Copyright © 2021, 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/08/2021 - Stephen C. Wills -// Generated original version of source code. -// -//****************************************************************************************************** - -using System.ComponentModel; -using System.Configuration; - -namespace openXDA.Configuration -{ - public class DataPusherSection - { - public const string CategoryName = "DataPusher"; - - [Setting] - [DefaultValue("systemSettings")] - public string ConnectionString { get; set; } - } -} diff --git a/Libraries/openXDA.Configuration/EMAXSection.cs b/Libraries/openXDA.Configuration/EMAXSection.cs deleted file mode 100644 index 22ba7df7..00000000 --- a/Libraries/openXDA.Configuration/EMAXSection.cs +++ /dev/null @@ -1,66 +0,0 @@ -//****************************************************************************************************** -// EMAXSection.cs - Gbtc -// -// Copyright © 2021, 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/25/2017 - Stephen C. Wills -// Generated original version of source code. -// -//****************************************************************************************************** - -using System.ComponentModel; -using System.Configuration; - -namespace openXDA.Configuration -{ - public class EMAXSection - { - #region [ Members ] - - // Constants - public const string CategoryName = "EMAX"; - - #endregion - - #region [ Properties ] - - /// - /// Gets or sets the value that indicates whether timestamp - /// correction should be applied when reading native EMAX files. - /// - [Setting] - [DefaultValue(true)] - public bool ApplyTimestampCorrection { get; set; } - - /// - /// Gets or sets the value that indicates whether value - /// correction should be applied when reading native EMAX files. - /// - [Setting] - [DefaultValue(true)] - public bool ApplyValueCorrection { get; set; } - - /// - /// Gets or sets the path to the directory where COMTRADE - /// exports should be located after reading an EMAX file. - /// - [Setting] - [DefaultValue("")] - public string COMTRADEExportDirectory { get; set; } - - #endregion - } -} diff --git a/Libraries/openXDA.Configuration/EPRICapBankAnalyticSection.cs b/Libraries/openXDA.Configuration/EPRICapBankAnalyticSection.cs deleted file mode 100644 index 41d0f1d5..00000000 --- a/Libraries/openXDA.Configuration/EPRICapBankAnalyticSection.cs +++ /dev/null @@ -1,89 +0,0 @@ -//****************************************************************************************************** -// EPRICapBankAnalyticSection.cs - Gbtc -// -// Copyright © 2021, 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/04/2020 - C. Lackner -// Generated original version of source code. -// -//****************************************************************************************************** - -using System.ComponentModel; -using System.Configuration; - -namespace openXDA.Configuration -{ - public class EPRICapBankAnalyticSection - { - public const string CategoryName = "EPRICapBankAnalytic"; - - [Setting] - [DefaultValue(false)] - public bool Enabled { get; set; } - - [Setting] - [DefaultValue("./CapBankAnalysis/Data/")] - public string DataFileLocation { get; set; } - - [Setting] - [DefaultValue("./CapBankAnalysis/Parameter/")] - public string ParameterFileLocation { get; set; } - - [Setting] - [DefaultValue("./CapBankAnalysis/Results/")] - public string ResultFileLocation { get; set; } - - [Setting] - [DefaultValue(500.0D)] - public double VThreshhold { get; set; } - - [Setting] - [DefaultValue(4.0D)] - public double IThreshhold { get; set; } - - [Setting] - [DefaultValue(10.0D)] - public double THDLimit { get; set; } - - [Setting] - [DefaultValue(1.0D)] - public double Toffset { get; set; } - - [Setting] - [DefaultValue(true)] - public bool EvalPreInsertion { get; set; } - - [Setting] - [DefaultValue("openXDA.CapSwitchAnalysis.dll")] - public string AnalysisRoutineAssembly { get; set; } - - [Setting] - [DefaultValue("openXDA.CapSwitchAnalysis.Analyzer.RunAnalytic")] - public string AnalysisRoutineMethod { get; set; } - - [Setting] - [DefaultValue("")] - public string Analyzer { get; set; } - - [Setting] - [DefaultValue(1200000)] - public int Delay { get; set; } - - [Setting] - [DefaultValue(false)] - public bool KeepFiles { get; set; } - } -} diff --git a/Libraries/openXDA.Configuration/Edition/EditionChecker.cs b/Libraries/openXDA.Configuration/Edition/EditionChecker.cs deleted file mode 100644 index 2d4d7d73..00000000 --- a/Libraries/openXDA.Configuration/Edition/EditionChecker.cs +++ /dev/null @@ -1,98 +0,0 @@ -//****************************************************************************************************** -// EditionChecker.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 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: -// ---------------------------------------------------------------------------------------------------- -// 10/07/2024 - Gabriel Santos -// Generated original version of source code. -// -//****************************************************************************************************** - -using System; -using Gemstone.Configuration; -using Gemstone.Data; -using Gemstone.Data.Model; -using openXDA.Model; - -namespace openXDA.Configuration -{ - /// - /// Enum to hold different editions of XDA - /// - public enum Edition - { - Base = 0, - Enterprise = 1 - } - - /// - /// Helper class that checks the edition of XDA - /// - public static class EditionChecker - { - /// - /// Current XDA Edition - /// - private static Edition CheckedEdition { get; set; } - // ToDo: Think about replacing this with a hash function instead - private static Guid MagicGuid = new Guid("aa644f0c-a82b-4cf1-ba6e-be3a1b05eb6a"); - - static EditionChecker() - { - UpdateEdition(); - } - - /// - /// Checks to see if current edition is equal to or higher than supplied - /// - /// response as a - public static bool CheckEdition(Edition editionLevel) - { - return CheckedEdition.CompareTo(editionLevel) >= 0; - } - - /// - /// Updates the current edition based on the value in the database - /// - public static void UpdateEdition() - { - using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) - { - Guid value; - try - { - value = new Guid(new TableOperations(connection).QueryRecordWhere($"Name = 'System.EditionKey'")?.Value); - } - catch - { - value = new Guid("00000000-0000-0000-0000-000000000000"); - } - if (value == MagicGuid) CheckedEdition = Edition.Enterprise; - else CheckedEdition = Edition.Base; - } - } - - /// - /// Returns the current edition - /// - /// response as a - public static Edition GetEdition() - { - return CheckedEdition; - } - - } -} diff --git a/Libraries/openXDA.Configuration/Edition/HttpEditionFilterAttribute.cs b/Libraries/openXDA.Configuration/Edition/HttpEditionFilterAttribute.cs deleted file mode 100644 index 79368e28..00000000 --- a/Libraries/openXDA.Configuration/Edition/HttpEditionFilterAttribute.cs +++ /dev/null @@ -1,57 +0,0 @@ -//****************************************************************************************************** -// HttpEditionFilterAttirbute.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 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: -// ---------------------------------------------------------------------------------------------------- -// 10/07/2024 - Gabriel Santos -// Generated original version of source code. -// -//****************************************************************************************************** - -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Filters; - -namespace openXDA.Configuration -{ - /// - /// Defines an attribute that will disallow an http method if it does not pass the edition requirement. - /// - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] - public class HttpEditionFilterAttribute : ActionFilterAttribute - { - /// - /// Gets the edition needed specified by attribute construction. - /// - public Edition EditionRequred { get; } - public HttpEditionFilterAttribute(Edition edition) - { - EditionRequred = edition; - } - - /// - /// Creates a new . - /// - /// Edition this http method requires. - public override void OnActionExecuting(ActionExecutingContext actionContext) - { - // Wrong edition: skip the method and return forbidden - if (!EditionChecker.CheckEdition(EditionRequred)) - actionContext.Result = new ForbidResult(); - - base.OnActionExecuting(actionContext); - } - } -} diff --git a/Libraries/openXDA.Configuration/EmailSection.cs b/Libraries/openXDA.Configuration/EmailSection.cs deleted file mode 100644 index c092607c..00000000 --- a/Libraries/openXDA.Configuration/EmailSection.cs +++ /dev/null @@ -1,111 +0,0 @@ -//****************************************************************************************************** -// EmailSection.cs - Gbtc -// -// Copyright © 2015, 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/02/2015 - Stephen C. Wills -// Generated original version of source code. -// -//****************************************************************************************************** - -using Gemstone.StringExtensions; -using System.ComponentModel; -using System.Configuration; -using System.Security; - -namespace openXDA.Configuration -{ - public class EmailSection - { - #region [ Members ] - - // Constants - public const string CategoryName = "Email"; - - #endregion - - #region [ Properties ] - - /// - /// Gets or sets the address used by the administrator of the email system. - /// - [Setting] - [DefaultValue("xda-admin@gridprotectionalliance.org")] - public string AdminAddress { get; set; } - - /// - /// Gets or sets the address used in the To line when blind-copying recipients. - /// - [Setting] - [DefaultValue("")] - public string BlindCopyAddress { get; set; } - - /// - /// Gets or sets the hostname or IP address of the SMTP server to - /// use for sending automated email notifications when faults occur. - /// - [Setting] - [DefaultValue("")] - public string SMTPServer { get; set; } - - /// - /// Gets or sets the email address used when sending automated email notifications. - /// - [Setting] - [DefaultValue("openXDA@gridprotectionalliance.org")] - public string FromAddress { get; set; } - - /// - /// Gets or sets the username used to authenticate to the SMTP server. - /// - [Setting] - [DefaultValue("")] - public string Username { get; set; } - - /// - /// Gets or sets the password used to authenticate to the SMTP server. - /// - [Setting] - [DefaultValue(null)] - public string Password - { - get => SecurePassword.ToUnsecureString() ?? ""; - set => SecurePassword = value?.ToSecureString() ?? new SecureString(); - } - - /// - /// Gets or sets the flag that determines whether to enable - /// SSL when establishing communications with the SMTP server. - /// - [Setting] - [DefaultValue(false)] - public bool EnableSSL { get; set; } - - /// - /// Sets the minimum number of samples in an email chart - /// - [Setting] - [DefaultValue(-1)] - public int MinimumChartSamplesPerCycle { get; set; } - - /// - /// Gets the password as a . - /// - public SecureString SecurePassword { get; private set; } - - #endregion - } -} diff --git a/Libraries/openXDA.Configuration/EventEmailSection.cs b/Libraries/openXDA.Configuration/EventEmailSection.cs deleted file mode 100644 index 64f48794..00000000 --- a/Libraries/openXDA.Configuration/EventEmailSection.cs +++ /dev/null @@ -1,68 +0,0 @@ -//****************************************************************************************************** -// EventEmailSection.cs - Gbtc -// -// Copyright © 2021, 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/13/2021 - Stephen C. Wills -// Generated original version of source code. -// -//****************************************************************************************************** - -using System.ComponentModel; -using System.Configuration; -using Gemstone.Configuration; - -namespace openXDA.Configuration -{ - public class EventEmailSection - { - public const string CategoryName = "EventEmail"; - - [Setting] - [DefaultValue(false)] - public bool Enabled { get; set; } - - [Setting] - [DefaultValue(0)] - public int MaxEmailCount { get; set; } - - [Setting] - [SettingName(nameof(MaxEmailSpan))] - [DefaultValue(0.0D)] - public double MaxEmailSeconds - { - get => MaxEmailSpan.TotalSeconds; - set => MaxEmailSpan = TimeSpan.FromSeconds(value); - } - - [Setting] - [DefaultValue("http://localhost:8989/RestoreEventEmail.cshtml")] - public string RestorationURL { get; set; } - - public TimeSpan MaxEmailSpan { get; set; } - - [Setting] - [SettingName(nameof(EmailDuplicateThresholdMinutes))] - [DefaultValue(0.0D)] - public double EmailDuplicateThresholdMinutes - { - get => EmailDuplicateThreshold.TotalMinutes; - set => EmailDuplicateThreshold = TimeSpan.FromMinutes(value); - } - - public TimeSpan EmailDuplicateThreshold { get; set; } - } -} diff --git a/Libraries/openXDA.Configuration/FaultLocationSection.cs b/Libraries/openXDA.Configuration/FaultLocationSection.cs deleted file mode 100644 index a9e6e72c..00000000 --- a/Libraries/openXDA.Configuration/FaultLocationSection.cs +++ /dev/null @@ -1,104 +0,0 @@ -//****************************************************************************************************** -// FaultLocationSection.cs - Gbtc -// -// Copyright © 2021, 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/02/2015 - Stephen C. Wills -// Generated original version of source code. -// -//****************************************************************************************************** - -using System.ComponentModel; -using System.Configuration; - -namespace openXDA.Configuration -{ - public enum FaultCalculationCycleMethod - { - MaxCurrent, - LastFaultedCycle, - LastFaultedCycleExceptAirGapRes - } - - public class FaultLocationSection - { - #region [ Members ] - - // Constants - public const string CategoryName = "FaultLocation"; - - #endregion - - #region [ Properties ] - - [Setting] - [DefaultValue(5.0D)] - public double PrefaultTrigger { get; set; } - - [Setting] - [DefaultValue(50.0D)] - public double PrefaultTriggerAdjustment { get; set; } - - [Setting] - [DefaultValue(1.05D)] - public double MaxFaultDistanceMultiplier { get; set; } - - [Setting] - [DefaultValue(-0.05D)] - public double MinFaultDistanceMultiplier { get; set; } - - [Setting] - [DefaultValue(FaultCalculationCycleMethod.MaxCurrent)] - public FaultCalculationCycleMethod FaultCalculationCycleMethod { get; set; } - - [Setting] - [DefaultValue(1.0D)] - public double MinFaultSegmentCycles { get; set; } - - [Setting] - [DefaultValue(10)] - public int FaultClearingAdjustmentSamples { get; set; } - - [Setting] - [DefaultValue(false)] - public bool WarnMissingDetectionLogic { get; set; } - - /// - /// Indicates whether to use the default fault detection logic - /// when the line-specific fault detection logic fails. - /// - [Setting] - [DefaultValue(true)] - public bool UseDefaultFaultDetectionLogic { get; set; } - - /// - /// Indicates whether to ignore line-specific fault detection logic. - /// - [Setting] - [DefaultValue(false)] - public bool IgnoreFaultDetectionLogic { get; set; } - - [Setting] - [DefaultValue(0.8D)] - public double FaultedVoltageThreshold { get; set; } - - [Setting] - [DefaultValue(0.001D)] - public double GroundedFaultVoltageThreshold { get; set; } - - #endregion - } -} diff --git a/Libraries/openXDA.Configuration/FileEnumeratorSection.cs b/Libraries/openXDA.Configuration/FileEnumeratorSection.cs deleted file mode 100644 index 0c403c30..00000000 --- a/Libraries/openXDA.Configuration/FileEnumeratorSection.cs +++ /dev/null @@ -1,80 +0,0 @@ -//****************************************************************************************************** -// FileEnumeratorSection.cs - Gbtc -// -// Copyright © 2021, 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/16/2021 - Stephen C. Wills -// Generated original version of source code. -// -//****************************************************************************************************** - -using System.ComponentModel; -using System.Configuration; -using Gemstone.IO; - -namespace openXDA.Configuration -{ - //copied from GSF.IO as there was no equivalent in Gemstone.IO - public enum FileEnumerationStrategy - { - // - // Summary: - // Enumerate all files sequentially. - Sequential, - // - // Summary: - // Enumerates the watch directories in parallel, but subdirectories are processed - // sequentially. - ParallelWatchDirectories, - // - // Summary: - // Enumerates every directory, including subdirectories, in parallel. - ParallelSubdirectories, - // - // Summary: - // Does not enumerate directories, relying only on the file watcher to handle file - // processing events. - None - } - - public class FileEnumeratorSection - { - public const string CategoryName = "FileEnumerator"; - - /// - /// Gets or sets the patterns used to determine which - /// folders to skip when enumerating watch directories. - /// - [Setting] - [DefaultValue("")] - public string FolderExclusion { get; set; } - - /// - /// Gets or sets the flag that determines whether the file watcher - /// should raise events for enumerated files in alphabetical order. - /// - [Setting] - [DefaultValue(false)] - public bool OrderedEnumeration { get; set; } - - /// - /// Gets or sets the strategy used for enumeration of files in the file watcher. - /// - [Setting] - [DefaultValue(FileEnumerationStrategy.ParallelSubdirectories)] - public FileEnumerationStrategy Strategy { get; set; } - } -} diff --git a/Libraries/openXDA.Configuration/FileProcessorSection.cs b/Libraries/openXDA.Configuration/FileProcessorSection.cs deleted file mode 100644 index 2d89b5aa..00000000 --- a/Libraries/openXDA.Configuration/FileProcessorSection.cs +++ /dev/null @@ -1,91 +0,0 @@ -//****************************************************************************************************** -// FileProcessorSection.cs - Gbtc -// -// Copyright © 2021, 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/16/2021 - Stephen C. Wills -// Generated original version of source code. -// -//****************************************************************************************************** - -using System; -using System.ComponentModel; -using System.Configuration; - -namespace openXDA.Configuration -{ - public class FileProcessorSection - { - public const string CategoryName = "FileProcessor"; - - /// - /// Gets or sets the pattern used to parse file paths in - /// order to identify the meter that the file came from. - /// - [Setting] - [DefaultValue(/*lang=regex*/ @"(?[^\\]+)\\[^\\]+$")] - public string FilePattern { get; set; } - - /// - /// Gets or sets the pattern that identifies the part of the file path that - /// - [Setting] - [DefaultValue(/*lang=regex*/ @"^(?.*)\.[^\.]*$")] - public string FileGroupingPattern { get; set; } - - /// - /// Gets or sets the maximum number of hours prior to the current system time - /// before the file creation time indicates that the data should not be processed. - /// - [Setting] - [DefaultValue(0.0D)] - public double MaxFileCreationTimeOffset { get; set; } - - /// - /// Gets or sets the maximum file size, in MB, - /// of the files processed by openXDA. - /// - [Setting] - [DefaultValue(30.0D)] - public double MaxFileSize { get; set; } - - /// - /// Gets or sets the number of threads used - /// for processing file data concurrently. - /// - /// - /// Values less than or equal to zero will be set to the number of logical processors. - /// - [Setting] - [DefaultValue(0)] - public int ProcessingThreadCount - { - get - { - return _ProcessingThreadCount; - } - set - { - _ProcessingThreadCount = value; - - if (_ProcessingThreadCount <= 0) - _ProcessingThreadCount = Environment.ProcessorCount; - } - } - - private int _ProcessingThreadCount { get; set; } - } -} diff --git a/Libraries/openXDA.Configuration/FilePrunerSection.cs b/Libraries/openXDA.Configuration/FilePrunerSection.cs deleted file mode 100644 index 2c9a4c2b..00000000 --- a/Libraries/openXDA.Configuration/FilePrunerSection.cs +++ /dev/null @@ -1,58 +0,0 @@ -//****************************************************************************************************** -// FilePrunerSection.cs - Gbtc -// -// Copyright © 2021, 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/08/2021 - Stephen C. Wills -// Generated original version of source code. -// -//****************************************************************************************************** - -using Gemstone.Configuration; -using System.ComponentModel; -using System.Configuration; - -namespace openXDA.Configuration -{ - public class FilePrunerSection - { - public const string CategoryName = "FilePruner"; - - [Setting] - [DefaultValue("* 0 * * *")] - public string Schedule { get; set; } - - /// - /// Gets or sets the amount of time, in days, - /// the file pruner should keep files in openXDA's - /// watch directories. - /// - [Setting] - [SettingName(nameof(RetentionPeriod))] - [DefaultValue(0)] - public int RetentionPeriodDays - { - get => (int)RetentionPeriod.TotalDays; - set => RetentionPeriod = TimeSpan.FromDays(value); - } - - /// - /// Gets or sets the amount of time the file pruner - /// should keep files in openXDA's watch directories. - /// - public TimeSpan RetentionPeriod { get; set; } - } -} diff --git a/Libraries/openXDA.Configuration/FileWatcherSection.cs b/Libraries/openXDA.Configuration/FileWatcherSection.cs deleted file mode 100644 index 4b25e6c1..00000000 --- a/Libraries/openXDA.Configuration/FileWatcherSection.cs +++ /dev/null @@ -1,308 +0,0 @@ -//****************************************************************************************************** -// FileWatcherSection.cs - Gbtc -// -// Copyright © 2021, 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/16/2021 - Stephen C. Wills -// Generated original version of source code. -// -//****************************************************************************************************** - -using Gemstone.Configuration; -using Gemstone.IO; -using Gemstone.StringExtensions; -using System.ComponentModel; -using System.Configuration; -using System.Security; -using FileShare = openXDA.Configuration.FileWatcher.FileShare; - -namespace openXDA.Configuration -{ - namespace FileWatcher - { - /// - /// Represents a file share. - /// - public class FileShare - { - #region [ Members ] - - // Fields - private string m_name; - private string m_domain; - private string m_username; - private SecureString m_password; - - private Exception m_authenticationException; - - #endregion - - #region [ Constructors ] - - /// - /// Creates a new instance of the class. - /// - public FileShare() - { - } - - /// - /// Creates a new instance of the class. - /// - /// A string containing the file share parameters as key-value pairs. - public FileShare(string connectionString) - { - ConnectionStringParser parser = new ConnectionStringParser(); - parser.ParseConnectionString(connectionString, this); - } - - #endregion - - #region [ Properties ] - - /// - /// Gets or sets the name of the file share (\\server\share). - /// - [Setting] - [DefaultValue("")] - public string Name - { - get - { - return m_name; - } - set - { - m_name = value; - } - } - - /// - /// Gets or sets the domain of the user used to authenticate to the file share. - /// - [Setting] - [DefaultValue("")] - public string Domain - { - get - { - return m_domain; - } - set - { - m_domain = value; - } - } - - /// - /// Gets or sets the username of the user used to authenticate to the file share. - /// - [Setting] - [DefaultValue("")] - public string Username - { - get - { - return m_username; - } - set - { - string[] splitValue; - - m_username = value; - - if ((object)value != null) - { - splitValue = value.Split('\\'); - - if (splitValue.Length == 2) - { - m_domain = splitValue[0]; - m_username = splitValue[1]; - } - } - } - } - - /// - /// Gets or sets the password of the user used to authenticate to the file share. - /// - [Setting] - [DefaultValue("")] - public string Password - { - get - { - return m_password.ToUnsecureString() ?? ""; - } - set - { - m_password = value.ToSecureString() ?? new SecureString(); - } - } - - /// - /// Gets the exception encountered during the most recent authentication attempt. - /// - public Exception AuthenticationException - { - get - { - return m_authenticationException; - } - } - - #endregion - - #region [ Methods ] - - /// - /// Attempts to authenticate to the file share. - /// - public void Authenticate() - { - try - { - FilePath.ConnectToNetworkShare(m_name, m_username, Password, m_domain); - m_authenticationException = null; - } - catch (Exception ex) - { - m_authenticationException = ex; - throw; - } - } - - /// - /// Attempts to authenticate to the file share. - /// - /// True if successfully authenticated. False otherwise. - public bool TryAuthenticate() - { - try - { - Authenticate(); - return true; - } - catch - { - return false; - } - } - - #endregion - } - } - - public class FileWatcherSection - { - public const string CategoryName = "FileWatcher"; - - /// - /// Gets or sets the size of the - /// s' internal buffers. - /// - /// - [Setting] - [DefaultValue(65536)] - public int BufferSize { get; set; } - - /// - /// Gets or sets a list of parameters used - /// to authenticate to multiple file shares. - /// - [Setting] - [DefaultValue("")] - public string FileShares - { - get - { - return _FileShares; - } - set - { - _FileShares = value; - - _FileShareList = value.ToNonNullString().ParseKeyValuePairs() - .Select(kvp => kvp.Value) - .Select(fileShareString => new FileShare(fileShareString)) - .ToList(); - } - } - - /// - /// Gets a list of file shares to be authenticated at startup. - /// - public IReadOnlyCollection FileShareList => _FileShareList.AsReadOnly(); - - private string _FileShares { get; set; } - private List _FileShareList { get; set; } - - /// - /// Gets or sets the number of threads used - /// internally to the file processor. - /// - /// - /// Values less than or equal to zero will be set to the number of logical processors. - /// - [Setting] - [DefaultValue(0)] - public int InternalThreadCount - { - get => _InternalThreadCount; - set => _InternalThreadCount = (value > 0) - ? value - : Environment.ProcessorCount; - } - - private int _InternalThreadCount { get; set; } - - /// - /// Gets or sets the list of directories to watch for files. - /// - [Setting] - [DefaultValue("Watch")] - public string WatchDirectories - - { - get - { - return _WatchDirectories; - } - set - { - _WatchDirectories = value; - - if ((object)value != null) - { - _WatchDirectoryList = value - .Split(Path.PathSeparator) - .Select(path => path.Trim()) - .ToList(); - } - } - } - - /// - /// Gets a list of directories to be watched - /// for files containing fault records. - /// - public IReadOnlyCollection WatchDirectoryList => _WatchDirectoryList.AsReadOnly(); - - private string _WatchDirectories { get; set; } - private List _WatchDirectoryList { get; set; } - } -} diff --git a/Libraries/openXDA.Configuration/GrafanaSection.cs b/Libraries/openXDA.Configuration/GrafanaSection.cs deleted file mode 100644 index a6d0bc31..00000000 --- a/Libraries/openXDA.Configuration/GrafanaSection.cs +++ /dev/null @@ -1,72 +0,0 @@ -//****************************************************************************************************** -// GrafanaSection.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: -// ---------------------------------------------------------------------------------------------------- -// 08/18/2023 - Stephen C. Wills -// Generated original version of source code. -// -//****************************************************************************************************** - -using System.ComponentModel; -using System.Configuration; - -namespace openXDA.Configuration -{ - public class GrafanaSection - { - public const string CategoryName = "Grafana"; - - /// - /// Gets or sets the base path to the Grafana server executable. - /// - [Setting] - [DefaultValue(@"Grafana\bin\grafana-server.exe")] - public string ServerPath { get; set; } - - /// - /// Gets or sets the base path to the - /// folder containing the Grafana process. - /// - [Setting] - [DefaultValue("Grafana")] - public string BasePath { get; set; } - - [Setting] - [DefaultValue("http://localhost:8185")] - public string HostedURL { get; set; } - - [Setting] - [DefaultValue("admin")] - public string AdminUser { get; set; } - - [Setting] - [DefaultValue(1)] - public int OrganizationID { get; set; } - - [Setting] - [DefaultValue("X-WEBAUTH-USER")] - public string AuthProxyHeaderName { get; set; } - - [Setting] - [DefaultValue("x-last-dashboard")] - public string LastDashboardCookieName { get; set; } - - [Setting] - [DefaultValue(30)] - public int InitializationTimeout { get; set; } - } -} diff --git a/Libraries/openXDA.Configuration/LSCVSSection.cs b/Libraries/openXDA.Configuration/LSCVSSection.cs deleted file mode 100644 index 3cd00c05..00000000 --- a/Libraries/openXDA.Configuration/LSCVSSection.cs +++ /dev/null @@ -1,68 +0,0 @@ -//****************************************************************************************************** -// LSCVSSection.cs - Gbtc -// -// Copyright © 2021, 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: -// ---------------------------------------------------------------------------------------------------- -// 04/11/2022 - G. Santos -// Generated original version of source code. -// -//****************************************************************************************************** - -using System.ComponentModel; -using System.Configuration; - -namespace openXDA.Configuration -{ - public class LSCVSSection - { - public const string CategoryName = "LSCVS"; - - /// - /// URL string of the LSCVS report server name. - /// - [Setting] - [DefaultValue("http://localhost/LSCVSReport")] - public string URL { get; set; } - - /// - /// The API Key to use to authenticate to LSCVS - /// - [Setting] - [DefaultValue("")] - public string APIKey { get; set; } - - /// - /// The API Token to use to authenticate to LSCVS - /// - [Setting] - [DefaultValue("")] - public string APIToken { get; set; } - - /// - /// MW ratio threshold to report LSCVS event. - /// - [Setting] - [DefaultValue(0.6D)] - public double ReportingThreshold { get; set; } - - /// - /// Ratio threshold for determining type III sag faults over type II or I. - /// - [Setting] - [DefaultValue(0.95D)] - public double TypeThreshold { get; set; } - } -} diff --git a/Libraries/openXDA.Configuration/OSIPISection.cs b/Libraries/openXDA.Configuration/OSIPISection.cs deleted file mode 100644 index b5d84055..00000000 --- a/Libraries/openXDA.Configuration/OSIPISection.cs +++ /dev/null @@ -1,71 +0,0 @@ -//****************************************************************************************************** -// OSIPISection.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: -// ---------------------------------------------------------------------------------------------------- -// 03/21/2025 - Stephen C. Wills -// Generated original version of source code. -// -//****************************************************************************************************** - -using System.ComponentModel; -using System.Configuration; - -namespace openXDA.Configuration -{ - public class OSIPISection - { - #region [ Members ] - - // Constants - public const string CategoryName = "OSIPI"; - - #endregion - - #region [ Properties ] - - /// - /// Gets or sets name of the PI historian server to connect to. - /// - [Setting] - [DefaultValue("")] - public string ServerName { get; set; } - - /// - /// Gets or sets user name for logging into PI historian server. - /// - [Setting] - [DefaultValue("")] - public string UserName { get; set; } - - /// - /// Gets or sets password for logging into PI historian server. - /// - [Setting] - [DefaultValue("")] - public string Password { get; set; } - - /// - /// Gets or sets time to wait when establishing - /// connection to PI server before failing. - /// - [Setting] - [DefaultValue(30000)] - public int ConnectTimeout { get; set; } - - #endregion - } -} diff --git a/Libraries/openXDA.Configuration/PQDIFSection.cs b/Libraries/openXDA.Configuration/PQDIFSection.cs deleted file mode 100644 index 670a5fd0..00000000 --- a/Libraries/openXDA.Configuration/PQDIFSection.cs +++ /dev/null @@ -1,46 +0,0 @@ -//****************************************************************************************************** -// PQDIFSection.cs - Gbtc -// -// Copyright © 2021, 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/08/2021 - Stephen C. Wills -// Generated original version of source code. -// -//****************************************************************************************************** - -using System.ComponentModel; -using System.Configuration; - -namespace openXDA.Configuration -{ - public class PQDIFSection - { - #region [ Members ] - - // Constants - public const string CategoryName = "PQDIF"; - - #endregion - - #region [ Properties ] - - [Setting] - [DefaultValue(false)] - public bool AllowMultipleDataSource { get; set; } - - #endregion - } -} diff --git a/Libraries/openXDA.Configuration/PQISection.cs b/Libraries/openXDA.Configuration/PQISection.cs deleted file mode 100644 index bbdeb1f1..00000000 --- a/Libraries/openXDA.Configuration/PQISection.cs +++ /dev/null @@ -1,66 +0,0 @@ -//****************************************************************************************************** -// PQISection.cs - Gbtc -// -// Copyright © 2021, 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/29/2021 - Stephen C. Wills -// Generated original version of source code. -// -//****************************************************************************************************** - -using System.ComponentModel; -using System.Configuration; - -namespace openXDA.Configuration -{ - public class PQISection - { - #region [ Members ] - - // Constants - public const string CategoryName = "PQI"; - - #endregion - - #region [ Properties ] - - [Setting] - [DefaultValue("https://pqiws.epri.com")] - public string BaseURL { get; set; } - - [Setting] - [DefaultValue("https://go.epri.com")] - public string PingURL { get; set; } - - [Setting] - [DefaultValue(null)] - public string ClientID { get; set; } - - [Setting] - [DefaultValue(null)] - public string ClientSecret { get; set; } - - [Setting] - [DefaultValue(null)] - public string Username { get; set; } - - [Setting] - [DefaultValue(null)] - public string Password { get; set; } - - #endregion - } -} diff --git a/Libraries/openXDA.Configuration/Properties/AssemblyInfo.cs b/Libraries/openXDA.Configuration/Properties/AssemblyInfo.cs deleted file mode 100644 index 0666cda4..00000000 --- a/Libraries/openXDA.Configuration/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("openXDA.Configuration")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("openXDA.Configuration")] -[assembly: AssemblyCopyright("Copyright © 2021")] -[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("83d8122c-2bcc-4813-b449-224d9f7e5aee")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("3.0.5.93")] -[assembly: AssemblyVersion("3.0.5.93")] -[assembly: AssemblyFileVersion("3.0.5.93")] diff --git a/Libraries/openXDA.Configuration/RabbitMQSection.cs b/Libraries/openXDA.Configuration/RabbitMQSection.cs deleted file mode 100644 index 12e6900a..00000000 --- a/Libraries/openXDA.Configuration/RabbitMQSection.cs +++ /dev/null @@ -1,90 +0,0 @@ -//****************************************************************************************************** -// RabbitMQSection.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: -// ---------------------------------------------------------------------------------------------------- -// 11/01/2025 - C. Lackner -// Generated original version of source code. -// -//****************************************************************************************************** - -using System.ComponentModel; -using System.Configuration; - -namespace openXDA.Configuration -{ - public class RabbitMQSection - { - public const string CategoryName = "RabbitMQ"; - - /// - /// The RabbbitMQ Server Hostname or IP Address. - /// - [Setting] - [DefaultValue("localhost")] - public string Hostname { get; set; } = "localhost"; - - /// - /// Defines the port RabbitMQ is listening on. - /// - [Setting] - [DefaultValue(5672)] - public int Port { get; set; } = 5672; - - /// - /// Defines the name of the exchange to listen to. - /// - [Setting] - [DefaultValue("openxda")] - public string ExchangeName { get; set; } = "openxda"; - - /// - /// Defines the routing key used to listen for messages. - /// - [Setting] - [DefaultValue("openxda")] - public string RoutingKey { get; set; } = "openxda"; - - /// - /// Defines a flag that determines if RabbitMQ integration is enabled. - /// - [Setting] - [DefaultValue(false)] - public bool Enabled { get; set; } = false; - - - /// - /// Defines the routing key used for outbound messages. - /// - [Setting] - [DefaultValue("analytic")] - public string OutboundRoutingKey { get; set; } = "analytic"; - - /// - /// Defines the username used for authenticating to the RabbitMQ service. - /// - [Setting] - [DefaultValue("guest")] - public string UserName { get; set; } = "guest"; - - /// - /// Defines the password used for authenticating to the RabbitMQ service. - /// - [Setting] - [DefaultValue("guest")] - public string Password { get; set; } = "guest"; - } -} diff --git a/Libraries/openXDA.Configuration/SCADASection.cs b/Libraries/openXDA.Configuration/SCADASection.cs deleted file mode 100644 index acb47c91..00000000 --- a/Libraries/openXDA.Configuration/SCADASection.cs +++ /dev/null @@ -1,94 +0,0 @@ -//****************************************************************************************************** -// SCADASection.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: -// ---------------------------------------------------------------------------------------------------- -// 03/21/2025 - Stephen C. Wills -// Generated original version of source code. -// -//****************************************************************************************************** - -using System; -using System.ComponentModel; -using System.Configuration; - -namespace openXDA.Configuration -{ - public class SCADASection - { - #region [ Members ] - - // Nested Types - public enum SCADAHistorian - { - None, - eDNA, - OSIPI - } - - // Constants - public const string CategoryName = "SCADA"; - - #endregion - - #region [ Properties ] - - /// - /// Gets or sets historian used by the SCADA system. - /// - [Setting] - [DefaultValue(SCADAHistorian.None)] - public SCADAHistorian Historian { get; set; } - - /// - /// Gets or sets the database query to get the list of - /// SCADA points that indicate breaker state for the line. - /// - [Setting] - [DefaultValue("SELECT NULL AS Point WHERE 1 IS NULL")] - public string PointQuery { get; set; } - - /// - /// Gets or sets the tolerance, in seconds, that determines the - /// time range to be queried around the fault clearing point. - /// - [Setting] - [DefaultValue(4.0D)] - public double QueryTolerance { get; set; } - - /// - /// Gets or sets the value of the point representing - /// breaker state when the breaker is open. - /// - [Setting] - [DefaultValue(0.0D)] - public double BreakerOpenValue { get; set; } - - /// - /// Gets the tolerance that determines the time range - /// to be queried around the fault clearing point. - /// - public TimeSpan QueryToleranceSpan - { - get - { - return TimeSpan.FromSeconds(QueryTolerance); - } - } - - #endregion - } -} diff --git a/Libraries/openXDA.Configuration/SSAMSSection.cs b/Libraries/openXDA.Configuration/SSAMSSection.cs deleted file mode 100644 index dff73793..00000000 --- a/Libraries/openXDA.Configuration/SSAMSSection.cs +++ /dev/null @@ -1,73 +0,0 @@ -//****************************************************************************************************** -// SSAMSSection.cs - Gbtc -// -// Copyright © 2021, 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/08/2021 - Stephen C. Wills -// Generated original version of source code. -// -//****************************************************************************************************** - -using System.ComponentModel; -using System.Configuration; - -namespace openXDA.Configuration -{ - public class SSAMSSection - { - public const string CategoryName = "SSAMS"; - - private const string DefaultDataProviderString = - "AssemblyName={System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089}; " + - "ConnectionType=System.Data.SqlClient.SqlConnection; " + - "AdapterType=System.Data.SqlClient.SqlDataAdapter;"; - - /// - /// Cron string frequency at which the SSAMS process is scheduled. - /// - [Setting] - [DefaultValue("* 0 * * *")] - public string Schedule { get; set; } - - /// - /// Defines the connection string of the SSAMS DB. - /// - [Setting] - [DefaultValue("Data Source=localhost; Initial Catalog=openXDA; Integrated Security=SSPI")] - public string ConnectionString { get; set; } - - /// - /// Defines the connection string of the SSAMS DB. - /// - [Setting] - [DefaultValue(DefaultDataProviderString)] - public string DataProviderString { get; set; } - - /// - /// Command or procedure name that defines the command executed by the external DB. - /// - [Setting] - [DefaultValue("sp_LogSsamEvent")] - public string DatabaseCommand { get; set; } - - /// - /// Parameters for the external DB procedure. - /// - [Setting] - [DefaultValue("1,1,'OpenXDA_HEARTBEAT','','OpenXDA adapter heartbeat at {Timestamp} UTC',''")] - public string CommandParameters { get; set; } - } -} diff --git a/Libraries/openXDA.Configuration/Subscription.cs b/Libraries/openXDA.Configuration/Subscription.cs deleted file mode 100644 index eac8f878..00000000 --- a/Libraries/openXDA.Configuration/Subscription.cs +++ /dev/null @@ -1,65 +0,0 @@ -//****************************************************************************************************** -// SubscriptionSection.cs - Gbtc -// -// Copyright © 2015, 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/02/2015 - Stephen C. Wills -// Generated original version of source code. -// -//****************************************************************************************************** - -using System.ComponentModel; -using System.Configuration; - -namespace openXDA.Configuration -{ - public class SubscriptionSection - { - #region [ Members ] - - // Constants - public const string CategoryName = "Subscription"; - - #endregion - - #region [ Properties ] - - /// - /// Gets or sets the Subject Line used when sending the confirmation email - /// - [Setting] - [DefaultValue("OpenXDA Confirm Email")] - public string ConfirmSubject { get; set; } - - /// - /// Gets or sets the Template used when generating the Confirmation Email - /// - [Setting] - [DefaultValue("Please click the following Link to confirm your email address \n http://localhost/SystemCenterNotification/ConfirmEmail")] - public string ConfirmTemplate { get; set; } - - - /// - /// Gets or sets the flag that determines whether a confirmed Email is Required - /// when subscribing to a notification - /// - [Setting] - [DefaultValue(true)] - public bool RequireConfirmation { get; set; } - - #endregion - } -} diff --git a/Libraries/openXDA.Configuration/SystemSection.cs b/Libraries/openXDA.Configuration/SystemSection.cs deleted file mode 100644 index 4649edf6..00000000 --- a/Libraries/openXDA.Configuration/SystemSection.cs +++ /dev/null @@ -1,109 +0,0 @@ -//****************************************************************************************************** -// SystemSection.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: -// ---------------------------------------------------------------------------------------------------- -// 09/30/2014 - Stephen C. Wills -// Generated original version of source code. -// -//****************************************************************************************************** - -using System; -using System.ComponentModel; -using System.Configuration; - -namespace openXDA.Configuration -{ - /// - /// Represents the system settings for openXDA. - /// - public class SystemSection - { - public const string CategoryName = "System"; - - /// - /// Gets or sets the time zone identifier for the time zone - /// used by meters in the system unless configured otherwise. - /// - [Setting] - [DefaultValue("UTC")] - public string DefaultMeterTimeZone - { - get => DefaultMeterTimeZoneInfo.Id; - set => DefaultMeterTimeZoneInfo = string.IsNullOrEmpty(value) - ? TimeZoneInfo.Local - : TimeZoneInfo.FindSystemTimeZoneById(value); - } - - /// - /// Gets the used by meters - /// in the system unless configured otherwise. - /// - public TimeZoneInfo DefaultMeterTimeZoneInfo { get; private set; } - - /// - /// Gets or sets the amount of time each database - /// query is given to complete, in seconds. - /// - [Setting] - [DefaultValue(120)] - public int DbTimeout { get; set; } - - /// - /// Gets or sets the time zone identifier for the - /// time zone used by openXDA to store data. - /// - /// - /// The default value for this setting (empty string) - /// causes the setting to assume the value of the local - /// time zone of the system openXDA is running on. - /// - [Setting] - [DefaultValue("UTC")] - public string XDATimeZone - { - get - { - return _XDATimeZone; - } - set - { - if (string.IsNullOrEmpty(value)) - _XDATimeZone = TimeZoneInfo.Local.Id; - else - _XDATimeZone = value; - } - } - - /// - /// Gets the for the - /// time zone used by openXDA to store data. - /// - public TimeZoneInfo XDATimeZoneInfo - { - get - { - if ((object)_XDATimeZoneInfo == null) - _XDATimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(_XDATimeZone); - - return _XDATimeZoneInfo; - } - } - - private string _XDATimeZone { get; set; } - private TimeZoneInfo _XDATimeZoneInfo { get; set; } - } -} diff --git a/Libraries/openXDA.Configuration/TaskProcessorSection.cs b/Libraries/openXDA.Configuration/TaskProcessorSection.cs deleted file mode 100644 index e42b01fc..00000000 --- a/Libraries/openXDA.Configuration/TaskProcessorSection.cs +++ /dev/null @@ -1,68 +0,0 @@ -//****************************************************************************************************** -// TaskProcessorSection.cs - Gbtc -// -// Copyright © 2021, 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/24/2021 - Stephen C. Wills -// Generated original version of source code. -// -//****************************************************************************************************** - -using System; -using System.ComponentModel; -using System.Configuration; - -namespace openXDA.Configuration -{ - public class TaskProcessorSection - { - public const string CategoryName = "TaskProcessor"; - - /// - /// Gets or sets the query used to filter - /// meters when polling for analysis tasks. - /// - [Setting] - [DefaultValue("SELECT ID FROM Meter")] - public string MeterFilterQuery { get; set; } - - /// - /// Gets or sets the number of threads used - /// for processing meter data concurrently. - /// - /// - /// Values less than or equal to zero will be set to the number of logical processors. - /// - [Setting] - [DefaultValue(0)] - public int ProcessingThreadCount - { - get - { - return _ProcessingThreadCount; - } - set - { - _ProcessingThreadCount = value; - - if (_ProcessingThreadCount <= 0) - _ProcessingThreadCount = Environment.ProcessorCount; - } - } - - private int _ProcessingThreadCount { get; set; } - } -} diff --git a/Libraries/openXDA.Configuration/TrendingDataSection.cs b/Libraries/openXDA.Configuration/TrendingDataSection.cs deleted file mode 100644 index 389daad4..00000000 --- a/Libraries/openXDA.Configuration/TrendingDataSection.cs +++ /dev/null @@ -1,122 +0,0 @@ -//****************************************************************************************************** -// TrendingDataSection.cs - Gbtc -// -// Copyright © 2022, 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/26/2022 - Stephen C. Wills -// Generated original version of source code. -// -//****************************************************************************************************** - -using System.ComponentModel; -using System.Configuration; - -namespace openXDA.Configuration -{ - public class TrendingDataSection - { - #region [ Members ] - - // Nested Types - public class RMSSubSection - { - [Setting] - [DefaultValue(@"Trms.dat")] - public string FolderPath { get; set; } - [Setting] - [DefaultValue(null)] - public string DescriptionRegexMatchFilter { get; set; } - } - - public class FlickerSubSection - { - [Setting] - [DefaultValue(@"FkrR[0-9]*\.dat")] - public string FolderPath { get; set; } - [Setting] - [DefaultValue(null)] - public string DescriptionRegexMatchFilter { get; set; } - } - - public class TriggerSubSection - { - [Setting] - [DefaultValue(@"TrR[0-9]*\.dat")] - public string FolderPath { get; set; } - [Setting] - [DefaultValue(@"\s-\sV\S*\sRMS\s*$")] - public string DescriptionTriggerRMSMatch { get; set; } - [Setting] - [DefaultValue(@"\s-\sV\S*\sImpulse\s*$")] - public string DescriptionTriggerImpulseMatch { get; set; } - [Setting] - [DefaultValue(@"\s-\s\S+\sTHD\s*$")] - public string DescriptionTriggerTHDMatch { get; set; } - [Setting] - [DefaultValue(@"\s-\sUnbalance\s*$")] - public string DescriptionTriggerUnbalanceMatch { get; set; } - [Setting] - [DefaultValue(@"\s-\s\S*I\S*\s*$")] - public string DescriptionTriggerCurrentMatch { get; set; } - [Setting] - [DefaultValue(null)] - public string DescriptionRegexMatchFilter { get; set; } - } - - public class FrequencySubSection - { - [Setting] - [DefaultValue(@"Tfrq[0-9]*\.dat")] - public string FolderPath { get; set; } - [Setting] - [DefaultValue(null)] - public string DescriptionRegexMatchFilter { get; set; } - } - - // Constants - public const string CategoryName = "TrendingData"; - - #endregion - - #region [ Properties ] - - /// - /// Gets RMS folder path settings. - /// - [Category] - public RMSSubSection RMS { get; } = new RMSSubSection(); - - /// - /// Gets flicker folder path settings. - /// - [Category] - public FlickerSubSection Flicker { get; } = new FlickerSubSection(); - - /// - /// Gets trigger folder path settings. - /// - [Category] - public TriggerSubSection Trigger { get; } = new TriggerSubSection(); - - /// - /// Gets frequency folder path settings. - /// - [Category] - public FrequencySubSection Frequency { get; } = new FrequencySubSection(); - - #endregion - } -} diff --git a/Libraries/openXDA.Configuration/openXDA.Configuration.csproj b/Libraries/openXDA.Configuration/openXDA.Configuration.csproj deleted file mode 100644 index 147221b2..00000000 --- a/Libraries/openXDA.Configuration/openXDA.Configuration.csproj +++ /dev/null @@ -1,50 +0,0 @@ - - - - Debug - AnyCPU - net9.0 - Debug;Development;Release - Library - openXDA.Configuration - openXDA.Configuration - false - enable - enable - true - - - - true - full - false - ..\..\..\Build\Output\Debug\Libraries\ - DEBUG;TRACE - 4 - - - - pdbonly - true - ..\..\..\Build\Output\Release\Libraries\ - TRACE - 4 - - - - - - - - - - - - - - - ..\..\Dependencies\OpenXDA\openXDA.Model.dll - - - - diff --git a/Libraries/openXDA.PQI/Address.cs b/Libraries/openXDA.PQI/Address.cs deleted file mode 100644 index 37d6e91d..00000000 --- a/Libraries/openXDA.PQI/Address.cs +++ /dev/null @@ -1,81 +0,0 @@ -//****************************************************************************************************** -// Address.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: -// ---------------------------------------------------------------------------------------------------- -// 05/19/2023 - C. Lackner -// Generated original version of source code. -// -//****************************************************************************************************** - -namespace openXDA.PQI -{ - /// - /// Summary of the . - /// - public class Address - { - /// - /// Path to query this address - /// - public string Path { get; set; } - - /// - /// Path to query the company at this address - /// - public string Company { get; set; } - - /// - /// Path to query facilities at this address - /// - public string Facilities { get; set; } - - /// - /// First line of the address (street address) - /// - public string AddressLine1 { get; set; } - - /// - /// Second line of the address (building number, etc.) - /// - public string AddressLine2 { get; set; } - - /// - /// City in which the address is located - /// - public string City { get; set; } - - /// - /// State or province in which the address is located - /// - public string StateOrProvince { get; set; } - - /// - /// Zip code/postal code - /// - public string PostalCode { get; set; } - - /// - /// Country in which the address is located - /// - public string Country { get; set; } - - /// - /// True/false to indicate whether the address is primary - /// - public bool Primary { get; set; } - } -} diff --git a/Libraries/openXDA.PQI/AuditCurve.cs b/Libraries/openXDA.PQI/AuditCurve.cs deleted file mode 100644 index 24ba81d2..00000000 --- a/Libraries/openXDA.PQI/AuditCurve.cs +++ /dev/null @@ -1,56 +0,0 @@ -//****************************************************************************************************** -// AuditCurve.cs - Gbtc -// -// Copyright © 2021, 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/09/2022 - C. Lackner -// Generated original version of source code. -// -//****************************************************************************************************** - -namespace openXDA.PQI -{ - - public class AuditCurve: PQIModel - { - /// - /// Path to query this audit curve - /// - public string Path { get; set; } - - /// - /// Path to query the equipment this audit curve applies to - /// - public string Equipment { get; set; } - - /// - /// Path to query the areas in which the equipment resides - /// - public string Curve { get; set; } - - /// - /// Path to query the curve itself - /// - public string AuditCurves { get; set; } - - /// - /// The type of the curve (TOLERANCE, PROTECTION, PQDATA) - /// - public string CurveType { get; set; } - - - } -} diff --git a/Libraries/openXDA.PQI/Company.cs b/Libraries/openXDA.PQI/Company.cs deleted file mode 100644 index 24d0701e..00000000 --- a/Libraries/openXDA.PQI/Company.cs +++ /dev/null @@ -1,56 +0,0 @@ -//****************************************************************************************************** -// Company.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: -// ---------------------------------------------------------------------------------------------------- -// 05/19/2023 - C. Lackner -// Generated original version of source code. -// -//****************************************************************************************************** - -namespace openXDA.PQI -{ - /// - /// Summary of the . - /// - public class Company - { - /// - /// Path to query this company - /// - public string Path { get; set; } - - /// - /// Path to query the addresses for this company - /// - public string Addresses { get; set; } - - /// - /// Type of the company (EPRI, UTILITY, CONTRACTOR) - /// - public string Type { get; set; } - - /// - /// Name of the company - /// - public string Name { get; set; } - - /// - /// Industry the company is in - /// - public string Industry { get; set; } - } -} diff --git a/Libraries/openXDA.PQI/Equipment.cs b/Libraries/openXDA.PQI/Equipment.cs deleted file mode 100644 index 5c250b6d..00000000 --- a/Libraries/openXDA.PQI/Equipment.cs +++ /dev/null @@ -1,74 +0,0 @@ -//****************************************************************************************************** -// Equipment.cs - Gbtc -// -// Copyright © 2021, 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/22/2021 - Stephen C. Wills -// Generated original version of source code. -// -//****************************************************************************************************** - -namespace openXDA.PQI -{ - /// - /// Impacted Equipment - /// - /// - /// This does not correspond to the PQI Equipment model. For the PQI model see - /// - public class Equipment - { - /// - /// Name of the facility - /// - public string Facility { get; set; } - - /// - /// Title of the area - /// - public string Area { get; set; } - - /// - /// Title of the equipment - /// - public string SectionTitle { get; set; } - - /// - /// Rank of the equipment - /// - public int SectionRank { get; set; } - - /// - /// Model of the component - /// - public string ComponentModel { get; set; } - - /// - /// Name of the manufacturer - /// - public string Manufacturer { get; set; } - - /// - /// Name of the series - /// - public string Series { get; set; } - - /// - /// Type of component - /// - public string ComponentType { get; set; } - } -} diff --git a/Libraries/openXDA.PQI/Facility.cs b/Libraries/openXDA.PQI/Facility.cs deleted file mode 100644 index eca4ee2d..00000000 --- a/Libraries/openXDA.PQI/Facility.cs +++ /dev/null @@ -1,56 +0,0 @@ -//****************************************************************************************************** -// Facility.cs - Gbtc -// -// Copyright © 2021, 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/18/2022 - C. Lackner -// Generated original version of source code. -// -//****************************************************************************************************** - -namespace openXDA.PQI -{ - /// - /// Summary of the . For more detailed information use - /// - public class Facility - { - /// - /// Name of the facility - /// - public string Name { get; set; } - - /// - /// Voltage at the facility - /// - public string Voltage { get; set; } - - /// - /// Utility voltage supplied to the facility - /// - public string UtilitySupplyVoltage { get; set; } - - /// - /// Path to query this Facility - /// - public string Path { get; set; } - - /// - /// Path to query the address of the facility - /// - public string Address { get; set; } - } -} diff --git a/Libraries/openXDA.PQI/FacilityAudit.cs b/Libraries/openXDA.PQI/FacilityAudit.cs deleted file mode 100644 index cf65c696..00000000 --- a/Libraries/openXDA.PQI/FacilityAudit.cs +++ /dev/null @@ -1,53 +0,0 @@ -//****************************************************************************************************** -// FacilityAudit.cs - Gbtc -// -// Copyright © 2021, 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/18/2022 - C. Lackner -// Generated original version of source code. -// -//****************************************************************************************************** - -namespace openXDA.PQI -{ - /// - /// Summary of the . - /// - public class FacilityAudit: PQIModel - { - /// - /// Name of the Audit - /// - public string AuditName { get; set; } - - /// - /// Path to query the equipment in the facility that was audited - /// - public string Equipment { get; set; } - - /// - /// Path to query the facility that was audited. - /// - public string Facility { get; set; } - - /// - /// Path to query this FacilityAudit - /// - public string Path { get; set; } - - - } -} diff --git a/Libraries/openXDA.PQI/FacilityInfo.cs b/Libraries/openXDA.PQI/FacilityInfo.cs deleted file mode 100644 index 5c66b549..00000000 --- a/Libraries/openXDA.PQI/FacilityInfo.cs +++ /dev/null @@ -1,83 +0,0 @@ -//****************************************************************************************************** -// FacilityInfo.cs - Gbtc -// -// Copyright © 2021, 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/22/2021 - Stephen C. Wills -// Generated original version of source code. -// -//****************************************************************************************************** - -namespace openXDA.PQI -{ - public class FacilityInfo - { - /// - /// Name of the facility - /// - public string FacilityName { get; set; } - - /// - /// Voltage at the facility - /// - public string FacilityVoltage { get; set; } - - /// - /// Utility voltage supplied to the facility - /// - public string UtilitySupplyVoltage { get; set; } - - /// - /// First line of the address (street address) - /// - public string AddressLine1 { get; set; } - - /// - /// Second line of the address (building number, etc.) - /// - public string AddressLine2 { get; set; } - - /// - /// City in which the address is located - /// - public string City { get; set; } - - /// - /// State or province in which the address is located - /// - public string StateOrProvince { get; set; } - - /// - /// Zip code/postal code - /// - public string PostalCode { get; set; } - - /// - /// Country in which the address is located. - /// - public string Country { get; set; } - - /// - /// Name of the company - /// - public string CompanyName { get; set; } - - /// - /// Industry the company is in - /// - public string Industry { get; set; } - } -} diff --git a/Libraries/openXDA.PQI/HttpClientExtensions.cs b/Libraries/openXDA.PQI/HttpClientExtensions.cs deleted file mode 100644 index b65b8d69..00000000 --- a/Libraries/openXDA.PQI/HttpClientExtensions.cs +++ /dev/null @@ -1,42 +0,0 @@ -//****************************************************************************************************** -// HttpClientExtensions.cs - Gbtc -// -// Copyright © 2021, 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/27/2021 - Stephen C. Wills -// Generated original version of source code. -// -//****************************************************************************************************** - -using System; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; - -namespace openXDA.PQI -{ - internal static class HttpClientExtensions - { - public static async Task SendRequestAsync(this HttpClient client, Action configure, CancellationToken cancellationToken) - { - using (HttpRequestMessage request = new HttpRequestMessage()) - { - configure(request); - return await client.SendAsync(request, cancellationToken).ConfigureAwait(false); - } - } - } -} diff --git a/Libraries/openXDA.PQI/HttpClientProvider.cs b/Libraries/openXDA.PQI/HttpClientProvider.cs deleted file mode 100644 index a246171b..00000000 --- a/Libraries/openXDA.PQI/HttpClientProvider.cs +++ /dev/null @@ -1,37 +0,0 @@ -//****************************************************************************************************** -// HttpClientProvider.cs - Gbtc -// -// Copyright © 2021, 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/26/2021 - Stephen C. Wills -// Generated original version of source code. -// -//****************************************************************************************************** - -using System; -using System.Net.Http; - -namespace openXDA.PQI -{ - public static class HttpClientProvider - { - public static HttpClient GetClient() => - LazyInstance.Value; - - private static Lazy LazyInstance { get; } - = new Lazy(() => new HttpClient()); - } -} diff --git a/Libraries/openXDA.PQI/PQIEquipment.cs b/Libraries/openXDA.PQI/PQIEquipment.cs deleted file mode 100644 index a4187930..00000000 --- a/Libraries/openXDA.PQI/PQIEquipment.cs +++ /dev/null @@ -1,65 +0,0 @@ -//****************************************************************************************************** -// PQIEquipment.cs - Gbtc -// -// Copyright © 2021, 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/09/2022 - C. Lackner -// Generated original version of source code. -// -//****************************************************************************************************** - -namespace openXDA.PQI -{ - - public class PQIEquipment: PQIModel - { - /// - /// Path to query this equipment - /// - public string Path { get; set; } - - /// - /// Path to query the facility audit in which the equipment was audited - /// - public string FacilityAudit { get; set; } - - /// - /// Path to query the areas in which the equipment resides - /// - public string Areas { get; set; } - - /// - /// Path to query the audit curves assigned to the equipment - /// - public string AuditCurves { get; set; } - - /// - /// Title of the equipment - /// - public string Title { get; set; } - - /// - /// Rank of the equipment - /// - public int Rank { get; set; } - - /// - /// Content of the equipment - /// - public string Content { get; set; } - - } -} diff --git a/Libraries/openXDA.PQI/PQIModel.cs b/Libraries/openXDA.PQI/PQIModel.cs deleted file mode 100644 index 8ca730d5..00000000 --- a/Libraries/openXDA.PQI/PQIModel.cs +++ /dev/null @@ -1,36 +0,0 @@ -//****************************************************************************************************** -// PQIModel.cs - Gbtc -// -// Copyright © 2021, 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/09/2022 - C. Lackner -// Generated original version of source code. -// -//****************************************************************************************************** - -using System; - -namespace openXDA.PQI -{ - - public interface PQIModel - { - /// - /// Path to query this Model - /// - string Path { get; set; } - } -} diff --git a/Libraries/openXDA.PQI/PQIWSClient.cs b/Libraries/openXDA.PQI/PQIWSClient.cs deleted file mode 100644 index 9ea65937..00000000 --- a/Libraries/openXDA.PQI/PQIWSClient.cs +++ /dev/null @@ -1,325 +0,0 @@ -//****************************************************************************************************** -// PQIWSClient.cs - Gbtc -// -// Copyright © 2021, 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/22/2021 - Stephen C. Wills -// Generated original version of source code. -// -//****************************************************************************************************** - -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Threading; -using System.Threading.Tasks; - -namespace openXDA.PQI -{ - public class PQIWSClient - { - private const string BasePath = "PQDashboard"; - - public PQIWSClient(string baseURL, Func tokenProvider) - { - BaseURL = baseURL; - TokenProvider = tokenProvider; - } - - private string BaseURL { get; } - private Func TokenProvider { get; } - - public async Task> GetAllFacilities(CancellationToken cancellationToken = default) - { - string url = BuildURL(BaseURL, "Facility"); - - void ConfigureRequest(HttpRequestMessage request) - { - request.RequestUri = new Uri($"{url}"); - request.Method = HttpMethod.Get; - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", TokenProvider()); - - MediaTypeWithQualityHeaderValue acceptHeader = new MediaTypeWithQualityHeaderValue("application/json"); - request.Headers.Accept.Add(acceptHeader); - } - - using (HttpResponseMessage response = await HttpClient.SendRequestAsync(ConfigureRequest, cancellationToken).ConfigureAwait(false)) - return await response.Content.ReadAsAsync>().ConfigureAwait(false); - } - - public async Task> GetAllAddresses(CancellationToken cancellationToken = default) - { - string url = BuildURL(BaseURL, "Address"); - - void ConfigureRequest(HttpRequestMessage request) - { - request.RequestUri = new Uri($"{url}"); - request.Method = HttpMethod.Get; - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", TokenProvider()); - - MediaTypeWithQualityHeaderValue acceptHeader = new MediaTypeWithQualityHeaderValue("application/json"); - request.Headers.Accept.Add(acceptHeader); - } - - using (HttpResponseMessage response = await HttpClient.SendRequestAsync(ConfigureRequest, cancellationToken).ConfigureAwait(false)) - return await response.Content.ReadAsAsync>().ConfigureAwait(false); - } - - public async Task> GetAllCompanies(CancellationToken cancellationToken = default) - { - string url = BuildURL(BaseURL, "Company"); - - void ConfigureRequest(HttpRequestMessage request) - { - request.RequestUri = new Uri($"{url}"); - request.Method = HttpMethod.Get; - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", TokenProvider()); - - MediaTypeWithQualityHeaderValue acceptHeader = new MediaTypeWithQualityHeaderValue("application/json"); - request.Headers.Accept.Add(acceptHeader); - } - - using (HttpResponseMessage response = await HttpClient.SendRequestAsync(ConfigureRequest, cancellationToken).ConfigureAwait(false)) - return await response.Content.ReadAsAsync>().ConfigureAwait(false); - } - - public async Task GetFacilityInfoAsync(int facilityID, CancellationToken cancellationToken = default) - { - string url = BuildURL(BaseURL, BasePath, "GetFacilityInfo"); - string queryString = $"facilityID={facilityID}"; - - void ConfigureRequest(HttpRequestMessage request) - { - request.RequestUri = new Uri($"{url}?{queryString}"); - request.Method = HttpMethod.Get; - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", TokenProvider()); - - MediaTypeWithQualityHeaderValue acceptHeader = new MediaTypeWithQualityHeaderValue("application/json"); - request.Headers.Accept.Add(acceptHeader); - } - - using (HttpResponseMessage response = await HttpClient.SendRequestAsync(ConfigureRequest, cancellationToken).ConfigureAwait(false)) - return await response.Content.ReadAsAsync().ConfigureAwait(false); - } - - public async Task IsImpactedAsync(int facilityID, double magnitude, double duration, CancellationToken cancellationToken = default) - { - string url = BuildURL(BaseURL, BasePath, "IsImpacted"); - - string queryString = - $"facilityID={facilityID}&" + - $"magnitude={magnitude}&" + - $"duration={duration}"; - - void ConfigureRequest(HttpRequestMessage request) - { - request.RequestUri = new Uri($"{url}?{queryString}"); - request.Method = HttpMethod.Get; - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", TokenProvider()); - - MediaTypeWithQualityHeaderValue acceptHeader = new MediaTypeWithQualityHeaderValue("application/json"); - request.Headers.Accept.Add(acceptHeader); - } - - using (HttpResponseMessage response = await HttpClient.SendRequestAsync(ConfigureRequest, cancellationToken).ConfigureAwait(false)) - return await response.Content.ReadAsAsync().ConfigureAwait(false); - } - - public async Task> GetImpactedEquipmentAsync(int facilityID, double magnitude, double duration, CancellationToken cancellationToken = default) - { - string url = BuildURL(BaseURL, BasePath, "GetEquipmentImpacted"); - - string queryString = - $"facilityID={facilityID}&" + - $"magnitude={magnitude}&" + - $"duration={duration}"; - - void ConfigureRequest(HttpRequestMessage request) - { - request.RequestUri = new Uri($"{url}?{queryString}"); - request.Method = HttpMethod.Get; - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", TokenProvider()); - - MediaTypeWithQualityHeaderValue acceptHeader = new MediaTypeWithQualityHeaderValue("application/json"); - request.Headers.Accept.Add(acceptHeader); - } - - using (HttpResponseMessage response = await HttpClient.SendRequestAsync(ConfigureRequest, cancellationToken).ConfigureAwait(false)) - return await response.Content.ReadAsAsync>().ConfigureAwait(false); - } - - public async Task> GetFacilityAudits(int facilityID, CancellationToken cancellationToken = default) - { - string url = BuildURL(BaseURL, "FacilityAudit"); - string queryString = $"facilityID={facilityID}"; - - void ConfigureRequest(HttpRequestMessage request) - { - request.RequestUri = new Uri($"{url}?{queryString}"); - request.Method = HttpMethod.Get; - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", TokenProvider()); - - MediaTypeWithQualityHeaderValue acceptHeader = new MediaTypeWithQualityHeaderValue("application/json"); - request.Headers.Accept.Add(acceptHeader); - } - - using (HttpResponseMessage response = await HttpClient.SendRequestAsync(ConfigureRequest, cancellationToken).ConfigureAwait(false)) - return await response.Content.ReadAsAsync>().ConfigureAwait(false); - } - - public async Task> GetAuditedEquipment(int facilityAuditID, CancellationToken cancellationToken = default) - { - string url = BuildURL(BaseURL, "Equipment"); - string queryString = $"facilityAuditID={facilityAuditID}"; - - void ConfigureRequest(HttpRequestMessage request) - { - request.RequestUri = new Uri($"{url}?{queryString}"); - request.Method = HttpMethod.Get; - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", TokenProvider()); - - MediaTypeWithQualityHeaderValue acceptHeader = new MediaTypeWithQualityHeaderValue("application/json"); - request.Headers.Accept.Add(acceptHeader); - } - - using (HttpResponseMessage response = await HttpClient.SendRequestAsync(ConfigureRequest, cancellationToken).ConfigureAwait(false)) - return await response.Content.ReadAsAsync>().ConfigureAwait(false); - } - - public async Task> GetAuditedEquipment(FacilityAudit facilityAudit, CancellationToken cancellationToken = default) - { - string url = BuildURL(BaseURL, facilityAudit.Equipment); - - void ConfigureRequest(HttpRequestMessage request) - { - request.RequestUri = new Uri($"{url}"); - request.Method = HttpMethod.Get; - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", TokenProvider()); - - MediaTypeWithQualityHeaderValue acceptHeader = new MediaTypeWithQualityHeaderValue("application/json"); - request.Headers.Accept.Add(acceptHeader); - } - - using (HttpResponseMessage response = await HttpClient.SendRequestAsync(ConfigureRequest, cancellationToken).ConfigureAwait(false)) - return await response.Content.ReadAsAsync>().ConfigureAwait(false); - } - - public async Task> GetAuditCurve(PQIEquipment equipment, CancellationToken cancellationToken = default) - { - string url = BuildURL(BaseURL, equipment.AuditCurves); - - void ConfigureRequest(HttpRequestMessage request) - { - request.RequestUri = new Uri($"{url}"); - request.Method = HttpMethod.Get; - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", TokenProvider()); - - MediaTypeWithQualityHeaderValue acceptHeader = new MediaTypeWithQualityHeaderValue("application/json"); - request.Headers.Accept.Add(acceptHeader); - } - - using (HttpResponseMessage response = await HttpClient.SendRequestAsync(ConfigureRequest, cancellationToken).ConfigureAwait(false)) - return await response.Content.ReadAsAsync>().ConfigureAwait(false); - } - - public async Task> GetAuditCurve(int equipmentID, CancellationToken cancellationToken = default) - { - string url = BuildURL(BaseURL, "AuditCurve"); - string queryString = $"equipmentID={equipmentID}"; - - void ConfigureRequest(HttpRequestMessage request) - { - request.RequestUri = new Uri($"{url}?{queryString}"); - request.Method = HttpMethod.Get; - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", TokenProvider()); - - MediaTypeWithQualityHeaderValue acceptHeader = new MediaTypeWithQualityHeaderValue("application/json"); - request.Headers.Accept.Add(acceptHeader); - } - - using (HttpResponseMessage response = await HttpClient.SendRequestAsync(ConfigureRequest, cancellationToken).ConfigureAwait(false)) - return await response.Content.ReadAsAsync>().ConfigureAwait(false); - } - - public async Task GetTestCurve(AuditCurve auditCurve, CancellationToken cancellationToken = default) - { - string url = BuildURL(BaseURL, auditCurve.Curve); - - void ConfigureRequest(HttpRequestMessage request) - { - request.RequestUri = new Uri($"{url}"); - request.Method = HttpMethod.Get; - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", TokenProvider()); - - MediaTypeWithQualityHeaderValue acceptHeader = new MediaTypeWithQualityHeaderValue("application/json"); - request.Headers.Accept.Add(acceptHeader); - } - - using (HttpResponseMessage response = await HttpClient.SendRequestAsync(ConfigureRequest, cancellationToken).ConfigureAwait(false)) - return await response.Content.ReadAsAsync().ConfigureAwait(false); - } - - public async Task> GetTestCurvePoints(TestCurve testCurve, CancellationToken cancellationToken = default) - { - string url = BuildURL(BaseURL, testCurve.Points); - - void ConfigureRequest(HttpRequestMessage request) - { - request.RequestUri = new Uri($"{url}"); - request.Method = HttpMethod.Get; - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", TokenProvider()); - - MediaTypeWithQualityHeaderValue acceptHeader = new MediaTypeWithQualityHeaderValue("application/json"); - request.Headers.Accept.Add(acceptHeader); - } - - using (HttpResponseMessage response = await HttpClient.SendRequestAsync(ConfigureRequest, cancellationToken).ConfigureAwait(false)) - return await response.Content.ReadAsAsync>().ConfigureAwait(false); - } - - private static string BuildURL(params string[] parts) - { - const string Separator = "/"; - string combinedPath = ""; - - foreach (string path in parts) - { - if (path == null) - throw new ArgumentNullException(nameof(parts), "One of the strings in the array is null."); - - if (path.Length == 0) - continue; - - if (combinedPath.Length == 0) - combinedPath = path; - else if (path.StartsWith(Separator) && combinedPath.EndsWith(Separator)) - combinedPath += path.Substring(Separator.Length); - else if (path.StartsWith(Separator)) - combinedPath += path; - else if (combinedPath.EndsWith(Separator)) - combinedPath += path; - else - combinedPath += Separator + path; - } - - return combinedPath; - } - - private static HttpClient HttpClient => - HttpClientProvider.GetClient(); - } -} diff --git a/Libraries/openXDA.PQI/PQIWSQueryHelper.cs b/Libraries/openXDA.PQI/PQIWSQueryHelper.cs deleted file mode 100644 index 04b2a11c..00000000 --- a/Libraries/openXDA.PQI/PQIWSQueryHelper.cs +++ /dev/null @@ -1,282 +0,0 @@ -//****************************************************************************************************** -// PQIWSQueryHelper.cs - Gbtc -// -// Copyright © 2021, 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/29/2021 - Stephen C. Wills -// Generated original version of source code. -// -//****************************************************************************************************** - -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Gemstone.Data; -using Gemstone.Data.DataExtensions; - -namespace openXDA.PQI -{ - public class PQIWSQueryHelper - { - private class EquipmentComparer : IEqualityComparer - { - public bool Equals(Equipment x, Equipment y) - { - if (x is null && y is null) - return true; - - if (x is null || y is null) - return false; - - object xKey = GetKey(x); - object yKey = GetKey(y); - return Equals(xKey, yKey); - } - - public int GetHashCode(Equipment equipment) - { - object key = GetKey(equipment); - return key.GetHashCode(); - } - - private object GetKey(Equipment equipment) - { - return new - { - equipment.Facility, - equipment.Area, - equipment.SectionTitle, - equipment.SectionRank, - equipment.ComponentModel, - equipment.Manufacturer, - equipment.Series, - equipment.ComponentType - }; - } - - public static EquipmentComparer Instance { get; } - = new EquipmentComparer(); - } - - private class PQIModelComparer: IEqualityComparer where T: class, PQIModel - { - public bool Equals(T x, T y) - { - if (x is null && y is null) - return true; - - if (x is null || y is null) - return false; - - object xKey = GetKey(x); - object yKey = GetKey(y); - return Equals(xKey, yKey); - } - - public int GetHashCode(T facilityAudit) - { - object key = GetKey(facilityAudit); - return key.GetHashCode(); - } - - private object GetKey(T facilityAudit) - { - return new - { - facilityAudit.Path - }; - } - - public static PQIModelComparer Instance { get; } - = new PQIModelComparer(); - } - - private const string FacilityDisturbanceQueryFormat = - @"SELECT - Customer.PQIFacilityID, - Disturbance.PerUnitMagnitude, - Disturbance.DurationSeconds - FROM Disturbance LEFT JOIN - Event ON Event.ID = Disturbance.EventID LEFT JOIN - CustomerMeter ON CustomerMeter.MeterID = Event.MeterID LEFT JOIN - Customer ON Customer.ID = CustomerMeter.CustomerID - WHERE PQIFacilityID IS NOT NULL AND EventID = {0} - UNION - SELECT - Customer.PQIFacilityID, - Disturbance.PerUnitMagnitude, - Disturbance.DurationSeconds - FROM Disturbance LEFT JOIN - Event ON Event.ID = Disturbance.EventID LEFT JOIN - CustomerAsset ON CustomerAsset.AssetID = Event.AssetID LEFT JOIN - Customer ON Customer.ID = CustomerAsset.CustomerID - WHERE PQIFacilityID IS NOT NULL AND EventID = {0}"; - - public PQIWSQueryHelper(Func connectionFactory, PQIWSClient pqiwsClient) - { - ConnectionFactory = connectionFactory; - PQIWSClient = pqiwsClient; - } - - private Func ConnectionFactory { get; } - private PQIWSClient PQIWSClient { get; } - - public async Task HasImpactedComponentsAsync(int eventID, CancellationToken cancellationToken = default) - { - async Task IsImpactedAsync(DataRow facilityDisturbance) - { - int facilityID = facilityDisturbance.ConvertField("PQIFacilityID"); - double magnitude = facilityDisturbance.ConvertField("PerUnitMagnitude"); - double duration = facilityDisturbance.ConvertField("DurationSeconds"); - return await PQIWSClient.IsImpactedAsync(facilityID, magnitude, duration, cancellationToken).ConfigureAwait(false); - } - - async Task AnyImpactedAsync(IEnumerable> isImpactedTasks) - { - foreach (Task task in isImpactedTasks) - { - if (await task.ConfigureAwait(false)) - return true; - } - - return false; - } - - using (AdoDataConnection connection = ConnectionFactory()) - using (DataTable table = connection.RetrieveData(FacilityDisturbanceQueryFormat, eventID)) - { - var tasks = table - .AsEnumerable() - .Select(IsImpactedAsync); - - return await AnyImpactedAsync(tasks).ConfigureAwait(false); - } - } - - public async Task> GetAllImpactedEquipmentAsync(int eventID, CancellationToken cancellationToken = default) - { - using (AdoDataConnection connection = ConnectionFactory()) - return await GetAllImpactedEquipmentAsync(connection, eventID, cancellationToken).ConfigureAwait(false); - } - - public async Task> GetAllImpactedEquipmentAsync(IEnumerable eventIDs, CancellationToken cancellationToken = default) - { - using (AdoDataConnection connection = ConnectionFactory()) - { - Task> _GetAllImpactedEquipmentAsync(int eventID) => - GetAllImpactedEquipmentAsync(connection, eventID, cancellationToken); - - var tasks = eventIDs.Select(_GetAllImpactedEquipmentAsync); - List[] equipmentLists = await Task.WhenAll(tasks).ConfigureAwait(false); - return Flatten(equipmentLists); - } - } - - public async Task>>> GetAllTestCurvesAsync (IEnumerable eventIDs, CancellationToken cancellationToken = default) - { - using (AdoDataConnection connection = ConnectionFactory()) - { - Task>>> _GetAllTestCurvesAsync(int eventID) => - GetAllCurvesAsync(connection, eventID, cancellationToken); - - var tasks = eventIDs.Select(_GetAllTestCurvesAsync); - List>>[] curveList = await Task.WhenAll(tasks).ConfigureAwait(false); - return curveList.SelectMany(list => list).ToList(); - } - } - - private async Task>>> GetAllCurvesAsync(AdoDataConnection connection, int eventID, CancellationToken cancellationToken = default) - { - int GetFacilityID(DataRow facilityDisturbance) - { - return facilityDisturbance.ConvertField("PQIFacilityID"); - } - - async Task> GetFacilityAudits(int facilityID) - { - return await PQIWSClient.GetFacilityAudits(facilityID, cancellationToken).ConfigureAwait(false); - } - - async Task> GetAuditedEquipment(FacilityAudit facilityAudit) - { - return await PQIWSClient.GetAuditedEquipment(facilityAudit, cancellationToken).ConfigureAwait(false); - } - - async Task> GetAuditCurves(PQIEquipment equipment) - { - return await PQIWSClient.GetAuditCurve(equipment, cancellationToken).ConfigureAwait(false); - } - - async Task GetTestCurve(AuditCurve auditCurve) - { - return await PQIWSClient.GetTestCurve(auditCurve, cancellationToken).ConfigureAwait(false); - } - - async Task>> GetTestCurvePoints(TestCurve testCurve) - { - return new Tuple>(testCurve, (await PQIWSClient.GetTestCurvePoints(testCurve, cancellationToken).ConfigureAwait(false))); - } - - using (DataTable table = connection.RetrieveData(FacilityDisturbanceQueryFormat, eventID)) - { - int[] facilityIDs = table - .AsEnumerable() - .Select(GetFacilityID) - .ToArray(); - - List[] facilityAudits = await Task.WhenAll(facilityIDs.Distinct().Select(GetFacilityAudits)).ConfigureAwait(false); - List[] auditedEquipment = await Task.WhenAll(Flatten(facilityAudits).Select(GetAuditedEquipment)).ConfigureAwait(false); - List[] auditCurves = await Task.WhenAll(Flatten(auditedEquipment).Select(GetAuditCurves)).ConfigureAwait(false); - TestCurve[] testCurves = await Task.WhenAll(Flatten(auditCurves).Select(GetTestCurve)).ConfigureAwait(false); - - return (await Task.WhenAll(testCurves.Distinct().Select(GetTestCurvePoints)).ConfigureAwait(false)).ToList(); - } - } - private async Task> GetAllImpactedEquipmentAsync(AdoDataConnection connection, int eventID, CancellationToken cancellationToken) - { - async Task> GetImpactedEquipmentAsync(DataRow facilityDisturbance) - { - int facilityID = facilityDisturbance.ConvertField("PQIFacilityID"); - double magnitude = facilityDisturbance.ConvertField("PerUnitMagnitude"); - double duration = facilityDisturbance.ConvertField("DurationSeconds"); - return await PQIWSClient.GetImpactedEquipmentAsync(facilityID, magnitude, duration, cancellationToken).ConfigureAwait(false); - } - - using (DataTable table = connection.RetrieveData(FacilityDisturbanceQueryFormat, eventID)) - { - var tasks = table - .AsEnumerable() - .Select(GetImpactedEquipmentAsync); - - List[] equipmentLists = await Task.WhenAll(tasks).ConfigureAwait(false); - return Flatten(equipmentLists); - } - } - - private List Flatten(IEnumerable> equipmentLists) => equipmentLists - .SelectMany(list => list) - .Distinct(EquipmentComparer.Instance) - .ToList(); - - private List Flatten(IEnumerable> facilityAuditLists) where T: class,PQIModel => facilityAuditLists - .SelectMany(list => list) - .Distinct(PQIModelComparer.Instance) - .ToList(); - } -} diff --git a/Libraries/openXDA.PQI/PingClient.cs b/Libraries/openXDA.PQI/PingClient.cs deleted file mode 100644 index 51875a0c..00000000 --- a/Libraries/openXDA.PQI/PingClient.cs +++ /dev/null @@ -1,97 +0,0 @@ -//****************************************************************************************************** -// PingClient.cs - Gbtc -// -// Copyright © 2021, 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/26/2021 - Stephen C. Wills -// Generated original version of source code. -// -//****************************************************************************************************** - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Newtonsoft.Json.Linq; - -namespace openXDA.PQI -{ - public class PingClient - { - public PingClient(string tokenURL) - { - TokenURL = tokenURL; - IsExpiredFunc = () => true; - } - - private string TokenURL { get; } - private Func IsExpiredFunc { get; set; } - - public string AccessToken { get; private set; } - public bool IsExpired => IsExpiredFunc(); - - /// - /// Exchange credentials for a new access token. - /// - /// The client application's credentials. - /// The user's credentials. - /// Token for cancelling the exchange. - /// The task that represents the exchange of credentials for an access token. - public async Task ExchangeAsync(NetworkCredential clientCredential, NetworkCredential userCredential, CancellationToken cancellationToken = default) - { - void ConfigureRequest(HttpRequestMessage request) - { - request.RequestUri = new Uri(TokenURL); - request.Method = HttpMethod.Post; - - string clientID = clientCredential.UserName; - string clientSecret = clientCredential.Password; - string credentials = $"{clientID}:{clientSecret}"; - byte[] encodedCredentials = Encoding.UTF8.GetBytes(credentials); - string credentials64 = Convert.ToBase64String(encodedCredentials); - request.Headers.Authorization = new AuthenticationHeaderValue("Basic", credentials64); - - List> body = new List>(); - body.Add(new KeyValuePair("username", userCredential.UserName)); - body.Add(new KeyValuePair("password", userCredential.Password)); - body.Add(new KeyValuePair("grant_type", "password")); - request.Content = new FormUrlEncodedContent(body); - - MediaTypeWithQualityHeaderValue acceptHeader = new MediaTypeWithQualityHeaderValue("application/json"); - request.Headers.Accept.Add(acceptHeader); - } - - using (HttpResponseMessage response = await HttpClient.SendRequestAsync(ConfigureRequest, cancellationToken).ConfigureAwait(false)) - { - response.EnsureSuccessStatusCode(); - - Stopwatch stopwatch = Stopwatch.StartNew(); - JObject content = await response.Content.ReadAsAsync(cancellationToken).ConfigureAwait(false); - int expiration = content["expires_in"].Value(); - AccessToken = content["access_token"].Value(); - IsExpiredFunc = () => stopwatch.Elapsed.TotalSeconds > expiration; - } - } - - private static HttpClient HttpClient => - HttpClientProvider.GetClient(); - } -} diff --git a/Libraries/openXDA.PQI/Properties/AssemblyInfo.cs b/Libraries/openXDA.PQI/Properties/AssemblyInfo.cs deleted file mode 100644 index 1d163786..00000000 --- a/Libraries/openXDA.PQI/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("openXDA.PQI")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("openXDA.PQI")] -[assembly: AssemblyCopyright("Copyright © 2021")] -[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("3dec81f1-9a02-4bbf-8325-f332ac63393e")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("3.0.5.93")] -[assembly: AssemblyVersion("3.0.5.93")] -[assembly: AssemblyFileVersion("3.0.5.93")] diff --git a/Libraries/openXDA.PQI/TestCurve.cs b/Libraries/openXDA.PQI/TestCurve.cs deleted file mode 100644 index 20b900d7..00000000 --- a/Libraries/openXDA.PQI/TestCurve.cs +++ /dev/null @@ -1,96 +0,0 @@ -//****************************************************************************************************** -// TestCurve.cs - Gbtc -// -// Copyright © 2021, 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/09/2022 - C. Lackner -// Generated original version of source code. -// -//****************************************************************************************************** - -using System; - -namespace openXDA.PQI -{ - - public class TestCurve - { - /// - /// Path to query this test curve (aka ride-through curve) - /// - public string Path { get; set; } - - /// - /// Path to query the component this curve was assigned to - /// - public string Component { get; set; } - - /// - /// Path to query the audit curves associated with this test curve - /// - public string AuditCurves { get; set; } - - /// - /// Path to query the points in the curve - /// - public string Points { get; set; } - - /// - /// Name of the curve - /// - public string Name { get; set; } - - /// - /// Description of the curve - /// - public string Description { get; set; } - - /// - /// Entity - /// - public string Entity { get; set; } - - /// - /// Location - /// - public string Location { get; set; } - - /// - /// Investigator - /// - public string Investigator { get; set; } - - /// - /// Date - /// - public DateTime? Date { get; set; } - - /// - /// Additional notes about the curve - /// - public string Notes { get; set; } - - /// - /// Frequency (Hz) of the voltage - /// - public double? Frequency { get; set; } - - /// - /// Voltage level (in volts) during normal conditions - /// - public double? NominalVoltage { get; set; } - } -} diff --git a/Libraries/openXDA.PQI/TestCurvePoint.cs b/Libraries/openXDA.PQI/TestCurvePoint.cs deleted file mode 100644 index bad0c129..00000000 --- a/Libraries/openXDA.PQI/TestCurvePoint.cs +++ /dev/null @@ -1,53 +0,0 @@ -//****************************************************************************************************** -// TestCurvePoint.cs - Gbtc -// -// Copyright © 2021, 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/09/2022 - C. Lackner -// Generated original version of source code. -// -//****************************************************************************************************** - -using System; - -namespace openXDA.PQI -{ - - public class TestCurvePoint - { - /// - /// Path to query this test curve point - /// - public string Path { get; set; } - - /// - /// Path to query the test curve this point belongs to - /// - public string TestCurve { get; set; } - - /// - /// X-value (duration) on a mag/dur chart - /// - public double X { get; set; } - - /// - /// Y-value(magnitude) on a mag/dur chart - /// - public double Y { get; set; } - - - } -} diff --git a/Libraries/openXDA.PQI/openXDA.PQI.csproj b/Libraries/openXDA.PQI/openXDA.PQI.csproj deleted file mode 100644 index f24661b6..00000000 --- a/Libraries/openXDA.PQI/openXDA.PQI.csproj +++ /dev/null @@ -1,32 +0,0 @@ - - - net9.0 - Debug;Development;Release - openXDA.PQI - openXDA.PQI - false - - - - - - - - - true - full - false - ..\..\..\Build\Output\Debug\Libraries\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - ..\..\..\Build\Output\Release\Libraries\ - TRACE - prompt - 4 - - \ No newline at end of file diff --git a/Libraries/openXDA.Reports/AllBreakersReport.cs b/Libraries/openXDA.Reports/AllBreakersReport.cs deleted file mode 100644 index c248f090..00000000 --- a/Libraries/openXDA.Reports/AllBreakersReport.cs +++ /dev/null @@ -1,411 +0,0 @@ -//****************************************************************************************************** -// AllBreakersReport.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/05/2019 - Billy Ernest -// Generated original version of source code. -// -//****************************************************************************************************** - -using System; -using System.Collections.Generic; -using System.Data; -using System.Drawing; -using System.IO; -using System.Linq; -using Gemstone.Configuration; -using Gemstone.Data; -using log4net; -using Root.Reports; - -namespace openXDA.Reports -{ - public class AllBreakersReport : Report - { - #region [ Members ] - - // Nested Types - public class Point - { - public DateTime Time; - public double Value; - } - - // Constants - private const double PageMarginMillimeters = 12.7D; // 1-inch margin - private const double PageHeightMillimeters = 8.5D * 25.4D; // 8.5 inch height (landscape) - private const double PageWidthMillimeters = 11.0D * 25.4D; // 11 inch width (landscape) - private const double FooterHeightMillimeters = (10.0D / 72.0D) * 25.4D; - private const double SpacingMillimeters = 6.0D; - - private const string TitleText = "All Breakers Report"; - - private const string Query = @" - SELECT - MAX(BreakerOperation.TripCoilEnergized) as LastOperationDate, - BreakerOperation.BreakerNumber, - COUNT(BreakerOperation.ID) as Total, - MaximoBreaker.AssetNum, - Asset.AssetName, - -- last timing info - (SELECT Name from Phase WHERE ID = (SELECT TOP 1 PhaseID FROM BreakerOperation as bo WHERE bo.BreakerNumber = BreakerOperation.BreakerNumber ORDER BY TripCoilEnergized)) as LastPhase, - (SELECT TOP 1 BreakerTiming FROM BreakerOperation as bo WHERE bo.BreakerNumber = BreakerOperation.BreakerNumber ORDER BY TripCoilEnergized) as LastWaveformTiming, - (SELECT TOP 1 StatusTiming FROM BreakerOperation as bo WHERE bo.BreakerNumber = BreakerOperation.BreakerNumber ORDER BY TripCoilEnergized) as LastStatusTiming, - MaximoBreaker.BreakerSpeed as MfrSpeed, - (SELECT Name from BreakerOperationType WHERE ID = (SELECT TOP 1 BreakerOperationTypeID FROM BreakerOperation as bo WHERE bo.BreakerNumber = BreakerOperation.BreakerNumber ORDER BY TripCoilEnergized)) as OperationTiming, - (SELECT TOP 1 CASE WHEN StatusTiming < BreakerTiming THEN 'Status' ELSE 'Waveform' END FROM BreakerOperation as bo WHERE bo.BreakerNumber = BreakerOperation.BreakerNumber ORDER BY TripCoilEnergized) as LastMethod, - -- last slow - COUNT(LateBreakerOperation.ID) as TotalLateOperation, - MAX(LateBreakerOperation.TripCoilEnergized) as LastLateOperation, - -- mfr info - MaximoBreaker.Manufacturer, - MaximoBreaker.SerialNum, - MaximoBreaker.MfrYear, - MaximoBreaker.ModelNum, - MaximoBreaker.InterruptCurrentRating, - MaximoBreaker.ContinuousAmpRating - FROM - BreakerOperation JOIN - BreakerOperationType ON BreakerOperation.BreakerOperationTypeID = BreakerOperationType.ID LEFT OUTER JOIN - BreakerOperation LateBreakerOperation ON - BreakerOperation.ID = LateBreakerOperation.ID AND - BreakerOperationType.Name = 'Late' OUTER APPLY - ( - SELECT TOP 1 Asset.* - FROM - Asset JOIN - Channel ON Channel.AssetID = Asset.ID JOIN - BreakerChannel ON BreakerChannel.ChannelID = Channel.ID - WHERE BreakerChannel.BreakerNumber = BreakerOperation.BreakerNumber - ORDER BY Asset.ID - ) Asset LEFT OUTER JOIN - MaximoBreaker ON BreakerOperation.BreakerNumber = SUBSTRING(MaximoBreaker.BreakerNum, PATINDEX('%[^0]%', MaximoBreaker.BreakerNum + '.'), LEN(MaximoBreaker.BreakerNum)) - WHERE CAST(BreakerOperation.TripCoilEnergized AS DATE) BETWEEN {0} AND {1} - GROUP BY - BreakerOperation.BreakerNumber, - MaximoBreaker.AssetNum, - Asset.AssetName, - MaximoBreaker.Manufacturer, - MaximoBreaker.SerialNum, - MaximoBreaker.MfrYear, - MaximoBreaker.ModelNum, - MaximoBreaker.InterruptCurrentRating, - MaximoBreaker.ContinuousAmpRating, - MaximoBreaker.BreakerSpeed - ORDER BY - LastOperationDate - "; - - private const string TestQuery = @" - SELECT - MAX(BreakerOperation.TripCoilEnergized) as LastOperationDate, - BreakerOperation.BreakerNumber, - COUNT(BreakerOperation.ID) as Total, - MaximoBreaker.[Breaker Number] as AssetNum, - MaximoBreaker.[Line Name] as LineName, - -- last timing info - (SELECT Name from Phase WHERE ID = (SELECT TOP 1 PhaseID FROM GTCBreakerOperationsTable as bo WHERE bo.BreakerNumber = BreakerOperation.BreakerNumber ORDER BY TripCoilEnergized)) as LastPhase, - (SELECT TOP 1 BreakerTiming FROM GTCBreakerOperationsTable as bo WHERE bo.BreakerNumber = BreakerOperation.BreakerNumber ORDER BY TripCoilEnergized) as LastWaveformTiming, - (SELECT TOP 1 StatusTiming FROM GTCBreakerOperationsTable as bo WHERE bo.BreakerNumber = BreakerOperation.BreakerNumber ORDER BY TripCoilEnergized) as LastStatusTiming, - MaximoBreaker.[Breaker Mfr Speed] as MfrSpeed, - (SELECT Name from BreakerOperationType WHERE ID = (SELECT TOP 1 BreakerOperationTypeID FROM GTCBreakerOperationsTable as bo WHERE bo.BreakerNumber = BreakerOperation.BreakerNumber ORDER BY TripCoilEnergized)) as OperationTiming, - (SELECT TOP 1 CASE WHEN StatusTiming < BreakerTiming THEN 'Status' ELSE 'Waveform' END FROM GTCBreakerOperationsTable as bo WHERE bo.BreakerNumber = BreakerOperation.BreakerNumber ORDER BY TripCoilEnergized) as LastMethod, - -- last slow - COUNT(BOLate.ID) as TotalLateOperation, - MAX(BOLate.TripCoilEnergized) as LastLateOperation, - -- mfr info - MaximoBreaker.Manufacturer, - MaximoBreaker.[Serial Number] as SerialNum, - MaximoBreaker.[Mfr Year] as MfrYear, - MaximoBreaker.[Model Number] as ModelNum, - MaximoBreaker.[Interrupt current Rating (A)] as InterruptCurrentRating, - MaximoBreaker.[ Continuous Amp Rating (A)] as ContinuousAmpRating - FROM - GTCBreakerOperationsTable as BreakerOperation LEFT JOIN - GTCBreakerOperationsTable as BOLate ON BreakerOperation.ID = BOLate.ID AND BOLate.BreakerOperationTypeID = (SELECT ID FROM BreakerOperationType WHERE Name = 'Late') LEFT JOIN - MaximoBreakerInfo as MaximoBreaker ON BreakerOperation.BreakerNumber = SUBSTRING(MaximoBreaker.[Breaker Number], PATINDEX('%[^0]%', MaximoBreaker.[Breaker Number] + '.'), LEN(MaximoBreaker.[Breaker Number])) - WHERE - Cast(BreakerOperation.TripCoilEnergized as Date) BETWEEN {0} AND {1} - GROUP BY - BreakerOperation.BreakerNumber, - MaximoBreaker.[Breaker Number], - MaximoBreaker.[Line Name], - MaximoBreaker.Manufacturer, - MaximoBreaker.[Serial Number], - MaximoBreaker.[Mfr Year], - MaximoBreaker.[Breaker Mfr Speed], - MaximoBreaker.[Model Number], - MaximoBreaker.[Interrupt current Rating (A)], - MaximoBreaker.[ Continuous Amp Rating (A)] - ORDER BY - LastOperationDate - "; - - #endregion - - #region [ Constructors ] - - public AllBreakersReport(DateTime startTime, DateTime endTime) - { - StartTime = startTime; - EndTime = endTime; - FontDefinition = new FontDef(this, FontDef.StandardFont.Helvetica); - - using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) - { -#if DEBUG - DataTable = connection.RetrieveData(TestQuery, startTime, endTime); -#else - DataTable = connection.RetrieveData(Query, startTime, endTime); -#endif - } - } - - #endregion - - #region [ Properties ] - - public DateTime StartTime { get; set; } - public DateTime EndTime { get; set; } - public FontDef FontDefinition { get; set; } - public DataTable DataTable { get; set; } - - #endregion - - #region [ Methods ] - - public byte[] CreatePDF() - { - try - { - GenerateReport(); - - using (MemoryStream stream = new MemoryStream()) - { - formatter.Create(this, stream); - return stream.ToArray(); - } - } - catch (Exception ex) { - Log.Error(ex.ToString(), ex); - return null; - } - } - - private void GenerateReport() - { - CreatePage(); - - double verticalMillimeters = InsertHeader(); - CreateTable(verticalMillimeters); - - foreach (Page page in enum_Page) - InsertFooter(page); - } - - private double CreateTable(double verticalMillimeters) - { - if (DataTable.Rows.Count == 0) - { - verticalMillimeters += InsertItalicText(verticalMillimeters, $" No Breaker Operations during {StartTime:MM/dd/yyyy} - {EndTime:MM/dd/yyyy}"); - return verticalMillimeters; - } - - FontProp headerProp = new FontProp(FontDefinition, 0); - headerProp.rSizePoint = 7.0D; - - using (TableLayoutManager tlm = new TableLayoutManager(headerProp)) - { - tlm.tlmCellDef_Header.rAlignV = RepObj.rAlignCenter; // set vertical alignment of all header cells - tlm.tlmCellDef_Default.penProp_LineBottom = new PenProp(this, 0.05, Color.LightGray); // set bottom line for all cells - tlm.tlmHeightMode = TlmHeightMode.AdjustLast; - - tlm.eNewContainer += (oSender, ea) => - { - verticalMillimeters += tlm.rCurY_MM; - verticalMillimeters = NextTablePage(verticalMillimeters, ea); - }; - - List columns = new List() - { - new { Name = "Last Op", Column = "LastOperationDate", Width = (PageWidthMillimeters - 2 * PageMarginMillimeters)*.05 }, - new { Name = "Breaker", Column = "BreakerNumber", Width = (PageWidthMillimeters - 2 * PageMarginMillimeters) * .04 }, - new { Name = "# of Ops", Column = "Total", Width = (PageWidthMillimeters - 2 * PageMarginMillimeters) * .04 }, - new { Name = "Asset", Column = "AssetNum", Width = (PageWidthMillimeters - 2 * PageMarginMillimeters) * .05 }, - new { Name = "Line", Column = "LineName", Width = (PageWidthMillimeters - 2 * PageMarginMillimeters) * .10 }, - new { Name = "Phase", Column = "LastPhase", Width =(PageWidthMillimeters - 2 * PageMarginMillimeters) * .04 }, - new { Name = "Timing (wf)", Column = "LastWaveformTiming", Width = (PageWidthMillimeters - 2 * PageMarginMillimeters) * .05 }, - new { Name = "Timing (sb)", Column = "LastStatusTiming", Width =(PageWidthMillimeters - 2 * PageMarginMillimeters) * .05 }, - new { Name = "MFR Speed", Column = "MfrSpeed", Width =(PageWidthMillimeters - 2 * PageMarginMillimeters) * .04 }, - new { Name = "Operation Timing", Column = "OperationTiming", Width =(PageWidthMillimeters - 2 * PageMarginMillimeters) * .06 }, - new { Name = "Method", Column = "LastMethod", Width =(PageWidthMillimeters - 2 * PageMarginMillimeters) * .05 }, - new { Name = "# Late", Column = "TotalLateOperation", Width = (PageWidthMillimeters - 2 * PageMarginMillimeters) * .03 }, - new { Name = "Last Late", Column = "LastLateOperation", Width =(PageWidthMillimeters - 2 * PageMarginMillimeters) * .05 }, - new { Name = "MFR", Column = "Manufacturer", Width =(PageWidthMillimeters - 2 * PageMarginMillimeters) * .07 }, - new { Name = "Serial #", Column = "SerialNum", Width =(PageWidthMillimeters - 2 * PageMarginMillimeters) * .08 }, - new { Name = "MFR Year", Column = "MfrYear", Width =(PageWidthMillimeters - 2 * PageMarginMillimeters) * .04 }, - new { Name = "Model #", Column = "ModelNum", Width =(PageWidthMillimeters - 2 * PageMarginMillimeters) * .07}, - new { Name = "ICR (A)", Column = "InterruptCurrentRating", Width =(PageWidthMillimeters - 2 * PageMarginMillimeters) * .05}, - new { Name = "CAR (A)", Column = "ContinuousAmpRating", Width =(PageWidthMillimeters - 2 * PageMarginMillimeters) * .05 } - }; - - foreach (var column in columns) - new TlmColumnMM(tlm, column.Name, column.Width); - - List centeredCols = new List() { "Total", "BreakerNumber", "AssetNum", "LastPhase" }; - - foreach (DataRow row in DataTable.Rows) - { - tlm.NewRow(); - int index = 0; - - foreach (var column in columns) - { - if (column.Column == "LastOperationDate" || (column.Column == "LastLateOperation" && row[column.Column].ToString() != string.Empty)) - { - FontProp textProp = new FontProp(FontDefinition, 0); - textProp.rSizePoint = 6.0D; - string date = DateTime.Parse(row[column.Column].ToString()).ToString("MM/dd/yyyy"); - string time = DateTime.Parse(row[column.Column].ToString()).ToString("HH:mm:ss"); - tlm.Add(index, new RepString(textProp, date)); - tlm.NewLine(index); - tlm.Add(index++, new RepString(textProp, time)); - } - else - { - FontProp textProp = new FontProp(FontDefinition, 0); - textProp.rSizePoint = 6.0D; - string value = ""; - - value = row[column.Column].ToString(); - RepString repObj = new RepString(textProp, value); - if (centeredCols.Contains(column.Column)) - repObj.rAlignH = RepObj.rAlignCenter; - - AddDataColumn(tlm, index, value, textProp, column.Width); - index++; - - } - } - } - - tlm.Commit(); - - verticalMillimeters += tlm.rCurY_MM + 10; - } - - return verticalMillimeters; - } - - private void AddDataColumn(TlmBase tlm, int index, string value, FontProp fontProp, double width) - { - double initialWidth = fontProp.rGetTextWidthMM(value); - string remainingString = value.Trim(); - - while (initialWidth > width) - { - string[] tokens = remainingString.Split(' '); - string splitValue = tokens.First(); - - foreach (string token in tokens.Skip(1)) - { - if (fontProp.rGetTextWidthMM(splitValue + token) < width) - splitValue = splitValue + ' ' + token; - else - break; - } - - remainingString = remainingString.Replace(splitValue, ""); - tlm.Add(index, new RepString(fontProp, splitValue)); - tlm.NewLine(index); - initialWidth = fontProp.rGetTextWidthMM(remainingString); - } - - tlm.Add(index, new RepString(fontProp, remainingString)); - } - - // Creates a page and sets width and height to standard 8.5x11 inches. - private Page CreatePage() - { - Page page = new Page(this); - page.rWidthMM = PageWidthMillimeters; - page.rHeightMM = PageHeightMillimeters; - return page; - } - - // Inserts the page footer onto the given page, which includes the time of report generation as well as the page number. - private double InsertFooter(Page page) - { - FontProp font = new FontProp(FontDefinition, 0.0D); - font.rSizePoint = 12.0D; - page.AddMM(PageWidthMillimeters - PageMarginMillimeters - font.rGetTextWidthMM(page.iPageNo.ToString()), PageHeightMillimeters - 5, new RepString(font, page.iPageNo.ToString())); - return font.rSizeMM; - } - - // Inserts the page footer onto the given page, which includes the time of report generation as well as the page number. - private double InsertHeader() - { - string ReportIdentifier = "All Breakers Report - " + StartTime.ToString("MM/dd/yyyy") + " - " + EndTime.ToString("MM/dd/yyyy") + ""; - - FontProp font = new FontProp(FontDefinition, 0.0D); - font.rSizePoint = 12.0D; - - FontProp meterNameFont = new FontProp(FontDefinition, 0.0D); - meterNameFont.rSizePoint = 12.0D; - - int height = 6; - double reportIdentifierHorizontalPosition = PageWidthMillimeters / 2 - font.rGetTextWidthMM(ReportIdentifier) / 2; - page_Cur.AddMM(reportIdentifierHorizontalPosition, height, new RepString(font, ReportIdentifier)); - page_Cur.AddMM(0, 10, new RepRectMM(new BrushProp(this, Color.Black), PageWidthMillimeters, 0.1D)); - - return 20; - } - - // Inserts the given text as a section header (16-pt, bold). - private double InsertItalicText(double verticalMillimeters, string text) - { - FontProp font = new FontProp(FontDefinition, 0.0D); - font.rSizePoint = 14.0D; - font.bBold = false; - font.bItalic = true; - page_Cur.AddMM(PageMarginMillimeters, verticalMillimeters, new RepString(font, text)); - return font.rSizeMM + 5; - } - - private double NextTablePage( double verticalMillimeters, TlmBase.NewContainerEventArgs ea) - { - if (verticalMillimeters > (PageHeightMillimeters * 0.75)) - { - CreatePage(); - verticalMillimeters = InsertHeader(); - } - - ea.container.rHeightMM = PageHeightMillimeters - verticalMillimeters - PageMarginMillimeters; - page_Cur.AddMM(PageMarginMillimeters, verticalMillimeters, ea.container); - - return verticalMillimeters; - } - - #endregion - - #region [ Static ] - - private static readonly ILog Log = LogManager.GetLogger(typeof(AllBreakersReport)); - - #endregion - } -} diff --git a/Libraries/openXDA.Reports/EmailWriter.cs b/Libraries/openXDA.Reports/EmailWriter.cs deleted file mode 100644 index 943f9d7e..00000000 --- a/Libraries/openXDA.Reports/EmailWriter.cs +++ /dev/null @@ -1,205 +0,0 @@ -//****************************************************************************************************** -// EmailWriter.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: -// ---------------------------------------------------------------------------------------------------- -// 07/13/2018 - Billy Ernest -// Generated original version of source code. -// -//****************************************************************************************************** - -using Gemstone.Configuration; -using Gemstone.Data; -using Gemstone.Data.DataExtensions; -using Gemstone.StringExtensions; -using log4net; -using openXDA.Configuration; -using openXDA.Model; -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using System.Net; -using System.Net.Mail; -using System.Xml.Linq; - -namespace openXDA.Reports -{ - public class EmailWriter - { - #region [ Constructors ] - - public EmailWriter(PQReportsSettings reportsSettings, EmailSection emailSettings) - { - ReportSettings = reportsSettings; - EmailSettings = emailSettings; - } - - #endregion - - #region [ Properties ] - - public EmailSection EmailSettings { get; } - public PQReportsSettings ReportSettings { get; } - - #endregion - - #region [ Methods ] - - public void Execute(int month, int year) - { - GenerateEmail(month, year); - } - - private void GenerateEmail(int month, int year) - { - using(AdoDataConnection connection = new AdoDataConnection(Settings.Default)) - { - string sql = $"SELECT Email FROM UserAccount WHERE Email IS NOT NULL AND Email <> '' AND ID IN (SELECT UserAccountID FROM EmailGroupUserAccount WHERE EmailGroupID IN (SELECT EmailGroupID FROM EmailGroupType WHERE EmailTypeID IN (SELECT ID FROM EmailType WHERE EmailCategoryID = (SELECT ID FROM EmailCategory WHERE Name = 'PQReport'))))"; - DataTable emailTable = connection.RetrieveData(sql); - List recipients = emailTable.Select().Select(row => row.ConvertField("Email")).ToList(); - string template = connection.ExecuteScalar("SELECT Template FROM XSLTemplate WHERE Name = 'PQReport'"); - string data = connection.ExecuteScalar(@" - SELECT - {0} as [Month], - {1} as [Year], - (select value from DashSettings where Name = 'System.XDAInstance') as [XDALink], - (SELECT - Meter.AssetKey as [Meter], - Report.Results as [Result], - Report.ID as [ReportID] - FROM - Report JOIN - Meter ON Report.MeterID = Meter.ID - WHERE - Month = {0} AND - Year = {1} - FOR XML RAW('Report') ,TYPE, ELEMENTS) as [Reports] - FOR XML RAW ('PQReport'),TYPE, ELEMENTS - ", month, year); - - XDocument htmlDocument = XDocument.Parse(data.ApplyXSLTransform(template), LoadOptions.PreserveWhitespace); - - try - { - string subject = (string)htmlDocument.Descendants("title").FirstOrDefault() ?? "Fault detected by openXDA"; - string html = htmlDocument.ToString(SaveOptions.DisableFormatting).Replace("&", "&").Replace("<", "<").Replace(">", ">"); - - - SendEmail(recipients, subject, html); - LoadSentEmail(connection, recipients, subject, html); - - } - catch (Exception ex) - { - Log.Error(ex.ToString()); - } - - } - } - - - private int LoadSentEmail(AdoDataConnection connection, List recipients, string subject, string body) - { - TimeZoneInfo xDATimeZone = TimeZoneInfo.FindSystemTimeZoneById(connection.ExecuteScalar("SELECT Value FROM Setting WHERE Name = 'XDATimeZone'")); - DateTime now = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, xDATimeZone); - string toLine = string.Join("; ", recipients.Select(recipient => recipient.Trim())); - connection.ExecuteNonQuery("INSERT INTO SentEmail VALUES({0}, {1}, {2}, {3})", now, toLine, subject, body); - return connection.ExecuteScalar("SELECT @@IDENTITY"); - } - - private void SendEmail(List recipients, string subject, string body) - { - const int DefaultSMTPPort = 25; - - if (string.IsNullOrEmpty(EmailSettings.SMTPServer)) - return; - - string[] smtpServerParts = EmailSettings.SMTPServer.Split(':'); - string host = smtpServerParts[0]; - int port; - - if (smtpServerParts.Length <= 1 || !int.TryParse(smtpServerParts[1], out port)) - port = DefaultSMTPPort; - - using (SmtpClient smtpClient = new SmtpClient(host, port)) - using (MailMessage emailMessage = new MailMessage()) - { - if (!string.IsNullOrEmpty(EmailSettings.Username) && (object)EmailSettings.SecurePassword != null) - smtpClient.Credentials = new NetworkCredential(EmailSettings.Username, EmailSettings.SecurePassword); - - smtpClient.EnableSsl = EmailSettings.EnableSSL; - - emailMessage.From = new MailAddress(EmailSettings.FromAddress); - emailMessage.Subject = subject; - emailMessage.Body = body; - emailMessage.IsBodyHtml = true; - - // Add the specified To recipients for the email message - foreach (string toRecipient in recipients) - emailMessage.To.Add(toRecipient.Trim()); - - // Send the email - smtpClient.Send(emailMessage); - } - } - - - public void SendEmailWithAttachment(List recipients, string subject, string body, List attachments) - { - const int DefaultSMTPPort = 25; - - if (string.IsNullOrEmpty(EmailSettings.SMTPServer)) - return; - - string[] smtpServerParts = EmailSettings.SMTPServer.Split(':'); - string host = smtpServerParts[0]; - int port; - - if (smtpServerParts.Length <= 1 || !int.TryParse(smtpServerParts[1], out port)) - port = DefaultSMTPPort; - - using (SmtpClient smtpClient = new SmtpClient(host, port)) - using (MailMessage emailMessage = new MailMessage()) - { - if (!string.IsNullOrEmpty(EmailSettings.Username) && (object)EmailSettings.SecurePassword != null) - smtpClient.Credentials = new NetworkCredential(EmailSettings.Username, EmailSettings.SecurePassword); - - smtpClient.EnableSsl = EmailSettings.EnableSSL; - - emailMessage.From = new MailAddress(EmailSettings.FromAddress); - emailMessage.Subject = subject; - emailMessage.Body = body; - emailMessage.IsBodyHtml = true; - attachments.ForEach(x => emailMessage.Attachments.Add(x)); - - // Add the specified To recipients for the email message - foreach (string toRecipient in recipients) - emailMessage.To.Add(toRecipient.Trim()); - - // Send the email - smtpClient.Send(emailMessage); - } - } - - #endregion - - #region [ Static ] - // Static Fields - private static readonly ILog Log = LogManager.GetLogger(typeof(EmailWriter)); - #endregion - } -} diff --git a/Libraries/openXDA.Reports/IndividualBreakerReport.cs b/Libraries/openXDA.Reports/IndividualBreakerReport.cs deleted file mode 100644 index 4359c7e8..00000000 --- a/Libraries/openXDA.Reports/IndividualBreakerReport.cs +++ /dev/null @@ -1,701 +0,0 @@ -//****************************************************************************************************** -// IndividualBreakerReport.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/02/2019 - Billy Ernest -// Generated original version of source code. -// -//****************************************************************************************************** - -using System; -using System.Collections.Generic; -using System.Data; -using System.Drawing; -using System.IO; -using System.Linq; -using System.Windows.Forms.DataVisualization.Charting; -using Gemstone.Data; -using log4net; -using Root.Reports; -using ChartSeries = System.Windows.Forms.DataVisualization.Charting.Series; -using Gemstone.Configuration; - -namespace openXDA.Reports -{ - public class IndividualBreakerReport : Root.Reports.Report - { - #region [ Members ] - public class Point { - public DateTime Time { get; set; } - public double? Value { get; set; } - } - // Constants - private const double PageMarginMillimeters = 25.4D; // 1-inch margin - private const double PageWidthMillimeters = 8.5D * 25.4D; // 8.5 inch width - private const double PageHeightMillimeters = 11.0D * 25.4D; // 11 inch height - - private const double FooterHeightMillimeters = (10.0D / 72.0D) * 25.4D; - private const double SpacingMillimeters = 6.0D; - - private const string TitleText = "Individual Breaker Report"; - - public const string timingQuery = - @" - SELECT - BreakerOperation.TripCoilEnergized as Time, - BreakerOperation.BreakerNumber, - MeterLine.LineName, - Phase.Name as Phase, - BreakerOperation.BreakerTiming, - CASE - WHEN Phase.Name = 'AN' THEN CAST(BreakerOperation.APhaseBreakerTiming as varchar(max)) - WHEN Phase.Name = 'BN' THEN CAST(BreakerOperation.BPhaseBreakerTiming as varchar(max)) - WHEN Phase.Name = 'CN' THEN CAST(BreakerOperation.CPhaseBreakerTiming as varchar(max)) - ELSE 'N/A' - END as WaveformTiming, - BreakerOperation.StatusTiming as StatusTiming, - MaximoBreaker.BreakerSpeed, - MaximoBreaker.BreakerSpeed * 1.12 as SpeedBandwidth, - MaximoBreaker.BreakerSpeed * 0.12 as Bandwidth, - BreakerOperationType.Name as OperationTiming, - MaximoBreaker.Manufacturer, - MaximoBreaker.SerialNum, - MaximoBreaker.MfrYear, - MaximoBreaker.ModelNum, - MaximoBreaker.InterruptCurrentRating, - MaximoBreaker.ContinuousAmpRating, - MIN(ROUND(FaultSummary.PrefaultCurrent, 0)) as PrefaultCurrent, - CASE - WHEN EventStat.IAMax >= EventStat.IBMax AND EventStat.IAMax >= EventStat.ICMax THEN Round(EventStat.IAMax, 0) - WHEN EventStat.IBMax > EventStat.IAMax AND EventStat.IAMax > EventStat.ICMax THEN Round(EventStat.IAMax, 0) - WHEN EventStat.ICMax > EventStat.IAMax AND EventStat.IAMax > EventStat.IBMax THEN Round(EventStat.IAMax, 0) - ELSE NULL - END as MaxCurrent - FROM - BreakerOperation JOIN - BreakerOperationType ON BreakerOperation.BreakerOperationTypeID = BreakerOperationType.ID JOIN - Phase ON Phase.ID = BreakerOperation.PhaseID JOIN - Event ON BreakerOperation.EventID = Event.ID JOIN - MeterLINE ON Event.MeterID = MeterLine.MeterID AND Event.LineID = MeterLine.LineID LEFT JOIN - MaximoBreaker ON BreakerOperation.BreakerNumber = SUBSTRING(MaximoBreaker.BreakerNum, PATINDEX('%[^0]%', MaximoBreaker.BreakerNum + '.'), LEN(MaximoBreaker.BreakerNum)) LEFT JOIN - FaultSummary ON Event.ID = FaultSummary.EventID AND FaultSummary.IsSelectedAlgorithm = 1 LEFT JOIN - EventStat ON Event.ID = EventStat.EventID - WHERE - BreakerOperation.BreakerNumber = {0} AND - CAST(BreakerOperation.TripCoilEnergized as Date) BETWEEN {1} AND {2} - GROUP BY - BreakerOperation.TripCoilEnergized, - BreakerOperation.BreakerNumber, - MeterLine.LineName, - Phase.Name, - BreakerOperation.BreakerTiming, - BreakerOperation.StatusTiming, - MaximoBreaker.BreakerSpeed, - MaximoBreaker.BreakerSpeed * 1.12, - MaximoBreaker.BreakerSpeed * 0.12, - BreakerOperationType.Name, - MaximoBreaker.Manufacturer, - MaximoBreaker.SerialNum, - MaximoBreaker.MfrYear, - MaximoBreaker.ModelNum, - MaximoBreaker.InterruptCurrentRating, - MaximoBreaker.ContinuousAmpRating, - BreakerOperation.APhaseBreakerTiming, - BreakerOperation.BPhaseBreakerTiming, - BreakerOperation.CPhaseBreakerTiming, - EventStat.IAMax, - EventStat.IBMax, - EventStat.ICMax - ORDER BY - Time - "; - - private const string testTimingQuery = @" - SELECT - * - FROM - IndividualBreakerQuery - WHERE - BreakerNumber = {0} AND - Cast(Time as Date) BETWEEN {1} AND {2} - ORDER BY Time - "; - - private const string infoQuery = - @" - SELECT DISTINCT - MaximoBreaker.BreakerNum as [Breaker Number], - MaximoBreaker.BreakerSpeed as [Breaker Mfr Speed], - MaximoBreaker.BreakerSpeed * 1.12 as [Speed Bandwidth], - MaximoBreaker.BreakerSpeed * 0.12 as [12% Speed Bandwidth], - MaximoBreaker.Manufacturer, - MaximoBreaker.SerialNum as [Serial Number], - MaximoBreaker.MfrYear as [Mfr Year], - MaximoBreaker.ModelNum as [Model Number], - MaximoBreaker.InterruptCurrentRating as [Interrupt Current Rating (A)], - MaximoBreaker.ContinuousAmpRating as [Continuous Amp Rating (A)] - FROM - MaximoBreaker - WHERE - BreakerNum = {0} - - "; - - private const string testInfoQuery = @" - SELECT - [Breaker Number], - [Breaker Mfr Speed], - [Speed Bandwidth], - [12% Speed Bandwidth], - Manufacturer, - [Serial Number], - [Mfr Year], - [Model Number], - [Interrupt Current Rating (A)], - [ Continuous Amp Rating (A)] - FROM - MaximoBreakerInfo - WHERE - [Breaker Number] = {0} - "; - - #endregion - - #region [ Properties ] - - public string BreakerID { get; set; } - public DateTime StartTime { get; set; } - public DateTime EndTime { get; set; } - public FontDef FontDefinition { get; set; } - public DataTable TimingDataTable { get; set; } - public DataTable InfoDataTable { get; set; } - public List TimingPoints { get; set; } - public List MaxCurrentPoints { get; set; } - - public double? SpeedBandwidth { get; set; } - public double? Speed { get; set; } - public double? InterruptCurrentRating { get; set; } - - #endregion - - #region [ Constructors ] - - public IndividualBreakerReport( string breakerId, DateTime startTime, DateTime endTime) - { - StartTime = startTime; - EndTime = endTime; - BreakerID = breakerId; - FontDefinition = new FontDef(this, "Helvetica"); - - using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) { -#if DEBUG - TimingDataTable = connection.RetrieveData(testTimingQuery, breakerId, startTime, endTime); -#else - TimingDataTable = connection.RetrieveData(timingQuery, breakerId, startTime, endTime); -#endif - - TimingPoints = TimingDataTable.Select().Select(x => { - double r; - bool s = double.TryParse(x["WaveformTiming"].ToString(), out r); - return new Point { Time = DateTime.Parse(x["Time"].ToString()), Value = (s ? (double?)r : null) }; - }).OrderBy(x => x.Time).ToList(); - - MaxCurrentPoints = TimingDataTable.Select().Select(x => { - double r; - bool s = double.TryParse(x["MaxCurrent"].ToString(), out r); - return new Point { Time = DateTime.Parse(x["Time"].ToString()), Value = (s ? (double?)r : null) }; - }).OrderBy(x => x.Time).ToList(); - - -#if DEBUG - InfoDataTable = connection.RetrieveData(testInfoQuery, breakerId, startTime, endTime); -#else - InfoDataTable = connection.RetrieveData(infoQuery, breakerId, startTime, endTime); -#endif - - double result; - bool success = double.TryParse(InfoDataTable.Select().FirstOrDefault()?["Breaker Mfr Speed"].ToString(), out result); - Speed = (success ? (double?)result : null); - - success = double.TryParse(InfoDataTable.Select().FirstOrDefault()?["Speed Bandwidth"].ToString(), out result); - SpeedBandwidth = (success ? (double?)result : null); - - success = double.TryParse(InfoDataTable.Select().FirstOrDefault()?["Interrupt Current Rating (A)"].ToString(), out result); - InterruptCurrentRating = (success ? (double?)result : null); - - - } - } - - #endregion - - #region [ Methods ] - - public byte[] createPDF() - { - try - { - GenerateReport(); - - using (MemoryStream stream = new MemoryStream()) - { - this.formatter.Create(this, stream); - return stream.ToArray(); - } - } - catch (Exception ex) { - Log.Error(ex.ToString(), ex); - return null; - } - } - - private void GenerateReport() - { - CreatePage(); - double verticalMillimeters = InsertHeader(); - - verticalMillimeters = CreateInfoTable(verticalMillimeters); - verticalMillimeters = CreateTimingTable(verticalMillimeters); - - if (verticalMillimeters > (PageHeightMillimeters * 0.75)) - { - CreatePage(); - verticalMillimeters = InsertHeader(); - } - - Chart chart = GenerateTimingLineChart(); - verticalMillimeters += 75; - - if(chart != null) - page_Cur.AddMM(PageMarginMillimeters, verticalMillimeters, new RepImageMM(ChartToImage(chart), PageWidthMillimeters - 2 * PageMarginMillimeters, 75)); - - if (verticalMillimeters > (PageHeightMillimeters * 0.75)) - { - CreatePage(); - verticalMillimeters = InsertHeader(); - } - - chart = GenerateMaxCurrentLineChart(); - verticalMillimeters += 75; - - if (chart != null) - page_Cur.AddMM(PageMarginMillimeters, verticalMillimeters, new RepImageMM(ChartToImage(chart), PageWidthMillimeters - 2 * PageMarginMillimeters, 75)); - - foreach (Page page in enum_Page) - { - InsertFooter(page); - } - } - - private double CreateInfoTable(double verticalMillimeters) - { - - if (InfoDataTable.Rows.Count == 0) - { - verticalMillimeters += InsertItalicText(verticalMillimeters, $" No Breaker Information available."); - return verticalMillimeters; - } - - FontProp headerProp = new FontProp(FontDefinition, 0); - headerProp.rSizePoint = 10.0D; - using (TableLayoutManager tlm = new TableLayoutManager(headerProp)) - { - FontProp textProp = new FontProp(FontDefinition, 0); - textProp.rSizePoint = 8.0D; - tlm.tlmCellDef_Header.rAlignV = RepObj.rAlignCenter; // set vertical alignment of all header cells - tlm.tlmCellDef_Default.penProp_LineBottom = new PenProp(this, 0.05, Color.LightGray); // set bottom line for all cells - tlm.tlmHeightMode = TlmHeightMode.AdjustLast; - tlm.eNewContainer += (oSender, ea) => - { - verticalMillimeters += tlm.rCurY_MM; - verticalMillimeters = NextTablePage(verticalMillimeters, ea); - }; - - new TlmColumnMM(tlm, "Breaker Info", (PageWidthMillimeters - 2 * PageMarginMillimeters) * .5); - new TlmColumnMM(tlm, "Value", (PageWidthMillimeters - 2 * PageMarginMillimeters) * .5); - - DataRow dataRow = InfoDataTable.Select().First(); - foreach (DataColumn column in InfoDataTable.Columns) - { - tlm.NewRow(); - tlm.Add(0, new RepString(textProp, column.ColumnName)); - tlm.Add(1, new RepString(textProp, dataRow[column.ColumnName].ToString())); - } - - tlm.Commit(); - - verticalMillimeters += tlm.rCurY_MM + 10; - } - - return verticalMillimeters; - } - - private double CreateTimingTable(double verticalMillimeters) - { - - - List columns = new List() - { - new { Name = "Time", Column = "Time", Width = (PageWidthMillimeters - 2 * PageMarginMillimeters)*.1 }, - new { Name = "Line", Column = "LineName", Width = (PageWidthMillimeters - 2 * PageMarginMillimeters)*.2 }, - new { Name = "Phase", Column = "Phase", Width = (PageWidthMillimeters - 2 * PageMarginMillimeters) * .1 }, - new { Name = "Timing (br)", Column = "BreakerTiming", Width = (PageWidthMillimeters - 2 * PageMarginMillimeters) * .1 }, - new { Name = "Timing (wf)", Column = "WaveformTiming", Width = (PageWidthMillimeters - 2 * PageMarginMillimeters) * .1}, - new { Name = "Timing (sp)", Column = "StatusTiming", Width = (PageWidthMillimeters - 2 * PageMarginMillimeters) * .1 }, - new { Name = "Classification", Column = "OperationTiming", Width = (PageWidthMillimeters - 2 * PageMarginMillimeters) * .1 }, - new { Name = "Prefault Current", Column = "PrefaultCurrent", Width =(PageWidthMillimeters - 2 * PageMarginMillimeters) * .1 }, - new { Name = "Max Current", Column = "MaxCurrent", Width =(PageWidthMillimeters - 2 * PageMarginMillimeters) * .1 }, - }; - - if (TimingDataTable.Rows.Count == 0) - { - verticalMillimeters += InsertItalicText(verticalMillimeters, $" No Breaker Operations during {StartTime.ToString("MM/dd/yyyy")} - {EndTime.ToString("MM/dd/yyyy")}"); - return verticalMillimeters; - } - - FontProp headerProp = new FontProp(FontDefinition, 0); - headerProp.rSizePoint = 10.0D; - using (TableLayoutManager tlm = new TableLayoutManager(headerProp)) - { - FontProp textProp = new FontProp(FontDefinition, 0); - textProp.rSizePoint = 8.0D; - tlm.tlmCellDef_Header.rAlignV = RepObj.rAlignCenter; // set vertical alignment of all header cells - tlm.tlmCellDef_Default.penProp_LineBottom = new PenProp(this, 0.05, Color.LightGray); // set bottom line for all cells - tlm.tlmHeightMode = TlmHeightMode.AdjustLast; - tlm.eNewContainer += (oSender, ea) => - { - verticalMillimeters += tlm.rCurY_MM; - verticalMillimeters = NextTablePage(verticalMillimeters, ea); - }; - - - // define columns - foreach (var column in columns) { - new TlmColumnMM(tlm, column.Name, column.Width); - } - - - foreach(DataRow row in TimingDataTable.Rows) - { - tlm.NewRow(); - int index = 0; - foreach (var column in columns) { - if (column.Column == "Time") - { - string date = DateTime.Parse(row[column.Column].ToString()).ToString("MM/dd/yyyy"); - string time = DateTime.Parse(row[column.Column].ToString()).ToString("HH:mm:ss"); - tlm.Add(index, new RepString(textProp, date)); - tlm.NewLine(index); - tlm.Add(index++, new RepString(textProp, time)); - - } - else if (column.Column == "LineName") - AddDataColumn(tlm, index++, row[column.Column].ToString(), textProp, column.Width); - else - tlm.Add(index++, new RepString(textProp, row[column.Column].ToString())); - } - } - - tlm.Commit(); - - verticalMillimeters += tlm.rCurY_MM + 10; - } - - return verticalMillimeters; - } - - private void AddDataColumn(TlmBase tlm, int index, string value, FontProp fontProp, double width) - { - double initialWidth = fontProp.rGetTextWidthMM(value); - string remainingString = value.Trim(); - while (initialWidth > width) - { - string[] tokens = remainingString.Split(' '); - string splitValue = tokens.First(); - - foreach (string token in tokens.Skip(1)) - { - if (fontProp.rGetTextWidthMM(splitValue + token) < width) - splitValue = splitValue + ' ' + token; - else - break; - } - - remainingString = remainingString.Replace(splitValue, ""); - tlm.Add(index, new RepString(fontProp, splitValue)); - tlm.NewLine(index); - initialWidth = fontProp.rGetTextWidthMM(remainingString); - } - tlm.Add(index, new RepString(fontProp, remainingString)); - } - - - // Creates a page and sets width and height to standard 8.5x11 inches. - private Page CreatePage() - { - Page page = new Page(this); - page.rWidthMM = PageWidthMillimeters; - page.rHeightMM = PageHeightMillimeters; - return page; - } - - // Inserts the page footer onto the given page, which includes the time of report generation as well as the page number. - private double InsertFooter(Page page) - { - FontProp font = new FontProp(FontDefinition, 0.0D); - font.rSizePoint = 12.0D; - page.AddMM(PageWidthMillimeters - PageMarginMillimeters - font.rGetTextWidthMM(page.iPageNo.ToString()), PageHeightMillimeters - 15, new RepString(font, page.iPageNo.ToString())); - return font.rSizeMM; - } - - // Inserts the page footer onto the given page, which includes the time of report generation as well as the page number. - private double InsertHeader() - { - string ReportIdentifier = "Individual Breaker Report - " + StartTime.ToString("MM/dd/yyyy") + " - " + EndTime.ToString("MM/dd/yyyy") + ""; - - FontProp font = new FontProp(FontDefinition, 0.0D); - font.rSizePoint = 12.0D; - - FontProp meterNameFont = new FontProp(FontDefinition, 0.0D); - meterNameFont.rSizePoint = 12.0D; - - int height = 6; - - double reportIdentifierHorizontalPosition = PageWidthMillimeters / 2 - font.rGetTextWidthMM(ReportIdentifier) / 2; - - page_Cur.AddMM(reportIdentifierHorizontalPosition, height, new RepString(font, ReportIdentifier)); - page_Cur.AddMM(0, 10, new RepRectMM(new BrushProp(this, Color.Black), PageWidthMillimeters, 0.1D)); - - return 20; - } - - - // Inserts the given text as a section header (16-pt, bold). - private double InsertItalicText(double verticalMillimeters, string text) - { - FontProp font = new FontProp(FontDefinition, 0.0D); - font.rSizePoint = 14.0D; - font.bBold = false; - font.bItalic = true; - page_Cur.AddMM(PageMarginMillimeters, verticalMillimeters, new RepString(font, text)); - return font.rSizeMM + 5; - } - - private double NextTablePage( double verticalMillimeters, TlmBase.NewContainerEventArgs ea) - { - if (verticalMillimeters > (PageHeightMillimeters * 0.75)) - { - CreatePage(); - verticalMillimeters = InsertHeader(); - } - - ea.container.rHeightMM = PageHeightMillimeters - verticalMillimeters - PageMarginMillimeters; - page_Cur.AddMM(PageMarginMillimeters, verticalMillimeters, ea.container); - - return verticalMillimeters; - } - - private Chart GenerateTimingLineChart() - { - if (TimingPoints.Count == 0) - { - return null; - } - - Chart chart; - double timingMax = Math.Ceiling(TimingPoints.Select(x => (x.Value == null ? 0 : (double)x.Value)).Max()); - double bandwidthMax = Math.Ceiling((SpeedBandwidth == null ? 0 : (double)SpeedBandwidth)); - - ChartArea area; - ChartSeries series; - area = new ChartArea(); - - // style x axis - area.AxisX.MajorGrid.Enabled = false; - area.AxisX.IsLabelAutoFit = false; - //area.AxisX.LabelAutoFitMinFontSize = 20; - //area.AxisX.LabelStyle.Format = "MM/dd/yyyy"; - area.AxisX.LabelStyle.Font = new Font(FontFamily.GenericSansSerif, 15); - area.AxisX.IntervalAutoMode = IntervalAutoMode.VariableCount; - area.AxisX.Maximum = TimingPoints.Count + 1; - area.AxisX.Minimum = 0; - //area.AxisX.Interval = 1; - //area.AxisX.MajorTickMark.Interval = 1; - area.AxisX.TextOrientation = TextOrientation.Rotated90; - area.AxisX.LabelStyle.Angle = -45; - //area.AxisX.LabelStyle.IsEndLabelVisible = true; - //area.AxisX.LabelStyle.IsStaggered = true; - - // style y axis - //area.AxisY.MajorGrid.LineColor = Color.LightGray; - area.AxisY.Maximum = (bandwidthMax > timingMax ? bandwidthMax : timingMax); - area.AxisY.Minimum = 0; - area.AxisY.Interval = 1; - area.AxisY.LabelAutoFitMinFontSize = 15; - area.AxisY.LabelStyle.Format = "0"; - - - //create chart - chart = new Chart(); - chart.Width = 1200; - chart.Height = 500; - chart.Name = "Waveform Timing"; - - Legend legend = new Legend("Legend"); - legend.Font = new Font(FontFamily.GenericSansSerif, 15); - chart.Legends.Add(legend); - chart.ChartAreas.Add(area); - - - series = new ChartSeries("Waveform Timing"); - series.ChartType = SeriesChartType.Line; - series.BorderWidth = 2; - series.Color = Color.Black; - series.IsValueShownAsLabel = true; - - int index = 1; - foreach (var point in TimingPoints) - { - area.AxisX.CustomLabels.Add(index, index +.9, point.Time.ToString("MM/dd/yyyy")); - - if (point.Value == null) { - //series.Points.AddY(index++); - } - else { - DataPoint dataPoint = new DataPoint(index++, (double)point.Value); - - series.Points.Add(dataPoint); - } - } - - - chart.Series.Add(series); - - if(SpeedBandwidth != null) - chart.Series.Add(MakeLimitSeries((double)SpeedBandwidth, Color.Red, "12% Bandwidth", 0, TimingPoints.Count+1)); - if (Speed != null) - chart.Series.Add(MakeLimitSeries((double)Speed, Color.Orange, "MFR Speed", 0, TimingPoints.Count+1)); - - return chart; - - } - - private Chart GenerateMaxCurrentLineChart() - { - var nonNullPoints = MaxCurrentPoints.Where(x => x.Value != null); - - if (!nonNullPoints.Any()) - { - return null; - } - - Chart chart; - double max = Math.Ceiling(nonNullPoints.Select(x => (x.Value == null ? 0 : (double)x.Value)).Max()); - double icrMax = Math.Ceiling((InterruptCurrentRating == null ? 0 : (double)InterruptCurrentRating)); - - - ChartArea area; - ChartSeries series; - area = new ChartArea(); - // style x axis - area.AxisX.MajorGrid.Enabled = false; - area.AxisX.IsLabelAutoFit = false; - //area.AxisX.LabelAutoFitMinFontSize = 20; - //area.AxisX.LabelStyle.Format = "MM/dd/yyyy"; - area.AxisX.LabelStyle.Font = new Font(FontFamily.GenericSansSerif, 15); - area.AxisX.IntervalAutoMode = IntervalAutoMode.VariableCount; - area.AxisX.Maximum = nonNullPoints.Count() + 1; - area.AxisX.Minimum = 0; - area.AxisX.Interval = 1; - area.AxisX.MajorTickMark.Interval = 1; - area.AxisX.TextOrientation = TextOrientation.Rotated90; - area.AxisX.LabelStyle.Angle = -45; - area.AxisX.LabelStyle.IsEndLabelVisible = true; - - // style y axis - //area.AxisY.MajorGrid.LineColor = Color.LightGray; - area.AxisY.Maximum = (icrMax > max ? icrMax : max); - area.AxisY.LabelAutoFitMinFontSize = 15; - area.AxisY.LabelStyle.Format = "0"; - - //create chart - chart = new Chart(); - chart.Width = 1200; - chart.Height = 500; - chart.Name = "Max Current"; - - Legend legend = new Legend("Legend"); - legend.Font = new Font(FontFamily.GenericSansSerif, 15); - chart.Legends.Add(legend); - chart.ChartAreas.Add(area); - - - series = new ChartSeries("Max Current"); - series.ChartType = SeriesChartType.Line; - series.BorderWidth = 2; - series.Color = Color.Black; - series.IsValueShownAsLabel = true; - - int index = 1; - foreach (var point in nonNullPoints) - { - area.AxisX.CustomLabels.Add(index, index + .9, point.Time.ToString("MM/dd/yyyy")); - - DataPoint dataPoint = new DataPoint(index++, (double)point.Value); - - series.Points.Add(dataPoint); - } - - chart.Series.Add(series); - - if(InterruptCurrentRating != null) - chart.Series.Add(MakeLimitSeries((double)InterruptCurrentRating, Color.Red, "ICR", 0, nonNullPoints.Count() + 1)); - - return chart; - - } - - - private ChartSeries MakeLimitSeries(double value, Color color, string name, int start, int end) - { - ChartSeries cs = new ChartSeries(name); - cs.ChartType = SeriesChartType.FastLine; - cs.BorderWidth = 1; - - cs.Color = color; - cs.Points.AddXY(start, value); - cs.Points.AddXY(end, value); - return cs; - } - - private Stream ChartToImage(Chart chart) - { - MemoryStream stream = new MemoryStream(); - chart.SaveImage(stream, ChartImageFormat.Jpeg); - stream.Position = 0; - return stream; - } - - #endregion - - #region [ Static ] - - private static readonly ILog Log = LogManager.GetLogger(typeof(PQReport)); - - #endregion - } -} diff --git a/Libraries/openXDA.Reports/PQReport.cs b/Libraries/openXDA.Reports/PQReport.cs deleted file mode 100644 index d7f202e7..00000000 --- a/Libraries/openXDA.Reports/PQReport.cs +++ /dev/null @@ -1,2034 +0,0 @@ -//****************************************************************************************************** -// PQReport.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: -// ---------------------------------------------------------------------------------------------------- -// 07/12/2018 - Billy Ernest -// Generated original version of source code. -// -//****************************************************************************************************** - -using Gemstone.Data; -using Gemstone.Data.Model; -using log4net; -using openHistorian.XDALink; -using openXDA.Model; -using Root.Reports; -using System; -using System.Collections.Generic; -using System.Data; -using System.Drawing; -using System.IO; -using System.Linq; -using System.Windows.Forms.DataVisualization.Charting; -using ChartSeries = System.Windows.Forms.DataVisualization.Charting.Series; - -namespace openXDA.Reports -{ - public class PQReport : Root.Reports.Report - { - #region [ Members ] - - // Constants - private const double PageMarginMillimeters = 25.4D; // 1-inch margin - private const double PageWidthMillimeters = 8.5D * 25.4D; // 8.5 inch width - private const double PageHeightMillimeters = 11.0D * 25.4D; // 11 inch height - - private const double FooterHeightMillimeters = (10.0D / 72.0D) * 25.4D; - private const double SpacingMillimeters = 6.0D; - - private const int NumBuckets = 45; - - private const string TitleText = "Monthly PQ Compliance Report"; - - private class PointGroup - { - public string Name { get; set; } - public List Data { get; set; } - public Color Color { get; set; } - } - - private class SummaryResults - { - public SummaryResults() - { - Frequency = new FrequencyData(); - VoltageLN = new VoltageData(); - VoltageLL = new VoltageData(); - Flicker = new FlickerData(); - Imbalance = new ImbalanceData(); - THD = new THDData(); - Harmonics = new HarmonicsData(); - Sags = new SagsData(); - Swells = new SwellsData(); - Interruptions = new InterruptionsData(); - Faults = new FaultsData(); - } - - public FrequencyData Frequency { get; set; } - public VoltageData VoltageLN { get; set; } - public VoltageData VoltageLL { get; set; } - public FlickerData Flicker { get; set; } - public ImbalanceData Imbalance { get; set; } - public THDData THD { get; set; } - public HarmonicsData Harmonics { get; set; } - public SagsData Sags { get; set; } - public SwellsData Swells { get; set; } - public InterruptionsData Interruptions { get; set; } - public FaultsData Faults { get; set; } - - public class FrequencyData - { - public double Min { get; set; } - public double Avg { get; set; } - public double Max { get; set; } - public string Compliance { get; set; } - public double Nominal { get; set; } - } - public class VoltageData - { - public double Min { get; set; } - public double Avg { get; set; } - public double Max { get; set; } - public string Compliance { get; set; } - public double Nominal { get; set; } - } - public class FlickerData - { - public double Max { get; set; } - public string Compliance { get; set; } - } - public class ImbalanceData - { - public double Avg { get; set; } - public string Compliance { get; set; } - } - public class THDData - { - public double Min { get; set; } - public double Avg { get; set; } - public double Max { get; set; } - public string Compliance { get; set; } - } - - public class HarmonicsData - { - public string Compliance { get; set; } - } - - public class SagsData - { - public int Count { get; set; } - } - - public class SwellsData - { - public int Count { get; set; } - } - - public class InterruptionsData - { - public int Count { get; set; } - } - - public class FaultsData - { - public int Count { get; set; } - } - } - - private class Curves - { - public static List> IticUpperCurve { get; set; } - public static List> IticLowerCurve { get; set; } - - static Curves() - { - IticUpperCurve = new List>() - { - Tuple.Create(0.0001666667D, 5.0D), - Tuple.Create(0.001D, 2.0D), - Tuple.Create(0.003D, 1.4D), - Tuple.Create(0.003D, 1.2D), - Tuple.Create(0.5D, 1.2D), - Tuple.Create(0.5D, 1.1D), - Tuple.Create(10.0D, 1.1D) - }; - IticLowerCurve = new List>() - { - Tuple.Create(0.02D, 0.0D), - Tuple.Create(0.02D, 0.7D), - Tuple.Create(0.5D, 0.7D), - Tuple.Create(0.5D, 0.8D), - Tuple.Create(10.0D, 0.8D), - Tuple.Create(10.0D, 0.9D) - }; - } - } - - #endregion - - #region [ Properties ] - - public PQReportsSettings ReportsSettings { get; set; } - public Meter Meter { get; set; } - public DateTime FirstOfMonth { get; set; } - public DateTime EndOfMonth { get; set; } - public string Result { get; set; } - public FontDef FontDefinition { get; set; } - private SummaryResults Summary { get; set; } - private AdoDataConnection Connection { get; set; } - #endregion - - #region [ Constructors ] - - public PQReport(PQReportsSettings reportsSettings, Meter meter, DateTime firstOfMonth, DateTime endOfMonth, AdoDataConnection connection) - { - ReportsSettings = reportsSettings; - Meter = meter; - FirstOfMonth = firstOfMonth; - EndOfMonth = endOfMonth; - FontDefinition = new FontDef(this, "Helvetica"); - Summary = new SummaryResults(); - Connection = connection; - } - - #endregion - - #region [ Methods ] - - public byte[] createPDF() - { - try - { - GenerateReport(); - - using (MemoryStream stream = new MemoryStream()) - { - this.formatter.Create(this, stream); - return stream.ToArray(); - } - } - catch (Exception ex) { - Log.Error(ex.ToString(), ex); - return null; - } - } - - private void GenerateReport() - { - DateTime now = DateTime.Now; - - // Build Report - Page coverPage = CreatePage(); - Page pageTwo = CreatePage(); - CreateFrequencyPage(); - CreateVoltageLNPage(); - CreateVoltageLLPage(); - CreateFlickerPage(); - CreateImbalancePage(); - CreateTHDPage(); - //CreateHarmonicsPage(); - CreateMagDurPage(); - double verticalMillimeters = CreateInteruptionsPage(); - verticalMillimeters = CreateSagsPage(verticalMillimeters); - verticalMillimeters = CreateSwellsPage(verticalMillimeters); - verticalMillimeters = CreateFaultsPage(verticalMillimeters); - CreateSummaryPage(pageTwo); - - Result = ( - Summary.Frequency.Compliance == "Pass" && - Summary.VoltageLN.Compliance == "Pass" && - Summary.VoltageLL.Compliance == "Pass" && - Summary.Flicker.Compliance == "Pass" && - Summary.Imbalance.Compliance == "Pass" && - Summary.THD.Compliance == "Pass" && - Summary.Harmonics.Compliance == "Pass" - ? "Pass" : "Fail"); - - CreateCoverPage(coverPage); - - foreach (Page page in enum_Page) - { - if (page.iPageNo != 1) - InsertFooter(page); - } - } - - private void CreateCoverPage(Page page) - { - double verticalMillimeters = PageMarginMillimeters; - verticalMillimeters += InsertTitle(page, verticalMillimeters) + SpacingMillimeters; - - } - - private void CreateFrequencyPage() - { - Page page = CreatePage(); - double verticalMillimeters = InsertHeader(page); - verticalMillimeters += InsertSectionHeader(page, verticalMillimeters, "Section 1: Power Frequency"); - verticalMillimeters += InsertNominal(page, verticalMillimeters, "Frequency", "60.00", "Hz"); - verticalMillimeters += InsertFrequencyPage(page, verticalMillimeters); - } - - private void CreateVoltageLNPage() - { - Page page = CreatePage(); - double verticalMillimeters = InsertHeader(page); - verticalMillimeters += InsertSectionHeader(page, verticalMillimeters, "Section 2a: Supply Voltage (L-N)"); - verticalMillimeters += InsertVoltageLNPage(page, verticalMillimeters); - } - - private void CreateVoltageLLPage() - { - Page page = CreatePage(); - double verticalMillimeters = InsertHeader(page); - verticalMillimeters += InsertSectionHeader(page, verticalMillimeters, "Section 2b: Supply Voltage (L-L)"); - verticalMillimeters += InsertVoltageLLPage(page, verticalMillimeters); - - } - - private void CreateFlickerPage() - { - Page page = CreatePage(); - double verticalMillimeters = InsertHeader(page); - verticalMillimeters += InsertSectionHeader(page, verticalMillimeters, "Section 3: Flicker Severity"); - verticalMillimeters += InsertFlickerPage(page, verticalMillimeters); - } - - private void CreateImbalancePage() - { - Page page = CreatePage(); - double verticalMillimeters = InsertHeader(page); - verticalMillimeters += InsertSectionHeader(page, verticalMillimeters, "Section 4: Voltage Unbalance"); - verticalMillimeters += InsertImbalancePage(page, verticalMillimeters); - } - - private void CreateTHDPage() - { - Page page = CreatePage(); - double verticalMillimeters = InsertHeader(page); - verticalMillimeters += InsertSectionHeader(page, verticalMillimeters, "Section 5: Voltage THD"); - verticalMillimeters += InsertTHDPage(page, verticalMillimeters); - } - - private void CreateHarmonicsPage() - { - Page page = CreatePage(); - double verticalMillimeters = InsertHeader(page); - verticalMillimeters += InsertSectionHeader(page, verticalMillimeters, "Section 6: Harmonics"); - } - - private double CreateInteruptionsPage() - { - Page page = CreatePage(); - double verticalMillimeters = InsertHeader(page); - verticalMillimeters += InsertSectionHeader(page, verticalMillimeters, "Section 8: Interruptions"); - - DataTable dataTable = Connection.RetrieveData(@" - Select - cast(Disturbance.StartTime as Date) as Date, - cast(Disturbance.StartTime as Time) as Time, - Disturbance.PerUnitMagnitude as Depth, - Disturbance.DurationSeconds as Duration - from - Event Join - Disturbance ON Event.ID = Disturbance.EventID - WHERE - Disturbance.PhaseID = (SELECT ID FROM Phase WHERE Name = 'Worst') AND - Event.EventTypeID = (SELECT ID FROM EventType WHERE Name ='Interruption') AND - Disturbance.StartTime BETWEEN {0} AND {1} AND - Event.MeterID = {2} - ", FirstOfMonth, EndOfMonth, Meter.ID); - - Summary.Interruptions.Count = dataTable.Rows.Count; - - if (dataTable.Rows.Count == 0) - { - verticalMillimeters += InsertItalicText( page, verticalMillimeters, $" No Interruptions during {FirstOfMonth.ToString("MM/dd/yyyy")} - {EndOfMonth.ToString("MM/dd/yyyy")}"); - return verticalMillimeters; - } - - FontProp headerProp = new FontProp(FontDefinition, 0); - headerProp.rSizePoint = 10.0D; - using (TableLayoutManager tlm = new TableLayoutManager(headerProp)) - { - FontProp textProp = new FontProp(FontDefinition, 0); - textProp.rSizePoint = 8.0D; - tlm.tlmCellDef_Header.rAlignV = RepObj.rAlignCenter; // set vertical alignment of all header cells - tlm.tlmCellDef_Default.penProp_LineBottom = new PenProp(this, 0.05, Color.LightGray); // set bottom line for all cells - tlm.tlmHeightMode = TlmHeightMode.AdjustLast; - tlm.eNewContainer += (oSender, ea) => verticalMillimeters = NextTablePage(page, verticalMillimeters, ea); - - // define columns - TlmColumn col; - col = new TlmColumnMM(tlm, "Date", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.25); - col = new TlmColumnMM(tlm, "Time", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.25); - col = new TlmColumnMM(tlm, "Depth", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.25); - col = new TlmColumnMM(tlm, "Duration", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.25); - - foreach (DataRow row in dataTable.Rows) - { - tlm.NewRow(); - tlm.Add(0, new RepString(textProp, row["Date"].ToString())); - tlm.Add(1, new RepString(textProp, row["Time"].ToString())); - tlm.Add(2, new RepString(textProp, row["Depth"].ToString())); - tlm.Add(3, new RepString(textProp, row["Duration"].ToString())); - } - - tlm.Commit(); - - verticalMillimeters += tlm.rCurY_MM + 10; - } - - return verticalMillimeters; - } - - private double CreateSagsPage(double verticalMillimeters) - { - Page page = page_Cur; - if (verticalMillimeters > (PageHeightMillimeters * 0.75)) - { - page = CreatePage(); - verticalMillimeters = InsertHeader(page); - } - - verticalMillimeters += InsertSectionHeader(page, verticalMillimeters, "Section 9: Sags"); - - DataTable dataTable = Connection.RetrieveData(@" - Select - cast(Disturbance.StartTime as Date) as Date, - cast(Disturbance.StartTime as Time) as Time, - Disturbance.PerUnitMagnitude as Depth, - Disturbance.DurationSeconds as Duration - from - Event Join - Disturbance ON Event.ID = Disturbance.EventID - WHERE - Disturbance.PhaseID = (SELECT ID FROM Phase WHERE Name = 'Worst') AND - Event.EventTypeID = (SELECT ID FROM EventType WHERE Name ='Sag') AND - Disturbance.StartTime BETWEEN {0} AND {1} AND - Event.MeterID = {2} - ", FirstOfMonth, EndOfMonth, Meter.ID); - - Summary.Sags.Count = dataTable.Rows.Count; - - if (dataTable.Rows.Count == 0) - { - verticalMillimeters += InsertItalicText(page, verticalMillimeters, $" No Faults during {FirstOfMonth.ToString("MM/dd/yyyy")} - {EndOfMonth.ToString("MM/dd/yyyy")}"); - return verticalMillimeters; - } - - FontProp headerProp = new FontProp(FontDefinition, 0); - headerProp.rSizePoint = 10.0D; - using (TableLayoutManager tlm = new TableLayoutManager(headerProp)) - { - FontProp textProp = new FontProp(FontDefinition, 0); - textProp.rSizePoint = 8.0D; - tlm.tlmCellDef_Header.rAlignV = RepObj.rAlignCenter; // set vertical alignment of all header cells - tlm.tlmCellDef_Default.penProp_LineBottom = new PenProp(this, 0.05, Color.LightGray); // set bottom line for all cells - tlm.tlmHeightMode = TlmHeightMode.AdjustLast; - tlm.eNewContainer += (oSender, ea) => verticalMillimeters = NextTablePage(page, verticalMillimeters, ea); - // define columns - TlmColumn col; - col = new TlmColumnMM(tlm, "Date", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.25); - col = new TlmColumnMM(tlm, "Time", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.25); - col = new TlmColumnMM(tlm, "Depth", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.25); - col = new TlmColumnMM(tlm, "Duration (seconds)", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.25); - - foreach (DataRow row in dataTable.Rows) - { - tlm.NewRow(); - tlm.Add(0, new RepString(textProp, ((DateTime)row["Date"]).ToString("MM/dd/yyyy"))); - tlm.Add(1, new RepString(textProp, row["Time"].ToString())); - tlm.Add(2, new RepString(textProp, string.Format("{0:N2}", double.Parse(row["Depth"].ToString()) * 100))); - tlm.Add(3, new RepString(textProp, string.Format("{0:N3}", double.Parse(row["Duration"].ToString())))); - } - - tlm.Commit(); - - verticalMillimeters += tlm.rCurY_MM + 10; - } - - return verticalMillimeters; - - } - - private double CreateSwellsPage(double verticalMillimeters) - { - Page page = page_Cur; - if (verticalMillimeters > (PageHeightMillimeters * 0.75)) - { - page = CreatePage(); - verticalMillimeters = InsertHeader(page); - } - - verticalMillimeters += InsertSectionHeader(page, verticalMillimeters, "Section 10: Swells"); - DataTable dataTable = Connection.RetrieveData(@" - Select - cast(Disturbance.StartTime as Date) as Date, - cast(Disturbance.StartTime as Time) as Time, - Disturbance.PerUnitMagnitude as Depth, - Disturbance.DurationSeconds as Duration - from - Event Join - Disturbance ON Event.ID = Disturbance.EventID - WHERE - Disturbance.PhaseID = (SELECT ID FROM Phase WHERE Name = 'Worst') AND - Event.EventTypeID = (SELECT ID FROM EventType WHERE Name ='Swell') AND - Disturbance.StartTime BETWEEN {0} AND {1} AND - Event.MeterID = {2} - ", FirstOfMonth, EndOfMonth, Meter.ID); - - Summary.Swells.Count = dataTable.Rows.Count; - - - if (dataTable.Rows.Count == 0) - { - verticalMillimeters += InsertItalicText(page, verticalMillimeters, $" No Swells during {FirstOfMonth.ToString("MM/dd/yyyy")} - {EndOfMonth.ToString("MM/dd/yyyy")}"); - return verticalMillimeters; - } - - FontProp headerProp = new FontProp(FontDefinition, 0); - headerProp.rSizePoint = 10.0D; - using (TableLayoutManager tlm = new TableLayoutManager(headerProp)) - { - FontProp textProp = new FontProp(FontDefinition, 0); - textProp.rSizePoint = 8.0D; - tlm.tlmCellDef_Header.rAlignV = RepObj.rAlignCenter; // set vertical alignment of all header cells - tlm.tlmCellDef_Default.penProp_LineBottom = new PenProp(this, 0.05, Color.LightGray); // set bottom line for all cells - tlm.tlmHeightMode = TlmHeightMode.AdjustLast; - tlm.eNewContainer += (oSender, ea) => verticalMillimeters = NextTablePage(page, verticalMillimeters, ea); - // define columns - TlmColumn col; - col = new TlmColumnMM(tlm, "Date", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.25); - col = new TlmColumnMM(tlm, "Time", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.25); - col = new TlmColumnMM(tlm, "Depth", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.25); - col = new TlmColumnMM(tlm, "Duration", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.25); - - foreach (DataRow row in dataTable.Rows) - { - tlm.NewRow(); - tlm.Add(0, new RepString(textProp, row["Date"].ToString())); - tlm.Add(1, new RepString(textProp, row["Time"].ToString())); - tlm.Add(2, new RepString(textProp, string.Format("{0:N2}", double.Parse(row["Depth"].ToString()) * 100))); - tlm.Add(3, new RepString(textProp, string.Format("{0:N3}", double.Parse(row["Duration"].ToString())))); - } - - tlm.Commit(); - - verticalMillimeters += tlm.rCurY_MM + 10; - } - - return verticalMillimeters; - } - - private void CreateMagDurPage() - { - Page page = CreatePage(); - double verticalMillimeters = InsertHeader(page); - verticalMillimeters += InsertSectionHeader( page, verticalMillimeters, "Section 7: Mag-Dur Chart"); - - DataTable dataTable = Connection.RetrieveData(@" - Select - Disturbance.PerUnitMagnitude as Depth, - Disturbance.DurationSeconds as Duration - from - Event Join - Disturbance ON Event.ID = Disturbance.EventID - WHERE - Disturbance.PhaseID = (SELECT ID FROM Phase WHERE Name = 'Worst') AND - ( - Event.EventTypeID = (SELECT ID FROM EventType WHERE Name ='Sag') OR - Event.EventTypeID = (SELECT ID FROM EventType WHERE Name ='Swell') OR - Event.EventTypeID = (SELECT ID FROM EventType WHERE Name ='Interruption') - ) AND - Disturbance.StartTime BETWEEN {0} AND {1} AND - Event.MeterID = {2} - ", FirstOfMonth, EndOfMonth, Meter.ID); - - Chart chart = GenerateMagDurChart(dataTable.Select().Select(row => new Tuple(double.Parse(row["Duration"].ToString()), double.Parse(row["Depth"].ToString()))).ToList(), "0.0000 s", "0.0 %"); - verticalMillimeters += 150; - - page.AddMM(PageMarginMillimeters, verticalMillimeters, new RepImageMM(ChartToImage(chart), PageWidthMillimeters - 2 * PageMarginMillimeters, 150)); - - } - - private double CreateFaultsPage(double verticalMillimeters) - { - Page page = page_Cur; - if (verticalMillimeters > (PageHeightMillimeters * 0.75)) - { - page = CreatePage(); - verticalMillimeters = InsertHeader(page); - } - - verticalMillimeters += InsertSectionHeader(page, verticalMillimeters, "Section 11: Faults"); - DataTable dataTable = Connection.RetrieveData(@" - SELECT - Cast(FaultSummary.Inception as Date) as Date, - Cast(FaultSummary.Inception as Time) as Time, - FaultSummary.Distance, - FaultSummary.DurationSeconds as Duration - FROM - Event JOIN - FaultSummary ON Event.ID = FaultSummary.EventID - WHERE - FaultSummary.IsSelectedAlgorithm = 1 AND - FaultSummary.IsSuppressed = 0 AND - FaultSummary.IsValid = 1 AND - FaultSummary.Inception BETWEEN {0} AND {1} AND - Event.MeterID = {2} - ", FirstOfMonth, EndOfMonth, Meter.ID); - - Summary.Faults.Count = dataTable.Rows.Count; - - if (dataTable.Rows.Count == 0) - { - verticalMillimeters += InsertItalicText(page, verticalMillimeters, $" No Faults during {FirstOfMonth.ToString("MM/dd/yyyy")} - {EndOfMonth.ToString("MM/dd/yyyy")}"); - return verticalMillimeters; - } - - FontProp headerProp = new FontProp(FontDefinition, 0); - headerProp.rSizePoint = 10.0D; - using (TableLayoutManager tlm = new TableLayoutManager(headerProp)) - { - FontProp textProp = new FontProp(FontDefinition, 0); - textProp.rSizePoint = 8.0D; - tlm.tlmCellDef_Header.rAlignV = RepObj.rAlignCenter; // set vertical alignment of all header cells - tlm.tlmCellDef_Default.penProp_LineBottom = new PenProp(this, 0.05, Color.LightGray); // set bottom line for all cells - tlm.tlmHeightMode = TlmHeightMode.AdjustLast; - tlm.eNewContainer += (oSender, ea) => verticalMillimeters = NextTablePage(page, verticalMillimeters, ea); - // define columns - TlmColumn col; - col = new TlmColumnMM(tlm, "Date", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.25); - col = new TlmColumnMM(tlm, "Time", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.25); - col = new TlmColumnMM(tlm, "Distance", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.25); - col = new TlmColumnMM(tlm, "Duration", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.25); - - foreach (DataRow row in dataTable.Rows) - { - tlm.NewRow(); - tlm.Add(0, new RepString(textProp, row["Date"].ToString())); - tlm.Add(1, new RepString(textProp, row["Time"].ToString())); - tlm.Add(2, new RepString(textProp, string.Format("{0:N2}", double.Parse(row["Distance"].ToString()) * 100))); - tlm.Add(3, new RepString(textProp, string.Format("{0:N3}", double.Parse(row["Duration"].ToString())))); - } - - tlm.Commit(); - - verticalMillimeters += tlm.rCurY_MM + 10; - } - - return verticalMillimeters; - } - - private void CreateSummaryPage(Page page) - { - double verticalMillimeters = InsertHeader(page); - verticalMillimeters += InsertSectionHeader(page, verticalMillimeters, "Summary of Results"); - - FontProp headerProp = new FontProp(FontDefinition, 0); - headerProp.rSizePoint = 10.0D; - - using (TableLayoutManager tlm = new TableLayoutManager(headerProp)) - { - FontProp textProp = new FontProp(FontDefinition, 0); - textProp.rSizePoint = 8.0D; - tlm.tlmCellDef_Header.rAlignV = RepObj.rAlignCenter; // set vertical alignment of all header cells - tlm.tlmCellDef_Default.penProp_LineBottom = new PenProp(this, 0.05, Color.LightGray); // set bottom line for all cells - tlm.tlmHeightMode = TlmHeightMode.AdjustLast; - tlm.eNewContainer += (oSender, ea) => verticalMillimeters = NextTablePage(page, verticalMillimeters, ea); - // define columns - TlmColumn col; - col = new TlmColumnMM(tlm, "Measurement", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.35); - col = new TlmColumnMM(tlm, "Min", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.15); - col = new TlmColumnMM(tlm, "Avg", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.15); - col = new TlmColumnMM(tlm, "Max", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.15); - col = new TlmColumnMM(tlm, "Compliance", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.20); - - tlm.NewRow(); - tlm.Add(0, new RepString(textProp, $"1. Frequency ({Summary.Frequency.Nominal} Hz)")); - tlm.Add(1, new RepString(textProp, string.Format("{0:N2}", Summary.Frequency.Min))); - tlm.Add(2, new RepString(textProp, string.Format("{0:N2}", Summary.Frequency.Avg))); - tlm.Add(3, new RepString(textProp, string.Format("{0:N2}", Summary.Frequency.Max))); - tlm.Add(4, new RepString(textProp, Summary.Frequency.Compliance)); - - tlm.NewRow(); - tlm.Add(0, new RepString(textProp, $"2a. Voltage L-N ({string.Format("{0:N0}", Summary.VoltageLN.Nominal)} V)")); - tlm.Add(1, new RepString(textProp, string.Format("{0:N2}", Summary.VoltageLN.Min))); - tlm.Add(2, new RepString(textProp, string.Format("{0:N2}", Summary.VoltageLN.Avg))); - tlm.Add(3, new RepString(textProp, string.Format("{0:N2}", Summary.VoltageLN.Max))); - tlm.Add(4, new RepString(textProp, Summary.VoltageLN.Compliance)); - - tlm.NewRow(); - tlm.Add(0, new RepString(textProp, $"2b. Voltage L-L ({string.Format("{0:N0}", Summary.VoltageLL.Nominal)} V)")); - tlm.Add(1, new RepString(textProp, string.Format("{0:N2}", Summary.VoltageLL.Min))); - tlm.Add(2, new RepString(textProp, string.Format("{0:N2}", Summary.VoltageLL.Avg))); - tlm.Add(3, new RepString(textProp, string.Format("{0:N2}", Summary.VoltageLL.Max))); - tlm.Add(4, new RepString(textProp, Summary.VoltageLL.Compliance)); - - tlm.NewRow(); - tlm.Add(0, new RepString(textProp, $"3. Flicker")); - tlm.Add(1, new RepString(textProp, "")); - tlm.Add(2, new RepString(textProp, "")); - tlm.Add(3, new RepString(textProp, string.Format("{0:N2}", Summary.Flicker.Max))); - tlm.Add(4, new RepString(textProp, Summary.Flicker.Compliance)); - - tlm.NewRow(); - tlm.Add(0, new RepString(textProp, $"4. Imbalance")); - tlm.Add(1, new RepString(textProp, "")); - tlm.Add(2, new RepString(textProp, string.Format("{0:N2}", Summary.Imbalance.Avg))); - tlm.Add(3, new RepString(textProp, "")); - tlm.Add(4, new RepString(textProp, Summary.Imbalance.Compliance)); - - tlm.NewRow(); - tlm.Add(0, new RepString(textProp, $"5. THD")); - tlm.Add(1, new RepString(textProp, string.Format("{0:N2}", Summary.THD.Min))); - tlm.Add(2, new RepString(textProp, string.Format("{0:N2}", Summary.THD.Avg))); - tlm.Add(3, new RepString(textProp, string.Format("{0:N2}", Summary.THD.Max))); - tlm.Add(4, new RepString(textProp, Summary.THD.Compliance)); - - tlm.NewRow(); - tlm.Add(0, new RepString(textProp, $"6. Harmonics")); - tlm.Add(1, new RepString(textProp, "")); - tlm.Add(2, new RepString(textProp, "")); - tlm.Add(3, new RepString(textProp, "")); - tlm.Add(4, new RepString(textProp, Summary.Harmonics.Compliance)); - - tlm.Commit(); - - verticalMillimeters += tlm.rCurY_MM + 10; - } - - using (TableLayoutManager tlm = new TableLayoutManager(headerProp)) - { - FontProp textProp = new FontProp(FontDefinition, 0); - textProp.rSizePoint = 8.0D; - tlm.tlmCellDef_Header.rAlignV = RepObj.rAlignCenter; // set vertical alignment of all header cells - tlm.tlmCellDef_Default.penProp_LineBottom = new PenProp(this, 0.05, Color.LightGray); // set bottom line for all cells - tlm.tlmHeightMode = TlmHeightMode.AdjustLast; - tlm.eNewContainer += (oSender, ea) => verticalMillimeters = NextTablePage(page, verticalMillimeters, ea); - // define columns - TlmColumn col; - col = new TlmColumnMM(tlm, "Event Type", (PageWidthMillimeters / 2 - PageMarginMillimeters) * 0.65); - col = new TlmColumnMM(tlm, "Count", (PageWidthMillimeters / 2 - PageMarginMillimeters) * 0.35); - - tlm.NewRow(); - tlm.Add(0, new RepString(textProp, $"8. Interruptions")); - tlm.Add(1, new RepString(textProp, string.Format("{0}", Summary.Interruptions.Count))); - - tlm.NewRow(); - tlm.Add(0, new RepString(textProp, $"9. Sags")); - tlm.Add(1, new RepString(textProp, string.Format("{0}", Summary.Sags.Count))); - - tlm.NewRow(); - tlm.Add(0, new RepString(textProp, $"10. Swells")); - tlm.Add(1, new RepString(textProp, string.Format("{0}", Summary.Swells.Count))); - - tlm.NewRow(); - tlm.Add(0, new RepString(textProp, $"11. Faults")); - tlm.Add(1, new RepString(textProp, string.Format("{0}", Summary.Faults.Count))); - - tlm.Commit(); - - verticalMillimeters += tlm.rCurY_MM + 10; - } - } - - // Creates a page and sets width and height to standard 8.5x11 inches. - private Page CreatePage() - { - Page page = new Page(this); - page.rWidthMM = PageWidthMillimeters; - page.rHeightMM = PageHeightMillimeters; - return page; - } - - // Inserts the title and company text on the given page. - private double InsertTitle(Page page, double verticalMillimeters) - { - FontProp titleFont = new FontProp(FontDefinition, 0.0D); - FontProp companyFont = new FontProp(FontDefinition, 0.0D); - - titleFont.rSizePoint = 20.0D; - companyFont.rSizePoint = 14.0D; - titleFont.bBold = true; - companyFont.bBold = true; - - // Title - page.AddCB_MM(verticalMillimeters + titleFont.rSizeMM, new RepString(titleFont, TitleText)); - verticalMillimeters += 1.5D * titleFont.rSizeMM; - verticalMillimeters += 5; - - // Company - page.AddCB_MM(verticalMillimeters + companyFont.rSizeMM, new RepString(companyFont, $"{Connection.ExecuteScalar("SELECT Value FROM Setting WHERE Name = 'CompanyName'")}")); - - verticalMillimeters += 1.5D * titleFont.rSizeMM; - - // openXDA - page.AddCB_MM(verticalMillimeters + companyFont.rSizeMM, new RepString(companyFont, $"openXDA")); - - verticalMillimeters += 1.5D * titleFont.rSizeMM; - - // Date - page.AddCB_MM(verticalMillimeters + companyFont.rSizeMM, new RepString(companyFont, $"{FirstOfMonth.ToString("MMMM yyyy")}")); - - verticalMillimeters += 1.5D * titleFont.rSizeMM; - - // Meter Name - page.AddCB_MM(verticalMillimeters + companyFont.rSizeMM, new RepString(companyFont, $"{Meter.Name} - {Result}")); - - verticalMillimeters += 1.5D * companyFont.rSizeMM; - - - return verticalMillimeters; - } - - // Inserts the page footer onto the given page, which includes the time of report generation as well as the page number. - private double InsertFooter(Page page) - { - FontProp font = new FontProp(FontDefinition, 0.0D); - font.rSizePoint = 12.0D; - page.AddMM(PageWidthMillimeters - PageMarginMillimeters - font.rGetTextWidthMM(page.iPageNo.ToString()), PageHeightMillimeters - 15, new RepString(font, page.iPageNo.ToString())); - return font.rSizeMM; - } - - // Inserts the page footer onto the given page, which includes the time of report generation as well as the page number. - private double InsertHeader(Page page) - { - const string ReportIdentifier = "PQ Report - openXDA"; - - FontProp font = new FontProp(FontDefinition, 0.0D); - font.rSizePoint = 12.0D; - - FontProp meterNameFont = new FontProp(FontDefinition, 0.0D); - meterNameFont.rSizePoint = 12.0D; - - int height = 6; - - double reportIdentifierHorizontalPosition = PageWidthMillimeters / 2 - font.rGetTextWidthMM(ReportIdentifier) / 2; - double meterNameMaxWidth = reportIdentifierHorizontalPosition - PageMarginMillimeters - 4; - string meterName = Meter.Name; - - while (meterNameFont.rGetTextWidthMM(meterName) > meterNameMaxWidth) - meterNameFont.rSizePoint -= 1.0D; - - page.AddMM(PageMarginMillimeters, height, new RepString(meterNameFont, Meter.Name)); - page.AddMM(reportIdentifierHorizontalPosition, height, new RepString(font, ReportIdentifier)); - page.AddMM(PageWidthMillimeters - PageMarginMillimeters - font.rGetTextWidthMM(FirstOfMonth.ToString("MMMM yyyy")), height, new RepString(font, FirstOfMonth.ToString("MMMM yyyy"))); - page.AddMM(0, 10, new RepRectMM(new BrushProp(this, Color.Black), PageWidthMillimeters, 0.1D)); - - return 20; - } - - - // Inserts the given text as a section header (16-pt, bold). - private double InsertSectionHeader(Page page, double verticalMillimeters, string text) - { - FontProp font = new FontProp(FontDefinition, 0.0D); - font.rSizePoint = 14.0D; - font.bBold = true; - page.AddMM(PageMarginMillimeters, verticalMillimeters, new RepString(font, text)); - return font.rSizeMM + 5; - } - - // Inserts the given text as a section header (16-pt, bold). - private double InsertItalicText(Page page, double verticalMillimeters, string text) - { - FontProp font = new FontProp(FontDefinition, 0.0D); - font.rSizePoint = 14.0D; - font.bBold = false; - font.bItalic = true; - page.AddMM(PageMarginMillimeters, verticalMillimeters, new RepString(font, text)); - return font.rSizeMM + 5; - } - - // Inserts the given text as a section header (16-pt, bold). - private double InsertNominal(Page page, double verticalMillimeters, string type, string value, string units) - { - FontProp font = new FontProp(FontDefinition, 0.0D); - font.rSizePoint = 10.0D; - page.AddMM(PageMarginMillimeters, verticalMillimeters, new RepString(font, $"Nominal {type}: {value} {units}")); - return font.rSizeMM + 5; - } - - private double NextTablePage(Page page, double verticalMillimeters, TlmBase.NewContainerEventArgs ea) - { - if (verticalMillimeters > (PageHeightMillimeters * 0.75)) - { - page = CreatePage(); - verticalMillimeters = InsertHeader(page); - verticalMillimeters += InsertSectionHeader(page, verticalMillimeters, "Section 8: Sags (cont.)"); - } - - ea.container.rHeightMM = PageHeightMillimeters - verticalMillimeters - PageMarginMillimeters; - page.AddMM(PageMarginMillimeters, verticalMillimeters, ea.container); - - return verticalMillimeters; - } - - private double InsertFrequencyPage(Page page, double verticalMillimeters) - { - string historianServer = Connection.ExecuteScalar("SELECT Value FROM Setting WHERE Name = 'Historian.Server'") ?? "127.0.0.1"; - string historianInstance = Connection.ExecuteScalar("SELECT Value FROM Setting WHERE Name = 'Historian.Instance'") ?? "XDA"; - IEnumerable channels = new TableOperations(Connection).QueryRecordsWhere("MeterID = {0} AND MeasurementCharacteristicID = (SELECT ID FROM MeasurementCharacteristic WHERE Name = 'Frequency') AND MeasurementTypeID = (SELECT ID FROM MeasurementType WHERE Name = 'Voltage')", Meter.ID); - - if (!channels.Any()) - { - verticalMillimeters += InsertItalicText(page, verticalMillimeters, $" No Frequency channels setup on this meter."); - Summary.Frequency.Compliance = "Pass"; - return verticalMillimeters; - } - - double nominal = Summary.Frequency.Nominal = 60.0D; - - using (Historian historian = new Historian(historianServer, historianInstance)) - { - List points = historian.Read(channels.Select(x => x.ID), FirstOfMonth, EndOfMonth).ToList(); - - if (!points.Any()) - { - verticalMillimeters += InsertItalicText(page, verticalMillimeters, $" No Frequency data during {FirstOfMonth.ToString("MM/dd/yyyy")} - {EndOfMonth.ToString("MM/dd/yyyy")}"); - return verticalMillimeters; - } - - List avg = points - .Where(point => point.SeriesID == SeriesID.Average) - .Select(point => point.Value) - .OrderBy(value => value) - .ToList(); - - int firstPercentileSkipCount = (int)(avg.Count * (100.0D - ReportsSettings.FirstFrequencyPercentile) / 100.0D / 2.0D); - double firstPercentileMinLimit = nominal * (100.0D - ReportsSettings.FirstFrequencyDeviationLimit) / 100.0D; - double firstPercentileMaxLimit = nominal * (100.0D + ReportsSettings.FirstFrequencyDeviationLimit) / 100.0D; - double firstPercentileMin = avg.Skip(firstPercentileSkipCount).FirstOrDefault(); - double firstPercentileMax = avg.AsEnumerable().Reverse().Skip(firstPercentileSkipCount).FirstOrDefault(); - - bool firstTest = - firstPercentileMin >= firstPercentileMinLimit && - firstPercentileMax <= firstPercentileMaxLimit; - - int secondPercentileSkipCount = (int)(avg.Count * (100.0D - ReportsSettings.SecondFrequencyPercentile) / 100.0D / 2.0D); - double secondPercentileMinLimit = nominal * (100.0D - ReportsSettings.SecondFrequencyDeviationLimit) / 100.0D; - double secondPercentileMaxLimit = nominal * (100.0D + ReportsSettings.SecondFrequencyDeviationLimit) / 100.0D; - double secondPercentileMin = avg.Skip(secondPercentileSkipCount).FirstOrDefault(); - double secondPercentileMax = avg.AsEnumerable().Reverse().Skip(secondPercentileSkipCount).FirstOrDefault(); - - bool secondTest = - secondPercentileMin >= secondPercentileMinLimit && - secondPercentileMax <= secondPercentileMaxLimit; - - Summary.Frequency.Compliance = (firstTest && secondTest ? "Pass" : "Fail"); - - FontProp headerProp = new FontProp(FontDefinition, 0); - headerProp.rSizePoint = 10.0D; - - using (TableLayoutManager tlm = new TableLayoutManager(headerProp)) - { - FontProp textProp = new FontProp(FontDefinition, 0); - textProp.rSizePoint = 8.0D; - - tlm.tlmCellDef_Header.rAlignV = RepObj.rAlignCenter; // set vertical alignment of all header cells - tlm.tlmCellDef_Default.penProp_LineBottom = new PenProp(this, 0.05, Color.LightGray); // set bottom line for all cells - tlm.tlmHeightMode = TlmHeightMode.AdjustLast; - tlm.eNewContainer += (oSender, ea) => page.AddMM(PageMarginMillimeters, verticalMillimeters, ea.container); - - // define columns - TlmColumn col; - col = new TlmColumnMM(tlm, "Monthly PQ Report Requirement", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.40); - col = new TlmColumnMM(tlm, "Measured Frequency", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.40); - col = new TlmColumnMM(tlm, "Result", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.20); - - tlm.NewRow(); - tlm.Add(0, new RepString(textProp, $"{ReportsSettings.FirstFrequencyPercentile:0.##}% of the time: {firstPercentileMinLimit:N2} Hz - {firstPercentileMaxLimit:N2} Hz")); - tlm.Add(1, new RepString(textProp, (avg.Any() ? $"{firstPercentileMin:N2} - {firstPercentileMax:N2}" : "No Data Provided"))); - tlm.Add(2, new RepString(textProp, (firstTest ? "Pass" : "Fail"))); - - tlm.NewRow(); - tlm.Add(0, new RepString(textProp, $"{ReportsSettings.SecondFrequencyPercentile:0.##}% of the time: {secondPercentileMinLimit:N2} Hz - {secondPercentileMaxLimit:N2} Hz")); - tlm.Add(1, new RepString(textProp, (avg.Any() ? $"{secondPercentileMin:N2} - {secondPercentileMax:N2}" : "No Data Provided"))); - tlm.Add(2, new RepString(textProp, (secondTest ? "Pass" : "Fail"))); - - tlm.Commit(); - - verticalMillimeters += tlm.rCurY_MM + 10; - } - - List pointGroups = new List(); - - pointGroups.Add(new PointGroup() - { - Name = "Frequency", - Color = Color.DarkBlue, - Data = points.Where(point => point.SeriesID == SeriesID.Average).ToList() - }); - - pointGroups.Add(new PointGroup() - { - Name = "Frequency Max", - Color = Color.DarkGreen, - Data = points.Where(point => point.SeriesID == SeriesID.Maximum).ToList() - }); - - pointGroups.Add(new PointGroup() - { - Name = "Frequency Min", - Color = Color.Purple, - Data = points.Where(point => point.SeriesID == SeriesID.Minimum).ToList() - }); - - Chart chart = GenerateLineChart("Hz", pointGroups, secondPercentileMaxLimit + (secondPercentileMaxLimit - secondPercentileMinLimit) * .10, secondPercentileMinLimit - (secondPercentileMaxLimit - secondPercentileMinLimit) * .10, "MM/dd", "0.00", "Frequency", secondPercentileMaxLimit, firstPercentileMaxLimit, secondPercentileMinLimit, firstPercentileMinLimit, FirstOfMonth, EndOfMonth); - verticalMillimeters += 75; - - page.AddMM(PageMarginMillimeters, verticalMillimeters, new RepImageMM(ChartToImage(chart), PageWidthMillimeters - 2 * PageMarginMillimeters, 75)); - verticalMillimeters += 75; - - double maxValue = Summary.Frequency.Max = avg.Max(); - double minValue = Summary.Frequency.Min = avg.Min(); - chart = GenerateBarChart("0.00 Hz", avg, maxValue, minValue); - - page.AddMM(PageMarginMillimeters, verticalMillimeters, new RepImageMM(ChartToImage(chart), PageWidthMillimeters - 2 * PageMarginMillimeters, 75)); - - return verticalMillimeters; - } - } - - private double InsertVoltageLNPage( Page page, double verticalMillimeters) - { - string historianServer = Connection.ExecuteScalar("SELECT Value FROM Setting WHERE Name = 'Historian.Server'") ?? "127.0.0.1"; - string historianInstance = Connection.ExecuteScalar("SELECT Value FROM Setting WHERE Name = 'Historian.Instance'") ?? "XDA"; - - Channel line1 = new TableOperations(Connection).QueryRecordWhere("MeterID = {0} AND MeasurementCharacteristicID = (SELECT ID FROM MeasurementCharacteristic WHERE Name = 'RMS') AND MeasurementTypeID = (SELECT ID FROM MeasurementType WHERE Name = 'Voltage') AND PhaseID = (SELECT ID FROM Phase WHERE Name = 'AN')", Meter.ID); - Channel line2 = new TableOperations(Connection).QueryRecordWhere("MeterID = {0} AND MeasurementCharacteristicID = (SELECT ID FROM MeasurementCharacteristic WHERE Name = 'RMS') AND MeasurementTypeID = (SELECT ID FROM MeasurementType WHERE Name = 'Voltage') AND PhaseID = (SELECT ID FROM Phase WHERE Name = 'BN')", Meter.ID); - Channel line3 = new TableOperations(Connection).QueryRecordWhere("MeterID = {0} AND MeasurementCharacteristicID = (SELECT ID FROM MeasurementCharacteristic WHERE Name = 'RMS') AND MeasurementTypeID = (SELECT ID FROM MeasurementType WHERE Name = 'Voltage') AND PhaseID = (SELECT ID FROM Phase WHERE Name = 'CN')", Meter.ID); - - int lineId; - - if (line1 == null && line2 == null && line3 == null) - { - verticalMillimeters += InsertItalicText(page, verticalMillimeters, $" No L-N Voltage channels setup on this meter."); - Summary.VoltageLN.Compliance = "Pass"; - return verticalMillimeters; - } - - if (line1 != null) - lineId = line1.AssetID; - else if (line2 != null) - lineId = line2.AssetID; - else - lineId = line3.AssetID; - - double nominal = Summary.VoltageLN.Nominal = Connection.ExecuteScalar("SELECT VoltageKV FROM Asset WHERE ID = {0}", lineId) * 1000 / Math.Sqrt(3); - verticalMillimeters += InsertNominal(page, verticalMillimeters, "Voltage", string.Format("{0:N0}", nominal), "V L-N"); - - using (Historian historian = new Historian(historianServer, historianInstance)) - { - List points = historian.Read(new List() { line1.ID, line2.ID, line3.ID }, FirstOfMonth, EndOfMonth).ToList(); - - List avgLine1 = points - .Where(point => point.ChannelID == line1.ID) - .Where(point => point.SeriesID == SeriesID.Average) - .Select(point => point.Value) - .OrderBy(value => value) - .ToList(); - - List avgLine2 = points - .Where(point => point.ChannelID == line2.ID) - .Where(point => point.SeriesID == SeriesID.Average) - .Select(point => point.Value) - .OrderBy(value => value) - .ToList(); - - List avgLine3 = points - .Where(point => point.ChannelID == line1.ID) - .Where(point => point.SeriesID == SeriesID.Average) - .Select(point => point.Value) - .OrderBy(value => value) - .ToList(); - - if (!avgLine1.Any() && !avgLine2.Any() && !avgLine3.Any()) - { - verticalMillimeters += InsertItalicText(page, verticalMillimeters, $" No L-N Voltage data during {FirstOfMonth.ToString("MM/dd/yyyy")} - {EndOfMonth.ToString("MM/dd/yyyy")}"); - Summary.VoltageLN.Compliance = "Pass"; - return verticalMillimeters; - } - - Summary.VoltageLN.Min = points.Where(point => point.SeriesID == SeriesID.Minimum).Select(point => point.Value).Min(); - Summary.VoltageLN.Avg = points.Where(point => point.SeriesID == SeriesID.Average).Select(point => point.Value).Average(); - Summary.VoltageLN.Max = points.Where(point => point.SeriesID == SeriesID.Maximum).Select(point => point.Value).Max(); - - double firstPercentileMinLimit = nominal * (100.0D - ReportsSettings.FirstVoltageDeviationLimit) / 100.0D; - double firstPercentileMaxLimit = nominal * (100.0D + ReportsSettings.FirstVoltageDeviationLimit) / 100.0D; - - int firstPercentileSkipCountLine1 = (int)(avgLine1.Count * (100.0D - ReportsSettings.FirstVoltagePercentile) / 100.0D / 2.0D); - int firstPercentileSkipCountLine2 = (int)(avgLine2.Count * (100.0D - ReportsSettings.FirstVoltagePercentile) / 100.0D / 2.0D); - int firstPercentileSkipCountLine3 = (int)(avgLine3.Count * (100.0D - ReportsSettings.FirstVoltagePercentile) / 100.0D / 2.0D); - double firstPercentileMinLine1 = avgLine1.Skip(firstPercentileSkipCountLine1).FirstOrDefault(); - double firstPercentileMinLine2 = avgLine2.Skip(firstPercentileSkipCountLine2).FirstOrDefault(); - double firstPercentileMinLine3 = avgLine3.Skip(firstPercentileSkipCountLine3).FirstOrDefault(); - double firstPercentileMaxLine1 = avgLine1.AsEnumerable().Reverse().Skip(firstPercentileSkipCountLine1).FirstOrDefault(); - double firstPercentileMaxLine2 = avgLine2.AsEnumerable().Reverse().Skip(firstPercentileSkipCountLine2).FirstOrDefault(); - double firstPercentileMaxLine3 = avgLine3.AsEnumerable().Reverse().Skip(firstPercentileSkipCountLine3).FirstOrDefault(); - - bool firstTest = - firstPercentileMinLine1 >= firstPercentileMinLimit && - firstPercentileMaxLine1 <= firstPercentileMaxLimit && - firstPercentileMinLine2 >= firstPercentileMinLimit && - firstPercentileMaxLine2 <= firstPercentileMaxLimit && - firstPercentileMinLine3 >= firstPercentileMinLimit && - firstPercentileMaxLine3 <= firstPercentileMaxLimit; - - double secondPercentileMinLimit = nominal * (100.0D - ReportsSettings.SecondVoltageDeviationLimit) / 100.0D; - double secondPercentileMaxLimit = nominal * (100.0D + ReportsSettings.SecondVoltageDeviationLimit) / 100.0D; - - int secondPercentileSkipCountLine1 = (int)(avgLine1.Count * (100.0D - ReportsSettings.SecondVoltagePercentile) / 100.0D / 2.0D); - int secondPercentileSkipCountLine2 = (int)(avgLine2.Count * (100.0D - ReportsSettings.SecondVoltagePercentile) / 100.0D / 2.0D); - int secondPercentileSkipCountLine3 = (int)(avgLine3.Count * (100.0D - ReportsSettings.SecondVoltagePercentile) / 100.0D / 2.0D); - double secondPercentileMinLine1 = avgLine1.Skip(secondPercentileSkipCountLine1).FirstOrDefault(); - double secondPercentileMinLine2 = avgLine2.Skip(secondPercentileSkipCountLine2).FirstOrDefault(); - double secondPercentileMinLine3 = avgLine3.Skip(secondPercentileSkipCountLine3).FirstOrDefault(); - double secondPercentileMaxLine1 = avgLine1.AsEnumerable().Reverse().Skip(secondPercentileSkipCountLine1).FirstOrDefault(); - double secondPercentileMaxLine2 = avgLine2.AsEnumerable().Reverse().Skip(secondPercentileSkipCountLine2).FirstOrDefault(); - double secondPercentileMaxLine3 = avgLine3.AsEnumerable().Reverse().Skip(secondPercentileSkipCountLine3).FirstOrDefault(); - - bool secondTest = - secondPercentileMinLine1 >= secondPercentileMinLimit && - secondPercentileMaxLine1 <= secondPercentileMaxLimit && - secondPercentileMinLine2 >= secondPercentileMinLimit && - secondPercentileMaxLine2 <= secondPercentileMaxLimit && - secondPercentileMinLine3 >= secondPercentileMinLimit && - secondPercentileMaxLine3 <= secondPercentileMaxLimit; - - Summary.VoltageLN.Compliance = (firstTest && secondTest ? "Pass" : "Fail"); - - FontProp headerProp = new FontProp(FontDefinition, 0); - headerProp.rSizePoint = 10.0D; - - using (TableLayoutManager tlm = new TableLayoutManager(headerProp)) - { - FontProp textProp = new FontProp(FontDefinition, 0); - textProp.rSizePoint = 8.0D; - tlm.tlmCellDef_Header.rAlignV = RepObj.rAlignCenter; // set vertical alignment of all header cells - tlm.tlmCellDef_Default.penProp_LineBottom = new PenProp(this, 0.05, Color.LightGray); // set bottom line for all cells - tlm.tlmHeightMode = TlmHeightMode.AdjustLast; - tlm.eNewContainer += (oSender, ea) => page.AddMM(PageMarginMillimeters, verticalMillimeters, ea.container); - - // define columns - TlmColumn col; - col = new TlmColumnMM(tlm, "Monthly PQ Report Requirement", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.30); - col = new TlmColumnMM(tlm, "Measured L1 Voltage", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.20); - col = new TlmColumnMM(tlm, "Measured L2 Voltage", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.20); - col = new TlmColumnMM(tlm, "Measured L3 Voltage", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.20); - col = new TlmColumnMM(tlm, "Result", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.10); - - tlm.NewRow(); - tlm.Add(0, new RepString(textProp, $"{ReportsSettings.FirstVoltagePercentile:0.###}% of the time: {firstPercentileMinLimit:N1} V - {firstPercentileMaxLimit:N1} V")); - tlm.Add(1, new RepString(textProp, (avgLine1.Any() ? $"{firstPercentileMinLine1:N1}V - {firstPercentileMaxLine1:N1}V" : "No data for time period"))); - tlm.Add(2, new RepString(textProp, (avgLine2.Any() ? $"{firstPercentileMinLine2:N1}V - {firstPercentileMaxLine2:N2}V" : "No data for time period"))); - tlm.Add(3, new RepString(textProp, (avgLine3.Any() ? $"{firstPercentileMinLine3:N1}V - {firstPercentileMaxLine3:N2}V" : "No data for time period"))); - tlm.Add(4, new RepString(textProp, (firstTest ? "Pass" : "Fail"))); - - tlm.NewRow(); - tlm.Add(0, new RepString(textProp, $"{ReportsSettings.SecondVoltagePercentile:0.###}% of the time: {secondPercentileMinLimit:N1} V - {secondPercentileMaxLine1:N1} V")); - tlm.Add(1, new RepString(textProp, (avgLine1.Any() ? $"{secondPercentileMinLine1:N1}V - {secondPercentileMaxLine1:N1}V" : "No data for time period"))); - tlm.Add(2, new RepString(textProp, (avgLine2.Any() ? $"{secondPercentileMinLine2:N1}V - {secondPercentileMaxLine2:N1}V" : "No data for time period"))); - tlm.Add(3, new RepString(textProp, (avgLine3.Any() ? $"{secondPercentileMinLine3:N1}V - {secondPercentileMaxLine3:N1}V" : "No data for time period"))); - tlm.Add(4, new RepString(textProp, (secondTest ? "Pass" : "Fail"))); - - tlm.Commit(); - - verticalMillimeters += tlm.rCurY_MM + 10; - } - - List pointGroups = new List(); - - pointGroups.Add(new PointGroup() - { - Name = "L1-N", - Color = Color.DarkBlue, - Data = points - .Where(point => point.ChannelID == line1.ID) - .Where(point => point.SeriesID == SeriesID.Average) - .ToList() - }); - - pointGroups.Add(new PointGroup() - { - Name = "L2-N", - Color = Color.DarkGreen, - Data = points - .Where(point => point.ChannelID == line2.ID) - .Where(point => point.SeriesID == SeriesID.Average) - .ToList() - }); - - pointGroups.Add(new PointGroup() - { - Name = "L3-N", - Color = Color.Purple, - Data = points - .Where(point => point.ChannelID == line3.ID) - .Where(point => point.SeriesID == SeriesID.Average) - .ToList() - }); - - List avg = points - .Where(point => point.SeriesID == SeriesID.Average) - .Select(point => point.Value) - .ToList(); - - double maxValue = avg.Max(); - double minValue = avg.Min(); - double chartHigh = (maxValue > secondPercentileMaxLimit ? maxValue : secondPercentileMaxLimit); - double chartLow = (minValue < secondPercentileMinLimit ? minValue : secondPercentileMinLimit); - double chartMax = chartHigh + (chartHigh - chartLow) * .10; - double chartMin = chartLow - (chartHigh - chartLow) * .10; - - Chart chart = GenerateLineChart("V", pointGroups, chartMax, chartMin, "MM/dd", "0.00", "Voltage L-L", secondPercentileMaxLimit, firstPercentileMaxLimit, secondPercentileMinLimit, firstPercentileMinLimit, FirstOfMonth, EndOfMonth); - verticalMillimeters += 75; - - page.AddMM(PageMarginMillimeters, verticalMillimeters, new RepImageMM(ChartToImage(chart), PageWidthMillimeters - 2 * PageMarginMillimeters, 75)); - verticalMillimeters += 75; - - chart = GenerateBarChart("0", avg, maxValue, minValue); - page.AddMM(PageMarginMillimeters, verticalMillimeters, new RepImageMM(ChartToImage(chart), PageWidthMillimeters - 2 * PageMarginMillimeters, 75)); - - return verticalMillimeters; - } - } - - private double InsertVoltageLLPage(Page page, double verticalMillimeters) - { - string historianServer = Connection.ExecuteScalar("SELECT Value FROM Setting WHERE Name = 'Historian.Server'") ?? "127.0.0.1"; - string historianInstance = Connection.ExecuteScalar("SELECT Value FROM Setting WHERE Name = 'Historian.Instance'") ?? "XDA"; - IEnumerable channels = new TableOperations(Connection).QueryRecordsWhere("MeterID = {0} AND MeasurementCharacteristicID = (SELECT ID FROM MeasurementCharacteristic WHERE Name = 'RMS') AND MeasurementTypeID = (SELECT ID FROM MeasurementType WHERE Name = 'Voltage')", Meter.ID); - - Channel line1 = new TableOperations(Connection).QueryRecordWhere("MeterID = {0} AND MeasurementCharacteristicID = (SELECT ID FROM MeasurementCharacteristic WHERE Name = 'RMS') AND MeasurementTypeID = (SELECT ID FROM MeasurementType WHERE Name = 'Voltage') AND PhaseID = (SELECT ID FROM Phase WHERE Name = 'AB')", Meter.ID); - Channel line2 = new TableOperations(Connection).QueryRecordWhere("MeterID = {0} AND MeasurementCharacteristicID = (SELECT ID FROM MeasurementCharacteristic WHERE Name = 'RMS') AND MeasurementTypeID = (SELECT ID FROM MeasurementType WHERE Name = 'Voltage') AND PhaseID = (SELECT ID FROM Phase WHERE Name = 'BC')", Meter.ID); - Channel line3 = new TableOperations(Connection).QueryRecordWhere("MeterID = {0} AND MeasurementCharacteristicID = (SELECT ID FROM MeasurementCharacteristic WHERE Name = 'RMS') AND MeasurementTypeID = (SELECT ID FROM MeasurementType WHERE Name = 'Voltage') AND PhaseID = (SELECT ID FROM Phase WHERE Name = 'CA')", Meter.ID); - - int lineId; - - if (line1 == null && line2 == null && line3 == null) - { - verticalMillimeters += InsertItalicText(page, verticalMillimeters, $" No L-L Voltage channels setup on this meter."); - Summary.VoltageLL.Compliance = "Pass"; - return verticalMillimeters; - } - - if (line1 != null) - lineId = line1.AssetID; - else if (line2 != null) - lineId = line2.AssetID; - else - lineId = line3.AssetID; - - double nominal = Summary.VoltageLL.Nominal = Connection.ExecuteScalar("SELECT VoltageKV FROM Asset WHERE ID = {0}", lineId) * 1000; - verticalMillimeters += InsertNominal(page, verticalMillimeters, "Voltage", string.Format("{0:N0}", nominal), "V L-L"); - - using (Historian historian = new Historian(historianServer, historianInstance)) - { - List points = historian.Read(new List() { line1.ID, line2.ID, line3.ID }, FirstOfMonth, EndOfMonth).ToList(); - - List avgLine1 = points - .Where(point => point.ChannelID == line1.ID) - .Where(point => point.SeriesID == SeriesID.Average) - .Select(point => point.Value) - .OrderBy(value => value) - .ToList(); - - List avgLine2 = points - .Where(point => point.ChannelID == line2.ID) - .Where(point => point.SeriesID == SeriesID.Average) - .Select(point => point.Value) - .OrderBy(value => value) - .ToList(); - - List avgLine3 = points - .Where(point => point.ChannelID == line1.ID) - .Where(point => point.SeriesID == SeriesID.Average) - .Select(point => point.Value) - .OrderBy(value => value) - .ToList(); - - if (!avgLine1.Any() && !avgLine2.Any() && !avgLine3.Any()) - { - verticalMillimeters += InsertItalicText(page, verticalMillimeters, $" No L-L Voltage data during {FirstOfMonth.ToString("MM/dd/yyyy")} - {EndOfMonth.ToString("MM/dd/yyyy")}"); - Summary.VoltageLL.Compliance = "Pass"; - return verticalMillimeters; - } - - Summary.VoltageLL.Min = points.Where(point => point.SeriesID == SeriesID.Minimum).Select(point => point.Value).Min(); - Summary.VoltageLL.Avg = points.Where(point => point.SeriesID == SeriesID.Average).Select(point => point.Value).Average(); - Summary.VoltageLL.Max = points.Where(point => point.SeriesID == SeriesID.Maximum).Select(point => point.Value).Max(); - - double firstPercentileMinLimit = nominal * (100.0D - ReportsSettings.FirstVoltageDeviationLimit) / 100.0D; - double firstPercentileMaxLimit = nominal * (100.0D + ReportsSettings.FirstVoltageDeviationLimit) / 100.0D; - - int firstPercentileSkipCountLine1 = (int)(avgLine1.Count * (100.0D - ReportsSettings.FirstVoltagePercentile) / 100.0D / 2.0D); - int firstPercentileSkipCountLine2 = (int)(avgLine2.Count * (100.0D - ReportsSettings.FirstVoltagePercentile) / 100.0D / 2.0D); - int firstPercentileSkipCountLine3 = (int)(avgLine3.Count * (100.0D - ReportsSettings.FirstVoltagePercentile) / 100.0D / 2.0D); - double firstPercentileMinLine1 = avgLine1.Skip(firstPercentileSkipCountLine1).FirstOrDefault(); - double firstPercentileMinLine2 = avgLine2.Skip(firstPercentileSkipCountLine2).FirstOrDefault(); - double firstPercentileMinLine3 = avgLine3.Skip(firstPercentileSkipCountLine3).FirstOrDefault(); - double firstPercentileMaxLine1 = avgLine1.AsEnumerable().Reverse().Skip(firstPercentileSkipCountLine1).FirstOrDefault(); - double firstPercentileMaxLine2 = avgLine2.AsEnumerable().Reverse().Skip(firstPercentileSkipCountLine2).FirstOrDefault(); - double firstPercentileMaxLine3 = avgLine3.AsEnumerable().Reverse().Skip(firstPercentileSkipCountLine3).FirstOrDefault(); - - bool firstTest = - firstPercentileMinLine1 >= firstPercentileMinLimit && - firstPercentileMaxLine1 <= firstPercentileMaxLimit && - firstPercentileMinLine2 >= firstPercentileMinLimit && - firstPercentileMaxLine2 <= firstPercentileMaxLimit && - firstPercentileMinLine3 >= firstPercentileMinLimit && - firstPercentileMaxLine3 <= firstPercentileMaxLimit; - - double secondPercentileMinLimit = nominal * (100.0D - ReportsSettings.SecondVoltageDeviationLimit) / 100.0D; - double secondPercentileMaxLimit = nominal * (100.0D + ReportsSettings.SecondVoltageDeviationLimit) / 100.0D; - - int secondPercentileSkipCountLine1 = (int)(avgLine1.Count * (100.0D - ReportsSettings.SecondVoltagePercentile) / 100.0D / 2.0D); - int secondPercentileSkipCountLine2 = (int)(avgLine2.Count * (100.0D - ReportsSettings.SecondVoltagePercentile) / 100.0D / 2.0D); - int secondPercentileSkipCountLine3 = (int)(avgLine3.Count * (100.0D - ReportsSettings.SecondVoltagePercentile) / 100.0D / 2.0D); - double secondPercentileMinLine1 = avgLine1.Skip(secondPercentileSkipCountLine1).FirstOrDefault(); - double secondPercentileMinLine2 = avgLine2.Skip(secondPercentileSkipCountLine2).FirstOrDefault(); - double secondPercentileMinLine3 = avgLine3.Skip(secondPercentileSkipCountLine3).FirstOrDefault(); - double secondPercentileMaxLine1 = avgLine1.AsEnumerable().Reverse().Skip(secondPercentileSkipCountLine1).FirstOrDefault(); - double secondPercentileMaxLine2 = avgLine2.AsEnumerable().Reverse().Skip(secondPercentileSkipCountLine2).FirstOrDefault(); - double secondPercentileMaxLine3 = avgLine3.AsEnumerable().Reverse().Skip(secondPercentileSkipCountLine3).FirstOrDefault(); - - bool secondTest = - secondPercentileMinLine1 >= secondPercentileMinLimit && - secondPercentileMaxLine1 <= secondPercentileMaxLimit && - secondPercentileMinLine2 >= secondPercentileMinLimit && - secondPercentileMaxLine2 <= secondPercentileMaxLimit && - secondPercentileMinLine3 >= secondPercentileMinLimit && - secondPercentileMaxLine3 <= secondPercentileMaxLimit; - - Summary.VoltageLL.Compliance = (firstTest && secondTest ? "Pass" : "Fail"); - - FontProp headerProp = new FontProp(FontDefinition, 0); - headerProp.rSizePoint = 10.0D; - - using (TableLayoutManager tlm = new TableLayoutManager(headerProp)) - { - FontProp textProp = new FontProp(FontDefinition, 0); - textProp.rSizePoint = 8.0D; - tlm.tlmCellDef_Header.rAlignV = RepObj.rAlignCenter; // set vertical alignment of all header cells - tlm.tlmCellDef_Default.penProp_LineBottom = new PenProp(this, 0.05, Color.LightGray); // set bottom line for all cells - tlm.tlmHeightMode = TlmHeightMode.AdjustLast; - tlm.eNewContainer += (oSender, ea) => page.AddMM(PageMarginMillimeters, verticalMillimeters, ea.container); - - // define columns - TlmColumn col; - col = new TlmColumnMM(tlm, "Monthly PQ Report Requirement", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.30); - col = new TlmColumnMM(tlm, "Measured L1 Voltage", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.20); - col = new TlmColumnMM(tlm, "Measured L2 Voltage", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.20); - col = new TlmColumnMM(tlm, "Measured L3 Voltage", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.20); - col = new TlmColumnMM(tlm, "Result", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.10); - - tlm.NewRow(); - tlm.Add(0, new RepString(textProp, $"{ReportsSettings.FirstVoltagePercentile:0.###}% of the time: {firstPercentileMinLimit:N1} V - {firstPercentileMaxLimit:N1} V")); - tlm.Add(1, new RepString(textProp, (avgLine1.Any() ? $"{firstPercentileMinLine1:N1}V - {firstPercentileMaxLine1:N1}V" : "No data for time period"))); - tlm.Add(2, new RepString(textProp, (avgLine2.Any() ? $"{firstPercentileMinLine2:N1}V - {firstPercentileMaxLine2:N1}V" : "No data for time period"))); - tlm.Add(3, new RepString(textProp, (avgLine3.Any() ? $"{firstPercentileMinLine3:N1}V - {firstPercentileMaxLine3:N1}V" : "No data for time period"))); - tlm.Add(4, new RepString(textProp, (firstTest ? "Pass" : "Fail"))); - - tlm.NewRow(); - tlm.Add(0, new RepString(textProp, $"{ReportsSettings.SecondVoltagePercentile:0.###}% of the time: {secondPercentileMinLimit:N1} V - {secondPercentileMaxLimit:N1} V")); - tlm.Add(1, new RepString(textProp, (avgLine1.Any() ? $"{secondPercentileMinLine1:N1}V - {secondPercentileMaxLine1:N1}V" : "No data for time period"))); - tlm.Add(2, new RepString(textProp, (avgLine2.Any() ? $"{secondPercentileMinLine2:N1}V - {secondPercentileMaxLine2:N1}V" : "No data for time period"))); - tlm.Add(3, new RepString(textProp, (avgLine3.Any() ? $"{secondPercentileMinLine3:N1}V - {secondPercentileMaxLine3:N1}V" : "No data for time period"))); - tlm.Add(4, new RepString(textProp, (secondTest ? "Pass" : "Fail"))); - - tlm.Commit(); - - verticalMillimeters += tlm.rCurY_MM + 10; - } - - List pointGroups = new List(); - pointGroups.Add(new PointGroup() - { - Name = "L1-L2", - Color = Color.DarkBlue, - Data = points - .Where(point => point.ChannelID == line1.ID) - .Where(point => point.SeriesID == SeriesID.Average) - .ToList() - }); - - pointGroups.Add(new PointGroup() - { - Name = "L2-L3", - Color = Color.DarkGreen, - Data = points - .Where(point => point.ChannelID == line2.ID) - .Where(point => point.SeriesID == SeriesID.Average) - .ToList() - }); - - pointGroups.Add(new PointGroup() - { - Name = "L3-L1", - Color = Color.Purple, - Data = points - .Where(point => point.ChannelID == line3.ID) - .Where(point => point.SeriesID == SeriesID.Average) - .ToList() - }); - - List avg = points - .Where(point => point.SeriesID == SeriesID.Average) - .Select(point => point.Value) - .ToList(); - - double maxValue = avg.Max(); - double minValue = avg.Min(); - double chartHigh = (maxValue > secondPercentileMaxLimit ? maxValue : secondPercentileMaxLimit); - double chartLow = (minValue < secondPercentileMinLimit ? minValue : secondPercentileMinLimit); - double chartMax = chartHigh + (chartHigh - chartLow) * .10; - double chartMin = chartLow - (chartHigh - chartLow) * .10; - - Chart chart = GenerateLineChart("V", pointGroups, chartMax, chartMin, "MM/dd", "0.00", "Voltage L-L", secondPercentileMaxLimit, firstPercentileMaxLimit, secondPercentileMinLimit, firstPercentileMinLimit, FirstOfMonth, EndOfMonth); - verticalMillimeters += 75; - - page.AddMM(PageMarginMillimeters, verticalMillimeters, new RepImageMM(ChartToImage(chart), PageWidthMillimeters - 2 * PageMarginMillimeters, 75)); - verticalMillimeters += 75; - - chart = GenerateBarChart("0", avg, maxValue, minValue); - page.AddMM(PageMarginMillimeters, verticalMillimeters, new RepImageMM(ChartToImage(chart), PageWidthMillimeters - 2 * PageMarginMillimeters, 75)); - - return verticalMillimeters; - } - } - - private double InsertFlickerPage(Page page, double verticalMillimeters) - { - string historianServer = Connection.ExecuteScalar("SELECT Value FROM Setting WHERE Name = 'Historian.Server'") ?? "127.0.0.1"; - string historianInstance = Connection.ExecuteScalar("SELECT Value FROM Setting WHERE Name = 'Historian.Instance'") ?? "XDA"; - Channel line1 = new TableOperations(Connection).QueryRecordWhere("MeterID = {0} AND MeasurementCharacteristicID = (SELECT ID FROM MeasurementCharacteristic WHERE Name = 'FlkrPST') AND MeasurementTypeID = (SELECT ID FROM MeasurementType WHERE Name = 'Voltage') AND PhaseID = (SELECT ID FROM Phase WHERE Name = 'AN')", Meter.ID); - Channel line2 = new TableOperations(Connection).QueryRecordWhere("MeterID = {0} AND MeasurementCharacteristicID = (SELECT ID FROM MeasurementCharacteristic WHERE Name = 'FlkrPST') AND MeasurementTypeID = (SELECT ID FROM MeasurementType WHERE Name = 'Voltage') AND PhaseID = (SELECT ID FROM Phase WHERE Name = 'BN')", Meter.ID); - Channel line3 = new TableOperations(Connection).QueryRecordWhere("MeterID = {0} AND MeasurementCharacteristicID = (SELECT ID FROM MeasurementCharacteristic WHERE Name = 'FlkrPST') AND MeasurementTypeID = (SELECT ID FROM MeasurementType WHERE Name = 'Voltage') AND PhaseID = (SELECT ID FROM Phase WHERE Name = 'CN')", Meter.ID); - - if (line1 == null && line2 == null && line3 == null) - { - verticalMillimeters += InsertItalicText(page, verticalMillimeters, $" No Flicker channels setup on this meter."); - Summary.Flicker.Compliance = "Pass"; - return verticalMillimeters; - } - - using (Historian historian = new Historian(historianServer, historianInstance)) - { - List points = historian.Read(new List() { line1.ID, line2.ID, line3.ID }, FirstOfMonth, EndOfMonth).Where(x => x.SeriesID == SeriesID.Maximum).ToList(); - - if (!points.Any()) - { - verticalMillimeters += InsertItalicText(page, verticalMillimeters, $" No Flicker data during {FirstOfMonth.ToString("MM/dd/yyyy")} - {EndOfMonth.ToString("MM/dd/yyyy")}"); - return verticalMillimeters; - } - - Summary.Flicker.Max = points.Max(point => point.Value); - - List maxLine1 = points - .Where(point => point.ChannelID == line1.ID) - .Select(point => point.Value) - .OrderByDescending(value => value) - .ToList(); - - List maxLine2 = points - .Where(point => point.ChannelID == line2.ID) - .Select(point => point.Value) - .OrderByDescending(value => value) - .ToList(); - - List maxLine3 = points - .Where(point => point.ChannelID == line3.ID) - .Select(point => point.Value) - .OrderByDescending(value => value) - .ToList(); - - double percentileMaxLimit = ReportsSettings.FlickerHighLimit; - - int percentileSkipCountLine1 = (int)(maxLine1.Count * (100.0D - ReportsSettings.FlickerPercentile) / 100.0D); - int percentileSkipCountLine2 = (int)(maxLine2.Count * (100.0D - ReportsSettings.FlickerPercentile) / 100.0D); - int percentileSkipCountLine3 = (int)(maxLine3.Count * (100.0D - ReportsSettings.FlickerPercentile) / 100.0D); - double percentileMaxLine1 = maxLine1.Skip(percentileSkipCountLine1).FirstOrDefault(); - double percentileMaxLine2 = maxLine2.Skip(percentileSkipCountLine2).FirstOrDefault(); - double percentileMaxLine3 = maxLine3.Skip(percentileSkipCountLine3).FirstOrDefault(); - - bool test = - percentileMaxLine1 <= percentileMaxLimit && - percentileMaxLine2 <= percentileMaxLimit && - percentileMaxLine3 <= percentileMaxLimit; - - Summary.Flicker.Compliance = (test ? "Pass" : "Fail"); - - FontProp headerProp = new FontProp(FontDefinition, 0); - headerProp.rSizePoint = 10.0D; - - using (TableLayoutManager tlm = new TableLayoutManager(headerProp)) - { - FontProp textProp = new FontProp(FontDefinition, 0); - textProp.rSizePoint = 8.0D; - tlm.tlmCellDef_Header.rAlignV = RepObj.rAlignCenter; // set vertical alignment of all header cells - tlm.tlmCellDef_Default.penProp_LineBottom = new PenProp(this, 0.05, Color.LightGray); // set bottom line for all cells - tlm.tlmHeightMode = TlmHeightMode.AdjustLast; - tlm.eNewContainer += (oSender, ea) => page.AddMM(PageMarginMillimeters, verticalMillimeters, ea.container); - - // define columns - TlmColumn col; - col = new TlmColumnMM(tlm, "Monthly PQ Report Requirement", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.30); - col = new TlmColumnMM(tlm, "Measured L1 Plt", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.20); - col = new TlmColumnMM(tlm, "Measured L2 Plt", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.20); - col = new TlmColumnMM(tlm, "Measured L3 Plt", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.20); - col = new TlmColumnMM(tlm, "Result", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.10); - - tlm.NewRow(); - tlm.Add(0, new RepString(textProp, $"{ReportsSettings.FlickerPercentile:0.###}% of the time: Plt <= {ReportsSettings.FlickerHighLimit:0.###}")); - tlm.Add(1, new RepString(textProp, $"{percentileMaxLine1:N2}")); - tlm.Add(2, new RepString(textProp, $"{percentileMaxLine2:N2}")); - tlm.Add(3, new RepString(textProp, $"{percentileMaxLine3:N2}")); - tlm.Add(4, new RepString(textProp, (test ? "Pass" : "Fail"))); - - tlm.Commit(); - - verticalMillimeters += tlm.rCurY_MM + 10; - } - - List pointGroups = new List(); - - pointGroups.Add(new PointGroup() - { - Name = "L1Max", - Color = Color.DarkBlue, - Data = points - .Where(point => point.ChannelID == line1.ID) - .ToList() - }); - - pointGroups.Add(new PointGroup() - { - Name = "L2Max", - Color = Color.DarkGreen, - Data = points - .Where(point => point.ChannelID == line2.ID) - .ToList() - }); - - pointGroups.Add(new PointGroup() - { - Name = "L3Max", - Color = Color.Purple, - Data = points - .Where(point => point.ChannelID == line3.ID) - .ToList() - }); - - double maxValue = points.Select(point => point.Value).Max(); - Chart chart = GenerateLineChart("", pointGroups, maxValue * 1.1, 0, "MM/dd", "0.00", "Flicker", ReportsSettings.FlickerHighLimit, null, null, null, FirstOfMonth, EndOfMonth); - verticalMillimeters += 75; - - page.AddMM(PageMarginMillimeters, verticalMillimeters, new RepImageMM(ChartToImage(chart), PageWidthMillimeters - 2 * PageMarginMillimeters, 75)); - verticalMillimeters += 75; - - chart = GenerateBarChart("0.00", points.Select(x => x.Value), maxValue, 0); - page.AddMM(PageMarginMillimeters, verticalMillimeters, new RepImageMM(ChartToImage(chart), PageWidthMillimeters - 2 * PageMarginMillimeters, 75)); - - return verticalMillimeters; - } - } - - private double InsertImbalancePage(Page page, double verticalMillimeters) - { - string historianServer = Connection.ExecuteScalar("SELECT Value FROM Setting WHERE Name = 'Historian.Server'") ?? "127.0.0.1"; - string historianInstance = Connection.ExecuteScalar("SELECT Value FROM Setting WHERE Name = 'Historian.Instance'") ?? "XDA"; - Channel channel = new TableOperations(Connection).QueryRecordWhere("MeterID = {0} AND MeasurementCharacteristicID = (SELECT ID FROM MeasurementCharacteristic WHERE Name = 'AvgImbal') AND MeasurementTypeID = (SELECT ID FROM MeasurementType WHERE Name = 'Voltage')", Meter.ID); - - if (channel == null) - { - verticalMillimeters += InsertItalicText(page, verticalMillimeters, $" No Imbalance channel setup on this meter."); - Summary.Imbalance.Compliance = "Pass"; - return verticalMillimeters; - } - - using (Historian historian = new Historian(historianServer, historianInstance)) - { - List points = historian.Read(new List() { channel.ID }, FirstOfMonth, EndOfMonth).Where(x => x.SeriesID == SeriesID.Average).ToList(); - - if (!points.Any()) - { - verticalMillimeters += InsertItalicText(page, verticalMillimeters, $" No Imbalance data during {FirstOfMonth.ToString("MM/dd/yyyy")} - {EndOfMonth.ToString("MM/dd/yyyy")}"); - return verticalMillimeters; - } - - List avg = points - .Select(point => point.Value) - .OrderBy(value => value) - .ToList(); - - Summary.Imbalance.Avg = points.Select(point => point.Value).Average(); - - int percentileSkipCount = (int)(avg.Count * (100.0D - ReportsSettings.VoltageUnbalancePercentile) / 100.0D / 2.0D); - double percentileMinLimit = ReportsSettings.VoltageUnbalanceLowLimit; - double percentileMaxLimit = ReportsSettings.VoltageUnbalanceHighLimit; - double percentileMin = avg.Skip(percentileSkipCount).FirstOrDefault(); - double percentileMax = avg.AsEnumerable().Reverse().Skip(percentileSkipCount).FirstOrDefault(); - - bool test = - percentileMin >= percentileMinLimit && - percentileMax <= percentileMaxLimit; - - Summary.Imbalance.Compliance = (test ? "Pass" : "Fail"); - - FontProp headerProp = new FontProp(FontDefinition, 0); - headerProp.rSizePoint = 10.0D; - - using (TableLayoutManager tlm = new TableLayoutManager(headerProp)) - { - FontProp textProp = new FontProp(FontDefinition, 0); - textProp.rSizePoint = 8.0D; - tlm.tlmCellDef_Header.rAlignV = RepObj.rAlignCenter; // set vertical alignment of all header cells - tlm.tlmCellDef_Default.penProp_LineBottom = new PenProp(this, 0.05, Color.LightGray); // set bottom line for all cells - tlm.tlmHeightMode = TlmHeightMode.AdjustLast; - tlm.eNewContainer += (oSender, ea) => page.AddMM(PageMarginMillimeters, verticalMillimeters, ea.container); - - // define columns - TlmColumn col; - col = new TlmColumnMM(tlm, "Monthly PQ Report Requirement", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.40); - col = new TlmColumnMM(tlm, "Measured Unbalance u2", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.40); - col = new TlmColumnMM(tlm, "Result", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.20); - - tlm.NewRow(); - tlm.Add(0, new RepString(textProp, $"{ReportsSettings.VoltageUnbalancePercentile:0.###}% of the time: {percentileMinLimit:0.###}% ~ {percentileMaxLimit:0.###}% u2")); - tlm.Add(1, new RepString(textProp, $"{percentileMax:N2}")); - tlm.Add(2, new RepString(textProp, (test ? "Pass" : "Fail"))); - - tlm.Commit(); - - verticalMillimeters += tlm.rCurY_MM + 10; - } - - List pointGroups = new List(); - - pointGroups.Add(new PointGroup() - { - Name = "Unbalance", - Color = Color.DarkBlue, - Data = points - }); - - Chart chart = GenerateLineChart("", pointGroups, Math.Max(percentileMax, percentileMaxLimit) * 1.1, Math.Min(percentileMin, percentileMinLimit), "MM/dd", "0.00", "Flicker", percentileMaxLimit, null, percentileMinLimit, null, FirstOfMonth, EndOfMonth); - verticalMillimeters += 75; - - page.AddMM(PageMarginMillimeters, verticalMillimeters, new RepImageMM(ChartToImage(chart), PageWidthMillimeters - 2 * PageMarginMillimeters, 75)); - verticalMillimeters += 75; - - chart = GenerateBarChart("0.00", avg, percentileMax, percentileMin); - page.AddMM(PageMarginMillimeters, verticalMillimeters, new RepImageMM(ChartToImage(chart), PageWidthMillimeters - 2 * PageMarginMillimeters, 75)); - - return verticalMillimeters; - } - } - - private double InsertTHDPage(Page page, double verticalMillimeters) - { - string historianServer = Connection.ExecuteScalar("SELECT Value FROM Setting WHERE Name = 'Historian.Server'") ?? "127.0.0.1"; - string historianInstance = Connection.ExecuteScalar("SELECT Value FROM Setting WHERE Name = 'Historian.Instance'") ?? "XDA"; - - Channel line1 = new TableOperations(Connection).QueryRecordWhere("MeterID = {0} AND MeasurementCharacteristicID = (SELECT ID FROM MeasurementCharacteristic WHERE Name = 'TotalTHD') AND MeasurementTypeID = (SELECT ID FROM MeasurementType WHERE Name = 'Voltage') AND PhaseID = (SELECT ID FROM Phase WHERE Name = 'AN')", Meter.ID); - Channel line2 = new TableOperations(Connection).QueryRecordWhere("MeterID = {0} AND MeasurementCharacteristicID = (SELECT ID FROM MeasurementCharacteristic WHERE Name = 'TotalTHD') AND MeasurementTypeID = (SELECT ID FROM MeasurementType WHERE Name = 'Voltage') AND PhaseID = (SELECT ID FROM Phase WHERE Name = 'BN')", Meter.ID); - Channel line3 = new TableOperations(Connection).QueryRecordWhere("MeterID = {0} AND MeasurementCharacteristicID = (SELECT ID FROM MeasurementCharacteristic WHERE Name = 'TotalTHD') AND MeasurementTypeID = (SELECT ID FROM MeasurementType WHERE Name = 'Voltage') AND PhaseID = (SELECT ID FROM Phase WHERE Name = 'CN')", Meter.ID); - - if (line1 == null && line2 == null && line3 == null) - { - verticalMillimeters += InsertItalicText(page, verticalMillimeters, $" No THD channels setup on this meter."); - Summary.THD.Compliance = "Pass"; - return verticalMillimeters; - } - - using (Historian historian = new Historian(historianServer, historianInstance)) - { - List points = historian.Read(new List() { line1.ID, line2.ID, line3.ID }, FirstOfMonth, EndOfMonth).ToList(); - - if (!points.Any()) - { - verticalMillimeters += InsertItalicText(page, verticalMillimeters, $" No THD data during {FirstOfMonth.ToString("MM/dd/yyyy")} - {EndOfMonth.ToString("MM/dd/yyyy")}"); - return verticalMillimeters; - } - - Summary.THD.Min = points.Where(point => point.SeriesID == SeriesID.Minimum).Select(point => point.Value).Min(); - Summary.THD.Avg = points.Where(point => point.SeriesID == SeriesID.Average).Select(point => point.Value).Average(); - Summary.THD.Max = points.Where(point => point.SeriesID == SeriesID.Maximum).Select(point => point.Value).Max(); - - List maxLine1 = points - .Where(point => point.ChannelID == line1.ID) - .Where(point => point.SeriesID == SeriesID.Maximum) - .Select(point => point.Value) - .OrderByDescending(value => value) - .ToList(); - - List maxLine2 = points - .Where(point => point.ChannelID == line2.ID) - .Where(point => point.SeriesID == SeriesID.Maximum) - .Select(point => point.Value) - .OrderByDescending(value => value) - .ToList(); - - List maxLine3 = points - .Where(point => point.ChannelID == line3.ID) - .Where(point => point.SeriesID == SeriesID.Maximum) - .Select(point => point.Value) - .OrderByDescending(value => value) - .ToList(); - - double percentileMaxLimit = ReportsSettings.VoltageTHDHighLimit; - - int percentileSkipCountLine1 = (int)(maxLine1.Count * (100.0D - ReportsSettings.VoltageTHDPercentile) / 100.0D); - int percentileSkipCountLine2 = (int)(maxLine2.Count * (100.0D - ReportsSettings.VoltageTHDPercentile) / 100.0D); - int percentileSkipCountLine3 = (int)(maxLine3.Count * (100.0D - ReportsSettings.VoltageTHDPercentile) / 100.0D); - double percentileMinLine1 = maxLine1.Last(); - double percentileMaxLine1 = maxLine1.Skip(percentileSkipCountLine1).FirstOrDefault(); - double percentileMinLine2 = maxLine2.Last(); - double percentileMaxLine2 = maxLine2.Skip(percentileSkipCountLine2).FirstOrDefault(); - double percentileMinLine3 = maxLine3.Last(); - double percentileMaxLine3 = maxLine3.Skip(percentileSkipCountLine3).FirstOrDefault(); - - bool test = - percentileMaxLine1 <= percentileMaxLimit && - percentileMaxLine2 <= percentileMaxLimit && - percentileMaxLine3 <= percentileMaxLimit; - - Summary.THD.Compliance = (test ? "Pass" : "Fail"); - - FontProp headerProp = new FontProp(FontDefinition, 0); - headerProp.rSizePoint = 10.0D; - using (TableLayoutManager tlm = new TableLayoutManager(headerProp)) - { - FontProp textProp = new FontProp(FontDefinition, 0); - textProp.rSizePoint = 8.0D; - tlm.tlmCellDef_Header.rAlignV = RepObj.rAlignCenter; // set vertical alignment of all header cells - tlm.tlmCellDef_Default.penProp_LineBottom = new PenProp(this, 0.05, Color.LightGray); // set bottom line for all cells - tlm.tlmHeightMode = TlmHeightMode.AdjustLast; - tlm.eNewContainer += (oSender, ea) => page.AddMM(PageMarginMillimeters, verticalMillimeters, ea.container); - - // define columns - TlmColumn col; - col = new TlmColumnMM(tlm, "Monthly PQ Report Requirement", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.30); - col = new TlmColumnMM(tlm, "Measured L1 THD", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.20); - col = new TlmColumnMM(tlm, "Measured L2 THD", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.20); - col = new TlmColumnMM(tlm, "Measured L3 THD", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.20); - col = new TlmColumnMM(tlm, "Result", (PageWidthMillimeters - 2 * PageMarginMillimeters) * 0.10); - - tlm.NewRow(); - tlm.Add(0, new RepString(textProp, $"{ReportsSettings.VoltageTHDPercentile:0.###}% of the time: THD <= {percentileMaxLimit:0.###}%")); - tlm.Add(1, new RepString(textProp, (maxLine1.Any() ? $"{percentileMinLine1:N2}% - {percentileMaxLine1:N2}%" : "No data for time period"))); - tlm.Add(2, new RepString(textProp, (maxLine2.Any() ? $"{percentileMinLine2:N2}% - {percentileMaxLine2:N2}%" : "No data for time period"))); - tlm.Add(3, new RepString(textProp, (maxLine3.Any() ? $"{percentileMinLine3:N2}% - {percentileMaxLine3:N2}%" : "No data for time period"))); - tlm.Add(4, new RepString(textProp, (test ? "Pass" : "Fail"))); - - tlm.Commit(); - - verticalMillimeters += tlm.rCurY_MM + 10; - } - - List pointGroups = new List(); - - pointGroups.Add(new PointGroup() - { - Name = "L1", - Color = Color.DarkBlue, - Data = points - .Where(point => point.ChannelID == line1.ID) - .Where(point => point.SeriesID == SeriesID.Average) - .ToList() - }); - - pointGroups.Add(new PointGroup() - { - Name = "L2", - Color = Color.DarkGreen, - Data = points - .Where(point => point.ChannelID == line2.ID) - .Where(point => point.SeriesID == SeriesID.Average) - .ToList() - }); - - pointGroups.Add(new PointGroup() - { - Name = "L3", - Color = Color.DarkMagenta, - Data = points - .Where(point => point.ChannelID == line3.ID) - .Where(point => point.SeriesID == SeriesID.Average) - .ToList() - }); - - pointGroups.Add(new PointGroup() - { - Name = "L1-Max", - Color = Color.Blue, - Data = points - .Where(point => point.ChannelID == line1.ID) - .Where(point => point.SeriesID == SeriesID.Maximum) - .ToList() - }); - - pointGroups.Add(new PointGroup() - { - Name = "L2-Max", - Color = Color.Green, - Data = points - .Where(point => point.ChannelID == line2.ID) - .Where(point => point.SeriesID == SeriesID.Maximum) - .ToList() - }); - - pointGroups.Add(new PointGroup() - { - Name = "L3-Max", - Color = Color.Magenta, - Data = points - .Where(point => point.ChannelID == line3.ID) - .Where(point => point.SeriesID == SeriesID.Maximum) - .ToList() - }); - - pointGroups.Add(new PointGroup() - { - Name = "L1-Min", - Color = Color.LightBlue, - Data = points - .Where(point => point.ChannelID == line1.ID) - .Where(point => point.SeriesID == SeriesID.Minimum) - .ToList() - }); - - pointGroups.Add(new PointGroup() - { - Name = "L2-Min", - Color = Color.LightGreen, - Data = points - .Where(point => point.ChannelID == line2.ID) - .Where(point => point.SeriesID == SeriesID.Minimum) - .ToList() - }); - - pointGroups.Add(new PointGroup() - { - Name = "L3-Min", - Color = Color.LightPink, - Data = points - .Where(point => point.ChannelID == line3.ID) - .Where(point => point.SeriesID == SeriesID.Minimum) - .ToList() - }); - - double maxValue = points.Select(point => point.Value).Max(); - double minValue = points.Select(point => point.Value).Min(); - double chartHigh = (maxValue > 8 ? maxValue : 8); - double chartLow = (minValue < 0 ? minValue : 0); - double chartMax = chartHigh + (chartHigh - chartLow) * .10; - double chartMin = chartLow - (chartHigh - chartLow) * .10; - - Chart chart = GenerateLineChart("V", pointGroups, chartMax, chartMin, "MM/dd", "0.00", "Voltage L-L", percentileMaxLimit, null, null, null, FirstOfMonth, EndOfMonth); - verticalMillimeters += 75; - - page.AddMM(PageMarginMillimeters, verticalMillimeters, new RepImageMM(ChartToImage(chart), PageWidthMillimeters - 2 * PageMarginMillimeters, 75)); - verticalMillimeters += 75; - - chart = GenerateBarChart("0.00", points.Select(x => x.Value), maxValue, minValue); - page.AddMM(PageMarginMillimeters, verticalMillimeters, new RepImageMM(ChartToImage(chart), PageWidthMillimeters - 2 * PageMarginMillimeters, 75)); - - return verticalMillimeters; - } - } - - private Chart GenerateLineChart(string units, List pointsGroup, double max, double min, string xFormat, string yFormat, string title, double? highThreshold, double? highWarning, double? lowThreshold, double? lowWarning, DateTime start, DateTime end) - { - - Chart chart; - ChartArea area; - ChartSeries series; - area = new ChartArea(); - area.AxisX.MajorGrid.Enabled = false; - area.AxisX.LabelStyle.Format = xFormat; - area.AxisY.Title = units; - area.AxisY.LabelStyle.Format = yFormat; - area.AxisY.MajorGrid.LineColor = Color.LightGray; - area.AxisY.Maximum = max; - area.AxisY.Minimum = min; - - chart = new Chart(); - chart.Width = 1200; - chart.Height = 500; - chart.Name = title; - - chart.Legends.Add(new Legend()); - chart.ChartAreas.Add(area); - - foreach (var group in pointsGroup) - { - - series = new ChartSeries(group.Name); - series.ChartType = SeriesChartType.FastLine; - series.BorderWidth = 2; - series.Color = group.Color; - - foreach (var point in group.Data) - { - series.Points.AddXY(point.Timestamp, point.Value); - } - - chart.Series.Add(series); - } - - if (highThreshold.HasValue) - chart.Series.Add(MakeLimitSeries((double)highThreshold, Color.Red, "High Threshold", start, end)); - if (lowThreshold.HasValue) - chart.Series.Add(MakeLimitSeries((double)lowThreshold, Color.Red, "Low Threshold", start, end)); - if (highWarning.HasValue) - chart.Series.Add(MakeLimitSeries((double)highWarning, Color.Orange, "High Warning", start, end)); - if (lowWarning.HasValue) - chart.Series.Add(MakeLimitSeries((double)lowWarning, Color.Orange, "Low Warning", start, end)); - - return chart; - - } - - private Chart GenerateBarChart(string units, IEnumerable points, double max, double min) - { - ChartArea area = new ChartArea(); - area.AxisX.MajorGrid.Enabled = false; - area.AxisY.Enabled = AxisEnabled.False; - area.AxisX.IntervalAutoMode = IntervalAutoMode.VariableCount; - area.AxisX.LabelAutoFitStyle = LabelAutoFitStyles.DecreaseFont; - - Chart chart = new Chart(); - chart.Width = 1200; - chart.Height = 500; - chart.ChartAreas.Add(area); - - ChartSeries series = new ChartSeries("bars"); - series.ChartType = SeriesChartType.Column; - series.BorderWidth = 20; - series.Color = Color.DarkBlue; - - double step = (max - min) / NumBuckets; - - Dictionary buckets = Enumerable.Range(0, NumBuckets) - .GroupJoin(points, key => key, point => (int)((point - min) / step), (Key, grouping) => new { Key, Val = grouping.Count() }) - .ToDictionary(obj => (obj.Key * step + min).ToString(units), obj => obj.Val); - - foreach (var kvp in buckets) - series.Points.AddXY(kvp.Key, kvp.Value); - - chart.Series.Add(series); - - return chart; - } - - private Chart GenerateMagDurChart(List> events, string xFormat, string yFormat) - { - - Chart chart; - ChartArea area; - ChartSeries series; - area = new ChartArea(); - area.AxisX.MajorGrid.Enabled = false; - area.AxisX.LabelStyle.Format = xFormat; - area.AxisX.Maximum = 10; - area.AxisX.Minimum = 0.00001; - area.AxisX.IsLogarithmic = true; - area.AxisY.LabelStyle.Format = yFormat; - area.AxisY.MajorGrid.LineColor = Color.LightGray; - area.AxisY.Maximum = 6; - area.AxisY.Minimum = 0; - - chart = new Chart(); - chart.Width = 1000; - chart.Height = 1000; - chart.Name = "ITIC Curve (Interruptions, Sags, Swells)"; - chart.ChartAreas.Add(area); - - series = new ChartSeries("Points"); - series.ChartType = SeriesChartType.Point; - series.BorderWidth = 2; - series.Color = Color.DarkBlue; - - foreach (Tuple evt in events) - { - series.Points.AddXY(evt.Item1, evt.Item2); - } - - chart.Series.Add(series); - - series = new ChartSeries("ITIC Upper"); - series.ChartType = SeriesChartType.Line; - series.BorderWidth = 2; - series.Color = Color.Red; - - foreach (Tuple point in Curves.IticUpperCurve) - { - series.Points.AddXY(point.Item1, point.Item2); - } - - chart.Series.Add(series); - - series = new ChartSeries("ITIC Lower"); - series.ChartType = SeriesChartType.Line; - series.BorderWidth = 2; - series.Color = Color.Red; - - foreach (Tuple point in Curves.IticLowerCurve) - { - series.Points.AddXY(point.Item1, point.Item2); - } - - chart.Series.Add(series); - - return chart; - - } - - private ChartSeries MakeLimitSeries(double value, Color color, string name, DateTime start, DateTime end) - { - ChartSeries cs = new ChartSeries(name); - cs.ChartType = SeriesChartType.FastLine; - cs.BorderWidth = 1; - - cs.Color = color; - cs.Points.AddXY(start, value); - cs.Points.AddXY(end, value); - return cs; - } - - private Stream ChartToImage(Chart chart) - { - MemoryStream stream = new MemoryStream(); - chart.SaveImage(stream, ChartImageFormat.Jpeg); - stream.Position = 0; - return stream; - } - - #endregion - - #region [ Static ] - - private static readonly ILog Log = LogManager.GetLogger(typeof(PQReport)); - - #endregion - } -} diff --git a/Libraries/openXDA.Reports/Properties/AssemblyInfo.cs b/Libraries/openXDA.Reports/Properties/AssemblyInfo.cs deleted file mode 100644 index 46f31e06..00000000 --- a/Libraries/openXDA.Reports/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("openXDA.Reports")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("openXDA.Reports")] -[assembly: AssemblyCopyright("Copyright © 2018")] -[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("656f97d9-4fb6-4f65-a9a6-1a1dde3af634")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("3.0.5.93")] -[assembly: AssemblyVersion("3.0.5.93")] -[assembly: AssemblyFileVersion("3.0.5.93")] diff --git a/Libraries/openXDA.Reports/README.md b/Libraries/openXDA.Reports/README.md deleted file mode 100644 index 02165864..00000000 --- a/Libraries/openXDA.Reports/README.md +++ /dev/null @@ -1,7 +0,0 @@ -![Icon](http://www.gridprotectionalliance.org/images/products/icons%2064/openXDA.png)![openXDA](http://www.gridprotectionalliance.org/images/products/productTitles32/PQSiteReport.png) - -**eXtensible Disturbance Analytics PQ Site Report** - -# Overview -The OpenXDA PQ Site Report was developed to provide the OpenXDA service with a monthly reporting feature that will build and distribute a per meter pdf trending data report -each month that shows if there have been any compliance issues with regards to Voltage and THD. diff --git a/Libraries/openXDA.Reports/ReportsEngine.cs b/Libraries/openXDA.Reports/ReportsEngine.cs deleted file mode 100644 index b0d7f831..00000000 --- a/Libraries/openXDA.Reports/ReportsEngine.cs +++ /dev/null @@ -1,382 +0,0 @@ -//****************************************************************************************************** -// ReportsEngine.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: -// ---------------------------------------------------------------------------------------------------- -// 06/14/2018 - Billy Ernest -// Generated original version of source code. -// -//****************************************************************************************************** - -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Configuration; -using System.Data; -using System.IO; -using System.Linq; -using System.Net.Mail; -using System.Text; -using Gemstone; -using Gemstone.Configuration; -using Gemstone.Data; -using Gemstone.Data.Model; -using Gemstone.Scheduling; -using log4net; -using openXDA.Configuration; -using openXDA.Model; - -namespace openXDA.Reports -{ - public class ReportsEngine : IDisposable - { - #region [ Members ] - private bool m_disposed; - - #endregion - - #region [ Constructors ] - - public ReportsEngine() - { - PQReportsSettings = new PQReportsSettings(); - EmailSettings = new EmailSection(); - BreakerReportsSettings = new BreakerReportsSettings(); - - Scheduler = new ScheduleManager(); - Scheduler.Starting += Scheduler_Starting; - Scheduler.Started += Scheduler_Started; - Scheduler.ScheduleDue += Scheduler_ScheduleDue; - } - - #endregion - - #region [ Properties ] - - private ScheduleManager Scheduler { get; } - - public bool Running { get; private set; } - - [Category] - [SettingName(PQReportsSettings.CategoryName)] - public PQReportsSettings PQReportsSettings { get; } - - [Category] - [SettingName(BreakerReportsSettings.CategoryName)] - public BreakerReportsSettings BreakerReportsSettings { get; } - - [Category] - [SettingName(EmailSection.CategoryName)] - public EmailSection EmailSettings { get; } - - #endregion - - #region [ Methods ] - - public void ProcessMonthlyReport(Meter meter, DateTime month) - { - DateTime firstOfMonth = month.AddDays(1 - month.Day); - DateTime endOfMonth = firstOfMonth.AddMonths(1).AddDays(-1); - - using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) - { - ProcessMonthlyReport(meter, firstOfMonth, endOfMonth, connection); - } - } - - public bool Start() - { - try - { - if (!Running) - { - bool scheduled = false; - - if (PQReportsSettings.Enabled) { - Scheduler.AddSchedule("PQReports", PQReportsSettings.Schedule); - scheduled = true; - } - if (BreakerReportsSettings.Enabled) - { - Scheduler.AddSchedule("BreakerReports", PQReportsSettings.Schedule); - scheduled = true; - } - - if (scheduled) - { - Scheduler.Start(); - Running = true; - } - } - - return true; - } - catch (Exception ex) - { - Log.Error(ex.ToString(), ex); - return false; - } - } - - public void Stop() - { - if (Scheduler.IsRunning) - { - bool scheduled1 = false; - bool scheduled2 = false; - - if (!PQReportsSettings.Enabled) - scheduled1 = Scheduler.RemoveSchedule("PQReports"); - if (!BreakerReportsSettings.Enabled) - scheduled2 = Scheduler.RemoveSchedule("BreakerReports"); - - if (scheduled1 && scheduled2 ) - { - Scheduler.Stop(); - Running = false; - } - } - } - - public void Dispose() - { - if (m_disposed) - return; - - Scheduler.Starting -= Scheduler_Starting; - Scheduler.Started -= Scheduler_Started; - Scheduler.ScheduleDue -= Scheduler_ScheduleDue; - Scheduler.Dispose(); - - m_disposed = true; - } - - public void ReloadSystemSettings(string connectionString) - { - ConnectionStringParser.ParseConnectionString(connectionString, this); - - Scheduler.AddSchedule("PQReports", PQReportsSettings.Schedule, true); - Scheduler.AddSchedule("BreakerReports", BreakerReportsSettings.Schedule, true); - - if (PQReportsSettings.Enabled || BreakerReportsSettings.Enabled) - Start(); - else if (!PQReportsSettings.Enabled || !BreakerReportsSettings.Enabled) - Stop(); - - } - - private void Scheduler_Starting(object sender, EventArgs e) - { - } - - private void Scheduler_Started(object sender, EventArgs e) - { - Log.Info("Reports Engine has started successfully..."); - } - - private void Scheduler_Disposed(object sender, EventArgs e) - { - Log.Info("Reports Engine is disposed..."); - } - - private void Scheduler_ScheduleDue(object sender, EventArgs e) - { - Log.Info(string.Format($"Processing {e.Argument.Name}...")); - if (e.Argument.Name == "PQReports") - ProcessPQReports(); - else if (e.Argument.Name == "BreakerReports") - ProcessBreakerReports(); - } - - private void ProcessBreakerReports() { - DateTime today = DateTime.Now; - DateTime firstOfMonth = today.AddDays(1 - today.Day).AddMonths(-1); - DateTime endOfMonth = firstOfMonth.AddMonths(1).AddDays(-1); - - AllBreakersReport report = new AllBreakersReport(firstOfMonth, endOfMonth); - byte[] pdf = report.CreatePDF(); - if (pdf == null) return; - byte[] csv = ExportAllToCSV(report.DataTable, firstOfMonth, endOfMonth); - if (csv == null) return; - - using (MemoryStream pdfStream = new MemoryStream(pdf)) - using (MemoryStream csvStream = new MemoryStream(csv)) - { - string fileName = "AllBreakersReport_" + firstOfMonth.ToString("MM_dd_yyyy") + "_" + endOfMonth.ToString("MM_dd_yyyy"); - string pdfContentType = "application/pdf"; - string csvContentType = "text/csv"; - string pdfName = fileName + ".pdf"; - string csvName = fileName + ".csv"; - - List attachments = new List() { - new Attachment(pdfStream, pdfName, pdfContentType), - new Attachment(csvStream, csvName, csvContentType) - }; - - EmailWriter emailWriter = new EmailWriter(PQReportsSettings, EmailSettings); - emailWriter.SendEmailWithAttachment(BreakerReportsSettings.EmailList.Split(',').ToList(), $"Breaker Report for {fileName}", "", attachments); - - } - - } - - private void ProcessPQReports() - { - DateTime today = DateTime.Now; - DateTime firstOfMonth = today.AddDays(1 - today.Day).AddMonths(-1); - DateTime endOfMonth = firstOfMonth.AddMonths(1).AddDays(-1); - - using (AdoDataConnection connection = new AdoDataConnection(Settings.Default)) - { - // TODO: There is no EmailGroupAssetGroup - IEnumerable meters = new TableOperations(connection).QueryRecordsWhere("ID IN (SELECT MeterID FROM MeterAssetGroup WHERE AssetGroupID IN (SELECT AssetGroupID FROM EmailGroupAssetGroup WHERE EmailGroupID = (SELECT ID FROM EmailGroup WHERE Name = 'PQ Report')))"); - - foreach (Meter meter in meters) - ProcessMonthlyReport(meter, firstOfMonth, endOfMonth, connection); - - EmailWriter emailWriter = new EmailWriter(PQReportsSettings, EmailSettings); - emailWriter.Execute(firstOfMonth.Month, firstOfMonth.Year); - } - } - - private void ProcessMonthlyReport(Meter meter, DateTime firstOfMonth, DateTime endOfMonth, AdoDataConnection connection) - { - Log.Info($"Starting monthly Report for {meter.Name}..."); - PQReport pQReport = new PQReport(PQReportsSettings, meter, firstOfMonth, endOfMonth, connection); - byte[] pdf = pQReport.createPDF(); - Log.Info($"Completed monthly Report for {meter.Name}"); - - try - { - TableOperations to = new TableOperations(connection); - Report report = to.QueryRecordWhere("MeterID = {0} AND Month = {1} AND Year = {2}", meter.ID, firstOfMonth.Month, firstOfMonth.Year); - - if (report != null) - { - report.MeterID = meter.ID; - report.Month = firstOfMonth.Month; - report.Year = firstOfMonth.Year; - report.Results = pQReport.Result; - report.PDF = pdf; - - to.UpdateRecord(report); - } - else - { - to.AddNewRecord(new Report() - { - MeterID = meter.ID, - Month = firstOfMonth.Month, - Year = firstOfMonth.Year, - Results = pQReport.Result, - PDF = pdf - }); - } - - Log.Info($"Loaded monthly Report for {meter.Name}"); - } - catch (Exception ex) - { - Log.Error(ex.ToString(), ex); - } - } - - public string GetHelpMessage(string command) - { - StringBuilder helpMessage = new StringBuilder(); - - helpMessage.Append("Processes the month PQ Reports"); - helpMessage.AppendLine(); - helpMessage.AppendLine(); - helpMessage.Append(" Usage:"); - helpMessage.AppendLine(); - helpMessage.Append(" " + command); - helpMessage.AppendLine(); - helpMessage.Append(" " + command + " -?"); - helpMessage.AppendLine(); - helpMessage.AppendLine(); - helpMessage.Append(" Options:"); - helpMessage.AppendLine(); - helpMessage.Append(" -?".PadRight(25)); - helpMessage.Append("Displays this help message"); - - return helpMessage.ToString(); - } - - private DateTime GetPreviousMatch(Schedule schedule, DateTime now) - { - bool match = false; - - while (!match) - { - now = now.AddMinutes(-1); - - match = schedule.MinutePart.Matches(now); - match &= schedule.HourPart.Matches(now); - match &= schedule.DayPart.Matches(now); - match &= schedule.MonthPart.Matches(now); - match &= schedule.DaysOfWeekPart.Matches(now); - } - - return now; - } - - public void ProcessBreakerReportCommand() { - ProcessBreakerReports(); - } - - public byte[] ExportAllToCSV(DataTable table, DateTime fromDate, DateTime toDate) - { - if (table.Rows.Count == 0) return null; - - using (MemoryStream ms = new MemoryStream()) - using (StreamWriter writer = new StreamWriter(ms)) - { - // Write the CSV header to the file - writer.WriteLine(GetCSVHeader(table)); - - // Write data to the file - foreach (DataRow row in table.Rows) - writer.WriteLine(ToCSV(table, row)); - - return ms.ToArray(); - } - } - - // Converts the data group row of CSV data. - private string ToCSV(DataTable table, DataRow row) - { - IEnumerable columns = table.Columns.Cast().Select(x => "\"" + row[x.ColumnName] + "\""); - return string.Join(",", columns); - } - - // Converts the data group row of CSV data. - private string GetCSVHeader(DataTable table) - { - IEnumerable headers = table.Columns.Cast().Select(x => "\"" + x.ColumnName + "\""); - return string.Join(",", headers); - } - - #endregion - - #region [ Static ] - - private static readonly ILog Log = LogManager.GetLogger(typeof(ReportsEngine)); - private static readonly ConnectionStringParser ConnectionStringParser = new ConnectionStringParser(); - - #endregion - } -} diff --git a/Libraries/openXDA.Reports/openXDA.Reports.csproj b/Libraries/openXDA.Reports/openXDA.Reports.csproj deleted file mode 100644 index d16fe4a1..00000000 --- a/Libraries/openXDA.Reports/openXDA.Reports.csproj +++ /dev/null @@ -1,54 +0,0 @@ - - - - Debug - AnyCPU - net9.0-windows - Library - true - openXDA.Reports - openXDA.Reports - false - Debug;Development;Release - - - true - full - false - ..\..\..\Build\Output\Debug\Libraries\ - DEBUG;TRACE - prompt - 4 - - - true - full - false - ..\..\..\Build\Output\Debug\Libraries\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - ..\..\..\Build\Output\Release\Libraries\ - TRACE - prompt - 4 - - - - - - - - - - - ..\..\Dependencies\OpenXDA\openXDA.Model.dll - - - - - diff --git a/SEBrowser-dev.slnx b/SEBrowser-dev.slnx index d5f00332..53018827 100644 --- a/SEBrowser-dev.slnx +++ b/SEBrowser-dev.slnx @@ -5,13 +5,6 @@ - - - - - - - diff --git a/SEBrowser/SEBrowser.csproj b/SEBrowser/SEBrowser.csproj index f8710a8d..9da4f380 100644 --- a/SEBrowser/SEBrowser.csproj +++ b/SEBrowser/SEBrowser.csproj @@ -50,7 +50,9 @@ - + + ..\Dependencies\OpenXDA\openXDA.APIAuthentication.dll + ..\Dependencies\OpenXDA\FaultAlgorithms.dll @@ -60,7 +62,9 @@ ..\Dependencies\OpenXDA\openXDA.Model.dll - + + ..\Dependencies\OpenXDA\openXDA.PQI.dll + From ea1e5a76de23a760dd5f04403e7eefb4a3aef056 Mon Sep 17 00:00:00 2001 From: prestoncraw Date: Tue, 4 Aug 2026 09:26:26 -0400 Subject: [PATCH 4/4] add missing proj reference --- SEBrowser/SEBrowser.csproj | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/SEBrowser/SEBrowser.csproj b/SEBrowser/SEBrowser.csproj index 9da4f380..43a6dc5d 100644 --- a/SEBrowser/SEBrowser.csproj +++ b/SEBrowser/SEBrowser.csproj @@ -37,10 +37,15 @@ True True - - - - + + + + True + True + + + +