Add Ip- and Mac address

This commit is contained in:
Holger Börchers 2025-04-03 16:05:46 +02:00
parent 0e75065ff7
commit af503dc1bb
6 changed files with 121 additions and 17 deletions

View File

@ -2,9 +2,14 @@
x:Class="SddpViewer.App" x:Class="SddpViewer.App"
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:collections="clr-namespace:System.Collections;assembly=System.Runtime"
xmlns:sddpViewer="clr-namespace:SddpViewer"
RequestedThemeVariant="Default"> RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. --> <!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.Resources>
<sddpViewer:IpAddressComparer x:Key="IpAddressComparer" />
</Application.Resources>
<Application.Styles> <Application.Styles>
<FluentTheme /> <FluentTheme />
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml" /> <StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml" />

View File

@ -1,4 +1,5 @@
using System.Net; using System.Net;
using System.Net.NetworkInformation;
namespace SddpViewer; namespace SddpViewer;
@ -7,10 +8,11 @@ using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using Rssdp; using Rssdp;
public partial class DiscoveredDeviceViewModel : ObservableObject public partial class DiscoveredDeviceViewModel : ObservableObject, IDisposable
{ {
private readonly DiscoveredSsdpDevice _device; private readonly DiscoveredSsdpDevice _device;
private SsdpDevice? _ssdpDevice; private SsdpDevice? _ssdpDevice;
private readonly CancellationTokenSource _cancellationTokenSource = new();
public DiscoveredDeviceViewModel(DiscoveredSsdpDevice device) public DiscoveredDeviceViewModel(DiscoveredSsdpDevice device)
{ {
@ -20,19 +22,45 @@ public partial class DiscoveredDeviceViewModel : ObservableObject
IpAddress = EvaluateIpAddress(device); IpAddress = EvaluateIpAddress(device);
MacAddress = EvaluateMacAddress(); MacAddress = EvaluateMacAddress();
HostName = EvaluateHostName(); HostName = EvaluateHostName();
RunPing(_cancellationTokenSource.Token);
}
private async void RunPing(CancellationToken token)
{
try
{
using Ping ping = new();
while (!token.IsCancellationRequested)
{
var result = await ping.SendPingAsync(IpAddress, TimeSpan.FromSeconds(1), cancellationToken: token).ConfigureAwait(true);
Online = result.Status == IPStatus.Success;
await Task.Delay(1000, token).ConfigureAwait(true);
}
}
catch (TaskCanceledException e)
{
Console.WriteLine(e);
}
catch (OperationCanceledException e)
{
Console.WriteLine(e);
}
} }
public string HostName { get; set; } public string HostName { get; set; }
public string MacAddress { get; set; } public string MacAddress { get; set; }
public string IpAddress { get; set; } public IPAddress IpAddress { get; set; }
public DateTime DiscoveredAt { get; } public DateTime DiscoveredAt { get; }
[ObservableProperty] [ObservableProperty]
public partial string ResponseHeader { get; set; } public partial string ResponseHeader { get; set; }
[ObservableProperty]
public partial bool Online { get; set; }
/// <summary> /// <summary>
/// Sets or returns the type of notification, being either a uuid, device type, service type or upnp:rootdevice. /// Sets or returns the type of notification, being either a uuid, device type, service type or upnp:rootdevice.
/// </summary> /// </summary>
@ -73,6 +101,11 @@ public partial class DiscoveredDeviceViewModel : ObservableObject
[ObservableProperty] [ObservableProperty]
public partial string Version { get; set; } = ""; public partial string Version { get; set; } = "";
public void Cancel()
{
_cancellationTokenSource.Cancel();
}
public async Task GetFurtherInformationAsync() public async Task GetFurtherInformationAsync()
{ {
_ssdpDevice = await _device.GetDeviceInfo().ConfigureAwait(false); _ssdpDevice = await _device.GetDeviceInfo().ConfigureAwait(false);
@ -81,7 +114,7 @@ public partial class DiscoveredDeviceViewModel : ObservableObject
PresentationUrl = _ssdpDevice.PresentationUrl; PresentationUrl = _ssdpDevice.PresentationUrl;
ModelNumber = _ssdpDevice.ModelNumber; ModelNumber = _ssdpDevice.ModelNumber;
Version = _ssdpDevice.SerialNumber?.Split(',').Last() ?? new Version().ToString(); Version = _ssdpDevice.SerialNumber?.Split(',').Last() ?? new Version().ToString();
HttpClient client = new HttpClient(); using var client = new HttpClient();
var response = await client.GetAsync(_ssdpDevice.ModelUrl).ConfigureAwait(false); var response = await client.GetAsync(_ssdpDevice.ModelUrl).ConfigureAwait(false);
ResponseHeader = await response.Content.ReadAsStringAsync(); ResponseHeader = await response.Content.ReadAsStringAsync();
} }
@ -91,11 +124,12 @@ public partial class DiscoveredDeviceViewModel : ObservableObject
private string GetResponseHeader() => private string GetResponseHeader() =>
string.Join("," + Environment.NewLine, _device.ResponseHeaders.Select(x => $"{{{x.Key} : {string.Join(";", x.Value)}}}")); string.Join("," + Environment.NewLine, _device.ResponseHeaders.Select(x => $"{{{x.Key} : {string.Join(";", x.Value)}}}"));
private string EvaluateIpAddress(DiscoveredSsdpDevice device) => device.DescriptionLocation.Host; private IPAddress EvaluateIpAddress(DiscoveredSsdpDevice device) =>
IPAddress.TryParse(device.DescriptionLocation.Host, out var value) ? value : IPAddress.Any;
private string EvaluateMacAddress() private string EvaluateMacAddress()
{ {
var lookupResult = ArpLookup.Arp.Lookup(IPAddress.Parse(IpAddress)); var lookupResult = ArpLookup.Arp.Lookup(IpAddress);
return lookupResult is null ? "Unknown" : string.Join(":", lookupResult.GetAddressBytes().Select(b => $"{b:x2}")); return lookupResult is null ? "Unknown" : string.Join(":", lookupResult.GetAddressBytes().Select(b => $"{b:x2}"));
} }
@ -103,4 +137,10 @@ public partial class DiscoveredDeviceViewModel : ObservableObject
{ {
return obj is DiscoveredDeviceViewModel viewModel && Equals(viewModel.Usn, Usn); return obj is DiscoveredDeviceViewModel viewModel && Equals(viewModel.Usn, Usn);
} }
public void Dispose()
{
_cancellationTokenSource.Cancel();
_cancellationTokenSource.Dispose();
}
} }

View File

@ -1,3 +1,4 @@
// Global using directives // Global using directives
global using System.Collections;
global using Avalonia; global using Avalonia;

13
src/IpAddressComparer.cs Normal file
View File

@ -0,0 +1,13 @@
namespace SddpViewer;
public class IpAddressComparer : IComparer, IComparer<DiscoveredDeviceViewModel>
{
public int Compare(DiscoveredDeviceViewModel? x, DiscoveredDeviceViewModel? y)
{
var aByte = x?.IpAddress.GetAddressBytes().Sum(b => (long)b) ?? 0;
var bByte = y?.IpAddress.GetAddressBytes().Sum(b => (long)b) ?? 0;
return aByte.CompareTo(bByte);
}
public int Compare(object? x, object? y) => Compare(x as DiscoveredDeviceViewModel, y as DiscoveredDeviceViewModel);
}

View File

@ -43,12 +43,20 @@
Grid.Column="0" Grid.Column="0"
Grid.ColumnSpan="2" Grid.ColumnSpan="2"
AutoGenerateColumns="False" AutoGenerateColumns="False"
FrozenColumnCount="2"
IsReadOnly="True" IsReadOnly="True"
ItemsSource="{Binding SddpDevices}"> ItemsSource="{Binding SddpDevices}">
<DataGrid.Columns> <DataGrid.Columns>
<DataGridCheckBoxColumn
Width="50"
Binding="{Binding Online}"
IsReadOnly="True" />
<DataGridTextColumn Binding="{Binding FriendlyName}" Header="Name" /> <DataGridTextColumn Binding="{Binding FriendlyName}" Header="Name" />
<DataGridTextColumn Binding="{Binding Usn}" Header="Usn" /> <DataGridTextColumn Binding="{Binding Usn}" Header="Usn" />
<DataGridTextColumn Binding="{Binding IpAddress}" Header="Ip address" /> <DataGridTextColumn
Binding="{Binding IpAddress}" CanUserSort="True"
CustomSortComparer="{StaticResource IpAddressComparer}"
Header="Ip address" />
<DataGridTextColumn Binding="{Binding HostName}" Header="Hostname" /> <DataGridTextColumn Binding="{Binding HostName}" Header="Hostname" />
<DataGridTextColumn Binding="{Binding MacAddress}" Header="Mac address" /> <DataGridTextColumn Binding="{Binding MacAddress}" Header="Mac address" />
<DataGridTextColumn Binding="{Binding Version}" Header="Version" /> <DataGridTextColumn Binding="{Binding Version}" Header="Version" />

View File

@ -14,8 +14,8 @@ public sealed partial class MainWindowViewModel : ObservableObject, IDisposable
public MainWindowViewModel() public MainWindowViewModel()
{ {
NetworkAdapters = NetworkAdapter.GetAvailableNetworkAdapter().ToArray(); NetworkAdapters = NetworkAdapter.GetAvailableNetworkAdapter().ToArray();
SelectedNetworkAdapter = NetworkAdapters.FirstOrDefault(); SelectedNetworkAdapter = NetworkAdapters.FirstOrDefault(x => x.IpAddress.ToString() == DeviceIpAddress);
SddpDevices = new ObservableCollection<DiscoveredDeviceViewModel>(); SddpDevices = [];
SddpDevices.CollectionChanged += SddpDevices_OnCollectionChanged; SddpDevices.CollectionChanged += SddpDevices_OnCollectionChanged;
} }
@ -23,18 +23,55 @@ public sealed partial class MainWindowViewModel : ObservableObject, IDisposable
{ {
try try
{ {
foreach (object eNewItem in e.NewItems ?? Array.Empty<object>()) switch (e.Action)
{ {
if (eNewItem is DiscoveredDeviceViewModel discoveredDeviceViewModel) case NotifyCollectionChangedAction.Add:
{ await AddAction(e.NewItems);
await discoveredDeviceViewModel.GetFurtherInformationAsync(); break;
} case NotifyCollectionChangedAction.Remove:
RemoveAction(e.OldItems);
break;
case NotifyCollectionChangedAction.Replace:
RemoveAction(e.OldItems);
await AddAction(e.NewItems);
break;
case NotifyCollectionChangedAction.Reset:
RemoveAction(sender as ObservableCollection<DiscoveredDeviceViewModel>);
break;
case NotifyCollectionChangedAction.Move:
break;
default:
throw new ArgumentOutOfRangeException();
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
throw; // TODO handle exception throw; // TODO handle exception
} }
return;
}
private static void RemoveAction(IList? list)
{
foreach (object eOldItem in list ?? Array.Empty<object>())
{
if (eOldItem is DiscoveredDeviceViewModel discoveredDeviceViewModel)
{
discoveredDeviceViewModel.Dispose();
}
}
}
private static async Task AddAction(IList? list)
{
foreach (object eNewItem in list ?? Array.Empty<object>())
{
if (eNewItem is DiscoveredDeviceViewModel discoveredDeviceViewModel)
{
await discoveredDeviceViewModel.GetFurtherInformationAsync();
}
}
} }
private void LocatorOnDeviceUnavailable(object? sender, DeviceUnavailableEventArgs e) private void LocatorOnDeviceUnavailable(object? sender, DeviceUnavailableEventArgs e)
@ -58,16 +95,16 @@ public sealed partial class MainWindowViewModel : ObservableObject, IDisposable
} }
[ObservableProperty] [ObservableProperty]
private IReadOnlyList<NetworkAdapter> _networkAdapters; public partial IReadOnlyList<NetworkAdapter> NetworkAdapters { get; set; }
[ObservableProperty] [ObservableProperty]
private NetworkAdapter? _selectedNetworkAdapter; public partial NetworkAdapter? SelectedNetworkAdapter { get; set; }
[ObservableProperty] [ObservableProperty]
private string _deviceIpAddress = "192.168.42.193"; public partial string DeviceIpAddress { get; set; } = "192.168.42.192";
[ObservableProperty] [ObservableProperty]
private string _notificationFilter = "upnp:rootdevice"; public partial string NotificationFilter { get; set; } = "upnp:rootdevice";
private SsdpDeviceLocator? _locator; private SsdpDeviceLocator? _locator;