From a5ecb430773ef29d4a14e6b3dcf7b18d783b615f Mon Sep 17 00:00:00 2001
From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com>
Date: Tue, 13 Jun 2023 20:56:56 +0200
Subject: [PATCH] Implement `NotificationService` and `EnergyPriceService`
---
.gitignore | 1 +
.../Elektrifikatsiya/Pages/Dashboard.razor | 7 +-
.../Elektrifikatsiya/Pages/Dashboard.razor.cs | 18 ++--
.../Elektrifikatsiya/Program.cs | 40 +++++----
.../Services/IEnergyPriceService.cs | 13 +++
.../Implementations/EnergyPriceService.cs | 86 +++++++++++++++++++
.../Implementations/NotifcationService.cs | 25 +++---
.../Utilities/PrometheusQuery.cs | 40 ++++-----
.../appsettings.Development.json | 19 ----
9 files changed, 167 insertions(+), 82 deletions(-)
create mode 100644 src/Elektrifikatsiya/Elektrifikatsiya/Services/IEnergyPriceService.cs
create mode 100644 src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/EnergyPriceService.cs
delete mode 100644 src/Elektrifikatsiya/Elektrifikatsiya/appsettings.Development.json
diff --git a/.gitignore b/.gitignore
index 688c331..85d8065 100644
--- a/.gitignore
+++ b/.gitignore
@@ -353,3 +353,4 @@ MigrationBackup/
*.sqlite*
src/Elektrifikatsiya/Elektrifikatsiya/appsettings.Development.json
/src/Elektrifikatsiya/Elektrifikatsiya/appsettings.Development.json~origin_Development
+/src/Elektrifikatsiya/Elektrifikatsiya/appsettings.Development.json
diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor
index 6269368..b5149d5 100644
--- a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor
+++ b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor
@@ -12,6 +12,7 @@
@inject IDeviceStatusService DeviceStatusService;
@inject IDeviceManagmentService DeviceManagmentService;
@inject IEventService EventService;
+@inject IEnergyPriceService EnergyPriceService;
@inject MainDatabaseContext MainDatabaseContext;
@@ -107,19 +108,19 @@
Daily Cost
-
2€
+
@($"{EnergyPriceService.DailyEnergyCost:F2}")€
Monthly Cost
- 60€
+ @($"{EnergyPriceService.MonthlyEnergyCost:F2}")€
Yearly Cost
- 720€
+ @($"{EnergyPriceService.YearlyEnergyCost:F2}")€
diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor.cs
index f3e4caa..91b8687 100644
--- a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor.cs
+++ b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor.cs
@@ -1,22 +1,18 @@
using Blazorise.Charts;
+using Elektrifikatsiya.Database;
using Elektrifikatsiya.Models;
using Elektrifikatsiya.Utilities;
-using System.Diagnostics;
-using System.Text;
-using Elektrifikatsiya.Services.Implementations;
-using Elektrifikatsiya.Database;
-
namespace Elektrifikatsiya.Pages;
public partial class Dashboard
{
+ private readonly List
labels = new List();
+
//TODO: insert new event here if plug produces one
private List events = new List() { new Event("Text", "Text", DateTime.Today, null) };
- private List labels = new List();
-
//code for graph
private LineChart lineChart;
@@ -36,11 +32,11 @@ public partial class Dashboard
plugs = DeviceStatusService.GetDevices().ValueOrDefault ?? new List();
events = EventService.Events;
- EventService.OnEventCalled += (_, e) =>
+ EventService.OnEventCalled += (__, e) =>
{
_ = InvokeAsync(StateHasChanged);
};
- DeviceStatusService.OnDeviceStatusChanged += (_, e) =>
+ DeviceStatusService.OnDeviceStatusChanged += (__, e) =>
{
_ = InvokeAsync(StateHasChanged);
};
@@ -61,14 +57,14 @@ public partial class Dashboard
}
plugnames = plugnames[0..^1];
- double energyPrice = MainDatabaseContext.EnergyPriceChanges.OrderByDescending(e =>e.DateTime).FirstOrDefault()?.EnergyPrice ?? 0;
+ double energyPrice = MainDatabaseContext.EnergyPriceChanges.OrderByDescending(e => e.DateTime).FirstOrDefault()?.EnergyPrice ?? 0;
PrometheusDataWrapper? deviceData = (await promQueryer.Query($$"""sum(power{sensor=~"{{plugnames}}"})[1h:1m]"""))?.Data;
//PrometheusDataWrapper? priceData = (await promQueryer.Query($$"""sum(sum_over_time(power{sensor=~"{{plugnames}}"}[1y])*{{energyPrice}})"""))?.Data;
//Console.WriteLine(priceData.VectorTypeToTimestampFloatTuple());
- var r = new Random(DateTime.Now.Millisecond);
+ Random r = new Random(DateTime.Now.Millisecond);
return deviceData?.MatrixTypeToTimestampFloatTuple().ValueOrDefault?.Select(x =>
{
diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Program.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Program.cs
index 1e0c08d..c2a81dc 100644
--- a/src/Elektrifikatsiya/Elektrifikatsiya/Program.cs
+++ b/src/Elektrifikatsiya/Elektrifikatsiya/Program.cs
@@ -10,6 +10,7 @@ using Elektrifikatsiya.Models;
using Elektrifikatsiya.Services;
using Elektrifikatsiya.Services.Implementations;
using Elektrifikatsiya.Utilities;
+
using HiveMQtt.Client;
using HiveMQtt.Client.Options;
@@ -30,22 +31,24 @@ builder.Services.AddHostedService();
builder.Services.AddSingleton();
builder.Services.AddSingleton();
builder.Services.AddSingleton();
+builder.Services.AddSingleton();
builder.Services.AddSingleton(s => s.GetRequiredService());
builder.Services.AddSingleton(s => s.GetRequiredService());
+builder.Services.AddSingleton(s => s.GetRequiredService());
builder.Services.AddSingleton();
builder.Services.AddSingleton();
-builder.Services.AddSingleton((provider)=>
+builder.Services.AddSingleton((provider) =>
{
- HiveMQClientOptions options = new()
- {
- Host = "localhost",
- Port = 1883,
- UseTLS = false,
- };
+ HiveMQClientOptions options = new()
+ {
+ Host = "localhost",
+ Port = 1883,
+ UseTLS = false,
+ };
- HiveMQClient client = new(options);
- client.ConnectAsync().ConfigureAwait(false);
- return client;
+ HiveMQClient client = new(options);
+ _ = client.ConnectAsync().ConfigureAwait(false);
+ return client;
});
builder.Services.AddTransient();
builder.Services.AddScoped();
@@ -56,7 +59,7 @@ builder.Services.AddBootstrapProviders();
builder.Services.AddHttpClient();
builder.Services.AddBlazorise(options =>
{
- options.Immediate = true;
+ options.Immediate = true;
});
AddBlazorise(builder.Services);
@@ -66,9 +69,9 @@ WebApplication app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
- _ = app.UseExceptionHandler("/Error");
- // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
- _ = app.UseHsts();
+ _ = app.UseExceptionHandler("/Error");
+ // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
+ _ = app.UseHsts();
}
app.UseHttpsRedirection();
@@ -80,7 +83,6 @@ app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.MapControllers();
-
app.Services.GetRequiredService>().Value.CompileTemplates();
IServiceScope serviceScope = app.Services.GetRequiredService().CreateScope();
@@ -92,14 +94,14 @@ IAuthenticationService authenticationService = serviceScope.ServiceProvider.GetR
if (!mainDatabase.Users.Any())
{
- _ = authenticationService.RegisterUserAsync("admin", "admin", Role.Admin);
+ _ = authenticationService.RegisterUserAsync("admin", "admin", Role.Admin);
}
app.Run();
static void AddBlazorise(IServiceCollection services)
{
- _ = services.AddBlazorise();
- _ = services.AddMaterialProviders();
- _ = services.AddMaterialIcons();
+ _ = services.AddBlazorise();
+ _ = services.AddMaterialProviders();
+ _ = services.AddMaterialIcons();
}
\ No newline at end of file
diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Services/IEnergyPriceService.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Services/IEnergyPriceService.cs
new file mode 100644
index 0000000..179cce7
--- /dev/null
+++ b/src/Elektrifikatsiya/Elektrifikatsiya/Services/IEnergyPriceService.cs
@@ -0,0 +1,13 @@
+using Elektrifikatsiya.Models;
+using Elektrifikatsiya.Utilities;
+
+namespace Elektrifikatsiya.Services;
+
+public interface IEnergyPriceService : IScheduledService
+{
+ public double DailyEnergyCost { get; }
+ public double MonthlyEnergyCost { get; }
+ public double YearlyEnergyCost { get; }
+
+ public Task GetEnergyPrice(DateTime startime, DateTime endTime, List plugs, List changes);
+}
\ No newline at end of file
diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/EnergyPriceService.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/EnergyPriceService.cs
new file mode 100644
index 0000000..e81031c
--- /dev/null
+++ b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/EnergyPriceService.cs
@@ -0,0 +1,86 @@
+using Elektrifikatsiya.Database;
+using Elektrifikatsiya.Models;
+using Elektrifikatsiya.Utilities;
+
+using System.Globalization;
+
+namespace Elektrifikatsiya.Services.Implementations;
+
+public class EnergyPriceService : IEnergyPriceService
+{
+ private readonly MainDatabaseContext mainDatabaseContext;
+ private readonly PrometheusQuery prometheusQuerier = new("http://127.0.0.1:9090");
+
+ public TimeSpan ExecutionRepeatDelay { get; } = TimeSpan.FromSeconds(5);
+ public DateTime FirstExecutionTime { get; } = DateTime.Now.AddSeconds(5);
+ public double MonthlyEnergyCost { get; private set; }
+
+ public double YearlyEnergyCost { get; private set; }
+
+ public double DailyEnergyCost { get; private set; }
+
+ public EnergyPriceService(IServiceScopeFactory serviceScopeFactory)
+ {
+ mainDatabaseContext = serviceScopeFactory.CreateScope().ServiceProvider.GetRequiredService();
+ }
+
+ ///
+ /// Calculate the energy price for the given timespan
+ ///
+ /// The timestamp that has the greater value
+ /// The timestamp that has the lower value
+ ///
+ ///
+ ///
+ ///
+ public async Task GetEnergyPrice(DateTime startime, DateTime endTime, List plugs, List changes)
+ {
+ string plugnames = "";
+ foreach (Device device in plugs)
+ {
+ plugnames += $"shellyplug-s-{device.MacAddress}/relay/0|";
+ }
+ plugnames = plugnames[0..^1];
+
+ if (startime < endTime)
+ {
+ throw new ArgumentException("Read the xmlDoc");
+ }
+
+ changes = changes.OrderByDescending(x => x.DateTime).ToList();
+
+ double total = (await prometheusQuerier.Query($$"""sum_over_time(sum(power{sensor=~"{{plugnames}}"})[{{(int)Math.Ceiling((startime - changes.First().DateTime).TotalMinutes)}}m:1s] @ {{((DateTimeOffset)startime).ToUnixTimeSeconds()}}) * {{(changes.First().EnergyPrice / 3600).ToString(CultureInfo.InvariantCulture)}}"""))?.Data?.VectorTypeToTimestampFloatTuple().ValueOrDefault.Item2 ?? 0;
+
+ for (int i = 0; i < changes.Count - 1; i++)
+ {
+ bool inMinutes = true;
+ int duration = (int)Math.Ceiling((changes[i].DateTime - changes[i + 1].DateTime).TotalMinutes);
+ if (duration > 100000)
+ {
+ inMinutes = false;
+ duration = (int)Math.Ceiling(duration / 1440.0);
+ }
+ long timestamp = ((DateTimeOffset)changes[i].DateTime).ToUnixTimeSeconds();
+
+ double price = changes[i + 1].EnergyPrice;
+
+ total += (await prometheusQuerier.Query($$"""sum_over_time(sum(power{sensor=~"{{plugnames}}"})[{{duration}}{{(inMinutes ? "m" : "d")}}:1s] @ {{timestamp}}) * {{(price / 3600).ToString(CultureInfo.InvariantCulture)}}"""))?.Data?.VectorTypeToTimestampFloatTuple().ValueOrDefault.Item2 ?? 0;
+ if (changes[i].DateTime < endTime)
+ {
+ break;
+ }
+ }
+
+ return total;
+ }
+
+ public async void Update()
+ {
+ List plugs = mainDatabaseContext.Devices.ToList();
+ List energyPriceChanges = mainDatabaseContext.EnergyPriceChanges.ToList();
+
+ DailyEnergyCost = await GetEnergyPrice(DateTime.Now, DateTime.Now.AddDays(-1), plugs, energyPriceChanges);
+ MonthlyEnergyCost = await GetEnergyPrice(DateTime.Now, DateTime.Now.AddMonths(-1), plugs, energyPriceChanges);
+ YearlyEnergyCost = await GetEnergyPrice(DateTime.Now, DateTime.Now.AddYears(-1), plugs, energyPriceChanges);
+ }
+}
\ No newline at end of file
diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/NotifcationService.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/NotifcationService.cs
index 12c6ff0..b8ddd0c 100644
--- a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/NotifcationService.cs
+++ b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/NotifcationService.cs
@@ -1,24 +1,29 @@
-using Elektrifikatsiya.Utilities;
+namespace Elektrifikatsiya.Services.Implementations;
-namespace Elektrifikatsiya.Services.Implementations;
-
-public class NotifcationService : INotifcationService
+public class NotifcationService : INotificationService
{
+ private readonly IEmailService emailService;
+ private readonly IEnergyPriceService energyPriceService;
+ private readonly Microsoft.Extensions.Configuration.IConfiguration configuration;
+ private readonly ILogger logger;
public TimeSpan ExecutionRepeatDelay => TimeSpan.FromDays(1);
public DateTime FirstExecutionTime => DateTime.Now.AddDays(0);
- private readonly IEmailService emailService;
- private readonly ILogger logger;
- public NotifcationService(ILogger logger, IEmailService emailService)
+ public NotifcationService(ILogger logger, IEmailService emailService, IEnergyPriceService energyPriceService, Microsoft.Extensions.Configuration.IConfiguration configuration)
{
this.logger = logger;
this.emailService = emailService;
+ this.energyPriceService = energyPriceService;
+ this.configuration = configuration;
}
public async void Update()
{
- //logger.LogInformation("Sending Email");
- //await emailService.SendWithTemeplateAsync("manuel.strubegger@htl-saalfelden.at", "AAAA", "DailyNotification", templateParameters: new() { { "yesterday", "10" }, { "yearly", "100" }, { "link", "localhost:5565/yes" } });
- //logger.LogInformation("Done sending");
+ logger.LogInformation("Sending Email");
+
+ string url = configuration.GetValue("Url") ?? "https://http.cat/images/404.jpg";
+
+ await emailService.SendWithTemeplateAsync("stone-red@stone-red.net", "Daily Notification", "DailyNotification", templateParameters: new() { { "yesterday", $"{energyPriceService.DailyEnergyCost:F2}" }, { "yearly", $"{energyPriceService.YearlyEnergyCost:F2}" }, { "link", url } });
+ logger.LogInformation("Done sending");
}
}
\ No newline at end of file
diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Utilities/PrometheusQuery.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Utilities/PrometheusQuery.cs
index c952d7f..975cd16 100644
--- a/src/Elektrifikatsiya/Elektrifikatsiya/Utilities/PrometheusQuery.cs
+++ b/src/Elektrifikatsiya/Elektrifikatsiya/Utilities/PrometheusQuery.cs
@@ -1,31 +1,31 @@
-using System.Net;
-using System.Net.Sockets;
+using Elektrifikatsiya.Models;
+
+using System.Diagnostics;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;
-using Elektrifikatsiya.Models;
-using FluentResults;
namespace Elektrifikatsiya.Utilities;
public class PrometheusQuery
{
- private readonly HttpClient client = new();
+ private readonly HttpClient client = new();
- public PrometheusQuery(string connectionString)
- {
- client.BaseAddress = new Uri(connectionString);
- }
+ public PrometheusQuery(string connectionString)
+ {
+ client.BaseAddress = new Uri(connectionString);
+ }
- public Task Query(string query)
- {
- return client.GetFromJsonAsync($"/api/v1/query?query={UrlEncoder.Create().Encode(query)}", new JsonSerializerOptions()
- {
- PropertyNameCaseInsensitive = true,
- Converters =
- {
- new JsonStringEnumConverter()
- }
- });
- }
+ public Task Query(string query)
+ {
+ Debug.WriteLine($"{client.BaseAddress}/api/v1/query?query={query}");
+ return client.GetFromJsonAsync($"/api/v1/query?query={UrlEncoder.Create().Encode(query)}", new JsonSerializerOptions()
+ {
+ PropertyNameCaseInsensitive = true,
+ Converters =
+ {
+ new JsonStringEnumConverter()
+ }
+ });
+ }
}
\ No newline at end of file
diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/appsettings.Development.json b/src/Elektrifikatsiya/Elektrifikatsiya/appsettings.Development.json
deleted file mode 100644
index 617c1e4..0000000
--- a/src/Elektrifikatsiya/Elektrifikatsiya/appsettings.Development.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "DetailedErrors": true,
- "Logging": {
- "LogLevel": {
- "Default": "Information",
- "Microsoft.AspNetCore": "Warning"
- }
- },
- "EmailSettings": {
- "User": "",
- "Key": "",
- "DefaultEmail": "SMTP_EMAIL_ADDRESS",
- "Templates": {
- "DailyNotification": "../assets/DailyNotif.html"
- },
- "SmtpServer": "",
- "SmtpPort": 0
- }
-}