diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Models/Device.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Models/Device.cs index 0bcca1a..4d15c15 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Models/Device.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Models/Device.cs @@ -34,4 +34,6 @@ public class Device User = user; Room = room; } + + private Device(){} } \ No newline at end of file diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/AddPlug.razor b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/AddPlug.razor index fa1bb7a..41cb0fb 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/AddPlug.razor +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/AddPlug.razor @@ -1,7 +1,14 @@ @page "/addplug" @using Blazorise.Components; @using Elektrifikatsiya.Models; +@using System.Net +@using Elektrifikatsiya.Services +@using Elektrifikatsiya.Services.Implementations +@using Elektrifikatsiya.Utilities @inject IVersionProvider VersionProvider +@inject IDeviceManagmentService DeviceManagmentService +@inject IAuthenticationService AuthenticationService +
@@ -16,32 +23,36 @@ Add a plug via wifi - - - + + + + + - - - - IP: @context.Item.Address - - - - - + + @context.Item - - - + + + IP: @context.Item + + + + + + + + + + @@ -80,5 +91,15 @@
@code { - List plugs = new List() { new Device("Placeholder", "Plug", System.Net.IPAddress.Broadcast, 60, "Oldin", "4AHINF"), new Device("Placeholder", "Plug", System.Net.IPAddress.Broadcast, 60, "Oldin", "4AHINF"), new Device("Placeholder", "Plug", System.Net.IPAddress.Broadcast, 60, "Oldin", "4AHINF") }; + List ipAddresses = new List(); + + protected override void OnInitialized() + { + ipAddresses.AddRange(MdnsDiscovery.GetChachedDevices()); + MdnsDiscovery.OnDeviceFound += address => + { + ipAddresses.Add(address); + InvokeAsync(StateHasChanged); + }; + } } diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor index fa442fe..049c64e 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor @@ -1,6 +1,7 @@ @page "/" @using Blazorise.Components; @using Elektrifikatsiya.Models; +@using System.Net @inject IVersionProvider VersionProvider @@ -90,8 +91,7 @@ //TODO: insert new event here if plug produces one List events = new List() { new Event("Placeholder", "Placeholder", DateTime.Now), new Event("Placeholder", "Placeholder", DateTime.Now), new Event("Placeholder", "Placeholder", DateTime.Now) }; //TODO: insert new Device here if user adds one - List plugs = new List() { new Device("Placeholder", "Plug", System.Net.IPAddress.Broadcast, 60, "Oldin", "4AHINF"), new Device("Placeholder", "Plug", System.Net.IPAddress.Broadcast, 60, "Oldin", "4AHINF"), new Device("Placeholder", "Plug", System.Net.IPAddress.Broadcast, 60, "Oldin", "4AHINF") }; - + List plugs = new List() { new Device("ffff:ffff:ffff:ffff", "Huso", IPAddress.Broadcast, new User("Joe", "qiowruioewjfopa", Role.Admin), "Raum"), new Device("ffff:ffff:ffff:ffff", "Huso", IPAddress.Broadcast, new User("Joe", "qiowruioewjfopa", Role.Admin), "Raum"), new Device("ffff:ffff:ffff:ffff", "Huso", IPAddress.Broadcast, new User("Joe", "qiowruioewjfopa", Role.Admin), "Raum"), }; //code for graph LineChart lineChart; diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Program.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Program.cs index dfa27db..3c21487 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Program.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Program.cs @@ -4,6 +4,7 @@ using Blazorise.Icons.Material; using Blazorise.Material; using Elektrifikatsiya.Database; +using Elektrifikatsiya.Models; using Elektrifikatsiya.Services; using Elektrifikatsiya.Services.Implementations; @@ -14,15 +15,20 @@ WebApplicationBuilder builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddRazorPages(); builder.Services.AddServerSideBlazor(); +builder.Services.AddHttpContextAccessor(); builder.Services.AddHostedService(); builder.Services.AddSingleton(); builder.Services.AddTransient(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddDbContext(options => options.UseSqlite("Data Source=./UserDatabase.sqlite")); builder.Services.AddDbContext((options) => options.UseSqlite("Data Source=./DeviceManagement.sqlite")); builder.Services.AddBootstrapProviders(); +builder.Services.AddHttpClient(); builder.Services.AddBlazorise(options => { - options.Immediate = true; + options.Immediate = true; }); AddBlazorise(builder.Services); @@ -32,9 +38,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(); @@ -44,12 +50,11 @@ app.UseStaticFiles(); app.UseRouting(); app.MapBlazorHub(); app.MapFallbackToPage("/_Host"); +app.MapControllers(); IServiceScope serviceScope = app.Services.GetRequiredService().CreateScope(); serviceScope.ServiceProvider.GetRequiredService().Database.EnsureCreated(); -app.MapControllers(); - AsyncServiceScope scope = app.Services.CreateAsyncScope(); scope.ServiceProvider.GetRequiredService().Database.EnsureCreated(); @@ -57,7 +62,7 @@ app.Run(); void AddBlazorise(IServiceCollection services) { - _ = services.AddBlazorise(); - _ = services.AddMaterialProviders(); - _ = services.AddMaterialIcons(); + _ = services.AddBlazorise(); + _ = services.AddMaterialProviders(); + _ = services.AddMaterialIcons(); } \ No newline at end of file diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Properties/launchSettings.json b/src/Elektrifikatsiya/Elektrifikatsiya/Properties/launchSettings.json index 71c02c5..fe359ad 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Properties/launchSettings.json +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Properties/launchSettings.json @@ -7,7 +7,7 @@ "ASPNETCORE_ENVIRONMENT": "Development" }, "dotnetRunMessages": true, - "applicationUrl": "https://localhost:7297;http://localhost:5255" + "applicationUrl": "https://localhost:56656;http://localhost:5255" }, "IIS Express": { "commandName": "IISExpress", diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Services/IDeviceManagmentService.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Services/IDeviceManagmentService.cs index bda0e67..764ee22 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Services/IDeviceManagmentService.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Services/IDeviceManagmentService.cs @@ -8,9 +8,9 @@ namespace Elektrifikatsiya.Services; public interface IDeviceManagmentService { - public Task> Register(IPAddress ip, User user, string? name = null, string room = "default"); + public Task> RegisterDevice(IPAddress ip, User user, string? name = null, string room = "default"); - public Task Unregister(string macAdress); + public Task UnregisterDevice(string macAdress); public Result GetDevice(string macAdress); @@ -19,4 +19,6 @@ public interface IDeviceManagmentService public Result> GetDevicesInRoom(string room); public Result> GetDevicesOfUser(int userId); + + public Task UpdateDevice(Device device); } \ No newline at end of file diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/DeviceManagmentService.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/DeviceManagmentService.cs index b659cf1..34916e0 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/DeviceManagmentService.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/DeviceManagmentService.cs @@ -10,116 +10,153 @@ namespace Elektrifikatsiya.Services.Implementations; public class DeviceManagmentService : IDeviceManagmentService { - private readonly DeviceManagmentDatabaseContext deviceManagmentDatabaseContext; - private readonly IDeviceStatusService deviceStatusService; - private readonly HttpClient httpClient; + private readonly IServiceScopeFactory serviceScopeFactory; + private readonly IDeviceStatusService deviceStatusService; + private readonly ILogger logger; + private readonly HttpClient httpClient; - public DeviceManagmentService(DeviceManagmentDatabaseContext deviceManagmentDatabaseContext, IDeviceStatusService deviceStatusService, HttpClient httpClient) - { - this.deviceManagmentDatabaseContext = deviceManagmentDatabaseContext; - this.deviceStatusService = deviceStatusService; - this.httpClient = httpClient; - } + public DeviceManagmentService(IServiceScopeFactory serviceScopeFactory, IDeviceStatusService deviceStatusService, ILogger logger, HttpClient httpClient) + { + this.serviceScopeFactory = serviceScopeFactory; + this.deviceStatusService = deviceStatusService; + this.logger = logger; + this.httpClient = httpClient; + } - public Result GetDevice(string macAdress) - { - Result> getDevicesResult = deviceStatusService.GetDevices(); + public Result GetDevice(string macAdress) + { + Result> getDevicesResult = deviceStatusService.GetDevices(); - if (getDevicesResult.IsFailed) - { - return getDevicesResult.ToResult(); - } + if (getDevicesResult.IsFailed) + { + return getDevicesResult.ToResult(); + } - Device? device = getDevicesResult.Value.FirstOrDefault(d => d.MacAddress == macAdress); + Device? device = getDevicesResult.Value.FirstOrDefault(d => d.MacAddress == macAdress); - if (device is null) - { - return Result.Fail("Device does not exist!"); - } + if (device is null) + { + return Result.Fail("Device does not exist!"); + } - return device; - } + return device; + } - public Result> GetDevices() - { - Result> getDevicesResult = deviceStatusService.GetDevices(); + public Result> GetDevices() + { + Result> getDevicesResult = deviceStatusService.GetDevices(); - if (getDevicesResult.IsFailed) - { - return getDevicesResult.ToResult(); - } + if (getDevicesResult.IsFailed) + { + return getDevicesResult.ToResult(); + } - return getDevicesResult.Value.ToList(); - } + return getDevicesResult.Value.ToList(); + } - public Result> GetDevicesInRoom(string room) - { - Result> getDevicesResult = deviceStatusService.GetDevices(); + public Result> GetDevicesInRoom(string room) + { + Result> getDevicesResult = deviceStatusService.GetDevices(); - if (getDevicesResult.IsFailed) - { - return getDevicesResult.ToResult(); - } + if (getDevicesResult.IsFailed) + { + return getDevicesResult.ToResult(); + } - return getDevicesResult.Value.Where(d => d.Room == room).ToList(); - } + return getDevicesResult.Value.Where(d => d.Room == room).ToList(); + } - public Result> GetDevicesOfUser(int userId) - { - Result> getDevicesResult = deviceStatusService.GetDevices(); + public Result> GetDevicesOfUser(int userId) + { + Result> getDevicesResult = deviceStatusService.GetDevices(); - if (getDevicesResult.IsFailed) - { - return getDevicesResult.ToResult(); - } + if (getDevicesResult.IsFailed) + { + return getDevicesResult.ToResult(); + } - return getDevicesResult.Value.Where(d => d.User.Id == userId).ToList(); - } + return getDevicesResult.Value.Where(d => d.User.Id == userId).ToList(); + } - public async Task> Register(IPAddress ip, User user, string? name = null, string room = "default") - { - ShellyResponse? shellyResponse = await httpClient.GetFromJsonAsync($"{ip}/shelly"); + public async Task> RegisterDevice(IPAddress ip, User user, string? name = null, string room = "default") + { + using IServiceScope scope = serviceScopeFactory.CreateScope(); + DeviceManagmentDatabaseContext deviceManagmentDatabaseContext = scope.ServiceProvider.GetRequiredService(); - if(shellyResponse is null || shellyResponse.Type != "SHPLG-S") - { - return Result.Fail("Device is not a \"SHPLG-S\" or not reachable!"); - } + ShellyResponse? shellyResponse = null; - string mac = shellyResponse.Mac; + try + { + shellyResponse = await httpClient.GetFromJsonAsync($"http://{ip}/shelly"); + } + catch (Exception ex) + { + logger.LogError("HTTP request failed! {message}", ex.Message); + } - if(PhysicalAddress.TryParse(mac, out _)) - { - return Result.Fail("Invalid mac address!"); - } + if (shellyResponse is null || shellyResponse.Type != "SHPLG-S") + { + return Result.Fail("Device is not reachable or not a \"SHPLG-S\"!"); + } - Device device = new Device(mac, name ?? mac, ip, user, room); + string mac = shellyResponse.Mac; - deviceManagmentDatabaseContext.Add(device); - Result saveDatabaseChangesResult = await Result.Try(async Task () => await deviceManagmentDatabaseContext.SaveChangesAsync()); + if (!PhysicalAddress.TryParse(mac, out _)) + { + return Result.Fail("Invalid mac address!"); + } - Result trackDeviceResult = deviceStatusService.TrackDevice(device); + Device device = new Device(mac, name ?? mac, ip, user, room); - return Result.Merge(saveDatabaseChangesResult, trackDeviceResult).ToResult(device); - } + _ = deviceManagmentDatabaseContext.Add(device); + Result saveDatabaseChangesResult = await Result.Try(Task () => deviceManagmentDatabaseContext.SaveChangesAsync()); - public async Task Unregister(string macAdress) - { - Result getDeviceResult = GetDevice(macAdress); + if (saveDatabaseChangesResult.IsFailed) + { + return saveDatabaseChangesResult; + } - if (getDeviceResult.IsFailed) - { - return getDeviceResult.ToResult(); - } + return deviceStatusService.TrackDevice(device); + } - Result untrackDeviceResult = deviceStatusService.UntrackDevice(macAdress); + public async Task UnregisterDevice(string macAdress) + { + using IServiceScope scope = serviceScopeFactory.CreateScope(); + DeviceManagmentDatabaseContext deviceManagmentDatabaseContext = scope.ServiceProvider.GetRequiredService(); - if (untrackDeviceResult.IsFailed) - { - return untrackDeviceResult; - } + Result getDeviceResult = GetDevice(macAdress); - deviceManagmentDatabaseContext.Remove(getDeviceResult.Value); + if (getDeviceResult.IsFailed) + { + return getDeviceResult.ToResult(); + } - return await Result.Try(async Task () => await deviceManagmentDatabaseContext.SaveChangesAsync()); - } + Result untrackDeviceResult = deviceStatusService.UntrackDevice(macAdress); + + if (untrackDeviceResult.IsFailed) + { + return untrackDeviceResult; + } + + _ = deviceManagmentDatabaseContext.Remove(getDeviceResult.Value); + + return await Result.Try(Task () => deviceManagmentDatabaseContext.SaveChangesAsync()); + } + + public async Task UpdateDevice(Device device) + { + using IServiceScope scope = serviceScopeFactory.CreateScope(); + DeviceManagmentDatabaseContext deviceManagmentDatabaseContext = scope.ServiceProvider.GetRequiredService(); + + Result result = deviceStatusService.UpdateDeviceStatus(device); + + if (result.IsFailed) + { + return result; + } + + _ = deviceManagmentDatabaseContext.Update(device); + + return await Result.Try(Task () => deviceManagmentDatabaseContext.SaveChangesAsync()); + } } \ No newline at end of file diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/DeviceStatusService.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/DeviceStatusService.cs index 3574cec..2e6e904 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/DeviceStatusService.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/DeviceStatusService.cs @@ -6,10 +6,15 @@ namespace Elektrifikatsiya.Services.Implementations; public class DeviceStatusService : IDeviceStatusService { - public event EventHandler? OnDeviceStatusChanged; + private readonly ILogger logger; + public event EventHandler? OnDeviceStatusChanged; private readonly Dictionary devices = new(); - + + public DeviceStatusService(ILogger logger) + { + this.logger = logger; + } public Result> GetDevices() { return devices.Values.ToList(); @@ -41,6 +46,8 @@ public class DeviceStatusService : IDeviceStatusService return Result.Fail("Device already tracked!"); } + logger.LogInformation("Tracking new Device {device}", device.IpAddress); + devices[device.MacAddress] = device; return Result.Ok(); diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/UpdateService.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/UpdateService.cs index 980826d..da74f9f 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/UpdateService.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/UpdateService.cs @@ -10,19 +10,22 @@ public class UpdateService : IHostedService, IDisposable { private readonly ILogger logger; private readonly IDeviceStatusService deviceStatusService; - private readonly DeviceManagmentDatabaseContext deviceManagmentDatabaseContext; + private readonly IServiceScopeFactory serviceScopeFactory; private Timer? timer = null; - public UpdateService(ILogger logger, IDeviceStatusService deviceStatusService, DeviceManagmentDatabaseContext deviceManagmentDatabaseContext) + public UpdateService(ILogger logger, IDeviceStatusService deviceStatusService, IServiceScopeFactory serviceScopeFactory) { this.logger = logger; this.deviceStatusService = deviceStatusService; - this.deviceManagmentDatabaseContext = deviceManagmentDatabaseContext; + this.serviceScopeFactory = serviceScopeFactory; } public async Task StartAsync(CancellationToken cancellationToken) { - logger.LogInformation("Starting update service..."); + IServiceScope serviceScope = serviceScopeFactory.CreateScope(); + DeviceManagmentDatabaseContext deviceManagmentDatabaseContext = serviceScope.ServiceProvider.GetRequiredService(); + + logger.LogInformation("Starting update service..."); foreach (Device device in await deviceManagmentDatabaseContext.Devices.AsNoTracking().ToListAsync(cancellationToken)) { diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Utilities/MdnsDiscovery.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Utilities/MdnsDiscovery.cs new file mode 100644 index 0000000..55a8943 --- /dev/null +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Utilities/MdnsDiscovery.cs @@ -0,0 +1,25 @@ +using System.Net; + +using Tmds.MDns; + +namespace Elektrifikatsiya.Utilities; + +public static class MdnsDiscovery +{ + public static event Action OnDeviceFound = null!; + + private static readonly ServiceBrowser serviceBrowser = new ServiceBrowser(); + + static MdnsDiscovery() + { + serviceBrowser.StartBrowse("_http._tcp"); + serviceBrowser.ServiceAdded += (_, eventArgs) => OnDeviceFound?.Invoke(eventArgs.Announcement.Addresses.First()); + serviceBrowser.ServiceChanged += (_, eventArgs) => OnDeviceFound?.Invoke(eventArgs.Announcement.Addresses.First()); + serviceBrowser.ServiceRemoved += (_, eventArgs) => OnDeviceFound?.Invoke(eventArgs.Announcement.Addresses.First()); + } + + public static IEnumerable GetChachedDevices() + { + return serviceBrowser.Services.Select(s => s.Addresses.First()); + } +} \ No newline at end of file