Implement NotificationService and EnergyPriceService

This commit is contained in:
Stone_Red
2023-06-13 21:02:50 +02:00
parent 5b8915d7b4
commit a5ecb43077
9 changed files with 167 additions and 82 deletions
+1
View File
@@ -353,3 +353,4 @@ MigrationBackup/
*.sqlite* *.sqlite*
src/Elektrifikatsiya/Elektrifikatsiya/appsettings.Development.json src/Elektrifikatsiya/Elektrifikatsiya/appsettings.Development.json
/src/Elektrifikatsiya/Elektrifikatsiya/appsettings.Development.json~origin_Development /src/Elektrifikatsiya/Elektrifikatsiya/appsettings.Development.json~origin_Development
/src/Elektrifikatsiya/Elektrifikatsiya/appsettings.Development.json
@@ -12,6 +12,7 @@
@inject IDeviceStatusService DeviceStatusService; @inject IDeviceStatusService DeviceStatusService;
@inject IDeviceManagmentService DeviceManagmentService; @inject IDeviceManagmentService DeviceManagmentService;
@inject IEventService EventService; @inject IEventService EventService;
@inject IEnergyPriceService EnergyPriceService;
@inject MainDatabaseContext MainDatabaseContext; @inject MainDatabaseContext MainDatabaseContext;
<Div> <Div>
@@ -107,19 +108,19 @@
<Div Flex="Flex.JustifyContent.Between" Width="Width.Is100"> <Div Flex="Flex.JustifyContent.Between" Width="Width.Is100">
<Heading Size="HeadingSize.Is6">Daily Cost</Heading> <Heading Size="HeadingSize.Is6">Daily Cost</Heading>
</Div> </Div>
<Paragraph Margin="Margin.Is1.FromBottom">2€</Paragraph> <Paragraph Margin="Margin.Is1.FromBottom">@($"{EnergyPriceService.DailyEnergyCost:F2}")€</Paragraph>
</ListGroupItem> </ListGroupItem>
<ListGroupItem> <ListGroupItem>
<Div Flex="Flex.JustifyContent.Between" Width="Width.Is100"> <Div Flex="Flex.JustifyContent.Between" Width="Width.Is100">
<Heading Size="HeadingSize.Is6">Monthly Cost</Heading> <Heading Size="HeadingSize.Is6">Monthly Cost</Heading>
</Div> </Div>
<Paragraph Margin="Margin.Is1.FromBottom">60€</Paragraph> <Paragraph Margin="Margin.Is1.FromBottom">@($"{EnergyPriceService.MonthlyEnergyCost:F2}")€</Paragraph>
</ListGroupItem> </ListGroupItem>
<ListGroupItem> <ListGroupItem>
<Div Flex="Flex.JustifyContent.Between" Width="Width.Is100"> <Div Flex="Flex.JustifyContent.Between" Width="Width.Is100">
<Heading Size="HeadingSize.Is6">Yearly Cost</Heading> <Heading Size="HeadingSize.Is6">Yearly Cost</Heading>
</Div> </Div>
<Paragraph Margin="Margin.Is1.FromBottom">720€</Paragraph> <Paragraph Margin="Margin.Is1.FromBottom">@($"{EnergyPriceService.YearlyEnergyCost:F2}")€</Paragraph>
</ListGroupItem> </ListGroupItem>
</ListGroup> </ListGroup>
@@ -1,22 +1,18 @@
using Blazorise.Charts; using Blazorise.Charts;
using Elektrifikatsiya.Database;
using Elektrifikatsiya.Models; using Elektrifikatsiya.Models;
using Elektrifikatsiya.Utilities; using Elektrifikatsiya.Utilities;
using System.Diagnostics;
using System.Text;
using Elektrifikatsiya.Services.Implementations;
using Elektrifikatsiya.Database;
namespace Elektrifikatsiya.Pages; namespace Elektrifikatsiya.Pages;
public partial class Dashboard public partial class Dashboard
{ {
private readonly List<string> labels = new List<string>();
//TODO: insert new event here if plug produces one //TODO: insert new event here if plug produces one
private List<Event> events = new List<Event>() { new Event("Text", "Text", DateTime.Today, null) }; private List<Event> events = new List<Event>() { new Event("Text", "Text", DateTime.Today, null) };
private List<string> labels = new List<string>();
//code for graph //code for graph
private LineChart<double> lineChart; private LineChart<double> lineChart;
@@ -36,11 +32,11 @@ public partial class Dashboard
plugs = DeviceStatusService.GetDevices().ValueOrDefault ?? new List<Device>(); plugs = DeviceStatusService.GetDevices().ValueOrDefault ?? new List<Device>();
events = EventService.Events; events = EventService.Events;
EventService.OnEventCalled += (_, e) => EventService.OnEventCalled += (__, e) =>
{ {
_ = InvokeAsync(StateHasChanged); _ = InvokeAsync(StateHasChanged);
}; };
DeviceStatusService.OnDeviceStatusChanged += (_, e) => DeviceStatusService.OnDeviceStatusChanged += (__, e) =>
{ {
_ = InvokeAsync(StateHasChanged); _ = InvokeAsync(StateHasChanged);
}; };
@@ -68,7 +64,7 @@ public partial class Dashboard
//Console.WriteLine(priceData.VectorTypeToTimestampFloatTuple()); //Console.WriteLine(priceData.VectorTypeToTimestampFloatTuple());
var r = new Random(DateTime.Now.Millisecond); Random r = new Random(DateTime.Now.Millisecond);
return deviceData?.MatrixTypeToTimestampFloatTuple().ValueOrDefault?.Select(x => return deviceData?.MatrixTypeToTimestampFloatTuple().ValueOrDefault?.Select(x =>
{ {
@@ -10,6 +10,7 @@ using Elektrifikatsiya.Models;
using Elektrifikatsiya.Services; using Elektrifikatsiya.Services;
using Elektrifikatsiya.Services.Implementations; using Elektrifikatsiya.Services.Implementations;
using Elektrifikatsiya.Utilities; using Elektrifikatsiya.Utilities;
using HiveMQtt.Client; using HiveMQtt.Client;
using HiveMQtt.Client.Options; using HiveMQtt.Client.Options;
@@ -30,8 +31,10 @@ builder.Services.AddHostedService<ServiceScheduler>();
builder.Services.AddSingleton<IUpdateService, UpdateService>(); builder.Services.AddSingleton<IUpdateService, UpdateService>();
builder.Services.AddSingleton<IDeviceStatusService, DeviceStatusService>(); builder.Services.AddSingleton<IDeviceStatusService, DeviceStatusService>();
builder.Services.AddSingleton<INotificationService, NotifcationService>(); builder.Services.AddSingleton<INotificationService, NotifcationService>();
builder.Services.AddSingleton<IEnergyPriceService, EnergyPriceService>();
builder.Services.AddSingleton<IScheduledService>(s => s.GetRequiredService<IUpdateService>()); builder.Services.AddSingleton<IScheduledService>(s => s.GetRequiredService<IUpdateService>());
builder.Services.AddSingleton<IScheduledService>(s => s.GetRequiredService<INotificationService>()); builder.Services.AddSingleton<IScheduledService>(s => s.GetRequiredService<INotificationService>());
builder.Services.AddSingleton<IScheduledService>(s => s.GetRequiredService<IEnergyPriceService>());
builder.Services.AddSingleton<IEmailService, EmailService>(); builder.Services.AddSingleton<IEmailService, EmailService>();
builder.Services.AddSingleton<IEventService, EventService>(); builder.Services.AddSingleton<IEventService, EventService>();
builder.Services.AddSingleton<IHiveMQClient, HiveMQClient>((provider) => builder.Services.AddSingleton<IHiveMQClient, HiveMQClient>((provider) =>
@@ -44,7 +47,7 @@ builder.Services.AddSingleton<IHiveMQClient, HiveMQClient>((provider)=>
}; };
HiveMQClient client = new(options); HiveMQClient client = new(options);
client.ConnectAsync().ConfigureAwait(false); _ = client.ConnectAsync().ConfigureAwait(false);
return client; return client;
}); });
builder.Services.AddTransient<IDeviceManagmentService, DeviceManagmentService>(); builder.Services.AddTransient<IDeviceManagmentService, DeviceManagmentService>();
@@ -80,7 +83,6 @@ app.MapBlazorHub();
app.MapFallbackToPage("/_Host"); app.MapFallbackToPage("/_Host");
app.MapControllers(); app.MapControllers();
app.Services.GetRequiredService<IOptions<EmailSettings>>().Value.CompileTemplates(); app.Services.GetRequiredService<IOptions<EmailSettings>>().Value.CompileTemplates();
IServiceScope serviceScope = app.Services.GetRequiredService<IServiceScopeFactory>().CreateScope(); IServiceScope serviceScope = app.Services.GetRequiredService<IServiceScopeFactory>().CreateScope();
@@ -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<double> GetEnergyPrice(DateTime startime, DateTime endTime, List<Device> plugs, List<EnergyPriceChange> changes);
}
@@ -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<MainDatabaseContext>();
}
/// <summary>
/// Calculate the energy price for the given timespan
/// </summary>
/// <param name="startime">The timestamp that has the greater value</param>
/// <param name="endTime">The timestamp that has the lower value</param>
/// <param name="plugs"></param>
/// <param name="changes"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<double> GetEnergyPrice(DateTime startime, DateTime endTime, List<Device> plugs, List<EnergyPriceChange> 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<Device> plugs = mainDatabaseContext.Devices.ToList();
List<EnergyPriceChange> 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);
}
}
@@ -1,24 +1,29 @@
using Elektrifikatsiya.Utilities; namespace Elektrifikatsiya.Services.Implementations;
namespace Elektrifikatsiya.Services.Implementations; public class NotifcationService : INotificationService
public class NotifcationService : INotifcationService
{ {
private readonly IEmailService emailService;
private readonly IEnergyPriceService energyPriceService;
private readonly Microsoft.Extensions.Configuration.IConfiguration configuration;
private readonly ILogger<NotifcationService> logger;
public TimeSpan ExecutionRepeatDelay => TimeSpan.FromDays(1); public TimeSpan ExecutionRepeatDelay => TimeSpan.FromDays(1);
public DateTime FirstExecutionTime => DateTime.Now.AddDays(0); public DateTime FirstExecutionTime => DateTime.Now.AddDays(0);
private readonly IEmailService emailService;
private readonly ILogger<NotifcationService> logger;
public NotifcationService(ILogger<NotifcationService> logger, IEmailService emailService) public NotifcationService(ILogger<NotifcationService> logger, IEmailService emailService, IEnergyPriceService energyPriceService, Microsoft.Extensions.Configuration.IConfiguration configuration)
{ {
this.logger = logger; this.logger = logger;
this.emailService = emailService; this.emailService = emailService;
this.energyPriceService = energyPriceService;
this.configuration = configuration;
} }
public async void Update() public async void Update()
{ {
//logger.LogInformation("Sending Email"); logger.LogInformation("Sending Email");
//await emailService.SendWithTemeplateAsync("[email protected]", "AAAA", "DailyNotification", templateParameters: new() { { "yesterday", "10" }, { "yearly", "100" }, { "link", "localhost:5565/yes" } });
//logger.LogInformation("Done sending"); string url = configuration.GetValue<string>("Url") ?? "https://http.cat/images/404.jpg";
await emailService.SendWithTemeplateAsync("[email protected]", "Daily Notification", "DailyNotification", templateParameters: new() { { "yesterday", $"{energyPriceService.DailyEnergyCost:F2}" }, { "yearly", $"{energyPriceService.YearlyEnergyCost:F2}" }, { "link", url } });
logger.LogInformation("Done sending");
} }
} }
@@ -1,10 +1,9 @@
using System.Net; using Elektrifikatsiya.Models;
using System.Net.Sockets;
using System.Diagnostics;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Elektrifikatsiya.Models;
using FluentResults;
namespace Elektrifikatsiya.Utilities; namespace Elektrifikatsiya.Utilities;
@@ -19,6 +18,7 @@ public class PrometheusQuery
public Task<PrometheusQueryResult?> Query(string query) public Task<PrometheusQueryResult?> Query(string query)
{ {
Debug.WriteLine($"{client.BaseAddress}/api/v1/query?query={query}");
return client.GetFromJsonAsync<PrometheusQueryResult>($"/api/v1/query?query={UrlEncoder.Create().Encode(query)}", new JsonSerializerOptions() return client.GetFromJsonAsync<PrometheusQueryResult>($"/api/v1/query?query={UrlEncoder.Create().Encode(query)}", new JsonSerializerOptions()
{ {
PropertyNameCaseInsensitive = true, PropertyNameCaseInsensitive = true,
@@ -1,19 +0,0 @@
{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"EmailSettings": {
"User": "<SMTP_USER>",
"Key": "<SMTP_KEY>",
"DefaultEmail": "SMTP_EMAIL_ADDRESS",
"Templates": {
"DailyNotification": "../assets/DailyNotif.html"
},
"SmtpServer": "<SMTP_SERVER>",
"SmtpPort": 0
}
}