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

This commit is contained in:
Friend2868
2023-06-01 15:23:59 +02:00
7 changed files with 224 additions and 169 deletions
@@ -6,11 +6,13 @@ namespace Elektrifikatsiya.Database;
public class MainDatabaseContext : DbContext public class MainDatabaseContext : DbContext
{ {
public MainDatabaseContext(DbContextOptions<MainDatabaseContext> dbContextOptions) : base(dbContextOptions) public DbSet<Device> Devices { get; set; }
{
}
public DbSet<User> Users { get; set; } public DbSet<User> Users { get; set; }
public DbSet<Device> Devices { get; set; }
public DbSet<Event> Events { get; set; } public DbSet<EnergyPriceChange> EnergyPriceChanges { get; set; }
public MainDatabaseContext(DbContextOptions<MainDatabaseContext> dbContextOptions) : base(dbContextOptions)
{
}
} }
@@ -0,0 +1,23 @@
using System.ComponentModel.DataAnnotations;
namespace Elektrifikatsiya.Models;
public class EnergyPriceChange
{
[Key]
public DateTime DateTime { get; set; }
public double EnergyPrice { get; set; }
public EnergyPriceChange(DateTime dateTime, double energyPrice)
{
DateTime = dateTime;
EnergyPrice = energyPrice;
}
public EnergyPriceChange(double energyPrice)
{
EnergyPrice = energyPrice;
DateTime = DateTime.Now;
}
}
@@ -1,5 +1,6 @@
@page "/" @page "/"
@using Blazorise.Components; @using Blazorise.Components;
@using Elektrifikatsiya.Database;
@using Elektrifikatsiya.Models; @using Elektrifikatsiya.Models;
@using System.Net @using System.Net
@using Elektrifikatsiya.Services @using Elektrifikatsiya.Services
@@ -7,88 +8,92 @@
@using System.Diagnostics @using System.Diagnostics
@using Elektrifikatsiya.Services.Implementations @using Elektrifikatsiya.Services.Implementations
@inject IVersionProvider VersionProvider @inject IVersionProvider VersionProvider
@inject IDeviceStatusService DeviceStatusService
@inject IEventService EventService
<Row Padding="Padding.Is3"> @inject IDeviceStatusService DeviceStatusService;
<Div Display="Display.Flex.Row.OnDesktop" Margin="Margin.Is3.FromBottom" Width="Width.Is100"> @inject IDeviceManagmentService DeviceManagmentService;
<Column ColumnSize="ColumnSize.Is6.OnDesktop.Is12"> @inject IEventService EventService;
<Bar Breakpoint="Breakpoint.Desktop" @inject MainDatabaseContext MainDatabaseContext;
Background="Background.White"
ThemeContrast="ThemeContrast.None"
Shadow="Shadow.Remove"
Border="Border.Is1.RoundedTop">
<BarBrand>
Plugs
</BarBrand>
</Bar>
<ListView TItem="Device"
Data="@plugs"
TextField="@((_)=>(""))"
ValueField="@((_)=>(""))"
Mode="ListGroupMode.Static"
Style="border-radius: 0px 0px">
<ItemTemplate>
<ListGroupItem Border="Border.OnBottom">
<Row>
<Column ColumnSize="ColumnSize.Is8">
<Row>
<Column ColumnSize="ColumnSize.Is7">
<Heading Size="HeadingSize.Is6" Margin="Margin.Is1.FromBottom">@context.Item.Name</Heading>
</Column>
</Row>
<Row>
<Column>
<Small>User: @context.Item.User.Name</Small>
<br />
<Small>Room: @context.Item.Room</Small>
</Column>
<Column ColumnSize="ColumnSize.Is5">
<Paragraph Margin="Margin.Is1.OnY">@context.Item.PowerUsage W</Paragraph>
</Column>
</Row>
</Column>
<Column ColumnSize="ColumnSize.Is4">
<Button Position="Position.Absolute.Top.Is50.Start.Is50.Translate.Middle" Color="Color.Dark" Outline>
<BarIcon IconName="IconName.Bolt" />
</Button>
</Column>
</Row>
</ListGroupItem>
</ItemTemplate>
</ListView>
</Column>
<Column ColumnSize="ColumnSize.Is6.OnDesktop.Is12">
<Bar Breakpoint="Breakpoint.Desktop"
Background="Background.White"
ThemeContrast="ThemeContrast.None"
Shadow="Shadow.Remove"
Border="Border.Is1.Rounded.RoundedTop">
<BarBrand>
Logs
</BarBrand>
</Bar>
<ListView TItem="Event"
Data="@events"
TextField="@((_)=>(""))"
ValueField="@((_)=>(""))"
Mode="ListGroupMode.Static"
Style="border-radius: 0px 0px">
<ItemTemplate> <Div>
<Div Flex="Flex.InlineFlex.JustifyContent.Between" Width="Width.Is100"> <Row Padding="Padding.Is3">
<Heading Size="HeadingSize.Is6" Margin="Margin.Is2.FromBottom">@context.Item.EventName</Heading> <Div Display="Display.Flex.Row.OnDesktop" Margin=" Margin.Is3.FromBottom" Width="Width.Is100">
<Small>@context.Item.Date</Small> <Column ColumnSize="ColumnSize.Is6.OnDesktop.Is12">
</Div> <Bar Breakpoint="Breakpoint.Desktop"
<Paragraph Border="Border.OnBottom" Margin="Margin.Is2.FromBottom">@context.Item.Description</Paragraph> Background="Background.White"
</ItemTemplate> ThemeContrast="ThemeContrast.None"
</ListView> Shadow="Shadow.Remove"
</Column> Border="Border.Is1.RoundedTop">
</Div> <BarBrand>
</Row> Plugs
<Row Padding="Padding.Is3"> </BarBrand>
<Column> </Bar>
<Button Color="Color.Primary" Style="border-radius: 0px" Clicked="@(async () => await HandleRedraw())">Aktualisieren</Button> <ListView TItem="Device"
<LineChart @ref="lineChart" TItem="double" /> Data="@plugs"
</Column> TextField="@((_)=>(""))"
</Row> ValueField="@((_)=>(""))"
Mode="ListGroupMode.Static"
Style="border-radius: 0px 0px">
<ItemTemplate>
<ListGroupItem>
<Row>
<Column ColumnSize="ColumnSize.Is8">
<Row>
<Column ColumnSize="ColumnSize.Is7">
<Heading Size="HeadingSize.Is6" Margin="Margin.Is1.FromBottom">@context.Item.Name</Heading>
</Column>
</Row>
<Row>
<Column>
<Small>User: @context.Item.User.Name</Small>
<br />
<Small>Room: @context.Item.Room</Small>
</Column>
<Column ColumnSize="ColumnSize.Is5">
<Paragraph Margin="Margin.Is1.OnY">@context.Item.PowerUsage W</Paragraph>
</Column>
</Row>
</Column>
<Column ColumnSize="ColumnSize.Is4">
<Button Clicked="() => Switch(context.Item)" Position="Position.Absolute.Top.Is50.Start.Is50.Translate.Middle" Color="Color.Primary" Primary Outline>
<BarIcon TextColor="(context.Item.Enabled?TextColor.Success:TextColor.Danger)" IconName="IconName.Bolt" />
</Button>
</Column>
</Row>
</ListGroupItem>
</ItemTemplate>
</ListView>
</Column>
<Column ColumnSize="ColumnSize.Is6.OnDesktop.Is12">
<Bar Breakpoint="Breakpoint.Desktop"
Background="Background.White"
ThemeContrast="ThemeContrast.None"
Shadow="Shadow.Remove"
Border="Border.Is1.Rounded.RoundedTop">
<BarBrand>
Logs
</BarBrand>
</Bar>
<ListView TItem="Event"
Data="@events"
TextField="@((_)=>(""))"
ValueField="@((_)=>(""))"
Mode="ListGroupMode.Static"
Style="border-radius: 0px 0px">
<ItemTemplate>
<Div Flex="Flex.InlineFlex.JustifyContent.Between" Width="Width.Is100">
<Heading Size="HeadingSize.Is6" Margin="Margin.Is2.FromBottom">@context.Item.EventName</Heading>
<Small>@context.Item.Date</Small>
</Div>
<Paragraph Margin="Margin.Is2.FromBottom">@context.Item.Description</Paragraph>
</ItemTemplate>
</ListView>
</Column>
</Div>
</Row>
<Row Padding="Padding.Is3">
<Column>
<Button Color="Color.Primary" Style="border-radius: 0px" Clicked="@(async () => await HandleRedraw())">Aktualisieren</Button>
<LineChart Height="Height.Is100" Width="Width.Is100" @ref="lineChart" TItem="double" />
</Column>
</Row>
</Div>
@@ -5,88 +5,101 @@ using Elektrifikatsiya.Utilities;
using System.Diagnostics; using System.Diagnostics;
using System.Text; using System.Text;
using Elektrifikatsiya.Services.Implementations;
using Elektrifikatsiya.Database;
namespace Elektrifikatsiya.Pages; namespace Elektrifikatsiya.Pages;
public partial class Dashboard public partial class Dashboard
{ {
//TODO: insert new event here if plug produces one //TODO: insert new event here if plug produces one
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) };
//TODO: insert new Device here if user adds one
List<Device> plugs = new List<Device>();
//code for graph
LineChart<double> lineChart;
protected override void OnInitialized() private List<string> labels = new List<string>();
{
plugs = DeviceStatusService.GetDevices().ValueOrDefault ?? new List<Device>();
events = EventService.Events;
EventService.OnEventCalled += (_, e) => //code for graph
{ private LineChart<double> lineChart;
_ = InvokeAsync(StateHasChanged);
};
DeviceStatusService.OnDeviceStatusChanged += (_, e) =>
{
_ = InvokeAsync(StateHasChanged);
};
}
protected override async Task OnAfterRenderAsync(bool firstRender) //TODO: insert new Device here if user adds one
{ private List<Device> plugs = new List<Device>();
if (firstRender)
{
await HandleRedraw();
}
}
async Task HandleRedraw() protected override async Task OnAfterRenderAsync(bool firstRender)
{ {
labels.Clear(); if (firstRender)
await lineChart.Clear(); {
await lineChart.AddLabelsDatasetsAndUpdate(labels, await GetLineChartDataset()); await HandleRedraw();
} }
}
//TODO: insert dataset of current and last voltage usages protected override void OnInitialized()
async Task<LineChartDataset<double>> GetLineChartDataset() {
{ plugs = DeviceStatusService.GetDevices().ValueOrDefault ?? new List<Device>();
return new LineChartDataset<double> events = EventService.Events;
{
Label = "Wattage",
Data = await RandomizeData(),
Fill = true,
PointRadius = 3,
CubicInterpolationMode = "monotone",
};
}
List<string> labels = new List<string>(); EventService.OnEventCalled += (_, e) =>
{
_ = InvokeAsync(StateHasChanged);
};
DeviceStatusService.OnDeviceStatusChanged += (_, e) =>
{
_ = InvokeAsync(StateHasChanged);
};
}
async Task<List<double>> RandomizeData() private async Task<List<double>> GetData()
{ {
if (plugs.Count == 0) if (plugs.Count == 0)
{ {
return new(); return new();
} }
PrometheusQuery promQueryer = new PrometheusQuery("http://localhost:9090"); PrometheusQuery promQueryer = new PrometheusQuery("http://localhost:9090");
string plugnames = ""; string plugnames = "";
foreach (Device device in plugs) foreach (Device device in plugs)
{ {
plugnames += $"shellyplug-s-{device.MacAddress}/relay/0|"; plugnames += $"shellyplug-s-{device.MacAddress}/relay/0|";
} }
plugnames = plugnames[0..^1]; plugnames = plugnames[0..^1];
Debug.WriteLine($$"""sum(power{sensor=~"{{plugnames}}"})[1h:1m]"""); double energyPrice = MainDatabaseContext.EnergyPriceChanges.OrderByDescending(e =>e.DateTime).FirstOrDefault()?.EnergyPrice ?? 0;
PrometheusDataWrapper? deviceData = (await promQueryer.Query($$"""sum(power{sensor=~"{{plugnames}}"})[1h:1m]"""))?.Data; 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;
var r = new Random(DateTime.Now.Millisecond); Console.WriteLine(priceData.VectorTypeToTimestampFloatTuple());
return deviceData?.MatrixTypeToTimestampFloatTuple().ValueOrDefault?.Select(x => var r = new Random(DateTime.Now.Millisecond);
{
labels.Add(DateTimeOffset.FromUnixTimeSeconds(long.Parse($"1{x.Item1}0")).UtcDateTime.ToLocalTime().ToShortTimeString()); return deviceData?.MatrixTypeToTimestampFloatTuple().ValueOrDefault?.Select(x =>
return x.Item2; {
}).ToList() ?? new List<double>(); labels.Add(DateTimeOffset.FromUnixTimeSeconds(long.Parse($"1{x.Item1}0")).UtcDateTime.ToLocalTime().ToShortTimeString());
} return x.Item2;
}).ToList() ?? new List<double>();
}
//TODO: insert dataset of current and last voltage usages
private async Task<LineChartDataset<double>> GetLineChartDataset()
{
return new LineChartDataset<double>
{
Label = "Wattage",
Data = await GetData(),
Fill = true,
PointRadius = 3,
CubicInterpolationMode = "monotone",
};
}
private async Task HandleRedraw()
{
labels.Clear();
await lineChart.Clear();
await lineChart.AddLabelsDatasetsAndUpdate(labels, await GetLineChartDataset());
}
private async Task Switch(Device device)
{
device.Enabled = !device.Enabled;
_ = await DeviceManagmentService.UpdateDevice(device);
}
} }
@@ -1,5 +1,6 @@
@page "/Settings" @page "/Settings"
@using Blazorise.Components; @using Blazorise.Components;
@using Elektrifikatsiya.Database;
@using Elektrifikatsiya.Models; @using Elektrifikatsiya.Models;
@using System.Net @using System.Net
@using System.Text.Json @using System.Text.Json
@@ -9,6 +10,7 @@
@inject IDeviceManagmentService DeviceManagmentService @inject IDeviceManagmentService DeviceManagmentService
@inject IMessageService MessageService @inject IMessageService MessageService
@inject MainDatabaseContext MainDatabaseContext
<Div> <Div>
<Row Style="width: 100%; height: 100%" Margin="Margin.Is3.FromTop.OnMobile" Padding="Padding.Is4.FromStart.OnMobile"> <Row Style="width: 100%; height: 100%" Margin="Margin.Is3.FromTop.OnMobile" Padding="Padding.Is4.FromStart.OnMobile">
@@ -124,7 +126,7 @@
Here you enter your electricity price. This is gonna calculate the overall Price of your System Here you enter your electricity price. This is gonna calculate the overall Price of your System
</Paragraph> </Paragraph>
</FieldLabel> </FieldLabel>
<TextEdit MaxLength="8" Placeholder="Enter Electricity Price" /> <NumericEdit @bind-Value="electricityPrice" MaxLength="8" Placeholder="Enter Electricity Price" />
</Field> </Field>
</Div> </Div>
<Column ColumnSize="ColumnSize.IsFull" TextAlignment="TextAlignment.End"> <Column ColumnSize="ColumnSize.IsFull" TextAlignment="TextAlignment.End">
@@ -139,7 +141,9 @@
@code { @code {
List<Device> plugs = new List<Device>(); List<Device> plugs = new List<Device>();
private bool hideButtonSettings = true; bool hideButtonSettings = true;
double electricityPrice = 0;
Device? SelectedDevice = null; Device? SelectedDevice = null;
Device? DeviceCopy = null; Device? DeviceCopy = null;
@@ -152,6 +156,8 @@
{ {
plugs = getDevicesResult.Value; plugs = getDevicesResult.Value;
} }
electricityPrice = MainDatabaseContext.EnergyPriceChanges.OrderByDescending(e=>e.DateTime).FirstOrDefault()?.EnergyPrice ?? 0;
} }
private void Toggle(Device device) private void Toggle(Device device)
@@ -161,16 +167,19 @@
DeviceCopy = device.CopyDevice(); DeviceCopy = device.CopyDevice();
} }
private void OnSave() private async void OnSave()
{ {
if (hideButtonSettings || DeviceCopy is null || SelectedDevice is null) if (hideButtonSettings)
{ {
MainDatabaseContext.EnergyPriceChanges.Add(new EnergyPriceChange(electricityPrice));
await MainDatabaseContext.SaveChangesAsync();
} }
else else if(DeviceCopy is not null && SelectedDevice is not null)
{ {
SelectedDevice.OverwiteDevice(DeviceCopy); SelectedDevice.OverwiteDevice(DeviceCopy);
DeviceManagmentService.UpdateDevice(SelectedDevice); await DeviceManagmentService.UpdateDevice(SelectedDevice);
} }
} }
@@ -15,7 +15,8 @@ using HiveMQtt.Client.Options;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Prometheus;
using Prometheus.HttpMetrics;
WebApplicationBuilder builder = WebApplication.CreateBuilder(args); WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
@@ -32,7 +33,7 @@ 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<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<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 +45,7 @@ builder.Services.AddSingleton<IHiveMQClient, HiveMQClient>((provider)=>
UseTLS = false, UseTLS = false,
}; };
HiveMQClient client = new(options); HiveMQClient client = new(options);
client.ConnectAsync().ConfigureAwait(false); client.ConnectAsync().ConfigureAwait(false);
return client; return client;
}); });
@@ -60,7 +61,6 @@ builder.Services.AddBlazorise(options =>
options.Immediate = true; options.Immediate = true;
}); });
AddBlazorise(builder.Services); AddBlazorise(builder.Services);
WebApplication app = builder.Build(); WebApplication app = builder.Build();
@@ -81,6 +81,9 @@ app.UseRouting();
app.MapBlazorHub(); app.MapBlazorHub();
app.MapFallbackToPage("/_Host"); app.MapFallbackToPage("/_Host");
app.MapControllers(); app.MapControllers();
app.MapMetrics();
app.UseHttpMetrics();
app.Services.GetRequiredService<IOptions<EmailSettings>>().Value.CompileTemplates(); app.Services.GetRequiredService<IOptions<EmailSettings>>().Value.CompileTemplates();
@@ -98,7 +101,7 @@ if (!mainDatabase.Users.Any())
app.Run(); app.Run();
void AddBlazorise(IServiceCollection services) static void AddBlazorise(IServiceCollection services)
{ {
_ = services.AddBlazorise(); _ = services.AddBlazorise();
_ = services.AddMaterialProviders(); _ = services.AddMaterialProviders();
@@ -4,7 +4,7 @@ namespace Elektrifikatsiya.Services.Implementations;
public class NotifcationService : INotifcationService public class NotifcationService : INotifcationService
{ {
public TimeSpan ExecutionRepeatDelay => TimeSpan.FromSeconds(30); 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 IEmailService emailService;
private readonly ILogger<NotifcationService> logger; private readonly ILogger<NotifcationService> logger;