diff options
| author | Roy Ben Shabat <Roy.mail.net@gmail.com> | 2025-07-29 00:54:47 +0300 |
|---|---|---|
| committer | Roy Ben Shabat <Roy.mail.net@gmail.com> | 2025-07-29 00:54:47 +0300 |
| commit | 70f9f4dcbe3d4537ff2bd503f18288b9a7b0e19e (patch) | |
| tree | ab7c70dbe23c04e9e6ea9a165b6840e0b2ed1df7 | |
| parent | d70056692f43b2a39dbaefebe7c6e096a1205fb4 (diff) | |
| download | Tango-70f9f4dcbe3d4537ff2bd503f18288b9a7b0e19e.tar.gz Tango-70f9f4dcbe3d4537ff2bd503f18288b9a7b0e19e.zip | |
Tango.Telemetry v1
22 files changed, 1323 insertions, 0 deletions
diff --git a/Software/Visual_Studio/Tango.Telemetry/Destinations/AzureHubTelemetryDestination.cs b/Software/Visual_Studio/Tango.Telemetry/Destinations/AzureHubTelemetryDestination.cs new file mode 100644 index 000000000..713805471 --- /dev/null +++ b/Software/Visual_Studio/Tango.Telemetry/Destinations/AzureHubTelemetryDestination.cs @@ -0,0 +1,56 @@ +using Microsoft.Azure.Devices.Client; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.Core; + +namespace Tango.Telemetry.Destinations +{ + public class AzureHubTelemetryDestination : ExtendedObject, ITelemetryDestination + { + private DeviceClient _hubClient; + + public bool Enable { get; set; } = true; + public String ConnectionString { get; private set; } + + public IReadOnlyList<TelemetrySource> SupportedSources { get; private set; } + + public AzureHubTelemetryDestination(String connectionString) + { + ConnectionString = connectionString; + SupportedSources = new List<TelemetrySource>() { TelemetrySource.PendingStorage, TelemetrySource.Streaming, TelemetrySource.ExternalStorage }; + } + + public async Task Publish(TelemetryPublishPackage package, List<KeyValuePair<String, String>> properties) + { + if (_hubClient == null) + { + _hubClient = DeviceClient.CreateFromConnectionString(ConnectionString, TransportType.Mqtt); + _hubClient.SetConnectionStatusChangesHandler((status, reason) => + { + LogManager.Log($"IoT hub status changed to: {status}, Reason: {reason}."); + }); + } + + var message = new Message(Encoding.UTF8.GetBytes(package.ToPayload())) + { + ContentType = "application/json", + ContentEncoding = "utf-8" + }; + + foreach (var prop in properties) + { + message.Properties.Add(prop.Key, prop.Value); + } + + await _hubClient.SendEventAsync(message); + } + + public void Dispose() + { + _hubClient?.Dispose(); + } + } +} diff --git a/Software/Visual_Studio/Tango.Telemetry/Destinations/MqttTelemetryDestination.cs b/Software/Visual_Studio/Tango.Telemetry/Destinations/MqttTelemetryDestination.cs new file mode 100644 index 000000000..a37d149a1 --- /dev/null +++ b/Software/Visual_Studio/Tango.Telemetry/Destinations/MqttTelemetryDestination.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Collections; +using MQTTnet.Client; +using MQTTnet.Client.Options; +using MQTTnet; +using System.Reflection; +using MQTTnet.Client.Connecting; +using Tango.Core; +using MQTTnet.Packets; + +namespace Tango.Telemetry.Destinations +{ + public class MqttTelemetryDestination : ExtendedObject, ITelemetryDestination + { + private IMqttClient _mqttClient; + private IMqttClientOptions _mqttOptions; + + public bool Enable { get; set; } = true; + public String Address { get; private set; } + public int Port { get; private set; } + public String Topic { get; set; } + + public IReadOnlyList<TelemetrySource> SupportedSources { get; private set; } + + /// <summary> + /// + /// </summary> + /// <param name="topic">example machie/telemetry/serial number</param> + /// <param name="address">Default localhost</param> + /// <param name="port">Default 1883</param> + public MqttTelemetryDestination(String topic, String address = "localhost", int port = 1883) + { + Topic = topic; + Port = port; + SupportedSources = new List<TelemetrySource>() { TelemetrySource.Streaming }; + } + + public async Task<bool> EnsureConnection() + { + if (_mqttClient == null || !_mqttClient.IsConnected) + { + try + { + var factory = new MqttFactory(); + _mqttClient = factory.CreateMqttClient(); + + String exeName = Assembly.GetEntryAssembly().GetName().FullName; + _mqttOptions = new MqttClientOptionsBuilder() + .WithClientId(exeName) + .WithTcpServer(Address, Port) + .WithCleanSession() + .Build(); + + var result = await _mqttClient.ConnectAsync(_mqttOptions); + if (result.ResultCode != MqttClientConnectResultCode.Success) + { + LogManager.Log(new Exception($"Error connecting to MQTT broker. {result.ResultCode}")); + return false; + } + } + catch (Exception ex) + { + LogManager.Log(ex, "Error connecting to MQTT broker."); + return false; + } + } + + return true; + } + + public async Task Publish(TelemetryPublishPackage package, List<KeyValuePair<String, String>> properties) + { + if (await EnsureConnection()) + { + var message = new MqttApplicationMessageBuilder() + .WithTopic($"{Topic}/{package.TelemetryObject.TelemetryName()}") + .WithPayload(package.ToPayload()) + .WithExactlyOnceQoS() + .WithRetainFlag(false) + .Build(); + + foreach (var prop in properties) + { + message.UserProperties.Add(new MqttUserProperty(prop.Key, prop.Value)); + } + + + await _mqttClient.PublishAsync(message); + } + } + + public void Dispose() + { + _mqttClient?.Dispose(); + } + } +} diff --git a/Software/Visual_Studio/Tango.Telemetry/ExtensionMethods/ITelemetryExtensions.cs b/Software/Visual_Studio/Tango.Telemetry/ExtensionMethods/ITelemetryExtensions.cs new file mode 100644 index 000000000..6130f332e --- /dev/null +++ b/Software/Visual_Studio/Tango.Telemetry/ExtensionMethods/ITelemetryExtensions.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.Telemetry; + +public static class ITelemetryObjectExtensions +{ + public static String TelemetryName(this ITelemetry obj) + { + var att = obj.GetType().GetCustomAttributes(typeof(TelemetryNameAttribute), false).Cast<TelemetryNameAttribute>().FirstOrDefault(); + return att?.Name ?? obj.GetType().Name; + } +} diff --git a/Software/Visual_Studio/Tango.Telemetry/ITelemetry.cs b/Software/Visual_Studio/Tango.Telemetry/ITelemetry.cs new file mode 100644 index 000000000..3aa6f10a9 --- /dev/null +++ b/Software/Visual_Studio/Tango.Telemetry/ITelemetry.cs @@ -0,0 +1,20 @@ +using LiteDB; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Telemetry +{ + public interface ITelemetry + { + [BsonId(true)] + int Id { get; set; } + DateTime Time { get; set; } + List<String> PendingDestinations { get; set; } + String ToJson(Formatting format = Formatting.None, bool flatten = true); + byte[] ToBytes(Formatting format = Formatting.None, bool flatten = true); + } +} diff --git a/Software/Visual_Studio/Tango.Telemetry/ITelemetryDestination.cs b/Software/Visual_Studio/Tango.Telemetry/ITelemetryDestination.cs new file mode 100644 index 000000000..7c1c1032b --- /dev/null +++ b/Software/Visual_Studio/Tango.Telemetry/ITelemetryDestination.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Telemetry +{ + public interface ITelemetryDestination : IDisposable + { + bool Enable { get; set; } + IReadOnlyList<TelemetrySource> SupportedSources { get; } + Task Publish(TelemetryPublishPackage package, List<KeyValuePair<String, String>> properties); + } +} diff --git a/Software/Visual_Studio/Tango.Telemetry/JsonFlattener.cs b/Software/Visual_Studio/Tango.Telemetry/JsonFlattener.cs new file mode 100644 index 000000000..230a4612c --- /dev/null +++ b/Software/Visual_Studio/Tango.Telemetry/JsonFlattener.cs @@ -0,0 +1,89 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; + +namespace Tango.Telemetry +{ + public static class JsonFlattener + { + public static string FlattenObjectToFlatJson(object obj, Formatting format) + { + var flat = new JObject(); + FlattenRecursive(obj, flat, prefix: null); + return flat.ToString(format); + } + + private static void FlattenRecursive(object obj, JObject target, string prefix) + { + if (obj == null) + return; + + var type = obj.GetType(); + if (type == typeof(JObject)) + { + foreach (var prop in ((JObject)obj).Properties()) + { + FlattenRecursive(prop.Value, target, Combine(prefix, prop.Name)); + } + return; + } + + if (obj is JValue jVal) + { + target[Combine(prefix, "Value")] = JToken.FromObject(jVal.Value); + return; + } + + if (obj is JToken jToken && jToken.Type == JTokenType.Object) + { + foreach (var prop in ((JObject)jToken).Properties()) + { + FlattenRecursive(prop.Value, target, Combine(prefix, prop.Name)); + } + return; + } + + foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + if (!prop.CanRead) continue; + + var value = prop.GetValue(obj); + + if (value == null) continue; + + var valueType = value.GetType(); + + if (IsSimpleType(valueType)) + { + target[Combine(prefix, prop.Name)] = JToken.FromObject(value); + } + else if (value is IEnumerable enumerable && !(value is string)) + { + int index = 0; + foreach (var item in enumerable) + { + FlattenRecursive(item, target, Combine(prefix, $"{prop.Name}_{index}")); + index++; + } + } + else + { + FlattenRecursive(value, target, Combine(prefix, prop.Name)); + } + } + } + + private static string Combine(string prefix, string name) + { + return string.IsNullOrEmpty(prefix) ? name : $"{prefix}_{name}"; + } + + private static bool IsSimpleType(Type type) + { + return type.IsPrimitive || type.IsValueType || type == typeof(string); + } + } +} diff --git a/Software/Visual_Studio/Tango.Telemetry/Properties/AssemblyInfo.cs b/Software/Visual_Studio/Tango.Telemetry/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..b5e1ddb12 --- /dev/null +++ b/Software/Visual_Studio/Tango.Telemetry/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("Tango.Telemetry")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Tango.Telemetry")] +[assembly: AssemblyCopyright("Copyright © 2025")] +[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("af593663-d4e9-4a14-a3f2-fea57f30e9e6")] + +// 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.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Software/Visual_Studio/Tango.Telemetry/Tango.Telemetry.csproj b/Software/Visual_Studio/Tango.Telemetry/Tango.Telemetry.csproj new file mode 100644 index 000000000..03ac99116 --- /dev/null +++ b/Software/Visual_Studio/Tango.Telemetry/Tango.Telemetry.csproj @@ -0,0 +1,257 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{AF593663-D4E9-4A14-A3F2-FEA57F30E9E6}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>Tango.Telemetry</RootNamespace> + <AssemblyName>Tango.Telemetry</AssemblyName> + <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <Deterministic>true</Deterministic> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <ItemGroup> + <Reference Include="DotNetty.Buffers, Version=0.6.0.0, Culture=neutral, PublicKeyToken=bc13ca065fa06c29, processorArchitecture=MSIL"> + <HintPath>..\packages\DotNetty.Buffers.0.6.0\lib\net45\DotNetty.Buffers.dll</HintPath> + </Reference> + <Reference Include="DotNetty.Codecs, Version=0.6.0.0, Culture=neutral, PublicKeyToken=bc13ca065fa06c29, processorArchitecture=MSIL"> + <HintPath>..\packages\DotNetty.Codecs.0.6.0\lib\net45\DotNetty.Codecs.dll</HintPath> + </Reference> + <Reference Include="DotNetty.Codecs.Mqtt, Version=0.6.0.0, Culture=neutral, PublicKeyToken=bc13ca065fa06c29, processorArchitecture=MSIL"> + <HintPath>..\packages\DotNetty.Codecs.Mqtt.0.6.0\lib\net45\DotNetty.Codecs.Mqtt.dll</HintPath> + </Reference> + <Reference Include="DotNetty.Common, Version=0.6.0.0, Culture=neutral, PublicKeyToken=bc13ca065fa06c29, processorArchitecture=MSIL"> + <HintPath>..\packages\DotNetty.Common.0.6.0\lib\net45\DotNetty.Common.dll</HintPath> + </Reference> + <Reference Include="DotNetty.Handlers, Version=0.6.0.0, Culture=neutral, PublicKeyToken=bc13ca065fa06c29, processorArchitecture=MSIL"> + <HintPath>..\packages\DotNetty.Handlers.0.6.0\lib\net45\DotNetty.Handlers.dll</HintPath> + </Reference> + <Reference Include="DotNetty.Transport, Version=0.6.0.0, Culture=neutral, PublicKeyToken=bc13ca065fa06c29, processorArchitecture=MSIL"> + <HintPath>..\packages\DotNetty.Transport.0.6.0\lib\net45\DotNetty.Transport.dll</HintPath> + </Reference> + <Reference Include="LiteDB, Version=5.0.4.0, Culture=neutral, PublicKeyToken=4ee40123013c9f27, processorArchitecture=MSIL"> + <HintPath>..\packages\LiteDB.5.0.4\lib\net45\LiteDB.dll</HintPath> + </Reference> + <Reference Include="Microsoft.Azure.Amqp, Version=2.4.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> + <HintPath>..\packages\Microsoft.Azure.Amqp.2.5.10\lib\net45\Microsoft.Azure.Amqp.dll</HintPath> + </Reference> + <Reference Include="Microsoft.Azure.Devices.Client, Version=1.41.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> + <HintPath>..\packages\Microsoft.Azure.Devices.Client.1.41.0\lib\net451\Microsoft.Azure.Devices.Client.dll</HintPath> + </Reference> + <Reference Include="Microsoft.Azure.Devices.Shared, Version=1.30.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> + <HintPath>..\packages\Microsoft.Azure.Devices.Shared.1.30.1\lib\net451\Microsoft.Azure.Devices.Shared.dll</HintPath> + </Reference> + <Reference Include="Microsoft.Azure.KeyVault.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> + <HintPath>..\packages\Microsoft.Azure.KeyVault.Core.1.0.0\lib\net40\Microsoft.Azure.KeyVault.Core.dll</HintPath> + </Reference> + <Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions, Version=1.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL"> + <HintPath>..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.1.1.0\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll</HintPath> + </Reference> + <Reference Include="Microsoft.Extensions.Logging, Version=1.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL"> + <HintPath>..\packages\Microsoft.Extensions.Logging.1.1.1\lib\netstandard1.1\Microsoft.Extensions.Logging.dll</HintPath> + </Reference> + <Reference Include="Microsoft.Extensions.Logging.Abstractions, Version=1.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL"> + <HintPath>..\packages\Microsoft.Extensions.Logging.Abstractions.1.1.1\lib\netstandard1.1\Microsoft.Extensions.Logging.Abstractions.dll</HintPath> + </Reference> + <Reference Include="Microsoft.Owin, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> + <HintPath>..\packages\Microsoft.Owin.4.0.0\lib\net451\Microsoft.Owin.dll</HintPath> + </Reference> + <Reference Include="Microsoft.Win32.Primitives, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll</HintPath> + </Reference> + <Reference Include="Microsoft.WindowsAzure.Storage, Version=9.3.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> + <HintPath>..\packages\WindowsAzure.Storage.9.3.2\lib\net45\Microsoft.WindowsAzure.Storage.dll</HintPath> + </Reference> + <Reference Include="MQTTnet, Version=3.1.2.0, Culture=neutral, PublicKeyToken=b69712f52770c0a7, processorArchitecture=MSIL"> + <HintPath>..\packages\MQTTnet.3.1.2\lib\net461\MQTTnet.dll</HintPath> + </Reference> + <Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> + <HintPath>..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll</HintPath> + </Reference> + <Reference Include="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL"> + <HintPath>..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath> + </Reference> + <Reference Include="System" /> + <Reference Include="System.AppContext, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll</HintPath> + </Reference> + <Reference Include="System.Collections.Immutable, Version=1.2.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\packages\System.Collections.Immutable.1.5.0\lib\netstandard2.0\System.Collections.Immutable.dll</HintPath> + </Reference> + <Reference Include="System.ComponentModel.Composition" /> + <Reference Include="System.Configuration" /> + <Reference Include="System.Console, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\packages\System.Console.4.3.0\lib\net46\System.Console.dll</HintPath> + <Private>True</Private> + <Private>True</Private> + </Reference> + <Reference Include="System.Core" /> + <Reference Include="System.Diagnostics.DiagnosticSource, Version=4.0.1.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL"> + <HintPath>..\packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll</HintPath> + </Reference> + <Reference Include="System.Globalization.Calendars, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll</HintPath> + <Private>True</Private> + <Private>True</Private> + </Reference> + <Reference Include="System.IO.Compression, Version=4.1.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL"> + <HintPath>..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll</HintPath> + <Private>True</Private> + <Private>True</Private> + </Reference> + <Reference Include="System.IO.Compression.FileSystem" /> + <Reference Include="System.IO.Compression.ZipFile, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL"> + <HintPath>..\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll</HintPath> + <Private>True</Private> + <Private>True</Private> + </Reference> + <Reference Include="System.IO.FileSystem, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll</HintPath> + <Private>True</Private> + <Private>True</Private> + </Reference> + <Reference Include="System.IO.FileSystem.Primitives, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll</HintPath> + <Private>True</Private> + <Private>True</Private> + </Reference> + <Reference Include="System.Net.Http, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\packages\System.Net.Http.4.3.0\lib\net46\System.Net.Http.dll</HintPath> + <Private>True</Private> + <Private>True</Private> + </Reference> + <Reference Include="System.Net.Http.Formatting, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> + <HintPath>..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll</HintPath> + </Reference> + <Reference Include="System.Net.Http.WebRequest" /> + <Reference Include="System.Net.Sockets, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll</HintPath> + <Private>True</Private> + <Private>True</Private> + </Reference> + <Reference Include="System.Numerics" /> + <Reference Include="System.Runtime" /> + <Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll</HintPath> + </Reference> + <Reference Include="System.Runtime.InteropServices.RuntimeInformation, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll</HintPath> + <Private>True</Private> + <Private>True</Private> + </Reference> + <Reference Include="System.Security.Cryptography.Algorithms, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net461\System.Security.Cryptography.Algorithms.dll</HintPath> + <Private>True</Private> + <Private>True</Private> + </Reference> + <Reference Include="System.Security.Cryptography.Encoding, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll</HintPath> + <Private>True</Private> + <Private>True</Private> + </Reference> + <Reference Include="System.Security.Cryptography.Primitives, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll</HintPath> + <Private>True</Private> + <Private>True</Private> + </Reference> + <Reference Include="System.Security.Cryptography.X509Certificates, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll</HintPath> + <Private>True</Private> + <Private>True</Private> + </Reference> + <Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL"> + <HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.1\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll</HintPath> + </Reference> + <Reference Include="System.Transactions" /> + <Reference Include="System.Web" /> + <Reference Include="System.Xml.Linq" /> + <Reference Include="System.Data.DataSetExtensions" /> + <Reference Include="Microsoft.CSharp" /> + <Reference Include="System.Data" /> + <Reference Include="System.Xml" /> + <Reference Include="System.Xml.ReaderWriter, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll</HintPath> + <Private>True</Private> + <Private>True</Private> + </Reference> + </ItemGroup> + <ItemGroup> + <Compile Include="Destinations\AzureHubTelemetryDestination.cs" /> + <Compile Include="ExtensionMethods\ITelemetryExtensions.cs" /> + <Compile Include="ITelemetryDestination.cs" /> + <Compile Include="Destinations\MqttTelemetryDestination.cs" /> + <Compile Include="TelemetryNameAttribute.cs" /> + <Compile Include="TelemetryPackagePublishedEventArgs.cs" /> + <Compile Include="TelemetryPackagePublishFailedEventArgs.cs" /> + <Compile Include="TelemetryPublisherEventArgs.cs" /> + <Compile Include="TelemetryPackagePublishingEventArgs.cs" /> + <Compile Include="TelemetryPublishPackage.cs" /> + <Compile Include="TelemetryPublisher.cs" /> + <Compile Include="TelemetryPublisherConfiguration.cs" /> + <Compile Include="TelemetrySource.cs" /> + <Compile Include="TelemetryPendingStorageManager.cs" /> + <Compile Include="ITelemetry.cs" /> + <Compile Include="JsonFlattener.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="TelemetryBase.cs" /> + <Compile Include="TelemetryDiagnosticsFrame.cs" /> + <Compile Include="TelemetryJobRun.cs" /> + </ItemGroup> + <ItemGroup> + <None Include="packages.config" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\Tango.BL\Tango.BL.csproj"> + <Project>{f441feee-322a-4943-b566-110e12fd3b72}</Project> + <Name>Tango.BL</Name> + </ProjectReference> + <ProjectReference Include="..\Tango.Core\Tango.Core.csproj"> + <Project>{a34ee0f0-649d-41c8-8489-b6f1cc6924ee}</Project> + <Name>Tango.Core</Name> + </ProjectReference> + <ProjectReference Include="..\Tango.Insights\Tango.Insights.csproj"> + <Project>{4a55c185-3f8d-41b0-8815-c15f6213a14a}</Project> + <Name>Tango.Insights</Name> + </ProjectReference> + <ProjectReference Include="..\Tango.Integration\Tango.Integration.csproj"> + <Project>{4206ac58-3b57-4699-8835-90bf6db01a61}</Project> + <Name>Tango.Integration</Name> + </ProjectReference> + <ProjectReference Include="..\Tango.Logging\Tango.Logging.csproj"> + <Project>{BC932DBD-7CDB-488C-99E4-F02CF441F55E}</Project> + <Name>Tango.Logging</Name> + </ProjectReference> + <ProjectReference Include="..\Tango.PMR\Tango.PMR.csproj"> + <Project>{e4927038-348d-4295-aaf4-861c58cb3943}</Project> + <Name>Tango.PMR</Name> + </ProjectReference> + <ProjectReference Include="..\Tango.Transport\Tango.Transport.csproj"> + <Project>{74E700B0-1156-4126-BE40-EE450D3C3026}</Project> + <Name>Tango.Transport</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <Folder Include="EventArgs\" /> + </ItemGroup> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> +</Project>
\ No newline at end of file diff --git a/Software/Visual_Studio/Tango.Telemetry/TelemetryBase.cs b/Software/Visual_Studio/Tango.Telemetry/TelemetryBase.cs new file mode 100644 index 000000000..3eda84a67 --- /dev/null +++ b/Software/Visual_Studio/Tango.Telemetry/TelemetryBase.cs @@ -0,0 +1,40 @@ +using LiteDB; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Telemetry +{ + public class TelemetryBase : ITelemetry + { + [BsonId(true)] + public int Id { get; set; } + public DateTime Time { get; set; } + public List<string> PendingDestinations { get; set; } + + public TelemetryBase() + { + PendingDestinations = new List<string>(); + } + + public byte[] ToBytes(Formatting format = Formatting.None, bool flatten = true) + { + return Encoding.UTF8.GetBytes(ToJson(format, flatten)); + } + + public string ToJson(Formatting format = Formatting.None, bool flatten = true) + { + if (flatten) + { + return JsonFlattener.FlattenObjectToFlatJson(this, format); + } + else + { + return JsonConvert.SerializeObject(this, format); + } + } + } +} diff --git a/Software/Visual_Studio/Tango.Telemetry/TelemetryDiagnosticsFrame.cs b/Software/Visual_Studio/Tango.Telemetry/TelemetryDiagnosticsFrame.cs new file mode 100644 index 000000000..89fe910b6 --- /dev/null +++ b/Software/Visual_Studio/Tango.Telemetry/TelemetryDiagnosticsFrame.cs @@ -0,0 +1,18 @@ +using LiteDB; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.PMR.Insights; + +namespace Tango.Telemetry +{ + [TelemetryName("Diagnostics")] + public class TelemetryDiagnosticsFrame : TelemetryBase + { + public InsightsMonitors Monitors { get; set; } + } +} diff --git a/Software/Visual_Studio/Tango.Telemetry/TelemetryJobRun.cs b/Software/Visual_Studio/Tango.Telemetry/TelemetryJobRun.cs new file mode 100644 index 000000000..091306089 --- /dev/null +++ b/Software/Visual_Studio/Tango.Telemetry/TelemetryJobRun.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Telemetry +{ + [TelemetryName("JobRuns")] + public class TelemetryJobRun : TelemetryBase + { + + } +} diff --git a/Software/Visual_Studio/Tango.Telemetry/TelemetryNameAttribute.cs b/Software/Visual_Studio/Tango.Telemetry/TelemetryNameAttribute.cs new file mode 100644 index 000000000..f6adb2d61 --- /dev/null +++ b/Software/Visual_Studio/Tango.Telemetry/TelemetryNameAttribute.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Telemetry +{ + public class TelemetryNameAttribute : Attribute + { + public String Name { get; set; } + + public TelemetryNameAttribute(String name) + { + Name = name; + } + } +} diff --git a/Software/Visual_Studio/Tango.Telemetry/TelemetryPackagePublishFailedEventArgs.cs b/Software/Visual_Studio/Tango.Telemetry/TelemetryPackagePublishFailedEventArgs.cs new file mode 100644 index 000000000..d2c303a40 --- /dev/null +++ b/Software/Visual_Studio/Tango.Telemetry/TelemetryPackagePublishFailedEventArgs.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Telemetry +{ + public class TelemetryPackagePublishFailedEventArgs : TelemetryPublisherEventArgs + { + public ITelemetryDestination Destination { get; set; } + public Exception Exception { get; set; } + } +} diff --git a/Software/Visual_Studio/Tango.Telemetry/TelemetryPackagePublishedEventArgs.cs b/Software/Visual_Studio/Tango.Telemetry/TelemetryPackagePublishedEventArgs.cs new file mode 100644 index 000000000..ed9429b5a --- /dev/null +++ b/Software/Visual_Studio/Tango.Telemetry/TelemetryPackagePublishedEventArgs.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Telemetry +{ + public class TelemetryPackagePublishedEventArgs : TelemetryPublisherEventArgs + { + public ITelemetryDestination Destination { get; set; } + } +} diff --git a/Software/Visual_Studio/Tango.Telemetry/TelemetryPackagePublishingEventArgs.cs b/Software/Visual_Studio/Tango.Telemetry/TelemetryPackagePublishingEventArgs.cs new file mode 100644 index 000000000..d5ed4cdce --- /dev/null +++ b/Software/Visual_Studio/Tango.Telemetry/TelemetryPackagePublishingEventArgs.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Telemetry +{ + public class TelemetryPackagePublishingEventArgs : TelemetryPublisherEventArgs + { + public ITelemetryDestination Destination { get; set; } + public bool Cancel { get; set; } + } +} diff --git a/Software/Visual_Studio/Tango.Telemetry/TelemetryPendingStorageManager.cs b/Software/Visual_Studio/Tango.Telemetry/TelemetryPendingStorageManager.cs new file mode 100644 index 000000000..70903b833 --- /dev/null +++ b/Software/Visual_Studio/Tango.Telemetry/TelemetryPendingStorageManager.cs @@ -0,0 +1,80 @@ +using LiteDB; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Telemetry +{ + public class TelemetryPendingStorageManager + { + private bool _disposed; + private LiteDatabase _database; + + public String DatabasePath { get; private set; } + + public TelemetryPendingStorageManager() + { + DatabasePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Twine", "Tango", "Telemetry", Path.GetFileNameWithoutExtension(AppDomain.CurrentDomain.FriendlyName) + ".telemetry"); + Directory.CreateDirectory(Path.GetDirectoryName(DatabasePath)); + + _database = new LiteDatabase($"Filename={DatabasePath}"); + _database.Pragma("TIMEOUT", 10); //Read Timeout + _database.Pragma("UTC_DATE", true); //Keep time as UTC when getting data + _database.Commit(); + } + + public virtual void Dispose() + { + if (_database != null) + { + try + { + _disposed = true; + _database.Dispose(); + _database = null; + } + catch { } + } + } + + ~TelemetryPendingStorageManager() + { + Dispose(); + } + + private ILiteCollection<T> GetCollection<T>() where T : ITelemetry + { + return _database.GetCollection<T>(); + } + + public void InsertOrUpdateTelemetryObject<T>(T telemetry) where T : ITelemetry + { + var collection = GetCollection<T>(); + collection.Upsert(telemetry); + } + + public void DeleteTelemetryObject<T>(T telemetry) where T : ITelemetry + { + var collection = GetCollection<T>(); + collection.Delete(telemetry.Id); + } + + public List<ITelemetry> GetTelemetryAll() + { + var names = _database.GetCollectionNames(); + + List<ITelemetry> telemetryAll = new List<ITelemetry>(); + + foreach (var name in names) + { + var collection = _database.GetCollection<ITelemetry>(name); + telemetryAll.AddRange(collection.FindAll().ToList()); + } + + return telemetryAll.OrderBy(x => x.Time).ToList(); + } + } +} diff --git a/Software/Visual_Studio/Tango.Telemetry/TelemetryPublishPackage.cs b/Software/Visual_Studio/Tango.Telemetry/TelemetryPublishPackage.cs new file mode 100644 index 000000000..a3ae6359c --- /dev/null +++ b/Software/Visual_Studio/Tango.Telemetry/TelemetryPublishPackage.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Telemetry +{ + public class TelemetryPublishPackage + { + private String _payload; + + public ITelemetry TelemetryObject { get; set; } + public TelemetrySource Source { get; set; } + + public String ToPayload() + { + if (_payload == null) + { + _payload = TelemetryObject.ToJson(Newtonsoft.Json.Formatting.None, flatten: true); + } + + return _payload; + } + } +} diff --git a/Software/Visual_Studio/Tango.Telemetry/TelemetryPublisher.cs b/Software/Visual_Studio/Tango.Telemetry/TelemetryPublisher.cs new file mode 100644 index 000000000..81cd88b2d --- /dev/null +++ b/Software/Visual_Studio/Tango.Telemetry/TelemetryPublisher.cs @@ -0,0 +1,362 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Timers; +using Tango.Core; +using Tango.Insights; +using Tango.Integration.Operation; +using Tango.PMR.Diagnostics; +using Tango.PMR.Insights; + +namespace Tango.Telemetry +{ + public class TelemetryPublisher : ExtendedObject, IDisposable + { + public const int MIN_SAMPLING_INTERVAL_SECONDS = 1; + + public event EventHandler<TelemetryPackagePublishingEventArgs> PublishingPackage; + public event EventHandler<TelemetryPackagePublishedEventArgs> PackagePublished; + public event EventHandler<TelemetryPackagePublishFailedEventArgs> PublishPackageFailed; + + private System.Timers.Timer _diagnosticsSamplingTimer; + private System.Timers.Timer _pendingStorageCheckTimer; + + private Thread _publishThread; + private ProducerConsumerQueue<TelemetryPublishPackage> _publishQueue; + + private List<StartDiagnosticsResponse> _diagnosticsQueue; + private bool _writing; + private bool _emptyWritten; + + private IMachineOperator _machineOperator; + private TelemetryPendingStorageManager _pendingStorageManager; + + #region Properties + + public TelemetryPublisherConfiguration Config { get; private set; } + public bool IsStarted { get; private set; } + + #endregion + + #region Constructor + + public TelemetryPublisher(IMachineOperator machineOperator, TelemetryPendingStorageManager storageManager, TelemetryPublisherConfiguration config) + { + _machineOperator = machineOperator; + _pendingStorageManager = storageManager; + + Config = config ?? new TelemetryPublisherConfiguration(); + + _publishQueue = new ProducerConsumerQueue<TelemetryPublishPackage>(); + _publishThread = new Thread(PublishThreadMethod); + _publishThread.IsBackground = true; + + _diagnosticsQueue = new List<StartDiagnosticsResponse>(); + + RegisterForEvents(); + } + + #endregion + + #region Register / Unregister Events + + private void RegisterForEvents() + { + _machineOperator.DiagnosticsDataAvailable += MachineOperator_DiagnosticsDataAvailable; + } + + private void UnregisterEvents() + { + _machineOperator.DiagnosticsDataAvailable -= MachineOperator_DiagnosticsDataAvailable; + } + + #endregion + + #region Start / Stop + + public void Start() + { + if (!IsStarted) + { + Config.Validate(); + + IsStarted = true; + + if (Config.DiagnosticsSamplingInterval.TotalSeconds < MIN_SAMPLING_INTERVAL_SECONDS) + { + Config.DiagnosticsSamplingInterval = TimeSpan.FromSeconds(MIN_SAMPLING_INTERVAL_SECONDS); + } + + if (_diagnosticsSamplingTimer == null) + { + _diagnosticsSamplingTimer = new System.Timers.Timer(); + _diagnosticsSamplingTimer.Interval = Config.DiagnosticsSamplingInterval.TotalMilliseconds; + _diagnosticsSamplingTimer.Elapsed += DiagnosticsSamplingTimer_Elapsed; + } + + if (_pendingStorageCheckTimer == null) + { + _pendingStorageCheckTimer = new System.Timers.Timer(); + _pendingStorageCheckTimer.Interval = Config.PendingStorageCheckInterval.TotalMilliseconds; + _pendingStorageCheckTimer.Elapsed += PendingStorageCheckTimer_Elapsed; + } + + _diagnosticsQueue.Clear(); + + _writing = false; + _diagnosticsSamplingTimer.Start(); + _pendingStorageCheckTimer.Start(); + + _publishThread.Start(); + } + } + + public void Stop() + { + if (IsStarted) + { + IsStarted = false; + _diagnosticsSamplingTimer.Stop(); + _pendingStorageCheckTimer.Stop(); + _diagnosticsQueue.Clear(); + _publishQueue.BlockEnqueue(null); + } + } + + #endregion + + #region Incoming Data Event Handlers + + private void MachineOperator_DiagnosticsDataAvailable(object sender, StartDiagnosticsResponse diagnostics) + { + if (IsStarted && diagnostics.Monitors != null) + { + _diagnosticsQueue.Add(diagnostics); + } + } + + #endregion + + #region Timers + + private void DiagnosticsSamplingTimer_Elapsed(object sender, ElapsedEventArgs e) + { + if (!IsStarted || _writing) return; + + try + { + _writing = true; + + if (_diagnosticsQueue.Count > 0) + { + var queue = _diagnosticsQueue.ToList(); + _diagnosticsQueue.Clear(); + _emptyWritten = false; + + var monitorsAvg = InsightsHelper.AverageMonitors(queue.Select(x => x.Monitors).ToList()); + queue.Clear(); + + TelemetryDiagnosticsFrame frame = new TelemetryDiagnosticsFrame(); + frame.Monitors = monitorsAvg; + frame.Time = DateTime.UtcNow.Subtract(Config.DiagnosticsSamplingInterval); + + PushTelemetryPackage(frame, TelemetrySource.Streaming); + } + else + { + if (!_emptyWritten) + { + TelemetryDiagnosticsFrame frame = new TelemetryDiagnosticsFrame(); + frame.Monitors = new InsightsMonitors(); + frame.Time = DateTime.UtcNow.Subtract(Config.DiagnosticsSamplingInterval); + PushTelemetryPackage(frame, TelemetrySource.Streaming); + _emptyWritten = true; + } + } + } + catch (Exception ex) + { + LogManager.Log(ex, "Error occurred on insights frame insertion."); + } + finally + { + _writing = false; + } + } + + private void PendingStorageCheckTimer_Elapsed(object sender, ElapsedEventArgs e) + { + var telemetryAll = _pendingStorageManager.GetTelemetryAll(); + + foreach (var t in telemetryAll) + { + PushTelemetryPackage(t, TelemetrySource.PendingStorage); + } + } + + #endregion + + #region Push + + private void PushTelemetryPackage(ITelemetry telemetryObject, TelemetrySource source) + { + _publishQueue.BlockEnqueue(new TelemetryPublishPackage() { TelemetryObject = telemetryObject, Source = source }); + } + + private void PushTelemetryPackage(TelemetryPublishPackage package) + { + _publishQueue.BlockEnqueue(package); + } + + #endregion + + #region Publish + + private async void PublishThreadMethod() + { + while (IsStarted) + { + TelemetryPublishPackage package = _publishQueue.BlockDequeue(); + if (package == null) + { + _publishQueue = new ProducerConsumerQueue<TelemetryPublishPackage>(); + return; + } + + try + { + await PublishTelemetryPackage(package); + } + catch + { + Thread.Sleep(1000); + } + } + } + + private async Task PublishTelemetryPackage(TelemetryPublishPackage package) + { + List<KeyValuePair<String, String>> properties = new List<KeyValuePair<string, string>>(); + + properties.Add(new KeyValuePair<string, string>("MachineID", Config.MachineID)); + properties.Add(new KeyValuePair<string, string>("Model", Config.MachineType.ToShortName())); + properties.Add(new KeyValuePair<string, string>("Environment", Config.Environment)); + + List<String> pendingDestinations = package.TelemetryObject.PendingDestinations.ToList(); + + //Add all destinations if streaming or external (They will be remove later if successfull) + //If source is "PendingStorage" the "PendingDestination" would be already propagated from the pending storage db. + if (package.Source == TelemetrySource.Streaming || package.Source == TelemetrySource.ExternalStorage) + { + pendingDestinations.AddRange(Config.TelemetryDestinations.Select(x => x.GetType().Name)); + } + + pendingDestinations = pendingDestinations.Distinct().ToList(); + + foreach (var destination in Config.TelemetryDestinations.Where(x => x.SupportedSources.Contains(package.Source))) + { + String destinationName = destination.GetType().Name; + + if (pendingDestinations.Contains(destinationName)) + { + try + { + pendingDestinations.Remove(destinationName); + if (OnPublishingPackage(package, destination)) + { + await destination.Publish(package, properties); + OnPackagePublished(package, destination); + } + } + catch (Exception ex) + { + LogManager.Log(ex, $"Error publishing telemetry package to destination {destinationName}."); + + OnPackagePublishFailed(package, destination, ex); + + if (destination.SupportedSources.Contains(TelemetrySource.PendingStorage)) + { + if (!pendingDestinations.Contains(destinationName)) + { + pendingDestinations.Add(destinationName); + } + } + } + } + } + + package.TelemetryObject.PendingDestinations = pendingDestinations; + + if (package.Source == TelemetrySource.PendingStorage && package.TelemetryObject.PendingDestinations.Count == 0) + { + _pendingStorageManager.DeleteTelemetryObject(package.TelemetryObject); + } + else + { + _pendingStorageManager.InsertOrUpdateTelemetryObject(package.TelemetryObject); + } + } + + #endregion + + #region Virtual Methods + + protected virtual bool OnPublishingPackage(TelemetryPublishPackage package, ITelemetryDestination destination) + { + try + { + var args = new TelemetryPackagePublishingEventArgs() { Package = package, Destination = destination }; + PublishingPackage?.Invoke(this, args); + return !args.Cancel; + } + catch + { + return true; + } + } + + protected virtual void OnPackagePublished(TelemetryPublishPackage package, ITelemetryDestination destination) + { + try + { + PublishingPackage?.Invoke(this, new TelemetryPackagePublishingEventArgs() { Package = package, Destination = destination }); + } + catch { } + } + + protected virtual void OnPackagePublishFailed(TelemetryPublishPackage package, ITelemetryDestination destination, Exception exception) + { + try + { + PublishPackageFailed?.Invoke(this, new TelemetryPackagePublishFailedEventArgs() { Package = package, Destination = destination, Exception = exception }); + } + catch { } + } + + #endregion + + #region Dispose + + public void Dispose() + { + UnregisterEvents(); + + if (IsStarted) + { + Stop(); + } + + foreach (var destination in Config.TelemetryDestinations) + { + destination.Dispose(); + } + } + + #endregion + } +} diff --git a/Software/Visual_Studio/Tango.Telemetry/TelemetryPublisherConfiguration.cs b/Software/Visual_Studio/Tango.Telemetry/TelemetryPublisherConfiguration.cs new file mode 100644 index 000000000..56f826591 --- /dev/null +++ b/Software/Visual_Studio/Tango.Telemetry/TelemetryPublisherConfiguration.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.BL.Enumerations; + +namespace Tango.Telemetry +{ + public class TelemetryPublisherConfiguration + { + public String MachineID { get; set; } + public MachineTypes MachineType { get; set; } + public String Environment { get; set; } + public TimeSpan DiagnosticsSamplingInterval { get; set; } + public TimeSpan PendingStorageCheckInterval { get; set; } + public List<ITelemetryDestination> TelemetryDestinations { get; private set; } + + public TelemetryPublisherConfiguration() + { + TelemetryDestinations = new List<ITelemetryDestination>(); + + DiagnosticsSamplingInterval = TimeSpan.FromSeconds(10); + PendingStorageCheckInterval = TimeSpan.FromMinutes(1); + } + + public void Validate() + { + if (!MachineID.IsNotNullOrEmpty()) throw new ArgumentNullException($"{nameof(MachineID)} is not set."); + if (!Environment.IsNotNullOrEmpty()) throw new ArgumentNullException($"{nameof(Environment)} is not set."); + } + } +} diff --git a/Software/Visual_Studio/Tango.Telemetry/TelemetryPublisherEventArgs.cs b/Software/Visual_Studio/Tango.Telemetry/TelemetryPublisherEventArgs.cs new file mode 100644 index 000000000..d10353d69 --- /dev/null +++ b/Software/Visual_Studio/Tango.Telemetry/TelemetryPublisherEventArgs.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Telemetry +{ + public class TelemetryPublisherEventArgs : EventArgs + { + public TelemetryPublishPackage Package { get; set; } + } +} diff --git a/Software/Visual_Studio/Tango.Telemetry/TelemetrySource.cs b/Software/Visual_Studio/Tango.Telemetry/TelemetrySource.cs new file mode 100644 index 000000000..09bbdc539 --- /dev/null +++ b/Software/Visual_Studio/Tango.Telemetry/TelemetrySource.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Telemetry +{ + public enum TelemetrySource + { + Streaming, + ExternalStorage, + PendingStorage, + } +} diff --git a/Software/Visual_Studio/Tango.Telemetry/packages.config b/Software/Visual_Studio/Tango.Telemetry/packages.config new file mode 100644 index 000000000..b94abaac9 --- /dev/null +++ b/Software/Visual_Studio/Tango.Telemetry/packages.config @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="DotNetty.Buffers" version="0.6.0" targetFramework="net461" /> + <package id="DotNetty.Codecs" version="0.6.0" targetFramework="net461" /> + <package id="DotNetty.Codecs.Mqtt" version="0.6.0" targetFramework="net461" /> + <package id="DotNetty.Common" version="0.6.0" targetFramework="net461" /> + <package id="DotNetty.Handlers" version="0.6.0" targetFramework="net461" /> + <package id="DotNetty.Transport" version="0.6.0" targetFramework="net461" /> + <package id="LiteDB" version="5.0.4" targetFramework="net461" /> + <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net461" /> + <package id="Microsoft.Azure.Amqp" version="2.5.10" targetFramework="net461" /> + <package id="Microsoft.Azure.Devices.Client" version="1.41.0" targetFramework="net461" /> + <package id="Microsoft.Azure.Devices.Shared" version="1.30.1" targetFramework="net461" /> + <package id="Microsoft.Azure.KeyVault.Core" version="1.0.0" targetFramework="net461" /> + <package id="Microsoft.CSharp" version="4.7.0" targetFramework="net461" /> + <package id="Microsoft.Extensions.DependencyInjection.Abstractions" version="1.1.0" targetFramework="net461" /> + <package id="Microsoft.Extensions.Logging" version="1.1.1" targetFramework="net461" /> + <package id="Microsoft.Extensions.Logging.Abstractions" version="1.1.1" targetFramework="net461" /> + <package id="Microsoft.NETCore.Platforms" version="1.1.0" targetFramework="net461" /> + <package id="Microsoft.Owin" version="4.0.0" targetFramework="net461" /> + <package id="Microsoft.Win32.Primitives" version="4.3.0" targetFramework="net461" /> + <package id="MQTTnet" version="3.1.2" targetFramework="net461" /> + <package id="NETStandard.Library" version="1.6.1" targetFramework="net461" /> + <package id="Newtonsoft.Json" version="12.0.3" targetFramework="net461" /> + <package id="Owin" version="1.0" targetFramework="net461" /> + <package id="System.AppContext" version="4.3.0" targetFramework="net461" /> + <package id="System.Collections" version="4.3.0" targetFramework="net461" /> + <package id="System.Collections.Concurrent" version="4.3.0" targetFramework="net461" /> + <package id="System.Collections.Immutable" version="1.5.0" targetFramework="net461" /> + <package id="System.ComponentModel" version="4.3.0" targetFramework="net461" /> + <package id="System.Console" version="4.3.0" targetFramework="net461" /> + <package id="System.Diagnostics.Debug" version="4.3.0" targetFramework="net461" /> + <package id="System.Diagnostics.DiagnosticSource" version="4.3.0" targetFramework="net461" /> + <package id="System.Diagnostics.Tools" version="4.3.0" targetFramework="net461" /> + <package id="System.Diagnostics.Tracing" version="4.3.0" targetFramework="net461" /> + <package id="System.Globalization" version="4.3.0" targetFramework="net461" /> + <package id="System.Globalization.Calendars" version="4.3.0" targetFramework="net461" /> + <package id="System.IO" version="4.3.0" targetFramework="net461" /> + <package id="System.IO.Compression" version="4.3.0" targetFramework="net461" /> + <package id="System.IO.Compression.ZipFile" version="4.3.0" targetFramework="net461" /> + <package id="System.IO.FileSystem" version="4.3.0" targetFramework="net461" /> + <package id="System.IO.FileSystem.Primitives" version="4.3.0" targetFramework="net461" /> + <package id="System.Linq" version="4.3.0" targetFramework="net461" /> + <package id="System.Linq.Expressions" version="4.3.0" targetFramework="net461" /> + <package id="System.Net.Http" version="4.3.0" targetFramework="net461" /> + <package id="System.Net.Primitives" version="4.3.0" targetFramework="net461" /> + <package id="System.Net.Sockets" version="4.3.0" targetFramework="net461" /> + <package id="System.ObjectModel" version="4.3.0" targetFramework="net461" /> + <package id="System.Reflection" version="4.3.0" targetFramework="net461" /> + <package id="System.Reflection.Extensions" version="4.3.0" targetFramework="net461" /> + <package id="System.Reflection.Primitives" version="4.3.0" targetFramework="net461" /> + <package id="System.Resources.ResourceManager" version="4.3.0" targetFramework="net461" /> + <package id="System.Runtime" version="4.3.0" targetFramework="net461" /> + <package id="System.Runtime.CompilerServices.Unsafe" version="4.5.2" targetFramework="net461" /> + <package id="System.Runtime.Extensions" version="4.3.0" targetFramework="net461" /> + <package id="System.Runtime.Handles" version="4.3.0" targetFramework="net461" /> + <package id="System.Runtime.InteropServices" version="4.3.0" targetFramework="net461" /> + <package id="System.Runtime.InteropServices.RuntimeInformation" version="4.3.0" targetFramework="net461" /> + <package id="System.Runtime.Numerics" version="4.3.0" targetFramework="net461" /> + <package id="System.Security.Cryptography.Algorithms" version="4.3.0" targetFramework="net461" /> + <package id="System.Security.Cryptography.Encoding" version="4.3.0" targetFramework="net461" /> + <package id="System.Security.Cryptography.Primitives" version="4.3.0" targetFramework="net461" /> + <package id="System.Security.Cryptography.X509Certificates" version="4.3.0" targetFramework="net461" /> + <package id="System.Text.Encoding" version="4.3.0" targetFramework="net461" /> + <package id="System.Text.Encoding.Extensions" version="4.3.0" targetFramework="net461" /> + <package id="System.Text.RegularExpressions" version="4.3.0" targetFramework="net461" /> + <package id="System.Threading" version="4.3.0" targetFramework="net461" /> + <package id="System.Threading.Tasks" version="4.3.0" targetFramework="net461" /> + <package id="System.Threading.Tasks.Extensions" version="4.5.1" targetFramework="net461" /> + <package id="System.Threading.Timer" version="4.3.0" targetFramework="net461" /> + <package id="System.Xml.ReaderWriter" version="4.3.0" targetFramework="net461" /> + <package id="System.Xml.XDocument" version="4.3.0" targetFramework="net461" /> + <package id="WindowsAzure.Storage" version="9.3.2" targetFramework="net461" /> +</packages>
\ No newline at end of file |
