aboutsummaryrefslogtreecommitdiffstats
path: root/Software
diff options
context:
space:
mode:
authorRoy Ben Shabat <Roy.mail.net@gmail.com>2025-07-29 00:54:47 +0300
committerRoy Ben Shabat <Roy.mail.net@gmail.com>2025-07-29 00:54:47 +0300
commit70f9f4dcbe3d4537ff2bd503f18288b9a7b0e19e (patch)
treeab7c70dbe23c04e9e6ea9a165b6840e0b2ed1df7 /Software
parentd70056692f43b2a39dbaefebe7c6e096a1205fb4 (diff)
downloadTango-70f9f4dcbe3d4537ff2bd503f18288b9a7b0e19e.tar.gz
Tango-70f9f4dcbe3d4537ff2bd503f18288b9a7b0e19e.zip
Tango.Telemetry v1
Diffstat (limited to 'Software')
-rw-r--r--Software/Visual_Studio/Tango.Telemetry/Destinations/AzureHubTelemetryDestination.cs56
-rw-r--r--Software/Visual_Studio/Tango.Telemetry/Destinations/MqttTelemetryDestination.cs101
-rw-r--r--Software/Visual_Studio/Tango.Telemetry/ExtensionMethods/ITelemetryExtensions.cs15
-rw-r--r--Software/Visual_Studio/Tango.Telemetry/ITelemetry.cs20
-rw-r--r--Software/Visual_Studio/Tango.Telemetry/ITelemetryDestination.cs15
-rw-r--r--Software/Visual_Studio/Tango.Telemetry/JsonFlattener.cs89
-rw-r--r--Software/Visual_Studio/Tango.Telemetry/Properties/AssemblyInfo.cs36
-rw-r--r--Software/Visual_Studio/Tango.Telemetry/Tango.Telemetry.csproj257
-rw-r--r--Software/Visual_Studio/Tango.Telemetry/TelemetryBase.cs40
-rw-r--r--Software/Visual_Studio/Tango.Telemetry/TelemetryDiagnosticsFrame.cs18
-rw-r--r--Software/Visual_Studio/Tango.Telemetry/TelemetryJobRun.cs14
-rw-r--r--Software/Visual_Studio/Tango.Telemetry/TelemetryNameAttribute.cs18
-rw-r--r--Software/Visual_Studio/Tango.Telemetry/TelemetryPackagePublishFailedEventArgs.cs14
-rw-r--r--Software/Visual_Studio/Tango.Telemetry/TelemetryPackagePublishedEventArgs.cs13
-rw-r--r--Software/Visual_Studio/Tango.Telemetry/TelemetryPackagePublishingEventArgs.cs14
-rw-r--r--Software/Visual_Studio/Tango.Telemetry/TelemetryPendingStorageManager.cs80
-rw-r--r--Software/Visual_Studio/Tango.Telemetry/TelemetryPublishPackage.cs26
-rw-r--r--Software/Visual_Studio/Tango.Telemetry/TelemetryPublisher.cs362
-rw-r--r--Software/Visual_Studio/Tango.Telemetry/TelemetryPublisherConfiguration.cs33
-rw-r--r--Software/Visual_Studio/Tango.Telemetry/TelemetryPublisherEventArgs.cs13
-rw-r--r--Software/Visual_Studio/Tango.Telemetry/TelemetrySource.cs15
-rw-r--r--Software/Visual_Studio/Tango.Telemetry/packages.config74
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