OpenPhonos UPnP
July 16, 2026 ยท View on GitHub
Introduction
This UPnP stack has only ever been used with Sonos devices. There's no known reason why it won't work on anything else, but I've never tried it.
Overview
To use UPnP you need a device on your network. First you need to find your devices, using SSDP to scan for them. Having found your devices, you can then query them for their services. This codebase uses pre-built types and methods, and you can find the Sonos ones in that project's Services folder. You can create your own types and methods using the included UPNPServiceCreator.
Initialization
This codebase has been run on many different platforms and UX frameworks. To handle the differences, you need to provide an implementation of the IPlatform interface and set the class up before using it. The sample app includes the AvaloniaPlatform class. It's not complicated.
OpenPhonos.UPnP.Platform.Instance = new MyPlatform(); // MyPlatform must derive from IPlatform
UPnPConfig.ListenerName = "MyGreatApp"; // this is used on NOTIFY events
UPnPConfig.UserAgent = "Me UPnP/1.0 MyApp MyOS"; // this is used on all calls to the devices
Device Discovery
string TestUrn = "urn:schemas-upnp-org:device:ZonePlayer:1";
var finder = new Finder();
var devices = new List<Device>();
await finder.ByURNAsync(TestUrn, async (location, network, headers) =>
{
// This delegate can be called on any thread, don't block it
var device = await Device.CreateAsync(location);
lock (devices)
{
Console.WriteLine("Found Device at {0}", location);
devices.Add(device);
}
return await Task.FromResult(true); // carry on searching
});
Services
A Device isn't much good on its own, you need to get the services from it. Here's how you can get the services from a device (using one of the Sonos pre-built classes):
var info = device.FindServiceInfo(TestUrn, "urn:upnp-org:serviceId:ContentDirectory", throwIfMissing: false);
if (info != null)
{
var service = new SonosServices.ContentDirectory1(info);
Methods (aka UPnP Actions)
Now you have a service, you can make calls on it. All calls are async, but I did not append that to the method names, contrary to general guidelines and the rest of this project. Here's an example of calling a method on a service:
// assume service is a ContentDirectory1
var response = await service.GetSystemUpdateID();
The return value for all of these methods is derived from the OpenPhonos.UPnP.Service.ActionResult class. Note that these calls do not throw exceptions by default. Over the years I have found that I often don't care if many of these calls fail, they are 'fire and forget', so I chose an unusual calling pattern. The return value includes an Error property, which is null if the call was successful. If it is not null, it will contain a UPnPException which you can throw if you want to.
var response = await service.GetSystemUpdateID();
if (response.Error != null)
{
throw response.Error;
}
// alternate pattern:
response.ThrowIfFailed();
In more recent code I have been using some extension methods, to make it clearer if the call will throw, or not.
// assume service is an AVTransport1
var info = await service.GetMediaInfo(0).Optional(); // failure is ok to ignore
var response = await service.Play(0, "1").Required(); // failure is not ok
You can see real examples of all of this in the UPnP.Test project.
UPnP Events
You can subscribe to UPnP events to receive change notifications from the services. When you are finished you should unsubscribe from them. The library looks after renewing the event, until you unsubscribe. For mobile devices, you should also unsubscribe when your app is about to be suspended, else the UPnP device will likely stop sending you events as you cannot respond to them when in a suspended state. The Listener class can also handle the bulk-unsubscription/resubscription during suspend/resume, but you need to call it at the right moment.
// assume service is an AVTransport1, pass a timeout, a debug-string, and a handler
await service.SubscribeAsync(Listener.MinimumTimeout, "AVT", AVTransportHandler);
...
private async Task AVTransportHandler(Service sender, EventSubscriptionArgs args)
{
// this is called on the UI thread, as determined by IPlatform.OnUIThread
if (!args.Items.TryGetValue("LastChange", out string? lastchange))
{
return;
}
Debug.WriteLine("AVT-LastChange");
var xml = XElement.Parse(lastchange);
await RefreshAVTransportAsync(xml);
}
// when you are done
await service.UnsubscribeAsync();
// before you exit, or your app is suspended
await Listener.OnSuspendAllAsync();
// when you come back after being suspended
Listener.OnResumeAll();
UPnPServiceGenerator
To use with your own UPnP devices, or perhaps to update the service descriptions in the provided Sonos project, you can run this tool. Give it the url of one of your devices, and it will create the types and methods for you.