Merge branch 'Development' of https://github.com/Stefan-5422/-T5-Elektrifikatsiya into Development

This commit is contained in:
Friend2868
2023-06-13 21:23:13 +02:00
9 changed files with 166 additions and 81 deletions
+1
View File
@@ -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
@@ -12,6 +12,7 @@
@inject IDeviceStatusService DeviceStatusService;
@inject IDeviceManagmentService DeviceManagmentService;
@inject IEventService EventService;
@inject IEnergyPriceService EnergyPriceService;
@inject MainDatabaseContext MainDatabaseContext;
<Div>
@@ -107,19 +108,19 @@
<Div Flex="Flex.JustifyContent.Between" Width="Width.Is100">
<Heading Size="HeadingSize.Is6">Daily Cost</Heading>
</Div>
<Paragraph Margin="Margin.Is1.FromBottom">2€</Paragraph>
<Paragraph Margin="Margin.Is1.FromBottom">@($"{EnergyPriceService.DailyEnergyCost:F2}")€</Paragraph>
</ListGroupItem>
<ListGroupItem>
<Div Flex="Flex.JustifyContent.Between" Width="Width.Is100">
<Heading Size="HeadingSize.Is6">Monthly Cost</Heading>
</Div>
<Paragraph Margin="Margin.Is1.FromBottom">60€</Paragraph>
<Paragraph Margin="Margin.Is1.FromBottom">@($"{EnergyPriceService.MonthlyEnergyCost:F2}")€</Paragraph>
</ListGroupItem>
<ListGroupItem>
<Div Flex="Flex.JustifyContent.Between" Width="Width.Is100">
<Heading Size="HeadingSize.Is6">Yearly Cost</Heading>
</Div>
<Paragraph Margin="Margin.Is1.FromBottom">720€</Paragraph>
<Paragraph Margin="Margin.Is1.FromBottom">@($"{EnergyPriceService.YearlyEnergyCost:F2}")€</Paragraph>
</ListGroupItem>
</ListGroup>
@@ -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<string> labels = new List<string>();
//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<string> labels = new List<string>();
//code for graph
private LineChart<double> lineChart;
@@ -36,11 +32,11 @@ public partial class Dashboard
plugs = DeviceStatusService.GetDevices().ValueOrDefault ?? new List<Device>();
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 =>
{
@@ -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<ServiceScheduler>();
builder.Services.AddSingleton<IUpdateService, UpdateService>();
builder.Services.AddSingleton<IDeviceStatusService, DeviceStatusService>();
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<INotificationService>());
builder.Services.AddSingleton<IScheduledService>(s => s.GetRequiredService<IEnergyPriceService>());
builder.Services.AddSingleton<IEmailService, EmailService>();
builder.Services.AddSingleton<IEventService, EventService>();
builder.Services.AddSingleton<IHiveMQClient, HiveMQClient>((provider)=>
builder.Services.AddSingleton<IHiveMQClient, HiveMQClient>((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<IDeviceManagmentService, DeviceManagmentService>();
builder.Services.AddScoped<IAuthenticationService, AuthenticationService>();
@@ -56,7 +59,7 @@ builder.Services.AddBootstrapProviders();
builder.Services.AddHttpClient<IDeviceManagmentService, DeviceManagmentService>();
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<IOptions<EmailSettings>>().Value.CompileTemplates();
IServiceScope serviceScope = app.Services.GetRequiredService<IServiceScopeFactory>().CreateScope();
@@ -99,7 +101,7 @@ app.Run();
static void AddBlazorise(IServiceCollection services)
{
_ = services.AddBlazorise();
_ = services.AddMaterialProviders();
_ = services.AddMaterialIcons();
_ = services.AddBlazorise();
_ = services.AddMaterialProviders();
_ = services.AddMaterialIcons();
}
@@ -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 : INotifcationService
public class NotifcationService : INotificationService
{
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 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.emailService = emailService;
this.energyPriceService = energyPriceService;
this.configuration = configuration;
}
public async void Update()
{
//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");
logger.LogInformation("Sending Email");
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,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<PrometheusQueryResult?> Query(string query)
{
return client.GetFromJsonAsync<PrometheusQueryResult>($"/api/v1/query?query={UrlEncoder.Create().Encode(query)}", new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = true,
Converters =
{
new JsonStringEnumConverter()
}
});
}
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()
{
PropertyNameCaseInsensitive = true,
Converters =
{
new JsonStringEnumConverter()
}
});
}
}
@@ -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
}
}