From 40b9d9aa614456f39e7ea4c0be18280f709d9543 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Wed, 15 Mar 2023 10:35:15 +0100 Subject: [PATCH] Improve device discovery service and add page --- .../DeviceManagementDatabaseContext.cs | 14 -- ...abaseContext.cs => MainDatabaseContext.cs} | 5 +- .../Elektrifikatsiya/Pages/AddPlug.razor | 226 +++++++++++------- .../Elektrifikatsiya/Program.cs | 23 +- .../Implementations/AuthenticationService.cs | 26 +- .../Implementations/DeviceManagmentService.cs | 224 ++++++++--------- .../Services/Implementations/UpdateService.cs | 17 +- .../Utilities/MdnsDiscovery.cs | 55 ++++- 8 files changed, 334 insertions(+), 256 deletions(-) delete mode 100644 src/Elektrifikatsiya/Elektrifikatsiya/Database/DeviceManagementDatabaseContext.cs rename src/Elektrifikatsiya/Elektrifikatsiya/Database/{UserDatabaseContext.cs => MainDatabaseContext.cs} (58%) diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Database/DeviceManagementDatabaseContext.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Database/DeviceManagementDatabaseContext.cs deleted file mode 100644 index 85d2699..0000000 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Database/DeviceManagementDatabaseContext.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Elektrifikatsiya.Models; -using Microsoft.EntityFrameworkCore; - -namespace Elektrifikatsiya.Database -{ - public class DeviceManagmentDatabaseContext : DbContext - { - public DbSet Devices { get; set; } - - public DeviceManagmentDatabaseContext(DbContextOptions options) : base(options) - { - } - } -} diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Database/UserDatabaseContext.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Database/MainDatabaseContext.cs similarity index 58% rename from src/Elektrifikatsiya/Elektrifikatsiya/Database/UserDatabaseContext.cs rename to src/Elektrifikatsiya/Elektrifikatsiya/Database/MainDatabaseContext.cs index b3a6730..d58b2b1 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Database/UserDatabaseContext.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Database/MainDatabaseContext.cs @@ -4,11 +4,12 @@ using Microsoft.EntityFrameworkCore; namespace Elektrifikatsiya.Database; -public class UserDatabaseContext : DbContext +public class MainDatabaseContext : DbContext { - public UserDatabaseContext(DbContextOptions dbContextOptions) : base(dbContextOptions) + public MainDatabaseContext(DbContextOptions dbContextOptions) : base(dbContextOptions) { } public DbSet Users { get; set; } + public DbSet Devices { get; set; } } \ No newline at end of file diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/AddPlug.razor b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/AddPlug.razor index f14522f..e3c034f 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/AddPlug.razor +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/AddPlug.razor @@ -5,98 +5,150 @@ @using Elektrifikatsiya.Services @using Elektrifikatsiya.Services.Implementations @using Elektrifikatsiya.Utilities +@using FluentResults; @inject IVersionProvider VersionProvider @inject IDeviceManagmentService DeviceManagmentService -@inject IAuthenticationService AuthenticationService +@inject IAuthenticationService AuthenticationService +@inject IMessageService MessageService -
- - - - - - - Add a plug via wifi - - - - - - - - - - - - IP: @context.Item - - - - - - - - - - - - - - - - - - - - - Add a plug via IP - - - - -
- - - -
- -
-
-
-
-
-
- -
-
+
+ + + + + + + Add a plug via wifi + + + + + + + + + + + + IP: @context.Item + + + + + + + + + + + + + + + + + + + + + Add a plug via IP + + + + +
+ + + + + + Please enter a ip address. + Ip address is valid. + Ip address is not valid! + + +
+ +
+
+
+
+
+
+
+ +
+
@code { - List ipAddresses = new List(); + List ipAddresses = new List(); - protected override void OnInitialized() - { - ipAddresses.AddRange(MdnsDiscovery.GetChachedDevices()); - MdnsDiscovery.OnDeviceFound += address => - { - ipAddresses.Add(address); - InvokeAsync(StateHasChanged); - }; - } + Validation? ipValidation; + + string ipText = null!; + + protected override void OnInitialized() + { + MdnsDiscovery.OnDeviceFound += async ipAddress => + { + if (!ipAddresses.Contains(ipAddress)) + { + ipAddresses.Add(ipAddress); + await InvokeAsync(StateHasChanged); + } + }; + + MdnsDiscovery.FetchChachedDevices(); + } + + private async Task RegisterDevice(IPAddress ipAddress) + { + Result result = await DeviceManagmentService.RegisterDevice(ipAddress, (await AuthenticationService.GetUserAsync()).ValueOrDefault); + + if (result.IsSuccess) + { + await MessageService.Success("Device added!"); + } + else + { + await MessageService.Error(string.Join(',', result.Errors.Select(e => e.Message)), "Adding device failed!"); + } + } + + public void ValidateIPv4(ValidatorEventArgs e) + { + string? input = Convert.ToString(e.Value); + + if (string.IsNullOrWhiteSpace(input)) + { + e.Status = ValidationStatus.None; + return; + } + + string[] splitValues = input.Split('.'); + if (splitValues.Length != 4) + { + e.Status = ValidationStatus.Error; + return; + } + + e.Status = splitValues.All(r => byte.TryParse(r, out _)) ? ValidationStatus.Success : ValidationStatus.Error; + } } diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Program.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Program.cs index 3c21487..6bf5c06 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Program.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Program.cs @@ -4,7 +4,6 @@ using Blazorise.Icons.Material; using Blazorise.Material; using Elektrifikatsiya.Database; -using Elektrifikatsiya.Models; using Elektrifikatsiya.Services; using Elektrifikatsiya.Services.Implementations; @@ -22,13 +21,12 @@ 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.AddDbContext(options => options.UseSqlite("Data Source=./MainDatabase.sqlite")); builder.Services.AddBootstrapProviders(); builder.Services.AddHttpClient(); builder.Services.AddBlazorise(options => { - options.Immediate = true; + options.Immediate = true; }); AddBlazorise(builder.Services); @@ -38,9 +36,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(); @@ -53,16 +51,13 @@ app.MapFallbackToPage("/_Host"); app.MapControllers(); IServiceScope serviceScope = app.Services.GetRequiredService().CreateScope(); -serviceScope.ServiceProvider.GetRequiredService().Database.EnsureCreated(); - -AsyncServiceScope scope = app.Services.CreateAsyncScope(); -scope.ServiceProvider.GetRequiredService().Database.EnsureCreated(); +serviceScope.ServiceProvider.GetRequiredService().Database.EnsureCreated(); 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/Services/Implementations/AuthenticationService.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/AuthenticationService.cs index 36ce68e..b0637e1 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/AuthenticationService.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/AuthenticationService.cs @@ -12,13 +12,13 @@ namespace Elektrifikatsiya.Services.Implementations; public class AuthenticationService : IAuthenticationService { - private readonly UserDatabaseContext userDatabaseContext; + private readonly MainDatabaseContext mainDatabaseContext; private readonly IHttpContextAccessor httpContextAccessor; private readonly ICookieService cookieService; - public AuthenticationService(UserDatabaseContext userDatabaseContext, IHttpContextAccessor httpContextAccessor, ICookieService cookieService) + public AuthenticationService(MainDatabaseContext mainDatabaseContext, IHttpContextAccessor httpContextAccessor, ICookieService cookieService) { - this.userDatabaseContext = userDatabaseContext; + this.mainDatabaseContext = mainDatabaseContext; this.httpContextAccessor = httpContextAccessor; this.cookieService = cookieService; } @@ -32,8 +32,8 @@ public class AuthenticationService : IAuthenticationService return getUserResult.ToResult(); } - _ = userDatabaseContext.Users.Remove(getUserResult.Value); - return (await Result.Try(() => userDatabaseContext.SaveChangesAsync())).ToResult(); + _ = mainDatabaseContext.Users.Remove(getUserResult.Value); + return (await Result.Try(() => mainDatabaseContext.SaveChangesAsync())).ToResult(); } public async Task> GetUserAsync() @@ -45,7 +45,7 @@ public class AuthenticationService : IAuthenticationService return Result.Fail("Token is not valid!"); } - User? user = await userDatabaseContext.Users.FirstOrDefaultAsync(u => u.SessionToken == token); + User? user = await mainDatabaseContext.Users.FirstOrDefaultAsync(u => u.SessionToken == token); if (user is null || DateTime.UtcNow - user.LastLoginDate > TimeSpan.FromDays(7)) { @@ -62,7 +62,7 @@ public class AuthenticationService : IAuthenticationService public async Task LoginUserAsync(string name, string password) { - User? user = await userDatabaseContext.Users.FirstOrDefaultAsync(u => u.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)); + User? user = await mainDatabaseContext.Users.FirstOrDefaultAsync(u => u.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)); if (user is null) { @@ -79,7 +79,7 @@ public class AuthenticationService : IAuthenticationService user.LastLoginDate = DateTime.UtcNow; user.SessionToken = newToken; - _ = await userDatabaseContext.SaveChangesAsync(); + _ = await mainDatabaseContext.SaveChangesAsync(); await cookieService.WriteCookieAsync("token", newToken, 7); @@ -97,7 +97,7 @@ public class AuthenticationService : IAuthenticationService return Result.Ok(); } - User? user = await userDatabaseContext.Users.FirstOrDefaultAsync(u => u.SessionToken == token); + User? user = await mainDatabaseContext.Users.FirstOrDefaultAsync(u => u.SessionToken == token); if (user is null) { @@ -106,7 +106,7 @@ public class AuthenticationService : IAuthenticationService user.SessionToken = null; - _ = await userDatabaseContext.SaveChangesAsync(); + _ = await mainDatabaseContext.SaveChangesAsync(); return Result.Ok(); } @@ -121,14 +121,14 @@ public class AuthenticationService : IAuthenticationService } User user = new User(name, BC.HashPassword(password), role); - _ = userDatabaseContext.Users.Add(user); + _ = mainDatabaseContext.Users.Add(user); - return (await Result.Try(() => userDatabaseContext.SaveChangesAsync())).ToResult(); + return (await Result.Try(() => mainDatabaseContext.SaveChangesAsync())).ToResult(); } public Task> UserExistsAsync(string name) { return Result.Try(() => - userDatabaseContext.Users.AnyAsync(u => u.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase))); + mainDatabaseContext.Users.AnyAsync(u => u.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase))); } } \ 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 34916e0..b5df353 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/DeviceManagmentService.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/DeviceManagmentService.cs @@ -10,153 +10,153 @@ namespace Elektrifikatsiya.Services.Implementations; public class DeviceManagmentService : IDeviceManagmentService { - private readonly IServiceScopeFactory serviceScopeFactory; - private readonly IDeviceStatusService deviceStatusService; - private readonly ILogger logger; - private readonly HttpClient httpClient; + private readonly IServiceScopeFactory serviceScopeFactory; + private readonly IDeviceStatusService deviceStatusService; + private readonly ILogger logger; + private readonly 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 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> RegisterDevice(IPAddress ip, User user, string? name = null, string room = "default") - { - using IServiceScope scope = serviceScopeFactory.CreateScope(); - DeviceManagmentDatabaseContext deviceManagmentDatabaseContext = scope.ServiceProvider.GetRequiredService(); + public async Task> RegisterDevice(IPAddress ip, User user, string? name = null, string room = "default") + { + using IServiceScope scope = serviceScopeFactory.CreateScope(); + MainDatabaseContext mainDatabaseContext = scope.ServiceProvider.GetRequiredService(); - ShellyResponse? shellyResponse = null; + ShellyResponse? shellyResponse = null; - try - { - shellyResponse = await httpClient.GetFromJsonAsync($"http://{ip}/shelly"); - } - catch (Exception ex) - { - logger.LogError("HTTP request failed! {message}", ex.Message); - } + try + { + shellyResponse = await httpClient.GetFromJsonAsync($"http://{ip}/shelly"); + } + catch (Exception ex) + { + logger.LogError("HTTP request failed! {message}", ex.Message); + } - if (shellyResponse is null || shellyResponse.Type != "SHPLG-S") - { - return Result.Fail("Device is not reachable or not a \"SHPLG-S\"!"); - } + if (shellyResponse is null || shellyResponse.Type != "SHPLG-S") + { + return Result.Fail("Device is not reachable or not a \"SHPLG-S\"!"); + } - string mac = shellyResponse.Mac; + string mac = shellyResponse.Mac; - if (!PhysicalAddress.TryParse(mac, out _)) - { - return Result.Fail("Invalid mac address!"); - } + if (!PhysicalAddress.TryParse(mac, out _)) + { + return Result.Fail("Invalid mac address!"); + } - Device device = new Device(mac, name ?? mac, ip, user, room); + Device device = new Device(mac, name ?? mac, ip, user, room); - _ = deviceManagmentDatabaseContext.Add(device); - Result saveDatabaseChangesResult = await Result.Try(Task () => deviceManagmentDatabaseContext.SaveChangesAsync()); + _ = mainDatabaseContext.Add(device); + Result saveDatabaseChangesResult = await Result.Try(Task () => mainDatabaseContext.SaveChangesAsync()); - if (saveDatabaseChangesResult.IsFailed) - { - return saveDatabaseChangesResult; - } + if (saveDatabaseChangesResult.IsFailed) + { + return saveDatabaseChangesResult; + } - return deviceStatusService.TrackDevice(device); - } + return deviceStatusService.TrackDevice(device); + } - public async Task UnregisterDevice(string macAdress) - { - using IServiceScope scope = serviceScopeFactory.CreateScope(); - DeviceManagmentDatabaseContext deviceManagmentDatabaseContext = scope.ServiceProvider.GetRequiredService(); + public async Task UnregisterDevice(string macAdress) + { + using IServiceScope scope = serviceScopeFactory.CreateScope(); + MainDatabaseContext mainDatabaseContext = scope.ServiceProvider.GetRequiredService(); - Result getDeviceResult = GetDevice(macAdress); + Result getDeviceResult = GetDevice(macAdress); - if (getDeviceResult.IsFailed) - { - return getDeviceResult.ToResult(); - } + if (getDeviceResult.IsFailed) + { + return getDeviceResult.ToResult(); + } - Result untrackDeviceResult = deviceStatusService.UntrackDevice(macAdress); + Result untrackDeviceResult = deviceStatusService.UntrackDevice(macAdress); - if (untrackDeviceResult.IsFailed) - { - return untrackDeviceResult; - } + if (untrackDeviceResult.IsFailed) + { + return untrackDeviceResult; + } - _ = deviceManagmentDatabaseContext.Remove(getDeviceResult.Value); + _ = mainDatabaseContext.Remove(getDeviceResult.Value); - return await Result.Try(Task () => deviceManagmentDatabaseContext.SaveChangesAsync()); - } + return await Result.Try(Task () => mainDatabaseContext.SaveChangesAsync()); + } - public async Task UpdateDevice(Device device) - { - using IServiceScope scope = serviceScopeFactory.CreateScope(); - DeviceManagmentDatabaseContext deviceManagmentDatabaseContext = scope.ServiceProvider.GetRequiredService(); + public async Task UpdateDevice(Device device) + { + using IServiceScope scope = serviceScopeFactory.CreateScope(); + MainDatabaseContext mainDatabaseContext = scope.ServiceProvider.GetRequiredService(); - Result result = deviceStatusService.UpdateDeviceStatus(device); + Result result = deviceStatusService.UpdateDeviceStatus(device); - if (result.IsFailed) - { - return result; - } + if (result.IsFailed) + { + return result; + } - _ = deviceManagmentDatabaseContext.Update(device); + _ = mainDatabaseContext.Update(device); - return await Result.Try(Task () => deviceManagmentDatabaseContext.SaveChangesAsync()); - } + return await Result.Try(Task () => mainDatabaseContext.SaveChangesAsync()); + } } \ No newline at end of file diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/UpdateService.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/UpdateService.cs index da74f9f..4055d80 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/UpdateService.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/UpdateService.cs @@ -1,8 +1,9 @@ using Elektrifikatsiya.Database; using Elektrifikatsiya.Models; + using FluentResults; + using Microsoft.EntityFrameworkCore; -using System.Threading.Tasks; namespace Elektrifikatsiya.Services.Implementations; @@ -22,22 +23,22 @@ public class UpdateService : IHostedService, IDisposable public async Task StartAsync(CancellationToken cancellationToken) { - IServiceScope serviceScope = serviceScopeFactory.CreateScope(); - DeviceManagmentDatabaseContext deviceManagmentDatabaseContext = serviceScope.ServiceProvider.GetRequiredService(); + IServiceScope serviceScope = serviceScopeFactory.CreateScope(); + MainDatabaseContext mainDatabaseContext = serviceScope.ServiceProvider.GetRequiredService(); - logger.LogInformation("Starting update service..."); + logger.LogInformation("Starting update service..."); - foreach (Device device in await deviceManagmentDatabaseContext.Devices.AsNoTracking().ToListAsync(cancellationToken)) + foreach (Device device in await mainDatabaseContext.Devices.AsNoTracking().ToListAsync(cancellationToken)) { _ = deviceStatusService.TrackDevice(device); } - timer = new Timer(async (_) => await Update(), null, TimeSpan.Zero, TimeSpan.FromSeconds(15)); + timer = new Timer((_) => Update(), null, TimeSpan.Zero, TimeSpan.FromSeconds(15)); logger.LogInformation("Update service started."); } - private async Task Update() + private void Update() { Result> getDeviceStatusResult = deviceStatusService.GetDevices(); @@ -46,7 +47,7 @@ public class UpdateService : IHostedService, IDisposable logger.LogError("Updating devices failed!"); } - foreach(Device device in getDeviceStatusResult.Value) + foreach (Device device in getDeviceStatusResult.Value) { //TODO: Some update magic } diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Utilities/MdnsDiscovery.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Utilities/MdnsDiscovery.cs index 55a8943..1041b79 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Utilities/MdnsDiscovery.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Utilities/MdnsDiscovery.cs @@ -1,4 +1,8 @@ -using System.Net; +using Elektrifikatsiya.Models; + +using Microsoft.Extensions.Caching.Memory; + +using System.Net; using Tmds.MDns; @@ -9,17 +13,56 @@ public static class MdnsDiscovery public static event Action OnDeviceFound = null!; private static readonly ServiceBrowser serviceBrowser = new ServiceBrowser(); + private static readonly HttpClient client = new(); + private static readonly MemoryCache memoryCache = new MemoryCache(new MemoryCacheOptions()); 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()); + serviceBrowser.ServiceAdded += async (_, eventArgs) => await AddDevice(eventArgs.Announcement); + serviceBrowser.ServiceChanged += async (_, eventArgs) => await AddDevice(eventArgs.Announcement); + serviceBrowser.ServiceRemoved += async (_, eventArgs) => await AddDevice(eventArgs.Announcement); } - public static IEnumerable GetChachedDevices() + public static void FetchChachedDevices() { - return serviceBrowser.Services.Select(s => s.Addresses.First()); + serviceBrowser.Services.ToList().ForEach(async d => await AddDevice(d)); + } + + private static async Task AddDevice(ServiceAnnouncement announcement) + { + IPAddress ipAddress = announcement.Addresses.First(); + + if (!memoryCache.TryGetValue(ipAddress.ToString(), out bool isShellyDevice)) + { + isShellyDevice = await IsShellyDevice(ipAddress); + _ = memoryCache.Set(ipAddress.ToString(), isShellyDevice, TimeSpan.FromHours(1)); + } + + if (isShellyDevice) + { + OnDeviceFound?.Invoke(ipAddress); + } + } + + private static async Task IsShellyDevice(IPAddress ipAddress) + { + ShellyResponse? shellyResponse; + + try + { + shellyResponse = await client.GetFromJsonAsync($"http://{ipAddress}/shelly"); + } + catch (HttpRequestException) + { + return false; + } + + if (shellyResponse is null || shellyResponse.Type != "SHPLG-S") + { + return false; + } + + return true; } } \ No newline at end of file