diff --git a/.gitignore b/.gitignore index e258c5e..4bb99c7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ bin/ obj/ /packages/ .idea/ -.vs \ No newline at end of file +.vs +NucuCar.Sensors/firebaseConfig.json \ No newline at end of file diff --git a/NucuCar.Domain/NucuCar.Domain.csproj b/NucuCar.Domain/NucuCar.Domain.csproj index e97d6f1..c323176 100644 --- a/NucuCar.Domain/NucuCar.Domain.csproj +++ b/NucuCar.Domain/NucuCar.Domain.csproj @@ -16,6 +16,7 @@ + diff --git a/NucuCar.Domain/Telemetry/TelemetryPublisher.cs b/NucuCar.Domain/Telemetry/TelemetryPublisher.cs index 4b66135..86f4644 100644 --- a/NucuCar.Domain/Telemetry/TelemetryPublisher.cs +++ b/NucuCar.Domain/Telemetry/TelemetryPublisher.cs @@ -115,7 +115,7 @@ namespace NucuCar.Domain.Telemetry var metadata = new Dictionary { ["source"] = TelemetrySource ?? nameof(TelemetryPublisher), - ["timestamp"] = DateTime.Now, + ["timestamp"] = DateTime.UtcNow, ["data"] = data.ToArray() }; return metadata; diff --git a/NucuCar.Domain/Telemetry/TelemetryPublisherAzure.cs b/NucuCar.Domain/Telemetry/TelemetryPublisherAzure.cs index df56bd3..bfbc376 100644 --- a/NucuCar.Domain/Telemetry/TelemetryPublisherAzure.cs +++ b/NucuCar.Domain/Telemetry/TelemetryPublisherAzure.cs @@ -8,6 +8,13 @@ using Newtonsoft.Json; namespace NucuCar.Domain.Telemetry { + /// + /// Constructs an instance of . It is used to publish telemetry to Microsoft + /// Azure IotHub + /// + /// The connection string can be found in your Azure IotHub. + /// + /// public class TelemetryPublisherAzure : TelemetryPublisher { protected readonly DeviceClient DeviceClient; diff --git a/NucuCar.Domain/Telemetry/TelemetryPublisherDisk.cs b/NucuCar.Domain/Telemetry/TelemetryPublisherDisk.cs index 95e9d71..7c01e13 100644 --- a/NucuCar.Domain/Telemetry/TelemetryPublisherDisk.cs +++ b/NucuCar.Domain/Telemetry/TelemetryPublisherDisk.cs @@ -11,7 +11,7 @@ using NucuCar.Domain.Utilities; namespace NucuCar.Domain.Telemetry { /// - /// 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. /// public class TelemetryPublisherDisk : TelemetryPublisher { @@ -22,7 +22,7 @@ namespace NucuCar.Domain.Telemetry /// Constructs an instance of . /// /// 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", ","); diff --git a/NucuCar.Domain/Telemetry/TelemetryPublisherFactory.cs b/NucuCar.Domain/Telemetry/TelemetryPublisherFactory.cs index f6c28bb..4782df5 100644 --- a/NucuCar.Domain/Telemetry/TelemetryPublisherFactory.cs +++ b/NucuCar.Domain/Telemetry/TelemetryPublisherFactory.cs @@ -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}.") }; } diff --git a/NucuCar.Domain/Telemetry/TelemetryPublisherFirestore.cs b/NucuCar.Domain/Telemetry/TelemetryPublisherFirestore.cs new file mode 100644 index 0000000..8f7ad1b --- /dev/null +++ b/NucuCar.Domain/Telemetry/TelemetryPublisherFirestore.cs @@ -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 +{ + /// + /// 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 + /// + /// 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 + /// + /// + 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() + { + } + } +} diff --git a/NucuCar.Domain/Telemetry/TelemetryPublisherType.cs b/NucuCar.Domain/Telemetry/TelemetryPublisherType.cs index f4f691f..f1b664d 100644 --- a/NucuCar.Domain/Telemetry/TelemetryPublisherType.cs +++ b/NucuCar.Domain/Telemetry/TelemetryPublisherType.cs @@ -8,5 +8,6 @@ namespace NucuCar.Domain.Telemetry { public const string Azure = "Azure"; public const string Disk = "Disk"; + public const string Firestore = "Firestore"; } } \ No newline at end of file