diff --git a/Dependencies/openHistorian/openHistorian.Core.dll b/Dependencies/openHistorian/openHistorian.Core.dll
new file mode 100644
index 0000000..8d36e8c
Binary files /dev/null and b/Dependencies/openHistorian/openHistorian.Core.dll differ
diff --git a/Libraries/openHistorian.XDALink/Historian.cs b/Libraries/openHistorian.XDALink/Historian.cs
new file mode 100644
index 0000000..a751701
--- /dev/null
+++ b/Libraries/openHistorian.XDALink/Historian.cs
@@ -0,0 +1,203 @@
+//******************************************************************************************************
+// 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
new file mode 100644
index 0000000..e6721fb
--- /dev/null
+++ b/Libraries/openHistorian.XDALink/ImportedMeasurement.cs
@@ -0,0 +1,222 @@
+//******************************************************************************************************
+// 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
new file mode 100644
index 0000000..6548742
--- /dev/null
+++ b/Libraries/openHistorian.XDALink/TrendingDataPoint.cs
@@ -0,0 +1,92 @@
+//******************************************************************************************************
+// 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
new file mode 100644
index 0000000..78a85a2
--- /dev/null
+++ b/Libraries/openHistorian.XDALink/openHistorian.XDALink.csproj
@@ -0,0 +1,21 @@
+
+
+
+ net9.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+ ..\..\Dependencies\openHistorian\openHistorian.Core.dll
+
+
+
+
diff --git a/Libraries/openXDA.APIAuthentication/APIQuery.cs b/Libraries/openXDA.APIAuthentication/APIQuery.cs
new file mode 100644
index 0000000..58f8643
--- /dev/null
+++ b/Libraries/openXDA.APIAuthentication/APIQuery.cs
@@ -0,0 +1,311 @@
+//******************************************************************************************************
+// 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
new file mode 100644
index 0000000..3b4d8b0
--- /dev/null
+++ b/Libraries/openXDA.APIAuthentication/IAPICredentialRetriever.cs
@@ -0,0 +1,65 @@
+//******************************************************************************************************
+// 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
new file mode 100644
index 0000000..3305774
--- /dev/null
+++ b/Libraries/openXDA.APIAuthentication/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+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
new file mode 100644
index 0000000..46315a8
--- /dev/null
+++ b/Libraries/openXDA.APIAuthentication/XDAAPI.cs
@@ -0,0 +1,232 @@
+//******************************************************************************************************
+// 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
new file mode 100644
index 0000000..ace01df
--- /dev/null
+++ b/Libraries/openXDA.APIAuthentication/XDAAPIHelper.cs
@@ -0,0 +1,149 @@
+//******************************************************************************************************
+// 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
new file mode 100644
index 0000000..871f63c
--- /dev/null
+++ b/Libraries/openXDA.APIAuthentication/openXDA.APIAuthentication.csproj
@@ -0,0 +1,15 @@
+
+
+
+ net9.0
+ false
+
+
+
+
+
+
+
+
+
+
diff --git a/Libraries/openXDA.Configuration/BreakerSection.cs b/Libraries/openXDA.Configuration/BreakerSection.cs
new file mode 100644
index 0000000..d6d07ff
--- /dev/null
+++ b/Libraries/openXDA.Configuration/BreakerSection.cs
@@ -0,0 +1,119 @@
+//******************************************************************************************************
+// 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
new file mode 100644
index 0000000..e457f27
--- /dev/null
+++ b/Libraries/openXDA.Configuration/COMTRADESection.cs
@@ -0,0 +1,67 @@
+//******************************************************************************************************
+// 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
new file mode 100644
index 0000000..d62142e
--- /dev/null
+++ b/Libraries/openXDA.Configuration/DataAnalysisSection.cs
@@ -0,0 +1,120 @@
+//******************************************************************************************************
+// 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
new file mode 100644
index 0000000..78cd591
--- /dev/null
+++ b/Libraries/openXDA.Configuration/DataPusherSection.cs
@@ -0,0 +1,37 @@
+//******************************************************************************************************
+// 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
new file mode 100644
index 0000000..22ba7df
--- /dev/null
+++ b/Libraries/openXDA.Configuration/EMAXSection.cs
@@ -0,0 +1,66 @@
+//******************************************************************************************************
+// 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
new file mode 100644
index 0000000..41d0f1d
--- /dev/null
+++ b/Libraries/openXDA.Configuration/EPRICapBankAnalyticSection.cs
@@ -0,0 +1,89 @@
+//******************************************************************************************************
+// 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
new file mode 100644
index 0000000..2d4d7d7
--- /dev/null
+++ b/Libraries/openXDA.Configuration/Edition/EditionChecker.cs
@@ -0,0 +1,98 @@
+//******************************************************************************************************
+// 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
new file mode 100644
index 0000000..79368e2
--- /dev/null
+++ b/Libraries/openXDA.Configuration/Edition/HttpEditionFilterAttribute.cs
@@ -0,0 +1,57 @@
+//******************************************************************************************************
+// 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
new file mode 100644
index 0000000..c092607
--- /dev/null
+++ b/Libraries/openXDA.Configuration/EmailSection.cs
@@ -0,0 +1,111 @@
+//******************************************************************************************************
+// 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
new file mode 100644
index 0000000..64f4879
--- /dev/null
+++ b/Libraries/openXDA.Configuration/EventEmailSection.cs
@@ -0,0 +1,68 @@
+//******************************************************************************************************
+// 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
new file mode 100644
index 0000000..a9e6e72
--- /dev/null
+++ b/Libraries/openXDA.Configuration/FaultLocationSection.cs
@@ -0,0 +1,104 @@
+//******************************************************************************************************
+// 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
new file mode 100644
index 0000000..0c403c3
--- /dev/null
+++ b/Libraries/openXDA.Configuration/FileEnumeratorSection.cs
@@ -0,0 +1,80 @@
+//******************************************************************************************************
+// 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
new file mode 100644
index 0000000..2d89b5a
--- /dev/null
+++ b/Libraries/openXDA.Configuration/FileProcessorSection.cs
@@ -0,0 +1,91 @@
+//******************************************************************************************************
+// 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
new file mode 100644
index 0000000..2c9a4c2
--- /dev/null
+++ b/Libraries/openXDA.Configuration/FilePrunerSection.cs
@@ -0,0 +1,58 @@
+//******************************************************************************************************
+// 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
new file mode 100644
index 0000000..4b25e6c
--- /dev/null
+++ b/Libraries/openXDA.Configuration/FileWatcherSection.cs
@@ -0,0 +1,308 @@
+//******************************************************************************************************
+// 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
new file mode 100644
index 0000000..a6d0bc3
--- /dev/null
+++ b/Libraries/openXDA.Configuration/GrafanaSection.cs
@@ -0,0 +1,72 @@
+//******************************************************************************************************
+// 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
new file mode 100644
index 0000000..3cd00c0
--- /dev/null
+++ b/Libraries/openXDA.Configuration/LSCVSSection.cs
@@ -0,0 +1,68 @@
+//******************************************************************************************************
+// 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
new file mode 100644
index 0000000..b5d8405
--- /dev/null
+++ b/Libraries/openXDA.Configuration/OSIPISection.cs
@@ -0,0 +1,71 @@
+//******************************************************************************************************
+// 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
new file mode 100644
index 0000000..670a5fd
--- /dev/null
+++ b/Libraries/openXDA.Configuration/PQDIFSection.cs
@@ -0,0 +1,46 @@
+//******************************************************************************************************
+// 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
new file mode 100644
index 0000000..bbdeb1f
--- /dev/null
+++ b/Libraries/openXDA.Configuration/PQISection.cs
@@ -0,0 +1,66 @@
+//******************************************************************************************************
+// 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
new file mode 100644
index 0000000..0666cda
--- /dev/null
+++ b/Libraries/openXDA.Configuration/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+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
new file mode 100644
index 0000000..12e6900
--- /dev/null
+++ b/Libraries/openXDA.Configuration/RabbitMQSection.cs
@@ -0,0 +1,90 @@
+//******************************************************************************************************
+// 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
new file mode 100644
index 0000000..acb47c9
--- /dev/null
+++ b/Libraries/openXDA.Configuration/SCADASection.cs
@@ -0,0 +1,94 @@
+//******************************************************************************************************
+// 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
new file mode 100644
index 0000000..dff7379
--- /dev/null
+++ b/Libraries/openXDA.Configuration/SSAMSSection.cs
@@ -0,0 +1,73 @@
+//******************************************************************************************************
+// 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
new file mode 100644
index 0000000..eac8f87
--- /dev/null
+++ b/Libraries/openXDA.Configuration/Subscription.cs
@@ -0,0 +1,65 @@
+//******************************************************************************************************
+// 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
new file mode 100644
index 0000000..4649edf
--- /dev/null
+++ b/Libraries/openXDA.Configuration/SystemSection.cs
@@ -0,0 +1,109 @@
+//******************************************************************************************************
+// 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
new file mode 100644
index 0000000..e42b01f
--- /dev/null
+++ b/Libraries/openXDA.Configuration/TaskProcessorSection.cs
@@ -0,0 +1,68 @@
+//******************************************************************************************************
+// 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
new file mode 100644
index 0000000..389daad
--- /dev/null
+++ b/Libraries/openXDA.Configuration/TrendingDataSection.cs
@@ -0,0 +1,122 @@
+//******************************************************************************************************
+// 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
new file mode 100644
index 0000000..e5c3b0e
--- /dev/null
+++ b/Libraries/openXDA.Configuration/openXDA.Configuration.csproj
@@ -0,0 +1,21 @@
+
+
+
+ net9.0
+ enable
+ enable
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Libraries/openXDA.Model/Channels/Channel.cs b/Libraries/openXDA.Model/Channels/Channel.cs
index 491748e..9d65c5b 100644
--- a/Libraries/openXDA.Model/Channels/Channel.cs
+++ b/Libraries/openXDA.Model/Channels/Channel.cs
@@ -21,13 +21,14 @@
//
//******************************************************************************************************
+using Gemstone.Data;
+using Gemstone.Data.Model;
+using Newtonsoft.Json;
+
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Data;
using System.Transactions;
-using Gemstone.Data;
-using Gemstone.Data.Model;
-using Newtonsoft.Json;
using IsolationLevel = System.Transactions.IsolationLevel;
namespace openXDA.Model
@@ -476,6 +477,54 @@ public int GetHashCode(Channel obj)
}
}
+ [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)]
@@ -577,4 +626,4 @@ public static UserDashSettings GetOrAdd(this TableOperations t
}
-}
\ No newline at end of file
+}
diff --git a/Libraries/openXDA.Model/Channels/ChannelGroup.cs b/Libraries/openXDA.Model/Channels/ChannelGroup.cs
new file mode 100644
index 0000000..b5b463c
--- /dev/null
+++ b/Libraries/openXDA.Model/Channels/ChannelGroup.cs
@@ -0,0 +1,43 @@
+//******************************************************************************************************
+// 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/DERAnalytic/DERAnalyticResult.cs b/Libraries/openXDA.Model/DERAnalytic/DERAnalyticResult.cs
new file mode 100644
index 0000000..c667215
--- /dev/null
+++ b/Libraries/openXDA.Model/DERAnalytic/DERAnalyticResult.cs
@@ -0,0 +1,54 @@
+//******************************************************************************************************
+// 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/EventType.cs b/Libraries/openXDA.Model/Events/EventType.cs
new file mode 100644
index 0000000..3d5a0c1
--- /dev/null
+++ b/Libraries/openXDA.Model/Events/EventType.cs
@@ -0,0 +1,42 @@
+//******************************************************************************************************
+// 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/Meters/AssetGroup.cs b/Libraries/openXDA.Model/Meters/AssetGroup.cs
new file mode 100644
index 0000000..9848035
--- /dev/null
+++ b/Libraries/openXDA.Model/Meters/AssetGroup.cs
@@ -0,0 +1,60 @@
+//******************************************************************************************************
+// 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/Note.cs b/Libraries/openXDA.Model/Note.cs
new file mode 100644
index 0000000..dd2407c
--- /dev/null
+++ b/Libraries/openXDA.Model/Note.cs
@@ -0,0 +1,63 @@
+//******************************************************************************************************
+// 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/Reports/Report.cs b/Libraries/openXDA.Model/Reports/Report.cs
new file mode 100644
index 0000000..a4b4dea
--- /dev/null
+++ b/Libraries/openXDA.Model/Reports/Report.cs
@@ -0,0 +1,46 @@
+//******************************************************************************************************
+// 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
new file mode 100644
index 0000000..b328042
--- /dev/null
+++ b/Libraries/openXDA.Model/SEBrowser/DetailedSeries.cs
@@ -0,0 +1,37 @@
+//******************************************************************************************************
+// 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
new file mode 100644
index 0000000..6b084f6
--- /dev/null
+++ b/Libraries/openXDA.Model/SEBrowser/TrendChannel.cs
@@ -0,0 +1,48 @@
+//******************************************************************************************************
+// 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/SearchRestrictionHelper.cs b/Libraries/openXDA.Model/SearchRestrictionHelper.cs
new file mode 100644
index 0000000..ca740df
--- /dev/null
+++ b/Libraries/openXDA.Model/SearchRestrictionHelper.cs
@@ -0,0 +1,55 @@
+//******************************************************************************************************
+// 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