Device discovery and maybe adding devices ╮(╯▽╰)╭

This commit is contained in:
Stefan
2023-03-02 16:48:37 +01:00
parent 1f745951d4
commit 894a23855f
7 changed files with 168 additions and 107 deletions
@@ -2,13 +2,18 @@
@using Blazorise.Components; @using Blazorise.Components;
@using Elektrifikatsiya.Models; @using Elektrifikatsiya.Models;
@using System.Net @using System.Net
@using Elektrifikatsiya.Services
@using Elektrifikatsiya.Services.Implementations
@using Elektrifikatsiya.Utilities
@inject IVersionProvider VersionProvider @inject IVersionProvider VersionProvider
@inject IDeviceManagmentService DeviceManagmentService
@inject IAuthenticationService AuthenticationService
<Row Height=Height.Is50 Padding="Padding.Is3.OnDesktop.Is0"> <Row Height=Height.Is50 Padding="Padding.Is3.OnDesktop.Is0">
<Column ColumnSize="ColumnSize.Is12" style="border-bottom-style:solid; border-bottom-color:#1a237e"> <Column ColumnSize="ColumnSize.Is12" style="border-bottom-style:solid; border-bottom-color:#1a237e">
<ListView Class="w-100" <ListView Class="w-100"
TItem="Device" TItem="IPAddress"
Data="@plugs" Data="@ipAddresses"
TextField="@((_)=>(""))" TextField="@((_)=>(""))"
ValueField="@((_)=>(""))" ValueField="@((_)=>(""))"
Mode="ListGroupMode.Static"> Mode="ListGroupMode.Static">
@@ -18,17 +23,17 @@
<Column ColumnSize="ColumnSize.Is6"> <Column ColumnSize="ColumnSize.Is6">
<Row> <Row>
<Column > <Column >
<Heading Size="HeadingSize.Is6" Margin="Margin.Is1.FromBottom">@context.Item.Name</Heading> <Heading Size="HeadingSize.Is6" Margin="Margin.Is1.FromBottom">@context.Item</Heading>
</Column> </Column>
</Row> </Row>
<Row Width="Width.Is100"> <Row Width="Width.Is100">
<Column> <Column>
<Small>IP: @context.Item.IpAddress</Small> <Small>IP: @context.Item</Small>
</Column> </Column>
</Row> </Row>
</Column> </Column>
<Column ColumnSize="ColumnSize.Is4.Is2.WithOffset"> <Column ColumnSize="ColumnSize.Is4.Is2.WithOffset">
<Button Position="Position.Absolute.Top.Is50.Start.Is50.Translate.Middle" Color="Color.Dark" Outline> <Button Position="Position.Absolute.Top.Is50.Start.Is50.Translate.Middle" Color="Color.Dark" Clicked="@(async () => await DeviceManagmentService.RegisterDevice(context.Item,(await AuthenticationService.GetUserAsync()).ValueOrDefault))" Outline>
<BarIcon IconName="IconName.Add" /> <BarIcon IconName="IconName.Add" />
</Button> </Button>
</Column> </Column>
@@ -40,5 +45,14 @@
</Row> </Row>
@code { @code {
List<Device> plugs = new List<Device>() { 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"), }; List<IPAddress> ipAddresses = new List<IPAddress>();
protected override async Task OnInitializedAsync()
{
MdnsDiscovery.OnDeviceFound += address =>
{
ipAddresses.Add(address);
InvokeAsync(StateHasChanged);
};
}
} }
@@ -15,16 +15,20 @@ WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
// Add services to the container. // Add services to the container.
builder.Services.AddRazorPages(); builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor(); builder.Services.AddServerSideBlazor();
builder.Services.AddHttpContextAccessor();
builder.Services.AddHostedService<UpdateService>(); builder.Services.AddHostedService<UpdateService>();
builder.Services.AddSingleton<IDeviceStatusService, DeviceStatusService>(); builder.Services.AddSingleton<IDeviceStatusService, DeviceStatusService>();
builder.Services.AddTransient<IDeviceManagmentService, DeviceManagmentService>(); builder.Services.AddTransient<IDeviceManagmentService, DeviceManagmentService>();
builder.Services.AddScoped<IAuthenticationService, AuthenticationService>();
builder.Services.AddScoped<IAuthorizationService, AuthorizationService>();
builder.Services.AddScoped<ICookieService, CookieService>();
builder.Services.AddDbContext<UserDatabaseContext>(options => options.UseSqlite("Data Source=./UserDatabase.sqlite")); builder.Services.AddDbContext<UserDatabaseContext>(options => options.UseSqlite("Data Source=./UserDatabase.sqlite"));
builder.Services.AddDbContext<DeviceManagmentDatabaseContext>((options) => options.UseSqlite("Data Source=./DeviceManagement.sqlite")); builder.Services.AddDbContext<DeviceManagmentDatabaseContext>((options) => options.UseSqlite("Data Source=./DeviceManagement.sqlite"));
builder.Services.AddBootstrapProviders(); builder.Services.AddBootstrapProviders();
builder.Services.AddHttpClient<IDeviceManagmentService, DeviceManagmentService>(); builder.Services.AddHttpClient<IDeviceManagmentService, DeviceManagmentService>();
builder.Services.AddBlazorise(options => builder.Services.AddBlazorise(options =>
{ {
options.Immediate = true; options.Immediate = true;
}); });
AddBlazorise(builder.Services); AddBlazorise(builder.Services);
@@ -34,9 +38,9 @@ WebApplication app = builder.Build();
// Configure the HTTP request pipeline. // Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment()) if (!app.Environment.IsDevelopment())
{ {
_ = app.UseExceptionHandler("/Error"); _ = 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. // 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.UseHsts();
} }
app.UseHttpsRedirection(); app.UseHttpsRedirection();
@@ -46,12 +50,11 @@ app.UseStaticFiles();
app.UseRouting(); app.UseRouting();
app.MapBlazorHub(); app.MapBlazorHub();
app.MapFallbackToPage("/_Host"); app.MapFallbackToPage("/_Host");
app.MapControllers();
IServiceScope serviceScope = app.Services.GetRequiredService<IServiceScopeFactory>().CreateScope(); IServiceScope serviceScope = app.Services.GetRequiredService<IServiceScopeFactory>().CreateScope();
serviceScope.ServiceProvider.GetRequiredService<UserDatabaseContext>().Database.EnsureCreated(); serviceScope.ServiceProvider.GetRequiredService<UserDatabaseContext>().Database.EnsureCreated();
app.MapControllers();
AsyncServiceScope scope = app.Services.CreateAsyncScope(); AsyncServiceScope scope = app.Services.CreateAsyncScope();
scope.ServiceProvider.GetRequiredService<DeviceManagmentDatabaseContext>().Database.EnsureCreated(); scope.ServiceProvider.GetRequiredService<DeviceManagmentDatabaseContext>().Database.EnsureCreated();
@@ -59,7 +62,7 @@ app.Run();
void AddBlazorise(IServiceCollection services) void AddBlazorise(IServiceCollection services)
{ {
_ = services.AddBlazorise(); _ = services.AddBlazorise();
_ = services.AddMaterialProviders(); _ = services.AddMaterialProviders();
_ = services.AddMaterialIcons(); _ = services.AddMaterialIcons();
} }
@@ -7,7 +7,7 @@
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
}, },
"dotnetRunMessages": true, "dotnetRunMessages": true,
"applicationUrl": "https://localhost:7297;http://localhost:5255" "applicationUrl": "https://localhost:56656;http://localhost:5255"
}, },
"IIS Express": { "IIS Express": {
"commandName": "IISExpress", "commandName": "IISExpress",
@@ -8,9 +8,9 @@ namespace Elektrifikatsiya.Services;
public interface IDeviceManagmentService public interface IDeviceManagmentService
{ {
public Task<Result<Device>> Register(IPAddress ip, User user, string? name = null, string room = "default"); public Task<Result<Device>> RegisterDevice(IPAddress ip, User user, string? name = null, string room = "default");
public Task<Result> Unregister(string macAdress); public Task<Result> UnregisterDevice(string macAdress);
public Result<Device> GetDevice(string macAdress); public Result<Device> GetDevice(string macAdress);
@@ -19,4 +19,6 @@ public interface IDeviceManagmentService
public Result<List<Device>> GetDevicesInRoom(string room); public Result<List<Device>> GetDevicesInRoom(string room);
public Result<List<Device>> GetDevicesOfUser(int userId); public Result<List<Device>> GetDevicesOfUser(int userId);
public Task<Result> UpdateDevice(Device device);
} }
@@ -10,123 +10,138 @@ namespace Elektrifikatsiya.Services.Implementations;
public class DeviceManagmentService : IDeviceManagmentService public class DeviceManagmentService : IDeviceManagmentService
{ {
private readonly DeviceManagmentDatabaseContext deviceManagmentDatabaseContext; private readonly DeviceManagmentDatabaseContext deviceManagmentDatabaseContext;
private readonly IDeviceStatusService deviceStatusService; private readonly IServiceScopeFactory serviceScopeFactory;
private readonly IServiceScopeFactory serviceScopeFactory; private readonly IDeviceStatusService deviceStatusService;
private readonly HttpClient httpClient; private readonly HttpClient httpClient;
public DeviceManagmentService(IServiceScopeFactory serviceScopeFactory, IDeviceStatusService deviceStatusService, HttpClient httpClient) public DeviceManagmentService(IServiceScopeFactory serviceScopeFactory, IDeviceStatusService deviceStatusService, HttpClient httpClient)
{ {
this.deviceManagmentDatabaseContext = deviceManagmentDatabaseContext; this.serviceScopeFactory = serviceScopeFactory;
this.deviceStatusService = deviceStatusService; this.deviceStatusService = deviceStatusService;
this.httpClient = httpClient; this.httpClient = httpClient;
}
public Result<Device> GetDevice(string macAdress) }
{
Result<List<Device>> getDevicesResult = deviceStatusService.GetDevices();
if (getDevicesResult.IsFailed) public Result<Device> GetDevice(string macAdress)
{ {
return getDevicesResult.ToResult(); Result<List<Device>> getDevicesResult = deviceStatusService.GetDevices();
}
Device? device = getDevicesResult.Value.FirstOrDefault(d => d.MacAddress == macAdress); if (getDevicesResult.IsFailed)
{
return getDevicesResult.ToResult();
}
if (device is null) Device? device = getDevicesResult.Value.FirstOrDefault(d => d.MacAddress == macAdress);
{
return Result.Fail("Device does not exist!");
}
return device; if (device is null)
} {
return Result.Fail("Device does not exist!");
}
public Result<List<Device>> GetDevices() return device;
{ }
Result<List<Device>> getDevicesResult = deviceStatusService.GetDevices();
if (getDevicesResult.IsFailed) public Result<List<Device>> GetDevices()
{ {
return getDevicesResult.ToResult(); Result<List<Device>> getDevicesResult = deviceStatusService.GetDevices();
}
return getDevicesResult.Value.ToList(); if (getDevicesResult.IsFailed)
} {
return getDevicesResult.ToResult();
}
public Result<List<Device>> GetDevicesInRoom(string room) return getDevicesResult.Value.ToList();
{ }
Result<List<Device>> getDevicesResult = deviceStatusService.GetDevices();
if (getDevicesResult.IsFailed) public Result<List<Device>> GetDevicesInRoom(string room)
{ {
return getDevicesResult.ToResult(); Result<List<Device>> getDevicesResult = deviceStatusService.GetDevices();
}
return getDevicesResult.Value.Where(d => d.Room == room).ToList(); if (getDevicesResult.IsFailed)
} {
return getDevicesResult.ToResult();
}
public Result<List<Device>> GetDevicesOfUser(int userId) return getDevicesResult.Value.Where(d => d.Room == room).ToList();
{ }
Result<List<Device>> getDevicesResult = deviceStatusService.GetDevices();
if (getDevicesResult.IsFailed) public Result<List<Device>> GetDevicesOfUser(int userId)
{ {
return getDevicesResult.ToResult(); Result<List<Device>> getDevicesResult = deviceStatusService.GetDevices();
}
return getDevicesResult.Value.Where(d => d.User.Id == userId).ToList(); if (getDevicesResult.IsFailed)
} {
return getDevicesResult.ToResult();
}
public async Task<Result<Device>> Register(IPAddress ip, User user, string? name = null, string room = "default") return getDevicesResult.Value.Where(d => d.User.Id == userId).ToList();
{ }
using IServiceScope scope = serviceScopeFactory.CreateScope();
DeviceManagmentDatabaseContext deviceManagmentDatabaseContext = scope.ServiceProvider.GetRequiredService<DeviceManagmentDatabaseContext>();
ShellyResponse? shellyResponse = await httpClient.GetFromJsonAsync<ShellyResponse>($"{ip}/shelly"); public async Task<Result<Device>> RegisterDevice(IPAddress ip, User user, string? name = null, string room = "default")
{
using IServiceScope scope = serviceScopeFactory.CreateScope();
DeviceManagmentDatabaseContext deviceManagmentDatabaseContext = scope.ServiceProvider.GetRequiredService<DeviceManagmentDatabaseContext>();
if (shellyResponse is null || shellyResponse.Type != "SHPLG-S") ShellyResponse? shellyResponse = await httpClient.GetFromJsonAsync<ShellyResponse>($"http://{ip}/shelly");
{
return Result.Fail("Device is not a \"SHPLG-S\" or not reachable!");
}
string mac = shellyResponse.Mac; if (shellyResponse is null || shellyResponse.Type != "SHPLG-S")
{
return Result.Fail("Device is not reachable or not a \"SHPLG-S\"!");
}
if (PhysicalAddress.TryParse(mac, out _)) string mac = shellyResponse.Mac;
{
return Result.Fail("Invalid mac address!");
}
Device device = new Device(mac, name ?? mac, ip, user, room); if (!PhysicalAddress.TryParse(mac, out _))
{
return Result.Fail("Invalid mac address!");
}
deviceManagmentDatabaseContext.Add(device); Device device = new Device(mac, name ?? mac, ip, user, room);
Result saveDatabaseChangesResult = await Result.Try(async Task () => await deviceManagmentDatabaseContext.SaveChangesAsync());
Result trackDeviceResult = deviceStatusService.TrackDevice(device); deviceManagmentDatabaseContext.Add(device);
Result saveDatabaseChangesResult = await Result.Try(Task () => deviceManagmentDatabaseContext.SaveChangesAsync());
return Result.Merge(saveDatabaseChangesResult, trackDeviceResult).ToResult(device); Result trackDeviceResult = deviceStatusService.TrackDevice(device);
}
public async Task<Result> Unregister(string macAdress) return Result.Merge(saveDatabaseChangesResult, trackDeviceResult).ToResult(device);
{ }
using IServiceScope scope = serviceScopeFactory.CreateScope();
DeviceManagmentDatabaseContext deviceManagmentDatabaseContext = scope.ServiceProvider.GetRequiredService<DeviceManagmentDatabaseContext>(); public async Task<Result> UnregisterDevice(string macAdress)
{
using IServiceScope scope = serviceScopeFactory.CreateScope();
DeviceManagmentDatabaseContext deviceManagmentDatabaseContext = scope.ServiceProvider.GetRequiredService<DeviceManagmentDatabaseContext>();
Result<Device> getDeviceResult = GetDevice(macAdress); Result<Device> getDeviceResult = GetDevice(macAdress);
if (getDeviceResult.IsFailed) if (getDeviceResult.IsFailed)
{ {
return getDeviceResult.ToResult(); return getDeviceResult.ToResult();
} }
Result untrackDeviceResult = deviceStatusService.UntrackDevice(macAdress); Result untrackDeviceResult = deviceStatusService.UntrackDevice(macAdress);
if (untrackDeviceResult.IsFailed) if (untrackDeviceResult.IsFailed)
{ {
return untrackDeviceResult; return untrackDeviceResult;
} }
deviceManagmentDatabaseContext.Remove(getDeviceResult.Value); deviceManagmentDatabaseContext.Remove(getDeviceResult.Value);
return await Result.Try(async Task () => await deviceManagmentDatabaseContext.SaveChangesAsync()); return await Result.Try(Task () => deviceManagmentDatabaseContext.SaveChangesAsync());
} }
public async Task<Result> UpdateDevice(Device device)
{
Result result = deviceStatusService.UpdateDeviceStatus(device);
if (result.IsFailed)
{
return result;
}
deviceManagmentDatabaseContext.Update(device);
return await Result.Try(Task () => deviceManagmentDatabaseContext.SaveChangesAsync());
}
} }
@@ -6,10 +6,15 @@ namespace Elektrifikatsiya.Services.Implementations;
public class DeviceStatusService : IDeviceStatusService public class DeviceStatusService : IDeviceStatusService
{ {
public event EventHandler<DeviceStatusChagedEventArgs>? OnDeviceStatusChanged; private readonly ILogger<DeviceStatusService> logger;
public event EventHandler<DeviceStatusChagedEventArgs>? OnDeviceStatusChanged;
private readonly Dictionary<string, Device> devices = new(); private readonly Dictionary<string, Device> devices = new();
public DeviceStatusService(ILogger<DeviceStatusService> logger)
{
this.logger = logger;
}
public Result<List<Device>> GetDevices() public Result<List<Device>> GetDevices()
{ {
return devices.Values.ToList(); return devices.Values.ToList();
@@ -41,6 +46,8 @@ public class DeviceStatusService : IDeviceStatusService
return Result.Fail("Device already tracked!"); return Result.Fail("Device already tracked!");
} }
logger.LogInformation("Tracking new Device {device}", device.IpAddress);
devices[device.MacAddress] = device; devices[device.MacAddress] = device;
return Result.Ok(); return Result.Ok();
@@ -0,0 +1,20 @@
using System.Net;
using FluentResults;
using Tmds.MDns;
namespace Elektrifikatsiya.Utilities;
public static class MdnsDiscovery
{
public static event Action<IPAddress> OnDeviceFound;
static 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());
}
}