2019-11-17 16:27:58 +00:00
|
|
|
using System;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Text;
|
|
|
|
using System.Threading;
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
using Microsoft.Azure.Devices.Client;
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
using Newtonsoft.Json;
|
|
|
|
|
|
|
|
namespace NucuCar.Domain.Telemetry
|
|
|
|
{
|
2019-11-23 18:53:04 +00:00
|
|
|
public class TelemetryPublisherAzure : TelemetryPublisher
|
2019-11-17 16:27:58 +00:00
|
|
|
{
|
2019-11-23 18:53:04 +00:00
|
|
|
protected readonly DeviceClient DeviceClient;
|
|
|
|
|
|
|
|
public TelemetryPublisherAzure(TelemetryPublisherBuilderOptions opts) : base(opts)
|
2019-11-17 16:27:58 +00:00
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
2019-11-23 14:09:44 +00:00
|
|
|
DeviceClient = DeviceClient.CreateFromConnectionString(ConnectionString, TransportType.Mqtt);
|
2019-11-17 16:27:58 +00:00
|
|
|
}
|
|
|
|
catch (FormatException)
|
|
|
|
{
|
2019-11-23 18:53:04 +00:00
|
|
|
Logger?.LogCritical("Can't start telemetry service! Malformed connection string!");
|
2019-11-17 16:27:58 +00:00
|
|
|
throw;
|
|
|
|
}
|
2019-11-23 18:53:04 +00:00
|
|
|
|
2019-12-01 16:04:46 +00:00
|
|
|
Logger?.LogDebug("Started the AzureTelemetryPublisher!");
|
2019-11-23 18:53:04 +00:00
|
|
|
}
|
|
|
|
|
2019-11-24 13:12:12 +00:00
|
|
|
/// <summary>
|
|
|
|
/// Creates an instance of <see cref="TelemetryPublisher"/> that is used to publish data to Microsoft Azure.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="connectionString">The device connection string for Microsoft Azure IoT hub device.</param>
|
|
|
|
/// <returns>A <see cref="TelemetryPublisher"/> instance.</returns>
|
2019-11-23 18:53:04 +00:00
|
|
|
public static TelemetryPublisher CreateFromConnectionString(string connectionString)
|
|
|
|
{
|
2019-11-23 20:18:25 +00:00
|
|
|
Guard.ArgumentNotNullOrWhiteSpace(nameof(connectionString), connectionString);
|
2019-11-23 18:53:04 +00:00
|
|
|
return new TelemetryPublisherAzure(new TelemetryPublisherBuilderOptions()
|
|
|
|
{ConnectionString = connectionString, TelemetrySource = "TelemetryPublisherAzure"});
|
|
|
|
}
|
2019-11-24 13:12:12 +00:00
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Creates an instance of <see cref="TelemetryPublisher"/> that is used to publish data to Microsoft Azure.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="connectionString">Device connection string for Microsoft Azure IoT hub device.</param>
|
|
|
|
/// <param name="telemetrySource">String that is used to identify the source of the telemetry data.</param>
|
|
|
|
/// <returns>A <see cref="TelemetryPublisher"/> instance.</returns>
|
2019-11-23 18:53:04 +00:00
|
|
|
public static TelemetryPublisher CreateFromConnectionString(string connectionString,
|
|
|
|
string telemetrySource)
|
|
|
|
{
|
2019-11-23 20:18:25 +00:00
|
|
|
Guard.ArgumentNotNullOrWhiteSpace(nameof(connectionString), connectionString);
|
|
|
|
Guard.ArgumentNotNullOrWhiteSpace(nameof(telemetrySource), telemetrySource);
|
2019-11-23 18:53:04 +00:00
|
|
|
return new TelemetryPublisherAzure(new TelemetryPublisherBuilderOptions()
|
|
|
|
{ConnectionString = connectionString, TelemetrySource = telemetrySource});
|
|
|
|
}
|
2019-11-24 13:12:12 +00:00
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Creates an instance of <see cref="TelemetryPublisher"/> that is used to publish data to Microsoft Azure.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="connectionString">Device connection string for Microsoft Azure IoT hub device.</param>
|
|
|
|
/// <param name="telemetrySource">String that is used to identify the source of the telemetry data.</param>
|
|
|
|
/// <param name="logger">An <see cref="ILogger"/> logger instance. </param>
|
|
|
|
/// <returns>A <see cref="TelemetryPublisher"/> instance.</returns>
|
2019-11-23 18:53:04 +00:00
|
|
|
public static TelemetryPublisher CreateFromConnectionString(string connectionString,
|
|
|
|
string telemetrySource, ILogger logger)
|
|
|
|
{
|
2019-11-23 20:18:25 +00:00
|
|
|
Guard.ArgumentNotNullOrWhiteSpace(nameof(connectionString), connectionString);
|
|
|
|
Guard.ArgumentNotNullOrWhiteSpace(nameof(telemetrySource), telemetrySource);
|
|
|
|
Guard.ArgumentNotNull(nameof(logger), logger);
|
2019-11-23 18:53:04 +00:00
|
|
|
return new TelemetryPublisherAzure(new TelemetryPublisherBuilderOptions()
|
|
|
|
{ConnectionString = connectionString, TelemetrySource = telemetrySource, Logger = logger});
|
2019-11-17 16:27:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public override async Task PublishAsync(CancellationToken cancellationToken)
|
|
|
|
{
|
2019-12-05 20:40:24 +00:00
|
|
|
var data = GetTelemetry();
|
|
|
|
|
|
|
|
var messageString = JsonConvert.SerializeObject(data);
|
|
|
|
Logger?.LogDebug($"Telemetry message: {messageString}");
|
|
|
|
var message = new Message(Encoding.ASCII.GetBytes(messageString));
|
|
|
|
|
|
|
|
await PublishToCloudAsync(message, cancellationToken);
|
|
|
|
}
|
|
|
|
|
|
|
|
private Dictionary<string, object> GetTelemetry()
|
|
|
|
{
|
|
|
|
var data = new List<Dictionary<string, object>>();
|
2019-11-17 16:27:58 +00:00
|
|
|
foreach (var telemeter in RegisteredTelemeters)
|
|
|
|
{
|
2019-12-05 20:40:24 +00:00
|
|
|
var telemetryData = telemeter.GetTelemetryData();
|
|
|
|
if (telemetryData == null)
|
2019-11-17 16:27:58 +00:00
|
|
|
{
|
2019-11-23 18:53:04 +00:00
|
|
|
Logger?.LogWarning($"Warning! Data for {telemeter.GetIdentifier()} is null!");
|
2019-11-17 16:27:58 +00:00
|
|
|
continue;
|
|
|
|
}
|
2019-11-23 18:53:04 +00:00
|
|
|
|
2019-12-05 20:40:24 +00:00
|
|
|
telemetryData["_id"] = telemeter.GetIdentifier();
|
|
|
|
data.Add(telemetryData);
|
2019-11-17 16:27:58 +00:00
|
|
|
}
|
2019-12-05 20:40:24 +00:00
|
|
|
|
|
|
|
var metadata = new Dictionary<string, object>
|
|
|
|
{
|
|
|
|
["source"] = TelemetrySource ?? nameof(TelemetryPublisherAzure),
|
|
|
|
["timestamp"] = DateTime.Now,
|
|
|
|
["data"] = data.ToArray()
|
|
|
|
};
|
|
|
|
return metadata;
|
2019-11-17 16:27:58 +00:00
|
|
|
}
|
|
|
|
|
2019-12-05 20:40:24 +00:00
|
|
|
private async Task PublishToCloudAsync(Message message, CancellationToken cancellationToken, int maxRetries = 3)
|
2019-11-17 16:27:58 +00:00
|
|
|
{
|
2019-12-05 20:40:24 +00:00
|
|
|
var retry = 0;
|
|
|
|
while (retry < maxRetries)
|
2019-11-17 16:27:58 +00:00
|
|
|
{
|
2019-12-05 20:40:24 +00:00
|
|
|
if (cancellationToken.IsCancellationRequested)
|
|
|
|
{
|
|
|
|
Logger?.LogInformation("Publishing telemetry cancelled!");
|
|
|
|
break;
|
|
|
|
}
|
2019-11-23 18:53:04 +00:00
|
|
|
|
2019-12-05 20:40:24 +00:00
|
|
|
try
|
|
|
|
{
|
|
|
|
var cts = new CancellationTokenSource();
|
|
|
|
cts.CancelAfter(5000);
|
|
|
|
cts.Token.ThrowIfCancellationRequested();
|
|
|
|
|
|
|
|
/* Should throw OperationCanceledException on timeout or cancel. */
|
|
|
|
await DeviceClient.SendEventAsync(message, cts.Token);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
catch (OperationCanceledException e)
|
|
|
|
{
|
|
|
|
retry += 1;
|
|
|
|
Logger?.LogWarning($"Telemetry not sent! Retry {retry}.");
|
|
|
|
}
|
|
|
|
}
|
2019-11-17 16:27:58 +00:00
|
|
|
}
|
2019-11-23 18:53:04 +00:00
|
|
|
|
|
|
|
public override void Dispose()
|
2019-11-17 16:27:58 +00:00
|
|
|
{
|
2019-11-23 18:53:04 +00:00
|
|
|
DeviceClient?.CloseAsync().GetAwaiter().GetResult();
|
2019-11-17 16:27:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|