Thursday, September 18, 2014

UPnP and Python

I guess that you already know what UPnP is. Let's see how we can write some python code which we can use to control some UPnP device. First we need to discover device and services provided by it, for this we will use gssdp-discover -n 10 gssdp-discover is part of the gupnp-tools package.
gssdp-discover -n 10
We will see something like this:
resource available
  USN:      uuid:03166842-2743-2000-0000-7c1e52c26ca7::upnp:rootdevice
  Location: http://10.0.0.9:1027/
resource available
  USN:      uuid:03166842-2743-2000-0000-7c1e52c26ca7::urn:schemas-upnp-org:device:MediaRenderer:1
  Location: http://10.0.0.9:1027/
resource available
  USN:      uuid:03166842-2743-2000-0000-7c1e52c26ca7
  Location: http://10.0.0.9:1027/
resource available
  USN:      uuid:03166842-2743-2000-0000-7c1e52c26ca7::urn:schemas-upnp-org:service:AVTransport:1
  Location: http://10.0.0.9:1027/
resource available
  USN:      uuid:03166842-2743-2000-0000-7c1e52c26ca7::urn:schemas-upnp-org:service:ConnectionManager:1
  Location: http://10.0.0.9:1027/
resource available
  USN:      uuid:03166842-2743-2000-0000-7c1e52c26ca7::urn:schemas-upnp-org:service:RenderingControl:1
  Location: http://10.0.0.9:1027/
As you can see this device provides MediaRenderer, AVTransport, ConnectionManager and RenderingControl. This means we can play media on this device and control it (Play, Pause, Stop, Volume Up, ...) We know that UPnP is basically SOAP so we first need to create header,
POST control URL HTTP/1.1
HOST: host:port
CONTENT-LENGTH: bytes in body
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:serviceType:v#actionName”
As you can see above we will need to find control URL, host:port (this we already know from gssdp-discovery) and action we want to perform. If we use any browser and open url in give by Location (from gssdp-discovery) we will get XML with basic information about device plus services provided. There is tag ServiceList which contains info about services for each we will need controlURL and SCPDURL, SCPDURL is path xml definition of function provided by the service. Bellow we can see example of service. So we can fin out that control URL we need is /AVTrasnport/control and definition of actions we will find in host:port/AVTransport/scpd.xml
<service>
  <serviceType>urn:schemas-upnp-org:service:AVTransport:1</serviceType>
  <serviceId>urn:upnp-org:serviceId:AVTransport_1F88B77A-8236-49b9-B344-974969412930</serviceId>
  <SCPDURL>AVTransport/scpd.xml</SCPDURL>
  <controlURL>AVTransport/control</controlURL>
  <eventSubURL>AVTransport/event</eventSubURL>
</service>
Now we will open http://host:port/AVTransport/scpd.xml and find action we want to perform, bellow we can see example of such action definitoin, we will need name and argument with direction in, direction out is actually what we will get in response.
<action>
  <name>GetMediaInfo</name>
   <argumentlist>
     <argument>
       <name>InstanceID</name>
       <direction>in</direction>
       <relatedstatevariable>A_ARG_TYPE_InstanceID</relatedstatevariable>
     </argument>
     <argument>
       <name>NrTracks</name>
       <direction>out</direction>
       <relatedstatevariable>NumberOfTracks</relatedstatevariable>
     </argument>
     <argument>
       <name>MediaDuration</name>
       <direction>out</direction>
       <relatedstatevariable>CurrentMediaDuration</relatedstatevariable>
     </argument>
     <argument>
       <name>CurrentURI</name>
       <direction>out</direction>
       <relatedstatevariable>AVTransportURI</relatedstatevariable>
     </argument>
     ...
  </argumentlist>
</action>
In our case action name is GetMediaInfo and argument is InstanceID, this instanceID as we can further see is related to variable A_ARG_TYPE_InstanceID if we search further in the definition xml we find this statevariable and its default value.
<statevariable sendevents="no">
  <name>A_ARG_TYPE_InstanceID</name>
  <datatype>ui4</datatype>
  <defaultvalue>0</defaultvalue>
</statevariable>
SOAP Message should look like this:
<s:Envelope xmlns: s=“http://schemas.xmlsoap.org/soap/envelope” s:encodingStyle=“http://schemas.xmlsoap.org/soap/encoding”>
<s:Body>
    <u:actionName xmlns:u="urn:schemas-upnp-org:service:serviceType:v">
        <argumentName>in arg value</argumentName>       
   </u:actionName>
</s:Body>
</s:Envelope>
As we already know all we need we will create testUPnP.py script in python which will take action as argument
import sys
import httplib, urllib
import re

action = sys.argv[1]

conn = httplib.HTTPConnection("10.0.0.9:1027")
soap_data = '<s:Envelope xmlns: s="http://schemas.xmlsoap.org/soap/envelope" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding"><s:Body><u:'+action+' xmlns:u="urn:schemas-upnp-org:service:AVTransport:1"><InstanceID>0</InstanceID></u:'+action+'></s:Body></s:Envelope>'

params = urllib.urlencode({'q': 'set'})
headers = { "Content-Type": "text/xml", "Content-Length": "%d" % len(soap_data), "SOAPACTION": "urn:schemas-upnp-org:service:AVTransport:1#"+action+'"' }

conn.request("POST", "/AVTransport/control", "", headers)
conn.send(soap_data)

response = conn.getresponse()

#FOR DEBUG
print response.status, response.reason
print response.read()

No comments: