This document describes a Python UPnP control interface based on libupnpp.

libupnpp does a lot of work to translate the data from well-known services to C++ natural data structures. However, the full C++ API has many quirks which would make it complicated to use with Swig and would need quite a lot of additional glue code.

So the initial Python interface is a simpler string-based one, where the module mostly takes care of discovery and SOAP encoding, but the Python program must deal with some of the data decoding (for example, parsing the XML in AVTransport events).

However, the Python module does have support for parsing Content Directory data (XML DIDL format), which is probably the most common need.

As it is, the interface makes it easy to write quick programs to interface with UPnP devices, querying their state, requesting changes, and receiving events.

You will need the git version of libupnpp (0.16 branch) for building the Python interface.

Discovery

The Discovery interface is designed for accessing services, and is provided by a single function:

import upnpp

service = upnpp.findTypedService(devname, servicename, fuzzy)

devname defines the device and can be provided either as an UUID or as a case-insensitive friendly name.

servicename can be provided either as an exact service string (e.g. urn:schemas-upnp-org:service:AVTransport:1), or, if fuzzy is True as a case-insensitive substring (e.g. avtransport).

The returned value is None if the device/service is not found.

There is no provision for listing the device directory for now.

Actions

Once connected to a service, its runAction method allows calling one of its actions.

See the samples/getmedinfo.py sample script for a working example.

import upnpp

args = upnpp.VectorString()
retdata = upnpp.MapStringString()

action = "SomeAction"

args.append("arg1")
args.append("arg2")

ret = srv.runAction(action, args, retdata)

if ret:
    print("%s failed with %d" % (action, ret))
else:
    if len(retdata) != 0:
        for nm, val in retdata.iteritems():
            print("    %s : %s" % (nm, val))

action is the action name (e.g. GetMediaInfo).

args is an array of strings holding the action parameters, in the order prescribed by the service definition.

retdata is a dictionary for the return values.

The strange VectorString and MapStringString types are due to the direct mapping between Python and the C++ interface. Of course, it would be possible to provide another layer for translating from natural Python types to these.

The method returns 0 for success or an integer pupnp error code.

Events

The module allows subscribing to a service’s events.

import time
import upnpp

srv = upnpp.findTypedService(friendlyname, fuzzyservicename, True)

class EventReporter:
   def upnp_event(self, nm, value):
      print("%s -> %s" % (nm, value))

reporter = EventReporter()
# You do need to store the result of installReporter
bridge = upnpp.installReporter(srv, reporter)

while True:
   time.sleep(20000)

See the events.py sample.

Unfortunately, the libupnpp C++ service class has no interface suitable for doing this directly from Python, so a bridge class was defined to provide the translation.

You need to define a class with an upnp_event() method which is the user callback, create an instance, and subscribe to events by calling upnpp.installReporter(), which returns an object which you need to store, until you want to unsubscribe from the service events.

Calling installReporter() from an EventReporter method and storing the result in the object has the consequence that the EventReporter object (and the bridge object) will not be automatically deleted because the bridge holds a reference to the user object. If you want to do this, you need to explicitly delete the bridge object for unsubscribing. See the events.py sample for examples of the two approaches and more explanation.

This is quite unnatural, and I’d be glad to take hints from a Swig/Python master on the subject… However, it works.

Data parsers

Content Directory records

UPnP accepts and outputs track metadata in an XML format named DIDL lite.

The Python wrapper gives access to the functions from the cdirobject.hxx libupnpp module, which can translate from the XML format.

The main class is upnpp.UPnPDirContent, which performs the parsing, and has vector members for items and containers entries.

An example follows, taken from the getmedinfo.py sample, accessing the current metadata from a GetMediaInfo command. For this command, if CurrentURIMetaData is set, it is the metadata for the currently playing track, and there will be a single item, from which we extract the title and properties, then the details from the resource entry (which describe the actual format details).

Refer to the comments in cdircontent.hxx for more details on the data structures, which are just reflected in the Python objects.

import upnpp
srv = upnpp.findTypedService(devname, "avtransport", True)
args = upnpp.VectorString()
retdata = upnpp.MapStringString()
args.append("0")
runaction(srv, "GetMediaInfo", args, retdata)

metadata = retdata["CurrentURIMetaData"]
if metadata:
   print("\nParsed metadata:")

   dirc = upnpp.UPnPDirContent()
   dirc.parse(metadata)

   if dirc.m_items.size():
      dirobj = dirc.m_items[0]
      print("  title: %s "% dirobj.m_title)
      for nm, val in dirobj.m_props.iteritems():
         print("  %s : %s" % (nm, val))

      resources = dirobj.m_resources
      if len(resources):
         print("Resource object details:")
         for nm, val in resources[0].m_props.iteritems():
            print("  %s : %s" % (nm, val))

Building and installing

There are no packages or releases for now, you need to clone the Git repositories for libupnpp and libupnpp-bindings

Dependencies:

  • Python 2 development package

  • autotools (autoconf/automake/libtool)

  • libupnpp (0.16 branch from git at this moment).

  • Swig (at least 2.0 I think).

git clone https://medoc@opensourceprojects.eu/git/p/libupnpp/code libupnpp
cd libupnpp/
git checkout 0.16
sh autogen.sh
configure --prefix=/usr
make
sudo make install

cd ..
git clone https://medoc@opensourceprojects.eu/git/p/libupnppbinding/code libupnpp-bindings
cd libupnpp-bindings
sh autogen.sh
configure --prefix=/usr
make
sudo make install

There are a number of small example scripts in the samples/ directory to try things out.