Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Find Belkin WeMo devices with UPnP.FindByType

Tags:

c#

upnp

I'm trying to create a C# app for controlling WeMo devices. I've followed the code samples and directions given by Bernacules here.

The relevant bit of code is pasted below.

private void GetAllDevices()
{
    var finder = new UPnPDeviceFinder();
    var foundDevices = new List<UPnPDevice>();

    var deviceType = "upnp:rootdevice";
    var devices = finder.FindByType(deviceType, 1);

    foreach (UPnPDevice upnpDevice in devices)
    {
        if (upnpDevice.Type.StartsWith("urn:Belkin:"))
        {
            ....
        }
    }
}

When I iterate the devices found by UPnPDeviceFinder, I come up with all sorts of things, my media server, some app running on my Android phone, my Chromecast, but no WeMos, nothing that even remotely had Belkin in the urn.

After a bit more Googling, I found this article which allowed me to figure out how to query the 'setup.xml' for each device. By replacing the "upnp:rootdevice" in the code above with the value from <DeviceType> in the xml (I believe it was "urn:Belkin:device:controllee:1"), I came up with 0 results from the UPnPDeviceFinder. I'm guessing Belkin has recently changed their configuration, but can someone with a bit more network saaviness clue me in on what value I can use for 'deviceType' which will find my WeMos?

like image 517
Duck Jones Avatar asked Aug 22 '14 17:08

Duck Jones


2 Answers

What version of windows are you using?

My UPnP scanner finds the Wemo just fine on Windows 7 but it doesn't work on Windows 8.1

On Windows 8.1 all my other UPnP devices show up, but the Wemo does not appear at all.

like image 76
user3590552 Avatar answered Oct 16 '22 09:10

user3590552


I managed to make a workaround. It's a little hacky, and doesn't really use the upnp protocol (as I was unable to get that working at all on Windows 10).

Basically, what we're doing is making use of a netsh command to get a list of devices, and their addresses. Then we try to access the setup.xml on the different ports that we know the wemo devices can run on. When we get a proper response, it contains all the upnp device info, which we can then parse for anything we'd like to know about the device. In this sample, I just made a simplified parsing, just checking if it contains the word "Belkin". It does kind of work, but we should be parsing the UDN to determine model, and friendlyName to show to the end-user. Else, other devices made by Belkin might show up.

This method does work for me on a Windows 10 pc. I have not tried other platforms. It's kind of a brute-force method, but all requests are made async and in parallel, so we get a response rather quickly. Anyway, here's the 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")) // TODO: Parse upnp device data and determine device
                    OnWemoDeviceFound?.Invoke(new WemoDevice {Address = ip, Port = port});
            }
        }
        catch (Exception)
        {

        }
    }
}

And a few model classes:

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; }
}

Usage is fairly simple. If you want it to run in an async manner (in the background), simply hook up to event OnWemoDeviceFound and call StartScanning() like so:

var scanner = new WemoScanner();
scanner.OnWemoDeviceFound += device => Console.WriteLine($"{device.Address}:{device.Port}");
scanner.StartScanning();

Else, if you want a method that runs synchronously and returns a list of devices, just call GetWemoDevices(). Mind you, that this isn't really recommended, as it will not return until a timeout has happened on all the "invalid" requests. You could modify the timeout for the web requests if you wanted to make this time shorter.

like image 4
Inrego Avatar answered Oct 16 '22 08:10

Inrego