mirror of
https://github.com/Stefan-5422/-T5-Elektrifikatsiya.git
synced 2026-09-04 00:45:58 +02:00
Squashed commit of the following:
commita0adfc850dAuthor: Stefan <[email protected]> Date: Thu Jun 1 15:28:39 2023 +0200 Revert using Prom.Net commit08a5bd4772Merge:a47c2529d64698Author: Stefan <[email protected]> Date: Thu Jun 1 15:27:48 2023 +0200 Merge remote-tracking branch 'origin/feature-DeviceRegistration' into feature-DeviceRegistration commita47c2525e1Author: Stefan <[email protected]> Date: Thu Jun 1 15:27:30 2023 +0200 Revert using Prom.Net commit9d646988d1Merge:889fc5e4ef3958Author: Friend2868 <[email protected]> Date: Thu Jun 1 15:24:56 2023 +0200 Merge branch 'feature-DeviceRegistration' of https://github.com/Stefan-5422/-T5-Elektrifikatsiya into feature-DeviceRegistration commit889fc5e3ddAuthor: Friend2868 <[email protected]> Date: Thu Jun 1 15:24:21 2023 +0200 Final2 commit92a7538996Merge:d5f5ec2f04724eAuthor: Friend2868 <[email protected]> Date: Thu Jun 1 15:23:59 2023 +0200 Merge branch 'feature-DeviceRegistration' of https://github.com/Stefan-5422/-T5-Elektrifikatsiya into feature-DeviceRegistration commit4ef3958da3Author: Stone_Red <[email protected]> Date: Thu Jun 1 15:23:10 2023 +0200 Fix .gitignore commitd5f5ec2d0eAuthor: Friend2868 <[email protected]> Date: Thu Jun 1 15:21:37 2023 +0200 Final commit236c2418c5Author: Friend2868 <[email protected]> Date: Thu Jun 1 15:18:57 2023 +0200 Update .gitignore commitf04724e258Merge:9b2db9cf74cff3Author: Stefan <[email protected]> Date: Thu Jun 1 15:15:57 2023 +0200 Merge remote-tracking branch 'origin/feature-DeviceRegistration' into feature-DeviceRegistration commit9b2db9c97dAuthor: Stefan <[email protected]> Date: Thu Jun 1 15:15:44 2023 +0200 Changes 🐙 commitf74cff3d39Author: Stone_Red <[email protected]> Date: Wed May 24 11:48:07 2023 +0200 Update appsettings.Development.json commit2f79ccb22dAuthor: Stone_Red <[email protected]> Date: Wed May 24 11:45:16 2023 +0200 Update appsettings.Development.json commit104bd37bcbAuthor: Stone_Red <[email protected]> Date: Wed May 24 11:44:58 2023 +0200 Update appsettings.Development.json commitbdac3581deAuthor: Stefan <[email protected]> Date: Wed May 24 11:29:38 2023 +0200 Revert test shid commit5b58318699Merge:5b342c1b054b9cAuthor: Stefan <[email protected]> Date: Wed May 24 11:28:20 2023 +0200 Merge remote-tracking branch 'origin/feature-DeviceRegistration' into feature-DeviceRegistration commit5b342c1a8fAuthor: Stefan <[email protected]> Date: Wed May 24 11:28:12 2023 +0200 Add `ScheduledService` and email things ༼ つ ◕_◕ ༽つ commitb054b9cfe3Author: Friend2868 <[email protected]> Date: Wed May 24 11:16:18 2023 +0200 EventService finish commit9520fa04b8Author: Stefan <[email protected]> Date: Wed May 10 12:13:31 2023 +0200 Email Inferstructure
This commit is contained in:
@@ -351,3 +351,4 @@ MigrationBackup/
|
||||
|
||||
# Project specific
|
||||
*.sqlite*
|
||||
src/Elektrifikatsiya/Elektrifikatsiya/appsettings.Development.json
|
||||
|
||||
@@ -6,11 +6,13 @@ namespace Elektrifikatsiya.Database;
|
||||
|
||||
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<Device> Devices { get; set; }
|
||||
public DbSet<Event> Events { get; set; }
|
||||
public DbSet<User> Users { get; set; }
|
||||
|
||||
public DbSet<EnergyPriceChange> EnergyPriceChanges { get; set; }
|
||||
|
||||
public MainDatabaseContext(DbContextOptions<MainDatabaseContext> dbContextOptions) : base(dbContextOptions)
|
||||
{
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@@ -42,7 +42,9 @@
|
||||
<PackageReference Include="Blazorise.Icons.Material" Version="1.2.0" />
|
||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
||||
<PackageReference Include="FluentResults" Version="3.15.2" />
|
||||
<PackageReference Include="Handlebars.Net" Version="2.1.4" />
|
||||
<PackageReference Include="HiveMQtt" Version="0.1.10" />
|
||||
<PackageReference Include="MailKit" Version="4.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.4">
|
||||
@@ -50,6 +52,7 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.18.1" />
|
||||
<PackageReference Include="Mjml.Net" Version="1.22.0" />
|
||||
<PackageReference Include="Tmds.MDns" Version="0.7.1" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
namespace Elektrifikatsiya.Models;
|
||||
|
||||
public class DeviceStatusChagedEventArgs : EventArgs
|
||||
public class DeviceStatusChangedEventArgs : EventArgs
|
||||
{
|
||||
public string MacAddress { get; set; }
|
||||
|
||||
public DeviceStatusChagedEventArgs(string macAddress)
|
||||
public DeviceStatusChangedEventArgs(string macAddress)
|
||||
{
|
||||
MacAddress = macAddress;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -11,14 +11,15 @@ namespace Elektrifikatsiya.Models
|
||||
public string EventName { get; set; }
|
||||
public string Description { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
public Device Plug { get; set; }
|
||||
|
||||
//if Plug Class implemented -> Property for which plug this event is from
|
||||
|
||||
public Event(string eventName, string description, DateTime date)
|
||||
public Event() { }
|
||||
public Event(string eventName, string description, DateTime date, Device plug)
|
||||
{
|
||||
EventName = eventName;
|
||||
Description = description;
|
||||
Date = date;
|
||||
Plug = plug;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Elektrifikatsiya.Models
|
||||
{
|
||||
public class EventServiceEventArgs : EventArgs
|
||||
{
|
||||
public Event NewEvent { get; set; } = new Event();
|
||||
public EventServiceEventArgs(Event newEvent)
|
||||
{
|
||||
NewEvent = newEvent;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,29 +5,30 @@ namespace Elektrifikatsiya.Models;
|
||||
|
||||
public class User
|
||||
{
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
[Key]
|
||||
public int Id { get; private set; }
|
||||
[Required]
|
||||
public List<Device> Devices { get; set; } = new();
|
||||
|
||||
[Required]
|
||||
public string Name { get; private set; }
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
[Key]
|
||||
public int Id { get; private set; }
|
||||
|
||||
[Required]
|
||||
public string PasswordHash { get; private set; }
|
||||
public DateTime LastLoginDate { get; set; }
|
||||
|
||||
[Required]
|
||||
public Role Role { get; set; }
|
||||
[Required]
|
||||
public string Name { get; private set; }
|
||||
|
||||
public string? SessionToken { get; set; }
|
||||
public DateTime LastLoginDate { get; set; }
|
||||
[Required]
|
||||
public string PasswordHash { get; private set; }
|
||||
|
||||
[Required]
|
||||
public List<Device> Devices { get; set; } = new();
|
||||
[Required]
|
||||
public Role Role { get; set; }
|
||||
|
||||
public User(string name, string passwordHash, Role role)
|
||||
{
|
||||
Name = name;
|
||||
PasswordHash = passwordHash;
|
||||
Role = role;
|
||||
}
|
||||
public string? SessionToken { get; set; }
|
||||
|
||||
public User(string name, string passwordHash, Role role)
|
||||
{
|
||||
Name = name;
|
||||
PasswordHash = passwordHash;
|
||||
Role = role;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
@page "/"
|
||||
@using Blazorise.Components;
|
||||
@using Elektrifikatsiya.Database;
|
||||
@using Elektrifikatsiya.Models;
|
||||
@using System.Net
|
||||
@using Elektrifikatsiya.Services
|
||||
@@ -10,6 +11,8 @@
|
||||
|
||||
@inject IDeviceStatusService DeviceStatusService;
|
||||
@inject IDeviceManagmentService DeviceManagmentService;
|
||||
@inject IEventService EventService;
|
||||
@inject MainDatabaseContext MainDatabaseContext;
|
||||
|
||||
<Div>
|
||||
<Row Padding="Padding.Is3">
|
||||
|
||||
@@ -5,29 +5,23 @@ using Elektrifikatsiya.Utilities;
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using Elektrifikatsiya.Services.Implementations;
|
||||
using Elektrifikatsiya.Database;
|
||||
|
||||
namespace Elektrifikatsiya.Pages;
|
||||
|
||||
public partial class Dashboard
|
||||
{
|
||||
//TODO: insert new event here if plug produces one
|
||||
private readonly List<Event> events = new List<Event>() { new Event("Placeholder", "Placeholder", DateTime.Now), new Event("Placeholder", "Placeholder", DateTime.Now), new Event("Placeholder", "Placeholder", DateTime.Now) };
|
||||
private List<Event> events = new List<Event>() { new Event("Text", "Text", DateTime.Today, null) };
|
||||
|
||||
//TODO: insert new Device here if user adds one
|
||||
private List<Device> plugs = new List<Device>();
|
||||
private List<string> labels = new List<string>();
|
||||
|
||||
//code for graph
|
||||
private LineChart<double> lineChart;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
plugs = DeviceStatusService.GetDevices().ValueOrDefault ?? new List<Device>();
|
||||
|
||||
DeviceStatusService.OnDeviceStatusChanged += (_, e) =>
|
||||
{
|
||||
_ = InvokeAsync(StateHasChanged);
|
||||
};
|
||||
}
|
||||
//TODO: insert new Device here if user adds one
|
||||
private List<Device> plugs = new List<Device>();
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
@@ -37,10 +31,63 @@ public partial class Dashboard
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Switch(Device device)
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
device.Enabled = !device.Enabled;
|
||||
_ = await DeviceManagmentService.UpdateDevice(device);
|
||||
plugs = DeviceStatusService.GetDevices().ValueOrDefault ?? new List<Device>();
|
||||
events = EventService.Events;
|
||||
|
||||
EventService.OnEventCalled += (_, e) =>
|
||||
{
|
||||
_ = InvokeAsync(StateHasChanged);
|
||||
};
|
||||
DeviceStatusService.OnDeviceStatusChanged += (_, e) =>
|
||||
{
|
||||
_ = InvokeAsync(StateHasChanged);
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<List<double>> GetData()
|
||||
{
|
||||
if (plugs.Count == 0)
|
||||
{
|
||||
return new();
|
||||
}
|
||||
PrometheusQuery promQueryer = new PrometheusQuery("http://localhost:9090");
|
||||
|
||||
string plugnames = "";
|
||||
foreach (Device device in plugs)
|
||||
{
|
||||
plugnames += $"shellyplug-s-{device.MacAddress}/relay/0|";
|
||||
}
|
||||
plugnames = plugnames[0..^1];
|
||||
|
||||
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);
|
||||
|
||||
return deviceData?.MatrixTypeToTimestampFloatTuple().ValueOrDefault?.Select(x =>
|
||||
{
|
||||
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()
|
||||
@@ -50,48 +97,9 @@ public partial class Dashboard
|
||||
await lineChart.AddLabelsDatasetsAndUpdate(labels, await GetLineChartDataset());
|
||||
}
|
||||
|
||||
//TODO: insert dataset of current and last voltage usages
|
||||
private async Task<LineChartDataset<double>> GetLineChartDataset()
|
||||
private async Task Switch(Device device)
|
||||
{
|
||||
return new LineChartDataset<double>
|
||||
{
|
||||
Label = "Wattage",
|
||||
Data = await DeviceData(),
|
||||
Fill = true,
|
||||
PointRadius = 3,
|
||||
CubicInterpolationMode = "monotone",
|
||||
};
|
||||
}
|
||||
|
||||
private readonly List<string> labels = new List<string>();
|
||||
|
||||
private async Task<List<double>> DeviceData()
|
||||
{
|
||||
PrometheusQuery promQueryer = new PrometheusQuery("http://localhost:9090");
|
||||
|
||||
StringBuilder builder = new();
|
||||
foreach (Device device in plugs)
|
||||
{
|
||||
_ = builder.Append($"shellyplug-s-{device.MacAddress}/relay/0|");
|
||||
}
|
||||
|
||||
if (builder.Length <= 1)
|
||||
{
|
||||
return new();
|
||||
}
|
||||
|
||||
string plugnames = builder.ToString()[..^1];
|
||||
|
||||
Debug.WriteLine($$"""sum(power{sensor=~"{{plugnames}}"})[1h:1m]""");
|
||||
|
||||
PrometheusDataWrapper? deviceData = (await promQueryer.Query($$"""sum(power{sensor=~"{{plugnames}}"})[1h:1m]"""))?.Data;
|
||||
|
||||
Random r = new Random(DateTime.Now.Millisecond);
|
||||
|
||||
return deviceData?.MatrixTypeToTimestampFloatTuple().ValueOrDefault?.Select(x =>
|
||||
{
|
||||
labels.Add(DateTimeOffset.FromUnixTimeSeconds(long.Parse($"1{x.Item1}0")).UtcDateTime.ToLocalTime().ToShortTimeString());
|
||||
return x.Item2;
|
||||
}).ToList() ?? new List<double>();
|
||||
device.Enabled = !device.Enabled;
|
||||
_ = await DeviceManagmentService.UpdateDevice(device);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,19 @@
|
||||
@using System.Diagnostics;
|
||||
@using Elektrifikatsiya.Layouts;
|
||||
@using Elektrifikatsiya.Services;
|
||||
@inject IAuthenticationService AuthenticationService;
|
||||
@inject NavigationManager NavigationManager
|
||||
@page "/login"
|
||||
@layout LoginLayout;
|
||||
@layout LoginLayout;
|
||||
|
||||
@code {
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
bool isAuthenticated = (await AuthenticationService.IsAuthenticated()).ValueOrDefault;
|
||||
|
||||
if (isAuthenticated)
|
||||
{
|
||||
NavigationManager.NavigateTo("/");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
@page "/Settings"
|
||||
@using Blazorise.Components;
|
||||
@using Elektrifikatsiya.Database;
|
||||
@using Elektrifikatsiya.Models;
|
||||
@using System.Net
|
||||
@using System.Text.Json
|
||||
@@ -9,6 +10,7 @@
|
||||
|
||||
@inject IDeviceManagmentService DeviceManagmentService
|
||||
@inject IMessageService MessageService
|
||||
@inject MainDatabaseContext MainDatabaseContext
|
||||
|
||||
<Div>
|
||||
<Row Style="width: 100%; height: 100%" Margin="Margin.Is3.FromTop.OnMobile" Padding="Padding.Is4.FromStart.OnMobile">
|
||||
@@ -90,21 +92,7 @@
|
||||
Here you can change the name of your plug
|
||||
</Paragraph>
|
||||
</FieldLabel>
|
||||
<TextEdit @bind-Text="@DeviceCopy.Name" Placeholder="Enter Name" />
|
||||
</Field>
|
||||
<br />
|
||||
<br />
|
||||
<Field>
|
||||
<FieldLabel>
|
||||
<h5>
|
||||
Change Max Output
|
||||
</h5>
|
||||
<Paragraph>
|
||||
Here you can limit how much power your plug uses
|
||||
</Paragraph>
|
||||
</FieldLabel>
|
||||
<TextEdit Placeholder="Enter Max Output" />
|
||||
<!--TODO: What is this?-->
|
||||
<TextEdit MaxLength="30" @bind-Text="@DeviceCopy.Name" Placeholder="Enter Name" />
|
||||
</Field>
|
||||
<br />
|
||||
<br />
|
||||
@@ -117,7 +105,7 @@
|
||||
Here you select or change the room the plug belongs to.
|
||||
</Paragraph>
|
||||
</FieldLabel>
|
||||
<TextEdit @bind-text="@DeviceCopy.Room" Placeholder="Enter the name of the room here"/>
|
||||
<TextEdit MaxLength="30" @bind-text="@DeviceCopy.Room" Placeholder="Enter the name of the room here" />
|
||||
</Field>
|
||||
</Div>
|
||||
}
|
||||
@@ -138,7 +126,7 @@
|
||||
Here you enter your electricity price. This is gonna calculate the overall Price of your System
|
||||
</Paragraph>
|
||||
</FieldLabel>
|
||||
<TextEdit Placeholder="Enter Electricity Price" />
|
||||
<NumericEdit @bind-Value="electricityPrice" MaxLength="8" Placeholder="Enter Electricity Price" />
|
||||
</Field>
|
||||
</Div>
|
||||
<Column ColumnSize="ColumnSize.IsFull" TextAlignment="TextAlignment.End">
|
||||
@@ -153,7 +141,9 @@
|
||||
@code {
|
||||
List<Device> plugs = new List<Device>();
|
||||
|
||||
private bool hideButtonSettings = true;
|
||||
bool hideButtonSettings = true;
|
||||
|
||||
double electricityPrice = 0;
|
||||
|
||||
Device? SelectedDevice = null;
|
||||
Device? DeviceCopy = null;
|
||||
@@ -166,6 +156,8 @@
|
||||
{
|
||||
plugs = getDevicesResult.Value;
|
||||
}
|
||||
|
||||
electricityPrice = MainDatabaseContext.EnergyPriceChanges.OrderByDescending(e=>e.DateTime).FirstOrDefault()?.EnergyPrice ?? 0;
|
||||
}
|
||||
|
||||
private void Toggle(Device device)
|
||||
@@ -175,16 +167,19 @@
|
||||
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);
|
||||
DeviceManagmentService.UpdateDevice(SelectedDevice);
|
||||
await DeviceManagmentService.UpdateDevice(SelectedDevice);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
global using INotificationService = Elektrifikatsiya.Services.INotifcationService;
|
||||
|
||||
using Blazorise;
|
||||
using Blazorise.Bootstrap;
|
||||
using Blazorise.Icons.Material;
|
||||
@@ -7,20 +9,31 @@ using Elektrifikatsiya.Database;
|
||||
using Elektrifikatsiya.Models;
|
||||
using Elektrifikatsiya.Services;
|
||||
using Elektrifikatsiya.Services.Implementations;
|
||||
|
||||
using Elektrifikatsiya.Utilities;
|
||||
using HiveMQtt.Client;
|
||||
using HiveMQtt.Client.Options;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Read configuration
|
||||
builder.Services.Configure<EmailSettings>(builder.Configuration.GetRequiredSection("EmailSettings"));
|
||||
|
||||
// Add services to the container.
|
||||
builder.Services.AddRazorPages();
|
||||
builder.Services.AddServerSideBlazor();
|
||||
builder.Services.AddOptions();
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
builder.Services.AddHostedService<UpdateService>();
|
||||
builder.Services.AddHostedService<ServiceScheduler>();
|
||||
builder.Services.AddSingleton<IUpdateService, UpdateService>();
|
||||
builder.Services.AddSingleton<IDeviceStatusService, DeviceStatusService>();
|
||||
builder.Services.AddSingleton<INotificationService, NotifcationService>();
|
||||
builder.Services.AddSingleton<IScheduledService>(s => s.GetRequiredService<IUpdateService>());
|
||||
builder.Services.AddSingleton<IScheduledService>(s => s.GetRequiredService<INotificationService>());
|
||||
builder.Services.AddSingleton<IEmailService, EmailService>();
|
||||
builder.Services.AddSingleton<IEventService, EventService>();
|
||||
builder.Services.AddSingleton<IHiveMQClient, HiveMQClient>((provider)=>
|
||||
{
|
||||
HiveMQClientOptions options = new()
|
||||
@@ -30,7 +43,7 @@ builder.Services.AddSingleton<IHiveMQClient, HiveMQClient>((provider)=>
|
||||
UseTLS = false,
|
||||
};
|
||||
|
||||
HiveMQClient client = new(options);
|
||||
HiveMQClient client = new(options);
|
||||
client.ConnectAsync().ConfigureAwait(false);
|
||||
return client;
|
||||
});
|
||||
@@ -67,6 +80,9 @@ app.MapBlazorHub();
|
||||
app.MapFallbackToPage("/_Host");
|
||||
app.MapControllers();
|
||||
|
||||
|
||||
app.Services.GetRequiredService<IOptions<EmailSettings>>().Value.CompileTemplates();
|
||||
|
||||
IServiceScope serviceScope = app.Services.GetRequiredService<IServiceScopeFactory>().CreateScope();
|
||||
|
||||
MainDatabaseContext mainDatabase = serviceScope.ServiceProvider.GetRequiredService<MainDatabaseContext>();
|
||||
@@ -81,7 +97,7 @@ if (!mainDatabase.Users.Any())
|
||||
|
||||
app.Run();
|
||||
|
||||
void AddBlazorise(IServiceCollection services)
|
||||
static void AddBlazorise(IServiceCollection services)
|
||||
{
|
||||
_ = services.AddBlazorise();
|
||||
_ = services.AddMaterialProviders();
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Elektrifikatsiya.Services;
|
||||
|
||||
public interface IDeviceStatusService
|
||||
{
|
||||
public event EventHandler<DeviceStatusChagedEventArgs> OnDeviceStatusChanged;
|
||||
public event EventHandler<DeviceStatusChangedEventArgs> OnDeviceStatusChanged;
|
||||
|
||||
public Result<List<Device>> GetDevices();
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Elektrifikatsiya.Services;
|
||||
|
||||
public interface IEmailService
|
||||
{
|
||||
Task SendAsync(string to, string subject, string html, string? from = null);
|
||||
|
||||
Task SendWithTemeplateAsync(string to, string subject, string templateKey, string? from = null, Dictionary<string, string>? templateParameters = null);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Elektrifikatsiya.Models;
|
||||
|
||||
namespace Elektrifikatsiya.Services
|
||||
{
|
||||
public interface IEventService
|
||||
{
|
||||
public List<Event> Events { get; }
|
||||
public event EventHandler<EventServiceEventArgs> OnEventCalled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using Elektrifikatsiya.Utilities;
|
||||
|
||||
namespace Elektrifikatsiya.Services;
|
||||
|
||||
public interface INotifcationService : IScheduledService
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using Elektrifikatsiya.Utilities;
|
||||
|
||||
namespace Elektrifikatsiya.Services;
|
||||
|
||||
public interface IUpdateService : IScheduledService
|
||||
{
|
||||
|
||||
}
|
||||
+2
@@ -173,6 +173,8 @@ public class DeviceManagmentService : IDeviceManagmentService
|
||||
|
||||
PublishResult res = await hiveMQClient.PublishAsync($"shellies/shellyplug-s-{device.MacAddress}/relay/0/command", device.Enabled ? "on" : "off");
|
||||
|
||||
device.User = mainDatabaseContext.Users.First(u => u.Id == device.User.Id);
|
||||
|
||||
_ = mainDatabaseContext.Update(device);
|
||||
|
||||
return await Result.Try(Task () => mainDatabaseContext.SaveChangesAsync());
|
||||
|
||||
+3
-5
@@ -1,5 +1,4 @@
|
||||
using Elektrifikatsiya.Models;
|
||||
|
||||
using FluentResults;
|
||||
|
||||
namespace Elektrifikatsiya.Services.Implementations;
|
||||
@@ -7,15 +6,14 @@ namespace Elektrifikatsiya.Services.Implementations;
|
||||
public class DeviceStatusService : IDeviceStatusService
|
||||
{
|
||||
private readonly ILogger<DeviceStatusService> logger;
|
||||
|
||||
public event EventHandler<DeviceStatusChagedEventArgs>? OnDeviceStatusChanged;
|
||||
public event EventHandler<DeviceStatusChangedEventArgs>? OnDeviceStatusChanged;
|
||||
|
||||
private readonly Dictionary<string, Device> devices = new();
|
||||
|
||||
public DeviceStatusService(ILogger<DeviceStatusService> logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
}
|
||||
|
||||
public Result<List<Device>> GetDevices()
|
||||
{
|
||||
@@ -36,7 +34,7 @@ public class DeviceStatusService : IDeviceStatusService
|
||||
modDevice.IpAddress = device.IpAddress;
|
||||
modDevice.Name = device.Name;
|
||||
|
||||
OnDeviceStatusChanged?.Invoke(this, new DeviceStatusChagedEventArgs(device.MacAddress));
|
||||
OnDeviceStatusChanged?.Invoke(this, new DeviceStatusChangedEventArgs(device.MacAddress));
|
||||
|
||||
return Result.Ok();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
using Elektrifikatsiya.Utilities;
|
||||
using HandlebarsDotNet;
|
||||
using MailKit.Net.Smtp;
|
||||
using MailKit.Security;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MimeKit;
|
||||
using MimeKit.Text;
|
||||
|
||||
namespace Elektrifikatsiya.Services.Implementations
|
||||
{
|
||||
public class EmailService : IEmailService
|
||||
{
|
||||
private readonly EmailSettings emailSettings;
|
||||
|
||||
public EmailService(IOptions<EmailSettings> emailSettings)
|
||||
{
|
||||
this.emailSettings = emailSettings.Value;
|
||||
}
|
||||
|
||||
public async Task SendAsync(string to, string subject, string html, string? from = null)
|
||||
{
|
||||
from ??= emailSettings.DefaultEmail;
|
||||
|
||||
MimeMessage message = new MimeMessage();
|
||||
message.From.Add(MailboxAddress.Parse(from));
|
||||
message.To.Add(MailboxAddress.Parse(to));
|
||||
message.Subject = subject;
|
||||
message.Body = new TextPart(TextFormat.Html) { Text = html };
|
||||
|
||||
using SmtpClient smtp = new SmtpClient();
|
||||
await smtp.ConnectAsync(emailSettings.SmtpServer, emailSettings.SmtpPort, SecureSocketOptions.StartTls);
|
||||
await smtp.AuthenticateAsync(emailSettings.User, emailSettings.Key);
|
||||
await smtp.SendAsync(message);
|
||||
await smtp.DisconnectAsync(true);
|
||||
}
|
||||
|
||||
public Task SendWithTemeplateAsync(string to, string subject, string templateKey, string? from = null, Dictionary<string, string>? templateParameters = null)
|
||||
{
|
||||
string content = emailSettings.CompiledTemplates[templateKey](templateParameters ?? new());
|
||||
return SendAsync(to, subject, content, from);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using Elektrifikatsiya.Models;
|
||||
using FluentResults;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Elektrifikatsiya.Services.Implementations
|
||||
{
|
||||
public class EventService : IEventService
|
||||
{
|
||||
private readonly ILogger<EventService> logger;
|
||||
public event EventHandler<EventServiceEventArgs> OnEventCalled;
|
||||
public List<Event> Events { get; } = new();
|
||||
|
||||
public EventService(ILogger<EventService> logger, IDeviceStatusService deviceStatusService, IDeviceManagmentService deviceManagmentService)
|
||||
{
|
||||
this.logger = logger;
|
||||
List<Device> deviceList = deviceManagmentService.GetDevices().ValueOrDefault.Select(x=>x.CopyDevice()).ToList();
|
||||
|
||||
deviceStatusService.OnDeviceStatusChanged += (_, e) =>
|
||||
{
|
||||
bool newDevice = true;
|
||||
Device currentDevice = deviceManagmentService.GetDevice(e.MacAddress).ValueOrDefault;
|
||||
|
||||
for (int i = 0; i < deviceList.Count; i++)
|
||||
{
|
||||
if (deviceList[i].MacAddress == currentDevice.MacAddress)
|
||||
{
|
||||
PropertyInfo[] properties = typeof(Device).GetProperties();
|
||||
|
||||
foreach(PropertyInfo property in properties) {
|
||||
var currentvalue = property.GetValue(deviceList[i], null);
|
||||
var newvalue = property.GetValue(currentDevice, null);
|
||||
if(newvalue is not null && !newvalue.ToString().Equals(currentvalue.ToString()))
|
||||
{
|
||||
string eventName = $"Plug {deviceList[i].Name} changed: {property.Name}";
|
||||
string description = $"Changed {property.Name} of plug [{currentvalue}] to [{newvalue}]";
|
||||
DateTime dateTime = DateTime.Now;
|
||||
|
||||
Event newEvent = new Event(eventName, description, dateTime, currentDevice);
|
||||
Events.Insert(0,newEvent);
|
||||
OnEventCalled?.Invoke(this, new EventServiceEventArgs(newEvent));
|
||||
}
|
||||
|
||||
}
|
||||
deviceList[i].OverwiteDevice(currentDevice);
|
||||
newDevice = false; break;
|
||||
}
|
||||
}
|
||||
if (newDevice)
|
||||
{
|
||||
deviceList.Add(deviceManagmentService.GetDevice(e.MacAddress).ValueOrDefault);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Elektrifikatsiya.Utilities;
|
||||
|
||||
namespace Elektrifikatsiya.Services.Implementations;
|
||||
|
||||
public class NotifcationService : INotifcationService
|
||||
{
|
||||
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)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.emailService = emailService;
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using Elektrifikatsiya.Utilities;
|
||||
|
||||
namespace Elektrifikatsiya.Services.Implementations;
|
||||
|
||||
public class ServiceScheduler : IHostedService
|
||||
{
|
||||
private readonly ILogger<ServiceScheduler> logger;
|
||||
private readonly IEnumerable<IScheduledService> scheduledServices;
|
||||
private readonly Dictionary<IScheduledService, DateTime> lastExectued;
|
||||
|
||||
private Timer timer = null!;
|
||||
|
||||
public ServiceScheduler(ILogger<ServiceScheduler> logger, IServiceProvider serviceProvider)
|
||||
{
|
||||
this.logger = logger;
|
||||
scheduledServices = serviceProvider.GetServices<IScheduledService>();
|
||||
lastExectued = scheduledServices.ToDictionary(x => x, x => x.FirstExecutionTime);
|
||||
|
||||
}
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
logger.LogInformation("Starting service scheduler...");
|
||||
|
||||
timer = new Timer(async (_) => await Update(), null, TimeSpan.Zero, Timeout.InfiniteTimeSpan);
|
||||
|
||||
logger.LogInformation("Service scheduler started.");
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
logger.LogInformation("Stopping service scheduler.");
|
||||
|
||||
timer.Dispose();
|
||||
|
||||
logger.LogInformation("Stopped service scheduler.");
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task Update()
|
||||
{
|
||||
TimeSpan s = lastExectued.Min(x => (x.Value + x.Key.ExecutionRepeatDelay)) - DateTime.Now;
|
||||
|
||||
if (s < TimeSpan.Zero)
|
||||
{
|
||||
s = TimeSpan.FromSeconds(1);
|
||||
}
|
||||
|
||||
timer.Change(s, Timeout.InfiniteTimeSpan);
|
||||
|
||||
foreach (IScheduledService scheduledService in scheduledServices.Where(s => s.FirstExecutionTime < DateTime.Now))
|
||||
{
|
||||
if (lastExectued[scheduledService] + scheduledService.ExecutionRepeatDelay <= DateTime.Now)
|
||||
{
|
||||
lastExectued[scheduledService] = DateTime.Now;
|
||||
scheduledService.Update();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,38 +8,29 @@ using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Elektrifikatsiya.Services.Implementations;
|
||||
|
||||
public class UpdateService : IHostedService, IDisposable
|
||||
public class UpdateService : IUpdateService
|
||||
{
|
||||
private readonly ILogger<UpdateService> logger;
|
||||
private readonly IDeviceStatusService deviceStatusService;
|
||||
private readonly IServiceScopeFactory serviceScopeFactory;
|
||||
private Timer? timer = null;
|
||||
|
||||
public UpdateService(ILogger<UpdateService> logger, IDeviceStatusService deviceStatusService, IServiceScopeFactory serviceScopeFactory)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.deviceStatusService = deviceStatusService;
|
||||
this.serviceScopeFactory = serviceScopeFactory;
|
||||
}
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
IServiceScope serviceScope = serviceScopeFactory.CreateScope();
|
||||
MainDatabaseContext mainDatabaseContext = serviceScope.ServiceProvider.GetRequiredService<MainDatabaseContext>();
|
||||
|
||||
logger.LogInformation("Starting update service...");
|
||||
|
||||
foreach (Device device in await mainDatabaseContext.Devices.Include(d => d.User).AsNoTracking().ToListAsync(cancellationToken))
|
||||
foreach (Device device in mainDatabaseContext.Devices.Include(d => d.User).AsNoTracking().ToList())
|
||||
{
|
||||
_ = deviceStatusService.TrackDevice(device);
|
||||
}
|
||||
|
||||
timer = new Timer(async (_) => await Update(), null, TimeSpan.Zero, TimeSpan.FromSeconds(15));
|
||||
|
||||
logger.LogInformation("Update service started.");
|
||||
}
|
||||
|
||||
private async Task Update()
|
||||
public DateTime FirstExecutionTime { get; } = DateTime.Now.AddSeconds(15);
|
||||
public TimeSpan ExecutionRepeatDelay { get; } = TimeSpan.FromSeconds(15);
|
||||
|
||||
public async void Update()
|
||||
{
|
||||
Result<List<Device>> getDeviceStatusResult = deviceStatusService.GetDevices();
|
||||
|
||||
@@ -61,26 +52,4 @@ public class UpdateService : IHostedService, IDisposable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
logger.LogInformation("Stopping update service.");
|
||||
|
||||
_ = timer?.Change(Timeout.Infinite, 0);
|
||||
|
||||
logger.LogInformation("Stopped update service.");
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
timer?.Dispose();
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,23 @@
|
||||
using HandlebarsDotNet;
|
||||
|
||||
namespace Elektrifikatsiya.Utilities;
|
||||
|
||||
public class EmailSettings
|
||||
{
|
||||
public string User { get; set; }
|
||||
public string DefaultEmail { get; set; }
|
||||
public string Key { get; set; }
|
||||
public Dictionary<string, string> Templates { get; set; }
|
||||
public string SmtpServer { get; set; }
|
||||
public int SmtpPort { get; set; }
|
||||
|
||||
public Dictionary<string, HandlebarsTemplate<object, object>> CompiledTemplates = new();
|
||||
|
||||
public void CompileTemplates()
|
||||
{
|
||||
foreach ((string templateName, string templatePath) in Templates)
|
||||
{
|
||||
CompiledTemplates.Add(templateName, Handlebars.Compile(File.ReadAllText(templatePath)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Elektrifikatsiya.Utilities;
|
||||
|
||||
public interface IScheduledService
|
||||
{
|
||||
DateTime FirstExecutionTime { get; }
|
||||
TimeSpan ExecutionRepeatDelay { get; }
|
||||
|
||||
void Update();
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"DetailedErrors": true,
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,49 @@
|
||||
<mjml>
|
||||
<mj-head>
|
||||
<mj-attributes>
|
||||
<mj-class name="primary" background-color="#1a237e"/>
|
||||
</mj-attributes>
|
||||
<mj-style>
|
||||
@font-face {
|
||||
font-family: Nunito;
|
||||
src: url(https://fonts.gstatic.com/s/nunito/v16/XRXV3I6Li01BKofINeaB.woff2)
|
||||
format('truetype');
|
||||
}
|
||||
</mj-style>
|
||||
</mj-head>
|
||||
<mj-body background-color="white">
|
||||
<mj-wrapper padding-right="20px" padding-left="20px" padding-top="7%" >
|
||||
<mj-section border-right="2px solid #B0B0B0" border-top="2px solid #B0B0B0" border-left="2px solid #B0B0B0" border-radius="33px">
|
||||
</mj-section>
|
||||
<mj-section padding-bottom="0px" padding-top="0px" border-left="2px solid #B0B0B0" border-right="2px solid #B0B0B0">
|
||||
<mj-column>
|
||||
<mj-text align="center" font-family="Nunito, Sans-Serif" font-weight="bold" font-size="30px">
|
||||
Daily Notification
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
<mj-section padding-bottom="0px" border-left="2px solid #B0B0B0" border-right="2px solid #B0B0B0">
|
||||
<mj-column padding-left="5%" padding-right="5%">
|
||||
<mj-text font-family="Nunito, Sans-Serif" align="center" font-size="18px">
|
||||
Energy Consumption yesterday: {{yesterday}}
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
<mj-column>
|
||||
<mj-text font-family="Nunito, Sans-Serif" algin="center" font-size="18px">
|
||||
Energy Consumption since the start of the year: {{yearly}}
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
<mj-section padding-top="0px" border-right="2px solid #B0B0B0" border-left="2px solid #B0B0B0" border-bottom="2px solid #B0B0B0" border-radius="33px">
|
||||
<mj-column>
|
||||
<mj-button font-size="16px" mj-class="primary" font-weight="bold" font-family="Nunito, Sans-Serif">
|
||||
<mj-text>
|
||||
<a href="{{link}}" target="_blank" style="color:white; text-decoration:none;">Goto Dashboard</a>
|
||||
</mj-text>
|
||||
</mj-button>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
</mj-wrapper>
|
||||
|
||||
</mj-body>
|
||||
</mjml>
|
||||
Reference in New Issue
Block a user