NUC-27: Implement Firebase firestore telemetry publisher

This commit is contained in:
Denis-Cosmin Nutiu 2020-02-08 19:47:44 +02:00
parent c78fef2ea0
commit 3f8edbe608
8 changed files with 95 additions and 5 deletions

1
.gitignore vendored
View file

@ -3,3 +3,4 @@ obj/
/packages/
.idea/
.vs
NucuCar.Sensors/firebaseConfig.json

View file

@ -16,6 +16,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Google.Cloud.Firestore" Version="1.1.0" />
<PackageReference Include="Google.Protobuf" Version="3.10.1" />
<PackageReference Include="Grpc" Version="2.25.0" />
<PackageReference Include="Grpc.Tools" Version="2.25.0" />

View file

@ -115,7 +115,7 @@ namespace NucuCar.Domain.Telemetry
var metadata = new Dictionary<string, object>
{
["source"] = TelemetrySource ?? nameof(TelemetryPublisher),
["timestamp"] = DateTime.Now,
["timestamp"] = DateTime.UtcNow,
["data"] = data.ToArray()
};
return metadata;

View file

@ -8,6 +8,13 @@ using Newtonsoft.Json;
namespace NucuCar.Domain.Telemetry
{
/// <summary>
/// Constructs an instance of <see cref="TelemetryPublisherAzure"/>. It is used to publish telemetry to Microsoft
/// Azure IotHub
/// <remarks>
/// The connection string can be found in your Azure IotHub.
/// </remarks>
/// </summary>
public class TelemetryPublisherAzure : TelemetryPublisher
{
protected readonly DeviceClient DeviceClient;

View file

@ -11,7 +11,7 @@ using NucuCar.Domain.Utilities;
namespace NucuCar.Domain.Telemetry
{
/// <summary>
/// The TelemetryPublisherDisk is used to publish telemetry data to a file residing on the disk.
/// The TelemetryPublisherDisk is used to publish telemetry data to a file on the disk.
/// </summary>
public class TelemetryPublisherDisk : TelemetryPublisher
{
@ -22,7 +22,7 @@ namespace NucuCar.Domain.Telemetry
/// Constructs an instance of <see cref="TelemetryPublisherDisk"/>.
/// <remarks>
/// The connection string must contain the following options:
/// Filename (required) - The path of the filename in which to log telemetry data.
/// Filename (optional) - The path of the filename in which to log telemetry data.
/// FileExtension (optional) - The extension of the filename. Default csv
/// Separator (optional) - The separator of the messages. Default ,
/// BufferSize (optional) - The buffer size for the async writer. Default: 4096
@ -32,7 +32,7 @@ namespace NucuCar.Domain.Telemetry
public TelemetryPublisherDisk(TelemetryPublisherBuilderOptions opts) : base(opts)
{
var connectionStringParams = ConnectionStringParser.Parse(opts.ConnectionString);
var fileName = connectionStringParams.GetValueOrDefault("FileName");
var fileName = connectionStringParams.GetValueOrDefault("FileName", "telemetry");
var fileExtension = connectionStringParams.GetValueOrDefault("FileExtension", "csv");
var bufferSize = connectionStringParams.GetValueOrDefault("BufferSize", "4096");
_separator = connectionStringParams.GetValueOrDefault("Separator", ",");

View file

@ -47,6 +47,7 @@ namespace NucuCar.Domain.Telemetry
{
TelemetryPublisherType.Azure => (TelemetryPublisher) new TelemetryPublisherAzure(opts),
TelemetryPublisherType.Disk => new TelemetryPublisherDisk(opts),
TelemetryPublisherType.Firestore => new TelemetryPublisherFirestore(opts),
_ => throw new ArgumentException($"Invalid TelemetryPublisher type: {type}.")
};
}

View file

@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Google.Cloud.Firestore;
using Microsoft.Extensions.Logging;
using NucuCar.Domain.Utilities;
namespace NucuCar.Domain.Telemetry
{
/// <summary>
/// This class is used to publish the telemetry data to Google's Cloud Firestore.
/// Requires the environment variable: GOOGLE_APPLICATION_CREDENTIALS to be set.
/// See: https://cloud.google.com/docs/authentication/getting-started
/// or Firebase > Project Settings > Service Accounts
/// <remarks>
/// The connection string has the following parameters:
/// ProjectId (required) — The string for the Firestore project id.
/// CollectionName (required) — The string for the Firestore collection name.
/// Timeout (optional) — The number in milliseconds in which to timeout if publishing fails. Default: 10000
/// </remarks>
/// </summary>
public class TelemetryPublisherFirestore : TelemetryPublisher
{
private readonly FirestoreDb _database;
private readonly string _firestoreCollection;
private readonly int _timeout;
public TelemetryPublisherFirestore(TelemetryPublisherBuilderOptions opts) : base(opts)
{
var options = ConnectionStringParser.Parse(opts.ConnectionString);
if (!options.TryGetValue("ProjectId", out var firestoreProjectId))
{
Logger?.LogCritical(
$"Can't start {nameof(TelemetryPublisherFirestore)}! Malformed connection string! " +
$"Missing ProjectId!");
}
if (!options.TryGetValue("CollectionName", out _firestoreCollection))
{
Logger?.LogCritical(
$"Can't start {nameof(TelemetryPublisherFirestore)}! Malformed connection string! " +
$"Missing CollectionName!");
}
_timeout = int.Parse(options.GetValueOrDefault("Timeout", "10000"));
_database = FirestoreDb.Create(firestoreProjectId);
Logger?.LogInformation($"Initialized {nameof(TelemetryPublisherFirestore)}");
}
public override async Task PublishAsync(CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
var docRef = _database.Collection(_firestoreCollection).Document();
var data = GetTelemetry();
var cts = new CancellationTokenSource();
cts.CancelAfter(_timeout);
try
{
await docRef.SetAsync(data, cancellationToken: cts.Token);
Logger?.LogInformation("Published data to Firestore!");
}
catch (Exception e)
{
Logger?.LogError($"Failed to publish telemetry data!\n{e.GetType().FullName}: {e.Message}");
throw;
}
}
public override void Dispose()
{
}
}
}

View file

@ -8,5 +8,6 @@ namespace NucuCar.Domain.Telemetry
{
public const string Azure = "Azure";
public const string Disk = "Disk";
public const string Firestore = "Firestore";
}
}