Squashed commit of the following:

commit a0adfc850d
Author: Stefan <[email protected]>
Date:   Thu Jun 1 15:28:39 2023 +0200

    Revert using Prom.Net

commit 08a5bd4772
Merge: a47c252 9d64698
Author: Stefan <[email protected]>
Date:   Thu Jun 1 15:27:48 2023 +0200

    Merge remote-tracking branch 'origin/feature-DeviceRegistration' into feature-DeviceRegistration

commit a47c2525e1
Author: Stefan <[email protected]>
Date:   Thu Jun 1 15:27:30 2023 +0200

    Revert using Prom.Net

commit 9d646988d1
Merge: 889fc5e 4ef3958
Author: 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

commit 889fc5e3dd
Author: Friend2868 <[email protected]>
Date:   Thu Jun 1 15:24:21 2023 +0200

    Final2

commit 92a7538996
Merge: d5f5ec2 f04724e
Author: 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

commit 4ef3958da3
Author: Stone_Red <[email protected]>
Date:   Thu Jun 1 15:23:10 2023 +0200

    Fix .gitignore

commit d5f5ec2d0e
Author: Friend2868 <[email protected]>
Date:   Thu Jun 1 15:21:37 2023 +0200

    Final

commit 236c2418c5
Author: Friend2868 <[email protected]>
Date:   Thu Jun 1 15:18:57 2023 +0200

    Update .gitignore

commit f04724e258
Merge: 9b2db9c f74cff3
Author: Stefan <[email protected]>
Date:   Thu Jun 1 15:15:57 2023 +0200

    Merge remote-tracking branch 'origin/feature-DeviceRegistration' into feature-DeviceRegistration

commit 9b2db9c97d
Author: Stefan <[email protected]>
Date:   Thu Jun 1 15:15:44 2023 +0200

    Changes 🐙

commit f74cff3d39
Author: Stone_Red <[email protected]>
Date:   Wed May 24 11:48:07 2023 +0200

    Update appsettings.Development.json

commit 2f79ccb22d
Author: Stone_Red <[email protected]>
Date:   Wed May 24 11:45:16 2023 +0200

    Update appsettings.Development.json

commit 104bd37bcb
Author: Stone_Red <[email protected]>
Date:   Wed May 24 11:44:58 2023 +0200

    Update appsettings.Development.json

commit bdac3581de
Author: Stefan <[email protected]>
Date:   Wed May 24 11:29:38 2023 +0200

    Revert test shid

commit 5b58318699
Merge: 5b342c1 b054b9c
Author: Stefan <[email protected]>
Date:   Wed May 24 11:28:20 2023 +0200

    Merge remote-tracking branch 'origin/feature-DeviceRegistration' into feature-DeviceRegistration

commit 5b342c1a8f
Author: Stefan <[email protected]>
Date:   Wed May 24 11:28:12 2023 +0200

    Add `ScheduledService` and email things ༼ つ ◕_◕ ༽つ

commit b054b9cfe3
Author: Friend2868 <[email protected]>
Date:   Wed May 24 11:16:18 2023 +0200

    EventService finish

commit 9520fa04b8
Author: Stefan <[email protected]>
Date:   Wed May 10 12:13:31 2023 +0200

    Email Inferstructure
This commit is contained in:
Stefan
2023-06-01 15:33:18 +02:00
parent 0364016766
commit 1028cbfb40
34 changed files with 529 additions and 166 deletions
+1
View File
@@ -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 DbSet<Device> Devices { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<EnergyPriceChange> EnergyPriceChanges { get; set; }
public MainDatabaseContext(DbContextOptions<MainDatabaseContext> dbContextOptions) : base(dbContextOptions)
{
}
public DbSet<User> Users { get; set; }
public DbSet<Device> Devices { get; set; }
public DbSet<Event> Events { get; set; }
}
@@ -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>
@@ -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,10 +5,15 @@ namespace Elektrifikatsiya.Models;
public class User
{
[Required]
public List<Device> Devices { get; set; } = new();
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int Id { get; private set; }
public DateTime LastLoginDate { get; set; }
[Required]
public string Name { get; private set; }
@@ -19,10 +24,6 @@ public class User
public Role Role { get; set; }
public string? SessionToken { get; set; }
public DateTime LastLoginDate { get; set; }
[Required]
public List<Device> Devices { get; set; } = new();
public User(string name, string 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;
@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()
@@ -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
{
}
@@ -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());
@@ -1,5 +1,4 @@
using Elektrifikatsiya.Models;
using FluentResults;
namespace Elektrifikatsiya.Services.Implementations;
@@ -7,8 +6,7 @@ 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();
@@ -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();
}
}
@@ -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>