diff --git a/Samples/DotNetSdk/DotNetSdkExamples.sln b/Samples/DotNetSdk/DotNetSdkExamples.sln index e2b405d..a77f493 100644 --- a/Samples/DotNetSdk/DotNetSdkExamples.sln +++ b/Samples/DotNetSdk/DotNetSdkExamples.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29403.142 +# Visual Studio Version 17 +VisualStudioVersion = 17.3.32929.385 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ObservationValidator", "ObservationValidator\ObservationValidator.csproj", "{4BB8F57C-DD37-4401-85F7-C1B4D97F36C7}" EndProject @@ -22,6 +22,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NWFWMD-LabFileImporter", "N EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ObservationReportExporter", "ObservationReportExporter\ObservationReportExporter.csproj", "{8D25D462-6D50-48A1-A5E4-05116EC7B51F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReflectedSeriesAggregator", "ReflectedSeriesAggregator\ReflectedSeriesAggregator.csproj", "{E86145A8-0713-4CF3-9EC2-A955B58C6A3B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -56,6 +58,10 @@ Global {8D25D462-6D50-48A1-A5E4-05116EC7B51F}.Debug|Any CPU.Build.0 = Debug|Any CPU {8D25D462-6D50-48A1-A5E4-05116EC7B51F}.Release|Any CPU.ActiveCfg = Release|Any CPU {8D25D462-6D50-48A1-A5E4-05116EC7B51F}.Release|Any CPU.Build.0 = Release|Any CPU + {E86145A8-0713-4CF3-9EC2-A955B58C6A3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E86145A8-0713-4CF3-9EC2-A955B58C6A3B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E86145A8-0713-4CF3-9EC2-A955B58C6A3B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E86145A8-0713-4CF3-9EC2-A955B58C6A3B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Samples/DotNetSdk/ReflectedSeriesAggregator/App.config b/Samples/DotNetSdk/ReflectedSeriesAggregator/App.config new file mode 100644 index 0000000..85ef8c8 --- /dev/null +++ b/Samples/DotNetSdk/ReflectedSeriesAggregator/App.config @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Samples/DotNetSdk/ReflectedSeriesAggregator/AquariusClientHelperExt.cs b/Samples/DotNetSdk/ReflectedSeriesAggregator/AquariusClientHelperExt.cs new file mode 100644 index 0000000..18509c6 --- /dev/null +++ b/Samples/DotNetSdk/ReflectedSeriesAggregator/AquariusClientHelperExt.cs @@ -0,0 +1,180 @@ +using Aquarius.TimeSeries.Client; +using Aquarius.TimeSeries.Client.ServiceModels.Provisioning; +using Aquarius.TimeSeries.Client.ServiceModels.Publish; +using NodaTime; +using System; +using System.Collections.Generic; +using System.Linq; +using MonitoringMethod = Aquarius.TimeSeries.Client.ServiceModels.Provisioning.MonitoringMethod; + +namespace ReflectedSeriesAggregator +{ + internal static class AquariusClientHelperExt + { + public static readonly Duration NoGaps = Duration.FromTicks(long.MaxValue); + + public class GetOrCreateTimeSeriesResponse + { + public bool IsNew { get; set; } + public Guid UniqueId { get; set; } + public string SeriesIdentifier { get; set; } + } + + static public GetOrCreateTimeSeriesResponse GetOrCreateTimeSeries(this IAquariusClient client, + LocationDescription location, + Parameter parameter, + string label, + string methodCode, + bool publish, + Duration gapTolerance, + SeriesCreateType seriesCreateType = SeriesCreateType.Reflected, + Dictionary extendedAttributes = null) + { + var timeSeriesDescriptions = GetTimeSeriesDescriptions(client, location.Identifier, parameter.Identifier); + + var timeSeriesDescription = timeSeriesDescriptions.FirstOrDefault(ts => ts.Label == label); + if (timeSeriesDescription != null) + return new GetOrCreateTimeSeriesResponse { IsNew = false, UniqueId = timeSeriesDescription.UniqueId, SeriesIdentifier = timeSeriesDescription.Identifier }; + + Dictionary remappedExtendedAttibutesByUniqueId = new Dictionary(); + if (extendedAttributes != null) + { + var timeseriesExtendedAttributes = new GetExtendedAttributes() + { + Applicability = new List() + { + ExtendedAttributeApplicability.AppliesToTimeSeries + } + }; + + // Remap extended attributes + var aqExtendedAttributes = client.Provisioning.Get(timeseriesExtendedAttributes).Results; + foreach (var userExtendedAttribute in extendedAttributes) + { + var aqExtendedAttribute = aqExtendedAttributes.Where(a => a.Key.Equals(userExtendedAttribute.Key, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); + if (aqExtendedAttribute != null) + remappedExtendedAttibutesByUniqueId.Add(aqExtendedAttribute.UniqueId.ToString("N"), userExtendedAttribute.Value); + } + } + + var timeSeries = seriesCreateType == SeriesCreateType.Basic + ? client.CreateBasicTimeSeries(location.UniqueId, label, parameter, Offset.FromMilliseconds((int)location.UtcOffset * 60 * 60 * 1000), methodCode, publish, gapTolerance, remappedExtendedAttibutesByUniqueId) + : client.CreateReflectedTimeSeries(location.UniqueId, label, parameter, Offset.FromMilliseconds((int)location.UtcOffset * 60 * 60 * 1000), methodCode, publish, gapTolerance, remappedExtendedAttibutesByUniqueId); + + return new GetOrCreateTimeSeriesResponse { IsNew = true, UniqueId = timeSeries.UniqueId, SeriesIdentifier = timeSeries.Identifier }; + } + + private static Aquarius.TimeSeries.Client.ServiceModels.Provisioning.TimeSeries CreateReflectedTimeSeries( + this IAquariusClient client, + Guid locationUniqueId, + string label, + Parameter parameter, + Offset utcOffset, + string methodCode, + bool publish, + Duration gapTolerance, + Dictionary extendedAttributes) + { + return client.Provisioning.Post( + new PostReflectedTimeSeries + { + LocationUniqueId = locationUniqueId, + Parameter = parameter.ParameterId, + Label = label, + Unit = parameter.UnitIdentifier, + InterpolationType = parameter.InterpolationType, + Method = methodCode, + UtcOffset = utcOffset, + Publish = publish, + GapTolerance = gapTolerance, + ExtendedAttributeValues = extendedAttributes?.Select( + x => new ExtendedAttributeValue { UniqueId = x.Key, Value = x.Value }).ToList() + }); + } + + + private static Aquarius.TimeSeries.Client.ServiceModels.Provisioning.TimeSeries CreateBasicTimeSeries( + this IAquariusClient client, + Guid locationUniqueId, + string label, + Parameter parameter, + Offset utcOffset, + string methodCode, + bool publish, + Duration gapTolerance, + Dictionary extendedAttributes) + { + return client.Provisioning.Post( + new PostBasicTimeSeries + { + LocationUniqueId = locationUniqueId, + Parameter = parameter.ParameterId, + Label = label, + Unit = parameter.UnitIdentifier, + InterpolationType = parameter.InterpolationType, + Method = methodCode, + Publish = publish, + UtcOffset = utcOffset, + GapTolerance = gapTolerance, + ExtendedAttributeValues = extendedAttributes?.Select( + x => new ExtendedAttributeValue { UniqueId = x.Key, Value = x.Value }).ToList() + }); + } + + public static List GetLocationDescriptions(this IAquariusClient client, List tags) => + (client.Publish.Get(new LocationDescriptionListServiceRequest + { + TagKeys = tags, + })).LocationDescriptions; + + + public static List GetLocationDescriptions(this IAquariusClient client, string tag = null) => GetLocationDescriptions(client, new List { tag }); + + public static List GetTimeSeriesDescriptions(this IAquariusClient client, string locationIdentifier, + string parameter = null) => + client.Publish.Get(new TimeSeriesDescriptionServiceRequest + { + LocationIdentifier = locationIdentifier, + Parameter = parameter + }).TimeSeriesDescriptions; + + public static List GetTimeSeriesDescriptionsByTag(this IAquariusClient client, string tag) + { + List timeSeriesDescriptions = new List(); + var locations = client.GetLocationDescriptions(tag); + foreach (var location in locations) + timeSeriesDescriptions.AddRange(client.GetTimeSeriesDescriptions(location.Identifier)); + + return timeSeriesDescriptions; + } + + public static TimeSeriesDescription GetTimeSeries(this IAquariusClient client, Guid timeSeriesUniqueId) + { + var ts = client.Provisioning.Get(new GetTimeSeries + { + TimeSeriesUniqueId = timeSeriesUniqueId + }); + + return GetTimeSeriesDescriptions(client, ts.LocationIdentifier, ts.Parameter).FirstOrDefault(tsd => tsd.Label == ts.Label); + } + + private static TimeSeriesType ParseTimeSeriesType(TimeSeriesDescription timeSeriesDescription) + { + return + (TimeSeriesType) + Enum.Parse(typeof(TimeSeriesType), timeSeriesDescription.TimeSeriesType, true); + } + + public static List GetParameters(this IAquariusClient client) => + (client.Provisioning.Get(new GetParameters())).Results; + + public static List GetMonitoringMethods(this IAquariusClient client) => client.Provisioning.Get( + new GetMonitoringMethods()).Results; + } + + public enum SeriesCreateType + { + Basic, + Reflected + } +} diff --git a/Samples/DotNetSdk/ReflectedSeriesAggregator/Encryption.cs b/Samples/DotNetSdk/ReflectedSeriesAggregator/Encryption.cs new file mode 100644 index 0000000..9e6949f --- /dev/null +++ b/Samples/DotNetSdk/ReflectedSeriesAggregator/Encryption.cs @@ -0,0 +1,77 @@ +using Microsoft.Win32; +using System; +using System.CodeDom; +using System.Collections.Generic; +using System.Linq; +using System.Security; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace ReflectedSeriesAggregator +{ + internal class Encryption + { + private static string SecurityKey = ""; // Add your unique security per customer. + + public static string EncryptPlainTextToCipherText(string PlainText) + { + if (string.IsNullOrWhiteSpace(SecurityKey)) + return PlainText; + + // Getting the bytes of Input String. + byte[] toEncryptedArray = UTF8Encoding.UTF8.GetBytes(PlainText); + + MD5CryptoServiceProvider objMD5CryptoService = new MD5CryptoServiceProvider(); + //Gettting the bytes from the Security Key and Passing it to compute the Corresponding Hash Value. + byte[] securityKeyArray = objMD5CryptoService.ComputeHash(UTF8Encoding.UTF8.GetBytes(SecurityKey)); + //De-allocatinng the memory after doing the Job. + objMD5CryptoService.Clear(); + + var objTripleDESCryptoService = new TripleDESCryptoServiceProvider(); + //Assigning the Security key to the TripleDES Service Provider. + objTripleDESCryptoService.Key = securityKeyArray; + //Mode of the Crypto service is Electronic Code Book. + objTripleDESCryptoService.Mode = CipherMode.ECB; + //Padding Mode is PKCS7 if there is any extra byte is added. + objTripleDESCryptoService.Padding = PaddingMode.PKCS7; + + + var objCrytpoTransform = objTripleDESCryptoService.CreateEncryptor(); + //Transform the bytes array to resultArray + byte[] resultArray = objCrytpoTransform.TransformFinalBlock(toEncryptedArray, 0, toEncryptedArray.Length); + objTripleDESCryptoService.Clear(); + return Convert.ToBase64String(resultArray, 0, resultArray.Length); + } + + //This method is used to convert the Encrypted/Un-Readable Text back to readable format. + public static string DecryptCipherTextToPlainText(string CipherText) + { + if (string.IsNullOrWhiteSpace(SecurityKey)) + return CipherText; + + byte[] toEncryptArray = Convert.FromBase64String(CipherText); + MD5CryptoServiceProvider objMD5CryptoService = new MD5CryptoServiceProvider(); + + //Gettting the bytes from the Security Key and Passing it to compute the Corresponding Hash Value. + byte[] securityKeyArray = objMD5CryptoService.ComputeHash(UTF8Encoding.UTF8.GetBytes(SecurityKey)); + objMD5CryptoService.Clear(); + + var objTripleDESCryptoService = new TripleDESCryptoServiceProvider(); + //Assigning the Security key to the TripleDES Service Provider. + objTripleDESCryptoService.Key = securityKeyArray; + //Mode of the Crypto service is Electronic Code Book. + objTripleDESCryptoService.Mode = CipherMode.ECB; + //Padding Mode is PKCS7 if there is any extra byte is added. + objTripleDESCryptoService.Padding = PaddingMode.PKCS7; + + var objCrytpoTransform = objTripleDESCryptoService.CreateDecryptor(); + //Transform the bytes array to resultArray + byte[] resultArray = objCrytpoTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length); + objTripleDESCryptoService.Clear(); + + //Convert and return the decrypted data/byte into string format. + return UTF8Encoding.UTF8.GetString(resultArray); + } + } +} diff --git a/Samples/DotNetSdk/ReflectedSeriesAggregator/Program.cs b/Samples/DotNetSdk/ReflectedSeriesAggregator/Program.cs new file mode 100644 index 0000000..048aeac --- /dev/null +++ b/Samples/DotNetSdk/ReflectedSeriesAggregator/Program.cs @@ -0,0 +1,41 @@ +using Microsoft.Extensions.Configuration; +using Serilog; +using System; +using System.IO; + +namespace ReflectedSeriesAggregator +{ + internal class Program + { + static void Main(string[] args) + { + // Read app settings + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json"); + var configuration = builder.Build(); + + // Configure & create serilog logger using above configuration + using (var logger = new LoggerConfiguration() + .ReadFrom.Configuration(configuration) + .CreateLogger()) + { + try + { + // Parse command line + if (!ArgHandler.Parse(args, logger, out Settings settings)) + Environment.Exit(0); + + // Do some work + new Work(logger, settings).Run(); + Environment.Exit(0); + } + catch (Exception ex) + { + logger.Fatal(ex.Message, ex); + Environment.Exit(-1); + } + } + } + } +} diff --git a/Samples/DotNetSdk/ReflectedSeriesAggregator/Properties/AssemblyInfo.cs b/Samples/DotNetSdk/ReflectedSeriesAggregator/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c1403d5 --- /dev/null +++ b/Samples/DotNetSdk/ReflectedSeriesAggregator/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("ReflectedSeriesAggregator")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ReflectedSeriesAggregator")] +[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("e86145a8-0713-4cf3-9ec2-a955b58c6a3b")] + +// 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("1.0.*")] +[assembly: AssemblyVersion("1.1.1.0")] +[assembly: AssemblyFileVersion("1.1.1.0")] diff --git a/Samples/DotNetSdk/ReflectedSeriesAggregator/ReflectedSeriesAggregator.csproj b/Samples/DotNetSdk/ReflectedSeriesAggregator/ReflectedSeriesAggregator.csproj new file mode 100644 index 0000000..60ce778 --- /dev/null +++ b/Samples/DotNetSdk/ReflectedSeriesAggregator/ReflectedSeriesAggregator.csproj @@ -0,0 +1,256 @@ + + + + + Debug + AnyCPU + {E86145A8-0713-4CF3-9EC2-A955B58C6A3B} + Exe + ReflectedSeriesAggregator + ReflectedSeriesAggregator + v4.8 + 512 + true + true + + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Aquarius.SDK.21.4.2\lib\net472\Aquarius.Client.dll + + + ..\packages\Microsoft.Bcl.AsyncInterfaces.6.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll + + + ..\packages\Microsoft.Extensions.Configuration.6.0.0\lib\net461\Microsoft.Extensions.Configuration.dll + + + ..\packages\Microsoft.Extensions.Configuration.Abstractions.6.0.0\lib\net461\Microsoft.Extensions.Configuration.Abstractions.dll + + + ..\packages\Microsoft.Extensions.Configuration.Binder.6.0.0\lib\net461\Microsoft.Extensions.Configuration.Binder.dll + + + ..\packages\Microsoft.Extensions.Configuration.FileExtensions.6.0.0\lib\net461\Microsoft.Extensions.Configuration.FileExtensions.dll + + + ..\packages\Microsoft.Extensions.Configuration.Json.6.0.0\lib\net461\Microsoft.Extensions.Configuration.Json.dll + + + ..\packages\Microsoft.Extensions.DependencyInjection.6.0.0\lib\net461\Microsoft.Extensions.DependencyInjection.dll + + + ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.6.0.0\lib\net461\Microsoft.Extensions.DependencyInjection.Abstractions.dll + + + ..\packages\Microsoft.Extensions.DependencyModel.3.0.0\lib\net451\Microsoft.Extensions.DependencyModel.dll + + + ..\packages\Microsoft.Extensions.FileProviders.Abstractions.6.0.0\lib\net461\Microsoft.Extensions.FileProviders.Abstractions.dll + + + ..\packages\Microsoft.Extensions.FileProviders.Physical.6.0.0\lib\net461\Microsoft.Extensions.FileProviders.Physical.dll + + + ..\packages\Microsoft.Extensions.FileSystemGlobbing.6.0.0\lib\net461\Microsoft.Extensions.FileSystemGlobbing.dll + + + ..\packages\Microsoft.Extensions.Hosting.Abstractions.3.1.8\lib\netstandard2.0\Microsoft.Extensions.Hosting.Abstractions.dll + + + ..\packages\Microsoft.Extensions.Logging.6.0.0\lib\net461\Microsoft.Extensions.Logging.dll + + + ..\packages\Microsoft.Extensions.Logging.Abstractions.6.0.0\lib\net461\Microsoft.Extensions.Logging.Abstractions.dll + + + ..\packages\Microsoft.Extensions.Logging.Configuration.6.0.0\lib\net461\Microsoft.Extensions.Logging.Configuration.dll + + + ..\packages\Microsoft.Extensions.Logging.Console.6.0.0\lib\net461\Microsoft.Extensions.Logging.Console.dll + + + ..\packages\Microsoft.Extensions.Options.6.0.0\lib\net461\Microsoft.Extensions.Options.dll + + + ..\packages\Microsoft.Extensions.Options.ConfigurationExtensions.6.0.0\lib\net461\Microsoft.Extensions.Options.ConfigurationExtensions.dll + + + ..\packages\Microsoft.Extensions.Primitives.6.0.0\lib\net461\Microsoft.Extensions.Primitives.dll + + + ..\packages\Mono.Options.6.12.0.148\lib\net40\Mono.Options.dll + + + ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll + + + ..\packages\NodaTime.1.3.0\lib\net35-Client\NodaTime.dll + + + ..\packages\Serilog.2.10.0\lib\net46\Serilog.dll + + + ..\packages\Serilog.Extensions.Hosting.5.0.1\lib\netstandard2.0\Serilog.Extensions.Hosting.dll + + + ..\packages\Serilog.Extensions.Logging.3.1.0\lib\netstandard2.0\Serilog.Extensions.Logging.dll + + + ..\packages\Serilog.Settings.Configuration.3.4.0\lib\net461\Serilog.Settings.Configuration.dll + + + ..\packages\Serilog.Sinks.Console.4.1.0\lib\net45\Serilog.Sinks.Console.dll + + + ..\packages\Serilog.Sinks.File.5.0.0\lib\net45\Serilog.Sinks.File.dll + + + ..\packages\ServiceStack.Client.5.10.4\lib\net45\ServiceStack.Client.dll + + + ..\packages\ServiceStack.HttpClient.5.10.4\lib\net45\ServiceStack.HttpClient.dll + + + ..\packages\ServiceStack.Interfaces.5.10.4\lib\net472\ServiceStack.Interfaces.dll + + + ..\packages\ServiceStack.Text.5.10.4\lib\net45\ServiceStack.Text.dll + + + + ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll + + + + + + + ..\packages\System.Diagnostics.DiagnosticSource.6.0.0\lib\net461\System.Diagnostics.DiagnosticSource.dll + + + ..\packages\System.IO.4.3.0\lib\net462\System.IO.dll + True + True + + + ..\packages\System.Memory.4.5.4\lib\net461\System.Memory.dll + + + + ..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Extensions.dll + + + ..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Primitives.dll + + + + + ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll + + + ..\packages\System.Runtime.4.3.0\lib\net462\System.Runtime.dll + True + True + + + ..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll + + + ..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll + True + True + + + ..\packages\System.Security.Cryptography.Algorithms.4.3.1\lib\net463\System.Security.Cryptography.Algorithms.dll + True + True + + + ..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll + True + True + + + ..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll + True + True + + + + ..\packages\System.Text.Encodings.Web.6.0.0\lib\net461\System.Text.Encodings.Web.dll + + + ..\packages\System.Text.Json.6.0.0\lib\net461\System.Text.Json.dll + + + ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll + + + ..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll + + + + + + + + + + + + + + + + + + + + + + + + Always + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + \ No newline at end of file diff --git a/Samples/DotNetSdk/ReflectedSeriesAggregator/SampleSettings/Settings.txt b/Samples/DotNetSdk/ReflectedSeriesAggregator/SampleSettings/Settings.txt new file mode 100644 index 0000000..7484269 --- /dev/null +++ b/Samples/DotNetSdk/ReflectedSeriesAggregator/SampleSettings/Settings.txt @@ -0,0 +1,47 @@ +# Credentials for an account with permissions to append points and create missing timeseries (CanReadData, CanAddData, CanEditLocationDetails) +-Server= +-Username= +-Password= + +# If set to true data aggregation is performed. Otherwise, false to only list the aggregates. +-Aggregate=false + +# List of tags which describe the tags to search for +# Each tag is assumed to be ValueType=None and AppliesToLocations=true +# Each -Tags= option can be -Tags={tagname} or -Tags={tagname}@{locationIdentifier} +# If no @{locationIdentifier} is specified, the {tagname} is assumed to be a locationIdentifier. +# If no tags are specified, nothing will be done +-Tags=Horsham@HORSHAM +-Tags=Ararat@ARARAT + +# Options to filter the reflected series from AQSamples that will be aggregated. +# Filtering by labels and parameterIDs is possible, to further reduce the aggregated series. + +# Filter by parameterID. If no -ParameterIds= options are set, all series will be aggregated by Parameter +#-ParameterIds=Alkalinity +#-ParameterIds=Boron +#-ParameterIds=FC +#-ParameterIds=Free Chlorine + +# Filter by label. If no -Labels= options are set, all AQSamples reflected series will be aggregated by Parameter and label. +# To aggregate multiple labels together -Labels=label1|label2|label3 +-Labels=LabData|FieldData + +# The aggregation label to use for aggregated series. +-AggrSeriesLabel_Single={Label} Aggregated +-AggrSeriesLabel_Multi=Aggregated + +# How to handle duplicate timestamps in the aggregated points. +# When duplicate point timestamps are encountered, always log an ERROR message showing the timestamp, source series identifiers, and source values. +# Defaults to false +# If false, don't add any point values at that ambiguous timestamp. +# If true, add a second to each of the duplicate points to make them distinct. +-AdjustDuplicateTimestamps=true + +# Publish the created aggregate time-series to AQWebPortal. +# Will be ignored if the time-series location has its publish set to false. +-Publish=false + + + + diff --git a/Samples/DotNetSdk/ReflectedSeriesAggregator/Settings.cs b/Samples/DotNetSdk/ReflectedSeriesAggregator/Settings.cs new file mode 100644 index 0000000..a10af7d --- /dev/null +++ b/Samples/DotNetSdk/ReflectedSeriesAggregator/Settings.cs @@ -0,0 +1,202 @@ +using Mono.Options; +using Serilog; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace ReflectedSeriesAggregator +{ + public enum ReadTimeSeriesFromType + { + SourceSOR, + TargetEOR, + TimeStamp + }; + + public class Settings + { + public string Server { get; set; } + public string Username { get; set; } + public string Password { get; set; } + public List Tags { get; set; } = new List(); + public string AggrSeriesLabelSingle { get; set; } + public string AggrSeriesLabelMulti { get; set; } + public List Labels { get; set; } = new List(); + public List ParameterIds { get; set; } = new List(); + public bool AdjustDuplicateTimestamps { get; set; } + public bool ShouldShowHelp { get; set; } = false; + public bool Publish { get; set; } = false; + public string ReadTimeSeriesFrom { get; set; } = string.Empty; + public bool Aggregate { get; set; } + } + + public static class ArgHandler + { + static OptionSet GetOptionSet(Settings settings) + { + return new OptionSet { + { "SetPassword=", "Set a new password for API access. Replaces -Password settings in @files. Can only be used in a command line argument and not an @file.", p => { /*dummy placeholder */} }, + { "Server=", "AQ TS host name or ip address.", h => settings.Server = h }, + { "Username=", "AQ TS username for API access.", u => settings.Username = u }, + { "Password=", "AQ TS password for API access.", p => { + try + { + settings.Password = Encryption.DecryptCipherTextToPlainText(p); + } + catch(Exception ex) + { + throw new Exception("An encrypted password is required. Please provide -SetPassword in the command line (and not the file)", ex); + }} + }, + { "Tags=", "List of tags which describe the tags to search for.", + t => { + if( string.IsNullOrWhiteSpace(t)) + throw new Exception("Tags= setting must be set with a value"); + + if( settings.Tags.Contains(t)) + throw new Exception($"Tags {t} already specified"); + settings.Tags.Add(t); + } + }, + { "AggrSeriesLabel_Single=", "The aggregation label to use for every aggregated series. Defaults to \"Aggregated\".", l => settings.AggrSeriesLabelSingle = l }, + { "AggrSeriesLabel_Multi=", "The aggregation label to use for every aggregated series. Defaults to \"Aggregated\".", l => settings.AggrSeriesLabelMulti = l }, + { "Labels=", "Filter by label. If no -Labels= options are set, all AQSamples reflected series will be aggregated.", + l => { + if( string.IsNullOrWhiteSpace(l)) + throw new Exception("Label= setting must be set with a value."); + + if( settings.Labels.Contains(l)) + throw new Exception($"Label {l} already specified"); + settings.Labels.Add(l); + } + }, + { "ParameterIds=", "Filter by parameterID. If no -ParameterIds= options are set, all series will be aggregated.", t => settings.ParameterIds.Add(t) }, + { "Publish=", "Set publish flag on newly created time series. The locations publish must also be set to true", p => settings.Publish = bool.Parse(p) }, + { "ReadTimeSeriesFrom=", "AQ TS password for API access.", r => settings.ReadTimeSeriesFrom = r }, + { "AdjustDuplicateTimestamps=", "How to handle duplicate timestamps in the aggregated points. When duplicate point timestamps are encountered, always log an ERROR message showing the timestamp, source series identifiers, and source values. Defaults to false. If false, don't add any point values at that ambiguous timestamp.If true, add a second to each of the duplicate points to make them distinct.", a => settings.AdjustDuplicateTimestamps = bool.Parse(a) }, + { "Aggregate=", "If set to true data aggregation is performed. Otherwise false to display the list of aggregates.", d=> settings.Aggregate = bool.Parse(d) }, + { "?|help", "show this message and exit", h => settings.ShouldShowHelp = h != null } + }; + } + + static List ExpandArguments(string[] args) + { + var expandedArgs = new List(); + foreach (var arg in args) + { + if (!arg.StartsWith("@")) + { + expandedArgs.Add(arg); + continue; + } + + var filePath = arg.TrimStart('@'); + var fileArgs = + System.IO.File.ReadAllLines(filePath) + .Where(l => !string.IsNullOrWhiteSpace(l) && !l.Trim().StartsWith("#")) + .Select(l => l.Trim()).ToArray(); + + if (fileArgs.Any(a => a.StartsWith("-NewPassword="))) + throw new Exception($"SetPassword must be set as a command line argument. Please remove from file {filePath}."); + + expandedArgs.AddRange(fileArgs); + } + + return expandedArgs; + } + + public static bool Parse(string[] args, ILogger logger, out Settings settings) + { + settings = new Settings(); + var options = GetOptionSet(settings); + + List extra; + try + { + if (SetNewPassword(args, logger)) + return false; + + var expandedArgs = ExpandArguments(args); + extra = options.Parse(expandedArgs.ToArray()); + + if (settings.ShouldShowHelp) + { + Console.WriteLine("Options:"); + options.WriteOptionDescriptions(Console.Out); + return false; + } + + Validate(settings); + return true; + } + catch (Exception e) + { + // output some error message + Console.WriteLine("Command line argument error:"); + Console.WriteLine(e.Message); + Console.WriteLine(Environment.NewLine); + Console.WriteLine("Options:"); + options.WriteOptionDescriptions(Console.Out); + return false; + } + } + + private static bool SetNewPassword(string[] args, ILogger logger) + { + string newPassword = args.Where(arg => arg.Trim().StartsWith("-SetPassword=")).FirstOrDefault()?.Split("=".ToCharArray())[1]; + if (string.IsNullOrWhiteSpace(newPassword)) + return false; + + string replacementSetting = $"-Password={Encryption.EncryptPlainTextToCipherText(newPassword)}"; + + var files = args.Where(arg => arg.StartsWith("@")).Select(arg => arg.TrimStart('@')).ToList(); + if (files.Count == 0) + throw new Exception("No setting files specified to change"); + + int changesMade = 0; + foreach (var file in files) + { + var fileLines = System.IO.File.ReadAllLines(file).ToArray(); + + bool found = false; + for (int idx = 0; idx < fileLines.Length; idx++) + { + if (fileLines[idx].Trim().StartsWith("-Password=")) + { + changesMade++; + found = true; + fileLines[idx] = replacementSetting; + } + } + + if (found) + System.IO.File.WriteAllLines(file, fileLines); + } + + if (changesMade == 0) + { + var file = files.First(); + var fileLines = System.IO.File.ReadAllLines(file).ToList(); + fileLines.Insert(0, replacementSetting); + System.IO.File.WriteAllLines(file, fileLines); + } + + logger.Information("New password set"); + return true; + } + + static void Validate(Settings settings) + { + if (string.IsNullOrWhiteSpace(settings.Server)) + throw new Exception("Server: An AQ TS Server name or ip address is required."); + if (string.IsNullOrWhiteSpace(settings.Username)) + throw new Exception("Username: An AQ TS username for API access is required."); + if (string.IsNullOrWhiteSpace(settings.Password)) + throw new Exception("Password: An AQ TS password for API access is required."); + if (string.IsNullOrWhiteSpace(settings.AggrSeriesLabelSingle)) + throw new Exception("AggregatedSeriesLabelForSingleLabel: An aggregate label must be set for time-series creation."); + if (string.IsNullOrWhiteSpace(settings.AggrSeriesLabelMulti)) + throw new Exception("AggregatedSeriesLabelForMultiLabels: An aggregate label must be set for time-series creation."); + } + } +} diff --git a/Samples/DotNetSdk/ReflectedSeriesAggregator/TimeSeriesReading.cs b/Samples/DotNetSdk/ReflectedSeriesAggregator/TimeSeriesReading.cs new file mode 100644 index 0000000..6d52bf0 --- /dev/null +++ b/Samples/DotNetSdk/ReflectedSeriesAggregator/TimeSeriesReading.cs @@ -0,0 +1,33 @@ +using System; + +namespace ReflectedSeriesAggregator +{ + [Serializable] + public class TimeSeriesReading + { + public string SeriesIdentifier { get; set; } + public DateTimeOffset Timestamp { get; } + public double? Value { get; set; } + + public int? Grade { get; set; } + + public TimeSeriesReading(string seriesIdentifier, DateTimeOffset datetime, double? val, int? grade = null) + { + SeriesIdentifier = seriesIdentifier; + Timestamp = datetime; + Value = val; + Grade = grade; + } + + public override string ToString() + { + if( Grade == null) + return $"{SeriesIdentifier},{ToIsoString(Timestamp)},{Value ?? double.NaN}"; + + return $"{SeriesIdentifier},{ToIsoString(Timestamp)},{Value ?? double.NaN},{Grade}"; + } + + static string ToIsoString(DateTimeOffset datetime) => datetime.ToString("yyyy-MM-dd HH:mm:ss"); + } +} + diff --git a/Samples/DotNetSdk/ReflectedSeriesAggregator/TimeSeriesReadings.cs b/Samples/DotNetSdk/ReflectedSeriesAggregator/TimeSeriesReadings.cs new file mode 100644 index 0000000..a608c27 --- /dev/null +++ b/Samples/DotNetSdk/ReflectedSeriesAggregator/TimeSeriesReadings.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace ReflectedSeriesAggregator +{ + public class TimeSeriesReadings + { + readonly SortedDictionary _tsData = new SortedDictionary(); + + public int Count { get => _tsData.Count; } + + public bool Add(string seriesIdentifier, DateTimeOffset timestamp, double? value, bool alwaysInsert) + { + if (!_tsData.ContainsKey(timestamp)) + { + _tsData.Add(timestamp, new TimeSeriesReading(seriesIdentifier, timestamp, value)); + return true; + } + + if (alwaysInsert) + { + TimeSeriesReading timeSeriesReading = _tsData[timestamp]; + timeSeriesReading.SeriesIdentifier = seriesIdentifier; + timeSeriesReading.Value = value; + return true; + } + + return false; + } + + public bool Add(string seriesIdentifier, DateTimeOffset timestamp, double? value, int grade, bool alwaysInsert) + { + if (!_tsData.ContainsKey(timestamp)) + { + _tsData.Add(timestamp, new TimeSeriesReading(seriesIdentifier, timestamp, value, grade)); + return true; + } + + if (alwaysInsert) + { + TimeSeriesReading timeSeriesReading = _tsData[timestamp]; + timeSeriesReading.SeriesIdentifier = seriesIdentifier; + timeSeriesReading.Value = value; + timeSeriesReading.Grade = grade; + return true; + } + + return false; + } + + + public bool Add(TimeSeriesReading tsReading, bool alwaysInsert) + { + if (!_tsData.ContainsKey(tsReading.Timestamp)) + { + _tsData.Add(tsReading.Timestamp, new TimeSeriesReading(tsReading.SeriesIdentifier, tsReading.Timestamp, tsReading.Value, tsReading.Grade)); + return true; + } + + if (alwaysInsert) + { + TimeSeriesReading timeSeriesReading = _tsData[tsReading.Timestamp]; + timeSeriesReading.SeriesIdentifier = tsReading.SeriesIdentifier; + timeSeriesReading.Value = tsReading.Value; + timeSeriesReading.Grade = tsReading.Grade; + return true; + } + + return false; + } + + public TimeSeriesReading GetReading(DateTimeOffset timestamp) => _tsData[timestamp]; + + public void Clear() => _tsData.Clear(); + + public bool IsEmpty { get => _tsData.Count == 0; } + + public List ToList() => _tsData.Values.ToList(); + + public DateTimeOffset? MinTimestamp => _tsData.Keys.FirstOrDefault(); + + public DateTimeOffset? MaxTimestamp => _tsData.Keys.LastOrDefault(); + } +} diff --git a/Samples/DotNetSdk/ReflectedSeriesAggregator/Work.cs b/Samples/DotNetSdk/ReflectedSeriesAggregator/Work.cs new file mode 100644 index 0000000..4ca0b36 --- /dev/null +++ b/Samples/DotNetSdk/ReflectedSeriesAggregator/Work.cs @@ -0,0 +1,370 @@ +using Aquarius.TimeSeries.Client; +using Aquarius.TimeSeries.Client.ServiceModels.Acquisition; +using Aquarius.TimeSeries.Client.ServiceModels.Publish; +using NodaTime; +using Serilog; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Net; +using static ReflectedSeriesAggregator.AquariusClientHelperExt; +using MonitoringMethod = Aquarius.TimeSeries.Client.ServiceModels.Provisioning.MonitoringMethod; +using Parameter = Aquarius.TimeSeries.Client.ServiceModels.Provisioning.Parameter; +using PostReflectedTimeSeries = Aquarius.TimeSeries.Client.ServiceModels.Acquisition.PostReflectedTimeSeries; +using TimeSeriesPoint = Aquarius.TimeSeries.Client.ServiceModels.Acquisition.TimeSeriesPoint; + +namespace ReflectedSeriesAggregator +{ + public class Work + { + List _allLocationDescriptions; + List _allParameters; + List _allMonitoringMethods; + ILogger _logger { get; } + Settings _settings; + IAquariusClient _client; + + public Work(ILogger logger, Settings settings) + { + _logger = logger; + _settings = settings; + } + + public void Run() + { + var stopWatch = Stopwatch.StartNew(); + _logger.Information("Running..."); + + try + { + ConnectClient(); + Initialise(); + ValidateAndFix(); + + _logger.Debug("Building aggregate source list."); + var workItems = BuildWorkItems(); + + _logger.Information($"Found {workItems.Count} aggregate(s)..."); + foreach (var workItem in workItems) + { + try + { + ProcessWorkItem(workItem); + } + catch (Exception ex) + { + _logger.Error($"{ex.Message}", ex); + } + } + _logger.Information($"Process Commplete. Time Taken: {stopWatch.Elapsed}"); + } + catch (Exception ex) + { + _logger.Fatal($"{ex.Message}", ex); + _logger.Fatal($"Process Failed. Time Taken: {stopWatch.Elapsed}"); + } + finally + { + _client?.Dispose(); + } + } + + TimeSeriesReadings AggregateReadings(WorkItem workItem) + { + TimeSeriesReadings readings = new TimeSeriesReadings(); + foreach (var tsd in workItem.GroupedTimeSeriesSourceDescriptions) + { + _logger.Debug($"Reading series: '{tsd.Identifier}'"); + var sourcePoints = _client.Publish.Get(new TimeSeriesDataCorrectedServiceRequest + { + TimeSeriesUniqueId = tsd.UniqueId, + GetParts = "PointsOnly" + }).Points; + + if (sourcePoints.Count == 0) + _logger.Information($"No points found for series: '{tsd.Identifier}'"); + else + _logger.Information($"Read {sourcePoints.Count} point(s) in the range {sourcePoints.Min(p => p.Timestamp.DateTimeOffset)} to {sourcePoints.Max(p => p.Timestamp.DateTimeOffset)} from series: '{tsd.Identifier}'"); + + foreach (var sourcePoint in sourcePoints) + { + int secondsAdded = 0; + bool ok = true; + + TimeSeriesReading newReading = null; + + do + { + newReading = new TimeSeriesReading(tsd.Identifier, sourcePoint.Timestamp.DateTimeOffset.AddSeconds(secondsAdded), sourcePoint.Value.Numeric); + ok = readings.Add(newReading, false); + if (!ok) + { + if (_settings.AdjustDuplicateTimestamps) + secondsAdded++; + else + { + string message = $"Duplicate found. Winning reading: '{readings.GetReading(newReading.Timestamp)}' Discarding reading: '{newReading}'."; + _logger.Warning(message); + break; + } + } + } while (!ok); + + if (secondsAdded > 0) + { + string message = $"Reading {tsd.Identifier} has been adjusted by {secondsAdded} second(s)."; + _logger.Warning(message); + } + } + } + return readings; + } + List BuildWorkItems() + { + List workItems = new List(); + + foreach (var rawTag in _settings.Tags.Distinct()) + { + BuildWorkItems_GetTagAndLocationIdentifier(rawTag, out string searchTag, out string aggregateLocationIdentifier); + + List availableTimeseries = _client.GetTimeSeriesDescriptionsByTag(searchTag); + List filterParameters = BuildWorkItems_GetFilterParameters(availableTimeseries); + + if (_settings.Aggregate) + _logger.Debug($"{filterParameters.Count} Filter Parameter(s): {string.Join(", ", filterParameters.Select(p => p.DisplayName))}"); + else + _logger.Information($"{filterParameters.Count} Filter Parameter(s): {string.Join(", ", filterParameters.Select(p => p.DisplayName))}"); + + foreach (var filterParameter in filterParameters) + { + List> filterLabels = BuildWorkItems_GetFilterLabels(filterParameter, availableTimeseries); + + foreach (var filterLabel in filterLabels) + { + var filteredTimeSeries = new List(); + foreach (var timeSeriesDescription in availableTimeseries) + { + if (IsSeriesIncluded(filterParameter, filterLabel, timeSeriesDescription)) + filteredTimeSeries.Add(timeSeriesDescription); + } + + if (filteredTimeSeries.Count == 0) + { + string message = $"Tag: '{rawTag}', Parameter: '{filterParameter.DisplayName}', Filter Label(s): '{string.Join(",", filterLabels.Select(l => string.Join("|", l)))}. No aggregate series found. IGNORING'"; + if (_settings.Aggregate) + _logger.Debug(message); + else + _logger.Information(message); + continue; + } + else + { + string message = $"Tag: '{rawTag}', Parameter: '{filterParameter.DisplayName}', Filter Label(s): '{string.Join(", ", filterLabels.Select(l => string.Join("|", l)))}'. Found {filteredTimeSeries.Count} aggregates(s)."; + if (_settings.Aggregate) + _logger.Debug(message); + else + _logger.Information(message); + } + + string aggregateLabel = null; + if (filterLabel.Count == 1) + aggregateLabel = _settings.AggrSeriesLabelSingle.Replace("{Label}", filterLabel[0]); + else + aggregateLabel = _settings.AggrSeriesLabelMulti; + + workItems.Add(new WorkItem + { + Parameter = filterParameter, + Labels = filterLabel, + Tag = searchTag, + Publish = _settings.Publish, + TargetLocationIdentifier = aggregateLocationIdentifier, + TargetLabel = aggregateLabel, + GroupedTimeSeriesSourceDescriptions = filteredTimeSeries + }); + } + } + } + return workItems; + } + + List> BuildWorkItems_GetFilterLabels(Parameter filterParameter, List timeSeriesDescriptions) + { + List> fileterLabels = new List>(); + + if (_settings.Labels.Count > 0) + foreach (var label in _settings.Labels.Distinct()) + { + if (string.IsNullOrEmpty(label)) + continue; + + fileterLabels.Add(label.Split("|".ToCharArray(), StringSplitOptions.RemoveEmptyEntries).Distinct().ToList()); + } + else + { + foreach (var label in timeSeriesDescriptions.Where(t => (t.ParameterId == filterParameter.ParameterId)).Select(tsd => tsd.Label).Distinct()) + fileterLabels.Add(new List { label }); + } + + return fileterLabels; + } + + List BuildWorkItems_GetFilterParameters(List timeSeriesDescriptions) + { + var filterParameters = new List(); + if (_settings.ParameterIds.Count > 0) + { + foreach (var parameterId in _settings.ParameterIds) + { + if (string.IsNullOrWhiteSpace(parameterId)) + continue; + + var parameter = _allParameters.FirstOrDefault(p => (parameterId == p.ParameterId) || (parameterId == p.DisplayName) || (parameterId == p.Identifier)); + if (parameter != null) + filterParameters.Add(parameter); + } + } + else + filterParameters.AddRange( + timeSeriesDescriptions + .Select(tsd => tsd.ParameterId).Distinct() + .Select(pId => _allParameters.First(p => p.ParameterId == pId))); + + return filterParameters; + } + + void BuildWorkItems_GetTagAndLocationIdentifier(string rawTag, out string searchTag, out string aggregateLocationIdentifier) + { + // Extract tag and aggr location identifier + if (rawTag.Contains("@")) + { + var tagLocId = rawTag.Split("@".ToCharArray(), 2); + searchTag = tagLocId[0]; + aggregateLocationIdentifier = tagLocId[1]; + } + else + searchTag = aggregateLocationIdentifier = rawTag; + } + + bool CreateAggregateTimeSeries(WorkItem workItem, out GetOrCreateTimeSeriesResponse response) + { + response = null; + + var location = _allLocationDescriptions.FirstOrDefault(l => l.Identifier == workItem.TargetLocationIdentifier); + if (location == null) + { + _logger.Error($"Unable to find location Identifier '{workItem.TargetLocationIdentifier}'"); + return false; + } + + var methodCode = (_allMonitoringMethods.FirstOrDefault(m => m.ParameterId == workItem.Parameter.ParameterId)?.MethodCode) ?? _allMonitoringMethods.First(m => m.ParameterId == null).MethodCode; + var gapTolerance = AquariusClientHelperExt.NoGaps; + + // Publish check + bool publish = workItem.Publish & location.Publish; + response = _client.GetOrCreateTimeSeries(location, workItem.Parameter, workItem.TargetLabel, methodCode, publish, gapTolerance); + if (response.IsNew) + { + if (workItem.Publish == publish) + _logger.Information($"Created aggregate series: '{response.SeriesIdentifier}' with Publish={publish}"); + else + _logger.Warning($"Created target series: '{response.SeriesIdentifier}' with Publish={publish} because its locations is set to Publish={location.Publish}."); + } + + return true; + } + void ConnectClient() + { + _logger.Information($"Making a client connection to {_settings.Server} with user {_settings.Username}..."); + _client = AquariusClient.CreateConnectedClient(_settings.Server, _settings.Username, _settings.Password); + _logger.Debug($"Connection successfull."); + } + + void Initialise() + { + _logger.Information("Initialising..."); + // Use this to lookup the unique id from loc identifier + _logger.Debug("Fetching all location descriptions."); + _allLocationDescriptions = _client.GetLocationDescriptions(); + _logger.Debug("Fetching all parameters."); + _allParameters = _client.GetParameters(); + _logger.Debug("Fetching all monitoring methods."); + _allMonitoringMethods = _client.GetMonitoringMethods(); + } + bool IsSeriesIncluded(Parameter filterParameter, List filterLabel, TimeSeriesDescription tsd) + { + if (tsd.TimeSeriesType != "Reflected" || tsd.Comment != "Time-Series created by AQUARIUS Samples Connector") + return false; + + if (filterLabel.Any() && !filterLabel.Contains(tsd.Label)) + return false; + + if ((filterParameter != null) && (filterParameter.ParameterId != tsd.ParameterId)) + return false; + + return true; + } + void ProcessWorkItem(WorkItem workItem) + { + _logger.Information($"{new string('-', 80)}"); + + if (!_settings.Aggregate) + { + _logger.Information(workItem.ToFullString()); + return; + } + + if (!CreateAggregateTimeSeries(workItem, out GetOrCreateTimeSeriesResponse aggregateTsDetails)) + return; + + TimeSeriesReadings readings = AggregateReadings(workItem); + if (readings.IsEmpty) + { + _logger.Information("No points to export."); + return; + } + WriteSeries(aggregateTsDetails, readings); + } + void ValidateAndFix() + { + List parameters = new List(); + List invalidParameters = new List(); + List duplicateParameters = new List(); + + foreach (var parameterId in _settings.ParameterIds) + { + var parameter = _allParameters.FirstOrDefault(p => (parameterId == p.ParameterId) || (parameterId == p.DisplayName)); + if (parameter == null) + invalidParameters.Add(parameterId); + else if (!parameters.Contains(parameter)) + parameters.Add(parameter); + else if (!duplicateParameters.Contains(parameterId)) + duplicateParameters.Add(parameterId); + + } + if (invalidParameters.Count > 0) + _logger.Error($"The following setting parameters: '{string.Join(", ", invalidParameters)}' cannot be found and will be ignored."); + if (duplicateParameters.Count > 0) + _logger.Warning($"Duplicate setting paramaters found: '{string.Join(", ", duplicateParameters)}' and will be ignored."); + + _settings.ParameterIds = parameters.Select(p => p.ParameterId).ToList(); + } + void WriteSeries(GetOrCreateTimeSeriesResponse aggregateTsDetails, TimeSeriesReadings readings) + { + var timeseriesPoints = readings.ToList().Select(r => new TimeSeriesPoint() { Type = PointType.Point, Time = Instant.FromDateTimeOffset(r.Timestamp), Value = r.Value, GradeCode = r.Grade }).ToList(); + + var writeResponse = _client.Acquisition.Post(new PostReflectedTimeSeries + { + UniqueId = aggregateTsDetails.UniqueId, + Points = timeseriesPoints, + TimeRange = new Interval(Instant.MinValue, Instant.MaxValue) + }); + + _logger.Information($"{readings.Count} point(s) written to series: {aggregateTsDetails.SeriesIdentifier}"); + } + } +} + + + diff --git a/Samples/DotNetSdk/ReflectedSeriesAggregator/WorkItem.cs b/Samples/DotNetSdk/ReflectedSeriesAggregator/WorkItem.cs new file mode 100644 index 0000000..8c37720 --- /dev/null +++ b/Samples/DotNetSdk/ReflectedSeriesAggregator/WorkItem.cs @@ -0,0 +1,31 @@ +using Aquarius.TimeSeries.Client.ServiceModels.Provisioning; +using Aquarius.TimeSeries.Client.ServiceModels.Publish; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace ReflectedSeriesAggregator +{ + public class WorkItem + { + public string Tag { get; set; } + public Parameter Parameter { get; set; } + public List Labels { get; set; } = new List(); + public string TargetLocationIdentifier { get; set; } + public string TargetLabel { get; set; } + public List GroupedTimeSeriesSourceDescriptions { get; set; } = new List(); + public bool Publish { get; set; } + public string TargetSeriesIdentifier { get => $"{Parameter.DisplayName}.{TargetLabel}@{TargetLocationIdentifier}"; } + public override string ToString() => $"Tag:'{Tag}' ParameterId:'{Parameter.DisplayName}' Label:'{string.Join("|", Labels)}' Series to aggregate:{GroupedTimeSeriesSourceDescriptions.Count} Target series:'{TargetSeriesIdentifier}'"; + public string ToFullString() + { + string fullString = this.ToString(); + if (GroupedTimeSeriesSourceDescriptions.Count > 0) + { + fullString += Environment.NewLine; + fullString += string.Join(Environment.NewLine, GroupedTimeSeriesSourceDescriptions.Select(t => $" {t.Parameter}.{t.Label}@{t.LocationIdentifier}")); + } + return fullString; + } + } +} diff --git a/Samples/DotNetSdk/ReflectedSeriesAggregator/appsettings.json b/Samples/DotNetSdk/ReflectedSeriesAggregator/appsettings.json new file mode 100644 index 0000000..ed8a3d4 --- /dev/null +++ b/Samples/DotNetSdk/ReflectedSeriesAggregator/appsettings.json @@ -0,0 +1,25 @@ +{ + "Serilog": { + "Using": [ + "Serilog.Sinks.Console", + "Serilog.Sinks.File" + ], + "MinimumLevel": "Information", + "WriteTo": [ + { + "Name": "Console" + }, + { + "Name": "File", + "Args": { + "path": "%ProgramData%\\Aquatic Informatics\\AQUARIUS\\Logs\\ReflectedSeriesAggregator_.log", + "rollingInterval": "Day", + "retainedFileCountLimit": 7 + } + } + ], + "Properties": { + "Application": "ReflectedSeriesAggregator" + } + } +} \ No newline at end of file diff --git a/Samples/DotNetSdk/ReflectedSeriesAggregator/packages.config b/Samples/DotNetSdk/ReflectedSeriesAggregator/packages.config new file mode 100644 index 0000000..6793738 --- /dev/null +++ b/Samples/DotNetSdk/ReflectedSeriesAggregator/packages.config @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file