I managed to make a workaround. It is a bit hacked and doesn’t actually use the upnp protocol (since I couldn’t get this working at all on Windows 10).
Basically, we do the netsh command to get a list of devices and their addresses. Then we try to access setup.xml on different ports, which, as we know, can work on wemo devices. When we get the correct answer, it contains all the information about the upnp device, which we can parse into everything that we would like to know about the device. In this example, I simply simplified the parsing by simply checking to see if it contains the word "Belkin." This is some kind of work, but we have to parse the UDN to determine the model, and friendlyName to show the end user. Otherwise, other devices created by Belkin may appear.
This method works for me on a Windows 10 PC. I have not tried other platforms. This is a kind of brute force method, but all requests are executed async and in parallel, so we get the answer pretty quickly. Anyway, here is a class that does all the magic:
public class WemoScanner { public delegate void WemoDeviceFound(WemoDevice device); public event WemoDeviceFound OnWemoDeviceFound; public void StartScanning() { var addresses = GetAddresses().Where(a => a.Type == "Dynamic"); var tasks = addresses.SelectMany(CheckWemoDevice); Task.Run(async () => { await Task.WhenAll(tasks).ConfigureAwait(false); }); } public List<WemoDevice> GetWemoDevices() { var devices = new List<WemoDevice>(); OnWemoDeviceFound += device => devices.Add(device); var addresses = GetAddresses().Where(a => a.Type == "Dynamic"); var tasks = addresses.SelectMany(CheckWemoDevice); Task.WhenAll(tasks).GetAwaiter().GetResult(); return devices; } private NetshResult[] GetAddresses() { Process p = new Process(); p.StartInfo.FileName = "netsh.exe"; p.StartInfo.Arguments = "interface ip show ipnet"; p.StartInfo.UseShellExecute = false; p.StartInfo.RedirectStandardOutput = true; p.Start(); string output = p.StandardOutput.ReadToEnd(); var lines = output.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries) .SkipWhile(l => !l.StartsWith("--------")) .Skip(1); var results = lines.Select(l => new NetshResult(l.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries))) .ToArray(); return results; } private IEnumerable<Task> CheckWemoDevice(NetshResult netshResult) { var tasks = new List<Task>(); for (uint i = 49150; i <= 49156; i++) { tasks.Add(CheckWemoDevice(netshResult.IpAddress, i)); } return tasks; } private async Task CheckWemoDevice(string ip, uint port) { var url = $"http://{ip}:{port}/setup.xml"; try { using (var wc = new WebClient()) { var resp = await wc.DownloadStringTaskAsync(url); if (resp.Contains("Belkin"))
And several classes of models:
public class NetshResult { public NetshResult(string[] columns) { PhysicalAddress = columns[0]; IpAddress = columns[1]; Type = columns[2]; Interface = columns[3]; } public string PhysicalAddress { get; private set; } public string IpAddress { get; private set; } public string Type { get; private set; } public string Interface { get; private set; } } public class WemoDevice { public string Address { get; set; } public uint Port { get; set; } }
The use is pretty simple. If you want it to run asynchronously (in the background), just hook into the OnWemoDeviceFound event and call StartScanning() as follows:
var scanner = new WemoScanner(); scanner.OnWemoDeviceFound += device => Console.WriteLine($"{device.Address}:{device.Port}"); scanner.StartScanning();
If you need a method that works synchronously and returns a list of devices, just call GetWemoDevices() . Keep in mind that this is not recommended, as it will not be returned until a timeout occurs in all "invalid" requests. You can change the timeout for web requests if you want to reduce the time.